在这里插入图片描述

[TOC]

Linux i2c-dev 字符设备接口解析

[drivers/i2c/i2c-dev.c] [I2C 用户态访问接口(/dev/i2c-X)] [把 I2C/SMBus 适配器以字符设备形式暴露给用户态,通过 read/write/ioctl 执行传输]


介绍

i2c-dev.c 的核心作用是:为每个 I2C adapter 创建一个字符设备节点 /dev/i2c-N,让用户态可以不写内核驱动也能直接做 I2C/SMBus 访问。
它通常被 i2c-tools(如 i2cdetect/i2cget/i2cset/i2ctransfer)等工具使用,也常用于板级 bring-up、调试、产测脚本、以及临时验证寄存器读写。


历史与背景

诞生为了解决什么问题?

  • 调试与快速验证:很多时候只想验证总线是否通、寄存器是否可读写,不希望立刻写完整内核驱动。
  • 用户态工具生态:把“原始 I2C 事务”能力开放给用户态,便于通用工具工作。

重要迭代特征(从当前设计可推断的演进方向)

  • 从“只支持简单 read/write”逐步扩展到:

    • I2C_RDWR:一次 ioctl 传多个 message(支持 repeated-start 组合事务)
    • I2C_SMBUS:SMBus 语义的 ioctl(byte/word/block 等)
    • compat ioctl:32/64 位兼容处理
  • 适配器热插拔/动态注册:能在 adapter 出现/消失时创建/删除 /dev/i2c-N 节点(例如平台驱动加载/卸载 I2C 控制器时)。

社区活跃度与主流应用

i2c-dev 是 I2C 子系统长期稳定组件:驱动开发者日常调试会用,发行版也普遍启用。但它也一直被强调为“调试/通用访问通道”,而不是取代内核驱动的长期方案。


核心原理与设计

1) “一个 adapter 一个字符设备”

  • 内核里每个 I2C 控制器实例对应一个 i2c_adapter
  • i2c-dev 为每个 adapter 分配一个 minor,并创建 /dev/i2c-N
  • 打开 /dev/i2c-N 相当于拿到“在该 adapter 上发起 I2C 事务”的句柄。

你可以把它理解成:文件描述符绑定 adapter;ioctl 再绑定从设备地址/传输参数

2) 文件私有数据:用“伪 i2c_client”承载目标地址与标志位

典型实现是:open() 时构造一个临时/伪 i2c_client(或同等结构)挂到 file->private_data,里面保存:

  • 指向的 adapter
  • 当前选择的从设备地址(通过 I2C_SLAVE / I2C_SLAVE_FORCE 设置)
  • 10-bit 地址、PEC、重试次数、超时等标志

这样 read/write 就不需要每次都重新传地址:地址变成“该 fd 的状态”

3) 数据面:read/write 对应单消息传输

  • write():用户态给一段 buffer,内核构造一个 i2c_msg(WRITE),调用核心传输函数发出去。
  • read():同理构造 READ msg,从总线上收数据再 copy_to_user。

局限:read/write 通常只能表达“单条 message”。需要“写寄存器地址 + repeated-start 再读”的原子组合事务时,read/write 往往不够,需要 I2C_RDWR

4) 控制面:ioctl 是能力的主体

I2C_SLAVE / I2C_SLAVE_FORCE

  • 设置该 fd 后续访问的 7-bit 从地址。
  • FORCE 的语义是:即便内核里已经有驱动绑定了该地址,也允许你强行抢占访问(这也是它危险的原因之一)。

I2C_RDWR

  • 一次 ioctl 传入一个 messages 数组(每个含 addr/flags/len/buf)。
  • 内核会复制用户态描述,逐条构造/校验 message,并调用底层传输函数一次性跑完。
  • 这是用户态实现“写寄存器地址再读”的标准方式(两个 msg:先写 1~2 字节寄存器地址,再读 N 字节)。

I2C_SMBUS

  • 走 SMBus 的语义层(byte/byte_data/word_data/block_data 等),最终调用 SMBus 传输入口。
  • 对于“控制器原生支持 SMBus 协议”的场景更贴合;不支持时也可能由 I2C core 做一定程度的仿真(取决于适配器能力位)。

I2C_FUNCS

  • 返回 adapter 的能力位集合(用户态可据此判断支持哪些事务类型)。

I2C_TENBIT / I2C_PEC / I2C_RETRIES / I2C_TIMEOUT

  • 分别控制 10-bit 地址、PEC、重试次数、超时等参数(是否真正生效取决于底层 adapter/算法是否支持对应能力)。

5) 并发与锁边界

  • I2C 核心层通常会对同一个 adapter 的传输进行串行化(保证总线事务不交错)。
  • i2c-dev 无法替你做“协议级互斥”:如果用户态和内核驱动同时访问同一从设备地址,哪怕总线事务串行,设备寄存器状态也可能被打乱(例如:驱动在做多步状态机,你插入一次读写就会破坏它的假设)。

使用场景

首选场景

  1. 板级 bring-up / 产测 / 工装
  • 扫描设备地址是否响应、读芯片 ID、做简单寄存器 poke。
  1. 调试内核驱动
  • 用用户态快速验证“寄存器值是否符合预期”,定位是硬件问题、时序问题还是驱动逻辑问题。
  1. 临时脚本化控制
  • 例如在系统早期阶段通过脚本调整 PMIC、mux、GPIO expander 的寄存器(但长期方案仍建议内核驱动化)。

不推荐使用的场景(原因)

  1. 量产系统长期依赖用户态直接访问同一设备
  • 会绕过内核驱动的电源管理、时序约束、错误恢复、互斥保护。
  1. 高频/低延迟数据采集
  • 用户态 ioctl + copy 开销明显,且每次事务都有用户态/内核态切换。
  1. 已经有内核驱动绑定的设备还强行 I2C_SLAVE_FORCE
  • 极易与驱动并发冲突,造成不可预测行为。

