2. Linux I2C 子系统架构:三层架构解析

Linux I2C 子系统采用经典的分层设计思想,将 I2C 总线相关的功能抽象为三个独立的层次:I2C 核心层、I2C 总线驱动层和I2C 设备驱动层。这种分层架构实现了硬件无关性与硬件相关性的解耦,使得驱动开发者可以专注于各自层次的逻辑,而无需关心其他层次的实现细节。

核心设计理念: 三层架构的核心目标是“复用”与“抽象”。核心层提供通用机制,总线层管理硬件时序,设备层处理具体外设协议。任何一层的修改都不应影响其他层的稳定性。

2.1 三层架构总览

下图展示了 Linux I2C 子系统的完整分层结构及各层之间的交互关系:

Linux I2C 子系统三层架构 用户空间 (User Space) I2C 设备驱动层 (I2C Device Driver) i2c_driver / i2c_client / 中断处理 / 设备树匹配 示例:eeprom_driver, sensor_driver, audio_codec_driver I2C 核心层 (I2C Core) i2c-core.c / i2c-dev.c / 总线注册 / 设备注册 / 协议抽象 i2c_transfer() / i2c_smbus_*() / 地址解析 / 锁定机制 I2C 总线驱动层 (I2C Bus Adapter Driver) i2c_adapter / i2c_algorithm / 硬件时序控制 示例:i2c-imx.c, i2c-omap.c, i2c-s3c2410.c 硬件层:I2C 控制器 (I2C Controller) + I2C 从设备 内核空间 (Kernel Space)

2.2 各层职责与核心数据结构

每一层都有其明确的数据结构和职责边界。下表总结了各层的核心结构、主要功能及典型实现文件:

层次 核心数据结构 主要职责 典型源文件
I2C 设备驱动层 struct i2c_driver
struct i2c_client
实现具体外设的读写协议、中断处理、设备树匹配、sysfs 接口 drivers/i2c/i2c-core-of.c
drivers/misc/eeprom/at24.c
I2C 核心层 struct i2c_adapter
struct i2c_algorithm
总线注册/注销、设备枚举、地址分配、SMBus 协议封装、锁定与重试 drivers/i2c/i2c-core.c
drivers/i2c/i2c-dev.c
I2C 总线驱动层 struct platform_driver
struct i2c_algorithm
操作 I2C 控制器寄存器、产生 START/STOP/ACK 时序、时钟拉伸处理 drivers/i2c/busses/i2c-imx.c
drivers/i2c/busses/i2c-omap.c

2.3 设备驱动层详解

I2C 设备驱动层是驱动开发者最常接触的层次。它负责与具体的 I2C 从设备(如 EEPROM、传感器、音频编解码器)进行通信。该层的主要工作包括:

  • 定义 i2c_driver 结构体:填充 probe()、remove()、id_table 等回调函数。
  • 设备树匹配:通过 of_match_table 将设备节点与驱动绑定。
  • 中断注册:在 probe() 中调用 request_threaded_irq() 或 devm_request_irq() 注册中断处理函数。
  • 数据传输:调用核心层提供的 i2c_transfer() 或 i2c_smbus_*() 系列函数完成读写操作。
关键点: 设备驱动层不直接操作硬件寄存器,所有 I2C 总线时序操作都委托给核心层和总线驱动层完成。设备驱动只关心“发什么数据”和“收什么数据”。

2.4 核心层详解

I2C 核心层是整个子系统的“中枢神经系统”,它提供了以下关键机制:

  1. 总线注册与管理:维护系统中所有 I2C 适配器的链表,提供 i2c_add_adapter() / i2c_del_adapter() 接口。
  2. 设备注册与枚举:解析设备树或板级代码,创建 i2c_client 实例,并调用匹配的驱动 probe()。
  3. 传输抽象:封装 i2c_transfer() 函数,处理消息队列、重试逻辑、总线锁定与原子上下文。
  4. SMBus 协议支持:提供 i2c_smbus_read_byte()、i2c_smbus_write_word_data() 等便捷函数,简化常见协议操作。
  5. 设备文件接口:通过 i2c-dev.c 提供 /dev/i2c-N 字符设备,允许用户空间直接访问 I2C 总线。

2.5 总线驱动层详解

总线驱动层是离硬件最近的层次,它直接操作 I2C 控制器的寄存器。该层的核心是 struct i2c_algorithm,它定义了底层硬件如何执行 I2C 总线协议:

struct i2c_algorithm {
    /* 标准 I2C 传输函数 */
    int (*master_xfer)(struct i2c_adapter *adap,
                       struct i2c_msg *msgs, int num);
    /* SMBus 传输函数(可选) */
    int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
                      unsigned short flags, char read_write,
                      u8 command, int size,
                      union i2c_smbus_data *data);
    /* 功能支持查询 */
    u32 (*functionality)(struct i2c_adapter *adap);
};

总线驱动层的主要工作包括:

  • 实现 master_xfer:解析 i2c_msg 数组,逐条生成 START、SLAVE_ADDR、DATA、STOP 等时序。
  • 时钟与速率控制:设置 I2C 控制器的时钟分频器,支持标准模式(100kHz)和快速模式(400kHz)。
  • 中断处理:处理控制器产生的中断(如传输完成、仲裁丢失、NACK 接收),并唤醒等待的传输线程。
  • 电源管理:实现运行时挂起/恢复,在总线空闲时关闭控制器时钟以降低功耗。
常见误区: 初学者常将设备驱动层的 i2c_driver 与总线驱动层的 platform_driver 混淆。总线驱动层通常以平台驱动形式注册,而设备驱动层以 I2C 驱动形式注册。两者通过核心层的适配器机制连接。

2.6 三层交互流程

下面以一次典型的 I2C 读操作为例,展示三层之间的完整调用链:

I2C 读操作三层调用流程 设备驱动层 i2c_smbus_read_byte_data() I2C 核心层 i2c_transfer() → 消息队列封装 总线驱动层 master_xfer() I2C 控制器寄存器操作 START + ADDR + DATA + STOP 返回数据或错误码 调用链说明: 1. 设备驱动调用 i2c_smbus_read_byte_data(client, reg_addr) 2. 核心层构造 i2c_msg 数组(写 reg_addr + 读数据),调用 i2c_transfer() 3. 核心层获取总线锁,调用 adapter->algo->master_xfer() 4. 总线驱动层操作硬件寄存器,完成物理时序,返回传输结果

2.7 分层设计的优势

Linux I2C 子系统的三层架构带来了以下工程优势:

优势 说明
可移植性 更换 SoC 时只需重写总线驱动层,设备驱动层和核心层无需修改。
代码复用 同一份 EEPROM 驱动可以在不同平台的 I2C 控制器上运行。
关注点分离 设备驱动开发者无需了解 I2C 控制器的寄存器细节,只需调用标准 API。
稳定性 核心层提供经过充分测试的锁定、重试和错误处理机制,减少驱动缺陷。
调试便利 通过 /dev/i2c-N 和 i2c-tools 可以在用户空间直接验证总线通信。
实践建议: 在开发新的 I2C 设备驱动时,优先在现有内核源码中查找同类型设备的驱动作为参考。大多数常见外设(如 AT24 EEPROM、BMP280 传感器、DS1307 RTC)在内核中已有现成驱动,理解其分层调用模式是快速上手的关键。