4. Linux I2C核心层架构

I2C 核心层(I2C Core)是 Linux 内核中 I2C 子系统的心脏,它向上为 I2C 设备驱动提供统一的注册与通信接口,向下通过 i2c_adapter 抽象物理控制器,并通过 i2c_algorithm 封装具体的硬件收发方法。同时,i2c_client 代表挂接在总线上的每一个从设备。理解这三个数据结构之间的协作关系,是掌握 I2C 驱动框架的关键。

核心关系速览: i2c_adapter 是“适配器”(控制器),它内部持有 i2c_algorithm(算法)来执行实际的 SCL/SDA 时序;i2c_client 是“客户端”(从设备),它绑定到某个 adapter 上,并通过 adapter 的 algorithm 与硬件通信。

4.1 i2c_adapter:I2C 控制器抽象

i2c_adapter 是 I2C 核心层对物理 I2C 控制器的软件抽象。每个 I2C 总线控制器(如 SoC 内部的 I2C 外设、GPIO 模拟的 I2C 总线)在内核中都对应一个 i2c_adapter 实例。它负责管理总线编号、设备链表、锁机制以及底层的通信算法。

成员 类型 描述
nr int 总线编号,用于在系统中唯一标识该 I2C 总线
name char[48] 适配器名称,通常与硬件平台设备名对应
algo struct i2c_algorithm * 指向该适配器使用的通信算法结构体
algo_data void * 算法私有数据,通常指向控制器寄存器基址或平台数据
dev struct device 内核设备模型中的 device 对象,用于挂接到驱动模型
userspace_clients struct list_head 用户空间创建的 client 链表头
bus_lock struct mutex 总线访问互斥锁,防止并发访问
timeout int 通信超时时间(单位 jiffies)
retries int 通信失败时的重试次数
编程要点: 在注册 adapter 时,核心层会自动分配总线编号(nr),开发者也可以指定静态编号。adapter 注册成功后,系统会在 /sys/bus/i2c/devices/ 下创建对应的目录,例如 i2c-0、i2c-1。

4.2 i2c_algorithm:硬件通信方法集

i2c_algorithm 是 I2C 核心层与底层硬件之间的“契约”。它定义了一组函数指针,用于实现 I2C 总线的标准通信协议。不同的硬件平台(如 IMX、STM32、GPIO 模拟)通过实现这些回调函数来适配 I2C 核心层。

struct i2c_algorithm {
    /* 主模式发送/接收:发送一个 i2c_msg 数组 */
    int (*master_xfer)(struct i2c_adapter *adap,
                       struct i2c_msg *msgs,
                       int num);
    /* 从模式发送/接收(较少使用) */
    int (*slave_xfer)(struct i2c_adapter *adap,
                      struct i2c_msg *msgs,
                      int num);
    /* 查询适配器功能(如是否支持 10 位地址、SMBus 等) */
    u32 (*functionality)(struct i2c_adapter *adap);
    /* 注册从设备回调(用于从模式) */
    int (*reg_slave)(struct i2c_adapter *adap,
                     struct i2c_client *client);
    int (*unreg_slave)(struct i2c_adapter *adap,
                       struct i2c_client *client);
};
回调函数 作用 必须实现?
master_xfer 核心函数,执行一次主设备传输(包含起始、数据、停止条件) 是
functionality 返回适配器支持的能力掩码,如 I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL 是
slave_xfer 从模式传输,用于适配器作为从设备时 否
reg_slave / unreg_slave 注册/注销从设备,用于支持从模式的适配器 否
注意: master_xfer 是 I2C 适配器驱动必须实现的核心回调。该函数接收一个 i2c_msg 数组,每个 msg 包含从设备地址、读写标志、数据缓冲区及长度。驱动需要按照 I2C 协议在硬件上产生对应的 SCL/SDA 时序。

4.3 i2c_client:I2C 从设备抽象

i2c_client 代表一个挂接在 I2C 总线上的从设备(如温度传感器、加速度计、EEPROM)。每个 client 都绑定到一个特定的 i2c_adapter 和一个 7 位或 10 位从设备地址。设备驱动通过 client 结构体与硬件进行数据交换。

