(二)USB驱动程序_USB设备驱动(Host)

USB设备驱动(Host)

深入,并且广泛
			-沉默犀牛

有了第一篇文章的基础,我们这篇文章来看一下USB设备驱动的源码。与其他的Driver一样,USB的driver也表现为一个结构体:struct usb_driver

驱动整体结构

在编写新的USB设备驱动时,主要应该完成的工作是probe()和disconnect()函数,它们分别在Device被插入和拔出的时候调用,用于初始化和释放软硬件资源。usb_driver结构体中的id_table成员描述了这个USB驱动所支持的USB设备列表,它指向一个usb_device_id数组,实例如下:

static const struct usb_device_id skel_table[] = {
	{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
	{ }					/* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, skel_table);

static struct usb_driver skel_driver = {
	.name =		"skeleton",
	.probe =	skel_probe,			//probe
	.disconnect =	skel_disconnect,		//disconnent
	.suspend =	skel_suspend,
	.resume =	skel_resume,
	.pre_reset =	skel_pre_reset,
	.post_reset =	skel_post_reset,
	.id_table =	skel_table,			//id_table
	.supports_autosuspend = 1,
};

USB_DEVICE这个宏是可以根据Vendor ID和Product ID生成一个usb_device_id结构体的实例化。
当USB core检测到某个Device的属性和某个Drivre的usb_device_id结构体所携带的信息一致的时候,这个Driver的probe()函数就被执行(如果这个USB驱动是个模块的话,相关的.ko还应被Linux自动加载)。拔掉Device或者卸载Drivre模块后,USB核心就执行disconnect()函数来响应这个动作。

usb_driver结构体中的函数是USB设备驱动中与USB相关的部分,而USB只是一个总线,USB设备驱动真正的主体工作仍然是USB设备
本身所属类型的驱动,比如字符设备、tty设备、块设备、输入设备等。
因此USB设备驱动包含其作为
1.总线上挂接设备的驱动
2.本身所属设备类型的驱动两部分

尽管USB本身所属设备驱动的结构与其挂不挂在USB总线上没什么关系,但是据此在访问方式上却有很大的变化,例如,对于USB的TP而言,尽管仍然是中断服务函数、固件升级这些函数,但是在这些函数中,贯穿始终的是称为URB的USB请求块

举个书中的例子:
我们把整个USB构架(Host侧)看做一颗大叔,HostController是树根,树叶比作具体的USB设备,树干和树枝就是USB总线树叶本身与树枝是通过usb_driver结构体连接,而树叶本身的驱动(读写、控制)则需要通过树叶设备本身所属类设备驱动来完成。树根和树叶之间的“通信”依靠在树干和树枝中“流淌”的URB完成

由此可见,usb_driver本身只是有找到USB设备,管理USB设备连接和断开的作用,也就是说,它是公司入口处的“打卡机”,可以获得员工(USB设备)的上下班情况。树叶和员工一样,可以是研发工程师,也可以是销售,而作为USB设备的树叶可以是字符树叶、网络树叶或块树叶,因此必须实现相应设备类的驱动

URB介绍

URB结构体

根据上面的描述,我们知道了URB承载着USB设备和Host controller通信的核心数据,使用struct urb来描述:
仅注释了重要成员

struct urb {
	/* private: usb core and host controller only fields in the urb */
	struct kref kref;		
	void *hcpriv;			
	atomic_t use_count;		
	atomic_t reject;		
	int unlinked;			

	/* public: documented fields in the urb that can be used by drivers */
	struct list_head urb_list;
					
	struct list_head anchor_list;	
	struct usb_anchor *anchor;
	struct usb_device *dev;		//urb所要发送的目标 struct usb_device指针。该变量在urb可以被发送到
					  USB core之前,必须由USB驱动程序初始化
	struct usb_host_endpoint *ep;	
	unsigned int pipe;		//urb所要发送的特定目标struct usb_device的端点信息。该变量在urb可以
					  被发送到USB core之前,必须由USB驱动程序初始化
	unsigned int stream_id;		
	
	int status;			//当urb结束之后,或者正在被USB core处理时,该变量被设置为urb的当前状态
					  这是为了防止当urb被USB core处理时竞态的发生。
	
	unsigned int transfer_flags;	//该变量可以设为许多不同的位值,取决与USB驱动程序对urb的具体操作
					  详情见LDD3 P333
					  
	void *transfer_buffer;		//指向用于发送数据到设备(OUT urb)或者从设备接受数据(IN urb)的缓冲区
					  的指针,为了使Host controller可以正确的访问该缓冲区,必须使用kmalloc
					  来创建它,而不是在栈中或者静态内存中。对于控制端点,该缓冲区用于传输数
					  据的中转
					  
	dma_addr_t transfer_dma;	// 用于以DMA方式传输数据到USB设备的缓冲区
	struct scatterlist *sg;		
	int num_mapped_sgs;		
	int num_sgs;			
	u32 transfer_buffer_length;	//transfer_buffer或者transfer_dma变量所指向的缓冲区的大小(因为一个
					  urb 只能使用其中的一个)。若值为0,两个传输缓冲区都没有被USB core使用
					  
	u32 actual_length;		//当urb结束之后,该变量被设置为urb所发出的数据(OUT urb)或者urb所接收
					  的数据(IN urb)的实际长度。对于IN urb,必须使用该变量而不是
					  transfer_buffer_length变量,因为所接收的数据可能小于整个缓冲区的尺寸
	
	unsigned char *setup_packet;	//指向控制urb的设置数据包的指针。它在传输缓冲区中的数据之前被传送。该变量
				  	  只对控制urb有效
				  	  
	dma_addr_t setup_dma;		//控制urb用于设置数据包的DMA缓冲区。它在普通传输 缓冲区中的数据之前被传送
					  该变量只对控制urb有效
					  
	int start_frame;		//设置或者返回初始的帧数量,用于等时传输
	
	int number_of_packets;		//仅对等时urb有效,指定该urb所助理的等时传输缓冲区的数量。对于等时urb,在
					  urb被发送到USB core之前,USB驱动程序必须设置该值
					  
	int interval;			//urb被轮询的时间间隔。仅对中断或者等时urb有效
	
	int error_count;		//有USB core设置,仅用于等时urb结束之后。它表示报告了任何一种类型错误的等
					  时传输的数量
	
	void *context;			//指向一个可以被USB驱动程序设置的数据块。它可以在结束处理程序中,当urb被返
					  回到驱动程序时使用。
		
	usb_complete_t complete;	//指向一个结束处理程序的指针,让urb被完全传输 或者发生错误时,USB core将调
					  用该函数。在该函数中,USB驱动程序可以检查urb,释放它,或者把它重新提交到
					  另一个传输中去
					  
	struct usb_iso_packet_descriptor iso_frame_desc[0];
					//仅对等时urb有效。
					
};
URB处理流程

