输入子系统 框架

目录

  • 回顾引入
  • 简介
  • 框架小结
    • 次设备号
    • 框架结构图
    • 数据管理结构
    • 关键函数
  • 框架分析
    • input_init
    • input_open_file
    • input_register_handler
    • input_register_device
    • input_attach_handler
    • connect
    • read
  • 程序设计
    • 无框架驱动
    • 框架架构
  • 测试
  • hexdump分析
  • tty读取分析
  • 按键连发


title: 输入子系统 框架

tags: linux

date: 2018-11-28 15:39:22

toc: true

---

参考 cnblog

回顾引入

以前的驱动程序框架如下:

  1. 使用file_operations来实现文件的概念,也就是驱动的接口open/close
  2. 使用register_chrdev来注册,unregister_chrdev来卸载
  3. 使用class_createclass_device_create来实现mdev自动创建设备
  4. 使用request_irq实现中断,poll实现查询休眠机制,使用fasync实现信号机制

如果一个应用程序需要调用多个驱动程序时,就需要打开多个设备文件,这就导致设备文件混乱,你需要知道所有驱动对应的设备文件.Linux在这里引入了输入子系统的概念.

简介

输入子系统也是一个驱动框架,输入设备各有不同,所以输入子系统要实现其共性,具体的差异由设备驱动实现,也就是在设备驱动上再封装了.速览下drivers/input/input.c有以下代码,和以前的驱动框架类似,但是没有module_init而是用subsys_initcall修饰,也就是说输入子系统也是一个module框架

MODULE_AUTHOR("Vojtech Pavlik <[email protected]>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
module_exit(input_exit);
subsys_initcall(input_init);   //修饰入口函数

框架小结

  1. 入口input_init,注册类,注册驱动

    class_register(&input_class)
    register_chrdev(INPUT_MAJOR, "input", &input_fops);
  2. 打开驱动,将真正的file_oprations引入,执行open,该文件接口同时存储在input_table
    struct input_handler *handler = input_table[iminor(inode) >> 5];
  3. input_register_handler,初始化正在的input_table处理函数,添加实际的handler,加入到全局链表input_handler_list,同时调用connect简历handlerdev链接
  4. input_register_device注册具体的input_dev,加入到全局链表input_dev_list,同时调用connect简历handlerdev链接
  5. connect建立链接关系,这里有一个新的链接结构handle,包含了handlerinput_dev,这里具体的新建一个evdev结构的变量,成员变量指向上面创建的devhandler,分配次设备号,创建设备
    • 调用input_register_handle,这里创建了一个管理结构
  6. app读取数据最终会调用到具体的handler中的file中的read,阻塞方式如果进入休眠,会被设备中断调用handler中的event来唤醒

次设备号

这里的次设备号中低5位用做特殊处理,高位用作input_table的索引.也就是说一类handler最多有2^5个次设备号.也就是事件占据64~95,摇杆占据0~16,触摸屏占据128~144

摇杆的次设备号从JOYDEV_MINOR_BASE=0起  计算次设备号在connnect devt = MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor),  0~ JOYDEV_MINORS=16
事件的次设备号从EVDEV_MINOR_BASE=64起 ,计算次设备号在devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),  0~ EVDEV_MINORS=32
触摸 TSDEV_MINORS     devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor),  minor<TSDEV_MINORS/2
/// 摇杆
#define JOYDEV_MINOR_BASE   0
#define JOYDEV_MINORS       16
#define JOYDEV_BUFFER_SIZE  64
/// 事件
#define EVDEV_MINOR_BASE    64
#define EVDEV_MINORS        32
#define EVDEV_BUFFER_SIZE   64
/// 触摸屏
#define TSDEV_MINOR_BASE    128
#define TSDEV_MINORS        32
#define TSDEV_BUFFER_SIZE   64

框架结构图

数据管理结构

  • input_dev对应于实际的device端
  • input_handler从名字也可以猜出来是对device的处理
  • input_handle,它是连接input_devinput_handler
  • 通过input_dev,可以遍历所有与它有关的input_handler,通过input_handler,也可以遍历所有与它有关的input_dev。

关键函数

框架分析

input_init

首先从入口开始分析代码

