Input子系统与TP驱动

本文深入探讨Linux内核Input子系统,包括三层结构:事件处理层、核心层和设备驱动层。详细解析了input_dev、input_handler和input_handle三大结构体的关系,以TP驱动为例阐述其在驱动中的作用。此外,介绍了事件上报流程和设备驱动层的工作,特别是TP驱动的I2C驱动部分,以及设备的注册和匹配硬件的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Input子系统与TP驱动

from vine_farer

1、层级结构

jiang-pc:~/build_projects2/build/60_ali/kernel-3.18/drivers/input$ ls
apm-power.c  ff-core.c     goodix_finger   input-polldev.c  keyboard    Makefile         serio
cdfinger     ff-memless.c  input.c         jmt101           keycombo.c  matrix-keymap.c  sparse-keymap.c
elan         fingerprint   input-compat.c  joydev.c         keyreset.c  misc             sw9551_fp
evbug.c      focaltech_fp  input-compat.h  joystick         madev       mouse            tablet
evdev.c      gameport      input-mt.c      Kconfig          madev_080t  mousedev.c       touchscreen

这是Input子系统在内核驱动中的顶层目录(MTK平台

Input子系统分为三个层级:

事件处理层:evdev.c 、joydev.c、mousedev.c

核心层:input.c

设备驱动层:touchscreen、mouse、fingerprint 等

事件处理层:通过核心层的API获取输入事件上报的数据,定义API与应用层交互

核心层:为事件处理层和设备驱动层提供接口API

设备驱动层:采集输入设备的数据信息,通过核心层提供的API上报数据

层级框架视图:

image


2、基础数据结构

struct input_dev:会在具体设备驱动层中被填充

struct input_handle:会在事件处理层和设备驱动层注册设备时通过input_dev或input_handler间接调用

struct input_handler:会在事件处理层如evdev.c中被实例化

结构体定义在kernel-3.18/include/linux/input.h

三大结构体关系视图一:

image1

1)input_dev
struct input_dev {
    const char *name;/*导出到用户空间的相关信息,在sys文件可以看到*/
    const char *phys;
    const char *uniq;
    struct input_id id;/*与input_handler匹配用的id*/

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
    /*输入设备的事件支持位图*/
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];/*支持所有事件*/
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];/*按键事件*/
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];/*相对位移事件*/
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];/*绝对位移事件*/
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];/*其它事件*/
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];/*LED事件*/
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/*声音事件*/
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];/*受力事件*/
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];/*开关机事件*/

    unsigned int hint_events_per_packet;

    unsigned int keycodemax;
    unsigned int keycodesize;
    void *keycode;

    int (*setkeycode)(struct input_dev *dev,
              const struct input_keymap_entry *ke,
              unsigned int *old_keycode);
    int (*getkeycode)(struct input_dev *dev,
              struct input_keymap_entry *ke);

    struct ff_device *ff;

    unsigned int repeat_key;//最近一次的按键值
    struct timer_list timer;

    int rep[REP_CNT];

    struct input_mt *mt;

    struct input_absinfo *absinfo;

    unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反应设备当前的按键状态
    unsigned long led[BITS_TO_LONGS(LED_CNT)];//反应设备当前的led状态
    unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反应设备当前的声音输入状态
    unsigned long sw[BITS_TO_LONGS(SW_CNT)];//反应设备当前的开关状态

    int (*open)(struct input_dev *dev);//第一次打开设备时调用,初始化设备用
    void (*close)(struct input_dev *dev);//最后一个应用程序释放设备时用,关闭设备
    int (*flush)(struct input_dev *dev, struct file *file);/*用于处理传递给设备的事件,如LED事件和声音事件*/
    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

    struct input_handle __rcu *grab;//当前占有该设备的input_handle

    spinlock_t event_lock;
    struct mutex mutex;

    unsigned int users;
    bool going_away;

    struct device dev;

    struct list_head    h_list;//该链表头用于链接此input_dev所关联的input_handle
    struct list_head    node;//用于将此input_dev链接到input_dev_list

    unsigned int num_vals;
    unsigned int max_vals;
    struct input_value *vals;

    bool devres_managed;
};
2)input_handler
struct input_handler {

