Skip to content
Permalink
Browse files

Simplify startup with CPU cores claiming memory on a first come, firs…

…t served basis. This should scale better and fewer istructions means fewer bugs. Each CPU core gets its own private slab of memory in which it can build its own heap and hold private variables. this reduces contention and pushes detailed physical memory management into the supervisoer kernel(s). all the machine/hypervisor kernel needs to do is track regions of physical memory for each supervisor environment. this can be done using a tree structure in future. the aim is to make the hypervisor/machine kernel as lean as possible, farming out resources to supervisors to manage separately and securely
  • Loading branch information...
diodesign committed Dec 14, 2018
1 parent 0b195d7 commit 10dbdd600673acee5e06d9ac608f8546da620783
@@ -18,3 +18,6 @@ Cargo.toml

# ignore rustfmt stuff
**/*.rs.bk

# ignore IDE stuff for now
.vscode
@@ -0,0 +1,17 @@
/* diosix machine kernel's CPU core management
*
* (c) Chris Williams, 2018.
*
* See LICENSE for usage and copying.
*/

/* platform-specific code must implement all this */
use platform;

/* intiialize CPU core. Prepare it for running supervisor code.
=> cpu_nr = CPU ID number (0 = boot CPU)
<= returns true if success, or false for failure */
pub fn init(cpu_nr: usize) -> bool
{
return platform::common::cpu::init(cpu_nr);
}

This file was deleted.

Oops, something went wrong.
@@ -15,57 +15,65 @@ extern crate platform;
#[macro_use]
mod debug; /* get us some kind of debug output, typically to a serial port */
mod abort; /* implement abort() and panic() handlers */
mod heap; /* manage machine kernel's heap memory */
mod irq; /* handle hw interrupts and sw exceptions, collectively known as IRQs */
mod irq; /* handle hw interrupts and sw exceptions, collectively known as IRQs */
mod physmem; /* manage physical memory */
mod cpu; /* manage CPU cores */

/* function naming note: machine kernel entry points start with a k, such as kmain,
kwait, kirq_handler. supervisor kernel entry points start with an s, such as smain.
kirq_handler. supervisor kernel entry points start with an s, such as smain.
generally, kernel = machine/hypervisor kernel, supervisor = supervisor kernel. */

/* pointer sizes: do not assume this is a 32-bit or 64-bit system. it could be either.
stick to usize as much as possible */

/* kmain
The boot CPU core branches here when ready.
This code runs at the machine/hypervisor level, with physical memory access.
Its job is to create environments in which supervisor kernels run. Thus the kernel is
Its job is to create environments in which supervisor kernels run. FWIW, the kernel is
split into two halves: a machine/hv lower half, and an upper half supervisor that
manages user-mode code.
manages user-mode code. This code here is that lower half.
=> device_tree_buf = phys RAM pointer to device tree describing the hardware
Assumes all CPUs enter this function during startup.
The boot CPU is chosen to initialize the system in pre-SMP mode.
If we're on a single CPU core then everything should run OK.
=> cpu_nr = CPU ID number. 0 = boot CPU
device_tree_buf = phys RAM pointer to device tree describing the hardware
<= return to halt kernel on this core
*/
#[no_mangle]
pub extern "C" fn kmain(device_tree_buf: &u8)
pub extern "C" fn kmain(cpu_nr: usize, device_tree_buf: &u8)
{
klog!("Welcome to diosix {}", env!("CARGO_PKG_VERSION"));
/* make the boot CPU setup physical memory etc for other cores to come online */
if cpu_nr == 0
{
pre_smp_init(device_tree_buf);
}

/* set up the physical memory managemenwt */
match physmem::init(device_tree_buf)
/* set up all processor cores, including the boot CPU */
match cpu::init(cpu_nr)
{
Some(s) => klog!(
"Total physical memory avilable: {} MiB ({} bytes)",
s / 1024 / 1024,
s
),
None =>
true => klog!("CPU core initialized"),
false =>
{
kalert!("Insufficient physical memory, halting.");
kalert!("Failed to initialize CPU core");
return;
}
};

/* test kernel heap */

}
}

/* kwait
Non-boot CPU cores arrive here when ready to do some work.
<= return to halt kernel on this core
*/
#[no_mangle]
pub extern "C" fn kwait()
/* perform any preflight checks and initialize the kernel prior to SMP */
fn pre_smp_init(device_tree: &u8)
{
klog!("CPU core alive and waiting");
klog!("Welcome to diosix {} ... using device tree at 0x{:x}", env!("CARGO_PKG_VERSION"), device_tree);

/* set up the physical memory management */
match physmem::init(device_tree)
{
Some(s) => klog!("Total physical memory available: {} MiB ({} bytes)", s / 1024 / 1024, s),
None =>
{
kalert!("Physical memory failure: too little RAM, or config error");
return;
}
};
}
@@ -1,12 +1,13 @@
/* diosix top-level code for handling physical memory
/* diosix machine kernel physical memory management
*
* This allocates physical memory to CPU cores to use for private stacks + heaps
* It also allocates contiguous physical memory to supervisor kernels
*
* (c) Chris Williams, 2018.
*
* See LICENSE for usage and copying.
*/