对比分析

下面选 3 个最常见“相似方案”对比:

1) i2c-dev vs 内核 I2C 客户端驱动(in-kernel driver)

  • 实现方式

    • i2c-dev:用户态构造事务,内核只负责转发
    • 内核驱动:内核维护设备状态机、缓存、错误恢复、PM
  • 性能开销

    • i2c-dev:频繁 syscall + copy,开销更高
    • 内核驱动:可批处理、可在内核态减少拷贝
  • 资源占用

    • i2c-dev:内核侧结构很轻
    • 内核驱动:代码/状态更多
  • 隔离级别

    • i2c-dev:隔离弱,容易与其他访问者冲突
    • 内核驱动:能建立清晰的“谁拥有设备”的边界
  • 启动速度

    • i2c-dev:adapter 出现即创建设备节点,快
    • 内核驱动:取决于 probe、固件描述与依赖资源

2) i2c-dev 的 read/write vs I2C_RDWR

  • 实现方式

    • read/write:单 msg
    • I2C_RDWR:多 msg,可表达 repeated-start 组合事务
  • 性能开销

    • I2C_RDWR:一次 ioctl 完成一组事务,通常比多次 read/write 更省 syscall
  • 隔离级别

    • I2C_RDWR:更容易保持“组合事务的原子性”(至少在总线层面不会插入别的 msg)

3) i2c-dev 的 I2C_SMBUS vs 直接 I2C_RDWR

  • 实现方式

    • I2C_SMBUS:用 SMBus 协议语义表达,代码更直观(命令/数据)
    • I2C_RDWR:更底层、通用
  • 性能与兼容性

    • 若控制器原生 SMBus 支持好:I2C_SMBUS 更合适
    • 若只是 I2C 控制器:I2C_RDWR 往往更通用、行为更可预测

总结

关键特性

  • 为每个 I2C adapter 提供 /dev/i2c-N 字符设备入口;
  • 通过 ioctl 完成核心能力:设置从地址、组合事务(I2C_RDWR)、SMBus 传输(I2C_SMBUS)、能力查询、超时/重试/PEC 等;
  • 适合调试与工具化访问,但长期产品方案更推荐内核驱动化;
  • 最大风险点是:与内核驱动并发访问同一从设备会破坏设备级协议假设。

i2c_dev_init / i2c_dev_exit / i2c_dev_attach_adapter / i2c_dev_detach_adapter:i2c-dev 字符设备节点的初始化、适配器绑定与注销清理

平台关注:单核、无 MMU、ARMv7-M(STM32H750)

  • 该代码片段的关键设计点是 “同时覆盖已存在的 I2C 适配器 + 未来动态加入/移除的 I2C 适配器”:通过 i2c_for_each_dev() 立即绑定现存适配器,通过 bus_register_notifier() 追踪后续变化。
  • 单核并不消除并发:适配器的添加/移除可能由不同执行上下文触发(例如驱动加载、设备枚举),因此使用 notifier 进行“事件驱动式绑定”仍有必要。
  • 无 MMU 不改变这里的驱动框架语义;但更应避免初始化失败路径资源泄漏,因此这里采用严格的分阶段注册 + goto 统一回滚结构。

i2c_dev_attach_adapter:供 i2c_for_each_dev 遍历时调用的“绑定适配器”回调

1
2
3
4
5
6
static int __init i2c_dev_attach_adapter(struct device *dev, void *dummy)
{
/** @brief 将一个 I2C 适配器 device 交给 i2c-dev 进行绑定(通常创建 /dev/i2c-X 等节点所需的内部对象)。 */
i2cdev_attach_adapter(dev); /**< 该函数在本文件其他位置实现:通常把 adapter 映射到字符设备次设备号并建立 class 设备。 */
return 0; /**< 返回 0 表示遍历继续进行。 */
}

i2c_dev_detach_adapter:供 i2c_for_each_dev 遍历时调用的“解绑适配器”回调

1
2
3
4
5
6
static int __exit i2c_dev_detach_adapter(struct device *dev, void *dummy)
{
/** @brief 将一个 I2C 适配器 device 从 i2c-dev 解绑(通常撤销 /dev/i2c-X 等节点对应关系)。 */
i2cdev_detach_adapter(dev); /**< 该函数在本文件其他位置实现:通常释放次设备占用并销毁 class 设备节点。 */
return 0; /**< 返回 0 表示遍历继续进行。 */
}

i2c_dev_init:模块加载时的初始化(字符设备号段 + class + bus notifier + 绑定现存适配器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static int __init i2c_dev_init(void)
{
int res;

pr_info("i2c /dev entries driver\n");

/**
* @brief 预先申请一段字符设备号(主设备号固定为 I2C_MAJOR,次设备号覆盖 I2C_MINORS 个)。
*
* 设计意义:
* - i2c-dev 通常以固定主设备号暴露用户态接口;
* - 通过一段连续次设备号,为不同 I2C 适配器分配 /dev/i2c-X 的映射空间。
*/
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); /**< MKDEV 组合主/次设备号起始值。 */
if (res)
goto out;

/**
* @brief 注册 class,用于在 /sys/class 下生成对应类并配合 uevent 创建设备节点。
*
* 这里的 i2c_dev_class 是全局对象(本文件其他位置定义),
* 其 class->devnode 等行为会影响 /dev/i2c-* 的呈现方式。
*/
res = class_register(&i2c_dev_class);
if (res)
goto out_unreg_chrdev;

/**
* @brief 注册到 I2C bus 的 notifier。
*
* 设计意义:
* - 使 i2c-dev 能感知“后续新增/移除适配器”的事件;
* - 避免只在 init 时扫描一次导致遗漏热插拔/动态加载的适配器。
*
* i2cdev_notifier 为 notifier_block(本文件其他位置定义),
* 通常在 BUS_NOTIFY_ADD_DEVICE / BUS_NOTIFY_DEL_DEVICE 等事件中调用 attach/detach。
*/
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;

/**
* @brief 立即绑定当前已经存在的 I2C 适配器。
*
* i2c_for_each_dev() 会遍历挂在 I2C 总线上的相关 device(这里期望覆盖适配器设备),
* 并对每个条目调用 i2c_dev_attach_adapter() 完成映射建立。
*
* 该步骤解决“模块加载时系统已存在多个 I2C 适配器”的场景,
* notifier 只覆盖后续变化,不能替代对存量对象的扫描绑定。
*/
i2c_for_each_dev(NULL, i2c_dev_attach_adapter);

return 0;

out_unreg_class:
class_unregister(&i2c_dev_class); /**< 回滚:撤销 class 注册,避免 sysfs 残留。 */
out_unreg_chrdev:
unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS); /**< 回滚:释放字符设备号段。 */
out:
pr_err("Driver Initialisation failed\n");
return res;
}

