现在的位置: 首页 > 综合 > 正文

DriverStudio开发PCI设备DMA数据传输

2013年10月22日 ⁄ 综合 ⁄ 共 6359字 ⁄ 字号 评论关闭

DriverWizard向导可以创建基本的wDM驱动程序框架,包括总线类型,地址空间,中断源,DMA资源,以及IOCTL(i/o控制代码)的定义等等。详细情况可参看DriverStudio的帮助文档,以下主要介绍如何用DriverWorks编写DMA方式的驱动程序。

 

    DriverWorks关于DMA操作。封装了三个类:KDmaAdapter KDmaTransferKCommonCmaBuffer KDmaAdapter类用于建立一个DMA适配器。说明DMA通道的特

性; KDmaTransfer类用于控制DMA传输: KCommonCmaBuffer类用于申请公共缓冲区。

 

    DriverWorks中很少涉及到映射寄存器。只是提到KDmaTransfer类“由于硬件或者缓冲区大小的限制。将一个DMA请求分段进行。为每个段提供了一个传输段描述符数组,当设备不支持分散/集中时,这个数组中的描述符只有一个。每个描述符包含一个物理地址和相应的字节数。其结构如下:

typedef struct TRANSFERDESCRIPTOR

{

PHYSICAL_ADDRESS td_PhysAddr,

ULONG tdLength

} PrRANSFERDESCRIPTOR

    其中的物理地址就是要提供给总线控制器的,实际上就是前面所说的逻辑地址;而字节数也就是相应的连续逻辑地址范围的长度。DriverWorks通过封装,隐藏了映射寄存器的操作,简化了编程。

 

     DriverWorks关于DMA的驱动编程步骤如下:

(1)定义适配器对象。在驱动程序基类的成员变量中定义一个KDmaAdapter类的适配器对象,并且在启动设备的时候对其初始化,正确的建立适配器描述符(DEVICE_DISCRItrflON),说明是否为总线主设备、是否支持分散/集中以及总线类型等特征,详细定义参考DDK文档。

(2)定义KDmaTransfer类的对象。可在全局范围内静态定义,也可定义为驱动程序基类的成员变量,或者在需要时在内存堆上分配。进~TDMA传输的设备使用Queuelrp串行IRP,于是DMA传输通常在成员函数Startlo中初始化,同时定义一个DMA回调例程(作为KDmaTransfer类初始化成员函数的一个参数)

(3)DMA回调例程。如前所述,KDmaTransfer类的一个重要功能就是根据硬件或者缓冲区大小的限制将一个传输分解成段。每当准备传输一个新段时,KDmaTransfer的对象将通知驱动程序调用回调例程。回调函数的原型typedef DMAREADY CALLBACK指定, 通常使用宏DEVMEMBER DMAREADY,声明回调函数为驱动程序基类的成员函数。调函数首先检查传输是否完成。调用成员函数BytesRemaining,如果返回为0, 回调函数将调用成员函数Terminate,完成相应的IRP。否则,回调函数继续传输。

(4)编写处理当前DMA段传输结束的代码。一段传输结束,通常硬件以一个DMA中断通知系统。在中断服务例程中判断是否为DMA中断,如果是,就请求DPC (延迟调用过程)。在DPC中,驱动程序必须调用成员函数Continue通知传输对象此段传输完成。这个成员函数按照需要清空缓冲区,更新传输状态。如果整个传输并没有完成。则建立下一段传输调用DMA回调例程。

 

利用DriverWizard生成驱动程序框架共需要11个步骤,其中比较重要的包括:

(1)在第四步中选中PCI.并在Device IDVendor ID中分别输入设备号和厂商号.还需要填入PCI Subsystem IDPCI Revision ID。这些ID都保存在PCI配置寄存器PCR中。31版中的C DriverWizard提供一个PnP View可以查看这些值,另外也可以使用PCI Tree等免费软件查看。

(2)第九步是比较关键的一步。首先在Resources中添加资源.在Name中输入变量名.在PCI Base Address中输入05的序列号.05BAROBAR5一一对应。实例中有两个IoPort资源。在设置中断对话框中.在name栏写人中断服务程序的名称,选中创建中断服务程序ISR(Interrupt Service Route),选中创建延迟程序调用DPC(Deferred Processing Call),选中Make ISRDPC class functions

然后选中Buffer选取读写方式.用于描述与IO操作相关的数据缓冲区。实例中不需要传送大量数据,因此采用Buffered方式。

 

OnStartDevice(Klrp I)

