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

来源:互联网 发布:广数圆弧螺纹编程实例 编辑:程序博客网 时间:2024/05/16 18:33
uhci_scan_schedule()是这个函数的核心操作.也是经常出现的一个函数.代码如下:
static void uhci_scan_schedule(struct uhci_hcd *uhci)
{
   int i;
    struct uhci_qh *qh;
 
    /* Don't allow re-entrant calls */
    //如果正在进行scan处理.设置need_rescan之后退出
    if (uhci->scan_in_progress) {
        uhci->need_rescan = 1;
        return;
    }
    //设置scan_in_progess.防止被其它进程打扰
    uhci->scan_in_progress = 1;
rescan:
    uhci->need_rescan = 0;
    uhci->fsbr_is_wanted = 0;
 
    //该TD所在的FRAME结束之后, 不要发送中断
    uhci_clear_next_interrupt(uhci);
    //得到当前的frame号
    uhci_get_current_frame_number(uhci);
    uhci->cur_iso_frame = uhci->frame_number;
 
    /* Go through all the QH queues and process the URBs in each one */
    //扫描所有的QH
    for (i = 0; i < UHCI_NUM_SKELQH - 1; ++i) {
        //Normal QH是链接在SKEL QH的node链表中
        uhci->next_qh = list_entry(uhci->skelqh[i]->node.next,
                struct uhci_qh, node);
        //遍历skelqh下的QH
        while ((qh = uhci->next_qh) != uhci->skelqh[i]) {
            uhci->next_qh = list_entry(qh->node.next,
                    struct uhci_qh, node);
 
            if (uhci_advance_check(uhci, qh)) {
                //uhci_advance_check返回1.则会转入此外,即表示有传输完成的TD
                uhci_scan_qh(uhci, qh);
                if (qh->state == QH_STATE_ACTIVE) {
                    uhci_urbp_wants_fsbr(uhci,
    list_entry(qh->queue.next, struct urb_priv, node));
                }
            }
        }
    }
 
    //last_iso_frame:最近的帧号
    uhci->last_iso_frame = uhci->cur_iso_frame;
    //需要再次扫描
    if (uhci->need_rescan)
        goto rescan;
    //将其置为0,表示扫描完成了
    uhci->scan_in_progress = 0;
 
    if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted &&
            !uhci->fsbr_expiring) {
        uhci->fsbr_expiring = 1;
        mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY);
    }
 
    //如果skelqh[0]为空,清除term_td的IOC标志位
    if (list_empty(&uhci->skel_unlink_qh->node))
        uhci_clear_next_interrupt(uhci);
    else
        //允许frame 完了之后产生中断
        uhci_set_next_interrupt(uhci);
}
这段代码使用了标志位的方来来防止它在同一时刻被多次运行.在第一次进去的时候,将scan_in_progress设为1.此后如果有进程再 进来,判断scan_in_progress为1后,将need_rescan设为1就退出来.每次执行完这个函数的时候,会检查need_rescan 是否为1,如果是1的话,再去扫描一次.
我们来关注一下代码中的,关于FSBR的操作.代码片段如下:
if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted &&
            !uhci->fsbr_expiring) {
        uhci->fsbr_expiring = 1;
        mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY);
    }
如果if判断条件满足,就将会fsbr_expiring设为1,然后重置定时器,定时器到期之后,会调用定时器处理函数 uhci_fsbr_timeout().在定时器处理函数中,判断fsbr_expiring的值.如果大于1,就会调用 uhci_fsbr_off().将skel_term_qh构成的环断开,也就是说断开了FSBR.
那什么时候才会满足这个if条件呢?
条件1: uhci->fsbr_is_on为1.之前分析过,在启用FSBR的时候,也就是在uhci_fsbr_on()的函数中,会将uhci->fsbr_is_on设为1. 如果该值为0.说明当前并没有使用FSBR.也就不需要断开FSBR了.
条件2:uhci->fsbr_is_wanted为0.这个条件一般是会满足的,因为在刚进入uhci_scan_schedule()的时候,就将它设为了0.除非,在那几个for循环中,再次调用了uhci_urbp_wants_fsbr().
条件3: uhci->fsbr_expiring为0.除了特殊设置, uhci_fsbr_on()处理之后,这个字段是为0的.
我们注意到,重置定时器的到期时间为FSBR_OFF_DELAY之后,内核开发者认为,在FSBR_OFF_DELAY的时间内,完全可以完成URB的传输了.
那如果,在超时定时器运行期间,又有URB开启了FSBR会怎么呢?
在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);
        }
    }
}
在这种情况下,就会进入else if的处理流程,就将这个定时器删除了.
下次启动定时器,又会在中断处理中,又将超时设为了FSBR_OFF_DELAY.*^_^*.这样,每启动一次FSBR,就会保证它至少在FSBR_OFF_DELAY才会将FSBR关闭,让它有足够的时间完成URB的传输.
 
这个函数的核心操作就是在这个for循环中了.在for循环中,它依次遍历uhci->skelqh[0]~skelqh[9]上的QH.然后对每一个QH的处理如下:
    1:调用uhci_advance_check()来判断每一个QH.关于uhci_advance_check()函数,先看它的注释:
 Check for queues that have made some forward progress. Returns 0 if the queue is not Isochronous, is ACTIVE, and has not advanced since last examined; 1 otherwise.
注释中说道,如果不是实时传输,或者QH是ACTIVE状态而且QH自从上次检查点开始就没有被调度过就会返回0.其它的情况就会返回1.
2:如果uhci_advance_check()返回1,就会调用uhci_scan_qh()等后续处理.另外,如果当前QH是 Active的,表明QH还没有调度完.就会再次调用uhci_urbp_wants_fsbr().这样也阻止了后面fsbr_timer定时器的启 动.
 
挨个分析上面的函数:
Uhci_advance_check()的代码如下示:
static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    struct urb_priv *urbp = NULL;
    struct uhci_td *td;
    int ret = 1;
    unsigned status;
    //如果是实时传输,就马上返回1
    if (qh->type == USB_ENDPOINT_XFER_ISOC)
        goto done;
 
     //如果QH不为Active
    if (qh->state != QH_STATE_ACTIVE) {
        urbp = NULL;
        status = 0;
 
    } else {
        //如果QH状态为Active.
        //则取挂在下面的urbp.再到urbp下面找到要传输的td_list
        urbp = list_entry(qh->queue.next, struct urb_priv, node);
        td = list_entry(urbp->td_list.next, struct uhci_td, list);
        //如果QH前进了,那么最先改变状态的是1个TD
        status = td_status(td);
        //如果QH已经前进了,返回1
        if (!(status & TD_CTRL_ACTIVE)) {
 
            /* We're okay, the queue has advanced */
            //置QH的wait_expired为0.advance_jiffies为前进时间戳.
            qh->wait_expired = 0;
            qh->advance_jiffies = jiffies;
            goto done;
        }
        ret = 0;
    }
 
    //下面就是对应QH没有前进,或者是QH不为Active的情况,另外QH不是ISO
    /* The queue hasn't advanced; check for timeout */
    if (qh->wait_expired)
        goto done;
    //如果QH超过QH_WAIT_TIMEOUT没有前进过了
    if (time_after(jiffies, qh->advance_jiffies + QH_WAIT_TIMEOUT)) {
 
        /* Detect the Intel bug and work around it */
        //修正intel的BUG
        if (qh->post_td && qh_element(qh) == LINK_TO_TD(qh->post_td)) {
            qh->element = qh->post_td->link;
            qh->advance_jiffies = jiffies;
            ret = 1;
            goto done;
        }
 
        //将qh->wait_expired置为1.表示该QH已经延迟
        qh->wait_expired = 1;
 
         //如果urbp使用FSBR,且QH中的第一个TD没有IOC标志,即不是最后要传输的TD
        if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC))
            //将QH放到skel_unlink_qh
            uhci_unlink_qh(uhci, qh);
 
    } else {
        /* Unmoving but not-yet-expired queues keep FSBR alive */
        //如果还没有到延时到期. 重启FSBR
        if (urbp)
            uhci_urbp_wants_fsbr(uhci, urbp);
    }
 
done:
    return ret;
}
如果是实时传输,马上返回1,对于不是QH_STATE_ACTIVE的QH,就要检查这个QH是否被调度到了.从前面的分析我们可以看 到,urbp链接在qh->queue上,而要传输的td又是链接在urbp->td_list上.对于所有链接在td_list上的td, 第一个TD肯定是被第一个调度的,如果第一个TD的状态变为了INACTIVE.说明这个QH已经被调度到了.立即返回1.从而退出这个函数.
而对于那些依然没有被调度到的QH.kernel设定了一个最大时间.如果在这个时间内,QH依然还没有被调度到,则认为这个QH可能发生了问题.
在前面的分析中,各种传输的提交都要经过uhci_activate_qh().在这个函数里,将qh->advance_jiffies 设置为了提交urb的时间.而在每一次检测到qh被调度的时候,又会更新advance_jiffies值.因此,只要在检测到末调度的时候,比较当前时 间和advance_jiffies的时间戳就可以计算出,有多长时间没有得到过调度了.
我们跟进看一下,如果超时没有得到调度,会怎么处理.
将相关部份代码列出:
static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
        ......
        ......
    if (time_after(jiffies, qh->advance_jiffies + QH_WAIT_TIMEOUT)) {
 
        /* Detect the Intel bug and work around it */
        //修正intel的BUG
        if (qh->post_td && qh_element(qh) == LINK_TO_TD(qh->post_td)) {
            qh->element = qh->post_td->link;
            qh->advance_jiffies = jiffies;
            ret = 1;
            goto done;
        }
 
        //将qh->wait_expired置为1.表示该QH已经延迟
        qh->wait_expired = 1;
 
         //如果urbp使用FSBR,且QH中的第一个TD没有IOC标志,即不是最后要传输的TD
        if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC))
            //将QH放到skel_unlink_qh
            uhci_unlink_qh(uhci, qh);
 
    }
......
......
}
这段代码中的第一个if语句是为了修正intel的一个BUG.作者在注释中扫描过这个bug:
 Early Intel controllers have a bug which causes qh->element sometimes not to advance when a TD completes successfully.  The queue remains stuck on the inactive completed TD.  We detect such cases and advance the element pointer by hand.
大意是说,在intel早期的控制器中,TD传输完成之后,它的QH->element项不会自动更新.
正常的处理是这样的:如果TD传输完成,就会将其从QH的链上删除,QH->element会指向下一个QH.
对应到代码中.qh->post_td是QH中上一次调度完成的TD(我们在后面可以看到),如果这个调度完成的TD依然挂在QH->element上面,那就说明它没有自动更新过来,需要手动把TD摘除.
 
然后,将qh->wait_expired设为1.表示该QH已经延迟了.
最后,如果urbp使用了FSBR.然且,QH中的第一个TD不是最后的TD.则会调用uhci_unlink_qh().代码如下:
static void uhci_unlink_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    //如果QH已经是unlink了
    if (qh->state == QH_STATE_UNLINKING)
        return;
    //有效性判断
    WARN_ON(qh->state != QH_STATE_ACTIVE || !qh->udev);
    //将状态置为UNLINKING
    qh->state = QH_STATE_UNLINKING;
 
    /* Unlink the QH from the schedule and record when we did it */
    //如果是实时传输
    if (qh->skel == SKEL_ISO)
        ;
    //unlink中断队列
    else if (qh->skel < SKEL_ASYNC)
        unlink_interrupt(uhci, qh);
    //unlink控制队列或者bulk队列
    else
        unlink_async(uhci, qh);
 
    //得到当前调度帧号
    uhci_get_current_frame_number(uhci);
    //unlink时候的QH 的帧号
    qh->unlink_frame = uhci->frame_number;
 
    /* Force an interrupt so we know when the QH is fully unlinked */
    //如果unlink_qh是空的
    if (list_empty(&uhci->skel_unlink_qh->node))
        uhci_set_next_interrupt(uhci);
 
    /* Move the QH from its old list to the end of the unlinking list */
    //更新uhci->next_qh
    if (qh == uhci->next_qh)
        uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,
                node);
    //将qh加到unlink_qh
    list_move_tail(&qh->node, &uhci->skel_unlink_qh->node);
}
逻辑很简单,就是将QH链接到skel_unlink_qh中.来看下中断传输和控制/批量传输的unlink过程.
对于中断传输,调用unlink_interrupt().代码如下:
static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    struct uhci_qh *pqh;
 
    pqh = list_entry(qh->node.prev, struct uhci_qh, node);
    pqh->link = qh->link;
    mb();
}
很简单,就是将QH从对应的调度队列中删除了.
对于控制/批量传输,调用unlink_async():
static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    struct uhci_qh *pqh;
    __le32 link_to_next_qh = qh->link;
 
    pqh = list_entry(qh->node.prev, struct uhci_qh, node);
    pqh->link = link_to_next_qh;
 
    /* If this was the old first FSBR QH, link the terminating skeleton
     * QH to the next (new first FSBR) QH. */
     //如果要删除的是第一个FSBR指向的QH,也要更新skel_term_qh的指向
    if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
        uhci->skel_term_qh->link = link_to_next_qh;
    mb();
}
以中断传输不相同的是,如果要删除的QH,是第一个使用FSBR的QH,还要更新skel_term_qh的值.
 
