深入Linux内核:fixed-link如何用软件‘伪造’一个PHY设备来驱动MAC直连?
深入Linux内核:fixed-link如何用软件‘伪造’一个PHY设备来驱动MAC直连?
在嵌入式系统和网络设备开发中,我们经常会遇到MAC控制器需要直接连接另一个MAC的情况,而中间没有物理PHY芯片。这种场景下,Linux内核通过一种精妙的软件模拟机制——fixed-link,实现了对无PHY硬件的完美支持。本文将带您深入内核源码,揭示这一机制背后的设计哲学与实现细节。
1. fixed-link的起源与设计动机
现代网络设备中,MAC与PHY的协作是网络通信的基础。但在某些特定场景下,比如:
- 两块开发板通过RJ45接口直连
- 芯片内部两个MAC控制器直接对接
- 低成本设备省略外部PHY芯片
这些情况下,传统PHY芯片的缺失会导致一系列问题:
- 协商机制失效:没有PHY意味着无法自动协商速率、双工模式等参数
- 状态监测困难:链路状态、错误统计等关键信息无法获取
- 驱动兼容性问题:现有网络驱动架构严重依赖PHY抽象
Linux内核的解决方案是引入fixed-link机制,其核心设计理念是:
- 透明兼容:让MAC驱动无需修改就能工作
- 配置灵活:通过设备树指定连接参数
- 性能无损:避免不必要的软件开销
2. fixed-link的软件架构剖析
fixed-link的实现涉及内核网络子系统的多个关键组件,其架构可分为三个层次:
2.1 虚拟MDIO总线层
struct fixed_mdio_bus { struct mii_bus *mii_bus; struct list_head phys; }; static struct fixed_mdio_bus platform_fmb = { .phys = LIST_HEAD_INIT(platform_fmb.phys), };这个自定义的fixed_mdio_bus结构体是fixed-link架构的核心,它与常规MDIO总线的关键差异在于:
| 特性 | 常规MDIO总线 | fixed-link MDIO总线 |
|---|---|---|
| 总线操作 | 真实硬件访问 | 软件模拟 |
| 寄存器读写 | 真实PHY寄存器 | 状态机动态生成 |
| 中断处理 | 硬件中断 | 轮询或GPIO监测 |
| 设备发现 | 扫描物理PHY | 静态配置 |
2.2 虚拟PHY设备层
struct fixed_phy { int addr; struct phy_device *phydev; seqcount_t seqcount; struct fixed_phy_status status; int (*link_update)(struct net_device *, struct fixed_phy_status *); struct list_head node; int link_gpio; };每个fixed-link实例都会创建一个fixed_phy结构体,其关键设计点包括:
- 状态保护机制:使用
seqcount实现无锁读取 - GPIO支持:允许通过硬件引脚检测实际链路状态
- 回调接口:支持动态更新连接状态
2.3 设备树交互层
Linux内核支持两种fixed-link设备树配置方式:
传统格式(5元组):
fixed-link = <1 1 1000 0 0>;现代格式(子节点):
fixed-link { speed = <1000>; full-duplex; };内核通过of_phy_is_fixed_link()函数智能识别这两种格式:
bool of_phy_is_fixed_link(struct device_node *np) { /* 检查新格式 */ if (of_get_child_by_name(np, "fixed-link")) return true; /* 检查旧格式 */ if (of_get_property(np, "fixed-link", &len) && len == (5 * sizeof(__be32))) return true; return false; }3. fixed-link的核心实现机制
3.1 虚拟PHY的注册流程
fixed-link PHY的完整注册过程如下:
地址分配:使用IDA分配唯一的PHY地址
phy_addr = ida_simple_get(&phy_fixed_ida, 0, PHY_MAX_ADDR, GFP_KERNEL);状态初始化:根据设备树配置设置初始状态
fp->status = *status; // 包含speed、duplex等参数设备创建:通过通用PHY接口创建设备实例
phy = get_phy_device(fmb->mii_bus, phy_addr, false);驱动绑定:最终绑定到通用PHY驱动
phy->dev.driver = &genphy_driver.mdiodrv.driver;
3.2 寄存器模拟引擎
swphy_read_reg()是fixed-link最精妙的部分,它动态生成PHY寄存器值:
int swphy_read_reg(int reg, const struct fixed_phy_status *state) { switch (reg) { case MII_BMCR: // 基本模式控制寄存器 return speed[speed_index].bmcr & duplex[duplex_index].bmcr; case MII_BMSR: // 基本模式状态寄存器 return BMSR_ANEGCAPABLE | (state->link ? BMSR_LSTATUS : 0); case MII_LPA: // 链路伙伴能力寄存器 return lpa | (state->pause ? LPA_PAUSE_CAP : 0); default: return 0xffff; } }关键寄存器模拟策略:
- BMCR:反映配置的速率和双工模式
- BMSR:始终宣告支持自协商,链路状态根据实际更新
- LPA:模拟一个理想链路伙伴的能力
- PHYID:返回0,表明这不是真实PHY
3.3 与MAC驱动的交互
fixed-link与真实PHY在MAC驱动视角下完全一致:
探测过程:
phy_dev = of_phy_connect(dev, phy_node, &adjust_link, 0, phy_if);状态机处理:
phy_start(phy_dev); // 启动PHY状态机数据收发:
- 完全依赖标准网络设备接口
- 无需特殊处理
4. fixed-link的高级应用场景
4.1 动态链路状态更新
通过注册link_update回调,可以实现动态链路控制:
int my_link_update(struct net_device *dev, struct fixed_phy_status *status) { status->link = gpio_get_value(gpio_link); status->speed = SPEED_1000; return 0; } fixed_phy_add(PHY_POLL, phy_addr, &status, -1); fp->link_update = my_link_update;4.2 多端口负载均衡
在交换机芯片应用中,可以创建多个fixed-link实例:
for (i = 0; i < PORT_COUNT; i++) { status.speed = port_speeds[i]; fixed_phy_register(PHY_POLL, &status, -1, np); }4.3 与真实PHY混合使用
系统可以同时包含真实PHY和fixed-link:
phy-handle = <&real_phy>; fixed-link { speed = <1000>; full-duplex; };内核会智能处理这种混合场景:
- 优先使用
phy-handle指定的真实PHY - 仅当没有真实PHY时回退到fixed-link
5. 性能优化与调试技巧
5.1 关键性能指标
| 操作 | 典型延迟(ns) | 优化建议 |
|---|---|---|
| 寄存器读取 | 50-100 | 避免高频状态轮询 |
| 链路状态更新 | 100-200 | 使用GPIO中断替代轮询 |
| 数据包传输 | 无额外开销 | 保持MTU合理设置 |
5.2 调试工具与方法
查看fixed-link状态:
cat /sys/kernel/debug/fixed_phy/status监控MDIO总线活动:
echo 1 > /sys/kernel/debug/tracing/events/mdio/enable cat /sys/kernel/debug/tracing/trace_pipe常见问题排查:
链路不UP:
- 检查设备树fixed-link配置
- 确认
link_update回调正确设置状态
速率不匹配:
- 确保两端MAC配置相同的fixed-link参数
- 检查MAC驱动是否支持配置的速率
性能低下:
- 避免过于频繁的状态轮询
- 考虑使用GPIO中断检测链路变化
