linux usb 驅動詳解 二

USB 設備驅動代碼通過urb和所有的 USB 設備通訊。urb用 struct urb 結構描述(include/linux/usb.h )。
 urb 以一種異步的方式同一個特定USB設備的特定端點發送或接受數據。一個 USB 設備驅動可根據驅動的需要,分配多個 urb 給一個端點或重用單個 urb 給多個不同的端點。設備中的每個端點都處理一個 urb 隊列, 所以多個 urb 可在隊列清空之前被髮送到相同的端點。

 一個 urb 的典型生命循環如下:
 (1)被創建;
 (2)被分配給一個特定 USB 設備的特定端點;
 (3)被提交給 USB 核心;
 (4)被 USB 核心提交給特定設備的特定 USB 主機控制器驅動;
 (5)被 USB 主機控制器驅動處理, 並傳送到設備;
 (6)以上操作完成後,USB主機控制器驅動通知 USB 設備驅動。
 
 urb 也可被提交它的驅動在任何時間取消;如果設備被移除,urb 可以被USB核心取消。urb 被動態創建幷包含一個內部引用計數,使它們可以在最後一個用戶釋放它們時被自動釋放。

struct urb
 

struct urb {
    /* private: usb core and host controller only fields in the urb */
    struct kref kref;        /* URB引用計數 */
    void *hcpriv;            /* host控制器的私有數據 */
    atomic_t use_count;        /* 當前提交計數 */
    atomic_t reject;        /* 提交失敗計數 */
    int unlinked;            /* 連接失敗代碼 */

    /* public: documented fields in the urb that can be used by drivers */
    struct list_head urb_list;    /* list head for use by the urb's
                     * current owner */

    struct list_head anchor_list;    /* the URB may be anchored */
    struct usb_anchor *anchor;
    struct usb_device *dev;     /* 指向這個 urb 要發送的目標 struct usb_device 的指針,這個變量必須在這個 urb 被髮送到 USB 核心之前被 USB 驅動初始化.*/
    struct usb_host_endpoint *ep;    /* (internal) pointer to endpoint */
    unsigned int pipe;        /* 這個 urb 所要發送到的特定struct usb_device的端點消息,這個變量必須在這個 urb 被髮送到 USB 核心之前被 USB 驅動初始化.必須由下面的函數生成*/
    int status;            /* 當 urb開始由 USB 核心處理或處理結束, 這個變量被設置爲 urb 的當前狀態. USB 驅動可安全訪問這個變量的唯一時間是在 urb 結束處理例程函數中. 這個限制是爲防止競態. 對於等時 urb, 在這個變量中成功值(0)只表示這個 urb 是否已被去鏈. 爲獲得等時 urb 的詳細狀態, 應當檢查 iso_frame_desc 變量. */
    unsigned int transfer_flags;    /* 傳輸設置*/
    void *transfer_buffer;        /* 指向用於發送數據到設備(OUT urb)或者從設備接收數據(IN urb)的緩衝區指針。爲了主機控制器驅動正確訪問這個緩衝, 它必須使用 kmalloc 調用來創建, 不是在堆棧或者靜態內存中。 對控制端點, 這個緩衝區用於數據中轉*/
    dma_addr_t transfer_dma;    /* 用於以 DMA 方式傳送數據到 USB 設備的緩衝區*/
    int transfer_buffer_length;    /* transfer_buffer 或者 transfer_dma 變量指向的緩衝區大小。如果這是 0, 傳送緩衝沒有被 USB 核心所使用。對於一個 OUT 端點, 如果這個端點大小比這個變量指定的值小, 對這個 USB 設備的傳輸將被分成更小的塊,以正確地傳送數據。這種大的傳送以連續的 USB 幀進行。在一個 urb 中提交一個大塊數據, 並且使 USB 主機控制器去劃分爲更小的塊, 比以連續地順序發送小緩衝的速度快得多*/
    int actual_length;        /* 當這個 urb 完成後, 該變量被設置爲這個 urb (對於 OUT urb)發送或(對於 IN urb)接受數據的真實長度.對於 IN urb, 必須是用此變量而非 transfer_buffer_length , 因爲接收的數據可能比整個緩衝小*/
    unsigned char *setup_packet;    /* 指向控制urb的設置數據包指針.它在傳送緩衝中的數據之前被傳送(用於控制 urb)*/
    dma_addr_t setup_dma;        /* 控制 urb 用於設置數據包的 DMA 緩衝區地址,它在傳送普通緩衝區中的數據之前被傳送(用於控制 urb)*/
    int start_frame;        /* 設置或返回初始的幀數量(用於等時urb) */
    int number_of_packets;        /* 指定urb所處理的等時傳輸緩衝區的數量(用於等時urb,在urb被髮送到USB核心前,必須設置) */
    int interval;            /*urb 被輪詢的時間間隔. 僅對中斷或等時 urb 有效. 這個值的單位依據設備速度而不同. 對於低速和高速的設備, 單位是幀, 它等同於毫秒. 對於其他設備, 單位是微幀, 等同於 1/8 毫秒. 在 urb被髮送到 USB 核心之前,此值必須設置.*/
    int error_count;        /* 等時urb的錯誤計數,由USB核心設置 */
    void *context;            /* 指向一個可以被USB驅動模塊設置的數據塊. 當 urb 被返回到驅動時,可在結束處理例程中使用. */
    usb_complete_t complete;    /* 結束處理例程函數指針, 當 urb 被完全傳送或發生錯誤,它將被 USB 核心調用. 此函數檢查這個 urb, 並決定釋放它或重新提交給另一個傳輸中*/
    struct usb_iso_packet_descriptor iso_frame_desc[0];
                    /* (僅用於等時urb)usb_iso_packet_descriptor結構體允許單個urb一次定義許多等時傳輸,它用於收集每個單獨的傳輸狀態*/
    };    

