[arch][riscv] Initial implementation of MMU for RISC-V

Implements both SV39 and SV48. No 32bit support yet.

Currently implements basic setup of paging in start.S by mapping a large
chunk of memory into both an identity map and to the bottom of the
kernel address space. Run the kernel out of this physical mapping.

Added basic arch mmu support for querying existing paging structures and
mapping 4K pages. No unmap support as of yet.

System boots with mmu on when running supervisor test on qemu. Untested
on real hardware as of yet.
This commit is contained in:
Travis Geiselbrecht
2020-01-26 20:25:35 -08:00
parent 0b6866830d
commit 7c9906a5ff
10 changed files with 710 additions and 21 deletions

View File

@@ -67,12 +67,17 @@ void arch_init(void) {
riscv_init_percpu();
// print some arch info
#if RISCV_M_MODE
dprintf(INFO, "RISCV: Machine mode\n");
dprintf(INFO, "RISCV: mvendorid %#lx marchid %#lx mimpid %#lx mhartid %#x\n",
riscv_get_mvendorid(), riscv_get_marchid(),
riscv_get_mimpid(), riscv_current_hart());
#if RISCV_M_MODE
dprintf(INFO, "RISCV: misa %#lx\n", riscv_csr_read(RISCV_CSR_MISA));
#else
dprintf(INFO, "RISCV: Supervisor mode\n");
#if RISCV_MMU
dprintf(INFO, "RISCV: MMU enabled sv%u\n", RISCV_MMU);
#endif
dprintf(INFO, "RISCV: SBI impl id %#lx version %#lx\n", sbi_call(SBI_GET_SBI_IMPL_ID).value, sbi_call(SBI_GET_SBI_IMPL_VERSION).value);
// probe some SBI extensions

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <lk/compiler.h>
#include <arch/riscv/mmu.h>
__BEGIN_CDECLS
struct arch_aspace {
/* pointer to the translation table */
paddr_t pt_phys;
volatile riscv_pte_t *pt_virt;
uint flags;
/* range of address space */
vaddr_t base;
size_t size;
};
__END_CDECLS

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2020 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#if RISCV_MMU
#include <stdint.h>
#include <arch/defines.h>
// RISC-V specific mmu #defines and structures here
#define KB (1024UL)
#define MB (1024UL*1024UL)
#define GB (1024UL*1024UL*1024UL)
typedef uintptr_t riscv_pte_t;
// some constants based on our particular implementation
#if RISCV_MMU == 48
#define RISCV_MMU_PT_LEVELS 4
#define RISCV_MMU_PT_SHIFT 9
#define RISCV_MMU_PT_ENTRIES 512 // 1 << PT_SHIFT
#define RISCV_MMU_CANONICAL_MASK ((1UL << 48) - 1)
#define RISCV_MMU_PPN_BITS 56
#elif RISCV_MMU == 39
#define RISCV_MMU_PT_LEVELS 3
#define RISCV_MMU_PT_SHIFT 9
#define RISCV_MMU_PT_ENTRIES 512 // 1 << PT_SHIFT
#define RISCV_MMU_CANONICAL_MASK ((1UL << 39) - 1)
#define RISCV_MMU_PPN_BITS 56
#elif RISCV_MMU == 32
#define RISCV_MMU_PT_LEVELS 2
#define RISCV_MMU_PT_SHIFT 10
#define RISCV_MMU_PT_ENTRIES 1024 // 1 << PT_SHIFT
#define RISCV_MMU_CANONICAL_MASK UINT32_MASK
#define RISCV_MMU_PPN_BITS 32
#else
#error implement
#endif
// page table bits
#define RISCV_PTE_V (1 << 0) // valid
#define RISCV_PTE_R (1 << 1) // read
#define RISCV_PTE_W (1 << 2) // write
#define RISCV_PTE_X (1 << 3) // execute
#define RISCV_PTE_PERM_MASK (0x7 << 1)
#define RISCV_PTE_U (1 << 4) // user
#define RISCV_PTE_G (1 << 5) // global
#define RISCV_PTE_A (1 << 6) // accessed
#define RISCV_PTE_D (1 << 7) // dirty
#define RISCV_PTE_RSW_MASK (3 << 8) // reserved for software
#define RISCV_PTE_PPN_SHIFT (10)
#define RISCV_PTE_PPN_MASK (((1UL << (RISCV_MMU_PPN_BITS - PAGE_SIZE_SHIFT)) - 1) << RISCV_PTE_PPN_SHIFT)
// riscv PPN is stored shifed over 2 from the natural alignment
#define RISCV_PTE_PPN(pte) (((pte) & RISCV_PTE_PPN_MASK) << (PAGE_SIZE_SHIFT - RISCV_PTE_PPN_SHIFT))
#define RISCV_PTE_PPN_TO_PTE(paddr) (((paddr) >> PAGE_SIZE_SHIFT) << RISCV_PTE_PPN_SHIFT)
// SATP register, contains the current mmu mode, address space id, and
// pointer to root page table
#define RISCV_SATP_MODE_NONE (0)
#define RISCV_SATP_MODE_SV32 (1)
#define RISCV_SATP_MODE_SV39 (8)
#define RISCV_SATP_MODE_SV48 (9)
#define RISCV_SATP_MODE_SV57 (10)
#define RISCV_SATP_MODE_SV64 (11)
#if __riscv_xlen == 32
#define RISCV_SATP_MODE_SHIFT (31)
#define RISCV_SATP_ASID_SHIFT (22)
#define RISCV_SATP_ASID_SIZE (9)
#define RISCV_SATP_ASID_MASK ((1 << RISCV_SATP_ASID_SIZE) - 1)
#elif __riscv_xlen == 64
#define RISCV_SATP_MODE_SHIFT (60)
#define RISCV_SATP_ASID_SHIFT (44)
#define RISCV_SATP_ASID_SIZE (16)
#define RISCV_SATP_ASID_MASK ((1UL << RISCV_SATP_ASID_SIZE) - 1)
#endif
#endif // RISCV_MMU

341
arch/riscv/mmu.c Normal file
View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) 2020 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#if RISCV_MMU
#include "arch/riscv/mmu.h"
#include <assert.h>
#include <string.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <arch/ops.h>
#include <arch/mmu.h>
#include <arch/riscv.h>
#include <arch/riscv/csr.h>
#include <kernel/vm.h>
#define LOCAL_TRACE 0
#include <kernel/vm.h>
#if __riscv_xlen == 32
#error "32 bit mmu not supported yet"
#endif
riscv_pte_t kernel_pgtable[512] __ALIGNED(PAGE_SIZE);
paddr_t kernel_pgtable_phys; // filled in by start.S
// initial memory mappings. VM uses to construct mappings after the fact
struct mmu_initial_mapping mmu_initial_mappings[] = {
// all of memory, mapped in start.S
{
.phys = 0,
.virt = KERNEL_ASPACE_BASE,
#if RISCV_MMU == 48
.size = 512UL * GB,
#elif RISCV_MMU == 39
.size = 64UL * GB,
#else
#error implement
#endif
.flags = 0,
.name = "memory"
},
// null entry to terminate the list
{ 0 }
};
static inline void riscv_set_satp(uint asid, paddr_t pt) {
ulong satp;
#if RISCV_MMU == 48
satp = RISCV_SATP_MODE_SV48;
#elif RISCV_MMU == 39
satp = RISCV_SATP_MODE_SV39;
#endif
// make sure the asid is in range
DEBUG_ASSERT((asid & RISCV_SATP_ASID_MASK) == 0);
satp |= (ulong)asid << RISCV_SATP_ASID_SHIFT;
// make sure the page table is aligned
DEBUG_ASSERT(IS_PAGE_ALIGNED(pt));
satp |= pt;
riscv_csr_write(RISCV_CSR_SATP, satp);
// TODO: TLB flush here or use asid properly
// sfence.vma zero, zero
}
// given a va address and the level, compute the index in the current PT
static inline uint vaddr_to_index(vaddr_t va, uint level) {
// levels count down from PT_LEVELS - 1
DEBUG_ASSERT(level < RISCV_MMU_PT_LEVELS);
// canonicalize the address
va &= RISCV_MMU_CANONICAL_MASK;
uint index = ((va >> PAGE_SIZE_SHIFT) >> (level * RISCV_MMU_PT_SHIFT)) & (RISCV_MMU_PT_ENTRIES - 1);
LTRACEF_LEVEL(3, "canonical va %#lx, level %u = index %#x\n", va, level, index);
return index;
}
static uintptr_t page_size_per_level(uint level) {
// levels count down from PT_LEVELS - 1
DEBUG_ASSERT(level < RISCV_MMU_PT_LEVELS);
return 1UL << (PAGE_SIZE_SHIFT + level * RISCV_MMU_PT_SHIFT);
}
static uintptr_t page_mask_per_level(uint level) {
return page_size_per_level(level) - 1;
}
static volatile riscv_pte_t *alloc_ptable(paddr_t *pa) {
// grab a page from the pmm
vm_page_t *p = pmm_alloc_page();
if (!p) {
return NULL;
}
// get the physical and virtual mappings of the page
*pa = vm_page_to_paddr(p);
riscv_pte_t *pte = paddr_to_kvaddr(*pa);
// zero it out
memset(pte, 0, PAGE_SIZE);
smp_wmb();
LTRACEF_LEVEL(3, "returning pa %#lx, va %p\n", *pa, pte);
return pte;
}
static riscv_pte_t mmu_flags_to_pte(uint flags) {
riscv_pte_t pte = 0;
pte |= (flags & ARCH_MMU_FLAG_PERM_USER) ? RISCV_PTE_U : 0;
pte |= (flags & ARCH_MMU_FLAG_PERM_RO) ? RISCV_PTE_R : (RISCV_PTE_R | RISCV_PTE_W);
pte |= (flags & ARCH_MMU_FLAG_PERM_NO_EXECUTE) ? 0 : RISCV_PTE_X;
return pte;
}
static uint pte_flags_to_mmu_flags(riscv_pte_t pte) {
uint f = 0;
if ((pte & (RISCV_PTE_R | RISCV_PTE_W)) == RISCV_PTE_R) {
f |= ARCH_MMU_FLAG_PERM_RO;
}
f |= (pte & RISCV_PTE_X) ? 0 : ARCH_MMU_FLAG_PERM_NO_EXECUTE;
f |= (pte & RISCV_PTE_U) ? ARCH_MMU_FLAG_PERM_USER : 0;
return f;
}
// public api
// initialize per address space
status_t arch_mmu_init_aspace(arch_aspace_t *aspace, vaddr_t base, size_t size, uint flags) {
LTRACEF("aspace %p, base %#lx, size %#zx, flags %#x\n", aspace, base, size, flags);
DEBUG_ASSERT(aspace);
// validate that the base + size is sane and doesn't wrap
DEBUG_ASSERT(size > PAGE_SIZE);
DEBUG_ASSERT(base + size - 1 > base);
aspace->flags = flags;
if (flags & ARCH_ASPACE_FLAG_KERNEL) {
// at the moment we can only deal with address spaces as globally defined
DEBUG_ASSERT(base == KERNEL_ASPACE_BASE);
DEBUG_ASSERT(size == KERNEL_ASPACE_SIZE);
aspace->base = base;
aspace->size = size;
aspace->pt_virt = kernel_pgtable;
aspace->pt_phys = kernel_pgtable_phys;
} else {
PANIC_UNIMPLEMENTED;
}
LTRACEF("pt phys %#lx, pt virt %p\n", aspace->pt_phys, aspace->pt_virt);
return NO_ERROR;
}
status_t arch_mmu_destroy_aspace(arch_aspace_t *aspace) {
LTRACEF("aspace %p\n", aspace);
PANIC_UNIMPLEMENTED;
}
// routines to map/unmap/query mappings per address space
int arch_mmu_map(arch_aspace_t *aspace, vaddr_t vaddr, paddr_t paddr, uint count, const uint flags) {
LTRACEF("vaddr %#lx paddr %#lx count %u flags %#x\n", vaddr, paddr, count, flags);
DEBUG_ASSERT(aspace);
restart:
if (count == 0)
return NO_ERROR;
// bootstrap the top level walk
uint level = RISCV_MMU_PT_LEVELS - 1;
uint index = vaddr_to_index(vaddr, level);
volatile riscv_pte_t *ptep = aspace->pt_virt + index;
for (;;) {
LTRACEF_LEVEL(2, "level %u, index %u, pte %p (%#lx) va %#lx pa %#lx\n",
level, index, ptep, *ptep, vaddr, paddr);
// look at our page table entry
riscv_pte_t pte = *ptep;
if (level > 0 && !(pte & RISCV_PTE_V)) {
// invalid entry, will have to add a page table
paddr_t ptp;
volatile riscv_pte_t *ptv = alloc_ptable(&ptp);
if (!ptv) {
return ERR_NO_MEMORY;
}
LTRACEF_LEVEL(2, "new ptable table %p, pa %#lx\n", ptv, ptp);
// link it in. RMW == 0 is a page table link
pte = RISCV_PTE_PPN_TO_PTE(ptp) | RISCV_PTE_V;
*ptep = pte;
// go one level deeper
level--;
index = vaddr_to_index(vaddr, level);
ptep = ptv + index;
} else if ((pte & RISCV_PTE_V) && !(pte & RISCV_PTE_PERM_MASK)) {
// next level page table pointer (RWX = 0)
paddr_t ptp = RISCV_PTE_PPN(pte);
volatile riscv_pte_t *ptv = paddr_to_kvaddr(ptp);
LTRACEF_LEVEL(2, "next level page table at %p, pa %#lx\n", ptv, ptp);
// go one level deeper
level--;
index = vaddr_to_index(vaddr, level);
ptep = ptv + index;
} else if (pte & RISCV_PTE_V) {
// terminal entry already exists
if (level > 0) {
PANIC_UNIMPLEMENTED_MSG("terminal large page entry");
} else {
PANIC_UNIMPLEMENTED_MSG("terminal page entry");
}
} else {
DEBUG_ASSERT(level == 0 && !(pte & RISCV_PTE_V));
// hit a open terminal page table entry, lets add ours
pte = RISCV_PTE_PPN_TO_PTE(paddr);
pte |= mmu_flags_to_pte(flags);
pte |= RISCV_PTE_A | RISCV_PTE_D | RISCV_PTE_V;
pte |= (aspace->flags & ARCH_ASPACE_FLAG_KERNEL) ? RISCV_PTE_G : 0;
LTRACEF_LEVEL(2, "added new terminal entry: pte %#lx\n", pte);
*ptep = pte;
// simple algorithm: restart walk from top, one page at a time
// TODO: more efficiently deal with runs and large pages
count--;
paddr += PAGE_SIZE;
vaddr += PAGE_SIZE;
goto restart;
}
// make sure we didn't decrement level one too many
DEBUG_ASSERT(level < RISCV_MMU_PT_LEVELS);
}
// unreachable
}
int arch_mmu_unmap(arch_aspace_t *aspace, vaddr_t vaddr, uint count) {
LTRACEF("vaddr %#lx count %u\n", vaddr, count);
PANIC_UNIMPLEMENTED;
}
status_t arch_mmu_query(arch_aspace_t *aspace, const vaddr_t vaddr, paddr_t *paddr, uint *flags) {
LTRACEF("aspace %p, vaddr %#lx\n", aspace, vaddr);
DEBUG_ASSERT(aspace);
// trim the vaddr to the aspace
if (vaddr < aspace->base || vaddr > aspace->base + aspace->size - 1) {
return ERR_OUT_OF_RANGE;
}
uint level = RISCV_MMU_PT_LEVELS - 1;
uint index = vaddr_to_index(vaddr, level);
volatile riscv_pte_t *ptep = aspace->pt_virt + index;
// walk down through the levels, looking for a terminal entry that matches our address
for (;;) {
LTRACEF_LEVEL(2, "level %u, index %u, pte %p (%#lx)\n", level, index, ptep, *ptep);
// look at our page table entry
riscv_pte_t pte = *ptep;
if ((pte & RISCV_PTE_V) == 0) {
// invalid entry, terminate search
return ERR_NOT_FOUND;
} else if ((pte & RISCV_PTE_PERM_MASK) == 0) {
// next level page table pointer (RWX = 0)
paddr_t ptp = RISCV_PTE_PPN(pte);
volatile riscv_pte_t *ptv = paddr_to_kvaddr(ptp);
LTRACEF_LEVEL(2, "next level page table at %p, pa %#lx\n", ptv, ptp);
// go one level deeper
level--;
index = vaddr_to_index(vaddr, level);
ptep = ptv + index;
} else {
// terminal entry
LTRACEF_LEVEL(3, "terminal entry\n");
if (paddr) {
// extract the ppn
paddr_t pa = RISCV_PTE_PPN(pte);
uintptr_t page_mask = page_mask_per_level(level);
// add the va offset into the physical address
*paddr = pa | (vaddr & page_mask);
LTRACEF_LEVEL(3, "raw pa %#lx, page_mask %#lx, final pa %#lx\n", pa, page_mask, *paddr);
}
if (flags) {
// compute the flags
*flags = pte_flags_to_mmu_flags(pte);
LTRACEF_LEVEL(3, "computed flags %#x\n", *flags);
}
return NO_ERROR;
}
// make sure we didn't decrement level one too many
DEBUG_ASSERT(level < RISCV_MMU_PT_LEVELS);
}
// unreachable
}
// load a new user address space context.
// aspace argument NULL should load kernel-only context
void arch_mmu_context_switch(arch_aspace_t *aspace) {
LTRACEF("aspace %p\n", aspace);
PANIC_UNIMPLEMENTED;
}
#endif

