Patrick Rudolph has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/30119
Change subject: arch/x86/mmu: Port armv8 MMU to x86_64 ......................................................................
arch/x86/mmu: Port armv8 MMU to x86_64
Add functions to setup page tables for long mode. Required to support x86_64, as the MMU is always active.
Tested on qemu.
Change-Id: I6e8b46e65925823a84b8ccd647c7d6848aa20992 Signed-off-by: Patrick Rudolph siro@das-labor.org --- M src/arch/x86/Kconfig A src/arch/x86/include/arch/mmu.h A src/arch/x86/mmu.c M src/commonlib/include/commonlib/cbmem_id.h 4 files changed, 486 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/19/30119/1
diff --git a/src/arch/x86/Kconfig b/src/arch/x86/Kconfig index 7c8371e..11389c0 100644 --- a/src/arch/x86/Kconfig +++ b/src/arch/x86/Kconfig @@ -317,6 +317,13 @@ help The number of 4KiB pages that should be pre-allocated for page tables.
+config NUM_PAGE_TABLE_PAGES + int + default 128 + depends on ARCH_X86 + help + The number of 4KiB pages that should be available for ramstage. + # Provide the interrupt handlers to every stage. Not all # stages may take advantage. config IDT_IN_EVERY_STAGE diff --git a/src/arch/x86/include/arch/mmu.h b/src/arch/x86/include/arch/mmu.h new file mode 100644 index 0000000..4cb0354 --- /dev/null +++ b/src/arch/x86/include/arch/mmu.h @@ -0,0 +1,103 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2014 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#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 + */ + +/* Descriptor attributes */ + +#define DESC_MASK 0xeaf +#define INVALID_DESC 0 +#define TABLE_DESC 0x623 +#define PAGE_DESC 0x4a3 +#define BLOCK_DESC 0x2a3 + +#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 + + +/* Block descriptor */ +#define BLOCK_NS (1 << 5) + +#define BLOCK_AP_RW (0 << 7) +#define BLOCK_AP_RO (1 << 7) + +#define BLOCK_ACCESS (1 << 10) + +#define BLOCK_XN (1ULL << 54) + +#define BLOCK_SH_SHIFT (8) +#define BLOCK_SH_NON_SHAREABLE (0 << BLOCK_SH_SHIFT) +#define BLOCK_SH_UNPREDICTABLE (1 << BLOCK_SH_SHIFT) +#define BLOCK_SH_OUTER_SHAREABLE (2 << BLOCK_SH_SHIFT) +#define BLOCK_SH_INNER_SHAREABLE (3 << BLOCK_SH_SHIFT) + +/* 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 + +/* Initialize MMU registers and page table memory region. */ +void mmu_init(void *new_page_table); +/* Install page tables */ +void mmu_enable(void *new_page_table); +void mmu_config_range(uint64_t start, uint64_t size, uint64_t tag); + +#endif /* __ARCH_X86_MMU_H__ */ diff --git a/src/arch/x86/mmu.c b/src/arch/x86/mmu.c new file mode 100644 index 0000000..39802f0 --- /dev/null +++ b/src/arch/x86/mmu.c @@ -0,0 +1,375 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <symbols.h> + +#include <console/console.h> +#include <arch/mmu.h> +#include <arch/cache.h> +#include <cpu/x86/msr.h> +#include <cbmem.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 CAR_GLOBAL uint64_t *next_free_table; +static CAR_GLOBAL uint64_t *page_tables; + +#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_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 | PTE_A | PTE_D) + +static void print_tag(int level, 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(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; + } +} + +static uint64_t *pagetable(void) +{ + return car_get_var(page_tables); +} + +static uint64_t *epagetable(void) +{ + return (uint64_t *)((u8 *)car_get_var(page_tables) + + (CONFIG_NUM_PAGE_TABLE_PAGES * 4 * KiB)); +} + +/* Func : get_block_attr + * Desc : Get block descriptor attributes based on the value of tag in memrange + * region + */ +static uint64_t get_block_attr(unsigned long tag, bool ps) +{ + uint64_t flags = PTE_PRES | PTE_RW | PTE_US | PTE_A | PTE_D; + flags |= pte_pat_flags(tag); + if (ps) { + if (flags & PTE_PAT) { + /* If PS=1 PAT is at position 12 instead of 7 */ + flags &= ~PTE_PAT; + flags |= (PTE_PAT << 5); + } + flags |= PDE_PS; + } + return flags; +} + +/* Func : setup_new_table + * 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, uint64_t xlat_size) +{ + uint64_t *nft = car_get_var(next_free_table); + while (nft[0] != UNUSED_DESC) { + car_set_var(next_free_table, car_get_var(next_free_table) + + GRANULE_SIZE/sizeof(*next_free_table)); + nft = car_get_var(next_free_table); + if (epagetable() - car_get_var(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), nft); + + if (desc == INVALID_DESC) { + memset(nft, 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++) { + nft[i] = desc; + desc += xlat_size; + } + } + + return nft; +} + +/* Func: get_next_level_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); +} + +/* + Func : init_xlat_table + * 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. + */ +static uint64_t init_xlat_table(uint64_t base_addr, + uint64_t size, + uint64_t tag) +{ + 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 = pagetable(); + uint64_t desc; + + /* check if CPU supports 1GB huge tables */ + const bool hugepage_1gb = !!(cpuid_edx(0x80000001) & (1 << 26)); + + /* 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 | BLOCK_DESC | get_block_attr(tag, 1); + 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 | BLOCK_DESC | get_block_attr(tag, 1); + 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 | PAGE_DESC | get_block_attr(tag, 0); + table[l3_index] = desc; + return L3_XLAT_SIZE; +} + +/* Func : sanity_check + * Desc : Check address/size alignment of a table or page. + */ +static void sanity_check(uint64_t addr, uint64_t size) +{ + assert(!(addr & GRANULE_SIZE_MASK) && + !(size & GRANULE_SIZE_MASK) && + (addr + size < (1ULL << BITS_PER_VA)) && + size >= GRANULE_SIZE); +} + +/* Func : get_pte + * Desc : Returns the page table entry governing a specific address. */ +static uint64_t get_pte(uint64_t addr) +{ + int shift = L0_ADDR_SHIFT; + /* Using pointer here is OK, as _pagetable must be in 32bit space */ + uint64_t *pte = pagetable(); + + 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; + } +} + +/* Func : assert_correct_pagetable_mapping + * 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(uint64_t addr) +{ + return; + + uint64_t pte = get_pte(addr); + assert(((pte >> BLOCK_INDEX_SHIFT) & BLOCK_INDEX_MASK) + == BLOCK_INDEX_MEM_NORMAL); +} + +/* Func : mmu_config_range + * 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(uint64_t start, uint64_t size, 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); + + while (temp_size) + temp_size -= init_xlat_table(base_addr + (size - temp_size), + temp_size, tag); + +} + +/** + * @brief Init MMU for long mode + * + * @param new_page_table The new area for page tables. Must not overlap with + * currently installed page tables. + * + * Can be called multiple times, as long as the new page table area does not + * overlap with page tables in use. + * If paging isn't active, there are no limitations. + */ +void mmu_init(void *new_page_table) +{ + assert(new_page_table); + assert(pagetable() != new_page_table); + + car_set_var(page_tables, new_page_table); + car_set_var(next_free_table, new_page_table); + + /* Initially mark all table slots unused (first PTE == UNUSED_DESC). */ + uint64_t *table = pagetable(); + for (; epagetable() - table > 0; table += GRANULE_SIZE/sizeof(*table)) + table[0] = UNUSED_DESC; + + printk(BIOS_DEBUG, "MMU: Initialize the root table (L0)\n"); + /* Initialize the root table (L0) to be completely unmapped. */ + uint64_t *root = setup_new_table(INVALID_DESC, L0_XLAT_SIZE); + assert(root == pagetable()); +} + +/* Func : mmu_enable + * Desc : Install page tables, enable PAE and enable long mode. + */ +void mmu_enable(void *new_page_table) +{ + assert_correct_pagetable_mapping((uintptr_t)new_page_table); + assert_correct_pagetable_mapping((uintptr_t)new_page_table + + (CONFIG_NUM_PAGE_TABLE_PAGES * 4 * KiB) - 1); + + /* Load the new page table address */ + write_cr3((uintptr_t)new_page_table); + + printk(BIOS_INFO, "MMU: Installed new page tables\n"); + + CRx_TYPE cr0; + + cr0 = read_cr0(); + if (!(cr0 & CR0_PG)) { + /* Enable paging */ + cr0 |= CR0_PG; + write_cr0(cr0); + } +} diff --git a/src/commonlib/include/commonlib/cbmem_id.h b/src/commonlib/include/commonlib/cbmem_id.h index 2e0eeb6..4a37663 100644 --- a/src/commonlib/include/commonlib/cbmem_id.h +++ b/src/commonlib/include/commonlib/cbmem_id.h @@ -46,6 +46,7 @@ #define CBMEM_ID_NONE 0x00000000 #define CBMEM_ID_PIRQ 0x49525154 #define CBMEM_ID_POWER_STATE 0x50535454 +#define CBMEM_ID_PAGE 0x50414745 #define CBMEM_ID_RAM_OOPS 0x05430095 #define CBMEM_ID_RAMSTAGE 0x9a357a9e #define CBMEM_ID_RAMSTAGE_CACHE 0x9a3ca54e