linux3.4.2 之usb鼠标驱动,键盘驱动

目录

1  USB相关基本知识

2  USB鼠标编程指导

3 USB鼠标驱动程序完整源码

4  USB鼠标驱动测试

5  USB键盘基本知识

6 USB键盘驱动程序 

4  USB键盘驱动测试


1  USB相关基本知识

摘自博客:https://www.cnblogs.com/lifexy/p/7634511.html

  • 在USB描述符中,从上到下分为四个层次

USB设备描述符(usb_device_descriptor)、
USB配置描述符(usb_config_descriptor)、
USB接口描述符(usb_interface_descriptor)、
USB端点描述符(usb_endpoint_descriptor)、

一个设置描述符可以有多个配置描述符
一个配置描述符可以有多个接口描述符(比如声卡驱动,就有两个接口:录音接口和播放接口)
一个接口描述符可以有多个端点描述符

  • USB设备描述符结构体如下所示:
struct usb_device_descriptor {
 __u8  bLength;                          //本描述符的size
 __u8  bDescriptorType;              //描述符的类型,这里是设备描述符DEVICE
 __u16 bcdUSB;                           //指明usb的版本,比如usb2.0
 __u8  bDeviceClass;                 //类
 __u8  bDeviceSubClass;             //子类
 __u8  bDeviceProtocol;              //指定协议
 __u8  bMaxPacketSize0;            //端点0对应的最大包大小
 __u16 idVendor;                         //厂家ID
 __u16 idProduct;                        //产品ID
 __u16 bcdDevice;                       //设备的发布号
 __u8  iManufacturer;                 //字符串描述符中厂家ID的索引
 __u8  iProduct;                         //字符串描述符中产品ID的索引
 __u8  iSerialNumber;                 //字符串描述符中设备序列号的索引
 __u8  bNumConfigurations;              //配置描述符的个数,表示有多少个配置描述符
} __attribute__ ((packed));

USB设备描述符位于USB设备结构体usb_device中的成员descriptor中

同样地,配置、接口、端点描述符也是位于USB配置、接口、端点结构体中,不过这3个对于我们写驱动的不是很常用

  • usb_device结构体如下所示:
struct usb_device {
   int devnum;           //设备号,是在USB总线的地址
   char devpath [16];       //用于消息的设备ID字符串
   enum usb_device_state state; //设备状态:已配置、未连接等等
   enum usb_device_speed speed; //设备速度:高速、全速、低速或错误
  
   struct usb_tt *tt;       //处理传输者信息;用于低速、全速设备和高速HUB
   int ttport;           //位于tt HUB的设备口
  
   unsigned int toggle[2];    //每个端点的占一位,表明端点的方向([0] = IN, [1] = OUT)  
   struct usb_device *parent;  //上一级HUB指针
   struct usb_bus *bus;       //总线指针
   struct usb_host_endpoint ep0; //端点0数据
   struct device dev;         //一般的设备接口数据结构
 
   struct usb_device_descriptor descriptor; //USB设备描述符,
   struct usb_host_config *config;       //设备的所有配置结构体,配置结构体里包含了配置描述符
   struct usb_host_config *actconfig;     //被激活的设备配置
   struct usb_host_endpoint *ep_in[16];     //输入端点数组
   struct usb_host_endpoint *ep_out[16];     //输出端点数组
  
   char **rawdescriptors;             //每个配置的raw描述符
  
   unsigned short bus_mA;         //可使用的总线电流

   u8 portnum;               //父端口号
   u8 level;                //USB HUB的层数
  
   unsigned can_submit:1;         //URB可被提交标志
   unsigned discon_suspended:1;      //暂停时断开标志
   unsigned persist_enabled:1;       //USB_PERSIST使能标志
   unsigned have_langid:1;         //string_langid存在标志
   unsigned authorized:1; 
   unsigned authenticated:1;
   unsigned wusb:1;             //无线USB标志
   int string_langid;             //字符串语言ID
  
   /* static strings from the device */ //设备的静态字符串
   char *product;               //产品名
   char *manufacturer;             //厂商名
   char *serial;                 //产品串号
  
   struct list_head filelist;         //此设备打开的usbfs文件
  #ifdef CONFIG_USB_DEVICE_CLASS
   struct device *usb_classdev;       //用户空间访问的为usbfs设备创建的USB类设备
  #endif
  #ifdef CONFIG_USB_DEVICEFS
   struct dentry *usbfs_dentry;        //设备的usbfs入口
  #endif
  
   int maxchild;                     //(若为HUB)接口数
   struct usb_device *children[USB_MAXCHILDREN];//连接在这个HUB上的子设备
   int pm_usage_cnt;                 //自动挂起的使用计数
   u32 quirks; 
   atomic_t urbnum;                   //这个设备所提交的URB计数
  
   unsigned long active_duration;         //激活后使用计时

  #ifdef CONFIG_PM                 //电源管理相关
   struct delayed_work autosuspend;       //自动挂起的延时
   struct work_struct autoresume;       //(中断的)自动唤醒需求
   struct mutex pm_mutex;           //PM的互斥锁 
  
   unsigned long last_busy;         //最后使用的时间
   int autosuspend_delay; 
   unsigned long connect_time;       //第一次连接的时间
  
   unsigned auto_pm:1;           //自动挂起/唤醒
   unsigned do_remote_wakeup:1;     //远程唤醒
   unsigned reset_resume:1;       //使用复位替代唤醒
   unsigned autosuspend_disabled:1;   //挂起关闭
   unsigned autoresume_disabled:1;   //唤醒关闭
   unsigned skip_sys_resume:1;     //跳过下个系统唤醒
  #endif
   struct wusb_dev *wusb_dev;     //(如果为无线USB)连接到WUSB特定的数据结构
  };
  • 配置描述符结构如下所示:
struct usb_config_descriptor {   
  __u8  bLength;                          //描述符的长度
  __u8  bDescriptorType;              //描述符类型的编号

  __le16 wTotalLength;                        //配置 所返回的所有数据的大小
  __u8  bNumInterfaces;              //配置 所支持的接口个数, 表示有多少个接口描述符
  __u8  bConfigurationValue;        //Set_Configuration命令需要的参数值
  __u8  iConfiguration;                        //描述该配置的字符串的索引值
  __u8  bmAttributes;                         //供电模式的选择
  __u8  bMaxPower;                    //设备从总线提取的最大电流
 } __attribute__ ((packed));
  • 接口描述符结构如下所示: 

USB接口只处理一种USB逻辑连接。一个USB接口代表一个逻辑上的设备,比如声卡驱动,就有两个接口:录音接口和播放接口

这可以在windows系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。

struct usb_interface_descriptor {  
  __u8  bLength;                          //描述符的长度
  __u8  bDescriptorType;              //描述符类型的编号

