当前位置 : 主页 > 网络编程 > 其它编程 >

input子系统驱动编写(按键)

来源:互联网 收集:自由互联 发布时间:2023-07-02
之前已经分析过了编写一个驱动程序主要有以下几个步骤自己设定或由系统自动分配驱动设备的主设备号编写设备操作函数drv 之前已经分析过了编写一个驱动程序主要有以下几个步骤
之前已经分析过了编写一个驱动程序主要有以下几个步骤自己设定或由系统自动分配驱动设备的主设备号编写设备操作函数drv

之前已经分析过了编写一个驱动程序主要有以下几个步骤

  • 自己设定或由系统自动分配驱动设备的主设备号
  • 编写设备操作函数drv_open、drv_read、drv_write、drv_close等
  • 构造file_operations结构体将上一步编写的操作函数赋值给结构体内的.open、.read、.write、.poll、.fasync等
  • 注册驱动程序调用register_chrdev(major, name, fops);
  • 编写入口函数和出口函数。
  • 但输入子系统驱动框架将以上步骤分开了它是由设备层、核心层、事件层共同组成的。其中核心层提供一些设备层与事件层公用的函数比如说注册函数、反注册函数、事件到来的处理函数等等完成了驱动程序编写的第1-4步。但在input框架的file_operations中只含有一个open函数正是通过它调用特定input_handler结构体其里面包含有根据不同次设备号映射到不同的输入设备大类的**.fops**事件处理层其实在Linux内核里面已经帮我们写好了很多有关的事件而设备层就是我们要新添加到输入系统的具体设备相关的程序了。

    在分析输入子系统框架时我们已经知道内核对不同类型的输入设备已经抽象出了不同的handler进行处理device层实现纯硬件的操作我们可以根据所实现驱动的功能对device层进行设计主要是内容是当检测有效输入发送调用input_event函数向handler层上报结果即可。

    1、编写符合输入子系统框架的驱动程序步骤

  • 分配一个input_dev结构体调用input_allocate_device()或者devm_input_allocate_device(struct input_dev*)实现
  • 编写函数设置input_dev结构体选择input设备的事件类型调用set_bit()函数实现
  • 注册input_dev结构体调用input_register_device(struct input_dev*)函数实现
  • 编写硬件相关代码注册中断处理函数比如键盘设备需要编写按键的抬起、放下触摸屏设备需要编写按下、抬起、绝对移动鼠标设备需要编写单击、抬起、相对移动并且需要在必要的时候提交硬件数据键值/坐标/状态等等即上报输入事件。
  • 当提交输入设备产生的输入事件之后需要调用void input_sync(struct input_dev *dev)函数来通知输入子系统以处理设备产生的完整事件。
  • Linux输入子系统支持的事件类型在include/linux/input.h中

    EV_SYN 0x00 同步事件EV_KEY 0x01 按键事件EV_REL 0x02 相对坐标(如鼠标移动报告相对最后一次位置的偏移)EV_ABS 0x03 绝对坐标(如触摸屏或操作杆报告绝对的坐标位置)EV_MSC 0x04 其它EV_SW 0x05 开关EV_LED 0x11 按键/设备灯EV_SND 0x12 声音/警报EV_REP 0x14 重复EV_FF 0x15 力反馈EV_PWR 0x16 电源EV_FF_STATUS 0x17 力反馈状态EV_MAX 0x1f 事件类型最大个数和提供位掩码支持

    Linux输入子系统提供的设备驱动层上报输入事件的函数在include/linux/input.h中

    void input_event(struct input_dev* dev, unsigned int type, unsigned int code, int value); //上报指定的type、code的输入事件void input_report_key(struct input_dev *dev, unsigned int code, int value);//上报按键事件void input_report_rel(struct input_dev *dev, unsigned int code, int value);//上报相对坐标事件void input_report_abs(struct input_dev *dev, unsigned int code, int value);//上报绝对坐标事件

    2、编写符合input系统框架的按键驱动程序

    现在我们开始使用内核的输入子系统框架将自己编写驱动程序融入其中编写更通用的按键驱动程序。这里以JZ2440开发板上的4个按键作为输入子系统的按键我们编写的驱动程序实现将开发板上的4个按键分别映射成键盘上不同键值代表键盘上的字母L、S和Enter回车。这几个值是在include\linux\input.h中被定义的。

    2.1 编写入口函数、出口函数搭建好框架

    /* 参考drivers\input\keyboard\gpio_keys.c */#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int buttons_init(void){/* 1.分配一个input_dev结构体。 *//* 2.设置input_dev结构体。 *//* 3.注册input_dev结构体。 *//* 4.硬件相关的操作。 */}static void buttons_exit(vod){}module_init(buttons_init);module_exit(buttons_exit);MODULE_LICENSE("GPL");

    2.1.1 分配一个input_dev结构体

    /* 以下结构体定义位于函数外属于全局变量*/static struct input_dev *buttons_dev;/*以下语句位于buttons_init函数中*/buttons_dev input_allocate_device();if (!buttons_dev)return -ENOMEM;

    2.1.2 设置input_dev结构体

    先看以下Input_dev结构体的定义

    image-20210714230647641

    • evbit用来设置该设备能产生哪类事件类型在第1节有具体描述输入子系统所支持的所有事件类型在此我们选择按键事件EV_KEY和EV_REP代码如下

      • set_bit(EV_KEY, buttons_input->evbit);//按键事件set_bit(EV_REP, buttons_input->evbit);//重复事件长按按键会重复输入按键值

    • keybit用来设置该设备能产生哪些按键值在此我们设置本节开头说的键盘上的L、S、左shift、enter为了可以在开发板上执行ls命令。按键码定义在include\linux\input.h中截取其中一小部分

      • #define KEY_ENTER 28//enter的按键码#define KEY_S 31//S的按键码#define KEY_L 38//L的按键码#define KEY_LEFTSHIFT 42//leftshift的按键码

        设置输入事件类型的哪一种按键的代码

        set_bit(KEY_L, buttons_input->keybit);set_bit(KEY_S, buttons_input->keybit);set_bit(KEY_ENTER, buttons_input->keybit);set_bit(KEY_LEFTSHIFT, buttons_input->keybit);

    • relbit表示能产生哪些相对位移事件, 例如滚轮

    • absbit表示能产生哪些绝对位移事件。

    2.1.3 注册input_dev结构体

    注册的功能其实就是将当前设备buttons_dev放到input_dev链表中去然后和事件层的evdev_handler逐个比较通过比较id和id.table[]如果匹配则会调用evdev_handler中的.connect函数产生一个handle结构体只含有.handler和.dev一个指向设备一个指向handler将当前设备与handler建立起关联将当前dev及其匹配的handler放到各自的.h_list中。代码如下

    input_register_device(buttons_input);//注册设备驱动

    2.1.4 硬件相关的操作

    • 定义各按键描述结构体

    /* 以下2个结构体定义位于函数外属于全局变量*/struct pin_desc{int irq;char *name;unsigned int pin;unsigned int key_val;};struct pin_desc pins_desc[4] {{IRQ_EINT0, "S2", S3C2410_GPF(0), KEY_L},{IRQ_EINT2, "S3", S3C2410_GPF(2), KEY_S},{IRQ_EINT11, "S4", S3C2410_GPG(3), KEY_ENTER},};

    • 初始化定时器防抖动

    /* 以下结构体定义位于函数外属于全局变量*/static struct timer_list buttons_timer;//定义一个定时器/*以下语句位于buttons_init函数中*/init_timer(//初始化定时器buttons_timer.function buttons_timer_function;//定义超时处理函数add_timer(//定义超时时间

    • 为每个按键注册中断

    /*以下语句位于buttons_init函数中*/for (i 0; i < 3; i)注册中断{request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, }

    • 所以整体buttons_init函数的代码如下

    static int buttons_init(void){int i;/* 1. 分配一个input_dev结构体 */buttons_dev input_allocate_device();;/* 2. 设置 *//* 2.1 能产生哪类事件 */set_bit(EV_KEY, buttons_dev->evbit);set_bit(EV_REP, buttons_dev->evbit);/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */set_bit(KEY_L, buttons_dev->keybit);set_bit(KEY_S, buttons_dev->keybit);set_bit(KEY_ENTER, buttons_dev->keybit);/* 3. 注册 */input_register_device(buttons_dev);/* 4. 硬件相关的操作 */init_timer(buttons_timer.function buttons_timer_function;add_timer(for (i 0; i < 3; i){request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, }return 0;}

    • 编写出口函数

    static void buttons_exit(void){int i;for (i 0; i < 3; i){free_irq(pins_desc[i].irq, }del_timer(input_unregister_device(buttons_dev);input_free_device(buttons_dev);}

    2.2 编写按键的中断处理函数

    /* 以下结构体指针定义位于函数外属于全局变量*/static struct pin_desc *irq_pd;static irqreturn_t buttons_irq(int irq, void *dev_id){/* 10ms后启动定时器 */irq_pd (struct pin_desc *)dev_id;mod_timer(HZ/100);return IRQ_RETVAL(IRQ_HANDLED);}

    2.3 编写定时器超时处理函数

    当有按键数据产生时调用input_event函数来上报事件该函数会从input_dev链表中的.h_list找到对应的.handler并调用里面的.event函数。

    static void buttons_timer_function(unsigned long data){struct pin_desc * pindesc irq_pd;unsigned int pinval;if (!pindesc)return;pinval s3c2410_gpio_getpin(pindesc->pin);if (pinval){/* 松开 : 最后一个参数: 0-松开, 1-按下 */input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);input_sync(buttons_dev);//上报同步事件}else{/* 按下 */input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);input_sync(buttons_dev);}}

    2.4 整体代码buttons.c

    /* 参考drivers\input\keyboard\gpio_keys.c *//* 运行于JZ2440开发板、Linux3.4.2内核、 gcc4.3.2*/#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include struct pin_desc{int irq;char *name;unsigned int pin;unsigned int key_val;};struct pin_desc pins_desc[4] {{IRQ_EINT0, "S2", S3C2410_GPF(0), KEY_L},{IRQ_EINT2, "S3", S3C2410_GPF(2), KEY_S},{IRQ_EINT11, "S4", S3C2410_GPG(3), KEY_ENTER},//{IRQ_EINT19, "S5", S3C2410_GPG(11), KEY_LEFTSHIFT},};static struct input_dev *buttons_dev;static struct pin_desc *irq_pd;static struct timer_list buttons_timer;static irqreturn_t buttons_irq(int irq, void *dev_id){/* 10ms后启动定时器 */irq_pd (struct pin_desc *)dev_id;mod_timer(HZ/100);return IRQ_RETVAL(IRQ_HANDLED);}static void buttons_timer_function(unsigned long data){struct pin_desc * pindesc irq_pd;unsigned int pinval;if (!pindesc)return;pinval s3c2410_gpio_getpin(pindesc->pin);if (pinval){/* 松开 : 最后一个参数: 0-松开, 1-按下 */input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);input_sync(buttons_dev);}else{/* 按下 */input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);input_sync(buttons_dev);}}static int buttons_init(void){int i;/* 1. 分配一个input_dev结构体 */buttons_dev input_allocate_device();;/* 2. 设置 *//* 2.1 能产生哪类事件 */set_bit(EV_KEY, buttons_dev->evbit);set_bit(EV_REP, buttons_dev->evbit);/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */set_bit(KEY_L, buttons_dev->keybit);set_bit(KEY_S, buttons_dev->keybit);set_bit(KEY_ENTER, buttons_dev->keybit);//set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);/* 3. 注册 */input_register_device(buttons_dev);/* 4. 硬件相关的操作 */init_timer(buttons_timer.function buttons_timer_function;add_timer(for (i 0; i < 3; i){request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, }return 0;}static void buttons_exit(void){int i;for (i 0; i < 3; i){free_irq(pins_desc[i].irq, }del_timer(input_unregister_device(buttons_dev);input_free_device(buttons_dev);}module_init(buttons_init);module_exit(buttons_exit);MODULE_LICENSE("GPL");

    3、测试

    3.1 利用hexdump测试

    hexdump /dev/event1//等价于执行open /dev/event1; read();秒 微秒 类 code value0000000 0bb2 0000 0e48 000c 0001 0026 0001 00000000010 0bb2 0000 0e54 000c 0000 0000 0000 00000000020 0bb2 0000 5815 000e 0001 0026 0000 00000000030 0bb2 0000 581f 000e 0000 0000 0000 0000

    3.2 如果当前没有启动QT则按如下方法

    • 执行cat /dev/tty1
    • 开发板上按下:S2、S3、S4
    • 可以看到ls

    image-20210717121010117

    或者

    • 执行exec 0
    • 可以使用开发板上的键盘来输入ls命令

    image-20210717120914031

    3.3 如果已经启动了QT

    • 先打开记事本
    • 然后按键S2、S3、S4
    • 可以看到记事本上显示ls

    4、小结

    • 打开输入设备后发生了什么

      • drivers/input/input.c
        • input_init > err register_chrdev(INPUT_MAJOR, “input”,
      • static const struct file_operations input_fops { .owner THIS_MODULE, .open input_open_file, };
    • 怎么读按键

      • intput_open_file
        • struct input_handler *handler input_table[iminor(inode) >> 5];
        • new_fops fops_get(handler->fops) // > new_fops;
        • err new_fops->open(inode, file);
      • APP: read > … >file->f_op->read
    • input_table数组由谁构造

      • input_register_handler
    • 如何注册input_handler

      • input_register_handler
        • input_table[handler->minor >> 5] handler; // 放入数组
        • list_add_tail( // 放入链表
        • // 对于每个input_dev调用input_attach_handler
        • list_for_each_entry(dev, // 根据input_handler的id_table判断能否支持这个input_dev
  • 如何 注册输入设备:

    • input_register_device

      • list_add_tail( // 放入链表
      • // 对于每一个input_handler都调用input_attach_handler list_for_each_entry(handler, // 根据input_handler的id_table判断能否支持这个input_dev

    input_attach_handler

    • id input_match_device(handler->id_table, dev);
    • error handler->connect(handler, dev, id);

    注册input_dev或input_handler时会两两比较左边的input_dev和右边的input_handler, 根据input_handler的id_table判断这个input_handler能否支持这个input_dev 如果能支持则调用input_handler的connect函数建立"连接"

  • 输入子系统驱动

    • 怎么建立连接
  • 分配一个input_handle结构体
  • input_handle.dev input_dev; // 指向左边的input_dev input_handle.handler input_handler; // 指向右边的input_handler
  • 注册 input_handler->h_list inpu_dev->h_list
  • evdev_connect

  • evdev kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
  • // 设置 evdev->handle.dev dev; // 指向左边的input_dev evdev->handle.name evdev->name; evdev->handle.handler handler; // 指向右边的input_handler evdev->handle.private evdev;
  • // 注册 error input_register_handle(
    • 怎么读按键

      • APP应用程序执行: read

      • 。。。。

      • evdev_read

        • // 无数据并且是非阻塞方式打开则立刻返回 if (client->head client->tail

          // 否则休眠 retval wait_event_interruptible(evdev->wait, client->head ! client->tail || !evdev->exist);

      • 谁来唤醒 evdev_event wake_up_interruptible(

      • evdev_event被谁调用 猜应该是硬件相关的代码input_dev那层调用的 在设备的中断服务程序里确定事件是什么然后调用相应的input_handler的event处理函数 gpio_keys_isr

        • // 上报事件 input_event(input, type, button->code, !!state); input_sync(input);
        • input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
          • struct input_handle *handle;
          • list_for_each_entry(handle,
    【转自:阜宁网页开发 http://www.1234xp.com/funing.html 欢迎留下您的宝贵建议】
    上一篇:nodejs+mongodb运用
    下一篇:没有了
    网友评论