    void *private;

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);/*event用于处理事件*/
    void (*events)(struct input_handle *handle,
               const struct input_value *vals, unsigned int count);
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *handler, struct input_dev *dev);
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);/*connect用于建立handler和device的联系*/
    void (*disconnect)(struct input_handle *handle);/*disconnect用于解除handler和device的联系*/
    void (*start)(struct input_handle *handle);

    bool legacy_minors;
    int minor;//次设备号
    const char *name;

    const struct input_device_id *id_table;//用于和input_dev匹配

    struct list_head    h_list;//用于链接和此input_handler相关的input_handle
    struct list_head    node;//用于将该input_handler链入input_handler_list
};
3)input_handle
struct input_handle {

    void *private;

    int open;//记录设备的打开次数(有多少个应用程序访问设备)
    const char *name;

    struct input_dev *dev;//指向所属的device
    struct input_handler *handler;//指向所属的handler

    struct list_head    d_node;//用于将此input_handle链入所属input_dev的h_list链表
    struct list_head    h_node;//用于将此input_handle链入所属input_handler的h_list链表
};
三大结构体关系视图二:

image2

浅析三大结构体关系
  • input_handle是连接input_deviceinput_handler的桥梁

  • input_device可以通过input_handle找到input_handler,同样的input_handler可以通过input_handle找到input_device

  • 一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应evdev_handler,也可以对应mouse_handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle,至于以何种方式来传递事件,就由用户程序打开哪个实例来决定

  • 后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备

  • input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256

以TP驱动为例:

MTK平台的TP驱动是分为两个部分组合在一起的,全平台的共享驱动mtk_tpd.c(抽象),以及各个型号TP的独立驱动(真实)

mtk_tpd.c负责将TP注册到platform总线,以及利用input子系统核心层提供的API向事件处理层上报键值

各个型号的独立驱动负责I2C总线挂接,读取键值提交给mtk_tpd.c

我们在此处先单独分析三大结构体在驱动中的作用

1)input_dev

input_dev结构体被定义在tpd_device结构体中,tpd_device如下定义

~/kernel-3.18/drivers/input/touchscreen/mediatek/tpd.h

struct tpd_device {
    struct device *tpd_dev;
    struct regulator *reg;
    struct regulator *io_reg;
    struct input_dev *dev;
    struct input_dev *kpd;
    struct timer_list timer;
    struct tasklet_struct tasklet;
    int btn_state;
};

然后我们分析mtk_tpd.c

~/kernel-3.18/drivers/input/touchscreen/mediatek/mtk_tpd.c

struct tpd_device *tpd = 0;

在此处我们可看见代码中定义了tpd_device的结构体指针,后面对input_dev结构体的调用和填充,都是通过tpd->dev来实现的

我们之前提到过,mtk_tpd.c做的重要的一件事就是注册platform平台总线,对设备的申请、注册,一些事件的属性设置,以及对各型号TP的兼容遍历,都是在其probe函数中完成的

static struct platform_driver tpd_driver = {
    .remove = tpd_remove,
    .shutdown = NULL,
    .probe = tpd_probe,
    .driver = {
            .name = TPD_DEVICE,
            .pm = &tpd_pm_ops,
            .owner = THIS_MODULE,
            .of_match_table = touch_of_match,//设备树匹配
    },
}; 

再看具体的tpd_probe函数

