Input子系统框架(编写触摸屏驱动程序_基于QEMU)

didi1. 框架分析

1. 1 字符设备驱动程序框架

怎么编写字符设备驱动程序?

  • 确定主设备号

  • 创建file_operations结构体

    • 在里面填充drv_open/drv_read/drv_ioctl等函数

  • 注册file_operations结构体

    • register_chrdev(major, &fops, name)

  • 谁调用register_chrdev?在入口函数调用

  • 有入口自然就有出口

    • 在出口函数unregister_chrdev

  • 辅助函数(帮助系统自动创建设备节点)

    • class_create

    • device_create

1.2 Input子系统框架

1.

1.3 Input子系统内部实现

1.3.1 重要结构体

左边的input_dev表示输入设备

右边的input_handler表示"处理程序"

匹配之后使用input_handle保存2者之间的联系

设备获取、上报数据:input_event

1.3.2 注册流程演示

  • 注册input_dev:input_register_device

    • 把input_dev放入input.c的input_dev_list链表

    • 对于input.c的input_handler_list链表中的每一个input_handler,一一比较

    • 如果匹配则调用input_handler.connect

  • 注册input_handler: input_register_handler

    • 把input_dev放入input.c的input_handler_list链表

    • 对于input.c的input_dev_list链表中的每一个input_dev,一一比较

    • 如果匹配则调用input_handler.connect

  • 怎么判断input_dev和input_handler是否匹配

    • input_handler中有一个id_table,表示它支持哪类设备

    • input_handler还有一个match函数,可以用来进行复杂的判断

    • match函数可以省略

    • 判断是否匹配流程

      • 如果没有match函数:input_dev跟id_table比较即可

      • 如果有match函数:input_dev先跟id_table比较,成功后再用match函数再次判断

  • 匹配后做的事情

    • 调用input_handler.connect

      • 创建/注册一个input_handle,把input_dev, input_handler放在里面

注册字符设备驱动程序  

1.3.3 读取一个数据的流程演示

  • APP调用open函数打开/dev/input/event0

    • 在驱动程序evdev_open里,创建一个evdev_client,表示一个"客户"

  • APP调用read/poll读取、等待数据

    • 没有数据时休眠:wait_event_interruptible(evdev->wait, ...)

  • 点击、操作输入设备,产生中断

  • 在中断服务程序里

    • 从硬件读取到数据

    • 使用以下函数上报数据

  • input_event做什么?

    • 从dev->h_list中取出input_handle,从input_handle取出input_handler

    • 优先调用input_handler->filter来处理

    • 如果没有input_handler->filter或者没处理成功

      • 调用input_handler->events

      • 没有input_handler->events的话,调用input_handler->event

  • 以evdev.c为例

    • 它有evdev_events:用来处理多个事件

    • 也有evdev_event:实质还是调用evdev_events

    • 唤醒"客户":wake_up_interruptible(&evdev->wait);

2. 编写input_dev驱动框架

2.1 分配/设置/注册input_dev

2.2 硬件相关的操作
  • 申请中断

  • 在中断服务程序里

    • 读取硬件获得数据

    • 上报数据

2.3 编程 代码分析

  • 在设备树里创建一个节点(设备树文件)

    • 指定硬件资源等信息

  • 编译一个plartform_driver驱动(驱动.c文件)

    • 在probe函数里

      • 从设备树获得资源

      • 分配/设置/注册input_dev

      • 硬件相关的操作

        • request_irq等

驱动程序所依赖的一些头文件

定义结构体指针,中断线(指针),中断服务

probe函数:从设备树获取硬件信息,分配并设置输入设备

1.设置输入设备ID

2.设置支持的事件类型

3.设置具体事件

4.设置绝对值参数

在Linux输入子系统中,设置绝对值参数(absolute axis values)的作用是定义输入设备绝对轴的特性,如屏幕坐标、滑条位置等。这些参数告诉内核如何解释从输入设备接收到的原始数据。

