Linux I2C子系统全面详解:从理论到实战

本文结合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 = &reg;
    
    // 读数据
    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设备:

  1. 分层架构:清晰的核心层、适配器层、设备驱动层分离
  2. 多种匹配机制:设备树、ID表、用户空间动态创建
  3. 统一的Sysfs接口:为用户空间提供标准化的设备访问
  4. 灵活的驱动模型:支持热插拔、动态加载等高级特性
  5. 完善的调试支持:丰富的调试工具和接口

通过深入理解I2C子系统的工作原理,开发者可以更高效地编写和维护I2C设备驱动,充分利用Linux设备模型的强大功能。

本文基于Linux内核5.x版本,具体实现可能因内核版本而异。建议结合内核源码和实际硬件平台进行开发验证。

参考链接

相关推荐
A小辣椒5 分钟前
TShark:基础知识
linux
AlfredZhao2 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao17 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux