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


三:傳輸過程的實現
說到傳輸過程,我們必須要從URB開始說起,這個結構的就好比是網絡子系統中的skb,好比是I/O中的bio.USB系統的信息傳輸就是打成URB結構,然后再過行傳送的.
URB的全稱叫USB request block.下面從它的接口說起.
3.1:URB的相關接口
1:URB的創建
URB的創建是由usb_alloc_urb()完成的.這個函數會完成URB內存的分配和基本成員的初始化工作.代碼如下:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
{
   struct urb *urb;

    urb = kmalloc(sizeof(struct urb) +
        iso_packets * sizeof(struct usb_iso_packet_descriptor),
        mem_flags);
    if (!urb) {
        err("alloc_urb: kmalloc failed");
        return NULL;
    }
    usb_init_urb(urb);
    return urb;
}
這個函數有兩個參數,一個是iso_packets.僅僅用於ISO傳輸.表示ISO數據包個數,如果用於其它類型的傳輸,此參數為0.另一個是mem_flags.是分配內存的參數.
Usb_init_urb()如下:
void usb_init_urb(struct urb *urb)
{
    if (urb) {
        memset(urb, 0, sizeof(*urb));
        kref_init(&urb->kref);
        INIT_LIST_HEAD(&urb->anchor_list);
    }
}
由此可以看到,它的初始化只是初始化了引用計數和ahchor_list鏈表.這個鏈表在URB被鎖定的時候會用到.

2:URB的初始化
USB2.0 spec中定義了四種傳輸,為別為ISO,INTER,BULK,CONTORL.linux kernel為INTER,BULK,CONTORL的URB初始化提供了一些API,ISO的傳輸只能夠手動去初始化.這些API如下:
static inline void usb_fill_control_urb(struct urb *urb,
                    struct usb_device *dev,
                    unsigned int pipe,
                    unsigned char *setup_packet,
                    void *transfer_buffer,
                    int buffer_length,
                    usb_complete_t complete_fn,
                    void *context)
static inline void usb_fill_bulk_urb(struct urb *urb,
                     struct usb_device *dev,
                     unsigned int pipe,
                     void *transfer_buffer,
                     int buffer_length,
                     usb_complete_t complete_fn,
                     void *context)
static inline void usb_fill_int_urb(struct urb *urb,
                    struct usb_device *dev,
                    unsigned int pipe,
                    void *transfer_buffer,
                    int buffer_length,
                    usb_complete_t complete_fn,
                    void *context,
                    int interval)
