Linux驱动-在用户态控制I2C-两种方案ioctl和文件控制
提示Linux驱动-在用户态控制I2C文章目录前言一、参考资料二、关联代码关联源码关联源码解析核心定位核心数据结构struct i2c_dev结构体open 时创建匿名 struct i2c_client文件操作接口 i2cdev_fops重中之重i2cdev_write ()i2cdev_read ()ioctl 核心函数 i2cdev_ioctl()分支 1I2C_SLAVE / I2C_SLAVE_FORCE分支 2I2C_RDWR推荐方案入口分支 3I2C_SMBUS设备初始化与生命周期1.模块入口 i2c_dev_init2. 总线通知器 i2cdev_notifier_call3. open /release 流程两条用户态 API 路径完整调用链路对比路径 Aread () /write () 方案I2C_SLAVE路径 Bioctl (I2C_RDWR) 方案推荐三、Client 端控制代码ioctl 实现i2c 控制文件方式read/write 方式实现i2c 控制两种client端控制i2c 区别总结前言前面知识点都是基于驱动程序知识点下面我们看看用户态 client 端如何通过i2c 接口实现i2c 驱动控制。这里讲解两种控制方案ioctl 和 文件控制 方案。一、参考资料这里侧重于之前的笔记知识点i2c 基本知识点前面知识点基础一定要掌握Linux驱动-i2c 驱动框架编写Linux驱动-IIC完善FT5X06设备节点和驱动Linux驱动-I2C通信-FT5X06驱动程序部分编写Linux驱动-通过逻辑分析仪-认识-分析IIC波形Linux 驱动-I2C输入子系统相关知识点Linux驱动-IIC通信-FT5X06驱动-数据上报案例-终端测试RK3568驱动指南第十五篇 I2C-第179章在应用程序中使用I2Cioctl 知识点驱动-传参实验-ioctl驱动-地址传参实验-ioctl二、关联代码关联源码路径/kernel/drivers/i2c/i2c-dev.c关联源码解析核心定位i2c-dev.c是 Linux 内核提供的【用户态 I2C 适配器驱动】作用暴露/dev/i2c-X字符设备允许应用层程序直接操作硬件 I2C 总线不需要编写内核驱动。它不是 I2C 硬件控制器驱动只是上层封装最终所有传输都会调用 I2C 核心i2c_transfer()。核心数据结构struct i2c_dev结构体struct i2c_dev{struct list_head list;struct i2c_adapter*adap;// 指向硬件I2C适配器I2C控制器struct device dev;struct cdev cdev;// 字符设备核心结构绑定file_operations};每一条硬件 I2C 总线/dev/i2c-0//dev/i2c-1对应一个i2c_devadap底层硬件控制器RK3568/MTK 的 I2C 硬件驱动最终操作这个 adapteropen 时创建匿名struct i2c_clientstaticinti2cdev_open(struct inode*inode,struct file*file){// ...clientkzalloc(sizeof(*client),GFP_KERNEL);client-adapteradap;file-private_dataclient;}打开/dev/i2c-x时内核分配一个匿名 i2c_client。这个 client不会注册到内核设备树驱动模型仅仅用来保存从机地址client-addr由I2C_SLAVEioctl 设置标志位10bit 地址、PEC 校验等如果你使用read()/write()这套方案必须先调用ioctl(I2C_SLAVE_FORCE, addr)填充 client-addr否则读写直接报错。I2C_RDWR不需要依赖这个 client 内部 addr因为每条i2c_msg自带 addr。文件操作接口i2cdev_fops重中之重staticconststruct file_operations i2cdev_fops{.ownerTHIS_MODULE,.llseekno_llseek,.readi2cdev_read,// 用户态 read(fd, buf, len).writei2cdev_write,// 用户态 write(fd, buf, len).unlocked_ioctli2cdev_ioctl,// 用户态 ioctl().compat_ioctlcompat_i2cdev_ioctl,// 32位兼容.openi2cdev_open,.releasei2cdev_release,};i2cdev_write ()staticssize_ti2cdev_write(struct file*file,constchar__user*buf,size_t count,loff_t*offset){struct i2c_client*clientfile-private_data;// 1. 用户空间buf拷贝到内核空间 tmptmpmemdup_user(buf,count);// 2. 调用 i2c_master_sendreti2c_master_send(client,tmp,count);kfree(tmp);returnret;}i2c_master_send内部行为自动组装单条struct i2c_msg写标志调用i2c_transfer()时序START 数据 STOP每次 write 都是独立 I2C 事务首尾自动加 START/STOPi2cdev_read ()staticssize_ti2cdev_read(struct file*file,char__user*buf,size_t count,loff_t*offset){struct i2c_client*clientfile-private_data;reti2c_master_recv(client,tmp,count);// 内核数据拷贝回用户空间copy_to_user(buf,tmp,ret);}i2c_master_recv自动组装单条带I2C_M_RD的 i2c_msg时序START 读地址 接收数据 STOP致命限制// 向寄存器 0x80 写入数据 0x66ft5x06_write_reg(fd,0x80,0x66);// 读取寄存器 0x80 的值ft5x06_read_reg(fd,0x80);内核执行两次独立事务writeSTART reg STOPreadSTART READ STOP中间出现 STOP没有 RESTART绝大多数寄存器芯片FT5x06收到 STOP 后丢弃寄存器地址读取失败。根源就在read/write 各自封装独立一次 i2c_transfer无法合并多条 msg。ioctl 核心函数i2cdev_ioctl()所有ioctl(fd, cmd, arg)进入这个函数通过 cmd 分支分发。分支 1I2C_SLAVE / I2C_SLAVE_FORCEcaseI2C_SLAVE:caseI2C_SLAVE_FORCE:client-addrarg;// 把从机地址存入匿名clientreturn0;I2C_SLAVE如果内核已经有驱动占用该从地址 → 返回-EBUSYI2C_SLAVE_FORCE强制覆盖地址无视内核驱动占用⚠️ 风险用户态和内核驱动同时访问同一个从机总线冲突、数据错乱分支 2I2C_RDWR推荐方案入口caseI2C_RDWR:{struct i2c_rdwr_ioctl_data rdwr_arg;// 1. 把用户态 struct i2c_rdwr_ioctl_data 拷贝进内核copy_from_user(rdwr_arg,...);// 2. 拷贝用户态 i2c_msg 数组到内核rdwr_pamemdup_user(rdwr_arg.msgs,rdwr_arg.nmsgs*sizeof(struct i2c_msg));// 3. 调用核心处理函数returni2cdev_ioctl_rdwr(client,rdwr_arg.nmsgs,rdwr_pa);}重点i2cdev_ioctl_rdwr()一次性传入多条 i2c_msg 数组内核 I2C 核心规则同一组多条连续 i2c_msg只发送一次 START消息之间使用RESTART无 STOP全部执行完成后发送一次 STOP。✅ 刚好满足 FT5x06 寄存器读时序msg0写寄存器地址 → RESTART → msg1读数据这就是为什么I2C_RDWR可以正常读取触控芯片寄存器分支 3I2C_SMBUS提供 SMBus 简化接口兼容 SMBus 协议设备普通 I2C 寄存器外设很少使用。设备初始化与生命周期1.模块入口i2c_dev_init注册字符设备主设备号I2C_MAJOR注册总线通知器监听 I2C 适配器的插拔遍历所有已经存在的 I2C 适配器自动创建/dev/i2c-X2. 总线通知器i2cdev_notifier_call新增 I2C 适配器 →i2cdev_attach_adapter()→ 创建/dev/i2c-x卸载 I2C 适配器 →i2cdev_detach_adapter()→ 删除设备节点3. open /release 流程open(/dev/i2c-1)→i2cdev_open()→ 获取i2c_adapter → 创建匿名i2c_client存入file-private_dataclose(fd)→i2cdev_release()→ 释放i2c_client内存 → 释放adapter引用计数两条用户态 API 路径完整调用链路对比路径 Aread () /write () 方案I2C_SLAVEappwrite()→ sys_write →i2cdev_write()→i2c_master_send()→i2c_transfer()【单条msg】 appread()→ sys_read →i2cdev_read()→i2c_master_recv()→i2c_transfer()【单条msg】缺陷两次系统调用 两次独立i2c_transfer中间 STOP无法 RESTART。路径 Bioctl (I2C_RDWR) 方案推荐appioctl(fd,I2C_RDWR,data)→ sys_ioctl →i2cdev_ioctl()→i2cdev_ioctl_rdwr()→i2c_transfer(...,nmsgs条消息)优势一次系统调用多条 msg 交给内核连续执行支持 RESTART 时序。三、Client 端控制代码ioctl 实现i2c 控制#includestdio.h#includelinux/i2c.h#includelinux/i2c-dev.h#includesys/ioctl.h#includesys/types.h#includesys/stat.h#includefcntl.h#includestring.h/** * brief 从 I2C 设备的寄存器中读取数据 * param fd 打开的 I2C 设备文件描述符 * param slave_addr I2C 设备的从机地址 * param reg_addr 要读取的寄存器地址 * return 寄存器的值 */intft5x06_read_reg(intfd,unsignedcharslave_addr,unsignedcharreg_addr){unsignedchardata;struct i2c_rdwr_ioctl_data i2c_msgs;intret;// 定义两个 i2c_msg 结构体, 第一个用于写入寄存器地址, 第二个用于读取数据struct i2c_msg dev_msgs[]{[0]{.addrslave_addr,.flags0,.lensizeof(reg_addr),.bufreg_addr,},[1]{.addrslave_addr,.flagsI2C_M_RD,// 设置读取标志.lensizeof(data),.bufdata,}};i2c_msgs.msgsdev_msgs;i2c_msgs.nmsgs2;// 两个 i2c_msg 结构体// 调用 ioctl 函数执行读取操作retioctl(fd,I2C_RDWR,i2c_msgs);if(ret0){printf(read error\n);returnret;}returndata;}/** * brief 向 I2C 设备的寄存器写入数据 * param fd 打开的 I2C 设备文件描述符 * param slave_addr I2C 设备的从机地址 * param reg_addr 要写入的寄存器地址 * param data 要写入的数据 * param len 数据长度 */voidft5x06_write_reg(intfd,unsignedcharslave_addr,unsignedcharreg_addr,unsignedchar*data,intlen){unsignedcharbuff[256];struct i2c_rdwr_ioctl_data i2c_msgs;intret;// 定义一个 i2c_msg 结构体, 用于写入寄存器地址和数据struct i2c_msg dev_msgs[]{[0]{.addrslave_addr,.flags0,.lenlen1,.bufbuff,}};// 将寄存器地址和数据拷贝到 buff 数组中buff[0]reg_addr;memcpy(buff[1],data,len);i2c_msgs.msgsdev_msgs;i2c_msgs.nmsgs1;// 调用 ioctl 函数执行写入操作retioctl(fd,I2C_RDWR,i2c_msgs);if(ret0){printf(write error\n);}}intmain(intargc,char*argv[]){intfd;intID_G_THGROUP;// 打开 I2C 设备文件fdopen(/dev/i2c-1,O_RDWR);if(fd0){printf(open error\n);returnfd;}// 向 0x38 地址的寄存器 0x80 写入 0x55unsignedchardata0x55;ft5x06_write_reg(fd,0x38,0x80,data,1);// 从 0x38 地址的寄存器 0x80 读取数据ID_G_THGROUPft5x06_read_reg(fd,0x38,0x80);printf(ID_G_THGROUP is 0x%02X\n,ID_G_THGROUP);return0;}文件方式read/write 方式实现i2c 控制#includestdio.h#includesys/ioctl.h#includesys/types.h#includesys/stat.h#includefcntl.h#includestring.h#includelinux/i2c.h#includelinux/i2c-dev.h#includeunistd.h/* * 从 I2C 设备读取寄存器值 * param fd: I2C 设备文件句柄 * param reg_addr: 要读取的寄存器地址 */voidft5x06_read_reg(intfd,unsignedcharreg_addr){unsignedcharrd_data[1];rd_data[0]reg_addr;write(fd,rd_data,1);read(fd,rd_data,1);printf(reg value is %x\n,rd_data[0]);}/* * 向 I2C 设备写入寄存器值 * param fd: I2C 设备文件句柄 * param reg_addr: 要写入的寄存器地址 * param data: 要写入的数据 */voidft5x06_write_reg(intfd,unsignedcharreg_addr,unsignedchardata){unsignedcharwr_data[2];wr_data[0]reg_addr;wr_data[1]data;write(fd,wr_data,2);}intmain(intargc,char*argv[]){intfd;// 打开 I2C 设备文件fdopen(/dev/i2c-1,O_RDWR);if(fd0){printf(open error\n);returnfd;}// 设置从设备地址为 0x38ioctl(fd,I2C_SLAVE_FORCE,0x38);// 向寄存器 0x80 写入数据 0x66ft5x06_write_reg(fd,0x80,0x66);// 读取寄存器 0x80 的值ft5x06_read_reg(fd,0x80);return0;}备注并不是所有的IC 都支持这种方式所以对于主控也是默认关闭的那么自己要检查一下是否打开了这种通讯方式。当然对于从设备的i2c 的IC 可能都不支持实战中如果发现不支持要核对下时是否真的支持不然茫然。两种client端控制i2c 区别对比项I2C_RDWR (方案 1)I2C_SLAVE read/write (方案 2)系统调用入口ioctl(I2C_RDWR)open → ioctl(I2C_SLAVE) → read/write事务控制一次 ioctl 执行多条 msg支持 RESTART每次 read/write 独立事务强制 STOP从机地址每条 i2c_msg 独立指定支持多从机整个 fd 绑定单一地址切换需要重设 ioctl标准寄存器读时序完美支持写 RESTART 读时序断裂大部分芯片读取失败代码复杂度较高很低和内核驱动i2c_transfer行为高度一致上层封装内核自动拆分事务总结这里给出了两种client 端控制i2c 外设的方式。 ioctl 和 文件控制方式。多琢磨不同方案的不同、优劣自己思考。

相关新闻

3分钟掌握:如何高效获取国家中小学智慧教育平台电子课本PDF文件

3分钟掌握:如何高效获取国家中小学智慧教育平台电子课本PDF文件

3分钟掌握:如何高效获取国家中小学智慧教育平台电子课本PDF文件 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具,帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载,让您更方便地获取课本内容。 …

2026/10/15 7:11:39 阅读更多 →
从理论到实践:Geotorch约束优化的数学原理与代码实现

从理论到实践:Geotorch约束优化的数学原理与代码实现

从理论到实践:Geotorch约束优化的数学原理与代码实现 【免费下载链接】geotorch Constrained optimization toolkit for PyTorch 项目地址: https://gitcode.com/gh_mirrors/ge/geotorch Geotorch是一个专为PyTorch设计的约束优化工具包,它提供了…

2026/10/13 2:03:10 阅读更多 →
AI学情分析报告生成逻辑全拆解(从原始行为日志到个性化干预建议的7层推理链)

AI学情分析报告生成逻辑全拆解(从原始行为日志到个性化干预建议的7层推理链)

更多请点击: https://intelliparadigm.com 第一章:AI学情分析报告生成逻辑全拆解(从原始行为日志到个性化干预建议的7层推理链) AI学情分析报告并非简单统计点击与停留时长,而是依托七层递进式语义推理引擎&#xff0…

2026/10/11 10:28:19 阅读更多 →

最新新闻

复现膀胱癌机器学习顶刊论文:影像组学、多组学与病理切片实战

复现膀胱癌机器学习顶刊论文:影像组学、多组学与病理切片实战

前阵子集中干了件事:把三篇顶刊上关于膀胱癌与机器学习的论文端到端复现了一遍。说是“复现”,其实不光是跑通代码,而是从公开数据源把原始数据拉下来,按论文的方法描述重新做特征提取、建模、评估,最后跟论文报告的数…

2026/10/15 16:08:28 阅读更多 →
ELMo与BERT在FAQ问答机器人中的语义匹配实战对比

ELMo与BERT在FAQ问答机器人中的语义匹配实战对比

1. 项目起底:FAQ问答机器人为什么需要ELMo和BERT1.1 先看需求:FAQ问答机器人到底在解决什么问题先从一个最常见的场景说起。无论是电商客服、银行客服,还是企业内部IT支持,海量重复问题占据了大部分咨询量。"我的订单什么时候…

2026/10/15 16:08:28 阅读更多 →
Legion Zone卸载后驱动残留怎么办?拯救者清理与恢复全指南

Legion Zone卸载后驱动残留怎么办?拯救者清理与恢复全指南

很多拯救者用户跟我吐槽过同一个问题:联想 Legion Zone 卸载之后,系统里还会残留一堆用不上的驱动组件,看着像垃圾驱动,删又不敢乱删。今天这篇就把这件事彻底讲清楚——Legion Zone 到底装了哪些驱动、哪些能删、哪些千万别碰&am…

2026/10/15 16:08:28 阅读更多 →
DeepSeek与OpenAI技术路径差异及工程选型指南

DeepSeek与OpenAI技术路径差异及工程选型指南

简介:本资源是一份聚焦人工智能前沿竞争格局的深度对比分析报告,面向AI研究人员、技术决策者及对大模型发展路径感兴趣的从业者,旨在厘清DeepSeek与OpenAI在技术路线、应用落地与商业策略上的关键差异。全文以详实数据支撑观点,涵…

2026/10/15 16:08:28 阅读更多 →
核线影像纠正原理与C++高精度实现:从基础矩阵到亚像素重采样

核线影像纠正原理与C++高精度实现:从基础矩阵到亚像素重采样

简介:本资源是一套完整的核线影像纠正C实现程序,面向遥感图像处理方向的开发者、GIS工程师及高校测绘/地信专业高年级学生,用于解决卫星或航空影像因地球曲率与传感器姿态导致的几何失真问题,支撑立体匹配、数字高程模型生成等关键…

2026/10/15 16:08:28 阅读更多 →
梯度下降法MATLAB实现:从代价曲线到参数调优全解析

梯度下降法MATLAB实现:从代价曲线到参数调优全解析

简介:梯度下降法是机器学习与优化问题中最常用的数值求解方法之一,对于需要实现线性回归、逻辑回归或神经网络权重更新的MATLAB用户,这份程序提供了直接可用的代码参考。资源仅包含1个m文件,压缩后大小2KB,轻量小巧&am…

2026/10/15 16:07:27 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →