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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_36445866/article/details/116692031

智能推荐

mysql到pg怎么高效_利用Navicat高效率postgresql转mysql数据库_轲幽的博客-程序员宅基地

本人很喜欢postgresql数据库,也一直认为postgresql比mysql要更好更强大。可生态环境太差了,无奈,最近要把一个小站转成mysql数据库。小站主要表数据110万,pg_dump备份下载的压缩数据库将近3G。怎么转成mysql呢?尝试1:我首先尝试了工具,结果只找到两款国外的工具( PostgresToMysql和 DBConvert for MySQL & Postgre...

Jackson_拾 -.-的博客-程序员宅基地

Jackson常用注解属性命名@JsonProperty注解指定一个属性用于JSON映射,默认情况下映射的JSON属性与注解的属性名称相同,不过可以使用该注解的value值修改JSON属性名,该注解还有一个index属性指定生成JSON属性的顺序,如果有必要的话。属性包含还有一些注解可以管理在映射JSON的时候包含或排除某些属性,下面介绍一下常用的几个。@JsonIgnore注解用于排除某个属性,这样该属性就不会被Jackson序列化和反序列化。@JsonIgnoreProperties注解是类注解

[问题已处理]-服务器支持中文但是java日志输出中文乱码_爷来辣的博客-程序员宅基地_java 日志中文乱码

导语:服务器支持中文但是java日志输出中文乱码。最后解决的办法。app04上中文正常app03上中文乱码 为???检查了2台服务器的lang参数 系统版本 jar包启动方式都是一样 但是就是有问题# 测试服务器是不是中文乱码 echo "哈哈" > 哈哈;cat 哈哈# 或者date输出看一下是否乱码 如果你设置的是中文的话会显示如下# 2020年 12月 01日 星期二 13:49:47 CST发现服务器上输出中文没问题 那就是jar包的问题# 使用UTF-8 另一台

python 操作ps脚本_python – 有没有办法以编程方式获得ps输出?_陈晓卿的博客-程序员宅基地

我有一个网络服务器,我目前正在对CPU使用情况进行基准测试.我正在做的实际上是运行一个进程来请求服务器请求,然后运行以下bash脚本来确定CPU使用情况:#! /bin/bashfor (( ;; ))doecho "`python -c 'import time; print time.time()'`, `ps -p $1 -o '%cpu' | grep -vi '%CPU'`"sleep ...

分布式系统概述_zhongbiaoguo的博客-程序员宅基地_分布式系统 综述

分布式系统分布式系统概述:分布式系统是在同一个网络下,不同的组件通过网络进行通信和协调,表现如一个系统的系统。简单来说,一个分布式系统是一组计算机系统一起工作,在终端用户看来,就像一台计算机在工作一样。分布式是相对中心化而来,强调的是任务在多个物理隔离的节点上进行。中心化带来的主要问题是可靠性,若中心节点宕机则整个系统不可用,分布式除了解决部分中心化问题,也倾向于分散负载,但分布式会带来很多的其他问题,最主要的就是一致性。微服务架构概述:微服务架构:把一个大型的单个应用程序和服务

php学生宿舍管理系统免费,PHP学生宿舍管理系统_周思益的博客-程序员宅基地

因为学生大多数都是出外求学所以宿舍就是学生的第二个家,作为教务一定要管理好学生的宿舍信息。但是传统的宿舍管理模式已经逐渐的被淘汰。人们需要。一种新的模式来的宿舍信息进行管理,那就是信息化宿舍管理系统。PHP学生宿舍管理系统通过PHP+MySQL进行开发,系统主要分为管理员,协管员,教师和学生4中用户角色用户登录界面在登陆录界面,我们分为管理员,协管员,教师和学生四部分。多用户输入对应的用户名和密...

随便推点

Server 2008 R2部署active directory服务器-ad域_weixin_34127717的博客-程序员宅基地

一、部署AD域:系统环境:Windows server 2008 R2 标准版 ip:172.16.1.149服务器开机后会自动弹出一个初始配置任务窗口(这个不要随便关闭,因为我不知道关闭后能在哪里打开)选择添加角色,选中Active Directory域服务,接下来就默认安装好了。 接下来还要进行配置 ctrl+r 运行dcpromo 前面默认,后面选中在新林中新建域,点...

php layui文件批量上传,layui+php多文件上传_miss废柴的博客-程序员宅基地

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个不可多得的自学网站,送给正要提升自己的人,一生受益!_Rank92的博客-程序员宅基地

你还在每天庸庸碌碌的过日子吗?你不想改变下自己吗?曾近有句话说过:"知识很宝贵,就好像是金矿。学好知识,掌握好本领,会对我终身有益"。其实网上有很多一系列的教学视频网站,我们只要坚持下去,成功可能就在下一个街角等着你!那么今天小编在这里为大家整理了珍藏7个不可多得的自学网站,送给正要提升自己的人,一生受益!1、 我爱自学网这是一款涵盖了各类软件教程、程序设计以及吉他教程的综合性网站网站里面...

三星s7e港版linux,SAMSUNG 三星 S7 edge 港版简单使用感受_weixin_39926540的博客-程序员宅基地

SAMSUNG 三星 S7 edge 港版简单使用感受2016-03-16 17:50:0050点赞37收藏97评论三星2016年新出的旗舰机型S7 edge购买火热,上周去香港预订,仔细对比了三种颜色:金色、银色、黑色。个人感受黑色宣传照尤其好看,但真机中庸。银色很亮眼,有反光,所以指纹看上去更明显。倒是金色,并不是那种土豪金,更像是古铜金,所以整机质感很不错。因为金色在网上倍受诟病冷落,优惠6...

mysql connector getstring出错_MySQL Connector C++ - 生成错误1_weixin_39811036的博客-程序员宅基地

我正在用C++编写应用程序(使用带有Linux GCC的Eclipse),它应该与我的MySQL服务器进行交互。我已经下载了MySQL Connector C++ a,预编译并将文件复制到目录中(/ usr/lib,/ usr/include)。我在Eclipse的Project Properties(“mysqlcppconn”)的GCC C++链接器部分中引用了它。我的代码直接来自MySQL参...

java 判断链表环_[笔试面试]单链表如何检测有环,环入口,环长,环前长度——快慢指针法(百度JAVA面试)..._恶少恶言的博客-程序员宅基地

问题描述:在单向链表中,每个结点都包含一个指向下一个结点的指针,最后一个结点的这个指针被设置为空。但如果把最后一个结点的指针指向链表中存在的某个结点,就会形成一个环,在顺序遍历链表的时候,程序就会陷入死循环。问题:如何检测一个链表中是否有环,如果检测到环,如何确定环的入口点(即求出环长,环前面的链长)。一种比较耗空间的做法是,从头开始遍历链表,把每次访问到的结点(或其地址)存入一个集合(hashs...

推荐文章

热门文章

相关标签