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

也许,有人会有这样的疑问:
对于控制传输,它不也是基于toggle的纠错么,为什么它就不需要修改后续的包的toggle值呢?
这是因为,控制传输的toggle都是从1开始的,删除掉当前的urb,也不会对后面的发包造成影响.
之后,处理完之后,将无用的td删除.
跟踪一下toggle的修正过程.对应的函数为uhci_fixup_toggles().如下所示:
static void uhci_fixup_toggles(struct uhci_qh *qh, int skip_first)
{
    struct urb_priv *urbp = NULL;
    struct uhci_td *td;
    unsigned int toggle = qh->initial_toggle;
    unsigned int pipe;
 
    /* Fixups for a short transfer start with the second URB in the
     * queue (the short URB is the first). */
    if (skip_first)
        urbp = list_entry(qh->queue.next, struct urb_priv, node);
 
    /* When starting with the first URB, if the QH element pointer is
     * still valid then we know the URB's toggles are okay. */
    else if (qh_element(qh) != UHCI_PTR_TERM)
        toggle = 2;
 
    /* Fix up the toggle for the URBs in the queue.  Normally this
     * loop won't run more than once: When an error or short transfer
     * occurs, the queue usually gets emptied. */
    urbp = list_prepare_entry(urbp, &qh->queue, node);
    //从第二个URB开始遍历qh上的URB
    list_for_each_entry_continue(urbp, &qh->queue, node) {
 
        /* If the first TD has the right toggle value, we don't
         * need to change any toggles in this URB */
         //取挂在urb上的第一个TD
        td = list_entry(urbp->td_list.next, struct uhci_td, list);
        //如果下一个传输的URB的起始TD就是损坏包的toggle
        if (toggle > 1 || uhci_toggle(td_token(td)) == toggle) {
            //取此次URB传输的最后一个td
            td = list_entry(urbp->td_list.prev, struct uhci_td,
                    list);
            //最后一个td取反
            toggle = uhci_toggle(td_token(td)) ^ 1;
 
        /* Otherwise all the toggles in the URB have to be switched */
        } else {
            //如果toggle不相符合,则依次给urbp中的td转换toggle
            list_for_each_entry(td, &urbp->td_list, list) {
                td->token ^= __constant_cpu_to_le32(
                            TD_TOKEN_TOGGLE);
                toggle ^= 1;
            }
        }
    }
    //将最后的toggle保存进usb device中
    wmb();
    pipe = list_entry(qh->queue.next, struct urb_priv, node)->urb->pipe;
    usb_settoggle(qh->udev, usb_pipeendpoint(pipe),
            usb_pipeout(pipe), toggle);
    qh->needs_fixup = 0;
}
在调用这个函数之前,有这样的设置:
qh->initial_toggle = uhci_toggle(td_token(qh->post_td)) ^ 1;
即将qh->initial_toggle设置成为了发生错误的TD的toggle值的相反值.也就是继错误TD之后的数据包的toggle值.
由于调用这个函数的第二个参数为1.所以,会从qh的第二个urb开始遍历.如果URB的起始包的toggle与下一个包的toggle相同,则这个URB的toggle值不需要改变,否则,就需要挨个改变URB中的TD的toggle.
最后,还需要将最后的toggle值保存到usb_dev->toggle[]中.因为下次发包的时候,还要从这里面去取相应的toggle值做为当前发包的toggle.
 
第三个要分析的函数是uhci_giveback_urb().代码如下示:
static void uhci_giveback_urb(struct uhci_hcd *uhci, struct uhci_qh *qh,
        struct urb *urb, int status)
__releases(uhci->lock)
__acquires(uhci->lock)
{
    struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
 
    //urb->actual_length为负,说明传输失败
    if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
 
        /* urb->actual_length < 0 means the setup transaction didn't
         * complete successfully.  Either it failed or the URB was
         * unlinked first.  Regardless, don't confuse people with a
         * negative length. */
        urb->actual_length = max(urb->actual_length, 0);
    }
 
    /* When giving back the first URB in an Isochronous queue,
     * reinitialize the QH's iso-related members for the next URB. */
     //如果是实时传输
     //如果要删除的QH是第第一个实时传输的URB,则要修改qh的iso_frame和iso_packet_decket
    else if (qh->type == USB_ENDPOINT_XFER_ISOC &&
            urbp->node.prev == &qh->queue &&
            urbp->node.next != &qh->queue) {
        struct urb *nurb = list_entry(urbp->node.next,
                struct urb_priv, node)->urb;
 
        qh->iso_packet_desc = &nurb->iso_frame_desc[0];
        qh->iso_frame = nurb->start_frame;
    }
 
    /* Take the URB off the QH's queue.  If the queue is now empty,
     * this is a perfect time for a toggle fixup. */
     //将urbp从QH的链表上删除,如果QH是空的,修正toggle值
    list_del_init(&urbp->node);
    if (list_empty(&qh->queue) && qh->needs_fixup) {
        usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
                usb_pipeout(urb->pipe), qh->initial_toggle);
        qh->needs_fixup = 0;
    }
 
    //释放urbp所占的所用空间,包括它的TD
    uhci_free_urb_priv(uhci, urbp);
    //从ep的链表上将urb删除
    usb_hcd_unlink_urb_from_ep(uhci_to_hcd(uhci), urb);
 
    spin_unlock(&uhci->lock);
    usb_hcd_giveback_urb(uhci_to_hcd(uhci), urb, status);
    spin_lock(&uhci->lock);
 
    /* If the queue is now empty, we can unlink the QH and give up its
     * reserved bandwidth. */
     //如果QH为空,将qh断开,将QH占用的带宽释放
    if (list_empty(&qh->queue)) {
        uhci_unlink_qh(uhci, qh);
        if (qh->bandwidth_reserved)
            uhci_release_bandwidth(uhci, qh);
    }
}
在控制传输的过程中,是将urb->actual_length设为-8的,这样是为了跳过前面的SETUP过程的数据包.如果 SETUP阶段发生了错误,那么urb->actual_length将会是一个负值,所以,先要将urb->actual_length修 正为大于或者等于0的数.
如果要删除的URB是实时队列QH的第一个URB.那必须更新qh->iso_packet_desc和qh->iso_frame.使其指向有效的起始位置.
另外,如果QH是空的,然后qh->needs_fixup为1,就会在usb_dev->toggle[]修正一次 toggle.(为什么要这么做?这里先放一下,后面会有分析.)这和我们在上面的分析的uhci_fixup_short_transfer()是不同 的.也不会重复.因为uhci_fixup_short_transfer()处理完了之后,会将qh->needs_fixup值设为0.这个 if判断不会满足.
将uhci_result_common()中关于need_fixup的部份列出如下:
static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb)
{
        ......
        ......
if (ret < 0) {
        /* Note that the queue has stopped and save
         * the next toggle value */
        qh->element = UHCI_PTR_TERM;
        qh->is_stopped = 1;
        qh->needs_fixup = (qh->type != USB_ENDPOINT_XFER_CONTROL);
        qh->initial_toggle = uhci_toggle(td_token(td)) ^
                (ret == -EREMOTEIO);
 
    } else      /* Short packet received */
        ret = uhci_fixup_short_transfer(uhci, qh, urbp);
    return ret;
}
其中,ret<0是表示传输错误的情况,而ret=1是表示短包错误的情况.而这两种情况有什么区别呢?
对于短包错误,接收是正常的,这时,接收方会回一个ACK.然后再”倒转”自己这边验证的toggle.因为接收方的验证toggle改变了,所以,错误包之后的所有包,都要符号它的验证条件.
而对于传输错误的包,接收方接收错误,例如CRC检测错误,这里给对方回一个ACK之后,不会更新本地验证的toggle值.因此,这种情况下 的后续包没有必要更改toggle值.但是对于-EREMOTEIO就不同了,这种情况是发生在设置了URB_SHORT_NOT_OK标志的情况下,这 种情况下,接收方的接包是正常,所以也是需要”倒转”toggle.
说到这里,可能有人又会产生一个疑问,对于短包错误的,会修正它后面URB的toggle值,对于传输错误的,就不需要了么?
不着急,在后面自然会看到.
返回到uhci_giveback_urb()中,继续断开URB的一些关联以及释放和它相关结构所占的空间,另外,还会在usb_hcd_giveback_urb()中调用urb->  complete()来唤醒等待URB传输完成的进程.特别注意到,如果QH中没有URB了,就需要将QH的带宽回收了. (!!!要等到QH为空再释放带宽么?为什么不是释放URB就释放它所占的带宽?)
其实,跟踪代码可以发现,QH只会在添加第一个URB的时候,才会计算保留带宽,也就是说,不管QH中添加了多少URB,它的所占带宽都是一样的.
对于中断传输来说,这一点很好理解,QH下面挂着TD,每调度一次QH只会调度一个TD,因此,就算QH下挂了再多的TD,也不会影响带宽.
而对于等时传输来说,它的TD是直接挂在UHCI的调度数组上,每个TD相连之后再联QH.因为后面添加实时TD不会计算带宽.这样,后面就算提交了再多的等时传输也是不会去的判断(等时+中断<90%),这样做是不是欠妥?
 
 
现在,就来解释一下上面提出的问题,即对于传输错误的toggle修正问题.
我们在前面看到,对于传输错误的URB,会将它所属的qh设为:
qh->element = UHCI_PTR_TERM;
        qh->is_stopped = 1;
        qh->needs_fixup = (qh->type != USB_ENDPOINT_XFER_CONTROL);
        qh->initial_toggle = uhci_toggle(td_token(td)) ^
                (ret == -EREMOTEIO);
 
其中,如果是控制传输的话,是不需要修正toggle的.在这里,要特别注意,将qh的element设为了UHCI_PTR_TERM.也就是说,这个QH是一个空的,它下面没有挂任何的TD.那其它的urb是怎么继续得到调度的呢?
在uhci_scan_qh()中,有这样一段代码:
static void uhci_scan_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
        ......
        ......
if (!list_empty(&qh->queue)) {
        if (qh->needs_fixup)
            uhci_fixup_toggles(qh, 0);
 
        /* If the first URB on the queue wants FSBR but its time
         * limit has expired, set the next TD to interrupt on
         * completion before reactivating the QH. */
        urbp = list_entry(qh->queue.next, struct urb_priv, node);
        if (urbp->fsbr && qh->wait_expired) {
            struct uhci_td *td = list_entry(urbp->td_list.next,
                    struct uhci_td, list);
 
            td->status |= __cpu_to_le32(TD_CTRL_IOC);
        }
 
        uhci_activate_qh(uhci, qh);
    }
......
}
如上代码所示,传输超时和URB传输错误的QH都会由它进行处理.如果qh->needs_fixup为了,调用uhci_fixup_toggles()修正它的toggle值.
如果是一个需要FSBR的URB,但又传输超时,设定下一个TD带IOC属性,这样,在下一次中断的时候,就又会启用FSBR了.
Uhci_activate_qh()已经很熟悉了,我们在之前已经分析过.
就这样,QH又会被调度起来了.
不妨思考一下,对于传输超时的QH,为什么要先将它加到skel_unlink_qh.然后再重新加到调度队列呢?为什么对于传输错误的QH,要先将qh-> element设为UHCI_PTR_TERM.然后再加入调度队列呢?
对于传输超时,它先链接在skel_unlink_qh.那,必须要等到下个frame中断的的时候,才会将qh加回调度队列.调用 Uhci_activate_qh()将其加回调度队列的时候,是加到调度队列的末尾.( 为什么是下一个frame呢?注意代码中的QH_FINISHED_UNLINKING()操作)
对于传输错误的QH,它能在本次中断加回调度队列,但也是加到调度队列的末尾.(因为传输错误的时候,在uhci_result_common()会将is_stopped设为1)
由此可见:
Linux采用,是一种缓时调度的机制,将传输错误或者是传输超时的QH,放到调度队列的末尾,显然,这样的机制对实时传输是不合适的,因此,在代码中对实现传输做了特殊处理.
 
虽然,在这里的修正必须要满足QH不为空的情况,当QH为空的情况,它的修正就是在上面分析的uhci_giveback_urb()中完成的.
 
