最近在修改一份基于DriverStudio的PCI视频采集卡驱动代码,遇到了关于DMA的使用上的疑惑。
其OnStartDevice初始化了相关资源,代码如下:
PCM_RESOURCE_LIST pResListRaw = I.AllocatedResources();
// Get the list of translated resources from the IRP
PCM_RESOURCE_LIST pResListTranslated = I.TranslatedResources();
// Initialize the device descriptor for the DMA object using the assigned resource
DEVICE_DESCRIPTION dd;
RtlZeroMemory(&dd, sizeof(dd));
dd.Version = DEVICE_DESCRIPTION_VERSION;
dd.Master = TRUE;
dd.ScatterGather = FALSE;
dd.DemandMode = TRUE;
dd.AutoInitialize = FALSE;
dd.Dma32BitAddresses = TRUE;
dd.IgnoreCount = FALSE;
dd.DmaChannel = 3;
dd.InterfaceType = PCIBus;
dd.DmaWidth = Width32Bits; // PCI default width
dd.DmaSpeed = MaximumDmaSpeed;
dd.MaximumLength = m_nDmaBufferSize;
// Initialize the DMA adapter object
m_Dma.Initialize(&dd, m_Lower.TopOfStack());
m_DmaBuffer.Initialize(&m_Dma,m_nDmaBufferSize,true);
// Create an instance of KPciConfiguration so we can map Base Address
// Register indicies to ordinals for memory or I/O port ranges.
KPciConfiguration PciConfig(m_Lower.TopOfStack());
// For each I/O port mapped region, initialize the I/O port range using
// the resources provided by NT. Once initialized, use member functions such as
// inb/outb, or the array element operator to access the ports range.
status = m_IoPortRange0.Initialize(
pResListTranslated,
pResListRaw,
PciConfig.BaseAddressIndexToOrdinal(0)
);
status = m_Irq.InitializeAndConnect(
pResListTranslated,
LinkTo(Isr_Irq),
this
);
if (!NT_SUCCESS(status))
{
Invalidate();
return status;
}
// Setup the DPC to be used for interrupt processing
m_DpcFor_Irq.Setup(LinkTo(DpcFor_Irq), this);
这里初始化了DMA,但是没有绑定PCI设备物理地址与驱动空间地址的相关代码。然而在IoControl中却有以下操作
//写
UCHAR* pDMA = (UCHAR*)m_DmaBuffer.pVirtualAddress() + pCapInfo->nCode*pCapInfo->nSize + pCapInfo->nOffset;
memcpy(pDMA,pCapInfo->pData,pCapInfo->nSize);
//读
UCHAR* pDMA = (UCHAR*)m_DmaBuffer.pVirtualAddress() + pCapInfo->nCode*pCapInfo->nSize + pCapInfo->nOffset;
memcpy(pCapInfo->pData,pDMA,pCapInfo->nSize);
从代码可以看出,直接将m_DmaBuffer当做PCI的内存进行了访问。简单阅读了DriverStudio的封装源代码,初始化过程实际上调用了IoGetDmaAdapter与AllocateCommonBuffer,也就是说m_DmaBuffer.pVirtualAddress()实际上是AllocateCommonBuffer分配的公用缓冲区,而实际上并没有任何地方将该地址与PCI实际的物理地址进行绑定。查阅相关资料,AllocateCommonBuffer一般用于ScatterGather模式,这里使用的是block DMA模式。难道是IoGetDmaAdapter或者AllocateCommonBuffer自动将pVirtualAddress()与PCI的物理地址空间进行了绑定?查询相关资料,也没有这种解释。那究竟为什么能直接通过m_DmaBuffer.pVirtualAddress()操作PCI的物理地址空间呢?
求各位大神解答。
貌似KCommonDmaBuffer的物理地址通过IO传输给PCI采集卡DMA控制器做主动DMA目标地址后就是能用。与PCI采集卡内存空间分配的地址大小等完全无关
根据您提供的代码和描述,似乎确实没有明确将m_DmaBuffer.pVirtualAddress()与PCI设备的物理地址进行绑定。在没有绑定的情况下,直接通过该地址访问PCI设备的物理地址空间是不正确的。
在DriverStudio的代码中,m_DmaBuffer是通过AllocateCommonBuffer函数分配的一个公用缓冲区。AllocateCommonBuffer函数会分配一块连续的内存空间,并返回该内存空间的虚拟地址(pVirtualAddress)。但是,这个虚拟地址并没有直接与PCI设备的物理地址进行绑定。
为了正确地使用DMA进行数据传输,您需要执行以下步骤:
以下是一个示例代码片段,展示了如何将物理地址与虚拟地址进行映射,并在IoControl函数中使用映射后的地址进行读写操作:
// 获取PCI设备的物理地址
PHYSICAL_ADDRESS physAddr = m_DmaBuffer.PhysicalAddress();
// 将物理地址与虚拟地址进行映射
PVOID pMappedAddress = MmMapIoSpace(physAddr, m_nDmaBufferSize, MmNonCached);
// 在IoControl中使用映射后的地址进行读写操作
UCHAR* pDMA = (UCHAR*)pMappedAddress + pCapInfo->nCode*pCapInfo->nSize + pCapInfo->nOffset;
memcpy(pDMA, pCapInfo->pData, pCapInfo->nSize);
// 读取数据
UCHAR* pDMA = (UCHAR*)pMappedAddress + pCapInfo->nCode*pCapInfo->nSize + pCapInfo->nOffset;
memcpy(pCapInfo->pData, pDMA, pCapInfo->nSize);
// 解除映射
MmUnmapIoSpace(pMappedAddress, m_nDmaBufferSize);
您需要根据您的具体情况进行适当的修改和调整。确保在使用MmMapIoSpace函数映射物理地址时,使用正确的地址和大小参数