Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use core::alloc::Layout;
9use core::mem::MaybeUninit;
10use core::ops::Range;
11use core::slice;
12
13use kmem::{PhysicalAddress, VirtualAddress};
14use loader_api::{BootInfo, MemoryRegion, MemoryRegionKind, MemoryRegions, TlsTemplate};
15
16use crate::arch;
17use crate::frame_alloc::FrameAllocator;
18
19#[expect(clippy::too_many_arguments, reason = "")]
20pub fn prepare_boot_info(
21 mut frame_alloc: FrameAllocator,
22 physical_address_offset: VirtualAddress,
23 physical_memory_map: Range<VirtualAddress>,
24 kernel_virt: Range<VirtualAddress>,
25 maybe_tls_template: Option<TlsTemplate>,
26 loader_phys: Range<PhysicalAddress>,
27 kernel_phys: Range<PhysicalAddress>,
28 fdt_phys: Range<PhysicalAddress>,
29 hart_mask: usize,
30 rng_seed: [u8; 32],
31) -> crate::Result<*mut BootInfo> {
32 let frame = frame_alloc.allocate_contiguous_zeroed(
33 Layout::from_size_align(arch::PAGE_SIZE, arch::PAGE_SIZE).unwrap(),
34 arch::KERNEL_ASPACE_BASE,
35 )?;
36 let page = physical_address_offset.add(frame.get());
37
38 let memory_regions = init_boot_info_memory_regions(
39 page,
40 frame_alloc,
41 fdt_phys,
42 loader_phys,
43 kernel_phys.clone(),
44 );
45
46 let mut boot_info = BootInfo::new(memory_regions);
47 boot_info.physical_address_offset = physical_address_offset;
48 boot_info.physical_memory_map = physical_memory_map;
49 boot_info.tls_template = maybe_tls_template;
50 boot_info.kernel_virt = kernel_virt;
51 boot_info.kernel_phys = kernel_phys;
52 boot_info.cpu_mask = hart_mask;
53 boot_info.rng_seed = rng_seed;
54
55 #[expect(
56 clippy::cast_ptr_alignment,
57 reason = "`page` is actually page aligned, so this is perfectly fine"
58 )]
59 let boot_info_ptr = page.as_mut_ptr().cast::<BootInfo>();
60 // Safety: we just allocated the boot info frame
61 unsafe { boot_info_ptr.write(boot_info) }
62
63 Ok(boot_info_ptr)
64}
65
66fn init_boot_info_memory_regions(
67 page: VirtualAddress,
68 frame_alloc: FrameAllocator,
69 fdt_phys: Range<PhysicalAddress>,
70 loader_phys: Range<PhysicalAddress>,
71 kernel_phys: Range<PhysicalAddress>,
72) -> MemoryRegions {
73 // Safety: we just allocated a whole frame for the boot info
74 let regions: &mut [MaybeUninit<MemoryRegion>] = unsafe {
75 let base = page.add(size_of::<BootInfo>());
76 let len = (arch::PAGE_SIZE - size_of::<BootInfo>()) / size_of::<MemoryRegion>();
77
78 #[expect(
79 clippy::cast_ptr_alignment,
80 reason = "`page` is actually page aligned, so this is perfectly fine"
81 )]
82 slice::from_raw_parts_mut(base.as_mut_ptr().cast::<MaybeUninit<MemoryRegion>>(), len)
83 };
84
85 let mut len = 0;
86 let mut push_region = |region: MemoryRegion| {
87 debug_assert!(!region.range.is_empty());
88 regions[len].write(region);
89 len += 1;
90 };
91
92 // Report the memory we consumed during startup as used.
93 for used_region in frame_alloc.used_regions() {
94 push_region(MemoryRegion {
95 range: used_region,
96 kind: MemoryRegionKind::Loader,
97 });
98 }
99
100 // Report the free regions as usable.
101 for free_region in frame_alloc.free_regions() {
102 push_region(MemoryRegion {
103 range: free_region,
104 kind: MemoryRegionKind::Usable,
105 });
106 }
107
108 // Most of the memory occupied by the loader is not needed once the kernel is running,
109 // but the kernel itself lies somewhere in the loader memory.
110 //
111 // We can still mark the range before and after the kernel as usable.
112 push_region(MemoryRegion {
113 range: loader_phys.start..kernel_phys.start,
114 kind: MemoryRegionKind::Usable,
115 });
116 push_region(MemoryRegion {
117 range: kernel_phys.end..loader_phys.end,
118 kind: MemoryRegionKind::Usable,
119 });
120
121 // Report the flattened device tree as a separate region.
122 push_region(MemoryRegion {
123 range: fdt_phys,
124 kind: MemoryRegionKind::FDT,
125 });
126
127 // Truncate the slice to include only initialized elements
128 // Safety: closure above ensures the slice up to len is valid
129 let regions = unsafe { regions[0..len].assume_init_mut() };
130
131 // Sort the memory regions by start address, we do this now in the loader
132 // because the BootInfo struct will be passed as a read-only static reference to the kernel.
133 regions.sort_unstable_by_key(|region| region.range.start);
134
135 MemoryRegions::from(regions)
136}