单片机
返回首页

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

2024-07-18 来源:cnblogs

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 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)
                    
                | (1 
        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 
            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 
            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 
            n = rh_string (wValue & 0xff, hcd, ubuf, wLength);
            if (n 
                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 %dn',
            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 yetn');
        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 ==> %dn',
                typeReq, wValue, wIndex,
                wLength, status);
        }
    }

    //copy返回的数据到transfer_buffer
    if (len) {
        if (urb->transfer_buffer_length 
            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 = 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 
        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 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 >= 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 
        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;

    /*
     * Build the DATA TDs
     */
     //批量传输的数据阶段TD
    plink = NULL;
    td = qh->dummy_td;
    do {    /* Allow zero length packets */
        int pktsze = maxsze;

        //最后的分包了    
        if (len 
            pktsze = len;
            //如果URB的标志设置了URB_SHORT_NOT_OK.则表示短包是不可以接受的
            if (!(urb->transfer_flags & URB_SHORT_NOT_OK))
                //允许短包,清除SPD
                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 
                data);
        plink = &td->link;
        //设置ACTIVE位,则表示该TD有效
        status |= TD_CTRL_ACTIVE;

        data += pktsze;
        len -= maxsze;
        toggle ^= 1;
    } while (len > 0);

    /*
     * URB_ZERO_PACKET means adding a 0-length packet, if direction
     * is OUT and the transfer_length was an exact multiple of maxsze,
     * hence (len = transfer_length - N * maxsze) == 0
     * however, if transfer_length == 0, the zero packet was already
     * prepared above.
     */

    
    //判断是URB是否设置了URB_ZERO_PACKET.
    //如果该位被置,而且是OUT方向.且数据长度是最大允许长度的整数位
    //就需要传输一个0长度的数据包来结速此次传输
    if ((urb->transfer_flags & URB_ZERO_PACKET) &&
            usb_pipeout(urb->pipe) && len == 0 &&
            urb->transfer_buffer_length > 0) {
        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(0) |
                    (toggle 
                data);
        plink = &td->link;

        toggle ^= 1;
    }

    /* Set the interrupt-on-completion flag on the last packet.
     * A more-or-less typical 4 KB URB (= size of one memory page)
     * will require about 3 ms to transfer; that's a little on the
     * fast side but not enough to justify delaying an interrupt
     * more than 2 or 3 URBs, so we will ignore the URB_NO_INTERRUPT
     * flag setting. */
     //设置IOC位.表示执行传输完后,就会触发一次中断
    td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);

    /*
     * Build the new dummy TD and activate the old one
     */
     //队列结束的TD
    td = uhci_alloc_td(uhci);
    if (!td)
        goto nomem;
    *plink = LINK_TO_TD(td);

    uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);
    wmb();
    qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);
    qh->dummy_td = td;
    //设置usb_dev的toggle数组
    usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
            usb_pipeout(urb->pipe), toggle);
    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;
}
这里的操作和控制传输的的相应操作类似,只是对于BULK来说,它只有数据阶段.并不像CONTORL那样有三个相位.
另外,对于BULK来说,它的数据信息包PID也是DATA0和DATA1轮流反转的.用来做同步和错误检测.也就是对应上面代码中的toggle操作,关于toggle[]数组,在之前已经分析过了.
另外,对于设置了URB_ZERO_PACKET标志的情况,如果传输长度刚好是所允许最大长度整数倍,且为OUT方向,就需要发送一个0长度的数据包,表示本次传输已经结束了.
对于传输长度不是允许最大长度整数倍的情况,设备检测到传输长度小于所允许的最大长度就认为本次传输已经完成了.
返回到uhci_urb_enqueue()中,后面的操作都跟控制传输完全一样了.

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • 红外线探测报警器

  • 用NE555制作定时器

  • 带有短路保护系统的5V直流稳压电源电路图

  • 如何构建一个触摸传感器电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章