21.Linux-写USB键盘驱动(详解)
本节目的:
根据上节写的USB鼠标驱动,来依葫芦画瓢写出键盘驱动
1.首先我们通过上节的代码中修改,来打印下键盘驱动的数据到底是怎样的
先来回忆下,我们之前写的鼠标驱动的id_table是这样:
所以我们要修改id_table,使这个驱动为键盘的驱动,如下图所示:
然后修改中断函数,通过printk()打印数据:
我们先按下按键A为例,打印出0x04,如下图:
我们再同时按下按键A和S,打印出0x04,0X16, 如下图:
显然这些普通按键都是从buf[2]开始的,那第一个数组到底又存什么值?
我们按完所有键盘按键,发现只有8个按键会打印在buf[0]里,如下图所示:
所以buf[0]是用来保存键盘的特定功能的键,而buf[1]可能是个保留键,没有用到的,buf[2]~buf[7]是普通按键,比如ABCD,1234,F1,F2等等,能支持最多6个按键同时按下。
2.那么每个按键的数据又是怎么定义的?
2.1比如我们按下按键A,为什么打印0X04?
我们找到输入子系统(input.h)中按键A定义的值,它对应的却是30,看来不是直接调用的,如下图:
我们再来参考内核自带的USB键盘驱动 (/drivers/hid/usbhid/usbkbd.c)
发现它的中断函数中有个键盘描述码表(其中0表示保留的意思):
发现该数组的0X04就是0X30,看来要写个键盘驱动,还需要上面的数组才行.
那么问题又来了,如果我们按下左alt键,buf[0]中会出现0x04,如果也代入到键盘描述码表中,显然就会当作键盘按键A来使用。
2.2我们来分析内核的键盘中断函数是如何处理的:
发现有这么一句:
for (i = ; i < ; i++) input_report_key(kbd->dev, usb_kbd_keycode[i+ ], (kbd->new[] >> i) & );
其中kbd->new表示的就是键盘数据数组,它将buf[0]的每一位通通以usb_kbd_keycode[i+ 224]的形式上传到按键事件中
显然我们的buf[0]的0X04就是上传的usb_kbd_keycode[4+ 224]
2.3我们来看看usb_kbd_keycode[226]里的数据对应的到底是不是左ALT键
找到usb_kbd_keycode[226]=56:
然后再进入input.h,找到56的定义,刚好就是KEY_LEFTALT(左边的alt键)
3.接下来再来仔细分析下内核自带的USB键盘驱动usbkbd.c里的中断函数:
代码如下:
static void usb_kbd_irq(struct urb *urb)
{
struct usb_kbd *kbd = urb->context;
int i;
switch (urb->status) { // 只有urb->status==0时,说明数据传输成功
case : /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
} for (i = ; i < ; i++) //上传crtl、shift、atl、windows 等按键
input_report_key(kbd->dev, usb_kbd_keycode[i + ], (kbd->new[] >> i) & ); for (i = ; i < ; i++) { //上传普通按键
/*通过上个状态的按键数据kbd->old[i]的非0值,来查找当前状态的按键数据,若没有找到,说明已经松开了该按键 */
if (kbd->old[i] > && memscan(kbd->new + , kbd->old[i], ) == kbd->new + ) {
if (usb_kbd_keycode[kbd->old[i]]) //再次判断键盘描述码表的值是否非0
input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], ); //上传松开事件
else
info("Unknown key (scancode %#x) released.", kbd->old[i]);
} /*通过当前状态的按键数据kbd->new[i]的非0值,来查找上个状态的按键数据,若没有找到,说明已经按下了该按键 */
if (kbd->new[i] > && memscan(kbd->old + , kbd->new[i], ) == kbd->old + ) {
if (usb_kbd_keycode[kbd->new[i]]) //再次判断键盘描述码表的值是否非0
input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], ); //上传按下事件
else
info("Unknown key (scancode %#x) pressed.", kbd->new[i]);
}
} input_sync(kbd->dev);
memcpy(kbd->old, kbd->new, ); //更新上个状态值
resubmit:
i = usb_submit_urb (urb, GFP_ATOMIC);
if (i)
err ("can't resubmit intr, %s-%s/input0, status %d",
kbd->usbdev->bus->bus_name,
kbd->usbdev->devpath, i);
}
3.1上面获取普通按键时,为什么不直接判断非0,要判断按键数据> 3?
之前我们就分析了,当按键数据=0X01、0X02时,代表的是特定功能的键(crtl、shift),是属于buf[0]的数据
其中memscan()是用来匹配上次按键和当前按键的数据,它这么做的原因是怕上个buf[]和当前buf[]的数据错位,这里就不做详细分析了
一切迎刃而解,我们只需要将自己的代码也通过这个码表添加所有按键按键事件,然后再在键盘中断函数中根据数据来上传事件即可
4.本节键盘代码如下:
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h> static struct input_dev *myusb_kbd_dev; //input_dev
static unsigned char *myusb_kbd_buf; //虚拟地址缓存区
static dma_addr_t myusb_kbd_phyc; //DMA缓存区; static __le16 myusb_kbd_size; //数据包长度
static struct urb *myusb_kbd_urb; //urb static const unsigned char usb_kbd_keycode[] = {
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , ,,,,,,,,,
,,, , , , , , , , , , , , , ,
, , , , ,,,,,,,,,,,,
,,,,,,,,,,,,,,,,
,, , , ,, , , ,, , , , , , ,
,, , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , ,, , ,,,,,,,,,,,
,,,,,,,,,,,
}; //键盘码表共有252个数据 void my_memcpy(unsigned char *dest,unsigned char *src,int len) //复制缓存
{
while(len--)
{
*dest++= *src++;
}
} static void myusb_kbd_irq(struct urb *urb) //键盘中断函数
{
static unsigned char buf1[]={,,,,,,,};
int i; /*上传crtl、shift、atl、windows 等按键*/
for (i = ; i < ; i++)
if(((myusb_kbd_buf[]>>i)&)!=((buf1[]>>i)&))
{
input_report_key(myusb_kbd_dev, usb_kbd_keycode[i + ], (myusb_kbd_buf[]>> i) & );
input_sync(myusb_kbd_dev); //上传同步事件
} /*上传普通按键*/
for(i=;i<;i++)
if(myusb_kbd_buf[i]!=buf1[i])
{
if(myusb_kbd_buf[i] ) //按下事件
input_report_key(myusb_kbd_dev,usb_kbd_keycode[myusb_kbd_buf[i]], );
else if(buf1[i]) //松开事件
input_report_key(myusb_kbd_dev,usb_kbd_keycode[buf1[i]], );
input_sync(myusb_kbd_dev); //上传同步事件
} my_memcpy(buf1, myusb_kbd_buf, ); //更新数据
usb_submit_urb(myusb_kbd_urb, GFP_KERNEL);
} static int myusb_kbd_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
volatile unsigned char i;
struct usb_device *dev = interface_to_usbdev(intf); //设备
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *interface; //当前接口
int pipe; //端点管道
interface=intf->cur_altsetting;
endpoint = &interface->endpoint[].desc; //当前接口下的端点描述符
printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct); /* 1)分配一个input_dev结构体 */
myusb_kbd_dev=input_allocate_device(); /* 2)设置input_dev支持 按键事件*/
set_bit(EV_KEY, myusb_kbd_dev->evbit);
set_bit(EV_REP, myusb_kbd_dev->evbit); //支持重复按功能 for (i = ; i < ; i++)
set_bit(usb_kbd_keycode[i], myusb_kbd_dev->keybit); //添加所有键
clear_bit(, myusb_kbd_dev->keybit); /* 3)注册input_dev结构体*/
input_register_device(myusb_kbd_dev); /* 4)设置USB键盘数据传输 */
/*->4.1)通过usb_rcvintpipe()创建一个端点管道*/
pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress); /*->4.2)通过usb_buffer_alloc()申请USB缓冲区*/
myusb_kbd_size=endpoint->wMaxPacketSize;
myusb_kbd_buf=usb_buffer_alloc(dev,myusb_kbd_size,GFP_ATOMIC,&myusb_kbd_phyc); /*->4.3)通过usb_alloc_urb()和usb_fill_int_urb()申请并初始化urb结构体 */
myusb_kbd_urb=usb_alloc_urb(,GFP_KERNEL);
usb_fill_int_urb (myusb_kbd_urb, //urb结构体
dev, //usb设备
pipe, //端点管道
myusb_kbd_buf, //缓存区地址
myusb_kbd_size, //数据长度
myusb_kbd_irq, //中断函数
,
endpoint->bInterval); //中断间隔时间 /*->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址*/
myusb_kbd_urb->transfer_dma =myusb_kbd_phyc; //设置DMA地址
myusb_kbd_urb->transfer_flags =URB_NO_TRANSFER_DMA_MAP; //设置使用DMA地址 /*->4.5)使用usb_submit_urb()提交urb*/
usb_submit_urb(myusb_kbd_urb, GFP_KERNEL);
return ;
} static void myusb_kbd_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf); //设备
usb_kill_urb(myusb_kbd_urb);
usb_free_urb(myusb_kbd_urb);
usb_buffer_free(dev, myusb_kbd_size, myusb_kbd_buf,myusb_kbd_phyc);
input_unregister_device(myusb_kbd_dev); //注销内核中的input_dev
input_free_device(myusb_kbd_dev); //释放input_dev
} static struct usb_device_id myusb_kbd_id_table [] = {
{ USB_INTERFACE_INFO(
USB_INTERFACE_CLASS_HID, //接口类:hid类
USB_INTERFACE_SUBCLASS_BOOT, //子类:启动设备类
USB_INTERFACE_PROTOCOL_KEYBOARD) }, //USB协议:键盘协议
}; static struct usb_driver myusb_kbd_drv = {
.name = "myusb_kbd",
.probe = myusb_kbd_probe,
.disconnect = myusb_kbd_disconnect,
.id_table = myusb_kbd_id_table,
}; /*入口函数*/
static int myusb_kbd_init(void)
{
usb_register(&myusb_kbd_drv);
return ;
} /*出口函数*/
static void myusb_kbd_exit(void)
{
usb_deregister(&myusb_kbd_drv);
} module_init(myusb_kbd_init);
module_exit(myusb_kbd_exit);
MODULE_LICENSE("GPL");
5.测试运行
5.1 重新设置编译内核(去掉默认的hid_USB驱动)
make menuconfig ,进入menu菜单重新设置内核参数:
进入-> Device Drivers -> HID Devices
<> USB Human Interface Device (full HID) support //hid:人机交互的USB驱动,比如鼠标,键盘等
然后make uImage 编译内核
将新的键盘驱动模块放入nfs文件系统目录中
5.2然后烧写内核,装载触摸屏驱动模块
如下图,当我们插上USB键盘时,可以看到该VID和PID,和电脑上的键盘的参数一样
5.3使用cat tty1进程测试
5.4 使用exec 0</dev/tty1测试
(exec命令详解入口地址: http://www.cnblogs.com/lifexy/p/7553228.html)
如下图,就能通过板子上的键盘来操作了
下章学习:
22.Linux-块设备驱动之框架详细分析(详解)
21.Linux-写USB键盘驱动(详解)的更多相关文章
- Linux下usb设备驱动详解
USB驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已经做好了,我们可以不管,我们只需要了解它的功能.形象的说,USB的bus驱动相当于铺出一条路来,让所有的信息都可以通过这条USB ...
- 13.Linux键盘驱动 (详解)
版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一节分析输入子系统内的intput_handler软件处理部分后,接下来我们开始写input_dev驱动 本节目标: 实现键盘驱动,让开发板的 ...
- 16.Linux-LCD驱动(详解)
在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置fb_info 3) 设置硬件相关的操作 ...
- 16.Linux-LCD驱动(详解)【转】
转自:https://www.cnblogs.com/lifexy/p/7604011.html 在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构 ...
- linux usb 驱动详解
linux usb 驱动详解 USB 设备驱动代码通过urb和所有的 USB 设备通讯.urb用 struct urb 结构描述(include/linux/usb.h ). urb 以一种异步的方式 ...
- Linux USB 鼠标驱动程序详解(转)
Linux USB 鼠标驱动程序详解 USB 总线引出两个重要的链表!一个 USB 总线引出两个重要的链表,一个为 USB 设备链表,一个为 USB 驱动链表.设备链表包含各种系统中的 USB 设备以 ...
- 25.Linux-Nor Flash驱动(详解)
1.nor硬件介绍: 从原理图中我们能看到NOR FLASH有地址线,有数据线,它和我们的SDRAM接口相似,能直接读取数据,但是不能像SDRAM直接写入数据,需要有命令才行 1.1其中我们2440的 ...
- Linux文件权限与属性详解 之 一般权限
目录 一般属性 1. iNode: 3152621 2. 文件类型 3.文件访问权限 4. 链接数目: 5. 文件所有者 6. 文件所属组 7. 文件大小 8. 修改时间 9. 文件名称 Linux文 ...
- Linux文件权限与属性详解 之 su & sudo
Linux文件权限与属性详解 之 一般权限 Linux文件权限与属性详解 之 ACL Linux文件权限与属性详解 之 SUID.SGID & SBIT Linux文件权限与属性详解 之 ch ...
随机推荐
- JSON、数组、时间戳
// on removeClass addClass slice $('.pic-subgroup').on('click',function(){ $('.pic_div.active').remo ...
- C#构建DataTable(转)
Asp.net DataTable添加列和行的方法 方法一: DataTable tblDatas = new DataTable("Datas"); DataColumn dc ...
- java.lang.ClassNotFoundException: com.sun.jna.Native
在使用sprng boot加elasticsearch的时候遇到 java.lang.ClassNotFoundException: com.sun.jna.Native,百度之后要我导入 <d ...
- 转:【Java集合源码剖析】Java集合框架
转载轻注明出处:http://blog.csdn.net/ns_code/article/details/35564663 Java集合工具包位于Java.util包下,包含了很多常用的数据结构, ...
- 如何将RDS的数据同步到本地自建数据库
长期以来有很多的用户咨询如何将RDS的数据同步到本地的数据库环境中,本篇文章以在阿里云的ECS服务器为例来说明如何将RDS的数据同步到本地数据库中.RDS对外提供服务是一个DNS地址+端口3306,这 ...
- 团队作业10——复审和事后分析(Beta版本)
团队作业10--事后分析(Beta版本) http://www.cnblogs.com/newteam6/p/6953992.html 团队作业10--复审(Beta版本) http://www.cn ...
- 201521123017 《Java程序设计》第8周学习总结
1. 本周学习总结 2. 书面作业 Q1.List中指定元素的删除(题目4-1) 1.1 实验总结 for (int i = list.size()-1; i >=0; i--) {//从最后一 ...
- 201521123060《Java程序设计》第2周学习总结
1. 本周学习总结 a.进一步熟悉了Eclipse的使用和java程序的编写: b.学习了java数据的基本类型:整数类型,浮点类型等: c.学习了算数运算符,赋值运算符,位运算符,关系运算符,逻辑运 ...
- 201521044091 《Java程序设计》第2周学习总结
1本章学习总结 (1)一些java的基本语法 (2)java API文件 (3)使用码云管理自己的代码 2.Java Q&A 1)使用Eclipse关联jdk源代码(截图),并查看String ...
- 201521123075 《Java程序设计》第1周学习总结
1. 本周学习总结 (1)Java不仅是程序语言,还是一种标准规范,代表着解决问题的方案.Java是一个面向对象的编程语言,熟悉后相对于c++更方便,其一大特色就是能够跨平台运行. (2)Java发展 ...