函數usb_control_msg完成一些初始化后調用了usb_internal_control_msg之后就free urb。剩下的活,全部留給usb_internal_control_msg去做了,那就去了解一下它背后的生活吧。

一個中心:struct urb結構體,就是咱們前面多次提到又多次飄過,只聞其名不見其形的urb,全稱usb request block,站在咱們的角度看,usb通信靠的就是它這張臉。
第一個基本點:usb_alloc_urb函數,創建一個struct urb結構體,只能使用這個函數來創建,它是urb在usb世界里的獨家代理。
第二個基本點:usb_fill_control_urb函數,初始化一個控制urb,urb被創建之后,使用之前必須要正確的初始化。
第三個基本點:usb_start_wait_urb函數,將urb提交給咱們的usb core,以便分配給特定的主機控制器驅動進行處理,然后默默的等待處理結果,或者超時。

既然是核心結構體,代碼的注釋貼上來怎么看都不過分。
kref,urb的引用計數。甭看它是隱藏在urb內部的一個不起眼的小角色,但小角色做大事情,它決定了一個urb的生死存亡。一個urb有用沒用,是繼續委以重任還是無情銷毀都要看它的臉色。
那第一個問題就來了,為什么urb的生死要掌握在這個小小的引用計數手里邊兒?之前說過,主機與設備之間通過管道傳輸數據,管道的一端是主機上的一個緩沖區,另一端是設備上的端點。管道之中流動的數據,在主機控制器和設備看來是一個個packets,在咱們看來就是urb。因而,端點之中就有那么一個隊列,叫urb隊列。不過,這並不代表一個urb只能發配給一個端點,它可能通過不同的管道發配給不同的端點,那么這樣一來,我們如何知道這個urb正在被多少個端點使用,如何判斷這個urb的生命已經over?如果沒有任何一個端點在使用它,而我們又無法判斷這種情況,因此需要引用計數。每多一個使用者,它的這個引用計數就加1,每減少一個使用者,引用計數就減一,如果連最后一個使用者都釋放了這個urb,宣稱不再使用它了,那它的生命周期就走到了盡頭,會自動的銷毀。
接下來就是第二個問題,如何來表示這個神奇的引用計數?其實它是一個struct kref結構體,在include/linux/kref.h里定義,別看這個結構體簡單,內核里就是使用它來判斷一個對象是否有用。它里邊兒只包括了一個原子變量,為什么是原子變量?既然都使用引用計數了,那就說明可能同時有多個地方在使用這個對象,總要考慮一下它們同時修改這個計數的可能性吧,也就是俗稱的並發訪問,那怎么辦?加個鎖?就這么一個整數值專門加個鎖未免也忒大材小用了些,所以就使用了原子變量。圍繞這個結構,就多說一點吧。內核里還定義了幾個專門操作引用計數的函數,它們在lib/kref.c里定義,包括kref_init,kref_get,kref_put等函數。kref_init初始化,kref_get將引用計數加1,kref_put將引用計數減一並判斷是不是為0,為0的話就調用參數里release函數指針指向的函數把對象銷毀掉。友情提醒一下,kref_init初始化時,是把refcount的值初始化為1了的,不是0。還有一點要說的是kref_put參數里的那個函數指針,你不能傳遞一個NULL過去,否則這個引用計數就只是計數,而背離了最初的目的,要記住我們需要在這個計數減為0的時候將嵌入這個引用計數struct kref結構體的對象給銷毀掉,所以這個函數指針也不能為kfree,因為這樣的話就只是把這個struct kref結構體給銷毀了,而不是整個對象。
第三個問題,如何使用struct kref結構來為我們的對象計數?當然我們需要把這樣一個結構嵌入到你希望計數的對象里邊,不然你根本就無法對對象在它整個生命周期里的使用情況作出判斷。但是我們應該是幾乎見不到內核里邊兒直接使用上面那幾個函數來給對象計數的,而是每種對象又定義了自己專用的引用計數函數,比如咱們的urb,在drivers/usb/core/urb.c里定義。usb_init_urb、usb_get_urb、usb_free_urb這三個函數分別調用了前面看到的struct kref結構的三個操作函數來進行引用計數的初始化、加1、減一。什么叫封裝?這就叫封裝。usb_init_urb和usb_get_urb都沒什么好說的,比較感興趣的是usb_free_urb里給kref_put傳遞的那個函數urb_destroy,它也在urb.c里定義。這個urb_destroy首先調用了to_urb,實際上就是一個container_of來獲得引用計數關聯的那個urb,然后使用kfree將它銷毀。
圍繞一個小小的引用計數講了那么多,趕緊回到urb那個結構體。
hcpriv,你需要明白這個urb最終還是要提交給主機控制器驅動的,這個字段就是留給urb里主機控制器驅動的。
use_count,這里又是一個使用計數,不過此計數非彼計數,它與上面那個用來追蹤urb生命周期的kref一點兒血緣關系也沒。那它是用來做什么的?簡單說一下使用urb來完成一次完整的usb通信都要經歷哪些階段:首先,驅動程序發現自己需要與usb設備通信,於是創建一個urb結構體,並指定它的目的地是設備上的哪個端點,然后提交給usb core,usb core將它做一些初始化后再移交給主機控制器的驅動程序HCD,HCD會去解析這個urb,了解它的目的是什么,並與usb設備進行相應的交流,在交流結束,urb的目的達到之后,HCD再把這個urb的所有權移交回驅動程序。這里的use_count就是在usb core將urb移交給HCD的時候使用。什么時候減1?在HCD重新將urb的所有權移交回驅動程序的時候。這樣說吧,只要HCD擁有這個urb的所有權,那么該urb的use_count就不會為0。這么一說,似乎use_count也有點追蹤urb生命周期的味道了,當它的值大於0時,就表示當前有HCD正在處理它,和上面的kref概念上有部分的重疊,不過,顯然它們之間是有區別的。上面的那個kref實現方式是內核里統一的引用計數機制,當計數減為0時,urb對象就被urb_destroy給銷毀了。這里的use_count只是用來統計當前這個urb是不是正在被哪個HCD處理,即使它的值為0,也只是說明沒有HCD在使用它而已,並不代表就得把它給銷毀掉。比方說,HCD利用完了urb,把它還給了驅動,這時驅動還可以對這個urb檢修檢修,再提交給哪個HCD去使用。
那它究竟是用來干啥的?還要從剛提到的那幾個階段說起。創建urb結構體並提交后,到達了HCD那里正在處理中,突然驅動反悔了,它不想繼續這次通信了,想將這個urb給終止掉,這時usb core當然會給驅動提供這樣的接口來滿足這樣的需要。那么怎么辦呢?寫內核的兄弟還是考慮到這里了,想到了兩種處理方法。一種是驅動只想通過usb core告訴HCD一聲,說這個urb我想終止掉,您就別費心再處理了,然后它不想在那里等着HCD的處理,想忙別的事去,這就是俗稱的異步,對應的是usb_unlink_urb函數。另一種就是同步處理,驅動會在那里苦苦等候着HCD的處理結果,等待着urb被終止,對應的是usb_kill_urb函數。而HCD將這次通信終止后,同樣會將urb的所有權移交回驅動。那么驅動通過什么判斷HCD已經終止了這次通信?就是通過這里的use_count,驅動會在usb_kill_urb里面一直等待着這個值變為0。
reject,拒絕,拒絕什么?在目前版本的內核里,只有usb_kill_urb函數有特權對它進行修改。顯然reject就與上面說的urb終止有關了,那就看看drivers/usb/core/urb.c里定義的這個函數。