分別用來填充CONTORL,BULK,INT類型的URB.
觀察他們的函數原型,發現有很多相的的參數.先對這些參數做一下解釋:
Urb:是要初始化的urb
Dev:表示消息要被發送到的USB設備
Pipe:表示消息被發送到的端點
transfer_buffer:表示發送數據的緩沖區
length:就是transfer_buffer所表示的緩沖區大小
context:完成處理函數的上下文
complete_fn:傳輸完了之后要調用的函數.
usb_fill_control_urb()的setup_packet:即將被發送到端點的設備數據包
usb_fill_int_urb()中的interval:這個urb應該被調度的間隔.
函數的實際都是差不多的.以usb_fill_control_urb()為例:
static inline void usb_fill_control_urb(struct urb *urb,
                    struct usb_device *dev,
                    unsigned int pipe,
                    unsigned char *setup_packet,
                    void *transfer_buffer,
                    int buffer_length,
                    usb_complete_t complete_fn,
                    void *context)
{
    urb->dev = dev;
    urb->pipe = pipe;
    urb->setup_packet = setup_packet;
    urb->transfer_buffer = transfer_buffer;
    urb->transfer_buffer_length = buffer_length;
    urb->complete = complete_fn;
    urb->context = context;
}
如上所示,只是將函數的參數賦值給了URB相關的成員而已.
另外,關於ISO的URB初始化雖然沒有可以調用的API,但它的初始化也很簡單,對應就是填充幾個成員而已.
另外,對於pipe的參數.有一系列輔助的宏.如下示:
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint)   \
    ((PIPE_CONTROL 
#define usb_rcvctrlpipe(dev,endpoint)   \
    ((PIPE_CONTROL 
#define usb_sndisocpipe(dev,endpoint)   \
    ((PIPE_ISOCHRONOUS 
#define usb_rcvisocpipe(dev,endpoint)   \
    ((PIPE_ISOCHRONOUS 
#define usb_sndbulkpipe(dev,endpoint)   \
    ((PIPE_BULK 
#define usb_rcvbulkpipe(dev,endpoint)   \
    ((PIPE_BULK 
#define usb_sndintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT 
#define usb_rcvintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT 
這個宏都是根據usb2.0 spec的規范來設計的.

3:提交URB
提交urb的接口是usb_submit_urb().代碼如下:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
    int             xfertype, max;
    struct usb_device       *dev;
    struct usb_host_endpoint    *ep;
    int             is_out;

    if (!urb || urb->hcpriv || !urb->complete)
        return -EINVAL;
    dev = urb->dev;
    if ((!dev) || (dev->state 
        return -ENODEV;

    /* For now, get the endpoint from the pipe.  Eventually drivers
     * will be required to set urb->ep directly and we will eliminate
     * urb->pipe.
     */

    //取得要傳輸的端口.對端地址是由方向+dev address+port number組成的
    ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out)
            [usb_pipeendpoint(urb->pipe)];
    if (!ep)
        return -ENOENT;

    urb->ep = ep;
    urb->status = -EINPROGRESS;
    urb->actual_length = 0;

    /* Lots of sanity checks, so HCDs can rely on clean data
     * and don't need to duplicate tests
     */
     //取得ep的傳輸類型
    xfertype = usb_endpoint_type(&ep->desc);
    //如果是控制傳輸.端點0默認是控制傳輸
    if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
        //控制傳輸的urb如果沒有setup_packet是非法的
        struct usb_ctrlrequest *setup =
                (struct usb_ctrlrequest *) urb->setup_packet;

        if (!setup)
            return -ENOEXEC;
        //判斷是否是out方向的傳輸
        is_out = !(setup->bRequestType & USB_DIR_IN) ||
                !setup->wLength;
    } else {
        //如果不是控制傳輸,在端點描述符的bEndportAddress的bit7 包含有端點的傳輸方向
        is_out = usb_endpoint_dir_out(&ep->desc);
    }

    /* Cache the direction for later use */
    //根據傳輸方向.置urb->transfer_flags的方向位
    urb->transfer_flags = (urb->transfer_flags & ~URB_DIR_MASK) |
            (is_out ? URB_DIR_OUT : URB_DIR_IN);

    //根據usb2.0 spec.除控制傳輸外的其它傳輸只有在config狀態的時候才能進行
    if (xfertype != USB_ENDPOINT_XFER_CONTROL &&
            dev->state 
        return -ENODEV;

    //傳送/接收的最大字節.如果這個最大巧若拙字節還要小於0,那就是非法的
    max = le16_to_cpu(ep->desc.wMaxPacketSize);
    if (max 
        dev_dbg(&dev->dev,
            "bogus endpoint ep%d%s in %s (bad maxpacket %d)\n",
            usb_endpoint_num(&ep->desc), is_out ? "out" : "in",
            __FUNCTION__, max);
        return -EMSGSIZE;
    }

    /* periodic transfers limit size per frame/uframe,
     * but drivers only control those sizes for ISO.
     * while we're checking, initialize return status.
     */
     //如果是實時傳輸
    if (xfertype == USB_ENDPOINT_XFER_ISOC) {
        int n, len;

        /* "high bandwidth" mode, 1-3 packets/uframe? */
        //如果是高速傳輸.則要修正它的MAX值
        //高速傳輸時, 一個微幀內可以修輸多個數據.bit 11~bit12用來表示一個微幀內
        //傳輸包的個數.
        //在USB1.1中是不支持HIGH的
        if (dev->speed == USB_SPEED_HIGH) {
            int mult = 1 + ((max >> 11) & 0x03);
            max &= 0x07ff;
            max *= mult;
        }

        //實現傳輸的數據包數目不能小於等於0
        if (urb->number_of_packets 
            return -EINVAL;
        //urb->number_of_packets: 實時數據包個數.每個實時數據包對應urb->iso_frame_desc[]中的一項
        for (n = 0; n number_of_packets; n++) {
            len = urb->iso_frame_desc[n].length;
            if (len  max)
                return -EMSGSIZE;
            urb->iso_frame_desc[n].status = -EXDEV;
            urb->iso_frame_desc[n].actual_length = 0;
        }
    }

    /* the I/O buffer must be mapped/unmapped, except when length=0 */
    //如果要傳輸的緩存區大小小於0.非法
    if (urb->transfer_buffer_length 
        return -EMSGSIZE;

#ifdef DEBUG
    /* stuff that drivers shouldn't do, but which shouldn't
     * cause problems in HCDs if they get it wrong.
     */
    {
    unsigned int    orig_flags = urb->transfer_flags;
    unsigned int    allowed;

    /* enforce simple/standard policy */
    allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP |
            URB_NO_INTERRUPT | URB_DIR_MASK | URB_FREE_BUFFER);
    switch (xfertype) {
    case USB_ENDPOINT_XFER_BULK:
        if (is_out)
            allowed |= URB_ZERO_PACKET;
        /* FALLTHROUGH */
    case USB_ENDPOINT_XFER_CONTROL:
        allowed |= URB_NO_FSBR; /* only affects UHCI */
        /* FALLTHROUGH */
    default:            /* all non-iso endpoints */
        if (!is_out)
            allowed |= URB_SHORT_NOT_OK;
        break;
    case USB_ENDPOINT_XFER_ISOC:
        allowed |= URB_ISO_ASAP;
        break;
    }
    urb->transfer_flags &= allowed;

    /* fail if submitter gave bogus flags */
    if (urb->transfer_flags != orig_flags) {
        err("BOGUS urb flags, %x --> %x",
            orig_flags, urb->transfer_flags);
        return -EINVAL;
    }
    }
#endif
    /*
     * Force periodic transfer intervals to be legal values that are
     * a power of two (so HCDs don't need to).
     *
     * FIXME want bus->{intr,iso}_sched_horizon values here.  Each HC
     * supports different values... this uses EHCI/UHCI defaults (and
     * EHCI can use smaller non-default values).
     */

//關於實時傳輸和中斷傳輸的interval處理
    switch (xfertype) {
    case USB_ENDPOINT_XFER_ISOC:
    case USB_ENDPOINT_XFER_INT:
        /* too small? */
        //interval不能小於或等於0
        if (urb->interval 
            return -EINVAL;
        /* too big? */
        switch (dev->speed) {
        case USB_SPEED_HIGH:    /* units are microframes */
            /* NOTE usb handles 2^15 */
            if (urb->interval > (1024 * 8))
                urb->interval = 1024 * 8;
            max = 1024 * 8;
            break;
        case USB_SPEED_FULL:    /* units are frames/msec */
        case USB_SPEED_LOW:
            if (xfertype == USB_ENDPOINT_XFER_INT) {
                if (urb->interval > 255)
                    return -EINVAL;
                /* NOTE ohci only handles up to 32 */
                max = 128;
            } else {
                if (urb->interval > 1024)
                    urb->interval = 1024;
                /* NOTE usb and ohci handle up to 2^15 */
                max = 1024;
            }
            break;
        default:
            return -EINVAL;
        }
        /* Round down to a power of 2, no more than max */
        urb->interval = min(max, 1 interval));
    }

    return usb_hcd_submit_urb(urb, mem_flags);
}
這段代碼雖然很長,但邏輯很清楚.對照代碼中的注釋理解應該是沒有問題的.在這里要注意,UHCI是屬於USB1.1的,它不支持HIGH傳輸.
對URB進行一系列處理之后,就會將urb丟給hcd進行處理了.usb_hcd_submit_urb()代碼如下:
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
    int         status;
    //從usb_bus的地址取得usb_hcd的地址
    struct usb_hcd      *hcd = bus_to_hcd(urb->dev->bus);

    /* increment urb's reference count as part of giving it to the HCD
     * (which will control it).  HCD guarantees that it either returns
     * an error or calls giveback(), but not both.
     */
     //增加有關的引用計數,usbmon*系列的函數是編譯選擇的.忽略
    usb_get_urb(urb);
    atomic_inc(&urb->use_count);
    atomic_inc(&urb->dev->urbnum);
    usbmon_urb_submit(&hcd->self, urb);

    /* NOTE requirements on root-hub callers (usbfs and the hub
     * driver, for now):  URBs' urb->transfer_buffer must be
     * valid and usb_buffer_{sync,unmap}() not be needed, since
     * they could clobber root hub response data.  Also, control
     * URBs must be submitted in process context with interrupts
     * enabled.
     */
     //對傳輸的緩存區進行DMA映射
    status = map_urb_for_dma(hcd, urb, mem_flags);
    //出現錯誤,返回
    if (unlikely(status)) {
        usbmon_urb_submit_error(&hcd->self, urb, status);
        goto error;
    }

    //如果是root hub
    if (is_root_hub(urb->dev))
        status = rh_urb_enqueue(hcd, urb);
    else
        //如果是一般的設備 
        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);

    if (unlikely(status)) {
        usbmon_urb_submit_error(&hcd->self, urb, status);
        unmap_urb_for_dma(hcd, urb);
error:
        urb->hcpriv = NULL;
        INIT_LIST_HEAD(&urb->urb_list);
        atomic_dec(&urb->use_count);
        atomic_dec(&urb->dev->urbnum);
        if (urb->reject)
            wake_up(&usb_kill_urb_queue);
        usb_put_urb(urb);
    }
    return status;
}
在這里函數里要注意到,urb->transfer_buffer是一個虛擬地址,用於UHCI的時候,必須要將其映射物理地址,以供設備使用.這 也就是map_urb_for_dma()要完成的工作. map_urb_for_dma()函數比較簡單,這里就不做詳細分析.
可能有人會有這樣的疑惑,對於root hub的情況,為什么不用對傳輸緩存區進行DMA映射呢?
在后面的處理中我們可以看到,其實對於root hub ,它不需要進行實際的物理傳輸,linux按照spec上的規定,將它靜態放置在內存中,在進行相關操作的時候,只要直接copy過去就可以了.
其次,要注意,這個函數不能用於中斷上下文,因為該函數是同步的,會引起睡眠.
在這里,我們看到,流程最終轉入到了下面的代碼片段中:
    //如果是root hub
    if (is_root_hub(urb->dev))
        status = rh_urb_enqueue(hcd, urb);
    else
        //如果是一般的設備 
        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
下面,就分情況來剖析,各種傳輸到底是怎么完成的.


免責聲明!

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



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