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

3.4:中断传输过程
1:root hub的中断传输
在usb_hcd_submit_urb()à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_queue_status()中:
static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
{
    int     retval;
    unsigned long   flags;
    int     len = 1 + (urb->dev->maxchild / 8);

    spin_lock_irqsave (&hcd_root_hub_lock, flags);
    //如果root hub已经在处理中断传输或者是urb非法
    if (hcd->status_urb || urb->transfer_buffer_length 
        dev_dbg (hcd->self.controller, "not queuing rh status urb\n");
        retval = -EINVAL;
        goto done;
    }

    //将urb添加到urb->ep_>urb_list
    retval = usb_hcd_link_urb_to_ep(hcd, urb);
    if (retval)
        goto done;

    hcd->status_urb = urb;
    urb->hcpriv = hcd;  /* indicate it's queued */
    //uhci_start(),会将uses_newpolling设为了1
    if (!hcd->uses_new_polling)
        mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));

    /* If a status change has already occurred, report it ASAP */
    else if (hcd->poll_pending)
        mod_timer(&hcd->rh_timer, jiffies);
    retval = 0;
done:
    spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
    return retval;
}
从上面的代码可以看到,urb最终会交给hcd->rh_timer这个定时器进行处理.在前面的分析中,我们看到了在uhci_start()中 设置了usrs_new_polling.搜索整个linux源代码,发现其它地方没有更改这个值.所以,上面代码中的if判断是不会满足的.那 hcd->poll_pending会不会满足呢?
要回答这个问题,我们得返回看一下usb_add_hcd()的处理,在那里我们忽略了一个函数的处理.在这个函数中,有这样的代码片段:
int usb_add_hcd(struct usb_hcd *hcd,
        unsigned int irqnum, unsigned long irqflags)
{
        ......
        ......
        if (hcd->uses_new_polling && hcd->poll_rh)
        usb_hcd_poll_rh_status(hcd);
            return retval;
......
}
在start_rh()中,会将hcd->poll_rh设为1,那在初始化阶段,usb_hcd_poll_rh_status()会得到运行:
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
    struct urb  *urb;
    int     length;
    unsigned long   flags;
    char        buffer[4];  /* Any root hubs with > 31 ports? */

    if (unlikely(!hcd->rh_registered))
        return;
    if (!hcd->uses_new_polling && !hcd->status_urb)
        return;

    length = hcd->driver->hub_status_data(hcd, buffer);
    if (length > 0) {

        /* try to complete the status urb */
        spin_lock_irqsave(&hcd_root_hub_lock, flags);
        urb = hcd->status_urb;
        if (urb) {
            hcd->poll_pending = 0;
            hcd->status_urb = NULL;
            urb->actual_length = length;
            memcpy(urb->transfer_buffer, buffer, length);

            usb_hcd_unlink_urb_from_ep(hcd, urb);
            spin_unlock(&hcd_root_hub_lock);
            usb_hcd_giveback_urb(hcd, urb, 0);
            spin_lock(&hcd_root_hub_lock);
        } else {
            length = 0;
            hcd->poll_pending = 1;
        }
        spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
    }

    if (hcd->uses_new_polling ? hcd->poll_rh :
            (length == 0 && hcd->status_urb != NULL))
        mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}
首先进行一些合法性检测.如果hcd没有注册root hub,会出错退出.实际上,这个函数就是为了周期性检测root hub的状态而设的.
之后会调用length = hcd->driver->hub_status_data(hcd, buffer);
它会回溯到UHCI驱动的hub_status_data().这个函数会去取root hub的状态,如果状态发生改变,则将改变的状态存放在buffer中.然后返回1.如果没有必变,则返回0.
hcd->status_urb终于对应到前面rh_queue_status()的分析了.我们知道,如果主机发出中断传输,会将hcd->status_urb指向这个urb.
那,这个函数对应的操作为:
如果主机有发了中断传输来获得root hub的状态,就将状态值赋值到urb的传输缓存区.如果没有,状态却发生了改变,将hcd->poll_pending设为1.
函数后面的if判断是肯定会成功的.因为hcd->uses_new_polling ,hcd->poll_rh都为真.然后,启动了定时器hcd->rh_timer.
这个定时器的初始化如下:
在usb_create_hcd()中:
......
init_timer(&hcd->rh_timer);
    hcd->rh_timer.function = rh_timer_func;
    hcd->rh_timer.data = (unsigned long) hcd;
......
也就是说,这个定时器的处理函数为rh_timer_func().参数为hcd.
处理函数代码如下:
static void rh_timer_func (unsigned long _hcd)
{
    usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
}
看到了吧.这个定时器的处理函数就是上面分析的usb_hcd_poll_rh_status.
也就是说,从系统初始化后,这个定时器就会一直在运行了.

OK.返回到rh_queue_status().如果root_hub的状态发生了改变,就会重新设定定时器hcd->rh_timer.这样做是为了防止某些情况下(例如改变了hcd->uses_new_polling的值)定时器停止.
我们在上面分析过了这个定时器的处理函数.不过还留下了一个尾巴,也就是hcd->driver->hub_status_data().下面就来分析一下这个函数.
这个函数指针的接口为
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
    struct uhci_hcd *uhci = hcd_to_uhci(hcd);
    unsigned long flags;
    int status = 0;

    spin_lock_irqsave(&uhci->lock, flags);
    //扫描调度队列,将一些已经运先完毕的TD,QH删除掉
    uhci_scan_schedule(uhci);
    //如果hcd不处于HCD_FLAG_HW_ACCESSIBLE状态,或者处于"死"状态
    //即不可操作状态
    if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
        goto done;
    //检查UHCI的端口,也即root hub的端口
    uhci_check_ports(uhci);
    //判断端口状态是否改变
    status = get_hub_status_data(uhci, buf);

    //端口状态改变的相应处理
    //uhci->rh_state,是root hub以前的状态
    //如果status为1,则状态发生了改变
    switch (uhci->rh_state) {
        //电源管理部份,忽略
        case UHCI_RH_SUSPENDING:
        case UHCI_RH_SUSPENDED:
        /* if port change, ask to be resumed */
        if (status)
            usb_hcd_resume_root_hub(hcd);
        break;

        case UHCI_RH_AUTO_STOPPED:
        /* if port change, auto start */
        //如果旧状态是stop,且发生了改变,那就将root hub启动了
        if (status)
            wakeup_rh(uhci);
        break;

    //如果旧状态是RH_RUNNING.则判断root hub下面的端口状态.如果端口末连接
    //将其置为UHCI_RH_RUNNING_NODEVS.且设置auto_stop_time为 jiffies + HZ
        case UHCI_RH_RUNNING:
        /* are any devices attached? */
        if (!any_ports_active(uhci)) {
            uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
            uhci->auto_stop_time = jiffies + HZ;
        }
        break;

    //如果旧状态是UHCI_RH_RUNNING_NODEVS.判断是否有设备连上了root hub
    //如果有连上,将其状态置为UHCI_RH_RUNNING
    //如果还是超时还是没连上,将端口suspend
        case UHCI_RH_RUNNING_NODEVS:
        /* auto-stop if nothing connected for 1 second */
        if (any_ports_active(uhci))
            uhci->rh_state = UHCI_RH_RUNNING;
        else if (time_after_eq(jiffies, uhci->auto_stop_time))
            suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
        break;

        default:
        break;
    }