第四个要分析的函数uhci_cleanup_queue().
static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh,
        struct urb *urb)
{
    struct urb_priv *urbp = urb->hcpriv;
    struct uhci_td *td;
    int ret = 1;
 
    /* Isochronous pipes don't use toggles and their TD link pointers
     * get adjusted during uhci_urb_dequeue().  But since their queues
     * cannot truly be stopped, we have to watch out for dequeues
     * occurring after the nominal unlink frame. */
     //必须要等它调度完了才能删除
    if (qh->type == USB_ENDPOINT_XFER_ISOC) {
        ret = (uhci->frame_number + uhci->is_stopped !=
                qh->unlink_frame);
        goto done;
    }
 
    /* If the URB isn't first on its queue, adjust the link pointer
     * of the last TD in the previous URB.  The toggle doesn't need
     * to be saved since this URB can't be executing yet. */
     //如果不是QH中的第一个urb
    if (qh->queue.next != &urbp->node) {
        struct urb_priv *purbp;
        struct uhci_td *ptd;
 
        //urb的前一个urb
        purbp = list_entry(urbp->node.prev, struct urb_priv, node);
        WARN_ON(list_empty(&purbp->td_list));
        //URB的前面urb中的最后一个TD
        ptd = list_entry(purbp->td_list.prev, struct uhci_td,
                list);
        //URB的最后一个TD
        td = list_entry(urbp->td_list.prev, struct uhci_td,
                list);
        //跳过urb的td项
        ptd->link = td->link;
        goto done;
    }
 
    //后面的处理,对应要删除的urb是qh上的第一个urb
    //因此不要管它的链接情况,不过要更新usb_dev的toggle
    /* If the QH element pointer is UHCI_PTR_TERM then then currently
     * executing URB has already been unlinked, so this one isn't it. */
     //如果qh->element等于UHCI_PTR_TERM.说明QH下面没有链接TD了
    if (qh_element(qh) == UHCI_PTR_TERM)
        goto done;
    qh->element = UHCI_PTR_TERM;
 
    /* Control pipes don't have to worry about toggles */
    //如果是控制传输,不需要更新toggle
    if (qh->type == USB_ENDPOINT_XFER_CONTROL)
        goto done;
 
    /* Save the next toggle value */
    //initial_toggle更新为qh中起始td的toggle值.
    //因为是要删除qh中的td.因此,qh之后的td因为要以这个toggle为准值
    WARN_ON(list_empty(&urbp->td_list));
    td = list_entry(urbp->td_list.next, struct uhci_td, list);
    qh->needs_fixup = 1;
    qh->initial_toggle = uhci_toggle(td_token(td));
 
done:
    return ret;
}
这个函数比较简单,对照添加的注释自行阅读即可.
不过要注意,这个函数也涉及到了toggle修正.在后面也会经过上面分析的toggle修正的流程(传输错误的QH的toggle修正部份).
疑问:为什么不是QH中的第一个URB就不需要修正toggle?
 
最后要分析的函数是uhci_make_qh_idle().
static void uhci_make_qh_idle(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    //如果QH依然是ACTIVE状态,非法...
    WARN_ON(qh->state == QH_STATE_ACTIVE);
    //如果要删除的QH是uhci->next_qh.则更新uhci->next_qh
    if (qh == uhci->next_qh)
        uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,
                node);
    //将QH移到uhci->idle_qh_list链表上
    list_move(&qh->node, &uhci->idle_qh_list);
    //将QH的状态更改为IDLE
    qh->state = QH_STATE_IDLE;
 
    /* Now that the QH is idle, its post_td isn't being used */
    //现在这个QH已经没有什么用处了,如果还有一个缓冲的TD,要将其释放
    if (qh->post_td) {
        uhci_free_td(uhci, qh->post_td);
        qh->post_td = NULL;
    }
 
    /* If anyone is waiting for a QH to become idle, wake them up */
    //如果有进程在进程QH,将他们唤醒...
    if (uhci->num_waiting)
        wake_up_all(&uhci->waitqh);
}
当QH空闲,QH也就可以完全释放了.有人或许有疑问,这个函数处理过后,QH只是回到了初始状态,并没有将QH所占空间释放.那他是在什么时候释放的呢?
首先,考虑一下,每个端点的传输类型都是相同的,因此对应每个端点,它的QH也是同一种类型,因此,以后的数据传输就会复用这个QH(参考usb2.0 spec上的端口描述符的bmAttribtes字段).
那QH要等到usb_hcd_disable_endpoint()的时候才会将其删除.
经过这样一个漫长的过程,UHCI的中断处理终于到此结束了.
 
