现在的位置: 首页 > 综合 > 正文

linux设备驱动之USB数据传输分析

2013年06月21日 ⁄ 综合 ⁄ 共 32804字 ⁄ 字号 评论关闭
三:传输过程的实现
说到传输过程,我们必须要从URB开始说起,这个结构的就好比是网络子系统中的skb,好比是I/O中的bio.USB系统的信息传输就是打成URB结构,然后再过行传送的.
URB的全称叫USB request block.下面从它的接口说起.
3.1:URB的相关接口
1:URB的创建
URB的创建是由usb_alloc_urb()完成的.这个函数会完成URB内存的分配和基本成员的初始化工作.代码如下:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
{
   struct urb *urb;
 
    urb = kmalloc(sizeof(struct urb) +
        iso_packets * sizeof(struct usb_iso_packet_descriptor),
        mem_flags);
    if (!urb) {
        err("alloc_urb: kmalloc failed");
        return NULL;
    }
    usb_init_urb(urb);
    return urb;
}
这个函数有两个参数,一个是iso_packets.仅仅用于ISO传输.表示ISO数据包个数,如果用于其它类型的传输,此参数为0.另一个是mem_flags.是分配内存的参数.
Usb_init_urb()如下:
void usb_init_urb(struct urb *urb)
{
    if (urb) {
        memset(urb, 0, sizeof(*urb));
        kref_init(&urb->kref);
        INIT_LIST_HEAD(&urb->anchor_list);
    }
}
由此可以看到,它的初始化只是初始化了引用计数和ahchor_list链表.这个链表在URB被锁定的时候会用到.
 
2:URB的初始化
USB2.0 spec中定义了四种传输,为别为ISO,INTER,BULK,CONTORL.linux kernel为INTER,BULK,CONTORL的URB初始化提供了一些API,ISO的传输只能够手动去初始化.这些API如下:
static inline 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_fn,
                    void *context)
static inline void usb_fill_bulk_urb(struct urb *urb,
                     struct usb_device *dev,
                     unsigned int pipe,
                     void *transfer_buffer,
                     int buffer_length,
                     usb_complete_t complete_fn,
                     void *context)
static inline void usb_fill_int_urb(struct urb *urb,
                    struct usb_device *dev,
                    unsigned int pipe,
                    void *transfer_buffer,
                    int buffer_length,
                    usb_complete_t complete_fn,
                    void *context,
                    int interval)
分别用来填充CONTORL,BULK,INT类型的URB.
观察他们的函数原型,发现有很多相的的参数.先对这些参数做一下解释:
Urb:是要初始化的urb
Dev:表示消息要被发送到的USB设备
Pipe:表示消息被发送到的端点
transfer_buffer:表示发送数据的缓冲区
length:就是transfer_buffer所表示的缓冲区大小
context:完成处理函数的上下文
complete_fn:传输完了之后要调用的函数.
usb_fill_control_urb()的setup_packet:即将被发送到端点的设备数据包
usb_fill_int_urb()中的interval:这个urb应该被调度的间隔.
函数的实际都是差不多的.以usb_fill_control_urb()为例:
static inline 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_fn,
                    void *context)
{
    urb->dev = dev;
    urb->pipe = pipe;
    urb->setup_packet = setup_packet;
    urb->transfer_buffer = transfer_buffer;
    urb->transfer_buffer_length = buffer_length;
    urb->complete = complete_fn;
    urb->context = context;
}
如上所示,只是将函数的参数赋值给了URB相关的成员而已.
另外,关于ISO的URB初始化虽然没有可以调用的API,但它的初始化也很简单,对应就是填充几个成员而已.
另外,对于pipe的参数.有一系列辅助的宏.如下示:
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint)   /
    ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev,endpoint)   /
    ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev,endpoint)   /
    ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev,endpoint)   /
    ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev,endpoint)   /
    ((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev,endpoint)   /
    ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev,endpoint)    /
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev,endpoint)    /
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
这个宏都是根据usb2.0 spec的规范来设计的.
 
