Skip to content

Commit bab1368

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

51 files changed

Lines changed: 7243 additions & 1 deletion

Some content is hidden

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

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ members = [
1414
"arch/riscv",
1515
"arch/rv32i",
1616
"arch/x86",
17+
"arch/x86-next",
1718
"boards/nano_rp2040_connect",
1819
"boards/arty_e21",
1920
"boards/opentitan/earlgrey-cw310",
@@ -52,6 +53,7 @@ members = [
5253
"boards/nano33ble",
5354
"boards/nano33ble_rev2",
5455
"boards/qemu_i486_q35",
56+
"boards/qemu_i486_q35-next",
5557
"boards/qemu_rv32_virt",
5658
"boards/weact_f401ccu6/",
5759
"boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256",
@@ -88,6 +90,7 @@ members = [
8890
"chips/nrf52840",
8991
"chips/nrf5x",
9092
"chips/x86_q35",
93+
"chips/x86_q35-next",
9194
"chips/qemu_rv32_virt_chip",
9295
"chips/psoc62xa",
9396
"chips/rp2040",

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 with virtual memory support.
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: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
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 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+
43+
impl 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+
}

0 commit comments

Comments
 (0)