strat9_kernel/arch/x86_64/gdt.rs
1//! Global Descriptor Table (GDT) for Strat9-OS
2//!
3//! Sets up the GDT with kernel code/data segments, user segments for SYSRET,
4//! and a TSS descriptor. The TSS must be initialized before this module.
5//!
6//! ## Segment layout (SYSRET-compatible)
7//!
8//! ```text
9//! Index 0: Null
10//! Index 1: Kernel Code 64-bit (CS=0x08) ← SYSCALL loads CS from STAR[47:32]
11//! Index 2: Kernel Data (SS=0x10) ← SYSCALL loads SS
12//! Index 3: User Code 32-bit (dummy, 0x18) ← STAR[63:48] base
13//! Index 4: User Data (0x20) ← SYSRETQ: SS = base+8 | RPL3 = 0x23
14//! Index 5: User Code 64-bit (0x28) ← SYSRETQ: CS = base+16 | RPL3 = 0x2B
15//! Index 6-7: TSS (16-byte descriptor)
16//! ```
17//!
18//! STAR MSR: `[47:32]=0x08` (kernel CS), `[63:48]=0x18` (index 3).
19//! SYSRETQ: CS = 0x18+16 = 0x28 | RPL3 = 0x2B, SS = 0x18+8 = 0x20 | RPL3 = 0x23.
20
21use core::{
22 mem::MaybeUninit,
23 sync::atomic::{AtomicBool, Ordering},
24};
25use x86_64::{
26 instructions::{
27 segmentation::{Segment, CS, DS, SS},
28 tables::load_tss,
29 },
30 structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector},
31};
32
33/// GDT storage : per CPU
34static mut GDT: [MaybeUninit<GlobalDescriptorTable>; crate::arch::x86_64::percpu::MAX_CPUS] =
35 [const { MaybeUninit::uninit() }; crate::arch::x86_64::percpu::MAX_CPUS];
36
37/// Cached segment selectors after GDT is loaded (per CPU)
38static mut SELECTORS: [MaybeUninit<Selectors>; crate::arch::x86_64::percpu::MAX_CPUS] =
39 [const { MaybeUninit::uninit() }; crate::arch::x86_64::percpu::MAX_CPUS];
40static SELECTORS_INIT: [AtomicBool; crate::arch::x86_64::percpu::MAX_CPUS] =
41 [const { AtomicBool::new(false) }; crate::arch::x86_64::percpu::MAX_CPUS];
42
43#[derive(Copy, Clone)]
44struct Selectors {
45 kernel_code: SegmentSelector,
46 kernel_data: SegmentSelector,
47 #[allow(dead_code)]
48 user_code32: SegmentSelector,
49 user_data: SegmentSelector,
50 user_code64: SegmentSelector,
51 #[allow(dead_code)]
52 tss: SegmentSelector,
53}
54
55/// Initialize the GDT with kernel segments, user segments, and TSS.
56///
57/// **Prerequisite**: `tss::init()` must be called first.
58///
59/// The segment ordering is critical for SYSRET to work correctly.
60/// SYSRET expects: STAR[63:48] points to a base where base+0 = user_code32,
61/// base+8 = user_data, base+16 = user_code64.
62pub fn init() {
63 init_cpu(0);
64}
65
66/// Initialize the GDT for a given CPU index.
67pub fn init_cpu(cpu_index: usize) {
68 // Bounds check: prevent OOB access into static arrays before any unsafe.
69 assert!(
70 cpu_index < crate::arch::x86_64::percpu::MAX_CPUS,
71 "GDT init_cpu: cpu_index {} >= MAX_CPUS {}",
72 cpu_index,
73 crate::arch::x86_64::percpu::MAX_CPUS,
74 );
75 // SAFETY: Called during init (BSP) or AP bring-up before interrupts are enabled on that CPU.
76 unsafe {
77 let gdt = &mut *GDT[cpu_index].as_mut_ptr();
78 *gdt = GlobalDescriptorTable::new();
79
80 // Index 1: Kernel Code 64-bit (0x08)
81 let kernel_code = gdt.append(Descriptor::kernel_code_segment());
82 // Index 2: Kernel Data (0x10)
83 let kernel_data = gdt.append(Descriptor::kernel_data_segment());
84
85 // Index 3: User Code 32-bit dummy (0x18)
86 // SYSRET requires this slot to exist. We create a 32-bit code segment
87 // with DPL=3 that is never actually used for execution.
88 // Descriptor bits: Present | DPL=3 | Code segment | Readable | 32-bit
89 let user_code32_bits: u64 = (1 << 47) // Present
90 | (3 << 45) // DPL = 3
91 | (1 << 44) // S = 1 (code/data)
92 | (1 << 43) // Executable
93 | (1 << 41) // Readable
94 | (1 << 54); // D = 1 (32-bit default)
95 let user_code32 = gdt.append(Descriptor::UserSegment(user_code32_bits));
96
97 // Index 4: User Data (0x20)
98 let user_data = gdt.append(Descriptor::user_data_segment());
99 // Index 5: User Code 64-bit (0x28)
100 let user_code64 = gdt.append(Descriptor::user_code_segment());
101
102 // Index 6-7: TSS (16-byte descriptor)
103 let tss_sel = gdt.append(Descriptor::tss_segment(super::tss::tss_for(cpu_index)));
104
105 gdt.load_unsafe();
106
107 // Reload segment registers with new selectors
108 CS::set_reg(kernel_code);
109 DS::set_reg(kernel_data);
110 SS::set_reg(kernel_data);
111
112 // Load TSS into task register
113 load_tss(tss_sel);
114
115 SELECTORS[cpu_index].write(Selectors {
116 kernel_code,
117 kernel_data,
118 user_code32,
119 user_data,
120 user_code64,
121 tss: tss_sel,
122 });
123 SELECTORS_INIT[cpu_index].store(true, Ordering::Release);
124
125 // Raw e9 output : e9_println! with format_args hangs before IDT is loaded.
126 for &b in b"GDT init OK\n" {
127 unsafe {
128 core::arch::asm!("out 0xe9, al", in("al") b, options(nomem, nostack));
129 }
130 }
131 }
132}
133
134/// Get the kernel code segment selector
135pub fn kernel_code_selector() -> SegmentSelector {
136 selectors_for(current_cpu_index()).kernel_code
137}
138
139/// Get the kernel data segment selector
140pub fn kernel_data_selector() -> SegmentSelector {
141 selectors_for(current_cpu_index()).kernel_data
142}
143
144/// Get the user code 64-bit segment selector (with RPL=3)
145pub fn user_code_selector() -> SegmentSelector {
146 let sel = selectors_for(current_cpu_index()).user_code64;
147 SegmentSelector(sel.0 | 3) // Set RPL=3
148}
149
150/// Get the user data segment selector (with RPL=3)
151pub fn user_data_selector() -> SegmentSelector {
152 let sel = selectors_for(current_cpu_index()).user_data;
153 SegmentSelector(sel.0 | 3) // Set RPL=3
154}
155
156/// Get the raw STAR MSR value for SYSCALL/SYSRET.
157///
158/// STAR[47:32] = kernel CS selector (for SYSCALL entry)
159/// STAR[63:48] = user code 32 selector base (for SYSRET calculation)
160pub fn star_msr_value() -> u64 {
161 let sels = selectors_for(current_cpu_index());
162 let kernel_cs = sels.kernel_code.0 as u64;
163 let user_base = sels.user_code32.0 as u64; // 0x18 (without RPL)
164 (kernel_cs << 32) | (user_base << 48)
165}
166
167/// Performs the selectors for operation.
168fn selectors_for(cpu_index: usize) -> Selectors {
169 if !SELECTORS_INIT[cpu_index].load(Ordering::Acquire) {
170 panic!("GDT selectors for CPU{} not initialized", cpu_index);
171 }
172 // SAFETY: Initialized in init_cpu and stored for 'static lifetime.
173 unsafe { *SELECTORS[cpu_index].as_ptr() }
174}
175
176/// Performs the current cpu index operation.
177fn current_cpu_index() -> usize {
178 crate::arch::x86_64::percpu::current_cpu_index()
179}