一、设备驱动程序

  在上一篇随笔中已经分析,linux输入子系统分为设备驱动层、核心层和事件层。要利用linux内核中自带的输入子系统实现一个某个设备的操作,我们一般只需要完成驱动层的程序即可,核心层和事件层内核已经帮我们做好了。因此这篇随笔主要介绍按键操作设备驱动层的代码。

1.1设备驱动入口函数

  在设备驱动入口函数中我们需要做的事:(1)分配一个input_dev 结构体

                    (2)设置这个input_dev 结构体

                    (3)调用input_register_device注册这个input_dev

                    (4)完成硬件相关操作:如注册中断处理函数,添加定时器等

static int button_init(void)
{
int i;
int err;
/* 1. 分配一个 input_dev 结构体*/
button_dev = input_allocate_device();
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, button_dev->evbit);
/* 2.2 能产生这类事件下的哪些操作: L S ENTER LEFTSHIT*/
set_bit(KEY_L, button_dev->keybit);
set_bit(KEY_S, button_dev->keybit);
set_bit(KEY_ENTER, button_dev->keybit);
set_bit(KEY_LEFTSHIFT, button_dev->keybit);
/* 3. 注册 */
input_register_device(button_dev);
/* 4. 硬件相关操作*/
gpkcon = ioremap(GPKCON_PA, ); //io口映射
gpkdat = ioremap(GPKDAT_PA, );
gpndat = ioremap(GPNDAT_PA, ); init_timer(&button_timer); // 初始化定时器
button_timer.function = button_timer_function;// 指定定时器的处理函数
add_timer(&button_timer); // 添加定时器
for (i=; i<; i++)
{
err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,button_irqs[i].name,(void *)&button_irqs[i]);
if (err)
break;
}
return ;
}

1.1.1在初始化iinput_dev结构体过程

  主要对其中的如下数组做了初始化,从而来确定该设备支持哪些事件,支持哪些操作。

      

  初始化时首先要确定设备能够产生哪一类事件

      事件的类型如下: 

      

      事件类型的设置主要对evbit[]数组中的相应位做设置: set_bit(EV_KEY, button_dev->evbit);

  然后再确定设备支持该类事件下的哪些操作

      例如:在相对坐标事件下可以支持如下操作

      

      在本次按键驱动程序中按键设备产生的事件自然是按键事件,按键事件支持KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT  4个操作,分别对应 L ,S, enter,shift

      set_bit(KEY_L, button_dev->keybit);

      set_bit(KEY_S, button_dev->keybit);

      set_bit(KEY_ENTER, button_dev->keybit;

      set_bit(KEY_LEFTSHIFT, button_dev->keybit);

1.1.2 注册输入设备input_register_device()

  这个函数在上一篇博客中已经做了简要分析,这里在提一下input_register_device()中做了哪些事

    (1) 设置同步事件、清除KEY_RESERVED、清除bitmasks中没有提到的位

    (2) 初始化定时器,确定定时器的处理函数。这里定时器与重复上报事件有关,注意在事件类型中有EV_REP事件,设置这个事件在ev_bit中的相应位,就可以重复上报事件。

    (3) 设置getkeycode 和 setkeycode 函数

    (4) device_add, input_dev包含的device结构注册到Linux设备模型中,在sysfs文件系统中可以看到增加了设备input1

        

    (5)list_add_tail  在上一篇博客中已经介绍了

    (6)遍历iinput_handler_list 中的事件处理器 input_handler, 与input_handler 进行匹配 连接操作。 具体的连接操作在下边分析。

1.1.3 硬件相关操作

  定时器、IO映射、注册中断

2. 中断处理函数

static void button_timer_function(unsigned long data)
{
int num;
int tmp;
struct button_irq_desc* pindesc = irq_pdesc;
if (! pindesc)
return;
num = pindesc->number;
tmp = *gpndat;
input_event(button_dev, EV_KEY,button_irqs[num].key_val , !(tmp &(<<num)));
input_sync(button_dev);
return;
}
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
/* 10 ms 则 hz/100 100 ms 则 hz/10*/
irq_pdesc = (struct button_irq_desc *)dev_id;
mod_timer(&button_timer, jiffies+HZ/);
return IRQ_RETVAL(IRQ_HANDLED);
}

 中断处理函数包括两部分:定时中断用于消除按键抖动

             按键中断处理函数主要用来调

