【问题标题】:Why shouldn't I use ioremap on system memory for ARMv6+?为什么我不应该在 ARMv6+ 的系统内存上使用 ioremap?
【发布时间】:2017-08-24 23:09:17
【问题描述】:

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

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

通过在启动时将mem= 参数传递给内核来保留RAM 的顶部。例如,如果您有 256 MB,则参数 mem=255M 会阻止内核使用最高兆字节。您的模块稍后可以使用以下代码来访问此类内存: 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 mem=255M memmap=1M$255M
# 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
8ff00000-8fffffff : foo

到目前为止,一切看起来都不错,而且我的驱动程序运行良好。我可以直接读写我选择的特定物理地址。

但是,在启动过程中,触发了一个可怕的警告 ()

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);
}

这究竟会导致什么问题?它们可以减轻吗?我的替代方案是什么?

【问题讨论】:

  • 现代、基于 devicetree 的嵌入式系统的一种替代方法是在 devicetree 中为设备保留内存。请参阅内核源代码中的“Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt”。

标签: linux linux-kernel arm embedded-linux ioremap


【解决方案1】:

所以我确实做到了,而且它正在工作。

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

这究竟会导致什么问题?

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

ARM 内核将 RAM 映射为具有写回缓存的普通内存;它还在单处理器系统上标记为非共享。用于映射 I/O 内存以供 CPU 使用的 ioremap() 系统调用不同:该内存被映射为设备内存,未缓存,并且可能是共享的。这些不同的映射为两种类型的内存提供了预期的行为。当有人调用 ioremap() 为系统 RAM 创建新映射时,事情变得棘手。

这些多重映射的问题在于它们将具有不同的属性。从 ARM 体系结构的第 6 版开始,这种情况下的指定行为是“不可预测的”。

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

可以缓解吗?

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


附录

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

的结果
memblock_is_memory(pfn << PAGE_SHIFT);  

这反过来只是简单地返回结果

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

我建议破解内核代码以暴露冲突。
在调用 ioremap() 之前,将 TRUE 分配给全局变量 memblock_debug
以下补丁应显示有关内存冲突的重要信息。
(memblock 列表按基地址排序,因此 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 ATAGsthis related answer

【讨论】:

  • 我已经添加了您要求的详细信息 - 我映射的 RAM 似乎不是系统内存。
  • 或者,更确切地说,我已经成功地将其从系统中移除。
  • 添加建议供您尝试。
  • 我会试试这个并在早上报告。我省略了我的代码,因为驱动程序代码可能会变得很长,而且我认为它不会有帮助(因为已经描述了该技术,这实际上是关于内核内部和 ARM 规范)。如果您不同意,我可以清理并发布。
  • 提出了一个可以提供更多信息的补丁,也可能是根本原因。
猜你喜欢
  • 2012-07-26
  • 1970-01-01
  • 1970-01-01
  • 2011-09-12
  • 1970-01-01
  • 1970-01-01
  • 2017-09-14
  • 2010-09-30
  • 2011-03-19
相关资源
最近更新 更多