总览
本文使用 linux-2.6.22.6 内核, 使用jz2440开发板.
输入子系统
输入子系统是Linux对硬件设备进一步的抽象.
将输入系统的逻辑概念统一写好, 我们称之为软件抽象层.
而硬件部分的底层工作需要由开发人员具体实现, 我们称之为硬件设备层.
中间还有个连接层, 用于实现软件抽象层和硬件设备层的多对多关联.
按键, 鼠标, 键盘, 触摸屏等设备都可以归属为输入子系统.
这些设备的硬件驱动和输入子系统对接, 输入子系统再统一将输入事件传递给应用层.
这样, 应用层的设备就无需考虑底层硬件的区别.
而我们之前写的按键驱动, LED驱动, 一般只是给公司内部调用的, 不具有通用性!
整个输入子系统完成的工作其实和自己写的驱动是一样的.
只是部分可以软件抽象的功能被输入子系统打包掉了.
其核心代码在 /drivers/input/input.c
整体框架如下:
文件 /drivers/input/input.c 内, 关键函数 input_init class_register register_chrdev
input_register_device input_register_handler input_register_handle
1. static 初始化一个 input_handler 全局变量 2. 注册此变量, input_register_handler 3. 实现 event connect 等函数
struct input_handler { event,connect, disconnect, start int minor; const char *name;
const struct input_device_id *id_table; const struct input_device_id *blacklist;
struct list_head h_list; struct list_head node; };
多个文件 /drivers/input 1. *dev_connect里分配 input_handle(没有r)变量 2. 设置/初始化此变量 3. 注册, input_register_handle(没有r)
struct input_handle(没有r) { void *private; struct input_dev *dev; struct input_handler *handler;
struct list_head d_node; struct list_head h_node; };
1. 分配一个input_dev变量 2. 设置/初始化此变量 3. 注册, input_register_device 4. 硬件相关代码, open, close, event, sync等等.
struct input_dev { const char *name; const char *phys; 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)];
struct list_head h_list; struct list_head node; };
|
如图, 较为直观的说明了三者的指针关系, 以及 input_handler
和 input_dev
通过 input_handle(没有r)
的建立的多对多关系
整个input初始化流程大致如下:
static int __init input_init(void) class_register(&input_class); input_proc_init(); register_chrdev(INPUT_MAJOR, "input", &input_fops); static int input_open_file(struct inode *inode, struct file *file) struct input_handler *handler = input_table[iminor(inode) >> 5]; new_fops = fops_get(handler->fops) file->f_op = new_fops; new_fops->open(inode, file);
|
上述代码, 关键点在于从 input_table 取出 input_handler的指针.
那么 input_table 由谁构造呢? 搜索可知是: input_register_handler
. 继续往下看.
软件抽象层
注册 input_handler 的过程:
int input_register_handler(struct input_handler *handler) input_table[handler->minor >> 5] = handler; // 将input_handler指针放入数组 list_add_tail(&handler->node, &input_handler_list); list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler);
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) id = input_match_device(handler->id_table, dev); error = handler->connect(handler, dev, id);
|
硬件设备层
注册 input_dev 的过程:
int input_register_device(struct input_dev *dev) list_add_tail(&dev->node, &input_dev_list); 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) id = input_match_device(handler->id_table, dev); error = handler->connect(handler, dev, id);
|
连接层
有上述分析可知, 软件抽象层和硬件设备层建立连接的关键是 handler->connect
指向的函数.
建立连接的基本步骤如下:
- 分配一个
input_handle(没有r)
结构体
- 设置/初始化
input_handle(没有r)
input_handle.dev = input_dev;
保存硬件设备, input_dev
input_handle.handler = input_handler;
保存软件抽象, input_handler
- 软件层和硬件层分别注册
input_handle(没有r)
, 将其指针保存到各自结构体的h_list
项中
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
硬件设备层的代码最终是要自己写的, 因此我们会比较熟悉.
下面以软件抽象层的 drivers/input/evdev.c
为例, 分析一下整个匹配过程.
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) id = input_match_device(handler->id_table, dev); error = handler->connect(handler, dev, id);
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
evdev->handle.dev = dev; evdev->handle.name = evdev->name; evdev->handle.handler = handler; evdev->handle.private = evdev;
class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);
error = input_register_handle(&evdev->handle); list_add_tail(&handle->d_node, &handle->dev->h_list); list_add_tail(&handle->h_node, &handler->h_list);
|
APP层相关函数
最后, 从APP层读取输入子系统进行分析. 以按键为例, 用到 drivers/input/evdev.c
和 drivers/char/keyboard.c
在书写按键的硬件模块时, input子系统会自动匹配关联到上述两个软件抽象层.
(猜测, keyboard的抽象层次比evdev更高. 因此keyboard没有input_table初始值)
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_event wake_up_interruptible(&evdev->wait);
|
硬件设备端源码
由于input子系统的实际上是帮我们完成了相当一部分的注册工作, 并实现了通用的逻辑功能.
因此实际写硬件设备驱动时, 反而变得更简单了. 核心步骤如下:
- 分配一个input_dev结构体.
input_allocate_device
- 设置事件, 设置事件支持的操作类型
- 注册
input_register_device
- 硬件初始化和逻辑判断
- 上报事件:
input_event
input_sync
依旧通过 LinK+ 软件来写驱动. LinK+设置步骤可参考 驱动之基于LinK+设计按键驱动
与input有关的设置页面如下:
#include"input_keys.h"
MODULE_LICENSE("GPL"); MODULE_AUTHOR("DRAAPHO");
struct keys_desc{ int irq; char *name; unsigned int pin; unsigned int key_val; };
struct keys_desc keys_desc_public[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}, };
struct input_keys_private { struct input_dev *dev; struct keys_desc *keysdesc; struct timer_list keys_timer; };
struct input_keys_private *input_keys_priv;
static irqreturn_t keys_irq(int irq, void *dev_id) { if (!input_keys_priv) return IRQ_NONE;
input_keys_priv->keysdesc = (struct keys_desc *)dev_id; mod_timer(&input_keys_priv->keys_timer, jiffies+HZ/100); return IRQ_RETVAL(IRQ_HANDLED); }
static void keys_timer_function(unsigned long data) { struct input_dev *keydev; struct keys_desc *keydesc; unsigned int pinval;
if (!input_keys_priv) return;
keydesc= input_keys_priv->keysdesc; keydev = input_keys_priv->dev; pinval = s3c2410_gpio_getpin(keydesc->pin);
if (pinval) { input_event(keydev, EV_KEY, keydesc->key_val, 0); input_sync(keydev); } else { input_event(keydev, EV_KEY, keydesc->key_val, 1); input_sync(keydev); } }
static int input_keys_open(struct input_dev *dev) { PINFO("input_keys_open \n"); return 0; }
static void input_keys_close(struct input_dev *dev) { PINFO("input_keys_close \n"); }
static int __init input_keys_init(void) { int i, res;
PINFO("input_keys_init\n"); input_keys_priv = kzalloc(sizeof(struct input_keys_private),GFP_KERNEL); input_keys_priv->dev = input_allocate_device(); input_keys_priv->dev->name = DRIVER_NAME; input_keys_priv->dev->open = input_keys_open; input_keys_priv->dev->close = input_keys_close;
set_bit(EV_KEY,input_keys_priv->dev->evbit); set_bit(EV_REP,input_keys_priv->dev->evbit); set_bit(KEY_L, input_keys_priv->dev->keybit); set_bit(KEY_S, input_keys_priv->dev->keybit); set_bit(KEY_ENTER, input_keys_priv->dev->keybit); set_bit(KEY_LEFTSHIFT, input_keys_priv->dev->keybit);
input_keys_priv->keysdesc = keys_desc_public; input_keys_priv->keys_timer.function = keys_timer_function; input_set_drvdata(input_keys_priv->dev , input_keys_priv);
PINFO("input_keys_init_befor_register\n"); res = input_register_device(input_keys_priv->dev); if(res<0) { PERR("input registration failed. error_id=%d\n", res); goto fail1; } PINFO("input_keys_init_after_register\n");
for (i = 0; i < 4; i++) { res = request_irq(keys_desc_public[i].irq, keys_irq, IRQT_BOTHEDGE, keys_desc_public[i].name, &keys_desc_public[i]); if (res<0) { PERR("request_irq(%d), error_id=%d\n", i, res); goto fail2; } }
init_timer(&input_keys_priv->keys_timer); add_timer(&input_keys_priv->keys_timer); return 0;
fail2: for (i = 0; i < 4; i++) { free_irq(keys_desc_public[i].irq, &keys_desc_public[i]); } fail1: input_unregister_device(input_keys_priv->dev); input_free_device(input_keys_priv->dev); kfree(input_keys_priv); return -EBUSY; }
static void __exit input_keys_exit(void) { int i;
del_timer(&input_keys_priv->keys_timer); for (i = 0; i < 4; i++) { free_irq(keys_desc_public[i].irq, &keys_desc_public[i]); }
PINFO("input_keys_exit_before_unregister\n"); input_unregister_device(input_keys_priv->dev); PINFO("input_keys_exit_after_unregister\n"); input_free_device(input_keys_priv->dev); kfree(input_keys_priv); PINFO("input_keys_exit\n"); }
module_init(input_keys_init); module_exit(input_keys_exit);
|
#define DRIVER_NAME "input_keys" #define PDEBUG(fmt,args...) printk(KERN_DEBUG"%s:"fmt,DRIVER_NAME, ##args) #define PERR(fmt,args...) printk(KERN_ERR"%s:"fmt,DRIVER_NAME,##args) #define PINFO(fmt,args...) printk(KERN_INFO"%s:"fmt,DRIVER_NAME, ##args) #include<linux/init.h> #include<linux/input.h> #include<linux/interrupt.h> #include<linux/module.h> #include<linux/slab.h>
#include <linux/irq.h> #include <asm/gpio.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h>
|
Makefile
obj-m := input_keys.o KERN_SRC := /home/draapho/share/jz2440/kernel/linux-2.6.22.6/ PWD := $(shell pwd)
modules: make -C $(KERN_SRC) M=$(PWD) modules
install: make -C $(KERN_SRC) M=$(PWD) modules_install depmod -a
clean: make -C $(KERN_SRC) M=$(PWD) clean
|
测试
$ insmod input_keys.ko input_keys:input_keys_init input_keys:input_keys_init_befor_register input: input_keys as /class/input/input1 input_keys:input_keys_open input_keys:input_keys_init_after_register
$ cat /dev/tty1
$ exec 0</dev/tty1 $ ls
$ vi /etc/inittab ::sysinit:/etc/init.d/rcS s3c2410_serial0::askfirst:-/bin/sh tty1::askfirst:-/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r $ reboot
$ rmmod input_keys.ko input_keys:input_keys_exit_before_unregister input_keys:input_keys_close input_keys:input_keys_exit_after_unregister input_keys:input_keys_exit
|
额外说一下 hexdump
的测试方法
$ insmod input_keys.ko $ hexdump /dev/event1
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
|
参考资料