3:提交URB
提交urb的接口是usb_submit_urb().代码如下:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
    int             xfertype, max;
    struct usb_device       *dev;
    struct usb_host_endpoint    *ep;
    int             is_out;
 
    if (!urb || urb->hcpriv || !urb->complete)
        return -EINVAL;
    dev = urb->dev;
    if ((!dev) || (dev->state < USB_STATE_DEFAULT))
        return -ENODEV;
 
    /* For now, get the endpoint from the pipe.  Eventually drivers
     * will be required to set urb->ep directly and we will eliminate
     * urb->pipe.
     */
 
    //取得要传输的端口.对端地址是由方向+dev address+port number组成的
    ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out)
            [usb_pipeendpoint(urb->pipe)];
    if (!ep)
        return -ENOENT;
 
    urb->ep = ep;
    urb->status = -EINPROGRESS;
    urb->actual_length = 0;
 
    /* Lots of sanity checks, so HCDs can rely on clean data
     * and don't need to duplicate tests
     */
     //取得ep的传输类型
    xfertype = usb_endpoint_type(&ep->desc);
    //如果是控制传输.端点0默认是控制传输
    if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
        //控制传输的urb如果没有setup_packet是非法的
        struct usb_ctrlrequest *setup =
                (struct usb_ctrlrequest *) urb->setup_packet;
 
        if (!setup)
            return -ENOEXEC;
        //判断是否是out方向的传输
        is_out = !(setup->bRequestType & USB_DIR_IN) ||
                !setup->wLength;
    } else {
        //如果不是控制传输,在端点描述符的bEndportAddress的bit7 包含有端点的传输方向
        is_out = usb_endpoint_dir_out(&ep->desc);
    }
 
    /* Cache the direction for later use */
    //根据传输方向.置urb->transfer_flags的方向位
    urb->transfer_flags = (urb->transfer_flags & ~URB_DIR_MASK) |
            (is_out ? URB_DIR_OUT : URB_DIR_IN);
 
    //根据usb2.0 spec.除控制传输外的其它传输只有在config状态的时候才能进行
    if (xfertype != USB_ENDPOINT_XFER_CONTROL &&
            dev->state < USB_STATE_CONFIGURED)
        return -ENODEV;
 
    //传送/接收的最大字节.如果这个最大巧若拙字节还要小于0,那就是非法的
    max = le16_to_cpu(ep->desc.wMaxPacketSize);
    if (max <= 0) {
        dev_dbg(&dev->dev,
            "bogus endpoint ep%d%s in %s (bad maxpacket %d)/n",
            usb_endpoint_num(&ep->desc), is_out ? "out" : "in",
            __FUNCTION__, max);
        return -EMSGSIZE;
    }
 
    /* periodic transfers limit size per frame/uframe,
     * but drivers only control those sizes for ISO.
     * while we're checking, initialize return status.
     */
     //如果是实时传输
    if (xfertype == USB_ENDPOINT_XFER_ISOC) {
        int n, len;
 
        /* "high bandwidth" mode, 1-3 packets/uframe? */
        //如果是高速传输.则要修正它的MAX值
        //高速传输时, 一个微帧内可以修输多个数据.bit 11~bit12用来表示一个微帧内
        //传输包的个数.
        //在USB1.1中是不支持HIGH的
        if (dev->speed == USB_SPEED_HIGH) {
            int mult = 1 + ((max >> 11) & 0x03);
            max &= 0x07ff;
            max *= mult;
        }
 
        //实现传输的数据包数目不能小于等于0
        if (urb->number_of_packets <= 0)
            return -EINVAL;
        //urb->number_of_packets: 实时数据包个数.每个实时数据包对应urb->iso_frame_desc[]中的一项
        for (n = 0; n < urb->number_of_packets; n++) {
            len = urb->iso_frame_desc[n].length;
            if (len < 0 || len > max)
                return -EMSGSIZE;
            urb->iso_frame_desc[n].status = -EXDEV;
            urb->iso_frame_desc[n].actual_length = 0;
        }
    }
 
    /* the I/O buffer must be mapped/unmapped, except when length=0 */
    //如果要传输的缓存区大小小于0.非法
    if (urb->transfer_buffer_length < 0)
        return -EMSGSIZE;
 
#ifdef DEBUG
    /* stuff that drivers shouldn't do, but which shouldn't
     * cause problems in HCDs if they get it wrong.
     */
    {
    unsigned int    orig_flags = urb->transfer_flags;
    unsigned int    allowed;
 
    /* enforce simple/standard policy */
    allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP |
            URB_NO_INTERRUPT | URB_DIR_MASK | URB_FREE_BUFFER);
    switch (xfertype) {
    case USB_ENDPOINT_XFER_BULK:
        if (is_out)
            allowed |= URB_ZERO_PACKET;
        /* FALLTHROUGH */
    case USB_ENDPOINT_XFER_CONTROL:
        allowed |= URB_NO_FSBR; /* only affects UHCI */
        /* FALLTHROUGH */
    default:            /* all non-iso endpoints */
        if (!is_out)
            allowed |= URB_SHORT_NOT_OK;
        break;
    case USB_ENDPOINT_XFER_ISOC:
        allowed |= URB_ISO_ASAP;
        break;
    }
    urb->transfer_flags &= allowed;
 
    /* fail if submitter gave bogus flags */
    if (urb->transfer_flags != orig_flags) {
        err("BOGUS urb flags, %x --> %x",
            orig_flags, urb->transfer_flags);
        return -EINVAL;
    }
    }
#endif
    /*
     * Force periodic transfer intervals to be legal values that are
     * a power of two (so HCDs don't need to).
     *
     * FIXME want bus->{intr,iso}_sched_horizon values here.  Each HC
     * supports different values... this uses EHCI/UHCI defaults (and
     * EHCI can use smaller non-default values).
     */
 
