技术标签: 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.然后在中断处理中,将接收到的扫描码转换之后,再给上层上报一个事件。在这一小节里,对具体的键盘驱动讲述较少。只因为这部份的东西部份有很多详尽的资料描述。在此不再赘述.
本人很喜欢postgresql数据库,也一直认为postgresql比mysql要更好更强大。可生态环境太差了,无奈,最近要把一个小站转成mysql数据库。小站主要表数据110万,pg_dump备份下载的压缩数据库将近3G。怎么转成mysql呢?尝试1:我首先尝试了工具,结果只找到两款国外的工具( PostgresToMysql和 DBConvert for MySQL & Postgre...
Jackson常用注解属性命名@JsonProperty注解指定一个属性用于JSON映射,默认情况下映射的JSON属性与注解的属性名称相同,不过可以使用该注解的value值修改JSON属性名,该注解还有一个index属性指定生成JSON属性的顺序,如果有必要的话。属性包含还有一些注解可以管理在映射JSON的时候包含或排除某些属性,下面介绍一下常用的几个。@JsonIgnore注解用于排除某个属性,这样该属性就不会被Jackson序列化和反序列化。@JsonIgnoreProperties注解是类注解
导语:服务器支持中文但是java日志输出中文乱码。最后解决的办法。app04上中文正常app03上中文乱码 为???检查了2台服务器的lang参数 系统版本 jar包启动方式都是一样 但是就是有问题# 测试服务器是不是中文乱码 echo "哈哈" > 哈哈;cat 哈哈# 或者date输出看一下是否乱码 如果你设置的是中文的话会显示如下# 2020年 12月 01日 星期二 13:49:47 CST发现服务器上输出中文没问题 那就是jar包的问题# 使用UTF-8 另一台
我有一个网络服务器,我目前正在对CPU使用情况进行基准测试.我正在做的实际上是运行一个进程来请求服务器请求,然后运行以下bash脚本来确定CPU使用情况:#! /bin/bashfor (( ;; ))doecho "`python -c 'import time; print time.time()'`, `ps -p $1 -o '%cpu' | grep -vi '%CPU'`"sleep ...
分布式系统分布式系统概述:分布式系统是在同一个网络下,不同的组件通过网络进行通信和协调,表现如一个系统的系统。简单来说,一个分布式系统是一组计算机系统一起工作,在终端用户看来,就像一台计算机在工作一样。分布式是相对中心化而来,强调的是任务在多个物理隔离的节点上进行。中心化带来的主要问题是可靠性,若中心节点宕机则整个系统不可用,分布式除了解决部分中心化问题,也倾向于分散负载,但分布式会带来很多的其他问题,最主要的就是一致性。微服务架构概述:微服务架构:把一个大型的单个应用程序和服务
因为学生大多数都是出外求学所以宿舍就是学生的第二个家,作为教务一定要管理好学生的宿舍信息。但是传统的宿舍管理模式已经逐渐的被淘汰。人们需要。一种新的模式来的宿舍信息进行管理,那就是信息化宿舍管理系统。PHP学生宿舍管理系统通过PHP+MySQL进行开发,系统主要分为管理员,协管员,教师和学生4中用户角色用户登录界面在登陆录界面,我们分为管理员,协管员,教师和学生四部分。多用户输入对应的用户名和密...
一、部署AD域:系统环境:Windows server 2008 R2 标准版 ip:172.16.1.149服务器开机后会自动弹出一个初始配置任务窗口(这个不要随便关闭,因为我不知道关闭后能在哪里打开)选择添加角色,选中Active Directory域服务,接下来就默认安装好了。 接下来还要进行配置 ctrl+r 运行dcpromo 前面默认,后面选中在新林中新建域,点...
HTML文件多文件选择上传文件预览图:php文件0,'msg'=> '','data' =>array('src' => $dir . $_FILES["file"]["name"]),);$file_info = $_FILES['file'];$file_error = $file_info['error'];if (!is_dir($dir)) {//判断目录是否存在mk...
你还在每天庸庸碌碌的过日子吗?你不想改变下自己吗?曾近有句话说过:"知识很宝贵,就好像是金矿。学好知识,掌握好本领,会对我终身有益"。其实网上有很多一系列的教学视频网站,我们只要坚持下去,成功可能就在下一个街角等着你!那么今天小编在这里为大家整理了珍藏7个不可多得的自学网站,送给正要提升自己的人,一生受益!1、 我爱自学网这是一款涵盖了各类软件教程、程序设计以及吉他教程的综合性网站网站里面...
SAMSUNG 三星 S7 edge 港版简单使用感受2016-03-16 17:50:0050点赞37收藏97评论三星2016年新出的旗舰机型S7 edge购买火热,上周去香港预订,仔细对比了三种颜色:金色、银色、黑色。个人感受黑色宣传照尤其好看,但真机中庸。银色很亮眼,有反光,所以指纹看上去更明显。倒是金色,并不是那种土豪金,更像是古铜金,所以整机质感很不错。因为金色在网上倍受诟病冷落,优惠6...
我正在用C++编写应用程序(使用带有Linux GCC的Eclipse),它应该与我的MySQL服务器进行交互。我已经下载了MySQL Connector C++ a,预编译并将文件复制到目录中(/ usr/lib,/ usr/include)。我在Eclipse的Project Properties(“mysqlcppconn”)的GCC C++链接器部分中引用了它。我的代码直接来自MySQL参...
问题描述:在单向链表中,每个结点都包含一个指向下一个结点的指针,最后一个结点的这个指针被设置为空。但如果把最后一个结点的指针指向链表中存在的某个结点,就会形成一个环,在顺序遍历链表的时候,程序就会陷入死循环。问题:如何检测一个链表中是否有环,如果检测到环,如何确定环的入口点(即求出环长,环前面的链长)。一种比较耗空间的做法是,从头开始遍历链表,把每次访问到的结点(或其地址)存入一个集合(hashs...