Files
RedBear-OS/src/main.rs
T
Red Bear OS d7d719775b bootloader: clean per-phase screens + Red Bear branding
Comprehensive fix for overlapping bootloader text. The loader ran three phases
(filesystem, resolution menu, loading) that all wrote to one never-fully-cleared
screen at absolute cursor positions, so they overprinted each other. Most
visibly, the loading phase began printing "live: .../..." exactly where the menu
had left the cursor (the "Autobooting" row), so "live" landed on top of the
countdown. clear_text() also only ran for the 2nd+ video output, so on a single
display the menu was drawn under the header.

Introduce draw_header(os): clear the screen and draw a consistent, branded
"Red Bear OS" title + version/platform + separator. Call it at the start of each
phase so every screen is clean and no phase overprints another:
  - Phase 1 (filesystem / password prompt)
  - Phase 2 (resolution menu, per video output)
  - Phase 3 (kernel/initfs load + optional live preload)

Also:
  - Rebrand user-facing strings: "Redox OS Bootloader" -> "Red Bear OS", and
    the env editor title -> "Red Bear OS Boot Environment Editor". (RedoxFS /
    the RedoxFtw initfs magic are left as-is: those are real format names.)
  - Drop the raw "Hardware descriptor: {:x?}" Debug dump from the UI.
  - Indent all loading-phase progress lines (RedoxFS/live/kernel/initfs) to
    match the header for a consistent layout.

Verified: cargo check passes for x86_64-unknown-uefi (--bin) and
x86-unknown-none (--lib).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 18:35:09 +09:00

860 lines
27 KiB
Rust

#![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<OsVideoMode> {
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<OsVideoMode>,
is_more: bool,
}
let mut entries: Vec<MenuEntry> = 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<OsVideoMode> {
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<OsVideoMode> {
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<O: Os>(os: &O) -> (redoxfs::FileSystem<O::D>, 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<O: Os>(
os: &O,
fs: &mut redoxfs::FileSystem<O::D>,
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>| 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, 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, 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;
}
println!("\r {}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64);
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);
}
}
}
/// Clear the screen and draw the Red Bear OS bootloader header.
///
/// 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) {
os.clear_text();
os.set_text_position(0, 0);
os.set_text_color(TextColor::Red);
println!(" Red Bear OS");
os.set_text_color(TextColor::Default);
println!(" Bootloader {} ({})", env!("CARGO_PKG_VERSION"), os.name());
println!(" --------------------------------------------");
println!();
}
/// Print the RedoxFS identity line (uuid + size) below the current cursor.
fn print_fs_line<D: Disk>(fs: &redoxfs::FileSystem<D>) {
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", 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", 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
};
}
println!("\r live: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
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;
}
#[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::<OsMemoryEntry>()) as u64 },
bootstrap_base,
bootstrap_size,
},
)
}