Compare commits
13 Commits
2a1bf17d8b
...
trunk
| Author | SHA1 | Date | |
|---|---|---|---|
|
e5c2889acc
|
|||
|
bf1a81cf82
|
|||
|
3308fd2959
|
|||
|
028ac8fb13
|
|||
|
dd596d5378
|
|||
|
b0f804e412
|
|||
|
4c32bcfb35
|
|||
|
b1f9dc0f8a
|
|||
|
8a71cdfcc5
|
|||
|
7dcf244ee6
|
|||
|
afcef918a3
|
|||
|
af24a5a783
|
|||
|
35d18495ec
|
Generated
+25
@@ -2,6 +2,13 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "client"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dusk-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dusk"
|
name = "dusk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -9,8 +16,26 @@ dependencies = [
|
|||||||
"limine",
|
"limine",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dusk-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "echo"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dusk-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "limine"
|
name = "limine"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9"
|
checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "omega3"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dusk-sys",
|
||||||
|
]
|
||||||
|
|||||||
@@ -3,5 +3,13 @@ name = "dusk"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [".", "userspace/*"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
limine = "0.6.5"
|
limine = "0.6.5"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "dusk"
|
||||||
|
test = false
|
||||||
|
bench = false
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
ARTIFACTS_PATH ?= bin
|
ARTIFACTS_PATH ?= bin
|
||||||
IMAGE_NAME ?= dusk.iso
|
IMAGE_NAME ?= dusk.iso
|
||||||
MODE ?= release
|
MODE ?= debug
|
||||||
ARCH ?= x86_64
|
ARCH ?= x86_64
|
||||||
MEMORY ?= 512M
|
MEMORY ?= 512M
|
||||||
# In MB
|
# In MB
|
||||||
ISO_SIZE ?= 512
|
ISO_SIZE ?= 512
|
||||||
QEMU_OPTS ?=
|
QEMU_OPTS ?=
|
||||||
#MKSQUASHFS_OPTS ?=
|
|
||||||
GDB ?=
|
GDB ?=
|
||||||
CPUS ?= 1
|
CPUS ?= 1
|
||||||
# FAT type
|
# FAT type
|
||||||
ESP_BITS ?= 32
|
ESP_BITS ?= 32
|
||||||
EXPORT_SYMBOLS = true
|
#EXPORT_SYMBOLS = true
|
||||||
|
|
||||||
ISO_PATH = ${ARTIFACTS_PATH}/iso_root
|
ISO_PATH = ${ARTIFACTS_PATH}/iso_root
|
||||||
#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
|
INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
|
||||||
IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME}
|
IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME}
|
||||||
|
ESP_IMAGE = ${ARTIFACTS_PATH}/esp.img
|
||||||
|
USERSPACE_CARGO_OPTS = --target ${ARCH}-unknown-none
|
||||||
CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json
|
CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json
|
||||||
QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH}
|
QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH}
|
||||||
LIMINE_BOOT_VARIATION = X64
|
LIMINE_BOOT_VARIATION = X64
|
||||||
@@ -26,6 +27,7 @@ KERNEL_FILE = target/${ARCH}-unknown-none/${MODE}/dusk.elf
|
|||||||
|
|
||||||
ifeq (${MODE},release)
|
ifeq (${MODE},release)
|
||||||
CARGO_OPTS += --release
|
CARGO_OPTS += --release
|
||||||
|
USERSPACE_CARGO_OPTS += --release
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq (${CPUS},1)
|
ifneq (${CPUS},1)
|
||||||
@@ -36,112 +38,87 @@ ifneq (${GDB},)
|
|||||||
QEMU_OPTS += -s -S
|
QEMU_OPTS += -s -S
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq (${ARCH},aarch64)
|
ifneq (${KVM},)
|
||||||
LIMINE_BOOT_VARIATION := AA64
|
QEMU_OPTS += -accel kvm -cpu host
|
||||||
UEFI := true
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq (${UEFI},)
|
ifneq (${UEFI},)
|
||||||
RUN_OPTS := ovmf-${ARCH}
|
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
|
QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: all build
|
.PHONY: all build
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso
|
build: prepare-bin-files compile-bootloader compile-binaries compile-initramfs build-iso
|
||||||
|
|
||||||
check:
|
check:
|
||||||
cargo check
|
cargo check -Zjson-target-spec
|
||||||
|
|
||||||
prepare-bin-files:
|
prepare-bin-files:
|
||||||
# Remove ISO and everything in the bin directory
|
# Remove ISO and everything in the bin directory
|
||||||
rm -f ${IMAGE_PATH}
|
rm -f ${IMAGE_PATH}
|
||||||
rm -rf ${ARTIFACTS_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 ${ARTIFACTS_PATH}
|
||||||
mkdir -p ${ISO_PATH}
|
mkdir -p ${ISO_PATH}
|
||||||
# mkdir -p ${INITRAMFS_PATH}
|
mkdir -p ${INITRAMFS_PATH}
|
||||||
mkdir -p ${ARTIFACTS_PATH}/mnt
|
|
||||||
|
|
||||||
#copy-initramfs-files:
|
compile-user:
|
||||||
# echo "Hello World from Initramfs" > ${INITRAMFS_PATH}/example.txt
|
RUSTFLAGS="-C relocation-model=static" cargo build --package omega3 ${USERSPACE_CARGO_OPTS}
|
||||||
# echo "Second file for testing" > ${INITRAMFS_PATH}/example2.txt
|
RUSTFLAGS="-C relocation-model=static" cargo build --package client ${USERSPACE_CARGO_OPTS}
|
||||||
# mkdir -p ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/
|
RUSTFLAGS="-C relocation-model=static" cargo build --package echo ${USERSPACE_CARGO_OPTS}
|
||||||
# mkdir ${INITRAMFS_PATH}/mnt/
|
|
||||||
# echo "Nexted file reads!!" > ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/yeah.txt
|
|
||||||
|
|
||||||
#compile-initramfs: copy-initramfs-files
|
copy-initramfs-files: compile-user
|
||||||
# # Make squashfs without compression temporaily so I can get it working before I have to write a gzip driver
|
cp -v target/${ARCH}-unknown-none/${MODE}/omega3 ${INITRAMFS_PATH}/omega3.elf
|
||||||
# mksquashfs ${INITRAMFS_PATH} ${ARTIFACTS_PATH}/initramfs.img ${MKSQUASHFS_OPTS}
|
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:
|
compile-initramfs: copy-initramfs-files
|
||||||
# Place the build ID into the binary so it can be read at runtime
|
(cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img
|
||||||
@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}/
|
|
||||||
|
|
||||||
copy-iso-files:
|
copy-iso-files:
|
||||||
# Limine files
|
# Limine files
|
||||||
mkdir -p ${ISO_PATH}/boot/limine
|
mkdir -p ${ISO_PATH}/boot/limine
|
||||||
mkdir -p ${ISO_PATH}/EFI/BOOT
|
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.conf limine/limine-bios.sys ${ISO_PATH}/boot/limine
|
||||||
cp -v limine/BOOT${LIMINE_BOOT_VARIATION}.EFI ${ISO_PATH}/EFI/BOOT/
|
cp -v limine/BOOT${LIMINE_BOOT_VARIATION}.EFI ${ISO_PATH}/EFI/BOOT/
|
||||||
|
|
||||||
# OS files
|
# OS files
|
||||||
cp -v ${KERNEL_FILE} ${ISO_PATH}/boot
|
cp -v ${KERNEL_FILE} ${ISO_PATH}/boot
|
||||||
#cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot
|
cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot
|
||||||
|
|
||||||
partition-iso: copy-iso-files
|
build-esp: copy-iso-files
|
||||||
# Make empty ISO of 64M in size
|
# 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}
|
dd if=/dev/zero of=${IMAGE_PATH} bs=1M count=0 seek=${ISO_SIZE}
|
||||||
|
ifneq (${UEFI},)
|
||||||
parted -s ${IMAGE_PATH} mklabel gpt
|
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
|
parted -s ${IMAGE_PATH} mkpart ESP fat${ESP_BITS} 2048s 262144s
|
||||||
|
parted -s ${IMAGE_PATH} set 1 esp on
|
||||||
# Make ISO with 1 partition starting at sector 2048 that is 32768 sectors, or 16MiB, in size
|
else
|
||||||
# Then a second partition spanning the rest of the disk
|
parted -s ${IMAGE_PATH} mklabel msdos
|
||||||
parted -s ${IMAGE_PATH} mkpart primary 262145s 100%
|
parted -s ${IMAGE_PATH} mkpart primary fat${ESP_BITS} 2048s 262144s
|
||||||
parted -s ${IMAGE_PATH} set 2 esp on
|
parted -s ${IMAGE_PATH} set 1 boot on
|
||||||
|
|
||||||
build-iso: partition-iso
|
|
||||||
ifeq (${ARCH},x86_64)
|
|
||||||
# Install the Limine bootloader for bios installs
|
|
||||||
./limine/limine bios-install ${IMAGE_PATH}
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
sudo losetup -Pf --show ${IMAGE_PATH} > loopback_dev
|
# Make second partition spanning the rest of the disk
|
||||||
sudo mkfs.fat -F ${ESP_BITS} `cat loopback_dev`p2
|
parted -s ${IMAGE_PATH} mkpart primary 262145s 100%
|
||||||
sudo mount `cat loopback_dev`p2 ${ARTIFACTS_PATH}/mnt
|
|
||||||
sudo cp -r ${ISO_PATH}/* ${ARTIFACTS_PATH}/mnt
|
build-iso: partition-iso build-esp
|
||||||
sync
|
# Splice ESP partition into sector 2048 of the disk image
|
||||||
sudo umount ${ARTIFACTS_PATH}/mnt
|
dd if=${ESP_IMAGE} of=${IMAGE_PATH} bs=512 seek=2048 conv=notrunc
|
||||||
sudo losetup -d `cat loopback_dev`
|
ifeq (${UEFI},)
|
||||||
rm loopback_dev
|
# install limine for legacy bios
|
||||||
|
./limine/limine bios-install ${IMAGE_PATH}
|
||||||
|
endif
|
||||||
|
|
||||||
compile-bootloader:
|
compile-bootloader:
|
||||||
@if [ ! -f "limine/.version" ] || [ "$$(cat limine/.version)" != "${LIMINE_VERSION}" ]; then \
|
@if [ ! -f "limine/.version" ] || [ "$$(cat limine/.version)" != "${LIMINE_VERSION}" ]; then \
|
||||||
@@ -158,26 +135,24 @@ compile-bootloader:
|
|||||||
compile-binaries:
|
compile-binaries:
|
||||||
cargo build ${CARGO_OPTS}
|
cargo build ${CARGO_OPTS}
|
||||||
|
|
||||||
ovmf-x86_64: ovmf
|
ovmf-x86_64:
|
||||||
mkdir -p ovmf/ovmf-x86_64
|
mkdir -p ovmf/ovmf-x86_64
|
||||||
@if [ ! -d "ovmf/ovmf-x86_64/OVMF.fd" ]; then \
|
@if [ ! -f "ovmf/ovmf-x86_64/OVMF.fd" ]; then \
|
||||||
cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \
|
cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
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:
|
# In debug mode, open a terminal and run this command:
|
||||||
# gdb target/x86_64-unknown-none/debug/CappuccinOS.elf -ex "target remote :1234"
|
# gdb target/x86_64-unknown-none/debug/CappuccinOS.elf -ex "target remote :1234"
|
||||||
|
|
||||||
run: build ${RUN_OPTS} run-${ARCH}
|
run: build ${RUN_OPTS} run-${ARCH}
|
||||||
|
run-serial: build ${RUN_OPTS} run-${ARCH}-serial
|
||||||
|
|
||||||
run-x86_64:
|
run-x86_64:
|
||||||
tmux new-session -d -s qemu 'qemu-system-x86_64 ${QEMU_OPTS}'
|
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:
|
line-count:
|
||||||
cloc --quiet --exclude-dir=bin --include-lang=Rust --csv src/ | tail -n 1 | awk -F, '{print $$5}'
|
cloc --quiet --exclude-dir=bin --include-lang=Rust --csv src/ | tail -n 1 | awk -F, '{print $$5}'
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
# DuskOS
|
# 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.
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=src/arch/x86_64/linker.ld");
|
||||||
|
}
|
||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
timeout: 3
|
timeout: 0
|
||||||
|
|
||||||
/DuskOS
|
/DuskOS
|
||||||
protocol: limine
|
protocol: limine
|
||||||
|
|
||||||
path: boot():/boot/dusk.elf
|
path: boot():/boot/dusk.elf
|
||||||
|
module_path: boot():/boot/initramfs.img
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,9 @@ mod x86_64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use 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,
|
||||||
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
@@ -1,13 +1,15 @@
|
|||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
|
||||||
|
use crate::memory::VirtualAddr;
|
||||||
|
|
||||||
#[repr(C, align(8))]
|
#[repr(C, align(8))]
|
||||||
struct Gdt {
|
pub(super) struct Gdt {
|
||||||
entries: [u64; 5],
|
pub entries: [u64; 7],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gdt {
|
impl Gdt {
|
||||||
pub const fn new() -> Self {
|
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_CODE_SELECTOR: u16 = 1 * 8;
|
||||||
pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8;
|
pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8;
|
||||||
pub(super) const 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))]
|
#[repr(align(16))]
|
||||||
|
#[allow(unused)] // field 0 is read, rust just cant tell
|
||||||
struct ExceptionStack([u8; DOUBLE_FAULT_STACK_SIZE]);
|
struct ExceptionStack([u8; DOUBLE_FAULT_STACK_SIZE]);
|
||||||
|
|
||||||
static mut GDT: Gdt = Gdt::new();
|
static mut GDT: Gdt = Gdt::new();
|
||||||
static mut TSS: TaskStateSegment = TaskStateSegment::new();
|
static mut TSS: TaskStateSegment = TaskStateSegment::new();
|
||||||
static mut DOUBLE_FAULT_STACK: ExceptionStack = ExceptionStack([0; DOUBLE_FAULT_STACK_SIZE]);
|
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::<TaskStateSegment>() == 104);
|
const _: () = assert!(core::mem::size_of::<GdtPointer>() == 10);
|
||||||
const _: () = assert!(core::mem::size_of::<GdtPointer>() == 10);
|
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let stack_bottom = core::ptr::addr_of_mut!(DOUBLE_FAULT_STACK) as u64;
|
let stack_bottom = core::ptr::addr_of_mut!(DOUBLE_FAULT_STACK) as u64;
|
||||||
let stack_top = stack_bottom + DOUBLE_FAULT_STACK_SIZE as u64;
|
let stack_top = stack_bottom + DOUBLE_FAULT_STACK_SIZE as u64;
|
||||||
@@ -73,6 +78,10 @@ pub fn init() {
|
|||||||
0,
|
0,
|
||||||
kernel_code_descriptor(),
|
kernel_code_descriptor(),
|
||||||
kernel_data_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_low,
|
||||||
tss_high,
|
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() {
|
fn gdt_reload() {
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(
|
asm!(
|
||||||
@@ -118,17 +133,34 @@ fn gdt_reload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PRESENT: u64 = 1 << 47;
|
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 EXECUTABLE: u64 = 1 << 43;
|
||||||
const READ_WRITE: u64 = 1 << 41;
|
const READ_WRITE: u64 = 1 << 41;
|
||||||
|
const GRANULARITY: u64 = 1 << 55;
|
||||||
|
const SIZE: u64 = 1 << 54;
|
||||||
const LONG_MODE: u64 = 1 << 53;
|
const LONG_MODE: u64 = 1 << 53;
|
||||||
|
|
||||||
fn kernel_code_descriptor() -> u64 {
|
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 {
|
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] {
|
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);
|
||||||
|
}
|
||||||
@@ -1,77 +1,115 @@
|
|||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
|
||||||
use super::idt::{self, InterruptStackFrame};
|
use super::idt::{self, InterruptFrame, InterruptStackFrame, stub_err, stub_no_err};
|
||||||
use crate::{hcf, println};
|
use crate::{
|
||||||
|
hcf, println,
|
||||||
|
task::tcb::{ExitReason, Fault},
|
||||||
|
};
|
||||||
|
|
||||||
macro_rules! fatal_without_error_code {
|
stub_no_err!(stub_divide_error, 0);
|
||||||
($handler:ident, $name:literal) => {
|
stub_no_err!(stub_debug, 1);
|
||||||
extern "x86-interrupt" fn $handler(frame: InterruptStackFrame) {
|
stub_no_err!(stub_non_maskable_interrupt, 2);
|
||||||
fatal_exception($name, &frame, None);
|
stub_no_err!(stub_breakpoint, 3);
|
||||||
}
|
stub_no_err!(stub_invalid_opcode, 6);
|
||||||
};
|
stub_no_err!(stub_device_not_available, 7);
|
||||||
}
|
stub_err!(stub_double_fault, 8);
|
||||||
|
stub_err!(stub_invalid_tss, 10);
|
||||||
|
stub_err!(stub_segment_not_present, 11);
|
||||||
|
stub_err!(stub_stack_segment_fault, 12);
|
||||||
|
stub_err!(stub_general_protection, 13);
|
||||||
|
stub_err!(stub_page_fault, 14);
|
||||||
|
stub_no_err!(stub_x87_floating_point, 16);
|
||||||
|
stub_err!(stub_alignment_check, 17);
|
||||||
|
stub_no_err!(stub_machine_check, 18);
|
||||||
|
stub_no_err!(stub_simd_floating_point, 19);
|
||||||
|
stub_no_err!(stub_user_test_exit, 0x80);
|
||||||
|
|
||||||
macro_rules! fatal_with_error_code {
|
const EXCEPTION_NAMES: [&str; 32] = [
|
||||||
($handler:ident, $name:literal) => {
|
"DIVIDE ERROR",
|
||||||
extern "x86-interrupt" fn $handler(frame: InterruptStackFrame, error_code: u64) {
|
"DEBUG",
|
||||||
fatal_exception($name, &frame, Some(error_code));
|
"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",
|
||||||
|
];
|
||||||
|
|
||||||
extern "x86-interrupt" fn debug_handler(frame: InterruptStackFrame) {
|
pub(super) fn handle(frame: &mut InterruptFrame) {
|
||||||
fatal_exception("DEBUG EXCEPTION", &frame, None);
|
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");
|
||||||
|
|
||||||
extern "x86-interrupt" fn non_maskable_interrupt_handler(frame: InterruptStackFrame) {
|
if !is_user {
|
||||||
fatal_exception("NON-MASKABLE INTERRUPT", &frame, None);
|
if vector == 14 {
|
||||||
}
|
report_exception(name, &frame.stack_frame, Some(frame.error_code));
|
||||||
|
|
||||||
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());
|
println!("Faulting address: {:#X}", read_cr2());
|
||||||
print_page_fault_error(error_code);
|
print_page_fault_error(frame.error_code);
|
||||||
hcf();
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
pub(super) fn install(idt: &mut idt::Idt) {
|
||||||
idt.set_handler(0, divide_error_handler, 0);
|
idt.set_handler(0, stub_divide_error, 0);
|
||||||
idt.set_handler(1, debug_handler, 0);
|
idt.set_handler(1, stub_debug, 0);
|
||||||
idt.set_handler(2, non_maskable_interrupt_handler, 0);
|
idt.set_handler(2, stub_non_maskable_interrupt, 0);
|
||||||
idt.set_handler(3, breakpoint_handler, 0);
|
idt.set_user_handler(3, stub_breakpoint, 0);
|
||||||
idt.set_handler(6, invalid_opcode_handler, 0);
|
idt.set_handler(6, stub_invalid_opcode, 0);
|
||||||
idt.set_handler(7, device_not_available_handler, 0);
|
idt.set_handler(7, stub_device_not_available, 0);
|
||||||
idt.set_error_code_handler(8, double_fault_handler, 1);
|
idt.set_handler(8, stub_double_fault, 1);
|
||||||
idt.set_error_code_handler(10, invalid_tss_handler, 0);
|
idt.set_handler(10, stub_invalid_tss, 0);
|
||||||
idt.set_error_code_handler(11, segment_not_present_handler, 0);
|
idt.set_handler(11, stub_segment_not_present, 0);
|
||||||
idt.set_error_code_handler(12, stack_segment_fault_handler, 0);
|
idt.set_handler(12, stub_stack_segment_fault, 0);
|
||||||
idt.set_error_code_handler(13, general_protection_handler, 0);
|
idt.set_handler(13, stub_general_protection, 0);
|
||||||
idt.set_error_code_handler(14, page_fault_handler, 0);
|
idt.set_handler(14, stub_page_fault, 0);
|
||||||
idt.set_handler(16, x87_floating_point_handler, 0);
|
idt.set_handler(16, stub_x87_floating_point, 0);
|
||||||
idt.set_error_code_handler(17, alignment_check_handler, 0);
|
idt.set_handler(17, stub_alignment_check, 0);
|
||||||
idt.set_handler(18, machine_check_handler, 0);
|
idt.set_handler(18, stub_machine_check, 0);
|
||||||
idt.set_handler(19, simd_floating_point_handler, 0);
|
idt.set_handler(19, stub_simd_floating_point, 0);
|
||||||
|
|
||||||
|
idt.set_user_handler(0x80, stub_user_test_exit, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_cr2() -> u64 {
|
fn read_cr2() -> u64 {
|
||||||
@@ -134,10 +172,10 @@ fn report_exception(name: &'static str, frame: &InterruptStackFrame, error_code:
|
|||||||
"kernel"
|
"kernel"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
println!("RIP: {:#X}", frame.instruction_pointer.as_u64());
|
println!("RIP: {:#X}", frame.instruction_pointer.as_usize());
|
||||||
println!("CS: {:#X}", frame.code_segment);
|
println!("CS: {:#X}", frame.code_segment);
|
||||||
println!("FLAGS: {:#X}", frame.cpu_flags);
|
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);
|
println!("SS: {:#X}", frame.stack_segment);
|
||||||
|
|
||||||
if let Some(error_code) = error_code {
|
if let Some(error_code) = error_code {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use super::exceptions;
|
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)]
|
#[repr(C, packed)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -15,15 +18,15 @@ struct IdtEntry {
|
|||||||
|
|
||||||
impl IdtEntry {
|
impl IdtEntry {
|
||||||
const fn missing() -> Self {
|
const fn missing() -> Self {
|
||||||
return Self {
|
Self {
|
||||||
offset_low: 0,
|
offset_low: 0,
|
||||||
code_selector: 0x08,
|
code_selector: 0,
|
||||||
ist: 0,
|
ist: 0,
|
||||||
attributes: 0,
|
attributes: 0,
|
||||||
offset_middle: 0,
|
offset_middle: 0,
|
||||||
offset_high: 0,
|
offset_high: 0,
|
||||||
reserved: 0,
|
reserved: 0,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ struct IdtPointer {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(super) struct InterruptStackFrame {
|
pub struct InterruptStackFrame {
|
||||||
pub instruction_pointer: VirtualAddr,
|
pub instruction_pointer: VirtualAddr,
|
||||||
pub code_segment: u64,
|
pub code_segment: u64,
|
||||||
pub cpu_flags: u64,
|
pub cpu_flags: u64,
|
||||||
@@ -43,12 +46,36 @@ pub(super) struct InterruptStackFrame {
|
|||||||
pub stack_segment: u64,
|
pub stack_segment: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptFrame {
|
||||||
|
pub rax: u64,
|
||||||
|
pub rcx: u64,
|
||||||
|
pub rdx: u64,
|
||||||
|
pub rsi: u64,
|
||||||
|
pub rdi: u64,
|
||||||
|
pub r8: u64,
|
||||||
|
pub r9: u64,
|
||||||
|
pub r10: u64,
|
||||||
|
pub r11: u64,
|
||||||
|
pub rbx: u64,
|
||||||
|
pub rbp: u64,
|
||||||
|
pub r12: u64,
|
||||||
|
pub r13: u64,
|
||||||
|
pub r14: u64,
|
||||||
|
pub r15: u64,
|
||||||
|
pub vector: u64,
|
||||||
|
pub error_code: u64,
|
||||||
|
pub stack_frame: InterruptStackFrame,
|
||||||
|
}
|
||||||
|
|
||||||
const INTERRUPT_GATE: u8 = 0b1110;
|
const INTERRUPT_GATE: u8 = 0b1110;
|
||||||
const PRESENT: u8 = 1 << 7;
|
const PRESENT: u8 = 1 << 7;
|
||||||
const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE;
|
const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE;
|
||||||
|
const USER_DPL: u8 = 3 << 5;
|
||||||
|
const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE;
|
||||||
|
|
||||||
pub(super) type Handler = extern "x86-interrupt" fn(InterruptStackFrame);
|
pub(super) type RawHandler = unsafe extern "C" fn();
|
||||||
pub(super) type ErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64);
|
|
||||||
|
|
||||||
pub(super) struct Idt {
|
pub(super) struct Idt {
|
||||||
entries: [IdtEntry; 256],
|
entries: [IdtEntry; 256],
|
||||||
@@ -61,27 +88,22 @@ impl Idt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn set_handler(&mut self, vector: u8, handler: Handler, ist: u8) {
|
pub(super) fn set_handler(&mut self, vector: u8, handler: RawHandler, ist: u8) {
|
||||||
self.set_handler_address(vector, handler as usize, ist);
|
self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn set_error_code_handler(
|
pub(super) fn set_user_handler(&mut self, vector: u8, handler: RawHandler, ist: u8) {
|
||||||
&mut self,
|
self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE);
|
||||||
vector: u8,
|
|
||||||
handler: ErrorCodeHandler,
|
|
||||||
ist: u8,
|
|
||||||
) {
|
|
||||||
self.set_handler_address(vector, handler as usize, ist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
self.entries[vector as usize] = IdtEntry {
|
||||||
offset_low: address as u16,
|
offset_low: address as u16,
|
||||||
offset_middle: (address >> 16) as u16,
|
offset_middle: (address >> 16) as u16,
|
||||||
offset_high: (address >> 32) as u32,
|
offset_high: (address >> 32) as u32,
|
||||||
code_selector: KERNEL_CODE_SELECTOR,
|
code_selector: KERNEL_CODE_SELECTOR,
|
||||||
ist: ist & 0b111,
|
ist: ist & 0b111,
|
||||||
attributes: KERNEL_INTERRUPT_GATE,
|
attributes,
|
||||||
reserved: 0,
|
reserved: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -89,14 +111,119 @@ impl Idt {
|
|||||||
|
|
||||||
static mut IDT: Idt = Idt::new();
|
static mut IDT: Idt = Idt::new();
|
||||||
|
|
||||||
pub fn idt_init() {
|
const _: () = assert!(core::mem::size_of::<IdtEntry>() == 16);
|
||||||
const _: () = assert!(core::mem::size_of::<IdtEntry>() == 16);
|
const _: () = assert!(core::mem::size_of::<IdtPointer>() == 10);
|
||||||
const _: () = assert!(core::mem::size_of::<IdtPointer>() == 10);
|
const _: () = assert!(core::mem::size_of::<InterruptStackFrame>() == 40);
|
||||||
const _: () = assert!(core::mem::size_of::<InterruptStackFrame>() == 40);
|
const _: () = assert!(core::mem::size_of::<InterruptFrame>() == 176);
|
||||||
|
const _: () = assert!(core::mem::offset_of!(InterruptFrame, stack_frame) == 136);
|
||||||
|
|
||||||
|
#[unsafe(naked)]
|
||||||
|
pub(super) unsafe extern "C" fn interrupt_common() {
|
||||||
|
core::arch::naked_asm!(
|
||||||
|
"push r15",
|
||||||
|
"push r14",
|
||||||
|
"push r13",
|
||||||
|
"push r12",
|
||||||
|
"push rbp",
|
||||||
|
"push rbx",
|
||||||
|
"push r11",
|
||||||
|
"push r10",
|
||||||
|
"push r9",
|
||||||
|
"push r8",
|
||||||
|
"push rdi",
|
||||||
|
"push rsi",
|
||||||
|
"push rdx",
|
||||||
|
"push rcx",
|
||||||
|
"push rax",
|
||||||
|
|
||||||
|
// Check CS: bit 0 and 1 are CPL. If CPL != 0 (user mode), swapgs
|
||||||
|
"test byte ptr [rsp + 144], 3",
|
||||||
|
"jz 1f",
|
||||||
|
"swapgs",
|
||||||
|
"1:",
|
||||||
|
|
||||||
|
"mov rdi, rsp",
|
||||||
|
"cld",
|
||||||
|
"call {dispatch}",
|
||||||
|
|
||||||
|
// Check CS: if returning to user mode, swapgs
|
||||||
|
"test byte ptr [rsp + 144], 3",
|
||||||
|
"jz 2f",
|
||||||
|
"swapgs",
|
||||||
|
"2:",
|
||||||
|
|
||||||
|
"pop rax",
|
||||||
|
"pop rcx",
|
||||||
|
"pop rdx",
|
||||||
|
"pop rsi",
|
||||||
|
"pop rdi",
|
||||||
|
"pop r8",
|
||||||
|
"pop r9",
|
||||||
|
"pop r10",
|
||||||
|
"pop r11",
|
||||||
|
"pop rbx",
|
||||||
|
"pop rbp",
|
||||||
|
"pop r12",
|
||||||
|
"pop r13",
|
||||||
|
"pop r14",
|
||||||
|
"pop r15",
|
||||||
|
|
||||||
|
"add rsp, 16",
|
||||||
|
"iretq",
|
||||||
|
|
||||||
|
dispatch = sym interrupt_dispatch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn interrupt_dispatch(frame: &mut InterruptFrame) {
|
||||||
|
let vector = frame.vector as u8;
|
||||||
|
match vector {
|
||||||
|
0..=31 | 0x80 => exceptions::handle(frame),
|
||||||
|
apic_vectors::PIT_CALIBRATION_VECTOR
|
||||||
|
| apic_vectors::APIC_TIMER_VECTOR
|
||||||
|
| apic_vectors::APIC_ERROR_VECTOR
|
||||||
|
| apic_vectors::APIC_SPURIOUS_VECTOR => apic_vectors::handle(frame),
|
||||||
|
_ => {
|
||||||
|
crate::println!("Unhandled interrupt vector: {:#X}", vector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! stub_no_err {
|
||||||
|
($name:ident, $vec:literal) => {
|
||||||
|
#[unsafe(naked)]
|
||||||
|
pub(super) unsafe extern "C" fn $name() {
|
||||||
|
core::arch::naked_asm!(
|
||||||
|
"push 0",
|
||||||
|
concat!("push ", stringify!($vec)),
|
||||||
|
"jmp {common}",
|
||||||
|
common = sym $crate::arch::x86_64::interrupts::idt::interrupt_common,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! stub_err {
|
||||||
|
($name:ident, $vec:literal) => {
|
||||||
|
#[unsafe(naked)]
|
||||||
|
pub(super) unsafe extern "C" fn $name() {
|
||||||
|
core::arch::naked_asm!(
|
||||||
|
concat!("push ", stringify!($vec)),
|
||||||
|
"jmp {common}",
|
||||||
|
common = sym $crate::arch::x86_64::interrupts::idt::interrupt_common,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) use stub_err;
|
||||||
|
pub(super) use stub_no_err;
|
||||||
|
|
||||||
|
pub fn idt_init() {
|
||||||
let mut idt = Idt::new();
|
let mut idt = Idt::new();
|
||||||
|
|
||||||
exceptions::install(&mut idt);
|
exceptions::install(&mut idt);
|
||||||
|
apic_vectors::install(&mut idt);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::addr_of_mut!(IDT).write(idt);
|
core::ptr::addr_of_mut!(IDT).write(idt);
|
||||||
|
|||||||
@@ -1,12 +1,48 @@
|
|||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
|
||||||
|
pub(super) mod apic_vectors;
|
||||||
mod exceptions;
|
mod exceptions;
|
||||||
mod idt;
|
mod idt;
|
||||||
|
|
||||||
pub use idt::idt_init as init;
|
pub use idt::idt_init as init;
|
||||||
|
|
||||||
pub fn disable_interrupts() {
|
#[inline(always)]
|
||||||
|
pub fn disable_interrupts_and_save() -> u64 {
|
||||||
|
let flags: u64;
|
||||||
|
|
||||||
unsafe {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,10 @@ SECTIONS
|
|||||||
/* that is the beginning of the region. */
|
/* that is the beginning of the region. */
|
||||||
/* Additionally, leave space for the ELF headers by adding SIZEOF_HEADERS to the */
|
/* Additionally, leave space for the ELF headers by adding SIZEOF_HEADERS to the */
|
||||||
/* base load address. */
|
/* base load address. */
|
||||||
. = 0xffffffff80000000 + SIZEOF_HEADERS;
|
. = 0xffffffff80000000;
|
||||||
|
|
||||||
|
__text_start = .;
|
||||||
|
. += SIZEOF_HEADERS;
|
||||||
|
|
||||||
.text : {
|
.text : {
|
||||||
*(.text .text.*)
|
*(.text .text.*)
|
||||||
@@ -33,6 +36,8 @@ SECTIONS
|
|||||||
|
|
||||||
/* Move to the next memory page for .rodata */
|
/* Move to the next memory page for .rodata */
|
||||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||||
|
__text_end = .;
|
||||||
|
__rodata_start = .;
|
||||||
|
|
||||||
.rodata : {
|
.rodata : {
|
||||||
*(.rodata .rodata.*)
|
*(.rodata .rodata.*)
|
||||||
@@ -40,6 +45,8 @@ SECTIONS
|
|||||||
|
|
||||||
/* Move to the next memory page for .data */
|
/* Move to the next memory page for .data */
|
||||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||||
|
__rodata_end = .;
|
||||||
|
__data_start = .;
|
||||||
|
|
||||||
.data : {
|
.data : {
|
||||||
*(.data .data.*)
|
*(.data .data.*)
|
||||||
@@ -56,6 +63,11 @@ SECTIONS
|
|||||||
*(.dynamic)
|
*(.dynamic)
|
||||||
} :data :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 */
|
/* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
|
||||||
/* unnecessary zeros will be written to the binary. */
|
/* unnecessary zeros will be written to the binary. */
|
||||||
/* If you need, for example, .init_array and .fini_array, those should be placed */
|
/* If you need, for example, .init_array and .fini_array, those should be placed */
|
||||||
@@ -65,6 +77,9 @@ SECTIONS
|
|||||||
*(COMMON)
|
*(COMMON)
|
||||||
} :data
|
} :data
|
||||||
|
|
||||||
|
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||||
|
__data_end = .;
|
||||||
|
|
||||||
/* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
|
/* 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 */
|
/* 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 */
|
/* more or less equivalent to the --no-dynamic-linker linker flag, except that it */
|
||||||
|
|||||||
+193
-3
@@ -1,19 +1,209 @@
|
|||||||
|
pub(super) mod apic;
|
||||||
|
mod cpu;
|
||||||
mod gdt;
|
mod gdt;
|
||||||
mod interrupts;
|
mod interrupts;
|
||||||
|
pub(super) mod io_apic;
|
||||||
|
mod paging;
|
||||||
|
mod pit;
|
||||||
pub mod port;
|
pub mod port;
|
||||||
|
mod syscall;
|
||||||
|
pub mod timer;
|
||||||
|
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
|
||||||
pub use interrupts::disable_interrupts;
|
pub use cpu::{ThreadContext, switch_context};
|
||||||
|
pub use interrupts::{disable_interrupts, disable_interrupts_and_save, restore_interrupts};
|
||||||
|
pub(crate) use paging::{
|
||||||
|
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();
|
disable_interrupts();
|
||||||
println!("Loading GDT...");
|
println!("Loading GDT...");
|
||||||
gdt::init();
|
gdt::init();
|
||||||
println!("Loading IDT...");
|
println!("Loading IDT...");
|
||||||
interrupts::init();
|
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() {
|
pub fn halt() {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
@@ -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
@@ -8,52 +8,52 @@ pub unsafe fn read_u8(port: u16) -> u8 {
|
|||||||
"in al, dx",
|
"in al, dx",
|
||||||
in("dx") port,
|
in("dx") port,
|
||||||
out("al") value,
|
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),
|
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
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
// #[inline(always)]
|
||||||
pub unsafe fn read_u32(port: u16) -> u32 {
|
// pub unsafe fn read_u8_slice(port: u16, slice: &mut [u8]) {
|
||||||
let value: u32;
|
// unsafe {
|
||||||
unsafe {
|
// asm!(
|
||||||
asm!(
|
// "rep insb",
|
||||||
"in eax, dx",
|
// in("dx") port,
|
||||||
in("dx") port,
|
// inout("rdi") slice.as_mut_ptr() => _,
|
||||||
out("eax") value,
|
// inout("rcx") slice.len() => _,
|
||||||
options(nomem, nostack, preserves_flags),
|
// options(nostack, preserves_flags),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
value
|
// }
|
||||||
}
|
|
||||||
|
// #[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)]
|
#[inline(always)]
|
||||||
pub unsafe fn write_u8(port: u16, value: u8) {
|
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",
|
"out dx, al",
|
||||||
in("dx") port,
|
in("dx") port,
|
||||||
in("al") value,
|
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),
|
options(nostack, preserves_flags),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
// #[inline(always)]
|
||||||
pub unsafe fn write_u16(port: u16, value: u16) {
|
// pub unsafe fn write_u8_slice(port: u16, slice: &[u8]) {
|
||||||
unsafe {
|
// unsafe {
|
||||||
asm!(
|
// asm!(
|
||||||
"out dx, ax",
|
// "rep outsb",
|
||||||
in("dx") port,
|
// in("dx") port,
|
||||||
in("ax") value,
|
// inout("rsi") slice.as_ptr() => _,
|
||||||
options(nomem, nostack, preserves_flags),
|
// inout("rcx") slice.len() => _,
|
||||||
);
|
// options(nostack, preserves_flags),
|
||||||
}
|
// );
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
#[inline(always)]
|
// #[inline(always)]
|
||||||
pub unsafe fn write_u32(port: u16, value: u32) {
|
// pub unsafe fn write_u16(port: u16, value: u16) {
|
||||||
unsafe {
|
// unsafe {
|
||||||
asm!(
|
// asm!(
|
||||||
"out dx, eax",
|
// "out dx, ax",
|
||||||
in("dx") port,
|
// in("dx") port,
|
||||||
in("eax") value,
|
// in("ax") value,
|
||||||
options(nomem, nostack, preserves_flags),
|
// 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),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -8,11 +8,13 @@
|
|||||||
"target-c-int-width": 32,
|
"target-c-int-width": 32,
|
||||||
"features": "-mmx,-sse,+soft-float",
|
"features": "-mmx,-sse,+soft-float",
|
||||||
"rustc-abi": "softfloat",
|
"rustc-abi": "softfloat",
|
||||||
"os": "DawnOS",
|
|
||||||
"linker": "rust-lld",
|
"linker": "rust-lld",
|
||||||
"linker-flavor": "ld.lld",
|
"linker-flavor": "ld.lld",
|
||||||
"pre-link-args": {
|
"pre-link-args": {
|
||||||
"ld.lld": ["-melf_x86_64", "--script=./src/arch/x86_64/linker.ld"]
|
"ld.lld": [
|
||||||
|
"-melf_x86_64",
|
||||||
|
"--script=./src/arch/x86_64/linker.ld"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"panic-strategy": "abort",
|
"panic-strategy": "abort",
|
||||||
"exe-suffix": ".elf",
|
"exe-suffix": ".elf",
|
||||||
|
|||||||
+185
-37
@@ -1,8 +1,15 @@
|
|||||||
use ::limine as limine_api;
|
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::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest};
|
||||||
use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker};
|
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.
|
/// Sets the base revision to the latest revision supported by the crate.
|
||||||
/// See specification for further info.
|
/// See specification for further info.
|
||||||
/// Be sure to mark all limine requests with #[used], otherwise they may be removed by the compiler.
|
/// 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")]
|
#[unsafe(link_section = ".requests")]
|
||||||
static KERNEL_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new();
|
static KERNEL_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new();
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
#[unsafe(link_section = ".requests")]
|
||||||
|
static KERNEL_CMDLINE_REQUEST: ExecutableCmdlineRequest = ExecutableCmdlineRequest::new();
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
#[unsafe(link_section = ".requests")]
|
||||||
|
static KERNEL_MODULE_REQUEST: ModulesRequest = ModulesRequest::new();
|
||||||
|
|
||||||
#[used]
|
#[used]
|
||||||
#[unsafe(link_section = ".requests")]
|
#[unsafe(link_section = ".requests")]
|
||||||
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
|
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
|
||||||
@@ -23,6 +38,15 @@ static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
|
|||||||
#[unsafe(link_section = ".requests")]
|
#[unsafe(link_section = ".requests")]
|
||||||
static MEMMAP_REQUEST: MemmapRequest = MemmapRequest::new();
|
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.
|
/// Define the stand and end markers for Limine requests.
|
||||||
#[used]
|
#[used]
|
||||||
#[unsafe(link_section = ".requests_start_marker")]
|
#[unsafe(link_section = ".requests_start_marker")]
|
||||||
@@ -31,22 +55,98 @@ static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
|
|||||||
#[unsafe(link_section = ".requests_end_marker")]
|
#[unsafe(link_section = ".requests_end_marker")]
|
||||||
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
|
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 struct BootInfo {
|
||||||
pub kernel_address: crate::memory::PhysicalAddr,
|
pub kernel_layout: KernelMemoryLayout,
|
||||||
pub hhdm_offset: u64,
|
pub command_line: BootString<MAX_COMMAND_LINE_LENGTH>,
|
||||||
entries: &'static [&'static limine_api::memmap::Entry],
|
pub initramfs: InitramfsImage,
|
||||||
|
pub hhdm_offset: usize,
|
||||||
|
pub memory_map: MemoryMap,
|
||||||
|
pub rsdp: VirtualAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BootInfo {
|
impl BootInfo {
|
||||||
pub fn memory_regions(&self) -> impl Iterator<Item = crate::memory::MemoryRegion> + '_ {
|
pub fn memory_regions(&self) -> impl Iterator<Item = MemoryRegion> + Clone + '_ {
|
||||||
use crate::memory::{MemoryRegion, MemoryRegionKind};
|
self.memory_map.iter()
|
||||||
self.entries.iter().map(|&entry| MemoryRegion {
|
}
|
||||||
start: crate::memory::PhysicalAddr::new(entry.base),
|
}
|
||||||
length: entry.length,
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BootError {
|
||||||
|
UnsupportedBaseRevision,
|
||||||
|
FailedToGetKernelAddress,
|
||||||
|
FailedToGetKernelCmdline,
|
||||||
|
FailedToGetModules,
|
||||||
|
FailedToGetInitramfs,
|
||||||
|
FailedToGetHHDMAddress,
|
||||||
|
FailedToGetMemmap,
|
||||||
|
TooManyMemoryRegions,
|
||||||
|
FailedToLocateKernel,
|
||||||
|
FailedToGetRsdp,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_boot_info() -> Result<BootInfo, BootError> {
|
||||||
|
if !BASE_REVISION.is_supported() {
|
||||||
|
return Err(BootError::UnsupportedBaseRevision);
|
||||||
|
}
|
||||||
|
|
||||||
|
let kernel_address = KERNEL_ADDRESS_REQUEST
|
||||||
|
.response()
|
||||||
|
.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 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_ {
|
kind: match entry.type_ {
|
||||||
limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable,
|
limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable,
|
||||||
limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved,
|
limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved,
|
||||||
limine_api::memmap::MEMMAP_ACPI_RECLAIMABLE => MemoryRegionKind::AcpiReclaimable,
|
limine_api::memmap::MEMMAP_ACPI_RECLAIMABLE => {
|
||||||
|
MemoryRegionKind::AcpiReclaimable
|
||||||
|
}
|
||||||
limine_api::memmap::MEMMAP_ACPI_NVS => MemoryRegionKind::AcpiNvs,
|
limine_api::memmap::MEMMAP_ACPI_NVS => MemoryRegionKind::AcpiNvs,
|
||||||
limine_api::memmap::MEMMAP_BAD_MEMORY => MemoryRegionKind::BadMemory,
|
limine_api::memmap::MEMMAP_BAD_MEMORY => MemoryRegionKind::BadMemory,
|
||||||
limine_api::memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => {
|
limine_api::memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => {
|
||||||
@@ -60,39 +160,87 @@ impl BootInfo {
|
|||||||
_ => MemoryRegionKind::Reserved,
|
_ => MemoryRegionKind::Reserved,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
.map_err(|_| BootError::TooManyMemoryRegions)?;
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum BootError {
|
|
||||||
UnsupportedBaseRevision,
|
|
||||||
FailedToGetKernelAddress,
|
|
||||||
FailedToGetHHDMAddress,
|
|
||||||
FailedToGetMemmap,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_boot_info() -> Result<BootInfo, BootError> {
|
|
||||||
if !BASE_REVISION.is_supported() {
|
|
||||||
return Err(BootError::UnsupportedBaseRevision);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let kernel_address = KERNEL_ADDRESS_REQUEST
|
let mut segment_physical = kernel_address.physical_base as usize;
|
||||||
.response()
|
let mut segment_virtual = core::ptr::addr_of!(__text_start) as usize;
|
||||||
.ok_or(BootError::FailedToGetKernelAddress)?
|
|
||||||
.physical_base;
|
|
||||||
let hhdm_offset = HHDM_REQUEST
|
|
||||||
.response()
|
|
||||||
.ok_or(BootError::FailedToGetHHDMAddress)?
|
|
||||||
.offset;
|
|
||||||
|
|
||||||
let memmap = MEMMAP_REQUEST
|
let mut segment_length = 0;
|
||||||
.response()
|
|
||||||
.ok_or(BootError::FailedToGetMemmap)?
|
let kernel_text_segment = KernelSegment {
|
||||||
.entries();
|
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 {
|
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,
|
hhdm_offset,
|
||||||
entries: memmap,
|
memory_map,
|
||||||
|
rsdp: VirtualAddr::new(rsdp.address as usize),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
mod limine;
|
mod limine;
|
||||||
|
|
||||||
pub use limine::{BootError, BootInfo, load_boot_info};
|
pub use limine::{BootInfo, load_boot_info};
|
||||||
|
|||||||
+8
-2
@@ -9,13 +9,13 @@ mod register {
|
|||||||
pub const INTERRUPT_ENABLE: u16 = 1;
|
pub const INTERRUPT_ENABLE: u16 = 1;
|
||||||
pub const DIVISOR_HIGH: 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 FIFO_CONTROL: u16 = 2;
|
||||||
|
|
||||||
pub const LINE_CONTROL: u16 = 3;
|
pub const LINE_CONTROL: u16 = 3;
|
||||||
pub const MODEM_CONTROL: u16 = 4;
|
pub const MODEM_CONTROL: u16 = 4;
|
||||||
pub const LINE_STATUS: u16 = 5;
|
pub const LINE_STATUS: u16 = 5;
|
||||||
pub const MODEM_STATUS: u16 = 6;
|
// pub const MODEM_STATUS: u16 = 6;
|
||||||
pub const SCRATCH: u16 = 7;
|
pub const SCRATCH: u16 = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +160,12 @@ pub fn init() -> Result<(), SerialPortError> {
|
|||||||
com1().init()
|
com1().init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_bytes(bytes: &[u8]) {
|
||||||
|
for byte in bytes {
|
||||||
|
com1().write_byte(*byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print(args: core::fmt::Arguments) {
|
pub fn print(args: core::fmt::Arguments) {
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod cpio;
|
||||||
|
pub mod elf;
|
||||||
+116
-7
@@ -1,4 +1,3 @@
|
|||||||
#![feature(abi_x86_interrupt)]
|
|
||||||
#![allow(clippy::needless_return)]
|
#![allow(clippy::needless_return)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
@@ -6,23 +5,133 @@
|
|||||||
mod arch;
|
mod arch;
|
||||||
mod boot;
|
mod boot;
|
||||||
mod debug;
|
mod debug;
|
||||||
|
mod format;
|
||||||
mod memory;
|
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)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn _start() -> ! {
|
pub extern "C" fn _start() -> ! {
|
||||||
serial::init().unwrap();
|
serial::init().unwrap();
|
||||||
|
|
||||||
arch::init();
|
let arch_state = arch::init();
|
||||||
let _boot_info = boot::load_boot_info().unwrap();
|
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 {
|
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]
|
#[panic_handler]
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct PhysicalAddr(u64);
|
pub struct PhysicalAddr(usize);
|
||||||
|
|
||||||
impl PhysicalAddr {
|
impl PhysicalAddr {
|
||||||
pub fn new(addr: u64) -> Self {
|
pub const fn new(addr: usize) -> Self {
|
||||||
Self(addr)
|
Self(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn as_u64(self) -> u64 {
|
pub const fn as_usize(self) -> usize {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct VirtualAddr(u64);
|
pub struct VirtualAddr(usize);
|
||||||
|
|
||||||
impl VirtualAddr {
|
impl VirtualAddr {
|
||||||
pub fn new(addr: u64) -> Self {
|
pub const fn new(addr: usize) -> Self {
|
||||||
Self(addr)
|
Self(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn as_u64(self) -> u64 {
|
pub const fn as_usize(self) -> usize {
|
||||||
self.0
|
self.0 as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn as_mut_ptr<T>(self) -> *mut T {
|
pub const unsafe fn as_mut_ptr<T>(self) -> *mut T {
|
||||||
self.as_u64() as *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 {
|
pub enum MemoryRegionKind {
|
||||||
Usable,
|
Usable,
|
||||||
Reserved,
|
Reserved,
|
||||||
@@ -43,9 +135,73 @@ pub enum MemoryRegionKind {
|
|||||||
MappedReserved,
|
MappedReserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum CachePolicy {
|
||||||
|
Uncacheable,
|
||||||
|
WriteBack,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct MemoryRegion {
|
pub struct MemoryRegion {
|
||||||
pub start: PhysicalAddr,
|
pub start: PhysicalAddr,
|
||||||
pub length: u64,
|
pub length: usize,
|
||||||
pub kind: MemoryRegionKind,
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod acpi;
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod bootstrap;
|
||||||
|
pub mod scheduler;
|
||||||
|
pub mod tcb;
|
||||||
@@ -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
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "dusk-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = false
|
||||||
|
bench = false
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user