天天看點

簡單分析USB裝置驅動架構(秒懂~)

作者:linux上的碼農

在生活、工作中經常會接觸到USB裝置,如滑鼠、鍵盤、攝像頭、可移動硬碟、掃碼槍等。這些裝置通過USB接口連接配接到電腦上後,電腦會立刻提示“檢測到新硬體...”、安裝驅動等。這裡需要強調下USB裝置使用的是USB總線,window或Linux核心中都會自帶usb總線驅動,是以接上USB裝置後,主機能夠立刻檢測到,提醒需要安裝裝置驅動是指安裝USB裝置驅動。

USB裝置驅動使用USB總線,是以很多操作由USB總線驅動幫我們完成了,我們隻需要的按照總線、裝置、驅動架構來實作USB裝置的驅動既可。USB裝置資料的讀寫操作由總線驅動現在,我們可以直接使用總線讀取到的資料,然後解析這些資料的含義、再進行相關的操作就可以了(這裡需要注意的一點是USB總線驅動隻提供USB裝置的讀寫操作函數,這函數是通用的,即裡面的資料的含義總線驅動并不知道)。

USB裝置驅動的架構圖下,具體的代碼可以參考核心中的/drivers/hid/usbhid/usbmouse.c

配置設定/設定usb_driver結構體,填充裡面的.id_table、.probe、.disconnect成員。

調用usb_register把usb_driver結構體注冊到核心中。

static struct usb_driver usbtouch_driver = {
	.name		= "myusb",
	.probe		= usbtouch_probe,
	.disconnect	= usbtouch_disconnect,
	.id_table	= usbtouch_devices,
};
 
static int __init usbtouch_init(void)
{
	return usb_register(&usbtouch_driver);
}
 
static void __exit usbtouch_cleanup(void)
{
	usb_deregister(&usbtouch_driver);
}
           

struct usb_driver中的id_table成員是用與和usb裝置進行比對的選項,表示這個驅動支援的裝置。

.probe成員為函數指針,就是在裝置和驅動比對成功的時候執行。

.disconnect成員也為函數指針,在裝置和驅動關聯成功後再斷開執行,如拔了USB裝置或解除安裝USB裝置驅動。

更多linux核心視訊教程文檔資料免費領取背景私信【核心】自行擷取.

簡單分析USB裝置驅動架構(秒懂~)
簡單分析USB裝置驅動架構(秒懂~)

Linux核心源碼/記憶體調優/檔案系統/程序管理/裝置驅動/網絡協定棧-學習視訊教程-騰訊課堂

.probe函數中我們需要處理的事情如下(這裡以滑鼠為例子):

建立input_dev裝置

設定input_dev支援的類,需要支援按鍵類、相對位移類。

設定input_dev支援的事件,能夠産生左、中、右、以及側邊附加按鍵事件,還有xy方向上的相對位移事件和滾輪滑動事件。

注冊input_dev到input輸入子系統中。

硬體相關的操作,不同的裝置的操作存在差異

USB總線為組從結構,USB總線驅動隻能輪詢去擷取USB裝置上的資料,USB裝置不能主動通知USB主機傳輸資料。在USB驅動中需要建構urb(usb request block)結構體,然後填充使用。urb中要指定USB裝置資料存放到主機上的實體位址以及虛拟位址,還有USB資料傳輸的方向、長度,和裝置通信的端點等。

使用usb_alloc_urb()函數配置設定urb結構體,結束後使用usb_free_urb()釋放這個結構體。

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
/*用于配置設定urb結構體給usb驅動使用*/
 /*  If the driver want to use this urb for interrupt, control, or bulk
 * endpoints, pass '0' as the number of iso packets.
 * The driver must call usb_free_urb() when it is finished with the urb.
*/
           

USB裝置的資料要存放在主機上的什麼地方,USB裝置驅動中需要指明。

void *usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma)
/*用于配置設定usb緩存,用于存放讀寫的資料*/
/*@dma: used to return DMA address of buffer 這裡是記憶體的實體位址
*函數傳回的是虛拟位址
*/
           

找到對應的裝置端點和資料長度

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/*pipe為源位址*/
len = endpoint->wMaxPacketSize;
/*len 為一次傳輸的資料的大小,即包的長度*/
           

現在資料傳輸的三要素(源、長度、目的)都确定了,剩下的就是要進行填充urb結構體,即告訴主機資料傳輸的三要素。