参数I中包含了系统分配的两种资源配置信息,一种是原始的资源分配信息(AllocatedResources),一种是转换后的资源分配信息(AllocatedResources Translated)。因为I/O总线与CPU在寻址物理硬件的方式上不同.所以存在着两种资源列表。在WDM出现之前.内核模式驱动程序从注册表、PCI配置空间、或其它地方获取原始的资源值,并通过调用诸如HalTranslateBusAddressHalGetInterruptVector函数转换这些数值。现在。接收和转换工作全部由PnP

管理器来完成.WDM驱动程序需要做的仅是从设备启动IRP中获取这些资源。

PCM_RESOURCE_LIST pRaw = I.AllocatedResources();

PCM_RESUOURCE_LIST pTranslated = I.TranslatedResources();

接下来.程序员需要根据自己硬件分配的资源.初始化相应的寄存器和中断等。采集卡中只有BARl(PCI I/O访问基地址)BAR2(PCI本地空间0基地址)两个地址有效。定义和初始化如下:

KIoRange m_IoConfig

KIoRange m_LocalAddrSpace0

NTSTATUS status = m_IoConfig.Initialize(pTranslatedpRaw0)

NTSTATUS status = m_LocalAddrSpace0.Initialize(pTranslatedpRaw1)

其中第3个参数为I/O口对应的基地址.用来转换成特定端口资源的序数。在将硬件中断与中断服务例程连接之前.先要关闭PCI采集卡中断.方法是将9052控制芯片I/0基地址偏移0x4ChINTCSR寄存器中断使能位(6)屏蔽。方法如下:

m_IoConfig&=~PLX9052_INTCSR]=~PLx9552_INTCSR_IE.其中PLX9052 INTCSRPLX9052 INTCSR IE定义分别为

#define PLX9052_INTCSR ULONG(0x004C)

#define PLX9052_INTCSR_IE ULONG(1<<6)

然后利用InitializeAndConnect()初始化并连接中断。

接下来在PCI6038中定义设置中断模式为下降沿触发.设置通道一为读方式,具体操作是向PCI6038特定偏移的地址中写入特定的控制字节,最后开中断。

中断服务例程

当外部中断到来时,调用中断服务例程。这时必须首先判断该中断是否是用户期待的中断源所产生。读取9052控制芯片INTCSR寄存器的LINTil Status(2)的值,若为1,表明中断有效;否则,直接返回。

