4、PHY驱动注册与匹配:设备树(DT)中PHY节点描述、of_phy_connect与phy_connect函数、PHY ID匹配机制

4.1 设备树中PHY节点描述规范

在嵌入式Linux系统中,PHY设备通常作为MAC节点的子节点或通过phandle链接进行描述。设备树(Device Tree)是PHY驱动与硬件拓扑绑定的核心载体,其节点描述必须遵循内核PHY绑定规范。

4.1.1 标准PHY节点属性清单

属性名类型是否必需说明
regu32是PHY设备地址(MDIO总线地址,通常为0x00~0x1F)
compatiblestring推荐PHY厂商兼容字符串,如"ethernet-phy-id001c.c915"
max-speedu32否限制PHY最大速率(如1000、100)
interruptsu32否PHY中断号,用于链路状态变化中断
broken-turn-aroundempty否标志位,表示PHY不支持MDIO turnaround
reset-gpiosgpio否PHY复位GPIO引脚
reset-assert-usu32否复位信号保持低电平时间(微秒)
reset-deassert-usu32否复位释放后等待时间(微秒)

4.1.2 典型设备树片段示例

/* 方式一:PHY作为MAC子节点(推荐) */
&macb0 {
    status = "okay";
    phy-mode = "rgmii-id";
    
    ethernet-phy@0 {
        reg = <0x00>;
        compatible = "ethernet-phy-id001c.c915",
                     "ethernet-phy-ieee802.3-c22";
        max-speed = <1000>;
        interrupts = <GIC_SPI 32 IRQ_TYPE_EDGE_RISING>;
        reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
        reset-assert-us = <10000>;
        reset-deassert-us = <20000>;
    };
};

/* 方式二:PHY独立节点,通过phy-handle引用 */
&mac1 {
    status = "okay";
    phy-mode = "sgmii";
    phy-handle = <&phy1>;
};

phy1: ethernet-phy@1 {
    reg = <0x01>;
    compatible = "ethernet-phy-id0141.0dd0", "ethernet-phy-ieee802.3-c22";
    max-speed = <1000>;
};
工程要点:compatible属性中的"ethernet-phy-idXXXX.YYYY"格式,XXXX为PHY ID寄存器(MII_PHYSID1,地址0x02)的值,YYYY为PHY ID寄存器(MII_PHYSID2,地址0x03)的值。内核通过此ID字符串进行PHY驱动匹配。

4.2 PHY驱动匹配机制:从设备树到phy_device

PHY驱动匹配的核心流程分为三个阶段:设备树解析 → PHY ID获取 → 驱动绑定。内核通过of_phy_connect和phy_connect两个关键API完成MAC层与PHY设备的连接。

4.2.1 匹配流程总览

PHY驱动匹配与连接流程 设备树解析 创建 phy_device 结构体 读取 PHY ID 寄存器 (0x02, 0x03) PHY ID 匹配驱动列表 驱动 probe 初始化 MAC 层连接 (phy_connect) 关键数据结构 • phy_device: 描述PHY设备 • phy_driver: 描述PHY驱动 • phy_id: 32位PHY标识符 ID寄存器映射 • MII_PHYSID1 (0x02): OUI[21:16] + OUI[15:0] • MII_PHYSID2 (0x03): OUI[6:0] + 厂商型号 + 版本

4.3 of_phy_connect 与 phy_connect 函数详解

这两个函数是MAC驱动连接PHY设备的核心入口,它们的区别在于设备树依赖性和参数传递方式。

4.3.1 函数原型与参数对比

函数原型设备树依赖适用场景
of_phy_connectstruct phy_device *of_phy_connect(struct net_device *dev, struct device_node *phy_np, void (*hndlr)(struct net_device *), u32 flags, phy_interface_t iface)强依赖,需传入device_node设备树平台(推荐)
phy_connectstruct phy_device *phy_connect(struct net_device *dev, const char *bus_id, void (*hndlr)(struct net_device *), phy_interface_t iface)无依赖,需传入总线ID字符串非设备树平台或PCI网卡

4.3.2 of_phy_connect 典型调用示例

#include <linux/phy.h>
#include <linux/of_net.h>

static int mac_probe(struct platform_device *pdev)
{
    struct net_device *ndev;
    struct phy_device *phydev;
    struct device_node *phy_np;
    phy_interface_t phy_mode;
    
    /* 1. 从设备树获取PHY节点 */
    phy_np = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
    if (!phy_np) {
        /* 回退:查找子节点中的PHY */
        phy_np = of_get_child_by_name(pdev->dev.of_node, "ethernet-phy");
        if (!phy_np) {
            dev_err(&pdev->dev, "no PHY node found\n");
            return -ENODEV;
        }
    }
    
    /* 2. 获取PHY接口模式 */
    phy_mode = of_get_phy_mode(pdev->dev.of_node);
    if (phy_mode < 0) {
        dev_err(&pdev->dev, "invalid phy-mode\n");
        of_node_put(phy_np);
        return -EINVAL;
    }
    
    /* 3. 连接PHY设备 */
    phydev = of_phy_connect(ndev, phy_np, phy_link_change_callback,
                            PHY_INTERFACE_MODE_NA, phy_mode);
    if (!phydev) {
        dev_err(&pdev->dev, "of_phy_connect failed\n");
        of_node_put(phy_np);
        return -ENODEV;
    }
    
    /* 4. 配置PHY */
    phydev->supported &= PHY_GBIT_FEATURES;
    phydev->advertising = phydev->supported;
    
    dev_info(&pdev->dev, "PHY [%s] connected: %s, id=0x%08x\n",
             phydev->drv->name, phydev_name(phydev), phydev->phy_id);
    
    of_node_put(phy_np);
    return 0;
}

4.3.3 phy_connect 典型调用示例(非设备树场景)

/* 适用于PCI网卡或固定MDIO总线场景 */
static int pci_net_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct net_device *ndev;
    struct phy_device *phydev;
    char bus_id[MII_BUS_ID_SIZE + 3];
    
    /* 假设MDIO总线名称为"mdio0",PHY地址为0x01 */
    snprintf(bus_id, sizeof(bus_id), PHY_ID_FMT, "mdio0", 0x01);
    
    /* 连接PHY */
    phydev = phy_connect(ndev, bus_id, phy_link_change_callback,
                         PHY_INTERFACE_MODE_RGMII);
    if (IS_ERR(phydev)) {
        dev_err(&pdev->dev, "phy_connect failed: %ld\n", PTR_ERR(phydev));
        return PTR_ERR(phydev);
    }
    
    dev_info(&pdev->dev, "PHY connected at %s\n", bus_id);
    return 0;
}
常见陷阱:of_phy_connect内部会调用phy_attach_direct,该函数会尝试读取PHY ID寄存器。如果MDIO总线尚未就绪或PHY地址错误,会导致连接失败。务必确保在调用前MDIO控制器已初始化完成。

4.4 PHY ID匹配机制深度解析

PHY ID匹配是驱动绑定的核心决策环节。内核维护一个全局的phy_drivers链表,每个PHY驱动通过phy_driver_register注册时携带其支持的PHY ID列表。

4.4.1 PHY ID结构定义

/* 内核源码:include/linux/phy.h */
#define PHY_ANY_ID "MATCH ANY PHY ID"
#define PHY_ANY_UID 0xffffffff

struct phy_device {
    /* ... */
    u32 phy_id;         /* 32位PHY标识符,由PHY ID寄存器读取 */
    u32 phy_id_mask;    /* 匹配掩码,用于模糊匹配 */
    /* ... */
};

struct phy_driver {
    /* ... */
    u32 phy_id;         /* 驱动支持的PHY ID */
    u32 phy_id_mask;    /* 匹配掩码,默认0xffffffff */
    const char *name;    /* 驱动名称 */
    /* ... */
};

