arm64: avoid early __va translations
Subsystems (3)
v3 cover arm64: avoid early __va translations
This is a somewhat cleaner approach for dealing with the issue that the early FDT performs __va() translations before the linear mapping has been set up. Being able to defer the assignment of memstart_addr until after we have discovered all of memory is an important piece of functionality, not only for KASLR but also for mapping the linear region as efficiently as possible.
Changes since v2:
- replace __weak function in patches #1 and #2 with a preprocessor macro
- add patch to address the circular header dependency when including asm/bug.h in asm/memory.h
- turned the hack that adds the BUG_ON() into a proper patch
Patch #1 refactors the early FDT code so that the actual assignment of initrd_start and initrd_end (which is where the __va() translations are performed) can be overridden in architecture specific code.
Patch #2 performs the override, and only records the physical addresses as they are found in the /chosen node, or on the command line, and defers the __va() translation until after memstart_addr has been assigned.
Patch #3 reshuffles some #includes and #defines so that asm/bug.h can be included (and used) in asm/memory.h
Patch #4 implements a BUG_ON() check to ensure that no references to memstart_addr are made before it has been assigned.
Ard Biesheuvel (4):
of/fdt: factor out assignment of initrd_start/initrd_end
arm64: defer __va translation of initrd_start and initrd_end
arm64: prevent potential circular header dependencies in asm/bug.h
arm64: prevent __va() translations before memstart_addr is assigned
arch/arm64/include/asm/bug.h | 2 +- arch/arm64/include/asm/debug-monitors.h | 2 +- arch/arm64/include/asm/memory.h | 12 ++++++++++- arch/arm64/kvm/hyp/debug-sr.c | 1 + arch/arm64/mm/init.c | 21 +++++++++++++++----- drivers/of/fdt.c | 14 ++++++++++--- 6 files changed, 41 insertions(+), 11 deletions(-)
Signature · 1 line
2.5.0
PATCH v3 1/4 of/fdt: factor out assignment of initrd_start/initrd_end
Since architectures may not yet have their linear mapping up and running when the initrd address is discovered from the DT, factor out the assignment of initrd_start and initrd_end, so that an architecture can override it and use the translation it needs.
--- drivers/of/fdt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
drivers/of/fdt.c +11 -3
@@ -760,6 +760,16 @@ const void * __init of_flat_dt_match_machine(const void *default_match, }#ifdef CONFIG_BLK_DEV_INITRD +#ifndef __early_init_dt_declare_initrd +static void __early_init_dt_declare_initrd (unsigned long start, +unsigned long end) +{ + initrd_start = (unsigned long )__va (start); + initrd_end = (unsigned long )__va (end); + initrd_below_start_ok =1 ; +} +#endif +/** * early_init_dt_check_for_initrd - Decode initrd location from flat tree * @node: reference to node containing initrd location ('chosen')
@@ -782,9 +792,7 @@ static void __init early_init_dt_check_for_initrd(unsigned long node)return ; end =of_read_number (prop, len/4 ); - initrd_start = (unsigned long )__va (start); - initrd_end = (unsigned long )__va (end); - initrd_below_start_ok =1 ; +__early_init_dt_declare_initrd (start, end);pr_debug ("initrd_start=0x%llx initrd_end=0x%llx\n" , (unsigned long long )start, (unsigned long long )end);
Signature · 1 line
2.5.0
PATCH v3 1/4 of/fdt: factor out assignment of initrd_start/initrd_end
Quoted 10 lines · Ard Biesheuvel
Since architectures may not yet have their linear mapping up and running when the initrd address is discovered from the DT, factor out the assignment of
initrd_startandinitrd_end, so that an architecture can override it and use the translation it needs.Cc Rob Herring <[email protected]>Signed-off-by Ard Biesheuvel <[email protected]>--- drivers/of/fdt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
Mark, Rob, any comments on this?
Will
Quoted 35 lines
drivers/of/fdt.c +11
-3@@ -760,6 +760,16 @@ const void * __init of_flat_dt_match_machine(const void *default_match, }#ifdef CONFIG_BLK_DEV_INITRD +#ifndef __early_init_dt_declare_initrd +static void __early_init_dt_declare_initrd (unsigned long start, +unsigned long end) +{ + initrd_start = (unsigned long )__va (start); + initrd_end = (unsigned long )__va (end); + initrd_below_start_ok =1 ; +} +#endif +/** * early_init_dt_check_for_initrd - Decode initrd location from flat tree * @node: reference to node containing initrd location ('chosen') @@ -782,9 +792,7 @@ static void __init early_init_dt_check_for_initrd(unsigned long node)return ; end =of_read_number (prop, len/4 ); - initrd_start = (unsigned long )__va (start); - initrd_end = (unsigned long )__va (end); - initrd_below_start_ok =1 ; +__early_init_dt_declare_initrd (start, end);pr_debug ("initrd_start=0x%llx initrd_end=0x%llx\n" , (unsigned long long )start, (unsigned long long )end);Signature · 1 line
2.5.0
PATCH v3 1/4 of/fdt: factor out assignment of initrd_start/initrd_end
Quoted 6 lines · Will Deacon
Quoted 5 lines · Ard Biesheuvel
Since architectures may not yet have their linear mapping up and running when the initrd address is discovered from the DT, factor out the assignment of
initrd_startandinitrd_end, so that an architecture can override it and use the translation it needs.
Quoted 8 lines
Quoted 5 lines
Cc Rob Herring <[email protected]>Signed-off-by Ard Biesheuvel <[email protected]>--- drivers/of/fdt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)Mark, Rob, any comments on this?
Already acked by Rob, and pulled into -next by Catalin ...
PATCH v3 1/4 of/fdt: factor out assignment of initrd_start/initrd_end
Quoted 13 lines · Ard Biesheuvel
Quoted 12 lines · Will Deacon
Quoted 10 lines · Ard Biesheuvel
Since architectures may not yet have their linear mapping up and running when the initrd address is discovered from the DT, factor out the assignment of
initrd_startandinitrd_end, so that an architecture can override it and use the translation it needs.Cc Rob Herring <[email protected]>Signed-off-by Ard Biesheuvel <[email protected]>--- drivers/of/fdt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
Quoted 4 lines
Quoted 2 lines
Mark, Rob, any comments on this?
Already acked by Rob, and pulled into -next by Catalin ...
Weird, I don't see that in my inbox or on the list archives. Where should I be looking?
I'd certainly like to see some performance measurement of the BUG_ON in PHYS_ADDR that is introduced later in the series too.
Will
PATCH v3 1/4 of/fdt: factor out assignment of initrd_start/initrd_end
Quoted 17 lines · Will Deacon
Quoted 16 lines · Ard Biesheuvel
Quoted 14 lines · Will Deacon
Quoted 10 lines · Ard Biesheuvel
Since architectures may not yet have their linear mapping up and running when the initrd address is discovered from the DT, factor out the assignment of
initrd_startandinitrd_end, so that an architecture can override it and use the translation it needs.Cc Rob Herring <[email protected]>Signed-off-by Ard Biesheuvel <[email protected]>--- drivers/of/fdt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)Mark, Rob, any comments on this?
Quoted 8 lines
Quoted 1 line
Already acked by Rob, and pulled into -next by Catalin ...
Weird, I don't see that in my inbox or on the list archives. Where should I be looking?
I'd certainly like to see some performance measurement of the
BUG_ONinPHYS_ADDRthat is introduced later in the series too.
Apologies, I removed you from cc unintentionally.
PATCH v3 2/4 arm64: defer __va translation of initrd_start and initrd_end
In reply to Ard BiesheuvelCommit 66f51676a095 ("arm64: allow kernel Image to be loaded anywhere in physical memory") defers the assignment of memstart_addr to the point where all memory has been discovered and possibly clipped based on the size of the linear region and the presence of a mem= command line parameter.
However, this results in __va() translations that have been performed up to this point to have been carried out with a preliminary, incorrect value of memstart_addr, and these values need to be fixed up. So wire up the generic support to declare the initrd addresses, and implement it without __va() translations, and perform the translation after memstart_addr has been assigned.
--- arch/arm64/include/asm/memory.h | 8 ++++++++ arch/arm64/mm/init.c | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-)
arch/arm64/include/asm/memory.h +8 -0
@@ -121,6 +121,14 @@#define IOREMAP_MAX_ORDER (PMD_SHIFT)#endif +#ifdef CONFIG_BLK_DEV_INITRD +#define __early_init_dt_declare_initrd (__start, __end) \ + do { \ + initrd_start = (__start); \ + initrd_end = (__end); \ + } while (0) +#endif +#ifndef __ASSEMBLY__extern phys_addr_t memstart_addr;
arch/arm64/mm/init.c +9 -4
@@ -61,8 +61,8 @@ static int __init early_initrd(char *p)if (*endp ==',' ) { size =memparse (endp +1 ,NULL ); - initrd_start = (unsigned long )__va (start); - initrd_end = (unsigned long )__va (start + size); + initrd_start = start; + initrd_end = start + size; }return 0 ; }
@@ -213,8 +213,13 @@ void __init arm64_memblock_init(void)*/ memblock_reserve (__pa (_text), _end - _text);#ifdef CONFIG_BLK_DEV_INITRD -if (initrd_start) -memblock_reserve (__virt_to_phys (initrd_start), initrd_end - initrd_start); +if (initrd_start) { +memblock_reserve (initrd_start, initrd_end - initrd_start); + +/* the generic initrd code expects virtual addresses */ + initrd_start =__phys_to_virt (initrd_start); + initrd_end =__phys_to_virt (initrd_end); + }#endif early_init_fdt_scan_reserved_mem ();
Signature · 1 line
2.5.0
PATCH v3 3/4 arm64: prevent potential circular header dependencies in asm/bug.h
In reply to Ard BiesheuvelCurrently, using BUG_ON() in header files is cumbersome, due to the fact that asm/bug.h transitively includes a lot of other header files, resulting in the actual BUG_ON() invocation appearing before its definition in the preprocessor input. So let's reverse the #include dependency between asm/bug.h and asm/debug-monitors.h, by moving the definition of BUG_BRK_IMM from the latter to the former. Also fix up one user of asm/debug-monitors.h which relied on a transitive include.
--- arch/arm64/include/asm/bug.h | 2 +- arch/arm64/include/asm/debug-monitors.h | 2 +- arch/arm64/kvm/hyp/debug-sr.c | 1 + 3 files changed, 3 insertions(+), 2 deletions(-)
arch/arm64/include/asm/bug.h +1 -1
@@ -18,7 +18,7 @@#ifndef _ARCH_ARM64_ASM_BUG_H#define _ARCH_ARM64_ASM_BUG_H -#include <asm/debug-monitors.h> +#define BUG_BRK_IMM 0x800#ifdef CONFIG_GENERIC_BUG #define HAVE_ARCH_BUG
arch/arm64/include/asm/debug-monitors.h +1 -1
@@ -20,6 +20,7 @@#include <linux/errno.h> #include <linux/types.h> +#include <asm/bug.h> #include <asm/esr.h> #include <asm/insn.h> #include <asm/ptrace.h>
@@ -57,7 +58,6 @@#define FAULT_BRK_IMM 0x100#define KGDB_DYN_DBG_BRK_IMM 0x400#define KGDB_COMPILED_DBG_BRK_IMM 0x401 -#define BUG_BRK_IMM 0x800/* * BRK instruction encoding
arch/arm64/kvm/hyp/debug-sr.c +1 -0
@@ -18,6 +18,7 @@#include <linux/compiler.h> #include <linux/kvm_host.h> +#include <asm/debug-monitors.h> #include <asm/kvm_asm.h> #include <asm/kvm_mmu.h>
Signature · 1 line
2.5.0
PATCH v3 3/4 arm64: prevent potential circular header dependencies in asm/bug.h
Quoted 21 lines · Ard Biesheuvel
Currently, using
BUG_ON()in header files is cumbersome, due to the fact thatasm/bug.htransitively includes a lot of other header files, resulting in the actualBUG_ON()invocation appearing before its definition in the preprocessor input. So let's reverse the #include dependency betweenasm/bug.handasm/debug-monitors.h, by moving the definition ofBUG_BRK_IMMfrom the latter to the former. Also fix up one user ofasm/debug-monitors.hwhich relied on a transitive include.Cc Dave Martin <[email protected]>Signed-off-by Ard Biesheuvel <[email protected]>--- arch/arm64/include/asm/bug.h | 2 +- arch/arm64/include/asm/debug-monitors.h | 2 +- arch/arm64/kvm/hyp/debug-sr.c | 1 + 3 files changed, 3 insertions(+), 2 deletions(-)arch/arm64/include/asm/bug.h +0
-0
arch/arm64/include/asm/bug.h line 21
Quoted 5 lines
arch/arm64/include/asm/bug.h +1
-1@@ -18,7 +18,7 @@#ifndef _ARCH_ARM64_ASM_BUG_H#define _ARCH_ARM64_ASM_BUG_H -#include <asm/debug-monitors.h> +#define BUG_BRK_IMM 0x800
I'd really like to keep all the BRK immediates together, otherwise it's error-prone if/when we allocate new encodings. Maybe we could add a new header file for them, if we really need to?
Alternatively, given that this is all behind DEBUG_VM, you could rewrite the check as if (unlikely(...)) panic(...);
Will
PATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
In reply to Ard BiesheuvelSince memstart_addr is assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no __va() translation occur until memstart_addr has been set to a meaningful value.
So initialize memstart_addr to a value that cannot represent a valid physical address, and BUG() if memstart_addr is referenced while it still holds this value. Note that the > comparison against LLONG_MAX (not ULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.
--- arch/arm64/include/asm/memory.h | 4 +++- arch/arm64/mm/init.c | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-)
arch/arm64/include/asm/memory.h +3 -1
@@ -24,6 +24,7 @@#include <linux/compiler.h> #include <linux/const.h> #include <linux/types.h> +#include <asm/bug.h> #include <asm/sizes.h> /*
@@ -133,7 +134,8 @@extern phys_addr_t memstart_addr;/* PHYS_OFFSET - the physical address of the start of memory. */ -#define PHYS_OFFSET ({ memstart_addr; }) +#define PHYS_OFFSET \ + ({ BUG_ON(memstart_addr > LLONG_MAX); memstart_addr; })/* the virtual base of the kernel image (minus TEXT_OFFSET) */ extern u64 kimage_vaddr;
arch/arm64/mm/init.c +7 -1
@@ -48,7 +48,13 @@#include "mm.h" -phys_addr_t memstart_addr__read_mostly =0 ; +/* +* We need to be able to catch inadvertent references to memstart_addr +* that occur (potentially in generic code) before arm64_memblock_init() +* executes, which assigns it its actual value. So use a default value +* that cannot be mistaken for a real physical address. +*/ +phys_addr_t memstart_addr__read_mostly =ULLONG_MAX ;phys_addr_t arm64_dma_phys_limit__read_mostly ;#ifdef CONFIG_BLK_DEV_INITRD
Signature · 1 line
2.5.0
PATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
Quoted 10 lines · Ard Biesheuvel
Since
memstart_addris assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no__va()translation occur untilmemstart_addrhas been set to a meaningful value.So initialize
memstart_addrto a value that cannot represent a valid physical address, andBUG()ifmemstart_addris referenced while it still holds this value. Note that the > comparison againstLLONG_MAX(notULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.
Even so, I'd imagine that having a measurable impact on system performance. Did you have a go at benchmarking this?
Will
PATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
Quoted 6 lines · Will Deacon
Quoted 5 lines · Ard Biesheuvel
Since
memstart_addris assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no__va()translation occur untilmemstart_addrhas been set to a meaningful value.
Quoted 9 lines
Quoted 5 lines
So initialize
memstart_addrto a value that cannot represent a valid physical address, andBUG()ifmemstart_addris referenced while it still holds this value. Note that the > comparison againstLLONG_MAX(notULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.Even so, I'd imagine that having a measurable impact on system performance. Did you have a go at benchmarking this?
So in what kind of workload would the __pa() translation be on a hot path? If you're dealing with DMA or other things that involve physical addresses, surely, the single predicted non-taken branch instruction shouldn't hurt?
Catalin Marinas
maintainerPATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
Quoted 13 lines · Ard Biesheuvel
Quoted 12 lines · Will Deacon
Quoted 10 lines · Ard Biesheuvel
Since
memstart_addris assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no__va()translation occur untilmemstart_addrhas been set to a meaningful value.So initialize
memstart_addrto a value that cannot represent a valid physical address, andBUG()ifmemstart_addris referenced while it still holds this value. Note that the > comparison againstLLONG_MAX(notULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.
Quoted 7 lines
Quoted 2 lines
Even so, I'd imagine that having a measurable impact on system performance. Did you have a go at benchmarking this?
So in what kind of workload would the
__pa()translation be on a hot path? If you're dealing with DMA or other things that involve physical addresses, surely, the single predicted non-taken branch instruction shouldn't hurt?
I recall we looked at this in the early arm64 days and found a lot of memory accesses to memstart_addr but we decided to keep it as the alternatives would have been: (a) no more single Image or (b) always 4-levels page tables.
You could try perf to get some statistics but, for example, most of the code that works on pages (e.g. block I/O) and needs to access a page ends up doing a kmap(page) which in turns does a __va(). You also have lots of virt_to_page() calls in sl*b, so we need to see what impact this change has.
Signature · 1 line
Catalin
PATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
Quoted 27 lines · Catalin Marinas
Quoted 13 lines · Ard Biesheuvel
Quoted 12 lines · Will Deacon
Quoted 10 lines · Ard Biesheuvel
Since
memstart_addris assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no__va()translation occur untilmemstart_addrhas been set to a meaningful value.So initialize
memstart_addrto a value that cannot represent a valid physical address, andBUG()ifmemstart_addris referenced while it still holds this value. Note that the > comparison againstLLONG_MAX(notULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.Quoted 7 lines
Quoted 2 lines
Even so, I'd imagine that having a measurable impact on system performance. Did you have a go at benchmarking this?
So in what kind of workload would the
__pa()translation be on a hot path? If you're dealing with DMA or other things that involve physical addresses, surely, the single predicted non-taken branch instruction shouldn't hurt?I recall we looked at this in the early arm64 days and found a lot of memory accesses to
memstart_addrbut we decided to keep it as the alternatives would have been: (a) no more single Image or (b) always 4-levels page tables.
Quoted 6 lines
You could try perf to get some statistics but, for example, most of the code that works on pages (e.g. block I/O) and needs to access a page ends up doing a kmap(page) which in turns does a
__va(). You also have lots ofvirt_to_page()calls in sl*b, so we need to see what impact this change has.
I guess virt_to_page() is only valid on linear addresses, so we could reimplement it not to use the comparison with PAGE_OFFSET that I added to __pa() for the kernmap stuff
Catalin Marinas
maintainerPATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
In reply to Will DeaconQuoted 6 lines · Will Deacon
Quoted 5 lines · Ard Biesheuvel
Since
memstart_addris assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no__va()translation occur untilmemstart_addrhas been set to a meaningful value.
Quoted 8 lines
Quoted 5 lines
So initialize
memstart_addrto a value that cannot represent a valid physical address, andBUG()ifmemstart_addris referenced while it still holds this value. Note that the > comparison againstLLONG_MAX(notULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.Even so, I'd imagine that having a measurable impact on system performance. Did you have a go at benchmarking this?
Good point, I forgot about this discussion. We should revert this part indeed or at least make it dependent on some config option like DEBUG_VM.
Signature · 1 line
Catalin
PATCH v3 4/4 arm64: prevent __va() translations before memstart_addr is assigned
Quoted 13 lines · Catalin Marinas
Quoted 12 lines · Will Deacon
Quoted 10 lines · Ard Biesheuvel
Since
memstart_addris assigned relatively late in the boot code, after generic code like DT parsing and memblock manipulation has already occurred, we need to ensure that no__va()translation occur untilmemstart_addrhas been set to a meaningful value.So initialize
memstart_addrto a value that cannot represent a valid physical address, andBUG()ifmemstart_addris referenced while it still holds this value. Note that the > comparison againstLLONG_MAX(notULLONG_MAX) resolves to a single tbnz instruction that performs a conditional jump to a brk instruction that is emitted out of line.
Quoted 7 lines
Quoted 2 lines
Even so, I'd imagine that having a measurable impact on system performance. Did you have a go at benchmarking this?
Good point, I forgot about this discussion. We should revert this part indeed or at least make it dependent on some config option like
DEBUG_VM.
OK, let me code that up
v3 cover arm64: avoid early __va translations
In reply to Ard BiesheuvelHi Ard,
Quoted 33 lines · Ard Biesheuvel
This is a somewhat cleaner approach for dealing with the issue that the early FDT performs
__va()translations before the linear mapping has been set up. Being able to defer the assignment ofmemstart_addruntil after we have discovered all of memory is an important piece of functionality, not only for KASLR but also for mapping the linear region as efficiently as possible.Changes since v2:
- replace__weakfunction in patches #1 and #2 with a preprocessor macro
- add patch to address the circular header dependency when includingasm/bug.hinasm/memory.h
- turned the hack that adds theBUG_ON()into a proper patchPatch #1 refactors the early FDT code so that the actual assignment of
initrd_startandinitrd_end(which is where the__va()translations are performed) can be overridden in architecture specific code.Patch #2 performs the override, and only records the physical addresses as they are found in the /chosen node, or on the command line, and defers the
__va()translation until aftermemstart_addrhas been assigned.Patch #3 reshuffles some #includes and #defines so that
asm/bug.hcan be included (and used) inasm/memory.hPatch #4 implements a
BUG_ON()check to ensure that no references tomemstart_addrare made before it has been assigned.Ard Biesheuvel (4):
of/fdt: factor out assignment of initrd_start/initrd_end
arm64: defer__vatranslation ofinitrd_startandinitrd_end
arm64: prevent potential circular header dependencies inasm/bug.h
arm64: prevent__va()translations beforememstart_addris assigned
Quoted 7 lines
arch/arm64/include/asm/bug.h | 2 +- arch/arm64/include/asm/debug-monitors.h | 2 +- arch/arm64/include/asm/memory.h | 12 ++++++++++- arch/arm64/kvm/hyp/debug-sr.c | 1 + arch/arm64/mm/init.c | 21 +++++++++++++++----- drivers/of/fdt.c | 14 ++++++++++--- 6 files changed, 41 insertions(+), 11 deletions(-)
Tried these on one of our LS2 boards so here's a:
---
Best Regards, Laurentiu