本节目的:

    根据上节写的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 = 0; i < 8; i++)

       input_report_key(kbd->dev, usb_kbd_keycode[i+ 224], (kbd->new[0] >> i) & 1);

 

其中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 0:                  /* success */
              break;
       case -ECONNRESET:     /* unlink */
       case -ENOENT:
       case -ESHUTDOWN:
              return;
       /* -EPIPE:  should clear the halt */
       default:          /* error */
              goto resubmit;
       }

       for (i = 0; i < 8; i++)                           //上传crtl、shift、atl、windows 等按键
              input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);

       for (i = 2; i < 8; i++) {                  //上传普通按键
              /*通过上个状态的按键数据kbd->old[i]的非0值,来查找当前状态的按键数据,若没有找到,说明已经松开了该按键 */
          if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
              if (usb_kbd_keycode[kbd->old[i]])              //再次判断键盘描述码表的值是否非0
                input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); //上传松开事件
               else
                info("Unknown key (scancode %#x) released.", kbd->old[i]);
              }

      /*通过当前状态的按键数据kbd->new[i]的非0值,来查找上个状态的按键数据,若没有找到,说明已经按下了该按键 */
         if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
             if (usb_kbd_keycode[kbd->new[i]]) //再次判断键盘描述码表的值是否非0
                input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);      //上传按下事件
             else
                info("Unknown key (scancode %#x) pressed.", kbd->new[i]);
              }
       }

       input_sync(kbd->dev); 
       memcpy(kbd->old, kbd->new, 8);                     //更新上个状态值 
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] = {
         0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
        50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
         4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
        27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
        65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
       105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
        72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
       191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
       115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
       122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
       150,158,159,128,136,177,178,176,142,152,173,140
};       //键盘码表共有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[8]={0,0,0,0,0,0,0,0};
   int i;

      /*上传crtl、shift、atl、windows 等按键*/
     for (i = 0; i < 8; i++)
     if(((myusb_kbd_buf[0]>>i)&1)!=((buf1[0]>>i)&1))
     {    
             input_report_key(myusb_kbd_dev, usb_kbd_keycode[i + 224], (myusb_kbd_buf[0]>> i) & 1);
             input_sync(myusb_kbd_dev);             //上传同步事件
      }


     /*上传普通按键*/
    for(i=2;i<8;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]], 1);   
    else  if(buf1[i])                                             //松开事件
    input_report_key(myusb_kbd_dev,usb_kbd_keycode[buf1[i]], 0);
    input_sync(myusb_kbd_dev);             //上传同步事件
    }

  my_memcpy(buf1, myusb_kbd_buf, 8);       //更新数据    
  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[0].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 = 0; i < 252; i++)
       set_bit(usb_kbd_keycode[i], myusb_kbd_dev->keybit);     //添加所有键
       clear_bit(0, 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(0,GFP_KERNEL);
       usb_fill_int_urb (myusb_kbd_urb,              //urb结构体
                                 dev,                                       //usb设备
                                 pipe,                                      //端点管道
                                 myusb_kbd_buf,               //缓存区地址
                                 myusb_kbd_size,              //数据长度
                                 myusb_kbd_irq,               //中断函数
                                 0,
                                 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 0;
}

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 0;
}
 
/*出口函数*/
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-块设备驱动之框架详细分析(详解)

转载请注明出处:http://www.zhongtian365.com/article/20230516/813437.html

随机推荐

  1. 21分钟学会写编译器

      知乎上有一种说法是「编译器、图形学、操作系统是程序员的三大浪漫」。   先不管这个说法是对是错,我们假设一个程序员在国内互联网公司写代码,业余时间不看相关书籍。那么三年之后,他的这些知识会比在校时损耗多少? 很显然,损耗的比例肯定非...

  2. 21寸宽屏液晶显示器的最佳分辨率调为多少合适

    很多人对显示器的分辨率多少并不是很在意,其实不然,显示器的分辨率是否合适可以影响到图像的展现,每一款尺寸的显示器分辨率都有所不同,过大或过小都会影响图像显示。生活中21寸显示器是较受大众的显示器尺寸,那么21寸显示器最佳分辨率设置为多少合...

  3. 21 的用法说明

    http://blog.sina.com.cn/s/blog_5842daa30101enz5.html经常关注linux脚本的人,一定看到过 21 这样的用法,最初一定不明白其中的含义以及为什么是这样的一种组合。昨天偶然间再次看到了这个...

  4. Linux shell中21的含义

    1、关于21的含义 (1)含义:将标准错误输出重定向到标准输出。(2)符号是一个整体,不可分开,分开后就不是上述含义了。  比如有些人可能会这么想:2是标准错误输入,1是标准输出,是重定向符号,那么"将标准错误输出重定向到标准输出"是不是...

  5. 21岁时的梦

    21岁时的梦: java精通,学透狂神,及黑马程序中后面的项目或者叫实战 拥有计算机二级C证书 拥有英语A/B级——英语四级证书 一个好的身体,熬夜不可避免,那就尽量练功和打坐吧 爱爸爸妈妈和姐姐,爱我的亲人,不要任性 每晚看一个短文,...

  6. 21个在网页设计的精美图标使用灵感

    图标是一个有吸引力的和有效的网站的一个非常重要的元素。不要紧,如果你使用的是从免费包的图标,或您自己的今天,我们收集的图标使用的几个例子,让您的想法浮动。从手工绘制的图标,你会看到,简单或彩色,漂亮的图标,可以使界面更引人入胜。 S...

  7. 21、Shuffle原理剖析与源码分析

    一、普通shuffle原理1、图解假设有一个节点上面运行了4个 ShuffleMapTask,然后这个节点上只有2个 cpu core。假如有另外一台节点,上面也运行了4个ResultTask,现在呢,正等着要去 ShuffleMapT...

  8. 21、如何解决Redis的并发竞争Key问题

    所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我 们期望的顺序不同,这样也就导致了结果的不同! 推荐一种方案:分布式锁(zookeeper 和 Redis 都可以实现分布式锁)...

  9. 【21天精听打卡 3/21】20211117 Is VR the new reality?

    20211117 BBC精听 Is VR the new reality?原文 Most people associate virtual reality with the world of gaming or entertainme...

  10. ?21 docker volume数据卷

    volume 数据卷 将宿主机的一个目录与容器的一个目录做映射 可以再宿主机中操作目录中的内容,那么容器内部映射的文件,也会跟着一起改变 创建数据卷 # 创建数据卷之后,默认会存放一个目录下 /var/lib/docker/volum...