feat: user-space processes
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
//! aarch64 common boot logic
|
||||
|
||||
use crate::arch::{aarch64::reg::CPACR_EL1, machine};
|
||||
use crate::arch::{aarch64::reg::{CPACR_EL1, CNTKCTL_EL1}, machine};
|
||||
use crate::dev::{fdt::DeviceTree, irq::IntSource, Device};
|
||||
use crate::debug::Level;
|
||||
//use crate::debug::Level;
|
||||
use crate::mem::{
|
||||
self, heap,
|
||||
phys::{self, PageUsage},
|
||||
@@ -18,6 +18,9 @@ extern "C" fn __aa64_bsp_main(fdt_base: usize) -> ! {
|
||||
// Disable FP instruction trapping
|
||||
CPACR_EL1.modify(CPACR_EL1::FPEN::TrapNone);
|
||||
|
||||
// Disable CNTPCT and CNTFRQ trapping from EL0
|
||||
CNTKCTL_EL1.modify(CNTKCTL_EL1::EL0PCTEN::SET);
|
||||
|
||||
extern "C" {
|
||||
static aa64_el1_vectors: u8;
|
||||
}
|
||||
|
||||
@@ -5,24 +5,30 @@
|
||||
|
||||
.set PT_REGS_SIZE, 16 * 6
|
||||
|
||||
__aa64_ctx_enter_kernel:
|
||||
__aa64_ctx_enter_user:
|
||||
ldp x0, x1, [sp, #0]
|
||||
msr sp_el0, x0
|
||||
msr ttbr0_el1, x1
|
||||
|
||||
tst x0, x0
|
||||
mov x0, #5
|
||||
bne 1f
|
||||
b 2f
|
||||
1:
|
||||
mov x0, #4
|
||||
2:
|
||||
|
||||
msr spsr_el1, x0
|
||||
msr spsr_el1, xzr
|
||||
ldp x0, x1, [sp, #16]
|
||||
msr elr_el1, x1
|
||||
add sp, sp, #32
|
||||
|
||||
mov x1, xzr
|
||||
__return_to_user:
|
||||
eret
|
||||
|
||||
__aa64_ctx_enter_kernel:
|
||||
msr sp_el0, xzr
|
||||
msr ttbr0_el1, xzr
|
||||
|
||||
mov x0, #5
|
||||
msr spsr_el1, x0
|
||||
ldp x0, x1, [sp, #0]
|
||||
msr elr_el1, x1
|
||||
add sp, sp, #16
|
||||
|
||||
mov x1, xzr
|
||||
eret
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ struct Stack {
|
||||
pub struct Context {
|
||||
/// Thread's kernel stack pointer
|
||||
pub k_sp: usize, // 0x00
|
||||
/// Thread's translation table physical address with ASID
|
||||
pub ttbr0: usize, // 0x08
|
||||
|
||||
stack_base_phys: usize,
|
||||
stack_page_count: usize,
|
||||
@@ -25,7 +23,24 @@ pub struct Context {
|
||||
|
||||
impl Context {
|
||||
/// Constructs a new kernel-space thread context
|
||||
pub fn kernel(entry: usize, arg: usize, ttbr0: usize, ustack: usize) -> Self {
|
||||
pub fn kernel(entry: usize, arg: usize) -> Self {
|
||||
let mut stack = Stack::new(8);
|
||||
|
||||
stack.push(entry);
|
||||
stack.push(arg);
|
||||
|
||||
stack.setup_common(__aa64_ctx_enter_kernel as usize);
|
||||
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
|
||||
stack_base_phys: stack.bp,
|
||||
stack_page_count: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new user-space thread context
|
||||
pub fn user(entry: usize, arg: usize, ttbr0: usize, ustack: usize) -> Self {
|
||||
let mut stack = Stack::new(8);
|
||||
|
||||
stack.push(entry);
|
||||
@@ -33,22 +48,10 @@ impl Context {
|
||||
stack.push(ttbr0);
|
||||
stack.push(ustack);
|
||||
|
||||
stack.push(__aa64_ctx_enter_kernel as usize); // x30/lr
|
||||
stack.push(0); // x29
|
||||
stack.push(0); // x28
|
||||
stack.push(0); // x27
|
||||
stack.push(0); // x26
|
||||
stack.push(0); // x25
|
||||
stack.push(0); // x24
|
||||
stack.push(0); // x23
|
||||
stack.push(0); // x22
|
||||
stack.push(0); // x21
|
||||
stack.push(0); // x20
|
||||
stack.push(0); // x19
|
||||
stack.setup_common(__aa64_ctx_enter_user as usize);
|
||||
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
ttbr0,
|
||||
|
||||
stack_base_phys: stack.bp,
|
||||
stack_page_count: 8,
|
||||
@@ -86,6 +89,21 @@ impl Stack {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_common(&mut self, entry: usize) {
|
||||
self.push(entry); // x30/lr
|
||||
self.push(0); // x29
|
||||
self.push(0); // x28
|
||||
self.push(0); // x27
|
||||
self.push(0); // x26
|
||||
self.push(0); // x25
|
||||
self.push(0); // x24
|
||||
self.push(0); // x23
|
||||
self.push(0); // x22
|
||||
self.push(0); // x21
|
||||
self.push(0); // x20
|
||||
self.push(0); // x19
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: usize) {
|
||||
if self.bp == self.sp {
|
||||
panic!("Stack overflow");
|
||||
@@ -100,6 +118,7 @@ impl Stack {
|
||||
|
||||
extern "C" {
|
||||
fn __aa64_ctx_enter_kernel();
|
||||
fn __aa64_ctx_enter_user();
|
||||
fn __aa64_ctx_switch(dst: *mut Context, src: *mut Context);
|
||||
fn __aa64_ctx_switch_to(dst: *mut Context);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
//! AArch64 exception handling
|
||||
|
||||
use crate::arch::machine;
|
||||
use crate::debug::Level;
|
||||
use crate::dev::irq::{IntController, IrqContext};
|
||||
use cortex_a::registers::{ESR_EL1, FAR_EL1};
|
||||
use tock_registers::interfaces::Readable;
|
||||
use crate::debug::Level;
|
||||
|
||||
/// Trapped SIMD/FP functionality
|
||||
pub const EC_FP_TRAP: u64 = 0b000111;
|
||||
/// Data Abort at current EL
|
||||
pub const EC_DATA_ABORT_ELX: u64 = 0b100101;
|
||||
/// Data Abort at lower EL
|
||||
pub const EC_DATA_ABORT_EL0: u64 = 0b100100;
|
||||
/// SVC instruction in AA64 state
|
||||
pub const EC_SVC_AA64: u64 = 0b010101;
|
||||
|
||||
@@ -74,7 +76,7 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) {
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
match err_code {
|
||||
EC_DATA_ABORT_ELX => {
|
||||
EC_DATA_ABORT_ELX | EC_DATA_ABORT_EL0 => {
|
||||
let far = FAR_EL1.get();
|
||||
dump_data_abort(Level::Error, esr, far);
|
||||
}
|
||||
@@ -87,11 +89,11 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) {
|
||||
}
|
||||
|
||||
errorln!(
|
||||
"Unhandled exception at ELR={:#018x}, ESR={:#010x}, exc ctx @ {:p}",
|
||||
"Unhandled exception at ELR={:#018x}, ESR={:#010x}",
|
||||
exc.elr_el1,
|
||||
esr,
|
||||
exc
|
||||
);
|
||||
errorln!("Error code: {:#08b}", err_code);
|
||||
|
||||
panic!("Unhandled exception");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//! CNTKCTL_EL1 register
|
||||
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields,
|
||||
};
|
||||
|
||||
register_bitfields! {
|
||||
u64,
|
||||
#[allow(missing_docs)]
|
||||
/// Counter-timer Kernel Control Register
|
||||
pub CNTKCTL_EL1 [
|
||||
/// If set, disables CNTPCT and CNTFRQ trapping from EL0
|
||||
EL0PCTEN OFFSET(0) NUMBITS(1) []
|
||||
]
|
||||
}
|
||||
|
||||
/// CNTKCTL_EL1 register
|
||||
pub struct Reg;
|
||||
|
||||
impl Readable for Reg {
|
||||
type T = u64;
|
||||
type R = CNTKCTL_EL1::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
let mut tmp;
|
||||
unsafe {
|
||||
asm!("mrs {}, cntkctl_el1", out(reg) tmp);
|
||||
}
|
||||
tmp
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Reg {
|
||||
type T = u64;
|
||||
type R = CNTKCTL_EL1::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
asm!("msr cntkctl_el1, {}", in(reg) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CNTKCTL_EL1 register
|
||||
pub const CNTKCTL_EL1: Reg = Reg;
|
||||
@@ -2,3 +2,6 @@
|
||||
|
||||
pub mod cpacr_el1;
|
||||
pub use cpacr_el1::CPACR_EL1;
|
||||
|
||||
pub mod cntkctl_el1;
|
||||
pub use cntkctl_el1::CNTKCTL_EL1;
|
||||
|
||||
@@ -33,6 +33,10 @@ pub mod util;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||
unsafe {
|
||||
asm!("msr daifset, #2");
|
||||
}
|
||||
|
||||
errorln!("Panic: {:?}", pi);
|
||||
// TODO
|
||||
loop {}
|
||||
|
||||
@@ -28,7 +28,7 @@ pub enum PageUsage {
|
||||
/// Translation tables
|
||||
Paging,
|
||||
/// Userspace page
|
||||
UserStack,
|
||||
UserPrivate,
|
||||
}
|
||||
|
||||
/// Data structure representing a single physical memory page
|
||||
|
||||
@@ -44,8 +44,11 @@ bitflags! {
|
||||
const PXN = 1 << 53;
|
||||
|
||||
// AP field
|
||||
// Default behavior is: read-write for EL1, no access for EL0
|
||||
/// If set, the page referred to by this entry is read-only for both EL0/EL1
|
||||
const AP_BOTH_READONLY = 3 << 6;
|
||||
/// If set, the page referred to by this entry is read-write for both EL0/EL1
|
||||
const AP_BOTH_READWRITE = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+46
-84
@@ -61,110 +61,62 @@ struct Phdr<E: Elf> {
|
||||
align: E::Xword,
|
||||
}
|
||||
|
||||
unsafe fn load_bytes(
|
||||
space: &mut Space,
|
||||
dst_virt: usize,
|
||||
src: *const u8,
|
||||
size: usize,
|
||||
flags: usize,
|
||||
) -> Result<(), Errno> {
|
||||
let mut off = 0usize;
|
||||
let mut rem = size;
|
||||
fn map_flags(elf_flags: usize) -> MapAttributes {
|
||||
let mut dst_flags = MapAttributes::NOT_GLOBAL | MapAttributes::SH_OUTER;
|
||||
|
||||
// TODO unaligned loads
|
||||
assert!(dst_virt & 0xFFF == 0);
|
||||
|
||||
while rem != 0 {
|
||||
let page_idx = off / mem::PAGE_SIZE;
|
||||
let page_off = off % mem::PAGE_SIZE;
|
||||
let count = core::cmp::min(rem, mem::PAGE_SIZE - page_off);
|
||||
|
||||
let page = phys::alloc_page(PageUsage::Kernel)?;
|
||||
let mut dst_flags = MapAttributes::NOT_GLOBAL | MapAttributes::SH_OUTER;
|
||||
|
||||
if flags & (1 << 0) /* PF_X */ == 0 {
|
||||
dst_flags |= MapAttributes::UXN | MapAttributes::PXN;
|
||||
}
|
||||
|
||||
match (flags & (3 << 1)) >> 1 {
|
||||
// No access: not sure if such mapping should exist at all
|
||||
0 => todo!(),
|
||||
// Write-only: not sure if such mapping should exist at all
|
||||
1 => todo!(),
|
||||
// Read-only
|
||||
2 => dst_flags |= MapAttributes::AP_BOTH_READONLY,
|
||||
// Read+Write
|
||||
3 => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
debugln!(
|
||||
"Mapping {:#x} {:?}",
|
||||
dst_virt + page_idx * mem::PAGE_SIZE,
|
||||
dst_flags
|
||||
);
|
||||
space.map(dst_virt + page_idx * mem::PAGE_SIZE, page, dst_flags)?;
|
||||
|
||||
let dst =
|
||||
core::slice::from_raw_parts_mut(mem::virtualize(page + page_off) as *mut u8, count);
|
||||
let src = core::slice::from_raw_parts(src.add(off), count);
|
||||
|
||||
dst.copy_from_slice(src);
|
||||
|
||||
rem -= count;
|
||||
off += count;
|
||||
if elf_flags & (1 << 0) /* PF_X */ == 0 {
|
||||
dst_flags |= MapAttributes::UXN | MapAttributes::PXN;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
match (elf_flags & (3 << 1)) >> 1 {
|
||||
// No access: not sure if such mapping should exist at all
|
||||
0 => todo!(),
|
||||
// Write-only: not sure if such mapping should exist at all
|
||||
1 => todo!(),
|
||||
// Read-only
|
||||
2 => dst_flags |= MapAttributes::AP_BOTH_READONLY,
|
||||
// Read+Write
|
||||
3 => dst_flags |= MapAttributes::AP_BOTH_READWRITE,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
dst_flags
|
||||
}
|
||||
|
||||
unsafe fn zero_bytes(
|
||||
unsafe fn load_bytes<F>(
|
||||
space: &mut Space,
|
||||
dst_virt: usize,
|
||||
read: F,
|
||||
size: usize,
|
||||
flags: usize,
|
||||
) -> Result<(), Errno> {
|
||||
) -> Result<(), Errno>
|
||||
where
|
||||
F: Fn(usize, &mut [u8]) -> Result<(), Errno>,
|
||||
{
|
||||
let dst_page_off = dst_virt & 0xFFF;
|
||||
let dst_page = dst_virt & !0xFFF;
|
||||
let mut off = 0usize;
|
||||
let mut rem = size;
|
||||
|
||||
while rem != 0 {
|
||||
let page_idx = (dst_virt + off - (dst_virt & !0xFFF)) / mem::PAGE_SIZE;
|
||||
let page_off = (dst_virt + off) % mem::PAGE_SIZE;
|
||||
let page_idx = (dst_page_off + off) / mem::PAGE_SIZE;
|
||||
let page_off = (dst_page_off + off) % mem::PAGE_SIZE;
|
||||
let count = core::cmp::min(rem, mem::PAGE_SIZE - page_off);
|
||||
|
||||
let page = phys::alloc_page(PageUsage::Kernel)?;
|
||||
let mut dst_flags = MapAttributes::NOT_GLOBAL | MapAttributes::SH_OUTER;
|
||||
|
||||
if flags & (1 << 0) /* PF_X */ == 0 {
|
||||
dst_flags |= MapAttributes::UXN | MapAttributes::PXN;
|
||||
}
|
||||
|
||||
match (flags & (3 << 1)) >> 1 {
|
||||
// No access: not sure if such mapping should exist at all
|
||||
0 => todo!(),
|
||||
// Write-only: not sure if such mapping should exist at all
|
||||
1 => todo!(),
|
||||
// Read-only
|
||||
2 => dst_flags |= MapAttributes::AP_BOTH_READONLY,
|
||||
// Read+Write
|
||||
3 => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
debugln!(
|
||||
"Mapping {:#x} {:?}",
|
||||
dst_virt + page_idx * mem::PAGE_SIZE,
|
||||
dst_flags
|
||||
);
|
||||
if let Err(e) = space.map(dst_virt + page_idx * mem::PAGE_SIZE, page, dst_flags) {
|
||||
// TODO fetch existing mapping and test flag equality instead
|
||||
// if flags differ, bail out
|
||||
if let Err(e) = space.map(dst_page + page_idx * mem::PAGE_SIZE, page, map_flags(flags)) {
|
||||
if e != Errno::AlreadyExists {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
let dst =
|
||||
core::slice::from_raw_parts_mut(mem::virtualize(page + page_off) as *mut u8, count);
|
||||
dst.fill(0);
|
||||
let dst_page_virt = mem::virtualize(page + page_off);
|
||||
let dst = core::slice::from_raw_parts_mut(dst_page_virt as *mut u8, count);
|
||||
|
||||
read(off, dst)?;
|
||||
|
||||
rem -= count;
|
||||
off += count;
|
||||
@@ -201,7 +153,13 @@ pub fn load_elf(space: &mut Space, elf_base: *const u8) -> Result<usize, Errno>
|
||||
load_bytes(
|
||||
space,
|
||||
phdr.vaddr as usize,
|
||||
elf_base.add(phdr.offset as usize),
|
||||
|off, dst| {
|
||||
let src = elf_base.add(phdr.offset as usize + off);
|
||||
assert!(off + dst.len() <= phdr.filesz as usize);
|
||||
let src_slice = core::slice::from_raw_parts(src, dst.len());
|
||||
dst.copy_from_slice(src_slice);
|
||||
Ok(())
|
||||
},
|
||||
phdr.filesz as usize,
|
||||
phdr.flags as usize,
|
||||
)?;
|
||||
@@ -211,9 +169,13 @@ pub fn load_elf(space: &mut Space, elf_base: *const u8) -> Result<usize, Errno>
|
||||
if phdr.memsz > phdr.filesz {
|
||||
let len = (phdr.memsz - phdr.filesz) as usize;
|
||||
unsafe {
|
||||
zero_bytes(
|
||||
load_bytes(
|
||||
space,
|
||||
phdr.vaddr as usize + phdr.filesz as usize,
|
||||
|_, dst| {
|
||||
dst.fill(0);
|
||||
Ok(())
|
||||
},
|
||||
len,
|
||||
phdr.flags as usize,
|
||||
)?;
|
||||
|
||||
+187
-101
@@ -7,11 +7,12 @@ use crate::mem::{
|
||||
};
|
||||
use crate::sync::IrqSafeNullLock;
|
||||
use crate::util::InitOnce;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::{BTreeMap, VecDeque};
|
||||
use alloc::rc::Rc;
|
||||
use core::cell::UnsafeCell;
|
||||
use core::fmt;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
use alloc::boxed::Box;
|
||||
use error::Errno;
|
||||
|
||||
pub use crate::arch::platform::context::{self, Context};
|
||||
@@ -21,21 +22,41 @@ pub mod elf;
|
||||
/// Wrapper type for a process struct reference
|
||||
pub type ProcessRef = Rc<UnsafeCell<Process>>;
|
||||
|
||||
/// Wrapper type for process ID
|
||||
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Pid(u32);
|
||||
|
||||
/// List of possible process states
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ProcessState {
|
||||
/// Process is ready to be executed and/or is scheduled for it
|
||||
Ready,
|
||||
/// Process is currently running or is in system call/interrupt handler
|
||||
Running,
|
||||
/// Process has finished execution and is waiting to be reaped
|
||||
Finished,
|
||||
/// Process is waiting for some external event
|
||||
Waiting,
|
||||
}
|
||||
|
||||
/// Structure describing an operating system process
|
||||
#[allow(dead_code)]
|
||||
pub struct Process {
|
||||
ctx: Context,
|
||||
space: &'static mut Space,
|
||||
id: u32,
|
||||
// TODO move to Option<Box<>>ed user data struct
|
||||
space: Option<&'static mut Space>,
|
||||
state: ProcessState,
|
||||
id: Pid,
|
||||
}
|
||||
|
||||
struct SchedulerInner {
|
||||
// TODO the process list itself is not a scheduler-related thing so maybe
|
||||
// move it outside?
|
||||
processes: BTreeMap<u32, ProcessRef>,
|
||||
queue: VecDeque<u32>,
|
||||
idle: u32,
|
||||
current: Option<u32>,
|
||||
processes: BTreeMap<Pid, ProcessRef>,
|
||||
queue: VecDeque<Pid>,
|
||||
idle: Option<Pid>,
|
||||
current: Option<Pid>,
|
||||
}
|
||||
|
||||
/// Process scheduler state and queues
|
||||
@@ -43,54 +64,68 @@ pub struct Scheduler {
|
||||
inner: InitOnce<IrqSafeNullLock<SchedulerInner>>,
|
||||
}
|
||||
|
||||
static LAST_PID: AtomicU32 = AtomicU32::new(0);
|
||||
impl Pid {
|
||||
/// Kernel idle process always has PID of zero
|
||||
pub const IDLE: Self = Self(0 | Self::KERNEL_BIT);
|
||||
|
||||
const KERNEL_BIT: u32 = 1 << 31;
|
||||
|
||||
/// Allocates a new kernel-space PID
|
||||
pub fn new_kernel() -> Self {
|
||||
static LAST: AtomicU32 = AtomicU32::new(0);
|
||||
let id = LAST.fetch_add(1, Ordering::Relaxed);
|
||||
assert!(id & Self::KERNEL_BIT == 0, "Out of kernel PIDs");
|
||||
Self(id | Self::KERNEL_BIT)
|
||||
}
|
||||
|
||||
/// Allocates a new user-space PID.
|
||||
///
|
||||
/// First user PID is #1.
|
||||
pub fn new_user() -> Self {
|
||||
static LAST: AtomicU32 = AtomicU32::new(1);
|
||||
let id = LAST.fetch_add(1, Ordering::Relaxed);
|
||||
assert!(id < 256, "Out of user PIDs");
|
||||
Self(id)
|
||||
}
|
||||
|
||||
/// Returns `true` if this PID belongs to a kernel process
|
||||
pub fn is_kernel(self) -> bool {
|
||||
self.0 & Self::KERNEL_BIT != 0
|
||||
}
|
||||
|
||||
/// Returns address space ID of a user-space process.
|
||||
///
|
||||
/// Panics if called on kernel process PID.
|
||||
pub fn asid(self) -> u8 {
|
||||
assert!(!self.is_kernel());
|
||||
self.0 as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Pid(#{}{})",
|
||||
if self.is_kernel() { "K" } else { "U" },
|
||||
self.0 & !Self::KERNEL_BIT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SchedulerInner {
|
||||
const USTACK_VIRT_TOP: usize = 0x100000000;
|
||||
|
||||
fn new_kernel<F: FnOnce(&mut Space) -> Result<usize, Errno>>(
|
||||
&mut self,
|
||||
loader: F,
|
||||
ustack_pages: usize,
|
||||
arg: usize,
|
||||
) -> u32 {
|
||||
let id = LAST_PID.fetch_add(1, Ordering::Relaxed);
|
||||
if id == 256 {
|
||||
panic!("Ran out of ASIDs (TODO FIXME)");
|
||||
}
|
||||
let space = Space::alloc_empty().unwrap();
|
||||
|
||||
let ustack_virt_bottom = Self::USTACK_VIRT_TOP - ustack_pages * mem::PAGE_SIZE;
|
||||
for i in 0..ustack_pages {
|
||||
let page = phys::alloc_page(PageUsage::Kernel).unwrap();
|
||||
space
|
||||
.map(
|
||||
ustack_virt_bottom + i * mem::PAGE_SIZE,
|
||||
page,
|
||||
MapAttributes::SH_OUTER
|
||||
| MapAttributes::NOT_GLOBAL
|
||||
| MapAttributes::UXN
|
||||
| MapAttributes::PXN,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let entry = loader(space).unwrap();
|
||||
fn new_kernel(&mut self, entry: extern "C" fn(usize) -> !, arg: usize) -> Pid {
|
||||
let id = Pid::new_kernel();
|
||||
|
||||
let proc = Process {
|
||||
ctx: Context::kernel(
|
||||
entry,
|
||||
arg,
|
||||
((space as *mut _ as usize) - mem::KERNEL_OFFSET) | ((id as usize) << 48),
|
||||
if ustack_pages != 0 {
|
||||
Self::USTACK_VIRT_TOP
|
||||
} else {
|
||||
0
|
||||
},
|
||||
),
|
||||
space,
|
||||
ctx: Context::kernel(entry as usize, arg),
|
||||
space: None,
|
||||
state: ProcessState::Ready,
|
||||
id,
|
||||
};
|
||||
debugln!("Created kernel process with PID {}", id);
|
||||
debugln!("Created kernel process: {}", id);
|
||||
|
||||
assert!(self
|
||||
.processes
|
||||
@@ -100,19 +135,15 @@ impl SchedulerInner {
|
||||
id
|
||||
}
|
||||
|
||||
fn new_idle(&mut self) -> u32 {
|
||||
self.new_kernel(|_| Ok(idle_fn as usize), 0, 0)
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
let mut this = Self {
|
||||
processes: BTreeMap::new(),
|
||||
queue: VecDeque::new(),
|
||||
idle: 0,
|
||||
idle: None,
|
||||
current: None,
|
||||
};
|
||||
|
||||
this.idle = this.new_idle();
|
||||
this.idle = Some(this.new_kernel(idle_fn, 0));
|
||||
|
||||
this
|
||||
}
|
||||
@@ -122,16 +153,8 @@ impl Scheduler {
|
||||
/// Constructs a new kernel-space process with `entry` and `arg`.
|
||||
/// Returns resulting process ID
|
||||
// TODO see the first TODO here
|
||||
pub fn new_kernel<F: FnOnce(&mut Space) -> Result<usize, Errno>>(
|
||||
&self,
|
||||
loader: F,
|
||||
ustack_pages: usize,
|
||||
arg: usize,
|
||||
) -> u32 {
|
||||
self.inner
|
||||
.get()
|
||||
.lock()
|
||||
.new_kernel(loader, ustack_pages, arg)
|
||||
pub fn new_kernel(&self, entry: extern "C" fn(usize) -> !, arg: usize) -> Pid {
|
||||
self.inner.get().lock().new_kernel(entry, arg)
|
||||
}
|
||||
|
||||
/// Initializes inner data structure:
|
||||
@@ -143,7 +166,7 @@ impl Scheduler {
|
||||
}
|
||||
|
||||
/// Schedules a thread for execution
|
||||
pub fn enqueue(&self, pid: u32) {
|
||||
pub fn enqueue(&self, pid: Pid) {
|
||||
self.inner.get().lock().queue.push_back(pid);
|
||||
}
|
||||
|
||||
@@ -156,13 +179,15 @@ impl Scheduler {
|
||||
let thread = {
|
||||
let mut inner = self.inner.get().lock();
|
||||
let id = if inner.queue.is_empty() {
|
||||
inner.idle
|
||||
inner.idle.unwrap()
|
||||
} else {
|
||||
inner.queue.pop_front().unwrap()
|
||||
};
|
||||
|
||||
inner.current = Some(id);
|
||||
inner.processes.get(&id).unwrap().clone()
|
||||
let proc = inner.processes.get(&id).unwrap().clone();
|
||||
(*proc.get()).state = ProcessState::Running;
|
||||
proc
|
||||
};
|
||||
|
||||
(*thread.get()).ctx.enter();
|
||||
@@ -170,23 +195,38 @@ impl Scheduler {
|
||||
|
||||
/// Switches to the next task scheduled for execution. If there're
|
||||
/// none present in the queue, switches to the idle task.
|
||||
pub fn switch(&self) {
|
||||
pub fn switch(&self, discard: bool) {
|
||||
let (from, to) = {
|
||||
let mut inner = self.inner.get().lock();
|
||||
let current = inner.current.unwrap();
|
||||
// Put the process into the back of the queue
|
||||
inner.queue.push_back(current);
|
||||
|
||||
if !discard && current != Pid::IDLE {
|
||||
// Put the process into the back of the queue
|
||||
inner.queue.push_back(current);
|
||||
}
|
||||
|
||||
let next = if inner.queue.is_empty() {
|
||||
inner.idle
|
||||
inner.idle.unwrap()
|
||||
} else {
|
||||
inner.queue.pop_front().unwrap()
|
||||
};
|
||||
|
||||
inner.current = Some(next);
|
||||
(
|
||||
inner.processes.get(¤t).unwrap().clone(),
|
||||
inner.processes.get(&next).unwrap().clone(),
|
||||
)
|
||||
let from = inner.processes.get(¤t).unwrap().clone();
|
||||
let to = inner.processes.get(&next).unwrap().clone();
|
||||
|
||||
if !discard {
|
||||
unsafe {
|
||||
assert_eq!((*from.get()).state, ProcessState::Running);
|
||||
(*from.get()).state = ProcessState::Ready;
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
assert_eq!((*to.get()).state, ProcessState::Ready);
|
||||
(*to.get()).state = ProcessState::Running;
|
||||
}
|
||||
|
||||
(from, to)
|
||||
};
|
||||
|
||||
if !Rc::ptr_eq(&from, &to) {
|
||||
@@ -197,7 +237,7 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a Rc-reference to currently running process
|
||||
pub fn current_process(&self) -> ProcessRef {
|
||||
let inner = self.inner.get().lock();
|
||||
let current = inner.current.unwrap();
|
||||
@@ -206,7 +246,26 @@ impl Scheduler {
|
||||
}
|
||||
|
||||
impl Process {
|
||||
/// Returns current process Rc-reference.
|
||||
///
|
||||
/// See [Scheduler::current_process].
|
||||
#[inline]
|
||||
pub fn this() -> ProcessRef {
|
||||
SCHED.current_process()
|
||||
}
|
||||
|
||||
/// Terminates a process.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: only allowed to be called on "self" process at this moment.
|
||||
pub unsafe fn exit(&mut self) -> ! {
|
||||
self.state = ProcessState::Finished;
|
||||
SCHED.switch(true);
|
||||
panic!("This code should never run");
|
||||
}
|
||||
|
||||
/// Loads a new program into process address space
|
||||
pub fn execve<F: FnOnce(&mut Space) -> Result<usize, Errno>>(
|
||||
&mut self,
|
||||
loader: F,
|
||||
@@ -217,64 +276,82 @@ impl Process {
|
||||
asm!("msr daifset, #2");
|
||||
}
|
||||
|
||||
let id = if self.id.is_kernel() {
|
||||
let r = Pid::new_user();
|
||||
debugln!(
|
||||
"Process downgrades from kernel to user: {} -> {}",
|
||||
self.id,
|
||||
r
|
||||
);
|
||||
r
|
||||
} else {
|
||||
self.id
|
||||
};
|
||||
|
||||
let ustack_pages = 4;
|
||||
let new_space = Space::alloc_empty()?;
|
||||
let new_space_phys = ((new_space as *mut _ as usize) - mem::KERNEL_OFFSET); // | ((id as usize) << 48),
|
||||
let new_space_phys = (new_space as *mut _ as usize) - mem::KERNEL_OFFSET;
|
||||
|
||||
let ustack_virt_bottom = SchedulerInner::USTACK_VIRT_TOP - ustack_pages * mem::PAGE_SIZE;
|
||||
for i in 0..ustack_pages {
|
||||
let page = phys::alloc_page(PageUsage::Kernel).unwrap();
|
||||
let page = phys::alloc_page(PageUsage::UserPrivate).unwrap();
|
||||
let flags = MapAttributes::SH_OUTER
|
||||
| MapAttributes::NOT_GLOBAL
|
||||
| MapAttributes::UXN
|
||||
| MapAttributes::PXN
|
||||
| MapAttributes::AP_BOTH_READWRITE;
|
||||
new_space
|
||||
.map(
|
||||
ustack_virt_bottom + i * mem::PAGE_SIZE,
|
||||
page,
|
||||
MapAttributes::SH_OUTER
|
||||
| MapAttributes::NOT_GLOBAL
|
||||
| MapAttributes::UXN
|
||||
| MapAttributes::PXN,
|
||||
)
|
||||
.map(ustack_virt_bottom + i * mem::PAGE_SIZE, page, flags)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let entry = loader(new_space)?;
|
||||
|
||||
self.ctx = Context::kernel(
|
||||
debugln!("Will now enter at {:#x}", entry);
|
||||
self.ctx = Context::user(
|
||||
entry,
|
||||
0,
|
||||
new_space_phys | ((self.id as usize) << 48),
|
||||
arg,
|
||||
new_space_phys | ((id.asid() as usize) << 48),
|
||||
SchedulerInner::USTACK_VIRT_TOP,
|
||||
);
|
||||
self.space = new_space;
|
||||
self.space = Some(new_space);
|
||||
// TODO drop old address space
|
||||
|
||||
unsafe {
|
||||
self.ctx.enter();
|
||||
}
|
||||
panic!("This should not run");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
extern "C" fn idle_fn(_a: usize) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
extern "C" fn init_fn(initrd_ptr: usize) -> ! {
|
||||
debugln!("Running kernel init process");
|
||||
macro_rules! spawn {
|
||||
(fn ($dst_arg:ident : usize) $body:block, $src_arg:expr) => {{
|
||||
#[inline(never)]
|
||||
extern "C" fn __inner_func($dst_arg : usize) -> ! {
|
||||
$body;
|
||||
#[allow(unreachable_code)]
|
||||
unsafe { (*Process::this().get()).exit() }
|
||||
}
|
||||
|
||||
let (start, _end) = unsafe { *(initrd_ptr as *const (usize, usize)) };
|
||||
let proc = unsafe { &mut *SCHED.current_process().get() };
|
||||
proc.execve(|space| elf::load_elf(space, start as *const u8), 0).unwrap();
|
||||
loop {}
|
||||
$crate::proc::SCHED.enqueue($crate::proc::SCHED.new_kernel(__inner_func, $src_arg));
|
||||
}};
|
||||
|
||||
(fn () $body:block) => (spawn!(fn (_arg: usize) $body, 0usize))
|
||||
}
|
||||
|
||||
/// Performs a task switch.
|
||||
///
|
||||
/// See [Scheduler::switch]
|
||||
pub fn switch() {
|
||||
SCHED.switch();
|
||||
SCHED.switch(false);
|
||||
}
|
||||
|
||||
static SCHED: Scheduler = Scheduler {
|
||||
///
|
||||
pub static SCHED: Scheduler = Scheduler {
|
||||
inner: InitOnce::new(),
|
||||
};
|
||||
|
||||
@@ -289,7 +366,16 @@ pub unsafe fn enter(initrd: Option<(usize, usize)>) -> ! {
|
||||
SCHED.init();
|
||||
if let Some((start, end)) = initrd {
|
||||
let initrd = Box::into_raw(Box::new((mem::virtualize(start), mem::virtualize(end))));
|
||||
SCHED.enqueue(SCHED.new_kernel(|_| Ok(init_fn as usize), 0, initrd as usize));
|
||||
|
||||
spawn!(fn (initrd_ptr: usize) {
|
||||
debugln!("Running kernel init process");
|
||||
|
||||
let (start, _end) = unsafe { *(initrd_ptr as *const (usize, usize)) };
|
||||
let proc = unsafe { &mut *SCHED.current_process().get() };
|
||||
proc.execve(|space| elf::load_elf(space, start as *const u8), 0)
|
||||
.unwrap();
|
||||
panic!("This code should not run");
|
||||
}, initrd as usize);
|
||||
}
|
||||
SCHED.enter();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user