再次強調重要的結構體和函數處理,我會連注釋和代碼一起貼上來,以后不說了。
說說其中函數might_sleep()的作用:因為usb_kill_urb函數要一直等候着HCD將urb終止掉,它必須是可以休眠的。所以說usb_kill_urb不能用在中斷上下文,必須能夠休眠將自己占的資源給讓出來。用它來判斷一下這個函數是不是處在能夠休眠的情況,如果不是,就會打印出一大堆的堆棧信息,比如你在中斷上下文調用了這個函數時。不過,它也就是基於調試的目的用一用,方便日后找錯,並不能強制哪個函數改變自己的上下文。
接下就是判斷一下urb要去的那個設備和端點。
atomic_inc(&urb->reject)獲得每個urb都有的那個變量,然后將reject加1。加1有什么用?在目前的內核版本里,有兩處使用reject,第一處在usb core將urb提交給HCD的時候,如果reject大於0,就不再接着移交了,也就是說這個urb被HCD給拒絕了。這是為了防止這邊兒正在終止這個urb,那邊兒某個地方卻又妄想將這個urb重新提交給HCD。
usb_hcd_unlink_urb這里告訴HCD驅動要終止這個urb了,usb_hcd_unlink_urb函數也只是告訴HCD一聲,然后不管HCD怎么處理就返回了。usb_hcd_unlink_urb返回后並不代表HCD已經將urb給終止了,HCD可能沒那么快,所以這里usb_kill_urb要休息休息,等人通知它。這里使用了wait_event宏來實現休眠,usb_kill_urb_queue是在/drivers/usb/core/hcd.h里定義的一個等待隊列,專門給usb_kill_urb休息用的。需要注意的是這里的喚醒條件,use_count必須等於0(只有變為了0才能表示這次通信over),終於看到use_count實戰的地方了。那在哪里喚醒正在睡大覺的usb_kill_urb?這牽扯到了第二個使用reject來做判斷的地方。在HCD將urb的所有權返還給驅動的時候,會對reject進行判斷,如果reject大於0,就調用wake_up喚醒在usb_kill_urb_queue上休息的usb_kill_urb。這也好理解,HCD都要將urb的所有權返回給驅動了,那當然就是已經處理完了,放在這里就是已經將這個urb終止了,usb_kill_urb等的就是這一天的到來,當然就要醒過來繼續往下走了。有興趣可以再去看看usb_unlink_urb函數。
接下來將reject剛才增加的那個1給減掉。urb都已經終止了,也沒人再會去拒絕它了,於是reject開始什么樣兒,結束的時候就什么樣吧。
回到struct urb結構里的前面幾個private變量,最后unlinked整型變量表示unlink的錯誤碼,不多說了。這些private變量是usb core和主機控制器驅動需要關心的。而驅動要做的只是創建一個urb,然后初始化,再把它提交給usb core就可以了,使用不使用引用計數,加不加鎖之類的一點都不用去操心。
urb_list,還記得每個端點都會有的那個urb隊列么?那個隊列就是由這里的urb_list一個一個的鏈接起來的。HCD每收到一個urb,就會將它添加到這個urb指定的那個端點的urb隊列里去。這個鏈表的頭兒在哪兒?當然是在端點里,就是端點里的那個struct list_head結構體成員。
anchor_list和 anchor表示the URB may be anchored,什么叫anchored???以前的內核中沒有這個東東,還是糊塗一點吧。我也不知道有什么用。
dev,它表示的是urb要去的那個usb設備。指向這個 urb 要發送的目標 struct usb_device 的指針,這個變量必須在這個 urb 被發送到 USB 核心之前被 USB 驅動初始化。
ep就是表示指向端點的指針,沒什么好說的,該說的前面都講了。
pipe,urb到達端點之前,需要經過一個通往端點的管道,就是這個pipe。那第一個問題,怎么表示一個pipe?管道有兩端,一端是主機上的緩沖區,一端是設備上的端點,既然有兩端,總要有個方向吧,早先說過,端點有四種類型,那么與端點相生相依的管道也應該不只一種吧。這么說來,確定一條管道至少要知道兩端的地址、方向和類型了,不過這兩端里主機是確定的,需要確定的只是另一端設備的地址和端點的地址。
那第一個問題來了,怎么將這些內容連接起來表示成一個管道?一個包含了各種成員屬性的結構再加上一些操作函數?多么完美的封裝,但是不需要這么搞,也可以復雜簡單化,一個整型值再加上一些宏就夠了。下面就講講寫內核代碼的哥們是怎么想的吧。把相關代碼先放上來再說,圍繞管道的一些宏,在include/linux/usb.h里定義。
/* ----------------------------------------------------------------------- */
/*
* For various legacy reasons, Linux has a small cookie that's paired with
* a struct usb_device to identify an endpoint queue. Queue characteristics
* are defined by the endpoint's descriptor. This cookie is called a "pipe",
* an unsigned int encoded as:
*
* - direction: bit 7 (0 = Host-to-Device [Out],
* 1 = Device-to-Host [In] ...
* like endpoint bEndpointAddress)
* - device address: bits 8-14 ... bit positions known to uhci-hcd
* - endpoint: bits 15-18 ... bit positions known to uhci-hcd
* - pipe type: bits 30-31 (00 = isochronous, 01 = interrupt,
* 10 = control, 11 = bulk)
*
* Given the device address and endpoint descriptor, pipes are redundant.
*/
/* NOTE: these are not the standard USB_ENDPOINT_XFER_* values!! */
/* (yet ... they're the values used by usbfs) */
#define PIPE_ISOCHRONOUS 0
#define PIPE_INTERRUPT 1
#define PIPE_CONTROL 2
#define PIPE_BULK 3
#define usb_pipein(pipe) ((pipe) & USB_DIR_IN)
#define usb_pipeout(pipe) (!usb_pipein(pipe))
#define usb_pipedevice(pipe) (((pipe) >> 8) & 0x7f)
#define usb_pipeendpoint(pipe) (((pipe) >> 15) & 0xf)
#define usb_pipetype(pipe) (((pipe) >> 30) & 3)
#define usb_pipeisoc(pipe) (usb_pipetype((pipe)) == PIPE_ISOCHRONOUS)
#define usb_pipeint(pipe) (usb_pipetype((pipe)) == PIPE_INTERRUPT)
#define usb_pipecontrol(pipe) (usb_pipetype((pipe)) == PIPE_CONTROL)
#define usb_pipebulk(pipe) (usb_pipetype((pipe)) == PIPE_BULK)
就是這個整型值pipe的構成,bit7用來表示方向,bit8~14表示設備地址,bit15~18表示端點號,早先說過,設備地址用7位來表示,端點號用4位來表示,剩下來的bit30~31表示管道類型。
現在看第二個問題,如何創建一個管道?主機和設備進行交流必須通過管道,你必須得創建一個管道給urb,它才知道路怎么走。於是內核的include/linux/usb.h文件里多了很多專門用來創建不同管道的宏。端點是有四種的,對應着管道也就有四種,同時端點是有IN也有OUT的,相應的管道也就有兩個方向,於是二四得八,上面就出現了八個創建管道的宏。有了struct usb_device結構體,也就是說知道了設備地址,再加上端點號,你就可以需要什么管道就創建什么管道。__create_pipe宏只是一個幕后的角色,用來將設備地址和端點號放在管道正確的位置上。自己不信,可以去include/linux/usb.h文件看看是不是定義了八個創建管道的宏。
status,urb的當前狀態。urb可以有多種狀態的。至於各種具體的狀態代表了什么意思,碰到了再說。
transfer_flags,一些標記,可用的值都在include/linux/usb.h里有定義。
/*
* urb->transfer_flags:
*
* Note: URB_DIR_IN/OUT is automatically set in usb_submit_urb().
*/
#define URB_SHORT_NOT_OK 0x0001 /* report short reads as errors */
#define URB_ISO_ASAP 0x0002 /* iso-only, urb->start_frame
* ignored */
#define URB_NO_TRANSFER_DMA_MAP 0x0004 /* urb->transfer_dma valid on submit */
#define URB_NO_SETUP_DMA_MAP 0x0008 /* urb->setup_dma valid on submit */
#define URB_NO_FSBR 0x0020 /* UHCI-specific */
#define URB_ZERO_PACKET 0x0040 /* Finish bulk OUT with short packet */
#define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt
* needed */
#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
就順便說說這些宏的作用吧。
URB_SHORT_NOT_OK,這個標記只對用來從IN端點讀取數據的urb有效,意思就是說如果從一個IN端點那里讀取了一個比較短的數據包,就可以認為是錯誤的。那么這里的short究竟short到什么程度?之前說到端點的時候,就知道端點描述符里有一個叫wMaxPacketSize成員,指明了端點一次能夠處理的最大字節數。然后在usb的世界里是有四種PID類型,其中Data類型里邊兒有個數據字段是用來傳輸數據的,但是它里面並不是只有一個數據字段,還有SYNC、PID、地址域、CRC等陪伴在數據字段的左右。那現在一個問題出來了,每個端點描述符里的wMaxPacketSize所表示的最大字節數都包括了哪些部分?是整個packet的長度么?我可以負責任的告訴你,它只包括了Data包里面數據字段,俗稱data payload,和TCP/IP里的報頭差不多。wMaxPacketSize與short有什么關系?關系還不小,short不short就是與wMaxPacketSize相比的,如果從IN端點那兒收到了一個比wMaxPacketSize要短的包,同時也設置了URB_SHORT_NOT_OK這個標志,那么就可以認為傳輸出錯了。本來如果收到一個比較短的包是意味着這次傳輸到此為止就結束了,你想想data payload的長度最大必須為wMaxPacketSize這個規定是不可違背的了,但是如果端點想給你的數據不止那么多,怎么辦?就需要分成多個wMaxPacketSize大小的data payload來傳輸,事情有時不會那么湊巧,剛好能平分成多個整份,這時,最后一個data payload的長度就會比wMaxPacketSize要小,這種情況本來意味着端點已經傳完了它想傳的,釋放完了自己的需求,這次傳輸就該結束了,不過如果你設置了URB_SHORT_NOT_OK標志,HCD這邊就會認為錯誤發生了。
URB_ISO_ASAP,這個標志只是為了方便等時傳輸用的。等時傳輸和中斷傳輸在spec里都被認為是periodic transfers,也就是周期傳輸,咱們都知道在usb的世界里都是主機占主導地位,設備是沒多少發言權的,但是對於等時傳輸和中斷傳輸,端點可以對主機表達自己一種美好的期望,希望主機能夠隔多長時間訪問自己一次,這個期望的時間就是這里說的周期。當然,期望與現實是有一段距離的。端點的這個期望能不能得到滿足,要看主機控制器答應不答應。對於等時傳輸,一般來說也就一幀(微幀)一次,主機那兒也很忙,再多也抽不出空兒來。那么如果你有個用於等時傳輸的urb,你提交給HCD的時候,就得告訴HCD它應該從哪一幀開始的,就要對下面要說的那個start_frame賦值,也就是說告訴HCD等時傳輸開始的那一幀(微幀)的幀號,如果你留心,應該還會記得前面說過在每幀或微幀(Mircoframe)的開始都會有個SOF Token包,這個包里就含有個幀號字段,記錄了那一幀的編號。這樣的話,一是要去設置這個start_frame,二是到你設置的那一幀的時候,如果主機控制器沒空開始等時傳輸,怎么辦?於是,就出現了URB_ISO_ASAP,它的意思就是告訴HCD啥時候不忙就啥時候開始,就不用指定什么開始的幀號了,是不是感覺特輕松?所以說,你如果想進行等時傳輸,又不想標新立異的話,就還是把它給設置了吧。
URB_NO_TRANSFER_DMA_MAP,還有URB_NO_SETUP_DMA_MAP,這兩個標志都是有關DMA的,什么是DMA?就是外設,比如咱們的usb攝像頭,和內存之間直接進行數據交換,把CPU給撇一邊兒了,本來,在咱們的電腦里,CPU自認為是老大,什么事都要去插一腳,都要經過它去協調處理。可是這樣的話就影響了數據傳輸的速度,所以dma也是少不了的。一般來說,都是驅動里提供了kmalloc等分配的緩沖區,HCD做一定的DMA映射處理,DMA映射是干嗎的?外設和內存之間進行數據交換,總要互相認識。外設是通過各種總線連到主機里邊兒的,使用的是總線地址,而內存使用的是虛擬地址,它們之間本來就是兩條互不相交的平行線,要讓它們中間產生連接點,必須得將一個地址轉化為另一個地址,這樣才能找得到對方,才能互通有無,而DMA映射就是干這個的。於是就有了這里的兩個標志,告訴HCD不要再自己做DMA映射了,驅動提供的urb里已經有DMA緩沖區地址,具體提供了哪些DMA緩沖區?就涉及到urb結構體成員中的transfer_buffer,transfer_dma,還有setup_packet,setup_dma這兩對兒了。
URB_NO_FSBR,這是給UHCI用的。
URB_ZERO_PACKET,這個標志表示批量的OUT傳輸必須使用一個short packet來結束。批量傳輸的數據大於批量端點的wMaxPacketSize時,需要分成多個Data包來傳輸,最后一個data payload的長度可能等於wMaxPacketSize,也可能小於,當等於wMaxPacketSize時,如果同時設置了URB_ZERO_PACKET標志,就需要再發送一個長度為0的數據包來結束這次傳輸,如果小於wMaxPacketSize就沒必要多此一舉了。你要問,當批量傳輸的數據小於wMaxPacketSize時那?也沒必要再發送0長的數據包,因為此時發送的這個數據包本身就是一個short packet。
URB_NO_INTERRUPT,這個標志用來告訴HCD,在URB完成后,不要請求一個硬件中斷,當然這就意味着你的結束處理函數可能不會在urb完成后立即被調用,而是在之后的某個時間被調用,咱們的usb core會保證為每個urb調用一次結束處理函數。
transfer_buffer,transfer_dma,transfer_buffer_length,前面說過管道的一端是主機上的緩沖區,一端是設備上的端點,這三個家伙就是描述主機上的那個緩沖區的。transfer_buffer是使用kmalloc分配的緩沖區,transfer_dma是使用usb_buffer_alloc分配的dma緩沖區,HCD不會同時使用它們兩個,如果你的urb自帶了transfer_dma,就要同時設置URB_NO_TRANSFER_DMA_MAP來告訴HCD一聲,不用它再費心做DMA映射了。transfer_buffer 是必須要設置的,因為不是所有的主機控制器都能夠使用DMA的,萬一遇到這樣的情況,也好有個備用。transfer_buffer_length指的就是transfer_buffer或transfer_dma的長度。
actual_length,urb結束之后,會用這個字段告訴你實際上傳輸了多少數據。
setup_packet,setup_dma,同樣是兩個緩沖區,一個是kmalloc分配的,一個是用usb_buffer_alloc分配的,不過,這兩個緩沖區是控制傳輸專用的,如果你的urb設置了setup_dma,同樣要設置URB_NO_SETUP_DMA_MAP標志來告訴HCD。如果進行的是控制傳輸,setup_packet是必須要設置的,也是為了防止出現主機控制器不能使用DMA的情況。
start_frame,如果你沒有指定URB_ISO_ASAP標志,就必須自己設置start_frame,指定等時傳輸在哪幀或微幀開始。如果指定了URB_ISO_ASAP,urb結束時會使用這個值返回實際的開始幀號。
interval,等時和中斷傳輸專用。interval間隔時間的意思,什么的間隔時間?就是上面說的端點希望主機輪詢自己的時間間隔。這個值和端點描述符里的bInterval是一樣的,你不能隨便兒的指定一個,協議里對你能指定的值是有范圍限制的,對於中斷傳輸,全速時,這個范圍為1~255ms,低速是為10~255ms,高速時為1~16,這個1~16只是bInterval可以取的值,實際的間隔時間需要計算一下,為2的(bInterval-1)次方乘以125微妙,也就是2的(bInterval-1)次方個微幀。對於等時傳輸,沒有低速了,等時傳輸根本就不是低速端點負擔得起的,對於全速和高速,這個范圍也是為1~16,間隔時間由2的(bInterval-1)次方算出來,單位為幀或微幀。這樣看來,每一幀或微幀里,你最多只能期望有一次等時和中斷傳輸,不過即使完全按照上面的范圍來取,你的期望也並不是就肯定可以實現的,因為對於高速來說,最多有80%的總線時間給這兩種傳輸用,對於低速和全速要多點兒,達到90%,這個時間怎么分配,都由主機控制器掌握着,所以你的期望能不能實現還要看主機控制器的臉色,沒辦法,它就有這種權力。
context,驅動設置了給下面的結束處理函數用的。比如可以將自己驅動里描述自己設備的結構體放在里邊兒,在結束處理函數里就可以取出來。
complete,一個指向結束處理函數的指針,傳輸成功完成,或者中間發生錯誤的時候就會調用它,驅動可以在這里邊兒檢查urb的狀態,並做一些處理,比如可以釋放這個urb,或者重新提交給HCD。比如攝像頭吧,你向HCD提交了個等時的urb從攝像頭那里讀取視頻數據,傳輸完成的時候調用了你指定的這個結束處理函數,並在里面取出了urb里面獲得的數據進行解碼等處理,然后怎么着?總不會這一個urb讀取的數據就夠你向mm表白了吧,你的愛慕之情可是猶如滔滔江水連綿不絕,所以需要獲得更多的數據,那你也總不會再去創建、初始化一個等時的urb吧,即使再窮極無聊的人也不會那么做,明顯剛剛的那個可以繼續用的,只要將它再次提交給HCD就可以了。這個函數指針的定義在include/linux/usb.h
typedef void (*usb_complete_t)(struct urb *);
還有三個成員是等時傳輸專用的,分別是iso_frame_desc、number_of_packets、error_count,等時傳輸與其它傳輸不一樣,可以指定傳輸多少個packet,每個packet使用struct usb_iso_packet_descriptor結構來描述。iso_frame_desc就表示了一個變長的struct usb_iso_packet_descriptor結構體數組,number_of_packets指定了要這個結構體數組的大小,也就是要傳輸多少個packet。這里說的packet不是說你在一次等時傳輸里傳輸了多個Data packet,而是說你在一個urb里指定了多次的等時傳輸,每個struct usb_iso_packet_descriptor結構體都代表了一次等時傳輸。
這里說一下等時傳輸底層的packet情況。不像控制傳輸最少要有SETUP和STATUS兩個階段的transaction,等時傳輸只有Isochronous transaction,即等時transaction一個階段,一次等時傳輸就是一次等時transaction的過程。而等時transaction也只有兩個階段,就是主機向設備發送OUT Token包,然后發送一個Data包,或者是主機向設備發送IN Token包,然后設備向主機發送一個Data包,這個Data包里data payload的長度只能小於或者等於等時端點的wMaxPacketSize值。這里沒有了Handshake包,因為不需要,等時傳輸是不保證數據完全正確無誤的到達的,沒有什么錯誤重傳機制,也就不需要使用Handshake包來匯報OK不OK。對它來說實時要比正確性重要的多,你的攝像頭一秒鍾少給你一幀多給你一幀,沒什么本質的區別,如果給你延遲個幾秒,就明顯的感覺不爽了。所以對於等時傳輸來說,在完成了number_of_packets次傳輸之后,會去調用你的結束處理函數,在里面對數據做處理,而error_count記錄了這么多次傳輸中發生錯誤的次數。
最后看一下struct usb_iso_packet_descriptor結構的定義,在include/linux/usb.h里定義
952 struct usb_iso_packet_descriptor {
953 unsigned int offset;
954 unsigned int length; /* expected length */
955 unsigned int actual_length;
956 int status;
957 };
offset表示transfer_buffer里的偏移位置,指定了要進行number_of_packets次等時傳輸么,同時也要准備夠這么多次傳輸用的緩沖區。當然不是說准備多個緩沖區,沒必要,都放transfer_buffer或者transfer_dma里面就行了,只要記着每次傳輸對應的數據偏移就可以。length是預期的這次等時傳輸Data包里數據的長度,注意這里說的是預期,因為實際傳輸時因為種種原因可能不會有那么多數據,urb結束時,每個struct usb_iso_packet_descriptor結構體的actual_length就表示了各次等時傳輸實際傳輸的數據長度,而status分別記錄了它們的狀態。
struct urb結構暫且說到這里了。好累!!!!!!