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

/*-------------------------------------------------------------------*/
// returns status (negative) or length (positive)
static int usb_internal_control_msg(struct usb_device *usb_dev,
unsigned int pipe,
struct usb_ctrlrequest *cmd,
void *data, int len, int timeout)
{
struct urb *urb;
int retv;
int length; urb = usb_alloc_urb(0, GFP_NOIO);
if (!urb)
return -ENOMEM; usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
len, usb_api_blocking_completion, NULL); retv = usb_start_wait_urb(urb, timeout, &length);
if (retv < 0)
return retv;
else
return length;
}
这个函数大概包含一个中心,三个基本点,以一个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,以便分配给特定的主机控制器驱动进行处理,然后默默的等待处理结果,或者超时。

/**
* struct urb - USB Request Block
* @urb_list: For use by current owner of the URB.
* @pipe: Holds endpoint number, direction, type, and more.
* Create these values with the eight macros available;
* usb_{snd,rcv}TYPEpipe(dev,endpoint), where the TYPE is "ctrl"
* (control), "bulk", "int" (interrupt), or "iso" (isochronous).
* For example usb_sndbulkpipe() or usb_rcvintpipe(). Endpoint
* numbers range from zero to fifteen. Note that "in" endpoint two
* is a different endpoint (and pipe) from "out" endpoint two.
* The current configuration controls the existence, type, and
* maximum packet size of any given endpoint.
* @dev: Identifies the USB device to perform the request.
* @status: This is read in non-iso completion functions to get the
* status of the particular request. ISO requests only use it
* to tell whether the URB was unlinked; detailed status for
* each frame is in the fields of the iso_frame-desc.
* @transfer_flags: A variety of flags may be used to affect how URB
* submission, unlinking, or operation are handled. Different
* kinds of URB can use different flags.
* @transfer_buffer: This identifies the buffer to (or from) which
* the I/O request will be performed (unless URB_NO_TRANSFER_DMA_MAP
* is set). This buffer must be suitable for DMA; allocate it with
* kmalloc() or equivalent. For transfers to "in" endpoints, contents
* of this buffer will be modified. This buffer is used for the data
* stage of control transfers.
* @transfer_dma: When transfer_flags includes URB_NO_TRANSFER_DMA_MAP,
* the device driver is saying that it provided this DMA address,
* which the host controller driver should use in preference to the
* transfer_buffer.
* @transfer_buffer_length: How big is transfer_buffer. The transfer may
* be broken up into chunks according to the current maximum packet
* size for the endpoint, which is a function of the configuration
* and is encoded in the pipe. When the length is zero, neither
* transfer_buffer nor transfer_dma is used.
* @actual_length: This is read in non-iso completion functions, and
* it tells how many bytes (out of transfer_buffer_length) were
* transferred. It will normally be the same as requested, unless
* either an error was reported or a short read was performed.
* The URB_SHORT_NOT_OK transfer flag may be used to make such
* short reads be reported as errors.
* @setup_packet: Only used for control transfers, this points to eight bytes
* of setup data. Control transfers always start by sending this data
* to the device. Then transfer_buffer is read or written, if needed.
* @setup_dma: For control transfers with URB_NO_SETUP_DMA_MAP set, the
* device driver has provided this DMA address for the setup packet.
* The host controller driver should use this in preference to
* setup_packet.
* @start_frame: Returns the initial frame for isochronous transfers.
* @number_of_packets: Lists the number of ISO transfer buffers.
* @interval: Specifies the polling interval for interrupt or isochronous
* transfers. The units are frames (milliseconds) for for full and low
* speed devices, and microframes (1/8 millisecond) for highspeed ones.
* @error_count: Returns the number of ISO transfers that reported errors.
* @context: For use in completion functions. This normally points to
* request-specific driver context.
* @complete: Completion handler. This URB is passed as the parameter to the
* completion function. The completion function may then do what
* it likes with the URB, including resubmitting or freeing it.
* @iso_frame_desc: Used to provide arrays of ISO transfer buffers and to
* collect the transfer status for each buffer.
*
* This structure identifies USB transfer requests. URBs must be allocated by
* calling usb_alloc_urb() and freed with a call to usb_free_urb().
* Initialization may be done using various usb_fill_*_urb() functions. URBs
* are submitted using usb_submit_urb(), and pending requests may be canceled
* using usb_unlink_urb() or usb_kill_urb().
*
* Data Transfer Buffers:
*
* Normally drivers provide I/O buffers allocated with kmalloc() or otherwise
* taken from the general page pool. That is provided by transfer_buffer
* (control requests also use setup_packet), and host controller drivers
* perform a dma mapping (and unmapping) for each buffer transferred. Those
* mapping operations can be expensive on some platforms (perhaps using a dma
* bounce buffer or talking to an IOMMU),
* although they're cheap on commodity x86 and ppc hardware.
*
* Alternatively, drivers may pass the URB_NO_xxx_DMA_MAP transfer flags,
* which tell the host controller driver that no such mapping is needed since
* the device driver is DMA-aware. For example, a device driver might
* allocate a DMA buffer with usb_buffer_alloc() or call usb_buffer_map().
* When these transfer flags are provided, host controller drivers will
* attempt to use the dma addresses found in the transfer_dma and/or
* setup_dma fields rather than determining a dma address themselves. (Note
* that transfer_buffer and setup_packet must still be set because not all
* host controllers use DMA, nor do virtual root hubs).
*
* Initialization:
*
* All URBs submitted must initialize the dev, pipe, transfer_flags (may be
* zero), and complete fields. All URBs must also initialize
* transfer_buffer and transfer_buffer_length. They may provide the
* URB_SHORT_NOT_OK transfer flag, indicating that short reads are
* to be treated as errors; that flag is invalid for write requests.
*
* Bulk URBs may
* use the URB_ZERO_PACKET transfer flag, indicating that bulk OUT transfers
* should always terminate with a short packet, even if it means adding an
* extra zero length packet.
*
* Control URBs must provide a setup_packet. The setup_packet and
* transfer_buffer may each be mapped for DMA or not, independently of
* the other. The transfer_flags bits URB_NO_TRANSFER_DMA_MAP and
* URB_NO_SETUP_DMA_MAP indicate which buffers have already been mapped.
* URB_NO_SETUP_DMA_MAP is ignored for non-control URBs.
*
* Interrupt URBs must provide an interval, saying how often (in milliseconds
* or, for highspeed devices, 125 microsecond units)
* to poll for transfers. After the URB has been submitted, the interval
* field reflects how the transfer was actually scheduled.
* The polling interval may be more frequent than requested.
* For example, some controllers have a maximum interval of 32 milliseconds,
* while others support intervals of up to 1024 milliseconds.
* Isochronous URBs also have transfer intervals. (Note that for isochronous
* endpoints, as well as high speed interrupt endpoints, the encoding of
* the transfer interval in the endpoint descriptor is logarithmic.
* Device drivers must convert that value to linear units themselves.)
*
* Isochronous URBs normally use the URB_ISO_ASAP transfer flag, telling
* the host controller to schedule the transfer as soon as bandwidth
* utilization allows, and then set start_frame to reflect the actual frame
* selected during submission. Otherwise drivers must specify the start_frame
* and handle the case where the transfer can't begin then. However, drivers
* won't know how bandwidth is currently allocated, and while they can
* find the current frame using usb_get_current_frame_number () they can't
* know the range for that frame number. (Ranges for frame counter values
* are HC-specific, and can go from 256 to 65536 frames from "now".)
*
* Isochronous URBs have a different data transfer model, in part because
* the quality of service is only "best effort". Callers provide specially
* allocated URBs, with number_of_packets worth of iso_frame_desc structures
* at the end. Each such packet is an individual ISO transfer. Isochronous
* URBs are normally queued, submitted by drivers to arrange that
* transfers are at least double buffered, and then explicitly resubmitted
* in completion handlers, so
* that data (such as audio or video) streams at as constant a rate as the
* host controller scheduler can support.
*
* Completion Callbacks:
*
* The completion callback is made in_interrupt(), and one of the first
* things that a completion handler should do is check the status field.
* The status field is provided for all URBs. It is used to report
* unlinked URBs, and status for all non-ISO transfers. It should not
* be examined before the URB is returned to the completion handler.
*
* The context field is normally used to link URBs back to the relevant
* driver or request state.
*
* When the completion callback is invoked for non-isochronous URBs, the
* actual_length field tells how many bytes were transferred. This field
* is updated even when the URB terminated with an error or was unlinked.
*
* ISO transfer status is reported in the status and actual_length fields
* of the iso_frame_desc array, and the number of errors is reported in
* error_count. Completion callbacks for ISO transfers will normally
* (re)submit URBs to ensure a constant transfer rate.
*
* Note that even fields marked "public" should not be touched by the driver
* when the urb is owned by the hcd, that is, since the call to
* usb_submit_urb() till the entry into the completion routine.
*/
struct urb
{
/* private: usb core and host controller only fields in the urb */
struct kref kref; /* reference count of the URB */
spinlock_t lock; /* lock for the URB */
void *hcpriv; /* private data for host controller */
atomic_t use_count; /* concurrent submissions counter */
u8 reject; /* submissions will fail */ /* 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 usb_device *dev; /* (in) pointer to associated device */
unsigned int pipe; /* (in) pipe information */
int status; /* (return) non-ISO status */
unsigned int transfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/
void *transfer_buffer; /* (in) associated data buffer */
dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */
int transfer_buffer_length; /* (in) data buffer length */
int actual_length; /* (return) actual transfer length */
unsigned char *setup_packet; /* (in) setup packet (control only) */
dma_addr_t setup_dma; /* (in) dma addr for setup_packet */
int start_frame; /* (modify) start frame (ISO) */
int number_of_packets; /* (in) number of ISO packets */
int interval; /* (modify) transfer interval
* (INT/ISO) */
int error_count; /* (return) number of ISO errors */
void *context; /* (in) context for completion */
usb_complete_t complete; /* (in) completion routine */
struct usb_iso_packet_descriptor iso_frame_desc[0];
/* (in) ISO ONLY */
};
既然是核心结构体,代码的注释贴上来怎么看都不过分。 

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_kill_urb - cancel a transfer request and wait for it to finish
* @urb: pointer to URB describing a previously submitted request,
* may be NULL
*
* This routine cancels an in-progress request. It is guaranteed that
* upon return all completion handlers will have finished and the URB
* will be totally idle and available for reuse. These features make
* this an ideal way to stop I/O in a disconnect() callback or close()
* function. If the request has not already finished or been unlinked
* the completion handler will see urb->status == -ENOENT.
*
* While the routine is running, attempts to resubmit the URB will fail
* with error -EPERM. Thus even if the URB's completion handler always
* tries to resubmit, it will not succeed and the URB will become idle.
*
* This routine may not be used in an interrupt context (such as a bottom
* half or a completion handler), or when holding a spinlock, or in other
* situations where the caller can't schedule().
*/
void usb_kill_urb(struct urb *urb)
{
might_sleep();
if (!(urb && urb->dev && urb->dev->bus))
return;
spin_lock_irq(&urb->lock);
++urb->reject;
spin_unlock_irq(&urb->lock); usb_hcd_unlink_urb(urb, -ENOENT);
wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); spin_lock_irq(&urb->lock);
--urb->reject;
spin_unlock_irq(&urb->lock);
}

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

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

