diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-01 17:38:57 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-01 17:38:57 +0200 |
| commit | 8f05ea44f69cb796e7b1f581ba1b8045c98d615b (patch) | |
| tree | 3049f0a5fc33dd08f94af0e6493c588b2a4818b9 /rsnix/src/gdt.rs | |
| parent | CONTEXT.md: versioning policy (a.b.c — overhaul/feature/fix) (diff) | |
| download | rs-dos-8f05ea44f69cb796e7b1f581ba1b8045c98d615b.tar.gz rs-dos-8f05ea44f69cb796e7b1f581ba1b8045c98d615b.zip | |
rsnix: preemptive kernel tasks (ADR 0003) — sched, IST fault stacks, 176-byte frames
- sched: 16-slot static table, shell=task 0, idle=task 1, spawn with
guard page + 8 KiB stack, RR on the 100 Hz tick, int 0x60 yield,
sleep_ms, lazy reap, jobs/bg/sleep/kill shell commands
- gdt: own GDT mirroring UEFI selectors + TSS with IST1/IST2
(guard-page overflows die gracefully instead of triple-faulting)
- interrupts: isr_dispatch returns next frame in rax; IntFrame grows
rsp/ss fields — QEMU's 64-bit iretq always pops all five slots from
a 176-byte frame, so synthetic task frames must carry rsp=top+ss
(a 160-byte frame made the first iretq pop rsp/ss from the unmapped
page beyond the frame)
- panic handler: task panics abort the task, shell keeps running
- main: sched::init moved after page allocator + adopted tables
(the idle's stack+guard need both; it was silently not spawning)
Diffstat (limited to 'rsnix/src/gdt.rs')
| -rw-r--r-- | rsnix/src/gdt.rs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/rsnix/src/gdt.rs b/rsnix/src/gdt.rs new file mode 100644 index 0000000..514ed2e --- /dev/null +++ b/rsnix/src/gdt.rs | |||
| @@ -0,0 +1,242 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // GDT + TSS — kernel-owned segment tables and interrupt stack tables (IST). | ||
| 3 | // | ||
| 4 | // The kernel boots on UEFI's GDT. We take over with our own GDT that | ||
| 5 | // mirrors the current selector layout (flat ring-0 code/data descriptors | ||
| 6 | // at the same indices UEFI chose), so CS/DS/SS/ES keep working without | ||
| 7 | // any far-jump or segment-reload gymnastics. On top of that we add a | ||
| 8 | // TSS with two interrupt stack tables: | ||
| 9 | // | ||
| 10 | // - IST1: page-fault (vector 14) stack. A stack-overflowing task | ||
| 11 | // faults inside its unmapped guard page; the CPU pushes the fault | ||
| 12 | // frame onto this dedicated stack instead of the faulting (also | ||
| 13 | // unmapped) stack, so we can kill the task gracefully instead of | ||
| 14 | // double/triple-faulting into a machine reset. | ||
| 15 | // - IST2: double-fault (vector 8) stack. If anything else goes wrong | ||
| 16 | // during exception handling we land here and halt cleanly. | ||
| 17 | // | ||
| 18 | // We never change privilege level, so the TSS's RSP0..2 are unused — | ||
| 19 | // only the IST pointers matter. | ||
| 20 | // --------------------------------------------------------------------------- | ||
| 21 | use core::arch::asm; | ||
| 22 | |||
| 23 | const GDT_ENTRIES: usize = 64; | ||
| 24 | const TSS_INDEX: usize = GDT_ENTRIES - 1; // 63 | ||
| 25 | const TSS_SEL: u16 = (TSS_INDEX << 3) as u16; // 0x1F8 | ||
| 26 | |||
| 27 | /// 8-byte segment descriptor. | ||
| 28 | #[repr(C, packed)] | ||
| 29 | #[derive(Clone, Copy)] | ||
| 30 | struct GdtEntry { | ||
| 31 | limit_lo: u16, | ||
| 32 | base_lo: u16, | ||
| 33 | base_mid: u8, | ||
| 34 | access: u8, | ||
| 35 | flags_limit: u8, // limit 19:16 | flags nibble (G D/B L AVL) | ||
| 36 | base_hi: u8, | ||
| 37 | } | ||
| 38 | |||
| 39 | impl GdtEntry { | ||
| 40 | const fn null() -> Self { | ||
| 41 | Self { | ||
| 42 | limit_lo: 0, | ||
| 43 | base_lo: 0, | ||
| 44 | base_mid: 0, | ||
| 45 | access: 0, | ||
| 46 | flags_limit: 0, | ||
| 47 | base_hi: 0, | ||
| 48 | } | ||
| 49 | } | ||
| 50 | /// Flat ring-0 64-bit code segment (present, exec/read, L=1). | ||
| 51 | const fn code() -> Self { | ||
| 52 | Self { | ||
| 53 | limit_lo: 0xFFFF, | ||
| 54 | base_lo: 0, | ||
| 55 | base_mid: 0, | ||
| 56 | access: 0x9A, | ||
| 57 | flags_limit: 0xAF, // G=1, D/B=0, L=1, AVL=0; limit 0xF | ||
| 58 | base_hi: 0, | ||
| 59 | } | ||
| 60 | } | ||
| 61 | /// Flat ring-0 data segment (present, read/write). | ||
| 62 | const fn data() -> Self { | ||
| 63 | Self { | ||
| 64 | limit_lo: 0xFFFF, | ||
| 65 | base_lo: 0, | ||
| 66 | base_mid: 0, | ||
| 67 | access: 0x92, | ||
| 68 | flags_limit: 0xCF, // G=1, D/B=1, L=0, AVL=0; limit 0xF | ||
| 69 | base_hi: 0, | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | /// 64-bit TSS: only the IST pointers are used (no privilege changes). | ||
| 75 | #[repr(C, packed)] | ||
| 76 | #[derive(Clone, Copy)] | ||
| 77 | struct Tss { | ||
| 78 | _res1: u32, | ||
| 79 | rsp0: u64, | ||
| 80 | rsp1: u64, | ||
| 81 | rsp2: u64, | ||
| 82 | _res2: u64, | ||
| 83 | ist1: u64, | ||
| 84 | ist2: u64, | ||
| 85 | ist3: u64, | ||
| 86 | ist4: u64, | ||
| 87 | ist5: u64, | ||
| 88 | ist6: u64, | ||
| 89 | ist7: u64, | ||
| 90 | _res3: u64, | ||
| 91 | _res4: u16, | ||
| 92 | iomap_base: u16, | ||
| 93 | } | ||
| 94 | |||
| 95 | const TSS_SIZE: usize = 104; | ||
| 96 | |||
| 97 | impl Tss { | ||
| 98 | const fn zero() -> Self { | ||
| 99 | Self { | ||
| 100 | _res1: 0, | ||
| 101 | rsp0: 0, | ||
| 102 | rsp1: 0, | ||
| 103 | rsp2: 0, | ||
| 104 | _res2: 0, | ||
| 105 | ist1: 0, | ||
| 106 | ist2: 0, | ||
| 107 | ist3: 0, | ||
| 108 | ist4: 0, | ||
| 109 | ist5: 0, | ||
| 110 | ist6: 0, | ||
| 111 | ist7: 0, | ||
| 112 | _res3: 0, | ||
| 113 | _res4: 0, | ||
| 114 | iomap_base: 0, | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | /// 4 KiB page-aligned block for the IST stacks. | ||
| 120 | #[repr(align(4096))] | ||
| 121 | struct Page([u8; 4096]); | ||
| 122 | |||
| 123 | static mut PF_STACK: Page = Page([0; 4096]); | ||
| 124 | static mut DF_STACK: Page = Page([0; 4096]); | ||
| 125 | static mut TSS: Tss = Tss::zero(); | ||
| 126 | // GDT slots are 8 bytes except the TSS descriptor at index 63, which is | ||
| 127 | // 16 bytes — total 64*8 + 16. | ||
| 128 | static mut GDT: [u8; GDT_ENTRIES * 8 + 16] = [0; GDT_ENTRIES * 8 + 16]; | ||
| 129 | static mut GDTR: [u16; 5] = [0; 5]; | ||
| 130 | |||
| 131 | unsafe fn read_seg(which: u8) -> u16 { | ||
| 132 | // `which` picks the segment register to read (0=cs, 1=ds, 2=ss, ...). | ||
| 133 | let v: u64; | ||
| 134 | unsafe { | ||
| 135 | match which { | ||
| 136 | 0 => asm!("mov {0}, cs", out(reg) v, options(nostack, preserves_flags)), | ||
| 137 | 1 => asm!("mov {0}, ds", out(reg) v, options(nostack, preserves_flags)), | ||
| 138 | 2 => asm!("mov {0}, ss", out(reg) v, options(nostack, preserves_flags)), | ||
| 139 | 3 => asm!("mov {0}, es", out(reg) v, options(nostack, preserves_flags)), | ||
| 140 | 4 => asm!("mov {0}, fs", out(reg) v, options(nostack, preserves_flags)), | ||
| 141 | _ => asm!("mov {0}, gs", out(reg) v, options(nostack, preserves_flags)), | ||
| 142 | } | ||
| 143 | } | ||
| 144 | v as u16 | ||
| 145 | } | ||
| 146 | |||
| 147 | /// Install the kernel's own GDT (mirroring UEFI's selectors) and a TSS | ||
| 148 | /// with IST stacks for page-fault and double-fault handling. | ||
| 149 | /// | ||
| 150 | /// Returns false if UEFI's selector layout cannot be mirrored (any | ||
| 151 | /// nonzero selector at an index >= TSS_INDEX) — then we keep UEFI's GDT | ||
| 152 | /// and the IST-based fault stacks stay off (guard pages still exist but | ||
| 153 | /// an overflow would fault while pushing the fault frame → machine | ||
| 154 | /// reset instead of a graceful task death). | ||
| 155 | /// | ||
| 156 | /// Must run with interrupts disabled and before `interrupts::init` (the | ||
| 157 | /// IDT's vector-8/14 entries carry the IST indices). | ||
| 158 | pub fn init() -> bool { | ||
| 159 | unsafe { | ||
| 160 | asm!("cli", options(nostack)); | ||
| 161 | |||
| 162 | let cs = read_seg(0); | ||
| 163 | let ds = read_seg(1); | ||
| 164 | let ss = read_seg(2); | ||
| 165 | let es = read_seg(3); | ||
| 166 | let fs = read_seg(4); | ||
| 167 | let gs = read_seg(5); | ||
| 168 | let sels = [cs, ds, ss, es, fs, gs]; | ||
| 169 | |||
| 170 | // Every nonzero selector must land on a descriptor slot below | ||
| 171 | // TSS_INDEX (63 holds the 16-byte TSS descriptor). | ||
| 172 | for &s in &sels { | ||
| 173 | if s != 0 && (s >> 3) as usize >= TSS_INDEX { | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | let gdt = core::ptr::addr_of_mut!(GDT[0]); | ||
| 179 | let write_entry = |idx: usize, e: GdtEntry| { | ||
| 180 | let p = gdt.add(idx * 8) as *mut GdtEntry; | ||
| 181 | core::ptr::write_unaligned(p, e); | ||
| 182 | }; | ||
| 183 | |||
| 184 | write_entry(0, GdtEntry::null()); | ||
| 185 | write_entry((cs >> 3) as usize, GdtEntry::code()); | ||
| 186 | let cs_idx = (cs >> 3) as usize; | ||
| 187 | for &s in &sels { | ||
| 188 | let idx = (s >> 3) as usize; | ||
| 189 | if s != 0 && idx != 0 && idx != cs_idx { | ||
| 190 | write_entry(idx, GdtEntry::data()); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | // TSS descriptor (16 bytes) at index 63. | ||
| 195 | let tss_base = core::ptr::addr_of_mut!(TSS) as usize as u64; | ||
| 196 | TSS.ist1 = core::ptr::addr_of_mut!(PF_STACK.0[0]) as usize as u64 + 4096; | ||
| 197 | TSS.ist2 = core::ptr::addr_of_mut!(DF_STACK.0[0]) as usize as u64 + 4096; | ||
| 198 | TSS.iomap_base = TSS_SIZE as u16; // no I/O bitmap | ||
| 199 | |||
| 200 | let mut td = [0u8; 16]; | ||
| 201 | td[0..2].copy_from_slice(&((TSS_SIZE - 1) as u16).to_le_bytes()); | ||
| 202 | td[2..4].copy_from_slice(&(tss_base as u16).to_le_bytes()); | ||
| 203 | td[4] = (tss_base >> 16) as u8; | ||
| 204 | td[5] = 0x89; // present, available 64-bit TSS | ||
| 205 | td[6] = 0x00; // limit 19:16 + flags = 0 | ||
| 206 | td[7] = (tss_base >> 24) as u8; | ||
| 207 | td[8..12].copy_from_slice(&((tss_base >> 32) as u32).to_le_bytes()); | ||
| 208 | // td[12..16] reserved — zero. | ||
| 209 | core::ptr::copy_nonoverlapping(td.as_ptr(), gdt.add(TSS_INDEX * 8), 16); | ||
| 210 | |||
| 211 | // GDTR: limit = 63*8 + 16 - 1 = 519. | ||
| 212 | GDTR = [ | ||
| 213 | (TSS_INDEX * 8 + 16 - 1) as u16, | ||
| 214 | gdt as u16, | ||
| 215 | (gdt as u64 >> 16) as u16, | ||
| 216 | (gdt as u64 >> 32) as u16, | ||
| 217 | (gdt as u64 >> 48) as u16, | ||
| 218 | ]; | ||
| 219 | asm!("lgdt [{}]", in(reg) core::ptr::addr_of!(GDTR), options(nostack, preserves_flags)); | ||
| 220 | asm!("ltr ax", in("ax") TSS_SEL, options(nostack, preserves_flags)); | ||
| 221 | |||
| 222 | // Re-load the data segments so the CPU re-validates them against | ||
| 223 | // the new GDT (CS cannot be reloaded by mov; its descriptor is | ||
| 224 | // unchanged at the same index). | ||
| 225 | if ds != 0 { | ||
| 226 | asm!("mov ds, ax", in("ax") ds, options(nostack, preserves_flags)); | ||
| 227 | } | ||
| 228 | if ss != 0 { | ||
| 229 | asm!("mov ss, ax", in("ax") ss, options(nostack, preserves_flags)); | ||
| 230 | } | ||
| 231 | if es != 0 { | ||
| 232 | asm!("mov es, ax", in("ax") es, options(nostack, preserves_flags)); | ||
| 233 | } | ||
| 234 | if fs != 0 { | ||
| 235 | asm!("mov fs, ax", in("ax") fs, options(nostack, preserves_flags)); | ||
| 236 | } | ||
| 237 | if gs != 0 { | ||
| 238 | asm!("mov gs, ax", in("ax") gs, options(nostack, preserves_flags)); | ||
| 239 | } | ||
| 240 | true | ||
| 241 | } | ||
| 242 | } | ||
