mvebu_mem_map[] deliberately leaves the ATF and TEE region unmapped:

        {
                /* RAM 0-64MB */
                .phys = 0x0UL,
                .size = ATF_REGION_START,
                ...
        },
        /* ATF and TEE region 0x4000000-0x5400000 not mapped */
        {
                /* RAM 66MB-2GB */
                .phys = ATF_REGION_END,
                ...
        },

Nothing tells LMB about the hole, so the whole of DRAM bank 0 is added
as available memory (sizes elided from the dumps below for width):

 lmb_dump_all:
  memory.count = 0x2
  memory[0] [0x0-0xbfffffff], flags: none
  memory[1] [0x100000000-0x43fffffff], flags: none
  reserved.count = 0x4
  reserved[0] [0x4000000-0x41fffff], flags: no-map
  reserved[1] [0x7eb1d000-0x7eb1ffff], flags: no-notify, no-overwrite
  reserved[2] [0x7eb20f40-0xbfffffff], flags: no-overwrite
  reserved[3] [0x100000000-0x43fffffff], flags: no-overwrite

The only part of the hole that is reserved is the 2 MiB psci-area from
armada-ap80x.dtsi.  The remaining 18 MiB is allocatable, and since
lmb_add() notifies the EFI allocator, it is also published to EFI
payloads as EFI_CONVENTIONAL_MEMORY.  Any access to it faults:

  => md 0x4200000 4
  "Synchronous Abort" handler, esr 0x96000006, far 0x4200000
  Resetting CPU ...

ESR 0x96000006 is a data abort at the current exception level with
DFSC 0x06, a level 2 translation fault - there is no mapping, which is
exactly what mvebu_mem_map[] intends.  The same abort with WnR set
(esr 0x96000046) is what MemTest86 v11 for ARM64 hits on its first
write, because the EFI memory map it is handed says:

  0x000004000000 - 0x0000041FFFFF (2MB)  {Reserved Memory}
  0x000004200000 - 0x000006FFFFFF (46MB) {Free Memory}

Add an lmb_arch_add_memory() for Armada 8k that adds each DRAM bank
around the ATF and TEE region instead of over it, and select
LMB_ARCH_MEM_MAP for ARMADA_8K.  The hook replaces the generic
lmb_add_memory() rather than extending it, so it also has to reproduce
the LMB_LIMIT_DMA_BELOW_RAM_TOP reservation above gd->ram_top; both are
needed on this SoC, which maps only the first 2 GiB of DRAM.

After the change:

 lmb_dump_all:
  memory.count = 0x3
  memory[0] [0x0-0x3ffffff], flags: none
  memory[1] [0x5400000-0xbfffffff], flags: none
  memory[2] [0x100000000-0x43fffffff], flags: none
  reserved.count = 0x3
  reserved[0] [0x7eb1d000-0x7eb1ffff], flags: no-notify, no-overwrite
  reserved[1] [0x7eb20f40-0xbfffffff], flags: no-overwrite
  reserved[2] [0x100000000-0x43fffffff], flags: no-overwrite

  => md 0x4200000 4
  "Synchronous Abort" handler, esr 0x96000006, far 0x4200000
  => md 0x5400000 4
  05400000: 00000000 ffffffff 00000000 ffdfffff  ................

and MemTest86 completes passes instead of aborting.

Note the psci-area reservation disappears from the dump: the region is
no longer part of the LMB memory map, so boot_fdt_reserve_region() gets
-EINVAL back from lmb_alloc_mem() and skips it.  That is harmless - the
memory is not allocatable either way - but it is a visible change in
"bdinfo" output.

board_get_usable_ram_top() already clamps gd->ram_top to the 2 GiB that
mvebu_mem_map[] maps, for the same underlying reason.  A ram_top limit
cannot express a hole in the middle of a bank, hence this patch.

Tested on a SolidRun MACCHIATObin (Armada 8040) with 16 GiB of DRAM.

Signed-off-by: Bruno Banelli <[email protected]>
---
 arch/arm/mach-mvebu/armada8k/cpu.c | 89 ++++++++++++++++++++++++++++++
 lib/Kconfig                        |  2 +-
 2 files changed, 90 insertions(+), 1 deletion(-)

diff --git a/arch/arm/mach-mvebu/armada8k/cpu.c 
b/arch/arm/mach-mvebu/armada8k/cpu.c
index 220b32dd02..666be973a7 100644
--- a/arch/arm/mach-mvebu/armada8k/cpu.c
+++ b/arch/arm/mach-mvebu/armada8k/cpu.c
@@ -6,8 +6,11 @@
 #include <cpu_func.h>
 #include <dm.h>
 #include <fdtdec.h>