static int __init input_init(void)
{
    int err;

    err = class_register(&input_class);     //(1)注册类,放在/sys/class,使用mdev机制
    if (err) {
        printk(KERN_ERR "input: unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();            //在/proc下面建立相关的文件
    if (err)
        goto fail1;

    err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注册驱动
    if (err) {
        printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

    return 0;

 fail2: input_proc_exit();
 fail1: class_unregister(&input_class);
    return err;
}
  1. err = class_register(&input_class);这个是类似以前的class_create,在/sys/class下创建一个名为input的类,这里只创建了类但是没有用class_device_create创建具体的驱动设备.

    struct class input_class = {
     .name           = "input",
     .release        = input_dev_release,
     .uevent         = input_dev_uevent,
    };

    备注: 当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的

  2. register_chrdev注册驱动,主设备号是13, 只有一个open接口,实际上具体接口并不在这里
    static const struct file_operations input_fops = {
     .owner = THIS_MODULE,
     .open = input_open_file,
    };
    #define INPUT_MAJOR      13

input_open_file

input_init中注册了input_fops,里面的open函数是驱动的真正的入口函数.在drivers/input/input.c中定义

static int input_open_file(struct inode *inode, struct file *file)
{
    struct input_handler *handler = input_table[iminor(inode) >> 5];
    const struct file_operations *old_fops, *new_fops = NULL;
    int err;

    /* No load-on-demand here? */
    if (!handler || !(new_fops = fops_get(handler->fops)))
        return -ENODEV;

    /*
     * That‘s _really_ odd. Usually NULL ->open means "nothing special",
     * not "no device". Oh, well...
     */
    if (!new_fops->open) {
        fops_put(new_fops);
        return -ENODEV;
    }
    old_fops = file->f_op;
    file->f_op = new_fops;

    err = new_fops->open(inode, file);

    if (err) {
        fops_put(file->f_op);
        file->f_op = fops_get(old_fops);
    }
    fops_put(old_fops);
    return err;
}
static inline unsigned iminor(const struct inode *inode)
{
    return MINOR(inode->i_rdev);
}
  1. struct input_handler *handler = input_table[iminor(inode) >> 5];iminor是取出次设备号右移5位,也就是次设备号的高三位对应着input_table的索引也就是8个.
  2. if (!handler || !(new_fops = fops_get(handler->fops)))如果步骤1中取得了有效的handler,则新的file_operations结构new指向handlerfops并直接调用新的open
  3. 最后file->f_op = new_fops;表示了输入子系统的file_operations最终指向了handler也就是input_table中的file_operations结构,输入子系统原来的file_operations先暂存到old
  4. fops_put(old_fops);执行原有的输入子系统的file_operations

input_register_handler

drivers/input/input.c中使用input_register_handlerhandler存入input_table

int input_register_handler(struct input_handler *handler)
{
    struct input_dev *dev;

    INIT_LIST_HEAD(&handler->h_list);

    if (handler->fops != NULL) {
        if (input_table[handler->minor >> 5])
            return -EBUSY;

        input_table[handler->minor >> 5] = handler;
    }

    list_add_tail(&handler->node, &input_handler_list);

    //这个函数在`input_register_device`也有,用做连接 相同类型的 hanlder与device
    list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler);

    input_wakeup_procfs_readers();
    return 0;
}
EXPORT_SYMBOL(input_register_handler);

这里有个链表nput_handler_list,程序会将新增的这个handle放进去.这个函数会被其他的设备调用,搜索有以下

evdev.c(事件设备),
tsdev.c(触摸屏设备),
joydev.c(joystick操作杆设备),
keyboard.c(键盘设备),
mousedev.c(鼠标设备)

这里从evdev(事件设备)为例分析,查看注册的具体结构,次设备号minor=64左移5位=2,也就是存储在input_table[2]

static int __init evdev_init(void)
{
       return input_register_handler(&evdev_handler);  //注册
}
static struct input_handler evdev_handler = {
    .event =    evdev_event,
    .connect =  evdev_connect,
    .disconnect =   evdev_disconnect,
    .fops =     &evdev_fops,                //文件操作结构体
    .minor =    EVDEV_MINOR_BASE,           //用来存放次设备号,EVDEV_MINOR_BASE=64>>5=2
    .name =     "evdev",
    .id_table = evdev_ids,
};
  • .connect,将设备input_dev和某个input_handler建立连接
  • id_table表示能支持哪些输入设备,比如某个驱动设备的input_dev->id和某个input_handlerid_table相匹配,就会调用.connect连接函数

input_register_device

注册具体设备的接口也由drivers/input/input.c提供

int input_register_device(struct input_dev *dev)    //*dev:要注册的驱动设备
{
    static atomic_t input_no = ATOMIC_INIT(0);
    struct input_handler *handler;
    const char *path;
    int error;
...                             //将要注册的input_dev驱动设备放在input_dev_list链表中
    list_add_tail(&dev->node, &input_dev_list);
 ...
    list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);
...
}
EXPORT_SYMBOL(input_register_device);
  • list_add_tail将注册的设备驱动加入到input_dev_list链表
  • input_handler_list这个在input_register_handler是注册具体handler时将handler组织的链表
  • list_for_each_entry遍历input_handle调用input_attach_handler来判断类型,以来决定是否链接设备驱动与设备handler,这个函数在input_register_handler注册hanlder也有

input_attach_handler

这个函数的目的是匹配deviceshanlder的类型,以来决定是否链接具体的deviceshanlder.调用handler->connect(handler, dev, id);链接

    list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    const struct input_device_id *id;
    int error;
...
    id = input_match_device(handler->id_table, dev);
    if (!id)
        return -ENODEV;
    error = handler->connect(handler, dev, id);
....

    return error;
}

