From 5a626d52df9b018ae1e17611efbf2090ed7db5e0 Mon Sep 17 00:00:00 2001 From: "Cliff L. Biffle" Date: Mon, 30 Jun 2025 13:59:40 -0700 Subject: [PATCH 01/61] WIP --- Cargo.lock | 11 +- app/demo-stm32h7-nucleo/app-h753.toml | 1 - idl/packrat.idol | 30 +++++ sys/abi/src/lib.rs | 6 + task/packrat/Cargo.toml | 3 + task/packrat/src/main.rs | 154 +++++++++++++++++++++++++- 6 files changed, 201 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bc0afbcf4..221d82a34c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3831,6 +3831,12 @@ dependencies = [ "minicbor-derive", ] +[[package]] +name = "minicbor" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb9d59e79ad66121ab441a0d1950890906a41e01ae14145ecf8401aa8894f50" + [[package]] name = "minicbor-derive" version = "0.15.3" @@ -3848,7 +3854,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "becf18ac384ecf6f53b2db3b1549eebff664c67ecf259ae99be5912193291686" dependencies = [ - "minicbor", + "minicbor 0.25.1", "serde", ] @@ -5671,11 +5677,14 @@ dependencies = [ "build-util", "cfg-if", "drv-cpu-seq-api", + "hubris-task-names", "idol", "idol-runtime", + "minicbor 0.26.4", "mutable-statics", "num-traits", "ringbuf", + "snitch-core", "spd", "static-cell", "static_assertions", diff --git a/app/demo-stm32h7-nucleo/app-h753.toml b/app/demo-stm32h7-nucleo/app-h753.toml index dd3e0c1a4b..53336da6b7 100644 --- a/app/demo-stm32h7-nucleo/app-h753.toml +++ b/app/demo-stm32h7-nucleo/app-h753.toml @@ -71,7 +71,6 @@ task-slots = ["sys"] [tasks.packrat] name = "task-packrat" priority = 2 -max-sizes = {flash = 8192, ram = 2048} start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/idl/packrat.idol b/idl/packrat.idol index 1c8386c9a6..db275d5723 100644 --- a/idl/packrat.idol +++ b/idl/packrat.idol @@ -94,6 +94,36 @@ Interface( reply: Simple("()"), idempotent: true, ), + + "deliver_ereport": ( + doc: "Hand an encoded ereport to packrat for buffering.", + args: { + }, + leases: { + "data": (type: "[u8]", read: true, max_len: Some(1024)), + }, + reply: Simple("()"), + idempotent: true, + ), + "read_ereports": ( + doc: "Read ereports starting with a watermark (ENA) value", + args: { + "first_ena": "u64", + }, + leases: { + "data": (type: "[u8]", write: true), + }, + reply: Simple("usize"), + idempotent: true, + ), + "flush_ereports": ( + doc: "Free storage for all ereports up thru an ENA.", + args: { + "last_ena": "u64", + }, + reply: Simple("()"), + idempotent: true, + ), }, ) diff --git a/sys/abi/src/lib.rs b/sys/abi/src/lib.rs index 8b830e838f..b8df57f85d 100644 --- a/sys/abi/src/lib.rs +++ b/sys/abi/src/lib.rs @@ -87,6 +87,12 @@ impl From for Generation { } } +impl From for u8 { + fn from(x: Generation) -> Self { + x.0 + } +} + /// Newtype wrapper for an interrupt index #[derive( Copy, diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 494be1a378..f811b6e609 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -12,11 +12,14 @@ zerocopy.workspace = true zerocopy-derive.workspace = true drv-cpu-seq-api = { path = "../../drv/cpu-seq-api", optional = true } +hubris-task-names = { path = "../../sys/task-names" } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } static-cell = { path = "../../lib/static-cell" } task-packrat-api = { path = "../packrat-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +snitch-core = { version = "0.1.0", path = "../../lib/snitch-core" } +minicbor = "0.26.4" [build-dependencies] anyhow.workspace = true diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 9adfe64468..dc61de9548 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -30,14 +30,17 @@ #![no_main] use core::convert::Infallible; -use idol_runtime::{Leased, LenLimit, NotificationHandler, RequestError}; +use idol_runtime::{ClientError, Leased, LenLimit, NotificationHandler, RequestError}; +use minicbor::CborLen; use ringbuf::{ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; use task_packrat_api::{ CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, VpdIdentity, }; -use userlib::RecvMessage; +use userlib::{RecvMessage, TaskId}; +use zerocopy::little_endian::{U128, U64}; +use zerocopy::{FromBytes, AsBytes, Unaligned}; #[cfg(feature = "gimlet")] mod gimlet; @@ -94,6 +97,10 @@ enum TraceSet { ringbuf!(Trace, 16, Trace::None); +/// Number of bytes of RAM dedicated to ereport buffer storage. Each individual +/// report consumes a small amount of this (currently 12 bytes). +const EREPORT_BUFFER_SIZE: usize = 4096; + #[export_name = "main"] fn main() -> ! { struct StaticBufs { @@ -103,14 +110,21 @@ fn main() -> ! { gimlet_bufs: gimlet::StaticBufs, #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs, + ereport_storage: snitch_core::Store, + ereport_recv: [u8; 1024], } let StaticBufs { ref mut mac_address_block, ref mut identity, #[cfg(feature = "gimlet")] ref mut gimlet_bufs, +<<<<<<< Updated upstream #[cfg(feature = "cosmo")] ref mut cosmo_bufs, +======= + ref mut ereport_storage, + ref mut ereport_recv, +>>>>>>> Stashed changes } = { static BUFS: ClaimOnceCell = ClaimOnceCell::new(StaticBufs { @@ -118,15 +132,26 @@ fn main() -> ! { identity: None, #[cfg(feature = "gimlet")] gimlet_bufs: gimlet::StaticBufs::new(), +<<<<<<< Updated upstream #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs::new(), +======= + ereport_storage: snitch_core::Store::DEFAULT, + ereport_recv: [0; 1024], +>>>>>>> Stashed changes }); BUFS.claim() }; + ereport_storage.initialize(0, 0); // TODO tid timestamp + let mut server = ServerImpl { mac_address_block, identity, + ereport_storage, + ereport_recv, + restart_nonce: 0xDEAD, // TODO + next_ena: 1, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData::new(gimlet_bufs), #[cfg(feature = "grapefruit")] @@ -144,6 +169,10 @@ fn main() -> ! { struct ServerImpl { mac_address_block: &'static mut Option, identity: &'static mut Option, + ereport_storage: &'static mut snitch_core::Store, + ereport_recv: &'static mut [u8; 1024], + restart_nonce: u128, + next_ena: u64, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData, #[cfg(feature = "grapefruit")] @@ -408,6 +437,96 @@ impl idl::InOrderPackratImpl for ServerImpl { )) } } + + fn deliver_ereport( + &mut self, + msg: &RecvMessage, + data: LenLimit, 1024usize>, + ) -> Result<(), RequestError> { + data.read_range(0..data.len(), self.ereport_recv).map_err(|_| ClientError::WentAway.fail())?; + self.ereport_storage.insert(msg.sender.0, 0, &self.ereport_recv[..data.len()]); + Ok(()) + } + + fn read_ereports( + &mut self, + _msg: &RecvMessage, + begin_ena: u64, + data: Leased, + ) -> Result> { + // Skip over a header-sized initial chunk, plus space for the + let first_data_byte = size_of::(); + + let mut position = first_data_byte; + let mut first_written_ena = None; + + // Beginning with the first + for r in self.ereport_storage.read_from(begin_ena) { + if first_written_ena.is_none() { + first_written_ena = Some(r.ena); + } + + // TODO start list + + let tid = TaskId(r.tid); + let task_name = hubris_task_names::TASK_NAMES.get(tid.index()) + .copied() + .unwrap_or({ + // This represents an internal error, where we've recorded + // an out-of-range task ID somehow. We still want to get the + // ereport out, so we'll use a recognizable but illegal task + // name to indicate that it's missing. + "-" // TODO + }); + // TODO write task name + let generation = tid.generation(); + // TODO write generation number + r.timestamp; // TODO + + let entry = ( + task_name, + u8::from(generation), + r.timestamp, + ByteGather(r.slices.0, r.slices.1), + ); + let mut c = minicbor::encode::write::Cursor::new(&mut self.ereport_recv[..]); + match minicbor::encode(&entry, &mut c) { + Ok(()) => { + let size = c.position(); + data.write_range(position..position + size, &self.ereport_recv[..size]) + .map_err(|_| ClientError::WentAway.fail())?; + position += size; + } + Err(_end) => { + // This is an odd one; we've admitted a record into our + // queue that won't fit in our buffer. This can happen + // because of the encoding overhead, in theory, but should + // be prevented. + // TODO + } + } + } + + let first_ena = first_written_ena.unwrap_or(self.next_ena); + let header = EreportResponse { + version: 0, + unused: [0; 3], + instance_id: self.restart_nonce.into(), + first_ena: first_ena.into(), + }; + data.write_range(0..size_of_val(&header), header.as_bytes()) + .map_err(|_| ClientError::WentAway.fail())?; + Ok(position) + } + + fn flush_ereports( + &mut self, + _msg: &RecvMessage, + last_ena: u64, + ) -> Result<(), RequestError> { + self.ereport_storage.flush_thru(last_ena); + Ok(()) + } } impl NotificationHandler for ServerImpl { @@ -421,6 +540,37 @@ impl NotificationHandler for ServerImpl { } } +#[derive(Copy, Clone, Debug, FromBytes, AsBytes, Unaligned)] +#[repr(C)] +struct EreportResponse { + version: u8, + message_cookie: u8, + instance_id: U128, + first_ena: U64, +} + +struct ByteGather<'a, 'b>(&'a [u8], &'b [u8]); + +impl minicbor::Encode for ByteGather<'_, '_> { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.bytes_len((self.0.len() + self.1.len()) as u64)?; + e.writer_mut().write_all(self.0).map_err(minicbor::encode::Error::write)?; + e.writer_mut().write_all(self.1).map_err(minicbor::encode::Error::write)?; + Ok(()) + } +} + +impl CborLen for ByteGather<'_, '_> { + fn cbor_len(&self, ctx: &mut C) -> usize { + let n = self.0.len() + self.1.len(); + n.cbor_len(ctx) + n + } +} + mod idl { use super::{ CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, From da365ee4bdd07114569f4a71d9addfbd8cea2444 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 1 Jul 2025 15:28:25 -0700 Subject: [PATCH 02/61] disembreak merge conflict with cosmo --- task/packrat/src/main.rs | 47 +++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index dc61de9548..7ac61a0407 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -30,7 +30,9 @@ #![no_main] use core::convert::Infallible; -use idol_runtime::{ClientError, Leased, LenLimit, NotificationHandler, RequestError}; +use idol_runtime::{ + ClientError, Leased, LenLimit, NotificationHandler, RequestError, +}; use minicbor::CborLen; use ringbuf::{ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; @@ -40,7 +42,7 @@ use task_packrat_api::{ }; use userlib::{RecvMessage, TaskId}; use zerocopy::little_endian::{U128, U64}; -use zerocopy::{FromBytes, AsBytes, Unaligned}; +use zerocopy::{AsBytes, FromBytes, Unaligned}; #[cfg(feature = "gimlet")] mod gimlet; @@ -118,13 +120,10 @@ fn main() -> ! { ref mut identity, #[cfg(feature = "gimlet")] ref mut gimlet_bufs, -<<<<<<< Updated upstream #[cfg(feature = "cosmo")] ref mut cosmo_bufs, -======= ref mut ereport_storage, ref mut ereport_recv, ->>>>>>> Stashed changes } = { static BUFS: ClaimOnceCell = ClaimOnceCell::new(StaticBufs { @@ -132,13 +131,10 @@ fn main() -> ! { identity: None, #[cfg(feature = "gimlet")] gimlet_bufs: gimlet::StaticBufs::new(), -<<<<<<< Updated upstream #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs::new(), -======= ereport_storage: snitch_core::Store::DEFAULT, ereport_recv: [0; 1024], ->>>>>>> Stashed changes }); BUFS.claim() }; @@ -443,8 +439,13 @@ impl idl::InOrderPackratImpl for ServerImpl { msg: &RecvMessage, data: LenLimit, 1024usize>, ) -> Result<(), RequestError> { - data.read_range(0..data.len(), self.ereport_recv).map_err(|_| ClientError::WentAway.fail())?; - self.ereport_storage.insert(msg.sender.0, 0, &self.ereport_recv[..data.len()]); + data.read_range(0..data.len(), self.ereport_recv) + .map_err(|_| ClientError::WentAway.fail())?; + self.ereport_storage.insert( + msg.sender.0, + 0, + &self.ereport_recv[..data.len()], + ); Ok(()) } @@ -454,13 +455,13 @@ impl idl::InOrderPackratImpl for ServerImpl { begin_ena: u64, data: Leased, ) -> Result> { - // Skip over a header-sized initial chunk, plus space for the + // Skip over a header-sized initial chunk, plus space for the let first_data_byte = size_of::(); let mut position = first_data_byte; let mut first_written_ena = None; - // Beginning with the first + // Beginning with the first for r in self.ereport_storage.read_from(begin_ena) { if first_written_ena.is_none() { first_written_ena = Some(r.ena); @@ -469,7 +470,8 @@ impl idl::InOrderPackratImpl for ServerImpl { // TODO start list let tid = TaskId(r.tid); - let task_name = hubris_task_names::TASK_NAMES.get(tid.index()) + let task_name = hubris_task_names::TASK_NAMES + .get(tid.index()) .copied() .unwrap_or({ // This represents an internal error, where we've recorded @@ -489,12 +491,17 @@ impl idl::InOrderPackratImpl for ServerImpl { r.timestamp, ByteGather(r.slices.0, r.slices.1), ); - let mut c = minicbor::encode::write::Cursor::new(&mut self.ereport_recv[..]); + let mut c = minicbor::encode::write::Cursor::new( + &mut self.ereport_recv[..], + ); match minicbor::encode(&entry, &mut c) { Ok(()) => { let size = c.position(); - data.write_range(position..position + size, &self.ereport_recv[..size]) - .map_err(|_| ClientError::WentAway.fail())?; + data.write_range( + position..position + size, + &self.ereport_recv[..size], + ) + .map_err(|_| ClientError::WentAway.fail())?; position += size; } Err(_end) => { @@ -558,8 +565,12 @@ impl minicbor::Encode for ByteGather<'_, '_> { _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { e.bytes_len((self.0.len() + self.1.len()) as u64)?; - e.writer_mut().write_all(self.0).map_err(minicbor::encode::Error::write)?; - e.writer_mut().write_all(self.1).map_err(minicbor::encode::Error::write)?; + e.writer_mut() + .write_all(self.0) + .map_err(minicbor::encode::Error::write)?; + e.writer_mut() + .write_all(self.1) + .map_err(minicbor::encode::Error::write)?; Ok(()) } } From 7c600746fb0c505347dea07dde6805301d527a26 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 1 Jul 2025 15:37:36 -0700 Subject: [PATCH 03/61] restart nonce plumbing --- Cargo.lock | 1 + task/packrat/Cargo.toml | 1 + task/packrat/src/main.rs | 17 +++++++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 221d82a34c..45bcd8e808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5677,6 +5677,7 @@ dependencies = [ "build-util", "cfg-if", "drv-cpu-seq-api", + "drv-rng-api", "hubris-task-names", "idol", "idol-runtime", diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index f811b6e609..f34a83f60b 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -12,6 +12,7 @@ zerocopy.workspace = true zerocopy-derive.workspace = true drv-cpu-seq-api = { path = "../../drv/cpu-seq-api", optional = true } +drv-rng-api = { path = "../../drv/rng-api" } hubris-task-names = { path = "../../sys/task-names" } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 7ac61a0407..8534bf6e01 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -103,6 +103,8 @@ ringbuf!(Trace, 16, Trace::None); /// report consumes a small amount of this (currently 12 bytes). const EREPORT_BUFFER_SIZE: usize = 4096; +task_slot!(RNG, rng); + #[export_name = "main"] fn main() -> ! { struct StaticBufs { @@ -146,7 +148,7 @@ fn main() -> ! { identity, ereport_storage, ereport_recv, - restart_nonce: 0xDEAD, // TODO + restart_nonce: get_restart_nonce(), next_ena: 1, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData::new(gimlet_bufs), @@ -162,6 +164,17 @@ fn main() -> ! { } } +// This is a function so that the 16-byte buffer we provide to the RNG IPC +// doesn't stay on the stack forever (as it might if we did this in `main`?). +fn get_restart_nonce() -> u128 { + let rng = drv_rng_api::Rng::from(RNG.get_task_id()); + let mut buf = [0u8; 16]; + // XXX(eliza): if this fails we are TURBO SCREWED... + rng.fill(&mut buf).unwrap_lite(); + // endianness doesn't matter here, they're random bytes lol + u128::from_be_bytes(buf) +} + struct ServerImpl { mac_address_block: &'static mut Option, identity: &'static mut Option, @@ -517,7 +530,7 @@ impl idl::InOrderPackratImpl for ServerImpl { let first_ena = first_written_ena.unwrap_or(self.next_ena); let header = EreportResponse { version: 0, - unused: [0; 3], + message_cookie: todo!(), instance_id: self.restart_nonce.into(), first_ena: first_ena.into(), }; From b3d580e5a2192ef911ee7dcecf3d8b10dfd316aa Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 1 Jul 2025 15:49:26 -0700 Subject: [PATCH 04/61] ringbuf --- Cargo.lock | 1 + task/packrat/Cargo.toml | 1 + task/packrat/src/main.rs | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 45bcd8e808..587a50663b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5676,6 +5676,7 @@ dependencies = [ "anyhow", "build-util", "cfg-if", + "counters", "drv-cpu-seq-api", "drv-rng-api", "hubris-task-names", diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index f34a83f60b..37da052bd8 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -16,6 +16,7 @@ drv-rng-api = { path = "../../drv/rng-api" } hubris-task-names = { path = "../../sys/task-names" } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } +counters = { path = "../../lib/counters" } static-cell = { path = "../../lib/static-cell" } task-packrat-api = { path = "../packrat-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 8534bf6e01..c93061ddf0 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -97,7 +97,20 @@ enum TraceSet { AttemptedSetToNewValue(T), } +/// Separate ring buffer for ereport events, as we probably don't care that much +/// about the sequence of ereport events relative to other packrat API events. +#[derive(Copy, Clone, counters::Count)] +enum EreportTrace { + #[count(skip)] + None, + EreportDelivered { + src: TaskId, + len: u32, + }, +} + ringbuf!(Trace, 16, Trace::None); +counted_ringbuf!(EREPORT_RINGBUF, EreportTrace, 16, EreportTrace::None); /// Number of bytes of RAM dedicated to ereport buffer storage. Each individual /// report consumes a small amount of this (currently 12 bytes). @@ -459,6 +472,15 @@ impl idl::InOrderPackratImpl for ServerImpl { 0, &self.ereport_recv[..data.len()], ); + // TODO(eliza): would maybe be nice to say something if the ereport got + // eaten... + ringbuf_entry!( + EREPORT_RINGBUF, + EreportTrace { + src: msg.sender, + len: data.len() as u32 + } + ); Ok(()) } From 6c401691c6307541ccb0d3eedd092de998800742 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 2 Jul 2025 15:17:36 -0700 Subject: [PATCH 05/61] actually make CBOR lists --- task/packrat/src/main.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index c93061ddf0..4b63997485 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -500,10 +500,15 @@ impl idl::InOrderPackratImpl for ServerImpl { for r in self.ereport_storage.read_from(begin_ena) { if first_written_ena.is_none() { first_written_ena = Some(r.ena); + // Start CBOR list + // XXX(eliza): in theory it might be nicer to use + // `minicbor::data::Token::BeginArray` here, but it's way more + // annoying in practice... + data.write_at(position, 0x9f) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; } - // TODO start list - let tid = TaskId(r.tid); let task_name = hubris_task_names::TASK_NAMES .get(tid.index()) @@ -549,6 +554,13 @@ impl idl::InOrderPackratImpl for ServerImpl { } } + if first_written_ena.is_some() { + // End CBOR list, if we wrote anything. + data.write_at(position, 0xff) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + } + let first_ena = first_written_ena.unwrap_or(self.next_ena); let header = EreportResponse { version: 0, From 0b6406c7b67a3b80866b567af030e7574c8a5727 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 2 Jul 2025 16:19:55 -0700 Subject: [PATCH 06/61] reticulating packrat --- Cargo.lock | 540 ++++++++++++++++++------------------ Cargo.toml | 1 + idl/packrat.idol | 5 +- task/packrat-api/Cargo.toml | 1 + task/packrat-api/src/lib.rs | 1 + task/packrat/Cargo.toml | 1 + task/packrat/src/main.rs | 68 ++--- 7 files changed, 317 insertions(+), 300 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 587a50663b..5841be4b1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "byteorder", "phash", "serde", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -178,8 +178,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1007,8 +1007,8 @@ dependencies = [ "sha3", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1026,8 +1026,8 @@ dependencies = [ "stm32h7", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1069,8 +1069,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1097,8 +1097,8 @@ dependencies = [ "ringbuf", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1108,8 +1108,8 @@ dependencies = [ "counters", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1123,8 +1123,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1139,8 +1139,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1156,8 +1156,8 @@ dependencies = [ "sha3", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1174,7 +1174,7 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -1197,8 +1197,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1208,8 +1208,8 @@ dependencies = [ "drv-fpga-api", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1228,8 +1228,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1266,8 +1266,8 @@ dependencies = [ "static_assertions", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1290,8 +1290,8 @@ dependencies = [ "task-jefe-api", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1304,8 +1304,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1323,8 +1323,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1334,8 +1334,8 @@ dependencies = [ "counters", "drv-i2c-types", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1353,8 +1353,8 @@ dependencies = [ "smbus-pec", "task-power-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1399,8 +1399,8 @@ dependencies = [ "serde", "static_assertions", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1418,8 +1418,8 @@ dependencies = [ "mutable-statics", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1437,8 +1437,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1451,8 +1451,8 @@ dependencies = [ "drv-oxide-vpd", "idol", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1475,8 +1475,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1493,8 +1493,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1507,8 +1507,8 @@ dependencies = [ "lpc55-pac", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1525,8 +1525,8 @@ dependencies = [ "rand_chacha", "rand_core", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1546,8 +1546,8 @@ dependencies = [ "lpc55-pac", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1565,8 +1565,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1600,8 +1600,8 @@ dependencies = [ "static_assertions", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1633,8 +1633,8 @@ dependencies = [ "serde", "static_assertions", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1649,8 +1649,8 @@ dependencies = [ "num-traits", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1662,8 +1662,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1682,8 +1682,8 @@ dependencies = [ "serde", "stage0-handoff", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1701,8 +1701,8 @@ dependencies = [ "nb 1.0.0", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1727,8 +1727,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1740,8 +1740,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1757,8 +1757,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1785,8 +1785,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1803,8 +1803,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1822,8 +1822,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1847,8 +1847,8 @@ dependencies = [ "serde", "serde_json", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1864,8 +1864,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1879,8 +1879,8 @@ dependencies = [ "num-traits", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1897,8 +1897,8 @@ dependencies = [ "userlib", "vsc7448", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1907,8 +1907,8 @@ version = "0.1.0" dependencies = [ "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1918,8 +1918,8 @@ dependencies = [ "drv-onewire", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1931,8 +1931,8 @@ dependencies = [ "idol", "ringbuf", "tlvc", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2001,8 +2001,8 @@ dependencies = [ "num-traits", "rand_core", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2021,8 +2021,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2036,8 +2036,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2061,8 +2061,8 @@ dependencies = [ "userlib", "vsc7448-pac", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2085,8 +2085,8 @@ dependencies = [ "serde", "serde_json", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2113,8 +2113,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2142,8 +2142,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2156,8 +2156,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2178,8 +2178,8 @@ dependencies = [ "ringbuf", "task-config", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2191,8 +2191,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2221,8 +2221,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2253,8 +2253,8 @@ dependencies = [ "tlvc", "unwrap-lite", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2265,8 +2265,8 @@ dependencies = [ "stm32f3", "stm32f4", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2278,8 +2278,8 @@ dependencies = [ "stm32f3", "stm32f4", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2293,8 +2293,8 @@ dependencies = [ "num-traits", "stm32g0", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2341,8 +2341,8 @@ dependencies = [ "stm32h7", "userlib", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2360,8 +2360,8 @@ dependencies = [ "num-traits", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2372,8 +2372,8 @@ dependencies = [ "stm32h7", "userlib", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2389,8 +2389,8 @@ dependencies = [ "ringbuf", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2401,8 +2401,8 @@ dependencies = [ "ringbuf", "stm32h7", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2417,8 +2417,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2448,7 +2448,7 @@ dependencies = [ "stm32h7", "syn 2.0.98", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -2475,8 +2475,8 @@ dependencies = [ "ssmarshal", "static-cell", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2502,8 +2502,8 @@ dependencies = [ "serde", "stage0-handoff", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2524,8 +2524,8 @@ dependencies = [ "stm32g0", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2543,8 +2543,8 @@ dependencies = [ "stm32g0", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2588,8 +2588,8 @@ dependencies = [ "stm32h7", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2606,8 +2606,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2631,8 +2631,8 @@ dependencies = [ "task-sensor-api", "transceiver-messages", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2665,8 +2665,8 @@ dependencies = [ "task-thermal-api", "transceiver-messages", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2683,8 +2683,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2705,8 +2705,8 @@ dependencies = [ "stm32f4", "task-config", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2718,8 +2718,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2736,8 +2736,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2753,8 +2753,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2842,7 +2842,7 @@ name = "endoscope-abi" version = "0.1.0" dependencies = [ "sha3", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -2984,6 +2984,14 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "gateway-ereport-messages" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/management-gateway-service#57ffd1c24f3ad1919fb4a605d5a501f2c5deb54c" +dependencies = [ + "zerocopy 0.8.26", +] + [[package]] name = "gateway-messages" version = "0.1.0" @@ -2999,7 +3007,7 @@ dependencies = [ "strum", "strum_macros", "uuid", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -3235,8 +3243,8 @@ dependencies = [ "serde_repr", "static_assertions", "unwrap-lite", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3305,8 +3313,8 @@ dependencies = [ "serde", "serde-big-array 0.5.1", "static_assertions", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3362,7 +3370,7 @@ source = "git+https://github.com/oxidecomputer/idolatry.git#6c54f3b87c58f329b07d dependencies = [ "counters", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -3485,8 +3493,8 @@ dependencies = [ "ssmarshal", "syn 2.0.98", "unwrap-lite", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3540,8 +3548,8 @@ dependencies = [ "static_assertions", "unwrap-lite", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", "zeroize", ] @@ -3605,8 +3613,8 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3662,8 +3670,8 @@ dependencies = [ "static_assertions", "toml", "unwrap-lite", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", "zeroize", ] @@ -3694,8 +3702,8 @@ dependencies = [ "task-jefe-api", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -4080,7 +4088,7 @@ version = "0.1.0" dependencies = [ "hubpack", "serde", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -5108,8 +5116,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5194,8 +5202,8 @@ dependencies = [ "static-cell", "unwrap-lite", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5261,8 +5269,8 @@ dependencies = [ "task-vpd-api", "update-buffer", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5279,8 +5287,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5302,8 +5310,8 @@ dependencies = [ "task-packrat-api", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5329,8 +5337,8 @@ dependencies = [ "task-jefe-api", "task-net-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5349,8 +5357,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5433,8 +5441,8 @@ dependencies = [ "static-cell", "test-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5481,8 +5489,8 @@ dependencies = [ "task-sensor-api", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5497,8 +5505,8 @@ dependencies = [ "num-traits", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5530,8 +5538,8 @@ dependencies = [ "ssmarshal", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5549,8 +5557,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5580,8 +5588,8 @@ dependencies = [ "vsc7448", "vsc7448-pac", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5628,8 +5636,8 @@ dependencies = [ "userlib", "vsc7448-pac", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5651,8 +5659,8 @@ dependencies = [ "smoltcp", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5679,6 +5687,7 @@ dependencies = [ "counters", "drv-cpu-seq-api", "drv-rng-api", + "gateway-ereport-messages", "hubris-task-names", "idol", "idol-runtime", @@ -5692,8 +5701,8 @@ dependencies = [ "static_assertions", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5702,14 +5711,15 @@ version = "0.1.0" dependencies = [ "counters", "derive-idol-err", + "gateway-ereport-messages", "host-sp-messages", "idol", "idol-runtime", "num-traits", "oxide-barcode", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5761,8 +5771,8 @@ dependencies = [ "task-power-api", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5780,8 +5790,8 @@ dependencies = [ "static_assertions", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5804,8 +5814,8 @@ dependencies = [ "serde", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5825,8 +5835,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5843,8 +5853,8 @@ dependencies = [ "ringbuf", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5896,8 +5906,8 @@ dependencies = [ "task-sensor-api", "task-thermal-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5915,8 +5925,8 @@ dependencies = [ "serde", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5947,8 +5957,8 @@ dependencies = [ "task-net-api", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5972,8 +5982,8 @@ dependencies = [ "idol", "task-net-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5995,8 +6005,8 @@ dependencies = [ "serde", "task-validate-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6014,8 +6024,8 @@ dependencies = [ "serde", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6035,8 +6045,8 @@ dependencies = [ "ringbuf", "task-vpd-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6050,8 +6060,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6080,8 +6090,8 @@ dependencies = [ "build-util", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6094,8 +6104,8 @@ dependencies = [ "num-traits", "test-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6110,8 +6120,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6126,8 +6136,8 @@ dependencies = [ "ssmarshal", "test-idol-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6143,8 +6153,8 @@ dependencies = [ "ringbuf", "test-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6164,8 +6174,8 @@ dependencies = [ "test-api", "test-idol-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6453,8 +6463,8 @@ dependencies = [ "ssmarshal", "unwrap-lite", "volatile-const", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6543,8 +6553,8 @@ dependencies = [ "userlib", "vsc-err", "vsc7448-pac", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6932,7 +6942,7 @@ dependencies = [ "toml-task", "toml_edit", "walkdir", - "zerocopy 0.8.25", + "zerocopy 0.8.26", "zip", ] @@ -6958,11 +6968,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "zerocopy-derive 0.8.25", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6989,9 +6999,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 01689b3ba6..34ded37e56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,7 @@ zip = { version = "0.6", default-features = false, features = ["bzip2", "deflate attest-data = { git = "https://github.com/oxidecomputer/dice-util", default-features = false, version = "0.4.0" } dice-mfg-msgs = { git = "https://github.com/oxidecomputer/dice-util", default-features = false, version = "0.2.1" } gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", default-features = false, features = ["smoltcp"] } +gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", default-features = false } gimlet-inspector-protocol = { git = "https://github.com/oxidecomputer/gimlet-inspector-protocol", version = "0.1.0" } hif = { git = "https://github.com/oxidecomputer/hif", default-features = false } humpty = { git = "https://github.com/oxidecomputer/humpty", default-features = false, version = "0.1.3" } diff --git a/idl/packrat.idol b/idl/packrat.idol index db275d5723..42e5b44169 100644 --- a/idl/packrat.idol +++ b/idl/packrat.idol @@ -108,7 +108,9 @@ Interface( "read_ereports": ( doc: "Read ereports starting with a watermark (ENA) value", args: { - "first_ena": "u64", + "request_id": "ereport_messages::RequestIdV0", + "restart_id": "ereport_messages::RestartId", + "start_ena": "ereport_messages::Ena", }, leases: { "data": (type: "[u8]", write: true), @@ -126,4 +128,3 @@ Interface( ), }, ) - diff --git a/task/packrat-api/Cargo.toml b/task/packrat-api/Cargo.toml index 91e00b0ef1..cbe37acc06 100644 --- a/task/packrat-api/Cargo.toml +++ b/task/packrat-api/Cargo.toml @@ -10,6 +10,7 @@ host-sp-messages.path = "../../lib/host-sp-messages" oxide-barcode.path = "../../lib/oxide-barcode" userlib.path = "../../sys/userlib" +gateway-ereport-messages.workspace = true idol-runtime.workspace = true num-traits.workspace = true zerocopy.workspace = true diff --git a/task/packrat-api/src/lib.rs b/task/packrat-api/src/lib.rs index ea6a878335..44ea8b62b7 100644 --- a/task/packrat-api/src/lib.rs +++ b/task/packrat-api/src/lib.rs @@ -12,6 +12,7 @@ use zerocopy::{ FromBytes, Immutable, IntoBytes, KnownLayout, LittleEndian, U16, }; +pub use gateway_ereport_messages as ereport_messages; pub use host_sp_messages::HostStartupOptions; pub use oxide_barcode::VpdIdentity; diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 37da052bd8..787cdf7cef 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +gateway-ereport-messages.workspace = true idol-runtime.workspace = true num-traits.workspace = true spd.workspace = true diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 4b63997485..de07a76b28 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -30,19 +30,19 @@ #![no_main] use core::convert::Infallible; +use gateway_ereport_messages as ereport_messages; use idol_runtime::{ ClientError, Leased, LenLimit, NotificationHandler, RequestError, }; use minicbor::CborLen; -use ringbuf::{ringbuf, ringbuf_entry}; +use ringbuf::{counted_ringbuf, ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; use task_packrat_api::{ CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, VpdIdentity, }; -use userlib::{RecvMessage, TaskId}; -use zerocopy::little_endian::{U128, U64}; -use zerocopy::{AsBytes, FromBytes, Unaligned}; +use userlib::{RecvMessage, TaskId, UnwrapLite}; +use zerocopy::IntoBytes; #[cfg(feature = "gimlet")] mod gimlet; @@ -99,7 +99,7 @@ enum TraceSet { /// Separate ring buffer for ereport events, as we probably don't care that much /// about the sequence of ereport events relative to other packrat API events. -#[derive(Copy, Clone, counters::Count)] +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] enum EreportTrace { #[count(skip)] None, @@ -116,7 +116,7 @@ counted_ringbuf!(EREPORT_RINGBUF, EreportTrace, 16, EreportTrace::None); /// report consumes a small amount of this (currently 12 bytes). const EREPORT_BUFFER_SIZE: usize = 4096; -task_slot!(RNG, rng); +userlib::task_slot!(RNG, rng); #[export_name = "main"] fn main() -> ! { @@ -161,7 +161,7 @@ fn main() -> ! { identity, ereport_storage, ereport_recv, - restart_nonce: get_restart_nonce(), + restart_id: get_restart_id(), next_ena: 1, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData::new(gimlet_bufs), @@ -179,13 +179,12 @@ fn main() -> ! { // This is a function so that the 16-byte buffer we provide to the RNG IPC // doesn't stay on the stack forever (as it might if we did this in `main`?). -fn get_restart_nonce() -> u128 { +fn get_restart_id() -> ereport_messages::RestartId { let rng = drv_rng_api::Rng::from(RNG.get_task_id()); let mut buf = [0u8; 16]; // XXX(eliza): if this fails we are TURBO SCREWED... rng.fill(&mut buf).unwrap_lite(); - // endianness doesn't matter here, they're random bytes lol - u128::from_be_bytes(buf) + u128::from_le_bytes(buf).into() } struct ServerImpl { @@ -193,7 +192,7 @@ struct ServerImpl { identity: &'static mut Option, ereport_storage: &'static mut snitch_core::Store, ereport_recv: &'static mut [u8; 1024], - restart_nonce: u128, + restart_id: gateway_ereport_messages::RestartId, next_ena: u64, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData, @@ -476,7 +475,7 @@ impl idl::InOrderPackratImpl for ServerImpl { // eaten... ringbuf_entry!( EREPORT_RINGBUF, - EreportTrace { + EreportTrace::EreportDelivered { src: msg.sender, len: data.len() as u32 } @@ -487,17 +486,28 @@ impl idl::InOrderPackratImpl for ServerImpl { fn read_ereports( &mut self, _msg: &RecvMessage, - begin_ena: u64, + request_id: ereport_messages::RequestIdV0, + restart_id: ereport_messages::RestartId, + begin_ena: ereport_messages::Ena, data: Leased, ) -> Result> { - // Skip over a header-sized initial chunk, plus space for the - let first_data_byte = size_of::(); + // Skip over a header-sized initial chunk. + let first_data_byte = size_of::(); let mut position = first_data_byte; let mut first_written_ena = None; + // If the requested restart ID matches the current restart ID, then read + // from the requested ENA. If not, start at ENA 0. + let begin_ena = if restart_id == self.restart_id { + begin_ena.into() + } else { + // TODO(eliza): encode metadata if restart ID doesn't match! + 0 + }; + // Beginning with the first - for r in self.ereport_storage.read_from(begin_ena) { + for r in self.ereport_storage.read_from(begin_ena.into()) { if first_written_ena.is_none() { first_written_ena = Some(r.ena); // Start CBOR list @@ -562,12 +572,13 @@ impl idl::InOrderPackratImpl for ServerImpl { } let first_ena = first_written_ena.unwrap_or(self.next_ena); - let header = EreportResponse { - version: 0, - message_cookie: todo!(), - instance_id: self.restart_nonce.into(), - first_ena: first_ena.into(), - }; + let header = ereport_messages::ResponseHeader::V0( + ereport_messages::ResponseHeaderV0 { + request_id, + restart_id: self.restart_id, + start_ena: first_ena.into(), + }, + ); data.write_range(0..size_of_val(&header), header.as_bytes()) .map_err(|_| ClientError::WentAway.fail())?; Ok(position) @@ -594,15 +605,6 @@ impl NotificationHandler for ServerImpl { } } -#[derive(Copy, Clone, Debug, FromBytes, AsBytes, Unaligned)] -#[repr(C)] -struct EreportResponse { - version: u8, - message_cookie: u8, - instance_id: U128, - first_ena: U64, -} - struct ByteGather<'a, 'b>(&'a [u8], &'b [u8]); impl minicbor::Encode for ByteGather<'_, '_> { @@ -631,8 +633,8 @@ impl CborLen for ByteGather<'_, '_> { mod idl { use super::{ - CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, - VpdIdentity, + ereport_messages, CacheGetError, CacheSetError, HostStartupOptions, + MacAddressBlock, VpdIdentity, }; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); From 2df36e8e695758b0f78ec71a5abce384b792bf70 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 09:28:49 -0700 Subject: [PATCH 07/61] add rng_driver deps --- app/cosmo/base.toml | 14 +++++++++++--- app/gimlet/base.toml | 16 ++++++++++++---- app/gimletlet/app.toml | 12 ++++++++++-- app/sidecar/base.toml | 2 +- task/packrat/src/main.rs | 2 +- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index bc6783caec..8e4c1f5c1a 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -120,12 +120,20 @@ notifications = ["i2c1-irq", "i2c2-irq", "i2c3-irq", "i2c4-irq"] "i2c4.event" = "i2c4-irq" "i2c4.error" = "i2c4-irq" +[tasks.rng_driver] +features = ["h753"] +name = "drv-stm32h7-rng" +priority = 2 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys"] + [tasks.packrat] name = "task-packrat" -priority = 1 +priority = 3 start = true -# task-slots is explicitly empty: packrat should not send IPCs! -task-slots = [] +task-slots = ["rng_driver"] features = ["cosmo"] [tasks.thermal] diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index a6878740da..f2a49385c2 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -110,7 +110,7 @@ notifications = ["i2c2-irq", "i2c3-irq", "i2c4-irq"] [tasks.spd] name = "task-gimlet-spd" features = ["h753"] -priority = 2 +priority = 4 max-sizes = {flash = 16384, ram = 16384} uses = ["i2c1"] start = true @@ -123,10 +123,9 @@ notifications = ["i2c1-irq", "jefe-state-change"] [tasks.packrat] name = "task-packrat" -priority = 1 +priority = 3 start = true -# task-slots is explicitly empty: packrat should not send IPCs! -task-slots = [] +task-slots = ["rng_driver"] features = ["gimlet"] [tasks.thermal] @@ -195,6 +194,15 @@ interrupts = {"hash.irq" = "hash-irq"} task-slots = ["sys"] notifications = ["hash-irq"] +[tasks.rng_driver] +features = ["h753"] +name = "drv-stm32h7-rng" +priority = 2 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys"] + [tasks.hf] name = "drv-gimlet-hf-server" features = ["h753"] diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 5d7a85650f..3a572f0de4 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -40,13 +40,21 @@ port = "D" pin = 0 owner = {name = "sprot", notification = "rot_irq"} +[tasks.rng_driver] +features = ["h753"] +name = "drv-stm32h7-rng" +priority = 2 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys"] + [tasks.packrat] name = "task-packrat" priority = 3 max-sizes = {flash = 8192, ram = 2048} start = true -# task-slots is explicitly empty: packrat should not send IPCs! -task-slots = [] +task-slots = ["rng_driver"] [caboose] tasks = ["control_plane_agent"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index db88e6cbb2..ff6acda18c 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -56,7 +56,7 @@ owner = {name = "sprot", notification = "rot_irq"} [tasks.rng_driver] features = ["h753"] name = "drv-stm32h7-rng" -priority = 6 +priority = 2 uses = ["rng"] start = true stacksize = 512 diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index de07a76b28..0c62d282f7 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -116,7 +116,7 @@ counted_ringbuf!(EREPORT_RINGBUF, EreportTrace, 16, EreportTrace::None); /// report consumes a small amount of this (currently 12 bytes). const EREPORT_BUFFER_SIZE: usize = 4096; -userlib::task_slot!(RNG, rng); +userlib::task_slot!(RNG, rng_driver); #[export_name = "main"] fn main() -> ! { From 9dde194e661811a299cc483a3c5cefa51984cd98 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 10:15:14 -0700 Subject: [PATCH 08/61] implement more of packrat, feature-flag ereports --- app/cosmo/base.toml | 2 +- app/gimlet/base.toml | 2 +- app/gimletlet/app.toml | 2 +- app/sidecar/base.toml | 4 +- idl/packrat.idol | 9 +- task/packrat/Cargo.toml | 7 +- task/packrat/src/ereport.rs | 244 +++++++++++++++++++++++++++++++++++ task/packrat/src/main.rs | 247 ++++++++---------------------------- 8 files changed, 305 insertions(+), 212 deletions(-) create mode 100644 task/packrat/src/ereport.rs diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 8e4c1f5c1a..8d2daafe44 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -134,7 +134,7 @@ name = "task-packrat" priority = 3 start = true task-slots = ["rng_driver"] -features = ["cosmo"] +features = ["cosmo", "ereport"] [tasks.thermal] name = "task-thermal" diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index f2a49385c2..547789cc2d 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -126,7 +126,7 @@ name = "task-packrat" priority = 3 start = true task-slots = ["rng_driver"] -features = ["gimlet"] +features = ["gimlet", "ereport"] [tasks.thermal] name = "task-thermal" diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 3a572f0de4..04920a1fab 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -54,7 +54,7 @@ name = "task-packrat" priority = 3 max-sizes = {flash = 8192, ram = 2048} start = true -task-slots = ["rng_driver"] +task-slots = ["rng_driver", "ereport"] [caboose] tasks = ["control_plane_agent"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index ff6acda18c..a041b63b09 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -260,8 +260,8 @@ name = "task-packrat" priority = 3 max-sizes = {flash = 8192, ram = 2048} start = true -# task-slots is explicitly empty: packrat should not send IPCs! -task-slots = [] +task-slots = ["rng_driver"] +features = ["ereport"] [tasks.sequencer] name = "drv-sidecar-seq-server" diff --git a/idl/packrat.idol b/idl/packrat.idol index 42e5b44169..4e4a0d96a4 100644 --- a/idl/packrat.idol +++ b/idl/packrat.idol @@ -111,6 +111,7 @@ Interface( "request_id": "ereport_messages::RequestIdV0", "restart_id": "ereport_messages::RestartId", "start_ena": "ereport_messages::Ena", + "committed_ena": "ereport_messages::Ena", }, leases: { "data": (type: "[u8]", write: true), @@ -118,13 +119,5 @@ Interface( reply: Simple("usize"), idempotent: true, ), - "flush_ereports": ( - doc: "Free storage for all ereports up thru an ENA.", - args: { - "last_ena": "u64", - }, - reply: Simple("()"), - idempotent: true, - ), }, ) diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 787cdf7cef..3dadaf6470 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -13,7 +13,7 @@ zerocopy.workspace = true zerocopy-derive.workspace = true drv-cpu-seq-api = { path = "../../drv/cpu-seq-api", optional = true } -drv-rng-api = { path = "../../drv/rng-api" } +drv-rng-api = { path = "../../drv/rng-api", optional = true } hubris-task-names = { path = "../../sys/task-names" } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } @@ -21,8 +21,8 @@ counters = { path = "../../lib/counters" } static-cell = { path = "../../lib/static-cell" } task-packrat-api = { path = "../packrat-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } -snitch-core = { version = "0.1.0", path = "../../lib/snitch-core" } -minicbor = "0.26.4" +snitch-core = { version = "0.1.0", path = "../../lib/snitch-core", optional = true } +minicbor = { version = "0.26.4", optional = true } [build-dependencies] anyhow.workspace = true @@ -37,6 +37,7 @@ gimlet = ["drv-cpu-seq-api"] grapefruit = [] boot-kmdb = [] no-ipc-counters = ["idol/no-counters"] +ereport = ["dep:drv-rng-api", "dep:snitch-core", "dep:minicbor"] # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs new file mode 100644 index 0000000000..e500492c04 --- /dev/null +++ b/task/packrat/src/ereport.rs @@ -0,0 +1,244 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use super::ereport_messages; + +use core::convert::Infallible; +use idol_runtime::{ClientError, Leased, LenLimit, RequestError}; +use minicbor::CborLen; +use ringbuf::{counted_ringbuf, ringbuf_entry}; +use userlib::{RecvMessage, TaskId, UnwrapLite}; +use zerocopy::IntoBytes; + +pub(crate) struct EreportStore { + storage: &'static mut snitch_core::Store, + recv: &'static mut [u8; RECV_BUF_SIZE], + next_ena: u64, + restart_id: ereport_messages::RestartId, +} + +pub(crate) struct EreportBufs { + storage: snitch_core::Store, + recv: [u8; RECV_BUF_SIZE], +} + +/// Number of bytes of RAM dedicated to ereport storage. Each individual +/// report consumes a small amount of this (currently 12 bytes). +const STORE_SIZE: usize = 4096; + +/// Number of bytes for the receive buffer. This only needs to fit a single +/// ereport at a time (and implicitly, limits the maximum size of an ereport). +pub(crate) const RECV_BUF_SIZE: usize = 1024; + +userlib::task_slot!(RNG, rng_driver); + +/// Separate ring buffer for ereport events, as we probably don't care that much +/// about the sequence of ereport events relative to other packrat API events. +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] +enum EreportTrace { + #[count(skip)] + None, + EreportDelivered { + src: TaskId, + len: u32, + }, + Flushed { + committed_ena: ereport_messages::Ena, + }, + RestartIdMismatch { + current: ereport_messages::RestartId, + requested: ereport_messages::RestartId, + }, +} +counted_ringbuf!(EreportTrace, 16, EreportTrace::None); + +impl EreportStore { + pub(crate) fn new( + EreportBufs { + ref mut storage, + ref mut recv, + }: &'static mut EreportBufs, + ) -> Self { + let rng = drv_rng_api::Rng::from(RNG.get_task_id()); + let mut buf = [0u8; 16]; + // XXX(eliza): if this fails we are TURBO SCREWED... + rng.fill(&mut buf).unwrap_lite(); + let restart_id = + ereport_messages::RestartId::from(u128::from_le_bytes(buf)); + + storage.initialize(0, 0); // TODO tid timestamp + + Self { + storage, + recv, + next_ena: 0, + restart_id, + } + } +} + +impl EreportStore { + pub(crate) fn deliver_ereport( + &mut self, + msg: &RecvMessage, + data: LenLimit, RECV_BUF_SIZE>, + ) -> Result<(), RequestError> { + data.read_range(0..data.len(), self.recv) + .map_err(|_| ClientError::WentAway.fail())?; + self.storage + .insert(msg.sender.0, 0, &self.recv[..data.len()]); + // TODO(eliza): would maybe be nice to say something if the ereport got + // eaten... + ringbuf_entry!(EreportTrace::EreportDelivered { + src: msg.sender, + len: data.len() as u32 + }); + Ok(()) + } + + pub(crate) fn read_ereports( + &mut self, + request_id: ereport_messages::RequestIdV0, + restart_id: ereport_messages::RestartId, + begin_ena: ereport_messages::Ena, + committed_ena: ereport_messages::Ena, + data: Leased, + ) -> Result> { + // Skip over a header-sized initial chunk. + let first_data_byte = size_of::(); + + let mut position = first_data_byte; + let mut first_written_ena = None; + + // If the requested restart ID matches the current restart ID, then read + // from the requested ENA. If not, start at ENA 0. + let begin_ena = if restart_id == self.restart_id { + // If the restart ID matches, flush previous ereports up to + // `committed_ena`, if there is one. + if committed_ena != ereport_messages::Ena::NONE { + self.storage.flush_thru(committed_ena.into()); + ringbuf_entry!(EreportTrace::Flushed { committed_ena }); + } + begin_ena.into() + } else { + // TODO(eliza): encode metadata if restart ID doesn't match! + ringbuf_entry!(EreportTrace::RestartIdMismatch { + requested: restart_id, + current: self.restart_id + }); + 0 + }; + + // Beginning with the first + for r in self.storage.read_from(begin_ena.into()) { + if first_written_ena.is_none() { + first_written_ena = Some(r.ena); + // Start CBOR list + // XXX(eliza): in theory it might be nicer to use + // `minicbor::data::Token::BeginArray` here, but it's way more + // annoying in practice... + data.write_at(position, 0x9f) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + } + + let tid = TaskId(r.tid); + let task_name = hubris_task_names::TASK_NAMES + .get(tid.index()) + .copied() + .unwrap_or({ + // This represents an internal error, where we've recorded + // an out-of-range task ID somehow. We still want to get the + // ereport out, so we'll use a recognizable but illegal task + // name to indicate that it's missing. + "-" // TODO + }); + // TODO write task name + let generation = tid.generation(); + // TODO write generation number + r.timestamp; // TODO + + let entry = ( + task_name, + u8::from(generation), + r.timestamp, + ByteGather(r.slices.0, r.slices.1), + ); + let mut c = + minicbor::encode::write::Cursor::new(&mut self.recv[..]); + match minicbor::encode(&entry, &mut c) { + Ok(()) => { + let size = c.position(); + data.write_range( + position..position + size, + &self.recv[..size], + ) + .map_err(|_| ClientError::WentAway.fail())?; + position += size; + } + Err(_end) => { + // This is an odd one; we've admitted a record into our + // queue that won't fit in our buffer. This can happen + // because of the encoding overhead, in theory, but should + // be prevented. + // TODO + } + } + } + + if first_written_ena.is_some() { + // End CBOR list, if we wrote anything. + data.write_at(position, 0xff) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + } + + let first_ena = first_written_ena.unwrap_or(self.next_ena); + let header = ereport_messages::ResponseHeader::V0( + ereport_messages::ResponseHeaderV0 { + request_id, + restart_id: self.restart_id, + start_ena: first_ena.into(), + }, + ); + data.write_range(0..size_of_val(&header), header.as_bytes()) + .map_err(|_| ClientError::WentAway.fail())?; + Ok(position) + } +} + +impl EreportBufs { + pub(crate) const fn new() -> Self { + Self { + storage: snitch_core::Store::DEFAULT, + recv: [0u8; RECV_BUF_SIZE], + } + } +} + +struct ByteGather<'a, 'b>(&'a [u8], &'b [u8]); + +impl minicbor::Encode for ByteGather<'_, '_> { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.bytes_len((self.0.len() + self.1.len()) as u64)?; + e.writer_mut() + .write_all(self.0) + .map_err(minicbor::encode::Error::write)?; + e.writer_mut() + .write_all(self.1) + .map_err(minicbor::encode::Error::write)?; + Ok(()) + } +} + +impl CborLen for ByteGather<'_, '_> { + fn cbor_len(&self, ctx: &mut C) -> usize { + let n = self.0.len() + self.1.len(); + n.cbor_len(ctx) + n + } +} diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 0c62d282f7..fc6120135d 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -31,18 +31,14 @@ use core::convert::Infallible; use gateway_ereport_messages as ereport_messages; -use idol_runtime::{ - ClientError, Leased, LenLimit, NotificationHandler, RequestError, -}; -use minicbor::CborLen; -use ringbuf::{counted_ringbuf, ringbuf, ringbuf_entry}; +use idol_runtime::{Leased, LenLimit, NotificationHandler, RequestError}; +use ringbuf::{ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; use task_packrat_api::{ CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, VpdIdentity, }; -use userlib::{RecvMessage, TaskId, UnwrapLite}; -use zerocopy::IntoBytes; +use userlib::RecvMessage; #[cfg(feature = "gimlet")] mod gimlet; @@ -61,6 +57,9 @@ use gimlet::SpdData; #[cfg(feature = "cosmo")] use cosmo::SpdData; +#[cfg(feature = "ereport")] +mod ereport; + #[cfg(not(any(feature = "gimlet", feature = "cosmo")))] type SpdData = spd_data::SpdData<0, 0>; // dummy type @@ -97,27 +96,7 @@ enum TraceSet { AttemptedSetToNewValue(T), } -/// Separate ring buffer for ereport events, as we probably don't care that much -/// about the sequence of ereport events relative to other packrat API events. -#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] -enum EreportTrace { - #[count(skip)] - None, - EreportDelivered { - src: TaskId, - len: u32, - }, -} - ringbuf!(Trace, 16, Trace::None); -counted_ringbuf!(EREPORT_RINGBUF, EreportTrace, 16, EreportTrace::None); - -/// Number of bytes of RAM dedicated to ereport buffer storage. Each individual -/// report consumes a small amount of this (currently 12 bytes). -const EREPORT_BUFFER_SIZE: usize = 4096; - -userlib::task_slot!(RNG, rng_driver); - #[export_name = "main"] fn main() -> ! { struct StaticBufs { @@ -127,8 +106,8 @@ fn main() -> ! { gimlet_bufs: gimlet::StaticBufs, #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs, - ereport_storage: snitch_core::Store, - ereport_recv: [u8; 1024], + #[cfg(feature = "ereport")] + ereport_bufs: ereport::EreportBufs, } let StaticBufs { ref mut mac_address_block, @@ -137,8 +116,8 @@ fn main() -> ! { ref mut gimlet_bufs, #[cfg(feature = "cosmo")] ref mut cosmo_bufs, - ref mut ereport_storage, - ref mut ereport_recv, + #[cfg(feature = "ereport")] + ref mut ereport_bufs, } = { static BUFS: ClaimOnceCell = ClaimOnceCell::new(StaticBufs { @@ -148,27 +127,23 @@ fn main() -> ! { gimlet_bufs: gimlet::StaticBufs::new(), #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs::new(), - ereport_storage: snitch_core::Store::DEFAULT, - ereport_recv: [0; 1024], + #[cfg(feature = "ereport")] + ereport_bufs: ereport::EreportBufs::new(), }); BUFS.claim() }; - ereport_storage.initialize(0, 0); // TODO tid timestamp - let mut server = ServerImpl { mac_address_block, identity, - ereport_storage, - ereport_recv, - restart_id: get_restart_id(), - next_ena: 1, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData::new(gimlet_bufs), #[cfg(feature = "grapefruit")] grapefruit_data: grapefruit::GrapefruitData::new(), #[cfg(feature = "cosmo")] cosmo_data: cosmo::CosmoData::new(cosmo_bufs), + #[cfg(feature = "ereport")] + ereport_store: ereport::EreportStore::new(ereport_bufs), }; let mut buffer = [0; idl::INCOMING_SIZE]; @@ -177,29 +152,17 @@ fn main() -> ! { } } -// This is a function so that the 16-byte buffer we provide to the RNG IPC -// doesn't stay on the stack forever (as it might if we did this in `main`?). -fn get_restart_id() -> ereport_messages::RestartId { - let rng = drv_rng_api::Rng::from(RNG.get_task_id()); - let mut buf = [0u8; 16]; - // XXX(eliza): if this fails we are TURBO SCREWED... - rng.fill(&mut buf).unwrap_lite(); - u128::from_le_bytes(buf).into() -} - struct ServerImpl { mac_address_block: &'static mut Option, identity: &'static mut Option, - ereport_storage: &'static mut snitch_core::Store, - ereport_recv: &'static mut [u8; 1024], - restart_id: gateway_ereport_messages::RestartId, - next_ena: u64, #[cfg(feature = "gimlet")] gimlet_data: gimlet::GimletData, #[cfg(feature = "grapefruit")] grapefruit_data: grapefruit::GrapefruitData, #[cfg(feature = "cosmo")] cosmo_data: cosmo::CosmoData, + #[cfg(feature = "ereport")] + ereport_store: ereport::EreportStore, } impl ServerImpl { @@ -459,138 +422,56 @@ impl idl::InOrderPackratImpl for ServerImpl { } } + #[cfg(not(feature = "ereport"))] + fn deliver_ereport( + &mut self, + _: &RecvMessage, + _: LenLimit, 1024usize>, + ) -> Result<(), RequestError> { + // go away, we don't know how to do that + idol_runtime::ClientError::UnknownOperation.fail() + } + + #[cfg(feature = "ereport")] fn deliver_ereport( &mut self, msg: &RecvMessage, data: LenLimit, 1024usize>, ) -> Result<(), RequestError> { - data.read_range(0..data.len(), self.ereport_recv) - .map_err(|_| ClientError::WentAway.fail())?; - self.ereport_storage.insert( - msg.sender.0, - 0, - &self.ereport_recv[..data.len()], - ); - // TODO(eliza): would maybe be nice to say something if the ereport got - // eaten... - ringbuf_entry!( - EREPORT_RINGBUF, - EreportTrace::EreportDelivered { - src: msg.sender, - len: data.len() as u32 - } - ); - Ok(()) + self.ereport_store.deliver_ereport(msg, data) } + #[cfg(not(feature = "ereport"))] fn read_ereports( &mut self, _msg: &RecvMessage, - request_id: ereport_messages::RequestIdV0, - restart_id: ereport_messages::RestartId, - begin_ena: ereport_messages::Ena, - data: Leased, + _: ereport_messages::RequestIdV0, + _: ereport_messages::RestartId, + _: ereport_messages::Ena, + _: ereport_messages::Ena, + _: Leased, ) -> Result> { - // Skip over a header-sized initial chunk. - let first_data_byte = size_of::(); - - let mut position = first_data_byte; - let mut first_written_ena = None; - - // If the requested restart ID matches the current restart ID, then read - // from the requested ENA. If not, start at ENA 0. - let begin_ena = if restart_id == self.restart_id { - begin_ena.into() - } else { - // TODO(eliza): encode metadata if restart ID doesn't match! - 0 - }; - - // Beginning with the first - for r in self.ereport_storage.read_from(begin_ena.into()) { - if first_written_ena.is_none() { - first_written_ena = Some(r.ena); - // Start CBOR list - // XXX(eliza): in theory it might be nicer to use - // `minicbor::data::Token::BeginArray` here, but it's way more - // annoying in practice... - data.write_at(position, 0x9f) - .map_err(|_| ClientError::WentAway.fail())?; - position += 1; - } - - let tid = TaskId(r.tid); - let task_name = hubris_task_names::TASK_NAMES - .get(tid.index()) - .copied() - .unwrap_or({ - // This represents an internal error, where we've recorded - // an out-of-range task ID somehow. We still want to get the - // ereport out, so we'll use a recognizable but illegal task - // name to indicate that it's missing. - "-" // TODO - }); - // TODO write task name - let generation = tid.generation(); - // TODO write generation number - r.timestamp; // TODO - - let entry = ( - task_name, - u8::from(generation), - r.timestamp, - ByteGather(r.slices.0, r.slices.1), - ); - let mut c = minicbor::encode::write::Cursor::new( - &mut self.ereport_recv[..], - ); - match minicbor::encode(&entry, &mut c) { - Ok(()) => { - let size = c.position(); - data.write_range( - position..position + size, - &self.ereport_recv[..size], - ) - .map_err(|_| ClientError::WentAway.fail())?; - position += size; - } - Err(_end) => { - // This is an odd one; we've admitted a record into our - // queue that won't fit in our buffer. This can happen - // because of the encoding overhead, in theory, but should - // be prevented. - // TODO - } - } - } - - if first_written_ena.is_some() { - // End CBOR list, if we wrote anything. - data.write_at(position, 0xff) - .map_err(|_| ClientError::WentAway.fail())?; - position += 1; - } - - let first_ena = first_written_ena.unwrap_or(self.next_ena); - let header = ereport_messages::ResponseHeader::V0( - ereport_messages::ResponseHeaderV0 { - request_id, - restart_id: self.restart_id, - start_ena: first_ena.into(), - }, - ); - data.write_range(0..size_of_val(&header), header.as_bytes()) - .map_err(|_| ClientError::WentAway.fail())?; - Ok(position) + // go away, we don't know how to do that + idol_runtime::ClientError::UnknownOperation.fail() } - fn flush_ereports( + #[cfg(feature = "ereport")] + fn read_ereports( &mut self, _msg: &RecvMessage, - last_ena: u64, - ) -> Result<(), RequestError> { - self.ereport_storage.flush_thru(last_ena); - Ok(()) + request_id: ereport_messages::RequestIdV0, + restart_id: ereport_messages::RestartId, + begin_ena: ereport_messages::Ena, + committed_ena: ereport_messages::Ena, + data: Leased, + ) -> Result> { + self.ereport_store.read_ereports( + request_id, + restart_id, + begin_ena, + committed_ena, + data, + ) } } @@ -605,32 +486,6 @@ impl NotificationHandler for ServerImpl { } } -struct ByteGather<'a, 'b>(&'a [u8], &'b [u8]); - -impl minicbor::Encode for ByteGather<'_, '_> { - fn encode( - &self, - e: &mut minicbor::Encoder, - _ctx: &mut C, - ) -> Result<(), minicbor::encode::Error> { - e.bytes_len((self.0.len() + self.1.len()) as u64)?; - e.writer_mut() - .write_all(self.0) - .map_err(minicbor::encode::Error::write)?; - e.writer_mut() - .write_all(self.1) - .map_err(minicbor::encode::Error::write)?; - Ok(()) - } -} - -impl CborLen for ByteGather<'_, '_> { - fn cbor_len(&self, ctx: &mut C) -> usize { - let n = self.0.len() + self.1.len(); - n.cbor_len(ctx) + n - } -} - mod idl { use super::{ ereport_messages, CacheGetError, CacheSetError, HostStartupOptions, From 7dca1ed7935e54c28cae2b8d4ee1a4e0e5ac14cc Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 10:37:18 -0700 Subject: [PATCH 09/61] add VPD metadata --- task/packrat/src/ereport.rs | 35 ++++++++++++++++++++++++++++++++++- task/packrat/src/main.rs | 1 + 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index e500492c04..aa2af4d5ac 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -8,6 +8,7 @@ use core::convert::Infallible; use idol_runtime::{ClientError, Leased, LenLimit, RequestError}; use minicbor::CborLen; use ringbuf::{counted_ringbuf, ringbuf_entry}; +use task_packrat_api::VpdIdentity; use userlib::{RecvMessage, TaskId, UnwrapLite}; use zerocopy::IntoBytes; @@ -104,6 +105,7 @@ impl EreportStore { begin_ena: ereport_messages::Ena, committed_ena: ereport_messages::Ena, data: Leased, + vpd: Option<&VpdIdentity>, ) -> Result> { // Skip over a header-sized initial chunk. let first_data_byte = size_of::(); @@ -122,11 +124,42 @@ impl EreportStore { } begin_ena.into() } else { - // TODO(eliza): encode metadata if restart ID doesn't match! ringbuf_entry!(EreportTrace::RestartIdMismatch { requested: restart_id, current: self.restart_id }); + + // Encode the metadata map into our buffer. + // TODO(eliza): this will panic if the encoded metadata map is + // longer than 1024B...currently it should never be that, given that + // everything we encode here is fixed-size. But, yuck... + let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); + let mut encoder = minicbor::Encoder::new(c); + encoder.begin_map().unwrap_lite(); + if let Some(vpd) = vpd { + encoder + .str("baseboard_part_number") + .unwrap_lite() + .bytes(vpd.part_number.as_bytes()) + .unwrap_lite() + .str("baseboard_serial_number") + .unwrap_lite() + .bytes(vpd.serial.as_bytes()) + .unwrap_lite() + .str("rev") + .unwrap_lite() + .u32(vpd.revision) + .unwrap_lite(); + } + encoder.end().unwrap_lite(); + + // Write the encoded metadata map. + let size = encoder.into_writer().position(); + data.write_range(position..position + size, &self.recv[..size]) + .map_err(|_| ClientError::WentAway.fail())?; + position += size; + + // Begin at ENA 0 0 }; diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index fc6120135d..6f534d0094 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -471,6 +471,7 @@ impl idl::InOrderPackratImpl for ServerImpl { begin_ena, committed_ena, data, + self.identity.as_ref(), ) } } From a0d29548a871cd0ac5518da29e61de116a9a6375 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 11:17:39 -0700 Subject: [PATCH 10/61] reticulating app.toml --- app/gimletlet/app.toml | 3 ++- app/psc/base.toml | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 04920a1fab..36e5c59ff0 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -54,7 +54,8 @@ name = "task-packrat" priority = 3 max-sizes = {flash = 8192, ram = 2048} start = true -task-slots = ["rng_driver", "ereport"] +task-slots = ["rng_driver"] +features = ["ereport"] [caboose] tasks = ["control_plane_agent"] diff --git a/app/psc/base.toml b/app/psc/base.toml index 56ac3f5af3..bcf5f2af2d 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -91,6 +91,14 @@ owner = {name = "sequencer", notification = "psu_pwr_ok_6"} +[tasks.rng_driver] +features = ["h753"] +name = "drv-stm32h7-rng" +priority = 2 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys"] [tasks.i2c_driver] name = "drv-stm32xx-i2c-server" @@ -113,8 +121,8 @@ name = "task-packrat" priority = 3 max-sizes = {flash = 8192, ram = 2048} start = true -# task-slots is explicitly empty: packrat should not send IPCs! -task-slots = [] +task-slots = ["rng_driver"] +features = ["ereport"] [tasks.sequencer] name = "drv-psc-seq-server" From 4689314510252f63d04a46873a1997a02f678ebb Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 12:58:59 -0700 Subject: [PATCH 11/61] draw the rest of the owl --- Cargo.lock | 17 ++++++ app/gimletlet/app.toml | 17 ++++++ task/snitch/Cargo.toml | 32 +++++++++++ task/snitch/build.rs | 8 +++ task/snitch/src/main.rs | 124 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+) create mode 100644 task/snitch/Cargo.toml create mode 100644 task/snitch/build.rs create mode 100644 task/snitch/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 5841be4b1f..ee1e2566be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5857,6 +5857,23 @@ dependencies = [ "zerocopy-derive 0.8.26", ] +[[package]] +name = "task-snitch" +version = "0.1.0" +dependencies = [ + "anyhow", + "build-util", + "counters", + "gateway-ereport-messages", + "idol-runtime", + "static-cell", + "task-net-api", + "task-packrat-api", + "userlib", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", +] + [[package]] name = "task-sp-measure" version = "0.1.0" diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 36e5c59ff0..37fd4405d9 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -197,6 +197,15 @@ task-slots = ["net", "packrat"] features = ["vlan"] notifications = ["socket"] +[tasks.snitch] +name = "task-snitch" +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + # VLAN configuration [config.net.vlans.sidecar1] vid = 0x301 @@ -245,6 +254,14 @@ port = 11113 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 1024 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# N.B. (eliza): rxbuf could be made smaller... +rx = { packets = 3, bytes = 512 } + [tasks.sprot] name = "drv-stm32h7-sprot-server" priority = 5 diff --git a/task/snitch/Cargo.toml b/task/snitch/Cargo.toml new file mode 100644 index 0000000000..a5ed4bdfd2 --- /dev/null +++ b/task/snitch/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "task-snitch" +version = "0.1.0" +edition = "2021" + +[dependencies] +userlib = { path = "../../sys/userlib" } +counters = { path = "../../lib/counters" } +static-cell = { path = "../../lib/static-cell" } +idol-runtime.workspace = true +zerocopy.workspace = true +zerocopy-derive.workspace = true + +gateway-ereport-messages.workspace = true +task-net-api = { path = "../net-api", features = ["use-smoltcp"] } +task-packrat-api = { path = "../packrat-api" } + +[build-dependencies] +build-util = { path = "../../build/util" } +anyhow = { workspace = true } + +[features] +vlan = ["task-net-api/vlan"] + +[[bin]] +name = "task-snitch" +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/task/snitch/build.rs b/task/snitch/build.rs new file mode 100644 index 0000000000..9596a97e79 --- /dev/null +++ b/task/snitch/build.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> anyhow::Result<()> { + build_util::build_notifications()?; + Ok(()) +} diff --git a/task/snitch/src/main.rs b/task/snitch/src/main.rs new file mode 100644 index 0000000000..2542b130e6 --- /dev/null +++ b/task/snitch/src/main.rs @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#![no_std] +#![no_main] + +use counters::{count, counters, Count}; +use gateway_ereport_messages::Request; +use task_net_api::{ + LargePayloadBehavior, Net, RecvError, SendError, SocketName, +}; +use task_packrat_api::Packrat; +use userlib::{sys_recv_notification, task_slot}; +use zerocopy::TryFromBytes; + +task_slot!(NET, net); +task_slot!(PACKRAT, packrat); + +#[derive(Count, Copy, Clone)] +enum Event { + RecvPacket, + RequestRejected, + Respond, +} + +struct StaticBufs { + rx_buf: [u8; REQ_SZ], + tx_buf: [u8; UDP_PACKET_SZ], +} + +const REQ_SZ: usize = core::mem::size_of::(); +const UDP_PACKET_SZ: usize = 1024; + +counters!(Event); + +#[export_name = "main"] +fn main() -> ! { + let net = Net::from(NET.get_task_id()); + let packrat = Packrat::from(PACKRAT.get_task_id()); + + const SOCKET: SocketName = SocketName::ereport; + + let StaticBufs { + ref mut rx_buf, + ref mut tx_buf, + } = { + static BUFS: static_cell::ClaimOnceCell = + static_cell::ClaimOnceCell::new(StaticBufs { + rx_buf: [0u8; REQ_SZ], + tx_buf: [0u8; UDP_PACKET_SZ], + }); + BUFS.claim() + }; + + loop { + let meta = match net.recv_packet( + SOCKET, + LargePayloadBehavior::Discard, + &mut rx_buf[..], + ) { + Ok(meta) => meta, + Err(RecvError::QueueEmpty) => { + // Our incoming queue is empty. Wait for more packets. + sys_recv_notification(notifications::SOCKET_MASK); + continue; + } + Err(RecvError::ServerRestarted) => { + // `net` restarted; just retry. + continue; + } + }; + + // Okay, we got a packet! + count!(Event::RecvPacket); + let request = + match Request::try_ref_from_bytes(&rx_buf[..meta.size as usize]) { + Ok(req) => req, + Err(_) => { + // We ignore malformatted, truncated, etc. packets. + count!(Event::RequestRejected); + continue; + } + }; + + let size = match request { + Request::V0(req) => packrat.read_ereports( + req.request_id, + req.restart_id, + req.start_ena, + req.committed_ena() + .copied() + .unwrap_or(gateway_ereport_messages::Ena::NONE), + &mut tx_buf[..], + ), + }; + + // With the response packet prepared, we may need to attempt + // sending more than once. + loop { + match net.send_packet(SOCKET, meta, &tx_buf[..size]) { + Ok(()) => { + count!(Event::Respond); + break; + } + // If `net` just restarted, immediately retry our send. + Err(SendError::ServerRestarted) => continue, + // If our tx queue is full, wait for space. This is the + // same notification we get for incoming packets, so we + // might spuriously wake up due to an incoming packet + // (which we can't service anyway because we are still + // waiting to respond to a previous request); once we + // finally succeed in sending we'll peel any queued + // packets off our recv queue at the top of our main + // loop. + Err(SendError::QueueFull) => { + sys_recv_notification(notifications::SOCKET_MASK); + } + } + } + } +} + +include!(concat!(env!("OUT_DIR"), "/notifications.rs")); From c4d45300305b42773be17df63eb3de0333859972 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:07:27 -0700 Subject: [PATCH 12/61] right-size rx buf, give everyone a snitch --- app/cosmo/base.toml | 18 ++++++++++++++++++ app/gimlet/base.toml | 18 ++++++++++++++++++ app/gimletlet/app.toml | 5 +++-- app/psc/base.toml | 18 ++++++++++++++++++ app/sidecar/base.toml | 18 ++++++++++++++++++ 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 8d2daafe44..f0d2dd5efc 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -354,6 +354,15 @@ extern-regions = ["sram1", "sram2", "sram3", "sram4"] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.sbrmi] name = "drv-sbrmi" priority = 4 @@ -1532,6 +1541,15 @@ port = 11113 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 1024 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } + [config.sprot] # ROT_IRQ (af=0 for GPIO, af=15 when EXTI is implemneted) rot_irq = { port = "F", pin = 2, af = 0} # XXX can we use EXTI now? diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 547789cc2d..b71a4e5070 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -345,6 +345,15 @@ extern-regions = ["sram1", "sram2", "sram3", "sram4"] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.sbrmi] name = "drv-sbrmi" priority = 4 @@ -1324,6 +1333,15 @@ port = 23547 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 512 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } + [config.sprot] # ROT_IRQ (af=0 for GPIO, af=15 when EXTI is implemneted) rot_irq = { port = "E", pin = 3, af = 0} diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 37fd4405d9..da505256be 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -259,8 +259,9 @@ kind = "udp" owner = {name = "snitch", notification = "socket"} port = 57005 tx = { packets = 3, bytes = 1024 } -# N.B. (eliza): rxbuf could be made smaller... -rx = { packets = 3, bytes = 512 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } [tasks.sprot] name = "drv-stm32h7-sprot-server" diff --git a/app/psc/base.toml b/app/psc/base.toml index bcf5f2af2d..82f832ca88 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -323,6 +323,15 @@ extern-regions = [ "sram1", "sram2", "sram3", "sram4" ] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.idle] name = "task-idle" priority = 7 @@ -545,3 +554,12 @@ owner = {name = "dump_agent", notification = "socket"} port = 11113 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 1024 } + +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index a041b63b09..5f516e3adc 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -334,6 +334,15 @@ extern-regions = [ "sram1", "sram2", "sram3", "sram4" ] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.idle] name = "task-idle" priority = 8 @@ -1164,6 +1173,15 @@ port = 11112 tx = { packets = 3, bytes = 2048 } rx = { packets = 3, bytes = 2048 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } + [config.auxflash] memory-size = 33_554_432 # 256 Mib / 32 MiB slot-count = 16 # 2 MiB slots From 31a951e18f016048211f5457a006ccf79f0f7e17 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:09:55 -0700 Subject: [PATCH 13/61] rm packrat size limits --- app/gimletlet/app.toml | 1 - app/psc/base.toml | 1 - app/sidecar/base.toml | 1 - 3 files changed, 3 deletions(-) diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index da505256be..147fd8e509 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -52,7 +52,6 @@ task-slots = ["sys"] [tasks.packrat] name = "task-packrat" priority = 3 -max-sizes = {flash = 8192, ram = 2048} start = true task-slots = ["rng_driver"] features = ["ereport"] diff --git a/app/psc/base.toml b/app/psc/base.toml index 82f832ca88..b5216a191c 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -119,7 +119,6 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" priority = 3 -max-sizes = {flash = 8192, ram = 2048} start = true task-slots = ["rng_driver"] features = ["ereport"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 5f516e3adc..0e4fb1b1c0 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -258,7 +258,6 @@ notifications = ["socket", "timer"] [tasks.packrat] name = "task-packrat" priority = 3 -max-sizes = {flash = 8192, ram = 2048} start = true task-slots = ["rng_driver"] features = ["ereport"] From c055af3f898c36c2e7c2673543d05378dd91455c Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:24:01 -0700 Subject: [PATCH 14/61] untangle rng_driver prio mess on gimletlet --- app/gimletlet/app.toml | 13 ++----------- task/packrat/src/ereport.rs | 3 --- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 147fd8e509..5580036754 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -40,18 +40,9 @@ port = "D" pin = 0 owner = {name = "sprot", notification = "rot_irq"} -[tasks.rng_driver] -features = ["h753"] -name = "drv-stm32h7-rng" -priority = 2 -uses = ["rng"] -start = true -stacksize = 512 -task-slots = ["sys"] - [tasks.packrat] name = "task-packrat" -priority = 3 +priority = 7 start = true task-slots = ["rng_driver"] features = ["ereport"] @@ -198,7 +189,7 @@ notifications = ["socket"] [tasks.snitch] name = "task-snitch" -priority = 6 +priority = 8 stacksize = 1200 start = true task-slots = ["net", "packrat"] diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index aa2af4d5ac..3fbabc1e4d 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -187,10 +187,7 @@ impl EreportStore { // name to indicate that it's missing. "-" // TODO }); - // TODO write task name let generation = tid.generation(); - // TODO write generation number - r.timestamp; // TODO let entry = ( task_name, From 3334d726102244a4351f82d104302b7f5cf26fc4 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:26:51 -0700 Subject: [PATCH 15/61] proper timestampiness --- task/packrat/src/ereport.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 3fbabc1e4d..411d8c47e5 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -9,7 +9,7 @@ use idol_runtime::{ClientError, Leased, LenLimit, RequestError}; use minicbor::CborLen; use ringbuf::{counted_ringbuf, ringbuf_entry}; use task_packrat_api::VpdIdentity; -use userlib::{RecvMessage, TaskId, UnwrapLite}; +use userlib::{sys_get_timer, RecvMessage, TaskId, UnwrapLite}; use zerocopy::IntoBytes; pub(crate) struct EreportStore { @@ -87,8 +87,9 @@ impl EreportStore { ) -> Result<(), RequestError> { data.read_range(0..data.len(), self.recv) .map_err(|_| ClientError::WentAway.fail())?; + let timestamp = sys_get_timer().now; self.storage - .insert(msg.sender.0, 0, &self.recv[..data.len()]); + .insert(msg.sender.0, timestamp, &self.recv[..data.len()]); // TODO(eliza): would maybe be nice to say something if the ereport got // eaten... ringbuf_entry!(EreportTrace::EreportDelivered { From db5d6d6673ff8c3221297e786c67a9d54ebd1427 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:29:06 -0700 Subject: [PATCH 16/61] PROPERLY untangle priority mess on gimletlet --- app/gimletlet/app.toml | 4 ++-- app/gimletlet/base-gimletlet2.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 5580036754..6af9b504fc 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -42,7 +42,7 @@ owner = {name = "sprot", notification = "rot_irq"} [tasks.packrat] name = "task-packrat" -priority = 7 +priority = 4 start = true task-slots = ["rng_driver"] features = ["ereport"] @@ -189,7 +189,7 @@ notifications = ["socket"] [tasks.snitch] name = "task-snitch" -priority = 8 +priority = 7 stacksize = 1200 start = true task-slots = ["net", "packrat"] diff --git a/app/gimletlet/base-gimletlet2.toml b/app/gimletlet/base-gimletlet2.toml index 7eb80dc1df..70b169d04b 100644 --- a/app/gimletlet/base-gimletlet2.toml +++ b/app/gimletlet/base-gimletlet2.toml @@ -53,7 +53,7 @@ notifications = ["i2c2-irq", "i2c3-irq", "i2c4-irq"] [tasks.user_leds] name = "drv-user-leds" features = ["stm32h7"] -priority = 5 +priority = 2 max-sizes = {flash = 2048, ram = 1024} start = true task-slots = ["sys"] @@ -105,7 +105,7 @@ start = true [tasks.rng_driver] features = ["h753"] name = "drv-stm32h7-rng" -priority = 6 +priority = 3 uses = ["rng"] start = true stacksize = 512 From adaa20d29c2b444941a8a317f384d6cc6c05d8a0 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:47:40 -0700 Subject: [PATCH 17/61] BEHOLD THE EREPORTULATOR --- Cargo.lock | 12 +++++++ Cargo.toml | 1 + app/gimletlet/app-ereportlet.toml | 17 +++++++++ task/ereportulator/Cargo.toml | 21 +++++++++++ task/ereportulator/src/main.rs | 58 +++++++++++++++++++++++++++++++ task/packrat/Cargo.toml | 2 +- 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 app/gimletlet/app-ereportlet.toml create mode 100644 task/ereportulator/Cargo.toml create mode 100644 task/ereportulator/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index ee1e2566be..68a21da6e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5361,6 +5361,18 @@ dependencies = [ "zerocopy-derive 0.8.26", ] +[[package]] +name = "task-ereportulator" +version = "0.1.0" +dependencies = [ + "idol-runtime", + "minicbor 0.26.4", + "task-packrat-api", + "userlib", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", +] + [[package]] name = "task-framulator" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 34ded37e56..2123a8968f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ leb128 = { version = "0.2.5", default-features = false } lpc55-pac = { version = "0.4", default-features = false } memchr = { version = "2.4", default-features = false } memoffset = { version = "0.6.5", default-features = false } +minicbor = { version = "0.26.4", default-features = false } multimap = { version = "0.8.3", default-features = false } nb = { version = "1", default-features = false } num = { version = "0.4", default-features = false } diff --git a/app/gimletlet/app-ereportlet.toml b/app/gimletlet/app-ereportlet.toml new file mode 100644 index 0000000000..49ac1d41cb --- /dev/null +++ b/app/gimletlet/app-ereportlet.toml @@ -0,0 +1,17 @@ +name = "gimletlet-ereportlet" +inherit = "app.toml" + +[tasks.jefe.config.allowed-callers] +request_reset = ["hiffy"] + +[tasks.hiffy] +features = ["h753", "stm32h7", "i2c", "gpio", "spi"] +task-slots = ["sys", "i2c_driver", "user_leds"] + +[tasks.ereportulator] +name = "task-ereportulator" +priority = 8 +# start paused so that we can be triggered by humility +start = false +task-slots = ["packrat"] +notifications = [] diff --git a/task/ereportulator/Cargo.toml b/task/ereportulator/Cargo.toml new file mode 100644 index 0000000000..048d609ff2 --- /dev/null +++ b/task/ereportulator/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "task-ereportulator" +version = "0.1.0" +edition = "2021" + +[dependencies] +userlib = { path = "../../sys/userlib" } +idol-runtime.workspace = true +zerocopy.workspace = true +zerocopy-derive.workspace = true +task-packrat-api = { path = "../packrat-api" } +minicbor.workspace = true + +[[bin]] +name = "task-ereportulator" +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/task/ereportulator/src/main.rs b/task/ereportulator/src/main.rs new file mode 100644 index 0000000000..0b1639e680 --- /dev/null +++ b/task/ereportulator/src/main.rs @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! +//! # BEHOLD THE EREPORTULATOR! +//! +//! stupid ereport demo task +//! +#![no_std] +#![no_main] + +use task_packrat_api::Packrat; +task_slot!(PACKRAT, packrat); + +use minicbor::Encoder; +use userlib::{sys_recv_notification, task_slot, UnwrapLite}; + +#[export_name = "main"] +fn main() -> ! { + let packrat = Packrat::from(PACKRAT.get_task_id()); + + let mut buf = [0u8; 256]; + + let encoded_len = { + let c = minicbor::encode::write::Cursor::new(&mut buf[..]); + let mut encoder = Encoder::new(c); + encoder + .begin_map() + .unwrap_lite() + .str("k") + .unwrap_lite() + .str("TEST EREPORT PLS IGNORE") + .unwrap_lite() + .str("badness") + .unwrap_lite() + .u32(10000) + .unwrap_lite() + .str("msg") + .unwrap_lite() + .str("im dead") + .unwrap_lite() + .end() + .unwrap_lite(); + + encoder.into_writer().position() + }; + + packrat.deliver_ereport(&buf[..encoded_len]); + + loop { + // now die! + sys_recv_notification(0); + // TODO(eliza): eventually it might be a lil nicer if we had an IPC + // interface for sending an ereport, so we could trigger this multiple + // times from humility... + } +} diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 3dadaf6470..776add227c 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -22,7 +22,7 @@ static-cell = { path = "../../lib/static-cell" } task-packrat-api = { path = "../packrat-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } snitch-core = { version = "0.1.0", path = "../../lib/snitch-core", optional = true } -minicbor = { version = "0.26.4", optional = true } +minicbor = { workspace = true, optional = true } [build-dependencies] anyhow.workspace = true From 653765f884f5a883fab2722f645b4a717da54af2 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 13:53:32 -0700 Subject: [PATCH 18/61] shut clippy up --- task/packrat/src/ereport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 411d8c47e5..378acc8ee5 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -165,7 +165,7 @@ impl EreportStore { }; // Beginning with the first - for r in self.storage.read_from(begin_ena.into()) { + for r in self.storage.read_from(begin_ena) { if first_written_ena.is_none() { first_written_ena = Some(r.ena); // Start CBOR list From 3dd9f201421638a4d971530d286b93c071999f77 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 15:21:59 -0700 Subject: [PATCH 19/61] always send empty meta map --- task/packrat/src/ereport.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 378acc8ee5..62b1ad90f4 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -114,6 +114,11 @@ impl EreportStore { let mut position = first_data_byte; let mut first_written_ena = None; + // Begin metadata map. + data.write_at(position, 0xbf) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + // If the requested restart ID matches the current restart ID, then read // from the requested ENA. If not, start at ENA 0. let begin_ena = if restart_id == self.restart_id { @@ -136,7 +141,6 @@ impl EreportStore { // everything we encode here is fixed-size. But, yuck... let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); let mut encoder = minicbor::Encoder::new(c); - encoder.begin_map().unwrap_lite(); if let Some(vpd) = vpd { encoder .str("baseboard_part_number") @@ -152,7 +156,6 @@ impl EreportStore { .u32(vpd.revision) .unwrap_lite(); } - encoder.end().unwrap_lite(); // Write the encoded metadata map. let size = encoder.into_writer().position(); @@ -164,6 +167,11 @@ impl EreportStore { 0 }; + // End metadata map. + data.write_at(position, 0xff) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + // Beginning with the first for r in self.storage.read_from(begin_ena) { if first_written_ena.is_none() { From 60bb54af041d2901aedd381b69ff14d4cbd2b0b2 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 15:28:42 -0700 Subject: [PATCH 20/61] dont ringbuf zerocopy types --- task/packrat/src/ereport.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 62b1ad90f4..3c8cf81dca 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -45,11 +45,15 @@ enum EreportTrace { len: u32, }, Flushed { - committed_ena: ereport_messages::Ena, + ena: u64, }, RestartIdMismatch { - current: ereport_messages::RestartId, - requested: ereport_messages::RestartId, + current: u128, + requested: u128, + }, + Reported { + start_ena: u64, + reports: u32, }, } counted_ringbuf!(EreportTrace, 16, EreportTrace::None); @@ -126,13 +130,15 @@ impl EreportStore { // `committed_ena`, if there is one. if committed_ena != ereport_messages::Ena::NONE { self.storage.flush_thru(committed_ena.into()); - ringbuf_entry!(EreportTrace::Flushed { committed_ena }); + ringbuf_entry!(EreportTrace::Flushed { + ena: committed_ena.into() + }); } begin_ena.into() } else { ringbuf_entry!(EreportTrace::RestartIdMismatch { - requested: restart_id, - current: self.restart_id + requested: restart_id.into(), + current: self.restart_id.into() }); // Encode the metadata map into our buffer. From 5f1634b04b689bf14c78726bd4684d9664e98bc0 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Jul 2025 15:28:55 -0700 Subject: [PATCH 21/61] be smart about not overflowing buf --- task/packrat/src/ereport.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 3c8cf81dca..716fe8197a 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -178,6 +178,7 @@ impl EreportStore { .map_err(|_| ClientError::WentAway.fail())?; position += 1; + let mut reports = 0; // Beginning with the first for r in self.storage.read_from(begin_ena) { if first_written_ena.is_none() { @@ -215,12 +216,17 @@ impl EreportStore { match minicbor::encode(&entry, &mut c) { Ok(()) => { let size = c.position(); + // If there's no room left for this one in the lease, we're done here. + if position + size >= data.len() { + break; + } data.write_range( position..position + size, &self.recv[..size], ) .map_err(|_| ClientError::WentAway.fail())?; position += size; + reports += 1; } Err(_end) => { // This is an odd one; we've admitted a record into our @@ -232,11 +238,12 @@ impl EreportStore { } } - if first_written_ena.is_some() { + if let Some(start_ena) = first_written_ena { // End CBOR list, if we wrote anything. data.write_at(position, 0xff) .map_err(|_| ClientError::WentAway.fail())?; position += 1; + ringbuf_entry!(EreportTrace::Reported { start_ena, reports }); } let first_ena = first_written_ena.unwrap_or(self.next_ena); From ee7d004a6161fbbdac97085fe340cf25d2bbf0cc Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Jul 2025 11:49:14 -0700 Subject: [PATCH 22/61] implement limit --- idl/packrat.idol | 1 + task/packrat/src/ereport.rs | 15 +++++++++++++-- task/packrat/src/main.rs | 3 +++ task/snitch/src/main.rs | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/idl/packrat.idol b/idl/packrat.idol index 4e4a0d96a4..e8dfd0ca9c 100644 --- a/idl/packrat.idol +++ b/idl/packrat.idol @@ -111,6 +111,7 @@ Interface( "request_id": "ereport_messages::RequestIdV0", "restart_id": "ereport_messages::RestartId", "start_ena": "ereport_messages::Ena", + "limit": "u8", "committed_ena": "ereport_messages::Ena", }, leases: { diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 716fe8197a..a49d5da58b 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -53,7 +53,8 @@ enum EreportTrace { }, Reported { start_ena: u64, - reports: u32, + reports: u8, + limit: u8, }, } counted_ringbuf!(EreportTrace, 16, EreportTrace::None); @@ -108,6 +109,7 @@ impl EreportStore { request_id: ereport_messages::RequestIdV0, restart_id: ereport_messages::RestartId, begin_ena: ereport_messages::Ena, + limit: u8, committed_ena: ereport_messages::Ena, data: Leased, vpd: Option<&VpdIdentity>, @@ -181,6 +183,10 @@ impl EreportStore { let mut reports = 0; // Beginning with the first for r in self.storage.read_from(begin_ena) { + if reports >= limit { + break; + } + if first_written_ena.is_none() { first_written_ena = Some(r.ena); // Start CBOR list @@ -243,7 +249,12 @@ impl EreportStore { data.write_at(position, 0xff) .map_err(|_| ClientError::WentAway.fail())?; position += 1; - ringbuf_entry!(EreportTrace::Reported { start_ena, reports }); + + ringbuf_entry!(EreportTrace::Reported { + start_ena, + reports, + limit + }); } let first_ena = first_written_ena.unwrap_or(self.next_ena); diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 6f534d0094..52193a5ca2 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -448,6 +448,7 @@ impl idl::InOrderPackratImpl for ServerImpl { _: ereport_messages::RequestIdV0, _: ereport_messages::RestartId, _: ereport_messages::Ena, + _: u8, _: ereport_messages::Ena, _: Leased, ) -> Result> { @@ -462,6 +463,7 @@ impl idl::InOrderPackratImpl for ServerImpl { request_id: ereport_messages::RequestIdV0, restart_id: ereport_messages::RestartId, begin_ena: ereport_messages::Ena, + limit: u8, committed_ena: ereport_messages::Ena, data: Leased, ) -> Result> { @@ -469,6 +471,7 @@ impl idl::InOrderPackratImpl for ServerImpl { request_id, restart_id, begin_ena, + limit, committed_ena, data, self.identity.as_ref(), diff --git a/task/snitch/src/main.rs b/task/snitch/src/main.rs index 2542b130e6..1ee529057e 100644 --- a/task/snitch/src/main.rs +++ b/task/snitch/src/main.rs @@ -88,6 +88,7 @@ fn main() -> ! { req.request_id, req.restart_id, req.start_ena, + req.limit, req.committed_ena() .copied() .unwrap_or(gateway_ereport_messages::Ena::NONE), From 809fe116a8f20e5f7790f5a696ba0551c90d1209 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sat, 5 Jul 2025 11:00:16 -0700 Subject: [PATCH 23/61] give ereportulator a Hiffy interface --- Cargo.lock | 4 + app/gimletlet/app-ereportlet.toml | 8 +- idl/ereportulator.idol | 14 +++ task/ereportulator/Cargo.toml | 12 +++ task/ereportulator/build.rs | 17 ++++ task/ereportulator/src/main.rs | 142 +++++++++++++++++++++++------- 6 files changed, 161 insertions(+), 36 deletions(-) create mode 100644 idl/ereportulator.idol create mode 100644 task/ereportulator/build.rs diff --git a/Cargo.lock b/Cargo.lock index 68a21da6e2..90ab30eb5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5365,8 +5365,12 @@ dependencies = [ name = "task-ereportulator" version = "0.1.0" dependencies = [ + "counters", + "idol", "idol-runtime", "minicbor 0.26.4", + "num-traits", + "ringbuf", "task-packrat-api", "userlib", "zerocopy 0.8.26", diff --git a/app/gimletlet/app-ereportlet.toml b/app/gimletlet/app-ereportlet.toml index 49ac1d41cb..1ff2a4ba00 100644 --- a/app/gimletlet/app-ereportlet.toml +++ b/app/gimletlet/app-ereportlet.toml @@ -6,12 +6,12 @@ request_reset = ["hiffy"] [tasks.hiffy] features = ["h753", "stm32h7", "i2c", "gpio", "spi"] -task-slots = ["sys", "i2c_driver", "user_leds"] +task-slots = ["sys", "i2c_driver", "user_leds", "ereportulator"] [tasks.ereportulator] name = "task-ereportulator" -priority = 8 -# start paused so that we can be triggered by humility -start = false +priority = 5 +start = true task-slots = ["packrat"] +features = ["fake-vpd"] notifications = [] diff --git a/idl/ereportulator.idol b/idl/ereportulator.idol new file mode 100644 index 0000000000..75501c458f --- /dev/null +++ b/idl/ereportulator.idol @@ -0,0 +1,14 @@ +// Interface to the ereport demo task. +Interface( + name: "Ereportulator", + ops: { + "fake_ereport": ( + doc: "Make a fake error report.", + args: { + "n": "u32", + }, + reply: Simple("()"), + idempotent: true, + ), + } +) diff --git a/task/ereportulator/Cargo.toml b/task/ereportulator/Cargo.toml index 048d609ff2..460632cb50 100644 --- a/task/ereportulator/Cargo.toml +++ b/task/ereportulator/Cargo.toml @@ -10,6 +10,18 @@ zerocopy.workspace = true zerocopy-derive.workspace = true task-packrat-api = { path = "../packrat-api" } minicbor.workspace = true +num-traits.workspace = true +ringbuf = { path = "../../lib/ringbuf", features = ["counters"] } +counters = { path = "../../lib/counters" } + +[build-dependencies] +idol.workspace = true + +[features] +# If set, report a fake VPD identity to Packrat before generating ereports, so +# that we populate the metadata fields. This is useful for Gimletlet and +# friends, which don't have "real" VPD. +fake-vpd = [] [[bin]] name = "task-ereportulator" diff --git a/task/ereportulator/build.rs b/task/ereportulator/build.rs new file mode 100644 index 0000000000..a9c967c508 --- /dev/null +++ b/task/ereportulator/build.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> Result<(), Box> { + idol::Generator::new() + .with_counters( + idol::CounterSettings::default().with_server_counters(false), + ) + .build_server_support( + "../../idl/ereportulator.idol", + "server_stub.rs", + idol::server::ServerStyle::InOrder, + )?; + + Ok(()) +} diff --git a/task/ereportulator/src/main.rs b/task/ereportulator/src/main.rs index 0b1639e680..bbd38c0eaa 100644 --- a/task/ereportulator/src/main.rs +++ b/task/ereportulator/src/main.rs @@ -10,49 +10,127 @@ #![no_std] #![no_main] +use core::convert::Infallible; + +use idol_runtime::RequestError; +use minicbor::Encoder; +use ringbuf::{counted_ringbuf, ringbuf_entry}; use task_packrat_api::Packrat; +use userlib::{task_slot, RecvMessage, UnwrapLite}; + task_slot!(PACKRAT, packrat); -use minicbor::Encoder; -use userlib::{sys_recv_notification, task_slot, UnwrapLite}; +#[derive(Copy, Clone, Eq, PartialEq, counters::Count)] +enum Trace { + #[count(skip)] + None, + #[cfg(feature = "fake-vpd")] + VpdAlreadySet, + #[cfg(feature = "fake-vpd")] + SetFakeVpd, + + EreportRequested(u32), + EreportDelivered { + encoded_len: usize, + }, +} + +counted_ringbuf!(Trace, 16, Trace::None); #[export_name = "main"] fn main() -> ! { let packrat = Packrat::from(PACKRAT.get_task_id()); - let mut buf = [0u8; 256]; - - let encoded_len = { - let c = minicbor::encode::write::Cursor::new(&mut buf[..]); - let mut encoder = Encoder::new(c); - encoder - .begin_map() - .unwrap_lite() - .str("k") - .unwrap_lite() - .str("TEST EREPORT PLS IGNORE") - .unwrap_lite() - .str("badness") - .unwrap_lite() - .u32(10000) - .unwrap_lite() - .str("msg") - .unwrap_lite() - .str("im dead") - .unwrap_lite() - .end() - .unwrap_lite(); - - encoder.into_writer().position() + #[cfg(feature = "fake-vpd")] + fake_vpd(&packrat); + + let mut server = ServerImpl { + buf: [0; 256], + packrat, }; - packrat.deliver_ereport(&buf[..encoded_len]); + let mut buffer = [0; idl::INCOMING_SIZE]; loop { - // now die! - sys_recv_notification(0); - // TODO(eliza): eventually it might be a lil nicer if we had an IPC - // interface for sending an ereport, so we could trigger this multiple - // times from humility... + idol_runtime::dispatch(&mut buffer, &mut server); } } + +struct ServerImpl { + buf: [u8; 256], + packrat: Packrat, +} + +impl idl::InOrderEreportulatorImpl for ServerImpl { + fn fake_ereport( + &mut self, + _msg: &RecvMessage, + n: u32, + ) -> Result<(), RequestError> { + ringbuf_entry!(Trace::EreportRequested(n)); + + let encoded_len = { + let c = minicbor::encode::write::Cursor::new(&mut self.buf[..]); + let mut encoder = Encoder::new(c); + encoder + .begin_map() + .unwrap_lite() + .str("k") + .unwrap_lite() + .str("TEST EREPORT PLS IGNORE") + .unwrap_lite() + .str("badness") + .unwrap_lite() + .u32(n) + .unwrap_lite() + .str("msg") + .unwrap_lite() + .str("im dead") + .unwrap_lite() + .end() + .unwrap_lite(); + + encoder.into_writer().position() + }; + + self.packrat.deliver_ereport(&self.buf[..encoded_len]); + ringbuf_entry!(Trace::EreportDelivered { encoded_len }); + + Ok(()) + } +} + +impl idol_runtime::NotificationHandler for ServerImpl { + fn current_notification_mask(&self) -> u32 { + // We don't use notifications, don't listen for any. + 0 + } + + fn handle_notification(&mut self, _bits: u32) { + unreachable!() + } +} + +#[cfg(feature = "fake-vpd")] +fn fake_vpd(packrat: &Packrat) { + // If someone else has already set identity, just don't clobber it. + if packrat.get_identity().is_ok() { + ringbuf_entry!(Trace::VpdAlreadySet); + return; + } + + // Just make up some nonsense. + packrat + .set_identity(task_packrat_api::VpdIdentity { + part_number: *b"LOLNO000000", + serial: *b"69426661337", + revision: 42, + }) + .unwrap_lite(); + + ringbuf_entry!(Trace::SetFakeVpd); +} + +mod idl { + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +} From 90b401d844f4b6c1a4c7dcdd450870db5bcaaf09 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 6 Jul 2025 12:35:17 -0700 Subject: [PATCH 24/61] fix type mismatch for disabled ereport IPCs --- task/packrat/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 52193a5ca2..722fe4c449 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -429,7 +429,7 @@ impl idl::InOrderPackratImpl for ServerImpl { _: LenLimit, 1024usize>, ) -> Result<(), RequestError> { // go away, we don't know how to do that - idol_runtime::ClientError::UnknownOperation.fail() + Err(idol_runtime::ClientError::UnknownOperation.fail()) } #[cfg(feature = "ereport")] @@ -453,7 +453,7 @@ impl idl::InOrderPackratImpl for ServerImpl { _: Leased, ) -> Result> { // go away, we don't know how to do that - idol_runtime::ClientError::UnknownOperation.fail() + Err(idol_runtime::ClientError::UnknownOperation.fail()) } #[cfg(feature = "ereport")] From 728dfbba212b00cf077d44a96f19d4daa5d33209 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 6 Jul 2025 12:42:23 -0700 Subject: [PATCH 25/61] packrat docs --- task/packrat/src/ereport.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index a49d5da58b..75621dfaaf 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -2,6 +2,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//! Packrat ereport aggregation. +//! +//! As described in [RFD 545 § 4.3], `packrat`'s role in the ereport subsystem +//! is to aggregate ereports from other tasks in a circular buffer. Ereports are +//! submitted to `packrat` via the `deliver_ereport` IPC call. The `snitch` task +//! requests ereports from `packrat` using the `read_ereports` IPC call, which +//! also flushes committed ereports from the buffer. +//! +//! [RFD 545 § 4.3]: https://rfd.shared.oxide.computer/rfd/0545#_aggregation + use super::ereport_messages; use core::convert::Infallible; @@ -222,7 +232,8 @@ impl EreportStore { match minicbor::encode(&entry, &mut c) { Ok(()) => { let size = c.position(); - // If there's no room left for this one in the lease, we're done here. + // If there's no room left for this one in the lease, we're + // done here. if position + size >= data.len() { break; } From 3135cb8383933ec80169212841a841c8b3411148 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 6 Jul 2025 13:00:15 -0700 Subject: [PATCH 26/61] don't crash packrat on internal errors --- app/gimletlet/app.toml | 1 + task/packrat/src/ereport.rs | 81 +++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 6af9b504fc..227e2375d2 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -45,6 +45,7 @@ name = "task-packrat" priority = 4 start = true task-slots = ["rng_driver"] +stacksize = 1000 features = ["ereport"] [caboose] diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 75621dfaaf..c751ef0716 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -66,7 +66,16 @@ enum EreportTrace { reports: u8, limit: u8, }, + InternalError(#[count(children)] Error), } + +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] +enum Error { + TaskIdOutOfRange, + VpdMetadataTooLong, + EreportTooLong, +} + counted_ringbuf!(EreportTrace, 16, EreportTrace::None); impl EreportStore { @@ -154,33 +163,26 @@ impl EreportStore { }); // Encode the metadata map into our buffer. - // TODO(eliza): this will panic if the encoded metadata map is - // longer than 1024B...currently it should never be that, given that - // everything we encode here is fixed-size. But, yuck... - let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); - let mut encoder = minicbor::Encoder::new(c); if let Some(vpd) = vpd { - encoder - .str("baseboard_part_number") - .unwrap_lite() - .bytes(vpd.part_number.as_bytes()) - .unwrap_lite() - .str("baseboard_serial_number") - .unwrap_lite() - .bytes(vpd.serial.as_bytes()) - .unwrap_lite() - .str("rev") - .unwrap_lite() - .u32(vpd.revision) - .unwrap_lite(); + match self.encode_vpd_metadata(vpd) { + Ok(encoded) => { + data.write_range( + position..position + encoded.len(), + encoded, + ) + .map_err(|_| ClientError::WentAway.fail())?; + + position += encoded.len(); + } + Err(_end) => { + // Encoded VPD metadata was too long! + ringbuf_entry!(EreportTrace::InternalError( + Error::VpdMetadataTooLong + )); + } + } } - // Write the encoded metadata map. - let size = encoder.into_writer().position(); - data.write_range(position..position + size, &self.recv[..size]) - .map_err(|_| ClientError::WentAway.fail())?; - position += size; - // Begin at ENA 0 0 }; @@ -212,11 +214,14 @@ impl EreportStore { let task_name = hubris_task_names::TASK_NAMES .get(tid.index()) .copied() - .unwrap_or({ + .unwrap_or_else(|| { // This represents an internal error, where we've recorded // an out-of-range task ID somehow. We still want to get the // ereport out, so we'll use a recognizable but illegal task // name to indicate that it's missing. + ringbuf_entry!(EreportTrace::InternalError( + Error::TaskIdOutOfRange + )); "-" // TODO }); let generation = tid.generation(); @@ -250,7 +255,9 @@ impl EreportStore { // queue that won't fit in our buffer. This can happen // because of the encoding overhead, in theory, but should // be prevented. - // TODO + ringbuf_entry!(EreportTrace::InternalError( + Error::EreportTooLong + )); } } } @@ -280,6 +287,28 @@ impl EreportStore { .map_err(|_| ClientError::WentAway.fail())?; Ok(position) } + + fn encode_vpd_metadata(&mut self, vpd: &VpdIdentity) -> Result<&[u8], ()> { + let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); + let mut encoder = minicbor::Encoder::new(c); + encoder + .str("baseboard_part_number") + .map_err(|_| ())? + .bytes(vpd.part_number.as_bytes()) + .map_err(|_| ())?; + encoder + .str("baseboard_serial_number") + .map_err(|_| ())? + .bytes(vpd.serial.as_bytes()) + .map_err(|_| ())?; + encoder + .str("rev") + .map_err(|_| ())? + .u32(vpd.revision) + .map_err(|_| ())?; + let size = encoder.into_writer().position(); + Ok(&self.recv[..size]) + } } impl EreportBufs { From 9745992c90084bd94f85bc1502edc8094b362d42 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 6 Jul 2025 13:12:48 -0700 Subject: [PATCH 27/61] embiggen packrat ereport stacks --- app/gimlet/base.toml | 1 + app/psc/base.toml | 1 + app/sidecar/base.toml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index b71a4e5070..58d758b8fb 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -124,6 +124,7 @@ notifications = ["i2c1-irq", "jefe-state-change"] [tasks.packrat] name = "task-packrat" priority = 3 +stacksize = 1000 start = true task-slots = ["rng_driver"] features = ["gimlet", "ereport"] diff --git a/app/psc/base.toml b/app/psc/base.toml index b5216a191c..97c83596b4 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -119,6 +119,7 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" priority = 3 +stacksize = 1000 start = true task-slots = ["rng_driver"] features = ["ereport"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 0e4fb1b1c0..709c867faf 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -258,6 +258,7 @@ notifications = ["socket", "timer"] [tasks.packrat] name = "task-packrat" priority = 3 +stacksize = 1000 start = true task-slots = ["rng_driver"] features = ["ereport"] From 9a9befaa9018984adab871e69560bd3bb6774cfc Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 6 Jul 2025 13:36:27 -0700 Subject: [PATCH 28/61] reticulating stack sizes --- app/cosmo/base.toml | 1 + app/gimlet/base.toml | 2 +- app/psc/base.toml | 2 +- app/sidecar/base.toml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index f0d2dd5efc..ba861373c4 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -133,6 +133,7 @@ task-slots = ["sys"] name = "task-packrat" priority = 3 start = true +stacksize = 954 task-slots = ["rng_driver"] features = ["cosmo", "ereport"] diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 58d758b8fb..f46aea4198 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -124,7 +124,7 @@ notifications = ["i2c1-irq", "jefe-state-change"] [tasks.packrat] name = "task-packrat" priority = 3 -stacksize = 1000 +stacksize = 954 start = true task-slots = ["rng_driver"] features = ["gimlet", "ereport"] diff --git a/app/psc/base.toml b/app/psc/base.toml index 97c83596b4..e62a3a4245 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -119,7 +119,7 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" priority = 3 -stacksize = 1000 +stacksize = 954 start = true task-slots = ["rng_driver"] features = ["ereport"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 709c867faf..a22a74d50e 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -258,7 +258,7 @@ notifications = ["socket", "timer"] [tasks.packrat] name = "task-packrat" priority = 3 -stacksize = 1000 +stacksize = 954 start = true task-slots = ["rng_driver"] features = ["ereport"] From b88c6aeec8cda021ac47aae91b3dd174247fe703 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 6 Jul 2025 14:36:57 -0700 Subject: [PATCH 29/61] rng to packrat: "don't call us, we'll call you" --- Cargo.lock | 2 ++ app/cosmo/base.toml | 23 +++++++------ app/gimlet/base.toml | 13 +++---- app/gimletlet/app.toml | 11 ++++-- app/gimletlet/base-gimletlet2.toml | 4 +-- app/psc/base.toml | 11 +++--- app/sidecar/base.toml | 9 ++--- drv/stm32h7-rng/Cargo.toml | 2 ++ drv/stm32h7-rng/src/main.rs | 36 +++++++++++++++++++ idl/packrat.idol | 17 +++++++-- task/packrat-api/Cargo.toml | 1 + task/packrat-api/build.rs | 8 ++--- task/packrat-api/src/lib.rs | 7 ++++ task/packrat/src/ereport.rs | 27 ++++++--------- task/packrat/src/main.rs | 55 ++++++++++++++++++++++++++---- task/snitch/src/main.rs | 12 +++++-- 16 files changed, 174 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90ab30eb5f..8388cbfd9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2388,6 +2388,7 @@ dependencies = [ "num-traits", "ringbuf", "stm32h7", + "task-packrat-api", "userlib", "zerocopy 0.8.26", "zerocopy-derive 0.8.26", @@ -5725,6 +5726,7 @@ dependencies = [ name = "task-packrat-api" version = "0.1.0" dependencies = [ + "anyhow", "counters", "derive-idol-err", "gateway-ereport-messages", diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index ba861373c4..cd543afa0b 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -120,22 +120,23 @@ notifications = ["i2c1-irq", "i2c2-irq", "i2c3-irq", "i2c4-irq"] "i2c4.event" = "i2c4-irq" "i2c4.error" = "i2c4-irq" +[tasks.packrat] +name = "task-packrat" +priority = 1 +stacksize = 1024 +start = true +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] +features = ["cosmo", "ereport"] + [tasks.rng_driver] -features = ["h753"] +features = ["h753", "ereport"] name = "drv-stm32h7-rng" -priority = 2 +priority = 6 uses = ["rng"] start = true stacksize = 512 -task-slots = ["sys"] - -[tasks.packrat] -name = "task-packrat" -priority = 3 -start = true -stacksize = 954 -task-slots = ["rng_driver"] -features = ["cosmo", "ereport"] +task-slots = ["sys", "packrat"] [tasks.thermal] name = "task-thermal" diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index f46aea4198..335bb4a079 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -123,10 +123,11 @@ notifications = ["i2c1-irq", "jefe-state-change"] [tasks.packrat] name = "task-packrat" -priority = 3 -stacksize = 954 +priority = 1 +stacksize = 1024 start = true -task-slots = ["rng_driver"] +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] features = ["gimlet", "ereport"] [tasks.thermal] @@ -196,13 +197,13 @@ task-slots = ["sys"] notifications = ["hash-irq"] [tasks.rng_driver] -features = ["h753"] +features = ["h753", "ereport"] name = "drv-stm32h7-rng" -priority = 2 +priority = 6 uses = ["rng"] start = true stacksize = 512 -task-slots = ["sys"] +task-slots = ["sys", "packrat"] [tasks.hf] name = "drv-gimlet-hf-server" diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 227e2375d2..1936040982 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -42,10 +42,11 @@ owner = {name = "sprot", notification = "rot_irq"} [tasks.packrat] name = "task-packrat" -priority = 4 +priority = 1 start = true -task-slots = ["rng_driver"] -stacksize = 1000 +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] +stacksize = 1024 features = ["ereport"] [caboose] @@ -197,6 +198,10 @@ task-slots = ["net", "packrat"] features = ["vlan"] notifications = ["socket"] +[tasks.rng_driver] +features = ["h753", "ereport"] +task-slots = ["sys", "user_leds", "packrat"] + # VLAN configuration [config.net.vlans.sidecar1] vid = 0x301 diff --git a/app/gimletlet/base-gimletlet2.toml b/app/gimletlet/base-gimletlet2.toml index 70b169d04b..7eb80dc1df 100644 --- a/app/gimletlet/base-gimletlet2.toml +++ b/app/gimletlet/base-gimletlet2.toml @@ -53,7 +53,7 @@ notifications = ["i2c2-irq", "i2c3-irq", "i2c4-irq"] [tasks.user_leds] name = "drv-user-leds" features = ["stm32h7"] -priority = 2 +priority = 5 max-sizes = {flash = 2048, ram = 1024} start = true task-slots = ["sys"] @@ -105,7 +105,7 @@ start = true [tasks.rng_driver] features = ["h753"] name = "drv-stm32h7-rng" -priority = 3 +priority = 6 uses = ["rng"] start = true stacksize = 512 diff --git a/app/psc/base.toml b/app/psc/base.toml index e62a3a4245..3ce2459722 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -92,13 +92,13 @@ owner = {name = "sequencer", notification = "psu_pwr_ok_6"} [tasks.rng_driver] -features = ["h753"] +features = ["h753", "ereport"] name = "drv-stm32h7-rng" -priority = 2 +priority = 6 uses = ["rng"] start = true stacksize = 512 -task-slots = ["sys"] +task-slots = ["sys", "packrat"] [tasks.i2c_driver] name = "drv-stm32xx-i2c-server" @@ -119,9 +119,10 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" priority = 3 -stacksize = 954 +stacksize = 1024 start = true -task-slots = ["rng_driver"] +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] features = ["ereport"] [tasks.sequencer] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index a22a74d50e..ffd8c4ab40 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -56,7 +56,7 @@ owner = {name = "sprot", notification = "rot_irq"} [tasks.rng_driver] features = ["h753"] name = "drv-stm32h7-rng" -priority = 2 +priority = 6 uses = ["rng"] start = true stacksize = 512 @@ -257,10 +257,11 @@ notifications = ["socket", "timer"] [tasks.packrat] name = "task-packrat" -priority = 3 -stacksize = 954 +priority = 1 +stacksize = 1024 start = true -task-slots = ["rng_driver"] +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] features = ["ereport"] [tasks.sequencer] diff --git a/drv/stm32h7-rng/Cargo.toml b/drv/stm32h7-rng/Cargo.toml index fa69fcebc4..1c02db57f0 100644 --- a/drv/stm32h7-rng/Cargo.toml +++ b/drv/stm32h7-rng/Cargo.toml @@ -12,6 +12,7 @@ zerocopy-derive = { workspace = true } drv-rng-api = { path = "../rng-api" } drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" } +task-packrat-api = { path = "../../task/packrat-api", optional = true } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } ringbuf = { path = "../../lib/ringbuf" } counters = { version = "0.1.0", path = "../../lib/counters" } @@ -22,6 +23,7 @@ idol = { workspace = true } [features] h743 = ["stm32h7/stm32h743", "drv-stm32xx-sys-api/h743"] h753 = ["stm32h7/stm32h753", "drv-stm32xx-sys-api/h753"] +ereport = ["task-packrat-api"] no-ipc-counters = ["idol/no-counters"] # This section is here to discourage RLS/rust-analyzer from doing test builds, diff --git a/drv/stm32h7-rng/src/main.rs b/drv/stm32h7-rng/src/main.rs index 8981015a70..3ad29293d6 100644 --- a/drv/stm32h7-rng/src/main.rs +++ b/drv/stm32h7-rng/src/main.rs @@ -25,6 +25,9 @@ use userlib::*; task_slot!(SYS, sys); +#[cfg(feature = "ereport")] +task_slot!(PACKRAT, packrat); + counted_ringbuf!(Trace, 32, Trace::Blank); #[derive(Copy, Clone, Debug, Eq, PartialEq, counters::Count)] @@ -34,6 +37,8 @@ enum Trace { ClockError, SeedError, Recovered, + #[cfg(feature = "ereport")] + SetRestartId(Result<(), task_packrat_api::CacheSetError>), } struct Stm32h7Rng { @@ -228,11 +233,42 @@ impl NotificationHandler for Stm32h7RngServer { } } +#[cfg(feature = "ereport")] +fn generate_restart_id(rng: &mut Stm32h7Rng) { + use task_packrat_api::Packrat; + const BYTES: usize = 16; + + let mut bytes = [0u8; BYTES]; + for i in 0..(BYTES / 4) { + 'retry: loop { + match rng.read() { + Ok(word) => { + let word_bytes = &word.to_ne_bytes(); + bytes[i * 4..i * 4 + 4].copy_from_slice(word_bytes); + break 'retry; + } + Err(_) => { + // XXX(eliza): what do we do if this fails? + let _ = rng.attempt_recovery(); + } + } + } + } + + let id = u128::from_ne_bytes(bytes); + let packrat = Packrat::from(PACKRAT.get_task_id()); + let result = packrat.set_ereport_restart_id(id); + ringbuf_entry!(Trace::SetRestartId(result)); +} + #[export_name = "main"] fn main() -> ! { let mut rng = Stm32h7Rng::new(); rng.init(); + #[cfg(feature = "ereport")] + generate_restart_id(&mut rng); + let mut srv = Stm32h7RngServer::new(rng); let mut buffer = [0u8; idl::INCOMING_SIZE]; diff --git a/idl/packrat.idol b/idl/packrat.idol index e8dfd0ca9c..a8568ac62d 100644 --- a/idl/packrat.idol +++ b/idl/packrat.idol @@ -94,7 +94,17 @@ Interface( reply: Simple("()"), idempotent: true, ), - + "set_ereport_restart_id": ( + doc: "Set the restart ID for ereports", + args: { + "restart_id": "u128", + }, + reply: Result( + ok: "()", + err: CLike("CacheSetError"), + ), + idempotent: true, + ), "deliver_ereport": ( doc: "Hand an encoded ereport to packrat for buffering.", args: { @@ -117,7 +127,10 @@ Interface( leases: { "data": (type: "[u8]", write: true), }, - reply: Simple("usize"), + reply: Result( + ok: "usize", + err: CLike("EreportReadError"), + ), idempotent: true, ), }, diff --git a/task/packrat-api/Cargo.toml b/task/packrat-api/Cargo.toml index cbe37acc06..21b6794324 100644 --- a/task/packrat-api/Cargo.toml +++ b/task/packrat-api/Cargo.toml @@ -25,6 +25,7 @@ bench = false [build-dependencies] idol.workspace = true +anyhow.workspace = true [lints] workspace = true diff --git a/task/packrat-api/build.rs b/task/packrat-api/build.rs index 8249074539..8ef885bb70 100644 --- a/task/packrat-api/build.rs +++ b/task/packrat-api/build.rs @@ -2,10 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -fn main() -> Result<(), Box> { - idol::client::build_client_stub( - "../../idl/packrat.idol", - "client_stub.rs", - )?; +fn main() -> anyhow::Result<()> { + idol::client::build_client_stub("../../idl/packrat.idol", "client_stub.rs") + .map_err(|e| anyhow::anyhow!("{e}"))?; Ok(()) } diff --git a/task/packrat-api/src/lib.rs b/task/packrat-api/src/lib.rs index 44ea8b62b7..e109705369 100644 --- a/task/packrat-api/src/lib.rs +++ b/task/packrat-api/src/lib.rs @@ -53,4 +53,11 @@ pub enum CacheSetError { ValueAlreadySet = 1, } +#[derive( + Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, IdolError, counters::Count, +)] +pub enum EreportReadError { + RestartIdNotSet = 1, +} + include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index c751ef0716..ebd581ad19 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -18,15 +18,15 @@ use core::convert::Infallible; use idol_runtime::{ClientError, Leased, LenLimit, RequestError}; use minicbor::CborLen; use ringbuf::{counted_ringbuf, ringbuf_entry}; -use task_packrat_api::VpdIdentity; -use userlib::{sys_get_timer, RecvMessage, TaskId, UnwrapLite}; +use task_packrat_api::{EreportReadError, VpdIdentity}; +use userlib::{sys_get_timer, RecvMessage, TaskId}; use zerocopy::IntoBytes; pub(crate) struct EreportStore { storage: &'static mut snitch_core::Store, recv: &'static mut [u8; RECV_BUF_SIZE], next_ena: u64, - restart_id: ereport_messages::RestartId, + pub(super) restart_id: Option, } pub(crate) struct EreportBufs { @@ -42,8 +42,6 @@ const STORE_SIZE: usize = 4096; /// ereport at a time (and implicitly, limits the maximum size of an ereport). pub(crate) const RECV_BUF_SIZE: usize = 1024; -userlib::task_slot!(RNG, rng_driver); - /// Separate ring buffer for ereport events, as we probably don't care that much /// about the sequence of ereport events relative to other packrat API events. #[derive(Copy, Clone, PartialEq, Eq, counters::Count)] @@ -85,20 +83,13 @@ impl EreportStore { ref mut recv, }: &'static mut EreportBufs, ) -> Self { - let rng = drv_rng_api::Rng::from(RNG.get_task_id()); - let mut buf = [0u8; 16]; - // XXX(eliza): if this fails we are TURBO SCREWED... - rng.fill(&mut buf).unwrap_lite(); - let restart_id = - ereport_messages::RestartId::from(u128::from_le_bytes(buf)); - storage.initialize(0, 0); // TODO tid timestamp Self { storage, recv, next_ena: 0, - restart_id, + restart_id: None, } } } @@ -132,7 +123,9 @@ impl EreportStore { committed_ena: ereport_messages::Ena, data: Leased, vpd: Option<&VpdIdentity>, - ) -> Result> { + ) -> Result> { + let current_restart_id = + self.restart_id.ok_or(EreportReadError::RestartIdNotSet)?; // Skip over a header-sized initial chunk. let first_data_byte = size_of::(); @@ -146,7 +139,7 @@ impl EreportStore { // If the requested restart ID matches the current restart ID, then read // from the requested ENA. If not, start at ENA 0. - let begin_ena = if restart_id == self.restart_id { + let begin_ena = if restart_id == current_restart_id { // If the restart ID matches, flush previous ereports up to // `committed_ena`, if there is one. if committed_ena != ereport_messages::Ena::NONE { @@ -159,7 +152,7 @@ impl EreportStore { } else { ringbuf_entry!(EreportTrace::RestartIdMismatch { requested: restart_id.into(), - current: self.restart_id.into() + current: current_restart_id.into() }); // Encode the metadata map into our buffer. @@ -279,7 +272,7 @@ impl EreportStore { let header = ereport_messages::ResponseHeader::V0( ereport_messages::ResponseHeaderV0 { request_id, - restart_id: self.restart_id, + restart_id: current_restart_id, start_ena: first_ena.into(), }, ); diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 722fe4c449..b19a10e362 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -35,8 +35,8 @@ use idol_runtime::{Leased, LenLimit, NotificationHandler, RequestError}; use ringbuf::{ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; use task_packrat_api::{ - CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, - VpdIdentity, + CacheGetError, CacheSetError, EreportReadError, HostStartupOptions, + MacAddressBlock, VpdIdentity, }; use userlib::RecvMessage; @@ -70,7 +70,13 @@ enum Trace { MacAddressBlockSet(TraceSet), VpdIdentitySet(TraceSet), SetNextBootHostStartupOptions(HostStartupOptions), - SpdDataUpdate { index: u8, offset: usize, len: u8 }, + SpdDataUpdate { + index: u8, + offset: usize, + len: u8, + }, + #[cfg(feature = "ereport")] + RestartIdSet(TraceSet), } impl From> for Trace { @@ -85,6 +91,21 @@ impl From> for Trace { } } +#[cfg(feature = "ereport")] +impl From> for Trace { + fn from(value: TraceSet) -> Self { + // Turn this into a TraceSet instead of the newtype so that + // Humility formats it in a less ugly way. + Self::RestartIdSet(match value { + TraceSet::Set(id) => TraceSet::Set(id.into()), + TraceSet::SetToSameValue(id) => TraceSet::SetToSameValue(id.into()), + TraceSet::AttemptedSetToNewValue(id) => { + TraceSet::AttemptedSetToNewValue(id.into()) + } + }) + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum TraceSet { // Initial set (always succeeds) @@ -422,6 +443,26 @@ impl idl::InOrderPackratImpl for ServerImpl { } } + #[cfg(not(feature = "ereport"))] + fn set_ereport_restart_id( + &mut self, + _: &RecvMessage, + _: u128, + ) -> Result<(), RequestError> { + Ok(()) + } + + #[cfg(feature = "ereport")] + fn set_ereport_restart_id( + &mut self, + _: &RecvMessage, + value: u128, + ) -> Result<(), RequestError> { + let restart_id = ereport_messages::RestartId::new(value); + Self::set_once(&mut self.ereport_store.restart_id, restart_id) + .map_err(Into::into) + } + #[cfg(not(feature = "ereport"))] fn deliver_ereport( &mut self, @@ -451,7 +492,7 @@ impl idl::InOrderPackratImpl for ServerImpl { _: u8, _: ereport_messages::Ena, _: Leased, - ) -> Result> { + ) -> Result> { // go away, we don't know how to do that Err(idol_runtime::ClientError::UnknownOperation.fail()) } @@ -466,7 +507,7 @@ impl idl::InOrderPackratImpl for ServerImpl { limit: u8, committed_ena: ereport_messages::Ena, data: Leased, - ) -> Result> { + ) -> Result> { self.ereport_store.read_ereports( request_id, restart_id, @@ -492,8 +533,8 @@ impl NotificationHandler for ServerImpl { mod idl { use super::{ - ereport_messages, CacheGetError, CacheSetError, HostStartupOptions, - MacAddressBlock, VpdIdentity, + ereport_messages, CacheGetError, CacheSetError, EreportReadError, + HostStartupOptions, MacAddressBlock, VpdIdentity, }; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); diff --git a/task/snitch/src/main.rs b/task/snitch/src/main.rs index 1ee529057e..aba8caec9e 100644 --- a/task/snitch/src/main.rs +++ b/task/snitch/src/main.rs @@ -21,6 +21,7 @@ task_slot!(PACKRAT, packrat); enum Event { RecvPacket, RequestRejected, + ReadError(#[count(children)] task_packrat_api::EreportReadError), Respond, } @@ -84,7 +85,7 @@ fn main() -> ! { }; let size = match request { - Request::V0(req) => packrat.read_ereports( + Request::V0(req) => match packrat.read_ereports( req.request_id, req.restart_id, req.start_ena, @@ -93,7 +94,14 @@ fn main() -> ! { .copied() .unwrap_or(gateway_ereport_messages::Ena::NONE), &mut tx_buf[..], - ), + ) { + Ok(size) => size, + Err(e) => { + // Packrat's mad. Reject the request. + count!(Event::ReadError(e)); + continue; + } + }, }; // With the response packet prepared, we may need to attempt From f98b43f0448b52d50845efc9f13b136e16e97ddf Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 7 Jul 2025 14:50:08 -0700 Subject: [PATCH 30/61] record in the ringbuf when data is lost --- lib/snitch-core/src/lib.rs | 30 +++++++++++++++++++++++++++--- task/packrat/src/ereport.rs | 14 +++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index 9299a15683..c446be7b92 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -86,7 +86,8 @@ impl Store { // If the queue has never been touched, insert our "arbitrary data loss" // record into the stream to consume ENA 0. if !self.initialized() { - self.insert_impl( + // Should always succeed... + let _ = self.insert_impl( self.our_task_id, timestamp, // This is a canned CBOR message that decodes as... @@ -135,7 +136,17 @@ impl Store { /// implementation. This maximum size is larger than we expect our backing /// buffer to be. Any records larger than the max will be treated as not /// fitting. (Currently the max is 64 kiB.) - pub fn insert(&mut self, sender: u16, timestamp: u64, data: &[u8]) { + /// + /// # Returns + /// + /// - `Ok(())` if the record was successfully inserted. + /// - `Err(())` if the record was lost due to insufficient space. + pub fn insert( + &mut self, + sender: u16, + timestamp: u64, + data: &[u8], + ) -> Result<(), ()> { debug_assert!(self.initialized()); self.insert_impl(sender, timestamp, data) } @@ -242,7 +253,17 @@ impl Store { /// /// If we are `Collecting` but `data` plus a header won't fit, we enter the /// `Losing` state with a count of 1. - fn insert_impl(&mut self, sender: u16, timestamp: u64, data: &[u8]) { + /// + /// # Returns + /// + /// - `Ok(())` if the record was successfully inserted. + /// - `Err(())` if the record was lost due to insufficient space. + fn insert_impl( + &mut self, + sender: u16, + timestamp: u64, + data: &[u8], + ) -> Result<(), ()> { // We attempt recovery here so that we can _avoid_ generating a loss // record if this next record (`data`) is just going to kick us back // into loss state. @@ -262,15 +283,18 @@ impl Store { for &byte in data { self.storage.push_back(byte).unwrap_lite(); } + Ok(()) } else { self.insert_state = InsertState::Losing { count: NonZeroU32::new(1).unwrap_lite(), timestamp, }; + Err(()) } } InsertState::Losing { count, .. } => { *count = count.saturating_add(1); + Err(()) } } } diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index ebd581ad19..7dc4f97640 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -51,6 +51,8 @@ enum EreportTrace { EreportDelivered { src: TaskId, len: u32, + #[count(children)] + inserted: Result<(), ()>, }, Flushed { ena: u64, @@ -103,13 +105,15 @@ impl EreportStore { data.read_range(0..data.len(), self.recv) .map_err(|_| ClientError::WentAway.fail())?; let timestamp = sys_get_timer().now; - self.storage - .insert(msg.sender.0, timestamp, &self.recv[..data.len()]); - // TODO(eliza): would maybe be nice to say something if the ereport got - // eaten... + let inserted = self.storage.insert( + msg.sender.0, + timestamp, + &self.recv[..data.len()], + ); ringbuf_entry!(EreportTrace::EreportDelivered { src: msg.sender, - len: data.len() as u32 + len: data.len() as u32, + inserted, }); Ok(()) } From 4a92c667343ac932a281a3cdea8a92c8ffebe05d Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 09:30:27 -0700 Subject: [PATCH 31/61] include task id and timestamp --- Cargo.lock | 1 + task/packrat/Cargo.toml | 4 +-- task/packrat/build.rs | 49 +++++++++++++++++++++++++++++++++++-- task/packrat/src/ereport.rs | 7 +++++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8388cbfd9c..f05a443eb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5711,6 +5711,7 @@ dependencies = [ "minicbor 0.26.4", "mutable-statics", "num-traits", + "quote", "ringbuf", "snitch-core", "spd", diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 776add227c..947ddcf77b 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -28,7 +28,7 @@ minicbor = { workspace = true, optional = true } anyhow.workspace = true cfg-if.workspace = true idol.workspace = true - +quote = { workspace = true, optional = true } build-util = { path = "../../build/util" } [features] @@ -37,7 +37,7 @@ gimlet = ["drv-cpu-seq-api"] grapefruit = [] boot-kmdb = [] no-ipc-counters = ["idol/no-counters"] -ereport = ["dep:drv-rng-api", "dep:snitch-core", "dep:minicbor"] +ereport = ["dep:drv-rng-api", "dep:snitch-core", "dep:minicbor", "dep:quote"] # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. diff --git a/task/packrat/build.rs b/task/packrat/build.rs index 3a13c73984..681f142fd0 100644 --- a/task/packrat/build.rs +++ b/task/packrat/build.rs @@ -1,8 +1,12 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use anyhow::{anyhow, Result}; -fn main() -> Result<(), Box> { +#[cfg(feature = "ereport")] +use anyhow::Context; + +fn main() -> Result<()> { idol::Generator::new() .with_counters( idol::CounterSettings::default().with_server_counters(false), @@ -11,7 +15,8 @@ fn main() -> Result<(), Box> { "../../idl/packrat.idol", "server_stub.rs", idol::server::ServerStyle::InOrder, - )?; + ) + .map_err(|e| anyhow!("{e}"))?; // Ensure the "gimlet" feature is enabled on gimlet boards. #[cfg(not(feature = "gimlet"))] @@ -39,5 +44,45 @@ fn main() -> Result<(), Box> { _ => (), } + #[cfg(feature = "ereport")] + gen_ereport_config().context("failed to generate ereport config")?; + + Ok(()) +} + +#[cfg(feature = "ereport")] +fn gen_ereport_config() -> Result<()> { + use std::io::Write; + + let our_name = build_util::task_name(); + let tasks = build_util::task_ids(); + let id = tasks.get(&our_name).ok_or_else(|| { + anyhow!( + "task ID for {our_name:?} not found in task IDs map; this is \ + probably a bug in the build system", + ) + })?; + let id = u16::try_from(id).with_context(|| { + format!( + "packrat's task ID ({id}) exceeds u16::MAX, this is definitely \ + a bug" + ) + })?; + + let out_dir = build_util::out_dir(); + let dest_path = out_dir.join("ereport_config.rs"); + + let mut out = std::fs::File::create(&dest_path).with_context(|| { + format!("failed to create file {}", dest_path.display()) + })?; + writeln!( + out, + "{}", + quote::quote! { + pub(crate) const TASK_ID: u16 = #id; + } + ) + .with_context(|| format!("failed to write to {}", dest_path.display()))?; + Ok(()) } diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 7dc4f97640..11ee5deedb 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -85,7 +85,8 @@ impl EreportStore { ref mut recv, }: &'static mut EreportBufs, ) -> Self { - storage.initialize(0, 0); // TODO tid timestamp + let now = sys_get_timer().now; + storage.initialize(config::TASK_ID, now); Self { storage, @@ -342,3 +343,7 @@ impl CborLen for ByteGather<'_, '_> { n.cbor_len(ctx) + n } } + +mod config { + include!(concat!(env!("OUT_DIR"), "/ereport_config.rs")); +} From e0a14caab9fac9da77c8d09aa2a96e3d15a878fa Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 09:34:44 -0700 Subject: [PATCH 32/61] ENAs start at 1 --- lib/snitch-core/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index c446be7b92..9bdf944615 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -98,6 +98,8 @@ impl Store { // f6 # null / None &[0xA1, 0x64, 0x6C, 0x6F, 0x73, 0x74, 0xF6], ); + // ENAs start at 1. + self.earliest_ena = 1; } } From 06297652fafbc7ad43485219b495e28869be3908 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 09:41:16 -0700 Subject: [PATCH 33/61] encode serial and part numbers as CBOR strings --- task/packrat/src/ereport.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 11ee5deedb..6c5eb1bceb 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -73,6 +73,7 @@ enum EreportTrace { enum Error { TaskIdOutOfRange, VpdMetadataTooLong, + VpdMetadataNotUtf8 EreportTooLong, } @@ -286,24 +287,31 @@ impl EreportStore { Ok(position) } - fn encode_vpd_metadata(&mut self, vpd: &VpdIdentity) -> Result<&[u8], ()> { + fn encode_vpd_metadata( + &mut self, + vpd: &VpdIdentity, + ) -> Result<&[u8], InternalError> { let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); let mut encoder = minicbor::Encoder::new(c); + let part_number = core::str::from_utf8(&vpd.part_number[..]) + .map_err(|_| InternalError::VpdMetadataNotUtf8)?; + let serial_number = core::str::from_utf8(&vpd.serial[..]) + .map_err(|_| InternalError::VpdMetadataNotUtf8)?; encoder .str("baseboard_part_number") - .map_err(|_| ())? - .bytes(vpd.part_number.as_bytes()) - .map_err(|_| ())?; + .map_err(|_| InternalError::VpdMetadataTooLong)? + .str(part_number) + .map_err(|_| InternalError::VpdMetadataTooLong)?; encoder .str("baseboard_serial_number") - .map_err(|_| ())? - .bytes(vpd.serial.as_bytes()) - .map_err(|_| ())?; + .map_err(|_| InternalError::VpdMetadataTooLong)? + .str(serial_number) + .map_err(|_| InternalError::VpdMetadataTooLong)?; encoder .str("rev") - .map_err(|_| ())? + .map_err(|_| InternalError::VpdMetadataTooLong)? .u32(vpd.revision) - .map_err(|_| ())?; + .map_err(|_| InternalError::VpdMetadataTooLong)?; let size = encoder.into_writer().position(); Ok(&self.recv[..size]) } From 593c20428fe88aa5920b8694c158d82588ef8a89 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 09:43:56 -0700 Subject: [PATCH 34/61] ena tidiness --- task/packrat/src/ereport.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 6c5eb1bceb..1a133b1682 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -25,7 +25,6 @@ use zerocopy::IntoBytes; pub(crate) struct EreportStore { storage: &'static mut snitch_core::Store, recv: &'static mut [u8; RECV_BUF_SIZE], - next_ena: u64, pub(super) restart_id: Option, } @@ -274,7 +273,7 @@ impl EreportStore { }); } - let first_ena = first_written_ena.unwrap_or(self.next_ena); + let first_ena = first_written_ena.unwrap_or(0); let header = ereport_messages::ResponseHeader::V0( ereport_messages::ResponseHeaderV0 { request_id, From 81497b6de7b5f7f4d314a90f0f5f64a8d9f54ab7 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 09:45:06 -0700 Subject: [PATCH 35/61] constify CBOR break byte --- task/packrat/src/ereport.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 1a133b1682..3ef05a8662 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -129,6 +129,10 @@ impl EreportStore { data: Leased, vpd: Option<&VpdIdentity>, ) -> Result> { + /// Byte indicating the end of an indeterminate-length CBOR array or + /// map. + const CBOR_BREAK: u8 = 0xff; + let current_restart_id = self.restart_id.ok_or(EreportReadError::RestartIdNotSet)?; // Skip over a header-sized initial chunk. @@ -186,7 +190,7 @@ impl EreportStore { }; // End metadata map. - data.write_at(position, 0xff) + data.write_at(position, CBOR_BREAK) .map_err(|_| ClientError::WentAway.fail())?; position += 1; @@ -262,7 +266,7 @@ impl EreportStore { if let Some(start_ena) = first_written_ena { // End CBOR list, if we wrote anything. - data.write_at(position, 0xff) + data.write_at(position, CBOR_BREAK) .map_err(|_| ClientError::WentAway.fail())?; position += 1; From e633749bd4c9dcc318dd323774b2c88dabc41811 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 09:45:19 -0700 Subject: [PATCH 36/61] blargh --- task/packrat/src/ereport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 3ef05a8662..1b9dc6d0f1 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -72,7 +72,7 @@ enum EreportTrace { enum Error { TaskIdOutOfRange, VpdMetadataTooLong, - VpdMetadataNotUtf8 + VpdMetadataNotUtf8, EreportTooLong, } From 28b5b87bfeabbc1541614328b18edfd09c92eba7 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 10:12:32 -0700 Subject: [PATCH 37/61] metadata embetterment --- app/cosmo/base.toml | 2 +- app/gimlet/base.toml | 2 +- app/gimletlet/app.toml | 2 +- app/psc/base.toml | 2 +- app/sidecar/base.toml | 2 +- task/packrat/src/ereport.rs | 107 ++++++++++++++++++++++-------------- 6 files changed, 70 insertions(+), 47 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index cd543afa0b..62abc4ff93 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -123,7 +123,7 @@ notifications = ["i2c1-irq", "i2c2-irq", "i2c3-irq", "i2c4-irq"] [tasks.packrat] name = "task-packrat" priority = 1 -stacksize = 1024 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 335bb4a079..c163676baa 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -124,7 +124,7 @@ notifications = ["i2c1-irq", "jefe-state-change"] [tasks.packrat] name = "task-packrat" priority = 1 -stacksize = 1024 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 1936040982..6a8b6f1693 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -46,7 +46,7 @@ priority = 1 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] -stacksize = 1024 +stacksize = 1040 features = ["ereport"] [caboose] diff --git a/app/psc/base.toml b/app/psc/base.toml index 3ce2459722..699026ca7c 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -119,7 +119,7 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" priority = 3 -stacksize = 1024 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index ffd8c4ab40..75655aa214 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -258,7 +258,7 @@ notifications = ["socket", "timer"] [tasks.packrat] name = "task-packrat" priority = 1 -stacksize = 1024 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 1b9dc6d0f1..020df950a9 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -19,12 +19,13 @@ use idol_runtime::{ClientError, Leased, LenLimit, RequestError}; use minicbor::CborLen; use ringbuf::{counted_ringbuf, ringbuf_entry}; use task_packrat_api::{EreportReadError, VpdIdentity}; -use userlib::{sys_get_timer, RecvMessage, TaskId}; +use userlib::{kipc, sys_get_timer, RecvMessage, TaskId}; use zerocopy::IntoBytes; pub(crate) struct EreportStore { storage: &'static mut snitch_core::Store, recv: &'static mut [u8; RECV_BUF_SIZE], + image_id: [u8; 8], pub(super) restart_id: Option, } @@ -72,7 +73,8 @@ enum EreportTrace { enum Error { TaskIdOutOfRange, VpdMetadataTooLong, - VpdMetadataNotUtf8, + PartNumberNotUtf8, + SerialNumberNotUtf8, EreportTooLong, } @@ -87,11 +89,15 @@ impl EreportStore { ) -> Self { let now = sys_get_timer().now; storage.initialize(config::TASK_ID, now); + let image_id = { + let id = kipc::read_image_id(); + u64::to_le_bytes(id) + }; Self { storage, recv, - next_ena: 0, + image_id, restart_id: None, } } @@ -165,25 +171,22 @@ impl EreportStore { }); // Encode the metadata map into our buffer. - if let Some(vpd) = vpd { - match self.encode_vpd_metadata(vpd) { - Ok(encoded) => { - data.write_range( - position..position + encoded.len(), - encoded, - ) - .map_err(|_| ClientError::WentAway.fail())?; - - position += encoded.len(); - } - Err(_end) => { - // Encoded VPD metadata was too long! - ringbuf_entry!(EreportTrace::InternalError( - Error::VpdMetadataTooLong - )); - } + match self.encode_metadata(vpd) { + Ok(encoded) => { + data.write_range( + position..position + encoded.len(), + encoded, + ) + .map_err(|_| ClientError::WentAway.fail())?; + + position += encoded.len(); } - } + Err(err) => { + // Encoded VPD metadata was too long, or couldn't be + // represented as a CBOR string. + ringbuf_entry!(EreportTrace::InternalError(err)); + } + }; // Begin at ENA 0 0 @@ -290,31 +293,51 @@ impl EreportStore { Ok(position) } - fn encode_vpd_metadata( + fn encode_metadata( &mut self, - vpd: &VpdIdentity, - ) -> Result<&[u8], InternalError> { + vpd: Option<&VpdIdentity>, + ) -> Result<&[u8], Error> { let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); let mut encoder = minicbor::Encoder::new(c); - let part_number = core::str::from_utf8(&vpd.part_number[..]) - .map_err(|_| InternalError::VpdMetadataNotUtf8)?; - let serial_number = core::str::from_utf8(&vpd.serial[..]) - .map_err(|_| InternalError::VpdMetadataNotUtf8)?; - encoder - .str("baseboard_part_number") - .map_err(|_| InternalError::VpdMetadataTooLong)? - .str(part_number) - .map_err(|_| InternalError::VpdMetadataTooLong)?; + // TODO(eliza): presently, this code bails out if the metadata map gets + // longer than our buffer. It would be nice to have a way to keep the + // encoded metadata up to the last complete key-value pair... encoder - .str("baseboard_serial_number") - .map_err(|_| InternalError::VpdMetadataTooLong)? - .str(serial_number) - .map_err(|_| InternalError::VpdMetadataTooLong)?; - encoder - .str("rev") - .map_err(|_| InternalError::VpdMetadataTooLong)? - .u32(vpd.revision) - .map_err(|_| InternalError::VpdMetadataTooLong)?; + .str("hubris_archive_id") + .map_err(|_| Error::VpdMetadataTooLong)? + .bytes(&self.image_id[..]) + .map_err(|_| Error::VpdMetadataTooLong)?; + if let Some(vpd) = vpd { + match core::str::from_utf8(&vpd.part_number[..]) { + Ok(part_number) => { + encoder + .str("baseboard_part_number") + .map_err(|_| Error::VpdMetadataTooLong)? + .str(part_number) + .map_err(|_| Error::VpdMetadataTooLong)?; + } + Err(_) => ringbuf_entry!(EreportTrace::InternalError( + Error::PartNumberNotUtf8 + )), + } + match core::str::from_utf8(&vpd.serial[..]) { + Ok(serial_number) => { + encoder + .str("baseboard_serial_number") + .map_err(|_| Error::VpdMetadataTooLong)? + .str(serial_number) + .map_err(|_| Error::VpdMetadataTooLong)?; + } + Err(_) => ringbuf_entry!(EreportTrace::InternalError( + Error::SerialNumberNotUtf8 + )), + } + encoder + .str("rev") + .map_err(|_| Error::VpdMetadataTooLong)? + .u32(vpd.revision) + .map_err(|_| Error::VpdMetadataTooLong)?; + } let size = encoder.into_writer().position(); Ok(&self.recv[..size]) } From d2c77d5d1b8805fe454f16270ec46dc1d942e35c Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 14:17:06 -0700 Subject: [PATCH 38/61] ENAs start at 1 now --- lib/snitch-core/src/lib.rs | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index 9bdf944615..9b612f2d1c 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -459,7 +459,7 @@ mod tests { let &[r] = initial_contents.as_slice() else { panic!("missing initial loss record"); }; - assert_eq!(r.ena, 0); + assert_eq!(r.ena, 1); assert_eq!(r.tid, OUR_FAKE_TID); assert_eq!(r.timestamp, 1); @@ -482,7 +482,7 @@ mod tests { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 1); - assert_eq!(snapshot[0].ena, 1); + assert_eq!(snapshot[0].ena, 2); assert_eq!(snapshot[0].tid, ANOTHER_FAKE_TID); assert_eq!(snapshot[0].timestamp, 5); assert_eq!(snapshot[0].contents, b"hello, world!"); @@ -510,7 +510,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: ANOTHER_FAKE_TID, timestamp: 5, contents: vec![0; 64 - OVERHEAD] @@ -541,7 +541,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: OUR_FAKE_TID, timestamp: 5, contents: LossRecord { lost: Some(1) }, @@ -572,7 +572,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: ANOTHER_FAKE_TID, timestamp: 5, contents: vec![0; 28], @@ -582,7 +582,7 @@ mod tests { assert_eq!( snapshot[1].decode_as::(), Item { - ena: 2, + ena: 3, tid: OUR_FAKE_TID, timestamp: 10, // time when loss began contents: LossRecord { lost: Some(1) }, @@ -615,7 +615,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: OUR_FAKE_TID, timestamp: 5, // time of _first_ loss contents: LossRecord { lost: Some(11) }, @@ -645,7 +645,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: ANOTHER_FAKE_TID, timestamp: 5, contents: vec![0; 16], @@ -655,7 +655,7 @@ mod tests { assert_eq!( snapshot[1].decode_as::(), Item { - ena: 2, + ena: 3, tid: OUR_FAKE_TID, timestamp: 10, contents: LossRecord { lost: Some(1) }, @@ -665,7 +665,7 @@ mod tests { assert_eq!( snapshot[2], Item { - ena: 3, + ena: 4, tid: ANOTHER_FAKE_TID, timestamp: 15, contents: vec![0; 16], @@ -689,7 +689,7 @@ mod tests { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 5); for (i, rec) in snapshot.iter().enumerate() { - assert_eq!(rec.ena, 1 + i as u64); + assert_eq!(rec.ena, 2 + i as u64); assert_eq!(rec.tid, ANOTHER_FAKE_TID); assert_eq!(rec.timestamp, 5 + i as u64); assert_eq!(rec.contents, &[i as u8]); @@ -704,7 +704,7 @@ mod tests { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 4); for (i, rec) in snapshot.iter().enumerate() { - assert_eq!(rec.ena, 2 + i as u64); + assert_eq!(rec.ena, 3 + i as u64); assert_eq!(rec.tid, ANOTHER_FAKE_TID); assert_eq!(rec.timestamp, 6 + i as u64); assert_eq!(rec.contents, &[i as u8 + 1]); @@ -712,19 +712,19 @@ mod tests { } // Flush all but the last. - s.flush_thru(4); + s.flush_thru(5); { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 1); for (i, rec) in snapshot.iter().enumerate() { - assert_eq!(rec.ena, 5 + i as u64); + assert_eq!(rec.ena, 6 + i as u64); assert_eq!(rec.tid, ANOTHER_FAKE_TID); assert_eq!(rec.timestamp, 9 + i as u64); assert_eq!(rec.contents, &[i as u8 + 4]); } } // Finally... - s.flush_thru(5); + s.flush_thru(6); assert_eq!(copy_contents_raw(&mut s), []); } @@ -742,16 +742,16 @@ mod tests { assert_eq!(s.stored_record_count, 2); - // Flushing ENA 0 should have no effect (we already got rid of it) - s.flush_thru(0); + // Flushing ENA 1 should have no effect (we already got rid of it) + s.flush_thru(1); assert_eq!(s.stored_record_count, 2); - // Flushing ENA 1 should drop one record. - s.flush_thru(1); + // Flushing ENA 2 should drop one record. + s.flush_thru(2); assert_eq!(s.stored_record_count, 1); // 0 and 1 are both no-ops now. - for ena in [0, 1] { + for ena in [0, 1, 2] { s.flush_thru(ena); assert_eq!(s.stored_record_count, 1); } @@ -772,8 +772,8 @@ mod tests { assert_eq!(s.stored_record_count, 2); - // ENA 3 has not yet been issued, and should not cause any change: - s.flush_thru(3); + // ENA 4 has not yet been issued, and should not cause any change: + s.flush_thru(4); assert_eq!(s.stored_record_count, 2); } @@ -782,7 +782,7 @@ mod tests { let &[r] = initial_contents.as_slice() else { panic!("missing initial loss record"); }; - assert_eq!(r.ena, 0); + assert_eq!(r.ena, 1); assert_eq!(r.tid, OUR_FAKE_TID); assert_eq!(r.timestamp, 1); From ff93754b694fbbc6c9f60c0b85bab4b5470e3f98 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 14:23:40 -0700 Subject: [PATCH 39/61] add test reproducing panic when trying to insert loss --- lib/snitch-core/src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index 9b612f2d1c..d16dcb8fe8 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -623,6 +623,46 @@ mod tests { ); } + /// Tests that the buffer is never allowed to fill so much that it cannot + /// fit a loss record. This reproduces a panic where there was insufficient + /// space to record a loss record. + #[test] + fn data_loss_on_full_queue() { + let mut s = Store::<64>::DEFAULT; + s.initialize(OUR_FAKE_TID, 1); + consume_initial_loss(&mut s); + + // Fill half the buffer. + s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]); + // Try to fill the other half of the buffer, *to the brim*. Allowing + // this record in will mean that the buffer no longer has space for a + // last loss record, so this record should *not* be accepted. + s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]); + // This one definitely gets lost. + s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]); + + let snapshot: Vec>> = copy_contents_raw(&mut s); + assert_eq!(snapshot.len(), 2, "{snapshot:?}"); + assert_eq!( + snapshot[0], + Item { + ena: 2, + tid: ANOTHER_FAKE_TID, + timestamp: 5, + contents: Vec::from([0; 32 - OVERHEAD]) + } + ); + assert_eq!( + snapshot[1].decode_as::(), + Item { + ena: 3, + tid: OUR_FAKE_TID, + timestamp: 6, + contents: LossRecord { lost: Some(2) }, + } + ); + } + /// Arranges for the queue to contain: valid data; a loss record; more valid /// data. This helps exercise recovery behavior. #[test] From 0b8e1ed5d940d94d2ab92a04c34f4bd2b70334be Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 14:34:40 -0700 Subject: [PATCH 40/61] always reserve space for loss Currently, `snitc-core` will panic on reads if we have lost data and there is no space for an additional loss record in the buffer. This is because reads while the store is in the `Losing` state will attempt to insert a loss record to report that data has been lost. However, if there is no space in the queue, inserting the loss record will panic. This commit fixes this by reserving space for one additional loss record at all times. We will no longer admit new messages into the buffer if they mean we cannot encode a loss record when more messages come in. This, unfortunately, means that we can no longer admit entries that fill the entire queue, due to this reservation. --- lib/snitch-core/src/lib.rs | 48 +++++++++++--------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index d16dcb8fe8..6b7d1f9d4f 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -116,9 +116,7 @@ impl Store { InsertState::Losing { .. } => { // Indicate how much space we have after recovery succeeds, // which may be zero if we can't recover yet. - (N - self.storage.len()) - .saturating_sub(OVERHEAD) - .saturating_sub(DATA_LOSS_LEN) + (N - self.storage.len()).saturating_sub(ONE_LOSS_RECORD_LEN) } } } @@ -276,7 +274,17 @@ impl Store { match &mut self.insert_state { InsertState::Collecting => { let room = self.storage.capacity() - self.storage.len(); - if data_len.is_some_and(|n| room >= OVERHEAD + n as usize) { + if data_len.is_some_and(|n| { + let required = + // Space for the record itself. + OVERHEAD + n as usize + // Always reserve space for an additional loss + // record at the end of the buffer, as we must + // be able to record loss if more records + // come in. + + ONE_LOSS_RECORD_LEN; + room >= required + }) { self.write_header( data_len.unwrap_lite(), sender, @@ -418,6 +426,8 @@ fn take_slice<'a>( const DATA_LOSS_LEN: usize = 11; const OVERHEAD: usize = 12; +const ONE_LOSS_RECORD_LEN: usize = DATA_LOSS_LEN + OVERHEAD; + #[derive(Copy, Clone, Debug)] enum InsertState { /// We successfully recorded the last record (which may have been a loss @@ -492,36 +502,6 @@ mod tests { assert_eq!(copy_contents_raw(&mut s), []); } - /// Verifies that a message that consumes 100% of the queue can be enqueued. - #[test] - fn filling_completely() { - let mut s = Store::<64>::DEFAULT; - s.initialize(OUR_FAKE_TID, 1); - consume_initial_loss(&mut s); - - // This message just fits. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD]); - - assert_eq!(s.free_space(), 0); - - // Which means it should be able to come back out. - let snapshot = copy_contents_raw(&mut s); - assert_eq!(snapshot.len(), 1); - assert_eq!( - snapshot[0], - Item { - ena: 2, - tid: ANOTHER_FAKE_TID, - timestamp: 5, - contents: vec![0; 64 - OVERHEAD] - } - ); - - // And be flushed. - s.flush_thru(snapshot[0].ena); - assert_eq!(s.free_space(), 64); - } - /// Tests behavior if the queue goes directly from empty to overflow. /// Verifies that the situation is recoverable at the next read (no flush is /// required). From 9bb7ffafaa806e14be2bd45b294237a09ace69d9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 14:47:54 -0700 Subject: [PATCH 41/61] Revert "always reserve space for loss" This reverts commit 0b8e1ed5d940d94d2ab92a04c34f4bd2b70334be. --- lib/snitch-core/src/lib.rs | 48 +++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index 6b7d1f9d4f..d16dcb8fe8 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -116,7 +116,9 @@ impl Store { InsertState::Losing { .. } => { // Indicate how much space we have after recovery succeeds, // which may be zero if we can't recover yet. - (N - self.storage.len()).saturating_sub(ONE_LOSS_RECORD_LEN) + (N - self.storage.len()) + .saturating_sub(OVERHEAD) + .saturating_sub(DATA_LOSS_LEN) } } } @@ -274,17 +276,7 @@ impl Store { match &mut self.insert_state { InsertState::Collecting => { let room = self.storage.capacity() - self.storage.len(); - if data_len.is_some_and(|n| { - let required = - // Space for the record itself. - OVERHEAD + n as usize - // Always reserve space for an additional loss - // record at the end of the buffer, as we must - // be able to record loss if more records - // come in. - + ONE_LOSS_RECORD_LEN; - room >= required - }) { + if data_len.is_some_and(|n| room >= OVERHEAD + n as usize) { self.write_header( data_len.unwrap_lite(), sender, @@ -426,8 +418,6 @@ fn take_slice<'a>( const DATA_LOSS_LEN: usize = 11; const OVERHEAD: usize = 12; -const ONE_LOSS_RECORD_LEN: usize = DATA_LOSS_LEN + OVERHEAD; - #[derive(Copy, Clone, Debug)] enum InsertState { /// We successfully recorded the last record (which may have been a loss @@ -502,6 +492,36 @@ mod tests { assert_eq!(copy_contents_raw(&mut s), []); } + /// Verifies that a message that consumes 100% of the queue can be enqueued. + #[test] + fn filling_completely() { + let mut s = Store::<64>::DEFAULT; + s.initialize(OUR_FAKE_TID, 1); + consume_initial_loss(&mut s); + + // This message just fits. + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD]); + + assert_eq!(s.free_space(), 0); + + // Which means it should be able to come back out. + let snapshot = copy_contents_raw(&mut s); + assert_eq!(snapshot.len(), 1); + assert_eq!( + snapshot[0], + Item { + ena: 2, + tid: ANOTHER_FAKE_TID, + timestamp: 5, + contents: vec![0; 64 - OVERHEAD] + } + ); + + // And be flushed. + s.flush_thru(snapshot[0].ena); + assert_eq!(s.free_space(), 64); + } + /// Tests behavior if the queue goes directly from empty to overflow. /// Verifies that the situation is recoverable at the next read (no flush is /// required). From 22044d10948b7e68fe98917b0b3ef35bbbe998d4 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 14:56:39 -0700 Subject: [PATCH 42/61] nicer solution: make the check for loss record space better --- lib/snitch-core/src/lib.rs | 62 ++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index d16dcb8fe8..c22c7f89f1 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -111,16 +111,7 @@ impl Store { /// Returns the current free space in the queue, in bytes. This is raw; /// subtract 12 to get the largest single message that can be enqueued. pub fn free_space(&self) -> usize { - match self.insert_state { - InsertState::Collecting => N - self.storage.len(), - InsertState::Losing { .. } => { - // Indicate how much space we have after recovery succeeds, - // which may be zero if we can't recover yet. - (N - self.storage.len()) - .saturating_sub(OVERHEAD) - .saturating_sub(DATA_LOSS_LEN) - } - } + N - self.storage.len() } /// Inserts a record, or records it as lost. @@ -275,7 +266,7 @@ impl Store { match &mut self.insert_state { InsertState::Collecting => { - let room = self.storage.capacity() - self.storage.len(); + let room = self.free_space(); if data_len.is_some_and(|n| room >= OVERHEAD + n as usize) { self.write_header( data_len.unwrap_lite(), @@ -320,9 +311,17 @@ impl Store { fn recover_if_required(&mut self, space_required: Option) { // We only need to take action if we're in Losing state. if let InsertState::Losing { count, timestamp } = self.insert_state { - // Note: already includes OVERHEAD/DATA_LOSS_LEN let room = self.free_space(); - let required = space_required.unwrap_or(0); + + // We can recover only if there is room for both the required + // amount of space *and* the additional loss record we must + // insert in order to recover. + let required = + // The space we hope to be able to use after recovery + space_required.unwrap_or(0) + // The length of the loss record itself + + DATA_LOSS_LEN + OVERHEAD + ; if room >= required { // We can recover! self.write_header( @@ -634,9 +633,9 @@ mod tests { // Fill half the buffer. s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]); - // Try to fill the other half of the buffer, *to the brim*. Allowing - // this record in will mean that the buffer no longer has space for a - // last loss record, so this record should *not* be accepted. + // Try to fill the other half of the buffer, *to the brim*. After this + // record is accepted, we start losing data, but we cannot yet create a + // loss record until something is removed from the buffer. s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]); // This one definitely gets lost. s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]); @@ -653,12 +652,37 @@ mod tests { } ); assert_eq!( - snapshot[1].decode_as::(), + snapshot[1], Item { ena: 3, - tid: OUR_FAKE_TID, + tid: ANOTHER_FAKE_TID, timestamp: 6, - contents: LossRecord { lost: Some(2) }, + contents: Vec::from([0; 32 - OVERHEAD]) + } + ); + + // Flush a record. We will now record the lost data, as there's now + // room for the loss record. + s.flush_thru(2); + + let snapshot: Vec>> = copy_contents_raw(&mut s); + assert_eq!(snapshot.len(), 2, "{snapshot:?}"); + assert_eq!( + snapshot[0], + Item { + ena: 3, + tid: ANOTHER_FAKE_TID, + timestamp: 6, + contents: Vec::from([0; 32 - OVERHEAD]) + } + ); + assert_eq!( + snapshot[1].decode_as::(), + Item { + ena: 4, + tid: OUR_FAKE_TID, + timestamp: 7, + contents: LossRecord { lost: Some(1) }, } ); } From 610b0750bba72c5c71b38c289dcfcbaa2334bd7f Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 15:38:54 -0700 Subject: [PATCH 43/61] these are results now --- lib/snitch-core/src/lib.rs | 48 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index c22c7f89f1..d016fe562c 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -477,7 +477,7 @@ mod tests { consume_initial_loss(&mut s); // Insert a thing! We don't care if it's valid CBOR. - s.insert(ANOTHER_FAKE_TID, 5, b"hello, world!"); + s.insert(ANOTHER_FAKE_TID, 5, b"hello, world!").unwrap(); let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 1); @@ -499,7 +499,7 @@ mod tests { consume_initial_loss(&mut s); // This message just fits. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD]); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD]).unwrap(); assert_eq!(s.free_space(), 0); @@ -531,7 +531,8 @@ mod tests { consume_initial_loss(&mut s); // This message is juuuuust too long to fit, by one byte. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]) + .unwrap_err(); // Because the queue is otherwise empty, the next read should produce a // data loss message. @@ -557,10 +558,10 @@ mod tests { consume_initial_loss(&mut s); // This message fits. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 28]); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 28]).unwrap(); // This message would fit on its own, but there is not enough room for // it. - s.insert(ANOTHER_FAKE_TID, 10, &[0; 28]); + s.insert(ANOTHER_FAKE_TID, 10, &[0; 28]).unwrap_err(); // We should still be able to read out the first message, followed by a // one-record loss. There should be enough space available in the queue @@ -600,11 +601,13 @@ mod tests { consume_initial_loss(&mut s); // This message is juuuuust too long to fit, by one byte. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]) + .unwrap_err(); // Now that we're in losing state, any message too big to allow recovery // just accumulates. Let's do that a few times, shall we? for i in 0..10 { - s.insert(ANOTHER_FAKE_TID, 5 + i, &[0; 64 - OVERHEAD + 1]); + s.insert(ANOTHER_FAKE_TID, 5 + i, &[0; 64 - OVERHEAD + 1]) + .unwrap_err(); } // Because the queue is otherwise empty, the next read should produce a @@ -632,13 +635,14 @@ mod tests { consume_initial_loss(&mut s); // Fill half the buffer. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]).unwrap(); // Try to fill the other half of the buffer, *to the brim*. After this // record is accepted, we start losing data, but we cannot yet create a // loss record until something is removed from the buffer. - s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]); + s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]).unwrap(); // This one definitely gets lost. - s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]); + s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]) + .unwrap_err(); let snapshot: Vec>> = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 2, "{snapshot:?}"); @@ -696,12 +700,12 @@ mod tests { consume_initial_loss(&mut s); // Insert a message... - s.insert(ANOTHER_FAKE_TID, 5, &[0; 16]); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 16]).unwrap(); assert_eq!(s.free_space(), 100); // Drop a message... - s.insert(ANOTHER_FAKE_TID, 10, &[0; 100]); + s.insert(ANOTHER_FAKE_TID, 10, &[0; 100]).unwrap_err(); // Insert a message that will fit along with recovery... - s.insert(ANOTHER_FAKE_TID, 15, &[0; 16]); + s.insert(ANOTHER_FAKE_TID, 15, &[0; 16]).unwrap(); let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 3, "{snapshot:?}"); @@ -746,7 +750,7 @@ mod tests { // Insert a series of five records occupying ENAs 1-5. for i in 0..5 { - s.insert(ANOTHER_FAKE_TID, 5 + i, &[i as u8]); + s.insert(ANOTHER_FAKE_TID, 5 + i, &[i as u8]).unwrap(); } { @@ -799,10 +803,10 @@ mod tests { s.initialize(OUR_FAKE_TID, 1); consume_initial_loss(&mut s); - // This record occupies ENA 1 - s.insert(ANOTHER_FAKE_TID, 5, &[1]); - // ENA 2 - s.insert(ANOTHER_FAKE_TID, 6, &[2]); + // This record occupies ENA 2 + s.insert(ANOTHER_FAKE_TID, 5, &[1]).unwrap(); + // ENA 3 + s.insert(ANOTHER_FAKE_TID, 6, &[2]).unwrap(); assert_eq!(s.stored_record_count, 2); @@ -829,10 +833,10 @@ mod tests { s.initialize(OUR_FAKE_TID, 1); consume_initial_loss(&mut s); - // This record occupies ENA 1 - s.insert(ANOTHER_FAKE_TID, 5, &[1]); - // ENA 2 - s.insert(ANOTHER_FAKE_TID, 6, &[2]); + // This record occupies ENA 2 + s.insert(ANOTHER_FAKE_TID, 5, &[1]).unwrap(); + // ENA 3 + s.insert(ANOTHER_FAKE_TID, 6, &[2]).unwrap(); assert_eq!(s.stored_record_count, 2); From 920d9f8c857a37c5a0f8a4369db95a8bc45d7696 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 8 Jul 2025 17:03:24 -0700 Subject: [PATCH 44/61] prettier ringbuf --- Cargo.lock | 1 + app/gimletlet/app.toml | 2 +- lib/snitch-core/Cargo.toml | 1 + lib/snitch-core/src/lib.rs | 78 +++++++++++++++++++++---------------- task/packrat/Cargo.toml | 2 +- task/packrat/src/ereport.rs | 12 +++--- 6 files changed, 55 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f05a443eb0..4f117e16a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4922,6 +4922,7 @@ dependencies = [ name = "snitch-core" version = "0.1.0" dependencies = [ + "counters", "heapless", "minicbor-serde", "serde", diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 6a8b6f1693..56d70f6836 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -46,7 +46,7 @@ priority = 1 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] -stacksize = 1040 +stacksize = 2048 features = ["ereport"] [caboose] diff --git a/lib/snitch-core/Cargo.toml b/lib/snitch-core/Cargo.toml index 3b82c43bd2..ac352830cf 100644 --- a/lib/snitch-core/Cargo.toml +++ b/lib/snitch-core/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] heapless.workspace = true unwrap-lite = { version = "0.1.0", path = "../unwrap-lite" } +counters = { path = "../counters", optional = true} [lints] workspace = true diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index d016fe562c..c092e2091f 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -63,6 +63,13 @@ pub struct Store { stored_record_count: usize, } +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "counters", derive(counters::Count))] +pub enum InsertResult { + Inserted, + Lost, +} + impl Store { /// Empty store constant for use in static initializers. /// @@ -132,14 +139,15 @@ impl Store { /// /// # Returns /// - /// - `Ok(())` if the record was successfully inserted. - /// - `Err(())` if the record was lost due to insufficient space. + /// - [`InsertResult::Inserted`] if the record was successfully inserted. + /// - [`InsertResult::Lost`] if the record was lost due to insufficient + /// space. pub fn insert( &mut self, sender: u16, timestamp: u64, data: &[u8], - ) -> Result<(), ()> { + ) -> InsertResult { debug_assert!(self.initialized()); self.insert_impl(sender, timestamp, data) } @@ -193,11 +201,15 @@ impl Store { /// /// If the ENA is either lower than any of our stored records, or higher /// than what we've vended out, this is a no-op. - pub fn flush_thru(&mut self, last_written_ena: u64) { + /// + /// # Returns + /// + /// This method returns the number of records that were discarded. + pub fn flush_thru(&mut self, last_written_ena: u64) -> usize { let Some(index) = last_written_ena.checked_sub(self.earliest_ena) else { // Cool, we're already aware that record has been written. - return; + return 0; }; if index >= self.stored_record_count as u64 { // Uhhhh. We have not issued this ENA. It could not possibly have @@ -205,7 +217,7 @@ impl Store { // // TODO: is this an opportunity for the queue _itself_ to generate // a defect report? For now, we'll just ignore it. - return; + return 0; } for _ in 0..=index { @@ -231,6 +243,7 @@ impl Store { // full. If we are able to recover here, but do _not_ have enough bytes // free for the next incoming record, then we'd just kick back into loss // state.... necessitating another loss record. And so forth. + index as usize } /// Makes a best effort at inserting a record. @@ -249,14 +262,15 @@ impl Store { /// /// # Returns /// - /// - `Ok(())` if the record was successfully inserted. - /// - `Err(())` if the record was lost due to insufficient space. + /// - [`InsertResult::Inserted`] if the record was successfully inserted. + /// - [`InsertResult::Lost`] if the record was lost due to insufficient + /// space. fn insert_impl( &mut self, sender: u16, timestamp: u64, data: &[u8], - ) -> Result<(), ()> { + ) -> InsertResult { // We attempt recovery here so that we can _avoid_ generating a loss // record if this next record (`data`) is just going to kick us back // into loss state. @@ -276,18 +290,18 @@ impl Store { for &byte in data { self.storage.push_back(byte).unwrap_lite(); } - Ok(()) + InsertResult::Inserted } else { self.insert_state = InsertState::Losing { count: NonZeroU32::new(1).unwrap_lite(), timestamp, }; - Err(()) + InsertResult::Lost } } InsertState::Losing { count, .. } => { *count = count.saturating_add(1); - Err(()) + InsertResult::Lost } } } @@ -477,7 +491,7 @@ mod tests { consume_initial_loss(&mut s); // Insert a thing! We don't care if it's valid CBOR. - s.insert(ANOTHER_FAKE_TID, 5, b"hello, world!").unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, b"hello, world!"); let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 1); @@ -499,7 +513,7 @@ mod tests { consume_initial_loss(&mut s); // This message just fits. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD]); assert_eq!(s.free_space(), 0); @@ -531,8 +545,7 @@ mod tests { consume_initial_loss(&mut s); // This message is juuuuust too long to fit, by one byte. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]) - .unwrap_err(); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]); // Because the queue is otherwise empty, the next read should produce a // data loss message. @@ -558,10 +571,10 @@ mod tests { consume_initial_loss(&mut s); // This message fits. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 28]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 28]); // This message would fit on its own, but there is not enough room for // it. - s.insert(ANOTHER_FAKE_TID, 10, &[0; 28]).unwrap_err(); + s.insert(ANOTHER_FAKE_TID, 10, &[0; 28]); // We should still be able to read out the first message, followed by a // one-record loss. There should be enough space available in the queue @@ -601,13 +614,11 @@ mod tests { consume_initial_loss(&mut s); // This message is juuuuust too long to fit, by one byte. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]) - .unwrap_err(); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 64 - OVERHEAD + 1]); // Now that we're in losing state, any message too big to allow recovery // just accumulates. Let's do that a few times, shall we? for i in 0..10 { - s.insert(ANOTHER_FAKE_TID, 5 + i, &[0; 64 - OVERHEAD + 1]) - .unwrap_err(); + s.insert(ANOTHER_FAKE_TID, 5 + i, &[0; 64 - OVERHEAD + 1]); } // Because the queue is otherwise empty, the next read should produce a @@ -635,14 +646,13 @@ mod tests { consume_initial_loss(&mut s); // Fill half the buffer. - s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]); // Try to fill the other half of the buffer, *to the brim*. After this // record is accepted, we start losing data, but we cannot yet create a // loss record until something is removed from the buffer. - s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]); // This one definitely gets lost. - s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]) - .unwrap_err(); + s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]); let snapshot: Vec>> = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 2, "{snapshot:?}"); @@ -700,12 +710,12 @@ mod tests { consume_initial_loss(&mut s); // Insert a message... - s.insert(ANOTHER_FAKE_TID, 5, &[0; 16]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, &[0; 16]); assert_eq!(s.free_space(), 100); // Drop a message... - s.insert(ANOTHER_FAKE_TID, 10, &[0; 100]).unwrap_err(); + s.insert(ANOTHER_FAKE_TID, 10, &[0; 100]); // Insert a message that will fit along with recovery... - s.insert(ANOTHER_FAKE_TID, 15, &[0; 16]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 15, &[0; 16]); let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 3, "{snapshot:?}"); @@ -750,7 +760,7 @@ mod tests { // Insert a series of five records occupying ENAs 1-5. for i in 0..5 { - s.insert(ANOTHER_FAKE_TID, 5 + i, &[i as u8]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5 + i, &[i as u8]); } { @@ -804,9 +814,9 @@ mod tests { consume_initial_loss(&mut s); // This record occupies ENA 2 - s.insert(ANOTHER_FAKE_TID, 5, &[1]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, &[1]); // ENA 3 - s.insert(ANOTHER_FAKE_TID, 6, &[2]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 6, &[2]); assert_eq!(s.stored_record_count, 2); @@ -834,9 +844,9 @@ mod tests { consume_initial_loss(&mut s); // This record occupies ENA 2 - s.insert(ANOTHER_FAKE_TID, 5, &[1]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 5, &[1]); // ENA 3 - s.insert(ANOTHER_FAKE_TID, 6, &[2]).unwrap(); + s.insert(ANOTHER_FAKE_TID, 6, &[2]); assert_eq!(s.stored_record_count, 2); diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 947ddcf77b..41556efce9 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -21,7 +21,7 @@ counters = { path = "../../lib/counters" } static-cell = { path = "../../lib/static-cell" } task-packrat-api = { path = "../packrat-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } -snitch-core = { version = "0.1.0", path = "../../lib/snitch-core", optional = true } +snitch-core = { version = "0.1.0", path = "../../lib/snitch-core", optional = true, features = ["counters"] } minicbor = { workspace = true, optional = true } [build-dependencies] diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 020df950a9..f99a065d67 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -52,10 +52,11 @@ enum EreportTrace { src: TaskId, len: u32, #[count(children)] - inserted: Result<(), ()>, + result: snitch_core::InsertResult, }, Flushed { ena: u64, + flushed: usize, }, RestartIdMismatch { current: u128, @@ -112,7 +113,7 @@ impl EreportStore { data.read_range(0..data.len(), self.recv) .map_err(|_| ClientError::WentAway.fail())?; let timestamp = sys_get_timer().now; - let inserted = self.storage.insert( + let result = self.storage.insert( msg.sender.0, timestamp, &self.recv[..data.len()], @@ -120,7 +121,7 @@ impl EreportStore { ringbuf_entry!(EreportTrace::EreportDelivered { src: msg.sender, len: data.len() as u32, - inserted, + result, }); Ok(()) } @@ -158,9 +159,10 @@ impl EreportStore { // If the restart ID matches, flush previous ereports up to // `committed_ena`, if there is one. if committed_ena != ereport_messages::Ena::NONE { - self.storage.flush_thru(committed_ena.into()); + let flushed = self.storage.flush_thru(committed_ena.into()); ringbuf_entry!(EreportTrace::Flushed { - ena: committed_ena.into() + ena: committed_ena.into(), + flushed, }); } begin_ena.into() From 32244eee2d85c774ae9c379c57d2fffd0529c158 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Jul 2025 09:35:01 -0700 Subject: [PATCH 45/61] don't encode any metadata if we don't have VPD See: https://github.com/oxidecomputer/hubris/pull/2126#discussion_r2193653564 --- task/packrat/src/ereport.rs | 133 ++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 58 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index f99a065d67..7a3f9a3053 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -62,21 +62,29 @@ enum EreportTrace { current: u128, requested: u128, }, + MetadataError(#[count(children)] MetadataError), + MetadataEncoded { + len: u32, + }, + EreportError(#[count(children)] EreportError), Reported { start_ena: u64, reports: u8, limit: u8, }, - InternalError(#[count(children)] Error), } #[derive(Copy, Clone, PartialEq, Eq, counters::Count)] -enum Error { - TaskIdOutOfRange, - VpdMetadataTooLong, +enum MetadataError { + TooLong, PartNumberNotUtf8, SerialNumberNotUtf8, - EreportTooLong, +} + +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] +enum EreportError { + TaskIdOutOfRange, + TooLong, } counted_ringbuf!(EreportTrace, 16, EreportTrace::None); @@ -172,24 +180,35 @@ impl EreportStore { current: current_restart_id.into() }); - // Encode the metadata map into our buffer. - match self.encode_metadata(vpd) { - Ok(encoded) => { - data.write_range( - position..position + encoded.len(), - encoded, - ) - .map_err(|_| ClientError::WentAway.fail())?; - - position += encoded.len(); - } - Err(err) => { - // Encoded VPD metadata was too long, or couldn't be - // represented as a CBOR string. - ringbuf_entry!(EreportTrace::InternalError(err)); + // If we don't have our VPD identity yet, don't send any metadata. + // + // We *could* include the Hubris image ID here even if our VPD + // identity hasn't been set, but sending an empty metadata map + // ensures that MGS will continue asking for metadata on subsequent + // requests. + if let Some(vpd) = vpd { + // Encode the metadata map into our buffer. + match self.encode_metadata(vpd) { + Ok(encoded) => { + data.write_range( + position..position + encoded.len(), + encoded, + ) + .map_err(|_| ClientError::WentAway.fail())?; + + position += encoded.len(); + + ringbuf_entry!(EreportTrace::MetadataEncoded { + len: encoded.len() as u32 + }); + } + Err(err) => { + // Encoded VPD metadata was too long, or couldn't be + // represented as a CBOR string. + ringbuf_entry!(EreportTrace::MetadataError(err)); + } } - }; - + } // Begin at ENA 0 0 }; @@ -226,8 +245,8 @@ impl EreportStore { // an out-of-range task ID somehow. We still want to get the // ereport out, so we'll use a recognizable but illegal task // name to indicate that it's missing. - ringbuf_entry!(EreportTrace::InternalError( - Error::TaskIdOutOfRange + ringbuf_entry!(EreportTrace::EreportError( + EreportError::TaskIdOutOfRange )); "-" // TODO }); @@ -262,8 +281,8 @@ impl EreportStore { // queue that won't fit in our buffer. This can happen // because of the encoding overhead, in theory, but should // be prevented. - ringbuf_entry!(EreportTrace::InternalError( - Error::EreportTooLong + ringbuf_entry!(EreportTrace::EreportError( + EreportError::TooLong )); } } @@ -297,8 +316,8 @@ impl EreportStore { fn encode_metadata( &mut self, - vpd: Option<&VpdIdentity>, - ) -> Result<&[u8], Error> { + vpd: &VpdIdentity, + ) -> Result<&[u8], MetadataError> { let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); let mut encoder = minicbor::Encoder::new(c); // TODO(eliza): presently, this code bails out if the metadata map gets @@ -306,40 +325,38 @@ impl EreportStore { // encoded metadata up to the last complete key-value pair... encoder .str("hubris_archive_id") - .map_err(|_| Error::VpdMetadataTooLong)? + .map_err(|_| MetadataError::TooLong)? .bytes(&self.image_id[..]) - .map_err(|_| Error::VpdMetadataTooLong)?; - if let Some(vpd) = vpd { - match core::str::from_utf8(&vpd.part_number[..]) { - Ok(part_number) => { - encoder - .str("baseboard_part_number") - .map_err(|_| Error::VpdMetadataTooLong)? - .str(part_number) - .map_err(|_| Error::VpdMetadataTooLong)?; - } - Err(_) => ringbuf_entry!(EreportTrace::InternalError( - Error::PartNumberNotUtf8 - )), + .map_err(|_| MetadataError::TooLong)?; + match core::str::from_utf8(&vpd.part_number[..]) { + Ok(part_number) => { + encoder + .str("baseboard_part_number") + .map_err(|_| MetadataError::TooLong)? + .str(part_number) + .map_err(|_| MetadataError::TooLong)?; } - match core::str::from_utf8(&vpd.serial[..]) { - Ok(serial_number) => { - encoder - .str("baseboard_serial_number") - .map_err(|_| Error::VpdMetadataTooLong)? - .str(serial_number) - .map_err(|_| Error::VpdMetadataTooLong)?; - } - Err(_) => ringbuf_entry!(EreportTrace::InternalError( - Error::SerialNumberNotUtf8 - )), + Err(_) => ringbuf_entry!(EreportTrace::MetadataError( + MetadataError::PartNumberNotUtf8 + )), + } + match core::str::from_utf8(&vpd.serial[..]) { + Ok(serial_number) => { + encoder + .str("baseboard_serial_number") + .map_err(|_| MetadataError::TooLong)? + .str(serial_number) + .map_err(|_| MetadataError::TooLong)?; } - encoder - .str("rev") - .map_err(|_| Error::VpdMetadataTooLong)? - .u32(vpd.revision) - .map_err(|_| Error::VpdMetadataTooLong)?; + Err(_) => ringbuf_entry!(EreportTrace::MetadataError( + MetadataError::SerialNumberNotUtf8 + )), } + encoder + .str("rev") + .map_err(|_| MetadataError::TooLong)? + .u32(vpd.revision) + .map_err(|_| MetadataError::TooLong)?; let size = encoder.into_writer().position(); Ok(&self.recv[..size]) } From 1f70fd173647b33b6334ef94a6b9af3dba65187d Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Jul 2025 09:43:14 -0700 Subject: [PATCH 46/61] shrink ringbuf a bit previously, we had a ringbuf entry that contained both the current and requested restart IDs, and was therefore 256B --- meaning the ringbuf was an array of 16 entries that were at least 257B each (including the enum discriminant). that's like 4KiB of ringbuf, which is quite uncomfortable. by moving things around a bit so that we have separate entries for "we got a request with this restart ID" and "restart IDs didn't match", we can make the max sized ringbuf entry 128B (plus an enum discriminant), which basically cuts the size of the ringbuf in half. --- task/packrat/src/ereport.rs | 45 ++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 7a3f9a3053..3230d6b907 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -45,22 +45,24 @@ pub(crate) const RECV_BUF_SIZE: usize = 1024; /// Separate ring buffer for ereport events, as we probably don't care that much /// about the sequence of ereport events relative to other packrat API events. #[derive(Copy, Clone, PartialEq, Eq, counters::Count)] -enum EreportTrace { +enum Trace { #[count(skip)] None, - EreportDelivered { + EreportReceived { src: TaskId, len: u32, #[count(children)] result: snitch_core::InsertResult, }, + ReadRequest { + restart_id: u128, + }, Flushed { - ena: u64, + committed_ena: u64, flushed: usize, }, RestartIdMismatch { - current: u128, - requested: u128, + current_restart_id: u128, }, MetadataError(#[count(children)] MetadataError), MetadataEncoded { @@ -87,7 +89,7 @@ enum EreportError { TooLong, } -counted_ringbuf!(EreportTrace, 16, EreportTrace::None); +counted_ringbuf!(Trace, 16, Trace::None); impl EreportStore { pub(crate) fn new( @@ -126,7 +128,7 @@ impl EreportStore { timestamp, &self.recv[..data.len()], ); - ringbuf_entry!(EreportTrace::EreportDelivered { + ringbuf_entry!(Trace::EreportReceived { src: msg.sender, len: data.len() as u32, result, @@ -144,6 +146,10 @@ impl EreportStore { data: Leased, vpd: Option<&VpdIdentity>, ) -> Result> { + ringbuf_entry!(Trace::ReadRequest { + restart_id: restart_id.into() + }); + /// Byte indicating the end of an indeterminate-length CBOR array or /// map. const CBOR_BREAK: u8 = 0xff; @@ -168,16 +174,15 @@ impl EreportStore { // `committed_ena`, if there is one. if committed_ena != ereport_messages::Ena::NONE { let flushed = self.storage.flush_thru(committed_ena.into()); - ringbuf_entry!(EreportTrace::Flushed { - ena: committed_ena.into(), + ringbuf_entry!(Trace::Flushed { + committed_ena: committed_ena.into(), flushed, }); } begin_ena.into() } else { - ringbuf_entry!(EreportTrace::RestartIdMismatch { - requested: restart_id.into(), - current: current_restart_id.into() + ringbuf_entry!(Trace::RestartIdMismatch { + current_restart_id: current_restart_id.into() }); // If we don't have our VPD identity yet, don't send any metadata. @@ -198,14 +203,14 @@ impl EreportStore { position += encoded.len(); - ringbuf_entry!(EreportTrace::MetadataEncoded { + ringbuf_entry!(Trace::MetadataEncoded { len: encoded.len() as u32 }); } Err(err) => { // Encoded VPD metadata was too long, or couldn't be // represented as a CBOR string. - ringbuf_entry!(EreportTrace::MetadataError(err)); + ringbuf_entry!(Trace::MetadataError(err)); } } } @@ -245,7 +250,7 @@ impl EreportStore { // an out-of-range task ID somehow. We still want to get the // ereport out, so we'll use a recognizable but illegal task // name to indicate that it's missing. - ringbuf_entry!(EreportTrace::EreportError( + ringbuf_entry!(Trace::EreportError( EreportError::TaskIdOutOfRange )); "-" // TODO @@ -281,9 +286,7 @@ impl EreportStore { // queue that won't fit in our buffer. This can happen // because of the encoding overhead, in theory, but should // be prevented. - ringbuf_entry!(EreportTrace::EreportError( - EreportError::TooLong - )); + ringbuf_entry!(Trace::EreportError(EreportError::TooLong)); } } } @@ -294,7 +297,7 @@ impl EreportStore { .map_err(|_| ClientError::WentAway.fail())?; position += 1; - ringbuf_entry!(EreportTrace::Reported { + ringbuf_entry!(Trace::Reported { start_ena, reports, limit @@ -336,7 +339,7 @@ impl EreportStore { .str(part_number) .map_err(|_| MetadataError::TooLong)?; } - Err(_) => ringbuf_entry!(EreportTrace::MetadataError( + Err(_) => ringbuf_entry!(Trace::MetadataError( MetadataError::PartNumberNotUtf8 )), } @@ -348,7 +351,7 @@ impl EreportStore { .str(serial_number) .map_err(|_| MetadataError::TooLong)?; } - Err(_) => ringbuf_entry!(EreportTrace::MetadataError( + Err(_) => ringbuf_entry!(Trace::MetadataError( MetadataError::SerialNumberNotUtf8 )), } From f0ff13a569045abdca94b71771d26eeaac2127ee Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Jul 2025 10:21:37 -0700 Subject: [PATCH 47/61] add lots of commentary --- app/gimletlet/app-ereportlet.toml | 15 +++++++++++++ task/ereportulator/src/main.rs | 25 +++++++++++++++++++-- task/packrat/src/main.rs | 36 ++++++++++++++++++++++++++++++- task/snitch/src/main.rs | 34 +++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/app/gimletlet/app-ereportlet.toml b/app/gimletlet/app-ereportlet.toml index 1ff2a4ba00..d356f49de4 100644 --- a/app/gimletlet/app-ereportlet.toml +++ b/app/gimletlet/app-ereportlet.toml @@ -1,3 +1,18 @@ +# Gimletlet Ereport test application +# +# This image includes the `ereportulator` task, which may be used to generate +# fake error reports to test the ereport aggregation and evacuation subsystem. +# +# Ereports may be generated using `humility hiffy` to call the +# `Ereportulator.fake_ereport` IPC operation. This takes one argument, `n`, +# which is an arbitrary `u32` value included in the ereport data payload. This +# is intended to be used to differentiate between multiple ereports during +# testing. +# +# For example: +# +# $ humility hiffy -t gimletlet hiffy -c Ereportulator.fake_ereport -a n=42 +# name = "gimletlet-ereportlet" inherit = "app.toml" diff --git a/task/ereportulator/src/main.rs b/task/ereportulator/src/main.rs index bbd38c0eaa..8dceac22a6 100644 --- a/task/ereportulator/src/main.rs +++ b/task/ereportulator/src/main.rs @@ -5,7 +5,26 @@ //! //! # BEHOLD THE EREPORTULATOR! //! -//! stupid ereport demo task +//! This is a demo/testing task for the ereport subsystem; it is not intended +//! to be included in production images. The ereportulator is a simple task for +//! generating fake ereports when requested via Hiffy, for testing purposes. +//! +//! Ereports are requested using the `Ereportulator.fake_ereport` IPC operation. +//! This takes one argument, `n`, which is an arbitrary `u32` value to include +//! in the ereport --- intended for differentiating between ereports generated +//! during a test. +//! +//! For example: +//! +//! ```console +//! $ humility -t gimletlet hiffy -c Ereportulator.fake_ereport -a n=420 +//! ``` +//! +//! In addition, when testing on systems which lack real vital product data +//! (VPD) EEPROMs, such as on Gimletlet, this task can be configured to send a +//! made-up VPD identity to packrat upon startup, so that ereports generated in +//! testing can have realistic-looking VPD metadata. This is enabled by the +//! "fake-vpd" feature flag. //! #![no_std] #![no_main] @@ -72,12 +91,14 @@ impl idl::InOrderEreportulatorImpl for ServerImpl { let encoded_len = { let c = minicbor::encode::write::Cursor::new(&mut self.buf[..]); let mut encoder = Encoder::new(c); + + // It's bad on purpose to make you click, Cliff! encoder .begin_map() .unwrap_lite() .str("k") .unwrap_lite() - .str("TEST EREPORT PLS IGNORE") + .str("test.ereport.please.ignore") .unwrap_lite() .str("badness") .unwrap_lite() diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index b19a10e362..0a3e43c39e 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -25,7 +25,41 @@ //! functions. //! 3. packrat never calls into any other task, as calling into a task gives the //! callee opportunity to fault the caller. - +//! +//! ## ereport aggregation +//! +//! When the "ereport" feature flag is enabled, packrat is also responsible for +//! aggregating ereports received from other tasks, as described in [RFD 545]. +//! In addition to enabling packrat's "ereport" feature, the RNG driver task +//! must have its "ereport" feature flag enabled, so that it can generate a +//! restart ID and send it to packrat on startup. Otherwise, ereports will never +//! be reported. +//! +//! Other tasks interact with the ereport aggregation subsystem through three +//! IPC operations: +//! +//! - `deliver_ereport`: called by any task which wishes to record an ereport, +//! with a read-only lease containing the CBOR-encoded ereport data. Packrat +//! will store the ereport in its buffer, provided that space remains for the +//! message. +//! +//! - `read_ereports`: called by the `snitch` task, this IPC reads ereports +//! starting at the requested starting ENA into the provided lease. The +//! `committed_ena` parameter indicates that all ereports with ENAs earlier +//! than the provided one have been written to persistent storage, and +//! packrat may flush them from its buffer, to free memory for new +//! ereports. +//! +//! - `set_ereport_restart_id`: called by the `rng` task to set the +//! 128-bit random restart ID that uniquely identifies this system's +//! boot/restart. No ereports will be reported until this IPC has been +//! called. +//! +//! If the "ereport" feature flag is *not* enabled, packrat's `deliver_ereport` +//! and `read_ereports` IPCs will always fail with +//! `ClientError::UnknownOperation`. +//! +//! [RFD 545]: https://rfd.shared.oxide.computer/rfd/0545 #![no_std] #![no_main] diff --git a/task/snitch/src/main.rs b/task/snitch/src/main.rs index aba8caec9e..97f8eff410 100644 --- a/task/snitch/src/main.rs +++ b/task/snitch/src/main.rs @@ -2,6 +2,37 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//! the snitch: ereport evacuation +//! +//! The snitch is the component of the ereport subsystem responsible for +//! evacuating ereports over the network, as described in [RFD 545 § 4.4]. The +//! snitch task does not store ereports in its memory; this is `packrat`'s +//! responsibility. Instead, the snitch's role is to receive requests for +//! ereports over the management network, read them from packrat, and forward +//! them to the requesting management gateway. +//! +//! This split is necessary because, in order to communicate over the management +//! network, the snitch must run at a relatively low priority: in particular, it +//! must be lower than that of the `net` task, of which it is a client. Since we +//! would like a variety of tasks to be able to *report* errors through the +//! ereport subsystem, the task responsible for aggregating ereports in memory +//! must run at a high priority, so that as many other tasks as possible may act +//! as clients of it. Furthermore, we would like to include the SP's VPD +//! identity in ereport messages, and this is already stored in packrat. +//! Therefore, we separate the responsibility for storing ereports from the +//! responsibility for sending them over the network. +//! +//! Due to this separation of responsibilities, the snitch task is fairly +//! simple. It receives packets sent to the ereport socket, interprets the +//! request message, and forwards the request to packrat. Any ereports sent back +//! by packrat are sent in response to the request. The snitch ends up being a +//! pretty dumb proxy: as the response packet is encoded by packrat; all we end +//! up doing is taking the bytes received from packrat and stuffing them into +//! the socket's send queue. The real purpose of this thing is just to serve as +//! a trampoline between the high priority level of packrat and a priority level +//! lower than that of the net task. +//! +//! [RFD 545 § 4.4]: https://rfd.shared.oxide.computer/rfd/0545#_evacuation #![no_std] #![no_main] @@ -98,6 +129,9 @@ fn main() -> ! { Ok(size) => size, Err(e) => { // Packrat's mad. Reject the request. + // + // Presently, the only time we'd see an error here is if we + // have yet to generate a restart ID. count!(Event::ReadError(e)); continue; } From 8e5949c9928c556294a56169fba1406dfe6b003b Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Jul 2025 11:07:40 -0700 Subject: [PATCH 48/61] ereportulator: user-controllable fake VPD this is intended to help test cases where ereports are generated before VPD is set. --- app/gimletlet/app-ereportlet.toml | 1 - idl/ereportulator.idol | 10 ++++++ task/ereportulator/Cargo.toml | 6 ---- task/ereportulator/src/main.rs | 58 ++++++++++++++----------------- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/app/gimletlet/app-ereportlet.toml b/app/gimletlet/app-ereportlet.toml index d356f49de4..c303920857 100644 --- a/app/gimletlet/app-ereportlet.toml +++ b/app/gimletlet/app-ereportlet.toml @@ -28,5 +28,4 @@ name = "task-ereportulator" priority = 5 start = true task-slots = ["packrat"] -features = ["fake-vpd"] notifications = [] diff --git a/idl/ereportulator.idol b/idl/ereportulator.idol index 75501c458f..30e71f9434 100644 --- a/idl/ereportulator.idol +++ b/idl/ereportulator.idol @@ -10,5 +10,15 @@ Interface( reply: Simple("()"), idempotent: true, ), + "set_fake_vpd": ( + doc: "Send fake vital product data (VPD) to Packrat.", + args: { + }, + reply: Result( + ok: "()", + err: CLike("task_packrat_api::CacheSetError"), + ), + idempotent: true, + ), } ) diff --git a/task/ereportulator/Cargo.toml b/task/ereportulator/Cargo.toml index 460632cb50..1f0371b463 100644 --- a/task/ereportulator/Cargo.toml +++ b/task/ereportulator/Cargo.toml @@ -17,12 +17,6 @@ counters = { path = "../../lib/counters" } [build-dependencies] idol.workspace = true -[features] -# If set, report a fake VPD identity to Packrat before generating ereports, so -# that we populate the metadata fields. This is useful for Gimletlet and -# friends, which don't have "real" VPD. -fake-vpd = [] - [[bin]] name = "task-ereportulator" test = false diff --git a/task/ereportulator/src/main.rs b/task/ereportulator/src/main.rs index 8dceac22a6..be3474861f 100644 --- a/task/ereportulator/src/main.rs +++ b/task/ereportulator/src/main.rs @@ -21,10 +21,15 @@ //! ``` //! //! In addition, when testing on systems which lack real vital product data -//! (VPD) EEPROMs, such as on Gimletlet, this task can be configured to send a -//! made-up VPD identity to packrat upon startup, so that ereports generated in -//! testing can have realistic-looking VPD metadata. This is enabled by the -//! "fake-vpd" feature flag. +//! (VPD) EEPROMs, such as on Gimletlet, this task can be asked to send a +//! made-up VPD identity to packrat. This way, ereports generated in testing +//! can have realistic-looking VPD metadata. Fake VPD is requested using the +//! `Ereportulator.set_fake_vpd` IPC operation: +//! +//! ```console +//! $ humility -t gimletlet hiffy -c Ereportulator.set_fake_vpd +//! ``` +//! //! #![no_std] #![no_main] @@ -43,11 +48,8 @@ task_slot!(PACKRAT, packrat); enum Trace { #[count(skip)] None, - #[cfg(feature = "fake-vpd")] - VpdAlreadySet, - #[cfg(feature = "fake-vpd")] - SetFakeVpd, + SetFakeVpd(#[count(children)] Result<(), task_packrat_api::CacheSetError>), EreportRequested(u32), EreportDelivered { encoded_len: usize, @@ -60,9 +62,6 @@ counted_ringbuf!(Trace, 16, Trace::None); fn main() -> ! { let packrat = Packrat::from(PACKRAT.get_task_id()); - #[cfg(feature = "fake-vpd")] - fake_vpd(&packrat); - let mut server = ServerImpl { buf: [0; 256], packrat, @@ -119,6 +118,23 @@ impl idl::InOrderEreportulatorImpl for ServerImpl { Ok(()) } + + fn set_fake_vpd( + &mut self, + _msg: &RecvMessage, + ) -> Result<(), RequestError> { + let result = self.packrat.set_identity(task_packrat_api::VpdIdentity { + part_number: *b"LOLNO000000", + serial: *b"69426661337", + revision: 42, + }); + + ringbuf_entry!(Trace::SetFakeVpd(result)); + + result?; + + Ok(()) + } } impl idol_runtime::NotificationHandler for ServerImpl { @@ -132,26 +148,6 @@ impl idol_runtime::NotificationHandler for ServerImpl { } } -#[cfg(feature = "fake-vpd")] -fn fake_vpd(packrat: &Packrat) { - // If someone else has already set identity, just don't clobber it. - if packrat.get_identity().is_ok() { - ringbuf_entry!(Trace::VpdAlreadySet); - return; - } - - // Just make up some nonsense. - packrat - .set_identity(task_packrat_api::VpdIdentity { - part_number: *b"LOLNO000000", - serial: *b"69426661337", - revision: 42, - }) - .unwrap_lite(); - - ringbuf_entry!(Trace::SetFakeVpd); -} - mod idl { include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); } From f09f2831764ca054579f6d120dea075f47180a24 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Jul 2025 11:53:19 -0700 Subject: [PATCH 49/61] add ereport stuff to grapefruit --- app/gimletlet/app.toml | 2 +- app/grapefruit/app-dev.toml | 40 ++++++++++++++++++++++++++++++++++ app/grapefruit/base.toml | 1 - task/ereportulator/src/main.rs | 8 +++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 56d70f6836..6a8b6f1693 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -46,7 +46,7 @@ priority = 1 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] -stacksize = 2048 +stacksize = 1040 features = ["ereport"] [caboose] diff --git a/app/grapefruit/app-dev.toml b/app/grapefruit/app-dev.toml index 9d1a066fa1..df6893ed2e 100644 --- a/app/grapefruit/app-dev.toml +++ b/app/grapefruit/app-dev.toml @@ -6,3 +6,43 @@ inherit = "base.toml" features = ["uart8"] uses = ["uart8"] interrupts = {"uart8.irq" = "usart-irq"} + +# Ereport stuff +[tasks.packrat] +stacksize = 1040 +features = ["ereport"] + +[tasks.snitch] +name = "task-snitch" +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + +[tasks.rng_driver] +features = ["h753", "ereport"] +name = "drv-stm32h7-rng" +priority = 6 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys", "packrat"] + +# Demo/test task for ereports +[tasks.ereportulator] +name = "task-ereportulator" +priority = 6 +start = true +task-slots = ["packrat"] +notifications = [] + +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } diff --git a/app/grapefruit/base.toml b/app/grapefruit/base.toml index ed3a3eab14..70629d4b71 100644 --- a/app/grapefruit/base.toml +++ b/app/grapefruit/base.toml @@ -108,7 +108,6 @@ notifications = ["timer"] [tasks.packrat] name = "task-packrat" priority = 3 -max-sizes = {flash = 8192, ram = 2048} start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/task/ereportulator/src/main.rs b/task/ereportulator/src/main.rs index be3474861f..42483faa02 100644 --- a/task/ereportulator/src/main.rs +++ b/task/ereportulator/src/main.rs @@ -18,6 +18,10 @@ //! //! ```console //! $ humility -t gimletlet hiffy -c Ereportulator.fake_ereport -a n=420 +//! humility: WARNING: archive on command-line overriding archive in environment file +//! humility: attached to 0483:3754:000B00154D46501520383832 via ST-Link V3 +//! Ereportulator.fake_ereport() => () +//! //! ``` //! //! In addition, when testing on systems which lack real vital product data @@ -28,6 +32,10 @@ //! //! ```console //! $ humility -t gimletlet hiffy -c Ereportulator.set_fake_vpd +//! humility: WARNING: archive on command-line overriding archive in environment file +//! humility: attached to 0483:3754:000B00154D46501520383832 via ST-Link V3 +//! Ereportulator.set_fake_vpd() => () +//! //! ``` //! //! From 864fa57a7c34a6225deddcffa0c7d54c3063eab6 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Jul 2025 11:58:15 -0700 Subject: [PATCH 50/61] priority wiggling (ensure sntich is right under net) --- app/cosmo/base.toml | 2 ++ app/gimlet/base.toml | 2 ++ app/gimletlet/app.toml | 4 +++- app/psc/base.toml | 4 +++- app/sidecar/base.toml | 2 ++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 62abc4ff93..36e1067d00 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -358,6 +358,8 @@ features = ["net", "vlan"] [tasks.snitch] name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. priority = 6 stacksize = 1200 start = true diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index c163676baa..8bc2f4d5a4 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -349,6 +349,8 @@ features = ["net", "vlan"] [tasks.snitch] name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. priority = 6 stacksize = 1200 start = true diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 6a8b6f1693..09a1d406dd 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -191,7 +191,9 @@ notifications = ["socket"] [tasks.snitch] name = "task-snitch" -priority = 7 +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 4 stacksize = 1200 start = true task-slots = ["net", "packrat"] diff --git a/app/psc/base.toml b/app/psc/base.toml index 699026ca7c..7b5603f698 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -326,7 +326,9 @@ features = ["net", "vlan"] [tasks.snitch] name = "task-snitch" -priority = 6 +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 5 stacksize = 1200 start = true task-slots = ["net", "packrat"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 75655aa214..28a1c11456 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -337,6 +337,8 @@ features = ["net", "vlan"] [tasks.snitch] name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. priority = 6 stacksize = 1200 start = true From 21057baf1c5f730c6a0754045c09019fc04e9e7b Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 10 Jul 2025 11:57:57 -0700 Subject: [PATCH 51/61] reticulating priorities --- app/gimlet/base.toml | 2 +- app/grapefruit/app-dev.toml | 2 +- app/grapefruit/base.toml | 2 +- app/psc/base.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 8bc2f4d5a4..85bd06f3e1 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -110,7 +110,7 @@ notifications = ["i2c2-irq", "i2c3-irq", "i2c4-irq"] [tasks.spd] name = "task-gimlet-spd" features = ["h753"] -priority = 4 +priority = 2 max-sizes = {flash = 16384, ram = 16384} uses = ["i2c1"] start = true diff --git a/app/grapefruit/app-dev.toml b/app/grapefruit/app-dev.toml index df6893ed2e..9109c815e9 100644 --- a/app/grapefruit/app-dev.toml +++ b/app/grapefruit/app-dev.toml @@ -14,7 +14,7 @@ features = ["ereport"] [tasks.snitch] name = "task-snitch" -priority = 6 +priority = 4 stacksize = 1200 start = true task-slots = ["net", "packrat"] diff --git a/app/grapefruit/base.toml b/app/grapefruit/base.toml index 70629d4b71..8a7393bc12 100644 --- a/app/grapefruit/base.toml +++ b/app/grapefruit/base.toml @@ -107,7 +107,7 @@ notifications = ["timer"] [tasks.packrat] name = "task-packrat" -priority = 3 +priority = 1 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/app/psc/base.toml b/app/psc/base.toml index 7b5603f698..9efabada11 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -118,7 +118,7 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" -priority = 3 +priority = 1 stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! From a3386d705e26949b0100eebfaba72d4035d9341f Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 10 Jul 2025 13:57:36 -0700 Subject: [PATCH 52/61] review suggestions from @mkeeter --- task/packrat/src/ereport.rs | 40 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 3230d6b907..2927c29f8a 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -269,7 +269,9 @@ impl EreportStore { Ok(()) => { let size = c.position(); // If there's no room left for this one in the lease, we're - // done here. + // done here. Note that the use of `>=` rather than `>` is + // intentional, as we want to ensure that there's room for + // the final `CBOR_BREAK` byte that ends the CBOR array. if position + size >= data.len() { break; } @@ -327,17 +329,11 @@ impl EreportStore { // longer than our buffer. It would be nice to have a way to keep the // encoded metadata up to the last complete key-value pair... encoder - .str("hubris_archive_id") - .map_err(|_| MetadataError::TooLong)? - .bytes(&self.image_id[..]) - .map_err(|_| MetadataError::TooLong)?; + .str("hubris_archive_id")? + .bytes(&self.image_id[..])?; match core::str::from_utf8(&vpd.part_number[..]) { Ok(part_number) => { - encoder - .str("baseboard_part_number") - .map_err(|_| MetadataError::TooLong)? - .str(part_number) - .map_err(|_| MetadataError::TooLong)?; + encoder.str("baseboard_part_number")?.str(part_number)?; } Err(_) => ringbuf_entry!(Trace::MetadataError( MetadataError::PartNumberNotUtf8 @@ -345,21 +341,13 @@ impl EreportStore { } match core::str::from_utf8(&vpd.serial[..]) { Ok(serial_number) => { - encoder - .str("baseboard_serial_number") - .map_err(|_| MetadataError::TooLong)? - .str(serial_number) - .map_err(|_| MetadataError::TooLong)?; + encoder.str("baseboard_serial_number")?.str(serial_number)?; } Err(_) => ringbuf_entry!(Trace::MetadataError( MetadataError::SerialNumberNotUtf8 )), } - encoder - .str("rev") - .map_err(|_| MetadataError::TooLong)? - .u32(vpd.revision) - .map_err(|_| MetadataError::TooLong)?; + encoder.str("rev")?.u32(vpd.revision)?; let size = encoder.into_writer().position(); Ok(&self.recv[..size]) } @@ -382,7 +370,7 @@ impl minicbor::Encode for ByteGather<'_, '_> { e: &mut minicbor::Encoder, _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - e.bytes_len((self.0.len() + self.1.len()) as u64)?; + e.bytes_len((self.0.len().wrapping_add(self.1.len())) as u64)?; e.writer_mut() .write_all(self.0) .map_err(minicbor::encode::Error::write)?; @@ -395,8 +383,14 @@ impl minicbor::Encode for ByteGather<'_, '_> { impl CborLen for ByteGather<'_, '_> { fn cbor_len(&self, ctx: &mut C) -> usize { - let n = self.0.len() + self.1.len(); - n.cbor_len(ctx) + n + let n = self.0.len().wrapping_add(self.1.len()); + n.cbor_len(ctx).wrapping_add(n) + } +} + +impl From for MetadataError { + fn from(_: minicbor::encode::write::EndOfSlice) -> MetadataError { + MetadataError::TooLong } } From c76f9d4e76c99c4caaad72b85b03624e1dd271b5 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 10 Jul 2025 14:11:33 -0700 Subject: [PATCH 53/61] oh, that was the wrong error type --- task/packrat/src/ereport.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 2927c29f8a..04e830d481 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -388,8 +388,12 @@ impl CborLen for ByteGather<'_, '_> { } } -impl From for MetadataError { - fn from(_: minicbor::encode::write::EndOfSlice) -> MetadataError { +impl From> + for MetadataError +{ + fn from( + _: minicbor::encode::Error, + ) -> MetadataError { MetadataError::TooLong } } From 89519098481e332c56a28b9aab9a53609c7d01dc Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 10 Jul 2025 14:17:01 -0700 Subject: [PATCH 54/61] make things be constants --- task/packrat/src/ereport.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs index 04e830d481..a4ed286b7a 100644 --- a/task/packrat/src/ereport.rs +++ b/task/packrat/src/ereport.rs @@ -150,6 +150,15 @@ impl EreportStore { restart_id: restart_id.into() }); + // XXX(eliza): in theory it might be nicer to use + // `minicbor::data::Token::{BeginArray, BeginMap, Break}` for these, but + // it's way more annoying in practice, because you need to have an + // `Encoder` and can't just put it in the buffer. + /// Byte indicating the beginning of an indeterminate-length CBOR + /// array. + const CBOR_BEGIN_ARRAY: u8 = 0x9f; + /// Byte indicating the beginning of an indeterminate-length CBOR map. + const CBOR_BEGIN_MAP: u8 = 0xbf; /// Byte indicating the end of an indeterminate-length CBOR array or /// map. const CBOR_BREAK: u8 = 0xff; @@ -162,8 +171,11 @@ impl EreportStore { let mut position = first_data_byte; let mut first_written_ena = None; - // Begin metadata map. - data.write_at(position, 0xbf) + // Start the metadata map. + // + // MGS expects us to always include this, and to just have it be + // empty if we didn't send any metadata. + data.write_at(position, CBOR_BEGIN_MAP) .map_err(|_| ClientError::WentAway.fail())?; position += 1; @@ -232,11 +244,8 @@ impl EreportStore { if first_written_ena.is_none() { first_written_ena = Some(r.ena); - // Start CBOR list - // XXX(eliza): in theory it might be nicer to use - // `minicbor::data::Token::BeginArray` here, but it's way more - // annoying in practice... - data.write_at(position, 0x9f) + // Start the ereport array + data.write_at(position, CBOR_BEGIN_ARRAY) .map_err(|_| ClientError::WentAway.fail())?; position += 1; } @@ -294,7 +303,7 @@ impl EreportStore { } if let Some(start_ena) = first_written_ena { - // End CBOR list, if we wrote anything. + // End CBOR array, if we wrote anything. data.write_at(position, CBOR_BREAK) .map_err(|_| ClientError::WentAway.fail())?; position += 1; From 17ee24ef429270d28422415fd36c4b4f74ccedc8 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 11 Jul 2025 13:45:41 -0700 Subject: [PATCH 55/61] properly account for number of flushed ereports --- lib/snitch-core/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index c092e2091f..5480a6011d 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -220,6 +220,7 @@ impl Store { return 0; } + let mut discarded = 0; for _ in 0..=index { // Discard the lead record in the queue. let mut slices = self.storage.as_slices(); @@ -235,6 +236,7 @@ impl Store { self.stored_record_count -= 1; self.earliest_ena += 1; + discarded += 1; } // You might be curious why we don't do our loss recovery process here, @@ -243,7 +245,7 @@ impl Store { // full. If we are able to recover here, but do _not_ have enough bytes // free for the next incoming record, then we'd just kick back into loss // state.... necessitating another loss record. And so forth. - index as usize + discarded } /// Makes a best effort at inserting a record. From 47194c1fe2b62e4198f29276ac7e1f9acc241bcf Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 10 Jul 2025 11:09:32 -0700 Subject: [PATCH 56/61] strawman MAX5970 ereport sketch --- Cargo.lock | 15 ++++++- Cargo.toml | 1 + app/gimlet/base.toml | 2 +- task/power/Cargo.toml | 3 ++ task/power/src/bsp/gimlet_bcdef.rs | 69 +++++++++++++++++++++++++++--- task/power/src/main.rs | 16 ++++++- 6 files changed, 97 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f117e16a7..30e0e405d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3867,6 +3867,16 @@ dependencies = [ "serde", ] +[[package]] +name = "minicbor-serde" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e45e8beeefea1b8b6f52fa188a5b6ea3746c2885606af8d4d8bf31cee633fb" +dependencies = [ + "minicbor 0.26.4", + "serde", +] + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -4924,7 +4934,7 @@ version = "0.1.0" dependencies = [ "counters", "heapless", - "minicbor-serde", + "minicbor-serde 0.3.2", "serde", "unwrap-lite", ] @@ -5781,6 +5791,8 @@ dependencies = [ "hubpack", "idol", "idol-runtime", + "minicbor 0.26.4", + "minicbor-serde 0.4.1", "mutable-statics", "num-traits", "paste", @@ -5788,6 +5800,7 @@ dependencies = [ "ringbuf", "serde", "static_assertions", + "task-packrat-api", "task-power-api", "task-sensor-api", "userlib", diff --git a/Cargo.toml b/Cargo.toml index 2123a8968f..ec0360fda5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ lpc55-pac = { version = "0.4", default-features = false } memchr = { version = "2.4", default-features = false } memoffset = { version = "0.6.5", default-features = false } minicbor = { version = "0.26.4", default-features = false } +minicbor-serde = { version = "0.4.0", default-features = false } multimap = { version = "0.8.3", default-features = false } nb = { version = "1", default-features = false } num = { version = "0.4", default-features = false } diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 0c673706d5..76609ffcb2 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -146,7 +146,7 @@ priority = 6 max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor", "gimlet_seq"] +task-slots = ["i2c_driver", "sensor", "gimlet_seq", "packrat"] notifications = ["timer"] [tasks.hiffy] diff --git a/task/power/Cargo.toml b/task/power/Cargo.toml index 9c7425c04c..cffd1f122e 100644 --- a/task/power/Cargo.toml +++ b/task/power/Cargo.toml @@ -9,6 +9,8 @@ cortex-m.workspace = true hubpack.workspace = true idol-runtime.workspace = true num-traits.workspace = true +minicbor.workspace = true +minicbor-serde.workspace = true paste.workspace = true pmbus.workspace = true serde.workspace = true @@ -23,6 +25,7 @@ drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-stm32xx-sys-api = { path = "../../drv/stm32xx-sys-api", features = ["family-stm32h7"], optional = true } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } +task-packrat-api = { path = "../packrat-api" } task-power-api = { path = "../power-api" } task-sensor-api = { path = "../sensor-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index de81dca8ed..1a38726e2d 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -9,6 +9,8 @@ use crate::{ use drv_i2c_devices::max5970::*; use ringbuf::*; +use serde::Serialize; +use task_packrat_api::Packrat; use userlib::units::*; pub(crate) const CONTROLLER_CONFIG_LEN: usize = 37; @@ -145,8 +147,10 @@ ringbuf!(Trace, TRACE_DEPTH, Trace::None); /// If any I2C operation fails, this will abort its work and return. fn trace_max5970( dev: &Max5970, + rail: &'static str, sensor: SensorId, peaks: &mut Max5970Peaks, + packrat: &mut Packrat, now: u32, ) { let max_vout = match dev.max_vout() { @@ -170,9 +174,41 @@ fn trace_max5970( }; // TODO: this update should probably happen after all I/O is done. - if peaks.iout.bounced(min_iout, max_iout) - || peaks.vout.bounced(min_vout, max_vout) - { + let mut ereport_buf = [0u8; 128]; + if peaks.iout.bounced(min_iout, max_iout) { + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + let report = IoutCrossbounceEreport { + k: "power.crossbounce.iout", + rail, + sensor_id: sensor.into(), + min_iout, + max_iout, + time: now, + }; + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); + } + peaks.last_bounce_detected = Some(now); + } + if peaks.vout.bounced(min_vout, max_vout) { + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + let report = VoutCrossbounceEreport { + k: "power.crossbounce.vout", + rail, + sensor_id: sensor.into(), + min_vout, + max_vout, + time: now, + }; + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); + } peaks.last_bounce_detected = Some(now); } @@ -222,6 +258,26 @@ struct Max5970Peak { crossbounce_max: f32, } +#[derive(serde::Serialize)] +struct IoutCrossbounceEreport { + k: &'static str, + rail: &'static str, + min_iout: f32, + max_iout: f32, + time: u32, + sensor_id: u32, +} + +#[derive(serde::Serialize)] +struct VoutCrossbounceEreport { + k: &'static str, + rail: &'static str, + min_vout: f32, + max_vout: f32, + time: u32, + sensor_id: u32, +} + impl Default for Max5970Peak { fn default() -> Self { Self { @@ -288,6 +344,7 @@ impl State { &mut self, devices: &[Device], state: PowerState, + packrat: &mut Packrat, ) { // // Trace the detailed state every ten seconds, provided that we are in A0. @@ -295,19 +352,19 @@ impl State { if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { ringbuf_entry!(Trace::Now(self.fired)); - for ((dev, sensor), peak) in CONTROLLER_CONFIG + for ((dev, rail, sensor), peak) in CONTROLLER_CONFIG .iter() .zip(devices.iter()) .filter_map(|(c, dev)| { if let Device::Max5970(dev) = dev { - Some((dev, c.current)) + Some((dev, c.rail, c.current)) } else { None } }) .zip(self.peaks.iter_mut()) { - trace_max5970(dev, sensor, peak, self.fired); + trace_max5970(dev, rail, sensor, peak, packrat, self.fired); } } diff --git a/task/power/src/main.rs b/task/power/src/main.rs index 7a3dbce033..e768117834 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -23,6 +23,7 @@ use drv_i2c_devices::raa229620a::*; use drv_i2c_devices::tps546b24a::*; use pmbus::Phase; use ringbuf::*; +use task_packrat_api::Packrat; use task_power_api::{ Bmr491Event, PmbusValue, RawPmbusBlock, RenesasBlackbox, MAX_BLOCK_LEN, }; @@ -57,6 +58,7 @@ enum PowerState { const TIMER_INTERVAL: u32 = 1000; task_slot!(I2C, i2c_driver); +task_slot!(PACKRAT, packrat); task_slot!(SENSOR, sensor); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); @@ -104,6 +106,7 @@ struct PowerControllerConfig { input_current: Option, temperature: Option, phases: Option<&'static [u8]>, + rail: &'static str, } /// Bound device, which exposes sensor functions @@ -320,6 +323,7 @@ macro_rules! rail_controller { sensors::[<$dev:upper _ $rail:upper _TEMPERATURE_SENSOR>] ), phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], + rail: stringify!($rail:upper), } } }; @@ -340,6 +344,7 @@ macro_rules! rail_controller_notemp { input_current: None, temperature: None, phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], + rail: stringify!($rail:upper), } } }; @@ -362,6 +367,7 @@ macro_rules! adm1272_controller { sensors::[] ), phases: None, + rail: stringify!($rail:upper), } } }; @@ -382,6 +388,7 @@ macro_rules! ltc4282_controller { input_current: None, temperature: None, phases: None, + rail: stringify!($rail:upper), } } }; @@ -404,6 +411,7 @@ macro_rules! lm5066_controller { sensors::[] ), phases: None, + rail: stringify!($rail:upper), } } }; @@ -426,6 +434,7 @@ macro_rules! lm5066i_controller { sensors::[] ), phases: None, + rail: stringify!($rail:upper), } } }; @@ -449,6 +458,7 @@ macro_rules! max5970_controller { input_current: None, temperature: None, phases: None, + rail: stringify!($rail:upper), } } }; @@ -474,6 +484,7 @@ macro_rules! mwocp68_controller { temperature: None, // Temperature sensors are independent of // power rails and measured separately phases: None, + rail: stringify!($rail:upper), } } }; @@ -520,6 +531,7 @@ fn main() -> ! { sensor: sensor_api::Sensor::from(SENSOR.get_task_id()), devices: claim_devices(i2c_task), bsp: bsp::State::init(), + packrat: Packrat::from(PACKRAT.get_task_id()), }; let mut buffer = [0; idl::INCOMING_SIZE]; @@ -534,6 +546,7 @@ struct ServerImpl { sensor: sensor_api::Sensor, devices: &'static mut [Device; bsp::CONTROLLER_CONFIG_LEN], bsp: bsp::State, + packrat: Packrat, } impl ServerImpl { @@ -608,7 +621,8 @@ impl ServerImpl { } } - self.bsp.handle_timer_fired(self.devices, state); + self.bsp + .handle_timer_fired(self.devices, state, &mut self.packrat); } /// Find the BMR491 and return an `I2cDevice` handle From d6d12aa1346f7269b5a5554c6864664f72127577 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 18 Jul 2025 14:03:52 -0700 Subject: [PATCH 57/61] wip --- task/power/src/bsp/gimlet_bcdef.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index 1a38726e2d..67bb91c52d 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -180,7 +180,7 @@ fn trace_max5970( minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), ); let report = IoutCrossbounceEreport { - k: "power.crossbounce.iout", + k: "ereport.power.crossbounce.iout", rail, sensor_id: sensor.into(), min_iout, @@ -198,7 +198,7 @@ fn trace_max5970( minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), ); let report = VoutCrossbounceEreport { - k: "power.crossbounce.vout", + k: "ereport.power.crossbounce.vout", rail, sensor_id: sensor.into(), min_vout, From 906e139966ad97e9103e99b4df1cc45a580aeb68 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 4 Aug 2025 09:40:49 -0700 Subject: [PATCH 58/61] okay actually just make one ereport --- task/power/src/bsp/gimlet_bcdef.rs | 108 +++++++++++++++-------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index 67bb91c52d..a303f8e2aa 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -174,41 +174,27 @@ fn trace_max5970( }; // TODO: this update should probably happen after all I/O is done. - let mut ereport_buf = [0u8; 128]; + let mut ereport = None; if peaks.iout.bounced(min_iout, max_iout) { - let mut s = minicbor_serde::Serializer::new( - minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), - ); - let report = IoutCrossbounceEreport { - k: "ereport.power.crossbounce.iout", - rail, - sensor_id: sensor.into(), - min_iout, - max_iout, - time: now, - }; - if report.serialize(&mut s).is_ok() { - let len = s.into_encoder().into_writer().position(); - packrat.deliver_ereport(&ereport_buf[..len]); - } + ereport + .get_or_insert_with(|| { + CrossbounceEreport::new(rail, sensor.into(), now) + }) + .iout = Some(EreportPeaks { + min: min_iout, + max: max_iout, + }); peaks.last_bounce_detected = Some(now); } if peaks.vout.bounced(min_vout, max_vout) { - let mut s = minicbor_serde::Serializer::new( - minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), - ); - let report = VoutCrossbounceEreport { - k: "ereport.power.crossbounce.vout", - rail, - sensor_id: sensor.into(), - min_vout, - max_vout, - time: now, - }; - if report.serialize(&mut s).is_ok() { - let len = s.into_encoder().into_writer().position(); - packrat.deliver_ereport(&ereport_buf[..len]); - } + ereport + .get_or_insert_with(|| { + CrossbounceEreport::new(rail, sensor.into(), now) + }) + .vout = Some(EreportPeaks { + min: min_vout, + max: max_vout, + }); peaks.last_bounce_detected = Some(now); } @@ -248,6 +234,17 @@ fn trace_max5970( crossbounce_min_vout: peaks.vout.crossbounce_min, crossbounce_max_vout: peaks.vout.crossbounce_max, }); + + if let Some(report) = ereport { + let mut ereport_buf = [0u8; 128]; + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); + } + } } #[derive(Copy, Clone)] @@ -258,26 +255,6 @@ struct Max5970Peak { crossbounce_max: f32, } -#[derive(serde::Serialize)] -struct IoutCrossbounceEreport { - k: &'static str, - rail: &'static str, - min_iout: f32, - max_iout: f32, - time: u32, - sensor_id: u32, -} - -#[derive(serde::Serialize)] -struct VoutCrossbounceEreport { - k: &'static str, - rail: &'static str, - min_vout: f32, - max_vout: f32, - time: u32, - sensor_id: u32, -} - impl Default for Max5970Peak { fn default() -> Self { Self { @@ -373,3 +350,32 @@ impl State { } pub const HAS_RENDMP_BLACKBOX: bool = true; + +#[derive(serde::Serialize)] +struct CrossbounceEreport { + k: &'static str, + rail: &'static str, + iout: Option, + vout: Option, + time: u32, + sensor_id: u32, +} + +#[derive(serde::Serialize)] +struct EreportPeaks { + min: f32, + max: f32, +} + +impl CrossbounceEreport { + pub fn new(rail: &'static str, time: u32, sensor_id: u32) -> Self { + Self { + k: "pwr.xbounce", + rail, + iout: None, + vout: None, + time, + sensor_id, + } + } +} From 0baa28e301c4510ae026b5d3ef7fa0514c8c6b36 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 4 Aug 2025 10:04:55 -0700 Subject: [PATCH 59/61] make other boards build --- app/cosmo/base.toml | 2 +- app/gimletlet/app.toml | 2 +- app/minibar/app.toml | 2 +- app/psc/base.toml | 2 +- app/sidecar/base.toml | 2 +- task/power/src/bsp/cosmo_a.rs | 48 ++++++++++++++++++++++++++---- task/power/src/bsp/gimlet_bcdef.rs | 41 ++++--------------------- task/power/src/bsp/gimletlet_2.rs | 1 + task/power/src/bsp/minibar.rs | 1 + task/power/src/bsp/psc_bc.rs | 1 + task/power/src/bsp/sidecar_bcd.rs | 1 + task/power/src/ereports.rs | 43 ++++++++++++++++++++++++++ task/power/src/main.rs | 4 +++ 13 files changed, 104 insertions(+), 46 deletions(-) create mode 100644 task/power/src/ereports.rs diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index c2052fa721..24f85fbf43 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -154,7 +154,7 @@ priority = 8 max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor", "cosmo_seq"] +task-slots = ["i2c_driver", "sensor", "cosmo_seq", "packrat"] notifications = ["timer"] [tasks.hiffy] diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 47fd30fed8..628a5ad46d 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -300,5 +300,5 @@ priority = 6 max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor"] +task-slots = ["i2c_driver", "sensor", "packrat"] notifications = ["timer"] diff --git a/app/minibar/app.toml b/app/minibar/app.toml index 57b46fdc47..e653e0f9b9 100644 --- a/app/minibar/app.toml +++ b/app/minibar/app.toml @@ -124,7 +124,7 @@ priority = 6 max-sizes = {flash = 32768, ram = 8192 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor"] +task-slots = ["i2c_driver", "sensor", "packrat"] notifications = ["timer"] [tasks.sensor] diff --git a/app/psc/base.toml b/app/psc/base.toml index 9c343bdf41..95ad0ddae7 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -286,7 +286,7 @@ priority = 4 max-sizes = {flash = 32768, ram = 4096} stacksize = 2504 start = true -task-slots = ["i2c_driver", "sensor", "sys"] +task-slots = ["i2c_driver", "sensor", "sys", "packrat"] features = ["psc"] notifications = ["timer"] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 02561d8d88..b3e6bf6daf 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -294,7 +294,7 @@ priority = 6 max-sizes = {flash = 32768, ram = 8192 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor", "sequencer"] +task-slots = ["i2c_driver", "sensor", "sequencer", "packrat"] notifications = ["timer"] [tasks.validate] diff --git a/task/power/src/bsp/cosmo_a.rs b/task/power/src/bsp/cosmo_a.rs index edec0d859f..3cd17e2db1 100644 --- a/task/power/src/bsp/cosmo_a.rs +++ b/task/power/src/bsp/cosmo_a.rs @@ -10,8 +10,12 @@ use crate::{ use drv_i2c_devices::{lm5066i::*, max5970::*}; use ringbuf::*; +use serde::Serialize; +use task_packrat_api::Packrat; use userlib::units::*; +use crate::ereports; + pub(crate) const CONTROLLER_CONFIG_LEN: usize = 43; const MAX5970_CONFIG_LEN: usize = 22; @@ -188,8 +192,10 @@ ringbuf!(Trace, TRACE_DEPTH, Trace::None); /// If any I2C operation fails, this will abort its work and return. fn trace_max5970( dev: &Max5970, + rail: &'static str, sensor: SensorId, peaks: &mut Max5970Peaks, + packrat: &mut Packrat, now: u32, ) { let max_vout = match dev.max_vout() { @@ -213,9 +219,27 @@ fn trace_max5970( }; // TODO: this update should probably happen after all I/O is done. - if peaks.iout.bounced(min_iout, max_iout) - || peaks.vout.bounced(min_vout, max_vout) - { + let mut ereport = None; + if peaks.iout.bounced(min_iout, max_iout) { + ereport + .get_or_insert_with(|| { + ereports::Crossbounce::new(rail, now, sensor) + }) + .iout = Some(ereports::Peaks { + min: min_iout, + max: max_iout, + }); + peaks.last_bounce_detected = Some(now); + } + if peaks.vout.bounced(min_vout, max_vout) { + ereport + .get_or_insert_with(|| { + ereports::Crossbounce::new(rail, now, sensor) + }) + .vout = Some(ereports::Peaks { + min: min_vout, + max: max_vout, + }); peaks.last_bounce_detected = Some(now); } @@ -255,6 +279,17 @@ fn trace_max5970( crossbounce_min_vout: peaks.vout.crossbounce_min, crossbounce_max_vout: peaks.vout.crossbounce_max, }); + + if let Some(report) = ereport { + let mut ereport_buf = [0u8; 128]; + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); + } + } } #[derive(Copy, Clone)] @@ -392,6 +427,7 @@ impl State { &mut self, devices: &[Device], state: PowerState, + packrat: &mut Packrat, ) { // // Trace the detailed state every ten seconds, provided that we are in A0. @@ -399,19 +435,19 @@ impl State { if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { ringbuf_entry!(Trace::Now(self.fired)); - for ((dev, sensor), peak) in CONTROLLER_CONFIG + for ((dev, rail, sensor), peak) in CONTROLLER_CONFIG .iter() .zip(devices.iter()) .filter_map(|(c, dev)| { if let Device::Max5970(dev) = dev { - Some((dev, c.current)) + Some((dev, c.rail, c.current)) } else { None } }) .zip(self.peaks.iter_mut()) { - trace_max5970(dev, sensor, peak, self.fired); + trace_max5970(dev, rail, sensor, peak, packrat, self.fired); } } diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index a303f8e2aa..0a9a49745f 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -3,8 +3,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::{ - i2c_config, i2c_config::sensors, Device, PowerControllerConfig, PowerState, - SensorId, + ereports, i2c_config, i2c_config::sensors, Device, PowerControllerConfig, + PowerState, SensorId, }; use drv_i2c_devices::max5970::*; @@ -178,9 +178,9 @@ fn trace_max5970( if peaks.iout.bounced(min_iout, max_iout) { ereport .get_or_insert_with(|| { - CrossbounceEreport::new(rail, sensor.into(), now) + ereports::Crossbounce::new(rail, now, sensor) }) - .iout = Some(EreportPeaks { + .iout = Some(ereports::Peaks { min: min_iout, max: max_iout, }); @@ -189,9 +189,9 @@ fn trace_max5970( if peaks.vout.bounced(min_vout, max_vout) { ereport .get_or_insert_with(|| { - CrossbounceEreport::new(rail, sensor.into(), now) + ereports::Crossbounce::new(rail, now, sensor) }) - .vout = Some(EreportPeaks { + .vout = Some(ereports::Peaks { min: min_vout, max: max_vout, }); @@ -350,32 +350,3 @@ impl State { } pub const HAS_RENDMP_BLACKBOX: bool = true; - -#[derive(serde::Serialize)] -struct CrossbounceEreport { - k: &'static str, - rail: &'static str, - iout: Option, - vout: Option, - time: u32, - sensor_id: u32, -} - -#[derive(serde::Serialize)] -struct EreportPeaks { - min: f32, - max: f32, -} - -impl CrossbounceEreport { - pub fn new(rail: &'static str, time: u32, sensor_id: u32) -> Self { - Self { - k: "pwr.xbounce", - rail, - iout: None, - vout: None, - time, - sensor_id, - } - } -} diff --git a/task/power/src/bsp/gimletlet_2.rs b/task/power/src/bsp/gimletlet_2.rs index d0a762faee..d00d8fb859 100644 --- a/task/power/src/bsp/gimletlet_2.rs +++ b/task/power/src/bsp/gimletlet_2.rs @@ -32,6 +32,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/bsp/minibar.rs b/task/power/src/bsp/minibar.rs index 7c7bb3af13..81ee1b728d 100644 --- a/task/power/src/bsp/minibar.rs +++ b/task/power/src/bsp/minibar.rs @@ -32,6 +32,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/bsp/psc_bc.rs b/task/power/src/bsp/psc_bc.rs index b07edfdf63..c20371c04b 100644 --- a/task/power/src/bsp/psc_bc.rs +++ b/task/power/src/bsp/psc_bc.rs @@ -55,6 +55,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/bsp/sidecar_bcd.rs b/task/power/src/bsp/sidecar_bcd.rs index 0db2b52eb5..acdd15d554 100644 --- a/task/power/src/bsp/sidecar_bcd.rs +++ b/task/power/src/bsp/sidecar_bcd.rs @@ -55,6 +55,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/ereports.rs b/task/power/src/ereports.rs new file mode 100644 index 0000000000..b9c753817b --- /dev/null +++ b/task/power/src/ereports.rs @@ -0,0 +1,43 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Ereport type definitions used by multiple BSPs. + +// Which ereport types are used depends on the board. +#![allow(dead_code)] + +use crate::sensor_api::SensorId; + +#[derive(serde::Serialize)] +pub(crate) struct Crossbounce { + k: &'static str, + pub(crate) rail: &'static str, + pub(crate) iout: Option, + pub(crate) vout: Option, + pub(crate) time: u32, + pub(crate) sensor_id: u32, +} + +#[derive(serde::Serialize)] +pub(crate) struct Peaks { + pub(crate) min: f32, + pub(crate) max: f32, +} + +impl Crossbounce { + pub(crate) fn new( + rail: &'static str, + time: u32, + sensor_id: SensorId, + ) -> Self { + Self { + k: "pwr.xbounce", + rail, + iout: None, + vout: None, + time, + sensor_id: sensor_id.into(), + } + } +} diff --git a/task/power/src/main.rs b/task/power/src/main.rs index e768117834..0119818d3f 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -38,6 +38,8 @@ use drv_i2c_devices::{ VoltageSensor, }; +mod ereports; + #[derive(Copy, Clone, PartialEq)] enum Trace { GotVersion(u32), @@ -106,6 +108,8 @@ struct PowerControllerConfig { input_current: Option, temperature: Option, phases: Option<&'static [u8]>, + // May or may not be used, depending on BSP. + #[allow(dead_code)] rail: &'static str, } From 48c7f2e073323ca6741c405ad2a3e48742695736 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 4 Aug 2025 12:13:26 -0700 Subject: [PATCH 60/61] detect sags, not bounces (whoops) --- task/power/src/bsp/cosmo_a.rs | 176 +++++++++++++++++++++-------- task/power/src/bsp/gimlet_bcdef.rs | 173 ++++++++++++++++++++-------- task/power/src/ereports.rs | 28 ++--- task/power/src/main.rs | 16 ++- 4 files changed, 287 insertions(+), 106 deletions(-) diff --git a/task/power/src/bsp/cosmo_a.rs b/task/power/src/bsp/cosmo_a.rs index 3cd17e2db1..2e847589d3 100644 --- a/task/power/src/bsp/cosmo_a.rs +++ b/task/power/src/bsp/cosmo_a.rs @@ -10,7 +10,6 @@ use crate::{ use drv_i2c_devices::{lm5066i::*, max5970::*}; use ringbuf::*; -use serde::Serialize; use task_packrat_api::Packrat; use userlib::units::*; @@ -19,6 +18,10 @@ use crate::ereports; pub(crate) const CONTROLLER_CONFIG_LEN: usize = 43; const MAX5970_CONFIG_LEN: usize = 22; +/// Minimum threshold for MAX970 12V output error reporting. If the output +/// voltage drops below this threshold, we generate an ereport. +const V12OUT_EREPORT_MIN: Option = Some(11.0); + pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; CONTROLLER_CONFIG_LEN] = [ rail_controller!(IBC, bmr491, v12_sys_a2, A2), @@ -57,25 +60,95 @@ pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; ), max5970_controller!(HotSwapIO, v3p3_m2a_a0hp, A0, Ohms(0.003)), max5970_controller!(HotSwapIO, v3p3_m2b_a0hp, A0, Ohms(0.003)), - max5970_controller!(HotSwapIO, v12_u2a_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2a_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2a_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2b_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2b_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2b_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2c_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2c_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2c_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2d_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2d_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2d_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2e_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2e_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2e_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2f_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2f_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2f_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2g_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2g_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2g_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2h_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2h_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2h_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2i_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2i_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2i_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2j_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2j_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2j_a0, A0, Ohms(0.005)), ltc4282_controller!(HotSwapIO, v12_mcio_a0hp, A0, Ohms(0.001)), ltc4282_controller!(HotSwapIO, v12_ddr5_abcdef_a0, A0, Ohms(0.001)), @@ -166,6 +239,7 @@ enum Trace { Max5970 { sensor: SensorId, last_bounce_detected: Option, + vout_sag_began_at: Option, status0: u8, status1: u8, status3: u8, @@ -192,8 +266,7 @@ ringbuf!(Trace, TRACE_DEPTH, Trace::None); /// If any I2C operation fails, this will abort its work and return. fn trace_max5970( dev: &Max5970, - rail: &'static str, - sensor: SensorId, + ctrl: &PowerControllerConfig, peaks: &mut Max5970Peaks, packrat: &mut Packrat, now: u32, @@ -218,34 +291,41 @@ fn trace_max5970( _ => return, }; + let sensor = ctrl.current; + // TODO: this update should probably happen after all I/O is done. let mut ereport = None; - if peaks.iout.bounced(min_iout, max_iout) { - ereport - .get_or_insert_with(|| { - ereports::Crossbounce::new(rail, now, sensor) - }) - .iout = Some(ereports::Peaks { - min: min_iout, - max: max_iout, - }); - peaks.last_bounce_detected = Some(now); - } - if peaks.vout.bounced(min_vout, max_vout) { - ereport - .get_or_insert_with(|| { - ereports::Crossbounce::new(rail, now, sensor) - }) - .vout = Some(ereports::Peaks { - min: min_vout, - max: max_vout, - }); + if peaks.iout.bounced(min_iout, max_iout) + || peaks.vout.bounced(min_vout, max_vout) + { peaks.last_bounce_detected = Some(now); + } else if let Some(threshold) = ctrl.vout_threshold { + // If we are tracking a minimum output voltage threshold on this sensor, + // check if we're below it, and generate an ereport. Once a sag is + // detected, this "latches" until the minimum output voltage rises above + // the threshold again. + // + // We only do this if a bounce is not detected,as we don't want to + // report sags that occur due to the system's power state changing + // intentionally. + if peaks.vout_sag_began_at.is_none() && min_vout <= threshold { + peaks.vout_sag_began_at = Some(now); + ereport = Some(ereports::VoutSag::new( + ctrl.rail, + now, + sensor.into(), + min_vout, + threshold, + )); + } else if peaks.vout_sag_began_at.is_some() && min_vout > threshold { + peaks.vout_sag_began_at = None; + } } ringbuf_entry!(Trace::Max5970 { sensor, last_bounce_detected: peaks.last_bounce_detected, + vout_sag_began_at: peaks.vout_sag_began_at, status0: match dev.read_reg(Register::status0) { Ok(reg) => reg, _ => return, @@ -281,14 +361,21 @@ fn trace_max5970( }); if let Some(report) = ereport { - let mut ereport_buf = [0u8; 128]; - let mut s = minicbor_serde::Serializer::new( - minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), - ); - if report.serialize(&mut s).is_ok() { - let len = s.into_encoder().into_writer().position(); - packrat.deliver_ereport(&ereport_buf[..len]); - } + deliver_ereport(packrat, &report); + } +} + +// This is in its own function so that we only push a stack frame large enough +// for the ereport buffer if needed. +#[inline(never)] +fn deliver_ereport(packrat: &mut Packrat, report: &impl serde::Serialize) { + let mut ereport_buf = [0u8; 128]; + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); } } @@ -347,6 +434,7 @@ struct Max5970Peaks { iout: Max5970Peak, vout: Max5970Peak, last_bounce_detected: Option, + vout_sag_began_at: Option, } pub(crate) struct State { @@ -435,19 +523,19 @@ impl State { if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { ringbuf_entry!(Trace::Now(self.fired)); - for ((dev, rail, sensor), peak) in CONTROLLER_CONFIG + for ((dev, ctrl), peak) in CONTROLLER_CONFIG .iter() .zip(devices.iter()) .filter_map(|(c, dev)| { if let Device::Max5970(dev) = dev { - Some((dev, c.rail, c.current)) + Some((dev, c)) } else { None } }) .zip(self.peaks.iter_mut()) { - trace_max5970(dev, rail, sensor, peak, packrat, self.fired); + trace_max5970(dev, ctrl, peak, packrat, self.fired); } } diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index 0a9a49745f..ab709f14c6 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -9,12 +9,12 @@ use crate::{ use drv_i2c_devices::max5970::*; use ringbuf::*; -use serde::Serialize; use task_packrat_api::Packrat; use userlib::units::*; pub(crate) const CONTROLLER_CONFIG_LEN: usize = 37; const MAX5970_CONFIG_LEN: usize = 22; +const V12OUT_EREPORT_MIN: Option = Some(11.0); pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; CONTROLLER_CONFIG_LEN] = [ @@ -35,25 +35,95 @@ pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; adm1272_controller!(Fan, v54_fan, A2, Ohms(0.002)), max5970_controller!(HotSwapIO, v3p3_m2a_a0hp, A0, Ohms(0.004)), max5970_controller!(HotSwapIO, v3p3_m2b_a0hp, A0, Ohms(0.004)), - max5970_controller!(HotSwapIO, v12_u2a_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2a_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2a_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2b_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2b_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2b_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2c_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2c_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2c_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2d_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2d_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2d_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2e_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2e_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2e_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2f_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2f_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2f_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2g_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2g_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2g_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2h_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2h_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2h_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2i_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2i_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2i_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2j_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2j_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2j_a0, A0, Ohms(0.008)), ]; @@ -121,6 +191,7 @@ enum Trace { Max5970 { sensor: SensorId, last_bounce_detected: Option, + vout_sag_began_at: Option, status0: u8, status1: u8, status3: u8, @@ -147,8 +218,7 @@ ringbuf!(Trace, TRACE_DEPTH, Trace::None); /// If any I2C operation fails, this will abort its work and return. fn trace_max5970( dev: &Max5970, - rail: &'static str, - sensor: SensorId, + ctrl: &PowerControllerConfig, peaks: &mut Max5970Peaks, packrat: &mut Packrat, now: u32, @@ -173,34 +243,41 @@ fn trace_max5970( _ => return, }; + let sensor = ctrl.current; + // TODO: this update should probably happen after all I/O is done. let mut ereport = None; - if peaks.iout.bounced(min_iout, max_iout) { - ereport - .get_or_insert_with(|| { - ereports::Crossbounce::new(rail, now, sensor) - }) - .iout = Some(ereports::Peaks { - min: min_iout, - max: max_iout, - }); - peaks.last_bounce_detected = Some(now); - } - if peaks.vout.bounced(min_vout, max_vout) { - ereport - .get_or_insert_with(|| { - ereports::Crossbounce::new(rail, now, sensor) - }) - .vout = Some(ereports::Peaks { - min: min_vout, - max: max_vout, - }); + if peaks.iout.bounced(min_iout, max_iout) + || peaks.vout.bounced(min_vout, max_vout) + { peaks.last_bounce_detected = Some(now); + } else if let Some(threshold) = ctrl.vout_threshold { + // If we are tracking a minimum output voltage threshold on this sensor, + // check if we're below it, and generate an ereport. Once a sag is + // detected, this "latches" until the minimum output voltage rises above + // the threshold again. + // + // We only do this if a bounce is not detected,as we don't want to + // report sags that occur due to the system's power state changing + // intentionally. + if peaks.vout_sag_began_at.is_none() && min_vout <= threshold { + peaks.vout_sag_began_at = Some(now); + ereport = Some(ereports::VoutSag::new( + ctrl.rail, + now, + sensor.into(), + min_vout, + threshold, + )); + } else if peaks.vout_sag_began_at.is_some() && min_vout > threshold { + peaks.vout_sag_began_at = None; + } } ringbuf_entry!(Trace::Max5970 { sensor, last_bounce_detected: peaks.last_bounce_detected, + vout_sag_began_at: peaks.vout_sag_began_at, status0: match dev.read_reg(Register::status0) { Ok(reg) => reg, _ => return, @@ -236,14 +313,21 @@ fn trace_max5970( }); if let Some(report) = ereport { - let mut ereport_buf = [0u8; 128]; - let mut s = minicbor_serde::Serializer::new( - minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), - ); - if report.serialize(&mut s).is_ok() { - let len = s.into_encoder().into_writer().position(); - packrat.deliver_ereport(&ereport_buf[..len]); - } + deliver_ereport(packrat, &report); + } +} + +// This is in its own function so that we only push a stack frame large enough +// for the ereport buffer if needed. +#[inline(never)] +fn deliver_ereport(packrat: &mut Packrat, report: &impl serde::Serialize) { + let mut ereport_buf = [0u8; 128]; + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); } } @@ -302,6 +386,7 @@ struct Max5970Peaks { iout: Max5970Peak, vout: Max5970Peak, last_bounce_detected: Option, + vout_sag_began_at: Option, } pub(crate) struct State { @@ -329,19 +414,19 @@ impl State { if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { ringbuf_entry!(Trace::Now(self.fired)); - for ((dev, rail, sensor), peak) in CONTROLLER_CONFIG + for ((dev, ctrl), peak) in CONTROLLER_CONFIG .iter() .zip(devices.iter()) .filter_map(|(c, dev)| { if let Device::Max5970(dev) = dev { - Some((dev, c.rail, c.current)) + Some((dev, c)) } else { None } }) .zip(self.peaks.iter_mut()) { - trace_max5970(dev, rail, sensor, peak, packrat, self.fired); + trace_max5970(dev, ctrl, peak, packrat, self.fired); } } diff --git a/task/power/src/ereports.rs b/task/power/src/ereports.rs index b9c753817b..d3b427f3c3 100644 --- a/task/power/src/ereports.rs +++ b/task/power/src/ereports.rs @@ -10,34 +10,30 @@ use crate::sensor_api::SensorId; #[derive(serde::Serialize)] -pub(crate) struct Crossbounce { +pub(crate) struct VoutSag { k: &'static str, - pub(crate) rail: &'static str, - pub(crate) iout: Option, - pub(crate) vout: Option, - pub(crate) time: u32, - pub(crate) sensor_id: u32, + rail: &'static str, + time: u32, + sensor_id: u32, + vout_min: f32, + threshold: f32, } -#[derive(serde::Serialize)] -pub(crate) struct Peaks { - pub(crate) min: f32, - pub(crate) max: f32, -} - -impl Crossbounce { +impl VoutSag { pub(crate) fn new( rail: &'static str, time: u32, sensor_id: SensorId, + vout_min: f32, + threshold: f32, ) -> Self { Self { - k: "pwr.xbounce", + k: "pwr.vout_under_threshold", rail, - iout: None, - vout: None, time, sensor_id: sensor_id.into(), + vout_min, + threshold, } } } diff --git a/task/power/src/main.rs b/task/power/src/main.rs index 0119818d3f..019f5471f8 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -111,6 +111,10 @@ struct PowerControllerConfig { // May or may not be used, depending on BSP. #[allow(dead_code)] rail: &'static str, + // Threshold for output voltage error report. + // May or may not be used, depending on BSP. + #[allow(dead_code)] + vout_threshold: Option, } /// Bound device, which exposes sensor functions @@ -328,6 +332,7 @@ macro_rules! rail_controller { ), phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], rail: stringify!($rail:upper), + vout_threshold: None, } } }; @@ -349,6 +354,7 @@ macro_rules! rail_controller_notemp { temperature: None, phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], rail: stringify!($rail:upper), + vout_threshold: None, } } }; @@ -372,6 +378,7 @@ macro_rules! adm1272_controller { ), phases: None, rail: stringify!($rail:upper), + vout_threshold: None, } } }; @@ -393,6 +400,7 @@ macro_rules! ltc4282_controller { temperature: None, phases: None, rail: stringify!($rail:upper), + vout_threshold: None, } } }; @@ -416,6 +424,7 @@ macro_rules! lm5066_controller { ), phases: None, rail: stringify!($rail:upper), + vout_threshold: None, } } }; @@ -439,6 +448,7 @@ macro_rules! lm5066i_controller { ), phases: None, rail: stringify!($rail:upper), + vout_threshold: None, } } }; @@ -447,9 +457,9 @@ macro_rules! lm5066i_controller { #[allow(unused_macros)] macro_rules! max5970_controller { ($which:ident, $rail:ident, $state:ident, $rsense:expr) => { - max5970_controller!($which, $rail, $state, $rsense, false) + max5970_controller!($which, $rail, $state, $rsense, false, None) }; - ($which:ident, $rail:ident, $state:ident, $rsense:expr, $avg:expr) => { + ($which:ident, $rail:ident, $state:ident, $rsense:expr, $avg:expr, $vout_threshold:expr) => { paste::paste! { PowerControllerConfig { state: $crate::PowerState::$state, @@ -463,6 +473,7 @@ macro_rules! max5970_controller { temperature: None, phases: None, rail: stringify!($rail:upper), + vout_threshold: $vout_threshold, } } }; @@ -489,6 +500,7 @@ macro_rules! mwocp68_controller { // power rails and measured separately phases: None, rail: stringify!($rail:upper), + vout_threshold: $vout_threshold, } } }; From 7a174efc486ef6471ef308b652767f134b61d175 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 4 Aug 2025 13:12:05 -0700 Subject: [PATCH 61/61] use `f32::MIN` as sentinel instead of `None` that should save a byte or so... --- task/power/src/bsp/cosmo_a.rs | 54 ++++++++++++++++------------ task/power/src/bsp/gimlet_bcdef.rs | 57 ++++++++++++++++++------------ task/power/src/main.rs | 30 +++++++++------- 3 files changed, 82 insertions(+), 59 deletions(-) diff --git a/task/power/src/bsp/cosmo_a.rs b/task/power/src/bsp/cosmo_a.rs index 2e847589d3..c3d3edf4a2 100644 --- a/task/power/src/bsp/cosmo_a.rs +++ b/task/power/src/bsp/cosmo_a.rs @@ -20,7 +20,7 @@ const MAX5970_CONFIG_LEN: usize = 22; /// Minimum threshold for MAX970 12V output error reporting. If the output /// voltage drops below this threshold, we generate an ereport. -const V12OUT_EREPORT_MIN: Option = Some(11.0); +const V12OUT_EREPORT_MIN: f32 = 11.0; pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; CONTROLLER_CONFIG_LEN] = [ @@ -232,13 +232,16 @@ enum Trace { /// Trace record written for each MAX5970. /// - /// The `last_bounce_detected` field and those starting with `crossbounce_` - /// are copied from running state and may not be updated on every trace - /// event. The other fields are read while emitting the trace record and - /// should be current. + /// The `last_bounce_detected` and `vout_sag_began_at` fields, and those + /// starting with `crossbounce_` are copied from running state and may not + /// be updated on every trace event. The other fields are read while + /// emitting the trace record and should be current. Max5970 { sensor: SensorId, last_bounce_detected: Option, + /// If output voltage dipped below the configured minimum threshold, + /// this is the time at which the voltage sag was observed. This value + /// is cleared when output voltage rises above the minimum threshold. vout_sag_began_at: Option, status0: u8, status1: u8, @@ -299,28 +302,33 @@ fn trace_max5970( || peaks.vout.bounced(min_vout, max_vout) { peaks.last_bounce_detected = Some(now); - } else if let Some(threshold) = ctrl.vout_threshold { - // If we are tracking a minimum output voltage threshold on this sensor, - // check if we're below it, and generate an ereport. Once a sag is - // detected, this "latches" until the minimum output voltage rises above - // the threshold again. + // Presumably we have power-cycled, so clear the current sag detection + // so that we detect a new one once we've come back. + peaks.vout_sag_began_at = None; + } else if peaks.vout_sag_began_at.is_none() + && min_vout < ctrl.vout_min_threshold + { + // If we are tracking a minimum output voltage threshold on this + // sensor, check if we're below it, and generate an ereport. Once a + // sag is detected, this "latches" until the minimum output voltage + // rises above the threshold again. // // We only do this if a bounce is not detected,as we don't want to // report sags that occur due to the system's power state changing // intentionally. - if peaks.vout_sag_began_at.is_none() && min_vout <= threshold { - peaks.vout_sag_began_at = Some(now); - ereport = Some(ereports::VoutSag::new( - ctrl.rail, - now, - sensor.into(), - min_vout, - threshold, - )); - } else if peaks.vout_sag_began_at.is_some() && min_vout > threshold { - peaks.vout_sag_began_at = None; - } - } + peaks.vout_sag_began_at = Some(now); + ereport = Some(ereports::VoutSag::new( + ctrl.rail, + now, + sensor.into(), + min_vout, + ctrl.vout_min_threshold, + )); + } else if min_vout >= ctrl.vout_min_threshold { + // If we have gone back above the threshold, clear the current sag so + // that we will report a new one. + peaks.vout_sag_began_at = None; + }; ringbuf_entry!(Trace::Max5970 { sensor, diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index ab709f14c6..5233f6aa1f 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -14,7 +14,10 @@ use userlib::units::*; pub(crate) const CONTROLLER_CONFIG_LEN: usize = 37; const MAX5970_CONFIG_LEN: usize = 22; -const V12OUT_EREPORT_MIN: Option = Some(11.0); + +/// Minimum threshold for MAX970 12V output error reporting. If the output +/// voltage drops below this threshold, we generate an ereport. +const V12OUT_EREPORT_MIN: f32 = 11.0; pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; CONTROLLER_CONFIG_LEN] = [ @@ -184,13 +187,16 @@ enum Trace { /// Trace record written for each MAX5970. /// - /// The `last_bounce_detected` field and those starting with `crossbounce_` - /// are copied from running state and may not be updated on every trace - /// event. The other fields are read while emitting the trace record and - /// should be current. + /// The `last_bounce_detected` and `vout_sag_began_at` fields, and those + /// starting with `crossbounce_` are copied from running state and may not + /// be updated on every trace event. The other fields are read while + /// emitting the trace record and should be current. Max5970 { sensor: SensorId, last_bounce_detected: Option, + /// If output voltage dipped below the configured minimum threshold, + /// this is the time at which the voltage sag was observed. This value + /// is cleared when output voltage rises above the minimum threshold. vout_sag_began_at: Option, status0: u8, status1: u8, @@ -251,28 +257,33 @@ fn trace_max5970( || peaks.vout.bounced(min_vout, max_vout) { peaks.last_bounce_detected = Some(now); - } else if let Some(threshold) = ctrl.vout_threshold { - // If we are tracking a minimum output voltage threshold on this sensor, - // check if we're below it, and generate an ereport. Once a sag is - // detected, this "latches" until the minimum output voltage rises above - // the threshold again. + // Presumably we have power-cycled, so clear the current sag detection + // so that we detect a new one once we've come back. + peaks.vout_sag_began_at = None; + } else if peaks.vout_sag_began_at.is_none() + && min_vout < ctrl.vout_min_threshold + { + // If we are tracking a minimum output voltage threshold on this + // sensor, check if we're below it, and generate an ereport. Once a + // sag is detected, this "latches" until the minimum output voltage + // rises above the threshold again. // // We only do this if a bounce is not detected,as we don't want to // report sags that occur due to the system's power state changing // intentionally. - if peaks.vout_sag_began_at.is_none() && min_vout <= threshold { - peaks.vout_sag_began_at = Some(now); - ereport = Some(ereports::VoutSag::new( - ctrl.rail, - now, - sensor.into(), - min_vout, - threshold, - )); - } else if peaks.vout_sag_began_at.is_some() && min_vout > threshold { - peaks.vout_sag_began_at = None; - } - } + peaks.vout_sag_began_at = Some(now); + ereport = Some(ereports::VoutSag::new( + ctrl.rail, + now, + sensor.into(), + min_vout, + ctrl.vout_min_threshold, + )); + } else if min_vout >= ctrl.vout_min_threshold { + // If we have gone back above the threshold, clear the current sag so + // that we will report a new one. + peaks.vout_sag_began_at = None; + }; ringbuf_entry!(Trace::Max5970 { sensor, diff --git a/task/power/src/main.rs b/task/power/src/main.rs index 019f5471f8..aae7265eb1 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -111,10 +111,14 @@ struct PowerControllerConfig { // May or may not be used, depending on BSP. #[allow(dead_code)] rail: &'static str, - // Threshold for output voltage error report. - // May or may not be used, depending on BSP. + /// Minimum threshold for MAX5970 output voltage sag error reporting. If + /// output voltage drops below this value, an ereport is generated. + /// + /// Note that `f32::MIN` is equivalent to "don't monitor for voltage sag on this rail". + /// + /// May or may not be used, depending on BSP. #[allow(dead_code)] - vout_threshold: Option, + vout_min_threshold: f32, } /// Bound device, which exposes sensor functions @@ -332,7 +336,7 @@ macro_rules! rail_controller { ), phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], rail: stringify!($rail:upper), - vout_threshold: None, + vout_min_threshold: f32::MAX, } } }; @@ -354,7 +358,7 @@ macro_rules! rail_controller_notemp { temperature: None, phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], rail: stringify!($rail:upper), - vout_threshold: None, + vout_min_threshold: f32::MIN, } } }; @@ -378,7 +382,7 @@ macro_rules! adm1272_controller { ), phases: None, rail: stringify!($rail:upper), - vout_threshold: None, + vout_min_threshold: f32::MIN, } } }; @@ -400,7 +404,7 @@ macro_rules! ltc4282_controller { temperature: None, phases: None, rail: stringify!($rail:upper), - vout_threshold: None, + vout_min_threshold: f32::MIN, } } }; @@ -424,7 +428,7 @@ macro_rules! lm5066_controller { ), phases: None, rail: stringify!($rail:upper), - vout_threshold: None, + vout_min_threshold: f32::MIN, } } }; @@ -448,7 +452,7 @@ macro_rules! lm5066i_controller { ), phases: None, rail: stringify!($rail:upper), - vout_threshold: None, + vout_min_threshold: f32::MIN, } } }; @@ -457,9 +461,9 @@ macro_rules! lm5066i_controller { #[allow(unused_macros)] macro_rules! max5970_controller { ($which:ident, $rail:ident, $state:ident, $rsense:expr) => { - max5970_controller!($which, $rail, $state, $rsense, false, None) + max5970_controller!($which, $rail, $state, $rsense, false, f32::MIN) }; - ($which:ident, $rail:ident, $state:ident, $rsense:expr, $avg:expr, $vout_threshold:expr) => { + ($which:ident, $rail:ident, $state:ident, $rsense:expr, $avg:expr, $vout_min_threshold:expr) => { paste::paste! { PowerControllerConfig { state: $crate::PowerState::$state, @@ -473,7 +477,7 @@ macro_rules! max5970_controller { temperature: None, phases: None, rail: stringify!($rail:upper), - vout_threshold: $vout_threshold, + vout_min_threshold: $vout_min_threshold, } } }; @@ -500,7 +504,7 @@ macro_rules! mwocp68_controller { // power rails and measured separately phases: None, rail: stringify!($rail:upper), - vout_threshold: $vout_threshold, + vout_min_threshold: f32::MIN, } } };