Linux input按键设备驱动

本文基于mini2440开发板,Linux版本号是:linux-2.6.32.2

一. input模块的注册

input模块被当成了一个字符设备,注册到内核中。

#define INPUT_MAJOR		13
static int __init input_init(void)
{
	int err;
    
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	if (err) {
		printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}
	return 0;
}

可以看到,调用register_chrdev函数来注册input设备,input设备的主设备号是13.

2. open函数与input模块如何关联

当我们在应用中要操作input模块时,我们通常会open input设备,然后就可以对input设备进行读写操作,那么这之间的过程是什么呢。

  • input模块的fops
static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};
  • input模块的fops注册过程

    —>register_chrdev
    —>__register_chrdev(major, 0, 256, name, fops)

    主设备号是major,次设备号是0
    Linux input按键设备驱动_第1张图片
    可以看到,在__register_chrdev函数中,申请了一个cdev的结构体,然后将input模块的fops赋值给了cdev结构体,然后将cdev结构体添加进入内核。

    看一下cdev_add函数。
    cdev_add
    —>kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p)

    cdev_map是一个kobj_map * 全局变量,指向分配的struct kobj_map大小的内存。
    在这里插入图片描述
    Linux input按键设备驱动_第2张图片
    在kobj_map函数中,临时分配了一个probe类型的p指针,将cdev结构体当成私有数据赋值给p,然后将p插入到kobj_map指向的数组中,总之cdev结构体保存在了kobj_map指向的数组中。

  • 字符设备的open操作
    在用户空间open一个字符设备简单的流程如下:
    open
    —>sys_open
    —>chrdev_open

    分析一下chrdev_open函数
    Linux input按键设备驱动_第3张图片
    由上图可以,chrdev_open处理的事情是:
    ①判断inode->i_cdev是否存在,如果不存在,根据inode的设备号在cdev_map中找到对应的 cdev,就是上面的new,并将cdev赋值给inode->i_cdev。
    ②若inode->i_cdev已经存在,就不用再找了。
    ③获取inode->i_cdev的fops,并将它赋值给filp的f_op。
    ④filp->f_op->open函数。
    实际上,根据设备号cdev设备就是之前注册的input设备,input设备的fops如下:

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

至此,应用层调用open函数已经关联到了对驱动设备的open操作。

3.input模块的open操作

Linux input按键设备驱动_第4张图片
从上图可知,open函数主要做了一下操作:

  • 从input_table变量中获取一个handler。
  • 获取handler的fops函数,也保存file自己的fops。
  • 调用handler的fops的open函数,出错了再调用file的fops的open函数。
    此处好奇input_table是怎么来的。通过搜索,发现是在input_register_handler函数中注册的。
4.input模块handler的注册

在evdev.c这个文件里面调用了函数input_register_handler注册了一个handler。

static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.fops		= &evdev_fops,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

minor = 64.

进入input_register_handler函数里面看一下。

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;
	int retval;

	retval = mutex_lock_interruptible(&input_mutex);
	if (retval)
		return retval;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5]) {
			retval = -EBUSY;
			goto out;
		}
		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();

 out:
	mutex_unlock(&input_mutex);
	return retval;
}

上面的函数做的操作有:
①将注册的handler保存在input_table这个全局数组中,具体保存在input_table[64>>5] = input_table[2];
②将handler添加到input_handler_list链表的尾部。
③遍历input_dev_list链表,取出里面的每一个input_dev,尝试将dev和这个handler绑定。
④绑定的过程就是调用handler的connect函数。

5.input模块handler和input dev绑定

绑定过程调用的是handler的connect函数,最终在evdev.c里面调用的是evdev_connect函数。
①分配一个struct evdev结构体。
②evdev里面有一个handle结构体,将input dev赋值给handle的dev,将handler赋值给handle的handler。

	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;

③注册这个handle

input_register_handle(&evdev->handle);

④将handle添加到dev的h_list链表

list_add_tail_rcu(&handle->d_node, &dev->h_list);

⑤将handle添加到handler的h_list链表

list_add_tail(&handle->h_node, &handler->h_list);

⑥调用handler的start函数。

handler->start(handle);

⑦添加dev设备,设备名称是event0, event1

