Chinaunix首页 | 论坛 | 博客
  • 博客访问: 16399
  • 博文数量: 6
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-17 20:29
文章分类

全部博文(6)

文章存档

2015年(6)

我的朋友
最近访客

分类: 嵌入式

2015-09-09 12:48:23

 

深入浅出linux之总线之上的总线(serio总线)

 1428人阅读 评论(1) 收藏 举报
 

从前文驱动的层次分析中,我们知道驱动可以分为几个层次,驱动之间可以嵌套。而总线也可以分为几个层次,一种总线可以架构在另一种总线之上。

  上节在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总线。

阅读(1116) | 评论(0) | 转发(0) |
0

上一篇:linux输入子系统

下一篇:没有了

给主人留下些什么吧!~~