内核驱动 (一)Linux 输入子系统

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JerryGou/article/details/82317597

一、Linux 输入子系统

1. 事件层:负责与用户程序打交道,将核心层传来的事件报告给用户程序。
2. 核心层:是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
3. 设备驱动层:负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,键盘、鼠标、触摸屏等字符设备驱动功能的实现工作主要在这层。

二、数据结构及操作

2.1 input_dev结构

          input_dev结构体用来描述一个input设备,位于设备驱动层,声明一个设备能产生哪类以及该类事件中的哪些事件


struct input_dev {  
	const char *name;    设备名,将在sys/class/input/XXX/name 中保存
	const char *phys;    设备系统层的物理路径, 在sys/class/input/XXX/phys 中保存  
	const char *uniq;    
	struct input_id id;   输入设备id 总线类型;厂商编号,产品id,产品版本与input_hander匹配时会用到  
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     事件类型标志位  
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   按键事件支持的子事件,按键类型  
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   相对位移事件标志位  
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   绝对位移事件标志位  
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   杂项事件标志位  
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   led指示灯标志位  
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   声音事件  
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];   强制反馈事件  
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];   开关事件标志位  

	unsigned int hint_events_per_packet;  
	unsigned int keycodemax;    键盘码表大小  
	unsigned int keycodesize;   键盘码大小  
	void *keycode;              键盘码表指针  

	int (*setkeycode)(struct input_dev *dev,unsigned int scancode, unsigned int keycode);   设置键盘码  
	int (*getkeycode)(struct input_dev *dev,unsigned int scancode, unsigned int *keycode);   获取键盘码  
	int (*setkeycode_new)(struct input_dev *dev,const struct input_keymap_entry *ke,unsigned int *old_keycode);   
	int (*getkeycode_new)(struct input_dev *dev,struct input_keymap_entry *ke);  

	struct ff_device *ff;    强制反馈设备  
	unsigned int repeat_key;     重复按键标志位  
	struct timer_list timer;     定时器  
	int rep[REP_CNT];        重复次数  
	struct input_mt_slot *mt;  
	int mtsize;  
	int slot;  
	struct input_absinfo *absinfo;  
	unsigned long key[BITS_TO_LONGS(KEY_CNT)];    
	unsigned long led[BITS_TO_LONGS(LED_CNT)];    
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];     
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];     

	int (*open)(struct input_dev *dev);      open方法  
	void (*close)(struct input_dev *dev);    close方法  
	int (*flush)(struct input_dev *dev, struct file *file);  
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  

	struct input_handle __rcu *grab;  
	spinlock_t event_lock;  
	struct mutex mutex;  
	unsigned int users;  
	bool going_away;  
	bool sync;  
	struct device dev;      设备文件  
	struct list_head    h_list;   input_handler处理器链表头  
	struct list_head    node;     input_device设备链表头  
}; 

2.2 input_handler结构

input_handler结构体用来描述一个事件处理器,结构体中的input_device_id *id_table成员用来和input_dev设备匹配,看是否支持该输入设备。

struct input_handler {  
	void *private;   私有数据  
	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);    事件处理  
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);    过滤器  
	bool (*match)(struct input_handler *handler, struct input_dev *dev);   设备匹配  
	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;    输入设备 id表  
	struct list_head    h_list;    input_handler处理器链表头  
	struct list_head    node;      input_device设备链表头  
};
struct input_device_id {
	kernel_ulong_t flags;
	__u16 bustype;
	__u16 vendor;
	__u16 product;
	__u16 version;
	kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
	kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
	kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
	kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
	......
	kernel_ulong_t driver_info;
};

下面我们来跟踪一下内核,看看一个输入设备是如何被注册到系统中工作的。

三、输入设备 & 注册

3.1 申请一个输入设备

struct input_dev *input_allocate_device(void) @input.c
{
	struct input_dev *dev;
	dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
	device_initialize(&dev->dev);  初始化内嵌的device结构体
	INIT_LIST_HEAD(&dev->h_list);
	INIT_LIST_HEAD(&dev->node);
	return dev;
}

3.2 注册一个输入设备

int input_register_device(struct input_dev *dev) @input.c
{
	struct input_handler *handler;
	error = device_add(&dev->dev);
	list_add_tail(&dev->node, &input_dev_list);   加入input_dev_list链表
	 对于每一个input_handler,都调用input_attach_handler
	list_for_each_entry(handler, &input_handler_list, node)
	input_attach_handler(dev, handler);
}

当向系统注册一个输入设备时,系统会将该设备加入input_dev_list链表中,并从input_handler_list链表中取出每一个handler,调用input_attach_handler看看是否支持该设备,要知道当一个输入设备向系统注册时,可能会有好几个handler同时支持它。假设此时的input_handler是evdev_handler(@evdev.c)。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) @input.c
{
	const struct input_device_id *id;
	id = input_match_device(handler->id_table, dev);
	error = handler->connect(handler, dev, id);
}

evdev.c中input_device_id的设置:

static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 }, /* Matches all devices */
	{ }, /* Terminating zero entry */
};

