引子
最近,在调试基于Freescale IMX6UL板子的以太网口时,遇到了一个奇怪的问题:网口插拔时,系统检测不到Link Down、Link UP事件。并且,在使用ifconfig eth0 up,然后再ifconfig eth0 down时,会提示:
$ sudo ifconfig eth0 up $ sudo ifconfig eth0 down ifconfig: SIOCSIFFLAGS: No such device
首先,可以确定的是,以太网PHY芯片驱动可以正确加载,这说明芯片的DTS配置应该没有问题。之后,又尝试其他的检测网口插拔事件的实现方式,比如通过socket的SIOCGIFFLAGS获取网口的状态,以及通过/sys/class/net/eth0/iplink查看网口的状态等等,事实证明,这些方法都不能正确获取到网口的状态变化。
最后,通过下载IMX6UL的最新固件,发现可以正确的监测到网线的插拔状态,那问题应该内核配置或者PHY芯片的DTS配置有有问题。通过对比PHY芯片相关的内核配置以及DTS配置,最后确定是DTS的ethernet的PHY_ID配置错了。这个PHY_ID为PHY芯片进行MDIO通信时的设备地址,这里所说的MDIO为MAC控制器控制PHY芯片时所采用的的通信方式,其原理与I2C通信类似,PHY_ID类似于I2C中的设备地址。到这里,一切都真相大白了,难怪无法检测到网线的插拔插拔状态,因为CPU的MAC控制器根本没有办法和PHY芯片进行通信,也就没有办法获取到PHY芯片的状态了。之后,也知道了其实ifconfig最终也是通过MDIO与PHY通信来实现的,所以,才会有上面的错误提示。
说了这么多,其实,里面涉及到的知识量很大,比如,一个网卡硬件的组成部分包括哪些?PHY芯片和MAC控制器担当的是什么?它们之间是如何连接、通信的?ifconfig、ip、ethtool这些应用程序工具是如何控制真实的硬件的?下面分小节一一简单说明一下,算是对以上知识的一个科普。
测试环境
CPU:Freescale i.MX6UltraLite 开发板:飞凌OKMX6UL-C2开发板 内核:3.6.18 以太网PHY:KSZ8081RNB
以太网控制器
网卡工作在 OSI 网络体系的最后两层,物理层和数据链路层,物理层定义了数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。物理层的芯片称之为 PHY。数据链路层则提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。以太网卡中数据链路层的芯片称之为 MAC 控制器。很多网卡的这两个部分是做到一起的。他们之间的关系是 PCI 总线接 MAC 总线,MAC 接 PHY,PHY 接网线(当然也不是直接接上的,还有一个变压装置)。
图 1. 一个典型的符合 IEEE802.3 标准的的以太网控制器结构图 这里简单解析一下:
- MAC:Media Access Control,即媒体访问控制子层协议。该协议位于 OSI 七层协议中数据链路层的下半部分,主要负责控制与连接物理层的物理介质。在发送数据的时候,MAC 协议可以事先判断是否可以发送数据,如果可以发送将给数据加上一些控制信息,最终将数据以及控制信息以规定的格式发送到物理层;在接收数据的时候,MAC 协议首先判断输入的信息并是否发生传输错误,如果没有错误,则去掉控制信息发送至 LLC 层。以太网 MAC 由 IEEE-802.3 以太网标准定义。
- PHY:工作在OSI七层协议中的物理层, 嵌入式系统中“网卡”芯片一般都是PHY。它实现物理层。包括 MII/GMII(介质独立接口)子层、PCS(物理编码子层)、PMA(物理介质附加)子层、 PMD(物理介质相关)子层、MDI 子层。
- MDC/MDIO为MII的管理通信接口,工作方式为两线,双工,MDC为时钟,MDIO为双向数据通信,原理上很类似于I2C总线,上图也可以看出MAC可以通过MDC/MDIO挂接多个PHY。
RMII、MII
MII
MII Media Independant Interface,即媒体独立接口 ,其对MAC和PHY之间的通信方式进行了抽象,MAC和PHY各自实现MII接口,就可以相关通信。包括分别用于发送器和接收器的两条独立信道。每条信道都有自己的数据、时钟和控制信号。MII 数据接口总共需要 16 个信号,包括 TX_ER,TXD<3:0>,TX_EN,TX_CLK,COL,RXD<3:0>,RX_EX,RX_CLK,CRS,RX_DV 等。MII的时钟为25MHz,传输速率为10/100Mbps。
MAC与PHY通过MII连接的示意图如下:
- MII_TX_CLK:发送数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
- MII_RX_CLK:接收数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
- MII_TX_EN:传输使能信号,此信号必需与数据前导符的起始位同步出现,并在传输完毕前一直保持。
- MII_TXD[3:0]:发送数据线,每次传输4位数据,数据在MII_TX_EN信号有效时有效。MII_TXD[0]是数据的最低位,MII_TXD[3]是最高位。当MII_TX_EN信号无效时,PHY忽略传输的数据。
- MII_CRS:载波侦听信号,仅工作在半双工模式下,由PHY控制,当发送或接收的介质非空闲时,使能此信号。 PHY必需保证MII_CRS信号在发生冲突的整个时间段内都保持有效,不需要此信号与发送/接收的时钟同步。
- MII_COL:冲突检测信号,仅工作在半双工模式下,由PHY控制,当检测到介质发生冲突时,使能此信号,并且在整个冲突的持续时间内,保持此信号有效。此信号不需要和发送/接收的时钟同步。
- MII_RXD[3:0]:接收数据线,每次接收4位数据,数据在MII_RX_DV信号有效时有效。MII_RXD[0]是数据的最低位,MII_RXD[3]是最高位。当MII_RX_EN无效,而MII_RX_ER有效时,MII_RXD[3:0]数据值代表特定的信息(请参考表194)。
- MII_RX_DV:接收数据使能信号,由PHY控制,当PHY准备好数据供MAC接收时,使能该信号。此信号必需和帧数据的首位同步出现,并保持有效直到数据传输完成。在传送最后4位数据后的第一个时钟之前,此信号必需变为无效状态。为了正确的接收一个帧,有效电平不能滞后于数据线上的SFD位出现。
- MII_RX_ER:接收出错信号,保持一个或多个时钟周期(MII_RX_CLK)的有效状态,表明MAC在接收过程中检测到错误。具体错误原因需配合MII_RX_DV的状态及MII_RXD[3:0]的数据值。
RMMI
RMII (Reduced Media Independant Interface ) 是简化的 MII 接口,在数据的收发上它比 MII 接口少了一倍的信号线,所以它一般要求是 50 M的总线时钟,MII的时钟总线为25M。RMII 一般用在多端口的交换机,它不是每个端口安排收、发两个时钟,而是所有的数据端口公用一个时钟用于所有端口的收发 ,这里就节省了不少的端口数目。RMII 的一个端口要求 7 个数据线 ,比 MII 少了一倍,所以交换机能够接入多一倍数据的端口。和 MII 一样,RMII 支持 10 兆和 100 兆的总线接口速度 。RMII的时钟为50MHz,传输速率为10/100Mbps。MAC与PHY通过RMII连接的示意图如下:
GMII
GMII(Gigabit MII) 是千兆网的 MII 接口,这个也有相应的 RGMII 接口,表示简化了的 GMII 接口。GMII 采用 8 位接口数据,工作时钟 125MHz,因此传输速率可达 1000Mbps 。同时兼容 MII 所规定的 10/100 Mbps 工作方式。GMII的时钟频率为:2.5/25/125MHz),传输速率为:10/100/1000Mbps。
下面MII、RMII、GMII三种接口的对比:
接口类型 | 信号数量 | 时钟速率 | 时钟源 | 传输速率 |
MII | 16 | 25MHz | 外部晶振或者MAC提供,不需要与MAC时钟同步 | 10/100Mbps |
RMII | 8 | 50MHz | 一般是MAC提供,需要与MAC时钟同步 | 0/1000Mbps |
GRMII | 8 | 125MHz | 一般是MAC提供,需要与MAC时钟同步 | 10/100/1000Mbps |
MDC/MDIO
基本原理
MDC/MDIO为MII的管理通信接口,工作方式为两线,双工,MDC为时钟,MDIO为双向数据通信,原理上很类似于I2C总线,上图也可以看出MAC可以通过MDC/MDIO挂接多个PHY。该总线由 IEEE 通过以太网标准 IEEE 802.3 的若干条款加以定义。MDIO 是一种简单的双线串行接口,将管理器件 ( 如 MAC 控制器、微处理器 ) 与具备管理功能的收发器 ( 如多端口吉比特以太网收发器或 10GbE XAUI 收发器 ) 相连接,从而控制收发器并从收发器收集状态信息。可收集的信息包括链接状态、传输速度与选择、断电、低功率休眠状态、TX/RX 模式选择、自动协商控制、环回模式控制等。除了拥有 IEEE 要求的功能之外,收发器厂商还可添加更多的信息收集功能,例如流控的打开关闭,自协商模式还是强制模式等,这也是 ethtool 的工作原理。MDC 则是管理数据的时钟输入,最高速率可达 8.3MHz。MDIO 是管理数据的输入输出双向接口,数据是与 MDC 时钟同步的。
特性
MDC/MDIO基本特性:
- 两线制:MDC(时钟线)和MDIO(数据线)。
- 时钟频率:2.5MHz
- 通信方式:总线制,可同时接入的PHY数量为32个。
工作流程
MDIO 的工作流程为:
- MDIO 接口在没有传输数据的空闲状态(IDLE)数据线 MDIO 处于高阻态。
- MDIO 出现一个 2bit 的开始标识码 (01) 一个读 / 写操作开始。
- MDIO 出现一个 2bit 数据来标识是读操作 (10) 还是写操作 (01)。
- MDIO 出现一个 5bit 数据标识 PHY 的地址。
- MDIO 出现一个 5bitPHY 寄存器地址。
- MDIO 需要 2 个时钟的访问时间。
- MDIO 串行读出 / 写入 16bit 的寄存器数据。
- MDIO 恢复成 IDLE 状态,同时 MDIO 进入高阻状态。
IEEE 802.3 规定的 MII 寄存器
关于 MII/GMII 接口 PHY 寄存器的定义在 802.3 的 22.2.4 Management functions. 章节中。如该章节中的 Table 22 – 6 和 Table 22 – 7(即本文的图 3 和图 4,均出自 standards.ieee.org/getieee802/…
图 2. IEEE802.3 定义的 MII 管理寄存器集
可以看到寄存器分为基本集和扩展集,基本集的定义因 GMII 和 MII 而不同,对于 MII, 基本集包括寄存器 0 控制寄存器和 1 状态寄存器,而对于 GMII;基本集包括寄存器 0、1 和 15。控制寄存器 0 和状态寄存器 1 的定义如图 2所示。
图3 IEEE802.3 定义的寄存器 0 控制寄存器和 1 状态寄存器
对寄存器 0 和寄存器 1 的读写可以实现对网卡的管理,清单 1 列出了部分 PHY 管理寄存器以及控制寄存器和状态寄存器的各个 bit 的定义。
Linux内核中的/kernel/drivers/net/Mii.h, 定义 PHY 管理寄存器。
#define MII_BMCR 0x00 /* Basic mode control register */ #define MII_BMSR 0x01 /* Basic mode status register */ #define MII_PHYSID1 0x02 /* PHYS ID 1 */ #define MII_PHYSID2 0x03 /* PHYS ID 2 */ #define MII_ADVERTISE 0x04 /* Advertisement control reg */ #define MII_LPA 0x05 /* Link partner ability reg */ #define MII_EXPANSION 0x06 /* Expansion register */ #define MII_CTRL1000 0x09 /* 1000BASE-T control */ ... /* Basic mode control register. */ #define BMCR_RESV 0x003f /* Unused... */ #define BMCR_SPEED1000 0x0040 /* MSB of Speed (1000) */ #define BMCR_CTST 0x0080 /* Collision test */ #define BMCR_FULLDPLX 0x0100 /* Full duplex */ #define BMCR_ANRESTART 0x0200 /* Auto negotiation restart */ #define BMCR_ISOLATE 0x0400 /* Disconnect DP83840 from MII */ #define BMCR_PDOWN 0x0800 /* Powerdown the DP83840 */ #define BMCR_ANENABLE 0x1000 /* Enable auto negotiation */ #define BMCR_SPEED100 0x2000 /* Select 100Mbps */ #define BMCR_LOOPBACK 0x4000 /* TXD loopback bits */ #define BMCR_RESET 0x8000 /* Reset the DP83840 */ /* Basic mode status register. */ #define BMSR_ERCAP 0x0001 /* Ext-reg capability */ #define BMSR_JCD 0x0002 /* Jabber detected */ #define BMSR_LSTATUS 0x0004 /* Link status */ #define BMSR_ANEGCAPABLE 0x0008 /* Able to do auto-negotiation */ #define BMSR_RFAULT 0x0010 /* Remote fault detected */ #define BMSR_ANEGCOMPLETE 0x0020 /* Auto-negotiation complete */ #define BMSR_RESV 0x00c0 /* Unused... */ #define BMSR_ESTATEN 0x0100 /* Extended Status in R15 */ #define BMSR_100FULL2 0x0200 /* Can do 100BASE-T2 HDX */ #define BMSR_100HALF2 0x0400 /* Can do 100BASE-T2 FDX */ #define BMSR_10HALF 0x0800 /* Can do 10mbps, half-duplex */ #define BMSR_10FULL 0x1000 /* Can do 10mbps, full-duplex */ #define BMSR_100HALF 0x2000 /* Can do 100mbps, half-duplex */ #define BMSR_100FULL 0x4000 /* Can do 100mbps, full-duplex */ #define BMSR_100BASE4 0x8000 /* Can do 100mbps, 4k packets */
通过 MDC/MDIO 读写 MII 寄存器的具体实现
在本文的前面部分介绍过 MDC/MDIO 的工作流程,网卡驱动程序中的 MDIO 读写函数 mdio_read 和 mdio_write,这些函数的具体实现是在各个网卡的驱动程序文件中完成的,都遵从 IEEE802.3 MDIO 的帧格式。典型的帧格式是第 22 条款中定义的格式:
|域| 长度(bit)| 说明| |--|--|--|--| |ST |2bits |01b |OP |2bits |操作码,|写为 01b,读为 10b |PHYADR |5bits |PHY ID |REGADR |5bits |寄存器地址 |TA |2 bits |状态转换域,读操作为 X0b, 写操作为 10b |DATA| 16 bits| 数据
Linux驱动解析
上文说了,网卡硬件一般包括MAC控制器和PHY,一个处理数据链路层的数据,一个处理物理层的数据。有的网卡会将MAC和PHY集成到一起,然后通过PCI总线与CPU连接;另外的网卡,MAC控制器集成到SoC芯片,PHY作为单独芯片,然后,两者通过MII或者RMII接口进行连接。
嵌入式Linux开发模式下, 网卡的硬件架构一般都是第二种方式,即MAC与PHY是独立的。至于网卡的驱动也分为两部分实现:MAC控制器驱动由SoC厂商开发,PHY芯片驱动由PHY厂商开发,当然,驱动之间要完成通信,必须严格按照IEEE802.3制定的协议。
涉及到具体的芯片驱动程序,其实现一般比较复杂,实现方式一般都是按照网络架构实现具体的接口,本文不会去具体解析驱动的实现代码,只是通过几个实例来描述一下,如何实现网卡的控制。
ifconfig
ifconfig一般用来简单的管理网卡,例如,查看状态,配置ip、掩码、网关,up/down网卡等。那么,对于一块具体的PHY或者MAC,ifconfig是如何实现管理的呢?下面通过一个接口调用图说明一下。
- ifconfig与网卡交互通过ioctl系统调用实现。
- 内核网络子系统预定义了很多命令,比如,SIOCGIFFLAGS用于配置网卡状态,比如up/down。
- PHY和MAC驱动通过实现具体的接口来为上层的ifconfig提供服务。
- MAC驱动实现最终与PHY通信的MII接口,通过mdio和mdio_read可以实现PHY的管理,比如,设置传输速率、获取link status等等。
内核的/include/uapi/linux/sockios.h定义了socket配置命令:
/* Socket configuration controls. */ #define SIOCGIFNAME 0x8910 /* get iface name */ #define SIOCSIFLINK 0x8911 /* set iface channel */ #define SIOCGIFCONF 0x8912 /* get iface list */ #define SIOCGIFFLAGS 0x8913 /* get flags */ #define SIOCSIFFLAGS 0x8914 /* set flags */ #define SIOCGIFADDR 0x8915 /* get PA address */ #define SIOCSIFADDR 0x8916 /* set PA address */ #define SIOCGIFDSTADDR 0x8917 /* get remote PA address */ #define SIOCSIFDSTADDR 0x8918 /* set remote PA address */ #define SIOCGIFBRDADDR 0x8919 /* get broadcast PA address */ #define SIOCSIFBRDADDR 0x891a /* set broadcast PA address */ #define SIOCGIFNETMASK 0x891b /* get network PA mask */ #define SIOCSIFNETMASK 0x891c /* set network PA mask */ #define SIOCGIFMETRIC 0x891d /* get metric */ #define SIOCSIFMETRIC 0x891e /* set metric */ #define SIOCGIFMEM 0x891f /* get memory address (BSD) */ #define SIOCSIFMEM 0x8920 /* set memory address (BSD) */ #define SIOCGIFMTU 0x8921 /* get MTU size */ #define SIOCSIFMTU 0x8922 /* set MTU size */ #define SIOCSIFNAME 0x8923 /* set interface name */ #define SIOCSIFHWADDR 0x8924 /* set hardware address */ #define SIOCGIFENCAP 0x8925 /* get/set encapsulations */ #define SIOCSIFENCAP 0x8926 #define SIOCGIFHWADDR 0x8927 /* Get hardware address */ #define SIOCGIFSLAVE 0x8929 /* Driver slaving support */ #define SIOCSIFSLAVE 0x8930 #define SIOCADDMULTI 0x8931 /* Multicast address lists */ #define SIOCDELMULTI 0x8932 #define SIOCGIFINDEX 0x8933 /* name -> if_index mapping */ #define SIOGIFINDEX SIOCGIFINDEX /* misprint compatibility :-) */ #define SIOCSIFPFLAGS 0x8934 /* set/get extended flags set */ #define SIOCGIFPFLAGS 0x8935 #define SIOCDIFADDR 0x8936 /* delete PA address */ #define SIOCSIFHWBROADCAST 0x8937 /* set hardware broadcast addr */ #define SIOCGIFCOUNT 0x8938 /* get number of devices */ #define SIOCGIFBR 0x8940 /* Bridging support */ #define SIOCSIFBR 0x8941 /* Set bridging options */ #define SIOCGIFTXQLEN 0x8942 /* Get the tx queue length */ #define SIOCSIFTXQLEN 0x8943 /* Set the tx queue length */ /* SIOCGIFDIVERT was: 0x8944 Frame diversion support */ /* SIOCSIFDIVERT was: 0x8945 Set frame diversion options */ #define SIOCETHTOOL 0x8946 /* Ethtool interface */ #define SIOCGMIIPHY 0x8947 /* Get address of MII PHY in use. */ #define SIOCGMIIREG 0x8948 /* Read MII PHY register. */ #define SIOCSMIIREG 0x8949 /* Write MII PHY register. */
问题解决
回到文章开始时问题,1)系统无法检测到网线的插拔事件;2)通过ifconfig配置网络设备提示:ifconfig: SIOCSIFFLAGS: No such device。
通过上面的讲解,可以猜测,可能是MAC驱动程序不能与PHY硬件进行正常的通信,从而导致上述的问题。那么,下一步就是要检查DTS中关于MAC的配置,对于本文中板子,IMX6UL,其配置信息如下:
fec1: ethernet@02188000 { compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec"; reg = <0x02188000 0x4000>; interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_ENET>, <&clks IMX6UL_CLK_ENET_AHB>, <&clks IMX6UL_CLK_ENET_PTP>, <&clks IMX6UL_CLK_ENET_REF>, <&clks IMX6UL_CLK_ENET_REF>; clock-names = "ipg", "ahb", "ptp", "enet_clk_ref", "enet_out"; stop-mode = <&gpr 0x10 3>; fsl,num-tx-queues=<1>; fsl,num-rx-queues=<1>; fsl,magic-packet; fsl,wakeup_irq = <0>; status = "disabled"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rmii"; phy-handle = <ðphy0>; status = "okay"; mdio { #address-cells = <1>; #size-cells = <0>; ethphy0: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <0>; }; }; };
这里需要关注的是:
- phy-mode:指定MAC和PHY之间的接口模式,这里用的是rmii模式;
- phy-handle:指定MAC与PHY之间通信的配置信息;
- mdio:关于MDC/MDIO通信相关配置信息;
可以参照PHY的datasheet和具体的原理图,查看上面几项配置信息,是否正确。最终发现,mdio中的reg项配置错误,该项指定了PHY的地址,用于MAC和PHY之间的通信。本文用到的KSZ8081RNB这款PHY芯片,默认PHY地址为1,而mdio中的reg将其配置成了0,所以导致MAC和PHY之间无法通信,从而导致上述两个问题。修改reg为1之后,问题解决。
总结
本文通过一个具体的问题作为引子,主要讲解了关于网卡的一般架构,其都是遵循IEEE 802.3协议的,进而又分析了MAC和PHY之间的通信接口MII/RMII,之后,通过linux系统下ifconfig的实现框架,讲解了应用程序如何与具体网卡芯片进行通信,最后,分析如何解决开始遇到的问题。这里得出几点结论:
- 遇到问题时,要学会由现象到本质的分析方法。即使一时解决了问题,也需要保持一颗好奇心,想方设法去探究问题的根本。只有这样,才能做到“知其然,知其所以然”,再遇到类似问题,才能从容应对。
- 由MAC和PHY之间通信接口MII,可以看到,良好的接口可以极大的降低系统的复杂度,降低系统之间耦合。无论软件和硬件,这条规律都是适用的。
- 由ifconfig的实现架构,可以推而广之去分析类似Linux应用工具,比如,ethtool。
- 最后,stay hungry stay foolish!