最近,在调试基于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。
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。
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(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为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基本特性:
MDIO 的工作流程为:
关于 MII/GMII 接口 PHY 寄存器的定义在 802.3 的 22.2.4 Management functions. 章节中。如该章节中的 Table 22 – 6 和 Table 22 – 7(即本文的图 3 和图 4,均出自 http://standards.ieee.org/getieee802/download/802.3-2008_section2.pdf)所示。
图 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 的工作流程,网卡驱动程序中的 MDIO 读写函数 mdio_read 和 mdio_write,这些函数的具体实现是在各个网卡的驱动程序文件中完成的,都遵从 IEEE802.3 MDIO 的帧格式。典型的帧格式是第 22 条款中定义的格式:
域 | 长度(bit) | 说明 |
---|---|---|
ST | 2bits | 01b |
OP | 2bits | 操作码, |
PHYADR | 5bits | PHY ID |
REGADR | 5bits | 寄存器地址 |
TA | 2 bits | 状态转换域,读操作为 X0b, 写操作为 10b |
DATA | 16 bits | 数据 |
上文说了,网卡硬件一般包括MAC控制器和PHY,一个处理数据链路层的数据,一个处理物理层的数据。有的网卡会将MAC和PHY集成到一起,然后通过PCI总线与CPU连接;另外的网卡,MAC控制器集成到SoC芯片,PHY作为单独芯片,然后,两者通过MII或者RMII接口进行连接。
嵌入式Linux开发模式下, 网卡的硬件架构一般都是第二种方式,即MAC与PHY是独立的。至于网卡的驱动也分为两部分实现:MAC控制器驱动由SoC厂商开发,PHY芯片驱动由PHY厂商开发,当然,驱动之间要完成通信,必须严格按照IEEE802.3制定的协议。
涉及到具体的芯片驱动程序,其实现一般比较复杂,实现方式一般都是按照网络架构实现具体的接口,本文不会去具体解析驱动的实现代码,只是通过几个实例来描述一下,如何实现网卡的控制。
ifconfig一般用来简单的管理网卡,例如,查看状态,配置ip、掩码、网关,up/down网卡等。那么,对于一块具体的PHY或者MAC,ifconfig是如何实现管理的呢?下面通过一个接口调用图说明一下。
内核的/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的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的实现框架,讲解了应用程序如何与具体网卡芯片进行通信,最后,分析如何解决开始遇到的问题。这里得出几点结论: