注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

LINUX驱动学习——内核USB驱动编写七USB键盘1  

2013-01-06 21:14:28|  分类: USB系列 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
 

LINUX驱动学习——内核USB驱动编写七USB键盘1

说明一下,这个键盘的驱动是从网上转来的,但有些疑问自己在这里重新注释清楚了,所以写下来,让大家看一下

1.   指定USB 键盘驱动所需的头文件:

#include <linux/kernel.h>/*内核头文件,含有内核一些常用函数的原型定义*/

#include <linux/slab.h>/*定义内存分配的一些函数*/

#include <linux/module.h>/*模块编译必须的头文件*/

#include <linux/input.h>/*输入设备相关函数的头文件*/

#include <linux/init.h>/*linux初始化模块函数定义*/

#include <linux/usb.h> /*USB设备相关函数定义*/

2.   定义键盘码表数组:

/*使用第一套键盘扫描码表:A-1E;B-30;C-2E…*/

static unsigned char usb_kbd_keycode[256] = {

      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

};

3.   编写设备ID表:

static struct usb_device_id usb_kbd_id_table [] = {

    { USB_INTERFACE_INFO(3, 1, 1) }, /*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘

接口类;鼠标为3,1,2*/

    { }        /* Terminating entry */

};

MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);/*指定设备 ID 表*/

4.   定义USB 键盘结构体:

struct usb_kbd {

   struct input_dev *dev; /*定义一个输入设备*/

   struct usb_device *usbdev;/*定义一个usb设备*/

   unsigned char old[8]; /*按键离开时所用之数据缓冲区*/

   struct urb *irq/*usb键盘之中断请求块*/, *led/*usb键盘之指示灯请求块*/;

   unsigned char newleds;/*目标指定灯状态*/

   char name[128];/*存放厂商名字及产品名字*/

   char phys[64];/*设备之节点*/

 

 

 

   unsigned char *new;/*按键按下时所用之数据缓冲区*/

   struct usb_ctrlrequest *cr;/*控制请求结构*/

   unsigned char *leds;/*当前指示灯状态*/

    dma_addr_t cr_dma; /*控制请求DMA缓冲地址*/

    dma_addr_t new_dma;  /*中断urb会使用该DMA缓冲区*/

    dma_addr_t leds_dma; /*指示灯DAM缓冲地址*/

};

5.   编写USB 键盘驱动结构(任何一个LINUX下的驱动都有个类似的驱动结构):

/*USB键盘驱动结构体*/

static struct usb_driver usb_kbd_driver = {

    .name =    "usbkbd",/*驱动名字*/

    .probe =  usb_kbd_probe,/*驱动探测函数,加载时用到*/

    .disconnect = usb_kbd_disconnect,/*驱动断开函数,在卸载时用到*/

    .id_table =  usb_kbd_id_table,/*驱动设备ID表,用来指定设备或接口*/

};

6.   编写模块加载函数(每个驱动都会有一个加载函数,由module_init 调用):

/*驱动程序生命周期的开始点,向 USB core 注册这个键盘驱动程序。*/

static int __init usb_kbd_init(void)

{

   int result = usb_register(&usb_kbd_driver);/*注册USB键盘驱动*/

   if (result == 0) /*注册失败*/

      info(DRIVER_VERSION ":" DRIVER_DESC);

   return result;

}

7.   编写模块卸载函数(每个驱动都会有一个卸载函数,由module_exit 调用):

/* 驱动程序生命周期的结束点,向 USB core 注销这个键盘驱动程序。 */

static void __exit usb_kbd_exit(void)

{

    printk("SUNWILL-USBKBD:usb_kbd_exit begin...\n");

    usb_deregister(&usb_kbd_driver); /*注销USB键盘驱动*/

}

8.   指定模块初始化函数(被指定的函数在insmod 驱动时调用):

module_init(usb_kbd_init);

9.   指定模块退出函数(被指定的函数在rmmod 驱动时调用):

module_exit(usb_kbd_exit);

10.  编写中断请求处理函数:

/*中断请求处理函数,有中断请求到达时调用该函数*/

static void usb_kbd_irq(struct urb *urb, struct pt_regs *regs)

{

   struct usb_kbd *kbd = urb->context;

   int i;

   switch (urb->status) {

   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++)/*8次的值依次是:29-42-56-125-97-54-100-126*/

  {

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

  }

  

/*若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下*/

   for (i = 2; i < 8; i++) {

   /*获取键盘离开的中断*/

说明:

比如发过来数据new[5]=0x1e(按键1),则在i=5时, 在new[2]至new[7]中寻找和old[5]相等的字符,如果相等返回new中相等字符的地址;否则返回new的最后一个元素的下一个地址,实际就是new+8。所以若没找到, 则memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8成立  总之,此if做了:检查old[i]值的hid规范合法性,并和新出现的按键值即new[2]--new[7]一一比较,如果新出现的按键值没有old[i],说明old[i]已经被释放了,所以执行以下操作 如果不是新的释放事件,而是原来的键一直处于释放状态,则不会执行下面的操作,说明只会在按键释放瞬间发生按键释放上报事件,而在未按期间不会上报释放事件。

   if (kbd->old[i] > 3 && memscan(kbd-> new + 2, kbd->old[i], 6) == kbd-> new + 8) {/*

同时没有该KEY的按下状态*/

    if (usb_kbd_keycode[kbd->old[i]])

    {

      input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);

    }

    else

      info("Unknown key (scancode %#x) released.", kbd->old[i]);

   }

 

   /*获取键盘按下的中断*/  

这种情况发生在原来释放的键按下时。 

此if非彼if,它做了:检查new[i]值的hid规范合法性,并和old[2]--old[7]比较,如果没有一个值和new[i]相等,说明new[i]是新按 下的按键,所以执行下面的操作 如果不是新键按下,而是原来的按键一直没有释放,则不会执行下面的操作,说明只会在按键按下时上报按键事件,而按着过程中,不会上报按键事件。 

   if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {/*

同时没有该KEY的离开状态*/

    if (usb_kbd_keycode[kbd->new[i]])

    {

      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);/*发送USB请求块*/

   if (i)

      err ("can't resubmit intr, %s-%s/input0, status %d",

    kbd->usbdev->bus->bus_name,

    kbd->usbdev->devpath, i);

}

1、这里有几个疑问,需要重点说明一下:

new是kbd的成员,在usb_kbd_alloc_mem函数中分配8字节内存:kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma), 