一个典型的urb生命周期如下:
1.由USB设备驱动程序创建
2.分配给一个特定的USB设备的特定端点
3.由USB设备驱动程序递交给USB core
4.由USB core递交到特定设备的特定USB Host controller驱动程序
5.由USB Host controller驱动程序处理,它从设备进行USB传送
6.当urb结束之后,USB Host controller驱动程序通知USB设备驱动程序

urb可以在任何时候被递交该urb的驱动程序取消掉,或者被USB核心取消,如果该设备已从系统中移除。urb被动态的创建,它包含一个内部引用计数,使得它们可以在最后一个使用者释放它们时自动地销毁

实例分析

我们刚刚分析了USB驱动的整体结构,也说明了URB的用途和处理流程,接下来我们就找一个实际的例子来看看以下是kernel/drivers/input/touchscreen/usbtouchscreen.c文件中的probe函数
(只保留了有关input 和 urb处理的部分)

static int usbtouch_probe(struct usb_interface *intf,
			  const struct usb_device_id *id)
{
	struct usbtouch_usb *usbtouch;
	struct input_dev *input_dev;
	struct usb_endpoint_descriptor *endpoint;
	struct usb_device *udev = interface_to_usbdev(intf);
	struct usbtouch_device_info *type;
	int err = -ENOMEM;
	...
	input_dev = input_allocate_device(); 				 //input	
	...
	usbtouch->type = type;
	if (!type->process_pkt)
		type->process_pkt = usbtouch_process_pkt;  		//之后会在urb完成处理函数中调用

	...
	usbtouch->irq = usb_alloc_urb(0, GFP_KERNEL);			//urb的创建,(urb流程的第一步)

	...
	input_dev->name = usbtouch->name;
	input_dev->phys = usbtouch->phys;
	usb_to_input_id(udev, &input_dev->id);
	input_dev->dev.parent = &intf->dev;

	input_set_drvdata(input_dev, usbtouch);

	input_dev->open = usbtouch_open;
	input_dev->close = usbtouch_close;

	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
	input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0);
	input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0);
	if (type->max_press)
		input_set_abs_params(input_dev, ABS_PRESSURE, type->min_press,
		                     type->max_press, 0, 0)
	...
												
									//中间这一部分是关于input的设置

	if (usb_endpoint_type(endpoint) == USB_ENDPOINT_XFER_INT)	
		usb_fill_int_urb(usbtouch->irq, udev,			//分配urb给特定USB的特定端点(urb处理流程第二步)
			 usb_rcvintpipe(udev, endpoint->bEndpointAddress),
			 usbtouch->data, usbtouch->data_size,
			 usbtouch_irq, usbtouch, endpoint->bInterval);	//usbtouch_irq就是urb处理完成函数
	else
		usb_fill_bulk_urb(usbtouch->irq, udev,
			 usb_rcvbulkpipe(udev, endpoint->bEndpointAddress),
			 usbtouch->data, usbtouch->data_size,
			 usbtouch_irq, usbtouch);
	...

	err = input_register_device(usbtouch->input);			//注册到input子系统
	...

	if (usbtouch->type->irq_always) {				//this can‘t fail  一定会进这个if
		/* this can't fail */
		usb_autopm_get_interface(intf);
		err = usb_submit_urb(usbtouch->irq, GFP_KERNEL);	//递交给USB core (urb处理流程第四步)
		...
	}

	return 0;
	...
}

