90 lines
3.9 KiB
Zig
90 lines
3.9 KiB
Zig
//! 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);
|
|
}
|