linux键盘驱动程序分析,linux设备驱动之键盘驱动分析

一:前言:

在分析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.然后在中断处理中,将接收到的扫描码转换之后,再给上层上报一个事件。在这一小节里,对具体的键盘驱动讲述较少。只因为这部份的东西部份有很多详尽的资料描述。在此不再赘述.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值