View File

@@ -7,6 +7,7 @@ MODULE_SRCS += $(LOCAL_DIR)/arch.c
MODULE_SRCS += $(LOCAL_DIR)/asm.S
MODULE_SRCS += $(LOCAL_DIR)/exceptions.c
MODULE_SRCS += $(LOCAL_DIR)/thread.c
MODULE_SRCS += $(LOCAL_DIR)/mmu.c
MODULE_SRCS += $(LOCAL_DIR)/mp.c
MODULE_SRCS += $(LOCAL_DIR)/time.c
@@ -16,6 +17,7 @@ MODULE_CFLAGS += -Wno-override-init
SMP_MAX_CPUS ?= 1
RISCV_MAX_HARTS ?= $(SMP_MAX_CPUS)
RISCV_BOOT_HART ?= 0
RISCV_MMU ?= none
GLOBAL_DEFINES += SMP_MAX_CPUS=$(SMP_MAX_CPUS)
GLOBAL_DEFINES += RISCV_MAX_HARTS=$(RISCV_MAX_HARTS)
@@ -33,19 +35,81 @@ RISCV_MODE ?= machine
ifeq ($(strip $(RISCV_MODE)),machine)
$(info RISCV: Machine Mode)
GLOBAL_DEFINES += RISCV_M_MODE=1
else
ifeq ($(strip $(RISCV_MODE)),supervisor)
ifneq ($(RISCV_MMU),none)
$(error RISCV mmu not supported in machine mode)
endif
else ifeq ($(strip $(RISCV_MODE)),supervisor)
$(info RISCV: Supervisor Mode)
GLOBAL_DEFINES += RISCV_S_MODE=1
else
$(error Unknown RISC-V mode: "$(strip $(RISCV_MODE))" (valid values are "machine", "supervisor"))
endif
ifeq ($(RISCV_MMU),sv48)
ifeq ($(SUBARCH),32)
$(error RISCV: sv48 mmu not supported for 32 bit riscv)
endif
$(info RISCV: MMU sv48)
GLOBAL_DEFINES += RISCV_MMU=48
WITH_KERNEL_VM ?= 1
# 48 bits split between two 47 bit halves
KERNEL_ASPACE_BASE := 0xffff800000000000
KERNEL_ASPACE_SIZE := 0x0000800000000000
USER_ASPACE_BASE := 0x0000000001000000
USER_ASPACE_SIZE := 0x00007ffffe000000
else ifeq ($(RISCV_MMU),sv39)
ifeq ($(SUBARCH),32)
$(error RISCV: sv39 mmu not supported for 32 bit riscv)
endif
$(info RISCV: MMU sv39)
GLOBAL_DEFINES += RISCV_MMU=39
WITH_KERNEL_VM ?= 1
# 39 bits split between two 38 bit halves
KERNEL_ASPACE_BASE := 0xffffffc000000000
KERNEL_ASPACE_SIZE := 0x0000004000000000
USER_ASPACE_BASE := 0x0000000001000000
USER_ASPACE_SIZE := 0x0000003ffe000000
else ifeq ($(RISCV_MMU),sv32)
$(info RISCV: MMU sv32)
GLOBAL_DEFINES += RISCV_MMU=32
WITH_KERNEL_VM ?= 1
# 32 bits split between two 31 bit halves
KERNEL_ASPACE_BASE := 0x80000000
KERNEL_ASPACE_SIZE := 0x80000000
USER_ASPACE_BASE := 0x01000000
USER_ASPACE_SIZE := 0x7e000000
else ifeq ($(RISCV_MMU),none)
else
$(error Unknown RISCV_MMU: "$(strip $(RISCV_MMU))" (valid values are "none", "sv32", "sv39", "sv48"))
endif
WITH_LINKER_GC ?= 0
ifeq ($(WITH_KERNEL_VM),1)
GLOBAL_DEFINES += \
KERNEL_ASPACE_BASE=$(KERNEL_ASPACE_BASE) \
KERNEL_ASPACE_SIZE=$(KERNEL_ASPACE_SIZE) \
USER_ASPACE_BASE=$(USER_ASPACE_BASE) \
USER_ASPACE_SIZE=$(USER_ASPACE_SIZE)
KERNEL_BASE ?= $(KERNEL_ASPACE_BASE)+$(MEMBASE)
KERNEL_LOAD_OFFSET ?= 0
GLOBAL_DEFINES += KERNEL_BASE=$(KERNEL_BASE)
GLOBAL_DEFINES += KERNEL_LOAD_OFFSET=$(KERNEL_LOAD_OFFSET)
else # no kernel vm
KERNEL_BASE ?= $(MEMBASE)
KERNEL_LOAD_OFFSET ?= 0
endif
ROMBASE ?= 0
GLOBAL_DEFINES += ROMBASE=$(ROMBASE)
@@ -67,6 +131,7 @@ ifndef TOOLCHAIN_PREFIX
TOOLCHAIN_PREFIX := riscv64-elf-
endif
ARCH_COMPILEFLAGS := -march=rv64imac -mabi=lp64 -mcmodel=medany
GLOBAL_DEFINES += IS_64BIT=1
else
$(error SUBARCH not set or set to something unknown)
@@ -81,6 +146,8 @@ else
ARCH_OPTFLAGS ?= -O2
endif
WITH_LINKER_GC ?= 0
LIBGCC := $(shell $(TOOLCHAIN_PREFIX)gcc $(GLOBAL_COMPILEFLAGS) $(ARCH_COMPILEFLAGS) $(GLOBAL_CFLAGS) -print-libgcc-file-name)
$(info LIBGCC = $(LIBGCC))

