input子系统分析

Linux的输入子系统不仅支持鼠标、键盘等常规输入设备,而且还支持蜂鸣器、触摸屏等设备。本章将对 Linux 输入子系统进行详细的分析。

结构:


下面根据上面的结构一层一层的分析

一、硬件驱动层(input_dev)

1  input 子系统入门 
输入子系统又叫 input 子系统。其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。本节将从一个实例开始,介绍编写输入子系统硬件驱动程序的方法。
1.1  简单的实例  
本节将讲述一个简单的输入设备驱动实例。这个输入设备只有一个按键,按键被连接到一条中断线上,当按键被按下时,将产生一个中断,内核将检测到这个中断,并对其进行处理。
该实例的代码如下: 

#include <asm/irq.h>   
#include <asm/io.h>   
static struct input_dev *button_dev;    //输入设备input_dev结构体    
static  irqreturn_t  button_interrupt(int  irq,  void  *dummy)   //中断处理函数  
{   
      input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1); //向输入子系统报告产生按键事件    
      input_sync(button_dev);//通知接收者,一个报告发送完毕    
      return IRQ_HANDLED;    
} 
  
static int __init button_init(void) /*初始化加载函数*/    
{    
     int error;    
     if  (request_irq(BUTTON_IRQ,  button_interrupt,  0,  "button",  NULL))  /* 申请中断处理函数*/  
     {    /*申请失败,则打印出错信息*/    
          printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);    
          return -EBUSY;    
      }    
      button_dev = input_allocate_device();   /*分配一个设备结构体*/    
      if (!button_dev)                        /* 判断分配是否成功*/    
      {   
          printk(KERN_ERR "button.c: Not enough memory\n");   
          error = -ENOMEM;    
          goto err_free_irq;    
      }    
      button_dev->evbit[0] = BIT_MASK(EV_KEY);    /*设置按键信息*/    
      button_dev ->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);     
      error = input_register_device(button_dev);  /* 注册一个输入设备*/    
      if (error)    
      {    
          printk(KERN_ERR "button.c: Failed to register device\n");     
          goto err_free_dev;    
      }    
      return 0;    
err_free_dev:                               /*以下是错误处理*/    
      input_free_device(button_dev);   
err_free_irq:    
      free_irq(BUTTON_IRQ, button_interrupt);     
      return error;    
}    
static void __exit button_exit(void)            /*卸载函数*/    
{    
      input_unregister_device(button_dev);        /* 注销按键设备*/    
      free_irq(BUTTON_IRQ, button_interrupt); /*释放按键占用的中断线*/    
}    
module_init(button_init);    
module_exit(button_exit)
这个实例程序代码比较简单,在初始化函数 button_init()中注册了一个中断处理函数,然后调用   input_allocate_device() 函数分配了一个 input_dev结构体,并调用 input_register_device()函数对其进行了注册。在中断处理函数button_interrupt()中,实例将接收到的按键信息上报给input 子系统。从而通过 input 子系统,向用户态程序提供按键输入信息。 

本实例采用了中断方式,除了中断相关的代码外,实例中包含了一些input 子系统提供的函数,现对其中一些重要的函数进行分析。

input_allocate_device() 

这个函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化。该函数的代码如下:

struct input_dev *input_allocate_device(void)
{
	struct input_dev *dev;

	dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
	if (dev) {
		dev->cdev.class = &input_class;/*设置为输入设备类,注册后,会在/sys/class/input下生成对应的类*/ 
		dev->cdev.groups = input_dev_attr_groups;
		class_device_initialize(&dev->cdev);//作用?
		mutex_init(&dev->mutex);//初始化互斥锁
		INIT_LIST_HEAD(&dev->h_list);//初始化h_list链表
		INIT_LIST_HEAD(&dev->node);//初始化node链表

		__module_get(THIS_MODULE);
	}

	return dev;
}

该函数返回一个指向 input_dev 类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如设备支持的按键码、设备的名称、设备支持的事件等。在本章用到这个结构体时,将对其进行详细介绍。此处将注意力集中在实例中的函数上

问题:

1.groups是什么?

2.class_device_initialize(&dev->cdev);作用是什么?


input_register_device()

