为什么我不应该在 ARMv6+ 的系统内存上使用 ioremap?

Why shouldn't I use ioremap on system memory for ARMv6+?

我需要从内核中保留一个大的物理连续 RAM 缓冲区,并能够保证该缓冲区将始终使用特定的硬编码物理地址。该缓冲区应在内核的整个生命周期内保留。我已经编写了一个 chardev 驱动程序作为在用户空间中访问此缓冲区的接口。我的平台是一个嵌入式系统,采用 ARMv7 架构 运行 2.6 Linux 内核。

Linux Device Drivers, Third Edition 的第 15 章关于主题(第 443 页)有以下内容:

Reserving the top of RAM is accomplished by passing a mem= argument to the kernel at boot time. For example, if you have 256 MB, the argument mem=255M keeps the kernel from using the top megabyte. Your module could later use the following code to gain access to such memory: dmabuf = ioremap (0xFF00000 /* 255M */, 0x100000 /* 1M */);

我已经做到了,还有一些其他的事情:

  1. 除了 mem 之外,我还在使用 memmap bootarg。 kernel boot parameters documentation 建议在使用 mem 时始终使用 memmap 以避免地址冲突。
  2. 我在调用 ioremap 之前使用了 request_mem_region,当然,我在继续之前检查它是否成功。

这是我完成所有操作后系统的样子:

# cat /proc/cmdline 
root=/dev/mtdblock2 console=ttyS0,115200 init=/sbin/preinit earlyprintk debug <strong>mem=255M memmap=1M5M</strong>
# cat /proc/iomem 
08000000-0fffffff : PCIe Outbound Window, Port 0
  08000000-082fffff : PCI Bus 0001:01
    08000000-081fffff : 0001:01:00.0
    08200000-08207fff : 0001:01:00.0
18000300-18000307 : serial
18000400-18000407 : serial
1800c000-1800cfff : dmu_regs
18012000-18012fff : pcie0
18013000-18013fff : pcie1
18014000-18014fff : pcie2
19000000-19000fff : cru_regs
1e000000-1fffffff : norflash
40000000-47ffffff : PCIe Outbound Window, Port 1
  40000000-403fffff : PCI Bus 0002:01
    40000000-403fffff : 0002:01:00.0
  40400000-409fffff : PCI Bus 0002:01
    40400000-407fffff : 0002:01:00.0
    40800000-40807fff : 0002:01:00.0
80000000-8fefffff : System RAM
  80052000-8045dfff : Kernel text
  80478000-80500143 : Kernel data
<strong>8ff00000-8fffffff : foo</strong>

到目前为止一切看起来都很好,我的驱动程序工作得很好。我可以直接读取和写入我选择的特定物理地址。

然而,在启动过程中,触发了一个可怕的大警告():

BUG: Your driver calls ioremap() on system memory.  This leads
to architecturally unpredictable behaviour on ARMv6+, and ioremap()
will fail in the next kernel release.  Please fix your driver.
------------[ cut here ]------------
WARNING: at arch/arm/mm/ioremap.c:211 __arm_ioremap_pfn_caller+0x8c/0x144()
Modules linked in:
[] (unwind_backtrace+0x0/0xf8) from [] (warn_slowpath_common+0x4c/0x64)
[] (warn_slowpath_common+0x4c/0x64) from [] (warn_slowpath_null+0x1c/0x24)
[] (warn_slowpath_null+0x1c/0x24) from [] (__arm_ioremap_pfn_caller+0x8c/0x144)
[] (__arm_ioremap_pfn_caller+0x8c/0x144) from [] (__arm_ioremap_caller+0x50/0x58)
[] (__arm_ioremap_caller+0x50/0x58) from [] (foo_init+0x204/0x2b0)
[] (foo_init+0x204/0x2b0) from [] (do_one_initcall+0x30/0x19c)
[] (do_one_initcall+0x30/0x19c) from [] (kernel_init+0x154/0x218)
[] (kernel_init+0x154/0x218) from [] (kernel_thread_exit+0x0/0x8)
---[ end trace 1a4cab5dbc05c3e7 ]---