connect

具体的connecthandler获取到具体的input_table中的connet函数决定.这里一时间驱动为例分析evdev_connect

static struct input_handler evdev_handler = {
    .event =    evdev_event,
    .connect =  evdev_connect,
    .disconnect =   evdev_disconnect,
    .fops =     &evdev_fops,
    .minor =    EVDEV_MINOR_BASE,
    .name =     "evdev",
    .id_table = evdev_ids,
};
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    struct evdev *evdev;
    struct class_device *cdev;
    dev_t devt;
    int minor;
    int error;
/*******************  1 *****************************/
    for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
    if (minor == EVDEV_MINORS) {
        printk(KERN_ERR "evdev: no more free evdev devices\n");
        return -ENFILE;
    }
/*******************  2 *****************************/
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    if (!evdev)
        return -ENOMEM;

    INIT_LIST_HEAD(&evdev->client_list);
    init_waitqueue_head(&evdev->wait);
/*******************  3 *****************************/
    evdev->exist = 1;
    evdev->minor = minor;
    evdev->handle.dev = dev;
    evdev->handle.name = evdev->name;
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;
    sprintf(evdev->name, "event%d", minor);

    evdev_table[minor] = evdev;
/*******************  4 *****************************/
    devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

    cdev = class_device_create(&input_class, &dev->cdev, devt,
                   dev->cdev.dev, evdev->name);
    if (IS_ERR(cdev)) {
        error = PTR_ERR(cdev);
        goto err_free_evdev;
    }

    /* temporary symlink to keep userspace happy */
    error = sysfs_create_link(&input_class.subsys.kobj,
                  &cdev->kobj, evdev->name);
    if (error)
        goto err_cdev_destroy;
/*******************  5 *****************************/
    error = input_register_handle(&evdev->handle);
    if (error)
        goto err_remove_link;

    return 0;

 err_remove_link:
    sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
 err_cdev_destroy:
    class_device_destroy(&input_class, devt);
 err_free_evdev:
    kfree(evdev);
    evdev_table[minor] = NULL;
    return error;
}
  1. 在1中,遍历次设备号,这里EVDEV_MINORS=32,这里就是在evdev_table中找到第一个空的
  2. 分配空间给一个input_handle,指向evdev,注意这里不是input_handler,没有尾吧r,这个结构是链接handlerdev结构的,==重点记忆==
    struct evdev {
     int exist;
     int open;
     int minor;
     char name[16];
     struct input_handle handle;
     wait_queue_head_t wait;
     struct evdev_client *grab;
     struct list_head client_list;
    };
  3. 赋值操作,evdev的具体指向了对应的设备与之前说的input_handler,在步骤1中先找到最早的次设备号.因为没有设置子设备号,默认从小到大排列,其中event0是表示这个input子系统,所以这个键盘驱动名字就是event1
     evdev->exist = 1;
     evdev->minor = minor;
     evdev->handle.dev = dev;
     evdev->handle.name = evdev->name;
     evdev->handle.handler = handler;
     evdev->handle.private = evdev;
     sprintf(evdev->name, "event%d", minor);
    
     evdev_table[minor] = evdev;
    ls /sys/class/input
    event0 event1 
  4. 主设备号这里固定为13,次设备号这里低位.MKDEV(a, b)a就是主设备号,b是次设备号=64+minor,然后创建class下的设备.事件的次设备号从64~127,这里最终会在/sys/class/input/下新建设备
    #define INPUT_MAJOR      13
    #define EVDEV_MINOR_BASE 64
    devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
    cdev = class_device_create(&input_class, &dev->cdev, devt,
                               dev->cdev.dev, evdev->name);
    
  5. input_register_handle最终来注册这个新建的结构,建立链接关系
    evdev->handle.dev = dev;         //dev设备驱动
    evdev->handle.handler = handler; //输入类型框架hander,事件,键盘,触摸等
    
    int input_register_handle(struct input_handle *handle)
    {
     struct input_handler *handler = handle->handler;
    
     list_add_tail(&handle->d_node, &handle->dev->h_list);
     //将 handle->d_node 存入 dev->h_list
    
     list_add_tail(&handle->h_node, &handler->h_list);
        //将 handle->h_node 存入 handler->h_list
    
     if (handler->start)
         handler->start(handle);
    
     return 0;
    }

    在这里要看清楚handlehandler.