5.注册输入设备

硬件操作,获取IRQ资源并注册中断服务程序

// 平台设备探测函数
static int input_dev_demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int error;
    struct resource *irq;

    // 从设备树获取硬件信息
    // ...

    // 分配并设置输入设备
    g_input_dev = devm_input_allocate_device(dev);
    g_input_dev->name = "input_dev_demo";
    g_input_dev->phys = "input_dev_demo";
    g_input_dev->dev.parent = dev;

    // 设置输入设备ID
    g_input_dev->id.bustype = BUS_HOST;
    g_input_dev->id.vendor = 0x0001;
    g_input_dev->id.product = 0x0001;
    g_input_dev->id.version = 0x0100;

    // 设置支持的事件类型
    __set_bit(EV_KEY, g_input_dev->evbit); // 按键事件
    __set_bit(EV_ABS, g_input_dev->evbit);  // 绝对值事件,用于触摸屏等

    // 设置具体的事件
    __set_bit(BTN_TOUCH, g_input_dev->keybit); // 触摸按键
    __set_bit(ABS_MT_SLOT, g_input_dev->absbit); // 触摸屏槽位
    __set_bit(ABS_MT_POSITION_X, g_input_dev->absbit); // 触摸屏X坐标
    __set_bit(ABS_MT_POSITION_Y, g_input_dev->absbit); // 触摸屏Y坐标

    // 设置绝对值参数
    input_set_abs_params(g_input_dev, ABS_MT_POSITION_X, 0, 0xffff, 0, 0);
    input_set_abs_params(g_input_dev, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0);

    // 注册输入设备
    error = input_register_device(g_input_dev);

    // 硬件操作,获取IRQ资源并注册中断服务程序
    irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    g_irq = irq->start;
    request_irq(g_irq, input_dev_demo_isr, IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL);

    return 0;
}

定义 平台设备移除函数,设备树匹配表

定义平台驱动结构体

在Linux内核中,平台驱动结构体(platform_driver)是用于定义和注册平台设备驱动程序的一种机制。平台设备通常用于嵌入式系统或固定硬件,其中设备的配置和资源在编译时就已经确定。

定义模块初始化函数,模块退出函数3. 

3. 编写触摸屏驱动程序_基于QEMU

  • request_irq

  • 在中断处理函数里

    • 上报按下、松开的事件

  • 如果触摸屏被按下,启动定时器

  • 如果触摸屏被松开,取消定时器

依赖的头文件和定时时间

#include <linux/module.h>      // 模块化编程支持
#include <linux/init.h>        // 模块初始化和清理宏
#include <linux/fs.h>          // 文件系统支持
#include <linux/interrupt.h>   // 中断处理
#include <linux/irq.h>         // IRQ支持
#include <linux/sched.h>       // 调度程序支持
#include <linux/pm.h>          // 电源管理
#include <linux/slab.h>        // 内存分配
#include <linux/sysctl.h>      // 系统控制接口
#include <linux/proc_fs.h>     // 进程文件系统
#include <linux/delay.h>       // 延时函数
#include <linux/platform_device.h> // 平台设备支持
#include <linux/input.h>       // 输入子系统
#include <linux/gpio_keys.h>    // GPIO按键支持
#include <linux/workqueue.h>   // 工作队列
#include <linux/gpio.h>        // GPIO支持
#include <linux/gpio/consumer.h> // GPIO消费者
#include <linux/of.h>          // 设备树支持
#include <linux/of_irq.h>      // 设备树中的中断支持
#include <linux/of_gpio.h>     // 设备树中的GPIO支持
#include <linux/spinlock.h>    // 自旋锁

#define TOUCHSCREEN_POLL_TIME_MS 10 // 触摸屏轮询时间(毫秒)

定义触摸屏控制器结构体

定义定时器中断处理函数

