c++ - 在 mmap 区域上使用 memcpy 会崩溃,for 循环不会

标签 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,就好了。 IE。阅读内容正确。

但是,如果我在同一地址范围内使用 std::memcpy,Tegra CPU 总是会卡住。我没有看到任何错误消息,如果附加了 GDB,我在尝试跨过 memcpy 行时也没有在 Eclipse 中看到任何东西,它只是硬着头皮停止。由于远程控制台被卡住,我必须使用硬件重置按钮重置 CPU。

这是没有优化 (-O0) 的调试版本,使用 gcc-linaro-6.3.1-2017.05-i686-mingw32_arm-linux-gnueabihf。有人告诉我 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];
}

最佳答案

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

另一个表明您的逻辑没有正确响应的迹象是您需要额外阅读才能获得正确的响应。

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

我发现 super 有用的是添加逻辑以将所有 PCIE 事务记录到 SRAM 中,并能够转储 SRAM 以查看逻辑是如何运行或运行不正常的。我们有一个漂亮的实用程序,pcieflat ,每行打印一个 PCIE TLP。它甚至有 documentation .

当 PCIE 接口(interface)不能正常工作时,我将日志流式传输到十六进制的 UART,它可以被 pcieflat 解码。

此工具对于调试性能问题也很有用——您可以查看 DMA 读写的流水线化程度。

或者,如果您在 FPGA 上集成了逻辑分析器或类似工具,则可以通过这种方式跟踪事件。但是根据 PCIE 协议(protocol)解析 TLP 会更好。

关于c++ - 在 mmap 区域上使用 memcpy 会崩溃,for 循环不会,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52162620/

相关文章:

c++ - 是否有一组标准的编译器选项?

c++ - CreateDIBSection 图形工件或如何正确访问位图位

c++ - 如何判断 .cpp 文件在哪个项目中

linux - 打印宏在汇编语言中如何工作?

vhdl - 如何根据常数的对数设置 VHDL 向量大小

vhdl - 如何在不使用加法器的情况下制作数字的 2 补码

c++ - 宏参数列表中的指针

java - 以另一个用户身份加载进程?

java - 如何使用 Java 程序在 Linux 服务器中使用给定用户 ID 的密码执行 sudo 命令?

operating-system - "full source package"是什么意思