以太网扫盲(四)phy驱动link up流程分析

1. 简介

在调试网口驱动的过程中发现phy芯片的驱动框架结构还有点复杂,不仔细研究的话还不好搞懂,另外百度到的资料也不够全面,这篇就总结梳理一下这方面的知识。

我们知道一个 phy 驱动的原理是非常简单的,一般流程如下:

  • 1、用轮询/中断的方式通过 mdio 总线读取 phy 芯片的状态。
  • 2、在 phy link 状态变化的情况下,正确配置 mac 的状态。(例如:根据 phy 自协商的速率 10/100/1000M 把 mac 配置成对应速率)

下面就以 stmmac 网口驱动为例,展示一下 phy 驱动整个调用过程。整个 phy 驱动的主要调用流程如下图所示:

2. phy_device

首先每个 phy 芯片会创建一个 struct phy_device 类型的设备,对应的有 struct phy_driver 类型的驱动,这两者实际上是挂载在 mdio_bus_type 总线上的。

2.1 mdiobus

mdio 总线的定义:

 
struct bus_type mdio_bus_type = {
.name = "mdio_bus",
.dev_groups = mdio_bus_dev_groups,
.match = mdio_bus_match,
.uevent = mdio_uevent,
};

2.2 mdio device

网口驱动在初始化 probe() 时遍历 dts 的定义创建相应struct phy_device 类型的设备:

 
stmmac_dvr_probe()
`-| stmmac_mdio_register()
`-| stmmac_mdio_register()
`-| {
| new_bus = mdiobus_alloc();
| new_bus->read = &stmmac_xgmac2_mdio_read; // mdio 读写函数
| new_bus->write = &stmmac_xgmac2_mdio_write;
|
| of_mdiobus_register(new_bus, mdio_node);
`-| of_mdiobus_register_phy(mdio, child, addr);
`-| get_phy_device()
`-| get_phy_c22_id(bus, addr, &phy_id);
`-| {
| phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); // 通过 mdio 总线读取 phy 芯片 id
| phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);
| }
| phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
`-| {
| mdiodev->dev.bus = &mdio_bus_type;
| mdiodev->dev.type = &mdio_bus_phy_type;
| mdiodev->bus_match = phy_bus_match;
| INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); // 这里就是 phy device 的轮询任务
| }
| of_mdiobus_phy_device_register()
`-| phy_device_register()
`-| device_add()

2.3 mdio driver

mdio bus 会根据 struct phy_device 的 phy id 和 struct phy_driver 进行 match,如果没有找到对应驱动会使用通用驱动 genphy_driver

 
static struct phy_driver genphy_driver = {
.phy_id = 0xffffffff,
.phy_id_mask = 0xffffffff,
.name = "Generic PHY",
.get_features = genphy_read_abilities,
.suspend = genphy_suspend,
.resume = genphy_resume,
.set_loopback = genphy_loopback,
};

以 genphy_driver 为例 struct phy_device 的注册过程如下:

 
phy_init()
`-| phy_driver_register(&genphy_driver, THIS_MODULE);
`-| {
| new_driver->mdiodrv.driver.bus = &mdio_bus_type;
| new_driver->mdiodrv.driver.probe = phy_probe;
| new_driver->mdiodrv.driver.remove = phy_remove;
| }
| driver_register()

其中一个关键点是 mdio driver 的 probe 函数是一个通用函数 phy_probe(),match 成功时会调用它读取状态寄存器来确定 phy 芯片的能力:

 
phy_probe()
`-| genphy_read_abilities()
`-| {
| val = phy_read(phydev, MII_BMSR); // 读取 mdio 0x01 寄存器来确定 phy 的 10/100M 能力
| linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported, val & BMSR_ANEGCAPABLE);
| linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported, val & BMSR_100FULL);
| linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported, val & BMSR_100HALF);
| linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported, val & BMSR_10FULL);
| linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported, val & BMSR_10HALF);
| if (val & BMSR_ESTATEN) {
| val = phy_read(phydev, MII_ESTATUS); // 读取 mdio 0x0f 寄存器来确定 phy 的 1000M 能力
| linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported, val & ESTATUS_1000_TFULL);
| linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported, val & ESTATUS_1000_THALF);
| linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->supported, val & ESTATUS_1000_XFULL);
| }
| }

2.4 poll task