read

实际的读取会调用到具体的handler中的读取函数,在evdev中是evdev_read函数

static ssize_t evdev_read(struct file *file, char __user *      buffer, size_t count, loff_t *ppos)
{
 ... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;

/*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据),则return返回*/
 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;

/*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态  */
  retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

  ... ...           //上传数据

}

休眠

这里和我们以前的字符设备驱动做法一致

  • 非阻塞方式读取,如果没有数据直接返回
  • 阻塞方式读取,没有数据进入休眠状态
    wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

唤醒

有休眠就有唤醒,搜索下休眠队列evdev->wait,如下,这个函数是在handler中注册的

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{...
    //这个函数会遍历所有的handler
    list_for_each_entry(client, &evdev->client_list, node)
    kill_fasync(&client->fasync, SIGIO, POLL_IN);//发信号
 ...
    wake_up_interruptible(&evdev->wait);
}
static struct input_handler evdev_handler = {
    .event =    evdev_event,
    .connect =  evdev_connect,
    .disconnect =   evdev_disconnect,
    .fops =     &evdev_fops,
    .minor =    EVDEV_MINOR_BASE,
    .name =     "evdev",
    .id_table = evdev_ids,
};

调用唤醒

这里不再具体分析,这里的唤醒肯定是硬件设备触发的,比如中断等.比如在函数gpio_keys_isr()

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
     /*获取按键值,赋到state里*/
     ... ...

    /*上报事件*/
    input_event(input, type, button->code, !!state);
    input_sync(input);                        //同步信号通知,表示事件发送完毕
}
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    struct input_handle *handle;
    ... ...

    /* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
    list_for_each_entry(handle, &dev->h_list, d_node)
    if (handle->open)  //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体
    {
        //调用evdev_event()的.event事件函数
        handle->handler->event(handle, type, code, value);
    }

}

即通过设备触发,调用input_event去遍历具体的handle,如果该handle已经被打开,则调用这个handle中的handler中的event,也就是注册的evdev_event

程序设计

无框架驱动

  1. 确定主设备号
  2. 编写file_opreation结构,编写硬件相关操作
  3. 构造class结构,自动创建dev设备
  4. 注册设备驱动
  5. 定义入口,出口

框架架构

  • Input_device设备驱动
  • Input_handler处理框架,包含Evdev.c,keyboard.c

流程设计

input.c完成了主设备的注册,驱动设备注册,在这个框架下需要实现硬件设备驱动的程序即可.参考drivers\input\keyboard\gpio_keys.c即可

  1. 向内核申请input_dev结构体
  2. 设置input_dev的成员
  3. 注册input_dev 驱动设备.input_register_device
  4. 初始化定时器和中断
  5. 写中断服务函数
  6. 写定时器超时函数
  7. 在出口函数中 释放中断函数,删除定时器,卸载释放驱动

关键数据结构

struct input_dev {
   void *private;
   const char *name;  //设备名字
   const char *phys;  //文件路径,比如 input/buttons
   const char *uniq;
   struct input_id id;

    //表示支持哪类事件,常用有以下几种事件(可以多选)
   unsigned long evbit[NBITS(EV_MAX)];  

   //EV_SYN      同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
   //EV_KEY       键盘事件
   //EV_REL       (relative)相对坐标事件,比如鼠标
   //EV_ABS       (absolute)绝对坐标事件,比如摇杆、触摸屏感应
   //EV_MSC      其他事件,功能
   //EV_LED       LED灯事件
   //EV_SND      (sound)声音事件

   //EV_REP       重复键盘按键事件
    //(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)   

   //EV_FF         受力事件
   //EV_PWR      电源事件
   //EV_FF_STATUS  受力状态事件

   unsigned long keybit[NBITS(KEY_MAX)];   //存放支持的键盘按键值
                                //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)

   unsigned long relbit[NBITS(REL_MAX)];    //存放支持的相对坐标值
   unsigned long absbit[NBITS(ABS_MAX)];   //存放支持的绝对坐标值
   unsigned long mscbit[NBITS(MSC_MAX)];   //存放支持的其它事件,也就是功能
   unsigned long ledbit[NBITS(LED_MAX)];    //存放支持的各种状态LED
   unsigned long sndbit[NBITS(SND_MAX)];    //存放支持的各种声音
   unsigned long ffbit[NBITS(FF_MAX)];       //存放支持的受力设备
   unsigned long swbit[NBITS(SW_MAX)];     //存放支持的开关功能

 ... ...
}

关键函数

//向内核中申请一个input_dev设备,然后返回这个设备
struct input_dev *input_allocate_device(void);
//释放input_dev这个结构体, 一般在驱动出口函数写
input_free_device(struct input_dev *dev);
//卸载/sys/class/input目录下的input_dev这个类设备, 一般在驱动出口函数写
input_unregister_device(struct input_dev *dev);  

// 用来设置位变量,这里用来设置支持的事件类型
set_bit(nr,p);                  //设置某个结构体成员p里面的某位等于nr,支持这个功能
/* 比如:
set_bit(EV_KEY,buttons_dev->evbit);   //设置input_dev结构体buttons_dev->evbit支持EV_KEY
set_bit(KEY_S,buttons_dev->keybit);  //设置input_dev结构体buttons_dev->keybit支持按键”S”
*/

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);  //上报事件
 // input_dev *dev :要上报哪个input_dev驱动设备的事件
 // type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY
 // code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L
 //value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0

