#![no_std] #![cfg_attr(any(target_arch = "riscv64", target_os = "uefi"), no_main)] extern crate alloc; #[cfg(any(target_arch = "riscv64", target_os = "uefi"))] #[macro_use] extern crate uefi_std as std; use alloc::{format, string::String, vec::Vec}; use core::{ convert::TryFrom, fmt::{self, Write}, mem, ptr, slice, str, }; use redoxfs::{Disk, Node, TreeData}; use self::arch::{paging_create, paging_framebuffer}; use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode, TextColor}; #[macro_use] mod os; mod arch; mod editor; mod logger; mod serial_16550; const KIBI: usize = 1024; const MIBI: usize = KIBI * KIBI; //TODO: allocate this in a more reasonable manner static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry { base: 0, size: 0, kind: OsMemoryKind::Null, }; 1024]; static mut AREAS_LEN: usize = 0; pub fn area_add(area: OsMemoryEntry) { #[allow(static_mut_refs)] unsafe { for existing_area in &mut AREAS[0..AREAS_LEN] { if existing_area.kind == area.kind { if existing_area.base.unchecked_add(existing_area.size) == area.base { existing_area.size += area.size; return; } if area.base.unchecked_add(area.size) == existing_area.base { existing_area.size += area.size; existing_area.base = area.base; return; } } } *AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area; AREAS_LEN += 1; } } pub static mut KERNEL_64BIT: bool = false; pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None; struct SliceWriter<'a> { slice: &'a mut [u8], i: usize, } impl<'a> Write for SliceWriter<'a> { fn write_str(&mut self, s: &str) -> fmt::Result { for b in s.bytes() { if let Some(slice_b) = self.slice.get_mut(self.i) { *slice_b = b; self.i += 1; } else { return Err(fmt::Error); } } Ok(()) } } #[allow(dead_code)] #[derive(Debug)] #[repr(C, packed(8))] pub struct KernelArgs { kernel_base: u64, kernel_size: u64, stack_base: u64, stack_size: u64, env_base: u64, env_size: u64, /// The base pointer to the saved RSDP. /// /// This field can be NULL, and if so, the system has not booted with UEFI or in some other way /// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS /// memory instead. On UEFI systems, searching is not guaranteed to actually work though. acpi_rsdp_base: u64, /// The size of the RSDP region. acpi_rsdp_size: u64, areas_base: u64, areas_size: u64, bootstrap_base: u64, bootstrap_size: u64, } fn select_mode( os: &impl Os, output_i: usize, live: &mut bool, edit_env: &mut bool, ) -> Option { const DEFAULT_WIDTH: u32 = 1280; const DEFAULT_HEIGHT: u32 = 720; const AUTOBOOT_SECONDS: usize = 5; let mut all_modes = Vec::new(); for mode in os.video_modes(output_i) { all_modes.push(mode); } if all_modes.is_empty() { return None; } all_modes.sort_by(|a, b| (b.width * b.height).cmp(&(a.width * a.height))); fn categorize(width: u32, height: u32) -> u8 { match (width, height) { (w, _) if w >= 3840 => 0, (w, _) if w >= 2560 => 1, (1920, 1080) => 2, (1280, 720) => 3, (1024, 768) => 4, _ => 5, } } let category_labels = ["4K", "2.5K", "FullHD", "HD", "1024x768", "Other"]; struct MenuEntry { label: String, mode: Option, is_more: bool, } let mut entries: Vec = Vec::new(); for cat in 0..5 { let mut cat_modes: Vec<&OsVideoMode> = all_modes .iter() .filter(|m| categorize(m.width, m.height) == cat) .collect(); if cat_modes.is_empty() { continue; } cat_modes.sort_by(|a, b| (b.width * b.height).cmp(&(a.width * a.height))); for m in cat_modes { let label = if cat == 3 || cat == 2 || cat == 4 { format!("{}", category_labels[cat as usize]) } else { format!("{} ({}x{})", category_labels[cat as usize], m.width, m.height) }; entries.push(MenuEntry { label, mode: Some(*m), is_more: false, }); } } let obscure_count = all_modes .iter() .filter(|m| categorize(m.width, m.height) == 5) .count(); if obscure_count > 0 { entries.push(MenuEntry { label: format!("More resolutions ({} more)", obscure_count), mode: None, is_more: true, }); } if entries.is_empty() { return None; } // Pick the initial highlight: prefer the Red Bear default resolution // (1280x720); if the panel does not offer it, fall back to the display's // EDID-preferred mode (best_resolution) so real hardware still boots at its // native resolution; only then fall back to entries[0] (the largest mode). let default_idx = entries .iter() .position(|e| { e.mode .map(|m| m.width == DEFAULT_WIDTH && m.height == DEFAULT_HEIGHT) .unwrap_or(false) }) .or_else(|| { os.best_resolution(output_i).and_then(|(bw, bh)| { entries.iter().position(|e| { e.mode.map(|m| m.width == bw && m.height == bh).unwrap_or(false) }) }) }) .unwrap_or(0); os.set_text_color(TextColor::Cyan); println!("Output {}", output_i); os.set_text_color(TextColor::Default); let selected_mode = |entries: &[MenuEntry], idx: usize| -> Option { entries[idx].mode }; let mut selected = default_idx; let mut countdown = AUTOBOOT_SECONDS; let mut countdown_active = true; // Capture the countdown line's row now, so the in-loop countdown updates it // in place. Deriving it from list_y with a fixed offset landed one row too // low (on the "Use Up/Down" line), leaving this header as a stale second // "Autobooting in X seconds" copy above the live one. let countdown_y = os.get_text_position().1; os.set_text_color(TextColor::Yellow); println!( " Autobooting in {} seconds (press any key to cancel)", countdown ); os.set_text_color(TextColor::Default); println!(" Use Up/Down to navigate, Enter to select."); let live_y = { if *live { println!(" [L] Live mode: ON"); } else { println!(" [L] Live mode: OFF"); } os.get_text_position().1 }; println!(" [E] Edit boot environment"); println!(); let (list_x, list_y) = os.get_text_position(); loop { for (i, entry) in entries.iter().enumerate() { os.set_text_position(list_x, list_y + i); os.set_text_highlight(i == selected); let marker = if i == selected { ">" } else { " " }; let default_tag = if entries[i] .mode .map(|m| m.width == DEFAULT_WIDTH && m.height == DEFAULT_HEIGHT) .unwrap_or(false) { " [DEFAULT]" } else { "" }; // Pad to a fixed width: the UEFI text console does not clear old // characters when a shorter line overwrites a longer one at the // same position, so without padding the menu garbles as the // highlight/marker/tag change length between frames. print!("{:<50}", format!("{} {}{}", marker, entry.label, default_tag)); } os.set_text_highlight(false); os.set_text_position(0, countdown_y); if countdown_active { os.set_text_color(TextColor::Yellow); let msg = format!( " Autobooting in {} seconds (press any key to cancel)", countdown ); print!("{:<70}", msg); os.set_text_color(TextColor::Default); } else { print!("{:<70}", " Manual selection - press Enter to boot"); } match if countdown_active { os.get_key_timeout(1000) } else { os.get_key() } { OsKey::Timeout => { if countdown_active { if countdown == 0 { if let Some(mode) = selected_mode(&entries, selected) { return Some(mode); } countdown_active = false; } else { countdown -= 1; } } } OsKey::Up => { countdown_active = false; if selected > 0 { selected -= 1; } } OsKey::Down => { countdown_active = false; if selected + 1 < entries.len() { selected += 1; } } OsKey::Enter => { if entries[selected].is_more { return select_obscure(os, &all_modes, live, edit_env); } if let Some(mode) = selected_mode(&entries, selected) { return Some(mode); } } OsKey::Char('l') => { countdown_active = false; *live = !*live; os.set_text_position(0, live_y); let msg = if *live { " [L] Live mode: ON" } else { " [L] Live mode: OFF" }; print!("{:<40}", msg); } OsKey::Char('e') => { countdown_active = false; if let Some(mode) = selected_mode(&entries, selected) { *edit_env = true; return Some(mode); } } _ => { countdown_active = false; } } } } fn select_obscure( os: &impl Os, all_modes: &[OsVideoMode], live: &mut bool, edit_env: &mut bool, ) -> Option { let obscure: Vec<&OsVideoMode> = all_modes .iter() .filter(|m| match (m.width, m.height) { (w, _) if w >= 3840 => false, (w, _) if w >= 2560 => false, (1920, 1080) => false, (1280, 720) => false, (1024, 768) => false, _ => true, }) .collect(); if obscure.is_empty() { return None; } os.clear_text(); os.set_text_color(TextColor::Cyan); println!("More Resolutions"); os.set_text_color(TextColor::Default); println!(" Use Up/Down to navigate, Enter to select, Backspace to go back"); println!(); let (_, list_y) = os.get_text_position(); let mut selected = 0; loop { for (i, mode) in obscure.iter().enumerate() { os.set_text_position(0, list_y + i); os.set_text_highlight(i == selected); let marker = if i == selected { ">" } else { " " }; // Pad to a fixed width (see select_mode): prevents UEFI console // garbling as the highlight marker moves between rows. print!("{:<40}", format!("{} {:>4}x{:<4}", marker, mode.width, mode.height)); } os.set_text_highlight(false); match os.get_key() { OsKey::Up => { if selected > 0 { selected -= 1; } } OsKey::Down => { if selected + 1 < obscure.len() { selected += 1; } } OsKey::Backspace => { return None; } OsKey::Enter => { return Some(*obscure[selected]); } OsKey::Char('l') => { *live = !*live; } OsKey::Char('e') => { *edit_env = true; return Some(*obscure[selected]); } _ => {} } } } fn redoxfs(os: &O) -> (redoxfs::FileSystem, Option<&'static [u8]>) { let attempts = 10; for attempt in 0..=attempts { let mut password_opt = None; if attempt > 0 { print!("\rRedoxFS password ({}/{}): ", attempt, attempts); let mut password = String::new(); loop { match os.get_key() { OsKey::Backspace | OsKey::Delete => { if !password.is_empty() { print!("\x08 \x08"); password.pop(); } } OsKey::Char(c) => { print!("*"); password.push(c) } OsKey::Enter => break, _ => (), } } // Erase password information while os.get_text_position().0 > 0 { print!("\x08 \x08"); } if !password.is_empty() { password_opt = Some(password); } } match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) { Ok(fs) => { return ( fs, password_opt.map(|password| { // Copy password to page aligned memory let password_size = password.len(); let password_base = os.alloc_zeroed_page_aligned(password_size); area_add(OsMemoryEntry { base: password_base as u64, size: password_size as u64, kind: OsMemoryKind::Reserved, }); unsafe { ptr::copy(password.as_ptr(), password_base, password_size); slice::from_raw_parts(password_base, password_size) } }), ); } Err(err) => match err.errno { // Incorrect password, try again syscall::ENOKEY => (), _ => { panic!("Failed to open RedoxFS: {}", err); } }, } } panic!("RedoxFS out of unlock attempts"); } #[derive(PartialEq)] enum Filetype { Elf, Initfs, } fn load_to_memory( os: &O, fs: &mut redoxfs::FileSystem, path: &str, filetype: Filetype, ) -> &'static mut [u8] { fs.tx(|tx| { let mut node = None; for component in path.split('/') { node = Some( tx.find_node( node.map_or(redoxfs::TreePtr::root(), |node: TreeData| node.ptr()), component, ) .unwrap_or_else(|err| panic!("Failed to find {component}: {err}")), ); } let node = node.unwrap(); let size = node.data().size(); print!(" {}: {} 0/{} MiB", path, progress_bar(0, size), size / MIBI as u64); let ptr = os.alloc_zeroed_page_aligned(size as usize); if ptr.is_null() { panic!("Failed to allocate memory for {}", path); } let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; let mut i = 0; for chunk in slice.chunks_mut(MIBI) { print!("\r {}: {} {}/{} MiB", path, progress_bar(i, size), i / MIBI as u64, size / MIBI as u64); i += tx .read_node_inner(&node, i, chunk) .unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", path, err)) as u64; } os.set_text_color(TextColor::Green); println!("\r {}: {} {}/{} MiB", path, progress_bar(size, size), size / MIBI as u64, size / MIBI as u64); os.set_text_color(TextColor::Default); if filetype == Filetype::Elf { let magic = &slice[..4]; if magic != b"\x7FELF" { panic!("{} has invalid magic number {:#X?}", path, magic); } } else if filetype == Filetype::Initfs { let magic = &slice[..8]; if magic != b"RedoxFtw" { panic!("{} has invalid magic number {:#X?}", path, magic); } } Ok(slice) }) .unwrap_or_else(|err| { panic!( "RedoxFS transaction failed while loading `{}`: {}", path, err ) }) } fn elf_entry(data: &[u8]) -> (u64, bool) { match (data[4], data[5]) { // 32-bit, little endian (1, 1) => ( u32::from_le_bytes( <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), ) as u64, false, ), // 32-bit, big endian (1, 2) => ( u32::from_be_bytes( <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), ) as u64, false, ), // 64-bit, little endian (2, 1) => ( u64::from_le_bytes( <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), ), true, ), // 64-bit, big endian (2, 2) => ( u64::from_be_bytes( <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), ), true, ), (ei_class, ei_data) => { panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data); } } } // ASCII wordmark (figlet "standard"), pure ASCII so it renders identically on // the UEFI Unicode console and the VGA CP437 text console (which truncates each // char to a byte). Keep this ASCII-only. const WORDMARK: &[&str] = &[ " ____ _ ____ ___ ____", "| _ \\ ___ __| | __ ) ___ __ _ _ __ / _ \\/ ___|", "| |_) / _ \\/ _` | _ \\ / _ \\/ _` | '__| | | | \\___ \\", "| _ < __/ (_| | |_) | __/ (_| | | | |_| |___) |", "|_| \\_\\___|\\__,_|____/ \\___|\\__,_|_| \\___/|____/", ]; const WORDMARK_W: usize = 53; /// Return `text` centered within `width` columns, padded with spaces on both /// sides to exactly `width` characters (truncated if it does not fit). fn center_in(width: usize, text: &str) -> String { let tlen = text.chars().count(); if tlen >= width { return text.chars().take(width).collect(); } let pad = width - tlen; let left = pad / 2; let mut s = String::with_capacity(width); for _ in 0..left { s.push(' '); } s.push_str(text); for _ in 0..(pad - left) { s.push(' '); } s } /// A fixed-width ASCII progress bar like `[##### ]` for `cur`/`total`. fn progress_bar(cur: u64, total: u64) -> String { const BAR_W: usize = 20; let filled = if total == 0 { BAR_W } else { ((core::cmp::min(cur, total) as u128 * BAR_W as u128) / total as u128) as usize }; let mut s = String::with_capacity(BAR_W + 2); s.push('['); for i in 0..BAR_W { s.push(if i < filled { '#' } else { ' ' }); } s.push(']'); s } /// Clear the screen and draw the Red Bear OS bootloader header: a full-width /// frame around the centered ASCII wordmark, with a centered version/platform /// subtitle below. /// /// Called at the start of each phase (filesystem, resolution menu, loading) so /// every phase renders on a clean, consistently-branded screen and no phase /// overprints another. Previously only the second-and-later video outputs were /// cleared, so on a single display the menu was drawn under the header and the /// loading progress was drawn on top of the still-visible menu (e.g. "live:" /// landing on the "Autobooting" line). fn draw_header(os: &impl Os) { let w = os.text_columns().max(20); os.clear_text(); os.set_text_position(0, 0); os.set_text_color(TextColor::Red); if w >= WORDMARK_W + 2 { let inner = w - 2; let border = format!("+{}+", "=".repeat(inner)); println!("{}", border); for line in WORDMARK { println!("|{}|", center_in(inner, line)); } println!("{}", border); } else { // Narrow console: skip the wordmark, just center the name. println!("{}", center_in(w, "RedBear OS")); } os.set_text_color(TextColor::Cyan); let subtitle = format!("Bootloader {} ({})", env!("CARGO_PKG_VERSION"), os.name()); println!("{}", center_in(w, &subtitle)); os.set_text_color(TextColor::Default); println!(); } /// Print the RedoxFS identity line (uuid + size) below the current cursor. fn print_fs_line(fs: &redoxfs::FileSystem) { print!(" RedoxFS "); for i in 0..fs.header.uuid().len() { if i == 4 || i == 6 || i == 8 || i == 10 { print!("-"); } print!("{:>02x}", fs.header.uuid()[i]); } println!(": {} MiB", fs.header.size() / MIBI as u64); } fn main(os: &impl Os) -> (usize, u64, KernelArgs) { // Phase 1: open the filesystem. redoxfs() prompts for a password on this // header screen if the disk is encrypted. draw_header(os); let hwdesc = os.hwdesc(); let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc { OsHwDesc::Acpi(base, size) => (base, size), OsHwDesc::DeviceTree(base, size) => (base, size), OsHwDesc::NotFound => (0, 0), }; let (mut fs, password_opt) = redoxfs(os); // Phase 2: resolution selection — a clean, titled screen per video output. let mut mode_opts = Vec::new(); let mut live = cfg!(feature = "live"); let mut edit_env = false; for output_i in 0..os.video_outputs() { draw_header(os); mode_opts.push(select_mode(os, output_i, &mut live, &mut edit_env)); } let stack_size = 128 * KIBI; let stack_base = os.alloc_zeroed_page_aligned(stack_size); if stack_base.is_null() { panic!("Failed to allocate memory for stack"); } // Phase 3: load kernel + initfs (and optionally preload the live image) on a // fresh titled screen, so loading progress never overprints the menu. draw_header(os); print_fs_line(&fs); let live_opt = if live { let size = fs.header.size(); print!(" live: {} 0/{} MiB", progress_bar(0, size), size / MIBI as u64); let live_size = match usize::try_from(size) { Ok(live_size) => live_size, Err(_) => { println!("\r live: disabled (image too large for bootloader address space)"); live = false; 0 } }; let ptr = if live { os.alloc_zeroed_page_aligned(live_size) } else { ptr::null_mut() }; if live && ptr.is_null() { println!( "\r live: disabled (unable to allocate {} MiB upfront)", size / MIBI as u64 ); live = false; } let live = if live { Some(unsafe { slice::from_raw_parts_mut(ptr, live_size) }) } else { println!(" Continuing without live preload"); None }; if let Some(live) = live { let mut i = 0; for chunk in live.chunks_mut(MIBI) { print!("\r live: {} {}/{} MiB", progress_bar(i, size), i / MIBI as u64, size / MIBI as u64); i += unsafe { fs.disk .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) .expect("Failed to read live disk") as u64 }; } os.set_text_color(TextColor::Green); println!("\r live: {} {}/{} MiB", progress_bar(size, size), size / MIBI as u64, size / MIBI as u64); os.set_text_color(TextColor::Default); println!(" Switching to live disk"); unsafe { LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, live_size))); } area_add(OsMemoryEntry { base: live.as_ptr() as u64, size: live.len() as u64, kind: OsMemoryKind::Reserved, }); Some(live) } else { None } } else { None }; let (kernel, kernel_entry) = { let kernel = load_to_memory(os, &mut fs, "usr/lib/boot/kernel", Filetype::Elf); let (kernel_entry, kernel_64bit) = elf_entry(kernel); unsafe { KERNEL_64BIT = kernel_64bit; } (kernel, kernel_entry) }; let (bootstrap_size, bootstrap_base) = { let initfs_slice = load_to_memory(os, &mut fs, "usr/lib/boot/initfs", Filetype::Initfs); let memory = unsafe { let total_size = initfs_slice.len().next_multiple_of(4096); let ptr = os.alloc_zeroed_page_aligned(total_size); assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory"); core::slice::from_raw_parts_mut(ptr, total_size) }; memory[..initfs_slice.len()].copy_from_slice(initfs_slice); (memory.len() as u64, memory.as_mut_ptr() as u64) }; let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) } .expect("Failed to set up paging"); let max_env_size = 64 * KIBI; let mut env_size = max_env_size; let env_base = os.alloc_zeroed_page_aligned(env_size); if env_base.is_null() { panic!("Failed to allocate memory for stack"); } { let mut w = SliceWriter { slice: unsafe { slice::from_raw_parts_mut(env_base, max_env_size) }, i: 0, }; match hwdesc { OsHwDesc::Acpi(addr, size) => { writeln!(w, "RSDP_ADDR={addr:016x}").unwrap(); writeln!(w, "RSDP_SIZE={size:016x}").unwrap(); } OsHwDesc::DeviceTree(addr, size) => { writeln!(w, "DTB_ADDR={addr:016x}").unwrap(); writeln!(w, "DTB_SIZE={size:016x}").unwrap(); } OsHwDesc::NotFound => {} } if let Some(live) = live_opt { writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap(); writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap(); writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap(); } else { writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap(); } write!(w, "REDOXFS_UUID=").unwrap(); for i in 0..fs.header.uuid().len() { if i == 4 || i == 6 || i == 8 || i == 10 { write!(w, "-").unwrap(); } write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap(); } writeln!(w).unwrap(); if let Some(password) = password_opt { writeln!( w, "REDOXFS_PASSWORD_ADDR={:016x}", password.as_ptr() as usize ) .unwrap(); writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap(); } #[cfg(target_arch = "riscv64")] { let boot_hartid = os::efi_get_boot_hartid() .expect("Could not retrieve boot hart id from EFI implementation!"); writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap(); } if edit_env { editor::edit_env(os, env_base, &mut w.i, max_env_size); } for output_i in 0..os.video_outputs() { if let Some(mut mode) = mode_opts[output_i] { // Set mode to get updated values os.set_video_mode(output_i, &mut mode); if output_i == 0 { let virt = unsafe { paging_framebuffer( os, page_phys, mode.base, (mode.stride * mode.height * 4) as u64, ) } .expect("Failed to map framebuffer"); writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap(); writeln!(w, "FRAMEBUFFER_VIRT={virt:016x}").unwrap(); writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap(); writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap(); writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap(); } else { writeln!( w, "FRAMEBUFFER{}={:#x},{},{},{}", output_i, mode.base, mode.width, mode.height, mode.stride, ) .unwrap(); } } } env_size = w.i; } os.set_text_color(TextColor::Green); println!(); println!( "{}", center_in(os.text_columns().max(20), "Booting RedBear OS...") ); os.set_text_color(TextColor::Default); #[allow(static_mut_refs)] ( page_phys, kernel_entry, KernelArgs { kernel_base: kernel.as_ptr() as u64, kernel_size: kernel.len() as u64, stack_base: stack_base as u64, stack_size: stack_size as u64, env_base: env_base as u64, env_size: env_size as u64, acpi_rsdp_base, acpi_rsdp_size, areas_base: unsafe { AREAS.as_ptr() as u64 }, areas_size: unsafe { (AREAS.len() * mem::size_of::()) as u64 }, bootstrap_base, bootstrap_size, }, ) }