4. phylink 核心数据结构解析
开始写 phylink 驱动的配套代码,你首先得跟三个结构体打交道。
它们分别是 struct phylink、struct phylink_config 和 struct phylink_mac_ops。
说白了,这三兄弟就是 phylink 框架的钢筋水泥。把它们的角色搞清楚,后面写驱动才能顺风顺水。
我记得第一次接触 phylink 时,对着头文件看了半天,心里直犯嘀咕:“怎么这么多回调?到底哪个先调?”
后来踩了几个坑才明白,这三个结构体各管一摊,组合起来才构成了完整的链路控制逻辑。
4.1 struct phylink —— 核心状态机
struct phylink 是 phylink 框架的“大脑”,负责维护链路状态、调度回调、协调 MAC 与 PHY 的交互。
每个网络接口对应一个 struct phylink 实例,通常在 ndo_open 中创建,在 ndo_stop 中销毁。
关键成员我就不一一列了,我挑几个你在适配中一定会用到的:
struct phy_device *phydev—— 当前绑定的 PHY 设备指针,phylink 通过它读取 PHY 状态。struct net_device *ndev—— 上层网络设备,收发数据靠它。struct phylink_config *config—— MAC 侧配置,包括能力位掩码、接口类型等。const struct phylink_mac_ops *ops—— MAC 回调表,所有硬件操作都在里面。unsigned int link_an_mode—— 当前自协商模式(AN, In-band AN, Forced)。
// 头文件:include/linux/phylink.h
struct phylink {
struct phy_device *phydev;
struct net_device *ndev;
struct phylink_config *config;
const struct phylink_mac_ops *ops;
unsigned int link_an_mode;
/* ... 内部状态、定时器、互斥锁等 */
};
struct phylink * 指针,而不是每次通过网卡结构体去查。这样在回调函数中访问 phylink 实例更快,也避免重复的 container_of 查找。
4.2 struct phylink_config —— MAC 能力描述卡
如果说 phylink 是“大脑”,那 struct phylink_config 就是“身份证”。它告诉 phylink 框架:你的 MAC 芯片支持什么速率、什么接口模式、是否支持流控等。
这个结构体的最关键字段是 mac_capabilities,它是一个位掩码,表示 MAC 侧支持的速率和双工组合。
常用宏有 MAC_10、MAC_100、MAC_1000、MAC_2500FD 等等。你想想看,如果你的 MAC 只支持千兆全双工,却把能力位设成了百兆半双工,那协商出来的结果肯定乱套。我就在这上面吃过亏——当时板子死活协商不上千兆,查了半天,结果是能力位少写了一个 MAC_1000FD。
struct phylink_config {
struct device *dev;
unsigned long mac_capabilities; // MAC能力位掩码
phy_interface_t type; // 默认接口模式(如 PHY_INTERFACE_MODE_RGMII)
bool mac_managed_pm; // MAC是否自己管理电源?通常设为 false
/* 内部字段,驱动不用管 */
const struct phylink_mac_ops *mac_ops;
};
mac_capabilities 和 PHY 的能力搞混。记住,mac_capabilities 只代表 MAC 侧硬件能力,PHY 侧的能力由 PHY 自己的寄存器报告。phylink 在协商时会取 MAC 和 PHY 能力集合的交集,所以两边都要配准确。
4.3 struct phylink_mac_ops —— 硬件操作回调表
这是最“干活”的部分。所有与 MAC 硬件直接打交道的工作,都通过这个回调表完成。
我把它称为“MAC 驱动的手脚”。
回调函数数量不少,但核心的就下面几个。我以表格形式列出来,方便你对照:
| 回调函数 | 触发时机 | 你需要做什么 |
|---|---|---|
mac_config(config, mode, state) | 链路模式或接口类型改变时 | 配置 MAC 的速率、双工、流控、接口切换(如 RGMII 时延) |
mac_link_up(config, mode, speed, duplex, tx_pause, rx_pause) | PHY 报告链路已建立 | 使能 MAC 发送/接收、配置流控寄存器、启动队列 |
mac_link_down(config, mode) | PHY 报告链路断开 | 停止 MAC 发送/接收、清空队列、关闭 PHY 时钟(可选) |
mac_an_restart(config) | 用户触发自协商重启 | 向 MAC 侧自协商模块发出重启命令(如果 MAC 参与协商) |
struct phylink_mac_ops {
void (*mac_config)(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state);
void (*mac_link_up)(struct phylink_config *config, struct phy_device *phy,
unsigned int mode, phy_interface_t interface,
int speed, int duplex, bool tx_pause, bool rx_pause);
void (*mac_link_down)(struct phylink_config *config, unsigned int mode,
phy_interface_t interface);
void (*mac_an_restart)(struct phylink_config *config);
/* 可选:mac_pcs_get_state、mac_fixed_link 等 */
};
mac_config 一定在 mac_link_up 之前被调用。常见新手错误是在
mac_config 里就把 TX/RX 都打开了,结果 mac_link_up 时发现 PHY 还没准备好,导致丢包。正确的做法是:mac_config 只配寄存器不使能数据路径,使能放到 mac_link_up。
4.4 三者的协作关系
下面这张 SVG 图能帮你把三个结构体之间的指针关系理清楚。我当年就是靠画类似的关系图才彻底搞明白的。
从图上可以清楚看到:struct phylink 持有 config 和 phydev、ndev 的指针;config 内部又保存了 mac_ops 回调表;而 mac_ops 的具体实现则是由你写的 MAC Driver 来填充的。整套框架就像一套“代理人”机制——phylink 只负责决策,具体硬件操作全委托给回调。
写驱动时,你一般只需要:
- 实现
struct phylink_mac_ops里的回调函数。 - 在 probe 函数中分配
struct phylink_config,并初始化mac_capabilities、type,把mac_ops赋值给config->mac_ops。 - 调用
phylink_create()创建struct phylink *实例,将 config 和 ndev 传进去。 - 后续一切由 phylink 状态机驱动,你只需安心实现回调。
mac_config 里做的。但有些外置 PHY 需要在初始化时就固定接口模式,否则 PHY 不识别。这种情况我会在 probe 阶段先通过 device tree 解析出接口模式,直接写入 phylink_config->type,并在 mac_config 中提前把 MAC 侧 GPIO/寄存器切好。这样能避免回调触发时 PHY 已经挂掉。
总结一下:struct phylink 是总控,struct phylink_config 是配置卡,struct phylink_mac_ops 是操作表。三兄弟各司其职,配合 PHY 驱动就能把链路层管理得明明白白。
搞懂它们,你离一个合格的 phylink 驱动就不远了。