当前位置: 首页 > news >正文

Linux MDIO 深入分析

🖥️ Linux MDIO 深入分析

MDIO(Management Data Input/Output)是IEEE 802.3定义的一种串行管理接口,用于以太网MAC层与PHY层之间的通信。Linux内核中的MDIO子系统提供了一套完整的框架,用于管理通过MDIO总线连接的PHY设备。它将PHY设备的访问抽象化,使得驱动开发者能够以统一的方式操作不同厂商的PHY芯片。

1 MDIO 工作原理与协议基础

MDIO接口最初由IEEE 802.3标准定义,包含两根信号线:

  • MDC(Management Data Clock):时钟信号,由MAC设备产生,最高频率可达2.5MHz(MDIO标准定义)或更高(后续标准扩展)。
  • MDIO(Management Data Input/Output):双向数据信号,用于在MAC和PHY之间传输数据。

1.1 MDIO 协议帧结构

MDIO协议采用一种简单的基于帧的通信格式,基本帧结构如下:

+---------------+---------------+---------------+----------------+--------+--------+----------+
| PRE (32 bits) | ST (2 bits)   | OP (2 bits)   | PHYAD (5 bits) | REGAD (5 bits) | TA (2 bits) | DATA (16 bits) |
+---------------+---------------+---------------+----------------+--------+--------+----------+

各字段含义如下:

  • PRE(Preamble):前缀域,32个连续的"1"比特,用于同步时钟。
  • ST(Start):起始位,固定为"01",标识帧开始。
  • OP(Operation Code):操作码,"10"表示读操作,"01"表示写操作。
  • PHYAD(PHY Address):PHY芯片地址,5比特,最多支持32个PHY设备。
  • REGAD(Register Address):寄存器地址,5比特,每个PHY最多32个寄存器。
  • TA(Turnaround):状态转换域,读操作时第一比特为高阻态,第二比特由PHY置"0";写操作时输出"10"。
  • DATA:数据域,16比特,传输的寄存器数据。
  • IDLE:空闲状态,MDIO进入高阻状态,通常由上拉电阻保持高电平。

MDIO协议读写操作时序如下:

MAC/MDIO ControllerPHY DeviceMDIO Write Operation发送帧头(PRE, ST, OP=01, PHYAD, REGAD)发送TA(10)发送DATA(16位)无响应(操作完成)MDIO Read Operation发送帧头(PRE, ST, OP=10, PHYAD, REGAD)发送TA(第一个时钟周期高阻)驱动TA(第二个时钟周期置0)发送DATA(16位)MAC/MDIO ControllerPHY Device

2 Linux MDIO 子系统实现机制

Linux内核中的MDIO子系统采用标准的总线-设备-驱动模型,提供了灵活且可扩展的框架来管理PHY设备。

2.1 MDIO 总线架构

MDIO子系统遵循Linux设备模型,主要包含以下组件:

MDIO Bus
MDIO Devices
MDIO Drivers
PHY Device 1
PHY Device 2
PHY Device N
Generic PHY Driver
Specific PHY Driver
Another PHY Driver

2.2 核心数据结构

2.2.1 struct mii_bus - MDIO总线结构
struct mii_bus {const char *name;           // 总线名称char id[MII_BUS_ID_SIZE];   // 总线IDvoid *priv;                 // 私有数据int (*read)(struct mii_bus *bus, int phy_id, int regnum); // 读操作函数指针int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val); // 写操作函数指针int phy_mask;               // PHY地址掩码struct phy_device *phy_map[PHY_MAX_ADDR]; // PHY设备数组struct device dev;          // 设备结构struct module *owner;       // 所属模块// 其他字段省略...
};
2.2.2 struct phy_device - PHY设备结构
struct phy_device {struct mii_bus *bus;        // 所属MDIO总线int phy_id;                 // PHY标识符int addr;                   // PHY地址u32 phy_id_mask;            // PHY ID掩码u32 phy_id_value;           // PHY ID值struct phy_driver *drv;     // 关联的PHY驱动void *priv;                 // 私有数据int speed;                  // 当前速度int duplex;                 // 当前双工模式int link;                   // 链路状态// 其他字段省略...
};
2.2.3 struct phy_driver - PHY驱动结构
struct phy_driver {u32 phy_id;                 // PHY标识符char *name;                 // 驱动名称u32 phy_id_mask;            // PHY ID掩码int (*probe)(struct phy_device *phydev);    // 探测函数int (*config_init)(struct phy_device *phydev); // 配置初始化函数int (*config_aneg)(struct phy_device *phydev); // 自动协商函数int (*read_status)(struct phy_device *phydev); // 读取状态函数int (*ack_interrupt)(struct phy_device *phydev); // 中断确认函数int (*config_intr)(struct phy_device *phydev); // 配置中断函数void (*remove)(struct phy_device *phydev);  // 移除函数// 其他字段省略...
};

