(轉)linux設備驅動之USB數據傳輸分析 二


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 %d\n",
            wValue);
        break;

    /* INTERFACE REQUESTS (no defined feature/status flags) */

    /* ENDPOINT REQUESTS */

    case EndpointRequest | USB_REQ_GET_STATUS:
        // ENDPOINT_HALT flag
        tbuf [0] = 0;
        tbuf [1] = 0;
        len = 2;
            /* FALLTHROUGH */
    case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
    case EndpointOutRequest | USB_REQ_SET_FEATURE:
        dev_dbg (hcd->self.controller, "no endpoint features yet\n");
        break;

    /* CLASS REQUESTS (and errors) */

    default:
        /* non-generic request */
        switch (typeReq) {
        case GetHubStatus:
        case GetPortStatus:
            len = 4;
            break;
        case GetHubDescriptor:
            len = sizeof (struct usb_hub_descriptor);
            break;
        }
        status = hcd->driver->hub_control (hcd,
            typeReq, wValue, wIndex,
            tbuf, wLength);
        break;
error:
        /* "protocol stall" on error */
        status = -EPIPE;
    }

    //如果發生了錯誤,將len置為0,表示沒有數據要返回的
    if (status) {
        len = 0;
        if (status != -EPIPE) {
            dev_dbg (hcd->self.controller,
                "CTRL: TypeReq=0x%x val=0x%x "
                "idx=0x%x len=%d ==> %d\n",
                typeReq, wValue, wIndex,
                wLength, status);
        }
    }

    //copy返回的數據到transfer_buffer
    if (len) {
        if (urb->transfer_buffer_length 
            len = 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()中,后面的操作都跟控制傳輸完全一樣了.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM