声明:本文章是看完韦东山老师的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键盘和鼠标