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
3 changes: 3 additions & 0 deletions Cargo.lock

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

35 changes: 19 additions & 16 deletions bootloader/src/rootimage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use alloc::alloc::Layout;
use alloc::string::String;
use core::num::NonZeroU64;

use toyos_gpt::{Guid, Partition, Sectors};
use toyos_gpt::{Guid, Located, Sectors};
use toyos_rootimage::chunk;
use toyos_update::slots::{self, Table};
use uefi::prelude::*;
Expand Down Expand Up @@ -178,29 +178,31 @@ impl<'a> Disk<'a> {

/// The partition `guid` names on this disk, checked against the table as
/// `toyos_gpt::locate` checks every one.
pub fn locate(&mut self, guid: [u8; 16]) -> Result<Partition, String> {
pub fn locate(&mut self, guid: [u8; 16]) -> Result<Located, String> {
toyos_gpt::locate(self, Guid(guid))
.map(|located| located.partition)
.map_err(|e| alloc::format!("partition {} on the boot disk: {e:?}", Guid(guid)))
}

/// The slot table on this disk's one TOYOS-SLOTS partition.
pub fn slot_table(&mut self) -> Result<Table, String> {
let blank = Partition { index: 0, type_guid: Guid::ZERO, unique_guid: Guid::ZERO, first_lba: 0, last_lba: 0 };
let mut found = [blank; 2];
let mut found = [None; 2];
let scan = toyos_gpt::locate_type(self, Guid::TOYOS_SLOTS, &mut found)
.map_err(|e| alloc::format!("the boot disk's partition table: {e:?}"))?;
if scan.matched != 1 {
return Err(alloc::format!("the boot disk carries {} slot tables, and a machine has one", scan.matched));
}
let part = self.locate(found[0].unique_guid.0)?;
let listed = match (scan.matched, found[0]) {
(1, Some(Ok(listed))) => listed,
(1, Some(Err(unplaced))) => {
return Err(alloc::format!("the boot disk's slot table entry is no partition on it: {unplaced:?}"));
}
(n, _) => return Err(alloc::format!("the boot disk carries {n} slot tables, and a machine has one")),
};
let part = self.locate(listed.unique_guid().0)?.partition();
let lbas = BLOCK as u64 / u64::from(self.lba_bytes);
if part.lba_count() < slots::COPIES * lbas {
if part.lba_count().get() < slots::COPIES * lbas {
return Err(alloc::format!("the slot table's partition is {} blocks, short of its two copies", part.lba_count()));
}
let mut copies = [[0u8; BLOCK]; 2];
for (i, copy) in copies.iter_mut().enumerate() {
let at = part.first_lba + i as u64 * lbas;
let at = part.first_lba() + i as u64 * lbas;
self.io
.read_blocks(self.media_id, at, self.scratch)
.map_err(|e| alloc::format!("the slot table's copy {i} would not read: {:?}", e.status()))?;
Expand All @@ -220,8 +222,9 @@ impl<'a> Disk<'a> {
/// resets the machine, if the firmware honours it. What shows where it
/// stopped is the slot's line before the read and the attempt count the
/// next pass reads.
pub fn read_root(&mut self, bs: &BootServices, part: &Partition, len: u64) -> Result<RootImage, String> {
let capacity = part.lba_count().saturating_mul(u64::from(self.lba_bytes));
pub fn read_root(&mut self, bs: &BootServices, part: &Located, len: u64) -> Result<RootImage, String> {
let part = part.partition();
let capacity = part.lba_count().get().saturating_mul(u64::from(self.lba_bytes));
if len > capacity || !len.is_multiple_of(BLOCK as u64) {
return Err(alloc::format!("{len} bytes of ROOT do not fit whole in its {capacity}-byte partition"));
}
Expand All @@ -241,8 +244,8 @@ impl<'a> Disk<'a> {
let chunk = chunk::chunk_bytes(CHUNK_BOUND, BLOCK, self.lba_bytes, granularity.unwrap_or(0));
let began = crate::tsc();
let mut device = Firmware { io: &self.io, media_id: self.media_id };
let read = chunk::read(&mut device, part.first_lba, self.lba_bytes, chunk, into);
let image = RootImage { at, len, partition: part.unique_guid.0, cycles: crate::tsc().wrapping_sub(began) };
let read = chunk::read(&mut device, part.first_lba(), self.lba_bytes, chunk, into);
let image = RootImage { at, len, partition: part.unique_guid().0, cycles: crate::tsc().wrapping_sub(began) };
if let Err(failed) = read {
let why = alloc::format!(
"the read of {} blocks at LBA {} failed: {:?}, after {} of {len} bytes read",
Expand All @@ -256,7 +259,7 @@ impl<'a> Disk<'a> {
}
println!(
"{READ_AT} {at:#x}+{len:#x} from LBA {}+{}, {chunk} bytes a request (optimal granularity: {}), in {} TSC cycles",
part.first_lba,
part.first_lba(),
len / u64::from(self.lba_bytes),
match granularity {
Some(lbas) => alloc::format!("{lbas} block(s)"),
Expand Down
113 changes: 57 additions & 56 deletions kernel/src/gpt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ static DATA: Lock<Vec<Candidate>> = Lock::new(Vec::new());
/// partition claim is looked for on. Taken alone.
static DISKS: Lock<Vec<(Handle, u32)>> = Lock::new(Vec::new());

/// Every entry each disk's table stated when [`probe`] read it, for the
/// inventory: a table is outside every partition, so nothing a holder writes
/// changes it, and nothing here reads a disk again to answer.
/// A table is outside every partition, so nothing a holder writes changes it,
/// and nothing here reads a disk again to answer.
static LISTED: Lock<Vec<Listed>> = Lock::new(Vec::new());

/// One disk's entries, as [`probe`] listed them.
Expand All @@ -83,9 +82,8 @@ struct Listed {
/// when it is probed.
const MAX_LISTED: usize = 128;

/// Every GPT entry on every disk [`probe`] read, and who holds exactly its
/// span now, from the block layer's holds. A partition that is not whole
/// blocks is held by nothing, since no view can be made of it.
/// A partition that is not whole blocks is held by nothing, since no view can
/// be made of it.
pub fn inventory() -> Vec<(DeviceId, Partition, Option<crate::block::Holder>)> {
let listed: Vec<(Handle, u32, Vec<Partition>)> = LISTED
.lock()
Expand All @@ -95,7 +93,7 @@ pub fn inventory() -> Vec<(DeviceId, Partition, Option<crate::block::Holder>)> {
let mut out = Vec::new();
for (handle, lba_bytes, parts) in listed {
for part in parts {
let holder = match crate::block::span_blocks(part.first_lba, part.lba_count(), lba_bytes) {
let holder = match crate::block::span_blocks(part.first_lba(), part.lba_count().get(), lba_bytes) {
Ok((first_block, blocks)) => handle.holder(first_block, first_block + blocks),
Err(_) => None,
};
Expand All @@ -108,19 +106,31 @@ pub fn inventory() -> Vec<(DeviceId, Partition, Option<crate::block::Holder>)> {
/// List `handle`'s table into [`LISTED`], once per disk.
fn list(sectors: &mut DeviceSectors<'_>, handle: &Handle, lba_bytes: u32) {
let id = handle.device_id();
let mut found = alloc::vec![BLANK; MAX_LISTED];
let mut found = alloc::vec![None; MAX_LISTED];
// A disk with no table this kernel parses carries no partition, and
// `collect` says so, naming the refusal.
let Ok(scan) = toyos_gpt::list(sectors, &mut found) else { return };
if scan.matched as usize > scan.listed {
if scan.matched as usize > MAX_LISTED {
log!(
"gpt: device {id} carries {} partitions and the inventory lists {}",
scan.matched,
scan.listed
"gpt: device {id} carries {} partitions and the inventory lists {MAX_LISTED}",
scan.matched
);
}
found.truncate(scan.listed);
LISTED.lock().push(Listed { handle: handle.clone(), lba_bytes, parts: found });
let mut parts = Vec::new();
for entry in found.into_iter().flatten() {
match entry {
Ok(part) => parts.push(part),
Err(unplaced) => log!(
"gpt: device {id} states entry {} ({}) at LBA {}..={}, whose blocks are no \
partition on it, and the inventory does not list it",
unplaced.index,
unplaced.unique_guid,
unplaced.first,
unplaced.last
),
}
}
LISTED.lock().push(Listed { handle: handle.clone(), lba_bytes, parts });
}

/// How many partitions of one ToyOS type one device may offer this kernel.
Expand Down Expand Up @@ -216,13 +226,13 @@ pub fn probe(handle: &Handle, lba_bytes: u32) {
};

// Firmware's and the table's accounts must agree; a mismatch refuses, never repairs.
let part = found.partition;
if part.first_lba != firmware.start_lba || part.lba_count() != firmware.blocks {
let part = found.partition();
if part.first_lba() != firmware.start_lba || part.lba_count().get() != firmware.blocks {
log!(
"gpt: device {id} puts {} at LBA {}+{} but firmware said {}+{} — not treating it as \
the boot volume",
part.unique_guid,
part.first_lba,
part.unique_guid(),
part.first_lba(),
part.lba_count(),
firmware.start_lba,
firmware.blocks
Expand All @@ -233,8 +243,8 @@ pub fn probe(handle: &Handle, lba_bytes: u32) {
let volume = Volume {
device: id,
lba_bytes,
start_lba: part.first_lba,
blocks: part.lba_count(),
start_lba: part.first_lba(),
blocks: part.lba_count().get(),
};

let mut resolved = RESOLVED.lock();
Expand All @@ -248,9 +258,9 @@ pub fn probe(handle: &Handle, lba_bytes: u32) {
volume.start_lba,
volume.blocks,
lba_bytes,
part.index,
found.used_entries,
found.disk_guid,
part.index(),
found.used_entries(),
found.disk_guid(),
if part.is_efi_system() { "" } else { " — and its type is not ESP" }
);
*resolved = Resolution::Found { boot: volume, log };
Expand Down Expand Up @@ -283,46 +293,46 @@ fn collect(
ty: Guid,
into: &Lock<Vec<Candidate>>,
) {
let mut found = [BLANK; MAX_PER_DEVICE];
let mut found = [None; MAX_PER_DEVICE];
let scan = match toyos_gpt::locate_type(sectors, ty, &mut found) {
Ok(scan) => scan,
Err(e) => {
log!("gpt: device {id} carries no {what} this kernel can read: {e:?}");
return;
}
};
if scan.matched as usize > scan.listed {
if scan.matched as usize > MAX_PER_DEVICE {
log!(
"gpt: device {id} carries {} {what} partitions and this kernel looks at {}",
scan.matched,
scan.listed
"gpt: device {id} carries {} {what} partitions and this kernel looks at {MAX_PER_DEVICE}",
scan.matched
);
}
for candidate in &found[..scan.listed] {
let checked = match toyos_gpt::locate(sectors, candidate.unique_guid) {
Ok(located) => located.partition,
// An entry whose blocks are no partition was logged once, by `list`.
for candidate in found.iter().flatten().flatten() {
let checked = match toyos_gpt::locate(sectors, candidate.unique_guid()) {
Ok(located) => located.partition(),
Err(e) => {
log!(
"gpt: device {id} names a {what} {} its own table then refuses: {e:?}",
candidate.unique_guid
candidate.unique_guid()
);
continue;
}
};
log!(
"gpt: device {id} carries the {what} candidate {} at LBA {}+{}",
checked.unique_guid,
checked.first_lba,
checked.unique_guid(),
checked.first_lba(),
checked.lba_count()
);
into.lock().push(Candidate {
volume: Volume {
device: id,
lba_bytes,
start_lba: checked.first_lba,
blocks: checked.lba_count(),
start_lba: checked.first_lba(),
blocks: checked.lba_count().get(),
},
guid: checked.unique_guid,
guid: checked.unique_guid(),
});
}
}
Expand Down Expand Up @@ -410,7 +420,7 @@ pub fn seek(guid: PartGuid) -> Sought {
for (handle, lba_bytes) in &disks {
let id = handle.device_id();
let part = match toyos_gpt::locate(&mut DeviceSectors::new(handle, *lba_bytes), target) {
Ok(located) => located.partition,
Ok(located) => located.partition(),
Err(e) => {
match table_refused(id, target, e) {
Ok(Unread::Lacks) => {}
Expand All @@ -432,10 +442,10 @@ pub fn seek(guid: PartGuid) -> Sought {
volume: Volume {
device: id,
lba_bytes: *lba_bytes,
start_lba: part.first_lba,
blocks: part.lba_count(),
start_lba: part.first_lba(),
blocks: part.lba_count().get(),
},
unique: part.unique_guid,
unique: part.unique_guid(),
});
}
Sought { found: Ok(found), silent }
Expand Down Expand Up @@ -486,33 +496,24 @@ fn table_refused(id: DeviceId, target: Guid, e: GptError) -> Result<Unread, Unna
}
}

/// A slot [`toyos_gpt::locate_type`] has not filled in.
const BLANK: Partition = Partition {
index: 0,
type_guid: Guid::ZERO,
unique_guid: Guid::ZERO,
first_lba: 0,
last_lba: 0,
};

/// The log partition on the device already proven to carry the boot partition, or `None`.
fn locate_log(sectors: &mut DeviceSectors<'_>, id: DeviceId, lba_bytes: u32) -> Option<Volume> {
let target = LOG_GUID.lock().expect("gpt::init runs before any device is probed");
match toyos_gpt::locate(sectors, target) {
Ok(found) => {
let part = found.partition;
let part = found.partition();
log!(
"gpt: device {id} carries the log partition {target} at LBA {}+{}, entry {} of {}",
part.first_lba,
part.first_lba(),
part.lba_count(),
part.index,
found.used_entries
part.index(),
found.used_entries()
);
Some(Volume {
device: id,
lba_bytes,
start_lba: part.first_lba,
blocks: part.lba_count(),
start_lba: part.first_lba(),
blocks: part.lba_count().get(),
})
}
Err(e) => {
Expand Down
15 changes: 7 additions & 8 deletions kernel/src/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
//!
//! **Assembled from what each subsystem already keeps**: the PCI functions and
//! their identities are `pcidev`'s from enumeration, the USB devices are the
//! ones the xHCI driver bound, the block devices are the ones registered, and
//! the partitions are what each disk's table stated when `gpt::probe` listed
//! it. A partition's state is the block layer's hold on exactly its span, the
//! ones the xHCI driver bound, and the block devices are the ones registered.
//! A partition's state is the block layer's hold on exactly its span, the
//! record every view is refused against.
//!
//! **A holder is found where its handle is**: every process's table is walked
Expand Down Expand Up @@ -33,11 +32,11 @@ pub fn collect() -> Vec<Record> {
for (device, part, holder) in crate::gpt::inventory() {
out.push(Record::Partition(Partition {
device,
index: part.index,
type_guid: part.type_guid.0,
unique_guid: part.unique_guid.0,
first_lba: part.first_lba,
lbas: part.lba_count(),
index: part.index(),
type_guid: part.type_guid().0,
unique_guid: part.unique_guid().0,
first_lba: part.first_lba(),
lbas: part.lba_count().get(),
state: match holder {
None => PartState::Free,
Some(crate::block::Holder::Kernel(_)) => PartState::Kernel,
Expand Down
Loading
Loading