Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions bootloader/src/arch/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,6 @@ pub fn counter() -> u64 {
count
}

/// What the loader's report says beside the counter: its rate. Where it counts
/// from is firmware's to say and no register here does.
pub fn counter_origin() -> alloc::string::String {
let hz: u64;
// SAFETY: reads a register EL1 and EL2 may always read.
unsafe { core::arch::asm!("mrs {}, cntfrq_el0", out(reg) hz, options(nomem, nostack, preserves_flags)) };
alloc::format!("CNTFRQ_EL0 {hz} Hz; the counter's origin is firmware's")
}

/// What the loader says about the CPU as firmware handed it over, or why the
/// kernel cannot run on it: entered at EL2 on a CPU without FEAT_E2H0,
/// `HCR_EL2.E2H` is RES1, so the kernel's entry cannot clear it and every
Expand Down
18 changes: 0 additions & 18 deletions bootloader/src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,6 @@ pub fn counter() -> u64 {
unsafe { core::arch::x86_64::_rdtsc() }
}

/// What the loader's report says beside the counter: `IA32_TSC_ADJUST`,
/// where CPUID says the CPU has it — every write to the TSC since reset is
/// added to it (Intel SDM Vol. 3B, "Time-Stamp Counter Adjustment"), so zero
/// is a counter firmware never wrote and the TSC is time since power-on.
pub fn counter_origin() -> alloc::string::String {
let max = core::arch::x86_64::__cpuid(0).eax;
// Leaf 7 exists when the maximum leaf reaches it.
if max < 7 || core::arch::x86_64::__cpuid_count(7, 0).ebx & (1 << 1) == 0 {
return alloc::string::String::from("IA32_TSC_ADJUST not on this CPU");
}
let (lo, hi): (u32, u32);
// SAFETY: the loader runs at CPL 0, and CPUID.07H:EBX[1] says the MSR exists.
unsafe {
core::arch::asm!("rdmsr", in("ecx") 0x3bu32, out("eax") lo, out("edx") hi, options(nomem, nostack))
};
alloc::format!("IA32_TSC_ADJUST {}", ((u64::from(hi) << 32) | u64::from(lo)) as i64)
}

/// `CLFLUSH`'s line on every x86-64 part.
const LINE: u64 = 64;

Expand Down
25 changes: 1 addition & 24 deletions bootloader/src/attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,8 @@
//! back, and which slot's image it handed it to, kept on the stick it boots
//! from.
//!
//! **The bound on a hang, and the machine has no other way out of one.**
//! `bootnext::point_at_us` aims `BootNext` at this loader before every kernel
//! handoff, so a kernel that hangs and an owner who cuts power get: firmware,
//! this loader, the same kernel, the same hang — for ever. The black box cannot
//! break it, because a power cut is exactly what empties the black box: the next
//! pass finds nothing to report, arms a fresh record and boots the same kernel
//! again. The only ways out of that are the firmware's boot menu and pulling the
//! stick, and neither of those is the loop.
//!
//! What survives a power cut is the stick. So the count is a file on the log
//! partition, beside `loader.log`: **one flash writes a fresh log partition, so
//! the count is per image and per flash without anything having to say so.** It
//! carries the partition's own signature anyway, because a file that says what
//! it counts for is one a reader can be handed on its own.
//!
//! One hand per hang, never two: the second attempt of an image whose first
//! never reported boots no kernel at all. **The same file is the slots'
//! record** (`toyos_update::record`): the image a pass handed the machine to,
//! and every image that died on a boot of its own, so the pass after a hang or
//! a death boots the other slot rather than the one that died.
//!
//! Written twice a pass: before the log opens, with the count and whatever the
//! last boot's end taught, so a pass that dies before its handoff has still
//! counted; and after the slot is chosen, through the log's own open volume,
//! with the image it chose.
//! never reported boots no kernel at all.

