arm64: use fixmap region for permanent FDT mapping
authorArd Biesheuvel <ard.biesheuvel@linaro.org>
Mon, 1 Jun 2015 11:40:32 +0000 (13:40 +0200)
committerCatalin Marinas <catalin.marinas@arm.com>
Tue, 2 Jun 2015 15:31:33 +0000 (16:31 +0100)
Currently, the FDT blob needs to be in the same 512 MB region as
the kernel, so that it can be mapped into the kernel virtual memory
space very early on using a minimal set of statically allocated
translation tables.

Now that we have early fixmap support, we can relax this restriction,
by moving the permanent FDT mapping to the fixmap region instead.
This way, the FDT blob may be anywhere in memory.

This also moves the vetting of the FDT to mmu.c, since the early
init code in head.S does not handle mapping of the FDT anymore.
At the same time, fix up some comments in head.S that have gone stale.

Reviewed-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Documentation/arm64/booting.txt
arch/arm64/include/asm/boot.h [new file with mode: 0644]
arch/arm64/include/asm/fixmap.h
arch/arm64/include/asm/mmu.h
arch/arm64/kernel/head.S
arch/arm64/kernel/setup.c
arch/arm64/mm/Makefile
arch/arm64/mm/init.c
arch/arm64/mm/mmu.c

index f3c05b5f9f08df8bf3cfe669615c29767ff2f4b5..1690350f16e77beb441efee1d797576087edd612 100644 (file)
@@ -45,11 +45,13 @@ sees fit.)
 
 Requirement: MANDATORY
 
-The device tree blob (dtb) must be placed on an 8-byte boundary within
-the first 512 megabytes from the start of the kernel image and must not
-cross a 2-megabyte boundary. This is to allow the kernel to map the
-blob using a single section mapping in the initial page tables.
+The device tree blob (dtb) must be placed on an 8-byte boundary and must
+not exceed 2 megabytes in size. Since the dtb will be mapped cacheable
+using blocks of up to 2 megabytes in size, it must not be placed within
+any 2M region which must be mapped with any specific attributes.
 
+NOTE: versions prior to v4.2 also require that the DTB be placed within
+the 512 MB region starting at text_offset bytes below the kernel Image.
 
 3. Decompress the kernel image
 ------------------------------
diff --git a/arch/arm64/include/asm/boot.h b/arch/arm64/include/asm/boot.h
new file mode 100644 (file)
index 0000000..81151b6
--- /dev/null
@@ -0,0 +1,14 @@
+
+#ifndef __ASM_BOOT_H
+#define __ASM_BOOT_H
+
+#include <asm/sizes.h>
+
+/*
+ * arm64 requires the DTB to be 8 byte aligned and
+ * not exceed 2MB in size.
+ */
+#define MIN_FDT_ALIGN          8
+#define MAX_FDT_SIZE           SZ_2M
+
+#endif
index 95e6b6dcbe37ecdc191a3d37bb0c263265095f31..c0739187a920434282422ad7e6f5cb73754ae3c3 100644 (file)
@@ -17,6 +17,7 @@
 
 #ifndef __ASSEMBLY__
 #include <linux/kernel.h>
+#include <asm/boot.h>
 #include <asm/page.h>
 
 /*
  */
 enum fixed_addresses {
        FIX_HOLE,
+
+       /*
+        * Reserve a virtual window for the FDT that is 2 MB larger than the
+        * maximum supported size, and put it at the top of the fixmap region.
+        * The additional space ensures that any FDT that does not exceed
+        * MAX_FDT_SIZE can be mapped regardless of whether it crosses any
+        * 2 MB alignment boundaries.
+        *
+        * Keep this at the top so it remains 2 MB aligned.
+        */
+#define FIX_FDT_SIZE           (MAX_FDT_SIZE + SZ_2M)
+       FIX_FDT_END,
+       FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1,
+
        FIX_EARLYCON_MEM_BASE,
        FIX_TEXT_POKE0,
        __end_of_permanent_fixed_addresses,
index 3d311761e3c27459de2457d035062baacae1903d..79fcfb048884401ef7a2082e9079324fe8396ceb 100644 (file)
@@ -34,5 +34,6 @@ extern void init_mem_pgprot(void);
 extern void create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys,
                               unsigned long virt, phys_addr_t size,
                               pgprot_t prot);
+extern void *fixmap_remap_fdt(phys_addr_t dt_phys);
 
 #endif