注意在上报事件后需要调用input_sync来同步事件通知内核,实际上input_sync只是上报了同步事件

static inline void input_sync(struct input_dev *dev)
{
     //就是上报同步事件,告诉内核:input_event()事件执行完毕
    input_event(dev, EV_SYN, SYN_REPORT, 0);
}

入口函数

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);
    buttons_timer.function = buttons_timer_function;
    add_timer(&buttons_timer);

    for (i = 0; i < 4; i++)
    {
        request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
    }

    return 0;
}

出口函数

static void buttons_exit(void)
{
    int i;
    for (i = 0; i < 4; i++)
    {
        free_irq(pins_desc[i].irq, &pins_desc[i]);
    }

    del_timer(&buttons_timer);
    input_unregister_device(buttons_dev);
    input_free_device(buttons_dev);
}

中断设计

  • 使用input_event上报事件
  • 使用input_sync同步事件,其实也是使用input_event来上报一个同步事件
    input_event(dev, EV_SYN, SYN_REPORT, 0);
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    /* 10ms后启动定时器 */
    irq_pd = (struct pin_desc *)dev_id;
    mod_timer(&buttons_timer, jiffies+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);
    }
}

完整程序


/* 参考drivers\input\keyboard\gpio_keys.c */

#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.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/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>

struct pin_desc{
    int irq;
    char *name;
    unsigned int pin;
    unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
    {IRQ_EINT0,  "S2", S3C2410_GPF0,   KEY_L},
    {IRQ_EINT2,  "S3", S3C2410_GPF2,   KEY_S},
    {IRQ_EINT11, "S4", S3C2410_GPG3,   KEY_ENTER},
    {IRQ_EINT19, "S5",  S3C2410_GPG11, 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(&buttons_timer, jiffies+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);
    buttons_timer.function = buttons_timer_function;
    add_timer(&buttons_timer);

    for (i = 0; i < 4; i++)
    {
        request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
    }

    return 0;
}

static void buttons_exit(void)
{
    int i;
    for (i = 0; i < 4; i++)
    {
        free_irq(pins_desc[i].irq, &pins_desc[i]);
    }

    del_timer(&buttons_timer);
    input_unregister_device(buttons_dev);
    input_free_device(buttons_dev);
}

module_init(buttons_init);

module_exit(buttons_exit);

MODULE_LICENSE("GPL");

测试