上面 phy_device_create() 函数中创建了一个重要的 work phy_state_machine(),这个就是 phy_device 查询任务的主体,用来查询 phy 芯片的状态维护 phy 状态机。

在网口驱动启动时会启动这个 work:

 
net_device_ops->ndo_open()
`-| stmmac_open()
`-| phylink_start()
`-| phy_start()
`-| phydev->state = PHY_UP;
| phy_start_machine()
`-| phy_trigger_machine()
`-| phy_queue_state_machine(phydev, 0);
`-| mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, jiffies);

phy_state_machine() 的核心逻辑如下所示:

 
void phy_state_machine(struct work_struct *work)
{
old_state = phydev->state;
/* (1) 状态机主体 */
switch (phydev->state) {
/* (1.1) 在 PHY_DOWN/PHY_READY 状态下不动作 */
case PHY_DOWN:
case PHY_READY:
break;
/* (1.2) 在 PHY_UP 状态下,表明网口被 up 起来,需要启动自协商并且查询自协商后的 link 状态
如果自协商结果是 link up,进入 PHY_RUNNING 状态
如果自协商结果是 link down,进入 PHY_NOLINK 状态
*/
case PHY_UP:
needs_aneg = true;
break;
/* (1.3) 在运行的过程中定时轮询 link 状态
如果 link up,进入 PHY_RUNNING 状态
如果 link down,进入 PHY_NOLINK 状态
*/
case PHY_NOLINK:
case PHY_RUNNING:
err = phy_check_link_status(phydev);
break;
}
/* (2) 如果需要,启动自协商配置 */
if (needs_aneg)
err = phy_start_aneg(phydev);
/* (3) 如果 phy link 状态有变化,通知给对应网口 netdev */
if (old_state != phydev->state) {
phydev_dbg(phydev, "PHY state change %s -> %s\n",
phy_state_to_str(old_state),
phy_state_to_str(phydev->state));
if (phydev->drv && phydev->drv->link_change_notify)
phydev->drv->link_change_notify(phydev);
}
/* (4) 重新启动 work,周期为 1s */
if (phy_polling_mode(phydev) && phy_is_started(phydev))
phy_queue_state_machine(phydev, PHY_STATE_TIME);
}

标准的 mdio/mii 寄存器列表定义如下:

 
/* Generic MII registers. */
#define MII_BMCR 0x00 /* Basic mode control register */
#define MII_BMSR 0x01 /* Basic mode status register */
#define MII_PHYSID1 0x02 /* PHYS ID 1 */
#define MII_PHYSID2 0x03 /* PHYS ID 2 */
#define MII_ADVERTISE 0x04 /* Advertisement control reg */
#define MII_LPA 0x05 /* Link partner ability reg */
#define MII_EXPANSION 0x06 /* Expansion register */
#define MII_CTRL1000 0x09 /* 1000BASE-T control */
#define MII_STAT1000 0x0a /* 1000BASE-T status */
#define MII_MMD_CTRL 0x0d /* MMD Access Control Register */
#define MII_MMD_DATA 0x0e /* MMD Access Data Register */
#define MII_ESTATUS 0x0f /* Extended Status */
#define MII_DCOUNTER 0x12 /* Disconnect counter */
#define MII_FCSCOUNTER 0x13 /* False carrier counter */
#define MII_NWAYTEST 0x14 /* N-way auto-neg test reg */
#define MII_RERRCOUNTER 0x15 /* Receive error counter */
#define MII_SREVISION 0x16 /* Silicon revision */
#define MII_RESV1 0x17 /* Reserved... */
#define MII_LBRERROR 0x18 /* Lpback, rx, bypass error */
#define MII_PHYADDR 0x19 /* PHY address */
#define MII_RESV2 0x1a /* Reserved... */
#define MII_TPISTATUS 0x1b /* TPI status for 10mbps */
#define MII_NCONFIG 0x1c /* Network interface config */

2.4.1 自协商配置

具体启动 phy 自协商的代码流程如下:

 
phy_state_machine()
`-| phy_start_aneg()
`-| phy_config_aneg()
`-| genphy_config_aneg()
`-| __genphy_config_aneg()
`-| genphy_setup_master_slave() // (1) 如果是千兆网口,配置其 master/slave
`-| {
| phy_modify_changed(phydev, MII_CTRL1000, // 配置 mdio 0x09 寄存器
| (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER | CTL1000_PREFER_MASTER), ctl);
| }
| genphy_config_advert() // (2) 设置本端的 advert 能力
`-| {
| linkmode_and(phydev->advertising, phydev->advertising, phydev->supported);
| adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
| phy_modify_changed(phydev, MII_ADVERTISE, // 10M/100M 能力配置到 mdio 0x04 寄存器
| ADVERTISE_ALL | ADVERTISE_100BASE4 |
| ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM, adv);
| if (!(bmsr & BMSR_ESTATEN)) return changed;
| adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
| phy_modify_changed(phydev, MII_CTRL1000, // 1000M 能力配置到 mdio 0x09 寄存器
| ADVERTISE_1000FULL | ADVERTISE_1000HALF, adv);
| }
| genphy_check_and_restart_aneg()
`-| genphy_restart_aneg() // (3) 启动 phy 自协商
`-| {
| phy_modify(phydev, MII_BMCR, BMCR_ISOLATE, // 配置 mdio 0x00 寄存器
| BMCR_ANENABLE | BMCR_ANRESTART);
| }

