usb键鼠标驱动分析

时间:2022-11-22 02:17:21

一、鼠标

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键鼠标驱动分析
当输入设备上报完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
};

usb键鼠标驱动分析

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);
}

usb键鼠标驱动分析

Usage
index
(dec)

Usage
Index
(hex)

Usage

Ref:typical
AT-101
position

PC-AT

Mac-
intosh

UNIX

Boot

Reserved (no event indicated) 9

N/A

Ö

Ö

Ö

84/101/104

Keyboard ErrorRollOver

N/A

Ö

Ö

Ö

84/101/104

Keyboard POSTFail

N/A

Ö

Ö

Ö

84/101/104

Keyboard ErrorUndefined

N/A

Ö

Ö

Ö

84/101/104

Keyboard a and A

Ö

Ö

Ö

84/101/104

Keyboard b and B

Ö

Ö

Ö

84/101/104

Keyboard c and C

Ö

Ö

Ö

84/101/104

Keyboard d and D

Ö

Ö

Ö

84/101/104

Keyboard e and E

Ö

Ö

Ö

84/101/104

Keyboard f and F

Ö

Ö

Ö

84/101/104

0A

Keyboard g and G

Ö

Ö

Ö

84/101/104

0B

Keyboard h and H

Ö

Ö

Ö

84/101/104

0C

Keyboard i and I

Ö

Ö

Ö

84/101/104

0D

Keyboard j and J

Ö

Ö

Ö

84/101/104

0E

Keyboard k and K

Ö

Ö

Ö

84/101/104

0F

Keyboard l and L

Ö

Ö

Ö

84/101/104

Keyboard m and M

Ö

Ö

Ö

84/101/104

Keyboard n and N

Ö

Ö

Ö

84/101/104

Keyboard o and O

Ö

Ö

Ö

84/101/104

Keyboard p and P

Ö

Ö

Ö

84/101/104

Keyboard q and Q

Ö

Ö

Ö

84/101/104

Keyboard r and R

Ö

Ö

Ö

84/101/104

Keyboard s and S

Ö

Ö

Ö

84/101/104

Keyboard t and T

Ö

Ö

Ö

84/101/104

Keyboard u and U

Ö

Ö

Ö

84/101/104

Keyboard v and V

Ö

Ö

Ö

84/101/104

1A

Keyboard w and W

Ö

Ö

Ö

84/101/104

1B

Keyboard x and X

Ö

Ö

Ö

84/101/104

1C

Keyboard y and Y

Ö

Ö

Ö

84/101/104

1D

Keyboard z and Z

Ö

Ö

Ö

84/101/104

1E

Keyboard 1 and ! 4

Ö

Ö

Ö

84/101/104

1F

Keyboard 2 and @

Ö

Ö

Ö

84/101/104

Keyboard 3 and #

Ö

Ö

Ö

84/101/104

Keyboard 4 and $

Ö

Ö

Ö

84/101/104

Keyboard 5 and %

Ö

Ö

Ö

84/101/104

Keyboard 6 and ^

Ö

Ö

Ö

84/101/104

Keyboard 7 and &

Ö

Ö

Ö

84/101/104

Keyboard 8 and *

Ö

Ö

Ö

84/101/104

Keyboard 9 and (

Ö

Ö

Ö

84/101/104

Keyboard 0 and ) 4

Ö

Ö

Ö

84/101/104

Keyboard Return(ENTER) 5

Ö

Ö

Ö

84/101/104

Keyboard ESCAPE

Ö

Ö

Ö

84/101/104

2A

Keyboard DELETE
(Backspace) 13

Ö

Ö

Ö

84/101/104

2B

Keyboard Tab

Ö

Ö

Ö

84/101/104

2C

Keyboard Spacebar

Ö

Ö

Ö

84/101/104

2D

Keyboard - and (underscore) 4

Ö

Ö

Ö

84/101/104

2E

Keyboard = and+

Ö

Ö

Ö

84/101/104

2F

Keyboard [ and {

Ö

Ö

Ö

84/101/104

Keyboard ] and }

Ö

Ö

Ö

84/101/104

Keyboard \ and |

Ö

Ö

Ö

84/101/104

Keyboard Non-US# and ~

Ö

Ö

Ö

84/101/104

Keyboard

Ö

Ö

Ö

84/101/104

Keyboard ‘ and “

Ö

Ö

Ö

84/101/104

Keyboard Grave Accent and

Tilde

Ö

Ö

Ö

84/101/104

Keyboard , and <

Ö

Ö

Ö

84/101/104

Keyboard . and >

Ö

Ö

Ö

84/101/104

Keyboard / and ? 4

Ö

Ö

Ö

84/101/104

Keyboard CapsLock

Ö

Ö

Ö

84/101/104

3A

Keyboard F1

Ö

Ö

Ö

84/101/104

3B

Keyboard F2

Ö

Ö

Ö

84/101/104

3C

Keyboard F3

Ö

Ö

Ö

84/101/104

3D

Keyboard F4

Ö

Ö

Ö

84/101/104

3E

Keyboard F5

Ö

Ö

Ö

84/101/104

3F

Keyboard F6

Ö

Ö

Ö

84/101/104

Keyboard F7

Ö

Ö

Ö

84/101/104

Keyboard F8

Ö

Ö

Ö

84/101/104

Keyboard F9

Ö

Ö

Ö

84/101/104

Keyboard F10

Ö

Ö

Ö

84/101/104

Keyboard F11

Ö

Ö

Ö

101/104

Keyboard F12

Ö

Ö

Ö

101/104

Keyboard PrintScreen

