Skip to content
Permalink
Browse files

Enable scheduler's hardware timer for interrupting running threads

  • Loading branch information...
diodesign committed Jan 1, 2019
1 parent 2ce4101 commit 05a8887bbe4231791725a17458de8f92c484df37
@@ -2,7 +2,7 @@

[![Build Status](https://travis-ci.org/diodesign/diosix.svg?branch=master)](https://travis-ci.org/diodesign/diosix) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/diosix/Lobby)

This is a lightweight, secure, and multithreaded multiprocessor hypervisor-microkernel
This is a lightweight, secure, multithreaded, and multiprocessor container-based hypervisor-microkernel
operating system written in Rust for 32-bit and 64-bit RISC-V systems.

It is a work in progress: I'm starting from scratch
@@ -72,4 +72,4 @@ Copyright © Chris Williams and contributors, 2018. See LICENSE for distribu
- src/contrib/spin-rs: Copyright © 2014 Mathijs van de Nes.
- src/contrib/spin-rs/src/atomic.rs: Reimplements Rust's MIT-licensed [core::sync::atomic](https://github.com/rust-lang/rust/blob/master/src/libcore/sync/atomic.rs) API. Original implementation: Copyright © The Rust Project Developers.

And thanks to [David Craven](https://github.com/dvc94ch), [Alex Bradbury](https://github.com/asb), and everyone else who helped bring together Rust, LLVM, and RISC-V; the RISC-V world for designing the CPU cores and system-on-chips, and writing the emulators in the first place; Philipp Oppermann for his guide to writing [kernel-level Rust code](https://os.phil-opp.com/); and to the OSdev community for its [notes and documentation](http://wiki.osdev.org/Main_Page).
And thanks to [David Craven](https://github.com/dvc94ch), [Alex Bradbury](https://github.com/asb), and everyone else who brought Rust, LLVM, and RISC-V together; the RISC-V world for designing the CPU cores and system-on-chips in the first place; [Michael Clark](https://github.com/michaeljclark) and everyone else who worked on [Qemu](https://github.com/riscv/riscv-qemu) and other RISC-V emulators; Philipp Oppermann for his guide to writing [kernel-level Rust code](https://os.phil-opp.com/); and to the OSdev community for its [notes and documentation](http://wiki.osdev.org/Main_Page).
@@ -16,7 +16,6 @@ pub static mut DEBUG_LOCK: Spinlock = kspinlock!();
extern "C" {
fn platform_serial_write_byte(byte: u8);
pub fn platform_cpu_wait();
pub fn platform_get_cpu_id() -> usize;
}

/* top level debug macros */
@@ -25,15 +24,15 @@ extern "C" {
macro_rules! klog
{
($fmt:expr) => (kprintln!("[-] CPU {}: {}", ::cpu::Core::id(), $fmt));
($fmt:expr, $($arg:tt)*) => (kprintln!(concat!("[-] CPU {}: ", $fmt), ::debug::platform_get_cpu_id(), $($arg)*));
($fmt:expr, $($arg:tt)*) => (kprintln!(concat!("[-] CPU {}: ", $fmt), ::cpu::Core::id(), $($arg)*));
}

/* bad news: bug detection, failures, etc */
#[macro_export]
macro_rules! kalert
{
($fmt:expr) => (kprintln!("[!] CPU {}: ALERT: {}", ::cpu::Core::id(), $fmt));
($fmt:expr, $($arg:tt)*) => (kprintln!(concat!("[!] CPU {}: ", $fmt), ::debug::platform_get_cpu_id(), $($arg)*));
($fmt:expr, $($arg:tt)*) => (kprintln!(concat!("[!] CPU {}: ", $fmt), ::cpu::Core::id(), $($arg)*));
}

/* only output if debug build is enabled */
@@ -42,7 +41,7 @@ macro_rules! kalert
macro_rules! kdebug
{
($fmt:expr) => (kprintln!("[?] CPU {}: {}", ::cpu::Core::id(), $fmt));
($fmt:expr, $($arg:tt)*) => (kprintln!(concat!("[?] CPU {}: ", $fmt), ::debug::platform_get_cpu_id(), $($arg)*));
($fmt:expr, $($arg:tt)*) => (kprintln!(concat!("[?] CPU {}: ", $fmt), ::cpu::Core::id(), $($arg)*));
}

#[macro_export]
@@ -24,4 +24,7 @@ pub enum Cause

/* containers */
ContainerAlreadyExists,

/* scheduler and timer */
SchedTimerBadConfig
}
@@ -22,6 +22,7 @@ use platform::common::cpu::PrivilegeMode;
pub extern "C" fn kirq_handler(context: IRQContext)
{
let irq = platform::common::irq::dispatch(context);
klog!("IRQ!");

match irq.irq_type
{
@@ -38,15 +39,23 @@ fn exception(irq: IRQ)
(true, PrivilegeMode::Kernel) =>
{
kalert!(
"Fatal exception in kernel: {} at 0x{:x}, stack 0x{:x}",
irq.debug_cause(),
irq.pc,
irq.sp
);
loop
{}
"Fatal exception in hypervisor: {} at 0x{:x}, stack 0x{:x}",
irq.debug_cause(), irq.pc, irq.sp);
loop {}
},
(false, PrivilegeMode::Kernel) =>
{

},

/* fail on everything else */
(fatal, priviledge) =>
{
kalert!(
"Unhandled IRQ (fatal = {}, priv = {:?}): {} at 0x{:x}, stack 0x{:x}",
fatal, priviledge, irq.debug_cause(), irq.pc, irq.sp);
loop {}
}
(_, _) => (), /* ignore everything else */
}
}

@@ -26,6 +26,7 @@ extern crate hashmap_core;
#[macro_use]
extern crate lazy_static;
extern crate spin;
use spin::Mutex;

/* this will bring in all the hardware-specific code */
extern crate platform;
@@ -51,12 +52,18 @@ use error::Cause;
/* and our builtin supervisor kernel, which runs in its own container(s) */
mod supervisor;

/* tell Rust to use ourr kAllocator to allocate and free heap memory.
/* tell Rust to use our kAllocator to allocate and free heap memory.
while we'll keep track of physical memory, we'll let Rust perform essential
tasks, such as freeing memory when it's no longer needed, pointer checking, etc */
#[global_allocator]
static KERNEL_HEAP: heap::Kallocator = heap::Kallocator;

/* set to true to allow physical CPU cores to start running supervisor code */
lazy_static!
{
static ref INIT_DONE: Mutex<bool> = Mutex::new(false);
}

/* 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 */

@@ -104,25 +111,31 @@ fn kmain(cpu_nr: usize, device_tree_buf: &u8) -> Result<(), Cause>
/* delegate to boot CPU the welcome banner and set up global resources */
if cpu_nr == 0 /* boot CPU is zeroth core */
{
/* initialize global resources */
/* initialize global resources and root container */
init_global(device_tree_buf)?;
init_root_container()?;

/* create root container with 4MB of RAM and max CPU cores */
let root_mem = 4 * 1024 * 1024;
let root_name = "root";
let root_max_vcpu = 2;
container::create_from_builtin(root_name, root_mem, root_max_vcpu)?;

/* create a virtual CPU thread for the root container, starting it in sentry() with
top of allocated memory as the stack pointer */
scheduler::create_thread(root_name, supervisor::main::entry, root_mem, Priority::High);
/* allow other cores to continue */
*(INIT_DONE.lock()) = true;
}
else
{
/* non-boot cores must wait for early initialization to complete */
loop
{
if *(INIT_DONE.lock()) == true
{
break;
}
}
}

/* attach scheduler to timer and enable timer */
/* enable timer on this CPU core to sstart cheduling threads */
scheduler::start();

/* initialization complete. if we make it this far then fall through to infinite loop
waiting for a timer interrupt to come in. when it does fire, this stack will be flattened,
a thread loaded up to run, and this boot thread will disappear like tears in the rain. */
a virtual CPU loaded up to run, and this boot thread will disappear like tears in the rain. */
Ok(())
}

@@ -152,6 +165,8 @@ fn init_global(device_tree: &u8) -> Result<(), Cause>
}
};