static inline void usb_fill_int_urb (struct urb *urb,struct usb_device *dev,
                     unsigned int pipe,void *transfer_buffer,
				     int buffer_length,usb_complete_t complete_fn,
				     void *context,int interval)
 / * 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.
 */
	mouse->irq->transfer_dma = mouse->data_dma;
	mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
           

調用usb_fill_int_urb(),其中complete_fn相當于中斷函數,即主機讀取完資料會放到指定的記憶體data_dma中。然後通知CPU處理這些資料,這些資料隻有USB裝置驅動程式知道其含義。是以要在這個完成函數中處理這些資料,如上報。

最後就是送出usr請求塊了。

usb_submit_urb(urb, GFP_ATOMIC);
           

到這裡基本上就結束了,剩下的就是disconnect函數的操作。這個函數和.probe完全相反,申請了的資源需要在這裡釋放。

看到這裡是不是感覺USB驅動很簡單啊!架構确實很簡單!很簡單!很簡單!重要的事情要說三遍,要相信你們的實力。

下面的代碼是我在ARM闆子上的針對usb滑鼠寫的。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/slab.h>
 
/*USB裝置驅動可以使用input輸入子系統架構來完成資料的讀入即上報
*和傳統的input子系統存在差異,傳統的input輸入子系統是在中斷函數裡面擷取資料然後input_event上報資料
*在usb裝置中,資料的讀取由usb總線驅動完成,這個可以直接使用urb子產品來實作。然後再urb的中斷中上報資料
*/
 
static struct input_dev *input_dev;
static struct urb *urb;
static dma_addr_t data_dma;
static unsigned char *data;
static unsigned len;
 
static void usbtouch_irq(struct urb *urb)
{
	#if 1
	int status;
 
	switch (urb->status) {
	case 0:			/* success */
		break;
	case -ECONNRESET:	/* unlink */
	case -ENOENT:
	case -ESHUTDOWN:
		return;
	/* -EPIPE:  should clear the halt */
	default:		/* error */
		goto resubmit;
	}
	/*在usb裝置驅動程式中,需要對資料進行解析。即usb總線擷取到資料不會解析資料的含義
	*需要在usb裝置驅動中進行解析資料,我的usb數百data[1]為按鍵的值
	*data[2]為滑鼠在x方向上的相對位移值,data[3]為滑鼠在y方向上的相對位移值
	*data[4]為滑鼠滾輪滑動産生的值
	*/
	input_report_key(input_dev, BTN_LEFT,   data[1] & 0x01);
	input_report_key(input_dev, BTN_RIGHT,  data[1] & 0x02);
	input_report_key(input_dev, BTN_MIDDLE, data[1] & 0x04);
	input_report_key(input_dev, BTN_SIDE,   data[0] & 0x08);
	input_report_key(input_dev, BTN_EXTRA,  data[0] & 0x10);
 
	input_report_rel(input_dev, REL_X,     data[2]);
	input_report_rel(input_dev, REL_Y,     data[3]);
	input_report_rel(input_dev, REL_WHEEL, data[4]);
 
	input_sync(input_dev);
resubmit:
	/*上報後,重新送出urb請求*/
	status = usb_submit_urb (urb, GFP_ATOMIC);
	
	#else
	/*列印USB總線驅動擷取到的資料,分析資料。解析其資料含義*/
	int i =0,status;
	for(i = 0; i < len;i++){
		printk("%x ",data[i]);
	}
	printk("\n");
	status = usb_submit_urb (urb, GFP_ATOMIC);
	#endif
}
 
