linux下的usb鼠标驱动在/drivers/hid/usbhid/usbmouse.c中实现
linux下的usb键盘驱动在/drivers/hid/usbhid/usbkbd.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鼠标设备
- }