Skip to main content

obkrnl/
main.rs

1#![no_std]
2#![cfg_attr(not(test), no_main)]
3#![allow(clippy::needless_pub_self)] // rust-analyzer will use full path without pub(self).
4#![allow(clippy::type_complexity)] // Type aliasing hide the actual type.
5
6use self::config::{Config, Dipsw, PAGE_MASK, PAGE_SHIFT, PAGE_SIZE, Param1};
7use self::context::{ContextSetup, arch, config};
8use self::dmem::Dmem;
9use self::imgact::Ps4Abi;
10use self::malloc::KernelHeap;
11use self::proc::{Fork, Proc, ProcAbi, ProcMgr, Thread};
12use self::sched::sleep;
13use self::uma::Uma;
14use self::vm::Vm;
15use ::config::{BootEnv, MapType};
16use alloc::string::String;
17use alloc::sync::Arc;
18use core::cmp::min;
19use core::fmt::Write;
20use humansize::{DECIMAL, SizeFormatter};
21use krt::{boot_env, info, warn};
22
23#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
24#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
25mod arch;
26mod config;
27mod context;
28mod dmem;
29mod event;
30mod imgact;
31mod imgfmt;
32mod lock;
33mod malloc;
34mod mem;
35mod proc;
36mod sched;
37mod signal;
38mod subsystem;
39mod trap;
40mod uma;
41mod vm;
42
43extern crate alloc;
44
45/// This will be called by [`krt`] crate.
46///
47/// See Orbis kernel entry point for a reference.
48#[cfg_attr(target_os = "none", unsafe(no_mangle))]
49fn main(map: &'static ::config::KernelMap, config: &'static ::config::Config) -> ! {
50    // SAFETY: This function has a lot of restrictions. See Context documentation for more details.
51    let config = Config::new(config);
52    let params1 = Param1::new(&config);
53    let cpu = self::arch::identify_cpu();
54    let hw = match boot_env() {
55        BootEnv::Vm(vm) => vm.hypervisor(),
56    };
57
58    info!(
59        concat!(
60            "Starting Obliteration Kernel on {}.\n",
61            "cpu_vendor                 : {} × {}\n",
62            "cpu_id                     : {:#x}\n",
63            "boot_parameter.idps.product: {}\n",
64            "physfree                   : {:#x}"
65        ),
66        String::from_utf8_lossy(hw),
67        cpu.cpu_vendor,
68        config.cpu_count(),
69        cpu.cpu_id,
70        config.idps().product,
71        map.kern_vsize
72    );
73
74    // Setup the CPU after the first print to let the bootloader developer know (some of) their code
75    // are working.
76    let arch = unsafe { self::arch::setup_main_cpu(&config, cpu, map) };
77
78    // Setup proc0 to represent the kernel.
79    let proc0 = Proc::new_bare(Arc::new(Proc0Abi));
80
81    // Setup thread0 to represent this thread.
82    let proc0 = Arc::new(proc0);
83    let thread0 = Thread::new_bare(proc0);
84
85    // Activate CPU context.
86    let thread0 = Arc::new(thread0);
87
88    unsafe {
89        self::context::run_with_context(
90            config,
91            arch,
92            0,
93            thread0,
94            move |s| setup(s, map, params1),
95            run,
96        )
97    };
98}
99
100fn setup(
101    setup: &mut ContextSetup,
102    map: &'static ::config::KernelMap,
103    param1: Arc<Param1>,
104) -> SetupResult {
105    // Initialize physical memory.
106    let mut mi = load_memory_map();
107    let mut buf = String::with_capacity(0x2000);
108
109    fn format_map(tab: &[usize], last: usize, buf: &mut String) {
110        for i in (0..=last).step_by(2) {
111            let start = tab[i];
112            let end = tab[i + 1];
113            let size = SizeFormatter::new(end - start, DECIMAL);
114
115            write!(buf, "\n{start:#018x}-{end:#018x} ({size})").unwrap();
116        }
117    }
118
119    format_map(&mi.physmap, mi.physmap_last, &mut buf);
120
121    info!(
122        concat!(
123            "Memory map loaded with {} maps.\n",
124            "initial_memory_size: {} ({})\n",
125            "basemem            : {:#x}\n",
126            "boot_address       : {:#x}\n",
127            "mptramp_pagetables : {:#x}\n",
128            "Maxmem             : {:#x}",
129            "{}"
130        ),
131        mi.physmap_last,
132        mi.initial_memory_size,
133        SizeFormatter::new(mi.initial_memory_size, DECIMAL),
134        mi.boot_area,
135        mi.boot_info.addr,
136        mi.boot_info.page_tables,
137        mi.end_page,
138        buf
139    );
140
141    buf.clear();
142
143    // Initialize DMEM system.
144    let dmem = Dmem::new(&mut mi);
145
146    format_map(&mi.physmap, mi.physmap_last, &mut buf);
147
148    info!(
149        concat!(
150            "DMEM initialized.\n",
151            "Mode  : {} ({})\n",
152            "Maxmem: {:#x}",
153            "{}"
154        ),
155        dmem.mode(),
156        dmem.config().name,
157        mi.end_page,
158        buf
159    );
160
161    drop(buf);
162
163    // TODO: We probably want to remove hard-coded start address of the first map here.
164    let mut phys_avail = [0usize; 61];
165    let mut pa_indx = 0;
166    let mut dump_avail = [0usize; 61];
167    let mut da_indx = 1;
168    let mut physmem = 0;
169    let unk1 = 0xA494000 + 0x2200000; // TODO: What is this?
170    let paddr_free = match mi.unk {
171        0 => map.kern_vsize.get() + 0x400000, // TODO: Why 0x400000?
172        _ => map.kern_vsize.get(),
173    };
174
175    mi.physmap[0] = PAGE_SIZE.get();
176
177    phys_avail[pa_indx] = mi.physmap[0];
178    pa_indx += 1;
179    phys_avail[pa_indx] = mi.physmap[0];
180    dump_avail[da_indx] = mi.physmap[0];
181
182    for i in (0..=mi.physmap_last).step_by(2) {
183        let begin = mi.physmap[i]
184            .checked_next_multiple_of(PAGE_SIZE.get())
185            .unwrap();
186        let end = min(
187            mi.physmap[i + 1] & !PAGE_MASK.get(),
188            mi.end_page << PAGE_SHIFT,
189        );
190
191        for pa in (begin..end).step_by(PAGE_SIZE.get()) {
192            let mut full = false;
193
194            if (pa < (unk1 & 0xffffffffffe00000) || pa >= paddr_free)
195                && (mi.dcons_addr == 0
196                    || (pa < (mi.dcons_addr & 0xffffffffffffc000)
197                        || (mi.dcons_addr + mi.dcons_size <= pa)))
198            {
199                if mi.memtest == 0 {
200                    if pa == phys_avail[pa_indx] {
201                        phys_avail[pa_indx] = pa + PAGE_SIZE.get();
202                        physmem += 1;
203                    } else {
204                        let i = pa_indx + 1;
205
206                        if i == 60 {
207                            warn!("Too many holes in the physical address space, giving up.");
208                            full = true;
209                        } else {
210                            pa_indx += 2;
211                            phys_avail[i] = pa;
212                            phys_avail[pa_indx] = pa + PAGE_SIZE.get();
213                            physmem += 1;
214                        }
215                    }
216                } else {
217                    todo!()
218                }
219            }
220
221            if pa == dump_avail[da_indx] {
222                dump_avail[da_indx] = pa + PAGE_SIZE.get();
223            } else if (da_indx + 1) != 60 {
224                dump_avail[da_indx + 1] = pa;
225                dump_avail[da_indx + 2] = pa + PAGE_SIZE.get();
226                da_indx += 2;
227            }
228
229            if full {
230                break;
231            }
232        }
233    }
234
235    if mi.memtest != 0 {
236        todo!()
237    }
238
239    // TODO: What is this?
240    let msgbuf_size = param1.msgbuf_size().next_multiple_of(PAGE_SIZE.get());
241
242    #[allow(clippy::while_immutable_condition)] // TODO: Remove this once implement below todo.
243    while phys_avail[pa_indx] <= (phys_avail[pa_indx - 1] + PAGE_SIZE.get() + msgbuf_size) {
244        todo!()
245    }
246
247    mi.end_page = phys_avail[pa_indx] >> PAGE_SHIFT;
248    phys_avail[pa_indx] -= msgbuf_size;
249
250    // TODO: Set msgbufp and validate DMEM addresses.
251    // TODO: Why Orbis skip the first page?
252    let mut pa = String::with_capacity(0x2000);
253    let mut da = String::with_capacity(0x2000);
254
255    format_map(&phys_avail, pa_indx - 1, &mut pa);
256    format_map(&dump_avail, da_indx - 1, &mut da);
257
258    info!(
259        concat!(
260            "Available physical memory populated.\n",
261            "Maxmem    : {:#x}\n",
262            "physmem   : {}\n",
263            "phys_avail:",
264            "{}\n",
265            "dump_avail:",
266            "{}"
267        ),
268        mi.end_page, physmem, pa, da
269    );
270
271    drop(da);
272    drop(pa);
273
274    // Run sysinit vector for subsystem. The Orbis use linker to put all sysinit functions in a list
275    // then loop the list to execute all of it. We manually execute those functions instead for
276    // readability. This also allow us to pass data from one function to another function. See
277    // mi_startup function on the Orbis for a reference.
278    let pmgr = ProcMgr::new();
279    let (vm, uma) = init_vm(phys_avail, &dmem);
280
281    setup.set_uma(uma); // 161 on PS4 11.00.
282
283    SetupResult { pmgr, vm }
284}
285
286fn run(sr: SetupResult) -> ! {
287    // Activate stage 2 heap.
288    info!("Activating stage 2 heap.");
289
290    unsafe { KERNEL_HEAP.activate_stage2(sr.vm) };
291
292    // Run remaining sysinit vector.
293    create_init(&sr); // 659 on PS4 11.00.
294    swapper(&sr); // 1119 on PS4 11.00.
295}
296
297/// See `getmemsize` on the Orbis for a reference.
298///
299/// # Reference offsets
300/// | Version | Offset |
301/// |---------|--------|
302/// |PS4 11.00|0x25CF00|
303fn load_memory_map() -> MemoryInfo {
304    // TODO: Some of the logic around here are very hard to understand.
305    let mut physmap = [0usize; 60];
306    let mut last = 0usize;
307    let memory_map = match boot_env() {
308        BootEnv::Vm(v) => v.memory_map.as_slice(),
309    };
310
311    'top: for m in memory_map {
312        // We only interested in RAM.
313        match m.ty {
314            MapType::None => break,
315            MapType::Ram => (),
316            MapType::Reserved => continue,
317        }
318
319        // TODO: This should be possible only when booting from BIOS.
320        if m.len == 0 {
321            break;
322        }
323
324        // Check if we need to insert before the previous entries.
325        let mut insert_idx = last + 2;
326        let mut j = 0usize;
327
328        while j <= last {
329            if m.base < physmap[j + 1] {
330                // Check if end address overlapped.
331                if m.base + m.len > physmap[j] {
332                    warn!("Overlapping memory regions, ignoring second region.");
333                    continue 'top;
334                }
335
336                insert_idx = j;
337                break;
338            }
339
340            j += 2;
341        }
342
343        // Check if end address is the start address of the next entry. If yes we just change
344        // base address of it to increase its size.
345        if insert_idx <= last && m.base + m.len == physmap[insert_idx] {
346            physmap[insert_idx] = m.base;
347            continue;
348        }
349
350        // Check if start address is the end address of the previous entry. If yes we just
351        // increase the size of previous entry.
352        if insert_idx > 0 && m.base == physmap[insert_idx - 1] {
353            physmap[insert_idx - 1] = m.base + m.len;
354            continue;
355        }
356
357        last += 2;
358
359        if last == physmap.len() {
360            warn!("Too many segments in the physical address map, giving up.");
361            break;
362        }
363
364        // This loop does not make sense on the Orbis. It seems like if this loop once
365        // entered it will never exit.
366        #[allow(clippy::while_immutable_condition)]
367        while insert_idx < last {
368            todo!()
369        }
370
371        physmap[insert_idx] = m.base;
372        physmap[insert_idx + 1] = m.base + m.len;
373    }
374
375    // Check if bootloader provide us a memory map. The Orbis will check if
376    // preload_search_info() return null but we can't do that since we use a static size array
377    // to pass this information.
378    if physmap[1] == 0 {
379        panic!("no memory map provided to the kernel");
380    }
381
382    // Get initial memory size and BIOS boot area.
383    let mut initial_memory_size = 0;
384    let mut boot_area = None;
385
386    for i in (0..=last).step_by(2) {
387        // Check if BIOS boot area.
388        if physmap[i] == 0 {
389            // TODO: Why 1024?
390            boot_area = Some(physmap[i + 1] / 1024);
391        }
392
393        // Add to initial memory size.
394        let start = physmap[i].next_multiple_of(PAGE_SIZE.get());
395        let end = physmap[i + 1] & !PAGE_MASK.get();
396
397        initial_memory_size += end.saturating_sub(start);
398    }
399
400    // Check if we have boot area to start secondary CPU.
401    let boot_area = match boot_area {
402        Some(v) => v,
403        None => panic!("no boot area provided to the kernel"),
404    };
405
406    // TODO: This seems like it is assume the first physmap always a boot area. The problem is
407    // what is the point of the logic on the above to find boot_area?
408    let boot_info = adjust_boot_area(physmap[1] / 1024);
409
410    physmap[1] = boot_info.page_tables;
411
412    // Get end page.
413    let mut end_page = physmap[last + 1] >> PAGE_SHIFT;
414    let config = config();
415
416    if let Some(v) = config.env("hw.physmem") {
417        end_page = min(v.parse::<usize>().unwrap() >> PAGE_SHIFT, end_page);
418    }
419
420    // Get memtest flags.
421    let memtest = config
422        .env("hw.memtest.tests")
423        .map(|v| v.parse().unwrap())
424        .unwrap_or(1);
425
426    // TODO: There is some unknown calls here.
427    let mut unk = 0;
428
429    for i in (0..=last).rev().step_by(2) {
430        unk = (unk + physmap[i + 1]) - physmap[i];
431    }
432
433    // TODO: Figure out the name of this variable.
434    let mut unk = u32::from((unk >> 33) != 0);
435
436    // TODO: We probably want to remove this CPU model checks but better to keep it for now so we
437    // don't have a headache when the other places rely on the effect of this check.
438    #[cfg(target_arch = "x86_64")]
439    let cpu_ok = (arch().cpu.cpu_id & 0xffffff80) == 0x740f00;
440    #[cfg(not(target_arch = "x86_64"))]
441    let cpu_ok = true;
442
443    if cpu_ok && !config.dipsw(Dipsw::Unk140) && !config.dipsw(Dipsw::Unk146) {
444        unk |= 2;
445    }
446
447    // The call to pmap_bootstrap has been moved to setup_main_cpu().
448    let (dcons_addr, dcons_size) = match (config.env("dcons.addr"), config.env("dcons.size")) {
449        (Some(addr), Some(size)) => (addr.parse().unwrap(), size.parse().unwrap()),
450        _ => (0, 0),
451    };
452
453    // The call to initialize_dmem is moved to the caller of this function.
454    MemoryInfo {
455        physmap,
456        physmap_last: last,
457        boot_area,
458        boot_info,
459        dcons_addr,
460        dcons_size,
461        initial_memory_size,
462        end_page,
463        unk,
464        memtest,
465    }
466}
467
468/// See `mp_bootaddress` on the Orbis for a reference.
469///
470/// # Reference offsets
471/// | Version | Offset |
472/// |---------|--------|
473/// |PS4 11.00|0x1B9D20|
474fn adjust_boot_area(original: usize) -> BootInfo {
475    // TODO: Most logic here does not make sense.
476    let need = arch().secondary_start.len();
477    let addr = (original * 1024) & !PAGE_MASK.get();
478
479    // TODO: What is this?
480    let addr = if need <= ((original * 1024) & 0xC00) {
481        addr
482    } else {
483        addr - PAGE_SIZE.get()
484    };
485
486    BootInfo {
487        addr,
488        page_tables: addr - (PAGE_SIZE.get() * 3),
489    }
490}
491
492/// See `vm_mem_init` function on the Orbis for a reference.
493///
494/// # Reference offsets
495/// | Version | Offset |
496/// |---------|--------|
497/// |PS4 11.00|0x39A390|
498fn init_vm(phys_avail: [usize; 61], dmem: &Dmem) -> (&'static Vm, Arc<Uma>) {
499    // TODO: Get ma from parse_srat.
500    let vm = Vm::new(phys_avail, None, dmem).unwrap();
501
502    // Initialize UMA.
503    (vm, Uma::new(vm))
504}
505
506/// See `create_init` function on the Orbis for a reference.
507///
508/// # Reference offsets
509/// | Version | Offset |
510/// |---------|--------|
511/// |PS4 11.00|0x2BEF30|
512fn create_init(sr: &SetupResult) {
513    let abi = Arc::new(Ps4Abi);
514    let flags = Fork::CopyFd | Fork::CreateProcess;
515
516    info!("Creating init process.");
517
518    sr.pmgr.fork(abi, flags).unwrap();
519
520    todo!()
521}
522
523/// See `scheduler` function on the Orbis for a reference.
524///
525/// # Reference offsets
526/// | Version | Offset |
527/// |---------|--------|
528/// |PS4 11.00|0x437E00|
529fn swapper(sr: &SetupResult) -> ! {
530    // TODO: Subscribe to "system_suspend_phase2_pre_sync" and "system_resume_phase2" event.
531    loop {
532        // TODO: Implement a call to vm_page_count_min().
533        let procs = sr.pmgr.list();
534
535        if procs.len() == 0 {
536            // TODO: The PS4 check for some value for non-zero but it seems like that value always
537            // zero.
538            sleep();
539            continue;
540        }
541
542        todo!();
543    }
544}
545
546/// Implementation of [`ProcAbi`] for kernel process.
547///
548/// See `null_sysvec` on the PS4 for a reference.
549struct Proc0Abi;
550
551impl ProcAbi for Proc0Abi {
552    /// See `null_fetch_syscall_args` on the PS4 for a reference.
553    fn syscall_handler(&self) {
554        unimplemented!()
555    }
556}
557
558/// Result of [`setup()`].
559struct SetupResult {
560    pmgr: Arc<ProcMgr>,
561    vm: &'static Vm,
562}
563
564/// Contains memory information populated from memory map.
565struct MemoryInfo {
566    physmap: [usize; 60],
567    physmap_last: usize,
568    boot_area: usize,
569    boot_info: BootInfo,
570    dcons_addr: usize,
571    dcons_size: usize,
572    initial_memory_size: usize,
573    end_page: usize,
574    unk: u32, // Seems like the only possible values are 0 - 3.
575    memtest: u64,
576}
577
578/// Contains information for memory to boot a secondary CPU.
579struct BootInfo {
580    addr: usize,
581    page_tables: usize,
582}
583
584// SAFETY: PRIMITIVE_HEAP is a mutable static so it valid for reads and writes. This will be safe as
585// long as no one access PRIMITIVE_HEAP.
586#[allow(dead_code)]
587#[cfg_attr(target_os = "none", global_allocator)]
588static KERNEL_HEAP: KernelHeap = unsafe { KernelHeap::new(&raw mut PRIMITIVE_HEAP) };
589static mut PRIMITIVE_HEAP: [u8; 1024 * 1024 * 32] = [0; _];
590
591// We need virtual address space that large enough for all physical addresses to simplify the VM
592// system.
593#[cfg(not(target_pointer_width = "64"))]
594compile_error!("Obliteration can only be used with 64-bit CPU");