Skip to content

Commit 535d7af

Browse files
committed
kernel/task: Align kernel- and user-thread launch process
User-tasks use a different launch path than kernel-tasks. Kernel-tasks go directly to a Rust function on the `ret` in `switch_context` while for user-tasks the `ret`-target is an assembly function which calls the `setup_user_task()` helper and then goes into the `iret` path. This causes some fraction in how the initial stack layouts of both task types are set up, so consolidate this to be more similar in both cases. Kernel- and User thread now use a common thread entry in assembly code, which unifies the initial thread setup. Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
1 parent 6446366 commit 535d7af

4 files changed

Lines changed: 92 additions & 76 deletions

File tree

kernel/src/cpu/idt/svsm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn load_static_idt() {
5151
}
5252

5353
unsafe extern "C" {
54-
pub fn return_new_task();
54+
pub fn thread_entry_asm();
5555
pub fn default_return();
5656
fn asm_entry_de();
5757
fn asm_entry_db();

kernel/src/cpu/idt/svsm_entry.S

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,23 @@ return_user:
369369
// Put user-mode specific return code here
370370
jmp return_all_paths
371371

372-
.globl return_new_task
373-
return_new_task:
374-
call setup_user_task
375-
jmp default_return
372+
.globl thread_entry_asm
373+
thread_entry_asm:
374+
// Call common thread setup function
375+
call complete_new_thread
376+
377+
// Move thread fn and start parameter
378+
movq %r15, %rsi
379+
movq %r14, %rdi
380+
381+
// Fetch thread entry function pointer - do not touch stack pointer to
382+
// keep stack alignment
383+
movq (%rsp), %rax
384+
call *%rax
385+
386+
// Skip to return addr
387+
addq $8, %rsp
388+
ret
376389

377390
// #DE Divide-by-Zero-Error Exception (Vector 0)
378391
default_entry_no_ist name=de handler=terminate error_code=0 vector=0

kernel/src/cpu/shadow_stack.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ pub enum ShadowStackInit {
9797
/// The address of the first instruction that will be executed by the task.
9898
entry_return: usize,
9999
/// The address of the function that's executed when the task exits.
100-
exit_return: Option<usize>,
100+
exit_return: usize,
101+
/// Whether there is an iret frame on the bottom of the stack.
102+
iret_frame: bool,
101103
},
102104
/// A shadow stack to be used during context switches.
103105
///
@@ -116,58 +118,63 @@ pub fn init_shadow_stack(
116118
init: ShadowStackInit,
117119
) -> (Option<VirtAddr>, VirtAddr) {
118120
// Initialize the shadow stack.
119-
let mut chunk = [0; 24];
120-
let (base_token_addr, ssp) = match init {
121+
let mut chunk = [0; 32];
122+
let (base_token_addr, ssp, len) = match init {
121123
ShadowStackInit::Normal {
122124
entry_return,
123125
exit_return,
126+
iret_frame,
124127
} => {
125-
// If exit return is empty, then this thread will be used as a
126-
// user task stack. In that case, place a busy token at the
127-
// base of the shadow stack.
128128
let base_token_addr = top_of_sstack - 8;
129-
let base_token = match exit_return {
130-
Some(addr) => addr,
131-
None => base_token_addr.bits() + BUSY,
132-
};
133-
134129
let (token_bytes, rip_bytes) = chunk.split_at_mut(8);
135130

131+
let len: usize = if iret_frame { 32 } else { 24 };
132+
136133
// Create a shadow stack restore token.
137-
let token_addr = top_of_sstack - 24;
134+
let token_addr = top_of_sstack - len;
138135
let token = (token_addr + 8).bits() + MODE_64BIT;
139136
token_bytes.copy_from_slice(&token.to_ne_bytes());
140137

141138
let (entry_bytes, base_bytes) = rip_bytes.split_at_mut(8);
142139
entry_bytes.copy_from_slice(&entry_return.to_ne_bytes());
143-
base_bytes.copy_from_slice(&base_token.to_ne_bytes());
144140

145-
(Some(base_token_addr), token_addr)
141+
let (exit_bytes, bottom_bytes) = base_bytes.split_at_mut(8);
142+
exit_bytes.copy_from_slice(&exit_return.to_ne_bytes());
143+
144+
if iret_frame {
145+
// If iret_frame is true, then this thread will be used as a
146+
// user task stack. In that case, place a busy token at the
147+
// base of the shadow stack.
148+
let base_token = base_token_addr.bits() + BUSY;
149+
bottom_bytes.copy_from_slice(&base_token.to_ne_bytes());
150+
}
151+
152+
(Some(base_token_addr), token_addr, len)
146153
}
147154
ShadowStackInit::ContextSwitch => {
148-
let (_, token_bytes) = chunk.split_at_mut(16);
155+
let (_, token_bytes) = chunk.split_at_mut(24);
149156

150157
// Create a shadow stack restore token.
151158
let token_addr = top_of_sstack - 8;
152159
let token = (token_addr + 8).bits() + MODE_64BIT;
153160
token_bytes.copy_from_slice(&token.to_ne_bytes());
154161

155-
(None, token_addr)
162+
(None, token_addr, 32)
156163
}
157164
ShadowStackInit::Exception => {
158-
let (_, token_bytes) = chunk.split_at_mut(16);
165+
let (_, token_bytes) = chunk.split_at_mut(24);
159166

160167
// Create a supervisor shadow stack token.
161168
let token_addr = top_of_sstack - 8;
162169
let token = token_addr.bits();
163170
token_bytes.copy_from_slice(&token.to_ne_bytes());
164171

165-
(None, token_addr)
172+
(None, token_addr, 32)
166173
}
167-
ShadowStackInit::Init => (None, top_of_sstack - 8),
174+
ShadowStackInit::Init => (None, top_of_sstack - 8, 24),
168175
};
169176

170-
page.write(PAGE_SIZE - chunk.len(), &chunk);
177+
page.write(PAGE_SIZE - len, &chunk[..len]);
171178

172179
(base_token_addr, ssp)
173180
}

kernel/src/task/tasks.rs

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::address::{Address, VirtAddr};
2222
use crate::cpu::IrqGuard;
2323
use crate::cpu::ShadowStackInit;
2424
use crate::cpu::features::{Feature, cpu_has_feat};
25-
use crate::cpu::idt::svsm::return_new_task;
25+
use crate::cpu::idt::svsm::{default_return, thread_entry_asm};
2626
use crate::cpu::irq_state::EFLAGS_IF;
2727
use crate::cpu::irqs_enable;
2828
use crate::cpu::irqs_enabled;
@@ -231,29 +231,29 @@ pub struct TaskContext {
231231
#[derive(Default, Debug, Clone, Copy)]
232232
struct KernelTaskContext {
233233
pub task_context: TaskContext,
234+
pub thread_routine: u64,
234235
pub exit_addr: u64,
235236
}
236237

237238
#[repr(C)]
238239
#[derive(Default, Debug, Clone, Copy)]
239240
struct UserTaskContext {
240241
pub task_context: TaskContext,
242+
pub thread_routine: u64,
243+
pub exit_addr: u64,
241244
pub excp_context: X86ExceptionContext,
242245
}
243246

244247
// To ensure stack frames are 16b-aligned, ret_addr must be 16b-aligned
245248
// so that (%rsp + 8) is 16b-aligned after the ret instruction in
246249
// switch_context
247250
const _: () = assert!(
248-
(size_of::<KernelTaskContext>() - offset_of!(KernelTaskContext, task_context.ret_addr)) % 16
251+
((size_of::<KernelTaskContext>() - offset_of!(KernelTaskContext, task_context.ret_addr)) % 16)
252+
- 8
249253
== 0
250254
);
251-
252-
// User-task return to return_new_task which is asm code, so %rsp must be
253-
// 16b-aligned after the ret instruction in switch_context to ensure stack
254-
// frames are 16b-aligned
255255
const _: () = assert!(
256-
(size_of::<UserTaskContext>() - offset_of!(UserTaskContext, task_context.ret_addr) - 8) % 16
256+
((size_of::<UserTaskContext>() - offset_of!(UserTaskContext, task_context.ret_addr)) % 16) - 8
257257
== 0
258258
);
259259

@@ -442,11 +442,17 @@ impl Task {
442442

443443
// Determine which kernel-mode entry/exit routines will be used for
444444
// this task.
445-
let (entry_return, exit_return) = match args.start_info {
446-
ThreadStartInfo::User(_) => (return_new_task as *const () as usize, None),
447-
ThreadStartInfo::Kernel(ref info) => {
448-
(info.start_routine, Some(task_exit as *const () as usize))
449-
}
445+
let (entry_return, exit_return, iret_frame) = match args.start_info {
446+
ThreadStartInfo::User(_) => (
447+
thread_entry_asm as *const () as usize,
448+
default_return as *const () as usize,
449+
true,
450+
),
451+
ThreadStartInfo::Kernel(_) => (
452+
thread_entry_asm as *const () as usize,
453+
task_exit as *const () as usize,
454+
false,
455+
),
450456
};
451457

452458
let mut shadow_stack_offset = VirtAddr::null();
@@ -470,6 +476,7 @@ impl Task {
470476
ShadowStackInit::Normal {
471477
entry_return,
472478
exit_return,
479+
iret_frame,
473480
},
474481
);
475482

@@ -713,13 +720,15 @@ impl Task {
713720
let task_context = KernelTaskContext {
714721
task_context: TaskContext {
715722
regs: X86TaskSwitchRegs {
716-
rsi: entry,
717-
rdx: xsa_addr,
718-
rcx: start_parameter,
723+
rsi: xsa_addr,
724+
// Put these into callee-safed registers
725+
r14: entry,
726+
r15: start_parameter,
719727
..Default::default()
720728
},
721-
ret_addr: start_routine as u64,
729+
ret_addr: thread_entry_asm as *const () as u64,
722730
},
731+
thread_routine: start_routine as u64,
723732
exit_addr: task_exit as *const () as u64,
724733
};
725734

@@ -744,11 +753,13 @@ impl Task {
744753
rsi: xsa_addr, // XSAVE area addr
745754
..Default::default()
746755
},
747-
ret_addr: VirtAddr::from(return_new_task as *const ())
748-
.bits()
749-
.try_into()
750-
.unwrap(),
756+
ret_addr: thread_entry_asm as *const () as u64,
751757
},
758+
thread_routine: run_user_task as *const () as u64,
759+
exit_addr: VirtAddr::from(default_return as *const ())
760+
.bits()
761+
.try_into()
762+
.unwrap(),
752763
// Setup IRQ return frame. User-mode tasks always run with
753764
// interrupts enabled.
754765
excp_context: X86ExceptionContext {
@@ -995,23 +1006,8 @@ fn task_attach_console() {
9951006
.expect("Failed to attach console");
9961007
}
9971008

998-
/// Runs the first time a new task is scheduled, in the context of the new
999-
/// task. Any first-time initialization and setup work for a new task that
1000-
/// needs to happen in its context must be done here.
1001-
/// # Safety
1002-
/// The caller is required to verify the correctness of the save area address.
10031009
#[unsafe(no_mangle)]
1004-
unsafe fn setup_user_task(prev: usize, xsa_addr: u64) {
1005-
// SAFETY: caller needs to make sure that the previous task pointer and
1006-
// xsa_addr are valid.
1007-
unsafe {
1008-
// Needs to be the first function called here.
1009-
setup_new_task_common(xsa_addr, complete_task_switch(prev));
1010-
}
1011-
task_attach_console();
1012-
}
1013-
1014-
unsafe fn setup_new_task_common(xsa_addr: u64, prev_task: Option<TaskPointer>) {
1010+
unsafe fn complete_new_thread(prev: usize, xsa_addr: u64) {
10151011
// Re-enable IRQs here, as they are still disabled from the
10161012
// schedule()/sched_init() functions. After the context switch the IrqGuard
10171013
// from the previous task is not dropped, which causes IRQs to stay
@@ -1021,6 +1017,9 @@ unsafe fn setup_new_task_common(xsa_addr: u64, prev_task: Option<TaskPointer>) {
10211017
// is dropped, re-enabling IRQs.
10221018
irqs_enable();
10231019

1020+
// SAFETY: the scheduler passes the correct pointer to the previous task.
1021+
let prev_task = unsafe { complete_task_switch(prev) };
1022+
10241023
// Drop the previous task reference now that interrupts are reenabled.
10251024
// This ensures that the task destructor will run with interrupts enabled.
10261025
drop(prev_task);
@@ -1032,25 +1031,22 @@ unsafe fn setup_new_task_common(xsa_addr: u64, prev_task: Option<TaskPointer>) {
10321031
}
10331032
}
10341033

1034+
/// Runs the first time a new task is scheduled, in the context of the new
1035+
/// task. Any first-time initialization specific to user-tasks need to happen
1036+
/// here.
1037+
/// # Safety
1038+
/// This is called from assembly code. The caller needs to ensure the
1039+
/// parameters are passed correctly.
1040+
#[unsafe(no_mangle)]
1041+
unsafe fn run_user_task() {
1042+
task_attach_console();
1043+
}
1044+
10351045
/// # Safety
10361046
/// This function must only be invoked as a thread start function, and must
10371047
/// be invoked according to the start parameter data type expected by the entry
10381048
/// point.
1039-
unsafe fn run_kernel_task<T: KernelThreadStartParameter>(
1040-
prev: usize,
1041-
entry: fn(T),
1042-
xsa_addr: u64,
1043-
start_parameter: usize,
1044-
) {
1045-
// SAFETY: the scheduler passes the correct pointer to the previous task.
1046-
let prev_task = unsafe { complete_task_switch(prev) };
1047-
1048-
// SAFETY: the save area address is provided by the context switch assembly
1049-
// code.
1050-
unsafe {
1051-
setup_new_task_common(xsa_addr, prev_task);
1052-
}
1053-
1049+
unsafe fn run_kernel_task<T: KernelThreadStartParameter>(entry: fn(T), start_parameter: usize) {
10541050
// SAFETY: the `usize` start parameter was generated from an earlier call
10551051
// to `KernelThreadStartParameter::to_usize` when the start argument was
10561052
// prepared for use in the initial stack context, and therefore the safety

0 commit comments

Comments
 (0)