input子系统

input子系统介绍

  • 在linux系统中,输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备
  • 其一般的工作机制是:
    • 用户在按键、触摸等动作发生时产生一个中断
    • 然后cpu读取键值、坐标等数据,再放一个缓冲区
    • 字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。
  • 在Linux中,输入子系统是由:
    • 输入子系统设备驱动层
    • 输入子系统核心层(Input Core)
    • 输入子系统事件处理层(Event Handler)组成。


  • 其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;
  • 而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;
  • 而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。
  • 所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
  • 在 Linux中,输入子系统作为一个模块存在,向上,为用户层提供接口函数,向下,为驱动层程序提供统一的接口函数。其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系统通知驱动程序完成某项功能。


input子系统框架分析

linux-3.5内核

事件层

  • 核心代码在文件drivers/input/input.c


  • 子系统的初始化函数是input_init()


  • 该初始化函数最重要的部分就是注册一个字符设备:
    • INPUT_MAJOR 宏的值为 13
    • 驱动函数集合是input_fops
  • 驱动操作函数集合中只有一个open方法


  • open方法的代码简化如下:


    • 以次设备号为索引,找到一个handler
    • 将handler->fops拷贝给file->f_op
  • input_table[ ]数组分析
    • input_table[ ]是一个数组指针,指向一个struct input_handler结构;


    • struct input_handler结构在input.h中定义如下:


  • input_table[]数组添加成员的函数:


  • 在分析完上面的框架之后,那么谁去调用了inoput_register_handler()往input_table数组添加成员呢?
    • evdev.c、joydev.c 、mousedev.c这些文件都在模块入口函数中调用input_register_handler()函数注册一个handler到数组input_table[]中



事件层小结

  • 上面的分析基本上将事件层分析完毕
    • 事件层,基本上将输入设备的事件归纳为joykey, evdev, mouse, keyboard等的事件;
    • 同一类的事件在休眠、唤醒的代码基本相同的如;
      • handler->fops->read()函数中休眠;
      • handler->event()函数中唤醒进程数据到达;
    • 也就是说,要向应用层发送什么事件,只需要调用相应的 handler->event()函数即可;
  • 也就是说,我们的驱动只需要注册一个中断处理函数,在中断处理函数里面调用handler->event()函数就可以了!

核心层

  • 在input.c定义了两个链表,先来分析这两个链表
    • input_dev_list
    • input_handler_list
  • input_dev_list链表中节点的类型是:
    struct input_dev表示每个硬件设备
  • input_handler_list链表中的节点类型是:
    struct input_handler表示事件类型
  • 对链表input_handler_list的操作函数:



    将参数handler添加到链表中,并且对于链表input_handler_list中的每个成员都调用input_attach_handler函数

  • 对链表input_dev_list的操作函数:



    将参数dev添加到链表中,并且对于链表input_dev_list中的每个成员都调用input_attach_handler函数

  • input_attach_handler函数分析



    匹配handler与dev的ID,如果匹配调用handler->connect()方法


  • 框架图


  • 发送事件函数
    • 系统中,内核已经封装了发送事件的函数
      void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
    • 跟踪这个函数如下:
input_event()
    input_handle_event()
        input_pass_event()
            handler->event()

通过input_dev找到与之关联的handler,调用handler的event函数

关键数据结构

  • struct input_handler 事件处理结构体,定义怎么处理事件的逻辑
  • struct input_handle 用来创建input_dev和input_handler之间关系的结构体
  • struct input_dev 物理输入设备的基本数据结构,包含设备相关的一些信息
  • input_handler是输入设备的事件处理接口,为处理事件提供一个统一的函数模板
    • 以evdev.c中的evdev_handler为例:
 static struct input_handler evdev_handler = {  
  .event = evdev_event, //向系统报告input事件,唤醒进程 
  .connect = evdev_connect, //和input_dev匹配,调用connect构建  
  .disconnect = evdev_disconnect,
  .fops = &evdev_fops, //event设备文件的操作方法 
  .minor = EVDEV_MINOR_BASE, //次设备号基准值  
  .name = "evdev",  
  .id_table = evdev_ids, //匹配规则
};
  • input_handle的主要功能是用来连接input_dev和input_handler


整体框架

input driver编写要点

  • 分配、注册、注销input设备
struct input_dev *input_allocate_device(void)
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
  • 设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息
    一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。
  • 如果需要,设置input设备的打开、关闭、写入数据时的处理方法
  • 在发生输入事件时,向子系统报告事件
  • 注册输入设备的函数为:
    int input_register_device(struct input_dev *dev)
  • 注销输入设备的函数为:
    void input_unregister_device(struct input_dev *dev)
  • 设备驱动通过set_bit()告诉input子系统它支持哪些事件,
  • 如下所示:
set_bit(EV_KEY, button_dev.evbit)
Struct iput_dev中有两个成员,一个是evbit;一个是keybit。
  • 分别用来表示设备所支持的事件类型和按键类型。
  • 事件类型:
EV_RST Reset
EV_KEY 按键           
EV_REL 相对坐标 
EV_ABS 绝对坐标     
EV_MSC 其它   
EV_LED LED      
EV_SND 声音   
EV_REP Repeat       
EV_FF 力反馈
  • 用于报告EV_KEY、EV_REL和EV_ABS事件的函数分别为:
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)
  • code:
    事件的代码。如果事件的类型是EV_KEY,该代码code为设备键盘代码。代码值0127为键盘上的按键代码,0x1100x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件
  • value:
    事件的值。如果事件的类型是EV_KEY,当按键按下时值为1,松开时值为0。
  • input_sync()用于事件同步,它告知事件的接收者:驱动已经发出了一个完整的报告。
    例如,在触摸屏设备驱动中,一次坐标及按下状态的整个报告过程如下:
input_report_abs(input_dev, ABS_X, x); //X坐标
input_report_abs(input_dev, ABS_Y, y); //Y坐标
input_report_abs(input_dev, ABS_PRESSURE, pres);   //压力
input_sync(input_dev); //同步

设备描述

  • Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过InputCore和Eventhandler最终到达用户空间。


- code:
事件的代码。如果事件的类型是EV_KEY,该代码code为设备键盘代码。代码值0~127为键盘上的按键代码,0x110~0x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件

evbit   事件类型
#define EV_SYN 0x00 /*表示设备支持所有的事件*/
#define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/
#define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/
#define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/
#define EV_MSC 0x04 /*其他类型*/
#define EV_LED 0x11 /*LED灯设备*/
#define EV_SND 0x12 /*蜂鸣器,输入声音*/
#define EV_REP 0x14 /*允许重复按键类型*/
#define EV_PWR 0x16 /*电源管理事件*/

具体可以参考<linux/input.h>

实战代码

tiny4412btn_input.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/gpio.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>


/*  name  gpio              irqnum  keyvalue
    k1  EXYNOS4_GPX3(2)    ?        11
    k2  EXYNOS4_GPX3(3)         12
    k3  EXYNOS4_GPX3(4)         13
    k4  EXYNOS4_GPX3(5)         14
*/
struct key_t {
    char *name;
    unsigned int gpio;
    unsigned int irqnum;
    unsigned int keyvalue;
};

struct key_t keys[]={
    {"K1",EXYNOS4_GPX3(2),0,KEY_L},
    {"K2",EXYNOS4_GPX3(3),0,KEY_S},
    {"K3",EXYNOS4_GPX3(4),0,KEY_BACK},
    {"K4",EXYNOS4_GPX3(5),0,KEY_ENTER},
};

static struct input_dev *btn_dev;
static struct timer_list btn_timer;
static struct key_t  *keyp;

static irqreturn_t btn_handler(int irq, void *data)
{
    btn_timer.data = (unsigned long)data;
    mod_timer(&btn_timer,jiffies+HZ/100);
    return IRQ_RETVAL(IRQ_HANDLED);
}

static void btn_timer_function(unsigned long data)
{
    struct key_t *p = (struct key_t *)data;
    
    unsigned int pinval = 0;
    pinval = gpio_get_value(p->gpio);
    if(pinval == 1){
        input_event(btn_dev,EV_KEY, p->keyvalue, 0);
        input_sync(btn_dev);
    }
    else {
        input_event(btn_dev,EV_KEY, p->keyvalue, 1);
        input_sync(btn_dev);
    }
}

static int __init tiny4412btn_init(void)
{
    btn_dev = input_allocate_device();
    set_bit(EV_KEY,btn_dev->evbit);
    set_bit(EV_REP,btn_dev->evbit);
    int i;
    for(i=0;i<4;i++)
        set_bit(keys[i].keyvalue,btn_dev->keybit);

    input_register_device(btn_dev);
    
    
    init_timer(&btn_timer);
    btn_timer.function = btn_timer_function;
    add_timer(&btn_timer);
    
    /* gpio to irqnum*/
    for(i=0;i<4;i++){
        keys[i].irqnum= gpio_to_irq(keys[i].gpio);
        request_irq(keys[i].irqnum,btn_handler, 
            IRQ_TYPE_EDGE_BOTH,
            "tiny4412btn", &keys[i]);
    }
    
    return 0;

}

static void __exit tiny4412btn_exit(void)
{
    int i;
    for(i=0;i<4;i++){
        free_irq(keys[i].irqnum, &keys[i]);
    }

    del_timer(&btn_timer);
    input_unregister_device(btn_dev);
    input_free_device(btn_dev);
}

module_init(tiny4412btn_init);
module_exit(tiny4412btn_exit);
MODULE_LICENSE("GPL");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容