Skip to contentSkip to conversation

arm64: update/clarify/relax Image and FDT placement rules

PATCH Superseded Ard Biesheuvel 2 participants 29 messages Apr 10, 2015 – Apr 14, 2015

Subsystems (6)

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore
#0 lore raw

v3 cover arm64: update/clarify/relax Image and FDT placement rules

This series came about after Mark Rutland brought up the fact that the current FDT placement logic used by the EFI stub is flawed. But actually, it turned out that the documentation for both the Image and FDT placement was incorrect as well, or confusing at the very least.

Changes since v2:
This is a complete overhaul of the previous version. The FDT changes are mostly equivalent, but have been reimplemented in a way that does not rely on the linear mapping to have been initialized yet. This includes changes to the fixmap code itself to not rely on that either. Combined with the ID map reduction in patch #1, this paves the way for relaxing the Image placement requirements as well, i.e., the kernel Image can now be placed anywhere in memory without affecting the accessibility of memory below it, or causing the resulting mapping to be less efficient due to physical and virtual memory to not be relatively aligned.

Changes since v1:
- patch #1: split off reservation of the FDT binary itself from the memreserve processing, since the former assumes the FDT is accessed via the linear mapping, which we are about to change
- patch #2: mention the older, stricter FDT placement rules in booting.txt, get rid of early_print,
use correct format specifier for phys_addr_t,
use R/O mapping for FDT,
- patches #3 .. #5: add R-b, minor style and grammar tweaks

Ard Biesheuvel (11):
arm64: reduce ID map to a single page
arm64: drop sleep_idmap_phys and clean up cpu_resume() of/fdt: split off FDT self reservation from memreserve processing
arm64: use fixmap region for permanent FDT mapping arm64/efi: adapt to relaxed FDT placement requirements
arm64: implement our own early_init_dt_add_memory_arch()
arm64: fixmap: allow init before linear mapping is set up
arm64: mm: explicitly bootstrap the linear mapping
arm64: move kernel mapping out of linear region
arm64: allow kernel Image to be loaded anywhere in physical memory arm64/efi: adapt to relaxed kernel Image placement requirements

 Documentation/arm64/booting.txt         |  25 ++-
 arch/arm/mm/init.c                      |   1 +
 arch/arm64/include/asm/compiler.h       |   2 +
 arch/arm64/include/asm/efi.h            |   8 +-
 arch/arm64/include/asm/fixmap.h         |   9 ++
 arch/arm64/include/asm/memory.h         |  15 +-
 arch/arm64/include/asm/mmu.h            |   1 +
 arch/arm64/kernel/Makefile              |   1 +
 arch/arm64/kernel/efi-stub.c            |   4 +-
 arch/arm64/kernel/head.S                |  60 ++-----
 arch/arm64/kernel/setup.c               |  45 +++---
 arch/arm64/kernel/sleep.S               |   9 +-
 arch/arm64/kernel/suspend.c             |   3 -
 arch/arm64/kernel/vmlinux.lds.S         |  35 ++++-
 arch/arm64/mm/init.c                    |  35 +++++
 arch/arm64/mm/mmu.c                     | 271 ++++++++++++++++++++++++--------
 arch/arm64/mm/proc.S                    |   3 +-
 arch/powerpc/kernel/prom.c              |   1 +
 drivers/firmware/efi/libstub/arm-stub.c |   5 +-
 drivers/firmware/efi/libstub/efistub.h  |   1 -
 drivers/firmware/efi/libstub/fdt.c      |  23 +--
 drivers/of/fdt.c                        |  19 ++-
 include/linux/of_fdt.h                  |   2 +
 23 files changed, 380 insertions(+), 198 deletions(-)
Signature · 1 line
1.8.3.2
#1 lore raw

PATCH v3 1/11 arm64: reduce ID map to a single page

Commit ea8c2e112445 ("arm64: Extend the idmap to the whole kernel image") changed the early page table code so that the entire kernel Image is covered by the identity map. This allows functions that need to enable or disable the MMU to reside anywhere in the kernel Image.

However, this change has the unfortunate side effect that the Image cannot cross a physical 512 MB alignment boundary anymore, since the early page table code cannot deal with the Image crossing a /virtual/ 512 MB alignment boundary.

So instead, reduce the ID map to a single page, that is populated by the contents of the .idmap.text section. Only three functions reside there at the moment: __enable_mmu(), cpu_resume_mmu() and cpu_reset(). If new code is introduced that needs to manipulate the MMU state, it should be added to this section as well.

---
 arch/arm64/kernel/head.S        | 13 +++++++------
 arch/arm64/kernel/sleep.S       |  2 ++
 arch/arm64/kernel/vmlinux.lds.S | 11 ++++++++++-
 arch/arm64/mm/proc.S            |  3 ++-
 4 files changed, 21 insertions(+), 8 deletions(-)
arch/arm64/kernel/head.S +7 -6
index 19f915e8f6e0..f54125a95a6d 100644
@@ -382,7 +382,7 @@ __create_page_tables:
 	 * Create the identity mapping.
 	 */
 	mov	x0, x25				// idmap_pg_dir
-	adrp	x3, KERNEL_START		// __pa(KERNEL_START)
+	adrp	x3, __idmap_text_start		// __pa(__idmap_text_start)
 
 #ifndef CONFIG_ARM64_VA_BITS_48
 #define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)
@@ -405,11 +405,11 @@ __create_page_tables:
 
 	/*
 	 * Calculate the maximum allowed value for TCR_EL1.T0SZ so that the
-	 * entire kernel image can be ID mapped. As T0SZ == (64 - #bits used),
+	 * entire ID map region can be mapped. As T0SZ == (64 - #bits used),
 	 * this number conveniently equals the number of leading zeroes in
-	 * the physical address of KERNEL_END.
+	 * the physical address of __idmap_text_end.
 	 */
-	adrp	x5, KERNEL_END
+	adrp	x5, __idmap_text_end
 	clz	x5, x5
 	cmp	x5, TCR_T0SZ(VA_BITS)	// default T0SZ small enough?
 	b.ge	1f			// .. then skip additional level
@@ -424,8 +424,8 @@ __create_page_tables:
 #endif
 
 	create_pgd_entry x0, x3, x5, x6
-	mov	x5, x3				// __pa(KERNEL_START)
-	adr_l	x6, KERNEL_END			// __pa(KERNEL_END)
+	mov	x5, x3				// __pa(__idmap_text_start)
+	adr_l	x6, __idmap_text_end		// __pa(__idmap_text_end)
 	create_block_map x0, x7, x3, x5, x6
 
 	/*
@@ -669,6 +669,7 @@ ENDPROC(__secondary_switched)
  *
  * other registers depend on the function called upon completion
  */
+	.section	".idmap.text", #alloc, #execinstr
 __enable_mmu:
 	ldr	x5, =vectors
 	msr	vbar_el1, x5
arch/arm64/kernel/sleep.S +2 -0
index ede186cdd452..04dc9aa2831e 100644
@@ -130,12 +130,14 @@ ENDPROC(__cpu_suspend_enter)
 /*
  * x0 must contain the sctlr value retrieved from restored context
  */
+	.pushsection	".idmap.text", #alloc, #execinstr
 ENTRY(cpu_resume_mmu)
 	ldr	x3, =cpu_resume_after_mmu
 	msr	sctlr_el1, x0		// restore sctlr_el1
 	isb
 	br	x3			// global jump to virtual address
 ENDPROC(cpu_resume_mmu)
