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 被触发的完整流程:
匹配优先级: 在设备树使能的内核中,
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_add_driver是宏而非函数,实际调用i2c_register_driver(THIS_MODULE, driver)。- 设备树匹配时,
of_match_table必须以空结构体作为哨兵结束。 - 在
probe函数中获取设备树属性应使用device_property_read_*系列API。 - 中断号通过
i2c_client->irq获取,该值由内核在设备匹配时从设备树解析并填充。