  __u8  bInterfaceNumber;           //接口的编号
  __u8  bAlternateSetting;            //备用的接口描述符编号,提供不同质量的服务参数.
  __u8  bNumEndpoints;              //要使用的端点个数(不包括端点0), 表示有多少个端点描述符,比如鼠标就只有一个端点
  __u8  bInterfaceClass;              //接口类型,与驱动的id_table 
  __u8  bInterfaceSubClass;                 //接口子类型
  __u8  bInterfaceProtocol;                 //接口所遵循的协议
  __u8  iInterface;                        //描述该接口的字符串索引值
 } __attribute__ ((packed)

 它位于usb_interface->cur_altsetting->desc 这个成员结构体里,

  •  usb_interface结构体如下所示:
struct usb_interface { 
 struct usb_host_interface *altsetting; /* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。*/
   
 struct usb_host_interface *cur_altsetting; /* 指向altsetting内部的指针,表示当前激活的接口配置*/
    
 unsigned num_altsetting; /* 可选设置的数量*/
   
 /* If there is an interface association descriptor then it will list the associated interfaces */ 
 struct usb_interface_assoc_descriptor *intf_assoc;
    

 int minor; /* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/ 
  ... ...
}
  • cur_altsetting成员的结构体是usb_host_interface,如下: 
struct usb_host_interface {
    struct usb_interface_descriptor desc;   //当前被激活的接口描述符
    struct usb_host_endpoint *endpoint;   /* 这个接口的所有端点结构体的联合数组*/
    char *string;                 /* 接口描述字符串 */
    unsigned char *extra;           /* 额外的描述符 */
    int extralen;
};
  • 端点描述符结构如下所示:
struct usb_endpoint_descriptor {
__u8  bLength;                          //描述符的长度
__u8  bDescriptorType;              //描述符类型的编号

__u8  bEndpointAddress;              //端点编号,比如端点1,就是1
__u8  bmAttributes;                  //端点的属性, 比如中断传输类型,输入类型
__le16 wMaxPacketSize;               //一个端点的最大包大小,
__u8  bInterval;                     //间隔时间,用在中断传输上,比如间隔时间查询鼠标的数据

 
/* NOTE:  these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8  bRefresh;
__u8  bSynchAddress;

} __attribute__ ((packed));

 比如端点0,就位于usb_interface->cur_altsetting->desc->endpoint[0].desc

  • endpoint的结构体为usb_host_endpoint,如下所示:
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端点描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp;//超快速端点描述符
struct list_head urb_list; //本端口对应的urb链表
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */

unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled;//使能的话urb才能被提交到此端口
};

2  USB鼠标编程指导

  • 编写usb鼠标驱动最核心的结构体就是定义一个usb_mouse与usb_driver结构体,该结构体成员如下
struct usb_mouse {
	struct usb_device *usbdev;/* USB鼠标是一种USB设备,需要内嵌一个USB设备结构体来描述其USB属性 */  
	struct input_dev *inputdev;/* USB鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */  
	struct urb *urb;/* URB 请求包结构体,用于传送数据 */  

	signed char *data;/* 普通传输用的地址 */  
	dma_addr_t data_dma;/* dma 传输用的地址 */  
};

static struct usb_driver usb_mouse_driver = {
	.name = "usb_mouse",
	.probe = usb_mouse_probe,
	.disconnect = usb_mouse_disconnect,
	.id_table = usb_mouse_id_table,
};
  • USB鼠标是一种USB设备,所需要为其分配一个usb_device结构体,该结构体直接通过一下函数就可以获取
mouse->usbdev = interface_to_usbdev(intf);//获取usb_device结构体
  • USB数据控制的一个重要的结构体就是urb,该结构体初始化方式如下:
//分配,设置urb
mouse->urb = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_int_urb(mouse->urb,        //urb结构体
                     mouse->usbdev,     //usb设备
                     pipe,              //端点管道
                     mouse->data,       //缓存区地址
                     endpoint->wMaxPacketSize,//数据长度
                     usb_mouse_irq,     //usb中断处理函数
                     mouse,
                     endpoint->bInterval);//中断间隔时间
mouse->data = usb_alloc_coherent(mouse->usbdev, 8, GFP_ATOMIC, &mouse->data_dma);//分配dma传输地址
mouse->urb->transfer_dma = mouse->data_dma;//设置DMA地址
mouse->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//设置使用DMA地址

usb_submit_urb(mouse->urb, GFP_KERNEL);//提交urb
  • USB鼠标同时又是一种输入设备,所以需要为鼠标注册一个input_device结构体
//1.分配input_device结构体
mouse->inputdev = input_allocate_device();
//2.1 设置按键事件与相对位移事件
mouse->inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
//2.2 设置具体的按键事件
mouse->inputdev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | 
                      BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA);
//2.3 设置鼠具体的相对位移事件
mouse->inputdev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) | BIT_MASK(REL_WHEEL);
//3.注册
input_register_device(mouse->inputdev);
  • 当产生鼠标相关事件时,在中断处理函数中上报事件
input_report_key(mouse->inputdev, BTN_LEFT,   mouse->data[0] & 0x01);//鼠标左键
input_report_key(mouse->inputdev, BTN_RIGHT,  mouse->data[0] & 0x02);//鼠标右键
input_report_key(mouse->inputdev, BTN_MIDDLE, mouse->data[0] & 0x04);//鼠标中轮
input_report_key(mouse->inputdev, BTN_SIDE,   mouse->data[0] & 0x08);//鼠标侧边按钮
input_report_key(mouse->inputdev, BTN_EXTRA,  mouse->data[0] & 0x10);//鼠标扩展按钮
input_report_rel(mouse->inputdev, REL_X,     mouse->data[1]);//鼠标水平位移
input_report_rel(mouse->inputdev, REL_Y,     mouse->data[2]);//鼠标垂直位移
input_report_rel(mouse->inputdev, REL_WHEEL, mouse->data[3]);//鼠标滚轮位移
input_sync(mouse->inputdev);
  • usb鼠标传输给底层驱动的数据协议,usb鼠标一次传输4字节数据D0-D3,每个字节含义如下:
