
最近在调一块RK3399方案板卡系统是Android7.1要把GSL5680这颗国产触摸IC驱动合入内核结果编译kernel的时候直接卡在touchscreen目录下报了一堆奇奇怪怪的错误。这个场景做嵌入式BSP的朋友应该都不陌生驱动源码是从方案商那边拷来的代码风格属于“祖传代码”稍不注意就被编译器教做人。这篇文章把整个排查和修复过程整理成笔记从报错场景、根因分析到完整操作流程都写清楚同时把常见报错的速查方案放在后面。正在搞RK3399 Android7.1触摸驱动合入、尤其是碰到GSL5680编译报错的朋友可以直接参考这里的思路和命令省去自己反复试错的时间。1. 背景与报错场景还原1.1 项目硬件与软件环境先说下我这边的基础环境方便你对号入座主控平台RK3399双Cortex-A72加四核Cortex-A53六核设计做商显、BOX、工控板都很常见系统版本Android 7.1.2对应内核版本一般是Linux 4.4.x系列触摸ICGSL5680I2C接口带INT中断脚和RST复位脚支持5点或10点触摸具体看屏厂模组触摸屏尺寸7寸左右的一体机/平板模组编译主机Ubuntu 16.04 x86_64SDK自带交叉工具链这个组合在2017到2020年之间的方案板上非常典型。RK3399的SDK在Android7.1时代已经比较成熟但问题是touch驱动往往不是RK官方写的而是屏厂或方案商提供的来源五花八门有的从全志平台直接拷过来有的从高通平台移植过来还有的是原厂FAE通过QQ邮件发的“最终版”。驱动代码的适配水平参差不齐编译报错几乎是必然事件。1.2 驱动包来源与GSL5680的特殊性GSL5680是国产触控芯片方案它有一个非常典型的特点原厂提供的驱动包通常不是单个文件而是一个文件夹里面除了gsl_ts.c或gsl_ts_drv.c这类主文件还有gsl_point_idc.c这种点坐标解析文件以及一大堆以芯片型号命名的头文件。代码风格属于“一包多型”同一个驱动文件夹里支持GSL1680、GSL2680、GSL3680、GSL5680等多个型号具体编哪个芯片靠宏开关切换。这种设计带来的问题很明显如果你拿到驱动包后没有仔细看头文件里的宏定义直接把所有.c文件一股脑编进去或者该定义的型号宏没有定义编译器就会给你抛出一堆undefined reference、implicit declaration之类的错误。GSL系列驱动里芯片型号宏通常写在某个头文件里比如#define CONFIG_GSL_5680编GSL5680的时候这个宏必须存在而且其他型号的宏不能同时打开。有的驱动包写得更隐晦会用//#define CONFIG_GSL_1680 #define CONFIG_GSL_5680 //#define CONFIG_GSL_3680默认注释掉所有型号宏让你自己选一个打开。很多人第一次编译报错问题就出在这里宏没开或者开错了型号。这部分不是语法错误能直接看出来的得对驱动的整体框架有概念才能定位。1.3 常见的三种编译失败现场我这次踩到的编译问题归纳起来有三类基本覆盖了GSL5680驱动编译的绝大多数情况第一类是编译到gsl_ts.c时报“implicit declaration of function”翻译过来就是“函数隐式声明”。这是C语言编译的老问题多半是某个函数没有头文件声明或者函数根本不存在只是代码里写了调用。第二类是编译时报结构体成员不存在最常见的是i2c_driver结构体里的suspend/resume成员。Android7.1的内核4.4.x已经把这俩成员从i2c_driver里移除了但老驱动代码里还在给它们赋值编译器直接报“has no member named”。第三类是编译过程中cc1进程被杀或者报out of memory。GSL系列的初始化寄存器数组非常大动辄几十万字节甚至上百万字节的const数组编译优化开启时内存消耗飙升老电脑或者容器环境下特别容易触发。这三类问题看着都不一样但底层原因其实有共通的地方驱动代码是“老代码配新内核”系统在编译时没有用正确的宏开关、没有适配新内核的API接口。下面逐个展开讲。2. 编译错误的根因分析与修复思路2.1 找不到头文件与函数隐式声明GCC在编译C文件时如果遇到一个没有显式声明的函数调用正常情况下会报warning提示“implicit declaration of function”。但如果内核编译开启了-Werror也就是把warning当作error这个warning就会升级成error直接中断编译。GSL5680驱动里出现这种情况通常有两个原因。第一个原因头文件路径或包含顺序不对。GSL驱动内部有一套自己的头文件依赖关系比如gsl_ts.c文件头部会include类似#include gsl_ts.h #include gsl_point_idc.h如果你的驱动文件没有放在同一个目录下或者这些头文件本身又包含了其他头文件而其他头文件路径没有在Makefile里用ccflags-y指定编译时就找不到。解决办法是在驱动目录的Makefile里增加ccflags-y -I$(srctree)/drivers/input/touchscreen/gslx680/这样当前目录下的所有头文件都能被编译器找到。第二个原因函数确实不存在或未被条件编译包含。GSL驱动里很多函数是放在#ifdef XXX宏里包裹的比如gsl_point_idc.c里的坐标拟合函数只在特定宏开启时才编译。如果主文件调用了这个函数但这个宏没开就会出现隐式声明的报错。排查方法是grep一下这个函数定义在哪个文件里再确认它外面包了什么宏然后在配置头文件里打开对应宏。不要直接在报错文件里乱加extern那是治标不治本后面链接阶段还会炸。2.2 内核API版本差异造成结构体成员报错Android7.1的内核版本已经是Linux 4.4.x相比GSL老驱动当年开发时基于的内核3.x/2.6.xi2c子系统驱动框架做了一次比较大的调整。最典型的就是i2c_driver结构体里删除了suspend和resume成员。老代码通常是这样写的static struct i2c_driver gsl_ts_driver { .driver { .name gsl_ts, .owner THIS_MODULE, }, .probe gsl_ts_probe, .remove gsl_ts_remove, .suspend gsl_ts_suspend, .resume gsl_ts_resume, };新内核编译时报错error: struct i2c_driver has no member named suspend error: struct i2c_driver has no member named resume正确做法是改成dev_pm_ops的方式static int gsl_ts_suspend(struct device *dev) { /* 原有的suspend逻辑 */ return 0; } static int gsl_ts_resume(struct device *dev) { /* 原有的resume逻辑 */ return 0; } static const struct dev_pm_ops gsl_ts_pm_ops { .suspend gsl_ts_suspend, .resume gsl_ts_resume, }; static struct i2c_driver gsl_ts_driver { .driver { .name gsl_ts, .owner THIS_MODULE, .pm gsl_ts_pm_ops, }, .probe gsl_ts_probe, .remove gsl_ts_remove, };函数参数也要注意老代码里的suspend/resume参数可能是struct i2c_client或者struct platform_device新框架统一用struct device。改完后原来从client或pdev里取i2c_client、取私有数据的方式也要跟着调整通常是先拿到i2c_client指针struct i2c_client *client to_i2c_client(dev);这块内容属于典型的内核API演进问题。报错提示哪个成员没有了就去内核源码include/linux/i2c.h里确认当前结构体还有哪些成员对照着改不要自己硬塞回老成员那样编过了运行也会出问题。2.3 初始化数组过大导致编译内存不足GSL5680这类触摸IC有一个特点芯片的寄存器初始化参数不是固件烧在芯片内部而是由驱动在初始化时通过I2C写进去的。这些寄存器配置数据以const数组形式存在驱动源码里数组元素动辄几千上万个每个元素可能是地址值对整个数组能达到几百KB。在编译这种大数组时GCC的优化器会尝试进行常量传播、数组重排等操作内存消耗非常大。如果是低配编译主机或者用了make -j8这类高并行度参数很可能出现cc1: out of memory allocating 65536 bytes或者干脆internal compiler error: Killed (program cc1)这个问题的解决思路有几个方向。最直接的是降低优化等级。在驱动目录的Makefile里加上CFLAGS_gsl_ts.o -O0或者针对整个目录ccflags-y -O0-O0关掉优化后编译大数组时的内存消耗会明显下降实测很稳。代价是驱动运行性能略降但对于触摸驱动这种对实时性要求没那么极端的场景完全可以接受。另一种思路是把大数组从.c文件里拆出来单独做成一个数据文件再用objcopy方式编译进去但这个操作复杂度高而且GSL驱动这种代码结构拆分之后维护起来很痛苦非必要不建议。还有一个常用操作是给编译环境加swap尤其编译主机内存只有8G以下的时候。临时用dd创建一个swap文件比如4G能在关键时刻救急。不过治标不治本跟上司申请内存才是最舒服的解法。2.4 Kconfig/Makefile配置层面的隐藏坑GSL5680驱动编译报错还有一种非常隐蔽的情况代码本身没问题但你的Kconfig和Makefile配置根本没把驱动加进编译体系。Android7.1的RK3399 SDK中内核编译入口在kernel目录下。如果你把GSL驱动文件夹放到kernel/drivers/input/touchscreen/目录下但没有在touchscreen的Makefile里添加编译规则那么你写的所有代码根本不会参与编译。很多人的做法是直接改touchscreen/Makefile加一行obj-$(CONFIG_TOUCHSCREEN_GSLX680) gslx680/这里有个关键点CONFIG_TOUCHSCREEN_GSLX680这个宏必须在.config里是y或m驱动的子目录才会被编译。而Kconfig文件里如果没有对应配置项make menuconfig里就找不到这个选项.config里也不会有这个宏。正确的姿势是三步走第一步在kernel/drivers/input/touchscreen/Kconfig里添加config TOUCHSCREEN_GSLX680 tristate Silead GSL5680 touchscreen driver depends on I2C help Say Y here if you have a GSL5680 touchscreen connected to your system.第二步在kernel/drivers/input/touchscreen/Makefile里添加obj-$(CONFIG_TOUCHSCREEN_GSLX680) gslx680/第三步在arch/arm64/configs/rk3399_defconfig不同SDK可能叫rockchip_defconfig里添加CONFIG_TOUCHSCREEN_GSLX680y这三个操作缺一个驱动都编不进去或者编了也链接不到。很多“编译错误”其实不是错误是驱动压根没进编译列表然后你误以为代码有问题还去改代码浪费时间。3. 从拿到驱动到编译通过完整实操记录3.1 驱动源码入树目录、Kconfig与Makefile我把整个实操过程按时间顺序记录下来你可以直接照着抄。拿到GSL5680驱动包后先解压查看文件结构。一般是这样gslx680/ ├── gsl_ts.c ├── gsl_ts.h ├── gsl_point_idc.c ├── gsl_point_idc.h ├── gsl_5680.h └── ...我把它放到kernel/drivers/input/touchscreen/gslx680/然后修改kernel/drivers/input/touchscreen/Makefileobj-$(CONFIG_TOUCHSCREEN_GSLX680) gslx680/再修改kernel/drivers/input/touchscreen/Kconfigconfig TOUCHSCREEN_GSLX680 tristate GSL5680 touchscreen driver depends on I2C help Support for Silead GSL5680 touchscreen.这里注意Kconfig里的宏名称“CONFIG_TOUCHSCREEN_GSLX680”和Makefile里的保持一致都是GSLX680这个名称不是芯片型号而是驱动系列名。GSL原厂把整个GSL系列驱动统一叫GSLX680很多新人第一次看到会误以为搞错型号这里说明一下。3.2 打开内核配置开关接下来确认内核配置。在RK3399 SDK里内核配置文件根据不同编译方式可能有三处需要关注arch/arm64/configs/rk3399_defconfigarch/arm64/configs/rockchip_defconfigkernel目录下已经生成的.config我用SDK的编译方式时先执行cd kernel make rockchip_defconfig或者在你的SDK根目录下./build.sh kernel无论哪种方式最终都要检查生成的.config里是否有这一行CONFIG_TOUCHSCREEN_GSLX680y没有的话手动在defconfig里加上重新生成.config。注意直接改.config而不改defconfig的话下次执行make distclean或者重新解压SDK时会丢失配置必须改defconfig。还有一点要确认驱动源码内部还有自己的编译条件。GSL的gsl_ts.c中经常有#ifdef CONFIG_GSL_5680 #define GSL_TP_5POINT #endif这里的CONFIG_GSL_5680和内核Kconfig里的CONFIG_TOUCHSCREEN_GSLX680不是一回事前者是驱动源码内部自己定义的宏需要在对应头文件里手动打开#define CONFIG_GSL_5680我在实际调试中多次遇到内核配置明明打开了CONFIG_TOUCHSCREEN_GSLX680但驱动文件里因为没定义CONFIG_GSL_5680导致gsl_ts.c里大量代码被条件编译屏蔽编译出的驱动只有空壳probe直接失败。这是一个极其隐蔽的坑。3.3 修复API兼容性问题的具体操作在把编译选项都弄好后重新编译通常就会暴露出代码层面的错误。我这次遇到的是i2c_driver结构体的suspend/resume问题修复方法已经在2.2节里说明了。另外还有一个高频错误是error: implicit declaration of function input_mt_init_slots老内核里input_mt_init_slots函数可能还在但新内核要求调用它之前必须声明且参数类型有变化。GSL老驱动里也可能没有初始化multi-touch slots的代码触摸事件上报用的是input_report_abs直接上报这在触摸功能上能跑但不够规范。如果有这个报错检查头文件include#include linux/input.h #include linux/input/mt.h如果include没问题仍然报隐式声明那就去内核源码里grep一下input_mt_init_slots的声明位置确认是不是函数名变更或者需要额外打开某个配置宏。在修改代码时我养成了一个习惯每次改动前先把原文件备份或者用git管理。GSL驱动本身代购混乱改错一处可能引发连锁报错有版本管理能随时回退省很多事。3.4 编译与烧录验证代码修改完开始编译。我习惯先单编内核确认无误后再走SDK的完整打包流程。在RK3399的Android7.1 SDK里单编内核可以这样cd kernel export ARCHarm64 export CROSS_COMPILE../prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android- make rockchip_defconfig make -j8如果你的SDK环境变量已经配置好也可以直接./build.sh kernel编完后会生成kernel目录下的boot.img或者kernel.img取决于SDK版本。RK3399 Android7.1一般生成kernel.img这个image包含了内核本身。我一般只烧kernel.img和resource.img不重刷整个系统省时间# 通过fastboot烧录 fastboot flash kernel kernel.img fastboot reboot烧录后开机先看内核日志里有没有GSL驱动的打印adb shell dmesg | grep -i gsl正常情况下会看到类似gsl_ts_probe: enter gsl_ts_probe: i2c_client addr0x40如果没看到任何gsl相关日志说明驱动根本没probe问题多半出在dts配置或者驱动没编进内核。3.5 驱动自检与触摸事件确认驱动probe成功后触摸功能不一定就好使还要确认事件上报是否正常。我常用的验证步骤是先看设备节点adb shell cat /proc/bus/input/devices找到GSL对应的输入设备记下事件编号然后adb shell getevent -l手指触摸屏幕观察终端是否有类似输出/dev/input/event2: EV_ABS ABS_MT_POSITION_X 00000322 /dev/input/event2: EV_ABS ABS_MT_POSITION_Y 00000411 /dev/input/event2: EV_KEY BTN_TOUCH DOWN如果getevent有输出说明驱动到系统的通路是通的问题可能在Android上层比如tp的校准、坐标系转换、input设备权限等。如果没有任何输出再看dmesg里有没有I2C读写错误或者中断是否触发cat /proc/interrupts | grep -i gpio触摸时中断计数不增加说明硬件连接有问题跟驱动关系不大。4. 常见问题速查与排查方法实录4.1 报错特征与解决方案速查表我汇总了GSL5680及GSL系列驱动在RK3399 Android7.1平台编译时最容易遇到的几类问题整理成速查表遇到类似的直接对着查报错特征可能原因解决方案implicit declaration of function xxx头文件路径不对或函数被条件编译屏蔽在驱动目录Makefile加ccflags-y -I检查型号宏是否打开error: struct i2c_driver has no member named suspend内核API演进i2c_driver删除了suspend/resume改用dev_pm_ops方式注册休眠/唤醒回调cc1: out of memory allocating ...初始化寄存器数组过大编译优化内存不足对该.c文件使用-O0或增加编译主机swapundefined reference to gsl_xxxgsl_point_idc.c等辅助文件未编入或宏未开启确认Makefile和Kconfig配置检查驱动内部条件编译宏fatal error: gsl_xxx.h: No such file or directory驱动内部头文件缺失或相对路径不对确认所有头文件已拷贝到驱动目录检查include路径probe过程中i2c_transfer返回错误驱动寄存器初始化数据与模组不匹配或I2C地址不对核对屏厂提供的I2C地址换对应模组的初始化配置数组getevent无任何触摸事件中断未触发或input设备没注册检查dts中断GPIO配置cat /proc/interrupts确认中断计数这张表基本覆盖了从编译到运行时的大部分问题后面细说排查方法。4.2 定位编译问题的排错技巧编译报错的时候大多数人习惯看终端最后几行的错误信息但这样常常会漏掉真正的首个报错。GCC在报错时如果一个文件出错后续错误可能都是连锁反应真正的根因往往在第一个error处。我习惯把完整编译日志重定向到文件然后从头开始看make -j8 compile.log 21 grep -n error: compile.log | head -50如果报错之前有warning也一并看因为-Werror会把它升级成error。另外make的V1参数能显示完整编译命令比如make V1 drivers/input/touchscreen/gslx680/通过完整编译命令能确认编译器选的是哪个工具链、ccflags有没有生效、头文件搜索路径是否包含了驱动目录。还有一个很实用的排查手法在内核源码里搜索类似驱动的写法。比如RK官方本来就有自己的触摸驱动目录里面会有通用的I2C触摸框架代码直接对比GSL老驱动和RK新驱动的差异比自己瞎猜API兼容性要快得多。4.3 被忽略的细节clean与增量编译的坑GSL系列活动中有个“经典翻车场景”代码改完后重新编译结果报的错和之前一模一样仔细排查后发现是增量编译缓存了旧的.o文件或者.config没重新生成。头文件被修改后Makefile的依赖检测不一定能完全覆盖特别是GSL这种代码结构不规范、头文件互相include的情况。保险做法是在Kconfig/Makefile配置有调整时先执行make clean如果defconfig有改动则重新生成.configmake rockchip_defconfig不建议随便make mrproper那会把整个内核目录的配置删干净可能需要重来一遍时间成本比较高。另外编译主机工具链的选择也很重要。RK3399的Android7.1 SDK自带aarch64-linux-android-4.9工具链尽量用它编译不要图方便用系统自带的aarch64-linux-gnu-gcc。不同工具链对代码的检查严格程度不一样GSL老驱动在某个工具链下能编过换个工具链就报warning甚至error这个我踩过不止一次。5. 编译通过之后验证与进一步调试5.1 dts配置核对与硬件初始化时序GSL5680驱动编译通过并加载成功只是万里长征走了一半另一半在dts配置和硬件时序上。RK3399在Android7.1下的dts一般在arch/arm64/boot/dts/rockchip/rk3399.dtsi以及对应的板级dts文件里。GSL5680挂I2C总线上dts节点写法大致如下i2c2 { status okay; gsl568040 { compatible GSL5680; reg 0x40; interrupt-parent gpio4; interrupts RK_PA4 IRQ_TYPE_EDGE_FALLING; reset-gpio gpio4 RK_PA5 GPIO_ACTIVE_LOW; }; };几个关键点说明一下。compatible字段必须和驱动里的of_match_table对应。GSL驱动的compatible在不同版本里可能是“GSL5680”或“gsl5680”大小写敏感必须完全一致否则probe直接失败。reg是I2C地址GSL5680常见地址是0x40或0x41具体取决于芯片的地址引脚电平。如果地址不对dmesg里能看到i2c_transfer失败或者NACK错误。interrupts和reset-gpio必须确认GPIO编号在板子上没有复用冲突。RK3399同一个GPIO可能被多个功能占用dts编译时不会报错但运行时会导致中断不触发。检查方法是看dts里是否有其他地方引用了同一个GPIO或者读/sys/kernel/debug/gpio确认GPIO占用状态。5.2 运行时常见touch问题与日志分析编译和加载都正常但触摸不能用这类问题我整理了一下大致有几种情况。第一种完全没反应。先看dmesgdmesg | grep -i gsl如果probe成功但触摸没反应中断计数不增加重点排查INT脚配置。有的屏模组中断是低电平触发有的是下降沿触发dts里IRQ_TYPE设置不对就检测不到。用示波器量一下INT脚电平变化是最直接的验证手段没有示波器的情况下可以临时把中断触发方式改成IRQ_TYPE_LEVEL_LOW看是否能检测到。第二种触摸有事件但坐标不对。坐标反了、坐标跳变、触摸不准这种问题多半是寄存器初始化数据与当前模组不匹配。GSL5680出厂时屏厂会根据具体模组生成一组初始化参数通常是.h文件里的一个大数组。如果你用的是其他屏的GSL5680驱动参数不匹配就会出现坐标乱跳。解决办法是找屏厂要对应这个屏的驱动配置文件替换驱动里的初始化数组。第三种能触摸但会断触。排除硬件连接问题后多半是固件里的滤波参数设置和屏的噪声特性不匹配。这个层面的问题就要找屏厂或者IC原厂调了自己改寄存器风险比较大。5.3 GSL5680调试中的几条经验最后分享几条关于GSL5680调试的个人经验算是交过学费换来的。第一驱动源码不要乱改结构。GSL系列驱动内部逻辑组织混乱但它的函数调用关系是经过验证的哪怕风格再差能跑通就是真理。你只需要改适配层的部分比如电源控制、复位时序、I2C地址、结构体API核心的坐标解析算法尽量不要动。第二编译报错时先查配置再查代码。我见过的GSL编译问题有一大半是配置问题型号宏没开、Kconfig没加、defconfig没更新、头文件路径不对。真正需要改代码的反而是少数。第三备份一个能编译通过的干净版本。GSL驱动调试过程中很容易改着改着就编不过了手头有干净版本可以随时对比。我一般把初版源码打成一个tar包存起来出问题就解压对比不耽误时间。第四多准备几个不同来源的GSL驱动包。GSL原厂、方案商、屏厂手里的驱动版本可能各不一样有的支持直接拉高复位有的要先拉低再拉高有的需要额外配置中断唤醒。遇到问题时多对比几个版本的实现往往能找到答案。最后再啰嗦两句调这个GSL5680我最开始的半天时间全耗在跟报错死磕上后来冷静下来把报错分类发现一半是配置没对一半是内核接口差异。编译报错本身不可怕可怕的是拿着报错信息就开始乱改代码越改越乱。建议你拿到任何GSL系列的触控驱动第一件事不是编译而是先看驱动里的头文件把型号宏、坐标通道数、I2C地址这些关键参数确认清楚再去做Kconfig和Makefile入树最后才是编译和排错。按这个顺序来一半的编译错误根本不会出现。后面修改过程中的每一条报错日志和对应改动都记录下来这份笔记既是给自己复盘的也是给以后接手这块板子的兄弟留一份参考。