Linux以太网卡架构解析-MAC层和PHY层

引子

最近,在调试基于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 接网线(当然也不是直接接上的,还有一个变压装置)。
Linux以太网卡架构解析-MAC层和PHY层_第1张图片
图 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连接的示意图如下:
Linux以太网卡架构解析-MAC层和PHY层_第2张图片

  • 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连接的示意图如下:
Linux以太网卡架构解析-MAC层和PHY层_第3张图片

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基本特性:

  1. 两线制:MDC(时钟线)和MDIO(数据线)。
  2. 时钟频率:2.5MHz
  3. 通信方式:总线制,可同时接入的PHY数量为32个。

工作流程

MDIO 的工作流程为:

  1. MDIO 接口在没有传输数据的空闲状态(IDLE)数据线 MDIO 处于高阻态。
  2. MDIO 出现一个 2bit 的开始标识码 (01) 一个读 / 写操作开始。
  3. MDIO 出现一个 2bit 数据来标识是读操作 (10) 还是写操作 (01)。
  4. MDIO 出现一个 5bit 数据标识 PHY 的地址。
  5. MDIO 出现一个 5bitPHY 寄存器地址。
  6. MDIO 需要 2 个时钟的访问时间。
  7. MDIO 串行读出 / 写入 16bit 的寄存器数据。
  8. 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,均出自 http://standards.ieee.org/getieee802/download/802.3-2008_section2.pdf)所示。

图 2. IEEE802.3 定义的 MII 管理寄存器集

Linux以太网卡架构解析-MAC层和PHY层_第4张图片
可以看到寄存器分为基本集和扩展集,基本集的定义因 GMII 和 MII 而不同,对于 MII, 基本集包括寄存器 0 控制寄存器和 1 状态寄存器,而对于 GMII;基本集包括寄存器 0、1 和 15。控制寄存器 0 和状态寄存器 1 的定义如图 2所示。

图3 IEEE802.3 定义的寄存器 0 控制寄存器和 1 状态寄存器
Linux以太网卡架构解析-MAC层和PHY层_第5张图片
Linux以太网卡架构解析-MAC层和PHY层_第6张图片
对寄存器 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 操作码,
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是如何实现管理的呢?下面通过一个接口调用图说明一下。
Linux以太网卡架构解析-MAC层和PHY层_第7张图片

  • 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 = <&ethphy0>;                                                                                                                                                                                        
			    status = "okay";
			                    
			    mdio {
                  
			         #address-cells = <1>;
			         #size-cells = <0>;
			                    
			         ethphy0: ethernet-phy@1 {
     
			             compatible = "ethernet-phy-ieee802.3-c22";
			             reg = <0>;
			         };         
			   };   
    };   

这里需要关注的是:

  1. phy-mode:指定MAC和PHY之间的接口模式,这里用的是rmii模式;
  2. phy-handle:指定MAC与PHY之间通信的配置信息;
  3. 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的实现框架,讲解了应用程序如何与具体网卡芯片进行通信,最后,分析如何解决开始遇到的问题。这里得出几点结论:

  1. 遇到问题时,要学会由现象到本质的分析方法。即使一时解决了问题,也需要保持一颗好奇心,想方设法去探究问题的根本。只有这样,才能做到“知其然,知其所以然”,再遇到类似问题,才能从容应对。
  2. 由MAC和PHY之间通信接口MII,可以看到,良好的接口可以极大的降低系统的复杂度,降低系统之间耦合。无论软件和硬件,这条规律都是适用的。
  3. 由ifconfig的实现架构,可以推而广之去分析类似Linux应用工具,比如,ethtool。
  4. 最后,stay hungry stay foolish!

你可能感兴趣的:(Linux,linux,MII/RMII,MDIO/MDC,ifconfig)