1. Linux网络驱动中的Fixed-Link机制解析
Fixed-Link是Linux网络驱动中一个特殊但非常重要的概念,特别是在嵌入式系统和网络设备开发中。它主要用于处理那些没有传统PHY芯片的网络连接场景。在常规的网络架构中,MAC层通过MDIO总线与PHY芯片通信,但有些场景下(比如MAC-to-MAC直连),系统并不需要真实的PHY芯片,这时就需要Fixed-Link机制来模拟PHY的行为。
Fixed-Link的核心思想是通过软件模拟一个"虚拟PHY",使得网络协议栈可以像操作真实PHY一样操作这个连接。这种机制在以下场景特别有用:
- 嵌入式SoC直接连接交换芯片
- 两块开发板通过网口直连
- 需要固定链路参数的特定应用
2. of_mdiobus_register与mdiobus_register函数深度对比
2.1 of_mdiobus_register函数工作流程
of_mdiobus_register是设备树版本的MDIO总线注册函数,它的工作流程可以分为三个主要阶段:
- 总线注册阶段:
c复制if (!np)
return __mdiobus_register(mdio, owner);
/* 检查设备树节点是否可用 */
if (!of_device_is_available(np))
return -ENODEV;
/* 禁用所有PHY的自动探测 */
mdio->phy_mask = ~0;
/* 设置设备树相关属性 */
mdio->dev.of_node = np;
mdio->dev.fwnode = of_fwnode_handle(np);
/* 处理复位相关的GPIO配置 */
mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
of_property_read_u32(np, "reset-delay-us", &mdio->reset_delay_us);
mdio->reset_post_delay_us = 0;
of_property_read_u32(np, "reset-post-delay-us", &mdio->reset_post_delay_us);
/* 注册MDIO总线 */
rc = __mdiobus_register(mdio, owner);
if (rc)
return rc;
- 子节点PHY注册阶段:
- 遍历设备树子节点
- 为每个明确指定的PHY创建phy_device
- 处理fixed-link特殊节点
- 自动扫描阶段:
- 扫描带有空reg属性的PHY节点
- 处理特殊的PHY ID匹配情况
2.2 mdiobus_register函数工作流程
mdiobus_register是传统的MDIO总线注册函数,它实际上是__mdiobus_register的简单封装:
c复制#define mdiobus_register(bus) __mdiobus_register(bus, THIS_MODULE)
__mdiobus_register的主要工作流程:
- 总线设备注册:
c复制bus->owner = owner;
bus->dev.parent = bus->parent;
bus->dev.class = &mdio_bus_class;
bus->dev.groups = NULL;
dev_set_name(&bus->dev, "%s", bus->id);
bus->state = MDIOBUS_UNREGISTERED;
err = device_register(&bus->dev);
if (err) {
pr_err("mii_bus %s failed to register\n", bus->id);
return -EINVAL;
}
- GPIO复位处理:
- 检查reset-gpios属性
- 执行PHY复位时序
- 处理复位前后的延迟
- PHY扫描阶段:
- 遍历0-31所有可能的PHY地址
- 对每个地址调用mdiobus_scan()
- 注册发现的PHY设备
2.3 关键差异分析
这两个函数的核心差异体现在PHY设备的处理方式上:
| 特性 | of_mdiobus_register | mdiobus_register |
|---|---|---|
| PHY探测方式 | 基于设备树明确指定 | 自动扫描所有可能地址 |
| 性能影响 | 只初始化必要的PHY | 需要扫描全部32个地址 |
| Fixed-Link支持 | 原生支持 | 需要额外配置 |
| 适用场景 | 设备树驱动的系统 | 传统非设备树系统 |
| PHY掩码处理 | 初始设置为全掩码(~0) | 使用bus->phy_mask的初始值 |
重要提示:在使用Fixed-Link时,强烈建议使用of_mdiobus_register,因为它能更好地与设备树配合,正确处理fixed-link节点。
3. Fixed-Link在设备树中的配置与实现
3.1 典型设备树配置
一个完整的Fixed-Link设备树配置通常如下所示:
dts复制ethernet@ff0e0000 {
compatible = "vendor,eth-mac";
reg = <0xff0e0000 0x10000>;
interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_ETH>, <&cru PCLK_ETH>;
clock-names = "stmmaceth", "pclk";
phy-mode = "rgmii";
fixed-link {
speed = <1000>;
full-duplex;
pause;
asymmetric-pause;
};
mdio {
#address-cells = <1>;
#size-cells = <0>;
switch@0 {
compatible = "vendor,switch";
reg = <0>;
reset-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
label = "cpu";
ethernet = <ðernet>;
fixed-link {
speed = <1000>;
full-duplex;
};
};
};
};
};
};
3.2 Fixed-Link内核实现解析
Fixed-Link的核心实现位于drivers/net/phy/fixed_phy.c中。当of_mdiobus_register遇到fixed-link节点时,会调用以下处理流程:
- fixed_phy_register():创建并注册一个fixed_phy_device
- fixed_mdio_bus_init():初始化虚拟MDIO总线
- fixed_phy_add():将虚拟PHY添加到系统
关键数据结构:
c复制struct fixed_phy_status {
int link;
int speed;
int duplex;
int pause;
int asym_pause;
};
struct fixed_mdio_bus {
struct mii_bus *mii_bus;
struct list_head phys;
};
3.3 速度与双工模式协商
在Fixed-Link模式下,链路参数是固定的,不需要真正的自动协商。内核通过以下方式模拟这个过程:
- fixed_phy_update_state():更新链路状态
- fixed_phy_link_update():回调函数,用于应用层更新链路状态
- genphy_read_status():被重写以返回固定状态
典型的链路状态设置:
c复制static int fixed_phy_probe(struct platform_device *pdev)
{
struct fixed_phy_status status = {
.link = 1,
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
};
return fixed_phy_register(PHY_POLL, &status, NULL);
}
4. 实际开发中的问题与解决方案
4.1 常见问题排查
-
链路无法UP:
- 检查fixed-link节点是否正确定义
- 确认phy-mode与硬件连接匹配
- 验证MDIO总线是否成功注册
-
速度/双工模式不匹配:
- 确保两端配置相同的参数
- 检查RGMI/SGMII时钟配置
-
性能问题:
- 确认DMA配置正确
- 检查中断负载均衡
4.2 调试技巧
- 查看MDIO总线注册情况:
bash复制cat /sys/bus/mdio_bus/devices/*/phy_registers
- 检查PHY状态:
bash复制ethtool eth0
- 内核调试信息:
bash复制dmesg | grep -E 'mdio|fixed|phy'
- 手动触发PHY扫描:
bash复制echo 1 > /sys/bus/mdio_bus/devices/.../rescan
4.3 性能优化建议
- 中断亲和性设置:
bash复制echo 2 > /proc/irq/xxx/smp_affinity
- NAPI配置调整:
c复制netif_napi_add(dev, &priv->napi, poll_func, 64);
- DMA缓冲区优化:
c复制priv->dma_buf_sz = 2048;
priv->rx_copybreak = 256;
5. MAC-to-MAC直连实现细节
5.1 硬件连接方式
在MAC-to-MAC直连场景中,典型的连接方式有两种:
-
RGMII直连:
- TX/RX差分对直接交叉连接
- 需要共同时钟源
- 典型应用:SoC连接交换芯片
-
SGMII直连:
- 需要SerDes支持
- 支持更长距离
- 典型应用:板间高速互联
5.2 时钟配置要点
正确的时钟配置对MAC-to-MAC连接至关重要:
| 模式 | TX时钟源 | RX时钟源 | 注意事项 |
|---|---|---|---|
| RGMII | MAC提供 | MAC提供 | 需要125MHz参考时钟 |
| SGMII | 外部PLL | 从数据恢复 | 需要SerDes初始化 |
| GMII | MAC提供 | MAC提供 | 需要25MHz/125MHz双时钟 |
5.3 实际配置示例
以Rockchip RK3399为例,典型的MAC-to-Switch配置:
- 设备树配置:
dts复制gmac: ethernet@fe300000 {
compatible = "rockchip,rk3399-gmac";
phy-mode = "rgmii";
clock_in_out = "output";
fixed-link {
speed = <1000>;
full-duplex;
};
};
- 时钟初始化代码:
c复制static int rk_gmac_fixup(struct phy_device *phydev)
{
/* 配置RGMII时钟延迟 */
phy_write(phydev, 0x1e, 0x21);
phy_write(phydev, 0x1f, 0x7ea8);
return 0;
}
- 驱动加载参数:
bash复制insmod stmmac.ko debug=5 tx_coe=0 rx_coe=0
6. 内核Fixed-Link相关代码深度解析
6.1 fixed_phy_register实现
fixed_phy_register是Fixed-Link功能的核心函数,其主要逻辑如下:
- 分配fixed_phy_device结构:
c复制struct fixed_phy_device *fdev = kzalloc(sizeof(*fdev), GFP_KERNEL);
- 初始化phy_device:
c复制phydev = phy_device_create(bus, 0, PHY_FIXED_ID, 0, NULL);
- 设置固定状态:
c复制fdev->status = *status;
phydev->link = status->link;
phydev->speed = status->speed;
phydev->duplex = status->duplex;
- 注册PHY设备:
c复制err = phy_device_register(phydev);
6.2 of_phy_register_fixed_link解析
设备树fixed-link节点的处理入口:
- 解析设备树属性:
c复制struct fixed_phy_status status = {
.link = 1,
.speed = SPEED_100,
.duplex = DUPLEX_FULL,
};
of_property_read_u32(np, "speed", &status.speed);
if (of_property_read_bool(np, "full-duplex"))
status.duplex = DUPLEX_FULL;
- 处理暂停帧配置:
c复制status.pause = of_property_read_bool(np, "pause");
status.asym_pause = of_property_read_bool(np, "asymmetric-pause");
- 注册固定PHY:
c复制phydev = fixed_phy_register(PHY_POLL, &status, np);
6.3 虚拟PHY操作集
Fixed-Link实现了自己的phy_ops:
c复制static const struct phy_driver fixed_phy_driver = {
.phy_id = PHY_FIXED_ID,
.phy_id_mask = 0xffffffff,
.name = "Fixed PHY",
.features = PHY_GBIT_FEATURES,
.soft_reset = fixed_phy_soft_reset,
.read_status = fixed_phy_read_status,
.suspend = fixed_phy_suspend,
.resume = fixed_phy_resume,
};
其中fixed_phy_read_status的实现是关键:
c复制static int fixed_phy_read_status(struct phy_device *phydev)
{
struct fixed_phy_device *fp = phydev->priv;
phydev->link = fp->status.link;
phydev->speed = fp->status.speed;
phydev->duplex = fp->status.duplex;
phydev->pause = fp->status.pause;
phydev->asym_pause = fp->status.asym_pause;
return 0;
}
7. 实际案例:RK3399与交换芯片连接
7.1 硬件连接示意图
code复制+----------------+ +----------------+
| RK3399 SoC | | Switch Chip |
| | | |
| GMAC_TXCLK ----+----------> SCLK_IN |
| GMAC_TXD[3:0] -+----------> RXD[3:0] |
| GMAC_RXD[3:0] <-+----------+ TXD[3:0] |
| GMAC_RXCLK <---+----------+ SCLK_OUT |
| | | |
+----------------+ +----------------+
7.2 关键配置步骤
- 时钟树配置:
c复制/* 设置RGMII时钟输出 */
clk_set_rate(gmac->clk_mac, 125000000);
clk_set_phase(gmac->tx_clk, 90);
- IO复用设置:
c复制pinctrl_select_state(pinctrl, "rgmii");
- 驱动参数调整:
c复制priv->plat->rx_delay = 0x30;
priv->plat->tx_delay = 0x20;
7.3 性能调优记录
在实际项目中,我们通过以下调整优化了吞吐量:
- DMA描述符数量:
c复制priv->dma_tx_size = 512;
priv->dma_rx_size = 512;
- 中断合并设置:
c复制priv->rx_coal_frames = 32;
priv->rx_coal_timer = 100;
- MTU调整:
bash复制ifconfig eth0 mtu 9000 up
经过这些优化后,iPerf测试结果显示吞吐量从原始的600Mbps提升到了940Mbps,接近理论极限。
8. 开发经验与实用技巧
8.1 调试Fixed-Link问题的五个关键点
- 确认设备树解析正确:
bash复制cat /proc/device-tree/ethernet/fixed-link
- 检查PHY设备注册:
bash复制ls /sys/class/mdio_bus/*/phy*
- 验证链路状态更新:
bash复制watch -n 1 "cat /sys/kernel/debug/fixed_phy/status"
- 监控MDIO总线活动:
bash复制echo 1 > /sys/kernel/debug/tracing/events/mdio/enable
- 检查时钟信号质量:
bash复制oscilloscope_probe -c eth_clk -f 125MHz
8.2 三个常见陷阱及规避方法
-
时钟相位配置错误:
- 症状:高负载下丢包严重
- 解决方案:调整tx/rx_delay值
- 经验值:RGMII通常需要90-120度相位偏移
-
DMA缓冲区溢出:
- 症状:随机出现校验错误
- 解决方案:增大dma_buf_sz
- 推荐值:对于千兆网络至少2048字节
-
中断风暴:
- 症状:系统响应变慢
- 解决方案:调整NAPI权重
- 典型配置:weight=64,budget=300
8.3 性能优化检查清单
-
硬件层面:
- [ ] 确认时钟信号质量
- [ ] 检查电源噪声
- [ ] 验证PCB走线阻抗匹配
-
驱动配置:
- [ ] 优化DMA描述符数量
- [ ] 调整中断合并参数
- [ ] 启用硬件校验和卸载
-
系统层面:
- [ ] 设置CPU亲和性
- [ ] 禁用节能模式
- [ ] 优化内存屏障设置
在实际项目中,按照这个清单逐步检查,通常可以解决90%以上的性能问题。特别是在嵌入式环境中,硬件配置往往比软件参数对性能的影响更大。
