USB請求塊(URB)——架構及機制
《Linux裝置驅動開發詳解》本書全面而詳細地講解了Linux裝置驅動開發中涉及的理論以及多種裝置驅動的架構。第20章主要講解從主機側角度看到的USB主機控制器驅動和裝置驅動。本文寫的是USB請求塊(urb)。
1.urb結構體
USB請求塊(USB request block,urb)是USB裝置驅動中用來描述與USB裝置通信所用的基本載體和核心資料結構,非常類似于網絡裝置驅動中的sk_buff結構體,是USB主機與裝置通信的“電波”。
代碼清單20.13 urb結構體
1 struct urb
2 {
3
4 struct kref kref;
5 spinlock_t lock;
6 void *hcpriv;
7 int bandwidth;
8 atomic_t use_count;
9 u8 reject;
10
11
12 struct list_head urb_list;
13 struct usb_device *dev;
14 unsigned int pipe;
15 int status;
16 unsigned int transfer_flags;
17 void *transfer_buffer;
18 dma_addr_t transfer_dma;
19 int transfer_buffer_length;
20
21 int actual_length;
22 unsigned char *setup_packet;
23 dma_addr_t setup_dma;
24 int start_frame;
25 int number_of_packets;
26 int interval;
27 int error_count;
28 void *context;
29 usb_complete_t complete;
30 struct usb_iso_packet_descriptor iso_frame_desc[0];
31
32 };
當transfer_flags标志中的URB_NO_TRANSFER_DMA_MAP被置位時,USB核心将使用transfer_dma指向的緩沖區而非transfer_buffer指向的緩沖區,意味着即将傳輸DMA緩沖區。
當transfer_flags标志中的URB_NO_SETUP_DMA_MAP被置位時,對于有DMA緩沖區的控制urb而言,USB核心将使用setup_dma指向的緩沖區而非setup_packet指向的緩沖區。
2.urb處理流程
USB裝置中的每個端點都處理一個urb隊列,在隊列被清空之前,一個urb的典型生命周期如下:
(1)被一個 USB 裝置驅動建立。
建立urb結構體的函數為:
struct urb *usb_alloc_urb(int iso_packets,int mem_flags);
iso_packets是這個urb應當包含的等時資料包的數目,若為0表示不建立等時資料包。 mem_flags參數是配置設定記憶體的标志,和kmalloc()函數的配置設定标志參數含義相同。如果配置設定成功,該函數傳回一個urb結構體指針,否則傳回0。
urb結構體在驅動中不能靜态建立,因為這可能破壞USB核心給urb使用的引用計數方法。
usb_alloc_urb()的“反函數”為:
void usb_free_urb(struct urb *urb);
該函數用于釋放由usb_alloc_urb()配置設定的urb結構體。
(2)初始化,被安排給一個特定USB裝置的特定端點。
對于中斷urb,使用usb_fill_int_urb()函數來初始化urb,如下所示:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev, unsigned int pipe, void *transfer_buffer,
intbuffer_length, usb_complete_t complete, void*context, int interval);
urb參數指向要被初始化的urb的指針;dev指向這個urb要被發送到的USB裝置;pipe是這個urb要被發送到的USB裝置的特定端點;transfer_buffer是指向發送資料或接收資料的緩沖區的指針,和urb一樣,它也不能是靜态緩沖區,必須使用kmalloc()來配置設定;buffer_length是transfer_buffer指針所指向緩沖區的大小;complete指針指向當這個 urb完成時被調用的完成處理函數;context是完成處理函數的“上下文”;interval是這個urb應當被排程的間隔。
上述函數參數中的pipe使用usb_sndintpipe()或usb_rcvintpipe()建立。
對于批量urb,使用usb_fill_bulk_urb()函數來初始化urb,如下所示:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev, unsigned int pipe, void *transfer_buffer,
intbuffer_length, usb_complete_t complete, void*context);
除了沒有對應于排程間隔的interval參數以外,該函數的參數和usb_fill_int_urb()函數的參數含義相同。
上述函數參數中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函數來建立。
對于控制 urb,使用usb_fill_control_urb()函數來初始化urb,如下所示:
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, void *context);
除了增加了新的setup_packet參數以外,該函數的參數和usb_fill_bulk_urb()函數的參數含義相同。setup_packet參數指向即将被發送到端點的設定資料包。
上述函數參數中的pipe使用usb_sndctrlpipe()或usb_rcvictrlpipe()函數來建立。
等時urb沒有像中斷、控制和批量urb的初始化函數,我們隻能手動地初始化urb,而後才能送出給USB核心。代碼清單20.14給出了初始化等時urb的例子,它來自drivers/usb/media/usbvideo.c檔案。
代碼清單20.14 初始化等時urb
1 for(i = 0; i < USBVIDEO_NUMSBUF; i++)
2 {
3 int j, k;
4 struct urb *urb = uvd->sbuf[i].urb;
5 urb->dev = dev;
6 urb->context = uvd;
7 urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp);
8 urb->interval = 1;
9 urb->transfer_flags = URB_ISO_ASAP;
10 urb->transfer_buffer = uvd->sbuf[i].data;
11 urb->complete = usbvideo_IsocIrq;
12 urb->number_of_packets = FRAMES_PER_DESC;
13 urb->transfer_buffer_length = uvd->iso_packet_len*FRAMES_PER_DESC;
14 for (j = k = 0; j < FRAMES_PER_DESC; j++, k +=uvd->iso_packet_len)
15 {
16 urb->iso_frame_desc[j].offset = k;
17 urb->iso_frame_desc[j].length = uvd->iso_packet_len;
18 }
19 }
(3)被USB裝置驅動送出給USB 核心。
在完成第(1)、(2)步的建立和初始化urb後,urb便可以送出給USB核心,通過usb_submit_urb()函數來完成,如下所示:
int usb_submit_urb(struct urb *urb, intmem_flags);
urb參數是指向urb的指針,mem_flags參數與傳遞給kmalloc()函數參數的意義相同,它用于告知USB核心如何在此時配置設定記憶體緩沖區。
在送出urb到USB核心後,直到完成函數被調用之前,不要通路urb中的任何成員。
usb_submit_urb()在原子上下文和程序上下文中都可以被調用,mem_flags變量需根據調用環境進行相應的設定,如下所示。
- GFP_ATOMIC:在中斷處理函數、底半部、tasklet、定時器處理函數以及urb完成函數中,在調用者持有自旋鎖或者讀寫鎖時以及當驅動将current->state修改為非 TASK_ RUNNING時,應使用此标志。
- GFP_NOIO:在儲存設備的塊I/O和錯誤處理路徑中,應使用此标志;
- GFP_KERNEL:如果沒有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。
如果usb_submit_urb()調用成功,即urb的控制權被移交給USB核心,該函數傳回0;否則,傳回錯誤号。
(4)送出由USB核心指定的USB主機控制器驅動。
(5)被USB主機控制器處理,進行一次到USB裝置的傳送。
第(4)~(5)步由USB核心和主機控制器完成,不受USB裝置驅動的控制。
(6)當urb完成,USB主機控制器驅動通知USB裝置驅動。
在如下3種情況下,urb将結束,urb完成函數将被調用。
- urb 被成功發送給裝置,并且裝置傳回正确的确認。如果urb->status為0,意味着對于一個輸出urb,資料被成功發送;對于一個輸入urb,請求的資料被成功收到。
- 如果發送資料到裝置或從裝置接收資料時發生了錯誤,urb->status将記錄錯誤值。
- urb 被從USB 核心“去除連接配接”,這發生在驅動通過usb_unlink_urb()或usb_kill_urb()函數取消urb,或urb雖已送出,而USB裝置被拔出的情況下。
usb_unlink_urb()和usb_kill_urb()這兩個函數用于取消已送出的urb,其參數為要被取消的urb指針。對usb_unlink_urb()而言,如果urb結構體中的URB_ASYNC_UNLINK(即異步unlink)的标志被置位,則對該urb的usb_unlink_urb()調用将立即傳回,具體的unlink動作将在背景進行。否則,此函數一直等到urb被解開連結或結束時才傳回。usb_kill_urb()會徹底終止urb的生命周期,它通常在裝置的disconnect()函數中被調用。
當urb生命結束時(處理完成或被解除連結),通過urb結構體的status成員可以獲知其原因,如0表示傳輸成功,-ENOENT表示被usb_kill_urb()殺死,-ECONNRESET表示被usb_unlink_urb()殺死,-EPROTO表示傳輸中發生了bitstuff錯誤或者硬體未能及時收到響應資料包,-ENODEV表示USB裝置已被移除,-EXDEV表示等時傳輸僅完成了一部分等。
對以上urb的處理步驟進行一個總結,圖20.5給出了一個urb的整個處理流程,虛線框的usb_unlink_urb()和usb_kill_urb()并非一定會發生,它隻是在urb正在被USB核心和主機控制器處理時,被驅動程式取消的情況下才發生。
3.簡單的批量與控制URB
有時USB驅動程式隻是從USB裝置上接收或向USB裝置發送一些簡單的資料,這時候,沒有必要将urb建立、初始化、送出、完成處理的整個流程走一遍,而可以使用兩個更簡單的函數,如下所示。
(1)usb_bulk_msg()
usb_bulk_msg()函數建立一個USB批量urb 并将它發送到特定裝置,這個函數是同步的,它一直等待urb完成後才傳回。 usb_bulk_msg()函數的原型為:
int usb_bulk_msg(struct usb_device*usb_dev, unsigned int pipe, void *data, int len, int*actual_length, int timeout);

