Linux 驱动框架---input子系统
input 子系统也是作为内核的一个字符设备模块存在的,所以他也是字符设备自然也会有字符设备的文件接口.input子系统的注册过程主要分为两步,先注册了一个input class然后再注册一个字符设备 input。
static int __init input_init(void)
{
int err;
//子类注册
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
//proc 文件接口创建
err = input_proc_init();
if (err)
goto fail1;
//申请input设备号 主设备号13 最多1024个此类子设备
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
} return 0; fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
注意这里仅仅创建了input 类并未在这个类下创建设备,同时申请了主设备号为13,次设备号为0-1024的设备号。input子系统和platform_bus 的思想很相同,分为核心层、input_dev(类似设备) 和input_handler(类似drivers)。当一个input_dev添加到内核时如果设备的id和handler的ids相匹配则会执行handler的connet接口将设备和具体handler 链接。整个input子系统实现了输入设备的共性部分,而差异性的部分由具体的设备实现。具体来看其数据结构:
输入设备(dev)
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id; unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
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)]; unsigned int hint_events_per_packet; unsigned int keycodemax;
unsigned int keycodesize;
void *keycode; int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke); struct ff_device *ff; unsigned int repeat_key;
struct timer_list timer; int rep[REP_CNT]; struct input_mt *mt; struct input_absinfo *absinfo; unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)]; int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab; spinlock_t event_lock;
struct mutex mutex; unsigned int users;
bool going_away; struct device dev; struct list_head h_list;
struct list_head node; unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals; bool devres_managed;
};
输入事件处理结构(handler)
//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);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
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); bool legacy_minors;
int minor;
const char *name; const struct input_device_id *id_table; struct list_head h_list;
struct list_head node;
};
我这里不打算一开始就从驱动框架开始学习,因为直接上框架比较抽象所以我先从一个突破口一步一步探索,这里的突破口我选择的是输入设备注册开始的地方。
输入设备注册
int input_register_device(struct input_dev *dev)
{
struct input_devres *devres = NULL;
struct input_handler *handler;
unsigned int packet_size;
const char *path;
int error; if (dev->devres_managed) {
devres = devres_alloc(devm_input_device_unregister,
sizeof(struct input_devres), GFP_KERNEL);
if (!devres)
return -ENOMEM; devres->input = dev;
} /* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit); /* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit); /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev); packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size; dev->max_vals = dev->hint_events_per_packet + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals) {
error = -ENOMEM;
goto err_devres_free;
} /*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
} if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode; error = device_add(&dev->dev);
if (error)
goto err_free_vals; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",
dev->name ? dev->name : "Unspecified device",
path ? path : "N/A");
kfree(path); error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del; 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(); mutex_unlock(&input_mutex); if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0; err_device_del:
device_del(&dev->dev);
err_free_vals:
kfree(dev->vals);
dev->vals = NULL;
err_devres_free:
devres_free(devres);
return error;
}
将设备加入到设备链表然后遍历input_handler_list(这是一个handler 的链表在handler注册的过程中加入)依次执行匹配,调用过程如下
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)————>>input_match_device(struct input_dev *dev, struct input_handler *handler)
实际是由 input_match_device 检查匹配过程的如下:
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id; for (id = handler->id_table; id->flags || id->driver_info; id++) { 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; if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue; if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue; if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue; if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
continue; if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
continue; if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
continue; if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
continue; if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
continue; if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
continue; if (!handler->match || handler->match(handler, dev))
return id;
} return NULL;
}
先看一下一个数据结构struct input_device_id即
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
遍历handler 的id表,依次进行和当前设备id的匹配,匹配规则基本如下:
1、检查handler是否要求匹配总线类型,如果需要则进行匹配,匹配成功或不需要匹配则下一步,失败则下一个匹配项。
2、检查handler是否要求匹配厂商id,结果同上。
3、检查handler是否要求匹配产品id,同上。
4、检查handler是否要求匹配版本id,同上。
5、设备支持的所以输入事件类型是否都是当前的handler的子集,如果是则算匹配第一步符合继续下一步,否则下一个。
6、检查handler有无match接口函数,有则执行返回非0则算匹配成功返回handler 发生匹配的ID (struct input_device_id*),无则直接返回第一阶段匹配的Id。
再回到input_attach_handler 接口:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error; id = input_match_device(handler, dev);
if (!id)
return -ENODEV; error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error); return error;
}
可以看到如果前面的前面的input_match_device如果匹配了设备则返回id,然后则执行handler的connect 接口定义是int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);由这个接口完成dev和handler的链接。拿事件设备(evdev.c)作为例子来分析其具体接口的实现 。
事件设备的connect 接口:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error; minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
} evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
} INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
evdev->exist = true; dev_no = minor;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE;
dev_set_name(&evdev->dev, "event%d", dev_no); evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev; evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev); error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev; cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle; error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev; return 0; err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
err_free_minor:
input_free_minor(minor);
return error;
}
执行过程是分配一个事件设备子设备号在创建文件接口event%d 时需要,分配一个事件设备结构体,这个结构体如下:
struct evdev {
int open;
struct input_handle handle;
wait_queue_head_t wait;
struct evdev_client __rcu *grab;
struct list_head client_list;
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
struct cdev cdev;
bool exist;
};
他带出来了input子系统的另外一个重要数据类型struct input_handle,他是负责链接handler和dev的数据结构如下:
struct input_handle { void *private; int open;
const char *name; struct input_dev *dev;
struct input_handler *handler; struct list_head d_node;
struct list_head h_node;
};
这里暂时不继续深究,继续上面connect的过程分析,分配好对应的数据块后就是初始化,然后就是设置数据块中的设备名称这是为了sys文件系统的显示(event%d)然后就是handle发挥作用的时候如下这部分在connect中:
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
然后接着初始化新设备的相关内容,后面将要进行注册这个过程和platform设备的处理过程相似。
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
然后就是input自系统的API,input_register_handle(struct input_handle* )注意区分handle 和 handler 接下来就是“偷梁换柱”的准备工作了--->注意绑定了一个文件操作接口。
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle; error = device_add(&evdev->dev);
这里的evdev_fops 就是中间件用来“偷梁换柱”的关键,通过上面的操作这时候就会在/dev目录下看到对应的event%s设备了。到这里input子系统的三个成员已经出现了有input_dev、input_handler、input_handle,最后其实还有input_event 和 input_client。Linux内核对于input子系统的处理过程,除此之外上面描述的是设备的注册过程外同样的在handler的注册过程应该也是有一个类似的处理过程,思想和linux设备驱动的整个思想统一源码如下:
/**
* input_register_handler - register a new input handler
* @handler: handler to be registered
*
* This function registers a new input handler (interface) for input
* devices in the system and attaches it to all input devices that
* are compatible with the handler.
*/
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int error; error = mutex_lock_interruptible(&input_mutex);
if (error)
return error; INIT_LIST_HEAD(&handler->h_list); 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(); mutex_unlock(&input_mutex);
return 0;
}
后面我将分析一个常见input驱动evdev.c(事件设备)实例来分析学习下,其实上面的connect接口已经是事件设备的事件处理部分的接口了,所以可以看到在输入设备注册时符合匹配规则时就已经完成了事件处理接口的绑定,到这里驱动在内核中的加载的部分已经完成了接下来分析输入设备和用户空间是如何交互的。
事件设备实例
切入点从上面注册的字符设备的evdev_fops中的open接口开始作为切入点。
static int evdev_open(struct inode *inode, struct file *file)
{
//这里使用了一个架构知识盲区,即字符设备的inode中的i_cdev是记录字符设备的地址的
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
//按照这个输入事件的具体实现了一个策略确定事件buf的个数
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
int error; client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
if (!client)
client = vzalloc(size);
if (!client)
return -ENOMEM; client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
//关联起来 将client的挂接到evdev事件设备上
evdev_attach_client(evdev, client);
//evdev其中的 handle->open ++
error = evdev_open_device(evdev);
if (error)
goto err_free_client;
//将这个client 绑定到文件节点的私有数据上,私有数据太重要了
file->private_data = client;
//以不可以执行lseek的方式执行打开操作
nonseekable_open(inode, file); return 0; err_free_client:
evdev_detach_client(evdev, client);
kvfree(client);
return error;
}
每一个打开输入设备的的进程都会由内核open接口分配一个client,这个client都会和用户空间的struct file *file的私有数据关联起来,具体参考代码注释。然后就是读取接口了。
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
size_t read = 0;
int error; if (count != 0 && count < input_event_size())
return -EINVAL; for (;;) {
if (!evdev->exist || client->revoked)
return -ENODEV; if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN; /*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break; while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
//拷贝一个input_event 到buff
if (input_event_to_user(buffer + read, &event))
return -EFAULT;
//增加读取到的字节数以便读取结束后返回
read += input_event_size();
}
//读到了数据不需要后续的阻塞处理
if (read)
break;
//什么也没读到则需要处理阻塞的逻辑,这里是直接使调用进程在当前的wait list上睡眠
if (!(file->f_flags & O_NONBLOCK)) {
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
if (error)
return error;
//执行到这里就是因为有数据可以读而唤醒或者信号中断的
}
} return read;
}
先从文件的私有数据中取出clients然后进行必要的参数检查,读取大小不能是0并且不能小于一个事件sizeof(struct input_event) 大小,然后遍历循环读取事件知道读到指定数量的数据或无事件可读,关键的就是其中的evdev_fetch_next_event接口处理,他会返回是否有事件可以读取,当有事件可以读取时事件地址保存在event指针,然后再将事件拷贝到用户提供的buf中。
static int evdev_fetch_next_event(struct evdev_client *client,
struct input_event *event)
{
int have_event; spin_lock_irq(&client->buffer_lock); have_event = client->packet_head != client->tail;
if (have_event) {
*event = client->buffer[client->tail++];
client->tail &= client->bufsize - 1;
} spin_unlock_irq(&client->buffer_lock); return have_event;
}
从处理逻辑可以判断事件在发生后都是保存在client下的一个链表中,实际的数据保存在起初为这个client分配的空间中。循环读取事件知道满足待读取的大小或读完事件然后返回实际读取到的字节数。既然这里可以读,那么是在什么时候输入事件被挂接并保存到这个client上的呢?继续找,这肯定是要找到handler的添加过程了。找到定义如下:
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
事件的上报肯定从这里某一个接口实现的,猜测是设备调用的handler的接口来完成事件的上报,这里就直接看evdev_events接口,因为要想真的搞清楚还需要一个事件输入设备的代码来验证。
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
ktime_t time_mono, time_real; time_mono = ktime_get();
time_real = ktime_sub(time_mono, ktime_get_monotonic_offset()); rcu_read_lock(); //这一部分代码不知道什么分支会运行到这里所以暂时不看,正常是执行地下else的分支
client = rcu_dereference(evdev->grab);
//上面特殊情况执行,所以下面的判断一般不成立
if (client)
evdev_pass_values(client, vals, count, time_mono, time_real);
else
//遍历这个事件上的所有clients 依次将事件放到对应的client上
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count,
time_mono, time_real); rcu_read_unlock();
}
这个接口中主要就是获取系统时间,通过接口evdev_pass_values接口将事件绑定并保存到到每个client上如下:
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t mono, ktime_t real)
{
struct evdev *evdev = client->evdev;
const struct input_value *v;
struct input_event event;
bool wakeup = false;
//这个标志意味着这个client不接收事件
if (client->revoked)
return;
//构造事件
event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
mono : real); /* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock); for (v = vals; v != vals + count; v++) {
event.type = v->type;
event.code = v->code;
event.value = v->value;
//发送事件到client
__pass_event(client, &event);
if (v->type == EV_SYN && v->code == SYN_REPORT)
wakeup = true;
} spin_unlock(&client->buffer_lock);
//唤醒逻辑
if (wakeup)
wake_up_interruptible(&evdev->wait);
}
整个处理过程就是将传进来的内核事件加上时间信息组合封装成一个输入事件input_event然后通过接口__pass_event 将事件添加给client,最后如果有必要还需要唤醒阻塞在这个client上的进程。来看事件添加处理:
static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1; if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1); client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0; client->packet_head = client->tail;
} if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
}
就是数据拷贝和链表的操作流程。现在整个输入事假的输入过程越来越清晰明显了。现在就差一个最初的“引爆点”就是谁调用的handler的接口呢,继续往下找这个过程可以借助内核的一个输入设备驱动来辅助进行分析这里拿gpio_key的这个GPIO键盘驱动作为实例,但是这个驱动他是基于platform总线驱动的框架的,如果不清楚platform驱动框架的工作流程可以先去看我的前面的platform的驱动学习相关的内容。所以这里直接来看这个驱动的probe接口执行过程:
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
size_t size;
int i, error;
int wakeup = 0; if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} size = sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data);
ddata = devm_kzalloc(dev, size, GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n");
return -ENOMEM;
}
//申请一个输入设备(input_dev),这是输入设备联系前面的设备添加过程
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
//驱动框架相关的内容
ddata->pdata = pdata;
ddata->input = input;
mutex_init(&ddata->disable_lock); platform_set_drvdata(pdev, ddata);
//这里结合前面的输入设备的数据结构体就明白下面这部分时在初始化设备的一些信息等
input_set_drvdata(input, ddata); input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
//设备的两个重要接口函数,他是属于设备的
input->open = gpio_keys_open;
input->close = gpio_keys_close;
//这里是输入设备的信息字段设置,有时后会用于匹配handler 使用。
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100; /* Enable auto repeat feature of Linux input subsystem */
//设置设备支持的输入事件及特性,这一部分需要先熟悉输入设备的支持的事件的描述方法,bitmap,这些信息也是记录在设备中的,具体的面再看
//设置设备是否支持重复上报,这是按键类设备特有的,比如按键按下不松手是否需要一定间隔上报
if (pdata->rep)
__set_bit(EV_REP, input->evbit); for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
//这是这个驱动封装的接口实际上内部执行的处理过程还混杂这Linux GPIO子系统和工作队列等的相关内容,这里大致分析下执行流程,只关心我们目前关心的内容
/*
每个按键PIN执行:申请IO,根据这个按键的平台数据配置这个IO和申请中断等,然后初始化好一个工作。重点是设置这个input设备的支持的输入事件和对应的值
这里就是EV_KEY 和按键的键值。设置好这么多最后的将每个IO的的数据绑定到每个IO的对应中断服务接口上。然后就是中断服务接口
*/
error = gpio_keys_setup_key(pdev, input, bdata, button);
if (error)
return error; if (button->wakeup)
wakeup = 1;
} error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
return error;
} error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto err_remove_group;
} device_init_wakeup(&pdev->dev, wakeup); return 0; err_remove_group:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error;
}
建议是直接到内核源码中找到这一段学习,因为他同时综合了平台设备驱动的相关内容,这里暂时就不提了,直接挑重点的内容注释。其中重要的中断服务接口,这部分又使用了Linux内核的工作队列机制工作队列是在上面的设置过程中设置的,当按键按下时就会执行下面的中断接口函数这个函数操作也很简单直接调度工作队列由工作队列处理具体的事务,这是一个共享中断传入不同的按键对象作为区分。
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id; BUG_ON(irq != bdata->irq);
//唤醒电源相关的,暂时不看
if (bdata->button->wakeup)
pm_stay_awake(bdata->input->dev.parent);
//按键驱动的逻辑相关
if (bdata->timer_debounce)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(bdata->timer_debounce));
else
schedule_work(&bdata->work);//调度工作队列(中断低半部机制) return IRQ_HANDLED;
}
最后重点来看一下,前面绑定的工作队列处理接口。
static void gpio_keys_gpio_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work);
//得到当前按键的参数信息
gpio_keys_gpio_report_event(bdata);
//电源管理相关的
if (bdata->button->wakeup)
pm_relax(bdata->input->dev.parent);
}
gpio_keys_gpio_report_event分析如下:
static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned int type = button->type ?: EV_KEY;
//读取IO的状态(会睡眠因为在工作队列中)
int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;
//根据按键的类型进行不同的处理
if (type == EV_ABS) {
if (state)
input_event(input, type, button->code, button->value);
} else {
input_event(input, type, button->code, !!state);
}
input_sync(input);
}
这里就是input子系统的相关内容了,从按键参数中获取当前按键的绑定的input_dev,根据按键类型的不同进行事件的上报,下面就是input子系统的API接口函数了。
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
//检查当前设备是否支持当前上报的事件类型
if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags);
//真正的上报接口 这里的code和value 分别为前面设置能力和获取的IO状态
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
继续看下一个接口 input_handle_event
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition; disposition = input_get_disposition(dev, type, code, &value);
//检查是否通过设备自己的接口上报事件
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
//猜测是上面接口执行的返回值,检查是否执行成功,成功则返回不需要继续上报流程
if (!dev->vals)
return;
//通过handle 上报
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v; if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}
//打包事件的值
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
//检查是否需要立刻上报事件
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
//上报事件
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} }
继续深入 input_pass_values
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
//看到这个指针顿时很开心,因为前面的猜测要证实了
struct input_handle *handle;
struct input_value *v; if (!count)
return; rcu_read_lock();
//不止一种从input设备找到他关联的handle的方式,下面是一种
handle = rcu_dereference(dev->grab);
//上面方式未找到handle
if (handle) {
//进行事件过滤,这是handler的接口定义的,并执行handler的event接口进行事件的上报
count = input_to_handler(handle, vals, count);
} else {
//从设备的handle list上挨个找到handler
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
count = input_to_handler(handle, vals, count);
} rcu_read_unlock();
//
add_input_randomness(vals->type, vals->code, vals->value);
//重复上报的的处理过程
/* trigger auto repeat for key events */
for (v = vals; v != vals + count; v++) {
if (v->type == EV_KEY && v->value != 2) {
if (v->value)
input_start_autorepeat(dev, v->code);
else
input_stop_autorepeat(dev);
}
}
}
看其中的上报接口input_to_handler的具体处理过程
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler;
struct input_value *end = vals;
struct input_value *v; for (v = vals; v != vals + count; v++) {
//时间过滤
if (handler->filter &&
handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
} count = end - vals;
if (!count)
return 0;
//找到事件处理接口的上报接口,分为一次多个或单个事件上报
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != end; v++)
handler->event(handle, v->type, v->code, v->value); return count;
}
分析到这里对于Linux系统的基本工作过程有了大致的了解,这个过程充分说明了Linux的驱动很少是使用一点点的架构的知识就能了解全部的,往往一个驱动是对各方面的内容的综合运用,这里暂时分析到这里,后面还要对整个input系统进行更加宏观的架构层次进行总结分析学习。不过还有一个疑问就是这个input设备是在什么时候加入的,回头去详细找找看,其实就是在probe 接口的后面部分,创建完文件系统的内容后就调用了input子系统的接口函数input_register_device 完成了input设备的添加注册,这个接口前面分析过了其中就会涉及到handler的匹配和绑定操作。到此一个input子系统的微观线条已经出来了,后面又总结了有一个简单的系统框架性的总结可参考:Linux 驱动框架---input子系统框架。
Linux 驱动框架---input子系统的更多相关文章
- Linux 驱动框架---input子系统框架
前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...
- Linux 驱动框架---net驱动框架
这一篇主要是学习网络设备驱动框架性的东西具体的实例分析可以参考Linux 驱动框架---dm9000分析 .Linux 对于网络设备的驱动的定义分了四层分别是网络接口层对上是IP,ARP等网络协议,因 ...
- Linux 驱动框架---i2c驱动框架
i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开 ...
- 【Linux高级驱动】input子系统框架【转】
转自:http://www.cnblogs.com/lcw/p/3802617.html [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架? 1) 通过网 ...
- 【Linux高级驱动】input子系统框架
[1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架? 1) 通过网络搜索 2) 自己想办法跟内核代码! 2.1 定位此驱动是属于哪种类 ...
- 【驱动】input子系统全面分析
初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputC ...
- 【驱动】input子系统整体流程全面分析(触摸屏驱动为例)【转】
转自:http://www.cnblogs.com/lcw/p/3294356.html input输入子系统整体流程 input子系统在内核中的实现,包括输入子系统(Input Core),事件处理 ...
- Linux 驱动框架---linux 设备
Linux 设备 Linux驱动中的三大主要基础成员主要是设备,总线和驱动.今天先来从设备开始分析先把设备相关的数据结构放到这里方便后面看到来查,其中有些进行了简单的注释. struct device ...
- Linux 驱动框架---platform驱动框架
Linux系统的驱动框架主要就是三个主要部分组成,驱动.总线.设备.现在常见的嵌入式SOC已经不是单纯的CPU的概念了,它们都会在片上集成很多外设电路,这些外设都挂接在SOC内部的总线上,不同与IIC ...
随机推荐
- 数据分析——Numpy/pandas
NumPy NumPy是高性能科学计算和数据分析的基础包.部分功能如下: ndarray, 具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组. 用于对整组数据进行快速运算的标准数学函数(无需编 ...
- ESXI6.7主机降级至ESXI6.5
上一条博客vcenter添加主机失败:https://www.cnblogs.com/Crazy-Liu/p/11211760.html 原因esxi主机和vcenter版本不一致,因为vcenter ...
- Excel常见后缀名
1.格式.xlsx:excel2007-2016版默认的文件格式,不能有宏: 2.格式.xls:excel97-2003版,可以有宏: 3.格式.csv:以逗号分隔的文本文件,便于兼容其他程序,只保存 ...
- Prometheus—告警altermanger
Prometheus-告警altermanger 1.告警altermanger装配 2.告警Mysql 3.Prometheus针对nodes告警规则配置 相关内容原文地址链接: 51CTO:wfw ...
- BigDecimal 用法详解
BigDecimal简介 BigDecimal用法: BigDecimal的构造方法 BigDecimal常用方法描述 BigDecimal比较 BigDecimal总结 BigDecimal简介 J ...
- hbase 集群(完全分布式)方式安装
一,环境 1, 主节点一台: ubuntu desktop 16.04 zhoujun 172.16.12.1 从节点(slave)两台:ubuntu server 16.04 hadoo ...
- pugixml应用随笔
1. 修改元素值 second_node.set_value("miller");不对 必须second_node.first_child().set_value(" ...
- OpenStack (nova 计算服务)
nova介绍 Nova 负责维护和管理云环境的计算资源,Nova这个模块很重要,可以说是 OpenStack 的最核心的服务模块之一,以至于在 OpenStack 的初期版本里大部分的云系统管理功能都 ...
- 从云数据迁移服务看MySQL大表抽取模式
摘要:MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 小编最近在云上的一个迁移项目中被MySQL抽取模式折磨的很惨.一开始爆内存被客户怼,再后来迁移效率低下再被怼.MySQL ...
- 记录tomcat服务器开启关闭时间
1.IO流 package com.zy.exercise; import java.io.File; import java.io.FileNotFoundException; import jav ...