开发优秀的驱动程序
作为驱动开发工程师,我们需要在每一行代码上下功夫,因为驱动程序的效率直接影响着系统的性能.而新手往往不会注意到这些细节。以为功能实现以后就万事大吉了,其实不然,好的驱动程序不只是能实现预期的功能。它同样需要高的效率与规范的风格。用户花钱买我们系统是给他/她做事的,而不是给我们做测试的,所以我们要尽可能提高效率。同时好的代码风格能大大降低我们自己的维护成本。
高效率看似容易,但要注意到每个细节还是挺难的,我们可以从以下几点去注意这个问题:
1, 不要使用无关的代码,这点容易理解,尤其是调试代码,RELEASE时一定要去除这些代码。
2, 去掉多余的函数调用,尽可能的保存一些数据。即使是最快的函数,调用它时也会引发压栈与出栈,所以要尽量少做函数调用。当然如果一个函数返回的数据比较大,保存那些数据将占用比较多的内在空间,保存返回值就得不偿失了。比如,看到有的人每次在使用一个地址时就调用MmmapIoSpace将这个地址映射到程序地址空间,用完以后又立即Unmap这个地址,下次使用时又做MAP,这就是一种及不好的方法,每次需要多调用两个系统函数。
3.如果可行,不要在循环中使用条件判断,尤其在一个次数很多的循环中更应该如此。
比如:
For( i=0; i<1000;>
If( m==1) ..
Else if (m==2 )….
Else …..
}
这种代码,我们可以把If 写在for 之外,即,每一种不同的条件写一个循环体。
If( m==1) for ...
Else if (m==2 ) for ….
Else for …..
标签: 程序技巧
开发DMA驱动
使用DMA的好处就是它不需要CPU的干预而直接服务外设,这样CPU就可以去处理别的事务,从而提高系统的效率,对于慢速设备,如UART,其作用只是降低CPU的使用率,但对于高速设备,如硬盘,它不只是降低CPU的使用率,而且能大大提高硬件设备的吞吐量。因为对于这种设备,CPU直接供应数据的速度太低。
因CPU只能一个总线周期最多存取一次总线,而且对于ARM,它不能把内存中A地址的值直接搬到B地址。它只能先把A地址的值搬到一个寄存器,然后再从这个寄存器搬到B地址。也就是说,对于ARM,要花费两个总线周期才能将A地址的值送到B地址。而DMA就不同了,一般系统中的DMA都有突发(Burst)传输的能力,在这种模式下,DMA能一次传输几个甚至几十个字节的数据,所以使用DMA能使设备的吞吐能力大为增强。
使用DMA时我们必须要注意如下事实:
1. DMA使用物理地址,程序是使用虚拟地址的,所以配置DMA时必须将虚拟地址转化成物理地址。
2. 因为程序使用虚拟地址,而且一般使用CACHED地址,所以虚拟地址中的内容与其物理地址上的内容不一定一致辞,所以在启动DMA传输之前一定要将该地址的CACHE刷新,即写入内存。
3. OS并不能保证每次分配到的内在空间在物理上是连续的。尤其是在系统使用过一段时间而又分配了一块比较大的内存时。
所以每次都需要判断地址是不是连续的,如果不连续就需要把这段内存分成几段让DMA完成传输。
标签: BaseKnowledge
WINCE下USBFN驱动程序的一些概念
USBFN,即USB客户端驱动,用来将一个WINCE设备模拟成一定的USB设备,让主机端(如PC)访问。目前WINCE提供的USB客户端有存储设备,串口设备,及RNDIS网络接口设备。
存储设备用来将WINCE设备上的存储空间,例如FLASH,当作一块存储介质给主机访问,即将WINCE设备模拟成一个U盘。
串口设备将设备与主机的USB连线模拟成串口,WINCE和主机端都认为它们之前连接上了一根串口线,它们之间可以做串口通信,典型的应用是用来实现WINCE与PC机的同步连接。
RNDIS设备使两端认为它们之间建立了网络连接,通过注册表设置可以让主机通过WINCE设备上网或者使WINCE设备通过主机上网。
WINCE已经提供了以上三种设备的驱动程序,在同一时刻只能使用一个设备。而我们需要做的只是提供USBFN总线控制器的驱动程序。USBFN系统各个模块的关系如下:
USBFN总路线控制器作为一个总线驱动程序,被设备管理器加载,根据注册表设置加载相应的客户驱动程序,即存储设备,串口设备或者RNDIS设备。客户驱动程序即启动USBFN,引发主机配置设备,配置完成以后即可开始工作。
而USBFN总路线控制器驱动的MDD部分WINCE本身已经提供,PDD只需初始化硬件设备,提供传输即可。MDD在初始化时调用UfnPdd_Init函数得到PDD层的函数表,之后会根据需要调用各个函数。PDD还需要提供IST,用以处理各个中断。需要注意的是USBFN有一个与其它设备不同之处,它的注册表需要这样一个设置:
"BusIoctl"=dword:2a0048,用以让系统加载完设备之后调用值为0x2a0048的IOCTL代码去完成初始化,其定义为IOCTL_BUS_POSTINIT。
标签: BaseKnowledge
SOURCES文件详解
SOURCES文件是WINCE底层开发中最重要的文件之一,主要的配置项如下:
TARGETNAME,定义模块名称.
TARGETTYPE,模块的种类,可以是DYNLINK, LIBRARY,EXE.
如果TARGETTYPE是DLL,则可以定义DLLENTRY,将Dll入口定义成别的不是DLLMain的函数,如果DLL的入口是DllMain,则不需要别的定义。
如果TARGETTYPE是EXE,则可以定义EXEENTRY,用于指定EXE的入口函数.
如果TARGETTYPE是LIBRARY,则不需要定义入口函数。
INCLUDES,如果一个模块需要使用非标准路径下的头文件时,需要定义INCLUDES,用于包含更多的头文件路径,用法如下:
INCLUDES=$(INCLUDES);\new directory\...,注意定义新的INCLUDES时,需要包含INCLUDES原来的值,否则就需要包含所有可能的目录。
TARGETLIBS,SOURCELIBS用于定义该模块需要链接哪些库文件.
TARGETLIBS,如果一个库以DLL的形式提供给调用者,就需要用TARGETLIBS,它只链接一个函数地址,系统执行时会将被链接的库加载。比如coredll.lib就是这样的库文件。即动态链接。
SOURCELIBS,将库中的函数实体链接进来。即静态链接,用到的函数会在我们的文件中形成一份拷贝。
注意,内核这个执行文件是没有TARGETLIBS的,GIISR.DLL也不能有TARGETLIBS。
WINCECOD,如果将其定义为1,则编译器会为每一个文件生成.cod文件,它是一个汇编文件,调试时查看汇编代码也是一种很好的办法。
SOURCES,定义该模块需要哪些源文件.
标签: BaseKnowledge
多个设备共享同一个硬件中断
硬件中断线总是有限的,我们可能需要在已有的系统上做一些扩展,比如将串口扩展成好几个,有些硬件本身就设计成多个设备共享一条中断线,比如我的系统中两个串口就共享同一个CPU中断,任何一个串口发生中断以后都会触发CPU的同一条中断线,需要判断别的寄存器来确定是哪个串口发生了什么中断。
我们可以在OAL中分析各个中断源,然后返回不同的SYSINTR值,但这种做法扩展性不好。例如,OAL中设值某个中断源最多会产生三个SYSINTR,但以后扩展成了四个设备,有一个设备就无法正常工作了。
WINCE引入了可装载中断处理例程的概念。即在需要与别的设备共享中断的驱动程序中加载一个ISR,一般使用WINCE提供的GIISR即成满足需求。将其安装到内核。OAL中发生中断时调用NKCallIntChain来得到SYSINTR,这个函数会引起系统逐个调用在该IRQ上加载的所有可装载的ISR,当某个ISR认为这个中断是由它引发的时就返回其SYSINTR,否则就返回SYSINTR_CHAIN,系统就会接着调用其它的ISR,甚至所有的ISR都被调用或者有一个ISR返回了正确的SYSINTR。
驱动程序中的调用办法如下(CE帮助文档):
if (InstallIsr) {
// Install ISR handler
g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq);
if (!g_IsrHandle) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Couldn't install ISR handler\r\n"));
} else {
GIISR_INFO Info;
PVOID PhysAddr;
DWORD inIoSpace = 1; // io space
PHYSICAL_ADDRESS PortAddress = {ulIoBase, 0};
if (!TransBusAddrToStatic(PCIBus, 0, PortAddress, ulIoLen, &inIoSpace, &PhysAddr)) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Failed TransBusAddrToStatic\r\n"));
return FALSE;
}
DEBUGMSG(ZONE_PDD,
(L"WAVEDEV: Installed ISR handler, Dll = '%s', Handler = '%s', Irq =
%d, PhysAddr = 0x%x\r\n", IsrDll, IsrHandler, Irq, PhysAddr));
// Set up ISR handler
Info.SysIntr = ulSysIntr;
Info.CheckPort = TRUE;
Info.PortIsIO = TRUE;
Info.UseMaskReg = FALSE;
Info.PortAddr = (DWORD)PhysAddr + ES1371_dSTATUS_OFF;
Info.PortSize = sizeof(DWORD);
Info.Mask = ES1371_INTSTAT_PENDING;
if (!KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL)) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: KernelLibIoControl call failed.\r\n"));
}
}
}
这里需要注意一下,因为ISR在内核态运行,Info.PortAddr必须是系统最原始的虚拟地址,即没有用VirtualCopy映射过的,从OEMAddressTable中计算出来的虚拟地址。在这个例子中用TransBusAddrToStatic函数可以直接把物理地址转换成这种地址。而MmMapIoSpace得到是在当前程序空间中的地址,不能使用。而且GIIR要被加载到内核空间,所以在加入到OS包中时需要加上K标志,否则LoadIntChainHandler函数会失败。