D0bit7:   1   表示 Y 坐标的变化量超出-256~255的范围,0表示没有溢出
bit6:   1   表示 X 坐标的变化量超出-256~255的范围,0表示没有溢出
bit5:   Y   坐标变化的符号位,1表示负数,即鼠标向下移动 
bit4:   X   坐标变化的符号位,1表示负数,即鼠标向左移动 
bit3:   恒为1 
bit2:   1 表示中键按下 
bit1:   1 表示右键按下 
bit0:   1 表示左键按下 
D1X坐标变化量,与D0的bit4组成9位符号数,负数表示向左移,正数表右移。用补码表示变化量     0x01  向右移动    0xfe 向左移动
D2Y坐标变化量,与D0的bit5组成9位符号数,负数表示向下移,正数表上移。用补码表示变化量   0x01 向下移动   0xfe 向上移动
D3滚轮变化

 

3 USB鼠标驱动程序完整源码

//系统自带的usb鼠标驱动位于:drivers/hid/usbhid/usbmouse.c
#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>

struct usb_mouse {
	struct usb_device *usbdev;/* USB鼠标是一种USB设备,需要内嵌一个USB设备结构体来描述其USB属性 */  
	struct input_dev *inputdev;/* USB鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */  
	struct urb *urb;/* URB 请求包结构体,用于传送数据 */  

	signed char *data;/* 普通传输用的地址 */  
	dma_addr_t data_dma;/* dma 传输用的地址 */  
};

static struct usb_device_id usb_mouse_id_table[]={
	{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,  //USB类
                        USB_INTERFACE_SUBCLASS_BOOT, //USB子类
                        USB_INTERFACE_PROTOCOL_MOUSE)},//USB协议类型
};

static void usb_mouse_irq(struct urb * urb)
{
	struct usb_mouse *mouse = urb->context;
//	printk("mouse->data= %x,%x,%x,%x\n",mouse->data[0],mouse->data[1],mouse->data[2],mouse->data[3]);
    input_report_key(mouse->inputdev, BTN_LEFT,   mouse->data[0] & 0x01);//鼠标左键
	input_report_key(mouse->inputdev, BTN_RIGHT,  mouse->data[0] & 0x02);//鼠标右键
	input_report_key(mouse->inputdev, BTN_MIDDLE, mouse->data[0] & 0x04);//鼠标中轮
	input_report_key(mouse->inputdev, BTN_SIDE,   mouse->data[0] & 0x08);//鼠标侧边按钮
	input_report_key(mouse->inputdev, BTN_EXTRA,  mouse->data[0] & 0x10);//鼠标扩展按钮

	input_report_rel(mouse->inputdev, REL_X,     mouse->data[1]);//鼠标水平位移
	input_report_rel(mouse->inputdev, REL_Y,     mouse->data[2]);//鼠标垂直位移
	input_report_rel(mouse->inputdev, REL_WHEEL, mouse->data[3]);//鼠标滚轮位移

	input_sync(mouse->inputdev);
	//重新提交urb,以能够响应下次鼠标事件
	usb_submit_urb(mouse->urb, GFP_KERNEL);
}


static int usb_mouse_probe (struct usb_interface *intf,const struct usb_device_id *id)
{
    struct usb_mouse *mouse;
    struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
    int pipe;

	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

    mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
    mouse->usbdev = interface_to_usbdev(intf);//获取usb_device结构体

    //1.分配input_device结构体
    mouse->inputdev = input_allocate_device();
	//2.1 设置按键事件与相对位移事件
    mouse->inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
	//2.2 设置具体的按键事件
	mouse->inputdev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | 
                      BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA);
    //2.3 设置鼠具体的相对位移事件
    mouse->inputdev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) | BIT_MASK(REL_WHEEL);
	//3.注册
	input_register_device(mouse->inputdev);

	//4.硬件相关操作
	//源:USB设备某个端点
	pipe = usb_rcvintpipe(mouse->usbdev, endpoint->bEndpointAddress);

	//分配,设置urb
    mouse->urb = usb_alloc_urb(0, GFP_KERNEL);
	usb_fill_int_urb(mouse->urb,        //urb结构体
                     mouse->usbdev,     //usb设备
                     pipe,              //端点管道
                     mouse->data,       //缓存区地址
                     endpoint->wMaxPacketSize,//数据长度
                     usb_mouse_irq,     //usb中断处理函数
                     mouse,
                     endpoint->bInterval);//中断间隔时间
    mouse->data = usb_alloc_coherent(mouse->usbdev, 8, GFP_ATOMIC, &mouse->data_dma);//分配dma传输地址
	mouse->urb->transfer_dma = mouse->data_dma;//设置DMA地址
	mouse->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//设置使用DMA地址

	usb_submit_urb(mouse->urb, GFP_KERNEL);//提交urb

    usb_set_intfdata(intf, mouse);
	return 0;
}

