接前一篇文章:Linux MDIO子系统深度剖析:从原理到实践(4)

 

五、用户空间访问实例

1. 使用ioctl访问PHY寄存器

虽然大多数情况下PHY的管理由内核驱动自动处理,但在调试或特殊应用场景中,用户空间程序可能需要直接访问PHY寄存器。Linux提供了通过socket ioctl接口访问PHY寄存器的能力,这为用户空间工具提供了底层的PHY管理功能。

主要的ioctl命令包括:

  • SIOCGMIIPHY:获取MDIO总线上的当前PHY地址。
  • SIOCGMIIREG:读取指定PHY寄存器的值。
  • SIOCSMIIREG:设置指定PHY寄存器的值。

这些ioctl命令使用struct mii_ioctl_data作为数据传输结构,其定义如下:

struct mii_ioctl_data {
    __u16 phy_id;      // PHY 设备地址
    __u16 reg_num;     // 寄存器地址
    __u16 val_in;      // 写入的值(用于写操作)
    __u16 val_out;     // 读取的值(用于读操作)
};

这种用户空间的访问机制类似于提供了一个硬件调试终端,让开发者和系统管理员能够直接与PHY芯片"对话",检查状态、修改配置或进行故障诊断。

2. 完整代码示例

以下是一个完整的用户空间MDIO工具实现,支持读取和写入PHY寄存器。这个工具可以编译为命令行程序,用于调试和配置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;   \
        }

#define help() \
    printf("mdio: 一个用户空间 MDIO 调试工具\n");                  \
    printf("读取操作: mdio 接口名 寄存器地址\n");          \
    printf("写入操作: mdio 接口名 寄存器地址 数值\n");    \
    printf("示例:\n");            \
    printf("mdio eth0 1\t# 读取 eth0 的 PHY 寄存器 1\n");             \
    printf("mdio eth0 0 0x1120\t# 向 eth0 的 PHY 寄存器 0 写入 0x1120\n\n");      \

int sockfd;

int main(int argc, char *argv[]){
    if(argc == 1 || !strcmp(argv[1], "-h")){
        help();
        return 0;
    }

    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("读取 PHY 地址: 0x%x  寄存器: 0x%x   值: 0x%x\n", 
               mii->phy_id, mii->reg_num, mii->val_out);
        
        // 检查链路状态(寄存器 1 的第 2 位)
        if (mii->reg_num == 1) {
            if (mii->val_out & 0x0004) {
                printf("链路状态: UP\n");
            } else {
                printf("链路状态: 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(sockwd, SIOCSMIIREG, &ifr);
        reteck(ret);

        printf("写入 PHY 地址: 0x%x  寄存器: 0x%x  值: 0x%x\n", 
               mii->phy_id, mii->reg_num, mii->val_in);
    } else {
        help();
    }

lab:
    if (sockfd >= 0) close(sockfd);
    return 0;
}

3. 编译与运行

这个MDIO工具可以很容易地编译和使用:

(1)编译

gcc -o mdio mdio_tool.c

(2)安装(可选)

sudo cp mdio /usr/local/bin/

(3)使用示例

  • 读取PHY状态寄存器(寄存器 1)
./mdio eth0 1

输出结果:

读取 PHY 地址: 0x1  寄存器: 0x1   值: 0x796d
链路状态: UP
  • 写入PHY控制寄存器(寄存器0)
./mdio eth0 0 0x1140

输出结果:

写入 PHY 地址: 0x1  寄存器: 0x0  值: 0x1140
  • 检查链路状态
./mdio eth0 1

然后检查输出中的值,寄存器1的第2位(从0开始计数)表示链路状态:1表示链路UP,0表示链路DOWN。

这个工具提供了对PHY寄存器的底层访问能力,对于调试网络连接问题、验证PHY配置或学习MDIO总线工作原理都非常有用。但需要注意的是,不当的寄存器修改可能会导致网络连接中断,因此在生产环境中使用需要格外小心。

更多内容请看下回。

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