【Linux Phy 驱动解析 (一)】

老子为什么写这篇博客

最近在调试imx93板子的ENET QOS口的dpdk功能,因为这个口配合22.11版本的dpdk以及2.7版本的内核,可以让这个网口正常工作,且用dpdk自带的 l2fwd 工具测试成功的,且用自己的一个客户端小程序给该板子的ENET QOS口打流,是可以看到发送的数据可以被正常返回。

But, 我的目标是让这个网口能跑 EtherCAT,问题是Ethercat都是默认100M且无自协商功能的。而默认的ENET QOS跑dpdk是1000M有自协商功能的。所以这个网口无法实现对伺服电机的驱动。

可能的解决思路:用ethtool,网上铺天盖地都是这个,but,因为跑起来dpdk后,ethtool就看不到这个qos口啦,死路一条。我只能在qos不跑dpdk的情况下,修改内核代码,然后编译内核,跑不用dpdk的设备树,板子起来后通过ethtool看对内核代码的修改有无效果(步骤冗长,内核代码不懂)。

正因为看不懂内核代码,所以我需要学习一下这一块,这不有人已经分析过了,还得站在巨人的肩膀上啊。

1. phy 驱动的原理简介

phy 驱动的原理是非常简单的,一般流程如下:

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

2. 什么是PHY、MAC、MDIO等基础

以太网硬件主要分为MAC和PHY两块,他俩之间有两套接口,一个数据接口,一个管理接口,数据接口可能是MII,GMII,RGMII,SGMII管理接口MDIO总线。MDIO是双向的,可以读PHY寄存器,也可以写PHY寄存器,而数据接口用于传输数据,对于RGMII而言,如果以太网工作在1000M,则RGMII的频率为125MHz,如果以太网工作在100M,则RGMII的频率为25MHz。
PHY的驱动代码是drivers/net/phy目录下的phy.c,MAC的驱动代码是drivers/net/ethernet/
硬件架构相关图片
什么是MII:(Media Independent Interface)即媒体独立接口,该接口支持 10Mb/s 与 100Mb/s 的数据传输速率,数据传输的位宽为 4 位。(RMII、SMII、GMII均为从MII简化出来的标准,线少了)

3. 控制器驱动框架

PHY的控制器一般被描述为mdio_bus平台设备(既然是平台设备,那么设备树中必定要有可以被解析为平台设备的节点,也要有对应的平台设备驱动。与SPI驱动类似,PHY设备模型也是在控制器驱动的probe函数中注册的。)如下:

module_platform_driver(fec_enet_uio_driver);     //注册控制器平台设备驱动

4. mdio bus

phy设备驱动基于device,driver,bus的链接方式驱动涉及如下重要部分:

  1. 总线- sturct mii_bus (mii stand for media independent interface)
  2. 设备- struct phy_device
  3. 驱动- struct phy_driver
drivers/net/phy/mdio_bus.c
   1 struct bus_type mdio_bus_type = {
   2         .name           = "mdio_bus",
   3         .dev_groups     = mdio_bus_dev_groups,
   4         .match          = mdio_bus_match,
   5         .uevent         = mdio_uevent,
   6 };
mdio_bus总线注册相关函数解析
总线注册的入口函数
# \drivers\net\phy\phy_device.c
static int __init phy_init(void)
{
	int rc;
 
	rc = mdio_bus_init(); //mdio_bus总线的注册
	if (rc)
		return rc;
 
	rc = phy_drivers_register(genphy_driver,ARRAY_SIZE(genphy_driver), THIS_MODULE); //通用PHY驱动
	if (rc)
		mdio_bus_exit();
 
	return rc;
}
 
subsys_initcall(phy_init); 

subsys_initcall(phy_init) 这行的作用非常重要,这一行就决定了内核在启动的时候会调用该函数,注册完了之后紧接着又注册一个通用的PHY驱动。

5. phy_device设备注册相关函数解析

在phy_init函数中不仅注册了mdio_bus总线,还注册了一个通用的PHY驱动作为缺省的内核PHY驱动,但是如果PHY芯片的内部寄存器和802.3定义的并不一样或者需要特殊的功能配置以实现更强的功能,这就需要专有的驱动。

