分析USB鼠标——usbmouse.c

2019-07-12 23:09发布

声明:本文章是看完韦东山老师的USB鼠标驱动的视频后,结合usb鼠标的驱动程序锁写的自己对usbmouse.c的分析,如果有与您重复的地方,敬请原谅。同时希望大家可以通过本文了解到usb驱动鼠标。而我认为我们不应该只是学习老师所教的课程,而自己独立的分析内核代码也是我们所要学习的内容。 下面就要讲到对内核中usbmouse.c的分析,而在我分析之前,我要几个要点是希望大家可以明白的,因为只有明白了这些知识点,你才可以更好的了解我对usbmouse.c的分析。 1.usb是主从结构的,而我们缩写的驱动程序是为匹配从设备所写的驱动程序 2.说到usb驱动就要讲到usb_bus_type,要了解USB总线类型,只有了解这些才可以对下面的分析充分了解。否则只能是雾里看花 3.对usb的插入拔出主设备的过程有所了解, 4.还有就是对usb的描述符有所了解, 以上这些如果有不明白的我建议你可以看一下我写的文章: USB原理以及对照hub.c的代码分析 嵌入式Linux —— usb鼠标驱动 而在分析这代码之前我们要介绍一下usb驱动代码的大致格式: 1.分配设置:usb_driver 2.在入口函数中注册,在出口函数中注销 下面我们就进行代码分析,分析一个驱动程序就是要从他的入口程序开始分析:   static int __init usb_mouse_init(void) { int retval = usb_register(&usb_mouse_driver); //注册usb_driver结构体 if (retval == 0) info(DRIVER_VERSION ":" DRIVER_DESC); return retval; } 我们可以看出这个入口函数中注册了usb_driver 结构体,而这个结构体做了什么那?我们看一下他的设置:     static struct usb_driver usb_mouse_driver = { .name = "usbmouse", .probe = usb_mouse_probe, .disconnect = usb_mouse_disconnect, .id_table = usb_mouse_id_table, };
我们可以看到这个结构体中定义了name,probe函数,disconnect函数,以及id_table结构体,而后面的三项是并不可少的,而id_table就是用来与device匹配时进行比较的。只有匹配成功才可以调用probe函数。我们先来看一下这个id_table中定义了什么:     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 */ }; 代码中的USB_INTERFACE_INFO是个结构体:     /** * USB_INTERFACE_INFO - macro used to describe a class of usb interfaces * @cl: bInterfaceClass value * @sc: bInterfaceSubClass value * @pr: bInterfaceProtocol value * * This macro is used to create a struct usb_device_id that matches a * specific class of interfaces. */ #define USB_INTERFACE_INFO(cl,sc,pr) .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
通过注解可以知道这个宏是用来比较特定接口的结构体,而结合上边的id_table我们就可以知道,这个是通过比较接口类,接口子类以及接口协议来与设备进行匹配的。如果匹配成功那就将调用probe函数了:     static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
内核中的probe函数喜欢先做初始化相关的工作:     struct usb_device *dev = interface_to_usbdev(intf); //定义一个usb_device的机构体 struct usb_host_interface *interface; //定义一个usb主机接口 struct usb_endpoint_descriptor *endpoint; //定义一个端点描述符 struct usb_mouse *mouse; //定义一个usb_mouse结构体 struct input_dev *input_dev; //定义一个input_dev结构体 int pipe, maxp; //定义源和最大包数量 int error = -ENOMEM; interface = intf->cur_altsetting; //获得当前的接口 if (interface->desc.bNumEndpoints != 1) //对当前接口的端点的判断 return -ENODEV; endpoint = &interface->endpoint[0].desc; //获得端点,此处的endpoint[0]不是端点0,而是端点1 if (!usb_endpoint_is_int_in(endpoint)) //判断此端点是否为输入中断端点(鼠标是输入端点) return -ENODEV; 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(); //分配input_dev结构体 if (!mouse || !input_dev) goto fail1; mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma); //获得目的地址 if (!mouse->data) goto fail1; mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //分配urb(usb request block),用于传输数据 if (!mouse->irq) goto fail2; mouse->usbdev = dev; //将usb_device设备放入usb_mouse中 mouse->dev = input_dev; // 将input_dev放入usb_mouse中   从上面的代码可以看出,到这里probe主要做的就是定义各种将要用到的设备结构体,设备描述符结构体,并为其分配空间。同时还将通信时的源以及目标定义并获得相应的地址,并且定义了urb用于通信。 而下面这些则是通过usb_device和input_dev向usb_mouse这个结构体中添加数据:   if (dev->manufacturer) strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name)); //生产商 if (dev->product) { if (dev->manufacturer) strlcat(mouse->name, " ", sizeof(mouse->name)); strlcat(mouse->name, dev->product, sizeof(mouse->name)); //产品 } if (!strlen(mouse->name)) snprintf(mouse->name, sizeof(mouse->name), "USB HIDBP Mouse %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), //设备ID le16_to_cpu(dev->descriptor.idProduct)); //产品ID usb_make_path(dev, mouse->phys, sizeof(mouse->phys)); //?? strlcat(mouse->phys, "/input0", sizeof(mouse->phys)); input_dev->name = mouse->name; //设备名字 input_dev->phys = mouse->phys; //设备的物理地址 usb_to_input_id(dev, &input_dev->id); //input_dev 的ID input_dev->dev.parent = &intf->dev; //设备的父类 而下面的代码就是要设置input_dev结构体了:     input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL); //定义产生按键类和相对位移类事件 input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); //按键类中的左键,右键,中键 input_dev->relbit[0] = BIT(REL_X) | BIT(REL_Y); //相对位移类的X和Y方向的位移 input_dev->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA); //按键中的侧键以及附加键 input_dev->relbit[0] |= BIT(REL_WHEEL); //相对位移中的滚轮位移 input_set_drvdata(input_dev, mouse); //设置input_dev中的私有数据 input_dev->open = usb_mouse_open; //定义开函数:应用层打开鼠标设备时此函数被调用 input_dev->close = usb_mouse_close; //定义关函数:应用层关闭鼠标设备时此函数被调用 input_dev->close = usb_mouse_close; //定义关函数:应用层关闭鼠标设备时此函数被调用     而probe函数之后做的工作就是设置urb从而设置数据的传输:       /**  * usb_fill_int_urb - macro to help initialize a interrupt urb  * @urb: pointer to the urb to initialize.  * @dev: pointer to the struct usb_device for this urb.  * @pipe: the endpoint pipe  * @transfer_buffer: pointer to the transfer buffer  * @buffer_length: length of the transfer buffer  * @complete_fn: pointer to the usb_complete_t function  * @context: what to set the urb context to.  * @interval: what to set the urb interval to, encoded like  * the endpoint descriptor's bInterval value.  *  * Initializes a interrupt urb with the proper information needed to submit  * it to a device.  * Note that high speed interrupt endpoints use a logarithmic encoding of  * the endpoint interval, and express polling intervals in microframes  * (eight per millisecond) rather than in frames (one per millisecond).  */ usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, (maxp > 8 ? 8 : maxp), usb_mouse_irq, mouse, endpoint->bInterval); mouse->irq->transfer_dma = mouse->data_dma; //告诉urb物理地址 mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //告诉urbMAP标记位 the endpoint descriptor's bInterval value.  *  * Initializes a interrupt urb with the proper information needed to submit  * it to a device.  * Note that high speed interrupt endpoints use a logarithmic encoding of  * the endpoint interval, and express polling intervals in microframes  * (eight per millisecond) rather than in frames (one per millisecond).  */ usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, (maxp > 8 ? 8 : maxp), usb_mouse_irq, mouse, endpoint->bInterval); mouse->irq->transfer_dma = mouse->data_dma; //告诉urb物理地址 mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //告诉urbMAP标记位 usb_fill_int_urb函数的参数分别为:urb结构体,usb_device结构体,pipe源,目的地址(此处为虚拟地址),数据长度,中断函数(或完成函数),上下文,以访问时间间隔。
    最后probe会注册input_dev,并将usb_mouse这个结构体放入usb_interface中:   error = input_register_device(mouse->dev); usb_set_intfdata(intf, mouse);
