Files
yggdrasil/kernel/arch/x86/src/gdt.rs
T

282 lines
7.3 KiB
Rust

#[allow(dead_code)]
#[repr(C, packed)]
pub struct Entry {
pub limit_lo: u16,
pub base_lo: u16,
pub base_mi: u8,
pub access: u8,
pub flags: u8,
pub base_hi: u8,
}
#[allow(dead_code)]
#[repr(C, packed)]
pub struct Pointer {
pub limit: u16,
pub offset: usize,
}
impl Entry {
const ACC_PRESENT: u8 = 1 << 7;
const ACC_SYSTEM: u8 = 1 << 4;
const ACC_EXECUTE: u8 = 1 << 3;
const ACC_WRITE: u8 = 1 << 1;
#[allow(unused)]
const ACC_RING3: u8 = 3 << 5;
#[allow(unused)]
const ACC_ACCESS: u8 = 1 << 0;
pub const NULL: Self = Self {
base_lo: 0,
base_mi: 0,
base_hi: 0,
access: 0,
flags: 0,
limit_lo: 0,
};
const fn new(base: u32, limit: u32, flags: u8, access: u8) -> Self {
Self {
base_lo: (base & 0xFFFF) as u16,
base_mi: ((base >> 16) & 0xFF) as u8,
base_hi: ((base >> 24) & 0xFF) as u8,
access,
flags: (flags & 0xF0) | (((limit >> 16) & 0xF) as u8),
limit_lo: (limit & 0xFFFF) as u16,
}
}
}
#[cfg(any(target_arch = "x86", rust_analyzer))]
mod imp {
use super::Entry;
impl Entry {
pub const FLAG_4K: u8 = 1 << 7;
pub const FLAG_32: u8 = 1 << 6;
pub const RING0_CS32: Entry = Entry::new(
0,
0xFFFFF,
Entry::FLAG_4K | Entry::FLAG_32,
Entry::ACC_ACCESS | Entry::ACC_SYSTEM | Entry::ACC_EXECUTE | Entry::ACC_PRESENT,
);
pub const RING0_DS32: Entry = Entry::new(
0,
0xFFFFF,
Entry::FLAG_4K | Entry::FLAG_32,
Entry::ACC_ACCESS | Entry::ACC_SYSTEM | Entry::ACC_WRITE | Entry::ACC_PRESENT,
);
pub const RING3_CS32: Entry = Entry::new(
0,
0xFFFFF,
Entry::FLAG_4K | Entry::FLAG_32,
Entry::ACC_SYSTEM | Entry::ACC_EXECUTE | Entry::ACC_PRESENT | Entry::ACC_RING3,
);
pub const RING3_DS32: Entry = Entry::new(
0,
0xFFFFF,
Entry::FLAG_4K | Entry::FLAG_32,
Entry::ACC_SYSTEM | Entry::ACC_WRITE | Entry::ACC_PRESENT | Entry::ACC_RING3,
);
pub const RING3_GS32: Entry = Entry::new(
0,
0xF,
Entry::FLAG_32,
Entry::ACC_SYSTEM | Entry::ACC_WRITE | Entry::ACC_PRESENT | Entry::ACC_RING3,
);
pub const fn tss(base: u32, limit: u32) -> Self {
Self::new(
base,
limit,
0,
Entry::ACC_ACCESS | Entry::ACC_PRESENT | Entry::ACC_EXECUTE,
)
}
/// Updates the base of this descriptor.
///
/// # Safety
///
/// The caller must ensure this code cannot be preempted and the segment will be properly
/// reloaded afterwards to avoid desync between the GDT and the segment shadow.
pub unsafe fn set_base(&mut self, value: usize) {
use core::sync::atomic::Ordering;
self.base_lo = (value & 0xFFFF) as u16;
self.base_mi = ((value >> 16) & 0xFF) as u8;
self.base_hi = ((value >> 24) & 0xFF) as u8;
core::sync::atomic::fence(Ordering::Release);
}
}
}
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
mod imp {
use alloc::boxed::Box;
use super::{Entry, Pointer};
#[allow(dead_code)]
#[repr(C, packed)]
pub struct Tss {
_0: u32,
rsp0: u64,
rsp1: u64,
rsp2: u64,
_1: u32,
ist1: u64,
ist2: u64,
ist3: u64,
ist4: u64,
ist5: u64,
ist6: u64,
ist7: u64,
_2: u64,
_3: u16,
iopb_base: u16,
}
impl Tss {
const NULL: Self = Self {
_0: 0,
rsp0: 0,
rsp1: 0,
rsp2: 0,
_1: 0,
ist1: 0,
ist2: 0,
ist3: 0,
ist4: 0,
ist5: 0,
ist6: 0,
ist7: 0,
_2: 0,
_3: 0,
iopb_base: size_of::<Tss>() as u16,
};
}
impl Entry {
const FLAG_LONG: u8 = 1 << 5;
const RING0_CS64: Self = Entry::new(
0,
0,
Entry::FLAG_LONG,
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_EXECUTE,
);
const RING0_DS64: Self = Entry::new(
0,
0,
0,
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_WRITE,
);
const RING3_DS64: Self = Entry::new(
0,
0,
0,
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_RING3 | Entry::ACC_WRITE,
);
const RING3_CS64: Self = Entry::new(
0,
0,
Entry::FLAG_LONG,
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_RING3 | Entry::ACC_EXECUTE,
);
const fn tss_low(base: u32, limit: u32) -> Self {
Self::new(
base,
limit,
Entry::FLAG_LONG,
Entry::ACC_ACCESS | Entry::ACC_PRESENT | Entry::ACC_EXECUTE,
)
}
}
pub fn create_gdt() -> (&'static [Entry], &'static Tss) {
// Won't be deallocated, so leaks are not a concern
let tss = Box::leak(Box::new(Tss::NULL));
let tss_addr = (tss as *mut Tss).addr();
let mut gdt = Box::new([
Entry::NULL,
Entry::RING0_CS64,
Entry::RING0_DS64,
Entry::RING3_DS64,
Entry::RING3_CS64,
Entry::tss_low(tss_addr as u32, (size_of::<Tss>() - 1) as u32),
Entry::NULL,
]);
let tss_high = &mut gdt[6] as *mut _ as *mut u64;
unsafe { tss_high.write_unaligned((tss_addr >> 32) as u64) };
(Box::leak(gdt), tss)
}
pub(super) unsafe fn load_gdt(gdt: &[Entry]) {
let gdt_addr = gdt.as_ptr().addr();
let gdtr = Pointer {
limit: size_of_val(gdt) as u16 - 1,
offset: gdt_addr,
};
unsafe {
core::arch::asm!(
r#"
wbinvd
lgdt ({0})
// Have to use iretq here
mov %rsp, %rcx
leaq 1f(%rip), %rax
// SS:RSP
pushq $0x10
pushq %rcx
// RFLAGS
pushfq
// CS:RIP
pushq $0x08
pushq %rax
iretq
1:
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
mov $0x28, %ax
ltr %ax
"#,
in(reg) &gdtr,
out("rax") _,
out("rcx") _,
options(att_syntax)
);
}
}
/// Initializes and loads the GDT data structure for the current CPU.
///
/// # Safety
///
/// Intended to be called once per each CPU during their early initialization.
pub unsafe fn init() -> usize {
let (gdt, tss) = create_gdt();
unsafe { load_gdt(gdt) };
(tss as *const Tss).addr()
}
}
#[cfg(not(target_arch = "x86"))]
pub use imp::*;