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节点属性清单
| 属性名 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
reg | u32 | 是 | PHY设备地址(MDIO总线地址,通常为0x00~0x1F) |
compatible | string | 推荐 | PHY厂商兼容字符串,如"ethernet-phy-id001c.c915" |
max-speed | u32 | 否 | 限制PHY最大速率(如1000、100) |
interrupts | u32 | 否 | PHY中断号,用于链路状态变化中断 |
broken-turn-around | empty | 否 | 标志位,表示PHY不支持MDIO turnaround |
reset-gpios | gpio | 否 | PHY复位GPIO引脚 |
reset-assert-us | u32 | 否 | 复位信号保持低电平时间(微秒) |
reset-deassert-us | u32 | 否 | 复位释放后等待时间(微秒) |
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 匹配流程总览
4.3 of_phy_connect 与 phy_connect 函数详解
这两个函数是MAC驱动连接PHY设备的核心入口,它们的区别在于设备树依赖性和参数传递方式。
4.3.1 函数原型与参数对比
| 函数 | 原型 | 设备树依赖 | 适用场景 |
|---|---|---|---|
of_phy_connect | struct 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_connect | struct 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 匹配优先级与规则
- 精确匹配:驱动phy_id与设备phy_id完全一致(phy_id_mask = 0xffffffff)
- 掩码匹配:驱动phy_id & phy_id_mask == 设备phy_id & phy_id_mask
- 通配匹配:驱动phy_id = PHY_ANY_UID(0xffffffff),匹配任何PHY
- compatible匹配:通过设备树compatible属性中的"ethernet-phy-idXXXX.YYYY"字符串匹配
4.4.3 匹配算法流程图
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返回NULL | MDIO总线未就绪或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可以灵活控制匹配精度,开发阶段建议使用精确匹配