input
在此文章之前,我们讲解的都是简单的字符驱动,涉及的内容有字符驱动的框架、自动创建设备节点、linux中断、poll机制、异步通知、同步互斥、非阻塞、定时器去抖动。
上一节文章链接:http://blog.csdn.net/lwj103862095/article/details/17589311
在这一节里,我们要引入linux的分离分层的概念,linux输入子系统是一个很好的代表,在讲解如何编写input子系统的驱动之前,我们理所当然的要先好好认识一下input子系统的框架。
一、linux输入子系统的框架(摘自作者:刘洪涛,华清远见嵌入式学院讲师。)
下图是input输入子系统框架,输入子系统由输入子系统核心层(Input Core),驱动层和事件处理层(Event Handler)
三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过
input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。
二、drivers/input/input.c:
入口函数input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
- <span style="font-size:14px;">static int __init input_init(void)
- {
- int err;
- ...
- /* 创建类 */
- err = class_register(&input_class);
- ...
- /* 注册一个字符驱动,主设备号为13 */
- err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
- ...
- return 0;
- }</span>
只有一个open函数,其他read,write函数呢?
- <span style="font-size:14px;">static const struct file_operations input_fops = {
- .owner = THIS_MODULE,
- .open = input_open_file,
- };</span>
input_open_file函数
- <span style="font-size:14px;">static int input_open_file(struct inode *inode, struct file *file)
- {
- struct input_handler *handler;
- const struct file_operations *old_fops, *new_fops = NULL;
- int err;
- ...
- /* 以次设备号为下标,在input_table数组找到一项handler */
- handler = input_table[iminor(inode) >> 5];
- /* 通过handler找到一个新的fops */
- new_fops = fops_get(handler->fops);
- ...
- old_fops = file->f_op;
- /* 从此file->f_op = new_fops */
- file->f_op = new_fops;
- ...
- /* 用新的new_fops的打开函数 */
- err = new_fops->open(inode, file);
- ...
- return err;
- }</span>
input_handlerj结构体成员
- <span style="font-size:14px;">struct input_handler {
- void *private;
- void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
- int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
- void (*disconnect)(struct input_handle *handle);
- void (*start)(struct input_handle *handle);
- const struct file_operations *fops;
- 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;
- };</span>
问:怎么读按键?
APP:read > ... > file->f_op->read
问:input_table数组由谁构造?
答:input_register_handler
三、input_register_handler函数(注册input_handler)
- <span style="font-size:14px;">int input_register_handler(struct input_handler *handler)
- {
- struct input_dev *dev;
- ...
- INIT_LIST_HEAD(&handler->h_list);
- ...
- /* 将handler放入input_table数组 */
- input_table[handler->minor >> 5] = handler;
- ...
- /* 将handler放入input_handler_list链表 */
- list_add_tail(&handler->node, &input_handler_list);
- ...
- /* 对于每个input_dev,调用input_attach_handler
- * 根据input_handler的id_table判断能否支持这个input_dev
- */
- list_for_each_entry(dev, &input_dev_list, node)
- input_attach_handler(dev, handler);
- ...
- }</span>
四、input_register_device函数(注册inout_dev)
- <span style="font-size:14px;">int input_register_device(struct input_dev *dev)
- {
- ...
- struct input_handler *handler;
- ...
- device_add(&dev->dev);
- ...
- /* 把input_dev放入input_dev_list链表 */
- list_add_tail(&dev->node, &input_dev_list);
- ...
- /* 对于每一个input_handler,都调用input_attach_handler
- * 根据input_handler的id_table判断能否支持这个input_dev
- */
- list_for_each_entry(handler, &input_handler_list, node)
- input_attach_handler(dev, handler);
- ...
- }</span>
五、input_attach_handler函数
- <span style="font-size:14px;">static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
- {
- const struct input_device_id *id;
- ...
- /* 根据input_handler的id_table判断能否支持这个input_dev */
- input_match_device(handler->id_table, dev);
- ...
- /* 若支持,则调用handler的connect函数,建立连接 */
- handler->connect(handler, dev, id);
- ...
- }</span>
小总结:
注 册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,根据 input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用 input_handler的connect函数建立"连接"。
问:如何建立连接connect?
答:举例,evdev_connect函数
- <span style="font-size:14px;">static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
- const struct input_device_id *id)
- {
- struct evdev *evdev;
- ...
- /* 分配一个input_handle */
- evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
- ...
- snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
- evdev->exist = 1;
- evdev->minor = minor;
- evdev->handle.dev = input_get_device(dev); // 指向左边的input_dev
- evdev->handle.name = evdev->name;
- evdev->handle.handler = handler; // 指向右边的input_handler
- evdev->handle.private = evdev;
- /* 设置dev结构体成员 */
- dev_set_name(&evdev->dev, evdev->name);
- evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
- evdev->dev.class = &input_class;
- evdev->dev.parent = &dev->dev;
- evdev->dev.release = evdev_free;
- device_initialize(&evdev->dev);
- /* 注册 */
- input_register_handle(&evdev->handle);
- ...
- }</span>
input_handle结构体成员
- <span style="font-size:14px;">struct input_handle {
- void *private;
- int open;
- const char *name;
- struct input_dev *dev;
- struct input_handler *handler;
- struct list_head d_node;
- struct list_head h_node;
- };</span>
问:input_register_handle如何注册?
- <span style="font-size:14px;">int input_register_handle(struct input_handle *handle)
- {
- struct input_handler *handler = handle->handler;
- struct input_dev *dev = handle->dev;
- ...
- /* 把handle->d_node添加到dev->h_list
- * 这样,就可以从dev->h_list找到handle,进而找到handler
- */
- list_add_tail_rcu(&handle->d_node, &dev->h_list);
- ...
- /* 把handle->h_node添加到handler->h_list
- * 这样,就可以从handler->h_list找到handle,进而找到dev
- */
- list_add_tail(&handle->h_node, &handler->h_list);
- ...
- return 0;
- }</span>
小总结:
怎么建立连接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_read
- <span style="font-size:14px;">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;
- struct input_event event;
- ...
- /* 无数据并且是非阻塞方式打开,则立刻返回 */
- 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);
- ...
- }</span>
问:谁来唤醒?
搜索evdev->wait发现是evdev_event唤醒的
- <span style="font-size:14px;">static void evdev_event(struct input_handle *handle,
- unsigned int type, unsigned int code, int value)
- {
- struct evdev *evdev = handle->private;
- struct evdev_client *client;
- struct input_event event;
- ...
- /* 唤醒 */
- wake_up_interruptible(&evdev->wait);
- }</span>
问:evdev_event被谁调用?
答:应该是硬件相关的代码,input_dev那层调用的在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数。
举例,在drivers/input/keyboard/gpio_keys.c里的gpio_keys_isr函数
- <span style="font-size:14px;">static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
- {
- struct gpio_button_data *bdata = dev_id;
- struct gpio_keys_button *button = bdata->button;
- ...
- /* 上报事件 */
- gpio_keys_report_event(bdata);
- return IRQ_HANDLED;
- }</span>
gpio_keys_report_event函数
- <span style="font-size:14px;">static void gpio_keys_report_event(struct gpio_button_data *bdata)
- {
- struct gpio_keys_button *button = bdata->button;
- struct input_dev *input = bdata->input;
- unsigned int type = button->type ?: EV_KEY;
- int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;
- /* 上报事件 */
- input_event(input, type, button->code, !!state);
- input_sync(input);
- }</span>
问:input_event函数如何上报事件
答:
- input_event-->input_handle_event-->input_pass_event
- list_for_each_entry_rcu(handle, &dev->h_list, d_node)
- if (handle->open)
- handle->handler->event(handle,
- type, code, value);
怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件
这一节,我们来以输入子系统的框架来写一个按键驱动。
问:怎么写符合输入子系统框架的驱动程序?
答:
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件
问:如何分配input_dev结构体?
答:使用input_allocate_device函数
input_dev结构体的重要成员
- struct input_dev {
- 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)]; // 表示能产生哪些相对位移事件, x,y,滚轮
- unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
- unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
- unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
- unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
- unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
- ...
- }
问:第二步的设置,应该怎么设置,应该设置什么?
答:举例,在此按键驱动里
- /* 2.设置 */
- /* 2.1 设置按键能产生哪类事件 */
- set_bit(EV_KEY,buttons_dev->evbit);
- set_bit(EV_REP,buttons_dev->evbit);
- /* 2.2 设置能产生这类操作的哪些事件 */
- 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);
问:有哪些类呢?
答:在input.h里有以下类
- #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)
问:如何注册?
答:使用input_register_device(struct input_dev *dev)函数来注册
问:此按键驱动的硬件操作包括哪些操作?
答:申请定时器、申请中断操作
驱动源码:
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <linux/irq.h>
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <linux/module.h>
- #include <linux/device.h> //class_create
- #include <mach/regs-gpio.h> //S3C2410_GPF1
- //#include <asm/arch/regs-gpio.h>
- #include <mach/hardware.h>
- //#include <asm/hardware.h>
- #include <linux/interrupt.h> //wait_event_interruptible
- #include <linux/poll.h> //poll
- #include <linux/fcntl.h>
- #include <linux/input.h>
- static struct pin_desc{
- int irq;
- unsigned char *name;
- unsigned int pin;
- unsigned int key_val;
- };
- static struct pin_desc pins_desc[4] = {
- {IRQ_EINT1,"K1",S3C2410_GPF1,KEY_L},
- {IRQ_EINT4,"K2",S3C2410_GPF4,KEY_S},
- {IRQ_EINT2,"K3",S3C2410_GPF2,KEY_ENTER},
- {IRQ_EINT0,"K4",S3C2410_GPF0,KEY_LEFTSHIFT},
- };
- static struct pin_desc *irq_pd;
- static struct input_dev *buttons_dev;
- static struct timer_list buttons_timer;
- /* 用户中断处理函数 */
- static irqreturn_t buttons_irq(int irq, void *dev_id)
- {
- irq_pd = (struct pin_desc *)dev_id;
- /* 修改定时器定时时间,定时10ms,即10秒后启动定时器
- * HZ 表示100个jiffies,jiffies的单位为10ms,即HZ = 100*10ms = 1s
- * 这里HZ/100即定时10ms
- */
- mod_timer(&buttons_timer, jiffies + (HZ /100));
- return IRQ_HANDLED;
- }
- /* 定时器处理函数 */
- static void buttons_timer_function(unsigned long data)
- {
- struct pin_desc *pindesc = irq_pd;
- unsigned int pinval;
- 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_input_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 设置能产生这类操作的哪些事件 */
- 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.硬件相关的设置 */
- /* 4.1 定时器相关的操作 */
- init_timer(&buttons_timer);
- buttons_timer.function = buttons_timer_function;
- add_timer(&buttons_timer);
- /* 4.2 申请中断 */
- for(i = 0;i < sizeof(pins_desc)/sizeof(pins_desc[0]);i++)
- {
- request_irq(pins_desc[i].irq, buttons_irq, IRQ_TYPE_EDGE_BOTH, pins_desc[i].name, &pins_desc[i]);
- }
- return 0;
- }
- /* 驱动出口函数 */
- static void buttons_input_exit(void)
- {
- int i;
- for(i = 0;i < sizeof(pins_desc)/sizeof(pins_desc[0]);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_input_init); //用于修饰入口函数
- module_exit(buttons_input_exit); //用于修饰出口函数
- MODULE_AUTHOR("LWJ");
- MODULE_DESCRIPTION("Just for Demon");
- MODULE_LICENSE("GPL"); //遵循GPL协议
测试步骤方法一:
- [WJ2440]# ls
- Qt first_test second_test
- TQLedtest fourth_drv.ko sixth_drv.ko
- app_test fourth_test sixth_test
- bin home sixthdrvtest
- buttons_all_drv.ko lib sys
- buttons_all_test linuxrc third_drv.ko
- buttons_input.ko mnt third_test
- dev opt tmp
- driver_test proc udisk
- etc root usr
- fifth_drv.ko sbin var
- fifth_test sddisk web
- first_drv.ko second_drv.ko
- [WJ2440]# ls /dev/event* -l
- crw-rw---- 1 root root 13, 64 Jan 2 06:04 /dev/event0
- [WJ2440]# insmod buttons_input.ko
- input: Unspecified device as /devices/virtual/input/input1
- [WJ2440]# ls /dev/event* -l
- crw-rw---- 1 root root 13, 64 Jan 2 06:04 /dev/event0
- crw-rw---- 1 root root 13, 65 Jan 2 06:06 /dev/event1
- [WJ2440]# cat /dev/tty1
- [WJ2440]# cat /dev/tty1
- ls
- ls
输入cat /dev/tty1命令后,顺序按下K1,K2,K3则会显示ls
测试步骤方法二、
- [WJ2440]# hexdump /dev/event1
- 0000000 b738 495d 8456 0007 0001 0026 0001 0000
- 0000010 b738 495d 846f 0007 0000 0000 0000 0000
- 0000020 b738 495d 2fb8 000a 0001 0026 0000 0000
- 0000030 b738 495d 2fc7 000a 0000 0000 0000 0000
分析:
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
- struct input_event {
- struct timeval time; //时间
- __u16 type; //类
- __u16 code; //类下事件的值
- __s32 value; //0-松开, 1-按下,2-重复
- };
- struct timeval {
- __kernel_time_t tv_sec; //秒
- __kernel_suseconds_t tv_usec; //微秒
- };
疑问:在韦老师视频里,执行exec 0</dev/tty1 //标准输入改为tty1
我自己执行的时候,发现文件系统里并没有exec可执行文件,也就是busybox里没有移植有exec可执行文件,去找韦老师的文件系统时,也没有发现exec可执行文件,请教各位大虾,如果你有执行成功,请告之,感谢。

浙公网安备 33010602011771号