From fb470e7f2673bde937a95f842136b15e34a8aefc Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 14:48:03 -0800 Subject: [PATCH 01/17] Add ereports crate, use shared types in CPU sequencer tasks --- Cargo.lock | 11 +++++ drv/cosmo-seq-server/Cargo.toml | 1 + drv/cosmo-seq-server/src/main.rs | 76 ++++++---------------------- drv/cosmo-seq-server/src/vcore.rs | 16 +++--- drv/gimlet-seq-server/Cargo.toml | 1 + drv/gimlet-seq-server/src/main.rs | 79 +++++++----------------------- drv/gimlet-seq-server/src/vcore.rs | 27 +++++----- lib/ereports/Cargo.toml | 12 +++++ lib/ereports/src/cpu.rs | 4 ++ lib/ereports/src/lib.rs | 10 ++++ lib/ereports/src/pwr.rs | 41 ++++++++++++++++ 11 files changed, 134 insertions(+), 144 deletions(-) create mode 100644 lib/ereports/Cargo.toml create mode 100644 lib/ereports/src/cpu.rs create mode 100644 lib/ereports/src/lib.rs create mode 100644 lib/ereports/src/pwr.rs diff --git a/Cargo.lock b/Cargo.lock index 79adf90a97..5c623b1af8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,6 +1136,7 @@ dependencies = [ "drv-spartan7-loader-api", "drv-spi-api", "drv-stm32xx-sys-api", + "ereports", "fixedstr", "gnarle", "idol", @@ -1304,6 +1305,7 @@ dependencies = [ "drv-spi-api", "drv-stm32h7-spi", "drv-stm32xx-sys-api", + "ereports", "fixedstr", "gnarle", "hubpack", @@ -2970,6 +2972,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "ereports" +version = "0.1.0" +dependencies = [ + "drv-i2c-devices", + "fixedstr", + "microcbor", +] + [[package]] name = "errno" version = "0.2.8" diff --git a/drv/cosmo-seq-server/Cargo.toml b/drv/cosmo-seq-server/Cargo.toml index 38694a49ff..4405079d3f 100644 --- a/drv/cosmo-seq-server/Cargo.toml +++ b/drv/cosmo-seq-server/Cargo.toml @@ -16,6 +16,7 @@ drv-packrat-vpd-loader = { path = "../packrat-vpd-loader" } drv-spartan7-loader-api = { path = "../spartan7-loader-api" } drv-spi-api = { path = "../spi-api" } drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" } +ereports = { path = "../../lib/ereports" } gnarle = { path = "../../lib/gnarle" } ringbuf = { path = "../../lib/ringbuf" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index 504561a0db..d75321273b 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -24,6 +24,7 @@ use userlib::{ RecvMessage, }; +use crate::i2c_config::MAX_COMPONENT_ID_LEN as REFDES_LEN; use drv_hf_api::HostFlash; use ringbuf::{counted_ringbuf, ringbuf_entry, Count}; @@ -178,9 +179,10 @@ struct StateMachineStates { nic: Result, } -const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for!( - task_packrat_api::Ereport, -); +const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ + ereports::pwr::PmbusAlert, + ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, +]; #[export_name = "main"] fn main() -> ! { @@ -219,17 +221,15 @@ fn main() -> ! { if let Some(last_cause) = last_cause { // Report the failure even if we eventually succeeded. - try_send_ereport( - &packrat, - &mut ereport_buf[..], - EreportClass::Bmr491MitigationFailure, - EreportKind::Bmr491MitigationFailure { - refdes: FixedStr::from_str(dev.component_id()), - failures, - last_cause, - succeeded, - }, - ); + let ereport = ereports::pwr::Bmr491MitigationFailure { + refdes: FixedStr::<{ REFDES_LEN }>::from_str( + dev.component_id(), + ), + failures, + last_cause, + succeeded, + }; + try_send_ereport(&packrat, &mut ereport_buf[..], &ereport); } } @@ -455,42 +455,6 @@ struct ServerImpl { ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], } -#[derive(microcbor::Encode)] -pub enum EreportClass { - #[cbor(rename = "hw.pwr.pmbus.alert")] - PmbusAlert, - #[cbor(rename = "hw.pwr.bmr491.mitfail")] - Bmr491MitigationFailure, -} - -#[derive(microcbor::EncodeFields)] -pub(crate) enum EreportKind { - Bmr491MitigationFailure { - refdes: FixedStr<'static, { crate::i2c_config::MAX_COMPONENT_ID_LEN }>, - failures: u32, - last_cause: drv_i2c_devices::bmr491::MitigationFailureKind, - succeeded: bool, - }, - PmbusAlert { - refdes: FixedStr<'static, { crate::i2c_config::MAX_COMPONENT_ID_LEN }>, - rail: vcore::Rail, - time: u64, - pwr_good: Option, - pmbus_status: PmbusStatus, - }, -} - -#[derive(Copy, Clone, Default, microcbor::Encode)] -pub(crate) struct PmbusStatus { - word: Option, - input: Option, - iout: Option, - vout: Option, - temp: Option, - cml: Option, - mfr: Option, -} - impl ServerImpl { fn new( loader: drv_spartan7_loader_api::Spartan7Loader, @@ -1233,17 +1197,9 @@ impl NotificationHandler for ServerImpl { fn try_send_ereport( packrat: &task_packrat_api::Packrat, ereport_buf: &mut [u8], - class: EreportClass, - report: EreportKind, + ereport: &impl microcbor::StaticCborLen, ) { - let eresult = packrat.deliver_microcbor_ereport( - &task_packrat_api::Ereport { - class, - version: 0, - report, - }, - ereport_buf, - ); + let eresult = packrat.deliver_microcbor_ereport(&ereport, ereport_buf); match eresult { Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { diff --git a/drv/cosmo-seq-server/src/vcore.rs b/drv/cosmo-seq-server/src/vcore.rs index e514bed71b..5048c9f2d9 100644 --- a/drv/cosmo-seq-server/src/vcore.rs +++ b/drv/cosmo-seq-server/src/vcore.rs @@ -15,6 +15,7 @@ use super::i2c_config; use drv_i2c_api::ResponseCode; use drv_i2c_devices::raa229620a::{self, Raa229620A}; +use ereports::pwr::{PmbusAlert, PmbusStatus}; use fixedstr::FixedStr; use pmbus::commands::raa229620a::STATUS_WORD; use ringbuf::*; @@ -458,7 +459,7 @@ impl VCore { .map(|s| s.0); ringbuf_entry!(Trace::StatusMfrSpecific(rail, status_mfr)); - let pmbus_status = crate::PmbusStatus { + let pmbus_status = PmbusStatus { word: status_word.map(|s| s.0).ok(), input: status_input.ok(), vout: status_vout.ok(), @@ -468,19 +469,16 @@ impl VCore { mfr: status_mfr.ok(), }; - let ereport = crate::EreportKind::PmbusAlert { + let ereport = PmbusAlert { rail, - refdes: FixedStr::from_str(device.i2c_device().component_id()), + refdes: FixedStr::<{ crate::REFDES_LEN }>::from_str( + device.i2c_device().component_id(), + ), time: now, pmbus_status, pwr_good: power_good, }; - crate::try_send_ereport( - &self.packrat, - ereport_buf, - crate::EreportClass::PmbusAlert, - ereport, - ); + crate::try_send_ereport(&self.packrat, ereport_buf, &ereport); // TODO(eliza): if POWER_GOOD has been deasserted, we should produce a // subsequent ereport for that. diff --git a/drv/gimlet-seq-server/Cargo.toml b/drv/gimlet-seq-server/Cargo.toml index ed3cb41f9d..fdf8bcf295 100644 --- a/drv/gimlet-seq-server/Cargo.toml +++ b/drv/gimlet-seq-server/Cargo.toml @@ -15,6 +15,7 @@ drv-spi-api = { path = "../spi-api" } drv-stm32h7-spi = { path = "../stm32h7-spi" } drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" } counters = { path = "../../lib/counters" } +ereports = { path = "../../lib/ereports" } gnarle = { path = "../../lib/gnarle" } ringbuf = { path = "../../lib/ringbuf" } task-jefe-api = { path = "../../task/jefe-api" } diff --git a/drv/gimlet-seq-server/src/main.rs b/drv/gimlet-seq-server/src/main.rs index 8316906e10..110cb0fb7d 100644 --- a/drv/gimlet-seq-server/src/main.rs +++ b/drv/gimlet-seq-server/src/main.rs @@ -19,6 +19,7 @@ use userlib::{ }; use zerocopy::IntoBytes; +use crate::i2c_config::MAX_COMPONENT_ID_LEN as REFDES_LEN; use drv_cpu_seq_api::{PowerState, SeqError, StateChangeReason, Transition}; use drv_hf_api as hf_api; use drv_i2c_api as i2c; @@ -214,46 +215,14 @@ struct ServerImpl { } const TIMER_INTERVAL: u32 = 10; -const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for!( - task_packrat_api::Ereport -); - -#[derive(microcbor::Encode)] -pub enum EreportClass { - #[cbor(rename = "hw.pwr.pmbus.alert")] - PmbusAlert, - #[cbor(rename = "hw.pwr.bmr491.mitfail")] - Bmr491MitigationFailure, -} - -#[derive(microcbor::EncodeFields)] -pub(crate) enum EreportKind { - PmbusAlert { - refdes: FixedStr<'static, { crate::i2c_config::MAX_COMPONENT_ID_LEN }>, +const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ + ereports::pwr::PmbusAlert< // 9 is the maximum length rail name used in this module (`VDD_VCORE`) - rail: FixedStr<'static, 9>, - time: u64, - pwr_good: Option, - pmbus_status: PmbusStatus, - }, - Bmr491MitigationFailure { - refdes: FixedStr<'static, { crate::i2c_config::MAX_COMPONENT_ID_LEN }>, - failures: u32, - last_cause: drv_i2c_devices::bmr491::MitigationFailureKind, - succeeded: bool, - }, -} - -#[derive(Copy, Clone, Default, microcbor::Encode)] -pub(crate) struct PmbusStatus { - word: Option, - input: Option, - iout: Option, - vout: Option, - temp: Option, - cml: Option, - mfr: Option, -} + FixedStr<'static, 9>, + { REFDES_LEN }, + >, + ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, +]; impl ServerImpl { fn init( @@ -523,17 +492,15 @@ impl ServerImpl { if let Some(last_cause) = last_cause { // Report the failure even if we eventually succeeded. - try_send_ereport( - &packrat, - &mut ereport_buf[..], - EreportClass::Bmr491MitigationFailure, - EreportKind::Bmr491MitigationFailure { - refdes: FixedStr::from_str(dev.component_id()), - failures, - last_cause, - succeeded, - }, - ); + let ereport = ereports::pwr::Bmr491MitigationFailure { + refdes: FixedStr::<{ REFDES_LEN }>::from_str( + dev.component_id(), + ), + failures, + last_cause, + succeeded, + }; + try_send_ereport(&packrat, &mut ereport_buf[..], &ereport); } } @@ -1612,17 +1579,9 @@ cfg_if::cfg_if! { fn try_send_ereport( packrat: &packrat_api::Packrat, ereport_buf: &mut [u8], - class: EreportClass, - report: EreportKind, + ereport: &impl microcbor::StaticCborLen, ) { - let eresult = packrat.deliver_microcbor_ereport( - &packrat_api::Ereport { - class, - version: 0, - report, - }, - ereport_buf, - ); + let eresult = packrat.deliver_microcbor_ereport(&ereport, ereport_buf); match eresult { Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { diff --git a/drv/gimlet-seq-server/src/vcore.rs b/drv/gimlet-seq-server/src/vcore.rs index 6f8a61866f..e829480fdb 100644 --- a/drv/gimlet-seq-server/src/vcore.rs +++ b/drv/gimlet-seq-server/src/vcore.rs @@ -31,6 +31,7 @@ use crate::gpio_irq_pins::VCORE_TO_SP_ALERT_L; use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_i2c_devices::raa229618::Raa229618; use drv_stm32xx_sys_api as sys_api; +use ereports::pwr::{PmbusAlert, PmbusStatus}; use fixedstr::FixedStr; use ringbuf::*; use sys_api::IrqControl; @@ -331,7 +332,7 @@ impl VCore { .map(|s| s.0); ringbuf_entry!(Trace::StatusMfrSpecific(status_mfr_specific)); - let status = super::PmbusStatus { + let status = PmbusStatus { word: status_word.map(|s| s.0).ok(), input: status_input.ok(), vout: status_vout.ok(), @@ -342,20 +343,16 @@ impl VCore { }; static RAIL: FixedStr<'static, 9> = FixedStr::from_str("VDD_VCORE"); - crate::try_send_ereport( - &self.packrat, - &mut ereport_buf[..], - crate::EreportClass::PmbusAlert, - crate::EreportKind::PmbusAlert { - refdes: FixedStr::from_str( - self.device.i2c_device().component_id(), - ), - rail: RAIL, - time: now, - pwr_good, - pmbus_status: status, - }, - ); + let ereport = PmbusAlert { + refdes: FixedStr::<{ crate::REFDES_LEN }>::from_str( + self.device.i2c_device().component_id(), + ), + rail: RAIL, + time: now, + pwr_good, + pmbus_status: status, + }; + crate::try_send_ereport(&self.packrat, &mut ereport_buf[..], &ereport); // TODO(eliza): if POWER_GOOD has been deasserted, we should produce a // subsequent ereport for that. diff --git a/lib/ereports/Cargo.toml b/lib/ereports/Cargo.toml new file mode 100644 index 0000000000..9b7f517c23 --- /dev/null +++ b/lib/ereports/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ereports" +version = "0.1.0" +edition = "2024" + +[dependencies] +microcbor = { path = "../microcbor" } +fixedstr = { path = "../fixedstr", features = ["microcbor"] } +drv-i2c-devices = { path = "../../drv/i2c-devices" } + +[lints] +workspace = true diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs new file mode 100644 index 0000000000..c1464fa12c --- /dev/null +++ b/lib/ereports/src/cpu.rs @@ -0,0 +1,4 @@ +// 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/. + diff --git a/lib/ereports/src/lib.rs b/lib/ereports/src/lib.rs new file mode 100644 index 0000000000..dd134eb56f --- /dev/null +++ b/lib/ereports/src/lib.rs @@ -0,0 +1,10 @@ +// 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 message definitions shared between multiple tasks. + +#![no_std] + +pub mod cpu; +pub mod pwr; diff --git a/lib/ereports/src/pwr.rs b/lib/ereports/src/pwr.rs new file mode 100644 index 0000000000..6899c3120b --- /dev/null +++ b/lib/ereports/src/pwr.rs @@ -0,0 +1,41 @@ +// 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/. + +//! Common ereport types from the `hw.pwr.*` class hierarchy. + +use fixedstr::FixedStr; +use microcbor::{Encode, StaticCborLen}; + +/// An ereport representing a PMBus alert. +#[derive(Clone, Encode)] +#[ereport(class = "hw.pwr.pmbus.alert", version = 0)] +pub struct PmbusAlert { + pub refdes: FixedStr<'static, REFDES_LEN>, + pub rail: R, + pub time: u64, + pub pwr_good: Option, + pub pmbus_status: PmbusStatus, +} + +/// An ereport representing a failure to apply the BMR491 firmware mitigation. +#[derive(Clone, Encode)] +#[ereport(class = "hw.pwr.bmr491.mitfail", version = 0)] +pub struct Bmr491MitigationFailure { + pub refdes: FixedStr<'static, REFDES_LEN>, + pub failures: u32, + pub last_cause: drv_i2c_devices::bmr491::MitigationFailureKind, + pub succeeded: bool, +} + +/// PMBus status registers. +#[derive(Copy, Clone, Default, Encode)] +pub struct PmbusStatus { + pub word: Option, + pub input: Option, + pub iout: Option, + pub vout: Option, + pub temp: Option, + pub cml: Option, + pub mfr: Option, +} From e0c60d4d3d44649482e78470405357fc74cca992 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 25 Feb 2026 10:44:38 -0800 Subject: [PATCH 02/17] cpu_seq: start on seq ereports --- drv/cosmo-seq-server/src/main.rs | 132 +++++++++++++++++++----------- drv/cosmo-seq-server/src/vcore.rs | 15 ++-- drv/cpu-power-state/src/lib.rs | 1 + lib/ereports/src/cpu.rs | 45 ++++++++++ 4 files changed, 136 insertions(+), 57 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index d75321273b..476eb26c9b 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -171,19 +171,12 @@ const SP5R4_PULL: sys_api::Pull = sys_api::Pull::None; use gpio_irq_pins::SEQ_IRQ; -//////////////////////////////////////////////////////////////////////////////// - /// Helper type which includes both sequencer and NIC state machine states struct StateMachineStates { seq: Result, nic: Result, } -const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ - ereports::pwr::PmbusAlert, - ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, -]; - #[export_name = "main"] fn main() -> ! { // Populate packrat with our mac address and identity. @@ -197,6 +190,11 @@ fn main() -> ! { EREPORT_BUF.claim() }; + let mut ereporter = Ereporter { + packrat, + ereport_buf, + }; + // // Apply the configuration mitigation on the BMR491, if required. This // is an external device access and may fail. We'll attempt it thrice @@ -229,11 +227,11 @@ fn main() -> ! { last_cause, succeeded, }; - try_send_ereport(&packrat, &mut ereport_buf[..], &ereport); + ereporter.try_send_ereport(&ereport); } } - match init(packrat, ereport_buf) { + match init(ereporter) { // Set up everything nicely, time to start serving incoming messages. Ok(mut server) => { // Enable the backplane PCIe clock if requested @@ -275,10 +273,7 @@ fn main() -> ! { } } -fn init( - packrat: Packrat, - ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], -) -> Result { +fn init(ereporter: Ereporter) -> Result { let sys = sys_api::Sys::from(SYS.get_task_id()); // Pull the fault line low while we're loading @@ -369,7 +364,7 @@ fn init( // Turn on the chassis LED! sys.gpio_set(SP_CHASSIS_STATUS_LED); - Ok(ServerImpl::new(loader, packrat, ereport_buf)) + Ok(ServerImpl::new(loader, ereporter)) } /// Configures the front FPGA pins and holds it in reset @@ -450,16 +445,13 @@ struct ServerImpl { espi: fmc_periph::espi::Espi, debug: fmc_periph::debug_ctrl::DebugCtrl, vcore: VCore, - /// Static buffer for encoding ereports. This is a static so that we don't - /// have it on the stack when encoding ereports. - ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], + ereporter: Ereporter, } impl ServerImpl { fn new( loader: drv_spartan7_loader_api::Spartan7Loader, - packrat: Packrat, - ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], + ereporter: Ereporter, ) -> Self { let now = sys_get_timer().now; @@ -487,8 +479,8 @@ impl ServerImpl { seq, espi, debug, - vcore: VCore::new(I2C.get_task_id(), packrat), - ereport_buf, + vcore: VCore::new(I2C.get_task_id()), + ereporter, } } @@ -612,26 +604,33 @@ impl ServerImpl { }); // From sp5-mobo-guide-56870_1.1.pdf table 72 - match (coretype0, coretype1, coretype2) { + let coretype_ok = match (coretype0, coretype1, coretype2) { // These correspond to Type-2 and Type-3 - (true, false, true) | (true, false, false) => (), + (true, false, true) | (true, false, false) => true, // Reject all other combos and return to A0 - _ => { - self.seq.power_ctrl.modify(|m| m.set_a0_en(false)); - return Err(CpuSeqError::UnrecognizedCPU); - } + _ => false, }; // From sp5-mobo-guide-56870_1.1.pdf table 73 - match (sp5r1, sp5r2, sp5r3, sp5r4) { + let sp5rx_ok = // There is only combo we accept here - (true, false, false, false) => (), - // Reject all other combos and return to A0 - _ => { - self.seq.power_ctrl.modify(|m| m.set_a0_en(false)); - return Err(CpuSeqError::UnrecognizedCPU); - } - }; + (sp5r1, sp5r2, sp5r3, sp5r4) == (true, false, false, false); + + if !(coretype_ok && sp5rx_ok) { + self.seq.power_ctrl.modify(|m| m.set_a0_en(false)); + let ereport = ereports::cpu::UnsupportedCpu { + cpu: &HOST_CPU_REFDES, + cpu_type: CpuTypeBits { + coretype: [coretype0, coretype1, coretype2], + sp5rx: [sp5r1, sp5r2, sp5r3, sp5r4], + coretype_ok, + sp5rx_ok, + }, + }; + self.ereporter.try_send_ereport(&ereport); + return Err(CpuSeqError::UnrecognizedCPU); + } + // Turn on the voltage regulator undervolt alerts. self.enable_sequencer_interrupts(); @@ -855,7 +854,7 @@ impl ServerImpl { pwr_cont2: ifr.pwr_cont2_to_fpga1_alert, }; self.vcore - .handle_pmbus_alert(which_vrms, now, self.ereport_buf); + .handle_pmbus_alert(which_vrms, now, &mut self.ereporter); // We need not instruct the sequencer to reset. PMBus alerts from // the RAA229620As are divided into two categories, "warnings" and @@ -1194,19 +1193,54 @@ impl NotificationHandler for ServerImpl { } } -fn try_send_ereport( - packrat: &task_packrat_api::Packrat, - ereport_buf: &mut [u8], - ereport: &impl microcbor::StaticCborLen, -) { - let eresult = packrat.deliver_microcbor_ereport(&ereport, ereport_buf); - match eresult { - Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), - Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { - ringbuf_entry!(Trace::EreportLost(len, err)) - } - Err(task_packrat_api::EreportEncodeError::Encoder(_)) => { - ringbuf_entry!(Trace::EreportTooBig) +//////////////////////////////////////////////////////////////////////////////// + +const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ + ereports::pwr::PmbusAlert, + ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, + ereports::cpu::Thermtrip, + ereports::cpu::Smerr, + ereports::cpu::UnsupportedCpu +]; + +static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = + ereports::cpu::HostCpuRefdes { + refdes: fixedstr::FixedString::from_str("P0"), + dev_id: fixedstr::FixedString::from_str("sp5-host-cpu"), + }; + +#[derive(Clone, microcbor::EncodeFields)] +struct CpuTypeBits { + coretype: [bool; 3], + sp5rx: [bool; 4], + coretype_ok: bool, + sp5rx_ok: bool, +} + +/// This is just the Packrat API handle and the ereport buffer bundled together +/// in one thing so that it can be passed into various places as a single +/// argument. +pub(crate) struct Ereporter { + packrat: task_packrat_api::Packrat, + ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], +} + +impl Ereporter { + pub(crate) fn try_send_ereport( + &mut self, + ereport: &impl microcbor::StaticCborLen, + ) { + let eresult = self + .packrat + .deliver_microcbor_ereport(&ereport, &mut self.ereport_buf[..]); + match eresult { + Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), + Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { + ringbuf_entry!(Trace::EreportLost(len, err)) + } + Err(task_packrat_api::EreportEncodeError::Encoder(_)) => { + ringbuf_entry!(Trace::EreportTooBig) + } } } } diff --git a/drv/cosmo-seq-server/src/vcore.rs b/drv/cosmo-seq-server/src/vcore.rs index 5048c9f2d9..4f4b18f6c3 100644 --- a/drv/cosmo-seq-server/src/vcore.rs +++ b/drv/cosmo-seq-server/src/vcore.rs @@ -13,6 +13,7 @@ //! use super::i2c_config; +use crate::Ereporter; use drv_i2c_api::ResponseCode; use drv_i2c_devices::raa229620a::{self, Raa229620A}; use ereports::pwr::{PmbusAlert, PmbusStatus}; @@ -27,7 +28,6 @@ pub(super) struct VCore { /// `PWR_CONT2`: This regulator controls `VDDCR_CPU1` and `VDDIO_SP5` rails. vddcr_cpu1: Raa229620A, faulted: Vrms, - packrat: task_packrat_api::Packrat, } #[derive(Copy, Clone, PartialEq, microcbor::Encode)] @@ -177,7 +177,7 @@ const VCORE_UV_WARN_LIMIT: units::Volts = units::Volts(11.0); const VCORE_NSAMPLES: usize = 25; impl VCore { - pub fn new(i2c: TaskId, packrat: task_packrat_api::Packrat) -> Self { + pub fn new(i2c: TaskId) -> Self { let (device, rail) = i2c_config::pmbus::vddcr_cpu0_a0(i2c); let vddcr_cpu0 = Raa229620A::new(&device, rail); @@ -191,7 +191,6 @@ impl VCore { pwr_cont1: false, pwr_cont2: false, }, - packrat, } } @@ -269,7 +268,7 @@ impl VCore { &mut self, vrms: Vrms, now: u64, - ereport_buf: &mut [u8], + ereporter: &mut Ereporter, ) { ringbuf_entry!(Trace::PmbusAlert { timestamp: now, @@ -282,7 +281,7 @@ impl VCore { now, Rail::VddcrCpu0, vrms.pwr_cont1, - ereport_buf, + ereporter, ); input_fault |= state.input_fault; self.faulted.pwr_cont1 |= state.faulted; @@ -293,7 +292,7 @@ impl VCore { now, Rail::VddcrCpu1, vrms.pwr_cont1, - ereport_buf, + ereporter, ); input_fault |= state.input_fault; self.faulted.pwr_cont2 |= state.faulted; @@ -351,7 +350,7 @@ impl VCore { now: u64, rail: Rail, alerted: bool, - ereport_buf: &mut [u8], + ereporter: &mut Ereporter, ) -> RegulatorState { use pmbus::commands::raa229620a::STATUS_WORD; @@ -478,7 +477,7 @@ impl VCore { pmbus_status, pwr_good: power_good, }; - crate::try_send_ereport(&self.packrat, ereport_buf, &ereport); + ereporter.try_send_ereport(&ereport); // TODO(eliza): if POWER_GOOD has been deasserted, we should produce a // subsequent ereport for that. diff --git a/drv/cpu-power-state/src/lib.rs b/drv/cpu-power-state/src/lib.rs index 657cdf9180..87bf9e34f9 100644 --- a/drv/cpu-power-state/src/lib.rs +++ b/drv/cpu-power-state/src/lib.rs @@ -22,6 +22,7 @@ use zerocopy::{Immutable, IntoBytes, KnownLayout}; KnownLayout, counters::Count, )] +#[cfg_attr(feature = "microcbor", derive(microcbor::Encode))] #[repr(u8)] pub enum PowerState { /// Initial A2 state where the SP and most associated circuitry is powered. diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs index c1464fa12c..77fc793afb 100644 --- a/lib/ereports/src/cpu.rs +++ b/lib/ereports/src/cpu.rs @@ -2,3 +2,48 @@ // 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/. +//! Common ereport types from the `hw.pwr.*` class hierarchy. + +use fixedstr::FixedString; +use microcbor::{Encode, EncodeFields}; + +/// An ereport representing an AMD CPU's `THERMTRIP` assertion. +#[derive(Clone, Encode)] +#[ereport(class = "hw.cpu.amd.thermtrip", version = 0)] +pub struct Thermtrip { + #[cbor(flatten)] + pub cpu: &'static HostCpuRefdes, +} + +/// An ereport representing an AMD CPU's `SMERR_L` assertion. +#[derive(Clone, Encode)] +#[ereport(class = "hw.cpu.amd.smerr", version = 0)] +pub struct Smerr { + #[cbor(flatten)] + pub cpu: &'static HostCpuRefdes, +} + +/// An ereport representing an unsupported CPU. +#[derive(Clone, Encode)] +#[ereport(class = "hw.cpu.unsup", version = 0)] +pub struct UnsupportedCpu> { + #[cbor(flatten)] + pub cpu: &'static HostCpuRefdes, + #[cbor(flatten)] + pub cpu_type: T, +} + +#[derive(Clone, EncodeFields)] +pub struct HostCpuRefdes { + /// On both Gimlet and Cosmo, the host CPU's refdes is `P0`. + pub refdes: FixedString<2>, + /// As the host CPU's `control-plane-agent` device ID is different from its + /// refdes, we must include both in the ereport. + /// + /// On Gimlet, this is `sp3-host-cpu` and on Cosmo, it is `sp5-host-cpu`. + // + // TODO(eliza): It would be cool if we could get this from the same value as + // where `control-plane-agent` gets it from...but in practice that's + // annoying. + pub dev_id: FixedString<12>, +} From 761a3d4bc137d7496266f0a3b8b83d238aa6a9d9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 25 Feb 2026 10:47:17 -0800 Subject: [PATCH 03/17] cosmo_seq: wire up thermtrip and smerr --- drv/cosmo-seq-server/src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index 476eb26c9b..3e281b86f4 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -925,7 +925,9 @@ impl ServerImpl { self.seq.ifr.modify(|h| h.set_thermtrip(true)); ringbuf_entry!(Trace::Thermtrip); action = InternalAction::ThermTrip; - // Great place for an ereport? + self.ereporter.try_send_ereport(&ereports::cpu::Thermtrip { + cpu: &HOST_CPU_REFDES, + }); } if ifr.a0mapo { @@ -940,7 +942,9 @@ impl ServerImpl { self.seq.ifr.modify(|h| h.set_smerr_assert(true)); ringbuf_entry!(Trace::SmerrInterrupt); action = InternalAction::Smerr; - // Great place for an ereport? + self.ereporter.try_send_ereport(&ereports::cpu::Smerr { + cpu: &HOST_CPU_REFDES, + }); } // Fan Fault is unconnected From e98ef68b4f6de272c2b2682cd6fe58063b8f8037 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 25 Feb 2026 10:57:13 -0800 Subject: [PATCH 04/17] include current state in (some) seq ereports --- drv/cosmo-seq-server/src/main.rs | 23 ++++++++++++++++++----- drv/cpu-power-state/Cargo.toml | 1 + lib/ereports/Cargo.toml | 1 + lib/ereports/src/cpu.rs | 12 ++++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index 3e281b86f4..7043ee9179 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -438,6 +438,8 @@ fn init_front_fpga( #[allow(unused)] struct ServerImpl { state: PowerState, + /// The Hubris tick at which we transitioned to the current state. + since: u64, jefe: Jefe, sys: Sys, hf: HostFlash, @@ -473,6 +475,7 @@ impl ServerImpl { ServerImpl { state: PowerState::A2, + since: now, jefe, sys: Sys::from(SYS.get_task_id()), hf: HostFlash::from(HF.get_task_id()), @@ -690,13 +693,14 @@ impl ServerImpl { _ => return Err(CpuSeqError::IllegalTransition), } - self.set_state_internal(state); + self.set_state_internal(state, now); Ok(Transition::Changed) } /// Updates our internal `state` and the global state in `jefe` - fn set_state_internal(&mut self, state: PowerState) { + fn set_state_internal(&mut self, state: PowerState, now: u64) { self.state = state; + self.since = now; self.jefe.set_state(state as u32); self.poke_timer(); } @@ -927,6 +931,7 @@ impl ServerImpl { action = InternalAction::ThermTrip; self.ereporter.try_send_ereport(&ereports::cpu::Thermtrip { cpu: &HOST_CPU_REFDES, + state: self.ereport_current_state(), }); } @@ -944,6 +949,7 @@ impl ServerImpl { action = InternalAction::Smerr; self.ereporter.try_send_ereport(&ereports::cpu::Smerr { cpu: &HOST_CPU_REFDES, + state: self.ereport_current_state(), }); } // Fan Fault is unconnected @@ -959,7 +965,7 @@ impl ServerImpl { why: StateChangeReason::CpuReset, now, }); - self.set_state_internal(PowerState::A0Reset); + self.set_state_internal(PowerState::A0Reset, now); } InternalAction::NicMapo => { // Presumably we are in A0+HP, so send us back to A0 so that the @@ -971,7 +977,7 @@ impl ServerImpl { why: StateChangeReason::NicMapo, now, }); - self.set_state_internal(PowerState::A0); + self.set_state_internal(PowerState::A0, now); } InternalAction::ThermTrip => { // This is a terminal state; we set our state to `A0Thermtrip` @@ -982,7 +988,7 @@ impl ServerImpl { why: StateChangeReason::Overheat, now, }); - self.set_state_internal(PowerState::A0Thermtrip); + self.set_state_internal(PowerState::A0Thermtrip, now); } InternalAction::Mapo => { // This is a terminal state (for now) @@ -1005,6 +1011,13 @@ impl ServerImpl { fn is_seq_irq_asserted(&self) -> bool { self.sys.gpio_read(SEQ_IRQ) == 0 } + + fn ereport_current_state(&self) -> ereports::cpu::CurrentState { + ereports::cpu::CurrentState { + cur: self.state, + since: self.since, + } + } } impl idl::InOrderSequencerImpl for ServerImpl { diff --git a/drv/cpu-power-state/Cargo.toml b/drv/cpu-power-state/Cargo.toml index 58fcd88618..8060142ff6 100644 --- a/drv/cpu-power-state/Cargo.toml +++ b/drv/cpu-power-state/Cargo.toml @@ -9,6 +9,7 @@ zerocopy = { workspace = true } zerocopy-derive = { workspace = true } num-traits = { workspace = true } counters = { path = "../../lib/counters", features = ["derive"] } +microcbor = { path = "../../lib/microcbor", features = ["derive"], optional = true } [lib] test = false diff --git a/lib/ereports/Cargo.toml b/lib/ereports/Cargo.toml index 9b7f517c23..21c9f6ec94 100644 --- a/lib/ereports/Cargo.toml +++ b/lib/ereports/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] microcbor = { path = "../microcbor" } fixedstr = { path = "../fixedstr", features = ["microcbor"] } +drv-cpu-power-state = { path = "../../drv/cpu-power-state", features = ["microcbor"] } drv-i2c-devices = { path = "../../drv/i2c-devices" } [lints] diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs index 77fc793afb..37d3e22377 100644 --- a/lib/ereports/src/cpu.rs +++ b/lib/ereports/src/cpu.rs @@ -13,6 +13,7 @@ use microcbor::{Encode, EncodeFields}; pub struct Thermtrip { #[cbor(flatten)] pub cpu: &'static HostCpuRefdes, + pub state: CurrentState, } /// An ereport representing an AMD CPU's `SMERR_L` assertion. @@ -21,6 +22,7 @@ pub struct Thermtrip { pub struct Smerr { #[cbor(flatten)] pub cpu: &'static HostCpuRefdes, + pub state: CurrentState, } /// An ereport representing an unsupported CPU. @@ -33,6 +35,16 @@ pub struct UnsupportedCpu> { pub cpu_type: T, } +/// Represents the current CPU power state, and how long we have been in that state. +#[derive(Copy, Clone, Encode)] +pub struct CurrentState { + /// The current CPU power state. + pub cur: drv_cpu_power_state::PowerState, + /// The Hubris tick (in milliseconds) at which the transition to this state + /// occurred. + pub since: u64, +} + #[derive(Clone, EncodeFields)] pub struct HostCpuRefdes { /// On both Gimlet and Cosmo, the host CPU's refdes is `P0`. From f83f43a1632e481dbc6f9b0ccf602b51840f56f6 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 25 Feb 2026 12:30:49 -0800 Subject: [PATCH 05/17] reticulating --- drv/cosmo-seq-server/src/main.rs | 4 +++- lib/ereports/src/cpu.rs | 14 ++------------ lib/ereports/src/lib.rs | 10 ++++++++++ lib/ereports/src/pwr.rs | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index 7043ee9179..384d288078 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -1217,7 +1217,9 @@ const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, ereports::cpu::Thermtrip, ereports::cpu::Smerr, - ereports::cpu::UnsupportedCpu + ereports::cpu::UnsupportedCpu, + // CPU MAPO --- this must have a device ID in addition to a refdes. + ereports::WithDevId, 11>, ]; static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs index 37d3e22377..871b25182a 100644 --- a/lib/ereports/src/cpu.rs +++ b/lib/ereports/src/cpu.rs @@ -13,7 +13,7 @@ use microcbor::{Encode, EncodeFields}; pub struct Thermtrip { #[cbor(flatten)] pub cpu: &'static HostCpuRefdes, - pub state: CurrentState, + pub state: crate::pwr::CurrentState,, } /// An ereport representing an AMD CPU's `SMERR_L` assertion. @@ -22,7 +22,7 @@ pub struct Thermtrip { pub struct Smerr { #[cbor(flatten)] pub cpu: &'static HostCpuRefdes, - pub state: CurrentState, + pub state: crate::pwr::CurrentState, } /// An ereport representing an unsupported CPU. @@ -35,16 +35,6 @@ pub struct UnsupportedCpu> { pub cpu_type: T, } -/// Represents the current CPU power state, and how long we have been in that state. -#[derive(Copy, Clone, Encode)] -pub struct CurrentState { - /// The current CPU power state. - pub cur: drv_cpu_power_state::PowerState, - /// The Hubris tick (in milliseconds) at which the transition to this state - /// occurred. - pub since: u64, -} - #[derive(Clone, EncodeFields)] pub struct HostCpuRefdes { /// On both Gimlet and Cosmo, the host CPU's refdes is `P0`. diff --git a/lib/ereports/src/lib.rs b/lib/ereports/src/lib.rs index dd134eb56f..fe6537cdcd 100644 --- a/lib/ereports/src/lib.rs +++ b/lib/ereports/src/lib.rs @@ -8,3 +8,13 @@ pub mod cpu; pub mod pwr; + +/// A wrapper adding a device ID field to an ereport. This is to be used when an +/// ereport refers to a device where the `control-plane-agent` device ID is +/// different from the refdes of the device. +#[derive(Clone, microcbor::Encode)] +pub struct WithDevId, const DEVID_LEN: usize> { + pub dev_id: FixedStr<'static, DEVID_LEN>, + #[cbor(flatten)] + pub ereport: E, +} diff --git a/lib/ereports/src/pwr.rs b/lib/ereports/src/pwr.rs index 6899c3120b..92aa3e2cc8 100644 --- a/lib/ereports/src/pwr.rs +++ b/lib/ereports/src/pwr.rs @@ -28,6 +28,14 @@ pub struct Bmr491MitigationFailure { pub succeeded: bool, } +/// An ereport representing a mutually-assured power-off (MAPO) event. +#[derive(Clone, Encode)] +#[ereport(class = "hw.pwr.mapo", version = 0)] +pub struct Mapo { + pub refdes: FixedStr<'static, REFDES_LEN>, + pub state: crate::pwr::CurrentState, +} + /// PMBus status registers. #[derive(Copy, Clone, Default, Encode)] pub struct PmbusStatus { @@ -39,3 +47,13 @@ pub struct PmbusStatus { pub cml: Option, pub mfr: Option, } + +/// Represents the current power state, and how long we have been in that state. +#[derive(Copy, Clone, Encode)] +pub struct CurrentState { + /// The current CPU power state. + pub cur: drv_cpu_power_state::PowerState, + /// The Hubris tick (in milliseconds) at which the transition to this state + /// occurred. + pub since: u64, +} From 2da7f35b7af129ccaaa5849fa021c2969590ce81 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 09:15:54 -0800 Subject: [PATCH 06/17] oops there's no derive feature --- Cargo.lock | 2 ++ drv/cpu-power-state/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5c623b1af8..d848ace3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,7 @@ name = "drv-cpu-power-state" version = "0.1.0" dependencies = [ "counters", + "microcbor", "num-traits", "userlib", "zerocopy 0.8.27", @@ -2976,6 +2977,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" name = "ereports" version = "0.1.0" dependencies = [ + "drv-cpu-power-state", "drv-i2c-devices", "fixedstr", "microcbor", diff --git a/drv/cpu-power-state/Cargo.toml b/drv/cpu-power-state/Cargo.toml index 8060142ff6..badb1f7938 100644 --- a/drv/cpu-power-state/Cargo.toml +++ b/drv/cpu-power-state/Cargo.toml @@ -9,7 +9,7 @@ zerocopy = { workspace = true } zerocopy-derive = { workspace = true } num-traits = { workspace = true } counters = { path = "../../lib/counters", features = ["derive"] } -microcbor = { path = "../../lib/microcbor", features = ["derive"], optional = true } +microcbor = { path = "../../lib/microcbor", optional = true } [lib] test = false From 4d3c3daa36a1b4b7a5945f1f2c84e15b9c8f360b Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 09:16:34 -0800 Subject: [PATCH 07/17] oops --- lib/ereports/src/cpu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs index 871b25182a..fa87c775f2 100644 --- a/lib/ereports/src/cpu.rs +++ b/lib/ereports/src/cpu.rs @@ -13,7 +13,7 @@ use microcbor::{Encode, EncodeFields}; pub struct Thermtrip { #[cbor(flatten)] pub cpu: &'static HostCpuRefdes, - pub state: crate::pwr::CurrentState,, + pub state: crate::pwr::CurrentState, } /// An ereport representing an AMD CPU's `SMERR_L` assertion. From 9343353d805a8fa85bf8e9995aad7cfbb0617829 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 09:17:26 -0800 Subject: [PATCH 08/17] punt on MAPOs for a future PR --- drv/cosmo-seq-server/src/main.rs | 2 -- lib/ereports/src/lib.rs | 10 ---------- lib/ereports/src/pwr.rs | 8 -------- 3 files changed, 20 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index 384d288078..c0cb1119ee 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -1218,8 +1218,6 @@ const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ ereports::cpu::Thermtrip, ereports::cpu::Smerr, ereports::cpu::UnsupportedCpu, - // CPU MAPO --- this must have a device ID in addition to a refdes. - ereports::WithDevId, 11>, ]; static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = diff --git a/lib/ereports/src/lib.rs b/lib/ereports/src/lib.rs index fe6537cdcd..dd134eb56f 100644 --- a/lib/ereports/src/lib.rs +++ b/lib/ereports/src/lib.rs @@ -8,13 +8,3 @@ pub mod cpu; pub mod pwr; - -/// A wrapper adding a device ID field to an ereport. This is to be used when an -/// ereport refers to a device where the `control-plane-agent` device ID is -/// different from the refdes of the device. -#[derive(Clone, microcbor::Encode)] -pub struct WithDevId, const DEVID_LEN: usize> { - pub dev_id: FixedStr<'static, DEVID_LEN>, - #[cbor(flatten)] - pub ereport: E, -} diff --git a/lib/ereports/src/pwr.rs b/lib/ereports/src/pwr.rs index 92aa3e2cc8..46fd4ae606 100644 --- a/lib/ereports/src/pwr.rs +++ b/lib/ereports/src/pwr.rs @@ -28,14 +28,6 @@ pub struct Bmr491MitigationFailure { pub succeeded: bool, } -/// An ereport representing a mutually-assured power-off (MAPO) event. -#[derive(Clone, Encode)] -#[ereport(class = "hw.pwr.mapo", version = 0)] -pub struct Mapo { - pub refdes: FixedStr<'static, REFDES_LEN>, - pub state: crate::pwr::CurrentState, -} - /// PMBus status registers. #[derive(Copy, Clone, Default, Encode)] pub struct PmbusStatus { From 3b5852b3651aac8905c45302ecd41f52222f5e22 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 09:39:55 -0800 Subject: [PATCH 09/17] give gimlet the same treatment --- drv/cosmo-seq-server/src/main.rs | 6 +- drv/gimlet-seq-server/src/main.rs | 144 ++++++++++++++++++++--------- drv/gimlet-seq-server/src/vcore.rs | 26 ++---- 3 files changed, 108 insertions(+), 68 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index c0cb1119ee..a656149953 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -192,7 +192,7 @@ fn main() -> ! { let mut ereporter = Ereporter { packrat, - ereport_buf, + buf: ereport_buf, }; // @@ -1239,7 +1239,7 @@ struct CpuTypeBits { /// argument. pub(crate) struct Ereporter { packrat: task_packrat_api::Packrat, - ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], + buf: &'static mut [u8; EREPORT_BUF_LEN], } impl Ereporter { @@ -1249,7 +1249,7 @@ impl Ereporter { ) { let eresult = self .packrat - .deliver_microcbor_ereport(&ereport, &mut self.ereport_buf[..]); + .deliver_microcbor_ereport(&ereport, &mut self.buf[..]); match eresult { Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { diff --git a/drv/gimlet-seq-server/src/main.rs b/drv/gimlet-seq-server/src/main.rs index 110cb0fb7d..024ede540e 100644 --- a/drv/gimlet-seq-server/src/main.rs +++ b/drv/gimlet-seq-server/src/main.rs @@ -203,26 +203,18 @@ fn main() -> ! { struct ServerImpl { state: PowerState, + /// The Hubris tick at which we transitioned to the current state. + since: u64, sys: sys_api::Sys, seq: seq_spi::SequencerFpga, jefe: Jefe, hf: hf_api::HostFlash, vcore: vcore::VCore, deadline: u64, - // Buffer for encoding ereports. This is a static so that it's not on the - // stack when handling interrupts. - ereport_buf: &'static mut [u8; EREPORT_BUF_LEN], + ereporter: Ereporter, } const TIMER_INTERVAL: u32 = 10; -const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ - ereports::pwr::PmbusAlert< - // 9 is the maximum length rail name used in this module (`VDD_VCORE`) - FixedStr<'static, 9>, - { REFDES_LEN }, - >, - ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, -]; impl ServerImpl { fn init( @@ -459,13 +451,16 @@ impl ServerImpl { hl::sleep_for(1); } - let ereport_buf = { + let packrat = Packrat::from(PACKRAT.get_task_id()); + let mut ereporter = { use static_cell::ClaimOnceCell; static EREPORT_BUF: ClaimOnceCell<[u8; EREPORT_BUF_LEN]> = ClaimOnceCell::new([0; EREPORT_BUF_LEN]); - EREPORT_BUF.claim() + Ereporter { + buf: EREPORT_BUF.claim(), + packrat: packrat.clone(), + } }; - let packrat = Packrat::from(PACKRAT.get_task_id()); // // Apply the configuration mitigation on the BMR491, if required. This @@ -500,7 +495,7 @@ impl ServerImpl { last_cause, succeeded, }; - try_send_ereport(&packrat, &mut ereport_buf[..], &ereport); + ereporter.try_send_ereport(&ereport); } } @@ -548,13 +543,14 @@ impl ServerImpl { let mut server = Self { state: PowerState::A2, + since: 0, // we have been in A2 since we booted :) sys: sys.clone(), seq, jefe, hf, deadline: 0, - vcore: vcore::VCore::new(sys, packrat, &device, rail), - ereport_buf, + vcore: vcore::VCore::new(sys, &device, rail), + ereporter, }; // Power on, unless suppressed by the `stay-in-a2` feature @@ -594,7 +590,7 @@ impl NotificationHandler for ServerImpl { fn handle_notification(&mut self, bits: userlib::NotificationBits) { if bits.check_notification_mask(self.vcore.mask()) { - self.vcore.handle_notification(self.ereport_buf); + self.vcore.handle_notification(&mut self.ereporter); } if !bits.has_timer_fired(notifications::TIMER_MASK) { @@ -610,6 +606,7 @@ impl NotificationHandler for ServerImpl { }); if self.state == PowerState::A0 || self.state == PowerState::A0PlusHP { + let now = sys_get_timer().now; // // The first order of business is to check if sequencer saw a // falling edge on PWROK (denoting a reset) or a THERMTRIP. If it @@ -617,8 +614,8 @@ impl NotificationHandler for ServerImpl { // if both are indicated, we will clear both conditions -- but // land in A0Thermtrip). // - self.check_reset(ifr); - self.check_thermtrip(ifr); + self.check_reset(ifr, now); + self.check_thermtrip(ifr, now); // // Now we need to check NIC_PWREN_L to assure that our power state @@ -636,7 +633,7 @@ impl NotificationHandler for ServerImpl { self.seq .clear_bytes(Addr::NIC_CTRL, &[cld_rst]) .unwrap_lite(); - self.update_state_internal(PowerState::A0PlusHP); + self.update_state_internal(PowerState::A0PlusHP, now); } (PowerState::A0PlusHP, true) => { @@ -668,7 +665,10 @@ impl NotificationHandler for ServerImpl { self.seq .set_bytes(Addr::NIC_CTRL, &[cld_rst]) .unwrap_lite(); - self.update_state_internal(PowerState::A0); + self.update_state_internal( + PowerState::A0, + sys_get_timer().now, + ); } (PowerState::A0, true) | (PowerState::A0PlusHP, false) => { @@ -739,8 +739,9 @@ where } impl ServerImpl { - fn update_state_internal(&mut self, state: PowerState) { + fn update_state_internal(&mut self, state: PowerState, now: u64) { ringbuf_entry!(Trace::UpdateState(state)); + self.since = now; self.state = state; self.jefe.set_state(state as u32); } @@ -878,6 +879,15 @@ impl ServerImpl { // to be low (VSS on Type-0/Type-1/Type-2). // if !coretype || !sp3r1 || sp3r2 { + self.ereporter.try_send_ereport( + &ereports::cpu::UnsupportedCpu { + cpu: &HOST_CPU_REFDES, + cpu_type: CpuTypeBits { + coretype, + sp3rx: [sp3r1, sp3r2], + }, + }, + ); return Err(self.a0_failure(SeqError::UnrecognizedCPU)); } @@ -957,11 +967,10 @@ impl ServerImpl { // Using wrapping_sub here because the timer is monotonic, so // we, the programmers, know that now > start. rustc, the // compiler, is not aware of this. - ringbuf_entry!(Trace::A0( - (sys_get_timer().now.wrapping_sub(start)) as u16 - )); + let now = sys_get_timer().now; + ringbuf_entry!(Trace::A0((now.wrapping_sub(start)) as u16)); - self.update_state_internal(PowerState::A0); + self.update_state_internal(PowerState::A0, now); Ok(Transition::Changed) } @@ -1007,7 +1016,7 @@ impl ServerImpl { return Err(SeqError::MuxToSPFailed); } - self.update_state_internal(PowerState::A2); + self.update_state_internal(PowerState::A2, sys_get_timer().now); ringbuf_entry_v3p3_sys_a0_vout(); ringbuf_entry!(Trace::A2); @@ -1084,12 +1093,16 @@ impl ServerImpl { // seen it (and knowing that the FPGA has already taken care of the // time-critical bits to assure that we don't melt!). // - fn check_thermtrip(&mut self, ifr: u8) { + fn check_thermtrip(&mut self, ifr: u8, now: u64) { let thermtrip = Reg::IFR::THERMTRIP; if ifr & thermtrip != 0 { self.seq.clear_bytes(Addr::IFR, &[thermtrip]).unwrap_lite(); - self.update_state_internal(PowerState::A0Thermtrip); + self.ereporter.try_send_ereport(&ereports::cpu::Thermtrip { + cpu: &HOST_CPU_REFDES, + state: self.ereport_current_state(), + }); + self.update_state_internal(PowerState::A0Thermtrip, now); } } @@ -1101,7 +1114,7 @@ impl ServerImpl { // of RESET_L. If we have seen a host reset, we send ourselves to // A0Reset. // - fn check_reset(&mut self, ifr: u8) { + fn check_reset(&mut self, ifr: u8, now: u64) { let pwrok_fedge = Reg::IFR::AMD_PWROK_FEDGE; if ifr & pwrok_fedge != 0 { @@ -1125,7 +1138,7 @@ impl ServerImpl { let mask = pwrok_fedge | Reg::IFR::AMD_RSTN_FEDGE; self.seq.clear_bytes(Addr::IFR, &[mask]).unwrap_lite(); - self.update_state_internal(PowerState::A0Reset); + self.update_state_internal(PowerState::A0Reset, now); } } @@ -1146,6 +1159,13 @@ impl ServerImpl { _ => None, } } + + fn ereport_current_state(&self) -> ereports::pwr::CurrentState { + ereports::pwr::CurrentState { + cur: self.state, + since: self.since, + } + } } impl idl::InOrderSequencerImpl for ServerImpl { @@ -1576,23 +1596,57 @@ cfg_if::cfg_if! { } } -fn try_send_ereport( - packrat: &packrat_api::Packrat, - ereport_buf: &mut [u8], - ereport: &impl microcbor::StaticCborLen, -) { - let eresult = packrat.deliver_microcbor_ereport(&ereport, ereport_buf); - match eresult { - Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), - Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { - ringbuf_entry!(Trace::EreportLost(len, err)) - } - Err(task_packrat_api::EreportEncodeError::Encoder(_)) => { - ringbuf_entry!(Trace::EreportTooBig) +//////////////////////////////////////////////////////////////////////////////// + +const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ + ereports::pwr::PmbusAlert, { REFDES_LEN }>, + ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, + ereports::cpu::Thermtrip, + ereports::cpu::UnsupportedCpu, +]; + +static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = + ereports::cpu::HostCpuRefdes { + refdes: fixedstr::FixedString::from_str("P0"), + dev_id: fixedstr::FixedString::from_str("sp3-host-cpu"), + }; + +#[derive(Clone, microcbor::EncodeFields)] +struct CpuTypeBits { + coretype: bool, + sp3rx: [bool; 2], +} + +/// This is just the Packrat API handle and the ereport buffer bundled together +/// in one thing so that it can be passed into various places as a single +/// argument. +pub(crate) struct Ereporter { + packrat: task_packrat_api::Packrat, + buf: &'static mut [u8; EREPORT_BUF_LEN], +} + +impl Ereporter { + pub(crate) fn try_send_ereport( + &mut self, + ereport: &impl microcbor::StaticCborLen, + ) { + let eresult = self + .packrat + .deliver_microcbor_ereport(&ereport, &mut self.buf[..]); + match eresult { + Ok(len) => ringbuf_entry!(Trace::EreportSent(len)), + Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { + ringbuf_entry!(Trace::EreportLost(len, err)) + } + Err(task_packrat_api::EreportEncodeError::Encoder(_)) => { + ringbuf_entry!(Trace::EreportTooBig) + } } } } +//////////////////////////////////////////////////////////////////////////////// + mod idl { use super::StateChangeReason; diff --git a/drv/gimlet-seq-server/src/vcore.rs b/drv/gimlet-seq-server/src/vcore.rs index e829480fdb..0f68ec90db 100644 --- a/drv/gimlet-seq-server/src/vcore.rs +++ b/drv/gimlet-seq-server/src/vcore.rs @@ -28,6 +28,7 @@ use super::{retry_i2c_txn, I2cTxn}; /// A2 to A0 transition to clear faults. /// use crate::gpio_irq_pins::VCORE_TO_SP_ALERT_L; +use crate::Ereporter; use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_i2c_devices::raa229618::Raa229618; use drv_stm32xx_sys_api as sys_api; @@ -35,14 +36,12 @@ use ereports::pwr::{PmbusAlert, PmbusStatus}; use fixedstr::FixedStr; use ringbuf::*; use sys_api::IrqControl; -use task_packrat_api as packrat_api; use userlib::{sys_get_timer, units}; pub struct VCore { device: Raa229618, faulted: bool, sys: sys_api::Sys, - packrat: packrat_api::Packrat, } #[derive(Copy, Clone, PartialEq)] @@ -136,17 +135,11 @@ cfg_if::cfg_if! { } impl VCore { - pub fn new( - sys: &sys_api::Sys, - packrat: packrat_api::Packrat, - device: &I2cDevice, - rail: u8, - ) -> Self { + pub fn new(sys: &sys_api::Sys, device: &I2cDevice, rail: u8) -> Self { Self { device: Raa229618::new(device, rail), faulted: false, sys: sys.clone(), - packrat, } } @@ -181,10 +174,7 @@ impl VCore { Ok(()) } - pub fn handle_notification( - &mut self, - ereport_buf: &mut [u8; crate::EREPORT_BUF_LEN], - ) { + pub fn handle_notification(&mut self, ereporter: &mut Ereporter) { let now = sys_get_timer().now; let asserted = self.is_pmalert_asserted(); @@ -197,7 +187,7 @@ impl VCore { // Don't produce another ereport if PMALERT_L was already asserted // without being deasserted. if !self.faulted { - self.read_pmbus_status(now, ereport_buf); + self.read_pmbus_status(now, ereporter); } // Clear the fault now so that PMALERT_L is reasserted if a // subsequent fault occurs. Note that if the fault *condition* @@ -233,11 +223,7 @@ impl VCore { self.sys.gpio_read(VCORE_TO_SP_ALERT_L) == 0 } - fn read_pmbus_status( - &self, - now: u64, - ereport_buf: &mut [u8; crate::EREPORT_BUF_LEN], - ) { + fn read_pmbus_status(&self, now: u64, ereporter: &mut Ereporter) { use pmbus::commands::raa229618::STATUS_WORD; // Read PMBus status registers and prepare an ereport. @@ -352,7 +338,7 @@ impl VCore { pwr_good, pmbus_status: status, }; - crate::try_send_ereport(&self.packrat, &mut ereport_buf[..], &ereport); + ereporter.try_send_ereport(&ereport); // TODO(eliza): if POWER_GOOD has been deasserted, we should produce a // subsequent ereport for that. From 6495febc72b79e604cc544c31440ba0311bdd975 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 09:57:15 -0800 Subject: [PATCH 10/17] blargh --- drv/cosmo-seq-server/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index a656149953..cec5711b4e 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -1012,8 +1012,8 @@ impl ServerImpl { self.sys.gpio_read(SEQ_IRQ) == 0 } - fn ereport_current_state(&self) -> ereports::cpu::CurrentState { - ereports::cpu::CurrentState { + fn ereport_current_state(&self) -> ereports::pwr::CurrentState { + ereports::pwr::CurrentState { cur: self.state, since: self.since, } From 6717b05093125172d0649aae05a0598f16229979 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 13:13:15 -0800 Subject: [PATCH 11/17] get rid of unused `drv-i2c-devices` dep --- Cargo.lock | 1 - lib/ereports/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d848ace3d7..7e6e49c10e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2978,7 +2978,6 @@ name = "ereports" version = "0.1.0" dependencies = [ "drv-cpu-power-state", - "drv-i2c-devices", "fixedstr", "microcbor", ] diff --git a/lib/ereports/Cargo.toml b/lib/ereports/Cargo.toml index 21c9f6ec94..03dab0b388 100644 --- a/lib/ereports/Cargo.toml +++ b/lib/ereports/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" microcbor = { path = "../microcbor" } fixedstr = { path = "../fixedstr", features = ["microcbor"] } drv-cpu-power-state = { path = "../../drv/cpu-power-state", features = ["microcbor"] } -drv-i2c-devices = { path = "../../drv/i2c-devices" } [lints] workspace = true From 2090a1b461e815d02e5daa838f5eecacebed13f4 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 15:03:01 -0800 Subject: [PATCH 12/17] Revert "get rid of unused `drv-i2c-devices` dep" This reverts commit 6717b05093125172d0649aae05a0598f16229979. I forgot that we actaully do need this. --- Cargo.lock | 1 + lib/ereports/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7e6e49c10e..d848ace3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2978,6 +2978,7 @@ name = "ereports" version = "0.1.0" dependencies = [ "drv-cpu-power-state", + "drv-i2c-devices", "fixedstr", "microcbor", ] diff --git a/lib/ereports/Cargo.toml b/lib/ereports/Cargo.toml index 03dab0b388..21c9f6ec94 100644 --- a/lib/ereports/Cargo.toml +++ b/lib/ereports/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" microcbor = { path = "../microcbor" } fixedstr = { path = "../fixedstr", features = ["microcbor"] } drv-cpu-power-state = { path = "../../drv/cpu-power-state", features = ["microcbor"] } +drv-i2c-devices = { path = "../../drv/i2c-devices" } [lints] workspace = true From af575d9d2c31b291e8fb15e9743c69c8aceebd05 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 26 Feb 2026 15:08:36 -0800 Subject: [PATCH 13/17] fix build (real) --- lib/ereports/Cargo.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/ereports/Cargo.toml b/lib/ereports/Cargo.toml index 21c9f6ec94..e416c2a5d6 100644 --- a/lib/ereports/Cargo.toml +++ b/lib/ereports/Cargo.toml @@ -11,3 +11,12 @@ drv-i2c-devices = { path = "../../drv/i2c-devices" } [lints] workspace = true + +# This section is here to discourage RLS/rust-analyzer from doing test builds, +# since test builds don't work for cross compilation, and this crate dependencies +# on `userlib` (transitively, via `drv-i2c-devices`), which won't build on non-ARM +# targets. +[lib] +test = false +doctest = false +bench = false From 092f4949a3e2a074f6bf4412edd45a070e71e399 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 27 Feb 2026 11:10:59 -0800 Subject: [PATCH 14/17] psc_seq: also use shared pmbus registers types --- Cargo.lock | 1 + drv/psc-seq-server/Cargo.toml | 1 + drv/psc-seq-server/src/main.rs | 12 +----------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d848ace3d7..edf87e144e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2045,6 +2045,7 @@ dependencies = [ "drv-packrat-vpd-loader", "drv-psc-seq-api", "drv-stm32xx-sys-api", + "ereports", "fixedstr", "idol", "microcbor", diff --git a/drv/psc-seq-server/Cargo.toml b/drv/psc-seq-server/Cargo.toml index e615a77c30..389aca2707 100644 --- a/drv/psc-seq-server/Cargo.toml +++ b/drv/psc-seq-server/Cargo.toml @@ -14,6 +14,7 @@ task-packrat-api = { path = "../../task/packrat-api", features = ["microcbor"] } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } ringbuf = { path = "../../lib/ringbuf", features = ["counters"] } counters = { path = "../../lib/counters" } +ereports = { path = "../../lib/ereports" } static-cell = { path = "../../lib/static-cell" } microcbor.path = "../../lib/microcbor" fixedstr = { path = "../../lib/fixedstr", features = ["microcbor"] } diff --git a/drv/psc-seq-server/src/main.rs b/drv/psc-seq-server/src/main.rs index f0778433cf..56f6e8e99f 100644 --- a/drv/psc-seq-server/src/main.rs +++ b/drv/psc-seq-server/src/main.rs @@ -1191,6 +1191,7 @@ include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); mod ereport { use super::*; + pub(crate) use ereports::pwr::PmbusStatus; #[derive(Copy, Clone, Eq, PartialEq, microcbor::Encode)] pub(super) enum Class { @@ -1213,16 +1214,5 @@ mod ereport { pub(super) pmbus_status: Option, } - #[derive(Copy, Clone, Default, microcbor::Encode)] - pub(super) struct PmbusStatus { - pub(super) word: Option, - pub(super) input: Option, - pub(super) iout: Option, - pub(super) vout: Option, - pub(super) temp: Option, - pub(super) cml: Option, - pub(super) mfr: Option, - } - pub(super) type Ereport = task_packrat_api::Ereport; } From 981c9bdb2d7b9d4bdfb4ce1b42fe7dab585b259f Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 10 Mar 2026 11:04:19 -0700 Subject: [PATCH 15/17] tweaks to `CurrentState` --- drv/cosmo-seq-server/src/main.rs | 2 +- drv/gimlet-seq-server/src/main.rs | 2 +- lib/ereports/src/pwr.rs | 14 ++++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index cec5711b4e..bb34c1c3c7 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -1015,7 +1015,7 @@ impl ServerImpl { fn ereport_current_state(&self) -> ereports::pwr::CurrentState { ereports::pwr::CurrentState { cur: self.state, - since: self.since, + since_ms: self.since, } } } diff --git a/drv/gimlet-seq-server/src/main.rs b/drv/gimlet-seq-server/src/main.rs index 024ede540e..9113f35818 100644 --- a/drv/gimlet-seq-server/src/main.rs +++ b/drv/gimlet-seq-server/src/main.rs @@ -1163,7 +1163,7 @@ impl ServerImpl { fn ereport_current_state(&self) -> ereports::pwr::CurrentState { ereports::pwr::CurrentState { cur: self.state, - since: self.since, + since_ms: self.since, } } } diff --git a/lib/ereports/src/pwr.rs b/lib/ereports/src/pwr.rs index 46fd4ae606..d0614515c7 100644 --- a/lib/ereports/src/pwr.rs +++ b/lib/ereports/src/pwr.rs @@ -40,12 +40,18 @@ pub struct PmbusStatus { pub mfr: Option, } -/// Represents the current power state, and how long we have been in that state. +/// Represents the current power state when an event occurred, and the Hubris +/// timestamp at which the system transitioned to that state. +/// +/// When the event represented by an ereport is one which transitions the system +/// to a different state, the `CurrentState` represents the *prior* state, i.e. +/// the one at the time the event occurred, *not* the new state the ssytem will +/// transition to. #[derive(Copy, Clone, Encode)] pub struct CurrentState { /// The current CPU power state. pub cur: drv_cpu_power_state::PowerState, - /// The Hubris tick (in milliseconds) at which the transition to this state - /// occurred. - pub since: u64, + /// The Hubris uptime, in milliseconds, at which the transition to this + /// state occurred. + pub since_ms: u64, } From f1cd8e479f72b7a74d1dd5de9d832f87d7e5ba03 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 10 Mar 2026 11:22:23 -0700 Subject: [PATCH 16/17] tweaks to coretype as per https://github.com/oxidecomputer/hubris/pull/2399#discussion_r2908582151 --- drv/cosmo-seq-server/src/main.rs | 22 ++++++++-------------- drv/gimlet-seq-server/src/main.rs | 21 ++++++++++----------- lib/ereports/src/cpu.rs | 16 +++++++++++----- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index bb34c1c3c7..befd65d479 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -623,11 +623,13 @@ impl ServerImpl { self.seq.power_ctrl.modify(|m| m.set_a0_en(false)); let ereport = ereports::cpu::UnsupportedCpu { cpu: &HOST_CPU_REFDES, - cpu_type: CpuTypeBits { - coretype: [coretype0, coretype1, coretype2], - sp5rx: [sp5r1, sp5r2, sp5r3, sp5r4], - coretype_ok, - sp5rx_ok, + coretype: ereports::cpu::CpuTypeBits { + bits: [coretype0, coretype1, coretype2], + ok: coretype_ok, + }, + rev: ereports::cpu::CpuTypeBits { + bits: [sp5r1, sp5r2, sp5r3, sp5r4], + ok: sp5rx_ok, }, }; self.ereporter.try_send_ereport(&ereport); @@ -1217,7 +1219,7 @@ const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, ereports::cpu::Thermtrip, ereports::cpu::Smerr, - ereports::cpu::UnsupportedCpu, + ereports::cpu::UnsupportedCpu<3, 4>, ]; static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = @@ -1226,14 +1228,6 @@ static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = dev_id: fixedstr::FixedString::from_str("sp5-host-cpu"), }; -#[derive(Clone, microcbor::EncodeFields)] -struct CpuTypeBits { - coretype: [bool; 3], - sp5rx: [bool; 4], - coretype_ok: bool, - sp5rx_ok: bool, -} - /// This is just the Packrat API handle and the ereport buffer bundled together /// in one thing so that it can be passed into various places as a single /// argument. diff --git a/drv/gimlet-seq-server/src/main.rs b/drv/gimlet-seq-server/src/main.rs index 9113f35818..c8099ada65 100644 --- a/drv/gimlet-seq-server/src/main.rs +++ b/drv/gimlet-seq-server/src/main.rs @@ -878,13 +878,18 @@ impl ServerImpl { // be high (not connected on Type-0/Type-1/Type-2), and SP3R2 // to be low (VSS on Type-0/Type-1/Type-2). // - if !coretype || !sp3r1 || sp3r2 { + let rev_ok = sp3r1 && !sp3r2; + if !coretype || !rev_ok { self.ereporter.try_send_ereport( &ereports::cpu::UnsupportedCpu { cpu: &HOST_CPU_REFDES, - cpu_type: CpuTypeBits { - coretype, - sp3rx: [sp3r1, sp3r2], + coretype: ereports::cpu::CpuTypeBits { + bits: [coretype], + ok: coretype, + }, + rev: ereports::cpu::CpuTypeBits { + bits: [sp3r1, sp3r2], + ok: rev_ok, }, }, ); @@ -1602,7 +1607,7 @@ const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ ereports::pwr::PmbusAlert, { REFDES_LEN }>, ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, ereports::cpu::Thermtrip, - ereports::cpu::UnsupportedCpu, + ereports::cpu::UnsupportedCpu<1, 2>, ]; static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = @@ -1611,12 +1616,6 @@ static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = dev_id: fixedstr::FixedString::from_str("sp3-host-cpu"), }; -#[derive(Clone, microcbor::EncodeFields)] -struct CpuTypeBits { - coretype: bool, - sp3rx: [bool; 2], -} - /// This is just the Packrat API handle and the ereport buffer bundled together /// in one thing so that it can be passed into various places as a single /// argument. diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs index fa87c775f2..72e98d1e7a 100644 --- a/lib/ereports/src/cpu.rs +++ b/lib/ereports/src/cpu.rs @@ -25,14 +25,20 @@ pub struct Smerr { pub state: crate::pwr::CurrentState, } -/// An ereport representing an unsupported CPU. +/// An ereport representing an unsupported AMD CPU. #[derive(Clone, Encode)] -#[ereport(class = "hw.cpu.unsup", version = 0)] -pub struct UnsupportedCpu> { +#[ereport(class = "hw.cpu.amd.unsup", version = 0)] +pub struct UnsupportedCpu { #[cbor(flatten)] pub cpu: &'static HostCpuRefdes, - #[cbor(flatten)] - pub cpu_type: T, + pub coretype: CpuTypeBits, + pub rev: CpuTypeBits, +} + +#[derive(Clone, Encode)] +pub struct CpuTypeBits { + pub bits: [bool; BITS], + pub ok: bool, } #[derive(Clone, EncodeFields)] From f7fcd2e7255bdcdb3bf4404185cb2220efeeefc5 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 10 Mar 2026 12:31:28 -0700 Subject: [PATCH 17/17] also wire up CPU Missing ereports while we're here --- drv/cosmo-seq-server/src/main.rs | 6 ++++++ drv/gimlet-seq-server/src/main.rs | 6 ++++++ lib/ereports/src/cpu.rs | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs index befd65d479..51e479a98d 100644 --- a/drv/cosmo-seq-server/src/main.rs +++ b/drv/cosmo-seq-server/src/main.rs @@ -568,6 +568,11 @@ impl ServerImpl { if !present { ringbuf_entry!(Trace::CPUNotPresent); + self.ereporter.try_send_ereport( + &ereports::cpu::CpuMissing { + cpu: &HOST_CPU_REFDES, + }, + ); err = CpuSeqError::CPUNotPresent; break; } @@ -1220,6 +1225,7 @@ const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ ereports::cpu::Thermtrip, ereports::cpu::Smerr, ereports::cpu::UnsupportedCpu<3, 4>, + ereports::cpu::CpuMissing, ]; static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = diff --git a/drv/gimlet-seq-server/src/main.rs b/drv/gimlet-seq-server/src/main.rs index c8099ada65..df80a84ce0 100644 --- a/drv/gimlet-seq-server/src/main.rs +++ b/drv/gimlet-seq-server/src/main.rs @@ -859,6 +859,11 @@ impl ServerImpl { ringbuf_entry!(Trace::CPUPresent(present)); if !present { + self.ereporter.try_send_ereport( + &ereports::cpu::CpuMissing { + cpu: &HOST_CPU_REFDES, + }, + ); return Err(self.a0_failure(SeqError::CPUNotPresent)); } @@ -1608,6 +1613,7 @@ const EREPORT_BUF_LEN: usize = microcbor::max_cbor_len_for![ ereports::pwr::Bmr491MitigationFailure<{ REFDES_LEN }>, ereports::cpu::Thermtrip, ereports::cpu::UnsupportedCpu<1, 2>, + ereports::cpu::CpuMissing, ]; static HOST_CPU_REFDES: ereports::cpu::HostCpuRefdes = diff --git a/lib/ereports/src/cpu.rs b/lib/ereports/src/cpu.rs index 72e98d1e7a..a4c6f44675 100644 --- a/lib/ereports/src/cpu.rs +++ b/lib/ereports/src/cpu.rs @@ -35,6 +35,14 @@ pub struct UnsupportedCpu { pub rev: CpuTypeBits, } +/// An ereport representing a non- +#[derive(Clone, Encode)] +#[ereport(class = "hw.cpu.missing", version = 0)] +pub struct CpuMissing { + #[cbor(flatten)] + pub cpu: &'static HostCpuRefdes, +} + #[derive(Clone, Encode)] pub struct CpuTypeBits { pub bits: [bool; BITS],