scheduler::init(device_tree)?;

/* say hello */
klog!("Welcome to diosix {} ... using device tree at {:p}", env!("CARGO_PKG_VERSION"), device_tree);
klog!("Available RAM: {} MiB ({} bytes), CPU cores: {}", ram_size / 1024 / 1024, ram_size, cpus);
@@ -160,6 +175,21 @@ fn init_global(device_tree: &u8) -> Result<(), Cause>
return Ok(());
}

/* create the root container */
fn init_root_container() -> Result<(), Cause>
{
/* create root container with 4MB of RAM and max CPU cores */
let root_mem = 4 * 1024 * 1024;
let root_name = "root";
let root_max_vcpu = 2;
container::create_from_builtin(root_name, root_mem, root_max_vcpu)?;

/* create a virtual CPU thread for the root container, starting it in sentry() with
top of allocated memory as the stack pointer */
scheduler::create_thread(root_name, supervisor::main::entry, root_mem, Priority::High);
Ok(())
}

/* mandatory error handler for memory allocations */
#[alloc_error_handler]
fn kalloc_error(attempt: core::alloc::Layout) -> !
@@ -5,15 +5,34 @@
* See LICENSE for usage and copying.
*/

use error::Cause;
use spin::Mutex;
use alloc::boxed::Box;
use alloc::collections::linked_list::LinkedList;
use container::ContainerName;
use platform::common::cpu::SupervisorState;
use ::cpu;

