From 491e98f17c8a7384d3f71d4a8ecfcb036e9fe3a6 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Wed, 20 Nov 2024 20:09:53 -0800 Subject: [PATCH 01/11] wip --- Cargo.lock | 22 +++++- Cargo.toml | 1 + app/psc/base.toml | 9 ++- drv/i2c-devices/src/mwocp68.rs | 17 +++++ drv/psc-psu-update/Cargo.toml | 26 +++++++ drv/psc-psu-update/src/main.rs | 127 +++++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 drv/psc-psu-update/Cargo.toml create mode 100644 drv/psc-psu-update/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 3bf1cf0cfd..c181d72bb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,12 @@ dependencies = [ "nodrop", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "arrayvec" version = "0.7.4" @@ -1753,6 +1759,20 @@ dependencies = [ "userlib", ] +[[package]] +name = "drv-psc-psu-update" +version = "0.1.0" +dependencies = [ + "array-init 2.1.0", + "build-i2c", + "build-util", + "drv-i2c-api", + "drv-i2c-devices", + "ringbuf", + "static-cell", + "userlib", +] + [[package]] name = "drv-psc-seq-api" version = "0.1.0" @@ -4342,7 +4362,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" dependencies = [ - "array-init", + "array-init 0.0.4", "serde", "smallvec 0.6.14", ] diff --git a/Cargo.toml b/Cargo.toml index 1c376057b9..80a83aaf7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ path = "lib/counters" [workspace.dependencies] anyhow = { version = "1.0.31", default-features = false, features = ["std"] } +array-init = { version = "2.1.0" } arrayvec = { version = "0.7.4", default-features = false } atty = { version = "0.2", default-features = false } bitfield = { version = "0.13", default-features = false } diff --git a/app/psc/base.toml b/app/psc/base.toml index 55396ba017..1483f14bdb 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -6,7 +6,7 @@ fwid = true [kernel] name = "psc" -requires = {flash = 32868, ram = 5216} +requires = {flash = 32868, ram = 6000} features = ["dump"] [caboose] @@ -298,6 +298,13 @@ max-sizes = {flash = 16384, ram = 2048 } start = true task-slots = ["i2c_driver", "sensor"] +[tasks.psu_update] +name = "drv-psc-psu-update" +priority = 4 +max-sizes = {flash = 16384, ram = 8192 } +start = true +task-slots = ["i2c_driver"] + [tasks.dump_agent] name = "task-dump-agent" priority = 5 diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index 5dccabb02e..8e5382478a 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -17,6 +17,8 @@ use pmbus::*; use task_power_api::PmbusValue; use userlib::units::{Amperes, Volts}; +pub type Mwocp68FirmwareRev = [u8; 4]; + pub struct Mwocp68 { device: I2cDevice, @@ -350,6 +352,21 @@ impl Mwocp68 { Ok(val) } + /// Will return true if the device is present and valid -- false otherwise + pub fn present(&self) -> bool { + match Mwocp68::validate(&self.device) { + Ok(valid) => valid, + _ => false + } + } + + pub fn power_good(&self) -> Result { + use commands::mwocp68::STATUS_WORD::*; + + let status = pmbus_read!(self.device, STATUS_WORD)?; + Ok(status.get_power_good_status() == Some(PowerGoodStatus::PowerGood)) + } + pub fn i2c_device(&self) -> &I2cDevice { &self.device } diff --git a/drv/psc-psu-update/Cargo.toml b/drv/psc-psu-update/Cargo.toml new file mode 100644 index 0000000000..921427c300 --- /dev/null +++ b/drv/psc-psu-update/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "drv-psc-psu-update" +description = "Task for PSU firmware update" +version = "0.1.0" +edition = "2021" + +[dependencies] +drv-i2c-api = { path = "../i2c-api" } +drv-i2c-devices = { path = "../i2c-devices" } +ringbuf = { path = "../../lib/ringbuf" } +userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +static-cell = { path = "../../lib/static-cell" } +array-init.workspace = true + +[build-dependencies] +build-util = {path = "../../build/util"} +build-i2c = { path = "../../build/i2c" } + +[[bin]] +name = "drv-psc-psu-update" +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs new file mode 100644 index 0000000000..1d35ceb935 --- /dev/null +++ b/drv/psc-psu-update/src/main.rs @@ -0,0 +1,127 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#![no_std] +#![no_main] + +use drv_i2c_devices::mwocp68::{Error as Mwocp68Error, Mwocp68}; +use ringbuf::*; +use userlib::*; +use drv_i2c_api::*; +use drv_i2c_devices::Validate; +use static_cell::ClaimOnceCell; + +task_slot!(I2C, i2c_driver); + +const TIMER_INTERVAL: u64 = 10000; + +use i2c_config::devices; + +#[cfg(any(target_board = "psc-b", target_board = "psc-c"))] +static DEVICES: [fn(TaskId) -> I2cDevice; 6] = [ + devices::mwocp68_psu0mcu, + devices::mwocp68_psu1mcu, + devices::mwocp68_psu2mcu, + devices::mwocp68_psu3mcu, + devices::mwocp68_psu4mcu, + devices::mwocp68_psu5mcu, +]; + +static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new([Psu { + last_checked: None, + present: None, + power_good: None, + firmware_matches: None, + update_started: None, + update_failed: None, +}; 6]); + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum Trace { + None, + Start, + PowerGoodFailed(u8, drv_i2c_devices::mwocp68::Error), + Psu(u8), +} + +ringbuf!(Trace, 32, Trace::None); + +#[derive(Copy, Clone)] +struct Ticks(u64); + +#[derive(Copy, Clone)] +struct Psu { + /// When did we last check this device? + last_checked: Option, + + /// Is the device physically present? + present: Option, + + /// Is the device on and with POWER_GOOD set? + power_good: Option, + +// firmware_rev: Option + + /// Does the firmware we have match the firmware here? + firmware_matches: Option, + + /// What time did we start an update? + update_started: Option, + + /// What time did the update last fail, if any? + update_failed: Option, +} + +#[export_name = "main"] +fn main() -> ! { + let i2c_task = I2C.get_task_id(); + + ringbuf_entry!(Trace::Start); + + let mut psus = PSU.claim(); + + let devs: [Mwocp68; 6] = array_init::array_init(|ndx: usize| { + Mwocp68::new(&DEVICES[ndx](i2c_task), 0) + }); + + loop { + hl::sleep_for(TIMER_INTERVAL); + + for (ndx, psu) in psus.iter_mut().enumerate() { + let dev = &devs[ndx]; + + psu.last_checked = Some(Ticks(sys_get_timer().now)); + + // + // We're going to check all of these fields. + // + psu.power_good = None; + psu.firmware_matches = None; + + if !dev.present() { + psu.present = Some(false); + continue; + } + + psu.present = Some(true); + + match dev.power_good() { + Ok(power_good) => { + psu.power_good = Some(power_good); + + if !power_good { + continue; + } + } + + Err(err) => { + ringbuf_entry!(Trace::PowerGoodFailed(ndx as u8, err)); + continue; + } + } + } + } +} + +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); From bc4dad45c6a8c218a4b61233a945bdd442a4d219 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Wed, 20 Nov 2024 23:27:09 -0800 Subject: [PATCH 02/11] wip --- drv/i2c-devices/src/mwocp68.rs | 58 +++++++++- drv/psc-psu-update/src/main.rs | 187 +++++++++++++++++++++++++------- task/sensor-polling/src/main.rs | 1 + 3 files changed, 202 insertions(+), 44 deletions(-) diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index 8e5382478a..bdaba6bf20 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -17,7 +17,8 @@ use pmbus::*; use task_power_api::PmbusValue; use userlib::units::{Amperes, Volts}; -pub type Mwocp68FirmwareRev = [u8; 4]; +#[derive(Copy, Clone, PartialEq)] +pub struct Mwocp68FirmwareRev(pub [u8; 4]); pub struct Mwocp68 { device: I2cDevice, @@ -36,6 +37,9 @@ pub enum Error { BadData { cmd: u8 }, BadValidation { cmd: u8, code: ResponseCode }, InvalidData { err: pmbus::Error }, + BadFirmwareRevRead { code: ResponseCode }, + BadFirmwareRev, + BadFirmwareRevLength, } impl From for Error { @@ -64,6 +68,22 @@ impl From for Error { } } +// const MWOCP68_MFR_ID: &str = "Murata-PS"; +// const MWOCP68_MFR_MODEL: &str = "MWOCP68-3600-D-RM"; +// const MWOCP68_BOOT_LOADER_KEY: &str = "InVe"; +// const MWOCP68_PRODUCT_KEY: &str = "M5813-0000000000"; + +const MWOCP68_REVISION_LEN: usize = 14; +const MWOCP68_REVISION_FORMAT: &[u8; MWOCP68_REVISION_LEN] = b"XXXX-YYYY-0000"; + +const MWOCP68_KEY_DELAY: u64 = 3; +const MWOCP68_BOOT_DELAY: u64 = 1; +const MWOCP68_RESET_DELAY: u64 = 2; +const MWOCP68_BLOCK_LENGTH: usize = 32; +const MWOCP68_BLOCK_DELAY_MS: u8 = 100; +const MWOCP68_CHECKSUM_DELAY: u64 = 2; +const MWOCP68_REBOOT_DELAY: u64 = 5; + impl Mwocp68 { pub fn new(device: &I2cDevice, index: u8) -> Self { Mwocp68 { @@ -356,7 +376,7 @@ impl Mwocp68 { pub fn present(&self) -> bool { match Mwocp68::validate(&self.device) { Ok(valid) => valid, - _ => false + _ => false, } } @@ -367,6 +387,40 @@ impl Mwocp68 { Ok(status.get_power_good_status() == Some(PowerGoodStatus::PowerGood)) } + pub fn firmware_revision(&self) -> Result { + let mut data = [0u8; MWOCP68_REVISION_LEN]; + + let len = self + .device + .read_block(CommandCode::MFR_REVISION as u8, &mut data) + .map_err(|code| Error::BadFirmwareRevRead { code })?; + + // + // Per ACAN-114, we are expecting this to be of the format: + // + // XXXX-YYYY-0000 + // + // Where XXXX is the firmware revision on the primary MCU (AC input + // side) and YYYY is the firmware revision on the secondary MCU (DC + // output side). + // + if len != MWOCP68_REVISION_LEN { + return Err(Error::BadFirmwareRevLength); + } + + /* + for ndx in 0.. + for (ndx, val) in expected.iter() { + if data[ndx] != val { + return Err(Error::BadFirmwareRev); + } + } + + */ + + Ok(Mwocp68FirmwareRev([data[0], data[1], data[2], data[3]])) + } + pub fn i2c_device(&self) -> &I2cDevice { &self.device } diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index 1d35ceb935..50c8c2fdc0 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -5,12 +5,16 @@ #![no_std] #![no_main] -use drv_i2c_devices::mwocp68::{Error as Mwocp68Error, Mwocp68}; -use ringbuf::*; -use userlib::*; use drv_i2c_api::*; +use drv_i2c_devices::mwocp68::{ + Error as Mwocp68Error, Mwocp68, Mwocp68FirmwareRev, +}; use drv_i2c_devices::Validate; +use ringbuf::*; use static_cell::ClaimOnceCell; +use userlib::*; + +use core::ops::Add; task_slot!(I2C, i2c_driver); @@ -28,29 +32,54 @@ static DEVICES: [fn(TaskId) -> I2cDevice; 6] = [ devices::mwocp68_psu5mcu, ]; -static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new([Psu { - last_checked: None, - present: None, - power_good: None, - firmware_matches: None, - update_started: None, - update_failed: None, -}; 6]); +static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new( + [Psu { + last_checked: None, + present: None, + power_good: None, + firmware_matches: None, + firmware_revision: None, + update_started: None, + update_failed: None, + update_backoff: None, + }; 6], +); #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Trace { None, Start, PowerGoodFailed(u8, drv_i2c_devices::mwocp68::Error), + FirmwareRevFailed(u8, drv_i2c_devices::mwocp68::Error), Psu(u8), + AttemptingUpdate(u8), + BackingOff(u8), } +const MWOCP68_FIRMWARE_REV: Mwocp68FirmwareRev = Mwocp68FirmwareRev(*b"0762"); +const MWOCP68_FIRMWARE_PAYLOAD: &'static [u8] = + include_bytes!("mwocp68-0762.bin"); + ringbuf!(Trace, 32, Trace::None); -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialOrd, PartialEq)] struct Ticks(u64); -#[derive(Copy, Clone)] +impl Ticks { + fn now() -> Self { + Self(sys_get_timer().now) + } +} + +impl Add for Ticks { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +#[derive(Copy, Clone, Default)] struct Psu { /// When did we last check this device? last_checked: Option, @@ -61,7 +90,8 @@ struct Psu { /// Is the device on and with POWER_GOOD set? power_good: Option, -// firmware_rev: Option + /// The last firmware revision read + firmware_revision: Option, /// Does the firmware we have match the firmware here? firmware_matches: Option, @@ -71,6 +101,105 @@ struct Psu { /// What time did the update last fail, if any? update_failed: Option, + + /// How long should the next update backoff, if at all? (In ticks.) + update_backoff: Option, +} + +impl Psu { + fn update_should_be_attempted(&mut self, dev: &Mwocp68, ndx: u8) -> bool { + let now = Ticks::now(); + + self.last_checked = Some(now); + self.power_good = None; + self.firmware_matches = None; + self.firmware_revision = None; + + if !dev.present() { + self.present = Some(false); + + // + // If we are seeing our device as not present, we will clear our + // backoff value: if/when a PSU is plugged back in, we want to + // attempt to update it immediately if the firmware revision + // doesn't match our payload. + // + self.update_backoff = None; + return false; + } + + self.present = Some(true); + + match dev.power_good() { + Ok(power_good) => { + self.power_good = Some(power_good); + + if !power_good { + return false; + } + } + Err(err) => { + ringbuf_entry!(Trace::PowerGoodFailed(ndx, err)); + return false; + } + } + + match dev.firmware_revision() { + Ok(revision) => { + self.firmware_revision = Some(revision); + + if revision == MWOCP68_FIRMWARE_REV { + self.firmware_matches = Some(true); + return false; + } + + self.firmware_matches = Some(false); + } + Err(err) => { + ringbuf_entry!(Trace::FirmwareRevFailed(ndx, err)); + return false; + } + } + + if let (Some(started), Some(backoff)) = + (self.update_started, self.update_backoff) + { + if started + backoff > now { + ringbuf_entry!(Trace::BackingOff(ndx)); + return false; + } + } + + true + } + + fn update_firmware(&mut self, dev: &Mwocp68, ndx: u8) { + ringbuf_entry!(Trace::AttemptingUpdate(ndx)); + self.update_started = Some(Ticks::now()); + + self.update_backoff = match self.update_backoff { + Some(backoff) => Some(Ticks(backoff.0 * 2)), + None => Some(Ticks(20000)), + }; + + /* + // + // + write_boot_key, false + write_product_key + boot_into_boot_loader + + indicate_write_start + + fo XC + + write_block + + checksum() + reboot() + + */ + } } #[export_name = "main"] @@ -91,34 +220,8 @@ fn main() -> ! { for (ndx, psu) in psus.iter_mut().enumerate() { let dev = &devs[ndx]; - psu.last_checked = Some(Ticks(sys_get_timer().now)); - - // - // We're going to check all of these fields. - // - psu.power_good = None; - psu.firmware_matches = None; - - if !dev.present() { - psu.present = Some(false); - continue; - } - - psu.present = Some(true); - - match dev.power_good() { - Ok(power_good) => { - psu.power_good = Some(power_good); - - if !power_good { - continue; - } - } - - Err(err) => { - ringbuf_entry!(Trace::PowerGoodFailed(ndx as u8, err)); - continue; - } + if psu.update_should_be_attempted(dev, ndx as u8) { + psu.update_firmware(dev, ndx as u8); } } } diff --git a/task/sensor-polling/src/main.rs b/task/sensor-polling/src/main.rs index aef3d21fae..9da0ce043c 100644 --- a/task/sensor-polling/src/main.rs +++ b/task/sensor-polling/src/main.rs @@ -36,6 +36,7 @@ impl From for task_sensor_api::NoData { | Mwocp68Error::BadValidation { code, .. } => code.into(), Mwocp68Error::BadData { .. } | Mwocp68Error::InvalidData { .. } => Self::DeviceError, + _ => Self::DeviceError, }, } } From 5936a85edd0546cd3dfa0a81acef72d11ebccb19 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Thu, 21 Nov 2024 21:21:59 -0800 Subject: [PATCH 03/11] wip --- Cargo.lock | 4 +- Cargo.toml | 2 +- drv/i2c-devices/src/mwocp68.rs | 173 +++++++++++++++++++++++++++++---- drv/psc-psu-update/src/main.rs | 62 ++++++++---- 4 files changed, 203 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c181d72bb0..f2f42c25dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3895,8 +3895,8 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "pmbus" -version = "0.1.1" -source = "git+https://github.com/oxidecomputer/pmbus#735a70bbc90707a963ec1731c8a0fa427a013f21" +version = "0.1.3" +source = "git+https://github.com/oxidecomputer/pmbus?branch=mwocp#0b3cdb8a4679efa76d40bbfe3277490a78ee5267" dependencies = [ "anyhow", "convert_case", diff --git a/Cargo.toml b/Cargo.toml index 80a83aaf7a..20f0fe3aec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,7 +133,7 @@ idol = { git = "https://github.com/oxidecomputer/idolatry.git", default-features idol-runtime = { git = "https://github.com/oxidecomputer/idolatry.git", default-features = false } lpc55_sign = { git = "https://github.com/oxidecomputer/lpc55_support", default-features = false } ordered-toml = { git = "https://github.com/oxidecomputer/ordered-toml", default-features = false } -pmbus = { git = "https://github.com/oxidecomputer/pmbus", default-features = false } +pmbus = { git = "https://github.com/oxidecomputer/pmbus", branch = "mwocp", default-features = false } salty = { version = "0.3", default-features = false } spd = { git = "https://github.com/oxidecomputer/spd", default-features = false } tlvc = { git = "https://github.com/oxidecomputer/tlvc", default-features = false, version = "0.3.1" } diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index bdaba6bf20..2eac46f109 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -38,8 +38,11 @@ pub enum Error { BadValidation { cmd: u8, code: ResponseCode }, InvalidData { err: pmbus::Error }, BadFirmwareRevRead { code: ResponseCode }, - BadFirmwareRev, + BadFirmwareRev { index: u8 }, BadFirmwareRevLength, + UpdateInBootLoader, + UpdateNotInBootLoader, + UpdateAlreadySuccessful, } impl From for Error { @@ -70,19 +73,42 @@ impl From for Error { // const MWOCP68_MFR_ID: &str = "Murata-PS"; // const MWOCP68_MFR_MODEL: &str = "MWOCP68-3600-D-RM"; -// const MWOCP68_BOOT_LOADER_KEY: &str = "InVe"; -// const MWOCP68_PRODUCT_KEY: &str = "M5813-0000000000"; +const MWOCP68_BOOT_LOADER_KEY: &[u8] = b"InVe"; +const MWOCP68_PRODUCT_KEY: &[u8] = b"M5813-0000000000"; const MWOCP68_REVISION_LEN: usize = 14; const MWOCP68_REVISION_FORMAT: &[u8; MWOCP68_REVISION_LEN] = b"XXXX-YYYY-0000"; -const MWOCP68_KEY_DELAY: u64 = 3; -const MWOCP68_BOOT_DELAY: u64 = 1; -const MWOCP68_RESET_DELAY: u64 = 2; const MWOCP68_BLOCK_LENGTH: usize = 32; -const MWOCP68_BLOCK_DELAY_MS: u8 = 100; -const MWOCP68_CHECKSUM_DELAY: u64 = 2; -const MWOCP68_REBOOT_DELAY: u64 = 5; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum UpdateState { + WroteBootLoaderKey, + WroteProductKey, + BootedBootLoader, + StartedProgramming, + WroteBlock { offset: usize, checksum: u64 }, + WroteLastBlock { checksum: u64 }, + SentChecksum, + VerifiedChecksum, + RebootedPSU, + UpdateSuccessful, +} + +impl UpdateState { + fn delay_ms(&self) -> u64 { + match self { + Self::WroteBootLoaderKey | Self::WroteProductKey => 3_000, + Self::BootedBootLoader => 1_000, + Self::StartedProgramming => 2_000, + Self::WroteBlock { .. } | Self::WroteLastBlock { .. } => 100, + Self::SentChecksum => 2_000, + Self::VerifiedChecksum => 4_000, + Self::RebootedPSU => 5_000, + Self::UpdateSuccessful => 0, + } + } +} impl Mwocp68 { pub fn new(device: &I2cDevice, index: u8) -> Self { @@ -389,6 +415,7 @@ impl Mwocp68 { pub fn firmware_revision(&self) -> Result { let mut data = [0u8; MWOCP68_REVISION_LEN]; + let expected = MWOCP68_REVISION_FORMAT; let len = self .device @@ -402,25 +429,135 @@ impl Mwocp68 { // // Where XXXX is the firmware revision on the primary MCU (AC input // side) and YYYY is the firmware revision on the secondary MCU (DC - // output side). + // output side). We aren't going to be rigid about the format of + // either revision, but we will be rigid about the rest of the format. // if len != MWOCP68_REVISION_LEN { return Err(Error::BadFirmwareRevLength); } - /* - for ndx in 0.. - for (ndx, val) in expected.iter() { - if data[ndx] != val { - return Err(Error::BadFirmwareRev); - } - } + for index in 0..len { + if expected[index] == b'X' || expected[index] == b'Y' { + continue; + } - */ + if data[index] != expected[index] { + return Err(Error::BadFirmwareRev { index: index as u8 }); + } + } Ok(Mwocp68FirmwareRev([data[0], data[1], data[2], data[3]])) } + fn get_boot_loader_mode(&self) -> Result { + let status = pmbus_read!(self.device, BOOT_LOADER_STATUS)?; + Ok(status.get_mode().unwrap()) + } + + pub fn update( + &self, + state: Option, + payload: &[u8], + ) -> Result<(UpdateState, u64), Error> { + use BOOT_LOADER_STATUS::Mode; + + let write_boot_loader_key = || -> Result { + Ok(UpdateState::WroteBootLoaderKey) + }; + + let write_product_key = || -> Result { + Ok(UpdateState::WroteProductKey) + }; + + let boot_boot_loader = || -> Result { + Ok(UpdateState::BootedBootLoader) + }; + + let start_programming = || -> Result { + Ok(UpdateState::StartedProgramming) + }; + + let write_block = || -> Result { + let (mut offset, mut checksum) = match state { + Some(UpdateState::WroteBlock { offset, checksum }) => { + (offset, checksum) + } + Some(UpdateState::StartedProgramming) => (0, 0), + _ => panic!(), + }; + + offset += MWOCP68_BLOCK_LENGTH; + + if offset >= payload.len() { + Ok(UpdateState::WroteLastBlock { checksum }) + } else { + Ok(UpdateState::WroteBlock { offset, checksum }) + } + }; + + let send_checksum = + || -> Result { Ok(UpdateState::SentChecksum) }; + + let verify_checksum = || -> Result { + Ok(UpdateState::VerifiedChecksum) + }; + + let reboot_psu = + || -> Result { Ok(UpdateState::RebootedPSU) }; + + let verify_success = || -> Result { + Ok(UpdateState::UpdateSuccessful) + }; + + // + // We want to confirm that our boot loader is in the state that + // we think it should be in. On the one hand, this will fail in + // a non-totally-unreasonable fashion if we don't check this -- but + // we have an opportunity to assert our in-device state and fail + // cleanly if it doesn't match, and it feels like we should take it. + // + let expected = match state { + None + | Some(UpdateState::WroteBootLoaderKey) + | Some(UpdateState::WroteProductKey) + | Some(UpdateState::RebootedPSU) => Mode::NotBootLoader, + + Some(UpdateState::BootedBootLoader) + | Some(UpdateState::StartedProgramming) + | Some(UpdateState::WroteBlock { .. }) + | Some(UpdateState::WroteLastBlock { .. }) + | Some(UpdateState::SentChecksum) + | Some(UpdateState::VerifiedChecksum) => Mode::BootLoader, + + Some(UpdateState::UpdateSuccessful) => { + return Err(Error::UpdateAlreadySuccessful); + } + }; + + if self.get_boot_loader_mode()? != expected { + return Err(match expected { + Mode::BootLoader => Error::UpdateNotInBootLoader, + Mode::NotBootLoader => Error::UpdateInBootLoader, + }); + } + + let next = match state { + None => write_boot_loader_key()?, + Some(UpdateState::WroteBootLoaderKey) => write_product_key()?, + Some(UpdateState::WroteProductKey) => boot_boot_loader()?, + Some(UpdateState::BootedBootLoader) => start_programming()?, + Some(UpdateState::StartedProgramming) + | Some(UpdateState::WroteBlock { .. }) => write_block()?, + Some(UpdateState::WroteLastBlock { .. }) => send_checksum()?, + Some(UpdateState::SentChecksum) => verify_checksum()?, + Some(UpdateState::VerifiedChecksum) => reboot_psu()?, + Some(UpdateState::RebootedPSU) => verify_success()?, + Some(UpdateState::UpdateSuccessful) => panic!(), + }; + + Ok((next, next.delay_ms())) + } + pub fn i2c_device(&self) -> &I2cDevice { &self.device } diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index 50c8c2fdc0..d5db200cad 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -7,7 +7,7 @@ use drv_i2c_api::*; use drv_i2c_devices::mwocp68::{ - Error as Mwocp68Error, Mwocp68, Mwocp68FirmwareRev, + Error as Mwocp68Error, Mwocp68, Mwocp68FirmwareRev, UpdateState, }; use drv_i2c_devices::Validate; use ringbuf::*; @@ -40,12 +40,13 @@ static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new( firmware_matches: None, firmware_revision: None, update_started: None, - update_failed: None, + update_succeeded: None, + update_failure: None, update_backoff: None, }; 6], ); -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] enum Trace { None, Start, @@ -54,6 +55,13 @@ enum Trace { Psu(u8), AttemptingUpdate(u8), BackingOff(u8), + UpdateFailed, + UpdateFailedState(Option), + UpdateFailure(Mwocp68Error), + UpdateState(UpdateState), + WroteBlock, + UpdateSucceeded, + UpdateDelay(u64), } const MWOCP68_FIRMWARE_REV: Mwocp68FirmwareRev = Mwocp68FirmwareRev(*b"0762"); @@ -99,8 +107,11 @@ struct Psu { /// What time did we start an update? update_started: Option, + /// What time did the update complete? + update_succeeded: Option, + /// What time did the update last fail, if any? - update_failed: Option, + update_failure: Option<(Ticks, Option, Mwocp68Error)>, /// How long should the next update backoff, if at all? (In ticks.) update_backoff: Option, @@ -179,26 +190,43 @@ impl Psu { self.update_backoff = match self.update_backoff { Some(backoff) => Some(Ticks(backoff.0 * 2)), - None => Some(Ticks(20000)), + None => Some(Ticks(60_000)), }; - /* - // - // - write_boot_key, false - write_product_key - boot_into_boot_loader + let mut state = None; - indicate_write_start + loop { + match dev.update(state, MWOCP68_FIRMWARE_PAYLOAD) { + Err(err) => { + // + // We failed. Record everything we can and leave. + // + ringbuf_entry!(Trace::UpdateFailed); + ringbuf_entry!(Trace::UpdateFailedState(state)); + ringbuf_entry!(Trace::UpdateFailure(err)); - fo XC + self.update_failure = Some((Ticks::now(), state, err)); + break; + } - write_block + Ok((UpdateState::UpdateSuccessful, _)) => { + ringbuf_entry!(Trace::UpdateSucceeded); + self.update_succeeded = Some(Ticks::now()); + break; + } - checksum() - reboot() + Ok((next, delay)) => { + ringbuf_entry!(match next { + UpdateState::WroteBlock { .. } => Trace::WroteBlock, + _ => Trace::UpdateState(next), + }); - */ + ringbuf_entry!(Trace::UpdateDelay(delay)); + hl::sleep_for(delay); + state = Some(next); + } + } + } } } From f8da0464985102571b071edb090778b62b56539e Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Fri, 22 Nov 2024 00:04:51 -0800 Subject: [PATCH 04/11] wip --- app/psc/base.toml | 2 +- drv/i2c-devices/src/mwocp68.rs | 170 +++++++++++++++++++++++++++++---- drv/psc-psu-update/src/main.rs | 14 ++- 3 files changed, 164 insertions(+), 22 deletions(-) diff --git a/app/psc/base.toml b/app/psc/base.toml index 1483f14bdb..9a303fa2e3 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -301,7 +301,7 @@ task-slots = ["i2c_driver", "sensor"] [tasks.psu_update] name = "drv-psc-psu-update" priority = 4 -max-sizes = {flash = 16384, ram = 8192 } +max-sizes = {flash = 65536, ram = 8192 } start = true task-slots = ["i2c_driver"] diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index 2eac46f109..17dcb8d639 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -14,6 +14,7 @@ use pmbus::commands::mwocp68::*; use pmbus::commands::CommandCode; use pmbus::units::{Celsius, Rpm}; use pmbus::*; +use ringbuf::*; use task_power_api::PmbusValue; use userlib::units::{Amperes, Volts}; @@ -30,21 +31,64 @@ pub struct Mwocp68 { mode: Cell>, } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] +enum BootLoaderCommand { + ClearStatus = 0x00, + RestartProgramming = 0x01, + BootPrimary = 0x12, + BootSecondary = 0x02, + BootPSUFirmware = 0x03, +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { - BadRead { cmd: u8, code: ResponseCode }, - BadWrite { cmd: u8, code: ResponseCode }, - BadData { cmd: u8 }, - BadValidation { cmd: u8, code: ResponseCode }, - InvalidData { err: pmbus::Error }, - BadFirmwareRevRead { code: ResponseCode }, - BadFirmwareRev { index: u8 }, + BadRead { + cmd: u8, + code: ResponseCode, + }, + BadWrite { + cmd: u8, + code: ResponseCode, + }, + BadData { + cmd: u8, + }, + BadValidation { + cmd: u8, + code: ResponseCode, + }, + InvalidData { + err: pmbus::Error, + }, + BadFirmwareRevRead { + code: ResponseCode, + }, + BadFirmwareRev { + index: u8, + }, BadFirmwareRevLength, UpdateInBootLoader, UpdateNotInBootLoader, UpdateAlreadySuccessful, + BadBootLoaderStatus { + data: u8, + }, + BadBootLoaderCommand { + cmd: BootLoaderCommand, + code: ResponseCode, + }, + ChecksumNotSuccessful, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum Trace { + None, + BootLoaderMode(u8), } +ringbuf!(Trace, 32, Trace::None); + impl From for Error { fn from(value: BadValidation) -> Self { Self::BadValidation { @@ -73,8 +117,6 @@ impl From for Error { // const MWOCP68_MFR_ID: &str = "Murata-PS"; // const MWOCP68_MFR_MODEL: &str = "MWOCP68-3600-D-RM"; -const MWOCP68_BOOT_LOADER_KEY: &[u8] = b"InVe"; -const MWOCP68_PRODUCT_KEY: &[u8] = b"M5813-0000000000"; const MWOCP68_REVISION_LEN: usize = 14; const MWOCP68_REVISION_FORMAT: &[u8; MWOCP68_REVISION_LEN] = b"XXXX-YYYY-0000"; @@ -449,9 +491,40 @@ impl Mwocp68 { Ok(Mwocp68FirmwareRev([data[0], data[1], data[2], data[3]])) } + fn get_boot_loader_status( + &self, + ) -> Result { + use pmbus::commands::mwocp68::CommandCode; + let cmd = CommandCode::BOOT_LOADER_STATUS as u8; + let mut data = [0u8; 1]; + + let len = self + .device + .read_block(cmd, &mut data) + .map_err(|code| Error::BadRead { cmd, code })?; + + ringbuf_entry!(Trace::BootLoaderMode(data[0])); + + match BOOT_LOADER_STATUS::CommandData::from_slice(&data[0..]) { + Some(status) => Ok(status), + None => Err(Error::BadBootLoaderStatus { data: data[0] }), + } + } + fn get_boot_loader_mode(&self) -> Result { - let status = pmbus_read!(self.device, BOOT_LOADER_STATUS)?; - Ok(status.get_mode().unwrap()) + Ok(self.get_boot_loader_status()?.get_mode().unwrap()) + } + + fn boot_loader_command(&self, cmd: BootLoaderCommand) -> Result<(), Error> { + use pmbus::commands::mwocp68::CommandCode; + + let data = [CommandCode::BOOT_LOADER_STATUS as u8, 1, cmd as u8]; + + self.device + .write(&data) + .map_err(|code| Error::BadBootLoaderCommand { cmd, code })?; + + Ok(()) } pub fn update( @@ -459,25 +532,51 @@ impl Mwocp68 { state: Option, payload: &[u8], ) -> Result<(UpdateState, u64), Error> { + use pmbus::commands::mwocp68::CommandCode; use BOOT_LOADER_STATUS::Mode; let write_boot_loader_key = || -> Result { + const MWOCP68_BOOT_LOADER_KEY: &[u8] = b"InVe"; + let mut data = [0u8; MWOCP68_BOOT_LOADER_KEY.len() + 2]; + + data[0] = CommandCode::BOOT_LOADER_KEY as u8; + data[1] = MWOCP68_BOOT_LOADER_KEY.len() as u8; + data[2..].copy_from_slice(MWOCP68_BOOT_LOADER_KEY); + + self.device + .write(&data) + .map_err(|code| Error::BadWrite { cmd: data[0], code })?; + Ok(UpdateState::WroteBootLoaderKey) }; let write_product_key = || -> Result { + const MWOCP68_PRODUCT_KEY: &[u8] = b"M5813-0000000000"; + let mut data = [0u8; MWOCP68_PRODUCT_KEY.len() + 1]; + + data[0] = CommandCode::BOOT_LOADER_PRODUCT_KEY as u8; + data[1..].copy_from_slice(MWOCP68_PRODUCT_KEY); + + self.device + .write(&data) + .map_err(|code| Error::BadWrite { cmd: data[0], code })?; + Ok(UpdateState::WroteProductKey) }; let boot_boot_loader = || -> Result { + self.boot_loader_command(BootLoaderCommand::BootPrimary)?; Ok(UpdateState::BootedBootLoader) }; let start_programming = || -> Result { + self.boot_loader_command(BootLoaderCommand::RestartProgramming)?; Ok(UpdateState::StartedProgramming) }; let write_block = || -> Result { + const len: usize = MWOCP68_BLOCK_LENGTH; + let (mut offset, mut checksum) = match state { Some(UpdateState::WroteBlock { offset, checksum }) => { (offset, checksum) @@ -486,6 +585,15 @@ impl Mwocp68 { _ => panic!(), }; + let mut data = [0u8; len + 1]; + data[0] = CommandCode::BOOT_LOADER_MEMORY_BLOCK as u8; + data[1..].copy_from_slice(&payload[offset..offset + len]); + + self.device + .write(&data) + .map_err(|code| Error::BadWrite { cmd: data[0], code })?; + + checksum = data[1..].iter().fold(checksum, |c, &d| c + d as u64); offset += MWOCP68_BLOCK_LENGTH; if offset >= payload.len() { @@ -495,15 +603,45 @@ impl Mwocp68 { } }; - let send_checksum = - || -> Result { Ok(UpdateState::SentChecksum) }; + let send_checksum = || -> Result { + let mut data = [0u8; 4]; + + let checksum = match state { + Some(UpdateState::WroteLastBlock { checksum }) => checksum, + _ => panic!(), + }; + + data[0] = CommandCode::IMAGE_CHECKSUM as u8; + data[1] = 2; + data[2] = (checksum & 0xff) as u8; + data[3] = ((checksum >> 8) & 0xff) as u8; + + self.device + .write(&data) + .map_err(|code| Error::BadWrite { cmd: data[0], code })?; + + Ok(UpdateState::SentChecksum) + }; let verify_checksum = || -> Result { - Ok(UpdateState::VerifiedChecksum) + use BOOT_LOADER_STATUS::ChecksumSuccessful; + + let status = self.get_boot_loader_status()?; + + match status.get_checksum_successful() { + Some(ChecksumSuccessful::Successful) => { + Ok(UpdateState::VerifiedChecksum) + } + Some(ChecksumSuccessful::NotSuccessful) | None => { + Err(Error::ChecksumNotSuccessful) + } + } }; - let reboot_psu = - || -> Result { Ok(UpdateState::RebootedPSU) }; + let reboot_psu = || -> Result { + self.boot_loader_command(BootLoaderCommand::BootPSUFirmware)?; + Ok(UpdateState::RebootedPSU) + }; let verify_success = || -> Result { Ok(UpdateState::UpdateSuccessful) diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index d5db200cad..c698c1fd6a 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -216,12 +216,16 @@ impl Psu { } Ok((next, delay)) => { - ringbuf_entry!(match next { - UpdateState::WroteBlock { .. } => Trace::WroteBlock, - _ => Trace::UpdateState(next), - }); + match next { + UpdateState::WroteBlock { .. } => { + ringbuf_entry!(Trace::WroteBlock); + } + _ => { + ringbuf_entry!(Trace::UpdateState(next)); + ringbuf_entry!(Trace::UpdateDelay(delay)); + } + } - ringbuf_entry!(Trace::UpdateDelay(delay)); hl::sleep_for(delay); state = Some(next); } From d9291422f0822bc5a41bac2f4a7c7cea1418e0e7 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Fri, 22 Nov 2024 19:01:47 -0800 Subject: [PATCH 05/11] code review feedback + get building --- drv/i2c-devices/src/mwocp68.rs | 53 ++++++++++++------------ drv/psc-psu-update/build.rs | 9 ++++ drv/psc-psu-update/src/main.rs | 13 +++--- drv/psc-psu-update/src/mwocp68-0701.bin | Bin 0 -> 32768 bytes drv/psc-psu-update/src/mwocp68-0762.bin | Bin 0 -> 32768 bytes 5 files changed, 43 insertions(+), 32 deletions(-) create mode 100644 drv/psc-psu-update/build.rs create mode 100644 drv/psc-psu-update/src/mwocp68-0701.bin create mode 100644 drv/psc-psu-update/src/mwocp68-0762.bin diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index 17dcb8d639..b55cd0bd7c 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -33,7 +33,7 @@ pub struct Mwocp68 { #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(u8)] -enum BootLoaderCommand { +pub enum BootLoaderCommand { ClearStatus = 0x00, RestartProgramming = 0x01, BootPrimary = 0x12, @@ -115,14 +115,6 @@ impl From for Error { } } -// const MWOCP68_MFR_ID: &str = "Murata-PS"; -// const MWOCP68_MFR_MODEL: &str = "MWOCP68-3600-D-RM"; - -const MWOCP68_REVISION_LEN: usize = 14; -const MWOCP68_REVISION_FORMAT: &[u8; MWOCP68_REVISION_LEN] = b"XXXX-YYYY-0000"; - -const MWOCP68_BLOCK_LENGTH: usize = 32; - #[derive(Copy, Clone, Debug, PartialEq)] pub enum UpdateState { WroteBootLoaderKey, @@ -140,7 +132,8 @@ pub enum UpdateState { impl UpdateState { fn delay_ms(&self) -> u64 { match self { - Self::WroteBootLoaderKey | Self::WroteProductKey => 3_000, + Self::WroteBootLoaderKey => 3_000, + Self::WroteProductKey => 3_000, Self::BootedBootLoader => 1_000, Self::StartedProgramming => 2_000, Self::WroteBlock { .. } | Self::WroteLastBlock { .. } => 100, @@ -455,9 +448,14 @@ impl Mwocp68 { Ok(status.get_power_good_status() == Some(PowerGoodStatus::PowerGood)) } + /// + /// Returns the firmware revision of the primary MCU (AC input side). + /// pub fn firmware_revision(&self) -> Result { - let mut data = [0u8; MWOCP68_REVISION_LEN]; - let expected = MWOCP68_REVISION_FORMAT; + const REVISION_LEN: usize = 14; + + let mut data = [0u8; REVISION_LEN]; + let expected = b"XXXX-YYYY-0000"; let len = self .device @@ -474,7 +472,7 @@ impl Mwocp68 { // output side). We aren't going to be rigid about the format of // either revision, but we will be rigid about the rest of the format. // - if len != MWOCP68_REVISION_LEN { + if len != REVISION_LEN { return Err(Error::BadFirmwareRevLength); } @@ -487,7 +485,10 @@ impl Mwocp68 { return Err(Error::BadFirmwareRev { index: index as u8 }); } } - + + // + // Return the primary MCU version + // Ok(Mwocp68FirmwareRev([data[0], data[1], data[2], data[3]])) } @@ -496,12 +497,13 @@ impl Mwocp68 { ) -> Result { use pmbus::commands::mwocp68::CommandCode; let cmd = CommandCode::BOOT_LOADER_STATUS as u8; - let mut data = [0u8; 1]; + let mut data = [0u8]; - let len = self - .device - .read_block(cmd, &mut data) - .map_err(|code| Error::BadRead { cmd, code })?; + match self.device.read_block(cmd, &mut data) { + Ok(1) => Ok(()), + Ok(len) => Err(Error::BadBootLoaderStatus { data: len as u8 }), + Err(code) => Err(Error::BadRead { cmd, code }) + }?; ringbuf_entry!(Trace::BootLoaderMode(data[0])); @@ -575,7 +577,7 @@ impl Mwocp68 { }; let write_block = || -> Result { - const len: usize = MWOCP68_BLOCK_LENGTH; + const BLOCK_LEN: usize = 32; let (mut offset, mut checksum) = match state { Some(UpdateState::WroteBlock { offset, checksum }) => { @@ -585,16 +587,16 @@ impl Mwocp68 { _ => panic!(), }; - let mut data = [0u8; len + 1]; + let mut data = [0u8; BLOCK_LEN + 1]; data[0] = CommandCode::BOOT_LOADER_MEMORY_BLOCK as u8; - data[1..].copy_from_slice(&payload[offset..offset + len]); + data[1..].copy_from_slice(&payload[offset..offset + BLOCK_LEN]); self.device .write(&data) .map_err(|code| Error::BadWrite { cmd: data[0], code })?; checksum = data[1..].iter().fold(checksum, |c, &d| c + d as u64); - offset += MWOCP68_BLOCK_LENGTH; + offset += BLOCK_LEN; if offset >= payload.len() { Ok(UpdateState::WroteLastBlock { checksum }) @@ -606,9 +608,8 @@ impl Mwocp68 { let send_checksum = || -> Result { let mut data = [0u8; 4]; - let checksum = match state { - Some(UpdateState::WroteLastBlock { checksum }) => checksum, - _ => panic!(), + let Some(UpdateState::WroteLastBlock { checksum }) = state else { + panic!(); }; data[0] = CommandCode::IMAGE_CHECKSUM as u8; diff --git a/drv/psc-psu-update/build.rs b/drv/psc-psu-update/build.rs new file mode 100644 index 0000000000..660f92b3fc --- /dev/null +++ b/drv/psc-psu-update/build.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> Result<(), Box> { + build_util::expose_target_board(); + build_i2c::codegen(build_i2c::Disposition::Devices)?; + Ok(()) +} diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index c698c1fd6a..2f16a21c3c 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -9,7 +9,6 @@ use drv_i2c_api::*; use drv_i2c_devices::mwocp68::{ Error as Mwocp68Error, Mwocp68, Mwocp68FirmwareRev, UpdateState, }; -use drv_i2c_devices::Validate; use ringbuf::*; use static_cell::ClaimOnceCell; use userlib::*; @@ -52,7 +51,6 @@ enum Trace { Start, PowerGoodFailed(u8, drv_i2c_devices::mwocp68::Error), FirmwareRevFailed(u8, drv_i2c_devices::mwocp68::Error), - Psu(u8), AttemptingUpdate(u8), BackingOff(u8), UpdateFailed, @@ -65,10 +63,9 @@ enum Trace { } const MWOCP68_FIRMWARE_REV: Mwocp68FirmwareRev = Mwocp68FirmwareRev(*b"0762"); -const MWOCP68_FIRMWARE_PAYLOAD: &'static [u8] = - include_bytes!("mwocp68-0762.bin"); +const MWOCP68_FIRMWARE_PAYLOAD: &[u8] = include_bytes!("mwocp68-0762.bin"); -ringbuf!(Trace, 32, Trace::None); +ringbuf!(Trace, 64, Trace::None); #[derive(Copy, Clone, PartialOrd, PartialEq)] struct Ticks(u64); @@ -176,6 +173,10 @@ impl Psu { (self.update_started, self.update_backoff) { if started + backoff > now { + // + // Indicate we are backing off, but in a way that won't flood + // the ring buffer with the backing off of a single PSU. + // ringbuf_entry!(Trace::BackingOff(ndx)); return false; } @@ -240,7 +241,7 @@ fn main() -> ! { ringbuf_entry!(Trace::Start); - let mut psus = PSU.claim(); + let psus = PSU.claim(); let devs: [Mwocp68; 6] = array_init::array_init(|ndx: usize| { Mwocp68::new(&DEVICES[ndx](i2c_task), 0) diff --git a/drv/psc-psu-update/src/mwocp68-0701.bin b/drv/psc-psu-update/src/mwocp68-0701.bin new file mode 100644 index 0000000000000000000000000000000000000000..c22f31d652fdb665ae5f916098c2b3ca5db83fe9 GIT binary patch literal 32768 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 Hsu%ws;Wuth literal 0 HcmV?d00001 diff --git a/drv/psc-psu-update/src/mwocp68-0762.bin b/drv/psc-psu-update/src/mwocp68-0762.bin new file mode 100644 index 0000000000000000000000000000000000000000..baa93ca9b673006a6e58983ee7ddeec72f32143f GIT binary patch literal 32768 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 Date: Fri, 22 Nov 2024 19:26:49 -0800 Subject: [PATCH 06/11] fmt --- drv/i2c-devices/src/mwocp68.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index b55cd0bd7c..aa815fd098 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -485,7 +485,7 @@ impl Mwocp68 { return Err(Error::BadFirmwareRev { index: index as u8 }); } } - + // // Return the primary MCU version // @@ -502,7 +502,7 @@ impl Mwocp68 { match self.device.read_block(cmd, &mut data) { Ok(1) => Ok(()), Ok(len) => Err(Error::BadBootLoaderStatus { data: len as u8 }), - Err(code) => Err(Error::BadRead { cmd, code }) + Err(code) => Err(Error::BadRead { cmd, code }), }?; ringbuf_entry!(Trace::BootLoaderMode(data[0])); From 66c88e9c4fe16f3e00e78bd162058888690cd9e4 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Fri, 22 Nov 2024 19:39:16 -0800 Subject: [PATCH 07/11] clippy --- drv/i2c-devices/src/mwocp68.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index aa815fd098..2d0583b99f 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -435,10 +435,7 @@ impl Mwocp68 { /// Will return true if the device is present and valid -- false otherwise pub fn present(&self) -> bool { - match Mwocp68::validate(&self.device) { - Ok(valid) => valid, - _ => false, - } + Mwocp68::validate(&self.device).unwrap_or_default() } pub fn power_good(&self) -> Result { From dccc485e6ff179df4e4ee3bdc7685e090e495711 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Fri, 22 Nov 2024 23:36:24 -0800 Subject: [PATCH 08/11] more code review feedback from Matt --- Cargo.lock | 1 + drv/i2c-devices/src/mwocp68.rs | 47 ++++++------ drv/psc-psu-update/Cargo.toml | 3 +- drv/psc-psu-update/src/main.rs | 135 ++++++++++++++++++++++++++++----- 4 files changed, 143 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2f42c25dd..64ad8ae9d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1766,6 +1766,7 @@ dependencies = [ "array-init 2.1.0", "build-i2c", "build-util", + "counters", "drv-i2c-api", "drv-i2c-devices", "ringbuf", diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index 2d0583b99f..7811664698 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -14,13 +14,9 @@ use pmbus::commands::mwocp68::*; use pmbus::commands::CommandCode; use pmbus::units::{Celsius, Rpm}; use pmbus::*; -use ringbuf::*; use task_power_api::PmbusValue; use userlib::units::{Amperes, Volts}; -#[derive(Copy, Clone, PartialEq)] -pub struct Mwocp68FirmwareRev(pub [u8; 4]); - pub struct Mwocp68 { device: I2cDevice, @@ -31,6 +27,12 @@ pub struct Mwocp68 { mode: Cell>, } +#[derive(Copy, Clone, PartialEq)] +pub struct FirmwareRev(pub [u8; 4]); + +#[derive(Copy, Clone, PartialEq, Default)] +pub struct SerialNumber(pub [u8; 12]); + #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(u8)] pub enum BootLoaderCommand { @@ -81,14 +83,6 @@ pub enum Error { ChecksumNotSuccessful, } -#[derive(Copy, Clone, Debug, PartialEq)] -enum Trace { - None, - BootLoaderMode(u8), -} - -ringbuf!(Trace, 32, Trace::None); - impl From for Error { fn from(value: BadValidation) -> Self { Self::BadValidation { @@ -448,7 +442,7 @@ impl Mwocp68 { /// /// Returns the firmware revision of the primary MCU (AC input side). /// - pub fn firmware_revision(&self) -> Result { + pub fn firmware_revision(&self) -> Result { const REVISION_LEN: usize = 14; let mut data = [0u8; REVISION_LEN]; @@ -486,7 +480,18 @@ impl Mwocp68 { // // Return the primary MCU version // - Ok(Mwocp68FirmwareRev([data[0], data[1], data[2], data[3]])) + Ok(FirmwareRev([data[0], data[1], data[2], data[3]])) + } + + pub fn serial_number(&self) -> Result { + let mut serial = SerialNumber::default(); + + let _ = self + .device + .read_block(CommandCode::MFR_SERIAL as u8, &mut serial.0) + .map_err(|code| Error::BadFirmwareRevRead { code })?; + + Ok(serial) } fn get_boot_loader_status( @@ -502,8 +507,6 @@ impl Mwocp68 { Err(code) => Err(Error::BadRead { cmd, code }), }?; - ringbuf_entry!(Trace::BootLoaderMode(data[0])); - match BOOT_LOADER_STATUS::CommandData::from_slice(&data[0..]) { Some(status) => Ok(status), None => Err(Error::BadBootLoaderStatus { data: data[0] }), @@ -603,16 +606,16 @@ impl Mwocp68 { }; let send_checksum = || -> Result { - let mut data = [0u8; 4]; - let Some(UpdateState::WroteLastBlock { checksum }) = state else { panic!(); }; - data[0] = CommandCode::IMAGE_CHECKSUM as u8; - data[1] = 2; - data[2] = (checksum & 0xff) as u8; - data[3] = ((checksum >> 8) & 0xff) as u8; + let data = [ + CommandCode::IMAGE_CHECKSUM as u8, + 2, + (checksum & 0xff) as u8, + ((checksum >> 8) & 0xff) as u8, + ]; self.device .write(&data) diff --git a/drv/psc-psu-update/Cargo.toml b/drv/psc-psu-update/Cargo.toml index 921427c300..bfc2b02a9b 100644 --- a/drv/psc-psu-update/Cargo.toml +++ b/drv/psc-psu-update/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" [dependencies] drv-i2c-api = { path = "../i2c-api" } drv-i2c-devices = { path = "../i2c-devices" } -ringbuf = { path = "../../lib/ringbuf" } +counters = { path = "../../lib/counters" } +ringbuf = { path = "../../lib/ringbuf", features = ["counters"] } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } static-cell = { path = "../../lib/static-cell" } array-init.workspace = true diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index 2f16a21c3c..424a583292 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -7,7 +7,7 @@ use drv_i2c_api::*; use drv_i2c_devices::mwocp68::{ - Error as Mwocp68Error, Mwocp68, Mwocp68FirmwareRev, UpdateState, + Error as Mwocp68Error, FirmwareRev, Mwocp68, SerialNumber, UpdateState, }; use ringbuf::*; use static_cell::ClaimOnceCell; @@ -17,7 +17,7 @@ use core::ops::Add; task_slot!(I2C, i2c_driver); -const TIMER_INTERVAL: u64 = 10000; +const TIMER_INTERVAL_MS: u64 = 10_000; use i2c_config::devices; @@ -36,6 +36,7 @@ static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new( last_checked: None, present: None, power_good: None, + serial_number: None, firmware_matches: None, firmware_revision: None, update_started: None, @@ -45,10 +46,9 @@ static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new( }; 6], ); -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, counters::Count)] enum Trace { None, - Start, PowerGoodFailed(u8, drv_i2c_devices::mwocp68::Error), FirmwareRevFailed(u8, drv_i2c_devices::mwocp68::Error), AttemptingUpdate(u8), @@ -60,12 +60,16 @@ enum Trace { WroteBlock, UpdateSucceeded, UpdateDelay(u64), + PSUReplaced(u8), + SerialNumberError(u8, drv_i2c_devices::mwocp68::Error), + PGError(u8, drv_i2c_devices::mwocp68::Error), + PowerNotGood(u8), } -const MWOCP68_FIRMWARE_REV: Mwocp68FirmwareRev = Mwocp68FirmwareRev(*b"0762"); +const MWOCP68_FIRMWARE_REV: FirmwareRev = FirmwareRev(*b"0762"); const MWOCP68_FIRMWARE_PAYLOAD: &[u8] = include_bytes!("mwocp68-0762.bin"); -ringbuf!(Trace, 64, Trace::None); +counted_ringbuf!(Trace, 64, Trace::None); #[derive(Copy, Clone, PartialOrd, PartialEq)] struct Ticks(u64); @@ -95,8 +99,11 @@ struct Psu { /// Is the device on and with POWER_GOOD set? power_good: Option, + /// The last serial number read + serial_number: Option, + /// The last firmware revision read - firmware_revision: Option, + firmware_revision: Option, /// Does the firmware we have match the firmware here? firmware_matches: Option, @@ -108,7 +115,7 @@ struct Psu { update_succeeded: Option, /// What time did the update last fail, if any? - update_failure: Option<(Ticks, Option, Mwocp68Error)>, + update_failure: Option<(Ticks, Option, Option)>, /// How long should the next update backoff, if at all? (In ticks.) update_backoff: Option, @@ -138,6 +145,29 @@ impl Psu { self.present = Some(true); + // + // If we can read the serial number, we're going to store it -- and + // if we previously stored one and it DOESN'T match, we want to + // clear our backoff value so we don't delay at all in potentially + // trying to update the firmware of the (replaced) PSU. (If we can't + // read the serial number at all, we want to continue to potentially + // update the firmware.) + // + match (dev.serial_number(), self.serial_number) { + (Ok(read), Some(stored)) if read != stored => { + ringbuf_entry!(Trace::PSUReplaced(ndx)); + self.update_backoff = None; + self.serial_number = Some(read); + } + (Ok(_), Some(_)) => {} + (Ok(read), None) => { + self.serial_number = Some(read); + } + (Err(code), _) => { + ringbuf_entry!(Trace::SerialNumberError(ndx, code)); + } + } + match dev.power_good() { Ok(power_good) => { self.power_good = Some(power_good); @@ -189,30 +219,97 @@ impl Psu { ringbuf_entry!(Trace::AttemptingUpdate(ndx)); self.update_started = Some(Ticks::now()); + // + // Before we start, update our backoff. We'll double our backoff, up + // to a cap of around a day. + // self.update_backoff = match self.update_backoff { - Some(backoff) => Some(Ticks(backoff.0 * 2)), + Some(backoff) if backoff.0 < 86_400_000 => { + Some(Ticks(backoff.0 * 2)) + } + Some(backoff) => Some(backoff), None => Some(Ticks(60_000)), }; let mut state = None; + let mut update_failed = |state, err| { + // + // We failed. Record everything we can! + // + if let Some(err) = err { + ringbuf_entry!(Trace::UpdateFailure(err)); + } + + ringbuf_entry!(Trace::UpdateFailed); + ringbuf_entry!(Trace::UpdateFailedState(state)); + self.update_failure = Some((Ticks::now(), state, err)); + }; + loop { match dev.update(state, MWOCP68_FIRMWARE_PAYLOAD) { Err(err) => { - // - // We failed. Record everything we can and leave. - // - ringbuf_entry!(Trace::UpdateFailed); - ringbuf_entry!(Trace::UpdateFailedState(state)); - ringbuf_entry!(Trace::UpdateFailure(err)); - - self.update_failure = Some((Ticks::now(), state, err)); + update_failed(state, Some(err)); break; } Ok((UpdateState::UpdateSuccessful, _)) => { + let state = Some(UpdateState::UpdateSuccessful); + + // + // We should be back up! As a final measure, we are going + // to check that the firmware revision matches the + // revision we think we just wrote. If it doesn't, there + // is something amiss: it may be that the image is + // corrupt or that the version doesn't otherwise match. + // Regardless, we consider that to be an update failure. + // + match dev.firmware_revision() { + Ok(revision) if revision != MWOCP68_FIRMWARE_REV => { + update_failed(state, None); + break; + } + + Err(err) => { + update_failed(state, Some(err)); + break; + } + + Ok(_) => {} + } + + // + // We're on the new firmware! And now, a final final + // check: make sure that we are power-good. It is very + // unclear what to do here if are NOT power-good: we know + // that we WERE power-good before we started, so it + // certainly seems possible that we have put a firmware + // update on this PSU which has somehow incapacitated it. + // We would rather not put the system in a compromised + // state by continuing to potentially brick PSUs -- but we + // also want to assure that we make progress should this + // ever resolve (e.g., by pulling the bricked PSU). We will + // remain here until we see the updated PSU go power-good; + // if it never does, we will at least not attempt to put + // the (potentially) bad update anywhere else! + // + loop { + match dev.power_good() { + Ok(power_good) if power_good => break, + Ok(_) => { + ringbuf_entry!(Trace::PowerNotGood(ndx)); + } + Err(err) => { + ringbuf_entry!(Trace::PGError(ndx, err)); + } + } + + hl::sleep_for(TIMER_INTERVAL_MS); + } + ringbuf_entry!(Trace::UpdateSucceeded); self.update_succeeded = Some(Ticks::now()); + self.update_backoff = None; break; } @@ -239,8 +336,6 @@ impl Psu { fn main() -> ! { let i2c_task = I2C.get_task_id(); - ringbuf_entry!(Trace::Start); - let psus = PSU.claim(); let devs: [Mwocp68; 6] = array_init::array_init(|ndx: usize| { @@ -248,7 +343,7 @@ fn main() -> ! { }); loop { - hl::sleep_for(TIMER_INTERVAL); + hl::sleep_for(TIMER_INTERVAL_MS); for (ndx, psu) in psus.iter_mut().enumerate() { let dev = &devs[ndx]; From 2a1588f82bd7dcc7cc7e2baaba79194c9f0e2e3a Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Mon, 25 Nov 2024 01:21:50 -0800 Subject: [PATCH 09/11] wip --- Cargo.lock | 4 +-- Cargo.toml | 2 +- drv/i2c-devices/src/mwocp68.rs | 57 ++++++++++++++++++++++++++++++++++ drv/psc-psu-update/src/main.rs | 33 +++++++++++++++++++- 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64ad8ae9d3..5e164766b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3896,8 +3896,8 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "pmbus" -version = "0.1.3" -source = "git+https://github.com/oxidecomputer/pmbus?branch=mwocp#0b3cdb8a4679efa76d40bbfe3277490a78ee5267" +version = "0.1.4" +source = "git+https://github.com/oxidecomputer/pmbus#44568ce7eb86fe0b03dd088a75ad76ea0d8529bb" dependencies = [ "anyhow", "convert_case", diff --git a/Cargo.toml b/Cargo.toml index 20f0fe3aec..80a83aaf7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,7 +133,7 @@ idol = { git = "https://github.com/oxidecomputer/idolatry.git", default-features idol-runtime = { git = "https://github.com/oxidecomputer/idolatry.git", default-features = false } lpc55_sign = { git = "https://github.com/oxidecomputer/lpc55_support", default-features = false } ordered-toml = { git = "https://github.com/oxidecomputer/ordered-toml", default-features = false } -pmbus = { git = "https://github.com/oxidecomputer/pmbus", branch = "mwocp", default-features = false } +pmbus = { git = "https://github.com/oxidecomputer/pmbus", default-features = false } salty = { version = "0.3", default-features = false } spd = { git = "https://github.com/oxidecomputer/spd", default-features = false } tlvc = { git = "https://github.com/oxidecomputer/tlvc", default-features = false, version = "0.3.1" } diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index 7811664698..dc58270806 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -33,6 +33,14 @@ pub struct FirmwareRev(pub [u8; 4]); #[derive(Copy, Clone, PartialEq, Default)] pub struct SerialNumber(pub [u8; 12]); +// +// The boot loader command -- sent via BOOT_LOADER_CMD -- is unfortunately odd +// in that its command code is overloaded with BOOT_LOADER_STATUS. (That is, +// a read to the command code is BOOT_LOADER_STATUS, a write is +// BOOT_LOADER_CMD.) This is behavior that the PMBus crate didn't necessarily +// envision, so it can't necessarily help us out; we define the single-byte +// payload codes here rather than declaratively in the PMBus crate. +// #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(u8)] pub enum BootLoaderCommand { @@ -109,21 +117,51 @@ impl From for Error { } } +/// +/// Defines the state of the firmware update. Once `UpdateSuccessful` +/// has been returned, the update is complete. +/// #[derive(Copy, Clone, Debug, PartialEq)] pub enum UpdateState { + /// The boot loader key has been written WroteBootLoaderKey, + + /// The product key has been written WroteProductKey, + + /// The boot loader has been booted BootedBootLoader, + + /// Programming of firmware has been indicated to have started StartedProgramming, + + /// A block has been written; the next offset is at [`offset`], and the + /// running checksum is in [`checksum`] WroteBlock { offset: usize, checksum: u64 }, + + /// The last block has been written; the checksum is in [`checksum`] WroteLastBlock { checksum: u64 }, + + /// The checksum has been sent for verification SentChecksum, + + /// The checksum has been verified VerifiedChecksum, + + /// The PSU has been rebooted RebootedPSU, + + /// The entire update is complete and successful UpdateSuccessful, } impl UpdateState { + /// + /// Return the milliseconds of delay associated with the current state. + /// Note that some of these values differ slightly from Murata's "PSU + /// Firmware Update Process" document in that they reflect revised + /// guidance from Murata. + /// fn delay_ms(&self) -> u64 { match self { Self::WroteBootLoaderKey => 3_000, @@ -483,6 +521,9 @@ impl Mwocp68 { Ok(FirmwareRev([data[0], data[1], data[2], data[3]])) } + /// + /// Returns the serial number of the PSU. + /// pub fn serial_number(&self) -> Result { let mut serial = SerialNumber::default(); @@ -514,12 +555,19 @@ impl Mwocp68 { } fn get_boot_loader_mode(&self) -> Result { + // + // This unwrap is safe because the boot loader mode is a single bit. + // Ok(self.get_boot_loader_status()?.get_mode().unwrap()) } fn boot_loader_command(&self, cmd: BootLoaderCommand) -> Result<(), Error> { use pmbus::commands::mwocp68::CommandCode; + // + // The great unfortunateness: BOOT_LOADER_STATUS is overloaded to + // be BOOT_LOADER_CMD on a write. + // let data = [CommandCode::BOOT_LOADER_STATUS as u8, 1, cmd as u8]; self.device @@ -529,6 +577,15 @@ impl Mwocp68 { Ok(()) } + /// + /// Perform a firmware update, implementating the procedure contained + /// within Murata's "PSU Firmware Update Process" document. Note that + /// this function must be called initially with a state of `None`; it will + /// return either an error, or the next state in the update process, + /// along with a specified delay in milliseconds. It is up to the caller + /// to assure that the returned delay has been observed before calling + /// back into continue the update. + /// pub fn update( &self, state: Option, diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index 424a583292..884d72511f 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -2,6 +2,32 @@ // 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/. +//! Server for updating all PSUs to the contained binary payload. +//! +//! We have the capacity to dynamically update the MWOCP68 power supply units +//! connected to the PSC. This update does not involve any interruption of +//! the PSU while it is being performed, but necessitates a reset of the PSU, +//! which will result in a drop of its own output. We want these updates to +//! be automatic and autonomous; there is little that the control plane can +//! know that we do not know -- and even less for the operator. +//! +//! This task contains within it a payload that is the desired firmware image +//! (`MWOCP68_FIRMWARE_PAYLOAD`), along with the `MFR_REVISION` that that +//! pyaload represents (``MWOCP68_FIRMWARE_VERSION`). This task will check +//! every PSU periodically to see if the PSU's firmware revision matches the +//! payload revision; if it doesn't (or rather, until it does), an attempt +//! will be made to update the PSU. Each PSU will be updated sequentially: +//! while we can expect a properly configured and operating rack to support +//! the loss of any one PSU, we do not want to induce the loss of more than +//! one simultaneously due to update. If an update fails, the update of that +//! PSU will be exponentially backed off and repeated (up to a backoff of +//! about once per day). Note that we will continue to check PSUs that we +//! have already updated should they be replaced with a PSU with downrev +//! firmware. The state of this task can be ascertained by looking at the +//! `PSU` variable (which contains all of the per-PSU state) as well as the +//! ring buffer. +//! + #![no_std] #![no_main] @@ -66,6 +92,11 @@ enum Trace { PowerNotGood(u8), } +// +// The actual firmware revision and payload. It is very important that the +// revision match the revision contained within the payload, lest we will +// believe that the update has failed when it has in fact succeeded! +// const MWOCP68_FIRMWARE_REV: FirmwareRev = FirmwareRev(*b"0762"); const MWOCP68_FIRMWARE_PAYLOAD: &[u8] = include_bytes!("mwocp68-0762.bin"); @@ -228,7 +259,7 @@ impl Psu { Some(Ticks(backoff.0 * 2)) } Some(backoff) => Some(backoff), - None => Some(Ticks(60_000)), + None => Some(Ticks(75_000)), }; let mut state = None; From 7bec0ce2ad906a3b6d8bbf3d5e1fa5dcbe199081 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Mon, 25 Nov 2024 13:18:19 -0800 Subject: [PATCH 10/11] code review comments from matt + eliza --- drv/i2c-devices/src/mwocp68.rs | 4 +++- drv/psc-psu-update/src/main.rs | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drv/i2c-devices/src/mwocp68.rs b/drv/i2c-devices/src/mwocp68.rs index dc58270806..516612014d 100644 --- a/drv/i2c-devices/src/mwocp68.rs +++ b/drv/i2c-devices/src/mwocp68.rs @@ -652,7 +652,9 @@ impl Mwocp68 { .write(&data) .map_err(|code| Error::BadWrite { cmd: data[0], code })?; - checksum = data[1..].iter().fold(checksum, |c, &d| c + d as u64); + checksum = data[1..] + .iter() + .fold(checksum, |c, &d| c.wrapping_add(d.into())); offset += BLOCK_LEN; if offset >= payload.len() { diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index 884d72511f..18671dd124 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -74,6 +74,7 @@ static PSU: ClaimOnceCell<[Psu; 6]> = ClaimOnceCell::new( #[derive(Copy, Clone, Debug, PartialEq, counters::Count)] enum Trace { + #[count(skip)] None, PowerGoodFailed(u8, drv_i2c_devices::mwocp68::Error), FirmwareRevFailed(u8, drv_i2c_devices::mwocp68::Error), @@ -84,7 +85,7 @@ enum Trace { UpdateFailure(Mwocp68Error), UpdateState(UpdateState), WroteBlock, - UpdateSucceeded, + UpdateSucceeded(u8), UpdateDelay(u64), PSUReplaced(u8), SerialNumberError(u8, drv_i2c_devices::mwocp68::Error), @@ -338,7 +339,7 @@ impl Psu { hl::sleep_for(TIMER_INTERVAL_MS); } - ringbuf_entry!(Trace::UpdateSucceeded); + ringbuf_entry!(Trace::UpdateSucceeded(ndx)); self.update_succeeded = Some(Ticks::now()); self.update_backoff = None; break; From e4c436211a4bc4b99af69d68b408e6683b913893 Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Mon, 25 Nov 2024 13:26:53 -0800 Subject: [PATCH 11/11] adjust comment slightly --- drv/psc-psu-update/src/main.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/drv/psc-psu-update/src/main.rs b/drv/psc-psu-update/src/main.rs index 18671dd124..5584d66752 100644 --- a/drv/psc-psu-update/src/main.rs +++ b/drv/psc-psu-update/src/main.rs @@ -5,27 +5,27 @@ //! Server for updating all PSUs to the contained binary payload. //! //! We have the capacity to dynamically update the MWOCP68 power supply units -//! connected to the PSC. This update does not involve any interruption of -//! the PSU while it is being performed, but necessitates a reset of the PSU, -//! which will result in a drop of its own output. We want these updates to -//! be automatic and autonomous; there is little that the control plane can -//! know that we do not know -- and even less for the operator. +//! connected to the PSC. This update does not involve any interruption of the +//! PSU while it is being performed, but necessitates a reset of the PSU once +//! completed. We want these updates to be automatic and autonomous; there is +//! little that the control plane can know that we do not know -- and even less +//! for the operator. //! //! This task contains within it a payload that is the desired firmware image //! (`MWOCP68_FIRMWARE_PAYLOAD`), along with the `MFR_REVISION` that that -//! pyaload represents (``MWOCP68_FIRMWARE_VERSION`). This task will check +//! pyaload represents (`MWOCP68_FIRMWARE_VERSION`). This task will check //! every PSU periodically to see if the PSU's firmware revision matches the -//! payload revision; if it doesn't (or rather, until it does), an attempt -//! will be made to update the PSU. Each PSU will be updated sequentially: -//! while we can expect a properly configured and operating rack to support -//! the loss of any one PSU, we do not want to induce the loss of more than -//! one simultaneously due to update. If an update fails, the update of that -//! PSU will be exponentially backed off and repeated (up to a backoff of -//! about once per day). Note that we will continue to check PSUs that we -//! have already updated should they be replaced with a PSU with downrev -//! firmware. The state of this task can be ascertained by looking at the -//! `PSU` variable (which contains all of the per-PSU state) as well as the -//! ring buffer. +//! revision specified as corresponding to the payload; if they don't match (or +//! rather, until they do), an attempt will be made to update the PSU. Each +//! PSU will be updated sequentially: while we can expect a properly configured +//! and operating rack to support the loss of any one PSU, we do not want to +//! induce the loss of more than one simultaneously due to update. If an +//! update fails, the update of that PSU will be exponentially backed off and +//! repeated (up to a backoff of about once per day). Note that we will +//! continue to check PSUs that we have already updated should they be replaced +//! with a PSU with downrev firmware. The state of this task can be +//! ascertained by looking at the `PSU` variable (which contains all of the +//! per-PSU state) as well as the ring buffer. //! #![no_std]