q是我从1?日开始主持天极网论坛嵌入式开发版以来W一ơ发表文章,加上以前琐碎的文章共
?0。研I的多p感觉自己懂的太少Q其实在驱动开发方面我q是个菜鸟,我是惛_ơ抛砖引玉,让做驱动有Nq经验的人奉献一点出来,让大家减一?
研究驱动源码而又~少注释所带来的痛苦?br>
我想即读者看q微软的关于驱动开发的培训教材和CE帮助文档中的驱动部分Q头脑中仍然一片茫然。要想真正了(jin)解驱动程序必ȝ合一些驱动程序源码,在此我以串口驱动E序QCOM16550Q中初始化过Eؓ(f)U烦(ch)单讲一讲驱动开发的基础知识?br>
Windows
CE下的串口驱动E序能够处理所有I/O行ؓ(f)cM串口的设备,包括Z16450?6550
UARTQ通用异步收发芯片Q的讑֤和一些采用DMA的设备,常见的有9针串口、红外I/O口、Modem{。在%_WINCEROOT%\Public
\Common\OAK\Drivers\Serial目录下,COM_MDD2子目录包含新的串口驱动MDD层函C码。COM16550子目录包含串
口驱动PDD层代码。SER16550子目录包含的一pd函数专用于控制与16550兼容的UARTQ这样PDD层的主要工作是调用SER16550?
的函数。还有一个ISR16550子目录包含的是串口驱动程序专用的可安装ISRQ中断服务例E)(j)Q而很多硬件设备驱动程序采用CE默认的可安装ISR
giisr.dll。一般串口设备相应的注册表设|例子及(qing)意义如下Q?
?/strong> |
意义 |
"SysIntr"=dword:13 |
串口1的中断ID为十q制13 |
"IoBase"=dword:02F8 |
串口1的IOI间首地址为十六进?F8 |
"IoLen"=dword:8 |
串口1的IOI间长度?个字?/td>
|
"DeviceArrayIndex"=dword:0 |
串口1的烦(ch)引,?的由?/td>
|
"Order"=dword:0 |
串口1驱动的加载顺?/td>
|
"DeviceType"=dword:0 |
串口1的设备类?/td>
|
"DevConfig"=hex: 10,00 .... |
串口1在与Modem讑֤通讯时的配置Q如波特率、奇偶校(g){?/td>
|
"FriendlyName"="COM1:" |
串口1在拨L(fng)序中昄的名?/td>
|
"Tsp"="Unimodem.dll" |
串口1 被用于与Modem讑֤通讯的时候要加蝲的TSPQTAPI Service providerQDLL |
"Prefix"="COM" |
串口1的流接口的前~ |
"Dll"="com16550.Dll" |
串口1的驱动程序DLL |
SysIntr
由CE在文件Nkintr.h中预定义Q用于唯一标识中断讑֤。OEM可以在文件Oalintr.h中定义自qSysIntr。常见的预定?
SysIntr有SYSINTR_NOPQ中断只由ISR处理QIST不再处理Q,SYSINTR_RESCHEDQ重新调度线
E)(j)QSYSINTR_DEVICESQ由CE预定义的讑֤中断ID的基|(j)QSYSINTR_PROFILE、SYSINTR_TIMING?
SYSINTR_FIRMWARE{都是基于SYSINTR_DEVICES定义的。IoBase是串?的IO地址I间的首地址QIoLen是IOI间
的大。IO地址I间只存在于x86q_Q如果在其它q_g寄存器必L到物理地址I间Q那子键的名UCؓ(f)MemBase和MemLen。在x86q_
更多g的寄存器׃IOI间的局限也映射到物理地址I间。DeviceArrayIndex是设备的索引Q用于区分同cd的设备。Prefix是流驱动
E序的前~Q当应用E序调用CreateFile函数传递COM1:参数Ӟ文gpȝ负责与串口驱动程序通信Q串口驱动程序是在CE启动时由
device.exe加蝲的?br>
下面从MDD
层函数COM_Init开始探索串口驱动的初始化过E。COM_Init是在串口讑֤被检后p备管理器device.exe调用的,主要的作用是初始
化设备,它的唯一参数Identifier是由device.exe传递的Q其cd是一个字W串指针Q字W串的内Ҏ(gu)HLM\Drivers
\Active\xxQxx是一个十q制敎ͼdevice.exe?x)跟t系l中每个驱动E序Q把加蝲的驱动程序记录在Active键下Q?
COM_Init先分配一个HW_INDEP_INFOl构体,q个l构体是独立于串口硬件的头信息(MDD、PDD、SER16550都包含自q特的
l构体,具体的结构体定义请参见串口驱动源码)(j)Q分配之后再初始化结构体中每个成员,初始化结构体后调?
OpenDeviceKey((LPCTSTR)Identifier)打开HLM\Drivers\Active\xx\Key包含的注册表路径Q在q?
里\径一般ؓ(f)HLM\Drivers\BuiltIn\SerialQ即串口的驱动程序信息在注册表中所处的位置。COM_Init接着在HLM
\Drivers\BuiltIn\Serial下查询DeviceArrayIndex、Priority256的|Priority256指定?jin)?
动程序的优先U,如果没有q默认的优先。接下来调用GetSerialObject(DeviceArrayIndex)Q这个函数由P(pn)DD层定义,
q回HWOBJl构体,q个l构体主要包含PDD层和SER16550定义的函数的指针。也是说MDD通过调用q个函数才能调用底层实现的函数。接下来
的大多数工作都是调用底层函数实现初始化。第一个调用的底层函数SerInit主要讄q戯|的g配置Q例如线路控制、L特率。它调用
Ser_GetRegistryData函数得到保存在注册表中的g信息QSer_GetRegistryData在内部调用系l提供的
DDKReg_GetIsrInfoDDK和DDKReg_GetWindowInfo函数得到在HLM\Drivers\BuiltIn\Serial
下保存的IRQ、SysIntr、IsrDll、IsrHandler、IoBase、IoLen。IRQ是逻辑中断PIsrDll表示当前驱动E序?
可安装ISR所在的DLL名称QIsrHandler
表示可安装ISR的函数名U。在q里Z提一下可安装ISRQ读者在我以前发表的关于OAL的文章中可以?jin)解到OEM在OEMInit函数中关联IRQ?
SysIntrQ当g讑֤发生中断ӞISR?x)禁止同U和低中断Q然后根据IRQq回兌的SysIntrQ内核根据ISRq回的SysIntr唤醒
相应的ISTQSysIntr与IST创徏的Event兌Q,IST处理中断之后调用InterruptDone解除中断止。在OEMInit中关?
的缺Ҏ(gu)一旦编译了(jin)CE内核后就无法dq种兌?jin),而一些硬件设备会(x)随时插拔或者共享中断,要关联这L(fng)g讑֤解决Ҏ(gu)是可安装ISRQ可安装
ISR专用于处理指定的g讑֤发出的中断,所以如果硬件设备需要可安装ISR必须在注册表中添加I(yng)srDll、IsrHandler。多数硬件设备采?
CE默认的可安装ISR giisr.dllQ格式如下:(x)
"IsrDll"="giisr.dll"
"IsrHandler"="ISRHandler"
如果一个硬仉动程序需要可安装ISR而开发者又不想自己写一个,那么可以利用giisr.dll来实现。除?jin)在注册表中d如上所C外Q还要在驱动E序中调用相兛_数注册可安装ISR。伪代码如下Q?/p>
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
函数负责注册可安装ISRQ参?为DLL名称Q参?为ISR函数名称Q参?为IRQ。TransBusAddrToStatic函数在后面讲。如?
要利用giisr.dll作ؓ(f)可安装ISRQ必d填充GIISR_INFOl构体,CheckPort=TRUE表示giisr要检指定的寄存器来?
定当前发Z断的是否是这个设备。PortIsIO表示寄存器地址属于哪个地址I间QFALSE表示是内定空_(d)TRUE表示IOI间?
UseMaskReg=TRUE表示讑֤有一个掩码寄存器Q专用于指定当前讑֤是否是中断源Q也是发出中断Q而MaskAddr表示掩码寄存器的地址?
如果对Info.Mask赋|那么PortAddr表示一个特D的寄存器地址Q这个寄存器的gMask的?amp;q算的结果如果ؓ(f)真,则证明当?
讑֤是中断源Q否则返回SYSINTR_CHAINQ表C当前ISR没有处理中断Q内核将调用ISR链中下一个ISRQ,如果
UseMaskReg=TRUEQ那么MaskReg寄存器的gPortAddr指定的寄存器的?amp;q算的结果如果ؓ(f)真,则证明当前设备是中断
源?br>
函数SerInit接着调用函数
Ser_InternalMapRegisterAddresses转换IO地址q且映射?
址QSer_InternalMapRegisterAddresses在内部调用系l提供的HalTranslateBusAddress(Isa,
0, ioPhysicalBase, &inIoSpace,
&ioPhysicalBase)函数与ȝ相关的地址转换为系l地址Q参?为ȝcdQ参?为ȝP参数3转换的地址
QPHYSICAL_ADDRESScdQ实际是LARGE_INTEGER型)(j)Q参?指定寄存器地址属于IO地址I间q是物理地址I间Q参?q回?
换后的物理地址。观察HalTranslateBusAddress的源码得知如果是在x86q_Q这个函数除?jin)把参?赋给?jin)参?其余什么都没有做,
而非x86q_inIoSpace的值置?Q表CZ定是物理地址。在调用HalTranslateBusAddress前要定从注册表中得到的寄存
器地址到底是属于哪个地址I间的,例如Q?/p>
ULONG inIoSpace = 1; ///1表示是IOI间
PHYSICAL_ADDRESS ioPhysicalBase = {iobase, 0}; ///相当于ioPhysicalBase.LowPart = iobase
在地址转换后就要将转换后的地址映射到驱动程序(一般IST和应用程序一栯行在用户模式Q能够访问的虚拟地址I间Q?x80000000以下Q和ISR能够讉K的静(rn)态虚拟地址I间中(0x80000000以上Q。例如:(x)
////如果地址属于物理地址I间
ioPortBase = (PUCHAR)MmMapIoSpace(ioPhysicalBase, Size, FALSE);
TransBusAddrToStatic(Isa, 0, ioPhysicalBase, Size, &inIoSpace, ppStaticAddress);
MmMapIoSpace函数负责物理地址映射到驱动程序能够访问的虚拟地址I间中,通过源码分析MmMapIoSpace在内部分别调用:(x)
pVirtualAddress =VirtualAlloc(0, SourceSize, MEM_RESERVE, PAGE_NOACCESS);
VirtualCopy(pVirtualAddress, (PVOID)(SourcePhys >> 8), SourceSize, PAGE_PHYSICAL |
PAGE_READWRITE | (CacheEnable ? 0 : PAGE_NOCACHE));
VirtualAlloc
分配一块和MemLen一样大的虚拟地址I间Q因为参??Q所以内核自动分配。一般MemLen于2MBQ所以会(x)在应用程序的地址I间中分配?
VirtualCopy负责硬件设备寄存器的物理地址与VirtualAlloc分配的虚拟地址做一个映关p,q样驱动E序讉K
PvirtualAddress实际上就是访问第一个寄存器。因为硬件设备寄存器的物理地址一定是?12MBQCE支持RAM的最大|(j)以上Q所以除?
最后的参数要加PAGE_PHYSICAL外,W二个参数物理地址也要右移8位(或者除?56Q。映硬件寄存器当然PAGE_NOCACHE是必d
的。TransBusAddrToStatic函数负责物理地址映射到ISR能够讉K的静(rn)态虚拟地址I间中,当出C断共享时QISR要负责访问硬件设
备的某一个寄存器来判断中断源Q所以将寄存器的物理地址映射到静(rn)态虚拟地址I间中是必要的(ISR只能讉K?rn)态的虚拟地址I间Q。所谓静(rn)态虚拟地址I间?
指在OEMAddressTable中定义的虚拟地址I间Q当然是0x80000000以上Q。在x86q_一般这个表只定义RAM的物理地址与虚拟地址
对应关系Q而硬件设备的寄存器地址q不在该表中定义Q所以如果要创徏一块静(rn)态的虚拟地址I间供ISR讉KQ必d此之前调?
CreateStaticMapping函数?xC4000000?xE0000000虚拟地址I间中分配?
TransBusAddrToStatic函数在内部就是调用了(jin)CreateStaticMapping函数。注Q硬件设备的寄存器地址也可以在
OEMAddressTable中定义?/p>
////如果地址属于IOI间
ioPortBase = (PUCHAR)ioPhysicalBase.LowPart;
*ppStaticAddress=ioPortBase
q种情况只属于x86q_Q是IOI间可以直接访问,即是用h式?br>
SerInit
函数接着初始化SER_INFOl构体成员,之后调用SL_Init函数Q这个函数在ser16550中定义,负责初始化SER16550_INFOl构
体,在这个结构体中保存(sh)?个寄存器的地址。SerInit函数执行完毕后COM_Init函数创徏接收~冲区,然后调用
StartDispatchThread函数初始化中断ƈ且创建IST。StartDispatchThread函数在内部调?
InterruptInitialize函数兌SysIntr和EventQ然后调用InterruptDone函数告诉内核当前串口可以中断处理Q接
着调用CreateThread函数创徏ISTU程。(over吧,再往下说和串口g有关?jin),看多了(jin)没注释的代码我也?ch)Q!Q?/p>
]]>