中断服务程序 :读取数据,报告数据(发送具体数据,开始定时器,取消定时器(如果松手),结束时候告诉内核中断已经被处理)

/ 定义触摸屏控制器结构体
struct qemu_ts_con {
    volatile unsigned int pressure; // 触摸压力
    volatile unsigned int x;        // X坐标
    volatile unsigned int y;        // Y坐标
    volatile unsigned int clean;    // 清洁标志
};

static struct input_dev *g_input_dev; // 全局输入设备结构体指针
static int g_irq;                    // 全局IRQ线
static struct qemu_ts_con *ts_con;    // 触摸屏控制器
struct timer_list ts_timer;          // 定时器

// 定时器中断处理函数
static void ts_irq_timer(unsigned long _data)
{
    if (ts_con->pressure) // 如果有触摸压力
    {
        input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x); // 报告X坐标
        input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y); // 报告Y坐标
        input_sync(g_input_dev);                           // 同步事件

        // 重新设置定时器
        mod_timer(&ts_timer,
                  jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
    }
}

// 中断服务程序
static irqreturn_t input_dev_demo_isr(int irq, void *dev_id)
{
    /* 读取数据 */

    /* 报告数据 */
    if (ts_con->pressure)
    {
        input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x); // 报告X坐标
        input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y); // 报告Y坐标
        input_event(g_input_dev, EV_KEY, BTN_TOUCH, 1);     // 报告触摸按键
        input_sync(g_input_dev);                           // 同步事件

        /* 开始定时器 */
        mod_timer(&ts_timer,
                  jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
    }
    else
    {
        input_event(g_input_dev, EV_KEY, BTN_TOUCH, 0);     // 报告触摸释放
        input_sync(g_input_dev);                           // 同步事件

        /* 取消定时器 */
        del_timer_sync(&ts_timer);
    }

    return IRQ_HANDLED; // 告诉内核该中断已经被处理
}

初始化变量;

从设备树获取GPIO信息;

分配并设置输入设备:

1.哪种类型的事件?

2. 哪些事件?

3.时间参数?;

硬件操作:获取内存资源,并将其映射与物理地址绑定

初始化并设置定时器

// 平台设备探测函数
static int input_dev_demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int error;
    struct resource *io;
    int gpio;

    printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    gpio = of_get_gpio(pdev->dev.of_node, 0); // 从设备树获取GPIO

    /* 分配并设置输入设备 */
    g_input_dev = devm_input_allocate_device(dev);

    g_input_dev->name = "input_dev_demo";
    g_input_dev->phys = "input_dev_demo";
    g_input_dev->dev.parent = dev;

    g_input_dev->id.bustype = BUS_HOST;
    g_input_dev->id.vendor = 0x0001;
    g_input_dev->id.product = 0x0001;
    g_input_dev->id.version = 0x0100;

    /* 设置1: 哪种类型的事件?*/
    __set_bit(EV_KEY, g_input_dev->evbit); // 按键事件
    __set_bit(EV_ABS, g_input_dev->evbit);  // 绝对值事件
    __set_bit(INPUT_PROP_DIRECT, g_input_dev->propbit); // 直接属性

    /* 设置2: 哪些事件?*/
    __set_bit(BTN_TOUCH, g_input_dev->keybit); // 触摸按键
    __set_bit(ABS_X, g_input_dev->absbit);     // X坐标
    __set_bit(ABS_Y, g_input_dev->absbit);     // Y坐标

    /* 设置3: 事件参数?*/
    input_set_abs_params(g_input_dev, ABS_X, 0, 0xffff, 0, 0); // 设置X坐标参数
    input_set_abs_params(g_input_dev, ABS_Y, 0, 0xffff, 0, 0); // 设置Y坐标参数

    error = input_register_device(g_input_dev); // 注册输入设备

    /* 硬件操作 */
    io = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取内存资源
    ts_con = ioremap(io->start, io->end - io->start + 1); // 映射内存资源

    g_irq = gpio_to_irq(gpio); // 将GPIO转换为IRQ
    error = request_irq(g_irq, input_dev_demo_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL); // 请求IRQ

    setup_timer(&ts_timer, // 初始化定时器
                ts_irq_timer, (unsigned long)NULL);

    return 0;
}

