4. Linux内核I2C子系统架构:三层架构模型
Linux内核的I2C子系统采用经典的三层架构设计,将硬件控制、总线管理和设备操作解耦。理解这一架构是开发稳定、可移植的I2C设备驱动的基石。本节将详细剖析I2C核心层、总线驱动层和设备驱动层的职责、交互关系与核心数据结构。
4.1 三层架构总览
I2C子系统从上到下依次为:I2C设备驱动层、I2C核心层和I2C总线驱动层。设备驱动层面向具体的I2C从设备(如传感器、EEPROM、ADC等),总线驱动层面向具体的I2C控制器硬件(如S3C2440、OMAP、i.MX等平台的I2C适配器),核心层则提供统一的注册/注销接口、数据传输模板和地址解析机制。
i2c_transfer())发起通信;总线驱动负责将核心层的抽象请求转化为具体的硬件时序。这种分层使得设备驱动可以跨平台复用。
4.2 各层核心数据结构与职责
三层架构由几个关键内核数据结构串联。下表总结了每个层次的核心结构及其主要字段:
| 层次 | 核心结构 | 关键字段 / 方法 | 职责描述 |
|---|---|---|---|
| 设备驱动层 | struct i2c_driver |
probe(), remove(), id_table, driver |
绑定到特定从设备地址;初始化设备、创建输入/文件系统接口 |
| 设备驱动层 | struct i2c_client |
addr, adapter, name, irq |
代表一个物理或逻辑I2C从设备,由核心层在匹配时创建 |
| 核心层 | i2c-core.c |
i2c_add_driver(), i2c_transfer(), i2c_smbus_*() |
驱动/适配器注册、总线匹配、数据传输调度、SMBus协议仿真 |
| 总线驱动层 | struct i2c_adapter |
nr, name, algo, algo_data |
代表一个I2C控制器实例,提供传输通道 |
| 总线驱动层 | struct i2c_algorithm |
master_xfer(), smbus_xfer(), functionality() |
实现底层硬件时序:起始/停止条件、字节收发、ACK/NACK检测 |
4.3 设备驱动层详解
设备驱动层是驱动开发者最常接触的层次。一个典型的I2C设备驱动需要完成以下步骤:
- 定义
i2c_driver结构体:填充probe、remove回调以及id_table(设备ID匹配表)。 - 注册驱动:调用
i2c_add_driver()将驱动注册到核心层。 - 在
probe()中初始化设备:获取i2c_client指针,分配私有数据结构,注册中断(如有),创建字符设备或输入设备。 - 实现数据传输:通过
i2c_transfer()或i2c_smbus_read_byte_data()等API与从设备交换数据。
probe() 中不要进行耗时操作(如长延时轮询),应使用工作队列或定时器。中断服务程序应尽量短小,复杂数据处理放到下半部(如 threaded IRQ)。
以下是一个简化的设备驱动注册框架:
#include <linux/i2c.h>
#include <linux/module.h>
static int my_sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
// 1. 检查设备功能
// 2. 分配私有数据
// 3. 初始化硬件(如设置采样率)
// 4. 注册中断 / 创建设备节点
return 0;
}
static int my_sensor_remove(struct i2c_client *client)
{
// 释放资源、注销设备
return 0;
}
static const struct i2c_device_id my_sensor_id[] = {
{ "my-sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_sensor_id);
static struct i2c_driver my_sensor_driver = {
.driver = {
.name = "my_sensor",
},
.probe = my_sensor_probe,
.remove = my_sensor_remove,
.id_table = my_sensor_id,
};
module_i2c_driver(my_sensor_driver);
4.4 总线驱动层详解
总线驱动层负责与具体的I2C控制器硬件交互。每个I2C适配器(即一个I2C总线控制器)对应一个 struct i2c_adapter,其核心是 struct i2c_algorithm 中的 master_xfer() 函数。
master_xfer() 接收一个 struct i2c_msg 数组,每个消息包含:
- addr:从设备7位或10位地址。
- flags:读写标志(
I2C_M_RD)及其他控制位。 - len:数据长度。
- buf:数据缓冲区指针。
总线驱动的典型实现流程:
- 解析
i2c_msg数组,生成对应的硬件时序(START + 地址 + R/W + 数据 + STOP)。 - 操作控制器的寄存器:设置时钟分频、发送起始条件、移位数据、检测ACK、产生停止条件。
- 处理错误(如无ACK、仲裁丢失、超时),返回实际传输的消息数或负错误码。
4.5 核心层:粘合剂与仲裁者
I2C核心层(drivers/i2c/i2c-core.c)是三层架构的枢纽,主要提供以下功能:
| 功能模块 | 说明 |
|---|---|
| 驱动/适配器注册 | i2c_add_driver() / i2c_add_adapter(),将驱动和适配器加入内核链表 |
| 总线匹配 | 遍历适配器上的设备列表,通过 id_table 或设备树 compatible 属性匹配驱动 |
| 数据传输调度 | i2c_transfer() 调用适配器的 master_xfer(),处理重试和错误 |
| SMBus 兼容层 | 将 SMBus 协议(如 i2c_smbus_read_word_data())转换为标准的 I2C 消息 |
| 设备模型集成 | 将 i2c_client 和 i2c_driver 挂接到 Linux 设备模型(sysfs、uevent) |
4.6 数据流与消息传递
当设备驱动需要读取传感器数据时,数据流经过以下路径:
- 设备驱动 调用
i2c_transfer(client->adapter, msg, num)。 - 核心层 验证参数,调用适配器的
algo->master_xfer()。 - 总线驱动 将
i2c_msg转换为硬件时序,通过控制器发送到I2C总线。 - 从设备响应数据,总线驱动将数据写回
msg->buf。 - 返回值逐层返回,设备驱动获得实际传输的消息数或错误码。
i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 是核心层最重要的导出函数。它支持一次传输多个消息(组合事务),中间由重复起始条件(Repeated START)连接,无需STOP信号。
4.7 设备树与平台适配
在现代ARM/嵌入式Linux中,I2C设备信息通常通过设备树(Device Tree)描述。核心层在启动时解析设备树,为每个 i2c@... 节点创建 i2c_client,并与匹配的 i2c_driver 绑定。
设备树中I2C设备节点的典型结构:
&i2c1 {
status = "okay";
clock-frequency = <100000>;
temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <16 IRQ_TYPE_EDGE_FALLING>;
};
};
驱动端通过 of_match_table 匹配 compatible 属性:
static const struct of_device_id my_sensor_of_match[] = {
{ .compatible = "ti,tmp102" },
{ }
};
MODULE_DEVICE_TABLE(of, my_sensor_of_match);
static struct i2c_driver my_sensor_driver = {
.driver = {
.name = "tmp102",
.of_match_table = my_sensor_of_match,
},
// ...
};
4.8 本章小结
- 三层架构 将I2C子系统分为设备驱动层、核心层和总线驱动层,实现了硬件无关性与代码复用。
- 设备驱动层 关注具体从设备的逻辑,通过
i2c_driver和i2c_client与核心层交互。 - 核心层 提供注册、匹配、传输调度和SMBus仿真,是系统的中枢。
- 总线驱动层 封装硬件时序,通过
i2c_algorithm的master_xfer()实现物理层通信。 - 设备树 是描述I2C设备的主流方式,驱动应优先使用
of_match_table进行匹配。