
千兆网口freescale etsec + marvell 88e1111 uboot linux 驱动分析.doc
22页在连续两个平台的 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 == dev) return 0; memset(dev, 0, sizeof *dev);/*为 priv 分配空间*/priv = (struct tsec_private *)malloc(sizeof(*priv)); if (NULL == priv) return 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 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 /*挂载 MII 口的读写函数*/#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) / && !defined(BITBANGMII) miiphy_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 /* 通过读 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 里面也这么叫,真是误人子弟啊/if (regs->ecntrl & ECNTRL_SGMII_MODE) tsec_configure_serdes(priv);/*在符合条件的 PHY 的 phy_info 数组中调用其初始化配置函数*/priv->phyinfo = curphy; phy_run_commands(priv, priv->phyinfo->config); return 1;。
