Skip to content

Commit

Permalink
[hal] Support user space for riscv64 and aarch64
Browse files Browse the repository at this point in the history
  • Loading branch information
Azure-stars authored and equation314 committed Sep 29, 2024
1 parent 0706b21 commit a2e8311
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 17 deletions.
169 changes: 165 additions & 4 deletions modules/axhal/src/arch/aarch64/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(unused_imports)]

use core::arch::asm;
use memory_addr::VirtAddr;
use memory_addr::{PhysAddr, VirtAddr};

/// Saved registers when a trap (exception) occurs.
#[repr(C)]
Expand All @@ -15,6 +17,147 @@ pub struct TrapFrame {
pub spsr: u64,
}

impl TrapFrame {
/// Gets the 0th syscall argument.
pub const fn arg0(&self) -> usize {
self.r[0] as _
}

/// Gets the 1st syscall argument.
pub const fn arg1(&self) -> usize {
self.r[1] as _
}

/// Gets the 2nd syscall argument.
pub const fn arg2(&self) -> usize {
self.r[2] as _
}

/// Gets the 3rd syscall argument.
pub const fn arg3(&self) -> usize {
self.r[3] as _
}

/// Gets the 4th syscall argument.
pub const fn arg4(&self) -> usize {
self.r[4] as _
}

/// Gets the 5th syscall argument.
pub const fn arg5(&self) -> usize {
self.r[5] as _
}
}

/// Context to enter user space.
#[cfg(feature = "uspace")]
pub struct UspaceContext(TrapFrame);

#[cfg(feature = "uspace")]
impl UspaceContext {
/// Creates an empty context with all registers set to zero.
pub const fn empty() -> Self {
unsafe { core::mem::MaybeUninit::zeroed().assume_init() }
}

/// Creates a new context with the given entry point, user stack pointer,
/// and the argument.
pub fn new(entry: usize, ustack_top: VirtAddr, arg0: usize) -> Self {
use aarch64_cpu::registers::SPSR_EL1;
let mut regs = [0; 31];
regs[0] = arg0 as _;
Self(TrapFrame {
r: regs,
usp: ustack_top.as_usize() as _,
elr: entry as _,
spsr: (SPSR_EL1::M::EL0t
+ SPSR_EL1::D::Masked
+ SPSR_EL1::A::Masked
+ SPSR_EL1::I::Unmasked
+ SPSR_EL1::F::Masked)
.value,
})
}

/// Creates a new context from the given [`TrapFrame`].
pub const fn from(trap_frame: &TrapFrame) -> Self {
Self(*trap_frame)
}

/// Gets the instruction pointer.
pub const fn get_ip(&self) -> usize {
self.0.elr as _
}

/// Gets the stack pointer.
pub const fn get_sp(&self) -> usize {
self.0.usp as _
}

/// Sets the instruction pointer.
pub const fn set_ip(&mut self, pc: usize) {
self.0.elr = pc as _;
}

/// Sets the stack pointer.
pub const fn set_sp(&mut self, sp: usize) {
self.0.usp = sp as _;
}

/// Sets the return value register.
pub const fn set_retval(&mut self, r0: usize) {
self.0.r[0] = r0 as _;
}

/// Enters user space.
///
/// It restores the user registers and jumps to the user entry point
/// (saved in `elr`).
/// When an exception or syscall occurs, the kernel stack pointer is
/// switched to `kstack_top`.
///
/// # Safety
///
/// This function is unsafe because it changes processor mode and the stack.
#[inline(never)]
#[no_mangle]
pub unsafe fn enter_uspace(&self, kstack_top: VirtAddr) -> ! {
super::disable_irqs();
// We do not handle traps that occur at the current exception level,
// so the kstack ptr(`sp_el1`) will not change during running in user space.
// Then we don't need to save the `sp_el1` to the taskctx.
asm!(
"
mov sp, x1
ldp x30, x9, [x0, 30 * 8]
ldp x10, x11, [x0, 32 * 8]
msr sp_el0, x9
msr elr_el1, x10
msr spsr_el1, x11
ldp x28, x29, [x0, 28 * 8]
ldp x26, x27, [x0, 26 * 8]
ldp x24, x25, [x0, 24 * 8]
ldp x22, x23, [x0, 22 * 8]
ldp x20, x21, [x0, 20 * 8]
ldp x18, x19, [x0, 18 * 8]
ldp x16, x17, [x0, 16 * 8]
ldp x14, x15, [x0, 14 * 8]
ldp x12, x13, [x0, 12 * 8]
ldp x10, x11, [x0, 10 * 8]
ldp x8, x9, [x0, 8 * 8]
ldp x6, x7, [x0, 6 * 8]
ldp x4, x5, [x0, 4 * 8]
ldp x2, x3, [x0, 2 * 8]
ldp x0, x1, [x0]
eret",
in("x0") &self.0,
in("x1") kstack_top.as_usize() ,
options(noreturn),
)
}
}

