rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | //! 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 ;
use lazy_static;
use VirtAddr;
use ;
use 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 = 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: = ;
/// Selectors (GDT offsets + requested privilege level) of our segments.
lazy_static!
/// The segment selectors, e.g. to build an `iretq` frame into ring 3.
/// Load our GDT, reload the segment registers and load the 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.
|