【驱动】以太网扫盲(四)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);
| }
2.4.2 link 状态读取
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
| }
2.4.3 link 状态通知
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` 的轮询任务启动
| }
3. phylink
在 linux 内核中,以太网 mac 会被注册成 struct net_device
,phy 芯片会被注册成 struct phy_device
。 phy_device
的状态怎么传递给 net_device
,让其在 link 状态变化时做出对应的配置改变,这个任务就落在上述的 struct phylink
中介身上。
因为 phylink
只是一个中介,所以它不会创建对应的 device,它的核心在以下的几个函数当中。
3.1 phylink_create()
网口驱动在初始化 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,
};
3.2 phylink_connect_phy()
在网口驱动启动时会启动时,会连接 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);
| }
3.3 phylink_start()
在连接完成后,会同时启动 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
原文链接:https://blog.csdn.net/pwl999/article/details/128339747 ;本文仅作为交流分享,如有侵权,请联系作者删除。
【驱动】以太网扫盲(四)phy驱动link up流程分析的更多相关文章
- Android bluetooth介绍(四): a2dp connect流程分析
关键词:蓝牙blueZ A2DP.SINK.sink_connect.sink_disconnect.sink_suspend.sink_resume.sink_is_connected.sink_ ...
- Android7.0 Phone应用源码分析(四) phone挂断流程分析
电话挂断分为本地挂断和远程挂断,下面我们就针对这两种情况各做分析 先来看下本地挂断电话的时序图: 步骤1:点击通话界面的挂断按钮,会调用到CallCardPresenter的endCallClicke ...
- 驱动之路四------adc驱动(input设备)
开发板:smdk6410 开发环境:Linux 突然想起一点,写这些驱动,内核需要配成支持抢占才行. 前面的博客已经将其它的基本知识都解释了,这里也就不过多的阐述了,咱就直接写代码吧 这次写的是adc ...
- PHY驱动调试之 ---PHY设备驱动(三)
1. 前言 内核版本:linux 4.9.225,以freescale为例.(部分内容待修改和补充,不一定准确) 2. 概述 上一篇文章讲了控制器的驱动使用的是platform总线的连接方式,本节要讲 ...
- PHY驱动调试之 --- PHY控制器驱动(二)
1. 前言 内核版本:linux 4.9.225,以freescale为例. 2. 概述 PHY芯片为OSI的最底层-物理层(Physical Layer),通过MII/GMII/RMII/SGMII ...
- “四核”驱动的“三维”导航 -- 淘宝新UI(需求分析篇)
前言 孔子说:"软件是对客观世界的抽象". 首先声明,这里的"三维导航"和地图没一毛钱关系,"四核驱动"和硬件也没关系,而是为了复杂的应用而 ...
- 四核驱动的三维导航—淘宝新UI(设计篇)
前面有一篇博客说到了淘宝UWP的"四核驱动的三维导航—淘宝新UI(需求分析篇)",花了两周的时间实现了这个框架,然后又陆陆续续用了三周的时间完善它. 多窗口导航,与传统的导航方式的 ...
- 20145316&20145229实验四:驱动程序设计
20145316&20145229实验四:驱动程序设计 结对伙伴:20145316 许心远 博客链接:http://www.cnblogs.com/xxy745214935/p/6130871 ...
- Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析
源: Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析
- JDBC 学习笔记(二)—— 详解 JDBC 的四种驱动类型
JDBC 有四种驱动类型,分别是: JDBC-ODBC 桥(JDBC-ODBC bridge driver plus ODBC driver) 本地 API 驱动(Native-API partly ...
随机推荐
- 【已解决】【Tensorflow2.12.0版本以后合并CPU和GPU版】Tensorflow-gpu==2.12.0 安装失败解决办法
直接上解决方式,需要知道原因的看后文. 直接安装 tensroflow,从 2022 年 12 月起 tensorflow-gpu 已经合并到 tensorflow 包中了 pip install t ...
- C++ Qt开发:SqlRelationalTable关联表组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍SqlRela ...
- AutoGPT实战
1.概述 人工智能(AI)的能力持续在全球范围内引起轰动,并对我们日常生活和职业生涯带来重大变革.随着像ChatGPT这样的先进生成型AI模型以及从GPT-3到GPT-4的加速,我们在高级推理.理解更 ...
- 从零玩转前后端加解密之SM2-sm2
title: 从零玩转前后端加解密之SM2 date: 2022-08-21 19:42:00.907 updated: 2023-03-30 13:28:48.866 url: https://ww ...
- CTFHub 栈溢出 ret2text exp代码
exp代码: from pwn import * host='challenge-1868f48f1e630fd3.sandbox.ctfhub.com' port=27988 p=connect(h ...
- JavaFx css样式(三)
JavaFx css样式(三) JavaFX 从入门入门到入土系列 JavaFx css样式,前面我说过它类似html,他有css控制样式,不过最新的css标准并不支持,同时javafx的css样式都 ...
- 2023-05-13:你现在手里有一份大小为 n x n 的 网格 grid, 上面的每个 单元格 都用 0 和 1 标记好了其中 0 代表海洋,1 代表陆地。 请你找出一个海洋单元格,这个海洋单元格
2023-05-13:你现在手里有一份大小为 n x n 的 网格 grid, 上面的每个 单元格 都用 0 和 1 标记好了其中 0 代表海洋,1 代表陆地. 请你找出一个海洋单元格,这个海洋单元格 ...
- 最基本的SpringCloud的搭建
对于springcloud而言,模块是按业务进行区分的: 父工程 依赖 <parent> <groupId>org.springframework.boot</group ...
- 3种方法,用Java找出两个List中的重复元素
本文分享自华为云社区<如何用Java找出两个List中的重复元素,读这一篇就够了>,作者: 努力的阿飞. 在Java编程中,我们经常需要找出两个列表(List)中的重复元素.在本文中,我们 ...
- 微服务下,使用 ELK 进行日志采集以及统一处理
摘要:微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等. 微服务各个组件的相关实践会涉 ...