3. I2C设备驱动模型:struct i2c_driver与核心接口

在Linux内核中,I2C设备驱动采用标准的“总线-设备-驱动”模型。驱动开发者通过填充 struct i2c_driver 结构体,并调用 i2c_add_driver / i2c_del_driver 接口完成驱动的注册与注销。本章将深入剖析该结构体的关键成员、接口调用流程以及内核内部的注册机制。

3.1 struct i2c_driver 结构体深度解析

struct i2c_driver 定义在 include/linux/i2c.h 中,是I2C设备驱动的核心载体。它包含了驱动名称、设备ID表、探测/移除回调、电源管理操作以及中断处理函数等关键信息。

成员 类型 说明
driver struct device_driver 内核通用驱动基类,包含名称、owner、of_match_table等
probe int (*probe)(struct i2c_client *) 设备匹配成功后的探测回调,驱动初始化入口
remove int (*remove)(struct i2c_client *) 设备移除或驱动卸载时的清理回调
id_table const struct i2c_device_id * 传统设备ID匹配表,用于非设备树平台
of_match_table const struct of_device_id * 设备树匹配表,用于设备树平台,优先级高于id_table
shutdown void (*shutdown)(struct i2c_client *) 系统关机时的回调函数
suspend/resume 回调函数指针 系统电源管理回调(已弃用,推荐使用dev_pm_ops)
关键点: of_match_table 是设备树环境下驱动匹配的核心字段。内核通过该表与设备树节点的 compatible 属性进行比对,匹配成功后调用 probe 函数。

3.2 驱动注册与注销接口

内核提供了两个核心API用于管理I2C驱动的生命周期:

  • i2c_add_driver(driver):注册I2C驱动到内核。该宏展开后调用 i2c_register_driver(),完成驱动与I2C总线的绑定,并触发设备匹配。
  • i2c_del_driver(driver):从内核注销I2C驱动。该宏展开后调用 i2c_unregister_driver(),移除驱动并调用所有已绑定设备的 remove 回调。
最佳实践: 建议在模块初始化/退出函数中使用 module_i2c_driver() 宏,它自动封装了 module_init 和 module_exit,简化代码编写。

3.3 驱动注册流程与设备匹配机制

下图展示了从调用 i2c_add_driver 到 probe 被触发的完整流程:

I2C驱动注册与设备匹配流程 i2c_add_driver() i2c_register_driver() 总线匹配设备 of_match_table匹配 id_table匹配 调用probe()回调 驱动就绪
匹配优先级: 在设备树使能的内核中,of_match_table 的匹配优先级高于 id_table。内核首先尝试设备树匹配,若失败则回退到传统ID表匹配。

3.4 完整驱动模板示例

以下是一个典型的I2C设备驱动框架,展示了结构体填充与注册注销的标准写法:

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>

/* 设备树匹配表 */
static const struct of_device_id myi2c_of_match[] = {
    { .compatible = "vendor,my-i2c-device" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myi2c_of_match);

/* 传统ID匹配表 */
static const struct i2c_device_id myi2c_id[] = {
    { "my-i2c-device", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, myi2c_id);

/* probe回调:驱动初始化入口 */
static int myi2c_probe(struct i2c_client *client)
{
    struct device *dev = &client->dev;
    dev_info(dev, "I2C device probed\n");

    /* 在此处进行硬件初始化、中断注册、数据结构分配等 */
    return 0;
}

/* remove回调:驱动清理出口 */
static int myi2c_remove(struct i2c_client *client)
{
    struct device *dev = &client->dev;
    dev_info(dev, "I2C device removed\n");

    /* 在此处释放资源、注销中断等 */
    return 0;
}

/* i2c_driver结构体实例 */
static struct i2c_driver myi2c_driver = {
    .driver = {
        .name           = "my_i2c_driver",
        .of_match_table = myi2c_of_match,
    },
    .probe      = myi2c_probe,
    .remove     = myi2c_remove,
    .id_table   = myi2c_id,
};

/* 使用module_i2c_driver宏简化注册注销 */
module_i2c_driver(myi2c_driver);

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("I2C device driver example");
MODULE_LICENSE("GPL v2");

3.5 关键数据结构关系图

下图展示了 i2c_driver、i2c_client 与内核设备模型之间的关系:

I2C驱动模型核心数据结构关系 struct i2c_driver .driver.name .driver.of_match_table .probe() .remove() struct i2c_client .addr (7位/10位地址) .adapter (所属I2C总线) .dev (内核设备对象) .irq (中断号) 绑定 内核设备模型 (Device Model) i2c_bus_type 负责匹配 i2c_driver 与 i2c_client 匹配成功 → 调用 i2c_driver.probe(i2c_client)
注意事项:
  • i2c_add_driver 是宏而非函数,实际调用 i2c_register_driver(THIS_MODULE, driver)。
  • 设备树匹配时,of_match_table 必须以空结构体作为哨兵结束。
  • 在 probe 函数中获取设备树属性应使用 device_property_read_* 系列API。
  • 中断号通过 i2c_client->irq 获取,该值由内核在设备匹配时从设备树解析并填充。

3.6 本章小结