4.4.2 匹配优先级与规则

  1. 精确匹配:驱动phy_id与设备phy_id完全一致(phy_id_mask = 0xffffffff)
  2. 掩码匹配:驱动phy_id & phy_id_mask == 设备phy_id & phy_id_mask
  3. 通配匹配:驱动phy_id = PHY_ANY_UID(0xffffffff),匹配任何PHY
  4. compatible匹配:通过设备树compatible属性中的"ethernet-phy-idXXXX.YYYY"字符串匹配

4.4.3 匹配算法流程图

PHY ID 匹配算法决策树 开始匹配 compatible? 解析ID字符串匹配 读取PHY ID寄存器 精确匹配? 掩码匹配? 通配匹配? 匹配成功 → probe 匹配失败 → 使用通用PHY驱动 是 否 是 是 是 匹配掩码示例 • 驱动phy_id=0x001cc915 • 掩码0xfffffff0:忽略版本号 • 掩码0xffff0000:仅匹配OUI

4.5 工程实践:PHY ID匹配调试技巧

调试命令速查表:
  • ls /sys/bus/mdio_bus/devices/ — 查看系统识别的MDIO设备
  • cat /sys/bus/mdio_bus/devices/*/phy_id — 读取PHY ID值
  • cat /sys/bus/mdio_bus/drivers/*/bind — 查看驱动绑定状态
  • mdio-tool eth0 dump 0x02 0x03 — 用户态读取PHY ID寄存器
  • dmesg | grep "PHY" — 查看PHY驱动加载日志

4.5.1 常见匹配失败原因分析

现象可能原因排查方法
PHY驱动未加载PHY ID不匹配或驱动未注册检查compatible字符串和phy_id定义
phy_connect返回NULLMDIO总线未就绪或PHY地址错误确认reg属性值与硬件跳线一致
PHY工作在10M半双工匹配到通用PHY驱动,未加载厂商驱动检查phy_id_mask是否过宽
中断不触发设备树中断描述错误检查interrupts属性和GPIO配置

4.5.2 内核日志调试示例

# 正常匹配日志示例
[    2.345678] mdio_bus 2188000.ethernet-ffffffff: MDIO device at address 0 is "RTL8211F Gigabit Ethernet"
[    2.345789] libphy: 2188000.ethernet-ffffffff: probed
[    2.345890] rtl8211f 2188000.ethernet-ffffffff:00: attached PHY driver [RTL8211F Gigabit Ethernet] (mii_bus:phy_addr=2188000.ethernet-ffffffff:00, irq=32)
[    2.345901] macb 2188000.ethernet eth0: PHY [2188000.ethernet-ffffffff:00] driver [RTL8211F Gigabit Ethernet]

# 匹配失败日志示例
[    2.346789] mdio_bus 2188000.ethernet-ffffffff: MDIO device at address 0 is "Unknown PHY"
[    2.346800] libphy: 2188000.ethernet-ffffffff: probed
[    2.346811] macb 2188000.ethernet eth0: of_phy_connect failed: -ENODEV
实战建议:在开发阶段,可以在PHY驱动中临时设置phy_id_mask = 0xffffffff进行精确匹配验证。确认ID正确后,再根据实际需求放宽掩码(如忽略版本号)。使用mdio-tool或devmem2直接读取PHY寄存器0x02和0x03,可以快速验证硬件连接和PHY ID值。

4.6 本章小结

  • 设备树中PHY节点必须包含reg属性指定MDIO地址,compatible属性推荐使用"ethernet-phy-idXXXX.YYYY"格式
  • of_phy_connect适用于设备树平台,phy_connect适用于非设备树场景
  • PHY ID匹配优先级:精确匹配 → 掩码匹配 → 通配匹配 → 通用驱动
  • 匹配失败时优先检查设备树reg地址、MDIO总线状态和PHY ID寄存器值
  • 使用phy_id_mask可以灵活控制匹配精度,开发阶段建议使用精确匹配