Linux 输入子系统简介

内核版本:linux-3.10.61

代码分析只截取了部分重要的内容,如需了解更多细节请亲自阅读内核源码!

一、输入子系统概述

输入子系统是Linux内核为输入设备 如:鼠标,键盘,触摸屏等提供的一种驱动框架。输入子系统在逻辑上可以分成三层。核心层(input.c),Handler层(evdev.c、joydev.c等),Device层(由驱动工程师实现)。handler在注册时会被添加到handler链表,并且去逐个匹配device链表下的device。device在注册时也会被添加到device链表,也会逐个匹配handler链表中的handler。

下面是每一层的主要功能阐述:

核心层:在/proc/bus下创建文件夹input,并在input下创建文件devices、handlers用于描述input中匹配上的device和handler。申请1024个设备号,主设备号为13,次设备号0~1023。

static int __init input_init(void)
{
	int err;

	err = class_register(&input_class);    /* 注册一个类 */

	err = input_proc_init();    /* 在proc下创建文件 */

	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
				     INPUT_MAX_CHAR_DEVICES, "input");    /* ,申请设备号,注册字符设备 */

}

Handler层:注册input_handler,为device实现file_oprations结构体。

Device层:申请并初始化input_dev结构体,注册input_dev,硬件初始化硬件资源,上报输入事件。

二、设备探测

了解device和handler之间的探测过程有助于我们了解input子系统的具体实现。和之前的Platform分析一样,从input_dev和input_handler的注册函数来进行分析。

代码位于 /drivers/input/input.c

1、input_register_device()分析

分析注册函数之前先来看一下struct input_dev有那些成员(列举部分重要成员)。

struct input_dev {
	const char *name;    /* /proc/bus/inout/devices 的Name */
	const char *phys;    /* /proc/bus/inout/devices 的Phys*/
	const char *uniq;    /* /proc/bus/inout/devices 的Uniq*/
	struct input_id id;  /* /proc/bus/inout/devices 的I列取值,这个设备信息在match handler时也会用到*/

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    /* 设备的事件类型,键盘[key],鼠标[rel],触摸屏[abs] */
	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)];
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

    //...
	struct device dev;

	struct list_head	h_list;
	struct list_head	node;

};

struct input_id {
	__u16 bustype;
	__u16 vendor;
	__u16 product;
	__u16 version;
};

看完input_dev,来看一下input_register_device()在注册过程中做了那些操作

int input_register_device(struct input_dev *dev){

    __set_bit(EV_SYN, dev->evbit);    /* 设置同步事件,在每次上报完数据后都要上报一个同步事件,使用方法在后面的例子中说明 */
   list_add_tail(&dev->node, &input_dev_list);    /* 把input_dev 挂接到链表中 */

	list_for_each_entry(handler, &input_handler_list, node)    /* 把链表中的handler都取出来 */
		input_attach_handler(dev, handler);    /* 取出的handler,用于和input_dev进行匹配 */
}

2、input_register_handle()分析

同样的先来分析一下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);
    
    /* 在input_handler和input_dev匹配时调用 */
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
    /* 在device和handler匹配成功后调用connect函数 */
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);    
	void (*disconnect)(struct input_handle *handle);

	int minor;    /* 匹配上这个handler后分配minor的base */
	const char *name;    /* handler 名字,会写入到proc中 */

	const struct input_device_id *id_table;    /* 用于匹配device */

	struct list_head	h_list;
	struct list_head	node;
};

简单分析一下input_register_handler(),会发现和input_dev注册高度相似

int input_register_handler(struct input_handler *handler){
	list_add_tail(&handler->node, &input_handler_list);    /* 把handler 加入链表中 */

	list_for_each_entry(dev, &input_dev_list, node)    /* 把链表中的每一个device都取出来 */
		input_attach_handler(dev, handler);    /* 把取出的device和handler匹配 */
}

 3.匹配函数 input_attach_handler()

接下来就是匹配的详细细节了

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler){
	/*
     * 根据 handler->id_table和input_dev进行匹配,
     * 如果定义了handler->match函数,在里面也会调用其进行匹配操作
     */
    id = input_match_device(handler, dev);
	if (!id)
		return -ENODEV;
    
    /* 匹配成功后调用该handler的connect进行连接
     * 这边假设匹配上的handler为evdev,
     */
	error = handler->connect(handler, dev, id);

}

/* 分析evdev的connect函数 */
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id){

    /* 获取次设备号minor */
	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);

    /* 初始化handle */
	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;
    /* handle和input_dev、input_handler挂接 */
	error = input_register_handle(&evdev->handle);
	if (error)
		goto err_free_evdev;
    
    /* 绑定file_oprations */
	cdev_init(&evdev->cdev, &evdev_fops);
	evdev->cdev.kobj.parent = &evdev->dev.kobj;
	error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
}

三、input子系统使用实例

以内核中触摸屏驱动s3c2410_ts.c为例。s3c2410_ts使用Platform平台设备驱动模型和输入子系统共同完成。

来看看platform的probe函数做了什么

static int s3c2410ts_probe(struct platform_device *pdev){
    /* remap寄存器物理地址 */
    ts.io = ioremap(res->start, resource_size(res));

    /* 申请一个input_dev结构体 */
    input_dev = input_allocate_device();
    
    /* 初始化input_dev结构体 */
	ts.input = input_dev;
	ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);    // 设置触摸屏会产生什么事件
	ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); // 设置事件会产生什么值
	input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);       // 设置事件会产生什么值
	input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);       // 设置事件会产生什么值  

    /* 初始化name和id表,其内容会显示在proc中*/
	ts.input->name = "S3C24XX TouchScreen";
	ts.input->id.bustype = BUS_HOST;
	ts.input->id.vendor = 0xDEAD;
	ts.input->id.product = 0xBEEF;
	ts.input->id.version = 0x0102;

    /* 申请触摸类事件中断 */
	ret = request_irq(ts.irq_tc, stylus_irq, 0,
			  "s3c2410_ts_pen", ts.input);

    /* 注册input_dev */
	ret = input_register_device(ts.input);
}

触摸屏幕后触发中断,在中断处理函数读寄存器获取数据后调用input_event()上报事件。input_event()最终会调用handler->evdev_events或handler->evdev_event,然后events(event)会给应用层发送一个信号kill_fasync(&client->fasync, SIGIO, POLL_IN);通知应用层有数据到来。

static void touch_timer_fire(unsigned long data)
{

	if (down) {
		if (ts.count == (1 << ts.shift)) {
            /* 上报x坐标,此上报函数实际时调用input_event()进行上报 */
			input_report_abs(ts.input, ABS_X, ts.xp);
            /* 上报y坐标 */
			input_report_abs(ts.input, ABS_Y, ts.yp);
            /* 上报触摸类事件 */
			input_report_key(ts.input, BTN_TOUCH, 1);

            /*
             *上报同步事件,意味着此次上报数据结束 
             * 这个同步事件在input_dev时设置
             */
			input_sync(ts.input);
		}

	} else {
		input_report_key(ts.input, BTN_TOUCH, 0);
		input_sync(ts.input);
	}
}

 做个总结就是,实现一个输入设备的驱动程序至少要完成一下几点:

a、声请一个input_dev结构体。

b、初始化input_dev结构体。

c、初始化硬件,注册中断。

d、注册input_dev结构体。

e、上报事件。

恭喜获得知识点+1

若有讲解不妥之处烦请在评论区指正!

如果有收获那就一键三连鼓励一下吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值