//关于实时传输和中断传输的interval处理
    switch (xfertype) {
    case USB_ENDPOINT_XFER_ISOC:
    case USB_ENDPOINT_XFER_INT:
        /* too small? */
        //interval不能小于或等于0
        if (urb->interval <= 0)
            return -EINVAL;
        /* too big? */
        switch (dev->speed) {
        case USB_SPEED_HIGH:    /* units are microframes */
            /* NOTE usb handles 2^15 */
            if (urb->interval > (1024 * 8))
                urb->interval = 1024 * 8;
            max = 1024 * 8;
            break;
        case USB_SPEED_FULL:    /* units are frames/msec */
        case USB_SPEED_LOW:
            if (xfertype == USB_ENDPOINT_XFER_INT) {
                if (urb->interval > 255)
                    return -EINVAL;
                /* NOTE ohci only handles up to 32 */
                max = 128;
            } else {
                if (urb->interval > 1024)
                    urb->interval = 1024;
                /* NOTE usb and ohci handle up to 2^15 */
                max = 1024;
            }
            break;
        default:
            return -EINVAL;
        }
        /* Round down to a power of 2, no more than max */
        urb->interval = min(max, 1 << ilog2(urb->interval));
    }
 
    return usb_hcd_submit_urb(urb, mem_flags);
}
这段代码虽然很长,但逻辑很清楚.对照代码中的注释理解应该是没有问题的.在这里要注意,UHCI是属于USB1.1的,它不支持HIGH传输.
对URB进行一系列处理之后,就会将urb丢给hcd进行处理了.usb_hcd_submit_urb()代码如下:
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
    int         status;
    //从usb_bus的地址取得usb_hcd的地址
    struct usb_hcd      *hcd = bus_to_hcd(urb->dev->bus);
 
    /* increment urb's reference count as part of giving it to the HCD
     * (which will control it).  HCD guarantees that it either returns
     * an error or calls giveback(), but not both.
     */
     //增加有关的引用计数,usbmon*系列的函数是编译选择的.忽略
    usb_get_urb(urb);
    atomic_inc(&urb->use_count);
    atomic_inc(&urb->dev->urbnum);
    usbmon_urb_submit(&hcd->self, urb);
 
    /* NOTE requirements on root-hub callers (usbfs and the hub
     * driver, for now):  URBs' urb->transfer_buffer must be
     * valid and usb_buffer_{sync,unmap}() not be needed, since
     * they could clobber root hub response data.  Also, control
     * URBs must be submitted in process context with interrupts
     * enabled.
     */
     //对传输的缓存区进行DMA映射
    status = map_urb_for_dma(hcd, urb, mem_flags);
    //出现错误,返回
    if (unlikely(status)) {
        usbmon_urb_submit_error(&hcd->self, urb, status);
        goto error;
    }
 
    //如果是root hub
    if (is_root_hub(urb->dev))
        status = rh_urb_enqueue(hcd, urb);
    else
        //如果是一般的设备
        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
 
    if (unlikely(status)) {
        usbmon_urb_submit_error(&hcd->self, urb, status);
        unmap_urb_for_dma(hcd, urb);
 error:
        urb->hcpriv = NULL;
        INIT_LIST_HEAD(&urb->urb_list);
        atomic_dec(&urb->use_count);
        atomic_dec(&urb->dev->urbnum);
        if (urb->reject)
            wake_up(&usb_kill_urb_queue);
        usb_put_urb(urb);
    }
    return status;
}
在这里函数里要注意到,urb->transfer_buffer是一个虚拟地址,用于UHCI的时候,必须要将其映射物理地址,以供设备使用.这也就是map_urb_for_dma()要完成的工作.
map_urb_for_dma()函数比较简单,这里就不做详细分析.
可能有人会有这样的疑惑,对于root hub的情况,为什么不用对传输缓存区进行DMA映射呢?
在后面的处理中我们可以看到,其实对于root hub ,它不需要进行实际的物理传输,linux按照spec上的规定,将它静态放置在内存中,在进行相关操作的时候,只要直接copy过去就可以了.
其次,要注意,这个函数不能用于中断上下文,因为该函数是同步的,会引起睡眠.
在这里,我们看到,流程最终转入到了下面的代码片段中:
    //如果是root hub
    if (is_root_hub(urb->dev))
        status = rh_urb_enqueue(hcd, urb);
    else
        //如果是一般的设备
        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
下面,就分情况来剖析,各种传输到底是怎么完成的.
 