done:
    spin_unlock_irqrestore(&uhci->lock, flags);
    return status;
}
这个函数的操作逻辑还是比较清淅,不过大量状态的判断让人觉得头昏眼花.
首先函数调用uhci_scan_schedule()去扫描挂在UHCI上面的调度队列,如果有TD或者QH被执行完,就应该将它们从调度队列中删除. 这个函数在中断处理过程还会涉及到.到那时再进行详细的分析.在这里只要知道它是干什么的就可以了.它不会影响后续的流程.
然后,会调用uhci_check_ports()去检查端口.它主要是对超时末完成的Reset状态和resume信号的处理
接着,调用get_hub_status_data()去判断端口状态是否发生了改变,如果发生了改变,返回值为1.且将改变状态的端口在buf中置位.
最后,就是一个很长的switch语句来处理对应状态的改变.
如果之前的状态是UHCI_RH_SUSPENDING和UHCI_RH_SUSPENDED,那表明之前root hub处于挂起状态.如果状态发生了改变,就要将它从挂起状态中恢复过来.这一个过程涉及到了电源管理.暂且忽略掉这一倍份.
如果之前是UHCI_RH_AUTO_STOPPED状态.表明设备之前是STOP的,现在状改变了,当然要将它重新启用了.
如果之前是UHCI_RH_RUNNING状态,表示root hub处于正常的联连.那就去判断这个root hub上是否有设备相连.如果没有.则将其置为UHCI_RH_RUNNING_NODEVS.假设root hub上之前挂了一个U盘.此时的状态是UHCI_RH_RUNNING_RUNING的,之后我将U盘拨下,这时候端口会转变为 UHCI_RH_RUNNING_NODEVS状态.
如果之前是UHCI_RH_RUNNING_NODEVS,去判断hub下面是否有设备相联.如果有,则将它转换到UHCI_RH_RUNNING_RUNING.如果还是没有设备相联,且没有设备相联的时候超过了一个HZ.将就端口suspend.用来节省电源.
下面,挨个分析这个函数的几个子函数.
第一个要分析的子函数是: uhci_check_ports()
static void uhci_check_ports(struct uhci_hcd *uhci)
{
    unsigned int port;
    unsigned long port_addr;
    int status;
//遍历UHCI下面的端口
    for (port = 0; port rh_numports; ++port) {
        port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
        status = inw(port_addr);

        //端口处于Reset状态
        if (unlikely(status & USBPORTSC_PR)) {
            //在用户将root hub reset时,会将ports_timeout设置成iffies + //msecs_to_jiffies(50)
            //如果超时之后,RESET过程还末完成
            if (time_after_eq(jiffies, uhci->ports_timeout)) {
                //将一些状态位和PR位清了
                CLR_RH_PORTSTAT(USBPORTSC_PR);
                udelay(10);

                /* HP's server management chip requires
                 * a longer delay. */
                 //HP主机的特殊处理
                if (to_pci_dev(uhci_dev(uhci))->vendor ==
                        PCI_VENDOR_ID_HP)
                    wait_for_HP(port_addr);

                /* If the port was enabled before, turning
                 * reset on caused a port enable change.
                 * Turning reset off causes a port connect
                 * status change.  Clear these changes. */
                 //清除掉CSC,PEC,并设置PE
                CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);
                SET_RH_PORTSTAT(USBPORTSC_PE);
            }
        }

        //端口探测到了Resume 信号
        if (unlikely(status & USBPORTSC_RD)) {
            //对于Resume,有两个可能的情况,一种是用户设置端口成Resume
            //另外的一种是状态检测到端口变为Resume
            
            //如果用户设置,会将port在resuming_ports置位.否则,便是状态检测到端口变为Resume
            if (!test_bit(port, &uhci->resuming_ports)) {

                /* Port received a wakeup request */
                //如果是轮询检测的.将其在resuing_port中置位.
                //并且设置port_timeout为iffies +msecs_to_jiffies(20)
                //将状态检测定时器设置在prot_timeout后运行
                set_bit(port, &uhci->resuming_ports);
                uhci->ports_timeout = jiffies +
                        msecs_to_jiffies(20);

                /* Make sure we see the port again
                 * after the resuming period is over. */
                mod_timer(&uhci_to_hcd(uhci)->rh_timer,
                        uhci->ports_timeout);
            }
            //如果在超时过后,还是处于Resume.
            //需要软件来清除这个状态
            else if (time_after_eq(jiffies,
                        uhci->ports_timeout)) {
                uhci_finish_suspend(uhci, port, port_addr);
            }
        }
    }
}
第一个要处理的是RESET状态,用户可以通过控制传输将root hub 进行Rest操作,如果Reset过程超过了50ms.这就需要软件来处理了,驱动将一些状态标志清除之后,设置PE位,PE即port enable的意思.
第二个要处理的是Resume信息.本来设备在收到这个信号的时候,应该将设备从挂起状态恢复到正常状态.但是如果20ms之后,恢复过程还没有结束,就 需要软件去处理了.在代码中我们可以看到,它会调用uhci_finish_suspend()去完成软件处理的这一过程.代码如下:
static void uhci_finish_suspend(struct uhci_hcd *uhci, int port,
        unsigned long port_addr)
{
    int status;
    int i;

    //如果端口处于恢复或者是Resum状态
    if (inw(port_addr) & SUSPEND_BITS) {
        //将SUSP和RD位清除
        CLR_RH_PORTSTAT(SUSPEND_BITS);
        //如果端口在resuming被置,将其在suspend置位,表示是刚刚恢复过来的port
        if (test_bit(port, &uhci->resuming_ports))
            set_bit(port, &uhci->port_c_suspend);

        /* The controller won't actually turn off the RD bit until
         * it has had a chance to send a low-speed EOP sequence,
         * which is supposed to take 3 bit times (= 2 microseconds).
         * Experiments show that some controllers take longer, so
         * we'll poll for completion. */
         //等待UHCI清降RD位
        for (i = 0; i 
            if (!(inw(port_addr) & SUSPEND_BITS))
                break;
            udelay(1);
        }
    }
    //在resuming_ports中将对应位清除
    clear_bit(port, &uhci->resuming_ports);
}
从上面的处理我们可以看到,它先将SUSP和RD位清除,从代码的注释中可以看到,UHCI要发送一个EOP包之后,才会改变它的状态.因此在这里一直延时等待它的状态改变完成.
在这里,注意到刚刚恢复的端口会在port_c_suspend位图中置位.那要到什么时候才会将它在port_c_suspend位图中清除呢?
必须要等待UHCI向root hub发送CLEAR_FEATURE才会将其清除. 只是这个过程我们在分析控制传输的时候将它忽略过去了,:-)
到这里, uhci_check_ports()函数就分析完了.

第二个要分析的函数是get_hub_status_data().它会判断端口状态是否发生了改变.代码如下:
static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)
{
    int port;
    //mask设定为了SC1的RWC位.这些RWC属性的位其实都代表着端口状态的改变
    int mask = RWC_BITS;

    /* Some boards (both VIA and Intel apparently) report bogus
     * overcurrent indications, causing massive log spam unless
     * we completely ignore them.  This doesn't seem to be a problem
     * with the chipset so much as with the way it is connected on
     * the motherboard; if the overcurrent input is left to float
     * then it may constantly register false positives. */
     
     //有的主板会误报错误,使mask去掉这几位的匹配
    if (ignore_oc)
        mask &= ~USBPORTSC_OCC;

    *buf = 0;
    //如果端口发生了改变,则将buf中的相应位置1
    for (port = 0; port rh_numports; ++port) {
        if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) ||
                //或者是端口刚恢复过来
                test_bit(port, &uhci->port_c_suspend))
            *buf |= (1 
    }
    //如果buf不为空,返回真
    return !!*buf;
}
从UHCI的spec中可以看到,对应PORTSC寄存器的RW/C属性的位,其实都是状态改变位,判断这些位是否被置就可以得知端口状态是否发生了改变.
在这里要注意buf中置位操作,它是*buf |= (1
疑问:在这里通过读取PORTSC的值来判断ROOT HUB的状态是否发生了改变,那代码中又没看到什么地方对它清位.这是为什么呢???有一位高人告诉我,是在hub driver中完成的.初想之下似乎有点不可思议,不过,到分析HUB驱动的时候再来解答这个疑问好了:-(

第三个要分析的函数是wakeup_rh().这个函数将root hub重新运行起来,代码如下:
static void wakeup_rh(struct uhci_hcd *uhci)
__releases(uhci->lock)
__acquires(uhci->lock)
{
    dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
            "%s%s\n", __FUNCTION__,
            uhci->rh_state == UHCI_RH_AUTO_STOPPED ?
                " (auto-start)" : "");

    /* If we are auto-stopped then no devices are attached so there's
     * no need for wakeup signals.  Otherwise we send Global Resume
     * for 20 ms.
     */
     //如果UHCI处于挂起状态,将其恢复
    if (uhci->rh_state == UHCI_RH_SUSPENDED) {
        //将将态设为UHCI_RH_RESUMING.表示正在恢复
        uhci->rh_state = UHCI_RH_RESUMING;

        //置FGD,EGSM,CF位
        outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,
                uhci->io_addr + USBCMD);
        spin_unlock_irq(&uhci->lock);
        //延迟20s
        msleep(20);
        spin_lock_irq(&uhci->lock);
        //如果uhci dead,非法
        if (uhci->dead)
            return;

        /* End Global Resume and wait for EOP to be sent */
        //再次写入CF
        outw(USBCMD_CF, uhci->io_addr + USBCMD);
        mb();
        udelay(4);
        //照spec上的说法,20s过后,FGR标志应该是会清除的,如果没有,打印出警告
        if (inw(uhci->io_addr + USBCMD) & USBCMD_FGR)
            dev_warn(uhci_dev(uhci), "FGR not stopped yet!\n");
    }

    //启动RH
    start_rh(uhci);

    /* Restart root hub polling */
    mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies);
}
按照UHCI spec上的说法,如果是处于挂起状态的设备,可以给它发送一个强制的RESUME信号.当UHCI检测到USBCMD_FGR位被设置之后,会让设备处于活跃状态.发送强制的RESUME会有20s的时间.
之后的start_rh()我们在之前分析过,这里不再赘述.

第四个要分析的函数是any_ports_active().它会检测设备的端口状态.状态如下:
static int any_ports_active(struct uhci_hcd *uhci)
{
    int port;

    for (port = 0; port rh_numports; ++port) {
        if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) &
                (USBPORTSC_CCS | RWC_BITS)) ||
                //或者是端口刚刚恢复过来
                test_bit(port, &uhci->port_c_suspend))
            return 1;
    }
    return 0;
}
跟前面分析的一样,还是取PORTSC的值,如果状态有改变或者CCS被置,就认为端口上已经连上设备了.CCS位表示Current Connect Status.

