天天看点

输入子系统

又叫做 input 子系统

一. 简介:

输入子系统主要涉及到比如键盘上的按键,鼠标的按键,滚轮等的事件,在linux内核中相应部分叫做输入子系统,按照子系统的规则,可以复用内核中代码,很简单的实现开发板上的按键实现鼠标的左键,键盘上的按键等的动作。

        Linux之输入子系统分析(详解)

总结:

1. 注册输入子系统,进入input_init():

创建主设备号为13的"input"字符设备

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

2. 在open打开驱动时,进入input_open_file():

1)更新设备的file_oprations

file->f_op = fops_get(handler->fops);

2)执行file_oprations->open函数

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

3. 注册input_handler,进入input_register_handler():

1)添加到input_table[]处理数组中

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

2)添加到input_handler_list链表中

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

3)判断input_dev的id,是否有支持这个驱动的设备

list_for_each_entry(dev, &input_dev_list, node) //遍历查找input_dev_list链表里所有input_dev

input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。

4.注册input_dev,进入input_register_device():

1)放在input_dev_list链表中

list_add_tail(&dev->node, &input_dev_list);

2)判断input_handler的id,是否有支持这个设备的驱动

list_for_each_entry(handler, &input_handler_list, node) //遍历查找input_handler_list链表里所有input_handler

input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。

5.判断input_handler和input_dev的id,进入input_attach_handler():

1)匹配两者id,

input_match_device(handler->id_table, dev); //匹配input_handler和dev的id,不成功退出函数

2)匹配成功调用input_handler ->connect

handler->connect(handler, dev, id); //建立连接

6.建立input_handler和input_dev的连接,进入input_handler->connect():

1)创建全局结构体,通过input_handle结构体连接双方

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //创建两者连接的input_handle全局结构体

list_add_tail(&handle->d_node, &handle->dev->h_list); //连接input_dev->h_list

list_add_tail(&handle->h_node, &handler->h_list); // 连接input_handle->h_list

7.有事件发生时,比如按键中断,在中断函数中需要进入input_event()上报事件:

1)找到驱动处理结构体,然后执行input_handler->event()

list_for_each_entry(handle, &dev->h_list, d_node) // 通过input_dev ->h_list链表找到input_handle驱动处理结构体

if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体(有可能一个驱动设备在不同情况下有不同的驱动处理方式)

handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数

二、 几点说明:

上边的博客写的比较详细了,只是有几点需要特殊说明:

分析主要涉及的文件有 input.c evdev.c

event方法是在什么时候调用的:

是通过 input_event --> input_handle_event --> input_pass_event --> handler->event ..

evdev.c 中提供了read方法,此方法提供给了用户读的支持,可以帮助用户调试驱动程序,evdev_read中按照 struct input_event 结构发送给用户层。

输入子系统
输入子系统
输入子系统
输入子系统
输入子系统

三、用到的函数简介:

1. 分配一个 struct input_dev :

struct input_dev *input_allocate_device(void)

2. 把地址上的nr位置为1:

static inline void set_bit(int nr, volatile unsigned long *addr)

3. 注册设备:

int input_register_device(struct input_dev *dev)

4. 报告一个新的输入事件:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

type,事件类型

code,事件码

value,事件的值

5. 立即上报:

static inline void input_sync(struct input_dev *dev)

{

        input_event(dev, EV_SYN, SYN_REPORT, 0);

}

struct input_dev :

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.c中两个链表:

input_dev_list、input_handler_list

input_handle 是输入设备和输入设备handler的纽带或者说是提供链接。

输入子系统

四、 实例:

1. 源码:

/* 参考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 <mach/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_GPF(0),   KEY_L},
	{IRQ_EINT2,  "S3", S3C2410_GPF(2),   KEY_S},
	{IRQ_EINT11, "S4", S3C2410_GPG(3),   KEY_ENTER},
	{IRQ_EINT19, "S5",  S3C2410_GPG(11), 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, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 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");



           

Makefile

KERN_DIR = ~/your path

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= buttons.o
           

2. 分析:

定义全局变量:一个所有按键资源的集合,一个struct input_dev的指针、一个正在操作的的按键结构体指针、一个定时器

输入子系统

模块入口函数中

输入子系统

中断申请函数中,将按键的资源作为参数传递给中断相应服务函数,中断服务函数中,将传入的参数,传递给全局的 正在操作的 资源,并开始更改定时器的时间,定时器到时后,获取此时的按键值,根据按键值发送 event 事件

输入子系统
输入子系统

3. 系统中验证方法:

将nfs中的根目录挂载到板子的/mnt目录下

mount -t nfs -o nolock,vers=2 192.168.1.214:/source /mnt

将编译出来的模块拷贝到共享目录下

cp buttons.ko /source/

在板子上加载模块

> cat /dev/tty1 // 需要按入enter后才能显示

> exec 0</dev/tty1 // 将/dev/tty1挂载到-sh进程描述符0下,此时的键盘驱动就会直接打印在tty1终端上

我们看一下 event 的读的方法,之前介绍了,读的方法是将struct input_event结构拷贝给用户

struct input_event {

        struct timeval time; // 2个32位数

        __u16 type;

        __u16 code;

        __s32 value;

};

[email protected]:/dev/input$ od -x event1

0000000 45b1 386d 1d5a 0005 0001 001c 0001 0000

0000020 45b1 386d 1d86 0005 0000 0000 0000 0000

0000040 45b1 386d 7abd 0007 0001 001c 0000 0000

0000060 45b1 386d 7adf 0007 0000 0000 0000 0000

我们看前两行:

0000000 表示 编号 od命令自己的

45b1 386d 表示的是32位的s ---> 值是 0x 386d 45b1

1d5a 0005 us

0001 表示的是 type

001c 表示的是code

0001 0000 表示的是值 1

第二行表示的是 input_sync

下一篇: ctrl+alt+del

继续阅读