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 phylink 核心成员速览
  • 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 / mac_ops 关系图 struct phylink 大脑:状态机 + 调度 struct phylink_config 身份证:MAC能力、接口类型 struct phylink_mac_ops 手脚:mac_config / link_up/down PHY device (phydev) net_device (ndev) MAC Driver 实现 ops 持有 config 指针 config->mac_ops phydev 指针 ndev 指针 实现 phylink 通过 config 间接调用 mac_ops

从图上可以清楚看到:struct phylink 持有 config 和 phydev、ndev 的指针;config 内部又保存了 mac_ops 回调表;而 mac_ops 的具体实现则是由你写的 MAC Driver 来填充的。整套框架就像一套“代理人”机制——phylink 只负责决策,具体硬件操作全委托给回调。

组合拳打哪里?

写驱动时,你一般只需要:

  1. 实现 struct phylink_mac_ops 里的回调函数。
  2. 在 probe 函数中分配 struct phylink_config,并初始化 mac_capabilities、type,把 mac_ops 赋值给 config->mac_ops。
  3. 调用 phylink_create() 创建 struct phylink * 实例,将 config 和 ndev 传进去。
  4. 后续一切由 phylink 状态机驱动,你只需安心实现回调。
⚠️ 别踩的坑:有的 SoC 内置的 MAC 同时支持 RGMII 和 SGMII,接口选择是在 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 驱动就不远了。