五:关于复用的QH
在上面提到了第一次传输后的QH会被以后的传输复用,这部份的代码在前面的情景中都没有涉及到,现在把它串起来研究一下.
首先在uhci_urb_enqueue()中,有如下代码片段 :
static int uhci_urb_enqueue(struct usb_hcd *hcd,
        struct urb *urb, gfp_t mem_flags)
{
    ......
    ......
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;
    list_add_tail(&urbp->node, &qh->queue);
    if (qh->queue.next == &urbp->node && !qh->is_stopped) {
        uhci_activate_qh(uhci, qh);
        uhci_urbp_wants_fsbr(uhci, urbp);
    }
    goto done;
    ......
    ......
}
首先来看代码片段中的第一段 :
上一次数据传输之后,并没有将QH释放,相应的,QH仍然放置在ep->hcpriv,即传输端点的hcprive字段.
因此代码片段中的if是会满足的,也就是说会找到上次传输的QH,而不是新建一个.强调一句,还有一种情况是,上次提交的urb还没处理完成,相应ep上又提交了一个urb.这里也会找到这个相同的QH
其次,在代码片段的第二段:
它将urbp链接在qh->queue中,然后,判断urbp是是否是qh中的第一个元素,如果是,才会满足if判断,继而将QH激活.这是因为,如果urbp不是它的第一个元素的话,QH已经处理调度状态了,不需要再次激活.
有了一个大概的了解之后,分别来看一下各种操作的QH复用.
5.1:中断传输的QH复用
在uhci_submit_control()中,有如下代码片段:
static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    ......
    ......
td = qh->dummy_td;
    uhci_add_td_to_urbp(td, urbp);
    uhci_fill_td(td, status, destination | uhci_explen(8),
            urb->setup_dma);
    plink = &td->link;
    status |= TD_CTRL_ACTIVE;
    ......
}
经过前面的分析可以得到,qh->dummy_td其实就是存放链接在QH上的最后一个TD,这样,urbp的对应TD直接从dummy_td开始,相应的,这些TD链接在QH的调度链表上.
 
5.2:批量传输的QH复用
在uhci_submit_bulk()àuhci_submit_common()中,有如下代码片段:
static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    ......
    ......
td = qh->dummy_td;
    do {    /* Allow zero length packets */
        int pktsze = maxsze;
 
        if (len <= pktsze) {        /* The last packet */
            pktsze = len;
            if (!(urb->transfer_flags & URB_SHORT_NOT_OK))
                status &= ~TD_CTRL_SPD;
        }
 
        if (plink) {
            td = uhci_alloc_td(uhci);
            if (!td)
                goto nomem;
            *plink = LINK_TO_TD(td);
        }
        uhci_add_td_to_urbp(td, urbp);
        uhci_fill_td(td, status,
                destination | uhci_explen(pktsze) |
                    (toggle << TD_TOKEN_TOGGLE_SHIFT),
                data);
        plink = &td->link;
        status |= TD_CTRL_ACTIVE;
 
        data += pktsze;
        len -= maxsze;
        toggle ^= 1;
    } while (len > 0);
......
......
}
同上面分析的中断传输情况类型,urbp的TD也是从qh->dummy_td开始存放的,相应的,也是位于QH的调度链表中.
 
5.3:中断传输的QH复用
在uhci_submit_interrupt()中,有以下代码片段:
static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    ......
    ......