static void usb_mouse_disconnect (struct usb_interface *intf)
{
    struct usb_mouse *mouse = usb_get_intfdata (intf);
	usb_set_intfdata(intf, NULL);
    if (mouse) {
        usb_kill_urb(mouse->urb);
		input_unregister_device(mouse->inputdev);
        input_free_device(mouse->inputdev);
		usb_free_urb(mouse->urb);
		usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
		kfree(mouse);
    }
}

static struct usb_driver usb_mouse_driver = {
	.name = "usb_mouse",
	.probe = usb_mouse_probe,
	.disconnect = usb_mouse_disconnect,
	.id_table = usb_mouse_id_table,
};

module_usb_driver(usb_mouse_driver);
MODULE_LICENSE("GPL");

4  USB鼠标驱动测试

  • 去掉系统自带的鼠标驱动

-> Device Drivers
     -> HID Devices
           <>USB Human Interface Device (full HID) support

  • 重新make uImage,并烧录到开发板
  • 启动开发版后安装usb驱动insmod usb_mouse_drv.ko ,此时会有如下输出
usbcore: registered new interface driver usb_mouse  
  • 插入USB鼠标会有如下输出
usb 1-1: new low-speed USB device number 2 using s3c2410-ohci           
usb 1-1: New USB device found, idVendor=28a0, idProduct=1185                      
usb 1-1: New USB device strings: Mfr=0, Product=1, SerialNumber=0                 
usb 1-1: Product: USB OPTICAL MOUSE                                               
input: Unspecified device as /devices/virtual/input/input1      
  • 此时执行hexdump /dev/iniput/event1 命令会有如下输出
0000000 06aa 0000 4302 0008 0001 0110 0001 0000                                 
0000010 06aa 0000 4320 0008 0000 0000 0000 0000                                 
0000020 06aa 0000 94b4 000a 0001 0110 0000 0000                                 
0000030 06aa 0000 94ce 000a 0000 0000 0000 0000    

5  USB键盘基本知识

  • usb键盘按键数据协议

键盘采用中断传输的方式传输键盘数据,每次传输8个字节数据,该8字节定义见下表,其中D0位是按键修饰符用于判断是否有特殊按键按下(Shift,ctrl,win等),普通按键从D2开始,支持6个普通按键同时按下。

D0D1D2D3D4D5D6D7
Modefier keys保留keycode1keycode2keycode3keycode4keycode5keycode6
                                                   D0(Modefier keys)各bit含义(按下为1)
BIT0BIT1BIT2BIT3BIT4BIT5BIT6BIT7
Left ControlLeft ShiftLeft AltLeft metaRight ControlRight ShiftRight AltRight meta
  • 按键按下情景分析

1.  按下a键时,USB键盘传输的数据为:00 00 04 00 00 00 00 00.
(为什么按下a键盘为04,可以参考USB HID to PS/2 Scan Code Translation Table)

2. 当检测到 D2=04 时,调用input_report_key(kbd->inputdev,KEY_A, 1) 上报按键a按下事件,在include/linux/input.h中可以查到KEY_A = 30  (#define KEY_A 30)

3. 以上可知,当USB键盘传输的给内核的数据中D2 = 04时,驱动需要上报30(KEY_A)的事件
                      当USB键盘传输的给内核的数据中D2 = 05时,驱动需要上报48(KEY_B)的事件
                      ......
    由于键盘的按键特别多,而usb键盘传内核的按键编码与内核空间传给用户空间的按键编码又不一样,所以我们需要建立一个映射数组,以键盘传输的给内核的数据作为该数组的下标,该下标对应的数值为内核空间传给用户空间的按键编码,如下所示:

unsigned char usb_kbd_keycode[256] = {
	  0,  0,  0,  0, KEY_A, KEY_B,............
};

此时当usb检测到数据D2 = 4 时,上传usb_kbd_keycode[4]按下事件就行了,到了这里还有一个问题,这么多的编码一个个去对应太麻烦了,这时候就可以查看linux源码中自带的按键驱动drivers/hid/usbhid/usbkbd.c,里面可以查看到映射数组:

static const 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
};
  • led控制状态灯情景分析

