
[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 事务”能力开放给用户态,便于通用工具工作。
重要迭代特征(从当前设计可推断的演进方向)
社区活跃度与主流应用
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 无法替你做“协议级互斥”:如果用户态和内核驱动同时访问同一从设备地址,哪怕总线事务串行,设备寄存器状态也可能被打乱(例如:驱动在做多步状态机,你插入一次读写就会破坏它的假设)。
使用场景
首选场景
- 板级 bring-up / 产测 / 工装
- 扫描设备地址是否响应、读芯片 ID、做简单寄存器 poke。
- 调试内核驱动
- 用用户态快速验证“寄存器值是否符合预期”,定位是硬件问题、时序问题还是驱动逻辑问题。
- 临时脚本化控制
- 例如在系统早期阶段通过脚本调整 PMIC、mux、GPIO expander 的寄存器(但长期方案仍建议内核驱动化)。
不推荐使用的场景(原因)
- 量产系统长期依赖用户态直接访问同一设备
- 会绕过内核驱动的电源管理、时序约束、错误恢复、互斥保护。
- 高频/低延迟数据采集
- 用户态
ioctl + copy 开销明显,且每次事务都有用户态/内核态切换。
- 已经有内核驱动绑定的设备还强行
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) { i2cdev_attach_adapter(dev); return 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) { i2cdev_detach_adapter(dev); return 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");
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); if (res) goto out;
res = class_register(&i2c_dev_class); if (res) goto out_unreg_chrdev;
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); if (res) goto out_unreg_class;
i2c_for_each_dev(NULL, i2c_dev_attach_adapter);
return 0;
out_unreg_class: class_unregister(&i2c_dev_class); 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) { bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier);
i2c_for_each_dev(NULL, i2c_dev_detach_adapter);
class_unregister(&i2c_dev_class);
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
| MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"); MODULE_AUTHOR("Simon G. Vogl <simon@tk.uni-linz.ac.at>");
MODULE_DESCRIPTION("I2C /dev entries driver");
MODULE_LICENSE("GPL");
module_init(i2c_dev_init);
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 model:
struct 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", .dev_groups = i2c_groups, };
|
i2cdev_dev_release:device 对象最终释放时回收 i2c_dev 容器内存
1 2 3 4 5 6 7 8 9 10 11 12 13
|
static void i2cdev_dev_release(struct device *dev) { struct i2c_dev *i2c_dev;
i2c_dev = container_of(dev, struct i2c_dev, 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
|
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; adap = to_i2c_adapter(dev);
i2c_dev = get_free_i2c_dev(adap); if (IS_ERR(i2c_dev)) return NOTIFY_DONE;
cdev_init(&i2c_dev->cdev, &i2cdev_fops); i2c_dev->cdev.owner = THIS_MODULE;
device_initialize(&i2c_dev->dev); i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr); i2c_dev->dev.class = &i2c_dev_class; i2c_dev->dev.parent = &adap->dev; i2c_dev->dev.release = i2cdev_dev_release;
res = dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr); if (res) goto err_put_i2c_dev;
res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev); 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
|
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); if (!i2c_dev) return NOTIFY_DONE;
put_i2c_dev(i2c_dev, true);
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
|
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { struct device *dev = data;
switch (action) { case BUS_NOTIFY_ADD_DEVICE: return i2cdev_attach_adapter(dev); case BUS_NOTIFY_DEL_DEVICE: return i2cdev_detach_adapter(dev); }
return NOTIFY_DONE; }
|
i2cdev_notifier:notifier 节点本体
1 2 3
| static struct notifier_block i2cdev_notifier = { .notifier_call = i2cdev_notifier_call, };
|
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
|
struct i2c_dev { struct list_head list; struct i2c_adapter *adap; struct device dev; struct cdev cdev; };
#define I2C_MINORS (MINORMASK + 1)
static LIST_HEAD(i2c_dev_list); static DEFINE_SPINLOCK(i2c_dev_list_lock);
|
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
|
static struct i2c_dev *i2c_dev_get_by_minor(unsigned index) { struct i2c_dev *i2c_dev;
spin_lock(&i2c_dev_list_lock); list_for_each_entry(i2c_dev, &i2c_dev_list, list) { if (i2c_dev->adap->nr == index) goto found; } i2c_dev = 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
|
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); }
i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL); if (!i2c_dev) return ERR_PTR(-ENOMEM);
i2c_dev->adap = adap;
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
|
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);
put_device(&i2c_dev->dev); }
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
|
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;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -EOPNOTSUPP;
if (count > 8192) count = 8192;
tmp = kzalloc(count, GFP_KERNEL); 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); if (ret >= 0) if (copy_to_user(buf, tmp, ret)) 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
|
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;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -EOPNOTSUPP;
if (count > 8192) count = 8192;
tmp = memdup_user(buf, count); 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); 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
|
static int i2cdev_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); 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; file->private_data = client;
return 0; }
|
i2cdev_release:关闭文件描述符并释放匿名 i2c_client 与适配器引用
1 2 3 4 5 6 7 8 9 10 11 12 13
|
static int i2cdev_release(struct inode *inode, struct file *file) { struct i2c_client *client = file->private_data;
i2c_put_adapter(client->adapter); kfree(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
|
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct i2c_client *client = file->private_data; 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: if ((arg > 0x3ff) || (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f)) return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg)) return -EBUSY;
client->addr = arg; return 0;
case I2C_TENBIT: if (arg) client->flags |= I2C_M_TEN; else client->flags &= ~I2C_M_TEN; return 0;
case I2C_PEC:
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)); if (IS_ERR(rdwr_pa)) return PTR_ERR(rdwr_pa);
res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa); 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); break;
default: return -ENOTTY; }
return 0; }
|
i2cdev_fops:把 VFS 入口绑定到 i2c-dev 的实现
1 2 3 4 5 6 7 8 9 10 11 12
|
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
|
static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client, unsigned nmsgs, struct i2c_msg *msgs) { u8 __user **data_ptrs; int i, res;
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;
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]; } }
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
|
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 = {}; int datasize, res;
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; }
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; }
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);
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);
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; }
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);
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
|
static int i2cdev_check(struct device *dev, void *addrp) { struct i2c_client *client = i2c_verify_client(dev);
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
|
static int i2cdev_check_mux_parents(struct i2c_adapter *adapter, int addr) { struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter); 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
|
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); else result = i2cdev_check(dev, addrp);
return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
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);
return result; }
|