虽然probe函数讲完了,但是他里面的usb_mouse_irq,usb_mouse_open函数还没有完善。下面我们一边讲解一边完善。我们知道主机控制器会不断的查询usb设备,当获得数据后就会将数存储到buffer中并产生中断函数从而导致usb_mouse_irq函数被调用,下面我就将这个函数:     static void usb_mouse_irq(struct urb *urb) { struct usb_mouse *mouse = urb->context; //定义usb_mouse signed char *data = mouse->data; //定义目的 struct input_dev *dev = mouse->dev; //定义input_dev int status; 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_mouse_irq函数主要的工作就是上报数据。我们说过在usb驱动中我们没有直接直接对硬件的读写函数,而是通过usb总线驱动程序获得读写的函数,但是驱动知道数据的含义,所以他会对数据进行处理。并将这些数据通过urb传输。   而接下来我们要介绍的就是usb_mouse_open函数。当应用层打开鼠标设备时,usb_mouse_open将被调用,这句话告诉我们这个函数什么时候被调用。 代码为:   static int usb_mouse_open(struct input_dev *dev) { struct usb_mouse *mouse = input_get_drvdata(dev); mouse->irq->dev = mouse->usbdev; if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //上报urb return -EIO; return 0; }
从上面的代码中我们知道,usb_mouse_open函数的主要工作就是上报urb,当应用层打开鼠标设备时,就上报urb,然后就回到上面中断函数中的不断将按键和位移上报。   讲到这里,这个驱动程序就讲完了,而我还是想说前面的那句话,作为要学习驱动的同学,有时候多看看内核自己的驱动代码还是有好处的,虽然有时候你会觉得很难看懂,这时候你可以去网上找一下,也许有很好的解释。 这里我贴一篇对我有帮助的文章: 嵌入式linux下如何使用usb键盘和鼠标