本文结合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_device3.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 17.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 i2c7.3 直接硬件访问
            
            
              bash
              
              
            
          
          # 读取设备寄存器
i2cget -y 1 0x58 0x8b w  # 读取输出功率
# 写入设备寄存器  
i2cset -y 1 0x58 0x8b 0x1234 w
# 转储设备数据
i2cdump -y 1 0x588. 性能优化技巧
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/bind9.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.rules10. 总结
Linux I2C子系统提供了一个完整、灵活的框架来管理I2C设备:
- 分层架构:清晰的核心层、适配器层、设备驱动层分离
- 多种匹配机制:设备树、ID表、用户空间动态创建
- 统一的Sysfs接口:为用户空间提供标准化的设备访问
- 灵活的驱动模型:支持热插拔、动态加载等高级特性
- 完善的调试支持:丰富的调试工具和接口
通过深入理解I2C子系统的工作原理,开发者可以更高效地编写和维护I2C设备驱动,充分利用Linux设备模型的强大功能。
本文基于Linux内核5.x版本,具体实现可能因内核版本而异。建议结合内核源码和实际硬件平台进行开发验证。