summaryrefslogtreecommitdiffstats
path: root/rsnix/src/gdt.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 17:38:57 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 17:38:57 +0200
commit8f05ea44f69cb796e7b1f581ba1b8045c98d615b (patch)
tree3049f0a5fc33dd08f94af0e6493c588b2a4818b9 /rsnix/src/gdt.rs
parentCONTEXT.md: versioning policy (a.b.c — overhaul/feature/fix) (diff)
downloadrs-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 '')
-rw-r--r--rsnix/src/gdt.rs242
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// ---------------------------------------------------------------------------
21use core::arch::asm;
22
23const GDT_ENTRIES: usize = 64;
24const TSS_INDEX: usize = GDT_ENTRIES - 1; // 63
25const TSS_SEL: u16 = (TSS_INDEX << 3) as u16; // 0x1F8
26
27/// 8-byte segment descriptor.
28#[repr(C, packed)]
29#[derive(Clone, Copy)]
30struct 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
39impl 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)]
77struct 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
95const TSS_SIZE: usize = 104;
96
97impl 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))]
121struct Page([u8; 4096]);
122
123static mut PF_STACK: Page = Page([0; 4096]);
124static mut DF_STACK: Page = Page([0; 4096]);
125static 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.
128static mut GDT: [u8; GDT_ENTRIES * 8 + 16] = [0; GDT_ENTRIES * 8 + 16];
129static mut GDTR: [u16; 5] = [0; 5];
130
131unsafe 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).
158pub 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}