3. Linux I2C 子系统架构:三层架构设计
Linux I2C 子系统采用经典的三层软件架构设计,将协议处理、硬件适配与设备驱动解耦。理解这三层的职责划分与交互接口,是编写稳定、可移植 I2C 驱动的核心基础。
架构核心原则: 上层驱动不感知底层硬件细节,底层驱动不关心上层业务逻辑。I2C 核心层作为中间枢纽,提供统一的注册、通信与数据抽象接口。
3.1 三层架构总览
I2C 子系统从上到下依次为:I2C 设备驱动层、I2C 核心层、I2C 总线驱动层。每一层都有明确的职责边界和标准 API。
3.2 各层职责与关键数据结构
| 层级 | 核心数据结构 | 主要职责 | 典型文件 |
|---|---|---|---|
| 设备驱动层 | struct i2c_driverstruct i2c_client |
绑定具体从机设备,实现设备初始化、数据读写、中断处理、用户空间接口(sysfs/ioctl) | drivers/misc/eeprom/at24.c drivers/rtc/rtc-ds1307.c |
| 核心层 | struct i2c_adapterstruct i2c_algorithm |
管理总线适配器注册/注销、设备枚举与地址分配、协议传输调度、sysfs 属性创建 | drivers/i2c/i2c-core.c drivers/i2c/i2c-dev.c |
| 总线驱动层 | struct i2c_adapterstruct i2c_algorithm |
直接操作 I2C 控制器硬件寄存器,实现 start/stop/ack 时序,完成单次或多次消息传输 | drivers/i2c/busses/i2c-imx.c drivers/i2c/busses/i2c-omap.c |
3.3 设备驱动层详解
设备驱动层是开发者最常接触的层次。它通过 i2c_driver 结构体向核心层注册,当设备树中匹配的节点被枚举时,核心层调用 probe() 函数完成驱动初始化。
// i2c_driver 结构体核心成员
struct i2c_driver {
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
struct device_driver driver;
const struct i2c_device_id *id_table;
const struct of_device_id *of_match_table; // 设备树匹配表
};
工程实践: 设备驱动层应尽量使用
i2c_transfer() 或 i2c_smbus_read/write_*() 系列函数进行通信,避免直接操作 adapter 层的 master_xfer 回调。这保证了驱动在 I2C 多主、SMBus 等场景下的兼容性。
3.4 核心层详解
核心层是 I2C 子系统的中枢神经,负责以下关键任务:
- 总线适配器管理: 通过
i2c_add_adapter()注册总线适配器,分配总线编号(如 i2c-0、i2c-1)。 - 设备枚举: 解析设备树中 I2C 子节点,调用
i2c_new_device()创建i2c_client实例。 - 协议传输调度: 实现
i2c_transfer()函数,将上层消息队列传递给总线驱动的master_xfer()。 - sysfs 接口: 在
/sys/bus/i2c/下创建总线、设备和驱动属性文件。
3.5 总线驱动层详解
总线驱动层直接与硬件 I2C 控制器交互。每个 I2C 控制器对应一个 i2c_adapter 实例,其核心是 i2c_algorithm 结构体中的 master_xfer() 回调函数。
// i2c_algorithm 结构体核心成员
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags,
char read_write, u8 command, int size, union i2c_smbus_data *data);
};
关键注意事项: 总线驱动层必须正确处理以下硬件细节:
- 时钟延展(Clock Stretching)——从机拉低 SCL 时的超时处理
- 仲裁丢失(Arbitration Loss)——多主模式下的冲突检测与重试
- NAK 处理——从机无应答时的错误恢复
- 传输超时——根据总线速率计算合理的超时阈值
3.6 三层之间的数据流
一次完整的 I2C 读写操作,数据在三层之间的流动路径如下:
- 设备驱动层 调用
i2c_transfer()或i2c_smbus_read_byte()等封装函数。 - 核心层 接收请求,构造
struct i2c_msg消息数组,调用 adapter 的master_xfer()回调。 - 总线驱动层 将消息转换为硬件时序:发送 start 条件、从机地址、数据字节、stop 条件。
- 硬件控制器完成物理电平转换,通过 SDA/SCL 线与从机通信。
- 返回路径逆向:总线驱动层返回传输结果 → 核心层传递状态 → 设备驱动层获取数据。
| 步骤 | 调用链 | 所在层级 | 关键函数/操作 |
|---|---|---|---|
| 1 | 驱动发起读写 | 设备驱动层 | i2c_master_send() / i2c_master_recv() |
| 2 | 核心层封装消息 | 核心层 | i2c_transfer() → __i2c_transfer() |
| 3 | 总线驱动执行传输 | 总线驱动层 | adap->algo->master_xfer() |
| 4 | 硬件时序生成 | 硬件层 | SCL 时钟、SDA 数据、start/stop 条件 |
| 5 | 结果返回 | 逆向逐层返回 | 传输字节数 / 错误码 |
3.7 架构设计的工程价值
工程收益总结:
- 可移植性: 更换 SoC 时只需重写总线驱动层,设备驱动层无需修改。
- 可扩展性: 新增 I2C 从机设备只需编写设备驱动层代码,复用现有总线驱动。
- 可维护性: 三层职责清晰,调试时可快速定位问题所在层级。
- 标准化: 核心层提供统一的 API 和数据结构,降低团队协作成本。