struct usb_iso_packet_descriptor {
    unsigned int offset;        /* 該數據包的數據在傳輸緩衝區中的偏移量(第一個字節爲0) */
    unsigned int length;        /* 該數據包的傳輸緩衝區大小 */
    unsigned int actual_length;    /* 等時數據包接收到傳輸緩衝區中的數據長度 */
    int status;            /* 該數據包的單個等時傳輸狀態。它可以把相同的返回值作爲主struct urb 結構體的狀態變量 */
};

typedef void (*usb_complete_t)(struct urb *);

上述結構體中unsigned int pipe;的生成函數(define):

    static inline unsigned int __create_pipe(struct usb_device *dev,
        unsigned int endpoint)
{
    return (dev->devnum << 8) | (endpoint << 15);
}

/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint)    \
    ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev,endpoint)    \
    ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev,endpoint)    \
    ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev,endpoint)    \
    ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev,endpoint)    \
    ((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev,endpoint)    \
    ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
//snd:OUT rcv:IN  ctrl:控制  isoc:等時  bulk:批量 int:中斷

上述結構體中unsigned int transfer_flags;的值域:

/*
 * urb->transfer_flags:
 *
 * Note: URB_DIR_IN/OUT is automatically set in usb_submit_urb().
 */

#define URB_SHORT_NOT_OK    0x0001    /* 置位時,任何在 IN 端點上發生的簡短讀取, 被 USB 核心當作錯誤. 僅對從 USB 設備讀取的 urb 有用 */
#define URB_ISO_ASAP        0x0002    
/* 若爲等時 urb , 驅動想調度這個 urb 時,可置位該位, 只要帶寬允許且想在此時設置 urb 中的 start_frame 變量. 若沒有置位,則驅動必須指定 start_frame 值,且傳輸如果不能在當

時啓動的話,必須能夠正確恢復 */

#define URB_NO_TRANSFER_DMA_MAP    0x0004    /* 當 urb 包含要被髮送的 DMA 緩衝時,應被置位.USB 核心使用就會使用 transfer_dma 變量指向的緩衝, 而不是被 transfer_buffer 變量指向的緩衝. */
#define URB_NO_SETUP_DMA_MAP    0x0008    /* 和 URB_NO_TRANSFER_DMA_MAP 類似, 這個位用來控制 DMA 緩衝已經建立的 urb. 如果它被置位, USB 核心使用 setup_dma 變量而不是 setup_packet 變量指向的緩衝. */
#define URB_NO_FSBR        0x0020    /* 僅 UHCI USB 主機控制器驅動使用, 並且告訴它不要試圖使用前端總線回收( Front Side Bus Reclamation) 邏輯. 這個位通常應不設置, 因爲有 UHCI 主機控制器的機器會增加 CPU 負擔, 且PCI 總線會忙於等待設置了這個位的 urb */
#define URB_ZERO_PACKET        0x0040    /* 如果置位, 批量 OUT urb 通過發送不包含數據的短包來結束, 這時數據對齊到一個端點數據包邊界. 這被一些掉線的 USB 設備需要該位才能正確工作 */
#define URB_NO_INTERRUPT    0x0080    /* 如果置位, 當 urb 結束時硬件可能不產生一箇中斷. 該位應當小心使用並且只在多個 urb 排隊到相同端點時才使用. USB 核心函數使用該位進行 DMA 緩衝傳送. */
#define URB_FREE_BUFFER        0x0100    /* Free transfer buffer with the URB */

#define URB_DIR_IN        0x0200    /* Transfer from device to host */
#define URB_DIR_OUT        0
#define URB_DIR_MASK        URB_DIR_IN

上述結構體中int status;的常用值(in include/asm-generic/errno.h  and errno_base.h) :

// 0     表示 urb 傳送成功*/

//以下各個定義在使用時爲負值
#define    ENOENT         2    /* urb 被 usb_kill_urb 停止 */
#define    ECONNRESET    104    /* urb 被 usb_unlink_urb 去鏈, 且 transfer_flags 被設爲 URB_ASYNC_UNLINK */
#define    EINPROGRESS    115    /* urb 仍在 USB 主機控制器處理 */
#define    EPROTO        71    /* urb 發生錯誤: 在傳送中發生bitstuff 錯誤或硬件沒有及時收到響應幀 */
#define    EILSEQ        84    /* urb 傳送中出現 CRC 較驗錯 */
#define    EPIPE        32    /* 端點被停止. 若此端點不是控制端點, 則這個錯誤可通過函數 usb_clear_halt 清除 */
#define    ECOMM        70    /* 數據傳輸時的接收速度快於寫入系統內存的速度. 此錯誤僅出現在 IN urb */
#define    ENOSR        63    /* 從系統內存中獲取數據的速度趕不上USB 數據傳送速度,此錯誤僅出現在 OUT urb. */
#define    EOVERFLOW    75    /* urb 發生"babble"(串擾)錯誤:端點接受的數據大於端點的最大數據包大小 */
#define    EREMOTEIO    181    /* 當 urb 的 transfer_flags 變量的 URB_SHORT_NOT_OK 標誌被設置, urb 請求的數據沒有完整地收到 */
#define    ENODEV        19    /* USB 設備從系統中拔出 */
#define    EXDEV        18    /* 僅發生在等時 urb 中, 表示傳送部分完成. 爲了確定所傳輸的內容, 驅動必須看單獨的幀狀態. */
#define    EINVAL        22    /* 如果urb的一個參數設置錯誤或在提交 urb 給 USB 核心的 usb_submit_urb 調用中, 有不正確的參數,則可能發生次錯誤 */
#define    ESHUTDOWN    108    /* USB 主機控制器驅動有嚴重錯誤,它已被禁止, 或者設備從系統中拔出。且這個urb 在設備被移除後被提交. 它也可能發生在 urb 被提交給設備時,設備的配置已被改變*/


創建和註銷 urb

struct urb 結構不能靜態創建,必須使用 usb_alloc_urb 函數創建. 函數原型:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
//int iso_packets : urb 包含等時數據包的數目。如果不使用等時urb,則爲0
//gfp_t mem_flags : 與傳遞給 kmalloc 函數調用來從內核分配內存的標誌類型相同

//返回值          : 如果成功分配足夠內存給 urb , 返回值爲指向 urb 的指針. 如果返回值是 NULL, 則在 USB 核心中發生了錯誤, 且驅動需要進行適當清理

如果驅動已經對 urb 使用完畢, 必須調用 usb_free_urb 函數,釋放urb。函數原型:

void usb_free_urb(struct urb *urb);
//struct urb *urb : 要釋放的 struct urb 指針

根據內核源碼,可以通過自己kmalloc一個空間來創建urb,然後必須使用

void usb_init_urb(struct urb *urb);

進行初始化後纔可以繼續使用。

其實usb_alloc_urb函數就是這樣實現的,所以我當然不推薦這種自找麻煩的做法。

初始化 urb

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);

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_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);


