本文结合Linux内核源码,深度解析I2C子系统架构、驱动模型和实际开发案例
1. I2C子系统架构概览
Linux I2C子系统采用典型的分层架构,分为核心层、总线驱动层和设备驱动层:
应用层
↓
Sysfs接口 (/sys/class/hwmon/hwmon*/)
↓
I2C设备驱动 (crpspmbus, lm75, etc.)
↓
I2C核心层 (i2c-core)
↓
I2C适配器驱动 (i2c-adapter)
↓
硬件I2C控制器

2. I2C核心子系统详解
2.1 核心数据结构
I2C适配器 (Adapter)
c
struct i2c_adapter {
struct module *owner;
const struct i2c_algorithm *algo; // 通信算法
struct device dev; // 对应的设备
int nr; // 适配器编号
char name[48]; // 适配器名称
// ...
};
I2C客户端 (Client)
c
struct i2c_client {
unsigned short flags; // 设备标志
unsigned short addr; // I2C设备地址
char name[I2C_NAME_SIZE]; // 设备名称
struct i2c_adapter *adapter; // 所属适配器
struct device dev; // 设备结构
int irq; // 中断号
// ...
};
I2C驱动 (Driver)
c
struct i2c_driver {
int (*probe)(struct i2c_client *); // 设备探测
int (*remove)(struct i2c_client *); // 设备移除
struct device_driver driver; // 设备驱动
const struct i2c_device_id *id_table; // 设备ID表
const struct of_device_id *of_match_table; // 设备树匹配表
};
2.2 核心层关键函数
c
// 注册I2C适配器
int i2c_add_adapter(struct i2c_adapter *adapter);
// 注册I2C驱动
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
// 创建设备
struct i2c_client *i2c_new_device(struct i2c_adapter *adap,
struct i2c_board_info const *info);
// 数据传输
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
3. 设备匹配机制深度解析
3.1 三种匹配方式
方式1:设备树匹配(现代标准)
c
static const struct of_device_id crpspmbus_of_match[] = {
{ .compatible = "pmbus,crpspmbus" }, // 设备树兼容性字符串
{}
};
MODULE_DEVICE_TABLE(of, crpspmbus_of_match);
设备树配置:
dts
i2c1: i2c@400a0000 {
compatible = "vendor,i2c-controller";
power-supply@58 {
compatible = "pmbus,crpspmbus"; // 匹配驱动
reg = <0x58>; // I2C地址
};
};
方式2:ID表匹配(传统方式)
c
static const struct i2c_device_id crpspmbus_id[] = {
{ "crpspmbus", 0 }, // 设备名称匹配
{}
};
MODULE_DEVICE_TABLE(i2c, crpspmbus_id);
方式3:用户空间动态创建
bash
# 动态创建设备节点
echo "crpspmbus 0x58" > /sys/bus/i2c/devices/i2c-1/new_device
# 删除设备
echo "0x58" > /sys/bus/i2c/devices/i2c-1/delete_device
3.2 匹配流程源码分析
c
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client) return 0;
driver = to_i2c_driver(drv);
/* 优先级1: 设备树匹配 */
if (of_driver_match_device(dev, drv))
return 1;
/* 优先级2: ACPI匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;
/* 优先级3: ID表匹配 */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
4. 用户空间设备创建机制
4.1 Sysfs接口实现
c
// drivers/i2c/i2c-core-base.c
static ssize_t new_device_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_adapter *adap = to_i2c_adapter(dev);
struct i2c_board_info info;
char name[I2C_NAME_SIZE];
int res;
// 解析用户输入 "driver_name address"
res = sscanf(buf, "%19s %hi", name, &info.addr);
if (res < 2) return -EINVAL;
// 设置设备信息
strscpy(info.type, name, sizeof(info.type));
// 创建设备实例
struct i2c_client *client = i2c_new_client_device(adap, &info);
if (IS_ERR(client))
return PTR_ERR(client);
return count;
}
4.2 设备实例创建流程
用户空间: echo "crpspmbus 0x58" > /sys/bus/i2c/.../new_device
↓
内核: new_device_store() 解析输入
↓
i2c_new_client_device() 创建设备
↓
device_register() 注册到设备模型
↓
bus_probe_device() 触发驱动匹配
↓
driver_match_device() 执行匹配
↓
driver_probe_device() 调用驱动探测
↓
crpspmbus_probe() 设备初始化
5. 实战案例:crpspmbus驱动分析
5.1 驱动注册
c
static struct i2c_driver crpspmbus_driver = {
.driver = {
.name = "crpspmbus",
.of_match_table = of_match_ptr(crpspmbus_of_match),
},
.probe = crpspmbus_probe,
.remove = crpspmbus_remove,
.id_table = crpspmbus_id,
};
module_i2c_driver(crpspmbus_driver);
5.2 设备探测
c
static int crpspmbus_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
dev_info(dev, "Probing crpspmbus at address 0x%02x\n", client->addr);
// 初始化私有数据结构
// 设置平台数据
client->dev.platform_data = &crpspmbus_pdata;
// 调用PMBus核心探测
return pmbus_do_probe(client, &crpspmbus_info);
}
5.3 Sysfs属性创建
c
// 属性定义宏
SENSOR_DEVICE_ATTR_RO(power_out, crpspmbus_powerOut, power_out);
SENSOR_DEVICE_ATTR_RW(serial_number, crpspmbus_string, serial_number);
// 展开为完整结构体
static struct sensor_device_attribute sensor_dev_attr_power_out = {
.dev_attr = __ATTR(power_out, 0444, crpspmbus_powerOut_show, NULL),
.index = power_out
};
// 属性分组
static struct attribute *crpspmbus_attrs[] = {
&sensor_dev_attr_vendor.dev_attr.attr,
&sensor_dev_attr_model.dev_attr.attr,
&sensor_dev_attr_power_out.dev_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(crpspmbus);
6. I2C数据传输机制
6.1 通信算法接口
c
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);
u32 (*functionality)(struct i2c_adapter *adap);
};
6.2 消息传输结构
c
struct i2c_msg {
__u16 addr; // 设备地址
__u16 flags; // 标志位
#define I2C_M_RD 0x0001 // 读操作
__u16 len; // 数据长度
__u8 *buf; // 数据缓冲区
};
6.3 数据传输示例
c
// 读取传感器数据
static int read_sensor_data(struct i2c_client *client, u8 reg, u16 *value)
{
struct i2c_msg msgs[2];
u8 buf[2];
int ret;
// 写寄存器地址
msgs[0].addr = client->addr;
msgs[0].flags = 0; // 写操作
msgs[0].len = 1;
msgs[0].buf = ®
// 读数据
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; // 读操作
msgs[1].len = 2;
msgs[1].buf = buf;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret == 2) {
*value = (buf[0] << 8) | buf[1];
return 0;
}
return ret;
}
7. 调试和故障排查
7.1 系统状态检查
bash
# 查看I2C适配器
cat /sys/class/i2c-adapter/*/name
# 查看I2C设备
ls /sys/bus/i2c/devices/
# 查看驱动绑定
ls /sys/bus/i2c/drivers/
# 扫描I2C设备
i2cdetect -y 1
7.2 内核调试
bash
# 启用I2C调试
echo 1 > /sys/module/i2c_core/parameters/debug
# 动态调试
echo 'file i2c-core-base.c +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file crpspmbus.c +p' > /sys/kernel/debug/dynamic_debug/control
# 查看内核消息
dmesg | grep -i i2c
7.3 直接硬件访问
bash
# 读取设备寄存器
i2cget -y 1 0x58 0x8b w # 读取输出功率
# 写入设备寄存器
i2cset -y 1 0x58 0x8b 0x1234 w
# 转储设备数据
i2cdump -y 1 0x58
8. 性能优化技巧
8.1 数据缓存
c
struct pmbus_data {
struct mutex update_lock;
unsigned long last_updated;
int cache_data;
bool valid;
};
static ssize_t power_out_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
struct pmbus_data *data = dev_get_drvdata(dev);
mutex_lock(&data->update_lock);
// 缓存过期时更新
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
data->cache_data = read_hardware_data();
data->last_updated = jiffies;
data->valid = true;
}
mutex_unlock(&data->update_lock);
return sysfs_emit(buf, "%d\n", data->cache_data);
}
8.2 批量读取优化
c
// 一次性读取多个寄存器
static int read_multiple_registers(struct i2c_client *client,
u8 start_reg, u8 *buf, int count)
{
struct i2c_msg msgs[2];
// 写起始寄存器
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &start_reg;
// 读多个数据
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = count;
msgs[1].buf = buf;
return i2c_transfer(client->adapter, msgs, 2);
}
9. 常见问题解决方案
9.1 设备匹配失败
问题 :设备已创建但未绑定驱动
解决:
bash
# 检查设备名称
cat /sys/bus/i2c/devices/1-0058/name
# 手动绑定驱动
echo "1-0058" > /sys/bus/i2c/drivers/crpspmbus/bind
9.2 数据传输错误
问题 :I2C传输返回错误
解决:
c
// 添加重试机制
int retries = 3;
while (retries--) {
ret = i2c_transfer(client->adapter, msgs, num);
if (ret == num) break;
msleep(10);
}
9.3 权限问题
问题 :用户空间无法访问设备
解决:
bash
# 设置正确的权限
chmod 666 /sys/class/hwmon/hwmon23/power1_input
# 或通过udev规则
echo 'SUBSYSTEM=="hwmon", MODE="0666"' > /etc/udev/rules.d/99-hwmon.rules
10. 总结
Linux I2C子系统提供了一个完整、灵活的框架来管理I2C设备:
- 分层架构:清晰的核心层、适配器层、设备驱动层分离
- 多种匹配机制:设备树、ID表、用户空间动态创建
- 统一的Sysfs接口:为用户空间提供标准化的设备访问
- 灵活的驱动模型:支持热插拔、动态加载等高级特性
- 完善的调试支持:丰富的调试工具和接口
通过深入理解I2C子系统的工作原理,开发者可以更高效地编写和维护I2C设备驱动,充分利用Linux设备模型的强大功能。
本文基于Linux内核5.x版本,具体实现可能因内核版本而异。建议结合内核源码和实际硬件平台进行开发验证。