3. I2C驱动架构概述:Linux内核I2C子系统分层
Linux内核的I2C子系统采用经典的分层架构设计,将硬件细节、总线管理、设备枚举与驱动逻辑解耦。理解这一分层模型是进行中断处理优化与DMA调优的前提。本章将系统阐述I2C子系统的四层结构及其核心数据结构。
3.1 四层架构总览
I2C子系统从上到下依次分为:设备层、驱动层、核心层与总线层(适配器层)。其中设备层与驱动层通过Linux设备模型绑定,核心层提供统一的注册/注销/通信接口,总线层直接操作硬件寄存器。
架构设计要点: 核心层对上层驱动屏蔽了总线适配器的硬件差异,驱动开发者只需关注i2c_driver与i2c_client的匹配逻辑,无需关心底层I2C时序。
3.2 核心数据结构详解
I2C子系统的四层架构由四个关键数据结构支撑:i2c_adapter、i2c_algorithm、i2c_client、i2c_driver。下表从职责、所属层次、关键字段三个维度进行对比。
| 数据结构 | 所属层次 | 核心职责 | 关键字段(与中断/DMA相关) |
|---|---|---|---|
| i2c_adapter | 总线层 | 代表一个I2C总线控制器,管理物理总线 | nr(总线编号)、algo(算法指针)、lock_ops、timeout、retries |
| i2c_algorithm | 总线层 | 定义master_xfer、smbus_xfer等硬件传输函数 | master_xfer(中断/DMA传输入口)、functionality |
| i2c_client | 设备层 | 描述一个具体的I2C从设备 | addr(7位/10位地址)、adapter(所属适配器)、irq(中断号) |
| i2c_driver | 驱动层 | 驱动入口,实现probe/remove及用户空间接口 | probe(申请中断、配置DMA)、id_table、shutdown |
3.3 i2c_adapter 与 i2c_algorithm:总线层的基石
i2c_adapter 是I2C总线控制器的软件抽象,每个物理I2C总线对应一个adapter实例。它通过 i2c_algorithm 结构体将硬件传输能力暴露给核心层。
struct i2c_adapter {
struct module *owner;
unsigned int class; /* 适配器支持的设备类 */
const struct i2c_algorithm *algo; /* 算法指针:核心传输接口 */
void *algo_data; /* 算法私有数据,常指向DMA描述符池 */
/* 总线管理 */
int nr; /* 总线编号,由核心层分配 */
struct device dev; /* 内核设备模型嵌入 */
struct mutex bus_lock; /* 总线访问互斥锁 */
/* 超时与重试 */
unsigned long timeout; /* 传输超时(jiffies) */
unsigned int retries; /* 仲裁失败重试次数 */
/* DMA相关 */
struct i2c_dma_info dma_info; /* DMA通道与描述符信息 */
};
i2c_algorithm 是硬件传输的抽象接口,驱动开发者必须实现 master_xfer 回调。该回调是中断处理与DMA调优的核心战场。
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);
/* 功能位掩码:声明支持的操作(I2C_FUNC_I2C / I2C_FUNC_SMBUS_*) */
u32 (*functionality)(struct i2c_adapter *adap);
};
调优切入点:
master_xfer 是中断/DMA传输的入口。在实现该函数时,通常将消息拆分为DMA描述符链,触发硬件传输,并在传输完成中断中调用 i2c_transfer_complete() 唤醒等待队列。
3.4 i2c_client:从设备的软件化身
每个I2C从设备在Linux内核中对应一个 i2c_client 实例。该结构体在设备枚举阶段由核心层创建,或在设备树/ACPI中静态定义。
struct i2c_client {
unsigned short flags; /* I2C_CLIENT_TEN / I2C_CLIENT_SLAVE 等 */
unsigned short addr; /* 从设备地址(7位或10位) */
char name[I2C_NAME_SIZE]; /* 设备名称,用于驱动匹配 */
struct i2c_adapter *adapter; /* 所属适配器指针 */
struct device dev; /* 内核设备模型嵌入 */
int irq; /* 从设备中断号(重要!) */
/* 设备树/ACPI相关 */
struct list_head detected; /* 已检测设备链表 */
};
中断号传递:
i2c_client->irq 通常在设备树中通过 interrupts 属性指定,或在板级代码中通过 i2c_board_info.irq 赋值。驱动在probe阶段应使用 devm_request_irq(client->irq, ...) 注册中断处理函数。
3.5 i2c_driver:驱动逻辑的载体
i2c_driver 是驱动开发者主要打交道的结构体。它通过 id_table 与 i2c_client 匹配,在probe中完成硬件初始化、中断注册、DMA缓冲分配等操作。
struct i2c_driver {
unsigned int class; /* 驱动支持的设备类 */
/* 匹配与生命周期 */
int (*probe)(struct i2c_client *client,
const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
/* 电源管理 */
int (*suspend)(struct i2c_client *client, pm_message_t state);
int (*resume)(struct i2c_client *client);
/* 设备ID表:用于OF/ACPI/传统匹配 */
const struct i2c_device_id *id_table;
/* 设备驱动模型 */
struct device_driver driver;
};
3.6 分层协作流程:一次典型I2C传输
以下流程图展示了一次I2C读操作在四层之间的完整协作路径,重点标注了中断与DMA的介入点。
3.7 关键设计模式:分层解耦与回调机制
I2C子系统的分层设计遵循以下核心原则,这些原则直接影响中断与DMA的实现方式:
- 核心层不感知硬件细节:
i2c_transfer()只操作i2c_msg抽象,不关心底层是PIO、中断还是DMA。 - 总线层持有硬件控制权:
i2c_algorithm.master_xfer是唯一需要操作硬件寄存器的函数,中断服务例程(ISR)通常在此层注册。 - 驱动层通过回调获取结果: 驱动层通过
i2c_transfer()同步等待或异步回调(需自行封装)获取传输结果。 - 中断号传递路径: 设备树 →
i2c_client.irq→i2c_driver.probe→request_irq()。
DMA调优前置知识: 在总线层实现
master_xfer 时,若使用DMA,需在probe阶段预分配DMA缓冲池和描述符环。中断处理函数中应只做最小工作(如更新描述符状态、唤醒等待队列),避免在ISR中执行耗时操作。
3.8 数据结构关系总结
以下表格从“中断处理”和“DMA调优”两个视角,总结四个数据结构的关键作用:
| 数据结构 | 中断处理视角 | DMA调优视角 |
|---|---|---|
| i2c_adapter | 提供 dev 设备结构,用于中断注册;bus_lock 保护中断共享资源 |
dma_info 字段存储DMA通道、描述符基址、缓冲池指针 |
| i2c_algorithm | master_xfer 中使能硬件中断,注册ISR回调 |
master_xfer 中构建DMA描述符链,触发DMA传输 |
| i2c_client | irq 字段提供从设备中断号,驱动probe中注册 |
通常不直接涉及DMA,但可通过 dev 获取DMA掩码 |
| i2c_driver | probe中调用 devm_request_irq(),remove中释放 |
probe中分配DMA缓冲池,remove中释放 |