printk(KERN_INFO 'input: %s as %sn',
dev->name ? dev->name : 'Unspecified device', path ? path : 'N/A');
kfree(path);
list_for_each_entry(handler, &input_handler_list, node)//根据node这个成员在input_handler_list链表中循环查找handler结构
input_attach_handler(dev, handler);//对于每一个handler结构调用input_attach_handler函数
input_wakeup_procfs_readers();//将这个设备信息写入proc文件系统
return 0;
}
可以看到它同样也调用了input_attach_handler函数,将设备驱动层与事件层联系起来。这个函数也位于driversinputinput.c文件中。它的主要功能是
1、根据handler->id_table的值匹配dev,找到id
2、调用调用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);//根据handler->id_table的值匹配dev,找到id
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);//调用handler->connect进行匹配
if (error && error != -ENODEV)
printk(KERN_ERR
'input: failed to attach handler %s to device %s, '
'error: %dn',
handler->name, kobject_name(&dev->cdev.kobj), error);
return error;
}
接着看到input_match_device函数
static const struct input_device_id *input_match_device(const struct input_device_id *id,
struct input_dev *dev)
{
int i;
for (; id->flags || id->driver_info; id++) {//循环查找支持的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;
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;
}
return NULL;
再看到handler->connect函数,这里选取的是evdev_handler ->evdev_connect函数,这个函数的主要作用就是将handle、handler、evdev三者相互匹配起来
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
struct class_device *cdev;
dev_t devt;
int minor;
int error;
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);//取得次设备号,如果还没有利用则evdev_table为空
if (minor == EVDEV_MINORS) {
printk(KERN_ERR 'evdev: no more free evdev devicesn');//没有剩余的空间可以用了
return -ENFILE;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配一个evdev
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
init_waitqueue_head(&evdev->wait);
evdev->exist = 1; //evdev初始化
evdev->minor = minor;
evdev->handle.dev = dev; //初始化evdev->handle.dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //初始化evdev->handle.handler
evdev->handle.private = evdev;
sprintf(evdev->name, 'event%d', minor);//打印次设备号,每次注册新的设备驱动都会打印
evdev_table[minor] = evdev;//将分配的evdev放入evdev_table[minor]
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);//创建一个字符设备节点
if (IS_ERR(cdev)) {
error = PTR_ERR(cdev);
goto err_free_evdev;
}
/* temporary symlink to keep userspace happy */
error = sysfs_create_link(&input_class.subsys.kobj,
&cdev->kobj, evdev->name);
if (error)
goto err_cdev_destroy;
/*
llist_add_tail(&handle->d_node, &handle->dev->h_list);//将&handle->d_node 放入&handle->dev->h_list链表
list_add_tail(&handle->h_node, &handler->h_list); //将&handle->h_node 放入 &handler->h_list链表?
*/
error = input_register_handle(&evdev->handle);//注册evdev->handle
if (error)
goto err_remove_link;
return 0;
err_remove_link:
sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
err_cdev_destroy:
class_device_destroy(&input_class, devt);
err_free_evdev:
kfree(evdev);
evdev_table[minor] = NULL;
return error;
}
再回过头来看一下应用层是怎么读取按键值得:应用层调用C库的read函数,通过前面的分析可以知道,最终会通过系统调用会定位到内核的evdev_handler ->fops ->evdev_read,下面看到evdev_read函数,它位于事件层,这个函数根据读取方式的不同采取不同的方式,如果是阻塞方式打开的话最终会当前进程放入等待队列,一直等到有数据才将进程唤醒。
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;
int retval;
if (count < evdev_event_size())
return -EINVAL;
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);//阻塞方式读取,先将当前进程休眠,等待有数据后被唤醒
if (retval)
return retval;
if (!evdev->exist)
return -ENODEV;
while (client->head != client->tail && retval + evdev_event_size() <= count) {//头!=尾表示有数据
struct input_event *event = (struct input_event *) client->buffer + client->tail;
if (evdev_event_to_user(buffer + retval, event))//将得到的数据考回给用户层
return -EFAULT;
client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
retval += evdev_event_size();
}
return retval;
}
接下来的问题就是谁将进程唤醒,我们直接看到设备驱动层,即driversinputtabletkbtab.c,在这个文件中有一个kbtab_irq函数,它是一个中断处理函数,它位于设备驱动层,负责将中断过来的按键数据上报调用的是input_report_key函数,input_report_key函数最终调用的是input_event函数,他们全部都属于核心层。接着看一下input_event的核心代码
list_for_each_entry(handle, &dev->h_list, d_node)//根据dev设备驱动层的h_list找出handle结构体
if (handle->open)
handle->handler->event(handle, type, code, value);//调用事件层的handle->handler->event进行处理
再回过头看事件层的event,即evdev_event函数,可以看到在这个函数里将按键的相关的值取出后,最终进程的唤醒函数在这里调用wake_up_interruptible。
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
if (evdev->grab) {
client = evdev->grab;
do_gettimeofday(&client->buffer[client->head].time);
client->buffer[client->head].type = type;
client->buffer[client->head].code = code;
client->buffer[client->head].value = value;
client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
} else
list_for_each_entry(client, &evdev->client_list, node) {
do_gettimeofday(&client->buffer[client->head].time);//时间 8字节
client->buffer[client->head].type = type; //按键类型 2字节
上一篇:Linux驱动之一个简单的输入子系统程序编写
下一篇:Linux驱动之定时器在按键去抖中的应用
推荐阅读最新更新时间:2024-11-03 22:11
设计资源 培训 开发板 精华推荐
- 具有欠压锁定功能的 LT3973HMSE-5 5V 降压转换器的典型应用
- 用于IO-Link设备的STM32 Nucleo包与IO-Link v1.1(PHY和堆栈)完全兼容
- LTC3588-1 演示板,能量收集 (EH) 多源
- REF194 精密微功率、低压差电压基准的典型应用
- LT1086CM-3.6 电池后备稳压电源的典型应用
- LF18CPT-TR 1.8V 顺序极低压降稳压器多输出电源的典型应用
- NCP512SQ27T2G 80 mA、2.7 输出电压 CMOS 低压稳压器的典型应用
- 基于GD32F350的LED玩具
- 用于超级电容器后备电源的 LT8705IFE 同步降压-升压型 DC/DC 控制器的典型应用电路
- 使用 AD5933、12 位、1MSPS 阻抗转换器进行高精度阻抗测量