2 Windows环境下涉及DMA驱动程序编程的内容 DriverWorks提供了三个类:KDmaAdapter、KDmaTransfer和KCommonDmaBuffer类,用于实现DMA操作。KDmaAdapter类用于建立一个DMA适配器,它说明DMA通道的特性。KDmaTransfer类用于DMA传输控制。KCommonDmaBuffer类用于申请系统提供的公用缓冲区。下面简单介绍一下这三个类的主要成员函数: (1)KDmaAdapter类 Initialize( pDesc,,pPdo)或Initialize( pDesc,pPdo,nMaxScatterGatherPairs)。参数pDesc为设备描述结构指针;pPdo为物理设备对象指针;nMaxScatterGatherPairs为支持分散/聚集的总线主控设备的物理不连续缓冲区个数。 (2)KDmaTransfer类 ①KDmaTransfer( pDevice,pAdapter,pBuffer)或KDmaTransfer( void )。是该类的构造函数。参数pDevice为相关的设备对象指针;pAdapter为相关的适配器对象指针;pBuffer为相关的公用缓冲区对象指针。
②Initiate( Memory,Dir,Callback,pContext,BusMasterKeepAdapter )或 Initiate(pDevice,pAdapter,Memory,Dir,Callback,pBuffer,pContext,BusMasterKeepAdapter)。用于初始化DMA传输。参数Memory为内存对象指针;Dir为传输方向,取值为FromDeviceToMemory或FromMemoryToDevice;Callback为DMA准备就绪回调例程;pContext为传递给回调例程的环境参数地址指针;BusMasterKeepAdapter为是否保持适配器对象,正常情况下,总线主控设备释放适配器对象,但保持映射寄存器。 ③BytesRemaining( void )。返回当前传输的剩余字节数。 ④Terminate( void )。终止传输并释放适配器。 ⑤SequenceTransferDescriptors( ppTD )。获取当前传输段的单个描述符。只能在DMA准备就绪回调例程中调用。返回TRUE,表示取得下一个传输描述符。 ⑥Continue(XferCountType,Count)。继续传输,在一段传输完成后调用。参数XferCountType为传输长度类型;Count为指定的传输字节数。 另外,KDevice类还有一个成员函数DEVMEMBER_DMAREADY( class_name, function_name )与DMA准备就绪回调例程有关。用于声明DMA准备就绪回调例程为KDevice派生类的一个成员函数。参数class_name为定义DMA准备就绪回调例程为其成员函数的类名;function_name:DMA准备就绪回调例程名。
3 PCI接口卡下DMA驱动程序编程实例 下面结合在科研工作中的一个开发实例介绍WMD的编程。本实例中的PCI的接口卡,采用PLX公司的PCI9054芯片,局部总线接口模式为C模式。PCI9054芯片的局部总线信号线和一个FPGA芯片相连。设备的访问资源请求三个:前两个固定用于PCI9054芯片的操作寄存器,第三个为I/O映射空间,用于设备访问。在FPGA内部设计了一个FIFO,可以通过I/O指令将数据写入FIFO及清空FIFO。DMA传输采用块模式,从FIFO中读取数据。接口卡原理图如附图所示。 第一步:生成驱动程框架 使用DriverWorks中的DriverWizard创建PCI驱动程序框架,定义驱动程序工程名为PCI9054。注意需要声明所需的资源,如存储器空间和I/O空间,中断和DMA等。 第二步:修改和增加程序框架中的内容。 在VC中打开PCI9054工程,在程序框架基础上进一步进行编程。由于源代码内容太多,这里只给出修改和增加程序内容。 在PCI9054Device.h中改动如下: (1)在类class PCI9054Device : public KpnpDevice中增加下面的宏的成员函数 DEVMEMBER_DMAREADY(PCI9054Device, OnDmaReady)
DEVMEMBER_CANCELIRP(PCI9054Device, CancelQueuedIrp)
VOID StartDMA(ULONG PAddress,ULONG NBytes);
VOID OnDmaReady(KDmaTransfer* pXfer, KIrp I); // COMMENT_ONLY
(2)类class PCI9054Device : public KpnpDevice中增加所使用类的声名
KDmaTransfer* m_CurrentTransfer;
KCommonDmaBuffer m_Buffer;
PCI9054Device.cpp文件中改动如下:
(1)下面是PCI9054中有关中断和DMA操作寄存器的偏移地址
#define INTCSR 0x68
#define DMAMODE0 0x80
#define DMAPADR0 0x84
#define DMALADR0 0x88
#define DMASIZ0 0x8C
#define DMADPR0 0x90
#define DMACSR0 0xA8
(2)在函数VOID PCI9054Device::Invalidate()中增加下面函数调用
m_Buffer.Invalidate();
(3)在函数NTSTATUS PCI9054Device::OnStartDevice(KIrp I)中,在m_Dma.Initialize(&dd, m_Lower.TopOfStack())函数之后增加函数调用语句
m_Buffer.Initialize(&m_Dma,2048);
另外,在该函数最后境加下面函数调用语句
m_IoPortRange0.outd(INTCSR,0x40100); //允许PCI中断和DMA通道0中断
(4)在函数NTSTATUS PCI9054Device::OnStopDevice(KIrp I)中增加下面函数调用
m_IoPortRange0.outd(INTCSR,0); //禁止PCI中断和DMA通道0中断
m_Irq.Disconnect();
(5)NTSTATUS PCI9054Device::OnRemoveDevice(KIrp I) 中增加下面函数调用
m_IoPortRange0.outd(INTCSR,0);
m_Irq.Disconnect();
(6)在SerialRead()函数中增加有关创建KdmaTransfer类实例内容
void PCI9054Device::SerialRead(KIrp I) { NTSTATUS status = STATUS_SUCCESS;
m_CurrentTransfer = new(NonPagedPool) KDmaTransfer(this, &m_Dma);
if ( m_CurrentTransfer == NULL )
{ status = STATUS_INSUFFICIENT_RESOURCES; I.Information() = 0; I.Status() = status; PnpNextIrp(I); }
status = m_CurrentTransfer->Initiate(this, &m_Dma, I.Mdl(), (I.MajorFunction() == IRP_MJ_READ) ? FromDeviceToMemory : FromMemoryToDevice, LinkTo(OnDmaReady), &m_Buffer);
if ( ! NT_SUCCESS(status) )
{ delete m_CurrentTransfer;
m_CurrentTransfer = NULL;
I.Information() = 0;
I.Status() = status; PnpNextIrp(I); } }
(7)在SerialWrite()函数中增加有关对硬件操作的内容
void PCI9054Device::SerialWrite(KIrp I)
{ NTSTATUS status = STATUS_SUCCESS;
ULONG i; KMemory Mem(I.Mdl());
 UCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
ULONG dwTotalSize = I.WriteSize(CURRENT);
ULONG dwBytesSent = 0;
m_IoPortRange1.outb(0,0); //清空FIFO
for (i=0;i<dwTotalSize;i++) m_IoPortRange1.outb(0x4,*pBuffer++);//写数据
I.Information() = dwBytesSent;
I.Status() = status; PnpNextIrp(I); }
(8)修改下面几个函数为
VOID PCI9054Device:pcFor_Irq(PVOID Arg1, PVOID Arg2)
{ m_CurrentTransfer->Continue(UseTransferSize); }
BOOLEAN PCI9054Device::Isr_Irq(void)
{ ULONG status;
status= m_IoPortRange0.ind(INTCSR);
if ((status & 0x200000)==0) //判断是否为DMA通道0的传输结束中断
{ return FALSE;//不是,返回FALSE }
m_IoPortRange0.outd(DMAMODE0,0x20800);//先禁止中断
m_IoPortRange0.outb(DMACSR0,0x10);//再清除中断
return TRUE; }
(9)增加下面新的成员函数
VOID PCI9054Device::StartDMA(ULONG PAddress,ULONG NBytes)
{ m_IoPortRange0.outd(DMAMODE0,0x20C00);
m_IoPortRange0.outd(DMAPADR0,PAddress); //DMA通道0 的PCI地址
m_IoPortRange0.outd(DMALADR0,0x4); //自己设计的FIFO地址
m_IoPortRange0.outd(DMASIZ0,NBytes); //DMA通道0传输数据长度
m_IoPortRange0.outd(DMADPR0,0x8); //从设备到主机
m_IoPortRange0.outb(DMACSR0,0x3); //使通道0可用,并启动
}
VOID PCI9054Device::OnDmaReady(KDmaTransfer* pXfer, KIrp I)
{
if (pXfer->BytesRemaining() == 0) { //DMA结束时
pXfer->Terminate();
I.Information() = I.ReadSize(CURRENT);
I.Status() = STATUS_SUCCESS;
 npNextIrp(I);
m_CurrentTransfer = NULL; delete pXfer;
return;
}
 TRANSFER_DESCRIPTOR ptd; //DMA启动时
while (pXfer->SequenceTransferDescriptors(&ptd))
{ }
if ((ULONG) pXfer->BytesRemaining() == I.ReadSize()) StartDMA(ptd->td_PhysAddr.LowPart,ptd->td_Length); }
说明:本实例是在Windows 2000环境下,利用Windows 2000 DDK、DriverWorks和VC++ 6.0进行开发的,并经过Windows 2000/XP下的测试。
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) | Powered by Discuz! 7.0.0 |