  1. 查看下原有的类和设备

    # ls /dev/event*
    /dev/event0
    # cd /sys/class/
    # ls
    graphics      misc          printer       sound         vc
    hwmon         mmc_host      rtc           spi_master    vtconsole
    i2c-adapter   mtd           scsi_device   tty
    input         net           scsi_disk     usb_endpoint
    mem           ppdev         scsi_host     usb_host
    # cd input/
    # ls
    event0  input0  mice    mouse0  ts0
    
  2. 安装驱动
    # insmod buttons.ko
    input: Unspecified device as /class/input/input2
  3. 再查看下设备文件可以看到主设备号是13,此设备号是65,为什么次设备是15,看下函数connet中的注册函数即可知道了
    # ls /dev/event* -l
    crw-rw----    1 0        0         13,  64 Jan  1 00:00 /dev/event0
    crw-rw----    1 0        0         13,  65 Jan  1 01:11 /dev/event1
    
    # ls /sys/class/input/
    event0  event1  input0  input2  mice    mouse0  ts0
    
    # cat /sys/class/input/event1/dev
    13:65
    
  4. 测试打印
    • 使用cat /dev/tty1,这个方式就是通过tty_io.c来访问键盘驱动,然后打印在tty1终端上,注意这里的显示需要按下回车后才能显示ls
    • exec 0</dev/tty1,这里直接就是将标准输入定位到这个按键了,按下按键就直接打印了,也能执行命令,但是串口输入就失效了
    • 板子有QT界面的,打开记事本,按键按下就有输入,或者删除启动脚本/etc/init.d/rcSqt相关的
    • 使用hexdump去读取设备文件hexdump /dev/event1,hexdump实际上就是一个读取文本的工具,用hex显示出来

hexdump分析

使用命令hexdump /dev/event1去读取设备文件,这中间发生了什么?

  1. hexdump就是一个读取工具,所以也存在着先open,再read的过程
  2. open回去查找主设备号,这是驱动模块框架所决定的,查看下这个设备文件的设备号为13,也就是输入子系统的主设备号,他会调用输入子系统的open
    # ls -l /dev/event1
    crw-rw----    1 0        0         13,  65 Jan  1 00:16 /dev/event1
  3. open会调用输入子系统的input_open_file,里面会切换到子设备号对应的handler,这里是65,属于evdec,这里的自设备号都是有范围的.实际上存储handler是在数组input_table[2]
    static const struct file_operations input_fops = {
     .owner = THIS_MODULE,
     .open = input_open_file,
    };
  4. 转到evdev.c,这里可以回顾下它的注册过程,与步骤3对应,65>>5=2,这里继续调用evdev的读函数evdev_read
    static int __init evdev_init(void)
        input_register_handler(&evdev_handler)
        input_table[handler->minor >> 5] = handler;
    static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
    {
     struct evdev_client *client = file->private_data;
     struct evdev *evdev = client->evdev;
     int retval;
    
     if (count < evdev_event_size())
         return -EINVAL;
    //如果头=尾,也就是环形缓冲区空且非阻塞,直接退出
     if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
         return -EAGAIN;
    //否则直接睡眠
     retval = wait_event_interruptible(evdev->wait,
         client->head != client->tail || !evdev->exist);
     if (retval)
         return retval;
    
     if (!evdev->exist)
         return -ENODEV;
    //如果有数据或者被唤醒后有数据,读取
     while (client->head != client->tail && retval + evdev_event_size() <= count) {
    
         struct input_event *event = (struct input_event *) client->buffer + client->tail;
    //复制到用户空间,也就是read的值
         if (evdev_event_to_user(buffer + retval, event))
             return -EFAULT;
    
         client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
         retval += evdev_event_size();
     }
    
     return retval;
    }
    

    这里重点查看函数evdev_event_to_user实际上调用的就是copy_to_user,这正是我们以前最早的字符设备驱动中应用程序最后read的值了,这里的参数是evdev_event_to_user(buffer + retval, event),也就是将event数据赋值到buffer + retval,原始数据就是event