第五个要分析的函数是suspend_rh().
static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state)
__releases(uhci->lock)
__acquires(uhci->lock)
{
    int auto_stop;
    int int_enable, egsm_enable;

    auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
    dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
            "%s%s\n", __FUNCTION__,
            (auto_stop ? " (auto-stop)" : ""));

    /* If we get a suspend request when we're already auto-stopped
     * then there's nothing to do.
     */
     //如果已经在STOP状态了,不需要进行任何处理了
    if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
        uhci->rh_state = new_state;
        return;
    }

    /* Enable resume-detect interrupts if they work.
     * Then enter Global Suspend mode if _it_ works, still configured.
     */
    egsm_enable = USBCMD_EGSM;
    uhci->working_RD = 1;
    int_enable = USBINTR_RESUME;
    //选择编译函数
    if (remote_wakeup_is_broken(uhci))
        egsm_enable = 0;
    //这代码段也可以忽略,是用来修正某些设备的
    if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
            !device_may_wakeup(
                &uhci_to_hcd(uhci)->self.root_hub->dev))
        uhci->working_RD = int_enable = 0;

    //置RESUME位,表示如果有Resume信号,将会引发中断
    outw(int_enable, uhci->io_addr + USBINTR);
    //置位EGSM和CF
    outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
    mb();
    udelay(5);

    /* If we're auto-stopping then no devices have been attached
     * for a while, so there shouldn't be any active URBs and the
     * controller should stop after a few microseconds.  Otherwise
     * we will give the controller one frame to stop.
     */

    //调用这个函数的new_start有两种可能,UHCI_RH_SUSPENDED和UHCI_RH_AUTO_STOPPED

    //如果UHCI还没有halted?.再等1s
    if (!auto_stop && !(inw(uhci->io_addr + USBSTS) & USBSTS_HCH)) {
        //将状态置为SUSPENDING.表示正在进行挂起状态
        uhci->rh_state = UHCI_RH_SUSPENDING;
        spin_unlock_irq(&uhci->lock);
        msleep(1);
        spin_lock_irq(&uhci->lock);
        if (uhci->dead)
            return;
    }
    //如果还没halted,打印出警告
    if (!(inw(uhci->io_addr + USBSTS) & USBSTS_HCH))
        dev_warn(&uhci_to_hcd(uhci)->self.root_hub->dev,
            "Controller not stopped yet!\n");
    //得到当前传输的帧号
    uhci_get_current_frame_number(uhci);
    //更新状态,设is_stopped为1,在正常情况下,poll_rh会设为0.rh_timer也停止了
    uhci->rh_state = new_state;
    uhci->is_stopped = UHCI_IS_STOPPED;
    uhci_to_hcd(uhci)->poll_rh = !int_enable;
    //更新一下调度队列
    uhci_scan_schedule(uhci);
    //停止FSBR
    uhci_fsbr_off(uhci);
}
首先,调用这个函数的时候有两种情况,一种是以UHCI_RH_SUSPENDED为参数进行调用,另外的一种是以UHCI_RH_AUTO_STOPPED进行调用.即对root hub进行挂起或者停止操作.
然后,在命令寄存器中置位USBCMD_EGSM.这样,设备就会进入EGSM模式.再启用Resume中断.如果以后有resume就会进入到中断处理程序.
最后,设置poll_rh为0,rh_timer定时器随之停止了.
可能会有这样的疑问,既然root hub的轮询状态定时器已经停止,那之后要怎么去更改root hub的状态呢?我们不是在前面打开了Resume中断么?然后在中断处理中,又会将rh_timer唤起.
到这里, uhci_hub_status_data()函数已经分析完了.相应的,root_hub的中断传输返回了.

2:非root_hub的中断传输
对于非root_hub的中断传输中,流程会转向到hcd->driver->urb_enqueue()进行处理.
首先,在uhci_alloc_qh()对中断传输就有一些特殊的处理.如下代码片段如示 :
static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,
        struct usb_device *udev, struct usb_host_endpoint *hep)
{
        ......
        ......
//计算传输所占的总线时间,以微秒为单位
        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;
......
}
在代码中判断,如果是中断传输或者是实时传输,就计算传输数据所耗的总线时间.这些计算公式都是由usb2.0 spec上规定的.
细心一点朋友可能发现了,在这里计算传输的字节数是hep->desc.wMaxPacketSize,即一次传输的数据值,但是传输数据总量不是hep->desc.wMaxPacketSize,它有可能会打成多个包嘛?
嗯,不错,有可能中断传输需要多个包,它的数据总量也并不是hep->desc.wMaxPacketSize.对应到UHCI的调度来说,他需要 多个TD.前面我们分析FSBR机制的时候就说过,在一个frame内,默认用深度扫描的方式去扫描各个QH,然后处理一个挂下它下面的TD,然后就去处 理另外的QH.从这里,我们知道,在一个frame内.只会传输一个数据包的,这个数据包的数据最大值就是 hep->desc.wMaxPacketSize,那也就是说,一个frame内,所消耗的总线时间就是 hep->desc.wMaxPacketSize大小的耗时.
哪为什么要计算它所需的总线时间呢?因为USB2.0 spec规定,实时传输加上中断传输所耗的带宽不能超过总带宽的90%.对应到总线时间上,每一个frmae内, 实时传输加上中断传输的时间不能超过900微秒.因为一个frame是1毫秒,也就是1000微秒.

