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