/* notes: the physical memory manager's job is to allocate memory
on a per-page basis to supervisor-level code. */

/* platform-specific code must implement all this */
use platform;
@@ -1,7 +1,4 @@
# machine kernel memory locations and layout for RV32G targets
#
# Top page is a 4KB (4096 byte) page of read-write DRAM
# placed right above the boot-time kernel stacks.
# machine kernel memory locations and layout for common RV32G targets
#
# (c) Chris Williams, 2018.
# See LICENSE for usage and copying.
@@ -12,62 +9,34 @@
# .
# . kernel text, data
# .
# __kernel_cpu_stack_base = base of space for CPU boot + IRQ stacks + per-CPU variables
# .
# . N * 8KB * 2 stacks, one for boot, one for exceptions, N = max CPU cores
# . plus space for per-CPU variables. layout is as follows:
# .
# . variables
# . ~8KiB of interrupt/exception stack <--- mscratch always points here
# . 8KiB of boot stack
# .
# . once boot is over, and the CPU is running workloads, the IRQ stack rolls into the
# . boot stack, making it a per-CPU ~16KiB stack plus per-CPU globals at the top
# .
# __kernel_cpu_stack_top = top of stack space
# __kernel_top_page_base = base of 'top page', 4KB area for kernel variables
# .
# . 4KB of variables, locks, etc
# .
# __kernel_end = end of kernel in memory
# __kernel_pg_stack_base = base of upwards growing physical page stacks

# offsets into __kernel_top_page_base area of core global kernel variables
.equ KERNEL_DEBUG_SPIN_LOCK, (0 * 4)

# must hold KERNEL_PGSTACK_SPIN_LOCK to update KERNEL_PGSTACK_PTR and
# KERNEL_PGSTACK_MAX. KERNEL_PGSTACK_PTR = current phys page stack pointer
# and KERNEL_PGSTACK_MAX is the limit
.equ KERNEL_PGSTACK_SPIN_LOCK, (1 * 4)
.equ KERNEL_PGSTACK_PTR, (2 * 4)
.equ KERNEL_PGSTACK_MAX, (3 * 4)

# number of CPUs running on this system
.equ KERNEL_CPUS_ALIVE, (4 * 4)
# number of bytes of physical RAM present in this system
.equ KERNEL_PHYS_RAM_SIZE, (5 * 4)

# each CPU boot stack is 16KB, bottom 8KB used during startup
# higher 8KB for the interrupt/exception handler, minus space for per-CPU variables
# when startup is over, the IRQ handler can run into the lower 8KBs
.equ KERNEL_BOOT_STACK_OFFSET, (8 * 1024)

# sitting above the top of the per-CPU IRQ stack, pointed to by mscratch, are per-CPU global
# variables uded to store things like the per-CPU environment pointers, per-CPU heaps, etc
# basically, you load mscratch into sp and use it as the IRQ stack pointer, and also access
# variables above mscratch
.equ KERNEL_PER_CPU_VAR_SPACE, (1 * 4) # reserve 1 32-bit word

# offsets from top of IRQ stack into per-CPU variable space
.equ KERNEL_PER_CPU_HEAP_START, (0 * 4) # pointer to first page in per-CPU heap

# initialize the top page of variables
# corrupts t0, t1, t2
.macro _KERNEL_TOP_PAGE_INIT
la t0, __kernel_top_page_base
la t1, __kernel_pg_stack_base
addi t2, t0, KERNEL_PGSTACK_PTR
sw t1, (t2)
addi t2, t0, KERNEL_PGSTACK_MAX
sw t1, (t1)
.endm
# __kernel_globals_page_base = base of the kernel's page of global variables
# .
# . the kernel's global variables and locks are here
# .
# __kernel_globals_page_top = top of the kernel's page of global variables
# __kernel_end = top of the kernel's static footprint
# .
# . per-CPU slabs of physical memory: each CPU core has...
# . exeception / interrupt stack
# . page of private variables
# . private heap space

# describe per-CPU slab. each slab is 1 << 18 bytes in size = 256KB
.equ KERNEL_CPU_SLAB_SHIFT, (18)
.equ KERNEL_CPU_SLAB_SIZE, (1 << KERNEL_CPU_SLAB_SHIFT)
.equ KERNEL_CPU_STACK_SIZE, (32 * 1024)
.equ KERNEL_CPU_STACK_BASE, (0)
.equ KERNEL_CPU_PRIVATE_PAGE_SIZE, (4096)
.equ KENREL_CPU_PRIVATE_PAGE_BASE, (KERNEL_CPU_STACK_BASE + KERNEL_CPU_STACK_SIZE)
.equ KERNEL_CPU_HEAP_AREA_SIZE, (KERNEL_CPU_SLAB_SIZE - KERNEL_CPU_STACK_SIZE - KERNEL_CPU_PRIVATE_PAGE_SIZE)
.equ KERNEL_CPU_HEAP_BASE, (KENREL_CPU_PRIVATE_PAGE_BASE + KERNEL_CPU_PRIVATE_PAGE_SIZE)

