Wince驱动开发学习:寄存器、DMA、中断使用

一:寄存器的使用:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值