embedded-phy

Ethernet网口通常由CPU、MAC和PHY三部分组成,通常用DMA控制器参与网口数据传输,以减轻CPU的负担。有的CPU内部集成MAC控制器,有的则采用外置的MAC芯片。

MII(Media Independent Interface 媒体独立接口)是IEEE 802.3标准定义的以太网行业标准。它包括一个数据接口,以及一个MAC和PHY之间的管理接口。MII数据接口包括分别用于发送器和接收器的两条独立信道,每条信道拥有数据、时钟和控制信号,共16条信号线。MII管理接口即MDIO接口包含一个时钟信号(MDC)和一个数据信号(MDIO),通过管理接口,上层能监视和控制PHY。根据IEEE802.3标准定义,MII管理接口最多支持同时管理32个PHY。

CPU/MAC通过MDIO接口(类似于I2C接口,因此PHY拥有PHY ADDR)管理PHY芯片。

MAC和PHY之间的数据传输接口类型有MII/RMII(Reduced MII)/SMII(Stream MII)/GMII几种,根据传输速率(10M/100M/1000M)又有不同细分,比如千兆GMII/RGMII/SGMII。

GMII是千兆网的MII接口,这个也有相应的RGMII接口,表示简化了的GMII接口。

20211208_134157.png

MDIO

针对linux设备-总线-驱动模型而言,其提供了device_register/driver_register、device_unregister/driver_unregister方法,用于进行设备与驱动绑定与解绑操作,从而实现设备的探测与移除操作。

20211208_134254.png

按照OSI七层协议,MAC工作在数据链路层,PHY工作在物理层。在Linux网络子系统中,IP层、TCP/UDP层及其上的应用层均为软件实现,由内核协议栈和用户态socket代码实现。

20211208_134438.png

网卡驱动的初始化流程可以归纳为:
1)为网络数据收发分配内存(或者更进一步配置DMA直接内存存取)
2)初始化MDIO控制器和MAC控制器
3)通过MDIO接口初始化PHY控制器
4)注册内核网络协议栈接口
5)启动收发

20211208_134544.png

linux 下phy物理地址定义

Kernel下面的phy驱动
kernel/linux-3.0.y/drivers/net/stmmac/stmmac_mdio.c

3531有个stmmac.ko 驱动,默认是自适应模式,放在文件系统的
/hitoe/stmmac.ko
phy addr 物理地址是在 drivers/net/stmmac/Kconfig 文件里面定义的

查看kernel 配置找配置 phy address的地方,看到网络的kconfig有配置
又发现godnet 的kconfig也有配置,最后在kernel的配置菜单里面配置了,然后更新stmmac.ko模块就可以了

驱动调试
首先确认 Configuration Strapping Pins 这个是配置硬件接口方式的地方。
是芯片上电/复位后的默认配置过程,使得PHY芯片在上电/复位结束后能进入期望的工作状态,比如芯片的PHY地址,传输线路速率,是否速率自动协商,双工模式等。

ping 命令数据流向是 cpu 处理命令,通过 CPU 的 GMAC 口发出去,可以看 PHY 端 RGMII 口 MIB 的 ifOutOctets 计数器(发出多少字节),然后在通过 PHY 的 RGMII 接口交给 PHY 通过 PHY 的 UTP 口发出去,可以看 UTP 口MIB 的 ifOutOctets 计数器(发出多少字节)。

需要注意的地方就是当 ping 不通时,先确认 cpu 端的 GMAC 配置要和 phy 端的 mac 口配置保持一致,比如:都是1000M或者都是100M,然后去掉中间环节,直连电脑和板子,避免因为路由的原因导致不通。

20211206_141918.png

首先调试检查硬件,再调试软件
仔细阅读phy和主芯片datesheet ,尤其着重阅读mii寄存器,它是mac和phy交换信息的媒介。
硬件注意:关于phy的芯片需要注意的有几点:

  1. mdio接口和rgmii或者mii接口是否正确。
  2. 电源是否稳定,晶振是否起振并且频率匹配。
  3. phy地址的配置是否正确。
  4. PHY是否有延时电阻(RTL8211有)
  5. phy芯片输出时钟是否正确。(速率1000M clk 125M,100M clk 25M,10M clk 2.5M)
  6. phy和主芯片rgmii接口电平是否匹配。
  7. phy间的线序是否正确以及线路是否完好。
  8. mac和phy间的数据速率是否匹配即都是千兆百兆或10兆。
    关于主芯片mac部分接口注意:
  9. rgmii或者mii以及mdio接口是否正确。
  10. mdio时序是否正确。
  11. rgmii时钟是否正确。

二、软件确认
1、uboot涉及驱动:drivers/net/higmacv300,PHY配置驱动:drivers/net/phy/realtek.c
2、先看mdio能否扫描到phy,读出的phy id要和phy datasheet一致;同时,读出phyid的addr需要和上面硬件的phy_add一致
3、如果第二步是正确的,那么说明mdio接口和phy已经工作;接下来要检查rgmii接口
4、将板子和笔记本直连:GMAC<—>phy<—>pc
板子ping PC 以及PC ping板子,然后读GMAC和phy的寄存器(读GMAC要自己写,读phy有个mii命令)
可以查看GMAC收发了多少数据,PHY收发了多少数据,从而判断是GMAC发给PHY异常还是PHY发给PC异常,或GMAC接收异常还是PHY接收异常
查看 phy 的 0 4 9 寄存器是否正确
5、到这一步还是不行,就需要自己写回环测试代码:
第一个是GMAC回环:MAC_IF_STAT_CTRL寄存器
第二个PHY回环:BMCR寄存器
6、uboot遇到时通时不通时,将uboot强制成100M速率试试
千兆ping不通时:
看一下rgmii的clock出来没有
外面发播报进来data上有没有波形
如果有波形,再调一下rgmii delay试一下

海思芯片 3531

3531有个stmmac.ko 驱动,默认是自适应模式,放在文件系统的
/hitoe/stmmac.ko

phy 的物理地址是在 kernel 的 drivers/net/stmmac/Kconfig 文件里面定义的

cp arch/arm/configs/godnet_defconfig .config
make ARCH=arm CROSS_COMPILE=arm-hisiv100nptl-linux- clean
make ARCH=arm CROSS_COMPILE=arm-hisiv100nptl-linux- menuconfig
make ARCH=arm CROSS_COMPILE=arm-hisiv100nptl-linux- uImage
; 生成 ko 驱动模块
make ARCH=arm CROSS_COMPILE=arm-hisiv100nptl-linux- modules

修改 phy 地址,uboot和kernel下

uboot 在 include/configs/godnet.h STMMAC driver 里面定义的
kernel 在配置菜单里面 网络驱动-phy驱动-1000M 里面有定义
更新的时候 编译 stmmac.ko 模块就可以了

MDIO驱动

使用 RTL8363NB 举例

// 首先定义宏打开 MDIO 配置
#define MDC_MDIO_OPERATION

// 实现相关接口
/* MDC/MDIO, redefine/implement the following Macro */
#define MDC_MDIO_WRITE(preamableLength, phyID, regID, data)
#define MDC_MDIO_READ(preamableLength, phyID, regID, pData)


static void rtlglue_drvMutexLock(void)
{
/* It is empty currently. Implement this function if Lock/Unlock function is needed */
return;
}

static void rtlglue_drvMutexUnlock(void)
{
/* It is empty currently. Implement this function if Lock/Unlock function is needed */
return;
}

MIB信息

// send ping and get MIB info
/*

ifInOctets COUNTER
The total number of octets received on the interface,
including framing characters.

Discontinuities in the value of this counter can occur at
re-initialization of the management system, and at other
times as indicated by the value of
ifCounterDiscontinuityTime.

ifInOctets 是 0 , 说明 RGMII 没收到,不同的rxdelay都读看看,看一下port16的ifInOctets有没有计数
如果都不行,看一下cpu能不能加txdelay再看看

ifOutOctets COUNTER
The total number of octets transmitted out of the
interface, including framing characters.

Discontinuities in the value of this counter can occur at
re-initialization of the management system, and at other
times as indicated by the value of
ifCounterDiscontinuityTime.

*/
void ping_mib()
{

const char *APIMIBString[] =
{
"ifInOctets",

"dot3StatsFCSErrors",

"dot3StatsSymbolErrors",

"dot3InPauseFrames",

"dot3ControlInUnknownOpcodes",

"etherStatsFragments",

"etherStatsJabbers",

"ifInUcastPkts",

"etherStatsDropEvents",

"etherStatsOctets",

"etherStatsUndersizePkts",

"etherOversizeStats",

"etherStatsPkts64Octets",

"etherStatsPkts65to127Octets",

"etherStatsPkts128to255Octets",

"etherStatsPkts256to511Octets",

"etherStatsPkts512to1023Octets",

"etherStatsPkts1024to1518Octets",

"etherStatsMulticastPkts",

"etherStatsBroadcastPkts",

"ifOutOctets",

"dot3StatsSingleCollisionFrames",

"dot3StatsMultipleCollisionFrames",

"dot3StatsDeferredTransmissions",

"dot3StatsLateCollisions",

"etherStatsCollisions",

"dot3StatsExcessiveCollisions",

"dot3OutPauseFrames",

"dot1dBasePortDelayExceededDiscards",

"dot1dTpPortInDiscards",

"ifOutUcastPkts",

"ifOutMulticastPkts",

"ifOutBroadcastPkts",

"outOampduPkts",

"inOampduPkts",

"pktgenPkts",

"inMldChecksumError",

"inIgmpChecksumError",

"inMldSpecificQuery",

"inMldGeneralQuery",

"inIgmpSpecificQuery",

"inIgmpGeneralQuery",

"inMldLeaves",

"inIgmpInterfaceLeaves",

"inIgmpJoinsSuccess",

"inIgmpJoinsFail",

"inMldJoinsSuccess",

"inMldJoinsFail",

"inReportSuppressionDrop",

"inLeaveSuppressionDrop",

"outIgmpReports",

"outIgmpLeaves",

"outIgmpGeneralQuery",

"outIgmpSpecificQuery",

"outMldReports",

"outMldLeaves",

"outMldGeneralQuery",

"outMldSpecificQuery",

"inKnownMulticastPkts",

"ifInMulticastPkts",

"ifInBroadcastPkts"

};

const char * strtmpArr[] ={"ping","192.168.0.107"};

printf("before rtk_stat_global_reset... \n");
rtk_api_ret_t retVal = rtk_stat_global_reset();
if ( RT_ERR_OK != retVal )
{
printf("rtk_stat_global_reset failed.retVal=%x ===================== \n\n",retVal);
}else
{
printf("read gmac register before after ping STMMAC_GMACADDR=%x============= \n",STMMAC_GMACADDR);
retVal = HW_REG(STMMAC_GMACADDR);
printf("GMACN_CTRL_REG =%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x1014);
printf("DMAP_STATUS=%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x10C4);
printf("GMACN_MMC_RXUNICASTFRAMES_G=%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x016C);
printf("GMACN_MMC_TXEXCESSDEF =%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x0168);
printf("GMACN_MMC_TXFRAMECOUNT_G =%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x0010);
printf("GMACN_GMII_ADDR =%x\n",retVal);

int reg = HW_REG(0x20030000 + 0xec);
printf("PERI_CRG59 + 0xec,reg=%x ========= \n",reg);
reg = HW_REG(0x20030000 + 0x00F0);
printf("PERI_CRG60 0xF0,reg=%x ========= \n",reg);
reg = HW_REG(0x20050000 + 0x00E8);
printf("PERIPHCTRL45 0xE8,reg=%x ========= \n",reg);

cmd_tbl_t ctt;
int i = 0;
for (;i< 1;++i)
{
printf("before ping ...i=%x \n",i);
do_ping(&ctt,0,2,strtmpArr);
msleep (1000);
}

int port = UTP_PORT3;
rtk_stat_port_cntr_t portCntrs;
int portArr[] = {UTP_PORT3,EXT_PORT0};
i = 0;
for (;i<2;++i)
{
memset(&portCntrs,0,sizeof(rtk_stat_port_cntr_t));
port = portArr[i];
retVal = rtk_stat_port_getAll(port, &portCntrs);
if(RT_ERR_OK != retVal)
{
printf("rtk_stat_port_getAll failed.retVal=%x ===================== \n\n",retVal);
}else
{
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[0],0,portCntrs.ifInOctets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[1],1,portCntrs.dot3StatsFCSErrors);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[2],2,portCntrs.dot3StatsSymbolErrors);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[3],3,portCntrs.dot3InPauseFrames);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[4],4,portCntrs.dot3ControlInUnknownOpcodes);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[5],5,portCntrs.etherStatsFragments);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[6],6,portCntrs.etherStatsJabbers);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[7],7,portCntrs.ifInUcastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[8],8,portCntrs.etherStatsDropEvents);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[9],9,portCntrs.etherStatsOctets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[10],10,portCntrs.etherStatsUndersizePkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[11],11,portCntrs.etherStatsOversizePkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[12],12,portCntrs.etherStatsPkts64Octets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[13],13,portCntrs.etherStatsPkts65to127Octets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[14],14,portCntrs.etherStatsPkts128to255Octets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[15],15,portCntrs.etherStatsPkts256to511Octets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[16],16,portCntrs.etherStatsPkts512to1023Octets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[17],17,portCntrs.etherStatsPkts1024toMaxOctets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[18],18,portCntrs.etherStatsMcastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[19],19,portCntrs.etherStatsBcastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[20],20,portCntrs.ifOutOctets);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[21],21,portCntrs.dot3StatsSingleCollisionFrames);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[22],22,portCntrs.dot3StatsMultipleCollisionFrames);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[23],23,portCntrs.dot3StatsDeferredTransmissions);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[24],24,portCntrs.dot3StatsLateCollisions);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[25],25,portCntrs.etherStatsCollisions);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[26],26,portCntrs.dot3StatsExcessiveCollisions);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[27],27,portCntrs.dot3OutPauseFrames);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[28],28,portCntrs.dot1dBasePortDelayExceededDiscards);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[29],29,portCntrs.dot1dTpPortInDiscards);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[30],30,portCntrs.ifOutUcastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[31],31,portCntrs.ifOutMulticastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[32],32,portCntrs.ifOutBrocastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[33],33,portCntrs.outOampduPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[34],34,portCntrs.inOampduPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[35],35,portCntrs.pktgenPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[36],36,portCntrs.inMldChecksumError);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[37],37,portCntrs.inIgmpChecksumError);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[38],38,portCntrs.inMldSpecificQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[39],39,portCntrs.inMldGeneralQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[40],40,portCntrs.inIgmpSpecificQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[41],41,portCntrs.inIgmpGeneralQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[42],42,portCntrs.inMldLeaves);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[43],43,portCntrs.inIgmpLeaves);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[44],44,portCntrs.inIgmpJoinsSuccess);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[45],45,portCntrs.inIgmpJoinsFail);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[46],46,portCntrs.inMldJoinsSuccess);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[47],47,portCntrs.inMldJoinsFail);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[48],48,portCntrs.inReportSuppressionDrop);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[49],49,portCntrs.inLeaveSuppressionDrop);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[50],50,portCntrs.outIgmpReports);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[51],51,portCntrs.outIgmpLeaves);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[52],52,portCntrs.outIgmpGeneralQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[53],53,portCntrs.outIgmpSpecificQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[54],54,portCntrs.outMldReports);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[55],55,portCntrs.outMldLeaves);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[56],56,portCntrs.outMldGeneralQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[57],57,portCntrs.outMldSpecificQuery);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[58],58,portCntrs.inKnownMulticastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[59],59,portCntrs.ifInMulticastPkts);
printf("\r\nport %d: %-35s[%02d]: %25lld",port,APIMIBString[60],60,portCntrs.ifInBroadcastPkts);
printf("\n");
}
}

