Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2742096
  • 博文数量: 79
  • 博客积分: 30130
  • 博客等级: 大将
  • 技术积分: 2608
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-22 14:58
个人简介

博所搬至http://xiaogr.com

文章存档

2015年(2)

2009年(3)

2008年(56)

2007年(18)

分类: LINUX

2008-07-23 13:53:28

------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://ericxiao.cublog.cn/
------------------------------------------
一:前言:
在分析intel8042芯片驱动的时候,对其中断处理的后续流程还没有讨论完.在本章以键盘通道为索引讲述intel8042的后续处理,这部份内容实际上是独立的键盘驱动部份,多种型号的键盘都有自己的驱动程序,但原理都是一样的,都承接着intel8042芯片的后续处理.所以在这里为了讨论的方便,将其以单独小节的方式给出分析.
下面以基于2.6.25kernel的atkbd.c键盘驱动为例进行分析.
 
二:intel8042中断处理回顾
记得在intel8042的i8042_probe()处理的中断处理中,注册了几个serio port.以kbd通道为例,再次将其列出.
static int __devinit i8042_probe (void)
{
    ……
     i8042_setup_kbd ()
     ……
     i8042_register_ports()
     ……
}
在i8042_setup_kbd () -> i8042_create_kbd_port()中:
static int __devinit i8042_create_kbd_port(void)
{
     struct serio *serio;
     struct i8042_port *port = &i8042_ports[I8042_KBD_PORT_NO];
 
     serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
     if (!serio)
         return -ENOMEM;
 
     serio->id.type         = i8042_direct ? SERIO_8042 : SERIO_8042_XL;
     serio->write       = i8042_dumbkbd ? NULL : i8042_kbd_write;
     serio->start       = i8042_start;
     serio->stop        = i8042_stop;
     serio->port_data   = port;
     serio->dev.parent  = &i8042_platform_device->dev;
     strlcpy(serio->name, "i8042 KBD port", sizeof(serio->name));
     strlcpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys));
 
     port->serio = serio;
     port->irq = I8042_KBD_IRQ;
 
     return 0;
}
初始化了一个serio结构.
在i8042_register_ports()中:
static void __devinit i8042_register_ports(void)
{
     ……
     serio_register_port(i8042_ports[i].serio);
     ……
}
将这个serio注册到了总线.
根据我们之前分析的serio总线的相关知识.注册serio port的时候会产生serio port与serio driver的匹配事件
那serio driver在什么地方呢? 这就是今天要讨论的键盘驱动了.
 
