Implement cpu_local with GS and ensure GS points to TSS

This commit is contained in:
Qingsong Chen 2024-09-19 02:11:32 +00:00 committed by Tate, Hongliang Tian
parent 52bde1721e
commit c2f7a10b84
30 changed files with 901 additions and 112 deletions

42
Cargo.lock generated
View File

@ -852,7 +852,7 @@ dependencies = [
"uart_16550",
"uefi",
"uefi-services",
"x86_64 0.14.11",
"x86_64",
"xmas-elf 0.8.0",
]
@ -1090,11 +1090,10 @@ dependencies = [
"spin 0.9.8",
"static_assertions",
"tdx-guest",
"trapframe",
"unwinding",
"volatile",
"x86",
"x86_64 0.14.11",
"x86_64",
"xarray",
]
@ -1267,15 +1266,6 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "raw-cpuid"
version = "11.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "rle-decode-fast"
version = "1.0.3"
@ -1437,8 +1427,8 @@ dependencies = [
"bitflags 1.3.2",
"iced-x86",
"lazy_static",
"raw-cpuid 10.7.0",
"x86_64 0.14.11",
"raw-cpuid",
"x86_64",
]
[[package]]
@ -1512,16 +1502,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "trapframe"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "105000258ba41c463b63403c9341c55a298f35f6137b1cca08c10f0409ef8d3a"
dependencies = [
"raw-cpuid 11.0.2",
"x86_64 0.15.1",
]
[[package]]
name = "typeflags"
version = "0.1.0"
@ -1714,7 +1694,7 @@ checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385"
dependencies = [
"bit_field",
"bitflags 1.3.2",
"raw-cpuid 10.7.0",
"raw-cpuid",
]
[[package]]
@ -1729,18 +1709,6 @@ dependencies = [
"volatile",
]
[[package]]
name = "x86_64"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bc79523af8abf92fb1a970c3e086c5a343f6bcc1a0eb890f575cbb3b45743df"
dependencies = [
"bit_field",
"bitflags 2.6.0",
"rustversion",
"volatile",
]
[[package]]
name = "xarray"
version = "0.1.0"

View File