返回到uhci_urb_enqueue(),在switch的判断中,会进入到uhci_submit_interrupt().代码如下:
static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    int ret;

    /* USB 1.1 interrupt transfers only involve one packet per interval.
     * Drivers can submit URBs of any length, but longer ones will need
     * multiple intervals to complete.
     */

    //QH在初始化的时候会将bandwidth_reserved置为0
    if (!qh->bandwidth_reserved) {
        int exponent;

        /* Figure out which power-of-two queue to use */
        //计算合适的周期值
        for (exponent = 7; exponent >= 0; --exponent) {
            if ((1 interval)
                break;
        }
        if (exponent 
            return -EINVAL;
        //qh->period:调度QH的周期
        qh->period = 1 
        //QH所在的frame 项
        qh->skel = SKEL_INDEX(exponent);

        /* For now, interrupt phase is fixed by the layout
         * of the QH lists. */
         //检查带宽是否足够
        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 */

    //将数据保打成TD,然后挂以QH上
    ret = uhci_submit_common(uhci, urb, qh);
    if (ret == 0) {
        urb->interval = qh->period;
        //分得所需要的带宽
        if (!qh->bandwidth_reserved)
            uhci_reserve_bandwidth(uhci, qh);
    }
    return ret;
}
首先为传输选择一个合适的中断值.在前面我们分析过,UHCI的调度里,分为了从int1,int2,int4...int128这8种类型的中断.那在 urb中的间隔值可能没有落在这几个点上,例如,urb中的间隔值是34.落在int32和int64之间,所以要选择32做为它的周期.
SKEL_INDEX()是将周期值转换为skelqh中的序号.例如,int128对应的就是skelqh[2].
在这里注意到,打成TD的过程是调用uhci_submit_common().这和批量传输过程是一样的,在这里就不再做分析了.
在这里,要特别分析一下两个函数,一个是用来计算带宽是否足够的函数uhci_check_bandwidth().另一个是用来设置占用带宽的函数uhci_reserve_bandwidth().
先来分析uhci_check_bandwidth(),代码如下:
static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    int minimax_load;

    /* Find the optimal phase (unless it is already set) and get
     * its load value. */
    if (qh->phase >= 0)
        minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
    else {
        int phase, load;
        int max_phase = min_t(int, MAX_PHASE, qh->period);

        qh->phase = 0;
        minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
        for (phase = 1; phase 
            load = uhci_highest_load(uhci, phase, qh->period);
            if (load 
                minimax_load = load;
                qh->phase = phase;
            }
        }
    }

    /* Maximum allowable periodic bandwidth is 90%, or 900 us per frame */
    if (minimax_load + qh->load > 900) {
        dev_dbg(uhci_dev(uhci), "bandwidth allocation failed: "
                "period %d, phase %d, %d + %d us\n",
                qh->period, qh->phase, minimax_load, qh->load);
        return -ENOSPC;
    }
    return 0;
}
注意到,在uhci_submit_interrupt()中,将qh->phase设置如下:
    qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
这个phase肯定是大于或者等于0的.
根据上面的计算方式,int1,int2...int128分别对应的phase值是:
0,1,2,4,8,16,0,0
那这个phase代表的是什么呢?不着急,先看完整个过程的分析.
先撇开其它的,只看后面的一个if语句:
if (minimax_load + qh->load > 900)
......
根据注释和打印消息的提示,这里是带宽不够的情况,qh->load是我们在前面计算出来的,这次传输在frame所占的总线时间.以微秒为单位. 再根据注释的说明,再加上我们之前的分析:等时传输加上中断传输的带宽不能超过90%.也就是一个frame的900微秒.据此,我们可以肯定 minimax_load就是frame中已经被消耗的时间.
跟踪到uhci_highest_load():
static int uhci_highest_load(struct uhci_hcd *uhci, int phase, int period)
{
    int highest_load = uhci->load[phase];

    for (phase += period; phase 
        highest_load = max_t(int, highest_load, uhci->load[phase]);
    return highest_load;
}
这个函数用来计算中断传输所属的frame的负载情况.uhci有一个32项的数组.其实就是代表32个frame.在前面分析可以知道,UHCI调度中总共有1024项.这是就是以32项来表示1024项frame 的负载情况.
这个函数本身还是好懂,就是以phase为起点,以period为周期,找出数组项的最大值.
结合上面的分析:
int1,int2...int128分别对应的phase值是:
0,1,2,4,8,16,0,0
0,1,2,4,8,16这几项倒还是好理解,就是对应int1,int2,int4...int32在uhci->frame[]中的起始位置.这几个间隔在前32项数组里呈一个周期重复的关系,用32项数组来表示他们的负载情况毫无疑问是可以的.
Int1所占的位置是0.任何间隔后面都可以跟int1.因此,在这里,起始位置0也可以被其它间隔重用.
疑问:int64,int128都是以0项来表示负载的.即uhci->load[0].如果有这样 的情况,int64的中断传输已经处于饱和状态.那肯定会有: uhci->load[0]即将要大于900.那这时候,int128的中断传输是加不进来的.尽管uhci->frame[] 中,int128所占的frame项处于空闲状态 .
uhci_reserve_bandwidth()的代码如下:
static void uhci_reserve_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    int i;
    int load = qh->load;
    char *p = "??";

    for (i = qh->phase; i period) {
        uhci->load  += load;
        uhci->total_load += load;
    }
    uhci_to_hcd(uhci)->self.bandwidth_allocated =
            uhci->total_load / MAX_PHASE;
    switch (qh->type) {
    case USB_ENDPOINT_XFER_INT:
        ++uhci_to_hcd(uhci)->self.bandwidth_int_reqs;
        p = "INT";
        break;
    case USB_ENDPOINT_XFER_ISOC:
        ++uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs;
        p = "ISO";
        break;
    }
    qh->bandwidth_reserved = 1;
    dev_dbg(uhci_dev(uhci),
            "%s dev %d ep%02x-%s, period %d, phase %d, %d us\n",
            "reserve", qh->udev->devnum,
            qh->hep->desc.bEndpointAddress, p,
            qh->period, qh->phase, load);
}
首先,更新load[]数组,因为要传输一个中断数据包.会占据总线时间.在传输完成这个,同样也会更新load[].只不过,到时候是将它的值减下来.因为传输完成了,释放总线时间.
然后,更新uhci中的各项计算.
最后,将qh->bandwidth_reserver置为1.表示已经为这个QH分配带宽了.
就这样, uhci_submit_interrupt()就处理完了.流程返回到uhci_urb_enqueue().

同之前分析过的其它类型的传输一样,流程会进入到uhci_activate_qh().只不过,在函数里调用的是link_interrupt()用来跟具体的调度frame关联起来.代码如下:
static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    struct uhci_qh *pqh;

    //挂到skelqh[qh->skel]的最后面
    list_add_tail(&qh->node, &uhci->skelqh[qh->skel]->node);

    //找到链接在最末尾的QH
    pqh = list_entry(qh->node.prev, struct uhci_qh, node);
    //链接到这个QH的后面
    qh->link = pqh->link;
    wmb();
    pqh->link = LINK_TO_QH(qh);
}
很简单的操作,就是将qh插到对应的间隔的frame就可以了.

