4. Linux I2C子系统架构
Linux I2C 子系统采用经典的三层软件架构设计,将硬件控制、总线管理与设备操作解耦。理解这三层模型是进行 Android 驱动开发的基础,也是后续实现用户态驱动(i2c-dev)的前提。
4.1 三层模型总览
Linux I2C 子系统从上到下依次为:I2C 设备驱动层、I2C 核心层、I2C 总线驱动层。下面这张 SVG 图清晰地展示了这三层之间的调用关系与数据流。
4.2 各层职责与核心数据结构
每一层都有其特定的职责和核心数据结构。下表总结了各层的关键要素:
| 层次 | 核心数据结构 | 主要职责 | 对应源文件(示例) |
|---|---|---|---|
| I2C 设备驱动层 | i2c_driver, i2c_client |
实现具体外设逻辑;通过 i2c_transfer() 与设备通信;处理中断、上报数据 |
drivers/input/touchscreen/ |
| I2C 核心层 | i2c_bus_type, i2c_adapter (管理) |
维护总线、设备、驱动链表;提供 i2c_add_driver() / i2c_new_device() 等 API;实现 i2c-dev 字符设备 |
drivers/i2c/i2c-core.c |
| I2C 总线驱动层 | i2c_adapter, i2c_algorithm |
封装硬件控制器差异;实现 master_xfer() 函数;处理时钟、起始/停止条件、ACK/NACK |
drivers/i2c/busses/i2c-omap.c |
4.3 I2C 核心层:承上启下的枢纽
核心层是整个子系统的中枢,它不直接操作硬件,而是提供一套标准化的框架。核心层的主要功能包括:
- 总线管理: 注册/注销 I2C 总线(
i2c_add_adapter())。 - 设备与驱动管理: 提供
i2c_new_device()创建设备,i2c_add_driver()注册驱动,并负责匹配 probe。 - 协议传输: 封装
i2c_transfer()函数,该函数会找到对应的 adapter,并调用其 algorithm 中的master_xfer()。 - 用户态接口: 通过
i2c-dev.c实现/dev/i2c-N字符设备,允许用户态程序直接发起 I2C 通信。
i2c_transfer() 是设备驱动与总线驱动之间的唯一桥梁。无论是内核态驱动还是用户态程序(通过 ioctl),最终都会调用到此函数。
4.4 I2C 总线驱动层:硬件操作的封装
总线驱动层负责与具体的 I2C 控制器硬件打交道。每个 I2C 控制器对应一个 i2c_adapter 实例,而 i2c_algorithm 则定义了该适配器如何执行 I2C 总线上的数据传输。
一个典型的 i2c_algorithm 结构体定义如下:
struct i2c_algorithm {
/* 主模式传输函数,必须实现 */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* 从模式传输函数(可选) */
int (*slave_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs);
/* 功能支持查询 */
u32 (*functionality)(struct i2c_adapter *adap);
};
总线驱动开发者需要根据芯片手册,在 master_xfer() 中实现以下时序操作:
- 发送起始信号 (S)
- 发送 7 位或 10 位从设备地址 + 读写位
- 等待 ACK 信号
- 发送或接收数据字节
- 发送停止信号 (P)
4.5 I2C 设备驱动层:外设功能的实现
设备驱动层是开发者最常接触的层次。一个 I2C 设备驱动需要完成以下步骤:
- 定义 i2c_driver 结构体: 填充
probe、remove、id_table等回调函数。 - 注册驱动: 调用
i2c_add_driver()将驱动注册到核心层。 - 在 probe 中初始化设备: 分配私有数据结构、注册中断、创建输入设备或 ALSA 声卡等。
- 使用 i2c_transfer 通信: 构造
i2c_msg数组,调用i2c_transfer()或封装好的i2c_smbus_read_byte_data()等函数。
下面是一个简化的设备驱动 probe 函数示例:
static int my_sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct my_sensor_data *data;
int ret;
/* 1. 分配私有数据 */
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
i2c_set_clientdata(client, data);
data->client = client;
/* 2. 读取设备 ID 以确认连接 */
ret = i2c_smbus_read_byte_data(client, REG_DEVICE_ID);
if (ret < 0) {
dev_err(&client->dev, "Failed to read device ID\n");
return ret;
}
/* 3. 注册中断 */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
my_sensor_irq_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"my_sensor", data);
if (ret)
return ret;
/* 4. 注册输入设备或其它框架 */
// ...
dev_info(&client->dev, "My I2C sensor probed successfully\n");
return 0;
}
4.6 三层模型的数据流与协作
当用户态应用或内核驱动需要读取一个 I2C 传感器的数据时,完整的调用链如下:
i2c_adapter 属于设备驱动层。实际上,i2c_adapter 是总线驱动层的核心,它代表一个 I2C 控制器,而 i2c_client 才是挂在总线上的具体设备。一个 adapter 可以管理多个 client。
4.7 三层模型在 Android 系统中的体现
在 Android 系统中,三层模型依然适用,但增加了 HAL (Hardware Abstraction Layer) 这一层。通常,I2C 设备驱动(如触控、传感器)以内核模块形式存在,而 HAL 层通过 /dev/i2c-N 或内核驱动导出的接口与硬件交互。本课程后续章节将重点讲解如何利用 i2c-dev 在用户态 HAL 中直接实现驱动逻辑,绕过内核态设备驱动层。
下表对比了传统内核态驱动与基于 i2c-dev 的用户态驱动在三层模型中的位置差异:
| 对比维度 | 传统内核态驱动 | 用户态驱动 (i2c-dev) |
|---|---|---|
| 设备驱动层位置 | 内核空间,i2c_driver 实现 |
用户空间,通过 /dev/i2c-N 操作 |
| 核心层角色 | 管理驱动生命周期,调用 probe | 提供字符设备接口,透传 ioctl 请求 |
| 总线驱动层 | 不变,由 SoC 厂商提供 | 不变,由 SoC 厂商提供 |
| 开发与调试 | 复杂,需编译内核,容易导致系统崩溃 | 简单,可快速迭代,崩溃不影响内核 |
| 性能 | 高,无上下文切换开销 | 略低,有系统调用开销 |