+	.popsection
 cpu_resume_after_mmu:
 	mov	x0, #0			// return zero on success
 	ldp	x19, x20, [sp, #16]
arch/arm64/kernel/vmlinux.lds.S +10 -1
index a2c29865c3fe..98073332e2d0 100644
@@ -38,6 +38,12 @@ jiffies = jiffies_64;
 	*(.hyp.text)					\
 	VMLINUX_SYMBOL(__hyp_text_end) = .;
 
+#define IDMAP_TEXT					\
+	. = ALIGN(SZ_4K);				\
+	VMLINUX_SYMBOL(__idmap_text_start) = .;		\
+	*(.idmap.text)					\
+	VMLINUX_SYMBOL(__idmap_text_end) = .;
+
 /*
  * The size of the PE/COFF section that covers the kernel image, which
  * runs from stext to _edata, must be a round multiple of the PE/COFF
@@ -95,6 +101,7 @@ SECTIONS
 			SCHED_TEXT
 			LOCK_TEXT
 			HYPERVISOR_TEXT
+			IDMAP_TEXT
 			*(.fixup)
 			*(.gnu.warning)
 		. = ALIGN(16);
@@ -167,11 +174,13 @@ SECTIONS
 }
 
 /*
- * The HYP init code can't be more than a page long,
+ * The HYP init code and ID map text can't be longer than a page each,
  * and should not cross a page boundary.
  */
 ASSERT(__hyp_idmap_text_end - (__hyp_idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
 	"HYP init code too big or misaligned")
+ASSERT(__idmap_text_end - (__idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
+	"ID map text too big or misaligned")
 
 /*
  * If padding is applied before .head.text, virt<->phys conversions will fail.
arch/arm64/mm/proc.S +2 -1
index cdd754e19b9b..09da618906a3 100644
@@ -67,7 +67,7 @@ ENDPROC(cpu_cache_off)
  *
  *	- loc   - location to jump to for soft reset
  */
-	.align	5
+	.pushsection	".idmap.text", #alloc, #execinstr
 ENTRY(cpu_reset)
 	mrs	x1, sctlr_el1
 	bic	x1, x1, #1
@@ -75,6 +75,7 @@ ENTRY(cpu_reset)
 	isb
 	ret	x0
 ENDPROC(cpu_reset)
+	.popsection
 
 ENTRY(cpu_soft_restart)
 	/* Save address of cpu_reset() and reset address */
Signature · 1 line
1.8.3.2
reviewer
#2 lore raw

PATCH v3 1/11 arm64: reduce ID map to a single page

Hi Ard,

Quoted 6 lines · Ard Biesheuvel

Commit ea8c2e112445 ("arm64: Extend the idmap to the whole kernel image") changed the early page table code so that the entire kernel Image is covered by the identity map. This allows functions that need to enable or disable the MMU to reside anywhere in the kernel Image.

Quoted 8 lines

However, this change has the unfortunate side effect that the Image cannot cross a physical 512 MB alignment boundary anymore, since the early page table code cannot deal with the Image crossing a /virtual/ 512 MB alignment boundary.

So instead, reduce the ID map to a single page, that is populated by the contents of the .idmap.text section. Only three functions reside there at the moment: __enable_mmu(), cpu_resume_mmu() and cpu_reset().

It would be worth mentioning in the cover letter which branch this is based on (arm64 for-next/core?), given the __enable_mmu + __turn_mmu_on folding isn't in mainline yet.

Quoted 4 lines

If new code is introduced that needs to manipulate the MMU state, it should be added to this section as well.

Other than the minor nit below this looks good to me, and I've given it a spin on Juno with 4K and 64K pages. Assuming you can fix that up:

arch/arm64/kernel/head.S ENDPROC(__secondary_switched) · line 672

Quoted 5 lines
arch/arm64/kernel/head.S +1 -0
@@ -669,6 +669,7 @@ ENDPROC(__secondary_switched)
  *
  * other registers depend on the function called upon completion
  */
+	.section	".idmap.text", #alloc, #execinstr

We should use "ax" rather than #alloc, #execinstr to keep things consistent, unless there's some difference that I'm missing?

I've tested the patch locally with all instances changed to "ax".

Thanks,
Mark.

#3 lore raw

PATCH v3 1/11 arm64: reduce ID map to a single page

Quoted 14 lines · Mark Rutland

Hi Ard,

Quoted 11 lines · Ard Biesheuvel

Commit ea8c2e112445 ("arm64: Extend the idmap to the whole kernel image") changed the early page table code so that the entire kernel Image is covered by the identity map. This allows functions that need to enable or disable the MMU to reside anywhere in the kernel Image.

However, this change has the unfortunate side effect that the Image cannot cross a physical 512 MB alignment boundary anymore, since the early page table code cannot deal with the Image crossing a /virtual/ 512 MB alignment boundary.

Quoted 8 lines
Quoted 3 lines

So instead, reduce the ID map to a single page, that is populated by the contents of the .idmap.text section. Only three functions reside there at the moment: __enable_mmu(), cpu_resume_mmu() and cpu_reset().

It would be worth mentioning in the cover letter which branch this is based on (arm64 for-next/core?), given the __enable_mmu + __turn_mmu_on folding isn't in mainline yet.

Ah yes. That just feels so long ago already :-)

Quoted 11 lines
Quoted 4 lines

If new code is introduced that needs to manipulate the MMU state, it should be added to this section as well.

Other than the minor nit below this looks good to me, and I've given it a spin on Juno with 4K and 64K pages. Assuming you can fix that up:

Thanks!

Quoted 11 lines

arch/arm64/kernel/head.S ENDPROC(__secondary_switched) · line 672

Quoted 5 lines
arch/arm64/kernel/head.S +1 -0
@@ -669,6 +669,7 @@ ENDPROC(__secondary_switched)
  *
  * other registers depend on the function called upon completion
  */
+     .section        ".idmap.text", #alloc, #execinstr

We should use "ax" rather than #alloc, #execinstr to keep things consistent, unless there's some difference that I'm missing?

I've tested the patch locally with all instances changed to "ax".

OK, I will change it. I have a personal preference for wordy when it comes to things like this (try googling for ax), but as far as I know, they are 100% equivalent.

Signature · 1 line
Ard.
#4 lore raw

PATCH v3 2/11 arm64: drop sleep_idmap_phys and clean up cpu_resume()

In reply to Ard Biesheuvel

Two cleanups of the asm function cpu_resume():
- The global variable sleep_idmap_phys always points to idmap_pg_dir, so we can just use that value directly in the CPU resume path.
- Unclutter the load of sleep_save_sp::save_ptr_stash_phys.

---
 arch/arm64/kernel/sleep.S   | 7 ++-----
 arch/arm64/kernel/suspend.c | 3 ---
 2 files changed, 2 insertions(+), 8 deletions(-)
arch/arm64/kernel/sleep.S +2 -5
index 04dc9aa2831e..3c98fe1c1c2f 100644
@@ -164,15 +164,12 @@ ENTRY(cpu_resume)
 #else
 	mov	x7, xzr
 #endif
-	adrp	x0, sleep_save_sp
-	add	x0, x0, #:lo12:sleep_save_sp
-	ldr	x0, [x0, #SLEEP_SAVE_SP_PHYS]
+	ldr_l	x0, sleep_save_sp + SLEEP_SAVE_SP_PHYS
 	ldr	x0, [x0, x7, lsl #3]
 	/* load sp from context */
 	ldr	x2, [x0, #CPU_CTX_SP]
-	adrp	x1, sleep_idmap_phys
 	/* load physical address of identity map page table in x1 */
-	ldr	x1, [x1, #:lo12:sleep_idmap_phys]
+	adrp	x1, idmap_pg_dir
 	mov	sp, x2
 	/*
 	 * cpu_do_resume expects x0 to contain context physical address
arch/arm64/kernel/suspend.c +0 -3
index d7daf45ae7a2..f6073c27d65f 100644
@@ -118,7 +118,6 @@ int __cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
 }
 
 struct sleep_save_sp sleep_save_sp;
-phys_addr_t sleep_idmap_phys;
 
 static int __init cpu_suspend_init(void)
 {
@@ -132,9 +131,7 @@ static int __init cpu_suspend_init(void)
 
 	sleep_save_sp.save_ptr_stash = ctx_ptr;
 	sleep_save_sp.save_ptr_stash_phys = virt_to_phys(ctx_ptr);
-	sleep_idmap_phys = virt_to_phys(idmap_pg_dir);
 	__flush_dcache_area(&sleep_save_sp, sizeof(struct sleep_save_sp));
-	__flush_dcache_area(&sleep_idmap_phys, sizeof(sleep_idmap_phys));
 
 	return 0;
 }
Signature · 1 line
1.8.3.2
#5 lore raw

PATCH v3 3/11 of/fdt: split off FDT self reservation from memreserve processing

In reply to Ard Biesheuvel

This splits off the reservation of the memory occupied by the FDT binary itself from the processing of the memory reservations it contains. This is necessary because the physical address of the FDT, which is needed to perform the reservation, may not be known to the FDT driver core, i.e., it may be mapped outside the linear direct mapping, in which case __pa() returns a bogus value.

---
 arch/arm/mm/init.c         |  1 +
 arch/arm64/mm/init.c       |  1 +
 arch/powerpc/kernel/prom.c |  1 +
 drivers/of/fdt.c           | 19 ++++++++++++++-----
 include/linux/of_fdt.h     |  2 ++
 5 files changed, 19 insertions(+), 5 deletions(-)
arch/arm/mm/init.c +1 -0
index 1609b022a72f..0b8657c36fe4 100644
@@ -317,6 +317,7 @@ void __init arm_memblock_init(const struct machine_desc *mdesc)
 	if (mdesc->reserve)
 		mdesc->reserve();
 
+	early_init_fdt_reserve_self();
 	early_init_fdt_scan_reserved_mem();
 
 	/* reserve memory for DMA contiguous allocations */
arch/arm64/mm/init.c +1 -0
index ae85da6307bb..fa2389b0f7f0 100644
@@ -170,6 +170,7 @@ 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 */
arch/powerpc/kernel/prom.c +1 -0
index b8e15c678960..093ccfb384af 100644
@@ -573,6 +573,7 @@ static void __init early_reserve_mem_dt(void)
 	int len;
 	const __be32 *prop;
 
+	early_init_fdt_reserve_self();
 	early_init_fdt_scan_reserved_mem();
 
 	dt_root = of_get_flat_dt_root();
drivers/of/fdt.c +14 -5
index 3a896c9aeb74..bbb35cdb06e8 100644
@@ -561,11 +561,6 @@ void __init early_init_fdt_scan_reserved_mem(void)
 	if (!initial_boot_params)
 		return;
 
-	/* Reserve the dtb region */
-	early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
-					  fdt_totalsize(initial_boot_params),
-					  0);
-
 	/* Process header /memreserve/ fields */
 	for (n = 0; ; n++) {
 		fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
@@ -579,6 +574,20 @@ void __init early_init_fdt_scan_reserved_mem(void)
 }
 
 /**
+ * early_init_fdt_reserve_self() - reserve the memory used by the FDT blob
+ */
+void __init early_init_fdt_reserve_self(void)
+{
+	if (!initial_boot_params)
+		return;
+
+	/* Reserve the dtb region */
+	early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
+					  fdt_totalsize(initial_boot_params),
+					  0);
+}
+
+/**
  * of_scan_flat_dt - scan flattened tree blob and call callback on each.
  * @it: callback function
  * @data: context data pointer
include/linux/of_fdt.h +2 -0
index 0ff360d5b3b3..6ef6b33238d3 100644
@@ -62,6 +62,7 @@ extern int early_init_dt_scan_chosen(unsigned long node, const char *uname,
 extern int early_init_dt_scan_memory(unsigned long node, const char *uname,
 				     int depth, void *data);
 extern void early_init_fdt_scan_reserved_mem(void);
+extern void early_init_fdt_reserve_self(void);
 extern void early_init_dt_add_memory_arch(u64 base, u64 size);
 extern int early_init_dt_reserve_memory_arch(phys_addr_t base, phys_addr_t size,
 					     bool no_map);
@@ -89,6 +90,7 @@ extern u64 fdt_translate_address(const void *blob, int node_offset);
 extern void of_fdt_limit_memory(int limit);
 #else /* CONFIG_OF_FLATTREE */
 static inline void early_init_fdt_scan_reserved_mem(void) {}
+static inline void early_init_fdt_reserve_self(void) {}
 static inline const char *of_flat_dt_get_machine_name(void) { return NULL; }
 static inline void unflatten_device_tree(void) {}
 static inline void unflatten_and_copy_device_tree(void) {}
Signature · 1 line
1.8.3.2
#6 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

In reply to Ard Biesheuvel

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 setup.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.

---
 Documentation/arm64/booting.txt |  8 +++----
 arch/arm64/include/asm/fixmap.h |  9 ++++++++
 arch/arm64/include/asm/mmu.h    |  1 +
 arch/arm64/kernel/Makefile      |  1 +
 arch/arm64/kernel/head.S        | 39 +--------------------------------
 arch/arm64/kernel/setup.c       | 45 ++++++++++++++++++++------------------
 arch/arm64/mm/init.c            |  1 -
 arch/arm64/mm/mmu.c             | 48 +++++++++++++++++++++++++++++++++++++++++
 8 files changed, 88 insertions(+), 64 deletions(-)
Documentation/arm64/booting.txt +4 -4
index f3c05b5f9f08..6396460f6085 100644
@@ -45,11 +45,11 @@ 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.
 
+NOTE: versions prior to v4.2 also require that the dtb be placed within
+512 MB of the 2 MB aligned boundary right below the kernel Image.
 
 3. Decompress the kernel image
 ------------------------------
arch/arm64/include/asm/fixmap.h +9 -0
index 926495686554..fb3557c6901f 100644
@@ -32,6 +32,15 @@
  */
 enum fixed_addresses {
 	FIX_HOLE,
+
+	/*
+	 * Reserve 4 MB of virtual space for the FDT at the top of the fixmap
+	 * region. Keep this at the top so it remains 2 MB aligned.
+	 */
+#define FIX_FDT_SIZE		SZ_4M
+	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,
arch/arm64/include/asm/mmu.h +1 -0
index 3d311761e3c2..79fcfb048884 100644
@@ -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
arch/arm64/kernel/Makefile +1 -0
index d5e70747c7a2..772831cf6937 100644
@@ -6,6 +6,7 @@ CPPFLAGS_vmlinux.lds	:= -DTEXT_OFFSET=$(TEXT_OFFSET)
 AFLAGS_head.o		:= -DTEXT_OFFSET=$(TEXT_OFFSET)
 CFLAGS_efi-stub.o 	:= -DTEXT_OFFSET=$(TEXT_OFFSET)
 CFLAGS_armv8_deprecated.o := -I$(src)
+CFLAGS_setup.o		:= -I$(srctree)/scripts/dtc/libfdt/
 
 CFLAGS_REMOVE_ftrace.o = -pg
 CFLAGS_REMOVE_insn.o = -pg
arch/arm64/kernel/head.S +1 -38
index f54125a95a6d..208ca21868cc 100644
@@ -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
@@ -270,24 +268,6 @@ preserve_boot_args:
 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.
  *
  *	tbl:	page table address
@@ -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
@@ -439,22 +418,6 @@ __create_page_tables:
 	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
 	 * tables again to remove any speculatively loaded cache lines.
arch/arm64/kernel/setup.c +24 -21

80 lines not on this page · raw message

arch/arm64/mm/init.c +0 -1
index fa2389b0f7f0..ae85da6307bb 100644
@@ -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 */
arch/arm64/mm/mmu.c +48 -0

51 lines not on this page · raw message

Signature · 1 line
1.8.3.2
reviewer
#7 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Hi Ard,

Documentation/arm64/booting.txt sees fit.) · line 52

Quoted 9 lines
Documentation/arm64/booting.txt +4 -4
-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.
 
+NOTE: versions prior to v4.2 also require that the dtb be placed within
+512 MB of the 2 MB aligned boundary right below the kernel Image.

This reads a little odd (it sounds like you could load the DTB 512M below the boundary too).

How about:

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.

[...]

Quoted 14 lines

enum fixed_addresses {

arch/arm64/include/asm/fixmap.h +9 -0
 	FIX_HOLE,
+
+	/*
+	 * Reserve 4 MB of virtual space for the FDT at the top of the fixmap
+	 * region. Keep this at the top so it remains 2 MB aligned.
+	 */
+#define FIX_FDT_SIZE		SZ_4M
+	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,

[...]

arch/arm64/kernel/setup.c static void __init setup_processor(void) · line 329

Quoted 9 lines
arch/arm64/kernel/setup.c +9 -0
+	/*
+	 * Before passing the dt_virt pointer to early_init_dt_scan(), we have
+	 * to ensure that the FDT size as reported in the FDT itself does not
+	 * exceed the window we just mapped for it.
+	 */
+	if (!dt_virt ||
+	    fdt_check_header(dt_virt) != 0 ||
+	    (dt_phys & (SZ_2M - 1)) + fdt_totalsize(dt_virt) > FIX_FDT_SIZE ||
+	    !early_init_dt_scan(dt_virt)) {

Doesn't this allow a DTB > 2M (violating the boot protocol's 2M restriction)?

Perhaps we want a MAX_FDT_SIZE, and have FIX_FDT_SIZE be MAX_FDT_SIZE + 2M. Then we can check that the DTB <= MAX_FDT_SIZE, and we know we will always be able to map it.

Otherwise we could get intermittent failures for DTBs larger than 2MB, depending on where they're loaded. Always failing those for now would give us a consistent early warning that we need to bump MAX_FDT_SIZE.

[...]

arch/arm64/mm/mmu.c void __set_fixmap(enum fixed_addresses idx, · line 650

Quoted 4 lines
arch/arm64/mm/mmu.c +4 -0
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+{
+	const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
+	phys_addr_t dt_phys_base = dt_phys & ~(SZ_2M - 1);

Do we definitely retain the high bits here? I don't have the C integer promotion rules paged into my head, but the definition in include/linux/sizes.h makes me suspicious, given it should have an int type:

#define SZ_2M 0x00200000

Quoted 4 lines
arch/arm64/mm/mmu.c +4 -0
+	u64 pfn = dt_phys_base >> PAGE_SHIFT;
+	u64 pfn_end = pfn + (FIX_FDT_SIZE >> PAGE_SHIFT);
+	pgprot_t prot = PAGE_KERNEL | PTE_RDONLY;
+

arch/arm64/mm/mmu.c void __set_fixmap(enum fixed_addresses idx, · line 664

Quoted 10 lines
arch/arm64/mm/mmu.c +10 -0
+	/*
+	 * Make sure that the FDT region can be mapped without the need to
+	 * allocate additional translation table pages, or perform physical
+	 * to virtual address translations via the linear mapping.
+	 *
+	 * On 4k pages, we'll use section mappings for the region so we
+	 * only have to be in the same PUD as the rest of the fixmap.
+	 * On 64k pages, we need to be in the same PMD as well, as the region
+	 * will be mapped using PTEs.
+	 */

It would be nice to have the last two statements swapped (with appropriate wording fixups) to match the order of the if branches.

Otherwise this looks good to me.

Mark.

Quoted 32 lines
arch/arm64/mm/mmu.c +29 -0
+	BUILD_BUG_ON(dt_virt_base & (SZ_2M - 1));
+
+	if (IS_ENABLED(CONFIG_ARM64_64K_PAGES)) {
+		pte_t *pte;
+
+		BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PMD_SHIFT !=
+			     __fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT);
+
+		/* map the FDT using 64k pages */
+		pte = fixmap_pte(dt_virt_base);
+		do {
+			set_pte(pte++, pfn_pte(pfn++, prot));
+		} while (pfn < pfn_end);
+	} else {
+		pmd_t *pmd;
+
+		BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PUD_SHIFT !=
+			     __fix_to_virt(FIX_BTMAP_BEGIN) >> PUD_SHIFT);
+
+		/* map the FDT using 2 MB sections */
+		pmd = fixmap_pmd(dt_virt_base);
+		do {
+			set_pmd(pmd++, pfn_pmd(pfn, mk_sect_prot(prot)));
+			pfn += PMD_SIZE >> PAGE_SHIFT;
+		} while (pfn < pfn_end);
+	}
+
+	return (void *)(dt_virt_base + dt_phys - dt_phys_base);
+}
Signature · 1 line
1.8.3.2
#8 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Quoted 15 lines · Mark Rutland

Hi Ard,

Documentation/arm64/booting.txt sees fit.) · line 52

Quoted 9 lines
Documentation/arm64/booting.txt +4 -4
-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.
 
+NOTE: versions prior to v4.2 also require that the dtb be placed within
+512 MB of the 2 MB aligned boundary right below the kernel Image.

This reads a little odd (it sounds like you could load the DTB 512M below the boundary too).

Quoted 5 lines

How about:

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.

OK

Quoted 29 lines

[...]

Quoted 14 lines

enum fixed_addresses {
FIX_HOLE,

arch/arm64/include/asm/fixmap.h +9 -0
+
+     /*
+      * Reserve 4 MB of virtual space for the FDT at the top of the fixmap
+      * region. Keep this at the top so it remains 2 MB aligned.
+      */
+#define FIX_FDT_SIZE         SZ_4M
+     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,

[...]

arch/arm64/kernel/setup.c static void __init setup_processor(void) · line 329

Quoted 9 lines
arch/arm64/kernel/setup.c +9 -0
+     /*
+      * Before passing the dt_virt pointer to early_init_dt_scan(), we have
+      * to ensure that the FDT size as reported in the FDT itself does not
+      * exceed the window we just mapped for it.
+      */
+     if (!dt_virt ||
+         fdt_check_header(dt_virt) != 0 ||
+         (dt_phys & (SZ_2M - 1)) + fdt_totalsize(dt_virt) > FIX_FDT_SIZE ||
+         !early_init_dt_scan(dt_virt)) {
Quoted 3 lines

Doesn't this allow a DTB > 2M (violating the boot protocol's 2M restriction)?

Yes it does, and I was wondering what to do about it. We could be pedantic, and fail here with no user visible feedback about what went wrong, or we could be lax here, and add a warning later (similar to the x1/x2/x3 check) that the boot protocol was violated if the FDT exceeds 2 MB but otherwise proceed normally.

Quoted 8 lines

Perhaps we want a MAX_FDT_SIZE, and have FIX_FDT_SIZE be MAX_FDT_SIZE + 2M. Then we can check that the DTB <= MAX_FDT_SIZE, and we know we will always be able to map it.

Otherwise we could get intermittent failures for DTBs larger than 2MB, depending on where they're loaded. Always failing those for now would give us a consistent early warning that we need to bump MAX_FDT_SIZE.

Consistent, early, but emitted before we even have a console ...

Quoted 12 lines

[...]

arch/arm64/mm/mmu.c void __set_fixmap(enum fixed_addresses idx, · line 650

Quoted 4 lines
arch/arm64/mm/mmu.c +4 -0
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+{
+     const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
+     phys_addr_t dt_phys_base = dt_phys & ~(SZ_2M - 1);

Do we definitely retain the high bits here? I don't have the C integer promotion rules paged into my head, but the definition in include/linux/sizes.h makes me suspicious, given it should have an int type:

Quoted 2 lines

#define SZ_2M 0x00200000

I will use round_down() instead

Quoted 15 lines
Quoted 4 lines
arch/arm64/mm/mmu.c +4 -0
+     u64 pfn = dt_phys_base >> PAGE_SHIFT;
+     u64 pfn_end = pfn + (FIX_FDT_SIZE >> PAGE_SHIFT);
+     pgprot_t prot = PAGE_KERNEL | PTE_RDONLY;
+

arch/arm64/mm/mmu.c void __set_fixmap(enum fixed_addresses idx, · line 664

Quoted 10 lines
arch/arm64/mm/mmu.c +10 -0
+     /*
+      * Make sure that the FDT region can be mapped without the need to
+      * allocate additional translation table pages, or perform physical
+      * to virtual address translations via the linear mapping.
+      *
+      * On 4k pages, we'll use section mappings for the region so we
+      * only have to be in the same PUD as the rest of the fixmap.
+      * On 64k pages, we need to be in the same PMD as well, as the region
+      * will be mapped using PTEs.
+      */
Quoted 3 lines

It would be nice to have the last two statements swapped (with appropriate wording fixups) to match the order of the if branches.

ok

Quoted 2 lines

Otherwise this looks good to me.

thanks.

Signature · 1 line
Ard.
Quoted 32 lines
Quoted 32 lines
arch/arm64/mm/mmu.c +29 -1
+     BUILD_BUG_ON(dt_virt_base & (SZ_2M - 1));
+
+     if (IS_ENABLED(CONFIG_ARM64_64K_PAGES)) {
+             pte_t *pte;
+
+             BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PMD_SHIFT !=
+                          __fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT);
+
+             /* map the FDT using 64k pages */
+             pte = fixmap_pte(dt_virt_base);
+             do {
+                     set_pte(pte++, pfn_pte(pfn++, prot));
+             } while (pfn < pfn_end);
+     } else {
+             pmd_t *pmd;
+
+             BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PUD_SHIFT !=
+                          __fix_to_virt(FIX_BTMAP_BEGIN) >> PUD_SHIFT);
+
+             /* map the FDT using 2 MB sections */
+             pmd = fixmap_pmd(dt_virt_base);
+             do {
+                     set_pmd(pmd++, pfn_pmd(pfn, mk_sect_prot(prot)));
+                     pfn += PMD_SIZE >> PAGE_SHIFT;
+             } while (pfn < pfn_end);
+     }
+
+     return (void *)(dt_virt_base + dt_phys - dt_phys_base);
+}
--

1.8.3.2

reviewer
#9 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Quoted 24 lines
Quoted 10 lines

arch/arm64/kernel/setup.c static void __init setup_processor(void) · line 329

Quoted 9 lines
arch/arm64/kernel/setup.c +9 -0
+     /*
+      * Before passing the dt_virt pointer to early_init_dt_scan(), we have
+      * to ensure that the FDT size as reported in the FDT itself does not
+      * exceed the window we just mapped for it.
+      */
+     if (!dt_virt ||
+         fdt_check_header(dt_virt) != 0 ||
+         (dt_phys & (SZ_2M - 1)) + fdt_totalsize(dt_virt) > FIX_FDT_SIZE ||
+         !early_init_dt_scan(dt_virt)) {
Quoted 3 lines

Doesn't this allow a DTB > 2M (violating the boot protocol's 2M restriction)?

Yes it does, and I was wondering what to do about it. We could be pedantic, and fail here with no user visible feedback about what went wrong, or we could be lax here, and add a warning later (similar to the x1/x2/x3 check) that the boot protocol was violated if the FDT exceeds 2 MB but otherwise proceed normally.

Quoted 4 lines

Perhaps we want a MAX_FDT_SIZE, and have FIX_FDT_SIZE be MAX_FDT_SIZE + 2M. Then we can check that the DTB <= MAX_FDT_SIZE, and we know we will always be able to map it.

Quoted 6 lines
Quoted 4 lines

Otherwise we could get intermittent failures for DTBs larger than 2MB, depending on where they're loaded. Always failing those for now would give us a consistent early warning that we need to bump MAX_FDT_SIZE.

Consistent, early, but emitted before we even have a console ...

When I say warning, I mean that boot will consistently fail, rather than there being a pr_warn.

People seem to wait until their platform is hilariously broken before reporting bugs...

Quoted 10 lines
Quoted 10 lines

arch/arm64/mm/mmu.c void __set_fixmap(enum fixed_addresses idx, · line 650

Quoted 4 lines
arch/arm64/mm/mmu.c +4 -0
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+{
+     const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
+     phys_addr_t dt_phys_base = dt_phys & ~(SZ_2M - 1);

Do we definitely retain the high bits here? I don't have the C integer promotion rules paged into my head, but the definition in include/linux/sizes.h makes me suspicious, given it should have an int type:

Quoted 4 lines
Quoted 2 lines

#define SZ_2M 0x00200000

I will use round_down() instead

Ok.

Mark.

#10 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Quoted 29 lines · Mark Rutland
Quoted 29 lines
Quoted 10 lines

arch/arm64/kernel/setup.c static void __init setup_processor(void) · line 329

Quoted 9 lines
arch/arm64/kernel/setup.c +9 -0
+     /*
+      * Before passing the dt_virt pointer to early_init_dt_scan(), we have
+      * to ensure that the FDT size as reported in the FDT itself does not
+      * exceed the window we just mapped for it.
+      */
+     if (!dt_virt ||
+         fdt_check_header(dt_virt) != 0 ||
+         (dt_phys & (SZ_2M - 1)) + fdt_totalsize(dt_virt) > FIX_FDT_SIZE ||
+         !early_init_dt_scan(dt_virt)) {
Quoted 3 lines

Doesn't this allow a DTB > 2M (violating the boot protocol's 2M restriction)?

Yes it does, and I was wondering what to do about it. We could be pedantic, and fail here with no user visible feedback about what went wrong, or we could be lax here, and add a warning later (similar to the x1/x2/x3 check) that the boot protocol was violated if the FDT exceeds 2 MB but otherwise proceed normally.

Quoted 8 lines

Perhaps we want a MAX_FDT_SIZE, and have FIX_FDT_SIZE be MAX_FDT_SIZE + 2M. Then we can check that the DTB <= MAX_FDT_SIZE, and we know we will always be able to map it.

Otherwise we could get intermittent failures for DTBs larger than 2MB, depending on where they're loaded. Always failing those for now would give us a consistent early warning that we need to bump MAX_FDT_SIZE.

Quoted 8 lines
Quoted 1 line

Consistent, early, but emitted before we even have a console ...

When I say warning, I mean that boot will consistently fail, rather than there being a pr_warn.

People seem to wait until their platform is hilariously broken before reporting bugs...

Yeah, that is true. At this stage, there is still room to be pedantic.

I will take your MAX_FDT_SiZE/FIX_FDT_SIZE suggestion, and check fdt_totalsize() against the former instead.

I was wondering: this code now always maps the full 4 MB, also on 64k pages, even if in the majority of cases, the actual FDT is much smaller. Do you think there is a downside to that?

Signature · 1 line
Ard.
Quoted 18 lines
Quoted 10 lines
Quoted 10 lines

arch/arm64/mm/mmu.c void __set_fixmap(enum fixed_addresses idx, · line 650

Quoted 4 lines
arch/arm64/mm/mmu.c +4 -0
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+{
+     const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
+     phys_addr_t dt_phys_base = dt_phys & ~(SZ_2M - 1);

Do we definitely retain the high bits here? I don't have the C integer promotion rules paged into my head, but the definition in include/linux/sizes.h makes me suspicious, given it should have an int type:

Quoted 4 lines
Quoted 2 lines

#define SZ_2M 0x00200000

I will use round_down() instead

Ok.

Mark.

reviewer
#11 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Quoted 3 lines

I was wondering: this code now always maps the full 4 MB, also on 64k pages, even if in the majority of cases, the actual FDT is much smaller. Do you think there is a downside to that?

The two cases I can think of as problematic are when the FDT is in the last 2M of RAM, or in a 2M region immediately before some carveout which won't use MT_NORMAL attributes.

We could solve those by only mapping however many 2M chunks are necessary, or we could keep the requirement that it fits within a naturally-aligned 2M region for now.

I don't see that this should yield significant problems otherwise.

Mark.

#12 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Quoted 4 lines · Mark Rutland
Quoted 3 lines

I was wondering: this code now always maps the full 4 MB, also on 64k pages, even if in the majority of cases, the actual FDT is much smaller. Do you think there is a downside to that?

Quoted 8 lines

The two cases I can think of as problematic are when the FDT is in the last 2M of RAM, or in a 2M region immediately before some carveout which won't use MT_NORMAL attributes.

We could solve those by only mapping however many 2M chunks are necessary, or we could keep the requirement that it fits within a naturally-aligned 2M region for now.

I strongly prefer the former, since otherwise, you always need to verify the allocation explicitly, for an fdt of any size, and it turns out the logic is easily updated to move the vetting into fixmap_remap_fdt() and check the magic and size after mapping the first chunk, and set pfn_end accordingly

Quoted 3 lines

I don't see that this should yield significant problems otherwise.

Mark.

reviewer
#13 lore raw

PATCH v3 4/11 arm64: use fixmap region for permanent FDT mapping

Quoted 16 lines · Ard Biesheuvel
Quoted 4 lines · Mark Rutland
Quoted 3 lines

I was wondering: this code now always maps the full 4 MB, also on 64k pages, even if in the majority of cases, the actual FDT is much smaller. Do you think there is a downside to that?

Quoted 8 lines

The two cases I can think of as problematic are when the FDT is in the last 2M of RAM, or in a 2M region immediately before some carveout which won't use MT_NORMAL attributes.

We could solve those by only mapping however many 2M chunks are necessary, or we could keep the requirement that it fits within a naturally-aligned 2M region for now.

Quoted 5 lines

I strongly prefer the former, since otherwise, you always need to verify the allocation explicitly, for an fdt of any size, and it turns out the logic is easily updated to move the vetting into fixmap_remap_fdt() and check the magic and size after mapping the first chunk, and set pfn_end accordingly

I'm happy with that; it also decays to the old behaviour for anything that works currently.

We should probably make it clear in the booting document that the kernel should be expected to map the DTB in 2M chunks, so people know they shouldn't share any 2M chunk with a carveout.

Mark.

#14 lore raw

PATCH v3 5/11 arm64/efi: adapt to relaxed FDT placement requirements

In reply to Ard Biesheuvel

With the relaxed FDT placement requirements in place, we can change the allocation strategy used by the stub to put the FDT image higher up in memory. At the same time, reduce the minimal alignment to 8 bytes, and impose a 2 MB size limit, as per the new requirements.

---
 arch/arm64/include/asm/efi.h            |  8 +++-----
 drivers/firmware/efi/libstub/arm-stub.c |  5 ++---
 drivers/firmware/efi/libstub/efistub.h  |  1 -
 drivers/firmware/efi/libstub/fdt.c      | 23 +++++++++++++----------
 4 files changed, 18 insertions(+), 19 deletions(-)
arch/arm64/include/asm/efi.h +3 -5
index ef572206f1c3..bd513dd663b9 100644
@@ -39,12 +39,10 @@ extern void efi_init(void);
 /* arch specific definitions used by the stub code */
 
 /*
- * AArch64 requires the DTB to be 8-byte aligned in the first 512MiB from
- * start of kernel and may not cross a 2MiB boundary. We set alignment to
- * 2MiB so we know it won't cross a 2MiB boundary.
+ * AArch64 requires the DTB to be 8-byte aligned and not exceed 2MiB in size.
  */
-#define EFI_FDT_ALIGN	SZ_2M   /* used by allocate_new_fdt_and_exit_boot() */
-#define MAX_FDT_OFFSET	SZ_512M
+#define EFI_FDT_ALIGN		8
+#define EFI_FDT_MAX_SIZE	SZ_2M
 
 #define efi_call_early(f, ...) sys_table_arg->boottime->f(__VA_ARGS__)
 
drivers/firmware/efi/libstub/arm-stub.c +2 -3
index dcae482a9a17..f54c76a4fd32 100644
@@ -269,9 +269,8 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,
 
 	new_fdt_addr = fdt_addr;
 	status = allocate_new_fdt_and_exit_boot(sys_table, handle,
-				&new_fdt_addr, dram_base + MAX_FDT_OFFSET,
-				initrd_addr, initrd_size, cmdline_ptr,
-				fdt_addr, fdt_size);
+				&new_fdt_addr, initrd_addr, initrd_size,
+				cmdline_ptr, fdt_addr, fdt_size);
 
 	/*
 	 * If all went well, we need to return the FDT address to the
drivers/firmware/efi/libstub/efistub.h +0 -1
index 47437b16b186..c8e096094ea9 100644
@@ -35,7 +35,6 @@ efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
 efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 					    void *handle,
 					    unsigned long *new_fdt_addr,
-					    unsigned long max_addr,
 					    u64 initrd_addr, u64 initrd_size,
 					    char *cmdline_ptr,
 					    unsigned long fdt_addr,
drivers/firmware/efi/libstub/fdt.c +13 -10
index 91da56c4fd54..ace5ed70a88e 100644
@@ -165,10 +165,6 @@ fdt_set_fail:
 	return EFI_LOAD_ERROR;
 }
 
-#ifndef EFI_FDT_ALIGN
-#define EFI_FDT_ALIGN EFI_PAGE_SIZE
-#endif
-
 /*
  * Allocate memory for a new FDT, then add EFI, commandline, and
  * initrd related fields to the FDT.  This routine increases the
@@ -186,7 +182,6 @@ fdt_set_fail:
 efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 					    void *handle,
 					    unsigned long *new_fdt_addr,
-					    unsigned long max_addr,
 					    u64 initrd_addr, u64 initrd_size,
 					    char *cmdline_ptr,
 					    unsigned long fdt_addr,
@@ -197,6 +192,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	unsigned long mmap_key;
 	efi_memory_desc_t *memory_map, *runtime_map;
 	unsigned long new_fdt_size;
+	void *fdt_alloc;
 	efi_status_t status;
 	int runtime_entry_count = 0;
 
@@ -221,14 +217,21 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	 * will allocate a bigger buffer if this ends up being too
 	 * small, so a rough guess is OK here.
 	 */
-	new_fdt_size = fdt_size + EFI_PAGE_SIZE;
+	new_fdt_size = fdt_size + EFI_PAGE_SIZE + EFI_FDT_ALIGN;
 	while (1) {
-		status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
-					new_fdt_addr, max_addr);
+		if (new_fdt_size > EFI_FDT_MAX_SIZE) {
+			pr_efi_err(sys_table, "FDT size exceeds EFI_FDT_MAX_SIZE.\n");
+			goto fail;
+		}
+		status = sys_table->boottime->allocate_pool(EFI_LOADER_DATA,
+							    new_fdt_size,
+							    &fdt_alloc);
 		if (status != EFI_SUCCESS) {
 			pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
 			goto fail;
 		}
+		*new_fdt_addr = round_up((unsigned long)fdt_alloc,
+					 EFI_FDT_ALIGN);
 
 		/*
 		 * Now that we have done our final memory allocation (and free)
@@ -258,7 +261,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 			 * to get new one that reflects the free/alloc we do
 			 * on the device tree buffer.
 			 */
-			efi_free(sys_table, new_fdt_size, *new_fdt_addr);
+			sys_table->boottime->free_pool(&fdt_alloc);
 			sys_table->boottime->free_pool(memory_map);
 			new_fdt_size += EFI_PAGE_SIZE;
 		} else {
@@ -316,7 +319,7 @@ fail_free_mmap:
 	sys_table->boottime->free_pool(memory_map);
 
 fail_free_new_fdt:
-	efi_free(sys_table, new_fdt_size, *new_fdt_addr);
+	sys_table->boottime->free_pool(&fdt_alloc);
 
 fail:
 	sys_table->boottime->free_pool(runtime_map);
Signature · 1 line
1.8.3.2
reviewer
#15 lore raw

PATCH v3 5/11 arm64/efi: adapt to relaxed FDT placement requirements

Quoted 20 lines · Ard Biesheuvel

With the relaxed FDT placement requirements in place, we can change the allocation strategy used by the stub to put the FDT image higher up in memory. At the same time, reduce the minimal alignment to 8 bytes, and impose a 2 MB size limit, as per the new requirements.

---
 arch/arm64/include/asm/efi.h            |  8 +++-----
 drivers/firmware/efi/libstub/arm-stub.c |  5 ++---
 drivers/firmware/efi/libstub/efistub.h  |  1 -
 drivers/firmware/efi/libstub/fdt.c      | 23 +++++++++++++----------
 4 files changed, 18 insertions(+), 19 deletions(-)
arch/arm64/include/asm/efi.h +0 -0
index ef572206f1c3..bd513dd663b9 100644
@@ -39,12 +39,10 @@ extern void efi_init(void);
 /* arch specific definitions used by the stub code */
 

arch/arm64/include/asm/efi.h extern void efi_init(void); · line 45

Quoted 10 lines
arch/arm64/include/asm/efi.h +3 -5
 /*
- * AArch64 requires the DTB to be 8-byte aligned in the first 512MiB from
- * start of kernel and may not cross a 2MiB boundary. We set alignment to
- * 2MiB so we know it won't cross a 2MiB boundary.
+ * AArch64 requires the DTB to be 8-byte aligned and not exceed 2MiB in size.
  */
-#define EFI_FDT_ALIGN	SZ_2M   /* used by allocate_new_fdt_and_exit_boot() */
-#define MAX_FDT_OFFSET	SZ_512M
+#define EFI_FDT_ALIGN		8
+#define EFI_FDT_MAX_SIZE	SZ_2M

Is there any way we can organise things so we can share the kernel's MAX_FDT_SIZE definition, so as to keep the stub and kernel in sync?

Otherwise, the patch looks good to me.

Mark.

Quoted 108 lines
arch/arm64/include/asm/efi.h +0 -0
 #define efi_call_early(f, ...) sys_table_arg->boottime->f(__VA_ARGS__)
 
drivers/firmware/efi/libstub/arm-stub.c +2 -3
index dcae482a9a17..f54c76a4fd32 100644
@@ -269,9 +269,8 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,
 
 	new_fdt_addr = fdt_addr;
 	status = allocate_new_fdt_and_exit_boot(sys_table, handle,
-				&new_fdt_addr, dram_base + MAX_FDT_OFFSET,
-				initrd_addr, initrd_size, cmdline_ptr,
-				fdt_addr, fdt_size);
+				&new_fdt_addr, initrd_addr, initrd_size,
+				cmdline_ptr, fdt_addr, fdt_size);
 
 	/*
 	 * If all went well, we need to return the FDT address to the
drivers/firmware/efi/libstub/efistub.h +0 -1
index 47437b16b186..c8e096094ea9 100644
@@ -35,7 +35,6 @@ efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
 efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 					    void *handle,
 					    unsigned long *new_fdt_addr,
-					    unsigned long max_addr,
 					    u64 initrd_addr, u64 initrd_size,
 					    char *cmdline_ptr,
 					    unsigned long fdt_addr,
drivers/firmware/efi/libstub/fdt.c +13 -10
index 91da56c4fd54..ace5ed70a88e 100644
@@ -165,10 +165,6 @@ fdt_set_fail:
 	return EFI_LOAD_ERROR;
 }
 
-#ifndef EFI_FDT_ALIGN
-#define EFI_FDT_ALIGN EFI_PAGE_SIZE
-#endif
-
 /*
  * Allocate memory for a new FDT, then add EFI, commandline, and
  * initrd related fields to the FDT.  This routine increases the
@@ -186,7 +182,6 @@ fdt_set_fail:
 efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 					    void *handle,
 					    unsigned long *new_fdt_addr,
-					    unsigned long max_addr,
 					    u64 initrd_addr, u64 initrd_size,
 					    char *cmdline_ptr,
 					    unsigned long fdt_addr,
@@ -197,6 +192,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	unsigned long mmap_key;
 	efi_memory_desc_t *memory_map, *runtime_map;
 	unsigned long new_fdt_size;
+	void *fdt_alloc;
 	efi_status_t status;
 	int runtime_entry_count = 0;
 
@@ -221,14 +217,21 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	 * will allocate a bigger buffer if this ends up being too
 	 * small, so a rough guess is OK here.
 	 */
-	new_fdt_size = fdt_size + EFI_PAGE_SIZE;
+	new_fdt_size = fdt_size + EFI_PAGE_SIZE + EFI_FDT_ALIGN;
 	while (1) {
-		status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
-					new_fdt_addr, max_addr);
+		if (new_fdt_size > EFI_FDT_MAX_SIZE) {
+			pr_efi_err(sys_table, "FDT size exceeds EFI_FDT_MAX_SIZE.\n");
+			goto fail;
+		}
+		status = sys_table->boottime->allocate_pool(EFI_LOADER_DATA,
+							    new_fdt_size,
+							    &fdt_alloc);
 		if (status != EFI_SUCCESS) {
 			pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
 			goto fail;
 		}
+		*new_fdt_addr = round_up((unsigned long)fdt_alloc,
+					 EFI_FDT_ALIGN);
 
 		/*
 		 * Now that we have done our final memory allocation (and free)
@@ -258,7 +261,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 			 * to get new one that reflects the free/alloc we do
 			 * on the device tree buffer.
 			 */
-			efi_free(sys_table, new_fdt_size, *new_fdt_addr);
+			sys_table->boottime->free_pool(&fdt_alloc);
 			sys_table->boottime->free_pool(memory_map);
 			new_fdt_size += EFI_PAGE_SIZE;
 		} else {
@@ -316,7 +319,7 @@ fail_free_mmap:
 	sys_table->boottime->free_pool(memory_map);
 
 fail_free_new_fdt:
-	efi_free(sys_table, new_fdt_size, *new_fdt_addr);
+	sys_table->boottime->free_pool(&fdt_alloc);
 
 fail:
 	sys_table->boottime->free_pool(runtime_map);
Signature · 1 line
1.8.3.2
#16 lore raw

PATCH v3 5/11 arm64/efi: adapt to relaxed FDT placement requirements

Quoted 21 lines · Mark Rutland
Quoted 20 lines · Ard Biesheuvel

With the relaxed FDT placement requirements in place, we can change the allocation strategy used by the stub to put the FDT image higher up in memory. At the same time, reduce the minimal alignment to 8 bytes, and impose a 2 MB size limit, as per the new requirements.

---
 arch/arm64/include/asm/efi.h            |  8 +++-----
 drivers/firmware/efi/libstub/arm-stub.c |  5 ++---
 drivers/firmware/efi/libstub/efistub.h  |  1 -
 drivers/firmware/efi/libstub/fdt.c      | 23 +++++++++++++----------
 4 files changed, 18 insertions(+), 19 deletions(-)
arch/arm64/include/asm/efi.h +0 -0
index ef572206f1c3..bd513dd663b9 100644
@@ -39,12 +39,10 @@ extern void efi_init(void);
 /* arch specific definitions used by the stub code */
 
Quoted 14 lines

arch/arm64/include/asm/efi.h extern void efi_init(void); · line 45

Quoted 10 lines
arch/arm64/include/asm/efi.h +3 -5
 /*
- * AArch64 requires the DTB to be 8-byte aligned in the first 512MiB from
- * start of kernel and may not cross a 2MiB boundary. We set alignment to
- * 2MiB so we know it won't cross a 2MiB boundary.
+ * AArch64 requires the DTB to be 8-byte aligned and not exceed 2MiB in size.
  */
-#define EFI_FDT_ALIGN        SZ_2M   /* used by allocate_new_fdt_and_exit_boot() */
-#define MAX_FDT_OFFSET       SZ_512M
+#define EFI_FDT_ALIGN                8
+#define EFI_FDT_MAX_SIZE     SZ_2M

Is there any way we can organise things so we can share the kernel's MAX_FDT_SIZE definition, so as to keep the stub and kernel in sync?

I'll put MAX_FDT_SIZE in asm/page.h (unless you can propose a better place) That way, both fixmap.h and efi.h can refer to it.

Quoted 112 lines

Otherwise, the patch looks good to me.

Mark.

Quoted 108 lines
arch/arm64/include/asm/efi.h +0 -0
 #define efi_call_early(f, ...) sys_table_arg->boottime->f(__VA_ARGS__)
 
drivers/firmware/efi/libstub/arm-stub.c +2 -3
index dcae482a9a17..f54c76a4fd32 100644
@@ -269,9 +269,8 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,
 
      new_fdt_addr = fdt_addr;
      status = allocate_new_fdt_and_exit_boot(sys_table, handle,
-                             &new_fdt_addr, dram_base + MAX_FDT_OFFSET,
-                             initrd_addr, initrd_size, cmdline_ptr,
-                             fdt_addr, fdt_size);
+                             &new_fdt_addr, initrd_addr, initrd_size,
+                             cmdline_ptr, fdt_addr, fdt_size);
 
      /*
       * If all went well, we need to return the FDT address to the
drivers/firmware/efi/libstub/efistub.h +0 -1
index 47437b16b186..c8e096094ea9 100644
@@ -35,7 +35,6 @@ efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
 efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
                                          void *handle,
                                          unsigned long *new_fdt_addr,
-                                         unsigned long max_addr,
                                          u64 initrd_addr, u64 initrd_size,
                                          char *cmdline_ptr,
                                          unsigned long fdt_addr,
drivers/firmware/efi/libstub/fdt.c +13 -11
index 91da56c4fd54..ace5ed70a88e 100644
@@ -165,10 +165,6 @@ fdt_set_fail:
      return EFI_LOAD_ERROR;
 }
 
-#ifndef EFI_FDT_ALIGN
-#define EFI_FDT_ALIGN EFI_PAGE_SIZE
-#endif
-
 /*
  * Allocate memory for a new FDT, then add EFI, commandline, and
  * initrd related fields to the FDT.  This routine increases the
@@ -186,7 +182,6 @@ fdt_set_fail:
 efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
                                          void *handle,
                                          unsigned long *new_fdt_addr,
-                                         unsigned long max_addr,
                                          u64 initrd_addr, u64 initrd_size,
                                          char *cmdline_ptr,
                                          unsigned long fdt_addr,
@@ -197,6 +192,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
      unsigned long mmap_key;
      efi_memory_desc_t *memory_map, *runtime_map;
      unsigned long new_fdt_size;
+     void *fdt_alloc;
      efi_status_t status;
      int runtime_entry_count = 0;
 
@@ -221,14 +217,21 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
       * will allocate a bigger buffer if this ends up being too
       * small, so a rough guess is OK here.
       */
-     new_fdt_size = fdt_size + EFI_PAGE_SIZE;
+     new_fdt_size = fdt_size + EFI_PAGE_SIZE + EFI_FDT_ALIGN;
      while (1) {
-             status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
-                                     new_fdt_addr, max_addr);
+             if (new_fdt_size > EFI_FDT_MAX_SIZE) {
+                     pr_efi_err(sys_table, "FDT size exceeds EFI_FDT_MAX_SIZE.\n");
+                     goto fail;
+             }
+             status = sys_table->boottime->allocate_pool(EFI_LOADER_DATA,
+                                                         new_fdt_size,
+                                                         &fdt_alloc);
              if (status != EFI_SUCCESS) {
                      pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
                      goto fail;
              }
+             *new_fdt_addr = round_up((unsigned long)fdt_alloc,
+                                      EFI_FDT_ALIGN);
 
              /*
               * Now that we have done our final memory allocation (and free)
@@ -258,7 +261,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
                       * to get new one that reflects the free/alloc we do
                       * on the device tree buffer.
                       */
-                     efi_free(sys_table, new_fdt_size, *new_fdt_addr);
+                     sys_table->boottime->free_pool(&fdt_alloc);
                      sys_table->boottime->free_pool(memory_map);
                      new_fdt_size += EFI_PAGE_SIZE;
              } else {
@@ -316,7 +319,7 @@ fail_free_mmap:
      sys_table->boottime->free_pool(memory_map);
 
 fail_free_new_fdt:
-     efi_free(sys_table, new_fdt_size, *new_fdt_addr);
+     sys_table->boottime->free_pool(&fdt_alloc);
 
 fail:
      sys_table->boottime->free_pool(runtime_map);
--

1.8.3.2

reviewer
#17 lore raw

PATCH v3 5/11 arm64/efi: adapt to relaxed FDT placement requirements

Quoted 37 lines · Ard Biesheuvel
Quoted 21 lines · Mark Rutland
Quoted 20 lines · Ard Biesheuvel

With the relaxed FDT placement requirements in place, we can change the allocation strategy used by the stub to put the FDT image higher up in memory. At the same time, reduce the minimal alignment to 8 bytes, and impose a 2 MB size limit, as per the new requirements.

---
 arch/arm64/include/asm/efi.h            |  8 +++-----
 drivers/firmware/efi/libstub/arm-stub.c |  5 ++---
 drivers/firmware/efi/libstub/efistub.h  |  1 -
 drivers/firmware/efi/libstub/fdt.c      | 23 +++++++++++++----------
 4 files changed, 18 insertions(+), 19 deletions(-)
arch/arm64/include/asm/efi.h +0 -0
index ef572206f1c3..bd513dd663b9 100644
@@ -39,12 +39,10 @@ extern void efi_init(void);
 /* arch specific definitions used by the stub code */
 
Quoted 14 lines

arch/arm64/include/asm/efi.h extern void efi_init(void); · line 45

Quoted 10 lines
arch/arm64/include/asm/efi.h +3 -5
 /*
- * AArch64 requires the DTB to be 8-byte aligned in the first 512MiB from
- * start of kernel and may not cross a 2MiB boundary. We set alignment to
- * 2MiB so we know it won't cross a 2MiB boundary.
+ * AArch64 requires the DTB to be 8-byte aligned and not exceed 2MiB in size.
  */
-#define EFI_FDT_ALIGN        SZ_2M   /* used by allocate_new_fdt_and_exit_boot() */
-#define MAX_FDT_OFFSET       SZ_512M
+#define EFI_FDT_ALIGN                8
+#define EFI_FDT_MAX_SIZE     SZ_2M

Is there any way we can organise things so we can share the kernel's MAX_FDT_SIZE definition, so as to keep the stub and kernel in sync?

Quoted 2 lines

I'll put MAX_FDT_SIZE in asm/page.h (unless you can propose a better place) That way, both fixmap.h and efi.h can refer to it.

Using page.h feels a little weird, but I can't see that a better header exists currently.

We could add asm/boot.h for things related to boot protocol that we want to have shareable with the stub, but I can't see anything else that we'd want to put in there at the moment. If there are other things we could deduplicate it would probably make sense, but it seems overkill for a single definition. :/

Mark.

#18 lore raw

PATCH v3 5/11 arm64/efi: adapt to relaxed FDT placement requirements

Quoted 41 lines · Mark Rutland
Quoted 37 lines · Ard Biesheuvel
Quoted 21 lines · Mark Rutland
Quoted 20 lines · Ard Biesheuvel

With the relaxed FDT placement requirements in place, we can change the allocation strategy used by the stub to put the FDT image higher up in memory. At the same time, reduce the minimal alignment to 8 bytes, and impose a 2 MB size limit, as per the new requirements.

---
 arch/arm64/include/asm/efi.h            |  8 +++-----
 drivers/firmware/efi/libstub/arm-stub.c |  5 ++---
 drivers/firmware/efi/libstub/efistub.h  |  1 -
 drivers/firmware/efi/libstub/fdt.c      | 23 +++++++++++++----------
 4 files changed, 18 insertions(+), 19 deletions(-)
arch/arm64/include/asm/efi.h +0 -0
index ef572206f1c3..bd513dd663b9 100644
@@ -39,12 +39,10 @@ extern void efi_init(void);
 /* arch specific definitions used by the stub code */
 
Quoted 14 lines

arch/arm64/include/asm/efi.h extern void efi_init(void); · line 45

Quoted 10 lines
arch/arm64/include/asm/efi.h +3 -5
 /*
- * AArch64 requires the DTB to be 8-byte aligned in the first 512MiB from
- * start of kernel and may not cross a 2MiB boundary. We set alignment to
- * 2MiB so we know it won't cross a 2MiB boundary.
+ * AArch64 requires the DTB to be 8-byte aligned and not exceed 2MiB in size.
  */
-#define EFI_FDT_ALIGN        SZ_2M   /* used by allocate_new_fdt_and_exit_boot() */
-#define MAX_FDT_OFFSET       SZ_512M
+#define EFI_FDT_ALIGN                8
+#define EFI_FDT_MAX_SIZE     SZ_2M

Is there any way we can organise things so we can share the kernel's MAX_FDT_SIZE definition, so as to keep the stub and kernel in sync?

Quoted 2 lines

I'll put MAX_FDT_SIZE in asm/page.h (unless you can propose a better place) That way, both fixmap.h and efi.h can refer to it.

Quoted 9 lines

Using page.h feels a little weird, but I can't see that a better header exists currently.

We could add asm/boot.h for things related to boot protocol that we want to have shareable with the stub, but I can't see anything else that we'd want to put in there at the moment. If there are other things we could deduplicate it would probably make sense, but it seems overkill for a single definition. :/

I was going to add '#define MIN_FDT_ALIGN 8' to it as well, and refer to it from efi.h.

I think asm/boot.h is not so bad actually.

#19 lore raw

PATCH v3 6/11 arm64: implement our own early_init_dt_add_memory_arch()

In reply to Ard Biesheuvel

Override the __weak early_init_dt_add_memory_arch() with our own version. Initially, this version just ignores MAX_PHYS_ADDR, but later on, we will modify it so it can deal with PHYS_OFFSET not having been assigned yet.

---
 arch/arm64/mm/init.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
arch/arm64/mm/init.c +29 -0
index ae85da6307bb..48175b769074 100644
@@ -372,3 +372,32 @@ static int __init keepinitrd_setup(char *__unused)
 
 __setup("keepinitrd", keepinitrd_setup);
 #endif
+
+void __init early_init_dt_add_memory_arch(u64 base, u64 size)
+{
+	const u64 phys_offset = __pa(PAGE_OFFSET);
+
+	if (!PAGE_ALIGNED(base)) {
+		if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
+			pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
+				base, base + size);
+			return;
+		}
+		size -= PAGE_SIZE - (base & ~PAGE_MASK);
+		base = PAGE_ALIGN(base);
+	}
+	size &= PAGE_MASK;
+
+	if (base + size < phys_offset) {
+		pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
+			   base, base + size);
+		return;
+	}
+	if (base < phys_offset) {
+		pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
+			   base, phys_offset);
+		size -= phys_offset - base;
+		base = phys_offset;
+	}
+	memblock_add(base, size);
+}
Signature · 1 line
1.8.3.2
#20 lore raw

PATCH v3 7/11 arm64: fixmap: allow init before linear mapping is set up

In reply to Ard Biesheuvel

This reworks early_ioremap_init() so it populates the various levels of translation tables while taking the following into account:
- be prepared for any of the levels to have been populated already, as this may be the case once we move the kernel text mapping out of the linear mapping;
- don't rely on __va() to translate the physical address in a page table entry to a virtual address, since this produces linear mapping addresses; instead, use the fact that at any level, we know exactly which page in swapper_pg_dir an entry could be pointing to if it points anywhere.

This allows us to defer the initialization of the linear mapping until after we have figured out where our RAM resides in the physical address space.

---
 arch/arm64/include/asm/compiler.h |   2 +
 arch/arm64/kernel/vmlinux.lds.S   |  14 +++--
 arch/arm64/mm/mmu.c               | 117 +++++++++++++++++++++++++-------------
 3 files changed, 90 insertions(+), 43 deletions(-)
arch/arm64/include/asm/compiler.h +2 -0
index ee35fd0f2236..dd342af63673 100644
@@ -27,4 +27,6 @@
  */
 #define __asmeq(x, y)  ".ifnc " x "," y " ; .err ; .endif\n\t"
 
+#define __pgdir		__attribute__((section(".pgdir"),aligned(PAGE_SIZE)))
+
 #endif	/* __ASM_COMPILER_H */
arch/arm64/kernel/vmlinux.lds.S +9 -5
index 98073332e2d0..604f285d3832 100644
@@ -160,11 +160,15 @@ SECTIONS
 
 	BSS_SECTION(0, 0, 0)
 
-	. = ALIGN(PAGE_SIZE);
-	idmap_pg_dir = .;
-	. += IDMAP_DIR_SIZE;
-	swapper_pg_dir = .;
-	. += SWAPPER_DIR_SIZE;
+	.pgdir (NOLOAD) : {
+		. = ALIGN(PAGE_SIZE);
+		idmap_pg_dir = .;
+		. += IDMAP_DIR_SIZE;
+		swapper_pg_dir = .;
+		__swapper_bs_region = . + PAGE_SIZE;
+		. += SWAPPER_DIR_SIZE;
+		*(.pgdir)
+	}
 
 	_end = .;
 
arch/arm64/mm/mmu.c +79 -38

143 lines not on this page · raw message

Signature · 1 line
1.8.3.2
reviewer
#21 lore raw

PATCH v3 7/11 arm64: fixmap: allow init before linear mapping is set up

Hi Ard,

Quoted 9 lines · Ard Biesheuvel

This reworks early_ioremap_init() so it populates the various levels of translation tables while taking the following into account:
- be prepared for any of the levels to have been populated already, as this may be the case once we move the kernel text mapping out of the linear mapping;
- don't rely on __va() to translate the physical address in a page table entry to a virtual address, since this produces linear mapping addresses; instead, use the fact that at any level, we know exactly which page in swapper_pg_dir an entry could be pointing to if it points anywhere.

Can we not use Catalin's PHYS_OFFSET swizzling trick instead? i.e.

* Set PHYS_OFFSET so __va hits in the text mapping.

* Create the fixmap entries.

* Parse the DTB or UEFI memory map, figure out the real PHYS_OFFSET.

* Create linear mapping for the initial tables.

* Swap PHYS_OFFSET for the real version, and update init_mm->pgd to point at the linear map alias of the swapper pgd.

It seemed like that would require less open-coding of table manipulation code, as we could use __va() early.

Is there a limitation with that approach that I'm missing?

Otherwise, comments below.

Quoted 52 lines

This allows us to defer the initialization of the linear mapping until after we have figured out where our RAM resides in the physical address space.

---
 arch/arm64/include/asm/compiler.h |   2 +
 arch/arm64/kernel/vmlinux.lds.S   |  14 +++--
 arch/arm64/mm/mmu.c               | 117 +++++++++++++++++++++++++-------------
 3 files changed, 90 insertions(+), 43 deletions(-)
arch/arm64/include/asm/compiler.h +2 -0
index ee35fd0f2236..dd342af63673 100644
@@ -27,4 +27,6 @@
  */
 #define __asmeq(x, y)  ".ifnc " x "," y " ; .err ; .endif\n\t"
 
+#define __pgdir		__attribute__((section(".pgdir"),aligned(PAGE_SIZE)))
+
 #endif	/* __ASM_COMPILER_H */
arch/arm64/kernel/vmlinux.lds.S +9 -5
index 98073332e2d0..604f285d3832 100644
@@ -160,11 +160,15 @@ SECTIONS
 
 	BSS_SECTION(0, 0, 0)
 
-	. = ALIGN(PAGE_SIZE);
-	idmap_pg_dir = .;
-	. += IDMAP_DIR_SIZE;
-	swapper_pg_dir = .;
-	. += SWAPPER_DIR_SIZE;
+	.pgdir (NOLOAD) : {
+		. = ALIGN(PAGE_SIZE);
+		idmap_pg_dir = .;
+		. += IDMAP_DIR_SIZE;
+		swapper_pg_dir = .;
+		__swapper_bs_region = . + PAGE_SIZE;
+		. += SWAPPER_DIR_SIZE;
+		*(.pgdir)
+	}
 
 	_end = .;
 
arch/arm64/mm/mmu.c +0 -0
index 60be58a160a2..c0427b5c90c7 100644

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 344

Quoted 4 lines
arch/arm64/mm/mmu.c +1 -0
@@ -341,6 +341,70 @@ static void __init __map_memblock(phys_addr_t start, phys_addr_t end)
 }
 #endif
 
+struct mem_bootstrap_region {

The "region" naming is a little confusing IMO. To me it sounds like something akin to a VMA rather than a set of tables.

Quoted 23 lines
arch/arm64/mm/mmu.c +23 -0
+#if CONFIG_ARM64_PGTABLE_LEVELS > 3
+	pud_t	pud[PTRS_PER_PUD];
+#endif
+#if CONFIG_ARM64_PGTABLE_LEVELS > 2
+	pmd_t	pmd[PTRS_PER_PMD];
+#endif
+	pte_t	pte[PTRS_PER_PTE];
+};
+
+static void __init bootstrap_mem_region(unsigned long addr,
+					struct mem_bootstrap_region *reg,
+					pmd_t **ppmd, pte_t **ppte)
+{
+	/*
+	 * Avoid using the linear phys-to-virt translation __va() so that we
+	 * can use this code before the linear mapping is set up. Note that
+	 * any populated entries at any level can only point into swapper_pg_dir
+	 * since no other translation table pages have been allocated yet.
+	 * So at each level, we either need to populate it, or it has already
+	 * been populated by a swapper_pg_dir table at the same level, in which
+	 * case we can figure out its virtual address without applying __va()
+	 * on the contents of the entry, using the following struct.
+	 */

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 377

Quoted 10 lines
arch/arm64/mm/mmu.c +10 -0
+	extern struct mem_bootstrap_region __swapper_bs_region;
+
+	pgd_t *pgd = pgd_offset_k(addr);
+	pud_t *pud = (pud_t *)pgd;
+	pmd_t *pmd = (pmd_t *)pud;
+
+#if CONFIG_ARM64_PGTABLE_LEVELS > 3
+	if (pgd_none(*pgd)) {
+		clear_page(reg->pud);
+		pgd_populate(&init_mm, pgd, reg->pud);

What's PHYS_OFFSET expected to be at this point (for the purposes of __pa() in the *_populate*() macros)?

If we're relying on __pa() to convert text->phys, won't __va() convert phys->text at this point?

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 383

Quoted 6 lines
arch/arm64/mm/mmu.c +6 -0
+		pud = reg->pud;
+	} else {
+		pud = __swapper_bs_region.pud;
+	}
+	pud += pud_index(addr);
+#endif

Can we free the unused reg tables in the else cases? If __pa() works we should be able to hand them to memblock, no?

[...]

arch/arm64/mm/mmu.c void vmemmap_free(unsigned long start, unsigned long end) · line 649

Quoted 4 lines
arch/arm64/mm/mmu.c +1 -1
 	/*
 	 * The boot-ioremap range spans multiple pmds, for which
-	 * we are not preparted:
+	 * we are not prepared:

This typo has been bugging me for ages. I'll be glad to see it go!

Mark.

#22 lore raw

PATCH v3 7/11 arm64: fixmap: allow init before linear mapping is set up

Quoted 23 lines · Mark Rutland

Hi Ard,

Quoted 9 lines · Ard Biesheuvel

This reworks early_ioremap_init() so it populates the various levels of translation tables while taking the following into account:
- be prepared for any of the levels to have been populated already, as this may be the case once we move the kernel text mapping out of the linear mapping;
- don't rely on __va() to translate the physical address in a page table entry to a virtual address, since this produces linear mapping addresses; instead, use the fact that at any level, we know exactly which page in swapper_pg_dir an entry could be pointing to if it points anywhere.

Can we not use Catalin's PHYS_OFFSET swizzling trick instead? i.e.

* Set PHYS_OFFSET so __va hits in the text mapping.

* Create the fixmap entries.

* Parse the DTB or UEFI memory map, figure out the real PHYS_OFFSET.

* Create linear mapping for the initial tables.

Quoted 8 lines

* Swap PHYS_OFFSET for the real version, and update init_mm->pgd to point at the linear map alias of the swapper pgd.

It seemed like that would require less open-coding of table manipulation code, as we could use __va() early.

Is there a limitation with that approach that I'm missing?

I didn't quite catch Catalin's suggestion to mean the above, but the way you put it seems viable to me. I'll have a go and see how far I get with it.

Quoted 57 lines

Otherwise, comments below.

Quoted 55 lines

This allows us to defer the initialization of the linear mapping until after we have figured out where our RAM resides in the physical address space.

---
 arch/arm64/include/asm/compiler.h |   2 +
 arch/arm64/kernel/vmlinux.lds.S   |  14 +++--
 arch/arm64/mm/mmu.c               | 117 +++++++++++++++++++++++++-------------
 3 files changed, 90 insertions(+), 43 deletions(-)
arch/arm64/include/asm/compiler.h +2 -0
index ee35fd0f2236..dd342af63673 100644
@@ -27,4 +27,6 @@
  */
 #define __asmeq(x, y)  ".ifnc " x "," y " ; .err ; .endif\n\t"
 
+#define __pgdir              __attribute__((section(".pgdir"),aligned(PAGE_SIZE)))
+
 #endif       /* __ASM_COMPILER_H */
arch/arm64/kernel/vmlinux.lds.S +9 -5
index 98073332e2d0..604f285d3832 100644
@@ -160,11 +160,15 @@ SECTIONS
 
      BSS_SECTION(0, 0, 0)
 
-     . = ALIGN(PAGE_SIZE);
-     idmap_pg_dir = .;
-     . += IDMAP_DIR_SIZE;
-     swapper_pg_dir = .;
-     . += SWAPPER_DIR_SIZE;
+     .pgdir (NOLOAD) : {
+             . = ALIGN(PAGE_SIZE);
+             idmap_pg_dir = .;
+             . += IDMAP_DIR_SIZE;
+             swapper_pg_dir = .;
+             __swapper_bs_region = . + PAGE_SIZE;
+             . += SWAPPER_DIR_SIZE;
+             *(.pgdir)
+     }
 
      _end = .;
 
arch/arm64/mm/mmu.c +0 -0
index 60be58a160a2..c0427b5c90c7 100644
@@ -341,6 +341,70 @@ static void __init __map_memblock(phys_addr_t start, phys_addr_t end)
 }
 #endif
 
Quoted 5 lines

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 344

Quoted 1 line
arch/arm64/mm/mmu.c +1 -0
+struct mem_bootstrap_region {

The "region" naming is a little confusing IMO. To me it sounds like something akin to a VMA rather than a set of tables.

ok

Quoted 37 lines
Quoted 23 lines
arch/arm64/mm/mmu.c +23 -0
+#if CONFIG_ARM64_PGTABLE_LEVELS > 3
+     pud_t   pud[PTRS_PER_PUD];
+#endif
+#if CONFIG_ARM64_PGTABLE_LEVELS > 2
+     pmd_t   pmd[PTRS_PER_PMD];
+#endif
+     pte_t   pte[PTRS_PER_PTE];
+};
+
+static void __init bootstrap_mem_region(unsigned long addr,
+                                     struct mem_bootstrap_region *reg,
+                                     pmd_t **ppmd, pte_t **ppte)
+{
+     /*
+      * Avoid using the linear phys-to-virt translation __va() so that we
+      * can use this code before the linear mapping is set up. Note that
+      * any populated entries at any level can only point into swapper_pg_dir
+      * since no other translation table pages have been allocated yet.
+      * So at each level, we either need to populate it, or it has already
+      * been populated by a swapper_pg_dir table at the same level, in which
+      * case we can figure out its virtual address without applying __va()
+      * on the contents of the entry, using the following struct.
+      */

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 377

Quoted 10 lines
arch/arm64/mm/mmu.c +10 -0
+     extern struct mem_bootstrap_region __swapper_bs_region;
+
+     pgd_t *pgd = pgd_offset_k(addr);
+     pud_t *pud = (pud_t *)pgd;
+     pmd_t *pmd = (pmd_t *)pud;
+
+#if CONFIG_ARM64_PGTABLE_LEVELS > 3
+     if (pgd_none(*pgd)) {
+             clear_page(reg->pud);
+             pgd_populate(&init_mm, pgd, reg->pud);

What's PHYS_OFFSET expected to be at this point (for the purposes of __pa() in the *_populate*() macros)?

Quoted 3 lines

If we're relying on __pa() to convert text->phys, won't __va() convert phys->text at this point?

At this points, yes. But later on, when the kernel text moves out of the linear region, __pa() takes into account whether the input VA is above or below PAGE_OFFSET, and adds the kernel image offset in the latter case.
Changing __va() so it implements the inverse would be a can of worms i'd rather keep closed.

Quoted 10 lines

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 383

Quoted 6 lines
arch/arm64/mm/mmu.c +6 -0
+             pud = reg->pud;
+     } else {
+             pud = __swapper_bs_region.pud;
+     }
+     pud += pud_index(addr);
+#endif

Can we free the unused reg tables in the else cases? If __pa() works we should be able to hand them to memblock, no?

Only if we put the memblock_reserve() of the kernel image before early_fixmap_init(), otherwise we are freeing only to reserve it again later.

Quoted 9 lines

[...]

arch/arm64/mm/mmu.c void vmemmap_free(unsigned long start, unsigned long end) · line 649

Quoted 4 lines

/*
* The boot-ioremap range spans multiple pmds, for which

arch/arm64/mm/mmu.c +1 -1
-      * we are not preparted:
+      * we are not prepared:

This typo has been bugging me for ages. I'll be glad to see it go!

:-)

reviewer
#23 lore raw

PATCH v3 7/11 arm64: fixmap: allow init before linear mapping is set up

Quoted 30 lines · Ard Biesheuvel
Quoted 29 lines · Mark Rutland

Hi Ard,

Quoted 9 lines · Ard Biesheuvel

This reworks early_ioremap_init() so it populates the various levels of translation tables while taking the following into account:
- be prepared for any of the levels to have been populated already, as this may be the case once we move the kernel text mapping out of the linear mapping;
- don't rely on __va() to translate the physical address in a page table entry to a virtual address, since this produces linear mapping addresses; instead, use the fact that at any level, we know exactly which page in swapper_pg_dir an entry could be pointing to if it points anywhere.

Can we not use Catalin's PHYS_OFFSET swizzling trick instead? i.e.

* Set PHYS_OFFSET so __va hits in the text mapping.

* Create the fixmap entries.

* Parse the DTB or UEFI memory map, figure out the real PHYS_OFFSET.

* Create linear mapping for the initial tables.

* Swap PHYS_OFFSET for the real version, and update init_mm->pgd to point at the linear map alias of the swapper pgd.

It seemed like that would require less open-coding of table manipulation code, as we could use __va() early.

Quoted 6 lines
Quoted 2 lines

Is there a limitation with that approach that I'm missing?

I didn't quite catch Catalin's suggestion to mean the above, but the way you put it seems viable to me. I'll have a go and see how far I get with it.

We discussed it (and wrote it up) on the plane back from the FW summit, and it may have made more sense in person than it did on the list; I've only skimmed Catalin's responses. ;)

Quoted 37 lines
Quoted 37 lines
Quoted 23 lines
arch/arm64/mm/mmu.c +23 -0
+#if CONFIG_ARM64_PGTABLE_LEVELS > 3
+     pud_t   pud[PTRS_PER_PUD];
+#endif
+#if CONFIG_ARM64_PGTABLE_LEVELS > 2
+     pmd_t   pmd[PTRS_PER_PMD];
+#endif
+     pte_t   pte[PTRS_PER_PTE];
+};
+
+static void __init bootstrap_mem_region(unsigned long addr,
+                                     struct mem_bootstrap_region *reg,
+                                     pmd_t **ppmd, pte_t **ppte)
+{
+     /*
+      * Avoid using the linear phys-to-virt translation __va() so that we
+      * can use this code before the linear mapping is set up. Note that
+      * any populated entries at any level can only point into swapper_pg_dir
+      * since no other translation table pages have been allocated yet.
+      * So at each level, we either need to populate it, or it has already
+      * been populated by a swapper_pg_dir table at the same level, in which
+      * case we can figure out its virtual address without applying __va()
+      * on the contents of the entry, using the following struct.
+      */

arch/arm64/mm/mmu.c static void __init __map_memblock(phys_addr_t start, phys_addr_t end) · line 377

Quoted 10 lines
arch/arm64/mm/mmu.c +10 -0
+     extern struct mem_bootstrap_region __swapper_bs_region;
+
+     pgd_t *pgd = pgd_offset_k(addr);
+     pud_t *pud = (pud_t *)pgd;
+     pmd_t *pmd = (pmd_t *)pud;
+
+#if CONFIG_ARM64_PGTABLE_LEVELS > 3
+     if (pgd_none(*pgd)) {
+             clear_page(reg->pud);
+             pgd_populate(&init_mm, pgd, reg->pud);

What's PHYS_OFFSET expected to be at this point (for the purposes of __pa() in the *_populate*() macros)?

Quoted 8 lines
Quoted 3 lines

If we're relying on __pa() to convert text->phys, won't __va() convert phys->text at this point?

At this points, yes. But later on, when the kernel text moves out of the linear region, __pa() takes into account whether the input VA is above or below PAGE_OFFSET, and adds the kernel image offset in the latter case.

Ah, I see.

Quoted 2 lines

Changing __va() so it implements the inverse would be a can of worms i'd rather keep closed.

I completely agree.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
arch/arm64/mm/mmu.c +6 -0
+             pud = reg->pud;
+     } else {
+             pud = __swapper_bs_region.pud;
+     }
+     pud += pud_index(addr);
+#endif
Quoted 7 lines
Quoted 3 lines

Can we free the unused reg tables in the else cases? If __pa() works we should be able to hand them to memblock, no?

Only if we put the memblock_reserve() of the kernel image before early_fixmap_init(), otherwise we are freeing only to reserve it again later.

Damn. That gets really painful with the memory limit (which does a memblock_remove), early_param, and so on. There's horrible ordering dependencies between those.

We could get around that with an early page allocator. Have each user (just fixmap and linear map init?) place an upper bound on the pages they need into .pgtbl.pool, have them allocate from there as needed, and immediately after the memblock_reserve of the kernel, unreserve (remove
+ add) any remaining pages.

Mark.

#24 lore raw

PATCH v3 8/11 arm64: mm: explicitly bootstrap the linear mapping

In reply to Ard Biesheuvel

In preparation of moving the kernel text out of the linear mapping, ensure that the part of the kernel Image that contains the statically allocated page tables is made accessible via the linear mapping before performing the actual mapping of all of memory. This is needed by the normal mapping routines, that rely on the linear mapping to walk the page tables while manipulating them.

---
 arch/arm64/kernel/vmlinux.lds.S |  10 +++-
 arch/arm64/mm/mmu.c             | 104 ++++++++++++++++++++++++++++------------
 2 files changed, 81 insertions(+), 33 deletions(-)
arch/arm64/kernel/vmlinux.lds.S +8 -2
index 604f285d3832..b7cdf4feb9f1 100644
@@ -160,8 +160,7 @@ SECTIONS
 
 	BSS_SECTION(0, 0, 0)
 
-	.pgdir (NOLOAD) : {
-		. = ALIGN(PAGE_SIZE);
+	.pgdir (NOLOAD) : ALIGN(SZ_1M) {
 		idmap_pg_dir = .;
 		. += IDMAP_DIR_SIZE;
 		swapper_pg_dir = .;
@@ -187,6 +186,13 @@ ASSERT(__idmap_text_end - (__idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
 	"ID map text too big or misaligned")
 
 /*
+ * The pgdir region needs to be mappable using a single PMD or PUD sized region,
+ * so it should not cross a 512 MB or 1 GB alignment boundary, respectively
+ * (depending on page size). So align to an upper bound of its size.
+ */
+ASSERT(SIZEOF(.pgdir) < ALIGNOF(.pgdir), ".pgdir size exceeds its alignment")
+
+/*
  * If padding is applied before .head.text, virt<->phys conversions will fail.
  */
 ASSERT(_text == (PAGE_OFFSET + TEXT_OFFSET), "HEAD is misaligned")
arch/arm64/mm/mmu.c +73 -31

120 lines not on this page · raw message

Signature · 1 line
1.8.3.2
#25 lore raw

PATCH v3 9/11 arm64: move kernel mapping out of linear region

In reply to Ard Biesheuvel

This moves the primary mapping of the kernel Image out of the linear region. This is a preparatory step towards allowing the kernel Image to reside anywhere in physical memory without affecting the ability to map all of it efficiently.

---
 arch/arm64/include/asm/memory.h | 15 ++++++++++++---
 arch/arm64/kernel/head.S        |  8 ++++----
 arch/arm64/kernel/vmlinux.lds.S |  4 ++--
 arch/arm64/mm/mmu.c             |  2 ++
 4 files changed, 20 insertions(+), 9 deletions(-)
arch/arm64/include/asm/memory.h +12 -3
index f800d45ea226..d92268614ddc 100644
@@ -38,8 +38,10 @@
  */
 #define PCI_IO_SIZE		SZ_16M
 
+#define KIMAGE_OFFSET		SZ_64M
+
 /*
- * PAGE_OFFSET - the virtual address of the start of the kernel image (top
+ * PAGE_OFFSET - the virtual address of the base of the linear mapping (top
  *		 (VA_BITS - 1))
  * VA_BITS - the maximum number of bits for virtual addresses.
  * TASK_SIZE - the maximum size of a user space task.
@@ -49,7 +51,8 @@
  */
 #define VA_BITS			(CONFIG_ARM64_VA_BITS)
 #define PAGE_OFFSET		(UL(0xffffffffffffffff) << (VA_BITS - 1))
-#define MODULES_END		(PAGE_OFFSET)
+#define KIMAGE_VADDR		(PAGE_OFFSET - KIMAGE_OFFSET)
+#define MODULES_END		KIMAGE_VADDR
 #define MODULES_VADDR		(MODULES_END - SZ_64M)
 #define PCI_IO_END		(MODULES_VADDR - SZ_2M)
 #define PCI_IO_START		(PCI_IO_END - PCI_IO_SIZE)
@@ -77,7 +80,11 @@
  * private definitions which should NOT be used outside memory.h
  * files.  Use virt_to_phys/phys_to_virt/__pa/__va instead.
  */
-#define __virt_to_phys(x)	(((phys_addr_t)(x) - PAGE_OFFSET + PHYS_OFFSET))
+#define __virt_to_phys(x) ({						\
+	long __x = (long)(x) - PAGE_OFFSET;				\
+	__x >= 0 ? (phys_addr_t)(__x + PHYS_OFFSET) : 			\
+		   (phys_addr_t)(__x + PHYS_OFFSET + image_offset); })
+
 #define __phys_to_virt(x)	((unsigned long)((x) - PHYS_OFFSET + PAGE_OFFSET))
 
 /*
@@ -113,6 +120,8 @@ extern phys_addr_t		memstart_addr;
 /* PHYS_OFFSET - the physical address of the start of memory. */
 #define PHYS_OFFSET		({ memstart_addr; })
 
+extern u64 image_offset;
+
 /*
  * PFNs are used to describe any physical page; this means
  * PFN 0 == physical address 0.
arch/arm64/kernel/head.S +4 -4
index 208ca21868cc..729526a45f60 100644
@@ -36,8 +36,6 @@
 #include <asm/page.h>
 #include <asm/virt.h>
 
-#define __PHYS_OFFSET	(KERNEL_START - TEXT_OFFSET)
-
 #if (TEXT_OFFSET & 0xfff) != 0
 #error TEXT_OFFSET must be at least 4KB aligned
 #elif (PAGE_OFFSET & 0x1fffff) != 0
@@ -58,6 +56,8 @@
 
 #define KERNEL_START	_text
 #define KERNEL_END	_end
+#define KERNEL_BASE	(KERNEL_START - TEXT_OFFSET)
+
 
 /*
  * Initial memory map attributes.
@@ -235,7 +235,7 @@ section_table:
 ENTRY(stext)
 	bl	preserve_boot_args
 	bl	el2_setup			// Drop to EL1, w20=cpu_boot_mode
-	adrp	x24, __PHYS_OFFSET
+	adrp	x24, KERNEL_BASE
 	bl	set_cpu_boot_mode_flag
 	bl	__create_page_tables		// x25=TTBR0, x26=TTBR1
 	/*
@@ -411,7 +411,7 @@ __create_page_tables:
 	 * Map the kernel image (starting with PHYS_OFFSET).
 	 */
 	mov	x0, x26				// swapper_pg_dir
-	mov	x5, #PAGE_OFFSET
+	ldr	x5, =KERNEL_BASE
 	create_pgd_entry x0, x5, x3, x6
 	ldr	x6, =KERNEL_END			// __va(KERNEL_END)
 	mov	x3, x24				// phys offset
arch/arm64/kernel/vmlinux.lds.S +2 -2
index b7cdf4feb9f1..7241dd428462 100644
@@ -84,7 +84,7 @@ SECTIONS
 		*(.discard.*)
 	}
 
-	. = PAGE_OFFSET + TEXT_OFFSET;
+	. = KIMAGE_VADDR + TEXT_OFFSET;
 
 	.head.text : {
 		_text = .;
@@ -195,4 +195,4 @@ ASSERT(SIZEOF(.pgdir) < ALIGNOF(.pgdir), ".pgdir size exceeds its alignment")
 /*
  * If padding is applied before .head.text, virt<->phys conversions will fail.
  */
-ASSERT(_text == (PAGE_OFFSET + TEXT_OFFSET), "HEAD is misaligned")
+ASSERT(_text == (KIMAGE_VADDR + TEXT_OFFSET), "HEAD is misaligned")
arch/arm64/mm/mmu.c +2 -0
index ea35ec911393..4e2d696c5b9e 100644
@@ -49,6 +49,8 @@ u64 idmap_t0sz = TCR_T0SZ(VA_BITS);
 struct page *empty_zero_page;
 EXPORT_SYMBOL(empty_zero_page);
 
+u64 image_offset __read_mostly = KIMAGE_OFFSET;
+
 pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,
 			      unsigned long size, pgprot_t vma_prot)
 {
Signature · 1 line
1.8.3.2
#26 lore raw

PATCH v3 10/11 arm64: allow kernel Image to be loaded anywhere in physical memory

In reply to Ard Biesheuvel

This relaxes the kernel Image placement requirements, so that it may be placed at any 2 MB aligned offset in physical memory.

This is accomplished by ignoring PHYS_OFFSET when installing memblocks, and accounting for the apparent virtual offset of the kernel Image (in addition to the 64 MB that is is moved below PAGE_OFFSET). As a result, virtual address references below PAGE_OFFSET are correctly mapped onto physical references into the kernel Image regardless of where it sits in memory.

---
 Documentation/arm64/booting.txt | 17 +++++++----------
 arch/arm64/mm/init.c            | 32 +++++++++++++++++++-------------
 2 files changed, 26 insertions(+), 23 deletions(-)
Documentation/arm64/booting.txt +7 -10
index 6396460f6085..811d93548bdc 100644
@@ -110,16 +110,13 @@ Header notes:
   depending on selected features, and is effectively unbound.
 
 The Image must be placed text_offset bytes from a 2MB aligned base
-address near the start of usable system RAM and called there. Memory
-below that base address is currently unusable by Linux, and therefore it
-is strongly recommended that this location is the start of system RAM.
-At least image_size bytes from the start of the image must be free for
-use by the kernel.
-
-Any memory described to the kernel (even that below the 2MB aligned base
-address) which is not marked as reserved from the kernel e.g. with a
-memreserve region in the device tree) will be considered as available to
-the kernel.
+address anywhere in usable system RAM and called there. At least
+image_size bytes from the start of the image must be free for use
+by the kernel.
+
+Any memory described to the kernel which is not marked as reserved from
+the kernel e.g. with a memreserve region in the device tree) will be
+considered as available to the kernel.
 
 Before jumping into the kernel, the following conditions must be met:
 
arch/arm64/mm/init.c +19 -13
index 48175b769074..18234c7cf6e6 100644
@@ -375,8 +375,6 @@ __setup("keepinitrd", keepinitrd_setup);
 
 void __init early_init_dt_add_memory_arch(u64 base, u64 size)
 {
-	const u64 phys_offset = __pa(PAGE_OFFSET);
-
 	if (!PAGE_ALIGNED(base)) {
 		if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
 			pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
@@ -388,16 +386,24 @@ void __init early_init_dt_add_memory_arch(u64 base, u64 size)
 	}
 	size &= PAGE_MASK;
 
-	if (base + size < phys_offset) {
-		pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
-			   base, base + size);
-		return;
-	}
-	if (base < phys_offset) {
-		pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
-			   base, phys_offset);
-		size -= phys_offset - base;
-		base = phys_offset;
-	}
 	memblock_add(base, size);