Ö

Ö

Ö

101/104

Keyboard ScrollLock

Ö

Ö

Ö

84/101/104

Keyboard Pause

Ö

Ö

Ö

101/104

Keyboard Insert

Ö

Ö

Ö

101/104

4A

Keyboard Home

Ö

Ö

Ö

101/104

4B

Keyboard PageUp

Ö

Ö

Ö

101/104

4C

Keyboard Delete Forward

Ö

Ö

Ö

101/104

4D

Keyboard End

Ö

Ö

Ö

101/104

4E

Keyboard PageDown

Ö

Ö

Ö

101/104

4F

Keyboard RightArrow

Ö

Ö

Ö

101/104

Keyboard LeftArrow

Ö

Ö

Ö

101/104

Keyboard DownArrow

Ö

Ö

Ö

101/104

Keyboard UpArrow

Ö

Ö

Ö

101/104

Keypad NumLock and Clear

Ö

Ö

Ö

101/104

Keypad /

Ö

Ö

Ö

101/104

Keypad *

Ö

Ö

Ö

84/101/104

Keypad -

Ö

Ö

Ö

84/101/104

Keypad +

Ö

Ö

Ö

84/101/104

Keypad ENTER5

Ö

Ö

Ö

101/104

Keypad 1 and End

Ö

Ö

Ö

84/101/104

5A

Keypad 2 and Down Arrow

Ö

Ö

Ö

84/101/104

5B

Keypad 3 and PageDn

Ö

Ö

Ö

84/101/104

5C

Keypad 4 and Left Arrow

Ö

Ö

Ö

84/101/104

5D

Keypad 5

Ö

Ö

Ö

84/101/104

5E

Keypad 6 and Righ tArrow

Ö

Ö

Ö

84/101/104

5F

Keypad 7 and Home

Ö

Ö

Ö

84/101/104

Keypad 8 and Up Arrow

Ö

Ö

Ö

84/101/104

Keypad 9 and PageUp

Ö

Ö

Ö

84/101/104

Keypad 0 and Insert

Ö

Ö

Ö

84/101/104

Keypad . and Delete

Ö

Ö

Ö

84/101/104

Keyboard Non-US\ and |3;6

Ö

Ö

Ö

84/101/104

Keyboard Application

Ö

Ö

Keyboard Power

Ö

Ö

Keypad =

Ö

Keyboard F13

Ö

Keyboard F14

Ö

6A

Keyboard F15

Ö

6B

Keyboard F16

6C

Keyboard F17

6D

Keyboard F18

6E

Keyboard F19

6F

Keyboard F20

Keyboard F21

Keyboard F22

Keyboard F23

Keyboard F24

Keyboard Execute

Ö

Keyboard Help

Ö

Keyboard Menu

Ö

Keyboard Select

Ö

Keyboard Stop

Ö

Keyboard Again

Ö

7A

Keyboard Undo

Ö

7B

Keyboard Cut

Ö

7C

Keyboard Copy

Ö

7D

Keyboard Paste

Ö

7E

Keyboard Find

Ö

7F

Keyboard Mute

Ö

Keyboard Volume Up

Ö

Keyboard Volume Down

Ö

Keyboard Locking Caps Lock

Ö

Keyboard Locking Num Lock

Ö

Keyboard Locking Scroll

Ö

Lock

Keypad Comma

Keypad Equal Sign

Keyboard Kanji1

Keyboard Kanji2

Keyboard Kanji3

8A

Keyboard Kanji4

8B

Keyboard Kanji5

8C

Keyboard Kanji6

8D

Keyboard Kanji7

8E

Keyboard Kanji8

8F

Keyboard Kanji9

Keyboard LANG1

Keyboard LANG2

Keyboard LANG3

Keyboard LANG4

Keyboard LANG5

Keyboard LANG6

Keyboard LANG7

Keyboard LANG8

Keyboard LANG9

Keyboard AlternateErase

9A

Keyboard SysReq/Attenti

9B

Keyboard Cancel

9C

Keyboard Clear

9D

Keyboard Prior

9E

Keyboard Return

9F

Keyboard Separator

A0

Keyboard Out

A1

Keyboard Oper

A2

Keyboard Clear/Again

A3

Keyboard CrSel/Props

A4

Keyboard ExSel

165-223

A5-DF

Reserved

E0

Keyboard LeftControl

58

Ö

Ö

Ö

84/101/104

E1

Keyboard LeftShift

44

Ö

Ö

Ö

84/101/104

E2

Keyboard LeftAlt

60

Ö

Ö

Ö

84/101/104

E3

Keyboard Left GUI10;23

127

Ö

Ö

Ö

E4

Keyboard RightControl

64

Ö

Ö

Ö

101/104

E5

Keyboard RightShift

57

Ö

Ö

Ö

84/101/104

E6

Keyboard RightAlt

62

Ö

Ö

Ö

101/104

E7

Keyboard Right GUI10;24

128

Ö

Ö

Ö

232-255

E8-FF

Reserved

 

1.9 usb键盘的led指示灯

usb键鼠标驱动分析

当按下小键盘,大小写,滚动锁定,组合键,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_bh

kbd_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);	//大小写led
k_shift >>> clr_vc_kbd_led(kbd, VC_CAPSLOCK); //大小写led
fn_caps_toggle >>> chg_vc_kbd_led(kbd, VC_CAPSLOCK); //大小写led fn_bare_num >>> chg_vc_kbd_led(kbd, VC_NUMLOCK); //小键盘led con_stop >>> set_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK); //滚轮锁定led
con_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)
}