一、鼠标
linux下的usb鼠标驱动在/drivers/hid/usbhid/usbmouse.c中实现
1.加载初始化过程
1.1模块入口
module_init(usb_mouse_init);
1.2初始化函数
static int __init usb_mouse_init(void) //初始化{ int retval = usb_register(&usb_mouse_driver); //注册usb鼠标驱动 if (retval == 0) printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"DRIVER_DESC "\n"); return retval;}
1.3初始化函数注册了一个usb驱动usb_mouse_driver
static struct usb_driver usb_mouse_driver = { //usb鼠标驱动 .name = "usbmouse", //驱动名 .probe = usb_mouse_probe, //匹配方法 .disconnect = usb_mouse_disconnect, //拔出方法 .id_table = usb_mouse_id_table, //支持设备id表};
1.4当插入鼠标时会根据usb_mouse_id_table去匹配创建usb设备
static struct usb_device_id usb_mouse_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */};
它的匹配方式是接口id匹配.接口类USB_INTERFACE_CLASS_HID
usb插入枚举时候会获取usb鼠标的接口类型,获取其接口类信息,匹配成功的话会动态创建一个usb_device.
在分析probe和disconnect方法之前先介绍下驱动用来描述usb鼠标对象的结构体usb_mouse
struct usb_mouse { char name[128];//usb鼠标设备名 char phys[64];//路径 struct usb_device *usbdev;//usb设备 struct input_dev *dev;//输入设备 struct urb *irq;//urb结构体 signed char *data; //数据传输缓冲区指针 dma_addr_t data_dma;};
usb鼠标既包含usb设备(usb_device)的属性也包含input输入设备(input_dev)的属性
1.5 匹配成功了就会调用probe方法
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id){ struct usb_device *dev = interface_to_usbdev(intf); //根据usb接口获取动态创建的usb_device struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; struct usb_mouse *mouse; struct input_dev *input_dev; int pipe, maxp; int error = -ENOMEM; interface = intf->cur_altsetting; //获取usb_host_interface if (interface->desc.bNumEndpoints != 1) //鼠标的端点有且仅有1个控制端点 return -ENODEV; endpoint = &interface->endpoint[0].desc; //获取端点描述符 if (!usb_endpoint_is_int_in(endpoint)) //判断该端点是否中断端点 return -ENODEV; //上面判断了usb鼠标的属性,有且仅有1个控制端点(0号端点不算进来的) pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //设置端点为中断输入端点 maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //获取包数据最大值 mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); //分配usb_mouse对象 input_dev = input_allocate_device(); //初始化输入设备 if (!mouse || !input_dev) goto fail1; mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);//分配初始化usb鼠标数据缓冲区内存(默认8位数据) if (!mouse->data) goto fail1; mouse->irq = usb_alloc_urb(0, GFP_KERNEL);//分配初始化urb if (!mouse->irq) goto fail2; mouse->usbdev = dev; //设置usb鼠标设备的usb设备对象 mouse->dev = input_dev; //设备usb鼠标设备的input设备对象 if (dev->manufacturer) //枚举时候有获取到有效的厂商名 strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name)); //复制厂商名到name if (dev->product) { //枚举时候有获取到有效的产品名 if (dev->manufacturer) //如果也有厂商名 strlcat(mouse->name, " ", sizeof(mouse->name)); //则用空格将厂商名和产品名隔开 strlcat(mouse->name, dev->product, sizeof(mouse->name)); //追加产品名到name } if (!strlen(mouse->name)) //如果厂商和产品名都没有 snprintf(mouse->name, sizeof(mouse->name),"USB HIDBP Mouse %04x:%04x", le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct)); //则直接根据厂商id和产品id给name赋值 usb_make_path(dev, mouse->phys, sizeof(mouse->phys)); //设置设备路径名 strlcat(mouse->phys, "/input0", sizeof(mouse->phys)); //追加/input0 input_dev->name = mouse->name; //输入设备的名字设置成usb鼠标的名字 input_dev->phys = mouse->phys; //输入设备的路径设置成usb鼠标的路径 usb_to_input_id(dev, &input_dev->id); //设置输入设备的bustype,vendor,product,version input_dev->dev.parent = &intf->dev; //usb接口设备为输入设备的父设备 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); //输入事件类型按键+相对位移 input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); //按键类型 鼠标:左键,右键,中键 input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); //相对位移x方向+y方向 input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); //按键类型 鼠标:旁键,外部键 input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); //相对位移 鼠标滚轮事件 input_set_drvdata(input_dev, mouse); //usb鼠标驱动文件作为输入设备的设备文件的驱动数据 input_dev->open = usb_mouse_open; //设置输入事件的打开方法 input_dev->close = usb_mouse_close; //设置输入事件的关闭方法 usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,(maxp > 8 ? 8 : maxp),usb_mouse_irq, mouse, endpoint->bInterval); //填充中断类型urb 指定了urb的回调函数是usb_mouse_irq mouse->irq->transfer_dma = mouse->data_dma;//dma数据缓冲区指向usb鼠标设备的data_dma成员 mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//没DMA映射 error = input_register_device(mouse->dev); if (error) goto fail3; usb_set_intfdata(intf, mouse); ////usb鼠标驱动文件作为usb接口设备的设备文件的驱动数据 return 0;fail3: usb_free_urb(mouse->irq);fail2: usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);fail1: input_free_device(input_dev); kfree(mouse); return error;}
1.6 拔掉usb鼠标就会调用disconnect方法
static void usb_mouse_disconnect(struct usb_interface *intf){ struct usb_mouse *mouse = usb_get_intfdata (intf); //根据usb接口设备的设备文件的驱动数据,获取usb鼠标设备 usb_set_intfdata(intf, NULL); //清空usb接口设备的设备文件的驱动数据 if (mouse) { usb_kill_urb(mouse->irq); //断掉urb传输 input_unregister_device(mouse->dev); //注销输入设备 usb_free_urb(mouse->irq); //释放urb usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma); //清除传输数据缓冲区 kfree(mouse); //释放usb鼠标设备 }}
基本上disconnect只是probe的一个逆操作而已
经过probe过程,注册了输入设备则会在/dev/input/目录下会产生对应的鼠标设备节点,应用程序可以打开该节点来控制usb鼠标设备
此时会调用usb_mouse_open方法
1.7打开鼠标
static int usb_mouse_open(struct input_dev *dev){ struct usb_mouse *mouse = input_get_drvdata(dev); //通过输入设备获取usb鼠标设备 mouse->irq->dev = mouse->usbdev; //设置urb设备对应的usb设备 if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //提交urb return -EIO; return 0;}
通过urb提交之后,鼠标动作通过usb传输数据就会交由urb去处理了
1.8.urb数据传输
当操作鼠标的时候,会引起urb数据传输在数据传输之后会调用usb_mouse_irq
static void usb_mouse_irq(struct urb *urb){ struct usb_mouse *mouse = urb->context; //获取usb鼠标设备 signed char *data = mouse->data; //数据传输缓冲区指针 struct input_dev *dev = mouse->dev; //输入设备 int status; switch (urb->status) { //判断urb传输的状态 case 0: /* success */ //传输成功跳出switch break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } input_report_key(dev, BTN_LEFT, data[0] & 0x01); //右键 input_report_key(dev, BTN_RIGHT, data[0] & 0x02); //左键 input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); //中键 input_report_key(dev, BTN_SIDE, data[0] & 0x08); //边键 input_report_key(dev, BTN_EXTRA, data[0] & 0x10); //外部键 input_report_rel(dev, REL_X, data[1]); //相对x坐标位移 input_report_rel(dev, REL_Y, data[2]); //相对y坐标位移 input_report_rel(dev, REL_WHEEL, data[3]); //相对滚轮位移 input_sync(dev); //同步事件resubmit: status = usb_submit_urb (urb, GFP_ATOMIC); //继续提交urb if (status) err ("can"t resubmit intr, %s-%s/input0, status %d",mouse->usbdev->bus->bus_name,mouse->usbdev->devpath, status);}
usb接口传来的数据会保存在usb鼠标data指针成员指向的缓冲区中
这里可以看出usb鼠标传输的每次数据基本是4个字节
第0个字节的第1位表示右键,第2位表示左键,第3位表示中键,第4位表示边键,第5为表示外部键
而第1个字节表示相对x坐标的位移,第2个字节表示相对y坐标的位移,第3个字节表示相对滚轮的位移
当输入设备上报完usb接口接收来的数据后,需要调用input_sync同步事件消息,并调用usb_submit_urb提交urb
使其继续监视处理usb鼠标设备传递的新数据.
应用程序要获取鼠标操作信息可以打开对应的输入设备节点,并通过输入设备的读接口,获取到usb鼠标通过usb接口传递并交由输入设备上报过来的数据
漏掉的函数
1.应用程序关闭鼠标设备
static void usb_mouse_close(struct input_dev *dev){ struct usb_mouse *mouse = input_get_drvdata(dev); //通过输入设备获取usb鼠标设备 usb_kill_urb(mouse->irq); //当关闭鼠标设备时候,需要断掉urb传输}
2.模块移除调用的函数
module_exit(usb_mouse_exit);static void __exit usb_mouse_exit(void){ usb_deregister(&usb_mouse_driver); //注销掉usb鼠标设备}
二、键盘
linux下的usb键盘驱动在/drivers/hid/usbhid/usbkbd.c中实现
1.加载初始化过程
1.1 模块入口
module_init(usb_kbd_init);1.2 初始化函数
static int __init usb_kbd_init(void){ int result = usb_register(&usb_kbd_driver); //注册usb键盘 if (result == 0) printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"DRIVER_DESC "\n"); return result;}1.3 初始化函数注册了一个usb驱动usb_kbd_driver
static struct usb_driver usb_kbd_driver = { //usb键盘驱动 .name = "usbkbd", //驱动名 .probe = usb_kbd_probe, //匹配方法 .disconnect = usb_kbd_disconnect, //拔出方法 .id_table = usb_kbd_id_table, //支持设备id};1.4 当插入鼠标时会根据usb_kbd_id_table去匹配创建usb设备
static 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 */};
它的匹配方式是接口id匹配.接口类USB_INTERFACE_CLASS_HID
usb插入枚举时候会获取usb键盘的接口类型,获取其接口类信息,匹配成功的话会动态创建一个usb_device.
在分析probe和disconnect方法之前先介绍下驱动用来描述usb键盘对象的结构体usb_kbd
struct usb_kbd { struct input_dev *dev; //输入设备 struct usb_device *usbdev; //usb设备 unsigned char old[8]; //旧的键盘按键数据 struct urb *irq, *led; //键盘urb,led urb unsigned char newleds; //新的led数据 char name[128]; //usb键盘设备名字 char phys[64]; //usb键盘设备路径 unsigned char *new; //usb键盘按键 数据传输缓冲区指针 struct usb_ctrlrequest *cr; //setup数据包控制请求描述符 unsigned char *leds; //usb键盘led 数据传输缓冲区指针 dma_addr_t new_dma; //usb键盘按键DMA映射总线地址 dma_addr_t leds_dma; //usb键盘led DMA映射总线地址};usb键盘既包含usb设备(usb_device)的属性也包含input输入设备(input_dev)的属性
1.5 匹配成功了就会调用probe方法
static int usb_kbd_probe(struct usb_interface *iface,const struct usb_device_id *id){ struct usb_device *dev = interface_to_usbdev(iface); //根据usb接口获取动态创建的usb_device struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; struct usb_kbd *kbd; struct input_dev *input_dev; int i, pipe, maxp; int error = -ENOMEM; interface = iface->cur_altsetting; //获取usb_host_interface if (interface->desc.bNumEndpoints != 1) //键盘的端点有且仅有1个控制端点 return -ENODEV; endpoint = &interface->endpoint[0].desc; //获取端点描述符 if (!usb_endpoint_is_int_in(endpoint)) //判断该端点是否中断端点 return -ENODEV; //上面判断了usb键盘的属性,有且仅有1个控制端点(0号端点不算进来的) pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //设置端点为中断输入端点 maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //获取包数据最大值 kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); //分配usb_kbd对象 input_dev = input_allocate_device(); //初始化输入设备 if (!kbd || !input_dev) goto fail1; if (usb_kbd_alloc_mem(dev, kbd)) //分配usb键盘需要的内存 goto fail2; kbd->usbdev = dev; //设置usb键盘设备的usb设备对象 kbd->dev = input_dev; //设备usb键盘设备的input设备对象 if (dev->manufacturer) //枚举时候有获取到有效的厂商名 strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); //复制厂商名到name if (dev->product) { //枚举时候有获取到有效的产品名 if (dev->manufacturer) //如果也有厂商名 strlcat(kbd->name, " ", sizeof(kbd->name)); //则用空格将厂商名和产品名隔开 strlcat(kbd->name, dev->product, sizeof(kbd->name)); //追加产品名到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)); //则直接根据厂商id和产品id给name赋值 usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); //设置设备路径名 strlcat(kbd->phys, "/input0", sizeof(kbd->phys)); //追加/input0 input_dev->name = kbd->name; //输入设备的名字设置成usb键盘的名字 input_dev->phys = kbd->phys; //输入设备的路径设置成usb键盘的路径 usb_to_input_id(dev, &input_dev->id); //设置输入设备的bustype,vendor,product,version input_dev->dev.parent = &iface->dev; //usb接口设备为输入设备的父设备 input_set_drvdata(input_dev, kbd); //usb键盘驱动文件作为输入设备的设备文件的驱动数据 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | BIT_MASK(EV_REP); //输入事件类型 按键+led+重复 input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) | BIT_MASK(LED_KANA); //键盘led事件:小键盘,大小写,滚动锁定,组合键,KANA for (i = 0; i < 255; i++) set_bit(usb_kbd_keycode[i], input_dev->keybit); clear_bit(0, input_dev->keybit); //清除无效的0位 //键盘按键事件:遍历全局usb_kbd_keycode数组设置 input_dev->event = usb_kbd_event; //设置输入事件的event方法 input_dev->open = usb_kbd_open; //设置输入事件的open方法 input_dev->close = usb_kbd_close; //设置输入事件的close方法 usb_fill_int_urb(kbd->irq, dev, pipe,kbd->new, (maxp > 8 ? 8 : maxp),usb_kbd_irq, kbd, endpoint->bInterval); //填充中断类型urb 指定了urb的回调函数是usb_kbd_irq kbd->irq->transfer_dma = kbd->new_dma; //usb键盘按键设备DMA映射总线地址 kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //没DMA映射 //设置usb setup传输数据包控制请求结构体 kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; kbd->cr->bRequest = 0x09;//SET_IDLE? kbd->cr->wValue = cpu_to_le16(0x200); kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); kbd->cr->wLength = cpu_to_le16(1); usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),(void *) kbd->cr, kbd->leds, 1,usb_kbd_led, kbd); //设置为控制输出端点,填充控制类型urb,回调函数usb_kbd_led kbd->led->transfer_dma = kbd->leds_dma; //usb键盘led设备DMA映射总线地址 kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //没DMA映射 error = input_register_device(kbd->dev); //注册输入设备 if (error) goto fail2; usb_set_intfdata(iface, kbd); //usb键盘驱动文件作为usb接口设备的设备文件的驱动数据 device_set_wakeup_enable(&dev->dev, 1); //使能系统唤醒 return 0;fail2: usb_kbd_free_mem(dev, kbd); //分配失败则释放相关内存fail1: input_free_device(input_dev); //释放输入设备 kfree(kbd); //释放usb_kbd return error;}probe方法中调用的内存分配释放函数
分配内存
static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd){ if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL))) //分配按键urb return -1; if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL))) //分配led灯urb return -1; if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma))) //分配初始化usb键盘数据缓冲区内存(默认8位数据) return -1; if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) //分配setup包的控制请求描述符 return -1; if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma))) //分配初始化usb键盘led数据缓冲区内存 return -1; return 0;}释放内存
static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd){ usb_free_urb(kbd->irq); //释放键盘按键urb usb_free_urb(kbd->led); //释放键盘led urb usb_free_coherent(dev, 8, kbd->new, kbd->new_dma); //释放usb键盘数据缓冲区 kfree(kbd->cr); //释放setup包的控制请求描述符 usb_free_coherent(dev, 1, kbd->leds, kbd->leds_dma); //释放urb键盘led数据缓冲区内存}
配置用到的全局键值数组
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};
1.6 拔掉usb鼠标就会调用disconnect方法
static void usb_kbd_disconnect(struct usb_interface *intf){ struct usb_kbd *kbd = usb_get_intfdata (intf); //根据usb接口设备的设备文件的驱动数据,获取usb键盘设备 usb_set_intfdata(intf, NULL); //清空usb接口设备的设备文件的驱动数据 if (kbd) { usb_kill_urb(kbd->irq); //断掉urb传输 input_unregister_device(kbd->dev); //注销输入设备 usb_kbd_free_mem(interface_to_usbdev(intf), kbd); //释放usb键盘需要的内存 kfree(kbd); //释放usb键盘设备 }}
基本上disconnect只是probe的一个逆操作而已
经过probe过程,注册了输入设备则会在/dev/input/目录下会产生对应的键盘设备节点,应用程序可以打开该节点来控制usb键盘设备
此时会调用usb_kbd_open方法
1.7打开键盘
static int usb_kbd_open(struct input_dev *dev){ struct usb_kbd *kbd = input_get_drvdata(dev); //通过输入设备获取usb键盘设备 kbd->irq->dev = kbd->usbdev; //usb键盘按键urb捆绑usb设备 if (usb_submit_urb(kbd->irq, GFP_KERNEL)) //提交usb键盘按键urb return -EIO; return 0;}关闭键盘调用usb_kbd_close
static void usb_kbd_close(struct input_dev *dev){ struct usb_kbd *kbd = input_get_drvdata(dev); //通过输入设备获取usb键盘设备 usb_kill_urb(kbd->irq); //断开usb键盘按键urb}
通过urb提交之后,键盘动作通过usb传输数据就会交由urb去处理了
1.8.urb数据传输
static void usb_kbd_irq(struct urb *urb){ struct usb_kbd *kbd = urb->context; //获取usb键盘设备 int i; switch (urb->status) { //判断urb传输的状态 case 0: /* success */ //传输成功跳出switch break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } //L-ctrl,L-shift,L-alt,L-gui,R-ctrl,R-shift,R-alt,R-gui for (i = 0; i < 8; i++) //(224~231)判断新按下的是否组合键 input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); //组合键 //memscan(kbd->new + 2, kbd->old[i], 6)表示从kbd->new[2]扫描6个单位到kbd->new[7], //查找kbd->old[i]一样的字符,返回扫描到的单位地址"==kbd->new+8"表示没找到 //键盘扫描码0-No Event 1-Overrun Error 2-POST Fail 3-ErrorUndefined所以kbd->old[i] > 3 //键值usb_kbd_keycode[i]和扫描码new[i]/old[i]要区分好别乱了 //键盘扫描码和数据格式见函数下面图片 for (i = 2; i < 8; i++) { //新数据中没有查找到旧数据一样的码值--表示新的按键按下,旧的按键释放 if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) { if (usb_kbd_keycode[kbd->old[i]]) //松开的按键是正常的按键 input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); //上报释放按键事件 else dev_info(&urb->dev->dev,"Unknown key (scancode %#x) released.\n", kbd->old[i]); } //旧数据中没有查找到新数据一样的码值--表示新的按键按下,就的按键按下 if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) { if (usb_kbd_keycode[kbd->new[i]]) //按下的按键是正常的按键 input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1); //上报按下按键事件 else dev_info(&urb->dev->dev,"Unknown key (scancode %#x) released.\n", kbd->new[i]); } } //数据的第2~7字节用于存放键码,分别可以存放6个,也就是可以支持同时6个按键按下 //如果一直按住键盘的某个按键,则usb接收到的数据会都是一样的也就是kbd->old==kbd->new,则按下的时候会上报按下事件,一直按着的时候不会继续上报按下或释放按键 //若有新的按键按下,则所有的kdb->old的值可以在kdb->new中找到,而kdb->new中代表新按键键码的值在kdb->old中会找不到,所以触发第二个if条件成立,上报按下按键事件 //若之前的按键松开,则所有的kdb->new的值可以在kdb->old中找到,而kdb->old中代表旧按键键码的值在kdb->new中会找不到,所以触发第一个if条件成立,上报释放按键事件 input_sync(kbd->dev); //同步事件 memcpy(kbd->old, kbd->new, 8); //新的键值存放在旧的键值resubmit: i = usb_submit_urb (urb, GFP_ATOMIC); //提交urb if (i) err_hid ("can"t resubmit intr, %s-%s/input0, status %d",kbd->usbdev->bus->bus_name,kbd->usbdev->devpath, i);}
Usage
index
(dec)
Usage
Index
(hex)
Usage
Ref:typical
AT-101
position
PC-AT
Mac-
intosh
UNIX
Boot
0
00
Reserved(noeventindicated)9
N/A
84/101/104
1
01
KeyboardErrorRollOver9
N/A
84/101/104
2
02
KeyboardPOSTFail9
N/A
84/101/104
3
03
KeyboardErrorUndefined9
N/A
84/101/104
4
04
KeyboardaandA4
31
84/101/104
5
05
KeyboardbandB
50
84/101/104
6
06
KeyboardcandC4
48
84/101/104
7
07
KeyboarddandD
33
84/101/104
8
08
KeyboardeandE
19
84/101/104
9
09
KeyboardfandF
34
84/101/104
10
0A
KeyboardgandG
35
84/101/104
11
0B
KeyboardhandH
36
84/101/104
12
0C
KeyboardiandI
24
84/101/104
13
0D
KeyboardjandJ
37
84/101/104
14
0E
KeyboardkandK
38
84/101/104
15
0F
KeyboardlandL
39
84/101/104
16
10
KeyboardmandM4
52
84/101/104
17
11
KeyboardnandN
51
84/101/104
18
12
KeyboardoandO4
25
84/101/104
19
13
KeyboardpandP4
26
84/101/104
20
14
KeyboardqandQ4
17
84/101/104
21
15
KeyboardrandR
20
84/101/104
22
16
KeyboardsandS4
32
84/101/104
23
17
KeyboardtandT
21
84/101/104
24
18
KeyboarduandU
23
84/101/104
25
19
KeyboardvandV
49
84/101/104
26
1A
KeyboardwandW4
18
84/101/104
27
1B
KeyboardxandX4
47
84/101/104
28
1C
KeyboardyandY4
22
84/101/104
29
1D
KeyboardzandZ4
46
84/101/104
30
1E
Keyboard1and!4
2
84/101/104
31
1F
Keyboard2and@4
3
84/101/104
32
20
Keyboard3and#4
4
84/101/104
33
21
Keyboard4and$4
5
84/101/104
34
22
Keyboard5and%4
6
84/101/104
35
23
Keyboard6and^4
7
84/101/104
36
24
Keyboard7and&4
8
84/101/104
37
25
Keyboard8and*4
9
84/101/104
38
26
Keyboard9and(4
10
84/101/104
39
27
Keyboard0and)4
11
84/101/104
40
28
KeyboardReturn(ENTER)5
43
84/101/104
41
29
KeyboardESCAPE
110
84/101/104
42
2A
KeyboardDELETE
(Backspace)13
15
84/101/104
43
2B
KeyboardTab
16
84/101/104
44
2C
KeyboardSpacebar
61
84/101/104
45
2D
Keyboard-and(underscore)4
12
84/101/104
46
2E
Keyboard=and+4
13
84/101/104
47
2F
Keyboard[and{4
27
84/101/104
48
30
Keyboard]and}4
28
84/101/104
49
31
Keyboard\and|
29
84/101/104
50
32
KeyboardNon-US#and~2
42
84/101/104
51
33
Keyboard4
40
84/101/104
52
34
Keyboard‘and“4
41
84/101/104
53
35
KeyboardGraveAccentand
Tilde4
1
84/101/104
54
36
Keyboard,and<4
53
84/101/104
55
37
Keyboard.and>4
54
84/101/104
56
38
Keyboard/and?4
55
84/101/104
57
39
KeyboardCapsLock11
30
84/101/104
58
3A
KeyboardF1
112
84/101/104
59
3B
KeyboardF2
113
84/101/104
60
3C
KeyboardF3
114
84/101/104
61
3D
KeyboardF4
115
84/101/104
62
3E
KeyboardF5
116
84/101/104
63
3F
KeyboardF6
117
84/101/104
64
40
KeyboardF7
118
84/101/104
65
41
KeyboardF8
119
84/101/104
66
42
KeyboardF9
120
84/101/104
67
43
KeyboardF10
121
84/101/104
68
44
KeyboardF11
122
101/104
69
45
KeyboardF12
123
101/104
70
46
KeyboardPrintScreen1
124
101/104
71
47
KeyboardScrollLock11
125
84/101/104
72
48
KeyboardPause1
126
101/104
73
49
KeyboardInsert1
75
101/104
74
4A
KeyboardHome1
80
101/104
75
4B
KeyboardPageUp1
85
101/104
76
4C
KeyboardDeleteForward1
76
101/104
77
4D
KeyboardEnd1
81
101/104
78
4E
KeyboardPageDown1
86
101/104
79
4F
KeyboardRightArrow1
89
101/104
80
50
KeyboardLeftArrow1
79
101/104
81
51
KeyboardDownArrow1
84
101/104
82
52
KeyboardUpArrow1
83
101/104
83
53
KeypadNumLockandClear11
90
101/104
84
54
Keypad/1
95
101/104
85
55
Keypad*
100
84/101/104
86
56
Keypad-
105
84/101/104
87
57
Keypad+
106
84/101/104
88
58
KeypadENTER5
108
101/104
89
59
Keypad1andEnd
93
84/101/104
90
5A
Keypad2andDownArrow
98
84/101/104
91
5B
Keypad3andPageDn
103
84/101/104
92
5C
Keypad4andLeftArrow
92
84/101/104
93
5D
Keypad5
97
84/101/104
94
5E
Keypad6andRightArrow
102
84/101/104
95
5F
Keypad7andHome
91
84/101/104
96
60
Keypad8andUpArrow
96
84/101/104
97
61
Keypad9andPageUp
101
84/101/104
98
62
Keypad0andInsert
99
84/101/104
99
63
Keypad.andDelete
104
84/101/104
100
64
KeyboardNon-US\and|3;6
45
84/101/104
101
65
KeyboardApplication10
129
104
102
66
KeyboardPower9
103
67
Keypad=
104
68
KeyboardF13
105
69
KeyboardF14
106
6A
KeyboardF15
107
6B
KeyboardF16
108
6C
KeyboardF17
109
6D
KeyboardF18
110
6E
KeyboardF19
111
6F
KeyboardF20
112
70
KeyboardF21
113
71
KeyboardF22
114
72
KeyboardF23
115
73
KeyboardF24
116
74
KeyboardExecute
117
75
KeyboardHelp
118
76
KeyboardMenu
119
77
KeyboardSelect
120
78
KeyboardStop
121
79
KeyboardAgain
122
7A
KeyboardUndo
123
7B
KeyboardCut
124
7C
KeyboardCopy
125
7D
KeyboardPaste
126
7E
KeyboardFind
127
7F
KeyboardMute
128
80
KeyboardVolumeUp
129
81
KeyboardVolumeDown
130
82
KeyboardLockingCapsLock12
131
83
KeyboardLockingNumLock12
132
84
KeyboardLockingScroll
Lock12
133
85
KeypadComma
134
86
KeypadEqualSign
135
87
KeyboardKanji115
136
88
KeyboardKanji216
137
89
KeyboardKanji317
138
8A
KeyboardKanji418
139
8B
KeyboardKanji519
140
8C
KeyboardKanji620
141
8D
KeyboardKanji721
142
8E
KeyboardKanji822
143
8F
KeyboardKanji922
144
90
KeyboardLANG18
145
91
KeyboardLANG28
146
92
KeyboardLANG38
147
93
KeyboardLANG48
148
94
KeyboardLANG58
149
95
KeyboardLANG68
150
96
KeyboardLANG78
151
97
KeyboardLANG88
152
98
KeyboardLANG98
153
99
KeyboardAlternateErase7
154
9A
KeyboardSysReq/Attenti1
155
9B
KeyboardCancel
156
9C
KeyboardClear
157
9D
KeyboardPrior
158
9E
KeyboardReturn
159
9F
KeyboardSeparator
160
A0
KeyboardOut
161
A1
KeyboardOper
162
A2
KeyboardClear/Again
163
A3
KeyboardCrSel/Props
164
A4
KeyboardExSel
165-223
A5-DF
Reserved
224
E0
KeyboardLeftControl
58
84/101/104
225
E1
KeyboardLeftShift
44
84/101/104
226
E2
KeyboardLeftAlt
60
84/101/104
227
E3
KeyboardLeftGUI10;23
127
104
228
E4
KeyboardRightControl
64
101/104
229
E5
KeyboardRightShift
57
84/101/104
230
E6
KeyboardRightAlt
62
101/104
231
E7
KeyboardRightGUI10;24
128
104
232-255
E8-FF
Reserved
1.9 usb键盘的led指示灯
当按下小键盘,大小写,滚动锁定,组合键,KANA控制按键的时候,usb键盘按键urb会处理usb数据并上报数据给输入子系统处理
输入子系统对键值为小键盘,大小写,滚动锁定,组合键,KANA的事件做处理,处理后会调用输入设备的event方法也就是usb_kbd_event
static int usb_kbd_event(struct input_dev *dev, unsigned int type,unsigned int code, int value){ struct usb_kbd *kbd = input_get_drvdata(dev); //通过输入设备获取usb键盘设备 if (type != EV_LED) return -1; 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)); //判断是否有 小键盘,大小写,滚动锁定,组合键,KANA事件 if (kbd->led->status == -EINPROGRESS) return 0; if (*(kbd->leds) == kbd->newleds) //比较新旧指示灯状态,跟目前状态一致,则返回 return 0; *(kbd->leds) = kbd->newleds; //填充usb键盘led数据传输缓冲区 kbd->led->dev = kbd->usbdev; //捆绑usb设备 if (usb_submit_urb(kbd->led, GFP_ATOMIC)) //跟目前状态不一致,则提交usb键盘led urb 会通过控制输出端口发送setup包设置led灯状态 err_hid("usb_submit_urb(leds) failed"); return 0;}usb键盘led灯urb的回调函数
static void usb_kbd_led(struct urb *urb){ struct usb_kbd *kbd = urb->context; //通过urb获取usb键盘设备 if (urb->status) dev_warn(&urb->dev->dev, "led urb status %d received\n",urb->status); if (*(kbd->leds) == kbd->newleds) //比较新旧指示灯状态,跟目前状态一致,则返回 return; *(kbd->leds) = kbd->newleds; //填充usb键盘led数据传输缓冲区 kbd->led->dev = kbd->usbdev; //捆绑usb设备 if (usb_submit_urb(kbd->led, GFP_ATOMIC)) //跟目前状态不一致,提交usb键盘led urb 会通过控制输出端口发送setup包设置led灯状态 err_hid("usb_submit_urb(leds) failed");}urb会发送setup包,Set_Report请求包通过控制端点0,紧接着是个2字节的数据输出包,第一个字节对应报告id,第二个字节是led数据信息(上图)
2.0 后话 关于usb_kbd_event函数调用的流程
首先定义了一个键盘任务,任务会循环执行kbd_bh函数
这里定义的时候是禁用了,在后面的执行的kbd_init函数中会使能,和调度keyboard_tasklet任务
DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0); //创建keyboard_tasklet执行kbd_bhkbd_bh函数获取通过getleds函数获取led状态标志,然后最终会调用kbd_update_leds_helper函数
static void kbd_bh(unsigned long dummy){ unsigned char leds = getleds(); //获取led状态标志 if (leds != ledstate) { input_handler_for_each_handle(&kbd_handler, &leds,kbd_update_leds_helper); //会调用kbd_update_leds_helper ledstate = leds; }}getleds函数获取kbd->ledflagstate这个值,处理并返回.
static inline unsigned char getleds(void){ struct kbd_struct *kbd = kbd_table + fg_console; unsigned char leds; int i; if (kbd->ledmode == LED_SHOW_IOCTL) return ledioctl; leds = kbd->ledflagstate; //获取led标志状态 if (kbd->ledmode == LED_SHOW_MEM) { for (i = 0; i < 3; i++) if (ledptrs[i].valid) { if (*ledptrs[i].addr & ledptrs[i].mask) leds |= (1 << i); else leds &= ~(1 << i); } } return leds;}ldeflagstate的值可以由以下三个函数来设置
static inline void set_vc_kbd_led(struct kbd_struct * kbd, int flag){ kbd->ledflagstate |= 1 << flag;}static inline void clr_vc_kbd_led(struct kbd_struct * kbd, int flag){ kbd->ledflagstate &= ~(1 << flag);}static inline void chg_vc_kbd_led(struct kbd_struct * kbd, int flag){ kbd->ledflagstate ^= 1 << flag;}而这三个函数的调用情况如下,键盘按键处理事件
fn_caps_on >>> set_vc_kbd_led(kbd, VC_CAPSLOCK); //大小写ledk_shift >>> clr_vc_kbd_led(kbd, VC_CAPSLOCK); //大小写ledfn_caps_toggle >>> chg_vc_kbd_led(kbd, VC_CAPSLOCK); //大小写ledfn_bare_num >>> chg_vc_kbd_led(kbd, VC_NUMLOCK); //小键盘ledcon_stop >>> set_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK); //滚轮锁定ledcon_start >>> clr_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK); //滚轮锁定led获取led状态标志后,调用kbd_update_leds_helper函数,上报led事件
static int kbd_update_leds_helper(struct input_handle *handle, void *data){ unsigned char leds = *(unsigned char *)data; if (test_bit(EV_LED, handle->dev->evbit)) { input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 0x01)); //上报滚轮锁定事件 input_inject_event(handle, EV_LED, LED_NUML, !!(leds & 0x02)); //上报数字小键盘事件 input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & 0x04)); //上报大小写事件 input_inject_event(handle, EV_SYN, SYN_REPORT, 0); //同步事件 } return 0;}调用input_inject_event上报led事件,最终调用input_handle_event函数
void input_inject_event(struct input_handle *handle,unsigned int type, unsigned int code, int value){ struct input_dev *dev = handle->dev; struct input_handle *grab; unsigned long flags; if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); rcu_read_lock(); grab = rcu_dereference(dev->grab); if (!grab || grab == handle) input_handle_event(dev, handle->handler,type, code, value); //调用input_handle_event函数 rcu_read_unlock(); spin_unlock_irqrestore(&dev->event_lock, flags); }}EXPORT_SYMBOL(input_inject_event);input_handle_event函数处理各种事件分支,最终就会调用到input设备的event方法(usb_kbd_event)
static void input_handle_event(struct input_dev *dev,struct input_handler *src_handler,unsigned int type, unsigned int code, int value){ int disposition = INPUT_IGNORE_EVENT; switch (type) { case EV_SYN: //同步事件 switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL; break; case SYN_REPORT: //led同步事件分支 if (!dev->sync) { dev->sync = true; disposition = INPUT_PASS_TO_HANDLERS; } break; case SYN_MT_REPORT: dev->sync = false; disposition = INPUT_PASS_TO_HANDLERS; break; } break; case EV_KEY: if (is_event_supported(code, dev->keybit, KEY_MAX) &&!!test_bit(code, dev->key) != value) { if (value != 2) { __change_bit(code, dev->key); if (value) input_start_autorepeat(dev, code); else input_stop_autorepeat(dev); } disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_SW: if (is_event_supported(code, dev->swbit, SW_MAX) && !!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw); disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) disposition = input_handle_abs_event(dev, src_handler,code, &value); break; case EV_REL: if (is_event_supported(code, dev->relbit, REL_MAX) && value) disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC: if (is_event_supported(code, dev->mscbit, MSC_MAX)) disposition = INPUT_PASS_TO_ALL; break; case EV_LED: //led处理 if (is_event_supported(code, dev->ledbit, LED_MAX) && !!test_bit(code, dev->led) != value) { __change_bit(code, dev->led); //修改input设备的led标志位 disposition = INPUT_PASS_TO_ALL; } break; case EV_SND: if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value) __change_bit(code, dev->snd); disposition = INPUT_PASS_TO_ALL; } break; case EV_REP: if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { dev->rep[code] = value; disposition = INPUT_PASS_TO_ALL; } break; case EV_FF: if (value >= 0) disposition = INPUT_PASS_TO_ALL; break; case EV_PWR: disposition = INPUT_PASS_TO_ALL; break; } if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) dev->sync = false; if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) //led事件 dev->event(dev, type, code, value); //调用input设备的event方法(usb_kbd_event) if (disposition & INPUT_PASS_TO_HANDLERS) //led同步事件 input_pass_event(dev, src_handler, type, code, value); //会调用input_handler的event方法(kbd_event)}
如果觉得《usb键鼠标驱动分析》对你有帮助,请点赞、收藏,并留下你的观点哦!