diff --git a/Cargo.lock b/Cargo.lock index 182f9d111..4e0a52aa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6920,14 +6920,18 @@ dependencies = [ "drv-onewire-devices", "drv-sidecar-seq-api", "drv-transceivers-api", + "ereports", + "fixedstr", "hubpack", "idol", "idol-runtime", + "microcbor", "mutable-statics", "num-traits", "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 d9e888b49..7fc943e51 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -154,7 +154,7 @@ priority = 8 max-sizes = {flash = 32768, ram = 8192 } stacksize = 3000 start = true -task-slots = ["i2c_driver", "sensor", "cosmo_seq", "jefe"] +task-slots = ["i2c_driver", "sensor", "cosmo_seq", "packrat"] notifications = ["timer"] [tasks.power] diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 1bb9ba64b..9b2552ace 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -138,7 +138,7 @@ priority = 5 max-sizes = {flash = 32768, ram = 8192 } stacksize = 2000 start = true -task-slots = ["i2c_driver", "sensor", "gimlet_seq", "jefe"] +task-slots = ["i2c_driver", "sensor", "gimlet_seq", "packrat"] notifications = ["timer"] [tasks.power] diff --git a/app/grapefruit/rev-a-ruby.toml b/app/grapefruit/rev-a-ruby.toml index eef0348dd..736671572 100644 --- a/app/grapefruit/rev-a-ruby.toml +++ b/app/grapefruit/rev-a-ruby.toml @@ -9,14 +9,34 @@ features = ["usart6", "hardware_flow_control"] uses = ["usart6"] interrupts = {"usart6.irq" = "usart-irq"} +# needed for thermal ereports +[tasks.packrat] +features = ["ereport"] +stacksize = 1280 + +# We don't actually tell jefe to notify us on faults, +# but this is still required +notifications = ["task-faulted"] +task-slots = ["jefe"] + +# needed for thermal ereports +[tasks.rng_driver] +features = ["h753", "packrat"] +name = "drv-stm32h7-rng" +priority = 6 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys", "packrat"] + [tasks.thermal] name = "task-thermal" features = ["grapefruit"] priority = 5 -max-sizes = {flash = 32768, ram = 8192 } -stacksize = 6000 +max-sizes = {flash = 32768, ram = 5120 } +stacksize = 1536 start = true -task-slots = ["i2c_driver", "sensor", "jefe"] +task-slots = ["i2c_driver", "sensor", "packrat"] notifications = ["timer"] [config] diff --git a/app/minibar/base.toml b/app/minibar/base.toml index 31d68f295..280df4251 100644 --- a/app/minibar/base.toml +++ b/app/minibar/base.toml @@ -112,7 +112,7 @@ priority = 5 max-sizes = {flash = 32768, ram = 16384 } stacksize = 8096 start = true -task-slots = ["i2c_driver", "sensor"] +task-slots = ["i2c_driver", "sensor", "packrat"] notifications = ["timer"] [tasks.power] diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 6b5af55ff..20c674391 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -299,7 +299,7 @@ priority = 5 max-sizes = {flash = 32768, ram = 16384 } stacksize = 4000 start = true -task-slots = ["i2c_driver", "sensor", "sequencer"] +task-slots = ["i2c_driver", "sensor", "sequencer", "packrat"] notifications = ["timer"] [tasks.power] diff --git a/task/sensor-api/src/lib.rs b/task/sensor-api/src/lib.rs index fb35430cd..403663844 100644 --- a/task/sensor-api/src/lib.rs +++ b/task/sensor-api/src/lib.rs @@ -87,7 +87,7 @@ impl SensorId { /// 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( + pub const fn component_id( &self, ) -> fixedstr::FixedStr<'static, { config::MAX_COMPONENT_ID_LEN }> { config::SENSOR_ID_TO_COMPONENT_ID[self.0 as usize] @@ -95,7 +95,7 @@ impl SensorId { /// Returns the name of this sensor. #[cfg(feature = "sensor-name-lookup")] - pub fn name( + pub const fn name( &self, ) -> fixedstr::FixedStr<'static, { config::MAX_SENSOR_NAME_LEN }> { config::SENSOR_ID_TO_NAME[self.0 as usize] diff --git a/task/thermal/Cargo.toml b/task/thermal/Cargo.toml index 8f6829f8a..768c1e0d4 100644 --- a/task/thermal/Cargo.toml +++ b/task/thermal/Cargo.toml @@ -29,6 +29,12 @@ static-cell.path = "../../lib/static-cell" task-sensor-api = { path = "../sensor-api", features = ["sensor-name-lookup", "component-id-lookup"] } task-thermal-api.path = "../thermal-api" +# ereports deps +task-packrat-api.path= "../packrat-api" +ereports = { path = "../../lib/ereports", features = ["ereporter-macro"] } +microcbor = { path = "../../lib/microcbor" } +fixedstr = { path = "../../lib/fixedstr", features = ["microcbor"] } + [build-dependencies] anyhow = { workspace = true } idol = { workspace = true } diff --git a/task/thermal/src/bsp/common/emc2305.rs b/task/thermal/src/bsp/common/emc2305.rs index 3bf6f9574..32b04c00d 100644 --- a/task/thermal/src/bsp/common/emc2305.rs +++ b/task/thermal/src/bsp/common/emc2305.rs @@ -6,9 +6,12 @@ use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_i2c_devices::emc2305::Emc2305; +use drv_i2c_devices::emc2305::Fan as EmcFan; use ringbuf::ringbuf_entry_root; use task_sensor_api::SensorId; -use task_thermal_api::{SensorReadError, ThermalError}; +use task_thermal_api::{ + SANYO_DENKI_FAN_PROPERTIES, SensorReadError, ThermalError, +}; use crate::{ Trace, @@ -96,23 +99,19 @@ impl From for SensorReadError { #[allow(dead_code)] pub(crate) const fn make_consecutive_nonremovable_fans( sensors: &'static [SensorId; N], -) -> [crate::control::Fan; N] { - const ONE: crate::control::Fan = - crate::control::Fan::new( - SensorId::new(0), - drv_i2c_devices::emc2305::Fan::new_const(0), - ); +) -> [crate::control::Fan; N] { + const ONE: crate::control::Fan = crate::control::Fan::new( + SensorId::new(0), + SANYO_DENKI_FAN_PROPERTIES, + EmcFan::new_const(0), + ); let mut out = [ONE; N]; let mut idx = 0; while idx < N { - out[idx] = crate::control::Fan::new( - sensors[idx], - drv_i2c_devices::emc2305::Fan::new_const(idx as u8), - ); - out[idx].cur_state = FanState::Present(FanPresentState::Unresponsive( - SensorReadError::NoData, - )); + out[idx].rpm_sensor_id = sensors[idx]; + out[idx].bsp_data = EmcFan::new_const(idx as u8); + out[idx].cur_state = FanState::Present(FanPresentState::Unpolled); out[idx].presence_acked = true; idx += 1; } diff --git a/task/thermal/src/bsp/common/max31790.rs b/task/thermal/src/bsp/common/max31790.rs index a8bf5b69b..0475193a8 100644 --- a/task/thermal/src/bsp/common/max31790.rs +++ b/task/thermal/src/bsp/common/max31790.rs @@ -5,10 +5,13 @@ //! Common types and helpers for Max31790 Fan Controller use drv_i2c_api::{I2cDevice, ResponseCode}; +use drv_i2c_devices::max31790::Fan as MaxFan; use drv_i2c_devices::max31790::Max31790; use ringbuf::ringbuf_entry_root; use task_sensor_api::SensorId; -use task_thermal_api::{SensorReadError, ThermalError}; +use task_thermal_api::{ + SANYO_DENKI_FAN_PROPERTIES, SensorReadError, ThermalError, +}; use crate::{ Trace, @@ -103,23 +106,19 @@ impl From for SensorReadError { #[allow(dead_code)] pub(crate) const fn make_consecutive_nonremovable_fans( sensors: &'static [SensorId; N], -) -> [crate::control::Fan; N] { - const ONE: crate::control::Fan = - crate::control::Fan::new( - SensorId::new(0), - drv_i2c_devices::max31790::Fan::new_const(0), - ); +) -> [crate::control::Fan; N] { + const ONE: crate::control::Fan = crate::control::Fan::new( + SensorId::new(0), + SANYO_DENKI_FAN_PROPERTIES, + MaxFan::new_const(0), + ); let mut out = [ONE; N]; let mut idx = 0; while idx < N { - out[idx] = crate::control::Fan::new( - sensors[idx], - drv_i2c_devices::max31790::Fan::new_const(idx as u8), - ); - out[idx].cur_state = FanState::Present(FanPresentState::Unresponsive( - SensorReadError::NoData, - )); + out[idx].rpm_sensor_id = sensors[idx]; + out[idx].bsp_data = MaxFan::new_const(idx as u8); + out[idx].cur_state = FanState::Present(FanPresentState::Unpolled); out[idx].presence_acked = true; idx += 1; } diff --git a/task/thermal/src/bsp/cosmo_ab.rs b/task/thermal/src/bsp/cosmo_ab.rs index b1868c4ae..db6339fd6 100644 --- a/task/thermal/src/bsp/cosmo_ab.rs +++ b/task/thermal/src/bsp/cosmo_ab.rs @@ -15,10 +15,7 @@ pub use drv_cpu_seq_api::SeqError; use drv_cpu_seq_api::{PowerState, Sequencer, StateChangeReason}; use drv_i2c_devices::max31790::I2cWatchdog; use task_sensor_api::{Sensor, SensorId}; -use task_thermal_api::{ - SANYO_DENKI_FAN_PROPERTIES, SensorReadError, ThermalError, - ThermalProperties, -}; +use task_thermal_api::{ThermalError, ThermalProperties}; use userlib::{ TaskId, task_slot, units::{Celsius, PWMDuty}, @@ -126,9 +123,7 @@ impl crate::control::BspInterface for Bsp { if let Ok(fctl) = self.fctrl.try_initialize() { for fan in self.fans.iter_mut() { let bsp_data = fan.bsp_data; - fan.poll_rpm_with(&SANYO_DENKI_FAN_PROPERTIES, || { - fctl.fan_rpm(bsp_data).map_err(SensorReadError::I2cError) - }); + fan.poll_rpm_with(|| fctl.fan_rpm(bsp_data)); } } diff --git a/task/thermal/src/bsp/gimlet_bcdef.rs b/task/thermal/src/bsp/gimlet_bcdef.rs index 05f291495..27a4f9327 100644 --- a/task/thermal/src/bsp/gimlet_bcdef.rs +++ b/task/thermal/src/bsp/gimlet_bcdef.rs @@ -15,10 +15,7 @@ pub use drv_cpu_seq_api::SeqError; use drv_cpu_seq_api::{PowerState, Sequencer, StateChangeReason}; use drv_i2c_devices::max31790::I2cWatchdog; use task_sensor_api::{Sensor, SensorId}; -use task_thermal_api::{ - SANYO_DENKI_FAN_PROPERTIES, SensorReadError, ThermalError, - ThermalProperties, -}; +use task_thermal_api::{ThermalError, ThermalProperties}; use userlib::{ TaskId, task_slot, units::{Celsius, PWMDuty}, @@ -171,9 +168,7 @@ impl crate::control::BspInterface for Bsp { if let Ok(fctl) = self.fctrl.try_initialize() { for fan in self.fans.iter_mut() { let bsp_data = fan.bsp_data; - fan.poll_rpm_with(&SANYO_DENKI_FAN_PROPERTIES, || { - fctl.fan_rpm(bsp_data).map_err(SensorReadError::I2cError) - }); + fan.poll_rpm_with(|| fctl.fan_rpm(bsp_data)); } } diff --git a/task/thermal/src/bsp/grapefruit.rs b/task/thermal/src/bsp/grapefruit.rs index f072e7cc2..78bfda56f 100644 --- a/task/thermal/src/bsp/grapefruit.rs +++ b/task/thermal/src/bsp/grapefruit.rs @@ -8,10 +8,7 @@ use crate::control::{ActiveInputState, MiscSensorPollingOutcome}; use crate::control::{ChannelType, PidConfig}; use drv_i2c_devices::max31790::I2cWatchdog; use task_sensor_api::SensorId; -use task_thermal_api::{ - SANYO_DENKI_FAN_PROPERTIES, SensorReadError, ThermalError, - ThermalProperties, -}; +use task_thermal_api::{ThermalError, ThermalProperties}; use userlib::TaskId; use userlib::units::{Celsius, PWMDuty}; @@ -91,9 +88,7 @@ impl crate::control::BspInterface for Bsp { if let Ok(fctl) = self.fctrl.try_initialize() { for fan in self.fans.iter_mut() { let bsp_data = fan.bsp_data; - fan.poll_rpm_with(&SANYO_DENKI_FAN_PROPERTIES, || { - fctl.fan_rpm(bsp_data).map_err(SensorReadError::I2cError) - }); + fan.poll_rpm_with(|| fctl.fan_rpm(bsp_data)); } } diff --git a/task/thermal/src/bsp/sidecar_bcd.rs b/task/thermal/src/bsp/sidecar_bcd.rs index 59ac381ea..b0a916268 100644 --- a/task/thermal/src/bsp/sidecar_bcd.rs +++ b/task/thermal/src/bsp/sidecar_bcd.rs @@ -9,16 +9,15 @@ use crate::control::{ DynamicTemperatureState, MiscSensorPollingOutcome, PidConfig, TimestampedTemperatureReading, }; +use drv_i2c_devices::max31790::Fan as MaxFan; use drv_i2c_devices::max31790::Max31790; use drv_i2c_devices::tmp451::*; pub use drv_sidecar_seq_api::SeqError; use drv_sidecar_seq_api::{Sequencer, TofinoSeqState, TofinoSequencerPolicy}; use ringbuf::ringbuf_entry_root; use task_sensor_api::SensorId; -use task_thermal_api::ThermalError; -use task_thermal_api::{ - SANYO_DENKI_FAN_PROPERTIES, SensorReadError, ThermalProperties, -}; +use task_thermal_api::ThermalProperties; +use task_thermal_api::{SANYO_DENKI_FAN_PROPERTIES, ThermalError}; use userlib::{TaskId, task_slot, units::Celsius}; include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); @@ -155,17 +154,13 @@ impl crate::control::BspInterface for Bsp { if let Ok(fctl) = self.fctrl_east.try_initialize() { for fan in east.iter_mut() { let bsp_data = fan.bsp_data; - fan.poll_rpm_with(&SANYO_DENKI_FAN_PROPERTIES, || { - fctl.fan_rpm(bsp_data).map_err(SensorReadError::I2cError) - }); + fan.poll_rpm_with(|| fctl.fan_rpm(bsp_data)); } } if let Ok(fctl) = self.fctrl_west.try_initialize() { for fan in west.iter_mut() { let bsp_data = fan.bsp_data; - fan.poll_rpm_with(&SANYO_DENKI_FAN_PROPERTIES, || { - fctl.fan_rpm(bsp_data).map_err(SensorReadError::I2cError) - }); + fan.poll_rpm_with(|| fctl.fan_rpm(bsp_data)); } } @@ -521,40 +516,23 @@ const MISC_SENSORS: [TemperatureSensor; NUM_TEMPERATURE_SENSORS] = [ // 5 West NW 3 (4) // 6 West WSW 0 (1) // 7 West WNW 1 (2) -type Fan = crate::control::Fan; -const FANS: [Fan; NUM_FANS] = [ - // EAST FANS - Fan::new( - sensors::MAX31790_SPEED_SENSORS[0], - drv_i2c_devices::max31790::Fan::new_const(2), - ), - Fan::new( - sensors::MAX31790_SPEED_SENSORS[1], - drv_i2c_devices::max31790::Fan::new_const(3), - ), - Fan::new( - sensors::MAX31790_SPEED_SENSORS[2], - drv_i2c_devices::max31790::Fan::new_const(0), - ), - Fan::new( - sensors::MAX31790_SPEED_SENSORS[3], - drv_i2c_devices::max31790::Fan::new_const(1), - ), - // WEST FANS - Fan::new( - sensors::MAX31790_SPEED_SENSORS[4], - drv_i2c_devices::max31790::Fan::new_const(2), - ), - Fan::new( - sensors::MAX31790_SPEED_SENSORS[5], - drv_i2c_devices::max31790::Fan::new_const(3), - ), - Fan::new( - sensors::MAX31790_SPEED_SENSORS[6], - drv_i2c_devices::max31790::Fan::new_const(0), - ), - Fan::new( - sensors::MAX31790_SPEED_SENSORS[7], - drv_i2c_devices::max31790::Fan::new_const(1), - ), -]; +type Fan = crate::control::Fan; +const FAN_ORDER: [u8; NUM_FANS] = [2, 3, 0, 1, 2, 3, 0, 1]; +const fn make_fans() -> [Fan; NUM_FANS] { + const ONE_FAN: Fan = Fan::new( + SensorId::new(0), + SANYO_DENKI_FAN_PROPERTIES, + MaxFan::new_const(0), + ); + let mut fans = [ONE_FAN; NUM_FANS]; + let mut idx = 0; + while idx < NUM_FANS { + fans[idx].rpm_sensor_id = sensors::MAX31790_SPEED_SENSORS[idx]; + fans[idx].bsp_data = MaxFan::new_const(FAN_ORDER[idx]); + idx += 1; + } + + fans +} + +const FANS: [Fan; NUM_FANS] = make_fans(); diff --git a/task/thermal/src/control.rs b/task/thermal/src/control.rs index 5d5ae3108..9e2136378 100644 --- a/task/thermal/src/control.rs +++ b/task/thermal/src/control.rs @@ -72,8 +72,13 @@ use crate::{ThermalError, Trace, bsp::PowerBitmask}; use drv_i2c_devices::max31790::I2cWatchdog; +use microcbor::Encode; use ringbuf::ringbuf_entry_root as ringbuf_entry; -use task_sensor_api::{NoData, Sensor as SensorApi, SensorId}; +use task_packrat_api::Packrat; +use task_sensor_api::{ + NoData, Sensor as SensorApi, SensorId, + config::{MAX_COMPONENT_ID_LEN, MAX_SENSOR_NAME_LEN}, +}; use task_thermal_api::{ FanProperties, SensorReadError, ThermalAutoState, ThermalProperties, }; @@ -208,8 +213,10 @@ pub enum FanState { #[allow(dead_code)] // Not all bsps have fans! #[derive(Copy, Clone, PartialEq)] pub enum FanPresentState { - /// The fan is physically present, but is unresponsive to RPM queries - Unresponsive(SensorReadError), + /// The fan is present but has not yet been polled + Unpolled, + /// The fan is present, but is unresponsive to RPM queries + I2cReadError(drv_i2c_api::ResponseCode), /// The fan is present and at a reasonable speed Nominal(Rpm), /// The fan is present, but is overspeed @@ -238,18 +245,29 @@ pub struct Fan { pub cur_state: FanState, /// A BSP-specific ID used to identify the fan pub bsp_data: D, + /// Parameter model for this fan + pub model: FanProperties, + pub component_id: fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>, + pub name: fixedstr::FixedStr<'static, MAX_SENSOR_NAME_LEN>, } #[allow(dead_code)] // Not all bsps have fans! impl Fan { /// Create a new fan - pub const fn new(rpm_sensor_id: SensorId, bsp_data: D) -> Self { + pub const fn new( + rpm_sensor_id: SensorId, + model: FanProperties, + bsp_data: D, + ) -> Self { Self { rpm_sensor_id, presence_acked: false, state_acked: false, cur_state: FanState::NotPresent, bsp_data, + model, + name: rpm_sensor_id.name(), + component_id: rpm_sensor_id.component_id(), } } @@ -273,9 +291,7 @@ impl Fan { pub(crate) fn update_presence(&mut self, is_present: bool) { match (is_present, self.cur_state) { (true, FanState::NotPresent) => { - self.update_state(FanState::Present( - FanPresentState::Unresponsive(SensorReadError::NoData), - )) + self.update_state(FanState::Present(FanPresentState::Unpolled)) } (true, _) => {} (false, _) => { @@ -308,31 +324,27 @@ impl Fan { } // Present -> Present (Fs::Present(cur), Fs::Present(newp)) => match (cur, newp) { - // Same -> Same, just take state (Fps::Nominal(_), Fps::Nominal(_)) | (Fps::TooFast(_), Fps::TooFast(_)) | (Fps::TooSlow(_), Fps::TooSlow(_)) - | (Fps::Unresponsive(_), Fps::Unresponsive(_)) => { + | (Fps::I2cReadError(_), Fps::I2cReadError(_)) + | (Fps::Unpolled, Fps::Unpolled) => { self.cur_state = new; } - // Any of the following: - // - // - Nominal -> Deviant - // - Deviant -> Nominal - // - Deviant -> Deviant - // - // Take: - // - // - New state - // - Status ack state (Fps::Nominal(_), _) | (_, Fps::Nominal(_)) - | (Fps::TooFast(_), Fps::Unresponsive(_)) + | (Fps::TooFast(_), Fps::Unpolled) | (Fps::TooFast(_), Fps::TooSlow(_)) - | (Fps::TooSlow(_), Fps::Unresponsive(_)) + | (Fps::TooFast(_), Fps::I2cReadError(_)) + | (Fps::TooSlow(_), Fps::Unpolled) | (Fps::TooSlow(_), Fps::TooFast(_)) - | (Fps::Unresponsive(_), Fps::TooFast(_)) - | (Fps::Unresponsive(_), Fps::TooSlow(_)) => { + | (Fps::TooSlow(_), Fps::I2cReadError(_)) + | (Fps::Unpolled, Fps::TooFast(_)) + | (Fps::Unpolled, Fps::TooSlow(_)) + | (Fps::Unpolled, Fps::I2cReadError(_)) + | (Fps::I2cReadError(_), Fps::Unpolled) + | (Fps::I2cReadError(_), Fps::TooFast(_)) + | (Fps::I2cReadError(_), Fps::TooSlow(_)) => { self.cur_state = new; self.state_acked = false; } @@ -342,10 +354,9 @@ impl Fan { /// Update the RPM of a present fan with the given closure, which should /// retrieve the RPM. Used to share logic across different fan controllers - pub(crate) fn poll_rpm_with>( + pub(crate) fn poll_rpm_with( &mut self, - model: &FanProperties, - poll_rpm: impl FnOnce() -> Result, + poll_rpm: impl FnOnce() -> Result, ) { // If this fan is not present, then do not attempt to poll it. Presence // is only restored via presence polling. @@ -359,9 +370,9 @@ impl Fan { Ok(rpm) => { // The poll went well! Use the model to determine if this // reading is nominal or not, and report that as the state. - let state = if rpm < model.underspeed_rpm { + let state = if rpm < self.model.underspeed_rpm { FanPresentState::TooSlow(rpm) - } else if rpm > model.overspeed_rpm { + } else if rpm > self.model.overspeed_rpm { FanPresentState::TooFast(rpm) } else { FanPresentState::Nominal(rpm) @@ -371,7 +382,7 @@ impl Fan { Err(e) => { // No good, mark as unresponsive self.update_state(FanState::Present( - FanPresentState::Unresponsive(e.into()), + FanPresentState::I2cReadError(e), )); } } @@ -576,6 +587,9 @@ pub(crate) struct ThermalControl<'a, B: BspInterface> { /// Task to which we should post sensor data updates sensor_api: SensorApi, + /// Task to which we should post ereports + ereporter: Ereporter, + /// Target temperature margin. This must be >= 0; as it increases, parts /// are kept cooler than their target temperature value. target_margin: Celsius, @@ -909,7 +923,11 @@ impl<'a, B: BspInterface> ThermalControl<'a, B> { /// # Panics /// This function can only be called once, because it claims mutable static /// buffers. - pub fn new(bsp: &'a mut B, sensor_api: SensorApi) -> Self { + pub fn new( + bsp: &'a mut B, + sensor_api: SensorApi, + packrat_api: Packrat, + ) -> Self { use static_cell::ClaimOnceCell; let [err_blackbox, prev_err_blackbox] = { @@ -933,6 +951,7 @@ impl<'a, B: BspInterface> ThermalControl<'a, B> { prev_err_blackbox, fan_watchdog_configured: false, overheat_timer: None, + ereporter: Ereporter::claim_static_resources(packrat_api), } } @@ -1028,7 +1047,7 @@ impl<'a, B: BspInterface> ThermalControl<'a, B> { // Read fan data and log it to the sensors task let now = sys_get_timer().now; for fan in self.bsp.poll_fan_rpms() { - report_fan_state(fan, &self.sensor_api, now); + report_fan_state(fan, &self.sensor_api, now, &mut self.ereporter); } // Read miscellaneous temperature data and log it to the sensors task @@ -1536,7 +1555,12 @@ impl<'a, B: BspInterface> ThermalControl<'a, B> { /// - Sensor API data /// - Ringbuf logging on state changes /// - ereport logging on state changes -fn report_fan_state(fan: &mut Fan, sensor_api: &SensorApi, now_ms: u64) { +fn report_fan_state( + fan: &mut Fan, + sensor_api: &SensorApi, + now_ms: u64, + ereporter: &mut Ereporter, +) { // Make state matches a little less verbose use FanPresentState as Fps; use FanState as Fs; @@ -1544,11 +1568,22 @@ fn report_fan_state(fan: &mut Fan, sensor_api: &SensorApi, now_ms: u64) { // Step one: report presence, if necessary let id = fan.rpm_sensor_id; if !fan.presence_acked { - let trace = match fan.cur_state { - Fs::NotPresent => Trace::FanRemoved(id), - Fs::Present(_) => Trace::FanAdded(id), + match fan.cur_state { + Fs::NotPresent => { + ringbuf_entry!(Trace::FanRemoved(id)); + _ = ereporter.deliver_ereport(&FanRemoved { + name: fan.name, + component_id: fan.component_id, + }); + } + Fs::Present(_) => { + ringbuf_entry!(Trace::FanAdded(id)); + _ = ereporter.deliver_ereport(&FanInserted { + name: fan.name, + component_id: fan.component_id, + }) + } }; - ringbuf_entry!(trace); fan.presence_acked = true; } @@ -1565,7 +1600,7 @@ fn report_fan_state(fan: &mut Fan, sensor_api: &SensorApi, now_ms: u64) { }; match pres { // If the fan is unresponsive, clear the data from the sensor API - Fps::Unresponsive(_) => { + Fps::Unpolled | Fps::I2cReadError(_) => { sensor_api.nodata(id, NoData::DeviceUnavailable, now_ms); } // If we have valid RPM data, report it immediately. @@ -1576,13 +1611,125 @@ fn report_fan_state(fan: &mut Fan, sensor_api: &SensorApi, now_ms: u64) { // Step three: handle state reporting, if unreported if !fan.state_acked { - let trace = match pres { - Fps::Unresponsive(e) => Trace::FanReadFailed(id, e), - Fps::Nominal(_) => Trace::FanNominal(id), - Fps::TooFast(rpm) => Trace::FanOverspeed(id, rpm), - Fps::TooSlow(rpm) => Trace::FanUnderspeed(id, rpm), + let fan_info = || FanInfo { + name: fan.name, + component_id: fan.component_id, + lo_rpm_lim: fan.model.underspeed_rpm.0, + hi_rpm_lim: fan.model.overspeed_rpm.0, + }; + match pres { + Fps::I2cReadError(e) => { + _ = ereporter.deliver_ereport(&FanRpmReadFailed { + name: fan.name, + component_id: fan.component_id, + raw_response_code: e as u8, + }); + ringbuf_entry!(Trace::FanReadFailed( + id, + SensorReadError::I2cError(e) + )); + } + Fps::Nominal(_) => { + _ = ereporter.deliver_ereport(&FanNominal { info: fan_info() }); + ringbuf_entry!(Trace::FanNominal(id)); + } + Fps::TooFast(rpm) => { + _ = ereporter.deliver_ereport(&FanOverspeed { + info: fan_info(), + rpm: rpm.0, + }); + ringbuf_entry!(Trace::FanOverspeed(id, rpm)); + } + Fps::TooSlow(rpm) => { + _ = ereporter.deliver_ereport(&FanUnderspeed { + info: fan_info(), + rpm: rpm.0, + }); + ringbuf_entry!(Trace::FanUnderspeed(id, rpm)); + } + Fps::Unpolled => { + // This is likely a bug, this means that a BSP failed to call + // `poll_rpm_with`. Don't panic, because that's worse than just + // not monitoring the fan at all, but ringbuf so we can catch + // it while developing. + ringbuf_entry!(Trace::FanUnpolled(id)); + } }; - ringbuf_entry!(trace); fan.state_acked = true; } } + +ereports::declare_ereporter! { + struct Ereporter { + FanRemoved(FanRemoved), + FanInserted(FanInserted), + FanNominal(FanNominal), + FanOverspeed(FanOverspeed), + FanUnderspeed(FanUnderspeed), + FanRpmReadFailed(FanRpmReadFailed), + } +} + +#[derive(microcbor::EncodeFields)] +struct FanInfo { + name: fixedstr::FixedStr<'static, MAX_SENSOR_NAME_LEN>, + component_id: fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>, + lo_rpm_lim: u16, + hi_rpm_lim: u16, +} + +/// An ereport representing a fan being removed +#[derive(Encode)] +#[ereport(class = "hw.remove.fan", version = 0)] +struct FanRemoved { + name: fixedstr::FixedStr<'static, MAX_SENSOR_NAME_LEN>, + component_id: fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>, +} + +/// An ereport representing a fan being inserted +#[derive(Encode)] +#[ereport(class = "hw.insert.fan", version = 0)] +struct FanInserted { + name: fixedstr::FixedStr<'static, MAX_SENSOR_NAME_LEN>, + component_id: fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>, +} + +/// An ereport representing a fan entering a nominal state +#[derive(Encode)] +#[ereport(class = "hw.fan.ok", version = 0)] +struct FanNominal { + #[cbor(flatten)] + info: FanInfo, +} + +/// An ereport representing a fan becoming overspeed +#[derive(Encode)] +#[ereport(class = "hw.fan.rpm.hi", version = 0)] +struct FanOverspeed { + #[cbor(flatten)] + info: FanInfo, + rpm: u16, +} + +/// An ereport representing a fan becoming underspeed +#[derive(Encode)] +#[ereport(class = "hw.fan.rpm.lo", version = 0)] +struct FanUnderspeed { + #[cbor(flatten)] + info: FanInfo, + rpm: u16, +} + +/// An ereport representing a failure to read from a fan +#[derive(Encode)] +#[ereport(class = "hw.fan.rpm.err", version = 0)] +struct FanRpmReadFailed { + name: fixedstr::FixedStr<'static, MAX_SENSOR_NAME_LEN>, + component_id: fixedstr::FixedStr<'static, MAX_COMPONENT_ID_LEN>, + /// The raw I2C driver code reported when this query failed. This value + /// is not stable across versions of the SP firmware, and should only + /// be logged or used for interactive or post-mortem debugging. + /// Requires knowledge of the exact firmware revision to meaningfully + /// decode. + raw_response_code: u8, +} diff --git a/task/thermal/src/main.rs b/task/thermal/src/main.rs index 9383e0185..93c95dc3f 100644 --- a/task/thermal/src/main.rs +++ b/task/thermal/src/main.rs @@ -51,6 +51,7 @@ use drv_i2c_api::ResponseCode; use drv_i2c_devices::max31790::I2cWatchdog; use idol_runtime::{NotificationHandler, RequestError}; use ringbuf::*; +use task_packrat_api::Packrat; use task_sensor_api::{Sensor as SensorApi, SensorId}; use task_thermal_api::{ SensorReadError, ThermalAutoState, ThermalError, ThermalMode, @@ -62,6 +63,7 @@ use userlib::{ }; task_slot!(I2C, i2c_driver); +task_slot!(PACKRAT, packrat); task_slot!(SENSOR, sensor); #[derive(Copy, Clone, PartialEq, counters::Count)] @@ -125,6 +127,7 @@ enum Trace { /// because an entry with two u64s doubles the size of the ringbuf. #[count(skip)] CriticalFor(u64), + /// Fan is present and read failed FanReadFailed(SensorId, SensorReadError), MiscReadFailed(SensorId, SensorReadError), SensorReadFailed(SensorId, SensorReadError), @@ -158,6 +161,8 @@ enum Trace { FanOverspeed(SensorId, Rpm), /// Fan is present and underspeed FanUnderspeed(SensorId, Rpm), + /// Fan is present but BSP didn't poll it + FanUnpolled(SensorId), } counted_ringbuf!(Trace, 32, Trace::None); @@ -394,11 +399,12 @@ impl<'a, B: control::BspInterface> NotificationHandler for ServerImpl<'a, B> { fn main() -> ! { let i2c_task = I2C.get_task_id(); let sensor_api = SensorApi::from(SENSOR.get_task_id()); + let packrat = Packrat::from(PACKRAT.get_task_id()); ringbuf_entry!(Trace::Start); let mut bsp = Bsp::new(i2c_task); - let control = ThermalControl::new(&mut bsp, sensor_api); + let control = ThermalControl::new(&mut bsp, sensor_api, packrat); // This will put our timer in the past, and should immediately kick us. let deadline = sys_get_timer().now;