要使用内存映射的I/O,我们需要首先调用request_mem_region。
struct resource *request_mem_region(
unsigned long start,
unsigned long len,
char *name);
然后,由于内核在虚拟地址空间中运行,因此我们需要通过运行ioremap函数将物理地址映射到虚拟地址空间。
void *ioremap(unsigned long phys_addr, unsigned long size);
那为什么我们不能直接访问返回值。
从Linux设备驱动程序书
Once equipped with ioremap (and iounmap), a device driver can access any I/O memory address, whether or not it is directly mapped to virtual address space. Remember, though, that the addresses returned from ioremap should not be dereferenced directly; instead, accessor functions provided by the kernel should be used.
谁能用
ioread32
或iowrite8()
这样的访问器函数解释其背后的原因或优势?
最佳答案
您需要ioread8
/iowrite8
或至少转换为volatile*
的任何内容,以确保优化仍然导致精确的1次访问(而不是0或大于1)。实际上,他们在处理字节序方面做得比这还要多(它们还处理字节序,以little-endian的形式访问设备内存。或者big-endian的ioread32be
)以及Linux选择在这些函数中包括的编译时重新排序内存屏障语义。甚至由于DMA,读取后的运行时障碍。使用_rep
版本仅通过一个屏障即可从设备内存中复制一个块。
在C中,数据竞争是UB(未定义行为)。这意味着允许编译器假定通过non-volatile
指针访问的内存在两次访问之间不发生变化。而且可以将转换的if (x) y = *ptr;
转换为tmp = *ptr;
if (x) y = tmp;
,即编译时的推测性负载。
MMIO寄存器甚至在读取时也会有副作用,因此您必须停止编译器执行源代码中没有的加载,并且必须强制编译器一次执行源代码中的所有加载。
商店也一样。 (甚至不允许编译器发明对非 Volatile 对象的写操作,但它们可以删除无效存储。例如,*ioreg = 1; *ioreg = 2;
通常与*ioreg = 2;
编译相同。由于认为不可见的一面,第一个存储被删除为“无效”。影响。
C volatile
语义是MMIO的理想选择,但是Linux围绕它们包装的内容不止是易变的。
快速浏览谷歌搜索ioread8
并浏览https://elixir.bootlin.com/linux/latest/source/lib/iomap.c#L11后,我们发现Linux I/O地址可以对IO地址空间(端口I/O,又名PIO; x86上的in
/out
指令)与内存地址空间(正常负载/存储到特殊地址)。 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 +内联asm原子实现中,它使用的不是C11 _Atomic
)。如果编译时重新排序不是问题,那么这实际上可能适用于x86等一些低端的ISA,但是其他的则需要更多的障碍。如果查看definition of
readl
(ioread32
用于MMIO,而不是PIO的inl
),它将在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
只是volatile取消引用;一些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中
值得注意的是,具有超过1或2GB RAM的系统上的32位x86内核(取决于内核的配置方式:2:2或1:3内核:虚拟地址空间的用户划分)。借助用于36位物理地址空间的PAE,32位x86内核可以使用比一次映射要多得多的物理内存。 (这非常可怕,并且使内核难以生存:一些随机博客取代了Linus Torvald关于PAE really really sucks的评论。)
其他ISA也可能具有此功能,并且IDK在需要字节访问时Alpha对IO存储器的作用;可能较早处理了将字加载/存储映射到字节加载/存储的物理地址空间区域,因此您请求正确的物理地址。 (http://www.tldp.org/HOWTO/Alpha-HOWTO-8.html)
但是32位x86 PAE显然是Linux关心的一个ISA,即使在Linux的早期也是如此。
关于linux - 使用内存映射的IO时调用ioread函数有什么好处,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59113831/