news 2026/5/6 6:01:07

手把手教你用C语言写一个Linux MDIO调试工具(附完整源码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你用C语言写一个Linux MDIO调试工具(附完整源码)

手把手教你用C语言写一个Linux MDIO调试工具(附完整源码)

在嵌入式Linux开发中,调试网络PHY芯片寄存器是每个驱动工程师都会遇到的挑战。系统自带的工具如mii-tool和ethtool虽然方便,但功能有限,特别是在需要批量操作或自定义解析时显得力不从心。本文将带你从零开始构建一个轻量级、可扩展的MDIO调试工具,让你能够灵活地与PHY芯片交互。

1. MDIO工具开发基础

MDIO(Management 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; // 其他接口请求数据结构 }; };

工具的核心流程可以概括为:

  1. 创建PF_LOCAL域的socket
  2. 准备ifreq结构体,指定网络接口名
  3. 通过SIOCGMIIPHY获取PHY地址
  4. 使用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 -std=gnu99 mdio_tool.c -o mdio_tool

关键编译选项说明:

  • -Wall -Wextra:启用更多警告
  • -O2:优化执行速度
  • -std=gnu99:使用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 interactive

4.3 实际调试技巧

在调试PHY芯片时,有几个实用技巧:

  1. 链路状态检查:读取BMSR寄存器(0x01)的bit 2可以判断链路状态
  2. 自协商状态:BMCR寄存器(0x00)控制自协商,BMSR寄存器反映状态
  3. 环回测试:通过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配置问题,特别是在调试新硬件平台时。通过交互模式可以实时观察寄存器变化,而批量读取功能则大大提高了调试效率。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/6 5:53:32

AI辅助开发:让快马AI为你生成带JWT验证与防爆破的智能6x9ycc登录方案

最近在做一个需要安全认证的6x9ycc登录入口项目&#xff0c;正好尝试了用AI辅助开发的方式来完成。整个过程比我预想的顺利很多&#xff0c;特别是安全防护这块&#xff0c;AI给出的方案相当专业。下面分享下具体实现思路和关键点&#xff1a; 前端React组件搭建 首先用AI生成了…

作者头像 李华
网站建设 2026/5/6 5:50:12

深入解析Cappuccino:现代前端状态逻辑管理框架的设计与实践

1. 项目概述&#xff1a;一杯为开发者特调的“卡布奇诺”如果你在GitHub上搜索过“cappuccino”&#xff0c;可能会发现不止一个同名项目。但今天我们要聊的&#xff0c;是来自GML-FMGroup的这一个。它不是关于咖啡&#xff0c;而是一个在开发者圈子里&#xff0c;特别是那些需…

作者头像 李华
网站建设 2026/5/6 5:48:29

GRU-Mem:解决长文本序列建模的记忆增强技术

1. 项目背景与核心价值在自然语言处理领域&#xff0c;处理长文本序列一直是个棘手的问题。传统RNN结构存在梯度消失的缺陷&#xff0c;LSTM虽然缓解了这个问题&#xff0c;但在处理超长上下文时仍然面临记忆衰减的挑战。GRU-Mem正是针对这一痛点提出的创新解决方案。我去年参与…

作者头像 李华
网站建设 2026/5/6 5:47:08

如何用QrScan实现企业级图片二维码批量检测与识别

如何用QrScan实现企业级图片二维码批量检测与识别 【免费下载链接】QrScan 离线批量检测图片是否包含二维码以及识别二维码 项目地址: https://gitcode.com/gh_mirrors/qrs/QrScan 在数字化转型浪潮中&#xff0c;企业面临着海量图片资产中的二维码信息管理难题——如何…

作者头像 李华
网站建设 2026/5/6 5:46:32

G-Helper终极解决方案:高效管理华硕笔记本性能与散热

G-Helper终极解决方案&#xff1a;高效管理华硕笔记本性能与散热 【免费下载链接】g-helper Fast, native tool for tuning performance, fans, GPU, battery, and RGB on any Asus laptop or handheld - ROG Zephyrus, Flow, Strix, TUF, Vivobook, Zenbook, ProArt, Ally, an…

作者头像 李华
网站建设 2026/5/6 5:38:29

基于大语言模型的自然语言转SQL工具:从原理到企业级实践

1. 项目概述&#xff1a;当SQL遇上对话式AI 最近在数据圈子里&#xff0c;一个叫“dataease/SQLBot”的开源项目引起了我的注意。简单来说&#xff0c;它就是一个能让你用“说人话”的方式去操作数据库的工具。想象一下&#xff0c;你不再需要绞尽脑汁去拼写复杂的SQL语句&…

作者头像 李华