struct i2c_client {
    unsigned short addr;        /* 7 位从设备地址(低 7 位有效) */
    unsigned short flags;       /* 标志位:I2C_CLIENT_TEN(10 位地址)、
                                   I2C_CLIENT_SLAVE(从模式)等 */
    char name[I2C_NAME_SIZE];  /* 设备名称,用于匹配驱动 */
    struct i2c_adapter *adapter; /* 指向所属的适配器 */
    struct device dev;          /* 内核设备模型 device 对象 */
    int irq;                    /* 从设备的中断号(可选) */
    struct list_head detected;  /* 用于挂接到 adapter 的 detected 链表 */
};
成员 说明
addr 从设备地址,7 位地址存储在低 7 位,10 位地址需配合 I2C_CLIENT_TEN 标志
flags 控制地址模式、读写方向、从模式等属性
adapter 反向指针,指向该设备所连接的 I2C 总线适配器
dev 嵌入的 device 结构,用于驱动模型匹配(vendor/device ID)
irq 如果从设备支持中断,则在此记录中断号
关键设计: client 的 adapter 指针将设备驱动与控制器驱动连接起来。当设备驱动调用 i2c_transfer() 时,核心层通过 client->adapter->algo->master_xfer() 最终调用到具体的硬件发送函数。

4.4 三大数据结构协作关系图

下面的 SVG 图展示了 i2c_adapter、i2c_algorithm 和 i2c_client 之间的静态组合关系与动态调用路径。

I2C 核心层三大数据结构协作关系 I2C 核心层:adapter / algorithm / client 协作关系 i2c_adapter nr: 总线编号 name: "I2C-0" algo: 指向 algorithm dev: 内核 device bus_lock: 互斥锁 i2c_algorithm master_xfer() slave_xfer() functionality() reg_slave() unreg_slave() i2c_client addr: 0x48 (从设备地址) adapter: 指向所属 adapter dev: 内核 device irq: 中断号 algo 指针 adapter 指针 调用路径 i2c_transfer(client, msg, num) → client->adapter->algo ->master_xfer()

4.5 数据结构初始化与注册流程

在实际的 I2C 控制器驱动中,这三个数据结构的初始化与注册遵循固定的步骤。下面以平台驱动(platform driver)为例,展示典型的初始化流程:

  1. 分配并初始化 i2c_adapter: 设置 nr、name、algo、algo_data、timeout、retries 等字段。
  2. 实现 i2c_algorithm: 编写 master_xfer() 和 functionality() 回调函数,操作硬件寄存器完成时序。
  3. 注册 adapter: 调用 i2c_add_adapter() 或 i2c_add_numbered_adapter() 将 adapter 注册到核心层。
  4. 创建设备节点: 核心层根据设备树(Device Tree)或板级代码中的 I2C 设备信息,自动创建 i2c_client 实例。
  5. 驱动匹配: 当 client 创建后,核心层遍历已注册的 I2C 设备驱动,通过 i2c_device_id 或设备树 compatible 属性进行匹配。
/* 典型的 I2C 适配器驱动 probe 函数片段 */
static int my_i2c_probe(struct platform_device *pdev)
{
    struct i2c_adapter *adap;
    struct i2c_algorithm *algo;

    adap = devm_kzalloc(&pdev->dev, sizeof(*adap), GFP_KERNEL);
    algo = devm_kzalloc(&pdev->dev, sizeof(*algo), GFP_KERNEL);

    adap->nr = pdev->id;              /* 使用平台设备 ID 作为总线编号 */
    adap->name = "my_i2c_adapter";
    adap->algo = algo;
    adap->algo_data = platform_get_drvdata(pdev);
    adap->timeout = HZ;               /* 1 秒超时 */
    adap->retries = 3;

    algo->master_xfer = my_i2c_master_xfer;   /* 硬件发送函数 */
    algo->functionality = my_i2c_functionality;

    return i2c_add_numbered_adapter(adap);    /* 注册到核心层 */
}
调试技巧: 注册成功后,可以在 /sys/bus/i2c/devices/ 下看到 i2c-N 目录。通过 i2cdetect -l 命令可以列出所有已注册的 I2C 适配器及其编号。

4.6 数据结构对比总结

数据结构 角色 核心成员 生命周期
i2c_adapter I2C 总线控制器抽象 nr, algo, algo_data, dev, bus_lock 与硬件控制器共存亡
i2c_algorithm 硬件通信方法集 master_xfer, functionality 通常与 adapter 同时创建
i2c_client I2C 从设备抽象 addr, adapter, dev, irq 设备探测时创建,移除时销毁
常见错误: 在实现 master_xfer() 时,必须正确处理 i2c_msg 数组中的 flags 字段(尤其是 I2C_M_RD 读标志)。如果忽略该标志,会导致读写方向错误,造成设备无响应或数据错乱。