usb驱动开发12之设备生命线

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

usb驱动开发12之设备生命线

 

这个函数大概包含一个中心,三个基本点,以一个struct urb结构体为中心,以usb_alloc_urb、usb_fill_control_urb、usb_start_wait_urb三个函数为基本点。

一个中心: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,以便分配给特定的主机控制器驱动进行处理,然后默默的等待处理结果,或者超时。

usb驱动开发12之设备生命线

既然是核心结构体,代码的注释贴上来怎么看都不过分。 

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里定义的这个函数。

usb驱动开发12之设备生命线

 

再次强调重要的结构体和函数处理,我会连注释和代码一起贴上来,以后不说了。

说说其中函数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结构暂且说到这里了。好累!!!!!!

你可能感兴趣的:(开发)