//struct urb *urb :指向要被初始化的 urb 的指針
//struct usb_device *dev :指向 urb 要發送到的 USB 設備.
//unsigned int pipe : urb 要被髮送到的 USB 設備的特定端點. 必須使用前面提過的 usb_******pipe 函數創建
//void *transfer_buffer :指向外發數據或接收數據的緩衝區的指針.注意:不能是靜態緩衝,必須使用 kmalloc 來創建.
//int buffer_length :transfer_buffer 指針指向的緩衝區的大小
//usb_complete_t complete :指向 urb 結束處理例程函數指針
//void *context :指向一個小數據塊的指針, 被添加到 urb 結構中,以便被結束處理例程函數獲取使用.
//int interval :中斷 urb 被調度的間隔.
//函數不設置 urb 中的 transfer_flags 變量, 因此對這個成員的修改必須由驅動手動完成

/*等時 urb 沒有初始化函數,必須手動初始化,以下爲一個例子*/
urb->dev = dev;
urb->context = uvd;
urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
urb->interval = 1;
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = cam->sts_buf[i];
urb->complete = konicawc_isoc_irq;
urb->number_of_packets = FRAMES_PER_DESC;
urb->transfer_buffer_length = FRAMES_PER_DESC;
for (j=0; j < FRAMES_PER_DESC; j++) {
        urb->iso_frame_desc[j].offset = j;
        urb->iso_frame_desc[j].length = 1;
}