对于市场上存在的主流PHY品牌,一般在内核源码 drivers/net/phy目录下都有对应的驱动。

phy驱动的注册

(1)同一品牌的PHY设备有多种不同的型号,内核为了支持一次可以注册多个型号的PHY的驱动,在include\linux\phy.h中提供了用于注册PHY驱动的宏module_phy_driver。该宏的定义如下:

   1 /**
   2  * phy_module_driver() - Helper macro for registering PHY drivers
   3  * @__phy_drivers: array of PHY drivers to register
   4  * @__count: Numbers of members in array
   5  *
   6  * Helper macro for PHY drivers which do not do anything special in module
   7  * init/exit. Each module may only use this macro once, and calling it
   8  * replaces module_init() and module_exit().
   9  */
  10 #define phy_module_driver(__phy_drivers, __count)                       \
  11 static int __init phy_module_init(void)                                 \
  12 {                                                                       \
  13         return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \
  14 }                                                                       \
  15 module_init(phy_module_init);                                           \
  16 static void __exit phy_module_exit(void)                                \
  17 {                                                                       \
  18         phy_drivers_unregister(__phy_drivers, __count);                 \
  19 }                                                                       \
  20 module_exit(phy_module_exit)
  21
  22 #define module_phy_driver(__phy_drivers)                                \
  23         phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))
  24
  25 bool phy_driver_is_genphy(struct phy_device *phydev);
  26 bool phy_driver_is_genphy_10g(struct phy_device *phydev);

(2)其中phy_driver_register定义如下

   1 /**
   2  * phy_driver_register - register a phy_driver with the PHY layer
   3  * @new_driver: new phy_driver to register
   4  * @owner: module owning this PHY
   5  */
   6 int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
   7 {
   8         int retval;
   9
  10         /* Either the features are hard coded, or dynamically
  11          * determined. It cannot be both.
  12          */
  13         if (WARN_ON(new_driver->features && new_driver->get_features)) {
  14                 pr_err("%s: features and get_features must not both be set\n",
  15                        new_driver->name);
  16                 return -EINVAL;
  17         }
  18
  19         /* PHYLIB device drivers must not match using a DT compatible table
  20          * as this bypasses our checks that the mdiodev that is being matched
  21          * is backed by a struct phy_device. If such a case happens, we will
  22          * make out-of-bounds accesses and lockup in phydev->lock.
  23          */
  24         if (WARN(new_driver->mdiodrv.driver.of_match_table,
  25                  "%s: driver must not provide a DT match table\n",
  26                  new_driver->name))
  27                 return -EINVAL;
  28
  29         new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
  30         new_driver->mdiodrv.driver.name = new_driver->name;
  31         new_driver->mdiodrv.driver.bus = &mdio_bus_type;
  32         new_driver->mdiodrv.driver.probe = phy_probe;
  33         new_driver->mdiodrv.driver.remove = phy_remove;
  34         new_driver->mdiodrv.driver.shutdown = phy_shutdown;
  35         new_driver->mdiodrv.driver.owner = owner;
  36         new_driver->mdiodrv.driver.probe_type = PROBE_FORCE_SYNCHRONOUS;
  37
  38         retval = driver_register(&new_driver->mdiodrv.driver);
  39         if (retval) {
  40                 pr_err("%s: Error %d in registering driver\n",
  41                        new_driver->name, retval);
  42
  43                 return retval;
  44         }
  45
  46         pr_debug("%s: Registered new driver\n", new_driver->name);
  47
  48         return 0;
  49 }
  50 EXPORT_SYMBOL(phy_driver_register);
  51
  52 int phy_drivers_register(struct phy_driver *new_driver, int n,
  53                          struct module *owner)
  54 {
  55         int i, ret = 0;
  56
  57         for (i = 0; i < n; i++) {
  58                 ret = phy_driver_register(new_driver + i, owner);
  59                 if (ret) {
  60                         while (i-- > 0)
  61                                 phy_driver_unregister(new_driver + i);
  62                         break;
  63                 }
  64         }
  65         return ret;
  66 }
  67 EXPORT_SYMBOL(phy_drivers_register);

原文链接:https://blog.csdn.net/pwl999/article/details/128339747 ;本文仅作为交流分享,巩固所学,感谢原作者八辈祖宗。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值