Red Bear OS kernel baseline

From release 0.1.0 pre-patched archive.
This includes all Red Bear modifications previously maintained
as patches in local/patches/kernel/.
This commit is contained in:
Red Bear OS
2026-06-27 09:19:25 +03:00
commit 82feefbaee
234 changed files with 40494 additions and 0 deletions
+391
View File
@@ -0,0 +1,391 @@
use crate::{
arch::{device::cpu::registers::control_regs, interrupt::InterruptStack},
context::context::Kstack,
percpu::PercpuBlock,
syscall::FloatRegisters,
};
use core::{mem::offset_of, ptr, sync::atomic::AtomicBool};
use spin::Once;
use syscall::{EnvRegisters, Result};
/// This must be used by the kernel to ensure that context switches are done atomically
/// Compare and exchange this to true when beginning a context switch on any CPU
/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
/// This must be done, as no locks can be held on the stack during switch
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
// 512 bytes for registers, extra bytes for fpcr and fpsr
pub const KFX_ALIGN: usize = 16;
#[derive(Clone, Debug)]
pub struct Context {
elr_el1: usize,
sp_el0: usize,
pub(crate) tpidr_el0: usize, /* Pointer to TLS region for this Context */
pub(crate) tpidrro_el0: usize, /* Pointer to TLS (read-only) region for this Context */
spsr_el1: usize,
esr_el1: usize,
fx_loadable: bool,
sp: usize, /* Stack Pointer (x31) */
lr: usize, /* Link Register (x30) */
fp: usize, /* Frame pointer Register (x29) */
x28: usize, /* Callee saved Register */
x27: usize, /* Callee saved Register */
x26: usize, /* Callee saved Register */
x25: usize, /* Callee saved Register */
x24: usize, /* Callee saved Register */
x23: usize, /* Callee saved Register */
x22: usize, /* Callee saved Register */
x21: usize, /* Callee saved Register */
x20: usize, /* Callee saved Register */
x19: usize, /* Callee saved Register */
}
impl Context {
pub fn new() -> Context {
Context {
elr_el1: 0,
sp_el0: 0,
tpidr_el0: 0,
tpidrro_el0: 0,
spsr_el1: 0,
esr_el1: 0,
fx_loadable: false,
sp: 0,
lr: 0,
fp: 0,
x28: 0,
x27: 0,
x26: 0,
x25: 0,
x24: 0,
x23: 0,
x22: 0,
x21: 0,
x20: 0,
x19: 0,
}
}
fn set_stack(&mut self, address: usize) {
self.sp = address;
}
fn set_x28(&mut self, x28: usize) {
self.x28 = x28;
}
fn set_lr(&mut self, address: usize) {
self.lr = address;
}
fn set_context_handle(&mut self) {
let address = self as *const _ as usize;
self.tpidrro_el0 = address;
}
pub(crate) fn setup_initial_call(
&mut self,
stack: &Kstack,
func: extern "C" fn(),
userspace_allowed: bool,
) {
let mut stack_top = stack.initial_top();
const INT_REGS_SIZE: usize = size_of::<InterruptStack>();
if userspace_allowed {
unsafe {
// Zero-initialize InterruptStack registers.
stack_top = stack_top.sub(INT_REGS_SIZE);
stack_top.write_bytes(0_u8, INT_REGS_SIZE);
(&mut *stack_top.cast::<InterruptStack>()).init();
}
}
self.set_lr(crate::arch::interrupt::syscall::enter_usermode as usize);
self.set_x28(func as usize);
self.set_context_handle();
self.set_stack(stack_top as usize);
}
#[allow(unused)]
pub fn dump(&self) {
println!("elr_el1: 0x{:016x}", self.elr_el1);
println!("sp_el0: 0x{:016x}", self.sp_el0);
println!("tpidr_el0: 0x{:016x}", self.tpidr_el0);
println!("tpidrro_el0: 0x{:016x}", self.tpidrro_el0);
println!("spsr_el1: 0x{:016x}", self.spsr_el1);
println!("esr_el1: 0x{:016x}", self.esr_el1);
println!("sp: 0x{:016x}", self.sp);
println!("lr: 0x{:016x}", self.lr);
println!("fp: 0x{:016x}", self.fp);
println!("x28: 0x{:016x}", self.x28);
println!("x27: 0x{:016x}", self.x27);
println!("x26: 0x{:016x}", self.x26);
println!("x25: 0x{:016x}", self.x25);
println!("x24: 0x{:016x}", self.x24);
println!("x23: 0x{:016x}", self.x23);
println!("x22: 0x{:016x}", self.x22);
println!("x21: 0x{:016x}", self.x21);
println!("x20: 0x{:016x}", self.x20);
println!("x19: 0x{:016x}", self.x19);
}
}
impl super::Context {
pub fn get_fx_regs(&self) -> FloatRegisters {
if !self.arch.fx_loadable {
panic!("TODO: make get_fx_regs always work");
}
unsafe { ptr::read(self.kfx.as_ptr() as *const FloatRegisters) }
}
pub fn set_fx_regs(&mut self, new: FloatRegisters) {
if !self.arch.fx_loadable {
panic!("TODO: make set_fx_regs always work");
}
unsafe {
ptr::write(self.kfx.as_mut_ptr() as *mut FloatRegisters, new);
}
}
pub fn current_syscall(&self) -> Option<[usize; 7]> {
if !self.inside_syscall {
return None;
}
let regs = self.regs()?;
let scratch = &regs.scratch;
Some([
scratch.x8, scratch.x0, scratch.x1, scratch.x2, scratch.x3, scratch.x4, scratch.x5,
])
}
pub(crate) fn write_current_env_regs(&self, regs: EnvRegisters) -> Result<()> {
unsafe {
control_regs::tpidr_el0_write(regs.tpidr_el0 as u64);
control_regs::tpidrro_el0_write(regs.tpidrro_el0 as u64);
}
Ok(())
}
pub(crate) fn write_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
self.arch.tpidr_el0 = regs.tpidr_el0;
self.arch.tpidrro_el0 = regs.tpidrro_el0;
Ok(())
}
pub(crate) fn read_current_env_regs(&self) -> Result<EnvRegisters> {
unsafe {
Ok(EnvRegisters {
tpidr_el0: control_regs::tpidr_el0() as usize,
tpidrro_el0: control_regs::tpidrro_el0() as usize,
})
}
}
pub(crate) fn read_env_regs(&self) -> Result<EnvRegisters> {
Ok(EnvRegisters {
tpidr_el0: self.arch.tpidr_el0,
tpidrro_el0: self.arch.tpidrro_el0,
})
}
pub fn set_userspace_io_allowed(&mut self, _allowed: bool) {}
}
pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = Once::new();
// SAFETY: EMPTY_CR3 must be initialized.
pub unsafe fn empty_cr3() -> rmm::PhysicalAddress {
unsafe {
debug_assert!(EMPTY_CR3.poll().is_some());
*EMPTY_CR3.get_unchecked()
}
}
#[target_feature(enable = "neon")]
unsafe extern "C" fn fp_save(float_regs: &mut FloatRegisters) {
unsafe {
core::arch::asm!(
"stp q0, q1, [{3}, {0} + 16 * 0]",
"stp q2, q3, [{3}, {0} + 16 * 2]",
"stp q4, q5, [{3}, {0} + 16 * 4]",
"stp q6, q7, [{3}, {0} + 16 * 6]",
"stp q8, q9, [{3}, {0} + 16 * 8]",
"stp q10, q11, [{3}, {0} + 16 * 10]",
"stp q12, q13, [{3}, {0} + 16 * 12]",
"stp q14, q15, [{3}, {0} + 16 * 14]",
"stp q16, q17, [{3}, {0} + 16 * 16]",
"stp q18, q19, [{3}, {0} + 16 * 18]",
"stp q20, q21, [{3}, {0} + 16 * 20]",
"stp q22, q23, [{3}, {0} + 16 * 22]",
"stp q24, q25, [{3}, {0} + 16 * 24]",
"stp q26, q27, [{3}, {0} + 16 * 26]",
"stp q28, q29, [{3}, {0} + 16 * 28]",
"stp q30, q31, [{3}, {0} + 16 * 30]",
"mrs x9, fpcr",
"add {3}, {3}, {1}",
"str x9, [{3}]",
"mrs x9, fpsr",
"str x9, [{3}, {2} - {1}]",
const offset_of!(FloatRegisters, fp_simd_regs),
const offset_of!(FloatRegisters, fpcr),
const offset_of!(FloatRegisters, fpsr),
inout(reg) float_regs => _,
);
}
}
#[target_feature(enable = "neon")]
unsafe extern "C" fn fp_load(float_regs: &mut FloatRegisters) {
unsafe {
core::arch::asm!(
"ldp q0, q1, [{3}, {0} + 16 * 0]",
"ldp q2, q3, [{3}, {0} + 16 * 2]",
"ldp q4, q5, [{3}, {0} + 16 * 4]",
"ldp q6, q7, [{3}, {0} + 16 * 6]",
"ldp q8, q9, [{3}, {0} + 16 * 8]",
"ldp q10, q11, [{3}, {0} + 16 * 10]",
"ldp q12, q13, [{3}, {0} + 16 * 12]",
"ldp q14, q15, [{3}, {0} + 16 * 14]",
"ldp q16, q17, [{3}, {0} + 16 * 16]",
"ldp q18, q19, [{3}, {0} + 16 * 18]",
"ldp q20, q21, [{3}, {0} + 16 * 20]",
"ldp q22, q23, [{3}, {0} + 16 * 22]",
"ldp q24, q25, [{3}, {0} + 16 * 24]",
"ldp q26, q27, [{3}, {0} + 16 * 26]",
"ldp q28, q29, [{3}, {0} + 16 * 28]",
"ldp q30, q31, [{3}, {0} + 16 * 30]",
"add {3}, {3}, {1}",
"ldr x9, [{3}]",
"msr fpcr, x9",
"ldr x9, [{3}, {2} - {1}]",
"msr fpsr, x9",
const offset_of!(FloatRegisters, fp_simd_regs),
const offset_of!(FloatRegisters, fpcr),
const offset_of!(FloatRegisters, fpsr),
inout(reg) float_regs => _,
);
}
}
pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) {
unsafe {
fp_save(&mut *(prev.kfx.as_mut_ptr() as *mut FloatRegisters));
prev.arch.fx_loadable = true;
if next.arch.fx_loadable {
fp_load(&mut *(next.kfx.as_mut_ptr() as *mut FloatRegisters));
}
PercpuBlock::current()
.new_addrsp_tmp
.set(next.addr_space.clone());
switch_to_inner(&mut prev.arch, &mut next.arch)
}
}
#[unsafe(naked)]
unsafe extern "C" fn switch_to_inner(_prev: &mut Context, _next: &mut Context) {
core::arch::naked_asm!(
"
str x19, [x0, #{off_x19}]
ldr x19, [x1, #{off_x19}]
str x20, [x0, #{off_x20}]
ldr x20, [x1, #{off_x20}]
str x21, [x0, #{off_x21}]
ldr x21, [x1, #{off_x21}]
str x22, [x0, #{off_x22}]
ldr x22, [x1, #{off_x22}]
str x23, [x0, #{off_x23}]
ldr x23, [x1, #{off_x23}]
str x24, [x0, #{off_x24}]
ldr x24, [x1, #{off_x24}]
str x25, [x0, #{off_x25}]
ldr x25, [x1, #{off_x25}]
str x26, [x0, #{off_x26}]
ldr x26, [x1, #{off_x26}]
str x27, [x0, #{off_x27}]
ldr x27, [x1, #{off_x27}]
str x28, [x0, #{off_x28}]
ldr x28, [x1, #{off_x28}]
str x29, [x0, #{off_x29}]
ldr x29, [x1, #{off_x29}]
str x30, [x0, #{off_x30}]
ldr x30, [x1, #{off_x30}]
mrs x2, elr_el1
str x2, [x0, #{off_elr_el1}]
ldr x2, [x1, #{off_elr_el1}]
msr elr_el1, x2
mrs x2, sp_el0
str x2, [x0, #{off_sp_el0}]
ldr x2, [x1, #{off_sp_el0}]
msr sp_el0, x2
mrs x2, tpidr_el0
str x2, [x0, #{off_tpidr_el0}]
ldr x2, [x1, #{off_tpidr_el0}]
msr tpidr_el0, x2
mrs x2, tpidrro_el0
str x2, [x0, #{off_tpidrro_el0}]
ldr x2, [x1, #{off_tpidrro_el0}]
msr tpidrro_el0, x2
mrs x2, spsr_el1
str x2, [x0, #{off_spsr_el1}]
ldr x2, [x1, #{off_spsr_el1}]
msr spsr_el1, x2
mrs x2, esr_el1
str x2, [x0, #{off_esr_el1}]
ldr x2, [x1, #{off_esr_el1}]
msr esr_el1, x2
mov x2, sp
str x2, [x0, #{off_sp}]
ldr x2, [x1, #{off_sp}]
mov sp, x2
b {switch_hook}
",
off_x19 = const(offset_of!(Context, x19)),
off_x20 = const(offset_of!(Context, x20)),
off_x21 = const(offset_of!(Context, x21)),
off_x22 = const(offset_of!(Context, x22)),
off_x23 = const(offset_of!(Context, x23)),
off_x24 = const(offset_of!(Context, x24)),
off_x25 = const(offset_of!(Context, x25)),
off_x26 = const(offset_of!(Context, x26)),
off_x27 = const(offset_of!(Context, x27)),
off_x28 = const(offset_of!(Context, x28)),
off_x29 = const(offset_of!(Context, fp)),
off_x30 = const(offset_of!(Context, lr)),
off_elr_el1 = const(offset_of!(Context, elr_el1)),
off_sp_el0 = const(offset_of!(Context, sp_el0)),
off_tpidr_el0 = const(offset_of!(Context, tpidr_el0)),
off_tpidrro_el0 = const(offset_of!(Context, tpidrro_el0)),
off_spsr_el1 = const(offset_of!(Context, spsr_el1)),
off_esr_el1 = const(offset_of!(Context, esr_el1)),
off_sp = const(offset_of!(Context, sp)),
switch_hook = sym crate::context::switch_finish_hook,
);
}
+224
View File
@@ -0,0 +1,224 @@
use crate::{
arch::interrupt::InterruptStack, context::context::Kstack, memory::RmmA, percpu::PercpuBlock,
syscall::FloatRegisters,
};
use core::{mem::offset_of, sync::atomic::AtomicBool};
use rmm::{Arch, VirtualAddress};
use spin::Once;
use syscall::{error::*, EnvRegisters};
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
pub const KFX_ALIGN: usize = 16;
#[derive(Clone, Debug, Default)]
pub struct Context {
sp: usize,
ra: usize,
fp: usize,
s1: usize,
s2: usize,
s3: usize,
s4: usize,
s5: usize,
s6: usize,
s7: usize,
s8: usize,
s9: usize,
s10: usize,
s11: usize,
sstatus: usize,
}
impl Context {
pub fn new() -> Self {
Self::default()
}
fn set_stack(&mut self, address: usize) {
self.sp = address;
}
fn set_ra(&mut self, address: usize) {
self.ra = address;
}
fn set_s11(&mut self, address: usize) {
self.s11 = address;
}
pub(crate) fn setup_initial_call(
&mut self,
stack: &Kstack,
func: extern "C" fn(),
userspace_allowed: bool,
) {
let mut stack_top = stack.initial_top();
const INT_REGS_SIZE: usize = size_of::<InterruptStack>();
if userspace_allowed {
unsafe {
// Zero-initialize InterruptStack registers.
stack_top = stack_top.sub(INT_REGS_SIZE);
stack_top.write_bytes(0_u8, INT_REGS_SIZE);
(&mut *stack_top.cast::<InterruptStack>()).init();
}
}
self.set_ra(crate::arch::interrupt::syscall::enter_usermode as usize);
self.set_s11(func as usize);
self.set_stack(stack_top as usize);
}
}
impl super::Context {
pub fn get_fx_regs(&self) -> FloatRegisters {
unimplemented!()
}
pub fn set_fx_regs(&mut self, mut _new: FloatRegisters) {
unimplemented!()
}
pub fn current_syscall(&self) -> Option<[usize; 7]> {
if !self.inside_syscall {
return None;
}
let regs = self.regs()?;
let regs = &regs.registers;
Some([
regs.x17, regs.x10, regs.x11, regs.x12, regs.x13, regs.x14, regs.x15,
])
}
pub(crate) fn write_current_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
self.write_env_regs(regs)
}
pub(crate) fn write_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
if RmmA::virt_is_valid(VirtualAddress::new(regs.tp)) {
match self.regs_mut() {
Some(stack) => {
stack.registers.x4 = regs.tp;
Ok(())
}
None => Err(Error::new(ESRCH)),
}
} else {
Err(Error::new(EINVAL))
}
}
pub(crate) fn read_current_env_regs(&self) -> Result<EnvRegisters> {
self.read_env_regs()
}
pub(crate) fn read_env_regs(&self) -> Result<EnvRegisters> {
match self.regs() {
Some(stack) => Ok(EnvRegisters {
tp: stack.registers.x4,
}),
None => Err(Error::new(ESRCH)),
}
}
pub fn set_userspace_io_allowed(&mut self, _allowed: bool) {}
}
pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = Once::new();
// SAFETY: EMPTY_CR3 must be initialized.
pub unsafe fn empty_cr3() -> rmm::PhysicalAddress {
unsafe {
debug_assert!(EMPTY_CR3.poll().is_some());
*EMPTY_CR3.get_unchecked()
}
}
/// Switch to the next context by restoring its stack and registers
pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) {
unsafe {
// FIXME floating point
PercpuBlock::current()
.new_addrsp_tmp
.set(next.addr_space.clone());
switch_to_inner(&mut prev.arch, &mut next.arch);
}
}
#[unsafe(naked)]
unsafe extern "C" fn switch_to_inner(prev: &mut Context, next: &mut Context) {
core::arch::naked_asm!(r#"
sd s1, {off_s1}(a0)
ld s1, {off_s1}(a1)
sd s2, {off_s2}(a0)
ld s2, {off_s2}(a1)
sd s3, {off_s3}(a0)
ld s3, {off_s3}(a1)
sd s4, {off_s4}(a0)
ld s4, {off_s4}(a1)
sd s5, {off_s5}(a0)
ld s5, {off_s5}(a1)
sd s6, {off_s6}(a0)
ld s6, {off_s6}(a1)
sd s7, {off_s7}(a0)
ld s7, {off_s7}(a1)
sd s8, {off_s8}(a0)
ld s8, {off_s8}(a1)
sd s9, {off_s9}(a0)
ld s9, {off_s9}(a1)
sd s10, {off_s10}(a0)
ld s10, {off_s10}(a1)
sd s11, {off_s11}(a0)
ld s11, {off_s11}(a1)
sd s11, {off_s11}(a0)
ld s11, {off_s11}(a1)
sd sp, {off_sp}(a0)
ld sp, {off_sp}(a1)
sd ra, {off_ra}(a0)
ld ra, {off_ra}(a1)
sd fp, {off_fp}(a0)
ld fp, {off_fp}(a1)
csrr t0, sstatus
sd t0, {off_sstatus}(a0)
ld t0, {off_sstatus}(a1)
csrw sstatus, t0
j {switch_hook}
"#,
off_s1 = const(offset_of!(Context, s1)),
off_s2 = const(offset_of!(Context, s2)),
off_s3 = const(offset_of!(Context, s3)),
off_s4 = const(offset_of!(Context, s4)),
off_s5 = const(offset_of!(Context, s5)),
off_s6 = const(offset_of!(Context, s6)),
off_s7 = const(offset_of!(Context, s7)),
off_s8 = const(offset_of!(Context, s8)),
off_s9 = const(offset_of!(Context, s9)),
off_s10 = const(offset_of!(Context, s10)),
off_s11 = const(offset_of!(Context, s11)),
off_sp = const(offset_of!(Context, sp)),
off_ra = const(offset_of!(Context, ra)),
off_fp = const(offset_of!(Context, fp)),
off_sstatus = const(offset_of!(Context, sstatus)),
switch_hook = sym crate::context::switch_finish_hook,
);
}
+315
View File
@@ -0,0 +1,315 @@
use core::{mem::offset_of, sync::atomic::AtomicBool};
use rmm::{Arch, VirtualAddress};
use spin::Once;
use syscall::{error::*, EnvRegisters};
use crate::{
arch::{
gdt::{pcr, GDT_USER_FS, GDT_USER_GS},
interrupt::{self, InterruptStack},
},
context::context::Kstack,
memory::RmmA,
percpu::PercpuBlock,
syscall::FloatRegisters,
};
/// This must be used by the kernel to ensure that context switches are done atomically
/// Compare and exchange this to true when beginning a context switch on any CPU
/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
/// This must be done, as no locks can be held on the stack during switch
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;
pub const KFX_ALIGN: usize = 16;
#[derive(Clone, Debug)]
#[repr(C)]
pub struct Context {
/// EFLAGS register
eflags: usize,
/// EBX register
ebx: usize,
/// EDI register
edi: usize,
/// ESI register
esi: usize,
/// Base pointer
ebp: usize,
/// Stack pointer
pub(crate) esp: usize,
/// FSBASE.
///
/// NOTE: Same fsgsbase behavior as with gsbase.
pub(crate) fsbase: usize,
/// GSBASE.
///
/// NOTE: Without fsgsbase, this register will strictly be equal to the register value when
/// running. With fsgsbase, this is neither saved nor restored upon every syscall (there is no
/// need to!), and thus it must be re-read from the register before copying this struct.
pub(crate) gsbase: usize,
userspace_io_allowed: bool,
}
impl Context {
pub fn new() -> Context {
Context {
eflags: 0,
ebx: 0,
edi: 0,
esi: 0,
ebp: 0,
esp: 0,
fsbase: 0,
gsbase: 0,
userspace_io_allowed: false,
}
}
fn set_stack(&mut self, address: usize) {
self.esp = address;
}
pub(crate) fn setup_initial_call(
&mut self,
stack: &Kstack,
func: extern "C" fn(),
userspace_allowed: bool,
) {
let mut stack_top = stack.initial_top();
const INT_REGS_SIZE: usize = size_of::<InterruptStack>();
unsafe {
if userspace_allowed {
// Zero-initialize InterruptStack registers.
stack_top = stack_top.sub(INT_REGS_SIZE);
stack_top.write_bytes(0_u8, INT_REGS_SIZE);
(&mut *stack_top.cast::<InterruptStack>()).init();
stack_top = stack_top.sub(size_of::<usize>());
stack_top
.cast::<usize>()
.write(interrupt::syscall::enter_usermode as usize);
}
stack_top = stack_top.sub(size_of::<usize>());
stack_top.cast::<usize>().write(func as usize);
}
self.set_stack(stack_top as usize);
}
}
impl super::Context {
pub fn get_fx_regs(&self) -> FloatRegisters {
let mut regs = unsafe { self.kfx.as_ptr().cast::<FloatRegisters>().read() };
regs._reserved = 0;
let mut new_st = regs.st_space;
for st in &mut new_st {
// Only allow access to the 80 lowest bits
*st &= !ST_RESERVED;
}
regs.st_space = new_st;
regs
}
pub fn set_fx_regs(&mut self, mut new: FloatRegisters) {
{
let old = unsafe { &*(self.kfx.as_ptr().cast::<FloatRegisters>()) };
new._reserved = old._reserved;
let old_st = new.st_space;
let mut new_st = new.st_space;
for (new_st, old_st) in new_st.iter_mut().zip(&old_st) {
*new_st &= !ST_RESERVED;
*new_st |= old_st & ST_RESERVED;
}
new.st_space = new_st;
// Make sure we don't use `old` from now on
}
unsafe {
self.kfx.as_mut_ptr().cast::<FloatRegisters>().write(new);
}
}
pub fn set_userspace_io_allowed(&mut self, allowed: bool) {
self.arch.userspace_io_allowed = allowed;
if self.is_current_context() {
unsafe {
crate::arch::gdt::set_userspace_io_allowed(crate::arch::gdt::pcr(), allowed);
}
}
}
pub fn current_syscall(&self) -> Option<[usize; 7]> {
if !self.inside_syscall {
return None;
}
let regs = self.regs()?;
Some([
regs.scratch.eax,
regs.preserved.ebx,
regs.scratch.ecx,
regs.scratch.edx,
regs.preserved.esi,
regs.preserved.edi,
regs.preserved.ebp,
])
}
pub(crate) fn write_current_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
if RmmA::virt_is_valid(VirtualAddress::new(regs.fsbase as usize))
&& RmmA::virt_is_valid(VirtualAddress::new(regs.gsbase as usize))
{
unsafe {
(&mut *pcr()).gdt[GDT_USER_FS].set_offset(regs.fsbase);
(&mut *pcr()).gdt[GDT_USER_GS].set_offset(regs.gsbase);
}
self.arch.fsbase = regs.fsbase as usize;
self.arch.gsbase = regs.gsbase as usize;
Ok(())
} else {
Err(Error::new(EINVAL))
}
}
pub(crate) fn write_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
if RmmA::virt_is_valid(VirtualAddress::new(regs.fsbase as usize))
&& RmmA::virt_is_valid(VirtualAddress::new(regs.gsbase as usize))
{
self.arch.fsbase = regs.fsbase as usize;
self.arch.gsbase = regs.gsbase as usize;
Ok(())
} else {
Err(Error::new(EINVAL))
}
}
pub(crate) fn read_current_env_regs(&self) -> Result<EnvRegisters> {
unsafe {
Ok(EnvRegisters {
fsbase: (&*pcr()).gdt[GDT_USER_FS].offset(),
gsbase: (&*pcr()).gdt[GDT_USER_GS].offset(),
})
}
}
pub(crate) fn read_env_regs(&self) -> Result<EnvRegisters> {
Ok(EnvRegisters {
fsbase: self.arch.fsbase as u32,
gsbase: self.arch.gsbase as u32,
})
}
}
pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = Once::new();
// SAFETY: EMPTY_CR3 must be initialized.
pub unsafe fn empty_cr3() -> rmm::PhysicalAddress {
unsafe {
debug_assert!(EMPTY_CR3.poll().is_some());
*EMPTY_CR3.get_unchecked()
}
}
/// Switch to the next context by restoring its stack and registers
pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) {
unsafe {
let pcr = crate::arch::gdt::pcr();
if let Some(ref stack) = next.kstack {
crate::arch::gdt::set_tss_stack(pcr, stack.initial_top() as usize);
}
crate::arch::gdt::set_userspace_io_allowed(pcr, next.arch.userspace_io_allowed);
core::arch::asm!("
fxsave [{prev_fx}]
fxrstor [{next_fx}]
", prev_fx = in(reg) prev.kfx.as_mut_ptr(),
next_fx = in(reg) next.kfx.as_ptr(),
);
{
let gdt = &mut (*pcr).gdt;
prev.arch.fsbase = gdt[GDT_USER_FS].offset() as usize;
gdt[GDT_USER_FS].set_offset(next.arch.fsbase as u32);
prev.arch.gsbase = gdt[GDT_USER_GS].offset() as usize;
gdt[GDT_USER_GS].set_offset(next.arch.gsbase as u32);
}
PercpuBlock::current()
.new_addrsp_tmp
.set(next.addr_space.clone());
core::arch::asm!(
"call {inner}",
inner = sym switch_to_inner,
in("ecx") &mut prev.arch,
in("edx") &mut next.arch,
);
}
}
// Check disassembly!
#[unsafe(naked)]
unsafe extern "cdecl" fn switch_to_inner() {
use Context as Cx;
core::arch::naked_asm!(
// As a quick reminder for those who are unfamiliar with the System V ABI (extern "C"):
//
// - the current parameters are passed in the registers `edi`, `esi`,
// - we can modify scratch registers, e.g. rax
// - we cannot change callee-preserved registers arbitrarily, e.g. ebx, which is why we
// store them here in the first place.
"
// ecx is prev, edx is next
// Save old registers, and load new ones
mov [ecx + {off_ebx}], ebx
mov ebx, [edx + {off_ebx}]
mov [ecx + {off_edi}], edi
mov edi, [edx + {off_edi}]
mov [ecx + {off_esi}], esi
mov esi, [edx + {off_esi}]
mov [ecx + {off_ebp}], ebp
mov ebp, [edx + {off_ebp}]
mov [ecx + {off_esp}], esp
mov esp, [edx + {off_esp}]
// push EFLAGS (can only be modified via stack)
pushfd
// pop EFLAGS into `self.eflags`
pop DWORD PTR [ecx + {off_eflags}]
// push `next.eflags`
push DWORD PTR [edx + {off_eflags}]
// pop into EFLAGS
popfd
// When we return, we cannot even guarantee that the return address on the stack, points to
// the calling function, `context::switch`. Thus, we have to execute this Rust hook by
// ourselves, which will unlock the contexts before the later switch.
// Note that switch_finish_hook will be responsible for executing `ret`.
jmp {switch_hook}
",
off_eflags = const(offset_of!(Cx, eflags)),
off_ebx = const(offset_of!(Cx, ebx)),
off_edi = const(offset_of!(Cx, edi)),
off_esi = const(offset_of!(Cx, esi)),
off_ebp = const(offset_of!(Cx, ebp)),
off_esp = const(offset_of!(Cx, esp)),
switch_hook = sym crate::context::switch_finish_hook,
);
}
+395
View File
@@ -0,0 +1,395 @@
use core::{
ptr::{addr_of, addr_of_mut},
sync::atomic::AtomicBool,
};
use crate::syscall::FloatRegisters;
use crate::{arch::interrupt::InterruptStack, context::context::Kstack, memory::RmmA};
use core::mem::offset_of;
use rmm::{Arch, VirtualAddress};
use spin::Once;
use syscall::{error::*, EnvRegisters};
use x86::msr;
/// This must be used by the kernel to ensure that context switches are done atomically
/// Compare and exchange this to true when beginning a context switch on any CPU
/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
/// This must be done, as no locks can be held on the stack during switch
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;
#[cfg(cpu_feature_never = "xsave")]
pub const KFX_ALIGN: usize = 16;
#[cfg(not(cpu_feature_never = "xsave"))]
pub const KFX_ALIGN: usize = 64;
// TODO: stack guarding?
#[derive(Clone, Debug)]
#[repr(C)]
pub struct Context {
/// RFLAGS register
rflags: usize,
/// RBX register
rbx: usize,
/// R12 register
r12: usize,
/// R13 register
r13: usize,
/// R14 register
r14: usize,
/// R15 register
r15: usize,
/// Base pointer
rbp: usize,
/// Stack pointer
pub(crate) rsp: usize,
/// FSBASE.
///
/// NOTE: Same fsgsbase behavior as with gsbase.
pub(crate) fsbase: usize,
/// GSBASE.
///
/// NOTE: Without fsgsbase, this register will strictly be equal to the register value when
/// running. With fsgsbase, this is neither saved nor restored upon every syscall (there is no
/// need to!), and thus it must be re-read from the register before copying this struct.
pub(crate) gsbase: usize,
userspace_io_allowed: bool,
}
impl Context {
pub fn new() -> Context {
Context {
rflags: 0,
rbx: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
rbp: 0,
rsp: 0,
fsbase: 0,
gsbase: 0,
userspace_io_allowed: false,
}
}
fn set_stack(&mut self, address: usize) {
self.rsp = address;
}
pub(crate) fn setup_initial_call(
&mut self,
stack: &Kstack,
func: extern "C" fn(),
userspace_allowed: bool,
) {
let mut stack_top = stack.initial_top();
const INT_REGS_SIZE: usize = size_of::<InterruptStack>();
// Kstack::initial_top() is always at least 8 byte aligned. assertion to be safe
debug_assert!(
(stack_top as usize).is_multiple_of(8),
"Kstack not 8 byte aligned"
);
#[expect(clippy::cast_ptr_alignment)]
unsafe {
if userspace_allowed {
// Zero-initialize InterruptStack registers.
stack_top = stack_top.sub(INT_REGS_SIZE);
stack_top.write_bytes(0_u8, INT_REGS_SIZE);
(*stack_top.cast::<InterruptStack>()).init();
stack_top = stack_top.sub(size_of::<usize>());
stack_top
.cast::<usize>()
.write(crate::arch::interrupt::syscall::enter_usermode as usize);
}
stack_top = stack_top.sub(size_of::<usize>());
stack_top.cast::<usize>().write(func as usize);
}
self.set_stack(stack_top as usize);
}
}
impl super::Context {
pub fn get_fx_regs(&self) -> FloatRegisters {
let mut regs = unsafe { self.kfx.as_ptr().cast::<FloatRegisters>().read() };
regs._reserved = 0;
let mut new_st = regs.st_space;
for st in &mut new_st {
// Only allow access to the 80 lowest bits
*st &= !ST_RESERVED;
}
regs.st_space = new_st;
regs
}
pub fn set_fx_regs(&mut self, mut new: FloatRegisters) {
{
let old = unsafe { &*(self.kfx.as_ptr().cast::<FloatRegisters>()) };
new._reserved = old._reserved;
let old_st = new.st_space;
let mut new_st = new.st_space;
for (new_st, old_st) in new_st.iter_mut().zip(&old_st) {
*new_st &= !ST_RESERVED;
*new_st |= old_st & ST_RESERVED;
}
new.st_space = new_st;
// Make sure we don't use `old` from now on
}
unsafe {
self.kfx.as_mut_ptr().cast::<FloatRegisters>().write(new);
}
}
pub fn set_userspace_io_allowed(&mut self, allowed: bool) {
self.arch.userspace_io_allowed = allowed;
if self.is_current_context() {
unsafe {
crate::arch::gdt::set_userspace_io_allowed(crate::arch::gdt::pcr(), allowed);
}
}
}
pub(crate) fn current_syscall(&self) -> Option<[usize; 7]> {
if !self.inside_syscall {
return None;
}
let regs = self.regs()?;
let scratch = &regs.scratch;
Some([
scratch.rax,
scratch.rdi,
scratch.rsi,
scratch.rdx,
scratch.r10,
scratch.r8,
scratch.r9,
])
}
pub(crate) fn read_current_env_regs(&self) -> Result<EnvRegisters> {
// TODO: Avoid rdmsr if fsgsbase is not enabled, if this is worth optimizing for.
unsafe {
Ok(EnvRegisters {
fsbase: msr::rdmsr(msr::IA32_FS_BASE),
gsbase: msr::rdmsr(msr::IA32_KERNEL_GSBASE),
})
}
}
pub(crate) fn read_env_regs(&self) -> Result<EnvRegisters> {
Ok(EnvRegisters {
fsbase: self.arch.fsbase as u64,
gsbase: self.arch.gsbase as u64,
})
}
pub(crate) fn write_current_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
if RmmA::virt_is_valid(VirtualAddress::new(regs.fsbase as usize))
&& RmmA::virt_is_valid(VirtualAddress::new(regs.gsbase as usize))
{
unsafe {
x86::msr::wrmsr(x86::msr::IA32_FS_BASE, regs.fsbase);
// We have to write to KERNEL_GSBASE, because when the kernel returns to
// userspace, it will have executed SWAPGS first.
x86::msr::wrmsr(x86::msr::IA32_KERNEL_GSBASE, regs.gsbase);
}
self.arch.fsbase = regs.fsbase as usize;
self.arch.gsbase = regs.gsbase as usize;
Ok(())
} else {
Err(Error::new(EINVAL))
}
}
pub(crate) fn write_env_regs(&mut self, regs: EnvRegisters) -> Result<()> {
if RmmA::virt_is_valid(VirtualAddress::new(regs.fsbase as usize))
&& RmmA::virt_is_valid(VirtualAddress::new(regs.gsbase as usize))
{
self.arch.fsbase = regs.fsbase as usize;
self.arch.gsbase = regs.gsbase as usize;
Ok(())
} else {
Err(Error::new(EINVAL))
}
}
}
pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = Once::new();
// SAFETY: EMPTY_CR3 must be initialized.
pub unsafe fn empty_cr3() -> rmm::PhysicalAddress {
unsafe {
debug_assert!(EMPTY_CR3.poll().is_some());
*EMPTY_CR3.get_unchecked()
}
}
/// Switch to the next context by restoring its stack and registers
pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) {
unsafe {
let pcr = crate::arch::gdt::pcr();
if let Some(ref stack) = next.kstack {
crate::arch::gdt::set_tss_stack(pcr, stack.initial_top() as usize);
}
crate::arch::gdt::set_userspace_io_allowed(pcr, next.arch.userspace_io_allowed);
core::arch::asm!(
alternative2!(
feature1: "xsaveopt",
then1: ["
mov eax, 0xffffffff
mov edx, eax
xsaveopt64 [{prev_fx}]
xrstor64 [{next_fx}]
"],
feature2: "xsave",
then2: ["
mov eax, 0xffffffff
mov edx, eax
xsave64 [{prev_fx}]
xrstor64 [{next_fx}]
"],
default: ["
fxsave64 [{prev_fx}]
fxrstor64 [{next_fx}]
"]
),
prev_fx = in(reg) prev.kfx.as_mut_ptr(),
next_fx = in(reg) next.kfx.as_ptr(),
out("eax") _,
out("edx") _,
);
{
core::arch::asm!(
alternative!(
feature: "fsgsbase",
then: ["
mov rax, [{next}+{fsbase_off}]
mov rcx, [{next}+{gsbase_off}]
rdfsbase rdx
wrfsbase rax
swapgs
rdgsbase rax
wrgsbase rcx
swapgs
mov [{prev}+{fsbase_off}], rdx
mov [{prev}+{gsbase_off}], rax
"],
// TODO: Most applications will set FSBASE, but won't touch GSBASE. Maybe avoid
// wrmsr or even the swapgs+rdgsbase+wrgsbase+swapgs sequence if they are already
// equal?
default: ["
mov ecx, {MSR_FSBASE}
mov rdx, [{next}+{fsbase_off}]
mov eax, edx
shr rdx, 32
wrmsr
mov ecx, {MSR_KERNEL_GSBASE}
mov rdx, [{next}+{gsbase_off}]
mov eax, edx
shr rdx, 32
wrmsr
// {prev}
"]
),
out("rax") _,
out("rdx") _,
out("ecx") _, prev = in(reg) addr_of_mut!(prev.arch), next = in(reg) addr_of!(next.arch),
MSR_FSBASE = const msr::IA32_FS_BASE,
MSR_KERNEL_GSBASE = const msr::IA32_KERNEL_GSBASE,
gsbase_off = const offset_of!(Context, gsbase),
fsbase_off = const offset_of!(Context, fsbase),
);
}
(*pcr).percpu.new_addrsp_tmp.set(next.addr_space.clone());
switch_to_inner(&mut prev.arch, &mut next.arch)
}
}
// Check disassembly!
#[unsafe(naked)]
unsafe extern "sysv64" fn switch_to_inner(_prev: &mut Context, _next: &mut Context) {
use Context as Cx;
core::arch::naked_asm!(
// As a quick reminder for those who are unfamiliar with the System V ABI (extern "C"):
//
// - the current parameters are passed in the registers `rdi`, `rsi`,
// - we can modify scratch registers, e.g. rax
// - we cannot change callee-preserved registers arbitrarily, e.g. rbx, which is why we
// store them here in the first place.
concat!("
// Save old registers, and load new ones
mov [rdi + {off_rbx}], rbx
mov rbx, [rsi + {off_rbx}]
mov [rdi + {off_r12}], r12
mov r12, [rsi + {off_r12}]
mov [rdi + {off_r13}], r13
mov r13, [rsi + {off_r13}]
mov [rdi + {off_r14}], r14
mov r14, [rsi + {off_r14}]
mov [rdi + {off_r15}], r15
mov r15, [rsi + {off_r15}]
mov [rdi + {off_rbp}], rbp
mov rbp, [rsi + {off_rbp}]
mov [rdi + {off_rsp}], rsp
mov rsp, [rsi + {off_rsp}]
// push RFLAGS (can only be modified via stack)
pushfq
// pop RFLAGS into `self.rflags`
pop QWORD PTR [rdi + {off_rflags}]
// push `next.rflags`
push QWORD PTR [rsi + {off_rflags}]
// pop into RFLAGS
popfq
// When we return, we cannot even guarantee that the return address on the stack, points to
// the calling function, `context::switch`. Thus, we have to execute this Rust hook by
// ourselves, which will unlock the contexts before the later switch.
// Note that switch_finish_hook will be responsible for executing `ret`.
jmp {switch_hook}
"),
off_rflags = const(offset_of!(Cx, rflags)),
off_rbx = const(offset_of!(Cx, rbx)),
off_r12 = const(offset_of!(Cx, r12)),
off_r13 = const(offset_of!(Cx, r13)),
off_r14 = const(offset_of!(Cx, r14)),
off_r15 = const(offset_of!(Cx, r15)),
off_rbp = const(offset_of!(Cx, rbp)),
off_rsp = const(offset_of!(Cx, rsp)),
switch_hook = sym crate::context::switch_finish_hook,
);
}
File diff suppressed because it is too large Load Diff
+104
View File
@@ -0,0 +1,104 @@
//! File structs
use crate::{
event,
scheme::{self, SchemeId},
sync::{CleanLockToken, RwLock, L6},
syscall::error::Result,
};
use alloc::sync::Arc;
use syscall::{schemev2::NewFdFlags, RwFlags, O_APPEND, O_NONBLOCK};
pub type LockedFileDescription = RwLock<L6, FileDescription>;
/// A file description
#[derive(Clone, Copy, Debug)]
pub struct FileDescription {
/// The current file offset (seek)
pub offset: u64,
/// The scheme that this file refers to
pub scheme: SchemeId,
/// The number the scheme uses to refer to this file
pub number: usize,
/// The flags passed to open or fcntl(SETFL)
pub flags: u32,
pub internal_flags: InternalFlags,
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct InternalFlags: u32 {
const POSITIONED = 1 << 0;
const NOTIFY_ON_NEXT_DETACH = 1 << 1;
}
}
impl FileDescription {
pub fn rw_flags(&self, rw: RwFlags) -> u32 {
let mut ret = self.flags & !(O_NONBLOCK | O_APPEND) as u32;
if rw.contains(RwFlags::APPEND) {
ret |= O_APPEND as u32;
}
if rw.contains(RwFlags::NONBLOCK) {
ret |= O_NONBLOCK as u32;
}
ret
}
}
impl InternalFlags {
pub fn from_extra0(fl: u8) -> Option<Self> {
Some(
NewFdFlags::from_bits(fl)?
.iter()
.map(|fd| {
if fd == NewFdFlags::POSITIONED {
Self::POSITIONED
} else {
Self::empty()
}
})
.collect(),
)
}
}
/// A file descriptor
#[derive(Clone, Debug)]
#[must_use = "File descriptors must be closed"]
pub struct FileDescriptor {
/// Corresponding file description
pub description: Arc<LockedFileDescription>,
/// Cloexec flag
pub cloexec: bool,
}
impl FileDescription {
/// Try closing a file, although at this point the description will be destroyed anyway, if
/// doing so fails.
pub fn try_close(self, token: &mut CleanLockToken) -> Result<()> {
event::unregister_file(self.scheme, self.number, token);
let scheme = scheme::get_scheme(token.token(), self.scheme)?;
scheme.close(self.number, token)
}
}
impl FileDescriptor {
pub fn close(self, token: &mut CleanLockToken) -> Result<()> {
{
let (scheme_id, number, internal_flags) = {
let desc = self.description.read(token.token());
(desc.scheme, desc.number, desc.internal_flags)
};
if internal_flags.contains(InternalFlags::NOTIFY_ON_NEXT_DETACH) {
let scheme = scheme::get_scheme(token.token(), scheme_id)?;
scheme.detach(number, token)?;
}
}
if let Ok(file) = Arc::try_unwrap(self.description).map(RwLock::into_inner) {
file.try_close(token)?;
}
Ok(())
}
}
File diff suppressed because it is too large Load Diff
+324
View File
@@ -0,0 +1,324 @@
//! # Context management
//!
//! For resources on contexts, please consult [wikipedia](https://en.wikipedia.org/wiki/Context_switch) and [osdev](https://wiki.osdev.org/Context_Switching)
use alloc::{
collections::{BTreeSet, VecDeque},
sync::{Arc, Weak},
};
use core::{num::NonZeroUsize, ops::Deref};
use crate::{
context::memory::AddrSpaceWrapper,
cpu_set::LogicalCpuSet,
memory::{RmmA, RmmArch, TableKind},
percpu::PercpuBlock,
sync::{
ArcRwLockWriteGuard, CleanLockToken, LockToken, Mutex, MutexGuard, RwLock, RwLockReadGuard,
RwLockWriteGuard, L0, L1, L2, L4,
},
syscall::error::Result,
};
use self::context::Kstack;
pub use self::{
context::{BorrowedHtBuf, Context, Status},
switch::switch,
};
pub type ContextLock = RwLock<L4, Context>;
pub type ArcContextLockWriteGuard = ArcRwLockWriteGuard<L4, Context>;
#[cfg(target_arch = "aarch64")]
#[path = "arch/aarch64.rs"]
mod arch;
#[cfg(target_arch = "x86")]
#[path = "arch/x86.rs"]
mod arch;
#[cfg(target_arch = "x86_64")]
#[path = "arch/x86_64.rs"]
mod arch;
#[cfg(target_arch = "riscv64")]
#[path = "arch/riscv64.rs"]
mod arch;
/// Context struct
pub mod context;
/// Context switch function
pub mod switch;
/// File struct - defines a scheme and a file number
pub mod file;
/// Memory struct - contains a set of pages for a context
pub mod memory;
/// Signal handling
pub mod signal;
/// Timeout handling
pub mod timeout;
pub use self::switch::switch_finish_hook;
/// Maximum context files
pub const CONTEXT_MAX_FILES: usize = 65_536;
pub use self::arch::empty_cr3;
// Set of weak references to all contexts available for scheduling. The only strong references are
// the context file descriptors.
static CONTEXTS: RwLock<L2, BTreeSet<ContextRef>> = RwLock::new(BTreeSet::new());
// Actual context store for the scheduler
static RUN_CONTEXTS: Mutex<L1, RunContextData> = Mutex::new(RunContextData::new());
// Context that has been pushed out from RUN_CONTEXTS after being idle
static IDLE_CONTEXTS: Mutex<L2, VecDeque<WeakContextRef>> = Mutex::new(VecDeque::new());
pub struct RunContextData {
set: [VecDeque<WeakContextRef>; 40],
}
impl RunContextData {
pub const fn new() -> Self {
const EMPTY_VEC: VecDeque<WeakContextRef> = VecDeque::new();
Self {
set: [EMPTY_VEC; 40],
}
}
}
/// Get the global schemes list, const
pub fn contexts(token: LockToken<'_, L1>) -> RwLockReadGuard<'_, L2, BTreeSet<ContextRef>> {
CONTEXTS.read(token)
}
/// Get per cpu contexts, mutable
pub fn contexts_mut(token: LockToken<'_, L1>) -> RwLockWriteGuard<'_, L2, BTreeSet<ContextRef>> {
CONTEXTS.write(token)
}
pub fn idle_contexts(token: LockToken<'_, L1>) -> MutexGuard<'_, L2, VecDeque<WeakContextRef>> {
IDLE_CONTEXTS.lock(token)
}
pub fn idle_contexts_try(
token: LockToken<'_, L1>,
) -> Option<MutexGuard<'_, L2, VecDeque<WeakContextRef>>> {
IDLE_CONTEXTS.try_lock(token)
}
pub fn run_contexts(token: LockToken<'_, L0>) -> MutexGuard<'_, L1, RunContextData> {
RUN_CONTEXTS.lock(token)
}
pub fn init(token: &mut CleanLockToken) {
let owner = None; // kmain not owned by any fd
let mut context = Context::new(owner).expect("failed to create kmain context");
context.sched_affinity = LogicalCpuSet::empty();
context.sched_affinity.atomic_set(crate::cpu_id());
context.name.clear();
context.name.push_str("[kmain]");
self::arch::EMPTY_CR3.call_once(|| RmmA::table(TableKind::User));
context.status = Status::Runnable;
context.running = true;
context.cpu_id = Some(crate::cpu_id());
let context_lock = Arc::new(ContextLock::new(context));
let context_ref = ContextRef(Arc::clone(&context_lock));
contexts_mut(token.token().downgrade()).insert(context_ref.clone());
// Set this as current context and idle context, but don't treat it as regular context queue
unsafe {
let percpu = PercpuBlock::current();
percpu
.switch_internals
.set_current_context(Arc::clone(&context_lock));
percpu.switch_internals.set_idle_context(context_lock);
}
}
pub fn current() -> Arc<ContextLock> {
PercpuBlock::current()
.switch_internals
.with_context(Arc::clone)
}
pub fn try_current() -> Option<Arc<ContextLock>> {
PercpuBlock::current()
.switch_internals
.try_with_context(|context| context.map(Arc::clone))
}
pub fn is_current(context: &Arc<ContextLock>) -> bool {
PercpuBlock::current()
.switch_internals
.with_context(|current| Arc::ptr_eq(context, current))
}
#[derive(Clone)]
pub struct ContextRef(pub Arc<ContextLock>);
impl Deref for ContextRef {
type Target = Arc<ContextLock>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Ord for ContextRef {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
Ord::cmp(&Arc::as_ptr(&self.0), &Arc::as_ptr(&other.0))
}
}
impl PartialOrd for ContextRef {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(Ord::cmp(self, other))
}
}
impl PartialEq for ContextRef {
fn eq(&self, other: &Self) -> bool {
Ord::cmp(self, other) == core::cmp::Ordering::Equal
}
}
impl Eq for ContextRef {}
#[derive(Clone)]
pub struct WeakContextRef(pub Weak<ContextLock>);
impl WeakContextRef {
pub fn upgrade(&self) -> Option<Arc<ContextLock>> {
self.0.upgrade()
}
}
impl Ord for WeakContextRef {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
Ord::cmp(&Weak::as_ptr(&self.0), &Weak::as_ptr(&other.0))
}
}
impl PartialOrd for WeakContextRef {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(Ord::cmp(self, other))
}
}
impl PartialEq for WeakContextRef {
fn eq(&self, other: &Self) -> bool {
Ord::cmp(self, other) == core::cmp::Ordering::Equal
}
}
impl Eq for WeakContextRef {}
/// Spawn a context from a function.
pub fn spawn(
userspace_allowed: bool,
owner_proc_id: Option<NonZeroUsize>,
func: extern "C" fn(),
token: &mut CleanLockToken,
) -> Result<Arc<ContextLock>> {
let stack = Kstack::new()?;
let mut context = Context::new(owner_proc_id)?;
let _ = context.set_addr_space(Some(AddrSpaceWrapper::new()?), token.downgrade());
context
.arch
.setup_initial_call(&stack, func, userspace_allowed);
context.kstack = Some(stack);
context.userspace = userspace_allowed;
let context_lock = Arc::new(ContextLock::new(context));
let context_ref = ContextRef(Arc::clone(&context_lock));
let run_ref = WeakContextRef(Arc::downgrade(&context_ref.0));
idle_contexts(token.downgrade()).push_back(run_ref);
contexts_mut(token.downgrade()).insert(context_ref);
Ok(context_lock)
}
/// A guard that disables preemption for a context while it is alive.
///
/// This guard is used to ensure that a sequence of operations is atomic with respect to preemption.
/// It automatically re-enables preemption when dropped.
///
/// Because the guard must hold a mutable reference to the `CleanLockToken` to re-enable preemption
/// in `Drop`, it consumes the token. The `token()` method allows re-borrowing the token for use
/// within the guard's scope.
pub struct PreemptGuard<'a> {
context: &'a ContextLock,
token: &'a mut CleanLockToken,
}
impl<'a> PreemptGuard<'a> {
pub fn new(context: &'a ContextLock, token: &'a mut CleanLockToken) -> PreemptGuard<'a> {
context.write(token.token()).preempt_locks += 1;
PreemptGuard { context, token }
}
/// Get a mutable reference to the underlying `CleanLockToken`.
///
/// This is necessary because the `PreemptGuard` owns the mutable reference to the token
/// (to use it in `Drop`), so we cannot use the original `token` variable while the guard exists.
pub fn token(&mut self) -> &mut CleanLockToken {
self.token
}
}
impl Drop for PreemptGuard<'_> {
fn drop(&mut self) {
self.context.write(self.token.token()).preempt_locks -= 1;
}
}
/// Variant of PreemptGuard behind a one-level token
pub struct PreemptGuardL1<'a> {
context: &'a ContextLock,
token: &'a mut LockToken<'a, L1>,
}
impl<'a> PreemptGuardL1<'a> {
pub fn new(context: &'a ContextLock, token: &'a mut LockToken<'a, L1>) -> PreemptGuardL1<'a> {
context.write(token.token()).preempt_locks += 1;
PreemptGuardL1 { context, token }
}
/// Get a mutable reference to the underlying `LockToken<L1>`.
pub fn token(&mut self) -> &mut LockToken<'a, L1> {
self.token
}
}
impl Drop for PreemptGuardL1<'_> {
fn drop(&mut self) {
self.context.write(self.token.token()).preempt_locks -= 1;
}
}
/// Variant of PreemptGuard behind a one-level token
pub struct PreemptGuardL2<'a> {
context: &'a ContextLock,
token: &'a mut LockToken<'a, L2>,
}
impl<'a> PreemptGuardL2<'a> {
pub fn new(context: &'a ContextLock, token: &'a mut LockToken<'a, L2>) -> PreemptGuardL2<'a> {
context.write(token.token()).preempt_locks += 1;
PreemptGuardL2 { context, token }
}
/// Get a mutable reference to the underlying `LockToken<L2>`.
pub fn token(&mut self) -> &mut LockToken<'a, L2> {
self.token
}
}
impl Drop for PreemptGuardL2<'_> {
fn drop(&mut self) {
self.context.write(self.token.token()).preempt_locks -= 1;
}
}
+105
View File
@@ -0,0 +1,105 @@
use core::sync::atomic::Ordering;
use crate::{context, sync::CleanLockToken, syscall::flag::SigcontrolFlags};
pub fn signal_handler(token: &mut CleanLockToken) {
let context_lock = context::current();
let mut context_guard = context_lock.write(token.token());
let context = &mut *context_guard;
let being_sigkilled = context.being_sigkilled;
if being_sigkilled {
drop(context_guard);
drop(context_lock);
crate::syscall::process::exit_this_context(None, token);
}
/*let thumbs_down = ptrace::breakpoint_callback(
PTRACE_STOP_SIGNAL,
Some(ptrace_event!(PTRACE_STOP_SIGNAL)),
)
.and_then(|_| ptrace::next_breakpoint().map(|f| f.contains(PTRACE_FLAG_IGNORE)));*/
// TODO: thumbs_down
let Some((thread_ctl, proc_ctl, st)) = context.sigcontrol() else {
// Discard signal if sigcontrol is unset.
trace!("no sigcontrol, returning");
return;
};
if thread_ctl.currently_pending_unblocked(proc_ctl) == 0 {
// The context is currently Runnable. When transitioning into Blocked, it will check for
// signals (with the context lock held, which is required when sending signals). After
// that, any detection of pending unblocked signals by the sender, will result in the
// context being unblocked, and signals sent.
// TODO: prioritize signals over regular program execution
return;
}
let control_flags =
SigcontrolFlags::from_bits_retain(thread_ctl.control_flags.load(Ordering::Acquire));
if control_flags.contains(SigcontrolFlags::INHIBIT_DELIVERY) {
// Signals are inhibited to protect critical sections inside libc, but this code will run
// every time the context is switched to.
trace!("Inhibiting delivery, returning");
return;
}
let sigh_instr_ptr = st.user_handler.get();
let Some(regs) = context.regs_mut() else {
// TODO: is this even reachable?
trace!("No registers, returning");
return;
};
let ip = regs.instr_pointer();
let archdep_reg = regs.sig_archdep_reg();
regs.set_instr_pointer(sigh_instr_ptr);
let (thread_ctl, _, _) = context
.sigcontrol()
.expect("cannot have been unset while holding the lock");
thread_ctl.saved_ip.set(ip);
thread_ctl.saved_archdep_reg.set(archdep_reg);
thread_ctl.control_flags.store(
(control_flags | SigcontrolFlags::INHIBIT_DELIVERY).bits(),
Ordering::Release,
);
}
pub fn excp_handler(excp: syscall::Exception) {
let mut token = unsafe { CleanLockToken::new() };
let current = context::current();
let context = current.write(token.token());
let Some(eh) = context.sig.as_ref().and_then(|s| s.excp_handler) else {
// TODO: Let procmgr print this?
info!(
"UNHANDLED EXCEPTION, CPU {}, PID {}, NAME {}, CONTEXT {current:p}",
crate::cpu_id(),
context.pid,
context.name
);
drop(context);
// TODO: Allow exceptions to be caught by tracer etc, without necessarily exiting the
// context (closing files, dropping AddrSpace, etc)
crate::syscall::process::exit_this_context(Some(excp), &mut token);
};
// TODO
/*
let Some(regs) = context.regs_mut() else {
// TODO: unhandled exception in this case too?
return;
};
let old_ip = regs.instr_pointer();
let old_archdep_reg = regs.ar
let (tctl, pctl, sigst) = context.sigcontrol().expect("already checked");
tctl.saved_ip.set(excp.rsp);
tctl.saved_archdep_reg*/
}
+577
View File
@@ -0,0 +1,577 @@
//! This module provides a context-switching mechanism that utilizes a simple round-robin scheduler.
//! The scheduler iterates over available contexts, selecting the next context to run, while
//! handling process states and synchronization.
use crate::{
context::{
self, arch, idle_contexts, idle_contexts_try, run_contexts, ArcContextLockWriteGuard,
Context, ContextLock, WeakContextRef,
},
cpu_set::LogicalCpuId,
cpu_stats::{self, CpuState},
percpu::PercpuBlock,
sync::{ArcRwLockWriteGuard, CleanLockToken, L4},
};
use alloc::{sync::Arc, vec::Vec};
use core::{
cell::{Cell, RefCell},
hint, mem,
sync::atomic::Ordering,
};
use syscall::PtraceFlags;
enum UpdateResult {
CanSwitch,
Skip,
Blocked,
}
// A simple geometric series where value[i] ~= value[i - 1] * 1.25
const SCHED_PRIO_TO_WEIGHT: [usize; 40] = [
88761, 71755, 56483, 46273, 36291, 29154, 23254, 18705, 14949, 11916, 9548, 7620, 6100, 4904,
3906, 3121, 2501, 1991, 1586, 1277, 1024, 820, 655, 526, 423, 335, 272, 215, 172, 137, 110, 87,
70, 56, 45, 36, 29, 23, 18, 15,
];
/// Determines if a given context is eligible to be scheduled on a given CPU (in
/// principle, the current CPU).
///
/// # Safety
/// This function is unsafe because it modifies the `context`'s state directly without synchronization.
///
/// # Parameters
/// - `context`: The context (process/thread) to be checked.
/// - `cpu_id`: The logical ID of the CPU on which the context is being scheduled.
///
/// # Returns
/// - `UpdateResult::CanSwitch`: If the context can be switched to.
/// - `UpdateResult::Skip`: If the context should be skipped (e.g., it's running on another CPU).
unsafe fn update_runnable(
context: &mut Context,
cpu_id: LogicalCpuId,
switch_time: u128,
) -> UpdateResult {
// Ignore contexts that are already running.
if context.running {
return UpdateResult::Skip;
}
// Ignore contexts assigned to other CPUs.
if !context.sched_affinity.contains(cpu_id) {
return UpdateResult::Skip;
}
// If context is soft-blocked and has a wake-up time, check if it should wake up.
if context.status.is_soft_blocked()
&& let Some(wake) = context.wake
&& switch_time >= wake
{
context.wake = None;
context.unblock_no_ipi();
}
// If the context is runnable, indicate it can be switched to.
if context.status.is_runnable() {
UpdateResult::CanSwitch
} else {
UpdateResult::Blocked
}
}
struct SwitchResultInner {
_prev_guard: ArcContextLockWriteGuard,
_next_guard: ArcContextLockWriteGuard,
}
/// Tick function to update PIT ticks and trigger a context switch if necessary.
///
/// Called periodically, this function increments a per-CPU tick counter and performs a context
/// switch if the counter reaches a set threshold (e.g., every 3 ticks).
///
/// The function also calls the signal handler after switching contexts.
pub fn tick(token: &mut CleanLockToken) {
let ticks_cell = &PercpuBlock::current().switch_internals.pit_ticks;
let new_ticks = ticks_cell.get() + 1;
ticks_cell.set(new_ticks);
// Trigger a context switch after every 3 ticks (approx. 6.75 ms).
if new_ticks >= 3 {
switch(token);
crate::context::signal::signal_handler(token);
}
}
/// Finishes the context switch by clearing any temporary data and resetting the lock.
///
/// This function is called after a context switch is completed to perform cleanup, including
/// clearing the switch result data and releasing the context switch lock.
///
/// # Safety
/// This function involves unsafe operations such as resetting state and releasing locks.
pub unsafe extern "C" fn switch_finish_hook() {
unsafe {
match PercpuBlock::current().switch_internals.switch_result.take() {
Some(switch_result) => {
drop(switch_result);
}
_ => {
// TODO: unreachable_unchecked()?
crate::arch::stop::emergency_reset();
}
}
arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
crate::percpu::switch_arch_hook();
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SwitchResult {
Switched,
AllContextsIdle,
}
/// This function performs the context switch, using select_next_context to
/// actually select the next context to switch to.
///
/// # Warning
/// This is not memory-unsafe to call. But do NOT call this while holding locks!
///
/// # Returns
/// - `SwitchResult::Switched`: Indicates a successful switch to a new context.
/// - `SwitchResult::AllContextsIdle`: Indicates all contexts are idle, and the CPU will switch
/// to an idle context.
pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
let switch_time = crate::time::monotonic(token);
let percpu = PercpuBlock::current();
cpu_stats::add_context_switch();
//set PIT Interrupt counter to 0, giving each process same amount of PIT ticks
percpu.switch_internals.pit_ticks.set(0);
// Acquire the global lock to ensure exclusive access during context switch and avoid
// issues that would be caused by the unsafe operations below
// TODO: Better memory orderings?
while arch::CONTEXT_SWITCH_LOCK
.compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed)
.is_err()
{
hint::spin_loop();
percpu.maybe_handle_tlb_shootdown();
}
// Lock the previous context.
let prev_context_lock = crate::context::current();
// We are careful not to lock this context twice
let mut prev_context_guard = unsafe { prev_context_lock.write_arc() };
if !prev_context_guard.is_preemptable() {
// Unset global lock
arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
// Pretend to have finished switching, so CPU is not idled
return SwitchResult::Switched;
}
// Alarm (previously in update_runnable)
let wakeups = wakeup_contexts(token, switch_time);
if wakeups.len() > 0 {
let mut run_contexts = run_contexts(token.token());
for (prio, context_lock) in wakeups {
run_contexts.set[prio].push_back(context_lock);
}
}
let cpu_id = crate::cpu_id();
// Update per-cpu times
let percpu_nanos = switch_time.saturating_sub(percpu.switch_internals.switch_time.get()) as u64;
let percpu_ms = percpu_nanos / 1_000_000;
let was_idle = percpu.stats.add_time(percpu_ms) == CpuState::Idle as u8;
percpu.switch_internals.switch_time.set(switch_time);
let switch_context_opt = match select_next_context(
token,
percpu,
cpu_id,
switch_time,
was_idle,
&mut prev_context_guard,
) {
Ok(opt) => opt,
Err(early_ret) => return early_ret,
};
// Switch process states, TSS stack pointer, and store new context ID
match switch_context_opt {
Some(mut next_context_guard) => {
// Update context states and prepare for the switch.
let prev_context = &mut *prev_context_guard;
let next_context = &mut *next_context_guard;
// Set the previous context as "not running"
prev_context.running = false;
// Set the next context as "running"
next_context.running = true;
// Set the CPU ID for the next context
next_context.cpu_id = Some(cpu_id);
// Update times
if !was_idle {
prev_context.cpu_time += switch_time.saturating_sub(prev_context.switch_time);
}
next_context.switch_time = switch_time;
if next_context.userspace {
percpu.stats.set_state(cpu_stats::CpuState::User);
} else {
percpu.stats.set_state(cpu_stats::CpuState::Kernel);
}
unsafe {
percpu.switch_internals.set_current_context(Arc::clone(
ArcContextLockWriteGuard::rwlock(&next_context_guard),
));
}
// FIXME set the switch result in arch::switch_to instead
let prev_context = unsafe {
mem::transmute::<&'_ mut Context, &'_ mut Context>(&mut *prev_context_guard)
};
let next_context = unsafe {
mem::transmute::<&'_ mut Context, &'_ mut Context>(&mut *next_context_guard)
};
percpu
.switch_internals
.switch_result
.set(Some(SwitchResultInner {
_prev_guard: prev_context_guard,
_next_guard: next_context_guard,
}));
/*let (ptrace_session, ptrace_flags) = if let Some((session, bp)) = ptrace::sessions()
.get(&next_context.pid)
.map(|s| (Arc::downgrade(s), s.data.lock().breakpoint))
{
(Some(session), bp.map_or(PtraceFlags::empty(), |f| f.flags))
} else {
(None, PtraceFlags::empty())
};*/
let ptrace_flags = PtraceFlags::empty();
//*percpu.ptrace_session.borrow_mut() = ptrace_session;
percpu.ptrace_flags.set(ptrace_flags);
prev_context.inside_syscall =
percpu.inside_syscall.replace(next_context.inside_syscall);
#[cfg(feature = "syscall_debug")]
{
prev_context.syscall_debug_info = percpu
.syscall_debug_info
.replace(next_context.syscall_debug_info);
prev_context.syscall_debug_info.on_switch_from(token);
next_context.syscall_debug_info.on_switch_to(token);
}
percpu
.switch_internals
.being_sigkilled
.set(next_context.being_sigkilled);
unsafe {
arch::switch_to(prev_context, next_context);
}
// NOTE: After switch_to is called, the return address can even be different from the
// current return address, meaning that we cannot use local variables here, and that we
// need to use the `switch_finish_hook` to be able to release the locks. Newly created
// contexts will return directly to the function pointer passed to context::spawn, and not
// reach this code until the next context switch back.
SwitchResult::Switched
}
_ => {
// No target was found, unset global lock and return
arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
percpu.stats.set_state(cpu_stats::CpuState::Idle);
SwitchResult::AllContextsIdle
}
}
}
fn wakeup_contexts(token: &mut CleanLockToken, switch_time: u128) -> Vec<(usize, WeakContextRef)> {
// TODO: Optimise this somehow. Perhaps using a separate timer queue?
let mut wakeups = Vec::new();
let current_context = context::current();
let Some(idle_contexts) = idle_contexts_try(token.downgrade()) else {
// other cpus may spawning or killing contexts so let's skip wakeups to avoid contention
return wakeups;
};
let (mut idle_contexts, mut token) = idle_contexts.into_split();
let len = idle_contexts.len();
for _ in 0..len {
let Some(context_ref) = idle_contexts.pop_front() else {
break;
};
let Some(context) = context_ref.upgrade() else {
continue;
};
if Arc::ptr_eq(&context, &current_context) {
idle_contexts.push_back(context_ref);
continue;
}
let Some(guard) = context.try_read(token.token()) else {
idle_contexts.push_back(context_ref);
continue;
};
if guard.status.is_soft_blocked() {
if let Some(wake) = guard.wake {
if switch_time >= wake {
let prio = guard.prio;
drop(guard);
wakeups.push((prio, context_ref));
continue;
}
}
}
if guard.status.is_runnable() && !guard.running {
let prio = guard.prio;
drop(guard);
wakeups.push((prio, context_ref));
continue;
}
drop(guard);
idle_contexts.push_back(context_ref);
}
wakeups
}
/// This is the scheduler function which currently utilises Deficit Weighted Round Robin Scheduler
fn select_next_context(
token: &mut CleanLockToken,
percpu: &PercpuBlock,
cpu_id: LogicalCpuId,
switch_time: u128,
was_idle: bool,
prev_context_guard: &mut ArcRwLockWriteGuard<L4, Context>,
) -> Result<Option<ArcContextLockWriteGuard>, SwitchResult> {
let contexts_data = run_contexts(token.token());
let (mut contexts_data, mut token) = contexts_data.into_split();
let contexts_list = &mut contexts_data.set;
let idle_context = percpu.switch_internals.idle_context();
let mut balance = percpu.balance.get();
let mut i = percpu.last_queue.get() % 40;
// Lock the previous context.
let prev_context_lock = crate::context::current();
let mut empty_queues = 0;
let mut total_iters = 0;
let mut next_context_guard_opt = None;
let total_contexts: usize = contexts_list.iter().map(|q| q.len()).sum();
let mut skipped_contexts = 0;
'priority: loop {
i = (i + 1) % 40;
total_iters += 1;
// The least prioritised queue takes <5000 iters to build up
// balance = sched_prio_to_weight[20], if we have already spent
// that many iters and not found any context, it is better to just
// skip for now
if total_iters >= 5000 {
break 'priority;
}
if skipped_contexts > total_contexts && total_contexts > 0 {
break 'priority;
}
let contexts = contexts_list
.get_mut(i)
.expect("i should be between [0, 39]!");
if contexts.is_empty() {
empty_queues += 1;
if empty_queues >= 40 {
// If all queues are empty, just break out
break 'priority;
}
continue;
} else {
empty_queues = 0;
}
if balance[i] < SCHED_PRIO_TO_WEIGHT[20] {
// This queue does not have enough balance to run,
// increment the balance!
balance[i] += SCHED_PRIO_TO_WEIGHT[i];
continue;
}
let len = contexts.len();
for _ in 0..len {
let (next_context_ref, next_context_lock) = match contexts.pop_front() {
Some(lock) => match lock.upgrade() {
Some(new_lock) => (lock, new_lock),
None => {
skipped_contexts += 1;
continue; // Ghost Process, just continue
}
},
None => break, // Empty Queue
};
if Arc::ptr_eq(&next_context_lock, &prev_context_lock) {
contexts.push_back(next_context_ref);
continue;
}
if Arc::ptr_eq(&next_context_lock, &idle_context) {
contexts.push_back(next_context_ref);
continue;
}
let mut next_context_guard = unsafe { next_context_lock.write_arc() };
// Is this context runnable on this CPU?
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
if let UpdateResult::CanSwitch = sw {
next_context_guard_opt = Some(next_context_guard);
balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
break 'priority;
} else {
if matches!(sw, UpdateResult::Blocked) {
idle_contexts(token.token()).push_back(next_context_ref);
} else {
contexts.push_back(next_context_ref);
};
skipped_contexts += 1;
if skipped_contexts >= total_contexts {
break 'priority;
}
}
}
}
percpu.balance.set(balance);
percpu.last_queue.set(i);
if !Arc::ptr_eq(&prev_context_lock, &idle_context) {
// Send the old process to the back of the line (if it is still runnable)
let prev_ctx = WeakContextRef(Arc::downgrade(&prev_context_lock));
if prev_context_guard.status.is_runnable() {
let prio = prev_context_guard.prio;
contexts_list[prio].push_back(prev_ctx);
} else {
idle_contexts(token.token()).push_back(prev_ctx);
}
}
if let Some(next_context_guard) = next_context_guard_opt {
// We found a new process!
return Ok(Some(next_context_guard));
} else {
if !was_idle && !Arc::ptr_eq(&prev_context_lock, &idle_context) {
// We switch into the idle context
Ok(Some(unsafe { idle_context.write_arc() }))
} else {
// We found no other process to run.
Ok(None)
}
}
}
/// Holds per-CPU state necessary for context switching.
///
/// This struct contains information such as the idle context, current context, and PIT tick counts,
/// as well as fields required for managing ptrace sessions and signals.
pub struct ContextSwitchPercpu {
switch_result: Cell<Option<SwitchResultInner>>,
switch_time: Cell<u128>,
pit_ticks: Cell<usize>,
current_ctxt: RefCell<Option<Arc<ContextLock>>>,
/// The idle process.
idle_ctxt: RefCell<Option<Arc<ContextLock>>>,
pub(crate) being_sigkilled: Cell<bool>,
}
impl ContextSwitchPercpu {
pub const fn default() -> Self {
Self {
switch_result: Cell::new(None),
switch_time: Cell::new(0),
pit_ticks: Cell::new(0),
current_ctxt: RefCell::new(None),
idle_ctxt: RefCell::new(None),
being_sigkilled: Cell::new(false),
}
}
/// Applies a function to the current context, allowing controlled access.
///
/// # Parameters
/// - `f`: A closure that receives a reference to the current context and returns a value.
///
/// # Returns
/// The result of applying `f` to the current context.
pub fn with_context<T>(&self, f: impl FnOnce(&Arc<ContextLock>) -> T) -> T {
f(self
.current_ctxt
.borrow()
.as_ref()
.expect("not inside of context"))
}
/// Applies a function to the current context, allowing controlled access.
///
/// # Parameters
/// - `f`: A closure that receives a reference to the current context and returns a value.
///
/// # Returns
/// The result of applying `f` to the current context if any.
pub fn try_with_context<T>(&self, f: impl FnOnce(Option<&Arc<ContextLock>>) -> T) -> T {
f(self.current_ctxt.borrow().as_ref())
}
/// Sets the current context to a new value.
///
/// # Safety
/// This function is unsafe as it modifies the context state directly.
///
/// # Parameters
/// - `new`: The new context to be set as the current context.
pub unsafe fn set_current_context(&self, new: Arc<ContextLock>) {
*self.current_ctxt.borrow_mut() = Some(new);
}
/// Sets the idle context to a new value.
///
/// # Safety
/// This function is unsafe as it modifies the idle context state directly.
///
/// # Parameters
/// - `new`: The new context to be set as the idle context.
pub unsafe fn set_idle_context(&self, new: Arc<ContextLock>) {
*self.idle_ctxt.borrow_mut() = Some(new);
}
/// Retrieves the current idle context.
///
/// # Returns
/// A reference to the idle context.
pub fn idle_context(&self) -> Arc<ContextLock> {
Arc::clone(
self.idle_ctxt
.borrow()
.as_ref()
.expect("no idle context present"),
)
}
}
+82
View File
@@ -0,0 +1,82 @@
use alloc::collections::VecDeque;
use crate::{
event,
scheme::SchemeId,
sync::{CleanLockToken, LockToken, Mutex, MutexGuard, L0, L1},
syscall::{
data::TimeSpec,
flag::{CLOCK_MONOTONIC, CLOCK_REALTIME, EVENT_READ},
},
time,
};
#[derive(Debug)]
struct Timeout {
pub scheme_id: SchemeId,
pub event_id: usize,
pub clock: usize,
pub time: u128,
}
type Registry = VecDeque<Timeout>;
static REGISTRY: Mutex<L1, Registry> = Mutex::new(Registry::new());
/// Get the global timeouts list
fn registry(token: LockToken<'_, L0>) -> MutexGuard<'_, L1, Registry> {
REGISTRY.lock(token)
}
pub fn register(
scheme_id: SchemeId,
event_id: usize,
clock: usize,
time: TimeSpec,
token: &mut CleanLockToken,
) {
let mut registry = registry(token.token());
registry.push_back(Timeout {
scheme_id,
event_id,
clock,
time: (time.tv_sec as u128 * time::NANOS_PER_SEC) + (time.tv_nsec as u128),
});
}
pub fn trigger(token: &mut CleanLockToken) {
let mono = time::monotonic(token);
let real = time::realtime(token);
let mut i = 0;
loop {
let mut registry = registry(token.token());
let timeout = if i < registry.len() {
let trigger = match registry[i].clock {
CLOCK_MONOTONIC => {
let time = registry[i].time;
mono >= time
}
CLOCK_REALTIME => {
let time = registry[i].time;
real >= time
}
clock => {
println!("timeout::trigger: unknown clock {}", clock);
true
}
};
if trigger {
registry.remove(i).unwrap()
} else {
i += 1;
continue;
}
} else {
break;
};
drop(registry);
event::trigger(timeout.scheme_id, timeout.event_id, EVENT_READ, token);
}
}