    static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
    {
     if (copy_to_user(buffer, event, sizeof(struct input_event)))
         return -EFAULT;
    
     return 0;
    }
    static inline int copy_to_user(void __user *to, const void *from, int n)
  5. 查看下event的数据结构,时间为long 类型4字节
    struct input_event {
     struct timeval {
     time_t      tv_sec;     /* seconds */
     suseconds_t tv_usec;    /* microseconds */
     };
     __u16 type;
     __u16 code;
     __s32 value;
    };
  6. 我们在没有按下过按键的时候,执行命令没有输出,因为休眠了,然后我们按下按键就有输出了
    # hexdump /dev/event1
         [   秒  ] [  毫秒 ] [type][code][ value]
    0000000 0824 0000 c182 0001 0001 0026 0001 0000
    0000010 0824 0000 c18d 0001 0000 0000 0000 0000
    0000020 0824 0000 9427 0004 0001 0026 0000 0000
    0000030 0824 0000 942f 0004 0000 0000 0000 0000

    查看下以前是怎么上报事件的

    /* 最后一个参数: 0-松开, 1-按下 */
    input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
    input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
    //同步事件
    input_event(dev, EV_SYN, SYN_REPORT, 0);
    
    #define EV_SYN           0x00
    #define EV_KEY           0x01
    #define EV_REL           0x02
    #define EV_ABS           0x03
    #define EV_MSC           0x04
    #define EV_SW            0x05
    #define EV_LED           0x11
    #define EV_SND           0x12
    #define EV_REP           0x14
    #define EV_FF            0x15
    #define EV_PWR           0x16
    #define EV_FF_STATUS     0x17
    #define EV_MAX           0x1f
    #define EV_CNT           (EV_MAX+1)
    //按键值
    #define KEY_L            38
    #define KEY_S            31
    #define KEY_ENTER        28
    #define KEY_LEFTSHIFT        42
    • type表示的就是事件的类型,=01EV_KEY按键类事件
    • code表示按键值=0x26=3是键值为KEY_L
    • value这里是00010000,这里是小端,也就是01是最低字节,也就是value=1是按下
    • 接下去第二行type00表示同步事件

tty读取分析

使用cat /dev/tty1,这个方式就是通过tty_io.c来访问键盘驱动,然后打印在tty1终端上,也就是说实际上是通过tty_io.c访问了keyboard.c,查看下里面的init中注册的handler

// drivers/char/keyboard.c
static struct input_handler kbd_handler = {
    .event      = kbd_event,
    .connect    = kbd_connect,
    .disconnect = kbd_disconnect,
    .start      = kbd_start,
    .name       = "kbd",
    .id_table   = kbd_ids,
};

支持的事件在.id_table = kbd_ids,中定义,可以发现支持EV_KEY事件

static const struct input_device_id kbd_ids[] = {
    {
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_KEY) },
        },

    {
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_SND) },
        },

    { },    /* Terminating entry */
};

我们的dev驱动上报的就是这个EV_KEY事件,所以实际上也会connect

再查看下键盘中的event唤醒机制中调用了kbd_keycode,这个函数会与tty产生联系,...下面就不分析了,也就是说cat /dev/tty1 时,不是从“输入子系统”中过来的,而是从 tty关的部分进来的

static void kbd_event(struct input_handle *handle, unsigned int event_type,
              unsigned int event_code, int value)
{
    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));  //这里有tty
    tasklet_schedule(&keyboard_tasklet);
    do_poke_blanked_console = 1;
    schedule_console_callback();
}

按键连发

我们在代码中添加了支持按键连发,也就是支持事件的重复类事件,这个事件会启动定时器的

set_bit(EV_REP, buttons_dev->evbit);

input.c搜索这个事件EV_REP,有以下代码,启动了定时器mod_timer,

case EV_KEY:
...
    if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
    dev->repeat_key = code;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
...

搜索time可以发现在input_register_device中有对定时器操作,其回调函数是input_repeat_key

init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
    dev->timer.data = (long) dev;
    dev->timer.function = input_repeat_key;
    dev->rep[REP_DELAY] = 250;
    dev->rep[REP_PERIOD] = 33;
}

可以发现在定时器的函数中也在上报按键事件input_event(dev, EV_KEY, dev->repeat_key, 2);

static void input_repeat_key(unsigned long data)
{
    struct input_dev *dev = (void *) data;

    if (!test_bit(dev->repeat_key, dev->key))
        return;

    input_event(dev, EV_KEY, dev->repeat_key, 2);
    input_sync(dev);

    if (dev->rep[REP_PERIOD])
        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD]));
}

原文地址:https://www.cnblogs.com/zongzi10010/p/10037089.html

时间: 2024-11-03 09:54:22

输入子系统 框架的相关文章