其實那三個初始化函數只是簡單的包裝,是inline函數。所以其實和等時的urb手動初始化沒什麼大的區別。



 

提交 urb

一旦 urb 被正確地創建並初始化, 它就可以提交給 USB 核心以發送出到 USB 設備. 這通過調用函數 usb_submit_urb 實現:

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
//struct urb *urb :指向被提交的 urb 的指針
//gfp_t mem_flags :使用傳遞給 kmalloc 調用同樣的參數, 用來告訴 USB 核心如何及時分配內存緩衝

/*因爲函數 usb_submit_urb 可被在任何時候被調用(包括從一箇中斷上下文), mem_flags 變量必須正確設置. 根據 usb_submit_urb 被調用的時間,只有 3 個有效值可用:
GFP_ATOMIC
只要滿足以下條件,就應當使用此值:
1.調用者處於一個 urb 結束處理例程,中斷處理例程,底半部,tasklet或者一個定時器回調函數.
2.調用者持有自旋鎖或者讀寫鎖. 注意如果正持有一個信號量, 這個值不必要.
3.current->state 不是 TASK_RUNNING. 除非驅動已自己改變 current 狀態,否則狀態應該一直是 TASK_RUNNING .

GFP_NOIO
驅動處於塊 I/O 處理過程中. 它還應當用在所有的存儲類型的錯誤處理過程中.

GFP_KERNEL
所有不屬於之前提到的其他情況
*/

在 urb 被成功提交給 USB 核心之後, 直到結束處理例程函數被調用前,都不能訪問 urb 結構的任何成員.

urb結束處理例程

如果 usb_submit_urb 被成功調用, 並把對 urb 的控制權傳遞給 USB 核心, 函數返回 0; 否則返回一個負的錯誤代碼. 如果函數調用成功, 當 urb 被結束的時候結束處理例程會被調用一次.當這個函數被調用時, USB 核心就完成了這個urb, 並將它的控制權返回給設備驅動.
 
只有 3 種結束urb並調用結束處理例程的情況:
(1)urb 被成功發送給設備, 且設備返回正確的確認.如果這樣, urb 中的status變量被設置爲 0.
(2)發生錯誤, 錯誤值記錄在 urb 結構中的 status 變量.
(3)urb 從 USB 核心unlink. 這發生在要麼當驅動通過調用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一個已提交的 urb,或者在一個 urb 已經被提交給它時設備從系統中去除.

取消 urb

使用以下函數停止一個已經提交給 USB 核心的 urb:

void usb_kill_urb(struct urb *urb)
int usb_unlink_urb(struct urb *urb);

如果調用usb_kill_urb函數,則 urb 的生命週期將被終止. 這通常在設備從系統移除時,在斷開回調函數(disconnect callback)中調用.
對一些驅動, 應當調用 usb_unlink_urb 函數來使 USB 核心停止 urb. 這個函數不會等待 urb 完全停止才返回. 這對於在中斷處理例程中或者持有一個自旋鎖時去停止 urb 是很有用的, 因爲等待一個 urb 完全停止需要 USB 核心有使調用進程休眠的能力(wait_event()函數).

發佈了119 篇原創文章 · 獲贊 11 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章