RTL8367 I2C GPIO 驱动用户空间交互实现
本文介绍如何在用户空间通过 ioctl 接口与 RTL8367 I2C GPIO 驱动进行通信,实现寄存器读写及端口速率查询功能。
核心数据结构定义
首先定义与内核驱动交互的数据结构,包括 I2C 传输参数和端口速率信息:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#define RTL8367_MAGIC 's'
#define CMD_I2C_RD _IOWR(RTL8367_MAGIC, 0, struct i2c_msg)
#define CMD_I2C_WR _IOWR(RTL8367_MAGIC, 1, struct i2c_msg)
#define CMD_PORT_RATE_GET _IOWR(RTL8367_MAGIC, 2, struct port_info)
struct i2c_msg {
int bus; /* I2C 总线索引 */
int dev_addr; /* 从机地址(7位) */
int reg; /* 寄存器偏移 */
int val; /* 数据内容 */
};
struct port_info {
int port; /* 物理端口号 */
int rate; /* 协商速率 (Mbps) */
};
底层操作封装
将 ioctl 调用封装为可读性更高的函数,便于上层业务调用:
static inline void die(const char *ctx)
{
perror(ctx);
_exit(EXIT_FAILURE);
}
int reg_read(int fd, int bus, int sla, int reg_off)
{
struct i2c_msg msg = {
.bus = bus,
.dev_addr = sla,
.reg = reg_off,
.val = 0
};
if (ioctl(fd, CMD_I2C_RD, &msg) < 0)
die("寄存器读取失败");
printf("[读] 寄存器 0x%04X = 0x%04X\n", reg_off, msg.val);
return msg.val;
}
void reg_write(int fd, int bus, int sla, int reg_off, int data)
{
struct i2c_msg msg = {
.bus = bus,
.dev_addr = sla,
.reg = reg_off,
.val = data
};
if (ioctl(fd, CMD_I2C_WR, &msg) < 0)
die("寄存器写入失败");
printf("[写] 寄存器 0x%04X <- 0x%04X\n", reg_off, data);
}
int link_speed_query(int fd, int port_no)
{
struct port_info info = { .port = port_no, .rate = 0 };
if (ioctl(fd, CMD_PORT_RATE_GET, &info) < 0)
die("速率查询失败");
printf("端口 %d 当前速率: %d Mbps\n", port_no, info.rate);
return info.rate;
}
命令行交互实现
通过解析命令行参数,支持三种操作模式:读寄存器、写寄存器、查速率:
static void usage(const char *prog)
{
fprintf(stderr,
"用法: %s <设备节点> <命令> [参数...]\n\n"
"命令:\n"
" rd <总线> <从机地址> <寄存器> 读取寄存器\n"
" wr <总线> <从机地址> <寄存器> <值> 写入寄存器\n"
" rate <端口号> 查询端口速率\n",
prog);
}
int main(int argc, char **argv)
{
if (argc < 4) {
usage(argv[0]);
return EXIT_FAILURE;
}
int fd = open(argv[1], O_RDWR);
if (fd < 0)
die("打开设备失败");
const char *op = argv[2];
if (strcmp(op, "rd") == 0) {
if (argc != 6) { usage(argv[0]); return EXIT_FAILURE; }
int bus = atoi(argv[3]);
int sla = (int)strtol(argv[4], NULL, 0);
int reg = (int)strtol(argv[5], NULL, 0);
reg_read(fd, bus, sla, reg);
} else if (strcmp(op, "wr") == 0) {
if (argc != 7) { usage(argv[0]); return EXIT_FAILURE; }
int bus = atoi(argv[3]);
int sla = (int)strtol(argv[4], NULL, 0);
int reg = (int)strtol(argv[5], NULL, 0);
int val = (int)strtol(argv[6], NULL, 0);
reg_write(fd, bus, sla, reg, val);
} else if (strcmp(op, "rate") == 0) {
if (argc != 4) { usage(argv[0]); return EXIT_FAILURE; }
int port = atoi(argv[3]);
link_speed_query(fd, port);
} else {
fprintf(stderr, "未知命令: %s\n", op);
usage(argv[0]);
close(fd);
return EXIT_FAILURE;
}
close(fd);
return EXIT_SUCCESS;
}
编译与运行
使用 GCC 编译并赋予执行权限:
gcc -Wall -O2 -o rtl8367_ctl rtl8367_ctl.c
# 读取寄存器示例
./rtl8367_ctl /dev/rtl8367 rd 0 0x5c 0x1b00
# 写入寄存器示例
./rtl8367_ctl /dev/rtl8367 wr 0 0x5c 0x1b00 0x00ff
# 查询端口速率示例
./rtl8367_ctl /dev/rtl8367 rate 1
设计要点说明
- Magic Number 选择:使用字符
's'作为 ioctl 幻数,避免与系统其他驱动冲突 - 地址解析:采用
strtol自动识别十进制或十六进制输入(前缀0x) - 错误处理:统一封装错误输出,确保异常情况下的资源释放
- 可扩展性:数据结构预留字段,便于后续支持多字节读写或批量操作