閱讀783 返回首頁    go 阿裏雲 go 技術社區[雲棲]


VxWorks下USB驅動總結2

3:USBD驅動詳解

這一部分將要描述USBD(USB Host Driver)的典型應用。例如初始化,client注冊,動態連接注冊,設備配置,數據傳輸,同時還探討了USBD內部設計的關鍵特性。這部分是VxWorks下USB驅動的核心。

 

1 初始化USBD:分為兩步

(1)必須至少調用一次函數usbdInitialize()。在一個給定的係統中,usbdlnifialize()初始化內部USBD數據結構,並依次調用其它USB驅動棧模塊的入口。usbdinitialize()可以在啟動時調用一次,也可以對每一個設備各調用一次。USBD 自己記錄了調用usbdInitialize()(‘+’)和usbdShutDown()(‘-’)的次數。隻有大於等於1時才是真正初始化了,而等於0是關閉了。

 

...

/* Initialize the USBD. */

if (usbdInitialize () != OK)

return ERROR;

...

...

/* application code. */

...

...

/* Shut down the USBD. Note: we only execute this code if the return

* code from usbdInitialize() was OK. Note also that there’s really no

* reason to check the result of usbdShutdown().

*/

usbdShutdown ();

 

 

(2)用USBD 的lisbdHedAttaeh()函數來把至少一個HCD連接到USBD上。這一過程既可以在VxWorks啟動時,也可以在運行時把HCD 連接到USBD 上去。後一種機製可以支持“熱插拔”,而不用象前一種那樣需要重新啟動。

 

2 HCD的連接(attaching)與斷開(detaching)

當HCD連接到USBD 時,調用者為usbdHedattaeh函數傳遞HCD執行入口(表HCD_EXEC_FUNC)和HCD連接參數(HCD attach parameter)。USBD用HCD FNC ATYACH 服務請求依次激活HCD的執行入口,傳遞同樣的HCD attach參數。

需要強調雖然可以改變用HCD定義的參數,但是最好不應該有所改變。對於WindRiver提供的UHCI和OHCI的HCD,HCI attach參數是一個指向結構PCI_CFG_HEADER (定義在pciConstants.h) 的指針。

 

該結構用UHCI和OHCI主控製器的PCI配置頭來初始化,而HCD用這個結構中的信息來定位,管理特定的主控製器。典型的,調用者用usbPeiClassFind ()和usbPciConfigHeaderGet()來得到想要的主控製器的PCI配置頭- 這兩個函數定義在usbPciLib 中(stubUsbarchPciLib.h中)。如果有UHCI或OHCI要連接到USBD,就要調用這些函數來獲得每一個主控製器的PCI_CFG_HEADER。然後利用usbdHedAttaeh來激活已鑒別出的每一個主控製器。

 

注意:底層BSP可能不支持USB的HCD斷開,因為當中斷向量表重新使能時,如果還應用的是過期的向量表,會導致錯誤。

 

//掛接過程

UINT8 busNo;

UINT8 deviceNo;

UINT8 funcNo;

PCI_CFG_HEADER pciCfgHdr; /* PCI_CFG_HEADER defined in

pciConstants.h */

GENERIC_HANDLE uhciAttachToken; /* GENERIC_HANDLE defined in

usbHandleLib.h */

/* Locate the first (0th) UHCI controller. UHCI_CLASS, etc., are defined

* in usbUhci.h. The functions usbPciClassFind() and

* usbPciConfigHeaderGet() are exported by usbPciLib.

*/

if (!usbPciClassFind (UHCI_CLASS, UHCI_SUBCLASS, UHCI_PGMIF, 0,

&busNo, &deviceNo, &funcNo))

{

/* No UHCI controller was found. */

return ERROR;

}

usbPciConfigHeaderGet (busNo, deviceNo, funcNo, &pciCfgHdr);