input_register_device() 函数是输入子系统核心(input core)提供的函数。该函数将 input_dev结构体注册到输入子系统核心中,input_dev 结构体必须由前面讲的 input_allocate_device()函数来分配。input_register_device() 函数如果注册失败,必须调用input_free_device() 函数释放分配的空间。如果该函数注册成功,在卸载函数中应用 input_unregister_device() 函数来注销输入设备结构体。 

int input_register_device(struct input_dev *dev)
{
	static atomic_t input_no = ATOMIC_INIT(0);
	struct input_handler *handler;
	const char *path;
	int error;

	set_bit(EV_SYN, dev->evbit);//设置input_dev所支持的事件类型。EV_SYN表示设备支持所有的事件

	/*
	 * If delay and period are pre-set by the driver, then autorepeating
	 * is handled by the driver itself and we don't do it in input.c.
	 */

	init_timer(&dev->timer);//初始化一个 timer 定时器,这个定时器是为处理重复击键而定义的。
	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {//如果 dev->rep[REP_DELAY] 和dev->rep[REP_PERIOD] 没有设值,则将其赋默认值,这主要是为自动处理重复按键定义的。 
		dev->timer.data = (long) dev;//传进input_repeat_key的参数为input_dev
		dev->timer.function = input_repeat_key;//定时器调用函数
		dev->rep[REP_DELAY] = 250;
		dev->rep[REP_PERIOD] = 33;
	}
//检查getkeycode()函数和setkeycode()函数是否被定义,如果没定义,则使用默认的处理函数,这两个函数为input_default_getkeycode()和input_default_setkeycode()。 input_default_getkeycode()函数用来得到指定位置的键值。input_default_setkeycode()函数用来设置键值。
	if (!dev->getkeycode)
		dev->getkeycode = input_default_getkeycode;

	if (!dev->setkeycode)
		dev->setkeycode = input_default_setkeycode;
        //将这个设备input设备加入到input_dev_list链表中,在和input_handler进行匹配的时候会用到
	list_add_tail(&dev->node, &input_dev_list);
        //设置 input_dev 中的 device 的名字,名字以 input0 、input1 、input2 、input3 、input4等的形式出现在 sysfs 文件系统中。 
	snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
		 "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);

	if (!dev->cdev.dev)
		dev->cdev.dev = dev->dev.parent;
//使用 class_device_add()函数将 input_dev 包含的device 结构注册到 Linux设备模型中,并可以在sysfs 文件系统中表现出来。 
	error = class_device_add(&dev->cdev);
	if (error)
		return error;
        //打印路径
	path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
	printk(KERN_INFO "input: %s as %s\n",
		dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
	kfree(path);
        //遍历input_handler_list
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);//匹配 input_dev 和handler,下面分析

	input_wakeup_procfs_readers();

	return 0;
}

1.3  向子系统报告事件input_report_key

在之前的代码的中断函数中调用了 input_report_key() 函数向输入子系统报告发生的事件,这里就是一个按键事件。在 button_interrupt()中断函数中,不需要考虑重复按键的重复点击情况,input_report_key() 函数会自动检查这个问题,并报告一次事件给输入子系统。该函数的代码如下:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);
}
该函数的第1 个参数是产生事件的输入设备,第2 个参数是产生的事件,第3 个参数是事件的值。需要注意的是, 第2 个参数可以取类似 BTN_0 、  BTN_1 、BTN_LEFT 、BTN_RIGHT 等值,这些键值被定义在 include/linux/input.h 文件中。当第 2 个参数为按键时,第3 个参数表示按键的状态,value值为0 表示按键释放,非 0 表示按键按下。
input_event的实现
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	struct input_handle *handle;

	if (type > EV_MAX || !test_bit(type, dev->evbit))
		return;

	add_input_randomness(type, code, value);

	switch (type) {
...
		case EV_KEY:

			if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
				return;

			if (value == 2)
				break;

			change_bit(code, dev->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]));//对重复按键的处理
			}

			break;

...
	}

	if (type != EV_SYN)
		dev->sync = 0;

	if (dev->grab)
		dev->grab->handler->event(dev->grab, type, code, value);
	else
		list_for_each_entry(handle, &dev->h_list, d_node)
			if (handle->open)
				handle->handler->event(handle, type, code, value);//调用对应handler的event函数,这个event是input_handler层的函数
}

到这里硬件驱动层的就已经分析完成了。

