这次新平台采用了与之前不同的以太网方案, MAC是内置在SoC(System On Chip)上,而PHY采用了Marvell的一款100Mps的车规级的芯片,MAC/PHY的驱动都要重新开发适配,工作难度比之前预想的要大了很多,完成时间也比预想的慢了进一个星期。不过,往后看,这种直接与硬件打交道的经验也很能锻炼人,在一定程度改善了我对系统的认知与理解。这篇文章重点在梳理下车在以太网MAC/PHY遇到的一些问题,以及Linux下MAC/PHY驱动的一些基本流程。
大致分为如下几个部分:
- MAC/PHY的基础知识
- Linux下MAC/PHY驱动的加载流程
- 车载以太网MAC/PHY调试的一些经验总结
MAC/PHY的基本概念
MAC即媒介访问控制层(Media Access Control, 位于TCP/IP协议栈的第二层-数据链路层,用于数据传输过程的数据流控制,其将上层IP数据包分割成适合于物理层传输的数据帧,并负责进行数据传输的冲突管理。按照 IEEE Std 802-2001 上的定义,MAC主要做如下几个事情:
- 数据帧的封装与识别
- 根据MAC地址来与目标主机进行通讯
- 检测数据传输错误(MAC帧中有一个FCS, Frame Checksum Sequence)
- 物理媒介的访问控制,半双工情况下需要进行传输冲突控制,如CSMA/CD
而PHY(Physical layer)即物理层, 其主要负责物理信号的传输, 其通过线束(如光纤/铜线)与其他设备进行连接。一个PHY芯片主要包含了两个部分: PCS(Physical Coding Sublayer), PMD(Physical Medium Dependent), 对车载PHY芯片来说,通常还包含了一个PMA(Physical Media Attachment)子层, 位于PCS与PMD之间; 下图是一个以太网的大致结构图:
那么,MAC与PHY是具体如何通讯的? 其通讯接口实际分控制接口与数据接口。控制接口是哟用于访问控制PHY的寄存器的MDIO(Management Data Input/Output)/MDC(Management Data Clock), 其中MDIO是数据传输用,而MDC是为MDIO的访问提供时序。MDIO最初是在IEEE RFC802.3中定义,只有Clause22一种标准,允许MAC访问32个PHY的32个寄存器;后来,为了适应千兆以太网PHY,提供了clause45协议,最多支持65,536个寄存器的访问,同时兼容clause22的方式来访问clause45的寄存器。下图是Clause22协议访问PHY寄存器的帧结构:
其中:
- ST(2bits): SOF(start of frame), 对Clause22来说是01
- OP(2bits): 操作码, 读或写(01-write/10-read)
- PHYADDR(5bits): PHY的物理地址,这个与硬件配置有关
- REGADDR(5bits): 32位寄存器地址
- TA(2bits): 从STA(MAC)到MMD(PHY)总线使用权切换所需要的翻转时间(turnaround time)
- DATA(16bits): 数据,写寄存器是MAC将数据放到该位置; 读寄存器时PHY将结果放入该位置
更多关于MDIO的两种协议Clause22/Cluase45的信息可以参考:MDIO background
除了控制接口,MAC/PHY之间还有数据传输的接口MII(Media Independent Interface), 针对不同的应用场景,目前已有RMII(Reduced MII), GMII(Gigabit MII), RGMII(Reduced Gigabit MII), SGMII(Serial Gigabit MII), XGMII(10-gigabit MII)等多种接口。
Linux中MAC/PHY驱动的启动流程
这里讲MAC/PHY驱动,不会设计具体的芯片,只分析MAC/PHY启动的关键流程。总的来说, MAC/PHY启动大致有几个步骤:
- 内核加载MAC驱动
- MAC驱动对MAC/PHY芯片上电,并读取PHY的状态寄存器确认PHY正常上电
- MAC注册一个MDIO总线对象,提供PHY寄存器操作的接口
- MAC获取到MDIO总线上的PHY设备,并将其与MAC对应的网络设备进行连接
- 用户进程进行了interface up的操作并配置IP,MAC与PHY可以准备接发数据
这里只讲述下与硬件平台无关的核心部分流程(中间三个部分):
MDIO总线访问接口注册
在MAC/PHY都正常上电后, MAC驱动需要注册一个MDIO的总线接口供后续PHY驱动读写寄存器使用,接口位于include/linux/phy.h
:
1 |
|
mdiobus_alloc_size(0)
为mii_bus
对象分配内存空间:
1 |
|
这个结构miii_bus
对象即是MAC与PHY之间控制访问的接口,主要包括了用于访问PHY寄存器的函数read/write
以及用于PHY芯片软复位的reset
函数,这三个函数通常需要在MAC驱动根据实际的PHY寄存器访问协议来实现;另外还包括了mdio总线所包含的所有PHY设备mdio_map
(最多支持32个PHY)。
1 |
|
初始化完mii_bus
后, MAC驱动会通过mdiobus_register
注册该对象; 在这里,做的最重要的一个事情就是扫描所有MDIO下面的PHY设备,并将其保存到mdio_map
中:
1 |
|
扫描MIDO总线的PHY设备
函数mdiobus_scan
首先调用get_phy_device
获取指定地址上的PHY设备ID,并创建一个 phy_device
对象,然后通过phy_device_register
初始化创建的phy_device
对象:
1 |
|
函数get_phy_id
通过读取MII_PHYSID1/MII_PHYSID2
两个PHY的ID寄存器获取PHY的ID,这里mdiobus_read
正是之前MAC实现的mii_bus
的中的read
接口,如果该接口实现有问题,MAC就无法正常与PHY进行通讯。另外需要注意的是,默认情况下,Linux都是基于MDIO的Clause22协议来访问PHY的寄存器的(调试PHY驱动的时候需要留意)。
1 |
|
扫描到PHY后,将其注册到对应的mii_bus
中:
1 |
|
MAC与PHY进行匹配连接
通过mdiobus_get_phy
这个接口,MAC获取到当前MDIO总线上对应物理地址的PHY设备,然后通过phy_connect_direct
将MAC对应的网络设备与PHY设备进行连接绑定:
1 |
|
将MAC网络设备与PHY进行绑定,并进行初始化,如进行PHY的软复位; 这里要注意的时,PHY的驱动要根据PHYID提前做好适配,不然这里的d->driver
值未空,就无法正常进行phy的初始化了,网络自然无法正常工作。
1 |
|
有关PHY驱动与PHY设备如何进行匹配的有关细节,可以参考Linux内核的文档:
/kernel/msm-4.14/Documentation/driver-model/*.txt
MAC/PHY调试容易踩到的坑
一般来说,MAC跟PHY的连接有这么几种形式:
- MAC/PHY都采用独立的芯片,MAC通过PCI总线接入到系统
- MAC集成到SoC上,PHY采用外接芯片的形式
- MAC/PHY集成在一个芯片上,然后通过PCI总线接入到系统
对现如今集成度越来越高的系统来说,很多SoC都会采用将MAC集成到系统,采用EMAC(Embedded MAC)的形式,这样简化了硬件与软件的设计,对于开发人员来说最主要的工作就是PHY驱动以及相关协议的适配了。由于之前对MAC/PHY驱动的工作接触不多,这次是第一次完全从零开发以太网驱动,遇到了不少坑,总结下主要有如下几点:
- MAC/PHY之间的通讯实际上都是标准的MDIO/MII接口,相对而言都比较成熟了,驱动适配首先还是要确保使用的接口,比如是RGMII还是GMII,两者要确认一致; 另外速率要保持一致,比如MAC配置成100Mbps,同样PHY要对应是100Mbps,否则以太网可能没法工作
- 如今的PHY都支持千兆网速了,所以很多PHY都开始支持clause45的协议寄存器的访问,有些PHY是clause22/clause45都支持,有些PHY则只支持clause45,这个是比较容易出问题的地方。使用正确的MDIO协议访问寄存器才能正常读到PHY芯片的状态
- 最后也是很重要的一点,认真读下厂商提供的PHY芯片手册,以及硬件设计的要点,避免踩到不必要的坑
总的说来,梳理好MAC/PHY的流程,再进行驱动开发就会顺手不少。