Skip to content
Open
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
91 changes: 0 additions & 91 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions bootloader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,3 @@ toyos-update = { path = "../toyos-update" }
# with: this target is soft-float, and the x86 backend reaches for SSE and
# SHA-NI registers a UEFI application may not assume are its own.
sha2 = { version = "0.10", default-features = false, features = ["force-soft"] }
# `alloc`: `variable_keys` and `get_variable_boxed`, which are how the boot
# entry pointing at this image is found among the firmware's own variables.
uefi = { version = "0.26.0", default-features = false, features = ["alloc"] }
uefi-services = { version = "0.23.0", features = ["panic_handler", "logger"] }
22 changes: 10 additions & 12 deletions bootloader/src/attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@

use alloc::string::String;
use toyos_update::record::{self, Record};
use uefi::proto::media::file::{Directory, File, FileAttribute, FileMode};
use uefi::prelude::*;
use uefi::{cstr16, CStr16};
use crate::efi::{cstr16, CStr16, File, Mode, Status, SystemTable};

use crate::loaderlog;

Expand All @@ -25,12 +23,12 @@ const NAME: &CStr16 = cstr16!("attempts");
/// **`Err` is not zero.** A volume this cannot read is one the bound is off on,
/// and the caller says so rather than treating an unreadable stick as a first
/// attempt — which would be a bound that silently never fires.
pub fn read(system_table: &SystemTable<Boot>, guid: &[u8; 16]) -> Result<Record, String> {
pub fn read(system_table: &SystemTable, guid: &[u8; 16]) -> Result<Record, String> {
loaderlog::with_volume(system_table, guid, |root| {
let file = match root.open(NAME, FileMode::Read, FileAttribute::empty()) {
let file = match root.open(NAME, Mode::Read) {
Ok(file) => file,
// No file is a first attempt, which is every freshly flashed image.
Err(e) if e.status() == Status::NOT_FOUND => return Ok(Record::default()),
Err(Status::NOT_FOUND) => return Ok(Record::default()),
Err(e) => return Err(alloc::format!("{NAME} would not open ({e})")),
};
let Some(mut file) = file.into_regular_file() else {
Expand All @@ -50,7 +48,7 @@ pub fn read(system_table: &SystemTable<Boot>, guid: &[u8; 16]) -> Result<Record,
///
/// Written **before the kernel is handed the machine**, because a count written
/// after it is a count a hang never gets to.
pub fn write(system_table: &SystemTable<Boot>, guid: &[u8; 16], record: &Record) -> Result<(), String> {
pub fn write(system_table: &SystemTable, guid: &[u8; 16], record: &Record) -> Result<(), String> {
loaderlog::with_volume(system_table, guid, |root| put(root, guid, record)).and_then(|inner| inner)
}

Expand All @@ -60,25 +58,25 @@ pub fn write_chosen(guid: &[u8; 16], record: &Record) -> Result<(), String> {
loaderlog::with_open_volume(|root| put(root, guid, record)).and_then(|inner| inner)
}

fn put(root: &mut Directory, guid: &[u8; 16], record: &Record) -> Result<(), String> {
fn put(root: &mut File, guid: &[u8; 16], record: &Record) -> Result<(), String> {
// Deleted and recreated rather than rewound: a fixed-width record is
// still a record a shorter write would leave the tail of.
match root.open(NAME, FileMode::ReadWrite, FileAttribute::empty()) {
match root.open(NAME, Mode::ReadWrite) {
Ok(stale) => {
if let Err(e) = stale.delete() {
return Err(alloc::format!("{NAME} would not delete ({e})"));
}
}
Err(e) if e.status() == Status::NOT_FOUND => {}
Err(Status::NOT_FOUND) => {}
Err(e) => return Err(alloc::format!("{NAME} would not open ({e})")),
}
let file = root
.open(NAME, FileMode::CreateReadWrite, FileAttribute::empty())
.open(NAME, Mode::CreateReadWrite)
.map_err(|e| alloc::format!("{NAME} would not be created ({e})"))?;
let Some(mut file) = file.into_regular_file() else {
return Err(alloc::format!("{NAME} on the log partition is a directory"));
};
file.write(&record.encode(guid)).map_err(|e| alloc::format!("{NAME} would not write ({e})"))?;
file.write(&record.encode(guid)).map_err(|(e, _)| alloc::format!("{NAME} would not write ({e})"))?;
// **Flushed here and not at the handoff.** What this file exists to
// survive is a power cut, and a byte in a cache survives nothing.
file.flush().map_err(|e| alloc::format!("{NAME} would not flush ({e})"))?;
Expand Down
11 changes: 3 additions & 8 deletions bootloader/src/blackbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

use alloc::string::String;
use alloc::vec::Vec;
use uefi::prelude::*;
use uefi::table::boot::{AllocateType, MemoryType};
use crate::efi::{AllocateType, SystemTable};

use toyos_blackbox::{BYTES, PHYS, State};
use toyos_wallclock::Civil;
Expand Down Expand Up @@ -53,12 +52,8 @@ pub struct Page(u64);
/// last one's file or replaces it is what the page decides — so a refusal is
/// returned as a line for the caller to write rather than printed here, where a
/// machine with no console would lose it.
pub fn claim(system_table: &SystemTable<Boot>) -> (Option<Page>, Option<String>) {
match system_table.boot_services().allocate_pages(
AllocateType::Address(PHYS),
MemoryType::LOADER_DATA,
toyos_blackbox::PAGES,
) {
pub fn claim(system_table: &SystemTable) -> (Option<Page>, Option<String>) {
match system_table.boot_services().allocate_pages(AllocateType::Address(PHYS), toyos_blackbox::PAGES) {
Ok(at) => {
// `AllocateType::Address` allocates that address or fails; firmware
// answering with another one has not done what was asked of it.
Expand Down
45 changes: 18 additions & 27 deletions bootloader/src/bootnext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@
//! that booted us from a removable-media fallback path has no entry of ours at
//! all and must be told so rather than have one guessed at.

use uefi::prelude::*;
use uefi::proto::device_path::media::PartitionSignature;
use uefi::proto::device_path::{DevicePath, DeviceSubType, DeviceType};
use uefi::proto::loaded_image::LoadedImage;
use uefi::table::runtime::{VariableAttributes, VariableVendor};
use uefi::CStr16;
use crate::efi::{cstr16, CStr16, DevicePath, Handle, HardDrive, LoadedImage, SystemTable, VariableAttributes, GLOBAL_VARIABLE};

/// The head of every line this module writes.
const HEAD: &str = "Boot chain:";
Expand All @@ -39,7 +34,7 @@ const LOAD_OPTION_HEAD: usize = 6;
/// A refusal is not a failure of the boot: the kernel still runs and still seals
/// its page. What is lost is the *next* boot, so the line says exactly that
/// rather than reporting a variable write.
pub fn point_at_us(handle: Handle, system_table: &SystemTable<Boot>) {
pub fn point_at_us(handle: Handle, system_table: &SystemTable) {
let Some(ours) = our_partition(handle, system_table) else {
return println!(
"{HEAD} firmware did not load this image off a GPT partition, so there is no entry \
Expand All @@ -54,7 +49,7 @@ pub fn point_at_us(handle: Handle, system_table: &SystemTable<Boot>) {
};
let write = system_table.runtime_services().set_variable(
cstr16!("BootNext"),
&VariableVendor::GLOBAL_VARIABLE,
&GLOBAL_VARIABLE,
// Non-volatile, because it has to survive the reset that is the whole point.
VariableAttributes::NON_VOLATILE
| VariableAttributes::BOOTSERVICE_ACCESS
Expand All @@ -71,24 +66,21 @@ pub fn point_at_us(handle: Handle, system_table: &SystemTable<Boot>) {
}

/// The GPT partition GUID of the volume firmware loaded this image from.
fn our_partition(handle: Handle, system_table: &SystemTable<Boot>) -> Option<[u8; 16]> {
fn our_partition(handle: Handle, system_table: &SystemTable) -> Option<[u8; 16]> {
let bs = system_table.boot_services();
let image = crate::protocol::exclusive::<LoadedImage>(bs, handle).ok()?;
let image = bs.exclusive::<LoadedImage>(handle).ok()?;
let device = image.device()?;
let path = crate::protocol::exclusive::<DevicePath>(bs, device).ok()?;
hard_drive_guid(path.node_iter())
let path = bs.exclusive::<DevicePath>(device).ok()?;
hard_drive_guid(path.nodes())
}

/// The GPT signature of the first HARDDRIVE node in a device path, or `None`
/// where the path has none — a network boot, or a disk with no GPT.
fn hard_drive_guid<'a>(nodes: impl Iterator<Item = &'a uefi::proto::device_path::DevicePathNode>) -> Option<[u8; 16]> {
for node in nodes {
if node.full_type() != (DeviceType::MEDIA, DeviceSubType::MEDIA_HARD_DRIVE) {
continue;
}
let hd = <&uefi::proto::device_path::media::HardDrive>::try_from(node).ok()?;
if let PartitionSignature::Guid(guid) = hd.partition_signature() {
return Some(guid.to_bytes());
fn hard_drive_guid<'a>(nodes: impl Iterator<Item = &'a [u8]>) -> Option<[u8; 16]> {
for node in nodes.filter(|node| (node[0], node[1]) == HardDrive::TYPE) {
let hd = HardDrive::parse(node)?;
if hd.signature_type == HardDrive::GUID_SIGNATURE {
return Some(hd.signature);
}
}
None
Expand All @@ -98,17 +90,16 @@ fn hard_drive_guid<'a>(nodes: impl Iterator<Item = &'a uefi::proto::device_path:
///
/// Every entry is read rather than only those in `BootOrder`: an entry the owner
/// has moved out of the order is still ours and still the one to come back to.
fn entry_for(system_table: &SystemTable<Boot>, ours: &[u8; 16]) -> Option<u16> {
fn entry_for(system_table: &SystemTable, ours: &[u8; 16]) -> Option<u16> {
let rt = system_table.runtime_services();
let keys = rt.variable_keys().ok()?;
let mut found: Option<u16> = None;
for key in keys {
if key.vendor != VariableVendor::GLOBAL_VARIABLE {
for (name, vendor) in keys {
if vendor != GLOBAL_VARIABLE {
continue;
}
let Ok(name) = key.name() else { continue };
let Some(number) = entry_number(name) else { continue };
let Ok((bytes, _)) = rt.get_variable_boxed(name, &key.vendor) else { continue };
let Some(number) = entry_number(&name) else { continue };
let Ok((bytes, _)) = rt.get_variable(&name, &vendor) else { continue };
if !load_option_names(&bytes, ours) {
continue;
}
Expand All @@ -121,7 +112,7 @@ fn entry_for(system_table: &SystemTable<Boot>, ours: &[u8; 16]) -> Option<u16> {

/// `Boot0003` is entry 3; anything else here is some other global variable.
fn entry_number(name: &CStr16) -> Option<u16> {
let mut chars = name.iter().map(|c| char::from(*c));
let mut chars = name.units().iter().map(|&unit| char::from_u32(u32::from(unit)).unwrap_or(char::REPLACEMENT_CHARACTER));
for want in ENTRY_PREFIX.chars() {
if chars.next()? != want {
return None;
Expand Down
Loading
Loading