3.2:控制传输过程
1:root hub的控制传输
在前面看到,对于root hub的情况,流程会转入rh_urb_enqueue().代码如下:
static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
{
    //如果是中断传输的端点
    if (usb_endpoint_xfer_int(&urb->ep->desc))
        return rh_queue_status (hcd, urb);
    //如果是控制传输的端点
    if (usb_endpoint_xfer_control(&urb->ep->desc))
        return rh_call_control (hcd, urb);
    return -EINVAL;
}
对应是控制传输的时,流程转入了rh_call_control()中:
static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
{
    struct usb_ctrlrequest *cmd;
    u16     typeReq, wValue, wIndex, wLength;
    u8      *ubuf = urb->transfer_buffer;
    u8      tbuf [sizeof (struct usb_hub_descriptor)]
        __attribute__((aligned(4)));
    const u8    *bufp = tbuf;
    int     len = 0;
    int     patch_wakeup = 0;
    int     status;
    int     n;
 
    might_sleep();
 
    spin_lock_irq(&hcd_root_hub_lock);
    //将urb加到ep的urb传输链表
    status = usb_hcd_link_urb_to_ep(hcd, urb);
    spin_unlock_irq(&hcd_root_hub_lock);
    if (status)
        return status;
    //将urb的私有域指向了hcd
    urb->hcpriv = hcd;  /* Indicate it's queued */
 
    cmd = (struct usb_ctrlrequest *) urb->setup_packet;
    typeReq  = (cmd->bRequestType << 8) | cmd->bRequest;
    wValue   = le16_to_cpu (cmd->wValue);
    wIndex   = le16_to_cpu (cmd->wIndex);
    wLength  = le16_to_cpu (cmd->wLength);
 
    if (wLength > urb->transfer_buffer_length)
        goto error;
 
    urb->actual_length = 0;
    switch (typeReq) {
 
    /* DEVICE REQUESTS */
 
    /* The root hub's remote wakeup enable bit is implemented using
     * driver model wakeup flags.  If this system supports wakeup
     * through USB, userspace may change the default "allow wakeup"
     * policy through sysfs or these calls.
     *
     * Most root hubs support wakeup from downstream devices, for
     * runtime power management (disabling USB clocks and reducing
     * VBUS power usage).  However, not all of them do so; silicon,
     * board, and BIOS bugs here are not uncommon, so these can't
     * be treated quite like external hubs.
     *
     * Likewise, not all root hubs will pass wakeup events upstream,
     * to wake up the whole system.  So don't assume root hub and
     * controller capabilities are identical.
     */
 
    case DeviceRequest | USB_REQ_GET_STATUS:
        tbuf [0] = (device_may_wakeup(&hcd->self.root_hub->dev)
                    << USB_DEVICE_REMOTE_WAKEUP)
                | (1 << USB_DEVICE_SELF_POWERED);
        tbuf [1] = 0;
        len = 2;
        break;
    case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
        if (wValue == USB_DEVICE_REMOTE_WAKEUP)
            device_set_wakeup_enable(&hcd->self.root_hub->dev, 0);
        else
            goto error;
        break;
    case DeviceOutRequest | USB_REQ_SET_FEATURE:
        if (device_can_wakeup(&hcd->self.root_hub->dev)
                && wValue == USB_DEVICE_REMOTE_WAKEUP)
            device_set_wakeup_enable(&hcd->self.root_hub->dev, 1);
        else
            goto error;
        break;
    case DeviceRequest | USB_REQ_GET_CONFIGURATION:
        tbuf [0] = 1;
        len = 1;
            /* FALLTHROUGH */
    case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
        break;
    case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
        switch (wValue & 0xff00) {
        case USB_DT_DEVICE << 8:
            if (hcd->driver->flags & HCD_USB2)
                bufp = usb2_rh_dev_descriptor;
            else if (hcd->driver->flags & HCD_USB11)
                bufp = usb11_rh_dev_descriptor;
            else
                goto error;
            len = 18;
            break;
        case USB_DT_CONFIG << 8:
            if (hcd->driver->flags & HCD_USB2) {
                bufp = hs_rh_config_descriptor;
                len = sizeof hs_rh_config_descriptor;
            } else {
                bufp = fs_rh_config_descriptor;
                len = sizeof fs_rh_config_descriptor;
            }
            if (device_can_wakeup(&hcd->self.root_hub->dev))
                patch_wakeup = 1;
            break;
        case USB_DT_STRING << 8:
            n = rh_string (wValue & 0xff, hcd, ubuf, wLength);
            if (n < 0)
                goto error;
            urb->actual_length = n;
            break;
        default:
            goto error;
        }
        break;
    case DeviceRequest | USB_REQ_GET_INTERFACE:
        tbuf [0] = 0;
        len = 1;
            /* FALLTHROUGH */
    case DeviceOutRequest | USB_REQ_SET_INTERFACE:
        break;
    case DeviceOutRequest | USB_REQ_SET_ADDRESS:
        // wValue == urb->dev->devaddr
        dev_dbg (hcd->self.controller, "root hub device address %d/n",
            wValue);
        break;
 
    /* INTERFACE REQUESTS (no defined feature/status flags) */
 
    /* ENDPOINT REQUESTS */
 
    case EndpointRequest | USB_REQ_GET_STATUS:
        // ENDPOINT_HALT flag
        tbuf [0] = 0;
        tbuf [1] = 0;
        len = 2;
            /* FALLTHROUGH */
    case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
    case EndpointOutRequest | USB_REQ_SET_FEATURE:
        dev_dbg (hcd->self.controller, "no endpoint features yet/n");
        break;
 
    /* CLASS REQUESTS (and errors) */
 
    default:
        /* non-generic request */
        switch (typeReq) {
        case GetHubStatus:
        case GetPortStatus:
            len = 4;
            break;
        case GetHubDescriptor:
            len = sizeof (struct usb_hub_descriptor);
            break;
        }
        status = hcd->driver->hub_control (hcd,
            typeReq, wValue, wIndex,
            tbuf, wLength);
        break;
error:
        /* "protocol stall" on error */
        status = -EPIPE;
    }
 
    //如果发生了错误,将len置为0,表示没有数据要返回的
    if (status) {
        len = 0;
        if (status != -EPIPE) {
            dev_dbg (hcd->self.controller,
                "CTRL: TypeReq=0x%x val=0x%x "
                "idx=0x%x len=%d ==> %d/n",
                typeReq, wValue, wIndex,
                wLength, status);
        }
    }
 
    //copy返回的数据到transfer_buffer
    if (len) {
        if (urb->transfer_buffer_length < len)
            len = urb->transfer_buffer_length;
        urb->actual_length = len;
        // always USB_DIR_IN, toward host
        memcpy (ubuf, bufp, len);
 
        /* report whether RH hardware supports remote wakeup */
        if (patch_wakeup &&
                len > offsetof (struct usb_config_descriptor,
                        bmAttributes))
            ((struct usb_config_descriptor *)ubuf)->bmAttributes
                |= USB_CONFIG_ATT_WAKEUP;
    }
 
    /* any errors get returned through the urb completion */
    spin_lock_irq(&hcd_root_hub_lock);
    //处理完成了,可以将urb从ep->urb_list上脱落了
    usb_hcd_unlink_urb_from_ep(hcd, urb);
 
    /* This peculiar use of spinlocks echoes what real HC drivers do.
     * Avoiding calls to local_irq_disable/enable makes the code
     * RT-friendly.
     */
    spin_unlock(&hcd_root_hub_lock);
    //URB已经使用完了,对它进行后续处理.包括调用complete唤醒调用进程
    usb_hcd_giveback_urb(hcd, urb, status);
    spin_lock(&hcd_root_hub_lock);
 
    spin_unlock_irq(&hcd_root_hub_lock);
    return 0;
}
从这里看到,它只是将值是保存在内存中的或者是调用驱动的hub_control接口,去读取/设置相关寄存器.这些过程就不再详细分析了.
 
2:非root_hub的控制传输
对于非root_hub的情况,流程会转入status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
对于UHCI,它的对应接口为uhci_urb_enqueue().代码如下:
static int uhci_urb_enqueue(struct usb_hcd *hcd,
        struct urb *urb, gfp_t mem_flags)
{
    int ret;
    struct uhci_hcd *uhci = hcd_to_uhci(hcd);
    unsigned long flags;
    struct urb_priv *urbp;
    struct uhci_qh *qh;
 
    spin_lock_irqsave(&uhci->lock, flags);
 
    //将urb加到通信端点的传输链表 urb->urb_list 加至urb->ep->urb_list
    ret = usb_hcd_link_urb_to_ep(hcd, urb);
    if (ret)
        goto done_not_linked;
 
    ret = -ENOMEM;
    //初始化urb->hcpriv,它的结构为struct urb_priv
    //urb->hcpriv = urbp
    //urbp->urb = urb
    urbp = uhci_alloc_urb_priv(uhci, urb);
    if (!urbp)
        goto done;
 
