mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 10:11:27 +01:00
xen/riscv: introduce setup_initial_pages
The idea was taken from xvisor but the following changes
were done:
* Use only a minimal part of the code enough to enable MMU
* rename {_}setup_initial_pagetables functions
* add an argument for setup_initial_mapping to have
an opportunity to make set PTE flags.
* update setup_initial_pagetables function to map sections
with correct PTE flags.
* Rewrite enable_mmu() to C.
* map linker addresses range to load addresses range without
1:1 mapping. It will be 1:1 only in case when
load_start_addr is equal to linker_start_addr.
* add safety checks such as:
* Xen size is less than page size
* linker addresses range doesn't overlap load addresses
range
* Rework macros {THIRD,SECOND,FIRST,ZEROETH}_{SHIFT,MASK}
* change PTE_LEAF_DEFAULT to RW instead of RWX.
* Remove phys_offset as it is not used now
* Remove alignment of {map, pa}_start &= XEN_PT_LEVEL_MAP_MASK(0);
in setup_inital_mapping() as they should be already aligned.
Make a check that {map_pa}_start are aligned.
* Remove clear_pagetables() as initial pagetables will be
zeroed during bss initialization
* Remove __attribute__((section(".entry")) for setup_initial_pagetables()
as there is no such section in xen.lds.S
* Update the argument of pte_is_valid() to "const pte_t *p"
* Add check that Xen's load address is aligned at 4k boundary
* Refactor setup_initial_pagetables() so it is mapping linker
address range to load address range. After setup needed
permissions for specific section ( such as .text, .rodata, etc )
otherwise RW permission will be set by default.
* Add function to check that requested SATP_MODE is supported
Origin: git@github.com:xvisor/xvisor.git 9be2fdd7
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
Acked-by: Bobby Eshleman <bobbyeshleman@gmail.com>
This commit is contained in:
1 parent
efadb18dd5
commit
e66003e7be
10 files changed
+403
-1
No files matched your search
@@ -1,5 +1,6 @@
|
||||
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
|
||||
obj-y += entry.o
|
||||
obj-y += mm.o
|
||||
obj-$(CONFIG_RISCV_64) += riscv64/
|
||||
obj-y += sbi.o
|
||||
obj-y += setup.o
|
||||
|
||||
@@ -75,12 +75,24 @@
|
||||
name:
|
||||
#endif
|
||||
|
||||
#define XEN_VIRT_START _AT(UL, 0x80200000)
|
||||
#ifdef CONFIG_RISCV_64
|
||||
#define XEN_VIRT_START 0xFFFFFFFFC0000000 /* (_AC(-1, UL) + 1 - GB(1)) */
|
||||
#else
|
||||
#error "RV32 isn't supported"
|
||||
#endif
|
||||
|
||||
#define SMP_CACHE_BYTES (1 << 6)
|
||||
|
||||
#define STACK_SIZE PAGE_SIZE
|
||||
|
||||
#ifdef CONFIG_RISCV_64
|
||||
#define CONFIG_PAGING_LEVELS 3
|
||||
#define RV_STAGE1_MODE SATP_MODE_SV39
|
||||
#else
|
||||
#define CONFIG_PAGING_LEVELS 2
|
||||
#define RV_STAGE1_MODE SATP_MODE_SV32
|
||||
#endif
|
||||
|
||||
#endif /* __RISCV_CONFIG_H__ */
|
||||
/*
|
||||
* Local variables:
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef __ASM_CURRENT_H
|
||||
#define __ASM_CURRENT_H
|
||||
|
||||
#define switch_stack_and_jump(stack, fn) do { \
|
||||
asm volatile ( \
|
||||
"mv sp, %0\n" \
|
||||
"j " #fn :: "r" (stack), "X" (fn) : "memory" ); \
|
||||
unreachable(); \
|
||||
} while ( false )
|
||||
|
||||
#endif /* __ASM_CURRENT_H */
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef _ASM_RISCV_MM_H
|
||||
#define _ASM_RISCV_MM_H
|
||||
|
||||
#include <asm/page-bits.h>
|
||||
|
||||
#define pfn_to_paddr(pfn) ((paddr_t)(pfn) << PAGE_SHIFT)
|
||||
#define paddr_to_pfn(pa) ((unsigned long)((pa) >> PAGE_SHIFT))
|
||||
|
||||
void setup_initial_pagetables(void);
|
||||
|
||||
void enable_mmu(void);
|
||||
void cont_after_mmu_is_enabled(void);
|
||||
|
||||
#endif /* _ASM_RISCV_MM_H */
|
||||
@@ -4,4 +4,14 @@
|
||||
#define PAGE_SHIFT 12 /* 4 KiB Pages */
|
||||
#define PADDR_BITS 56 /* 44-bit PPN */
|
||||
|
||||
#ifdef CONFIG_RISCV_64
|
||||
#define PAGETABLE_ORDER (9)
|
||||
#else /* CONFIG_RISCV_32 */
|
||||
#define PAGETABLE_ORDER (10)
|
||||
#endif
|
||||
|
||||
#define PAGETABLE_ENTRIES (1 << PAGETABLE_ORDER)
|
||||
|
||||
#define PTE_PPN_SHIFT 10
|
||||
|
||||
#endif /* __RISCV_PAGE_BITS_H__ */
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef _ASM_RISCV_PAGE_H
|
||||
#define _ASM_RISCV_PAGE_H
|
||||
|
||||
#include <xen/const.h>
|
||||
#include <xen/types.h>
|
||||
|
||||
#include <asm/mm.h>
|
||||
#include <asm/page-bits.h>
|
||||
|
||||
#define VPN_MASK (PAGETABLE_ENTRIES - 1UL)
|
||||
|
||||
#define XEN_PT_LEVEL_ORDER(lvl) ((lvl) * PAGETABLE_ORDER)
|
||||
#define XEN_PT_LEVEL_SHIFT(lvl) (XEN_PT_LEVEL_ORDER(lvl) + PAGE_SHIFT)
|
||||
#define XEN_PT_LEVEL_SIZE(lvl) (_AT(paddr_t, 1) << XEN_PT_LEVEL_SHIFT(lvl))
|
||||
#define XEN_PT_LEVEL_MAP_MASK(lvl) (~(XEN_PT_LEVEL_SIZE(lvl) - 1))
|
||||
#define XEN_PT_LEVEL_MASK(lvl) (VPN_MASK << XEN_PT_LEVEL_SHIFT(lvl))
|
||||
|
||||
#define PTE_VALID BIT(0, UL)
|
||||
#define PTE_READABLE BIT(1, UL)
|
||||
#define PTE_WRITABLE BIT(2, UL)
|
||||
#define PTE_EXECUTABLE BIT(3, UL)
|
||||
#define PTE_USER BIT(4, UL)
|
||||
#define PTE_GLOBAL BIT(5, UL)
|
||||
#define PTE_ACCESSED BIT(6, UL)
|
||||
#define PTE_DIRTY BIT(7, UL)
|
||||
#define PTE_RSW (BIT(8, UL) | BIT(9, UL))
|
||||
|
||||
#define PTE_LEAF_DEFAULT (PTE_VALID | PTE_READABLE | PTE_WRITABLE)
|
||||
#define PTE_TABLE (PTE_VALID)
|
||||
|
||||
/* Calculate the offsets into the pagetables for a given VA */
|
||||
#define pt_linear_offset(lvl, va) ((va) >> XEN_PT_LEVEL_SHIFT(lvl))
|
||||
|
||||
#define pt_index(lvl, va) (pt_linear_offset((lvl), (va)) & VPN_MASK)
|
||||
|
||||
/* Page Table entry */
|
||||
typedef struct {
|
||||
#ifdef CONFIG_RISCV_64
|
||||
uint64_t pte;
|
||||
#else
|
||||
uint32_t pte;
|
||||
#endif
|
||||
} pte_t;
|
||||
|
||||
static inline pte_t paddr_to_pte(paddr_t paddr,
|
||||
unsigned int permissions)
|
||||
{
|
||||
return (pte_t) { .pte = (paddr_to_pfn(paddr) << PTE_PPN_SHIFT) | permissions };
|
||||
}
|
||||
|
||||
static inline paddr_t pte_to_paddr(pte_t pte)
|
||||
{
|
||||
return pfn_to_paddr(pte.pte >> PTE_PPN_SHIFT);
|
||||
}
|
||||
|
||||
static inline bool pte_is_valid(pte_t p)
|
||||
{
|
||||
return p.pte & PTE_VALID;
|
||||
}
|
||||
|
||||
#endif /* _ASM_RISCV_PAGE_H */
|
||||
@@ -69,6 +69,11 @@ static inline void die(void)
|
||||
wfi();
|
||||
}
|
||||
|
||||
static inline void sfence_vma(void)
|
||||
{
|
||||
asm volatile ( "sfence.vma" ::: "memory" );
|
||||
}
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* _ASM_RISCV_PROCESSOR_H */
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
#include <xen/compiler.h>
|
||||
#include <xen/init.h>
|
||||
#include <xen/kernel.h>
|
||||
#include <xen/pfn.h>
|
||||
|
||||
#include <asm/early_printk.h>
|
||||
#include <asm/csr.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/mm.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/processor.h>
|
||||
|
||||
struct mmu_desc {
|
||||
unsigned int num_levels;
|
||||
unsigned int pgtbl_count;
|
||||
pte_t *next_pgtbl;
|
||||
pte_t *pgtbl_base;
|
||||
};
|
||||
|
||||
extern unsigned char cpu0_boot_stack[STACK_SIZE];
|
||||
|
||||
#define PHYS_OFFSET ((unsigned long)_start - XEN_VIRT_START)
|
||||
#define LOAD_TO_LINK(addr) ((addr) - PHYS_OFFSET)
|
||||
#define LINK_TO_LOAD(addr) ((addr) + PHYS_OFFSET)
|
||||
|
||||
/*
|
||||
* It is expected that Xen won't be more then 2 MB.
|
||||
* The check in xen.lds.S guarantees that.
|
||||
* At least 3 page tables (in case of Sv39 ) are needed to cover 2 MB.
|
||||
* One for each page level table with PAGE_SIZE = 4 Kb.
|
||||
*
|
||||
* One L0 page table can cover 2 MB(512 entries of one page table * PAGE_SIZE).
|
||||
*
|
||||
* It might be needed one more page table in case when Xen load address
|
||||
* isn't 2 MB aligned.
|
||||
*/
|
||||
#define PGTBL_INITIAL_COUNT ((CONFIG_PAGING_LEVELS - 1) + 1)
|
||||
|
||||
pte_t __section(".bss.page_aligned") __aligned(PAGE_SIZE)
|
||||
stage1_pgtbl_root[PAGETABLE_ENTRIES];
|
||||
|
||||
pte_t __section(".bss.page_aligned") __aligned(PAGE_SIZE)
|
||||
stage1_pgtbl_nonroot[PGTBL_INITIAL_COUNT * PAGETABLE_ENTRIES];
|
||||
|
||||
#define HANDLE_PGTBL(curr_lvl_num) \
|
||||
index = pt_index(curr_lvl_num, page_addr); \
|
||||
if ( pte_is_valid(pgtbl[index]) ) \
|
||||
{ \
|
||||
/* Find L{ 0-3 } table */ \
|
||||
pgtbl = (pte_t *)pte_to_paddr(pgtbl[index]); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* Allocate new L{0-3} page table */ \
|
||||
if ( mmu_desc->pgtbl_count == PGTBL_INITIAL_COUNT ) \
|
||||
{ \
|
||||
early_printk("(XEN) No initial table available\n"); \
|
||||
/* panic(), BUG() or ASSERT() aren't ready now. */ \
|
||||
die(); \
|
||||
} \
|
||||
mmu_desc->pgtbl_count++; \
|
||||
pgtbl[index] = paddr_to_pte((unsigned long)mmu_desc->next_pgtbl, \
|
||||
PTE_VALID); \
|
||||
pgtbl = mmu_desc->next_pgtbl; \
|
||||
mmu_desc->next_pgtbl += PAGETABLE_ENTRIES; \
|
||||
}
|
||||
|
||||
static void __init setup_initial_mapping(struct mmu_desc *mmu_desc,
|
||||
unsigned long map_start,
|
||||
unsigned long map_end,
|
||||
unsigned long pa_start)
|
||||
{
|
||||
unsigned int index;
|
||||
pte_t *pgtbl;
|
||||
unsigned long page_addr;
|
||||
|
||||
if ( (unsigned long)_start % XEN_PT_LEVEL_SIZE(0) )
|
||||
{
|
||||
early_printk("(XEN) Xen should be loaded at 4k boundary\n");
|
||||
die();
|
||||
}
|
||||
|
||||
if ( (map_start & ~XEN_PT_LEVEL_MAP_MASK(0)) ||
|
||||
(pa_start & ~XEN_PT_LEVEL_MAP_MASK(0)) )
|
||||
{
|
||||
early_printk("(XEN) map and pa start addresses should be aligned\n");
|
||||
/* panic(), BUG() or ASSERT() aren't ready now. */
|
||||
die();
|
||||
}
|
||||
|
||||
for ( page_addr = map_start;
|
||||
page_addr < map_end;
|
||||
page_addr += XEN_PT_LEVEL_SIZE(0) )
|
||||
{
|
||||
pgtbl = mmu_desc->pgtbl_base;
|
||||
|
||||
switch ( mmu_desc->num_levels )
|
||||
{
|
||||
case 4: /* Level 3 */
|
||||
HANDLE_PGTBL(3);
|
||||
case 3: /* Level 2 */
|
||||
HANDLE_PGTBL(2);
|
||||
case 2: /* Level 1 */
|
||||
HANDLE_PGTBL(1);
|
||||
case 1: /* Level 0 */
|
||||
{
|
||||
unsigned long paddr = (page_addr - map_start) + pa_start;
|
||||
unsigned int permissions = PTE_LEAF_DEFAULT;
|
||||
pte_t pte_to_be_written;
|
||||
|
||||
index = pt_index(0, page_addr);
|
||||
|
||||
if ( is_kernel_text(LINK_TO_LOAD(page_addr)) ||
|
||||
is_kernel_inittext(LINK_TO_LOAD(page_addr)) )
|
||||
permissions =
|
||||
PTE_EXECUTABLE | PTE_READABLE | PTE_VALID;
|
||||
|
||||
if ( is_kernel_rodata(LINK_TO_LOAD(page_addr)) )
|
||||
permissions = PTE_READABLE | PTE_VALID;
|
||||
|
||||
pte_to_be_written = paddr_to_pte(paddr, permissions);
|
||||
|
||||
if ( !pte_is_valid(pgtbl[index]) )
|
||||
pgtbl[index] = pte_to_be_written;
|
||||
else
|
||||
{
|
||||
if ( (pgtbl[index].pte ^ pte_to_be_written.pte) &
|
||||
~(PTE_DIRTY | PTE_ACCESSED) )
|
||||
{
|
||||
early_printk("PTE overridden has occurred\n");
|
||||
/* panic(), <asm/bug.h> aren't ready now. */
|
||||
die();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef HANDLE_PGTBL
|
||||
|
||||
static bool __init check_pgtbl_mode_support(struct mmu_desc *mmu_desc,
|
||||
unsigned long load_start)
|
||||
{
|
||||
bool is_mode_supported = false;
|
||||
unsigned int index;
|
||||
unsigned int page_table_level = (mmu_desc->num_levels - 1);
|
||||
unsigned level_map_mask = XEN_PT_LEVEL_MAP_MASK(page_table_level);
|
||||
|
||||
unsigned long aligned_load_start = load_start & level_map_mask;
|
||||
unsigned long aligned_page_size = XEN_PT_LEVEL_SIZE(page_table_level);
|
||||
unsigned long xen_size = (unsigned long)(_end - start);
|
||||
|
||||
if ( (load_start + xen_size) > (aligned_load_start + aligned_page_size) )
|
||||
{
|
||||
early_printk("please place Xen to be in range of PAGE_SIZE "
|
||||
"where PAGE_SIZE is XEN_PT_LEVEL_SIZE( {L3 | L2 | L1} ) "
|
||||
"depending on expected SATP_MODE \n"
|
||||
"XEN_PT_LEVEL_SIZE is defined in <asm/page.h>\n");
|
||||
die();
|
||||
}
|
||||
|
||||
index = pt_index(page_table_level, aligned_load_start);
|
||||
stage1_pgtbl_root[index] = paddr_to_pte(aligned_load_start,
|
||||
PTE_LEAF_DEFAULT | PTE_EXECUTABLE);
|
||||
|
||||
sfence_vma();
|
||||
csr_write(CSR_SATP,
|
||||
PFN_DOWN((unsigned long)stage1_pgtbl_root) |
|
||||
RV_STAGE1_MODE << SATP_MODE_SHIFT);
|
||||
|
||||
if ( (csr_read(CSR_SATP) >> SATP_MODE_SHIFT) == RV_STAGE1_MODE )
|
||||
is_mode_supported = true;
|
||||
|
||||
csr_write(CSR_SATP, 0);
|
||||
|
||||
sfence_vma();
|
||||
|
||||
/* Clean MMU root page table */
|
||||
stage1_pgtbl_root[index] = paddr_to_pte(0x0, 0x0);
|
||||
|
||||
return is_mode_supported;
|
||||
}
|
||||
|
||||
/*
|
||||
* setup_initial_pagetables:
|
||||
*
|
||||
* Build the page tables for Xen that map the following:
|
||||
* 1. Calculate page table's level numbers.
|
||||
* 2. Init mmu description structure.
|
||||
* 3. Check that linker addresses range doesn't overlap
|
||||
* with load addresses range
|
||||
* 4. Map all linker addresses and load addresses ( it shouldn't
|
||||
* be 1:1 mapped and will be 1:1 mapped only in case if
|
||||
* linker address is equal to load address ) with
|
||||
* RW permissions by default.
|
||||
* 5. Setup proper PTE permissions for each section.
|
||||
*/
|
||||
void __init setup_initial_pagetables(void)
|
||||
{
|
||||
struct mmu_desc mmu_desc = { CONFIG_PAGING_LEVELS, 0, NULL, NULL };
|
||||
|
||||
/*
|
||||
* Access to _start, _end is always PC-relative thereby when access
|
||||
* them we will get load adresses of start and end of Xen.
|
||||
* To get linker addresses LOAD_TO_LINK() is required to use.
|
||||
*/
|
||||
unsigned long load_start = (unsigned long)_start;
|
||||
unsigned long load_end = (unsigned long)_end;
|
||||
unsigned long linker_start = LOAD_TO_LINK(load_start);
|
||||
unsigned long linker_end = LOAD_TO_LINK(load_end);
|
||||
|
||||
if ( (linker_start != load_start) &&
|
||||
(linker_start <= load_end) && (load_start <= linker_end) )
|
||||
{
|
||||
early_printk("(XEN) linker and load address ranges overlap\n");
|
||||
die();
|
||||
}
|
||||
|
||||
if ( !check_pgtbl_mode_support(&mmu_desc, load_start) )
|
||||
{
|
||||
early_printk("requested MMU mode isn't supported by CPU\n"
|
||||
"Please choose different in <asm/config.h>\n");
|
||||
die();
|
||||
}
|
||||
|
||||
mmu_desc.pgtbl_base = stage1_pgtbl_root;
|
||||
mmu_desc.next_pgtbl = stage1_pgtbl_nonroot;
|
||||
|
||||
setup_initial_mapping(&mmu_desc,
|
||||
linker_start,
|
||||
linker_end,
|
||||
load_start);
|
||||
}
|
||||
|
||||
void __init noreturn noinline enable_mmu()
|
||||
{
|
||||
/*
|
||||
* Calculate a linker time address of the mmu_is_enabled
|
||||
* label and update CSR_STVEC with it.
|
||||
* MMU is configured in a way where linker addresses are mapped
|
||||
* on load addresses so in a case when linker addresses are not equal
|
||||
* to load addresses, after MMU is enabled, it will cause
|
||||
* an exception and jump to linker time addresses.
|
||||
* Otherwise if load addresses are equal to linker addresses the code
|
||||
* after mmu_is_enabled label will be executed without exception.
|
||||
*/
|
||||
csr_write(CSR_STVEC, LOAD_TO_LINK((unsigned long)&&mmu_is_enabled));
|
||||
|
||||
/* Ensure page table writes precede loading the SATP */
|
||||
sfence_vma();
|
||||
|
||||
/* Enable the MMU and load the new pagetable for Xen */
|
||||
csr_write(CSR_SATP,
|
||||
PFN_DOWN((unsigned long)stage1_pgtbl_root) |
|
||||
RV_STAGE1_MODE << SATP_MODE_SHIFT);
|
||||
|
||||
asm volatile ( ".p2align 2" );
|
||||
mmu_is_enabled:
|
||||
/*
|
||||
* Stack should be re-inited as:
|
||||
* 1. Right now an address of the stack is relative to load time
|
||||
* addresses what will cause an issue in case of load start address
|
||||
* isn't equal to linker start address.
|
||||
* 2. Addresses in stack are all load time relative which can be an
|
||||
* issue in case when load start address isn't equal to linker
|
||||
* start address.
|
||||
*
|
||||
* We can't return to the caller because the stack was reseted
|
||||
* and it may have stash some variable on the stack.
|
||||
* Jump to a brand new function as the stack was reseted
|
||||
*/
|
||||
|
||||
switch_stack_and_jump((unsigned long)cpu0_boot_stack + STACK_SIZE,
|
||||
cont_after_mmu_is_enabled);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <xen/init.h>
|
||||
|
||||
#include <asm/early_printk.h>
|
||||
#include <asm/mm.h>
|
||||
|
||||
/* Xen stack for bringing up the first CPU. */
|
||||
unsigned char __initdata cpu0_boot_stack[STACK_SIZE]
|
||||
@@ -26,3 +27,13 @@ void __init noreturn start_xen(unsigned long bootcpu_id,
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
void __init noreturn cont_after_mmu_is_enabled(void)
|
||||
{
|
||||
early_printk("All set up\n");
|
||||
|
||||
for ( ;; )
|
||||
asm volatile ("wfi");
|
||||
|
||||
unreachable();
|
||||
}
|
||||
@@ -172,3 +172,5 @@ ASSERT(IS_ALIGNED(__bss_end, POINTER_ALIGN), "__bss_end is misaligned")
|
||||
|
||||
ASSERT(!SIZEOF(.got), ".got non-empty")
|
||||
ASSERT(!SIZEOF(.got.plt), ".got.plt non-empty")
|
||||
|
||||
ASSERT(_end - _start <= MB(2), "Xen too large for early-boot assumptions")
|
||||