在这里,要注意,流回返回uhci_urb_enqueue之后, uhci_urbp_wants_fsbr()是什么都不会做的.
因为中断传输并没有调用uhci_add_fsbr()将fsbr开启.
到这里,中断传输已经分析完了.

3.5:实时传输过程
Root hub是不支持实时传输的.困此,流程一直流到uhci_urb_enqueue()里面.
对于实时传输, uhci_alloc_qh()有特殊的处理.如下面的代码片段如示:
static int uhci_urb_enqueue(struct usb_hcd *hcd,
        struct urb *urb, gfp_t mem_flags)
{
        ......
        ......
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;
......
}
如上面的代码所示,它并不会像其它的传输那样去创建qh->dummy_td.它也会跟中断传输一样去计算它所耗的总线时间.

在uhci_urb_enqueue()中的switch判断中,流程转入到uhci_submit_isochronous(),在分析代码之前,先来看一下中断传输的调度周期和实时传输的调度周期的差别.
首先是调度周期的范围不一样,中断传输的调度周期是从int1到int128,而实时传输的调度周期是从int1到int1024,不过两种传输的调度周期都有一个规律,就是它的周期值都是2的n次方的形式.也就是说它的低n位是对齐的.
再者,他们调度的起始点不一样,从前面UHCI调度架构初始化部份可以看到,UHCI的1024个frmame,全指向的int1到int128,也即 uhci->skelqh[2]~uhci->skelqh[9]项.这些间隔的起始位置在frame中都是被限定好了的.而对于实时传输, 它的调度起点可以任意定.
好了,可以跟进代码了,分段分析,如下示:
static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
        struct uhci_qh *qh)
{
    struct uhci_td *td = NULL;  /* Since urb->number_of_packets > 0 */
    int i, frame;
    unsigned long destination, status;
    struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;

    /* Values must not be too big (could overflow below) */
    //间隔时间不能超过1024,数据包个数不能超为1024
    if (urb->interval >= UHCI_NUMFRAMES ||
            urb->number_of_packets >= UHCI_NUMFRAMES)
        return -EFBIG;

    /* Check the period and figure out the starting frame number */
    if (!qh->bandwidth_reserved) {
        qh->period = urb->interval;
        //URB_ISO_ASAP这个flag是专门给等时传输用的,
    //它的意思就是告诉驱动程序,只要带宽允许,那么就从此点开始设置这个urb的start_frame变//量
        if (urb->transfer_flags & URB_ISO_ASAP) {
            qh->phase = -1;     /* Find the best phase */
            //找到一个最合适的phase
            i = uhci_check_bandwidth(uhci, qh);
            if (i)
                return i;

            /* Allow a little time to allocate the TDs */
            //取得当前的frame 
            uhci_get_current_frame_number(uhci);
            //延迟10ms.让它有足够的时候分配内存
            frame = uhci->frame_number + 10;

            /* Move forward to the first frame having the
             * correct phase */
             //找到frame下一个周期的起始位置
            urb->start_frame = frame + ((qh->phase - frame) &
                    (qh->period - 1));
        } else {
            //指定的起始帧不能在扫描帧的前面,扫描帧,也就是当前UHCI调度的帧号
            //起始帧不能放到被调度帧的前面.这样避免要传输的ISO数据的前面的帧要晚于后面的帧调//度
            i = urb->start_frame - uhci->last_iso_frame;
            if (i = UHCI_NUMFRAMES)
                return -EINVAL;
            //计算phase
            //start_frame要落在这个周期点上
            qh->phase = urb->start_frame & (qh->period - 1);
            i = uhci_check_bandwidth(uhci, qh);
            if (i)
                return i;
        }

    }
该函数先对入口参数进行有效性判断.在UHCI的架构上,进入到这个函数时,qh是刚刚分配初始化好的,它的bandwidth_reserved肯定是0,所以上面的if判断是肯定会满足的.
Urb->interval是驱动程序在提交实时传输时设置的传输间隔,也就是ISO数据包的调度周期,如果用户指明了URB_ISO_ASAP,则表示该ISO数据包要尽快的传送.
对于指定了URB_ISO_ASAP的情况,就是为传输选定一个周期始点和ISO数据的起始帧号.如果没有指定这个标志,那就使用用户自定义的起始帧号,但是请注意,这个起始帧号不能在当前调度帧的前面.为什么?
如果起始帧在当前调度帧的前面,那有可能,ISO数据包后面的帧会在当前调度帧的后面.那有可能后面的ISO数据反而要比前面的ISO数据先进行调度.
这段代码涉及到phase和start_frame计算比较涩晦,稍后再进行详细分析.

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;
    }
    /* Make sure we won't have to go too far into the future */
    //数据不能超长
    if (uhci_frame_before_eq(uhci->last_iso_frame + UHCI_NUMFRAMES,
            urb->start_frame + urb->number_of_packets *
                urb->interval))
        return -EFBIG;
后面的这几个elseif ,if肯定是不会进去的,可以直接跳过.一次传输的ISO数据包也不能够太长,从上面的计算,可以看出,一次ISO传输不能超过1023个包.

    //置ACTIVE和IOS
    status = TD_CTRL_ACTIVE | TD_CTRL_IOS;
    destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);

    for (i = 0; i number_of_packets; i++) {
        td = uhci_alloc_td(uhci);
        if (!td)
            return -ENOMEM;

        uhci_add_td_to_urbp(td, urbp);
        uhci_fill_td(td, status, destination |
                uhci_explen(urb->iso_frame_desc.length),
                urb->transfer_dma +
                    urb->iso_frame_desc.offset);
    }

    /* Set the interrupt-on-completion flag on the last packet. */
    //最后的一个TD置IOC
    td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
