输入设备驱动详解

  • 输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作原理是底层在按键触摸等动作时产生一个中断(或驱动通过timer定时查询)。让后CPU通过SPI,I2C或外部存储器总线读取按键值。坐标等数据,放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让客户可以读取按键值,坐标等数据

  • 显然,在这些工作中,只是中断,按键值/坐标值是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。

  • 上图展示了输入子系统的操作。此子系统包括一前一后运行的两类驱动:

    输入事件(event)驱动和输入设备(device)驱动。
    输入事件驱动负责和应用程序的接口;
    而输入设备驱动负责和底层输入设备的通信。
    输入事件驱动和输入设备驱动都可以利用输入子系统的高效、可重用的核心提供的服务。

  • Now,我们看到输入子系统中有两个类型的驱动,当我们要为一个输入设备(如触摸屏)的编写驱动的时候,我们是要编写两个驱动:输入设备驱动和输入事件驱动??

    答案是否定的。在子系统中,事件驱动是标准的,对所有的输入类都是可以用的,所以你更可能的是实现输入设备驱动而不是输入事件驱动。你的设备可以利用一个已经存在的,合适的输入事件驱动通过输入核心和用户应用程序接口。

  • 总结如下:

输入子系统由输入子系统核心层( Input Core ),
驱动层和事件处理层(Event Handler)三部份组成。
一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等
通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。

  • 设备驱动层:
    将底层的硬件输入转化为统一事件型式,向输入核心(InputCore)汇报。

  • 输入核心层:
    为设备驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;

  • 事件驱动层:
    主要作用是和用户空间交互,如提供read,open等设备方法,创建设备文件等。

  • 输入子系统支持的输入事件

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    事件类型最大个数和提供位掩码支持一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。
  • 在发生输入事件时,向子系统报告事件
    用于报告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)//如果你觉得麻烦,你也可以只记住1个函数(因为上述函数都是通过它实现的)void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

Event Handler层解析

input_handler结构体

  • 以evdev.c中的evdev_handler为例:

static struct input_handler evdev_handler = {.event = evdev_event, //向系统报告input事件,系统通过read方法读取.connect = evdev_connect, //和input_dev匹配后调用connect构建.disconnect = evdev_disconnect,.fops = &evdev_fops, //event设备文件的操作方法.minor = EVDEV_MINOR_BASE, //次设备号基准值.name = "evdev",.id_table = evdev_ids, //匹配规则};

input字符设备注册过程

  • drivers/input/input.c中:
static int __init input_init(void)//input子系统入口函数{int err;err = class_register(&input_class);//注册一个类,用来设置设备号……err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//注册一个字符设备……}

input_fops定义static const struct file_operations input_fops = {.owner = THIS_MODULE,.open = input_open_file,};
  • Input_dev和input_handler匹配后调用input_handler的connect。以evdev_handler为例:

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;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;}evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//为每个匹配evdev_handler的设备创建一个evdev。if (!evdev)return -ENOMEM;INIT_LIST_HEAD(&evdev->client_list); //队列头init_waitqueue_head(&evdev->wait);//初始化等待队列头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;//记录evdev的位置,字符设备/dev/input/evnetx访问时根据次设备号及EVDEV_MINOR_BASE最终在evdev_open中找到对应的evdevdevt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);//创建了event字符设备节点}
  • input字符设备的打开过程

static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];//得到对应的input_handler
const struct file_operations *old_fops, *new_fops = NULL;int err;if (!handler || !(new_fops = fops_get(handler->fops)))//取出对应input_handler的file_operationsreturn -ENODEV;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;}
  • input字符设备的其它操作

  • 由于在open阶段已经把设备文件的操作操作方法重定位了到了具体的input_handler,所以其它接口操作(read、write、ioctl等),由各个input_handler的fops方法决定。如evdev.c中的:evdev_fops。


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,
};
  • 问:怎么读按键?

