文章目录
之前写的代码只适合内部使用(自己用或者公司内部用),不能通用,也许通过scanf就要去打开某个设备。
想要用户代码无需任何修改就能使用之前的驱动, 就要使用输入子系统。
输入子系统框架
input.c 是核心层
// 流程
drivers/input/input.c
err = 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];
new_fops = fops_get(handler->fops)
file->f_op = new_fops; // 将新的fops赋值给file的f_op
err = new_fops->open(inode, file);
// input_table由谁构造
input_table
int input_register_handler(struct input_handler *handler) // 构造了input_table数组的项
// 在keyboard.c evdev.c joydev.c mousedev.c tsdev.c中调用了input_register_handler,向上注册input_register_handler
主要代码在drivers/input/input.c
里,已经写好字符设备的注册等代码register_chrdev(INPUT_MAJOR, "input", &input_fops);
,但fops
只有.open
,
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
static int __init input_init(void)
{
int err;
err = class_register(&input_class);
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
查看这个.open函数,根据inode的次设备号获得新的fops,赋值给file的f_op,
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
int err;
/* No load-on-demand here? */
if (!handler || !(new_fops = fops_get(handler->fops))) // 获得新的new_fops
return -ENODEV;
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops; // 将新的fops赋值给file的f_op
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
其中input_table[iminor(inode) >> 5];
这个数组的项由input_register_handler
构造,input_register_handler
被多个调用(keyboard.c evdev.c joydev.c mousedev.c tsdev.c
),
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
比如在evdev.c的入口函数中,就调用了input_register_handler
,
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
input_register_handler
,函数的参数是input_handler
类型,input_register_handler
结构体包含了fops
结构,fops
就有了open read write等,
// input_handler结构体
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
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);
const struct file_operations *fops; // 包含了fops结果
int minor;
const char *name;
// 参数evdev_handler的初始化,
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect, // 支持这个设备就调用connect,与硬件建立连接
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE, // 64
.name = "evdev",
.id_table = evdev_ids, // 表示这个handler能够支持哪些输入设备
};
// file_operations结构体
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
注册输入设备 input_register_device (对称)
// 流程
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_handler都调用input_attach_handler
input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
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);
input_wakeup_procfs_readers();
return 0;
}
注册handler input_register_handler (对称)
// 流程
int input_register_handler(struct input_handler *handler) // 注册handler
list_add_tail(&handler->node, &input_handler_list); // 放入链表
list_for_each_entry(dev, &input_dev_list, node) // 对于每个input_dev调用input_attach_handler
input_attach_handler(dev, handler); // 根据handler的id_table判断能否支持这个input_dev
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
无论先注册input_register_handler
还是input_register_device
,最终都调用input_attach_handler(dev, handler);
。
input_attach_handler(dev, handler)
// 流程
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
id = input_match_device(handler->id_table, dev); // 根据handler的id_table和dev比较
error = handler->connect(handler, dev, id); // 如果支持则handler->connect
注册input_dev
或 input_handler
时,会两两比较左边的input_dev
和右边的input_handler
,根据input_handler
的id_table
判断这个input_handler
能否支持这个input_dev
,如果能支持,则调用input_handler
的connect
函数建立“连接”。
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->cdev.kobj), error);
return error;
}
以evdev.c为例 怎么建立连接,通过struct input_handle 建立连接
-
分配一个input_handle结构体
-
设置
evdev->handle.dev = dev; // 指向左边的input_dev evdev->handle.handler = handler; // 指向右边的handler
-
注册
list_add_tail(&handle->d_node, &handle->dev->h_list); // 将handle加入到dev->h_list list_add_tail(&handle->h_node, &handler->h_list); // 将handle加入到handler->h_list
流程:
int evdev_connect(struct input_handler, struct input_dev,const struct input_device_id)
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个evdev,evdev包含input_handle
// 设置
evdev->exist = 1;
evdev->minor = minor;
evdev->handle.dev = dev; // 指向左边的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右边的handler
evdev->handle.private = evdev;
// 注册
error = input_register_handle(&evdev->handle);
list_add_tail(&handle->d_node, &handle->dev->h_list); // 将handle加入到dev->h_list
list_add_tail(&handle->h_node, &handler->h_list); // 将handle加入到handler->h_list
可以从输入设备的h_list
找到input_handle
找到input_handler
;
同样从input_handler
的h_list
找到input_handle
找到输入设备。
读取按键的数据 (可能休眠)
用户在read
时候 会调用到具体的某个handler
的fops
的read
,以 evdev_read
为例,
// 读取数据的操作
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
// 无数据并且非阻塞方式,则立即返回-EAGAIN,,client缓冲区头等于尾
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);
// 唤醒操作在事件处理函数中
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
// 唤醒
wake_up_interruptible(&evdev->wait);
evdev_event去唤醒
evdev_event
事件处理函数在input_handler
中,
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,
};
evdev_event
被硬件相关的代码调用。以这个看,gpio_keys_isr
是一个例子,不对应实际硬件,在drivers\input\keyboard\gpio_keys.c中,
在设备的终端服务程序中,
先确定事件是什么,
然后调用相应的input_handler
的event
函数上报事件。
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
...
if (irq == gpio_to_irq(gpio)) {
unsigned int type = button->type ?: EV_KEY;
int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;
// 上报事件
input_event(input, type, button->code, !!state);
input_sync(input);
}
return IRQ_HANDLED;
}
中断服务程序中的去调用input_event 上报事件 去 调用evdev_event
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
// 遍历设备链表
list_for_each_entry(handle, &dev->h_list, d_node)
// 对每个已经打开的设备 去调用它的event函数
if (handle->open)
handle->handler->event(handle, type, code, value);
写符合输入子系统框架的 驱动程序:
- 分配一个input_dev结构体
- 设置
- 注册
- 硬件相关代码,如在中断服务程序中上报事件