Linux学习笔记协议篇(三):I2C内核子系统分析(下)

一、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 = <&reg_peri_3v3>;
		vddio-supply = <&reg_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,
};

用户可以通过通过 sysfsattrs)来配置设备和读取传感器数据。当用户操作 /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 - 博客园

linux 设备树解析及probe调用流程 - 月的光景 - 博客园

Linux驱动开发(十八):I2C驱动_你好,关于i2c驱动,目前有三个问题想请教下-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值