Skip to content

Commit 3d8e273

Browse files
arch: Added x86-next crate
Signed-off-by: Ioan-Cristian CÎRSTEA <ioan.cirstea@oxidos.io>
1 parent bb5f85f commit 3d8e273

49 files changed

Lines changed: 7094 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

arch/x86-next/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
[package]
6+
name = "x86-next"
7+
version.workspace = true
8+
authors.workspace = true
9+
edition.workspace = true
10+
11+
[dependencies]
12+
kernel = { path = "../../kernel" }
13+
tock-cells = { path = "../../libraries/tock-cells" }
14+
tock-registers = { path = "../../libraries/tock-register-interface" }
15+
16+
[lints]
17+
workspace = true

arch/x86-next/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
x86 Architecture Support Components
2+
===================================
3+
4+
This crate includes low-level code for x86 32-bit CPU architectures.
5+
6+
This crate implements flat segmentation for memory management. The entire address space of 4Gb is contained in a single unbroken ("flat") memory segment.
7+
8+
Virtual memory is used as an MPU with a 4KB section granularity.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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+
use core::fmt::Write;
6+
7+
use crate::registers::bits32::eflags::{EFlags, EFLAGS};
8+
9+
use kernel::process::FunctionCall;
10+
use kernel::syscall::{ContextSwitchReason, Syscall, SyscallReturn, UserspaceKernelBoundary};
11+
use kernel::ErrorCode;
12+
13+
use crate::interrupts::{IDT_RESERVED_EXCEPTIONS, SYSCALL_VECTOR};
14+
use crate::segmentation::{USER_CODE, USER_DATA};
15+
16+
use super::UserContext;
17+
18+
/// Defines the usermode-kernelmode ABI for x86 platforms.
19+
pub struct Boundary;
20+
21+
impl Default for Boundary {
22+
fn default() -> Self {
23+
Self::new()
24+
}
25+
}
26+
27+
impl Boundary {
28+
/// Minimum required size for initial process memory.
29+
///
30+
/// Need at least 5 dwords of initial stack space for CRT 0:
31+
///
32+
/// - 1 dword for initial upcall return address (although this will be zero for init_fn)
33+
/// - 4 dwords of scratch space for invoking memop syscalls
34+
const MIN_APP_BRK: u32 = 5 * core::mem::size_of::<usize>() as u32;
35+
36+
/// Constructs a new instance of `SysCall`.
37+
pub fn new() -> Self {
38+
Self
39+
}
40+
}
41+
42+
impl UserspaceKernelBoundary for Boundary {
43+
type StoredState = UserContext;
44+
45+
fn initial_process_app_brk_size(&self) -> usize {
46+
Self::MIN_APP_BRK as usize
47+
}
48+
49+
unsafe fn initialize_process(
50+
&self,
51+
accessible_memory_start: *const u8,
52+
app_brk: *const u8,
53+
state: &mut Self::StoredState,
54+
) -> Result<(), ()> {
55+
if (app_brk as u32 - accessible_memory_start as u32) < Self::MIN_APP_BRK {
56+
return Err(());
57+
}
58+
59+
// We pre-allocate 16 bytes on the stack for initial upcall arguments.
60+
let esp = (app_brk as u32) - 16;
61+
62+
let mut eflags = EFlags::new();
63+
eflags.0.modify(EFLAGS::FLAGS_IF::SET);
64+
65+
state.eax = 0;
66+
state.ebx = 0;
67+
state.ecx = 0;
68+
state.edx = 0;
69+
state.esi = 0;
70+
state.edi = 0;
71+
state.ebp = 0;
72+
state.esp = esp;
73+
state.eip = 0;
74+
state.eflags = eflags.0.get();
75+
state.cs = USER_CODE.bits() as u32;
76+
state.ss = USER_DATA.bits() as u32;
77+
state.ds = USER_DATA.bits() as u32;
78+
state.es = USER_DATA.bits() as u32;
79+
state.fs = USER_DATA.bits() as u32;
80+
state.gs = USER_DATA.bits() as u32;
81+
82+
Ok(())
83+
}
84+
85+
unsafe fn set_syscall_return_value(
86+
&self,
87+
_accessible_memory_start: *const u8,
88+
_app_brk: *const u8,
89+
state: &mut Self::StoredState,
90+
return_value: SyscallReturn,
91+
) -> Result<(), ()> {
92+
kernel::utilities::arch_helpers::encode_syscall_return_trd104(
93+
&kernel::utilities::arch_helpers::TRD104SyscallReturn::from_syscall_return(
94+
return_value,
95+
),
96+
&mut state.ebx,
97+
&mut state.ecx,
98+
&mut state.edx,
99+
&mut state.edi,
100+
);
101+
102+
Ok(())
103+
}
104+
105+
unsafe fn set_process_function(
106+
&self,
107+
_accessible_memory_start: *const u8,
108+
_app_brk: *const u8,
109+
state: &mut Self::StoredState,
110+
upcall: FunctionCall,
111+
) -> Result<(), ()> {
112+
state.ebx = upcall.argument0 as u32;
113+
state.ecx = upcall.argument1 as u32;
114+
state.edx = upcall.argument2 as u32;
115+
state.edi = upcall.argument3.as_usize() as u32;
116+
117+
// The next time we switch to this process, we will directly jump to the upcall. When the
118+
// upcall issues `ret`, it will return to wherever the yield syscall was invoked.
119+
state.eip = upcall.pc.addr() as u32;
120+
121+
Ok(())
122+
}
123+
124+
unsafe fn switch_to_process(
125+
&self,
126+
_accessible_memory_start: *const u8,
127+
_app_brk: *const u8,
128+
state: &mut Self::StoredState,
129+
) -> (ContextSwitchReason, Option<*const u8>) {
130+
// Sanity check: don't try to run a faulted app
131+
if state.exception != 0 || state.err_code != 0 {
132+
let stack_ptr = state.esp as *mut u8;
133+
return (ContextSwitchReason::Fault, Some(stack_ptr));
134+
}
135+
136+
let mut err_code = 0;
137+
let int_num = unsafe { super::switch_to_user(state, &mut err_code) };
138+
139+
let reason = match int_num as u8 {
140+
0..IDT_RESERVED_EXCEPTIONS => {
141+
state.exception = int_num as u8;
142+
state.err_code = err_code;
143+
ContextSwitchReason::Fault
144+
}
145+
146+
SYSCALL_VECTOR => {
147+
let num = state.eax as u8;
148+
149+
let arg0 = state.ebx;
150+
let arg1 = state.ecx;
151+
let arg2 = state.edx;
152+
let arg3 = state.edi;
153+
154+
Syscall::from_register_arguments(
155+
num,
156+
arg0 as usize,
157+
(arg1 as usize).into(),
158+
(arg2 as usize).into(),
159+
(arg3 as usize).into(),
160+
)
161+
.map_or(ContextSwitchReason::Fault, |syscall| {
162+
ContextSwitchReason::SyscallFired { syscall }
163+
})
164+
}
165+
_ => ContextSwitchReason::Interrupted,
166+
};
167+
168+
let stack_ptr = state.esp as *const u8;
169+
170+
(reason, Some(stack_ptr))
171+
}
172+
173+
unsafe fn print_context(
174+
&self,
175+
_accessible_memory_start: *const u8,
176+
_app_brk: *const u8,
177+
state: &Self::StoredState,
178+
writer: &mut dyn Write,
179+
) {
180+
let _ = writeln!(writer, "{}", state);
181+
}
182+
183+
fn store_context(
184+
&self,
185+
_state: &Self::StoredState,
186+
_out: &mut [u8],
187+
) -> Result<usize, ErrorCode> {
188+
unimplemented!()
189+
}
190+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
use core::fmt::{self, Display, Formatter};
6+
7+
use crate::registers::irq::EXCEPTIONS;
8+
9+
/// Stored CPU state of a user-mode app
10+
///
11+
/// This struct stores the complete CPU state of a user-mode Tock application on x86.
12+
///
13+
/// We access this struct from several assembly routines to perform context switching between user
14+
/// and kernel mode. For this reason, it is **critical** that the struct have a deterministic layout
15+
/// in memory. We use `#[repr(C)]` for this.
16+
#[repr(C)]
17+
#[derive(Default)]
18+
pub struct UserContext {
19+
pub eax: u32, // Offset: 0
20+
pub ebx: u32, // Offset: 4
21+
pub ecx: u32, // Offset: 8
22+
pub edx: u32, // Offset: 12
23+
pub esi: u32, // Offset: 16
24+
pub edi: u32, // Offset: 20
25+
pub ebp: u32, // Offset: 24
26+
pub esp: u32, // Offset: 28
27+
pub eip: u32, // Offset: 32
28+
pub eflags: u32, // Offset: 36
29+
pub cs: u32, // Offset: 40
30+
pub ss: u32, // Offset: 44
31+
pub ds: u32, // Offset: 48
32+
pub es: u32, // Offset: 52
33+
pub fs: u32, // Offset: 56
34+
pub gs: u32, // Offset: 60
35+
36+
/// If the process triggers a CPU exception, this field will be populated with the
37+
/// exception number. Otherwise this field must remain as zero.
38+
pub exception: u8,
39+
40+
/// If the process triggers a CPU exception with an associated error code, this field will be
41+
/// populated with the error code value. Otherwise this field must remain zero.
42+
pub err_code: u32,
43+
}
44+
45+
impl Display for UserContext {
46+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
47+
writeln!(f)?;
48+
writeln!(f, " CPU Registers:")?;
49+
writeln!(f)?;
50+
writeln!(f, " EAX: {:#010x} EBX: {:#010x}", self.eax, self.ebx)?;
51+
writeln!(f, " ECX: {:#010x} EDX: {:#010x}", self.ecx, self.edx)?;
52+
writeln!(f, " ESI: {:#010x} EDI: {:#010x}", self.esi, self.edi)?;
53+
writeln!(f, " EBP: {:#010x} ESP: {:#010x}", self.ebp, self.esp)?;
54+
writeln!(
55+
f,
56+
" EIP: {:#010x} EFLAGS: {:#010x}",
57+
self.eip, self.eflags
58+
)?;
59+
writeln!(
60+
f,
61+
" CS: {:#06x} SS: {:#06x}",
62+
self.cs, self.ss
63+
)?;
64+
writeln!(
65+
f,
66+
" DS: {:#06x} ES: {:#06x}",
67+
self.ds, self.es
68+
)?;
69+
writeln!(
70+
f,
71+
" FS: {:#06x} GS: {:#06x}",
72+
self.fs, self.gs
73+
)?;
74+
75+
if self.exception != 0 || self.err_code != 0 {
76+
writeln!(f)?;
77+
if let Some(msg) = EXCEPTIONS.get(self.exception as usize) {
78+
writeln!(f, " Exception: {}", msg)?;
79+
} else {
80+
writeln!(f, " Exception Number: {}", self.exception)?;
81+
}
82+
writeln!(f, " Error code: {:#010x}", self.err_code)?;
83+
}
84+
Ok(())
85+
}
86+
}

arch/x86-next/src/boundary/mod.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
71+
mod context;
72+
use context::UserContext;
73+
74+
mod boundary_impl;
75+
pub use self::boundary_impl::Boundary;
76+
77+
#[cfg(target_arch = "x86")]
78+
mod switch_to_user;
79+
80+
#[cfg(target_arch = "x86")]
81+
mod return_from_user;
82+
83+
extern "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+
}

0 commit comments

Comments
 (0)