obkrnl/
x86_64.rs

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