文章目录

  • 1. 简介
  • 2. phy_device
    • 2.1 mdio bus
    • 2.2 mdio device
    • 2.3 mdio driver
    • 2.4 poll task
      • 2.4.1 自协商配置
      • 2.4.2 link 状态读取
      • 2.4.3 link 状态通知
  • 3. phylink
    • 3.1 phylink_create()
    • 3.2 phylink_connect_phy()
    • 3.3 phylink_start()
    • 3.3 poll task
  • 参考资料

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 mdio bus

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_devicephy_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()

在网口驱动启动时会启动时,会连接 phylinkphy_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()

在连接完成后,会同时启动 phylinkphy_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 为相应状态

参考资料

1.以太网PHY寄存器分析

Linux Phy 驱动解析相关推荐

  1. AW9523 linux 按键驱动解析

    AW9523 linux 按键驱动解析 硬件介绍 AW9523是国产芯片,中文手册也是看着方便很多,我从datasheet中摘录一些编写驱动过程中重要信息贴到下面,当然,最好还是看芯片手册,项目使用A ...

  2. linux PHY驱动

    本文参考3.17版本的内核,结合TI的cpsw驱动(但去掉cpsw大部分代码,保留phy相关的),追踪一下通用PHY驱动.文中使用函数及主要功能语句的形式给出调用过程. MDIO驱动 Linux的md ...

  3. Linux phy驱动开发总结

    文章目录 基础 须知 数据结构 数据结构UML类图 MAC驱动 初始化简述 驱动实现 关于MAC连接PHY PHY驱动 动态注册 静态注册 通用PHY 参考模板 IP18xx驱动调试 须知: 问题 方 ...

  4. linux触摸屏代码解析,Linux触摸屏驱动解析

    Linux下开发触摸屏驱动,最好的范例莫过于mc68328digi.c的实现.在没有看到原文之前,我把其中用到的结构解析一下. 1,struct ts_pen_info 该结构是触摸屏的核心数据结构. ...

  5. linux I2C驱动解析

    I2C的设备ID,包含一个设备名字,和私有数据结构(目前没用到),属于i2c_driver; include/linux/mod_devicetable.h #define I2C_NAME_SIZE ...

  6. linux网卡phy地址修改,【基础技能】Linux PHY驱动调试方法

    https://gitee.com/lee790608/linuxPhyDriver STEP1 准备硬件环境 1. 开发环境,可以修改内核代码,编译 2. 硬件开发板,可以进入系统调试,可以传送代码 ...

  7. Mstar Mac Phy 驱动学习

    MII.h 和 MII.c 这是MII协议.里面有MII协议的PHY寄存器地址,及MII通信接口库 Linux的mdio主要是为了管理PHY芯片寄存器的,跟踪代码发现,它会进行创建PHY设备及一些初始 ...

  8. Linux eth phy驱动框架分析

    Linux中,对于eth phy的驱动,是依附于 mdiobus的,物理上,soc内部会有 mac控制器,要想实现以太网数据收发,需要外挂一颗 phy芯片,然后 soc内部的mac控制器驱动,一般都由 ...

  9. Linux Ethernet PHY 驱动

    今年在FPGA上和IC那边一起验证了MAC/PHY, 基于linux 3.6.4, 这边小结一下代码. phy的初始化顺序 第一步 phy_init   mdio_bus_init     bus_r ...

最新文章

  1. CSS3之利用选择器和content属性在页面中插入内容
  2. SSM整合shiro框架相关配置文件
  3. standalone应用_具有Spring Boot的Spring Integration Standalone应用程序
  4. minio分布式搭建_分布式存储Minio集群环境搭建
  5. 完全使用CSS制作下拉菜单
  6. ROS学习笔记七:使用rqt_console和roslaunch
  7. ArcGIS API for JavaScript心得体验
  8. JNI传递long时要谨慎,最好是int
  9. APISpace 银行卡四要素API
  10. AUTOSAR DCM
  11. 欧拉函数φ(x)简要介绍及c++实现
  12. SQlite跨库查询
  13. 世界上第一台电子计算机名叫试题,计算机应用基础习题填空题1世界上第一台电子计算机名为.DOC...
  14. springBoot启动事件监听机制
  15. 抖音素材哪里收集_抖音素材哪里收集 这个视频下载网站帮你处理好了
  16. 和老外聊天的几个网站
  17. HTML5+CSS3基础学习笔记:2
  18. 用pycharm进行python爬虫的步骤_使用Pycharm写一个网络爬虫
  19. 最新,2022中科院分区发生修改,新增1区期刊数十本(附修改版目录)
  20. 深度学习—— 人工智能概述

热门文章

  1. java多态实现动物叫唤_Java 多态 ——一个案例 彻底搞懂它
  2. win10由于启动计算机时页面配置问题,Win10提示"由于启动计算机时出现了页面文件配置问题"(图文)...
  3. php学习笔记(12):PHP+MYSQL留言板(下
  4. python——商业按揭贷款分析
  5. 贺新春丨大年初五 喜迎财神
  6. 听力阈值计算_听力损失到什么程度,一张图就看懂了
  7. UML设计系列(4):用例图
  8. office 2007 oracle 9,Office 2010与Office 2007用户界面对比赏析
  9. 计算机表格定义,怎么进行报表定义
  10. security面试_17道APP测试面试题分享带参考答案