View File

@@ -14,7 +14,7 @@ FUNCTION(_start)
.option push
.option norelax
// set the global pointer
la gp, __global_pointer$
lla gp, __global_pointer$
.option pop
#if RISCV_M_MODE
@@ -25,10 +25,10 @@ FUNCTION(_start)
// if the hart is too high, trap it
li t0, RISCV_MAX_HARTS
ble t0, a0, .Lhart_trap
ble t0, a0, hart_trap
// set the default stack per cpu
la sp, default_stack_top
lla sp, default_stack_top
// default stack locations for each hart:
// LOW ------------ HIGH
// [hart2][hart1][hart0]
@@ -38,13 +38,13 @@ FUNCTION(_start)
// if our hart isnt RISCV_BOOT_HART, trap the cpu
li t2, RISCV_BOOT_HART
bne t2, a0, .Lsecondary_trap
bne t2, a0, secondary_trap
#if ARCH_RISCV_TWOSEGMENT
// copy preinitialized data from flash to memory
la t0, __data_start_rom
la t1, __data_start
la t2, __data_end
lla t0, __data_start_rom
lla t1, __data_start
lla t2, __data_end
beq t0, t1, 1f
0:
@@ -57,17 +57,21 @@ FUNCTION(_start)
// zero bss
1:
la t0, __bss_start
la t1, __bss_end
lla t0, __bss_start
lla t1, __bss_end
0:
sw zero, (t0)
add t0, t0, 4
bne t0, t1, 0b
#if RISCV_MMU
jal _mmu_init
#endif
#if WITH_SMP
// Release any other harts into riscv_secondary_entry
fence w, w
la t1, _boot_status
lla t1, _boot_status
li t0, 1
sb t0, (t1)
#endif
@@ -89,12 +93,17 @@ FUNCTION(_start)
// should never return here
j .
.Lsecondary_trap:
LOCAL_FUNCTION(secondary_trap)
#if WITH_SMP
// wait for _boot_status to be nonzero, then go into riscv_secondary_entry
la t5, _boot_status
lla t5, _boot_status
lb t0, (t5)
beqz t0, .Lsecondary_trap
beqz t0, secondary_trap
#if RISCV_MMU
// TODO: enable the mmu on this core
jal .Lenable_mmu
#endif
// set the per cpu structure before getting into the secondary boot path
jal riscv_configure_percpu_early
@@ -106,11 +115,102 @@ FUNCTION(_start)
j .
#endif
.Lhart_trap:
LOCAL_FUNCTION(hart_trap)
// cpus with too high of a hart id go here and spin forever
wfi
j .
#if RISCV_MMU
// initialize the kernel page tables
// for all MMU versions, identity map some amount of memory near 0 and
// the same amount at the bottom of the kernel's address space
LOCAL_FUNCTION(_mmu_init)
lla t0, kernel_pgtable
// store the physical address of the pgtable for future use
lla t1, kernel_pgtable_phys
sd t0, (t1)
// compute kernel pgtable pointer (index 256)
addi t1, t0, (8 * 128)
addi t1, t1, (8 * 128)
// page table entry: address 0, A, D, G, XWR, V
li t2, (0 | (1<<7) | (1<<6) | (1<<5) | (1<<3) | (1<<2) | (1<<1) | (1<<0))
// num interations and increment count
#if RISCV_MMU == 48
// RV48: map the first 512GB of the physical address space at the
// bottom of the kernel address space using a single terapage
li t3, 1
li t4, (512 * 1024 * 1024 * 1024) >> 2
#elif RISCV_MMU == 39
// RV39: map the first 64GB of the physical address space at the
// bottom of the kernel address space using 64 1GB gigapages
li t3, 64
li t4, (1 * 1024 * 1024 * 1024) >> 2
#else
#error implement
#endif
// loop, writing t3 entries out and incrementing by t4 address
0:
sd t2, (t1)
sd t2, (t0)
add t2, t2, t4
addi t0, t0, 8
addi t1, t1, 8
addi t3, t3, -1
bnez t3, 0b
// ensure it's written out
fence w,w
.Lenable_mmu:
// set the satp register and enable the mmu
// ASID 0, kernel_pgtable address
lla t0, kernel_pgtable
srli t1, t0, 12
#if RISCV_MMU == 48
li t2, (9 << 60) // mode 9, SV48
#elif RISCV_MMU == 39
li t2, (8 << 60) // mode 8, SV39
#else
#error implement
#endif
or t1, t1, t2
csrw satp, t1
lla s0, kernel_pgtable
// global tlb fence
sfence.vma zero, zero
// mmu is initialized and we're running out of an identity physical map
// save the physical address of .Lhigh
lla t1, .Lhigh
// bounce to the high address
lla t0, .Lhigh_addr
ld t0, (t0)
jr t0
// the full virtual address of the .Lhigh label
.Lhigh_addr:
.quad .Lhigh
.Lhigh:
// we're now running at the high virtual address
// compute the delta between the old physical and newer high addresses
sub t0, t0, t1
// fix up the gp, stack pointer, and return address
add gp, gp, t0
add sp, sp, t0
add ra, ra, t0
ret
#endif // RISCV_MMU
.bss
.align 4
LOCAL_DATA(default_stack)

