4. Linux I2C 子系统架构:三层模型详解

Linux I2C 子系统采用经典的三层软件架构设计,将硬件控制、协议抽象和设备逻辑解耦。理解这一分层模型是编写 I2C 设备驱动、调试 QEMU 模拟 I2C 外设的基础。三层分别为:I2C 核心层、I2C 总线驱动(适配器驱动)、I2C 设备驱动。

核心思想: 总线驱动负责“怎么发”,设备驱动负责“发什么”。核心层提供统一的注册、收发、枚举机制,使上层驱动无需关心底层硬件差异。
Linux I2C 子系统三层架构 I2C 设备驱动层 i2c_driver / i2c_client 实现 probe()、remove()、i2c_transfer() 回调 I2C 核心层 (I2C Core) i2c-core.c / i2c-dev.c 设备注册/注销、地址检测、SMBus 协议、ioctl 接口 I2C 总线驱动层 (Adapter) i2c_adapter / i2c_algorithm 实现 master_xfer()、smbus_xfer(),操作具体硬件寄存器 用户空间可通过 /dev/i2c-N 字符设备经核心层访问总线驱动

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 等标志

总线驱动的注册流程:

  1. 分配并初始化 i2c_adapter 结构体,设置 algo、owner、class 等字段。
  2. 调用 i2c_add_adapter() 或 i2c_add_numbered_adapter() 注册到核心层。
  3. 核心层自动为适配器分配总线编号(如 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()。
I2C 设备驱动注册与通信流程 I2C 设备驱动 i2c_add_driver() I2C 核心层 匹配 i2c_client I2C 总线驱动 master_xfer() 注册 调用 通信过程:i2c_transfer() 设备驱动 → 核心层封装消息 → 总线驱动 master_xfer() → 硬件 返回传输字节数 / 错误码

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 驱动无需改动。