触发自:arc/arm/mm/ioremap.c

/*
 * Don't allow RAM to be mapped - this causes problems with ARMv6+
 */
if (pfn_valid(pfn)) {
    printk(KERN_WARNING "BUG: Your driver calls ioremap() on system memory.  This leads\n"
           KERN_WARNING "to architecturally unpredictable behaviour on ARMv6+, and ioremap()\n"
           KERN_WARNING "will fail in the next kernel release.  Please fix your driver.\n");
    WARN_ON(1);
}

这究竟会导致什么问题?它们可以减轻吗?我有什么选择?

So I've done exactly that, and it's working.

提供内核命令行(例如/proc/cmdline)和生成的内存映射(即/proc/iomem ) 来验证这一点。

What problems, exactly, could this cause?

在系统内存上使用 ioremap() 的问题是您最终会向内存分配冲突的属性,这会导致“不可预测”的行为。
请参阅文章 "ARM's multiply-mapped memory mess",其中提供了您正在触发的警告的历史记录。

The ARM kernel maps RAM as normal memory with writeback caching; it's also marked non-shared on uniprocessor systems. The ioremap() system call, used to map I/O memory for CPU use, is different: that memory is mapped as device memory, uncached, and, maybe, shared. These different mappings give the expected behavior for both types of memory. Where things get tricky is when somebody calls ioremap() to create a new mapping for system RAM.

The problem with these multiple mappings is that they will have differing attributes. As of version 6 of the ARM architecture, the specified behavior in that situation is "unpredictable."

请注意,“系统内存”是由内核管理的 RAM。
您触发警告的事实表明您的代码正在为内存区域生成多个映射。

Can they be mitigated?

您必须确保您想要 ioremap() 的 RAM 不是“系统内存”,即由内核管理。
另见 this answer.


附录

这个与您有关的警告是 pfn_valid(pfn) 返回 TRUE 而不是 FALSE 的结果。
根据您为版本 2.6.37 提供的 Linux 交叉引用 link, pfn_valid() 只是返回

的结果
memblock_is_memory(pfn << PAGE_SHIFT);  

这反过来只是返回

的结果
memblock_search(&memblock.memory, addr) != -1;  

我建议破解内核代码,以便揭示冲突。
在调用 ioremap() 之前,将 TRUE 赋值给全局变量 memblock_debug.
下面的补丁应该会显示内存冲突的重要信息。
(成员块列表按基地址排序,因此 memblock_search() 对该列表执行二进制搜索,因此使用 mid 作为索引。 )

 static int __init_memblock memblock_search(struct memblock_type *type, phys_addr_t addr)
 {
         unsigned int left = 0, right = type->cnt;
 
         do {
                 unsigned int mid = (right + left) / 2;
 
                 if (addr < type->regions[mid].base)
                         right = mid;
                 else if (addr >= (type->regions[mid].base +
                                   type->regions[mid].size))
                         left = mid + 1;
-                else
+                else {
+                        if (memblock_debug)
+                                pr_info("MATCH for 0x%x: m=0x%x b=0x%x s=0x%x\n", 
+                                                addr, mid, 
+                                                type->regions[mid].base, 
+                                                type->regions[mid].size);
                         return mid;
+                }
         } while (left < right);
         return -1;
 }

如果要查看所有内存块,则调用memblock_dump_all(),变量memblock_debug为TRUE。

[有趣的是,这本质上是一个编程问题,但我们还没有看到您的任何代码。]


附录 2

由于您可能正在使用 ATAG(而不是设备树),并且您想要专用内存区域,请修复 ATAG_MEM 以反映这个较小的物理内存大小。
假设您对启动代码进行了零更改,ATAG_MEM 仍然指定了完整的 RAM,因此这可能是导致警告的系统内存冲突的根源。
参见 this answer about ATAGs and this related answer