From 351c22cf0e4bfabdd4aff77217afa2611ae2f703 Mon Sep 17 00:00:00 2001 From: Brad Campbell Date: Mon, 3 Jun 2019 18:21:34 -0400 Subject: [PATCH 1/5] kernel: UserlandKernelBoundary v2 The initial version of the `UserlandKernelBoundary` trait abstracted details about context switching on cortex-m platforms out of process.rs, however, the trait was very much designed around the structure of how cortex-m handles switching between privilege modes. After working on RISC-V context switching, it is clear some of the interface does not map to RISC-V very well. To make `UserlandKernelBoundary` more generic, this commit makes two general changes to the trait. 1. Rather than having a separate "getter" for retreiving which syscall was actually called after learning that the process stopped running because it called a syscall, we now return the syscall information directly with the context switch reason. Arguably, this is how it should have been done before, and this change actually has nothing to do with making UKB more generic. 2. The Cortex-M context switch uses a special stack frame created by the SVC call. This approach is not used in RISC-V. Therefore, the terminology around "popping" and "pushing" stack frames doesn't make sense in all cases. Also, having `pop_stack_frame()` at all was a bit of blur between the kernel loop and the context switch interface details as pop was only called with a yield(). What is generic is the idea that we want to be able to call a function in the process that the process should execute when it starts (or resumes) running. That function takes over the old "push" function. "pop" has been removed. I believe that this new interface will be implementable in RISC-V, as well as Cortex-M, and actually improves the boundary between the kernel loop and the context switching code. This commit also then updates process.rs and sched.rs to use this new interface. This required some minor changes to process.rs as these functions are essentially passed through process.rs from the kernel sched loop to the UKB code. --- kernel/src/process.rs | 72 +++++++++++---------------- kernel/src/sched.rs | 27 +++++----- kernel/src/syscall.rs | 113 ++++++++++++++++++++++++++++++++---------- 3 files changed, 128 insertions(+), 84 deletions(-) diff --git a/kernel/src/process.rs b/kernel/src/process.rs index ff9f99cfc0..ac5b00df48 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -203,19 +203,12 @@ pub trait ProcessType { // functions for processes that are architecture specific - /// Get the syscall that the process called. - unsafe fn get_syscall(&self) -> Option; - /// Set the return value the process should see when it begins executing /// again after the syscall. unsafe fn set_syscall_return_value(&self, return_value: isize); - /// Remove the last stack frame from the process. - unsafe fn pop_syscall_stack_frame(&self); - - /// Replace the last stack frame with the new function call. This function - /// is what should be executed when the process is resumed. - unsafe fn push_function_call(&self, callback: FunctionCall); + /// Set the function that is to be executed when the process is resumed. + unsafe fn set_process_function(&self, callback: FunctionCall); /// Context switch to a specific process. unsafe fn switch_to(&self) -> Option; @@ -292,6 +285,12 @@ pub enum State { /// The process has caused a fault. Fault, + + /// The process has never actually been executed. This of course happens + /// when the board first boots and the kernel has not switched to any + /// processes yet. It can also happen if an process is terminated and all + /// of its state is reset as if it has not been executed yet. + Unstarted, } /// The reaction the kernel should take when an app encounters a fault. @@ -567,7 +566,7 @@ impl ProcessType for Process<'a, C> { app_flash_address.offset(self.header.get_init_function_offset() as isize) as usize }; - self.state.set(State::Yielded); + self.state.set(State::Unstarted); // Need to reset the grant region. unsafe { @@ -806,36 +805,15 @@ impl ProcessType for Process<'a, C> { self.process_name } - unsafe fn get_syscall(&self) -> Option { - let last_syscall = self.chip.userspace_kernel_boundary().get_syscall(self.sp()); - - // Record this for debugging purposes. - self.debug.map(|debug| { - debug.syscall_count += 1; - debug.last_syscall = last_syscall; - }); - - last_syscall - } - unsafe fn set_syscall_return_value(&self, return_value: isize) { - self.chip - .userspace_kernel_boundary() - .set_syscall_return_value(self.sp(), return_value); - } - - unsafe fn pop_syscall_stack_frame(&self) { let mut stored_state = self.stored_state.get(); - let new_stack_pointer = self - .chip + self.chip .userspace_kernel_boundary() - .pop_syscall_stack_frame(self.sp(), &mut stored_state); - self.current_stack_pointer - .set(new_stack_pointer as *const u8); + .set_syscall_return_value(self.sp(), &mut stored_state, return_value); self.stored_state.set(stored_state); } - unsafe fn push_function_call(&self, callback: FunctionCall) { + unsafe fn set_process_function(&self, callback: FunctionCall) { // First we need to get how much memory is available for this app's // stack. Since the stack is at the bottom of the process's memory // region, this is straightforward. @@ -845,12 +823,18 @@ impl ProcessType for Process<'a, C> { // stack. Architecture-specific code handles actually doing the push // since we don't know the details of exactly what the stack frames look // like. - let stored_state = self.stored_state.get(); - match self.chip.userspace_kernel_boundary().push_function_call( + let mut stored_state = self.stored_state.get(); + + // Check to see if this will be the first function call for this + // process. + let first_function = self.state.get() == State::Unstarted; + + match self.chip.userspace_kernel_boundary().set_process_function( self.sp(), remaining_stack_bytes, + &mut stored_state, callback, - &stored_state, + first_function, ) { Ok(stack_bottom) => { // If we got an `Ok` with the new stack pointer we are all @@ -872,11 +856,13 @@ impl ProcessType for Process<'a, C> { } Err(bad_stack_bottom) => { - // If we got an Error, then there was no room to add this - // stack frame. This process has essentially faulted, so we - // mark it as such. We also update the debugging metadata so - // that if the process fault message prints then it should - // be easier to debug that the process exceeded its stack. + // If we got an Error, then there was not enough room on the + // stack to allow the process to execute this function given the + // details of the particular architecture this is running on. + // This process has essentially faulted, so we mark it as such. + // We also update the debugging metadata so that if the process + // fault message prints then it should be easier to debug that + // the process exceeded its stack. self.debug.map(|debug| { let bad_stack_bottom = bad_stack_bottom as *const u8; if bad_stack_bottom < debug.min_stack_pointer { @@ -1229,7 +1215,7 @@ impl Process<'a, C> { process.flash = slice::from_raw_parts(app_flash_address, app_flash_size); process.stored_state = Cell::new(Default::default()); - process.state = Cell::new(State::Yielded); + process.state = Cell::new(State::Unstarted); process.fault_response = fault_response; process.mpu_config = MapCell::new(mpu_config); diff --git a/kernel/src/sched.rs b/kernel/src/sched.rs index e562167d99..7c6f29ee89 100644 --- a/kernel/src/sched.rs +++ b/kernel/src/sched.rs @@ -278,26 +278,25 @@ impl Kernel { // Let process deal with it as appropriate. process.set_fault_state(); } - Some(ContextSwitchReason::SyscallFired) => { + Some(ContextSwitchReason::SyscallFired { syscall }) => { // Handle each of the syscalls. - match process.get_syscall() { - Some(Syscall::MEMOP { operand, arg0 }) => { + match syscall { + Syscall::MEMOP { operand, arg0 } => { let res = memop::memop(process, operand, arg0); process.set_syscall_return_value(res.into()); } - Some(Syscall::YIELD) => { + Syscall::YIELD => { process.set_yielded_state(); - process.pop_syscall_stack_frame(); // There might be already enqueued callbacks continue; } - Some(Syscall::SUBSCRIBE { + Syscall::SUBSCRIBE { driver_number, subdriver_number, callback_ptr, appdata, - }) => { + } => { let callback_ptr = NonNull::new(callback_ptr); let callback = callback_ptr .map(|ptr| Callback::new(appid, appdata, ptr.cast())); @@ -314,12 +313,12 @@ impl Kernel { ); process.set_syscall_return_value(res.into()); } - Some(Syscall::COMMAND { + Syscall::COMMAND { driver_number, subdriver_number, arg0, arg1, - }) => { + } => { let res = platform.with_driver( driver_number, @@ -332,12 +331,12 @@ impl Kernel { ); process.set_syscall_return_value(res.into()); } - Some(Syscall::ALLOW { + Syscall::ALLOW { driver_number, subdriver_number, allow_address, allow_size, - }) => { + } => { let res = platform.with_driver(driver_number, |driver| { match driver { Some(d) => { @@ -353,7 +352,6 @@ impl Kernel { }); process.set_syscall_return_value(res.into()); } - _ => {} } } Some(ContextSwitchReason::TimesliceExpired) => { @@ -372,14 +370,15 @@ impl Kernel { } } } - process::State::Yielded => match process.dequeue_task() { + process::State::Yielded | process::State::Unstarted => match process.dequeue_task() + { // If the process is yielded it might be waiting for a // callback. If there is a task scheduled for this process // go ahead and set the process to execute it. None => break, Some(cb) => match cb { Task::FunctionCall(ccb) => { - process.push_function_call(ccb); + process.set_process_function(ccb); } Task::IPC((otherapp, ipc_type)) => { ipc.map_or_else( diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 531a77f418..942add3661 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -5,7 +5,7 @@ use core::fmt::Write; use crate::process; /// The syscall number assignments. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Syscall { /// Return to the kernel to allow other processes to execute or to wait for /// interrupts and callbacks. @@ -52,8 +52,8 @@ pub enum Syscall { /// Why the process stopped executing and execution returned to the kernel. #[derive(PartialEq)] pub enum ContextSwitchReason { - /// Process called a syscall. - SyscallFired, + /// Process called a syscall. Also returns the syscall and relevant values. + SyscallFired { syscall: Syscall }, /// Process triggered the hardfault handler. Fault, /// Process exceeded its timeslice. @@ -71,42 +71,54 @@ pub trait UserspaceKernelBoundary { /// registers that aren't stored on the stack. type StoredState: Default + Copy; - /// Get the syscall that the process called with the appropriate arguments. - unsafe fn get_syscall(&self, stack_pointer: *const usize) -> Option; - /// Set the return value the process should see when it begins executing - /// again after the syscall. - unsafe fn set_syscall_return_value(&self, stack_pointer: *const usize, return_value: isize); - - /// Remove the last stack frame from the process and return the new stack - /// pointer location. + /// again after the syscall. This will only be called after a process has + /// called a syscall. /// - /// This function assumes that `stack_pointer` is valid and at the end of - /// the process stack, that there is at least one stack frame on the - /// stack, and that that frame is the syscall. - unsafe fn pop_syscall_stack_frame( + /// To help implementations, both the current stack pointer of the process + /// and the saved state for the process are provided. The `return_value` is + /// the value that should be passed to the process so that when it resumes + /// executing it knows the return value of the syscall it called. + unsafe fn set_syscall_return_value( &self, stack_pointer: *const usize, state: &mut Self::StoredState, - ) -> *mut usize; + return_value: isize, + ); - /// Add a stack frame with the new function call. This function - /// is what should be executed when the process is resumed. + /// Set the function that the process should execute when it is resumed. + /// This has two major uses: 1) sets up the initial function call to + /// `_start` when the process is started for the very first time; 2) tells + /// the process to execute a callback function after calling `yield()`. + /// + /// ### Arguments + /// + /// - `stack_pointer` is the address of the stack pointer for the current + /// app. + /// - `remaining_stack_memory` is the number of bytes below the + /// `stack_pointer` that is allocated for the process. This value is + /// checked by the implementer to ensure that there is room for this stack + /// frame without overflowing the stack. + /// - `state` is the stored state for this process. + /// - `callback` is the function that should be executed when the process + /// resumes. + /// - `first_function` is true if this is the first time this process is + /// being run. This allows a `UserspaceKernelBoundary` implementation to + /// assume there are no stack frames on the process's stack. /// - /// `remaining_stack_memory` is the number of bytes below the - /// `stack_pointer` that is allocated for the process. This value is checked - /// by the implementer to ensure that there is room for this stack frame - /// without overflowing the stack. + /// ### Return /// - /// Returns `Ok` with the new stack pointer after adding the stack frame if - /// there was room for the stack frame, and an error with where the stack - /// would have ended up if the function call had been added otherwise. - unsafe fn push_function_call( + /// Returns `Ok` or `Err` with the current address of the stack pointer for + /// the process. One reason for returning `Err` is that adding the function + /// call requires adding to the stack, and there is insufficient room on the + /// stack to add the function call. + unsafe fn set_process_function( &self, stack_pointer: *const usize, remaining_stack_memory: usize, + state: &mut Self::StoredState, callback: process::FunctionCall, - state: &Self::StoredState, + first_function: bool, ) -> Result<*mut usize, *mut usize>; /// Context switch to a specific process. @@ -132,3 +144,50 @@ pub trait UserspaceKernelBoundary { writer: &mut Write, ); } + +/// Helper function for converting raw values passed back from an application +/// into a `Syscall` type in Tock. +/// +/// Different architectures may have different mechanisms for passing +/// information about what syscall an app called, but they will have have to +/// convert the series of raw values into a more useful Rust type. While +/// implementations are free to do this themselves, this provides a generic +/// helper function which should help reduce duplicated code. +/// +/// The mappings between raw `syscall_number` values and the associated syscall +/// type are specified and fixed by Tock. After that, this function only +/// converts raw values to more meaningful types based on the syscall. +pub fn arguments_to_syscall( + syscall_number: u8, + r0: usize, + r1: usize, + r2: usize, + r3: usize, +) -> Option { + match syscall_number { + 0 => Some(Syscall::YIELD), + 1 => Some(Syscall::SUBSCRIBE { + driver_number: r0, + subdriver_number: r1, + callback_ptr: r2 as *mut (), + appdata: r3, + }), + 2 => Some(Syscall::COMMAND { + driver_number: r0, + subdriver_number: r1, + arg0: r2, + arg1: r3, + }), + 3 => Some(Syscall::ALLOW { + driver_number: r0, + subdriver_number: r1, + allow_address: r2 as *mut u8, + allow_size: r3, + }), + 4 => Some(Syscall::MEMOP { + operand: r0, + arg0: r1, + }), + _ => None, + } +} From 522aa21430db3d45d13dc2752a1baae346b2cd73 Mon Sep 17 00:00:00 2001 From: Brad Campbell Date: Tue, 4 Jun 2019 14:03:28 -0400 Subject: [PATCH 2/5] cortex-m: update syscall to UKB v2 For portability reasons the syscall interface has been updated, and this commit implements it for cortex-m. The major change is that we no longer remove the SVC stack frame after a yield call. In fact, the API for popping stack frames has been removed. Instead, when we want the process to run a new function, we re-use the old SVC stack frame. In the case that the app has never run before we do have to create the stack frame just like we used to. In theory, this is actually more efficient since we save the effort of removing the stack frame, but that wasn't very substancial so who knows. The other change is we now set state.psr and state.yield_pc after every syscall, and not just effectively after yield(). I don't think this has any effect. --- arch/cortex-m/Cargo.lock | 6 +- arch/cortex-m/src/syscall.rs | 148 +++++++++++++++++------------------ 2 files changed, 75 insertions(+), 79 deletions(-) diff --git a/arch/cortex-m/Cargo.lock b/arch/cortex-m/Cargo.lock index 1c1277f5ac..a248ac1eb5 100644 --- a/arch/cortex-m/Cargo.lock +++ b/arch/cortex-m/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "cortexm" version = "0.1.0" @@ -10,7 +12,7 @@ name = "kernel" version = "0.1.0" dependencies = [ "tock-cells 0.1.0", - "tock-registers 0.2.0", + "tock-registers 0.3.0", ] [[package]] @@ -19,5 +21,5 @@ version = "0.1.0" [[package]] name = "tock-registers" -version = "0.2.0" +version = "0.3.0" diff --git a/arch/cortex-m/src/syscall.rs b/arch/cortex-m/src/syscall.rs index 765efabf02..92c81eeebc 100644 --- a/arch/cortex-m/src/syscall.rs +++ b/arch/cortex-m/src/syscall.rs @@ -74,94 +74,61 @@ impl SysCall { impl kernel::syscall::UserspaceKernelBoundary for SysCall { type StoredState = CortexMStoredState; - /// Get the syscall that the process called. - unsafe fn get_syscall(&self, stack_pointer: *const usize) -> Option { - // Get the four values that are passed with the syscall. - let r0 = read_volatile(stack_pointer.offset(0)); - let r1 = read_volatile(stack_pointer.offset(1)); - let r2 = read_volatile(stack_pointer.offset(2)); - let r3 = read_volatile(stack_pointer.offset(3)); - - // Get the actual SVC number. - let pcptr = read_volatile((stack_pointer as *const *const u16).offset(6)); - let svc_instr = read_volatile(pcptr.offset(-1)); - let svc_num = (svc_instr & 0xff) as u8; - match svc_num { - 0 => Some(kernel::syscall::Syscall::YIELD), - 1 => Some(kernel::syscall::Syscall::SUBSCRIBE { - driver_number: r0, - subdriver_number: r1, - callback_ptr: r2 as *mut (), - appdata: r3, - }), - 2 => Some(kernel::syscall::Syscall::COMMAND { - driver_number: r0, - subdriver_number: r1, - arg0: r2, - arg1: r3, - }), - 3 => Some(kernel::syscall::Syscall::ALLOW { - driver_number: r0, - subdriver_number: r1, - allow_address: r2 as *mut u8, - allow_size: r3, - }), - 4 => Some(kernel::syscall::Syscall::MEMOP { - operand: r0, - arg0: r1, - }), - _ => None, - } - } - - unsafe fn set_syscall_return_value(&self, stack_pointer: *const usize, return_value: isize) { + unsafe fn set_syscall_return_value( + &self, + stack_pointer: *const usize, + _state: &mut Self::StoredState, + return_value: isize, + ) { // For the Cortex-M arch we set this in the same place that r0 was // passed. let sp = stack_pointer as *mut isize; write_volatile(sp, return_value); } - unsafe fn pop_syscall_stack_frame( - &self, - stack_pointer: *const usize, - state: &mut CortexMStoredState, - ) -> *mut usize { - state.yield_pc = read_volatile(stack_pointer.offset(6)); - state.psr = read_volatile(stack_pointer.offset(7)); - (stack_pointer as *mut usize).offset(8) - } - - unsafe fn push_function_call( + unsafe fn set_process_function( &self, stack_pointer: *const usize, remaining_stack_memory: usize, + state: &mut CortexMStoredState, callback: kernel::procs::FunctionCall, - state: &CortexMStoredState, + first_function: bool, ) -> Result<*mut usize, *mut usize> { - // We need 32 bytes to add this frame. Ensure that there are 32 bytes - // available on the stack. - if remaining_stack_memory < 32 { - // Not enough room on the stack to add a frame. Return an error - // and where the stack would be to help with debugging. - Err((stack_pointer as *mut usize).offset(-8)) + // If this is the first time this process is being used then it has no + // stack and we have to create a new stack frame for the svc handler to + // use. If this process has called a syscall before (i.e. + // `first_function` is false), then we can re-use that stack frame to + // set this new function to be run after calling `svc`. + let stack_bottom = if first_function { + if remaining_stack_memory < 32 { + // Not enough room on the stack to add a frame. Return an error + // and where the stack would be to help with debugging. + return Err((stack_pointer as *mut usize).offset(-8)); + } else { + // Fill in initial stack expected by SVC handler + // Top minus 8 u32s for r0-r3, r12, lr, pc and xPSR + (stack_pointer as *mut usize).offset(-8) + } } else { - // Fill in initial stack expected by SVC handler - // Top minus 8 u32s for r0-r3, r12, lr, pc and xPSR - let stack_bottom = (stack_pointer as *mut usize).offset(-8); - write_volatile(stack_bottom.offset(7), state.psr); - write_volatile(stack_bottom.offset(6), callback.pc | 1); - - // Set the LR register to the saved PC so the callback returns to - // wherever wait was called. Set lowest bit to one because of THUMB - // instruction requirements. - write_volatile(stack_bottom.offset(5), state.yield_pc | 0x1); - write_volatile(stack_bottom, callback.argument0); - write_volatile(stack_bottom.offset(1), callback.argument1); - write_volatile(stack_bottom.offset(2), callback.argument2); - write_volatile(stack_bottom.offset(3), callback.argument3); - - Ok(stack_bottom) - } + // We can re-use the frame already present. + stack_pointer as *mut usize + }; + + // Now either modify the existing stack frame or create one where we just + // made space. In either case the process is the same. + write_volatile(stack_bottom.offset(7), state.psr); + write_volatile(stack_bottom.offset(6), callback.pc | 1); + + // Set the LR register to the saved PC so the callback returns to + // wherever wait was called. Set lowest bit to one because of THUMB + // instruction requirements. + write_volatile(stack_bottom.offset(5), state.yield_pc | 0x1); + write_volatile(stack_bottom, callback.argument0); + write_volatile(stack_bottom.offset(1), callback.argument1); + write_volatile(stack_bottom.offset(2), callback.argument2); + write_volatile(stack_bottom.offset(3), callback.argument3); + + Ok(stack_bottom) } unsafe fn switch_to_process( @@ -194,7 +161,34 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // handler and this process faulted. kernel::syscall::ContextSwitchReason::Fault } else if syscall_fired == 1 { - kernel::syscall::ContextSwitchReason::SyscallFired + // Save these fields after a syscall. If this is a synchronous + // syscall (i.e. we return a value to the app immediately) then this + // will have no effect. If we are doing something like `yield()`, + // however, then we need to have this state. + state.yield_pc = read_volatile(new_stack_pointer.offset(6)); + state.psr = read_volatile(new_stack_pointer.offset(7)); + + // Get the syscall arguments and return them along with the syscall. + // It's possible the app did something invalid, in which case we put + // the app in the fault state. + let r0 = read_volatile(new_stack_pointer.offset(0)); + let r1 = read_volatile(new_stack_pointer.offset(1)); + let r2 = read_volatile(new_stack_pointer.offset(2)); + let r3 = read_volatile(new_stack_pointer.offset(3)); + + // Get the actual SVC number. + let pcptr = read_volatile((new_stack_pointer as *const *const u16).offset(6)); + let svc_instr = read_volatile(pcptr.offset(-1)); + let svc_num = (svc_instr & 0xff) as u8; + + // Use the helper function to convert these raw values into a Tock + // `Syscall` type. + let syscall = kernel::syscall::arguments_to_syscall(svc_num, r0, r1, r2, r3); + + match syscall { + Some(s) => kernel::syscall::ContextSwitchReason::SyscallFired { syscall: s }, + None => kernel::syscall::ContextSwitchReason::Fault, + } } else if systick_expired == 1 { kernel::syscall::ContextSwitchReason::TimesliceExpired } else { From 42c48a5e6f9af1aab9ff22732f742dbb55d20df7 Mon Sep 17 00:00:00 2001 From: Brad Campbell Date: Tue, 11 Jun 2019 14:48:13 -0400 Subject: [PATCH 3/5] e310: update to new UKB interface --- chips/e310x/src/chip.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/chips/e310x/src/chip.rs b/chips/e310x/src/chip.rs index af58ea1c90..660a1bb3fa 100644 --- a/chips/e310x/src/chip.rs +++ b/chips/e310x/src/chip.rs @@ -23,26 +23,21 @@ impl NullSysCall { impl kernel::syscall::UserspaceKernelBoundary for NullSysCall { type StoredState = RvStoredState; - unsafe fn get_syscall(&self, _stack_pointer: *const usize) -> Option { - None - } - - unsafe fn set_syscall_return_value(&self, _stack_pointer: *const usize, _return_value: isize) {} - - unsafe fn pop_syscall_stack_frame( + unsafe fn set_syscall_return_value( &self, - stack_pointer: *const usize, + _stack_pointer: *const usize, _state: &mut RvStoredState, - ) -> *mut usize { - stack_pointer as *mut usize + _return_value: isize, + ) { } - unsafe fn push_function_call( + unsafe fn set_process_function( &self, stack_pointer: *const usize, _remaining_stack_memory: usize, + _state: &mut RvStoredState, _callback: kernel::procs::FunctionCall, - _state: &RvStoredState, + _first_function: bool, ) -> Result<*mut usize, *mut usize> { Err(stack_pointer as *mut usize) } From a6b10a0fb972403e507c237cd8c5d786d406c65e Mon Sep 17 00:00:00 2001 From: Pat Pannuto Date: Wed, 10 Jul 2019 18:04:58 -0700 Subject: [PATCH 4/5] syscall: make process init an explicit interface --- arch/cortex-m/src/syscall.rs | 45 +++++++++++++++++++----------------- chips/e310x/src/chip.rs | 10 +++++++- kernel/src/process.rs | 25 ++++++++++++++++---- kernel/src/syscall.rs | 14 +++++++---- 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/arch/cortex-m/src/syscall.rs b/arch/cortex-m/src/syscall.rs index 92c81eeebc..53a249aba6 100644 --- a/arch/cortex-m/src/syscall.rs +++ b/arch/cortex-m/src/syscall.rs @@ -74,6 +74,28 @@ impl SysCall { impl kernel::syscall::UserspaceKernelBoundary for SysCall { type StoredState = CortexMStoredState; + unsafe fn initialize_new_process( + &self, + stack_pointer: *const usize, + stack_size: usize, + _state: &mut Self::StoredState, + ) -> Result<*const usize, ()> { + // The first time a process runs it has no stack and we have to create + // a new stack frame for the svc handler to have "returned from". + + // Space for 8 u32s: r0-r3, r12, lr, pc, and xPSR + let svc_frame_size = 32; + + // Make sure there's enough room on the stack for the initial kernel frame + if stack_size < svc_frame_size { + // Not enough room on the stack to add a frame. + return Err(()); + } + + // Allocate the kernel frame + Ok((stack_pointer as *mut usize).offset(-8)) + } + unsafe fn set_syscall_return_value( &self, stack_pointer: *const usize, @@ -89,30 +111,11 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { unsafe fn set_process_function( &self, stack_pointer: *const usize, - remaining_stack_memory: usize, + _remaining_stack_memory: usize, state: &mut CortexMStoredState, callback: kernel::procs::FunctionCall, - first_function: bool, ) -> Result<*mut usize, *mut usize> { - // If this is the first time this process is being used then it has no - // stack and we have to create a new stack frame for the svc handler to - // use. If this process has called a syscall before (i.e. - // `first_function` is false), then we can re-use that stack frame to - // set this new function to be run after calling `svc`. - let stack_bottom = if first_function { - if remaining_stack_memory < 32 { - // Not enough room on the stack to add a frame. Return an error - // and where the stack would be to help with debugging. - return Err((stack_pointer as *mut usize).offset(-8)); - } else { - // Fill in initial stack expected by SVC handler - // Top minus 8 u32s for r0-r3, r12, lr, pc and xPSR - (stack_pointer as *mut usize).offset(-8) - } - } else { - // We can re-use the frame already present. - stack_pointer as *mut usize - }; + let stack_bottom = stack_pointer as *mut usize; // Now either modify the existing stack frame or create one where we just // made space. In either case the process is the same. diff --git a/chips/e310x/src/chip.rs b/chips/e310x/src/chip.rs index 660a1bb3fa..54096c9581 100644 --- a/chips/e310x/src/chip.rs +++ b/chips/e310x/src/chip.rs @@ -23,6 +23,15 @@ impl NullSysCall { impl kernel::syscall::UserspaceKernelBoundary for NullSysCall { type StoredState = RvStoredState; + unsafe fn initialize_new_process( + &self, + _stack_pointer: *const usize, + _stack_size: usize, + _state: &mut Self::StoredState, + ) -> Result<*const usize, ()> { + Err(()) + } + unsafe fn set_syscall_return_value( &self, _stack_pointer: *const usize, @@ -37,7 +46,6 @@ impl kernel::syscall::UserspaceKernelBoundary for NullSysCall { _remaining_stack_memory: usize, _state: &mut RvStoredState, _callback: kernel::procs::FunctionCall, - _first_function: bool, ) -> Result<*mut usize, *mut usize> { Err(stack_pointer as *mut usize) } diff --git a/kernel/src/process.rs b/kernel/src/process.rs index ac5b00df48..140b83b6bf 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -825,16 +825,11 @@ impl ProcessType for Process<'a, C> { // like. let mut stored_state = self.stored_state.get(); - // Check to see if this will be the first function call for this - // process. - let first_function = self.state.get() == State::Unstarted; - match self.chip.userspace_kernel_boundary().set_process_function( self.sp(), remaining_stack_bytes, &mut stored_state, callback, - first_function, ) { Ok(stack_bottom) => { // If we got an `Ok` with the new stack pointer we are all @@ -1254,6 +1249,26 @@ impl Process<'a, C> { })); }); + // Handle any architecture-specific requirements for a new process + let mut stored_state = process.stored_state.get(); + match chip.userspace_kernel_boundary().initialize_new_process( + process.sp(), + process.sp() as usize - process.memory.as_ptr() as usize, + &mut stored_state, + ) { + Ok(new_stack_pointer) => { + process + .current_stack_pointer + .set(new_stack_pointer as *mut u8); + process.debug_set_max_stack_depth(); + process.stored_state.set(stored_state); + } + Err(_) => { + return (None, app_flash_size, 0); + } + }; + + // Mark this process as having something to do (it has to start!) kernel.increment_work(); return ( diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 942add3661..4590747f56 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -71,6 +71,16 @@ pub trait UserspaceKernelBoundary { /// registers that aren't stored on the stack. type StoredState: Default + Copy; + /// Called by the kernel after a new process has been created by before it + /// is allowed to begin executing. Allows for architecture-specific process + /// setup, e.g. allocating a syscall stack frame. + unsafe fn initialize_new_process( + &self, + stack_pointer: *const usize, + stack_size: usize, + state: &mut Self::StoredState, + ) -> Result<*const usize, ()>; + /// Set the return value the process should see when it begins executing /// again after the syscall. This will only be called after a process has /// called a syscall. @@ -102,9 +112,6 @@ pub trait UserspaceKernelBoundary { /// - `state` is the stored state for this process. /// - `callback` is the function that should be executed when the process /// resumes. - /// - `first_function` is true if this is the first time this process is - /// being run. This allows a `UserspaceKernelBoundary` implementation to - /// assume there are no stack frames on the process's stack. /// /// ### Return /// @@ -118,7 +125,6 @@ pub trait UserspaceKernelBoundary { remaining_stack_memory: usize, state: &mut Self::StoredState, callback: process::FunctionCall, - first_function: bool, ) -> Result<*mut usize, *mut usize>; /// Context switch to a specific process. From c0b27f228e5c7a4df9d695039f22c3eb49fc7129 Mon Sep 17 00:00:00 2001 From: Pat Pannuto Date: Thu, 11 Jul 2019 14:47:19 -0700 Subject: [PATCH 5/5] syscall: documentation around process fn injection Add an explicit note about the hazard of funciton injection, namely that it will override any kernel return value (as the injected function will run first, and its return value will overwrite any kernel return value that has been set). In practice, this means that process function injection is only safe to call in response to syscalls without return values (i.e. only `yield` in the current syscall interface). --- arch/cortex-m/src/syscall.rs | 36 ++++++++++++++++++++++-------------- kernel/src/syscall.rs | 4 ++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/arch/cortex-m/src/syscall.rs b/arch/cortex-m/src/syscall.rs index 53a249aba6..0d0b8afd3d 100644 --- a/arch/cortex-m/src/syscall.rs +++ b/arch/cortex-m/src/syscall.rs @@ -108,6 +108,18 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { write_volatile(sp, return_value); } + /// When the process calls `svc` to enter the kernel, the hardware + /// automatically pushes a stack frame that will be unstacked when the + /// kernel returns to the process. In the special case of process startup, + /// `initialize_new_process` sets up an empty stack frame and stored state + /// as if an `svc` had been called. + /// + /// Here, we modify this stack frame such that the process resumes at the + /// beginning of the callback function that we want the process to run. We + /// place the originally intended return addess in the link register so + /// that when the function completes execution continues. + /// + /// In effect, this converts `svc` into `bl callback`. unsafe fn set_process_function( &self, stack_pointer: *const usize, @@ -115,21 +127,17 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { state: &mut CortexMStoredState, callback: kernel::procs::FunctionCall, ) -> Result<*mut usize, *mut usize> { + // Notes: + // - Instruction addresses require `|1` to indicate thumb code + // - Stack offset 4 is R12, which the syscall interface ignores let stack_bottom = stack_pointer as *mut usize; - - // Now either modify the existing stack frame or create one where we just - // made space. In either case the process is the same. - write_volatile(stack_bottom.offset(7), state.psr); - write_volatile(stack_bottom.offset(6), callback.pc | 1); - - // Set the LR register to the saved PC so the callback returns to - // wherever wait was called. Set lowest bit to one because of THUMB - // instruction requirements. - write_volatile(stack_bottom.offset(5), state.yield_pc | 0x1); - write_volatile(stack_bottom, callback.argument0); - write_volatile(stack_bottom.offset(1), callback.argument1); - write_volatile(stack_bottom.offset(2), callback.argument2); - write_volatile(stack_bottom.offset(3), callback.argument3); + write_volatile(stack_bottom.offset(7), state.psr); //......... -> APSR + write_volatile(stack_bottom.offset(6), callback.pc | 1); //... -> PC + write_volatile(stack_bottom.offset(5), state.yield_pc | 1); // -> LR + write_volatile(stack_bottom.offset(3), callback.argument3); // -> R3 + write_volatile(stack_bottom.offset(2), callback.argument2); // -> R2 + write_volatile(stack_bottom.offset(1), callback.argument1); // -> R1 + write_volatile(stack_bottom.offset(0), callback.argument0); // -> R0 Ok(stack_bottom) } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 4590747f56..389252e0b7 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -101,6 +101,10 @@ pub trait UserspaceKernelBoundary { /// `_start` when the process is started for the very first time; 2) tells /// the process to execute a callback function after calling `yield()`. /// + /// **Note:** This method cannot be called in conjunction with + /// `set_syscall_return_value`, as the injected function will clobber the + /// return value. + /// /// ### Arguments /// /// - `stack_pointer` is the address of the stack pointer for the current