资讯详情

i2c-hid standalone触摸屏驱动:独立编译、设备树绑定与排错实践

📅 2026/9/29 17:00:59 | 华诺云谱 👁 阅读
i2c-hid standalone触摸屏驱动:独立编译、设备树绑定与排错实践
简介针对联想拯救者 R7000 笔记本在 Linux 系统下触摸板异常问题的补丁源码包主要面向遇到硬件兼容性困扰的 Linux 用户尤其是使用 Manjaro 等发行版、具备基础内核模块编译能力的读者。压缩包采用 zip 格式共包含五个文件其中两个为 C 语言源文件两个为头文件一个为编译脚本整体大小约二十四 KB。源文件负责驱动核心逻辑与设备匹配头文件定义接口常量编译脚本用于生成可加载的内核模块让用户无需搭建完整内核源码环境即可独立完成编译。目前已有 1466 人学习或下载该资源。用户可以按照说明将编译生成的模块替换进系统并调整引导参数从而恢复触摸板正常功能这一过程也为阅读驱动代码、了解模块编译与注入思路提供了直观样例适合需要快速解决硬件异常或学习内核模块机制的读者参考。1. 先拆开 i2c-hid_standalone.zip 的包装这块触摸屏驱动的独立运行包到底解决了什么一块 ARM Linux 板子刚启动完触摸屏完全点不动dmesg里反复出现i2c_hid_acpi: probe failed with error -2。如果你做过嵌入式 BSP 或触控整机集成大概率遇到过这种场景。i2c-hid_standalone.zip常见于厂商或 BSP 团队把 HID over I2C 驱动单独抽出的工程包用来在不改完整内核树的前提下独立编译、按需加载快速验证触摸屏能不能工作。它要解决的就是这三件事编译独立、加载可控、排错可复现。适合做驱动移植、BSP 适配和整机调试的工程师也适合在树莓派这类能跑 Linux 的板子上折腾触控外设的人。下面按一条线展开驱动栈怎么分工、用什么命令把模块编出来、设备树怎么绑、坑在哪些地方以及最后怎么验证。2. 把它编进内核之前先看清 i2c-hid 驱动栈的三层分工和 standalone 的意义很多人拿到这种 standalone 包第一反应是找 README然后把.c文件直接丢进内核源码树里编。这个做法短期能用一换内核版本就崩。我更建议先把驱动栈里的分工看清楚再决定这个包应该放在哪个位置、用哪种方式编译。2.1 驱动栈三层分工i2c-core、i2c-hid、hid-core 谁在跟硬件说话I2C 触摸屏和 USB 触摸屏最大的区别在传输层。USB HID 设备枚举一次就能拿到接口描述符和报告描述符I2C 触屏要把 HID 协议跑在只有两根信号线的总线上设备地址由硬件决定中断脚单独接到某个 GPIO。Linux 处理这套协议的不是一个孤立驱动而是三层配合。最底层是 I2C core它在drivers/i2c下负责总线适配器、设备枚举、读写时序和重试。中间层是 i2c-hid也就是标题里这个驱动核心它实现 HID over I2C 协议的传输部分包含读 HID Descriptor、Set Power、Reset 这些命令。最上层是 hid-core 和 hid-input它们解析 report descriptor把坐标转成标准 input 事件。三层通过struct hid_ll_driver这类接口衔接最终暴露给用户态的就是/dev/input/eventX节点。层次典型代码位置职责加载时的角色I2C coredrivers/i2c设备发现与总线读写基础设施i2c-hiddrivers/hid/i2c-hidHID over I2C 协议平台驱动probe、resume、powerhid-core / hid-inputdrivers/hid报告描述符解析与事件上报输入子系统上层这里有一个经常被忽略的点i2c-hid 的 probe 入口在 x86 和 ARM 上并不一样。x86 上由 ACPI 枚举设备靠i2c_hid_acpi匹配ARM 上靠设备树里的 compatible 触发。standalone 包编译出的模块是同一个但它在不同平台上跑出来的行为差别很大这也是后面几章反复要提到的“匹配”问题。调 BSP 时如果只盯着协议层看很容易漏掉入口差异。2.2 standalone 包为什么值得单独编内核树污染、模块签名和部署姿态为什么不直接改内核源码树主要是回归成本。直接丢进drivers/hid/i2c-hid里编等于把厂商补丁和 mainline 代码混在一起下次内核升级、BSP 更新改动会被覆盖或者产生冲突。独立工程虽然多几步配置但源码边界清楚出问题能回退适合做交付物。拿到包后的第一个动作应该是解压看看里面带的是完整协议源码还是只有补丁文件。unzip i2c-hid_standalone.zip -d ~/workspace/i2c-hid-standalone cd ~/workspace/i2c-hid-standalone find . -maxdepth 2 -type f | sort解压后我一般会关注几个文件协议主文件可能是i2c-hid.c也可能是拆成多个.c的版本、Makefile 和说明文档。如果包内自带 Kconfig说明这套工程想保持与内核源码树一致的配置方式如果只有 Makefile说明它想完全独立。两种姿态决定了后边走make的方式不同先看清楚再动手比盲改要稳得多。独立工程通常长这样# 独立编译时用 obj-m主内核树一般不会这么写 obj-m : i2c-hid.o KDIR ? /lib/modules/$(shell uname -r)/build ARCH ? arm CROSS_COMPILE ? arm-linux-gnueabihf- all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean这里obj-m表示把驱动作为模块生成M$(PWD)让内核构建系统只在当前目录找目标文件。KDIR 必须指向已经配置过的内核构建目录不能只给源码目录否则很多头文件生成步骤会被跳过。遇到“很难编过”的 standalone 包九成问题出在这一个变量上。这种独立打包的思路和你在别处见到的 standalone installer 很像官方把运行环境、依赖和启动脚本收纳进一个包使用者解压就能跑。evergreen standalone installer 之所以受欢迎是因为它保证“拿到的东西自带完整上下文”驱动领域的 standalone 包也是同一套逻辑把 Makefile、头文件和针对某颗芯片的补丁都放进来避免使用者去 mainline 里翻旧补丁。带私有补丁的触控驱动尤其适合这么交付。提示如果包里的 Makefile 写死了绝对路径指向某个旧内核目录务必改成自己的 KDIR。别轻信“之前同事这么编过”内核接口一变链接时就会报一大堆 Unknown symbol。3. 用 standalone 源码头一次编出 i2c-hid.ko最小命令与三处必改参数3.1 编译前检查与最小编译命令拿到 standalone 包后的第一步我不会马上 make而是先核对目标机内核版本与编译机内核源码版本避免接口不匹配。版本不一致时编出来的模块塞进目标机最典型的就是Unknown symbol或 version magic 不匹配。version magic 错误会在 insmod 时直接拒绝加载连 dmesg 都不好查。# 目标板上看运行内核版本 uname -r # 编译机上确认内核构建树存在且版本与上面对得上 ls /lib/modules/$(uname -r)/build如果build目录不存在先在编译机上安装与目标机同版本的内核头文件或者把内核源码放到固定路径下并完成配置。接下来分两种平台执行。本地调试时KDIR 指到当前机器make clean make KDIR/lib/modules/$(uname -r)/build modules交叉编译到 ARM 板子时make clean make KDIR/home/user/linux-arm ARCHarm CROSS_COMPILEarm-linux-gnueabihf- modules这里三个变量一起说清KDIR是内核编译树路径必须已经执行过make modules_prepare或完整配置ARCH告诉构建系统目标架构CROSS_COMPILE是交叉编译工具链前缀少了它本地 gcc 会尝试编译 ARM 源码大概率直接报语法错或找不到头文件。三处里任何一处不对编译都过不去。编完后先看一次模块信息modinfo i2c-hid.komodinfo能列出模块依赖。i2c-hid 通常依赖hid和i2c-core如果depends:为空先回头检查 KDIR 里的内核配置而不是急着往板子上拷。确认依赖正常后再复制和加载scp i2c-hid.ko root目标板:/root/ ssh root目标板 insmod /root/i2c-hid.ko dmesg | grep -i hid调试阶段用insmod而不是modprobe是为了不让 depmod 自动拉依赖避免把系统里旧版本模块一起加载。等确认这一版没问题再替换到/lib/modules/$(uname -r)/kernel/drivers/hid/i2c-hid/下并执行depmod -a否则下次开机又回到旧模块。3.2 三处最常翻车的参数KDIR、CONFIG_HID、设备地址第一处是 KDIR 指错。报错Makefile: *** 没有规则可制作目标 modules多半是 KDIR 指向源码目录而不是配置好的编译树。解决先在内核源码目录下执行make ARCHarm CROSS_COMPILE... modules_prepare再回来编。第二处是内核里没开 CONFIG_HID。standalone 包只编译 i2c-hid 这一个模块但它调用 hid-core 的符号如果内核没编 HID 子系统MODPOST 阶段直接报Unknown symbol。解决方法是先确认主内核配置grep -E CONFIG_HID|CONFIG_I2C_HID /path/to/kernel/.config若没有在 menuconfig 里打开Device Drivers - HID support重编内核或至少生成 Module.symvers。很多人交叉编译时报Unknown symbol hid_register_driver其实不是源码问题而是主内核配置里根本没开 HID。第三处是设备树里的 reg 与总线上实际设备地址对不上。编译和加载都能通过但驱动永远 probe 不成功。常见做法是先扫描总线上有什么i2cdetect -y -r 1如果扫描不到设备问题可能在硬件上拉或地址线如果扫描到了但设备树里 reg 是另一个地址把 reg 改过来再重新加载。这里最坑的是有些触控芯片同时支持多个 I2C 地址厂商默认地址与设备树配置不一致时现象就是“模块加载了但没接管设备”。注意i2cdetect -r用 SMBus read byte 方式扫描对某些只响应 I2C 写地址的设备会漏报。扫描不到时不要急着下结论再用-q或直接读厂商手册确认地址。4. 设备树绑定 i2c-hidcompatible、reg 与中断号的配合决定模块接管成败4.1 ACPI 路径与设备树路径的两套 probe 入口i2c-hid 在驱动文件里通常注册两个平台驱动一个挂.acpi_match_table供 x86 使用一个挂.of_match_table供 ARM 设备树使用。拿到 standalone 包后先看它里面有没有.of_match_table。如果没有说明这套代码可能是从 ACPI 平台反推出来的要上 ARM 板子就得自己补。常见做法是参照内核上游i2c-hid-of.c的写法补全。补 match table 时注意 compatible 的写法。上游对 I2C HID 设备推荐的是compatible hid-over-i2c这是标准 binding但很多原厂 SDK 喜欢用自己的名字比如xxx,touchscreen。standalone 包里一般会列一组原厂名字如果设备树里写的是标准名驱动却只认原厂名就会出现“模块已加载设备未接管”的静默失败。4.2 一份可复现的设备树节点与中断触发类型的坑下面是一份常见可用的设备树节点写法适配大多数 I2C 触屏i2c1 { touchscreen: touch10 { compatible hid-over-i2c; reg 0x10; interrupt-parent gpio1; interrupts 23 IRQ_TYPE_EDGE_FALLING; hid-descr-addr 0x0001; pinctrl-names default; pinctrl-0 pinctrl_i2c1_touch; }; };每个字段都直接影响 probe 是否成功。compatible决定与驱动 of_match_table 是否匹配reg是 I2C 设备地址必须与硬件一致interrupt-parent和interrupts决定驱动在 reset 后怎么确认设备就绪触发类型写错了probe 会直接 timeouthid-descr-addr是 HID Descriptor 寄存器地址官方默认是0x0001但某些 SoC 的 I2C controller 读时序会把字节序反过来导致读到的 descriptor 是乱码。如果 dmesg 里出现类似failed to set a device to power-on state的日志把hid-descr-addr从0x0001改成0x0100试一次往往能解决字节序问题。这个字段在 ACPI 表里同样存在x86 平台改 ACPI 麻烦ARM 平台改设备树就简单多了。中断触发类型也是高频坑触屏芯片的 INT 通常是低有效设备数据准备好后拉低但如果主控 GPIO 内部有上拉外部又配了电平触发很容易出现中断一直被拉低、驱动认为设备一直忙。调试时先保持硬件默认再查cat /sys/kernel/debug/gpio看实际电平不要一上来就改代码。如果设备树暂时不方便改可以先手动绑定验证驱动本身# 先看设备有没有被 i2c 核心枚举到 ls /sys/bus/i2c/devices/ # 手动绑定到 i2c-hid 驱动 echo 1-0010 /sys/bus/i2c/drivers/i2c-hid/bind“1-0010”的格式是i2c总线号-设备地址地址用 16 进制。手动绑定适合快速验证驱动本身是否正常如果 bind 报No such device说明 compatible 或 address 不匹配先回设备树改而不是反复 insmod。5. i2c-hid_standalone 加载问题排查五条踩坑记录与 dmesg 逐项对照这些坑基本都踩过一遍有些是血泪经验按现象到原因再到解决的顺序写方便直接对照排错。5.1 现象insmod 报 Exec format error现象insmod i2c-hid.ko直接返回Exec format errordmesg 没有额外输出。 原因多数时候是架构不匹配比如在 x86 主机上加载 ARM 编译出来的.ko或者反过来少数情况是文件传输损坏scp 拷贝时中断模块文件不完整。 解决先做两个检查uname -m file i2c-hid.kofile输出里写的是ARM还是x86-64一眼就能判断是不是交叉编译问题。如果是传输损坏重新 scp 或用 rsync 传传完比对 md5。这个坑看着简单却在多人协作的 BSP 环境里最容易出现有人把 ARM 版本的包直接拷到了 x86 调试机上浪费一整个下午。5.2 现象modprobe 加载成功但 probe 一直 timeout现象modprobe i2c-hid返回成功但 dmesg 出现类似failed to reset device或probe timeout的记录。 原因驱动在 probe 阶段向设备发 reset 命令要等中断信号确认中断一直没来多数是中断号配错、GPIO 被复用或者中断触发类型不匹配。内核这里给的信息很少看起来像个黑匣子实际原因多半不在协议层而在中断脚。 解决先把设备树里的interrupts触发类型改成IRQ_TYPE_LEVEL_LOW试一次再看 dmesg。接着检查 GPIO 占用cat /sys/kernel/debug/gpio看对应 GPIO 的占用者是不是 i2c-hid 驱动如果显示被别的 pinctrl 占用去查pinctrl-0里是否漏配了该 GPIO 的 pinmux。这类问题改代码没用必须回到设备树。5.3 现象hid-generic 抢先接管i2c-hid 变成“透明人”现象模块加载后/dev/input/eventX没出现dmesg 里却能看到 hid-generic 相关的注册记录。 原因设备树 compatible 没有匹配上 i2c-hid 驱动而系统里另一个驱动用同一 compatible 或通过其他路径先接管了设备。 解决先看/sys/bus/i2c/drivers下面到底谁绑定了这个设备再用手动 bind 方式强制绑定。如果不想每次手动可以在设备树里把 compatible 改成驱动 of_match_table 认识的字符串或者在驱动里补上设备树用到的 compatible。这里最容易出现的误判是反复重编模块实际上模块根本没参与匹配。5.4 现象Secure Boot 或强制模块签名导致加载被拒现象insmod i2c-hid.ko报Required key not available或者模块能加载但内核直接 taint。 原因内核开启了CONFIG_MODULE_SIG_FORCE只允许带有效签名的模块。standalone 包编出来的模块没有厂商私钥自然被拒。 解决调试用内核实测时在 bootloader 里关闭 Secure Boot或换不带强制签名的内核必须保留签名的环境用内核源码里的scripts/sign-file对模块签名。签名校验不是玄学报错信息里会直接写清楚缺哪个 key别拿生产环境开这种玩笑。5.5 现象触摸事件一直不上报但 dmesg 很干净现象probe 正常、input 节点也在但evtest里手指触摸没有坐标事件。 原因可能是报告描述符里坐标用法没有被 hid-input 解析也可能是中断触发时机不对导致数据没读到。这类问题最隐蔽因为驱动栈每一层都“看起来正常”。 解决先看cat /proc/bus/input/devices里该设备有没有Abs能力再确认实际触摸时 GPIO 电平有没有变化必要时用i2cget手动读一次 HID descriptor 地址和驱动读到的结果对比。如果 GPIO 根本不变大概率是硬件连接或触控屏固件的问题再换驱动也只是原地打转。6. 验证触摸屏是否真的被 i2c-hid 接管一条从 dmesg 到 event 节点的自查线我每次拿到新的 standalone 包都会按一条固定顺序验证避免“模块加载成功但业务不能用”的错觉。第一步看 dmesg 里有没有 probe 成功日志dmesg | grep -iE i2c_hid|i2c-hid|hid over i2c正常会看到类似i2c_hid i2c-1-0010: probe with IRQ 123和input: HID 0xxx:xxxx TouchScreen的记录。只有模块加载记录但没有 probe 记录说明没匹配上有 probe 记录但没有 input 记录说明 report descriptor 解析阶段失败。第二步打开设备节点列表确认 input 层已经注册cat /proc/bus/input/devices | grep -A5 -i touch evtest /dev/input/event2从Handlers行能看到设备被哪个 event 节点承载。第三步做一次完整事件验证手指在触摸屏上滑动evtest里应看到ABS_MT_POSITION_X、ABS_MT_POSITION_Y连续变化。如果坐标只在一个轴上动多半是报告描述符里的 range 没解析对这和驱动本身关系不大要回去查触摸屏配置。最后可以用 i2cget 单独读一次寄存器确认 I2C 链路本身没有丢数据i2cget -y 1 0x10 0x0001返回的不是0xFF说明 I2C 链路是通的问题只可能在中断或协议解析如果返回0xFF说明地址或寄存器不对先回设备树核对。以前我拿到这种包第一反应就是解压丢进主内核树结果改了三天代码最后发现是设备树中断触发类型配错了。现在我会先独立编译、先验证设备树、再谈改协议层这一套顺序帮我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