/// FP & SIMD registers.
#[repr(C, align(16))]
#[derive(Debug, Default)]
Expand Down Expand Up @@ -47,7 +190,7 @@ impl FpState {
/// and the next task restores its context from memory to CPU.
#[allow(missing_docs)]
#[repr(C)]
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct TaskContext {
pub sp: u64,
pub tpidr_el0: u64,
Expand All @@ -63,6 +206,9 @@ pub struct TaskContext {
pub r28: u64,
pub r29: u64,
pub lr: u64, // r30
/// The `ttbr0_el1` register value, i.e., the page table root.
#[cfg(feature = "uspace")]
pub ttbr0_el1: PhysAddr,
#[cfg(feature = "fp_simd")]
pub fp_state: FpState,
}
Expand All @@ -75,25 +221,40 @@ impl TaskContext {
///
/// [`init`]: TaskContext::init
/// [`switch_to`]: TaskContext::switch_to
pub const fn new() -> Self {
unsafe { core::mem::MaybeUninit::zeroed().assume_init() }
pub fn new() -> Self {
Self::default()
}

/// Initializes the context for a new task, with the given entry point and
/// kernel stack.
pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, tls_area: VirtAddr) {
self.sp = kstack_top.as_usize() as u64;
self.lr = entry as u64;
// When under `uspace` feature, kernel will not use this register.
self.tpidr_el0 = tls_area.as_usize() as u64;
}

/// Changes the page table root for user space (`ttbr0_el1` register for aarch64 in el1 level).
///
/// If not set, it means that this task is a kernel task and only `ttbr1_el1` register will be used.
#[cfg(feature = "uspace")]
pub fn set_page_table_root(&mut self, ttbr0_el1: PhysAddr) {
self.ttbr0_el1 = ttbr0_el1;
}

/// Switches to another task.
///
/// It first saves the current task's context from CPU to this place, and then
/// restores the next task's context from `next_ctx` to CPU.
pub fn switch_to(&mut self, next_ctx: &Self) {
#[cfg(feature = "fp_simd")]
self.fp_state.switch_to(&next_ctx.fp_state);
#[cfg(feature = "uspace")]
{
if self.ttbr0_el1 != next_ctx.ttbr0_el1 {
unsafe { super::write_page_table_root0(next_ctx.ttbr0_el1) };
}
}
unsafe { context_switch(self, next_ctx) }
}
}
Expand Down
2 changes: 2 additions & 0 deletions modules/axhal/src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use aarch64_cpu::registers::{DAIF, TPIDR_EL0, TTBR0_EL1, TTBR1_EL1, VBAR_EL1};
use memory_addr::{PhysAddr, VirtAddr};
use tock_registers::interfaces::{Readable, Writeable};

#[cfg(feature = "uspace")]
pub use self::context::UspaceContext;
pub use self::context::{FpState, TaskContext, TrapFrame};

/// Allows the current CPU to respond to interrupts.
Expand Down
5 changes: 5 additions & 0 deletions modules/axhal/src/arch/aarch64/trap.S
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
mrs x11, spsr_el1
stp x30, x9, [sp, 30 * 8]
stp x10, x11, [sp, 32 * 8]

# We may have interrupted userspace, or a guest, or exit-from or
# return-to either of those. So we can't trust sp_el0, and need to
# restore it.
bl {cache_current_task_ptr}
.endm

.macro RESTORE_REGS
Expand Down
5 changes: 3 additions & 2 deletions modules/axhal/src/arch/aarch64/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use tock_registers::interfaces::Readable;

use super::TrapFrame;

global_asm!(include_str!("trap.S"));
global_asm!(include_str!("trap.S"), cache_current_task_ptr = sym crate::cpu::cache_current_task_ptr);

#[repr(u8)]
#[derive(Debug)]
Expand Down Expand Up @@ -98,8 +98,9 @@ fn handle_sync_exception(tf: &mut TrapFrame) {
let esr = ESR_EL1.extract();
let iss = esr.read(ESR_EL1::ISS);
match esr.read_as_enum(ESR_EL1::EC) {
#[cfg(feature = "uspace")]
Some(ESR_EL1::EC::Value::SVC64) => {
warn!("No syscall is supported currently!");
tf.r[0] = crate::trap::handle_syscall(tf, tf.r[8] as usize) as u64;
}
Some(ESR_EL1::EC::Value::InstrAbortLowerEL) => handle_instruction_abort(tf, iss, true),
Some(ESR_EL1::EC::Value::InstrAbortCurrentEL) => handle_instruction_abort(tf, iss, false),
Expand Down
Loading

0 comments on commit a2e8311

Please sign in to comment.