Linux输入子系统框架分析(1)

在Linux下的输入设备键盘.触摸屏.鼠标等都可以用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层,事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备驱动层将输入事件上报给核心层input.c,核心层找到匹配的事件层,将事件交给事件层处理,事件层处理完后传递到用户空间. 我们最终要搞清楚的是在用户空间调用open和read最终在内核中是怎样处理的,向内核上报的事件又是谁处理的,处理完后是怎样传递到用户空间的? 上面两个图是输入子系统的框架. 下面

Linux驱动之输入子系统框架

    好记性不如烂笔头,整理一下笔记~ Linux驱动之输入子系统框架 输入子系统将该类驱动划分为3部分 1.核心层 input.c 2.设备层 Gpio_keys.c ... 3.事件处理层 Evdev.c 事件处理层为纯软件的东西,设备层涉及底层硬件,它们通过核心层建立联系,对外提供open write等接口. 1.我们首先来看,核心层 input.c如何向外界提供接口 在 input_init 中注册了字符设备驱动 register_chrdev(INPUT_MAJOR, "input&

input输入子系统框架分析

input子系统的搭建要点: 核心层为事件驱动层和设备驱动层的注册提供API的实现.核心层为设备驱动层上报事件提供API的实现 .事件驱动层为应用层提供API的实现 . (1)核心层:提供事件驱动层和设备驱动层所需的函数接口(为input dev和input handler建立联) drivers/input/input.c: ##主要接口函数一览:## ①为事件驱动层提供的: 注册API: int input_register_handler(struct input_handler *han

input_subsys 输入子系统框架分析

在linux内核中 已做好各类驱动的框架,驱动程序也属于内核的一部分,我们可以在原有的驱动上修改,来匹配我们自已的硬件,也可以自已编写符合内核驱动框架的驱动程序.出于学习的目的,便于更好的理解各类驱动的框架和编程思想,先分析内核自带的驱动框架和流程,再自已编写符合内核框架的驱动程序.下面开始,从输入子系统开始学习分析,后面一步一步涉及各类驱动. 一.输入子系统 从 drivers/input/input.c 这个文件开始分析,分析驱动程序的时候,先从其入口函数开始,因为每当加载一个驱动程序的时候

输入子系统之按键驱动

上一篇博文<input输入子系统框架分析>,尝试使用这种驱动模型来看一个按键驱动程序. 下面的程序是根据韦东山老师写的代码进行修改的,我的开发板是tq2440. button.c文件: #include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h>

输入子系统概念介绍

输入子系统在内核中的位置:/driver/input drivers/input/input.c: input_init ---> err = register_chrdev(INPUT_MAJOR, "input", &input_fops); static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; 问:怎么读按键? inpu

Linux输入子系统

在Linux中,按键.触摸屏.鼠标等等输入设备都可以依靠输入子系统提供的接口函数来实现他们的设备驱动,在输入子系统中,系统已经完成了这些输入设备的共性,所以根据子系统提供的接口,只需要完成各自的独特性即可完成一个输入设备的设备驱动. Linux中,输入子系统由设备驱动层.核心层.事件处理层这三层组成.设备驱动层讲底层输入设备的响应转化为标准的输入事件,事件处理层就为应用程序提供统一的设备访问接口来跟底层交互数据,核心层则是连接驱动层和事件处理层的桥梁. 在输入子系统中重要的结构体就是input_

Linux驱动开发之输入子系统

2020-02-15 关键字: Linux 中输入设备大致可分以下几种: 1.按键/键盘(keyboard) 2.鼠标(mouse) 3.触摸屏(touchscreen) 4.游戏杆(joystick) 输入子系统的目的是为了屏蔽众多输入设备在硬件上的差异化,使得在开发输入设备的程序时能更简单统一.输入子系统屏蔽差异的方式就是为各种输入设备与上层应用提供统一的编程接口. Linux 输入子系统是一种编程框架,它可以自上而下分为以下几种层次: 1.应用层 2.input handler层:数据处理

关于linux input device输入子系统架构及android中的框架

关于linux input device输入子系统架构及android中的框架 应用app和windowmanagerservice的input event通信方式 在Native层的InputChannel就是一个通道,仅仅是一个通道,仅仅具有通信功能,不包含其他的.至于从数据流动方向,与InputChannel无关.数据流向是由InputPublisher和InputConsumer在组合了InputChannel后决定的.把InputChannel由应用程序传递到WindowManageS