Files
RedBear-OS/src/main.rs
T
Red Bear OS 3fd4403ecb bootloader: framed centered wordmark, progress bars, color polish
Beautification pass on the text UI (all pure ASCII so it renders identically on
the UEFI Unicode console and the VGA CP437 console, which truncates chars to
bytes):

  - Os::text_columns(): new trait method to query the real console width.
    BIOS returns the VGA width; UEFI queries SimpleTextOutput QueryMode for the
    current mode's columns (both fall back to 80). Enables true centering.
  - draw_header now draws a full-width frame around a centered figlet "RedBear
    OS" wordmark, with a centered version/platform subtitle, in brand red/cyan.
    Falls back to a plain centered name on consoles narrower than the wordmark.
  - Loading progress lines (live / kernel / initfs) gain a fixed-width ASCII
    progress bar next to the MiB counters, and turn green on completion.
  - A centered green "Booting RedBear OS..." closes the loading screen.

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:46:31 +09:00

938 lines
30 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, 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<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", 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::<OsMemoryEntry>()) as u64 },
bootstrap_base,
bootstrap_size,
},
)
}