Compare commits

...

13 Commits

53 changed files with 7550 additions and 335 deletions
Generated
+25
View File
@@ -2,6 +2,13 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "client"
version = "0.1.0"
dependencies = [
"dusk-sys",
]
[[package]]
name = "dusk"
version = "0.1.0"
@@ -9,8 +16,26 @@ dependencies = [
"limine",
]
[[package]]
name = "dusk-sys"
version = "0.1.0"
[[package]]
name = "echo"
version = "0.1.0"
dependencies = [
"dusk-sys",
]
[[package]]
name = "limine"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9"
[[package]]
name = "omega3"
version = "0.1.0"
dependencies = [
"dusk-sys",
]
+8
View File
@@ -3,5 +3,13 @@ name = "dusk"
version = "0.1.0"
edition = "2024"
[workspace]
members = [".", "userspace/*"]
[dependencies]
limine = "0.6.5"
[[bin]]
name = "dusk"
test = false
bench = false
+55 -80
View File
@@ -1,21 +1,22 @@
ARTIFACTS_PATH ?= bin
IMAGE_NAME ?= dusk.iso
MODE ?= release
MODE ?= debug
ARCH ?= x86_64
MEMORY ?= 512M
# In MB
ISO_SIZE ?= 512
QEMU_OPTS ?=
#MKSQUASHFS_OPTS ?=
GDB ?=
CPUS ?= 1
# FAT type
ESP_BITS ?= 32
EXPORT_SYMBOLS = true
#EXPORT_SYMBOLS = true
ISO_PATH = ${ARTIFACTS_PATH}/iso_root
#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME}
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
QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH}
LIMINE_BOOT_VARIATION = X64
@@ -26,6 +27,7 @@ KERNEL_FILE = target/${ARCH}-unknown-none/${MODE}/dusk.elf
ifeq (${MODE},release)
CARGO_OPTS += --release
USERSPACE_CARGO_OPTS += --release
endif
ifneq (${CPUS},1)
@@ -36,112 +38,87 @@ ifneq (${GDB},)
QEMU_OPTS += -s -S
endif
ifeq (${ARCH},aarch64)
LIMINE_BOOT_VARIATION := AA64
UEFI := true
ifneq (${KVM},)
QEMU_OPTS += -accel kvm -cpu host
endif
ifneq (${UEFI},)
RUN_OPTS := ovmf-${ARCH}
ifeq (${ARCH},aarch64)
QEMU_OPTS += -M virt -bios ovmf/ovmf-${ARCH}/OVMF.fd
else
QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd
endif
QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd
endif
.PHONY: 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:
cargo check
cargo check -Zjson-target-spec
prepare-bin-files:
# Remove ISO and everything in the bin directory
rm -f ${IMAGE_PATH}
rm -rf ${ARTIFACTS_PATH}/*
# Make bin/ bin/iso_root and bin/initramfs
# Make bin/ and bin/iso_root
mkdir -p ${ARTIFACTS_PATH}
mkdir -p ${ISO_PATH}
# mkdir -p ${INITRAMFS_PATH}
mkdir -p ${ARTIFACTS_PATH}/mnt
mkdir -p ${INITRAMFS_PATH}
#copy-initramfs-files:
# echo "Hello World from Initramfs" > ${INITRAMFS_PATH}/example.txt
# echo "Second file for testing" > ${INITRAMFS_PATH}/example2.txt
# mkdir -p ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/
# mkdir ${INITRAMFS_PATH}/mnt/
# echo "Nexted file reads!!" > ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/yeah.txt
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}
#compile-initramfs: copy-initramfs-files
# # Make squashfs without compression temporaily so I can get it working before I have to write a gzip driver
# mksquashfs ${INITRAMFS_PATH} ${ARTIFACTS_PATH}/initramfs.img ${MKSQUASHFS_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
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}
#ifeq (${EXPORT_SYMBOLS},true)
# nm ${KERNEL_FILE} > scripts/symbols.table
# @if [ ! -d "scripts/rustc_demangle" ]; then \
# git clone "https://github.com/juls0730/rustc_demangle.py" "scripts/rustc_demangle"; \
# fi
# python scripts/demangle-symbols.py
# mv scripts/symbols.table ${INITRAMFS_PATH}/
#endif
# python scripts/font.py
# mv scripts/font.psf ${INITRAMFS_PATH}/
#python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/
compile-initramfs: copy-initramfs-files
(cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img
copy-iso-files:
# Limine files
mkdir -p ${ISO_PATH}/boot/limine
mkdir -p ${ISO_PATH}/EFI/BOOT
mkdir -p ${ISO_PATH}/mnt
cp -v limine.conf limine/limine-bios.sys ${ISO_PATH}/boot/limine
cp -v limine/BOOT${LIMINE_BOOT_VARIATION}.EFI ${ISO_PATH}/EFI/BOOT/
# OS files
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
partition-iso: copy-iso-files
# Make empty ISO of 64M in size
build-esp: copy-iso-files
# Create and populate formatted FAT image for ESP partition (130048 1K-blocks = ~127MiB)
mkfs.fat -F ${ESP_BITS} -C ${ESP_IMAGE} 130048
mcopy -s -i ${ESP_IMAGE} ${ISO_PATH}/* ::
partition-iso:
# Make empty disk image
dd if=/dev/zero of=${IMAGE_PATH} bs=1M count=0 seek=${ISO_SIZE}
parted -s ${IMAGE_PATH} mklabel gpt
parted -s ${IMAGE_PATH} mkpart BIOSBOOT 1024s 2047s
parted -s ${IMAGE_PATH} set 1 bios_grub on
parted -s ${IMAGE_PATH} mkpart ESP fat${ESP_BITS} 2048s 262144s
# Make ISO with 1 partition starting at sector 2048 that is 32768 sectors, or 16MiB, in size
# Then a second partition spanning the rest of the disk
parted -s ${IMAGE_PATH} mkpart primary 262145s 100%
parted -s ${IMAGE_PATH} set 2 esp on
build-iso: partition-iso
ifeq (${ARCH},x86_64)
# Install the Limine bootloader for bios installs
./limine/limine bios-install ${IMAGE_PATH}
ifneq (${UEFI},)
parted -s ${IMAGE_PATH} mklabel gpt
parted -s ${IMAGE_PATH} mkpart ESP fat${ESP_BITS} 2048s 262144s
parted -s ${IMAGE_PATH} set 1 esp on
else
parted -s ${IMAGE_PATH} mklabel msdos
parted -s ${IMAGE_PATH} mkpart primary fat${ESP_BITS} 2048s 262144s
parted -s ${IMAGE_PATH} set 1 boot on
endif
sudo losetup -Pf --show ${IMAGE_PATH} > loopback_dev
sudo mkfs.fat -F ${ESP_BITS} `cat loopback_dev`p2
sudo mount `cat loopback_dev`p2 ${ARTIFACTS_PATH}/mnt
sudo cp -r ${ISO_PATH}/* ${ARTIFACTS_PATH}/mnt
sync
sudo umount ${ARTIFACTS_PATH}/mnt
sudo losetup -d `cat loopback_dev`
rm loopback_dev
# Make second partition spanning the rest of the disk
parted -s ${IMAGE_PATH} mkpart primary 262145s 100%
build-iso: partition-iso build-esp
# Splice ESP partition into sector 2048 of the disk image
dd if=${ESP_IMAGE} of=${IMAGE_PATH} bs=512 seek=2048 conv=notrunc
ifeq (${UEFI},)
# install limine for legacy bios
./limine/limine bios-install ${IMAGE_PATH}
endif
compile-bootloader:
@if [ ! -f "limine/.version" ] || [ "$$(cat limine/.version)" != "${LIMINE_VERSION}" ]; then \
@@ -158,26 +135,24 @@ compile-bootloader:
compile-binaries:
cargo build ${CARGO_OPTS}
ovmf-x86_64: 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; \
fi
ovmf-aarch64: ovmf
mkdir -p ovmf/ovmf-aarch64
@if [ ! -d "ovmf/ovmf-aarch64/OVMF.fd" ]; then \
cd ovmf/ovmf-aarch64 && curl -o OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEAARCH64_QEMU_EFI.fd; \
fi
# In debug mode, open a terminal and run this command:
# gdb target/x86_64-unknown-none/debug/CappuccinOS.elf -ex "target remote :1234"
run: build ${RUN_OPTS} run-${ARCH}
run-serial: build ${RUN_OPTS} run-${ARCH}-serial
run-x86_64:
tmux new-session -d -s qemu 'qemu-system-x86_64 ${QEMU_OPTS}'
run-x86_64-serial:
qemu-system-x86_64 ${QEMU_OPTS} -boot d -display none -serial stdio -monitor none -no-reboot
line-count:
cloc --quiet --exclude-dir=bin --include-lang=Rust --csv src/ | tail -n 1 | awk -F, '{print $$5}'
clean:
+2 -1
View File
@@ -1,3 +1,4 @@
# DuskOS
A simple microkernel and operating system written in Rust for x86_64.
A simple work-in-progress microkernel and operating system written in Rust for
x86_64.
+3
View File
@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=src/arch/x86_64/linker.ld");
}
+2 -1
View File
@@ -1,7 +1,8 @@
timeout: 3
timeout: 0
/DuskOS
protocol: limine
path: boot():/boot/dusk.elf
module_path: boot():/boot/initramfs.img
+6
View File
@@ -3,3 +3,9 @@ mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::*;
#[cfg(target_arch = "x86_64")]
pub(crate) use x86_64::{
PageTableCreateError, PageTableMapError, PageTableUnmapError, ThreadContext, set_kernel_stack,
switch_context,
};
+224
View File
@@ -0,0 +1,224 @@
use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use crate::{
arch::{
port::write_u8,
x86_64::{
cpu::{read_msr, write_msr},
interrupts::apic_vectors::{
APIC_ERROR_VECTOR, APIC_SPURIOUS_VECTOR, APIC_TIMER_VECTOR,
},
},
},
memory::{
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
},
};
const APIC_ID: u32 = 0x20;
// End Of Interrupt
const APIC_EOI: u32 = 0xB0;
// Task Priority Register
const APIC_TPR: u32 = 0x80;
// Spurious Interrupt Vector
const APIC_SVR: u32 = 0xF0;
// Error Status Register
const APIC_ESR: u32 = 0x280;
const APIC_LVT_ERROR: u32 = 0x370;
const APIC_LVT_MASKED: u64 = 1 << 16;
const APIC_LVT_TIMER_MODE_PERIODIC: u64 = 1 << 17;
pub const LOCAL_APIC_VIRTUAL_ADDRESS: VirtualAddr = VirtualAddr::new(0xFFFF_FFFD_0000_0000);
const APIC_LVT_TIMER: u32 = 0x320;
const APIC_TIMER_INITIAL_COUNT: u32 = 0x380;
const APIC_TIMER_CURRENT_COUNT: u32 = 0x390;
const APIC_TIMER_DIVIDE_CONFIG: u32 = 0x3E0;
enum LocalApicAccess {
X2Apic,
XApic,
}
impl LocalApicAccess {
fn read(&self, offset: u32) -> u64 {
match self {
Self::X2Apic => {
let msr = 0x800 + offset / 16;
unsafe { read_msr(msr) }
}
Self::XApic => unsafe {
LOCAL_APIC_VIRTUAL_ADDRESS
.as_ptr::<u8>()
.add(offset as usize)
.cast::<u32>()
.read_volatile() as u64
},
}
}
fn write(&self, offset: u32, value: u64) {
match self {
Self::X2Apic => {
let msr = 0x800 + offset / 16;
unsafe { write_msr(msr, value) };
}
Self::XApic => unsafe {
LOCAL_APIC_VIRTUAL_ADDRESS
.as_mut_ptr::<u8>()
.add(offset as usize)
.cast::<u32>()
.write_volatile(value as u32);
},
}
}
fn end_of_interrupt(&self) {
self.write(APIC_EOI, 0);
}
}
#[derive(Debug)]
pub enum LocalApicError {
AddressMismatch,
ApicDisabled,
FailedToMapApic,
NotBootSystemProcessor,
}
pub struct LocalApic {
id: u32,
access: LocalApicAccess,
}
const IA32_APIC_BASE: u32 = 0x1B;
const APIC_BASE_BSP: u64 = 1 << 8;
const APIC_BASE_X2APIC_ENABLE: u64 = 1 << 10;
const APIC_BASE_GLOBAL_ENABLE: u64 = 1 << 11;
// TODO: use MAXPHYADDR
const APIC_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
impl LocalApic {
pub fn init(
local_apic_address: PhysicalAddr,
allocator: &mut FrameAllocator,
address_space: &mut AddressSpace,
) -> Result<Self, LocalApicError> {
let apic_base = unsafe { read_msr(IA32_APIC_BASE) };
let bsp = (apic_base & APIC_BASE_BSP) != 0;
let x2apix = (apic_base & APIC_BASE_X2APIC_ENABLE) != 0;
let enabled = (apic_base & APIC_BASE_GLOBAL_ENABLE) != 0;
let xapic_physical_addr = PhysicalAddr::new((apic_base & APIC_BASE_ADDRESS_MASK) as usize);
if local_apic_address != xapic_physical_addr {
return Err(LocalApicError::AddressMismatch);
}
if !enabled {
return Err(LocalApicError::ApicDisabled);
}
if !bsp {
return Err(LocalApicError::NotBootSystemProcessor);
}
let access = match x2apix {
true => LocalApicAccess::X2Apic,
false => {
address_space
.map(
local_apic_address,
LOCAL_APIC_VIRTUAL_ADDRESS,
PagePermissions::new(true, false, false),
allocator,
CachePolicy::Uncacheable,
)
.map_err(|_| LocalApicError::FailedToMapApic)?;
LocalApicAccess::XApic
}
};
let raw_id = access.read(APIC_ID);
let id = match access {
LocalApicAccess::XApic => (raw_id >> 24) as u32,
LocalApicAccess::X2Apic => raw_id as u32,
};
// ensure legacy PIC is disabled
unsafe {
write_u8(0x21, 0xFF);
write_u8(0xA1, 0xFF);
}
access.write(APIC_LVT_ERROR, APIC_ERROR_VECTOR as u64 | APIC_LVT_MASKED);
access.write(APIC_ESR, 0);
let _ = access.read(APIC_ESR);
access.write(APIC_TPR, 0);
access.write(APIC_SVR, (1 << 8) | APIC_SPURIOUS_VECTOR as u64);
Ok(Self { id, access })
}
pub fn start_timer(&self) {
self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
}
pub fn start_calibration_counter(&self) {
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, u32::MAX as u64);
}
pub fn stop_timer(&self) {
self.access
.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
self.access.write(APIC_TIMER_INITIAL_COUNT, 0);
}
pub fn id(&self) -> u32 {
self.id
}
}
fn current_access() -> LocalApicAccess {
let apic_base = unsafe { read_msr(IA32_APIC_BASE) };
if apic_base & APIC_BASE_X2APIC_ENABLE != 0 {
LocalApicAccess::X2Apic
} else {
LocalApicAccess::XApic
}
}
pub fn current_timer_count() -> u32 {
current_access().read(APIC_TIMER_CURRENT_COUNT) as u32
}
pub(super) fn end_of_interrupt() {
current_access().end_of_interrupt();
}
static APIC_ERROR_COUNT: AtomicUsize = AtomicUsize::new(0);
static LAST_APIC_ERROR: AtomicU32 = AtomicU32::new(0);
pub(super) fn record_error() {
let access = current_access();
access.write(APIC_ESR, 0);
let error = access.read(APIC_ESR) as u32;
LAST_APIC_ERROR.store(error, Ordering::SeqCst);
APIC_ERROR_COUNT.fetch_add(1, Ordering::SeqCst);
}
static APIC_TIMER_COUNT: AtomicUsize = AtomicUsize::new(0);
pub(super) fn record_timer() {
APIC_TIMER_COUNT.fetch_add(1, Ordering::SeqCst);
}
+250
View File
@@ -0,0 +1,250 @@
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)]
pub enum CpuFeaturesError {
CpuidFeaturesNotSupported,
SyscallNotSupported,
InvalidPhysicalAddressWidth,
InvalidVirtualAddressWidth,
}
#[derive(Clone, Copy)]
pub(crate) struct CpuFeatures {
pub nx_supported: bool,
pub nx_enabled: bool,
pub global_pages: bool,
pub physical_address_bits: u8,
pub virtual_address_bits: u8,
pub five_level_paging_active: bool,
}
// Extended features
const IA32_EFER: u32 = 0xC0000080;
pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
let mut features = CpuFeatures {
nx_supported: false,
nx_enabled: false,
global_pages: false,
physical_address_bits: 0,
virtual_address_bits: 0,
five_level_paging_active: false,
};
let cpuid_result = core::arch::x86_64::__cpuid(0x80000000);
if cpuid_result.eax < 0x80000008 {
return Err(CpuFeaturesError::CpuidFeaturesNotSupported);
}
let cpuid_result = core::arch::x86_64::__cpuid(0x80000001);
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);
features.physical_address_bits = (cpuid_result.eax & 0xFF) as u8;
if !(12..=52).contains(&features.physical_address_bits) {
return Err(CpuFeaturesError::InvalidPhysicalAddressWidth);
}
features.virtual_address_bits = (cpuid_result.eax >> 8 & 0xFF) as u8;
features.five_level_paging_active = read_cr4() & (1 << 12) != 0;
let required_virtual_address_bits = if features.five_level_paging_active {
57
} else {
48
};
if features.virtual_address_bits < required_virtual_address_bits {
return Err(CpuFeaturesError::InvalidVirtualAddressWidth);
}
let cpuid_result = core::arch::x86_64::__cpuid(0x1);
features.global_pages = cpuid_result.edx & (1 << 13) != 0;
if features.global_pages {
let cr4 = read_cr4();
write_cr4(cr4 | 1 << 7);
}
if features.nx_supported {
let msr_supported = cpuid_result.edx & (1 << 5) != 0;
if !msr_supported {
return Err(CpuFeaturesError::CpuidFeaturesNotSupported);
}
// mother efer
let efer = unsafe { read_msr(IA32_EFER) };
unsafe {
write_msr(IA32_EFER, efer | (1 << 11));
}
features.nx_enabled = unsafe { read_msr(IA32_EFER) } & (1 << 11) != 0;
}
Ok(features)
}
fn write_cr4(value: usize) {
unsafe {
asm!(
"mov cr4, {}",
in(reg) value,
options(nostack, preserves_flags)
);
}
}
fn read_cr4() -> usize {
let value: usize;
unsafe {
asm!(
"mov {}, cr4",
out(reg) value,
options(nomem, nostack, preserves_flags),
);
}
value
}
pub(super) unsafe fn read_msr(msr: u32) -> u64 {
let low: u32;
let high: u32;
unsafe {
asm!(
"rdmsr",
in("ecx") msr,
out("eax") low,
out("edx") high,
options(nomem, nostack, preserves_flags),
);
}
((high as u64) << 32) | low as u64
}
pub(super) unsafe fn write_msr(msr: u32, value: u64) {
unsafe {
asm!(
"wrmsr",
in("ecx") msr,
in("eax") value as u32,
in("edx") (value >> 32) as u32,
options(nomem, nostack, preserves_flags),
);
}
}
+42 -10
View File
@@ -1,13 +1,15 @@
use core::arch::asm;
use crate::memory::VirtualAddr;
#[repr(C, align(8))]
struct Gdt {
entries: [u64; 5],
pub(super) struct Gdt {
pub entries: [u64; 7],
}
impl Gdt {
pub const fn new() -> Self {
Self { entries: [0; 5] }
Self { entries: [0; 7] }
}
}
@@ -47,19 +49,22 @@ const DOUBLE_FAULT_STACK_SIZE: usize = 16 * 1024;
pub(super) const KERNEL_CODE_SELECTOR: u16 = 1 * 8;
pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8;
pub(super) const TSS_SELECTOR: u16 = 3 * 8;
pub(super) const USER_DATA_SELECTOR: u16 = (3 * 8) | 3;
pub(super) const USER_CODE_SELECTOR: u16 = (4 * 8) | 3;
pub(super) const TSS_SELECTOR: u16 = 5 * 8;
#[repr(align(16))]
#[allow(unused)] // field 0 is read, rust just cant tell
struct ExceptionStack([u8; DOUBLE_FAULT_STACK_SIZE]);
static mut GDT: Gdt = Gdt::new();
static mut TSS: TaskStateSegment = TaskStateSegment::new();
static mut DOUBLE_FAULT_STACK: ExceptionStack = ExceptionStack([0; DOUBLE_FAULT_STACK_SIZE]);
pub fn init() {
const _: () = assert!(core::mem::size_of::<TaskStateSegment>() == 104);
const _: () = assert!(core::mem::size_of::<GdtPointer>() == 10);
const _: () = assert!(core::mem::size_of::<TaskStateSegment>() == 104);
const _: () = assert!(core::mem::size_of::<GdtPointer>() == 10);
pub fn init() {
unsafe {
let stack_bottom = core::ptr::addr_of_mut!(DOUBLE_FAULT_STACK) as u64;
let stack_top = stack_bottom + DOUBLE_FAULT_STACK_SIZE as u64;
@@ -73,6 +78,10 @@ pub fn init() {
0,
kernel_code_descriptor(),
kernel_data_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_code_descriptor(),
tss_low,
tss_high,
],
@@ -83,6 +92,12 @@ pub fn init() {
}
}
pub fn set_kernel_stack(stack_top: VirtualAddr) {
unsafe {
TSS.privilege_stacks[0] = stack_top.as_usize() as u64;
}
}
fn gdt_reload() {
unsafe {
asm!(
@@ -118,17 +133,34 @@ fn gdt_reload() {
}
const PRESENT: u64 = 1 << 47;
const USER_DESCRIPTOR: u64 = 1 << 44;
const CODE_DATA_DESCRIPTOR: u64 = 1 << 44;
const USER_PRIVILEGE: u64 = 3 << 45;
const EXECUTABLE: u64 = 1 << 43;
const READ_WRITE: u64 = 1 << 41;
const GRANULARITY: u64 = 1 << 55;
const SIZE: u64 = 1 << 54;
const LONG_MODE: u64 = 1 << 53;
fn kernel_code_descriptor() -> u64 {
PRESENT | USER_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE
PRESENT | CODE_DATA_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE | GRANULARITY
}
fn kernel_data_descriptor() -> u64 {
PRESENT | USER_DESCRIPTOR | READ_WRITE
PRESENT | CODE_DATA_DESCRIPTOR | READ_WRITE | SIZE | GRANULARITY
}
fn user_code_descriptor() -> u64 {
PRESENT
| CODE_DATA_DESCRIPTOR
| USER_PRIVILEGE
| EXECUTABLE
| READ_WRITE
| LONG_MODE
| GRANULARITY
}
fn user_data_descriptor() -> u64 {
PRESENT | CODE_DATA_DESCRIPTOR | USER_PRIVILEGE | READ_WRITE | SIZE | GRANULARITY
}
fn tss_descriptor(tss: *const TaskStateSegment) -> [u64; 2] {
@@ -0,0 +1,40 @@
use super::idt::{self, InterruptFrame, stub_no_err};
use crate::arch::{apic, timer};
pub const PIT_CALIBRATION_VECTOR: u8 = 0xF1;
pub const APIC_TIMER_VECTOR: u8 = 0xFD;
pub const APIC_ERROR_VECTOR: u8 = 0xFE;
pub const APIC_SPURIOUS_VECTOR: u8 = 0xFF;
stub_no_err!(stub_pit_calibration, 0xF1);
stub_no_err!(stub_apic_timer, 0xFD);
stub_no_err!(stub_apic_error, 0xFE);
stub_no_err!(stub_apic_spurious, 0xFF);
pub(super) fn handle(frame: &mut InterruptFrame) {
match frame.vector as u8 {
PIT_CALIBRATION_VECTOR => {
timer::record_pit_calibration();
apic::end_of_interrupt();
}
APIC_TIMER_VECTOR => {
apic::record_timer();
apic::end_of_interrupt();
}
APIC_ERROR_VECTOR => {
apic::record_error();
apic::end_of_interrupt();
}
APIC_SPURIOUS_VECTOR => {
// No EOI
}
_ => {}
}
}
pub(super) fn install(idt: &mut idt::Idt) {
idt.set_handler(PIT_CALIBRATION_VECTOR, stub_pit_calibration, 0);
idt.set_handler(APIC_ERROR_VECTOR, stub_apic_error, 0);
idt.set_handler(APIC_TIMER_VECTOR, stub_apic_timer, 0);
idt.set_handler(APIC_SPURIOUS_VECTOR, stub_apic_spurious, 0);
}
+106 -68
View File
@@ -1,77 +1,115 @@
use core::arch::asm;
use super::idt::{self, InterruptStackFrame};
use crate::{hcf, println};
use super::idt::{self, InterruptFrame, InterruptStackFrame, stub_err, stub_no_err};
use crate::{
hcf, println,
task::tcb::{ExitReason, Fault},
};
macro_rules! fatal_without_error_code {
($handler:ident, $name:literal) => {
extern "x86-interrupt" fn $handler(frame: InterruptStackFrame) {
fatal_exception($name, &frame, None);
stub_no_err!(stub_divide_error, 0);
stub_no_err!(stub_debug, 1);
stub_no_err!(stub_non_maskable_interrupt, 2);
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();
}
fatal_exception(name, &frame.stack_frame, Some(frame.error_code));
}
let fault = match vector {
// 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));
}
macro_rules! fatal_with_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 debug_handler(frame: InterruptStackFrame) {
fatal_exception("DEBUG EXCEPTION", &frame, None);
}
extern "x86-interrupt" fn non_maskable_interrupt_handler(frame: InterruptStackFrame) {
fatal_exception("NON-MASKABLE INTERRUPT", &frame, None);
}
extern "x86-interrupt" fn breakpoint_handler(frame: InterruptStackFrame) {
report_exception("BREAKPOINT", &frame, None);
}
extern "x86-interrupt" fn double_fault_handler(frame: InterruptStackFrame, error_code: u64) {
fatal_exception("DOUBLE FAULT", &frame, Some(error_code));
}
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!(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!(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");
pub(super) fn install(idt: &mut idt::Idt) {
idt.set_handler(0, divide_error_handler, 0);
idt.set_handler(1, debug_handler, 0);
idt.set_handler(2, non_maskable_interrupt_handler, 0);
idt.set_handler(3, breakpoint_handler, 0);
idt.set_handler(6, invalid_opcode_handler, 0);
idt.set_handler(7, device_not_available_handler, 0);
idt.set_error_code_handler(8, double_fault_handler, 1);
idt.set_error_code_handler(10, invalid_tss_handler, 0);
idt.set_error_code_handler(11, segment_not_present_handler, 0);
idt.set_error_code_handler(12, stack_segment_fault_handler, 0);
idt.set_error_code_handler(13, general_protection_handler, 0);
idt.set_error_code_handler(14, page_fault_handler, 0);
idt.set_handler(16, x87_floating_point_handler, 0);
idt.set_error_code_handler(17, alignment_check_handler, 0);
idt.set_handler(18, machine_check_handler, 0);
idt.set_handler(19, simd_floating_point_handler, 0);
idt.set_handler(0, stub_divide_error, 0);
idt.set_handler(1, stub_debug, 0);
idt.set_handler(2, stub_non_maskable_interrupt, 0);
idt.set_user_handler(3, stub_breakpoint, 0);
idt.set_handler(6, stub_invalid_opcode, 0);
idt.set_handler(7, stub_device_not_available, 0);
idt.set_handler(8, stub_double_fault, 1);
idt.set_handler(10, stub_invalid_tss, 0);
idt.set_handler(11, stub_segment_not_present, 0);
idt.set_handler(12, stub_stack_segment_fault, 0);
idt.set_handler(13, stub_general_protection, 0);
idt.set_handler(14, stub_page_fault, 0);
idt.set_handler(16, stub_x87_floating_point, 0);
idt.set_handler(17, stub_alignment_check, 0);
idt.set_handler(18, stub_machine_check, 0);
idt.set_handler(19, stub_simd_floating_point, 0);
idt.set_user_handler(0x80, stub_user_test_exit, 0);
}
fn read_cr2() -> u64 {
@@ -134,10 +172,10 @@ fn report_exception(name: &'static str, frame: &InterruptStackFrame, error_code:
"kernel"
}
);
println!("RIP: {:#X}", frame.instruction_pointer.as_u64());
println!("RIP: {:#X}", frame.instruction_pointer.as_usize());
println!("CS: {:#X}", frame.code_segment);
println!("FLAGS: {:#X}", frame.cpu_flags);
println!("RSP: {:#X}", frame.stack_pointer.as_u64());
println!("RSP: {:#X}", frame.stack_pointer.as_usize());
println!("SS: {:#X}", frame.stack_segment);
if let Some(error_code) = error_code {
+149 -22
View File
@@ -1,5 +1,8 @@
use super::exceptions;
use crate::{arch::x86_64::gdt::KERNEL_CODE_SELECTOR, memory::VirtualAddr};
use crate::{
arch::x86_64::{gdt::KERNEL_CODE_SELECTOR, interrupts::apic_vectors},
memory::VirtualAddr,
};
#[repr(C, packed)]
#[derive(Clone, Copy)]
@@ -15,15 +18,15 @@ struct IdtEntry {
impl IdtEntry {
const fn missing() -> Self {
return Self {
Self {
offset_low: 0,
code_selector: 0x08,
code_selector: 0,
ist: 0,
attributes: 0,
offset_middle: 0,
offset_high: 0,
reserved: 0,
};
}
}
}
@@ -35,7 +38,7 @@ struct IdtPointer {
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub(super) struct InterruptStackFrame {
pub struct InterruptStackFrame {
pub instruction_pointer: VirtualAddr,
pub code_segment: u64,
pub cpu_flags: u64,
@@ -43,12 +46,36 @@ pub(super) struct InterruptStackFrame {
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 PRESENT: u8 = 1 << 7;
const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE;
const USER_DPL: u8 = 3 << 5;
const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE;
pub(super) type Handler = extern "x86-interrupt" fn(InterruptStackFrame);
pub(super) type ErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64);
pub(super) type RawHandler = unsafe extern "C" fn();
pub(super) struct Idt {
entries: [IdtEntry; 256],
@@ -61,27 +88,22 @@ impl Idt {
}
}
pub(super) fn set_handler(&mut self, vector: u8, handler: Handler, ist: u8) {
self.set_handler_address(vector, handler as usize, ist);
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);
}
pub(super) fn set_error_code_handler(
&mut self,
vector: u8,
handler: ErrorCodeHandler,
ist: u8,
) {
self.set_handler_address(vector, handler as usize, ist);
pub(super) fn set_user_handler(&mut self, vector: u8, handler: RawHandler, ist: u8) {
self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE);
}
fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8) {
fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8, attributes: u8) {
self.entries[vector as usize] = IdtEntry {
offset_low: address as u16,
offset_middle: (address >> 16) as u16,
offset_high: (address >> 32) as u32,
code_selector: KERNEL_CODE_SELECTOR,
ist: ist & 0b111,
attributes: KERNEL_INTERRUPT_GATE,
attributes,
reserved: 0,
};
}
@@ -89,14 +111,119 @@ impl Idt {
static mut IDT: Idt = Idt::new();
pub fn idt_init() {
const _: () = assert!(core::mem::size_of::<IdtEntry>() == 16);
const _: () = assert!(core::mem::size_of::<IdtPointer>() == 10);
const _: () = assert!(core::mem::size_of::<InterruptStackFrame>() == 40);
const _: () = assert!(core::mem::size_of::<IdtEntry>() == 16);
const _: () = assert!(core::mem::size_of::<IdtPointer>() == 10);
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() {
let mut idt = Idt::new();
exceptions::install(&mut idt);
apic_vectors::install(&mut idt);
unsafe {
core::ptr::addr_of_mut!(IDT).write(idt);
+38 -2
View File
@@ -1,12 +1,48 @@
use core::arch::asm;
pub(super) mod apic_vectors;
mod exceptions;
mod idt;
pub use idt::idt_init as init;
pub fn disable_interrupts() {
#[inline(always)]
pub fn disable_interrupts_and_save() -> u64 {
let flags: u64;
unsafe {
asm!("cli");
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)]
pub fn disable_interrupts() {
unsafe {
asm!("cli", options(nostack));
}
}
#[inline(always)]
pub fn enable_interrupts() {
unsafe {
asm!("sti", options(nostack));
}
}
+193
View File
@@ -0,0 +1,193 @@
use crate::{
memory::{
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
},
platform::acpi::{InterruptPolarity, TriggerMode},
};
const IOWIN: usize = 0x10;
const IOAPIC_ID: u8 = 0x00;
const IOAPIC_VERSION: u8 = 0x01;
const IOAPIC_REDIRECTION_BASE: u8 = 0x10;
const MASKED: u32 = 1 << 16;
pub const IOAPIC_VIRTUAL_ADDRESS: VirtualAddr = VirtualAddr::new(0xFFFF_FFFD_1000_0000);
#[derive(Debug)]
#[allow(unused)]
pub enum IoApicError {
IdMismatch { expected: u8, actual: u8 },
GsiOutsideRange,
FailedToMapIoApic,
InvalidRedirectionIndex,
}
pub struct RedirectionConfig {
pub vector: u8,
pub destination: u8,
pub polarity: InterruptPolarity,
pub trigger: TriggerMode,
}
pub struct IoApic {
base: VirtualAddr,
global_interrupt_base: u32,
redirection_entry_count: u32,
}
impl IoApic {
pub fn new(
expected_id: u8,
physical_address: PhysicalAddr,
global_interrupt_base: u32,
virtual_address: VirtualAddr,
allocator: &mut FrameAllocator,
address_space: &mut AddressSpace,
) -> Result<Self, IoApicError> {
address_space
.map(
physical_address,
virtual_address,
PagePermissions::new(true, false, false),
allocator,
CachePolicy::Uncacheable,
)
.map_err(|_| IoApicError::FailedToMapIoApic)?;
let mut io_apic = Self {
base: virtual_address,
global_interrupt_base: global_interrupt_base,
redirection_entry_count: 0,
};
let version = io_apic.read(IOAPIC_VERSION);
io_apic.redirection_entry_count = ((version >> 16) & 0xFF) + 1;
let id = ((io_apic.read(IOAPIC_ID) >> 24) & 0xF) as u8;
if id != expected_id {
return Err(IoApicError::IdMismatch {
expected: expected_id,
actual: id,
});
}
Ok(io_apic)
}
fn read(&mut self, register: u8) -> u32 {
unsafe {
self.base
.as_mut_ptr::<u32>()
.write_volatile(register as u32);
self.base
.as_ptr::<u8>()
.add(IOWIN)
.cast::<u32>()
.read_volatile()
}
}
fn write(&mut self, register: u8, value: u32) {
unsafe {
self.base
.as_mut_ptr::<u32>()
.write_volatile(register as u32);
self.base
.as_mut_ptr::<u8>()
.add(IOWIN)
.cast::<u32>()
.write_volatile(value);
}
}
fn redirection_index(&self, gsi: u32) -> Result<u32, IoApicError> {
let index = gsi
.checked_sub(self.global_interrupt_base)
.ok_or(IoApicError::GsiOutsideRange)?;
if index >= self.redirection_entry_count {
return Err(IoApicError::GsiOutsideRange);
}
Ok(index)
}
pub fn handles_gsi(&mut self, gsi: u32) -> bool {
match gsi.checked_sub(self.global_interrupt_base) {
Some(index) => index < self.redirection_entry_count,
None => false,
}
}
fn redirection_registers(&mut self, gsi: u32) -> Result<(u8, u8), IoApicError> {
let index = self.redirection_index(gsi)?;
let low_register = u8::try_from(IOAPIC_REDIRECTION_BASE as u32 + index * 2)
.map_err(|_| IoApicError::InvalidRedirectionIndex)?;
let high_register = u8::try_from(IOAPIC_REDIRECTION_BASE as u32 + index * 2 + 1)
.map_err(|_| IoApicError::InvalidRedirectionIndex)?;
Ok((low_register, high_register))
}
pub fn configure_masked(
&mut self,
gsi: u32,
config: RedirectionConfig,
) -> Result<(), IoApicError> {
let mut entry = config.vector as u64;
match config.polarity {
InterruptPolarity::ActiveHigh => {
entry |= 0 << 13;
}
InterruptPolarity::ActiveLow => {
entry |= 1 << 13;
}
}
match config.trigger {
TriggerMode::Edge => {
entry |= 0 << 15;
}
TriggerMode::Level => {
entry |= 1 << 15;
}
}
entry |= 1 << 16;
entry |= (config.destination as u64) << 56;
let (low_register, high_register) = self.redirection_registers(gsi)?;
let old_low = self.read(low_register);
self.write(low_register, old_low | MASKED);
self.write(high_register, (entry >> 32) as u32);
self.write(low_register, entry as u32 | MASKED);
Ok(())
}
pub fn unmask(&mut self, gsi: u32) -> Result<(), IoApicError> {
let (low_register, _) = self.redirection_registers(gsi)?;
let low = self.read(low_register);
self.write(low_register, low & !MASKED);
Ok(())
}
pub fn mask(&mut self, gsi: u32) -> Result<(), IoApicError> {
let (low_register, _) = self.redirection_registers(gsi)?;
let low = self.read(low_register);
self.write(low_register, low | MASKED);
Ok(())
}
}
+17 -2
View File
@@ -25,7 +25,10 @@ SECTIONS
/* that is the beginning of the region. */
/* Additionally, leave space for the ELF headers by adding SIZEOF_HEADERS to the */
/* base load address. */
. = 0xffffffff80000000 + SIZEOF_HEADERS;
. = 0xffffffff80000000;
__text_start = .;
. += SIZEOF_HEADERS;
.text : {
*(.text .text.*)
@@ -33,6 +36,8 @@ SECTIONS
/* Move to the next memory page for .rodata */
. = ALIGN(CONSTANT(MAXPAGESIZE));
__text_end = .;
__rodata_start = .;
.rodata : {
*(.rodata .rodata.*)
@@ -40,7 +45,9 @@ SECTIONS
/* Move to the next memory page for .data */
. = ALIGN(CONSTANT(MAXPAGESIZE));
__rodata_end = .;
__data_start = .;
.data : {
*(.data .data.*)
@@ -56,6 +63,11 @@ SECTIONS
*(.dynamic)
} :data :dynamic
.got : {
*(.got .got.*)
*(.got.plt .got.plt.*)
} :data
/* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
/* unnecessary zeros will be written to the binary. */
/* If you need, for example, .init_array and .fini_array, those should be placed */
@@ -65,6 +77,9 @@ SECTIONS
*(COMMON)
} :data
. = ALIGN(CONSTANT(MAXPAGESIZE));
__data_end = .;
/* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
/* Also discard the program interpreter section since we do not need one. This is */
/* more or less equivalent to the --no-dynamic-linker linker flag, except that it */
+193 -3
View File
@@ -1,19 +1,209 @@
pub(super) mod apic;
mod cpu;
mod gdt;
mod interrupts;
pub(super) mod io_apic;
mod paging;
mod pit;
pub mod port;
mod syscall;
pub mod timer;
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::{
MapError as PageTableMapError, PageTableCreateError, UnmapError as PageTableUnmapError,
};
pub use paging::{PageTable, PagingConfig};
use crate::println;
pub struct ArchState {
pub paging: PagingConfig,
}
pub fn init() {
use crate::{
KernelHandoff,
arch::x86_64::cpu::BOOT_CPU,
memory::{AddressSpace, FrameAllocator, VirtualAddr},
platform::acpi::Madt,
println,
};
pub fn init() -> ArchState {
disable_interrupts();
println!("Loading GDT...");
gdt::init();
println!("Loading IDT...");
interrupts::init();
println!("Detecting CPU features...");
let cpu_features = cpu::detect_features_and_enable();
let paging =
PagingConfig::from_features(cpu_features.expect("required CPU features are not supported"));
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)]
#[allow(unused)]
pub enum InterruptInitError {
InvalidLocalApicId,
InvalidLocalApicAddress,
FailedToGetIoApic,
IoApicError(io_apic::IoApicError),
LocalApicError(apic::LocalApicError),
TimerCalibrationError(timer::TimerCalibrationError),
MalformedMadt,
PitNotHandled,
}
pub struct InterruptController {
local_apic: apic::LocalApic,
io_apic: io_apic::IoApic,
local_timer_frequency: u64,
}
pub fn init_interrupt_controller(
madt: &Madt<'_>,
allocator: &mut FrameAllocator,
address_space: &mut AddressSpace,
) -> Result<InterruptController, InterruptInitError> {
let local_apic_address = madt
.effective_local_apic_address()
.map_err(|_| InterruptInitError::InvalidLocalApicAddress)?;
let mut local_apic = apic::LocalApic::init(local_apic_address, allocator, address_space)
.map_err(|err| InterruptInitError::LocalApicError(err))?;
let io_apic_info = madt
.sole_io_apic()
.map_err(|_| InterruptInitError::FailedToGetIoApic)?;
let pit_route = madt
.isa_irq_route(0x0)
.map_err(|_| InterruptInitError::MalformedMadt)?;
let mut io_apic = io_apic::IoApic::new(
io_apic_info.id,
io_apic_info.apic_address,
io_apic_info.global_system_interrupt_base,
io_apic::IOAPIC_VIRTUAL_ADDRESS,
allocator,
address_space,
)
.map_err(|err| InterruptInitError::IoApicError(err))?;
let pit_handled = io_apic.handles_gsi(pit_route.gsi);
if !pit_handled {
return Err(InterruptInitError::PitNotHandled);
}
let destination =
u8::try_from(local_apic.id()).map_err(|_| InterruptInitError::InvalidLocalApicId)?;
io_apic
.configure_masked(
pit_route.gsi,
io_apic::RedirectionConfig {
vector: interrupts::apic_vectors::PIT_CALIBRATION_VECTOR,
destination,
polarity: pit_route.polarity,
trigger: pit_route.trigger,
},
)
.map_err(|err| InterruptInitError::IoApicError(err))?;
let local_timer_frequency =
timer::calibrate_local_apic(&mut local_apic, &mut io_apic, pit_route)
.map_err(|err| InterruptInitError::TimerCalibrationError(err))?;
Ok(InterruptController {
local_apic,
io_apic,
local_timer_frequency,
})
}
/// # Safety
///
/// The caller must ensure:
/// - The stack is currently mapped, writable, and 16-byte aligned
pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff) -> ! {
unsafe {
gdt::set_kernel_stack(stack_top);
BOOT_CPU.kernel_stack_top = stack_top.as_usize();
syscall::init(&raw const BOOT_CPU);
asm!(
"mov rsp, {stack_top}",
"xor rbp, rbp",
"mov rdi, {handoff}",
"call {kernel_main}",
stack_top = in(reg) stack_top.as_usize(),
handoff = in(reg) handoff,
kernel_main = sym crate::kernel_main,
options(noreturn)
);
};
}
/// # Safety
///
/// - `user_instruction_pointer` and `user_stack_pointer` must be valid user mappings.
/// - The active address space must contain the kernel and supplied user mappings.
pub unsafe fn enter_user(
user_instruction_pointer: VirtualAddr,
user_stack_pointer: VirtualAddr,
) -> ! {
unsafe {
asm!(
"mov ds, {user_data_selector:x}",
"mov es, {user_data_selector:x}",
"mov fs, {user_data_selector:x}",
"push {user_data_selector}",
"push {user_stack_pointer}",
"push 0x202", // RFLAGS (IF=1, bit 1 reserved=1)
"push {user_code_selector}",
"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",
user_data_selector = in(reg) gdt::USER_DATA_SELECTOR as usize,
user_code_selector = in(reg) gdt::USER_CODE_SELECTOR as usize,
user_instruction_pointer = in(reg) user_instruction_pointer.as_usize(),
user_stack_pointer = in(reg) user_stack_pointer.as_usize(),
options(noreturn)
);
}
}
pub fn halt() {
+834
View File
@@ -0,0 +1,834 @@
use core::arch::asm;
use crate::{
arch::x86_64::cpu::CpuFeatures,
memory::{
CachePolicy, DirectMap, FrameAddr, FrameAllocator, OwnedFrame, PagePermissions,
PageTableMapping, PhysicalAddr, VirtualAddr,
},
};
pub const PAGE_SIZE: usize = 4096;
pub const PAGE_TABLE_ENTRIES: usize = 512;
#[derive(Clone, Copy)]
pub struct PagingConfig {
physical_address_bits: u8,
global_pages: bool,
nx_enabled: bool,
mode: PagingMode,
}
impl PagingConfig {
pub fn from_features(features: CpuFeatures) -> Self {
Self {
physical_address_bits: features.physical_address_bits,
nx_enabled: features.nx_enabled,
global_pages: features.global_pages,
mode: if features.five_level_paging_active {
PagingMode::FiveLevel
} else {
PagingMode::FourLevel
},
}
}
pub const fn physical_address_mask(&self) -> usize {
((1 << self.physical_address_bits) - 1) & !0xFFF
}
pub const fn physical_address_limit(&self) -> usize {
1 << self.physical_address_bits
}
}
#[derive(Clone, Copy)]
enum PagingMode {
FourLevel,
FiveLevel,
}
const MAX_INTERMEDIATE_LEVELS: usize = 4;
const FOUR_LEVEL_INTERMEDIATES: [PageTableLevel; 3] = [
PageTableLevel::Pml4,
PageTableLevel::Pdpt,
PageTableLevel::PageDirectory,
];
const FIVE_LEVEL_INTERMEDIATES: [PageTableLevel; 4] = [
PageTableLevel::Pml5,
PageTableLevel::Pml4,
PageTableLevel::Pdpt,
PageTableLevel::PageDirectory,
];
impl PagingMode {
const fn virtual_address_bits(&self) -> u32 {
match self {
PagingMode::FourLevel => 48,
PagingMode::FiveLevel => 57,
}
}
fn intermediate_levels(&self) -> &'static [PageTableLevel] {
match self {
PagingMode::FourLevel => &FOUR_LEVEL_INTERMEDIATES,
PagingMode::FiveLevel => &FIVE_LEVEL_INTERMEDIATES,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum PageTableLevel {
Pml5,
Pml4,
Pdpt,
PageDirectory,
}
impl PageTableLevel {
const fn index(self, address: usize) -> usize {
let shift = match self {
Self::Pml5 => 48,
Self::Pml4 => 39,
Self::Pdpt => 30,
Self::PageDirectory => 21,
};
address >> shift & 0x1FF
}
const fn large_page_size(self) -> Option<usize> {
match self {
Self::Pdpt => Some(1 << 30),
Self::PageDirectory => Some(1 << 21),
Self::Pml5 | Self::Pml4 => None,
}
}
}
enum PageTableEntryError {
PhysicalAddressTooLarge,
NoExecuteUnsupported,
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
struct PageTableEntry(u64);
impl PageTableEntry {
const PRESENT: u64 = 1 << 0;
const WRITABLE: u64 = 1 << 1;
const USER_ACCESSIBLE: u64 = 1 << 2;
const HUGE_PAGE: u64 = 1 << 7;
const GLOBAL: u64 = 1 << 8;
const NX: u64 = 1 << 63;
const WRITE_THROUGH: u64 = 1 << 3;
const CACHE_DISABLED: u64 = 1 << 4;
// const PAT: u64 = 1 << 7;
const fn new(
physical_address: PhysicalAddr,
permissions: PagePermissions,
cache_policy: CachePolicy,
config: PagingConfig,
global: bool,
) -> Result<Self, PageTableEntryError> {
if physical_address.as_usize() >= config.physical_address_limit() {
return Err(PageTableEntryError::PhysicalAddressTooLarge);
}
let mut value = physical_address.as_usize() as u64 | Self::PRESENT;
if permissions.writable {
value |= Self::WRITABLE;
}
if permissions.user_accessible {
value |= Self::USER_ACCESSIBLE;
}
if !permissions.executable {
if !config.nx_enabled {
return Err(PageTableEntryError::NoExecuteUnsupported);
}
value |= Self::NX;
}
if global && config.global_pages {
value |= Self::GLOBAL;
}
// TODO: these bit positions very by page size
// set PAT
match cache_policy {
CachePolicy::WriteBack => {}
CachePolicy::Uncacheable => {
value |= Self::CACHE_DISABLED | Self::WRITE_THROUGH;
}
};
Ok(Self(value))
}
fn new_table(
frame: FrameAddr,
user_accessible: bool,
config: PagingConfig,
) -> Result<Self, PageTableEntryError> {
if frame.start_address().as_usize() >= config.physical_address_limit() {
return Err(PageTableEntryError::PhysicalAddressTooLarge);
}
let mut value = frame.start_address().as_usize() as u64 | Self::PRESENT | Self::WRITABLE;
if user_accessible {
value |= Self::USER_ACCESSIBLE;
}
Ok(Self(value))
}
const fn null() -> Self {
Self(0)
}
fn physical_address(&self, config: PagingConfig) -> PhysicalAddr {
PhysicalAddr::new(self.0 as usize & config.physical_address_mask())
}
fn is_present(&self) -> bool {
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 {
self.0 & Self::USER_ACCESSIBLE != 0
}
fn is_huge(&self) -> bool {
self.0 & Self::HUGE_PAGE != 0
}
fn is_global(&self) -> bool {
self.0 & Self::GLOBAL != 0
}
fn table_frame(&self, config: PagingConfig) -> Option<FrameAddr> {
if !self.is_present() || self.is_huge() {
return None;
}
FrameAddr::from_start_address(self.physical_address(config))
}
fn leaf_frame(&self, config: PagingConfig) -> Option<FrameAddr> {
if !self.is_present() {
return None;
}
FrameAddr::from_start_address(self.physical_address(config))
}
fn permissions(&self) -> PagePermissions {
PagePermissions::new(
self.writable(),
self.executable(),
self.is_user_accessible(),
)
}
}
pub(crate) enum MapError {
InvalidVirtualAddress,
VirtualAddressUnaligned,
PhysicalAddressTooLarge,
PageAlreadyMapped,
HugePageConflict,
NoExecuteUnsupported,
OutOfFrames,
PageTableOutsideDirectMap,
InvalidPageTableEntry,
}
#[derive(Debug)]
pub(crate) enum UnmapError {
InvalidVirtualAddress,
VirtualAddressUnaligned,
PageNotMapped,
HugePageConflict,
PageTableOutsideDirectMap,
InvalidPageTableEntry,
}
#[derive(Clone, Copy)]
struct EntryLocation {
table: FrameAddr,
index: usize,
}
#[derive(Debug)]
pub(crate) enum PageTableCreateError {
PhysicalAddressTooLarge,
OutOfFrames,
}
pub struct PageTable {
pub direct_map: DirectMap,
config: PagingConfig,
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 {
pub fn new(
direct_map: DirectMap,
config: PagingConfig,
allocator: &mut FrameAllocator,
) -> Result<Self, PageTableCreateError> {
let frame = allocator.alloc().ok_or(PageTableCreateError::OutOfFrames)?;
if frame.frame_address().start_address().as_usize() >= config.physical_address_limit() {
unsafe { allocator.dealloc(frame) };
return Err(PageTableCreateError::PhysicalAddressTooLarge);
}
Ok(Self {
frame,
direct_map,
config,
})
}
pub const fn config(&self) -> PagingConfig {
self.config
}
fn is_active(&self) -> bool {
let cr3 = unsafe { read_cr3(self.config) };
cr3.start_address() == self.frame.frame_address().start_address()
}
fn table(&self, frame: FrameAddr) -> Option<&[PageTableEntry; PAGE_TABLE_ENTRIES]> {
let virtual_addr = self.direct_map.translate(frame.start_address())?;
Some(unsafe { &*(virtual_addr.as_ptr::<[PageTableEntry; PAGE_TABLE_ENTRIES]>()) })
}
fn table_mut(&mut self, frame: FrameAddr) -> Option<&mut [PageTableEntry; PAGE_TABLE_ENTRIES]> {
let virtual_addr = self.direct_map.translate(frame.start_address())?;
Some(unsafe { &mut *(virtual_addr.as_mut_ptr::<[PageTableEntry; PAGE_TABLE_ENTRIES]>()) })
}
fn is_canonical(&self, addr: usize) -> bool {
let bits = self.config.mode.virtual_address_bits();
let shift = usize::BITS - bits;
(((addr << shift) as isize >> shift) as usize) == addr
}
pub fn to_physical(&self, addr: VirtualAddr) -> Option<PhysicalAddr> {
let address = addr.as_usize();
if !self.is_canonical(address) {
return None;
}
let mut table_frame = self.frame.frame_address();
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;
}
if entry.is_huge() {
return translate_huge_page(entry, address, level.large_page_size()?, self.config);
}
table_frame = entry.table_frame(self.config)?;
}
let page_table = self.table(table_frame)?;
let entry = page_table[p1_index(address)];
let physical_base = entry.leaf_frame(self.config)?.start_address().as_usize();
physical_base
.checked_add(page_offset(address))
.map(PhysicalAddr::new)
}
pub fn to_virtual(&self, addr: PhysicalAddr) -> Option<VirtualAddr> {
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(
&self,
parent: FrameAddr,
index: usize,
level: PageTableLevel,
) -> Result<FrameAddr, UnmapError> {
let parent_table = self
.table(parent)
.ok_or(UnmapError::PageTableOutsideDirectMap)?;
let entry = parent_table[index];
if entry.is_huge() {
return if level.large_page_size().is_some() {
Err(UnmapError::HugePageConflict)
} else {
Err(UnmapError::InvalidPageTableEntry)
};
}
entry
.table_frame(self.config)
.ok_or(UnmapError::PageNotMapped)
}
fn apply_user_upgrades(
&mut self,
upgrades: &[Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS],
count: usize,
) {
for location in upgrades[..count].iter().flatten() {
let entry = &mut self
.table_mut(location.table)
.expect("validated page table left the direct map")[location.index];
entry.0 |= PageTableEntry::USER_ACCESSIBLE;
}
}
pub fn map(
&mut self,
mapped_addr: VirtualAddr,
frame: FrameAddr,
permissions: PagePermissions,
allocator: &mut FrameAllocator,
cache_policy: CachePolicy,
global: bool,
) -> Result<(), MapError> {
let address = mapped_addr.as_usize();
if !self.is_canonical(address) {
return Err(MapError::InvalidVirtualAddress);
}
if address % PAGE_SIZE != 0 {
return Err(MapError::VirtualAddressUnaligned);
}
let leaf_entry = PageTableEntry::new(
frame.start_address(),
permissions,
cache_policy,
self.config,
global,
)
.map_err(|error| match error {
PageTableEntryError::PhysicalAddressTooLarge => MapError::PhysicalAddressTooLarge,
PageTableEntryError::NoExecuteUnsupported => MapError::NoExecuteUnsupported,
})?;
let levels = self.config.mode.intermediate_levels();
let mut current_table_frame_addr = self.frame.frame_address();
let mut first_missing = None;
let mut user_upgrades: [Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS] =
[None; MAX_INTERMEDIATE_LEVELS];
let mut user_upgrade_count = 0;
for (depth, &level) in levels.iter().enumerate() {
let index = level.index(address);
let table = self
.table(current_table_frame_addr)
.ok_or(MapError::PageTableOutsideDirectMap)?;
let entry = table[index];
if !entry.is_present() {
first_missing = Some((
depth,
EntryLocation {
table: current_table_frame_addr,
index,
},
));
break;
}
if entry.is_huge() {
return if level.large_page_size().is_some() {
Err(MapError::HugePageConflict)
} else {
Err(MapError::InvalidPageTableEntry)
};
}
if permissions.user_accessible && !entry.is_user_accessible() {
user_upgrades[user_upgrade_count] = Some(EntryLocation {
table: current_table_frame_addr,
index,
});
user_upgrade_count += 1;
}
current_table_frame_addr = entry
.table_frame(self.config)
.ok_or(MapError::InvalidPageTableEntry)?;
}
if first_missing.is_none() {
let pt = self
.table(current_table_frame_addr)
.ok_or(MapError::PageTableOutsideDirectMap)?;
if pt[p1_index(address)].is_present() {
return Err(MapError::PageAlreadyMapped);
}
self.apply_user_upgrades(&user_upgrades, user_upgrade_count);
self.table_mut(current_table_frame_addr)
.expect("validated page table left the direct map")[p1_index(address)] = leaf_entry;
self.flush_tlb_if_active(mapped_addr);
return Ok(());
}
let (missing_depth, publication_location) = first_missing.unwrap();
let private_table_count = levels.len() - missing_depth;
let mut private_tables: [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS] =
core::array::from_fn(|_| None);
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 {
let private_frame = private_tables[private_index]
.as_ref()
.unwrap()
.frame_address();
if private_index + 1 < private_table_count {
let child = private_tables[private_index + 1]
.as_ref()
.unwrap()
.frame_address();
let child_entry =
PageTableEntry::new_table(child, permissions.user_accessible, self.config)
.map_err(|_| MapError::PhysicalAddressTooLarge)?;
let child_index = levels[missing_depth + private_index + 1].index(address);
self.table_mut(private_frame)
.ok_or(MapError::PageTableOutsideDirectMap)?[child_index] = child_entry;
} else {
self.table_mut(private_frame)
.ok_or(MapError::PageTableOutsideDirectMap)?[p1_index(address)] =
leaf_entry;
}
}
PageTableEntry::new_table(
private_tables[0].as_ref().unwrap().frame_address(),
permissions.user_accessible,
self.config,
)
.map_err(|_| MapError::PhysicalAddressTooLarge)
})();
let publication_entry = match prepare_result {
Ok(entry) => entry,
Err(error) => {
for frame in private_tables.into_iter().rev().flatten() {
unsafe { allocator.dealloc(frame) };
}
return Err(error);
}
};
self.apply_user_upgrades(&user_upgrades, user_upgrade_count);
self.table_mut(publication_location.table)
.expect("validated publication table left the direct map")
[publication_location.index] = publication_entry;
// The published page table now owns these frames.
for frame in private_tables.into_iter().flatten() {
let _ = frame.into_raw();
}
self.flush_tlb_if_active(mapped_addr);
Ok(())
}
/// # Safety
///
/// The caller must ensure:
/// - The page being unmapped does not unmap the HHDM, kernel image, or stack
/// - The caller must ensure that the page is not currently in use
pub unsafe fn unmap(
&mut self,
mapped_addr: VirtualAddr,
allocator: &mut FrameAllocator,
) -> Result<FrameAddr, UnmapError> {
if !self.is_canonical(mapped_addr.as_usize()) {
return Err(UnmapError::InvalidVirtualAddress);
}
if mapped_addr.as_usize() % PAGE_SIZE != 0 {
return Err(UnmapError::VirtualAddressUnaligned);
}
let address = mapped_addr.as_usize();
let levels = self.config.mode.intermediate_levels();
let mut table_frames: [Option<FrameAddr>; MAX_INTERMEDIATE_LEVELS + 1] =
core::array::from_fn(|_| None);
table_frames[0] = Some(self.frame.frame_address());
let mut current_table = self.frame.frame_address();
for (depth, &level) in levels.iter().enumerate() {
current_table = self.get_next_level(current_table, level.index(address), level)?;
table_frames[depth + 1] = Some(current_table);
}
let config = self.config;
let page_table = self
.table_mut(current_table)
.ok_or(UnmapError::PageTableOutsideDirectMap)?;
let entry_index = p1_index(address);
let entry = page_table[entry_index];
let frame = entry.leaf_frame(config).ok_or(UnmapError::PageNotMapped)?;
page_table[entry_index] = PageTableEntry::null();
let mut child_is_empty = page_table.iter().all(|entry| !entry.is_present());
let mut deallocatable_frames: [Option<FrameAddr>; MAX_INTERMEDIATE_LEVELS] =
core::array::from_fn(|_| None);
let mut deallocatable_count = 0;
for depth in (0..levels.len()).rev() {
if !child_is_empty {
break;
}
let parent_frame_addr = table_frames[depth].unwrap();
let child_frame = table_frames[depth + 1].unwrap();
self.table_mut(parent_frame_addr)
.expect("validated page table left the direct map")[levels[depth].index(address)] =
PageTableEntry::null();
deallocatable_frames[deallocatable_count] = Some(child_frame);
deallocatable_count += 1;
if depth > 0 {
child_is_empty = self
.table(parent_frame_addr)
.expect("validated page table left the direct map")
.iter()
.all(|entry| !entry.is_present());
}
}
self.flush_tlb_if_active(mapped_addr);
for frame in deallocatable_frames[..deallocatable_count].iter().flatten() {
unsafe { allocator.dealloc(OwnedFrame::from_raw(*frame)) };
}
Ok(frame)
}
pub fn copy_kernel_mappings_to(&self, destination: &mut PageTable) {
let src = self
.table(self.frame.frame_address())
.expect("source page table outside direct map");
let dest = destination
.table_mut(destination.frame.frame_address())
.expect("destination page table outside direct map");
dest[256..512].copy_from_slice(&src[256..512]);
}
fn flush_tlb_if_active(&self, page: VirtualAddr) {
debug_assert!(self.is_canonical(page.as_usize()));
if self.is_active() {
unsafe {
asm!("invlpg [{}]", in(reg) page.as_usize(), options(nostack, preserves_flags));
}
}
}
/// # Safety
///
/// The caller must ensure:
/// - The new space must map the kernel image, stack, and the code being executed
/// - The new space must map the HHDM
pub unsafe fn activate(&self) {
unsafe {
asm!(
"mov cr3, {}",
in(reg) self.frame.frame_address().start_address().as_usize(),
options(nostack, preserves_flags)
);
};
}
fn destroy_children(&mut self, table: FrameAddr, depth: usize, allocator: &mut FrameAllocator) {
let levels = self.config.mode.intermediate_levels();
if depth == levels.len() {
return;
}
for idx in 0..PAGE_TABLE_ENTRIES {
let entry = self.table(table).expect("page table outside direct map")[idx];
if !entry.is_present() || entry.is_huge() {
continue;
}
let child = entry
.table_frame(self.config)
.expect("invalid page table entry");
self.destroy_children(child, depth + 1, allocator);
self.table_mut(table)
.expect("page table outside direct map")[idx] = PageTableEntry::null();
unsafe { allocator.dealloc(OwnedFrame::from_raw(child)) };
}
}
pub unsafe fn destroy(mut self, allocator: &mut FrameAllocator) {
assert!(!self.is_active(), "attempted to destroy active page table");
self.destroy_children(self.frame.frame_address(), 0, allocator);
unsafe { allocator.dealloc(self.frame) };
}
pub unsafe fn destroy_user(mut self, allocator: &mut FrameAllocator) {
assert!(!self.is_active(), "attempted to destroy active page table");
let root_frame = self.frame.frame_address();
for idx in 0..256 {
let entry = self.table(root_frame).expect("table outside direct map")[idx];
if !entry.is_present() || entry.is_huge() {
continue;
}
let child = entry
.table_frame(self.config)
.expect("invalid page table entry");
self.destroy_children(child, 1, allocator);
self.table_mut(root_frame)
.expect("table outside direct map")[idx] = PageTableEntry::null();
unsafe { allocator.dealloc(OwnedFrame::from_raw(child)) };
}
unsafe { allocator.dealloc(self.frame) };
}
}
fn translate_huge_page(
entry: PageTableEntry,
virtual_addr: usize,
page_size: usize,
config: PagingConfig,
) -> Option<PhysicalAddr> {
let physical_base = entry.physical_address(config).as_usize() & !(page_size - 1);
let offset = virtual_addr & (page_size - 1);
physical_base.checked_add(offset).map(PhysicalAddr::new)
}
fn p1_index(addr: usize) -> usize {
(addr >> 12) & 0x1FF
}
fn page_offset(addr: usize) -> usize {
addr & 0xFFF
}
unsafe fn read_cr3(config: PagingConfig) -> FrameAddr {
let value: usize;
unsafe {
asm!(
"mov {}, cr3",
out(reg) value,
options(nomem, nostack, preserves_flags),
);
}
FrameAddr::from_start_address(PhysicalAddr::new(value & config.physical_address_mask()))
.expect("CR3 contains an unaligned page-table address")
}
+23
View File
@@ -0,0 +1,23 @@
use crate::arch::port::write_u8;
const PIT_CHANNEL_0: u16 = 0x40;
const PIT_COMMAND: u16 = 0x43;
const CHANNEL_0: u8 = 0b00 << 6;
// access mode = lobyte/hibyte
const LOW_HIGH: u8 = 0b11 << 4;
// interrupt on terminal count
const MODE_0: u8 = 0b000 << 1;
const BINARY: u8 = 0;
pub const PIT_FREQUENCY: u64 = 1_193_182;
pub const PIT_CALIBRATION_COUNT: u16 = u16::MAX;
pub fn start_pit_one_shot(count: u16) {
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);
}
}
+75 -75
View File
@@ -8,52 +8,52 @@ pub unsafe fn read_u8(port: u16) -> u8 {
"in al, dx",
in("dx") port,
out("al") value,
options(nomem, nostack, preserves_flags),
);
}
value
}
#[inline(always)]
pub unsafe fn read_u8_slice(port: u16, slice: &mut [u8]) {
unsafe {
asm!(
"rep insb",
in("dx") port,
inout("rdi") slice.as_mut_ptr() => _,
inout("rcx") slice.len() => _,
options(nostack, preserves_flags),
);
}
}
#[inline(always)]
pub unsafe fn read_u16(port: u16) -> u16 {
let value: u16;
unsafe {
asm!(
"in ax, dx",
in("dx") port,
out("ax") value,
options(nomem, nostack, preserves_flags),
);
}
value
}
#[inline(always)]
pub unsafe fn read_u32(port: u16) -> u32 {
let value: u32;
unsafe {
asm!(
"in eax, dx",
in("dx") port,
out("eax") value,
options(nomem, nostack, preserves_flags),
);
}
value
}
// #[inline(always)]
// pub unsafe fn read_u8_slice(port: u16, slice: &mut [u8]) {
// unsafe {
// asm!(
// "rep insb",
// in("dx") port,
// inout("rdi") slice.as_mut_ptr() => _,
// inout("rcx") slice.len() => _,
// options(nostack, preserves_flags),
// );
// }
// }
// #[inline(always)]
// pub unsafe fn read_u16(port: u16) -> u16 {
// let value: u16;
// unsafe {
// asm!(
// "in ax, dx",
// in("dx") port,
// out("ax") value,
// options(nomem, nostack, preserves_flags),
// );
// }
// value
// }
// #[inline(always)]
// pub unsafe fn read_u32(port: u16) -> u32 {
// let value: u32;
// unsafe {
// asm!(
// "in eax, dx",
// in("dx") port,
// out("eax") value,
// options(nomem, nostack, preserves_flags),
// );
// }
// value
// }
#[inline(always)]
pub unsafe fn write_u8(port: u16, value: u8) {
@@ -62,44 +62,44 @@ pub unsafe fn write_u8(port: u16, value: u8) {
"out dx, al",
in("dx") port,
in("al") value,
options(nomem, nostack, preserves_flags),
);
}
}
#[inline(always)]
pub unsafe fn write_u8_slice(port: u16, slice: &[u8]) {
unsafe {
asm!(
"rep outsb",
in("dx") port,
inout("rsi") slice.as_ptr() => _,
inout("rcx") slice.len() => _,
options(nostack, preserves_flags),
);
}
}
#[inline(always)]
pub unsafe fn write_u16(port: u16, value: u16) {
unsafe {
asm!(
"out dx, ax",
in("dx") port,
in("ax") value,
options(nomem, nostack, preserves_flags),
);
}
}
// #[inline(always)]
// pub unsafe fn write_u8_slice(port: u16, slice: &[u8]) {
// unsafe {
// asm!(
// "rep outsb",
// in("dx") port,
// inout("rsi") slice.as_ptr() => _,
// inout("rcx") slice.len() => _,
// options(nostack, preserves_flags),
// );
// }
// }
#[inline(always)]
pub unsafe fn write_u32(port: u16, value: u32) {
unsafe {
asm!(
"out dx, eax",
in("dx") port,
in("eax") value,
options(nomem, nostack, preserves_flags),
);
}
}
// #[inline(always)]
// pub unsafe fn write_u16(port: u16, value: u16) {
// unsafe {
// asm!(
// "out dx, ax",
// in("dx") port,
// in("ax") value,
// options(nomem, nostack, preserves_flags),
// );
// }
// }
// #[inline(always)]
// pub unsafe fn write_u32(port: u16, value: u32) {
// unsafe {
// asm!(
// "out dx, eax",
// in("dx") port,
// in("eax") value,
// options(nomem, nostack, preserves_flags),
// );
// }
// }
+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;
}
+82
View File
@@ -0,0 +1,82 @@
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use crate::{
arch::{
apic::{self, LocalApic},
disable_interrupts,
io_apic::IoApic,
x86_64::{
interrupts::enable_interrupts,
pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, start_pit_one_shot},
},
},
platform::acpi::IsaIrqRoute,
};
static PIT_FIRED: AtomicBool = AtomicBool::new(false);
static LAPIC_COUNT_AT_PIT: AtomicU32 = AtomicU32::new(0);
#[derive(Debug)]
pub enum TimerCalibrationError {
PitTimeout,
IoApicNotHandled,
FrequencyOverflow,
InvalidTimerCount,
}
pub fn calibrate_local_apic(
local_apic: &mut LocalApic,
io_apic: &mut IoApic,
pit_route: IsaIrqRoute,
) -> Result<u64, TimerCalibrationError> {
PIT_FIRED.store(false, Ordering::SeqCst);
LAPIC_COUNT_AT_PIT.store(0, Ordering::SeqCst);
io_apic
.unmask(pit_route.gsi)
.map_err(|_| TimerCalibrationError::IoApicNotHandled)?;
local_apic.start_calibration_counter();
start_pit_one_shot(PIT_CALIBRATION_COUNT);
enable_interrupts();
while !PIT_FIRED.load(Ordering::SeqCst) {
if apic::current_timer_count() == 0 {
disable_interrupts();
let _ = io_apic.mask(pit_route.gsi);
local_apic.stop_timer();
return Err(TimerCalibrationError::PitTimeout);
}
core::hint::spin_loop();
}
disable_interrupts();
io_apic
.mask(pit_route.gsi)
.map_err(|_| TimerCalibrationError::IoApicNotHandled)?;
local_apic.stop_timer();
let elapsed = u32::MAX - LAPIC_COUNT_AT_PIT.load(Ordering::SeqCst);
if elapsed == 0 {
return Err(TimerCalibrationError::InvalidTimerCount);
}
let ticks_per_second = (elapsed as u64)
.checked_mul(PIT_FREQUENCY)
.ok_or(TimerCalibrationError::FrequencyOverflow)?
/ PIT_CALIBRATION_COUNT as u64;
Ok(ticks_per_second)
}
pub fn record_pit_calibration() {
let current = apic::current_timer_count();
LAPIC_COUNT_AT_PIT.store(current, Ordering::SeqCst);
PIT_FIRED.store(true, Ordering::SeqCst);
}
+21 -19
View File
@@ -1,20 +1,22 @@
{
"arch": "x86_64",
"cpu": "x86-64",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"llvm-target": "x86_64-unknown-none",
"target-endian": "little",
"target-pointer-width": 64,
"target-c-int-width": 32,
"features": "-mmx,-sse,+soft-float",
"rustc-abi": "softfloat",
"os": "DawnOS",
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"pre-link-args": {
"ld.lld": ["-melf_x86_64", "--script=./src/arch/x86_64/linker.ld"]
},
"panic-strategy": "abort",
"exe-suffix": ".elf",
"disable-redzone": true
}
"arch": "x86_64",
"cpu": "x86-64",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"llvm-target": "x86_64-unknown-none",
"target-endian": "little",
"target-pointer-width": 64,
"target-c-int-width": 32,
"features": "-mmx,-sse,+soft-float",
"rustc-abi": "softfloat",
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"pre-link-args": {
"ld.lld": [
"-melf_x86_64",
"--script=./src/arch/x86_64/linker.ld"
]
},
"panic-strategy": "abort",
"exe-suffix": ".elf",
"disable-redzone": true
}
+178 -30
View File
@@ -1,8 +1,15 @@
use ::limine as limine_api;
use limine::paging::PagingMode;
use limine::request::{ExecutableCmdlineRequest, ModulesRequest, PagingModeRequest, RsdpRequest};
use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest};
use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker};
use crate::memory::{
BootString, InitramfsImage, KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion,
MemoryRegionKind, PagePermissions, PhysicalAddr, VirtualAddr,
};
/// Sets the base revision to the latest revision supported by the crate.
/// See specification for further info.
/// Be sure to mark all limine requests with #[used], otherwise they may be removed by the compiler.
@@ -15,6 +22,14 @@ static BASE_REVISION: BaseRevision = BaseRevision::new();
#[unsafe(link_section = ".requests")]
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]
#[unsafe(link_section = ".requests")]
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
@@ -23,6 +38,15 @@ static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
#[unsafe(link_section = ".requests")]
static MEMMAP_REQUEST: MemmapRequest = MemmapRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static PAGING_REQUEST: PagingModeRequest =
PagingModeRequest::new(PagingMode::MAX, PagingMode::MAX, PagingMode::MIN);
#[used]
#[unsafe(link_section = ".requests")]
static RSDP_REQUEST: RsdpRequest = RsdpRequest::new();
/// Define the stand and end markers for Limine requests.
#[used]
#[unsafe(link_section = ".requests_start_marker")]
@@ -31,35 +55,31 @@ static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
#[unsafe(link_section = ".requests_end_marker")]
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
unsafe extern "C" {
static __text_start: u64;
static __text_end: u64;
static __rodata_start: u64;
static __rodata_end: u64;
static __data_start: u64;
static __data_end: u64;
}
const MAX_COMMAND_LINE_LENGTH: usize = 512;
pub struct BootInfo {
pub kernel_address: crate::memory::PhysicalAddr,
pub hhdm_offset: u64,
entries: &'static [&'static limine_api::memmap::Entry],
pub kernel_layout: KernelMemoryLayout,
pub command_line: BootString<MAX_COMMAND_LINE_LENGTH>,
pub initramfs: InitramfsImage,
pub hhdm_offset: usize,
pub memory_map: MemoryMap,
pub rsdp: VirtualAddr,
}
impl BootInfo {
pub fn memory_regions(&self) -> impl Iterator<Item = crate::memory::MemoryRegion> + '_ {
use crate::memory::{MemoryRegion, MemoryRegionKind};
self.entries.iter().map(|&entry| MemoryRegion {
start: crate::memory::PhysicalAddr::new(entry.base),
length: entry.length,
kind: match entry.type_ {
limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable,
limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved,
limine_api::memmap::MEMMAP_ACPI_RECLAIMABLE => MemoryRegionKind::AcpiReclaimable,
limine_api::memmap::MEMMAP_ACPI_NVS => MemoryRegionKind::AcpiNvs,
limine_api::memmap::MEMMAP_BAD_MEMORY => MemoryRegionKind::BadMemory,
limine_api::memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => {
MemoryRegionKind::BootloaderReclaimable
}
limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES => {
MemoryRegionKind::KernelAndModules
}
limine_api::memmap::MEMMAP_FRAMEBUFFER => MemoryRegionKind::Framebuffer,
limine_api::memmap::MEMMAP_MAPPED_RESERVED => MemoryRegionKind::MappedReserved,
_ => MemoryRegionKind::Reserved,
},
})
pub fn memory_regions(&self) -> impl Iterator<Item = MemoryRegion> + Clone + '_ {
self.memory_map.iter()
}
}
@@ -67,8 +87,14 @@ impl BootInfo {
pub enum BootError {
UnsupportedBaseRevision,
FailedToGetKernelAddress,
FailedToGetKernelCmdline,
FailedToGetModules,
FailedToGetInitramfs,
FailedToGetHHDMAddress,
FailedToGetMemmap,
TooManyMemoryRegions,
FailedToLocateKernel,
FailedToGetRsdp,
}
pub fn load_boot_info() -> Result<BootInfo, BootError> {
@@ -78,21 +104,143 @@ pub fn load_boot_info() -> Result<BootInfo, BootError> {
let kernel_address = KERNEL_ADDRESS_REQUEST
.response()
.ok_or(BootError::FailedToGetKernelAddress)?
.physical_base;
.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
.response()
.ok_or(BootError::FailedToGetHHDMAddress)?
.offset;
.offset as usize;
let rsdp = RSDP_REQUEST.response().ok_or(BootError::FailedToGetRsdp)?;
let memmap = MEMMAP_REQUEST
.response()
.ok_or(BootError::FailedToGetMemmap)?
.entries();
let mut memory_map = MemoryMap::new();
for entry in memmap.iter() {
memory_map
.push(MemoryRegion {
start: PhysicalAddr::new(entry.base as usize),
length: entry.length as usize,
kind: match entry.type_ {
limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable,
limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved,
limine_api::memmap::MEMMAP_ACPI_RECLAIMABLE => {
MemoryRegionKind::AcpiReclaimable
}
limine_api::memmap::MEMMAP_ACPI_NVS => MemoryRegionKind::AcpiNvs,
limine_api::memmap::MEMMAP_BAD_MEMORY => MemoryRegionKind::BadMemory,
limine_api::memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => {
MemoryRegionKind::BootloaderReclaimable
}
limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES => {
MemoryRegionKind::KernelAndModules
}
limine_api::memmap::MEMMAP_FRAMEBUFFER => MemoryRegionKind::Framebuffer,
limine_api::memmap::MEMMAP_MAPPED_RESERVED => MemoryRegionKind::MappedReserved,
_ => MemoryRegionKind::Reserved,
},
})
.map_err(|_| BootError::TooManyMemoryRegions)?;
}
let mut segment_physical = kernel_address.physical_base as usize;
let mut segment_virtual = core::ptr::addr_of!(__text_start) as usize;
let mut segment_length = 0;
let kernel_text_segment = KernelSegment {
physical_base: PhysicalAddr::new(segment_physical),
virtual_base: VirtualAddr::new(segment_virtual),
length: (core::ptr::addr_of!(__text_end) as usize)
- (core::ptr::addr_of!(__text_start) as usize),
permissions: PagePermissions::new(false, true, false),
};
segment_length += kernel_text_segment.length;
segment_virtual = core::ptr::addr_of!(__rodata_start) as usize;
segment_physical = kernel_address.physical_base as usize
+ (segment_virtual - kernel_address.virtual_base as usize);
let kernel_rodata_segment = KernelSegment {
physical_base: PhysicalAddr::new(segment_physical),
virtual_base: VirtualAddr::new(segment_virtual),
length: (core::ptr::addr_of!(__rodata_end) as usize)
- (core::ptr::addr_of!(__rodata_start) as usize),
permissions: PagePermissions::new(false, false, false),
};
segment_length += kernel_rodata_segment.length;
segment_virtual = core::ptr::addr_of!(__data_start) as usize;
segment_physical = kernel_address.physical_base as usize
+ (segment_virtual - kernel_address.virtual_base as usize);
let kernel_data_segment = KernelSegment {
physical_base: PhysicalAddr::new(segment_physical),
virtual_base: VirtualAddr::new(segment_virtual),
length: (core::ptr::addr_of!(__data_end) as usize)
- (core::ptr::addr_of!(__data_start) as usize),
permissions: PagePermissions::new(true, false, false),
};
segment_length += kernel_data_segment.length;
#[cfg(debug_assertions)]
{
let mut kernel_length = None;
for &entry in memmap.iter() {
if entry.type_ != limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES {
continue;
}
if entry.base != kernel_address.physical_base {
continue;
}
kernel_length = Some(entry.length as usize);
break;
}
if kernel_length.is_none() {
return Err(BootError::FailedToLocateKernel);
}
debug_assert_eq!(segment_length, kernel_length.unwrap());
}
Ok(BootInfo {
kernel_address: crate::memory::PhysicalAddr::new(kernel_address),
kernel_layout: KernelMemoryLayout {
segments: [
kernel_text_segment,
kernel_rodata_segment,
kernel_data_segment,
],
},
command_line,
initramfs,
hhdm_offset,
entries: memmap,
memory_map,
rsdp: VirtualAddr::new(rsdp.address as usize),
})
}
+1 -1
View File
@@ -1,3 +1,3 @@
mod limine;
pub use limine::{BootError, BootInfo, load_boot_info};
pub use limine::{BootInfo, load_boot_info};
+8 -2
View File
@@ -9,13 +9,13 @@ mod register {
pub const INTERRUPT_ENABLE: u16 = 1;
pub const DIVISOR_HIGH: u16 = 1;
pub const INTERRUPT_IDENTIFICATION: u16 = 2;
// pub const INTERRUPT_IDENTIFICATION: u16 = 2;
pub const FIFO_CONTROL: u16 = 2;
pub const LINE_CONTROL: u16 = 3;
pub const MODEM_CONTROL: u16 = 4;
pub const LINE_STATUS: u16 = 5;
pub const MODEM_STATUS: u16 = 6;
// pub const MODEM_STATUS: u16 = 6;
pub const SCRATCH: u16 = 7;
}
@@ -160,6 +160,12 @@ pub fn init() -> Result<(), SerialPortError> {
com1().init()
}
pub fn write_bytes(bytes: &[u8]) {
for byte in bytes {
com1().write_byte(*byte);
}
}
pub fn print(args: core::fmt::Arguments) {
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;
+116 -7
View File
@@ -1,4 +1,3 @@
#![feature(abi_x86_interrupt)]
#![allow(clippy::needless_return)]
#![no_std]
#![no_main]
@@ -6,23 +5,133 @@
mod arch;
mod boot;
mod debug;
mod format;
mod memory;
mod platform;
mod syscall;
mod task;
use crate::debug::serial;
use crate::{
debug::serial,
memory::{AddressSpace, MemoryRegionKind, init_frame_allocator, init_kernel_address_space},
};
pub struct KernelHandoff {
allocator: memory::FrameAllocator,
address_space: AddressSpace,
direct_map: memory::DirectMap,
boot_info: boot::BootInfo,
handoff_frame: memory::OwnedFrame,
}
const _: () = assert!(core::mem::size_of::<KernelHandoff>() <= memory::FRAME_SIZE);
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
serial::init().unwrap();
arch::init();
let _boot_info = boot::load_boot_info().unwrap();
let arch_state = arch::init();
let boot_info = boot::load_boot_info().unwrap();
let direct_map = memory::DirectMap::new(boot_info.hhdm_offset);
let mut allocator = memory::FrameAllocator::new(boot_info.memory_regions(), direct_map)
.expect("failed to create frame allocator");
println!("Initializing page table...");
let mut address_space = AddressSpace::new_kernel(
direct_map,
boot_info.memory_regions(),
&boot_info.kernel_layout,
arch_state.paging,
&mut allocator,
)
.expect("failed to create page table");
println!("Entering kernel main...");
let kernel_stack =
crate::task::scheduler::allocate_kernel_stack(&mut address_space, &mut allocator)
.expect("failed to allocate bootstrap stack");
let handoff_frame = allocator
.alloc()
.expect("failed to allocate frame for kernel handoff");
let handoff_addr = address_space
.to_virtual(handoff_frame.frame_address().start_address())
.expect("failed to map kernel handoff");
let bootstrap_stack_top = kernel_stack.top();
let handoff = KernelHandoff {
allocator,
address_space,
direct_map,
boot_info,
handoff_frame,
};
let addr = 0xDEADBEEF as *mut u32;
unsafe {
*addr = 0xDEADBEEF;
handoff_addr.as_mut_ptr::<KernelHandoff>().write(handoff);
(*handoff_addr.as_mut_ptr::<KernelHandoff>())
.address_space
.activate();
arch::enter_kernel(
bootstrap_stack_top,
handoff_addr.as_mut_ptr::<KernelHandoff>(),
);
}
}
pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! {
let (mut allocator, mut address_space, direct_map, boot_info, handoff_frame) = unsafe {
let handoff = handoff.read();
(
handoff.allocator,
handoff.address_space,
handoff.direct_map,
handoff.boot_info,
handoff.handoff_frame,
)
};
unsafe {
allocator.dealloc(handoff_frame);
}
hcf();
allocator.reclaim_regions(
boot_info.memory_regions(),
MemoryRegionKind::BootloaderReclaimable,
);
println!("Initializing local ACPI...",);
let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI");
println!("Parsing MADT...");
let madt = acpi
.madt()
.expect("failed to parse ACPI")
.expect("MADT not found");
println!("Initializing interrupt controller...");
let _interrupt_controller =
arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space)
.expect("failed to initialize interrupt controller");
task::bootstrap::spawn(
"omega3.elf",
&boot_info.initramfs,
&mut address_space,
&mut allocator,
direct_map,
);
init_frame_allocator(allocator);
init_kernel_address_space(address_space);
task::scheduler::start();
}
#[panic_handler]
+460
View File
@@ -0,0 +1,460 @@
use core::cell::UnsafeCell;
use crate::{
arch::{PageTable, PageTableCreateError, PageTableMapError, PageTableUnmapError, PagingConfig},
memory::{
CachePolicy, DirectMap, FRAME_SIZE, FrameAddr, FrameAllocator, KernelMemoryLayout,
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)]
pub enum MapError {
InvalidVirtualAddress,
VirtualAddressUnaligned,
PhysicalAddressTooLarge,
PhysicalAddressUnaligned,
RangeLengthUnaligned,
AddressOverflow,
AlreadyMapped,
MappingConflict,
UnsupportedPermissions,
OutOfMemory,
PageTableUnavailable,
CorruptedPageTable,
InvalidUserAddress,
InvalidUserMap,
}
impl From<PageTableMapError> for MapError {
fn from(error: PageTableMapError) -> Self {
match error {
PageTableMapError::InvalidVirtualAddress => Self::InvalidVirtualAddress,
PageTableMapError::VirtualAddressUnaligned => Self::VirtualAddressUnaligned,
PageTableMapError::PhysicalAddressTooLarge => Self::PhysicalAddressTooLarge,
PageTableMapError::PageAlreadyMapped => Self::AlreadyMapped,
PageTableMapError::HugePageConflict => Self::MappingConflict,
PageTableMapError::NoExecuteUnsupported => Self::UnsupportedPermissions,
PageTableMapError::OutOfFrames => Self::OutOfMemory,
PageTableMapError::PageTableOutsideDirectMap => Self::PageTableUnavailable,
PageTableMapError::InvalidPageTableEntry => Self::CorruptedPageTable,
}
}
}
#[derive(Debug)]
pub enum UnmapError {
InvalidVirtualAddress,
VirtualAddressUnaligned,
NotMapped,
MappingConflict,
PageTableUnavailable,
CorruptedPageTable,
InvalidUserAddress,
}
impl From<PageTableUnmapError> for UnmapError {
fn from(error: PageTableUnmapError) -> Self {
match error {
PageTableUnmapError::InvalidVirtualAddress => Self::InvalidVirtualAddress,
PageTableUnmapError::VirtualAddressUnaligned => Self::VirtualAddressUnaligned,
PageTableUnmapError::PageNotMapped => Self::NotMapped,
PageTableUnmapError::HugePageConflict => Self::MappingConflict,
PageTableUnmapError::PageTableOutsideDirectMap => Self::PageTableUnavailable,
PageTableUnmapError::InvalidPageTableEntry => Self::CorruptedPageTable,
}
}
}
#[derive(Debug)]
#[allow(unused)]
pub enum AddressSpaceCreateError {
AddressOutsideDirectMap,
PhysicalAddressTooLarge,
OutOfMemory,
Map(MapError),
}
impl From<PageTableCreateError> for AddressSpaceCreateError {
fn from(error: PageTableCreateError) -> Self {
match error {
PageTableCreateError::PhysicalAddressTooLarge => Self::PhysicalAddressTooLarge,
PageTableCreateError::OutOfFrames => Self::OutOfMemory,
}
}
}
pub struct PageTableMapping {
pub permissions: PagePermissions,
}
#[derive(PartialEq, Eq)]
enum AddressSpaceKind {
Kernel,
User,
}
#[derive(PartialEq, Eq)]
pub struct AddressSpace {
root: PageTable,
kind: AddressSpaceKind,
}
impl AddressSpace {
pub fn new_kernel<I: Iterator<Item = MemoryRegion> + Clone>(
direct_map: DirectMap,
memory_regions: I,
layout: &KernelMemoryLayout,
paging_config: PagingConfig,
allocator: &mut FrameAllocator,
) -> Result<Self, AddressSpaceCreateError> {
let mut space = AddressSpace {
root: PageTable::new(direct_map, paging_config, allocator)?,
kind: AddressSpaceKind::Kernel,
};
// map hhdm
for region in memory_regions.clone() {
// we exlcude KernelAndModules from the hhdm because if it were mapped, it would
// undermind the permissions of the explicitly mapped kernel image
if matches!(
region.kind,
MemoryRegionKind::Reserved | MemoryRegionKind::BadMemory
) {
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!(
region.kind,
MemoryRegionKind::MappedReserved | MemoryRegionKind::Framebuffer
) {
CachePolicy::Uncacheable
} else {
CachePolicy::WriteBack
};
let res = space.map_range(
region.start,
direct_map
.translate(region.start)
.ok_or(AddressSpaceCreateError::AddressOutsideDirectMap)?,
region.length,
PagePermissions::new(true, false, false),
allocator,
cache_policy,
);
if let Err(err) = res {
unsafe { space.destroy(allocator) };
return Err(AddressSpaceCreateError::Map(err));
}
}
let mut old_segment_stop: Option<usize> = None;
for segment in layout.segments.iter() {
if let Some(stop) = old_segment_stop {
debug_assert_eq!(segment.physical_base.as_usize(), stop);
}
old_segment_stop = Some(segment.physical_base.as_usize() + segment.length);
let res = space.map_range(
segment.physical_base,
segment.virtual_base,
segment.length,
segment.permissions,
allocator,
CachePolicy::WriteBack,
);
if let Err(err) = res {
unsafe { space.destroy(allocator) };
return Err(AddressSpaceCreateError::Map(err));
}
}
Ok(space)
}
pub fn new_user(&self, allocator: &mut FrameAllocator) -> Result<Self, PageTableCreateError> {
let mut user_root = PageTable::new(self.root.direct_map, self.root.config(), allocator)?;
self.root.copy_kernel_mappings_to(&mut user_root);
Ok(Self {
root: user_root,
kind: AddressSpaceKind::User,
})
}
pub fn map(
&mut self,
physical_addr: PhysicalAddr,
virtual_addr: VirtualAddr,
permissions: PagePermissions,
allocator: &mut FrameAllocator,
cache_policy: CachePolicy,
) -> Result<(), MapError> {
let global = self.kind == AddressSpaceKind::Kernel;
if self.kind == AddressSpaceKind::User {
if virtual_addr.as_usize() >= USER_SPACE_END.as_usize() {
return Err(MapError::InvalidUserAddress);
}
if !permissions.user_accessible {
return Err(MapError::InvalidUserMap);
}
// TODO: a user address space should not be able to map kernel memory
// or ACPI memory, or anything like that
}
let frame = FrameAddr::from_start_address(physical_addr)
.ok_or(MapError::PhysicalAddressUnaligned)?;
self.root
.map(
virtual_addr,
frame,
permissions,
allocator,
cache_policy,
global,
)
.map_err(MapError::from)
}
pub fn map_range(
&mut self,
physical_start: PhysicalAddr,
virtual_start: VirtualAddr,
length: usize,
permissions: PagePermissions,
allocator: &mut FrameAllocator,
cache_policy: CachePolicy,
) -> Result<(), MapError> {
if length == 0 {
return Ok(());
}
if physical_start.as_usize() % FRAME_SIZE != 0 {
return Err(MapError::PhysicalAddressUnaligned);
}
if virtual_start.as_usize() % FRAME_SIZE != 0 {
return Err(MapError::VirtualAddressUnaligned);
}
if length % FRAME_SIZE != 0 {
return Err(MapError::RangeLengthUnaligned);
}
let last_offset = length - FRAME_SIZE;
physical_start
.as_usize()
.checked_add(last_offset)
.ok_or(MapError::AddressOverflow)?;
virtual_start
.as_usize()
.checked_add(last_offset)
.ok_or(MapError::AddressOverflow)?;
let page_count = length / FRAME_SIZE;
let mut mapped_pages = 0;
while mapped_pages < page_count {
let offset = mapped_pages * FRAME_SIZE;
let physical_addr = PhysicalAddr::new(physical_start.as_usize() + offset);
let virtual_addr = VirtualAddr::new(virtual_start.as_usize() + offset);
if let Err(err) = self.map(
physical_addr,
virtual_addr,
permissions,
allocator,
cache_policy,
) {
for rollback_idx in (0..mapped_pages).rev() {
let rollback_offset = rollback_idx * FRAME_SIZE;
unsafe {
// make sure we use the root page tableq directl since the public AddressSpace API
// might *at some point* reject kernel mappings
self.root
.unmap(
VirtualAddr::new(virtual_start.as_usize() + rollback_offset),
allocator,
)
.expect("failed to roll back a mapped page");
}
}
return Err(err);
}
mapped_pages += 1;
}
Ok(())
}
/// # Safety
///
/// The caller must ensure:
/// - The page is not currently in use
pub unsafe fn unmap(
&mut self,
virtual_addr: VirtualAddr,
allocator: &mut FrameAllocator,
) -> Result<FrameAddr, UnmapError> {
if self.kind == AddressSpaceKind::User && virtual_addr.as_usize() >= 0x0000_8000_0000_0000 {
return Err(UnmapError::InvalidUserAddress);
}
unsafe { self.root.unmap(virtual_addr, allocator) }.map_err(UnmapError::from)
}
pub fn to_physical(&self, virtual_addr: VirtualAddr) -> Option<PhysicalAddr> {
self.root.to_physical(virtual_addr)
}
pub fn to_virtual(&self, physical_addr: PhysicalAddr) -> Option<VirtualAddr> {
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) {
unsafe { self.root.activate() }
}
/// # Safety
///
/// The caller must ensure this apge table is not active on any CPU and
/// no CPU or kernel operation can access its paging structures.
pub unsafe fn destroy(self, allocator: &mut FrameAllocator) {
unsafe {
match self.kind {
AddressSpaceKind::Kernel => self.root.destroy(allocator),
AddressSpaceKind::User => self.root.destroy_user(allocator),
}
}
}
}
+397
View File
@@ -0,0 +1,397 @@
use core::cell::UnsafeCell;
use crate::memory::{DirectMap, MemoryRegion, MemoryRegionKind, PhysicalAddr, VirtualAddr};
pub const FRAME_SIZE: usize = 4096;
pub fn align_up_to_frame(addr: usize) -> Option<usize> {
addr.checked_add(FRAME_SIZE as usize - 1)
.map(|addr| addr & !(FRAME_SIZE - 1))
}
pub fn align_down_to_frame(addr: usize) -> usize {
addr & !(FRAME_SIZE - 1)
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum FrameState {
Reserved = 0b00,
Free = 0b01,
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
struct Bitmap {
start: VirtualAddr,
frame_count: usize,
}
impl Bitmap {
fn new(start: VirtualAddr, frame_count: usize) -> Self {
Self { start, frame_count }
}
fn state(&self, frame_idx: usize) -> FrameState {
assert!(frame_idx < self.frame_count, "frame index out of bounds");
let byte_idx = frame_idx / 4;
let shift = (frame_idx % 4) * 2;
let byte = unsafe { self.start.as_ptr::<u8>().add(byte_idx).read() };
match (byte >> shift) & 0b11 {
0b00 => FrameState::Reserved,
0b01 => FrameState::Free,
0b10 => FrameState::Allocated,
_ => panic!("invalid frame state"),
}
}
fn set_state(&mut self, frame_idx: usize, state: FrameState) {
assert!(frame_idx < self.frame_count, "frame index out of bounds");
let byte_idx = frame_idx / 4;
let shift = (frame_idx % 4) * 2;
let ptr = unsafe { self.start.as_mut_ptr::<u8>().add(byte_idx) };
let byte = unsafe { ptr.read() };
let mask = 0b11 << shift;
unsafe {
ptr.write((byte & !mask) | ((state as u8) << shift));
}
}
}
#[derive(Debug)]
pub enum FrameAllocatorInitError {
AddressOverflow,
NoUsableFrames,
NoBitmapStorage,
BitmapOutsideDirectMap,
}
// very very simple bitmap frame/page allocator
pub struct FrameAllocator {
bitmap: Bitmap,
next_search: usize,
allocatable_frames: usize,
free_frames: usize,
direct_map: DirectMap,
}
impl FrameAllocator {
pub fn new<I>(regions: I, direct_map: DirectMap) -> Result<Self, FrameAllocatorInitError>
where
I: Iterator<Item = MemoryRegion> + Clone,
{
let mut highest_frame: Option<usize> = None;
for region in regions.clone() {
if !Self::should_track(region.kind) {
continue;
}
let range = Self::usable_frame_range(region)?;
if range.is_empty() {
continue;
}
highest_frame = Some(highest_frame.map_or(range.end, |current| current.max(range.end)));
}
let highest_frame = highest_frame.ok_or(FrameAllocatorInitError::NoUsableFrames)?;
let bitmap_bytes = highest_frame.div_ceil(4);
let bitmap_frame_count = bitmap_bytes.div_ceil(FRAME_SIZE);
let bitmap_storage_bytes = bitmap_frame_count
.checked_mul(FRAME_SIZE)
.ok_or(FrameAllocatorInitError::AddressOverflow)?;
let mut bitmap_start_frame: Option<usize> = None;
for region in regions.clone() {
if !Self::can_store_bitmap(region.kind) {
continue;
}
let range = Self::usable_frame_range(region)?;
if range.is_empty() {
continue;
}
if range.len() < bitmap_frame_count {
continue;
}
bitmap_start_frame = Some(range.start);
break;
}
if bitmap_start_frame.is_none() {
return Err(FrameAllocatorInitError::NoBitmapStorage);
}
let bitmap_start_frame = bitmap_start_frame.unwrap();
let bitmap_physical_addr = PhysicalAddr::new(bitmap_start_frame * FRAME_SIZE);
let bitmap_virtual = direct_map
.translate(bitmap_physical_addr)
.ok_or(FrameAllocatorInitError::BitmapOutsideDirectMap)?;
unsafe {
// set everyting to unavailable
core::ptr::write_bytes(bitmap_virtual.as_mut_ptr::<u8>(), 0, bitmap_storage_bytes);
}
let mut allocatable_frames = 0;
let mut free_frames = 0;
let mut bitmap = Bitmap::new(bitmap_virtual, highest_frame);
let bitmap_end_frame = bitmap_start_frame
.checked_add(bitmap_frame_count)
.ok_or(FrameAllocatorInitError::AddressOverflow)?;
for region in regions {
if !Self::is_initially_free(region.kind) {
continue;
}
for frame_idx in Self::usable_frame_range(region)? {
let is_bitmap_storage =
frame_idx >= bitmap_start_frame && frame_idx < bitmap_end_frame;
if is_bitmap_storage {
continue;
}
bitmap.set_state(frame_idx, FrameState::Free);
allocatable_frames += 1;
free_frames += 1;
}
}
Ok(Self {
bitmap,
next_search: bitmap_start_frame + bitmap_frame_count,
allocatable_frames,
free_frames,
direct_map,
})
}
fn should_track(kind: MemoryRegionKind) -> bool {
matches!(
kind,
MemoryRegionKind::Usable
| MemoryRegionKind::BootloaderReclaimable
| MemoryRegionKind::AcpiReclaimable
)
}
fn can_store_bitmap(kind: MemoryRegionKind) -> bool {
kind == MemoryRegionKind::Usable
}
fn is_initially_free(kind: MemoryRegionKind) -> bool {
kind == MemoryRegionKind::Usable
}
fn find_free_in(&self, start: usize, end: usize) -> Option<usize> {
(start..end).find(|&index| self.bitmap.state(index) == FrameState::Free)
}
fn find_free_frame(&self) -> Option<usize> {
self.find_free_in(self.next_search, self.bitmap.frame_count)
.or_else(|| self.find_free_in(0, self.next_search))
}
pub fn reclaim_regions<I: Iterator<Item = MemoryRegion> + Clone>(
&mut self,
memory_map: I,
region_kind: MemoryRegionKind,
) {
if !matches!(
region_kind,
MemoryRegionKind::BootloaderReclaimable | MemoryRegionKind::AcpiReclaimable
) {
return;
}
for region in memory_map {
if region.kind == region_kind {
for frame_idx in Self::usable_frame_range(region).expect("invalid memory region") {
if self.bitmap.state(frame_idx) != FrameState::Reserved {
continue;
}
self.bitmap.set_state(frame_idx, FrameState::Free);
self.allocatable_frames += 1;
self.free_frames += 1;
self.next_search = self.next_search.min(frame_idx);
}
}
}
}
pub fn alloc_nozero(&mut self) -> Option<OwnedFrame> {
if self.free_frames == 0 {
return None;
}
let frame_idx = self.find_free_frame()?;
self.bitmap.set_state(frame_idx, FrameState::Allocated);
self.free_frames -= 1;
self.next_search = frame_idx.saturating_add(1);
Some(OwnedFrame::new(FrameAddr::from_index(frame_idx)))
}
pub fn alloc(&mut self) -> Option<OwnedFrame> {
let frame = self.alloc_nozero()?;
let start = self
.direct_map
.translate(frame.frame_address().start_address())
.expect("frame is outside the direct map");
unsafe {
core::ptr::write_bytes(start.as_mut_ptr::<u8>(), 0, FRAME_SIZE);
}
Some(frame)
}
/// # Safety
///
/// The caller must ensure:
/// - The frame is currently owned by the caller
/// - it is not currently in use
/// - it has not been freed
pub unsafe fn dealloc(&mut self, frame: OwnedFrame) {
let frame_idx = frame.index();
match self.bitmap.state(frame_idx) {
FrameState::Allocated => {
self.bitmap.set_state(frame_idx, FrameState::Free);
self.free_frames += 1;
self.next_search = self.next_search.min(frame_idx);
}
FrameState::Free => panic!("attempted to free free frame"),
FrameState::Reserved => {
panic!("attempted to free reserved frame");
}
};
}
fn usable_frame_range(
region: MemoryRegion,
) -> Result<core::ops::Range<usize>, FrameAllocatorInitError> {
let region_end = region
.start
.as_usize()
.checked_add(region.length)
.ok_or(FrameAllocatorInitError::AddressOverflow)?;
let start = align_up_to_frame(region.start.as_usize())
.ok_or(FrameAllocatorInitError::AddressOverflow)?;
let end = align_down_to_frame(region_end);
Ok((start / FRAME_SIZE)..(end / FRAME_SIZE))
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameAddr(PhysicalAddr);
impl FrameAddr {
pub fn from_start_address(address: PhysicalAddr) -> Option<Self> {
if address.as_usize() % FRAME_SIZE != 0 {
return None;
}
Some(Self(address))
}
pub fn start_address(&self) -> PhysicalAddr {
self.0
}
fn from_index(index: usize) -> Self {
Self(PhysicalAddr::new(index * FRAME_SIZE))
}
fn index(&self) -> usize {
self.0.as_usize() / FRAME_SIZE
}
}
// specifically not Clone or Copy
pub struct OwnedFrame {
frame: FrameAddr,
}
impl OwnedFrame {
fn new(frame: FrameAddr) -> Self {
Self { frame }
}
pub fn into_raw(self) -> FrameAddr {
self.frame
}
pub unsafe fn from_raw(frame: FrameAddr) -> Self {
Self { frame }
}
pub fn frame_address(&self) -> FrameAddr {
self.frame
}
pub fn index(&self) -> usize {
self.frame.index()
}
}
+168 -12
View File
@@ -1,36 +1,128 @@
mod address_space;
mod frame;
mod stack;
mod user;
use core::ops::Add;
#[allow(unused)]
pub use address_space::{
AddressSpace, AddressSpaceCreateError, AddressSpaceId, MapError, PageTableMapping, UnmapError,
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 physical_base: PhysicalAddr,
pub virtual_base: VirtualAddr,
pub length: usize,
pub permissions: PagePermissions,
}
pub struct KernelMemoryLayout {
pub segments: [KernelSegment; 3],
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PagePermissions {
pub writable: bool,
pub executable: bool,
pub user_accessible: bool,
}
impl PagePermissions {
pub const fn new(writable: bool, executable: bool, user_accessible: bool) -> Self {
Self {
writable,
executable,
user_accessible,
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PhysicalAddr(u64);
pub struct PhysicalAddr(usize);
impl PhysicalAddr {
pub fn new(addr: u64) -> Self {
pub const fn new(addr: usize) -> Self {
Self(addr)
}
pub const fn as_u64(self) -> u64 {
pub const fn as_usize(self) -> usize {
self.0
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VirtualAddr(u64);
pub struct VirtualAddr(usize);
impl VirtualAddr {
pub fn new(addr: u64) -> Self {
pub const fn new(addr: usize) -> Self {
Self(addr)
}
pub const fn as_u64(self) -> u64 {
self.0
pub const fn as_usize(self) -> usize {
self.0 as usize
}
pub unsafe fn as_mut_ptr<T>(self) -> *mut T {
self.as_u64() as *mut T
pub const unsafe fn as_mut_ptr<T>(self) -> *mut T {
self.as_usize() as *mut T
}
pub const unsafe fn as_ptr<T>(self) -> *const T {
self.as_usize() as *const T
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
impl Add<usize> for VirtualAddr {
type Output = Self;
fn add(self, rhs: usize) -> Self::Output {
Self(self.0 + rhs)
}
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum MemoryRegionKind {
Usable,
Reserved,
@@ -43,9 +135,73 @@ pub enum MemoryRegionKind {
MappedReserved,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy)]
pub enum CachePolicy {
Uncacheable,
WriteBack,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct MemoryRegion {
pub start: PhysicalAddr,
pub length: u64,
pub length: usize,
pub kind: MemoryRegionKind,
}
#[derive(Clone, Copy)]
pub struct DirectMap {
offset: usize,
}
impl DirectMap {
pub fn new(offset: usize) -> Self {
Self { offset }
}
pub fn translate(self, addr: PhysicalAddr) -> Option<VirtualAddr> {
addr.as_usize()
.checked_add(self.offset)
.map(VirtualAddr::new)
}
}
const EMPTY_MEMORY_REGION: MemoryRegion = MemoryRegion {
start: PhysicalAddr::new(0),
length: 0,
kind: MemoryRegionKind::Reserved,
};
const MAX_MEMORY_REGIONS: usize = 128;
pub enum MemoryMapError {
TooManyRegions,
}
#[derive(Clone, Copy)]
pub struct MemoryMap {
pub entries: [MemoryRegion; MAX_MEMORY_REGIONS],
pub len: usize,
}
impl MemoryMap {
pub const fn new() -> Self {
Self {
entries: [EMPTY_MEMORY_REGION; MAX_MEMORY_REGIONS],
len: 0,
}
}
pub fn push(&mut self, entry: MemoryRegion) -> Result<(), MemoryMapError> {
if self.len >= MAX_MEMORY_REGIONS {
return Err(MemoryMapError::TooManyRegions);
}
self.entries[self.len] = entry;
self.len += 1;
Ok(())
}
pub fn iter(&self) -> impl Iterator<Item = MemoryRegion> + Clone + '_ {
self.entries[..self.len].iter().copied()
}
}
+249
View File
@@ -0,0 +1,249 @@
use crate::memory::{
AddressSpace, CachePolicy, FRAME_SIZE, FrameAllocator, MapError, OwnedFrame, PagePermissions,
VirtualAddr,
};
const GUARD_PAGES: usize = 1; // 4KiB
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 USER_STACK_TOP: VirtualAddr = VirtualAddr::new(0x0000_7FFF_FFFF_F000);
#[derive(Debug)]
pub enum StackCreateError {
AddressOverflow,
UnalignedStackTop,
OutOfFrames,
OutOfStacks,
GuardPageMapped,
Map(MapError),
}
#[derive(Debug)]
struct StackMapping {
guard_page: VirtualAddr,
mapped_start: VirtualAddr,
stack_size: usize,
top: VirtualAddr,
}
impl StackMapping {
fn allocate(
address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
stack_size: usize,
top: VirtualAddr,
permissions: PagePermissions,
) -> Result<Self, StackCreateError> {
if top.as_usize() % FRAME_SIZE != 0 {
return Err(StackCreateError::UnalignedStackTop);
}
let mapped_start = VirtualAddr::new(
top.as_usize()
.checked_sub(stack_size)
.ok_or(StackCreateError::AddressOverflow)?,
);
let guard_page = VirtualAddr::new(
mapped_start
.as_usize()
.checked_sub(FRAME_SIZE)
.ok_or(StackCreateError::AddressOverflow)?,
);
if address_space.to_physical(guard_page).is_some() {
return Err(StackCreateError::GuardPageMapped);
}
let mut mapped_pages = 0;
while mapped_pages < stack_size / FRAME_SIZE {
let virtual_address = VirtualAddr::new(
mapped_start
.as_usize()
.checked_add(mapped_pages * FRAME_SIZE)
.ok_or(StackCreateError::AddressOverflow)?,
);
let frame = match allocator.alloc() {
Some(frame) => frame,
None => {
Self::rollback(address_space, allocator, mapped_start, mapped_pages);
return Err(StackCreateError::OutOfFrames);
}
};
if let Err(error) = address_space.map(
frame.frame_address().start_address(),
virtual_address,
permissions,
allocator,
CachePolicy::WriteBack,
) {
unsafe { allocator.dealloc(frame) };
Self::rollback(address_space, allocator, mapped_start, mapped_pages);
return Err(StackCreateError::Map(error));
}
let _ = frame.into_raw();
mapped_pages += 1;
}
debug_assert!(address_space.to_physical(guard_page).is_none());
Ok(Self {
guard_page,
mapped_start,
stack_size,
top,
})
}
fn rollback(
address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
mapped_start: VirtualAddr,
mapped_pages: usize,
) {
for page in (0..mapped_pages).rev() {
let virtual_address = VirtualAddr::new(mapped_start.as_usize() + page * FRAME_SIZE);
let frame = unsafe {
address_space
.unmap(virtual_address, allocator)
.expect("failed to roll back stack mapping")
};
unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) };
}
}
/// # Safety
///
/// 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.
unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) {
for page in (0..self.stack_size / FRAME_SIZE).rev() {
let virtual_address =
VirtualAddr::new(self.mapped_start.as_usize() + page * FRAME_SIZE);
let frame = unsafe {
address_space
.unmap(virtual_address, allocator)
.expect("stack mapping was unexpectedly missing")
};
unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) };
}
debug_assert!(address_space.to_physical(self.guard_page).is_none());
}
}
#[derive(Debug)]
pub struct KernelStack {
mapping: StackMapping,
}
impl KernelStack {
pub fn allocate(
address_space: &mut AddressSpace,
top: usize,
allocator: &mut FrameAllocator,
) -> Result<Self, StackCreateError> {
let mapping = StackMapping::allocate(
address_space,
allocator,
KERNEL_STACK_SIZE,
VirtualAddr::new(top),
PagePermissions::new(true, false, false),
)?;
Ok(Self { mapping })
}
pub const fn top(&self) -> VirtualAddr {
self.mapping.top
}
/// # Safety
///
/// 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.
pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) {
unsafe { self.mapping.destroy(address_space, allocator) };
}
}
pub struct UserStack {
mapping: StackMapping,
}
impl UserStack {
pub fn allocate(
address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
) -> Result<Self, StackCreateError> {
let top = VirtualAddr::new(USER_STACK_TOP.as_usize());
let mapping = StackMapping::allocate(
address_space,
allocator,
USER_STACK_SIZE,
top,
PagePermissions::new(true, false, true),
)?;
Ok(Self { mapping })
}
pub const fn top(&self) -> VirtualAddr {
self.mapping.top
}
/// # Safety
///
/// The caller must ensure this stack is not active in any thread and cannot be accessed while
/// it is being destroyed.
pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) {
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)
}
+652
View File
@@ -0,0 +1,652 @@
use crate::memory::{DirectMap, PhysicalAddr, VirtualAddr};
#[derive(Debug)]
pub enum AcpiError {
InvalidInput,
AddressOverflow,
InvalidSdtLength,
InvalidRootTableLength,
MalformedAcpiTable,
MalformedMadt,
MissingIoApic,
MultipleIoApicsUnsupported,
}
pub struct AcpiTables {
direct_map: DirectMap,
root: RootTable,
}
impl AcpiTables {
pub fn madt(&self) -> Result<Option<Madt<'_>>, AcpiError> {
if let Some(table) = self.find_table(*b"APIC")? {
return Ok(Some(Madt::parse(self, table)?));
};
return Ok(None);
}
fn read<T: Copy>(&self, physical_addr: PhysicalAddr) -> Result<T, AcpiError> {
let virtual_addr = self
.direct_map
.translate(physical_addr)
.ok_or(AcpiError::AddressOverflow)?;
Ok(unsafe { core::ptr::read_unaligned(virtual_addr.as_ptr::<T>()) })
}
fn checksum_valid(
&self,
physical_addr: PhysicalAddr,
length: usize,
) -> Result<bool, AcpiError> {
let virtual_addr = self
.direct_map
.translate(physical_addr)
.ok_or(AcpiError::AddressOverflow)?;
let mut sum: u8 = 0;
for i in 0..length {
let byte_addr = virtual_addr
.as_usize()
.checked_add(i)
.ok_or(AcpiError::AddressOverflow)?;
let byte = unsafe { core::ptr::read_unaligned(byte_addr as *const u8) };
sum = sum.wrapping_add(byte);
}
Ok(sum == 0)
}
pub unsafe fn from_rsdp(
rsdp_ptr: VirtualAddr,
direct_map: DirectMap,
) -> Result<Self, AcpiError> {
let rsdp = unsafe { core::ptr::read_unaligned(rsdp_ptr.as_ptr::<Rsdp>()) };
let mut sum: u8 = 0;
for i in 0..core::mem::size_of::<Rsdp>() as usize {
sum = sum.wrapping_add(unsafe {
core::ptr::read_unaligned::<u8>(rsdp_ptr.as_ptr::<u8>().add(i))
});
}
if sum != 0 {
return Err(AcpiError::MalformedAcpiTable);
}
if rsdp.signature != *b"RSD PTR " {
return Err(AcpiError::MalformedAcpiTable);
}
let root_table = if rsdp.revision >= 2 {
let xsdp = unsafe { core::ptr::read_unaligned(rsdp_ptr.as_ptr::<Xsdp>()) };
if xsdp.length < core::mem::size_of::<Xsdp>() as u32 {
return Err(AcpiError::MalformedAcpiTable);
}
sum = 0;
for i in 0..xsdp.length as usize {
sum = sum.wrapping_add(unsafe {
core::ptr::read_unaligned::<u8>(rsdp_ptr.as_ptr::<u8>().add(i))
});
}
if sum != 0 {
return Err(AcpiError::MalformedAcpiTable);
}
let xsdt_address = PhysicalAddr::new(xsdp.xsdt_address as usize);
let xsdt_virtual_addr = unsafe {
direct_map
.translate(xsdt_address)
.ok_or(AcpiError::AddressOverflow)?
.as_ptr::<SDTHeader>()
};
let xsdt = unsafe { core::ptr::read_unaligned(xsdt_virtual_addr) };
sum = 0;
for i in 0..xsdt.length as usize {
sum = sum.wrapping_add(unsafe {
core::ptr::read_unaligned::<u8>(xsdt_virtual_addr.cast::<u8>().add(i))
});
}
if sum != 0 {
return Err(AcpiError::MalformedAcpiTable);
}
if &xsdt.signature != b"XSDT" {
return Err(AcpiError::MalformedAcpiTable);
}
RootTable::Xsdt(Sdt {
physical_addr: xsdt_address,
length: xsdt.length as usize,
signature: xsdt.signature,
})
} else {
let rsdt_address = PhysicalAddr::new(rsdp.rsdt_address as usize);
let rsdt_virtual_addr = unsafe {
direct_map
.translate(rsdt_address)
.ok_or(AcpiError::AddressOverflow)?
.as_ptr::<SDTHeader>()
};
let rsdt = unsafe { core::ptr::read_unaligned(rsdt_virtual_addr) };
sum = 0;
for i in 0..rsdt.length as usize {
sum = sum.wrapping_add(unsafe {
core::ptr::read_unaligned::<u8>(rsdt_virtual_addr.cast::<u8>().add(i))
});
}
if sum != 0 {
return Err(AcpiError::MalformedAcpiTable);
}
if &rsdt.signature != b"RSDT" {
return Err(AcpiError::MalformedAcpiTable);
}
RootTable::Rsdt(Sdt {
physical_addr: rsdt_address,
length: rsdt.length as usize,
signature: rsdt.signature,
})
};
Ok(Self {
direct_map: direct_map,
root: root_table,
})
}
pub fn find_table(&self, signature: [u8; 4]) -> Result<Option<Sdt>, AcpiError> {
let root = self.root.table();
let entry_width = self.root.entry_width();
let entries_start = root
.physical_addr
.as_usize()
.checked_add(size_of::<SDTHeader>())
.ok_or(AcpiError::AddressOverflow)?;
for i in 0..self.root.entry_count()? {
let entry_addr = entries_start
.checked_add(
i.checked_mul(entry_width)
.ok_or(AcpiError::AddressOverflow)?,
)
.ok_or(AcpiError::AddressOverflow)?;
let table_addr = match self.root {
RootTable::Rsdt(_) => self.read::<u32>(PhysicalAddr::new(entry_addr))? as usize,
RootTable::Xsdt(_) => self.read::<u64>(PhysicalAddr::new(entry_addr))? as usize,
};
let physical_addr = PhysicalAddr::new(table_addr);
let header = self.read::<SDTHeader>(physical_addr)?;
if header.length < size_of::<SDTHeader>() as u32 {
return Err(AcpiError::InvalidSdtLength);
}
if header.signature != signature {
continue;
}
if !self.checksum_valid(physical_addr, header.length as usize)? {
return Err(AcpiError::MalformedAcpiTable);
}
return Ok(Some(Sdt {
physical_addr,
length: header.length as usize,
signature: header.signature,
}));
}
Ok(None)
}
}
enum RootTable {
Rsdt(Sdt),
Xsdt(Sdt),
}
impl RootTable {
fn table(&self) -> &Sdt {
match self {
RootTable::Rsdt(sdt) | RootTable::Xsdt(sdt) => sdt,
}
}
const fn entry_width(&self) -> usize {
match self {
RootTable::Rsdt(_) => core::mem::size_of::<u32>(),
RootTable::Xsdt(_) => core::mem::size_of::<u64>(),
}
}
fn entry_count(&self) -> Result<usize, AcpiError> {
let payload_length = self
.table()
.length
.checked_sub(size_of::<SDTHeader>())
.ok_or(AcpiError::InvalidSdtLength)?;
if payload_length % self.entry_width() != 0 {
return Err(AcpiError::InvalidRootTableLength);
}
Ok(payload_length / self.entry_width())
}
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct Rsdp {
signature: [u8; 8],
checksum: u8,
oem_id: [u8; 6],
revision: u8,
rsdt_address: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct Xsdp {
rsdp: Rsdp,
length: u32,
xsdt_address: u64,
extended_checksum: u8,
reserved: [u8; 3],
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct SDTHeader {
signature: [u8; 4],
length: u32,
revision: u8,
checksum: u8,
oem_id: [u8; 6],
oem_table_id: [u8; 8],
oem_revision: u32,
creator_id: u32,
creator_revision: u32,
}
pub struct Sdt {
physical_addr: PhysicalAddr,
length: usize,
signature: [u8; 4],
}
#[allow(unused)]
pub struct Madt<'a> {
acpi: &'a AcpiTables,
table: Sdt,
pub local_apic_address: PhysicalAddr,
flags: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct MadtBody {
local_apic_address: u32,
flags: u32,
}
const MADT_ENTRIES_OFFSET: usize = size_of::<SDTHeader>() + size_of::<MadtBody>();
impl<'a> Madt<'a> {
pub fn entries(
&self,
) -> Result<impl Iterator<Item = Result<MadtEntry, AcpiError>> + '_, AcpiError> {
Ok(MadtEntries {
acpi: self.acpi,
current: PhysicalAddr::new(
self.table
.physical_addr
.as_usize()
.checked_add(MADT_ENTRIES_OFFSET)
.ok_or(AcpiError::AddressOverflow)?,
),
end: PhysicalAddr::new(
self.table
.physical_addr
.as_usize()
.checked_add(self.table.length)
.ok_or(AcpiError::AddressOverflow)?,
),
})
}
pub fn parse(acpi: &'a AcpiTables, table: Sdt) -> Result<Self, AcpiError> {
if table.signature != *b"APIC" {
return Err(AcpiError::InvalidInput);
}
if table.length < size_of::<SDTHeader>() + size_of::<MadtBody>() {
return Err(AcpiError::MalformedAcpiTable);
}
let body_addr = table
.physical_addr
.as_usize()
.checked_add(size_of::<SDTHeader>())
.ok_or(AcpiError::AddressOverflow)?;
let body = acpi.read::<MadtBody>(PhysicalAddr::new(body_addr))?;
Ok(Madt {
acpi,
table,
local_apic_address: PhysicalAddr::new(body.local_apic_address as usize),
flags: body.flags,
})
}
pub fn effective_local_apic_address(&self) -> Result<PhysicalAddr, AcpiError> {
let mut address = self.local_apic_address;
for result in self.entries()? {
let entry = result?;
match entry {
MadtEntry::LocalApicAddressOverride(local_apic_address_override) => {
address =
PhysicalAddr::new(local_apic_address_override.local_apic_address as usize);
}
_ => {}
}
}
Ok(address)
}
pub fn io_apics(
&self,
) -> Result<impl Iterator<Item = Result<IoApicInfo, AcpiError>> + '_, AcpiError> {
Ok(self.entries()?.filter_map(|result| match result {
Ok(MadtEntry::IoApic(io_apic)) => Some(Ok(IoApicInfo {
id: io_apic.id,
apic_address: PhysicalAddr::new(io_apic.apic_address as usize),
global_system_interrupt_base: io_apic.global_system_interrupt_base,
})),
Ok(_) => None,
Err(error) => Some(Err(error)),
}))
}
pub fn sole_io_apic(&self) -> Result<IoApicInfo, AcpiError> {
let mut entries = self.io_apics()?;
let first = entries
.next()
.transpose()?
.ok_or(AcpiError::MissingIoApic)?;
if entries.next().transpose()?.is_some() {
return Err(AcpiError::MultipleIoApicsUnsupported);
}
Ok(first)
}
pub fn isa_irq_route(&self, irq: u8) -> Result<IsaIrqRoute, AcpiError> {
for entry in self.entries()? {
match entry? {
MadtEntry::InterruptSourceOverride(interrupt_source_override) => {
if interrupt_source_override.bus != 0 {
continue;
}
if interrupt_source_override.source != irq {
continue;
}
return Ok(IsaIrqRoute {
gsi: interrupt_source_override.global_interrupt,
polarity: match interrupt_source_override.flags & 0b11 {
0 => InterruptPolarity::ActiveHigh,
1 => InterruptPolarity::ActiveHigh,
3 => InterruptPolarity::ActiveLow,
_ => Err(AcpiError::MalformedMadt)?,
},
trigger: match interrupt_source_override.flags >> 2 & 0b11 {
0 => TriggerMode::Edge,
1 => TriggerMode::Edge,
3 => TriggerMode::Level,
_ => Err(AcpiError::MalformedMadt)?,
},
});
}
_ => {}
}
}
Ok(IsaIrqRoute {
gsi: irq as u32,
polarity: InterruptPolarity::ActiveHigh,
trigger: TriggerMode::Edge,
})
}
}
pub struct MadtEntries<'a> {
acpi: &'a AcpiTables,
current: PhysicalAddr,
end: PhysicalAddr,
}
impl MadtEntries<'_> {
fn read_body<T: Copy>(&self, header: &MadtEntryHeader) -> Result<T, AcpiError> {
let required_length = size_of::<MadtEntryHeader>() + size_of::<T>();
if (header.length as usize) < required_length {
return Err(AcpiError::MalformedAcpiTable);
}
let body_addr = self
.current
.as_usize()
.checked_add(size_of::<MadtEntryHeader>())
.ok_or(AcpiError::AddressOverflow)?;
self.acpi.read::<T>(PhysicalAddr::new(body_addr))
}
fn read_next(&mut self) -> Result<MadtEntry, AcpiError> {
let header = self.acpi.read::<MadtEntryHeader>(self.current)?;
let entry = match header.kind {
0 => MadtEntry::LocalApic(self.read_body(&header)?),
1 => MadtEntry::IoApic(self.read_body(&header)?),
2 => MadtEntry::InterruptSourceOverride(self.read_body(&header)?),
3 => MadtEntry::IoApicNmi(self.read_body(&header)?),
4 => MadtEntry::LocalApicNmi(self.read_body(&header)?),
5 => MadtEntry::LocalApicAddressOverride(self.read_body(&header)?),
9 => MadtEntry::LocalX2Apic(self.read_body(&header)?),
_ => MadtEntry::Unknown {
kind: header.kind,
length: header.length,
},
};
self.current = PhysicalAddr::new(
self.current
.as_usize()
.checked_add(header.length as usize)
.ok_or(AcpiError::AddressOverflow)?,
);
Ok(entry)
}
}
impl<'a> Iterator for MadtEntries<'a> {
type Item = Result<MadtEntry, AcpiError>;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current.as_usize();
let end = self.end.as_usize();
if current == end {
return None;
}
if current > end {
self.current = self.end;
return Some(Err(AcpiError::MalformedAcpiTable));
}
let remaining = end - current;
if remaining < size_of::<MadtEntryHeader>() {
self.current = self.end;
return Some(Err(AcpiError::MalformedAcpiTable));
}
let header = match self.acpi.read::<MadtEntryHeader>(self.current) {
Ok(header) => header,
Err(error) => {
self.current = self.end;
return Some(Err(error));
}
};
let length = header.length as usize;
if length < size_of::<MadtEntryHeader>() || length > remaining {
self.current = self.end;
return Some(Err(AcpiError::MalformedAcpiTable));
}
let res = self.read_next();
if res.is_err() {
self.current = self.end;
}
Some(res)
}
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct MadtEntryHeader {
kind: u8,
length: u8,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct LocalApicEntry {
processor_id: u8,
id: u8,
flags: u32,
}
pub struct IoApicInfo {
pub id: u8,
pub apic_address: PhysicalAddr,
pub global_system_interrupt_base: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct IoApicEntry {
id: u8,
reserved: u8,
apic_address: u32,
global_system_interrupt_base: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct InterruptSourceOverride {
bus: u8,
source: u8,
global_interrupt: u32,
flags: u16,
}
#[derive(Clone, Copy)]
pub enum InterruptPolarity {
ActiveHigh,
ActiveLow,
}
#[derive(Clone, Copy)]
pub enum TriggerMode {
Edge,
Level,
}
#[derive(Clone, Copy)]
pub struct IsaIrqRoute {
pub gsi: u32,
pub polarity: InterruptPolarity,
pub trigger: TriggerMode,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct IoApicNmiEntry {
nmi_source: u8,
reserved: u8,
flags: u16,
global_system_interrupt: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct LocalApicNmiEntry {
processor_id: u8,
flags: u16,
lint_num: u8,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct LocalApicAddressOverride {
reserved: u16,
local_apic_address: u64,
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct LocalX2ApicEntry {
reserved: u16,
local_x2apic_id: u32,
flags: u32,
acpi_processor_uid: u32,
}
#[derive(Clone, Copy)]
#[allow(unused)]
pub enum MadtEntry {
LocalApic(LocalApicEntry),
IoApic(IoApicEntry),
InterruptSourceOverride(InterruptSourceOverride),
IoApicNmi(IoApicNmiEntry),
LocalApicNmi(LocalApicNmiEntry),
LocalApicAddressOverride(LocalApicAddressOverride),
LocalX2Apic(LocalX2ApicEntry),
Unknown { kind: u8, length: u8 },
}
pub fn init(
boot_info: &crate::boot::BootInfo,
direct_map: DirectMap,
) -> Result<AcpiTables, AcpiError> {
let acpi_table = unsafe { AcpiTables::from_rsdp(boot_info.rsdp, direct_map)? };
Ok(acpi_table)
}
+1
View File
@@ -0,0 +1 @@
pub mod acpi;
+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);
}