pub trait UserspaceKernelBoundary {
    type StoredState: Default;

    fn initial_process_app_brk_size(&self) -> usize;
    unsafe fn initialize_process(
        &self,
        accessible_memory_start: *const u8,
        app_brk: *const u8,
        state: &mut Self::StoredState
    ) -> Result<(), ()>; unsafe fn set_syscall_return_value(
        &self,
        accessible_memory_start: *const u8,
        app_brk: *const u8,
        state: &mut Self::StoredState,
        return_value: SyscallReturn
    ) -> Result<(), ()>; unsafe fn set_process_function(
        &self,
        accessible_memory_start: *const u8,
        app_brk: *const u8,
        state: &mut Self::StoredState,
        upcall: FunctionCall
    ) -> Result<(), ()>; unsafe fn switch_to_process(
        &self,
        accessible_memory_start: *const u8,
        app_brk: *const u8,
        state: &mut Self::StoredState
    ) -> (ContextSwitchReason, Option<*const u8>); unsafe fn print_context(
        &self,
        accessible_memory_start: *const u8,
        app_brk: *const u8,
        state: &Self::StoredState,
        writer: &mut dyn Write
    ); fn store_context(
        &self,
        state: &Self::StoredState,
        out: &mut [u8]
    ) -> Result<usize, ErrorCode>; }
Expand description

The UserspaceKernelBoundary trait is implemented by the architectural component of the chip implementation of Tock. This trait allows the kernel to switch to and from processes in an architecture-independent manner.

Exactly how upcalls and return values are passed between kernelspace and userspace is architecture specific. The architecture may use process memory to store state when switching. Therefore, functions in this trait are passed the bounds of process-accessible memory so that the architecture implementation can verify it is reading and writing memory that the process has valid access to. These bounds are passed through accessible_memory_start and app_brk pointers.

Required Associated Types

Some architecture-specific struct containing per-process state that must be kept while the process is not running. For example, for keeping CPU registers that aren’t stored on the stack.

Implementations should not rely on the Default constructor (custom or derived) for any initialization of a process’s stored state. The initialization must happen in the initialize_process() function.

Required Methods

Called by the kernel during process creation to inform the kernel of the minimum amount of process-accessible RAM needed by a new process. This allows for architecture-specific process layout decisions, such as stack pointer initialization.

This returns the minimum number of bytes of process-accessible memory the kernel must allocate to a process so that a successful context switch is possible.

Some architectures may not need any allocated memory, and this should return 0. In general, implementations should try to pre-allocate the minimal amount of process-accessible memory (i.e. return as close to 0 as possible) to provide the most flexibility to the process. However, the return value will be nonzero for architectures where values are passed in memory between kernelspace and userspace during syscalls or a stack needs to be setup.

Called by the kernel after it has memory allocated to it but before it is allowed to begin executing. Allows for architecture-specific process setup, e.g. allocating a syscall stack frame.

This function must also initialize the stored state (if needed).

The kernel calls this function with the start of memory allocated to the process by providing accessible_memory_start. It also provides the app_brk pointer which marks the end of process-accessible memory. The kernel guarantees that accessible_memory_start will be word-aligned.

If successful, this function returns Ok(). If the process syscall state cannot be initialized with the available amount of memory, or for any other reason, it should return Err().

This function may be called multiple times on the same process. For example, if a process crashes and is to be restarted, this must be called. Or if the process is moved this may need to be called.

Safety

This function guarantees that it if needs to change process memory, it will only change memory starting at accessible_memory_start and before app_brk. The caller is responsible for guaranteeing that those pointers are valid for the process.

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.

The process to set the return value for is specified by the state value. 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.

Safety

This function guarantees that it if needs to change process memory, it will only change memory starting at accessible_memory_start and before app_brk. The caller is responsible for guaranteeing that those pointers are valid for the process.

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 upcall 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
  • accessible_memory_start is the address of the start of the process-accessible memory region for this process.
  • app_brk is the address of the current process break. This marks the end of the memory region the process has access to. Note, this is not the end of the entire memory region allocated to the process. Some memory above this address is still allocated for the process, but if the process tries to access it an MPU fault will occur.
  • state is the stored state for this process.
  • upcall is the function that should be executed when the process resumes.
Return

Returns Ok(()) if the function was successfully enqueued for the process. Returns Err(()) if the function was not, likely because there is insufficient memory available to do so.

Safety

This function guarantees that it if needs to change process memory, it will only change memory starting at accessible_memory_start and before app_brk. The caller is responsible for guaranteeing that those pointers are valid for the process.

Context switch to a specific process.

This returns two values in a tuple.

  1. A ContextSwitchReason indicating why the process stopped executing and switched back to the kernel.
  2. Optionally, the current stack pointer used by the process. This is optional because it is only for debugging in process.rs. By sharing the process’s stack pointer with process.rs users can inspect the state and see the stack depth, which might be useful for debugging.
Safety

This function guarantees that it if needs to change process memory, it will only change memory starting at accessible_memory_start and before app_brk. The caller is responsible for guaranteeing that those pointers are valid for the process.

Display architecture specific (e.g. CPU registers or status flags) data for a process identified by the stored state for that process.

Safety

This function guarantees that it if needs to change process memory, it will only change memory starting at accessible_memory_start and before app_brk. The caller is responsible for guaranteeing that those pointers are valid for the process.

Store architecture specific (e.g. CPU registers or status flags) data for a process. On success returns the number of elements written to out.

Implementors