View File

@@ -30,3 +30,24 @@
#define PLIC_HART_IDX(hart) ((2 * (hart)) + 1)
#endif
#define MEMORY_BASE_PHYS (0x80000000)
#if __riscv_xlen == 64
// up to 64 GB of ram, which seems to be a soft cap
#define MEMORY_APERTURE_SIZE (64ULL * 1024 * 1024 * 1024)
#else
// cap after 1GB
#define MEMORY_APERTURE_SIZE (1UL * 1024 * 1024 * 1024)
#endif
// map all of 0-1GB into kernel space in one shot
#define PERIPHERAL_BASE_PHYS (0)
#define PERIPHERAL_BASE_SIZE (0x40000000UL) // 1GB
// XXX clean up
#if __riscv_xlen == 64
#define PERIPHERAL_BASE_VIRT (0xffffffffc0000000ULL) // -1GB
#else
#define PERIPHERAL_BASE_VIRT (0xc0000000UL) // -1GB
#endif

View File

@@ -8,7 +8,6 @@
#include <lk/reg.h>
#include <lk/trace.h>
#include <kernel/thread.h>
#include <kernel/novm.h>
#include <platform.h>
#include <platform/interrupts.h>
#include <platform/debug.h>
@@ -21,6 +20,11 @@
#if WITH_LIB_MINIP
#include <lib/minip.h>
#endif
#if WITH_KERNEL_VM
#include <kernel/vm.h>
#else
#include <kernel/novm.h>
#endif
#include "platform_p.h"
@@ -28,16 +32,33 @@
extern ulong lk_boot_args[4];
#if WITH_KERNEL_VM
#define DEFAULT_MEMORY_SIZE (MEMSIZE) /* try to fetch from the emulator via the fdt */
static pmm_arena_t arena = {
.name = "ram",
.base = MEMORY_BASE_PHYS,
.size = DEFAULT_MEMORY_SIZE,
.flags = PMM_ARENA_FLAG_KMAP,
};
#endif
// callbacks to the fdt_walk routine
static void memcallback(uint64_t base, uint64_t len, void *cookie) {
bool *found_mem = (bool *)cookie;
LTRACEF("base %#llx len %#llx cookie %p\n", base, len, cookie);
/* add another novm arena */
/* add another vm arena */
if (!*found_mem) {
printf("FDT: found memory arena, base %#llx size %#llx\n", base, len);
#if WITH_KERNEL_VM
arena.base = base;
arena.size = len;
pmm_add_arena(&arena);
#else
novm_add_arena("fdt", base, len);
#endif
*found_mem = true; // stop searching after the first one
}
}
@@ -75,7 +96,11 @@ void platform_early_init(void) {
}
if (!found_mem) {
#if WITH_KERNEL_VM
pmm_add_arena(&arena);
#else
novm_add_arena("default", MEMBASE, MEMSIZE);
#endif
}
if (cpu_count > 0) {
@@ -83,6 +108,13 @@ void platform_early_init(void) {
riscv_set_secondary_count(cpu_count - 1);
}
#if WITH_KERNEL_VM
/* reserve the first 128K of ram which is marked protected by the PMP in firmware */
struct list_node list = LIST_INITIAL_VALUE(list);
pmm_alloc_range(MEMBASE, 0x20000 / PAGE_SIZE, &list);
#endif
LTRACEF("done scanning FDT\n");
}

