From 99d409d3ba348aeebeb02288b62c97cd8cb336ee Mon Sep 17 00:00:00 2001 From: Kapil Porwal Date: Fri, 20 Feb 2026 12:16:26 +0530 Subject: [PATCH] arch/arm64: Add support for TTB relocation to DRAM When transitioning between boot stages, it is often necessary to move the Translation Table Base (TTB) from a temporary pre-RAM location to a permanent post-RAM region. ARM64 page tables contain absolute physical addresses for lower-level tables. When moving the TTB from one base address to another, these internal pointers must be adjusted. Implement mmu_relocate_ttb() to handle this transition. The logic copies the tables from _preram_ttb to _postram_ttb and performs a fixup on all descriptors to reflect the new offset. This ensures memory mapping remains consistent after the TTB base changes. BUG=b:436391478 TEST=Verify successful TTB relocation and stable MMU state on Quartz. Change-Id: I7fdd69bfa82fc3dae919693f4d5d0314687cbef9 Signed-off-by: Kapil Porwal Reviewed-on: https://review.coreboot.org/c/coreboot/+/91367 Tested-by: build bot (Jenkins) Reviewed-by: Subrata Banik Reviewed-by: Julius Werner --- src/arch/arm64/armv8/mmu.c | 81 +++++++++++++++++++++++-- src/arch/arm64/include/armv8/arch/mmu.h | 2 + 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/arch/arm64/armv8/mmu.c b/src/arch/arm64/armv8/mmu.c index 65d0f92e5d..044dd0cfbe 100644 --- a/src/arch/arm64/armv8/mmu.c +++ b/src/arch/arm64/armv8/mmu.c @@ -76,16 +76,25 @@ static const char *table_level_name(size_t xlat_size) } } +static int init_next_free_table(void *start, void *end) +{ + if ((void *)next_free_table < start || (void *)next_free_table >= end) + next_free_table = start; + while (next_free_table[0] != UNUSED_DESC) { + next_free_table += GRANULE_SIZE / sizeof(*next_free_table); + if ((void *)next_free_table >= end) + return -1; + } + return 0; +} + /* Func : setup_new_table * Desc : Get next free table from TTB and set it up to match old parent entry. */ static uint64_t *setup_new_table(uint64_t desc, size_t xlat_size) { - while (next_free_table[0] != UNUSED_DESC) { - next_free_table += GRANULE_SIZE/sizeof(*next_free_table); - if (_ettb - (u8 *)next_free_table <= 0) - die("Ran out of page table space!"); - } + if (init_next_free_table(_ttb, _ettb)) + die("Ran out of page table space!"); void *frame_base = (void *)(desc & XLAT_ADDR_MASK); const char *level_name = table_level_name(xlat_size); @@ -243,6 +252,7 @@ void mmu_config_range(void *start, size_t size, uint64_t tag) print_tag(BIOS_INFO, tag); sanity_check(base_addr, temp_size); + assert(raw_read_ttbr0() == (uint64_t)_ttb); while (temp_size) temp_size -= init_xlat_table(base_addr + (size - temp_size), @@ -326,3 +336,64 @@ void mmu_enable(void) isb(); } + +/* + * Func : mmu_relocate_ttb + * Desc : Relocates a Translation Table Base (TTB) from _preram_ttb + * to _postram_ttb. This involves copying the table data and updating + * all internal pointers within the translation table entries to reflect + * the new location. + */ +void mmu_relocate_ttb(void) +{ + const uintptr_t old_base = (uintptr_t)_preram_ttb; + const uintptr_t new_base = (uintptr_t)_postram_ttb; + const uintptr_t offset = new_base - old_base; + + ASSERT_MSG(offset, "TTB relocation is not required.\n"); + + /* Ensure that next_free_table pointer is correct. */ + init_next_free_table(_preram_ttb, _epreram_ttb); + + const size_t used_size = (uintptr_t)next_free_table - old_base; + uint64_t *entry; + uint64_t *end = (uint64_t *)(new_base + used_size); + + printk(BIOS_INFO, "Relocating TTB: 0x%lx -> 0x%lx (offset 0x%lx)\n", + old_base, new_base, offset); + + /* Copy the used TTB region to the new location. */ + memcpy((void *)new_base, (void *)old_base, used_size); + + /* + * Update all internal table pointers. Since the entire TTB region is + * moved as a single block, every table pointer inside the descriptors + * needs to be adjusted by the same relative offset. + */ + for (entry = (uint64_t *)new_base; entry < end; entry++) { + /* + * In ARMv8, a descriptor with bits [1:0] == 0b11 can be either a + * Table descriptor or a Page descriptor. To differentiate, we + * check the Access Flag (bit 10). Table descriptors do not use + * this bit, whereas we ensure all Page/Block descriptors have it + * set. This relies on TCR.HAFT[42] remaining 0. + */ + if ((*entry & DESC_MASK) == TABLE_DESC && !(*entry & BLOCK_ACCESS)) + *entry += offset; + } + + /* Mark remaining table slots unused (first PTE == UNUSED_DESC). */ + for (entry = end; (u8 *)entry < _epostram_ttb; entry += GRANULE_SIZE / sizeof(*entry)) + *entry = UNUSED_DESC; + + /* Update TTBR0 to point to the new base address. */ + dsb(); + raw_write_ttbr0((uintptr_t)new_base); + isb(); + + tlb_invalidate_all(); + dsb(); + isb(); + + printk(BIOS_INFO, "TTB relocation is complete.\n"); +} diff --git a/src/arch/arm64/include/armv8/arch/mmu.h b/src/arch/arm64/include/armv8/arch/mmu.h index 0db6d3ff28..739aa5e0b6 100644 --- a/src/arch/arm64/include/armv8/arch/mmu.h +++ b/src/arch/arm64/include/armv8/arch/mmu.h @@ -152,5 +152,7 @@ void mmu_config_range(void *start, size_t size, uint64_t tag); void mmu_enable(void); /* Disable the MMU (which also disables dcache but not icache). */ void mmu_disable(void); +/* Relocates a Translation Table Base (TTB) from _preram_ttb to _postram_ttb */ +void mmu_relocate_ttb(void); #endif /* __ARCH_ARM64_MMU_H__ */