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



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

Linux 设备驱动程序,第三版的第 15 章对这个主题有如下说明(第 443 页):

保留 RAM 顶部是通过在引导时将mem=参数传递给内核来完成的。例如,如果您有 256 MB,则参数mem=255M会阻止内核使用前兆字节。您的模块稍后可以使用以下代码来访问此类内存:dmabuf = ioremap (0xFF00000 /* 255M */, 0x100000 /* 1M */);

我已经这样做了,再加上其他几件事:

除了mem的引导之外,
  1. 我还在使用memmap引导。内核引导参数文档建议,无论何时使用mem,请始终使用memmap以避免地址冲突。
  2. 我在打电话之前使用了request_mem_regionioremap当然,我会在继续之前检查它是否成功。

这是我完成所有这些操作后系统的外观:

# cat /proc/cmdline 
root=/dev/mtdblock2 console=ttyS0,115200 init=/sbin/preinit earlyprintk debugmem=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 leadsn"
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);
}

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

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

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

这究竟会导致什么问题?

在系统内存上使用ioremap()的问题在于,您最终会为内存分配冲突的属性,从而导致"不可预测"的行为.
请参阅文章"ARM的乘法映射内存混乱",该文章提供了要触发的警告的历史记录。

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

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

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

它们能减轻吗?

你必须确保你想要的RAM ioremap()不是"系统内存",即由内核管理.
另请参阅此答案。

<小时 />

附录

此警告与您有关是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_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%xn", 
+                                                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,因此这可能是导致警告的系统内存冲突的根源。
请参阅有关 ATAG 的答案,并与此相关答。

最新更新