:解決Linux I2C觸摸板驅(qū)動問題)
簡介面向Linux下觸摸板異常問題該驅(qū)動補丁包針對R7000等機型提供i2c-hid驅(qū)動源碼。通過安裝linux-headers、執(zhí)行make即可生成i2c-hid.ko內(nèi)核模塊替換系統(tǒng)原有驅(qū)動并啟用polling_mode1后可有效解決觸摸板無響應(yīng)、誤觸或漂移等問題。壓縮包體積僅24KB共5個文件包含2個C源文件、2個頭文件與1個MakefileC源文件與頭文件構(gòu)成驅(qū)動核心Makefile提供編譯入口整體組織清晰。用戶可從中獲得手動編譯內(nèi)核模塊、替換驅(qū)動、更新grub引導參數(shù)等完整排錯路徑無需修改BIOS設(shè)置風險相對可控適合有一定Linux基礎(chǔ)、希望自行修復觸摸板驅(qū)動問題的中高級用戶。目前已有1461人學習對于使用Manjaro等發(fā)行版并受i2c-hid觸摸板問題困擾的開發(fā)者是一份簡潔可用的參考補丁也可作為內(nèi)核驅(qū)動編譯與替換的練習樣例。1. 項目概述i2c-hid_standalone.zip 到底是什么能解決什么問題先說一下我遇到這個包的場景。前陣子給自己的 Linux 筆記本換了塊觸控板型號是 Elan 的 I2C 接口設(shè)備結(jié)果裝完系統(tǒng)后發(fā)現(xiàn)觸控板完全沒反應(yīng)dmesg里只有兩行話i2c_hid_acpi i2c-ELAN0679:00: HID not enabled或者更慘直接device not found。查了一圈發(fā)現(xiàn)問題出在發(fā)行版內(nèi)核自帶的 i2c-hid 驅(qū)動版本太舊或者干脆沒把CONFIG_I2C_HID編進去。就在那個節(jié)骨眼上我從一個內(nèi)核開發(fā)者的個人倉庫里找到了這個i2c-hid_standalone.zip。這個包本質(zhì)上是一份從 Linux 內(nèi)核源碼樹中抽取出來的 i2c-hid 驅(qū)動獨立工程里面帶好了 Makefile、Kconfig 以及最關(guān)鍵的i2c-hid.c和i2c-hid-core.c源碼。standalone 的意思是它不需要你重新編譯整個內(nèi)核也不用你去折騰內(nèi)核配置文件只要你的系統(tǒng)裝了匹配的內(nèi)核頭文件就能像編譯普通內(nèi)核模塊一樣單獨把它編成一個.ko文件然后手動加載到正在運行的內(nèi)核里。誰的痛點最需要它首先是觸摸板或觸摸屏是 I2C 接口、且使用的是 Elan / Synaptics / Goodix 等常見控制芯片的 Linux 用戶其次是那些用著廠商定制內(nèi)核、但廠商沒開全 HID 驅(qū)動的嵌入式/筆記本用戶還有就是做內(nèi)核驅(qū)動開發(fā)調(diào)試的朋友想在不動整個內(nèi)核的情況下快速迭代驅(qū)動代碼這個包就是給這些人準備的。我這里基于的是 5.10 LTS 內(nèi)核版本的源碼提取你在其他版本上編譯時大概率要做少量適配后面我會把改動點列清楚。整體來說這個包能幫你解決 80% 的 I2C HID 設(shè)備驅(qū)動加載不成功的問題剩下的 20% 是硬件 ACPI 表問題或固件問題那種情況就不是單獨編譯一個驅(qū)動模塊能救的了。2. 整體設(shè)計思路為什么選擇 standalone 方式而不是直接改內(nèi)核2.1 standalone 模塊方案的優(yōu)勢和場景邊界你要搞清楚這個問題的前提是明白 i2c-hid 驅(qū)動在內(nèi)核里的存在形式。標準內(nèi)核里它是一個drivers/hid/i2c-hid/目錄下的內(nèi)核模塊受CONFIG_I2C_HID控制。用戶有兩種選擇編進內(nèi)核y或者編成模塊m。對于絕大多數(shù)發(fā)行版這個選項默認是m也就是生成i2c-hid.ko模塊由 udev 根據(jù) ACPI 表中HID設(shè)備的兼容 ID如PNP0C50、ACPI0011自動加載。那為什么還需要 standalone 版本我實際踩過的坑是這樣的make menuconfig里看到CONFIG_I2C_HID是灰色甚至完全不可選原因通常是內(nèi)核版本太老、I2C 子系統(tǒng)的 API 變化過大或者芯片廠商給的內(nèi)核補丁只針對特定版本。這時候你不可能為了一個觸控板去重編整個內(nèi)核重新編一個 5.15 或 6.1 內(nèi)核加上所有固件、Initramfs 更新動輒一兩個小時起步而且有把系統(tǒng)搞掛的風險。獨立模塊方案的好處就是快、可逆、不污染系統(tǒng)全局。我選擇這個方案還考慮到了一個實際問題調(diào)試方便。如果直接把驅(qū)動編進內(nèi)核出了問題你只能改 Kconfig 重新 build 內(nèi)核而 standalone 模塊模式下rmmod 修改源碼 make重新編譯 insmod整個循環(huán)下來不到一分鐘非常適合反復調(diào)參。我在調(diào)試 Elan 設(shè)備的report_descriptor解析問題時就深深體會到這一點。2.2 包內(nèi)結(jié)構(gòu)和核心文件的作用打開解壓后的i2c-hid_standalone目錄你會看到這幾個關(guān)鍵文件i2c-hid_standalone/ ├── Makefile ├── Kconfig ├── i2c-hid.c ├── i2c-hid-core.c ├── i2c-hid-dmi-quirks.c ├── i2c-hid.h └── README.mdi2c-hid.c是設(shè)備驅(qū)動的主入口負責模塊加載、卸載、注冊i2c_driver結(jié)構(gòu)體i2c-hid-core.c是核心邏輯包括 HID 設(shè)備的初始化、報告描述符解析、IRQ 處理、電源管理回調(diào)等i2c-hid-dmi-quirks.c則是一堆 DMI 匹配的兼容性補丁專門處理某些廠商設(shè)備在 ACPI 表或報告描述符上的非標準行為。Makefile 里已經(jīng)把編譯目標寫死為obj-m i2c-hid.o同時還做了ccflags-y的版本判斷自動適配內(nèi)核頭文件路徑。說句實在話我最開始拿到這個包第一反應(yīng)是怎么就這么幾個文件和我預想的一個完整內(nèi)核目錄差距很大。但實際用下來正是這種精簡結(jié)構(gòu)讓 standalone 編譯變得特別輕量因為內(nèi)核模塊編譯本來就不需要完整的內(nèi)核源碼樹只要有l(wèi)inux-headers里帶的build符號鏈接和Module.symvers就夠了。源碼本身是直接從內(nèi)核drivers/hid/i2c-hid/摳出來的所以邏輯上是完整的不是閹割版。2.3 編譯鏈路和配置依賴梳理在動手編譯之前必須先把依賴環(huán)境弄清楚否則大概率會碰到一股腦的報錯。我總結(jié)下來編譯這個模塊需要滿足以下條件系統(tǒng)內(nèi)核版本與當前運行的uname -r一致嚴格一致不然后面 insmod 會報 version magic 不匹配安裝了對應(yīng)版本的linux-headers-$(uname -r)并且/lib/modules/$(uname -r)/build符號鏈接指向正確的內(nèi)核源碼目錄有g(shù)cc、make、build-essential且 gcc 主版本和內(nèi)核編譯時的版本差異不要過大內(nèi)核開啟了CONFIG_I2C和CONFIG_HID這兩個一般發(fā)行版默認都有重點是確認一下。其中內(nèi)核版本匹配這條我用一個表格給大家做個直觀的對比場景內(nèi)核源碼樹linux-headers 包standalone 模塊能否編譯能否加載Ubuntu/Debian 發(fā)行版不需要已安裝匹配版本可以可以Arch/Manjaro 滾動更新不需要已安裝大概率可以偶爾頭文件滯后看運氣自己編譯的定制內(nèi)核需要不需要源碼樹即頭文件可以需要看 version magic內(nèi)核頭文件不匹配不需要未安裝/版本不對編譯報錯一定不行注意上面最后一行很多人就是栽在這個地方。make報錯信息特別抽象報的是./include/linux/kconfig.h:5: include/linux/autoconf.h: No such file or directory看著像是源碼缺失實際上就是你沒裝linux-headers或者沒建好build鏈接。這個坑我后面會再展開。3. 編譯安裝全流程實操一步一步把 i2c-hid.ko 跑起來3.1 編譯環(huán)境準備檢查內(nèi)核、安裝依賴先說最基礎(chǔ)的環(huán)境檢查。打開終端先確認內(nèi)核版本和頭文件情況uname -r dpkg -l | grep linux-headers-$(uname -r)如果在 Debian/Ubuntu 系上dpkg沒有輸出執(zhí)行安裝sudo apt update sudo apt install linux-headers-$(uname -r) build-essentialArch 系則是sudo pacman -S linux-headers。裝完之后務(wù)必驗證一下/lib/modules/$(uname -r)/build是否指向正確路徑ls -l /lib/modules/$(uname -r)/build這個鏈接正常應(yīng)該指向/usr/src/linux-headers-$(uname -r)一類的目錄。如果沒建好手動建一下sudo ln -s /usr/src/linux-headers-$(uname -r) /lib/modules/$(uname -r)/build這里有一個我后來才注意到的細節(jié)如果頭文件版本和運行內(nèi)核不一致例如你運行的是5.15.0-91-generic但 apt 給你裝的是5.15.0-86-generic那么/lib/modules/5.15.0-91-generic/build根本不會存在。解決的辦法是去 Ubuntu 內(nèi)核倉庫下載對應(yīng)的linux-headers-5.15.0-91三個 deb 包linux-headers-5.15.0-91_*、linux-headers-5.15.0-91-generic_*、linux-modules-5.15.0-91-generic_*手動安裝。這個過程不算太復雜但確實折磨了不少人。3.2 解壓與 Makefile 適配版本適配和路徑修改拿到i2c-hid_standalone.zip后先解壓unzip i2c-hid_standalone.zip cd i2c-hid_standalone打開 Makefile 看一眼內(nèi)容里面核心的幾行是obj-m i2c-hid.o i2c-hid-objs : i2c-hid-core.o i2c-hid-dmi-quirks.o i2c-hid.o KERNEL_DIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNEL_DIR) M$(PWD) modules關(guān)鍵就是KERNEL_DIR這個變量。大多數(shù)情況下默認值就能用不需要手動改。但如果你像我一樣交叉編譯或者頭文件放在自定義路徑下可以這樣指定make KERNEL_DIR/path/to/your/kernel/build接下來是源碼的版本適配問題。我基于 5.10 內(nèi)核提取的這個版本在 5.15 和 6.1 內(nèi)核上編譯時需要改動一兩個函數(shù)。核心差異點是i2c_hid_probe函數(shù)的返回值類型。5.10 之前的版本里I2C 探測回調(diào)返回int從 5.18 開始內(nèi)核把 I2C probe 回調(diào)統(tǒng)一改成了void返回實際上這個改動在 6.1 才強制落地。所以你編譯時如果看到類似這樣的報錯error: initialization of ‘int (*)(struct i2c_client *, const struct i2c_device_id *)’ from incompatible pointer type ‘void (*)(struct i2c_client *, const struct i2c_device_id *)’就在i2c-hid.c里把函數(shù)簽名從static int i2c_hid_probe(...)改成static void i2c_hid_probe(...)同時把函數(shù)末尾的return 0;刪掉并且把i2c_driver結(jié)構(gòu)體里.probe i2c_hid_probe改成.probe_new i2c_hid_probe。這個改動在 6.1 內(nèi)核上是必須的不做就編不過去。3.3 編譯、加載與開機自動加載從 make 到 insmod 的完整命令環(huán)境調(diào)整好之后直接編譯make -j$(nproc)編譯正常結(jié)束的話目錄下會生成i2c-hid.ko文件。用modinfo看一眼模塊信息確認 version magic 和vermagic字段與當前內(nèi)核一致modinfo i2c-hid.ko | grep vermagic這個輸出應(yīng)該和uname -r完全一致。如果發(fā)現(xiàn)不一致不要強行加載回去檢查頭文件版本。確認無誤后先嘗試加載sudo modprobe -r i2c-hid sudo insmod i2c-hid.ko我這里先modprobe -r是防止系統(tǒng)里已經(jīng)有舊模塊在占用設(shè)備導致 insmod 報Device or resource busy。加載后立即看內(nèi)核日志dmesg | tail -n 30如果一切正常你會看到類似這樣的輸出i2c_hid i2c-ELAN0679:00: i2c-hid: HID probe success input: ELAN0679:00 as /devices/pci0000:00/.../i2c-ELAN0679:00/input/input5這就說明驅(qū)動已經(jīng)成功枚舉出觸摸板了。接著打開設(shè)置里的觸摸板頁面滑動一下看光標是否跟隨移動。如果沒有反應(yīng)但日志顯示 probe 成功那么問題大概率出在libinput的配置或設(shè)備的HID report descriptor上需要進一步調(diào)試。為了實現(xiàn)開機自動加載有兩種方式。第一種是把編譯好的.ko文件復制到內(nèi)核模塊目錄然后跑depmodsudo cp i2c-hid.ko /lib/modules/$(uname -r)/kernel/drivers/hid/i2c-hid/ sudo depmod -a sudo modprobe i2c-hid第二種是更輕量的方案直接在/etc/modules-load.d/i2c-hid.conf里寫入一行i2c-hid這樣每次開機時 systemd 就會自動加載這個模塊。我個人更推薦第二種因為不動系統(tǒng)內(nèi)核模塊目錄卸載也方便改個文件或刪掉.conf就能恢復原樣。3.4 修改源碼后的循環(huán)編譯調(diào)試法如果你在做驅(qū)動開發(fā)調(diào)試頻繁改源碼的話我建議你養(yǎng)成一個習慣每次改完代碼不要急著直接insmod先rmmod卸載舊模塊再make clean make然后把編譯警告都看一遍。一個常見問題是在改函數(shù)簽名或結(jié)構(gòu)體成員后編譯器會報一堆類型不匹配的警告這些警告通常不影響生成.ko但運行時可能引發(fā)kernel NULL pointer dereference所以不能完全忽略。我這里分享一個高效的調(diào)試循環(huán)命令流程sudo rmmod i2c-hid make clean make -j$(nproc) sudo insmod i2c-hid.ko dmesg | tail -n 20配合一個簡單的測試操作比如xinput list查看輸入設(shè)備是否出現(xiàn)就能在幾十秒內(nèi)完成一次完整的驗證迭代。比起改整個內(nèi)核的編譯循環(huán)這個流程會快非常多也是 standalone 方案給我省下最多時間的地方。4. 常見問題與排查技巧我踩過的那些坑4.1 編譯階段的典型報錯與修復方法編譯期報錯是最多的我把最常見的幾類整理成一張表方便大家按圖索驥報錯癥狀根本原因解決辦法autoconf.h: No such file or directory沒裝 linux-headers 或 build 鏈接失效安裝對應(yīng)版本的 linux-headers重建/lib/modules/$(uname -r)/builderror: unknown type name ‘bool’內(nèi)核頭文件未正確包含確認編譯命令里有沒有-D__KERNEL__Makefile 是否走了內(nèi)核的 Kbuild 系統(tǒng)incompatible pointer typeprobe 相關(guān)內(nèi)核版本 probe 回調(diào)簽名變更按上文改成probe_new形式version magic mismatch頭文件版本和運行內(nèi)核不一致重新安裝精確匹配的頭文件包implicit declaration of function i2c_new_client_device內(nèi)核版本 API 被移除或改名把i2c_new_client_device換成i2c_new_client或檢查CONFIG_I2C是否開啟注意第一行那個報錯很多教程里會勸你下載完整內(nèi)核源碼其實完全不需要純屬誤導。只要頭文件裝對了這個報錯立即消失。有一個比較隱蔽的問題我單獨提一嘴頭文件裝對了但make仍然報錯找不到某些頭文件。這種情況多發(fā)生在你用的發(fā)行版把內(nèi)核頭文件分包了比如 Ubuntu 的linux-libc-dev和linux-headers-*是兩個獨立包前者提供用戶態(tài)頭文件后者提供內(nèi)核態(tài)編譯用頭文件。如果你僅僅裝了linux-libc-dev那make同樣會失敗。解決辦法很簡單老老實實裝linux-headers-$(uname -r)這個包。4.2 加載階段的常見失敗與排查方法insmod失敗通常分為兩類一類是insmod: ERROR: could not insert module i2c-hid.ko: Invalid module format這就是 version magic 不匹配內(nèi)核拒絕加載另一類是Operation not permitted這是 Secure Boot 沒關(guān)或者模塊簽名校驗失敗。后者在 UEFI 啟動的機器上非常常見很多新筆記本默認開啟 Secure Boot。Secure Boot 的排查方法是看內(nèi)核日志dmesg | tail -n 10如果看到Lockdown: insmod: unsigned module loading is restricted那就說明內(nèi)核被鎖定不允許加載未簽名模塊。解決辦法有幾個進入 BIOS 關(guān)閉 Secure Boot最直接但影響系統(tǒng)安全配置使用mokutil --import導入自定義 MOK 密鑰給模塊簽名直接用發(fā)行版自帶的dkms機制來管理DKMS 編譯時如果用了發(fā)行版官方工具鏈可以自動附帶簽名。我個人的建議是如果這臺機器只是個人開發(fā)機關(guān)閉 Secure Boot 是最省事的如果是工作電腦或要對安全策略負責的環(huán)境就用 MOK 簽名不要圖省事改全局安全配置。另外還有一個容易被忽略的問題系統(tǒng)里已經(jīng)存在同名的i2c-hid模塊發(fā)行版內(nèi)核自帶。如果你直接insmod i2c-hid.ko不會報錯但實際生效的可能是系統(tǒng)自帶的那個因為它被 udev 優(yōu)先加載了。這就是為什么我在前面強調(diào)要先modprobe -r i2c-hid卸掉舊模塊。驗證當前加載的是不是你編譯的版本可以看/sys/module/i2c_hid/initstate和modinfo的輸出路徑。4.3 運行階段的調(diào)試工具與方法模塊加載成功不等于設(shè)備一切正常。有時候你會發(fā)現(xiàn)dmesg里顯示i2c_hid i2c-ELAN0679:00: i2c-hid: HID probe success但觸摸板就是不工作。這時候要做的就是確認 I2C 總線上設(shè)備是否正常枚舉。使用i2cdetect掃描 I2C 總線是個好辦法sudo apt install i2c-tools sudo i2cdetect -l sudo i2cdetect -y 5其中-l列出系統(tǒng)所有 I2C 總線-y 5掃描第 5 號總線。如果能看到觸控板的地址Elan 設(shè)備一般是0x15或0x2c說明 I2C 通信正常。如果這里就掃不到設(shè)備問題在硬件或 ACPI 表的 GPIO/IRQ 配置層面驅(qū)動怎么改都白搭。另一個實踐里很有效的工具是evtest它直接讀取內(nèi)核 input 子系統(tǒng)和 HID 層的數(shù)據(jù)流。如果evtest /dev/input/eventX能列出設(shè)備且輸出事件說明內(nèi)核 HID 層和 input 層已經(jīng)通了問題在 libinput 或桌面環(huán)境的配置如果列不出設(shè)備問題在上游還要繼續(xù)往 i2c-hid 的協(xié)議層查。4.4 調(diào)試期間值得記錄的心得要點我在這個項目里最有價值的經(jīng)驗有兩條分享給大家第一不要一開始就改復雜的東西先驗證裸模塊能否加載。環(huán)境準備工作如果做了 30 分鐘還沒搞定停下來重新審一遍第一步。很多人卡在編譯階段其實是忽略了uname -r和頭文件版本必須完全一致這個基礎(chǔ)條件。第二注意 DMI quirk 文件的影響。i2c-hid-dmi-quirks.c這個文件里的 DMI 匹配會覆蓋某些設(shè)備的默認行為。如果你發(fā)現(xiàn)驅(qū)動加載后設(shè)備被識別了但工作異??梢钥纯词遣皇潜荒硞€ quirk 命中了。調(diào)試時可以臨時把 quirk 表內(nèi)容注釋掉重新編譯看行為是否有變化。這個文件里有很多廠商的特殊處理邏輯這是社區(qū)積累下來的寶貴經(jīng)驗但也意味著如果不確定是否適用你的設(shè)備不要輕易照搬別人的補丁。5. 后續(xù)擴展與方案對比standalone 模塊 vs 完整內(nèi)核編譯最后聊聊這個 standalone 方案的局限性以及和專業(yè)完整內(nèi)核編譯方案的對比。我用一個表格來總結(jié)對比維度standalone 模塊完整內(nèi)核編譯編譯時間1-3 分鐘20-60 分鐘依賴復雜度只需要 linux-headers需要完整源碼樹和大量構(gòu)建依賴系統(tǒng)影響可隨時 rmmod無殘留需要更新 initramfs重啟生效調(diào)試迭代快適合頻繁修改慢每次都要重啟驅(qū)動與內(nèi)核版本兼容性需要手動適配 API源碼樹自洽一般無版本問題適用場景快速驗證、單個設(shè)備調(diào)試大規(guī)模定制、需要隨內(nèi)核啟動如果你只是想讓一個 I2C 觸摸板跑起來standalone 就是最快的路但如果你需要同時修改hid-core、hid-input等多個內(nèi)核 HID 層驅(qū)動的交互邏輯那還是老老實實編譯完整內(nèi)核否則你會在模塊間的符號依賴上浪費大量時間。另外standalone 模塊加載后符號是動態(tài)鏈接進內(nèi)核的和內(nèi)核編譯期符號表不完全一致這可能導致某些內(nèi)核調(diào)試工具如ftrace、trace-cmd無法直接追蹤到模塊內(nèi)部的函數(shù)調(diào)試手段會受限。我個人的實踐體會是先快速驗證、再決定是否深入。先用 standalone 模塊把設(shè)備點亮確認硬件正常、HID 描述符解析沒問題之后再考慮要不要重新編譯完整內(nèi)核來集成長期修改。這樣既降低調(diào)試成本又避免在錯誤的方向上浪費一整天。希望這篇記錄能幫到你少走點我趟過的渾水。本文還有配套的精品資源點擊獲取