obkrnl/
main.rs

1#![no_std]
2#![cfg_attr(not(test), no_main)]
3
4use self::config::{Config, Dipsw, PAGE_MASK, PAGE_SHIFT, PAGE_SIZE};
5use self::context::{ContextSetup, arch, config, pmgr};
6use self::dmem::Dmem;
7use self::imgact::Ps4Abi;
8use self::malloc::KernelHeap;
9use self::proc::{Fork, Proc, ProcAbi, ProcMgr, Thread};
10use self::sched::sleep;
11use self::uma::Uma;
12use self::vm::Vm;
13use ::config::{BootEnv, MapType};
14use alloc::string::String;
15use alloc::sync::Arc;
16use core::cmp::min;
17use core::mem::zeroed;
18use humansize::{DECIMAL, SizeFormatter};
19use krt::{boot_env, info, warn};
20
21#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
22#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
23mod arch;
24mod config;
25mod context;
26mod dmem;
27mod event;
28mod imgact;
29mod imgfmt;
30mod lock;
31mod malloc;
32mod proc;
33mod sched;
34mod signal;
35mod subsystem;
36mod trap;
37mod uma;
38mod vm;
39
40extern crate alloc;
41
42/// This will be called by [`krt`] crate.
43///
44/// See Orbis kernel entry point for a reference.
45#[cfg_attr(target_os = "none", unsafe(no_mangle))]
46fn main(config: &'static ::config::Config) -> ! {
47    // SAFETY: This function has a lot of restrictions. See Context documentation for more details.
48    let config = Config::new(config);
49    let cpu = self::arch::identify_cpu();
50    let hw = match boot_env() {
51        BootEnv::Vm(vm) => vm.hypervisor(),
52    };
53
54    info!(
55        concat!(
56            "Starting Obliteration Kernel on {}.\n",
57            "cpu_vendor                 : {} × {}\n",
58            "cpu_id                     : {:#x}\n",
59            "boot_parameter.idps.product: {}"
60        ),
61        String::from_utf8_lossy(hw),
62        cpu.cpu_vendor,
63        config.max_cpu(),
64        cpu.cpu_id,
65        config.idps().product
66    );
67
68    // Setup the CPU after the first print to let the bootloader developer know (some of) their code
69    // are working.
70    let arch = unsafe { self::arch::setup_main_cpu(cpu) };
71
72    // Setup proc0 to represent the kernel.
73    let proc0 = Proc::new_bare(Arc::new(Proc0Abi));
74
75    // Setup thread0 to represent this thread.
76    let proc0 = Arc::new(proc0);
77    let thread0 = Thread::new_bare(proc0);
78
79    // Activate CPU context.
80    let thread0 = Arc::new(thread0);
81
82    unsafe { self::context::run_with_context(config, arch, 0, thread0, setup, run) };
83}
84
85fn setup() -> ContextSetup {
86    // Initialize physical memory.
87    let mut mi = load_memory_map();
88    let dmem = Dmem::new(&mut mi);
89
90    info!(
91        concat!("DMEM Mode  : {}\n", "DMEM Config: {}"),
92        dmem.mode(),
93        dmem.config().name
94    );
95
96    // Run sysinit vector for subsystem. The Orbis use linker to put all sysinit functions in a list
97    // then loop the list to execute all of it. We manually execute those functions instead for
98    // readability. This also allow us to pass data from one function to another function. See
99    // mi_startup function on the Orbis for a reference.
100    let procs = ProcMgr::new();
101    let uma = init_vm(&mi); // 161 on PS4 11.00.
102
103    ContextSetup { uma, pmgr: procs }
104}
105
106fn run() -> ! {
107    // Activate stage 2 heap.
108    info!("Activating stage 2 heap.");
109
110    unsafe { KERNEL_HEAP.activate_stage2() };
111
112    // Run remaining sysinit vector.
113    create_init(); // 659 on PS4 11.00.
114    swapper(); // 1119 on PS4 11.00.
115}
116
117/// See `getmemsize` on the Orbis for a reference.
118///
119/// # Reference offsets
120/// | Version | Offset |
121/// |---------|--------|
122/// |PS4 11.00|0x25CF00|
123fn load_memory_map() -> MemoryInfo {
124    // TODO: Some of the logic around here are very hard to understand.
125    let mut physmap = [0u64; 60];
126    let mut last = 0usize;
127    let map = match boot_env() {
128        BootEnv::Vm(v) => v.memory_map.as_slice(),
129    };
130
131    'top: for m in map {
132        // We only interested in RAM.
133        match m.ty {
134            MapType::None => break,
135            MapType::Ram => (),
136            MapType::Reserved => continue,
137        }
138
139        // TODO: This should be possible only when booting from BIOS.
140        if m.len == 0 {
141            break;
142        }
143
144        // Check if we need to insert before the previous entries.
145        let mut insert_idx = last + 2;
146        let mut j = 0usize;
147
148        while j <= last {
149            if m.base < physmap[j + 1] {
150                // Check if end address overlapped.
151                if m.base + m.len > physmap[j] {
152                    warn!("Overlapping memory regions, ignoring second region.");
153                    continue 'top;
154                }
155
156                insert_idx = j;
157                break;
158            }
159
160            j += 2;
161        }
162
163        // Check if end address is the start address of the next entry. If yes we just change
164        // base address of it to increase its size.
165        if insert_idx <= last && m.base + m.len == physmap[insert_idx] {
166            physmap[insert_idx] = m.base;
167            continue;
168        }
169
170        // Check if start address is the end address of the previous entry. If yes we just
171        // increase the size of previous entry.
172        if insert_idx > 0 && m.base == physmap[insert_idx - 1] {
173            physmap[insert_idx - 1] = m.base + m.len;
174            continue;
175        }
176
177        last += 2;
178
179        if last == physmap.len() {
180            warn!("Too many segments in the physical address map, giving up.");
181            break;
182        }
183
184        // This loop does not make sense on the Orbis. It seems like if this loop once
185        // entered it will never exit.
186        #[allow(clippy::while_immutable_condition)]
187        while insert_idx < last {
188            todo!()
189        }
190
191        physmap[insert_idx] = m.base;
192        physmap[insert_idx + 1] = m.base + m.len;
193    }
194
195    // Check if bootloader provide us a memory map. The Orbis will check if
196    // preload_search_info() return null but we can't do that since we use a static size array
197    // to pass this information.
198    if physmap[1] == 0 {
199        panic!("no memory map provided to the kernel");
200    }
201
202    // Get initial memory size and BIOS boot area.
203    let page_size = PAGE_SIZE.get().try_into().unwrap();
204    let page_mask = !u64::try_from(PAGE_MASK.get()).unwrap();
205    let mut initial_memory_size = 0;
206    let mut boot_area = None;
207
208    for i in (0..=last).step_by(2) {
209        // Check if BIOS boot area.
210        if physmap[i] == 0 {
211            // TODO: Why 1024?
212            boot_area = Some(physmap[i + 1] / 1024);
213        }
214
215        // Add to initial memory size.
216        let start = physmap[i].next_multiple_of(page_size);
217        let end = physmap[i + 1] & page_mask;
218
219        initial_memory_size += end.saturating_sub(start);
220    }
221
222    // Check if we have boot area to start secondary CPU.
223    let boot_area = match boot_area {
224        Some(v) => v,
225        None => panic!("no boot area provided to the kernel"),
226    };
227
228    // TODO: This seems like it is assume the first physmap always a boot area. The problem is
229    // what is the point of the logic on the above to find boot_area?
230    let boot_info = adjust_boot_area(physmap[1] / 1024);
231
232    physmap[1] = boot_info.page_tables;
233
234    // Get end page.
235    let mut end_page = physmap[last + 1] >> PAGE_SHIFT;
236    let config = config();
237
238    if let Some(v) = config.env("hw.physmem") {
239        end_page = min(v.parse::<u64>().unwrap() >> PAGE_SHIFT, end_page);
240    }
241
242    // TODO: There is some unknown calls here.
243    let mut unk = 0;
244
245    for i in (0..=last).rev().step_by(2) {
246        unk = (unk + physmap[i + 1]) - physmap[i];
247    }
248
249    // TODO: Figure out the name of this variable.
250    let mut unk = u32::from((unk >> 33) != 0);
251
252    // TODO: We probably want to remove this CPU model checks but better to keep it for now so we
253    // don't have a headache when the other places rely on the effect of this check.
254    #[cfg(target_arch = "x86_64")]
255    let cpu_ok = (arch().cpu.cpu_id & 0xffffff80) == 0x740f00;
256    #[cfg(not(target_arch = "x86_64"))]
257    let cpu_ok = true;
258
259    if cpu_ok && !config.dipsw(Dipsw::Unk140) && !config.dipsw(Dipsw::Unk146) {
260        unk |= 2;
261    }
262
263    load_pmap();
264
265    // The call to initialize_dmem is moved to the caller of this function.
266    MemoryInfo {
267        physmap,
268        physmap_last: last,
269        boot_area,
270        boot_info,
271        initial_memory_size,
272        end_page,
273        unk,
274    }
275}
276
277/// See `mp_bootaddress` on the Orbis for a reference.
278///
279/// # Reference offsets
280/// | Version | Offset |
281/// |---------|--------|
282/// |PS4 11.00|0x1B9D20|
283fn adjust_boot_area(original: u64) -> BootInfo {
284    // TODO: Most logic here does not make sense.
285    let page_size = u64::try_from(PAGE_SIZE.get()).unwrap();
286    let page_mask = !u64::try_from(PAGE_MASK.get()).unwrap();
287    let need = u64::try_from(arch().secondary_start.len()).unwrap();
288    let addr = (original * 1024) & page_mask;
289
290    // TODO: What is this?
291    let addr = if need <= ((original * 1024) & 0xC00) {
292        addr
293    } else {
294        addr - page_size
295    };
296
297    BootInfo {
298        addr,
299        page_tables: addr - (page_size * 3),
300    }
301}
302
303/// See `pmap_bootstrap` on the Orbis for a reference.
304///
305/// # Reference offsets
306/// | Version | Offset |
307/// |---------|--------|
308/// |PS4 11.00|0x1127C0|
309fn load_pmap() {
310    let config = config();
311
312    if config.is_allow_disabling_aslr() && config.dipsw(Dipsw::DisabledKaslr) {
313        todo!()
314    } else {
315        // TODO: There are a lot of unknown variables here so we skip implementing this until we
316        // run into the code that using them.
317    }
318}
319
320/// See `vm_mem_init` function on the Orbis for a reference.
321///
322/// # Reference offsets
323/// | Version | Offset |
324/// |---------|--------|
325/// |PS4 11.00|0x39A390|
326fn init_vm(mi: &MemoryInfo) -> Arc<Uma> {
327    // Initialize VM.
328    let vm = unsafe { Vm::new(mi).unwrap() };
329
330    info!(
331        concat!(
332            "initial_memory_size: {} ({})\n",
333            "basemem            : {:#x}\n",
334            "boot_address       : {:#x}\n",
335            "mptramp_pagetables : {:#x}\n",
336            "Maxmem             : {:#x}"
337        ),
338        vm.initial_memory_size(),
339        SizeFormatter::new(vm.initial_memory_size(), DECIMAL),
340        vm.boot_area(),
341        vm.boot_addr(),
342        vm.boot_tables(),
343        vm.end_page()
344    );
345
346    // Initialize UMA.
347    Uma::new(vm)
348}
349
350/// See `create_init` function on the Orbis for a reference.
351///
352/// # Reference offsets
353/// | Version | Offset |
354/// |---------|--------|
355/// |PS4 11.00|0x2BEF30|
356fn create_init() {
357    let pmgr = pmgr().unwrap();
358    let abi = Arc::new(Ps4Abi);
359    let flags = Fork::CopyFd | Fork::CreateProcess;
360
361    pmgr.fork(abi, flags).unwrap();
362
363    todo!()
364}
365
366/// See `scheduler` function on the Orbis for a reference.
367///
368/// # Reference offsets
369/// | Version | Offset |
370/// |---------|--------|
371/// |PS4 11.00|0x437E00|
372fn swapper() -> ! {
373    // TODO: Subscribe to "system_suspend_phase2_pre_sync" and "system_resume_phase2" event.
374    let procs = pmgr().unwrap();
375
376    loop {
377        // TODO: Implement a call to vm_page_count_min().
378        let procs = procs.list();
379
380        if procs.len() == 0 {
381            // TODO: The PS4 check for some value for non-zero but it seems like that value always
382            // zero.
383            sleep();
384            continue;
385        }
386
387        todo!();
388    }
389}
390
391/// Implementation of [`ProcAbi`] for kernel process.
392///
393/// See `null_sysvec` on the PS4 for a reference.
394struct Proc0Abi;
395
396impl ProcAbi for Proc0Abi {
397    /// See `null_fetch_syscall_args` on the PS4 for a reference.
398    fn syscall_handler(&self) {
399        unimplemented!()
400    }
401}
402
403/// Contains memory information populated from memory map.
404struct MemoryInfo {
405    physmap: [u64; 60],
406    physmap_last: usize,
407    boot_area: u64,
408    boot_info: BootInfo,
409    initial_memory_size: u64,
410    end_page: u64,
411    unk: u32, // Seems like the only possible values are 0 - 3.
412}
413
414/// Contains information for memory to boot a secondary CPU.
415struct BootInfo {
416    addr: u64,
417    page_tables: u64,
418}
419
420// SAFETY: PRIMITIVE_HEAP is a mutable static so it valid for reads and writes. This will be safe as
421// long as no one access PRIMITIVE_HEAP.
422#[allow(dead_code)]
423#[cfg_attr(target_os = "none", global_allocator)]
424static KERNEL_HEAP: KernelHeap = unsafe { KernelHeap::new(&raw mut PRIMITIVE_HEAP) };
425static mut PRIMITIVE_HEAP: [u8; 1024 * 1024] = unsafe { zeroed() };