Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b1f9dc0f8a
|
|||
|
8a71cdfcc5
|
|||
|
7dcf244ee6
|
|||
|
afcef918a3
|
@@ -5,3 +5,8 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
limine = "0.6.5"
|
||||
|
||||
[[bin]]
|
||||
name = "dusk"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ARTIFACTS_PATH ?= bin
|
||||
IMAGE_NAME ?= dusk.iso
|
||||
MODE ?= release
|
||||
MODE ?= debug
|
||||
ARCH ?= x86_64
|
||||
MEMORY ?= 512M
|
||||
# In MB
|
||||
@@ -16,6 +16,7 @@ EXPORT_SYMBOLS = true
|
||||
ISO_PATH = ${ARTIFACTS_PATH}/iso_root
|
||||
#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
|
||||
IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME}
|
||||
ESP_IMAGE = ${ARTIFACTS_PATH}/esp.img
|
||||
CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json
|
||||
QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH}
|
||||
LIMINE_BOOT_VARIATION = X64
|
||||
@@ -36,18 +37,13 @@ ifneq (${GDB},)
|
||||
QEMU_OPTS += -s -S
|
||||
endif
|
||||
|
||||
ifeq (${ARCH},aarch64)
|
||||
LIMINE_BOOT_VARIATION := AA64
|
||||
UEFI := true
|
||||
ifneq (${KVM},)
|
||||
QEMU_OPTS += -accel kvm -cpu host
|
||||
endif
|
||||
|
||||
ifneq (${UEFI},)
|
||||
RUN_OPTS := ovmf-${ARCH}
|
||||
ifeq (${ARCH},aarch64)
|
||||
QEMU_OPTS += -M virt -bios ovmf/ovmf-${ARCH}/OVMF.fd
|
||||
else
|
||||
QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd
|
||||
endif
|
||||
QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd
|
||||
endif
|
||||
|
||||
.PHONY: all build
|
||||
@@ -57,47 +53,22 @@ all: build
|
||||
build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso
|
||||
|
||||
check:
|
||||
cargo check
|
||||
cargo check -Zjson-target-spec
|
||||
|
||||
prepare-bin-files:
|
||||
# Remove ISO and everything in the bin directory
|
||||
rm -f ${IMAGE_PATH}
|
||||
rm -rf ${ARTIFACTS_PATH}/*
|
||||
|
||||
# Make bin/ bin/iso_root and bin/initramfs
|
||||
# Make bin/ and bin/iso_root
|
||||
mkdir -p ${ARTIFACTS_PATH}
|
||||
mkdir -p ${ISO_PATH}
|
||||
# mkdir -p ${INITRAMFS_PATH}
|
||||
mkdir -p ${ARTIFACTS_PATH}/mnt
|
||||
|
||||
#copy-initramfs-files:
|
||||
# echo "Hello World from Initramfs" > ${INITRAMFS_PATH}/example.txt
|
||||
# echo "Second file for testing" > ${INITRAMFS_PATH}/example2.txt
|
||||
# mkdir -p ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/
|
||||
# mkdir ${INITRAMFS_PATH}/mnt/
|
||||
# echo "Nexted file reads!!" > ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/yeah.txt
|
||||
|
||||
#compile-initramfs: copy-initramfs-files
|
||||
# # Make squashfs without compression temporaily so I can get it working before I have to write a gzip driver
|
||||
# mksquashfs ${INITRAMFS_PATH} ${ARTIFACTS_PATH}/initramfs.img ${MKSQUASHFS_OPTS}
|
||||
|
||||
run-scripts:
|
||||
# Place the build ID into the binary so it can be read at runtime
|
||||
@HASH=$$(md5sum ${KERNEL_FILE} | cut -c1-12) && \
|
||||
sed -i "s/__BUILD_ID__/$${HASH}/" ${KERNEL_FILE}
|
||||
|
||||
#ifeq (${EXPORT_SYMBOLS},true)
|
||||
# nm ${KERNEL_FILE} > scripts/symbols.table
|
||||
# @if [ ! -d "scripts/rustc_demangle" ]; then \
|
||||
# git clone "https://github.com/juls0730/rustc_demangle.py" "scripts/rustc_demangle"; \
|
||||
# fi
|
||||
# python scripts/demangle-symbols.py
|
||||
# mv scripts/symbols.table ${INITRAMFS_PATH}/
|
||||
#endif
|
||||
|
||||
# python scripts/font.py
|
||||
# mv scripts/font.psf ${INITRAMFS_PATH}/
|
||||
|
||||
#python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/
|
||||
|
||||
copy-iso-files:
|
||||
@@ -105,8 +76,6 @@ copy-iso-files:
|
||||
mkdir -p ${ISO_PATH}/boot/limine
|
||||
mkdir -p ${ISO_PATH}/EFI/BOOT
|
||||
|
||||
mkdir -p ${ISO_PATH}/mnt
|
||||
|
||||
cp -v limine.conf limine/limine-bios.sys ${ISO_PATH}/boot/limine
|
||||
cp -v limine/BOOT${LIMINE_BOOT_VARIATION}.EFI ${ISO_PATH}/EFI/BOOT/
|
||||
|
||||
@@ -114,8 +83,13 @@ copy-iso-files:
|
||||
cp -v ${KERNEL_FILE} ${ISO_PATH}/boot
|
||||
#cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot
|
||||
|
||||
partition-iso: copy-iso-files
|
||||
# Make empty ISO of 64M in size
|
||||
build-esp: copy-iso-files
|
||||
# Create and populate formatted FAT image for ESP partition (130048 1K-blocks = ~127MiB)
|
||||
mkfs.fat -F ${ESP_BITS} -C ${ESP_IMAGE} 130048
|
||||
mcopy -s -i ${ESP_IMAGE} ${ISO_PATH}/* ::
|
||||
|
||||
partition-iso:
|
||||
# Make empty disk image
|
||||
dd if=/dev/zero of=${IMAGE_PATH} bs=1M count=0 seek=${ISO_SIZE}
|
||||
ifneq (${UEFI},)
|
||||
parted -s ${IMAGE_PATH} mklabel gpt
|
||||
@@ -127,25 +101,17 @@ else
|
||||
parted -s ${IMAGE_PATH} set 1 boot on
|
||||
endif
|
||||
|
||||
# Make ISO with 1 partition starting at sector 2048 that is 32768 sectors, or 16MiB, in size
|
||||
# Then a second partition spanning the rest of the disk
|
||||
# Make second partition spanning the rest of the disk
|
||||
parted -s ${IMAGE_PATH} mkpart primary 262145s 100%
|
||||
|
||||
build-iso: partition-iso
|
||||
build-iso: partition-iso build-esp
|
||||
# Splice ESP partition into sector 2048 of the disk image
|
||||
dd if=${ESP_IMAGE} of=${IMAGE_PATH} bs=512 seek=2048 conv=notrunc
|
||||
ifeq (${UEFI},)
|
||||
# install limine for legacy bios
|
||||
./limine/limine bios-install ${IMAGE_PATH}
|
||||
endif
|
||||
|
||||
sudo losetup -Pf --show ${IMAGE_PATH} > loopback_dev
|
||||
sudo mkfs.fat -F ${ESP_BITS} `cat loopback_dev`p1
|
||||
sudo mount `cat loopback_dev`p1 ${ARTIFACTS_PATH}/mnt
|
||||
sudo cp -r ${ISO_PATH}/* ${ARTIFACTS_PATH}/mnt
|
||||
sync
|
||||
sudo umount ${ARTIFACTS_PATH}/mnt
|
||||
sudo losetup -d `cat loopback_dev`
|
||||
rm loopback_dev
|
||||
|
||||
compile-bootloader:
|
||||
@if [ ! -f "limine/.version" ] || [ "$$(cat limine/.version)" != "${LIMINE_VERSION}" ]; then \
|
||||
echo "Downloading Limine ${LIMINE_VERSION}..."; \
|
||||
@@ -167,12 +133,6 @@ ovmf-x86_64:
|
||||
cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \
|
||||
fi
|
||||
|
||||
ovmf-aarch64:
|
||||
mkdir -p ovmf/ovmf-aarch64
|
||||
@if [ ! -d "ovmf/ovmf-aarch64/OVMF.fd" ]; then \
|
||||
cd ovmf/ovmf-aarch64 && curl -o OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEAARCH64_QEMU_EFI.fd; \
|
||||
fi
|
||||
|
||||
# In debug mode, open a terminal and run this command:
|
||||
# gdb target/x86_64-unknown-none/debug/CappuccinOS.elf -ex "target remote :1234"
|
||||
|
||||
@@ -183,7 +143,7 @@ run-x86_64:
|
||||
tmux new-session -d -s qemu 'qemu-system-x86_64 ${QEMU_OPTS}'
|
||||
|
||||
run-x86_64-serial:
|
||||
qemu-system-x86_64 ${QEMU_OPTS} -boot d -display none -serial stdio -monitor none -no-reboot -no-shutdown
|
||||
qemu-system-x86_64 ${QEMU_OPTS} -boot d -display none -serial stdio -monitor none -no-reboot
|
||||
|
||||
line-count:
|
||||
cloc --quiet --exclude-dir=bin --include-lang=Rust --csv src/ | tail -n 1 | awk -F, '{print $$5}'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# DuskOS
|
||||
|
||||
A simple microkernel and operating system written in Rust for x86_64.
|
||||
A simple work-in-progress microkernel and operating system written in Rust for
|
||||
x86_64.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/arch/x86_64/linker.ld");
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
timeout: 3
|
||||
timeout: 0
|
||||
|
||||
/DuskOS
|
||||
protocol: limine
|
||||
|
||||
@@ -3,3 +3,6 @@ mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) use x86_64::{PageTableCreateError, PageTableMapError, PageTableUnmapError};
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
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,
|
||||
},
|
||||
println,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
#[derive(Debug)]
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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 delay_ticks(&self, count: u32) {
|
||||
// if count == 0 {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// APIC_TIMER_COUNT.store(0, Ordering::SeqCst);
|
||||
|
||||
// self.access
|
||||
// .write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
|
||||
// self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11);
|
||||
// self.access.write(APIC_TIMER_INITIAL_COUNT, count as u64);
|
||||
// self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
|
||||
|
||||
// while APIC_TIMER_COUNT.load(Ordering::SeqCst) == 0 {
|
||||
// unsafe {
|
||||
// core::arch::asm!("sti", "hlt", "cli", options(nomem, nostack));
|
||||
// }
|
||||
// }
|
||||
|
||||
// self.stop_timer();
|
||||
// }
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
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,142 @@
|
||||
use core::arch::asm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CpuFeaturesError {
|
||||
CpuidFeaturesNotSupported,
|
||||
InvalidPhysicalAddressWidth,
|
||||
InvalidVirtualAddressWidth,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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;
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
-6
@@ -1,13 +1,15 @@
|
||||
use core::arch::asm;
|
||||
|
||||
use crate::memory::VirtualAddr;
|
||||
|
||||
#[repr(C, align(8))]
|
||||
struct Gdt {
|
||||
entries: [u64; 5],
|
||||
entries: [u64; 7],
|
||||
}
|
||||
|
||||
impl Gdt {
|
||||
pub const fn new() -> Self {
|
||||
Self { entries: [0; 5] }
|
||||
Self { entries: [0; 7] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +49,9 @@ const DOUBLE_FAULT_STACK_SIZE: usize = 16 * 1024;
|
||||
|
||||
pub(super) const KERNEL_CODE_SELECTOR: u16 = 1 * 8;
|
||||
pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8;
|
||||
pub(super) const TSS_SELECTOR: u16 = 3 * 8;
|
||||
pub(super) const USER_CODE_SELECTOR: u16 = (3 * 8) | 3;
|
||||
pub(super) const USER_DATA_SELECTOR: u16 = (4 * 8) | 3;
|
||||
pub(super) const TSS_SELECTOR: u16 = 5 * 8;
|
||||
|
||||
#[repr(align(16))]
|
||||
#[allow(dead_code)] // field 0 is read, rust just cant tell
|
||||
@@ -74,6 +78,8 @@ pub fn init() {
|
||||
0,
|
||||
kernel_code_descriptor(),
|
||||
kernel_data_descriptor(),
|
||||
user_code_descriptor(),
|
||||
user_data_descriptor(),
|
||||
tss_low,
|
||||
tss_high,
|
||||
],
|
||||
@@ -84,6 +90,12 @@ pub fn init() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_kernel_stack(stack_top: VirtualAddr) {
|
||||
unsafe {
|
||||
TSS.privilege_stacks[0] = stack_top.as_usize() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
fn gdt_reload() {
|
||||
unsafe {
|
||||
asm!(
|
||||
@@ -119,17 +131,34 @@ fn gdt_reload() {
|
||||
}
|
||||
|
||||
const PRESENT: u64 = 1 << 47;
|
||||
const USER_DESCRIPTOR: u64 = 1 << 44;
|
||||
const CODE_DATA_DESCRIPTOR: u64 = 1 << 44;
|
||||
const USER_PRIVILEGE: u64 = 3 << 45;
|
||||
const EXECUTABLE: u64 = 1 << 43;
|
||||
const READ_WRITE: u64 = 1 << 41;
|
||||
const GRANULARITY: u64 = 1 << 55;
|
||||
const SIZE: u64 = 1 << 54;
|
||||
const LONG_MODE: u64 = 1 << 53;
|
||||
|
||||
fn kernel_code_descriptor() -> u64 {
|
||||
PRESENT | USER_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE
|
||||
PRESENT | CODE_DATA_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE | GRANULARITY
|
||||
}
|
||||
|
||||
fn kernel_data_descriptor() -> u64 {
|
||||
PRESENT | USER_DESCRIPTOR | READ_WRITE
|
||||
PRESENT | CODE_DATA_DESCRIPTOR | READ_WRITE | SIZE | GRANULARITY
|
||||
}
|
||||
|
||||
fn user_code_descriptor() -> u64 {
|
||||
PRESENT
|
||||
| CODE_DATA_DESCRIPTOR
|
||||
| USER_PRIVILEGE
|
||||
| EXECUTABLE
|
||||
| READ_WRITE
|
||||
| LONG_MODE
|
||||
| GRANULARITY
|
||||
}
|
||||
|
||||
fn user_data_descriptor() -> u64 {
|
||||
PRESENT | CODE_DATA_DESCRIPTOR | USER_PRIVILEGE | READ_WRITE | SIZE | GRANULARITY
|
||||
}
|
||||
|
||||
fn tss_descriptor(tss: *const TaskStateSegment) -> [u64; 2] {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::arch::{
|
||||
apic, timer,
|
||||
x86_64::interrupts::idt::{self, InterruptStackFrame},
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
extern "x86-interrupt" fn error_handler(_frame: InterruptStackFrame) {
|
||||
apic::record_error();
|
||||
apic::end_of_interrupt();
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn timer_handler(_frame: InterruptStackFrame) {
|
||||
apic::record_timer();
|
||||
apic::end_of_interrupt();
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn pit_calibration_handler(_frame: InterruptStackFrame) {
|
||||
timer::record_pit_calibration();
|
||||
apic::end_of_interrupt();
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn spurious_handler(_frame: InterruptStackFrame) {
|
||||
// No EOI
|
||||
}
|
||||
|
||||
pub(super) fn install(idt: &mut idt::Idt) {
|
||||
idt.set_handler(PIT_CALIBRATION_VECTOR, pit_calibration_handler, 0);
|
||||
idt.set_handler(APIC_ERROR_VECTOR, error_handler, 0);
|
||||
idt.set_handler(APIC_TIMER_VECTOR, timer_handler, 0);
|
||||
idt.set_handler(APIC_SPURIOUS_VECTOR, spurious_handler, 0);
|
||||
}
|
||||
@@ -46,11 +46,20 @@ fatal_with_error_code!(stack_segment_fault_handler, "STACK-SEGMENT FAULT");
|
||||
fatal_with_error_code!(general_protection_handler, "GENERAL PROTECTION FAULT");
|
||||
fatal_with_error_code!(alignment_check_handler, "ALIGNMENT CHECK");
|
||||
|
||||
extern "x86-interrupt" fn user_test_exit_handler(frame: InterruptStackFrame) {
|
||||
if frame.code_segment & 0b11 != 3 {
|
||||
panic!("user_test_exit_handler called from kernel");
|
||||
}
|
||||
|
||||
println!("User test exit");
|
||||
hcf();
|
||||
}
|
||||
|
||||
pub(super) fn install(idt: &mut idt::Idt) {
|
||||
idt.set_handler(0, divide_error_handler, 0);
|
||||
idt.set_handler(1, debug_handler, 0);
|
||||
idt.set_handler(2, non_maskable_interrupt_handler, 0);
|
||||
idt.set_handler(3, breakpoint_handler, 0);
|
||||
idt.set_user_handler(3, breakpoint_handler, 0);
|
||||
idt.set_handler(6, invalid_opcode_handler, 0);
|
||||
idt.set_handler(7, device_not_available_handler, 0);
|
||||
idt.set_error_code_handler(8, double_fault_handler, 1);
|
||||
@@ -63,6 +72,8 @@ pub(super) fn install(idt: &mut idt::Idt) {
|
||||
idt.set_error_code_handler(17, alignment_check_handler, 0);
|
||||
idt.set_handler(18, machine_check_handler, 0);
|
||||
idt.set_handler(19, simd_floating_point_handler, 0);
|
||||
|
||||
idt.set_user_handler(0x80, user_test_exit_handler, 0);
|
||||
}
|
||||
|
||||
fn read_cr2() -> u64 {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::exceptions;
|
||||
use crate::{arch::x86_64::gdt::KERNEL_CODE_SELECTOR, memory::VirtualAddr};
|
||||
use crate::{
|
||||
arch::x86_64::{gdt::KERNEL_CODE_SELECTOR, interrupts::apic_vectors},
|
||||
memory::VirtualAddr,
|
||||
};
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -17,7 +20,7 @@ impl IdtEntry {
|
||||
const fn missing() -> Self {
|
||||
return Self {
|
||||
offset_low: 0,
|
||||
code_selector: 0x08,
|
||||
code_selector: 0,
|
||||
ist: 0,
|
||||
attributes: 0,
|
||||
offset_middle: 0,
|
||||
@@ -46,6 +49,8 @@ pub(super) struct InterruptStackFrame {
|
||||
const INTERRUPT_GATE: u8 = 0b1110;
|
||||
const PRESENT: u8 = 1 << 7;
|
||||
const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE;
|
||||
const USER_DPL: u8 = 3 << 5;
|
||||
const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE;
|
||||
|
||||
pub(super) type Handler = extern "x86-interrupt" fn(InterruptStackFrame);
|
||||
pub(super) type ErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64);
|
||||
@@ -62,7 +67,7 @@ impl Idt {
|
||||
}
|
||||
|
||||
pub(super) fn set_handler(&mut self, vector: u8, handler: Handler, 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(
|
||||
@@ -71,17 +76,21 @@ impl Idt {
|
||||
handler: ErrorCodeHandler,
|
||||
ist: u8,
|
||||
) {
|
||||
self.set_handler_address(vector, handler as usize, ist);
|
||||
self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE);
|
||||
}
|
||||
|
||||
fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8) {
|
||||
pub(super) fn set_user_handler(&mut self, vector: u8, handler: Handler, ist: u8) {
|
||||
self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE);
|
||||
}
|
||||
|
||||
fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8, attributes: u8) {
|
||||
self.entries[vector as usize] = IdtEntry {
|
||||
offset_low: address as u16,
|
||||
offset_middle: (address >> 16) as u16,
|
||||
offset_high: (address >> 32) as u32,
|
||||
code_selector: KERNEL_CODE_SELECTOR,
|
||||
ist: ist & 0b111,
|
||||
attributes: KERNEL_INTERRUPT_GATE,
|
||||
attributes,
|
||||
reserved: 0,
|
||||
};
|
||||
}
|
||||
@@ -97,6 +106,7 @@ pub fn idt_init() {
|
||||
let mut idt = Idt::new();
|
||||
|
||||
exceptions::install(&mut idt);
|
||||
apic_vectors::install(&mut idt);
|
||||
|
||||
unsafe {
|
||||
core::ptr::addr_of_mut!(IDT).write(idt);
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
use core::arch::asm;
|
||||
|
||||
pub(super) mod apic_vectors;
|
||||
mod exceptions;
|
||||
mod idt;
|
||||
|
||||
pub use idt::idt_init as init;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn disable_interrupts() {
|
||||
unsafe {
|
||||
asm!("cli");
|
||||
asm!("cli", options(nostack));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable_interrupts() {
|
||||
unsafe {
|
||||
asm!("sti", options(nostack));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
use crate::{
|
||||
memory::{
|
||||
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
|
||||
},
|
||||
platform::acpi::{InterruptPolarity, TriggerMode},
|
||||
println,
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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. */
|
||||
/* Additionally, leave space for the ELF headers by adding SIZEOF_HEADERS to the */
|
||||
/* base load address. */
|
||||
. = 0xffffffff80000000 + SIZEOF_HEADERS;
|
||||
. = 0xffffffff80000000;
|
||||
|
||||
__text_start = .;
|
||||
. += SIZEOF_HEADERS;
|
||||
|
||||
.text : {
|
||||
*(.text .text.*)
|
||||
@@ -33,6 +36,8 @@ SECTIONS
|
||||
|
||||
/* Move to the next memory page for .rodata */
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
__text_end = .;
|
||||
__rodata_start = .;
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
@@ -40,7 +45,9 @@ SECTIONS
|
||||
|
||||
/* Move to the next memory page for .data */
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
|
||||
__rodata_end = .;
|
||||
__data_start = .;
|
||||
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
|
||||
@@ -56,6 +63,11 @@ SECTIONS
|
||||
*(.dynamic)
|
||||
} :data :dynamic
|
||||
|
||||
.got : {
|
||||
*(.got .got.*)
|
||||
*(.got.plt .got.plt.*)
|
||||
} :data
|
||||
|
||||
/* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
|
||||
/* unnecessary zeros will be written to the binary. */
|
||||
/* If you need, for example, .init_array and .fini_array, those should be placed */
|
||||
@@ -65,6 +77,9 @@ SECTIONS
|
||||
*(COMMON)
|
||||
} :data
|
||||
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
__data_end = .;
|
||||
|
||||
/* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
|
||||
/* Also discard the program interpreter section since we do not need one. This is */
|
||||
/* more or less equivalent to the --no-dynamic-linker linker flag, except that it */
|
||||
|
||||
+196
-3
@@ -1,20 +1,213 @@
|
||||
pub mod apic;
|
||||
mod cpu;
|
||||
mod gdt;
|
||||
mod interrupts;
|
||||
pub mod paging;
|
||||
pub mod io_apic;
|
||||
mod paging;
|
||||
mod pit;
|
||||
pub mod port;
|
||||
pub mod timer;
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
pub use interrupts::disable_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::{
|
||||
apic::LocalApic,
|
||||
io_apic::{IOAPIC_VIRTUAL_ADDRESS, IoApic},
|
||||
x86_64::interrupts::apic_vectors::PIT_CALIBRATION_VECTOR,
|
||||
},
|
||||
memory::{AddressSpace, FrameAllocator, VirtualAddr},
|
||||
platform::acpi::Madt,
|
||||
println,
|
||||
};
|
||||
|
||||
pub fn init() -> ArchState {
|
||||
disable_interrupts();
|
||||
println!("Loading GDT...");
|
||||
gdt::init();
|
||||
println!("Loading IDT...");
|
||||
interrupts::init();
|
||||
println!("Detecting CPU features...");
|
||||
let cpu_features = cpu::detect_features_and_enable();
|
||||
let paging =
|
||||
PagingConfig::from_features(cpu_features.expect("required CPU features are not supported"));
|
||||
ArchState { paging }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(unused)]
|
||||
pub enum InterruptInitError {
|
||||
InvalidLocalApicId,
|
||||
InvalidLocalApicAddress,
|
||||
FailedToGetIoApic,
|
||||
IoApicError(io_apic::IoApicError),
|
||||
LocalApicError(apic::LocalApicError),
|
||||
TimerCalibrationError(timer::TimerCalibrationError),
|
||||
MalformedMadt,
|
||||
PitNotHandled,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptController {
|
||||
local_apic: LocalApic,
|
||||
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,
|
||||
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: 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,
|
||||
})
|
||||
}
|
||||
|
||||
// #[derive(Debug)]
|
||||
// pub enum TimerError {
|
||||
// DurationOverflow,
|
||||
// }
|
||||
|
||||
// impl InterruptController {
|
||||
// pub fn delay(&self, duration: core::time::Duration) -> Result<(), TimerError> {
|
||||
// let nanoseconds = duration.as_nanos();
|
||||
|
||||
// let ticks = (nanoseconds
|
||||
// .checked_mul(self.local_timer_frequency as u128)
|
||||
// .ok_or(TimerError::DurationOverflow)?
|
||||
// + 999_999_999)
|
||||
// / 1_000_000_000;
|
||||
|
||||
// if ticks == 0 {
|
||||
// return Ok(());
|
||||
// }
|
||||
|
||||
// let mut remaining = ticks;
|
||||
|
||||
// while remaining > 0 {
|
||||
// let chunk = remaining.min(u32::MAX as u128) as u32;
|
||||
// self.local_apic.delay_ticks(chunk);
|
||||
// remaining -= chunk as u128;
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// 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);
|
||||
|
||||
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,
|
||||
) -> ! {
|
||||
println!("Entering user mode");
|
||||
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov ds, {user_data_selector:x}",
|
||||
"mov es, {user_data_selector:x}",
|
||||
"mov fs, {user_data_selector:x}",
|
||||
"mov gs, {user_data_selector:x}", // ss is handled by iretq
|
||||
|
||||
"push {user_data_selector}",
|
||||
"push {user_stack_pointer}",
|
||||
"pushfq",
|
||||
"push {user_code_selector}",
|
||||
"push {user_instruction_pointer}",
|
||||
"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() {
|
||||
|
||||
+691
-541
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
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 struct Pit;
|
||||
|
||||
impl Pit {
|
||||
pub fn start_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ pub unsafe fn read_u8(port: u16) -> u8 {
|
||||
"in al, dx",
|
||||
in("dx") port,
|
||||
out("al") value,
|
||||
options(nomem, nostack, preserves_flags),
|
||||
options(nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
value
|
||||
@@ -62,7 +62,7 @@ pub unsafe fn write_u8(port: u16, value: u8) {
|
||||
"out dx, al",
|
||||
in("dx") port,
|
||||
in("al") value,
|
||||
options(nomem, nostack, preserves_flags),
|
||||
options(nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, Pit},
|
||||
},
|
||||
},
|
||||
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();
|
||||
|
||||
Pit::start_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);
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"cpu": "x86-64",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"llvm-target": "x86_64-unknown-none",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": 64,
|
||||
"target-c-int-width": 32,
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
"rustc-abi": "softfloat",
|
||||
"os": "DawnOS",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "ld.lld",
|
||||
"pre-link-args": {
|
||||
"ld.lld": ["-melf_x86_64", "--script=./src/arch/x86_64/linker.ld"]
|
||||
},
|
||||
"panic-strategy": "abort",
|
||||
"exe-suffix": ".elf",
|
||||
"disable-redzone": true
|
||||
}
|
||||
"arch": "x86_64",
|
||||
"cpu": "x86-64",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"llvm-target": "x86_64-unknown-none",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": 64,
|
||||
"target-c-int-width": 32,
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
"rustc-abi": "softfloat",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "ld.lld",
|
||||
"pre-link-args": {
|
||||
"ld.lld": [
|
||||
"-melf_x86_64",
|
||||
"--script=./src/arch/x86_64/linker.ld"
|
||||
]
|
||||
},
|
||||
"panic-strategy": "abort",
|
||||
"exe-suffix": ".elf",
|
||||
"disable-redzone": true
|
||||
}
|
||||
+134
-43
@@ -1,9 +1,14 @@
|
||||
use ::limine as limine_api;
|
||||
use limine::paging::PagingMode;
|
||||
use limine::request::{PagingModeRequest, RsdpRequest};
|
||||
|
||||
use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest};
|
||||
use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker};
|
||||
|
||||
use crate::memory::{KernelImage, PhysicalAddr, VirtualAddr};
|
||||
use crate::memory::{
|
||||
KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion, MemoryRegionKind, PagePermissions,
|
||||
PhysicalAddr, VirtualAddr,
|
||||
};
|
||||
|
||||
/// Sets the base revision to the latest revision supported by the crate.
|
||||
/// See specification for further info.
|
||||
@@ -25,6 +30,15 @@ static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
|
||||
#[unsafe(link_section = ".requests")]
|
||||
static MEMMAP_REQUEST: MemmapRequest = MemmapRequest::new();
|
||||
|
||||
#[used]
|
||||
#[unsafe(link_section = ".requests")]
|
||||
static PAGING_REQUEST: PagingModeRequest =
|
||||
PagingModeRequest::new(PagingMode::MAX, PagingMode::MAX, PagingMode::MIN);
|
||||
|
||||
#[used]
|
||||
#[unsafe(link_section = ".requests")]
|
||||
static RSDP_REQUEST: RsdpRequest = RsdpRequest::new();
|
||||
|
||||
/// Define the stand and end markers for Limine requests.
|
||||
#[used]
|
||||
#[unsafe(link_section = ".requests_start_marker")]
|
||||
@@ -33,35 +47,27 @@ static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
|
||||
#[unsafe(link_section = ".requests_end_marker")]
|
||||
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
|
||||
|
||||
unsafe extern "C" {
|
||||
static __text_start: u64;
|
||||
static __text_end: u64;
|
||||
|
||||
static __rodata_start: u64;
|
||||
static __rodata_end: u64;
|
||||
|
||||
static __data_start: u64;
|
||||
static __data_end: u64;
|
||||
}
|
||||
|
||||
pub struct BootInfo {
|
||||
pub kernel_address: KernelImage,
|
||||
pub kernel_layout: KernelMemoryLayout,
|
||||
pub hhdm_offset: usize,
|
||||
entries: &'static [&'static limine_api::memmap::Entry],
|
||||
pub memory_map: MemoryMap,
|
||||
pub rsdp: VirtualAddr,
|
||||
}
|
||||
|
||||
impl BootInfo {
|
||||
pub fn memory_regions(&self) -> impl Iterator<Item = crate::memory::MemoryRegion> + Clone + '_ {
|
||||
use crate::memory::{MemoryRegion, MemoryRegionKind};
|
||||
self.entries.iter().map(|&entry| MemoryRegion {
|
||||
start: crate::memory::PhysicalAddr::new(entry.base as usize),
|
||||
length: entry.length as usize,
|
||||
kind: match entry.type_ {
|
||||
limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable,
|
||||
limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved,
|
||||
limine_api::memmap::MEMMAP_ACPI_RECLAIMABLE => MemoryRegionKind::AcpiReclaimable,
|
||||
limine_api::memmap::MEMMAP_ACPI_NVS => MemoryRegionKind::AcpiNvs,
|
||||
limine_api::memmap::MEMMAP_BAD_MEMORY => MemoryRegionKind::BadMemory,
|
||||
limine_api::memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => {
|
||||
MemoryRegionKind::BootloaderReclaimable
|
||||
}
|
||||
limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES => {
|
||||
MemoryRegionKind::KernelAndModules
|
||||
}
|
||||
limine_api::memmap::MEMMAP_FRAMEBUFFER => MemoryRegionKind::Framebuffer,
|
||||
limine_api::memmap::MEMMAP_MAPPED_RESERVED => MemoryRegionKind::MappedReserved,
|
||||
_ => MemoryRegionKind::Reserved,
|
||||
},
|
||||
})
|
||||
pub fn memory_regions(&self) -> impl Iterator<Item = MemoryRegion> + Clone + '_ {
|
||||
self.memory_map.iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +77,9 @@ pub enum BootError {
|
||||
FailedToGetKernelAddress,
|
||||
FailedToGetHHDMAddress,
|
||||
FailedToGetMemmap,
|
||||
TooManyMemoryRegions,
|
||||
FailedToLocateKernel,
|
||||
FailedToGetRsdp,
|
||||
}
|
||||
|
||||
pub fn load_boot_info() -> Result<BootInfo, BootError> {
|
||||
@@ -87,34 +95,117 @@ pub fn load_boot_info() -> Result<BootInfo, BootError> {
|
||||
.ok_or(BootError::FailedToGetHHDMAddress)?
|
||||
.offset;
|
||||
|
||||
let rsdp = RSDP_REQUEST.response().ok_or(BootError::FailedToGetRsdp)?;
|
||||
|
||||
let memmap = MEMMAP_REQUEST
|
||||
.response()
|
||||
.ok_or(BootError::FailedToGetMemmap)?
|
||||
.entries();
|
||||
|
||||
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;
|
||||
let mut memory_map = MemoryMap::new();
|
||||
for entry in memmap.iter() {
|
||||
memory_map
|
||||
.push(MemoryRegion {
|
||||
start: PhysicalAddr::new(entry.base as usize),
|
||||
length: entry.length as usize,
|
||||
kind: match entry.type_ {
|
||||
limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable,
|
||||
limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved,
|
||||
limine_api::memmap::MEMMAP_ACPI_RECLAIMABLE => {
|
||||
MemoryRegionKind::AcpiReclaimable
|
||||
}
|
||||
limine_api::memmap::MEMMAP_ACPI_NVS => MemoryRegionKind::AcpiNvs,
|
||||
limine_api::memmap::MEMMAP_BAD_MEMORY => MemoryRegionKind::BadMemory,
|
||||
limine_api::memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => {
|
||||
MemoryRegionKind::BootloaderReclaimable
|
||||
}
|
||||
limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES => {
|
||||
MemoryRegionKind::KernelAndModules
|
||||
}
|
||||
limine_api::memmap::MEMMAP_FRAMEBUFFER => MemoryRegionKind::Framebuffer,
|
||||
limine_api::memmap::MEMMAP_MAPPED_RESERVED => MemoryRegionKind::MappedReserved,
|
||||
_ => MemoryRegionKind::Reserved,
|
||||
},
|
||||
})
|
||||
.map_err(|_| BootError::TooManyMemoryRegions)?;
|
||||
}
|
||||
|
||||
let kernel_length = kernel_length.ok_or(BootError::FailedToLocateKernel)?;
|
||||
let mut segment_physical = kernel_address.physical_base as usize;
|
||||
let mut segment_virtual = core::ptr::addr_of!(__text_start) as usize;
|
||||
|
||||
let mut segment_length = 0;
|
||||
|
||||
let kernel_text_segment = KernelSegment {
|
||||
physical_base: PhysicalAddr::new(segment_physical),
|
||||
virtual_base: VirtualAddr::new(segment_virtual),
|
||||
length: (core::ptr::addr_of!(__text_end) as usize)
|
||||
- (core::ptr::addr_of!(__text_start) as usize),
|
||||
permissions: PagePermissions::new(false, true, false),
|
||||
};
|
||||
|
||||
segment_length += kernel_text_segment.length;
|
||||
|
||||
segment_virtual = core::ptr::addr_of!(__rodata_start) as usize;
|
||||
segment_physical = kernel_address.physical_base as usize
|
||||
+ (segment_virtual - kernel_address.virtual_base as usize);
|
||||
|
||||
let kernel_rodata_segment = KernelSegment {
|
||||
physical_base: PhysicalAddr::new(segment_physical),
|
||||
virtual_base: VirtualAddr::new(segment_virtual),
|
||||
length: (core::ptr::addr_of!(__rodata_end) as usize)
|
||||
- (core::ptr::addr_of!(__rodata_start) as usize),
|
||||
permissions: PagePermissions::new(false, false, false),
|
||||
};
|
||||
|
||||
segment_length += kernel_rodata_segment.length;
|
||||
|
||||
segment_virtual = core::ptr::addr_of!(__data_start) as usize;
|
||||
segment_physical = kernel_address.physical_base as usize
|
||||
+ (segment_virtual - kernel_address.virtual_base as usize);
|
||||
|
||||
let kernel_data_segment = KernelSegment {
|
||||
physical_base: PhysicalAddr::new(segment_physical),
|
||||
virtual_base: VirtualAddr::new(segment_virtual),
|
||||
length: (core::ptr::addr_of!(__data_end) as usize)
|
||||
- (core::ptr::addr_of!(__data_start) as usize),
|
||||
permissions: PagePermissions::new(true, false, false),
|
||||
};
|
||||
|
||||
segment_length += kernel_data_segment.length;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let mut kernel_length = None;
|
||||
for &entry in memmap.iter() {
|
||||
if entry.type_ != limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES {
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.base != kernel_address.physical_base {
|
||||
continue;
|
||||
}
|
||||
|
||||
kernel_length = Some(entry.length as usize);
|
||||
break;
|
||||
}
|
||||
|
||||
if kernel_length.is_none() {
|
||||
return Err(BootError::FailedToLocateKernel);
|
||||
}
|
||||
|
||||
debug_assert_eq!(segment_length, kernel_length.unwrap());
|
||||
}
|
||||
|
||||
Ok(BootInfo {
|
||||
kernel_address: KernelImage {
|
||||
physical_base: PhysicalAddr::new(kernel_address.physical_base as usize),
|
||||
virtual_base: VirtualAddr::new(kernel_address.virtual_base as usize),
|
||||
length: kernel_length,
|
||||
kernel_layout: KernelMemoryLayout {
|
||||
segments: [
|
||||
kernel_text_segment,
|
||||
kernel_rodata_segment,
|
||||
kernel_data_segment,
|
||||
],
|
||||
},
|
||||
hhdm_offset: hhdm_offset as usize,
|
||||
entries: memmap,
|
||||
memory_map,
|
||||
rsdp: VirtualAddr::new(rsdp.address as usize),
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
mod limine;
|
||||
|
||||
pub use limine::{BootError, BootInfo, load_boot_info};
|
||||
pub use limine::{BootInfo, load_boot_info};
|
||||
|
||||
+2
-2
@@ -9,13 +9,13 @@ mod register {
|
||||
pub const INTERRUPT_ENABLE: u16 = 1;
|
||||
pub const DIVISOR_HIGH: u16 = 1;
|
||||
|
||||
pub const INTERRUPT_IDENTIFICATION: u16 = 2;
|
||||
// pub const INTERRUPT_IDENTIFICATION: u16 = 2;
|
||||
pub const FIFO_CONTROL: u16 = 2;
|
||||
|
||||
pub const LINE_CONTROL: u16 = 3;
|
||||
pub const MODEM_CONTROL: u16 = 4;
|
||||
pub const LINE_STATUS: u16 = 5;
|
||||
pub const MODEM_STATUS: u16 = 6;
|
||||
// pub const MODEM_STATUS: u16 = 6;
|
||||
pub const SCRATCH: u16 = 7;
|
||||
}
|
||||
|
||||
|
||||
+113
-45
@@ -7,85 +7,153 @@ mod arch;
|
||||
mod boot;
|
||||
mod debug;
|
||||
mod memory;
|
||||
mod platform;
|
||||
|
||||
use crate::{
|
||||
arch::paging,
|
||||
debug::serial,
|
||||
memory::{PhysicalAddr, VirtualAddr},
|
||||
memory::{
|
||||
AddressSpace, KernelStack, MemoryRegionKind, PagePermissions, UserStack, VirtualAddr,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct KernelHandoff {
|
||||
allocator: memory::FrameAllocator,
|
||||
address_space: AddressSpace,
|
||||
direct_map: memory::DirectMap,
|
||||
boot_info: boot::BootInfo,
|
||||
handoff_frame: memory::OwnedFrame,
|
||||
}
|
||||
|
||||
const _: () = assert!(core::mem::size_of::<KernelHandoff>() <= memory::FRAME_SIZE);
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
serial::init().unwrap();
|
||||
|
||||
arch::init();
|
||||
let arch_state = arch::init();
|
||||
let boot_info = boot::load_boot_info().unwrap();
|
||||
let direct_map = memory::DirectMap::new(boot_info.hhdm_offset);
|
||||
|
||||
let mut allocator = memory::FrameAllocator::new(boot_info.memory_regions(), direct_map)
|
||||
.expect("failed to create frame allocator");
|
||||
|
||||
let mut page_table = paging::AddressSpace::new(
|
||||
println!("Initializing page table...");
|
||||
let mut address_space = AddressSpace::new_kernel(
|
||||
direct_map,
|
||||
boot_info.memory_regions(),
|
||||
boot_info.kernel_address,
|
||||
&boot_info.kernel_layout,
|
||||
arch_state.paging,
|
||||
&mut allocator,
|
||||
)
|
||||
.expect("failed to create page table");
|
||||
|
||||
// safety: trust me bro
|
||||
unsafe { page_table.activate() };
|
||||
println!("Entering kernel main...");
|
||||
|
||||
let frame = allocator.alloc().unwrap();
|
||||
let bootstrap_stack = KernelStack::allocate(&mut address_space, &mut allocator)
|
||||
.expect("failed to allocate bootstrap stack");
|
||||
|
||||
let direct_mapped = direct_map.translate(frame.start_address()).unwrap();
|
||||
let translated = page_table.translate(direct_mapped).unwrap();
|
||||
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");
|
||||
|
||||
println!("{:?}", translated);
|
||||
let bootstrap_stack_top = bootstrap_stack.top();
|
||||
let handoff = KernelHandoff {
|
||||
allocator,
|
||||
address_space,
|
||||
direct_map,
|
||||
boot_info,
|
||||
handoff_frame,
|
||||
};
|
||||
|
||||
let new_virtual = VirtualAddr::new(0x8000_0000);
|
||||
|
||||
assert!(page_table.translate(new_virtual).is_none());
|
||||
|
||||
let page = paging::Page::from_start_address(new_virtual).unwrap();
|
||||
|
||||
page_table
|
||||
.map(
|
||||
page,
|
||||
frame,
|
||||
paging::PagePermissions::KERNEL_DATA,
|
||||
&mut allocator,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
page_table.translate(new_virtual),
|
||||
Some(frame.start_address())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
page_table.translate(VirtualAddr::new(new_virtual.as_usize() + 123)),
|
||||
Some(PhysicalAddr::new(frame.start_address().as_usize() + 123))
|
||||
);
|
||||
|
||||
// write to the page and read it back via HHDM
|
||||
unsafe {
|
||||
core::ptr::write_bytes(new_virtual.as_mut_ptr::<u8>(), 0xFF, 0x1000);
|
||||
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);
|
||||
}
|
||||
|
||||
let slice = unsafe { core::slice::from_raw_parts(direct_mapped.as_ptr::<u8>(), 0x1000) };
|
||||
allocator.reclaim_regions(
|
||||
boot_info.memory_regions(),
|
||||
MemoryRegionKind::BootloaderReclaimable,
|
||||
);
|
||||
|
||||
assert!(slice.iter().all(|&byte| byte == 0xFF));
|
||||
println!("Initializing local ACPI...",);
|
||||
|
||||
println!("{:#X}", slice[0]);
|
||||
let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI");
|
||||
|
||||
let unmapped_frame = page_table.unmap(page, &mut allocator).unwrap();
|
||||
let madt = acpi
|
||||
.madt()
|
||||
.expect("failed to parse ACPI")
|
||||
.expect("MADT not found");
|
||||
|
||||
assert_eq!(unmapped_frame, frame);
|
||||
let interrupt_controller =
|
||||
arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space)
|
||||
.expect("failed to initialize interrupt controller");
|
||||
|
||||
unsafe { allocator.dealloc(unmapped_frame) };
|
||||
let mut user_addr_space = address_space
|
||||
.new_user(&mut allocator)
|
||||
.expect("failed to create user address space");
|
||||
|
||||
assert!(page_table.translate(new_virtual).is_none());
|
||||
let user_stack = UserStack::allocate(&mut user_addr_space, &mut allocator)
|
||||
.expect("failed to allocate user stack");
|
||||
|
||||
let user_instruction_pointer = VirtualAddr::new(0x8000);
|
||||
let user_code_page = allocator
|
||||
.alloc()
|
||||
.expect("failed to allocate user code page")
|
||||
.frame_address();
|
||||
user_addr_space
|
||||
.map(
|
||||
user_code_page.start_address(),
|
||||
user_instruction_pointer,
|
||||
PagePermissions::new(true, true, true),
|
||||
&mut allocator,
|
||||
memory::CachePolicy::WriteBack,
|
||||
)
|
||||
.expect("failed to map user code page");
|
||||
|
||||
unsafe {
|
||||
user_addr_space.activate();
|
||||
|
||||
let user_code: [u8; 5] = [
|
||||
0xCC, // INT3
|
||||
0xCD, 0x80, // INT 0x80
|
||||
0xEB, 0xFE, // JMP -2 (loop forever if exit returns)
|
||||
];
|
||||
|
||||
core::ptr::copy_nonoverlapping(
|
||||
user_code.as_ptr(),
|
||||
user_instruction_pointer.as_mut_ptr::<u8>(),
|
||||
user_code.len(),
|
||||
);
|
||||
|
||||
arch::enter_user(user_instruction_pointer, user_stack.top());
|
||||
}
|
||||
|
||||
hcf();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
use crate::{
|
||||
arch::{PageTable, PageTableCreateError, PageTableMapError, PageTableUnmapError, PagingConfig},
|
||||
memory::{
|
||||
CachePolicy, DirectMap, FRAME_SIZE, FrameAddr, FrameAllocator, KernelMemoryLayout,
|
||||
MemoryRegion, MemoryRegionKind, PagePermissions, PhysicalAddr, VirtualAddr,
|
||||
},
|
||||
};
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum AddressSpaceKind {
|
||||
Kernel,
|
||||
User,
|
||||
}
|
||||
|
||||
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
|
||||
| MemoryRegionKind::KernelAndModules
|
||||
) {
|
||||
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() >= 0x0000_8000_0000_0000 {
|
||||
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 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
-19
@@ -19,6 +19,7 @@ enum FrameState {
|
||||
Allocated = 0b10,
|
||||
}
|
||||
|
||||
// 64 KiB per GiB
|
||||
#[derive(Debug)]
|
||||
struct Bitmap {
|
||||
start: VirtualAddr,
|
||||
@@ -68,7 +69,7 @@ pub enum FrameAllocatorInitError {
|
||||
BitmapOutsideDirectMap,
|
||||
}
|
||||
|
||||
// very very simple linked list frame/page allocator
|
||||
// very very simple bitmap frame/page allocator
|
||||
#[derive(Debug)]
|
||||
pub struct FrameAllocator {
|
||||
bitmap: Bitmap,
|
||||
@@ -86,7 +87,7 @@ impl FrameAllocator {
|
||||
let mut highest_frame: Option<usize> = None;
|
||||
|
||||
for region in regions.clone() {
|
||||
if region.kind != MemoryRegionKind::Usable {
|
||||
if !Self::should_track(region.kind) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ impl FrameAllocator {
|
||||
let mut bitmap_start_frame: Option<usize> = None;
|
||||
|
||||
for region in regions.clone() {
|
||||
if region.kind != MemoryRegionKind::Usable {
|
||||
if !Self::can_store_bitmap(region.kind) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ impl FrameAllocator {
|
||||
.ok_or(FrameAllocatorInitError::AddressOverflow)?;
|
||||
|
||||
for region in regions {
|
||||
if region.kind != MemoryRegionKind::Usable {
|
||||
if !Self::is_initially_free(region.kind) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -177,6 +178,23 @@ impl FrameAllocator {
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -186,7 +204,35 @@ impl FrameAllocator {
|
||||
.or_else(|| self.find_free_in(0, self.next_search))
|
||||
}
|
||||
|
||||
pub fn alloc_nozero(&mut self) -> Option<PhysicalFrame> {
|
||||
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;
|
||||
}
|
||||
@@ -197,15 +243,15 @@ impl FrameAllocator {
|
||||
self.free_frames -= 1;
|
||||
self.next_search = frame_idx.saturating_add(1);
|
||||
|
||||
Some(PhysicalFrame::from_index(frame_idx))
|
||||
Some(OwnedFrame::new(FrameAddr::from_index(frame_idx)))
|
||||
}
|
||||
|
||||
pub fn alloc(&mut self) -> Option<PhysicalFrame> {
|
||||
pub fn alloc(&mut self) -> Option<OwnedFrame> {
|
||||
let frame = self.alloc_nozero()?;
|
||||
|
||||
let start = self
|
||||
.direct_map
|
||||
.translate(frame.start_address())
|
||||
.translate(frame.frame_address().start_address())
|
||||
.expect("frame is outside the direct map");
|
||||
|
||||
unsafe {
|
||||
@@ -221,7 +267,7 @@ impl FrameAllocator {
|
||||
/// - 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: PhysicalFrame) {
|
||||
pub unsafe fn dealloc(&mut self, frame: OwnedFrame) {
|
||||
let frame_idx = frame.index();
|
||||
|
||||
match self.bitmap.state(frame_idx) {
|
||||
@@ -237,14 +283,6 @@ impl FrameAllocator {
|
||||
};
|
||||
}
|
||||
|
||||
pub const fn free_frames(&self) -> usize {
|
||||
self.free_frames
|
||||
}
|
||||
|
||||
pub const fn allocatable_frames(&self) -> usize {
|
||||
self.allocatable_frames
|
||||
}
|
||||
|
||||
fn usable_frame_range(
|
||||
region: MemoryRegion,
|
||||
) -> Result<core::ops::Range<usize>, FrameAllocatorInitError> {
|
||||
@@ -264,9 +302,9 @@ impl FrameAllocator {
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct PhysicalFrame(PhysicalAddr);
|
||||
pub struct FrameAddr(PhysicalAddr);
|
||||
|
||||
impl PhysicalFrame {
|
||||
impl FrameAddr {
|
||||
pub fn from_start_address(address: PhysicalAddr) -> Option<Self> {
|
||||
if address.as_usize() % FRAME_SIZE != 0 {
|
||||
return None;
|
||||
@@ -287,3 +325,31 @@ impl PhysicalFrame {
|
||||
self.0.as_usize() / FRAME_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
// specifically not Clone or Copy
|
||||
#[derive(Debug)]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
+81
-7
@@ -1,12 +1,38 @@
|
||||
mod address_space;
|
||||
mod frame;
|
||||
pub mod stack;
|
||||
|
||||
pub use frame::{FRAME_SIZE, FrameAllocator, PhysicalFrame};
|
||||
#[allow(unused)]
|
||||
pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError};
|
||||
pub use frame::{FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame};
|
||||
pub use stack::{KernelStack, StackCreateError, UserStack};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct KernelImage {
|
||||
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, Debug, 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)]
|
||||
@@ -14,7 +40,7 @@ pub struct KernelImage {
|
||||
pub struct PhysicalAddr(usize);
|
||||
|
||||
impl PhysicalAddr {
|
||||
pub fn new(addr: usize) -> Self {
|
||||
pub const fn new(addr: usize) -> Self {
|
||||
Self(addr)
|
||||
}
|
||||
|
||||
@@ -28,7 +54,7 @@ impl PhysicalAddr {
|
||||
pub struct VirtualAddr(usize);
|
||||
|
||||
impl VirtualAddr {
|
||||
pub fn new(addr: usize) -> Self {
|
||||
pub const fn new(addr: usize) -> Self {
|
||||
Self(addr)
|
||||
}
|
||||
|
||||
@@ -36,15 +62,16 @@ impl VirtualAddr {
|
||||
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_usize() as *mut T
|
||||
}
|
||||
|
||||
pub unsafe fn as_ptr<T>(self) -> *const T {
|
||||
pub const unsafe fn as_ptr<T>(self) -> *const T {
|
||||
self.as_usize() as *const T
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum MemoryRegionKind {
|
||||
Usable,
|
||||
@@ -58,6 +85,12 @@ pub enum MemoryRegionKind {
|
||||
MappedReserved,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CachePolicy {
|
||||
Uncacheable,
|
||||
WriteBack,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct MemoryRegion {
|
||||
pub start: PhysicalAddr,
|
||||
@@ -81,3 +114,44 @@ impl DirectMap {
|
||||
.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,196 @@
|
||||
use crate::memory::{
|
||||
AddressSpace, CachePolicy, FRAME_SIZE, FrameAllocator, MapError, OwnedFrame, PagePermissions,
|
||||
VirtualAddr,
|
||||
};
|
||||
|
||||
const STACK_PAGES: usize = 16;
|
||||
const STACK_SIZE: usize = STACK_PAGES * FRAME_SIZE;
|
||||
|
||||
const KERNEL_STACK_TOP: VirtualAddr = VirtualAddr::new(0xFFFF_FFFE_0000_0000);
|
||||
const USER_STACK_TOP: VirtualAddr = VirtualAddr::new(0x0000_7FFF_FFFF_F000);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StackCreateError {
|
||||
AddressOverflow,
|
||||
UnalignedStackTop,
|
||||
OutOfFrames,
|
||||
GuardPageMapped,
|
||||
Map(MapError),
|
||||
}
|
||||
|
||||
struct StackMapping {
|
||||
guard_page: VirtualAddr,
|
||||
mapped_start: VirtualAddr,
|
||||
top: VirtualAddr,
|
||||
}
|
||||
|
||||
impl StackMapping {
|
||||
fn allocate(
|
||||
address_space: &mut AddressSpace,
|
||||
allocator: &mut FrameAllocator,
|
||||
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_PAGES {
|
||||
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,
|
||||
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..STACK_PAGES).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());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KernelStack {
|
||||
mapping: StackMapping,
|
||||
}
|
||||
|
||||
impl KernelStack {
|
||||
pub fn allocate(
|
||||
address_space: &mut AddressSpace,
|
||||
allocator: &mut FrameAllocator,
|
||||
) -> Result<Self, StackCreateError> {
|
||||
let mapping = StackMapping::allocate(
|
||||
address_space,
|
||||
allocator,
|
||||
KERNEL_STACK_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,
|
||||
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) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
use crate::memory::{DirectMap, PhysicalAddr, VirtualAddr};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AcpiError {
|
||||
InvalidInput,
|
||||
AddressOverflow,
|
||||
InvalidSdtLength,
|
||||
InvalidRootTableLength,
|
||||
MalformedAcpiTable,
|
||||
MalformedMadt,
|
||||
MissingIoApic,
|
||||
MultipleIoApicsUnsupported,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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, Debug)]
|
||||
struct Rsdp {
|
||||
signature: [u8; 8],
|
||||
checksum: u8,
|
||||
oem_id: [u8; 6],
|
||||
revision: u8,
|
||||
rsdt_address: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Xsdp {
|
||||
rsdp: Rsdp,
|
||||
length: u32,
|
||||
xsdt_address: u64,
|
||||
extended_checksum: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sdt {
|
||||
physical_addr: PhysicalAddr,
|
||||
length: usize,
|
||||
signature: [u8; 4],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(unused)]
|
||||
pub struct Madt<'a> {
|
||||
acpi: &'a AcpiTables,
|
||||
table: Sdt,
|
||||
pub local_apic_address: PhysicalAddr,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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, Debug)]
|
||||
pub struct MadtEntryHeader {
|
||||
kind: u8,
|
||||
length: u8,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LocalApicEntry {
|
||||
processor_id: u8,
|
||||
id: u8,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoApicInfo {
|
||||
pub id: u8,
|
||||
pub apic_address: PhysicalAddr,
|
||||
pub global_system_interrupt_base: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct IoApicEntry {
|
||||
id: u8,
|
||||
reserved: u8,
|
||||
apic_address: u32,
|
||||
global_system_interrupt_base: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct InterruptSourceOverride {
|
||||
bus: u8,
|
||||
source: u8,
|
||||
global_interrupt: u32,
|
||||
flags: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum InterruptPolarity {
|
||||
ActiveHigh,
|
||||
ActiveLow,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TriggerMode {
|
||||
Edge,
|
||||
Level,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct IsaIrqRoute {
|
||||
pub gsi: u32,
|
||||
pub polarity: InterruptPolarity,
|
||||
pub trigger: TriggerMode,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct IoApicNmiEntry {
|
||||
nmi_source: u8,
|
||||
reserved: u8,
|
||||
flags: u16,
|
||||
global_system_interrupt: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LocalApicNmiEntry {
|
||||
processor_id: u8,
|
||||
flags: u16,
|
||||
lint_num: u8,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LocalApicAddressOverride {
|
||||
reserved: u16,
|
||||
local_apic_address: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LocalX2ApicEntry {
|
||||
reserved: u16,
|
||||
local_x2apic_id: u32,
|
||||
flags: u32,
|
||||
acpi_processor_uid: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[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;
|
||||
Reference in New Issue
Block a user