接前一篇文章:Linux MDIO子系统深度剖析:从原理到实践(3)

四、驱动匹配流程

1. 设备注册与扫描

MDIO子系统中的设备注册过程始于MDIO总线控制器的初始化。当MDIO总线控制器驱动被加载时,它会创建一个mii_bus实例初始化必要的硬件资源,并调用mdiobus_register函数注册该总线

总线注册的关键步骤是mdiobus_scan函数,它负责探测总线上连接的PHY设备。探测过程会遍历所有可能的PHY地址(0到31),对每个地址执行以下操作:

1)发送读取PHYID的请求:通过MDIO总线读取该地址上的PHY标识符寄存器(REG2和REG3);

2)验证响应数据:检查读取到的值是否为有效的PHY ID(全0或全1通常表示没有设备);

3)创建phy_device:如果检测到有效的PHY ID,系统会创建一个phy_device实例并初始化关键字段;

4)注册设备:将新创建的phy_device注册到MDIO总线上。

这个扫描过程可以类比于酒店前台在客人入住前检查所有房间,确定哪些房间已经有客人入住,哪些还空着。对于每个有客人的房间,前台会创建一份客户档案,记录客人的基本信息和房间号。

在实际代码中,扫描过程的核心逻辑如下(简化版):

// 简化的总线扫描逻辑
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
    // 跳过已知无效的地址
    if (mii_bus->phy_map[addr])
        continue;
        
    // 读取 PHY 标识符
    phy_id = mdiobus_read(mii_bus, addr, MII_PHYSID1);
    if (phy_id < 0)
        continue;
        
    if (phy_id == 0xffff || phy_id == 0x0000)
        continue; // 无效的 PHY ID
        
    // 创建并注册 phy_device
    phy_dev = get_phy_device(mii_bus, addr, false);
    if (!IS_ERR(phy_dev)) {
        err = phy_device_register(phy_dev);
        if (err) {
            put_device(&phy_dev->dev);
        } else {
            mii_bus->phy_map[addr] = phy_dev;
        }
    }
}

值得注意的是,除了动态扫描,MDIO子系统也支持通过设备树静态指定PHY设备。在这种情况下,设备树中的MDIO节点会包含子节点,每个子节点描述一个PHY设备,指定其地址、兼容字符串和其它配置参数。这种方式在嵌入式系统中特别有用,可以避免自动扫描的不确定性,并提供更精确的设备配置。

2. 驱动匹配过程

驱动匹配是MDIO子系统中的核心环节,它确保了每个被探测到的PHY设备能够与合适的驱动程序关联起来匹配过程遵循Linux设备模型的标准流程,由mdio_bus_match函数实现

当一个新的PHY驱动通过phy_driver_register注册到系统时,内核会遍历MDIO总线上所有已注册但尚未绑定驱动的PHY设备,尝试将新驱动与这些设备匹配;反过来,当一个新的PHY设备注册时,内核也会遍历所有已注册的驱动,寻找匹配项。

匹配算法的核心是基于 PHY 标识符的比较,具体过程如下:

1)提取设备标识符:从phy_device中获取设备的phy_id字段,这个值是在总线扫描时从硬件的 PHYID寄存器读取的;
2)应用驱动掩码:对于每个候选驱动,使用驱动的phy_id_mask对设备的phy_id进行按位与操作,过滤掉不关心的位;
3)比较标识符:将掩码后的设备phy_id与驱动的phy_id进行比较,如果完全匹配,则表明该驱动支持这个设备;
4)执行探测函数:如果标识符匹配,内核会调用驱动的probe方法来完成设备初始化。

除了基于PHYID的主要匹配方式,MDIO子系统还支持其它匹配方法:

  • 设备树兼容性匹配:如果PHY设备是通过设备树定义的,系统会检查设备的“compatible”属性是否与驱动定义的兼容性列表匹配。
  • ACPI匹配:在支持ACPI的系统中,可以使用ACPI设备ID进行匹配。
  • 手动绑定:通过 sysfs 接口手动将设备与驱动关联。

匹配成功后,phy_device的drv字段会被设置为指向对应的phy_driver,而驱动的probe函数会被调用以完成设备特定的初始化工作。

3. probe过程详解

驱动的probe方法是PHY设备初始化的核心环节,它负责将通用的phy_device与具体的硬件特性关联起来。probe过程通常包括以下步骤:

1)硬件验证:通过读取特定的状态寄存器或标识符寄存器,确认PHY硬件确实存在且功能正常;
2)功能检测:检测PHY支持的特性,如支持的速度、双工模式、自动协商能力等;
3)中断配置:如果PHY支持中断,配置中断处理函数以响应链路状态变化等事件;
4)初始化配置:设置PHY的初始工作参数,如广播能力、电源管理模式等;
5)注册网络设备:将PHY设备与上层的网络设备关联,建立从网络接口到PHY的完整数据路径。

以下是一个简化的probe函数示例:

static int example_phy_probe(struct phy_device *phydev)
{
    int err;
    
    // 读取额外的标识寄存器进行硬件验证
    err = phy_read(phydev, EXAMPLE_EXTRA_ID_REG);
    if (err < 0)
        return err;
        
    if ((err & EXTRA_ID_MASK) != EXPECTED_EXTRA_ID) {
        dev_err(&phydev->dev, "Extra ID mismatch: expected 0x%x, got 0x%x\n",
            EXPECTED_EXTRA_ID, err & EXTRA_ID_MASK);
        return -ENODEV;
    }
    
    // 配置 PHY 特定的功能
    err = phy_write(phydev, MII_EXAMPLE_CTRL_REG, CTRL_INIT_VALUE);
    if (err < 0)
        return err;
    
    // 设置驱动特定的私有数据
    struct example_priv *priv = devm_kzalloc(&phydev->dev, 
        sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
        
    phydev->priv = priv;
    
    // 初始化私有数据
    priv->some_setting = DEFAULT_VALUE;
    
    dev_info(&phydev->dev, "Example PHY driver successfully probed\n");
    return 0;
}

probe函数成功返回后,PHY设备就进入了就绪状态,可以被上层网络子系统使用了。此时,PHY驱动会开始监控链路的连接状态,并在状态变化时通知上层,从而触发网络接口的UP或DOWN事件。

MDIO驱动匹配完整流程如下图所示:

图4:MDIO驱动匹配完整流程

更多内容请看下回。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