分析USB鼠标——usbmouse.c

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

 


 

 

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值