上述urb处理流程的第4、 5步由USB core来处理,等到urb处理完,会调用urb处理完成函数,如下:

static void usbtouch_irq(struct urb *urb)
{
	struct usbtouch_usb *usbtouch = urb->context;
	struct device *dev = &usbtouch->interface->dev;
	int retval;

	switch (urb->status) {					//在完成处理函数中,一般都会判断一下urb的状态
	case 0:
		/* success */
		break;
	case -ETIME:
		/* this urb is timing out */
		dev_dbg(dev,
			"%s - urb timed out - was the device unplugged?\n",
			__func__);
		return;
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
	case -EPIPE:
		/* this urb is terminated, clean up */
		dev_dbg(dev, "%s - urb shutting down with status: %d\n",
			__func__, urb->status);
		return;
	default:
		dev_dbg(dev, "%s - nonzero urb status received: %d\n",
			__func__, urb->status);
		goto exit;
	}

	usbtouch->type->process_pkt(usbtouch, usbtouch->data, urb->actual_length);	
							//还记得上面说的拿个(要被urb完成处理函数调用的)函数吗?
	...
}

以下就是usbtouch->type->process_pkt所指向的函数

static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch,
                                 unsigned char *pkt, int len)
{								//明显就是上报点的函数
	struct usbtouch_device_info *type = usbtouch->type;

	if (!type->read_data(usbtouch, pkt))
			return;

	input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch);

	if (swap_xy) {
		input_report_abs(usbtouch->input, ABS_X, usbtouch->y);
		input_report_abs(usbtouch->input, ABS_Y, usbtouch->x);
	} else {
		input_report_abs(usbtouch->input, ABS_X, usbtouch->x);
		input_report_abs(usbtouch->input, ABS_Y, usbtouch->y);
	}
	if (type->max_press)
		input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press);
	input_sync(usbtouch->input);
}

这样我们就用这个usbtouchscreen.c文件中的代码理清了USB设备驱动的用法,一般的挂载在i2c总线上的TP是通过触发中断来执行中断处理函数,在中断处理函数的下半部完成报点,而挂载在USB总线后,是通过urb处理完成,来出发urb完成处理函数,在urb完成处理函数中,完成报点。

猜你喜欢

转载自blog.csdn.net/qq_35065875/article/details/83014093