+#include <lmb.h>
+#include <log.h>
 #include <linux/libfdt.h>
 #include <linux/sizes.h>
+#include <asm/global_data.h>
 #include <asm/io.h>
 #include <asm/system.h>
 #include <asm/arch/cpu.h>
@@ -17,6 +20,8 @@
 
 #include "soc_info.h"
 
+DECLARE_GLOBAL_DATA_PTR;
+
 /* Armada 7k/8k */
 #define MVEBU_RFU_BASE                 (MVEBU_REGISTER(0x6f0000))
 #define RFU_GLOBAL_SW_RST              (MVEBU_RFU_BASE + 0x84)
@@ -61,6 +66,90 @@ static struct mm_region mvebu_mem_map[] = {
 
 struct mm_region *mem_map = mvebu_mem_map;
 
+#if CONFIG_IS_ENABLED(LMB_ARCH_MEM_MAP)
+/**
+ * mvebu_lmb_reserve() - mark a region as present but not allocatable
+ * @base:      start of the region
+ * @size:      size of the region
+ */
+static void mvebu_lmb_reserve(phys_addr_t base, phys_size_t size)
+{
+       phys_addr_t addr = base;
+
+       if (lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &addr, size, LMB_NOOVERWRITE))
+               log_err("Failed to reserve 0x%llx bytes at 0x%llx\n",
+                       (unsigned long long)size, (unsigned long long)base);
+}
+
+/**
+ * mvebu_lmb_add_bank() - add one memory range, honouring ram_top
+ * @base:      start of the range
+ * @size:      size of the range
+ * @ram_top:   highest address U-Boot may allocate from
+ *
+ * The reservation above @ram_top mirrors the generic lmb_add_memory(), which
+ * this hook replaces.
+ */
+static void mvebu_lmb_add_bank(phys_addr_t base, phys_size_t size, u64 ram_top)
+{
+       phys_addr_t bank_end = base + size;
+
+       lmb_add(base, size);
+
+       if (!IS_ENABLED(CONFIG_LMB_LIMIT_DMA_BELOW_RAM_TOP))
+               return;
+
+       if (base >= ram_top)
+               mvebu_lmb_reserve(base, size);
+       else if (bank_end > ram_top)
+               mvebu_lmb_reserve(ram_top, bank_end - ram_top);
+}
+
+/**
+ * lmb_arch_add_memory() - add DRAM to LMB, minus the ATF and TEE region
+ *
+ * mvebu_mem_map[] above deliberately has no entry for ATF_REGION_START to
+ * ATF_REGION_END, so U-Boot has no translation for that range and any access
+ * to it takes a translation fault. It must not be handed to LMB either:
+ * everything LMB holds as available is published to EFI payloads as
+ * EFI_CONVENTIONAL_MEMORY, and the first payload to use it aborts.
+ */
+void lmb_arch_add_memory(void)
+{
+       phys_addr_t bank_start, bank_end;
+       u64 ram_top = gd->ram_top;
+       int i;
+
+       /* Assume a 4GB ram_top if not defined */
+       if (!ram_top)
+               ram_top = 0x100000000ULL;
+
+       for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
+               if (!gd->dram[i].size)
+                       continue;
+
+               bank_start = gd->dram[i].start;
+               bank_end = bank_start + gd->dram[i].size;
+
+               if (bank_end <= ATF_REGION_START ||
+                   bank_start >= ATF_REGION_END) {
+                       mvebu_lmb_add_bank(bank_start, gd->dram[i].size,
+                                          ram_top);
+                       continue;
+               }
+
+               if (bank_start < ATF_REGION_START)
+                       mvebu_lmb_add_bank(bank_start,
+                                          ATF_REGION_START - bank_start,
+                                          ram_top);
+               if (bank_end > ATF_REGION_END)
+                       mvebu_lmb_add_bank(ATF_REGION_END,
+                                          bank_end - ATF_REGION_END,
+                                          ram_top);
+       }
+}
+#endif
+
 void enable_caches(void)
 {
        icache_enable();
diff --git a/lib/Kconfig b/lib/Kconfig
index 24e55ade4d..6b6b90bc91 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -1295,7 +1295,7 @@ config SPL_LMB
 config LMB_ARCH_MEM_MAP
        bool
        depends on LMB
-       default y if FSL_LAYERSCAPE || X86
+       default y if ARMADA_8K || FSL_LAYERSCAPE || X86
        help
          Some architectures have special or unique aspects which need
          consideration when adding memory ranges to the list of available
-- 
2.43.0

Reply via email to