分享
 
 
 

Windows CE下驱动程序开发基础

王朝system·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

这是我从1月6日开始主持天极网论坛嵌入式开发版以来第一次发表文章,加上以前琐碎的文章共计30篇。研究的越多就越感觉自己懂的太少,其实在驱动开发方面我还是个菜鸟,我是想再次抛砖引玉,让做驱动有N年经验的人奉献一点出来,让大家减少一些研究驱动源码而又缺少注释所带来的痛苦。

我想即使读者看过微软的关于驱动开发的培训教材和CE帮助文档中的驱动部分,头脑中仍然一片茫然。要想真正了解驱动程序必须结合一些驱动程序源码,在此我以串口驱动程序(COM16550)中初始化过程为线索简单讲一讲驱动开发的基础知识。

Windows CE下的串口驱动程序能够处理所有I/O行为类似串口的设备,包括基于16450、16550 UART(通用异步收发芯片)的设备和一些采用DMA的设备,常见的有9针串口、红外I/O口、Modem等。在%_WINCEROOT%\Public\Common\OAK\Drivers\Serial目录下,COM_MDD2子目录包含新的串口驱动MDD层函数代码。COM16550子目录包含串口驱动PDD层代码。SER16550子目录包含的一系列函数专用于控制与16550兼容的UART,这样PDD层的主要工作就是调用SER16550中的函数。还有一个ISR16550子目录包含的是串口驱动程序专用的可安装ISR(中断服务例程),而很多硬件设备驱动程序采用CE默认的可安装ISR giisr.dll。一般串口设备相应的注册表设置例子及意义如下:

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Serial_1]

键 意义

"SysIntr"=dWord:13 串口1的中断ID为十进制13

"IoBase"=dword:02F8 串口1的IO空间首地址为十六进制2F8

"IoLen"=dword:8 串口1的IO空间长度为8个字节

"DeviceArrayIndex"=dword:0 串口1的索引,是1的由来

"Order"=dword:0 串口1驱动的加载顺序

"DeviceType"=dword:0 串口1的设备类型

"DevConfig"=hex: 10,00 .... 串口1在与Modem设备通讯时的配置,如波特率、奇偶校检等

"FriendlyName"="COM1:" 串口1在拨号程序中显示的名字

"Tsp"="Unimodem.dll" 串口1 被用于与Modem设备通讯的时候要加载的TSP(TAPI Service provider)DLL

"Prefix"="COM" 串口1的流接口的前缀

"Dll"="com16550.Dll" 串口1的驱动程序DLL

SysIntr由CE在文件Nkintr.h中预定义,用于唯一标识中断设备。OEM可以在文件Oalintr.h中定义自己的SysIntr。常见的预定义SysIntr有SYSINTR_NOP(中断只由ISR处理,IST不再处理),SYSINTR_RESCHED(重新调度线程),SYSINTR_DEVICES(由CE预定义的设备中断ID的基值),SYSINTR_PROFILE、SYSINTR_TIMING、SYSINTR_FIRMWARE等都是基于SYSINTR_DEVICES定义的。IoBase是串口1的IO地址空间的首地址,IoLen是IO空间的大小。IO地址空间只存在于x86平台,假如在其它平台硬件寄存器必须映射到物理地址空间,那子键的名称为MemBase和MemLen。在x86平台更多硬件的寄存器由于IO空间的局限也映射到物理地址空间。DeviceArrayIndex是设备的索引,用于区分同类型的设备。Prefix是流驱动程序的前缀,当应用程序调用CreateFile函数传递COM1:参数时,文件系统负责与串口驱动程序通信,串口驱动程序是在CE启动时由device.exe加载的。

下面从MDD层函数COM_Init开始探索串口驱动的初始化过程。COM_Init是在串口设备被检测后由设备治理器device.exe调用的,主要的作用是初始化设备,它的唯一参数Identifier是由device.exe传递的,其类型是一个字符串指针,字符串的内容是HLM\Drivers\Active\xx,xx是一个十进制数(device.exe会跟踪系统中每个驱动程序,把加载的驱动程序记录在Active键下)。