2.3 设备驱动匹配机制

MDIO总线使用三种匹配机制来关联PHY设备和驱动:

  1. 设备树匹配:首先检查设备树节点的compatible属性是否与驱动中的of_match_table匹配。
  2. 自定义匹配函数:如果驱动实现了match_phy_device函数,则调用该函数进行匹配。
  3. PHY ID匹配:最后比较PHY设备的phy_id与驱动中的phy_idphy_id_mask是否匹配。

匹配流程的代码如下:

static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{struct phy_device *phydev = to_phy_device(dev);struct phy_driver *phydrv = to_phy_driver(drv);// 1. 设备树匹配if (of_driver_match_device(dev, drv))return 1;// 2. 自定义匹配函数if (phydrv->match_phy_device)return phydrv->match_phy_device(phydev);// 3. PHY ID匹配return (phydrv->phy_id & phydrv->phy_id_mask) ==(phydev->phy_id & phydrv->phy_id_mask);
}

3 MDIO 总线驱动开发

3.1 实现MDIO总线操作

开发MDIO总线驱动主要需要实现mii_bus结构的读写方法:

#include <linux/phy.h>
#include <linux/mii_bus.h>// MDIO总线读操作
static int my_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
{struct my_private_data *priv = bus->priv;u32 value;// 1. 硬件特定的MDIO读序列// 2. 设置MDIO帧格式并发送// 3. 读取返回的数据return value;
}// MDIO总线写操作
static int my_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value)
{struct my_private_data *priv = bus->priv;// 1. 硬件特定的MDIO写序列// 2. 设置MDIO帧格式并发送数据return 0;
}// MDIO总线初始化
static int my_mdio_probe(struct platform_device *pdev)
{struct mii_bus *bus;struct my_private_data *priv;int ret;// 分配MDIO总线结构bus = mdiobus_alloc_size(sizeof(struct my_private_data));if (!bus)return -ENOMEM;bus->name = "my_mdio_bus";bus->read = my_mdio_read;bus->write = my_mdio_write;bus->parent = &pdev->dev;snprintf(bus->id, MII_BUS_ID_SIZE, "my-mdio");priv = bus->priv;// 初始化硬件特定数据// 注册MDIO总线ret = of_mdiobus_register(bus, pdev->dev.of_node);if (ret) {dev_err(&pdev->dev, "Cannot register MDIO bus\n");mdiobus_free(bus);return ret;}platform_set_drvdata(pdev, bus);return 0;
}

3.2 实现PHY设备驱动

PHY驱动开发需要实现phy_driver结构的主要方法:

