【问题标题】:Writing to HW registers via /dev/mem needs extra write通过 /dev/mem 写入硬件寄存器需要额外的写入
【发布时间】:2016-09-20 02:28:15
【问题描述】:

通过 /dev/mem 写入硬件寄存器需要额外的写入

我想在 Linux 上使用 /dev/mem 写入一些硬件寄存器。 目标板是ZYBO(Zynq,ARM Cortex-A9),硬件是AXI4 Lite Slave,有4个寄存器,由Xilinx Vivado自动生成。

这是写入硬件寄存器的 C 代码。

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>

#define MAP_BASE        (0x43c30000)
#define MAP_RANGE       (0x10000)

int main(int argc, char *argv[])
{
        volatile unsigned int *p;
        void *iomap_ptr;
        int fd, i, v;

        fd = open("/dev/mem", O_RDWR | O_SYNC);
        iomap_ptr = mmap(0, MAP_RANGE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MAP_BASE);

        p = (volatile unsigned int *)iomap_ptr;
        for (i = 0; i < 4; i++)
                p[i] = i;
        for (i = 0; i < 4; i++)
                printf("%04X: %08X\n", i, p[i]);

        munmap(iomap_ptr, MAP_RANGE);
        close(fd);
        return 0;
}

当我第一次运行 C 代码时,结果是:

0000: 00000000
0001: 00000001
0002: 00000002
0003: 00000000

好像最后的write没有应用。

然后,当我再次运行 C 代码时,结果是:

0000: 00000000
0001: 00000001
0002: 00000002
0003: 00000003

经过调查,我意识到我需要对硬件进行额外的写入才能应用最后一次写入。

如何在不进行额外写入的情况下写入硬件寄存器?

【问题讨论】:

  • 是否需要刷新部分 D-Cache? Xilinx 应该有一些宏/函数用于使特定地址范围的缓存失效/刷新。
  • @rjp:地址空间不是“不可共享/非缓存/严格排序”(希望我没有遗漏什么),硬件设计被破坏了。外设寄存器空间不应出现缓存问题。
  • @Olaf,我不反对。我没有在 Vivado 方面工作过,所以我不知道这是否需要指定和/或是否可能设置不正确。
  • 旁注:不要对外围寄存器使用标准整数类型,以及其他需要固定宽度类型的地方。使用stdint.h 类型。将完整的寄存器指针定义封装到宏中也是一种很好的做法。这样可以避免每次使用容易出错的指针时手动转换。
  • 我从 Digilent 的基本系统设计中删除了视频 DMA、协议转换器和互连。最后,C 代码以某种方式正常工作。这很奇怪......我认为基本系统设计或我的设计充当了 FIFO。无论如何,谢谢大家的回复。

标签: arm embedded linux-device-driver embedded-linux memory-barriers


【解决方案1】:

作为序言,我不知何故错过了问题中的重要信息,所以这个答案的内容远没有它需要的可移植性,但我相信它仍然可以解决问题。关于 Zynq 和 mmap 的 SO 有两个相关问题:

Flush cache to DRAM

How to get writes via an mmap mapped memory pointer to flush immediately?

此处列出的赛灵思缓存函数似乎最终使用了与第一个链接问题中的最佳答案相同的方法。


这可能是缓存问题。在这种特定情况下,您可以使用 Xilinx 库来刷新寄存器所在的地址范围。头文件 xil_cache.h 有两个可用于此目的的函数:

void Xil_DCacheFlush(void); 
void Xil_DCacheFlushRange(unsigned int adr, unsigned len); 

根据版本,您可能有第三种选择(原型略有不同):

void Xil_DCacheFlush(void);
void Xil_DCacheFlushRange(INTPTR adr, u32 len);
void Xil_DCacheFlushLine(INTPTR adr);

第一个函数Xil_DCacheFlush 将刷新整个缓存。这不是一个特别理想的行为,因为可能会有大量与问题无关的缓存数据被刷新和失效,因此您可能会看到性能影响。

第三个函数Xil_DCacheFlushLine(如果可用)在这种情况下可能会起作用,因为您只有四个寄存器。它将强制写入(如果该行是脏的)然后使缓存无效,因此下一次读取将是未命中并且必须从您的寄存器中获取。不过,这不是最好的解决方案,因为它依赖于对外围设备的一些假设。

第二个函数Xil_DCacheFlushRange 可能就是你想要的函数。使用此功能,您可以指定外围设备的地址范围。在这种情况下,它很小,但该解决方案对于具有非常大寄存器集的复杂外围设备是可扩展的。

在对外围设备执行寄存器写入后,您应该能够调用上述任何您喜欢的函数来刷新全部或部分数据缓存。

也可能有一种方法可以在您的设计中禁用此地址范围的缓存,但我很难找到一个好的参考,而且您可能没有所需的区域位置/大小粒度。

【讨论】:

  • 如果 /dev/mem 返回一个可缓存的映射,可能是出了大问题。
  • 是的,我不知何故错过了那一点。看起来其他人在 Zynq 上也遇到过同样的问题:stackoverflow.com/questions/20750176/…
  • 我想的越多,它就越没有意义。其他问题确实与 Zynq 上 mmap 的缓存问题有关,但在这种情况下,我不明白为什么写入的值不会回来,即使它没有命中外围寄存器,因为正确的值应该在缓存中。
  • 我认为这个问题无法回答。我们不知道具体的设计如何,也不知道具体的内核等等。
  • 当然,如果它是不可缓存的,而不是设备映射,那么它仍然可以是可写缓冲的,但随后的读取应该在写缓冲区中命中。从本质上讲,就 CPU、缓存和互连而言(即架构内存模型),在同一个 CPU 上,写入地址然后读取 same 地址应该总是返回写入的值,因此很可能是外围设备本身的一些时序问题。您可以从软件中做的最好的事情是发出一个 DSB 来强制完成未完成的写入。
猜你喜欢
  • 2018-03-16
  • 1970-01-01
  • 2013-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-09
  • 2020-03-20
  • 1970-01-01
相关资源
最近更新 更多