+
+	/*
+	 * Set memstart_addr to the base of the lowest physical memory region,
+	 * rounded down to PUD/PMD alignment so we can map it efficiently.
+	 * Since this also affects the apparent offset of the kernel image in
+	 * the virtual address space, increase image_offset by the same amount
+	 * that we decrease memstart_addr.
+	 */
+	if (!memstart_addr || memstart_addr > base) {
+		u64 new_memstart_addr;
+
+		if (IS_ENABLED(CONFIG_ARM64_64K_PAGES))
+			new_memstart_addr = base & PMD_MASK;
+		else
+			new_memstart_addr = base & PUD_MASK;
+
+		image_offset += memstart_addr - new_memstart_addr;
+		memstart_addr = new_memstart_addr;
+	}
 }
Signature · 1 line
1.8.3.2
reviewer
#27 lore raw

PATCH v3 10/11 arm64: allow kernel Image to be loaded anywhere in physical memory

Quoted 31 lines · Ard Biesheuvel

This relaxes the kernel Image placement requirements, so that it may be placed at any 2 MB aligned offset in physical memory.

This is accomplished by ignoring PHYS_OFFSET when installing memblocks, and accounting for the apparent virtual offset of the kernel Image (in addition to the 64 MB that is is moved below PAGE_OFFSET). As a result, virtual address references below PAGE_OFFSET are correctly mapped onto physical references into the kernel Image regardless of where it sits in memory.