i2c_dev_exit:模块卸载时的反初始化(注销 notifier + 解绑所有适配器 + 注销 class 与设备号段)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void __exit i2c_dev_exit(void)
{
/** @brief 先注销 notifier,避免卸载过程中仍接收 I2C bus 事件导致访问已卸载代码或已释放对象。 */
bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier);

/** @brief 遍历并解绑所有当前仍存在的适配器,撤销 /dev/i2c-X 与内部映射关系。 */
i2c_for_each_dev(NULL, i2c_dev_detach_adapter);

/** @brief 注销 class,撤销 sysfs class 以及由其派生的设备呈现关系。 */
class_unregister(&i2c_dev_class);

/** @brief 释放字符设备号段,避免主/次设备号占用泄漏到后续模块加载周期。 */
unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
}

模块元信息与入口绑定(不涉及复杂机制,仅保留必要 Doxygen 注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** @brief 声明模块作者信息。 */
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
MODULE_AUTHOR("Simon G. Vogl <simon@tk.uni-linz.ac.at>");

/** @brief 声明模块功能描述。 */
MODULE_DESCRIPTION("I2C /dev entries driver");

/** @brief 声明模块许可协议。 */
MODULE_LICENSE("GPL");

/** @brief 绑定模块加载入口。 */
module_init(i2c_dev_init);

/** @brief 绑定模块卸载入口。 */
module_exit(i2c_dev_exit);

i2cdev_attach_adapter / i2cdev_detach_adapter / i2cdev_notifier_call / i2cdev_dev_release:i2c-dev 对适配器设备的动态绑定、字符设备发布与生命周期回收

单核、无 MMU、ARMv7-M(STM32H750)平台关注

  • 这里的核心机制来自 Linux device modelstruct device 的引用计数与 .release 回收回调、class 触发的 sysfs 组织与(配合 devtmpfs/用户态)设备节点呈现、以及 cdev 将文件操作表暴露给字符设备。
  • 单核情况下依然需要 notifier:适配器设备的“添加/移除”属于总线事件驱动,其触发与处理可能与其他上下文交错(例如模块加载、驱动探测),并不因为单核而消失。
  • 无 MMU 不改变这些内核抽象的语义,但对资源释放的严谨性要求更高:.release 必须存在且正确,否则长期运行会出现不可回收对象累积。

i2c_dev_class:定义 i2c-dev 的设备类(sysfs 归类与属性组)

1
2
3
4
static const struct class i2c_dev_class = {
.name = "i2c-dev", /**< class 名称:决定 /sys/class 下的目录名,并参与设备节点命名策略。 */
.dev_groups = i2c_groups, /**< sysfs 属性组:为该 class 下设备提供统一的属性集合。 */
};

i2cdev_dev_release:device 对象最终释放时回收 i2c_dev 容器内存

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @brief i2c-dev 设备对象的 release 回调。
*
* device model 规定:当 struct device 的引用计数降为 0 时,必须调用 .release 完成最终回收。
* 该回调通常只做“与 device 同生命周期的宿主结构体释放”,避免双重释放与时序问题。
*/
static void i2cdev_dev_release(struct device *dev)
{
struct i2c_dev *i2c_dev;

i2c_dev = container_of(dev, struct i2c_dev, dev); /**< 由嵌入的 dev 成员反推出宿主 i2c_dev。 */
kfree(i2c_dev); /**< 释放宿主结构;其余资源应在更早阶段已解绑/撤销。 */
}

i2cdev_attach_adapter:把一个 i2c_adapter 绑定为 /dev/i2c-N 字符设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @brief 将 I2C 适配器设备绑定为一个 i2c-dev 字符设备实例。
*
* 关键点:
* - 通过 adap->nr 映射次设备号,从而形成 /dev/i2c-N 的一一对应关系。
* - 同时创建 cdev(文件操作)与 device(sysfs/节点呈现),并用 cdev_device_add 原子化地关联二者。
*/
static int i2cdev_attach_adapter(struct device *dev)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;

if (dev->type != &i2c_adapter_type)
return NOTIFY_DONE; /**< 仅处理 I2C 适配器类型的 device,忽略其他 I2C 总线上的 device。 */
adap = to_i2c_adapter(dev); /**< 将通用 device 转换为 i2c_adapter。 */

i2c_dev = get_free_i2c_dev(adap); /**< 获取一个空闲的 i2c_dev 容器并建立与适配器的关联(含次设备号占用等)。 */
if (IS_ERR(i2c_dev))
return NOTIFY_DONE;

cdev_init(&i2c_dev->cdev, &i2cdev_fops); /**< 初始化 cdev,并绑定字符设备的 file_operations。 */
i2c_dev->cdev.owner = THIS_MODULE; /**< 绑定模块所有权:防止设备仍在使用时模块被卸载。 */

device_initialize(&i2c_dev->dev); /**< 初始化 device 基础状态(含引用计数/内部链表等),为后续字段赋值做准备。 */
i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr); /**< 主设备号固定为 I2C_MAJOR,次设备号使用适配器编号。 */
i2c_dev->dev.class = &i2c_dev_class; /**< 归入 i2c-dev class:形成 sysfs 组织与节点呈现策略。 */
i2c_dev->dev.parent = &adap->dev; /**< 设定父子层级:该 i2c-dev 设备从属于对应适配器设备。 */
i2c_dev->dev.release = i2cdev_dev_release; /**< 指定 release 回调:保证引用计数归零时可回收宿主结构。 */

res = dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr); /**< 设定 device 名称:影响 sysfs 目录名与 /dev 节点命名。 */
if (res)
goto err_put_i2c_dev;

res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev); /**< 将 cdev 与 device 作为整体注册,保证呈现与回滚一致性。 */
if (res)
goto err_put_i2c_dev;

pr_debug("adapter [%s] registered as minor %d\n", adap->name, adap->nr);
return NOTIFY_OK;

err_put_i2c_dev:
put_i2c_dev(i2c_dev, false); /**< 失败回滚:释放占用的次设备号/引用计数等;避免残留半初始化对象。 */
return NOTIFY_DONE;
}

i2cdev_detach_adapter:解绑适配器对应的字符设备实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @brief 将 I2C 适配器从 i2c-dev 字符设备体系中解绑。
*
* 关键点:
* - 通过 adap->nr 找到对应的 i2c_dev;
* - put_i2c_dev(..., true) 通常会触发撤销 cdev/device 注册,并减少引用计数,最终可能进入 .release 回收。
*/
static int i2cdev_detach_adapter(struct device *dev)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;

if (dev->type != &i2c_adapter_type)
return NOTIFY_DONE;
adap = to_i2c_adapter(dev);

i2c_dev = i2c_dev_get_by_minor(adap->nr); /**< 按次设备号查找已注册实例;返回 NULL 表示此前未成功绑定。 */
if (!i2c_dev)
return NOTIFY_DONE;

put_i2c_dev(i2c_dev, true); /**< true:表示“解绑路径”,通常包含注销 device/cdev 并触发引用计数下降。 */

pr_debug("adapter [%s] unregistered\n", adap->name);
return NOTIFY_OK;
}

i2cdev_notifier_call:总线事件分发到 attach/detach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @brief I2C 总线 notifier 回调:根据 bus 事件类型执行绑定或解绑。
*
* @param nb notifier_block。
* @param action bus 事件类型(例如 ADD/DEL device)。
* @param data 事件载荷,通常为 struct device *。
* @return NOTIFY_* 返回码,用于上层 notifier 链的控制。
*/
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
void *data)
{
struct device *dev = data; /**< bus_notify 传入的设备指针,代表被添加/移除的对象。 */

switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
return i2cdev_attach_adapter(dev); /**< 新增 device:若是 i2c_adapter,则创建 /dev/i2c-N。 */
case BUS_NOTIFY_DEL_DEVICE:
return i2cdev_detach_adapter(dev); /**< 删除 device:若是 i2c_adapter,则撤销对应实例。 */
}

return NOTIFY_DONE; /**< 其他事件不处理。 */
}

i2cdev_notifier:notifier 节点本体

1
2
3
static struct notifier_block i2cdev_notifier = {
.notifier_call = i2cdev_notifier_call, /**< notifier 链触发时调用的函数入口。 */
};

i2c_dev / I2C_MINORS / i2c_dev_list:i2c-dev 适配器实例的数据模型与全局索引表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @brief i2c-dev 的运行时实例:与一个 i2c_adapter 一一对应。
*
* 该结构用于将“内核中的 I2C 适配器对象”映射为“用户态可访问的字符设备节点”。
*/
struct i2c_dev {
struct list_head list; /**< 挂入全局 i2c_dev_list 的链表节点,用于按 minor 查找。 */
struct i2c_adapter *adap; /**< 对应的 I2C 适配器指针,adap->nr 用作次设备号。 */
struct device dev; /**< device model 对象:用于 sysfs/class 关联与生命周期管理。 */
struct cdev cdev; /**< 字符设备对象:承载 file_operations 并连接到 devt。 */
};

#define I2C_MINORS (MINORMASK + 1) /**< 次设备号空间大小:覆盖全部可表示的 minor 编号范围。 */

static LIST_HEAD(i2c_dev_list); /**< 全局 i2c_dev 实例链表:与适配器集合“并行维护”。 */
static DEFINE_SPINLOCK(i2c_dev_list_lock); /**< 保护 i2c_dev_list 的自旋锁:防止并发遍历/插入/删除破坏结构。 */

i2c_dev_get_by_minor:按次设备号在全局链表中查找对应 i2c_dev

作用与实现原理

  • i2c_dev_list 中线性查找 adap->nr == index 的条目。
  • 访问链表期间持有 i2c_dev_list_lock,保证遍历过程中链表结构不被并发修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @brief 根据次设备号查找 i2c_dev。
*
* @param index 次设备号(通常等于 i2c_adapter->nr)。
* @return 找到则返回 i2c_dev 指针;找不到返回 NULL。
*
* @note 本函数仅在锁内保证“查找过程”的结构安全;返回后不再持锁,
* 调用者应确保后续使用与生命周期约束匹配(例如在受控路径中立即操作)。
*/
static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
{
struct i2c_dev *i2c_dev;

spin_lock(&i2c_dev_list_lock); /**< 保护链表遍历:避免并发 list_del/list_add 导致遍历失效。 */
list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
if (i2c_dev->adap->nr == index)
goto found; /**< 命中后跳转统一解锁出口,避免重复解锁路径。 */
}
i2c_dev = NULL; /**< 未命中:显式返回 NULL。 */
found:
spin_unlock(&i2c_dev_list_lock);
return i2c_dev;
}