usb驱动开发12之设备生命线的更多相关文章

  1. usb驱动开发14之设备生命线

    直接看代码吧. /*-------------------------------------------------------------------*/ /** * usb_submit_urb ...

  2. usb驱动开发17之设备生命线

    拜会完了山头的几位大哥,还记得我们从哪里来要到哪里去吗?时刻不能忘记自身的使命啊.我们是从usb_submit_urb()最后的那个遗留问题usb_hcd_submit_urb()函数一路走来,现在就 ...

  3. usb驱动开发16之设备生命线

    回到struct usb_hcd,继续努力的往下看. kref,usb主机控制器的引用计数.struct usb_hcd也有自己专用的引用计数函数,看hcd.c文件. static void hcd_ ...

  4. usb驱动开发15之设备生命线

    总算是进入了HCD的片儿区,既然来到一个片区,怎么都要去拜会一下山头几个大哥吧.,先回忆一些我们怎么到这里的?给你列举一个调用函数过程usb_control_msg->usb_internal_ ...

  5. usb驱动开发18之设备生命线

    现在已经使用GET_DESCRIPTOR请求取到了包含一个配置里所有相关描述符内容的一堆数据,这些数据是raw的,即原始的,所有数据不管是配置描述符.接口描述符还是端点描述符都挤在一起,所以得想办法将 ...

  6. usb驱动开发13之设备生命线

    上一节勉勉强强把struct urb这个中心给说完,接着看那三个基本点. 第一个基本点,usb_alloc_urb函数,创建urb的专用函数,为一个urb申请内存并做初始化,在drviers/usb/ ...

  7. usb驱动开发11之设备生命线

    暂时先告别媒人,我们去分析各自的生命旅程,最后还会回到usb_device_match函数. 首先当你将usb设备连接在hub的某个端口上,hub检测到有设备连接了进来,它会为设备分配一个struct ...

  8. HarmonyOS USB DDK助你轻松实现USB驱动开发

    HDF(Hardware Driver Foundation)驱动框架是HarmonyOS硬件生态开放的基础,为开发者提供了驱动加载.驱动服务管理和驱动消息机制等驱动能力,让开发者能精准且高效地开发驱 ...

  9. usb驱动开发22之驱动生命线

    我们总是很喜欢高潮,不是吗?那就好好对待她哦.我们来看一下linux中的高潮部分设备是怎么从Address进入Configured的. usb_set_configuration函数的代码就不贴了,可 ...

随机推荐

  1. 【代码笔记】iOS-scrollerView里多个tableView加搜索框

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> #import "customCell.h&qu ...

  2. [读书笔记] Head First 设计模式

    OO基础 抽象 封装 多态 继承 OO原则 原则 描述 封装变化 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混合在一起. 把会变化的部分取出来并“封装”起来,好让其他部分不 ...

  3. Nodejs——包与NPM

    在模块之外,包和NPM则是将模块联系起来的一种机制. CommonJS的包规范由包结构和包描述文件组成. 包实际上是一个存档文件,即一个目录直接打包为.zip或tar.gz格式的文件. 完全符合Com ...

  4. 最新中国菜刀caidao-20160620下载和说明

    0x00 中国制造, 黑客之刀 中国菜刀是中国安全圈内使用非常广泛的一款Webshell管理工具,此次老兵大牛进行了更新,界面更加优美,功能更加丰富,有图有真相! 0x01 验证 此次更新还自带了官网 ...

  5. yii2搭建完美后台并实现rbac权限控制案例教程

    作者:白狼 出处:www.manks.top/article/yii2_frame_rbac_template 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连 ...

  6. NuGet学习笔记3——搭建属于自己的NuGet服务器

    文章导读 创建NuGetServer Web站点 发布站点到IIS 添加本地站点到包包数据源 在上一篇NuGet学习笔记(2) 使用图形化界面打包自己的类库 中讲解了如何打包自己的类库,接下来进行最重 ...

  7. SQL Server(三)——增、删、改、查

    一.数据库操作 create database 数据库名称 ——创建drop database 数据库名称 ——删除use 数据库名称 ——使用go 两条SQL语句之间分隔 二.表的操作 create ...

  8. java中的list,set,数组之间的转换

    使用该工具类import org.apache.commons.collections.CollectionUtils; 在Apache Jakarta Commons Collections中 St ...

  9. C++杂谈(三)产生随机数与time函数

    产生随机数在程序中很有用,这篇文章简单介绍一下产生随机数的方法. 伪随机数 使用标准库<cstdlib>中的rand()函数产生随机数. #include<iostream> ...

  10. PHP&MySQL(二)——困也得啃书

    madan,所有事情都敢赶在一起...以后每天中午去学车啊,好开心..晚上好困,但是困也得啃书........ 二.PHP脚本编程语言 什么变量啊,数据类型啊,特别特别基本的不记录了,说点容易忽略的. ...