4. Linux I2C子系统架构

Linux I2C 子系统采用经典的三层软件架构设计,将硬件控制、总线管理与设备操作解耦。理解这三层模型是进行 Android 驱动开发的基础,也是后续实现用户态驱动(i2c-dev)的前提。

核心思想: 分层设计使得 I2C 控制器驱动(总线驱动)与具体的外设驱动(设备驱动)相互独立,开发者只需关注自己负责的层次,大大提高了代码复用性和可移植性。

4.1 三层模型总览

Linux I2C 子系统从上到下依次为:I2C 设备驱动层、I2C 核心层、I2C 总线驱动层。下面这张 SVG 图清晰地展示了这三层之间的调用关系与数据流。

Linux I2C 子系统三层架构 I2C 设备驱动层 (I2C Device Driver) i2c_driver / i2c_client 实现具体外设功能(如:触摸屏、传感器、音频编解码器) 调用核心层 API I2C 核心层 (I2C Core) i2c-core.c / i2c-dev.c 提供统一接口、设备注册/注销、协议适配、地址解析 调用总线驱动算法 I2C 总线驱动层 (I2C Bus Driver) i2c_adapter / i2c_algorithm 直接操作 I2C 控制器硬件寄存器,产生 SCL/SDA 时序 用户态程序通过 /dev/i2c-N 访问核心层,核心层再调度总线驱动完成物理传输

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() 中实现以下时序操作:

  1. 发送起始信号 (S)
  2. 发送 7 位或 10 位从设备地址 + 读写位
  3. 等待 ACK 信号
  4. 发送或接收数据字节
  5. 发送停止信号 (P)

4.5 I2C 设备驱动层:外设功能的实现

设备驱动层是开发者最常接触的层次。一个 I2C 设备驱动需要完成以下步骤:

  1. 定义 i2c_driver 结构体: 填充 probe、remove、id_table 等回调函数。
  2. 注册驱动: 调用 i2c_add_driver() 将驱动注册到核心层。
  3. 在 probe 中初始化设备: 分配私有数据结构、注册中断、创建输入设备或 ALSA 声卡等。
  4. 使用 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 读操作数据流 用户态程序 ioctl / read I2C 核心层 (i2c-dev) i2c_transfer() I2C 总线驱动层 master_xfer() 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 厂商提供
开发与调试 复杂,需编译内核,容易导致系统崩溃 简单,可快速迭代,崩溃不影响内核
性能 高,无上下文切换开销 略低,有系统调用开销