@ -3,7 +3,7 @@
use ostd::cpu::UserContext;
use super::SyscallReturn;
use crate::{cpu::LinuxAbi, prelude::*};
use crate::prelude::*;
#[allow(non_camel_case_types)]
#[repr(u64)]
@ -18,7 +18,7 @@ pub enum ArchPrctlCode {
pub fn sys_arch_prctl(
code: u64,
addr: u64,
_ctx: &Context,
ctx: &Context,
user_ctx: &mut UserContext,
) -> Result<SyscallReturn> {
let arch_prctl_code = ArchPrctlCode::try_from(code)?;
@ -26,17 +26,24 @@ pub fn sys_arch_prctl(
"arch_prctl_code: {:?}, addr = 0x{:x}",
arch_prctl_code, addr
);
let res = do_arch_prctl(arch_prctl_code, addr, user_ctx).unwrap();
let res = do_arch_prctl(arch_prctl_code, addr, ctx, user_ctx).unwrap();
Ok(SyscallReturn::Return(res as _))
}
pub fn do_arch_prctl(code: ArchPrctlCode, addr: u64, ctx: &mut UserContext) -> Result<u64> {
pub fn do_arch_prctl(
code: ArchPrctlCode,
addr: u64,
ctx: &Context,
user_ctx: &mut UserContext,
) -> Result<u64> {
match code {
ArchPrctlCode::ARCH_SET_FS => {
ctx.set_tls_pointer(addr as usize);
ctx.task.set_tls_pointer(addr as usize);
user_ctx.set_tls_pointer(addr as usize);
user_ctx.activate_tls_pointer();
Ok(0)
}
ArchPrctlCode::ARCH_GET_FS => Ok(ctx.tls_pointer() as u64),
ArchPrctlCode::ARCH_GET_FS => Ok(user_ctx.tls_pointer() as u64),
ArchPrctlCode::ARCH_GET_GS | ArchPrctlCode::ARCH_SET_GS => {
return_errno_with_message!(Errno::EINVAL, "GS cannot be accessed from the user space")
}

View File

@ -5,7 +5,6 @@ use ostd::{cpu::UserContext, user::UserContextApi};
use super::{constants::*, SyscallReturn};
use crate::{
cpu::LinuxAbi,
fs::{
file_table::FileDesc,
fs_resolver::{FsPath, AT_FDCWD},

View File

@ -117,11 +117,17 @@ SECTIONS
# processor, while it would be copied to other dynamically allocated memory
# areas for the application processors.
. = ALIGN(4096);
.cpu_local : AT(ADDR(.cpu_local) - KERNEL_VMA) {
__cpu_local_start = .;
KEEP(*(SORT(.cpu_local)))
__cpu_local_end = .;
__cpu_local_start = .;
# Make sure that cpu_local_tss is right at the beginning of CPU local area,
# which stores the task state segment in x86_64 architecture, so that
# when trap from ring3 to ring0, CPU can switch stack correctly.
.cpu_local_tss : AT(ADDR(.cpu_local_tss) - KERNEL_VMA) {
*(.cpu_local_tss)
}
.cpu_local : AT(ADDR(.cpu_local) - KERNEL_VMA) {
KEEP(*(SORT(.cpu_local)))
}
__cpu_local_end = .;
.bss : AT(ADDR(.bss) - KERNEL_VMA) {
__bss = .;

View File

@ -39,7 +39,6 @@ owo-colors = { version = "3", optional = true }
ostd-pod = { git = "https://github.com/asterinas/ostd-pod", rev = "c4644be", version = "0.1.1" }
spin = "0.9.4"
static_assertions = "1.1.0"
trapframe = "0.10.0"
unwinding = { version = "0.2.2", default-features = false, features = ["fde-gnu-eh-frame-hdr", "hide-trace", "panic", "personality", "unwinder"] }
volatile = { version = "0.4.5", features = ["unstable"] }
xarray = { git = "https://github.com/asterinas/xarray", version = "0.1.0" }

View File

@ -2,25 +2,25 @@
//! Architecture dependent CPU-local information utilities.
use x86_64::registers::segmentation::{Segment64, FS};
use x86_64::registers::segmentation::{Segment64, GS};
/// Sets the base address for the CPU local storage by writing to the FS base model-specific register.
/// Sets the base address for the CPU local storage by writing to the GS base model-specific register.
/// This operation is marked as `unsafe` because it directly interfaces with low-level CPU registers.
///
/// # Safety
///
/// - This function is safe to call provided that the FS register is dedicated entirely for CPU local storage
/// - This function is safe to call provided that the GS register is dedicated entirely for CPU local storage
/// and is not concurrently accessed for other purposes.
/// - The caller must ensure that `addr` is a valid address and properly aligned, as required by the CPU.
/// - This function should only be called in contexts where the CPU is in a state to accept such changes,
/// such as during processor initialization.
pub(crate) unsafe fn set_base(addr: u64) {
FS::write_base(x86_64::addr::VirtAddr::new(addr));
GS::write_base(x86_64::addr::VirtAddr::new(addr));
}
/// Gets the base address for the CPU local storage by reading the FS base model-specific register.
/// Gets the base address for the CPU local storage by reading the GS base model-specific register.
pub(crate) fn get_base() -> u64 {
FS::read_base().as_u64()
GS::read_base().as_u64()
}
use crate::cpu::local::single_instr::{
@ -29,7 +29,7 @@ use crate::cpu::local::single_instr::{
SingleInstructionSubAssign,
};
/// The GDT ensures that the FS segment is initialized to zero on boot.
/// The GDT ensures that the GS segment is initialized to zero on boot.
/// This assertion checks that the base address has been set.
macro_rules! debug_assert_initialized {
() => {
@ -49,7 +49,7 @@ macro_rules! impl_numeric_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("add fs:[{0}], {1", $register_format, "}"),
concat!("add gs:[{0}], {1", $register_format, "}"),
in(reg) offset,
in($inout_type) val,
options(nostack),
@ -62,7 +62,7 @@ macro_rules! impl_numeric_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("sub fs:[{0}], {1", $register_format, "}"),
concat!("sub gs:[{0}], {1", $register_format, "}"),
in(reg) offset,
in($inout_type) val,
options(nostack),
@ -75,7 +75,7 @@ macro_rules! impl_numeric_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("and fs:[{0}], {1", $register_format, "}"),
concat!("and gs:[{0}], {1", $register_format, "}"),
in(reg) offset,
in($inout_type) val,
options(nostack),
@ -88,7 +88,7 @@ macro_rules! impl_numeric_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("or fs:[{0}], {1", $register_format, "}"),
concat!("or gs:[{0}], {1", $register_format, "}"),
in(reg) offset,
in($inout_type) val,
options(nostack),
@ -101,7 +101,7 @@ macro_rules! impl_numeric_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("xor fs:[{0}], {1", $register_format, "}"),
concat!("xor gs:[{0}], {1", $register_format, "}"),
in(reg) offset,
in($inout_type) val,
options(nostack),
@ -115,7 +115,7 @@ macro_rules! impl_numeric_single_instruction_for {
let val: Self;
core::arch::asm!(
concat!("mov {0", $register_format, "}, fs:[{1}]"),
concat!("mov {0", $register_format, "}, gs:[{1}]"),
out($inout_type) val,
in(reg) offset,
options(nostack, readonly),
@ -129,7 +129,7 @@ macro_rules! impl_numeric_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("mov fs:[{0}], {1", $register_format, "}"),
concat!("mov gs:[{0}], {1", $register_format, "}"),
in(reg) offset,
in($inout_type) val,
options(nostack),
@ -162,7 +162,7 @@ macro_rules! impl_generic_single_instruction_for {
let val: Self;
core::arch::asm!(
concat!("mov {0}, fs:[{1}]"),
concat!("mov {0}, gs:[{1}]"),
out(reg) val,
in(reg) offset,
options(nostack, readonly),
@ -176,7 +176,7 @@ macro_rules! impl_generic_single_instruction_for {
debug_assert_initialized!();
core::arch::asm!(
concat!("mov fs:[{0}], {1}"),
concat!("mov gs:[{0}], {1}"),
in(reg) offset,
in(reg) val,
options(nostack),
@ -202,7 +202,7 @@ impl SingleInstructionLoad for bool {
let val: u8;
core::arch::asm!(
"mov {0}, fs:[{1}]",
"mov {0}, gs:[{1}]",
out(reg_byte) val,
in(reg) offset,
options(nostack, readonly),
@ -218,7 +218,7 @@ impl SingleInstructionStore for bool {
let val: u8 = if val { 1 } else { 0 };
core::arch::asm!(
"mov fs:[{0}], {1}",
"mov gs:[{0}], {1}",
in(reg) offset,
in(reg_byte) val,
options(nostack),

View File

@ -12,10 +12,11 @@ use core::{
use bitflags::bitflags;
use cfg_if::cfg_if;
use log::debug;
pub use trapframe::GeneralRegs as RawGeneralRegs;
use trapframe::UserContext as RawUserContext;
use x86::bits64::segmentation::wrfsbase;
use x86_64::registers::rflags::RFlags;
pub use super::trap::GeneralRegs as RawGeneralRegs;
use super::trap::{TrapFrame, UserContext as RawUserContext};
use crate::{
task::scheduler,
trap::call_irq_callback_functions,
@ -76,6 +77,27 @@ impl UserContext {
pub fn fp_regs_mut(&mut self) -> &mut FpRegs {
&mut self.fp_regs
}
/// Sets thread-local storage pointer.
pub fn set_tls_pointer(&mut self, tls: usize) {
self.set_fsbase(tls)
}
/// Gets thread-local storage pointer.
pub fn tls_pointer(&self) -> usize {
self.fsbase()
}
/// Activates thread-local storage pointer on the current CPU.
///
/// # Safety
///
/// The method by itself is safe because the value of the TLS register won't affect kernel code.
/// But if the user relies on the TLS pointer, make sure that the pointer is correctly set when
/// entering the user space.
pub fn activate_tls_pointer(&self) {
unsafe { wrfsbase(self.fsbase() as u64) }
}
}
impl UserContextApiInternal for UserContext {
@ -135,8 +157,8 @@ impl UserContextApiInternal for UserContext {
return_reason
}
fn as_trap_frame(&self) -> trapframe::TrapFrame {
trapframe::TrapFrame {
fn as_trap_frame(&self) -> TrapFrame {
TrapFrame {
rax: self.user_context.general.rax,
rbx: self.user_context.general.rbx,
rcx: self.user_context.general.rcx,

View File

@ -9,11 +9,13 @@ use core::fmt::Debug;
use bitflags::bitflags;
use log::info;
use spin::Once;
use trapframe::TrapFrame;
use volatile::{access::ReadWrite, Volatile};
use super::registers::Capability;
use crate::{mm::Vaddr, trap::IrqLine};
use crate::{
mm::Vaddr,
trap::{IrqLine, TrapFrame},
};
#[derive(Debug)]
pub struct FaultEventRegisters {

View File

@ -8,10 +8,12 @@ use alloc::{boxed::Box, fmt::Debug, sync::Arc, vec::Vec};
use id_alloc::IdAlloc;
use spin::Once;
use trapframe::TrapFrame;
use x86_64::registers::rflags::{self, RFlags};
use crate::sync::{Mutex, PreemptDisabled, SpinLock, SpinLockGuard};
use crate::{
sync::{Mutex, PreemptDisabled, SpinLock, SpinLockGuard},
trap::TrapFrame,
};
/// The global allocator for software defined IRQ lines.
pub(crate) static IRQ_ALLOCATOR: Once<SpinLock<IdAlloc>> = Once::new();

View File

@ -8,7 +8,6 @@ use core::{
};
use log::info;
use trapframe::TrapFrame;
use x86::cpuid::cpuid;
use crate::{
@ -16,7 +15,7 @@ use crate::{
pit::{self, OperatingMode},
TIMER_FREQ,
},
trap::IrqLine,
trap::{IrqLine, TrapFrame},
};
/// The frequency of TSC(Hz)

View File

@ -60,6 +60,10 @@ pub(crate) fn check_tdx_init() {
}
pub(crate) fn init_on_bsp() {
// SAFETY: this function is only called once on BSP.
unsafe {
crate::arch::trap::init(true);
}
irq::init();
kernel::acpi::init();

View File

@ -10,10 +10,12 @@ use core::fmt::Write;
use log::debug;
use spin::Once;
use trapframe::TrapFrame;
use super::{device::serial::SerialPort, kernel::IO_APIC};
use crate::{sync::SpinLock, trap::IrqLine};
use crate::{
sync::SpinLock,
trap::{IrqLine, TrapFrame},
};
/// Prints the formatted arguments to the standard output using the serial port.
#[inline]

View File

@ -11,6 +11,7 @@ core::arch::global_asm!(include_str!("switch.S"));
pub(crate) struct TaskContext {
pub regs: CalleeRegs,
pub rip: usize,
pub fsbase: usize,
}
impl TaskContext {
@ -18,8 +19,19 @@ impl TaskContext {
Self {
regs: CalleeRegs::new(),
rip: 0,
fsbase: 0,
}
}
/// Sets thread-local storage pointer.
pub fn set_tls_pointer(&mut self, tls: usize) {
self.fsbase = tls;
}
/// Gets thread-local storage pointer.
pub fn tls_pointer(&self) -> usize {
self.fsbase
}
}
/// Callee-saved registers.

View File

@ -14,7 +14,11 @@ context_switch: # (cur: *mut TaskContext, nxt: *TaskContext)
mov [rdi + 32], r13
mov [rdi + 40], r14
mov [rdi + 48], r15
rdfsbase r15
mov [rdi + 64], r15
# Restore nxt's registers
mov r15, [rsi + 64]
wrfsbase r15
mov rsp, [rsi + 0]
mov rbx, [rsi + 8]
mov rbp, [rsi + 16]

View File

@ -2,7 +2,6 @@
use log::warn;
use tdx_guest::{tdcall::accept_page, tdvmcall::map_gpa, TdxTrapFrame};
use trapframe::TrapFrame;
use crate::{
mm::{
@ -13,6 +12,7 @@ use crate::{
PAGE_SIZE,
},
prelude::Paddr,
trap::TrapFrame,
};
const SHARED_BIT: u8 = 51;

View File

@ -10,7 +10,6 @@ use core::{
use log::info;
use spin::Once;
use trapframe::TrapFrame;
use x86::{
cpuid::cpuid,
msr::{wrmsr, IA32_TSC_DEADLINE},
@ -26,7 +25,7 @@ use crate::{
tsc::TSC_FREQ,
},
},
trap::IrqLine,
trap::{IrqLine, TrapFrame},
};
/// Initializes APIC with tsc deadline mode or periodic mode.

View File

@ -12,13 +12,12 @@ use core::{cell::RefCell, sync::atomic::Ordering};
pub use jiffies::Jiffies;
use spin::Once;
use trapframe::TrapFrame;
use self::apic::APIC_TIMER_CALLBACK;
use crate::{
arch::x86::kernel,
cpu_local,
trap::{self, IrqLine},
trap::{self, IrqLine, TrapFrame},
};
/// The timer frequency (Hz). Here we choose 1000Hz since 1000Hz is easier for unit conversion and

View File

@ -0,0 +1,120 @@
// SPDX-License-Identifier: MPL-2.0 OR MIT
//
// The original source code is from [trapframe-rs](https://github.com/rcore-os/trapframe-rs),
// which is released under the following license:
//
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2020 - 2024 Runji Wang
//
// We make the following new changes:
// * Link TaskStateSegment to .cpu_local area.
// * Init TaskStateSegment on bsp/ap respectively.
//
// These changes are released under the following license:
//
// SPDX-License-Identifier: MPL-2.0
//! Configure Global Descriptor Table (GDT).
use alloc::{boxed::Box, vec::Vec};
use core::cell::SyncUnsafeCell;
use x86_64::{
instructions::tables::{lgdt, load_tss, sgdt},
registers::{
model_specific::Star,
segmentation::{Segment64, GS},
},
structures::{
gdt::{Descriptor, SegmentSelector},
tss::TaskStateSegment,
DescriptorTablePointer,
},
PrivilegeLevel, VirtAddr,
};
/// Init TSS & GDT.
pub unsafe fn init(on_bsp: bool) {
// Allocate stack for trap from user, set the stack top to TSS,
// so that when trap from ring3 to ring0, CPU can switch stack correctly.
let tss = if on_bsp {
init_local_tss_on_bsp()
} else {
init_local_tss_on_ap()
};
let (tss0, tss1) = match Descriptor::tss_segment(tss) {
Descriptor::SystemSegment(tss0, tss1) => (tss0, tss1),
_ => unreachable!(),
};
// FIXME: the segment limit assumed by x86_64 does not include the I/O port bitmap.
// Get current GDT.
let gdtp = sgdt();
let entry_count = (gdtp.limit + 1) as usize / size_of::<u64>();
let old_gdt = core::slice::from_raw_parts(gdtp.base.as_ptr::<u64>(), entry_count);
// Allocate new GDT with 7 more entries.
//
// NOTICE: for fast syscall:
// STAR[47:32] = K_CS = K_SS - 8
// STAR[63:48] = U_CS32 = U_SS32 - 8 = U_CS - 16
let mut gdt = Vec::from(old_gdt);
gdt.extend([tss0, tss1, KCODE64, KDATA64, UCODE32, UDATA32, UCODE64].iter());
let gdt = Vec::leak(gdt);
// Load new GDT and TSS.
lgdt(&DescriptorTablePointer {
limit: gdt.len() as u16 * 8 - 1,
base: VirtAddr::new(gdt.as_ptr() as _),
});
load_tss(SegmentSelector::new(
entry_count as u16,
PrivilegeLevel::Ring0,
));
let sysret = SegmentSelector::new(entry_count as u16 + 4, PrivilegeLevel::Ring3).0;
let syscall = SegmentSelector::new(entry_count as u16 + 2, PrivilegeLevel::Ring0).0;
Star::write_raw(sysret, syscall);
USER_SS = sysret + 8;
USER_CS = sysret + 16;
}
// The linker script ensure that cpu_local_tss section is right
// at the beginning of cpu_local area, so that gsbase (offset zero)
// points to LOCAL_TSS.
#[allow(dead_code)]
#[link_section = ".cpu_local_tss"]
static LOCAL_TSS: SyncUnsafeCell<TaskStateSegment> = SyncUnsafeCell::new(TaskStateSegment::new());
unsafe fn init_local_tss_on_bsp() -> &'static TaskStateSegment {
let tss_ptr = LOCAL_TSS.get();
let trap_stack_top = Box::leak(Box::new([0u8; 0x1000])).as_ptr() as u64 + 0x1000;
(*tss_ptr).privilege_stack_table[0] = VirtAddr::new(trap_stack_top);
&*tss_ptr
}
unsafe fn init_local_tss_on_ap() -> &'static TaskStateSegment {
let gs_base = GS::read_base().as_u64();
let tss_ptr = gs_base as *mut TaskStateSegment;
let trap_stack_top = Box::leak(Box::new([0u8; 0x1000])).as_ptr() as u64 + 0x1000;
(*tss_ptr).privilege_stack_table[0] = VirtAddr::new(trap_stack_top);
&*tss_ptr
}
#[no_mangle]
static mut USER_SS: u16 = 0;
#[no_mangle]
static mut USER_CS: u16 = 0;
const KCODE64: u64 = 0x00209800_00000000; // EXECUTABLE | USER_SEGMENT | PRESENT | LONG_MODE
const UCODE64: u64 = 0x0020F800_00000000; // EXECUTABLE | USER_SEGMENT | USER_MODE | PRESENT | LONG_MODE
const KDATA64: u64 = 0x00009200_00000000; // DATA_WRITABLE | USER_SEGMENT | PRESENT
#[allow(dead_code)]
const UDATA64: u64 = 0x0000F200_00000000; // DATA_WRITABLE | USER_SEGMENT | USER_MODE | PRESENT
const UCODE32: u64 = 0x00cffa00_0000ffff; // EXECUTABLE | USER_SEGMENT | USER_MODE | PRESENT
const UDATA32: u64 = 0x00cff200_0000ffff; // EXECUTABLE | USER_SEGMENT | USER_MODE | PRESENT

View File

@ -0,0 +1,47 @@
// SPDX-License-Identifier: MPL-2.0 OR MIT
//
// The original source code is from [trapframe-rs](https://github.com/rcore-os/trapframe-rs),
// which is released under the following license:
//
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2020 - 2024 Runji Wang
//
// We make the following new changes:
// * Include `trap.S` in this file and remove unused function `sidt`.
// * Link `VECTORS` to `trap_handler_table` defined in `trap.S`.
//
// These changes are released under the following license:
//
// SPDX-License-Identifier: MPL-2.0
//! Configure Interrupt Descriptor Table (GDT).
use alloc::boxed::Box;
use core::arch::global_asm;
use x86_64::{
structures::idt::{Entry, HandlerFunc, InterruptDescriptorTable},
PrivilegeLevel,
};
global_asm!(include_str!("trap.S"));
pub fn init() {
extern "C" {
#[link_name = "trap_handler_table"]
static VECTORS: [HandlerFunc; 256];
}
let idt = Box::leak(Box::new(InterruptDescriptorTable::new()));
let entries: &'static mut [Entry<HandlerFunc>; 256] =
unsafe { core::mem::transmute_copy(&idt) };
for i in 0..256 {
let opt = unsafe { entries[i].set_handler_fn(VECTORS[i]) };
// Enable user space `int3` and `into`.
if i == 3 || i == 4 {
opt.set_privilege_level(PrivilegeLevel::Ring3);
}
}
idt.load();
}

View File

@ -1,11 +1,28 @@
// SPDX-License-Identifier: MPL-2.0 OR MIT
//
// The original source code is from [trapframe-rs](https://github.com/rcore-os/trapframe-rs),
// which is released under the following license:
//
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2020 - 2024 Runji Wang
//
// We make the following new changes:
// * Implement the `trap_handler` of Asterinas.
//
// These changes are released under the following license:
//
// SPDX-License-Identifier: MPL-2.0
//! Handles trap.
mod gdt;
mod idt;
mod syscall;
use align_ext::AlignExt;
use cfg_if::cfg_if;
use log::debug;
use trapframe::TrapFrame;
use super::ex_table::ExTable;
use crate::{
@ -31,6 +48,168 @@ cpu_local_cell! {
static IS_KERNEL_INTERRUPTED: bool = false;
}
/// Trap frame of kernel interrupt
///
/// # Trap handler
///
/// You need to define a handler function like this:
///
/// ```
/// #[no_mangle]
/// extern "sysv64" fn trap_handler(tf: &mut TrapFrame) {
/// match tf.trap_num {
/// 3 => {
/// println!("TRAP: BreakPoint");
/// tf.rip += 1;
/// }
/// _ => panic!("TRAP: {:#x?}", tf),
/// }
/// }
/// ```
#[derive(Debug, Default, Clone, Copy)]
#[repr(C)]
#[allow(missing_docs)]
pub struct TrapFrame {
// Pushed by 'trap.S'
pub rax: usize,
pub rbx: usize,
pub rcx: usize,
pub rdx: usize,
pub rsi: usize,
pub rdi: usize,
pub rbp: usize,
pub rsp: usize,
pub r8: usize,
pub r9: usize,
pub r10: usize,
pub r11: usize,
pub r12: usize,
pub r13: usize,
pub r14: usize,
pub r15: usize,
pub _pad: usize,
pub trap_num: usize,
pub error_code: usize,
// Pushed by CPU
pub rip: usize,
pub cs: usize,
pub rflags: usize,
}
/// Initialize interrupt handling on x86_64.
///
/// # Safety
///
/// This function will:
///
/// - Disable interrupt.
/// - Switch to a new [GDT], extend 7 more entries from the current one.
/// - Switch to a new [TSS], `GSBASE` pointer to its base address.
/// - Switch to a new [IDT], override the current one.
/// - Enable [`syscall`] instruction.
/// - set `EFER::SYSTEM_CALL_EXTENSIONS`
///
/// [GDT]: https://wiki.osdev.org/GDT
/// [IDT]: https://wiki.osdev.org/IDT
/// [TSS]: https://wiki.osdev.org/Task_State_Segment
/// [`syscall`]: https://www.felixcloutier.com/x86/syscall
///
#[cfg(any(target_os = "none", target_os = "uefi"))]
pub unsafe fn init(on_bsp: bool) {
x86_64::instructions::interrupts::disable();
gdt::init(on_bsp);
idt::init();
syscall::init();
}
/// User space context.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
#[repr(C)]
#[allow(missing_docs)]
pub struct UserContext {
pub general: GeneralRegs,
pub trap_num: usize,
pub error_code: usize,
}
/// General registers.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
#[repr(C)]
#[allow(missing_docs)]
pub struct GeneralRegs {
pub rax: usize,
pub rbx: usize,
pub rcx: usize,
pub rdx: usize,
pub rsi: usize,
pub rdi: usize,
pub rbp: usize,
pub rsp: usize,
pub r8: usize,
pub r9: usize,
pub r10: usize,
pub r11: usize,
pub r12: usize,
pub r13: usize,
pub r14: usize,
pub r15: usize,
pub rip: usize,
pub rflags: usize,
pub fsbase: usize,
pub gsbase: usize,
}
impl UserContext {
/// Get number of syscall.
pub fn get_syscall_num(&self) -> usize {
self.general.rax
}
/// Get return value of syscall.
pub fn get_syscall_ret(&self) -> usize {
self.general.rax
}
/// Set return value of syscall.
pub fn set_syscall_ret(&mut self, ret: usize) {
self.general.rax = ret;
}
/// Get syscall args.
pub fn get_syscall_args(&self) -> [usize; 6] {
[
self.general.rdi,
self.general.rsi,
self.general.rdx,
self.general.r10,
self.general.r8,
self.general.r9,
]
}
/// Set instruction pointer.
pub fn set_ip(&mut self, ip: usize) {
self.general.rip = ip;
}
/// Set stack pointer.
pub fn set_sp(&mut self, sp: usize) {
self.general.rsp = sp;
}
/// Get stack pointer.
pub fn get_sp(&self) -> usize {
self.general.rsp
}
/// Set thread-local storage pointer.
pub fn set_tls(&mut self, tls: usize) {
self.general.fsbase = tls;
}
}
/// Returns true if this function is called within the context of an IRQ handler
/// and the IRQ occurs while the CPU is executing in the kernel mode.
/// Otherwise, it returns false.
@ -38,7 +217,7 @@ pub fn is_kernel_interrupted() -> bool {
IS_KERNEL_INTERRUPTED.load()
}
/// Only from kernel
/// Handle traps (only from kernel).
#[no_mangle]
extern "sysv64" fn trap_handler(f: &mut TrapFrame) {
if CpuException::is_cpu_exception(f.trap_num as u16) {

View File

@ -0,0 +1,140 @@
/* SPDX-License-Identifier: MPL-2.0 OR MIT
*
* The original source code is from [trapframe-rs](https://github.com/rcore-os/trapframe-rs),
* which is released under the following license:
*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2020 - 2024 Runji Wang
*
* We make the following new changes:
* * Skip saving/restoring the fsgsbase registers.
*
* These changes are released under the following license:
*
* SPDX-License-Identifier: MPL-2.0
*/
.text
# extern "sysv64" fn syscall_return(&mut GeneralRegs)
.global syscall_return
syscall_return:
# disable interrupt
cli
# save callee-saved registers
push r15
push r14
push r13
push r12
push rbp
push rbx
push rdi # keep rsp 16 bytes align
mov gs:4, rsp # store kernel rsp -> TSS.sp0
mov rsp, rdi # set rsp -> GeneralRegs
# restore user gsbase
swapgs
pop rax
pop rbx
pop rcx
pop rdx
pop rsi
pop rdi
pop rbp
pop r8 # skip rsp
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
# rip
# rflags
# fsbase
# gsbase
# trap_num
# error_code
# determain sysret or iret
cmp dword ptr [rsp + 4*8], 0x100 # syscall?
je sysret
iret:
# construct trap frame
push [USER_SS] # push ss
push [rsp - 8*8] # push rsp
push [rsp + 3*8] # push rflags
push [USER_CS] # push cs
push [rsp + 4*8] # push rip
iretq
sysret:
pop rcx # rcx = rip
pop r11 # r11 = rflags
mov rsp, [rsp - 11*8] # load rsp
sysretq
# sysretq instruction do:
# - load cs, ss
# - load rflags <- r11
# - load rip <- rcx
.global syscall_entry
syscall_entry:
# syscall instruction do:
# - load cs
# - store rflags -> r11
# - mask rflags
# - store rip -> rcx
# - load rip
swapgs # swap in kernel gs
mov gs:12, rsp # store user rsp -> scratch at TSS.sp1
mov rsp, gs:4 # load kernel rsp <- TSS.sp0
pop rsp # load rsp -> GeneralRegs
add rsp, 21*8 # rsp -> error code of GeneralRegs
push 0x100 # push trap_num
sub rsp, 16 # skip fsbase, gsbase
# push general registers
push r11 # push rflags
push rcx # push rip
.global trap_syscall_entry
trap_syscall_entry:
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push gs:12 # push rsp
push rbp
push rdi
push rsi
push rdx
push rcx
push rbx
push rax
# restore callee-saved registers
mov rsp, gs:4 # load kernel rsp <- TSS.sp0
pop rbx
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
# go back to Rust
ret

View File

@ -0,0 +1,101 @@
// SPDX-License-Identifier: MPL-2.0 OR MIT
//
// The original source code is from [trapframe-rs](https://github.com/rcore-os/trapframe-rs),
// which is released under the following license:
//
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2020 - 2024 Runji Wang
//
// We make the following new changes:
// * Revise some comments.
//
// These changes are released under the following license:
//
// SPDX-License-Identifier: MPL-2.0
//! Configure fast syscall.
use core::arch::global_asm;
use x86::cpuid::CpuId;
use x86_64::{
registers::{
control::{Cr4, Cr4Flags},
model_specific::{Efer, EferFlags, LStar, SFMask},
rflags::RFlags,
},
VirtAddr,
};
use super::UserContext;
global_asm!(include_str!("syscall.S"));
pub fn init() {
let cpuid = CpuId::new();
unsafe {
// Enable `syscall` instruction.
assert!(cpuid
.get_extended_processor_and_feature_identifiers()
.unwrap()
.has_syscall_sysret());
Efer::update(|efer| {
efer.insert(EferFlags::SYSTEM_CALL_EXTENSIONS);
});
// Enable `FSGSBASE` instructions.
assert!(cpuid.get_extended_feature_info().unwrap().has_fsgsbase());
Cr4::update(|cr4| {
cr4.insert(Cr4Flags::FSGSBASE);
});
// Flags to clear on syscall.
// Copy from Linux 5.0, TF|DF|IF|IOPL|AC|NT
const RFLAGS_MASK: u64 = 0x47700;
LStar::write(VirtAddr::new(syscall_entry as usize as u64));
SFMask::write(RFlags::from_bits(RFLAGS_MASK).unwrap());
}
}
extern "sysv64" {
fn syscall_entry();
fn syscall_return(regs: &mut UserContext);
}
impl UserContext {
/// Go to user space with the context, and come back when a trap occurs.
///
/// On return, the context will be reset to the status before the trap.
/// Trap reason and error code will be placed at `trap_num` and `error_code`.
///
/// If the trap was triggered by `syscall` instruction, the `trap_num` will be set to `0x100`.
///
/// If `trap_num` is `0x100`, it will go user by `sysret` (`rcx` and `r11` are dropped),
/// otherwise it will use `iret`.
///
/// # Example
/// ```no_run
/// use trapframe::{UserContext, GeneralRegs};
///
/// // init user space context
/// let mut context = UserContext {
/// general: GeneralRegs {
/// rip: 0x1000,
/// rsp: 0x10000,
/// ..Default::default()
/// },
/// ..Default::default()
/// };
/// // go to user
/// context.run();
/// // back from user
/// println!("back from user: {:#x?}", context);
/// ```
pub fn run(&mut self) {
unsafe {
syscall_return(self);
}
}
}

View File

@ -0,0 +1,149 @@
/* SPDX-License-Identifier: MPL-2.0 OR MIT
*
* The original source code is from [trapframe-rs](https://github.com/rcore-os/trapframe-rs),
* which is released under the following license:
*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2020 - 2024 Runji Wang
*
* We make the following new changes:
* * Add the `trap_handler_table`.
*
* These changes are released under the following license:
*
* SPDX-License-Identifier: MPL-2.0
*/
.equ NUM_INT, 256
.altmacro
.macro DEF_HANDLER, i
.Ltrap_handler_\i:
.if \i == 8 || (\i >= 10 && \i <= 14) || \i == 17
# error code pushed by CPU
push \i # interrupt vector
jmp trap_common
.else
push 0 # fill in error code in TrapFrame
push \i # interrupt vector
jmp trap_common
.endif
.endm
.section .text
_trap_handlers:
.set i, 0
.rept NUM_INT
DEF_HANDLER %i
.set i, i + 1
.endr
.macro DEF_TABLE_ENTRY, i
.quad .Ltrap_handler_\i
.endm
.section .rodata
.global trap_handler_table
trap_handler_table:
.set i, 0
.rept NUM_INT
DEF_TABLE_ENTRY %i
.set i, i + 1
.endr
.section .text
.global trap_common
trap_common:
push rax
mov ax, [rsp + 4*8] # load cs
and ax, 0x3 # test
jz __from_kernel # continue trap
__from_user:
/*
kernel stack:
- ptr to GeneralRegs
- ss
- rsp
- rflags
- cs
- rip
- error code
- trap num
- rax
*/
swapgs # swap in kernel gs
mov rax, [rsp + 6*8] # rax = user rsp
mov gs:12, rax # store user rsp -> scratch at TSS.sp1
mov rsp, [rsp + 8*8] # load rsp -> GeneralRegs
add rsp, 22*8 # rsp -> top of GeneralRegs
mov rax, gs:4 # rax = kernel stack
# push trap_num, error_code
push [rax - 6*8] # push error_code
push [rax - 7*8] # push trap_num
sub rsp, 16 # skip fsbase, gsbase
# push general registers
push [rax - 3*8] # push rflags
push [rax - 5*8] # push rip
mov rax, [rax - 8*8] # pop rax
jmp trap_syscall_entry
__from_kernel:
/*
kernel stack:
- rflags
- cs
- rip
- error code
- trap num
- rax
*/
pop rax
push 0
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
lea r8, [rsp + 13*8]
push r8 # push rsp
push rbp
push rdi
push rsi
push rdx
push rcx
push rbx
push rax
mov rdi, rsp
call trap_handler
.global trap_return
trap_return:
pop rax
pop rbx
pop rcx
pop rdx
pop rsi
pop rdi
pop rbp
pop r8 # skip rsp
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
# skip padding, trap_num, error_code
add rsp, 24
iretq

View File

@ -121,7 +121,7 @@ fn ap_early_entry(local_apic_id: u32) -> ! {
// SAFETY: this function is only called once on this AP.
unsafe {
trapframe::init();
crate::arch::trap::init(false);
}
// SAFETY: this function is only called once on this AP, after the BSP has

View File

@ -18,6 +18,7 @@
#![feature(panic_info_message)]
#![feature(ptr_sub_ptr)]
#![feature(strict_provenance)]
#![feature(sync_unsafe_cell)]
// The `generic_const_exprs` feature is incomplete however required for the page table
// const generic implementation. We are using this feature in a conservative manner.
#![allow(incomplete_features)]
@ -87,7 +88,6 @@ pub unsafe fn init() {
mm::kspace::init_kernel_page_table(mm::init_page_meta());
mm::misc_init();
trapframe::init();
// SAFETY: This function is called only once in the entire system.
unsafe { trap::softirq::init() };
arch::init_on_bsp();

View File

@ -32,6 +32,7 @@ pub struct Task {
user_space: Option<Arc<UserSpace>>,
ctx: UnsafeCell<TaskContext>,
/// kernel stack, note that the top is SyscallFrame/TrapFrame
#[allow(dead_code)]
kstack: KernelStack,
schedule_info: TaskScheduleInfo,
@ -53,6 +54,22 @@ impl Task {
&self.ctx
}
/// Sets thread-local storage pointer.
pub fn set_tls_pointer(&self, tls: usize) {
let ctx_ptr = self.ctx.get();
// SAFETY: it's safe to set user tls pointer in kernel context.
unsafe { (*ctx_ptr).set_tls_pointer(tls) }
}
/// Gets thread-local storage pointer.
pub fn tls_pointer(&self) -> usize {
let ctx_ptr = self.ctx.get();
// SAFETY: it's safe to get user tls pointer in kernel context.
unsafe { (*ctx_ptr).tls_pointer() }
}
/// Yields execution so that another task may be scheduled.
///
/// Note that this method cannot be simply named "yield" as the name is
@ -176,21 +193,14 @@ impl TaskOptions {
current_task.exit();
}
let mut new_task = Task {
func: self.func.unwrap(),
data: self.data.unwrap(),
user_space: self.user_space,
ctx: UnsafeCell::new(TaskContext::default()),
kstack: KernelStack::new_with_guard_page()?,
schedule_info: TaskScheduleInfo {
cpu: AtomicCpuId::default(),
priority: self.priority,
cpu_affinity: self.cpu_affinity,
},
};
let kstack = KernelStack::new_with_guard_page()?;
let ctx = new_task.ctx.get_mut();
ctx.set_instruction_pointer(kernel_task_entry as usize);
let mut ctx = UnsafeCell::new(TaskContext::default());
if let Some(user_space) = self.user_space.as_ref() {
ctx.get_mut().set_tls_pointer(user_space.tls_pointer());
};
ctx.get_mut()
.set_instruction_pointer(kernel_task_entry as usize);
// We should reserve space for the return address in the stack, otherwise
// we will write across the page boundary due to the implementation of
// the context switch.
@ -199,7 +209,21 @@ impl TaskOptions {
// to at least 16 bytes. And a larger alignment is needed if larger arguments
// are passed to the function. The `kernel_task_entry` function does not
// have any arguments, so we only need to align the stack pointer to 16 bytes.
ctx.set_stack_pointer(crate::mm::paddr_to_vaddr(new_task.kstack.end_paddr() - 16));
ctx.get_mut()
.set_stack_pointer(crate::mm::paddr_to_vaddr(kstack.end_paddr() - 16));
let new_task = Task {
func: self.func.unwrap(),
data: self.data.unwrap(),
user_space: self.user_space,
ctx,
kstack,
schedule_info: TaskScheduleInfo {
cpu: AtomicCpuId::default(),
priority: self.priority,
cpu_affinity: self.cpu_affinity,
},
};
Ok(new_task)
}

View File

@ -1,8 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
use trapframe::TrapFrame;
use crate::{arch::irq::IRQ_LIST, cpu_local_cell};
use crate::{arch::irq::IRQ_LIST, cpu_local_cell, trap::TrapFrame};
pub(crate) fn call_irq_callback_functions(trap_frame: &TrapFrame, irq_number: usize) {
// For x86 CPUs, interrupts are not re-entrant. Local interrupts will be disabled when

View File

@ -4,11 +4,10 @@
use core::fmt::Debug;
use trapframe::TrapFrame;
use crate::{
arch::irq::{self, IrqCallbackHandle, IRQ_ALLOCATOR},
prelude::*,
trap::TrapFrame,
Error,
};

View File

@ -8,7 +8,7 @@ pub mod softirq;
pub use handler::in_interrupt_context;
pub use softirq::SoftIrqLine;
pub use trapframe::TrapFrame;
pub(crate) use self::handler::call_irq_callback_functions;
pub use self::irq::{disable_local, DisabledLocalIrqGuard, IrqCallbackFunction, IrqLine};
pub use crate::arch::trap::TrapFrame;

View File

@ -4,9 +4,7 @@
//! User space.
use trapframe::TrapFrame;
use crate::{cpu::UserContext, mm::VmSpace, prelude::*, task::Task};
use crate::{cpu::UserContext, mm::VmSpace, prelude::*, task::Task, trap::TrapFrame};
/// A user space.
///
@ -47,6 +45,16 @@ impl UserSpace {
pub fn user_mode(&self) -> UserMode<'_> {
todo!()
}
/// Sets thread-local storage pointer.
pub fn set_tls_pointer(&mut self, tls: usize) {
self.init_ctx.set_tls_pointer(tls)
}
/// Gets thread-local storage pointer.
pub fn tls_pointer(&self) -> usize {
self.init_ctx.tls_pointer()
}
}
/// Specific architectures need to implement this trait. This should only used in [`UserMode`]