4. Linux I2C 子系统架构:三层模型详解
Linux I2C 子系统采用经典的三层软件架构设计,将硬件控制、协议抽象和设备逻辑解耦。理解这一分层模型是编写 I2C 设备驱动、调试 QEMU 模拟 I2C 外设的基础。三层分别为:I2C 核心层、I2C 总线驱动(适配器驱动)、I2C 设备驱动。
核心思想: 总线驱动负责“怎么发”,设备驱动负责“发什么”。核心层提供统一的注册、收发、枚举机制,使上层驱动无需关心底层硬件差异。
4.1 I2C 核心层 (I2C Core)
核心层是连接总线驱动与设备驱动的桥梁,提供以下关键功能:
- 驱动与设备管理: 提供
i2c_add_driver()、i2c_del_driver()、i2c_new_device()等 API,管理i2c_driver和i2c_client的生命周期。 - 地址检测与枚举: 实现
i2c_detect()机制,通过i2c_algorithm的master_xfer()扫描总线地址,自动创建设备节点。 - 数据传输封装: 提供
i2c_transfer()、i2c_master_send()、i2c_master_recv()等高层接口,内部调用总线驱动的master_xfer()。 - SMBus 协议支持: 实现 SMBus 兼容的读写函数(
i2c_smbus_read_byte()等),方便简单传感器类设备驱动。 - 用户空间接口: 通过
i2c-dev.c提供/dev/i2c-N字符设备,支持I2C_RDWR、I2C_SLAVE等 ioctl 命令。
QEMU 调试提示: 在 QEMU 中模拟 I2C 控制器时,核心层代码无需修改。只需确保总线驱动正确注册
i2c_adapter,设备驱动即可通过标准 API 通信。
4.2 I2C 总线驱动 (Adapter Driver)
总线驱动直接操作 I2C 控制器硬件寄存器,实现底层时序。每个 I2C 总线对应一个 i2c_adapter 实例,其核心是 i2c_algorithm 结构体:
| 结构体成员 | 功能描述 | QEMU 模拟要点 |
|---|---|---|
master_xfer() |
执行 I2C 消息传输(起始、地址、数据、停止) | 在 QEMU 中模拟 SCL/SDA 时序,调用设备模型回调 |
smbus_xfer() |
执行 SMBus 协议传输(可选) | 可复用 master_xfer 或单独实现 |
functionality() |
返回适配器支持的功能掩码 | 根据模拟外设能力设置 I2C_FUNC_I2C 等标志 |
总线驱动的注册流程:
- 分配并初始化
i2c_adapter结构体,设置algo、owner、class等字段。 - 调用
i2c_add_adapter()或i2c_add_numbered_adapter()注册到核心层。 - 核心层自动为适配器分配总线编号(如
i2c-0、i2c-1)。
常见错误: 总线驱动未正确实现
master_xfer() 中的超时处理,或未在 QEMU 中模拟 ACK/NACK 响应,会导致设备驱动探测失败。
4.3 I2C 设备驱动 (Device Driver)
设备驱动面向具体外设(如温度传感器、EEPROM、RTC),通过核心层 API 与总线驱动交互。关键数据结构:
// i2c_driver 结构体核心字段
struct i2c_driver {
int (*probe)(struct i2c_client *client);
int (*remove)(struct i2c_client *client);
struct device_driver driver; // 继承通用驱动模型
const struct i2c_device_id *id_table; // 设备 ID 匹配表
unsigned int class; // 设备类标志
};
// i2c_client 结构体核心字段
struct i2c_client {
unsigned short addr; // 7 位从机地址
char name[I2C_NAME_SIZE]; // 设备名称
struct i2c_adapter *adapter; // 所属适配器
struct device dev; // 内核设备模型
};
设备驱动的典型实现步骤:
- 定义设备 ID 表: 使用
i2c_device_id数组声明支持的设备名称和驱动数据。 - 实现 probe() 回调: 在 probe 中分配私有数据结构、初始化硬件、注册字符设备或输入设备等。
- 实现 remove() 回调: 释放资源、注销设备。
- 注册驱动: 调用
module_i2c_driver()宏或手动调用i2c_add_driver()。
4.4 三层模型交互示例:QEMU 模拟 EEPROM 读取
以 QEMU 中模拟 AT24C02 EEPROM 为例,展示三层如何协作:
| 层次 | QEMU 侧实现 | Linux 侧实现 |
|---|---|---|
| 总线驱动 | QEMU 的 I2C 控制器模型(如 i.MX6 I2C)实现 SCL/SDA 时序,调用 i2c_send()/i2c_recv() |
Linux 的 imx-i2c.c 驱动注册 i2c_adapter,实现 master_xfer() |
| 核心层 | 无直接对应,QEMU 通过 i2c_bus 对象管理从设备 |
Linux 核心层匹配 i2c_driver 与 i2c_client,调用 i2c_transfer() |
| 设备驱动 | QEMU 的 at24c.c 设备模型实现存储读写,响应总线驱动请求 | Linux 的 at24.c 驱动通过 i2c_smbus_read_byte() 读取数据 |
关键理解: 在 QEMU 中,总线驱动和设备驱动都是模拟的“硬件模型”,而 Linux 内核中的对应驱动是“软件驱动”。两者通过 QEMU 的 I2C 总线对象进行跨边界通信。调试时,可以在 QEMU 侧设置断点观察
master_xfer() 的调用参数,验证 Linux 驱动是否正确构造了 I2C 消息。
4.5 三层模型的设计优势
- 可移植性: 设备驱动无需修改即可运行在不同 I2C 控制器上(如 i.MX6、OMAP、DesignWare)。
- 模块化: 总线驱动和设备驱动可分别独立开发和加载,通过核心层动态绑定。
- 调试便利: 核心层提供
i2c-tools(i2cdetect、i2cget、i2cset)等用户空间工具,方便验证硬件连接。 - QEMU 友好: 模拟新外设时,只需实现设备模型并挂接到 QEMU 的 I2C 总线上,Linux 驱动无需改动。