USB模块可以分为多端口主机(MPH)模块和双角色(DR)模块,它们都能够连接一个或者二个外部端口,这些模块和外部端口总称为USB接口。Mpc8379的USB模块采用的是DR,它的寄存器和数据结构均基于Intel的EHCI(Enhanced Host Controller Interface Specification for Universal Serial Bus),DR模块可以充当USB总线上的主机、外设、以及支持便携式On-The-Go(OTG))可协商主机/外设。
DR模块有三个基本操作模式:主机(Host)、设备和OTG。可以把DR模块配置成使用UTMI、ULPI 或者FS/LS串行收发器中的任何一种PHY接口。由于引脚的限制,UTMI接口只能用在设备操作模式。值得注意的是,设备模式下的DR模块不支持LS操作。8377使用的phy芯片smsc3300的接口为ULPI(UTMI + Low Pin Interface)。
风河USB为通用串行总线提供USB传输初始化(USB hosts)和允许vxWorks目标作为USB外设的支持,USB hosts(又叫USB host stack)和USB外设(又叫USB peripheral stack)都遵循USB 2.0规约。Host Stack使vxWorks可以使用USB设备,而Peripheral Stack允许Windows机器将vxWorks板当做一个USB设备。这里先介绍Host,下图为USB Host驱动栈结构:
USB Host Stack驱动包括USB驱动(USBD)、主机控制驱动(HCD)、hub驱动和class驱动。风河为标准接口协议EHCI、UHCI、OHCI提供HCD驱动,另外,还为USB控制器提供了root hub diver,为各种USB外设提供class driver合集。其中,USBD是硬件独立的,它提供了驱动栈上层(包括USB class drivers)与USB总线的通讯通道,还负责电源管理、USB带宽管理、动态挂载/释放USB设备等功能。下图为vxWorks中所体现的USB Host Stack组成图:
这里从上往下来理解,usbTool是风河提供的USB测试工具包,从使用usbTool的过程可知,USB在初始化时的步骤如下:
1、先执行usbInit初始化USB2的Host Stack,源码如下:
STATUS usbInit (void) { if (!usbdInitByKernel) { /* 为USB驱动设置内存空间(默认2M)*/ ossPartSizeSet(USB_MEM_PART_SIZE); /* 初始化USB2 Host Stack,主要初始化USBD2.0层的全局变量*/ if (usbdInit() == OK) { usbdInitByKernel = TRUE; #ifdef DEBUG_ANNOUNCE_USB printf("USB2 Host Stack Initialized\n"); #endif /*注册USB Hub Class驱动到USB Host驱动栈,以及初始化USB1.1转换层*/ if ((usrUsbHubInit() != OK) || (usbdTranslationInit() != OK)) { return ERROR; } } } else { printf("USB2 Host Stack Already Initialized\n"); } return OK; }
2、然后执行usbdInitialize初始化USBD,该函数在系统调用其他USBD函数前必须执行一次以上,有一个公用计数器guUSBDInited ,Initialize函数时会加1,ShutDown时会减1,大于0时表示初识化完成,注意:由于usbdInitialize可以嵌套执行,所以在执行计数器操作时,必须要遵循互斥访问规则。它用于准备访问URBs所需的USBD和传送单元。oss为O/S-independent services,用于保护互斥信号量。Host Stack的USBD2.0和Hub class模块的源码可以参考这两个目录:
installDir/target/src/hwif/usb和installDir/target/src/hwif/busCtlr/usb/hub
3、再初始化EHCI、OHCI、UHCI控制器,在usbTool中,这个过程叫做Attach,用于初始化hcd并注册到vxBus。先后调用usbxhcdInit和vxbUsbxhciRegister两个函数,前者的使用过程实际上就是调用usbHstHCDRegister,将HCD注册到USBD中,这里涉及到一个结构体USBHST_HC_DRIVER,它包含了HCD的函数指针,在HCD初始化时会将它传给USBD,后者利用这些指针和HCD进行通信。该结构体定义如下:
typedef struct usbhst_hc_driver { /* 用于此HCD的总线数目 */ UINT8 uNumberOfBus; /* 结构体vxbBusTypeInfo保存总线信息 */ struct vxbBusTypeInfo * pUsbHcdBusType; /*下面均为函数指针*/ /* 从frame number Reg中获取num*/ USBHST_STATUS (*getFrameNumber) (UINT8 uBusIndex, UINT16 *puFrameNumber); /* 设置frame位宽 */ USBHST_STATUS (*setBitRate) (UINT8 uBusIndex, BOOL bIncrement, UINT32 *puCurrentFrameWidth); /* 判断总线带宽是否足够支持接口设置的改动 */ USBHST_STATUS (*isBandwidthAvailable) (UINT8 uBusIndex, UINT8 uDeviceAddress, UINT8 uDeviceSpeed, UCHAR *pCurrentDescriptor, UCHAR *pNewDescriptor); /* 为特定USB endpoint解析并创建pipe */ USBHST_STATUS (*createPipe) (UINT8 uBusIndex, UINT8 uDeviceAddress, UINT8 uDeviceSpeed, UCHAR *pEndPointDescriptor, UINT16 uHighSpeedHubInfo, UINT32 *puPipeHandle); /* 修改默认pipe属性(address 0,endpoint 0) */ USBHST_STATUS (*modifyDefaultPipe) (UINT8 uBusIndex, UINT32 uDefaultPipeHandle, UINT8 uDeviceSpeed, UINT8 uMaxPacketSize, UINT16 uHighSpeedHubInfo); /* 删除pipe */ USBHST_STATUS (*deletePipe) (UINT8 uBusIndex, UINT32 uPipeHandle); /* pipe上的请求pending */ USBHST_STATUS (*isRequestPending)(UINT8 uBusIndex, UINT32 uPipeHandle); /* 提交请求到endpoint,URB:USB Request Block */ USBHST_STATUS (*submitURB) (UINT8 uBusIndex, UINT32 uPipeHandle, pUSBHST_URB pURB); USBHST_STATUS (*cancelURB) (UINT8 uBusIndex, UINT32 uPipeHandle, pUSBHST_URB pURB); /* 以下两个用于处理split transaction时出现的错误 */ USBHST_STATUS (*clearTTRequestComplete)(UINT8 uRelativeBusIndex, void * pContext, USBHST_STATUS nStatus); /* Function pointer to submit the status of the reset TT request */ USBHST_STATUS (*resetTTRequestComplete)(UINT8 uRelativeBusIndex, void * pContext, USBHST_STATUS nStatus); } USBHST_HC_DRIVER, *pUSBHST_HC_DRIVER;
后者则分析意义不大,全部都是调用vxbDevRegister注册到vxBus上。
至于这些驱动,风河已经编译好了(郁闷,还是只能看代码学习),见目录installDir/target/src/hwif/busCtrl/usb/hcd,三种接口都分开放置了,尽管一次只能使用一种接口,但风河允许同时添加多个接口驱动,从而加强了产品的兼容性。到这里需要注意,添加usbTool就不能添加其他任何Init函数,否则会编译出错。
若不是有usbTool,将组件完全包含后,系统若要使用USBD2.0接口,需要在vxWorks启动时经历4步初始化过程:
1、根据选择的组件将USB host控制器注册到vxBus。该过程是vxBus发现控制器设备并执行特定的vxBus初始化操作,调用函数为vxbUsbControllerRegister,
2、执行USBD入口函数usbdInit,
3、执行usbHubInit初始化hub class Drivers;
4、执行usbHcdInit将特定的HCD注册到USBD。
上图中还有个OSAL,该组件用于为vxWorks关于host Stack的操作系统服务提供一个抽象、简化的视图。它包含了进程管理、互斥量、内存调度以及系统时间等,在usbd中,就是ossLib库。需要注意,在编写USB驱动时,需要有大部分精力是放在互斥访问和内存调度上的。