//! GDT (Global Descriptor Table) + TSS (Task State Segment). //! //! In 64-bit mode segmentation is mostly dead, but the GDT still matters: //! //! * **Privilege levels.** The low 2 bits of CS are the *current privilege //! level* (CPL): ring 0 = kernel, ring 3 = user. The code segment //! descriptors in the GDT say which ring they belong to, so to run user code //! we need user code/data segments (DPL 3) next to the kernel ones. //! * **The TSS.** The GDT is how the CPU finds the TSS, which holds: //! - `privilege_stack_table[0]` ("RSP0"): the stack the CPU switches to when //! an interrupt/exception arrives while running *ring 3* code. User //! stacks can't be trusted, so every thread that runs user code has its //! own kernel stack, and the scheduler updates RSP0 on every switch. //! - the Interrupt Stack Table (IST): known-good stacks for specific //! exceptions. The double-fault handler uses one, so a kernel stack //! overflow doesn't turn into a triple fault (= reboot). //! //! Segment order matters because of `syscall`/`sysret` (see syscall.rs): they //! don't read the GDT entries' contents but *compute* selectors from the STAR //! MSR: kernel CS = base, kernel SS = base + 8; user SS = base2 + 8, //! user CS = base2 + 16. Hence: //! //! 0x00 null | 0x08 kernel code | 0x10 kernel data | 0x18 user data | //! 0x20 user code | 0x28 TSS (16 bytes, two slots) use core::ptr::{addr_of, addr_of_mut}; use lazy_static::lazy_static; use x86_64::VirtAddr; use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; use x86_64::structures::tss::TaskStateSegment; /// Which IST slot (0..=6) the double-fault handler uses. pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; /// The TSS. A `static mut` because the scheduler rewrites RSP0 on every /// context switch (the CPU re-reads it from memory on each ring 3 -> ring 0 /// transition, so writing the field is enough). Only touched with interrupts /// disabled, on a single CPU. static mut TSS: TaskStateSegment = TaskStateSegment::new(); /// Stack for the double-fault handler (static: no heap needed. It has no /// guard page, so don't do anything recursive in the double-fault handler!) const DOUBLE_FAULT_STACK_SIZE: usize = 4096 * 5; static mut DOUBLE_FAULT_STACK: [u8; DOUBLE_FAULT_STACK_SIZE] = [0; DOUBLE_FAULT_STACK_SIZE]; /// Selectors (GDT offsets + requested privilege level) of our segments. pub struct Selectors { pub kernel_code: SegmentSelector, pub kernel_data: SegmentSelector, pub user_data: SegmentSelector, // RPL 3 pub user_code: SegmentSelector, // RPL 3 tss: SegmentSelector, } lazy_static! { static ref GDT: (GlobalDescriptorTable, Selectors) = { let mut gdt = GlobalDescriptorTable::new(); // Order matters for syscall/sysret, see the module comment. let kernel_code = gdt.append(Descriptor::kernel_code_segment()); let kernel_data = gdt.append(Descriptor::kernel_data_segment()); let user_data = gdt.append(Descriptor::user_data_segment()); let user_code = gdt.append(Descriptor::user_code_segment()); // Safety: TSS is a static, so the reference lives forever. let tss = gdt.append(Descriptor::tss_segment(unsafe { &*addr_of!(TSS) })); (gdt, Selectors { kernel_code, kernel_data, user_data, user_code, tss }) }; } /// The segment selectors, e.g. to build an `iretq` frame into ring 3. pub fn selectors() -> &'static Selectors { &GDT.1 } /// Load our GDT, reload the segment registers and load the TSS. pub fn init() { use x86_64::instructions::segmentation::{CS, DS, ES, SS, Segment}; use x86_64::instructions::tables::load_tss; unsafe { // Stacks grow downwards on x86, so hand the CPU the *end* address. let start = VirtAddr::from_ptr(addr_of!(DOUBLE_FAULT_STACK)); (*addr_of_mut!(TSS)).interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = start + DOUBLE_FAULT_STACK_SIZE as u64; } GDT.0.load(); let s = &GDT.1; unsafe { // The old selectors point into the bootloader's GDT, which we just // replaced, so reload them. (`iretq` checks SS, so it must be valid.) CS::set_reg(s.kernel_code); SS::set_reg(s.kernel_data); DS::set_reg(s.kernel_data); ES::set_reg(s.kernel_data); load_tss(s.tss); } } /// Set RSP0: the kernel stack the CPU switches to when an interrupt arrives /// while user code runs. Called by the scheduler on every context switch. pub fn set_kernel_stack(stack_top: u64) { unsafe { (*addr_of_mut!(TSS)).privilege_stack_table[0] = VirtAddr::new(stack_top); } }