static int usbtouch_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
	int pipe;
	struct usb_device *udev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;

	interface = intf->cur_altsetting;
	/*除端點0外的第一個端點,并不是指裝置接口配置中的端點0*/
	endpoint = &interface->endpoint[0].desc;

	/*1.配置設定一個input_dev結果體*/
	input_dev = input_allocate_device();
	if(!input_dev){return -1;}
	/*2.設定結構體中的支援的類以及事件。滑鼠要支援案件類和相對位移類事件*/
	input_dev->name = "usb-mouse";

	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);
	input_dev->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
	input_dev->relbit[0] |= BIT(REL_WHEEL);


	/*3.注冊input_dev到系統中*/
	input_register_device(input_dev);
	/*4.硬體相關的操作。主要是usb資料的擷取以及上報*/
	urb=usb_alloc_urb(0, GFP_KERNEL);
	/*傳輸資料的來源,裝置、端點、方向等組合*/
	pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
	/*和裝置驅動互動一次傳輸的資料長度*/
	len = endpoint->wMaxPacketSize;
	/*USB總線驅動擷取到的資料存放的記憶體*/
	data = usb_buffer_alloc(udev, len,GFP_ATOMIC, &data_dma);
	urb->dev = udev;
	urb->transfer_dma = data_dma;
	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
	/*上下文參數(這裡使用的NULL),可以用來給完成函數提供參數*/
	usb_fill_int_urb(urb, udev,pipe,data, len,usbtouch_irq, NULL, endpoint->bInterval);
	/*送出urb請求*/		 
	usb_submit_urb(urb, GFP_ATOMIC);		 
	return 0;
}
 
static void usbtouch_disconnect(struct usb_interface *intf)
{
	usb_kill_urb(urb);
	input_unregister_device(input_dev);
	usb_free_urb(urb);
	usb_buffer_free(interface_to_usbdev(intf), len, data,data_dma);
	input_free_device(input_dev);
}
/*定義裝置驅動支援的裝置*/
static struct usb_device_id usbtouch_devices [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};
 
static struct usb_driver usbtouch_driver = {
	.name		= "myusb",
	.probe		= usbtouch_probe,
	.disconnect	= usbtouch_disconnect,
	.id_table	= usbtouch_devices,
};
 
static int __init usbtouch_init(void)
{
	return usb_register(&usbtouch_driver);
}
 
static void __exit usbtouch_cleanup(void)
{
	usb_deregister(&usbtouch_driver);
}
 
module_init(usbtouch_init);
module_exit(usbtouch_cleanup);
 
MODULE_LICENSE("GPL");
 
           

測試如下圖,按鍵和移動滑鼠都能産生相應的事件碼。這裡/dev/event0的格式就不說了,要是不太清楚的可以參考input輸入子系統。

usb驅動裝置使用了input輸入子系統架構,這個和之前的輸入子系統(按鍵、觸摸屏)之類的存在一點差異。之前的都是在中斷服務函數中直接擷取到資料然後調用input_event上報,而USB裝置驅動中的資料來源是USB總線驅動,在完成函數中調用input_event上報資料。

# hexdump /dev/event0
0000000 4998 0000 4b9a 000c 0001 0110 0001 0000
0000010 4998 0000 4bb6 000c 0000 0000 0000 0000
0000020 4998 0000 cc2f 000e 0001 0110 0000 0000
0000030 4998 0000 cc4b 000e 0000 0000 0000 0000
0000040 4999 0000 93c0 000d 0001 0111 0001 0000
0000050 4999 0000 93db 000d 0000 0000 0000 0000
0000060 499a 0000 2fe0 0001 0001 0111 0000 0000
0000070 499a 0000 2ff2 0001 0000 0000 0000 0000
0000080 499c 0000 b23e 0006 0002 0000 00ff 0000
0000090 499c 0000 b24e 0006 0002 0001 0001 0000
00000a0 499c 0000 b252 0006 0000 0000 0000 0000
00000b0 499e 0000 cf82 0007 0001 0112 0001 0000
00000c0 499e 0000 cf9d 0007 0000 0000 0000 0000
00000d0 499e 0000 59ad 000b 0001 0112 0000 0000
00000e0 499e 0000 59c2 000b 0000 0000 0000 0000
00000f0 499f 0000 df37 0007 0002 0008 00ff 0000
0000100 499f 0000 df4e 0007 0000 0000 0000 0000
0000110 499f 0000 cacc 000d 0002 0008 00ff 0000
0000120 499f 0000 cadf 000d 0000 0000 0000 0000
           

事情就是這麼個事情,情況就是這麼個情況。

這就是usb裝置驅動的簡單架構,後面有空在把寫操作(資料由主機發送到裝置)的驅動模型以及各種傳輸類型的驅動更新下

- - 核心技術中文網 - 建構全國最權威的核心技術交流分享論壇

轉載位址:簡單分析USB裝置驅動架構(秒懂~) - 圈點 - 核心技術中文網 - 建構全國最權威的核心技術交流分享論壇

簡單分析USB裝置驅動架構(秒懂~)

繼續閱讀