1. 程式人生 > >(二)USB驅動程式_USB裝置驅動(Host)

(二)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完成處理函式中,完成報點。