一:寄存器的使用:
1、wince内部对物理地址的访问:
可以有3个途径。
1:直接使用g_oalAddressTable(oemaddrtab_cfg.inc)的已经定义好的,物理地址对应的虚拟地址。
如:
DCD 0x80000000, 0x30000000, 128 ;
访问虚拟地址0x80000000实际就是访问物理地址0x30000000。
2:在OAL层,使用OALPAtoVA函数。
如:
volatile S3C2410X_IOPORT_REG *pIOCTR;
pIOCTR = (volatile S3C2410X_IOPORT_REG *)OALPAtoVA(S3C2410X_BASE_REG_PA_IOPORT, FALSE);
那么在访问pIOCTR指向的首地址,实际就是访问被映射后S3C2410X_BASE_REG_PA_IOPORT定义的物理地址。
VOID* OALPAtoVA( UINT32 pa, 参数1:需要映射的物理地址 BOOL cached 参数2:是否使用cache(驱动中要使用uncached))
3:在kernel里,使用MmMapIoSpace函数。
如:
pBaseAddress = (PUCHAR)MmMapIoSpace(ioPhysicalBase, Size, FALSE);
同上,访问pBaseAddress的指向地址,就为访问被映射后ioPhysicalBase定义的物理地址。
PVOID MmMapIoSpace( PHYSICAL_ADDRESS PhysicalAddress, 参数1:需要映射的物理地址 ULONG NumberOfBytes, 参数2:映射的地址长度 BOOLEAN CacheEnable 参数3:是否使用cache(驱动中要使用uncached));
与OALPAtoVA不同,在使用MmMapIoSpace后,必须使用MmUnmapIoSpace。
VOID MmUnmapIoSpace( PVOID BaseAddress, 参数1:被映射后的虚拟地址 ULONG NumberOfBytes 参数2:映射的地址长度);
在一般的NK驱动编写中,为了规范编程风格,请勿直接使用g_oalAddressTable中的虚拟地址。统一使用MmMapIoSpace、MmUnmapIoSpace函数。
2、wince标准的寄存器访问
定义一个结构体。此结构包含某功能模块的寄存器地址。
如:
typedef struct {
UINT32 GPACON; // Port A - offset 0
UINT32 GPADAT; // Data
UINT32 PAD1[2];
……
……
UINT32 GSTATUS0; // external pin status
UINT32 GSTATUS1; // chip ID
UINT32 GSTATUS2; // reset status
UINT32 GSTATUS3; // inform register
UINT32 GSTATUS4; // inform register
} S3C2410X_IOPORT_REG, *PS3C2410X_IOPORT_REG;
volatile S3C2410X_IOPORT_REG *pIOCTR;
pIOCTR = (volatile S3C2410X_IOPORT_REG *)OALPAtoVA(S3C2410X_BASE_REG_PA_IOPORT, FALSE);
这样,访问pIOCTR的各个成员,就为访问被映射后S3C2410X_BASE_REG_PA_IOPORT定义的物理偏移地址。
为了统一、兼容平台的驱动代码,我们加入了寄存器操作宏(oal_io.h):
#define REG8(_register_) (*(volatile unsigned char *)(_register_))
#define REG16(_register_) (*(volatile unsigned short *)(_register_))
#define REG32(_register_) (*(volatile unsigned long *)(_register_))
访问模块中的功能寄存器,使用加上偏移地址的方式。
#define USB_REG_FADDR_OFFSET (0x0000)
#define USB_REG_POWER_OFFSET (0x0001)
例子:
要编写某个模块的驱动,首先使用MmMapIoSpace或OALPAtoVA建立物理地址映射关系。
volatile BYTE *pUSBCtrlAddr;
pUSBCtrlAddr= (volatile BYTE *)OALPAtoVA(AK3224_BASE_REG_PA_USB, FALSE);
这样,usb模块的首地址就为pUSBCtrlAddr的指向地址。然后,使用REGXX的宏来访问各个功能寄存器。
REG8(pUSBCtrlAddr + USB_REG_POWER_OFFSET) = USB_POWER_ENSUSPEND;
ucIntStatusR = REG16(pUSBCtrlAddr + USB_REG_INTRRX1_OFFSET);
REG32(pUSBCtrlAddr + USB_DMA_COUNT_1_OFFSET) = 256;
在驱动编写中,请统一使用REGXX的宏操作。
二:DMA的使用
1、 芯片DMA的使用要点:
AK3224芯片的DMA使用中,RAM的地址作为DMA传输的目标地址、源地址,必须要4字节对齐。而且DMA的操作长度以内的RAM地址,必须连续。
不过在使用中发现:Nandflash驱动中RAM地址作为目标地址时,只需要2字节对齐。RAM地址作为源地址可不需对齐。(其他情况需要逐一验证)
2、 wince中的DMA使用:
根据DMA一次操作的RAM地址必须连续的特性,在驱动DMA使用时,我们需要确保虚拟地址映射的物理地址是连续的。有3个途径:
1:数据区地址是由应用层或者其他进程、线程传入的,驱动并不知道其虚拟地址对应的物理地址是否一直连续。
由于wince的内存申请,是以4K字节为一个页,一段数据的内存申请可能跨越多个页。因此,只要数据区长度大于1字节,就有可能其物理地址是跨越的、不连续的。为了确保DMA操作,我们必须查询这段数据区在RAM上的物理分布。
首先,得到数据区所在的虚拟页
VirPageStart = (ULONG)pSourceBuffer & 0xFFFFF000;
其次,得到数据区在页内的偏移地址
offset = (ULONG)pSourceBuffer & 0x0FFF;
计算数据区是否跨越页段
if(offset + NumberOfBytes > 4096)
PageSize = WCE_UNIFORM_SIZE - offset; //整个数据跨越此页,则DMA传输需要分多个部分,一次一个页段的传
else
PageSize = NumberOfBytes; //数据区没有跨越页
由得到的页地址,查询映射的物理地址。
if(!LockPages((LPVOID)VirPageStart, 4096, &TransAddr, LOCKFLAG_READ))
{
//异常处理
}
UnlockPages((LPVOID)VirPageStart, 4096);
得到了映射的物理地址TransAddr后,根据RAM是目标地址还是源地址,做进一步的处理。
假设一个数据区作为DMA源地址,大小为9K。在虚拟地址首页的偏移为4K。那么它必然跨越3个页段。
如图,我们的DMA操作也要分解3次。
首先查询第一页的物理地址发送,第一个页的2K数据。然后查询第二页的物理地址,发送4K数据。最后查询第三页的物理地址,发送3K数据。
2:数据区的申请可以使用AllocPhysMem函数申请。
LPVOID AllocPhysMem( DWORD cbSize, 参数1:数据区大小 DWORD fdwProtect, 参数2:保护标记 DWORD dwAlignmentMask, 参数3:0(default system) DWORD dwFlags, 参数4:0(Reserved for future use) PULONG pPhysicalAddress 参数5:得到数据区对应的物理地址);
AllocPhysMem函数返回值为指向申请后的虚拟地址指针。
如:
pSerialHead->RxBufferInfo.RxCharBuffer = //alloc physical memory
AllocPhysMem(pSerialHead->RxBufferInfo.Length + 16, PAGE_READWRITE, 0, 0, &RX_PhyAddr);
由于此函数必定申请到一片连续的物理地址,因此pSerialHead->RxBufferInfo.RxCharBuffer的使用不再需要查询是否跨越多个页段。
但是,AllocPhysMem函数申请的物理地址可能会跨越多个RAM CHIP。因此,在使用1片以上RAM芯片的系统中,依然需要查询是否跨越CHIP。
AllocPhysMem函数使用后,需要使用FreePhysMem函数进行释放。
3:数据区可以在系统config.bib文件中,预先定义好一片连续、不跨越CHIP的RAM空间。
如下,系统保留了虚拟地址0x80024000开始,大小为0x3000的一段RAM。
SER_DMA 80024000 00003000 RESERVED
那么驱动DMA使用中,不再需要对这段内存,进行任何的查询动作。我们只需要在进程空间中做映射即可。
pSerialHead->RxBufferInfo.RxCharBuffer = VirtualAlloc(0, RX_PhySize,
MEM_RESERVE, PAGE_NOACCESS);
if (pSerialHead->RxBufferInfo.RxCharBuffer == NULL)
{
DEBUGMSG(ZONE_ERROR, (TEXT("COM_Init:: VirtualAlloc failed!/r/n")));
return(NULL);
}
else
{
if (!VirtualCopy((PVOID)pSerialHead->RxBufferInfo.RxCharBuffer, (PVOID)(RX_PhyAddr),
RX_PhySize, (PAGE_READWRITE | PAGE_NOCACHE)))
{
DEBUGMSG(ZONE_ERROR, (TEXT("COM_Init:: VirtualCopy failed!/r/n")));
return(NULL);
}
}
上面这段程序中,先使用函数VirtualAlloc,在进程空间中申请一段保留的虚拟地址空间。然后使用VirtualCopy,把需要使用的物理地址空间,映射到已经申请好的虚拟地址上。使用完毕,必须使用函数VirtualFree进行释放。
LPVOID VirtualAlloc(
LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect );
BOOL VirtualCopy( LPVOID lpvDest, LPVOID lpvSrc, DWORD cbSize, DWORD fdwProtect );
BOOL VirtualFree( LPVOID lpAddress, DWORD dwSize, DWORD dwFreeType );
详细使用请查阅MSDN
三、中断的使用
1、wince中断简介
1: ISR的概念
ISR(interrupt service routine)是处理IRQs(interrupt request line)的程序。Windows CE用一个ISR来处理所有的IRQ请求。当一个中断发生时,内核的异常处理程序先调用内核ISR,内核ISR禁用所有具有相同优先级和较低优先级的中断,然后调用已经注册的OAL ISR程序,一般ISR有下列特征:
1) 执行最小的中断处理,最小的中断处理指能够检验、答复产生中断的硬件,而把更多的处理工作留给IST(interrupt service thread)。
2) 当ISR完成时返回中断ID(中断ID大部分是预定义的)。
2:中断注册步骤
1) 用SETUP_INTERRUPT_MAP宏关联SYSINTR和IRQ。以“SYSINTR_”为前缀的常量由内核使用,用于唯一标识发生中断的硬件。在Nkintr.h文件中预定义了一些SYSINTR,OEM可以在Oalintr.h文件中自定义SYSINTR。
2) 用HookInterrupt函数关联硬件中断号和ISR。这里的硬件中断号为物理中断号,而非逻辑中断号IRQ。
2、 驱动中IST使用
ISR是中断最小处理函数,因此各个驱动的中断处理函数称为IST。
系统中保留16个虚拟中断号,ak3224_intr.h已经定义好各个ISR的虚拟中断号28个。因此,驱动中的中断处理函数,只要与定义好的28个虚拟中断映射上即可。
例子:
首先,我们创建一个事件
pGPIOInfo->hGPIOEvent1 = CreateEvent(0,FALSE,FALSE,NULL);
其次创建一个处理事件的线程(IST)
pGPIOInfo->hGPIOThread1 = CreateThread(NULL, 0, GPIOFuncThread1,
pGPIOInfo, 0, NULL);
然后使用InterruptInitialize让虚拟中断号pGPIOInfo->dwIntID1与创建的事件pGPIOInfo->hGPIOEvent1挂钩。
InterruptInitialize(pGPIOInfo->dwIntID1, pGPIOInfo->hGPIOEvent1, NULL, 0)
那么,当GPIO的中断到来,与GPIO虚拟中断挂钩的事件pGPIOInfo->hGPIOEvent1就会被设为Active。线程pGPIOInfo->hGPIOThread1的语句
WaitForSingleObject(pGPIOInfo->hGPIOEvent1, INFINITE);
GPIOEventHandler1;
就会被唤醒,然后执行下一条指令。这里加入的函数GPIOEventHandler1(中断处理操作)就被执行。
当中断处理结束以后,必须使用
InterruptDone(pGPIOInfo->dwIntID1);
通知系统已经完成中断处理,那么下一次的中断到来,事件pGPIOInfo->hGPIOEvent1就才会再次被设为Active。
驱动卸载时,需要释放申请的事件及线程
CloseHandle(pGPIOInfo->hGPIOEvent1);
CloseHandle(pGPIOInfo->hGPIOThread1);
3、可安装ISR介绍
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;
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。
如果要利用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指定的寄存器的值&运算的结果如果为真,则证明当前设备是中断源。
可见,可安装ISR对与再次分解的中断处理是非常方便的。不过需要占用系统的一个虚拟中断号。而整个系统的虚拟中断号只有64个。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yexinghai/archive/2009/04/17/4087897.aspx