[coreboot-gerrit] Change in ...coreboot[master]: arch/x86/mmu: Port armv8 MMU to x86_64

Patrick Rudolph (Code Review) gerrit at coreboot.org
Sun Dec 9 14:37:54 CET 2018


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 at 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

-- 
To view, visit https://review.coreboot.org/c/coreboot/+/30119
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings

Gerrit-Project: coreboot
Gerrit-Branch: master
Gerrit-Change-Id: I6e8b46e65925823a84b8ccd647c7d6848aa20992
Gerrit-Change-Number: 30119
Gerrit-PatchSet: 1
Gerrit-Owner: Patrick Rudolph <siro at das-labor.org>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.coreboot.org/pipermail/coreboot-gerrit/attachments/20181209/a7a86bc9/attachment-0001.html>


More information about the coreboot-gerrit mailing list