平台设备移除函数,设备树匹配表,

平台驱动结构体:探测函数(从设备树获取硬件信息。分配设置注册inputdev(驱动结构体),中断硬件相关消息),移除函数,驱动信息(驱动名称和设备树匹配表)

模块初始化和退出函数。

// 平台设备移除函数
static int input_dev_demo_remove(struct platform_device *pdev)
{
    del_timer_sync(&ts_timer); // 删除定时器
    iounmap(ts_con);           // 取消映射内存资源
    free_irq(g_irq, NULL);      // 释放IRQ资源
    input_unregister_device(g_input_dev); // 注销输入设备
    return 0;
}

// 设备树匹配表
static const struct of_device_id input_dev_demo_of_match[] = {
    { .compatible = "100ask,input_dev_demo", },
    { },
};

// 平台驱动结构体
static struct platform_driver input_dev_demo_driver = {
    .probe = input_dev_demo_probe, // 探测函数
    .remove = input_dev_demo_remove, // 移除函数
    .driver = {
        .name = "input_dev_demo", // 驱动名称
        .of_match_table = input_dev_demo_of_match, // 设备树匹配表
    }
};

// 模块初始化函数
static int __init input_dev_demo_init(void)
{
    printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return platform_driver_register(&input_dev_demo_driver);
}

// 模块退出函数
static void __exit input_dev_demo_exit(void)
{
    platform_driver_unregister(&input_dev_demo_driver);
}

module_init(input_dev_demo_init); // 注册初始化函数
module_exit(input_dev_demo_exit); // 注册退出函数

MODULE_LICENSE("GPL"); // 模块许可证
  • /:设备树的根节点。

  • input_dev_demo:设备节点,它是一个自定义的输入设备演示节点。

  • compatible:字符串属性,用于标识设备的兼容信息。内核通过这个属性来确定应该使用哪个驱动程序来驱动这个设备。"100ask,input_dev_demo"是一个假设的兼容字符串,实际使用时应替换为正确的值。

  • reg:内存资源属性,它定义了设备的内存映射区域。<0x021B4000 16>表示设备使用的起始地址是0x021B4000,长度是16个字节。

  • //interrupt-parent = <&gpio1>;:被注释掉的属性,如果设备使用GPIO作为中断源,这个属性应该指向GPIO控制器的节点。

  • //interrupts = <5 IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING>;:同样被注释掉的属性,如果设备使用GPIO中断,这里定义了中断的类型和编号。IRQ_TYPE_EDGE_FALLINGIRQ_TYPE_EDGE_RISING表示中断类型是边沿触发,5是GPIO的中断号。

  • gpios:GPIO属性,用于定义设备使用的GPIO引脚。<&gpio1 5 1>表示GPIO控制器gpio1的第5个引脚被用作输入设备,1表示GPIO引脚的配置(例如,作为中断引脚)。

