操作系统

Intel8042芯片驱动分析
作者 xgr180 2008年07月23日 05:49

    ------------------------------------------

    本文系本站原创,欢迎转载!

    转载请注明出处: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(¶m, 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的设备模型理解会更新的深刻了.

    

    

    

    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/51562/showart_1089633.html

Intel8042芯片驱动分析

Intel8042芯片驱动分析

长按识别二维码 进入IT168查看全文

请长按保存图片
{{data.thematic.text}}