static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
{
	const struct input_device_id *id;

	for (id = handler->id_table; id->flags || id->driver_info; id++) {

		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;
		if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
			continue;
		if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
			continue;
		if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
			continue;
		if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
			continue;
		if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
			continue;
		if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
			continue;
		if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
			continue;
		if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
			continue;
		if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
			continue;
		if (!handler->match || handler->match(handler, dev))
			return id;
	}

	return NULL;
}

到此这个handler就可以支持该输入设备,之后将会调用它的connect方法,对于evdev.c来说就是evdev_handler.connect,至于驱动层的事件如何通过核心层和事件层到达用户空间,在后续的evdev.c文件分析的时候会看到。

其实对于编写一个底层的输入设备驱动,并不需要关心这么多,因为Linux系统为我们做了很好的分层,我们只要专注核心层给我们提供的接口,使用这些接口向上注册驱动就可以了,因此编写一个输入设备驱动就变得很简单,下面是基本的框架。
1. 分配input_dev结构体:input_allocate_device()
2. 设置可以支持哪类事件,支持该类事件的那些事件
3. 注册输入设备:input_register_device()
4. 向系统报告事件:input_event()

四、事件处理层evdev.c文件分析

事件处理层文件主要是用来支持输入设备并与用户空间交互,这部分代码一般不需要我们自己去编写,因为Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如evdev.c、mousedev.c、joydev.c等。一个事件处理器用struct input_handler结构体来表示,在evdev.c文件中的定义如下。

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

int input_register_handler(struct input_handler *handler)    @input.c
{
    struct input_dev *dev;
    INIT_LIST_HEAD(&handler->h_list);

    if (handler->fops != NULL) {
        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_list链表中,并从input_dev_list链表中取出每一个dev,调用input_attach_handler看看是否支持该设备。上面蓝色粗体语句表明,当注册一个handler时,它会去设置input_table数组的某一项,input_table的定义在input.c中(static struct input_handler *input_table[8]; ),从这个数组中我们也可以看出该系统最多支持8个事件处理器。
         对于输入子系统无论是注册设备input_register_device还是注册事件处理器input_register_handler最终都会调用到input_attach_handler进行匹配,当正确匹配的时候就会调用handler的connect方法,接下来我们就分析evdev.c中evdev_connect到底做了什么。

static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)    @evdev.c
{
    struct evdev *evdev;
    int minor;
    for (minor = 0; minor < EVDEV_MINORS; minor++)
        if (!evdev_table[minor])
            break;
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    INIT_LIST_HEAD(&evdev->client_list);
    dev_set_name(&evdev->dev, "event%d", minor);
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.handler = handler;
    evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
    evdev->dev.class = &input_class;
    device_initialize(&evdev->dev);
    error = input_register_handle(&evdev->handle);
    error = evdev_install_chrdev(evdev);
    error = device_add(&evdev->dev);
}

申请一个struct evdev结构体,对于evdev_handler事件处理器,它把所有自己可以支持的输入设备都定义为一个struct evdev设备,并且可以支持最多32该设备。在struct evdev中有一个很重要的成员struct input_handle handle,它是连接dev和handler的桥梁,下面给出它的定义:

struct input_handle{
	void *private;
	int open;
	const char *name;

	struct input_dev   *dev;    指向input_dev结构体
	struct input_handler   *handler;    指向input_handler结构体

	struct list_head   d_node;   通过d_node连接到input_dev上的h_list链表上
	struct list_head   h_node;   通过h_node连接到input_handler上的h_list链表上
}

在evdev_connect()中,handle的两个成员指针分别指向匹配的dev和handler,最后调用input_register_handle()。

int input_register_handle(struct input_handle *handle)    @input.c
{
    struct input_handler *handler = handle->handler;
    struct input_dev *dev = handle->dev;
    list_add_tail_rcu(&handle->d_node, &dev->h_list);
    list_add_tail(&handle->h_node, &handler->h_list);
}

认真观察input_dev、input_handler和input_handle三个结构体,不难看出,这三个结构体都维护了两个链表。对于input_dev是名为h_list和node的成员,一个系统中可能会有很多输入设备,这些输入设备就是通过node这个成员连成一个双向链表,其中链表头指向input_dev_list另一个成员h_list维护了一个handle的链表,一个输入设备可能同时会被几个事件处理器支持,而每一次匹配过程都会申请一个input_handle结构体,将输入设备dev和事件处理器handler关联在一起,对于这些handle有一个共同点是它的成员指针handle->dev都指向该输入设备,并且这些input_handle结构体中的成员d_node连接成一个链表并且链表头指向该输入设备的h_list。对于input_handler也一样,有兴趣的可以自己分析,下面给出一个简单的匹配图。

在上面的evdev_connect()函数分析中我们略去了下面几条语句。

    evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
    evdev->dev.class = &input_class;
    device_initialize(&evdev->dev);
    error = device_add(&evdev->dev);

这些语句是用来创建设备节点,关于Linux下如何自动创建设备节点的详细资料可以参考博文:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/JerryGou/article/details/82317597
今日推荐