    //创建QH
    if (urb->ep->hcpriv)
        qh = urb->ep->hcpriv;
    else {
        qh = uhci_alloc_qh(uhci, urb->dev, urb->ep);
        if (!qh)
            goto err_no_qh;
    }
    urbp->qh = qh;
    //各类型的不同操作
    switch (qh->type) {
    case USB_ENDPOINT_XFER_CONTROL:
        ret = uhci_submit_control(uhci, urb, qh);
        break;
    case USB_ENDPOINT_XFER_BULK:
        ret = uhci_submit_bulk(uhci, urb, qh);
        break;
    case USB_ENDPOINT_XFER_INT:
        ret = uhci_submit_interrupt(uhci, urb, qh);
        break;
    case USB_ENDPOINT_XFER_ISOC:
        urb->error_count = 0;
        ret = uhci_submit_isochronous(uhci, urb, qh);
        break;
    }
    if (ret != 0)
        goto err_submit_failed;
 
    /* Add this URB to the QH */
    urbp->qh = qh;
    //将urbp加到qh的queue链表中
    list_add_tail(&urbp->node, &qh->queue);
 
    /* If the new URB is the first and only one on this QH then either
     * the QH is new and idle or else it's unlinked and waiting to
     * become idle, so we can activate it right away.  But only if the
     * queue isn't stopped. */
     //qh的queue中只有一个urbp的时候,且QH没有被禁用
    if (qh->queue.next == &urbp->node && !qh->is_stopped) {
        //启用这个QH进行传输了
        uhci_activate_qh(uhci, qh);
        //fsbr:高速传输
        uhci_urbp_wants_fsbr(uhci, urbp);
    }
    goto done;
 
err_submit_failed:
    if (qh->state == QH_STATE_IDLE)
        uhci_make_qh_idle(uhci, qh);    /* Reclaim unused QH */
err_no_qh:
    uhci_free_urb_priv(uhci, urbp);
done:
    if (ret)
        usb_hcd_unlink_urb_from_ep(hcd, urb);
done_not_linked:
    spin_unlock_irqrestore(&uhci->lock, flags);
    return ret;
}
Urbp是urb的一个扩展区结构,这个urbp最后包含的很多信息,具体如下:
Urbp->urb:指向了要传输的urb
Urbp->qh:指向传输的QH
在进行传输的时候,先创建一个QH结构,然后将数据打成TD的形式,再将TD挂到QH上面.
对于QH的创建,有必要跟进去看一下:
static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,
        struct usb_device *udev, struct usb_host_endpoint *hep)
{
    dma_addr_t dma_handle;
    struct uhci_qh *qh;
 
    qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);
    if (!qh)
        return NULL;
 
    memset(qh, 0, sizeof(*qh));
    qh->dma_handle = dma_handle;
 
    qh->element = UHCI_PTR_TERM;
    qh->link = UHCI_PTR_TERM;
 
    INIT_LIST_HEAD(&qh->queue);
    INIT_LIST_HEAD(&qh->node);
 
    if (udev) {     /* Normal QH */
        qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
        if (qh->type != USB_ENDPOINT_XFER_ISOC) {
            qh->dummy_td = uhci_alloc_td(uhci);
            if (!qh->dummy_td) {
                dma_pool_free(uhci->qh_pool, qh, dma_handle);
                return NULL;
            }
        }
        qh->state = QH_STATE_IDLE;
        qh->hep = hep;
        qh->udev = udev;
        hep->hcpriv = qh;
 
        //如果是中断传输或者者是实时传输.计算数据传输所耗的总线时间
        if (qh->type == USB_ENDPOINT_XFER_INT ||
                qh->type == USB_ENDPOINT_XFER_ISOC)
            qh->load = usb_calc_bus_time(udev->speed,
                    usb_endpoint_dir_in(&hep->desc),
                    qh->type == USB_ENDPOINT_XFER_ISOC,
                    le16_to_cpu(hep->desc.wMaxPacketSize))
                / 1000 + 1;
 
    } else {        /* Skeleton QH */
        qh->state = QH_STATE_ACTIVE;
        qh->type = -1;
    }
    return qh;
}
这个函数在UHCI的start函数中已经看到过,不过在那个地方,它的udev参数是空的.这只是一个框架,并不会用来实际的传输.
而对于实际的传输,也就是代码中注释到的所谓”Normal QH”的情况.
我们在代码中看到.如果不是一个实时传输.它还会创建一个dummy_td,这个TD是用来做什么,我们暂且不要去管,只知道有这么回事就可以了.另外,对于中断传输和实现传输,还要计算传输字节所耗的总线带宽.
返回到uhci_urb_enqueue()中,对于控制传输的情况,流程会转入到uhci_submit_control()中,这个函数的代码如下所示:
 