到这里这后,我们知道这些队列得不到调度了,因为已经从调度系统中删除了,它们都保存在skel_unlink_qh中.
在这里再次强调一下,在uhci_advance_check()中,只有使用了FSBR的QH才会被移到skel_unlink_qh中.也就是说,只有控制传输和批量传输才会做这样的处理.
 
uhci_advance_check()返回之后,如果返回值是1,则会进入到uhci_scan_qh().返回1有三种情况:一种是QH 被调度过了.这种情况下,就要释放掉已经被调度过了的TD.另外的一种情况是QH不为ACTIVE状态.最后的一种是QH为实时传输.我们跟踪看下它的处 理.
这个函数会涉及到urb->unlinked成员的判断,整个USB的代码中,只有usb_kill_urb()或者是usb_unlink_urb()才会更改rub->unlinked.这两个函数是用来销毁URB的.两个函数的代码如下所示:
int usb_unlink_urb(struct urb *urb)
{
    if (!urb)
        return -EINVAL;
    if (!urb->dev)
        return -ENODEV;
    if (!urb->ep)
        return -EIDRM;
    return usb_hcd_unlink_urb(urb, -ECONNRESET);
}
void usb_kill_urb(struct urb *urb)
{
    static DEFINE_MUTEX(reject_mutex);
 
    might_sleep();
    if (!(urb && urb->dev && urb->ep))
        return;
    mutex_lock(&reject_mutex);
    ++urb->reject;
    mutex_unlock(&reject_mutex);
 
    usb_hcd_unlink_urb(urb, -ENOENT);
    wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0);
 
    mutex_lock(&reject_mutex);
    --urb->reject;
    mutex_unlock(&reject_mutex);
}
可以看到,两个函数都会调用usb_hcd_unlink_urb().只是参数不相同.
在usb_hcd_unlink_urb()àunlink1()àusb_hcd_check_unlink_urb()里,最终会更新 urb->unlinked.最后会调用uhci_unlink_qh()从调度队列中删除.这些流程都很简单,在这里不做详细分析.可以自行参阅 代码.
从上面的流程分析:
usb_unlink_urb()最终会将urb->unlinked设为-ECONNRESET.
usb_kill_urb()最终会将urb->unlinked设为-ENOENT.
好了,现在可以跟踪进uhci_scan_qh()的代码了:
static void uhci_scan_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    struct urb_priv *urbp;
    struct urb *urb;
    int status;
 
    //取挂在qh->queue下的urbp
    while (!list_empty(&qh->queue)) {
        urbp = list_entry(qh->queue.next, struct urb_priv, node);
        urb = urbp->urb;
 
        //实时传输的传理
        if (qh->type == USB_ENDPOINT_XFER_ISOC)
            status = uhci_result_isochronous(uhci, urb);
        else
            //其它类型的处理
            status = uhci_result_common(uhci, urb);
        //如果urb没有传输完成,就会返回-EINPROGRESS
        if (status == -EINPROGRESS)
            break;
 
遍历挂在qh上的所有urb.如果urb全部正常的传输完了,uhci_result_ isochronous()和uhci_result_common()会返回0.否则,便返回-EINPROGRESS.说明这个urb中还有末传送完的TD.
 
 
        //到这里的话,QH中的TD应该全被都传输完了
        /* Dequeued but completed URBs can't be given back unless
         * the QH is stopped or has finished unlinking. */
        if (urb->unlinked) {
            if (QH_FINISHED_UNLINKING(qh))
                qh->is_stopped = 1;
            else if (!qh->is_stopped)
                return;
        }
 
        uhci_giveback_urb(uhci, qh, urb, status);
        if (status < 0)
            break;
如果运行到这个地方的话,说明URB已经全部都传输完成了.
1:如果urb->unlinked不为0.说明这个URB已经在其它的地方调用了usb_unlink_urb()或者是 usb_kill_urb(). 这几个函数都会调用uhci_unlink_qh()将qh移放到skel_unlink_qh中,同时也会将qh->unlink_frame设 置为移除时的帧号.因为,如果遇上这种情况的话,肯定是在遍历slel_unlink_qh上的QH.在这里出现了一个比较有意思的宏: QH_FINISHED_UNLINKING().定义如下所示:
            #define QH_FINISHED_UNLINKING(qh)           \
        (qh->state == QH_STATE_UNLINKING && \
        uhci->frame_number + uhci->is_stopped != qh->unlink_frame)
这个宏的第一个条件QH必须是已经移除的.另外,uhci->is_stopped表示uhci的状态,为0是UHCI正常运行,为1说明UHCI已经停止了.
而我们在前面分析uhci_unlink_qh()可得知.qh->unlink_frame表示当时删除QH时的帧号.而uhci->frame_number每次经过中断的时候都会更新一次.因此,第二个条件如果要满足的话,可能要以下两种情况:
1): UHCI已经停止.(uhci->is_stopped为1,而uhci->frame_number在同一个周期内只会往大的方增长)
2):删除时候的帧不是当前运行的帧.这是因为,在当前调度的帧上,不能将它上面的QH删除,必须要等它调度完了之后才可以.
在QH_FINISHED_UNLINKING()不满足且qh->is_stopped为0的情况下,函数会直接退出.等下次中断进来 的时候再来处理这些QH. 关于qh->is_stopped字段.除了本函数之外,只有uhci_result_common()中,检测到传输发生错误的时候才会将其置为 1
之后,就可以将传输完成的URB调用uhci_giveback_urb()将其删除了
 
    }
 
    /* If the QH is neither stopped nor finished unlinking (normal case),
     * our work here is done. */
    if (QH_FINISHED_UNLINKING(qh))
        qh->is_stopped = 1;
    else if (!qh->is_stopped)
        return;
