//! Global Descriptor Table. In long mode segmentation is mostly vestigial, but //! the CPU still needs valid code/data segment descriptors, and the IDT's gates //! reference a code selector — so we install our own flat GDT with known //! selectors (0x08/0x10 kernel code/data, 0x18/0x20 user data/code for ring 3) //! rather than trusting whatever the firmware left in place. //! //! The code/data descriptors are identical on every core, but the **TSS descriptor //! is per-core** (each core needs its own TSS — its own interrupt/fault stacks; see //! tss.zig). Two cores can't share one TSS descriptor slot, so each core gets its //! own copy of the table with its own TSS descriptor. Slot 0 is the BSP. const parameters = @import("parameters"); /// Selectors into the table (index * 8). Same on every core's GDT. pub const kernel_code = 0x08; pub const kernel_data = 0x10; pub const user_data = 0x18; pub const user_code = 0x20; pub const tss_selector = 0x28; /// Ring-3 selectors as loaded from user mode: RPL 3 or'd in. The user *data* /// descriptor is load-bearing even in long mode — iretq to CPL 3 with a null SS /// raises #GP(0). pub const user_code_rpl3 = user_code | 3; pub const user_data_rpl3 = user_data | 3; const maximum_cpus = parameters.maximum_cpus; const entries = 7; // null, kcode, kdata, udata, ucode, TSS-low, TSS-high /// The shared descriptors (slots 0-4); slots 5-6 hold this core's TSS descriptor, /// filled in per core by `setTssFor`. /// kernel code: present, ring 0, executable, readable, L=1 -> 0x00AF9A00_0000FFFF /// kernel data: present, ring 0, writable -> 0x00CF9200_0000FFFF /// user data: present, ring 3, writable -> 0x00CFF200_0000FFFF /// user code: present, ring 3, executable, readable, L=1 -> 0x00AFFA00_0000FFFF /// User data sits below user code so a future SYSRET works unchanged: it loads /// CS = STAR.SYSRET_CS + 16 and SS = STAR.SYSRET_CS + 8, so with SYSRET_CS = 0x10 /// those land on 0x20 (user code) and 0x18 (user data). const template = [entries]u64{ 0, // null descriptor (required) 0x00AF9A000000FFFF, // kernel code (0x08) 0x00CF92000000FFFF, // kernel data (0x10) 0x00CFF2000000FFFF, // user data (0x18) 0x00AFFA000000FFFF, // user code (0x20) 0, // TSS descriptor low (0x28) 0, // TSS descriptor high }; /// One GDT per core (each a copy of the template, differing only in its TSS slot). var gdts = [_][entries]u64{template} ** maximum_cpus; /// Fill core `cpu`'s 64-bit TSS system descriptor (two GDT slots) so its task /// register can point at its own TSS. Type 0x89 = present, ring 0, available 64-bit /// TSS. Write it into that core's GDT before it loads the TSS selector. pub fn setTssFor(cpu: usize, base: u64, limit: u64) void { gdts[cpu][5] = (limit & 0xFFFF) | ((base & 0xFFFF) << 16) | (((base >> 16) & 0xFF) << 32) | (@as(u64, 0x89) << 40) | (((limit >> 16) & 0xF) << 48) | (((base >> 24) & 0xFF) << 56); gdts[cpu][6] = (base >> 32) & 0xFFFFFFFF; } /// The operand `lgdt` wants: table byte-length minus one, then its address. const Descriptor = packed struct { limit: u16, base: u64, }; /// Loads the GDT and reloads the segment registers (including CS). Defined in /// isr.s — it uses the selectors 0x08 (code) and 0x10 (data) that match the table. extern fn gdt_flush(descriptor: *const Descriptor) callconv(.c) void; /// Load core `cpu`'s GDT and switch onto its segments. Note this reloads the segment /// registers, which zeroes the GS base — so a core must publish its per-CPU pointer /// (setCpuLocal) *after* calling this. pub fn loadOnThisCpu(cpu: usize) void { const descriptor = Descriptor{ .limit = @sizeOf([entries]u64) - 1, .base = @intFromPtr(&gdts[cpu]), }; gdt_flush(&descriptor); } /// Install the bootstrap processor's GDT (slot 0) and switch onto its segments. pub fn init() void { loadOnThisCpu(0); }