---
 Documentation/arm64/booting.txt | 17 +++++++----------
 arch/arm64/mm/init.c            | 32 +++++++++++++++++++-------------
 2 files changed, 26 insertions(+), 23 deletions(-)
Documentation/arm64/booting.txt +0 -7
index 6396460f6085..811d93548bdc 100644
@@ -110,16 +110,13 @@ Header notes:
   depending on selected features, and is effectively unbound.
 
 The Image must be placed text_offset bytes from a 2MB aligned base
-address near the start of usable system RAM and called there. Memory
-below that base address is currently unusable by Linux, and therefore it
-is strongly recommended that this location is the start of system RAM.
-At least image_size bytes from the start of the image must be free for
-use by the kernel.
-
-Any memory described to the kernel (even that below the 2MB aligned base

Documentation/arm64/booting.txt Header notes: · line 119

Quoted 10 lines
Documentation/arm64/booting.txt +7 -3
-address) which is not marked as reserved from the kernel e.g. with a
-memreserve region in the device tree) will be considered as available to
-the kernel.
+address anywhere in usable system RAM and called there. At least
+image_size bytes from the start of the image must be free for use
+by the kernel.
+
+Any memory described to the kernel which is not marked as reserved from
+the kernel e.g. with a memreserve region in the device tree) will be
+considered as available to the kernel.

