驱动框架入门:Linux Input子系统架构

做触摸屏驱动,说白了就是跟Linux Input子系统打交道。我刚开始接触这块时,也觉得这玩意儿挺抽象——不就是按个屏幕吗,怎么还搞出个子系统来了?后来踩过几次坑才明白,Input子系统其实是Linux为了统一输入设备管理而设计的一套框架。

Input子系统三层架构

Input子系统分三层,我习惯这么记:

  • 核心层(Core):负责注册、管理、事件分发
  • 事件处理层(Event Handler):把原始事件转成用户空间能读的数据
  • 设备驱动层(Device Driver):咱们写触摸屏驱动主要待的地方

这三层的关系,可以用下面这张图来理解:

Linux Input 子系统三层架构 用户空间 (User Space) 事件处理层 (Event Handler) — evdev 核心层 (Input Core) — input.c 设备驱动层 (Device Driver) — 触摸屏驱动 硬件 (Hardware) — I2C/SPI 触摸屏 数据流向 硬件 → 驱动 → 核心 → 事件处理 → 用户 关键文件 input.c / evdev.c / input.h

你看,数据从硬件上来,经过驱动层、核心层、事件处理层,最后到用户空间。反过来,用户空间的配置也能通过这个通道往下走。嗯,这里要注意——驱动层只管跟硬件打交道,核心层负责注册和事件分发,事件处理层则把数据格式化成标准接口。

触摸屏驱动在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都进不去,那八成是设备树匹配出了问题。