设备树中的注释部分(以//开头)提供了额外的配置示例,但这些行在当前配置中被忽略了。如果设备确实需要通过GPIO中断来工作,你需要取消注释并可能修改这些行,以匹配你的硬件配置。

在实际使用中,你需要根据实际的硬件设计来调整设备树中的属性,确保它们正确地反映了你的硬件配置。一旦设备树被编译并加载到内核中,内核将使用这些信息来自动配置和初始化硬件设备

/ {
	input_dev_demo {
		compatible = "100ask,input_dev_demo";
		reg = <0x021B4000 16>;
		//interrupt-parent = <&gpio1>;
		//interrupts = <5 IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING>;
		gpios = <&gpio1 5 1>;
	};
};

 4. GPIO按键驱动分析与使用

4.1 驱动程序框架

组件说明:

  1. input.c

    • 这通常指的是内核源代码中处理输入子系统的C语言文件。它包含定义输入设备如何工作的代码。
  2. platform_bus_type

    • 这是一个内核中的总线类型,用于将平台设备连接到内核的设备模型。平台总线允许驱动程序通过设备树或ACPI表等机制来发现和配置设备。
  3. input_dev

    • 代表一个输入设备的结构体(通常是struct input_dev),它包含了输入设备的所有相关信息,如设备名称、类型、按键映射、事件处理函数等。
  4. input_handler

    • 这是一个处理输入事件的内核组件,它负责将输入事件传递给注册的处理器或驱动程序。
  5. probe

    • 这是一个在设备驱动程序中常见的函数,用于初始化和配置设备。在这个上下文中,它可能指的是平台驱动程序的probe函数,该函数在设备被发现时被调用。
  6. 设备树

    • 设备树(Device Tree)是一个数据结构,用于描述硬件的配置。它允许内核在启动时自动发现硬件并加载相应的驱动程序。
  7. platform_device

    • 这是一个内核中用于表示平台设备的抽象。它包含了设备的资源和配置信息,通常通过设备树或ACPI表定义。
  8. platform_driver

    • 这是一个内核中的驱动程序结构体,用于定义如何探测、初始化和管理平台设备。它通常包含proberemoveshutdown等回调函数。

关系说明:

  • platform_bus_typeplatform_device 之间的关系:

    • 平台设备通过平台总线连接到内核的设备模型。
  • platform_deviceplatform_driver 之间的关系:

    • 平台驱动程序负责管理平台设备。驱动程序的probe函数在设备被发现时被调用,用于初始化设备。
  • input_devinput_handler 之间的关系:

    • 输入设备通过输入处理器与内核的输入子系统交互。输入处理器负责处理输入事件并传递给相应的驱动程序。
  • probe 函数的作用:

    • 在设备探测过程中,probe函数会分配、设置和注册input_dev结构体。这涉及到初始化设备、设置事件类型和按键映射,以及注册设备到内核的输入子系统。

图示流程:

  1. 设备树定义了platform_device的配置。
  2. 内核通过platform_bus_type发现platform_device
  3. platform_driverprobe函数被调用,执行以下步骤:
    • 分配和初始化input_dev结构体。
    • 设置input_dev的属性,如名称、物理路径、事件类型和按键映射。
    • 注册input_dev到内核的输入子系统。
  4. input_handler处理来自input_dev的输入事件。

这个图示提供了一个高层次的视图,展示了Linux内核中输入设备驱动程序的组件和它们之间的交互方式。

4.2 设备树讲解

属性:

  • 必备:compatible = "gpio-keys";

  • 可选:

    • autorepeat: 表示自动重复,按下按键不松开,驱动会自动重复上报按键值

  • 对于每一个GPIO按键,都是一个子节点,有这些属性:

    • gpios:使用哪个GPIO

    • interrupts:对应的中断

    • linux,code:对应的按键值

    • 注意gpiosinterrupts至少要保留一个,不能都省略

    • debounce-interval: 消除抖动的间隔,单位:ms,默认是5ms

gpio-keys {
	compatible = "gpio-keys";
	pinctrl-names = "default";

	user1 {
		label = "User1 Button";
		gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
		gpio-key,wakeup;
		linux,code = <KEY_1>;
	};

	user2 {
		label = "User2 Button";
		gpios = <&gpio4 14 GPIO_ACTIVE_LOW>;
		gpio-key,wakeup;
		linux,code = <KEY_2>;
	};
};

4.3 gpio_keys.c驱动程序分析

4.3.1 套路

  • 根据设备树获得硬件信息:哪个GPIO、对于什么按键

  • 分配/设置/注册input_dev结构体

  • request_irq: 在中断处理函数中确定按键值、上报按键值

    • 有两种IRQ函数

    • gpio_keys_gpio_isr:设备树中的用gpios来描述用到的引脚

    • gpio_keys_irq_isr:设备树中的用interrupts来描述用到的引脚

4.3.2 gpio_keys_gpio_isr分析

理想状况是:按下、松开按键,各产生一次中断,也只产生一次中断。 但是对于机械开关,它的金属弹片会反复震动。GPIO电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。 如果不处理抖动的话,用户只操作一次按键,会发生多次中断,驱动程序可能会上报多个数据。

怎么处理按键抖动?

  • 在按键中断程序中,可以循环判断几十亳秒,发现电平稳定之后再上报

  • 使用定时器

显然第1种方法太耗时,违背“中断要尽快处理”的原则,你的系统会很卡。

怎么使用定时器?看下图:

核心在于:在GPIO中断中并不立刻记录按键值,而是修改定时器超时时间,10ms后再处理。
如果10ms内又发生了GPIO中断,那就认为是抖动,这时再次修改超时时间为10ms。
只有10ms之内再无GPIO中断发生,那么定时器的函数才会被调用。
在定时器函数中上报按键值。

4.3.3 gpio_keys_irq_isr分析

有个变量key_pressed,用来表示当前按键状态:初始值是false,表示按键没有被按下。

  • 发生中断

    • 上报"按下的值":input_event(input, EV_KEY, button->code, 1); input_sync(input);

    • 如果不延迟(!bdata->release_delay)

    • 马上上报"松开的值":input_event(input, EV_KEY, button->code, 0); input_sync(input);

    • 如果延迟(bdata->release_delay)

    • 启动定时器,过若干毫秒再上报"松开的值"

  • 所以,使用gpio_keys_irq_isr时,一次中断就会导致上报2个事件:按下、松开

  • 缺点:无法准确判断一个按键确实已经被松开了

 4.3.4 实验步骤

1.设置工具链;

2.配置内核;(QEMU的内核里已经配置了GPIO按键的设备树,只需要编译出gpio_keys驱动程序即可。 配置内核:执行make menuconfig

3.编译驱动

4.启动QEMU

5.挂载NFS、实验

5. I2C接口触摸屏驱动分析

图片展示了一个简化的流程图,描述了在Linux内核中,I2C输入设备驱动程序的组件和它们之间的交互方式。下面是对这个图的分析:

组件说明:

  1. input.c

    • 这可能指的是内核中处理输入子系统的源代码文件。
  2. i2c_bus_type

    • 表示I2C总线类型,用于I2C设备通信。
  3. input_dev

    • 表示输入设备的结构体,包含了输入设备的所有相关信息。
  4. (input_handler)

    • 表示输入处理器,用于处理来自输入设备的事件。
  5. i2c_client

    • 表示I2C设备的客户端结构体,代表I2C总线上的一个设备。
  6. i2c_driver

    • 表示I2C设备的驱动程序,负责管理I2C设备。
  7. 设备树

    • 用于在嵌入式Linux系统中描述硬件配置的数据结构。

流程说明:

  • 图中展示了I2C设备驱动程序与输入设备驱动程序的交互过程。

  • 设备树

    • 包含了I2C设备和输入设备的配置信息。
  • i2c_bus_typei2c_client

    • I2C总线类型用于连接I2C设备。每个I2C设备在系统中都是一个i2c_client实例。
  • i2c_driver

    • 负责探测和初始化I2C设备。它包含一个probe函数,当I2C设备被探测到时,这个函数会被调用。
  • 比较和匹配

    • 如果i2c_driver中的probe函数发现设备匹配(例如,通过检查设备地址或ID),它将为该设备分配资源并进行初始化。
  • probe

    • 当一个I2C设备被探测到,并且i2c_driver的probe函数确定它管理这个设备时,这个函数会被调用。
  • 分配/设置/注册input_dev

    • 在probe函数中,i2c_driver将为输入设备分配和设置input_dev结构体,然后注册到内核的输入子系统中。
  • (input_handler)

    • 一旦input_dev被注册,它将与一个输入处理器关联,该处理器会处理来自input_dev的事件。

图解流程:

  1. 设备树定义了I2C设备的配置。
  2. i2c_bus_type 用于I2C设备之间的通信。
  3. I2C设备在内核中表示为 i2c_client
  4. 当I2C设备被探测到时,内核查找合适的 i2c_driver
  5. i2c_driver 的 probe 函数被调用,如果设备匹配:
    • 为输入设备分配和设置 input_dev
    • 注册 input_dev 到内核的输入子系统。
  6. input_dev 与 (input_handler) 关联,以便处理输入事件。

这个图示提供了一个高层次的视图,展示了Linux内核中I2C输入设备驱动程序的组件和它们之间的交互方式。实际的驱动程序开发会涉及更多的细节,如错误处理、资源管理、电源管理等。

这张图片描述了Linux系统中输入设备驱动的架构和数据流,从硬件层到用户空间的交互过程。以下是核心内容的整理:

用户空间 (User Space)

  • 应用程序 (APP):
    • 应用程序可以直接访问输入设备的节点,例如/dev/input/event0/dev/input/event1等。
    • 应用程序可以通过特定的库,如ts liblibinput,来使用输入设备。

内核空间 (Kernel Space)

  • 文件操作结构体 (struct file_operations):

    • evdev_fops提供了一组函数,用于处理用户空间对输入设备的文件操作。
    • 包括openreleasereadwriteioctlpoll等操作。
  • Input Handler:

    • 负责承上启下,接收来自硬件的输入事件,并将其转换为标准的输入事件向上汇报。
    • evdev.c文件包含了处理输入事件的核心代码。
  • Input Core:

    • 位于内核空间,接收来自底层硬件的输入事件,并转发给上层的处理器(handler)。
    • input.c文件包含了输入核心的实现。
  • Input Device:

    • 硬件相关的驱动,从硬件获取数据。
    • 使用input_dev结构体表示输入设备,并转换为标准的输入事件向上汇报。

硬件 (Hardware)

  • 键盘/GPIO键/鼠标/触摸屏:
    • 这些是输入设备的实例,它们通过硬件驱动与内核空间交互。

用户空间与内核空间的交互

  • open: 打开输入设备文件。
  • ioctl: 控制输入设备的行为。
  • poll: 等待输入事件发生。
  • read: 读取输入事件数据。
  • release: 释放输入设备文件。
  • write: 可选,某些输入设备支持写操作。
  • fasync: 异步通知。
  • flush: 清除待处理的输入事件。
  • no_llseek: 输入设备文件不支持寻址。

内核空间内部组件

  • evdev_fops:

    • 为用户空间提供访问输入设备的接口。
    • 处理来自用户空间的请求,如读取输入事件。
  • evdev_open:

    • 打开设备时调用,初始化设备。
  • evdev_release:

    • 释放设备时调用,执行清理工作。
  • evdev_read:

    • 读取输入事件数据。
  • evdev_write:

    • 可选,某些情况下写入数据到设备。
  • evdev_ioctl:

    • 执行输入设备的控制命令。
  • evdev_poll:

    • 实现轮询机制,等待输入事件发生。
  • evdev_fasync:

    • 异步通知处理。
  • evdev_flush:

    • 清除待处理的输入事件。
  • no_llseek:

    • 输入设备文件不支持常规的寻址操作。

总结

这张图片展示了Linux输入系统的工作流程,从物理硬件获取输入数据,通过硬件驱动转换为标准事件,然后通过内核的输入子系统处理这些事件,最终提供给用户空间的应用程序使用。这个过程涉及到内核的多个层次和组件,以及它们之间的交互方式。

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值