As with the other docs changes we'll need a note w.r.t. the behaviour older kernels. This might also be worth a feature bitmap bit so loaders can do the best thing for the given kernel version.

Quoted 8 lines
Documentation/arm64/booting.txt +8 -0
+	/*
+	 * Set memstart_addr to the base of the lowest physical memory region,
+	 * rounded down to PUD/PMD alignment so we can map it efficiently.
+	 * Since this also affects the apparent offset of the kernel image in
+	 * the virtual address space, increase image_offset by the same amount
+	 * that we decrease memstart_addr.
+	 */
+	if (!memstart_addr || memstart_addr > base) {

Documentation/arm64/booting.txt Header notes:

Quoted 10 lines
Documentation/arm64/booting.txt +10 -0
+		u64 new_memstart_addr;
+
+		if (IS_ENABLED(CONFIG_ARM64_64K_PAGES))
+			new_memstart_addr = base & PMD_MASK;
+		else
+			new_memstart_addr = base & PUD_MASK;
+
+		image_offset += memstart_addr - new_memstart_addr;
+		memstart_addr = new_memstart_addr;
+	}

There's one slight snag with this. Given sufficient memory (e.g. more than 512GB) and a sufficiently small VA size (e.g 39 bit), if the kernel is loaded at the end of RAM we might not cover it in the linear mapping.

It would be nice if we could detect that and warn/stop if possible (earlycon should be up by this point), rather than blowing up in strange ways. The other option is to try to limit the memstart_addr such that we know that we can map the kernel text (removing the unusable memory).

Mark.

#28 lore raw

PATCH v3 11/11 arm64/efi: adapt to relaxed kernel Image placement requirements

In reply to Ard Biesheuvel

This adapts the EFI stub kernel placement to the new relaxed requirements, by placing the kernel Image at the highest available 2 MB offset in physical memory.

---
 arch/arm64/kernel/efi-stub.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
arch/arm64/kernel/efi-stub.c +2 -2
index f5374065ad53..c2141ed8a043 100644
@@ -28,8 +28,8 @@ efi_status_t __init handle_kernel_image(efi_system_table_t *sys_table,
 	kernel_size = _edata - _text;
 	if (*image_addr != (dram_base + TEXT_OFFSET)) {
 		kernel_memsize = kernel_size + (_end - _edata);
-		status = efi_low_alloc(sys_table, kernel_memsize + TEXT_OFFSET,
-				       SZ_2M, reserve_addr);
+		status = efi_high_alloc(sys_table, kernel_memsize + TEXT_OFFSET,
+				       SZ_2M, reserve_addr, ULONG_MAX);
 		if (status != EFI_SUCCESS) {
 			pr_efi_err(sys_table, "Failed to relocate kernel\n");
 			return status;
Signature · 1 line
1.8.3.2