dev_set_name(&evdev->dev, "event%d", minor);
device_add(&evdev->dev);
6.input_dev的注册
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);

	/*
	 * 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);
	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
		dev->timer.data = (long) dev;
		dev->timer.function = input_repeat_key;
		dev->rep[REP_DELAY] = 250;
		dev->rep[REP_PERIOD] = 33;
	}

	if (!dev->getkeycode)
		dev->getkeycode = input_default_getkeycode;

	if (!dev->setkeycode)
		dev->setkeycode = input_default_setkeycode;

	dev_set_name(&dev->dev, "input%ld",
		     (unsigned long) atomic_inc_return(&input_no) - 1);

	error = device_add(&dev->dev);
	if (error)
		return error;

	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
	printk(KERN_INFO "input: %s as %s\n",
		dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
	kfree(path);

	error = mutex_lock_interruptible(&input_mutex);
	if (error) {
		device_del(&dev->dev);
		return error;
	}

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

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

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	return 0;
}

在input_register_device函数中主要做了一下操作:
①调用device_add(&dev->dev)函数添加input设备
②将dev设备添加到input_dev_list链表内

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

③从input_handler_list这个链表中取出每一项handler,对该handler和dev进行绑定

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

④handler和dev的绑定过程上面有讲述。

7. input模块dev和handler的数据关系

8. input按键消息的采集
  • 注册一个gpio-keys的driver驱动
    Linux input按键设备驱动_第5张图片

  • 当有gpio-keys的device注册时,自动调用driver驱动的probe函数。

  • 分析该驱动的probe函数。
    ①申请分配一个input_dev结构体

input = input_allocate_device();

②填充input结构体的一些信息

input->name = pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;

③对每一个button对应的IO口进行申请,设置IO口方向

error = gpio_request(button->gpio, button->desc ?: "gpio_keys");
error = gpio_direction_input(button->gpio);

④对每一个button对应的IO口申请中断

irq = gpio_to_irq(button->gpio);

error = request_irq(irq, gpio_keys_isr,
				    IRQF_SHARED |
				    IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
				    button->desc ? button->desc : "gpio_keys",
				    bdata);

⑤对每一个button数据结构设置一个工作队列

INIT_WORK(&bdata->work, gpio_keys_report_event);

⑥对每一个button设置一个定时器防抖

setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);

⑦注册这个input dev

error = input_register_device(input);

⑧有按键触发,进入中断,中断没有做什么操作,直接进入了工作队列,工作队列的处理函数是gpio_keys_report_event

schedule_work(&bdata->work);

⑨工作队列的处理函数中检测gpio口电平变化,调用input_event上抛按键消息。
Linux input按键设备驱动_第6张图片

9.input按键消息的上抛

①调用input_event函数上抛按键消息

input_event(input, type, button->code, !!state);
type是按键的类型,code是按键的键值,state是按键的状态,按下,抬起还是长按。

②调用input_handle_event函数继续上抛按键消息
Linux input按键设备驱动_第7张图片

③进入input_handle_event函数,消息类型是EV_KEY,调用input_pass_event函数继续上抛消息

disposition = INPUT_PASS_TO_HANDLERS;
if (disposition & INPUT_PASS_TO_HANDLERS)
		input_pass_event(dev, type, code, value);

④从dev->h_list中取出每一个handle,调用handle的handler的event函数。

list_for_each_entry_rcu(handle, &dev->h_list, d_node)
    if (handle->open)
        handle->handler->event(handle, type, code, value);

⑤handle的handler对应的event处理函数为evdev_event
Linux input按键设备驱动_第8张图片
⑥evdev_event函数中,根据handle找到对应的evdev

struct evdev *evdev = handle->private;

⑦遍历evdev->client_list,调用evdev_pass_event函数,把按键消息发给每一个client

list_for_each_entry_rcu(client, &evdev->client_list, node)
    evdev_pass_event(client, &event);

⑧evdev_pass_event函数中实际上是把按键消息保存在client的环形buffer中。

static void evdev_pass_event(struct evdev_client *client,
			     struct input_event *event)
{
	/*
	 * Interrupts are disabled, just acquire the lock
	 */
	spin_lock(&client->buffer_lock);
	client->buffer[client->head++] = *event;
	client->head &= EVDEV_BUFFER_SIZE - 1;
	spin_unlock(&client->buffer_lock);

	kill_fasync(&client->fasync, SIGIO, POLL_IN);
}

⑨唤醒等待队列,有消息到来。

wake_up_interruptible(&evdev->wait);

至此按键消息已经保存到了client->buffer中,buffer大小为64。

10.上面的client是什么,是怎么来的

在上面的描述中,input模块的open操作,最终调用了handler->fops的open函数。最终调用的是evdev_open函数。
Linux input按键设备驱动_第9张图片
看一下evdev_open函数里面的操作。
①申请一个client结构体

client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);

②将client结构体添加到evdev->client_list的链表中。

evdev_attach_client(evdev, client);

③将client和file绑定

file->private_data = client;
11. 按键消息的读取

调用evdev_fops函数的read函数,最终是调用的evdev_read函数。

evdev_read函数中做了如下操作:
①判断按键消息buffer是否为空,若是空的,不阻塞的读取操作直接返回。

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);

③获取消息,并把消息拷贝给应用层

while (retval + input_event_size() <= count &&
       evdev_fetch_next_event(client, &event)) 
{

    if (input_event_to_user(buffer + retval, &event))
        return -EFAULT;

    retval += input_event_size();
}

④evdev_fetch_next_event获取按键消息,其实就是将client->buffer中的数据赋值给event

static int evdev_fetch_next_event(struct evdev_client *client,
				  struct input_event *event)
{
	int have_event;

	spin_lock_irq(&client->buffer_lock);

	have_event = client->head != client->tail;
	if (have_event) {
	   *event = client->buffer[client->tail++];
		client->tail &= EVDEV_BUFFER_SIZE - 1;
	}

	spin_unlock_irq(&client->buffer_lock);

	return have_event;
}

至此,已经把消息拷贝给了应用层。

你可能感兴趣的:(linux内核驱动)