/* Attach the UHCI HCD to the USBD. The function usbHcdUhciExec() is

* exported by usbHcdUhciLib.

*/

if (usbdHcdAttach (usbHcdUhciExec, &pciCfgHdr, &uhciAttachToken) != OK)

{

/* USBD failed to attach to UHCI HCD. */

return ERROR;

}

/* Attachment is complete. */

 

//取消掛接

/* Detach the UHCI HCD from the HCD. */

usbdHcdDetach (uhciAttachToken);

/* Detach is complete! */

  

3 啟動順序

必須在所有USBD函數前執行函數usbdInitialize()。存在以下兩種調用方式:

  (1)傳統的“啟動”初始化。執行順序與其意義如下:

  a.usbdInitialize();

  b.usbdPciClassFind():定位一個USB主控製器;

  c.usbdPeiConfigHeaderGet():讀USB主控製器配置頭;

  d.usbdHedAttaeh():連接HCD,將其作為特定的主控製器:

  e.調用USB class driver初始化入口點;

  f.USB class driver調用usbdlnitialize()。

 

  (2)“熱插拔”調用。執行順序與其意義如下:

  Boot Code裏調用:

  a.USB class driver初始化入口點;

  b.USB class driver調用usbdlnitialize();

 

  Hot-Swap code調用:
        c.Hot-Swap 鑒別USB主控製器的連接或斷開;

  d.Usbdlnitialize();

  e.UsbdPciConfigHeaderGet():讀USB主控製器配置頭;

  f.UsbdHedAttaeh():連接HCD,將其作為特定的主控製器。

 

因為熱插拔可以在任何時刻發生,所以USBD和其Client都必須被寫成可以動態識別USB設備被插入還是被拔出。當主控製器連接到係統時,USBD 自動地鑒別與其相連的設備,並通知相關的client;同樣,拔出設備時,也要通知相關設備。重要的是,USBD 的client,比如USB class driver,在client初始化時,從不設想特定的設備已經出現;而在其他時候,這些驅動隨時檢查設備是否已經連接到係統上。

 

4 總線任務

對每一個連接到USBD 的主控製器,例如插入或拔出設備,USBD都會產生一個總線任務,來監控總線事件。一般情況下,這些任務是休眠的(不消耗CPU),隻有當USB hub報告它的一個端口有變化時,它們才被喚醒。每一個USBD總線任務有VxWorks任務名:UsbdBus。

 

雖然HCD委托USBD來管理,但有可能HCD 親自監視主控製器事件。例如WindRiver提供了UHCI和OHCI的HCD來創造這樣的任務。對於WindRiver的UHCI模塊(usbHcdUheiLib),後台任務隻是被周期地喚醒,目的是為了檢查超時IRP(用一個中斷來通知OHCI根hub發生改變)。

 

用以在USBD和USB之問進行通信的client模塊,除了調用usbdlnitialize()外,必須調用usbClientRegister()使其在USBD注冊。當一個client注冊到USBD時,USBD把每一個以後將要用到的client的數據結構定位,並跟蹤那個client的請求。

 

對於每一個client,在client注冊過程中,USBD還創建了一個callback任務。在成功注冊client後,USBD返回一個句柄USBD_CLIENT_HANDLE。以下對USBD的調用,將會用到這個句柄。當所有句柄都不需要時,可以調用usbdClientUnregister()來釋放每一個client的數據結構和callback任務。注意:此時所有client要求的任務都會被取消。

 

