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

时间:2017-03-30 20:33:05

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

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

Linux Device Drivers, Third Edition的第15章对该主题(第443页)有如下说明:

  

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

我已经完成了这个以及其他一些事情:

  1. 除了memmap之外,我还使用mem bootarg。每当您使用memmap来避免地址冲突时,kernel boot parameters documentation建议始终使用mem
  2. 我在调用request_mem_region之前使用了ioremap,当然,我在继续前进之前检查它是否成功。
  3. 在我完成所有这些之后,这就是系统的样子:

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

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

1 个答案:

答案 0 :(得分:7)

  

所以我已经做到了,而且它正在发挥作用。

提供内核命令行(例如 / 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

ADDENDUM

与您有关的警告是 pfn_valid(pfn)返回TRUE而非FALSE的结果。
基于您为2.6.37版提供的Linux交叉引用链接, pfn_valid()只是返回

的结果
memblock_is_memory(pfn << PAGE_SHIFT);  

反过来只是返回

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

我建议破解内核代码,以便揭示冲突 在调用 ioremap()之前,请为全局变量memblock_debug指定TRUE。
以下补丁应显示有关内存冲突的重要信息 (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。

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

ADDENDUM 2

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