get_free_i2c_dev:分配一个新的 i2c_dev 并加入全局链表

作用与实现原理

  • adap->nr 作为次设备号索引,因此必须保证 adap->nr < I2C_MINORS
  • 通过 kzalloc() 分配并清零结构体,减少未初始化字段带来的状态不确定性。
  • 将新对象插入 i2c_dev_list,使后续能通过 i2c_dev_get_by_minor() 定位到它。
  • 采用 ERR_PTR(-Exxx) 形式返回错误,使调用者可用 IS_ERR() 统一处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @brief 为指定适配器分配一个 i2c_dev 实例,并登记到全局链表。
*
* @param adap 目标 I2C 适配器。
* @return 成功返回 i2c_dev 指针;失败返回 ERR_PTR(...)。
*/
static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap)
{
struct i2c_dev *i2c_dev;

if (adap->nr >= I2C_MINORS) {
pr_err("Out of device minors (%d)\n", adap->nr);
return ERR_PTR(-ENODEV); /**< 次设备号空间不足:无法为该适配器创建 /dev/i2c-N 映射。 */
}

i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL); /**< 进程上下文分配;失败则返回 -ENOMEM。 */
if (!i2c_dev)
return ERR_PTR(-ENOMEM);

i2c_dev->adap = adap; /**< 建立“实例 → 适配器”关联,后续以 adap->nr 作为 minor。 */

spin_lock(&i2c_dev_list_lock);
list_add_tail(&i2c_dev->list, &i2c_dev_list); /**< 插入尾部:仅用于索引管理,顺序无功能性约束。 */
spin_unlock(&i2c_dev_list_lock);

return i2c_dev;
}

put_i2c_dev:从全局链表移除 i2c_dev,并按需注销字符设备与释放 device 引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @brief 释放一个 i2c_dev:移出索引表,必要时注销 cdev/device,并释放 device 引用。
*
* @param i2c_dev 目标实例。
* @param del_cdev 是否需要执行 cdev_device_del()(仅对“已注册完成”的对象为 true)。
*/
static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
{
spin_lock(&i2c_dev_list_lock);
list_del(&i2c_dev->list); /**< 先移出全局索引:防止并发查找获得正在销毁的对象。 */
spin_unlock(&i2c_dev_list_lock);

if (del_cdev)
cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev); /**< 撤销字符设备与 device 的注册呈现。 */

put_device(&i2c_dev->dev); /**< 交由 device model 完成最终生命周期收敛,并在引用归零时调用 .release。 */
}

static ssize_t name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_dev *i2c_dev = i2c_dev_get_by_minor(MINOR(dev->devt));

if (!i2c_dev)
return -ENODEV;
return sysfs_emit(buf, "%s\n", i2c_dev->adap->name);
}
static DEVICE_ATTR_RO(name);

static struct attribute *i2c_attrs[] = {
&dev_attr_name.attr,
NULL,
};

i2cdev_read / i2cdev_write:i2c-dev 的 read/write 路径(用户缓冲区隔离与适配器能力约束)


i2cdev_read:从匿名 i2c_client 指向的地址读取数据并拷回用户态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @brief i2c-dev 的 read 实现:对当前会话地址执行 I2C 主接收并返回给用户。
*
* @param file 文件对象,file->private_data 保存匿名 i2c_client(包含 adapter/addr/flags)。
* @param buf 用户态接收缓冲区指针。
* @param count 用户请求读取的字节数。
* @param offset 未使用(字符设备语义上不维护文件偏移)。
* @return 成功返回实际读取字节数;失败返回负 errno。
*/
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
char *tmp;
int ret;

struct i2c_client *client = file->private_data; /**< 会话态匿名 client:提供目标地址与所属适配器。 */

/** 适配器能力门禁:必须支持原生 I2C 传输接口 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;

/** 单次读取上限:限制内存分配与传输规模 */
if (count > 8192)
count = 8192;

tmp = kzalloc(count, GFP_KERNEL); /**< 内核缓冲区:用于承接 i2c_master_recv 的数据。 */
if (tmp == NULL)
return -ENOMEM;

pr_debug("i2c-%d reading %zu bytes.\n", iminor(file_inode(file)), count);

ret = i2c_master_recv(client, tmp, count); /**< 以会话地址 client->addr 为目标执行接收;返回值为实际接收长度或负 errno。 */
if (ret >= 0)
if (copy_to_user(buf, tmp, ret)) /**< 将已接收数据拷回用户缓冲区;失败按 -EFAULT 处理。 */
ret = -EFAULT;

kfree(tmp); /**< 释放内核缓冲区,避免泄漏。 */
return ret;
}

i2cdev_write:从用户态拷贝写入数据并对匿名 i2c_client 指向的地址发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* @brief i2c-dev 的 write 实现:从用户缓冲区拷贝数据并对当前会话地址执行 I2C 主发送。
*
* @param file 文件对象,file->private_data 保存匿名 i2c_client。
* @param buf 用户态发送缓冲区指针。
* @param count 用户请求写入的字节数。
* @param offset 未使用。
* @return 成功返回实际发送字节数;失败返回负 errno。
*/
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp;
struct i2c_client *client = file->private_data; /**< 会话态匿名 client:提供目标地址与所属适配器。 */

/** 适配器能力门禁:必须支持原生 I2C 传输接口 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;

/** 单次写入上限:限制内存分配与传输规模 */
if (count > 8192)
count = 8192;

