[PATCH v8 33/43] arm64: Enable LPA2 at boot if supported by the system

Ryan Roberts ryan.roberts at arm.com
Wed Aug 7 14:41:10 PDT 2024


On 07/08/2024 09:46, Ryan Roberts wrote:
> On 06/08/2024 17:16, Ryan Roberts wrote:
>> Hi Ard,
>>
>> Sorry to drag up this old thread; I'm doing some work in this space and am
>> having a tough time convincing myself of the safety - see below...
>>
>> On 14/02/2024 12:29, Ard Biesheuvel wrote:
>>> From: Ard Biesheuvel <ardb at kernel.org>
>>>
>>> Update the early kernel mapping code to take 52-bit virtual addressing
>>> into account based on the LPA2 feature. This is a bit more involved than
>>> LVA (which is supported with 64k pages only), given that some page table
>>> descriptor bits change meaning in this case.
>>>
>>> To keep the handling in asm to a minimum, the initial ID map is still
>>> created with 48-bit virtual addressing, which implies that the kernel
>>> image must be loaded into 48-bit addressable physical memory. This is
>>> currently required by the boot protocol, even though we happen to
>>> support placement outside of that for LVA/64k based configurations.
>>>
>>> Enabling LPA2 involves more than setting TCR.T1SZ to a lower value,
>>> there is also a DS bit in TCR that needs to be set, and which changes
>>> the meaning of bits [9:8] in all page table descriptors. Since we cannot
>>> enable DS and every live page table descriptor at the same time, let's
>>> pivot through another temporary mapping. This avoids the need to
>>> reintroduce manipulations of the page tables with the MMU and caches
>>> disabled.
>>>
>>> To permit the LPA2 feature to be overridden on the kernel command line,
>>> which may be necessary to work around silicon errata, or to deal with
>>> mismatched features on heterogeneous SoC designs, test for CPU feature
>>> overrides first, and only then enable LPA2.
>>>
>>> Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
>>
>> [...]
>>
>>> +static void __init remap_idmap_for_lpa2(void)
>>> +{
>>> +	/* clear the bits that change meaning once LPA2 is turned on */
>>> +	pteval_t mask = PTE_SHARED;
>>> +
>>> +	/*
>>> +	 * We have to clear bits [9:8] in all block or page descriptors in the
>>> +	 * initial ID map, as otherwise they will be (mis)interpreted as
>>> +	 * physical address bits once we flick the LPA2 switch (TCR.DS). Since
>>> +	 * we cannot manipulate live descriptors in that way without creating
>>> +	 * potential TLB conflicts, let's create another temporary ID map in a
>>> +	 * LPA2 compatible fashion, and update the initial ID map while running
>>> +	 * from that.
>>> +	 */
>>> +	create_init_idmap(init_pg_dir, mask);
>>
>> Given the init_idmap always uses 48 bit VA, and the swapper VA size is
>> determined through Kconfig and may be smaller than 48 bit, how can you be
>> certain that init_pg_dir is big enough to hold the init idmap? Surely swapper
>> may use fewer levels and therefore be sized for fewer pages?
>>
>> I wonder if its possible that we end up running into the early_init_stack then
>> off the end of the BSS?
> 
> I'm bad at maths so decided to test this impirically by compiling the macros up
> into a test program and spitting out the values for all supported combinations
> of page size and va bits:
> 
> PAGE_SHIFT=12:
>   VA_BITS=52:
>     INIT_DIR_SIZE=53248
>     INIT_IDMAP_DIR_SIZE=32768
>     INIT_IDMAP_FDT_SIZE=24576
>   VA_BITS=48:
>     INIT_DIR_SIZE=45056
>     INIT_IDMAP_DIR_SIZE=32768
>     INIT_IDMAP_FDT_SIZE=24576
>   VA_BITS=39:
>     INIT_DIR_SIZE=36864
>     INIT_IDMAP_DIR_SIZE=32768
>     INIT_IDMAP_FDT_SIZE=24576
> PAGE_SHIFT=14:
>   VA_BITS=52:
>     INIT_DIR_SIZE=131072
>     INIT_IDMAP_DIR_SIZE=131072
>     INIT_IDMAP_FDT_SIZE=98304
>   VA_BITS=48:
>     INIT_DIR_SIZE=131072
>     INIT_IDMAP_DIR_SIZE=131072
>     INIT_IDMAP_FDT_SIZE=98304
>   VA_BITS=47:
>     INIT_DIR_SIZE=98304      <<< TOO SMALL!
>     INIT_IDMAP_DIR_SIZE=131072
>     INIT_IDMAP_FDT_SIZE=98304
>   VA_BITS=36:
>     INIT_DIR_SIZE=65536      <<< TOO SMALL!
>     INIT_IDMAP_DIR_SIZE=131072
>     INIT_IDMAP_FDT_SIZE=98304
> PAGE_SHIFT=16:
>   VA_BITS=52:
>     INIT_DIR_SIZE=327680
>     INIT_IDMAP_DIR_SIZE=327680
>     INIT_IDMAP_FDT_SIZE=262144
>   VA_BITS=48:
>     INIT_DIR_SIZE=327680
>     INIT_IDMAP_DIR_SIZE=327680
>     INIT_IDMAP_FDT_SIZE=262144
>   VA_BITS=42:
>     INIT_DIR_SIZE=196608     <<< TOO SMALL!
>     INIT_IDMAP_DIR_SIZE=327680
>     INIT_IDMAP_FDT_SIZE=262144
> 
> There are 3 configurations where the space allocated in BSS for the init_pg_dir
> is smaller than the space required for the init_idmap_pg_dir. So I think there
> is definitely a problem here?
> 
> As I said, I'm doing work in this area at the moment, so propose to send some
> patches to fix this by ensuring the space allocated for init_pg_dir is
> MAX(INIT_DIR_SIZE, INIT_IDMAP_DIR_SIZE). I'm also going to track the limit of
> the buffer that is being allocated from so we can runtime check we don't
> overflow. Shout if you disagree.

There's no bug here; We only take this path if LPA2 is enabled, and LPA2 is only
enabled if 52-bit VA is configured. So in that case, init_pg_dir must be at
least as big as init_idmap_pg_dir and can definitely hold the 48 bit VA init idmap.

Sorry for the noise.


> 
> Thanks,
> Ryan
> 
> 
>>
>> Thanks,
>> Ryan
>>
>>> +	dsb(ishst);
>>> +	set_ttbr0_for_lpa2((u64)init_pg_dir);
>>> +
>>> +	/*
>>> +	 * Recreate the initial ID map with the same granularity as before.
>>> +	 * Don't bother with the FDT, we no longer need it after this.
>>> +	 */
>>> +	memset(init_idmap_pg_dir, 0,
>>> +	       (u64)init_idmap_pg_dir - (u64)init_idmap_pg_end);
>>> +
>>> +	create_init_idmap(init_idmap_pg_dir, mask);
>>> +	dsb(ishst);
>>> +
>>> +	/* switch back to the updated initial ID map */
>>> +	set_ttbr0_for_lpa2((u64)init_idmap_pg_dir);
>>> +
>>> +	/* wipe the temporary ID map from memory */
>>> +	memset(init_pg_dir, 0, (u64)init_pg_end - (u64)init_pg_dir);
>>> +}
>>
> 




More information about the linux-arm-kernel mailing list