1.在probe函数设置好中断urb后,当按键事件发生时,会调用usb_kbd_event函数. (kbd->inputdev->event = usb_kbd_event;)
2.在usb_kbd_event函数中完成对led灯的控制,判断流程如下:

  • usb键盘状态灯显示数据协议

状态灯控制只需要1Byte数据进行控制,该数据各bit含义如下,我们需要做的就是控制BIT0-BIT2(3个状态灯)

BIT0BIT1BIT2BIT3BIT4BIT5-BIT7

NUM LOCK

小键盘数字键

CAPS LOCK

大小写锁定

SCROLL LOCK

滚动锁定键

COMPOSEKANACONSTANT
  • usb_ctrlrequest结构体分析
    struct usb_ctrlrequest {
    	__u8 bRequestType;
    	__u8 bRequest;
    	__le16 wValue;
    	__le16 wIndex;
    	__le16 wLength;
    } __attribute__ ((packed));
    bRequestType:
    
    BIT7BIT6-BIT5BIT4-BIT0
    BIT7=0时,表示后面的数据是从主控器发送到USB设备。在PC里,就是从PC机发送到USB的设备。
    当D7等于1时,表示后面的数据是从USB设备发送到主控器。在PC里,就是从USB设备发送到USB设备。
    0 是表示标准的请求。
    1 是表示类别的请求。
    2 是表示厂商的请求。
    3 是保留。
    0 是表示USB设备接收。
    1 是表示接口接收。
    2 是表示端点接收。
    3 是表示其它接收,不知道的。
    4-31是保留。

    bRequest  描述符的请求类型,其取值可以在include/linux/usb/ch9.h文件中查看,取值如下:

    #define USB_REQ_GET_STATUS		0x00
    #define USB_REQ_CLEAR_FEATURE		0x01
    #define USB_REQ_SET_FEATURE		0x03
    #define USB_REQ_SET_ADDRESS		0x05
    #define USB_REQ_GET_DESCRIPTOR		0x06
    #define USB_REQ_SET_DESCRIPTOR		0x07
    #define USB_REQ_GET_CONFIGURATION	0x08
    #define USB_REQ_SET_CONFIGURATION	0x09
    #define USB_REQ_GET_INTERFACE		0x0A
    #define USB_REQ_SET_INTERFACE		0x0B
    #define USB_REQ_SYNCH_FRAME		0x0C
    wValue:高字节表示描述符的类型,其取值可以在include/linux/usb/ch9.h文件中查看,取值如下。低字节表示描述符的索引。
#define USB_DT_DEVICE			0x01
#define USB_DT_CONFIG			0x02
#define USB_DT_STRING			0x03
#define USB_DT_INTERFACE		0x04
#define USB_DT_ENDPOINT			0x05
#define USB_DT_DEVICE_QUALIFIER		0x06
#define USB_DT_OTHER_SPEED_CONFIG	0x07
#define USB_DT_INTERFACE_POWER		0x08
wIndex:是根据不同的请求而设置不同的值。一般用来说明端点号或者说明接口标识
wLength是根据请求来决定下一阶段发送数据的长度。
  • usb_ctrlrequest结构体初始化
kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;//设定传输方向、请求类型等
kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION; //指定请求类型
kbd->cr->wValue = cpu_to_le16(0x200);//即将写到寄存器的数据
kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);//接口数量,也就是寄存器的偏移地址
kbd->cr->wLength = cpu_to_le16(1);//数据传输阶段传输多少个字节
  • led相关事件设置
kbd->inputdev->evbit[0] = BIT_MASK(EV_LED);//设置led事件
kbd->inputdev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |BIT_MASK(LED_KANA);

6 USB键盘驱动程序 

//系统自带驱动位于drivers/hid/usbhid/usbkbd.c
#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 const 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
};

struct usb_kbd {
	struct usb_device *usbdev;/* USB鼠标是一种USB设备,需要内嵌一个USB设备结构体来描述其USB属性 */  
	struct input_dev *inputdev;/* USB鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */  
	