phy link 状态读取的代码流程如下:

 
phy_state_machine()
`-| phy_check_link_status()
`-| phy_read_status() // (1) 读取 link 状态
`-| genphy_read_status()
`-| {
| genphy_update_link(phydev); // (1.1) 更新 link 状态
| if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) return 0;
| genphy_read_master_slave(phydev); // (1.2) 如果是千兆网口,更新本端和对端的 master/slave
| genphy_read_lpa(phydev); // (1.3) 更新对端(link partner) 声明的能力
| if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
| phy_resolve_aneg_linkmode(phydev); // (1.4.1) 自协商模式,解析 link 结果
| } else if (phydev->autoneg == AUTONEG_DISABLE) {
| genphy_read_status_fixed(phydev); // (1.4.2) 固定模式,解析 link 结果
| }
| }
| if (phydev->link && phydev->state != PHY_RUNNING) { // (2) link 状态 change 事件:变成 link up
| phydev->state = PHY_RUNNING;
| phy_link_up(phydev); // link up 事件,通知给 phylink
| } else if (!phydev->link && phydev->state != PHY_NOLINK) { // (3) link 状态 change 事件:变成 link down
| phydev->state = PHY_NOLINK;
| phy_link_down(phydev); // link down 事件,通知给 phylink
| }

phy 的 link 状态变化怎么通知给 netdev,并且让 mac 做出相应的配置改变,这个是通过一个中介 phylink 来实现的。

phy_device 把 link 状态通知给 phylink 的流程如下:

 
phy_link_up()/phy_link_down()
`-| phydev->phy_link_change(phydev, true/false);
`-| phylink_phy_change()
`-| {
| pl->phy_state.speed = phydev->speed; // (1) 把 `phy_device` 状态更新给 `phylink`
| pl->phy_state.duplex = phydev->duplex;
| pl->phy_state.interface = phydev->interface;
| pl->phy_state.link = up;
| phylink_run_resolve(pl); // (2) 通知 `phylink` 的轮询任务启动
| }

在 linux 内核中,以太网 mac 会被注册成 struct net_device,phy 芯片会被注册成 struct phy_device。 phy_device 的状态怎么传递给 net_device,让其在 link 状态变化时做出对应的配置改变,这个任务就落在上述的 struct phylink 中介身上。

因为 phylink 只是一个中介,所以它不会创建对应的 device,它的核心在以下的几个函数当中。

网口驱动在初始化 probe() 中创建 struct net_device 的同时也创建了 struct phylink

 
stmmac_dvr_probe()
`-| stmmac_phy_setup()
`-| phylink_create(&priv->phylink_config, fwnode, mode, &stmmac_phylink_mac_ops);
`-| {
| INIT_WORK(&pl->resolve, phylink_resolve); // phylink 的轮询任务
| pl->mac_ops = mac_ops;
| timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
| }

这里的 stmmac_phylink_mac_ops 就包含了 mac 对 phy link 状态变化相应的相关函数:

 
static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
.validate = stmmac_validate,
.mac_pcs_get_state = stmmac_mac_pcs_get_state,
.mac_config = stmmac_mac_config,
.mac_an_restart = stmmac_mac_an_restart,
.mac_link_down = stmmac_mac_link_down,
.mac_link_up = stmmac_mac_link_up,
};

