韦东山2440-学习笔记-输入子系统
1. 框架分析
1.1 connect
input_init
register_chrdev(INPUT_MAJOR, "input", &input_fops);
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
input_open_file
struct input_handler *handler = input_table[iminor(inode) >> 5]; // 从 input_table 中根据次设备号获得 handler
new_fops = fops_get(handler->fops); // 使用handler->fops替换file->f_op,并调用 open
file->f_op = new_fops;
new_fops->open(inode, file); // 调用下层驱动的open
input_table 被谁设置?
input_register_handler(struct input_handler *handler) // 被其他模块调用
input_table[handler->minor >> 5] = handler; // 其他模块定义的 handler将记录到 input_table
list_add_tail(&handler->node, &input_handler_list); // 其他模块的handler会加入 input_handler_list链表
list_for_each_entry(dev, &input_dev_list, node) // 遍历dev链表,进行dev和 handler的匹配
input_attach_handler(dev, handler);
以 input_dev_list 被谁添加?
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);
所以当handler和dev各自调用 input_register后,会调用input_attach_handler
input_attach_handler(struct input_dev *dev, struct input_handler *handler)
input_match_device(handler->id_table, dev); // 判断handler和dev是否匹配
for (; id->flags || id->driver_info; id++) { // 匹配方法是检查 handler是否支持 dev 的输入类型
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;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
return id;
}
handler->connect(handler, dev, id); // 如果匹配则调用handler->connect
查看实际handler的实际示例,查看connect函数
发现有很多模块都调用了input_register_handler,说明他们都提供handler,作为软件层
drivers/char/keyboard.c 无 fops
drivers/input/evbug.c 无 fops
drivers/input/evdev.c 有 fops 分析
...
以 evdev.c为例
evdev_init
input_register_handler(&evdev_handler);
evdev_handler定义
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect, // 匹配时调用
.disconnect = evdev_disconnect,
.fops = &evdev_fops, // input被open时,替换成 evdev_fops,并调用 evdev_fops.open
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids, // 决定是否匹配哪些dev
};
可见evdev匹配所有input dev
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); // 分配次设备号,主设备号为 input子系统提供
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 创建edev,edev包含 struct input_handle
evdev->handle.dev = dev; // input_handle 关联 dev 和 handler
evdev->handle.handler = handler;
evdev->handle.private = evdev;
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name); // 创建设备节点
input_register_handle(&evdev->handle);
list_add_tail(&handle->d_node, &handle->dev->h_list); // handle加入 dev->h_list 和 handler->h_list
list_add_tail(&handle->h_node, &handler->h_list);
handler->start(handle);
所以connect后,handler ,dev,handle可以通过任意一个,找到其他,并且dev->open > 0
他们的关系
handler->hlist --> handle
dev->hlist --> handle
handle->dev = dev
handle->handler = handler
1.2 open
由于创建设备节点使用的主设备号为 INPUT_MAJOR,所以应用程序open时调用的 f_op->open 为 input_open_file.
input_open_file
struct input_handler *handler = input_table[iminor(inode) >> 5]; // 获得 edev的handler
new_fops = fops_get(handler->fops); // 使用handler->fops替换file->f_op,并调用 open
file->f_op = new_fops;
new_fops->open(inode, file); // 调用edev的open
edev的file_operations实现了大量的IO操作,可见使用输入子系统可以减少大量开发量
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
};
evdev_open
evdev = evdev_table[i];
client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
client->evdev = evdev;
input_open_device(&evdev->handle);
dev->open(dev);
file->private_data = client;
evdev_open只是把struct file 进行派生,并给 dev open时初始化的机会
1.3 read
evdev_read
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) // 环形缓存为空,且用户使用非阻塞方式打开,则返回EAGAIN
return -EAGAIN;
wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); // 如果环形缓存为空,则在 evdev->wait 上睡眠
// 有数据被唤醒
evdev_event_to_user(buffer + retval, event); // 复制数据给用户空间
1.4 event
当进程睡眠到evdev->wait时,谁来唤醒
evdev_event
wake_up_interruptible(&evdev->wait); // 唤醒等待队列上的进程
那么谁调用 evdev_event
static struct input_handler evdev_handler = {
.event = evdev_event,
...
};
搜索后发现 input_event 中会调用handle->handler->event()
谁调用input_event,发现通常是在数据dev部分检查到数据到来,如dev的中断处理中
atkbd_interrupt
input_event
1.5 总结
输入子系统分为三部分:
dev和 handler首先 调用 input_register_xx 将自己注册到 input的两个链表,其中handler还需要将自己注册到input_table
如果发生匹配,则会调用 handler->connect,通常connect需要创建设备节点。并创建handle结构,让handler,dev关联
当用户open时,先调用 input->f_op->open,先通过此设备号从input_table中找到handler,再让 file->f_op 改变为 handler->f_op,并调用 new_fop->open()
当用户read时,由于fop已经改变,所以会调用handler的fop->read,通常会因没有数据而阻塞,
当dev数据到来,触发中断,dev中断处理中调用 input_event,以调用 handler->event,handler->event通常会唤醒等待进程。
2. 函数和细节
int input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
其中,各参数的含义如下:
dev: 指向 input_dev 结构体的指针,表示要发送事件的输入设备;
type: 事件类型,可以是 EV_SYN(同步事件),EV_KEY(按键事件),EV_REL(相对移动事件)等;
code: 事件代码,表示具体的按键或鼠标事件,例如 BTN_LEFT(左键按下),ABS_X(X 轴相对移动)等;
value: 事件的值,表示事件发生的状态或数值,例如 0(表示按键松开),1(表示按键按下),或者鼠标的相对移动量。
调用 input_event 函数后,输入子系统会将事件传递到相应的输入设备,并触发相应的事件处理程序。
int input_sync(struct input_dev *dev);
在 Linux 输入子系统中,input_sync 函数用于发送同步事件到输入设备。同步事件是一种特殊的事件类型,它用于告知输入设备当前事件序列的结束。输入设备在接收到同步事件后,会将之前的事件序列视为一个完整的事件,然后交给事件处理程序处理。
其中,dev 参数表示要发送同步事件的输入设备。调用 input_sync 函数后,输入子系统会向输入设备发送一个 EV_SYN 类型、SYN_REPORT 代码的同步事件,以表示事件序列的结束。通常,每个事件序列都应该以一个同步事件结尾,以确保输入设备能够正确地处理事件序列。
4. input_event 类型介绍
#define NBITS(x) (((x)/BITS_PER_LONG)+1)
#define BIT(x) (1UL<<((x)%BITS_PER_LONG))
#define LONG(x) ((x)/BITS_PER_LONG)
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
// 描述此输入设备类型
unsigned long evbit[NBITS(EV_MAX)]; // 类型 :EV_SYN(虚拟同步事件,用于将 输入事件组合序列), EV_KEY , EV_REL, EV_ABS, EV_MSC(杂项事件非标准事件,用于表示不属于 EV_KEY,EV_REL,EV_ABS的事件) ...
// 当 evbit 确定后,下面使用一项描述此设备支持的 code
unsigned long keybit[NBITS(KEY_MAX)]; // 若是键盘设备,支持哪些 code
unsigned long relbit[NBITS(REL_MAX)]; // 若是相对位移鼠标,支持哪些code
unsigned long absbit[NBITS(ABS_MAX)]; // 绝对位移鼠标
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)];
分析下 unsigned long evbit[NBITS(EV_MAX)]; 的位操作
#define NBITS(x) (((x)/BITS_PER_LONG)+1)
#define BIT(x) (1UL<<((x)%BITS_PER_LONG))
#define LONG(x) ((x)/BITS_PER_LONG)
unsigned long evbit[NBITS(EV_MAX)]; // 定义一段内存,内存大小支持 EV_MAX个bit 的位操作
// 设置位
static __inline__ void set_bit(int nr, volatile unsigned long *addr)
{
int *a = (int *)addr;
int mask;
unsigned long flags;
a += nr >> 5; // a是int *类型,所以以32位移动, nr >> 5 也就是 nr / 32,得到a应该移动的单位
mask = 1 << (nr & 0x1f); // nr & 0x1f 就是 nr % 32
local_irq_save(flags);
*a |= mask;
local_irq_restore(flags);
}
3. 实现按键设备
1.分配并设置input_device,注册
2.当有可读时用 input_event 发送给 handler
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/input.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm-arm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static void button_timeout(unsigned long);
static struct input_dev *button_dev;
static struct timer_list button_timer = TIMER_INITIALIZER(button_timeout, 0, 0);
struct button_desc {
unsigned int pin;
unsigned int val;
unsigned int irq;
const char *name;
unsigned int code;
};
static struct button_desc button_desc_arr[3] = {
{ .pin = S3C2410_GPF0, .val = 0x01, .irq = IRQ_EINT0 , .name = "s1", .code = KEY_L},
{ .pin = S3C2410_GPF2, .val = 0x02, .irq = IRQ_EINT2 , .name = "s2", .code = KEY_S},
{ .pin = S3C2410_GPG3, .val = 0x03, .irq = IRQ_EINT11 , .name = "s3", .code = KEY_ENTER},
};
static void
button_timeout(unsigned long data)
{
struct button_desc *bd = (struct button_desc *)data;
int up;
del_timer(&button_timer);
button_timer.data = 0;
up = s3c2410_gpio_getpin(bd->pin);
if (up) {
input_event(button_dev, EV_KEY, bd->code, 0);
input_sync(button_dev);
}
else {
input_event(button_dev, EV_KEY, bd->code, 1);
input_sync(button_dev);
}
}
static irqreturn_t
buttons_irq_handler(int irq, void *dev_id)
{
if (button_timer.data != 0)
return IRQ_HANDLED;
button_timer.data = (unsigned int)dev_id;
mod_timer(&button_timer, jiffies + msecs_to_jiffies(10));
return IRQ_HANDLED;
}
static int __init
button_dev_init(void)
{
int i, ret;
/* 注册input_dev */
button_dev = input_allocate_device();
/* 设置此设备能产生的事件 */
set_bit(EV_KEY, button_dev->evbit); // 产生按键事件
set_bit(EV_REP, button_dev->evbit); // 产生重复事件
set_bit(KEY_L, button_dev->keybit); // 键入 'l'
set_bit(KEY_S, button_dev->keybit); // 键入 's'
set_bit(KEY_ENTER, button_dev->keybit); // 键入 'enter'
button_dev->name = "buttons";
input_register_device(button_dev);
/* 硬件操作 */
/* 设置gpio为中断模式 */
s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);
s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_EINT0);
s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_EINT11);
/* 注册中断, IRQT_BOTHEDGE : 双边缘触发 */
for (i = 0; i < sizeof(button_desc_arr)/sizeof(*button_desc_arr); i++) {
ret = request_irq(button_desc_arr[i].irq, buttons_irq_handler,
IRQT_BOTHEDGE, button_desc_arr[i].name, button_desc_arr + i);
}
init_timer(&button_timer);
return 0;
}
static void __exit
button_dev_exit(void)
{
int i;
// 删除设备节点
input_unregister_device(button_dev);
// 释放input_device 内存
input_free_device(button_dev);
// 释放中断号
for (i = 0; i < sizeof(button_desc_arr)/sizeof(*button_desc_arr); i++) {
free_irq(button_desc_arr[i].irq, button_desc_arr + i);
}
// 删除定时器
del_timer(&button_timer);
}
module_init(button_dev_init);
module_exit(button_dev_exit);
MODULE_LICENSE("GPL");