static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    struct uhci_td *td;
    unsigned long destination, status;
    int maxsze = le16_to_cpu(qh->hep->desc.wMaxPacketSize);
    int len = urb->transfer_buffer_length;
    dma_addr_t data = urb->transfer_dma;
    __le32 *plink;
    struct urb_priv *urbp = urb->hcpriv;
    int skel;
 
    /* The "pipe" thing contains the destination in bits 8--18 */
    //dev_num + end_num
    destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;
 
    /* 3 errors, dummy TD remains inactive */
    //设置TD描述符的Status的C_ERR字段为3.如果失败次数超过了3.则将TD置为inactive
    status = uhci_maxerr(3);
    //如果是低速设备.设置STATUS的LS位,表示是一个低速设备
    if (urb->dev->speed == USB_SPEED_LOW)
        status |= TD_CTRL_LS;
 
    /*
     * Build the TD for the control request setup packet
     */
     //SETUP包
    td = qh->dummy_td;
    //将TD放到urbp的td_list链表
    uhci_add_td_to_urbp(td, urbp);
    //SETUP阶段的数据信息包只有8个字节
    uhci_fill_td(td, status, destination | uhci_explen(8),
            urb->setup_dma);
    plink = &td->link;
    status |= TD_CTRL_ACTIVE;
 
    /*
     * If direction is "send", change the packet ID from SETUP (0x2D)
     * to OUT (0xE1).  Else change it from SETUP to IN (0x69) and
     * set Short Packet Detect (SPD) for all data packets.
     *
     * 0-length transfers always get treated as "send".
     */
     
     //判断是往哪一个方向的
    if (usb_pipeout(urb->pipe) || len == 0)
        destination ^= (USB_PID_SETUP ^ USB_PID_OUT);
    else {
        destination ^= (USB_PID_SETUP ^ USB_PID_IN);
        //如果是IN方向的,必须要置SPD位
        //如果是输入方向且数据包被成功传递,但长度小于最大长度,就会将TD置为inactive
        //如果短包中断被使用,就会上报一个中断
        //SPD是OUT方向是无意义的
        status |= TD_CTRL_SPD;
    }
 
    /*
     * Build the DATA TDs
     */
     //数据传输阶段
    while (len > 0) {
        int pktsze = maxsze;
 
        //是后的一个包,确实是短包了,清除掉SPD
        if (len <= pktsze) {        /* The last data packet */
            pktsze = len;
            status &= ~TD_CTRL_SPD;
        }
 
        td = uhci_alloc_td(uhci);
        if (!td)
            goto nomem;
        *plink = LINK_TO_TD(td);
 
        /* Alternate Data0/1 (start with Data1) */
        //交替TOGGLE位,是用来同步的, usb2.0 spec上有详细说明
        destination ^= TD_TOKEN_TOGGLE;
        //将TD添加到td_list
        uhci_add_td_to_urbp(td, urbp);
        uhci_fill_td(td, status, destination | uhci_explen(pktsze),
                data);
        plink = &td->link;
 
        data += pktsze;
        len -= pktsze;
    }
 
    /*
     * Build the final TD for control status
     */
     //状态阶段
    td = uhci_alloc_td(uhci);
    if (!td)
        goto nomem;
    *plink = LINK_TO_TD(td);
 
    /* Change direction for the status transaction */
    //状态阶段的方向跟前一笔事务是相反的方向
    destination ^= (USB_PID_IN ^ USB_PID_OUT);
    //限定为DATA1
    destination |= TD_TOKEN_TOGGLE;     /* End in Data1 */
 
    uhci_add_td_to_urbp(td, urbp);
    //状态阶段中,只需要传送0长度的数据
    //IOC置位了,即传输完成了就会产生中断
    uhci_fill_td(td, status | TD_CTRL_IOC,
            destination | uhci_explen(0), 0);
    plink = &td->link;
 
    /*
     * Build the new dummy TD and activate the old one
     */
    td = uhci_alloc_td(uhci);
    if (!td)
        goto nomem;
    *plink = LINK_TO_TD(td);
 
    //起个标识作用,表示该TD已经结尾了,这个包是不会参与传输的,因为它没有置ACTIVE标志
    uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);
    wmb();
    //将dummy_td置为ACTIVE
    qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);
    //将dummy_td指向新创建的TD
    qh->dummy_td = td;
 
    /* Low-speed transfers get a different queue, and won't hog the bus.
     * Also, some devices enumerate better without FSBR; the easiest way
     * to do that is to put URBs on the low-speed queue while the device
     * isn't in the CONFIGURED state. */
     //确定QH是属于低速还是高速
    if (urb->dev->speed == USB_SPEED_LOW ||
            urb->dev->state != USB_STATE_CONFIGURED)
        skel = SKEL_LS_CONTROL;
    else {
        skel = SKEL_FS_CONTROL;
        uhci_add_fsbr(uhci, urb);
    }
    //QH的初始状态是为QH_STATE_IDLE
    if (qh->state != QH_STATE_ACTIVE)
        qh->skel = skel;
 
    //实际传输的长度置-8.正好是SETUP的包大小,以后返回的actual_length就是单纯的数据传输长度了
    urb->actual_length = -8;    /* Account for the SETUP packet */
    return 0;
 
nomem:
    /* Remove the dummy TD from the td_list so it doesn't get freed */
    uhci_remove_td_from_urbp(qh->dummy_td);
    return -ENOMEM;
}
理解这个函数需要参照usb2.0 spec.控制传输包含了三个阶段,分别是SETUP,DATA和HANDSHAKE.
Setup阶段的token信息包的PID为SETUP(1101B),数据信息包就是urb->
setup_packet部份,它的物理地址是urb->
setup_dma.为八个字节.
DATA阶段的token信息包的PID为IN/OUT(要根据传输方向来确定),后面跟的是具体的数据的数据信息包.另外,DATA阶段的数据信息包中的PID起同步作用,DATA1/DATA0轮流交换.
HANDSHAKE阶段的信息包的PID方向跟DATA阶段是相反的,数据信息包的PID值固定为DATA1,传输的一个长度为0的数据.
另外,虽然每一笔事务都有token信息包,数据信息包和联络信息包,联络信息包UHCI会自动管理,并不需要驱动进行操作.设备返回的联络信息包会反应到对应TD的STATUS段中.
其实,最后还附带有一个TQ,因为它没有置ACTIVE位,实际上他不会参与传输,只是起一个标识结尾的作用.
还有一个值得注意的地方:如果是全速的控制传输,将skel置为SKEL_FS_CONTROL后,会调用uhci_add_fsbr().这个函数代码如下:
static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb)
{
    struct urb_priv *urbp = urb->hcpriv;
 
    if (!(urb->transfer_flags & URB_NO_FSBR))
        urbp->fsbr = 1;
}
从上面的代码看到,如果URB没有带URB_NO_FSBR的标志,就会将urbp->fsbr置为1.这在后面关于FSBR的分析是会用到的.在这里特别提一下.
请自行对照代码中的注释进行阅读,这里就不进行详细分析了
经过上面的操作之后,所有的TD都链在了urbp->urb_list中.qh->
dummy_td指向它的最后一个TD.
 
