[Linux]学习笔记系列 -- [drivers][i2c]i2c-dev


title: i2c-dev

categories:

  • linux
  • drivers
  • I2C
    tags:
  • linux
  • drivers
  • I2C
    abbrlink: a8082287
    date: 2026-01-12 14:18:51

https://github.com/wdfk-prog/linux-study

文章目录

  • [Linux i2c-dev 字符设备接口解析](#Linux i2c-dev 字符设备接口解析)
  • [[drivers/i2c/i2c-dev.c] [I2C 用户态访问接口(/dev/i2c-X)] [把 I2C/SMBus 适配器以字符设备形式暴露给用户态,通过 read/write/ioctl 执行传输]](#[drivers/i2c/i2c-dev.c] [I2C 用户态访问接口(/dev/i2c-X)] [把 I2C/SMBus 适配器以字符设备形式暴露给用户态,通过 read/write/ioctl 执行传输])
    • 介绍
    • 历史与背景
    • 核心原理与设计
    • 使用场景
    • 对比分析
    • 总结
    • [i2c_dev_init / i2c_dev_exit / i2c_dev_attach_adapter / i2c_dev_detach_adapter:i2c-dev 字符设备节点的初始化、适配器绑定与注销清理](#i2c_dev_init / i2c_dev_exit / i2c_dev_attach_adapter / i2c_dev_detach_adapter:i2c-dev 字符设备节点的初始化、适配器绑定与注销清理)
      • [平台关注:单核、无 MMU、ARMv7-M(STM32H750)](#平台关注:单核、无 MMU、ARMv7-M(STM32H750))
      • [i2c_dev_attach_adapter:供 i2c_for_each_dev 遍历时调用的"绑定适配器"回调](#i2c_dev_attach_adapter:供 i2c_for_each_dev 遍历时调用的“绑定适配器”回调)
      • [i2c_dev_detach_adapter:供 i2c_for_each_dev 遍历时调用的"解绑适配器"回调](#i2c_dev_detach_adapter:供 i2c_for_each_dev 遍历时调用的“解绑适配器”回调)
      • [i2c_dev_init:模块加载时的初始化(字符设备号段 + class + bus notifier + 绑定现存适配器)](#i2c_dev_init:模块加载时的初始化(字符设备号段 + class + bus notifier + 绑定现存适配器))
      • [i2c_dev_exit:模块卸载时的反初始化(注销 notifier + 解绑所有适配器 + 注销 class 与设备号段)](#i2c_dev_exit:模块卸载时的反初始化(注销 notifier + 解绑所有适配器 + 注销 class 与设备号段))
      • [模块元信息与入口绑定(不涉及复杂机制,仅保留必要 Doxygen 注释)](#模块元信息与入口绑定(不涉及复杂机制,仅保留必要 Doxygen 注释))
    • [i2cdev_attach_adapter / i2cdev_detach_adapter / i2cdev_notifier_call / i2cdev_dev_release:i2c-dev 对适配器设备的动态绑定、字符设备发布与生命周期回收](#i2cdev_attach_adapter / i2cdev_detach_adapter / i2cdev_notifier_call / i2cdev_dev_release:i2c-dev 对适配器设备的动态绑定、字符设备发布与生命周期回收)
      • [单核、无 MMU、ARMv7-M(STM32H750)平台关注](#单核、无 MMU、ARMv7-M(STM32H750)平台关注)
      • [i2c_dev_class:定义 i2c-dev 的设备类(sysfs 归类与属性组)](#i2c_dev_class:定义 i2c-dev 的设备类(sysfs 归类与属性组))
      • [i2cdev_dev_release:device 对象最终释放时回收 i2c_dev 容器内存](#i2cdev_dev_release:device 对象最终释放时回收 i2c_dev 容器内存)
      • [i2cdev_attach_adapter:把一个 i2c_adapter 绑定为 /dev/i2c-N 字符设备](#i2cdev_attach_adapter:把一个 i2c_adapter 绑定为 /dev/i2c-N 字符设备)
      • i2cdev_detach_adapter:解绑适配器对应的字符设备实例
      • [i2cdev_notifier_call:总线事件分发到 attach/detach](#i2cdev_notifier_call:总线事件分发到 attach/detach)
      • [i2cdev_notifier:notifier 节点本体](#i2cdev_notifier:notifier 节点本体)
    • [i2c_dev / I2C_MINORS / i2c_dev_list:i2c-dev 适配器实例的数据模型与全局索引表](#i2c_dev / I2C_MINORS / i2c_dev_list:i2c-dev 适配器实例的数据模型与全局索引表)
    • [i2c_dev_get_by_minor:按次设备号在全局链表中查找对应 i2c_dev](#i2c_dev_get_by_minor:按次设备号在全局链表中查找对应 i2c_dev)
    • [get_free_i2c_dev:分配一个新的 i2c_dev 并加入全局链表](#get_free_i2c_dev:分配一个新的 i2c_dev 并加入全局链表)
    • [put_i2c_dev:从全局链表移除 i2c_dev,并按需注销字符设备与释放 device 引用](#put_i2c_dev:从全局链表移除 i2c_dev,并按需注销字符设备与释放 device 引用)
    • [i2cdev_read / i2cdev_write:i2c-dev 的 read/write 路径(用户缓冲区隔离与适配器能力约束)](#i2cdev_read / i2cdev_write:i2c-dev 的 read/write 路径(用户缓冲区隔离与适配器能力约束))
      • [i2cdev_read:从匿名 i2c_client 指向的地址读取数据并拷回用户态](#i2cdev_read:从匿名 i2c_client 指向的地址读取数据并拷回用户态)
      • [i2cdev_write:从用户态拷贝写入数据并对匿名 i2c_client 指向的地址发送](#i2cdev_write:从用户态拷贝写入数据并对匿名 i2c_client 指向的地址发送)
    • [i2c-dev 文件描述符模型与 ioctl 分发(i2cdev_open / i2cdev_release / i2cdev_ioctl / i2cdev_fops)](#i2c-dev 文件描述符模型与 ioctl 分发(i2cdev_open / i2cdev_release / i2cdev_ioctl / i2cdev_fops))
      • [i2cdev_open:打开 /dev/i2c-N 并创建匿名 i2c_client 作为会话态容器](#i2cdev_open:打开 /dev/i2c-N 并创建匿名 i2c_client 作为会话态容器)
      • [i2cdev_release:关闭文件描述符并释放匿名 i2c_client 与适配器引用](#i2cdev_release:关闭文件描述符并释放匿名 i2c_client 与适配器引用)
      • [i2cdev_ioctl:配置会话地址/标志并分发 I2C_RDWR 与 I2C_SMBUS](#i2cdev_ioctl:配置会话地址/标志并分发 I2C_RDWR 与 I2C_SMBUS)
      • [i2cdev_fops:把 VFS 入口绑定到 i2c-dev 的实现](#i2cdev_fops:把 VFS 入口绑定到 i2c-dev 的实现)
    • [i2c-dev ioctl 传输与地址占用检查(i2cdev_ioctl_rdwr / i2cdev_ioctl_smbus / i2cdev_check_addr 系列)](#i2c-dev ioctl 传输与地址占用检查(i2cdev_ioctl_rdwr / i2cdev_ioctl_smbus / i2cdev_check_addr 系列))
      • [i2cdev_ioctl_rdwr:实现 I2C_RDWR,执行向量化 i2c_transfer 并完成用户缓冲区隔离](#i2cdev_ioctl_rdwr:实现 I2C_RDWR,执行向量化 i2c_transfer 并完成用户缓冲区隔离)
      • [i2cdev_ioctl_smbus:实现 I2C_SMBUS,参数校验、用户数据拷入拷出与兼容模式转换](#i2cdev_ioctl_smbus:实现 I2C_SMBUS,参数校验、用户数据拷入拷出与兼容模式转换)
      • [i2cdev_check / i2cdev_check_mux_parents / i2cdev_check_mux_children / i2cdev_check_addr:为 I2C_SLAVE 提供"地址忙"判定(含 mux 树遍历)](#i2cdev_check / i2cdev_check_mux_parents / i2cdev_check_mux_children / i2cdev_check_addr:为 I2C_SLAVE 提供“地址忙”判定(含 mux 树遍历))

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 遍历时调用的"绑定适配器"回调

c 复制代码
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 遍历时调用的"解绑适配器"回调

c 复制代码
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 + 绑定现存适配器)

c 复制代码
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 与设备号段)

c 复制代码
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 注释)

c 复制代码
/** @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 归类与属性组)

c 复制代码
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 容器内存

c 复制代码
/**
 * @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 字符设备

c 复制代码
/**
 * @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:解绑适配器对应的字符设备实例

c 复制代码
/**
 * @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

c 复制代码
/**
 * @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 节点本体

c 复制代码
static struct notifier_block i2cdev_notifier = {
	.notifier_call = i2cdev_notifier_call, /**< notifier 链触发时调用的函数入口。 */
};

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

c 复制代码
/**
 * @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,保证遍历过程中链表结构不被并发修改。
c 复制代码
/**
 * @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() 统一处理。
c 复制代码
/**
 * @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 引用

c 复制代码
/**
 * @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 指向的地址读取数据并拷回用户态

c 复制代码
/**
 * @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 指向的地址发送

c 复制代码
/**
 * @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 作为会话态容器

c 复制代码
/**
 * @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 与适配器引用

c 复制代码
/**
 * @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

c 复制代码
/**
 * @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 的实现

c 复制代码
/**
 * @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 并完成用户缓冲区隔离

c 复制代码
/**
 * @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,参数校验、用户数据拷入拷出与兼容模式转换

c 复制代码
/**
 * @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 树遍历)

c 复制代码
/**
 * @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; /**< 仅当已绑定驱动时判定为忙。 */
}
c 复制代码
/**
 * @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;
}
c 复制代码
/**
 * @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;
}
c 复制代码
/**
 * @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;
}

相关推荐
土拨鼠烧电路2 小时前
笔记03:业务语言速成:“人、货、场”模型与IT系统全景图
笔记
2301_812731413 小时前
CSS3笔记
前端·笔记·css3
越努力越幸运5083 小时前
CSS3学习之网格布局grid
前端·学习·css3
Trouvaille ~3 小时前
【Linux】网络编程基础(二):数据封装与网络传输流程
linux·运维·服务器·网络·c++·tcp/ip·通信
chillxiaohan3 小时前
GO学习记录——多文件调用
开发语言·学习·golang
小乔的编程内容分享站4 小时前
记录使用VSCode调试含scanf()的C语言程序出现的两个问题
c语言·开发语言·笔记·vscode
旅途中的宽~4 小时前
【深度学习】通过nohup后台运行训练命令后,如何通过日志文件反向查找并终止进程?
linux·深度学习
中屹指纹浏览器5 小时前
2026年指纹浏览器技术迭代与风控对抗演进
经验分享·笔记
dump linux5 小时前
内核驱动调试接口与使用方法入门
linux·驱动开发·嵌入式硬件