Attention is currently required from: Jérémy Compostella.
Benjamin Doron has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/79161?usp=email )
Change subject: arch/x86/mmu: Port armv8 MMU to x86_64 ......................................................................
arch/x86/mmu: Port armv8 MMU to x86_64
Add functions to set up page tables for long mode. In addition generate new page tables where necessary: - before CBMEM setup, if CBMEM is above 4GiB - after CBMEM setup, if CBMEM is above 4GiB - at end of BS_DEV_RESOURCES in CBMEM
At end of BS_DEV_RESOURCES the memory map is fully known and the page tables can be properly generated based on the memory resources.
This allows the CPU to access all DRAM and MMIO even beyond 4GiB.
Right now there's no use case for this, but the code is necessary to: - Load stages above 4GiB - Load payloads above 4GiB - Install tables (like CBMEM/ACPI/SMBIOS) above 4GiB - allow coreboot PCI drivers to access BARs mapped above 4GiB
Tested on prodrive/hermes: Still boots to payload. Doesn't affect existing x86_32 code.
Change-Id: I6e8b46e65925823a84b8ccd647c7d6848aa20992 Signed-off-by: Patrick Rudolph siro@das-labor.org --- M src/arch/x86/Kconfig M src/arch/x86/Makefile.inc A src/arch/x86/include/arch/mmu.h A src/arch/x86/mmu-ramstage.c A src/arch/x86/mmu-romstage.c A src/arch/x86/mmu.c M src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h M src/lib/imd_cbmem.c 8 files changed, 636 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/61/79161/1
diff --git a/src/arch/x86/Kconfig b/src/arch/x86/Kconfig index 90ece988..fc35f34 100644 --- a/src/arch/x86/Kconfig +++ b/src/arch/x86/Kconfig @@ -92,6 +92,12 @@ The position where to place pagetables. Needs to be known at compile time. Must not overlap other files in CBFS.
+config ARCH_X86_MMU_PAGE_TABLES + bool + default y if ARCH_ALL_STAGES_X86_64 + help + Enable support for building page tables at runtime. + config RESERVED_PHYSICAL_ADDRESS_BITS_SUPPORT bool help diff --git a/src/arch/x86/Makefile.inc b/src/arch/x86/Makefile.inc index 75b9d3d..9048e5e 100644 --- a/src/arch/x86/Makefile.inc +++ b/src/arch/x86/Makefile.inc @@ -277,6 +277,9 @@ ramstage-$(CONFIG_COLLECT_TIMESTAMPS_TSC) += timestamp.c ramstage-$(CONFIG_HAVE_ACPI_RESUME) += wakeup.S ramstage-$(CONFIG_HAVE_CF9_RESET) += cf9_reset.c +all-$(CONFIG_ARCH_X86_MMU_PAGE_TABLES) += mmu.c +ramstage-$(CONFIG_ARCH_RAMSTAGE_X86_64) += mmu-ramstage.c +romstage-$(CONFIG_ARCH_ROMSTAGE_X86_64) += mmu-romstage.c
rmodules_x86_32-y += memcpy.c rmodules_x86_32-y += memmove_32.c diff --git a/src/arch/x86/include/arch/mmu.h b/src/arch/x86/include/arch/mmu.h new file mode 100644 index 0000000..759338e --- /dev/null +++ b/src/arch/x86/include/arch/mmu.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __ARCH_X86_MMU_H__ +#define __ARCH_X86_MMU_H__ + +#include <types.h> + +/* Memory attributes for mmap regions + * These attributes act as tag values for memrange regions + */ + +#define PTE_PRES (1ULL << 0) +#define PTE_RW (1ULL << 1) +#define PTE_US (1ULL << 2) +#define PTE_PWT (1ULL << 3) +#define PTE_PCD (1ULL << 4) +#define PTE_A (1ULL << 5) +#define PTE_D (1ULL << 6) +#define PTE_PAT (1ULL << 7) +#define PTE_G (1ULL << 8) +#define PTE_AVAL (7ULL << 9) +#define PTE_XD (1ULL << 63) +#define PDE_PS (1ULL << 7) + +#define BLOCK_INDEX_MASK (PTE_PWT | PTE_PCD | PTE_PRES | PTE_RW | \ + PTE_A | PTE_D) +#define BLOCK_INDEX_SHIFT 0 +#define BLOCK_INDEX_MEM_NORMAL (PTE_PRES | PTE_RW) + +/* Descriptor attributes */ + +#define AVAIL_SHIFT 9 +#define AVAIL_MASK (7 << AVAIL_SHIFT) +#define AVAIL_INVAL 0 +#define AVAIL_TABLE 3 +#define AVAIL_PAGE 2 +#define AVAIL_BLOCK 1 + +#define DESC_MASK (AVAIL_MASK | PTE_PRES | PTE_RW | PTE_A | PTE_D | \ + PTE_US | PDE_PS) +#define INVALID_DESC 0 +#define TABLE_DESC ((AVAIL_TABLE << AVAIL_SHIFT) | PTE_PRES | PTE_RW | \ + PTE_US | PTE_A | PTE_D) +#define PAGE_DESC ((AVAIL_PAGE << AVAIL_SHIFT) | PTE_PRES | PTE_RW | \ + PTE_US | PTE_A | PTE_D) +#define BLOCK_DESC ((AVAIL_BLOCK << AVAIL_SHIFT) | PTE_PRES | PTE_RW | \ + PTE_US | PTE_A | PTE_D | PDE_PS) + +/* Sentinel descriptor to mark first PTE of an unused table. It must be a value + * that cannot occur naturally as part of a page table. (Bits [1:0] = 0b00 makes + * this an unmapped page, but some page attribute bits are still set.) */ +#define UNUSED_DESC 0x6EbAAD0BBADbA000ULL + +/* XLAT Table Init Attributes */ + +#define VA_START 0x0 +#define BITS_PER_VA 48 +/* Granule size of 4KB is being used */ +#define GRANULE_SIZE_SHIFT 12 +#define GRANULE_SIZE (1 << GRANULE_SIZE_SHIFT) +#define XLAT_ADDR_MASK ((1ULL << BITS_PER_VA) - GRANULE_SIZE) +#define GRANULE_SIZE_MASK ((1ULL << GRANULE_SIZE_SHIFT) - 1) + +#define BITS_RESOLVED_PER_LVL (GRANULE_SIZE_SHIFT - 3) +#define L0_ADDR_SHIFT (GRANULE_SIZE_SHIFT + BITS_RESOLVED_PER_LVL * 3) +#define L1_ADDR_SHIFT (GRANULE_SIZE_SHIFT + BITS_RESOLVED_PER_LVL * 2) +#define L2_ADDR_SHIFT (GRANULE_SIZE_SHIFT + BITS_RESOLVED_PER_LVL * 1) +#define L3_ADDR_SHIFT (GRANULE_SIZE_SHIFT + BITS_RESOLVED_PER_LVL * 0) + +#define L0_ADDR_MASK (((1ULL << BITS_RESOLVED_PER_LVL) - 1) << L0_ADDR_SHIFT) +#define L1_ADDR_MASK (((1ULL << BITS_RESOLVED_PER_LVL) - 1) << L1_ADDR_SHIFT) +#define L2_ADDR_MASK (((1ULL << BITS_RESOLVED_PER_LVL) - 1) << L2_ADDR_SHIFT) +#define L3_ADDR_MASK (((1ULL << BITS_RESOLVED_PER_LVL) - 1) << L3_ADDR_SHIFT) + +/* These macros give the size of the region addressed by each entry of a xlat + table at any given level */ +#define L3_XLAT_SIZE (1ULL << L3_ADDR_SHIFT) +#define L2_XLAT_SIZE (1ULL << L2_ADDR_SHIFT) +#define L1_XLAT_SIZE (1ULL << L1_ADDR_SHIFT) +#define L0_XLAT_SIZE (1ULL << L0_ADDR_SHIFT) + +#define PAT_UC 0 +#define PAT_WC 1 +#define PAT_WT 4 +#define PAT_WP 5 +#define PAT_WB 6 +#define PAT_UC_MINUS 7 + +void mmu_init_pre_cbmem(void); + +struct region; +void mmu_init_region(struct region *r); +/* Initialize MMU registers and page table memory region. */ +void mmu_init(void); +/* Install page tables */ +void mmu_enable(void); + +void mmu_config_range(const uint64_t start, const uint64_t size, + const uint64_t tag); +#endif /* __ARCH_X86_MMU_H__ */ diff --git a/src/arch/x86/mmu-ramstage.c b/src/arch/x86/mmu-ramstage.c new file mode 100644 index 0000000..420b13b --- /dev/null +++ b/src/arch/x86/mmu-ramstage.c @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <assert.h> +#include <arch/mmu.h> +#include <bootstate.h> +#include <cpu/cpu.h> +#include <console/console.h> +#include <device/resource.h> +#include <symbols.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <commonlib/region.h> + +static void mmu_init_ranges(void *gp, struct device *dev, struct resource *res) +{ + if (!res->size || !IS_ALIGNED(res->size, 4 * KiB)) + return; + + const int addr_bits = MIN(cpu_phys_address_size(), BITS_PER_VA); + const uint64_t limit = 1ULL << addr_bits; + + if (res->size >= limit || res->base >= limit || (res->size + res->size) >= limit) + return; + + if (res->flags & IORESOURCE_CACHEABLE) + mmu_config_range(res->base, res->size, PAT_WB); + else if (res->flags & IORESOURCE_PREFETCH) + mmu_config_range(res->base, res->size, PAT_WT); + else if (res->flags & IORESOURCE_READONLY) + mmu_config_range(res->base, res->size, PAT_WP); + + /* Default mapping is UC, skip it */ +} + +static void mmu_setup_all_resources(void *unused) +{ + mmu_init(); + + /* 4 level paging only supports 48 bit */ + const int addr_bits = MIN(cpu_phys_address_size(), BITS_PER_VA); + + /* Mark the default range as UC. */ + mmu_config_range(0, (1ULL * GiB) << MAX(addr_bits - 30, 0), PAT_UC); + + search_global_resources(IORESOURCE_MEM | IORESOURCE_ASSIGNED, + IORESOURCE_MEM | IORESOURCE_ASSIGNED, mmu_init_ranges, 0); + + mmu_enable(); +} + +BOOT_STATE_INIT_ENTRY(BS_DEV_ENABLE, BS_ON_EXIT, mmu_setup_all_resources, NULL); diff --git a/src/arch/x86/mmu-romstage.c b/src/arch/x86/mmu-romstage.c new file mode 100644 index 0000000..426f241 --- /dev/null +++ b/src/arch/x86/mmu-romstage.c @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <arch/mmu.h> +#include <console/console.h> +#include <cbmem.h> +#include <stdint.h> +#include <string.h> +#include <commonlib/region.h> + +/* Need 9 pages table entries in the worst case. See mmu_setup_default_map + * for details. */ +static u8 __aligned(4096) page_tables_cbmem_setup[9 * 4 * KiB]; + +static bool installed_tmp_table; + +static void mmu_setup_default_map(void) +{ + const uintptr_t top = (uintptr_t)cbmem_top(); + size_t len; + + /* Need 1 page table entry for PM4LE */ + + /* Need (1 PDPE + 4 PDE) entries for 0-4GiB in the worst case */ + mmu_config_range(0, 4ULL * GiB, PAT_WB); + + /* Need (1 PDPE + 2 PDE) entries for 1 GiB range in the worst case */ + len = 1 * GiB + (top & (2 * MiB - 1)); + + /* Align to 2MiB page size to reduce page table usage. */ + mmu_config_range(ALIGN_DOWN(top, 2 * MiB) - len, len, PAT_WB); +} + +/* Create a temporary page table structure in page_tables_cbmem_setup, + * to be able to install cbmem root structure. As soon as cbmem is initialized + * it will switch to page tables in cbmem. + */ +void mmu_init_pre_cbmem(void) +{ + struct region r; + uintptr_t top = (uintptr_t)cbmem_top(); + + if (top < 4ULL * GiB) { + /* Page tables in ROM identity map everything in 32bit address space. + There's no need to install additional page tables to access cbmem. */ + printk(BIOS_DEBUG, "MMU: Skipping pre-cbmem init\n"); + return; + } + + installed_tmp_table = true; + + printk(BIOS_DEBUG, "MMU: Setup pagetables to map CBMEM at 0x%zx\n", top); + + r.offset = (uintptr_t)page_tables_cbmem_setup; + r.size = sizeof(page_tables_cbmem_setup); + mmu_init_region(&r); + + mmu_setup_default_map(); + + mmu_enable(); + + printk(BIOS_DEBUG, "MMU: Setup done\n"); +} + +/* Migrate page table into cbmem as the heap allocation is in CAR and will + * be invalid as soon as postcar stage loads. + */ +static void mmu_hook(int is_recovery) +{ + struct region r; + void *ptr; + + if (installed_tmp_table) { + printk(BIOS_INFO, "MMU: Switching to CBMEM page tables\n"); + ptr = (uint64_t *)cbmem_add(CBMEM_ID_PAGE_RAM, 10 * 4 * KiB); + if (!ptr) + die("Failed to allocate page tables in cbmem\n"); + + /* Page table directories must be aligned to page size */ + r.offset = ALIGN_UP((uintptr_t)ptr, 4 * KiB); + r.size = 9 * 4 * KiB; + mmu_init_region(&r); + mmu_setup_default_map(); + mmu_enable(); + } +} + +CBMEM_CREATION_HOOK(mmu_hook); diff --git a/src/arch/x86/mmu.c b/src/arch/x86/mmu.c new file mode 100644 index 0000000..d094528 --- /dev/null +++ b/src/arch/x86/mmu.c @@ -0,0 +1,379 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <assert.h> +#include <arch/mmu.h> +#include <cpu/cpu.h> +#include <console/console.h> +#include <cpu/x86/cr.h> +#include <cbmem.h> +#include <device/resource.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <commonlib/region.h> + +/* This just caches the next free table slot (okay to do since they fill up from + * bottom to top and can never be freed up again). It will reset to its initial + * value on stage transition, so we still need to check it for UNUSED_DESC. */ +static uint64_t *next_free_table; +static uint64_t *page_tables; +static uint64_t *epage_tables; + +static void print_tag(const int level, const uint64_t pat) +{ + switch (pat) { + case PAT_UC: + printk(level, "UC\n"); + break; + case PAT_WC: + printk(level, "WC\n"); + break; + case PAT_WT: + printk(level, "WT\n"); + break; + case PAT_WP: + printk(level, "WP\n"); + break; + case PAT_WB: + printk(level, "WB\n"); + break; + case PAT_UC_MINUS: + printk(level, "UC-\n"); + break; + default: + break; + } +} + +static uint64_t pte_pat_flags(const unsigned long pat) +{ + switch (pat) { + case PAT_UC: + return 0 | PTE_PCD | PTE_PWT; + case PAT_WC: + return 0 | 0 | PTE_PWT; + case PAT_WT: + return PTE_PAT | PTE_PCD | PTE_PWT; + case PAT_WP: + return PTE_PAT | 0 | PTE_PWT; + case PAT_WB: + return 0 | 0 | 0; + case PAT_UC_MINUS: + return 0 | PTE_PCD | 0; + default: + printk(BIOS_ERR, "MMU: PTE PAT defaulting to WB: %lx\n", pat); + return 0 | 0 | 0; + } +} + +/* Func : get_block_attr + * Desc : Get block descriptor attributes based on the value of tag in memrange + * region + */ +static uint64_t get_block_attr(const unsigned long tag) +{ + uint64_t flags = pte_pat_flags(tag); + if (flags & PTE_PAT) { + /* PAT is at position 12 instead of 7 */ + flags &= ~PTE_PAT; + flags |= (PTE_PAT << 5); + } + flags |= BLOCK_DESC; + + return flags; +} + +/* Func : get_page_attr + * Desc : Get page descriptor attributes based on the value of tag in memrange + * region + */ +static uint64_t get_page_attr(const unsigned long tag) +{ + uint64_t flags = pte_pat_flags(tag); + flags |= PAGE_DESC; + return flags; +} + +/** + * Desc : Get next free table from pagetable and set it up to match + * old parent entry. + */ +static uint64_t *setup_new_table(uint64_t desc, const uint64_t xlat_size) +{ + while (next_free_table[0] != UNUSED_DESC) { + next_free_table += GRANULE_SIZE / sizeof(*next_free_table); + if (epage_tables - next_free_table <= 0) + die("Ran out of page table space!"); + } + + uint64_t frame_base = desc & XLAT_ADDR_MASK; + printk(BIOS_DEBUG, + "MMU: Backing address range [0x%llx:0x%llx) with " + "new page table @%p\n", + frame_base, frame_base + (xlat_size << BITS_RESOLVED_PER_LVL), next_free_table); + + if (desc == INVALID_DESC) { + memset(next_free_table, 0, GRANULE_SIZE); + } else { + /* Can reuse old parent entry, but may need to adjust type. */ + if (xlat_size == L3_XLAT_SIZE) + desc |= PAGE_DESC; + + int i = 0; + for (; i < GRANULE_SIZE / sizeof(*next_free_table); i++) { + next_free_table[i] = desc; + desc += xlat_size; + } + } + + return next_free_table; +} + +/** + * Desc: Check if the table entry is a valid descriptor. If not, initialize new + * table, update the entry and return the table addr. If valid, return the addr + */ +static uint64_t *get_next_level_table(uint64_t *ptr, size_t xlat_size) +{ + uint64_t desc = *ptr; + + if ((desc & DESC_MASK) != TABLE_DESC) { + uint64_t *new_table = setup_new_table(desc, xlat_size); + desc = ((uintptr_t)new_table) | TABLE_DESC; + *ptr = desc; + } + return (uint64_t *)(uintptr_t)(desc & XLAT_ADDR_MASK); +} + +/** + * Desc : Given a base address and size, it identifies the indices within + * different level XLAT tables which map the given base addr. Similar to table + * walk, except that all invalid entries during the walk are updated + * accordingly. On success, it returns the size of the block/page addressed by + * the final table. + * The table names l0 to l3 are just shorter names for the x86 page tables. + * + * The mapping is as the following: + * l0: PM4LE + * l1: PDPE + * l2: PDE + * l3: PTE + */ +static uint64_t init_xlat_table(const uint64_t base_addr, const uint64_t size, + const uint64_t tag, const bool hugepage_1gb) +{ + uint64_t l0_index = (base_addr & L0_ADDR_MASK) >> L0_ADDR_SHIFT; + uint64_t l1_index = (base_addr & L1_ADDR_MASK) >> L1_ADDR_SHIFT; + uint64_t l2_index = (base_addr & L2_ADDR_MASK) >> L2_ADDR_SHIFT; + uint64_t l3_index = (base_addr & L3_ADDR_MASK) >> L3_ADDR_SHIFT; + uint64_t *table = page_tables; + uint64_t desc; + + /* L0 entry stores a table descriptor (doesn't support blocks) */ + table = get_next_level_table(&table[l0_index], L1_XLAT_SIZE); + + /* L1 table lookup */ + if ((size >= L1_XLAT_SIZE) && IS_ALIGNED(base_addr, (1UL << L1_ADDR_SHIFT)) + && hugepage_1gb) { + /* If block address is aligned and size is greater than + * or equal to size addressed by each L1 entry, we can + * directly store a block desc */ + desc = base_addr | get_block_attr(tag); + table[l1_index] = desc; + /* L2 lookup is not required */ + return L1_XLAT_SIZE; + } + + /* L1 entry stores a table descriptor */ + table = get_next_level_table(&table[l1_index], L2_XLAT_SIZE); + + /* L2 table lookup */ + if ((size >= L2_XLAT_SIZE) && IS_ALIGNED(base_addr, (1UL << L2_ADDR_SHIFT))) { + /* If block address is aligned and size is greater than + * or equal to size addressed by each L2 entry, we can + * directly store a block desc */ + desc = base_addr | get_block_attr(tag); + table[l2_index] = desc; + /* L3 lookup is not required */ + return L2_XLAT_SIZE; + } + + /* L2 entry stores a table descriptor */ + table = get_next_level_table(&table[l2_index], L3_XLAT_SIZE); + + /* L3 table lookup */ + desc = base_addr | get_page_attr(tag); + table[l3_index] = desc; + return L3_XLAT_SIZE; +} + +/** + * Desc : Check address/size alignment of a table or page. + */ +static void sanity_check(const uint64_t addr, const uint64_t size) +{ + assert(!(addr & GRANULE_SIZE_MASK) && !(size & GRANULE_SIZE_MASK) + && (addr + size < (1ULL << BITS_PER_VA)) && size >= GRANULE_SIZE); +} + +/** + * Desc : Returns the page table entry governing a specific address. + */ +static uint64_t get_pte(const uint64_t addr) +{ + int shift = L0_ADDR_SHIFT; + uint64_t *pte = page_tables; + + while (1) { + int index = ((uintptr_t)addr >> shift) & ((1ULL << BITS_RESOLVED_PER_LVL) - 1); + + if ((pte[index] & DESC_MASK) != TABLE_DESC || shift <= GRANULE_SIZE_SHIFT) + return pte[index]; + + pte = (uint64_t *)(uintptr_t)(pte[index] & XLAT_ADDR_MASK); + shift -= BITS_RESOLVED_PER_LVL; + } +} + +/** + * Desc : Asserts that mapping for addr matches the access type used by the + * page table walk (i.e. addr is correctly mapped to be part of the pagetable). + */ +static void assert_correct_pagetable_mapping(const uint64_t addr) +{ + return; + + uint64_t pte = get_pte(addr); + assert(((pte >> BLOCK_INDEX_SHIFT) & BLOCK_INDEX_MASK) == BLOCK_INDEX_MEM_NORMAL); +} + +static inline bool cpu_supports_1gb_hugepage(void) +{ + static bool checked, supports; + if (checked) + return supports; + + /* check if CPU supports 1GB hugepages */ + if (cpu_cpuid_extended_level() >= 0x80000001) + supports = !!(cpuid_edx(0x80000001) & (1 << 26)); + checked = true; + + return supports; +} + +/** + * @brief Configure memory range for long mode + * + * Desc : This function repeatedly calls init_xlat_table with the base + * address. Based on size returned from init_xlat_table, base_addr is updated + * and subsequent calls are made for initializing the xlat table until the whole + * region is initialized. + */ +void mmu_config_range(const uint64_t start, const uint64_t size, const uint64_t tag) +{ + uint64_t base_addr = start; + uint64_t temp_size = size; + + printk(BIOS_INFO, "MMU: Mapping address range [0x%llx:0x%llx) as ", start, + start + size); + print_tag(BIOS_INFO, tag); + + sanity_check(base_addr, temp_size); + + const bool hp = cpu_supports_1gb_hugepage(); + while (temp_size) + temp_size -= + init_xlat_table(base_addr + (size - temp_size), temp_size, tag, hp); +} + + +/** + * @brief Init a region with page tables to be used with the MMU. + * Must be called onced with an inactive (from MMU PoV) region before any call to + * - mmu_config_range + * - mmu_setup_default_map + * - mmu_enable + * + */ +void mmu_init_region(struct region *const r) +{ + printk(BIOS_DEBUG, "MMU: Init page table region\n"); + + /* Page table directories must be aligned to page size */ + page_tables = (uint64_t *)ALIGN_UP((uintptr_t)region_offset(r), 4 * KiB); + + /* Can't operate on page tables that are currently active! */ + assert((uintptr_t)page_tables != read_cr3()); + + next_free_table = page_tables; + epage_tables = (void *)region_end(r); + + /* Initially mark all table slots unused (first PTE == UNUSED_DESC). */ + uint64_t *table = page_tables; + for (; epage_tables - table > 0; table += GRANULE_SIZE / sizeof(*table)) + table[0] = UNUSED_DESC; + + printk(BIOS_DEBUG, "MMU: Initialize PM4LE\n"); + + /* Initialize the root table (PM4LE) to be completely unmapped. */ + uint64_t *root = setup_new_table(INVALID_DESC, L0_XLAT_SIZE); + assert(root == page_tables); + + printk(BIOS_DEBUG, "MMU: Page table region initialized\n"); +} + +/** + * @brief Init MMU for long mode + * + */ +void mmu_init(void) +{ + int pages, addr_bits; + struct region r; + void *ptr; + printk(BIOS_DEBUG, "MMU: Setup\n"); + + /* 4 level paging only supports 48 bit */ + addr_bits = MIN(cpu_phys_address_size(), BITS_PER_VA); + printk(BIOS_DEBUG, "MMU: CPU has %d address bits\n", addr_bits); + + const bool hp = cpu_supports_1gb_hugepage(); + printk(BIOS_DEBUG, "MMU: CPU %s 1GB hugepages\n", hp ? "supports" : "doesn't support"); + + if (hp) { + pages = 1; /* PML4E */ + const int PDPTs = 1 << MAX(addr_bits - L0_ADDR_SHIFT, 0); + pages += PDPTs; /* PDPT with 1 GiB pages */ + } else { + pages = 1; /* PML4E */ + const int PDPTs = 1 << MAX(addr_bits - L0_ADDR_SHIFT, 0); + pages += PDPTs; /* PDPT pointing to 1 GiB PLEs */ + const int PLEs = 1 << MAX(addr_bits - L1_ADDR_SHIFT, 0); + pages += PLEs; /* PLE with 2 MiB pages */ + } + printk(BIOS_DEBUG, "MMU: Need %d 4k pages for identity mapping\n", pages); + + /* Overallocate page table space for more fine grained memory map */ + pages += 256; + printk(BIOS_DEBUG, "MMU: Allocating %d 4k pages for mapping\n", pages); + + /* Add one for alignment */ + ptr = (uint64_t *)cbmem_add(CBMEM_ID_PAGE_RAM, (pages + 1) * 4 * KiB); + + /* Page table directories must be aligned to page size */ + r.offset = ALIGN_UP((uintptr_t)ptr, 4 * KiB); + r.size = pages * 4 * KiB; + mmu_init_region(&r); + + printk(BIOS_DEBUG, "MMU: Setup done\n"); +} + +void mmu_enable(void) +{ + assert_correct_pagetable_mapping((uintptr_t)page_tables); + printk(BIOS_DEBUG, "MMU: Activating new page tables\n"); + + /* Load the new page table address */ + write_cr3((uintptr_t)page_tables); + printk(BIOS_INFO, "MMU: Installed new page tables\n"); +} diff --git a/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h b/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h index 3536c46..ba5fa78 100644 --- a/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h +++ b/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h @@ -46,6 +46,8 @@ #define CBMEM_ID_NONE 0x00000000 #define CBMEM_ID_PIRQ 0x49525154 #define CBMEM_ID_POWER_STATE 0x50535454 +#define CBMEM_ID_PAGE_ROM 0x50414745 +#define CBMEM_ID_PAGE_RAM 0x50475432 #define CBMEM_ID_RAM_OOPS 0x05430095 #define CBMEM_ID_RAMSTAGE 0x9a357a9e #define CBMEM_ID_RAMSTAGE_CACHE 0x9a3ca54e @@ -131,6 +133,8 @@ { CBMEM_ID_MTC, "MTC " }, \ { CBMEM_ID_PIRQ, "IRQ TABLE " }, \ { CBMEM_ID_POWER_STATE, "POWER STATE" }, \ + { CBMEM_ID_PAGE_ROM, "PAGETABLE" }, \ + { CBMEM_ID_PAGE_RAM, "PAGETABLE" }, \ { CBMEM_ID_RAM_OOPS, "RAMOOPS " }, \ { CBMEM_ID_RAMSTAGE_CACHE, "RAMSTAGE $ " }, \ { CBMEM_ID_RAMSTAGE, "RAMSTAGE " }, \ diff --git a/src/lib/imd_cbmem.c b/src/lib/imd_cbmem.c index 91c8621..5c85632 100644 --- a/src/lib/imd_cbmem.c +++ b/src/lib/imd_cbmem.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */
+#include <arch/mmu.h> #include <assert.h> #include <boot/coreboot_tables.h> #include <bootmem.h> @@ -53,6 +54,10 @@ if (!ENV_CREATES_CBMEM) return;
+ /* Make cbmem visible in page tables */ + if (ENV_X86_64) + mmu_init_pre_cbmem(); + /* The test is only effective on X86 and when address hits UC memory. */ if (ENV_X86) quick_ram_check_or_die((uintptr_t)cbmem_top() - sizeof(u32));