index 19f915e8f6e0f001ce061b9eaf22cdedb9506c08..30cffc5e74020f4d59c9c3e03c26785329674329 100644 (file)
@@ -237,8 +237,6 @@ ENTRY(stext)
        bl      el2_setup                       // Drop to EL1, w20=cpu_boot_mode
        adrp    x24, __PHYS_OFFSET
        bl      set_cpu_boot_mode_flag
-
-       bl      __vet_fdt
        bl      __create_page_tables            // x25=TTBR0, x26=TTBR1
        /*
         * The following calls CPU setup code, see arch/arm64/mm/proc.S for
@@ -269,24 +267,6 @@ preserve_boot_args:
        b       __inval_cache_range             // tail call
 ENDPROC(preserve_boot_args)
 
-/*
- * Determine validity of the x21 FDT pointer.
- * The dtb must be 8-byte aligned and live in the first 512M of memory.
- */
-__vet_fdt:
-       tst     x21, #0x7
-       b.ne    1f
-       cmp     x21, x24
-       b.lt    1f
-       mov     x0, #(1 << 29)
-       add     x0, x0, x24
-       cmp     x21, x0
-       b.ge    1f
-       ret
-1:
-       mov     x21, #0
-       ret
-ENDPROC(__vet_fdt)
 /*
  * Macro to create a table entry to the next page.
  *
@@ -348,8 +328,7 @@ ENDPROC(__vet_fdt)
  * required to get the kernel running. The following sections are required:
  *   - identity mapping to enable the MMU (low address, TTBR0)
  *   - first few MB of the kernel linear mapping to jump to once the MMU has
- *     been enabled, including the FDT blob (TTBR1)
- *   - pgd entry for fixed mappings (TTBR1)
+ *     been enabled
  */
 __create_page_tables:
        adrp    x25, idmap_pg_dir
@@ -438,22 +417,6 @@ __create_page_tables:
        mov     x3, x24                         // phys offset
        create_block_map x0, x7, x3, x5, x6
 
-       /*
-        * Map the FDT blob (maximum 2MB; must be within 512MB of
-        * PHYS_OFFSET).
-        */
-       mov     x3, x21                         // FDT phys address
-       and     x3, x3, #~((1 << 21) - 1)       // 2MB aligned
-       mov     x6, #PAGE_OFFSET
-       sub     x5, x3, x24                     // subtract PHYS_OFFSET
-       tst     x5, #~((1 << 29) - 1)           // within 512MB?
-       csel    x21, xzr, x21, ne               // zero the FDT pointer
-       b.ne    1f
-       add     x5, x5, x6                      // __va(FDT blob)
-       add     x6, x5, #1 << 21                // 2MB for the FDT blob
-       sub     x6, x6, #1                      // inclusive range
-       create_block_map x0, x7, x3, x5, x6
-1:
        /*
         * Since the page tables have been populated with non-cacheable
         * accesses (MMU disabled), invalidate the idmap and swapper page
index 508cca1f8dce271d72b8e5d7120a9a5ff919819a..ffd3970721bf6497db2f622bcd3ed31fd3007de1 100644 (file)
@@ -105,18 +105,6 @@ static struct resource mem_res[] = {
 #define kernel_code mem_res[0]
 #define kernel_data mem_res[1]
 
-void __init early_print(const char *str, ...)
-{
-       char buf[256];
-       va_list ap;
-
-       va_start(ap, str);
-       vsnprintf(buf, sizeof(buf), str, ap);
-       va_end(ap);
-
-       printk("%s", buf);
-}
-
 /*
  * The recorded values of x0 .. x3 upon kernel entry.
  */