返回到uhci_urb_enqueue().流程会转入uhci_activate_qh().这个函数是各种传输所共用的.
代码如下:
static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    //qh->queure为空,也就是说QH没有链接到urbp->node
    WARN_ON(list_empty(&qh->queue));
 
    /* Set the element pointer if it isn't set already.
     * This isn't needed for Isochronous queues, but it doesn't hurt. */
     //QH的初始值是qh->element和qh->link都是UHCI_PTR_TERM
    if (qh_element(qh) == UHCI_PTR_TERM) {
        //从qh导出urbp
        struct urb_priv *urbp = list_entry(qh->queue.next,
                struct urb_priv, node);
        //经过前面的分析,所有td都是挂在td_list上面的
        struct uhci_td *td = list_entry(urbp->td_list.next,
                struct uhci_td, list);
 
        //将TD挂到QH上面
        qh->element = LINK_TO_TD(td);
    }
 
    /* Treat the queue as if it has just advanced */
    qh->wait_expired = 0;
    qh->advance_jiffies = jiffies;
 
    if (qh->state == QH_STATE_ACTIVE)
        return;
    qh->state = QH_STATE_ACTIVE;
 
    /* Move the QH from its old list to the correct spot in the appropriate
     * skeleton's list */
     //刚开始的时候uhci->next_qh为NULL
    if (qh == uhci->next_qh)
        uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,
                node);
    //初始化时qh->node为空
    list_del(&qh->node);
 
    //ISO传输
    if (qh->skel == SKEL_ISO)
        link_iso(uhci, qh);
    //INTER传输
    else if (qh->skel < SKEL_ASYNC)
        link_interrupt(uhci, qh);
    else
        //其它类型的
        link_async(uhci, qh);
}
上面的代码中的注释注明了各项操作.
QH在初始化时,将qh->element和qh->link都设置为了UHCI_PTR_TERM.也就是说它下面没有挂上TD,也没有链接其它的QH.在这个函数中,首先将QH和TD要关联起来,即,把TD连接到QH->element.
对于控制传输,流程最后转入link_async()中,是到QH和UHCI的frame list关联起来的时候了.
代码如下示:
static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    struct uhci_qh *pqh;
    __le32 link_to_new_qh;
 
    /* Find the predecessor QH for our new one and insert it in the list.
     * The list of QHs is expected to be short, so linear search won't
     * take too long. */
     
     //uhci->skel_async_qh:skelqh[9]
     //各钟ASYNC的QH按照从小到大的顺序链接在skelqh[9]->node上
     //注意了,list_for_each_entry_reverse()是一个反向搜索 
list_for_each_entry_reverse(pqh, &uhci->skel_async_qh->node, node) {
        if (pqh->skel <= qh->skel)
            break;
    }
    //在skelqh[9]->node中找到一个合适的位置,将QH插下去
    list_add(&qh->node, &pqh->node);
 
    /* Link it into the schedule */
    //接QH插入到调度队列
    qh->link = pqh->link;
    //这里涉及到物理地址的操作,为了防止编译器优化,插上内存屏障
    wmb();
    link_to_new_qh = LINK_TO_QH(qh);
    pqh->link = link_to_new_qh;
 
    /* If this is now the first FSBR QH, link the terminating skeleton
     * QH to it. */
     //如果是第1个fsbr,将skelqh[10]指向这个QH
     //skel_term_qh: skelqh[10]
    if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
        uhci->skel_term_qh->link = link_to_new_qh;
}
Async类型的传输包括控制传输和批量传输.这样的传输是不会保证实时性的,只有在带宽空闲的时候才会进行.从上面的代码中可以看到,它们的
QH都是挂在skeqh[9].即是挂在int1的QH后面的.根据我们之前对UHCI调度初始化的分析.int1是排在最后的一个QH.所以,挂在
int1后的QH,要等前面的QH运行完之后才能够运行.
而对于Control传输.根据设备的传输速度又有了SKEL_LS_CONTROL和SKEL_FS_CONTROL.对于BULK有22
这几个宏的定义如下:
#define SKEL_LS_CONTROL     20
#define SKEL_FS_CONTROL     21
#define SKEL_FSBR       SKEL_FS_CONTROL
#define SKEL_BULK       22
事实上,我们希望他们传殊的优先级顺序是SKEL_LS_CONTROL > SKEL_FS_CONTROL > SKEL_BULK
在这里,要特别注意上面的搜索是从后面往前面搜索的.不要被它弄迷糊了.
在最后,还将skel_term_qh指向第一个SKEL_FS_CONTROL的QH.在前面UHCI调度初始化中曾分析过.这个skel_term_qh实际上并挂在frame list中.在这里,
skel_term_qh->link指向第一个SKEL_FS_CONTROL是在做什么呢?暂且把这个问题放到这里.我们马上就会涉及到这部份内容了.
 
从uhci_activate_qh()返回之后,流程会进入到uhci_urbp_wants_fsbr().
在分析之个函数之前.先介绍一下什么叫FSBR.FSBR的全名叫:
Full Speed
Bus
Reclamtion.它是一种带宽回收机制.只有在全速传输中才会用到.所谓带宽回收,其实是跟UHCI默认遍历QH的方式有关的.在UHCI
的spec中,定义了两种遍历QH的方式.分别是广度优先搜索和深度优先搜索.深度优先搜索在PCI驱动结构中大量被用到.它类似于二叉树的先根遍历.深
度优先搜索类似于二叉树的层遍历.
具体到UHCI的QH遍历方式上:
深度优先:UHCI先处理完QH下面的所有TD,然后再处理下一个QH.很显然,这样的方式不会有带宽浪费.但对挂在后面的QH来说很不公平.
广度优先:UHCI先取QH下挂的TD(qh->element).先将这个TD处理完.然后再它后面链接的QH(qh->
link).这样的方式对挂在后面的QH都是公平的.但是遍历完所有QH之后.如果时间还有剩余,UHCI什么事都不会干.但很显然,每个QH下的TD并
不是都处理完了.
所以,针对广度优先搜索,出现了带宽回收.具体怎么样回收呢?我们来看uhci_urbp_wants_fsbr()的代码:
static void uhci_urbp_wants_fsbr(struct uhci_hcd *uhci, struct urb_priv *urbp)
{
    if (urbp->fsbr) {
        uhci->fsbr_is_wanted = 1;
        if (!uhci->fsbr_is_on)
            uhci_fsbr_on(uhci);
        else if (uhci->fsbr_expiring) {
            uhci->fsbr_expiring = 0;
            del_timer(&uhci->fsbr_timer);
        }
    }
}
记得在uhci_submit_control()分析的时候,提到过,如果是全速控制传输会调用uhci_add_fsbr()将urbp->fsbr置为1.
初始化的时候,会将uhci->fsbr_is_on设置为0.也就是说,在这个地方,流程会转入到uhci_fsbr_on().代码如下:
static void uhci_fsbr_on(struct uhci_hcd *uhci)
{
    struct uhci_qh *lqh;
 
    /* The terminating skeleton QH always points back to the first
     * FSBR QH.  Make the last async QH point to the terminating
     * skeleton QH. */
    uhci->fsbr_is_on = 1;
    lqh = list_entry(uhci->skel_async_qh->node.prev,
            struct uhci_qh, node);
    lqh->link = LINK_TO_QH(uhci->skel_term_qh);
}
在这个函数中,先将fsbr_is_on设置为1.然后,取得挂在skel_async_qh上的最后一个QH.然后将这个QH的指向skel_term_qh.
结合uhci_activate_qh()中对于skel_term_qh的操作.得出以下的示意图:
 
在uhci_activate_qh()的分析中,我们了解到:按照SKEL_LS_CONTROL >
SKEL_FS_CONTROL >
SKEL_BULK优先级链接在skel_async_qh->node中.所以SKEL_FS_CONTROL后面可能还会跟有
SKEL_BULK类型的传送包.
如上图所示:skel_term_qh起一个中间桥的作用,将SKEL_FS_CONTRO后的QH链接起来.这样,在UHCI有空闲带宽的时候就不会傻呆着无所事事了.它会循环处理SKEL_FS_CONTRO和SKEL_BULK的包.
另外,从上图中也可以看出.只有全速控制传和批量传输会用到FSBR.另外,对于批量传输,它无所谓低速批量传输.因为所有的BULK都会链接在上面的圆环中.
 
既然说到了uhci_fsbr_on().顺便说一下它的对立函数uhci_fsbr_off():
static void uhci_fsbr_off(struct uhci_hcd *uhci)
{
    struct uhci_qh *lqh;
 
    /* Remove the link from the last async QH to the terminating
     * skeleton QH. */
    uhci->fsbr_is_on = 0;
    lqh = list_entry(uhci->skel_async_qh->node.prev,
            struct uhci_qh, node);
    lqh->link = UHCI_PTR_TERM;
}
它将fsbr_is_on置为0.然后断开了上图中所示的圆环.
 
到这里之后,TD能够被UHCI调度了.那TD调度完了之后,驱动要怎样才能够知道呢?
回忆之前,我们在最后的一个有效TD的STATUS中,置位了IOC.也就是说传输完成之后,会上报一个中断.所以,控制传输完成之后是由中断
处理函数进行处理的.事实上,无论哪一种传输方式到传输完成的时候,就会由中断处理函数都行后续的处理工作.所以中断处理这部份放到最后统一进行分析.
上面的分析中涉及到了BULK传输.那就来看一下BULK传输的实现.
 
3.3:批量传输过程
Root hub没有批量传输.按照控制传输的流程.批量传输最终也会交给uhci_urb_enqueue()处理.与前面分析的控制传输不同的是,在switch的判断中,流程会转向uhci_submit_bulk().
static int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    int ret;
 
    /* Can't have low-speed bulk transfers */
    //如果是低速设备,退出.BULK不能在低速设备中使用
    if (urb->dev->speed == USB_SPEED_LOW)
        return -EINVAL;
 
    //qh的初始化state是QH_STATE_IDLE
    if (qh->state != QH_STATE_ACTIVE)
        qh->skel = SKEL_BULK;
    ret = uhci_submit_common(uhci, urb, qh);
 
    //uhci_add_fsbr():判断urb是否支持FSBR
    if (ret == 0)
        uhci_add_fsbr(uhci, urb);
    return ret;
}
首先一个低速设备是不能用做BULK传输的.另外,在初始化qh的时候,将它的状态初始化成QH_STATA_IDLE.代码中再判断一次,是
为了防止操作的QH是已经挂在UHCI调度的QH.然后再调用uhci_submit_common()去填充这次传输所需要的TD.最后同控制传输的情
况一下,也要进行FSBR的判断.
uhci_submit_common()代码如下:
static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    struct uhci_td *td;
    unsigned long destination, status;
    int maxsze = le16_to_cpu(qh->hep->desc.wMaxPacketSize);
    int len = urb->transfer_buffer_length;
    dma_addr_t data = urb->transfer_dma;
    __le32 *plink;
    struct urb_priv *urbp = urb->hcpriv;
    unsigned int toggle;
 
    if (len < 0)
        return -EINVAL;
 
    /* The "pipe" thing contains the destination in bits 8--18 */
    destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
    //取得toggle位
    toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe),
             usb_pipeout(urb->pipe));
 
    /* 3 errors, dummy TD remains inactive */
    //设置uhci spec规定的TD第二个寄存器中的C_ERR位,当错误次数超过3次,就会认为TD无效
    status = uhci_maxerr(3);
    //如果是低速设备,设置STATUS段的LS位,表明这是一个低速传输
    if (urb->dev->speed == USB_SPEED_LOW)
        status |= TD_CTRL_LS;
    //如果是输入管道,设置bit27的SPD
    //SPD只有在IN方向才有效
    if (usb_pipein(urb->pipe))
        status |= TD_CTRL_SPD;
 

抱歉!评论已关闭.