RK3588 kernel-6.1/drivers/i2c/i2c-dev.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-dev.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-dev.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-dev.c(约 782 行)

编译关系: 可选模块,由 CONFIG_I2C_CHARDEV 控制:

1
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o

Rockchip RK3588 产品 defconfig 中通常为 CONFIG_I2C_CHARDEV=y(如 rockchip_linux_defconfig),即 内置 i2c-dev.ko 或编进内核,用户空间可通过 /dev/i2c-N 访问各条 I2C 总线。

文档保存路径: linuxDoc/drivers/i2c/

定位: 为每条已注册的 i2c_adapter 创建字符设备节点,使用户态程序(i2c-toolslibi2c、自研工具)通过 read/write/ioctl 在该总线上与任意从地址通信,不经过 内核中已绑定的 i2c_driver(除非地址冲突检查失败)。

与内核驱动的关系: i2c-dev 打开的是 匿名 i2c_client(未注册到 driver core),与 DT 枚举的正式 client 并行存在;对已被内核驱动占用的地址,I2C_SLAVE 会返回 -EBUSYI2C_SLAVE_FORCE 可强制覆盖,有破坏内核驱动风险)。


2. 功能总览

模块 主要符号 作用
设备管理 struct i2c_dev、链表 每个 adapter 对应一个 cdev + sysfs 设备
总线通知 i2cdev_notifier adapter 增删时创建/销毁 /dev/i2c-N
打开/关闭 i2cdev_open / release 绑定 adapter、分配匿名 client
读写 i2cdev_read / write 需先 I2C_SLAVE,调用 i2c_master_recv/send
I2C_RDWR i2cdev_ioctl_rdwr 用户态 i2c_msg 批量传输 → i2c_transfer
I2C_SMBUS i2cdev_ioctl_smbus SMBus ioctl → i2c_smbus_xfer
其它 ioctl i2cdev_ioctl SLAVE、FUNCS、PEC、RETRIES、TIMEOUT 等
32 位兼容 compat_i2cdev_ioctl CONFIG_COMPAT 下 arm32 用户态
模块初始化 i2c_dev_init / exit 主设备号 89、class、notifier

3. 架构与数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
用户空间 (i2cget / i2cset / 自定义程序)
│ open("/dev/i2c-2")

i2cdev_open → i2c_get_adapter(2) → 匿名 i2c_client
│ ioctl(I2C_SLAVE, 0x51) // 可选,read/write 需要
│ ioctl(I2C_RDWR, ...) // 或
│ read() / write()

i2c_transfer / i2c_master_* / i2c_smbus_xfer

i2c-core-base.c + i2c-core-smbus.c

i2c-rk3x.c (rk3588)

设备节点:

  • 主设备号 I2C_MAJOR = 89include/linux/i2c-dev.h
  • 次设备号 = adap->nr(与 i2c-2 总线号一致)
  • 路径:/dev/i2c-0/dev/i2c-8(RK3588 最多 9 路内置 I2C,视 DTS 启用情况而定)
  • sysfs class:i2c-dev,设备下可读 name 属性(adapter 名称)

4. 核心数据结构

4.1 struct i2c_dev

1
2
3
4
5
6
struct i2c_dev {
struct list_head list;
struct i2c_adapter *adap;
struct device dev; /* sysfs: /sys/class/i2c-dev/i2c-N */
struct cdev cdev;
};

全局链表 i2c_dev_list + 自旋锁 i2c_dev_list_lock,按 adap->nr 查找。

4.2 打开文件后的 file->private_data

指向 堆上分配的匿名 struct i2c_client

  • client->adapter = 打开的 adapter;
  • client->addr = 由 I2C_SLAVE / I2C_SLAVE_FORCE 设置;
  • client->flags = I2C_M_TENI2C_CLIENT_PEC 等可由 ioctl 修改;
  • 调用 i2c_new_client_device()不出现在 /sys/bus/i2c/devices/ 的正式 client 列表中(名称为 "i2c-dev N" 仅内部使用)。

5. Adapter 注册与 /dev 节点生命周期

5.1 i2c_dev_init()

  1. register_chrdev_region(MKDEV(89, 0), I2C_MINORS, "i2c")
  2. class_create(..., "i2c-dev")
  3. bus_register_notifier(&i2c_bus_type, &i2cdev_notifier)
  4. i2c_for_each_dev(NULL, i2cdev_attach_adapter) — 绑定 已存在 的 adapter。

5.2 i2cdev_attach_adapter()

i2c_bus_type 上新增 adapter 类型 设备时:

1
2
3
4
get_free_i2c_dev(adap)
cdev_init(&i2cdev_fops)
dev_set_name("i2c-%d", adap->nr)
cdev_device_add() → /dev/i2c-N 与 /sys/class/i2c-dev/i2c-N

5.3 i2cdev_detach_adapter()

adapter 删除时 cdev_device_del,从链表移除并释放 i2c_dev

与 RK3588: i2c-rk3x probe 成功并 i2c_add_numbered_adapter() 后,自动出现对应 /dev/i2c-N(若 CONFIG_I2C_CHARDEV=y)。


6. 文件操作

6.1 read() / write()

项目 说明
前提 必须先 ioctl(I2C_SLAVE, addr) 设置从机地址
实现 i2c_master_recv / i2c_master_send(内部 i2c_transfer
长度上限 单次最多 8192 字节
缓冲区 内核 kmalloc / memdup_user,再拷贝到用户空间

适用于简单连续读写的用户态工具;复杂事务应用 I2C_RDWR

6.2 ioctl() 命令一览

定义于 include/uapi/linux/i2c-dev.h

ioctl 含义
I2C_SLAVE (0x0703) 设置 7/10 位从地址;若已有 带驱动 的正式 client 占址 → -EBUSY
I2C_SLAVE_FORCE (0x0706) 强制设置地址,不检查 是否已有驱动
I2C_TENBIT (0x0704) 启用 10 位地址(UAPI 注释称历史代码对 10 位支持不完整)
I2C_FUNCS (0x0705) 返回 i2c_get_functionality(adapter) 功能掩码
I2C_RDWR (0x0707) 提交 i2c_rdwr_ioctl_data(最多 42 条 msg)
I2C_PEC (0x0708) 匿名 client 的 PEC 标志(不影响 已注册内核 client)
I2C_SMBUS (0x0720) i2c_smbus_ioctl_datai2c_smbus_xfer
I2C_RETRIES (0x0701) 修改 adapter->retries(影响整条总线)
I2C_TIMEOUT (0x0702) 修改 adapter->timeout(单位 10ms,转 jiffies)

7. I2C_RDWRi2cdev_ioctl_rdwr()

7.1 流程

  1. 从用户空间复制 struct i2c_msg 数组(memdup_array_user);
  2. 每条消息:len ≤ 8192bufmemdup_user 拷入内核,置 I2C_M_DMA_SAFE
  3. I2C_M_RECV_LEN:校验读方向与 buf[0] 长度,并设置 msgs[i].len = buf[0]
  4. i2c_transfer(client->adapter, msgs, nmsgs)注意:使用 adapter,msg 内自带 addr不依赖 I2C_SLAVE 设置的 client->addr
  5. 读消息:copy_to_user 回传数据;释放内核缓冲区。

7.2 与 I2C_SLAVE 的关系

注释说明:打开后即可用 I2C_RDWR 向任意地址发消息(地址在每条 i2c_msg 中)。read/writeI2C_SMBUS 则需要先 I2C_SLAVE

7.3 RK3588

最终进入 rk3x_i2c_xfer(),支持组合读写(与 i2c_transfer 路径一致)。


8. I2C_SMBUSi2cdev_ioctl_smbus()

  • 校验 sizeread_write 合法;
  • Quick / Send Byte 等无 data 指针情况直接 i2c_smbus_xfer(..., NULL)
  • 其它从用户空间复制 union i2c_smbus_data,调用 i2c_smbus_xfer,读操作再 copy_to_user
  • 支持 I2C_SMBUS_I2C_BLOCK_BROKEN 兼容旧 ABI。

用户态 i2c_smbus_* 库函数通常封装此 ioctl。RK3588 上经 i2c_smbus_xfer_emulated 模拟(见 i2c-core-smbus.c 文档)。


9. 地址占用检查:i2cdev_check_addr()

i2c-core-base.ci2c_check_addr_busy() 的差异(文件注释):

检查逻辑 i2c-core i2c-dev
已有 client + 已绑定 driver 忙(I2C_SLAVE 失败)
已有 client + 无 driver 不忙(允许用户态访问)

递归检查 MUX 父子 adapter 上的同名地址(i2cdev_check_mux_parents / _children)。

I2C_SLAVE_FORCE 跳过 BUSY 检查,可能导致与内核驱动 同时访问同一从设备,仅调试时使用。


10. 与内核 I2C 子系统其它部分的关系

组件 关系
i2c-core-base.c i2c_transferi2c_get_adapteri2c_master_send/recv
i2c-core-smbus.c i2c_smbus_xfer
i2c-core-of.c DT 创建的正式 client;与 i2c-dev 匿名 client 独立
i2c-rk3x.c 实际硬件传输
adapter sysfs new_device 另一条 用户态创建设备路径(创建 正式 client)

生产环境建议: 正常外设由 内核驱动 + DT 管理;/dev/i2c-* 主要用于 调试、校准、产测、无驱动芯片的临时访问


11. RK3588 使用说明

11.1 配置

1
2
3
CONFIG_I2C_CHARDEV=y
CONFIG_I2C=y
CONFIG_I2C_RK3X=y

11.2 用户空间示例

1
2
3
4
5
6
7
8
9
10
# 列出总线
ls -l /dev/i2c-*

# i2c-tools(需交叉编译或板端安装)
i2cdetect -y 2
i2cget -y 2 0x51 0x00
i2cset -y 2 0x51 0x00 0xab

# 查看 adapter 名称
cat /sys/class/i2c-dev/i2c-2/name

总线号 N 与 DTS aliases { i2cN = &i2cN; }i2c_add_numbered_adapteradap->nr 一致。

11.3 与板级外设共存

例如 rk3588s-tablet.dtsi&i2c2 上的 hym8563@51 已被 hym8563 驱动 占用时:

  • ioctl(I2C_SLAVE, 0x51)-EBUSY
  • I2C_SLAVE_FORCE 可强制访问,但可能与 RTC 驱动冲突,导致系统异常。

11.4 权限

设备节点通常属 root:i2cchmod 666 或由 udev 规则赋权;嵌入式系统常在 init 脚本中 chmod 以便应用访问。


12. 安全与限制

限制 说明
单次传输 read/write/ioctl 单条 msg ≤ 8192 字节
I2C_RDWR 条数 ≤ 42I2C_RDWR_IOCTL_MAX_MSGS
次设备号 adap->nr < I2C_MINORS(通常 256)
10 位地址 UAPI 注明历史实现问题,慎用
修改 retries/timeout 影响 整条 adapter,非单文件描述符私有

用户态对 /dev/i2c-* 的访问等价于 root 级硬件控制,产线工具需防止误写 PMIC 等关键器件。


13. 模块初始化与卸载

1
2
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
  • =y 内置=m 模块 i2c-dev.ko
  • 卸载模块会注销 notifier 并删除所有 i2c-dev 字符设备(需无打开的文件描述符)。

14. 源码结构速查(约 782 行)

1
2
3
4
5
6
7
8
9
10
11
L42-96    i2c_dev 链表管理
L98-113 sysfs name 属性
L134-178 read / write
L180-233 地址检查(含 mux)
L235-309 i2cdev_ioctl_rdwr
L311-390 i2cdev_ioctl_smbus
L392-496 i2cdev_ioctl
L498-583 CONFIG_COMPAT
L585-635 open / release / file_operations
L649-724 attach/detach + bus notifier
L732-774 init / exit

15. 结论

i2c-dev.c 实现 I2C 子系统的 用户空间字符设备接口

  1. 每条 i2c_adapter 对应 /dev/i2c-N(主设备号 89);
  2. 通过 匿名 client 提供 I2C_SLAVE + read/writeI2C_RDWR / I2C_SMBUS
  3. 底层统一落到 i2c_transfer / i2c_smbus_xfer,RK3588 上即 rk3x 硬件驱动
  4. DT 内核驱动 并行,地址冲突时 I2C_SLAVE 保护已绑定设备;
  5. Rockchip 产品内核 默认启用 CONFIG_I2C_CHARDEV,调试与工具链普遍依赖本模块。

理解本文件有助于分析 用户态能否访问某 I2C 地址i2cdetect 与内核驱动冲突、以及 产测程序直接读写总线 的实现路径。


16. 推荐阅读顺序

  1. i2c-dev.ci2cdev_ioctli2cdev_open
  2. include/uapi/linux/i2c-dev.h — ioctl 编号与结构体
  3. Documentation/i2c/dev-interface.rst — 官方用户接口说明
  4. i2c-core-base.ci2c_transferi2c_get_adapter
  5. i2c-core-smbus.c — SMBus ioctl 底层

17. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-dev.c 分析。

文章互动

阅读 --

留言

0 条留言

正在加载留言…