内核版本: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
若有讲解不妥之处烦请在评论区指正!
如果有收获那就一键三连鼓励一下吧!