obkrnl/
x86_64.rs

1use crate::context::{current_trap_rsp_offset, current_user_rsp_offset};
2use crate::trap::{interrupt_handler, syscall_handler};
3use alloc::boxed::Box;
4use alloc::sync::Arc;
5use alloc::vec;
6use alloc::vec::Vec;
7use bitfield_struct::bitfield;
8use core::arch::{asm, global_asm};
9use core::mem::{transmute, zeroed};
10use core::ptr::addr_of;
11use x86_64::{Dpl, Efer, Rflags, SegmentDescriptor, SegmentSelector, Star};
12
13pub const GDT_KERNEL_CS: SegmentSelector = SegmentSelector::new().with_si(3);
14pub const GDT_KERNEL_DS: SegmentSelector = SegmentSelector::new().with_si(4);
15pub const GDT_USER_CS32: SegmentSelector = SegmentSelector::new().with_si(5).with_rpl(Dpl::Ring3);
16
17/// # Safety
18/// This function can be called only once and must be called by main CPU entry point.
19pub unsafe fn setup_main_cpu() -> Arc<ArchConfig> {
20    // Setup GDT.
21    let mut gdt = vec![
22        // Null descriptor.
23        SegmentDescriptor::new(),
24        // 32-bit GS for user.
25        SegmentDescriptor::new(),
26        // 32-bit FS for user.
27        SegmentDescriptor::new(),
28        // CS for kernel.
29        SegmentDescriptor::new()
30            .with_ty(0b1000) // This required somehow although the docs said it is ignored.
31            .with_s(true) // Same here.
32            .with_p(true)
33            .with_l(true), // 64-bit mode.
34        // DS for kernel.
35        SegmentDescriptor::new()
36            .with_ty(0b0010) // This required somehow although the docs said it is ignored.
37            .with_s(true) // Same here.
38            .with_p(true),
39        // 32-bit CS for user.
40        SegmentDescriptor::new(),
41        // DS for user.
42        SegmentDescriptor::new(),
43        // 64-bit CS for user.
44        SegmentDescriptor::new(),
45    ];
46
47    // Setup Task State Segment (TSS).
48    let trap_rsp = Box::new([0u8; 1024 * 128]);
49    let trap_rsp = Box::leak(trap_rsp);
50    let tss = unsafe { push_tss(&mut gdt, trap_rsp) };
51
52    // Switch GDT from bootloader GDT to our own.
53    let limit = (size_of::<SegmentDescriptor>() * gdt.len() - 1)
54        .try_into()
55        .unwrap();
56
57    gdt.shrink_to_fit();
58
59    unsafe {
60        set_gdtr(
61            &Gdtr {
62                limit,
63                addr: gdt.leak().as_ptr(),
64            },
65            GDT_KERNEL_CS,
66            GDT_KERNEL_DS,
67        )
68    };
69
70    // Set Task Register (TR).
71    unsafe {
72        asm!(
73            "ltr {v:x}",
74            v = in(reg) tss.into_bits(),
75            options(preserves_flags, nostack)
76        )
77    };
78
79    // See idt0 on the PS4 for a reference.
80    const IDT_LEN: usize = 256;
81    static mut IDT: [GateDescriptor; IDT_LEN] = unsafe { zeroed() };
82
83    let set_idt = |n: usize, f: unsafe extern "C" fn() -> !, ty, dpl, ist| {
84        let f = f as usize;
85        let d = GateDescriptor::new()
86            .with_offset1(f as u16)
87            .with_selector(GDT_KERNEL_CS)
88            .with_ist(ist)
89            .with_ty(ty)
90            .with_dpl(dpl)
91            .with_p(true)
92            .with_offset2((f >> 16).try_into().unwrap());
93
94        unsafe { IDT[n] = d };
95    };
96
97    set_idt(3, Xbpt, 0b1110, Dpl::Ring3, 0);
98
99    // Set IDT.
100    let limit = (size_of::<GateDescriptor>() * IDT_LEN - 1)
101        .try_into()
102        .unwrap();
103    let addr = (&raw const IDT).cast();
104    let idtr = Idtr { limit, addr };
105
106    unsafe {
107        asm!(
108            "lidt qword ptr [{v}]",
109            v = in(reg) &idtr,
110            options(preserves_flags, nostack)
111        )
112    };
113
114    // Set CS and SS for syscall and sysret instruction.
115    let star = Star::new()
116        .with_syscall_sel(GDT_KERNEL_CS)
117        .with_sysret_sel(GDT_USER_CS32)
118        .into_bits()
119        .try_into()
120        .unwrap();
121
122    unsafe { wrmsr(0xC0000081, star) };
123
124    // Set entry point for syscall instruction.
125    unsafe { wrmsr(0xC0000082, syscall_entry64 as usize) };
126    unsafe { wrmsr(0xC0000083, syscall_entry32 as usize) };
127
128    // Set SFMASK for syscall.
129    let mask = Rflags::new()
130        .with_cf(true)
131        .with_tf(true)
132        .with_if(true) // https://wiki.osdev.org/SWAPGS#Complications,_Part_2
133        .with_df(true)
134        .with_nt(true)
135        .into_bits()
136        .try_into()
137        .unwrap();
138
139    unsafe { wrmsr(0xC0000084, mask) };
140
141    // Switch EFER from bootloader to our own.
142    let efer = Efer::new()
143        .with_sce(true) // Enable syscall and sysret instruction.
144        .with_lme(true) // Long Mode Enable.
145        .with_lma(true) // Long Mode Active.
146        .into_bits()
147        .try_into()
148        .unwrap();
149
150    unsafe { wrmsr(0xC0000080, efer) };
151
152    // TODO: Find a better way.
153    let len = unsafe { secondary_end.as_ptr().offset_from(secondary_start.as_ptr()) }
154        .try_into()
155        .unwrap();
156
157    Arc::new(ArchConfig {
158        trap_rsp: trap_rsp.as_mut_ptr() as usize,
159        secondary_start: unsafe { core::slice::from_raw_parts(secondary_start.as_ptr(), len) },
160    })
161}
162
163pub unsafe fn wrmsr(reg: u32, val: usize) {
164    unsafe {
165        asm!(
166            "wrmsr",
167            in("ecx") reg,
168            in("edx") val >> 32,
169            in("eax") val,
170            options(nomem, preserves_flags, nostack)
171        )
172    };
173}
174
175/// # Safety
176/// `trap_rsp` must live forever.
177unsafe fn push_tss<const L: usize>(
178    gdt: &mut Vec<SegmentDescriptor>,
179    trap_rsp: *mut [u8; L],
180) -> SegmentSelector {
181    // Setup Task State Segment (TSS).
182    static mut TSS: Tss = unsafe { zeroed() };
183
184    unsafe { TSS.rsp0 = trap_rsp.add(1) as usize }; // Top-down.
185
186    // Add placeholder for TSS descriptor.
187    let tss = gdt.len();
188
189    gdt.push(SegmentDescriptor::new());
190    gdt.push(SegmentDescriptor::new());
191
192    // Setup TSS descriptor.
193    let desc: &mut TssDescriptor = unsafe { transmute(&mut gdt[tss]) };
194    let base = addr_of!(TSS) as usize;
195
196    desc.set_limit1((size_of::<Tss>() - 1).try_into().unwrap());
197    desc.set_base1((base & 0xFFFFFF).try_into().unwrap());
198    desc.set_base2((base >> 24).try_into().unwrap());
199    desc.set_ty(0b1001); // Available 64-bit TSS.
200    desc.set_p(true);
201
202    SegmentSelector::new().with_si(tss.try_into().unwrap())
203}
204
205unsafe extern "C" {
206    safe static secondary_start: [u8; 0];
207    safe static secondary_end: [u8; 0];
208
209    fn set_gdtr(v: &Gdtr, code: SegmentSelector, data: SegmentSelector);
210    fn Xbpt() -> !;
211    fn syscall_entry64() -> !;
212    fn syscall_entry32() -> !;
213}
214
215// See lgdt on the PS4 for a reference.
216global_asm!(
217    "set_gdtr:",
218    "lgdt qword ptr [rdi]",
219    "mov ds, dx",
220    "mov es, dx",
221    "mov fs, dx",
222    "mov gs, dx",
223    "mov ss, dx",
224    "pop rax",  // Return address.
225    "push rsi", // Code segment selector.
226    "push rax",
227    "retfq" // Set CS then return.
228);
229
230// See Xbpt on the PS4 for a reference.
231global_asm!(
232    "Xbpt:", // TODO: Check if coming from user-space.
233    "sub rsp, 0x80", // TODO: Use const from Rust 1.82.
234    "mov dword ptr [rsp+0x78], 3", // TODO: Use const from Rust 1.82.
235    "mov rdi, rsp",
236    "call {f}",
237    f = sym interrupt_handler
238);
239
240// See Xfast_syscall on the PS4 for a reference.
241global_asm!(
242    "syscall_entry64:",
243    "swapgs",
244    "mov gs:[{user_rsp}], rsp", // Save user RSP.
245    "mov rsp, gs:[{trap_rsp}]",
246    "call {handler}",
247    "ud2",
248    user_rsp = const current_user_rsp_offset(),
249    trap_rsp = const current_trap_rsp_offset(),
250    handler = sym syscall_handler
251);
252
253// See Xfast_syscall32 on the Orbis for a reference.
254global_asm!("syscall_entry32:", "ud2");
255
256// See mptramp_start and mptramp_end on the Orbis for a reference.
257global_asm!("secondary_start:", "ud2", "secondary_end:");
258
259/// Raw value of a Global Descriptor-Table Register.
260///
261/// See Global Descriptor-Table Register section on AMD64 Architecture Programmer's Manual Volume 2
262/// for details.
263#[repr(C, packed)]
264struct Gdtr {
265    limit: u16,
266    addr: *const SegmentDescriptor,
267}
268
269/// Raw value of a TSS descriptor.
270///
271/// See TSS Descriptor section on AMD64 Architecture Programmer's Manual Volume 2 for more details.
272#[bitfield(u128)]
273struct TssDescriptor {
274    limit1: u16,
275    #[bits(24)]
276    base1: u32,
277    #[bits(4)]
278    ty: u8,
279    #[bits(access = None)]
280    s: bool,
281    #[bits(2)]
282    dpl: Dpl,
283    p: bool,
284    #[bits(4)]
285    limit2: u8,
286    avl: bool,
287    #[bits(2)]
288    __: u8,
289    g: bool,
290    #[bits(40)]
291    base2: u64,
292    __: u32,
293}
294
295/// Raw value of Long Mode TSS.
296///
297/// See 64-Bit Task State Segment section on AMD64 Architecture Programmer's Manual Volume 2 for
298/// more details.
299#[repr(C, packed)]
300#[derive(Default)]
301struct Tss {
302    reserved1: u32,
303    rsp0: usize,
304    rsp1: usize,
305    rsp2: usize,
306    reserved2: u64,
307    ist1: usize,
308    ist2: usize,
309    ist3: usize,
310    ist4: usize,
311    ist5: usize,
312    ist6: usize,
313    ist7: usize,
314    reserved3: u64,
315    reserved4: u16,
316    io_map_base_address: u16,
317}
318
319/// Raw value of a Interrupt Descriptor-Table Register.
320///
321/// See Interrupt Descriptor-Table Register section on AMD64 Architecture Programmer's Manual Volume
322/// 2 for details.
323#[repr(C, packed)]
324struct Idtr {
325    limit: u16,
326    addr: *const GateDescriptor,
327}
328
329/// Raw value of a Gate Descriptor.
330///
331/// See Gate Descriptors section on AMD64 Architecture Programmer's Manual Volume 2 for more
332/// details.
333#[bitfield(u128)]
334struct GateDescriptor {
335    offset1: u16,
336    #[bits(16)]
337    selector: SegmentSelector,
338    #[bits(3)]
339    ist: u8,
340    #[bits(5)]
341    __: u8,
342    #[bits(4)]
343    ty: u8,
344    __: bool,
345    #[bits(2)]
346    dpl: Dpl,
347    p: bool,
348    #[bits(48)]
349    offset2: u64,
350    __: u32,
351}
352
353/// Contains architecture-specific configurations obtained from [`setup_main_cpu()`].
354pub struct ArchConfig {
355    pub trap_rsp: usize,
356    pub secondary_start: &'static [u8],
357}