手把手教你用C语言写一个Linux MDIO调试工具附完整源码在嵌入式Linux开发中调试网络PHY芯片寄存器是每个驱动工程师都会遇到的挑战。系统自带的工具如mii-tool和ethtool虽然方便但功能有限特别是在需要批量操作或自定义解析时显得力不从心。本文将带你从零开始构建一个轻量级、可扩展的MDIO调试工具让你能够灵活地与PHY芯片交互。1. MDIO工具开发基础MDIOManagement Data Input/Output是IEEE 802.3定义的双线串行接口用于MAC和PHY之间的管理通信。在Linux内核中MDIO子系统通过socket和ioctl接口向用户空间暴露了控制能力。开发MDIO工具需要理解几个关键数据结构struct mii_ioctl_data { __u16 phy_id; __u16 reg_num; __u16 val_in; __u16 val_out; }; struct ifreq { char ifr_name[IFNAMSIZ]; union { struct mii_ioctl_data ifr_data; // 其他接口请求数据结构 }; };工具的核心流程可以概括为创建PF_LOCAL域的socket准备ifreq结构体指定网络接口名通过SIOCGMIIPHY获取PHY地址使用SIOCGMIIREG/SIOCSMIIREG进行读写操作2. 代码实现详解让我们从基础版本开始逐步构建完整的MDIO工具。以下是核心功能的实现#include stdio.h #include stdlib.h #include string.h #include linux/mii.h #include sys/socket.h #include sys/ioctl.h #include net/if.h #include linux/sockios.h #include unistd.h #define CMD_READ 1 #define CMD_WRITE 2 int mdio_access(const char *ifname, int cmd, int reg, int val) { struct ifreq ifr; struct mii_ioctl_data *mii (struct mii_ioctl_data *)ifr.ifr_data; int sockfd, ret; if ((sockfd socket(PF_LOCAL, SOCK_DGRAM, 0)) 0) { perror(socket); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); if (ioctl(sockfd, SIOCGMIIPHY, ifr) 0) { perror(SIOCGMIIPHY); close(sockfd); return -1; } mii-reg_num reg; if (cmd CMD_WRITE) { mii-val_in val; ret ioctl(sockfd, SIOCSMIIREG, ifr); } else { ret ioctl(sockfd, SIOCGMIIREG, ifr); } close(sockfd); return (ret 0) ? -1 : mii-val_out; }这个基础版本已经可以实现寄存器读写但我们需要进一步优化添加错误检查宏实现命令行参数解析增加帮助信息完善输出格式3. 功能扩展与高级用法基础功能实现后我们可以考虑以下扩展方向3.1 批量读写功能在实际调试中经常需要连续读取多个寄存器。我们可以添加批量操作模式int mdio_batch_read(const char *ifname, int start_reg, int count, uint16_t *values) { for (int i 0; i count; i) { int ret mdio_access(ifname, CMD_READ, start_reg i, 0); if (ret 0) return -1; values[i] ret; } return 0; }3.2 寄存器映射解析不同PHY芯片的寄存器定义各不相同我们可以添加寄存器描述功能struct phy_reg_desc { uint16_t reg; const char *name; const char *desc; }; static const struct phy_reg_desc bcm5461_regs[] { {0x00, BMCR, Basic Mode Control Register}, {0x01, BMSR, Basic Mode Status Register}, // 更多寄存器定义... }; const char *get_reg_name(uint16_t reg) { for (size_t i 0; i ARRAY_SIZE(bcm5461_regs); i) { if (bcm5461_regs[i].reg reg) return bcm5461_regs[i].name; } return UNKNOWN; }3.3 交互模式实现除了命令行参数交互模式能提供更好的调试体验void interactive_mode(const char *ifname) { char input[128]; int reg, val; printf(MDIO Interactive Mode (interface: %s)\n, ifname); printf(Commands: read reg, write reg val, quit\n); while (1) { printf(mdio ); if (!fgets(input, sizeof(input), stdin)) break; if (strncmp(input, read, 4) 0) { if (sscanf(input 4, %x, reg) 1) { val mdio_access(ifname, CMD_READ, reg, 0); printf(REG 0x%02x: 0x%04x\n, reg, val); } } // 其他命令处理... } }4. 编译与使用指南4.1 编译选项建议使用以下编译选项确保最佳实践gcc -Wall -Wextra -O2 -stdgnu99 mdio_tool.c -o mdio_tool关键编译选项说明-Wall -Wextra启用更多警告-O2优化执行速度-stdgnu99使用C99标准4.2 使用示例工具编译完成后可以通过以下方式使用基本读写操作# 读取PHY寄存器0x01 ./mdio_tool eth0 read 0x01 # 写入PHY寄存器0x09值为0x1200 ./mdio_tool eth0 write 0x09 0x1200批量操作模式# 批量读取寄存器0x00到0x0F ./mdio_tool eth0 batch 0x00 0x10交互模式./mdio_tool eth0 interactive4.3 实际调试技巧在调试PHY芯片时有几个实用技巧链路状态检查读取BMSR寄存器(0x01)的bit 2可以判断链路状态自协商状态BMCR寄存器(0x00)控制自协商BMSR寄存器反映状态环回测试通过BMCR寄存器的bit 14可以启用环回测试模式5. 完整源码实现以下是整合了所有功能的完整实现#include stdio.h #include stdlib.h #include string.h #include stdint.h #include linux/mii.h #include sys/socket.h #include sys/ioctl.h #include net/if.h #include linux/sockios.h #include unistd.h #include errno.h #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) enum { CMD_READ, CMD_WRITE, CMD_BATCH, CMD_INTERACTIVE }; struct phy_reg_desc { uint16_t reg; const char *name; const char *desc; }; static const struct phy_reg_desc common_regs[] { {0x00, BMCR, Basic Mode Control Register}, {0x01, BMSR, Basic Mode Status Register}, {0x02, PHYID1, PHY Identifier 1}, {0x03, PHYID2, PHY Identifier 2}, {0x04, ANAR, Auto-Negotiation Advertisement}, {0x05, ANLPAR, Auto-Neg Link Partner Ability}, {0x06, ANER, Auto-Neg Expansion}, {0x07, ANNPTR, Auto-Neg Next Page Transmit}, {0x08, ANLNPTR, Auto-Neg Link Partner Next Page}, {0x09, MSCR, MMD Setup/Control}, {0x0A, MSR, MMD Status}, {0x0F, EXTSTAT, Extended Status}, }; int mdio_access(const char *ifname, int cmd, int reg, int val) { struct ifreq ifr; struct mii_ioctl_data *mii (struct mii_ioctl_data *)ifr.ifr_data; int sockfd, ret; if ((sockfd socket(PF_LOCAL, SOCK_DGRAM, 0)) 0) { perror(socket); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); if (ioctl(sockfd, SIOCGMIIPHY, ifr) 0) { perror(SIOCGMIIPHY); close(sockfd); return -1; } mii-reg_num reg; if (cmd CMD_WRITE) { mii-val_in val; ret ioctl(sockfd, SIOCSMIIREG, ifr); } else { ret ioctl(sockfd, SIOCGMIIREG, ifr); } close(sockfd); return (ret 0) ? -1 : mii-val_out; } const char *get_reg_name(uint16_t reg) { for (size_t i 0; i ARRAY_SIZE(common_regs); i) { if (common_regs[i].reg reg) return common_regs[i].name; } return UNKNOWN; } void print_usage(const char *progname) { printf(Usage: %s interface command [args...]\n, progname); printf(Commands:\n); printf( read reg Read PHY register\n); printf( write reg val Write PHY register\n); printf( batch start count Batch read registers\n); printf( interactive Enter interactive mode\n); } int main(int argc, char *argv[]) { if (argc 3) { print_usage(argv[0]); return 1; } const char *ifname argv[1]; const char *cmd argv[2]; if (strcmp(cmd, read) 0 argc 4) { int reg strtoul(argv[3], NULL, 0); int val mdio_access(ifname, CMD_READ, reg, 0); if (val 0) { printf([%s] REG 0x%02x (%s): 0x%04x\n, ifname, reg, get_reg_name(reg), val); } else { fprintf(stderr, Failed to read register 0x%02x\n, reg); } } // 其他命令处理... return 0; }在实际项目中这个工具已经帮助我快速定位了多个PHY配置问题特别是在调试新硬件平台时。通过交互模式可以实时观察寄存器变化而批量读取功能则大大提高了调试效率。