Compare commits

...

7 Commits

46 changed files with 3910 additions and 359 deletions
Generated
+25
View File
@@ -2,6 +2,13 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "client"
version = "0.1.0"
dependencies = [
"dusk-sys",
]
[[package]] [[package]]
name = "dusk" name = "dusk"
version = "0.1.0" version = "0.1.0"
@@ -9,8 +16,26 @@ dependencies = [
"limine", "limine",
] ]
[[package]]
name = "dusk-sys"
version = "0.1.0"
[[package]]
name = "echo"
version = "0.1.0"
dependencies = [
"dusk-sys",
]
[[package]] [[package]]
name = "limine" name = "limine"
version = "0.6.5" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9" checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9"
[[package]]
name = "omega3"
version = "0.1.0"
dependencies = [
"dusk-sys",
]
+3
View File
@@ -3,6 +3,9 @@ name = "dusk"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[workspace]
members = [".", "userspace/*"]
[dependencies] [dependencies]
limine = "0.6.5" limine = "0.6.5"
+20 -12
View File
@@ -6,17 +6,17 @@ MEMORY ?= 512M
# In MB # In MB
ISO_SIZE ?= 512 ISO_SIZE ?= 512
QEMU_OPTS ?= QEMU_OPTS ?=
#MKSQUASHFS_OPTS ?=
GDB ?= GDB ?=
CPUS ?= 1 CPUS ?= 1
# FAT type # FAT type
ESP_BITS ?= 32 ESP_BITS ?= 32
EXPORT_SYMBOLS = true #EXPORT_SYMBOLS = true
ISO_PATH = ${ARTIFACTS_PATH}/iso_root ISO_PATH = ${ARTIFACTS_PATH}/iso_root
#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME} IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME}
ESP_IMAGE = ${ARTIFACTS_PATH}/esp.img ESP_IMAGE = ${ARTIFACTS_PATH}/esp.img
USERSPACE_CARGO_OPTS = --target ${ARCH}-unknown-none
CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json
QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH} QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH}
LIMINE_BOOT_VARIATION = X64 LIMINE_BOOT_VARIATION = X64
@@ -27,6 +27,7 @@ KERNEL_FILE = target/${ARCH}-unknown-none/${MODE}/dusk.elf
ifeq (${MODE},release) ifeq (${MODE},release)
CARGO_OPTS += --release CARGO_OPTS += --release
USERSPACE_CARGO_OPTS += --release
endif endif
ifneq (${CPUS},1) ifneq (${CPUS},1)
@@ -50,7 +51,7 @@ endif
all: build all: build
build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso build: prepare-bin-files compile-bootloader compile-binaries compile-initramfs build-iso
check: check:
cargo check -Zjson-target-spec cargo check -Zjson-target-spec
@@ -63,13 +64,20 @@ prepare-bin-files:
# Make bin/ and bin/iso_root # Make bin/ and bin/iso_root
mkdir -p ${ARTIFACTS_PATH} mkdir -p ${ARTIFACTS_PATH}
mkdir -p ${ISO_PATH} mkdir -p ${ISO_PATH}
# mkdir -p ${INITRAMFS_PATH} mkdir -p ${INITRAMFS_PATH}
run-scripts:
# Place the build ID into the binary so it can be read at runtime
@HASH=$$(md5sum ${KERNEL_FILE} | cut -c1-12) && \
sed -i "s/__BUILD_ID__/$${HASH}/" ${KERNEL_FILE}
#python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/ compile-user:
RUSTFLAGS="-C relocation-model=static" cargo build --package omega3 ${USERSPACE_CARGO_OPTS}
RUSTFLAGS="-C relocation-model=static" cargo build --package client ${USERSPACE_CARGO_OPTS}
RUSTFLAGS="-C relocation-model=static" cargo build --package echo ${USERSPACE_CARGO_OPTS}
copy-initramfs-files: compile-user
cp -v target/${ARCH}-unknown-none/${MODE}/omega3 ${INITRAMFS_PATH}/omega3.elf
cp -v target/${ARCH}-unknown-none/${MODE}/client ${INITRAMFS_PATH}/client.elf
cp -v target/${ARCH}-unknown-none/${MODE}/echo ${INITRAMFS_PATH}/echo.elf
compile-initramfs: copy-initramfs-files
(cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img
copy-iso-files: copy-iso-files:
# Limine files # Limine files
@@ -81,7 +89,7 @@ copy-iso-files:
# OS files # OS files
cp -v ${KERNEL_FILE} ${ISO_PATH}/boot cp -v ${KERNEL_FILE} ${ISO_PATH}/boot
#cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot
build-esp: copy-iso-files build-esp: copy-iso-files
# Create and populate formatted FAT image for ESP partition (130048 1K-blocks = ~127MiB) # Create and populate formatted FAT image for ESP partition (130048 1K-blocks = ~127MiB)
@@ -129,7 +137,7 @@ compile-binaries:
ovmf-x86_64: ovmf-x86_64:
mkdir -p ovmf/ovmf-x86_64 mkdir -p ovmf/ovmf-x86_64
@if [ ! -d "ovmf/ovmf-x86_64/OVMF.fd" ]; then \ @if [ ! -f "ovmf/ovmf-x86_64/OVMF.fd" ]; then \
cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \ cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \
fi fi
+1
View File
@@ -4,4 +4,5 @@ timeout: 0
protocol: limine protocol: limine
path: boot():/boot/dusk.elf path: boot():/boot/dusk.elf
module_path: boot():/boot/initramfs.img
+4 -1
View File
@@ -5,4 +5,7 @@ mod x86_64;
pub use x86_64::*; pub use x86_64::*;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
pub(crate) use x86_64::{PageTableCreateError, PageTableMapError, PageTableUnmapError}; pub(crate) use x86_64::{
PageTableCreateError, PageTableMapError, PageTableUnmapError, ThreadContext, set_kernel_stack,
switch_context,
};
-25
View File
@@ -13,7 +13,6 @@ use crate::{
memory::{ memory::{
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr, AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
}, },
println,
}; };
const APIC_ID: u32 = 0x20; const APIC_ID: u32 = 0x20;
@@ -37,7 +36,6 @@ const APIC_TIMER_INITIAL_COUNT: u32 = 0x380;
const APIC_TIMER_CURRENT_COUNT: u32 = 0x390; const APIC_TIMER_CURRENT_COUNT: u32 = 0x390;
const APIC_TIMER_DIVIDE_CONFIG: u32 = 0x3E0; const APIC_TIMER_DIVIDE_CONFIG: u32 = 0x3E0;
#[derive(Debug)]
enum LocalApicAccess { enum LocalApicAccess {
X2Apic, X2Apic,
XApic, XApic,
@@ -89,7 +87,6 @@ pub enum LocalApicError {
NotBootSystemProcessor, NotBootSystemProcessor,
} }
#[derive(Debug)]
pub struct LocalApic { pub struct LocalApic {
id: u32, id: u32,
access: LocalApicAccess, access: LocalApicAccess,
@@ -184,28 +181,6 @@ impl LocalApic {
self.access.write(APIC_TIMER_INITIAL_COUNT, 0); self.access.write(APIC_TIMER_INITIAL_COUNT, 0);
} }
// pub fn delay_ticks(&self, count: u32) {
// if count == 0 {
// return;
// }
// APIC_TIMER_COUNT.store(0, Ordering::SeqCst);
// self.access
// .write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
// self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11);
// self.access.write(APIC_TIMER_INITIAL_COUNT, count as u64);
// self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
// while APIC_TIMER_COUNT.load(Ordering::SeqCst) == 0 {
// unsafe {
// core::arch::asm!("sti", "hlt", "cli", options(nomem, nostack));
// }
// }
// self.stop_timer();
// }
pub fn id(&self) -> u32 { pub fn id(&self) -> u32 {
self.id self.id
} }
+110 -2
View File
@@ -1,13 +1,109 @@
use core::arch::asm; use core::arch::{asm, naked_asm};
use crate::memory::VirtualAddr;
#[repr(C, align(64))]
pub struct CpuLocal {
pub kernel_stack_top: usize,
pub user_rsp_scratch: usize,
pub cpu_id: u32,
}
pub static mut BOOT_CPU: CpuLocal = CpuLocal {
kernel_stack_top: 0,
user_rsp_scratch: 0,
cpu_id: 0,
};
#[derive(Debug)]
pub struct ThreadContext {
rsp: usize,
}
impl ThreadContext {
pub fn new(
user_entry: VirtualAddr,
user_stack: VirtualAddr,
kernel_stack_top: VirtualAddr,
) -> Self {
// Stack layout (grows downwards from kernel_stack_top):
// [top - 8] = user_thread_entry (popped by `ret`)
// [top - 16] = rbp (0)
// [top - 24] = rbx (user_stack)
// [top - 32] = r12 (user_entry)
// [top - 40] = r13 (0)
// [top - 48] = r14 (0)
// [top - 56] = r15 (0) <- initial rsp
let stack_ptr = (kernel_stack_top.as_usize() - 56) as *mut usize;
unsafe {
stack_ptr.add(0).write(0); // r15
stack_ptr.add(1).write(0); // r14
stack_ptr.add(2).write(0); // r13
stack_ptr.add(3).write(user_entry.as_usize()); // r12 (user_entry)
stack_ptr.add(4).write(user_stack.as_usize()); // rbx (user_stack)
stack_ptr.add(5).write(0); // rbp (0)
stack_ptr
.add(6)
.write(user_thread_entry as *const () as usize); // return address
}
Self {
rsp: kernel_stack_top.as_usize() - 56,
}
}
pub fn empty() -> Self {
Self { rsp: 0 }
}
}
#[unsafe(naked)]
unsafe extern "C" fn user_thread_entry() -> ! {
naked_asm!(
// r12 = user_entry, rbx = user_stack
"mov rdi, r12",
"mov rsi, rbx",
"call {enter_user}",
enter_user = sym crate::arch::enter_user,
);
}
#[unsafe(naked)]
pub unsafe extern "C" fn switch_context(prev: *mut ThreadContext, next: *const ThreadContext) {
naked_asm!(
"push rbp",
"push rbx",
"push r12",
"push r13",
"push r14",
"push r15",
"",
// save current rsp into prev.rsp
"mov [rdi], rsp",
// load next rsp into rsp
"mov rsp, [rsi]",
"",
"pop r15",
"pop r14",
"pop r13",
"pop r12",
"pop rbx",
"pop rbp",
"ret",
)
}
#[derive(Debug)] #[derive(Debug)]
pub enum CpuFeaturesError { pub enum CpuFeaturesError {
CpuidFeaturesNotSupported, CpuidFeaturesNotSupported,
SyscallNotSupported,
InvalidPhysicalAddressWidth, InvalidPhysicalAddressWidth,
InvalidVirtualAddressWidth, InvalidVirtualAddressWidth,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub(crate) struct CpuFeatures { pub(crate) struct CpuFeatures {
pub nx_supported: bool, pub nx_supported: bool,
pub nx_enabled: bool, pub nx_enabled: bool,
@@ -40,6 +136,18 @@ pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
features.nx_supported = cpuid_result.edx & (1 << 20) != 0; features.nx_supported = cpuid_result.edx & (1 << 20) != 0;
// TODO: on AMD K6 *only*, this bit is bit 10, should we consider that edge case?
let syscall_supported = cpuid_result.edx & (1 << 11) != 0;
if !syscall_supported {
return Err(CpuFeaturesError::SyscallNotSupported);
}
unsafe {
let efer = read_msr(IA32_EFER);
let value = efer | 1;
write_msr(IA32_EFER, value);
};
let cpuid_result = core::arch::x86_64::__cpuid(0x80000008); let cpuid_result = core::arch::x86_64::__cpuid(0x80000008);
features.physical_address_bits = (cpuid_result.eax & 0xFF) as u8; features.physical_address_bits = (cpuid_result.eax & 0xFF) as u8;
+8 -6
View File
@@ -3,8 +3,8 @@ use core::arch::asm;
use crate::memory::VirtualAddr; use crate::memory::VirtualAddr;
#[repr(C, align(8))] #[repr(C, align(8))]
struct Gdt { pub(super) struct Gdt {
entries: [u64; 7], pub entries: [u64; 7],
} }
impl Gdt { impl Gdt {
@@ -49,12 +49,12 @@ const DOUBLE_FAULT_STACK_SIZE: usize = 16 * 1024;
pub(super) const KERNEL_CODE_SELECTOR: u16 = 1 * 8; pub(super) const KERNEL_CODE_SELECTOR: u16 = 1 * 8;
pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8; pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8;
pub(super) const USER_CODE_SELECTOR: u16 = (3 * 8) | 3; pub(super) const USER_DATA_SELECTOR: u16 = (3 * 8) | 3;
pub(super) const USER_DATA_SELECTOR: u16 = (4 * 8) | 3; pub(super) const USER_CODE_SELECTOR: u16 = (4 * 8) | 3;
pub(super) const TSS_SELECTOR: u16 = 5 * 8; pub(super) const TSS_SELECTOR: u16 = 5 * 8;
#[repr(align(16))] #[repr(align(16))]
#[allow(dead_code)] // field 0 is read, rust just cant tell #[allow(unused)] // field 0 is read, rust just cant tell
struct ExceptionStack([u8; DOUBLE_FAULT_STACK_SIZE]); struct ExceptionStack([u8; DOUBLE_FAULT_STACK_SIZE]);
static mut GDT: Gdt = Gdt::new(); static mut GDT: Gdt = Gdt::new();
@@ -78,8 +78,10 @@ pub fn init() {
0, 0,
kernel_code_descriptor(), kernel_code_descriptor(),
kernel_data_descriptor(), kernel_data_descriptor(),
user_code_descriptor(), // In Long Mode, userland CS will be loaded from STAR 63:48 + 16
// and userland SS from STAR 63:48 + 8 on SYSRET.
user_data_descriptor(), user_data_descriptor(),
user_code_descriptor(),
tss_low, tss_low,
tss_high, tss_high,
], ],
+29 -24
View File
@@ -1,35 +1,40 @@
use crate::arch::{ use super::idt::{self, InterruptFrame, stub_no_err};
apic, timer, use crate::arch::{apic, timer};
x86_64::interrupts::idt::{self, InterruptStackFrame},
};
pub const PIT_CALIBRATION_VECTOR: u8 = 0xF1; pub const PIT_CALIBRATION_VECTOR: u8 = 0xF1;
pub const APIC_TIMER_VECTOR: u8 = 0xFD; pub const APIC_TIMER_VECTOR: u8 = 0xFD;
pub const APIC_ERROR_VECTOR: u8 = 0xFE; pub const APIC_ERROR_VECTOR: u8 = 0xFE;
pub const APIC_SPURIOUS_VECTOR: u8 = 0xFF; pub const APIC_SPURIOUS_VECTOR: u8 = 0xFF;
extern "x86-interrupt" fn error_handler(_frame: InterruptStackFrame) { stub_no_err!(stub_pit_calibration, 0xF1);
apic::record_error(); stub_no_err!(stub_apic_timer, 0xFD);
apic::end_of_interrupt(); stub_no_err!(stub_apic_error, 0xFE);
} stub_no_err!(stub_apic_spurious, 0xFF);
extern "x86-interrupt" fn timer_handler(_frame: InterruptStackFrame) { pub(super) fn handle(frame: &mut InterruptFrame) {
apic::record_timer(); match frame.vector as u8 {
apic::end_of_interrupt(); PIT_CALIBRATION_VECTOR => {
} timer::record_pit_calibration();
apic::end_of_interrupt();
extern "x86-interrupt" fn pit_calibration_handler(_frame: InterruptStackFrame) { }
timer::record_pit_calibration(); APIC_TIMER_VECTOR => {
apic::end_of_interrupt(); apic::record_timer();
} apic::end_of_interrupt();
}
extern "x86-interrupt" fn spurious_handler(_frame: InterruptStackFrame) { APIC_ERROR_VECTOR => {
// No EOI apic::record_error();
apic::end_of_interrupt();
}
APIC_SPURIOUS_VECTOR => {
// No EOI
}
_ => {}
}
} }
pub(super) fn install(idt: &mut idt::Idt) { pub(super) fn install(idt: &mut idt::Idt) {
idt.set_handler(PIT_CALIBRATION_VECTOR, pit_calibration_handler, 0); idt.set_handler(PIT_CALIBRATION_VECTOR, stub_pit_calibration, 0);
idt.set_handler(APIC_ERROR_VECTOR, error_handler, 0); idt.set_handler(APIC_ERROR_VECTOR, stub_apic_error, 0);
idt.set_handler(APIC_TIMER_VECTOR, timer_handler, 0); idt.set_handler(APIC_TIMER_VECTOR, stub_apic_timer, 0);
idt.set_handler(APIC_SPURIOUS_VECTOR, spurious_handler, 0); idt.set_handler(APIC_SPURIOUS_VECTOR, stub_apic_spurious, 0);
} }
+101 -65
View File
@@ -1,79 +1,115 @@
use core::arch::asm; use core::arch::asm;
use super::idt::{self, InterruptStackFrame}; use super::idt::{self, InterruptFrame, InterruptStackFrame, stub_err, stub_no_err};
use crate::{hcf, println}; use crate::{
hcf, println,
task::tcb::{ExitReason, Fault},
};
macro_rules! fatal_without_error_code { stub_no_err!(stub_divide_error, 0);
($handler:ident, $name:literal) => { stub_no_err!(stub_debug, 1);
extern "x86-interrupt" fn $handler(frame: InterruptStackFrame) { stub_no_err!(stub_non_maskable_interrupt, 2);
fatal_exception($name, &frame, None); stub_no_err!(stub_breakpoint, 3);
stub_no_err!(stub_invalid_opcode, 6);
stub_no_err!(stub_device_not_available, 7);
stub_err!(stub_double_fault, 8);
stub_err!(stub_invalid_tss, 10);
stub_err!(stub_segment_not_present, 11);
stub_err!(stub_stack_segment_fault, 12);
stub_err!(stub_general_protection, 13);
stub_err!(stub_page_fault, 14);
stub_no_err!(stub_x87_floating_point, 16);
stub_err!(stub_alignment_check, 17);
stub_no_err!(stub_machine_check, 18);
stub_no_err!(stub_simd_floating_point, 19);
stub_no_err!(stub_user_test_exit, 0x80);
const EXCEPTION_NAMES: [&str; 32] = [
"DIVIDE ERROR",
"DEBUG",
"NON-MASKABLE INTERRUPT",
"BREAKPOINT",
"OVERFLOW",
"BOUND RANGE EXCEEDED",
"INVALID OPCODE",
"DEVICE NOT AVAILABLE",
"DOUBLE FAULT",
"COPROCESSOR SEGMENT OVERRUN",
"INVALID TSS",
"SEGMENT NOT PRESENT",
"STACK-SEGMENT FAULT",
"GENERAL PROTECTION FAULT",
"PAGE FAULT",
"RESERVED",
"x87 FLOATING-POINT EXCEPTION",
"ALIGNMENT CHECK",
"MACHINE CHECK",
"SIMD FLOATING-POINT EXCEPTION",
"VIRTUALIZATION EXCEPTION",
"CONTROL PROTECTION EXCEPTION",
"RESERVED",
"RESERVED",
"RESERVED",
"RESERVED",
"RESERVED",
"RESERVED",
"HYPERVISOR INJECTION EXCEPTION",
"VMM COMMUNICATION EXCEPTION",
"SECURITY EXCEPTION",
"RESERVED",
];
pub(super) fn handle(frame: &mut InterruptFrame) {
let is_user = frame.stack_frame.code_segment & 0b11 == 3;
let vector = frame.vector as u8;
let name = EXCEPTION_NAMES
.get(vector as usize)
.copied()
.unwrap_or("UNKNOWN EXCEPTION");
if !is_user {
if vector == 14 {
report_exception(name, &frame.stack_frame, Some(frame.error_code));
println!("Faulting address: {:#X}", read_cr2());
print_page_fault_error(frame.error_code);
hcf();
} }
};
}
macro_rules! fatal_with_error_code { fatal_exception(name, &frame.stack_frame, Some(frame.error_code));
($handler:ident, $name:literal) => {
extern "x86-interrupt" fn $handler(frame: InterruptStackFrame, error_code: u64) {
fatal_exception($name, &frame, Some(error_code));
}
};
}
extern "x86-interrupt" fn breakpoint_handler(frame: InterruptStackFrame) {
report_exception("BREAKPOINT", &frame, None);
}
extern "x86-interrupt" fn page_fault_handler(frame: InterruptStackFrame, error_code: u64) {
report_exception("PAGE FAULT", &frame, Some(error_code));
println!("Faulting address: {:#X}", read_cr2());
print_page_fault_error(error_code);
hcf();
}
fatal_without_error_code!(divide_error_handler, "DIVIDE ERROR");
fatal_without_error_code!(debug_handler, "DEBUG EXCEPTION");
fatal_without_error_code!(non_maskable_interrupt_handler, "NON-MASKABLE INTERRUPT");
fatal_without_error_code!(invalid_opcode_handler, "INVALID OPCODE");
fatal_without_error_code!(device_not_available_handler, "DEVICE NOT AVAILABLE");
fatal_without_error_code!(x87_floating_point_handler, "X87 FLOATING-POINT EXCEPTION");
fatal_without_error_code!(machine_check_handler, "MACHINE CHECK");
fatal_without_error_code!(simd_floating_point_handler, "SIMD FLOATING-POINT EXCEPTION");
fatal_with_error_code!(double_fault_handler, "DOUBLE FAULT");
fatal_with_error_code!(invalid_tss_handler, "INVALID TSS");
fatal_with_error_code!(segment_not_present_handler, "SEGMENT NOT PRESENT");
fatal_with_error_code!(stack_segment_fault_handler, "STACK-SEGMENT FAULT");
fatal_with_error_code!(general_protection_handler, "GENERAL PROTECTION FAULT");
fatal_with_error_code!(alignment_check_handler, "ALIGNMENT CHECK");
extern "x86-interrupt" fn user_test_exit_handler(frame: InterruptStackFrame) {
if frame.code_segment & 0b11 != 3 {
panic!("user_test_exit_handler called from kernel");
} }
println!("User test exit"); let fault = match vector {
hcf(); // Page fault, GPF, Stack/Segment faults -> SegmentationFault
11 | 12 | 13 | 14 => Fault::SegmentationFault,
// Invalid Opcode -> IllegalInstruction
6 => Fault::IllegalInstruction,
// Divide by zero, Alignment check, SIMD/x87 -> Abort
0 | 16 | 17 | 19 => Fault::Abort,
_ => Fault::Abort,
};
crate::task::scheduler::exit_current(ExitReason::Fault(fault));
} }
pub(super) fn install(idt: &mut idt::Idt) { pub(super) fn install(idt: &mut idt::Idt) {
idt.set_handler(0, divide_error_handler, 0); idt.set_handler(0, stub_divide_error, 0);
idt.set_handler(1, debug_handler, 0); idt.set_handler(1, stub_debug, 0);
idt.set_handler(2, non_maskable_interrupt_handler, 0); idt.set_handler(2, stub_non_maskable_interrupt, 0);
idt.set_user_handler(3, breakpoint_handler, 0); idt.set_user_handler(3, stub_breakpoint, 0);
idt.set_handler(6, invalid_opcode_handler, 0); idt.set_handler(6, stub_invalid_opcode, 0);
idt.set_handler(7, device_not_available_handler, 0); idt.set_handler(7, stub_device_not_available, 0);
idt.set_error_code_handler(8, double_fault_handler, 1); idt.set_handler(8, stub_double_fault, 1);
idt.set_error_code_handler(10, invalid_tss_handler, 0); idt.set_handler(10, stub_invalid_tss, 0);
idt.set_error_code_handler(11, segment_not_present_handler, 0); idt.set_handler(11, stub_segment_not_present, 0);
idt.set_error_code_handler(12, stack_segment_fault_handler, 0); idt.set_handler(12, stub_stack_segment_fault, 0);
idt.set_error_code_handler(13, general_protection_handler, 0); idt.set_handler(13, stub_general_protection, 0);
idt.set_error_code_handler(14, page_fault_handler, 0); idt.set_handler(14, stub_page_fault, 0);
idt.set_handler(16, x87_floating_point_handler, 0); idt.set_handler(16, stub_x87_floating_point, 0);
idt.set_error_code_handler(17, alignment_check_handler, 0); idt.set_handler(17, stub_alignment_check, 0);
idt.set_handler(18, machine_check_handler, 0); idt.set_handler(18, stub_machine_check, 0);
idt.set_handler(19, simd_floating_point_handler, 0); idt.set_handler(19, stub_simd_floating_point, 0);
idt.set_user_handler(0x80, user_test_exit_handler, 0); idt.set_user_handler(0x80, stub_user_test_exit, 0);
} }
fn read_cr2() -> u64 { fn read_cr2() -> u64 {
+133 -16
View File
@@ -18,7 +18,7 @@ struct IdtEntry {
impl IdtEntry { impl IdtEntry {
const fn missing() -> Self { const fn missing() -> Self {
return Self { Self {
offset_low: 0, offset_low: 0,
code_selector: 0, code_selector: 0,
ist: 0, ist: 0,
@@ -26,7 +26,7 @@ impl IdtEntry {
offset_middle: 0, offset_middle: 0,
offset_high: 0, offset_high: 0,
reserved: 0, reserved: 0,
}; }
} }
} }
@@ -38,7 +38,7 @@ struct IdtPointer {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub(super) struct InterruptStackFrame { pub struct InterruptStackFrame {
pub instruction_pointer: VirtualAddr, pub instruction_pointer: VirtualAddr,
pub code_segment: u64, pub code_segment: u64,
pub cpu_flags: u64, pub cpu_flags: u64,
@@ -46,14 +46,36 @@ pub(super) struct InterruptStackFrame {
pub stack_segment: u64, pub stack_segment: u64,
} }
#[repr(C)]
#[derive(Debug)]
pub struct InterruptFrame {
pub rax: u64,
pub rcx: u64,
pub rdx: u64,
pub rsi: u64,
pub rdi: u64,
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub rbx: u64,
pub rbp: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
pub vector: u64,
pub error_code: u64,
pub stack_frame: InterruptStackFrame,
}
const INTERRUPT_GATE: u8 = 0b1110; const INTERRUPT_GATE: u8 = 0b1110;
const PRESENT: u8 = 1 << 7; const PRESENT: u8 = 1 << 7;
const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE; const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE;
const USER_DPL: u8 = 3 << 5; const USER_DPL: u8 = 3 << 5;
const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE; const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE;
pub(super) type Handler = extern "x86-interrupt" fn(InterruptStackFrame); pub(super) type RawHandler = unsafe extern "C" fn();
pub(super) type ErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64);
pub(super) struct Idt { pub(super) struct Idt {
entries: [IdtEntry; 256], entries: [IdtEntry; 256],
@@ -66,20 +88,11 @@ impl Idt {
} }
} }
pub(super) fn set_handler(&mut self, vector: u8, handler: Handler, ist: u8) { pub(super) fn set_handler(&mut self, vector: u8, handler: RawHandler, ist: u8) {
self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE); self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE);
} }
pub(super) fn set_error_code_handler( pub(super) fn set_user_handler(&mut self, vector: u8, handler: RawHandler, ist: u8) {
&mut self,
vector: u8,
handler: ErrorCodeHandler,
ist: u8,
) {
self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE);
}
pub(super) fn set_user_handler(&mut self, vector: u8, handler: Handler, ist: u8) {
self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE); self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE);
} }
@@ -101,6 +114,110 @@ static mut IDT: Idt = Idt::new();
const _: () = assert!(core::mem::size_of::<IdtEntry>() == 16); const _: () = assert!(core::mem::size_of::<IdtEntry>() == 16);
const _: () = assert!(core::mem::size_of::<IdtPointer>() == 10); const _: () = assert!(core::mem::size_of::<IdtPointer>() == 10);
const _: () = assert!(core::mem::size_of::<InterruptStackFrame>() == 40); const _: () = assert!(core::mem::size_of::<InterruptStackFrame>() == 40);
const _: () = assert!(core::mem::size_of::<InterruptFrame>() == 176);
const _: () = assert!(core::mem::offset_of!(InterruptFrame, stack_frame) == 136);
#[unsafe(naked)]
pub(super) unsafe extern "C" fn interrupt_common() {
core::arch::naked_asm!(
"push r15",
"push r14",
"push r13",
"push r12",
"push rbp",
"push rbx",
"push r11",
"push r10",
"push r9",
"push r8",
"push rdi",
"push rsi",
"push rdx",
"push rcx",
"push rax",
// Check CS: bit 0 and 1 are CPL. If CPL != 0 (user mode), swapgs
"test byte ptr [rsp + 144], 3",
"jz 1f",
"swapgs",
"1:",
"mov rdi, rsp",
"cld",
"call {dispatch}",
// Check CS: if returning to user mode, swapgs
"test byte ptr [rsp + 144], 3",
"jz 2f",
"swapgs",
"2:",
"pop rax",
"pop rcx",
"pop rdx",
"pop rsi",
"pop rdi",
"pop r8",
"pop r9",
"pop r10",
"pop r11",
"pop rbx",
"pop rbp",
"pop r12",
"pop r13",
"pop r14",
"pop r15",
"add rsp, 16",
"iretq",
dispatch = sym interrupt_dispatch,
);
}
extern "C" fn interrupt_dispatch(frame: &mut InterruptFrame) {
let vector = frame.vector as u8;
match vector {
0..=31 | 0x80 => exceptions::handle(frame),
apic_vectors::PIT_CALIBRATION_VECTOR
| apic_vectors::APIC_TIMER_VECTOR
| apic_vectors::APIC_ERROR_VECTOR
| apic_vectors::APIC_SPURIOUS_VECTOR => apic_vectors::handle(frame),
_ => {
crate::println!("Unhandled interrupt vector: {:#X}", vector);
}
}
}
macro_rules! stub_no_err {
($name:ident, $vec:literal) => {
#[unsafe(naked)]
pub(super) unsafe extern "C" fn $name() {
core::arch::naked_asm!(
"push 0",
concat!("push ", stringify!($vec)),
"jmp {common}",
common = sym $crate::arch::x86_64::interrupts::idt::interrupt_common,
);
}
};
}
macro_rules! stub_err {
($name:ident, $vec:literal) => {
#[unsafe(naked)]
pub(super) unsafe extern "C" fn $name() {
core::arch::naked_asm!(
concat!("push ", stringify!($vec)),
"jmp {common}",
common = sym $crate::arch::x86_64::interrupts::idt::interrupt_common,
);
}
};
}
pub(super) use stub_err;
pub(super) use stub_no_err;
pub fn idt_init() { pub fn idt_init() {
let mut idt = Idt::new(); let mut idt = Idt::new();
+27
View File
@@ -6,6 +6,33 @@ mod idt;
pub use idt::idt_init as init; pub use idt::idt_init as init;
#[inline(always)]
pub fn disable_interrupts_and_save() -> u64 {
let flags: u64;
unsafe {
asm!("
pushfq",
"pop {flags}",
"cli",
flags = out(reg) flags,
);
}
flags
}
#[inline(always)]
pub fn restore_interrupts(flags: u64) {
unsafe {
asm!(
"push {flags}",
"popfq",
flags = in(reg) flags,
);
}
}
#[inline(always)] #[inline(always)]
pub fn disable_interrupts() { pub fn disable_interrupts() {
unsafe { unsafe {
-2
View File
@@ -3,7 +3,6 @@ use crate::{
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr, AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
}, },
platform::acpi::{InterruptPolarity, TriggerMode}, platform::acpi::{InterruptPolarity, TriggerMode},
println,
}; };
const IOWIN: usize = 0x10; const IOWIN: usize = 0x10;
@@ -32,7 +31,6 @@ pub struct RedirectionConfig {
pub trigger: TriggerMode, pub trigger: TriggerMode,
} }
#[derive(Debug)]
pub struct IoApic { pub struct IoApic {
base: VirtualAddr, base: VirtualAddr,
global_interrupt_base: u32, global_interrupt_base: u32,
+44 -48
View File
@@ -1,16 +1,18 @@
pub mod apic; pub(super) mod apic;
mod cpu; mod cpu;
mod gdt; mod gdt;
mod interrupts; mod interrupts;
pub mod io_apic; pub(super) mod io_apic;
mod paging; mod paging;
mod pit; mod pit;
pub mod port; pub mod port;
mod syscall;
pub mod timer; pub mod timer;
use core::arch::asm; use core::arch::asm;
pub use interrupts::disable_interrupts; pub use cpu::{ThreadContext, switch_context};
pub use interrupts::{disable_interrupts, disable_interrupts_and_save, restore_interrupts};
pub(crate) use paging::{ pub(crate) use paging::{
MapError as PageTableMapError, PageTableCreateError, UnmapError as PageTableUnmapError, MapError as PageTableMapError, PageTableCreateError, UnmapError as PageTableUnmapError,
}; };
@@ -22,11 +24,7 @@ pub struct ArchState {
use crate::{ use crate::{
KernelHandoff, KernelHandoff,
arch::{ arch::x86_64::cpu::BOOT_CPU,
apic::LocalApic,
io_apic::{IOAPIC_VIRTUAL_ADDRESS, IoApic},
x86_64::interrupts::apic_vectors::PIT_CALIBRATION_VECTOR,
},
memory::{AddressSpace, FrameAllocator, VirtualAddr}, memory::{AddressSpace, FrameAllocator, VirtualAddr},
platform::acpi::Madt, platform::acpi::Madt,
println, println,
@@ -45,6 +43,14 @@ pub fn init() -> ArchState {
ArchState { paging } ArchState { paging }
} }
pub fn set_kernel_stack(stack_top: VirtualAddr) {
gdt::set_kernel_stack(stack_top);
unsafe {
BOOT_CPU.kernel_stack_top = stack_top.as_usize();
}
}
#[derive(Debug)] #[derive(Debug)]
#[allow(unused)] #[allow(unused)]
pub enum InterruptInitError { pub enum InterruptInitError {
@@ -58,10 +64,9 @@ pub enum InterruptInitError {
PitNotHandled, PitNotHandled,
} }
#[derive(Debug)]
pub struct InterruptController { pub struct InterruptController {
local_apic: LocalApic, local_apic: apic::LocalApic,
io_apic: IoApic, io_apic: io_apic::IoApic,
local_timer_frequency: u64, local_timer_frequency: u64,
} }
@@ -89,7 +94,7 @@ pub fn init_interrupt_controller(
io_apic_info.id, io_apic_info.id,
io_apic_info.apic_address, io_apic_info.apic_address,
io_apic_info.global_system_interrupt_base, io_apic_info.global_system_interrupt_base,
IOAPIC_VIRTUAL_ADDRESS, io_apic::IOAPIC_VIRTUAL_ADDRESS,
allocator, allocator,
address_space, address_space,
) )
@@ -107,7 +112,7 @@ pub fn init_interrupt_controller(
.configure_masked( .configure_masked(
pit_route.gsi, pit_route.gsi,
io_apic::RedirectionConfig { io_apic::RedirectionConfig {
vector: PIT_CALIBRATION_VECTOR, vector: interrupts::apic_vectors::PIT_CALIBRATION_VECTOR,
destination, destination,
polarity: pit_route.polarity, polarity: pit_route.polarity,
trigger: pit_route.trigger, trigger: pit_route.trigger,
@@ -126,37 +131,6 @@ pub fn init_interrupt_controller(
}) })
} }
// #[derive(Debug)]
// pub enum TimerError {
// DurationOverflow,
// }
// impl InterruptController {
// pub fn delay(&self, duration: core::time::Duration) -> Result<(), TimerError> {
// let nanoseconds = duration.as_nanos();
// let ticks = (nanoseconds
// .checked_mul(self.local_timer_frequency as u128)
// .ok_or(TimerError::DurationOverflow)?
// + 999_999_999)
// / 1_000_000_000;
// if ticks == 0 {
// return Ok(());
// }
// let mut remaining = ticks;
// while remaining > 0 {
// let chunk = remaining.min(u32::MAX as u128) as u32;
// self.local_apic.delay_ticks(chunk);
// remaining -= chunk as u128;
// }
// Ok(())
// }
// }
/// # Safety /// # Safety
/// ///
/// The caller must ensure: /// The caller must ensure:
@@ -165,6 +139,9 @@ pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff)
unsafe { unsafe {
gdt::set_kernel_stack(stack_top); gdt::set_kernel_stack(stack_top);
BOOT_CPU.kernel_stack_top = stack_top.as_usize();
syscall::init(&raw const BOOT_CPU);
asm!( asm!(
"mov rsp, {stack_top}", "mov rsp, {stack_top}",
"xor rbp, rbp", "xor rbp, rbp",
@@ -186,20 +163,39 @@ pub unsafe fn enter_user(
user_instruction_pointer: VirtualAddr, user_instruction_pointer: VirtualAddr,
user_stack_pointer: VirtualAddr, user_stack_pointer: VirtualAddr,
) -> ! { ) -> ! {
println!("Entering user mode");
unsafe { unsafe {
asm!( asm!(
"mov ds, {user_data_selector:x}", "mov ds, {user_data_selector:x}",
"mov es, {user_data_selector:x}", "mov es, {user_data_selector:x}",
"mov fs, {user_data_selector:x}", "mov fs, {user_data_selector:x}",
"mov gs, {user_data_selector:x}", // ss is handled by iretq
"push {user_data_selector}", "push {user_data_selector}",
"push {user_stack_pointer}", "push {user_stack_pointer}",
"pushfq", "push 0x202", // RFLAGS (IF=1, bit 1 reserved=1)
"push {user_code_selector}", "push {user_code_selector}",
"push {user_instruction_pointer}", "push {user_instruction_pointer}",
// clear GPRs
"xor rax, rax",
"xor rbx, rbx",
"xor rcx, rcx",
"xor rdx, rdx",
"xor rsi, rsi",
"xor rdi, rdi",
"xor rbp, rbp",
"xor r8, r8",
"xor r9, r9",
"xor r10, r10",
"xor r11, r11",
"xor r12, r12",
"xor r13, r13",
"xor r14, r14",
"xor r15, r15",
// Kernel GS is CpuLocal; leave it in IA32_KERNEL_GS_BASE so
// syscall_entry can recover it with SWAPGS.
"swapgs",
"mov gs, {user_data_selector:x}",
"iretq", "iretq",
user_data_selector = in(reg) gdt::USER_DATA_SELECTOR as usize, user_data_selector = in(reg) gdt::USER_DATA_SELECTOR as usize,
user_code_selector = in(reg) gdt::USER_CODE_SELECTOR as usize, user_code_selector = in(reg) gdt::USER_CODE_SELECTOR as usize,
+89 -48
View File
@@ -4,14 +4,14 @@ use crate::{
arch::x86_64::cpu::CpuFeatures, arch::x86_64::cpu::CpuFeatures,
memory::{ memory::{
CachePolicy, DirectMap, FrameAddr, FrameAllocator, OwnedFrame, PagePermissions, CachePolicy, DirectMap, FrameAddr, FrameAllocator, OwnedFrame, PagePermissions,
PhysicalAddr, VirtualAddr, PageTableMapping, PhysicalAddr, VirtualAddr,
}, },
}; };
pub const PAGE_SIZE: usize = 4096; pub const PAGE_SIZE: usize = 4096;
pub const PAGE_TABLE_ENTRIES: usize = 512; pub const PAGE_TABLE_ENTRIES: usize = 512;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct PagingConfig { pub struct PagingConfig {
physical_address_bits: u8, physical_address_bits: u8,
global_pages: bool, global_pages: bool,
@@ -42,7 +42,7 @@ impl PagingConfig {
} }
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
enum PagingMode { enum PagingMode {
FourLevel, FourLevel,
FiveLevel, FiveLevel,
@@ -77,7 +77,7 @@ impl PagingMode {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
enum PageTableLevel { enum PageTableLevel {
Pml5, Pml5,
Pml4, Pml4,
@@ -106,14 +106,13 @@ impl PageTableLevel {
} }
} }
#[derive(Debug)]
enum PageTableEntryError { enum PageTableEntryError {
PhysicalAddressTooLarge, PhysicalAddressTooLarge,
NoExecuteUnsupported, NoExecuteUnsupported,
} }
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
struct PageTableEntry(u64); struct PageTableEntry(u64);
impl PageTableEntry { impl PageTableEntry {
@@ -200,6 +199,14 @@ impl PageTableEntry {
self.0 & Self::PRESENT != 0 self.0 & Self::PRESENT != 0
} }
fn writable(&self) -> bool {
self.0 & Self::WRITABLE != 0
}
fn executable(&self) -> bool {
self.0 & Self::NX == 0
}
fn is_user_accessible(&self) -> bool { fn is_user_accessible(&self) -> bool {
self.0 & Self::USER_ACCESSIBLE != 0 self.0 & Self::USER_ACCESSIBLE != 0
} }
@@ -227,9 +234,16 @@ impl PageTableEntry {
FrameAddr::from_start_address(self.physical_address(config)) FrameAddr::from_start_address(self.physical_address(config))
} }
fn permissions(&self) -> PagePermissions {
PagePermissions::new(
self.writable(),
self.executable(),
self.is_user_accessible(),
)
}
} }
#[derive(Debug)]
pub(crate) enum MapError { pub(crate) enum MapError {
InvalidVirtualAddress, InvalidVirtualAddress,
VirtualAddressUnaligned, VirtualAddressUnaligned,
@@ -270,6 +284,14 @@ pub struct PageTable {
frame: OwnedFrame, frame: OwnedFrame,
} }
impl PartialEq for PageTable {
fn eq(&self, other: &Self) -> bool {
self.frame.frame_address() == other.frame.frame_address()
}
}
impl Eq for PageTable {}
impl PageTable { impl PageTable {
pub fn new( pub fn new(
direct_map: DirectMap, direct_map: DirectMap,
@@ -357,6 +379,52 @@ impl PageTable {
self.direct_map.translate(addr) self.direct_map.translate(addr)
} }
pub fn mapping(&self, virtual_addr: VirtualAddr) -> Option<PageTableMapping> {
let address = virtual_addr.as_usize();
if !self.is_canonical(address) {
return None;
}
let mut table_frame = self.frame.frame_address();
let mut permissions = PagePermissions::new(true, true, true);
for &level in self.config.mode.intermediate_levels() {
let table = self.table(table_frame)?;
let entry = table[level.index(address)];
if !entry.is_present() {
return None;
}
let entry_permissions = entry.permissions();
permissions.writable &= entry_permissions.writable;
permissions.user_accessible &= entry_permissions.user_accessible;
permissions.executable &= entry_permissions.executable;
if entry.is_huge() {
level.large_page_size()?;
return Some(PageTableMapping { permissions });
}
table_frame = entry.table_frame(self.config)?;
}
let page_table = self.table(table_frame)?;
let entry = page_table[p1_index(address)];
if !entry.is_present() {
return None;
}
let entry_permissions = entry.permissions();
permissions.writable &= entry_permissions.writable;
permissions.user_accessible &= entry_permissions.user_accessible;
permissions.executable &= entry_permissions.executable;
Some(PageTableMapping { permissions })
}
fn get_next_level( fn get_next_level(
&self, &self,
parent: FrameAddr, parent: FrameAddr,
@@ -381,16 +449,6 @@ impl PageTable {
.ok_or(UnmapError::PageNotMapped) .ok_or(UnmapError::PageNotMapped)
} }
fn discard_private_tables(
frames: &mut [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS],
count: usize,
allocator: &mut FrameAllocator,
) {
for frame in frames[..count].iter_mut().rev().filter_map(Option::take) {
unsafe { allocator.dealloc(frame) };
}
}
fn apply_user_upgrades( fn apply_user_upgrades(
&mut self, &mut self,
upgrades: &[Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS], upgrades: &[Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS],
@@ -500,34 +558,18 @@ impl PageTable {
let private_table_count = levels.len() - missing_depth; let private_table_count = levels.len() - missing_depth;
let mut private_tables: [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS] = let mut private_tables: [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS] =
core::array::from_fn(|_| None); core::array::from_fn(|_| None);
let mut allocated_count = 0;
while allocated_count < private_table_count {
let private_frame = match allocator.alloc() {
Some(frame) => frame,
None => {
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(MapError::OutOfFrames);
}
};
if PageTableEntry::new_table(
private_frame.frame_address(),
permissions.user_accessible,
self.config,
)
.is_err()
{
unsafe { allocator.dealloc(private_frame) };
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(MapError::PhysicalAddressTooLarge);
}
private_tables[allocated_count] = Some(private_frame);
allocated_count += 1;
}
let prepare_result = (|| { let prepare_result = (|| {
for slot in &mut private_tables[..private_table_count] {
let frame = allocator.alloc().ok_or(MapError::OutOfFrames)?;
let address = frame.frame_address();
// Track ownership before validation so every error uses the same cleanup.
*slot = Some(frame);
PageTableEntry::new_table(address, permissions.user_accessible, self.config)
.map_err(|_| MapError::PhysicalAddressTooLarge)?;
}
for private_index in 0..private_table_count { for private_index in 0..private_table_count {
let private_frame = private_tables[private_index] let private_frame = private_tables[private_index]
.as_ref() .as_ref()
@@ -563,7 +605,9 @@ impl PageTable {
let publication_entry = match prepare_result { let publication_entry = match prepare_result {
Ok(entry) => entry, Ok(entry) => entry,
Err(error) => { Err(error) => {
Self::discard_private_tables(&mut private_tables, allocated_count, allocator); for frame in private_tables.into_iter().rev().flatten() {
unsafe { allocator.dealloc(frame) };
}
return Err(error); return Err(error);
} }
}; };
@@ -574,10 +618,7 @@ impl PageTable {
[publication_location.index] = publication_entry; [publication_location.index] = publication_entry;
// The published page table now owns these frames. // The published page table now owns these frames.
for frame in private_tables[..allocated_count] for frame in private_tables.into_iter().flatten() {
.iter_mut()
.flat_map(Option::take)
{
let _ = frame.into_raw(); let _ = frame.into_raw();
} }
+5 -9
View File
@@ -13,15 +13,11 @@ const BINARY: u8 = 0;
pub const PIT_FREQUENCY: u64 = 1_193_182; pub const PIT_FREQUENCY: u64 = 1_193_182;
pub const PIT_CALIBRATION_COUNT: u16 = u16::MAX; pub const PIT_CALIBRATION_COUNT: u16 = u16::MAX;
pub struct Pit; pub fn start_pit_one_shot(count: u16) {
unsafe {
write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY);
impl Pit { write_u8(PIT_CHANNEL_0, count as u8);
pub fn start_one_shot(count: u16) { write_u8(PIT_CHANNEL_0, (count >> 8) as u8);
unsafe {
write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY);
write_u8(PIT_CHANNEL_0, count as u8);
write_u8(PIT_CHANNEL_0, (count >> 8) as u8);
}
} }
} }
+114
View File
@@ -0,0 +1,114 @@
use core::arch::{asm, naked_asm};
use crate::arch::x86_64::{
cpu::{CpuLocal, write_msr},
gdt::{KERNEL_CODE_SELECTOR, KERNEL_DATA_SELECTOR},
};
const IA32_STAR: u32 = 0xC000_0081;
const IA32_LSTAR: u32 = 0xC000_0082;
const IA32_CSTAR: u32 = 0xC000_0083;
const IA32_FMASK: u32 = 0xC000_0084;
const IA32_GS_BASE: u32 = 0xC000_0101;
const IA32_KERNEL_GS_BASE: u32 = 0xC000_0102;
const RFLAGS_MASK: u64 = 0x257FD5; // Clear IF, TF, DF, IOPL, NT, AC
#[repr(C)]
struct SyscallFrame {
pub r15: u64,
pub r14: u64,
pub r13: u64,
pub r12: u64,
pub rbp: u64,
pub rbx: u64,
pub r9: u64, // arg5
pub r8: u64, // arg4
pub r10: u64, // arg3
pub rdx: u64, // arg2
pub rsi: u64, // arg1
pub rdi: u64, // arg0
pub rax: u64, // syscall number on entry / return value on exit
pub user_rip: u64, // rcx
pub user_rflags: u64, // r11
pub user_rsp: u64,
}
pub fn init(cpu_local: *const CpuLocal) {
unsafe {
let star = ((KERNEL_DATA_SELECTOR as u64) << 48) | ((KERNEL_CODE_SELECTOR as u64) << 32);
write_msr(IA32_STAR, star);
write_msr(IA32_LSTAR, syscall_entry as *const () as u64);
write_msr(IA32_CSTAR, 0);
write_msr(IA32_FMASK, RFLAGS_MASK);
write_msr(IA32_GS_BASE, 0);
write_msr(IA32_KERNEL_GS_BASE, cpu_local as u64);
// Kernel code always runs with GS pointing at CpuLocal. User entry
// swaps this into IA32_KERNEL_GS_BASE before transitioning to ring 3.
asm!("swapgs", options(nostack, preserves_flags));
}
}
#[unsafe(naked)]
unsafe extern "C" fn syscall_entry() {
naked_asm!(
"swapgs",
"mov gs:[8], rsp", // user_rsp_scratch
"mov rsp, gs:[0]", // kernel_stack_top
"",
// build the syscall frame
"push qword ptr gs:[8]", // user_rsp
"push r11", // user_rflags
"push rcx", // user_rip
"push rax",
"push rdi",
"push rsi",
"push rdx",
"push r10",
"push r8",
"push r9",
"push rbx",
"push rbp",
"push r12",
"push r13",
"push r14",
"push r15",
"",
// Syscall calling convention:
// Syscall number in rax, args in rdi, rsi, rdx, r10, r8, r9
"mov rdi, rsp",
"call {dispatch}",
"",
// restore the syscall frame
"pop r15",
"pop r14",
"pop r13",
"pop r12",
"pop rbp",
"pop rbx",
"pop r9",
"pop r8",
"pop r10",
"pop rdx",
"pop rsi",
"pop rdi",
"pop rax", // return value
"pop rcx", // user_rip for sysret
"pop r11", // user_rflags for sysret
"pop qword ptr gs:[8]", // user_rsp
"",
"mov rsp, gs:[8]", // switch to user stack
"swapgs",
"sysretq",
dispatch = sym syscall_dispatch,
);
}
extern "C" fn syscall_dispatch(frame: &mut SyscallFrame) {
let ret = crate::syscall::handle(
frame.rax, frame.rdi, frame.rsi, frame.rdx, frame.r10, frame.r8, frame.r9,
);
frame.rax = ret;
}
+2 -2
View File
@@ -7,7 +7,7 @@ use crate::{
io_apic::IoApic, io_apic::IoApic,
x86_64::{ x86_64::{
interrupts::enable_interrupts, interrupts::enable_interrupts,
pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, Pit}, pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, start_pit_one_shot},
}, },
}, },
platform::acpi::IsaIrqRoute, platform::acpi::IsaIrqRoute,
@@ -37,7 +37,7 @@ pub fn calibrate_local_apic(
.map_err(|_| TimerCalibrationError::IoApicNotHandled)?; .map_err(|_| TimerCalibrationError::IoApicNotHandled)?;
local_apic.start_calibration_counter(); local_apic.start_calibration_counter();
Pit::start_one_shot(PIT_CALIBRATION_COUNT); start_pit_one_shot(PIT_CALIBRATION_COUNT);
enable_interrupts(); enable_interrupts();
+40 -5
View File
@@ -1,13 +1,13 @@
use ::limine as limine_api; use ::limine as limine_api;
use limine::paging::PagingMode; use limine::paging::PagingMode;
use limine::request::{PagingModeRequest, RsdpRequest}; use limine::request::{ExecutableCmdlineRequest, ModulesRequest, PagingModeRequest, RsdpRequest};
use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest}; use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest};
use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker}; use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker};
use crate::memory::{ use crate::memory::{
KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion, MemoryRegionKind, PagePermissions, BootString, InitramfsImage, KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion,
PhysicalAddr, VirtualAddr, MemoryRegionKind, PagePermissions, PhysicalAddr, VirtualAddr,
}; };
/// Sets the base revision to the latest revision supported by the crate. /// Sets the base revision to the latest revision supported by the crate.
@@ -22,6 +22,14 @@ static BASE_REVISION: BaseRevision = BaseRevision::new();
#[unsafe(link_section = ".requests")] #[unsafe(link_section = ".requests")]
static KERNEL_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new(); static KERNEL_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static KERNEL_CMDLINE_REQUEST: ExecutableCmdlineRequest = ExecutableCmdlineRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static KERNEL_MODULE_REQUEST: ModulesRequest = ModulesRequest::new();
#[used] #[used]
#[unsafe(link_section = ".requests")] #[unsafe(link_section = ".requests")]
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new(); static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
@@ -58,8 +66,12 @@ unsafe extern "C" {
static __data_end: u64; static __data_end: u64;
} }
const MAX_COMMAND_LINE_LENGTH: usize = 512;
pub struct BootInfo { pub struct BootInfo {
pub kernel_layout: KernelMemoryLayout, pub kernel_layout: KernelMemoryLayout,
pub command_line: BootString<MAX_COMMAND_LINE_LENGTH>,
pub initramfs: InitramfsImage,
pub hhdm_offset: usize, pub hhdm_offset: usize,
pub memory_map: MemoryMap, pub memory_map: MemoryMap,
pub rsdp: VirtualAddr, pub rsdp: VirtualAddr,
@@ -75,6 +87,9 @@ impl BootInfo {
pub enum BootError { pub enum BootError {
UnsupportedBaseRevision, UnsupportedBaseRevision,
FailedToGetKernelAddress, FailedToGetKernelAddress,
FailedToGetKernelCmdline,
FailedToGetModules,
FailedToGetInitramfs,
FailedToGetHHDMAddress, FailedToGetHHDMAddress,
FailedToGetMemmap, FailedToGetMemmap,
TooManyMemoryRegions, TooManyMemoryRegions,
@@ -90,10 +105,28 @@ pub fn load_boot_info() -> Result<BootInfo, BootError> {
let kernel_address = KERNEL_ADDRESS_REQUEST let kernel_address = KERNEL_ADDRESS_REQUEST
.response() .response()
.ok_or(BootError::FailedToGetKernelAddress)?; .ok_or(BootError::FailedToGetKernelAddress)?;
let kernel_cmdline = KERNEL_CMDLINE_REQUEST
.response()
.ok_or(BootError::FailedToGetKernelCmdline)?;
let command_line = BootString::from_bytes(kernel_cmdline.cmdline().as_bytes());
let modules = KERNEL_MODULE_REQUEST
.response()
.ok_or(BootError::FailedToGetModules)?;
let initramfs = modules
.modules()
.get(0)
.map(|module| {
let start = VirtualAddr::new(module.data().as_ptr() as usize);
let length = module.data().len();
InitramfsImage { start, length }
})
.ok_or(BootError::FailedToGetInitramfs)?;
let hhdm_offset = HHDM_REQUEST let hhdm_offset = HHDM_REQUEST
.response() .response()
.ok_or(BootError::FailedToGetHHDMAddress)? .ok_or(BootError::FailedToGetHHDMAddress)?
.offset; .offset as usize;
let rsdp = RSDP_REQUEST.response().ok_or(BootError::FailedToGetRsdp)?; let rsdp = RSDP_REQUEST.response().ok_or(BootError::FailedToGetRsdp)?;
@@ -204,7 +237,9 @@ pub fn load_boot_info() -> Result<BootInfo, BootError> {
kernel_data_segment, kernel_data_segment,
], ],
}, },
hhdm_offset: hhdm_offset as usize, command_line,
initramfs,
hhdm_offset,
memory_map, memory_map,
rsdp: VirtualAddr::new(rsdp.address as usize), rsdp: VirtualAddr::new(rsdp.address as usize),
}) })
+6
View File
@@ -160,6 +160,12 @@ pub fn init() -> Result<(), SerialPortError> {
com1().init() com1().init()
} }
pub fn write_bytes(bytes: &[u8]) {
for byte in bytes {
com1().write_byte(*byte);
}
}
pub fn print(args: core::fmt::Arguments) { pub fn print(args: core::fmt::Arguments) {
use core::fmt::Write; use core::fmt::Write;
+80
View File
@@ -0,0 +1,80 @@
// CPIO newc
#[repr(C)]
struct Header {
pub c_magic: [u8; 6],
pub c_ino: [u8; 8],
pub c_mode: [u8; 8],
pub c_uid: [u8; 8],
pub c_gid: [u8; 8],
pub c_nlink: [u8; 8],
pub c_mtime: [u8; 8],
pub c_filesize: [u8; 8],
pub c_devmajor: [u8; 8],
pub c_devminor: [u8; 8],
pub c_rdevmajor: [u8; 8],
pub c_rdevminor: [u8; 8],
pub c_namesize: [u8; 8],
pub c_check: [u8; 8],
}
impl Header {
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < core::mem::size_of::<Header>() {
return None;
}
let header: Header = unsafe { core::ptr::read(bytes.as_ptr() as *const Header) };
if header.c_magic != *b"070701" {
return None;
}
Some(header)
}
}
pub fn find_file<'a>(archive: &'a [u8], target: &str) -> Option<&'a [u8]> {
let mut offset = 0;
while offset + core::mem::size_of::<Header>() <= archive.len() {
if offset + core::mem::size_of::<Header>() > archive.len() {
return None;
}
let header = Header::from_bytes(&archive[offset..])?;
let header_start = offset;
offset += core::mem::size_of::<Header>();
let file_len =
usize::from_str_radix(core::str::from_utf8(&header.c_filesize).ok()?, 16).ok()?;
let name_len =
usize::from_str_radix(core::str::from_utf8(&header.c_namesize).ok()?, 16).ok()?;
if offset + name_len > archive.len() {
return None;
}
let name_bytes = &archive[offset..offset + name_len];
let name = core::str::from_utf8(name_bytes)
.ok()?
.trim_end_matches('\0');
if name == "TRAILER!!!" {
return None;
}
let data_start = header_start + ((core::mem::size_of::<Header>() + name_len + 3) & !3);
if data_start + file_len > archive.len() {
return None;
}
if name == target {
return Some(&archive[data_start..data_start + file_len]);
}
offset = data_start + ((file_len + 3) & !3);
}
None
}
+105
View File
@@ -0,0 +1,105 @@
pub struct ElfError;
#[cfg(target_arch = "x86_64")]
const MACHINE: u16 = 62;
#[cfg(target_arch = "aarch64")]
const MACHINE: u16 = 183;
#[cfg(target_arch = "riscv64")]
const MACHINE: u16 = 243;
pub struct Elf<'a> {
bytes: &'a [u8],
headers: &'a [u8],
pub entry: usize,
}
pub struct Segment<'a> {
pub data: &'a [u8],
pub address: usize,
pub memory_size: usize,
pub writable: bool,
pub executable: bool,
}
impl<'a> Elf<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, ElfError> {
let header = bytes.get(..64).ok_or(ElfError)?;
// Bootstrap images are static ELF64 executables in the native ISA, always LE.
if &header[..7] != b"\x7fELF\x02\x01\x01"
|| u16_at(header, 16) != 2
|| u16_at(header, 18) != MACHINE
|| u32_at(header, 20) != 1
|| u16_at(header, 52) != 64
|| u16_at(header, 54) != 56
{
return Err(ElfError);
}
let offset = usize_at(header, 32);
let count = usize::from(u16_at(header, 56));
let end = offset.checked_add(count * 56).ok_or(ElfError)?;
let headers = bytes.get(offset..end).ok_or(ElfError)?;
if headers
.chunks_exact(56)
.any(|h| matches!(u32_at(h, 0), 2 | 3))
{
// There is no dynamic linker or relocation processing during bootstrap.
return Err(ElfError);
}
Ok(Self {
bytes,
headers,
entry: usize_at(header, 24),
})
}
pub fn segments(&self) -> impl Iterator<Item = Result<Segment<'a>, ElfError>> + '_ {
self.headers
.chunks_exact(56)
.filter(|h| u32_at(h, 0) == 1)
.map(|h| {
let offset = usize_at(h, 8);
let address = usize_at(h, 16);
let file_size = usize_at(h, 32);
let memory_size = usize_at(h, 40);
let alignment = usize_at(h, 48);
if file_size > memory_size
|| (alignment > 1
&& (!alignment.is_power_of_two()
|| address % alignment != offset % alignment))
{
return Err(ElfError);
}
let end = offset.checked_add(file_size).ok_or(ElfError)?;
let data = self.bytes.get(offset..end).ok_or(ElfError)?;
let flags = u32_at(h, 4);
Ok(Segment {
data,
address,
memory_size,
writable: flags & 2 != 0,
executable: flags & 1 != 0,
})
})
}
}
// Callers only read fixed offsets within already bounds-checked headers.
fn u16_at(bytes: &[u8], offset: usize) -> u16 {
let mut value = [0; 2];
value.copy_from_slice(&bytes[offset..offset + 2]);
u16::from_le_bytes(value)
}
fn u32_at(bytes: &[u8], offset: usize) -> u32 {
let mut value = [0; 4];
value.copy_from_slice(&bytes[offset..offset + 4]);
u32::from_le_bytes(value)
}
fn usize_at(bytes: &[u8], offset: usize) -> usize {
let mut value = [0; 8];
value.copy_from_slice(&bytes[offset..offset + 8]);
u64::from_le_bytes(value) as usize
}
+2
View File
@@ -0,0 +1,2 @@
pub mod cpio;
pub mod elf;
+23 -47
View File
@@ -1,4 +1,3 @@
#![feature(abi_x86_interrupt)]
#![allow(clippy::needless_return)] #![allow(clippy::needless_return)]
#![no_std] #![no_std]
#![no_main] #![no_main]
@@ -6,14 +5,15 @@
mod arch; mod arch;
mod boot; mod boot;
mod debug; mod debug;
mod format;
mod memory; mod memory;
mod platform; mod platform;
mod syscall;
mod task;
use crate::{ use crate::{
debug::serial, debug::serial,
memory::{ memory::{AddressSpace, MemoryRegionKind, init_frame_allocator, init_kernel_address_space},
AddressSpace, KernelStack, MemoryRegionKind, PagePermissions, UserStack, VirtualAddr,
},
}; };
pub struct KernelHandoff { pub struct KernelHandoff {
@@ -49,8 +49,9 @@ pub extern "C" fn _start() -> ! {
println!("Entering kernel main..."); println!("Entering kernel main...");
let bootstrap_stack = KernelStack::allocate(&mut address_space, &mut allocator) let kernel_stack =
.expect("failed to allocate bootstrap stack"); crate::task::scheduler::allocate_kernel_stack(&mut address_space, &mut allocator)
.expect("failed to allocate bootstrap stack");
let handoff_frame = allocator let handoff_frame = allocator
.alloc() .alloc()
@@ -59,7 +60,7 @@ pub extern "C" fn _start() -> ! {
.to_virtual(handoff_frame.frame_address().start_address()) .to_virtual(handoff_frame.frame_address().start_address())
.expect("failed to map kernel handoff"); .expect("failed to map kernel handoff");
let bootstrap_stack_top = bootstrap_stack.top(); let bootstrap_stack_top = kernel_stack.top();
let handoff = KernelHandoff { let handoff = KernelHandoff {
allocator, allocator,
address_space, address_space,
@@ -106,56 +107,31 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! {
let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI"); let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI");
println!("Parsing MADT...");
let madt = acpi let madt = acpi
.madt() .madt()
.expect("failed to parse ACPI") .expect("failed to parse ACPI")
.expect("MADT not found"); .expect("MADT not found");
let interrupt_controller = println!("Initializing interrupt controller...");
let _interrupt_controller =
arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space) arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space)
.expect("failed to initialize interrupt controller"); .expect("failed to initialize interrupt controller");
let mut user_addr_space = address_space task::bootstrap::spawn(
.new_user(&mut allocator) "omega3.elf",
.expect("failed to create user address space"); &boot_info.initramfs,
&mut address_space,
&mut allocator,
direct_map,
);
let user_stack = UserStack::allocate(&mut user_addr_space, &mut allocator) init_frame_allocator(allocator);
.expect("failed to allocate user stack"); init_kernel_address_space(address_space);
let user_instruction_pointer = VirtualAddr::new(0x8000); task::scheduler::start();
let user_code_page = allocator
.alloc()
.expect("failed to allocate user code page")
.frame_address();
user_addr_space
.map(
user_code_page.start_address(),
user_instruction_pointer,
PagePermissions::new(true, true, true),
&mut allocator,
memory::CachePolicy::WriteBack,
)
.expect("failed to map user code page");
unsafe {
user_addr_space.activate();
let user_code: [u8; 5] = [
0xCC, // INT3
0xCD, 0x80, // INT 0x80
0xEB, 0xFE, // JMP -2 (loop forever if exit returns)
];
core::ptr::copy_nonoverlapping(
user_code.as_ptr(),
user_instruction_pointer.as_mut_ptr::<u8>(),
user_code.len(),
);
arch::enter_user(user_instruction_pointer, user_stack.top());
}
hcf();
} }
#[panic_handler] #[panic_handler]
+127 -6
View File
@@ -1,11 +1,118 @@
use core::cell::UnsafeCell;
use crate::{ use crate::{
arch::{PageTable, PageTableCreateError, PageTableMapError, PageTableUnmapError, PagingConfig}, arch::{PageTable, PageTableCreateError, PageTableMapError, PageTableUnmapError, PagingConfig},
memory::{ memory::{
CachePolicy, DirectMap, FRAME_SIZE, FrameAddr, FrameAllocator, KernelMemoryLayout, CachePolicy, DirectMap, FRAME_SIZE, FrameAddr, FrameAllocator, KernelMemoryLayout,
MemoryRegion, MemoryRegionKind, PagePermissions, PhysicalAddr, VirtualAddr, MemoryRegion, MemoryRegionKind, PagePermissions, PhysicalAddr, USER_SPACE_END, VirtualAddr,
}, },
}; };
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct AddressSpaceId(usize);
const MAX_ADDRESS_SPACES: usize = 32;
struct AddressSpaceTable {
entries: [Option<AddressSpace>; MAX_ADDRESS_SPACES],
}
impl AddressSpaceTable {
const fn new() -> Self {
Self {
entries: [const { None }; MAX_ADDRESS_SPACES],
}
}
fn insert(&mut self, address_space: AddressSpace) -> Result<AddressSpaceId, AddressSpace> {
for (i, slot) in self.entries.iter_mut().enumerate() {
if slot.is_none() {
*slot = Some(address_space);
return Ok(AddressSpaceId(i));
}
}
Err(address_space)
}
fn get(&self, id: AddressSpaceId) -> Option<&AddressSpace> {
self.entries.get(id.0).and_then(Option::as_ref)
}
fn get_mut(&mut self, id: AddressSpaceId) -> Option<&mut AddressSpace> {
self.entries.get_mut(id.0).and_then(Option::as_mut)
}
fn remove(&mut self, id: AddressSpaceId) -> Option<AddressSpace> {
self.entries.get_mut(id.0).and_then(Option::take)
}
}
struct GlobalAddressSpaceTable(UnsafeCell<AddressSpaceTable>);
unsafe impl Sync for GlobalAddressSpaceTable {}
static ADDRESS_SPACE_TABLE: GlobalAddressSpaceTable =
GlobalAddressSpaceTable(UnsafeCell::new(AddressSpaceTable::new()));
pub fn insert_address_space(address_space: AddressSpace) -> Result<AddressSpaceId, AddressSpace> {
let table = unsafe { &mut *ADDRESS_SPACE_TABLE.0.get() };
table.insert(address_space)
}
pub fn remove_address_space(id: AddressSpaceId) -> Option<AddressSpace> {
let table = unsafe { &mut *ADDRESS_SPACE_TABLE.0.get() };
table.remove(id)
}
pub fn with_address_space<R>(id: AddressSpaceId, f: impl FnOnce(&AddressSpace) -> R) -> Option<R> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let table = unsafe { &*ADDRESS_SPACE_TABLE.0.get() };
let res = table.get(id).map(f);
crate::arch::restore_interrupts(interrupt_state);
res
}
pub fn with_address_space_mut<R>(
id: AddressSpaceId,
f: impl FnOnce(&mut AddressSpace) -> R,
) -> Option<R> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let table = unsafe { &mut *ADDRESS_SPACE_TABLE.0.get() };
let res = table.get_mut(id).map(f);
crate::arch::restore_interrupts(interrupt_state);
res
}
struct GlobalKernelAddressSpace(UnsafeCell<Option<AddressSpace>>);
unsafe impl Sync for GlobalKernelAddressSpace {}
static KERNEL_ADDRESS_SPACE: GlobalKernelAddressSpace =
GlobalKernelAddressSpace(UnsafeCell::new(None));
pub fn init_kernel_address_space(address_space: AddressSpace) {
let interrupt_state = crate::arch::disable_interrupts_and_save();
unsafe {
*KERNEL_ADDRESS_SPACE.0.get() = Some(address_space);
}
crate::arch::restore_interrupts(interrupt_state);
}
pub fn with_kernel_address_space<R>(f: impl FnOnce(&mut AddressSpace) -> R) -> R {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let space = unsafe {
(&mut *KERNEL_ADDRESS_SPACE.0.get())
.as_mut()
.expect("kernel address space not initialized")
};
let res = f(space);
crate::arch::restore_interrupts(interrupt_state);
res
}
#[derive(Debug)] #[derive(Debug)]
pub enum MapError { pub enum MapError {
InvalidVirtualAddress, InvalidVirtualAddress,
@@ -82,12 +189,17 @@ impl From<PageTableCreateError> for AddressSpaceCreateError {
} }
} }
#[derive(Debug, PartialEq, Eq)] pub struct PageTableMapping {
pub permissions: PagePermissions,
}
#[derive(PartialEq, Eq)]
enum AddressSpaceKind { enum AddressSpaceKind {
Kernel, Kernel,
User, User,
} }
#[derive(PartialEq, Eq)]
pub struct AddressSpace { pub struct AddressSpace {
root: PageTable, root: PageTable,
kind: AddressSpaceKind, kind: AddressSpaceKind,
@@ -112,13 +224,18 @@ impl AddressSpace {
// undermind the permissions of the explicitly mapped kernel image // undermind the permissions of the explicitly mapped kernel image
if matches!( if matches!(
region.kind, region.kind,
MemoryRegionKind::Reserved MemoryRegionKind::Reserved | MemoryRegionKind::BadMemory
| MemoryRegionKind::BadMemory
| MemoryRegionKind::KernelAndModules
) { ) {
continue; continue;
} }
// we shouldnt HHDM the kernel image, but we should map modules
if region.kind == MemoryRegionKind::KernelAndModules
&& region.start == layout.segments[0].physical_base
{
continue;
}
let cache_policy = if matches!( let cache_policy = if matches!(
region.kind, region.kind,
MemoryRegionKind::MappedReserved | MemoryRegionKind::Framebuffer MemoryRegionKind::MappedReserved | MemoryRegionKind::Framebuffer
@@ -194,7 +311,7 @@ impl AddressSpace {
let global = self.kind == AddressSpaceKind::Kernel; let global = self.kind == AddressSpaceKind::Kernel;
if self.kind == AddressSpaceKind::User { if self.kind == AddressSpaceKind::User {
if virtual_addr.as_usize() >= 0x0000_8000_0000_0000 { if virtual_addr.as_usize() >= USER_SPACE_END.as_usize() {
return Err(MapError::InvalidUserAddress); return Err(MapError::InvalidUserAddress);
} }
@@ -320,6 +437,10 @@ impl AddressSpace {
self.root.to_virtual(physical_addr) self.root.to_virtual(physical_addr)
} }
pub fn mapping(&self, virtual_addr: VirtualAddr) -> Option<PageTableMapping> {
self.root.mapping(virtual_addr)
}
pub unsafe fn activate(&self) { pub unsafe fn activate(&self) {
unsafe { self.root.activate() } unsafe { self.root.activate() }
} }
+46 -4
View File
@@ -1,3 +1,5 @@
use core::cell::UnsafeCell;
use crate::memory::{DirectMap, MemoryRegion, MemoryRegionKind, PhysicalAddr, VirtualAddr}; use crate::memory::{DirectMap, MemoryRegion, MemoryRegionKind, PhysicalAddr, VirtualAddr};
pub const FRAME_SIZE: usize = 4096; pub const FRAME_SIZE: usize = 4096;
@@ -12,15 +14,57 @@ pub fn align_down_to_frame(addr: usize) -> usize {
} }
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
enum FrameState { enum FrameState {
Reserved = 0b00, Reserved = 0b00,
Free = 0b01, Free = 0b01,
Allocated = 0b10, Allocated = 0b10,
} }
struct GlobalFrameAllocator(UnsafeCell<Option<FrameAllocator>>);
unsafe impl Sync for GlobalFrameAllocator {}
static FRAME_ALLOCATOR: GlobalFrameAllocator = GlobalFrameAllocator(UnsafeCell::new(None));
pub fn init_global(allocator: FrameAllocator) {
let interrupt_state = crate::arch::disable_interrupts_and_save();
unsafe {
*FRAME_ALLOCATOR.0.get() = Some(allocator);
}
crate::arch::restore_interrupts(interrupt_state);
}
pub fn alloc_frame() -> Option<OwnedFrame> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let allocator = unsafe { &mut *FRAME_ALLOCATOR.0.get() };
let frame = allocator.as_mut().and_then(|a| a.alloc());
crate::arch::restore_interrupts(interrupt_state);
frame
}
pub unsafe fn dealloc_frame(frame: OwnedFrame) {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let allocator = unsafe { &mut *FRAME_ALLOCATOR.0.get() };
if let Some(a) = allocator.as_mut() {
unsafe { a.dealloc(frame) };
}
crate::arch::restore_interrupts(interrupt_state);
}
#[allow(unused)]
pub fn with_allocator<R>(f: impl FnOnce(&mut FrameAllocator) -> R) -> R {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let allocator = unsafe {
(&mut *FRAME_ALLOCATOR.0.get())
.as_mut()
.expect("frame allocator not initialized")
};
let result = f(allocator);
crate::arch::restore_interrupts(interrupt_state);
result
}
// 64 KiB per GiB // 64 KiB per GiB
#[derive(Debug)]
struct Bitmap { struct Bitmap {
start: VirtualAddr, start: VirtualAddr,
frame_count: usize, frame_count: usize,
@@ -70,7 +114,6 @@ pub enum FrameAllocatorInitError {
} }
// very very simple bitmap frame/page allocator // very very simple bitmap frame/page allocator
#[derive(Debug)]
pub struct FrameAllocator { pub struct FrameAllocator {
bitmap: Bitmap, bitmap: Bitmap,
next_search: usize, next_search: usize,
@@ -327,7 +370,6 @@ impl FrameAddr {
} }
// specifically not Clone or Copy // specifically not Clone or Copy
#[derive(Debug)]
pub struct OwnedFrame { pub struct OwnedFrame {
frame: FrameAddr, frame: FrameAddr,
} }
+59 -9
View File
@@ -1,11 +1,53 @@
mod address_space; mod address_space;
mod frame; mod frame;
pub mod stack; mod stack;
mod user;
use core::ops::Add;
#[allow(unused)] #[allow(unused)]
pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError}; pub use address_space::{
pub use frame::{FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame}; AddressSpace, AddressSpaceCreateError, AddressSpaceId, MapError, PageTableMapping, UnmapError,
pub use stack::{KernelStack, StackCreateError, UserStack}; init_kernel_address_space, insert_address_space, remove_address_space, with_address_space,
with_address_space_mut, with_kernel_address_space,
};
pub use frame::{
FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame, alloc_frame, dealloc_frame,
init_global as init_frame_allocator, with_allocator,
};
#[allow(unused)]
pub use stack::{KernelStack, KernelStackPool, StackCreateError, UserStack};
#[allow(unused)]
pub use user::*;
pub struct BootString<const N: usize> {
bytes: [u8; N],
len: usize,
}
impl<const N: usize> BootString<N> {
pub fn from_bytes(bytes: &[u8]) -> Self {
let len = bytes.len();
let mut boot_string = Self { bytes: [0; N], len };
boot_string.bytes[..len].copy_from_slice(bytes);
boot_string
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.bytes[..self.len]).unwrap()
}
}
pub struct InitramfsImage {
pub start: VirtualAddr,
pub length: usize,
}
impl InitramfsImage {
pub fn data(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.start.as_ptr(), self.length) }
}
}
pub struct KernelSegment { pub struct KernelSegment {
pub physical_base: PhysicalAddr, pub physical_base: PhysicalAddr,
@@ -18,7 +60,7 @@ pub struct KernelMemoryLayout {
pub segments: [KernelSegment; 3], pub segments: [KernelSegment; 3],
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct PagePermissions { pub struct PagePermissions {
pub writable: bool, pub writable: bool,
pub executable: bool, pub executable: bool,
@@ -71,8 +113,16 @@ impl VirtualAddr {
} }
} }
impl Add<usize> for VirtualAddr {
type Output = Self;
fn add(self, rhs: usize) -> Self::Output {
Self(self.0 + rhs)
}
}
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub enum MemoryRegionKind { pub enum MemoryRegionKind {
Usable, Usable,
Reserved, Reserved,
@@ -85,20 +135,20 @@ pub enum MemoryRegionKind {
MappedReserved, MappedReserved,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub enum CachePolicy { pub enum CachePolicy {
Uncacheable, Uncacheable,
WriteBack, WriteBack,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct MemoryRegion { pub struct MemoryRegion {
pub start: PhysicalAddr, pub start: PhysicalAddr,
pub length: usize, pub length: usize,
pub kind: MemoryRegionKind, pub kind: MemoryRegionKind,
} }
#[derive(Debug, Clone, Copy)] #[derive(Clone, Copy)]
pub struct DirectMap { pub struct DirectMap {
offset: usize, offset: usize,
} }
+60 -7
View File
@@ -3,10 +3,16 @@ use crate::memory::{
VirtualAddr, VirtualAddr,
}; };
const STACK_PAGES: usize = 16; const GUARD_PAGES: usize = 1; // 4KiB
const STACK_SIZE: usize = STACK_PAGES * FRAME_SIZE; const KERNEL_STACK_PAGES: usize = 8; // 32KiB
const USER_STACK_PAGES: usize = 16; // 64KiB
const KERNEL_STACK_SIZE: usize = KERNEL_STACK_PAGES * FRAME_SIZE;
const USER_STACK_SIZE: usize = USER_STACK_PAGES * FRAME_SIZE;
const KERNEL_SLOT_SIZE: usize = (KERNEL_STACK_PAGES + GUARD_PAGES) * FRAME_SIZE;
const MAX_KERNEL_STACKS: usize = 64;
const KERNEL_STACK_BASE: usize = 0xFFFF_FFFE_0000_0000;
const KERNEL_STACK_TOP: VirtualAddr = VirtualAddr::new(0xFFFF_FFFE_0000_0000);
const USER_STACK_TOP: VirtualAddr = VirtualAddr::new(0x0000_7FFF_FFFF_F000); const USER_STACK_TOP: VirtualAddr = VirtualAddr::new(0x0000_7FFF_FFFF_F000);
#[derive(Debug)] #[derive(Debug)]
@@ -14,13 +20,16 @@ pub enum StackCreateError {
AddressOverflow, AddressOverflow,
UnalignedStackTop, UnalignedStackTop,
OutOfFrames, OutOfFrames,
OutOfStacks,
GuardPageMapped, GuardPageMapped,
Map(MapError), Map(MapError),
} }
#[derive(Debug)]
struct StackMapping { struct StackMapping {
guard_page: VirtualAddr, guard_page: VirtualAddr,
mapped_start: VirtualAddr, mapped_start: VirtualAddr,
stack_size: usize,
top: VirtualAddr, top: VirtualAddr,
} }
@@ -28,6 +37,7 @@ impl StackMapping {
fn allocate( fn allocate(
address_space: &mut AddressSpace, address_space: &mut AddressSpace,
allocator: &mut FrameAllocator, allocator: &mut FrameAllocator,
stack_size: usize,
top: VirtualAddr, top: VirtualAddr,
permissions: PagePermissions, permissions: PagePermissions,
) -> Result<Self, StackCreateError> { ) -> Result<Self, StackCreateError> {
@@ -37,7 +47,7 @@ impl StackMapping {
let mapped_start = VirtualAddr::new( let mapped_start = VirtualAddr::new(
top.as_usize() top.as_usize()
.checked_sub(STACK_SIZE) .checked_sub(stack_size)
.ok_or(StackCreateError::AddressOverflow)?, .ok_or(StackCreateError::AddressOverflow)?,
); );
let guard_page = VirtualAddr::new( let guard_page = VirtualAddr::new(
@@ -53,7 +63,7 @@ impl StackMapping {
let mut mapped_pages = 0; let mut mapped_pages = 0;
while mapped_pages < STACK_PAGES { while mapped_pages < stack_size / FRAME_SIZE {
let virtual_address = VirtualAddr::new( let virtual_address = VirtualAddr::new(
mapped_start mapped_start
.as_usize() .as_usize()
@@ -89,6 +99,7 @@ impl StackMapping {
Ok(Self { Ok(Self {
guard_page, guard_page,
mapped_start, mapped_start,
stack_size,
top, top,
}) })
} }
@@ -115,7 +126,7 @@ impl StackMapping {
/// The caller must ensure this stack is not active on any CPU and cannot be accessed by any /// The caller must ensure this stack is not active on any CPU and cannot be accessed by any
/// kernel operation while it is being destroyed. /// kernel operation while it is being destroyed.
unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) { unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) {
for page in (0..STACK_PAGES).rev() { for page in (0..self.stack_size / FRAME_SIZE).rev() {
let virtual_address = let virtual_address =
VirtualAddr::new(self.mapped_start.as_usize() + page * FRAME_SIZE); VirtualAddr::new(self.mapped_start.as_usize() + page * FRAME_SIZE);
let frame = unsafe { let frame = unsafe {
@@ -130,6 +141,7 @@ impl StackMapping {
} }
} }
#[derive(Debug)]
pub struct KernelStack { pub struct KernelStack {
mapping: StackMapping, mapping: StackMapping,
} }
@@ -137,12 +149,14 @@ pub struct KernelStack {
impl KernelStack { impl KernelStack {
pub fn allocate( pub fn allocate(
address_space: &mut AddressSpace, address_space: &mut AddressSpace,
top: usize,
allocator: &mut FrameAllocator, allocator: &mut FrameAllocator,
) -> Result<Self, StackCreateError> { ) -> Result<Self, StackCreateError> {
let mapping = StackMapping::allocate( let mapping = StackMapping::allocate(
address_space, address_space,
allocator, allocator,
KERNEL_STACK_TOP, KERNEL_STACK_SIZE,
VirtualAddr::new(top),
PagePermissions::new(true, false, false), PagePermissions::new(true, false, false),
)?; )?;
@@ -175,6 +189,7 @@ impl UserStack {
let mapping = StackMapping::allocate( let mapping = StackMapping::allocate(
address_space, address_space,
allocator, allocator,
USER_STACK_SIZE,
top, top,
PagePermissions::new(true, false, true), PagePermissions::new(true, false, true),
)?; )?;
@@ -194,3 +209,41 @@ impl UserStack {
unsafe { self.mapping.destroy(address_space, allocator) }; unsafe { self.mapping.destroy(address_space, allocator) };
} }
} }
pub struct KernelStackPool {
free_slots: u64, // bitmap
}
impl KernelStackPool {
pub const fn new() -> Self {
Self { free_slots: 0 }
}
pub fn allocate(
&mut self,
address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
) -> Result<KernelStack, StackCreateError> {
let mut slot = 0;
while slot < MAX_KERNEL_STACKS {
if self.free_slots & (1 << slot) == 0 {
self.free_slots |= 1 << slot;
let top = KERNEL_STACK_BASE + ((slot + 1) * KERNEL_SLOT_SIZE);
return KernelStack::allocate(address_space, top, allocator);
}
slot += 1;
}
Err(StackCreateError::OutOfStacks)
}
pub fn free(&mut self, stack: KernelStack) {
let slot = (stack.top().as_usize() - KERNEL_STACK_BASE) / KERNEL_SLOT_SIZE - 1;
crate::memory::with_kernel_address_space(|kernel_as| {
crate::memory::with_allocator(|allocator| unsafe {
stack.destroy(kernel_as, allocator)
});
});
self.free_slots &= !(1 << slot);
}
}
+123
View File
@@ -0,0 +1,123 @@
use crate::{
memory::{FRAME_SIZE, VirtualAddr, address_space::AddressSpaceId, with_address_space},
syscall::Status,
};
pub const USER_SPACE_END: VirtualAddr = VirtualAddr::new(0x0000_8000_0000_0000);
pub fn validate_user_range(
as_id: AddressSpaceId,
start: VirtualAddr,
len: usize,
writable: bool,
) -> Result<(), Status> {
let start_addr = start.as_usize();
let end_addr = start_addr.checked_add(len).ok_or(Status::BadAddress)?;
if start_addr >= USER_SPACE_END.as_usize() || end_addr > USER_SPACE_END.as_usize() {
return Err(Status::BadAddress);
}
if len == 0 {
return Ok(());
}
let page_start = start_addr & !(FRAME_SIZE - 1);
for page in (page_start..end_addr).step_by(FRAME_SIZE) {
let is_valid = with_address_space(as_id, |address_space| {
address_space
.mapping(VirtualAddr::new(page))
.is_some_and(|mapping| {
mapping.permissions.user_accessible
&& (!writable || mapping.permissions.writable)
})
})
.ok_or(Status::BadAddress)?;
if !is_valid {
return Err(Status::BadAddress);
}
}
Ok(())
}
/// # Safety
///
/// The caller must ensure that the user address range is valid, mapped, and user-accessible (e.g. via [`validate_user_range`]).
pub unsafe fn copy_from_user(src: VirtualAddr, dst: &mut [u8]) -> Result<(), Status> {
let end = src
.as_usize()
.checked_add(dst.len())
.ok_or(Status::BadAddress)?;
if end > USER_SPACE_END.as_usize() {
return Err(Status::BadAddress);
}
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), dst.len());
}
Ok(())
}
/// # Safety
///
/// The caller must ensure that the user address range is valid, mapped, user-accessible, and writable (e.g. via [`validate_user_range`]).
pub unsafe fn copy_to_user(dst: VirtualAddr, src: &[u8]) -> Result<(), Status> {
let end = dst
.as_usize()
.checked_add(src.len())
.ok_or(Status::BadAddress)?;
if end > USER_SPACE_END.as_usize() {
return Err(Status::BadAddress);
}
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr::<u8>(), src.len());
}
Ok(())
}
/// # Safety
///
/// The caller must ensure that the user address is valid, mapped, user-accessible, and writable (e.g. via [`validate_user_range`]).
pub unsafe fn copy_val_to_user<T: Copy>(dst: VirtualAddr, val: &T) -> Result<(), Status> {
if dst.as_usize() % core::mem::align_of::<T>() != 0 {
return Err(Status::InvalidArgument);
}
let end = dst
.as_usize()
.checked_add(core::mem::size_of::<T>())
.ok_or(Status::BadAddress)?;
if end > USER_SPACE_END.as_usize() {
return Err(Status::BadAddress);
}
unsafe {
(dst.as_mut_ptr::<T>()).write(*val);
}
Ok(())
}
/// # Safety
///
/// The caller must ensure that the user address is valid, mapped, and user-accessible (e.g. via [`validate_user_range`]).
#[allow(unused)]
pub unsafe fn copy_val_from_user<T: Copy>(src: VirtualAddr) -> Result<T, Status> {
if src.as_usize() % core::mem::align_of::<T>() != 0 {
return Err(Status::InvalidArgument);
}
let end = src
.as_usize()
.checked_add(core::mem::size_of::<T>())
.ok_or(Status::BadAddress)?;
if end > USER_SPACE_END.as_usize() {
return Err(Status::BadAddress);
}
let val = unsafe { src.as_ptr::<T>().read() };
Ok(val)
}
+16 -21
View File
@@ -12,7 +12,6 @@ pub enum AcpiError {
MultipleIoApicsUnsupported, MultipleIoApicsUnsupported,
} }
#[derive(Debug)]
pub struct AcpiTables { pub struct AcpiTables {
direct_map: DirectMap, direct_map: DirectMap,
root: RootTable, root: RootTable,
@@ -215,7 +214,6 @@ impl AcpiTables {
} }
} }
#[derive(Debug)]
enum RootTable { enum RootTable {
Rsdt(Sdt), Rsdt(Sdt),
Xsdt(Sdt), Xsdt(Sdt),
@@ -251,7 +249,7 @@ impl RootTable {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
struct Rsdp { struct Rsdp {
signature: [u8; 8], signature: [u8; 8],
checksum: u8, checksum: u8,
@@ -261,7 +259,7 @@ struct Rsdp {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
struct Xsdp { struct Xsdp {
rsdp: Rsdp, rsdp: Rsdp,
length: u32, length: u32,
@@ -271,7 +269,7 @@ struct Xsdp {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
struct SDTHeader { struct SDTHeader {
signature: [u8; 4], signature: [u8; 4],
length: u32, length: u32,
@@ -284,14 +282,12 @@ struct SDTHeader {
creator_revision: u32, creator_revision: u32,
} }
#[derive(Debug)]
pub struct Sdt { pub struct Sdt {
physical_addr: PhysicalAddr, physical_addr: PhysicalAddr,
length: usize, length: usize,
signature: [u8; 4], signature: [u8; 4],
} }
#[derive(Debug)]
#[allow(unused)] #[allow(unused)]
pub struct Madt<'a> { pub struct Madt<'a> {
acpi: &'a AcpiTables, acpi: &'a AcpiTables,
@@ -301,7 +297,7 @@ pub struct Madt<'a> {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
struct MadtBody { struct MadtBody {
local_apic_address: u32, local_apic_address: u32,
flags: u32, flags: u32,
@@ -543,21 +539,20 @@ impl<'a> Iterator for MadtEntries<'a> {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct MadtEntryHeader { pub struct MadtEntryHeader {
kind: u8, kind: u8,
length: u8, length: u8,
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct LocalApicEntry { pub struct LocalApicEntry {
processor_id: u8, processor_id: u8,
id: u8, id: u8,
flags: u32, flags: u32,
} }
#[derive(Debug)]
pub struct IoApicInfo { pub struct IoApicInfo {
pub id: u8, pub id: u8,
pub apic_address: PhysicalAddr, pub apic_address: PhysicalAddr,
@@ -565,7 +560,7 @@ pub struct IoApicInfo {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct IoApicEntry { pub struct IoApicEntry {
id: u8, id: u8,
reserved: u8, reserved: u8,
@@ -574,7 +569,7 @@ pub struct IoApicEntry {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct InterruptSourceOverride { pub struct InterruptSourceOverride {
bus: u8, bus: u8,
source: u8, source: u8,
@@ -582,19 +577,19 @@ pub struct InterruptSourceOverride {
flags: u16, flags: u16,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub enum InterruptPolarity { pub enum InterruptPolarity {
ActiveHigh, ActiveHigh,
ActiveLow, ActiveLow,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub enum TriggerMode { pub enum TriggerMode {
Edge, Edge,
Level, Level,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct IsaIrqRoute { pub struct IsaIrqRoute {
pub gsi: u32, pub gsi: u32,
pub polarity: InterruptPolarity, pub polarity: InterruptPolarity,
@@ -602,7 +597,7 @@ pub struct IsaIrqRoute {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct IoApicNmiEntry { pub struct IoApicNmiEntry {
nmi_source: u8, nmi_source: u8,
reserved: u8, reserved: u8,
@@ -611,7 +606,7 @@ pub struct IoApicNmiEntry {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct LocalApicNmiEntry { pub struct LocalApicNmiEntry {
processor_id: u8, processor_id: u8,
flags: u16, flags: u16,
@@ -619,14 +614,14 @@ pub struct LocalApicNmiEntry {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct LocalApicAddressOverride { pub struct LocalApicAddressOverride {
reserved: u16, reserved: u16,
local_apic_address: u64, local_apic_address: u64,
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct LocalX2ApicEntry { pub struct LocalX2ApicEntry {
reserved: u16, reserved: u16,
local_x2apic_id: u32, local_x2apic_id: u32,
@@ -634,7 +629,7 @@ pub struct LocalX2ApicEntry {
acpi_processor_uid: u32, acpi_processor_uid: u32,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
#[allow(unused)] #[allow(unused)]
pub enum MadtEntry { pub enum MadtEntry {
LocalApic(LocalApicEntry), LocalApic(LocalApicEntry),
+92
View File
@@ -0,0 +1,92 @@
mod table;
use table::*;
use crate::task::tcb::{ExitReason, Fault};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u64)]
pub enum Status {
// Status::Success = 0
InvalidArgument = 1, // EINVAL
BadAddress = 2, // EFAULT
BadFileDescriptor = 3, // EBADF
NoSuchTask = 4, // ESRCH
OutOfMemory = 5, // ENOMEM
BadHandle = 6, // EBADH
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u64)]
pub enum SyscallNumber {
Yield = 1,
Exit = 2,
Write = 3,
Send = 4,
Recv = 5,
FrameAlloc = 6,
FrameDealloc = 7,
AsCreate = 8,
Map = 9,
Unmap = 10,
TaskCreate = 11,
}
impl TryFrom<u64> for SyscallNumber {
type Error = ();
fn try_from(val: u64) -> Result<Self, Self::Error> {
match val {
1 => Ok(Self::Yield),
2 => Ok(Self::Exit),
3 => Ok(Self::Write),
4 => Ok(Self::Send),
5 => Ok(Self::Recv),
6 => Ok(Self::FrameAlloc),
7 => Ok(Self::FrameDealloc),
8 => Ok(Self::AsCreate),
9 => Ok(Self::Map),
10 => Ok(Self::Unmap),
11 => Ok(Self::TaskCreate),
_ => Err(()),
}
}
}
pub fn handle(num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, _arg5: u64) -> u64 {
let result = (|| -> Result<(), Status> {
let syscall = SyscallNumber::try_from(num).unwrap_or_else(|_| {
crate::task::scheduler::exit_current(ExitReason::Fault(Fault::BadSystemCall))
});
match syscall {
SyscallNumber::Yield => sys_yield(),
SyscallNumber::Exit => sys_exit(arg0 as usize),
SyscallNumber::Write => {
sys_write(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize)
}
SyscallNumber::Send => sys_send(arg0 as usize, arg1 as usize, arg2 as usize),
SyscallNumber::Recv => {
sys_recv(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize)
}
SyscallNumber::FrameAlloc => sys_frame_alloc(arg0 as usize),
SyscallNumber::FrameDealloc => sys_frame_dealloc(arg0 as usize),
SyscallNumber::AsCreate => sys_as_create(arg0 as usize),
SyscallNumber::Map => sys_map(
arg0 as usize,
arg1 as usize,
arg2 as usize,
arg3 as usize,
arg4 as usize,
),
SyscallNumber::Unmap => sys_unmap(arg0 as usize),
SyscallNumber::TaskCreate => {
sys_task_create(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize)
}
}
})();
match result {
Ok(()) => 0,
Err(err) => err as u64,
}
}
+647
View File
@@ -0,0 +1,647 @@
use crate::{
memory::{
FRAME_SIZE, MapError, PagePermissions, USER_SPACE_END, VirtualAddr, copy_from_user,
copy_to_user, copy_val_to_user, validate_user_range,
},
println,
task::{
scheduler::TaskId,
tcb::{BlockReason, ExitReason, Handle, KernelObject, MAX_MSG_SIZE, Message, Rights},
},
};
use super::Status;
pub fn sys_yield() -> Result<(), Status> {
crate::task::scheduler::yield_current();
Ok(())
}
pub fn sys_exit(exit_code: usize) -> ! {
crate::task::scheduler::exit_current(ExitReason::Exited(exit_code));
}
pub fn sys_write(fd: usize, buf_ptr: usize, len: usize, out_ptr: usize) -> Result<(), Status> {
if fd != 1 && fd != 2 {
return Err(Status::BadFileDescriptor);
}
crate::task::scheduler::with_task(crate::task::scheduler::current(), |current_task| {
validate_user_range(current_task.as_id, VirtualAddr::new(buf_ptr), len, false)?;
if out_ptr != 0 {
if out_ptr % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
validate_user_range(
current_task.as_id,
VirtualAddr::new(out_ptr),
core::mem::size_of::<usize>(),
true,
)?;
}
Ok(())
})
.expect("failed to resolve self task")?;
let mut chunk = [0u8; 128];
let mut written = 0;
while written < len {
let n = (len - written).min(chunk.len());
unsafe {
copy_from_user(VirtualAddr::new(buf_ptr + written), &mut chunk[..n])?;
}
crate::debug::serial::write_bytes(&chunk[..n]);
written += n;
}
if out_ptr != 0 {
unsafe {
copy_val_to_user(VirtualAddr::new(out_ptr), &written)?;
}
}
Ok(())
}
pub fn sys_send(dest_task_id: usize, msg_ptr: usize, len: usize) -> Result<(), Status> {
let dest_task_id = TaskId::new(dest_task_id);
if len > MAX_MSG_SIZE {
return Err(Status::InvalidArgument);
}
let sender = crate::task::scheduler::current();
crate::task::scheduler::with_task(sender, |current_task| {
validate_user_range(current_task.as_id, VirtualAddr::new(msg_ptr), len, false)
})
.expect("failed to resolve self task")?;
let mut msg_buf = [0u8; MAX_MSG_SIZE];
unsafe { copy_from_user(VirtualAddr::new(msg_ptr), &mut msg_buf[..len])? };
let msg = Message {
sender,
length: len,
data: msg_buf,
};
let should_unblock = crate::task::scheduler::with_task_mut(dest_task_id, |dest_task| {
if !dest_task.mailbox.push(msg) {
return Err(Status::OutOfMemory);
}
Ok(matches!(
dest_task.state,
crate::task::tcb::ThreadState::Blocked(BlockReason::Recv)
))
})
.ok_or(Status::NoSuchTask)??;
if should_unblock {
crate::task::scheduler::unblock(dest_task_id);
}
Ok(())
}
pub fn sys_recv(
out_ptr: usize,
max_len: usize,
out_actual_len: usize,
out_sender: usize,
) -> Result<(), Status> {
if out_ptr == 0 {
return Err(Status::InvalidArgument);
}
crate::task::scheduler::with_task(crate::task::scheduler::current(), |current_task| {
if current_task.mailbox.len == 0 {
crate::task::scheduler::block_current(BlockReason::Recv);
}
validate_user_range(current_task.as_id, VirtualAddr::new(out_ptr), max_len, true)?;
if out_actual_len != 0 {
if out_actual_len % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
validate_user_range(
current_task.as_id,
VirtualAddr::new(out_actual_len),
core::mem::size_of::<usize>(),
true,
)?;
}
if out_sender != 0 {
if out_sender % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
validate_user_range(
current_task.as_id,
VirtualAddr::new(out_sender),
core::mem::size_of::<usize>(),
true,
)?;
}
Ok(())
})
.expect("failed to resolve self task")?;
let msg =
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |current_task| {
current_task.mailbox.pop().ok_or(Status::NoSuchTask)
})
.expect("failed to resolve self task")?;
unsafe {
copy_to_user(
VirtualAddr::new(out_ptr),
&msg.data[..msg.length.min(max_len)],
)?;
if out_actual_len != 0 {
copy_val_to_user(VirtualAddr::new(out_actual_len), &msg.length)?;
}
if out_sender != 0 {
copy_val_to_user(VirtualAddr::new(out_sender), &msg.sender)?;
}
}
Ok(())
}
pub fn sys_frame_alloc(out_handle: usize) -> Result<(), Status> {
if out_handle == 0 {
return Err(Status::InvalidArgument);
}
if out_handle % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
crate::task::scheduler::with_task(crate::task::scheduler::current(), |current_task| {
validate_user_range(
current_task.as_id,
VirtualAddr::new(out_handle),
core::mem::size_of::<usize>(),
true,
)
})
.expect("failed to resolve self task")?;
let frame = crate::memory::alloc_frame().ok_or(Status::OutOfMemory)?;
let handle = Handle {
object: KernelObject::Frame(frame),
rights: Rights::READ | Rights::WRITE | Rights::EXECUTE | Rights::MAP,
};
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |current_task| {
let handle_id = match current_task.handles.push(handle) {
Ok(id) => id,
Err(handle) => {
let frame = match handle.object {
KernelObject::Frame(frame) => frame,
_ => unreachable!("pushed handle was not a frame"),
};
unsafe { crate::memory::dealloc_frame(frame) };
return Err(Status::OutOfMemory);
}
};
unsafe { copy_val_to_user(VirtualAddr::new(out_handle), &handle_id) }
})
.expect("failed to resolve self task")
}
pub fn sys_frame_dealloc(frame_handle_id: usize) -> Result<(), Status> {
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |task| {
let frame_handle = task
.handles
.take(frame_handle_id)
.ok_or(Status::BadHandle)?;
match frame_handle.object {
KernelObject::Frame(frame_addr) => {
unsafe { crate::memory::dealloc_frame(frame_addr) };
Ok(())
}
_ => {
// Wrong-type operations must not consume the handle
match task.handles.put(frame_handle_id, frame_handle) {
Ok(_) => {}
Err(_) => panic!("taken handle was unexpectedly occupied"),
}
return Err(Status::InvalidArgument);
}
}
})
.expect("failed to resolve self task")
}
pub fn sys_as_create(out_handle: usize) -> Result<(), Status> {
if out_handle == 0 {
return Err(Status::InvalidArgument);
}
if out_handle % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
let new_as =
crate::task::scheduler::with_task(crate::task::scheduler::current(), |current_task| {
validate_user_range(
current_task.as_id,
VirtualAddr::new(out_handle),
core::mem::size_of::<usize>(),
true,
)?;
let new_as = match crate::memory::with_address_space(current_task.as_id, |caller_as| {
crate::memory::with_allocator(|allocator| caller_as.new_user(allocator))
}) {
Some(Ok(as_space)) => as_space,
_ => return Err(Status::OutOfMemory),
};
Ok(new_as)
})
.expect("failed to resolve self task")?;
let as_id = match crate::memory::insert_address_space(new_as) {
Ok(id) => id,
Err(addr_space) => {
crate::memory::with_allocator(|allocator| unsafe { addr_space.destroy(allocator) });
return Err(Status::OutOfMemory);
}
};
let handle = Handle {
object: KernelObject::AddressSpace(as_id),
rights: Rights::READ | Rights::WRITE | Rights::EXECUTE,
};
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |current_task| {
let handle_id = match current_task.handles.push(handle) {
Ok(id) => id,
Err(handle) => {
let address_space = match handle.object {
KernelObject::AddressSpace(as_id) => crate::memory::remove_address_space(as_id)
.expect("address space was just inserted"),
_ => unreachable!("pushed handle was not an address space"),
};
crate::memory::with_allocator(|allocator| unsafe {
address_space.destroy(allocator)
});
return Err(Status::OutOfMemory);
}
};
unsafe { copy_val_to_user(VirtualAddr::new(out_handle), &handle_id) }
})
.expect("failed to resolve self task")
}
pub fn sys_map(
as_handle: usize,
frame_handle: usize,
virtual_addr: usize,
permissions: usize,
out_handle: usize,
) -> Result<(), Status> {
if out_handle == 0 || out_handle % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
if virtual_addr % FRAME_SIZE != 0 || permissions & !0b11 != 0 {
return Err(Status::InvalidArgument);
}
let writable = permissions & (1 << 0) != 0;
let executable = permissions & (1 << 1) != 0;
let end = virtual_addr
.checked_add(FRAME_SIZE)
.ok_or(Status::InvalidArgument)?;
if end > USER_SPACE_END.as_usize() {
return Err(Status::InvalidArgument);
}
let current_task = crate::task::scheduler::current();
let as_id = crate::task::scheduler::with_task(current_task, |task| {
validate_user_range(
task.as_id,
VirtualAddr::new(out_handle),
core::mem::size_of::<usize>(),
true,
)?;
let as_handle = task.handles.get(as_handle).ok_or(Status::BadHandle)?;
let as_id = match as_handle.object {
KernelObject::AddressSpace(as_id) => as_id,
_ => return Err(Status::InvalidArgument),
};
if as_handle.rights.0 & Rights::WRITE.0 == 0 {
return Err(Status::InvalidArgument);
}
let frame_handle = task.handles.get(frame_handle).ok_or(Status::BadHandle)?;
if !matches!(frame_handle.object, KernelObject::Frame(_)) {
return Err(Status::InvalidArgument);
}
let mut required_rights = Rights::READ | Rights::MAP;
if writable {
required_rights = required_rights | Rights::WRITE;
}
if executable {
required_rights = required_rights | Rights::EXECUTE;
}
if frame_handle.rights.0 & required_rights.0 != required_rights.0 {
return Err(Status::InvalidArgument);
}
Ok(as_id)
})
.expect("failed to resolve self task")?;
let handle = crate::task::scheduler::with_task_mut(current_task, |task| {
task.handles.take(frame_handle).ok_or(Status::BadHandle)
})
.expect("failed to resolve self task")?;
let Handle { object, rights } = handle;
let KernelObject::Frame(frame) = object else {
panic!("validated frame handle changed before it was taken");
};
let permissions = PagePermissions::new(writable, executable, true);
let virtual_addr = VirtualAddr::new(virtual_addr);
let map_result = crate::memory::with_address_space_mut(as_id, |target_as| {
crate::memory::with_allocator(|allocator| {
target_as.map(
frame.frame_address().start_address(),
virtual_addr,
permissions,
allocator,
crate::memory::CachePolicy::WriteBack,
)
})
})
.expect("failed to resolve self address space");
match map_result {
Ok(_) => {
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |task| {
match task.handles.put(
frame_handle,
Handle {
object: KernelObject::Mapping {
frame,
address_space: as_id,
virtual_addr,
},
rights,
},
) {
Ok(_) => {}
Err(_) => panic!("taken handle was unexpectedly occupied"),
}
});
unsafe {
copy_val_to_user(VirtualAddr::new(out_handle), &frame_handle)
.expect("out_handle has already been checked")
}
Ok(())
}
Err(err) => {
crate::task::scheduler::with_task_mut(current_task, |task| {
match task.handles.put(
frame_handle,
Handle {
object: KernelObject::Frame(frame),
rights,
},
) {
Ok(_) => {}
Err(_) => panic!("taken handle was unexpectedly occupied"),
}
})
.expect("failed to resolve self task");
match err {
MapError::AlreadyMapped | MapError::UnsupportedPermissions => {
Err(Status::InvalidArgument)
}
MapError::OutOfMemory => Err(Status::OutOfMemory),
MapError::InvalidVirtualAddress
| MapError::VirtualAddressUnaligned
| MapError::PhysicalAddressTooLarge
| MapError::PhysicalAddressUnaligned
| MapError::RangeLengthUnaligned
| MapError::AddressOverflow
| MapError::MappingConflict
| MapError::PageTableUnavailable
| MapError::CorruptedPageTable
| MapError::InvalidUserAddress
| MapError::InvalidUserMap => {
panic!("validated user mapping failed with an impossible error: {err:?}")
}
}
}
}
}
pub fn sys_unmap(mapping_handle: usize) -> Result<(), Status> {
let handle = crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |task| {
task.handles.take(mapping_handle).ok_or(Status::BadHandle)
})
.expect("failed to resolve self task")?;
let Handle { object, rights } = handle;
let KernelObject::Mapping {
frame,
address_space,
virtual_addr,
} = object
else {
// Wrong-type operations must not consume the handle
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |task| match task
.handles
.put(mapping_handle, Handle { object, rights })
{
Ok(_) => {}
Err(_) => panic!("taken handle was unexpectedly occupied"),
})
.expect("failed to resolve self task");
return Err(Status::InvalidArgument);
};
let unmap_result = crate::memory::with_address_space_mut(address_space, |target_as| {
crate::memory::with_allocator(|allocator| unsafe {
target_as.unmap(virtual_addr, allocator)
})
});
match unmap_result {
Some(Ok(unmapped_frame)) => {
if unmapped_frame == frame.frame_address() {
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |task| {
match task.handles.put(
mapping_handle,
Handle {
object: KernelObject::Frame(frame),
rights,
},
) {
Ok(_) => {}
Err(_) => panic!("taken handle was unexpectedly occupied"),
}
});
Ok(())
} else {
panic!("unmap resulted in a frame that was not the one we expected")
}
}
Some(Err(err)) => {
// every unmapping error should be impossible to occur
panic!("failed to unmap: {err:?}");
}
None => {
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |task| {
match task.handles.put(
mapping_handle,
Handle {
object: KernelObject::Mapping {
frame: frame,
address_space,
virtual_addr,
},
rights,
},
) {
Ok(_) => {}
Err(_) => panic!("taken handle was unexpectedly occupied"),
}
});
Err(Status::BadHandle)
}
}
}
pub fn sys_task_create(
as_handle: usize,
entry: usize,
user_stack: usize,
out_task_handle: usize,
) -> Result<(), Status> {
if entry == 0 || user_stack == 0 || out_task_handle == 0 {
return Err(Status::InvalidArgument);
}
if entry >= USER_SPACE_END.as_usize() || user_stack > USER_SPACE_END.as_usize() {
return Err(Status::BadAddress);
}
if out_task_handle % core::mem::align_of::<usize>() != 0 {
return Err(Status::InvalidArgument);
}
let as_id =
crate::task::scheduler::with_task(crate::task::scheduler::current(), |current_task| {
validate_user_range(
current_task.as_id,
VirtualAddr::new(out_task_handle),
core::mem::size_of::<usize>(),
true,
)?;
let as_handle = current_task
.handles
.get(as_handle)
.ok_or(Status::BadHandle)?;
let as_id = match as_handle.object {
KernelObject::AddressSpace(as_id) => as_id,
_ => return Err(Status::InvalidArgument),
};
if as_handle.rights.0 & Rights::EXECUTE.0 == 0 {
return Err(Status::InvalidArgument);
}
Ok(as_id)
})
.expect("failed to resolve self task")?;
if crate::memory::with_address_space(as_id, |_| ()).is_none() {
return Err(Status::BadHandle);
}
let stack_probe = user_stack.checked_sub(1).ok_or(Status::BadAddress)?;
validate_user_range(as_id, VirtualAddr::new(stack_probe), 1, true)?;
let entry_is_valid = crate::memory::with_address_space(as_id, |address_space| {
address_space
.mapping(VirtualAddr::new(entry))
.is_some_and(|mapping| {
mapping.permissions.user_accessible && mapping.permissions.executable
})
})
.ok_or(Status::BadHandle)?;
if !entry_is_valid {
return Err(Status::BadAddress);
}
let kernel_stack = match crate::memory::with_kernel_address_space(|kernel_as| {
crate::memory::with_allocator(|allocator| {
crate::task::scheduler::allocate_kernel_stack(kernel_as, allocator)
})
}) {
Ok(stack) => stack,
Err(err) => {
println!("Failed to allocate kernel stack: {:?}", err);
return Err(Status::OutOfMemory);
}
};
let new_tcb = crate::task::tcb::Tcb::new_user(
as_id,
kernel_stack,
VirtualAddr::new(entry),
VirtualAddr::new(user_stack),
);
let new_task_id = match crate::task::scheduler::add_task(new_tcb) {
Ok(id) => id,
Err(_) => return Err(Status::OutOfMemory),
};
let handle = Handle {
object: KernelObject::Thread(new_task_id),
rights: Rights::READ | Rights::WRITE | Rights::EXECUTE,
};
crate::task::scheduler::with_task_mut(crate::task::scheduler::current(), |current_task| {
let handle_id = match current_task.handles.push(handle) {
Ok(id) => id,
Err(_) => {
crate::task::scheduler::remove_task(new_task_id);
return Err(Status::OutOfMemory);
}
};
unsafe { copy_val_to_user(VirtualAddr::new(out_task_handle), &handle_id) }
})
.expect("failed to resolve self task")
}
+140
View File
@@ -0,0 +1,140 @@
use crate::{
format,
memory::{
self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, InitramfsImage, PagePermissions,
UserStack, VirtualAddr,
},
task::{scheduler::TaskId, tcb::Tcb},
};
pub fn spawn(
name: &str,
initramfs: &InitramfsImage,
kernel_as: &mut AddressSpace,
allocator: &mut FrameAllocator,
direct_map: DirectMap,
) -> TaskId {
let bytes = format::cpio::find_file(initramfs.data(), name)
.unwrap_or_else(|| panic!("{name} missing from initramfs"));
let kernel_stack = crate::task::scheduler::allocate_kernel_stack(kernel_as, allocator)
.expect("kernel stack allocation failed");
let initramfs_physical_addr = kernel_as
.to_physical(initramfs.start)
.expect("failed to translate initramfs start address");
let mut address_space = kernel_as
.new_user(allocator)
.expect("address space allocation failed");
address_space
.map_range(
initramfs_physical_addr,
VirtualAddr::new(0x4000_0000),
((initramfs.length) + 0xFFF) & !0xFFF,
PagePermissions::new(true, false, true),
allocator,
memory::CachePolicy::WriteBack,
)
.expect("failed to map initramfs");
let user_stack =
UserStack::allocate(&mut address_space, allocator).expect("user stack allocation failed");
let entry = load_elf(bytes, &mut address_space, allocator, direct_map).expect("invalid ELF");
let as_id = match crate::memory::insert_address_space(address_space) {
Ok(id) => id,
Err(_) => {
panic!("address space table is full");
}
};
let task = Tcb::new_user(as_id, kernel_stack, entry, user_stack.top());
crate::task::scheduler::add_task(task).expect("scheduler is full")
}
#[derive(Debug)]
enum ElfLoadError {
AddressTranslationFailed,
FailedToMapSegment,
OutOfMemory,
InvalidElf,
}
fn load_elf(
bytes: &[u8],
user_address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
direct_map: DirectMap,
) -> Result<VirtualAddr, ElfLoadError> {
let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?;
let mut executable_entry = false;
for segment in program.segments() {
let segment = segment.map_err(|_| ElfLoadError::InvalidElf)?;
let end = segment
.address
.checked_add(segment.memory_size)
.filter(|&end| end <= memory::USER_SPACE_END.as_usize())
.ok_or(ElfLoadError::InvalidElf)?;
if segment.memory_size == 0 {
continue;
}
executable_entry |= segment.executable && (segment.address..end).contains(&program.entry);
let page_start = segment.address & !(FRAME_SIZE - 1);
let file_end = segment.address + segment.data.len();
let permissions = PagePermissions::new(segment.writable, segment.executable, true);
// Overlapping segment pages are rejected by map(), including stack/archive collisions.
for page in (page_start..end).step_by(FRAME_SIZE) {
let frame = allocator.alloc_nozero().ok_or(ElfLoadError::OutOfMemory)?;
let physical = frame.frame_address().start_address();
let Some(destination) = direct_map.translate(physical) else {
unsafe { allocator.dealloc(frame) };
return Err(ElfLoadError::AddressTranslationFailed);
};
let copy_start = page.max(segment.address).min(page + FRAME_SIZE);
let copy_end = (page + FRAME_SIZE).min(file_end).max(copy_start);
let prefix = copy_start - page;
let copied = copy_end - copy_start;
unsafe {
let destination = destination.as_mut_ptr::<u8>();
// Initialize padding and BSS, but don't zero bytes we're about to overwrite.
core::ptr::write_bytes(destination, 0, prefix);
if copied != 0 {
core::ptr::copy_nonoverlapping(
segment.data.as_ptr().add(copy_start - segment.address),
destination.add(prefix),
copied,
);
}
core::ptr::write_bytes(
destination.add(prefix + copied),
0,
FRAME_SIZE - prefix - copied,
);
}
if user_address_space
.map(
physical,
VirtualAddr::new(page),
permissions,
allocator,
memory::CachePolicy::WriteBack,
)
.is_err()
{
unsafe { allocator.dealloc(frame) };
return Err(ElfLoadError::FailedToMapSegment);
}
let _ = frame.into_raw();
}
}
if !executable_entry {
return Err(ElfLoadError::InvalidElf);
}
Ok(VirtualAddr::new(program.entry))
}
+3
View File
@@ -0,0 +1,3 @@
pub mod bootstrap;
pub mod scheduler;
pub mod tcb;
+372
View File
@@ -0,0 +1,372 @@
use core::cell::UnsafeCell;
use crate::{
arch::ThreadContext,
memory::{
AddressSpace, AddressSpaceId, FrameAllocator, KernelStack, KernelStackPool,
StackCreateError, VirtualAddr,
},
println,
task::tcb::{BlockReason, ExitReason, Handle, KernelObject, Rights, Tcb, ThreadState},
};
const MAX_TASKS: usize = 32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct TaskId(usize);
impl TaskId {
pub const fn new(id: usize) -> Self {
Self(id)
}
}
struct Scheduler {
current: Option<TaskId>,
tasks: [Option<Tcb>; MAX_TASKS],
ready: ReadyQueue,
stacks: KernelStackPool,
}
impl Scheduler {
const fn new() -> Self {
Self {
current: None,
tasks: [const { None }; MAX_TASKS],
ready: ReadyQueue::new(),
stacks: KernelStackPool::new(),
}
}
fn make_switch(&mut self, current_id: TaskId, next_id: TaskId) -> Switch {
assert_ne!(current_id, next_id);
let current = self.tasks[current_id.0].as_mut().unwrap();
let prev_ctx = &mut current.context as *mut ThreadContext;
let prev_as_id = current.as_id;
let next = self.tasks[next_id.0].as_ref().unwrap();
let next_ctx = &next.context as *const ThreadContext;
let next_as_id = next.as_id;
let next_kernel_stack = next.kernel_stack.top();
Switch {
previous_context: prev_ctx,
next_context: next_ctx,
next_as_id,
next_kernel_stack,
activate_address_space: next_as_id != prev_as_id,
}
}
}
struct Switch {
previous_context: *mut ThreadContext,
next_context: *const ThreadContext,
next_as_id: AddressSpaceId,
next_kernel_stack: VirtualAddr,
activate_address_space: bool,
}
impl Switch {
unsafe fn perform(self) {
if self.activate_address_space {
crate::memory::with_address_space(self.next_as_id, |as_ref| unsafe {
as_ref.activate();
});
}
crate::arch::set_kernel_stack(self.next_kernel_stack);
unsafe {
crate::arch::switch_context(self.previous_context, self.next_context);
}
}
}
struct ReadyQueue {
entries: [TaskId; MAX_TASKS],
head: usize,
len: usize,
}
impl ReadyQueue {
pub const fn new() -> Self {
Self {
entries: [TaskId(0); MAX_TASKS],
head: 0,
len: 0,
}
}
pub fn push_back(&mut self, task: TaskId) -> bool {
if self.len == MAX_TASKS {
return false;
}
let tail = (self.head + self.len) % MAX_TASKS;
self.entries[tail] = task;
self.len += 1;
true
}
pub fn pop_front(&mut self) -> Option<TaskId> {
if self.len == 0 {
return None;
}
let task = self.entries[self.head];
self.head = (self.head + 1) % MAX_TASKS;
self.len -= 1;
Some(task)
}
pub fn remove(&mut self, task: TaskId) -> bool {
for i in 0..self.len {
let idx = (self.head + i) % MAX_TASKS;
if self.entries[idx] == task {
for j in i..(self.len - 1) {
let from = (self.head + j + 1) % MAX_TASKS;
let to = (self.head + j) % MAX_TASKS;
self.entries[to] = self.entries[from];
}
self.len -= 1;
return true;
}
}
false
}
}
struct GlobalScheduler(UnsafeCell<Scheduler>);
unsafe impl Sync for GlobalScheduler {}
static SCHEDULER: GlobalScheduler = GlobalScheduler(UnsafeCell::new(Scheduler::new()));
pub fn add_task(mut task: Tcb) -> Result<TaskId, Tcb> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let result = {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
match scheduler.tasks.iter().position(Option::is_none) {
Some(id) => {
let id = TaskId(id);
task.id = id;
match task.handles.push(Handle {
object: KernelObject::Thread(id),
rights: Rights::READ | Rights::WRITE | Rights::EXECUTE,
}) {
Ok(_) => {}
Err(_) => unreachable!("Cant push root thread handle"),
}
task.state = ThreadState::Ready;
scheduler.tasks[id.0] = Some(task);
assert!(scheduler.ready.push_back(id));
Ok(id)
}
None => Err(task),
}
};
crate::arch::restore_interrupts(interrupt_state);
result
}
pub fn start() -> ! {
crate::arch::disable_interrupts();
let mut bootstrap_context = ThreadContext::empty();
let switch = {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
let next_id = scheduler.ready.pop_front().expect("no tasks to run");
let next = scheduler.tasks[next_id.0]
.as_mut()
.expect("ready task is missing");
next.state = ThreadState::Running;
scheduler.current = Some(next_id);
Switch {
previous_context: &mut bootstrap_context,
next_context: &next.context,
next_as_id: next.as_id,
next_kernel_stack: next.kernel_stack.top(),
activate_address_space: true,
}
};
unsafe {
switch.perform();
}
panic!("scheduler returned to bootstrap context");
}
pub fn allocate_kernel_stack(
address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
) -> Result<KernelStack, StackCreateError> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
let result = scheduler.stacks.allocate(address_space, allocator);
crate::arch::restore_interrupts(interrupt_state);
result
}
pub fn remove_task(id: TaskId) -> bool {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let result = {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
if scheduler.current == Some(id) {
false
} else if let Some(task) = scheduler.tasks.get_mut(id.0).and_then(Option::take) {
scheduler.ready.remove(id);
scheduler.stacks.free(task.kernel_stack);
true
} else {
false
}
};
crate::arch::restore_interrupts(interrupt_state);
result
}
pub fn with_task<R>(id: TaskId, f: impl FnOnce(&Tcb) -> R) -> Option<R> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let scheduler = unsafe { &*SCHEDULER.0.get() };
let res = scheduler.tasks.get(id.0).and_then(Option::as_ref).map(f);
crate::arch::restore_interrupts(interrupt_state);
res
}
pub fn with_task_mut<R>(id: TaskId, f: impl FnOnce(&mut Tcb) -> R) -> Option<R> {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
let res = scheduler
.tasks
.get_mut(id.0)
.and_then(Option::as_mut)
.map(f);
crate::arch::restore_interrupts(interrupt_state);
res
}
pub fn current() -> TaskId {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
scheduler.current.expect("no current task")
}
pub fn block_current(reason: BlockReason) {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let switch = {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
let Some(next_id) = scheduler.ready.pop_front() else {
println!("Deadlock: all tasks blocked");
crate::hcf();
};
let current_id = scheduler.current.expect("no current task");
scheduler.tasks[current_id.0].as_mut().unwrap().state = ThreadState::Blocked(reason);
// explicitly do NOT push back the current task, because it is not ready
scheduler.tasks[next_id.0].as_mut().unwrap().state = ThreadState::Running;
scheduler.current = Some(next_id);
scheduler.make_switch(current_id, next_id)
};
unsafe {
switch.perform();
}
// this runs when this task is selected to run again
crate::arch::restore_interrupts(interrupt_state);
}
pub fn unblock(id: TaskId) {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
if let Some(task) = scheduler.tasks[id.0].as_mut() {
if matches!(task.state, ThreadState::Blocked(_)) {
task.state = ThreadState::Ready;
assert!(scheduler.ready.push_back(id));
}
}
crate::arch::restore_interrupts(interrupt_state);
}
pub fn yield_current() {
let interrupt_state = crate::arch::disable_interrupts_and_save();
let switch = {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
let Some(next_id) = scheduler.ready.pop_front() else {
crate::arch::restore_interrupts(interrupt_state);
return;
};
let current_id = scheduler.current.expect("no current task");
scheduler.tasks[current_id.0].as_mut().unwrap().state = ThreadState::Ready;
assert!(scheduler.ready.push_back(current_id));
scheduler.tasks[next_id.0].as_mut().unwrap().state = ThreadState::Running;
scheduler.current = Some(next_id);
scheduler.make_switch(current_id, next_id)
};
unsafe {
switch.perform();
}
// this runs when this task is selected to run again
crate::arch::restore_interrupts(interrupt_state);
}
pub fn exit_current(reason: ExitReason) -> ! {
crate::arch::disable_interrupts();
let switch = {
let scheduler = unsafe { &mut *SCHEDULER.0.get() };
let current_id = scheduler.current.expect("no current task");
let Some(next_id) = scheduler.ready.pop_front() else {
println!("All tasks exited");
crate::hcf();
};
let current = scheduler.tasks[current_id.0].as_mut().unwrap();
current.state = ThreadState::Dead(reason);
scheduler.tasks[next_id.0].as_mut().unwrap().state = ThreadState::Running;
scheduler.current = Some(next_id);
scheduler.make_switch(current_id, next_id)
};
unsafe {
switch.perform();
}
panic!("dead task was scheduled again");
}
+222
View File
@@ -0,0 +1,222 @@
use core::ops::BitOr;
use crate::{
arch::ThreadContext,
memory::{AddressSpaceId, KernelStack, OwnedFrame, VirtualAddr},
task::scheduler::TaskId,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Fault {
SegmentationFault,
IllegalInstruction,
Abort,
BadSystemCall,
}
// Thread Control Block
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExitReason {
Exited(usize),
Killed,
Fault(Fault),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BlockReason {
Recv,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ThreadState {
Ready,
Running,
Blocked(BlockReason),
Dead(ExitReason),
}
pub const MAX_MSG_SIZE: usize = 128;
pub const MAILBOX_CAPACITY: usize = 4;
#[derive(Clone, Copy)]
pub struct Message {
pub sender: TaskId,
pub length: usize,
pub data: [u8; MAX_MSG_SIZE],
}
pub struct Mailbox {
pub messages: [Option<Message>; MAILBOX_CAPACITY],
pub head: usize,
pub len: usize,
}
impl Mailbox {
const fn new() -> Self {
Self {
messages: [None; MAILBOX_CAPACITY],
head: 0,
len: 0,
}
}
pub fn pop(&mut self) -> Option<Message> {
if self.len == 0 {
return None;
}
let msg = self.messages[self.head];
self.head = (self.head + 1) % MAILBOX_CAPACITY;
self.len -= 1;
msg
}
pub fn push(&mut self, msg: Message) -> bool {
if self.len == MAILBOX_CAPACITY {
return false;
}
let tail = (self.head + self.len) % MAILBOX_CAPACITY;
self.messages[tail] = Some(msg);
self.len += 1;
true
}
}
const MAX_HANDLES: usize = 32;
pub enum KernelObject {
AddressSpace(AddressSpaceId),
Frame(OwnedFrame),
Mapping {
frame: OwnedFrame,
address_space: AddressSpaceId,
virtual_addr: VirtualAddr,
},
Thread(TaskId),
}
pub struct Handle {
pub object: KernelObject,
pub rights: Rights,
}
#[derive(Clone, Copy)]
pub struct Rights(pub u32);
impl Rights {
pub const READ: Self = Self(1 << 0);
pub const WRITE: Self = Self(1 << 1);
pub const EXECUTE: Self = Self(1 << 2);
pub const MAP: Self = Self(1 << 3);
}
impl BitOr for Rights {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
pub struct HandleTable {
handles: [Option<Handle>; MAX_HANDLES],
}
impl HandleTable {
pub const fn new() -> Self {
Self {
handles: [const { None }; MAX_HANDLES],
}
}
pub fn push(&mut self, handle: Handle) -> Result<usize, Handle> {
for (i, slot) in self.handles.iter_mut().enumerate() {
if slot.is_none() {
*slot = Some(handle);
return Ok(i);
}
}
Err(handle)
}
pub fn remove(&mut self, id: usize) -> Option<Handle> {
self.handles.get_mut(id).and_then(Option::take)
}
pub fn take(&mut self, id: usize) -> Option<Handle> {
self.handles.get_mut(id)?.take()
}
pub fn put(&mut self, id: usize, handle: Handle) -> Result<(), Handle> {
let Some(slot) = self.handles.get_mut(id) else {
return Err(handle);
};
if slot.is_some() {
return Err(handle);
}
*slot = Some(handle);
Ok(())
}
pub fn get(&self, id: usize) -> Option<&Handle> {
self.handles.get(id).and_then(Option::as_ref)
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut Handle> {
self.handles.get_mut(id).and_then(Option::as_mut)
}
}
pub struct Tcb {
pub id: TaskId,
pub as_id: AddressSpaceId,
pub state: ThreadState,
pub kernel_stack: KernelStack,
pub context: ThreadContext,
pub mailbox: Mailbox,
pub handles: HandleTable,
}
impl core::fmt::Debug for Tcb {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("Tcb")
.field("id", &self.id)
.field("as_id", &self.as_id)
.field("state", &self.state)
.finish_non_exhaustive()
}
}
impl Tcb {
pub fn new_user(
as_id: AddressSpaceId,
kernel_stack: KernelStack,
entry: VirtualAddr,
user_stack: VirtualAddr,
) -> Self {
let context = ThreadContext::new(entry, user_stack, kernel_stack.top());
let mut handles = HandleTable::new();
match handles.push(Handle {
object: KernelObject::AddressSpace(as_id),
rights: Rights::READ | Rights::WRITE | Rights::EXECUTE,
}) {
Ok(_) => {}
Err(_) => unreachable!("Cant push root address space handle"),
};
Self {
id: TaskId::new(0),
as_id,
state: ThreadState::Ready,
kernel_stack,
context,
mailbox: Mailbox::new(),
handles,
}
}
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "client"
version = "0.1.0"
edition = "2024"
[dependencies]
dusk-sys = { path = "../dusk-sys" }
[[bin]]
name = "client"
test = false
bench = false
+27
View File
@@ -0,0 +1,27 @@
#![no_std]
#![no_main]
use dusk_sys::{println, sys_exit, sys_recv, sys_send};
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
let msg = "Hello from client!";
println!("[client] Sent: {}", msg);
// TODO: we assume the echo server is task 1 (spawned by omega3)
sys_send(1, msg.as_bytes()).unwrap();
let mut out = [0u8; 128];
let (actual_len, _) = sys_recv(&mut out).unwrap();
println!(
"[client] Received: {}",
core::str::from_utf8(&out[..actual_len]).unwrap()
);
sys_exit(0);
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");
sys_exit(1);
}
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "dusk-sys"
version = "0.1.0"
edition = "2024"
[lib]
test = false
bench = false
+327
View File
@@ -0,0 +1,327 @@
#![no_std]
use core::arch::asm;
#[derive(Debug, PartialEq, Eq)]
pub enum Status {
// Success = 0,
InvalidArgument = 1,
BadAddress = 2,
BadFileDescriptor = 3,
NoSuchTask = 4,
OutOfMemory = 5,
BadHandle = 6,
}
// Opaque handle type
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AddressSpaceHandle(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameHandle(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MappingHandle(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ThreadHandle(usize);
// our own address space and thread handle are always given to us
pub const SELF_AS: AddressSpaceHandle = AddressSpaceHandle(0);
pub const SELF_THREAD: ThreadHandle = ThreadHandle(1);
impl From<usize> for Status {
fn from(value: usize) -> Self {
match value {
1 => Self::InvalidArgument,
2 => Self::BadAddress,
3 => Self::BadFileDescriptor,
4 => Self::NoSuchTask,
5 => Self::OutOfMemory,
6 => Self::BadHandle,
_ => Self::InvalidArgument,
}
}
}
#[repr(u64)]
pub enum SyscallNumber {
Yield = 1,
Exit = 2,
Write = 3,
Send = 4,
Recv = 5,
FrameAlloc = 6,
FrameDealloc = 7,
AsCreate = 8,
Map = 9,
Unmap = 10,
TaskCreate = 11,
}
pub fn sys_yield() {
unsafe {
asm!(
"syscall",
in("rax") 1usize,
lateout("rcx") _,
lateout("r11") _,
);
}
}
fn debug_write(buf: &str) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") 1,
in("rsi") buf.as_ptr(),
in("rdx") buf.len(),
in("r10") 0,
inlateout("rax") SyscallNumber::Write as usize => status,
lateout("rcx") _,
lateout("r11") _
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
struct DebugWriter;
impl core::fmt::Write for DebugWriter {
fn write_str(&mut self, value: &str) -> core::fmt::Result {
debug_write(value).map_err(|_| core::fmt::Error)
}
}
#[doc(hidden)]
pub fn __print(arguments: core::fmt::Arguments<'_>) {
use core::fmt::Write;
let _ = DebugWriter.write_fmt(arguments);
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{
$crate::__print(core::format_args!($($arg)*));
}};
}
#[macro_export]
macro_rules! println {
() => {{
$crate::print!("\n");
}};
($($arg:tt)*) => {{
$crate::print!("{}\n", core::format_args!($($arg)*));
}};
}
pub fn sys_exit(exit_code: usize) -> ! {
unsafe {
asm!(
"syscall",
in("rdi") exit_code,
in("rax") SyscallNumber::Exit as usize,
options(noreturn)
);
}
}
pub fn sys_send(dest_task_id: usize, msg: &[u8]) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") dest_task_id,
in("rsi") msg.as_ptr(),
in("rdx") msg.len(),
inlateout("rax") SyscallNumber::Send as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
pub fn sys_recv(buf: &mut [u8]) -> Result<(usize, usize), Status> {
let mut actual_len: usize = 0;
let mut sender: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") buf.as_mut_ptr(),
in("rsi") buf.len(),
in("rdx") &raw mut actual_len as usize,
in("r10") &raw mut sender as usize,
inlateout("rax") SyscallNumber::Recv as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok((actual_len, sender))
}
}
}
pub fn sys_frame_alloc() -> Result<FrameHandle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") &raw mut handle as usize,
inlateout("rax") SyscallNumber::FrameAlloc as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(FrameHandle(handle))
}
}
}
pub fn sys_frame_dealloc(frame_handle: FrameHandle) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") frame_handle.0,
inlateout("rax") SyscallNumber::FrameDealloc as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
pub fn sys_as_create() -> Result<AddressSpaceHandle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") &raw mut handle as usize,
inlateout("rax") SyscallNumber::AsCreate as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(AddressSpaceHandle(handle))
}
}
}
pub fn sys_map(
as_handle: AddressSpaceHandle,
frame_handle: FrameHandle,
virtual_addr: usize,
permissions: usize,
) -> Result<MappingHandle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") as_handle.0,
in("rsi") frame_handle.0,
in("rdx") virtual_addr,
in("r10") permissions,
in("r8") &raw mut handle as usize,
inlateout("rax") SyscallNumber::Map as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(MappingHandle(handle))
}
}
}
pub fn sys_unmap(mapping_handle: MappingHandle) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") mapping_handle.0,
inlateout("rax") SyscallNumber::Unmap as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
pub fn sys_task_create(
as_handle: AddressSpaceHandle,
entry: usize,
user_stack: usize,
) -> Result<ThreadHandle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") as_handle.0,
in("rsi") entry,
in("rdx") user_stack,
in("r10") &raw mut handle as usize,
inlateout("rax") SyscallNumber::TaskCreate as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(ThreadHandle(handle))
}
}
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "echo"
version = "0.1.0"
edition = "2024"
[dependencies]
dusk-sys = { path = "../dusk-sys" }
[[bin]]
name = "echo"
test = false
bench = false
+23
View File
@@ -0,0 +1,23 @@
#![no_std]
#![no_main]
use dusk_sys::{println, sys_exit, sys_recv, sys_send};
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
let mut out = [0u8; 128];
loop {
let (actual_len, sender) = sys_recv(&mut out).unwrap();
println!(
"[echo] Received: {}",
core::str::from_utf8(&out[..actual_len]).unwrap()
);
sys_send(sender, &out[..actual_len]).unwrap();
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");
sys_exit(1);
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "omega3"
version = "0.1.0"
edition = "2024"
[dependencies]
dusk-sys = { path = "../dusk-sys" }
[[bin]]
name = "omega3"
test = false
bench = false
+73
View File
@@ -0,0 +1,73 @@
// CPIO newc archive parser
#[repr(C)]
struct Header {
pub c_magic: [u8; 6],
pub c_ino: [u8; 8],
pub c_mode: [u8; 8],
pub c_uid: [u8; 8],
pub c_gid: [u8; 8],
pub c_nlink: [u8; 8],
pub c_mtime: [u8; 8],
pub c_filesize: [u8; 8],
pub c_devmajor: [u8; 8],
pub c_devminor: [u8; 8],
pub c_rdevmajor: [u8; 8],
pub c_rdevminor: [u8; 8],
pub c_namesize: [u8; 8],
pub c_check: [u8; 8],
}
impl Header {
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < core::mem::size_of::<Header>() {
return None;
}
let header: Header = unsafe { core::ptr::read(bytes.as_ptr() as *const Header) };
if header.c_magic != *b"070701" {
return None;
}
Some(header)
}
}
pub fn find_file<'a>(archive: *const u8, target: &str) -> Option<&'a [u8]> {
let mut offset = 0;
loop {
let header = Header::from_bytes(&unsafe {
core::slice::from_raw_parts(archive.add(offset), core::mem::size_of::<Header>())
})?;
let header_start = offset;
offset += core::mem::size_of::<Header>();
let file_len =
usize::from_str_radix(core::str::from_utf8(&header.c_filesize).ok()?, 16).ok()?;
let name_len =
usize::from_str_radix(core::str::from_utf8(&header.c_namesize).ok()?, 16).ok()?;
let name_bytes = &unsafe { core::slice::from_raw_parts(archive.add(offset), name_len) };
let name = core::str::from_utf8(name_bytes)
.ok()?
.trim_end_matches('\0');
if name == "TRAILER!!!" {
break;
}
let data_start = header_start + ((core::mem::size_of::<Header>() + name_len + 3) & !3);
if name == target {
return Some(&unsafe {
core::slice::from_raw_parts(archive.add(data_start), file_len)
});
}
offset = data_start + ((file_len + 3) & !3);
}
None
}
+425
View File
@@ -0,0 +1,425 @@
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElfIsa {
None,
Sparc,
X86,
Mips,
Ppc,
Arm,
SuperH,
Ia64,
Amd64,
AArch64,
Riscv,
}
impl ElfIsa {
fn from_u16(value: u16) -> Result<Self, ElfError> {
match value {
0x00 => Ok(Self::None),
0x02 => Ok(Self::Sparc),
0x03 => Ok(Self::X86),
0x08 => Ok(Self::Mips),
0x14 => Ok(Self::Ppc),
0x28 => Ok(Self::Arm),
0x2A => Ok(Self::SuperH),
0x32 => Ok(Self::Ia64),
0x3E => Ok(Self::Amd64),
0xB7 => Ok(Self::AArch64),
0xF3 => Ok(Self::Riscv),
_ => Err(ElfError::InvalidElf),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElfClass {
Elf32,
Elf64,
}
impl ElfClass {
fn from_u8(value: u8) -> Result<Self, ElfError> {
match value {
1 => Ok(Self::Elf32),
2 => Ok(Self::Elf64),
_ => Err(ElfError::InvalidElf),
}
}
const fn header_size(self) -> u16 {
match self {
Self::Elf32 => 52,
Self::Elf64 => 64,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Endianness {
Little,
Big,
}
impl Endianness {
fn from_u8(value: u8) -> Result<Self, ElfError> {
match value {
1 => Ok(Self::Little),
2 => Ok(Self::Big),
_ => Err(ElfError::InvalidElf),
}
}
}
#[repr(u16)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElfType {
Relocatable = 1,
Executable = 2,
SharedObject = 3,
Core = 4,
}
impl ElfType {
fn from_u16(value: u16) -> Result<Self, ElfError> {
match value {
1 => Ok(Self::Relocatable),
2 => Ok(Self::Executable),
3 => Ok(Self::SharedObject),
4 => Ok(Self::Core),
_ => Err(ElfError::InvalidElf),
}
}
}
#[derive(Debug)]
#[allow(unused)]
pub struct ElfHeader {
magic: [u8; 4],
pub class: ElfClass,
endianness: Endianness,
version: u8,
os_abi: u8,
_reserved: [u8; 8],
pub object_type: ElfType,
pub machine: ElfIsa,
version_1: u32,
entry: u64, // 2115136
program_header_offset: u64, // 64
section_header_offset: u64, // 2759752
flags: u32, // 0
header_size: u16, // 64
program_header_entry_size: u16, // 56
program_header_count: u16, // 6
section_header_entry_size: u16, // 64
section_header_count: u16, // 17
section_name_index: u16, // 15
}
#[derive(Debug)]
pub enum ElfError {
InvalidElf,
}
impl ElfHeader {
pub fn parse(bytes: &[u8]) -> Result<Self, ElfError> {
let mut reader = Reader::new(bytes);
let magic = reader.read_array()?;
if magic != *b"\x7fELF" {
return Err(ElfError::InvalidElf);
}
let class = ElfClass::from_u8(reader.read_u8()?)?;
let endianness = Endianness::from_u8(reader.read_u8()?)?;
reader.set_endianness(endianness);
let version = reader.read_u8()?;
let os_abi = reader.read_u8()?;
let reserved = reader.read_array()?;
let object_type = ElfType::from_u16(reader.read_u16()?)?;
let machine = ElfIsa::from_u16(reader.read_u16()?)?;
let version_1 = reader.read_u32()?;
let entry = reader.read_word(class)?;
let program_header_offset = reader.read_word(class)?;
let section_header_offset = reader.read_word(class)?;
let flags = reader.read_u32()?;
let header_size = reader.read_u16()?;
let program_header_entry_size = reader.read_u16()?;
let program_header_count = reader.read_u16()?;
let section_header_entry_size = reader.read_u16()?;
let section_header_count = reader.read_u16()?;
let section_name_index = reader.read_u16()?;
if header_size != class.header_size() {
return Err(ElfError::InvalidElf);
}
Ok(Self {
magic,
class,
endianness,
version,
os_abi,
_reserved: reserved,
object_type,
machine,
version_1,
entry,
program_header_offset,
section_header_offset,
flags,
header_size,
program_header_entry_size,
program_header_count,
section_header_entry_size,
section_header_count,
section_name_index,
})
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProgramHeaderType {
Null = 0,
Load = 1,
Dynamic = 2,
Interpreter = 3,
Note = 4,
Shlib = 5,
Phdr = 6,
GnuStack = 0x6474e551,
Relro = 0x6474e552,
Other(u32),
}
impl ProgramHeaderType {
fn from_u32(value: u32) -> Self {
match value {
0 => Self::Null,
1 => Self::Load,
2 => Self::Dynamic,
3 => Self::Interpreter,
4 => Self::Note,
5 => Self::Shlib,
6 => Self::Phdr,
0x6474e551 => Self::GnuStack,
0x6474e552 => Self::Relro,
_ => Self::Other(value),
}
}
}
#[derive(Debug)]
#[allow(unused)]
pub struct ProgramHeader {
pub segment_type: ProgramHeaderType,
pub flags: u32,
pub file_offset: u64,
pub virtual_address: u64,
_physical_address: u64,
pub file_size: u64,
pub memory_size: u64,
pub alignment: u64,
}
impl ProgramHeader {
pub fn parse(bytes: &[u8], class: ElfClass, endianness: Endianness) -> Result<Self, ElfError> {
let mut reader = Reader::new(bytes);
reader.set_endianness(endianness);
let segment_type = ProgramHeaderType::from_u32(reader.read_u32()?);
let flags = if class == ElfClass::Elf64 {
reader.read_u32()?
} else {
0
};
let file_offset = reader.read_word(class)?;
let virtual_address = reader.read_word(class)?;
let physical_address = reader.read_word(class)?;
let file_size = reader.read_word(class)?;
let memory_size = reader.read_word(class)?;
let flags = if class == ElfClass::Elf32 {
reader.read_u32()?
} else {
flags
};
let alignment = reader.read_word(class)?;
Ok(Self {
segment_type,
flags,
file_offset,
virtual_address,
_physical_address: physical_address,
file_size,
memory_size,
alignment,
})
}
}
pub struct Elf<'a> {
bytes: &'a [u8],
header: ElfHeader,
}
impl<'a> Elf<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, ElfError> {
let header = ElfHeader::parse(bytes)?;
Ok(Self { bytes, header })
}
pub fn program_headers(&self) -> Result<ProgramHeaders<'_>, ElfError> {
let offset =
usize::try_from(self.header.program_header_offset).map_err(|_| ElfError::InvalidElf)?;
let entry_size = usize::from(self.header.program_header_entry_size);
let count = usize::from(self.header.program_header_count);
let expected_entry_size = match self.header.class {
ElfClass::Elf32 => 32,
ElfClass::Elf64 => 56,
};
if entry_size != expected_entry_size {
return Err(ElfError::InvalidElf);
}
let table_size = entry_size.checked_mul(count).ok_or(ElfError::InvalidElf)?;
let table_end = offset.checked_add(table_size).ok_or(ElfError::InvalidElf)?;
let bytes = self
.bytes
.get(offset..table_end)
.ok_or(ElfError::InvalidElf)?;
Ok(ProgramHeaders {
bytes,
class: self.header.class,
endianness: self.header.endianness,
entry_size,
remaining: count,
})
}
pub fn bytes(&self) -> &[u8] {
self.bytes
}
#[allow(unused)]
pub fn machine(&self) -> ElfIsa {
self.header.machine
}
pub fn entry(&self) -> usize {
self.header.entry as usize
}
}
pub struct ProgramHeaders<'a> {
bytes: &'a [u8],
class: ElfClass,
endianness: Endianness,
entry_size: usize,
remaining: usize,
}
impl<'a> Iterator for ProgramHeaders<'a> {
type Item = Result<ProgramHeader, ElfError>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let entry = match self.bytes.get(..self.entry_size) {
Some(entry) => entry,
None => {
self.remaining = 0;
return None;
}
};
self.bytes = &self.bytes[self.entry_size..];
self.remaining -= 1;
Some(ProgramHeader::parse(entry, self.class, self.endianness))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for ProgramHeaders<'_> {}
struct Reader<'a> {
bytes: &'a [u8],
offset: usize,
endianness: Endianness,
}
impl<'a> Reader<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
offset: 0,
endianness: Endianness::Little,
}
}
fn set_endianness(&mut self, endianness: Endianness) {
self.endianness = endianness;
}
fn read_array<const N: usize>(&mut self) -> Result<[u8; N], ElfError> {
let end = self.offset.checked_add(N).ok_or(ElfError::InvalidElf)?;
let bytes = self
.bytes
.get(self.offset..end)
.ok_or(ElfError::InvalidElf)?;
self.offset = end;
bytes.try_into().map_err(|_| ElfError::InvalidElf)
}
fn read_u8(&mut self) -> Result<u8, ElfError> {
Ok(self.read_array::<1>()?[0])
}
fn read_u16(&mut self) -> Result<u16, ElfError> {
let bytes = self.read_array()?;
Ok(match self.endianness {
Endianness::Little => u16::from_le_bytes(bytes),
Endianness::Big => u16::from_be_bytes(bytes),
})
}
fn read_u32(&mut self) -> Result<u32, ElfError> {
let bytes = self.read_array()?;
Ok(match self.endianness {
Endianness::Little => u32::from_le_bytes(bytes),
Endianness::Big => u32::from_be_bytes(bytes),
})
}
fn read_u64(&mut self) -> Result<u64, ElfError> {
let bytes = self.read_array()?;
Ok(match self.endianness {
Endianness::Little => u64::from_le_bytes(bytes),
Endianness::Big => u64::from_be_bytes(bytes),
})
}
fn read_word(&mut self, class: ElfClass) -> Result<u64, ElfError> {
match class {
ElfClass::Elf32 => Ok(u64::from(self.read_u32()?)),
ElfClass::Elf64 => self.read_u64(),
}
}
}
+113
View File
@@ -0,0 +1,113 @@
#![no_std]
#![no_main]
mod cpio;
mod elf;
use dusk_sys::{
AddressSpaceHandle, SELF_AS, println, sys_as_create, sys_exit, sys_frame_alloc, sys_map,
sys_task_create, sys_unmap, sys_yield,
};
// Mapped into the root task's address space by the kernel.
static INITRAMFS_START: usize = 0x4000_0000;
const SCRATCH_PAGE: usize = 0x8000_0000;
const STACK_TOP: usize = 0x0000_7FFF_FFFF_F000;
const STACK_PAGES: usize = 4;
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
println!(r#"-----------------------------"#);
println!(r#" .d88888888b. .d88888b. "#);
println!(r#" d88P" "Y88b 88P" "Y88 "#);
println!(r#" 888 888 .od88P "#);
println!(r#" Y88b d88P "Y88b "#);
println!(r#" "88bo od88" 88b d88 "#);
println!(r#" d88888 88888b "Y88888P" "#);
println!(r#"----- Omega3 Dusk Root Server"#);
let echo_bytes = cpio::find_file(INITRAMFS_START as *const u8, "echo.elf").unwrap();
let echo_elf = elf::Elf::parse(echo_bytes).unwrap();
let echo_as = sys_as_create().unwrap();
let echo_entry = load_elf(&echo_elf, echo_as);
map_stack(echo_as, STACK_TOP, STACK_PAGES);
let _ = sys_task_create(echo_as, echo_entry, STACK_TOP).unwrap();
let client_bytes = cpio::find_file(INITRAMFS_START as *const u8, "client.elf").unwrap();
let client_elf = elf::Elf::parse(client_bytes).unwrap();
let client_as = sys_as_create().unwrap();
let client_entry = load_elf(&client_elf, client_as);
map_stack(client_as, STACK_TOP, STACK_PAGES);
let _ = sys_task_create(client_as, client_entry, STACK_TOP).unwrap();
// call a bogus system call
unsafe {
core::arch::asm!("syscall", in("rax") 134);
}
sys_exit(0);
}
fn load_elf(elf: &elf::Elf, target_as: AddressSpaceHandle) -> usize {
for header in elf.program_headers().unwrap() {
let header = header.unwrap();
if header.segment_type != elf::ProgramHeaderType::Load || header.memory_size == 0 {
continue;
}
let mut perms = 0;
if header.flags & 2 != 0 {
perms |= 1 << 0;
}
if header.flags & 1 != 0 {
perms |= 1 << 1;
}
let segment_start = header.virtual_address as usize;
let segment_end = segment_start + header.memory_size as usize;
let file_end = segment_start + header.file_size as usize;
let page_start = segment_start & !0xFFF;
for page in (page_start..segment_end).step_by(0x1000) {
let frame = sys_frame_alloc().unwrap();
let scratch_handle = sys_map(SELF_AS, frame, SCRATCH_PAGE, 0b01).unwrap();
unsafe {
core::ptr::write_bytes(SCRATCH_PAGE as *mut u8, 0, 0x1000);
let copy_start = page.max(segment_start).min(page + 0x1000);
let copy_end = (page + 0x1000).min(file_end).max(copy_start);
if copy_end > copy_start {
let page_offset = copy_start - page;
let file_offset = header.file_offset as usize + (copy_start - segment_start);
let len = copy_end - copy_start;
core::ptr::copy_nonoverlapping(
elf.bytes().as_ptr().add(file_offset),
(SCRATCH_PAGE as *mut u8).add(page_offset),
len,
);
}
}
sys_unmap(scratch_handle).unwrap();
sys_map(target_as, frame, page, perms).unwrap();
}
}
elf.entry()
}
fn map_stack(target_as: AddressSpaceHandle, stack_top: usize, pages: usize) {
for i in 1..=pages {
let frame = sys_frame_alloc().unwrap();
let page_addr = stack_top - i * 0x1000;
sys_map(target_as, frame, page_addr, 0b01).unwrap();
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");
sys_exit(1);
}