/* touch panel probe */
static int tpd_probe(struct platform_device *pdev)
{
    int touch_type = 1; /* 0:R-touch, 1: Cap-touch */
    int i = 0;
#ifndef CONFIG_CUSTOM_LCM_X
#ifdef CONFIG_LCM_WIDTH
    unsigned long tpd_res_x = 0, tpd_res_y = 0;
    int ret = 0;
#endif
#endif

    TPD_DMESG("enter %s, %d\n", __func__, __LINE__);

    if (misc_register(&tpd_misc_device))
        pr_err("mtk_tpd: tpd_misc_device register failed\n");
    tpd_get_gpio_info(pdev);
    tpd = kmalloc(sizeof(struct tpd_device), GFP_KERNEL);
    if (tpd == NULL)
    return -ENOMEM;
    memset(tpd, 0, sizeof(struct tpd_device));

    /* allocate input device */
    tpd->dev = input_allocate_device();
    if (tpd->dev == NULL) {
        kfree(tpd);
        return -ENOMEM;
    }
    ...

除了一些初始化和注册外,和我们的input_dev有关的函数是tpd->dev = input_allocate_device();,它主要为结构体申请内存空间,填充部分结构体信息和初始化一些锁、定时器、链表头,如下所示:

struct input_dev *input_allocate_device(void)
{
    static atomic_t input_no = ATOMIC_INIT(0);
    struct input_dev *dev;

    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
    if (dev) {
        dev->dev.type = &input_dev_type;
        dev->dev.class = &input_class;
        device_initialize(&dev->dev);
        mutex_init(&dev->mutex);
        spin_lock_init(&dev->event_lock);
        init_timer(&dev->timer);
        INIT_LIST_HEAD(&dev->h_list);
        INIT_LIST_HEAD(&de
<think>好的,我现在需要处理用户关于中断子系统TP(触摸屏)相关的面试问题查询。首先,用户提到了之前讨论的TP电源上下电毛刺问题,现在转向中断子系统TP中的应用。需要结合用户提供的引用内容,但看起来引用[1]和[2]主要涉及网络故障排查和Java线程中断,可能当前问题关联不大,但用户提示要参考之前的回答,所以应专注于之前关于TP电源的讨论,以及如何扩展到中断子系统。 用户的问题是关于中断子系统TP的面试常见问题。首先,我需要明确中断子系统在触摸屏(TP)中的作用。触摸屏通常通过中断机制向处理器报告触摸事件,例如当用户触摸屏幕时,TP控制器会触发一个硬件中断,通知CPU处理输入数据。因此,中断子系统的性能和处理机制对TP的响应速度和准确性至关重要。 接下来,我需要考虑面试中可能涉及的问题。常见的问题可能包括中断处理流程、上下半部机制、如何避免中断丢失、中断线程的交互、以及实际调试中断问题的经验等。此外,结合之前讨论的TP电源问题,可能还会涉及电源管理中的中断处理,例如在低功耗模式下中断如何唤醒系统。 需要确保回答结构清晰,涵盖基本概念、具体应用、常见问题及解决方案,以及可能的优化方法。同时,要结合之前提到的TP电源毛刺问题,说明中断子系统在此背景下的重要性,比如电源不稳定可能导致中断信号异常,进而影响TP的响应。 还需要注意引用之前回答中的内容,例如硬件设计、电源管理、PCB布局等如何影响中断的稳定性。例如,电源毛刺可能导致中断信号线上的噪声,触发错误的中断,或者导致中断丢失。因此,在解决TP问题时,除了电源设计,还需考虑中断信号线的布局和滤波措施。 可能还需要提到Linux内核中的中断子系统,例如设备树中的中断配置、中断处理函数的注册(request_irq)、顶半部和底半部的分工(如tasklet、workqueue等),以及如何调试中断冲突或性能问题。 最后,生成的相关问题需要围绕中断子系统的实现细节、调试方法、性能优化以及TP模块的集成等方面展开,帮助用户全面准备面试。</think>### 中断子系统TP相关的面试常见问题 --- #### 一、基础概念流程 1. **中断在TP中的作用** - **问题**:触摸屏(TP)如何通过中断处理器通信? - **回答**: TP控制器在检测到触摸事件时,通过硬件中断(如GPIO边沿触发或电平触发)通知CPU。例如,当用户触摸屏幕时,TP的INT引脚会拉低,触发中断服务程序(ISR)读取坐标数据。 - **关键流程**: 1. TP硬件检测到触摸 → 触发中断信号。 2. CPU响应中断,跳转至ISR(顶半部)。 3. ISR快速读取TP寄存器中的坐标数据并清除中断标志。 4. 将数据传递到底半部(如工作队列)进行进一步处理(如上报到输入子系统)[^1]。 2. **中断上下半部机制** - **问题**:为什么TP中断处理需要分顶半部和底半部? - **回答**: - **顶半部(Top Half)**: 负责快速响应硬件,读取TP数据寄存器并清除中断,要求执行时间极短(通常<1ms)。 - **底半部(Bottom Half)**: 处理耗时操作(如坐标滤波、手势识别),通过Tasklet、SoftIRQ或Workqueue实现,避免阻塞其他中断。 ```c // 示例:TP中断处理注册 irq_handler_t tp_isr(int irq, void *dev_id) { struct tp_data *data = (struct tp_data *)dev_id; uint32_t x = read_reg(TP_X_REG); uint32_t y = read_reg(TP_Y_REG); schedule_work(&data->work); // 触发底半部 return IRQ_HANDLED; } ``` --- #### 二、常见问题解决方案 1. **中断丢失抖动** - **问题**:TP中断频繁触发导致系统卡顿,如何分析? - **回答**: - **可能原因**: 1. **硬件噪声**:电源毛刺(参考之前TP电源问题)或PCB布局不良导致中断信号抖动[^1]。 2. **中断风暴**:TP固件错误或寄存器配置错误(如未正确清除中断状态)。 - **调试方法**: 1. 使用示波器捕获INT引脚波形,确认是否为硬件问题。 2. 在驱动中增加中断计数统计,观察触发频率: ```c static irqreturn_t tp_isr(...) { atomic_inc(&irq_count); // ... } ``` 2. **中断共享冲突** - **问题**:TP其他设备共享中断线(IRQ)时如何避免冲突? - **回答**: - **内核API**:使用`IRQF_SHARED`标志注册中断,并在ISR中通过`dev_id`区分设备: ```c request_irq(irq, tp_isr, IRQF_TRIGGER_FALLING | IRQF_SHARED, "tp", dev_id); ``` - **硬件设计**: 避免将TP中断其他高优先级设备(如USB)共享同一IRQ线,优先分配独立中断引脚。 3. **低功耗模式下的中断处理** - **问题**:系统休眠时,如何保证TP中断唤醒? - **回答**: - **配置唤醒源**:在设备树中标记TP中断为可唤醒: ```dts &tp { interrupts = <&gpio 5 IRQ_TYPE_EDGE_FALLING>; wakeup-source; }; ``` - **驱动实现**: 在suspend/resume回调中启用/禁用中断唤醒能力: ```c static int tp_suspend(struct device *dev) { enable_irq_wake(irq); return 0; } ``` --- #### 三、性能优化高级话题 1. **中断延迟优化** - **问题**:如何降低TP中断的响应延迟? - **回答**: - **内核配置**:启用`CONFIG_PREEMPT`(抢占式内核),减少任务调度延迟。 - **中断亲和性**:绑定TP中断到特定CPU核心,避免缓存抖动: ```bash echo 2 > /proc/irq/$(irq_num)/smp_affinity ``` 2. **TP输入子系统的集成** - **问题**:TP数据如何传递到用户空间? - **回答**: - **输入子系统流程**: 1. 底半部调用`input_report_abs(input_dev, ABS_X, x)`上报坐标。 2. 调用`input_sync()`通知事件完成。 - **示例代码**: ```c static void tp_work_handler(struct work_struct *work) { input_report_abs(data->input_dev, ABS_X, x); input_report_abs(data->input_dev, ABS_Y, y); input_sync(data->input_dev); } ``` 3. **调试工具技巧** - **问题**:如何快速定位TP中断问题? - **回答**: - **ftrace跟踪**: ```bash echo 1 > /sys/kernel/debug/tracing/events/irq/enable cat /sys/kernel/debug/tracing/trace_pipe ``` - **动态打印**:在驱动中添加`pr_debug()`,通过`dyndbg`动态启用: ```bash echo 'file tp_driver.c +p' > /sys/kernel/debug/dynamic_debug/control ``` --- ### 关键结论 1. **硬件-软件协同**:TP中断稳定性依赖电源质量(参考之前电源毛刺分析)驱动设计[^1]。 2. **实时性保障**:通过抢占式内核、中断亲和性等手段优化响应速度。 3. **调试优先**:结合示波器、ftrace和动态调试工具快速定位问题。 --- ### 相关问题 1. 在多核系统中,如何分配TP中断以实现负载均衡? 2. 如何通过设备树配置TP中断的触发类型(边沿/电平)? 3. 在Linux内核中,如何处理嵌套中断(Nested IRQ)? 4. TP中断线程化中断(threaded IRQ)的适用场景有何不同?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值