驱动框架入门:Linux Input子系统架构
做触摸屏驱动,说白了就是跟Linux Input子系统打交道。我刚开始接触这块时,也觉得这玩意儿挺抽象——不就是按个屏幕吗,怎么还搞出个子系统来了?后来踩过几次坑才明白,Input子系统其实是Linux为了统一输入设备管理而设计的一套框架。
Input子系统三层架构
Input子系统分三层,我习惯这么记:
- 核心层(Core):负责注册、管理、事件分发
- 事件处理层(Event Handler):把原始事件转成用户空间能读的数据
- 设备驱动层(Device Driver):咱们写触摸屏驱动主要待的地方
这三层的关系,可以用下面这张图来理解:
你看,数据从硬件上来,经过驱动层、核心层、事件处理层,最后到用户空间。反过来,用户空间的配置也能通过这个通道往下走。嗯,这里要注意——驱动层只管跟硬件打交道,核心层负责注册和事件分发,事件处理层则把数据格式化成标准接口。
触摸屏驱动在Input子系统中的位置
触摸屏驱动属于设备驱动层。我举个例子你就明白了:
- 你写一个I2C触摸屏驱动,注册的是
i2c_driver - 在probe函数里,你调用
input_allocate_device()分配一个input设备 - 然后设置设备类型为
EV_ABS,上报触摸坐标 - 最后调用
input_register_device()注册到核心层
说白了,触摸屏驱动就是Input子系统里的一个"生产者",负责把硬件触摸事件包装成标准格式,丢给核心层去分发。
我的经验:调试触摸屏时,先确认/dev/input/eventX节点有没有生成。如果没有,说明驱动根本没注册成功。用cat /proc/bus/input/devices也能看到设备信息。
设备树与平台驱动
现在做嵌入式Linux,设备树(Device Tree)几乎是标配。我当年刚接触设备树时也觉得麻烦,但用久了发现——它确实比写死板代码灵活多了。
设备树的作用
设备树说白了就是描述硬件信息的配置文件。它告诉内核:
- 这个触摸屏挂在哪个I2C总线上
- 中断引脚是哪个GPIO
- 复位引脚是哪个GPIO
- 触摸屏的型号、尺寸等参数
举个例子,一个典型的触摸屏设备树节点长这样:
i2c@ff130000 {
status = "okay";
touchscreen@38 {
compatible = "goodix,gt911";
reg = <0x38>;
interrupt-parent = <&gpio3>;
interrupts = <14 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <1024>;
touchscreen-size-y = <600>;
};
};
这里compatible属性是关键——驱动就是靠它来匹配设备的。我在项目中遇到过好几次因为compatible写错导致驱动加载不上的情况,查了半天才发现是少了个逗号。
平台驱动与设备树匹配
驱动这边,用of_match_table来匹配设备树节点:
static const struct of_device_id gt911_of_match[] = {
{ .compatible = "goodix,gt911" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gt911_of_match);
static struct i2c_driver gt911_driver = {
.probe = gt911_probe,
.id_table = gt911_id,
.driver = {
.name = "gt911",
.of_match_table = gt911_of_match,
},
};
内核启动时,会遍历设备树里的节点,找到compatible匹配的驱动,然后调用probe函数。这个过程是自动的,你不需要手动干预。
避坑指南:我曾经在设备树里把interrupts属性写成了interrupt(少了个s),结果中断一直不触发。设备树语法很严格,少个字母、多个逗号都不行。建议写完设备树后用dtc工具检查语法。
驱动注册流程总结
我把触摸屏驱动的注册流程整理成了一张表,方便你对照:
| 步骤 | 操作 | 关键函数/结构体 |
|---|---|---|
| 1 | 定义I2C驱动结构体 | struct i2c_driver |
| 2 | 填写probe、remove回调 | .probe, .remove |
| 3 | 填写设备树匹配表 | .of_match_table |
| 4 | 在probe中分配input设备 | input_allocate_device() |
| 5 | 设置设备类型和事件位 | set_bit(EV_ABS, ...) |
| 6 | 注册input设备 | input_register_device() |
| 7 | 注册I2C驱动 | i2c_add_driver() |
嗯,这里要注意——步骤4到6是在probe函数里完成的。probe函数是驱动的入口,也是你写触摸屏驱动的核心战场。
我的一点体会
Input子系统其实没那么神秘。你想想看,它就是一个"中间人"——硬件那边来了触摸事件,它帮你包装好,送到用户空间。设备树则是"翻译官",把硬件信息翻译成内核能读懂的格式。
我个人习惯是先搭好设备树,再写驱动框架。设备树写对了,驱动probe能进,后面的事情就好办了。如果probe都进不去,那八成是设备树匹配出了问题。