linux usb 驅動詳解
USB 設備驅動代碼經過urb和全部的 USB 設備通信。urb用 struct urb 結構描述(include/linux/usb.h )。
urb 以一種異步的方式同一個特定USB設備的特定端點發送或接受數據。一個 USB 設備驅動可根據驅動的須要,分配多個 urb 給一個端點或重用單個 urb 給多個不一樣的端點。設備中的每一個端點都處理一個 urb 隊列, 因此多個 urb 可在隊列清空以前被髮送到相同的端點。
一個 urb 的典型生命循環以下:
(1)被建立;
(2)被分配給一個特定 USB 設備的特定端點;
(3)被提交給 USB 核心;
(4)被 USB 核心提交給特定設備的特定 USB 主機控制器驅動;
(5)被 USB 主機控制器驅動處理, 並傳送到設備;
(6)以上操做完成後,USB主機控制器驅動通知 USB 設備驅動。
urb 也可被提交它的驅動在任什麼時候間取消;若是設備被移除,urb 能夠被USB核心取消。urb 被動態建立幷包含一個內部引用計數,使它們能夠在最後一個用戶釋放它們時被自動釋放。
struct urb
struct urb { /* private: usb core and host controller only fields in the urb */ struct kref kref; /* URB引用計數 */ void *hcpriv; /* host控制器的私有數據 */ 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; /* list head for use by the urb's * current owner */ struct list_head anchor_list; /* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev; /* 指向這個 urb 要發送的目標 struct usb_device 的指針,這個變量必須在這個 urb 被髮送到 USB 核心以前被 USB 驅動初始化.*/ struct usb_host_endpoint *ep; /* (internal) pointer to endpoint */ unsigned int pipe; /* 這個 urb 所要發送到的特定struct usb_device的端點消息,這個變量必須在這個 urb 被髮送到 USB 核心以前被 USB 驅動初始化.必須由下面的函數生成*/ int status; /* 當 urb開始由 USB 核心處理或處理結束, 這個變量被設置爲 urb 的當前狀態. USB 驅動可安全訪問這個變量的惟一時間是在 urb 結束處理例程函數中. 這個限制是爲防止競態. 對於等時 urb, 在這個變量中成功值(0)只表示這個 urb 是否已被去鏈. 爲得到等時 urb 的詳細狀態, 應當檢查 iso_frame_desc 變量. */ unsigned int transfer_flags; /* 傳輸設置*/ void *transfer_buffer; /* 指向用於發送數據到設備(OUT urb)或者從設備接收數據(IN urb)的緩衝區指針。爲了主機控制器驅動正確訪問這個緩衝, 它必須使用 kmalloc 調用來建立, 不是在堆棧或者靜態內存中。 對控制端點, 這個緩衝區用於數據中轉*/ dma_addr_t transfer_dma; /* 用於以 DMA 方式傳送數據到 USB 設備的緩衝區*/ int transfer_buffer_length; /* transfer_buffer 或者 transfer_dma 變量指向的緩衝區大小。若是這是 0, 傳送緩衝沒有被 USB 核心所使用。對於一個 OUT 端點, 若是這個端點大小比這個變量指定的值小, 對這個 USB 設備的傳輸將被分紅更小的塊,以正確地傳送數據。這種大的傳送以連續的 USB 幀進行。在一個 urb 中提交一個大塊數據, 而且使 USB 主機控制器去劃分爲更小的塊, 比以連續地順序發送小緩衝的速度快得多*/ int actual_length; /* 當這個 urb 完成後, 該變量被設置爲這個 urb (對於 OUT 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; /* 設置或返回初始的幀數量(用於等時urb) */ int number_of_packets; /* 指定urb所處理的等時傳輸緩衝區的數量(用於等時urb,在urb被髮送到USB核心前,必須設置) */ int interval; /*urb 被輪詢的時間間隔. 僅對中斷或等時 urb 有效. 這個值的單位依據設備速度而不一樣. 對於低速和高速的設備, 單位是幀, 它等同於毫秒. 對於其餘設備, 單位是微幀, 等同於 1/8 毫秒. 在 urb被髮送到 USB 核心以前,此值必須設置.*/ int error_count; /* 等時urb的錯誤計數,由USB核心設置 */ void *context; /* 指向一個能夠被USB驅動模塊設置的數據塊. 當 urb 被返回到驅動時,可在結束處理例程中使用. */ usb_complete_t complete; /* 結束處理例程函數指針, 當 urb 被徹底傳送或發生錯誤,它將被 USB 核心調用. 此函數檢查這個 urb, 並決定釋放它或從新提交給另外一個傳輸中*/ struct usb_iso_packet_descriptor iso_frame_desc[0]; /* (僅用於等時urb)usb_iso_packet_descriptor結構體容許單個urb一次定義許多等時傳輸,它用於收集每一個單獨的傳輸狀態*/ };
struct usb_iso_packet_descriptor { unsigned int offset; /* 該數據包的數據在傳輸緩衝區中的偏移量(第一個字節爲0) */ unsigned int length; /* 該數據包的傳輸緩衝區大小 */ unsigned int actual_length; /* 等時數據包接收到傳輸緩衝區中的數據長度 */ int status; /* 該數據包的單個等時傳輸狀態。它能夠把相同的返回值做爲主struct urb 結構體的狀態變量 */ };
typedef void (*usb_complete_t)(struct urb *); 前端 |
上述結構體中unsigned int pipe;的生成函數(define):linux
static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint) { return (dev->devnum << 8) | (endpoint << 15); }
/* Create various pipes... */ #define usb_sndctrlpipe(dev,endpoint) \ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint)) #define usb_rcvctrlpipe(dev,endpoint) \ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) #define usb_sndisocpipe(dev,endpoint) \ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint)) #define usb_rcvisocpipe(dev,endpoint) \ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) #define usb_sndbulkpipe(dev,endpoint) \ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint)) #define usb_rcvbulkpipe(dev,endpoint) \ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) #define usb_sndintpipe(dev,endpoint) \ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint)) #define usb_rcvintpipe(dev,endpoint) \ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) //snd:OUT rcv:IN ctrl:控制 isoc:等時 bulk:批量 int:中斷 安全 |
上述結構體中unsigned int transfer_flags;的值域: 異步
/* * urb->transfer_flags: * * Note: URB_DIR_IN/OUT is automatically set in usb_submit_urb(). */ #define URB_SHORT_NOT_OK 0x0001 /* 置位時,任何在 IN 端點上發生的簡短讀取, 被 USB 核心看成錯誤. 僅對從 USB 設備讀取的 urb 有用 */ #define URB_ISO_ASAP 0x0002 /* 若爲等時 urb , 驅動想調度這個 urb 時,可置位該位, 只要帶寬容許且想在此時設置 urb 中的 start_frame 變量. 若沒有置位,則驅動必須指定 start_frame 值,且傳輸若是不能在當
時啓動的話,必須可以正確恢復 */ #define URB_NO_TRANSFER_DMA_MAP 0x0004 /* 當 urb 包含要被髮送的 DMA 緩衝時,應被置位.USB 核心使用就會使用 transfer_dma 變量指向的緩衝, 而不是被 transfer_buffer 變量指向的緩衝. */ #define URB_NO_SETUP_DMA_MAP 0x0008 /* 和 URB_NO_TRANSFER_DMA_MAP 相似, 這個位用來控制 DMA 緩衝已經創建的 urb. 若是它被置位, USB 核心使用 setup_dma 變量而不是 setup_packet 變量指向的緩衝. */ #define URB_NO_FSBR 0x0020 /* 僅 UHCI USB 主機控制器驅動使用, 而且告訴它不要試圖使用前端總線回收( Front Side Bus Reclamation) 邏輯. 這個位一般應不設置, 由於有 UHCI 主機控制器的機器會增長 CPU 負擔, 且PCI 總線會忙於等待設置了這個位的 urb */ #define URB_ZERO_PACKET 0x0040 /* 若是置位, 批量 OUT urb 經過發送不包含數據的短包來結束, 這時數據對齊到一個端點數據包邊界. 這被一些掉線的 USB 設備須要該位才能正確工做 */ #define URB_NO_INTERRUPT 0x0080 /* 若是置位, 當 urb 結束時硬件可能不產生一箇中斷. 該位應當當心使用而且只在多個 urb 排隊到相同端點時才使用. USB 核心函數使用該位進行 DMA 緩衝傳送. */ #define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */
#define URB_DIR_IN 0x0200 /* Transfer from device to host */ #define URB_DIR_OUT 0 #define URB_DIR_MASK URB_DIR_IN ide |
上述結構體中int status;的經常使用值(in include/asm-generic/errno.h and errno_base.h) :函數
// 0 表示 urb 傳送成功*/
//如下各個定義在使用時爲負值 #define ENOENT 2 /* urb 被 usb_kill_urb 中止 */ #define ECONNRESET 104 /* urb 被 usb_unlink_urb 去鏈, 且 transfer_flags 被設爲 URB_ASYNC_UNLINK */ #define EINPROGRESS 115 /* urb 仍在 USB 主機控制器處理 */ #define EPROTO 71 /* urb 發生錯誤: 在傳送中發生bitstuff 錯誤或硬件沒有及時收到響應幀 */ #define EILSEQ 84 /* urb 傳送中出現 CRC 較驗錯 */ #define EPIPE 32 /* 端點被中止. 若此端點不是控制端點, 則這個錯誤可經過函數 usb_clear_halt 清除 */ #define ECOMM 70 /* 數據傳輸時的接收速度快於寫入系統內存的速度. 此錯誤僅出如今 IN urb */ #define ENOSR 63 /* 從系統內存中獲取數據的速度趕不上USB 數據傳送速度,此錯誤僅出如今 OUT urb. */ #define EOVERFLOW 75 /* urb 發生"babble"(串擾)錯誤:端點接受的數據大於端點的最大數據包大小 */ #define EREMOTEIO 181 /* 當 urb 的 transfer_flags 變量的 URB_SHORT_NOT_OK 標誌被設置, urb 請求的數據沒有完整地收到 */ #define ENODEV 19 /* USB 設備從系統中拔出 */ #define EXDEV 18 /* 僅發生在等時 urb 中, 表示傳送部分完成. 爲了肯定所傳輸的內容, 驅動必須看單獨的幀狀態. */ #define EINVAL 22 /* 若是urb的一個參數設置錯誤或在提交 urb 給 USB 核心的 usb_submit_urb 調用中, 有不正確的參數,則可能發生次錯誤 */ #define ESHUTDOWN 108 /* USB 主機控制器驅動有嚴重錯誤,它已被禁止, 或者設備從系統中拔出。且這個urb 在設備被移除後被提交. 它也可能發生在 urb 被提交給設備時,設備的配置已被改變*/ 大數據 |
建立和註銷 urbatom
struct urb 結構不能靜態建立,必須使用 usb_alloc_urb 函數建立. 函數原型:spa
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags); //int iso_packets : urb 包含等時數據包的數目。若是不使用等時urb,則爲0 //gfp_t mem_flags : 與傳遞給 kmalloc 函數調用來從內核分配內存的標誌類型相同 指針 //返回值 : 若是成功分配足夠內存給 urb , 返回值爲指向 urb 的指針. 若是返回值是 NULL, 則在 USB 核心中發生了錯誤, 且驅動須要進行適當清理 |
若是驅動已經對 urb 使用完畢, 必須調用 usb_free_urb 函數,釋放urb。函數原型:
void usb_free_urb(struct urb *urb); //struct urb *urb : 要釋放的 struct urb 指針 |
根據內核源碼,能夠經過本身kmalloc一個空間來建立urb,而後必須使用
void usb_init_urb(struct urb *urb); |
進行初始化後才能夠繼續使用。
其實usb_alloc_urb函數就是這樣實現的,因此我固然不推薦這種自找麻煩的作法。
初始化 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);
static inline void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context);
static inline void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context);
//struct urb *urb :指向要被初始化的 urb 的指針 //struct usb_device *dev :指向 urb 要發送到的 USB 設備. //unsigned int pipe : urb 要被髮送到的 USB 設備的特定端點. 必須使用前面提過的 usb_******pipe 函數建立 //void *transfer_buffer :指向外發數據或接收數據的緩衝區的指針.注意:不能是靜態緩衝,必須使用 kmalloc 來建立. //int buffer_length :transfer_buffer 指針指向的緩衝區的大小 //usb_complete_t complete :指向 urb 結束處理例程函數指針 //void *context :指向一個小數據塊的指針, 被添加到 urb 結構中,以便被結束處理例程函數獲取使用. //int interval :中斷 urb 被調度的間隔. //函數不設置 urb 中的 transfer_flags 變量, 所以對這個成員的修改必須由驅動手動完成
/*等時 urb 沒有初始化函數,必須手動初始化,如下爲一個例子*/ urb->dev = dev; urb->context = uvd; urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1); urb->interval = 1; urb->transfer_flags = URB_ISO_ASAP; urb->transfer_buffer = cam->sts_buf[i]; urb->complete = konicawc_isoc_irq; urb->number_of_packets = FRAMES_PER_DESC; urb->transfer_buffer_length = FRAMES_PER_DESC; for (j=0; j < FRAMES_PER_DESC; j++) { urb->iso_frame_desc[j].offset = j; urb->iso_frame_desc[j].length = 1; } |
其實那三個初始化函數只是簡單的包裝,是inline函數。因此其實和等時的urb手動初始化沒什麼大的區別。
提交 urb
一旦 urb 被正確地建立並初始化, 它就能夠提交給 USB 核心以發送出到 USB 設備. 這經過調用函數 usb_submit_urb 實現:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags); //struct urb *urb :指向被提交的 urb 的指針 //gfp_t mem_flags :使用傳遞給 kmalloc 調用一樣的參數, 用來告訴 USB 核心如何及時分配內存緩衝
/*由於函數 usb_submit_urb 可被在任什麼時候候被調用(包括從一箇中斷上下文), mem_flags 變量必須正確設置. 根據 usb_submit_urb 被調用的時間,只有 3 個有效值可用: GFP_ATOMIC 只要知足如下條件,就應當使用此值: 1.調用者處於一個 urb 結束處理例程,中斷處理例程,底半部,tasklet或者一個定時器回調函數. 2.調用者持有自旋鎖或者讀寫鎖. 注意若是正持有一個信號量, 這個值沒必要要. 3.current->state 不是 TASK_RUNNING. 除非驅動已本身改變 current 狀態,不然狀態應該一直是 TASK_RUNNING .
GFP_NOIO 驅動處於塊 I/O 處理過程當中. 它還應當用在全部的存儲類型的錯誤處理過程當中.
GFP_KERNEL 全部不屬於以前提到的其餘狀況 */ |
在 urb 被成功提交給 USB 核心以後, 直到結束處理例程函數被調用前,都不能訪問 urb 結構的任何成員.
urb結束處理例程
如 果 usb_submit_urb 被成功調用, 並把對 urb 的控制權傳遞給 USB 核心, 函數返回 0; 不然返回一個負的錯誤代碼. 若是函數調用成功, 當 urb 被結束的時候結束處理例程會被調用一次.當這個函數被調用時, USB 核心就完成了這個urb, 並將它的控制權返回給設備驅動.
只有 3 種結束urb並調用結束處理例程的狀況:
(1)urb 被成功發送給設備, 且設備返回正確的確認.若是這樣, urb 中的status變量被設置爲 0.
(2)發生錯誤, 錯誤值記錄在 urb 結構中的 status 變量.
(3)urb 從 USB 核心unlink. 這發生在要麼當驅動經過調用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一個已提交的 urb,或者在一個 urb 已經被提交給它時設備從系統中去除.
取消 urb
使用如下函數中止一個已經提交給 USB 核心的 urb:
void usb_kill_urb(struct urb *urb) int usb_unlink_urb(struct urb *urb); |
若是調用usb_kill_urb函數,則 urb 的生命週期將被終止. 這一般在設備從系統移除時,在斷開回調函數(disconnect callback)中調用.
對一些驅動, 應當調用 usb_unlink_urb 函數來使 USB 核心中止 urb. 這個函數不會等待 urb 徹底中止才返回. 這對於在中斷處理例程中或者持有一個自旋鎖時去中止 urb 是頗有用的, 由於等待一個 urb 徹底中止須要 USB 核心有使調用進程休眠的能力(wait_event()函數).