整定时器事件

 当有按键中断发生时,我们需要上报事件:

  input_event(button_dev, EV_KEY,button_irqs[num].key_val , !(tmp &(1<<num)));
  input_sync(button_dev);

 2.1.1 input_event 上报事件函数

void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
// 判断是否支持这种事件
if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags);
add_input_randomness(type, code, value);
// 执行事件处理函数
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
   .....
switch (type) {
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) {
if (value != ) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
else
input_stop_autorepeat(dev);
}
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = false;
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}

  大概可以分析出事件处理函数要么执行input_dev下的event, 要么执行input_handler下的event函数,至于执行哪一个和disposition这个变量有关。

  需要注意的是如果执行的是input_dev下边的event,那么应该只会执行一个event函数,如果要是执行的是input_handler下的event函数,那么会执行input_pass_event 函数。这个函数中通过input_dev->h_list链表上挂的input_handler结构体,找到与之匹配的input_handler。然后执行input_handler下的event函数。      如果input_dev与多个input_handler事件处理器匹配了,那么当设备驱动上报事件时,并且要执行input_handler中的event函数,那么会依次执行这些匹配好的input_handler的event函数。在接下的实验中可以看到这一点。

二、程序执行部分过程分析:

  2.1 input_dev与 input_handler 的连接

  因为我们的程序中已经注册了evdev_handler事件处理器(在上一篇博客中已经分析过了input_register_handler)和 kbd_handler 事件处理器(在keyboard.c 文件中 kbd_init-->input_register_handler(&kbd_handler)) ,当在驱动程序中input_register_device后,设备驱动和这两个事件处理器进行匹配和连接操作。

  2.1.1 button_dev 和 evdev_handler 连接

  匹配完成之后会执行连接操作,连接操作执行的是evdev_handler->connet 函数,这个函数已经在前一篇博客中分析了,这里只简述函数执行过程和效果。

  (1) 分配evdev 结构体

  (2) 设置evdev 结构体下边的 handle 和 dev

    (3) 注册evdev 下边的handle ,在上一篇博客中已经分析了这个handle是连接input_dev 和 input_handler 的桥梁

   (4) 添加设备device_add(&evdev->dev)

     执行完这个函数应该可以看到在/dev/input/下出现even0 设个设备

      

     可以看到主设备号为13, 次设备号为64, 这里次设备号=64+minor, 因为evdev_handler 还没有和任何设备建立连接,所以在input_table[] 数组中还没存放任何evdev结构体,因此minor的值为0, 故此设备号为64+0=64。

  2.1.2 button_dev 和 kbd_handler 的匹配过程

static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct input_handle *handle;
int error; handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return -ENOMEM; handle->dev = dev;
handle->handler = handler;
handle->name = "kbd"; error = input_register_handle(handle);
if (error)
goto err_free_handle; error = input_open_device(handle);
if (error)
goto err_unregister_handle; return ; err_unregister_handle:
input_unregister_handle(handle);
err_free_handle:
kfree(handle);
return error;
}

  这里的连接过程比较简单,没有上一个连接过程那么复杂,但是可以看到主要的数据结构input_handle并没有少,因为这个结构体是input_dev和input_handler的联系桥梁,复杂的数据结构之间的关系可以使input_dev 可以访问到input_handle从而访问到input_handler。(在事件上报函数中分析过怎样从input_dev找到input_handler)

2.2  事件上报函数分析

  前文已经提到了button_dev 与 evdev_handler 和 kbd_handler 匹配连接成功,那么在事件上报的时候,就会分别执行这两个事件处理器中的event函数。

  2.2.1 evdev->event 函数

  这个函数已经在上一篇博客中提前分析过了。

  2.2.2 kbd->event 函数

static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
/* We are called with interrupts disabled, just take the lock */
spin_lock(&kbd_event_lock); if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
kbd_rawcode(value);
if (event_type == EV_KEY)
kbd_keycode(event_code, value, HW_RAW(handle->dev)); spin_unlock(&kbd_event_lock); tasklet_schedule(&keyboard_tasklet);
do_poke_blanked_console = ;
schedule_console_callback();
}

三、实验效果

  3.1 执行hexdump /dev/input/event0

     依次按下 l   s   enter 对应的按键:l 对应0x26  s 对应0x1f  enter对应0x1c

      

  3.2  执行cat /dev/tty1

     并且将标准输入重定向到/dev/tty1  依次按下 l   s   enter 对应的按键

      

     可以看到依次按下l s enter 后执行了ls 命令

     同时需要强调的是:上边的两个命令的执行在不同的shell 下,但是只按下了一次l s enter后分别在两个shell中打印了不同的结果。这就证明了之前所说的一个button_dev 与 evdev_handler 和 kbd_handler 建立了连接,上报一次事件,会分别调用这两个事件处理器的event 函数。

    

   

  

  