ULONG IntCS=m_IoConfig[PLX8052_INTCSRl

if ((IntCSPLX9052_INTCSR_LINTlS)&&(IntCSPLX9052_INTCSR_LINTlE))

{

t<<’’LINTl arrived!<<BOL

//采集时间

}

其中PLX9052_INTCSR_LINTlS定义为:

#define PLX9052_INTCSR_LINTlS ULONG(1<<2)

由于中断服务例程运行在执行高于DISPATCH_LEVEL的中断运行级上.因此ISR中使用的代码和数据必须存在于非分页内存中。此外。ISR能调用的内核模式函数有限。为了提高系统性能,ISR应该尽可能快地执行。基本上.只做服务硬件所需的最小量的工作.然后立即返回。如果有额外的工作需要做,应该交给DPC来完成。

延迟调用例程

ISR中采集了B码发送的GPS时间.其余工作由DPC完成,包括清除中断、通知用户应用程序时间采集完成等。

由于PCI6038提供了中断清除寄存器.因此只要向该寄存器中写入0x00.就可以清除当前中断。在中断操作过程中,plx9052控制寄存器中的INTCSR寄存器中,除了INTl(bit#8)INT2(bit#9)使能外,其余bit#0bit#6均保持默认设置。

驱动程序调试

使用SoftIce作为调试工具.基本过程如下:

(1)使用Svmb01 Loader加载驱动程序水.Bins文件,然后激活SoftIce,设置断点跟踪调试;

(2)Genint命令产生虚拟中断,单步跟踪中断服务例程;

(3)改变B码发送频率.检验驱动程序响应速率。在驱动程序的调试过程中,经常出现系统“死机”、“蓝屏”等现象,这些情况可能由于内存访问分页错误、寄存器非法操作等因素造成。

 

加载存储板的操作都是经过PCI9054的配置,但是在驱动程序设计过程中,一般会遇到DMA传输、内存映射、文件读写、中断处理等问题。在此模块,应注意的是读写访问和内存映射问题,对于局部总线地址进行的读写访问,它的部分驱动程序如下:

NTSTATUS SIGNALDevice::SIGNAL_MEM2_WRITE_Handler(KIrp I)

{⋯⋯ULONG *length*offset

ULONG plengthpoffset

Offset = (ULONG*)I.IoctlBuffer();      //数据在局部地址上的偏移量

poffset = *offset;

length=(ULONG*)I.IoetlBuffer()+l;      //数据传输个数

plength=*length

m_MemoryRange2.outd(poffset(unsigned long*)I.IoctlBuffer()+2plength)

..}

对于内存映射问题,因为内存管理和数据缓存相互对应,数据传输时使用缓冲区的方式就是内存映射的方式,在驱动程序开发过程中,有3种内存映射的方式:

(1)Buffered方式:IO管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区,驱动程序将使用这个系统缓冲区工作。IO管理器就负责在用户模式缓冲区与系统缓冲区之间复制数据;

(2)Direct方式:IO管理器锁定了包含用户模式数据缓冲区的物理内存页,并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。本模块采用这种方式;

(3)Neither方式:以上两种方式之外的方式,在这个策略中,IO管理器把调用者的输入缓冲区的地址放IRP当前的IO堆栈单元的Parameters.DeviceIoContr01.TypelnputBuffer域中,把输出缓冲区的地址存放到IRP UserBuffer域中。

 

打开中断和关闭中断

在应用程序中,若想打开中断,先通过ReadFile获取中断使能寄存器的值,如值为0,则通过WriteFile往该寄存器写入1打开中断。若想关闭中断,先ReadFile获取中断使能寄存器的值,如值为1,则通过WriteFile往该寄存器写人0关闭中断。

应用程序中的ReadFileWriteFile并不能直接访问设备,它们是通过驱动程序的readwrite例程来访问设备的。

NTSTATUS PcicardDevice::Read(KIrp I)

{

PUCHAR pBuffer=(PUCHAR)I.BufferedReadDest();  //获取数据缓冲区地址

*pBuffer=m_IoPortRange.inb(IntEnableReg);     //从端口读中断使能寄存器的值

}

Write例程与Read例程相似,只是获得缓冲区地址的语句和访问设备的语句不同。

PUCHAR pBuffer=(PUCHAR)I.BufferedWriteSource();  //获取数据缓冲区地址

m_ioPonRange.outb(IntEnableReg*pBuffer);    //往端口写一个字节数据,

IO端口的访问

当驱动程序设置事件后,应用程序通过WaitForSingleObject(hEvent0)函数接到“通知”,开始利用DeviceloControl函数对设备发起读操作。

DeviceloControl(hDevicePCICARD_IOCTRL_EADNULL0DataBufferIOCTRL_OUTBUF_SIZE&nOutputNULL)

驱动程序在处理该IRP时,并不是直接去读设备,而是利用函数SynchronizeInterrupt(&m_IrqLinkTo(ReadDataFromDevice)PIRP(I))调用中断同步例程ReadDataFromDevice

B00LEAN PcicardDevice::ReadDataFromDevice(PVOID pIrp)

{

PUCHAR pBuffer=(PUCHAR)I.IoetlBuffer();//获得应用程序数据缓冲区地址

m_MemoryRange.inb(OffsetpBufferCount)

I.Information()=Count

m_bNotifyApp=TRUE  //当完成读操作后,给m_bNotifyApp赋值为TRUE,当数据采集卡再次发出中断时,在中断服务例程及延时处理例程中重新设置事件。

return TRUE

}

断处理例程

中断处理需要声明并在P11P启动例程中初始化KInterruptKDeferredCall类实例,还需要声明中断服务例程和延迟过程调用例程。当创建驱动程序框架时,若有中断资源,这些都是自动生成的。图1是中断处理流程图。

在中断服务例程中,首先判断该中断是否是自己的设备产生的,若不是,返回FALSE;若是,进行必要的处理,请求一个DPC,然后返回TRUE

BOOLEAN PcicardDevice::Isr_Irq(void)      //中断服务例程.

{

if((m_IoPortRange.inb(OxO)&&m_IoPortRange.inb(0x1))==0)

return FALSE              //判断是否为本设备产生的中断

m_IoPortRange.outb(OxOO)    //清除中断

if(!m_DpcFor_Irq.Request(NULLNULL)){}   //请求延迟过程调用

return TRUE

}

在延迟过程调用例程中进行相应的中断处理。

if(m_pEvent)

{

SynchStatus=SynchronizeInterrupt(&m_IrqLinkTo(TestAndClearNotifyApp)&Notify)      //调用中断同步例程

TestAndClearNotifyApp

if(Notify)

m_pEvent->Set()    //设置事件

}

 

在应用程序中用CreateEvent函数创建自动重置事件,并调用DeviceIoControl函数把事件的句柄传递给驱动程序。

m_hEvent =

抱歉!评论已关闭.