例如:注冊一個叫USBD_TEST的client,再注銷。

  注冊:usbdClientRegister("USBD_TEST,&usbdClientHandle);

  注銷:usbdClientUnregister(usbdClientHandle);

 

5 client回調(callback)任務

USB操作是嚴格遵守時序的。例如為使中斷傳輸和同步傳輸正確工作.需要依靠時鍾中斷。在一個有幾個不同client出現的主係統中.總是有可能出現一個client打斷其它client傳輸事件的發生。WindRiver USBD建議用client callback任務來解決這個問題。許多USB事件可以導致一個USB client的callback任務。例如, 每當USBD 完成USB IRP後,client的IRP callback函數被激活。同樣,當USBD識別出一個動態連接事件後,會激活一個或更多的動態attach callback操作。但不是馬上激活這些回調操作, 而是安排合適的相應的USBD client的回調任務來執行callback。

 

一般的情況下,每一個client的callback任務處於“休眠”態(阻塞態)。每一個client的callback,繼承了usbdClientRegister()產生的VxWorks任務優先級。這確保了每一個callback按其client的任務優先級來執行,而且可以利用優先級來寫client,保證對時間要求嚴格的USB傳輸。由於每一個client有它自己的callback任務,因此在callback期間,它們有很大的靈活性決定可以做什麼。例如,允許在不破壞USBD或其它USBD client性能的條件下,使callback執行代碼運行至阻塞態。

 

Client callback task有VxWorks任務名:tUsbdCln。

 

6 USBD內部Client

當第一次初始化USBD時,由USBD產生並注冊一個內部client,以跟蹤USB請求。

 

USBD 可以產生什麼類型的USB請求呢? 所有USBD與USB設備的傳輸,均利用調用USBD client的形式來完成。例如, 當一個設備第一次連接到係統時.USBD用一個控製管道(control pipe) 自動地創建設備需要的所有的control pipe,即USBD client要用usbdPipeCreate()來創建一個與USB endpoint0通話的通道,然後所有USBD 內部、外部client通過這個管道來發送諸如usbdDescriptorGet()或usbdFeatureGet()等的函數,進行操作。

 

所以,USBD 的一個機製就是USBD 循環利用它自己的entry point,而內部chent跟蹤這些請求。

 

7 動態連接的注冊

每當一個特定類型的設備插入或拔出時,USBD client都通知上一層。利用調用usbdDynamicAttachRegister()操作,client可以指定一個callback操作,以便可以獲取這樣的通知。

 

USB設備類型用class,subclass,protocol來區別。標準的USB 類在usb.h 中定義為USB_CLASS_XXXX。Subclass和protocol根據class來定義, 因此這些常數根據特定的class在頭文件中定義。

 

有時, 一個client當利用usbdDynamicAttachRegister()進行注冊時,隻對特定的class,subclass,protocol感興趣。例如,USB鍵盤類驅動usbkeyboardLib, 注冊了Human Device Interface (HID) 類,subclass 是USB_SUBCLASS_HID_ BOOT,protocol是USB_PROTOCOL_HID_BOOT _KEYBOARD。通過callback機製的響應,每當一個設備完全符合這樣的標準, 從設備上插入或拔出時,SBD便通知給keyboard class driver。而在其它情況下,client關注的範圍更廣泛了。常量USBD_NOTIFY(定義在usbdLib.h)可以替代任意的class,subclass,protocol。例如,USB打印機USB驅動,usbPrinterLib, 其class等於USB_CLASS_PRINTER,subclass 等於USB_SUBCLASS_PRINTER (usbPrinter.h),protocol等於USBD_ NOTIFY_ ALL。典型的,當一個client隻調用一次usbdDynamicAttachRegister()時,對一個client能擁有的並發通知請求數目沒有限製。

 

8 Node ID

USB設備一般用USBD_NODE_ID來區別。從其作用來看,USBD_ NODE_ ID 是USBD 用來跟蹤一個設備的句柄。它與USB設備真正的USB地址無關。這表明client並不真正關心想要了解設備是物理上與哪一個USB主控製器相連。應用為每個設備抽象定義的Node ID, 使client可以不用考慮物理設備的連接細節以及USB地址分配, 並允許USBD 在其內部對這些進行詳細的管理。

當一個client通知有一個設備連接或斷開時,USBD經常通過USBD_NODE_ID來定位設備。同樣,當一個client想通過USBD與一個特定的設備通信時,它必須向USBD傳遞那個設備的USBD_NODE_ID。

 

9 總線編號(bus enumeration)操作

usbdLib模塊提供了usbdBusCountGet(),usbdRootNodeldGet(),usbdHubPortCountGet(),usbdNodldGet()操作。它們被一起稱作總線編號操作。它們使USBD Client對連接到每一個主控製器上的設備進行編號。

這些操作對於診斷程序和測試工具很有用,例如usbTool(WindRiver提供的一個測試工具)。但是,利用它們編號之後,調用者無法知道USB的拓撲結構是否變化。因此, 建議USB class driver的開發者不要用這些操作。

 

10 數據傳輸

一旦client配置完成一個設備,就開始利用USBD提供的管道和傳輸功能與設備進行數據交換。為了和設備交換數據,client必須先創建管道。作為結果,USBD得到了一個USBD_PIPE_HANDLE,它被用於隨後對這個管道的所有client操作。

當client企圖創建一個管道時,USBD會檢查是否有足夠的可用帶寬。對於中斷和同步傳輸,帶寬限製是必需的。USBD不允許把90% 以上的可用帶寬分配給中斷和同步管道;而對於控製和塊傳輸,則沒有帶寬的限製。同時,保證至少10% 的帶寬用於控製傳輸,對塊傳輸則不保證會提供任何可用帶寬。

 

數據傳輸的具體過程:

(1)創建pipe :usbdPipeCreate(usbdClient Handle,nodeld,endpoint,configvalue,interface,  

USB_XFRTYPE_BULK,USB_ DIR_OUT,maxPacketSize,0,0,&outPipeHandle);

(2)定義callback:ourlrpCallback(pvoid P);

(3)初始化IRP的數據結構;

(4)發送IRP:usbdTransfer(usbdChentHandle,outPipeHandle,&irp)。

 

 

4、 VxWorks下USB驅動編寫流程

4.1 生成bootable工程,添加以下組件(根據不同硬件定製):

hardware->buses->USB Hosts->OHCI

hardware->buses->USB Hosts->USB Host Stack

hardware->buses->USB Hosts->USB Host Init->OHCI Init

hardware->buses->USB Hosts->USB Host Init->USB Host Stack Init

此時編譯後的內核在啟動時如果出現Attach OHCI...OK,表示USB協議棧加載成功。

 

4.2 修改和檢查config.h

/* USB Stuff */

#define INCLUDE_USB

#define INCLUDE_OHCI

#define INCLUDE_OHCI_INIT

#define INCLUDE_USB_INIT

#define INCLUDE_USB_MOUSE

#define INCLUDE_USB_KEYBOARD

#define INCLUDE_USB_MS_BULKONLY

#define INCLUDE_USB_MS_CBI

#define INCLUDE_USB_PRINTER

#define INCLUDE_USB_SPEAKER

 

//新增加,根據係統不同配製,可能不同

//IO 地址相關,該係統不采用動態PCI查找和影射

#define SL811H_IO_ADDR SL811H_MEMORY_START

#define SL811H_IO_ADDR_DATA ((SL811H_MEMORY_START) | 0x800000)

 

//中斷相關

#define SL811H_INT_LVL INT_LVL_EXT_IRQ_0 /* PPC405GP UIC Interrupt 25 - External IRQ 0 */

#define SL811H_INT_VEC INT_VEC_EXT_IRQ_0 /* PPC405GP UIC Interrupt 25 - External IRQ 0 */

 

4.3 wrSbc405gp.h(特定係統配製文件)

#define SL811H_MEMORY_START  0x70000000

#define SL811H_MEMSIZE         0x10000000

 

4.4 sysLib.c,增加MMU屬性配製

    ,{

    (void *) SL811H_MEMORY_START,

    (void *) SL811H_MEMORY_START,

    SL811H_MEMSIZE,

    VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE | VM_STATE_MASK_GUARDED,

    VM_STATE_VALID      | VM_STATE_WRITABLE      | VM_STATE_CACHEABLE_NOT  | VM_STATE_GUARDED

    }

 

 

 

5.調用流程

5.1 USB Init

///////////////////////////////////////////////////////////////////////////////////////////

UINT16 cmdUsbInit()

{

    UINT16 usbdVersion;

    char usbdMfg [USBD_NAME_LEN+1];

 

    UINT16 s;

 

    /* if already initialized, just show a warning */

    if (initialized)

       {

              printf( "Already initialized.\n");

              return RET_CONTINUE;

       }

 

    /* Initialize the USBD */

    s = usbdInitialize ();

    printf( "usbdInitialize() returned %d\n", s);

    if (s == OK)

       {

           /* Register ourselves with the USBD */

           s = usbdClientRegister (PGM_NAME, &usbdClientHandle);

           printf( "usbdClientRegister() returned %d\n", s);      

           if (s == OK)

           {             

               printf( "usbdClientHandle = 0x%x\n", (UINT32) usbdClientHandle);

       

               /* Display the USBD version */      

               if ((s = usbdVersionGet (&usbdVersion, usbdMfg)) != OK)

             {

                    printf( "usbdVersionGet() returned %d\n", s);

             }

               else

             {

                    printf( "USBD version = 0x%5.4x\n", usbdVersion);

                    printf( "USBD mfg = '%s'\n", usbdMfg);

             }

       

               if (s == OK)

             initialized = TRUE;       

           }

       }

   

    if (s != OK)

       {

              printf( "initialization failed\n");

       }

 

    return RET_CONTINUE;

}

/////////////////////////////////////////////////////////////////////////////////////////////

 

5.2執行HCD掛接

SL811_IO_CFG sl811IOCfg = {

SL811H_IO_ADDR,

SL811H_IO_ADDR_DATA,

SL811H_INT_VEC,

SL811H_INT_LVL

};

 

進入usbdHcdAttach,

 

5.3 setups the expansion bus access profile.(硬件單板的總線連接方式不同而不同)

 

5.4硬件相關寄存器初始化(因為硬件不同,所以相關過程不一樣)

 

5.5初始化中斷處理進程

        (pHost->intThread =

         taskSpawn ("tSl811Int", 0, 0, 0x4000,

                    (FUNCPTR) intThread, (int) pHost,

                    0, 0, 0, 0, 0, 0, 0, 0, 0))==ERROR

 

5.6 掛接中斷

    *pResult = intConnect ((VOIDFUNCPTR *)INUM_TO_IVEC (pHost->sl811CfgHdr.intVec), \

                          routine, (int)arg); \

    開中斷;

有必要的話,複位硬件(大部分時候如此)

 

6:數據發送和接受過程

6.1寫數據過程

A:bulkWrite

LOCAL UINT16 bulkWrite

    (

    long  bytes,

    FILE *fin,                 /* stream for input (if any) */

    FILE *fout                /* stream for output (if any) */

    )

   

    {

    /* Initialize the BULK class driver */

    char    filePath[1000];

    char    buffer[BULK_DRIVE_BUFFER_SIZE];

    UINT8  *ptr;

    int     fd;

    UINT32  i;

    UINT8   j=1,k=1;

    UINT32  len=0;

 

    sprintf(filePath,"%sbulkFile",BULK_DRIVE_NAME);

    remove(filePath);

    fd = open(filePath, O_CREAT | O_WRONLY, 0);

 

    if (fd == ERROR)

              fprintf (fout, "bulkWrite() error opening %s\n",filePath);

    else

              fprintf (fout, "bulkWrite() %s opened for write\n",filePath);

 

    ptr = (UINT8 *) buffer;

 

    for (i=1; i<=bytes; i++)

    {

        *ptr++ = j++;

        len++;

       

        if ( (i%BULK_DRIVE_BUFFER_SIZE)==0 || i==bytes )

        {

            if (len != write(fd,buffer,len))

            {

                fprintf (fout, "bulkWrite() error writing %u to %s\n",

                         i,filePath);

                close(fd);

                return ERROR;

            }

           

            if (i%BULK_DRIVE_DISPLAY_INTERVAL == 0)

            {

                fprintf (fout, "bulkWrite() wrote %u of %u bytes\n",

                         i,bytes);

            }

           

            len = 0;

            k++;

            j = k;

            ptr = (UINT8 *) buffer;

        }

    }

 

    close(fd);

    fprintf (fout, "bulkWrite() wrote %u bytes to %s\n",i-1,filePath);

 

    return RET_CONTINUE;

}

 

B:讀數據過程

LOCAL UINT16 bulkRead

    (

    long  bytes,

    FILE *fin,                 /* stream for input (if any) */

    FILE *fout                /* stream for output (if any) */

    )

   

    {

    /* Initialize the BULK class driver */

    char    filePath[1000];

    char    buffer[BULK_DRIVE_BUFFER_SIZE];

    UINT8  *ptr;

    int     fd;

    int     i;

    UINT8   j=1,k=1;

    int     len;

    UINT32  totLen=0;

 

    sprintf(filePath,"%sbulkFile",BULK_DRIVE_NAME);

 

    fd = open(filePath, O_RDONLY, 0);

 

    if (fd == ERROR)

       fprintf (fout, "bulkRead() error opening %s\n",filePath);

    else

       fprintf (fout, "bulkRead() %s opened for read\n",filePath);

 

    ptr = (UINT8 *) buffer;

 

    while ((len=read(fd,buffer,BULK_DRIVE_BUFFER_SIZE)) > 0)

        {

        for (i=0; i<len; i++)

            {

            totLen++;

            if (*ptr++ != j++)

                {

                fprintf (fout, "bulkRead() error %u != %u at %u from %s\n",

                         *(ptr-1),j-1,totLen,filePath);

                close(fd);

                return ERROR;

                }

            if (totLen%BULK_DRIVE_DISPLAY_INTERVAL == 0)

                {

                fprintf (fout, "bulkRead() verified %u of %u bytes\n",

                         totLen,bytes);

                }

            }

        k++;

        j = k;

        ptr = (UINT8 *) buffer;

        }

 

    close(fd);

 

    if (totLen==bytes)

        {

        fprintf (fout, "bulkRead() verified %u bytes from %s\n",

                 totLen,filePath);

        }

    else

        {

        fprintf (fout, "bulkRead() error reading at %u of %u in %s\n",

                 totLen,bytes,filePath);

        return ERROR;

        }

 

    return RET_CONTINUE;

    }

 

7:USB代碼的注意點

這一周主要在研究USB代碼的架構,發現一些問題:

(1)      數據是以禎的方式傳輸的,數據包的開頭是一段同步字段,同步字段的結尾是PID開始的標誌,包結束是以EOP為結束的標誌.

(2)      PE通過讀出CSR以及Buffer裏麵的狀態,來決定數據傳輸的可靠性,並不是PE來控製CSR

(3)      仲裁器仲裁總線傳輸和DMA傳輸, CSR[15]是DMA使能位,但是發現找不到DMA request 的觸發,Wishbone部分並沒有對這個信號進行相應的處理

(4)      從主機發過來的token會有兩個token寄存器進行寄存,token的每個位都有相應的含義

(5)      主機每秒發送的禎的個數,禎在Top層有一個禎的計數器

(6)      在代碼中,要保持信號的穩定性,往往都是采用計數器的方法,計數到一定時間後,再采樣一次,這樣來保持數據的穩定性

最後更新:2017-04-03 12:55:36

  上一篇:go Android 重寫係統Crash處理類,保存Crash信息到SD卡 和 完美退出程序的方法
  下一篇:go 百度地圖開發1