在连续两个平台的uboot和Linux系统移植过程中,在千兆网口调试这块都遇到了很大的麻烦。
由于寄存器数量庞大,千兆网口MAC和PHY内部结构复杂,MAC和PHY接口种类多,千兆以太网驱动的调试成了系统移植过程中最让人烦心的一个环节。
就像火箭队,每次都让球迷无比揪心,不是输的窝囊,就是伤兵满营,现在新赛季又两连败了,打的比勇士还勇士,后场两个比我还瘦的家伙,怎么防守。
算了,不扯这么多了,今天要说的是网口MAC+PHY的一些原理和代码分析。
(以Freescale的ETSEC和Marvell的88E1111为例。
)1 千兆以太网的物理层千兆以太网的物理层分为物理编码子层PCS(Physical Coding Sublayer)、物理介质连接子层PMA(Physical Medium Attachment)和物理介质相关子层PMD(Physical Medium Dependent)三层,如下图所示:其中PCS子层负责8b10b编码,它可以把从GMII口接收到的8位并行的数据转换成10位并行的数据输出。
因为10比特的数据能有效地减小直流分量,降低误码率,另外采用8b10b编码便于在数据中提取时钟和进行首发同步。
可以把PCS两头看成GMII接口和TBI接口。
PMA子层进一步将PCS子层的编码结果向各种物理媒体传送,主要是负责完成串并转换。
PCS层以125M的速率并行传送10位代码到PMA层,由PMA层转换为1.25Gbps的串行数据流进行发送,以便实际能得到1Gbps的千兆以太网传送速率。
可以把PMA子层的两头分别看做TBI接口和SGMII接口。
PMD子层将对各种实际的物理媒体完成接口,完成真正的物理连接。
由于1000BASE-X支持多种物理媒介,如光纤和屏蔽双绞线,它们的物理接口显然不会相同。
有的要进行光电转换,有的要完成从不平衡到平衡的转换。
PMD层将对这些具体的连接器作出规定。
2 Freescale 的ETSEC与PHY之间的接口Freescale的MPC8314和P2020都自带了三速以太网控制器ETSEC,可以提供10M,100M,1000M三种速率的接口。
当作为以太网时,需要外部的PHY芯片或者Serdes设备与其相连接。
每个ETSEC都支持多标准的MII接口,总体结构如下图所示,可以提供GMII,RGMII,MII,RMII,RTBI,SGMII 六种接口,下图为从MPC8314 datasheet中截取的ETSEC的结构图。
如果CPU与PHY之间是GMII接口或RGMII接口,那么PHY将提供完整的PCS,PMA,PMD三层工作;如果CPU与PHY之间是RTBI接口,那么PCS层的工作在ETSEC中已经做完了,ETSEC中的TBI模块可以做PCS层的工作,PHY只需要做PMA和PMD的工作即可;如果CPU与PHY之间是SGMII接口,那么PHY只需要完成PMD的工作,ETSEC中的PCS由TBI完成,而PMA由CPU自带的Serdes模块完成。
3 BD表结构在千兆以太网的驱动中,现在一般都使用一个叫BD表的东西来管理MAC层发送和接收的内存区域,如下图所示:在IMMR映射的寄存器空间中有两组寄存器TBASEn和RBASEn,分别为TxBD Ringn 和RxBD Ringn的指针。
MPC8314的ETSEC允许有8个TxBD Ring和8个RxBD Ring,他们都存放在内存的某个区域中。
每个Buffer Descriptor 都是有8个字节构成,两个字节的状态,两个字节的数据长度和四个字节的数据指针,这个指针指向内存的另一块地方,这才是真正存储发送接收数据的地方。
Buffer Descriptor必须在网口初始化的时候初始化,并将自己的地址赋给TBASEn和RBASEn。
在网口驱动程序中可以看到,每个BD Ring中的BD数量是可变的(我们设为64),而他们之间并没有指针连接,只是一段连续的空间,顺序下来的,所谓的环只是一个虚拟的概念,在最后一个BD时,需要将BD状态位中的W位(Wrap)置一,表示这是最后一个BD,他的下一个BD就是第一个BD。
如下图所示:下面一节将结合uboot源码分析一下网口初始化以及PHY配置的过程,再下一节会分析内核中的驱动。
为什么先说uboot,因为在我看来,驱动程序就是分为两个部分,1 按照Datasheet的说明去配置寄存器,2 添加符合操作系统规范去融入操作系统。
在uboot下系统很简单,代码一目了然,所以我们应该在boot 下先把寄存器配置好,再去分析复杂的多的内核代码。
这节分析uboot中的网口驱动代码。
1 网口驱动函数列表tsec_initialize( 网口初始化函数tsec_init( 网口启动函数tsec_local_mdio_write( MDIO口写函数tsec_local_mdio_read( MDIO口读函数tsec_send( 网口发送函数tsec_recv( 网口接收函数tsec_configure_serdes( 配置TBI PHY的函数fsl_serdes_init( Serdes模块初始化函数init_phy( PHY初始化函数adjust_link( 根据PHY状态配置MAC的函数2 tsec_initialize(函数该函数为ETSEC的初始化函数,在该函数中要初始化eth_device结构和私有的tsec_private结构,并初始化PHY。
int tsec_initialize(bd_t * bis, int index, char *devname{struct eth_device *dev;int i;struct tsec_private *priv;/*为dev分配空间*/dev = (struct eth_device *malloc(sizeof *dev;if (NULL == devreturn 0;memset(dev, 0, sizeof *dev;/*为priv分配空间*/priv = (struct tsec_private *malloc(sizeof(*priv;if (NULL == privreturn 0;/*从tsec_info 数组中取合适的值去初始化私有结构tsec_private*/privlist[num_tsecs++] = priv;priv->regs = tsec_info[index].regs; //每个tsec寄存器的基址priv->phyregs = tsec_info[index].miiregs; //PHY MDIO读写状态寄存器基址/*TBI PHY的MDIO读写状态寄存器基址*/priv->phyregs_sgmii = tsec_info[index].miiregs_sgmii;priv->phyaddr = tsec_info[index].phyaddr; //PHY 地址priv->flags = tsec_info[index].flags;priv->ID = index;/*使用将priv结构体挂到dev结构体下,挂载tsec的打开、关闭、发送、接收函数*/sprintf(dev->name, tsec_info[index].devname;dev->iobase = 0;dev->priv = priv;dev->init = tsec_init;dev->halt = tsec_halt;dev->send = tsec_send;dev->recv = tsec_recv;/*初始化IP地址*/for (i = 0; i < 6; i++dev->enetaddr[i] = 0;/*设置当前活跃的网口名相当于 set ethact eTSECn,将多个网口级联*/eth_register(dev;/* 通过设置MACCFG1寄存器重启 MAC */priv->regs->maccfg1 |= MACCFG1_SOFT_RESET;udelay(2; /* Soft Reset must be asserted for 3 TX clocks */priv->regs->maccfg1 &= ~(MACCFG1_SOFT_RESET;/*挂载MII口的读写函数*/#if defined(CONFIG_MII || defined(CONFIG_CMD_MII / && !defined(BITBANGMIImiiphy_register(dev->name, tsec_miiphy_read, tsec_miiphy_write;#endif/* 初始化PHY,并返回 */return init_phy(dev;}3 init_phy(static int init_phy(struct eth_device *dev{struct tsec_private *priv = (struct tsec_private *dev->priv; struct phy_info *curphy;volatile tsec_t *regs = priv->regs;/*在TBIPA的寄存器中写入TBI PHY的地址*/regs->tbipa = CONFIG_SYS_TBIPA_VALUE + priv->ID;asm("sync";/* 重启MII接口 */priv->phyregs->miimcfg = MIIMCFG_RESET;asm("sync";priv->phyregs->miimcfg = MIIMCFG_INIT_VALUE;asm("sync";while (priv->phyregs->miimind & MIIMIND_BUSY ;/* 通过读PHY的2号3号寄存器获得该ETSEC外连的PHY的ID,搜索phy_info数组,找到符合ID的PHY信息返回。
*/curphy = get_phy_info(dev;if (curphy == NULL{priv->phyinfo = NULL;printf("%s: No PHY found/n", dev->name;return 0;}/*如果是SGMII的接口,需要使用TBI PHY,初始化TBI PHY,注意这里名字竟然叫serdes配置,Linux里面也这么叫,真是误人子弟啊。