分类: 嵌入式
2015-09-09 12:48:23
从前文驱动的层次分析中,我们知道驱动可以分为几个层次,驱动之间可以嵌套。而总线也可以分为几个层次,一种总线可以架构在另一种总线之上。
上节在platform总线的分析中,在驱动提供的probe函数中,调用了一个serio_register_port。这就是总线嵌套的概念,引出了在内核中极重要的总线桥的概念。
什么是总线桥?我们知道计算机的体系架构中,PCI总线占有重要的地位,是连接主板和外部设备的标准总线。而网卡,声卡,显卡,SCSI卡等等设备很多都是以PCI卡的形式出现,并插入计算机的PCI插槽。这些设备中,声卡显卡加载驱动后,就可以直接读写操作,而像SCSI卡这种设备就比较麻烦了,因为SCSI卡本身又可以连接SCSI硬盘,那么加载SCSI卡的PCI驱动后,还必须进行SCSI总线扫描,进而再发现SCSI硬盘设备。这样才能正确的读写硬盘。这里,SCSI卡就担任了总线桥的任务,它提供了总线之间的协议转换和互操作。像SCSI卡这样的设备,被称为主机总线适配器(HBA),它一方面是个PCI设备,另一方面它又管理SCSI设备。
计算机系统的很多设备,像驱动键盘鼠标的芯片,本身不是PCI设备,也不被PCI总线管理,为了控制这种设备,内核创造了一个platform总线,把这些设备挂载到一条虚拟的platform总线上。这样就提供了一种统一的架构来管理设备。
当注册到platform总线的设备和驱动匹配之后,驱动本身就会探测端口并注册到serio_bus。serio_register_port就执行这个注册操作。查阅内核提供的platform文档,platform总线通常用来包装固件(firmware),这些设备不存在热插拔的问题,也不会离线和再次发现。
一 serio总线注册
我们从serio_register_port接续前面的分析:
====================================================================
static inline void serio_register_port(struct serio *serio)
{
/*这个端口指明类型是SERIO_8042,说明是8042兼容型的。I8042是intel开发的键盘鼠标控制芯片*/
__serio_register_port(serio, THIS_MODULE);
}
void __serio_register_port(struct serio *serio, struct module *owner)
{
/*初始化端口指明总线类型是serio_bus*/
serio_init_port(serio);
/*注册一个SERIO_REGISTER_PORT事件*/
serio_queue_event(serio, owner, SERIO_REGISTER_PORT);
}
======================================================================
static void serio_queue_event(void *object, struct module *owner,
enum serio_event_type event_type)
{
unsigned long flags;
struct serio_event *event;
spin_lock_irqsave(&serio_event_lock, flags);
/*
* Scan event list for the other events for the same serio port,
* starting with the most recent one. If event is the same we
* do not need add new one. If event is of different type we
* need to add this event and should not look further because
* we need to preseve sequence of distinct events.
*/
list_for_each_entry_reverse(event, &serio_event_list, node) {
/*如果发现相同的event,则退出。*/
if (event->object == object) {
if (event->type == event_type)
goto out;
break;
}
}
if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) {
if (!try_module_get(owner)) {
printk(KERN_WARNING "serio: Can't get module reference, dropping event %d\n", event_type);
kfree(event);
goto out;
}
event->type = event_type;
event->object = object;
event->owner = owner;
/*加event到链表尾,并唤醒线程。*/
list_add_tail(&event->node, &serio_event_list);
wake_up(&serio_wait);
} else {
printk(KERN_ERR "serio: Not enough memory to queue event %d\n", event_type);
}
out:
spin_unlock_irqrestore(&serio_event_lock, flags);
}
这个注册的事件在哪来处理?实际是serio_thread内核线程处理。
==================================================================
static int serio_thread(void *nothing)
{
do {
serio_handle_event();
/*挂起内核线程等条件满足。也就是serio_event_list链表非空*/
wait_event_interruptible(serio_wait,
kthread_should_stop() || !list_empty(&serio_event_list));
try_to_freeze();
} while (!kthread_should_stop());
printk(KERN_DEBUG "serio: kseriod exiting\n");
return 0;
}
实际的处理是serio_handle_event处理。为何驱动的作者要把设备注册(这里的端口就是设备)放到线程中处理?我们知道在进程上下文是可以睡眠的,而且不会太长时间占用CPU。需要调用可能引起睡眠的函数,就必须在进程上下文中执行。
继续看serio_handle_event
======================================================================
static void serio_handle_event(void)
{
struct serio_event *event;
mutex_lock(&serio_mutex);
/*
* Note that we handle only one event here to give swsusp
* a chance to freeze kseriod thread. Serio events should
* be pretty rare so we are not concerned about taking
* performance hit.
*/
if ((event = serio_get_event())) {
switch (event->type) {
case SERIO_REGISTER_PORT:
serio_add_port(event->object);
break;
case SERIO_UNREGISTER_PORT:
serio_disconnect_port(event->object);
serio_destroy_port(event->object);
break;
case SERIO_RECONNECT:
serio_reconnect_port(event->object);
break;
case SERIO_RESCAN:
serio_disconnect_port(event->object);
serio_find_driver(event->object);
break;
case SERIO_REGISTER_DRIVER:
serio_add_driver(event->object);
break;
default:
break;
}
serio_remove_duplicate_events(event);
serio_free_event(event);
}
mutex_unlock(&serio_mutex);
}
实际处理是在serio_add_port,继续看这个函数。
======================================================================
static void serio_add_port(struct serio *serio)
{
int error;
if (serio->parent) {
/*修改串口parent设备的参数*/
serio_pause_rx(serio->parent);
serio->parent->child = serio;
serio_continue_rx(serio->parent);
}
/*把串口设备加入链表*/
list_add_tail(&serio->node, &serio_list);
if (serio->start)
serio->start(serio);
error = device_add(&serio->dev);
if (error)
printk(KERN_ERR
"serio: device_add() failed for %s (%s), error: %d\n",
serio->phys, serio->name, error);
else {
serio->registered = 1;
error = sysfs_create_group(&serio->dev.kobj, &serio_id_attr_group);
if (error)
printk(KERN_ERR
"serio: sysfs_create_group() failed for %s (%s), error: %d\n",
serio->phys, serio->name, error);
}
}
这里关键函数是device_add。在platform总线的分析中,我们已经分析过这个函数。现在回忆一下,device_add核心就是遍历总线的驱动,通过match函数找到一个合适的驱动,然后调用probe函数。
这样,需要找到serio总线的match函数。就是serio_bus_match函数。
static int serio_bus_match(struct device *dev, struct device_driver *drv)
{
struct serio *serio = to_serio_port(dev);
struct serio_driver *serio_drv = to_serio_driver(drv);
/*如果指定了手工绑定,则匹配不成功*/
if (serio->manual_bind || serio_drv->manual_bind)
return 0;
return serio_match_port(serio_drv->id_table, serio);
}
static int serio_match_port(const struct serio_device_id *ids, struct serio *serio)
{
while (ids->type || ids->proto) {
if ((ids->type == SERIO_ANY || ids->type == serio->id.type) &&
(ids->proto == SERIO_ANY || ids->proto == serio->id.proto) &&
(ids->extra == SERIO_ANY || ids->extra == serio->id.extra) &&
(ids->id == SERIO_ANY || ids->id == serio->id.id))
return 1;
ids++;
}
return 0;
}
上面这两个函数很简单,就是检查type,proto等参数是否相等。我们登记设备的时候,赋予的type是SERIO_8042,查一下内核代码,和它匹配的驱动就是drivers/input/keyboard/atkbd.c
匹配之后,首先调用总线提供的probe函数。这个是所有总线的通用过程。
====================================================================
static int serio_driver_probe(struct device *dev)
{
struct serio *serio = to_serio_port(dev);
struct serio_driver *drv = to_serio_driver(dev->driver);
return serio_connect_driver(serio, drv);
}
=============================================================
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
{
int retval;
mutex_lock(&serio->drv_mutex);
retval = drv->connect(serio, drv);
mutex_unlock(&serio->drv_mutex);
return retval;
}
最终是调用驱动提供的connect函数。就是atkbd_connect。
static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
{
struct atkbd *atkbd;
struct input_dev *dev;
int err = -ENOMEM;
atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL);
/*创建一个input设备*/
dev = input_allocate_device();
if (!atkbd || !dev)
goto fail;
/*atkbd的dev赋值为创建的input设备。这样就进入另一个层次。Input也要为这个设备加载对应的input驱动*/
atkbd->dev = dev;
/*创建一个工作队列,这个队列做什么?就是处理input_event不处理的事件*/
ps2_init(&atkbd->ps2dev, serio);
INIT_WORK(&atkbd->event_work, atkbd_event_work, atkbd);
mutex_init(&atkbd->event_mutex);
switch (serio->id.type) {
case SERIO_8042_XL:
atkbd->translated = 1;
case SERIO_8042:
/*之前没赋值write函数*/
if (serio->write)
atkbd->write = 1;
break;
}
atkbd->softraw = atkbd_softraw;
atkbd->softrepeat = atkbd_softrepeat;
atkbd->scroll = atkbd_scroll;
if (atkbd->softrepeat)
atkbd->softraw = 1;
serio_set_drvdata(serio, atkbd);
/*打开serio登记的open函数。Serio是Q40kbd里面创建的。它的open函数是q40kbd_open*/
err = serio_open(serio, drv);
if (err)
goto fail;
if (atkbd->write) {
if (atkbd_probe(atkbd)) {
serio_close(serio);
err = -ENODEV;
goto fail;
}
atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
atkbd_activate(atkbd);
} else {
atkbd->set = 2;
atkbd->id = 0xab00;
}
/* 根据set值选择码表。*/
atkbd_set_keycode_table(atkbd);
/*设置input设备的属性*/
atkbd_set_device_attrs(atkbd);
device_create_file(&serio->dev, &atkbd_attr_extra);
device_create_file(&serio->dev, &atkbd_attr_scroll);
device_create_file(&serio->dev, &atkbd_attr_set);
device_create_file(&serio->dev, &atkbd_attr_softrepeat);
device_create_file(&serio->dev, &atkbd_attr_softraw);
atkbd_enable(atkbd);
/*注册input设备!!和input串联起来了。*/
input_register_device(atkbd->dev);
return 0;
fail: serio_set_drvdata(serio, NULL);
input_free_device(dev);
kfree(atkbd);
return err;
}
这个函数创建一个input设备,设置设备的属性,最后注册设备。input_register_device在input那一节分析过,它最终要为设备找到驱动。这个驱动就是虚拟键盘驱动,根据我们前面对input的分析,这个驱动应该通过input_register_handler函数加入到input的驱动列表。
后面另外分析虚拟键盘驱动的注册过程。
这个函数调用了serio注册的open函数。分析一下这个函数。
======================================================================
static int q40kbd_open(struct serio *port)
{
q40kbd_flush();
if (request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, "q40kbd", NULL)) {
printk(KERN_ERR "q40kbd.c: Can't get irq %d.\n", Q40_IRQ_KEYBOARD);
return -EBUSY;
}
/* off we go */
/*写芯片的控制端口*/
master_outb(-1, KEYBOARD_UNLOCK_REG);
master_outb(1, KEY_IRQ_ENABLE_REG);
return 0;
}
q40kbd_open函数要申请中断并且启动端口。我们知道,这个芯片是管理键盘的,在这里启动了键盘。而这里的中断是物理上存在的,它的中断处理函数需要调用serio设备,input设备等一系列虚拟设备的处理。这样才能把来自物理底层的事件一步步报上去。
二 虚拟键盘驱动的注册
根据前文对input的分析,input驱动是通过input_register_handler函数加入到input的驱动列表。虚拟键盘驱动位于根目录dirvers/char/keyboard.c。
=====================================================================
int __init kbd_init(void)
{
int i;
for (i = 0; i < MAX_NR_CONSOLES; i++) {
kbd_table[i].ledflagstate = KBD_DEFLEDS;
kbd_table[i].default_ledflagstate = KBD_DEFLEDS;
kbd_table[i].ledmode = LED_SHOW_FLAGS;
kbd_table[i].lockstate = KBD_DEFLOCK;
kbd_table[i].slockstate = 0;
kbd_table[i].modeflags = KBD_DEFMODE;
kbd_table[i].kbdmode = VC_XLATE;
}
input_register_handler(&kbd_handler);
tasklet_enable(&keyboard_tasklet);
tasklet_schedule(&keyboard_tasklet);
return 0;
}
不出所料,这个keyboard初始化的时候就把驱动kbd_handler注册到input里面。
回顾前面分析的input设备注册过程,在设备匹配到驱动以后,还要调用驱动的connect和start函数。继续分析。
=====================================================================
static struct input_handle *kbd_connect(struct input_handler *handler,
struct input_dev *dev,
struct input_device_id *id)
{
struct input_handle *handle;
int i;
/*从保留键开始测试。*/
for (i = KEY_RESERVED; i < BTN_MISC; i++)
if (test_bit(i, dev->keybit))
break;
if (i == BTN_MISC && !test_bit(EV_SND, dev->evbit))
return NULL;
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return NULL;
handle->dev = dev;
handle->handler = handler;
handle->name = "kbd";
input_open_device(handle);
return handle;
}
这段代码很简单。就是调用input设备的open函数。对这个例子来说,实际上没有注册open函数。
======================================================================
/*
* Start keyboard handler on the new keyboard by refreshing LED state to
* match the rest of the system.
*/
static void kbd_start(struct input_handle *handle)
{
unsigned char leds = ledstate;
tasklet_disable(&keyboard_tasklet);
if (leds != 0xff) {
input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 0x01));
input_inject_event(handle, EV_LED, LED_NUML, !!(leds & 0x02));
input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & 0x04));
input_inject_event(handle, EV_SYN, SYN_REPORT, 0);
}
tasklet_enable(&keyboard_tasklet);
}
Kbd_start开始控制真正的键盘设备,它的作用是刷新键盘的LEB灯,这就是我们在系统启动时候看到的键盘闪烁。input_inject_event的作用是闪烁,这个功能必须芯片来触发。可以想象,这个流程一定要通过input调用下面的serio设备,再写事件到相应的IO端口。读者可以分析一下这个流程。明白了原理和框架,其实内核的很多代码已经可以整理出大概过程,只是具体实现的不同。因为我们选的是一个老旧的芯片,这个芯片实际是不能驱动键盘闪烁的。读者可以分析一下,看是否得到同样的结论。
三 键盘中断(按键上报)
现在可以把前面的概念串联一下,用图来表示。可以看到,内核就是通过这样一层层建立了设备的驱动架构。
一旦设备,驱动都就位了。那么就可以从键盘接收用户的输入了。从最底层的中断开始分析:
======================================================================
static irqreturn_t q40kbd_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long flags;
spin_lock_irqsave(&q40kbd_lock, flags);
if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))
serio_interrupt(q40kbd_port, master_inb(KEYCODE_REG), 0, regs);
master_outb(-1, KEYBOARD_UNLOCK_REG);
spin_unlock_irqrestore(&q40kbd_lock, flags);
return IRQ_HANDLED;
}
很简单,就是调用serio_interrupt。
======================================================================
irqreturn_t serio_interrupt(struct serio *serio,
unsigned char data, unsigned int dfl, struct pt_regs *regs)
{
unsigned long flags;
irqreturn_t ret = IRQ_NONE;
spin_lock_irqsave(&serio->lock, flags);
/*如果已经匹配驱动了,那么调用驱动的中断处理函数*/
if (likely(serio->drv)) {
ret = serio->drv->interrupt(serio, data, dfl, regs);
} else if (!dfl && serio->registered) {
/*serio重新扫描,和前面的设备注册差不多的流程*/
serio_rescan(serio);
ret = IRQ_HANDLED;
}
spin_unlock_irqrestore(&serio->lock, flags);
return ret;
}
回忆一下,serio驱动是什么?就是前面分析的atkbd,所以这个中断处理函数就是atkbd_interrupt。
======================================================================
static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
unsigned int flags, struct pt_regs *regs)
{
struct atkbd *atkbd = serio_get_drvdata(serio);
struct input_dev *dev = atkbd->dev;
unsigned int code = data;
int scroll = 0, hscroll = 0, click = -1, add_release_event = 0;
int value;
unsigned char keycode;
#ifdef ATKBD_DEBUG
printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags);
#endif
*/
#if !defined(__i386__) && !defined (__x86_64__)
if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) {
printk(KERN_WARNING "atkbd.c: frame/parity error: %02x\n", flags);
serio_write(serio, ATKBD_CMD_RESEND);
atkbd->resend = 1;
goto out;
}
if (!flags && data == ATKBD_RET_ACK)
atkbd->resend = 0;
#endif
/*如果需要,回一个ACk给键盘。*/
if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK))
if (ps2_handle_ack(&atkbd->ps2dev, data))
goto out;
/*如果有进程在等键盘,那么唤醒等待的进程*/
if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD))
if (ps2_handle_response(&atkbd->ps2dev, data))
goto out;
if (!atkbd->enabled)
goto out;
/*上报原始的扫描码*/
input_event(dev, EV_MSC, MSC_RAW, code);
if (atkbd->translated) {
if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) {
atkbd->release = code >> 7;
code &= 0x7f;
}
if (!atkbd->emul)
atkbd_calculate_xl_bit(atkbd, data);
}
switch (code) {
case ATKBD_RET_BAT:
/*出错了,重新connect*/
atkbd->enabled = 0;
serio_reconnect(atkbd->ps2dev.serio);
goto out;
case ATKBD_RET_EMUL0:
atkbd->emul = 1;
goto out;
case ATKBD_RET_EMUL1:
atkbd->emul = 2;
goto out;
case ATKBD_RET_RELEASE:
atkbd->release = 1;
goto out;
case ATKBD_RET_ACK:
case ATKBD_RET_NAK:
printk(KERN_WARNING "atkbd.c: Spurious %s on %s. "
"Some program might be trying access hardware directly.\n",
data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys);
goto out;
case ATKBD_RET_HANGEUL:
case ATKBD_RET_HANJA:
/*
* These keys do not report release and thus need to be
* flagged properly
*/
add_release_event = 1;
break;
case ATKBD_RET_ERR:
printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys);
goto out;
}
code = atkbd_compat_scancode(atkbd, code);
if (atkbd->emul && --atkbd->emul)
goto out;
/*转换扫描码为通用的字符码,码表就是前面atkbd_connect里面注册进去的码表。*/
keycode = atkbd->keycode[code];
if (keycode != ATKBD_KEY_NULL)
input_event(dev, EV_MSC, MSC_SCAN, code);
switch (keycode) {
case ATKBD_KEY_NULL:
break;
case ATKBD_KEY_UNKNOWN:
printk(KERN_WARNING
"atkbd.c: Unknown key %s (%s set %d, code %#x on %s).\n",
atkbd->release ? "released" : "pressed",
atkbd->translated ? "translated" : "raw",
atkbd->set, code, serio->phys);
printk(KERN_WARNING
"atkbd.c: Use 'setkeycodes %s%02x <keycode>' to make it known.\n",
code & 0x80 ? "e0" : "", code & 0x7f);
input_sync(dev);
break;
case ATKBD_SCR_1:
scroll = 1 - atkbd->release * 2;
break;
case ATKBD_SCR_2:
scroll = 2 - atkbd->release * 4;
break;
case ATKBD_SCR_4:
scroll = 4 - atkbd->release * 8;
break;
case ATKBD_SCR_8:
scroll = 8 - atkbd->release * 16;
break;
case ATKBD_SCR_CLICK:
click = !atkbd->release;
break;
case ATKBD_SCR_LEFT:
hscroll = -1;
break;
case ATKBD_SCR_RIGHT:
hscroll = 1;
break;
default:
if (atkbd->release) {
value = 0;
atkbd->last = 0;
} else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) {
/* Workaround Toshiba laptop multiple keypress */
value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2;
} else {
value = 1;
atkbd->last = code;
atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2;
}
input_regs(dev, regs);
/*这里真正上报字符码*/
input_event(dev, EV_KEY, keycode, value);
/*同步事件,上报结束*/
input_sync(dev);
if (value && add_release_event) {
input_report_key(dev, keycode, 0);
input_sync(dev);
}
}
if (atkbd->scroll) {
input_regs(dev, regs);
if (click != -1)
input_report_key(dev, BTN_MIDDLE, click);
input_report_rel(dev, REL_WHEEL, scroll);
input_report_rel(dev, REL_HWHEEL, hscroll);
input_sync(dev);
}
atkbd->release = 0;
out:
return IRQ_HANDLED;
}
这个函数处理很复杂,其实就是把原始的扫描码变换为码表。还有对键盘按键控制码,断开码等等的控制。最终,input设备接收到的,是统一转换之后的各种类型码。下面列出按键事件类型。
EV_SYN 同步
EV_KEY 按键
EV_REL 相对坐标
EV_ABS 绝对坐标
EV_MSC 其它杂项事件
EV_LED LED灯事件
EV_SND 声音事件
EV_REP 自动重复的参数事件
EV_SW 切换事件
这些事件类型很多,每种事件里面有定义了很多子事件。针对具体事件的处理,需要相关驱动的开发人员仔细设计。继续往下看input_event。
====================================================================
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
if (type > EV_MAX || !test_bit(type, dev->evbit))
return;
/*加随机数,这里是利用按键产生随机数。如果有需要随机数的进程阻塞,就要唤醒进程。*/
add_input_randomness(type, code, value);
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
if (dev->event)
dev->event(dev, type, code, value);
break;
case SYN_REPORT:
if (dev->sync)
return;
dev->sync = 1;
break;
}
break;
case EV_KEY:
if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
return;
if (value == 2)
break;
change_bit(code, dev->key);
if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
dev->repeat_key = code;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
break;
case EV_SW:
if (code > SW_MAX || !test_bit(code, dev->swbit) || !!test_bit(code, dev->sw) == value)
return;
change_bit(code, dev->sw);
break;
case EV_ABS:
if (code > ABS_MAX || !test_bit(code, dev->absbit))
return;
if (dev->absfuzz[code]) {
if ((value > dev->abs[code] - (dev->absfuzz[code] >> 1)) &&
(value < dev->abs[code] + (dev->absfuzz[code] >> 1)))
return;
if ((value > dev->abs[code] - dev->absfuzz[code]) &&
(value < dev->abs[code] + dev->absfuzz[code]))
value = (dev->abs[code] * 3 + value) >> 2;
if ((value > dev->abs[code] - (dev->absfuzz[code] << 1)) &&
(value < dev->abs[code] + (dev->absfuzz[code] << 1)))
value = (dev->abs[code] + value) >> 1;
}
if (dev->abs[code] == value)
return;
dev->abs[code] = value;
break;
case EV_REL:
if (code > REL_MAX || !test_bit(code, dev->relbit) || (value == 0))
return;
break;
case EV_MSC:
if (code > MSC_MAX || !test_bit(code, dev->mscbit))
return;
if (dev->event)
dev->event(dev, type, code, value);
break;
case EV_LED:
if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
return;
change_bit(code, dev->led);
if (dev->event)
dev->event(dev, type, code, value);
break;
case EV_SND:
if (code > SND_MAX || !test_bit(code, dev->sndbit))
return;
if (!!test_bit(code, dev->snd) != !!value)
change_bit(code, dev->snd);
if (dev->event)
dev->event(dev, type, code, value);
break;
case EV_REP:
if (code > REP_MAX || value < 0 || dev->rep[code] == value)
return;
dev->rep[code] = value;
if (dev->event)
dev->event(dev, type, code, value);
break;
case EV_FF:
if (dev->event)
dev->event(dev, type, code, value);
break;
}
if (type != EV_SYN)
dev->sync = 0;
/*如果设备已经有了指定的input_handler,也就是有了指定的驱动,则调用驱动的event ,否则遍历设备的input_handler的链表,一个个调用。*/
if (dev->grab)
dev->grab->handler->event(dev->grab, type, code, value);
else
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
}
在input设备一节,我们已经简略介绍了这个函数。现在终于明白,用户的按键经过层层的驱动到最后,终于汇入了input字符设备定义的架构里面,只是这时候按键已经变成了标准的输入键。
这个函数很复杂,但对照前面介绍的按键类型,实际就是处理各种各样的按键事件,这个是由input_handler的event函数处理。也就是前面分析过的虚拟键盘驱动的kbd_event函数。
======================================================================
static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
/*原始码由kbd_rawcode处理*/
if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
kbd_rawcode(value);
/*标准的字符码,由kbd_keycode处理*/
if (event_type == EV_KEY)
kbd_keycode(event_code, value, HW_RAW(handle->dev), handle->dev->regs);
/*启动一个软中断,处理led事件等耗时间的操作*/
tasklet_schedule(&keyboard_tasklet);
do_poke_blanked_console = 1;
schedule_console_callback();
}
这里启用了一个tasklet_schedule,用软中断继续处理。回顾一下基础知识一节,软中断的好处是软中断里面可以打开中断。所以一些和硬件相关的操作放在中断里面,而和硬件无关的逻辑等等放在软中断,可以减少长时间关中断的代价。
我们继续看kbd_rawcode。主要是因为这个函数处理简单,而kbd_keycode又要调用虚拟键盘的事件处理表。
static void kbd_rawcode(unsigned char data)
{
struct vc_data *vc = vc_cons[fg_console].d;
kbd = kbd_table + fg_console;
if (kbd->kbdmode == VC_RAW)
put_queue(vc, data);
}
这个函数很简单,但是又出来一个重要的概念VC。VC就是虚拟控制台的意思。我们知道,linux系统可以支持多个控制台,而用户的输入要由当前的活跃的控制台接收。fg_consele就是当前活跃控制台的参数,put_queue最终把按键数据送到当前控制台的输入缓冲区里面。这就是调用标准库函数fgetc可以获得键盘输入的原因,因为此时读控制台缓冲区,就可以获得用户输入。这一部分后面再分析。
四 总结和引申
Platform总线和serio总线,构成了总线的层次关系。这个例子比较简单,但是却很典型。对于一个真正的计算机系统来说,最重要的总线是PCI总线,大多数设备都是PCI设备,而总线适配器(HBA)一般都是挂载到PCI总线上。作为最重要的总线,我们下文将分析PCI总线。