linux-2.6.38 input子系统(用输入子系统实现按键操作)的更多相关文章

  1. 嵌入式Linux驱动学习之路(十六)输入子系统

    以前写的一些输入设备的驱动都是采用字符设备处理的.问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的.不同类别的输入设备进行统一的驱动,所以 ...

  2. Linux 混杂设备、外部中断和输入子系统

    混杂设备也是一种字符设备,主设备号固定为10.相对于普通字符设备驱动,它不需要自己去生成设备文件. 1.声明使用的头文件 #include <linux/miscdevice.h> 2.定 ...

  3. 10. linux输入子系统/input 设备【转】

    转自:https://www.cnblogs.com/crmn/articles/6696819.html 按键事件信息之上报绝对事件信息之上报相对事件信息之上报功能键驱动编写多点触控事件的上报 只产 ...

  4. linux输入子系统(6)-input子系统介绍及结构图

    注:本系列转自: http://www.ourunix.org/post/290.html input子系统介绍         输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一 ...

  5. Linux驱动之一个简单的输入子系统程序编写

    的在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层.核心层.事件层共同组成的.其中核心层提供一些设备层与事件层公用的函数,比如说注册函数.反注册函数.事件到来的处理函数等等: ...

  6. Linux输入子系统 转载

    NQian 记录成长~ 首页 新随笔 联系 订阅 管理 随笔 - 305  文章 - 0  评论 - 254 12.Linux之输入子系统分析(详解)   在此节之前,我们学的都是简单的字符驱动,涉及 ...

  7. Linux输入子系统(转)

    Linux输入子系统(Input Subsystem) 1.1.input子系统概述 输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中 ...

  8. 12.Linux之输入子系统分析(详解)

    版权声明:本文为博主原创文章,转载请标注出处:   在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时 ...

  9. linux输入子系统

    linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputCore)和输入子系统设备驱 ...

随机推荐

  1. leetcode 将一个二维矩阵进行90度旋转

    import numpy as np import math if __name__ == '__main__': def rotate(matrix): n = len(matrix[0]) for ...

  2. 4.1 python类的特殊成员,偏函数,线程安全,栈,flask上下文

    目录 一. Python 类的特殊成员(部分) 二. Python偏函数 1. 描述 2. 实例一: 取余函数 3. 实例二: 求三个数的和 三. 线程安全 1. 实例一: 无线程,消耗时间过长 2. ...

  3. golang继承与接口

    继承 结构体 Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但Go语言放弃了包括继 承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性. 组合 ...

  4. 在先电IAAS平台中,搭建先电bigdata平台

    以两台节点为例来组件Hadoop分布式集群,这里采用的系统版本为Centos7,如下表所示: 主机名 内存 硬盘 IP地址 角色 master 8192MB 100G 192.168.200.131 ...

  5. ffmpeg学习笔记-初识ffmpeg

    ffmpeg用来对音视频进行处理,那么在使用ffmpeg前就需要ffmpeg有一个大概的了解,这里使用雷神的ppt素材进行整理,以便于复习 音视频基础知识 视频播放器的原理 播放视频的流程大致如下: ...

  6. bootstrap基础学习【菜单、按钮、导航】(四)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. 多路径IO

    多路径IO(MPIO)是一个微软架构,通过在存储设备和windows操作系统之间提供一个备用数据路径,来减轻主机总线适配器(HBA)故障的影响,MPIO能够支持多达32个备用路径,来增加Windows ...

  8. luogu P3031 [USACO11NOV]高于中位数Above the Median (树状数组优化dp)

    链接:https://www.luogu.org/problemnew/show/P3031 题面: 题目描述 Farmer John has lined up his N (1 <= N &l ...

  9. Android使用glide加载.9图片的方法

    我们在开发过程中会经常使用.9图片, 因为它可以使图片拉伸的时候,保证其不会失真. 而我们把.9图片放在服务器端,通过glide直接加载,会报错. 我们的解决方法是 通过sdk的aapt工具 把.9图 ...

  10. 二维码生成工具类java版

    注意:这里我不提供所需jar包的路径,我会把所有引用的jar包显示出来,大家自行Google package com.net.util; import java.awt.BasicStroke; im ...