COM_Init先分配一个HW_INDEP_INFO结构体,这个结构体是独立于串口硬件的头信息(MDD、PDD、SER16550都包含自己独特的结构体,具体的结构体定义请参见串口驱动源码),分配之后再初始化结构体中每个成员,初始化结构体后调用 OpenDeviceKey((LPCTSTR)Identifier)打开HLM\Drivers\Active\xx\Key包含的注册表路径,在这里路径一般为HLM\Drivers\BuiltIn\Serial,即串口的驱动程序信息在注册表中所处的位置。COM_Init接着在HLM\Drivers\BuiltIn\Serial下查询DeviceArrayIndex、Priority256的值,Priority256指定了驱动程序的优先级,假如没有就用默认的优先级。接下来调用GetSerialObject(DeviceArrayIndex),这个函数由PDD层定义,返回HWOBJ结构体,这个结构体主要包含PDD层和SER16550定义的函数的指针。

也就是说MDD通过调用这个函数才能调用底层实现的函数。接下来的大多数工作都是调用底层函数实现初始化。第一个调用的底层函数SerInit主要设置由用户设置的硬件配置,例如线路控制、波特率。它调用Ser_GetRegistryData函数得到保存在注册表中的硬件信息,Ser_GetRegistryData在内部调用系统提供的DDKReg_GetIsrInfoDDK和DDKReg_GetWindowInfo函数得到在HLM\Drivers\BuiltIn\Serial下保存的IRQ、SysIntr、IsrDll、IsrHandler、IoBase、IoLen。IRQ是逻辑中断号,IsrDll表示当前驱动程序的可安装ISR所在的DLL名称,IsrHandler 表示可安装ISR的函数名称。

在这里顺便提一下可安装ISR,读者在我以前发表的关于OAL的文章中可以了解到OEM在OEMInit函数中关联IRQ和SysIntr,当硬件设备发生中断时,ISR会禁止同级和低级中断,然后根据IRQ返回关联的SysIntr,内核根据ISR返回的SysIntr唤醒相应的IST(SysIntr与IST创建的Event关联),IST处理中断之后调用InterruptDone解除中断禁止。在OEMInit中关联的缺点是一旦编译了CE内核后就无法添加这种关联了,而一些硬件设备会随时插拔或者共享中断,要关联这样的硬件设备解决方法就是可安装ISR,可安装ISR专用于处理指定的硬件设备发出的中断,所以假如硬件设备需要可安装ISR必须在注册表中添加IsrDll、IsrHandler。多数硬件设备采用CE默认的可安装ISR giisr.dll,格式如下:

"IsrDll"="giisr.dll"

"IsrHandler"="ISRHandler"

假如一个硬件驱动程序需要可安装ISR而开发者又不想自己写一个,那么可以利用giisr.dll来实现。除了在注册表中添加如上所示外,还要在驱动程序中调用相关函数注册可安装ISR。伪代码如下:

g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq);

GIISR_INFO Info;

PHYSICAL_ADDRESS PortAddress = {PhysAddr, 0};

TransBusAddrToStatic(BusType, dwBusNumber, PortAddress, dwAddrLen, &dwIOSpace, &(PVOID)PhysAddr)

Info.SysIntr = dwSysIntr;

Info.CheckPort = TRUE;

Info.PortIsIO = (dwIOSpace) ? TRUE : FALSE;

Info.UseMaskReg = TRUE;

Info.PortAddr = PhysAddr + 0x0C;

Info.PortSize = sizeof(DWORD);

Info.MaskAddr = PhysAddr + 0x10;

KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL);

LoadIntChainHandler函数负责注册可安装ISR,参数1为DLL名称,参数2为ISR函数名称,参数3为IRQ。TransBusAddrToStatic函数在后面讲。假如要利用giisr.dll作为可安装ISR,必须先填充GIISR_INFO结构体,CheckPort=TRUE表示giisr要检测指定的寄存器来确定当前发出中断的是否是这个设备。PortIsIO表示寄存器地址属于哪个地址空间,FALSE表示是内定空间,TRUE表示IO空间。UseMaskReg=TRUE表示设备有一个掩码寄存器,专用于指定当前设备是否是中断源,也就是发出中断,而MaskAddr表示掩码寄存器的地址。假如对Info.Mask赋值,那么PortAddr表示一个非凡的寄存器地址,这个寄存器的值与Mask的值&运算的结果假如为真,则证实当前设备是中断源,否则返回SYSINTR_CHAIN(表示当前ISR没有处理中断,内核将调用ISR链中下一个ISR),假如UseMaskReg=TRUE,那么MaskReg寄存器的值与PortAddr指定的寄存器的值&运算的结果假如为真,则证实当前设备是中断源。