input_open_filestruct input_handler *handler = input_table[iminor(inode) >> 5];new_fops = fops_get(handler->fops)  //  =>&evdev_fopsfile->f_op = new_fops;err = new_fops->open(inode, file);
//通过应用程序的read函数进行读操作
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(&handler->node, &input_handler_list);// 对于每个input_dev,调用input_attach_handlerlist_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
  • 注册输入设备:

input_register_device// 放入链表list_add_tail(&dev->node, &input_dev_list);// 对于每一个input_handler,都调用input_attach_handlerlist_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_devinput_attach_handlerid = 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函数建立”连接”

  • 怎么建立连接?

    1. 分配一个input_handle结构体

    2. input_handle.dev = input_dev; // 指向左边的input_dev
      input_handle.handler = input_handler; // 指向右边的input_handler

    3. 注册:

      input_handler->h_list = &input_handle;
      inpu_dev->h_list = &input_handle;
      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(&evdev->handle);

  • 怎么读按键?

app: read  evdev_read// 无数据并且是非阻塞方式打开,则立刻返回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);
  • 谁来唤醒?
evdev_eventwake_up_interruptible(&evdev->wait);
  • 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, &dev->h_list, d_node)if (handle->open)handle->handler->event(handle, type, code, value);
  • 怎么写符合输入子系统框架的驱动程序?

    1. 分配一个input_dev结构体
    2. 设置
    3. 注册
    4. 硬件相关的代码,比如在中断服务程序里上报事件
  • 下面是input_dev结构体在2.6内核的定义


struct input_dev {void *private;const char *name;const char *phys;const char *uniq;struct input_id id;
//下面是定义的输入事件unsigned long evbit[NBITS(EV_MAX)];unsigned long keybit[NBITS(KEY_MAX)];unsigned long relbit[NBITS(REL_MAX)];unsigned long absbit[NBITS(ABS_MAX)];unsigned long mscbit[NBITS(MSC_MAX)];unsigned long ledbit[NBITS(LED_MAX)];unsigned long sndbit[NBITS(SND_MAX)];unsigned long ffbit[NBITS(FF_MAX)];unsigned long swbit[NBITS(SW_MAX)];unsigned int keycodemax;unsigned int keycodesize;void *keycode;int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);struct ff_device *ff;unsigned int repeat_key;struct timer_list timer;int state;int sync;int abs[ABS_MAX + 1];int rep[REP_MAX + 1];unsigned long key[NBITS(KEY_MAX)];unsigned long led[NBITS(LED_MAX)];unsigned long snd[NBITS(SND_MAX)];unsigned long sw[NBITS(SW_MAX)];int absmax[ABS_MAX + 1];int absmin[ABS_MAX + 1];int absfuzz[ABS_MAX + 1];int absflat[ABS_MAX + 1];int (*open)(struct input_dev *dev);void (*close)(struct input_dev *dev);int (*flush)(struct input_dev *dev, struct file *file);int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);struct input_handle *grab;struct mutex mutex; /*  序列化打开或关闭操作*/unsigned int users;struct class_device cdev;union {         /* 暂时切换到我们struct设备 */struct device *parent;} dev;struct list_head    h_list;struct list_head    node;
};
  • 下面是基于S3c2440开发板的按键输入驱动程序
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;
}

测试:

  1. 用hexdump测试

    hexdump /dev/event1 (open(/dev/event1), read(), )
       秒 微秒 类 code value
    0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
    0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
    0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
    0000030 0bb2 0000 581f 000e 0000 0000 0000 0000

  2. 如果没有启动QT:

    cat /dev/tty1
    按:s2,s3,s4
    就可以得到ls
    或者:
    exec 0 把输入设备切换到按键
    然后可以使用按键来输入

  3. 如果已经启动了QT:

    可以点开记事本
    然后按:s2,s3,s4

基于S3c244的input输入子系统相关推荐

  1. ARM Linux内核Input输入子系统浅解

    --以触摸屏驱动为例 第一章.了解linux input子系统       Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些设备 ...

  2. linux input输入子系统分析《四》:input子系统整体流程全面分析

    1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...

  3. linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例

    1.1    本节阅读前提 本节的说明建立在前两节的基础之上,需要先阅读如下两篇章: linux input输入子系统分析<一>:初识input输入子系统 linux input输入子系统 ...

  4. Linux驱动:input输入子系统

    input输入子系统 1.input输入子系统 1.1 简介 1.2 相关API函数 1.3 使用流程(驱动框架) 2.驱动示例 3.测试程序 4.测试结果 5.内核自带的input按键驱动 附:对应 ...

  5. Linux输入事件类型EV_SW,Linux的input输入子系统:总体框架

    一.input输入子系统总体框架 Linux输入子系统将输入驱动抽象为三层:设备驱动层.核心层.事件处理层. 设备驱动层:将底层的硬件输入事件转化为统一事件形式,向输入核心(Input Core)汇报 ...

  6. linux input输入子系统分析《一》:初识input输入子系统

    主要讲述本人在学习Linux内核input子系统的全部过程,如有分析不当,多谢指正.以下交流方式,文章欢迎转载,保留联系信息,以便交流. 邮箱:eabi010@gmail.com 主页:www.iel ...

  7. Linux的input输入子系统:设备驱动之按键驱动

    环境:kernel-2.6.30.4,arm-linux-gcc-4.3.3,目标板TQ2440 一.设备层驱动程序: #include <linux/module.h> #include ...

  8. android dispatch input输入子系统,10.12 android输入系统_InputStage理论

    android应用程序对输入系统的处理分为多个阶段,我们把这些阶段称为InputStage 理论处理流程: (1)activity发给window,如果window不能处理,再由activity处理; ...

  9. linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析

    1      mini2440的ADC驱动实例 这节与输入子系统无关,出现在这里是因为后面的章节会讲到触摸屏输入子系统驱动,由于触摸屏也使用ADC,因此本节是为了说明ADC通过驱动代码是如何控制的. ...

最新文章

  1. 灯鹭的简单开放,促进网站一举多赢
  2. SAP OData编程指南
  3. linux 常用命令05 常用的压缩与解压缩文件
  4. 还不懂Redis?看完这个故事就明白了!
  5. 在python中编写socket服务端模块(二):使用poll或epoll
  6. 详解基于机器学习的恶意代码检测技术
  7. 每次打开Word,Excel,弹出Office安装配置进度向导解决办法
  8. 双交换消元:模合数多项式矩阵行列式、新伴随矩阵算法
  9. 女孩起名字:诗经中惊艳的女孩名字
  10. Mybatis 札记(三、分页、注解开发)麻雀虽小,五脏俱全
  11. 陈睿竟也是个“大龄二次元宅”?还不知道的集美看过来
  12. 一、基础入门下------------视频处理
  13. Vuex 之二:3种拿到 state 中数据并执行 getters 中方法的过程与实例剖析
  14. oracle 脏读,脏读 不可重复读 幻读
  15. 互联网大佬做公益,图什么?
  16. 分享一个自己做的一个3DM手机客户端
  17. Web 前端开发技术 —— JavaScript
  18. 深度学习——模型构建
  19. 如何在工作中提升自己的学习能力
  20. python 全栈开发是什么意思_python全栈要学什么

热门文章

  1. 应用随机过程期中复习总结
  2. extjs给panel添加滚动条_ExtJs Panel 滚动条设置
  3. 关于未来计算机的英语作文,《 未来机器人》英语作文
  4. 国产RY8122 18V 2A 500KHz同步降压稳压芯片
  5. css:绘制一角是圆角的三角形
  6. 今日头条2018 坐标
  7. boost::asio::io_service的stop()和reset()和stopped()函数
  8. 斯嘉丽约翰逊60张pdf什么时间的?_什么叫美丽与演技并存?
  9. 【广告架构day1】爱奇艺广告系统的演进之路:实践中的一些经验
  10. NFS服务配置与mount nfs时-o nolock的问题