在前面的章节我们已经介绍了usb鼠标驱动的编写,并对usb摄像头驱动源码进行了分析。由于usb键盘驱动和usb鼠标驱动代码非常相似,所以这一节就粗略介绍一下usb键盘驱动的编写。
一、接收USB键盘数据准备工作
1.1 键盘数据格式
键盘发送给usb主机控制器的数据格式包含8个字节,BYTE0、BYTE1、BYTE2、BYTE3,BYTE4、BYTE5、BYTE6、BYTE7,定义分别是:
BYTE0 |
BYTE1 |
BYTE2~BYTE7 |
|
[7] | Right Ctrl1:按下 0:松开 |
保留 |
普通按键 比如A-Z、a-z、0-9等
|
[6] | Right Alt 1:按下 0:松开 | ||
[5] | Right Shift 1:按下 0:松开 | ||
[4] | Right Ctrl 1:按下 0:松开 | ||
[3] | Left GUI 1:按下 0:松开 | ||
[2] | Left Alt 1:按下 0:松开 | ||
[1] | Left Shift 1:按下 0:松开 | ||
[0] | Left Ctrl 1:按下 0:松开 |
比如我们按下A,接收到的8个字节数据为:
00 00 04 00 00 00 00 00
我们同时按下A和S,接收到的8个字节数据为:
00 00 04 16 00 00 00 00
我们可以看到按下A,usb键盘传输的数据为0x04、打下S时,usb键盘传输的数据是0x16。
我们找到输入子系统include/uapi/linux/input-event-codes.h中按键定义的值,可以看到按键A定义的值为30,按键S对应的值是31。
#define KEY_RESERVED 0 #define KEY_ESC 1 #define KEY_1 2 #define KEY_2 3 #define KEY_3 4 #define KEY_4 5 #define KEY_5 6 #define KEY_6 7 #define KEY_7 8 #define KEY_8 9 #define KEY_9 10 #define KEY_0 11 #define KEY_MINUS 12 #define KEY_EQUAL 13 #define KEY_BACKSPACE 14 #define KEY_TAB 15 #define KEY_Q 16 #define KEY_W 17 #define KEY_E 18 #define KEY_R 19 #define KEY_T 20 #define KEY_Y 21 #define KEY_U 22 #define KEY_I 23 #define KEY_O 24 #define KEY_P 25 #define KEY_LEFTBRACE 26 #define KEY_RIGHTBRACE 27 #define KEY_ENTER 28 #define KEY_LEFTCTRL 29 #define KEY_A 30 #define KEY_S 31 ......
在linux内核键盘驱动程序中drivers/hid/usbhid/usbkbd.c,定义了键盘描述码表:
/* usb键盘码 */ 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 };
发现该数据usb_kbd_keycode[0x04]=30,usb_kbd_keycode[0x16]=31。
1.2 usb键盘和接口驱动匹配
usb键盘接口驱动usb_kbd_drv的成员id_table应该怎么设置呢。我们参考drivers/hid/usbhid/usbkbd.c(内核自带的usb鼠标驱动)是如何使用的,如下:
static const struct usb_device_id usb_kbd_id_table[] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) }, { } /* Terminating entry */ };
usb鼠标和usb键盘的usb_mouse_id_table数组基本是一样的,都是HID类型设备,并且接口子类都是启动设备,只是接口协议略有不同,usb鼠标接口协议为USB_INTERFACE_PROTOCOL_MOUSE、而usb键盘接口协议为USB_INTERFACE_PROTOCOL_KEYBOARD。
二、usb鼠标接口驱动编写
新建项目15.usb_keyboard,编写usb键盘接口驱动程序,代码放在keyboard_dev.c文件。代码参考drivers/hid/usbhid/usbkbd.c。
2.1 初始化usb接口驱动
首先定义struct usb_kbd_driver 类型的全局变量,并初始化成员name、probe、disconnect、id_table:
static struct usb_driver usb_kbd_driver = { .name = "usbkbd", .probe = usb_kbd_probe, .disconnect = usb_kbd_disconnect, .id_table = usb_kbd_id_table, };
2.2 编写驱动模块入口函数
在模块入口函数,调用usb_register注册usb_struct结构体:
2.3 编写usb_kbd_probe
当usb键盘设备和usb接口驱动匹配时,usb_kbd_probe将会被调用:
2.3.1 动态分配input_device设备
- 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;
- 通过input_set_capability设置input设备可以上报哪些输入事件;
- 然后调用input_register_device注册这个设备;
2.3.2 设置usb数据传输
- 通过usb_rcvintpipe创建一个接收中断类型的端点管道,用来端点和数据缓冲区之间的连接
- 通过usb_buffer_alloc申请usb缓冲区
- 通过usb_alloc_coherent申请urb结构体;
- 通过usb_fill_int_urb填充urb结构体;当鼠标点击时触发urb处理完成函数,在urb处理完成函数进行输入事件上报;
- 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址;
- 使用usb_submit_urb提交urb
2.4 编写usb_mouse_disconnect
- 使用usb_kill_urb杀掉提交到内核中的urb;
- 使用usb_free_urb释放urb;
- 使用usb_free_coherent释放usb缓存区;
- 使用input_register_device函数注销input_dev;
- 使用input_free_device函数释放input_de;
2.5 编写驱动模块出口函数
在模块出口函数,调用usb_deregister注销usb_struct结构体:
2.6 完整代码keyboard_dev.c
#include <linux/module.h> #include <linux/fs.h> #include <linux/usb/input.h> #include <linux/hid.h> /* usb键盘码 */ 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 - state of each attached keyboard * @dev: input device associated with this keyboard * @usbdev: usb device associated with this keyboard * @old: data received in the past from the @irq URB representing which * keys were pressed. By comparing with the current list of keys * that are pressed, we are able to see key releases. * @irq: URB for receiving a list of keys that are pressed when a * new key is pressed or a key that was pressed is released. * @led: URB for sending LEDs (e.g. numlock, ...) * @newleds: data that will be sent with the @led URB representing which LEDs should be on * @name: Name of the keyboard. @dev's name field points to this buffer * @phys: Physical path of the keyboard. @dev's phys field points to this * buffer * @new: Buffer for the @irq URB * @cr: Control request for @led URB * @leds: Buffer for the @led URB * @new_dma: DMA address for @irq URB * @leds_dma: DMA address for @led URB * @leds_lock: spinlock that protects @leds, @newleds, and @led_urb_submitted * @led_urb_submitted: indicates whether @led is in progress, i.e. it has been * submitted and its completion handler has not returned yet * without resubmitting @led */ struct usb_kbd{ char name[128]; /* 键盘设备的名称,包括生产厂商、产品类别、产品等信息 */ char phys[64]; /* 设备节点名称 */ /* usb 键盘是一种 usb设备,需要内嵌一个usb设备结构体来描述其usb属性 */ struct usb_device *usbdev; /* usb键盘同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */ struct input_dev *dev; /* urb请求包结构体,用于接收键盘按下/松开时发送的数据 采用中断传输 */ struct urb *irq; /* 上一次键盘按下的值 */ unsigned char old[8]; /* Buffer for the @irq URB, 8个字节 */ unsigned char *new; /* 内存空间new的dma映射,即这块内存空间对应的 dma 地址 */ dma_addr_t new_dma; /* urb请求包结构体,用于主机控制器发送数据给键盘从而实现控制led 采用控制传输 */ struct urb *led; /* led待发送的数据,用来控制键盘灯 */ unsigned char newleds; /* 控制传输的数据指针 */ struct usb_ctrlrequest *cr; /* Buffer for the @led UR,1个字节 */ unsigned char *leds; /* 内存空间leds的dma映射,即这块内存空间对应的 dma 地址 */ dma_addr_t leds_dma; /* 自旋锁 */ spinlock_t leds_lock; /* led控制请求已经提交,urb处理尚未完成标志位 */ bool led_urb_submitted; }; /** * urb完成时被调用的完成处理函数(中断传输) */ static void usb_kbd_irq(struct urb *urb) { /* 私有数据 */ struct usb_kbd *kbd = urb->context; /* 接收到的数据 */ char *new = kbd->new; /* 上一次接收到的数据 */ char *old = kbd->old; /* input_dev */ struct input_dev *dev = kbd->dev; /* usb_device */ struct usb_device *usbdev = kbd->usbdev; int status,i; /* urb的当前状态 */ switch (urb->status) { case 0: /* success */ break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } /* * usb键盘数据含义 * 向输入子系统汇报键盘事件情况,以便作出反应。 * data[0] 每一位都有特特殊含义 Left Ctrl、Left Shift、Left Alt、... * data[1] 保留 * data[2]~date[7] 每一位都对应一个普通按键 */ for( i=0; i<8; i++) // 处理特定功能键 { input_report_key(dev, usb_kbd_keycode[i+224], (new[0] >> i) & 1); } /* 上报普通按键 */ for (i = 2; i < 8; i++) { /* 通过上个状态的按键数据old[i]的非0值,来查找当前状态的按键数据,若没有找到,说明已经松开了该按键 */ if (old[i] > 3 && memscan(new + 2, old[i], 6) == new + 8) { /* 再次判断键盘描述码表的值是否不是0 */ if (usb_kbd_keycode[old[i]]) input_report_key(dev, usb_kbd_keycode[old[i]], 0); // 按键已经松开 else hid_info(usbdev,"Unknown key (scancode %#x) released.\n",old[i]); } /* 通过当前状态的按键数据new[i]的非0值,来查找上个状态的按键数据,若没有找到,说明已经按下了该按键 */ if (new[i] > 3 && memscan(old + 2, new[i], 6) == old + 8) { /* 再次判断键盘描述码表的值是否不是0 */ if (usb_kbd_keycode[new[i]]) input_report_key(dev, usb_kbd_keycode[new[i]], 1); // 按键已经按下 else hid_info(usbdev,"Unknown key (scancode %#x) pressed.\n",new[i]); } } /*上报同步事件,通知系统有事件上报 */ input_sync(dev); /* 更新上个状态值 */ memcpy(old, new, 8); /* * 系统需要周期性不断地获取键盘的事件信息,因此在 urb 回调函数的末尾再次提交urb请求块,这样又会调用新的回调函数,周而复始。 * 在回调函数中提交urb一定只能是 GFP_ATOMIC 优先级的,因为 urb 回调函数运行于中断上下文中,在提 * 交urb过程中可能会需要申请内存、保持信号量,这些操作或许会导致USB core睡眠,一切导致睡眠的行 * 为都是不允许的。 */ resubmit: status = usb_submit_urb (urb, GFP_ATOMIC); if (status) dev_err(usbdev,"can't resubmit intr, %s-%s/input0, status %d\n", usbdev->bus->bus_name, usbdev->devpath, status); } /* * 事件处理函数,当读取input_dev设备时,并处于堵塞模式时, */ 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"); if (type != EV_LED) return -1; /* 获取自旋锁、关中断 */ spin_lock_irqsave(&kbd->leds_lock, flags); kbd->newleds = (!!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->led_urb_submitted){ spin_unlock_irqrestore(&kbd->leds_lock, flags); return 0; } if (*(kbd->leds) == kbd->newleds){ spin_unlock_irqrestore(&kbd->leds_lock, flags); return 0; } *(kbd->leds) = kbd->newleds; kbd->led->dev = kbd->usbdev; if (usb_submit_urb(kbd->led, GFP_ATOMIC)) pr_err("usb_submit_urb(leds) failed\n"); else kbd->led_urb_submitted = true; spin_unlock_irqrestore(&kbd->leds_lock, flags); return 0; } /** * urb完成时被调用的完成处理函数(控制传输) */ static void usb_kbd_led(struct urb *urb) { unsigned long flags; /* 私有数据 */ struct usb_kbd *kbd = urb->context; /* usb_device */ struct usb_device *usbdev = kbd->usbdev; /* urb的当前状态 */ if(urb->status) { hid_warn(usbdev,"led urb status %d received\n",urb->status); } /* 关中断、并获取自旋锁 */ spin_lock_irqsave(&kbd->leds_lock,flags); if (*(kbd->leds) == kbd->newleds){ kbd->led_urb_submitted = false; spin_unlock_irqrestore(&kbd->leds_lock, flags); return; } *(kbd->leds) = kbd->newleds; kbd->led->dev = usbdev; if (usb_submit_urb(kbd->led, GFP_ATOMIC)){ /* urb请求提交失败 */ hid_err(usbdev, "usb_submit_urb(leds) failed\n"); kbd->led_urb_submitted = false; } /* 释放自旋锁、开中断 */ spin_unlock_irqrestore(&kbd->leds_lock, flags); } /** * 用户usb接口设备和usb接口驱动匹配 */ static struct usb_device_id usb_kbd_id_table[] = { { USB_INTERFACE_INFO( USB_INTERFACE_CLASS_HID, //接口类:hid类 USB_INTERFACE_SUBCLASS_BOOT, //子类:启动设备类 USB_INTERFACE_PROTOCOL_KEYBOARD) }, //USB协议:键盘协议 }; /* * 打开input_dev设备时执行 */ static int usb_kbd_open(struct input_dev *dev) { /* 获取驱动数据 */ struct usb_kbd *kbd = input_get_drvdata(dev); kbd->irq->dev = kbd->usbdev; /* 使用usb_submit_urb提交urb */ if (usb_submit_urb(kbd->irq, GFP_KERNEL)) return -EIO; return 0; } /* * 关闭input_dev设备时执行 */ static void usb_kbd_close(struct input_dev *dev) { /* 获取驱动数据 */ struct usb_kbd *kbd = input_get_drvdata(dev); /* 杀掉提交到内核中的urb */ usb_kill_urb(kbd->irq); } /* 动态分配内存 */ static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) { /* struct urb */ if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL))) return -1; /* struct urb */ if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL))) return -1; /* 申请内存空间用于数据传输,new 为指向该空间的地址,new_dma 则是这块内存空间的dma映射, * 即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下,则使用 data_dma 指向的 dma 区域, * 否则使用 data 指向的普通内存区域进行传输。 */ if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma))) return -1; if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) return -1; if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma))) return -1; return 0; } /* 释放内存 */ static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd) { usb_free_urb(kbd->irq); usb_free_urb(kbd->led); usb_free_coherent(dev, 8, kbd->new, kbd->new_dma); kfree(kbd->cr); usb_free_coherent(dev, 1, kbd->leds, kbd->leds_dma); } /** * 当usb接口驱动和usb接口匹配成功之后,就会调用probe函数 * 可以参考hub_probe实现 */ static int usb_kbd_probe(struct usb_interface *intf, const struct usb_device_id *id) { /* 获取usb设备 */ struct usb_device *dev = interface_to_usbdev(intf); /* 当前激活的接口配置 */ struct usb_host_interface *interface; /* 当前usb接口下的端点0的端点描述符 */ struct usb_endpoint_descriptor *endpoint; /* usb键盘设备 */ struct usb_kbd *kbd; /* input_dev */ struct input_dev *input_dev; /* 端点管道 */ int pipe,maxp,i; int error = -ENOMEM; /* 当前激活的接口配置 */ interface = intf->cur_altsetting; /* 从接口描述符获取端点个数,键盘只有1个 */ if (interface->desc.bNumEndpoints != 1) return -ENODEV; /* 当前usb接口下的端点0的端点描述符 */ endpoint = &interface->endpoint[0].desc; if (!usb_endpoint_is_int_in(endpoint)) return -ENODEV; /* 通过usb_rcvintpipe创建一个端点管道 由设备地址[8:14]、端点号[15:18]、传输类型[30:31]、传输方向[7]构成 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 获取本端点接受或发送的最大信息包的大小 */ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); // 打印VID,PID printk("VID=%x,PID=%x\n,endpointAddress=%d,maxPacketSize=%d",dev->descriptor.idVendor,dev->descriptor.idProduct, endpoint->bEndpointAddress,maxp); /* 动态分配内存 */ kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); /* 分配一个input_dev结构体 */ input_dev = input_allocate_device(); if (!kbd || !input_dev) goto fail1; /* 初始化 */ kbd->usbdev = dev; kbd->dev = input_dev; /* 动态分配内存空间 */ if (usb_kbd_alloc_mem(dev,kbd)) goto fail2; /* 初始化自旋锁 */ spin_lock_init(&kbd->leds_lock); /* 获取键盘设备的名称 */ if (dev->manufacturer) strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); if (dev->product) { if(dev->manufacturer) strlcat(kbd->name, " ", sizeof(kbd->name)); strlcat(kbd->name, dev->product, sizeof(kbd->name)); } /* 如果键盘名没有 */ if (!strlen(kbd->name)) snprintf(kbd->name, sizeof(kbd->name), "USB HIDBP Keyboard %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); /* * 填充键盘设备结构体中的节点名。usb_make_path 用来获取usb设备在sysfs 中的路径,格式为:usb-usb总线号-路径名。 */ usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); strlcat(kbd->phys, "/input0", sizeof(kbd->phys)); /* 将键盘设备的名称赋给键盘设备内嵌的输入子系统结构体 */ input_dev->name = kbd->name; /* 将键盘设备的设备节点名赋给键盘设备内嵌的输入子系统结构体 */ input_dev->phys = kbd->phys; /* * input_dev中的input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符 * 中的编号赋给内嵌的输入子系统结构体 */ usb_to_input_id(dev, &input_dev->id); input_dev->dev.parent = &intf->dev; /* 设置上报事件,类型 */ set_bit(EV_KEY,input_dev->evbit); // 支持按键事件 set_bit(EV_LED,input_dev->evbit); // 支持LED事件 set_bit(EV_REP,input_dev->evbit); // 支持重复上报 /* 设置上报EV_KEY类型事件的事件码 */ for (i = 0; i < 255; i++) input_set_capability(input_dev,EV_KEY,usb_kbd_keycode[i]); // 支持的按键 clear_bit(KEY_RESERVED, input_dev->keybit); // 清除EV_KEY事件类型下KEY_RESERVED事件码对应的bit位,也就是不传输这种事件 /* 设置上报EV_LED类型事件的事件码 */ input_set_capability(input_dev,EV_LED,LED_NUML); input_set_capability(input_dev,EV_LED,LED_CAPSL); input_set_capability(input_dev,EV_LED,LED_SCROLLL); input_set_capability(input_dev,EV_LED,LED_COMPOSE); input_set_capability(input_dev,EV_LED,LED_KANA); /* 设置input_dev->dev->driver_data = kbd */ input_set_drvdata(input_dev, kbd); /* 初始化input_dev */ input_dev->event = usb_kbd_event; input_dev->open = usb_kbd_open; input_dev->close = usb_kbd_close; /* 填充@irq urb */ usb_fill_int_urb (kbd->irq , //urb结构体 kbd->usbdev, //usb设备 pipe, //端点管道 kbd->new, //缓存区地址 maxp, //数据长度 usb_kbd_irq, //中断函数 kbd, //urb完成函数上下文 endpoint->bInterval); //中断间隔时间 /* 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址 */ kbd->irq->transfer_dma = kbd->new_dma; //设置DMA地址 kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //设置使用DMA地址 /* 构建控制请求数据 */ kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; // 请求类型 0x1<<5 | 0x01 类请求命令、接收者为接口 kbd->cr->bRequest = 0x09; // 请求值 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 urb */ usb_fill_control_urb(kbd->led, //urb结构体 dev, //usb设备 usb_sndctrlpipe(dev, 0), //端点管道 使用端点0 (void *) kbd->cr, //pointer to the setup_packet buffer kbd->leds, //缓存区地址 1, //数据长度 usb_kbd_led, //中断函数 kbd); //urb完成函数上下文 kbd->led->transfer_dma = kbd->leds_dma; kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 注册input_dev */ error = input_register_device(kbd->dev); if (error) { printk("input device usb keyboard device registered failed\n"); goto fail2; } else { printk("input device usb keyboard device registered successfully\n"); } /* 设置intf->dev->driver_data = kbd */ usb_set_intfdata(intf, kbd); return 0; fail2: /* 释放动态申请的缓存区 */ usb_kbd_free_mem(dev,kbd); fail1: /* 释放input_dev */ input_free_device(input_dev); kfree(kbd); return error; } /* * 卸载usb接口驱动时执行 */ static usb_kbd_disconnect(struct usb_interface *intf) { /* 获取intf->dev->driver_data */ struct usb_kbd *kbd = usb_get_intfdata (intf); usb_set_intfdata(intf, NULL); if(kbd){ /* 杀掉提交到内核中的urb */ usb_kill_urb(kbd->irq); usb_kill_urb(kbd->led); /* 注销内核中的input_dev */ input_unregister_device(kbd->dev); /* 释放input_dev */ input_free_device(kbd->dev); /* 释放动态申请的缓存区 */ usb_kbd_free_mem(interface_to_usbdev(intf),kbd); kfree(kbd); } } /** * usb键盘接口驱动 */ static struct usb_driver usb_kbd_driver = { .name = "usbkbd", .probe = usb_kbd_probe, .disconnect = usb_kbd_disconnect, .id_table = usb_kbd_id_table, }; /* * usb键盘接口驱动模块入口 */ static int usb_kbd_init(void) { int ret; ret = usb_register(&usb_kbd_driver); if (ret){ printk("usb interface driver registered failed\n"); }else{ printk("usb interface driver registered successfully\n"); } return ret; } /* * usb键盘接口驱动模块出口 */ static void __exit usb_kbd_exit(void) { usb_deregister(&usb_kbd_driver); printk("usb interface driver deregistered successfully\n"); } module_init(usb_kbd_init); module_exit(usb_kbd_exit); MODULE_LICENSE("GPL");
三、测试
3.1 编译usb键盘接口驱动
在15.usb_keyboard路径下编译:
root@zhengyang:/work/sambashare/drivers/15.usb_keyboard# cd /work/sambashare/drivers/15.usb_keyboard/ root@zhengyang:/work/sambashare/drivers/15.usb_keyboard# make
拷贝驱动文件到nfs文件系统:
root@zhengyang:/work/sambashare/drivers/15.usb_keyboard# cp /work/sambashare/drivers/15.usb_keyboard/keyboard_dev.ko /work/nfs_root/rootfs/
3.2 安装驱动
重启开发板,加载usb键盘接口驱动,执行如下命令:
insmod keyboard_dev.ko
运行结果如下:
[root@zy:/]# insmod keyboard_dev.ko keyboard_dev: loading out-of-tree module taints kernel. usbcore: registered new interface driver usbkbd usb interface driver registered successfully
这里我没有多余的键盘,不然的话,键盘插入开发板后就可以看到usb键盘相关的信息输出。
3.3 编写应用程序
新建test文件夹,编写测试程序。
3.3.1 main.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(int argc, char const *argv[]) { //打开设备文件 int fd; int retval; fd_set readfds; struct timeval tv; if((fd = open("/dev/input/event0", O_RDONLY)) == -1) { perror("open error"); return -1; } //读取文件内容 struct input_event mykey; while(1){ FD_ZERO(&readfds); FD_SET(fd, &readfds); if((retval = select(fd+1, &readfds, NULL, NULL, &tv)) == 1) { if(read(fd, &mykey, sizeof(mykey)) == sizeof(mykey)){ // 事件类型 鼠标或者按键 if(mykey.type == EV_KEY) { printf("--------------------\n"); printf("type = %u.\n", mykey.type); printf("code = %u.\n", mykey.code); printf("value = %u.\n", mykey.value); /* 按键是按下还是释放,0释放、1按下、2长按 */ // 按下状态 if(mykey.value == 1) { printf("type:%#x, code:%d, value:%#x\n", mykey.type, mykey.code, mykey.value); switch (mykey.code) { case 17: puts("w键按下了"); break; case 30: puts("a键按下了"); break; case 31: puts("s键按下了"); break; case 32: puts("d键按下了"); break; case 0x110: puts("mouse left button down"); break; case 0x111: puts("mouse right button down"); break; case 0x112: puts("mouse middle button down"); break; } } } } } } return 0; }
3.3.2 Makefile
all: arm-linux-gcc -march=armv4t -o main main.c clean: rm -rf *.o main
参考文章
标签:usb,urb,linux,dev,kbd,input,驱动,struct From: https://www.cnblogs.com/zyly/p/16644624.html