三:键盘驱动入口
在atkbd.c中,module的入口函数为:
static int __init atkbd_init(void)
{
     dmi_check_system(atkbd_dmi_quirk_table);
 
     return serio_register_driver(&atkbd_drv);
}
在这个初始化函数里,注册了一个serio driver.即atkbd_drv.这就是上在讨论的serio driver的由来了.它的结构如下:
static struct serio_driver atkbd_drv = {
     .driver       = {
         .name    = "atkbd",
     },
     .description  = DRIVER_DESC,
     .id_table = atkbd_serio_ids,
     .interrupt    = atkbd_interrupt,
     .connect = atkbd_connect,
     .reconnect    = atkbd_reconnect,
     .disconnect   = atkbd_disconnect,
     .cleanup = atkbd_cleanup,
};
先来看一下它的id_table成员.这个成员决定着serio port与serio driver是否匹配成功.如下:
static struct serio_device_id atkbd_serio_ids[] = {
     {
         .type    = SERIO_8042,
         .proto   = SERIO_ANY,
         .id  = SERIO_ANY,
         .extra   = SERIO_ANY,
     },
     {
         .type    = SERIO_8042_XL,
         .proto   = SERIO_ANY,
         .id  = SERIO_ANY,
         .extra   = SERIO_ANY,
     },
     {
         .type    = SERIO_RS232,
         .proto   = SERIO_PS2SER,
         .id  = SERIO_ANY,
         .extra   = SERIO_ANY,
     },
     { 0 }
};
由此看出,所有类型为SERIO_8042, SERIO_8042_XL, SERIO_RS232的serio port都适用于此驱动.回顾前面分析的i8042_create_kbd_port()函数中,对serio的id成员赋值如下:
serio->id.type         = i8042_direct ? SERIO_8042 : SERIO_8042_XL;
也就是说,由i8042产生的serio类型为SERIO_8042或者 SERIO_8042_XL.都是适用这个驱动的.
就这样,我们找到了由intel8042产生serio port对应的驱动了.
四:serio drver的connect函数
在之前分析的serio总线中得知.如果serio port与驱动匹配成功,就会调用驱动的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);
     dev = input_allocate_device();
     if (!atkbd || !dev)
         goto fail1;
 
     atkbd->dev = dev;
     ps2_init(&atkbd->ps2dev, serio);
     INIT_DELAYED_WORK(&atkbd->event_work, atkbd_event_work);
     mutex_init(&atkbd->event_mutex);
 
     switch (serio->id.type) {
 
         case SERIO_8042_XL:
              atkbd->translated = 1;
         case SERIO_8042:
              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);
 
     err = serio_open(serio, drv);
     if (err)
         goto fail2;
 
     if (atkbd->write) {
 
         if (atkbd_probe(atkbd)) {
              err = -ENODEV;
              goto fail3;
         }
 
         atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
         atkbd_activate(atkbd);
 
     } else {
         atkbd->set = 2;
         atkbd->id = 0xab00;
     }
 
     atkbd_set_keycode_table(atkbd);
     atkbd_set_device_attrs(atkbd);
 
     err = sysfs_create_group(&serio->dev.kobj, &atkbd_attribute_group);
     if (err)
         goto fail3;
 
     atkbd_enable(atkbd);
 
     err = input_register_device(atkbd->dev);
     if (err)
         goto fail4;
 
     return 0;
 
 fail4: sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group);
 fail3:  serio_close(serio);
 fail2:  serio_set_drvdata(serio, NULL);
 fail1:  input_free_device(dev);
     kfree(atkbd);
     return err;
}
这段代码第一次看的时候可能觉得比较恐怖,很多陌生的接口,其实,这里面处理的事情并不多,首先它调用atkbd_set_keycode_table()根据设备的类型选择一套扫描码.什么叫扫描码?扫描码就是指根据从intel8042中读取到的数据转换为我们所用的字符的过程.例如,在第一套扫描码中,数字1的扫描码就是0x2.具体的知识请查阅相关资料,在这里不做详细分析,值得注意的是.,在这里.键盘的类型通常为SERIO_8042_XL型,因为在intel8042驱动中,默认是不支持i8042_direct.的
然后申请一个input_dev. Input_dev是输出子系统的一个概念.所谓输出子系统,是处理I/O设备与上层应用的一个中间层,它接收下层驱动的相关事件(例如键盘按键,鼠标移动等)发送到上层.这个子系统我们下一节再给出详细的分析,在这里只要知道它的概念就可以了.
接下来,调用input_register_device()将input_dev注册到了输入子系统,注意在这之前还会调用atkbd_set_device_attrs()设置iput_dev的相关属性,在这一节里,这并不是我们所分析的重点.在此不详细讨论.
 
五:键盘的中断处理
在intel8042接收到中断之后,会调用驱动的interrupt接口来处理数据,在这里,这个接口为atkbd_interrupt().代码如下:
static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
              unsigned int flags)
{
     struct atkbd *atkbd = serio_get_drvdata(serio);
     struct input_dev *dev = atkbd->dev;
     unsigned int code = data;
     int scroll = 0, hscroll = 0, click = -1;
     int value;
     unsigned char keycode;
 
#ifdef ATKBD_DEBUG
     printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags);
#endif
 
//不是在x86中的情况.
#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
 
     //intel 8042的应答消息处理
     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:
              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:
              if (printk_ratelimit())
                   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_ERR:
              atkbd->err_count++;
#ifdef ATKBD_DEBUG
              printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys);
#endif
              goto out;
     }
     //将接收到的数据通过扫描码转换成字符码
     code = atkbd_compat_scancode(atkbd, code);
 
     if (atkbd->emul && --atkbd->emul)
         goto out;
 
     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 ' 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_event(dev, EV_KEY, keycode, value);
              //sync事件,表明当前事件已经完了
              input_sync(dev);
             
              //按键松开了,上报一个松开的按键消息
              if (value && test_bit(code, atkbd->force_release_mask)) {
                   input_report_key(dev, keycode, 0);
                   input_sync(dev);
              }
     }
 
     if (atkbd->scroll) {
         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;
}
在这个中断处理程序里,涉及到了按键断开码,控制码,按键应答等处理.查阅intel8042的相关资料理解这部份并不难.在中断处理程序中,将接收到的按键扫描码转换之后,调用input_event()产生一个按键事件,将其上报给上层的input_handler.
 
六:小结
简而言之,键盘的驱动流程就是这样的,当驱动检测到设备之后,注册一个input device.然后在中断处理中,将接收到的扫描码转换之后,再给上层上报一个事件。在这一小节里,对具体的键盘驱动讲述较少。只因为这部份的东西部份有很多详尽的资料描述。在此不再赘述.
 
 
 
 
阅读(6136) | 评论(0) | 转发(7) |
给主人留下些什么吧!~~