x86/boundary/
mod.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
5//! Usermode-kernelmode boundary for the x86 architecture
6//!
7//! This module defines the boundary between user and kernel modes on the x86 architecture,
8//! including syscall/upcall calling conventions as well as process initialization.
9//!
10//! In contrast with other embedded architectures like ARM or RISC-V, x86 does _not_ have very many
11//! general purpose registers to spare. The ABI defined here draws heavily from the `cdecl` calling
12//! convention by using the stack instead of registers to pass data between user and kernel mode.
13//!
14//! ## System Calls
15//!
16//! System calls are dispatched from user- to kernel-mode using interrupt number 0x40. The system
17//! call class number is stored in EAX.
18//!
19//! ECX and EDX are treated as caller-saved registers, which means their values should be considered
20//! undefined after the return of a system call. The kernel itself will backup and restore ECX/EDX,
21//! however they may get clobbered if an upcall is pushed.
22//!
23//! Arguments and return values are passed via the user-mode stack in reverse order (similar to
24//! `cdecl`). The caller must _always_ push 4 values to the stack, even for system calls which have
25//! less than 4 arguments. This is because the kernel may return up to 4 values.
26//!
27//! As with `cdecl`, the caller is responsible for incrementing ESP to clean up the stack frame
28//! after the system call returns.
29//!
30//! The following assembly snippet shows how to invoke the `yield` syscall:
31//!
32//! ```text
33//! push    0           # arg 4: unused
34//! push    0           # arg 3: unused
35//! push    0           # arg 2: unused
36//! push    1           # arg 1: yield-wait
37//! mov     eax, 0      # class: yield
38//! int     0x40
39//! add     esp, 16     # clean up stack
40//! ```
41//!
42//! ## Yielding and Upcalls
43//!
44//! Upcalls are expected to be standard `cdecl` functions. The kernel will write arguments and a
45//! return value to the stack before jumping to an upcall.
46//!
47//! The return address pushed by the kernel will point to the instruction immediately following the
48//! `yield` syscall which led to the upcall. This means when the upcall finishes and returns, the
49//! app will continue executing wherever it left off when `yield` was called, without context
50//! switching back to kernel.
51//!
52//! The app should have allocated 16 bytes of stack space for arguments to/return values from the
53//! `yield` syscall (see above). Since `yield` does not actually return anything, this stack space
54//! is repurposed to store the upcall arguments. This way when an upcall returns, the app only needs
55//! to clean up 16 bytes of stack space; in this sense, the ABI of `yield` is no different than any
56//! other syscall.
57//!
58//! ## Process Initialization
59//!
60//! Tock treats process entry points just like upcalls, except they are never expected to return.
61//! For x86, this means the process entry point should use the `cdecl` function.
62//!
63//! For x86, we allocate an initial stack of 36 bytes before calling this upcall. The top 20 bytes
64//! are used to invoke the entry point itself: 16 for arguments, and 4 for a return address (which
65//! should never be used).
66//!
67//! The remaining 16 bytes are free for use by the entry point. This is exactly enough stack space
68//! to invoke system calls. In most cases, the first task of the app entry point will be to allocate
69//! itself a larger stack.
70
71mod context;
72use context::UserContext;
73
74mod boundary_impl;
75pub use self::boundary_impl::Boundary;
76
77#[cfg(target_arch = "x86")]
78mod switch_to_user;
79
80#[cfg(target_arch = "x86")]
81mod return_from_user;
82
83extern "cdecl" {
84    /// Performs a context switch to the given process.
85    ///
86    /// See _switch_to_user.s_ for complete details.
87    fn switch_to_user(context: *mut UserContext, error_code: *mut u32) -> u32;
88}