在网口驱动启动时会启动时,会连接 phylink 和 phy_device

 
net_device_ops->ndo_open()
`-| stmmac_open()
`-| stmmac_init_phy()
`-| phylink_of_phy_connect()
`-| phy_attach_direct()
| phylink_bringup_phy()
`-| {
| phy->phylink = pl;
| phy->phy_link_change = phylink_phy_change; // (1) 设置 phy_device 的通知函数
| timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
| }

在连接完成后,会同时启动 phylink 和 phy_device 的轮询任务:

 
net_device_ops->ndo_open()
`-| stmmac_open()
`-| phylink_start()
`-| mod_timer(&pl->link_poll, jiffies + HZ); // (1) 启动 `phylink` 的轮询任务
`-| phylink_fixed_poll()
`-| phylink_run_resolve()
`-| queue_work(system_power_efficient_wq, &pl->resolve);
| phy_start() // (2) 启动 `phy_device` 的轮询任务
`-| phydev->state = PHY_UP;
| phy_start_machine()
`-| phy_trigger_machine()
`-| phy_queue_state_machine(phydev, 0);
`-| mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, jiffies);

3.3 poll task

phylink 的轮询任务就是查询 phy_device 更新过来的 link 状态,调用 stmmac_phylink_mac_ops 相关函数来同步配置 mac:

 
phylink_resolve()
`-| {
| link_state = pl->phy_state;
| phylink_link_up()/phylink_link_down()
`-| pl->mac_ops->mac_link_up()
`-| stmmac_mac_link_up() // (1) 配置 mac 为相应状态
123456

原文链接:Linux Phy 驱动解析_linux以太网驱动程序流程图-优快云博客 ;本文仅作为交流分享,如有侵权,请联系作者删除。

内容概要:本文系统阐述了在RK3588平台上进行以太网PHY驱动调试与网络性能优化的全过程。文章首先介绍了RK3588平台的强大硬件架构及其在工业控制、智能安防和边缘计算等场景中对以太网的依赖,随后深入剖析了以太网PHY与MAC的协作机制、MDIO总线工作原理,并以RTL8211F芯片为例讲解常见PHY芯片特性。接着,通过实战角度详细说明了驱动调试的完整流程,包括硬件检查、内核配置、设备树设置、调试工具使用及常见问题解决方案。在网络性能优化方面,提出了驱动更新、MTU与缓冲区调整、物理连接改善、QoS策略部署以及多网卡负载均衡等综合优化手段,并结合iperf、netperf等工具进行性能测试与结果分析,形成闭环优化。最后展望了未来以太网技术的发展趋势。; 适合人群:具备嵌入式Linux开发经验,熟悉设备驱动与网络原理的中高级研发人员,尤其是从事RK3588或类似高性能SoC平台开发的工程师。; 使用场景及目标:①掌握RK3588平台以太网PHY驱动从零配置到调试上线的全流程;②解决实际项目中网络接口无法识别、链路不通、传输异常等问题;③通过多维度优化策略显著提升嵌入式系统的网络带宽、降低延迟与丢包率;④构建高可靠、高性能的网络通信系统。; 阅读建议:建议结合RK3588开发板动手实践,边读边操作,重点关注设备树配置、内核编译、调试命令和性能测试环节,同时对照日志输出与寄存器状态进行问题定位,建议在测试环境中反复验证优化效果,形成完整的调试与优化方法论。
<think>嗯,用户这次想深入了解以太网PHY层的Link Up流程,看来是个网络硬件方向的工程师或开发者。从引用资料看,用户可能正在调试车载以太网或嵌入式系统的物理层连接问题,尤其是Link Up时间异常或故障诊断的场景。 用户提供的三个引用很有价值:引用[1]提到IOP测试中的Link Up时间测量和故障模拟,引用[2]展示实际代码中卡在PHY链接等待循环的问题,引用[3]则揭示特定交换芯片的虚假Link Up BUG。这些线索说明用户需要的不只是理论流程,更关注实际调试中的关键点和故障排查。 PHY Link Up流程确实需要分层解释。物理层自协商是基础,但得强调它对双工模式和速率的协商机制,这是引用[3]故障的根本原因——虚假Link状态往往源于协商异常。链路训练阶段容易被忽略,但恰恰是车载以太网(如BroadR-Reach)确保信号完整性的核心,这里可以关联引用[1]的SQI指标。 状态机转换要突出超时机制,引用[2]的代码片段里那个0x3FFFFFFF循环计数器就是针对超时的工程实践。最后必须加入故障树分析,把三类引用中的典型问题(虚假Link、超时失效、线缆故障)归类到不同排查路径里。 用户可能会追问具体芯片的实现差异,比如Marvell和TI的不同PHY行为,所以最后的问题设计要引导到应用场景和厂商细节。测试方法那块特意加入了引用[1]的故障注入技术,因为用户既然引用IOP测试,应该对诊断方法有需求。 </think>以太网PHY Link Up流程是物理层建立稳定通信链路的关键过程,主要分为以下阶段: --- ### **一、PHY Link Up 核心流程** #### 1. **物理层自协商 (Auto-Negotiation)** - **作用**:协商通信速率(10/100/1000 Mbps)、双工模式(半双工/全双工)及其他能力(如流控)。 - **机制**: - PHY通过发送**快速链路脉冲(FLP)** 交换能力信息。 - 双方根据最高共同支持的能力确定通信参数[^1]。 - **时间**:典型耗时 **< 50ms**。 #### 2. **链路训练 (Link Training)** - **适用场景**:千兆及以上以太网(如1000BASE-T)。 - **过程**: - 收发器互相发送训练序列,动态调整**均衡器参数**(如预加重、回波消除),以补偿信道损耗[^1]。 - **目标**:最小化信号失真,确保眼图张开度达标。 #### 3. **链路稳定性检测** - PHY持续监测信号质量指标: - **信号质量指数(SQI)**:量化信噪比(SNR)。 - **误码率(BER)**:需低于阈值(通常 $< 10^{-12}$)。 - 稳定性条件:连续检测到 **> 32,000** 个无错误码流(IEEE 802.3标准)[^1]。 #### 4. **状态机切换至 "Link Up"** - 满足条件后,PHY置位寄存器状态位(如 `LINK_STATUS=1`),并通过中断或轮询通知MAC层。 - **关键寄存器**: - **BASIC_STATUS (Reg 0x01)**:比特 `Bit 2` 表示链路状态(1=Up, 0=Down)。 - **PHY_ID (Reg 0x02-0x03)**:用于诊断厂商兼容性[^2][^3]。 --- ### **二、故障诊断与调试要点** #### 1. **常见异常场景** | **故障现象** | **可能原因** | **调试方法** | |---------------------|-----------------------------|----------------------------------| | 卡在 `Wait_PHY_link` | 自协商失败/电缆故障 | 检查电缆阻抗匹配、PHY配置寄存器[^2] | | Link Up后虚假连接 | 芯片BUG(如Marvell 88E6390)| 更新PHY固件或应用厂商补丁[^3] | | SQI值过低 | 信道衰减/电磁干扰 | 测量眼图,优化PCB布线 | #### 2. **代码层调试关键** ```c // 示例:轮询PHY链路状态的代码逻辑 uint32_t timeout = MAX_WAIT_MS; while (timeout--) { if (read_phy_reg(BASIC_STATUS) & LINK_UP_BIT) { break; // Link Up成功 } delay_ms(1); } if (timeout == 0) { log_error("PHY Link Up超时!"); // 需排查物理层或配置问题[^2] } ``` #### 3. **测试验证方法** - **IOP测试**:注入开/短路故障,验证Link Up时间与SQI指标[^1]。 - **线缆诊断**:通过TDR(时域反射计)检测阻抗不连续点。 --- ### **三、总结流程图** ```mermaid flowchart TD A[上电初始化] --> B{自协商成功?} B -->|是| C[链路训练] B -->|否| H[检查电缆/PHY配置] C --> D{信号质量达标?} D -->|是| E[置位LINK_STATUS=1] D -->|否| F[调整均衡器参数] E --> G[通知MAC层数据传输] ``` --- ### **相关问题延伸** 1. **如何优化PHY Link Up时间以满足车载以太网的实时性要求?** 2. **不同PHY芯片(如TI DP83867 vs Marvell 88E1512)的Link Up流程有何差异?** 3. **在嵌入式系统中,如何通过MDIO接口诊断PHY链路故障?** [^1]: 车载以太网IOP测试通过模拟故障验证PHYLink Up时间与信号质量。 [^2]: C6678以太网调试中,PHY状态寄存器的轮询逻辑直接影响Link Up检测。 [^3]: Marvell 88E6390的虚假Link Up问题需厂商固件修复。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值