这里的判断跟上面的是差不多的,如果满足了删除的QH的条件,就将qh->is_stopped置为1.然对于qh->is_stopped的情况,是会直接返回的.
那能够进入到下面的处理流程的,只有以下这种情况:
1:调用了usb_unlink_urb()/usb_kill_urb()的URB.且符合了删除的条件.
2:由uhci_giveback_urb()删除,且满足删除条件的QH
3:之前在uhci_advance_check()中传输超时加入到skel_unlink_qh的QH且满足删除条件.
上面所说的”删除条件”是指必须要在QH调度帧完了之后,才会删除QH
 
    /* Otherwise give back each of the dequeued URBs */
restart:
    list_for_each_entry(urbp, &qh->queue, node) {
        urb = urbp->urb;
        if (urb->unlinked) {
 
            /* Fix up the TD links and save the toggles for
             * non-Isochronous queues.  For Isochronous queues,
             * test for too-recent dequeues. */
            if (!uhci_cleanup_queue(uhci, qh, urb)) {
                qh->is_stopped = 0;
                return;
            }
            uhci_giveback_urb(uhci, qh, urb, 0);
            goto restart;
        }
    }
处理上述分析中的第1种情况的urb的处理.
    qh->is_stopped = 0;
 
    /* There are no more dequeued URBs.  If there are still URBs on the
     * queue, the QH can now be re-activated. */
    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);
    }
