使用内存映射IO时调用ioread函数有什么好处

md.*_*mal 5 linux io x86 linux-device-driver linux-kernel

要使用内存映射 I/O,我们需要先调用 request_mem_region。

struct resource *request_mem_region(
                unsigned long start,
                unsigned long len,
                char *name);
Run Code Online (Sandbox Code Playgroud)

然后,由于内核在虚拟地址空间中运行,我们需要通过运行 ioremap 函数将物理地址映射到虚拟地址空间。

void *ioremap(unsigned long phys_addr, unsigned long size);
Run Code Online (Sandbox Code Playgroud)

那为什么不能直接访问返回值呢。

来自 Linux 设备驱动程序书

一旦配备了 ioremap(和 iounmap),设备驱动程序就可以访问任何 I/O 内存地址,无论它是否直接映射到虚拟地址空间。但请记住,从 ioremap 返回的地址不应直接取消引用;相反,应该使用内核提供的访问器函数。

谁能解释这背后的原因或与优势,像存取函数ioread32iowrite8()

Pet*_*des 6

您需要ioread8/iowrite8或任何至少要强制转换的内容volatile*,以确保优化仍会导致恰好 1 次访问(不是 0 次或超过 1 次)。事实上,它们做的远不止这些,处理字节序(它们也处理字节序,以小端序访问设备内存。或ioread32be大端序)和 Linux 选择包含在这些函数中的一些编译时重新排序内存屏障语义。由于 DMA,甚至读取后的运行时障碍。使用该_rep版本从只有一个屏障的设备内存中复制一个块。


在 C 中,数据竞争是 UB (Undefined Behaviour)。这意味着编译器可以假设通过非volatile指针访问的内存在访问之间不会改变。并且这种转变if (x) y = *ptr;可以转化为tmp = *ptr; if (x) y = tmp;编译时推测性加载。

MMIO寄存器可能有副作用,甚至阅读,所以你必须从这样做,是不是在源负载停止编译器,并且必须迫使它做的一切,负载在源一次。

商店的交易相同。(编译器甚至不允许对非易失性对象进行写入,但他们可以删除死存储。例如*ioreg = 1; *ioreg = 2;,通常编译与*ioreg = 2; 第一个存储被删除为“死”相同,因为它不被认为具有可见的副作用。

Cvolatile语义是 MMIO 的理想选择,但 Linux 围绕它们包装的东西不仅仅是 volatile。


通过谷歌搜索ioread8https://elixir.bootlin.com/linux/latest/source/lib/iomap.c#L11的快速浏览,我们看到 Linux I/O 地址可以编码 IO 地址空间(端口 I/ O,又名 PIO;in/ outx86 上的指令)与内存地址空间(正常加载/存储到特殊地址)。并且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.
 */
Run Code Online (Sandbox Code Playgroud)

例如实现,这里是ioread16. (IO_COND 是一个宏,它根据预定义的常量检查地址:低地址是 PIO 地址)。

unsigned int ioread16(void __iomem *addr)
{
    IO_COND(addr, return inw(port), return readw(addr));
    return 0xffff;
}
Run Code Online (Sandbox Code Playgroud)

如果你只是将ioremap结果转换为 ,会破坏什么volatile uint32_t*

例如,如果您使用READ_ONCE/WRITE_ONCE只是强制转换为volatile unsigned char*或其他,并且用于对共享变量的原子访问。(在 Linux 的手动 volatile + 内联 asm 原子实现中,它使用而不是 C11 _Atomic)。

如果编译时重新排序不是问题,那实际上可能适用于一些小端 ISA,例如 x86,但其他人需要更多障碍。如果你看的定义readl(其中ioread32使用了MMIO,而不是inl对PIO),它使用周围的非关联障碍volatile指针。

(this 和 this 使用的宏定义与io.hthis相同,或者您可以使用 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;
}
Run Code Online (Sandbox Code Playgroud)

泛型__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

尤其是 32 位 x86 内核,其 RAM 超过 1 或 2GB(取决于内核的配置方式:2:2 或 1:3 内核:用户分割虚拟地址空间)。对于 36 位物理地址空间的 PAE,32 位 x86 内核可以使用比它一次映射更多的物理内存。(这非常可怕,并且让内核难以忍受:一些随机博客转发了 Linus Torvald 关于PAE 真的很糟糕的评论。)


其他 ISA 可能也有这个,当需要字节访问时,IDK Alpha 对 IO 内存做了什么;也许将字加载/存储映射到字节加载/存储的物理地址空间区域较早处理,因此您请求正确的物理地址。( http://www.tldp.org/HOWTO/Alpha-HOWTO-8.html )

但是 32 位 x86 PAE 显然是 Linux 非常关心的 ISA,即使在 Linux 历史的早期也是如此。