您需要ioread8 / iowrite8 或任何至少 转换为volatile* 的东西,以确保优化仍会导致恰好 1 次访问(不是 0 或超过 1)。事实上,他们做的不止这些,处理字节序(他们也处理字节序,以小字节序访问设备内存。或者ioread32be 用于大字节序)和一些编译时重新排序的内存屏障语义,Linux 选择包含在这些语义中功能。由于 DMA,甚至读取后的运行时障碍。使用_rep 版本从设备内存中复制一个块,只有一个屏障。
在 C 中,数据竞争是 UB(未定义行为)。这意味着允许编译器假设通过非volatile 指针访问的内存在访问之间不会改变。而转向 if (x) y = *ptr; 可以转换为 tmp = *ptr; if (x) y = tmp; 即编译时推测加载。
MMIO 寄存器即使在读取时也可能有副作用,因此您必须阻止编译器执行不在源代码中的加载,并且必须强制它执行所有存在的加载 在源中恰好出现一次。
商店也一样。 (编译器甚至不允许对非易失性对象进行写操作,但他们可以删除死存储。例如,*ioreg = 1; *ioreg = 2; 通常会与*ioreg = 2; 编译相同,第一个存储被删除为“死”,因为它不被认为是有明显的副作用。
C volatile 语义对于 MMIO 来说是理想的,但 Linux 围绕它们包装了更多的东西,而不仅仅是 volatile。
通过谷歌搜索 ioread8 并在 https://elixir.bootlin.com/linux/latest/source/lib/iomap.c#L11 中四处寻找,我们看到 Linux I/O 地址可以编码 IO 地址空间(端口 I/O,又名 PIO;in/out 说明x86)与内存地址空间(正常加载/存储到特殊地址)。而ioread* 函数实际上会检查并相应地进行调度。
/*
* Read/write from/to an (offsettable) iomem cookie. It might be a PIO
* access or a MMIO access, these functions don't care. The info is
* encoded in the hardware mapping set up by the mapping functions
* (or the cookie itself, depending on implementation and hw).
*
* The generic routines don't assume any hardware mappings, and just
* encode the PIO/MMIO as part of the cookie. They coldly assume that
* the MMIO IO mappings are not in the low address range.
*
* Architectures for which this is not true can't use this generic
* implementation and should do their own copy.
*/
例如实现,这里是ioread16。 (IO_COND 是一个宏,它根据预定义的常量检查地址:低地址是 PIO 地址)。
unsigned int ioread16(void __iomem *addr)
{
IO_COND(addr, return inw(port), return readw(addr));
return 0xffff;
}
如果您只是将ioremap 结果转换为volatile uint32_t*,会出现什么问题?
例如如果您使用了READ_ONCE / WRITE_ONCE,它们只是转换为volatile unsigned char* 或其他任何东西,并用于对共享变量的原子访问。 (在 Linux 的手动 volatile + inline asm 原子实现中,它使用而不是 C11 _Atomic)。
如果编译时重新排序不成问题,这实际上可能适用于 x86 等一些 little-endian ISA,但其他一些则需要更多障碍。如果您查看 definition of readl(ioread32 用于 MMIO,而不是 inl 用于 PIO),它会在 volatile 指针的取消引用周围使用障碍。
(这个和它使用的宏在同一个io.h 中定义,或者您可以使用 LXR 链接进行导航:每个标识符都是一个超链接。)
static inline u32 readl(const volatile void __iomem *addr) {
u32 val;
__io_br();
val = __le32_to_cpu(__raw_readl(addr));
__io_ar(val);
return val;
}
通用的__raw_readl 只是可变的解引用;一些 ISA 可能会提供自己的。
__io_ar() 在阅读后使用 rmb() 或 barrier()。 /* prevent prefetching of coherent DMA data ahead of a dma-complete */。读前障碍只是barrier() - 阻止编译时重新排序而无需 asm 指令。
错误问题的旧答案:下面的文字回答了为什么您需要致电ioremap。
因为它是一个物理地址,并且内核内存没有身份映射 (virt = phys) 到物理地址。
并且返回虚拟地址不是一种选择:并非所有系统都有足够的虚拟地址空间,甚至可以将所有物理地址空间直接映射为连续的虚拟地址范围。 (但是当有足够的空间时,Linux 会这样做,例如 x86-64 Linux 的虚拟地址空间布局记录在 x86_64/mm.txt
在 RAM 超过 1GB 或 2GB 的系统上尤其是 32 位 x86 内核(取决于内核的配置方式:2:2 或 1:3 内核:虚拟地址空间的用户分割)。使用针对 36 位物理地址空间的 PAE,32 位 x86 内核可以使用比一次映射更多的物理内存更多。 (这太可怕了,让内核很难过:一些随机博客转发了 Linus Torvald 的 cmets 关于 PAE really really sucks 的问题。)
其他 ISA 也可能有这个,IDK Alpha 在需要字节访问时对 IO 内存做了什么;也许将字加载/存储映射到字节加载/存储的物理地址空间区域已提前处理,因此您请求正确的物理地址。 (http://www.tldp.org/HOWTO/Alpha-HOWTO-8.html)
但 32 位 x86 PAE 显然是 Linux 非常关心的一个 ISA,即使在 Linux 历史的早期也是如此。