一、I2C客户端驱动加载
1、设备树上对应资源解析
以imx6ull官方版资源下,imx6ull的i2c_adapter下挂i2c_client为例:
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
magnetometer@e {
compatible = "fsl,mag3110";
reg = <0x0e>;
vdd-supply = <®_peri_3v3>;
vddio-supply = <®_peri_3v3>;
};
};
- 这是一个典型的 i2c 设备节点,位于i2c1总线下。
- 设备是 Freescale 的 MAG3110 三轴磁力计,用于测量磁场强度。
- 设备地址 0x0E 是 I2C 的 7 位地址(实际通信时左移一位为 0x1C)
2、驱动加载流程解析
module_i2c_driver(mag3110_driver);
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
因此,该段会被预处理为:
(1)mag3110_driver_init
static int __init mag3110_driver_init(void)
{
return i2c_add_driver(&mag3110_driver);
}
module_init(mag3110_driver_init);
static void __exit mag3110_driver_exit(void)
{
i2c_del_driver(&mag3110_driver);
}
module_exit(mag3110_driver_exit);
这里,mag3110_driver 结构体:
static struct i2c_driver mag3110_driver = {
.driver = {
.name = "mag3110",
.of_match_table = mag3110_of_match,
.pm = MAG3110_PM_OPS,
},
.probe = mag3110_probe,
.remove = mag3110_remove,
.id_table = mag3110_id,
};
(2)i2c_add_driver
i2c_add_driver展开:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
/linux-5.14.10/drivers/i2c/i2c-core-base.c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* 检查 I2C 总线是否已初始化(防止在驱动模型未就绪时注册) */
if (WARN_ON(!is_registered))
return -EAGAIN; // 若未初始化,返回 -EAGAIN(稍后重试)
/* 初始化驱动的基础属性 */
driver->driver.owner = owner; // 设置驱动所属模块
driver->driver.bus = &i2c_bus_type; // 绑定到 I2C 总线类型
INIT_LIST_HEAD(&driver->clients); // 初始化客户端设备链表(用于管理匹配的设备)
/**
* 将驱动注册到驱动核心(driver core):
* 1. 将驱动添加到全局驱动列表。
* 2. 对所有已存在且匹配的设备调用 probe() 方法。
*/
res = driver_register(&driver->driver);
...
/**
* 遍历所有已注册的 I2C 适配器(adapter),检查是否有设备与该驱动匹配:
* - 对每个适配器调用 __process_new_driver,尝试绑定匹配的设备。
* - 若找到匹配设备,会触发驱动的 probe() 方法。
*/
i2c_for_each_dev(driver, __process_new_driver);
return 0; // 注册成功
}
(1)
driver_register
将驱动注册到 Linux 设备模型,关联到i2c_bus_type
总线。注册后,驱动核心会自动调用probe()
匹配所有未绑定的设备。(2)
i2c_for_each_dev
遍历现有 I2C 适配器,立即处理可能匹配的设备(而非等待热插拔事件)。__process_new_driver
是内部函数,用于检查设备与驱动的匹配性(通过id_table
或兼容性)。
(3)driver_register
/**
* driver_register - 向总线注册一个设备驱动
* 主要工作委托给 bus_add_driver(),因为大部分操作与总线结构相关。
*/
int driver_register(struct device_driver *drv)
{
...
/* 将驱动添加到总线(核心步骤) */
ret = bus_add_driver(drv);
...
/* 为驱动添加属性组(可选) */
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv); // 回滚:从总线移除驱动
return ret;
}
..
}
int bus_add_driver(struct device_driver *drv)
{
...
/* 初始化驱动私有数据 */
klist_init(&priv->klist_devices, NULL, NULL); // 初始化设备链表
priv->driver = drv; // 关联驱动
...
/* 在 sysfs 中创建驱动对应的 kobject, /sys/bus/i2c-x/drivers/ */
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name);
...
/* 将驱动添加到总线的驱动链表 */
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
/* 如果总线支持自动探测,尝试绑定匹配的设备 */
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister; // 绑定失败跳转到清理
}
/* 增加模块引用计数(防止驱动模块被意外卸载) */
module_add_driver(drv->owner, drv);
/* 在 sysfs 中创建 uevent 属性文件 */
error = driver_create_file(drv, &driver_attr_uevent);
if (error)
pr_err("%s: uevent attr (%s) failed\n", __func__, drv->name);
/* 添加总线指定的默认属性组 */
error = driver_add_groups(drv, bus->drv_groups);
if (error)
pr_err("%s: driver_add_groups(%s) failed\n", __func__, drv->name);
...
}
...
}
(1)分配
driver_private
结构体,保存设备链表(klist_devices
)和驱动状态,通过drv->p
建立驱动与私有数据的双向关联(2)通过
drv->p
建立驱动与私有数据的双向关联(3)
driver_attach
扫描所在总线上的设备,匹配后调用驱动的probe
方法,受drivers_autoprobe
控制(通常为true
)(4)
module_add_driver
增加所属模块的引用,确保驱动在使用时模块不会被卸载(5)用户空间交互,
driver_attr_uevent
允许用户空间触发驱动事件
(4)driver_attach
/linux-5.14.10/drivers/base/dd.c
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
/linux-5.14.10/drivers/base/bus.c
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
...
/* 初始化迭代器:从总线设备链表 (bus->p->klist_devices) 开始 */
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
/* 遍历设备链表,直到出错或遍历完成 */
while (!error && (dev = next_device(&i)))
error = fn(dev, data); // 对每个设备调用回调函数
/* 释放迭代器资源 */
klist_iter_exit(&i);
..
}
/linux-5.14.10/drivers/base/dd.c
/* __driver_attach - 尝试将驱动绑定到设备 */
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data; // 获取传入的驱动
int ret;
/* 1. 检查驱动与设备是否匹配 */
ret = driver_match_device(drv, dev);
/* ret > 0 表示匹配成功,继续执行, 否则根据ret进入相应的异常处理 */
...
/* 2. 若驱动支持,处理异步探测(异步探测可提高系统启动并行度)*/
if (driver_allows_async_probing(drv)) {
dev_dbg(dev, "probing driver %s asynchronously\n", drv->name);
device_lock(dev); // 保护设备状态
if (!dev->driver) {
get_device(dev); // 增加设备引用计数
dev->p->async_driver = drv; // 标记异步探测的驱动
async_schedule_dev(__driver_attach_async_helper, dev); // 调度异步探测
}
device_unlock(dev);
return 0;
}
/* 3. 同步探测(默认路径)*/
__device_driver_lock(dev, dev->parent); // 防止竞态条件
driver_probe_device(drv, dev); // 实际探测(可能调用驱动的 probe())
__device_driver_unlock(dev, dev->parent);
..
}
/linux-5.14.10/drivers/base/dd.c
static void __driver_attach_async_helper(void *_dev, async_cookie_t cookie)
{
...
__device_driver_lock(dev, dev->parent);
drv = dev->p->async_driver;
ret = driver_probe_device(drv, dev);
__device_driver_unlock(dev, dev->parent);
..
put_device(dev);
}
(1)
driver_match_device
调用总线的匹配方法(如设备树兼容性、ACPI ID 或id_table
),进行设备-驱动匹配。(2)若驱动声明支持异步探测(probe),通过
async_schedule_dev
在后台线程中执行探测。优点:避免阻塞其他设备的初始化,加快启动速度。(3)不支持异步时,进行同步探测,通过
driver_probe_device
直接调用驱动的probe
方法。使用__device_driver_lock
确保父子设备探测的原子性。
(5)driver_probe_device
__driver_probe_device
--> __driver_probe_device
--> really_probe(dev, drv)
--> call_driver_probe(dev, drv)
static int call_driver_probe(struct device *dev, struct device_driver *drv)
{
...
/* 优先调用总线级别的probe函数 */
if (dev->bus->probe)
ret = dev->bus->probe(dev);
/* 如果没有总线probe,则调用驱动自己的probe函数 */
else if (drv->probe)
ret = drv->probe(dev);
...
}
即扫描所在总线上的设备,匹配后调用驱动的 probe
方法时,优先调用总线级别的probe函数(dev->bus->probe),如果没有总线probe,则调用驱动自己的probe函数(drv->probe)
在 Linux学习笔记协议篇(二):I2C内核子系统分析(上)中,i2c_adapter(i2c-imx)注册后期,会调用i2c_new_client_device,给设备树下的每个节点创建成一个client(mag3110)。同时,会填充其device结构体:
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = of_node_get(info->of_node);
client->dev.fwnode = info->fwnode;
因此,这里dev->bus->probe为i2c_device_probe是存在的,优先调用i2c_device_probe:
static int i2c_device_probe(struct device *dev)
{
...
/*
* 调用驱动实际的probe函数
* 优先使用probe_new(新版接口),其次是传统probe
*/
if (driver->probe_new)
status = driver->probe_new(client);
else if (driver->probe)
status = driver->probe(client,
i2c_match_id(driver->id_table, client));
else
...
}
i2c_device_probe里仍然会调用驱动实际的probe函数:即指向.probe = mag3110_probe,该函数会通过一系列I2C操作如下等,将该mag3110设备硬件初始化完成
i2c_smbus_xfer
这一系列操作完成后可以做什么事情呢?根据/linux-5.14.10/drivers/iio/magnetometer/mag3110.c
/linux-5.14.10/drivers/iio/magnetometer/mag3110.c
static const struct iio_info mag3110_info = {
.attrs = &mag3110_group,
.read_raw = &mag3110_read_raw,
.write_raw = &mag3110_write_raw,
};
用户可以通过通过 sysfs
(attrs
)来配置设备和读取传感器数据。当用户操作 /sys/bus/iio/devices/iio:deviceX/
下的文件时,内核会自动调用对应的函数(如 mag3110_read_raw
),下面看一下mag3110_read_raw函数的调用链做了那些事情:
--> mag3110_read
--> i2c_smbus_read_i2c_block_data
--> i2c_smbus_xfer //从传感器读取 3 轴磁场数据(X/Y/Z)
-->i2c_smbus_read_byte_data
--> i2c_smbus_xfer // 读取温度寄存器(8 位数据)
--> i2c_smbus_xfer
--> __i2c_smbus_xfer
--> adapter->algo->smbus_xfer
static const struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
...
};
因此,可以看到均调用 i2c_adaper 中的绑定的通信方法 i2c_imx_xfer,控制适配器(imx主机)和客户机(mag3110从机)实现I2C通信,传输数据以实现功能
参考文档:
Linux内核的I2C驱动框架详解_linux i2c驱动架构-CSDN博客
Linux下的I2C驱动框架以及代码实现_linux i2c架构-CSDN博客
【驱动】linux下I2C驱动架构全面分析 - Leo.cheng - 博客园