x86/boundary/
boundary_impl.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5use core::fmt::Write;
6
7use crate::registers::bits32::eflags::{EFlags, EFLAGS};
8
9use kernel::process::FunctionCall;
10use kernel::syscall::{ContextSwitchReason, Syscall, SyscallReturn, UserspaceKernelBoundary};
11use kernel::ErrorCode;
12
13use crate::interrupts::{IDT_RESERVED_EXCEPTIONS, SYSCALL_VECTOR};
14use crate::segmentation::{USER_CODE, USER_DATA};
15
16use super::UserContext;
17
18/// Defines the usermode-kernelmode ABI for x86 platforms.
19pub struct Boundary;
20
21impl Default for Boundary {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl Boundary {
28    /// Minimum required size for initial process memory.
29    ///
30    /// Need at least 9 dwords of initial stack space for CRT 0:
31    ///
32    /// - 4 dwords for initial upcall arguments
33    /// - 1 dword for initial upcall return address (although this will be zero for init_fn)
34    /// - 4 dwords of scratch space for invoking memop syscalls
35    const MIN_APP_BRK: u32 = 9 * core::mem::size_of::<usize>() as u32;
36
37    /// Constructs a new instance of `SysCall`.
38    pub fn new() -> Self {
39        Self
40    }
41}
42
43impl UserspaceKernelBoundary for Boundary {
44    type StoredState = UserContext;
45
46    fn initial_process_app_brk_size(&self) -> usize {
47        Self::MIN_APP_BRK as usize
48    }
49
50    unsafe fn initialize_process(
51        &self,
52        accessible_memory_start: *const u8,
53        app_brk: *const u8,
54        state: &mut Self::StoredState,
55    ) -> Result<(), ()> {
56        if (app_brk as u32 - accessible_memory_start as u32) < Self::MIN_APP_BRK {
57            return Err(());
58        }
59
60        // We pre-allocate 16 bytes on the stack for initial upcall arguments.
61        let esp = (app_brk as u32) - 16;
62
63        let mut eflags = EFlags::new();
64        eflags.0.modify(EFLAGS::FLAGS_IF::SET);
65
66        state.eax = 0;
67        state.ebx = 0;
68        state.ecx = 0;
69        state.edx = 0;
70        state.esi = 0;
71        state.edi = 0;
72        state.ebp = 0;
73        state.esp = esp;
74        state.eip = 0;
75        state.eflags = eflags.0.get();
76        state.cs = USER_CODE.bits() as u32;
77        state.ss = USER_DATA.bits() as u32;
78        state.ds = USER_DATA.bits() as u32;
79        state.es = USER_DATA.bits() as u32;
80        state.fs = USER_DATA.bits() as u32;
81        state.gs = USER_DATA.bits() as u32;
82
83        Ok(())
84    }
85
86    unsafe fn set_syscall_return_value(
87        &self,
88        accessible_memory_start: *const u8,
89        app_brk: *const u8,
90        state: &mut Self::StoredState,
91        return_value: SyscallReturn,
92    ) -> Result<(), ()> {
93        let mut ret0 = 0;
94        let mut ret1 = 0;
95        let mut ret2 = 0;
96        let mut ret3 = 0;
97
98        // These operations are only safe so long as
99        // - the pointers are properly aligned. This is guaranteed because the
100        //   pointers are all offset multiples of 4 bytes from the stack
101        //   pointer, which is guaranteed to be properly aligned after
102        //   exception entry on x86. See
103        //   https://github.com/tock/tock/pull/2478#issuecomment-796389747
104        //   for more details.
105        // - the pointer is dereferencable, i.e. the memory range of
106        //   the given size starting at the pointer must all be within
107        //   the bounds of a single allocated object
108        // - the pointer must point to an initialized instance of its
109        //   type
110        // - during the lifetime of the returned reference (of the
111        //   cast, essentially an arbitrary 'a), the memory must not
112        //   get accessed (read or written) through any other pointer.
113        //
114        // Refer to
115        // https://doc.rust-lang.org/std/primitive.pointer.html#safety-13
116        kernel::utilities::arch_helpers::encode_syscall_return_trd104(
117            &kernel::utilities::arch_helpers::TRD104SyscallReturn::from_syscall_return(
118                return_value,
119            ),
120            &mut ret0,
121            &mut ret1,
122            &mut ret2,
123            &mut ret3,
124        );
125
126        // App allocates 16 bytes of stack space for passing syscall arguments. We re-use that stack
127        // space to pass return values.
128        //
129        // Safety: Caller of this function has guaranteed that the memory region is valid.
130        unsafe {
131            state.write_stack(0, ret0, accessible_memory_start, app_brk)?;
132            state.write_stack(1, ret1, accessible_memory_start, app_brk)?;
133            state.write_stack(2, ret2, accessible_memory_start, app_brk)?;
134            state.write_stack(3, ret3, accessible_memory_start, app_brk)?;
135        }
136
137        Ok(())
138    }
139
140    unsafe fn set_process_function(
141        &self,
142        accessible_memory_start: *const u8,
143        app_brk: *const u8,
144        state: &mut Self::StoredState,
145        upcall: FunctionCall,
146    ) -> Result<(), ()> {
147        // Our x86 port expects upcalls to be standard cdecl routines. We push args and return
148        // address onto the stack accordingly.
149        //
150        // Upcall arguments are written directly into the existing stack space (rather than
151        // being pushed on top). This is safe to do because:
152        //
153        // * When the process first starts ESP is initialized to `app_brk - 16`, giving us exactly
154        //   enough space for these arguments.
155        // * Otherwise, we assume the app is currently issuing a `yield` syscall. We re-use the
156        //   stack space from that syscall. This is okay because `yield` doesn't return anything.
157        //
158        // Safety: Caller of this function has guaranteed that the memory region is valid.
159        // usize is u32 on x86
160        unsafe {
161            state.write_stack(0, upcall.argument0 as u32, accessible_memory_start, app_brk)?;
162            state.write_stack(1, upcall.argument1 as u32, accessible_memory_start, app_brk)?;
163            state.write_stack(2, upcall.argument2 as u32, accessible_memory_start, app_brk)?;
164            state.write_stack(
165                3,
166                upcall.argument3.as_usize() as u32,
167                accessible_memory_start,
168                app_brk,
169            )?;
170
171            state.push_stack(state.eip, accessible_memory_start, app_brk)?;
172        }
173
174        // The next time we switch to this process, we will directly jump to the upcall. When the
175        // upcall issues `ret`, it will return to wherever the yield syscall was invoked.
176        state.eip = upcall.pc.addr() as u32;
177
178        Ok(())
179    }
180
181    unsafe fn switch_to_process(
182        &self,
183        accessible_memory_start: *const u8,
184        app_brk: *const u8,
185        state: &mut Self::StoredState,
186    ) -> (ContextSwitchReason, Option<*const u8>) {
187        // Sanity check: don't try to run a faulted app
188        if state.exception != 0 || state.err_code != 0 {
189            let stack_ptr = state.esp as *mut u8;
190            return (ContextSwitchReason::Fault, Some(stack_ptr));
191        }
192
193        let mut err_code = 0;
194        let int_num = unsafe { super::switch_to_user(state, &mut err_code) };
195
196        let reason = match int_num as u8 {
197            0..IDT_RESERVED_EXCEPTIONS => {
198                state.exception = int_num as u8;
199                state.err_code = err_code;
200                ContextSwitchReason::Fault
201            }
202
203            SYSCALL_VECTOR => {
204                let num = state.eax as u8;
205
206                // Syscall arguments are passed on the stack using cdecl convention.
207                //
208                // Safety: Caller of this function has guaranteed that the memory region is valid.
209                let arg0 =
210                    unsafe { state.read_stack(0, accessible_memory_start, app_brk) }.unwrap_or(0);
211                let arg1 =
212                    unsafe { state.read_stack(1, accessible_memory_start, app_brk) }.unwrap_or(0);
213                let arg2 =
214                    unsafe { state.read_stack(2, accessible_memory_start, app_brk) }.unwrap_or(0);
215                let arg3 =
216                    unsafe { state.read_stack(3, accessible_memory_start, app_brk) }.unwrap_or(0);
217
218                Syscall::from_register_arguments(
219                    num,
220                    arg0 as usize,
221                    (arg1 as usize).into(),
222                    (arg2 as usize).into(),
223                    (arg3 as usize).into(),
224                )
225                .map_or(ContextSwitchReason::Fault, |syscall| {
226                    ContextSwitchReason::SyscallFired { syscall }
227                })
228            }
229            _ => ContextSwitchReason::Interrupted,
230        };
231
232        let stack_ptr = state.esp as *const u8;
233
234        (reason, Some(stack_ptr))
235    }
236
237    unsafe fn print_context(
238        &self,
239        _accessible_memory_start: *const u8,
240        _app_brk: *const u8,
241        state: &Self::StoredState,
242        writer: &mut dyn Write,
243    ) {
244        let _ = writeln!(writer, "{}", state);
245    }
246
247    fn store_context(
248        &self,
249        _state: &Self::StoredState,
250        _out: &mut [u8],
251    ) -> Result<usize, ErrorCode> {
252        unimplemented!()
253    }
254}