对于上述分析的第3种情况的处理,这种情况下的QH,是可以重新安排它调度的
    /* The queue is empty.  The QH can become idle if it is fully
     * unlinked. */
    else if (QH_FINISHED_UNLINKING(qh))
        uhci_make_qh_idle(uhci, qh);
对于上述分析的第2种情况的处理.
}
 
里面有几个很重要的子函数,有必要详细分析如下:
1: uhci_result_isochronous().代码如下:
static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb)
{
    struct uhci_td *td, *tmp;
    struct urb_priv *urbp = urb->hcpriv;
    struct uhci_qh *qh = urbp->qh;
 
    list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {
        unsigned int ctrlstat;
        int status;
        int actlength;
 
        //还没有调度到实时传输
        if (uhci_frame_before_eq(uhci->cur_iso_frame, qh->iso_frame))
            return -EINPROGRESS;
 
        //该队列已经被调度过了,可以将它删除了
        uhci_remove_tds_from_frame(uhci, qh->iso_frame);
 
        ctrlstat = td_status(td);
        if (ctrlstat & TD_CTRL_ACTIVE) {
            status = -EXDEV;    /* TD was added too late? */
        } else {
            status = uhci_map_status(uhci_status_bits(ctrlstat),
                    usb_pipeout(urb->pipe));
            actlength = uhci_actual_length(ctrlstat);
 
            urb->actual_length += actlength;
            qh->iso_packet_desc->actual_length = actlength;
            qh->iso_packet_desc->status = status;
        }
        //如果发生了错误,更新错误计数
        if (status)
            urb->error_count++;
        //将td从urbp->td_list上删除
        uhci_remove_td_from_urbp(td);
        //释放这个td
        uhci_free_td(uhci, td);
        //更新iso_frame
        qh->iso_frame += qh->period;
        ++qh->iso_packet_desc;
    }
    return 0;
}
这个函数是对实时传输的QH的处理.
qh->iso_frame在提交ISO URB的时候,是设置为了调度的起始帧号.在上面的这个函数中,每过一次循环, qh->iso_frame就加上一个周期(qh->period),即遍历TD所在的调度frame.
Qh-> cur_iso_frame表示当前的调度帧号.
所以,只需要比较qh->iso_frame和qh->cur_iso_frame就可以知道当前调度队列中的iso TD是否已经被调度.对于那些已经被调度的TD,要断开它的链表,并释放它的空间.
从上面的代码中也可以看到,只要ISO中的TD还没传输完,就会返回-EINPROGRESS.
另外,提醒一下:对于挂在UHCI的1024个调度frame上的TD和挂在QH下的TD,UHCI对它们的处理是不相同的.对于挂在frame上的TD,UHCI处理完之后, UHCI处理完之后只会将其标记为INACITVE.(实时传输就这样的情况)
挂在QH中的TD,运行完了之后,会将其脱链.
这个函数比较简单,就不做详细分析了.
 