    struct urb *irq_urb;/* 用于中断传输的urb */  
    unsigned char old_data[8];
	signed char *keys_data;/* 普通传输用的地址 */  
	dma_addr_t keys_dma;/* dma 传输用的地址 */  

    struct urb *ctl_urb;/* 用于控制传输的urb */
    struct usb_ctrlrequest *cr;/*用于控制传输*/
    unsigned char *leds_data;
    unsigned char new_leds_data;
    dma_addr_t cr_dma; /*控制请求DMA缓冲地址*/ 
    dma_addr_t leds_dma;
    spinlock_t leds_lock;
};

static struct usb_device_id usb_kbd_id_table[]={
	{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,  //USB类
                        USB_INTERFACE_SUBCLASS_BOOT, //USB子类
                        USB_INTERFACE_PROTOCOL_KEYBOARD)},//USB协议类型
    { }						/* Terminating entry */
};

static void usb_kbd_irq(struct urb * urb)
{
	struct usb_kbd *kbd = urb->context;
    int i;
    // printk("data = %d,%d,%d,%d,%d,%d,%d,%d\n",
    //        kbd->keys_data[0],kbd->keys_data[1],kbd->keys_data[2],kbd->keys_data[3],
    //        kbd->keys_data[4],kbd->keys_data[5],kbd->keys_data[6],kbd->keys_data[7]);

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

    for(i=2;i<8;i++){
        if(kbd->keys_data[i] != kbd->old_data[i]){
            if(kbd->keys_data[i] )      //按下事件
                input_report_key(kbd->inputdev,usb_kbd_keycode[kbd->keys_data[i]], 1);   
            else  if(kbd->old_data[i]) //松开事件
                input_report_key(kbd->inputdev,usb_kbd_keycode[kbd->old_data[i]], 0);
        }
    }
    memcpy(kbd->old_data, kbd->keys_data, 8);
	input_sync(kbd->inputdev);
	//重新提交urb,以能够响应下次鼠标事件
	usb_submit_urb(kbd->irq_urb, GFP_KERNEL);
}

static void usb_kbd_led(struct urb *urb)
{
 
}

/*如果有事件被响应,我们会调用事件处理层的event函数*/
static int usb_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    unsigned long flags;
    struct usb_kbd *kbd = input_get_drvdata(dev);
    // printk("usb_kbd_event\n");
    if (type != EV_LED)    //不是LED事件就返回
        return -1;
    spin_lock_irqsave(&kbd->leds_lock, flags);//上锁
    //将当前的LED值保存在kbd->newleds中
	kbd->new_leds_data = (test_bit(LED_KANA,    dev->led) << 3) | 
                         (test_bit(LED_COMPOSE, dev->led) << 3) |
		                 (test_bit(LED_SCROLLL, dev->led) << 2) | 
                         (test_bit(LED_CAPSL,   dev->led) << 1) |
		                 (test_bit(LED_NUML,    dev->led));

    if (*(kbd->leds_data) == kbd->new_leds_data){
		return 0;
	}
    *(kbd->leds_data) = kbd->new_leds_data;//更新数据
    
    usb_submit_urb(kbd->ctl_urb, GFP_ATOMIC);//提交urb
    spin_unlock_irqrestore(&kbd->leds_lock, flags);//解锁
    return 0;
}


static int usb_kbd_probe (struct usb_interface *intf,const struct usb_device_id *id)
{
    struct usb_kbd *kbd;
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int pipe,i;

    spin_lock_init(&kbd->leds_lock);//初始化自旋锁

    kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);
    kbd->usbdev = interface_to_usbdev(intf);//获取usb_device结构体
    kbd->inputdev = input_allocate_device();//分配input_device结构体
    kbd->irq_urb = usb_alloc_urb(0, GFP_KERNEL);//分配中断urb
    kbd->ctl_urb = usb_alloc_urb(0, GFP_KERNEL);//分配控制urb
    kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);//分配控制请求描述符
    kbd->keys_data = usb_alloc_coherent(kbd->usbdev, 8, GFP_ATOMIC, &kbd->keys_dma);//分配中断传输dma传输地址
    kbd->leds_data = usb_alloc_coherent(kbd->usbdev, 1, GFP_ATOMIC, &kbd->leds_dma);//分配控制传输dma传输地址

    //设置 input_device结构体
    kbd->inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED);//设置按键事件,重复按键事件与led事件
    kbd->inputdev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |BIT_MASK(LED_KANA);//设置LED事件
    for (i = 0; i < 255; i++)//设置具体的按键事件
		set_bit(usb_kbd_keycode[i], kbd->inputdev->keybit);
    clear_bit(0, kbd->inputdev->keybit);
    kbd->inputdev->event = usb_kbd_event;//当有事件产生时,调用usb_kbd_event函数
    input_register_device(kbd->inputdev);//注册input_device结构体

    //获取端点属性
    interface = intf->cur_altsetting;
    endpoint = &interface->endpoint[0].desc;
    pipe = usb_rcvintpipe(kbd->usbdev, endpoint->bEndpointAddress);

	//设置 中断urb
    usb_fill_int_urb(kbd->irq_urb,        //urb结构体
                     kbd->usbdev,     //usb设备
                     pipe,              //端点管道
                     kbd->keys_data,       //缓存区地址
                     endpoint->wMaxPacketSize,//数据长度
                     usb_kbd_irq,     //usb中断处理函数
                     kbd,
                     endpoint->bInterval);//中断间隔时间
    kbd->irq_urb->transfer_dma = kbd->keys_dma;//设置DMA地址
    kbd->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//设置使用DMA地址
    usb_submit_urb(kbd->irq_urb, GFP_KERNEL);//提交urb

    //设置 控制请求描述符usb_ctrlrequest
    kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;//设定传输方向、请求类型等
	kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION; //指定请求类型
	kbd->cr->wValue = cpu_to_le16(0x200);//即将写到寄存器的数据
	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);//接口数量,也就是寄存器的偏移地址
	kbd->cr->wLength = cpu_to_le16(1);//数据传输阶段传输多少个字节

    //设置 控制urb
    usb_fill_control_urb(kbd->ctl_urb, 
                         kbd->usbdev, 
                         usb_sndctrlpipe(kbd->usbdev, 0),//the endpoint pipe
			             (void *) kbd->cr,//setup_packet buffer
                         kbd->leds_data, //transfer buffer
                         1,              //length of the transfer buffer
			             usb_kbd_led,    //pointer to the usb_complete_t function
                         kbd);
	kbd->ctl_urb->transfer_dma = kbd->leds_dma;
	kbd->ctl_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    
    input_set_drvdata(kbd->inputdev, kbd);//设置私有数据
    usb_set_intfdata(intf, kbd);
    return 0;
}

static void usb_kbd_disconnect (struct usb_interface *intf)
{
    struct usb_kbd *kbd = usb_get_intfdata (intf);
	usb_set_intfdata(intf, NULL);
    if (kbd) {
        usb_free_urb(kbd->irq_urb);
        usb_free_urb(kbd->ctl_urb);
        kfree(kbd->cr);
        usb_free_coherent(kbd->usbdev, 8, kbd->keys_data, kbd->keys_dma);
        usb_free_coherent(kbd->usbdev, 1, kbd->leds_data, kbd->leds_dma);

        input_unregister_device(kbd->inputdev);
        usb_kill_urb(kbd->irq_urb);
        usb_kill_urb(kbd->ctl_urb);
		kfree(kbd);
    }
}

static struct usb_driver usb_kbd_driver = {
	.name = "usb_kbd",
	.probe = usb_kbd_probe,
	.disconnect = usb_kbd_disconnect,
	.id_table = usb_kbd_id_table,
};

module_usb_driver(usb_kbd_driver);
MODULE_LICENSE("GPL");

 

7  USB键盘驱动测试

  • 去掉系统自带的鼠标驱动

-> Device Drivers
     -> HID Devices
           <>USB Human Interface Device (full HID) support

  • 重新make uImage,并烧录到开发板。
  • 启动开发版后安装usb驱动insmod usb_kbd_drv.ko。makefile文件如下:
KERN_DIR = /home/ningjw/linux-3.4.2

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= usb_kbd_drv.o
  • 驱动测试
cat /dev/tty1        //可在LCD屏幕上看到按键值
hexdump event0       //可在终端看到事件上传
  • 使用上节的键盘驱动在LCD终端打印命令行
vi  /etc/inittab                //修改inittab, inittab:配置文件,用于启动init进程时,读取inittab
添加->tty1::askfirst:-/bin/sh   //将sh进程(命令行)输出到tty1里,也就是使LCD输出信息

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值