View File

@@ -8,6 +8,14 @@ RISCV_MODE ?= machine
WITH_SMP ?= 1
SMP_MAX_CPUS ?= 8
ifeq ($(RISCV_MODE),supervisor)
ifeq ($(SUBARCH),32)
RISCV_MMU ?= sv32
else
RISCV_MMU ?= sv48
endif
endif
MODULE_DEPS += lib/cbuf
MODULE_DEPS += lib/fdt
MODULE_DEPS += lib/fdtwalk
@@ -19,7 +27,6 @@ MODULE_SRCS += $(LOCAL_DIR)/platform.c
MODULE_SRCS += $(LOCAL_DIR)/plic.c
MODULE_SRCS += $(LOCAL_DIR)/uart.c
#ROMBASE ?= 0x20400000 # if running from rom, start here
MEMBASE ?= 0x80000000
MEMSIZE ?= 0x01000000 # default to 16MB
ifeq ($(RISCV_MODE),supervisor)

View File

@@ -54,6 +54,7 @@ static inline void hexdump8(const void *ptr, size_t len) {
void panic(const char *fmt, ...) __PRINTFLIKE(1, 2) __NO_RETURN;
#define PANIC_UNIMPLEMENTED panic("%s unimplemented\n", __PRETTY_FUNCTION__)
#define PANIC_UNIMPLEMENTED_MSG(x...) panic("%s unimplemented: %s\n", __PRETTY_FUNCTION__, x)
/* spin the cpu for a period of (short) time */
void spin(uint32_t usecs);