wince驱动开发2
四、驱动的编写
1、 wince驱动介绍
1:WinCE毕竟是一个嵌入式系统,有其自身的特殊性,为了提高运行效率,所有驱动皆为动态链接库,驱动实现中可以调用所有标准的API。而在其他Windows系统中可能的驱动文件还有.vxd, .sys和动态链接库。
2:WinCE驱动从结构上讲分为本地驱动(Native Driver)和流接口驱动(Stream Driver)。
——本地驱动主要用于低级、内置的设备。实现它们的接口并不统一,而是针对不同类型的设备相应设计。因此开发过程相对复杂,没有固定的模式,一般做法是通过移植、定制现有的驱动样例来实现。
——流接口驱动是最基本的一种驱动结构,它的接口是一组固定的流接口函数,具有很高的通用性,WinCE的所有驱动程序都可以通过这种方式来实现。流接口驱动程序通过文件系统调用从设备管理器和应用程序接收命令。该驱动程序封装了将这些命令转换为它所控制的设备上的适当操作所需的全部信息。
流接口驱动是动态链接库,由一个叫做设备管理程序的特殊应用程序加载、管理和卸载。与本地驱动程序相比,所有流接口驱动程序使用同一组接口函数集,包括实现函数:XXX_Init、XXX_Deinit、XXX_Open、XXX_Close、XXX_Read、XXX_Write、XXX_PowerUp、XXX_PowerDown、XXX_Seek、XXX_IOControl,这些函数与硬件打交道。
用户函数:CreateFile、DeviceIoControl、 ReadFile、 WriteFile,这些函数方便用户使用驱动程序。
3:WinCE下驱动的加载方式:
——通过GWES(Graphics, Windowing, and Events
Subsystem):主要加载与显示和输入有关的驱动,如鼠标、键盘驱动等。这些驱动一般为本地驱动。
——通过设备管理器:两种结构的驱动都加载,加载的本地驱动主要由PCMCIA Host Controller,USB Host Controller driver,主要是总线类的驱动;流接口驱动主要有音频驱动,串并口驱动。
——动态加载:前两者都是系统启动时加载的,动态加载则允许设备挂载上系统时将驱动调入内核,主要有外接板卡驱动,USB设备驱动等。
2、流接口驱动函数介绍:
1:DWORD XXX_Init(LPCTSTR pContext, LPCVOID lpvBusContext);
pContext:指向一个字符串,包含注册表中该流接口活动键值的路径
lpvBusContext:
该函数是驱动挂载后第一个被执行的。主要负责完成对设备的初始化操作和驱动的安全性检查。由ActiveDeviceEx通过设备管理器调用。其返回值一般是一个数据结构指针,作为函数参数传递给其他流接口函数。
2:BOOL XXX_Deinit(DWORD hDeviceContext);
hDeviceContext:XXX_Init的返回值。
整个驱动中最后执行。用来停止和卸载设备。由DeactivateDevice触发设备管理器调用。成功返回TRUE。
3:DWORD XXX_Open(DWORD hDeviceContext, DWORD AccessCode , DWORD ShareMode);
hDeviceContext:XXX_Init的返回值。
AccessCode:访问模式标志,读、写或其他。
ShareMode:驱动的共享方式标志。
打开设备,为后面的操作初始化数据就够,准备相应的资源。应用程序通过CreateFile函数间接调用之。返回一个结构指针,用于区分哪个应用程序调用了驱动,这个值还作为参数传递给其他接口函数XXX_Read、XXX_Write、XXX_Seek、XXX_IOControl。
4:BOOL XXX_Close(DWORD hOpenContext);
hOpenContext:XXX_Open返回值。
关闭设备,释放资源。由CloseHandle函数间接调用。
5:DWORD XXX_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count);
hOpenContext:XXX_Open返回值。
pBuffer:缓冲区指针,接收数据。
Count:缓冲区长度。
由ReadFile函数间接调用,用来读取设备上的数据。返回读取的实际数据字节数。
6:DWORD XXX_Write(DWORD hOpenContext, LPCVOID pBuffer, DWORD Count);
hOpenContext:XXX_Open返回值。
pBuffer:缓冲区指针,接收数据。
Count:缓冲区长度。
由WriteFile函数间接调用,把数据写到设备上,返回实际写入的数据数。
7:BOOL XXX_IOControl(DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut);
hOpenContext:XXX_Open返回值。
dwCode:控制命令字。
pdwActualOut:实际输出数据长度。
用于向设备发送命令,应用程序通过DeviceIoControl调用来实现该功能。要调用这个接口还需要在应用层和驱动之间建立一套相同的命令,通过宏定义CTL_CODE(DeviceType, Function, Method, Access)来实现。如:
#define IOCTL_INIT_PORTS \
CTL_CODE(FILE_DEVICE_UNKNOWN,0X801,METHOD_BUFFERED,FILE_ANY_ACCESS)
8:void XXX_PowerDown(DWORD hDeviceContext);
hDeviceContext:XXX_Init的返回值。
负责设备的上电控制。
9:void XXX_PowerUp(DWORD hDeviceContext);
hDeviceContext:XXX_Init的返回值。
负责设备的断电控制
10: DWORD IOC_Seek(DWORD hOpenContext, long Amount, WORD Type)
hOpenContext:XXX_Open返回值。
Amount:指针的偏移量。
Type:指针的偏移方式。
将设备的数据指针指向特定的位置,应用程序通过SetFilePointer函数间接调用。不是所有设备的属性上都支持这项功能。
3、流接口驱动的加载和注册表设置:
系统启动时启动设备管理程序,设备管理程序读取HKEY_LOCAL_MACHINE\Drivers\BuiltIn键的内容并加载已列出的流接口驱动程序。因此注册表对于驱动的加载有着关键作用。下面是一个例子:
【HKEY_LOCAL_MACHINE\Drivers\BuiltI\IOControler】
"Prefix"="XXX"
"Dll"="drivername.dll"
"Order"=dword:0
"DeviceArrayIndex"=dword:0
"Prefix"="XXX"中的XXX要和XXX_Init等函数中的一样。CreateFile创建的驱动名前缀也必须和它们一致。
Dll是列出需要加载的驱动名称
注册表存在多个设备,如串口有3个,Order表明加载串口N的顺序
DeviceArrayIndex是应用使用CreatFile时打开设备的名字,如0为“COM1“,1为”COM2“…
注册表还可以加入许多驱动需要的信息,驱动中可以使用RegQueryValueEx读取。
4、程序的编写、编译及其相关目录、配置文件的格式和修改:
1:先必须在PB相应平台的的driver目录下建立要创建的驱动所在的目录。如在x: \WINCE500\PLATFORM\AK3224\SRC\DRIVERS\目录下建立一个GPIO
2:改Drivers目录下的dirs文件。
3:建驱动源文件XXX.c,在该文件中实现上述流接口函数。并且加入DLL入口函数:
BOOL DllEntry(HINSTANCE hinstDll, /*@parm Instance pointer. */
DWORD dwReason, /*@parm Reason routine is called. */
LPVOID lpReserved /*@parm system parameter. */
)
4:建Makefile和Sources和.def文件,控制编译。
5:用CEC Editor修改cec文件,编译添加的新特性。
6:修改注册表文件platform.reg和platform.bib文件。
7:Build & Make Image。
注:驱动也可以在PB中建立project来创建Dll
5、WINCE的内存配置
WINCE的内存(包括SDRAM及FLASH)的配置包含两个方面:源代码(包括C和汇编)中的定义,及系统配置文件CONFIG.BIB中的定义。源代码中需要定义内存的物理及虚拟地址,大小,并初始化名为OEMAddressTable的结构数组,以告知系统物理地址与虚拟地址的对应关系,系统根据其设置生成MMU页表。而CONFIG.BIB中一般会将内存定义成不同的段,各段用作不同的用途。
CONFIG.BIB文件:
CONFIG.BIB文件分两个部分,我们且称之为段,MEMORY段和CONFIG段。MEMORY段定义内存的分片方法,CONFIG段定义系统其它的一些属性。以下是一个CONFIG.BIB文件MEMORY段的例子:
MEMORY
; 名称 起始地址 大小 属性
RESERVED 80000000 00008000 RESERVED
DRV_GLB 80008000 00001000 RESERVED
CS8900 80010000 00030000 RESERVED
EDBG 80040000 00080000 RESERVED
NK 800C0000 00740000 RAMIMAGE
RAM 81000000 00800000 RAM
名称原则上可以取任意字符串,ROMIMAGE通过一个内存片的属性来判断它的用途。RESERVE属性表明该片内存是BSP自己使用的,系统不必关心其用途;RAMIMAGE说明它是一片存放OS IMAGE的内存;而RAM则表示些片内存为RAM,系统可以在其中分配空间,运行程序。
但存放ROM的这片内存的名称,即NK一般不要改动。因为BIB文件中定义将一个文件加入到哪个ROM片(WINCE支持将ROM IMAGE存放在不连续的几个内存片中)中时会用到这个名称,如下现这行BIB文件项就定义将touch.dll放在名称为NK这片ROM中,
touch.dll $(_FLATRELEASEDIR)\touch.dll NK SH
因而,如果将NK改为其它名称,则系统中所有的BIB文件中的这个NK串都需要改动。
注意:保证各片内存不要重叠;而且中间不要留空洞,以节约内存。
两种设备如果不能同时被加载,就应该只为其保留一片从而节约内存,例如,本例中的CS8950是为网卡驱动程序保留的,EDBG是为网卡作调试(KITL)用时保留的,而系统设计成这两个程序不会同时加载(CS8950在启动时判断如果EDBG在运行就会自动退出),这样为这两个驱动程序各保留一片内存实在浪费而且也没有必要。
RAM片必须在物理上是连续的,如果系统的物理内存被分成了几片,则在RAM片只能声明一片,其它的内存在启动阶段由OEMGetExtensionDRAM报告给系统,如果有多于一个的内存片,应该用OEMEnumExtensionDRAM报告。NK片则没有此限制,只是NK跨越两个以上物理内存片时,系统启动时会显示这个OS包跨越了多个物理内存片,认为是个错误,但并不影响系统的执行与稳定性,因为系统启动之时便会打开MMU而使用虚拟地址,从而看到连续的内存空间。当然,如果内核自己都被放在了两个内存片上,那系统应该就无法启动了。而其它保留起来的内存片是一般是给驱动程序DMA用,应该保证它们在物理上的连续性,因为DMA是直接用物理地址的。
CONFIG段中以下几个需要格外注意:
ROMSTART,它定义ROM的起始位置,应该和NK片的起始位置相同。
ROMSIZE,定义ROM的大小,应该和NK片的大小相同。
如果不需要NK。BIN文件,则可以不设这两个值。
ROMWIDTH,它只是定义ROMIMAG生成ROM包时如何组织文件,而非其字面含义:ROM的宽度,所以一般都应该为32
COMPRESSION,一般定义为ON,以打开压缩功能,从而减小BIN文件的尺寸。
AUTOSIZE,一般应该设为ON,以使系统将定义给ROM但没有用掉的内存当做RAM使用,而提高RAM的使用率。注意,如果ROM是FLASH,则不能设为ON,因为FLASH不能当作RAM使用。
ROMOFFSET,它定义OS起始位置(即ROMSTART)的物理地址和虚拟地址的差值,有些BSP中并没有使用这个定义。
OEMAddressTable及其它:
OEMAddressTable用来初始化系统中各种设备的虚拟地址与物理地址的对映关系。在我使用的BSP中,它是这样定义并初始化的:
typedef struct
{
ULONG ulVirtualAddress;
ULONG ulPhysicalAddress;
ULONG ulSizeInMegs;
} AddressTableStruct;
#define MEG(A) (((A - 1)>>20) + 1)
const AddressTableStruct OEMAddressTable[] =
{
{ SDRAM_VIRTUAL_MEMORY, //虚拟地址
PHYSICAL_ADDR_SDRAM_MAIN, //物理地址
MEG(SDRAM_MAIN_BLOCK_SIZE) //这段空间的大小,以M计
},
………………………
{
0,
0,
0
}
};
如例子所示,OEMAddressTable为一个结构数组,每项的第一个成员为虚拟地址,第二个成员为对应的物理地址,最后一个成员为该段空间的大小。这个数组的最后一项必须全部为0,以示整个数组的结束。内核启动时会读取这个数组的内容以初始化MMU页表,启用MMU,从尔使程序可以用虚拟地址来访问设备。当然,OEMAddressTable中所用到的每个物理地址及虚拟地址都需要在头文件中定义,每个BSP中定义这些值的文件不尽相同,所以,在此不能说明具体在哪个文件,读者朋友可以参考具体BSP的文档及代码。
(\WINCE500\PLATFORM\AK3224\SRC\BOOTLOADER\EBOOT\boot.bib)
不连续内存的处理:
如果内存在物理上是连续的,则OEMAddressTable中只需要一项就可以完成对内存的地址映射。但如果BSP运行在SDRAM物理上不连续的系统上时,OEMAddressTable中需要更多的项来将SDRAM映射到连续的虚拟地址上,当然也可以将它们映射到不连续的虚拟地址上,但似乎没有理由那么做。而且,当其物理地址不连续时系统需要做更多的工作。例如一个系统:32M SDRAM,16M FLASH,SDRAM在物理上不连续,被分成了4个8M的内存块CONFIG。BIB文件的MEMORY段如下所示:
MEMORY
RESERVED 80000000 00008000 RESERVED
DRV_GLB 80008000 00001000 RESERVED
CS8900 80010000 00030000 RESERVED
EDBG 80040000 00080000 RESERVED
NK 800C0000 00940000 RAMIMAGE
RAM 81800000 00800000 RAM
在这32M的空间中,BSP保留了前0x80000字节,接下来是NK,它占用了0x940000字节,而且它跨越了两个内存片,这些和其它BSP的设置都没有多大差别,接下来看RAM片,它只占用了最后的8M空间,前面说过,在这种物理内存不连续的系统中,RAM片不能跨越两个物理内存块,所以它被设计成只占用该系统中的最后一个物理内存片,而其它两片则由OEMEnumExtensionDRAM在运行时刻报告给系统,该函数的内容如下:
pMemSections[0].dwFlags=0;
pMemSections[0].dwStart=(SDRAM_VIRTUAL_MEMORY + 0x1000000);
pMemSections[0].dwLen=0x800000;
pMemSections[1].dwFlags=0;
pMemSections[1].dwStart=(SDRAM_VIRTUAL_MEMORY + 0x0A00000);
pMemSections[1].dwLen=0x600000;
return 2;
这样,系统所有的内存都被激活,系统可用内存就变成了8+8+6=24M,可以将RAM定义为这三片中的任意一片,而在OEMEnumExtensionDRAM中报告其它两片。但把RAM放在最后一片物理内存上有一个很大的好处,即如果NK变大,例如编译一个DEBUG版的系统时,这时,只需要将OEMEnumExtensionDRAM中的内容注释掉,CONFIG.BIB文件不用做任何改动,系统就可运行,只是在MAKEIMG时会有一个警告说系统包太大,可能无法运行,但实际不会影响系统的执行与稳定性,因为NK之后的那段内存并没有被使用,正好被涨大的系统占用,这在调试时极其方便。
而如果系统物理内存是连续的,所有者系统可用内存都可以定义在RAM片中。
对硬件知识了解不多请注意:SDRAM是否在物理上连续,与我们的板上有几片SDRAM没有关系,应该向硬件工程师了解SDRAM的地址分布情况。