tmp = memdup_user(buf, count); /**< 从用户态复制到内核态;失败返回 ERR_PTR 以携带 errno。 */
if (IS_ERR(tmp))
return PTR_ERR(tmp);

pr_debug("i2c-%d writing %zu bytes.\n", iminor(file_inode(file)), count);

ret = i2c_master_send(client, tmp, count); /**< 以会话地址 client->addr 为目标执行发送;返回值为实际发送长度或负 errno。 */
kfree(tmp); /**< 释放内核态副本,避免泄漏。 */
return ret;
}

i2c-dev 文件描述符模型与 ioctl 分发(i2cdev_open / i2cdev_release / i2cdev_ioctl / i2cdev_fops)


i2cdev_open:打开 /dev/i2c-N 并创建匿名 i2c_client 作为会话态容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @brief 打开 i2c-dev 设备文件,创建匿名 i2c_client 并绑定到 file->private_data。
*
* @note 匿名 i2c_client 不会注册到 driver model;仅用于保存会话态(addr/flags/adapter)。
*/
static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode); /**< 次设备号,语义上对应适配器编号 adap->nr。 */
struct i2c_client *client;
struct i2c_adapter *adap;

adap = i2c_get_adapter(minor); /**< 获取适配器并增加引用计数;失败表示该适配器不存在。 */
if (!adap)
return -ENODEV;

client = kzalloc(sizeof(*client), GFP_KERNEL);/**< 分配会话态对象;失败需归还适配器引用。 */
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}

snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr); /**< 仅用于标识/调试,不参与设备匹配。 */

client->adapter = adap; /**< 会话绑定到该 I2C 总线。 */
file->private_data = client; /**< 后续 read/write/ioctl 均从此处取得会话态对象。 */

return 0;
}

i2cdev_release:关闭文件描述符并释放匿名 i2c_client 与适配器引用

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @brief 关闭 i2c-dev 文件,释放 open 阶段建立的会话态资源。
*/
static int i2cdev_release(struct inode *inode, struct file *file)
{
struct i2c_client *client = file->private_data; /**< open 阶段创建的匿名 i2c_client。 */

i2c_put_adapter(client->adapter); /**< 归还 i2c_get_adapter 获取的引用计数。 */
kfree(client); /**< 释放匿名 client。 */
file->private_data = NULL; /**< 降低释放后误用风险。 */

return 0;
}

i2cdev_ioctl:配置会话地址/标志并分发 I2C_RDWR 与 I2C_SMBUS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* @brief i2c-dev ioctl 入口:更新会话态(addr/flags)并执行传输类 ioctl。
*
* @note I2C_SLAVE/I2C_SLAVE_FORCE 仅改变匿名 client 的 addr,不会影响已注册内核驱动的 i2c_client。
*/
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data; /**< 会话态对象:承载 addr/flags/adapter。 */
unsigned long funcs;

dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);

switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
/* 地址合法性:10-bit 最大 0x3ff;7-bit 最大 0x7f */
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;

/**
* @brief I2C_SLAVE 与 I2C_SLAVE_FORCE 的差异点
* I2C_SLAVE:检查该地址是否“忙”,避免用户态干扰已存在的设备实例;
* I2C_SLAVE_FORCE:跳过忙检查,允许强制占用地址。
*/
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;

client->addr = arg; /**< 写入会话态目标地址,供 read/write 与 SMBus ioctl 使用。 */
return 0;

case I2C_TENBIT:
if (arg)
client->flags |= I2C_M_TEN; /**< 置位:10-bit 寻址。 */
else
client->flags &= ~I2C_M_TEN; /**< 清位:7-bit 寻址。 */
return 0;

case I2C_PEC:
/**
* @brief PEC 标志只作用于 i2c-dev 会话态的 SMBus 事务
* 不会影响内核中已注册设备驱动使用的其它 i2c_client。
*/
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;

case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter); /**< 查询适配器能力位图。 */
return put_user(funcs, (unsigned long __user *)arg);

case I2C_RDWR: {
struct i2c_rdwr_ioctl_data rdwr_arg;
struct i2c_msg *rdwr_pa;
int res;

if (copy_from_user(&rdwr_arg,
(struct i2c_rdwr_ioctl_data __user *)arg,
sizeof(rdwr_arg)))
return -EFAULT;

if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
return -EINVAL;

if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS) /**< 防止一次性提交过多报文导致资源压力。 */
return -EINVAL;

rdwr_pa = memdup_array_user(rdwr_arg.msgs,
rdwr_arg.nmsgs, sizeof(struct i2c_msg)); /**< 将用户态 i2c_msg 向量拷入内核。 */
if (IS_ERR(rdwr_pa))
return PTR_ERR(rdwr_pa);

res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa); /**< 执行向量化 I2C 传输(内部会对 buf 做隔离拷贝)。 */
kfree(rdwr_pa);
return res;
}

case I2C_SMBUS: {
struct i2c_smbus_ioctl_data data_arg;

if (copy_from_user(&data_arg,
(struct i2c_smbus_ioctl_data __user *) arg,
sizeof(struct i2c_smbus_ioctl_data)))
return -EFAULT;

return i2cdev_ioctl_smbus(client, data_arg.read_write,
data_arg.command,
data_arg.size,
data_arg.data);
}

case I2C_RETRIES:
if (arg > INT_MAX)
return -EINVAL;
client->adapter->retries = arg; /**< 修改适配器全局重试次数(影响该适配器上的传输策略)。 */
break;

case I2C_TIMEOUT:
if (arg > INT_MAX)
return -EINVAL;
client->adapter->timeout = msecs_to_jiffies(arg * 10); /**< 用户态单位为 10ms,此处转换为 jiffies。 */
break;

default:
return -ENOTTY;
}

return 0;
}

