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

发布者:Huanle最新更新时间:2024-07-18 来源: cnblogs关键字: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 %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()中,后面的操作都跟控制传输完全一样了.

[1] [1]
关键字:linux  设备驱动  USB数据传输 引用地址:linux设备驱动之USB数据传输分析 二

上一篇:linux设备驱动之USB数据传输分析 一
下一篇:基于openwrt和s3c2440的无线ap实现

推荐阅读最新更新时间:2024-11-06 23:26

龙芯欲与OLPC项目合作 但未联系到尼葛洛庞帝
“从技术上看,目前龙芯2E在性能上远远超出100美元笔记本使用的处理器,所以我们认为龙梦和尼葛洛庞帝合作推广100美元笔记本具有一定的可行性。”中科龙梦科技有限公司总经理张福新博士告诉记者,龙梦科技公司有意与尼葛洛庞帝合作,“但是目前还没有联系上尼葛洛庞帝本人”。 张福新指出,携手100美元笔记本计划也可让我国青少年逐渐适应linux的操作环境。龙梦目前正与“一个孩子一台电脑”方面展开接触。 值得注意的是,旨在人人享用电脑的100美元笔记本计划之前在中国遭遇到一些困境,尼葛洛庞帝本人尽管经常来中国,但他却曾表示在中国“不知道应该和谁合作”。   新闻链接 100美元笔记本电脑由著名的未来学家、美国麻省理工学院(MIT)媒体实
[焦点新闻]
linux - 驱动中如何访问CPU中的寄存器?
正在移植2440, 偶然看2.6.32代码的时候, 发现smdk2440的mach-smdk2440.c中有如下结构体定义: static struct map_desc smdk2440_iodesc __initdata = { /* ISA IO Space map (memory space selected by A24) */ { .virtual = (u32)S3C24XX_VA_ISA_WORD, .pfn = __phys_to_pfn(S3C2410_CS2), .length = 0x10000, .type = MT_DEVICE, }, { .virtual = (u32)S3C24
[单片机]
基于嵌入式Linux的智能手机省电设计
引言 智能手机中包含了很多耗能设备, 诸如MP3、MPEG- 4、Wi-Fi、数码相机、3D 游戏等等。在手机电池容量还没有实现质的飞跃的前提下, 我们不得不考虑手机电源节能的问题。我主要通过了以下四个方面来阐述在基于Linux 平台上的智能手机的解决方案。 CPU 的选择 尽管现在有了各种在不过多加重功耗负担的前提下提高性能的技术, 但用一个芯片来处理这么多各种各样的任务, 恐怕已经不是一个很好的选择了。一是因为这些功能对芯片处理功能的要求可能各不相同, 二是因为一个负担着如此众多任务的芯片势必需要很高的速度, 降低功耗变得很困难。 在这种情况下, 多CPU 系统(MPCore)成为一个必然的趋势。多CPU 系统的一个明
[单片机]
基于嵌入式<font color='red'>Linux</font>的智能手机省电设计
arm linux 启动之二:start_kernel到创建1号进程
本文介绍arm linux启动的第二部分,C语言编写,主要讲述start_kernel到1号进程的创建。主要讲述大概过程,以后再对子函数进行讲解。 一、start_kernel   start_kernel位于init/main.c,主要完成linux一些子系统的初始化。    1)smp_setup_processor_id() 单CPU位为空。    2)lock_kernel() 锁CPU,linux是支持抢占的,多CPU时调用这个函数防止其他CPU抢占。     3)tick_init() 时间相关初始化     4)boot_cpu_init() 确定有多少个CPU可用。现在以单C
[单片机]
嵌入式Linux系统中I2C总线设备的驱动设计
引言 I2C总线是PHILIPS公司推出的两线式串行总线,用于连接微控制器及其外围设备,具有简单、高效等特点。由于其接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片引脚的数量,降低了互联成本,特别适用于嵌入式产品。 而Linux系统具有开源、免费、网上资源丰富等优点,目前已成为嵌入式系统的主流选择。因此如何在嵌入式Linux系统中实现I2C功能成为实际开发中的问题。 I2C总线 I2C 总线通过串行数据SDA 和串行时钟SCL线在连接到总线的器件间传递信息,每个器件都有一个唯一的地址识别。根据数据传输时的功能不同,把器件分为主机和从机。主机是初始化总线的数据传输并产生允许传输的时钟信
[单片机]
嵌入式<font color='red'>Linux</font>系统中I2C总线设备的驱动设计
第005课 linux进阶命令(文件查找,文件解压操作详解)
001节_Linux进阶命令__find命令 我们在Windows中搜索文件,一般查找文件需要传入两个条件: 1)在那些目录中查找; 2)查找的内容; 在Linux中,查找文件的也需要这两个条件,不同于Windows使用搜索框查找,Linux中使用 find 命令查找文件。 find 命令: *目的:查找符合条件的文件 *格式: find 目录名 选项 查找条件 举例1: find /work/001_linux_basic/dira/ -name test1.txt 说明: a)/work/001_linux_basic/dira/指明了查找的路径 b)-name表明以名字来查找文件
[单片机]
恰当选择嵌入式Linux环境下的GUI系统
在嵌入式 环境底下,GUI系统的整体构架跟PC Desktop相去不远,例如绘图函数库、字型、事件处理等也都是嵌入式GUI系统所要面临的。但是嵌入式系统本身由于体积小、资源少的特点,所以在整体设计上必须较为严谨,必须考虑的条件更多,有时很像又回到了Dos下编制程序的年代,对于软件所占的存储量有时可以说是锱铢必较。 Unix环境下的图形视窗标准为X Window System(以下简称X标准),Linux是类Unix系统,所以顶层运行的GUI系统是兼容X标准的XFree86系统。X标准大致可以划分X Server、Graphic Library(底层绘图函数库)、Toolkits、Window Manager、Internati
[嵌入式]
Tiny6410下的第一个Linux驱动程序
Linux系统环境是照着友善之臂的教程搭建的 //Hello World驱动程序源文件 1 #include linux/miscdevice.h 2 #include linux/delay.h 3 #include asm/irq.h 4 5 #include mach/hardware.h 6 #include linux/kernel.h 7 #include linux/module.h 8 #include linux/init.h 9 #include linux/mm.h 10 #include linux/fs.h 11 #include linux/types.h 12 #incl
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved