`
wyd018fw
  • 浏览: 16353 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

Intel8042芯片驱动分析

阅读更多

Intel8042芯片驱动分析
2011年11月03日
  重要提醒:系统检测到您的帐号可能存在被盗风险,请尽快查看风险提示,并立即修改密码。  |  关闭
  网易博客安全提醒:系统检测到您当前密码的安全性较低,为了您的账号安全,建议您适时修改密码    立即修改  |  关闭
  ------------------------------------------ 本文系本站原创,转载请注明出处:http://ericxiao.cublog.cn/----------------------- -------------------
  一:intel8042芯片概述
  Intel8024是intel公司的一款键盘控制器芯片,它为x86系统中的标准配置.虽然名为键盘控制器,但是鼠标也是由其控制的.
  分配给键盘控制器的I/O端口有四个,分别是0x60~0x64.在大部分情况中,只会使用到0x60和0x64.其余0x61~0x64的存在主要是为了兼容XT.可以将0x64看做是状态寄存器.0x60看成是数据寄存器.有时在给键盘控制器下指令的时候,这两个端口都要用到.两者配合来达到下指令与参数的目的. 
  在微机原理中学过,键盘通常使用IRQ1.鼠标通常使用IRQ12.其它IRQ1和IRQ12都是连接在键盘控制器上的.对应intel8042的两个端口.
  Intel8024的指令类型:
  1:取得控制器状态.通过读取0x64中的值
  2:键盘控制命令:将指令写入0x60中.如果该指令带有参数,也写入到0x60中.通过在20ms内.键盘会给一个应答(0xfa)
  3:键盘控制器命令:用来控制键盘控制器.将指令写入0x64.将参数写入0x60中
  有关更详细的说明请参阅有关intel 8042芯片的相关手册.
  二:架构概述
  先来看硬件的连接层次.如下图所示:
  
  红线部份标识的serio总线在硬件上是不存在的.这是Linux内核为了简化串行的输入输出设备添加一个中间层.
  如上层所示.串行键盘鼠标等外设都是连接在i8042控制器上的.串行I/O设备的输入输出数据以及设备的控制都是通过i8042进行的.结合我们之前分析过的serio总线代码.serio总线在这里正好为i8042和外设之间提供了一个通信层, 串行外设的驱动也是基于serio结构的.
  当i8042检测到一个外部设备,它生成一个serio device.然后将其注册到serio总线.这时就会去匹配注册到serio总线上的serio driver.
  当一个中断到来时,i8042会检测serio device设备是否已经关联到驱动,如果已经关联了,直接调用驱动中的中断处理函数,如果没有.手动使device去匹配serio driver.
  由此可以看出.这种架构模型极大的简化了驱动的设计,它会串行驱动设计者提供了一个统一的接口.
  在接下来的驱动代码分析中,会涉及到我们之前分析过的platform.serio等.
  三:intel8042驱动分析
  以下的代码分析是基于linux kernel 2.6.25 intel8042的驱动位于linux-2.6.25/drivers/input/serio/ i8042.c
  驱动对应的初始化入口如下:
  static int __init i8042_init(void)
  {
  int err;
  dbg_init();
  err = i8042_platform_init();
  if (err)
  return err;
  err = i8042_controller_check();
  if (err)
  goto err_platform_exit;
  err = platform_driver_register(&i8042_driver);
  if (err)
  goto err_platform_exit;
  i8042_platform_device = platform_device_alloc("i8042", -1);
  if (!i8042_platform_device) {
  err = -ENOMEM;
  goto err_unregister_driver;
  }
  err = platform_device_add(i8042_platform_device);
  if (err)
  goto err_free_device;
  panic_blink = i8042_panic_blink;
  return 0;
  err_free_device:
  platform_device_put(i8042_platform_device);
  err_unregister_driver:
  platform_driver_unregister(&i8042_driver);
  err_platform_exit:
  i8042_platform_exit();
  return err;
  }
  在初始化入口里,调用i8042_platform_init()来进行i8024驱动的一些初始化.然后调用i8042_controller_check()来检查8042芯片是否正常.如果一切正常,注意一个platform总线的驱动和设备.关于platform总线,参考 linux设备模型之platform总线>>
  挨个分析初始化入口里所调用的子函数.
  i8042_platform_init()用来进行一系统的初始化,代码如下:
  static int __init i8042_platform_init(void)
  {
  int retval;
  /*
  * On ix86 platforms touching the i8042 data register region can do really
  * bad things. Because of this the region is always reserved on ix86 boxes.
  *
  * if (!request_region(I8042_DATA_REG, 16, "i8042"))
  * return -EBUSY;
  */
  //键盘通道所对应的IRQ
  i8042_kbd_irq = I8042_MAP_IRQ(1);
  //鼠标通道所对应的IRQ
  i8042_aux_irq = I8042_MAP_IRQ(12);
  //PNP选择编译部份
  retval = i8042_pnp_init();
  if (retval)
  return retval;
  #if defined(__ia64__)
  i8042_reset = 1;
  #endif
  //DMI选择编译部份
  #if defined(__i386__) || defined(__x86_64__)
  if (dmi_check_system(i8042_dmi_noloop_table))
  i8042_noloop = 1;
  if (dmi_check_system(i8042_dmi_nomux_table))
  i8042_nomux = 1;
  #endif
  #ifdef CONFIG_X86
  if (dmi_check_system(i8042_dmi_dritek_table))
  i8042_dritek = 1;
  #endif /* CONFIG_X86 */
  return retval;
  }
  在这里,主要指定了键盘接口和鼠标接口的IRQ.其它部份为选择编译部份,忽略.
  i8042_controller_check()用来检查8042是否正常,代码如下:
  static int i8042_controller_check(void)
  {
  if (i8042_flush() == I8042_BUFFER_SIZE) {
  printk(KERN_ERR "i8042.c: No controller found.\n");
  return -ENODEV;
  }
  return 0;
  }
  当i8042_flush()返回I8042_BUFFER_SIZE的时候,会提示末找到8042控制器.看这个函数的名称是刷新什么东西.转进去看看
  static int i8042_flush(void)
  {
  unsigned long flags;
  unsigned char data, str;
  int i = 0;
  spin_lock_irqsave(&i8042_lock, flags);
  while (((str = i8042_read_status()) & I8042_STR_OBF) && (i 
  udelay(50);
  data = i8042_read_data();
  i++;
  dbg("%02x 
  str & I8042_STR_AUXDATA ? "aux" : "kbd");
  }
  spin_unlock_irqrestore(&i8042_lock, flags);
  return i;
  }
  从代码中可以看出.读取8042状态寄存器的值,如果返回值含有I8042_STR_OBF位.则延迟之后再读取,这样一直尝试I8042_BUFFER_SIZE次.如果读出来的值一直都包含I8042_STR_OBF位的话,那i8042_controller_check()就会返回错误了.
  I8042_STR_OBF定义如下:
  #define I8042_STR_OBF 0x01
  对应为寄存器的第1位,
  对于0x64第1位的定义为:如果此位被置,将表示数据端口0x60有数据.这样,对上面代码的逻辑含义就很好理解了:
  如果数据端口一直的数据,就说明此时可能出现了异常
  然后在初始化函数里,注册了一个platform 的驱动i8042_driver,如下:
  static struct platform_driver i8042_driver = {
  .driver = {
  .name = "i8042",
  .owner = THIS_MODULE,
  },
  .probe = i8042_probe,
  .remove = __devexit_p(i8042_remove),
  .shutdown = i8042_shutdown,
  #ifdef CONFIG_PM
  .suspend = i8042_suspend,
  .resume = i8042_resume,
  #endif
  };
  紧接着,又注册了一个名为i8042的platform设备,根据之前研究的platform总线的知识,可得知,进行设备与驱动匹配的时候,首先会判断设备和驱动的name是否相同.在这里是相同的.接着会调用platform driver的probe接口,在这里,这个接口对应为:
  i8042_probe().代码分段如下
  static int __devinit i8042_probe(struct platform_device *dev)
  {
  int error;
  error = i8042_controller_selftest();
  if (error)
  return error;
  i8042_controller_selftest()用于键盘控控制器的自检.控制器自检的指令为0xaa.若自检成功,返回0x55
  error = i8042_controller_init();
  if (error)
  return error;
  进行键盘控制器的初始化。
  if (!i8042_noaux) {
  error = i8042_setup_aux();
  if (error && error != -ENODEV && error != -EBUSY)
  goto out_fail;
  }
  if (!i8042_nokbd) {
  error = i8042_setup_kbd();
  if (error)
  goto out_fail;
  }
  建立两个通道。一个是aux.为鼠标使用。另一个是kbd.为键盘使用
  #ifdef CONFIG_X86
  if (i8042_dritek) {
  char param = 0x90;
  error = i8042_command(&param, 0x1059);
  if (error)
  goto out_fail;
  }
  #endif
  /*
  * Ok, everything is ready, let's register all serio ports
  */
  i8042_register_ports();
  注册端口。端口是在建立通道的时候建立的。
  return 0;
  out_fail:
  i8042_free_aux_ports(); /* in case KBD failed but AUX not */
  i8042_free_irqs();
  i8042_controller_reset();
  return error;
  }:
  I8042设置键盘和鼠标接口的过程非常复杂.我们在下面分段对这两个过程进行讲解.在讲解之前,我们先对i8042_register_ports()有一个了解.代码如下:
  static void __devinit i8042_register_ports(void)
  {
  int i;
  for (i = 0; i 
  if (i8042_ports.serio) {
  printk(KERN_INFO "serio: %s at %#lx,%#lx irq %d\n",
  i8042_ports.serio->name,
  (unsigned long) I8042_DATA_REG,
  (unsigned long) I8042_COMMAND_REG,
  i8042_ports.irq);
  serio_register_port(i8042_ports.serio);
  }
  }
  }
  显然,这段代码是对i8042_ports[]数组中被初始化的项进行处理,调用serio_register_port()将其注册到虚拟总线..
  i8042_setup_aux()用来设置鼠标通道.代码如下:
  static int __devinit i8042_setup_aux(void)
  {
  int (*aux_enable)(void);
  int error;
  int i;
  if (i8042_check_aux())
  return -ENODEV;
  i8042_check_aux()检查8042是否支持aux通道
  if (i8042_nomux || i8042_check_mux()) {
  error = i8042_create_aux_port(-1);
  if (error)
  goto err_free_ports;
  aux_enable = i8042_enable_aux_port;
  } else {
  for (i = 0; i 
  error = i8042_create_aux_port(i);
  if (error)
  goto err_free_ports;
  }
  aux_enable = i8042_enable_mux_ports;
  }
  一般情况下,流程会转入elsa中.即会通过i8042_create_aux_port()在i8042_ports[]数组中进行相关项的初始化.
  error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED,
  "i8042", i8042_platform_device);
  if (error)
  goto err_free_ports;
  if (aux_enable())
  goto err_free_irq;
  i8042_aux_irq_registered = 1;
  return 0;
  为鼠标对应的IRQ设置中断处理例程.并在8042中启用aux
  err_free_irq:
  free_irq(I8042_AUX_IRQ, i8042_platform_device);
  err_free_ports:
  i8042_free_aux_ports();
  return error;
  }
  i8042_create_aux_port()代码如下:
  static int __devinit i8042_create_aux_port(int idx)
  {
  struct serio *serio;
  int port_no = idx 
  struct i8042_port *port = &i8042_ports[port_no];
  serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
  if (!serio)
  return -ENOMEM;
  serio->id.type = SERIO_8042;
  serio->write = i8042_aux_write;
  serio->start = i8042_start;
  serio->stop = i8042_stop;
  serio->port_data = port;
  serio->dev.parent = &i8042_platform_device->dev;
  if (idx 
  strlcpy(serio->name, "i8042 AUX port", sizeof(serio->name));
  strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys));
  } else {
  snprintf(serio->name, sizeof(serio->name), "i8042 AUX%d port", idx);
  snprintf(serio->phys, sizeof(serio->phys), I8042_MUX_PHYS_DESC, idx + 1);
  }
  port->serio = serio;
  port->mux = idx;
  port->irq = I8042_AUX_IRQ;
  return 0;
  }
  就是通过这个接口为aux通道在i8042_ports[]中初始化相关项.
  Kdb通道的处理也差不多.代码如下:
  static int __devinit i8042_setup_kbd(void)
  {
  int error;
  error = i8042_create_kbd_port();
  if (error)
  return error;
  error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
  "i8042", i8042_platform_device);
  if (error)
  goto err_free_port;
  error = i8042_enable_kbd_port();
  if (error)
  goto err_free_irq;
  i8042_kbd_irq_registered = 1;
  return 0;
  err_free_irq:
  free_irq(I8042_KBD_IRQ, i8042_platform_device);
  err_free_port:
  i8042_free_kbd_port();
  return error;
  }
  它先调用i8042_create_kbd_port()在i8042_ports[]初始化关于kdb通道的相关项.然后为键盘IRQ注册中断处理例程,最后在8042芯片中开通此通道.
  来流览下i8042_create_kdb_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;
  }
  最后在调用i8042_register_ports()在serio总线中注册端口的时候,就会将设备与serio中驱动关联起来了.
  来看下8024为键盘IRQ和鼠标IRQ注册的两个中断处理例程:
  static irqreturn_t i8042_interrupt(int irq, void *dev_id)
  {
  struct i8042_port *port;
  unsigned long flags;
  unsigned char str, data;
  unsigned int dfl;
  unsigned int port_no;
  int ret = 1;
  spin_lock_irqsave(&i8042_lock, flags);
  str = i8042_read_status();
  //如果有输出缓存中有数据,0位会被置为1
  if (unlikely(~str & I8042_STR_OBF)) {
  spin_unlock_irqrestore(&i8042_lock, flags);
  if (irq) dbg("Interrupt %d, without any data", irq);
  ret = 0;
  goto out;
  }
  data = i8042_read_data();
  spin_unlock_irqrestore(&i8042_lock, flags);
  if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
  static unsigned long last_transmit;
  static unsigned char last_str;
  dfl = 0;
  if (str & I8042_STR_MUXERR) {
  dbg("MUX error, status is %02x, data is %02x", str, data);
  /*
  * When MUXERR condition is signalled the data register can only contain
  * 0xfd, 0xfe or 0xff if implementation follows the spec. Unfortunately
  * it is not always the case. Some KBCs also report 0xfc when there is
  * nothing connected to the port while others sometimes get confused which
  * port the data came from and signal error leaving the data intact. They
  * _do not_ revert to legacy mode (actually I've never seen KBC reverting
  * to legacy mode yet, when we see one we'll add proper handling).
  * Anyway, we process 0xfc, 0xfd, 0xfe and 0xff as timeouts, and for the
  * rest assume that the data came from the same serio last byte
  * was transmitted (if transmission happened not too long ago).
  */
  switch (data) {
  default:
  if (time_before(jiffies, last_transmit + HZ/10)) {
  str = last_str;
  break;
  }
  /* fall through - report timeout */
  case 0xfc:
  case 0xfd:
  case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break;
  case 0xff: dfl = SERIO_PARITY; data = 0xfe; break;
  }
  }
  port_no = I8042_MUX_PORT_NO + ((str >> 6) & 3);
  last_str = str;
  last_transmit = jiffies;
  } else {
  dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
  ((str & I8042_STR_TIMEOUT) ? SERIO_TIMEOUT : 0);
  port_no = (str & I8042_STR_AUXDATA) ?
  I8042_AUX_PORT_NO : I8042_KBD_PORT_NO;
  }
  port = &i8042_ports[port_no];
  dbg("%02x 
  data, port_no, irq,
  dfl & SERIO_PARITY ? ", bad parity" : "",
  dfl & SERIO_TIMEOUT ? ", timeout" : "");
  if (unlikely(i8042_suppress_kbd_ack))
  if (port_no == I8042_KBD_PORT_NO &&
  (data == 0xfa || data == 0xfe)) {
  i8042_suppress_kbd_ack--;
  goto out;
  }
  if (likely(port->exists))
  serio_interrupt(port->serio, data, dfl);
  out:
  return IRQ_RETVAL(ret);
  }
  在这个处理例程里,它会判断是键盘的IRQ还是鼠标的IRQ.然后转向serio_interrupt().在serio总线中我们分析过这个接口.如果serio设备没有被驱动绑定,则重新扫描一下驱动,否则,调用驱动的interrupt处理函数.
  四:小结
  在本节里,简单的分析了i8042芯片驱动的代码.本节中所涉及到的控制器驱动架构对其它的总线设备也是类似的,如pci,usb等.相信经过这一节的分析过后,我们对linux的设备模型理解会更新的深刻了.
  
  
  
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics