Linux内核input子系统浅析

kernel version: linux-4.9.13


1. 概述

Linux内核input子系统分为三层:

  • 设备驱动层
  • 核心层
  • 事件处理层

设备驱动层包含各类输入设备驱动(如触摸屏、鼠标、键盘等等),获取输入事件并上报;
核心层根据输入设备种类,分发事件至不同的事件处理器;
事件处理层包含:通用事件处理器(evdev)、鼠标事件处理器(mousedev)、摇杆事件处理器(joydev),缓存事件并提供接口等待用户获取。

input framework

2. 数据结构

2.1 输入设备 —— input_dev

每个输入设备驱动中都实例化了一个input_dev对象,用于记录设备硬件相关信息、事件位图设置(支持的事件类型)以及其他参数。

struct input_dev {
	const char *name;   // 设备名称
	/*
	struct input_id {
    	__u16 bustype;  // 总线 
    	__u16 vendor;   // 厂商
    	__u16 product;  // 产品
    	__u16 version;  // 版本
    };    
	*/
	struct input_id id; // 设备id,用于与input_handler匹配

	......
	
	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)];   // 绝对位移事件位图
    ......
    
	struct device dev;
	
	struct list_head	h_list;     // 内核链表头
	struct list_head	node;       // 内核链表节点

    ......
};

2.2 事件处理器 —— input_handler

同样,每个事件处理器也都实例化了input_handler对象,如evdev_handler、mousedev_handler、joydev_handler等等,input_handler类中提供了事件处理、与输入设备匹配连接相关的接口。

struct input_handler {

	void *private;
    
    /* 事件处理接口 */
	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	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);

	bool legacy_minors;
	int minor;
	const char *name;
	
    /* 输入设备支持列表,用于与input_dev匹配 */
	const struct input_device_id *id_table;  

	struct list_head	h_list;     // 内核链表头
	struct list_head	node;       // 内核链表节点
};

3. 输入设备与事件处理器的匹配连接

input子系统维护了两条重要链表:

  • 输入设备链表 —— input_dev_list
  • 事件处理器链表 —— input_handler_list

两条链表在核心层input.c中静态声明并初始化:

static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);

input list
当一个输入设备注册时,它会被加入到输入设备链表(input_dev_list),同时遍历事件处理器链表(input_handler_list)进行匹配:

int input_register_device(struct input_dev *dev)
{
    ......
    
    // 将输入设备加入内核链表 
	list_add_tail(&dev->node, &input_dev_list);

    // 遍历事件处理器链接匹配
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

    ......
}

匹配过程以事件处理器的id_table为判断条件1,如果输入设备与事件处理器匹配成功,则调用事件处理器的connect()方法进一步处理,以通用事件处理器(evdev)为例,最终调用evdev_connect():

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
    ......
    
	int minor;
	int dev_no;
	
    // 获取次设备号
	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
	if (minor < 0) {
		error = minor;
		pr_err("failed to reserve new minor: %d\n", error);
		return error;
	}

    // 设置设备节点名字,根据注册顺序依次为event0、event1......
	dev_no = minor;
	/* Normalize device number if it falls into legacy range */
	if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
		dev_no -= EVDEV_MINOR_BASE;
	dev_set_name(&evdev->dev, "event%d", dev_no);

    // 以主、次设备号合成设备号
	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
	// 关联input设备类    
	evdev->dev.class = &input_class;
	// 继承input_dev父类
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

    // 字符设备初始化及注册
	cdev_init(&evdev->cdev, &evdev_fops);
	evdev->cdev.kobj.parent = &evdev->dev.kobj;
	error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);
	if (error)
		goto err_cleanup_evdev;

	return 0;
}

在上述代码中可以看到,输入设备调用input_register_device()函数注册时,如果与通用事件处理器(evdev)成功配对,最后就会生成字符设备节点(/dev/input/eventN),这就是用户空间获取内核输入事件的接口。

4. 输入事件的上报流程

输入事件的传递以input_event为基本单位:

struct input_event {
	struct timeval time;    // 时间戳
	__u16 type;             // 事件总类型
	__u16 code;             // 事件子类型
	__s32 value;            // 事件值      
};

input event
在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;核心层将事件数据(type、code、value)打包、分发至事件处理器;事件处理器给输入事件加上时间戳,最后传递至上层用户空间。

整个上报流程的关键点在于事件处理器与用户空间的交互,以通用事件处理器(evdev)为例,其与用户程序组成了一对多的C/S2通信模型:
evdev_cilent

通用事件处理器(evdev)代表服务器,每一个打开设备节点(/dev/input/eventN)的用户程序则代表一个连接的客户端。

在evdev.c中有着对应描述的结构体:

// 服务器
struct evdev {
	int open;
	struct input_handle handle;
	wait_queue_head_t wait;
	struct evdev_client __rcu *grab;
	struct list_head client_list;       // 客户端连接列表
	spinlock_t client_lock; 
	struct mutex mutex;
	struct device dev;
	struct cdev cdev;
	bool exist;
};

// 客户端
struct evdev_client {
	unsigned int head;
	unsigned int tail;
	unsigned int packet_head; 
	spinlock_t buffer_lock;
	struct wake_lock wake_lock;
	bool use_wake_lock;
	char name[28];
	struct fasync_struct *fasync;
	struct evdev *evdev;
	struct list_head node;
	int clkid;
	unsigned int bufsize;
	struct input_event buffer[];    // 输入事件环形缓冲区
};

设备驱动上报事件并不是直接传递给用户程序,在通用事件处理器(evdev)中,事件被缓存在缓冲区中,当缓冲区不为空时,用户程序主动读取缓冲区获取事件。
producer-consumer
这个缓冲区就是在evdev_client中定义的环形缓冲区,也就是说每个打开设备节点的用户程序,在建立客户端连接的同时,也构建了自己的缓冲区用以获取事件,不同的客户端之间互不干涉。

关于缓冲区的详细分析请参考:input子系统事件处理层(evdev)的环形缓冲区


  1. 通用事件处理器(evdev)的id_table设置为匹配所有输入设备 ↩︎

  2. Client/Server,客户端与服务器架构 ↩︎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值