@@ -326,12 +314,14 @@ static void __init setup_processor(void)
 
 static void __init setup_machine_fdt(phys_addr_t dt_phys)
 {
-       if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) {
-               early_print("\n"
-                       "Error: invalid device tree blob at physical address 0x%p (virtual address 0x%p)\n"
-                       "The dtb must be 8-byte aligned and passed in the first 512MB of memory\n"
-                       "\nPlease check your bootloader.\n",
-                       dt_phys, phys_to_virt(dt_phys));
+       void *dt_virt = fixmap_remap_fdt(dt_phys);
+
+       if (!dt_virt || !early_init_dt_scan(dt_virt)) {
+               pr_crit("\n"
+                       "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
+                       "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
+                       "\nPlease check your bootloader.",
+                       &dt_phys, dt_virt);
 
                while (true)
                        cpu_relax();
@@ -374,8 +364,6 @@ void __init setup_arch(char **cmdline_p)
 {
        setup_processor();
 
-       setup_machine_fdt(__fdt_pointer);
-
        init_mm.start_code = (unsigned long) _text;
        init_mm.end_code   = (unsigned long) _etext;
        init_mm.end_data   = (unsigned long) _edata;
@@ -386,6 +374,8 @@ void __init setup_arch(char **cmdline_p)
        early_fixmap_init();
        early_ioremap_init();
 
+       setup_machine_fdt(__fdt_pointer);
+
        parse_early_param();
 
        /*
index 773d37a14039d9bb456f0896e022c4a3e1415773..9d84feb41a16601e411976aff3a2bc87a0d7dcbe 100644 (file)
@@ -4,3 +4,5 @@ obj-y                           := dma-mapping.o extable.o fault.o init.o \
                                   context.o proc.o pageattr.o
 obj-$(CONFIG_HUGETLB_PAGE)     += hugetlbpage.o
 obj-$(CONFIG_ARM64_PTDUMP)     += dump.o
+
+CFLAGS_mmu.o                   := -I$(srctree)/scripts/dtc/libfdt/
index 89a05f467ffb07470af0d668ac4160bcf79018f9..597831bdddf3db2c7d8f44e4d33fbc13bcb286cb 100644 (file)
@@ -170,7 +170,6 @@ void __init arm64_memblock_init(void)
                memblock_reserve(__virt_to_phys(initrd_start), initrd_end - initrd_start);
 #endif
 
-       early_init_fdt_reserve_self();
        early_init_fdt_scan_reserved_mem();
 
        /* 4GB maximum for 32-bit only capable devices */
index 5b8b664422d369a00dc6f6bc78b6f28ffd32da1b..82d3435bf14ffdf91fccb2e4857a1028c22750b6 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/kernel.h>
 #include <linux/errno.h>
 #include <linux/init.h>
+#include <linux/libfdt.h>
 #include <linux/mman.h>
 #include <linux/nodemask.h>
 #include <linux/memblock.h>
@@ -643,3 +644,68 @@ void __set_fixmap(enum fixed_addresses idx,
                flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
        }
 }
+
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+{
+       const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
+       pgprot_t prot = PAGE_KERNEL | PTE_RDONLY;
+       int granularity, size, offset;
+       void *dt_virt;
+
+       /*
+        * Check whether the physical FDT address is set and meets the minimum
+        * alignment requirement. Since we are relying on MIN_FDT_ALIGN to be
+        * at least 8 bytes so that we can always access the size field of the
+        * FDT header after mapping the first chunk, double check here if that
+        * is indeed the case.
+        */
+       BUILD_BUG_ON(MIN_FDT_ALIGN < 8);
+       if (!dt_phys || dt_phys % MIN_FDT_ALIGN)
+               return NULL;
+
+       /*
+        * Make sure that the FDT region can be mapped without the need to
+        * allocate additional translation table pages, so that it is safe
+        * to call create_mapping() this early.
+        *
+        * On 64k pages, the FDT will be mapped using PTEs, so we need to
+        * be in the same PMD as the rest of the fixmap.
+        * On 4k pages, we'll use section mappings for the FDT so we only
+        * have to be in the same PUD.
+        */
+       BUILD_BUG_ON(dt_virt_base % SZ_2M);
+
+       if (IS_ENABLED(CONFIG_ARM64_64K_PAGES)) {
+               BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PMD_SHIFT !=
+                            __fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT);
+
+               granularity = PAGE_SIZE;
+       } else {
+               BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PUD_SHIFT !=
+                            __fix_to_virt(FIX_BTMAP_BEGIN) >> PUD_SHIFT);
+
+               granularity = PMD_SIZE;
+       }
+
+       offset = dt_phys % granularity;
+       dt_virt = (void *)dt_virt_base + offset;
+
+       /* map the first chunk so we can read the size from the header */
+       create_mapping(round_down(dt_phys, granularity), dt_virt_base,
+                      granularity, prot);
+
+       if (fdt_check_header(dt_virt) != 0)
+               return NULL;
+
+       size = fdt_totalsize(dt_virt);
+       if (size > MAX_FDT_SIZE)
+               return NULL;
+
+       if (offset + size > granularity)
+               create_mapping(round_down(dt_phys, granularity), dt_virt_base,
+                              round_up(offset + size, granularity), prot);
+
+       memblock_reserve(dt_phys, size);
+
+       return dt_virt;
+}