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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | //! 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)
//!
//! **One GDT and TSS per CPU.** Each CPU runs a different thread, so each
//! needs its own RSP0 and its own double-fault stack, i.e. its own TSS. (A
//! TSS descriptor is also marked "busy" when loaded, so two CPUs can't share
//! one GDT entry either.) CPU 0 uses the static tables below, which work
//! before the heap exists; the other CPUs get heap-allocated copies from
//! `init_ap`. The selectors are the same on every CPU.
use crateMAX_CPUS;
use crateKernelStack;
use Box;
use ;
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;
/// CPU 0's 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, by CPU 0 itself.
static mut TSS: TaskStateSegment = new;
/// CPU 0's double-fault stack (static: no heap needed this early). 64 KiB,
/// because after a kernel stack overflow the double-fault handler runs the
/// code that kills the thread on it.
const DOUBLE_FAULT_STACK_SIZE: usize = 64 * 1024;
static mut DOUBLE_FAULT_STACK: = ;
/// Each CPU's TSS (CPU 0: the static one, others: leaked boxes).
static TSS_OF: = ;
/// Selectors (GDT offsets + requested privilege level) of our segments.
/// A GDT in our standard layout whose TSS entry points at `tss`.
lazy_static!
/// The segment selectors, e.g. to build an `iretq` frame into ring 3.
/// (Identical on every CPU.)
/// Load `gdt`, reload the segment registers and load the TSS.
/// CPU 0: load the static GDT and TSS (works without a heap).
/// Another CPU (`cpu` > 0): build and load its own GDT and TSS, with a
/// guard-paged double-fault stack. These live forever (CPUs never go away).
/// Set RSP0 of the current CPU: the kernel stack it switches to when an
/// interrupt arrives while user code runs. Called by the scheduler on
/// every context switch (with interrupts off).
|