圖20.5 urb處理流程
usb_dev參數為批量消息要發送的USB 裝置的指針,pipe為批量消息要發送到的USB裝置的端點,data參數為指向要發送或接收的資料緩沖區的指針,len參數為data參數所指向的緩沖區的長度,actual_length用于傳回實際發送或接收的位元組數,timeout是發送逾時,以jiffies為機關,0意味着永遠等待。
如果函數調用成功,傳回0;否則,傳回1個負的錯誤值。
(2)usb_control_msg()函數
usb_control_msg()函數與usb_bulk_msg()函數類似,不過它提供驅動發送和結束USB控制資訊而非批量資訊的能力,該函數的原型為:
int usb_control_msg(struct usb_device *dev,unsigned int pipe, _ _u8 request,
__u8 requesttype, __u16 value, __u16index, void *data, __u16 size, int timeout);
dev指向控制消息發往的USB裝置,pipe是控制消息要發往的USB裝置的端點,request是這個控制消息的USB請求值,requesttype是這個控制消息的USB請求類型,value是這個控制消息的USB消息值,index是這個控制消息的USB消息索引值,data指向要發送或接收的資料緩沖區,size是data參數所指向的緩沖區的大小,timeout是發送逾時,以jiffies為機關,0意味着永遠等待。
參數request、requesttype、value和index與USB規範中定義的USB控制消息直接對應。
如果函數調用成功,該函數傳回發送到裝置或從裝置接收到的位元組數;否則,傳回一個負的錯誤值。
對usb_bulk_msg()和usb_control_msg()函數的使用要特别慎重,由于它們是同步的,是以不能在中斷上下文和持有自旋鎖的情況下使用。而且,該函數也不能被任何其他函數取消,是以,務必要使得驅動程式的disconnect()函數掌握足夠的資訊,以判斷和等待該調用的結束。
轉自:http://book.51cto.com/art/200803/66930.htm