二、input_core核心层

这层就是 drivers\input\Input.c中的相关,它在硬件驱动层(input_dev)和handler层(input_handler)起着桥梁的作用。下面就看下它是如何起着桥梁作用的。

在前面的所说的input_register_device和之后要说的input_register_handler函数中都会有这么两段代码:

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

其中input_attach_handler就是关键函数:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

	if (handler->blacklist && input_match_device(handler->blacklist, dev))
		return -ENODEV;

	id = input_match_device(handler->id_table, dev);//使用handler->id_table和input_dev进行匹配
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);//handler层的connect函数
	if (error && error != -ENODEV)
		printk(KERN_ERR
			"input: failed to attach handler %s to device %s, "
			"error: %d\n",
			handler->name, kobject_name(&dev->cdev.kobj), error);

	return error;
}

三、input_handler层

在核心层中进行匹配要使用到handler层的connect函数



在以前写的字符设备驱动中,上层app是如何找到底层的驱动程序的?

上层app通过打开设备的主设备号(major)找到对应的file_operations,file_operations是通过register_chrdev注册,register_chrdev在入口函数中调用,入口函数又是由module_init调用。

这样的字符设备驱动不具有通用性。所以就引入了input子系统。在input子系统中关于上面字符设备的相关操作,系统已经帮助做好了。

在linux中像按键,键盘都是通过input子系统来完成的,所以了解input子系统内部的操作是比较重要的。

input_handler中的id_table表示支持哪一些设备,是input_handler和input_device建立连接的桥梁

一、input子系统的中转层input.c,代码位于drivers/input/input.c

首先看看它的入口:

static int __init input_init(void)
{
	int err;
	err = class_register(&input_class);//这个函数就是向内核注册input类,会在/sys/class下面生成input目录
	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);//向内核中注册一个名字为input,主设备号为13的字符设备。
	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;
}

static void __exit input_exit(void)
{
	input_proc_exit();
	unregister_chrdev(INPUT_MAJOR, "input");
	class_unregister(&input_class);
}

subsys_initcall(input_init);
module_exit(input_exit);
首先分析下初始化函数:

input_class的定义如下:

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

问题:

1.class_register的分析?

2.input_proc_init的分析?

3.subsys_initcall的作用?

这里注册的字符设备被的file_operation为:

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};
只有一个open函数,列出该open函数的实现如下:
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;

	/* No load-on-demand here? */
	if (!handler || !(new_fops = fops_get(handler->fops)))//从获取的handler中得到file_operations
		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;//将获取的file_operations付接给对应的file,这样以后要是读写的话就可以调用到新的fops了

	err = new_fops->open(inode, file);//获取新的open操作

	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
	fops_put(old_fops);
	return err;
}

由上面的代码可以看到input_table[iminor(inode) >> 5]是主要线索。搜索一下看看input_table是在哪定义的:

static struct input_handler *input_table[8];//input_table被定义为静态指针数组

赋值操作在以下函数中赋值:

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;//定义一个input_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);

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();
	return 0;
}
从上面函数可知input_table是由input_register_handler函数传进来的参数进行赋值的,继续搜索:

input_register_handler在很多地点被调用到了,比如Evdev.c,Joydev.c(手柄),Keyboard.c(键盘),Mousedev.c(鼠标)都用到了这个函数。拿Evdev.c为例:

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_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,//64
	.name =		"evdev",
	.id_table =	evdev_ids,
};
id_table的定义:

static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 },	/* Matches all devices表示匹配所有的设备 */
	{ },			/* Terminating zero entry */
};
看到这里回头看下:input_register_handler
int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;//定义一个input_dev变量

	INIT_LIST_HEAD(&handler->h_list);//初始化一个h_list链表

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

		input_table[handler->minor >> 5] = handler;//把evdev_handler放在input_table[2]中
	}

	list_add_tail(&handler->node, &input_handler_list);//将这个handler加入大到input_handler_list链表中

	list_for_each_entry(dev, &input_dev_list, node)//遍历input_dev_list链表
		input_attach_handler(dev, handler);//匹配input_dev和input_handler,下面分析

	input_wakeup_procfs_readers();
	return 0;
}
input_attach_handler
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;//定义一个input_device_id,就是之前的evdev中的id_table
	int error;

	if (handler->blacklist && input_match_device(handler->blacklist, dev))//如果使用evdev这里没有走
		return -ENODEV;

	id = input_match_device(handler->id_table, dev);//对input_handler和input_dev进行匹配操作
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);//只要id不为NULL,就调用handler的connect函数
	if (error && error != -ENODEV)
		printk(KERN_ERR
			"input: failed to attach handler %s to device %s, "
			"error: %d\n",
			handler->name, kobject_name(&dev->cdev.kobj), error);

	return error;
}
input_match_device
static const struct input_device_id *input_match_device(const struct input_device_id *id,
							struct input_dev *dev)
{
	int i;

	for (; id->flags || id->driver_info; id++) {//evdev中的driver_info为1,在这里可以看到,它是匹配所有的设备的

		if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
			if (id->bustype != dev->id.bustype)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
			if (id->vendor != dev->id.vendor)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
			if (id->product != dev->id.product)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
			if (id->version != dev->id.version)
				continue;

		MATCH_BIT(evbit,  EV_MAX);
		MATCH_BIT(keybit, KEY_MAX);
		MATCH_BIT(relbit, REL_MAX);
		MATCH_BIT(absbit, ABS_MAX);
		MATCH_BIT(mscbit, MSC_MAX);
		MATCH_BIT(ledbit, LED_MAX);
		MATCH_BIT(sndbit, SND_MAX);
		MATCH_BIT(ffbit,  FF_MAX);
		MATCH_BIT(swbit,  SW_MAX);

		return id;
	}

	return NULL;
}
下面就继续看看input_dev和input_handler是如何connect的,也就是上面的edev_handler中的connect函数 evdev_connect
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分配内存空间
	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,下面就是对handle进行一些初始化,这里是将input_dev付接到handle上
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler;//将input_handler付接到handle上
	evdev->handle.private = evdev;//将evdev也挂接上去
	sprintf(evdev->name, "event%d", minor);

	evdev_table[minor] = evdev;

	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;

	error = input_register_handle(&evdev->handle);//调用这个函数注册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;
}
input_register_handle
int input_register_handle(struct input_handle *handle)
{
	struct input_handler *handler = handle->handler;//根据传进来的handle获取input_handler

	list_add_tail(&handle->d_node, &handle->dev->h_list);//input_dev的链表加入到handle的d_node上
	list_add_tail(&handle->h_node, &handler->h_list);//将handler的链表加入到handle的h_node上
        //这样就dev和handler就可以通过handle进行联系了
	if (handler->start)
		handler->start(handle);

	return 0;
}
下面总结一下open操作:
问:怎么读按键?

input_open_file
 struct input_handler *handler = input_table[iminor(inode) >> 5];
 new_fops = fops_get(handler->fops)  //  =>&evdev_fops
 file->f_op = new_fops;
 err = new_fops->open(inode, file);

app: read > ... > file->f_op->read 

input_table数组由谁构造?

input_register_handler

这个函数又是由谁调用的呢?搜索可知很多文件调用:有evdev,joydev,keypaddev等等。
注册input_handler:
input_register_handler
 // 放入数组
 input_table[handler->minor >> 5] = handler;
 
 // 放入链表
 list_add_tail(&handler->node, &input_handler_list);

 // 对于每个input_dev,调用input_attach_handler
 list_for_each_entry(dev, &input_dev_list, node)
  input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
 
 下面为转载内容:linux input子系统分析

注册输入设备:
input_register_device
 // 放入链表
 list_add_tail(&dev->node, &input_dev_list);
 
 // 对于每一个input_handler,都调用input_attach_handler
 list_for_each_entry(handler, &input_handler_list, node)
  input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev


input_attach_handler
 id = 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函数建立"连接"

因为这个connetc函数有很多的handller,取evdev中的handler举个例子

怎么建立连接?
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,没哟‘r’哦
 
 // 设置
 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);
 到这里open操作已经结束了。


怎么读按键?
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_event
 wake_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子系统的按键程序:

#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_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 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");


 

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)]; // 表示能产生哪些相对位移事件, x,y,滚轮
 unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
 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)];

测试:
1.
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</dev/tty1
然后可以使用按键来输入


3. 如果已经启动了QT:
可以点开记事本
然后按:s2,s3,s4


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值