use alloc::string::String;
use toyos_update::record::{self, Record};
Expand Down
12 changes: 1 addition & 11 deletions bootloader/src/blackbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,7 @@ pub fn harvest(
return (None, None);
};
// **A record belongs to the stick that wrote it, and this is not always that
// stick.** The page is DRAM at a fixed address and nothing between two
// operating systems clears it: on the T14 a `DONE` record outlived two hours
// of Ubuntu, and the pass after it — booting a *different* image — read that
// record, took itself for its reporting pass, and handed the machine back
// without booting a kernel at all. No stamp could have caught it: the record
// was written before this boot, which is exactly what a real predecessor's
// is.
//
// Cleared rather than reported, and then this pass goes on to boot its
// kernel. A record this stick did not write is one nothing here can report
// truthfully, and leaving it would hand the same trap to the boot after.
// stick.**
if was != identity {
toyos_blackbox::clear(page);
flush(at);
Expand Down
3 changes: 0 additions & 3 deletions bootloader/src/floor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
//! `EFI_VARIABLE_RUNTIME_ACCESS` it is neither readable nor writable once
//! `ExitBootServices` has run), so no kernel this loader hands the machine to
//! — and nothing it lets write the disk — can lower it.
//!
//! What it cannot defend is `toyos_update::policy`'s to say: anything booted
//! before this loader, and the firmware's own reset of its variables.

use alloc::string::String;
use alloc::vec::Vec;
Expand Down
11 changes: 0 additions & 11 deletions bootloader/src/loaderlog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ pub const GOP_AT: &str = "GOP: mode";
const ENDS_AT: &str = "Loader log: the kernel handoff begins, so this file ends here";

/// The last line of a pass that reads the black box and boots no kernel.
///
/// It says the reset and not a return, because the reset is what it does:
/// returning is what leaves this image's exit-boot-services callback registered
/// for the next operating system to call into, and `end_this_pass` exists to
/// not do it. A line describing the shape this code was written to avoid is a
/// line that will be read as evidence one day.
pub const ENDS_AT_CHAIN: &str =
"Loader log: the last boot is accounted for, so this pass resets the machine";

Expand Down Expand Up @@ -78,11 +72,6 @@ static VOLUME: Volume = Volume(UnsafeCell::new(None));
/// reported on and the pass reporting on it are never read as one.
pub const SEPARATOR: &str = "--- the pass after the reset, reading what the boot above left";

/// Open `loader.log` on the partition `guid` names.
///
/// `truncate` replaces what the last boot left; a pass that appends has a
/// *report about* that boot, and the boot's own account has to stay readable
/// under it. One file for now: per-pass names are their own change.
/// The handle of the filesystem on the partition `guid` names, or why this
/// machine has none.
///
Expand Down
110 changes: 12 additions & 98 deletions bootloader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,6 @@ mod slot;
mod watchdog;

/// The largest file the bootloader will read off the ESP.
///
/// Nothing here has a caller to return an error to and nothing has run that
/// could recover, so every check in this file ends in a named panic rather
/// than an error path. This one exists so that a corrupt or hostile directory
/// entry is a refusal that says what it refused, instead of a firmware pool
/// request sized by whatever the ESP claimed.
///
/// Policy, and generous: `kernel.elf` is the largest file ToyOS puts on the
/// ESP, and this bound is orders of magnitude above it while still far below
/// what a UEFI implementation would serve in one allocation.
const MAX_ESP_FILE: u64 = 1024 * 1024 * 1024;

/// Descriptors of room held above what the map measured, for the descriptors
Expand Down Expand Up @@ -182,8 +172,7 @@ struct BootPartition {
/// `None` is a machine, not a failure: PXE, an unpartitioned device, and a
/// signature type firmware chose not to fill in all land here, and the kernel
/// is expected to boot on all of them knowing it has no partition of its own.
/// Every early-return below is one of those, so none of them panics — which
/// makes this the one function in this file that does not.
/// Every early-return below is one of those, so none of them panics.
fn boot_partition(handle: Handle, system_table: &SystemTable<Boot>) -> Option<BootPartition> {
let bs = system_table.boot_services();
let image = bs.open_protocol_exclusive::<LoadedImage>(handle).ok()?;
Expand Down Expand Up @@ -239,53 +228,6 @@ fn log_partition_guid(handle: Handle, system_table: &SystemTable<Boot>) -> [u8;
})
}

/// What firmware says the machine's time zone is, in minutes to add to the
/// CMOS RTC's own reading to get UTC.
///
/// Asked here because `GetTime` is a runtime service and the kernel never maps
/// the runtime, and asked at all because the RTC's registers carry no zone: the
/// same registers read 14:00 on a machine that keeps UTC and on one two hours
/// east of it that keeps local time, and only firmware can tell those apart.
/// `EFI_TIME::TimeZone` is the field, and its spec relation is
/// `Localtime = UTC - TimeZone`.
///
/// `None` is a machine and not a failure — the same as [`boot_partition`] — so
/// this does not panic where the rest of this file does. Firmware that declines
/// to say (`EFI_UNSPECIFIED_TIMEZONE`, which is what OVMF ships) and firmware
/// that cannot be asked are one answer to the kernel: it treats the RTC as UTC
/// and logs that it is doing so.
///
/// The range check is on untrusted input in the strict sense — the field is
/// whatever a vendor's NVRAM holds — and out of range is refused rather than
/// clamped, because an offset that is not a zone is not evidence about which
/// zone the machine is in.
fn rtc_utc_offset(system_table: &SystemTable<Boot>) -> Option<i32> {
/// The field's own bounds, from the UEFI spec: a day either side of UTC.
const MAX_OFFSET_MINUTES: i32 = 1440;

let time = match system_table.runtime_services().get_time() {
Ok(time) => time,
Err(e) => {
println!("RTC zone: firmware's GetTime failed ({e:?}), so the kernel assumes UTC");
return None;
}
};
let Some(zone) = time.time_zone() else {
println!("RTC zone: firmware names none ({time:?}), so the kernel assumes UTC");
return None;
};
let zone = zone as i32;
if !(-MAX_OFFSET_MINUTES..=MAX_OFFSET_MINUTES).contains(&zone) {
println!(
"RTC zone: firmware names {zone} minutes, outside +/-{MAX_OFFSET_MINUTES}, so it is \
ignored and the kernel assumes UTC"
);
return None;
}
println!("RTC zone: {zone} minutes to add to the RTC for UTC ({time:?})");
Some(zone)
}

/// [`toyos_tco::FIRMWARE_BOUND_MS`] in the seconds `set_watchdog_timer` takes.
const FIRMWARE_WATCHDOG_SECS: usize = (toyos_tco::FIRMWARE_BOUND_MS / 1_000) as usize;

Expand Down Expand Up @@ -323,19 +265,6 @@ fn file_range(bytes: &[u8], offset: u64, len: u64) -> Option<&[u8]> {
}

fn load_kernel_elf(kernel_elf_bytes: &[u8]) -> LoadedKernel {
// `toyos-elf` is the tree's one ELF decoder: the crate the kernel reads
// every program image with reads the kernel's own image here. Refused by
// name before anything is allocated — ELF32, big-endian, a version that is
// not `EV_CURRENT`, an `e_type` that is not `ET_DYN`, a machine that is not
// this loader's own, no program headers or a table outside the file, more than
// `toyos_elf::MAX_LOAD_SEGMENTS` `PT_LOAD`s or none at all, a `PT_LOAD`
// with `p_filesz > p_memsz` or a `p_vaddr + p_memsz` or `p_offset +
// p_filesz` that overflows, and an `e_entry` no segment covers.
//
// `p_filesz <= p_memsz` matters for the same reason it does in the kernel's
// loader: the pair is a (copy length, destination size) pair here too, as
// the image is sized from every `p_memsz` and each segment is then copied
// in at `p_filesz`.
let layout = toyos_elf::Layout::parse(kernel_elf_bytes, arch::ELF_MACHINE)
.unwrap_or_else(|e| panic!("kernel.elf: {e}"));

Expand Down Expand Up @@ -590,7 +519,7 @@ fn tsc() -> u64 {
}

#[allow(clippy::too_many_arguments)]
fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec<u8>, cmdline: vec::Vec<u8>, rsdp_addr: u64, gop: Option<GopInfo>, boot_part: Option<BootPartition>, log_partition_guid: [u8; 16], rtc_utc_offset: Option<i32>, root_image: Option<rootimage::RootImage>, entry_tsc: u64, system_table: SystemTable<Boot>) -> ! {
fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec<u8>, cmdline: vec::Vec<u8>, rsdp_addr: u64, gop: Option<GopInfo>, boot_part: Option<BootPartition>, log_partition_guid: [u8; 16], layout: u32, root_image: Option<rootimage::RootImage>, entry_tsc: u64, system_table: SystemTable<Boot>) -> ! {
// Said before it is refused, for `report_reach`'s reason.
match arch::cpu_as_entered() {
Ok(None) => {}
Expand Down Expand Up @@ -620,10 +549,6 @@ fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec<u8>, cmdline: v
let pt_mem = unsafe { alloc::alloc::alloc_zeroed(pt_layout) };
assert!(!pt_mem.is_null(), "page table allocation failed");

// Before the exit: `_print` unwraps a system table uefi-services nulls in its exit callback, so `println!` past it panics.
//
// Said before it is applied: a machine this refuses leaves the refusal in
// `loader.log`, which is the artifact a machine with no console has.
// Where firmware loaded this image, which is where the x86-64 switch to
// the boot map runs from: the map holds it wherever that is.
let loader = {
Expand Down Expand Up @@ -716,8 +641,7 @@ fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec<u8>, cmdline: v
boot_partition_guid,
boot_partition_present,
log_partition_guid,
rtc_utc_offset_minutes: rtc_utc_offset.unwrap_or(0),
rtc_utc_offset_known: rtc_utc_offset.is_some() as u32,
layout,
cmdline_addr: cmdline.as_ptr() as u64,
cmdline_len: cmdline.len() as u64,
root_bridge_window_count,
Expand All @@ -737,9 +661,8 @@ fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec<u8>, cmdline: v

kernel_args.loader_handoff_tsc = tsc();
println!(
"Loader TSC: {entry_tsc} at entry, {} at the handoff; {}",
"Loader TSC: {entry_tsc} at entry, {} at the handoff",
kernel_args.loader_handoff_tsc,
arch::counter_origin(),
);

// Last, and after every line above: a console write, a FAT write and a
Expand Down Expand Up @@ -825,14 +748,6 @@ fn armed_at(system_table: &SystemTable<Boot>) -> u64 {
/// `SIGNAL_EXIT_BOOT_SERVICES` callback that lives here; the next operating
/// system signals that group from inside its own `ExitBootServices`, and
/// firmware calls into memory that is no longer ours.
///
/// So the event is closed *and* the pass resets. Closing it is the invariant —
/// a pass that does not hand off leaves nothing registered in the firmware — and
/// the reset is what makes that invariant not have to be complete: the next
/// operating system comes up on firmware this image has never run on, for one
/// reboot. `BootNext` was consumed by this pass and this pass sets none, so the
/// firmware's own order takes the machine, and the page was cleared as it was
/// read, so a boot that does come back here boots normally.
fn end_this_pass(system_table: &SystemTable<Boot>, exit_event: Option<Event>) -> ! {
println!("{}", loaderlog::ENDS_AT_CHAIN);
loaderlog::close_without_a_kernel();
Expand All @@ -849,10 +764,6 @@ fn end_this_pass(system_table: &SystemTable<Boot>, exit_event: Option<Event>) ->
fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
// First: the TSC counts from reset, so this is what firmware took.
let entry_tsc = tsc();
// The event is kept, not discarded: it is a callback *inside this image*
// that firmware holds until it is closed, and a pass that returns to the
// boot manager is a pass whose image the boot manager then unloads. See
// `end_this_pass`.
let exit_event = uefi_services::init(&mut system_table).unwrap();
// First, because it covers everything below it: firmware starts a
// five-minute countdown when it loads an image and resets the machine if
Expand Down Expand Up @@ -1045,6 +956,13 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
.unwrap_or_else(|e| panic!("slot {}'s cmdline is not UTF-8: {e}", chosen.which.letter()));
println!("Boot parameter: {params:?}");

let layout = if toyos_abi::boot::actuators(params).any(|token| token == toyos_abi::boot::WRITE_NO_LAYOUT_PARAM) {
println!("Kernel arguments: layout 0 on {}", toyos_abi::boot::WRITE_NO_LAYOUT_PARAM);
0
} else {
toyos_abi::boot::LAYOUT
};

let root_image = if toyos_abi::boot::actuators(params).any(|token| token == toyos_abi::boot::WITHHOLD_ROOT_PARAM) {
println!("ROOT: withheld on {}; the kernel is handed no image", toyos_abi::boot::WITHHOLD_ROOT_PARAM);
chosen.root.free(system_table.boot_services());
Expand All @@ -1059,10 +977,6 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
// Query UEFI GOP before exiting boot services
let gop = query_gop(&system_table);

// Last of the firmware questions and for the same reason as the GOP: both
// answers die with Boot Services.
let rtc_offset = rtc_utc_offset(&system_table);

// The page says a kernel is running, and `BootNext` says this loader gets the
// machine again however that kernel ends.
blackbox::arm(page, armed_at(&system_table), log_guid);
Expand All @@ -1074,5 +988,5 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
watchdog::arm(&system_table, rsdp_addr, params);

println!("Starting kernel...");
start_kernel(loaded_kernel, kernel_bytes, cmdline, rsdp_addr, gop, boot_part, log_guid, rtc_offset, root_image, entry_tsc, system_table);
start_kernel(loaded_kernel, kernel_bytes, cmdline, rsdp_addr, gop, boot_part, log_guid, layout, root_image, entry_tsc, system_table);
}
21 changes: 1 addition & 20 deletions bootloader/src/rootbridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
use core::ffi::c_void;
use core::ptr::read_volatile;

use alloc::string::String;
use core::fmt::Write;

use toyos_abi::boot::RootBridgeWindow;
use toyos_acpi::{memory_windows, Phys, MAX_LIST_BYTES};
use uefi::prelude::*;
Expand Down Expand Up @@ -132,23 +129,7 @@ pub fn windows(system_table: &SystemTable<Boot>, out: &mut [RootBridgeWindow]) -
}

let list = List { at: resources as u64 };
let walk = memory_windows(list, list.at, &mut out[found..]);

// `readable` again here rather than resting on the walk's: `Phys`'s
// contract is that a byte is asked for only where a `readable` in the
// same reach accepted it, and a reader that argues its bound across two
// functions is one an edit to either can break silently.
let mut hex = String::with_capacity(walk.bytes * 2);
for i in 0..walk.bytes {
let at = list.at + i as u64;
if !list.readable(at, 1) {
break;
}
let _ = write!(hex, "{:02x}", list.byte(at));
}
println!("{HEAD} {index} (segment {}) {} bytes: {hex}", bridge.segment_number, walk.bytes);

match walk.windows {
match memory_windows(list, list.at, &mut out[found..]) {
Ok(count) => found += count,
Err(why) => {
println!("{HEAD} {index} (segment {}) {why}, so the kernel is handed no window", bridge.segment_number);
Expand Down
4 changes: 1 addition & 3 deletions bootloader/src/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,7 @@ fn signed_header(bs: &BootServices, which: Which, slot: &Slot) -> Result<(Header

/// The version the image the record says the last boot proved carries, read
/// out of its slot's signed header, verified in this pass; or why no version
/// is: **the record is on a partition the running system writes**, so its
/// word is only which slot to read and the digest that slot's header must
/// hash to.
/// is.
pub fn proven(handle: Handle, system_table: &SystemTable<Boot>, booted: &Booted) -> Result<u64, String> {
let bs = system_table.boot_services();
let letter = booted.slot.letter();
Expand Down
Loading
Loading