#include <linux/phy.h>// 读取PHY状态
static int my_phy_read_status(struct phy_device *phydev)
{int val;// 读取基本状态寄存器val = phy_read(phydev, MII_BMSR);if (val < 0)return val;// 检查链路状态if (val & BMSR_LSTATUS)phydev->link = 1;elsephydev->link = 0;// 读取更多状态信息(速度、双工模式等)// ...return 0;
}// 配置自动协商
static int my_phy_config_aneg(struct phy_device *phydev)
{// 配置自动协商广告能力// ...return genphy_config_aneg(phydev);
}// PHY驱动结构
static struct phy_driver my_phy_driver = {.phy_id         = 0x01410bc0,       // PHY标识符.name           = "My Ethernet PHY", // 驱动名称.phy_id_mask    = 0xfffffff0,       // PHY ID掩码.probe          = my_phy_probe,     // 探测函数.config_init    = my_phy_config_init, // 初始化配置.config_aneg    = my_phy_config_aneg, // 自动协商.read_status    = my_phy_read_status, // 读取状态.ack_interrupt  = my_phy_ack_interrupt, // 中断确认.config_intr    = my_phy_config_intr, // 配置中断
};// 模块初始化
static int __init my_phy_init(void)
{return phy_driver_register(&my_phy_driver);
}// 模块退出
static void __exit my_phy_exit(void)
{phy_driver_unregister(&my_phy_driver);
}module_init(my_phy_init);
module_exit(my_phy_exit);

4 用户空间访问MDIO总线

除了内核驱动,用户空间也可以直接访问MDIO总线来读写PHY寄存器。

4.1 使用ioctl系统调用

以下是一个用户空间程序,通过ioctl系统调用访问PHY寄存器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/mii.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <linux/types.h>
#include <netinet/in.h>#define reteck(ret)     \if(ret < 0){        \printf("%m! \"%s\" : line: %d\n", __func__, __LINE__); \goto lab;       \}int main(int argc, char *argv[])
{if (argc < 3) {printf("Usage: %s <interface> <reg> [value]\n", argv[0]);printf("Read:  %s eth0 1\n", argv[0]);printf("Write: %s eth0 0 0x1120\n", argv[0]);return -1;}int sockfd;struct mii_ioctl_data *mii = NULL;struct ifreq ifr;int ret;memset(&ifr, 0, sizeof(ifr));strncpy(ifr.ifr_name, argv[1], IFNAMSIZ - 1);// 创建套接字sockfd = socket(PF_LOCAL, SOCK_DGRAM, 0);reteck(sockfd);// 获取PHY地址ret = ioctl(sockfd, SIOCGMIIPHY, &ifr);reteck(ret);mii = (struct mii_ioctl_data*)&ifr.ifr_data;if (argc == 3) {// 读取寄存器mii->reg_num = (uint16_t)strtoul(argv[2], NULL, 0);ret = ioctl(sockfd, SIOCGMIIREG, &ifr);reteck(ret);printf("Read PHY addr: 0x%x, reg: 0x%x, value: 0x%x\n", mii->phy_id, mii->reg_num, mii->val_out);// 检查链路状态(寄存器1的第2位)if (mii->reg_num == 1 && (mii->val_out & 0x0004)) {printf("Link: UP\n");} else if (mii->reg_num == 1) {printf("Link: DOWN\n");}} else if (argc == 4) {// 写入寄存器mii->reg_num = (uint16_t)strtoul(argv[2], NULL, 0);mii->val_in = (uint16_t)strtoul(argv[3], NULL, 0);ret = ioctl(sockfd, SIOCSMIIREG, &ifr);reteck(ret);printf("Write PHY addr: 0x%x, reg: 0x%x, value: 0x%x\n", mii->phy_id, mii->reg_num, mii->val_in);}lab:close(sockfd);return 0;
}

编译和使用方法:

gcc -o mdio-tool mdio-tool.c
./mdio-tool eth0 1        # 读取寄存器1
./mdio-tool eth0 0 0x1120 # 写入值0x1120到寄存器0

4.2 使用mdio-tool工具

除了自己编写代码,还可以使用现有的mdio-tool工具来访问MDIO总线:

# 列出MDIO总线上的设备
mdio-tool list# 读取PHY寄存器
mdio-tool read 0 0x01# 写入PHY寄存器
mdio-tool write 0 0x00 0x8000

5 调试和故障排除

MDIO子系统调试常用的方法和工具如下:

5.1 常用调试命令

