********************************LoongEmbedded************************
作者:LoongEmbedded(kandi)
时间:2011.03.16
类别:WINCE驱动开发
********************************LoongEmbedded************************
注:这里提到的MDD层和PDD层是对于usb function controller driver来说的
主要基于activesync功能的实现来学习,也即Serial:用于支持USB Device作为串口设备。
1. usb function驱动架构
图1
Architecturally, a USB function client driver is above a USB function controller driver. A USB function client driver interfaces with a USB function controller driver through the USB function controller driver's hardware independent MDD. The USB function controller driver's MDD and all USB function client drivers are hardware platform independent. Multiple USB function client drivers interface with the same USB function controller driver. Each client interfaces with the USB function controller driver through the USB function controller driver's MDD. Along with the MDD, a USB function controller driver is also comprised of a PDD, which is architecturally below the MDD. The PDD interfaces directly with the USB function controller hardware.
从架构上来说,USB function client驱动位于USB function controller驱动的之上,USB function controller驱动的MDD层是USB function client驱动和USB function controller驱动之间的接口,多个USB function client驱动共用可以同一个USB function controller驱动,USB function controller驱动的PDD层直接和USB function controller hardware打交道。
2. usb function驱动组件
下图是WINCE6.0中usb function
图2
3. usb function硬件设计
图3
4. usb function驱动的实现
4.1 USB设备端点的概念
每个端点都是一个简单的连接点,或者支持数据流进设备,或者支持其流出设备,两者不可兼得。基于PnP机制,设备被枚举时,它必须向主机报告各个端点的特性,包括端点号,通信方向,端点支持的最大包大小,带宽要求等(其中端点支持的最大包大小叫做数据有效负载)。每个设备必须有端点0,它用于设备枚举和对设备进行一些基本的控制功能。除了端点0,其余的端点在设备配置之前不能与主机通信,只有向主机报告这些端点的特性并被确认后才能被激活。
特别地,缺省控制通道也是一个消息通道。当客户程序通过USB管道发送或接收数据时,它首先调用Win32 APl,调用最终将使功能驱动程序收到一个IRP。而驱动程序的工作就是把客户的请求引导到有正确端点的管道上。它把请求提交到总线驱动程序,总线驱动程序再把请求分解成多个事务,然后这些事务被送往总线。总线上的信息流以每毫秒一帧数据的形式流动。总线驱动程序必须安排好多个事务以使它们能被装入同一帧中。在主机和设备的端口之间,可视为一个通道。USB中有一个特殊的通道一缺省控制通道,它属于消息通道,设备一启动即存在,从而为设备的设置、状态查询和输入控制信息提供一个入口。
端点有三个状态:空闲,数据流入和数据流出。
4.2 USB function client驱动总线接口
USB function controller驱动的MDD层实现并导出了总线接口,这样就可以利用这个接口来加载USB function client驱动。IOCTL的初始化之后,USB function controller驱动的MDD层根据注册表项HKEY_LOCAL_MACHINE/Drivers/USB/FunctionDrivers下面的键值来加载默认的USB function client驱动,比如加了Device Drivers->USB Function->USB Function Clients->serial组件之后在common.reg中有此client驱动对应的注册表项
[HKEY_LOCAL_MACHINE/Drivers/USB/FunctionDrivers]
"DefaultClientDriver"=- ; erase previous default
[HKEY_LOCAL_MACHINE/Drivers/USB/FunctionDrivers]
"DefaultClientDriver"="Serial_Class"
如果此键值不存在,那么在IOCTL的初始化也会成功,但不能使能function controller。当然应用程序可以稍后增加这个注册表项,并且调用MDD层的IOCTL_BUS_ACTIVATE_CHILD来加载client驱动。
为了增加我们应用的灵活性,可以在platform.reg中增加注册表项HKEY_LOCAL_MACHINE/Drivers/USB/FunctionDrivers来替代common.reg中相应的内容,下面是我们的platform.reg中的相关注册表信息
图4
享有特权的应用程序可以通过DeviceIoControl函数获取MDD层总线接口的句柄,并且通过IOCTL_BUS_UFN_ENUMERATE_AVAILABLE_CLIENTS可以枚举不同的客户端程序,当然也可以获取当前的客户程序的名字,可以下载当前的客户程序并且加载新的客户程序等等,关于USB function controller驱动的IO控制码,见下面的描述
图5
对上面IOCTLs的支持和实现是在MDD层的Ufnbus.cpp下面的CUfnBus::IOControl()来实现的,
在此需要注意的是MDD层支持的总线访问IOCTLs不包含IOCTL_BUS_GET_CONFIGURE_DATA 和IOCTL_BUS_SET_CONFIGURE_DATA。
4.3 USB function controller驱动MDD层及DDI接口
MDD层和client的接口函数如下,在public/common/oak/inc/usbfntypes.h下定义
图6
既然是MDD层提供给Client调用的接口函数,就必须在MDD层来实现这些函数并且填充些个函数指针,先看MDD层的UFN_Init()函数中下面部分:
图7
而CUfnBus类主要是用于加载client驱动并且处理USB function controller驱动的IOCTLs,见图5,创建CUfnBus后会调用其成员函数CUfnBus::PostInit()
图8
Client驱动被加载后,此后此client就是usb bus下的一个child,接着client会调用ufnclient.cpp下面的函数UfnInitializeInterface()向usb bus发出IOCTL_UFN_GET_CLIENT_DATA_EX请求
图9
下面就来看CUfnBus::IOControl()函数对这个case的处理
图10
下面看看GetClientFunctions()函数
图11
到这里client就获取到MDD层为其提供的函数接口了,后面就可以调用这些函数了。
MDD层的主要工作如下:
4.3.1根据usb描述符来注册usb设备(在此指WINCE的usb function接口)
这里先来了解一下usb描述符,USB采用USB标准描述符说明一个USB设备,这些描述符包括设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符。
(1)设备描述符 (Device Descriptor)用于指出USB设备的总体信息,其内容对该设备中同一传输模式下的所有配置都有效。一个设备只能有一个设备描述符,但是一个设备允许多个配置描述符。设备描述符的结构体如下:
typedef struct _USB_DEVICE_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
USHORT bcdUSB;
UCHAR bDeviceClass;
UCHAR bDeviceSubClass;
UCHAR bDeviceProtocol;
UCHAR bMaxPacketSize0;
USHORT idVendor;
USHORT idProduct;
USHORT bcdDevice;
UCHAR iManufacturer;
UCHAR iProduct;
UCHAR iSerialNumber;
UCHAR bNumConfigurations;
} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;
(2)配置描述符 (Configuration Descriptor)为USB设备的配置指出其配置信息。USB设备的一个配置可以包含一个或者多个接口,且每个接口都可以相互独立工作,所有的USB设备都至少支持一个配置描述符,每个配置都必须有自己的配置描述符。当主机请求配置描述符时,其所有相关的接口描述符和端点描述符都将被返回。
typedef struct _USB_CONFIGURATION_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
USHORT wTotalLength;
UCHAR bNumInterfaces;
UCHAR bConfigurationValue;
UCHAR iConfiguration;
UCHAR bmAttributes;
UCHAR MaxPower;
} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR;
(3)接口描述符(interface DescriPtor)用于指定usB设备中各个接口的特性,设备的每个接口都必须有一个描述符。USB设备的接口是一个端点的集合,负责完成设备的特定功能,接口可以包含一个或者多个可替换配置,它们能够在USB设备处于配置状态时,改变当前接口所含端点的个数和特性。USB设备同一配置的各个接口间不能使用相同的端点,但是同一接口的各个可替换配置间可以使用相同的端点。
typedef struct _USB_INTERFACE_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
UCHAR bInterfaceNumber;
UCHAR bAlternateSetting;
UCHAR bNumEndpoints;
UCHAR bInterfaceClass;
UCHAR bInterfaceSubClass;
UCHAR bInterfaceProtocol;
UCHAR iInterface;
} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;
(4)端点描述符(EndPointDescriptor)用于指出usB设备端点的特性,如其所支持的传输类型、传输方向等信息。除端点O外,USB设备的每个端点都必须有一个端点描述符。
typedef struct _USB_ENDPOINT_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
UCHAR bEndpointAddress;
UCHAR bmAttributes;
USHORT wMaxPacketSize;
UCHAR bInterval;
} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR;
(5)字符串描述符(stringDescriptor)用于保存一些文本信息,它是可选的。在USB设备的其他描述符中,可以含有指向字符串描述符的索引值。
typedef struct _USB_STRING_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
WCHAR bString[1];
} USB_STRING_DESCRIPTOR, *PUSB_STRING_DESCRIPTOR;
USB function clients调用lpRegisterDevice函数(最终会调用MDD层的UfnMdd_RegisterDevice())把预先填充的描述符传递给MDD层,这些描述符包含端点数据包(endpoint packet)期望的最大包大小(在此为字节数)。MDD和PDD层在client指定的最大限制条件下协调使用的端点数据大小,MDD和PDD层可以使用小于端点本身供给的包大小,但不能大于。Client通过使用调整后的包长度来打开管道到端点的通道。
为了处理注册过程,client提供总线速度最大可能的端点大小,比如,对于USB1.1来说,支持的大块端点数据包是64个字节,而USB2.0支持的大块端点数据包是512个字节,这样允许MDD和PDD层可以灵活指定其大小。
4.3.2 处理client的枚举请求
对于USB1.1和2.0,client驱动传递usb描述符给MDD和PDD,除非PDD先处理标准的枚举的请求,否从由MDD来处理这些请求。当枚举完成后,MDD或PDD传递一个枚举完成事件给client,然后,client就适当地初始化端点。MDD需要处理下面的设置请求(setup request)
⑴Get device descriptor
⑵Get device qualifier
⑶Get high speed configuration descriptor, if the MDD is on a high speed bus
⑷Get Full Speed Configuration Descriptor, if the MDD is on a full speed bus
⑸Get string descriptor
⑹Set address
下面是help文档中对MDD层的描述,为避免误导大家,故贴出来稍加谈谈自己的理解。
Before interacting with the host(PC的usb host端), a function driver(usb function controller driver) must configure the underlying(下面的) USB function controller to support the function that it implements. The model device driver (MDD) must provide a mounted function driver(在此为总线驱动,见/PUBLIC/COMMON/OAK/DRIVERS/USBFN/CONTROLLER/MDD/ufnbus.cpp) with a way to configure the underlying USB function controller through a USB descriptor set. In addition, a mounted function driver must address a logical endpoint or pipe, through the endpoint address specified in a USB endpoint descriptor(通过端点描述符的bEndpointAddress为端点或通道编址). The MDD must provide a mapping between a pipe and a physical endpoint.
MDD functions wrap most of the similarly named PDD functions(这些函数见下图). This indirection provides the function driver with the ability to access logical endpoints or pipes. The indirection allows the MDD to map the specified pipe to a physical endpoint.
图12
The MDD provides functions to activate a particular configuration or interface. These functions handle configuration and pipe access.
The following steps show how the USB function driver is configured and how pipe access is set up:
⑴The function driver initializes the MDD, which initializes the PDD.
调用UfnPdd_Init()来初始化PDD,见图7
⑵The function driver registers a configuration with the MDD.
这个在UfnMdd_RegisterDevice函数中实现。
By querying the capabilities of the USB function controller, through the PDD, the MDD determines whether the supplied configuration can be supported.
在UfnPdd_Init函数中实现
⑶The function driver starts the USB function controller if the configuration can be supported.
主要是通过调用UfnPdd_Start函数来实现,在这里会创建线程ISTMain来检测usb function中断。
⑷The function driver specifies notification functions for device events and default pipe events.
⑸The PDD maps the interrupt to a USB event when an interrupt occurs, and then the PDD notifies the device associated with the interrupt.
In other words, the function driver starts the interrupt service thread (IST) of the USB function controller, and supplies the PDD with a set of functions to call on the when an interrupt occurs(中断发生的时候,MDD层调用PDD层为其提供的函数集). Handle USB events generated by the USB function controller as interrupts(在PDD层的HandleUSBEvent函数处理), and handle notification functions as interrupt service routines (ISRs).
⑺The USB function controller driver begins to service interrupts when the USB function controller is running and the default pipe is open.
默认控制管道是一个消息管道,用于在主机和USB设备的端点0之间传送控制和状态信息。
⑻The PDD calls the notification functions during USB enumeration.
These functions are supplied by the function driver in response to USB events. The notification function associated with the default pipe is required to read and parse setup token packets and respond to the associated USB standard requests associated with USB enumeration.
4.4 USB function controller驱动PDD层及DDI接口
图13
PDD主要的主要工作如下:
⑴为mounted function driver(usb总线驱动)呈现抽象的USB function controller,这样可以保证mounted function driver访问不同的USB function controller时可以移植过来。
⑵PDD层有能力检查USB function controller的,并且允许usb function controller driver驱动(准确来说应该是其MDD层)配置和分配端点。
⑶USB总线事件发生的时候通知mounted function driver,并且允许function driver处理和关闭此事件。
4.5 USB function controller驱动PDD层的实现
4.5.1 USB function controller驱动PDD层用到的中断及对应的线程的概述
⑴检测是否插入usb device线的中断及线程
根据图3可知是用GPF2/EINT2来检测WINCE设备是否和PC端接入usb device线,检测到之后由线程PLUG_IST函数来处理,具体的处理见下面的描述。
⑵检测usb device中断及处理线程
线程ISTMain函数用于检测并处理usb deivce中断,具体处理见下面的描述。
4.5.2 PDD层的初始化函数UfnPdd_Init
⑴初始化MDD层和PDD层的接口
图14
⑵读取注册表项中FunctionDrivers的内容来获取usb function client driver的信息
比如读取到下面的内容
[HKEY_LOCAL_MACHINE/Drivers/USB/FunctionDrivers]
"DefaultClientDriver"=- ; erase previous default
"DefaultClientDriver"="Serial_Class"
然后确定client驱动
⑶设置端口的信息
图15
这里涉及到端口的结构,如下
// Transfer structure passed to the PDD from the MDD in IssueTransfer.
typedef struct _STransfer {
DWORD dwFlags;
PVOID pvBuffer;
DWORD dwBufferPhysicalAddress;
DWORD cbBuffer;
DWORD cbTransferred;
DWORD dwUsbError; // Possible values are in usbfntypes.h
PVOID pvPddData; // PDD can do whatever it likes with this
PVOID pvPddTransferInfo; // Specific to PDD from client
} STransfer, *PSTransfer;
需要传输的数量,已经传输的数量
typedef struct EP_STATUS {
DWORD dwEndpointNumber;
DWORD dwDirectionAssigned;
DWORD dwPacketSizeAssigned;
BOOL fInitialized;
DWORD dwEndpointType;
PSTransfer pTransfer;
CRITICAL_SECTION cs;
} *PEP_STATUS;
这个结构体保存着一个端点传输中的基本信息:端点的Index号,传输方向,每次最大传输数量,端点的类型,此次传输的数据存放内存首地址
⑷调用DDKReg_GetWindowInfo()和DDKReg_GetIsrInfo()来读取注册表项[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SC2443USBFN]下保存的IRQ、SysIntr、IoBase、IoLen、等信息。
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SC2443USBFN]
"Dll"="sc2443usbfn.dll"
"Prefix"="UFN"
"Priority256"=dword:64
"IoBase"=dword:B0B00000
"IoLen"=dword:1000 ; Use one page
"Irq"=dword:19
"BusIoctl"=dword:2a0048
"IClass"=multi_sz:"{E2BDC372-598F-4619-BC50-54B3F7848D35}=%b","{6F40791D-300E-44E4-BC38-E0E63CA8375C}=%b"
⑸创建访问总线放入句柄pContext->hBusAccess,调用MapRegisterSet将USB device controller register地址映射到虚拟地址空间.,然后设置attachedState为UFN_DETACH,调用ResetDevice函数复位usb function device和端点。
图16
⑹传递MDD层和PDD层的接口,创建usb device线插拔的事件,中断及IST。
图17
下面来学习检测usb device线的插拔的线程PLUG_IST
图18
4.5.3 处理USB device中断的ISTMain()
CUsbFn::Init()->CUsbFn::StartUSBFunction()->UfnMdd_Start()->UfnPdd_Start(),ISTMain在UfnPdd_Start()被创建,先来看这个IST的函数体的前面部分
图19
下面来看后部分
图20
下面来学习HandleUSBEvent()
图21
接着分别介绍HandleUSBEvent函数中调用到的主要函数
⑴ HandleUSBBusIrq()
介绍这个函数之前先来认识一下USB2.0 function的系统状态寄存器(SSR),
图22
HandleUSBBusIrq()函数主要是处理来usb总线中断,记得当系统状态的改变导致的中断发生后,要对SSR寄存器相应的位置1来清除相应的位。如果是下面几种usb总线中断时,如果处于已连接的状态,就需要告诉MDD层来做相应的处理
图23
比如当检查到host发送过来的suspend信号,这时先要判断usb device是否处于连接的状态,如果是就要告诉MDD层来处理,
图24
这样就可以调用UfnMdd_Notify函数来处理这个总线中断请求,其他总线中断请求的处理可做类似的分析
图25
⑵ HandleEndpoint0Event()
先来看端点0相关寄存器
端点0中断寄存器(EIR)
图26
端点0状态寄存器(ESR)
图27
端点0控制寄存器(ECR)
图28
下面来看HandleEndpoint0Event的函数体部分
图29
图30
上面部分的主要工作是准备发送配置包,读取要发送包的字节数,获得USB Device Request指针,读取FIFO数据(EP0 Buffer Register)到pbUdr中。然后对读到的数据进行解析,如果数据长度大于0,获得传输方向,如果数据长度为0,设置sendDataEnd为TRUE表示数据传输完成.
图31
上面部分是根据图30中准备发送的数据包获得是发出数据还是接收数据状态,调用不同的处理流程
图32
⑶HandleEndpointEvent()
图33
图34
图35
CreateBusAccessHandle(LPCTSTR lpActiveRegPath)
该函数用于创建一个可以访问Bus设备驱动的句柄,一个客户端驱动(Client Driver)会在它的XXX_Init函数中调用该函数来获得Bus设备的句柄。lpActiveRegPath为Bus设备的注册表路径,返回值为句柄。pszActiveKey=Drivers/Active/51
Windows CE USB Function Driver驱动简析(1)-
http://blog.csdn.net/shevsten/archive/2010/07/15/5736889.aspx
2410 UDC driver 分析1
http://www.cevx.com/Bbs/viewthread.php?tid=13370
usb驱动
http://www.188928.com/sqd1/20110217/15727.htm
USB 软件、端点和管道
http://blog.csdn.net/Augusdi/archive/2009/05/13/4170026.aspx
wince USB设备驱动程序导读
http://yzcyn.blog.163.com/blog/static/38484300200831834045507/
WinCE系统USB Mass Storage实现
http://www.docin.com/p-55309404.html
WinCE USB驱动开发
http://blog.sina.com.cn/s/blog_4ad0a9940100g11a.html
WinCE系统USB功能定制
http://blog.csdn.net/nanjianhui/archive/2009/08/12/4438599.aspx
有关OHCI、UHCI、EHCI的知识
http://zhuairlunjj.blog.163.com/blog/static/8005094520107203058470/