多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

i2c-hid standalone驱动包:触摸屏调试的独立编译与排障实战

i2c-hid standalone驱动包:触摸屏调试的独立编译与排障实战 简介针对联想拯救者R7000笔记本在Linux系统下触摸板无法正常识别、点击漂移等问题这份补丁包以i2c-hid驱动独立源码形式提供修复方案。用户只需安装对应Linux内核头文件执行make即可生成i2c-hid.ko模块替换系统原有驱动并启用轮询模式即可恢复触摸板功能。适合具备基础编译操作、熟悉终端命令的中级Linux用户尤其适合使用Manjaro等发行版的笔记本玩家。压缩包共5个文件包含2个C源码、2个头文件和1个Makefile构建脚本整体仅24KB体积小巧结构清晰便于直接审阅和二次编译。目前已有1466人学习浏览方案经过社区验证。通过阅读C源码中的dmi quirks与核心处理逻辑可深入理解i2c-hid驱动在异常硬件上的适配方式同时也能掌握内核模块替换、depmod更新依赖及grub引导参数调整的完整排错思路对排查其他Linux外设问题同样有参考价值。1. 先搞懂 i2c-hid_standalone.zip它是触摸屏驱动调试里最值得留一份的“独立包”做触摸屏 bring-up 的工程师多半都经历过这种循环拿到一块新屏改设备树编内核烧镜像重启看一眼 dmesg再回去改。一次轮回半小时起步如果再来回几次一上午就没了。i2c-hid_standalone.zip 要解决的就是这个——把 i2c-hid 驱动从内核源码树里单独拎出来配一个外部模块 Makefile、若干设备树片段和加载脚本让你在 x86_64 主机上交叉编译出一个 .ko直接搬上板子 insmod。所谓 standalone和常见的 standalone installer 一个意思拿到就是完整可运行的一套不依赖全量内核源码树。这类包适合两类人。一类是做触摸屏、触摸板或指纹模组选型和验证的工程师需要在几分钟里确认某个模组到底能不能跑起来另一类是内核版本被 BSP 锁死、不想为了一个驱动重烧整个 boot 镜像的嵌入式软件工程师。要注意的是它不是万能药驱动依赖的 CONFIG_HID、CONFIG_I2C 还是得在内核里开着它只是把验证周期从半小时压缩到几分钟把“编整个内核”这个黑匣子拆开让你只碰需要碰的那几个变量。2. i2c-hid 驱动为什么能 standalone拆开 probe 链路看包里真正需要的是什么2.1 i2c-hid 的定位是“协议驱动”不是某款触摸屏的专用驱动先澄清一个容易误导人的点i2c-hid 不关心你的触摸屏是汇顶、新思还是义隆。它实现的是 HID over I2C 协议这是一套标准的寄存器读写规范。设备侧暴露一组寄存器驱动侧只做标准动作——上电、复位、读 HID Descriptor、传输 Input/Output Report。真正区分设备型号的逻辑在固件的 report descriptor 里内核这边只负责按协议搬运不负责理解业务。这颗驱动在内核源码里的位置是 drivers/hid/i2c-hid/从 4.x 一路到 6.x 都有只是内部结构做过拆分。老版本可能就一个 i2c-hid.c 文件新版本拆成 i2c-hid-core.c、i2c-hid-acpi.c、i2c-hid-of.c或者 i2c-hid-dt.c几个文件。无论怎么拆probe 做的事情是一样的I2C 子系统根据设备树或 ACPI 表生成一个 i2c_client驱动匹配上之后先向设备发 RESET 命令等待设备就绪然后读取 HID Descriptor拿到 report descriptor 长度和版本号再把整个设备注册进 HID core最后由 HID core 解析成 input 设备。换句话说这套链路只跟 I2C 子系统、HID 子系统、Input 子系统打交道和具体 SoC 的 pinctrl、clk、电源域没有强耦合。这就是它能被抽出来做独立编译的根本原因。只要目标内核里这三块是打开的并且编译时的内核版本和运行版本一致模块就能在板上正常加载不需要重新烧写整个内核镜像。2.2 standalone 与全量内核编译的差别少了 rebuild 周期多了两个风险全量编译的路径是改 dts 或 defconfig进入内核源码根目录make ARCHarm64生成 boot.img 或 kernel 镜像烧写分区重启验证。这套流程最大的问题是周期长而且每次烧写都有把板子变成砖头的风险。standalone 路径短得多用目标内核的 build 目录做 KDIR在主机上外部编译出 i2c-hid.koscp 到板子上insmod 或 modprobe看 dmesg。一次轮回三分钟以内。代价有两个。第一个是 vermagic 必须严格一致。全量编译时你烧的镜像就是当前环境模块和内核天然配套standalone 如果 KDIR 指错了内核版本insmod 会直接报 Invalid module format。第二个是外部依赖的符号必须被内核导出。如果驱动里用到的某个函数在这个版本里是 static或者没有 EXPORT_SYMBOL加载时会报 Unknown symbol这类问题基本只能换内核版本没有好的绕路办法。所以 standalone 包里的 Makefile 通常不会让你随便指向一个版本而是会强调“先准备好目标板同版本的 build 目录”。这也是为什么我拿到任何 standalone 包第一件事不是解压而是先确认版本匹配这一条放在下一章讲它值得排在所有命令前面。2.3 standalone 包里通常会有的几类文件一份文件职责对照我经手过的 standalone 包没有完全统一的规格但骨架都差不多一般逃不出下面这几样。它可能叫 standalone也可能只是个散装目录本质是一回事。文件/目录典型职责常见的翻车点i2c-hid 源码目录驱动本体取自内核 drivers/hid/i2c-hid/源码版本与目标内核不一致外部模块 Makefile指定 obj-m 与 KDIR 变量KDIR 指向作者电脑路径build.sh封装 ARCH、CROSS_COMPILE、KDIR 的编译入口交叉工具链前缀写错dts/overlay 片段描述 I2C 地址、中断、reset 时序中断类型与 hid-descr-addr 配错insmod/rmmod 脚本按顺序加载依赖模块忘记 depmodmodprobe 找不到依赖README/说明写清目标内核版本与支持平台文字描述与实际源码版本漂移注意我并不建议把这当成一个固定标准。很多人做 standalone 包就是简单粗暴地从内核源码里拷出 i2c-hid 目录再加一个自己写的 Makefile 就算完事文件数量和目录布局怎么舒服怎么来。你要做的是拿到包之后先看明白它从哪里来、对应哪个内核版本再动手改东西。3. 在 x86_64 主机上交叉编译 i2c-hid_standalone从解压到打出 .ko 的完整命令3.1 开工前先确认三件事目标内核版本、Kconfig、交叉工具链下面这套命令以 ARM64 板子为例x86、ARM、RISC-V 的区别只在 ARCH 和 CROSS_COMPILE 两个变量。第一步不是解压而是在目标板上确认三件事。# 在目标板上执行记录完整版本号 uname -r # 检查 i2c-hid 依赖的内核配置有没有打开 zcat /proc/config.gz | grep -E ^CONFIG_(I2C|HID|INPUT)代码里的 zcat 需要内核开启了 CONFIG_IKCONFIG 才会存在 /proc/config.gz。如果这个文件不存在去 /boot/config-$(uname -r) 下面找如果也没有只能去 BSP 提供的 defconfig 里人工确认。CONFIG_I2C、CONFIG_HID、CONFIG_INPUT 这三项只要不是 n 就行y 或 m 都可以i2c-hid 作为外部模块加载不要求它本身必须是 m。拿到 uname -r 的输出之后去准备一份同版本的完整内核源码树。常见做法是直接找 BSP 的 kernel 目录或者从内核官网下载对应 tag 的 tar 包。这个源码目录后续会作为 KDIR 使用前提是它已经在主机上 make 过一次生成了 .config、Module.symvers 和 include/generated/utsrelease.h。提示没有 make 过的内核源码是不能直接当地外部模块 KDIR 用的第一次编外部模块会报缺少 generated/utsrelease.h解决办法是先进入该源码目录执行 make ARCHarm64 defconfig 或你常用的配置文件再执行 make ARCHarm64 prepare scripts。3.2 解压后先看 MakefileKDIR 是作者电脑的路径不改必然失败unzip i2c-hid_standalone.zip -d ~/work/touch cd ~/work/touch find . -maxdepth 3 -type f | sort head -n 40 Makefile第一件事是看目录结构而不是立刻 make。find 帮你确认源码放在哪个子目录、有没有 dts 片段和加载脚本head 看 Makefile 里有没有写死 KDIR、ARCH、CROSS_COMPILE。这个环节最容易翻车。很多包在作者机器上能编过是因为作者把 KDIR 写成了自己电脑上的绝对路径比如 /home/td/linux到了你这边路径根本不存在或者 CROSS_COMPILE 用了作者自己安装的交叉工具链前缀和你环境里的不一样。不要试图在原 Makefile 上打补丁直接把这几行改成你自己环境的值是最省事的。如果包里压根没有 Makefile也不是不行。你可以把目标内核源码里 drivers/hid/i2c-hid/Makefile 拷出来作为模板再按外部模块的方式改写。如果连源码目录都没有那这个包就只是个空壳你需要先从同版本内核 tar 包里把 drivers/hid/i2c-hid/ 整个目录抽出来放进去。3.3 外部模块 Makefile 的基本形态obj-m、KDIR、ARCH、CROSS_COMPILE# 外部模块编译用的最小 Makefile # 多文件组合方式参考目标内核中 drivers/hid/i2c-hid/Makefile obj-m i2c-hid.o i2c-hid-objs : i2c-hid-core.o i2c-hid-acpi.o i2c-hid-of.o KDIR ? /path/to/target-kernel ARCH ? arm64 CROSS_COMPILE ? aarch64-linux-gnu- all: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) cleanobj-m 这一行决定最终生成的模块名i2c-hid-objs 决定这个模块由哪几个目标文件拼起来。不同内核版本的组合差异很大老版本可能只需要 i2c-hid.o 一个文件新版本里 acpi 和 of 是独立编成各自模块的。编译时报“缺少 i2c-hid-of.o”或者“重复定义”时不要瞎猜打开目标内核源码里同一路径的官方 Makefile照它抄。KDIR 必须指向目标板同版本、且已经 make 过的内核源码树不要填 /lib/modules/$(uname -r)/build那通常是主机内核指向它只能编出 x86_64 的模块。ARCH 填目标平台架构这个变量一旦错你 file 出来的 .ko 架构全错。CROSS_COMPILE 要能在 PATH 里找到比如 aarch64-linux-gnu-gcc不确定就用 which aarch64-linux-gnu-gcc 验一下。3.4 编出来的 .ko 先别急着上板file 与 modinfo 两道检查# 编译当前目录下的外部模块 make -j$(nproc) # 检查产物架构与内核版本 file i2c-hid.ko modinfo i2c-hid.komake 默认会执行 Makefile 里的 all 目标编出来的 i2c-hid.ko 就在当前目录。file 命令看两样东西架构是否是 ARM aarch64、格式是不是 ELF shared object。如果 file 显示 x86-64说明 ARCH 没生效或者 Makefile 里覆盖了你的设置去检查 ARCH 是不是被写成 x86_64或者 make 命令后面有没有环境变量串台。modinfo 是上板前最关键的检查。重点看 vermagic 和 depends 两行。vermagic 里包含内核版本、SMP、抢占模式、架构等编译参数必须和目标板 uname -r 输出一致差一个小版本号都会导致 insmod 报 Invalid module format。depends 给出这个模块依赖的其他模块一般是 i2c-core 和 hid。如果 depends 里有内容上板后要么先 modprobe 依赖要么直接 modprobe i2c-hid 让系统自动处理不要 solo 裸 insmod。3.5 目标板上的加载流程depmod、modprobe、dmesg 三步# 把模块放到目标板内核模块目录 scp i2c-hid.ko root192.168.1.10:/lib/modules/$(uname -r)/extra/ # 登入目标板重建模块依赖并加载 ssh root192.168.1.10 depmod -a modprobe i2c-hid # 查看驱动加载日志 ssh root192.168.1.10 dmesg | tail -n 30scp 传过去之后先 depmod 再 modprobe是因为 modprobe 需要依赖文件来解析模块依赖和符号。如果跳过 depmod会出现明明文件在却提示 modprobe: module i2c-hid not found 这类情况。放在 extra 目录只是一个习惯放在 /lib/modules/$(uname -r)/ 下任意标准子目录都可以前提是 depmod 扫得到。dmesg 里如果出现 Unknown symbol说明这个版本内核没有导出驱动用到的某个符号基本无解去匹配另一套内核版本如果出现 Invalid module format回到 3.4 检查 vermagic如果没有任何报错但也没有产生设备节点多半是设备树或 ACPI 表里还没有生成对应的 i2c_client也就是匹配根本没发生。这时候去查 dts 里有没有 compatible 为“hid-over-i2c”的节点I2C 总线号对不对。4. i2c-hid 独立模块上板排查5 个我把板子搞到黑屏/死机才记住的坑4.1 现象probe 显示成功但所有 read 回来的都是 0xFF原因I2C 速率过高、上拉电阻缺失或设备地址配错。触摸屏模组一般标称支持 400kHz但很多国产屏在 400kHz 下 SDA/SCL 时序跟不上回应你的就是全 F。上拉电阻没焊或者阻值太大总线被拉到不定状态也是同样的表现。还有一种情况是设备树里 reg 写的 0x38但实际模组地址是 0x40probe 匹配到了错误的 I2C 地址读出来自然全乱。解决先别让驱动背锅用 i2c-tools 直接探测地址。# 在目标板上扫描 5 号 I2C 总线上的设备地址 i2cdetect -y -r 5 # 尝试读取 0x38 地址的 0x00 寄存器 i2cget -y 5 0x38 0x00i2cdetect 能列出总线上响应的地址i2cget 能读回具体寄存器内容。如果读回来不是 0xFF说明链路是通的问题在地址或速率如果读回来是 0xFF先把 dts 里 I2C 控制器的 clock-frequency 改成 100000 再试同时检查板上 I2C 上拉电阻。这个顺序能省掉大量怀疑驱动的时间。4.2 现象insmod 之后突然中断风暴系统直接卡死原因IRQ 触发类型配错。触摸屏 INT 引脚多数是低电平有效设备主动拉低表示有数据要上报。如果设备树里配了 IRQ_TYPE_LEVEL_LOW而硬件上 INT 外部上拉没做或者模块上电时序异常驱动一加载就收到持续低电平中断 handler 被反复触发系统直接卡死。配成 IRQ_TYPE_EDGE_FALLING 可以暂时不风暴但会丢中断触摸表现为时灵时不灵。解决先查模组规格书确认 INT 有效电平再改设备树重编 overlay。// 设备树片段修正中断触发类型为低电平有效 i2c5 { touchscreen38 { compatible hid-over-i2c; reg 0x38; interrupt-parent gpio1; interrupts 13 IRQ_TYPE_LEVEL_LOW; hid-descr-addr 0x0001; }; };改完中断类型之后用示波器量 INT 脚在没有触摸时的静态电平。如果低电平是常态说明引脚配置或外部电路有问题不是驱动能解决的。这个坑最容易在“把别的项目 dts 抄过来”的时候出现抄的时候顺手把别人的 IRQ_TYPE 也抄了但不同模组的 INT 极性可能相反。4.3 现象hid_dump_device 读 descriptor 失败长度永远为 0原因复位后延时不够。HID over I2C 规范里驱动复位设备之后要等待设备准备好才能读 HID Descriptor但不同模组的准备时间差距很大。post-reset-delay-ms 配 5ms 甚至不配很多屏还没从复位里恢复过来驱动去读 descriptor 长度读回来就是 0 或者根本无响应。解决把复位延时调大同时检查 reset-gpios 的极性。有些国产屏的 reset 脚在模组上是反的标称低有效实际要高有效配反了等于一直按着复位键不放。// 设备树片段修正复位引脚与延时 touchscreen38 { compatible hid-over-i2c; reg 0x38; reset-gpios gpio1 15 GPIO_ACTIVE_LOW; post-reset-delay-ms 80; };post-reset-delay-ms 从 20 开始往上调80 是常见的稳定值。如果加了延时仍然读不到把示波器挂到 reset 脚上看驱动加载时有没有先拉低再拉高的动作没有就是 GPIO 没申请到或 dts 里 reset-gpios 写错引脚。4.4 现象系统睡眠唤醒后触摸没反应重启才能恢复原因独立模块没有和板子的电源域、pinctrl 完整配合。很多触摸屏在 suspend 时被断电唤醒后需要重新复位、重新读 descriptor但 standalone 模块往往只实现了基础的 probe 和 removePM 回调没有正确处理。另外 reset 引脚在 suspend 时被 pinctrl 切到别的状态导致设备始终处于复位中。解决短期用运行时手动 re-bind 来验证是不是 PM 问题。长期要进入驱动里补 power management 回调。# 应急恢复触摸重新绑定 i2c-hid 驱动 echo i2c-hid /sys/bus/platform/drivers/i2c-hid/unbind echo i2c-hid /sys/bus/platform/drivers/i2c-hid/bind如果这两条命令能让触摸恢复基本可以确定是 PM 场景下的设备状态没有恢复。再深入查二点suspend 期间 reset 引脚是否被 pinctrl 拉高以及设备的 supply 是否被电源域关掉、上电时序是否满足模组要求。这种问题在全量编译的内核里一样存在但独立模块更容易暴露因为它没有跟着 BSP 一起走完 PM 适配。4.5 现象同一套包和 dts换了另一批模组就失灵原因模组变更了 I2C 地址或 hid-descr-addr。这是最让人抓狂的情况。同一型号的屏不同批次可能把地址从 0x38 改成 0x10或者把 HID Descriptor 放在 0x0020 而不是标准默认的 0x0001。驱动代码没变变的只是两个设备树字段但现象是整块屏彻底不工作。解决换屏之后先扫描 I2C 地址再做修改。# 扫描总线上现有设备确认新模组实际地址 i2cdetect -y -r 5拿到实际地址后改 dts 的 reg。如果改了地址仍然 probe 失败再检查 hid-descr-addr。标准 HID over I2C 规范里HID Descriptor 所在寄存器偏移是 0x0001但部分厂商会放在 0x0020这个值配错驱动会在 probe 阶段读到长度为 0 的 descriptor。// 部分模组需要把 descriptor 偏移改到 0x0020 touchscreen10 { compatible hid-over-i2c; reg 0x10; hid-descr-addr 0x0020; };另外注意 compatible 字段。老内核里可能只识别“hid-over-i2c”新内核支持带厂商前缀的 compatible 字符串。不要把厂商前缀和“hid-over-i2c”混在一起乱配最好两个字符串都写上。5. 用 standalone 包验证触摸屏设备树配置建议保留的调试脚本与检查顺序5.1 insmod 前用 i2c-tools 把“硬件链路”从“驱动问题”里剥出来折腾次数多了之后我养成一个固定习惯驱动加载之前先把 I2C 地址从硬件层面验一遍。这个动作能把至少一半的排错成本砍掉。如果 i2cdetect 都扫不到地址就不用花时间调试驱动了直接回去查原理图、焊接和上拉。#!/bin/bash # check_touch.sh # 用法: ./check_touch.sh i2c_bus i2c_addr event_device BUS$1 ADDR$2 EVT$3 # 第一步确认 I2C 地址是否在线 i2cdetect -y -r ${BUS} # 第二步读取设备 ID 寄存器非 0xFF 则链路正常 i2cget -y ${BUS} ${ADDR} 0x00 2/dev/null # 第三步看驱动加载日志 dmesg -T | grep -i i2c # 第四步确认内核是否识别出 i2c client ls -l /sys/bus/i2c/devices/ # 第五步检查 input 子系统和触摸事件 cat /proc/bus/input/devices | grep -A 8 -i touch evtest ${EVT}这个脚本的思路是分层排查。第一步验证硬件第二步验证数据线第三步验证驱动第四步验证 bus 匹配第五步验证 input 上报。哪一步断了就只查哪一段不要把时间浪费在怀疑整条链路上。5.2 我的习惯把地址、中断、hid-descr-addr 三样写进 dts 模板再编译每次拿到新模组先把三个值从规格书或实测里确认下来再动 dts。配置项常见取值确认方法reg 地址0x10、0x38、0x40 都有i2cdetect 实测interrupt 类型LEVEL_LOW 或 EDGE_FALLING示波器看 INT 电平hid-descr-addr0x0001 或 0x0020probe 失败时逐一尝试这三个值都不需要编译内核只需要改 dts 重新生成 overlay配合 standalone 模块在几分钟内完成验证。地址错读不到设备中断错会死机descriptor 偏移错探不到 HID 描述符把这三个值稳定住剩下的就是模组固件行为上的微调。以前我拿到一块新屏第一反应是改 dts、编内核、烧镜像折腾半天才到 probe。后来我把 i2c-hid 抽成 standalone 包把这张三个值的检查模板贴在工位上每次只改三个字段半小时一个轮回变成三分钟一个轮回。这个习惯帮我省下大量重复编译时间也减少了很多因为内核版本不匹配带来的玄学问题。希望对你也有用。本文还有配套的精品资源点击获取
返回列表