到这里的话,就是要将数据打成TD了.首先,对于每一个TD,都要置ACTIVE和IOS位,表示TD是一个可被调度的实时传输型的数据包.然后,对于最后的一个TD还要设置IOC位,这样,速个数据传输完成之后就会产生中断.
对于urb->iso_frame_desc数组,这是驱动程序在提交URB的时候,就已经设置了相关项的.上面代码中的urb->iso_frame_desc.length, urb->iso_frame_desc.offset分别表示该帧的长度和在整个数据中的偏移值.
这样,ISO对应的TD包就全部在urbp->td_list上面了

    /* Add the TDs to the frame list */
    //将TD挂到相应的frame上
    frame = urb->start_frame;
    list_for_each_entry(td, &urbp->td_list, list) {
        uhci_insert_td_in_frame_list(uhci, td, frame);
        frame += qh->period;
    }

    if (list_empty(&qh->queue)) {
        qh->iso_packet_desc = &urb->iso_frame_desc[0];
        qh->iso_frame = urb->start_frame;
    }
    //将skel置为SKEL_ISO
    qh->skel = SKEL_ISO;
    //设置占用带宽
    if (!qh->bandwidth_reserved)
        uhci_reserve_bandwidth(uhci, qh);
    return 0;
}
创建好了TD之后就要跟UHCI的调度系统关联起来了.从上面的代码中可以看到,它是每隔特定的周期就在uhci->frame[]数组里插入对应的TD.
从前面的流程中看来, list_empty(&qh->queue)是肯定会满足的,就这样, qh->iso_packet_desc指定了urb->iso_frame_desc[]的首地址.qh->iso_frame就是 它的起始帧位置.

经过上面的分段分析之后,流程就很清晰了.在代码中,有几个情况必须要详细的分析一下.
1:在指定URB_ISO_ASAP标志的情况下,phase和start_frame的计算
正如之前在分析中断传输的过程一样,phase就是指在frame[]中起始位置.在中断传输中,指定间隔的起始位置都是固定好的.但在ISO传输中,就 没必要了,因为它对数据的实时性要求很高,只要能够传输ISO,就马上让它传输出去,没必须要等到指定的间隔位置到来之后才去调度它.
在代码中,先将phase置为-1.然后调用uhci_check_bandwidth()来选择一个合适的phase值.这个函数在之前分析过,在这里将其关部份列出:
static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
        ......
        ......
if (qh->phase >= 0)
        minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
    else {
        int phase, load;
        int max_phase = min_t(int, MAX_PHASE, qh->period);

        qh->phase = 0;
        minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
        for (phase = 1; phase 
            load = uhci_highest_load(uhci, phase, qh->period);
            if (load 
                minimax_load = load;
                qh->phase = phase;
            }
        }
    }
......
}
从代码中很容易很出,它就是在找到一个负载最轻的项.
找到合适的phase之后,将选择start_frame的基准,也就是frame变量,在当前调度帧的基础上后移十个帧.注释中说的很明白,这样就是为了有足够的时候让它分配内存.
最终关于start_frame的计算如下:
            urb->start_frame = frame + ((qh->phase - frame) &
                    (qh->period - 1));
start_frame要满足两上条件:
1:start_frame在frame的后面.这点是没什么疑问的.因为我们希望这些ISO数据可以尽快被调度到.
2:start_frame必须要满足:start_frame = phase+K*period(k=0,1,2...).也就是说,start_frame必须要落在它的周期点上.
对应上面的计算就是为了找到frame后的第一个周期点.这里的计算方式很隐晦,下面详细分析一下这个算法.
1:对于phase=frame的情况.上面的计算是满足的.
2:对于phase>frame的情况.
如下图示:


首先,先提醒一下,phase之前,不能还有一个周期的长度,也就是说,phase是周期起点,这必须要满足,phase period的话,那周期起点就不是phase,面是phase-N*period(N=0,1,2...)了.
另外,还是注意这个period是2的n次方形式的,也就低n是为0的.
在这种情况一,(phase-frame)&( period-1)就是等于phase-frame. Frame+(phase-frame)&( period-1)=period.很显然是满足的.
3:对于phase
因为phase-frmae的值要小于0.这样给我们的计算造成了不便,所以不能用常规的方法去分析它.
A+(-A)=0
那A的后面,第一个为period的整数倍的值为
A+(-A)&(period-1)
为什么呢?我们假设period=1

如上图中分析的,A的两种情况:
1:一种是A的后M位为0.显然,A是period的倍数, A+(-A)&(period-1)还是等于A.显然是正确的
2:另一种A的后M位不为0, A+(-A)&(period-1)之后,低M为0,而第M+1有进位,显然结果就是A后面最小的period的倍数值.

假设,现在是从位置0开始计算的周期,那frame后的周期点就是frame+(-frame)&(period-1).现在周期是从phase 开始计算的,那,frmae后的周期点就要加上phase,即frame+(-frame)&(period-1)+phase.根据前面对于 phase的要求,有phase=(phase)&(period-1).那么就有了下面的式子:
frame+(-frame)&(period-1)+(phase)&(period-1)
因为(X+Y)&Z = X&Z+Y&Z
所以下面的式子就等价于:
Frame+(period-frame)&(period-1)
很显然,他们也是满足的
就这样,就确定了urb->start_frame的位置.

2:在没有指定URB_ISO_ASAP的情况下,phase和start_frame的计算
如果没有指定URB_ISO_ASAP,那start_frame就使用用户指定的start_frame.剩下的工作就是计算phase值了.这样的工作刚好跟第1种情况是相反的,在第1种情况里,是根据phase值来计算start_frame.
在这里,计算phase的式子如下:
qh->phase = urb->start_frame & (qh->period - 1);
很显然,在这里必须要满足:start_frame = phase+K*period(k=0,1,2...)只不过start_frmae是已知的,而phase是末知的.
很显然,只需要取start_frame的低K位就可以了.想一想,如果周期起点位置是从0开始的话,那start_frame肯定是低K位对齐的.所以start_frmae的低K位,也就是周期起点的偏移值,即phase.
3: uhci_insert_td_in_frame_list()函数
uhci_insert_td_in_frame_list就是将TD加到相应的frame里面去.它的代码如下:
static inline void uhci_insert_td_in_frame_list(struct uhci_hcd *uhci,
        struct uhci_td *td, unsigned framenum)
{
    framenum &= (UHCI_NUMFRAMES - 1);

    td->frame = framenum;