printf("read gmac register after ping STMMAC_GMACADDR=%x============= \n",STMMAC_GMACADDR);
retVal = HW_REG(STMMAC_GMACADDR);
printf("GMACN_CTRL_REG =%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x1014);
printf("DMAP_STATUS=%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x10C4);
printf("GMACN_MMC_RXUNICASTFRAMES_G=%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x016C);
printf("GMACN_MMC_TXEXCESSDEF =%x\n",retVal);
retVal = HW_REG(STMMAC_GMACADDR+ 0x0168);
printf("GMACN_MMC_TXFRAMECOUNT_G =%x\n",retVal);

reg = HW_REG(0x20030000 + 0xec);
printf("PERI_CRG59 + 0xec,reg=%x ========= \n",reg);
reg = HW_REG(0x20030000 + 0x00F0);
printf("PERI_CRG60 0xF0,reg=%x ========= \n",reg);
reg = HW_REG(0x20050000 + 0x00E8);
printf("PERIPHCTRL45 0xE8,reg=%x ========= \n",reg);

}
}

loopback 环回测试

Loopback:Loopback是一个调试以及故障诊断中常用的功能,Bit14置1之后,PHY和外部MDI的连接在逻辑上将被断开,从MAC经过MII/GMII(也可能是其他的MAC/PHY接口)发送过来的数据将不会被发送到MDI上,而是在PHY内部(一般在PCS)回环到本端口的MII/GMII接收通道上,通过Loopback功能可以检查MII/GMII以及PHY接口部分是否工作正常,对于端口不通的情况可用于故障定位。需要注意的是,很多时候PHY设置Loopback后端口可能就Link down了,MAC无法向该端口发帧,这时就需要通过设置端口Force Link up才能使用Loopback功能。

本地回环测试就是ping本身的ip地址,这样数据就会接收回来

Loopback操作在PCS子层完成,两个方向的Loopback,如下图所示。
● 第一种模式,从MAC经过RGMII发送的帧到达PCS后被Loopback到RGMII的接收通道再送回给MAC(这种模式就是上面所描述的寄存器0 Loopback位控制的Loopback模式)
● 另一种模式,从MDI接收上来的帧到达PCS后被Loopback到MDI的发送通道,这种Loopback模式在IEEE802.3中并没有要求,但是目前常见的PHY都支持该功能

20211206_142353.png

简化PHY片的原理框图

20211206_142413.png

寄存器读写

未验证

20211206_142446.png 20211206_142525.png

Phy驱动

RTL8211F-CG,RTL8211E

注意 pcb 布线要求较高

TX,RX需要加延迟

遇到过一个怪异的事情,就是在时钟增加测试点的时候,因为被挡着,所以用了一小段引线,突然就调通了,去掉引线就 ping 不通网络。

Q&A

只能收包,却发不出去包,环回测试收不到

u-boot 和 linux下,都只能收包,却发不出去包,环回测试也收不到自己发的东西
时系统时钟问题,之前不能发包,是因为mcu给的TX时钟不对,而这个时钟是根据系统时钟分频来的