带你一文搞懂 Linux 网络 Phy 驱动
概述

上图来自 瑞昱半导体 (RealTek) 的 RTL8201F 系列网卡 PHY 芯片手册。按OSI 7层网络模型划分,网卡PHY 芯片(图中的RTL8201F)位于物理层,对应的软件层就是本文讨论的 PHY 驱动层;而 MAC 位于 数据链路层,也是通常软件上所说的网卡驱动层,它不是本文的重点,不做展开。另外,可通过 MDIO 接口对 PHY 芯片进行配置(如PHY芯片寄存器读写),而 PHY 和 MAC 通过 MII/RMII 进行数据传输。
PHY芯片通过MII/GMII/RMII/SGMII/XGMII等多种媒体独立接口(介质无关接口)与数据链路层的MAC芯片相连,并通过MDIO接口实现对PHY 状态的监控、配置和管理。
PHY与MAC整体的连接框图:

数据结构
每个 phy 芯片会创建一个 struct phy_device 类型的设备,对应的有 struct phy_driver 类型的驱动,这两者实际上是挂载在 mdio_bus_type 总线上的,mac 会被注册成 struct net_device。

phy_device
phy_driver
mii_bus
net_device
【文章福利】小编推荐自己的Linux内核技术交流群:【749907784】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


零声白金VIP体验卡(含基础架构/高性能存储/golang/QT/音视频/Linux内核)课程:

phy 设备的注册
以网卡 Fec 为例,网卡驱动在初始化 fec_probe() 时遍历 dts 的定义,创建相应 struct phy_device 类型的设备,主要步骤为:
注册网络设备 net_device
申请队列和 DMA
申请 MDIO 总线
创建并注册 Phy 设备
phy 驱动的注册
genphy_driver 通用驱动
内核中有 genphy_driver 通用驱动,以及专有驱动(这里以 NXP TJA 驱动为例),分别如下:
genphy_driver 的 struct phy_driver 的注册过程如下:
其中一个关键点是 mdio driver 的 probe 函数是一个通用函数 phy_probe:
其中通用 phy 驱动会调用函数 genphy_read_abilities 来读取状态寄存器来确定 phy 芯片的能力:
NXP TJA 专有驱动
NXP TJA 驱动的 struct phy_driver 的注册过程如下:
根据上面的分析,由于存在 phydev->drv->probe,所以会调用其注册的函数 tja11xx_probe。
网卡 fec 和 Phy 的协作
在 linux 内核中,以太网 mac 会被注册成 struct net_device,phy 芯片会被注册成 struct phy_device。 phy_device 的状态怎么传递给 net_device,让其在 link 状态变化时做出对应的配置改变,这个任务就落在上述的 struct phylink 中介身上。
下面就以 fec 网口驱动为例,展示一下网卡 fec 和 phy 的协作过程。整个 phy 驱动的主要调用流程如下图所示:

一个 phy 驱动的原理其实是非常简单的,一般流程如下:
用轮询/中断的方式通过 mdio 总线读取 phy 芯片的状态。
在 phy link 状态变化的情况下,正确配置 mac 的状态。(例如:根据 phy 自协商的速率 10/100/1000M 把 mac 配置成对应速率)
phy 芯片状态在 phy 设备注册的时候已经体现,这里详细讲下如何在 phy link 状态变化的情况下,正确配置 mac 的状态。
自协商配置
具体启动 phy 自协商的代码流程如下:
link 状态读取
phy link 状态读取的代码流程如下:
link 状态通知
phy 的 link 状态变化怎么通知给 netdev,并且让 mac 做出相应的配置改变,这个是通过一个中介 phylink 来实现的。
原文作者:人人极客社区