/* one tick represents one scheduling period rather than a clock tick */
type Ticks = usize;

/* initialize preemptive scheduling system's timer. this is used to interrupt the running
virtual CPU thread so another can be run next
<= return OK for success, or an error code */
pub fn init(device_tree_buf: &u8) -> Result<(), Cause>
{
match platform::common::timer::init(device_tree_buf)
{
true => Ok(()),
false => Err(Cause::SchedTimerBadConfig)
}
}

/* activate hardware timer and start running threads */
pub fn start()
{
platform::common::timer::start();
}

/* maintain a simple two-level round-robin scheduler. we can make it more fancy later.
the hypervisor tries to dish out CPU time fairly among evironments, and let the
container supervisors work out how best to allocate their time to userspace code.
@@ -93,7 +112,7 @@ pub fn run_thread(to_run: Thread)
_ => ()
}

cpu::context_switch(to_run);
::cpu::context_switch(to_run);
}

/* add the given thread to the appropriate waiting queue. put it to the back
@@ -11,7 +11,7 @@
.align 4

.global irq_early_init
.global platform_set_supervisor_return
.global irq_machine_handler

# include kernel constants, such as stack and lock locations
.include "src/platform/riscv32/common/asm/consts.s"
@@ -37,18 +37,6 @@ irq_early_init:
csrrs x0, mstatus, t0
ret

# set the machine-level flags necessary to return to supervisor mode
# rather than machine mode. context for the supervisor mode is loaded
# elsewhere
platform_set_supervisor_return:
# set 'previous' privilege level to supervisor by clearing bit 12
# and setting bit 11 in mstatus, defining MPP[12:11] as b01 = 1 for supervisor
li t0, 1 << 12
csrrc x0, mstatus, t0
li t0, 1 << 11
csrrs x0, mstatus, t0
ret

# macro to generate store instructions to push given 'reg' register
.macro PUSH_REG reg
sw x\reg, (\reg * 4)(sp)
@@ -93,8 +81,8 @@ irq_machine_handler:
csrrs t1, mepc, x0
csrrs t2, mtval, x0
# riscv sets epc to the address of the syscall instruction, if this was a syscall.
# in which case, we need to advance epc 4 bytes to next instruction.
# otherwise, we're going into a loop
# in which case, we need to advance epc 4 bytes to the next instruction.
# otherwise, we're going into a loop when we return
li t3, 9 # mcause = 9 for environment call from supervisor-to-hypervisor
bne t3, t0, cont # ... all usermode ecalls are handled at the supervisor level
addi t1, t1, 4 # ... and the hypervisor doesn't make ecalls into itself
@@ -0,0 +1,54 @@
# kernel low-level per-CPU core timer control
#
#
# (c) Chris Williams, 2018.
# See LICENSE for usage and copying.

.altmacro

.section .text
.align 4

.global platform_timer_target
.global platform_timer_now
.global platform_timer_irq_enable

# include kernel constants, such as stack and lock locations
.include "src/platform/riscv32/common/asm/consts.s"

# special memory mapped registers for controlling per-CPU timers
# when the value at mtimecmp > mtime then an IRQ is raised
# this is used to drive the scheduling system
# mtime is in a single location. each core has its own mtimecmp
# at mtimecmp + hartid * 8
.equ CLINT_BASE, 0x2000000
.equ mtimecmp, CLINT_BASE + 0x4000
.equ mtime, CLINT_BASE + 0xbff8

# set the per-CPU timer trigger value. when the timer value >= target, IRQ is raised
# => (a0, a1) = trigger on this 64-bit timer value
platform_timer_target:
# safely update the 64-bit timer target register with 32-bit writes
li t0, -1
li t1, mtimecmp
csrrc t2, mhartid, x0
slli t2, t2, 3 # t2 = hartid * 8
add t1, t1, t2 # t1 = mtimecmp + hartid * 8
sw t0, 0(t1)
sw a1, 4(t1)
sw a0, 0(t1)
ret

# return the CPU timer's latest value
# <= a0, a1 = 64-bit value of timer register
platform_timer_now:
li t0, mtime
lw a1, 4(t0)
lw a0, 0(t0)
ret

# enablet he per-CPU incremental timer
platform_timer_irq_enable:
li t0, 1 << 7 # bit 7 = machine timer enable
csrrs x0, mie, t0
ret
@@ -6,22 +6,16 @@
.section .text
.align 4

.global platform_get_cpu_id
.global platform_cpu_private_variables
.global platform_cpu_heap_base
.global platform_cpu_heap_size
.global platform_save_supervisor_state
.global platform_load_supervisor_state
.global platform_set_supervisor_return

# include kernel constants, such as stack and lock locations
.include "src/platform/riscv32/common/asm/consts.s"

# Look up the running core's hardwre-assigned ID
# <= a0 = CPU core / hart ID
platform_get_cpu_id:
csrrc a0, mhartid, x0
ret

# return pointer to this CPU's private variables
# <= a0 = pointer to kernel's CPU structure
platform_cpu_private_variables:
@@ -143,3 +137,15 @@ to_stack_copy_loop:
bnez t2, to_stack_copy_loop

ret

# set the machine-level flags necessary to return to supervisor mode
# rather than machine mode. context for the supervisor mode is loaded
# elsewhere
platform_set_supervisor_return:
# set 'previous' privilege level to supervisor by clearing bit 12
# and setting bit 11 in mstatus, defining MPP[12:11] as b01 = 1 for supervisor
li t0, 1 << 12
csrrc x0, mstatus, t0
li t0, 1 << 11
csrrs x0, mstatus, t0
ret
@@ -19,7 +19,7 @@ extern "C"
static mut CPU_CORE_COUNT: Option<usize> = None;

/* levels of privilege accepted by the kernel */
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum PrivilegeMode
{
Kernel, /* machine-mode kernel */
Oops, something went wrong.

0 comments on commit 05a8887

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