Compare commits

..

No commits in common. "c9481b05c34dc6e886e1014ce3ff26a3c89b70ce" and "fc9fc22261b30457ebff889158359214eaf0db2d" have entirely different histories.

32 changed files with 65 additions and 5599 deletions

1
.gitignore vendored
View file

@ -1,2 +1 @@
/.direnv/
/kernel/qemu.log

35
Cargo.lock generated
View file

@ -1,35 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "kernel"
version = "0.1.0"
dependencies = [
"bit_field",
"bitflags",
"rbtree",
"seq-macro",
]
[[package]]
name = "rbtree"
version = "0.1.0"
[[package]]
name = "seq-macro"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"

View file

@ -1,7 +0,0 @@
[workspace]
resolver = "3"
members = [
"crates/*",
"kernel",
]

View file

@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "rbtree"
version = "0.1.0"

View file

@ -1,10 +0,0 @@
[package]
name = "rbtree"
version = "0.1.0"
edition = "2024"
[features]
default = []
std = []
[dependencies]

View file

@ -1,9 +0,0 @@
#![cfg_attr(not(test), no_std)]
#![feature(negative_impls)]
#![cfg_attr(test, feature(box_vec_non_null))]
mod raw_node;
extern crate alloc;
pub use raw_node::{RBTree, TreeIter, TreeNodeIter};

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,6 @@
rust-pkg
clang
gcc
gdb
mold
limine-full

View file

@ -1,7 +1,10 @@
[profile.dev]
codegen-backend = "llvm"
[unstable]
json-target-spec = true # lets us specify a custom target specification file
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "alloc", "compiler_builtins"]
build-std = ["core", "compiler_builtins"]
[build]
target = "x86_64-unknown-kernel.json"

14
kernel/Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
version = "2.13.1"
@ -18,13 +12,5 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
name = "kernel"
version = "0.1.0"
dependencies = [
"bit_field",
"bitflags",
"seq-macro",
]
[[package]]
name = "seq-macro"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"

View file

@ -10,20 +10,8 @@ test = false
bench = false
[lib]
test = true
test = false
bench = false
[[test]]
name = "simple"
harness = false
[[test]]
name = "stack_overflow"
harness = false
[dependencies]
bit_field = "0.10.3"
bitflags = "2.13.1"
seq-macro = "0.3.6"
rbtree = { path = "../crates/rbtree" }

View file

@ -1,7 +1,6 @@
fn main() {
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-changed=kernel.lds");
println!("cargo::rustc-link-arg=-Tkernel/kernel.lds");
// println!("cargo::rustc-link-arg-tests=-Tkernel.lds");
println!("cargo::rustc-link-arg-bin=kernel=-Tkernel.lds");
println!("cargo::rerun-if-changed=kernel.lds");
}

View file

@ -8,41 +8,16 @@ canonicalised="$(realpath -- "$bin")"
parent="$(dirname -- "$canonicalised")"
bin_name="$(basename -- "$canonicalised")"
parent_base="$(basename -- "$parent")"
case "$parent_base" in
rustdoctest*)
is_doctest=1
;;
*)
is_doctest=0
;;
esac
case "$parent" in
*/deps|*/deps/)
is_test=1
;;
*)
is_test=$is_doctest
;;
esac
echo "Running $bin_name (is_test=$is_test, is_doctest=$is_doctest)"
if [[ $is_test -eq 1 ]]; then
QEMU_TEST_ARGS=("-display" "none" "-device" "isa-debug-exit,iobase=0xf4,iosize=0x04")
else
QEMU_TEST_ARGS=()
fi
image="$parent/$bin_name.img"
if [[ "$canonicalised" -nt "$image" ]]; then
echo "Creating image $image"
cat > /tmp/limine.conf <<EOF
timeout: 0
serial: yes
serial_baudrate: 9600
/Kernel
protocol: limine
@ -64,23 +39,10 @@ EOF
mcopy -i "$image" "$limine_uefi_path" ::/EFI/BOOT/BOOTX64.EFI
fi
# cp -n "$OVMF_PATH/FV/OVMF_VARS.fd" "$parent/$bin_name.OVMF_VARS.fd"
set +e
qemu-system-x86_64 \
-drive file="$image",format=raw \
-machine q35,accel=kvm -enable-kvm \
-m 2G \
-drive if=pflash,format=raw,readonly=on,file="$OVMF_PATH/FV/OVMF_CODE.fd" \
-chardev stdio,id=serial0,logfile=qemu.log,signal=on \
-serial chardev:serial0 \
-vga std \
${QEMU_TEST_ARGS[@]}
qemu_ret=$?
if [[ $is_test -eq 1 ]]; then
exit $([ $qemu_ret -eq 33 ])
fi
-serial stdio \
-vga std

View file

@ -1,75 +0,0 @@
use core::ops::{Bound, Range};
pub const trait Bits {
const BITS: usize;
fn get_bit(&self, index: usize) -> bool;
fn set_bit(&mut self, index: usize, value: bool) -> &mut Self;
fn get_bits(&self, range: Range<usize>) -> Self;
fn set_bits(&mut self, range: Range<usize>, value: Self) -> &mut Self;
}
const fn range_to_bounds<R: [const] core::ops::RangeBounds<usize>>(
range: &R,
max: usize,
) -> (usize, usize) {
let start = match range.start_bound() {
Bound::Included(&start) => start,
Bound::Excluded(&start) => start + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&end) => end + 1,
Bound::Excluded(&end) => end,
Bound::Unbounded => max,
};
assert!(
start <= end,
"Start of range must be less than or equal to end"
);
assert!(end <= max, "End of range must be less than or equal to max");
(start, end)
}
macro_rules! impl_bits_for {
($($t:ty),*) => {
$(
const impl Bits for $t {
const BITS: usize = <$t>::BITS as usize;
fn get_bit(&self, index: usize) -> bool {
assert!(index < <Self as Bits>::BITS, "Index out of bounds");
(*self & (1 << index)) != 0
}
fn set_bit(&mut self, index: usize, value: bool) -> &mut Self{
assert!(index < <Self as Bits>::BITS, "Index out of bounds");
if value {
*self |= 1 << index;
} else {
*self &= !(1 << index);
}
self
}
fn get_bits(&self, range: Range<usize>) -> Self {
let (start, end) = range_to_bounds(&range, <Self as Bits>::BITS);
let leading = <Self as Bits>::BITS - end;
let bits = (*self << leading) >> (end + start);
bits
}
fn set_bits(&mut self, range: Range<usize>, value: Self) -> &mut Self{
let (start, end) = range_to_bounds(&range, <Self as Bits>::BITS);
let mask = ((1 << (end - start)) - 1);
let value = (value & mask) << start;
*self &= !(mask << start);
*self |= value;
self
}
}
)*
};
}
impl_bits_for!(u8, u16, u32, u64, u128);

View file

@ -1,122 +0,0 @@
use crate::{limine, sync::OnceLock};
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum MemoryRegionType {
Usable = 0,
Reserved = 1,
AcpiReclaimable = 2,
AcpiNvs = 3,
BadMemory = 4,
Framebuffer = 5,
FirmwareReserved = 6,
FirmwareReclaimable = 7,
KernelAndModules = 8,
}
impl From<limine::MemMapEntryKind> for MemoryRegionType {
fn from(kind: limine::MemMapEntryKind) -> Self {
match kind {
limine::MemMapEntryKind::Usable => Self::Usable,
limine::MemMapEntryKind::Reserved => Self::Reserved,
limine::MemMapEntryKind::AcpiReclaimable => Self::AcpiReclaimable,
limine::MemMapEntryKind::AcpiNvs => Self::AcpiNvs,
limine::MemMapEntryKind::BadMemory => Self::BadMemory,
limine::MemMapEntryKind::BootloaderReclaimable => Self::FirmwareReclaimable,
limine::MemMapEntryKind::KernelAndModules => Self::KernelAndModules,
limine::MemMapEntryKind::Framebuffer => Self::Framebuffer,
limine::MemMapEntryKind::ReservedMapped => Self::FirmwareReserved,
limine::MemMapEntryKind::Unknown => Self::Reserved,
}
}
}
impl MemoryRegionType {
pub fn should_map(&self) -> bool {
!matches!(self, Self::Reserved | Self::BadMemory)
}
pub fn is_usable(&self) -> bool {
matches!(self, Self::Usable | Self::AcpiReclaimable)
}
}
#[derive(Debug, Clone, Copy)]
pub struct MemoryRegion {
pub start: u64,
pub length: u64,
pub region_type: MemoryRegionType,
}
impl From<limine::MemMapEntry> for MemoryRegion {
fn from(entry: limine::MemMapEntry) -> Self {
Self {
start: entry.base,
length: entry.length,
region_type: entry.kind().into(),
}
}
}
impl MemoryRegion {
pub fn iter_pages(&self, page_size: usize) -> MemoryRegionIter<'_> {
MemoryRegionIter {
region: self,
cursor: 0,
page_size,
}
}
}
pub struct MemoryRegionIter<'a> {
region: &'a MemoryRegion,
cursor: u64,
page_size: usize,
}
impl Iterator for MemoryRegionIter<'_> {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.region.length {
return None;
}
let addr = self.region.start + self.cursor;
self.cursor += self.page_size as u64;
Some(addr)
}
}
const impl Default for MemoryRegion {
fn default() -> Self {
Self {
start: 0,
length: 0,
region_type: MemoryRegionType::Reserved,
}
}
}
pub struct BootInfo {
pub hhdm_base: u64,
pub memory_map: &'static [MemoryRegion],
}
pub static BOOT_INFO: OnceLock<BootInfo> = OnceLock::new();
static mut MEMORY_MAP: [MemoryRegion; 128] = [MemoryRegion::default(); 128];
pub fn init_boot_info<I: Iterator<Item = MemoryRegion>>(hhdm_base: u64, memory_map: I) {
BOOT_INFO.initialize(|| {
for (i, region) in memory_map.enumerate() {
assert!(i < 128, "Memory map has more than 128 entries");
unsafe {
MEMORY_MAP[i] = region;
}
}
Ok::<_, !>(BootInfo {
hhdm_base,
memory_map: unsafe { (&raw const MEMORY_MAP).as_ref_unchecked() },
})
});
}

View file

@ -1,34 +1,11 @@
#![no_std]
#![feature(
const_trait_impl,
const_default,
const_range,
debug_closure_helpers,
allocator_api,
ptr_cast_slice,
likely_unlikely,
never_type
)]
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, test_runner(crate::tests::test_runner))]
#![cfg_attr(test, no_main)]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![feature(const_trait_impl, const_default)]
extern crate alloc;
pub mod bits;
pub mod limine;
pub mod serial;
pub mod sync;
pub mod x86_64;
pub mod boot;
pub mod memory;
pub mod testing;
#[cfg(test)]
mod tests;
/// # Safety
pub unsafe fn volatile_copy<T: Sized>(src: *const T, dst: *mut T, count: usize) {
unsafe {
@ -41,22 +18,31 @@ pub unsafe fn volatile_copy<T: Sized>(src: *const T, dst: *mut T, count: usize)
}
}
pub struct DropGuard<F: FnOnce()>(::core::mem::ManuallyDrop<F>);
impl<F: FnOnce()> DropGuard<F> {
pub fn new(f: F) -> Self {
DropGuard(::core::mem::ManuallyDrop::new(f))
}
#[macro_export]
macro_rules! drop_guard {
($($stmts:stmt)*) => {
{
struct __DropGuard<F: FnOnce()>(::core::mem::ManuallyDrop<F>);
impl<F: FnOnce()> __DropGuard<F> {
#[allow(dead_code)]
pub fn forget(self) {
fn forget(self) {
let mut this = ::core::mem::ManuallyDrop::new(self);
unsafe {
::core::mem::ManuallyDrop::drop(&mut this.0);
ManuallyDrop::drop(&mut this.0);
}
}
}
impl<F: FnOnce()> Drop for DropGuard<F> {
impl<F: FnOnce()> Drop for __DropGuard<F> {
fn drop(&mut self) {
unsafe { ::core::ptr::read(&*self.0)() }
}
}
__DropGuard(::core::mem::ManuallyDrop::new(|| {
$($stmts)*
}))
}
};
}

View file

@ -1,4 +1,4 @@
use core::{cell::UnsafeCell, fmt::Debug, ptr::NonNull};
use core::{cell::UnsafeCell, ptr::NonNull};
#[repr(C)]
pub struct BaseRevision(UnsafeCell<[u64; 3]>);
@ -165,110 +165,3 @@ impl FramebufferRequest {
.unwrap_or(&[])
}
}
#[repr(C)]
pub struct MemMapResponse {
count: u64,
entries: *const *const MemMapEntry,
}
unsafe impl Send for MemMapResponse {}
unsafe impl Sync for MemMapResponse {}
pub type MemMapRequest = Request<MemMapResponse>;
impl MemMapRequest {
const ID: [u64; 2] = [0x67cf3d9d378a806f, 0xe304acdfc50c3c62];
pub const fn new() -> Self {
Self::new_raw(Self::ID, 0, ())
}
pub fn entries<'a>(&self) -> &'a [&'a MemMapEntry] {
// SAFETY: limine responses are guaranteed to be valid for the lifetime of the memory mapping.
self.response()
.map(|response| unsafe {
core::slice::from_raw_parts(
response.response.entries.cast::<&'a MemMapEntry>(),
response.response.count as usize,
)
})
.unwrap_or(&[])
}
}
#[repr(u64)]
#[derive(Debug)]
pub enum MemMapEntryKind {
Usable = 0,
Reserved = 1,
AcpiReclaimable = 2,
AcpiNvs = 3,
BadMemory = 4,
BootloaderReclaimable = 5,
KernelAndModules = 6,
Framebuffer = 7,
ReservedMapped = 8,
Unknown = u64::MAX,
}
impl MemMapEntryKind {
pub fn from_u64(value: u64) -> Self {
match value {
0 => Self::Usable,
1 => Self::Reserved,
2 => Self::AcpiReclaimable,
3 => Self::AcpiNvs,
4 => Self::BadMemory,
5 => Self::BootloaderReclaimable,
6 => Self::KernelAndModules,
7 => Self::Framebuffer,
8 => Self::ReservedMapped,
_ => Self::Unknown,
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MemMapEntry {
pub base: u64,
pub length: u64,
pub kind: u64,
}
impl MemMapEntry {
pub fn kind(&self) -> MemMapEntryKind {
MemMapEntryKind::from_u64(self.kind)
}
}
impl Debug for MemMapEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MemMapEntry")
.field("base", &format_args!("{:#x}", self.base))
.field("length", &format_args!("{:#x}", self.length))
.field("kind", &self.kind())
.finish()
}
}
#[repr(C)]
#[derive(Debug)]
pub struct HhdmResponse {
pub offset: u64,
}
pub type HhdmRequest = Request<HhdmResponse>;
impl HhdmRequest {
const ID: [u64; 2] = [0x48dcf1cb8ad2b852, 0x63984e959a98244b];
pub const fn new() -> Self {
Self::new_raw(Self::ID, 0, ())
}
pub fn offset(&self) -> Option<u64> {
self.response().map(|response| response.response.offset)
}
}

View file

@ -1,79 +1,18 @@
#![no_std]
#![no_main]
use kernel::{
memory::VirtAddr,
serial_println,
sync::LazyLock,
x86_64::{gdt::GlobalDescriptorTable, idt::InterruptDescriptorTable},
};
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
kernel::serial_println!("[PANIC] {}", _info);
kernel::x86_64::halt_loop()
}
static IDT: LazyLock<InterruptDescriptorTable> =
LazyLock::new(InterruptDescriptorTable::new_default);
static GDT: LazyLock<GlobalDescriptorTable> = LazyLock::new(GlobalDescriptorTable::new);
/// Entry point for the kernel
#[unsafe(no_mangle)]
extern "C" fn _start() -> ! {
kernel::serial_println!("Hello, world!");
assert!(limine_requests::LIMINE_BASE_REVISION.is_supported());
_ = kernel::memory::HHDM_BASE
.try_insert(
limine_requests::HHDM_REQUEST
.offset()
.expect("HHDM offset not provided by bootloader"),
)
.expect("HHDM offset already set");
kernel::boot::init_boot_info(
limine_requests::HHDM_REQUEST
.offset()
.expect("HHDM offset not provided by bootloader"),
limine_requests::MEMMAP_REQUEST
.entries()
.iter()
.map(|&&e| e.into()),
);
kernel::serial_println!(
"HHDM offset: 0x{:#x}",
kernel::memory::HHDM_BASE.get().unwrap()
);
kernel::serial_println!(
"Memory map: {:#?}",
limine_requests::MEMMAP_REQUEST.entries()
);
GDT.load();
IDT.load();
kernel::serial_println!("entry point: 0x{:x}", _start as *const () as usize);
kernel::serial_println!(
"entry point phy: {:?}",
kernel::x86_64::paging::get_physical_addr(VirtAddr(_start as *const () as u64))
);
let pmm = kernel::memory::PhysicalMemoryAllocator::from_memory_map(
kernel::boot::BOOT_INFO
.get()
.expect("Boot info not initialized")
.memory_map,
);
let leaf = kernel::x86_64::cpuid::Leaf8000008::get();
serial_println!("max phy: {:#?}", leaf);
// kernel::serial_println!("PMM: {pmm:#?}");
let fb = limine_requests::FRAMEBUFFER_REQUEST
.framebuffers()
.first()
@ -113,14 +52,6 @@ mod limine_requests {
#[unsafe(link_section = ".limine_requests")]
pub static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static HHDM_REQUEST: kernel::limine::HhdmRequest = kernel::limine::HhdmRequest::new();
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static MEMMAP_REQUEST: kernel::limine::MemMapRequest = kernel::limine::MemMapRequest::new();
#[used]
#[unsafe(link_section = ".limine_requests_end")]
static LIMINE_REQUESTS_END: RequestsEndMarker = REQUESTS_END_MARKER;

File diff suppressed because it is too large Load diff

View file

@ -300,11 +300,11 @@ pub mod uart_16550 {
#[derive(Debug, Clone, Copy, Default)]
pub enum BaudRate {
#[default]
Baud115200,
Baud57600,
Baud38400,
Baud19200,
#[default]
Baud9600,
}

View file

@ -210,9 +210,9 @@ mod once {
// even though we don't have unwinding, for
// completeness sake we'll poison the lock if
// the closure panics.
let guard = crate::DropGuard::new(|| {
let guard = crate::drop_guard! {
self.state.store(POISONED, Ordering::Release)
});
};
let state = OnceState {
poisoned: state == POISONED,

View file

@ -1,37 +0,0 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum QemuExitCode {
Success = 0x10,
Failed = 0x11,
}
pub fn exit_qemu(exit_code: QemuExitCode) -> ! {
use crate::x86_64::PortU16;
unsafe {
let port = PortU16(0xf4);
port.write(exit_code as u16);
}
crate::x86_64::halt_loop()
}
pub fn test_panic_handler(info: &core::panic::PanicInfo) -> ! {
crate::serial_println!("[PANIC] {}", info);
exit_qemu(QemuExitCode::Failed)
}
pub trait Testable {
fn run(&self);
}
impl<T> Testable for T
where
T: Fn(),
{
fn run(&self) {
crate::serial_print!("{}...\t", core::any::type_name::<T>());
self();
crate::serial_println!("[ok]");
}
}

View file

@ -1,26 +0,0 @@
use crate::testing::{QemuExitCode, Testable, exit_qemu};
pub fn test_runner(tests: &[&dyn Testable]) {
crate::serial_println!("Running {} tests", tests.len());
for test in tests {
test.run();
}
crate::serial_println!("[ok] All tests passed");
}
#[panic_handler]
fn panic_thunk(info: &core::panic::PanicInfo) -> ! {
crate::testing::test_panic_handler(info)
}
#[unsafe(export_name = "_start")]
fn main() -> ! {
crate::test_main();
exit_qemu(QemuExitCode::Success)
}
// #[test_case]
// fn panics() {
// panic!("This test should panic");
// }

View file

@ -1,19 +1,11 @@
#![cfg(target_arch = "x86_64")]
pub mod cpuid;
pub mod gdt;
pub mod idt;
pub mod instructions;
pub mod paging;
pub mod registers;
use core::{arch::asm, fmt::Debug};
pub use instructions::hlt;
use crate::memory::VirtAddr;
pub const PAGE_SIZE: usize = 4096;
#[inline]
pub fn hlt() {
unsafe {
core::arch::asm!("hlt", options(nomem, nostack, preserves_flags));
}
}
pub fn halt_loop() -> ! {
loop {
@ -31,184 +23,27 @@ impl PortU16 {
#[inline]
pub unsafe fn read(&self) -> u16 {
unsafe { instructions::read_u16(self.0) }
let value: u16;
unsafe {
core::arch::asm!(
"in ax, dx",
in("dx") self.0,
out("ax") value,
options(nomem, nostack, preserves_flags)
);
}
value
}
#[inline]
pub unsafe fn write(&self, value: u16) {
unsafe { instructions::write_u16(self.0, value) }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
// registers in the order they are pushed onto the stack:
pub struct Registers {
pub r15: u64,
pub r14: u64,
pub r13: u64,
pub r12: u64,
pub r11: u64,
pub r10: u64,
pub r9: u64,
pub r8: u64,
pub rdi: u64,
pub rsi: u64,
pub rbp: u64,
// rsp is not included here
pub rbx: u64,
pub rdx: u64,
pub rcx: u64,
pub rax: u64,
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RFlags: u64 {
const CARRY = 1 << 0;
const DEFAULT = 1 << 1;
const PARITY = 1 << 2;
const AUXILIARY_CARRY = 1 << 4;
const ZERO = 1 << 6;
const SIGN = 1 << 7;
const TRAP = 1 << 8;
const INTERRUPT_ENABLE = 1 << 9;
const DIRECTION = 1 << 10;
const OVERFLOW = 1 << 11;
const IOPL_LOW = 1 << 12;
const IOPL_HIGH = 1 << 13;
const NESTED_TASK = 1 << 14;
const RESUME = 1 << 16;
const VIRTUAL_8086_MODE = 1 << 17;
const ALIGNMENT_CHECK = 1 << 18;
const VIRTUAL_INTERRUPT_FLAG = 1 << 19;
const VIRTUAL_INTERRUPT_PENDING = 1 << 20;
const ID_FLAG = 1 << 21;
}
}
pub trait VirtAddrExt {
const LEVEL5: u8 = 4;
const LEVEL4: u8 = 3;
const LEVEL3: u8 = 2;
const LEVEL2: u8 = 1;
const LEVEL1: u8 = 0;
const PT: u8 = Self::LEVEL1;
const PD: u8 = Self::LEVEL2;
const PDPT: u8 = Self::LEVEL3;
const PML4: u8 = Self::LEVEL4;
const PML5: u8 = Self::LEVEL5;
fn is_canonical(&self) -> bool;
fn page_table_index<const LEVEL: u8>(&self) -> u16;
fn offset_4k(&self) -> u16;
fn offset_2m(&self) -> u32;
fn offset_1g(&self) -> u32;
fn into_parts<E: sealed::PageSize>(self) -> (E::Entries, E::Offset);
}
pub struct L4Entries4K;
pub struct L4Entries2M;
pub struct L4Entries1G;
pub struct U12(pub u16);
pub struct U21(pub u32);
pub struct U30(pub u32);
pub(crate) mod sealed {
use super::{L4Entries1G, L4Entries2M, L4Entries4K, U12, U21, U30};
pub trait PageSize {
type Entries;
type Offset;
fn decompose(addr_bits: u64) -> (Self::Entries, Self::Offset);
}
impl PageSize for L4Entries4K {
type Entries = [u16; 4];
type Offset = U12;
fn decompose(addr_bits: u64) -> (Self::Entries, Self::Offset) {
let entries = [
((addr_bits >> 39) & 0x1FF) as u16,
((addr_bits >> 30) & 0x1FF) as u16,
((addr_bits >> 21) & 0x1FF) as u16,
((addr_bits >> 12) & 0x1FF) as u16,
];
let offset = U12((addr_bits & 0xFFF) as u16);
(entries, offset)
}
}
impl PageSize for L4Entries2M {
type Entries = [u16; 3];
type Offset = U21;
fn decompose(addr_bits: u64) -> (Self::Entries, Self::Offset) {
let entries = [
((addr_bits >> 39) & 0x1FF) as u16,
((addr_bits >> 30) & 0x1FF) as u16,
((addr_bits >> 21) & 0x1FF) as u16,
];
let offset = U21((addr_bits & 0x1FFFFF) as u32);
(entries, offset)
}
}
impl PageSize for L4Entries1G {
type Entries = [u16; 2];
type Offset = U30;
fn decompose(addr_bits: u64) -> (Self::Entries, Self::Offset) {
let entries = [
((addr_bits >> 39) & 0x1FF) as u16,
((addr_bits >> 30) & 0x1FF) as u16,
];
let offset = U30((addr_bits & 0x3FFFFFFF) as u32);
(entries, offset)
}
}
}
impl VirtAddrExt for VirtAddr {
fn is_canonical(&self) -> bool {
// sign bits are bits 48-63, and they must all be the same as bit 47
//
// shift right 48 bits puts the 47th bit into the carry flag.
// sign-bits + CF should be 0
let canonical: u8;
unsafe {
asm!(
"shr {bits}, 48",
"adc {bits}, 0",
"setz {canonical}",
bits = inout(reg) self.0 => _,
canonical = out(reg_byte) canonical,
core::arch::asm!(
"out dx, ax",
in("dx") self.0,
in("ax") value,
options(nomem, nostack, preserves_flags)
);
}
canonical == 1
}
fn page_table_index<const LEVEL: u8>(&self) -> u16 {
assert!(LEVEL <= 4, "LEVEL must be in the range 0..=4");
let shift = 12 + (LEVEL * 9);
((self.0 >> shift) & 0x1FF) as u16
}
fn offset_4k(&self) -> u16 {
(self.0 & 0xFFF) as u16
}
fn offset_2m(&self) -> u32 {
(self.0 & 0x1FFFFF) as u32
}
fn offset_1g(&self) -> u32 {
(self.0 & 0x3FFFFFFF) as u32
}
fn into_parts<E: sealed::PageSize>(self) -> (E::Entries, E::Offset) {
E::decompose(self.0)
}
}

View file

@ -1,454 +0,0 @@
#![allow(clippy::identity_op)]
use core::fmt::Debug;
use bit_field::BitField;
use bitflags::bitflags;
pub struct CpuidResult {
pub eax: u32,
pub ebx: u32,
pub ecx: u32,
pub edx: u32,
}
pub fn cpuid(eax: u32, ecx: u32) -> CpuidResult {
let result = core::arch::x86_64::__cpuid_count(eax, ecx);
CpuidResult {
eax: result.eax,
ebx: result.ebx,
ecx: result.ecx,
edx: result.edx,
}
}
pub struct CpuId {
max_eax: u32,
vendor_id: [u8; 12],
}
pub struct Leaf1(CpuidResult);
impl Leaf1 {
pub fn get() -> Self {
let result = cpuid(1, 0);
Self(result)
}
pub fn brand_index(&self) -> u8 {
self.0.ebx.get_bits(0..8) as u8
}
pub fn clflush_line_size(&self) -> u8 {
self.0.ebx.get_bits(8..16) as u8
}
pub fn max_logical_processors(&self) -> u8 {
self.0.ebx.get_bits(16..24) as u8
}
pub fn initial_apic_id(&self) -> u8 {
self.0.ebx.get_bits(24..32) as u8
}
pub fn family_id(&self) -> u8 {
self.0.eax.get_bits(8..12) as u8 | ((self.0.eax.get_bits(20..28) as u8) << 4)
}
pub fn model_id(&self) -> u8 {
self.0.eax.get_bits(4..8) as u8 | ((self.0.eax.get_bits(16..20) as u8) << 4)
}
pub fn stepping_id(&self) -> u8 {
self.0.eax.get_bits(0..4) as u8
}
pub fn processor_type(&self) -> u8 {
self.0.eax.get_bits(12..14) as u8
}
pub fn flags(&self) -> Leaf1Flags {
Leaf1Flags::from_bits_truncate(self.0.ecx as u64 | ((self.0.edx as u64) << 32))
}
}
bitflags! {
pub struct Leaf1Flags: u64 {
const SSE3 = 1 << 0;
const PCLMULQDQ = 1 << 1;
const DTES64 = 1 << 2;
const MONITOR = 1 << 3;
const DS_CPL = 1 << 4;
const VMX = 1 << 5;
const SMX = 1 << 6;
const EIST = 1 << 7;
const TM2 = 1 << 8;
const SSSE3 = 1 << 9;
const CNXT_ID = 1 << 10;
const SDBG = 1 << 11;
const FMA = 1 << 12;
const CMPXCHG16B = 1 << 13;
const XTPR_UPDATE_CONTROL = 1 << 14;
const PDCM = 1 << 15;
const PCID = 1 << 17;
const DCA = 1 << 18;
const SSE4_1 = 1 << 19;
const SSE4_2 = 1 << 20;
const X2APIC = 1 << 21;
const MOVBE = 1 << 22;
const POPCNT = 1 << 23;
const TSC_DEADLINE_TIMER = 1 << 24;
const AESNI = 1 << 25;
const XSAVE = 1 << 26;
const OSXSAVE = 1 << 27;
const AVX = 1 <<28 ;
const F16C = 1 << 29;
const RDRAND = 1 << 30;
const FPU = 1 << (0 + u32::BITS);
const VME = 1 << (1 + u32::BITS);
const DE = 1 << (2 + u32::BITS);
const PSE = 1 << (3 + u32::BITS);
const TSC = 1 << (4 + u32::BITS);
const MSR = 1 << (5 + u32::BITS);
const PAE = 1 << (6 + u32::BITS);
const MCE = 1 << (7 + u32::BITS);
const CX8 = 1 << (8 + u32::BITS);
const APIC = 1 << (9 + u32::BITS);
const SEP = 1 << (11 + u32::BITS);
const MTRR = 1 << (12 + u32::BITS);
const PGE = 1 << (13 + u32::BITS);
const MCA = 1 << (14 + u32::BITS);
const CMOV = 1 << (15 + u32::BITS);
const PAT = 1 << (16 + u32::BITS);
const PSE36 = 1 << (17 + u32::BITS);
const PSN = 1 << (18 + u32::BITS);
const CLFSH = 1 << (19 + u32::BITS);
const DS = 1 << (21 + u32::BITS);
const ACPI = 1 << (22 + u32::BITS);
const MMX = 1 << (23 + u32::BITS);
const FXSR = 1 << (24 + u32::BITS);
const SSE = 1 << (25 + u32::BITS);
const SSE2 = 1 << (26 + u32::BITS);
const SS = 1 << (27 + u32::BITS);
const HTT = 1 << (28 + u32::BITS);
const TM = 1 << (29 + u32::BITS);
const PBE = 1 << (31 + u32::BITS);
}
}
pub struct Leaf7(CpuidResult);
impl Leaf7 {
pub fn get() -> Self {
let result = cpuid(7, 0);
Self(result)
}
pub fn subleaf1(&self) -> Option<Leaf7Subleaf1> {
if self.max_subleaf() >= 1 {
let result = cpuid(7, 1);
Some(Leaf7Subleaf1(result))
} else {
None
}
}
pub fn max_subleaf(&self) -> u32 {
self.0.eax
}
pub fn flags(&self) -> Leaf7Flags {
Leaf7Flags::from_bits_truncate(unsafe {
core::mem::transmute::<[[u8; 4]; 4], u128>(
[self.0.ebx, self.0.ecx, self.0.edx, 0].map(u32::to_ne_bytes),
)
})
}
pub fn mawau(&self) -> u8 {
self.0.ecx.get_bits(17..22) as u8
}
}
bitflags! {
pub struct Leaf7Flags: u128{
const FSGSBASE = 1 << 0;
const IA32_TSC_ADJUST_MSR = 1 << 1;
const SGX = 1 << 2;
const BMI1 = 1 << 3;
const HLE = 1 << 4;
const AVX2 = 1 << 5;
const SMEP = 1 << 7;
const BMI2 = 1 << 8;
const ERMS = 1 << 9;
const INVPCID = 1 << 10;
const RTM = 1 << 11;
const PQM = 1 << 12;
const FPU_CS_DS_DEPRECATION = 1 << 13;
const MPX = 1 << 14;
const PQE = 1 << 15;
const AVX512F = 1 << 16;
const AVX512DQ = 1 << 17;
const RDSEED = 1 << 18;
const ADX = 1 << 19;
const SMAP = 1 << 20;
const AVX512IFMA = 1 << 21;
const PCOMMIT = 1 << 22;
const CLFLUSHOPT = 1 << 23;
const CLWB = 1 <<24 ;
const INTEL_PT = 1 << 25;
const AVX512PF = 1 << 26;
const AVX512ER = 1 << 27;
const AVX512CD = 1 << 28;
const SHA = 1 << 29;
const AVX512BW = 1 << 30;
const AVX512VL = 1 << 31;
const PREFETCHWT1 = 1 << (0 + u32::BITS);
const AVX512VBMI = 1 << (1 + u32::BITS);
const UMIP = 1 << (2 + u32::BITS);
const PKU = 1 << (3 + u32::BITS);
const OSPKE = 1 << (4 + u32::BITS);
const RDPID = 1 << (22 + u32::BITS);
const SGX_LC = 1 << (30 + u32::BITS);
const SGX_KEYS = 1 << (1 + 2 * u32::BITS);
const AVX512_4VNNIW = 1 << (2 + 2 * u32::BITS);
const AVX512_4FMAPS = 1 << (3 + 2 * u32::BITS);
const FSRM = 1 << (4 + 2 * u32::BITS);
const UINTR = 1 << (5 + 2 * u32::BITS);
}
}
pub struct Leaf7Subleaf1(CpuidResult);
impl Leaf7Subleaf1 {
pub fn flags(&self) -> Leaf7Subleaf1Flags {
Leaf7Subleaf1Flags::from_bits_truncate(unsafe {
core::mem::transmute::<[u32; 4], u128>([self.0.eax, self.0.ebx, self.0.ecx, self.0.edx])
})
}
}
// in eax:ebx:ecx:edx order
bitflags! {
pub struct Leaf7Subleaf1Flags: u128 {
const SHA512 = 1 << 0;
const SM3 = 1 << 1;
const SM4 = 1 << 2;
const RAO_INT = 1 << 3;
const AVX_VNNI = 1 << 4;
const AVX512_BF16 = 1 << 5;
const LASS = 1 << 6;
const CMPCCXADD = 1 << 7;
const ARCHPERFMONTEXT = 1 << 8;
const FZRM = 1 << 10;
const FSRS = 1 << 11;
const RSRCS = 1 << 12;
const FRED = 1 << 17;
const LKGS = 1 << 18;
const WRMSRNS = 1 << 19;
const NMI_SRC = 1 << 20;
const AMX_FP16 = 1 << 21;
const HRESET = 1 << 22;
const AVX_IFMA = 1 << 23;
const LAM = 1 << 26;
const MSRLIST = 1 << 27;
const INVD_DISABLE_POST_BIOS_DONE = 1 << 30;
const MOVRS = 1 << 31;
const PPINSTR = 1 << (0 + u32::BITS);
const PBNDKB = 1 << (1 + u32::BITS);
const CPUIDMAXVAL_LIM_RMV = 1 << (2 + u32::BITS);
const RDT_M_ASYM = 1 << (0 + 2 * u32::BITS);
const RDT_A_ASYM = 1 << (1 + 2 * u32::BITS);
const MSR_IMM = 1 << (2 + 2 * u32::BITS);
const ACE = 1 << (3 + 2 * u32::BITS);
const AVX_VNNI_INT8 = 1 << (4 + 3 * u32::BITS);
const AVX_NE_CONVERT = 1 << (5 + 3 * u32::BITS);
const AMX_COMPLEX = 1 << (8 + 3 * u32::BITS);
const AVX_VNNI_INT16 = 1 << (10 + 3 * u32::BITS);
const UTMR = 1 << (13 + 3 * u32::BITS);
const PREFETCHI = 1 << (14 + 3 * u32::BITS);
const USER_MRS = 1 << (15 + 3 * u32::BITS);
const UIRET_UIF_FROM_RFLAGS = 1 << (17 + 3 * u32::BITS);
const CET_SSS = 1 << (18 + 3 * u32::BITS);
const AVX10 = 1 << (19 + 3 * u32::BITS);
const APX_F = 1 << (21 + 3 * u32::BITS);
const SEC_TEE_ATTESTATION = 1 << (22 + 3 * u32::BITS);
const MWAIT = 1 << (23 + 3 * u32::BITS);
const SLSM = 1 << (24 + 3 * u32::BITS);
}
}
bitflags! {
pub struct Leaf8000001Flags: u64 {
const FPU = 1 << 0;
const VME = 1 << 1;
const DE = 1 << 2;
const PSE = 1 << 3;
const TSC = 1 << 4;
const MSR = 1 << 5;
const PAE = 1 << 6;
const MCE = 1 << 7;
const CX8 = 1 << 8;
const APIC = 1 << 9;
const SYSCALL_K9 = 1 << 10;
const SYSCALL = 1 << 11;
const MTRR = 1 << 12;
const PGE = 1 << 13;
const MCA = 1 << 14;
const CMOV = 1 << 15;
const PAT = 1 << 16;
const PSE36 = 1 << 17;
const ECC_K9 = 1 << 18;
const ECC = 1 << 19;
const NX = 1 << 20;
const MMXEXT = 1 << 22;
const MMX = 1 << 23;
const FXSR_OPT = 1 << 24;
const PDPE1GB = 1 << 26;
const RDTSCP = 1 << 27;
const REX32 = 1 << 28;
const LM = 1 << 29;
const _3DNOWEXT = 1 << 30;
const _3DNOW = 1 << 31;
const LAHF_LM = 1 << (0 + u32::BITS);
const CMP_LEGACY = 1 << (1 + u32::BITS);
const SVM = 1 << (2 + u32::BITS);
const EXTAPIC = 1 << (3 + u32::BITS);
const CR8_LEGACY = 1 << (4 + u32::BITS);
const LZCNT = 1 << (5 + u32::BITS);
const SSE4A = 1 << (6 + u32::BITS);
const MISALIGNSSE = 1 << (7 + u32::BITS);
const _3DNOWPREFETCH = 1 << (8 + u32::BITS);
const OSVW = 1 << (9 + u32::BITS);
const IBS = 1 << (10 + u32::BITS);
const XOP = 1 << (11 + u32::BITS);
const SKINIT = 1 << (12 + u32::BITS);
const WDT = 1 << (13 + u32::BITS);
const LWP = 1 << (15 + u32::BITS);
const FMA4 = 1 << (16 + u32::BITS);
const TCE = 1 << (17 + u32::BITS);
const NODEID_MSR = 1 << (19 + u32::BITS);
const TBM = 1 << (21 + u32::BITS);
const TOPOEXT = 1 << (22 + u32::BITS);
const PERFCTR_CORE = 1 << (23 + u32::BITS);
const PERFCTR_NB = 1 << (24 + u32::BITS);
const DBX = 1 << (26 + u32::BITS);
const PERFTSC = 1 << (27 + u32::BITS);
const PCX_L2I = 1 << (28 + u32::BITS);
const MONITORX = 1 << (29 + u32::BITS);
const ADDR_MASK_EXT = 1 << (30 + u32::BITS);
}
}
pub struct Leaf8000008(CpuidResult);
impl Leaf8000008 {
pub fn get() -> Self {
let result = cpuid(0x80000008, 0);
Self(result)
}
pub fn physical_address_bits(&self) -> u8 {
self.0.eax.get_bits(0..8) as u8
}
pub fn num_linear_address_bits(&self) -> u8 {
self.0.eax.get_bits(8..16) as u8
}
pub fn guest_physical_address_bits(&self) -> u8 {
self.0.eax.get_bits(16..24) as u8
}
pub fn num_physical_threads(&self) -> u8 {
self.0.ecx.get_bits(0..8) as u8 + 1
}
pub fn apic_id_size(&self) -> u8 {
self.0.ecx.get_bits(12..16) as u8
}
pub fn performance_timestamp_counter_size(&self) -> u8 {
self.0.ecx.get_bits(16..18) as u8
}
pub fn max_invlpgb_page_count(&self) -> u16 {
self.0.edx.get_bits(0..16) as u16
}
pub fn max_rdpru_ecx(&self) -> u16 {
self.0.edx.get_bits(16..32) as u16
}
pub fn flags(&self) -> Leaf8000008Flags {
Leaf8000008Flags::from_bits_truncate(self.0.ebx)
}
}
impl Debug for Leaf8000008 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Leaf8000008")
.field("physical_address_bits", &self.physical_address_bits())
.field("num_linear_address_bits", &self.num_linear_address_bits())
.field(
"guest_physical_address_bits",
&self.guest_physical_address_bits(),
)
.field("num_physical_threads", &self.num_physical_threads())
.field("apic_id_size", &self.apic_id_size())
.field(
"performance_timestamp_counter_size",
&self.performance_timestamp_counter_size(),
)
.field("max_invlpgb_page_count", &self.max_invlpgb_page_count())
.field("max_rdpru_ecx", &self.max_rdpru_ecx())
.field("flags", &self.flags())
.finish()
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Leaf8000008Flags: u32 {
const CLZERO = 1 << 0;
const RETIRED_INSTR = 1 << 1;
const XRSTOR_FP_ERR = 1 << 2;
const INVLPGB = 1 << 3;
const RDPRU = 1 << 4;
const XOTEXT = 1 << 5;
const MBE = 1 << 6;
const MCOMMIT = 1 << 8;
const WBNOINVD = 1 << 9;
const LBR_EXT_V1 = 1 << 10;
const IBPB = 1 << 12;
const WBINVD_INT = 1 << 13;
const IBRS = 1 << 14;
const STIBP = 1 << 15;
const IBRS_ALWAYS = 1 << 16;
const STIBP_ALWAYS = 1 << 17;
const IBRS_PREFERRED = 1 << 18;
const IBRS_SAME_MODE_PROT = 1 << 19;
const NO_EFER_LMSLE = 1 << 20;
const INVLPGB_NESTED = 1 << 21;
const LBR_TSX = 1 << 22;
const PPIN = 1 << 23;
const SSBD = 1 << 24;
const SSBD_LEGACY = 1 << 25;
const SSBD_NO = 1 << 26;
const CPPC = 1 << 27;
const PSFD = 1 << 28;
const BTC_NO = 1 << 29;
const IBPB_RET = 1 << 30;
const BRANCH_SAPLING = 1 << 31;
}
}
impl CpuId {
pub fn get() -> Self {
let result = cpuid(0, 0);
let vendor_id = unsafe {
core::mem::transmute::<[u32; 3], [u8; 12]>([result.ebx, result.edx, result.ecx])
};
Self {
max_eax: result.eax,
vendor_id,
}
}
pub fn vendor_id(&self) -> &[u8; 12] {
&self.vendor_id
}
}

View file

@ -1,516 +0,0 @@
use core::{
arch::asm,
fmt::Debug,
mem::offset_of,
ops::{Deref, DerefMut},
};
use bit_field::BitField;
use crate::{
serial_println,
sync::LazyLock,
x86_64::{idt::ToAddress, instructions::*},
};
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GlobalDescriptorTable {
pub null: RawGdtEntry,
pub kernel_code: RawGdtEntry,
pub kernel_data: RawGdtEntry,
pub tss: TssEntry,
}
impl GlobalDescriptorTable {
pub fn new() -> Self {
let tss = &*TSS;
GlobalDescriptorTable {
null: RawGdtEntry::new(0, 0, 0, 0),
kernel_code: RawGdtEntry::new(0xfffff, 0, 0, 0)
.with_segment(true)
.with_privilege_level(RING0)
.with_present(true)
.with_segment_kind(
CodeDataSegmentKind::CodeSegment
| CodeDataSegmentKind::Executable
| CodeDataSegmentKind::ReadWrite
| CodeDataSegmentKind::Accessed,
)
.with_present(true)
.with_code_segment64(true)
.with_granularity(true),
kernel_data: RawGdtEntry::new(0xfffff, 0, 0, 0)
.with_segment(true)
.with_privilege_level(RING0)
.with_present(true)
.with_segment_kind(CodeDataSegmentKind::ReadWrite | CodeDataSegmentKind::Accessed)
.with_code_segment64(true)
.with_granularity(true),
tss: TssEntry::new(
core::mem::size_of::<TaskStateSegment>() as u32 - 1,
tss as *const TaskStateSegment as u64,
),
}
}
pub fn load(&'static self) {
serial_println!("Loading GDT {:#?}", self);
unsafe { Self::load_unsafe(self) }
}
/// # Safety
/// The caller must ensure that `this` is a valid pointer to a `GlobalDescriptorTable` that remains valid for the lifetime of the gdt.
pub unsafe fn load_unsafe(this: *const Self) {
let gdt_register = GdtRegister {
limit: (core::mem::size_of::<Self>() - 1) as u16,
base: this as u64,
};
unsafe {
core::arch::asm!(
"lgdt [{}]",
in(reg) &gdt_register,
options(readonly, nostack, preserves_flags)
);
let gs = msr::read_msr(msr::MSR_GS_BASE);
asm!(
"push {code_seg}",
"lea rax, [rip + 2f]",
"push rax",
"retfq",
"2:",
"mov ax, {data_seg}",
"mov ds, ax",
"mov es, ax",
"mov fs, ax",
"mov gs, ax",
"mov ss, ax",
code_seg = const offset_of!(GlobalDescriptorTable, kernel_code),
data_seg = const offset_of!(GlobalDescriptorTable, kernel_data),
lateout("rax") _,
);
msr::write_msr(msr::MSR_GS_BASE, gs);
asm!(
"mov ax, {tss_seg}",
"ltr ax",
tss_seg = const offset_of!(GlobalDescriptorTable, tss),
lateout("rax") _,
);
}
}
}
#[repr(C, packed)]
struct GdtRegister {
limit: u16,
base: u64,
}
impl Default for GlobalDescriptorTable {
fn default() -> Self {
Self::new()
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SystemSegmentKind {
Reserved = 0b0000,
Ldt = 0b0010,
TssAvailable = 0b1001,
TssBusy = 0b1011,
CallGate = 0b1100,
InterruptGate = 0b1110,
TrapGate = 0b1111,
}
pub const RING0: u8 = 0;
pub const RING1: u8 = 1;
pub const RING2: u8 = 2;
pub const RING3: u8 = 3;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodeDataSegmentKind: u8 {
const CodeSegment = 1 << 3;
const Executable = 1 << 2;
/// `ReadWrite` is set for code segments if they are readable, and for
/// data segments if they are writable.
const ReadWrite = 1 << 1;
const Accessed = 1 << 0;
}
}
impl SystemSegmentKind {
pub fn into_u8(self) -> u8 {
self as u8
}
pub fn from_u8_or_reserved(value: u8) -> Self {
match value {
0b0010 => SystemSegmentKind::Ldt,
0b1001 => SystemSegmentKind::TssAvailable,
0b1011 => SystemSegmentKind::TssBusy,
0b1100 => SystemSegmentKind::CallGate,
0b1110 => SystemSegmentKind::InterruptGate,
0b1111 => SystemSegmentKind::TrapGate,
_ => SystemSegmentKind::Reserved,
}
}
/// # Safety
/// the caller must ensure that the lower 4 bits of `value` are a valid `GdtKind` discriminant.
pub unsafe fn from_u8_unchecked(value: u8) -> Self {
unsafe { core::mem::transmute(value & 0b1111) }
}
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct RawGdtEntry {
pub limit_low: u16,
pub base_low: u16,
pub base_middle: u8,
pub access: u8,
pub flags: u8,
pub base_high: u8,
}
fn debug_commont_gdt_fields<'a, 'b>(
entry: &RawGdtEntry,
dbg: &'a mut core::fmt::DebugStruct<'a, 'b>,
) -> &'a mut core::fmt::DebugStruct<'a, 'b> {
dbg.field_with("limit", |f| {
if entry.granularity() {
f.write_fmt(format_args!("0x{:x} (4KiB blocks)", entry.limit()))
} else {
f.write_fmt(format_args!("0x{:x} (bytes)", entry.limit()))
}
})
.field("segment", &entry.segment());
if entry.segment() {
dbg.field("segment_kind", &entry.segment_kind());
} else {
dbg.field("system_segment_kind", &entry.system_segment_kind());
}
dbg.field("privilege_level", &entry.privilege_level())
.field("present", &entry.present())
.field("available", &entry.available())
.field("code_segment64", &entry.code_segment64())
.field("default_operation_size", &entry.default_operation_size())
.field_with("granularity", |f| {
if entry.granularity() {
f.write_str("4KiB")
} else {
f.write_str("bytes")
}
});
dbg
}
impl Debug for RawGdtEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut dbg = f.debug_struct("RawGdtEntry");
dbg.field_with("base", |f| {
f.write_fmt(format_args!("0x{:x}", self.base32()))
});
let dbg = debug_commont_gdt_fields(self, &mut dbg);
dbg.finish()
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DefaultOperationSize {
Bit16 = 0,
Bit32 = 1,
}
impl DefaultOperationSize {
pub fn into_u8(self) -> u8 {
self as u8
}
pub fn from_bool(value: bool) -> Self {
if value {
DefaultOperationSize::Bit32
} else {
DefaultOperationSize::Bit16
}
}
}
impl AsRef<RawGdtEntry> for TssEntry {
fn as_ref(&self) -> &RawGdtEntry {
&self.gdt
}
}
impl RawGdtEntry {
pub fn new(limit: u32, base: u32, access: u8, flags: u8) -> Self {
let limit_low = limit as u16;
let limit_high = (limit >> 16) as u8;
let base_low = base as u16;
let base_middle = (base >> 16) as u8;
let base_high = (base >> 24) as u8;
RawGdtEntry {
limit_low,
base_low,
base_middle,
access,
flags: (flags & 0xf0) | limit_high,
base_high,
}
}
pub fn empty() -> Self {
RawGdtEntry {
limit_low: 0,
base_low: 0,
base_middle: 0,
access: 0,
flags: 0,
base_high: 0,
}
}
pub fn with_segment_kind(mut self, kind: CodeDataSegmentKind) -> Self {
self.set_segment_kind(kind);
self
}
pub fn with_system_segment_kind(mut self, kind: SystemSegmentKind) -> Self {
self.set_system_segment_kind(kind);
self
}
pub fn with_segment(mut self, segment: bool) -> Self {
self.set_segment(segment);
self
}
pub fn with_privilege_level(mut self, level: u8) -> Self {
self.set_privilege_level(level);
self
}
pub fn with_present(mut self, present: bool) -> Self {
self.set_present(present);
self
}
pub fn system_segment_kind(&self) -> SystemSegmentKind {
SystemSegmentKind::from_u8_or_reserved(self.access.get_bits(0..4))
}
pub fn set_system_segment_kind(&mut self, kind: SystemSegmentKind) {
self.access.set_bits(0..4, kind.into_u8());
}
/// `segment_kind` is valid if `segment` is set, otherwise
/// `system_segment_kind` is valid. `segment_kind` is the type of segment,
/// and is only valid for code and data segments.
pub fn segment_kind(&self) -> CodeDataSegmentKind {
CodeDataSegmentKind::from_bits_truncate(self.access.get_bits(0..4))
}
pub fn set_segment_kind(&mut self, kind: CodeDataSegmentKind) {
self.access.set_bits(0..4, kind.bits());
}
/// `segment` is set if this is a code or data segment, and clear if it is a system segment.
pub fn segment(&self) -> bool {
self.access.get_bit(4)
}
pub fn set_segment(&mut self, system_segment: bool) {
self.access.set_bit(4, system_segment);
}
pub fn privilege_level(&self) -> u8 {
self.access.get_bits(5..7)
}
pub fn set_privilege_level(&mut self, level: u8) {
self.access.set_bits(5..7, level);
}
pub fn present(&self) -> bool {
self.access.get_bit(7)
}
pub fn set_present(&mut self, present: bool) {
self.access.set_bit(7, present);
}
pub fn with_available(mut self, available: bool) -> Self {
self.set_available(available);
self
}
pub fn with_code_segment64(mut self, code_segment64: bool) -> Self {
self.set_code_segment64(code_segment64);
self
}
pub fn with_default_operation_size(mut self, size: DefaultOperationSize) -> Self {
self.set_default_operation_size(size);
self
}
pub fn with_granularity(mut self, granularity: bool) -> Self {
self.set_granularity(granularity);
self
}
pub fn with_limit_high(mut self, limit_high: u8) -> Self {
self.set_limit_high(limit_high);
self
}
fn limit_high(&self) -> u8 {
self.flags.get_bits(0..4)
}
fn set_limit_high(&mut self, limit_high: u8) {
self.flags.set_bits(0..4, limit_high);
}
pub fn available(&self) -> bool {
self.flags.get_bit(4)
}
pub fn set_available(&mut self, available: bool) {
self.flags.set_bit(4, available);
}
pub fn code_segment64(&self) -> bool {
self.flags.get_bit(5)
}
pub fn set_code_segment64(&mut self, code_segment64: bool) {
self.flags.set_bit(5, code_segment64);
}
/// `default_operation_size` is the default size of operands for this segment. If `default_operation_size` is set, the default size is 32 bits, otherwise it is 16 bits.
/// On 64-bit mode, this should be 1.
pub fn default_operation_size(&self) -> DefaultOperationSize {
DefaultOperationSize::from_bool(self.flags.get_bit(6))
}
pub fn set_default_operation_size(&mut self, size: DefaultOperationSize) {
self.flags.set_bit(6, size.into_u8() != 0);
}
/// `granularity` is the unit of the limit field. If `granularity` is set,
/// the limit is in 4KiB blocks, otherwise it is in bytes.
pub fn granularity(&self) -> bool {
self.flags.get_bit(7)
}
pub fn set_granularity(&mut self, granularity: bool) {
self.flags.set_bit(7, granularity);
}
pub fn limit(&self) -> u32 {
let limit_low = self.limit_low as u32;
let limit_high = (self.limit_high() as u32) << 16;
limit_low | limit_high
}
pub fn base32(&self) -> u32 {
let base_low = self.base_low as u32;
let base_middle = (self.base_middle as u32) << 16;
let base_high = (self.base_high as u32) << 24;
base_low | base_middle | base_high
}
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct TssEntry {
gdt: RawGdtEntry,
pub base_ext: u32,
reserved: u32,
}
impl Debug for TssEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut dbg = f.debug_struct("TssEntry");
dbg.field_with("base", |f| {
f.write_fmt(format_args!("0x{:x}", self.base64()))
});
let dbg = debug_commont_gdt_fields(&self.gdt, &mut dbg);
dbg.finish()
}
}
impl TssEntry {
pub fn base64(&self) -> u64 {
let base_low = self.gdt.base32() as u64;
let base_ext = self.base_ext as u64;
(base_ext << 32) | base_low
}
pub fn new(limit: u32, base: u64) -> Self {
let base_low = base as u32;
let base_ext = (base >> 32) as u32;
TssEntry {
gdt: RawGdtEntry::new(limit, base_low, 0, 0)
.with_present(true)
.with_privilege_level(RING0)
.with_system_segment_kind(SystemSegmentKind::TssAvailable),
base_ext,
reserved: 0,
}
}
}
impl Deref for TssEntry {
type Target = RawGdtEntry;
fn deref(&self) -> &Self::Target {
&self.gdt
}
}
impl DerefMut for TssEntry {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.gdt
}
}
#[repr(C, packed(4))]
pub struct TaskStateSegment {
_reserved1: [u8; 4],
pub privilege_stack_table: [u64; 3],
_reserved2: [u8; 8],
pub interrupt_stack_table: [u64; 7],
_reserved3: [u8; 10],
pub iomap_base: u16,
}
impl TaskStateSegment {
pub const fn new() -> Self {
Self {
privilege_stack_table: [0; 3],
interrupt_stack_table: [0; 7],
iomap_base: size_of::<TaskStateSegment>() as u16,
_reserved1: [0; 4],
_reserved2: [0; 8],
_reserved3: [0; 10],
}
}
}
const impl Default for TaskStateSegment {
fn default() -> Self {
Self::new()
}
}
pub const DF_STACK: u8 = 0;
pub const NMI_STACK: u8 = 1;
pub const MC_STACK: u8 = 2;
pub static TSS: LazyLock<TaskStateSegment> = LazyLock::new(|| {
let mut tss = TaskStateSegment::new();
const STACK_SIZE: usize = super::PAGE_SIZE * 5;
static mut STACKS: [[u8; STACK_SIZE]; 3] = [[0; STACK_SIZE]; 3];
let stack_top = |n: usize| unsafe { STACKS[n].as_ptr().add(STACK_SIZE).to_address() };
tss.interrupt_stack_table[DF_STACK as usize] = stack_top(DF_STACK as usize);
tss.interrupt_stack_table[NMI_STACK as usize] = stack_top(NMI_STACK as usize);
tss.interrupt_stack_table[MC_STACK as usize] = stack_top(MC_STACK as usize);
tss
});

View file

@ -1,546 +0,0 @@
use core::{arch::naked_asm, fmt::Debug, marker::PhantomData, ops::Deref, ptr::NonNull};
use bit_field::BitField;
use crate::{
serial_println,
x86_64::{RFlags, Registers, halt_loop},
};
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Entry {
pub offset_low: u16,
pub selector: u16,
pub options: EntryOptions,
pub offset_middle: u16,
pub offset_high: u32,
pub reserved: u32,
}
impl core::fmt::Debug for Entry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Entry")
.field("handler", &self.handler_address())
.field("selector", &self.selector)
.field("options", &self.options)
.finish()
}
}
pub struct ExceptionVector;
impl ExceptionVector {
pub const DIVIDE_BY_ZERO: u8 = 0;
pub const DEBUG: u8 = 1;
pub const NON_MASKABLE_INTERRUPT: u8 = 2;
pub const BREAKPOINT: u8 = 3;
pub const OVERFLOW: u8 = 4;
pub const BOUND_RANGE_EXCEEDED: u8 = 5;
pub const INVALID_OPCODE: u8 = 6;
pub const DEVICE_NOT_AVAILABLE: u8 = 7;
pub const DOUBLE_FAULT: u8 = 8;
pub const COPROCESSOR_SEGMENT_OVERRUN: u8 = 9;
pub const INVALID_TSS: u8 = 10;
pub const SEGMENT_NOT_PRESENT: u8 = 11;
pub const STACK_SEGMENT_FAULT: u8 = 12;
pub const GENERAL_PROTECTION_FAULT: u8 = 13;
pub const PAGE_FAULT: u8 = 14;
pub const X87_FLOATING_POINT_EXCEPTION: u8 = 16;
pub const ALIGNMENT_CHECK: u8 = 17;
pub const MACHINE_CHECK: u8 = 18;
pub const SIMD_FLOATING_POINT_EXCEPTION: u8 = 19;
pub const VIRTUALIZATION_EXCEPTION: u8 = 20;
pub const CONTROL_PROTECTION_EXCEPTION: u8 = 21;
pub const HYPERVISOR_EXCEPTION: u8 = 28;
pub const VMM_COMMUNICATION_EXCEPTION: u8 = 29;
pub const SECURITY_EXCEPTION: u8 = 30;
}
impl Entry {
pub unsafe fn new<T: ToAddress>(handler: T, selector: u16, options: EntryOptions) -> Self {
let handler_addr = handler.to_address();
Self {
offset_low: handler_addr as u16,
selector,
options,
offset_middle: (handler_addr >> 16) as u16,
offset_high: (handler_addr >> 32) as u32,
reserved: 0,
}
}
pub unsafe fn new_default_interrupt<const IDX: u8>() -> Self {
let cs = unsafe { super::instructions::get_cs() };
let mut options = EntryOptions::empty_interrupt_gate();
options.set_present(true);
let handler = match IDX {
8 | 10 | 11 | 12 | 13 | 14 | 17 | 21 | 29 | 30 => {
interrupt_trampoline_with_err::<IDX> as *const () as u64
}
_ => interrupt_trampoline_no_err::<IDX> as *const () as u64,
};
match IDX {
ExceptionVector::DOUBLE_FAULT => {
options.set_interrupt_stack_table_index(super::gdt::DF_STACK);
}
ExceptionVector::NON_MASKABLE_INTERRUPT => {
options.set_interrupt_stack_table_index(super::gdt::NMI_STACK);
}
ExceptionVector::MACHINE_CHECK => {
options.set_interrupt_stack_table_index(super::gdt::MC_STACK);
}
_ => {}
}
unsafe { Self::new(handler, cs, options) }
}
pub const fn missing_interrupt() -> Self {
Self {
offset_low: 0,
selector: 0,
options: EntryOptions::empty_interrupt_gate(),
offset_middle: 0,
offset_high: 0,
reserved: 0,
}
}
pub fn handler_address(&self) -> u64 {
let low = self.offset_low as u64;
let middle = self.offset_middle as u64;
let high = self.offset_high as u64;
(high << 32) | (middle << 16) | low
}
pub fn set_handler<T>(&mut self, handler: T)
where
T: ToAddress,
{
let handler_addr = handler.to_address();
self.offset_low = handler_addr as u16;
self.offset_middle = (handler_addr >> 16) as u16;
self.offset_high = (handler_addr >> 32) as u32;
self.options = EntryOptions::empty_interrupt_gate();
unsafe {
self.selector = super::instructions::get_cs();
}
self.options.set_present(true);
}
}
pub trait ToAddress {
fn to_address(&self) -> u64;
}
impl ToAddress for u64 {
fn to_address(&self) -> u64 {
*self
}
}
impl<T> ToAddress for *const T {
fn to_address(&self) -> u64 {
*self as u64
}
}
impl<T> ToAddress for *mut T {
fn to_address(&self) -> u64 {
*self as u64
}
}
impl<T> ToAddress for NonNull<T> {
fn to_address(&self) -> u64 {
self.as_ptr() as u64
}
}
impl<T> ToAddress for &T {
fn to_address(&self) -> u64 {
*self as *const T as u64
}
}
impl<T> ToAddress for &mut T {
fn to_address(&self) -> u64 {
*self as *const T as u64
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct EntryOptions(u16);
impl Debug for EntryOptions {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EntryOptions")
.field("present", &self.present())
.field("privilege_level", &self.privilege_level())
.field(
"interrupt_stack_table_index",
&self.interrupt_stack_table_index(),
)
.field("kind", &self.kind())
.finish()
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum EntryType {
#[default]
InterruptGate = 0b1110,
TrapGate = 0b1111,
}
impl EntryType {
pub fn into_u8(self) -> u8 {
self as u8
}
/// # Safety
/// The caller must ensure that `value` is either `0b1110` or `0b1111`, or
/// the result of calling `into_u8` on an `EntryType`.
pub unsafe fn from_u8_unchecked(value: u8) -> Self {
unsafe { core::mem::transmute(value) }
}
}
impl EntryOptions {
pub const fn empty_interrupt_gate() -> Self {
Self(0b1110_0000_0000)
}
pub const fn empty_trap_gate() -> Self {
Self(0b1111_0000_0000)
}
pub fn with_present(mut self, present: bool) -> Self {
self.set_present(present);
self
}
pub fn with_privilege_level(mut self, level: u8) -> Self {
self.set_privilege_level(level);
self
}
pub fn with_interrupt_stack_table_index(mut self, index: u8) -> Self {
self.set_interrupt_stack_table_index(index);
self
}
pub fn with_kind(mut self, kind: EntryType) -> Self {
self.set_kind(kind);
self
}
pub fn present(&self) -> bool {
self.0.get_bit(15)
}
pub fn set_present(&mut self, present: bool) {
self.0.set_bit(15, present);
}
pub fn privilege_level(&self) -> u8 {
self.0.get_bits(13..15) as u8
}
pub fn set_privilege_level(&mut self, level: u8) {
self.0.set_bits(13..15, level as u16);
}
pub fn interrupt_stack_table_index(&self) -> Option<u8> {
let index = self.0.get_bits(0..3) as u8;
if index == 0 { None } else { Some(index - 1) }
}
pub fn set_interrupt_stack_table_index(&mut self, index: u8) {
self.0.set_bits(0..3, index as u16 + 1);
}
pub fn kind(&self) -> EntryType {
unsafe { EntryType::from_u8_unchecked(self.0.get_bits(8..12) as u8) }
}
pub fn set_kind(&mut self, kind: EntryType) {
self.0.set_bits(8..12, kind.into_u8() as u16);
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct InterruptStackFrameInner {
pub instruction_pointer: u64,
pub code_segment: u16,
_reserved: [u16; 3],
pub flags: RFlags,
pub stack_pointer: u64,
pub stack_segment: u16,
_reserved2: [u16; 3],
}
#[repr(transparent)]
pub struct InterruptStackFrame(InterruptStackFrameInner);
impl Debug for InterruptStackFrame {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("InterruptStackFrame")
.field(
"instruction_pointer",
&format_args!("{:#x}", self.instruction_pointer),
)
.field("code_segment", &format_args!("{:#x}", self.code_segment))
.field("flags", &self.flags)
.field("stack_pointer", &format_args!("{:#x}", self.stack_pointer))
.field("stack_segment", &format_args!("{:#x}", self.stack_segment))
.finish()
}
}
impl Deref for InterruptStackFrame {
type Target = InterruptStackFrameInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct PageFaultErrorCode: u64 {
const PRESENT = 1 << 0;
const WRITE = 1 << 1;
const USER = 1 << 2;
const RESERVED = 1 << 3;
const INSTRUCTION_FETCH = 1 << 4;
const PROTECTION_KEY = 1 << 5;
const SGX = 1 << 15;
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C, packed(2))]
pub struct IdtRegister<'idt> {
pub limit: u16,
pub base: u64,
_pd: PhantomData<&'idt ()>,
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct InterruptDescriptorTable {
pub divide_by_zero: Entry,
pub debug: Entry,
pub non_maskable_interrupt: Entry,
pub breakpoint: Entry,
pub overflow: Entry,
pub bound_range_exceeded: Entry,
pub invalid_opcode: Entry,
pub device_not_available: Entry,
pub double_fault: Entry,
pub coprocessor_segment_overrun: Entry,
pub invalid_tss: Entry,
pub segment_not_present: Entry,
pub stack_segment_fault: Entry,
pub general_protection_fault: Entry,
pub page_fault: Entry,
_reserved1: Entry,
pub x87_floating_point: Entry,
pub alignment_check: Entry,
pub machine_check: Entry,
pub simd_floating_point: Entry,
pub virtualization: Entry,
pub cp_protection: Entry,
_reserved2: [Entry; 6],
pub hypervisor: Entry,
pub vmm_communication_exception: Entry,
pub security_exception: Entry,
_reserved3: Entry,
pub interrupts: [Entry; 256 - 32],
}
const impl Default for InterruptDescriptorTable {
fn default() -> Self {
Self::new_empty()
}
}
impl InterruptDescriptorTable {
pub const fn new_empty() -> Self {
Self {
divide_by_zero: Entry::missing_interrupt(),
debug: Entry::missing_interrupt(),
non_maskable_interrupt: Entry::missing_interrupt(),
breakpoint: Entry::missing_interrupt(),
overflow: Entry::missing_interrupt(),
bound_range_exceeded: Entry::missing_interrupt(),
invalid_opcode: Entry::missing_interrupt(),
device_not_available: Entry::missing_interrupt(),
double_fault: Entry::missing_interrupt(),
coprocessor_segment_overrun: Entry::missing_interrupt(),
invalid_tss: Entry::missing_interrupt(),
segment_not_present: Entry::missing_interrupt(),
stack_segment_fault: Entry::missing_interrupt(),
general_protection_fault: Entry::missing_interrupt(),
page_fault: Entry::missing_interrupt(),
_reserved1: Entry::missing_interrupt(),
x87_floating_point: Entry::missing_interrupt(),
alignment_check: Entry::missing_interrupt(),
machine_check: Entry::missing_interrupt(),
simd_floating_point: Entry::missing_interrupt(),
virtualization: Entry::missing_interrupt(),
cp_protection: Entry::missing_interrupt(),
_reserved2: [Entry::missing_interrupt(); 6],
hypervisor: Entry::missing_interrupt(),
vmm_communication_exception: Entry::missing_interrupt(),
security_exception: Entry::missing_interrupt(),
_reserved3: Entry::missing_interrupt(),
interrupts: [Entry::missing_interrupt(); 256 - 32],
}
}
pub fn new_default() -> Self {
let mut idt = Self::new_empty();
let slice = unsafe { idt.as_mut_type_erased() };
seq_macro::seq!(
N in 0..=255 {
slice[N] = unsafe { Entry::new_default_interrupt::<N>() };
}
);
idt
}
pub unsafe fn register(&self) -> IdtRegister<'_> {
IdtRegister {
limit: (core::mem::size_of::<Self>() - 1) as u16,
base: self as *const _ as u64,
_pd: PhantomData,
}
}
pub fn load(&'static self) {
unsafe { Self::load_unsafe(self) };
}
pub unsafe fn load_unsafe(&self) {
unsafe {
super::instructions::lidt(&self.register());
}
}
pub unsafe fn as_type_erased(&self) -> &[Entry; 256] {
unsafe { core::mem::transmute::<&Self, &[Entry; 256]>(self) }
}
pub unsafe fn as_mut_type_erased(&mut self) -> &mut [Entry; 256] {
unsafe { core::mem::transmute::<&mut Self, &mut [Entry; 256]>(self) }
}
}
#[unsafe(naked)]
extern "C" fn interrupt_trampoline_with_err<const IDX: u8>() {
naked_asm!(
"push {idx}",
"jmp {dispatcher}",
idx = const { IDX },
dispatcher = sym interrupt_dispatcher,
)
}
#[unsafe(naked)]
extern "C" fn interrupt_trampoline_no_err<const IDX: u8>() {
naked_asm!(
"push 0", // push a dummy error code of 0
"push {idx}",
"jmp {dispatcher}",
idx = const { IDX },
dispatcher = sym interrupt_dispatcher,
)
}
#[unsafe(naked)]
extern "C" fn interrupt_dispatcher() {
naked_asm!(
"push rax",
"push rcx",
"push rdx",
"push rbx",
"push rbp",
"push rsi",
"push rdi",
"push r8",
"push r9",
"push r10",
"push r11",
"push r12",
"push r13",
"push r14",
"push r15",
"lea rdi, [rsp + {registers_size} + 16]", // interrupt frame
"mov rdx, [rsp + {registers_size} + 8]", // error code
"mov rsi, [rsp + {registers_size}]", // interrupt index
"mov rcx, rsp", // pass the current stack pointer as the registers pointer
"call {global_handler}",
"pop r15",
"pop r14",
"pop r13",
"pop r12",
"pop r11",
"pop r10",
"pop r9",
"pop r8",
"pop rdi",
"pop rsi",
"pop rbp",
"pop rbx",
"pop rdx",
"pop rcx",
"pop rax",
"add rsp, 16", // pop the interrupt index and error code
"iretq",
registers_size = const { core::mem::size_of::<Registers>() },
global_handler = sym global_interrupt_handler,
)
}
extern "C" fn global_interrupt_handler(
frame: &InterruptStackFrame,
index: u8,
error_code: u64,
registers: &Registers,
) {
serial_println!(
"Interrupt {} occurred! Error code: {:#x}, Frame: {:#?}, Registers: {:#?}",
index,
error_code,
frame,
registers
);
match index {
ExceptionVector::BREAKPOINT => {
serial_println!("Breakpoint interrupt handled successfully.");
}
ExceptionVector::PAGE_FAULT => {
serial_println!("Page fault occurred! Error code: {:#x}", error_code);
halt_loop()
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::super::instructions;
use super::*;
#[test_case]
fn test_interrupt_handler_trait() {
use crate::sync::LazyLock;
static IDT: LazyLock<InterruptDescriptorTable> =
crate::sync::LazyLock::new(InterruptDescriptorTable::new_default);
unsafe { IDT.load_unsafe() };
instructions::int3(); // Trigger a breakpoint interrupt (interrupt 3)
serial_println!("Breakpoint interrupt handled successfully.");
}
}

View file

@ -1,188 +0,0 @@
#[inline]
pub fn hlt() {
unsafe {
core::arch::asm!("hlt", options(nomem, nostack, preserves_flags));
}
}
#[inline]
pub unsafe fn write_u8(port: u16, value: u8) {
unsafe {
core::arch::asm!(
"out dx, al",
in("dx") port,
in("al") value,
options(nomem, nostack, preserves_flags)
);
}
}
#[inline]
pub unsafe fn write_u16(port: u16, value: u16) {
unsafe {
core::arch::asm!(
"out dx, ax",
in("dx") port,
in("ax") value,
options(nomem, nostack, preserves_flags)
);
}
}
#[inline]
pub unsafe fn write_u32(port: u16, value: u32) {
unsafe {
core::arch::asm!(
"out dx, eax",
in("dx") port,
in("eax") value,
options(nomem, nostack, preserves_flags)
);
}
}
#[inline]
pub unsafe fn read_u8(port: u16) -> u8 {
let value: u8;
unsafe {
core::arch::asm!(
"in al, dx",
in("dx") port,
out("al") value,
options(nomem, nostack, preserves_flags)
);
}
value
}
#[inline]
pub unsafe fn read_u16(port: u16) -> u16 {
let value: u16;
unsafe {
core::arch::asm!(
"in ax, dx",
in("dx") port,
out("ax") value,
options(nomem, nostack, preserves_flags)
);
}
value
}
#[inline]
pub unsafe fn read_u32(port: u16) -> u32 {
let value: u32;
unsafe {
core::arch::asm!(
"in eax, dx",
in("dx") port,
out("eax") value,
options(nomem, nostack, preserves_flags)
);
}
value
}
#[inline]
pub unsafe fn lidt(idt: &super::idt::IdtRegister) {
unsafe {
core::arch::asm!(
"lidt [{}]",
in(reg) idt,
options(readonly, nostack, preserves_flags)
);
}
}
#[inline]
pub fn int3() {
unsafe {
core::arch::asm!("int3", options(nomem, nostack, preserves_flags));
}
}
macro_rules! read_segment {
($segment:literal) => {
{
let value: u16;
unsafe {
core::arch::asm!(
concat!("mov {0:x}, ", $segment),
out(reg) value,
options(nomem, nostack, preserves_flags)
);
}
value
}
}
}
#[inline]
pub unsafe fn get_cs() -> u16 {
read_segment!("cs")
}
pub mod msr {
use core::arch::asm;
pub const MSR_STAR: u32 = 0xC000_0081;
pub const MSR_LSTAR: u32 = 0xC000_0082;
pub const MSR_CSTAR: u32 = 0xC000_0083;
pub const MSR_SFMASK: u32 = 0xC000_0084;
pub const MSR_FS_BASE: u32 = 0xC000_0100;
pub const MSR_GS_BASE: u32 = 0xC000_0101;
pub const MSR_KERNEL_GS_BASE: u32 = 0xC000_0102;
pub const MSR_EFER: u32 = 0xC000_0080;
/// Reads the value of the specified Model-Specific Register (MSR).
/// # Safety
/// This operation is inherently unsafe.
#[inline]
pub unsafe fn read_msr(msr: u32) -> u64 {
unsafe {
let eax: u32;
let edx: u32;
asm!(
"rdmsr",
in("ecx") msr,
out("eax") eax,
out("edx") edx,
options(nomem, nostack, preserves_flags)
);
((edx as u64) << 32) | (eax as u64)
}
}
/// Writes the value to the specified Model-Specific Register (MSR).
/// # Safety
/// This operation is inherently unsafe.
#[inline]
pub unsafe fn write_msr(msr: u32, value: u64) {
unsafe {
let eax = value as u32;
let edx = (value >> 32) as u32;
asm!(
"wrmsr",
in("ecx") msr,
in("eax") eax,
in("edx") edx,
options(nomem, nostack, preserves_flags)
);
}
}
}
#[inline]
pub fn rdtsc() -> u64 {
let low: u32;
let high: u32;
unsafe {
core::arch::asm!(
"rdtsc",
out("eax") low,
out("edx") high,
options(nomem, nostack, preserves_flags)
);
}
((high as u64) << 32) | (low as u64)
}

View file

@ -1,241 +0,0 @@
use core::{
borrow::Borrow,
fmt::Debug,
hint::unlikely,
ops::{Deref, Index},
};
use bit_field::BitField;
use crate::{
memory::{PhyAddr, VirtAddr, VirtAddrTranslationExt},
serial_println,
x86_64::{VirtAddrExt, registers::Cr4},
};
#[repr(C, align(4096))]
pub struct PageTable {
entries: [PageTableEntry; 512],
}
impl PageTable {
pub fn get(&self, index: u16) -> Option<PageTableEntry> {
if index < 512 {
let entry = self.entries[index as usize];
if entry.present() { Some(entry) } else { None }
} else {
None
}
}
pub fn get_unchecked(&self, index: u16) -> PageTableEntry {
assert!(index < 512, "Page table index out of bounds");
self.entries[index as usize]
}
}
impl Index<u16> for PageTable {
type Output = PageTableEntry;
fn index(&self, index: u16) -> &Self::Output {
assert!(index < 512, "Page table index out of bounds");
&self.entries[index as usize]
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PageTableEntry(PageTableEntryFlags);
impl Debug for PageTableEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PageTableEntry")
.field(
"flags",
&PageTableEntryFlags::from_bits_truncate(self.as_raw()),
)
.field("phy", &self.phy())
.field("pk", &self.pk())
.field("pat_index", &self.pat_index())
.field("free_bits", &format_args!("{:#b}", self.free_bits()))
.finish()
}
}
impl AsRef<PageTableEntryFlags> for PageTableEntry {
fn as_ref(&self) -> &PageTableEntryFlags {
&self.0
}
}
impl Borrow<PageTableEntryFlags> for PageTableEntry {
fn borrow(&self) -> &PageTableEntryFlags {
&self.0
}
}
impl Deref for PageTableEntry {
type Target = PageTableEntryFlags;
fn deref(&self) -> &Self::Target {
&self.0
}
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PageTableEntryFlags: u64 {
const PRESENT = 1 << 0;
const WRITABLE = 1 << 1;
const USER_ACCESSIBLE = 1 << 2;
const WRITE_THROUGH = 1 << 3; // PWT
const NO_CACHE = 1 << 4; // PCD
const ACCESSED = 1 << 5;
const DIRTY = 1 << 6;
const HUGE_PAGE = 1 << 7;
const GLOBAL = 1 << 8;
const NO_EXECUTE = 1 << 63;
const PAT_4K = 1 << 7;
const PAT_HUGE = 1 << 12;
}
}
impl PageTableEntry {
pub fn from_raw(bits: u64) -> Self {
Self(PageTableEntryFlags::from_bits_retain(bits))
}
pub fn as_raw(&self) -> u64 {
self.0.bits()
}
pub fn as_mut_raw(&mut self) -> &mut u64 {
self.0.0.bits_mut()
}
pub fn present(&self) -> bool {
self.contains(PageTableEntryFlags::PRESENT)
}
pub fn phy(&self) -> PhyAddr {
PhyAddr(self.as_raw().get_bits(12..52) << 12)
}
pub fn try_as_page_table(&self) -> Option<&PageTable> {
if !self.contains(PageTableEntryFlags::PRESENT) {
return None;
}
if self.contains(PageTableEntryFlags::HUGE_PAGE) {
return None;
}
unsafe {
Some(
self.phy()
.into_hhdm_virt()
.as_ptr::<PageTable>()
.as_ref()
.unwrap_unchecked(),
)
}
}
pub unsafe fn as_page_table(&self) -> &PageTable {
// entry must be present
assert!(self.contains(PageTableEntryFlags::PRESENT));
// if the entry is a huge page, it does not point to a deeper page table.
assert!(!self.contains(PageTableEntryFlags::HUGE_PAGE));
unsafe {
self.phy()
.into_hhdm_virt()
.as_ptr::<PageTable>()
.as_ref()
.unwrap_unchecked()
}
}
pub fn pk(&self) -> u8 {
self.as_raw().get_bits(59..63) as u8
}
pub fn pat_index(&self) -> u8 {
let pat = if self.contains(PageTableEntryFlags::HUGE_PAGE) {
self.as_raw().get_bit(12) as u8
} else {
self.as_raw().get_bit(7) as u8
};
self.as_raw().get_bits(3..=4) as u8 | (pat << 2)
}
/// Returns the free bits in the page table entry, which are bits 9-11 and
/// 52-58, combined into a single 10-bit value.
pub fn free_bits(&self) -> u16 {
let low = self.bits().get_bits(9..12) as u16;
let high = self.bits().get_bits(52..59) as u16;
low | (high << 3)
}
pub fn set_free_bits(&mut self, value: u16) {
let low = (value & 0b111) as u64;
let high = (value >> 3) as u64;
self.as_mut_raw().set_bits(9..12, low);
self.as_mut_raw().set_bits(52..59, high);
}
}
impl VirtAddrTranslationExt for VirtAddr {
fn into_phy_addr(self) -> Option<PhyAddr> {
get_physical_addr(self)
}
}
pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
let cr3 = crate::x86_64::registers::Cr3::read();
let cr4 = crate::x86_64::registers::Cr4::read();
serial_println!("cr3: {:?}", cr3);
serial_println!("cr4: {:?}", cr4);
let frame = cr3.phy();
let page_table = unsafe {
frame
.into_hhdm_virt()
.as_ptr::<PageTable>()
.as_ref()
.unwrap_unchecked()
};
let pml4_entry = if unlikely(cr4.contains(Cr4::LA57)) {
let l5_entry = page_table[virt.page_table_index::<{ VirtAddr::PML5 }>()];
l5_entry
.try_as_page_table()?
.get_unchecked(virt.page_table_index::<{ VirtAddr::PML4 }>())
} else {
page_table[virt.page_table_index::<{ VirtAddr::PML4 }>()]
};
serial_println!("pml4_entry: {:?}", pml4_entry);
let pdpt_entry = pml4_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PDPT }>()];
serial_println!("pdpt_entry: {:?}", pdpt_entry);
if pdpt_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
const PHY_MASK_1G: u64 = !((1 << 30) - 1);
let phys_addr = (pdpt_entry.phy().0 & PHY_MASK_1G) + virt.offset_1g() as u64;
return Some(PhyAddr(phys_addr));
}
let pd_entry = pdpt_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PD }>()];
serial_println!("pd_entry: {:?}", pd_entry);
if pd_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
const PHY_MASK_2M: u64 = !((1 << 21) - 1);
let phys_addr = (pd_entry.phy().0 & PHY_MASK_2M) + virt.offset_2m() as u64;
return Some(PhyAddr(phys_addr));
}
let pt_entry = pd_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PT }>()];
serial_println!("pt_entry: {:?}", pt_entry);
if !pt_entry.contains(PageTableEntryFlags::PRESENT) {
return None;
}
let phy_addr = pt_entry.phy().into_hhdm_virt().0 + virt.offset_4k() as u64;
Some(PhyAddr(phy_addr))
}

View file

@ -1,227 +0,0 @@
use core::{arch::asm, fmt::Debug, ops::Index};
use bit_field::BitField;
use bitflags::bitflags;
use crate::memory::PhyAddr;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cr0Flags: u64 {
const PROTECTED_MODE_ENABLE = 1 << 0;
const MONITOR_COPROCESSOR = 1 << 1;
const EMULATE_COPROCESSOR = 1 << 2;
const TASK_SWITCHED = 1 << 3;
const EXTENSION_TYPE = 1 << 4;
const NUMERIC_ERROR = 1 << 5;
const WRITE_PROTECT = 1 << 16;
const ALIGNMENT_MASK = 1 << 18;
const NOT_WRITE_THROUGH = 1 << 29;
const CACHE_DISABLE = 1 << 30;
const PAGING = 1 << 31;
}
}
impl Cr0Flags {
pub fn read() -> Self {
let value: u64;
unsafe {
asm!("mov {}, cr0", out(reg) value, options(nomem, nostack, preserves_flags));
}
Self::from_bits_truncate(value)
}
pub unsafe fn write(&self) {
unsafe {
asm!("mov cr0, {}", in(reg) self.bits(), options(nomem, nostack, preserves_flags));
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cr4: u64 {
const VIRTUAL_8086_MODE_EXTENSIONS = 1 << 0;
const PROTECTED_MODE_VIRTUAL_INTERRUPTS = 1 << 1;
const TIME_STAMP_DISABLE = 1 << 2;
const DEBUGGING_EXTENSIONS = 1 << 3;
const PAGE_SIZE_EXTENSIONS = 1 << 4;
const PHYSICAL_ADDRESS_EXTENSION = 1 << 5;
const MACHINE_CHECK = 1 << 6;
const PAGE_GLOBAL = 1 << 7;
const PERFORMANCE_MONITOR_COUNTER = 1 << 8;
const OSFXSR_SUPPORT = 1 << 9;
const OSXMMEXCPT_SUPPORT = 1 << 10;
const USER_MODE_INSTRUCTION_PREVENTION = 1 << 11;
const LA57 = 1 << 12;
const VMX = 1 << 13;
const SMX = 1 << 14;
const FSGSBASE = 1 << 16;
const PCID = 1 << 17;
const OSXSAVE = 1 << 18;
const SMEP = 1 << 20;
const SMAP = 1 << 21;
}
}
impl Cr4 {
pub fn read() -> Self {
let value: u64;
unsafe {
asm!("mov {}, cr4", out(reg) value, options(nomem, nostack, preserves_flags));
}
Self::from_bits_truncate(value)
}
pub unsafe fn write(&self) {
unsafe {
asm!("mov cr4, {}", in(reg) self.bits(), options(nomem, nostack, preserves_flags));
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IA32EferFlags: u64 {
const SYSTEM_CALL_EXTENSIONS = 1 << 0;
const LONG_MODE_ENABLE = 1 << 8;
const LONG_MODE_ACTIVE = 1 << 10;
const NO_EXECUTE_ENABLE = 1 << 11;
}
}
impl IA32EferFlags {
pub fn read() -> Self {
use super::instructions::msr::{MSR_EFER, read_msr};
let bits = unsafe { read_msr(MSR_EFER) };
Self::from_bits_truncate(bits)
}
pub unsafe fn write(&self) {
use super::instructions::msr::{MSR_EFER, write_msr};
unsafe { write_msr(MSR_EFER, self.bits()) }
}
}
pub struct IA32Pat(u64);
const impl Default for IA32Pat {
fn default() -> Self {
Self::from_entries([
IA32PatEntry::WRITE_BACK,
IA32PatEntry::WRITE_THROUGH,
IA32PatEntry::UNCACHEABLE,
IA32PatEntry::UNCACHED,
IA32PatEntry::WRITE_BACK,
IA32PatEntry::WRITE_THROUGH,
IA32PatEntry::UNCACHEABLE,
IA32PatEntry::UNCACHED,
])
}
}
impl IA32Pat {
pub const fn from_entries(entries: [IA32PatEntry; 8]) -> Self {
let [
IA32PatEntry(e0),
IA32PatEntry(e1),
IA32PatEntry(e2),
IA32PatEntry(e3),
IA32PatEntry(e4),
IA32PatEntry(e5),
IA32PatEntry(e6),
IA32PatEntry(e7),
] = entries;
let val = e0 as u64
| ((e1 as u64) << 8)
| ((e2 as u64) << 16)
| ((e3 as u64) << 24)
| ((e4 as u64) << 32)
| ((e5 as u64) << 40)
| ((e6 as u64) << 48)
| ((e7 as u64) << 56);
Self(val)
}
pub fn get(&self, index: u8) -> IA32PatEntry {
assert!(index < 8, "Index out of bounds for IA32Pat");
let entry = (self.0 >> (index * 8)) & 0xF;
IA32PatEntry(entry as u8)
}
pub fn set(&mut self, index: u8, entry: IA32PatEntry) {
assert!(index < 8, "Index out of bounds for IA32Pat");
let mask = !(0xF << (index * 8));
self.0 = (self.0 & mask) | ((entry.0 as u64) << (index * 8));
}
}
pub struct IA32PatEntry(u8);
impl IA32PatEntry {
pub const UNCACHEABLE: Self = Self(0);
pub const WRITE_COMBINING: Self = Self(1);
pub const WRITE_THROUGH: Self = Self(4);
pub const WRITE_PROTECTED: Self = Self(5);
pub const WRITE_BACK: Self = Self(6);
pub const UNCACHED: Self = Self(7);
}
pub struct Cr3(u64);
impl Debug for Cr3 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Cr3")
.field("phy", &self.phy())
.field("flags", &self.flags())
.field("free_bits", &format_args!("{:#x}", self.free_bits()))
.finish()
}
}
bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cr3Flags: u64 {
const PAGE_LEVEL_WRITE_THROUGH = 1 << 3;
const PAGE_LEVEL_CACHE_DISABLE = 1 << 4;
}
}
impl Cr3 {
pub fn read() -> Self {
let value: u64;
unsafe {
asm!("mov {}, cr3", out(reg) value, options(nomem, nostack, preserves_flags));
}
Self(value)
}
pub unsafe fn write(&self) {
unsafe {
asm!("mov cr3, {}", in(reg) self.0, options(nomem, nostack, preserves_flags));
}
}
pub fn flags(&self) -> Cr3Flags {
Cr3Flags::from_bits_truncate(self.0)
}
pub fn set_flags(&mut self, flags: Cr3Flags) {
const MASK: u64 = Cr3Flags::all().bits();
self.0 = (self.0 & !MASK) | flags.bits();
}
pub fn phy(&self) -> PhyAddr {
PhyAddr(self.0.get_bits(12..52) << 12)
}
pub fn free_bits(&self) -> u16 {
let low = (self.0 & 0x7) as u16;
let high = self.0.get_bits(5..12) as u16;
low | (high << 3)
}
pub fn set_free_bits(&mut self, value: u16) {
let low = (value & 0x7) as u64;
let high = (value >> 3) as u64;
self.0.set_bits(0..3, low);
self.0.set_bits(5..12, high);
}
}

View file

@ -1,13 +0,0 @@
#![no_main]
#![no_std]
#[unsafe(export_name = "_start")]
pub extern "C" fn main() -> ! {
kernel::serial_println!("Hello, world!");
kernel::testing::exit_qemu(kernel::testing::QemuExitCode::Success)
}
#[panic_handler]
fn panic_thunk(info: &core::panic::PanicInfo) -> ! {
kernel::testing::test_panic_handler(info)
}

View file

@ -1,67 +0,0 @@
#![no_main]
#![no_std]
#![feature(abi_x86_interrupt)]
use core::{cell::UnsafeCell, mem::offset_of};
use kernel::{
sync::LazyLock,
x86_64::{
gdt::{DF_STACK, GlobalDescriptorTable, RING0},
idt::{self, Entry, InterruptDescriptorTable},
},
};
pub static GDT: LazyLock<GlobalDescriptorTable> = LazyLock::new(GlobalDescriptorTable::new);
#[unsafe(export_name = "_start")]
pub extern "C" fn main() -> ! {
kernel::serial_println!("Hello, world!");
GDT.load();
kernel::serial_println!("[ok] GDT loaded");
extern "x86-interrupt" fn double_fault_handler(
_stack_frame: &mut idt::InterruptStackFrame,
_error_code: u64,
) -> ! {
kernel::serial_println!("[ok] Double fault handler called");
kernel::testing::exit_qemu(kernel::testing::QemuExitCode::Success)
}
static IDT: LazyLock<InterruptDescriptorTable> = LazyLock::new(|| {
let mut idt = InterruptDescriptorTable::new_default();
idt.double_fault = unsafe {
Entry::new(
double_fault_handler as *const (),
offset_of!(GlobalDescriptorTable, kernel_code) as u16,
idt::EntryOptions::empty_interrupt_gate()
.with_present(true)
.with_privilege_level(RING0)
.with_interrupt_stack_table_index(DF_STACK),
)
};
idt
});
IDT.load();
stack_overflow();
panic!("Triggering a stack overflow to test double fault handling");
}
#[allow(unconditional_recursion)]
fn stack_overflow() {
stack_overflow(); // for each recursion, the return address is pushed
unsafe {
let cell = UnsafeCell::new(0);
cell.get().write_volatile(0);
}; // prevent tail recursion optimizations
}
#[panic_handler]
fn panic_thunk(info: &core::panic::PanicInfo) -> ! {
kernel::testing::test_panic_handler(info)
}