2: uhci_result_common()函数.
代码如下:
static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb)
{
    struct urb_priv *urbp = urb->hcpriv;
    struct uhci_qh *qh = urbp->qh;
    struct uhci_td *td, *tmp;
    unsigned status;
    int ret = 0;
 
    //遍历挂在urbp下面的td
    list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {
        unsigned int ctrlstat;
        int len;
 
        //取td的状态
        ctrlstat = td_status(td);
        status = uhci_status_bits(ctrlstat);
        //如果td还没有调度完成,退出. 不需要检查它后面的TD了.肯定也没有完成
        if (status & TD_CTRL_ACTIVE)
            return -EINPROGRESS;
 
        //更新actual_length (实际传输的长度)
        len = uhci_actual_length(ctrlstat);
        urb->actual_length += len;
        //如果TD传输发生错误,status不为0
        if (status) {
            ret = uhci_map_status(status,
                    uhci_packetout(td_token(td)));
            if ((debug == 1 && ret != -EPIPE) || debug > 1) {
                /* Some debugging code */
                dev_dbg(&urb->dev->dev,
                        "%s: failed with status %x\n",
                        __FUNCTION__, status);
 
                if (debug > 1 && errbuf) {
                    /* Print the chain for debugging */
                    uhci_show_qh(uhci, urbp->qh, errbuf,
                            ERRBUF_LEN, 0);
                    lprintk(errbuf);
                }
            }
 
        /* Did we receive a short packet? */
        }
        //如果收到了一个短包
        else if (len < uhci_expected_length(td_token(td))) {
 
            /* For control transfers, go to the status TD if
             * this isn't already the last data TD */
             //如果是控制传输.有三个阶段,即SETUP,DATA,HANDSHAKE.
            //在这里判断是否是最后的一个DATA TD,如果是的话,短包是正常的
            if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
                if (td->list.next != urbp->td_list.prev)
                    ret = 1;
            }
 
            /* For bulk and interrupt, this may be an error */
            //如果设置了URB_SHORT_NOT_OK,不允许短包
            else if (urb->transfer_flags & URB_SHORT_NOT_OK)
                ret = -EREMOTEIO;
 
            /* Fixup needed only if this isn't the URB's last TD */
            //对于非控制传输类型,判断是否是最后一个TD.最后一个TD短包是正常的
            else if (&td->list != urbp->td_list.prev)
                ret = 1;
        }
        //TD传输完成了.可以将它移除了
        uhci_remove_td_from_urbp(td);
        //qh->post_td用来何存最近完成的TD
        if (qh->post_td)
            uhci_free_td(uhci, qh->post_td);
        qh->post_td = td;
 
        if (ret != 0)
            goto err;
    }
    return ret;
 
err:
//如果传输错误,则终止QH的传输
    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 */
        //对于短包错误,还要进行fixup
        ret = uhci_fixup_short_transfer(uhci, qh, urbp);
    return ret;
}
对照上面的注释,这段代码应该很好懂.重点分析一下uhci_fixup_short_transfer():
static int uhci_fixup_short_transfer(struct uhci_hcd *uhci,
        struct uhci_qh *qh, struct urb_priv *urbp)
{
    struct uhci_td *td;
    struct list_head *tmp;
    int ret;
 
    td = list_entry(urbp->td_list.prev, struct uhci_td, list);
    if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
 
        /* When a control transfer is short, we have to restart
         * the queue at the status stage transaction, which is
         * the last TD. */
         //如果是控制传输,则需要传送最后一个handshake TD
        WARN_ON(list_empty(&urbp->td_list));
        qh->element = LINK_TO_TD(td);
        tmp = td->list.prev;
        ret = -EINPROGRESS;
 
    } else {
 
        /* When a bulk/interrupt transfer is short, we have to
         * fix up the toggles of the following URBs on the queue
         * before restarting the queue at the next URB. */
        qh->initial_toggle = uhci_toggle(td_token(qh->post_td)) ^ 1;
        uhci_fixup_toggles(qh, 1);
 
        if (list_empty(&urbp->td_list))
            td = qh->post_td;
        qh->element = td->link;
        tmp = urbp->td_list.prev;
        ret = 0;
    }
 
    /* Remove all the TDs we skipped over, from tmp back to the start */
    //删除tmp之前的td
    while (tmp != &urbp->td_list) {
        td = list_entry(tmp, struct uhci_td, list);
        tmp = tmp->prev;
 
        uhci_remove_td_from_urbp(td);
        uhci_free_td(uhci, td);
    }
    return ret;
}
对于控制传输和其它类型的传输的处理是不一样的,如果是控制传输,就算前面的数据传输错了,也不能将整个传输过程中断,必须要将最后的 handshake包传过去,如果是其它类型的传输,只是将此次传输终止掉就可以了.但是,按照usb2.0 spec规定的纠错方法,必须要修改后面发包的toggle值.


未完待续......
阅读
0 0
原创粉丝点击