i2cdev_fops:把 VFS 入口绑定到 i2c-dev 的实现

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief i2c-dev 文件操作表:定义 VFS 对该字符设备的读写与控制入口。
*/
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.compat_ioctl = compat_i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};

i2c-dev ioctl 传输与地址占用检查(i2cdev_ioctl_rdwr / i2cdev_ioctl_smbus / i2cdev_check_addr 系列)


i2cdev_ioctl_rdwr:实现 I2C_RDWR,执行向量化 i2c_transfer 并完成用户缓冲区隔离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* @brief 执行 I2C_RDWR ioctl 的核心逻辑:复制用户缓冲区、调用 i2c_transfer、按需拷回读数据。
*
* @param client 会话态匿名 i2c_client(提供 adapter/flags 等;地址信息在 i2c_msg 中自带)。
* @param nmsgs 报文条数。
* @param msgs 内核态 i2c_msg 数组(数组本体已在 ioctl 入口处从用户态复制到内核态)。
* @return i2c_transfer 返回值(成功为传输的报文数;失败为负 errno)。
*/
static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
unsigned nmsgs, struct i2c_msg *msgs)
{
u8 __user **data_ptrs; /**< 保存每条消息原始的用户态 buf 指针,用于读方向完成后 copy_to_user。 */
int i, res;

/** 适配器必须支持原生 I2C 传输接口,否则拒绝 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;

data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL); /**< 为指针表分配内存,避免栈上可变长数组风险。 */
if (!data_ptrs)
return -ENOMEM;

res = 0;
for (i = 0; i < nmsgs; i++) {
/** 限制单条消息长度,避免异常大分配与异常长传输 */
if (msgs[i].len > 8192) {
res = -EINVAL;
break;
}

data_ptrs[i] = (u8 __user *)msgs[i].buf; /**< 记录用户态指针。 */
msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);/**< 将用户缓冲区复制为内核缓冲区,保证传输期间地址稳定。 */
if (IS_ERR(msgs[i].buf)) {
res = PTR_ERR(msgs[i].buf);
break;
}

msgs[i].flags |= I2C_M_DMA_SAFE; /**< 标记:该 buf 为 GFP_KERNEL 分配,通常满足底层 DMA 映射要求。 */

/**
* @brief I2C_M_RECV_LEN 处理要点
* - 该标志表示“本次接收长度由从机返回的首字节决定”;
* - 必须保证:是读操作、len 至少为 1、buf[0] 至少为 1;
* - 并且 buf 总容量必须覆盖 (buf[0] + I2C_SMBUS_BLOCK_MAX) 的上限模型,
* 以满足底层驱动可能按最大允许块长度收取数据的实现习惯。
*/
if (msgs[i].flags & I2C_M_RECV_LEN) {
if (!(msgs[i].flags & I2C_M_RD) ||
msgs[i].len < 1 || msgs[i].buf[0] < 1 ||
msgs[i].len < msgs[i].buf[0] +
I2C_SMBUS_BLOCK_MAX) {
i++; /**< 使后续清理循环覆盖当前已分配项的边界一致性。 */
res = -EINVAL;
break;
}

msgs[i].len = msgs[i].buf[0]; /**< 将“可变接收长度”写回 msgs[i].len,供 i2c_transfer 使用。 */
}
}

/** 发生构造阶段错误:释放已分配的每条 msg.buf 与 data_ptrs */
if (res < 0) {
int j;
for (j = 0; j < i; ++j)
kfree(msgs[j].buf);
kfree(data_ptrs);
return res;
}

res = i2c_transfer(client->adapter, msgs, nmsgs); /**< 核心调用:将报文向量提交给适配器驱动执行。 */

/** 传输完成后:对读方向的消息,将内核缓冲区内容拷回原用户缓冲区 */
while (i-- > 0) {
if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
if (copy_to_user(data_ptrs[i], msgs[i].buf,
msgs[i].len))
res = -EFAULT;
}
kfree(msgs[i].buf); /**< 始终释放内核缓冲区副本。 */
}
kfree(data_ptrs);
return res;
}