命令描述示例
dmesg \| grep -i mdio查看内核MDIO相关消息dmesg \| grep -i mdio
dmesg \| grep -i phy查看内核PHY相关消息dmesg \| grep -i phy
mdio-tool list列出MDIO总线上的设备mdio-tool list
mdio-tool read读取PHY寄存器mdio-tool read 0 0x01
mdio-tool write写入PHY寄存器mdio-tool write 0 0x00 0x8000
cat /sys/class/net/eth0/phy_settings查看PHY设置cat /sys/class/net/eth0/phy_settings

5.2 常见问题及解决方法

  1. PHY设备未检测到

    # 错误信息
    davinci_mdio c000f00.mdio: phy[10]: device c000f00.mdio:0a, MDIO device at address 10 is missing.
    

    解决方法:检查设备树配置、硬件连接和电源。

  2. 使用通用PHY驱动

    # 错误信息
    am65-cpsw-nuss c000000.ethernet eth1: PHY [c000f00.mdio:0a] driver [Generic PHY] (irq=POLL)
    davinci_mdio c000f00.mdio: phy[10]: device c000f00.mdio:0a, driver unknown
    

    解决方法:确保专用PHY驱动已编译并加载到内核。

  3. 读取返回0xFFFF
    解决方法:检查MDIO总线物理连接、时钟信号和电源。

5.3 内核调试技巧

启用内核调试选项:

# 配置内核开启MDIO/PHY调试支持
make menuconfig
# 选择: Device Drivers -> Network device support -> PHY Device support and infrastructure -> Debugging support for PHY drivers

使用动态调试:

# 启用MDIO总线调试
echo "file mdio_bus.c +p" > /sys/kernel/debug/dynamic_debug/control# 启用PHY驱动调试
echo "file phy_device.c +p" > /sys/kernel/debug/dynamic_debug/control

6 总结

Linux MDIO子系统为以太网PHY设备管理提供了完整的框架,通过标准的总线-设备-驱动模型实现了不同PHY芯片的统一访问。开发MDIO相关驱动需要理解MDIO协议、Linux设备模型和PHY芯片特性。用户空间工具和内核调试机制为MDIO总线调试和故障排除提供了有效手段。

http://www.dtcms.com/a/354018.html

相关文章:

  • 电子电气架构 --- 软件项目复杂性的驾驭思路
  • [Sync_ai_vid] UNet模型 | 音频特征提取器(Whisper)
  • FPGA实现Aurora 64B66B图像视频传输,基于GTY高速收发器,提供2套工程源码和技术支持
  • 第三阶段数据库-10:存储过程,事务,异常,视图,自定义函数,触发器,N关键字
  • synchronized和Lock有什么区别?
  • Vue3》》基础
  • Ubuntu 20.04.6交叉编译得到Ubuntu 16.04.6的可执行文件
  • 5GNR CSI反馈 TypeI码本
  • Shader开发(十七)着色器中的纹理采样与渲染
  • OpenCV4.X库功能全解---个人笔记
  • 基于51单片机温度控制系统报警器恒温箱水温设计
  • Oh My Zsh + Tabby 终端配置指南
  • CB1-2-基础启航
  • 工业 DCS 全面科普:从入门到 AI 赋能的未来
  • 大视码垛机器人:以技术优势撬动工业码垛升级
  • 【datawhale组队学习】RAG技术 -TASK05 向量数据库实践(第三章3、4节)
  • Scala面试题及详细答案100道(21-30)-- 面向对象编程
  • 丝杆支撑座如何助力自动化设备精准定位?
  • 对接连连支付(四)-- 收款查询
  • 在Python中处理GDB、MDB和Shapefile文件转换
  • 滥用Mybatis一级缓存引发OOM问题
  • 如何使用asyncio库
  • 汽车电气系统的发展演进为测试带来了哪些影响?
  • LangChain4J-(3)-模型参数配置
  • AI生成音乐模型发展现状与前景
  • prettier、eslint、stylelint在项目中使用
  • 理解虚拟 DOM:前端开发中的高效渲染利器
  • Linux操作系统——TCP服务端并发模型
  • Java全栈开发面试实战:从基础到复杂场景的深度解析
  • 【51单片机】【protues仿真】基于51单片机点阵屏系统