mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
609 lines
20 KiB
Rust
609 lines
20 KiB
Rust
// SPDX-FileCopyrightText: 2020 Sean Cross <sean@xobs.io>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use core::mem;
|
|
static mut PROCESS: *mut ProcessImpl = 0xff80_1000 as *mut ProcessImpl;
|
|
pub const MAX_THREAD: TID = 31;
|
|
pub const EXCEPTION_TID: TID = 1;
|
|
pub const INITIAL_TID: TID = 2;
|
|
pub const IRQ_TID: TID = 0;
|
|
|
|
use crate::arch::mem::PAGE_SIZE;
|
|
use crate::services::ProcessInner;
|
|
use xous_kernel::{ProcessInit, ProcessStartup, ThreadInit, PID, TID};
|
|
|
|
// use crate::args::KernelArguments;
|
|
pub const DEFAULT_STACK_SIZE: usize = 128 * 1024;
|
|
pub const MAX_PROCESS_COUNT: usize = 64;
|
|
// pub use crate::arch::mem::DEFAULT_STACK_TOP;
|
|
|
|
/// This is the address a program will jump to in order to return from an ISR.
|
|
pub const RETURN_FROM_ISR: usize = 0xff80_2000;
|
|
|
|
/// This is the address a thread will return to when it exits.
|
|
pub const EXIT_THREAD: usize = 0xff80_3000;
|
|
|
|
/// This is the address a thread will return to when it finishes handling an exception.
|
|
pub const RETURN_FROM_EXCEPTION_HANDLER: usize = 0xff80_4000;
|
|
|
|
// Thread IDs have three possible meaning:
|
|
// Logical Thread ID: What the user sees
|
|
// Thread Context Index: An index into the thread slice
|
|
// Hardware Thread ID: The index that the ISR uses
|
|
//
|
|
// The Hardware Thread ID is always equal to the Thread Context
|
|
// Index, minus one. For example, the default thread ID is
|
|
// Hardware Thread ID 1 is Thread Context Index 0.
|
|
// The Logical Thread ID is equal to the Hardware Thread ID
|
|
// plus one again. This is because the ISR context is Thread
|
|
// Context Index 0.
|
|
// Therefore, the first Logical Thread ID is 1, which maps
|
|
// to Hardware Thread ID 2, which is Thread Context Index 1.
|
|
//
|
|
// +-----------------+-----------------+-----------------+
|
|
// | Thread ID | Context Index | Hardware Thread |
|
|
// +=================+=================+=================+
|
|
// | ISR Context | 0 | 1 |
|
|
// | 1 | 1 | 2 |
|
|
// | 2 | 2 | 3 |
|
|
|
|
// ProcessImpl occupies a multiple of pages mapped to virtual address `0xff80_1000`.
|
|
// Each thread is 128 bytes (32 4-byte registers). The first "thread" does not exist,
|
|
// and instead is any bookkeeping information related to the process.
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[repr(C)]
|
|
struct ProcessImpl {
|
|
/// Used by the interrupt handler to calculate offsets
|
|
scratch: usize,
|
|
|
|
/// The currently-active thread for this process. This must
|
|
/// be the 2nd item, because the ISR directly writes this value.
|
|
hardware_thread: usize,
|
|
|
|
/// Global parameters used by the operating system
|
|
pub inner: ProcessInner,
|
|
|
|
/// The last thread ID that was allocated
|
|
last_tid_allocated: u8,
|
|
|
|
/// Pad everything to 128 bytes, so the Thread slice starts at
|
|
/// offset 128.
|
|
_padding: [u32; 13],
|
|
|
|
/// This enables the kernel to keep track of threads in the
|
|
/// target process, and know which threads are ready to
|
|
/// receive messages.
|
|
threads: [Thread; MAX_THREAD],
|
|
}
|
|
|
|
/// Singleton process table. Each process in the system gets allocated from this table.
|
|
struct ProcessTable {
|
|
/// The process upon which the current syscall is operating
|
|
current: PID,
|
|
|
|
/// The actual table contents. `true` if a process is allocated,
|
|
/// `false` if it is free.
|
|
table: [bool; MAX_PROCESS_COUNT],
|
|
}
|
|
|
|
static mut PROCESS_TABLE: ProcessTable = ProcessTable {
|
|
current: unsafe { PID::new_unchecked(1) },
|
|
table: [false; MAX_PROCESS_COUNT],
|
|
};
|
|
|
|
#[repr(C)]
|
|
#[cfg(baremetal)]
|
|
/// The stage1 bootloader sets up some initial processes. These are reported
|
|
/// to us as (satp, entrypoint, sp) tuples, which can be turned into a structure.
|
|
/// The first element is always the kernel.
|
|
pub struct InitialProcess {
|
|
/// The RISC-V SATP value, which includes the offset of the root page
|
|
/// table plus the process ID.
|
|
pub satp: usize,
|
|
|
|
/// Where execution begins
|
|
pub entrypoint: usize,
|
|
|
|
/// Address of the top of the stack
|
|
pub sp: usize,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct Process {
|
|
pid: PID,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
/// Everything required to keep track of a single thread of execution.
|
|
pub struct Thread {
|
|
/// Storage for all RISC-V registers, minus $zero
|
|
pub registers: [usize; 31],
|
|
|
|
/// The return address. Note that if this thread was created because of an
|
|
/// `ecall` instruction, you will need to add `4` to this before returning,
|
|
/// to prevent that instruction from getting executed again. If this is 0,
|
|
/// then this thread is not valid.
|
|
pub sepc: usize,
|
|
}
|
|
|
|
impl Process {
|
|
pub fn current() -> Process {
|
|
let pid = unsafe { PROCESS_TABLE.current };
|
|
let hardware_pid = (riscv::register::satp::read().bits() >> 22) & ((1 << 9) - 1);
|
|
assert_eq!((pid.get() as usize), hardware_pid);
|
|
Process { pid }
|
|
}
|
|
|
|
/// Mark this process as running on the current core
|
|
pub fn activate(&mut self) -> Result<(), xous_kernel::Error> {
|
|
Ok(())
|
|
}
|
|
|
|
/// Calls the provided function with the current inner process state.
|
|
pub fn with_inner<F, R>(f: F) -> R
|
|
where
|
|
F: FnOnce(&ProcessInner) -> R,
|
|
{
|
|
let process = unsafe { &*PROCESS };
|
|
f(&process.inner)
|
|
}
|
|
|
|
/// Calls the provided function with the current inner process state.
|
|
pub fn with_current<F, R>(f: F) -> R
|
|
where
|
|
F: FnOnce(&Process) -> R,
|
|
{
|
|
let process = Self::current();
|
|
f(&process)
|
|
}
|
|
|
|
/// Calls the provided function with the current inner process state.
|
|
pub fn with_current_mut<F, R>(f: F) -> R
|
|
where
|
|
F: FnOnce(&mut Process) -> R,
|
|
{
|
|
let mut process = Self::current();
|
|
f(&mut process)
|
|
}
|
|
|
|
pub fn with_inner_mut<F, R>(f: F) -> R
|
|
where
|
|
F: FnOnce(&mut ProcessInner) -> R,
|
|
{
|
|
let process = unsafe { &mut *PROCESS };
|
|
f(&mut process.inner)
|
|
}
|
|
|
|
pub fn current_thread_mut(&mut self) -> &mut Thread {
|
|
let process = unsafe { &mut *PROCESS };
|
|
assert!(process.hardware_thread != 0, "thread number was 0");
|
|
&mut process.threads[process.hardware_thread - 1]
|
|
}
|
|
|
|
pub fn current_thread(&self) -> &Thread {
|
|
let process = unsafe { &mut *PROCESS };
|
|
&mut process.threads[process.hardware_thread - 1]
|
|
// self.thread(process.hardware_thread - 1)
|
|
}
|
|
|
|
pub fn current_tid(&self) -> TID {
|
|
let process = unsafe { &*PROCESS };
|
|
process.hardware_thread - 1
|
|
}
|
|
|
|
pub fn thread_exists(&self, tid: TID) -> bool {
|
|
self.thread(tid).sepc != 0
|
|
}
|
|
|
|
/// Set the current thread number.
|
|
pub fn set_tid(&mut self, thread: TID) -> Result<(), xous_kernel::Error> {
|
|
let mut process = unsafe { &mut *PROCESS };
|
|
klog!("Switching to thread {}", thread);
|
|
assert!(
|
|
thread <= process.threads.len(),
|
|
"attempt to switch to an invalid thread {}",
|
|
thread
|
|
);
|
|
process.hardware_thread = thread + 1;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn thread_mut(&mut self, thread: TID) -> &mut Thread {
|
|
let process = unsafe { &mut *PROCESS };
|
|
assert!(
|
|
thread <= process.threads.len(),
|
|
"attempt to retrieve an invalid thread {}",
|
|
thread
|
|
);
|
|
&mut process.threads[thread]
|
|
}
|
|
|
|
pub fn thread(&self, thread: TID) -> &Thread {
|
|
let process = unsafe { &mut *PROCESS };
|
|
assert!(
|
|
thread <= process.threads.len(),
|
|
"attempt to retrieve an invalid thread {}",
|
|
thread
|
|
);
|
|
&process.threads[thread]
|
|
}
|
|
|
|
pub fn for_each_thread_mut<F>(&self, mut op: F)
|
|
where
|
|
F: FnMut(TID, &Thread),
|
|
{
|
|
let process = unsafe { &mut *PROCESS };
|
|
for (idx, thread) in process.threads.iter_mut().enumerate() {
|
|
// Ignore threads that have no PC, and ignore the ISR thread
|
|
if thread.sepc == 0 || idx == IRQ_TID {
|
|
continue;
|
|
}
|
|
op(idx, thread);
|
|
}
|
|
}
|
|
|
|
pub fn find_free_thread(&self) -> Option<TID> {
|
|
let process = unsafe { &mut *PROCESS };
|
|
let start_tid = process.last_tid_allocated as usize;
|
|
let a = &process.threads[start_tid..process.threads.len()];
|
|
let b = &process.threads[0..start_tid];
|
|
for (index, thread) in a.iter().chain(b.iter()).enumerate() {
|
|
let mut tid = index + start_tid;
|
|
if tid >= process.threads.len() {
|
|
tid -= process.threads.len()
|
|
}
|
|
|
|
if tid != IRQ_TID && tid != EXCEPTION_TID && thread.sepc == 0 {
|
|
process.last_tid_allocated = tid as _;
|
|
return Some(tid as TID);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn set_thread_result(&mut self, thread_nr: TID, result: xous_kernel::Result) {
|
|
let vals = unsafe { mem::transmute::<_, [usize; 8]>(result) };
|
|
let thread = self.thread_mut(thread_nr);
|
|
for (idx, reg) in vals.iter().enumerate() {
|
|
thread.registers[9 + idx] = *reg;
|
|
}
|
|
}
|
|
|
|
pub fn retry_instruction(&mut self, tid: TID) -> Result<(), xous_kernel::Error> {
|
|
let process = unsafe { &mut *PROCESS };
|
|
let mut thread = &mut process.threads[tid];
|
|
if thread.sepc >= 4 {
|
|
thread.sepc -= 4;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Initialize this process thread with the given entrypoint and stack
|
|
/// addresses.
|
|
pub fn setup_process(pid: PID, thread_init: ThreadInit) -> Result<(), xous_kernel::Error> {
|
|
let mut process = unsafe { &mut *PROCESS };
|
|
let tid = INITIAL_TID;
|
|
|
|
assert_eq!(
|
|
pid,
|
|
crate::arch::current_pid(),
|
|
"hardware pid does not match setup pid"
|
|
);
|
|
assert!(tid != IRQ_TID, "tried to init using the irq thread");
|
|
assert!(
|
|
mem::size_of::<ProcessImpl>() == PAGE_SIZE,
|
|
"Process size is {}, not PAGE_SIZE ({}) (Thread size: {}, array: {}, Inner: {})",
|
|
mem::size_of::<ProcessImpl>(),
|
|
PAGE_SIZE,
|
|
mem::size_of::<Thread>(),
|
|
mem::size_of::<[Thread; MAX_THREAD + 1]>(),
|
|
mem::size_of::<ProcessInner>(),
|
|
);
|
|
assert!(
|
|
tid - 1 < process.threads.len(),
|
|
"tried to init a thread that's out of range"
|
|
);
|
|
assert!(
|
|
tid == INITIAL_TID,
|
|
"tried to init using a thread {} that wasn't {}. This probably isn't what you want.",
|
|
tid,
|
|
INITIAL_TID
|
|
);
|
|
|
|
klog!("Setting up new process {}", pid.get());
|
|
unsafe {
|
|
let pid_idx = (pid.get() as usize) - 1;
|
|
assert!(
|
|
!PROCESS_TABLE.table[pid_idx],
|
|
"process {} is already allocated",
|
|
pid
|
|
);
|
|
PROCESS_TABLE.table[pid_idx] = true;
|
|
}
|
|
|
|
// By convention, thread 0 is the trap thread. Therefore, thread 1 is
|
|
// the first default thread. There is an offset of 1 due to how the
|
|
// interrupt handler functions.
|
|
process.hardware_thread = tid + 1;
|
|
|
|
// Reset the thread state, since it's possibly uninitialized memory
|
|
for thread in process.threads.iter_mut() {
|
|
*thread = Default::default();
|
|
}
|
|
|
|
let mut thread = &mut process.threads[tid];
|
|
|
|
thread.sepc = unsafe { core::mem::transmute::<_, usize>(thread_init.call) };
|
|
thread.registers[1] = thread_init.stack.as_ptr() as usize + thread_init.stack.len();
|
|
thread.registers[9] = thread_init.arg1;
|
|
thread.registers[10] = thread_init.arg2;
|
|
thread.registers[11] = thread_init.arg3;
|
|
thread.registers[12] = thread_init.arg4;
|
|
|
|
klog!("thread_init: {:x?} thread: {:x?}", thread_init, thread);
|
|
|
|
#[cfg(any(feature = "debug-print", feature = "print-panics"))]
|
|
{
|
|
let pid = pid.get();
|
|
if pid != 1 {
|
|
klog!(
|
|
"initializing PID {} thread {} with entrypoint {:08x}, stack @ {:08x}, arg {:08x}",
|
|
pid, tid, thread.sepc, thread.registers[1], thread.registers[9],
|
|
);
|
|
}
|
|
}
|
|
|
|
process.inner = Default::default();
|
|
|
|
// Mark the stack as "unallocated-but-free"
|
|
let init_sp = (thread_init.stack.as_ptr() as usize) & !0xfff;
|
|
if init_sp != 0 {
|
|
let stack_size = thread_init.stack.len();
|
|
crate::mem::MemoryManager::with_mut(|memory_manager| {
|
|
memory_manager
|
|
.reserve_range(
|
|
init_sp as *mut u8,
|
|
stack_size,
|
|
xous_kernel::MemoryFlags::R | xous_kernel::MemoryFlags::W,
|
|
)
|
|
.expect("couldn't reserve stack")
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn setup_thread(
|
|
&mut self,
|
|
new_tid: TID,
|
|
setup: ThreadInit,
|
|
) -> Result<(), xous_kernel::Error> {
|
|
let entrypoint = unsafe { core::mem::transmute::<_, usize>(setup.call) };
|
|
// Create the new context and set it to run in the new address space.
|
|
let pid = self.pid.get();
|
|
let thread = self.thread_mut(new_tid);
|
|
// println!("Setting up thread {}, pid {}", new_tid, pid);
|
|
let sp = setup.stack.as_ptr() as usize + setup.stack.len();
|
|
if sp <= 16 {
|
|
return Err(xous_kernel::Error::BadAddress);
|
|
}
|
|
crate::arch::syscall::invoke(
|
|
thread,
|
|
pid == 1,
|
|
entrypoint,
|
|
(sp - 16) & !0xf,
|
|
EXIT_THREAD,
|
|
&[setup.arg1, setup.arg2, setup.arg3, setup.arg4],
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
/// Destroy a given thread and return its return value.
|
|
///
|
|
/// # Returns
|
|
/// The return value of the function
|
|
///
|
|
/// # Errors
|
|
/// xous::ThreadNotAvailable - the thread did not exist
|
|
pub fn destroy_thread(&mut self, tid: TID) -> Result<usize, xous_kernel::Error> {
|
|
let thread = self.thread_mut(tid);
|
|
|
|
// Ensure this thread is valid
|
|
if thread.sepc == 0 || tid == IRQ_TID {
|
|
return Err(xous_kernel::Error::ThreadNotAvailable);
|
|
}
|
|
|
|
// thread.registers[0] == x1
|
|
// thread.registers[1] == x2
|
|
// ...
|
|
// thread.registers[4] == x5 == t0
|
|
// ...
|
|
// thread.registers[9] == x10 == a0
|
|
// thread.registers[10] == x11 == a1
|
|
let return_value = thread.registers[9];
|
|
|
|
for val in &mut thread.registers {
|
|
*val = 0;
|
|
}
|
|
thread.sepc = 0;
|
|
|
|
Ok(return_value)
|
|
}
|
|
|
|
pub fn print_all_threads(&self) {
|
|
let process = unsafe { &mut *PROCESS };
|
|
for (tid_idx, &thread) in process.threads.iter().enumerate() {
|
|
let tid = tid_idx + 1;
|
|
if thread.registers[1] != 0 {
|
|
Self::print_thread(tid, &thread);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn print_current_thread(&self) {
|
|
let thread = self.current_thread();
|
|
let tid = self.current_tid();
|
|
Self::print_thread(tid, thread);
|
|
}
|
|
|
|
pub fn print_thread(_tid: TID, _thread: &Thread) {
|
|
println!("Thread {}:", _tid);
|
|
println!(
|
|
"PC:{:08x} SP:{:08x} RA:{:08x}",
|
|
_thread.sepc, _thread.registers[1], _thread.registers[0]
|
|
);
|
|
println!(
|
|
"GP:{:08x} TP:{:08x}",
|
|
_thread.registers[2], _thread.registers[3]
|
|
);
|
|
println!(
|
|
"T0:{:08x} T1:{:08x} T2:{:08x}",
|
|
_thread.registers[4], _thread.registers[5], _thread.registers[6]
|
|
);
|
|
println!(
|
|
"T3:{:08x} T4:{:08x} T5:{:08x} T6:{:08x}",
|
|
_thread.registers[27],
|
|
_thread.registers[28],
|
|
_thread.registers[29],
|
|
_thread.registers[30]
|
|
);
|
|
println!(
|
|
"S0:{:08x} S1:{:08x} S2:{:08x} S3:{:08x}",
|
|
_thread.registers[7],
|
|
_thread.registers[8],
|
|
_thread.registers[17],
|
|
_thread.registers[18]
|
|
);
|
|
println!(
|
|
"S4:{:08x} S5:{:08x} S6:{:08x} S7:{:08x}",
|
|
_thread.registers[19],
|
|
_thread.registers[20],
|
|
_thread.registers[21],
|
|
_thread.registers[22]
|
|
);
|
|
println!(
|
|
"S8:{:08x} S9:{:08x} S10:{:08x} S11:{:08x}",
|
|
_thread.registers[23],
|
|
_thread.registers[24],
|
|
_thread.registers[25],
|
|
_thread.registers[26]
|
|
);
|
|
println!(
|
|
"A0:{:08x} A1:{:08x} A2:{:08x} A3:{:08x}",
|
|
_thread.registers[9],
|
|
_thread.registers[10],
|
|
_thread.registers[11],
|
|
_thread.registers[12]
|
|
);
|
|
println!(
|
|
"A4:{:08x} A5:{:08x} A6:{:08x} A7:{:08x}",
|
|
_thread.registers[13],
|
|
_thread.registers[14],
|
|
_thread.registers[15],
|
|
_thread.registers[16]
|
|
);
|
|
}
|
|
|
|
/// Create a brand-new process. The memory space must already be set up.
|
|
pub fn create(
|
|
pid: PID,
|
|
init_data: ProcessInit,
|
|
services: &mut crate::SystemServices,
|
|
) -> Result<ProcessStartup, xous_kernel::Error> {
|
|
let current_pid = current_pid();
|
|
|
|
services.get_process(pid)?.mapping.activate()?;
|
|
let server_id = services.create_server_id()?;
|
|
let server_id_array = server_id.to_array();
|
|
|
|
// klog!("previous process init was {:x?}", init_data);
|
|
let initial_thread = ThreadInit::new(
|
|
init_data.start.get(),
|
|
init_data.stack,
|
|
server_id_array[0] as _,
|
|
server_id_array[1] as _,
|
|
server_id_array[2] as _,
|
|
server_id_array[3] as _,
|
|
);
|
|
|
|
Self::setup_process(pid, initial_thread).unwrap();
|
|
|
|
services.create_server_with_address(pid, server_id, false)?;
|
|
|
|
// klog!("activating parent process {}", current_pid.get());
|
|
services.get_process(current_pid)?.mapping.activate()?;
|
|
// klog!("connecting to server in parent process");
|
|
let cid = services.connect_process_to_server(current_pid, server_id)?;
|
|
|
|
services.send_memory(
|
|
init_data.text.as_ptr() as *mut usize,
|
|
pid,
|
|
init_data.text_destination.get() as *mut usize,
|
|
init_data.text.len(),
|
|
)?;
|
|
Ok(ProcessStartup::new(pid, cid))
|
|
}
|
|
|
|
pub fn destroy(pid: PID) -> Result<(), xous_kernel::Error> {
|
|
let mut process_table = unsafe { &mut PROCESS_TABLE };
|
|
let pid_idx = pid.get() as usize - 1;
|
|
if pid_idx >= process_table.table.len() {
|
|
panic!("attempted to destroy PID that exceeds table index: {}", pid);
|
|
}
|
|
process_table.table[pid_idx] = false;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn find_thread<F>(&self, op: F) -> Option<(TID, &mut Thread)>
|
|
where
|
|
F: Fn(TID, &Thread) -> bool,
|
|
{
|
|
let process = unsafe { &mut *PROCESS };
|
|
for (idx, thread) in process.threads.iter_mut().enumerate() {
|
|
if thread.sepc == 0 {
|
|
continue;
|
|
}
|
|
if op(idx, thread) {
|
|
return Some((idx, thread));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
impl Thread {
|
|
/// The current stack pointer for this thread
|
|
pub fn stack_pointer(&self) -> usize {
|
|
self.registers[1]
|
|
}
|
|
|
|
pub fn a0(&self) -> usize {
|
|
self.registers[9]
|
|
}
|
|
|
|
pub fn a1(&self) -> usize {
|
|
self.registers[10]
|
|
}
|
|
}
|
|
|
|
pub fn set_current_pid(pid: PID) {
|
|
let pid_idx = (pid.get() - 1) as usize;
|
|
unsafe {
|
|
let mut pt = &mut PROCESS_TABLE;
|
|
|
|
match pt.table.get(pid_idx) {
|
|
None | Some(false) => panic!("PID {} does not exist", pid),
|
|
_ => (),
|
|
}
|
|
pt.current = pid;
|
|
}
|
|
}
|
|
|
|
pub fn current_pid() -> PID {
|
|
unsafe { PROCESS_TABLE.current }
|
|
}
|
|
|
|
pub fn current_tid() -> TID {
|
|
unsafe { ((*PROCESS).hardware_thread) - 1 }
|
|
}
|