i2cdev_ioctl_smbus:实现 I2C_SMBUS,参数校验、用户数据拷入拷出与兼容模式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* @brief 执行 I2C_SMBUS ioctl 的核心逻辑:校验参数并调用 i2c_smbus_xfer。
*
* @param client 会话态匿名 i2c_client(提供 adapter/addr/flags)。
* @param read_write 读写方向(I2C_SMBUS_READ / I2C_SMBUS_WRITE)。
* @param command SMBus command 字段。
* @param size SMBus 事务类型(byte/word/block/proc-call 等)。
* @param data 用户态数据指针(部分 size 情况下可为 NULL)。
* @return 0 或正值表示成功(依底层语义);负 errno 表示失败。
*/
static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
u8 read_write, u8 command, u32 size,
union i2c_smbus_data __user *data)
{
union i2c_smbus_data temp = {}; /**< 内核侧临时缓冲区,用于与用户态 data 交换。 */
int datasize, res;

/** size 白名单校验:仅允许已知 SMBus 事务类型 */
if ((size != I2C_SMBUS_BYTE) &&
(size != I2C_SMBUS_QUICK) &&
(size != I2C_SMBUS_BYTE_DATA) &&
(size != I2C_SMBUS_WORD_DATA) &&
(size != I2C_SMBUS_PROC_CALL) &&
(size != I2C_SMBUS_BLOCK_DATA) &&
(size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
(size != I2C_SMBUS_I2C_BLOCK_DATA) &&
(size != I2C_SMBUS_BLOCK_PROC_CALL)) {
dev_dbg(&client->adapter->dev,
"size out of range (%x) in ioctl I2C_SMBUS.\n",
size);
return -EINVAL;
}

/** 读写方向校验:只允许 READ/WRITE */
if ((read_write != I2C_SMBUS_READ) &&
(read_write != I2C_SMBUS_WRITE)) {
dev_dbg(&client->adapter->dev,
"read_write out of range (%x) in ioctl I2C_SMBUS.\n",
read_write);
return -EINVAL;
}

/**
* @brief QUICK 与 BYTE(write) 为特殊事务:不携带数据区
* 这两类操作直接以 data=NULL 调用 i2c_smbus_xfer。
*/
if ((size == I2C_SMBUS_QUICK) ||
((size == I2C_SMBUS_BYTE) &&
(read_write == I2C_SMBUS_WRITE)))
return i2c_smbus_xfer(client->adapter, client->addr,
client->flags, read_write,
command, size, NULL);

/** 其余事务必须提供 data 指针 */
if (data == NULL) {
dev_dbg(&client->adapter->dev,
"data is NULL pointer in ioctl I2C_SMBUS.\n");
return -EINVAL;
}

/** 按事务类型确定需要与用户态交换的数据大小 */
if ((size == I2C_SMBUS_BYTE_DATA) ||
(size == I2C_SMBUS_BYTE))
datasize = sizeof(data->byte);
else if ((size == I2C_SMBUS_WORD_DATA) ||
(size == I2C_SMBUS_PROC_CALL))
datasize = sizeof(data->word);
else
datasize = sizeof(data->block);

/**
* @brief 哪些情况需要先从用户态拷入
* - 写方向必然需要输入数据;
* - PROC_CALL/BLOCK_PROC_CALL 是“写后读”的复合事务,也需要先拷入;
* - I2C_BLOCK_DATA 在某些语义下也需要输入块长度与内容。
*/
if ((size == I2C_SMBUS_PROC_CALL) ||
(size == I2C_SMBUS_BLOCK_PROC_CALL) ||
(size == I2C_SMBUS_I2C_BLOCK_DATA) ||
(read_write == I2C_SMBUS_WRITE)) {
if (copy_from_user(&temp, data, datasize))
return -EFAULT;
}

/**
* @brief I2C_SMBUS_I2C_BLOCK_BROKEN 兼容转换
* 目的:保持旧用户态 ABI 的二进制兼容性,把旧约定转换为新约定 I2C_BLOCK_DATA。
*/
if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
size = I2C_SMBUS_I2C_BLOCK_DATA;
if (read_write == I2C_SMBUS_READ)
temp.block[0] = I2C_SMBUS_BLOCK_MAX; /**< 旧约定下读块长度上限由内核填充为最大值。 */
}

res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
read_write, command, size, &temp); /**< 核心调用:执行 SMBus 事务。 */

/** 需要把结果拷回用户态的情况:读方向或复合事务返回数据 */
if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
(size == I2C_SMBUS_BLOCK_PROC_CALL) ||
(read_write == I2C_SMBUS_READ))) {
if (copy_to_user(data, &temp, datasize))
return -EFAULT;
}
return res;
}

i2cdev_check / i2cdev_check_mux_parents / i2cdev_check_mux_children / i2cdev_check_addr:为 I2C_SLAVE 提供“地址忙”判定(含 mux 树遍历)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 检查某个 device 是否为目标地址的 i2c_client,并判断其是否“忙”。
*
* @param dev 被遍历到的设备。
* @param addrp 指向待检查地址(unsigned int)的指针。
* @return
* 0:继续遍历;
* -EBUSY:该地址存在 i2c_client 且已绑定驱动,视为忙;
*
* @note 本实现将“已注册但未绑定驱动”的地址视为不忙,以放宽用户态访问限制。
*/
static int i2cdev_check(struct device *dev, void *addrp)
{
struct i2c_client *client = i2c_verify_client(dev); /**< 验证并转换为 i2c_client。 */

if (!client || client->addr != *(unsigned int *)addrp)
return 0;

return dev->driver ? -EBUSY : 0; /**< 仅当已绑定驱动时判定为忙。 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief 沿 mux 树向上递归检查:在父适配器链上检查地址是否忙。
*/
static int i2cdev_check_mux_parents(struct i2c_adapter *adapter, int addr)
{
struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter); /**< 若该适配器是 mux 子通道,则获得其父适配器。 */
int result;

result = device_for_each_child(&adapter->dev, &addr, i2cdev_check); /**< 在当前适配器的子设备中查找目标地址。 */
if (!result && parent)
result = i2cdev_check_mux_parents(parent, addr); /**< 若未忙且存在父适配器,则继续向上递归。 */

return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @brief 沿 mux 树向下递归检查:遍历子适配器与其子设备,检查地址是否忙。
*
* @note 该函数作为 device_for_each_child 的回调使用:
* - 若遍历到的是 i2c_adapter 类型 device,则继续深入其子设备;
* - 否则按 i2c_client 检查地址与忙状态。
*/
static int i2cdev_check_mux_children(struct device *dev, void *addrp)
{
int result;

if (dev->type == &i2c_adapter_type)
result = device_for_each_child(dev, addrp,
i2cdev_check_mux_children); /**< 深入子适配器,覆盖 mux 下游拓扑。 */
else
result = i2cdev_check(dev, addrp); /**< 非适配器则按 i2c_client 检查。 */

return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 对给定适配器与地址执行“忙”检查(覆盖 mux 父链与子树)。
*
* @param adapter 目标适配器(对应当前打开的 /dev/i2c-N)。
* @param addr 待设置的从地址。
* @return 0 表示不忙;-EBUSY 表示忙;其他负值表示遍历过程异常。
*/
static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
{
struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
int result = 0;

if (parent)
result = i2cdev_check_mux_parents(parent, addr); /**< 先查父链:避免在上游已有占用时误判为空闲。 */

if (!result)
result = device_for_each_child(&adapter->dev, &addr,
i2cdev_check_mux_children); /**< 再查本适配器及其 mux 下游子树。 */

return result;
}