USB 請求塊 (URB)¶
- 修訂:
2000-12-05
- 再次修訂:
2002-07-06
- 再次修訂:
2005-09-19
- 再次修訂:
2017-03-29
注意
USB 子系統現在在 Linux-USB 主機端 API 部分有一個重要的章節,該章節由當前原始碼生成。 這個特定的文件檔案並不完整,可能不會更新到最新版本;除了快速概覽之外,不要依賴它。
基本概念或“什麼是 URB?”¶
新驅動程式的基本思想是訊息傳遞,訊息本身被稱為 USB 請求塊,簡稱為 URB。
URB 包含執行任何 USB 事務並將資料和狀態返回的所有相關資訊。
URB 的執行本質上是一個非同步操作,即在成功將請求的操作排隊後,
usb_submit_urb()呼叫會立即返回。可以使用
usb_unlink_urb()隨時取消一個 URB 的傳輸。每個 URB 都有一個完成處理程式,該處理程式在操作成功完成或取消後被呼叫。 URB 還包含一個上下文指標,用於將資訊傳遞給完成處理程式。
裝置的每個端點在邏輯上都支援請求佇列。 您可以填充該佇列,以便 USB 硬體仍然可以將資料傳輸到端點,而您的驅動程式正在處理另一個端點的完成。 這最大限度地利用了 USB 頻寬,並支援在使用週期性傳輸模式時將資料無縫流式傳輸到裝置(或從裝置流式傳輸資料)。
URB 結構¶
struct urb 中的一些欄位是
struct urb
{
// (IN) device and pipe specify the endpoint queue
struct usb_device *dev; // pointer to associated USB device
unsigned int pipe; // endpoint information
unsigned int transfer_flags; // URB_ISO_ASAP, URB_SHORT_NOT_OK, etc.
// (IN) all urbs need completion routines
void *context; // context for completion routine
usb_complete_t complete; // pointer to completion routine
// (OUT) status after each completion
int status; // returned status
// (IN) buffer used for data transfers
void *transfer_buffer; // associated data buffer
u32 transfer_buffer_length; // data buffer length
int number_of_packets; // size of iso_frame_desc
// (OUT) sometimes only part of CTRL/BULK/INTR transfer_buffer is used
u32 actual_length; // actual data buffer length
// (IN) setup stage for CTRL (pass a struct usb_ctrlrequest)
unsigned char *setup_packet; // setup packet (control only)
// Only for PERIODIC transfers (ISO, INTERRUPT)
// (IN/OUT) start_frame is set unless URB_ISO_ASAP isn't set
int start_frame; // start frame
int interval; // polling interval
// ISO only: packets are only "best effort"; each can have errors
int error_count; // number of errors
struct usb_iso_packet_descriptor iso_frame_desc[0];
};
您的驅動程式必須使用它所宣告的介面中的相應端點描述符中的值來建立“管道”值。
如何獲取 URB?¶
透過呼叫 usb_alloc_urb() 來分配 URB
struct urb *usb_alloc_urb(int isoframes, int mem_flags)
返回值是指向已分配 URB 的指標,如果分配失敗則為 0。 引數 isoframes 指定要排程的同步傳輸幀的數量。 對於 CTRL/BULK/INT,使用 0。mem_flags 引數儲存標準記憶體分配標誌,允許您控制(除其他外)底層程式碼是否可以阻塞。
要釋放 URB,請使用 usb_free_urb()
void usb_free_urb(struct urb *urb)
您可以釋放已提交但尚未在完成回撥中返回給您的 urb。 當不再使用它時,它將自動被釋放。
需要填寫什麼?¶
根據事務的型別,在 linux/usb.h 中定義了一些行內函數來簡化初始化,例如 usb_fill_control_urb()、usb_fill_bulk_urb() 和 usb_fill_int_urb()。 通常,它們需要 usb 裝置指標、管道(來自 usb.h 的常用格式)、傳輸緩衝區、所需的傳輸長度、完成處理程式及其上下文。 檢視一些現有的驅動程式,看看它們是如何使用的。
標誌
對於 ISO,有兩種啟動行為:指定的 start_frame 或 ASAP。
對於 ASAP,在 transfer_flags 中設定
URB_ISO_ASAP。
如果短資料包不應被容忍,請在 transfer_flags 中設定 URB_SHORT_NOT_OK。
如何提交 URB?¶
只需呼叫 usb_submit_urb()
int usb_submit_urb(struct urb *urb, int mem_flags)
mem_flags 引數(例如 GFP_ATOMIC)控制記憶體分配,例如當記憶體不足時,較低層是否可以阻塞。
它會立即返回,返回狀態 0(請求已排隊)或一些錯誤程式碼,通常由以下原因引起
記憶體不足 (
-ENOMEM)拔出的裝置 (
-ENODEV)停止的端點 (
-EPIPE)排隊的 ISO 傳輸過多 (
-EAGAIN)請求的 ISO 幀過多 (
-EFBIG)無效的 INT 間隔 (
-EINVAL)INT 的資料包不止一個 (
-EINVAL)
提交後,urb->status 為 -EINPROGRESS; 但是,除了在完成回撥中之外,您永遠不應該檢視該值。
對於同步端點,您的完成處理程式應使用多重緩衝,使用 URB_ISO_ASAP 標誌將 URB (重新) 提交到同一端點,以獲得無縫的 ISO 流。
如何取消已經執行的 URB?¶
有兩種方法可以取消您已提交但尚未返回給您的驅動程式的 URB。 對於非同步取消,請呼叫 usb_unlink_urb()
int usb_unlink_urb(struct urb *urb)
它從內部列表中刪除該 urb 並釋放所有已分配的 HW 描述符。 狀態被更改以反映取消連結。 請注意,當 usb_unlink_urb() 返回時,URB 通常不會完成; 您仍然必須等待完成處理程式被呼叫。
要同步取消 URB,請呼叫 usb_kill_urb()
void usb_kill_urb(struct urb *urb)
它執行 usb_unlink_urb() 所做的一切,此外,它還會等待 URB 返回並且完成處理程式完成之後。 它還將 URB 標記為暫時不可用,以便如果完成處理程式或任何其他人嘗試重新提交它,他們將收到 -EPERM 錯誤。 因此,您可以確保當 usb_kill_urb() 返回時,URB 完全空閒。
有一個生存時間問題需要考慮。 URB 可能會在任何時候完成,並且完成處理程式可能會釋放 URB。 如果在 usb_unlink_urb() 或 usb_kill_urb() 正在執行時發生這種情況,則會導致記憶體訪問衝突。 驅動程式負責避免這種情況,這通常意味著需要某種鎖定來防止在使用 URB 時對其進行釋放。
另一方面,由於 usb_unlink_urb 最終可能會呼叫完成處理程式,因此處理程式不得采用在呼叫 usb_unlink_urb 時持有的任何鎖。 解決此問題的通用方法是在持有鎖的同時遞增 URB 的引用計數,然後釋放鎖並呼叫 usb_unlink_urb 或 usb_kill_urb,然後遞減 URB 的引用計數。 您可以透過呼叫 :c:func`usb_get_urb` 來遞增引用計數
struct urb *usb_get_urb(struct urb *urb)
(忽略返回值;它與引數相同),並透過呼叫 usb_free_urb() 來遞減引用計數。 當然,如果不存在 URB 被完成處理程式釋放的危險,則所有這些都是不必要的。
完成處理程式怎麼樣?¶
處理程式具有以下型別
typedef void (*usb_complete_t)(struct urb *)
也就是說,它獲取導致完成呼叫的 URB。 在完成處理程式中,您應該檢視 urb->status 以檢測任何 USB 錯誤。 由於上下文引數包含在 URB 中,因此您可以將資訊傳遞給完成處理程式。
請注意,即使報告了錯誤(或取消連結),也可能已傳輸資料。 這是因為 USB 傳輸是分組的; 可能需要 16 個數據包來傳輸您的 1KByte 緩衝區,並且在呼叫完成之前,其中 10 個數據包可能已成功傳輸。
警告
永遠不要在完成處理程式中休眠。
這些通常在原子上下文中呼叫。
在當前的核心中,完成處理程式在停用本地中斷的情況下執行,但將來會對此進行更改,因此不要假設本地 IRQ 始終在完成處理程式內部停用。
如何進行同步 (ISO) 傳輸?¶
除了批次傳輸中存在的欄位之外,對於 ISO,您還必須設定 urb->interval 以說明進行傳輸的頻率; 通常,每個幀一次(對於高速裝置,每微幀一次)。 使用的實際間隔將是 2 的冪,該冪不大於您指定的間隔。 您可以使用 usb_fill_int_urb() 宏來填充大多數 ISO 傳輸欄位。
對於 ISO 傳輸,您還必須為要排程的每個資料包填充一個 usb_iso_packet_descriptor 結構,該結構在 URB 的末尾由 usb_alloc_urb() 分配。
usb_submit_urb() 呼叫將 urb->interval 修改為小於或等於所請求的間隔值的已實現的間隔值。 如果使用 URB_ISO_ASAP 排程,則 urb->start_frame 也會被更新。
對於每個條目,您必須指定此幀的資料偏移量(基本是 transfer_buffer),以及您要寫入/期望讀取的長度。 完成後,actual_length 包含實際傳輸的長度,status 包含此幀的 ISO 傳輸的結果狀態。 允許指定從幀到幀的不同長度(例如,用於音訊同步/自適應傳輸速率)。 您還可以使用長度 0 來省略一個或多個幀(條帶化)。
對於排程,您可以選擇自己的起始幀或 URB_ISO_ASAP。 如前所述,如果您始終保持至少一個 URB 排隊,並且您的完成始終(重新)提交一個稍後的 URB,您將獲得平滑的 ISO 流(如果 USB 頻寬利用率允許)。
如果您指定自己的起始幀,請確保它比當前幀提前幾個幀。 如果您要將 ISO 資料與某些其他事件流同步,您可能需要此模型。
如何啟動中斷 (INT) 傳輸?¶
中斷傳輸(如同步傳輸)是週期性的,並且以 2 的冪(1、2、4 等)為單位的間隔發生。 單位是全速和低速裝置的幀,以及高速裝置的微幀。 您可以使用 usb_fill_int_urb() 宏來填充 INT 傳輸欄位。
usb_submit_urb() 呼叫將 urb->interval 修改為小於或等於所請求的間隔值的已實現的間隔值。
在 Linux 2.6 中,與早期版本不同,中斷 URB 在完成後不會自動重新啟動。 它們在呼叫完成處理程式時結束,就像其他 URB 一樣。 如果您希望重新啟動中斷 URB,您的完成處理程式必須重新提交它。 s