函数SerInit接着调用函数Ser_InternalMapRegisterAddresses转换IO地址并且映射地址,Ser_InternalMapRegisterAddresses在内部调用系统提供的HalTranslateBusAddress(Isa, 0, ioPhysicalBase, &inIoSpace, &ioPhysicalBase)函数将与总线相关的地址转换为系统地址,参数1为总线类型,参数2为总线号,参数3为要转换的地址(PHYSICAL_ADDRESS类型,实际是LARGE_INTEGER型),参数4指定寄存器地址属于IO地址空间还是物理地址空间,参数5返回转换后的物理地址。观察HalTranslateBusAddress的源码得知假如是在x86平台,这个函数除了把参数3赋给了参数5其余什么都没有做,而非x86平台将inIoSpace的值置为0,表示一定是物理地址。在调用HalTranslateBusAddress前要确定从注册表中得到的寄存器地址到底是属于哪个地址空间的,例如:

ULONG inIoSpace = 1; ///1表示是IO空间

PHYSICAL_ADDRESS ioPhysicalBase = {iobase, 0}; ///相当于ioPhysicalBase.LowPart = iobase

在地址转换后就要将转换后的地址映射到驱动程序(一般IST和应用程序一样运行在用户模式)能够访问的虚拟地址空间(0x80000000以下)和ISR能够访问的静态虚拟地址空间中(0x80000000以上)。例如:

////假如地址属于物理地址空间

ioPortBase = (PUCHAR)MmMapIoSpace(ioPhysicalBase, Size, FALSE);

TransBusAddrToStatic(Isa, 0, ioPhysicalBase, Size, &inIoSpace, ppStaticAddress);

MmMapIoSpace函数负责将物理地址映射到驱动程序能够访问的虚拟地址空间中,通过源码分析MmMapIoSpace在内部分别调用:

pVirtualAddress =VirtualAlloc(0, SourceSize, MEM_RESERVE, PAGE_NOAccess);

VirtualCopy(pVirtualAddress, (PVOID)(SourcePhys >> 8), SourceSize, PAGE_PHYSICAL PAGE_READWRITE

(CacheEnable ? 0 : PAGE_NOCACHE));

VirtualAlloc分配一块和MemLen一样大小的虚拟地址空间,因为参数1为0,所以内核自动分配。一般MemLen小于2MB,所以会在应用程序的地址空间中分配。VirtualCopy负责将硬件设备寄存器的物理地址与VirtualAlloc分配的虚拟地址做一个映射关系,这样驱动程序访问PvirtualAddress实际上就是访问第一个寄存器。因为硬件设备寄存器的物理地址一定是在512MB(CE支持RAM的最大值)以上,所以除了最后的参数要加PAGE_PHYSICAL外,第二个参数物理地址也要右移8位(或者除以256)。

映射硬件寄存器当然PAGE_NOCACHE是必须加的。TransBusAddrToStatic函数负责将物理地址映射到ISR能够访问的静态虚拟地址空间中,当出现中断共享时,ISR要负责访问硬件设备的某一个寄存器来判定中断源,所以将寄存器的物理地址映射到静态虚拟地址空间中是必要的(ISR只能访问静态的虚拟地址空间)。所谓静态虚拟地址空间是指在OEMAddressTable中定义的虚拟地址空间(当然是0x80000000以上)。在x86平台一般这个表只定义RAM的物理地址与虚拟地址对应关系,而硬件设备的寄存器地址并不在该表中定义,所以假如要创建一块静态的虚拟地址空间供ISR访问,必须在此之前调用CreateStaticMapping函数在0xC4000000到0xE0000000虚拟地址空间中分配。TransBusAddrToStatic函数在内部就是调用了CreateStaticMapping函数。注:硬件设备的寄存器地址也可以在OEMAddressTable中定义。

////假如地址属于IO空间

ioPortBase = (PUCHAR)ioPhysicalBase.LowPart;

*ppStaticAddress=ioPortBase

这种情况只属于x86平台,是IO空间就可以直接访问,即使是用户模式。

SerInit函数接着初始化SER_INFO结构体成员,之后调用SL_Init函数,这个函数在ser16550中定义,负责初始化SER16550_INFO结构体,在这个结构体中保存串口8个寄存器的地址。SerInit函数执行完毕后COM_Init函数创建接收缓冲区,然后调用StartDispatchThread函数初始化中断并且创建IST。StartDispatchThread函数在内部调用InterruptInitialize函数关联SysIntr和Event,然后调用InterruptDone函数告诉内核当前串口可以中断处理,接着调用CreateThread函数创建IST线程。(over吧,再往下说就和串口硬件有关了,看多了没注释的代码我也烦!!)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有