参考:https://blog.csdn.net/lizuobin2/article/details/51508839
目录
什么是输入设备
框架:
输入子系统的工作流程(以按键为例):
1、开机,执行input.c input_init函数
2、应用程序打开输入设备input_open_file,通过input_table找到事件处理器
3、应用程序调用read函数 也就是等同于调用handler->fops-read
4、有按键按下,该做的事情(上报事件):
4、事件上报过程,唤醒进程
5、事件处理层处理相应的事件,执行相应的read,write.....接口函数
输入子系统实现原理:
1、问题:
1、执行input_open_file input_table由谁来构造?
2、read函数是怎么找到的?是在哪里定义的?
2、 事件处理层handler 以evdev_handler事件处理器为例调用 input_register_handler
3、设备层驱动层dev, 真正需要的工作驱动编写步骤
input_register_device解析
1. 分配一个input_dev结构体input_allocate_device()
2. 设置事件类型和按键代码
3. 注册 调用input_register_device
4. 硬件相关的代码,比如在中断服务程序里上报事件
4、设备层 dev 和 事件处理层 handler 匹配建立连接
主要关注两个层
设备层:就是我们需要自己编写的,包括输入设备的注册,事件的设置,事件的上报
处理层handler : 这一般是系统定义的不需要自己编写,例如 evdev_handler,里面接受来自设备层的事件,定义file_operations结构体,提供read write..............接口函数
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file, //input_open_file其实不是正真的open 在它里面会调用正真的open
};
static int __init input_init(void)
{
int err;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //主设备号为13
}
//简化版
static int input_open_file(struct inode *inode, struct file *file)
{
// 根据次设备号,从 input_table 数组中取出对应的 handler
struct input_handler *handler = input_table[iminor(inode) >> 5];
err = file->f_op->open(inode, file);
}
一般是以阻塞方式读,没有读到值则休眠进程
evdev_read
// 无数据并且是非阻塞方式打开,则立刻返回
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);
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
/**
* input_event() - 函数功能:上报事件
* @dev: 输入子系统设备
* @type: 事件类型
* @code: event code 键的编号比如 #define KEY_ENTER 28 enter键是28号
* @value: value of the event 键值 比如,比如按下是读出的键值为0 松开为1
*
*/
static struct input_dev *buttons_dev; //定义一个输入设备
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
list_for_each_entry(handle, &dev->h_list, d_node) //通过之前匹配好的链表找到handle
if (handle->open)
handle->handler->event(handle, type, code, value); //通过handle找到handler->event
}
//handler->event 函数做了哪些事?
//evdev_handler 为例
// evdev_event函数 也就是 handler->event
{
wake_up_interruptible(&evdev->wait); //唤醒刚才休眠的进程
}
input_register_handler 解析:
注册input_handler:
input_register_handler
// 放入数组
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的id_table判断能否支持这个input_dev
在input_open_file 中找的
在evdev_handler 构造的
static int input_open_file(struct inode *inode, struct file *file)
{
.....................
const struct file_operations *old_fops, *new_fops = NULL;
// 将 handler 的fops赋值给file->f_op,并用调用新的open函数
old_fops = file->f_op;
//以后用户调用read函数就是调用 file->f_op->read 也就是等于调用handler->fops-read
file->f_op = fops_get(handler->fops);
...........................
}
static struct input_handler evdev_handler = {
.fops = &evdev_fops, //文件操作
};
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read, //读
.write = evdev_write, //写
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush
};
这个一般是系统自带的,不需要我们编写,了解原理即可
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);
}
注册输入设备:
input_register_device
// 放入链表
list_add_tail(&dev->node, &input_dev_list);
// 对于每一个input_handler,都调用input_attach_handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; // 表示能产生哪类事件
unsigned long keybit[NBITS(KEY_MAX)]; // 表示能产生哪些按键
unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮
unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
事件类型:
Linux中输入设备的事件类型有
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件,如KEY_VOLUMEDOWN
EV_REL 0x02 相对坐标, 如shubiao上报的坐标
EV_ABS 0x03 绝对坐标,如触摸屏上报的坐标
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈
~~~~~~~~~~~~~~~~~~~~~~~~
EV_PWR 电源
EV_FF_STATUS 状态按键编号:
KEY_L : 代表L按键
KEY_S 代表S按键
KEY_ENTER 代表enter按键
...........................等等
static struct input_dev *buttons_dev; //定义输入设备
static int buttons_init(void)
{
..............................
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注册 */
input_register_device(buttons_dev);
.......................
return 0;
}
//上报事件
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
先匹配,看两者是否匹配
在建立连接,其实就是挂到一个链表上去
input_attach_handler
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id);
注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,
根据input_handler的id_table判断这个input_handler能否支持这个input_dev,
如果能支持,则调用input_handler的connect函数建立"连接"
怎么建立连接?
1. 分配一个input_handle结构体
2.
input_handle.dev = input_dev; // 指向左边的input_dev
input_handle.handler = input_handler; // 指向右边的input_handler
3. 注册:
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
evdev_connect
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
// 设置
evdev->handle.dev = dev; // 指向左边的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右边的input_handler
evdev->handle.private = evdev;
// 注册
error = input_register_handle(&evdev->handle);