随着USB2.0设备的不断增加,USB设备驱动开发在嵌入式开发中变的越来越重要。Windows CE支持USB 2.0更是对这一波新技术浪潮产生巨大的推动。近期我负责一个这样的项目,在WinCE下开发USB接口的外围设备驱动。当时做这个项目花费了我相当多的时间和精力,错走许多冤枉路使我精疲力尽。
项目需求是在已调好的ARM9板子上开发USB WiFi无线网卡的驱动程序,具体要求是驱动程序平台是WinCE,CPU类型支持ARM构架,要能比较方便地移植到X86;驱动接口类型是USB2.0和Wlan802.11b。后来因为连接效率一直有问题,就东改西改,最后改的是一塌糊涂。幸好老板比较宽容,给了我充裕的时间和支持,这里将关于USB驱动开发的点滴理解与大家分享。
1. 什么是WinCE设备驱动程序?
(1)从驱动加载方式来区分
在深入探讨Windows CE所支持的外围设备驱动程序之前,先了解在WinCE平台上使用的两种设备:内建设备和可安装设备。因此,从驱动加载方式来看WinCE可分为本机设备驱动(Built-In Driver)、可加载驱动(Loadable Driver)以及混合型驱动。
①本机设备驱动
本机设备驱动即Native Device Drivers。WinCE设计成可直接使用内建设备,这些设备由本机驱动过程控制,而本机驱动程序又与WinCE的核心组件紧密相连。这些驱动对应的设备通常在系统启动时,在GWES的进程空间内被加载,因此它们不是以独立的DLL形式存在,也因此要求每一个本机驱动程序都必须与称为设备驱动程序接口(DDI)的特定接口一致。
本机设备是指整合进平台的设备,其中包括显示、触摸面板、音频、串行埠、LED、电池和PC卡插座等。如果没有这些本机设备整个系统就不能和用户信息交流,例如触摸面板和显示等。本机驱动程序一般设计为动态链接库,但有两个例外:电池和LED驱动程序由于小而设计为静态库(当建立CE图像时与GWES模块链接)。这些设备相应的驱动程序是在WinCE平台开发过程中由OEM开发的,它们储存在ROM或闪存内。通常只有OEM才会对本机设备驱动程序进行修改,其它*设备生产商只提供附加的硬件设备,对本机设备驱动程序不会有过多涉及。
②可加载设备驱动
可加载设备是指可与平台连接和分离的第三方接口设备,可由用户随时安装和卸载。这种外围设备的驱动也被称为流驱动,这些驱动可以在系统启动时或者和启动后的任何时候由设备管理器动态加载。通常这类驱动是以DLL动态链接库的形式存在,系统加载后这些驱动程序也只是以用户态的角色运行。可加载驱动程序是通过文件操作API来从设备管理器和应用程序获得命令。在WinCE典型的可加载驱动有:PCMCIA driver(PCMCIA.dll)、Serial driver(SERIAL.dll)、ATAFLASH driver(ATA.dll)、Ethernet driver(NE2000.dll,SMSC100FD.dll)。
与本机驱动程序不同的是,所有可加载流驱动程序都共享一个公用接口。该接口由每个驱动程序内的10个功能或记录点组成,这些功能与应用程序所用的文件API中的功能匹配。因此,控制可加载设备的流接口驱动程序一般由应用程序存取,流接口驱动程序由一个特殊文件来将设备功能展现给应用程序的,该文件可被打开、读取、写入和关闭。例如,用户将一个GPS设备与平台相连后,就可启动有GPS功能的应用程序来存取并使用该设备。WinCE是使用已有的API来让应用程序存取这些驱动程序,而不是建立新的API。
(2)从驱动程序层次上分类
一般可以分为独立驱动和层次型驱动两类。独立驱动程序是指将驱动程序编写成同时包含Model Device Driver(MDD)和Platform Dependent Driver(PDD)层的独立驱动。使用独立驱动的好处在于可以省去MDD和PDD层驱动之间的信息传递,这一点在实时处理中非常重要。独立驱动的代码包括中断服务例程和平台相关处理函数。另外,如果设备的操作和MDD驱动层的接口描述相吻合,用独立驱动程序可以提高处理性能。
层次型驱动是指分为两层,较上层的MDD和比较下层的PDD。MDD实现的是和平台无关的功能,它描述了一个通用的驱动程序框架;而PDD是和硬件以及平台相关的代码组成。MDD调用PDD中特定的接口来获取硬件相关的信息。当使用层次型驱动的时候,一般只需要基于相近的样列驱动程序,针对特定的硬件只修改PDD程序,MDD建立的框架可继续使用。但由于层次间接口的层层调用以及消息的传递,使得处理速度相对于独立驱动程序要慢。因此,在嵌入式实时要求苛刻的环境下,层次型驱动显得不是很适合。
简单的说,独立驱动是把PDD与MDD写在一起,没有做严格的区分,通常这种驱动比较简单,比如ATADISK。至于本机驱动和加载式流驱动是从驱动与系统其它模块(调用者)的接口形式上做的分类。所以,一个加载式驱动程序可以是独立的流式驱动,例如ATADISK;也可以是分层的流式驱动,例如OHCI。也就是说,独立和分层是驱动实现方式上的分类,而本机和加载流式则是驱动模型上的分类。所谓本机驱动就是操作系统有保留专门的接口,而加载流式驱动是指编写DLL文件导出各种流式接口函数的接口。
2. USB加载式流接口驱动要点分析
为了支持不同类型的外围设备,WinCE平台提供了具有定制接口的流接口驱动程序模型。因为大部分USB外围设备由于功能性更适合流接口驱动的结构,所以一般都采用加载式流接口驱动程序模型来开发USB设备驱动程序。
(1)USB系统结构分析
WinCE下USB系统软件由两层组成:较高USB设备驱动程序层和较低的USB函数层。较低的USB函数层本身又由两部分组成:较高的通用串行总线驱动程序(USBD)模块和较低的主控制器驱动程序(HCD)模块。通过HCD模块功能和USBD模块实现高层的USBD接口函数,USB设备驱动程序就能与外围设备进行通讯。
在数据传输的过程中,操作流程通常按下列的次序进行:①USB设备驱动程序进行数据传输的初始化,即通过USBD接口函数给USBD模块发送数据传输的请求。②USBD模块将该请求分成一些单独的事务。③HCD模块排出事务次序。④主控制器硬件执行事务。这里需要提醒的是,所有的事务都是从主机发出的,外围设备完全是被动接受型的。
(2)USB设备驱动程序入口点函数
从结构分析我们可知,所有的USB设备驱动程序必须在它们的DLL库设置一定的入口点与USBD模块进行适当的交互。设置入口点函数有两个作用:一是使得 USBD 模块能与外部设备交互;二是使得驱动程序能创建和管理任何可能需要的注册键。
下面简要介绍相关函数的作用:USBDeviceAttach是当 USB 设备连接到主计算机时运行,USBD模块会调用这个函数初始化USB设备,取得USB设备信息和配置USB设备,并且申请必需的资源。USBInstallDrive是在第一次加载USB设备驱动程序时首先被调用,它使得驱动程序能创建需要的注册键,用于将一个驱动程序所需的注册表信息写入到HKEY_LOCAL_MACHINE\Drivers\USB \ClientDrivers目录下,例如设备名称等。需要注意的是,USB设备驱动程序不使用标准的注册表函数,而是使用RegisterClientDriverID()、RegisterClientSettings()函数来注册相应的设备信息。
USBUninstallDriver是在用户删除USB设备驱动程序时调用,负责删除注册键并释放其它相关资源。它通过调用UnRegisterClientSettings()和UnRegisterClientDriverID()函数来删除由驱动程序的USBInstallDriver()函数创建的所有注册键。因此,我们在驱动程序中就需要严格按照这三个函数的原型来实现,否则就不能为设备管理器所识别。
3.USB设备流接口驱动的实现步骤
从WinCEUSB设备驱动模型及结构分析中,我们可以清晰的看到主机和外设之间的实现方式。在主机端,通过USBD模块和HCD模块使用默认的PIPE访问一个通用的逻辑设备,实际上就是说USBD和HCD是一组访问所有USB设备的逻辑接口,它们负责管理所有USB设备的连接、加载、移除、数据传输和通用配置。其中HCD是主机控制驱动,是为USBD提供底层的功能访问服务,USBD是USB总线驱动,位于HCD的上层,利用HCD的服务提供较高层次的功能。因此,实现USB加载流驱动程序大致需要完成以下步骤:
(1)选择代表设备的文件名前缀。前缀非常重要,设备管理器在注册表中通过前缀来识别设备。同时,在流接口命名时也将这个前缀作为入口点函数的前缀,如果设备前缀为XXX,那么流接口对应为XXX_Close,XXX_Init等。
(2)设置驱动的各个入口点函数。所谓入口点是指提供给设备管理器的标准文件I/O接口。在生成一个DLL后,就用设备文件名前缀替换名字中的XXX。因此,每个加载式流接口驱动程序必须实现XXX_Init()、XXX_IOControl()以及XXX_PowerUp()等一组标准的函数,用来完成标准的文件I/O函数和电源管理等。
(3)建立.DEF文件。当设备管理器初始化USB设备编译出来的流接口函数后,还必须建立一个.def文件。DEF文件定义了DLL要导出的接口集,而且加载式流驱动大多是以DLL形式存在的,所以应将DLL和DEF的文件名统一起来。DEF文件告诉链接程序需要输出什么样的函数,最后将驱动程序编译到内核中去,这样这个USB设备流接口驱动程序就可以被应用程序调用。
(4)在注册表中为驱动程序建立表项。在注册表中建立驱动程序入口点,这样设备管理器才能识别和管理这个驱动。此外,注册表中还能存储额外的信息,这些信息可以在驱动运行之后被使用到。
在这次USB驱动开发过程中,错走许多冤枉路使我叫苦连天。我感受最深的是由于WinCE提供了通用串行总线驱动程序(USBD)模块、USBD接口函数全集、样本主机控制器驱动程序(HCD)模块。所以,我们只需要根据USB设备硬件特性,利用USBD提供的不同函数,实现流接口函数与外围设备的交互。在没有特别的情况下,我最大的收获经验是把这些公用的源程序照搬过来,能极大的缩短开发周期,从而能更快速地进行嵌入式开发。