if (!qh->bandwidth_reserved) {
        int exponent;
 
     for (exponent = 7; exponent >= 0; --exponent) {
            if ((1 << exponent) <= urb->interval)
                break;
        }
        if (exponent < 0)
            return -EINVAL;
        qh->period = 1 << exponent;
        qh->skel = SKEL_INDEX(exponent);
        qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
        ret = uhci_check_bandwidth(uhci, qh);
        if (ret)
            return ret;
    } else if (qh->period > urb->interval)
        return -EINVAL;     /* Can't decrease the period */
    ret = uhci_submit_common(uhci, urb, qh);
    ......
    ......
}
对于QH复用的情况,前面的if判断中,
1:如果在QH中还有传输待传输的urb的时候是不会满足的,因为前面的urb已经分配了带宽,会将qh->bandwidth_reserved置为1.
2:如果QH中没有要传输的urb,也就是说QH已经空了,这个if判断还是会满足,因为会在uhci_giveback_urb()将它所占的带宽释放,重置qh->bandwidth_reserved为0.
 
对于第1种情况,它会接着去判断qh->period > urb->interva是否满足,如果满足此条件,就会直接退出.也就是说,如果调度间隔要小于urb指定的间隔,这是允许的,那如果调度的间隔 要大于urb指定的间隔,就是非法了.这在usb2.0 spec上有详细的描述.然后,流程转入uhci_submit_common().
对于第2种情况,还是去判断带宽是否满足,然后分得带宽,最后流程转入uhci_submit_common().
 
uhci_submit_common()这个函数在QH复用时的差别已经在上面分析了,这里不做赘述.
这里再强调一次,在QH中已经有待调度的urb的情况下,是不会再去计算占用带宽的.
 
5.4:实时传输的QH复用
在uhci_submit_isochronous()中,有以下代码片段 :
static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    ......
    ......
    if (!qh->bandwidth_reserved) {
        ......
    }  else if (qh->period != urb->interval) {
        return -EINVAL;     /* Can't change the period */
 
    } else {
        /* Find the next unused frame */
        if (list_empty(&qh->queue)) {
            frame = qh->iso_frame;
        } else {
            struct urb *lurb;
 
            lurb = list_entry(qh->queue.prev,
                    struct urb_priv, node)->urb;
            frame = lurb->start_frame +
                    lurb->number_of_packets *
                    lurb->interval;
        }
        if (urb->transfer_flags & URB_ISO_ASAP) {
            /* Skip some frames if necessary to insure
             * the start frame is in the future.
             */
            uhci_get_current_frame_number(uhci);
            if (uhci_frame_before_eq(frame, uhci->frame_number)) {
                frame = uhci->frame_number + 1;
                frame += ((qh->phase - frame) &
                    (qh->period - 1));
            }
        }   /* Otherwise pick up where the last URB leaves off */
        urb->start_frame = frame;
    }
    ......
    ......
}
在这里,跟上面分析的中断传输的情况类似,也有两种情况.一种是QH为空时,重新计算带宽.另外一种是QH中有待调度的urbp,这时不要计算带宽,流程会经过后面的elseif ...else中.
等时传输比控制传输要严格多了,必须要调度间隔完全相同才可以.
重要的操作就是在后面的这个else中了.
如果QH是空的,基准frame为下次会调度的frame值(qh->iso_frame的值的改变在 uhci_result_isochronous()中已经分析过了,当这个函数运行完了之后,qh->iso_frame表示下次将要调用qh的 帧号),如果QH不是空的,那么,基准frame值就是紧接在QH中最后的TD的下一个调度位置(注意 list_entry(qh->queue.prev,struct urb_priv, node)->urb取得的是挂在QH上的最后一个QH).
计算出这个基准frame之后,如果没有带URB_ISO_ASAP标志,那么该urb的起点调度帧号就是上面计算出来的基准frame.
如果带了URB_ISO_ASAP.表示要尽快调度这个URB.此时就会取当前调度帧+1后的第一个周期点.当前调度帧+1是为了给现在操作足够的时间来完成.
 
六:小结
在这一节里,对UHCI驱动和USB数据传输做了一个全面的分析.代码很复杂,不过,在阅读代码围绕着UHCI调度架构这一条主线,各种操作就会变得很明朗.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值