1. Windows CE驱动简介
1.0. 概述
设备驱动程序是操作系统内核和硬件的接口,操作系统定义了一组标准的接口,编写驱动的过程也就是实现这些接口。从应用程序到具体硬件间要经过如下步骤:应用程序调用OS函数-操作系统-驱动接口-驱动程序-硬件操作函数-硬件。在wince里驱动都以用户态的 DLL存在,需要通过进程加载到slot里。共有三类系统进程用来加载:Device.exe,GWES.exe,FileSys.exe。绝大多数设备驱动都是通过Device.exe加载的。不同的OS保留的设备驱动接口是不一样的,如桌面windows和wince就不同。
wince下设备的初始化分为两个阶段:Device.exe的初始化;外设的枚举和加载。其流程是:上电-启动bootloader-启动NK-启动注册表init键(Device.exe启动)-初始化数据结构、I/O、电源等-加载 BusEnum.dll(总线枚举器)-枚举注册表下Driver/buildin的所有子键。这里的枚举过程就是循环调用 ActivateDeviceEx()函数加载驱动的过程。在OS启动完毕后,我们可以用PB的Remote Registry Tool查看H_L_M/drivers/active包含的子键,看哪些驱动随启动而加载 。
1.1. 驱动开发工具
Windows CE驱动可以采用Platform Builder或VS开发。BSP开发人员和IHVs(Independent Hardware Vendors)一般采用Platform Builer开发驱动,应用程序开发人员较多采用VS开发驱动。
下表对PB和VS作了一个比较:
工具 比较项 | PB | VS |
调试 | 采用内核调试工具调试,可实现真正的内核级调试 | 只能通过打印信息实现应用程序级别的调试 |
可开发的驱动类型 | 全部 | 仅限网络和PC卡驱动 |
是否支行PPC设备调试 | 否 | 是 |
发布方式 | CAB | CAB |
1.2. 驱动程序分类
按加载方式和接口类型(驱动与系统其它模块(调用者)的接口形式)可分为本机驱动(Built-In Drivers)、流驱动(Stream Drivers)和混合型驱动。
n 本地驱动
通常由GWES(图形对象事件子系统)加载,驱动对外的接口一般都是定制的。调用本地驱动的模块给了它确定的要实现的接口,比如电源驱动和通用LED驱动,操作系统有专门的接口。
n 流驱动
流驱动通常由Device Manager加载,驱动对外的接口为标准的流式驱动接口,如串口,网卡驱动。最终的dll文件中应该可以导出各种标准流式接口函数。它把外设抽象成一个文件,应用程序使用文件API(CreateFile、DeviceIoControl、 ReadFile、 WriteFile,CloseHandle等)对设备进行访问,OS接受文件API调用FileSys.exe,转到device.exe,调用对应的流接口函数,与硬件交互。流驱动与OS和硬件的关系如下所示:
n 混合型驱动
既有定制的接口也有标准的流式驱动接口,和系统交互时只使用流式驱动接口,如PC卡操驱动。
按驱动的层次(实现方式)分为层次型驱动(Layered Driver)和独立型驱动(Monolithic Driver)。
n 层次型驱动
分层驱动分PDD与MDD两层:
MDD(Model Device Driver)
与硬件无关,面向上层应用程序,一般由微软建立统一框架。具有如下特点:
包含给定类型的所有驱动程序所共有的代码
调用 PDD 函数以访问硬件
链接到 PDD 层,并且定义MDD 期望在该层中调用的设备驱动程序服务提供程序接口 (DDSI) 函数
向操作系统 (OS) 公开设备驱动程序接口 (DDI) 函数
OS 的其他部分可以调用这些函数,相关设备可以共享相同的 DDI,整体式驱动程序还公开 DDI 函数
处理中断处理
可供开发人员重用
可以链接到多个PDD
通常不需要进行更改
如果进行了更改,则在将驱动程序迁移到将来的版本时可能会遇到麻烦
包含任何中断服务线程(IST)
PDD(Platform Dependent Driver)
针对具体硬件平台的操作代码,一般由驱动开发商实现,具有如下特点:
由硬件平台特有的代码组成
可能需要修改硬件平台
专门用于使用特定的 MDD 实现
公开 MDD 调用的 DDSI 函数
整体式驱动程序不公开 DDSI 函数
MDD和PDD之间通过标准的设备驱动服务供应商接口DDSI连接。
MDD、PDD及DDSI间关系如下图所示:
一般微软已经实现了MDD,可能也实现了PDD,我们只需要对PDD做些修改就能使用,比如音频的驱动,显示的驱动。
n 整体式驱动
整体式驱动程序包含了MDD和PDD两方面的代码,适用于操作不复杂的驱动,其优点是减少了MDD和PDD传递之间传递信息的开销,实时性更强。
在分层驱动和整体驱动间选择时考虑如下原则:
分层驱动程序可能只需要修改 PDD
分层驱动程序增加了设备驱动程序中的函数调用的系统开销,因为 MDD 调用到 PDD 中
整体式驱动程序改进了驱动程序性能,因为它将 MDD 和 PDD 到组合一个层之中,这消除了 MDD 对 PDD 进行的函数调用
整体式驱动程序更难以迁移到将来版本的 Windows CE,因为 Windows CE 所包含的大多数设备驱动程序都被划分为一个 PDD 和一个 MDD
如果设备的功能与 MDD 层中的函数执行的任务很好地匹配,则整体式驱动程序可以更简单、更有效
2. 流驱动开发
2.1. 流驱动(开发)的特点
Ø 必须实现一套标准接口
Ø 对I/O操作非常适用
Ø 应用程序可通过操作文件的接口操作流驱动
2.2. 实现流驱动
2.2.1. 选择代表设备的文件名前缀代表设备的文件名前缀将会在流驱动的标准接口函数中体现,必须依照以下规则选取:
Ø 必须是三个字母,系统中存在多个同类时接后接一位阿拉伯数字区分
Ø 必须唯一
2.2.2. 实现流设备驱动的标准接口函数
rel="File-List" href="file:///C:%5CDOCUME%7E1%5Czzx%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
1) XXX_Init:通知设备管理器为设备初始化分配资源
DWORD XXX_Init( DWORD dwContext )
Ø 参数dwContext指向一个描述设备接口的字符串,通常为流接口驱动在注册表中的对应项下的Prefix值
Ø 函数返回设备上下文句柄
Ø 描述:当用户开始使用一个设备的时候,例如,当PC卡初始化的时候,设备管理器调用这个函数来初始化PC卡设备。这个函数并不是由应用程序直接调用的,而是通过设备管理器提供的ActivateDeviceEx()函数来调用的。函数执行后如果成功则返回一个设备的句柄。
2) XXX_Deinit:通知设备管理器回收设备初始化时分配的资源
BOOL XXX_Deinit(DWORD hDeviceContext)
Ø 参数hDeviceContext指向设备上下文句柄,由XXX_Init创建时生成的设备句柄
Ø 函数返回是否卸载成功
Ø 描述:当一个用户需要卸载一个驱动程序的时候,设备管理器调用这个函数来卸载这个驱动程序,应用程序不能够直接调用这个函数设备管理器,通过DeactivateDevice()函数调用这个函数。
3) XXX_Open:应用程序调用CreateFile时通过文件系统映射为XXX_Open打开硬件设备
DWORD XXX_Open( DWORD hDeviceContext,
DWORD AccessCode,
DWORD ShareMode)
Ø hDeviceContext是设备上下文句柄,由XXX_Init函数创建的时候返回
Ø AccessCode是打开设备的权限描述符
Ø ShareMode是设备的文件共享模式
Ø 函数返回设备打开后的上下文句柄
Ø 描述:这个函数用于打开一个设备驱动程序,当应用程序准备对某一个设备进行读或写操作时,系统必须先执行CreateFile()这个函数用于打开这个设备,CreateFile 会映射到XXX_Open,这个函数执行了以后系统才能够执行读和写操作。
4) XXX_Close:应用程序调用CloseFile时通过文件系统映射为XXX_Close关闭硬件设备
DWORD XXX_Close(DWORD hOpenContext)
Ø hOpenContext是要关闭的设备上下文句柄,调用XXX_Open时获得
Ø 非0返回代表函数关闭失败
Ø 描述:这个函数用于关闭一个通过XXX_Open打开的驱动程序的实例。应用程序通过CloseHandle() 来调用这个函数,当执行完这个函数的时候驱动实例hOpenContext将不再有效。
5) XXX_PowerUp:上电时(设备)OS调用此函数完成必要的上电操作
void XXX_PowerUp(DWORD hDeviceContext)
Ø hDeviceContext是设备的上下文句柄,XXX_Init生成
6) XXX_PowerDown:掉电时(设备)OS调用此函数完成必要操作
void XXX_PowerDown(DWORD hDeviceContext)
Ø hDeviceContext是设备的上下文句柄,XXX_Init生成
Ø 描述:PowerDown、PowerUp这两个函数通常都必须要硬件的支持才能够有效,也就是说相关的硬件必须支持PowerDown和PowerUp这两个模式。
7) XXX_Read:应用程序调用ReadFile映射为XXX_Read从打开的设备中读数据
DWORD XXX_Read( DWORD hOpenContext,
LPVOID pBuffer,
DWORD count)
Ø hOpenContext是打开设备的上下文句柄,由CreatFile()返回
Ø pBuffer指向应用程序用于存放读取数据的缓冲区
Ø Count指定从设备中读取多少字节的数据
Ø 函数返回实际读取的数据数量
Ø 描述:这个函数与ReadFile很相似,当一个流接口驱动程序对应的设备已经被打开后,可以使用ReadFile()函数对这个设备进行读操作,ReadFile()里面的hFile 参数就是这个设备的引用实例句柄hOpenContext。
8) XXX_Write:应用程序调用WriteFile映射为XXX_Write向打开的设备写数据
DWORD XXX_Write( DWORD hOpenContext,
LPVOID lpBuffer,
DOWRD count)
Ø XXX_Write的参数与XXX_Read的参数相似,方向相反
Ø 函数返回实际写入的数据数量
Ø 描述:当一个流接口驱动程序打开后,可以使用WriteFile()函数进行写操作。
9) XXX_Seek:设备定位,是否支持视具体硬件而定
DWORD XXX_Seek( DWORD hOpenContext,
long Amount,
WORD type)
Ø hOpenContext是打开设备的上下文句柄
Ø Amount指定指针要移动多少字节,正值向文件尾移动,负值向文件头移动
Ø type描述了设备文件起始点位置
Ø 函数返回设备文件的当前指针
10) XXX_IOControl:I/O操作扩展,视具体硬件而定
BOOL XXX_IOControl( DWORD hOpenContext,
DWORD dwCode,
PBYTE pBufIn,
DWORD dwLenIn,
PBYTE pBufOut,
DWORD dwLenOut,
PDWORD pdwActualOut)
Ø hOpenContext是打开设备的上下文句柄,由CreateFile返回
Ø dwCode指定了IO操作的识别码,这些识别码由设备开发人员自定义
Ø pBufIn指向输入缓冲区的地址,其中有给驱动程序使用的数据
Ø dwLenIn指定了输入数据的长度
Ø pBufOut指向输出缓冲区的地址,其中有驱动传给应用程序的数据
Ø dwLenOut指定了需要输出的数据长度
Ø pdwActualOut是DWORD指针,需要知道从设备中实际读取的数据长度
Ø 函数返回操作是否成功
Ø 描述:这个函数通常用于向设备发送一个命令。应用程序使用DeviceIOControl函数来通知操作系统调用这个函数。通过参数dwCode来通知驱动程序要执行的操作。这个函数扩展了流接口驱动程序的功能。另外用户可以通过更改HKEY_LOCAL_MACHINE/Drivers/BuiltIn/YourDevice/Ioctl健来使自己的流接口驱动程序在加载的时候执行固定的操作。
2.2.3. 建立.def文件
流驱动一般以dll形式存在,通过DEF文件定义dll需要导出的接口函数,DEF文件的名称应该与设备驱动的名称相同。
以下为一个具体的DEF文件示例:
SampleDrv.DEF
LIBRARY SampleDrv
EXPORTS
SDV_Init
SDV_Deinit
SDV_Open
SDV_Close
SDV_Read
SDV_Write
SDV_Seek
SDV_IOControl
SDV_PowerUp
SDV_PowerDow
SDV_DLLEntry
为了加载前边的流驱动SampleDrv应该在注册表中增加流驱动的入口点,需要做以下工作:
1) 在注册表的[HKEY_LOCAL_MACHINE/Drivers/BuiltIn]下建立子项
2) 在1)中建立的子项SampleDrv下建立键值对,Prefix和DLL是必须的两个键
3) 建立其它子键
收下为针对SampleDrv 的注册表示例:
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SampleDrv]
“Prefix”=”SDV” //设备前缀名
“Dll”=SampleDrv.dll //流驱动对应的dll
“Order”=dword:2
“Ioctl”=dword:4
2.3. 流驱动开发实例
参见http://msdn.microsoft.com/zh-cn/library/aa446913.aspx。
2.4. 驱动调试
可以利用如下方法对驱动进行调试:
1) 调试区信息(Debug Zone)
一般和WinCE的控制台调试工具Cesh.exe配合使用,具有以下特点:
Ø 可在不打断OS运行的情况下进行驱动的实时调试
Ø 可利用宏开关控制需要输出的调试区信息
Ø 可以得到进程、线程、和调试状态信息
Ø 可以利用IDE动态选择开关调试区信息
2) 驱动程序输出调试信息
通过调用RETAIILMSG或DEBUGMSG完成,是最简单也是使用最广泛的一种驱动调试方法,具有如下特点:
Ø 必须借助至少一种外设显示调试信息
Ø 可动态输出设备状态信息
Ø 基本不影响OS运行保证了驱动程序运行的真实性
3) 核心调试工具(Kernel Debugger)
利用核心调试工具调试驱动时必须在PB下利用至少一种外设进行,具有如下特点:
Ø 禁止所有硬件中断,挂起OS
Ø 可以访问堆栈信息
Ø 可以单步调试OS或核心代码
4) 硬件辅助调试
硬件辅助调试利用硬件调试工具观察物理设备的真实状态,具有如下特点:
Ø 可以实时查看CPU的内部寄存器(利用JTAG)
Ø 可以实时查看物理外设的输出输出状态(利用逻辑分析仪或示波器)
Ø 可以实时显示驱动的状态信息(利用LED)
5) VS调试
利用VS内置工具进行状态调试,单步跟踪等。
2.5. 驱动测试
Windows的驱动测试有如下方法:
Ø 写一个简单的应用程序来测试驱动程序的正确性
Ø 模拟各种可能发生的硬件输入状态来测试驱动程序的正确性
Ø 利用Windows CE自带的测试工具CETK来测试驱动程序的性能和完备性
2.6. 驱动程序的集成
在已有的BSP中集成新的驱动程序按以下步骤进行:
n 在BSP的Driver目录下建立新的驱动文件夹SampleDrv
n 实现SampleDrv驱动以及相关的DEF文件
n 如果需要用到硬件中断资源,修改原BSP中的相关中断处理函数OEMInterruptEnable,OEMInterruptDisable, OEMInterruptDone, OEMInterruptHandler
n 在Platform.reg中,增加驱动程序相关项
n 在Platform.bib中,增加驱动程序的相关注册表项
n SampleDrv.dll $(_FLATRELEASEDIR)/SampleDrv.dll NK SH
2.6. 驱动程序的集成和发布
驱动的发布有如下两种方式:
1)利用CAB Wizard生成.cab驱动包
2)直接提供驱动程序文件夹以及相关注册表项和修改说明
参考文献
[1]. Windows CE 驱动开发基础. 陈黎.
[2]. 分层驱动程序与整体式驱动程序之比较. MSDN.
[3]. wince驱动开发学习笔记. milkyway的窝.