# offsets into __kernel_globals_page_base area of core global kernel variables
# it's worth keeping hot variables, like locks, in separate cache lines
# debug output lock
.equ KERNEL_DEBUG_SPIN_LOCK, (0 * 4)

# number of bytes of physical memory, total
.equ KERNEL_PHYS_MEMORY_SIZE, (100 * 4)
# number of CPUs awake at boot
.equ KERNEL_CPU_CORE_COUNT, (101 * 4)
@@ -18,7 +18,7 @@ platform_acquire_debug_spin_lock:
addi sp, sp, -4
sw ra, (sp)

la t0, __kernel_top_page_base
la t0, __kernel_globals_page_base
addi a0, t0, KERNEL_DEBUG_SPIN_LOCK
call platform_acquire_spin_lock

@@ -36,7 +36,7 @@ platform_release_debug_spin_lock:
addi sp, sp, -4
sw ra, (sp)

la t0, __kernel_top_page_base
la t0, __kernel_globals_page_base
addi a0, t0, KERNEL_DEBUG_SPIN_LOCK
call platform_release_spin_lock

@@ -0,0 +1,75 @@
# kernel common low-level entry points for RV32G platforms (Qemu Virt, SiFive U34)
#
# Assumes we're loaded and entered at 0x80000000
# with a0 = CPU/Hart ID number, a1 -> device tree
#
# Works with SMP and UMP. Assumes non-NUMA memory layout
#
# (c) Chris Williams, 2018.
# See LICENSE for usage and copying.

# _start *must* be the first routine in this file
.section .entry
.global _start

# include kernel constants, such as global variable and lock locations
# check this file for static kernel data layout
.include "src/platform/riscv32/common/asm/consts.s"

# hardware physical memory map
# 0x00000000, size: 0x100: Debug ROM/data
# 0x00001000, size: 0x11000: Boot ROM
# 0x00100000, size: 0x1000: Hardware test area
# 0x02000000, size: 0x10000: CLINT (Core Local Interruptor)
# 0x0c000000, size: 0x4000000: PLIC (Platform Level Interrupt Controller)
# 0x80000000: DRAM base (default 128MB, max 2GB) <-- kernel + entered loaded here
#
# see consts.s for top page of global variables locations and other memory layout decisions

# the boot ROM drops each core simultenously here with nothing setup
# this code is assumed to be loaded and running at 0x80000000
# interrupts and exceptions are disabled.
#
# => a0 = CPU core ID, aka hart ID
# a1 = pointer to device tree
# <= never returns
_start:
# each core should grab a slab of memory starting from the end of the kernel.
# in order to scale to many cores, not waste too much memory, and to cope with non-linear
# CPU ID / hart ID, each core will take memory using the follwing atomic counter.
# in other words, memory is allocated on a first come, first served basis
la t0, __kernel_globals_page_base
addi t1, t0, KERNEL_CPU_CORE_COUNT
li t2, 1
amoadd.w t3, t2, (t1)
# t3 = counter just before we incremented it. use this as a multiplier
# from the end of the kernel, using shifts to keep things easy
slli t3, t3, KERNEL_CPU_SLAB_SHIFT
la t1, __kernel_end
add t3, t3, t1
# t3 = base of this CPU's private memory slab

# write the top of the exception / interrupt stack to mscratch
li t1, KERNEL_CPU_STACK_BASE
li t2, KERNEL_CPU_STACK_SIZE
add t4, t2, t1
add t4, t4, t3
# t4 = top of the stack, t2 = stack size, t1 = stack base from slab base
csrrw x0, mscratch, t4

# use the lower half of the exception stack to bring up the environment
# set the boot stack pointer to halfway down the IRQ stack
srli t1, t2, 1
sub sp, t4, t1

# set up early exception/interrupt handling (corrupts t0)
call irq_early_init

# call kmain with CPU ID in a0 and devicetree in a1
la t0, kmain
jalr ra, t0, 0

# fall through to loop rather than crash into random instructions/data
infinite_loop:
wfi
j infinite_loop
@@ -13,6 +13,7 @@

# set up boot interrupt handling on this core so we can catch
# exceptions while the system is initializating
# <= corrupts t0
irq_early_init:
# point core at default machine-level exception/interrupt handler
la t0, irq_machine_handler
Oops, something went wrong.

0 comments on commit 10dbdd6

Please sign in to comment.
You can’t perform that action at this time.