在mmap'ed区域使用memcpy会崩溃,for循环不会

时间:2018-09-04 09:02:55

标签: c++ linux fpga pci-e memory-mapping

我在载板上有一个NVIDIA Tegra TK1处理器模块,并连接有PCI-e插槽。在该PCIe插槽中是一个FPGA板,该板通过PCIe暴露了一些寄存器和64K内存区域。

在Tegra板的ARM CPU上,最小的Linux安装正在运行。

我正在使用/ dev / mem和mmap函数来获取指向寄存器结构和64K内存区域的用户空间指针。 不同的寄存器文件和存储块都是分配的地址,这些地址是对齐的,并且对于4KB的存储页不重叠。 我使用getpagesize()的结果(也是4096)明确地用mmap映射了整个页面。

我可以从那些暴露的寄存器中读取/写入数据。 我可以从内存区域(64KB)中读取数据,然后在for循环中逐字读取uint32,就可以了。即读取的内容正确。

但是,如果我在相同的地址范围上使用std :: memcpy,则Tegra CPU总是死机。我没有看到任何错误消息,如果连接了GDB,当我尝试越过memcpy行时,在Eclipse中也看不到任何东西,它只是很难停止。而且,由于远程控制台被冻结,我必须使用硬件重置按钮来重置CPU。

这是使用gcc-linaro-6.3.1-2017.05-i686-mingw32_arm-linux-gnueabihf进行的没有优化(-O0)的调试版本。有人告诉我64K区域可以按字节访问,但我没有明确尝试。

是否有我需要担心的实际(潜在)问题,还是有特定原因导致memcpy无法工作并且在这种情况下不应该首先使用-我可以继续使用我的for循环,什么都没想到?

编辑:已观察到另一种效果:原始代码段在读取内存之前在复制for循环中缺少“重要的” printf。删除后,我没有取回有效数据。现在,我更新了代码片段,以从同一地址(而不是printf)进行额外读取,这也会产生正确的数据。混乱加剧了。

(我认为)正在发生的事情的重要摘录。进行了一些细微的修改,如图所示,以这种“简化”的形式。

// void* physicalAddr: PCIe "BAR0" address as reported by dmesg, added to the physical address offset of FPGA memory region
// long size: size of the physical region to be mapped 

//--------------------------------
// doing the memory mapping
//

const uint32_t pageSize = getpagesize();
assert( IsPowerOfTwo( pageSize ) );

const uint32_t physAddrNum = (uint32_t) physicalAddr;
const uint32_t offsetInPage = physAddrNum & (pageSize - 1);
const uint32_t firstMappedPageIdx = physAddrNum / pageSize;
const uint32_t lastMappedPageIdx = (physAddrNum + size - 1) / pageSize;
const uint32_t mappedPagesCount = 1 + lastMappedPageIdx - firstMappedPageIdx;
const uint32_t mappedSize = mappedPagesCount * pageSize;
const off_t targetOffset = physAddrNum & ~(off_t)(pageSize - 1);

m_fileID = open( "/dev/mem", O_RDWR | O_SYNC );
// addr passed as null means: we supply pages to map. Supplying non-null addr would mean, Linux takes it as a "hint" where to place.
void* mapAtPageStart = mmap( 0, mappedSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileID, targetOffset );
if (MAP_FAILED != mapAtPageStart)
{
    m_userSpaceMappedAddr = (volatile void*) ( uint32_t(mapAtPageStart) + offsetInPage );
}

//--------------------------------
// Accessing the mapped memory
//

//void* m_rawData: <== m_userSpaceMappedAddr
//uint32_t* destination: points to a stack object
//int length: size in 32bit words of the stack object (a struct with only U32's in it)

// this crashes:
std::memcpy( destination, m_rawData, length * sizeof(uint32_t) );

// this does not, AND does yield correct memory contents - but only with a preceding extra read
for (int i=0; i<length; ++i)
{
    // This extra read makes the data gotten in the 2nd read below valid.
    // Commented out, the data read into destination will not be valid.
    uint32_t tmp = ((const volatile uint32_t*)m_rawData)[i];
    (void)tmp; //pacify compiler

    destination[i] = ((const volatile uint32_t*)m_rawData)[i];
}

1 个答案:

答案 0 :(得分:1)

根据描述,您的FPGA代码似乎无法正确响应加载从FPGA上的位置读取的指令,并导致CPU锁定。它不会崩溃,它会永久停止,因此需要进行硬重置。在FPGA上调试PCIE逻辑时,我也遇到了这个问题。

另一个表明您的逻辑未正确响应的指示是,您需要额外阅读才能获得正确的响应。

您的循环正在执行32位加载,但是memcpy正在至少执行64位加载,这会改变逻辑的响应方式。例如,如果完成的前128位和完成的第二128位TLP中的后32位,则将需要使用两个具有32位响应的TLP。

我发现超级有用的是添加逻辑以将所有PCIE事务记录到SRAM中,并能够将SRAM转储出去以查看逻辑的行为方式或行为异常。我们有一个漂亮的工具pcieflat,每行打印一个PCIE TLP。它甚至有documentation

当PCIE接口无法正常工作时,我将日志以十六进制格式传输到UART,可以通过pcieflat对其进行解码。

此工具对于调试性能问题也很有用-您可以查看DMA读取和写入的流水线情况。

或者,如果您在FPGA上集成了逻辑分析仪或类似产品,则可以通过这种方式跟踪活动。但是最好是根据PCIE协议解析TLP。