    /* Is there a TD already mapped there? */
    if (uhci->frame_cpu[framenum]) {
        struct uhci_td *ftd, *ltd;

        ftd = uhci->frame_cpu[framenum];
        ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);

        list_add_tail(&td->fl_list, &ftd->fl_list);

        td->link = ltd->link;
        wmb();
        ltd->link = LINK_TO_TD(td);
    } else {
        td->link = uhci->frame[framenum];
        wmb();
        uhci->frame[framenum] = LINK_TO_TD(td);
        uhci->frame_cpu[framenum] = td;
    }
}
很显然,在第一次添加的时候, uhci->frame_cpu[framenum]是空的,也就是说流程会进入到else中.在else的操作中,将frame指向TD,再指 TD指向QH.然后将uhci->frame_cpu[framenum]指向这个TD.
很显然,当以后要往这个frame添加TD的时候,它是将TD加到这个TD的后面.那实际上, uhci->frame_cpu[framenum]就是表示,挂在frame[framenum]上的实时TD链表.
用图来表示上述操作过程,如下:


回忆一下之前讲UHCI调度初始化的时候,从UHCI spec中列出来的那副调度图,是不是很像了?*^_^*

到这里, uhci_submit_isochronous()已经全部分析完了.流程返回到uhci_urb_enqueue()中.像其它传输一样,流程会转入 uhci_activate_qh中.只不过,在uhci_activate_qh()中调用的子函数是link_iso().它的代码如下:
static inline void link_iso(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
    list_add_tail(&qh->node, &uhci->skel_iso_qh->node);

    /* Isochronous QHs aren't linked by the hardware */
}
很显然,就是将实时传输的QH加到了skel_iso_qh的链表上.
在ISO传输中,有一点要特别注意,ISO传输中并没有去创建一个用来表示结尾的TD(因为ISO传输是TD相联的,最后一个TD联QH,如果中间有无效TD,UHCI就不会处理后面的QH了),而其它类型的传输都会有一个表示QH结尾的TD.

到这里,四种传输类型全部都分析完了.下面来个小小的总结:
1:对于控制传输和批量传输,它的qh是加在skel_async_qh上的.另外,这两种传输还支持FSBR.对于FSBR,它会借助于skel_term_qh来构成一个循环
2:对于中断传输,它的qh是加在中断间隔对应的skelqh[]的链表上
3:对于实时传输,它的qh是加在skel_iso_qh上.

四:中断处理过程
在上面的传输分析中,都在在结尾的TD上加上一个IOC属性,这个属性位表示,如果该TD如在的frame处理完成之后,就会给CPU上报一个中断.而UHCI驱动可能根据这个中断来判断urb是否传输完成了.闲言少述,进入代码.
UHCI的中断处理程序为usb_hcd_irq().代码如下:
irqreturn_t usb_hcd_irq (int irq, void *__hcd)
{
    struct usb_hcd      *hcd = __hcd;
    int         start = hcd->state;

    if (unlikely(start == HC_STATE_HALT ||
        !test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
        return IRQ_NONE;
    if (hcd->driver->irq (hcd) == IRQ_NONE)
        return IRQ_NONE;

    set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);

    if (unlikely(hcd->state == HC_STATE_HALT))
        usb_hc_died (hcd);
    return IRQ_HANDLED;
}
从上面的代码可以看到,流程最终会转向driver->irq().对应的接口为uhci_irq().代码如下:
static irqreturn_t uhci_irq(struct usb_hcd *hcd)
{
    struct uhci_hcd *uhci = hcd_to_uhci(hcd);
    unsigned short status;

    /*
     * Read the interrupt status, and write it back to clear the
     * interrupt cause.  Contrary to the UHCI specification, the
     * "HC Halted" status bit is persistent: it is RO, not R/WC.
     */
     //STS寄存器
    status = inw(uhci->io_addr + USBSTS);
    //USBSTS_HCH:UHCI停止运行时,设置此位
    //没有中断.
    if (!(status & ~USBSTS_HCH))    /* shared interrupt, not mine */
        return IRQ_NONE;
    //R/WC.把值写回去,用来清除其中的erron 位
    outw(status, uhci->io_addr + USBSTS);       /* Clear it */

    //根据不同的错误.打印出不出的错误提示信息
    if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) {
        if (status & USBSTS_HSE)
            dev_err(uhci_dev(uhci), "host system error, "
                    "PCI problems?\n");
        if (status & USBSTS_HCPE)
            dev_err(uhci_dev(uhci), "host controller process "
                    "error, something bad happened!\n");
        if (status & USBSTS_HCH) {
            spin_lock(&uhci->lock);
            if (uhci->rh_state >= UHCI_RH_RUNNING) {
                dev_err(uhci_dev(uhci),
                    "host controller halted, "
                    "very bad!\n");
                if (debug > 1 && errbuf) {
                    /* Print the schedule for debugging */
                    uhci_sprint_schedule(uhci,
                            errbuf, ERRBUF_LEN);
                    lprintk(errbuf);
                }
                uhci_hc_died(uhci);

                /* Force a callback in case there are
                 * pending unlinks */
                mod_timer(&hcd->rh_timer, jiffies);
            }
            spin_unlock(&uhci->lock);
        }
    }

        //如果收到了Resume信号,则重新启用定时器轮询.
if (status & USBSTS_RD)
        usb_hcd_poll_rh_status(hcd);
    else {
        spin_lock(&uhci->lock);
        //扫描调度队列
        uhci_scan_schedule(uhci);
        spin_unlock(&uhci->lock);
    }

    return IRQ_HANDLED;
}
USBSTS寄存器全名叫USB STATUS REGISTER.即状态寄存器,这个寄存器里会反应出系统的状态和中断状态.另外,这个寄存器是R/WC类型的,也就是说往寄存器中某位写入1,会将其置为0.代码中也利用了这个特性来清除STS中的ERROR位.
还记得之前分析root hub的中断传输时候,分析到suspend_rh()函数曾说过,如果设备被挂起,会将端口状态轮询定时器停止的,但是开启了Resume中断,如果收 到了Resume信号,就会产生一个中断,中断处理函数就再次启用轮询定时器.这也就是对应上面代码的if(status & USBSTS_RD)部份.
(末完,待续...)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值