From be1d5e290211d21f0da2916828e3e1e4ab2e74a1 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 26 Jan 2026 10:59:40 -0800 Subject: [PATCH 1/9] sketching some ereports --- task/thermal/src/ereport.rs | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 task/thermal/src/ereport.rs diff --git a/task/thermal/src/ereport.rs b/task/thermal/src/ereport.rs new file mode 100644 index 000000000..2afc2ceb4 --- /dev/null +++ b/task/thermal/src/ereport.rs @@ -0,0 +1,60 @@ +// 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 crate::i2c_config::MAX_COMPONENT_ID_LEN; + +pub(crate) const EREPORT_BUF_SIZE: usize = + microcbor::max_cbor_len_for![Ereport,]; + +#[derive(microcbor::Encode)] +#[cbor(variant_id = "k")] +pub enum Ereport { + /// A component exceeded its critical threshold. + #[cbor(rename = "hw.temp.crit")] + ComponentCritical { + #[cbor(rename = "v")] + version: u8, + refdes: FixedStr<'static, MAX_COMPONENT_ID_LEN>, + sensor_id: u8, + temp_c: f32, + }, + /// A component exceeded its power-down threshold. + #[cbor(rename = "hw.temp.a2.thresh")] + ComponentShutdown { + #[cbor(rename = "v")] + version: u8, + refdes: FixedStr<'static, MAX_COMPONENT_ID_LEN>, + sensor_id: u8, + temp_c: f32, + overheat_ms: Option, + }, + /// The system is shutting down due to exceeding the critical threshold + /// timeout. + #[cbor(rename = "hw.temp.a2.timeout")] + TimeoutShutdown { + #[cbor(rename = "v")] + version: u8, + overheat_ms: OverheatDurations, + }, + /// All temperatures have returned to nominal. + #[cbor(rename = "hw.temp.ok")] + Nominal { + #[cbor(rename = "v")] + version: u8, + overheat_ms: OverheatDurations, + }, + #[cbor(rename = "hw.temp.readerr")] + SensorError { + #[cbor(rename = "v")] + version: u8, + refdes: FixedStr<'static, MAX_COMPONENT_ID_LEN>, + sensor_id: u8, + }, +} + +#[derive(microcbor::Encode)] +pub struct OverheatDurations { + pub crit: u64, + pub total: u64, +} From 550562550da7bf170a79754f21d03afd4552f3ac Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 26 Jan 2026 11:37:56 -0800 Subject: [PATCH 2/9] reticulating --- task/thermal/src/control.rs | 7 ++++-- task/thermal/src/ereport.rs | 43 ++++++++++++++++++++++++++++++++++--- task/thermal/src/main.rs | 36 ++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/task/thermal/src/control.rs b/task/thermal/src/control.rs index bc636f6bc..0c53f2576 100644 --- a/task/thermal/src/control.rs +++ b/task/thermal/src/control.rs @@ -4,7 +4,7 @@ use crate::{ bsp::{self, Bsp, PowerBitmask}, - Fan, ThermalError, Trace, + ereport, Fan, ThermalError, Trace, }; use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_i2c_devices::{ @@ -1088,7 +1088,10 @@ impl<'a> ThermalControl<'a> { /// Returns an error if the control loop failed to read critical sensors; /// the caller should set us to some kind of fail-safe mode if this /// occurs. - pub fn run_control(&mut self) -> Result<(), ThermalError> { + pub fn run_control( + &mut self, + ereports: &mut ereport::Ereporter, + ) -> Result<(), ThermalError> { let now_ms = sys_get_timer().now; // When the power mode changes, we may require a new set of sensors to diff --git a/task/thermal/src/ereport.rs b/task/thermal/src/ereport.rs index 2afc2ceb4..b2a2e9799 100644 --- a/task/thermal/src/ereport.rs +++ b/task/thermal/src/ereport.rs @@ -3,10 +3,47 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::i2c_config::MAX_COMPONENT_ID_LEN; +use crate::Trace; +use fixedstr::FixedStr; +use ringbuf::ringbuf_entry_root; +use task_packrat_api::Packrat; +use userlib::task_slot; + +task_slot!(PACKRAT, packrat); pub(crate) const EREPORT_BUF_SIZE: usize = microcbor::max_cbor_len_for![Ereport,]; +pub(crate) struct Ereporter { + buf: &'static mut [u8; EREPORT_BUF_SIZE], + packrat: Packrat, +} + +impl Ereporter { + pub(crate) fn claim_static_resources() -> Self { + static BUF: static_cell::ClaimOnceCell<[u8; EREPORT_BUF_SIZE]> = + static_cell::ClaimOnceCell::new([0u8; EREPORT_BUF_SIZE]); + + Self { + buf: BUF.claim().unwrap(), + packrat: Packrat::from(PACKRAT.get_task_id()), + } + } + + pub(crate) fn deliver_ereport(&mut self, ereport: &Ereport) { + let eresult = self.packrat.encode_ereport(&ereport, self.buf); + match eresult { + Ok(len) => ringbuf_entry_root!(Trace::EreportSent { len }), + Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { + ringbuf_entry_root!(Trace::EreportLost { len, err }) + } + Err(task_packrat_api::EreportEncodeError::Encoder(_)) => { + ringbuf_entry_root!(Trace::EreportTooBig) + } + } + } +} + #[derive(microcbor::Encode)] #[cbor(variant_id = "k")] pub enum Ereport { @@ -15,7 +52,7 @@ pub enum Ereport { ComponentCritical { #[cbor(rename = "v")] version: u8, - refdes: FixedStr<'static, MAX_COMPONENT_ID_LEN>, + refdes: FixedStr<'static, { MAX_COMPONENT_ID_LEN }>, sensor_id: u8, temp_c: f32, }, @@ -24,7 +61,7 @@ pub enum Ereport { ComponentShutdown { #[cbor(rename = "v")] version: u8, - refdes: FixedStr<'static, MAX_COMPONENT_ID_LEN>, + refdes: FixedStr<'static, { MAX_COMPONENT_ID_LEN }>, sensor_id: u8, temp_c: f32, overheat_ms: Option, @@ -48,7 +85,7 @@ pub enum Ereport { SensorError { #[cbor(rename = "v")] version: u8, - refdes: FixedStr<'static, MAX_COMPONENT_ID_LEN>, + refdes: FixedStr<'static, { MAX_COMPONENT_ID_LEN }>, sensor_id: u8, }, } diff --git a/task/thermal/src/main.rs b/task/thermal/src/main.rs index d92dd6380..ae9cb2983 100644 --- a/task/thermal/src/main.rs +++ b/task/thermal/src/main.rs @@ -116,6 +116,18 @@ enum Trace { RemovedDynamicInput(usize), SetFanWatchdogOk, SetFanWatchdogError(ThermalError), + #[cfg(feature = "ereport")] + EreportSent { + len: usize, + }, + #[cfg(feature = "ereport")] + EreportLost { + len: usize, + #[count(children)] + err: task_packrat_api::EreportWriteError, + }, + #[cfg(feature = "ereport")] + EreportTooBig, } counted_ringbuf!(Trace, 32, Trace::None); @@ -126,6 +138,7 @@ struct ServerImpl<'a> { control: ThermalControl<'a>, deadline: u64, runtime: u64, + ereporter: ereport::Ereporter, } const TIMER_INTERVAL: u64 = 1000; @@ -324,7 +337,9 @@ impl<'a> NotificationHandler for ServerImpl<'a> { // // (if things actually overheat, `run_control` will cut // power to the system) - if let Err(e) = self.control.run_control() { + if let Err(e) = + self.control.run_control(&mut self.ereporter) + { ringbuf_entry!(Trace::ControlError(e)); } } @@ -368,6 +383,7 @@ fn main() -> ! { control, deadline, runtime: 0, + ereporter: ereport::Ereporter::claim_static_resources(), }; if bsp::USE_CONTROLLER { server.set_mode_auto().unwrap_lite(); @@ -393,3 +409,21 @@ mod idl { include!(concat!(env!("OUT_DIR"), "/notifications.rs")); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); + +//////////////////////////////////////////////////////////////////////////////// + +// Stub ereport module to make initialization simpler when compiled sans +// ereports. +#[cfg(not(feature = "ereport"))] +mod ereport { + pub(crate) struct Ereporter {} + + impl Ereporter { + pub(crate) fn claim_static_resources() -> Self { + Self {} + } + } +} + +#[cfg(feature = "ereport")] +mod ereport; From c66b551dd94a3c70c418431589df105b4fe795ca Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 9 Feb 2026 11:29:02 -0800 Subject: [PATCH 3/9] tweak classes --- task/thermal/src/ereport.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/task/thermal/src/ereport.rs b/task/thermal/src/ereport.rs index b2a2e9799..7249a2d77 100644 --- a/task/thermal/src/ereport.rs +++ b/task/thermal/src/ereport.rs @@ -57,7 +57,7 @@ pub enum Ereport { temp_c: f32, }, /// A component exceeded its power-down threshold. - #[cbor(rename = "hw.temp.a2.thresh")] + #[cbor(rename = "hw.temp.pwrdown")] ComponentShutdown { #[cbor(rename = "v")] version: u8, @@ -68,7 +68,7 @@ pub enum Ereport { }, /// The system is shutting down due to exceeding the critical threshold /// timeout. - #[cbor(rename = "hw.temp.a2.timeout")] + #[cbor(rename = "hw.temp.crit.timeout")] TimeoutShutdown { #[cbor(rename = "v")] version: u8, From 1c1fad830b5a767987150eed24124f2d0d6c5f1a Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 9 Feb 2026 15:18:11 -0800 Subject: [PATCH 4/9] reticulating sensor codegen --- Cargo.lock | 2 ++ build/i2c/src/lib.rs | 45 ++++++++++++++++++++++++++++++++------ task/sensor-api/Cargo.toml | 4 +++- task/sensor-api/build.rs | 27 ++++++++++++++++++----- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab074128a..b9289096c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6179,6 +6179,8 @@ dependencies = [ "idol-runtime", "num-derive 0.4.2", "num-traits", + "phash", + "phash-gen", "serde", "userlib", "zerocopy 0.8.27", diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index d2618ce24..a4060faf3 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -12,6 +12,12 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Write; use std::fs::File; +/// Outputs from code generation which may be used by other build scripts. +pub struct CodegenOutputs { + pub component_ids_by_sensor_id: Option>, + pub num_i2c_sensors: Option, +} + // // Our definition of the `Config` type. We share this type with all other // build-specific types; we must not set `deny_unknown_fields` here. @@ -295,6 +301,7 @@ struct I2cSensorsDescription { by_device: MultiMap, by_name: MultiMap, by_refdes: MultiMap, + component_id_by_id: Option>, // list of all devices and a list of their sensors, with an optional sensor // name (if present) @@ -304,11 +311,16 @@ struct I2cSensorsDescription { } impl I2cSensorsDescription { - fn new(devices: &[I2cDevice]) -> Self { + fn new(devices: &[I2cDevice], component_ids: bool) -> Self { let mut desc = Self { by_device: MultiMap::with_capacity(devices.len()), by_name: MultiMap::new(), by_refdes: MultiMap::new(), + component_id_by_id: if component_ids { + Some(BTreeMap::new()) + } else { + None + }, device_sensors: vec![Vec::new(); devices.len()], total_sensors: 0, }; @@ -401,6 +413,18 @@ impl I2cSensorsDescription { } if let Some(refdes) = d.refdes.clone() { + // If we are also generating a LUT of device refdeses by sensor IDs, + // do that now... + if let Some(ref mut by_id) = self.component_id_by_id { + if let Some(prev) = by_id.insert(id, refdes.to_component_id()) { + panic!( + "weird: colliding refdes for sensor ID {id}: {prev:?} \ + and {:?}", + d.refdes.as_ref() + ); + }; + } + self.by_refdes.insert( DeviceRefdesKey { device: d.device.clone(), @@ -1571,10 +1595,10 @@ impl ConfigGenerator { } fn sensors_description(&self) -> I2cSensorsDescription { - I2cSensorsDescription::new(&self.devices) + I2cSensorsDescription::new(&self.devices, self.component_ids) } - pub fn generate_sensors(&mut self) -> Result<()> { + fn generate_sensors(&mut self) -> Result { let s = self.sensors_description(); write!( @@ -1665,7 +1689,7 @@ impl ConfigGenerator { } writeln!(&mut self.output, "\n }}")?; - Ok(()) + Ok(s) } pub fn generate_ports(&mut self) -> Result<()> { @@ -1709,7 +1733,7 @@ impl From for CodegenSettings { } } -pub fn codegen(settings: impl Into) -> Result<()> { +pub fn codegen(settings: impl Into) -> Result { let settings = settings.into(); use std::io::Write; @@ -1721,6 +1745,11 @@ pub fn codegen(settings: impl Into) -> Result<()> { g.generate_header()?; + let mut outputs = CodegenOutputs { + component_ids_by_sensor_id: None, + num_i2c_sensors: None, + }; + match settings.disposition { Disposition::Target => { let n = g.ncontrollers(); @@ -1755,7 +1784,9 @@ pub fn codegen(settings: impl Into) -> Result<()> { Disposition::Sensors => { g.generate_devices()?; - g.generate_sensors()?; + let desc = g.generate_sensors()?; + outputs.component_ids_by_sensor_id = desc.component_id_by_id; + outputs.num_i2c_sensors = Some(desc.total_sensors); } Disposition::Validation => { @@ -1768,7 +1799,7 @@ pub fn codegen(settings: impl Into) -> Result<()> { file.write_all(g.output.as_bytes())?; - Ok(()) + Ok(outputs) } pub struct I2cDeviceDescription { diff --git a/task/sensor-api/Cargo.toml b/task/sensor-api/Cargo.toml index 6e409b3d2..724c371b9 100644 --- a/task/sensor-api/Cargo.toml +++ b/task/sensor-api/Cargo.toml @@ -16,6 +16,7 @@ counters = { path = "../../lib/counters" } drv-i2c-api.path = "../../drv/i2c-api" derive-idol-err.path = "../../lib/derive-idol-err" userlib.path = "../../sys/userlib" +phash = { path = "../../lib/phash", optional = true } # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. @@ -31,13 +32,14 @@ serde = { workspace = true } build-i2c = { path = "../../build/i2c" } build-util = { path = "../../build/util" } +phash-gen = { path = "../../build/phash-gen", optional = true } [features] h743 = ["build-i2c/h743"] h753 = ["build-i2c/h753"] # Include component ID strings in I2cDevice structs. This is necessary if # `drv-i2c-api` is built with the `component-id` feature. -component-id = ["drv-i2c-api/component-id"] +component-id = ["drv-i2c-api/component-id", "phash-gen", "phash"] [lints] workspace = true diff --git a/task/sensor-api/build.rs b/task/sensor-api/build.rs index 1cb7a15c7..e8d3d6e70 100644 --- a/task/sensor-api/build.rs +++ b/task/sensor-api/build.rs @@ -34,7 +34,20 @@ fn main() -> Result<()> { idol::client::build_client_stub("../../idl/sensor.idol", "client_stub.rs") .map_err(|e| anyhow!("idol error: {e}"))?; - build_i2c::codegen(build_i2c::Disposition::Sensors)?; + let i2c_outputs = build_i2c::codegen(build_i2c::CodegenSettings { + disposition: build_i2c::Disposition::Sensors, + component_ids: cfg!(feature = "component-id"), + })?; + + #[cfg(feature = "component-id")] + let component_ids_by_id = _i2c_outputs.component_ids_by_id.expect( + "component IDs by sensor ID map should be generated if \ + `build-i2c/component-id` feature is enabled", + ); + let num_i2c_sensors = i2c_outputs.num_i2c_sensors.expect( + "i2c codegen should output `num_i2c_sensors` if run with \ + `Disposition::Sensors`", + ); let config: GlobalConfig = build_util::config()?; @@ -52,7 +65,7 @@ fn main() -> Result<()> { } let mut sensors_text = String::new(); - let mut sensor_id = 0; + let mut sensor_num = 0; for d in &config_sensor.devices { for (sensor_type, &sensor_count) in d.sensors.iter() { let sensor = format!( @@ -67,17 +80,19 @@ fn main() -> Result<()> { pub const NUM_{sensor}_SENSORS: usize = {sensor_count};" ) .unwrap(); + if sensor_count == 1 { + let sensor_id = num_i2c_sensors + sensor_num; + sensor_num += 1; writeln!( &mut sensors_text, " #[allow(dead_code)] pub const {sensor}_SENSOR: SensorId = \ // {} - SensorId(NUM_I2C_SENSORS as u32 + {sensor_id});", + SensorId({sensor_id});", d.description ) .unwrap(); - sensor_id += 1; } else { writeln!( &mut sensors_text, @@ -88,10 +103,10 @@ fn main() -> Result<()> { for _ in 0..sensor_count { writeln!( &mut sensors_text, - " SensorId(NUM_I2C_SENSORS as u32 + {sensor_id})," + " SensorId(sensor_id)," ) .unwrap(); - sensor_id += 1; + sensor_num += 1; } writeln!(&mut sensors_text, " ];").unwrap(); } From a06d6c44d91a46b93790da432c0b997f9eac34a2 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 10 Feb 2026 10:10:35 -0800 Subject: [PATCH 5/9] blargh whoops --- build/i2c/src/lib.rs | 1 + task/sensor-api/Cargo.toml | 6 +++++- task/sensor-api/build.rs | 19 ++++++++----------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index a4060faf3..6520aad60 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -1735,6 +1735,7 @@ impl From for CodegenSettings { pub fn codegen(settings: impl Into) -> Result { let settings = settings.into(); + assert_eq!(cfg!(feature = "component-id"), settings.component_ids); use std::io::Write; let out_dir = build_util::out_dir(); diff --git a/task/sensor-api/Cargo.toml b/task/sensor-api/Cargo.toml index 724c371b9..f1669e87f 100644 --- a/task/sensor-api/Cargo.toml +++ b/task/sensor-api/Cargo.toml @@ -39,7 +39,11 @@ h743 = ["build-i2c/h743"] h753 = ["build-i2c/h753"] # Include component ID strings in I2cDevice structs. This is necessary if # `drv-i2c-api` is built with the `component-id` feature. -component-id = ["drv-i2c-api/component-id", "phash-gen", "phash"] +component-id = [ + "drv-i2c-api/component-id", + "phash-gen", + "phash", +] [lints] workspace = true diff --git a/task/sensor-api/build.rs b/task/sensor-api/build.rs index e8d3d6e70..636f51959 100644 --- a/task/sensor-api/build.rs +++ b/task/sensor-api/build.rs @@ -34,19 +34,16 @@ fn main() -> Result<()> { idol::client::build_client_stub("../../idl/sensor.idol", "client_stub.rs") .map_err(|e| anyhow!("idol error: {e}"))?; - let i2c_outputs = build_i2c::codegen(build_i2c::CodegenSettings { - disposition: build_i2c::Disposition::Sensors, - component_ids: cfg!(feature = "component-id"), - })?; + let i2c_outputs = build_i2c::codegen(build_i2c::Disposition::Sensors)?; #[cfg(feature = "component-id")] - let component_ids_by_id = _i2c_outputs.component_ids_by_id.expect( + let component_ids_by_id = i2c_outputs.component_ids_by_sensor_id.expect( "component IDs by sensor ID map should be generated if \ - `build-i2c/component-id` feature is enabled", + `build-i2c/component-id` feature is enabled", ); let num_i2c_sensors = i2c_outputs.num_i2c_sensors.expect( "i2c codegen should output `num_i2c_sensors` if run with \ - `Disposition::Sensors`", + `Disposition::Sensors`", ); let config: GlobalConfig = build_util::config()?; @@ -80,7 +77,7 @@ fn main() -> Result<()> { pub const NUM_{sensor}_SENSORS: usize = {sensor_count};" ) .unwrap(); - + if sensor_count == 1 { let sensor_id = num_i2c_sensors + sensor_num; sensor_num += 1; @@ -102,9 +99,9 @@ fn main() -> Result<()> { .unwrap(); for _ in 0..sensor_count { writeln!( - &mut sensors_text, - " SensorId(sensor_id)," - ) + &mut sensors_text, + " SensorId(sensor_id)," + ) .unwrap(); sensor_num += 1; } From 56daf021dfb14a90c23f1dd64212b334277d4c15 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 10 Feb 2026 11:19:44 -0800 Subject: [PATCH 6/9] plumbing... --- Cargo.lock | 5 +- app/cosmo/base.toml | 2 +- build/i2c/src/lib.rs | 4 +- task/sensor-api/Cargo.toml | 11 ++-- task/sensor-api/build.rs | 104 ++++++++++++++++++++++++++++++++----- task/sensor-api/src/lib.rs | 11 ++++ task/thermal/Cargo.toml | 7 +++ 7 files changed, 120 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9289096c..d9cd0ee22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6174,13 +6174,12 @@ dependencies = [ "counters", "derive-idol-err", "drv-i2c-api", + "fixedstr", "hubpack", "idol", "idol-runtime", "num-derive 0.4.2", "num-traits", - "phash", - "phash-gen", "serde", "userlib", "zerocopy 0.8.27", @@ -6261,6 +6260,7 @@ dependencies = [ "drv-onewire-devices", "drv-sidecar-seq-api", "drv-transceivers-api", + "fixedstr", "hubpack", "idol", "idol-runtime", @@ -6268,6 +6268,7 @@ dependencies = [ "ringbuf", "serde", "static-cell", + "task-packrat-api", "task-sensor-api", "task-thermal-api", "userlib", diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 30562b763..62570f5d0 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -148,7 +148,7 @@ task-slots = ["sys", "packrat"] [tasks.thermal] name = "task-thermal" -features = ["cosmo"] +features = ["cosmo", "ereport"] priority = 8 max-sizes = {flash = 32768, ram = 8192 } stacksize = 6000 diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 6520aad60..c607b6ed2 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -230,7 +230,7 @@ struct I2cSensors { #[derive(Clone, Debug, Deserialize, Hash, PartialOrd, PartialEq, Eq, Ord)] #[serde(untagged)] -enum Refdes { +pub enum Refdes { Component(String), Path(Vec), } @@ -1870,7 +1870,7 @@ where } impl Refdes { - fn to_component_id(&self) -> String { + pub fn to_component_id(&self) -> String { self.join_with_case(str::make_ascii_uppercase, "/") } diff --git a/task/sensor-api/Cargo.toml b/task/sensor-api/Cargo.toml index f1669e87f..487464835 100644 --- a/task/sensor-api/Cargo.toml +++ b/task/sensor-api/Cargo.toml @@ -16,7 +16,7 @@ counters = { path = "../../lib/counters" } drv-i2c-api.path = "../../drv/i2c-api" derive-idol-err.path = "../../lib/derive-idol-err" userlib.path = "../../sys/userlib" -phash = { path = "../../lib/phash", optional = true } +fixedstr = { path = "../../lib/fixedstr", optional = true } # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. @@ -32,18 +32,15 @@ serde = { workspace = true } build-i2c = { path = "../../build/i2c" } build-util = { path = "../../build/util" } -phash-gen = { path = "../../build/phash-gen", optional = true } [features] h743 = ["build-i2c/h743"] h753 = ["build-i2c/h753"] # Include component ID strings in I2cDevice structs. This is necessary if # `drv-i2c-api` is built with the `component-id` feature. -component-id = [ - "drv-i2c-api/component-id", - "phash-gen", - "phash", -] +component-id = ["drv-i2c-api/component-id"] +# Also generate a table of component IDs by sensor ID. +component-id-lookup = ["component-id", "fixedstr"] [lints] workspace = true diff --git a/task/sensor-api/build.rs b/task/sensor-api/build.rs index 636f51959..727466c43 100644 --- a/task/sensor-api/build.rs +++ b/task/sensor-api/build.rs @@ -28,6 +28,8 @@ struct Sensor { device: String, description: String, sensors: BTreeMap, + #[cfg_attr(not(feature = "component-id-lookup"), allow(dead_code))] + refdes: Option, } fn main() -> Result<()> { @@ -36,7 +38,7 @@ fn main() -> Result<()> { let i2c_outputs = build_i2c::codegen(build_i2c::Disposition::Sensors)?; - #[cfg(feature = "component-id")] + #[cfg(feature = "component-id-lookup")] let component_ids_by_id = i2c_outputs.component_ids_by_sensor_id.expect( "component IDs by sensor ID map should be generated if \ `build-i2c/component-id` feature is enabled", @@ -48,6 +50,14 @@ fn main() -> Result<()> { let config: GlobalConfig = build_util::config()?; + let mut state = GeneratorState { + num_other_sensors: 0, + num_i2c_sensors, + #[cfg(feature = "component-id-lookup")] + component_ids_by_id, + #[cfg(feature = "component-id-lookup")] + max_component_id_len: 0, + }; let (count, text) = if let Some(config_sensor) = &config.sensor { let sensor_count: usize = config_sensor.devices.iter().map(|d| d.sensors.len()).sum(); @@ -62,7 +72,6 @@ fn main() -> Result<()> { } let mut sensors_text = String::new(); - let mut sensor_num = 0; for d in &config_sensor.devices { for (sensor_type, &sensor_count) in d.sensors.iter() { let sensor = format!( @@ -79,8 +88,7 @@ fn main() -> Result<()> { .unwrap(); if sensor_count == 1 { - let sensor_id = num_i2c_sensors + sensor_num; - sensor_num += 1; + let sensor_id = state.add_sensor(d)?; writeln!( &mut sensors_text, " #[allow(dead_code)] @@ -98,17 +106,25 @@ fn main() -> Result<()> { ) .unwrap(); for _ in 0..sensor_count { + let sensor_id = state.add_sensor(d)?; writeln!( &mut sensors_text, - " SensorId(sensor_id)," + " SensorId({sensor_id})," ) .unwrap(); - sensor_num += 1; } writeln!(&mut sensors_text, " ];").unwrap(); } } } + + #[cfg(feature = "component-id-lookup")] + writeln!( + &mut sensors_text, + " pub const MAX_COMPONENT_ID_LEN: usize = {};", + state.max_component_id_len + ) + .unwrap(); (sensor_count, sensors_text) } else { (0, String::new()) @@ -123,16 +139,12 @@ fn main() -> Result<()> { #[allow(unused_imports)] use super::SensorId; - // This is only included to determine the number of sensors include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); pub mod other_sensors {{ #[allow(unused_imports)] use super::SensorId; - #[allow(unused_imports)] - use super::NUM_I2C_SENSORS; // Used for offsetting - #[allow(dead_code)] pub const NUM_SENSORS: usize = {count}; {text} @@ -142,10 +154,78 @@ fn main() -> Result<()> { pub use i2c_sensors::NUM_SENSORS as NUM_I2C_SENSORS; pub use other_sensors::NUM_SENSORS as NUM_OTHER_SENSORS; - // Here's what we actually care about: pub const NUM_SENSORS: usize = NUM_I2C_SENSORS + NUM_OTHER_SENSORS; -}}"# +"# ) .unwrap(); + + #[cfg(feature = "component-id-lookup")] + { + writeln!(&mut file, + r#"pub const MAX_COMPONENT_ID_LEN: usize = if other_sensors::MAX_COMPONENT_ID_LEN > i2c_config::MAX_COMPONENT_ID_LEN {{ + other_sensors::MAX_COMPONENT_ID_LEN + }} else {{ + i2c_config::MAX_COMPONENT_ID_LEN + }};"# + ) + .unwrap(); + write!( + &mut file, + r#" + pub(super) const SENSOR_ID_TO_COMPONENT_ID: [ + fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>; + NUM_SENSORS + ] = ["#, + ) + .unwrap(); + for (_, cid) in state.component_ids_by_id { + writeln!(&mut file, " fixedstr::FixedStr::new(\"{cid}\"),",) + .unwrap(); + } + writeln!(&mut file, " ];").unwrap(); + } + + writeln!(&mut file, "}}").unwrap(); Ok(()) } + +struct GeneratorState { + num_i2c_sensors: usize, + num_other_sensors: usize, + #[cfg(feature = "component-id-lookup")] + component_ids_by_id: BTreeMap, + #[cfg(feature = "component-id-lookup")] + max_component_id_len: usize, +} + +impl GeneratorState { + fn add_sensor(&mut self, _d: &Sensor) -> Result { + let sensor_id = self.num_i2c_sensors + self.num_other_sensors; + self.num_other_sensors += 1; + #[cfg(feature = "component-id-lookup")] + { + let d = _d; + let Some(ref refdes) = d.refdes else { + anyhow::bail!( + "we were asked to generate sensor component IDs, but \ + sensor {} has no refdes", + d.name, + ) + }; + let component_id = refdes.to_component_id(); + self.max_component_id_len = + self.max_component_id_len.max(component_id.len()); + if let Some(prev) = + self.component_ids_by_id.insert(sensor_id, component_id) + { + anyhow::bail!( + "duplicate sensor ID {sensor_id} for {} \ + (previous entry had refdes {prev})", + d.name, + ); + } + } + + Ok(sensor_id) + } +} diff --git a/task/sensor-api/src/lib.rs b/task/sensor-api/src/lib.rs index 9f29705e9..b2b383dbf 100644 --- a/task/sensor-api/src/lib.rs +++ b/task/sensor-api/src/lib.rs @@ -81,6 +81,17 @@ impl SensorId { pub fn into_u32_array(ids: [Self; N]) -> [u32; N] { ids.map(Into::into) } + + /// Returns the component ID (refdes) corresponding to this sensor. + /// + /// Note that multiple sensor IDs may have the same component ID, when a + /// single device exposes multiple measurement channels. + #[cfg(feature = "component-id-lookup")] + pub fn component_id( + &self, + ) -> fixedstr::FixedStr<'static, { config::MAX_COMPONENT_ID_LEN }> { + config::COMPONENT_ID_LOOKUP[self.0 as usize] + } } impl TryFrom for SensorId { diff --git a/task/thermal/Cargo.toml b/task/thermal/Cargo.toml index 4e5003133..10675618b 100644 --- a/task/thermal/Cargo.toml +++ b/task/thermal/Cargo.toml @@ -23,10 +23,12 @@ drv-i2c-devices.path = "../../drv/i2c-devices" drv-onewire-devices.path = "../../drv/onewire-devices" drv-onewire.path = "../../drv/onewire" counters.path = "../../lib/counters" +fixedstr = { path = "../../lib/fixedstr", optional = true, features = ["microcbor"] } ringbuf.path = "../../lib/ringbuf" static-cell.path = "../../lib/static-cell" task-sensor-api.path = "../sensor-api" task-thermal-api.path = "../thermal-api" +task-packrat-api = { path = "../packrat-api", features = ["microcbor"], optional = true } [build-dependencies] anyhow = { workspace = true } @@ -45,6 +47,11 @@ minibar = ["h753"] h743 = ["build-i2c/h743"] h753 = ["build-i2c/h753"] no-ipc-counters = ["idol/no-counters"] +ereport = [ + "task-packrat-api", + "task-sensor-api/component-id-lookup", + "fixedstr", +] # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. From e135766fbf356911f57db57ca5cff73ccd217979 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 10 Feb 2026 11:37:52 -0800 Subject: [PATCH 7/9] cosmo builds now --- Cargo.lock | 1 + app/cosmo/base.toml | 27 +++++++++++++++++++++++++-- task/sensor-api/build.rs | 10 +++++++--- task/sensor-api/src/lib.rs | 2 +- task/thermal/Cargo.toml | 2 ++ task/thermal/src/ereport.rs | 5 +++-- 6 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9cd0ee22..8d3d9dc0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6264,6 +6264,7 @@ dependencies = [ "hubpack", "idol", "idol-runtime", + "microcbor", "num-traits", "ringbuf", "serde", diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 62570f5d0..f11465480 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -150,7 +150,7 @@ task-slots = ["sys", "packrat"] name = "task-thermal" features = ["cosmo", "ereport"] priority = 8 -max-sizes = {flash = 32768, ram = 8192 } +max-sizes = {flash = 32768, ram = 16384 } stacksize = 6000 start = true task-slots = ["i2c_driver", "sensor", "cosmo_seq", "jefe"] @@ -1236,145 +1236,168 @@ name = "A_TS0" device = "dimm" description = "DIMM A, sensor 0" sensors.temperature = 1 +refdes = "J101" [[config.sensor.devices]] name = "A_TS1" device = "dimm" description = "DIMM A, sensor 1" sensors.temperature = 1 - +refdes = "J101" [[config.sensor.devices]] name = "B_TS0" device = "dimm" description = "DIMM B, sensor 0" sensors.temperature = 1 +refdes = "J102" [[config.sensor.devices]] name = "B_TS1" device = "dimm" description = "DIMM B, sensor 1" sensors.temperature = 1 +refdes = "J102" [[config.sensor.devices]] name = "C_TS0" device = "dimm" description = "DIMM C, sensor 0" sensors.temperature = 1 +refdes = "J103" [[config.sensor.devices]] name = "C_TS1" device = "dimm" description = "DIMM C, sensor 1" sensors.temperature = 1 +refdes = "J103" [[config.sensor.devices]] name = "D_TS0" device = "dimm" description = "DIMM D, sensor 0" sensors.temperature = 1 +refdes = "J104" [[config.sensor.devices]] name = "D_TS1" device = "dimm" description = "DIMM D, sensor 1" sensors.temperature = 1 +refdes = "J104" [[config.sensor.devices]] name = "E_TS0" device = "dimm" description = "DIMM E, sensor 0" sensors.temperature = 1 +refdes = "J105" [[config.sensor.devices]] name = "E_TS1" device = "dimm" description = "DIMM E, sensor 1" sensors.temperature = 1 +refdes = "J105" [[config.sensor.devices]] name = "F_TS0" device = "dimm" description = "DIMM F, sensor 0" sensors.temperature = 1 +refdes = "J106" [[config.sensor.devices]] name = "F_TS1" device = "dimm" description = "DIMM F, sensor 1" sensors.temperature = 1 +refdes = "J106" [[config.sensor.devices]] name = "G_TS0" device = "dimm" description = "DIMM G, sensor 0" sensors.temperature = 1 +refdes = "J107" [[config.sensor.devices]] name = "G_TS1" device = "dimm" description = "DIMM G, sensor 1" sensors.temperature = 1 +refdes = "J107" [[config.sensor.devices]] name = "H_TS0" device = "dimm" description = "DIMM H, sensor 0" sensors.temperature = 1 +refdes = "J108" [[config.sensor.devices]] name = "H_TS1" device = "dimm" description = "DIMM H, sensor 1" sensors.temperature = 1 +refdes = "J108" [[config.sensor.devices]] name = "I_TS0" device = "dimm" description = "DIMM I, sensor 0" sensors.temperature = 1 +refdes = "J109" [[config.sensor.devices]] name = "I_TS1" device = "dimm" description = "DIMM I, sensor 1" sensors.temperature = 1 +refdes = "J109" [[config.sensor.devices]] name = "J_TS0" device = "dimm" description = "DIMM J, sensor 0" sensors.temperature = 1 +refdes = "J110" [[config.sensor.devices]] name = "J_TS1" device = "dimm" description = "DIMM J, sensor 1" sensors.temperature = 1 +refdes = "J110" [[config.sensor.devices]] name = "K_TS0" device = "dimm" description = "DIMM K, sensor 0" sensors.temperature = 1 +refdes = "J111" [[config.sensor.devices]] name = "K_TS1" device = "dimm" description = "DIMM K, sensor 1" sensors.temperature = 1 +refdes = "J111" [[config.sensor.devices]] name = "L_TS0" device = "dimm" description = "DIMM L, sensor 0" sensors.temperature = 1 +refdes = "J112" [[config.sensor.devices]] name = "L_TS1" device = "dimm" description = "DIMM L, sensor 1" sensors.temperature = 1 +refdes = "J112" ################################################################################ [config.spi.spi2] diff --git a/task/sensor-api/build.rs b/task/sensor-api/build.rs index 727466c43..e491e29bb 100644 --- a/task/sensor-api/build.rs +++ b/task/sensor-api/build.rs @@ -175,12 +175,16 @@ fn main() -> Result<()> { pub(super) const SENSOR_ID_TO_COMPONENT_ID: [ fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>; NUM_SENSORS - ] = ["#, + ] = [ +"#, ) .unwrap(); for (_, cid) in state.component_ids_by_id { - writeln!(&mut file, " fixedstr::FixedStr::new(\"{cid}\"),",) - .unwrap(); + writeln!( + &mut file, + " fixedstr::FixedStr::from_str(\"{cid}\"),", + ) + .unwrap(); } writeln!(&mut file, " ];").unwrap(); } diff --git a/task/sensor-api/src/lib.rs b/task/sensor-api/src/lib.rs index b2b383dbf..3443f659e 100644 --- a/task/sensor-api/src/lib.rs +++ b/task/sensor-api/src/lib.rs @@ -90,7 +90,7 @@ impl SensorId { pub fn component_id( &self, ) -> fixedstr::FixedStr<'static, { config::MAX_COMPONENT_ID_LEN }> { - config::COMPONENT_ID_LOOKUP[self.0 as usize] + config::SENSOR_ID_TO_COMPONENT_ID[self.0 as usize] } } diff --git a/task/thermal/Cargo.toml b/task/thermal/Cargo.toml index 10675618b..d1d826cfa 100644 --- a/task/thermal/Cargo.toml +++ b/task/thermal/Cargo.toml @@ -24,6 +24,7 @@ drv-onewire-devices.path = "../../drv/onewire-devices" drv-onewire.path = "../../drv/onewire" counters.path = "../../lib/counters" fixedstr = { path = "../../lib/fixedstr", optional = true, features = ["microcbor"] } +microcbor = { path = "../../lib/microcbor", optional = true } ringbuf.path = "../../lib/ringbuf" static-cell.path = "../../lib/static-cell" task-sensor-api.path = "../sensor-api" @@ -51,6 +52,7 @@ ereport = [ "task-packrat-api", "task-sensor-api/component-id-lookup", "fixedstr", + "microcbor", ] # This section is here to discourage RLS/rust-analyzer from doing test builds, diff --git a/task/thermal/src/ereport.rs b/task/thermal/src/ereport.rs index 7249a2d77..0f9e21823 100644 --- a/task/thermal/src/ereport.rs +++ b/task/thermal/src/ereport.rs @@ -25,13 +25,14 @@ impl Ereporter { static_cell::ClaimOnceCell::new([0u8; EREPORT_BUF_SIZE]); Self { - buf: BUF.claim().unwrap(), + buf: BUF.claim(), packrat: Packrat::from(PACKRAT.get_task_id()), } } pub(crate) fn deliver_ereport(&mut self, ereport: &Ereport) { - let eresult = self.packrat.encode_ereport(&ereport, self.buf); + let eresult = + self.packrat.deliver_microcbor_ereport(&ereport, self.buf); match eresult { Ok(len) => ringbuf_entry_root!(Trace::EreportSent { len }), Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { From 49bff3de0ca88b62ba9750a99b419e4f509b6dd4 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 12 Feb 2026 09:25:56 -0800 Subject: [PATCH 8/9] wip --- app/cosmo/base.toml | 2 +- task/thermal/src/control.rs | 21 +++++++++++++++++--- task/thermal/src/ereport.rs | 39 ++++++++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index f11465480..cf9c058fb 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -153,7 +153,7 @@ priority = 8 max-sizes = {flash = 32768, ram = 16384 } stacksize = 6000 start = true -task-slots = ["i2c_driver", "sensor", "cosmo_seq", "jefe"] +task-slots = ["i2c_driver", "sensor", "cosmo_seq", "jefe", "packrat"] notifications = ["timer"] [tasks.power] diff --git a/task/thermal/src/control.rs b/task/thermal/src/control.rs index 0c53f2576..9b33bc574 100644 --- a/task/thermal/src/control.rs +++ b/task/thermal/src/control.rs @@ -1229,9 +1229,23 @@ impl<'a> ThermalControl<'a> { temperature }); self.transition_to_uncontrollable(now_ms) - } else if let Some(due_to) = any_critical { + } else if let Some((sensor_id, temperature)) = any_critical { let values = *values; - self.transition_to_critical(due_to, now_ms, values) + *ereports.pending_mut() = + Some(ereport::Ereport::ComponentPowerDown { + version: 0, + refdes: sensor_id.component_id(), + sensor_id: sensor_id.into(), + temp_c: temperature.into(), + overheat_ms: None, + time: now_ms, + }); + self.transition_to_critical( + sensor_id, + temperature, + now_ms, + values, + ) } else { // We adjust the worst component margin by our target // margin, which must be > 0. This effectively tells the @@ -1397,7 +1411,8 @@ impl<'a> ThermalControl<'a> { /// component exceeding its critical threshold. fn transition_to_critical( &mut self, - (sensor_id, temperature): (SensorId, Celsius), + sensor_id: SensorId, + temperature: Celsius, now_ms: u64, values: [TemperatureReading; TEMPERATURE_ARRAY_SIZE], ) -> ControlResult { diff --git a/task/thermal/src/ereport.rs b/task/thermal/src/ereport.rs index 0f9e21823..d2eccd0c4 100644 --- a/task/thermal/src/ereport.rs +++ b/task/thermal/src/ereport.rs @@ -2,11 +2,11 @@ // 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 crate::i2c_config::MAX_COMPONENT_ID_LEN; use crate::Trace; use fixedstr::FixedStr; use ringbuf::ringbuf_entry_root; use task_packrat_api::Packrat; +use task_sensor_api::config::MAX_COMPONENT_ID_LEN; use userlib::task_slot; task_slot!(PACKRAT, packrat); @@ -16,25 +16,41 @@ pub(crate) const EREPORT_BUF_SIZE: usize = pub(crate) struct Ereporter { buf: &'static mut [u8; EREPORT_BUF_SIZE], + pending: &'static mut Option, packrat: Packrat, } impl Ereporter { pub(crate) fn claim_static_resources() -> Self { - static BUF: static_cell::ClaimOnceCell<[u8; EREPORT_BUF_SIZE]> = - static_cell::ClaimOnceCell::new([0u8; EREPORT_BUF_SIZE]); + use static_cell::ClaimOnceCell; + + static BUF: ClaimOnceCell<[u8; EREPORT_BUF_SIZE]> = + ClaimOnceCell::new([0u8; EREPORT_BUF_SIZE]); + static PENDING: ClaimOnceCell> = + ClaimOnceCell::new(None); Self { buf: BUF.claim(), + pending: PENDING.claim(), packrat: Packrat::from(PACKRAT.get_task_id()), } } - pub(crate) fn deliver_ereport(&mut self, ereport: &Ereport) { + pub(crate) fn pending_mut(&mut self) -> &mut Option { + self.pending + } + + pub(crate) fn flush_pending(&mut self) { + let Some(ereport) = self.pending.as_ref() else { + return; + }; let eresult = self.packrat.deliver_microcbor_ereport(&ereport, self.buf); match eresult { - Ok(len) => ringbuf_entry_root!(Trace::EreportSent { len }), + Ok(len) => { + ringbuf_entry_root!(Trace::EreportSent { len }); + self.pending = None; + } Err(task_packrat_api::EreportEncodeError::Packrat { len, err }) => { ringbuf_entry_root!(Trace::EreportLost { len, err }) } @@ -54,18 +70,20 @@ pub enum Ereport { #[cbor(rename = "v")] version: u8, refdes: FixedStr<'static, { MAX_COMPONENT_ID_LEN }>, - sensor_id: u8, + sensor_id: u32, temp_c: f32, + time: u64, }, /// A component exceeded its power-down threshold. #[cbor(rename = "hw.temp.pwrdown")] - ComponentShutdown { + ComponentPowerDown { #[cbor(rename = "v")] version: u8, refdes: FixedStr<'static, { MAX_COMPONENT_ID_LEN }>, - sensor_id: u8, + sensor_id: u32, temp_c: f32, overheat_ms: Option, + time: u64, }, /// The system is shutting down due to exceeding the critical threshold /// timeout. @@ -74,6 +92,7 @@ pub enum Ereport { #[cbor(rename = "v")] version: u8, overheat_ms: OverheatDurations, + time: u64, }, /// All temperatures have returned to nominal. #[cbor(rename = "hw.temp.ok")] @@ -81,13 +100,15 @@ pub enum Ereport { #[cbor(rename = "v")] version: u8, overheat_ms: OverheatDurations, + time: u64, }, #[cbor(rename = "hw.temp.readerr")] SensorError { #[cbor(rename = "v")] version: u8, refdes: FixedStr<'static, { MAX_COMPONENT_ID_LEN }>, - sensor_id: u8, + sensor_id: u32, + time: u64, }, } From 0a2c2c9744ace12642112ba9544af54b8e4e7b8a Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 12 Feb 2026 09:28:06 -0800 Subject: [PATCH 9/9] reticulating --- task/thermal/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/task/thermal/src/main.rs b/task/thermal/src/main.rs index ae9cb2983..ae1a7a450 100644 --- a/task/thermal/src/main.rs +++ b/task/thermal/src/main.rs @@ -356,6 +356,7 @@ impl<'a> NotificationHandler for ServerImpl<'a> { } self.deadline = now + TIMER_INTERVAL; } + self.ereporter.flush_pending(); // We can use wrapping arithmetic here because the timer is monotonic. self.runtime = sys_get_timer().now.wrapping_sub(now); sys_set_timer(Some(self.deadline), notifications::TIMER_MASK);