一、设备驱动程序
在上一篇随笔中已经分析,linux输入子系统分为设备驱动层、核心层和事件层。要利用linux内核中自带的输入子系统实现一个某个设备的操作,我们一般只需要完成驱动层的程序即可,核心层和事件层内核已经帮我们做好了。因此这篇随笔主要介绍按键操作设备驱动层的代码。
1.1设备驱动入口函数
在设备驱动入口函数中我们需要做的事:(1)分配一个input_dev 结构体
(2)设置这个input_dev 结构体
(3)调用input_register_device注册这个input_dev
(4)完成硬件相关操作:如注册中断处理函数,添加定时器等
static int button_init(void){ int i; int err; /* 1. 分配一个 input_dev 结构体*/ button_dev = input_allocate_device(); /* 2. 设置 */ /* 2.1 能产生哪类事件 */ set_bit(EV_KEY, button_dev->evbit); /* 2.2 能产生这类事件下的哪些操作: L S ENTER LEFTSHIT*/ set_bit(KEY_L, button_dev->keybit); set_bit(KEY_S, button_dev->keybit); set_bit(KEY_ENTER, button_dev->keybit); set_bit(KEY_LEFTSHIFT, button_dev->keybit); /* 3. 注册 */ input_register_device(button_dev); /* 4. 硬件相关操作*/ gpkcon = ioremap(GPKCON_PA, 4); //io口映射 gpkdat = ioremap(GPKDAT_PA, 4); gpndat = ioremap(GPNDAT_PA, 4); init_timer(&button_timer); // 初始化定时器 button_timer.function = button_timer_function;// 指定定时器的处理函数 add_timer(&button_timer); // 添加定时器 for (i=0; i<4; i++) { err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,button_irqs[i].name,(void *)&button_irqs[i]); if (err) break; } return 0;}
1.1.1在初始化iinput_dev结构体过程
主要对其中的如下数组做了初始化,从而来确定该设备支持哪些事件,支持哪些操作。
初始化时首先要确定设备能够产生哪一类事件
事件的类型如下:
事件类型的设置主要对evbit[]数组中的相应位做设置: set_bit(EV_KEY, button_dev->evbit);
然后再确定设备支持该类事件下的哪些操作
例如:在相对坐标事件下可以支持如下操作
在本次按键驱动程序中按键设备产生的事件自然是按键事件,按键事件支持KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT 4个操作,分别对应 L ,S, enter,shift
set_bit(KEY_L, button_dev->keybit);
set_bit(KEY_S, button_dev->keybit);
set_bit(KEY_ENTER, button_dev->keybit;
set_bit(KEY_LEFTSHIFT, button_dev->keybit);
1.1.2 注册输入设备input_register_device()
这个函数在上一篇博客中已经做了简要分析,这里在提一下input_register_device()中做了哪些事
(1) 设置同步事件、清除KEY_RESERVED、清除bitmasks中没有提到的位
(2) 初始化定时器,确定定时器的处理函数。这里定时器与重复上报事件有关,注意在事件类型中有EV_REP事件,设置这个事件在ev_bit中的相应位,就可以重复上报事件。
(3) 设置getkeycode 和 setkeycode 函数
(4) device_add, input_dev包含的device结构注册到Linux设备模型中,在sysfs文件系统中可以看到增加了设备input1
(5)list_add_tail 在上一篇博客中已经介绍了
(6)遍历iinput_handler_list 中的事件处理器 input_handler, 与input_handler 进行匹配 连接操作。 具体的连接操作在下边分析。
1.1.3 硬件相关操作
定时器、IO映射、注册中断
2. 中断处理函数
static void button_timer_function(unsigned long data){ int num; int tmp; struct button_irq_desc* pindesc = irq_pdesc; if (! pindesc) return; num = pindesc->number; tmp = *gpndat; input_event(button_dev, EV_KEY,button_irqs[num].key_val , !(tmp &(1<
中断处理函数包括两部分:定时中断用于消除按键抖动
按键中断处理函数主要用来调
整定时器事件
当有按键中断发生时,我们需要上报事件:
input_event(button_dev, EV_KEY,button_irqs[num].key_val , !(tmp &(1<<num)));
input_sync(button_dev);2.1.1 input_event 上报事件函数
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); add_input_randomness(type, code, value); // 执行事件处理函数 input_handle_event(dev, type, code, value); spin_unlock_irqrestore(&dev->event_lock, flags); }}
static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value){ int disposition = INPUT_IGNORE_EVENT; ..... switch (type) { case EV_KEY: if (is_event_supported(code, dev->keybit, KEY_MAX) && !!test_bit(code, dev->key) != value) { if (value != 2) { __change_bit(code, dev->key); if (value) input_start_autorepeat(dev, code); else input_stop_autorepeat(dev); } disposition = INPUT_PASS_TO_HANDLERS; } break; if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) dev->sync = false; if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) dev->event(dev, type, code, value); if (disposition & INPUT_PASS_TO_HANDLERS) input_pass_event(dev, type, code, value);}
大概可以分析出事件处理函数要么执行input_dev下的event, 要么执行input_handler下的event函数,至于执行哪一个和disposition这个变量有关。
需要注意的是如果执行的是input_dev下边的event,那么应该只会执行一个event函数,如果要是执行的是input_handler下的event函数,那么会执行input_pass_event 函数。这个函数中通过input_dev->h_list链表上挂的input_handler结构体,找到与之匹配的input_handler。然后执行input_handler下的event函数。 如果input_dev与多个input_handler事件处理器匹配了,那么当设备驱动上报事件时,并且要执行input_handler中的event函数,那么会依次执行这些匹配好的input_handler的event函数。在接下的实验中可以看到这一点。
二、程序执行部分过程分析:
2.1 input_dev与 input_handler 的连接
因为我们的程序中已经注册了evdev_handler事件处理器(在上一篇博客中已经分析过了input_register_handler)和 kbd_handler 事件处理器(在keyboard.c 文件中 kbd_init-->input_register_handler(&kbd_handler)) ,当在驱动程序中input_register_device后,设备驱动和这两个事件处理器进行匹配和连接操作。
2.1.1 button_dev 和 evdev_handler 连接
匹配完成之后会执行连接操作,连接操作执行的是evdev_handler->connet 函数,这个函数已经在前一篇博客中分析了,这里只简述函数执行过程和效果。
(1) 分配evdev 结构体
(2) 设置evdev 结构体下边的 handle 和 dev
(3) 注册evdev 下边的handle ,在上一篇博客中已经分析了这个handle是连接input_dev 和 input_handler 的桥梁
(4) 添加设备device_add(&evdev->dev)
执行完这个函数应该可以看到在/dev/input/下出现even0 设个设备
可以看到主设备号为13, 次设备号为64, 这里次设备号=64+minor, 因为evdev_handler 还没有和任何设备建立连接,所以在input_table[] 数组中还没存放任何evdev结构体,因此minor的值为0, 故此设备号为64+0=64。
2.1.2 button_dev 和 kbd_handler 的匹配过程
static int kbd_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id){ struct input_handle *handle; int error; handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); if (!handle) return -ENOMEM; handle->dev = dev; handle->handler = handler; handle->name = "kbd"; error = input_register_handle(handle); if (error) goto err_free_handle; error = input_open_device(handle); if (error) goto err_unregister_handle; return 0; err_unregister_handle: input_unregister_handle(handle); err_free_handle: kfree(handle); return error;}
这里的连接过程比较简单,没有上一个连接过程那么复杂,但是可以看到主要的数据结构input_handle并没有少,因为这个结构体是input_dev和input_handler的联系桥梁,复杂的数据结构之间的关系可以使input_dev 可以访问到input_handle从而访问到input_handler。(在事件上报函数中分析过怎样从input_dev找到input_handler)
2.2 事件上报函数分析
前文已经提到了button_dev 与 evdev_handler 和 kbd_handler 匹配连接成功,那么在事件上报的时候,就会分别执行这两个事件处理器中的event函数。
2.2.1 evdev->event 函数
这个函数已经在上一篇博客中提前分析过了。
2.2.2 kbd->event 函数
static void kbd_event(struct input_handle *handle, unsigned int event_type, unsigned int event_code, int value){ /* We are called with interrupts disabled, just take the lock */ spin_lock(&kbd_event_lock); if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev)) kbd_rawcode(value); if (event_type == EV_KEY) kbd_keycode(event_code, value, HW_RAW(handle->dev)); spin_unlock(&kbd_event_lock); tasklet_schedule(&keyboard_tasklet); do_poke_blanked_console = 1; schedule_console_callback();}
三、实验效果
3.1 执行hexdump /dev/input/event0
依次按下 l s enter 对应的按键:l 对应0x26 s 对应0x1f enter对应0x1c
3.2 执行cat /dev/tty1
并且将标准输入重定向到/dev/tty1 依次按下 l s enter 对应的按键
可以看到依次按下l s enter 后执行了ls 命令
同时需要强调的是:上边的两个命令的执行在不同的shell 下,但是只按下了一次l s enter后分别在两个shell中打印了不同的结果。这就证明了之前所说的一个button_dev 与 evdev_handler 和 kbd_handler 建立了连接,上报一次事件,会分别调用这两个事件处理器的event 函数。