并且在中断urb初始化函数中usb_fill_int_urb(kbd->irq, dev, pipe, kbd->new, (maxp > 8 ? 8 : maxp), usb_kbd_irq, kbd, endpoint->bInterval); 

指定new为urb的缓冲区,即从单片机端点里取得的数据会放在new指向的内存里面,最大为8字节 

根据hid规范,从单片机获取的数据的首字节即new[0],其8位分别表示 

      new[0] -- 

      |--bit0: Left Control是否按下,按下为1 

      |--bit1: Left Shift 是否按下,按下为1 

      |--bit2: Left Alt 是否按下,按下为1 

      |--bit3: Left GUI 是否按下,按下为1 

      |--bit4: Right Control是否按下,按下为1 

      |--bit5: Right Shift 是否按下,按下为1 

      |--bit6: Right Alt 是否按下,按下为1 

      |--bit7: Right GUI 是否按下,按下为1 

(kbd->new[0] >> i) & 1 检测第i位是否为1,如果为1,则上报按键usb_kbd_keycode[i + 224] 

例如,若Left Control键按下,则在首次循环中,(kbd->new[0] >> 0) & 1=1,所以上报usb_kbd_keycode[0 + 224],查上面usb_kbd_keycode数组可知, 

usb_kbd_keycode[224]=29,即上报键值29 

至于29到底是不是代表此键,可以查看输入子系统的键值定义,http://blog.csdn.net/songqqnew/article/details/6875502,发现有行  

 #define KEY_LEFTCTRL        29     

其他类似... 

 

可见通过input_report_key函数关联了3个按键表,总结一下 

1.hid规范的键盘码:本页可查到。是设备和主机间的约定,比如上面的例子,板子上有Left Control键按下,单片机就要按hid规范令new[0].0=1, 

主机获取到new[0].0=1则就会知道Left control键按下。又如对于鼠标按键,板子中键按下,单片机要按hid规范<SPAN></SPAN>令data[0]=0x04,主机获取到data[0]=0x04,便知道鼠标的左键按下。当然,鼠标hid和键盘hid的数据格式和含义不一样。 

2.input.h里的键盘码:http://blog.csdn.net/songqqnew/article/details/6875502。是input子系统里规定的某个按键按下要上报哪个数值。应该是input子系统和应用程序间的一个约定。 

3.usb_kbd_keycode[256]:本页可查到。此数组为了操作方便,用数组下标和对应成员值将hid规范码和input规范码统一起来,对于某个按键 

有usb_kbd_keycode[hid规范码]=input规范码 

比如按键a:usb_kbd_keycode[4]=30,按键1:usb_kbd_keycode[0x1e]=2 

2、为什么从[2]即第三个开始有效:

      /*键盘发送给PC的数据每次8个字节 

      data0 data1 data2 data3 data4 data5 data6 data7  

      定义分别是: 

      data0 -- 

      |--bit0: Left Control是否按下,按下为1 

      |--bit1: Left Shift 是否按下,按下为1 

      |--bit2: Left Alt 是否按下,按下为1 

      |--bit3: Left GUI 是否按下,按下为1 

      |--bit4: Right Control是否按下,按下为1 

      |--bit5: Right Shift 是否按下,按下为1 

      |--bit6: Right Alt 是否按下,按下为1 

      |--bit7: Right GUI 是否按下,按下为1 

      data1 -- 保留 

      data2--data7 -- 普通按键 

refer to hid spec 8.3*/  

3、为什么struct usb_kbd *kbd = urb->context;

在probe函数中, usb_fill_int_urb(kbd->irq, dev, pipe,

                             kbd->new, (maxp > 8 ? 8 : maxp),

                             usb_kbd_irq, kbd, endpoint->bInterval);

而其原型为:

static inline void usb_fill_int_urb(struct urb *urb,

                                         struct usb_device *dev,

                                         unsigned int pipe,

                                         void *transfer_buffer,

                                         int buffer_length,

                                         usb_complete_t complete_fn,

                                         void *context,

                                         int interval)

{

         urb->dev = dev;

         urb->pipe = pipe;

         urb->transfer_buffer = transfer_buffer;

         urb->transfer_buffer_length = buffer_length;

         urb->complete = complete_fn;

         urb->context = context;

         if (dev->speed == USB_SPEED_HIGH)

                   urb->interval = 1 << (interval - 1);

         else

                   urb->interval = interval;

         urb->start_frame = -1;

}

红色代码部分,明白了吧。

 

4、为何扫描时要>3:

这种情况发生在原来按下的键释放时。 至于>3,可以看hid规范码:有意义的hid规范码从4(字符a)开始。 数组old[]是new[]的一个拷贝,size 8,见最后一行。

其实很多东西网上的大牛们都走过并写下经过的困难,只要努力看看他们的博客,认真思考就行了。

君子性非异也,善假于物也。

  评论这张
 
阅读(1260)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017