From 8c7f5191a666b8ffbbd4a18c954c8ae13af400ae Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 18 Aug 2026 10:59:47 -0700 Subject: [PATCH 01/28] start trying to use James' PMBus capabilities for VPD registers --- drv/i2c-types/src/lib.rs | 25 +++++++++++++++++++++++++ task/control-plane-agent/build.rs | 9 +++++++++ 2 files changed, 34 insertions(+) diff --git a/drv/i2c-types/src/lib.rs b/drv/i2c-types/src/lib.rs index 0cfe412ba6..4372425552 100644 --- a/drv/i2c-types/src/lib.rs +++ b/drv/i2c-types/src/lib.rs @@ -286,6 +286,7 @@ pub mod pmbus_status { pub struct Capabilities(pub u32); impl Capabilities { + // --- capability bits for status registers --------------------------- pub const STATUS_WORD: Self = Self(1 << 0); pub const STATUS_VOUT: Self = Self(1 << 1); pub const STATUS_IOUT: Self = Self(1 << 2); @@ -297,6 +298,30 @@ pub mod pmbus_status { pub const STATUS_FANS_1_2: Self = Self(1 << 8); pub const STATUS_FANS_3_4: Self = Self(1 << 9); + // --- capability bits for VPD registers ------------------------------ + pub const MFR_ID: Self = Self(1 << 10); + pub const MFR_MODEL: Self = Self(1 << 11); + pub const MFR_REVISION: Self = Self(1 << 12); + pub const MFR_SERIAL: Self = Self(1 << 13); + pub const MFR_LOCATION: Self = Self(1 << 14); + pub const MFR_DATE: Self = Self(1 << 15); + pub const IC_DEVICE_ID: Self = Self(1 << 16); + pub const IC_DEVICE_REV: Self = Self(1 << 17); + + /// Bitmask for selecting *all* potential VPD register capability bits. + pub const ANY_VPD_REGS: Self = Self( + // XXX(eliza): this might be less gross if we just used the + // bitflags crate for this... + Self::MFR_ID.0 + | Self::MFR_MODEL.0 + | Self::MFR_REVISION.0 + | Self::MFR_SERIAL.0 + | Self::MFR_LOCATION.0 + | Self::MFR_DATE.0 + | Self::IC_DEVICE_ID.0 + | Self::IC_DEVICE_REV.0, + ); + /// Does this capability support all capabilities of `other`? /// /// `self` may support *more* capabilities than `other`, but diff --git a/task/control-plane-agent/build.rs b/task/control-plane-agent/build.rs index 0951db5257..18f2b133ab 100644 --- a/task/control-plane-agent/build.rs +++ b/task/control-plane-agent/build.rs @@ -216,6 +216,15 @@ macro_rules! generator { set_if_pmbus_read_illegal!(out, $module, STATUS_MFR_SPECIFIC); set_if_pmbus_read_illegal!(out, $module, STATUS_FANS_1_2); set_if_pmbus_read_illegal!(out, $module, STATUS_FANS_3_4); + // VPD bits + set_if_pmbus_read_illegal!(out, $module, MFR_ID); + set_if_pmbus_read_illegal!(out, $module, MFR_MODEL); + set_if_pmbus_read_illegal!(out, $module, MFR_REVISION); + set_if_pmbus_read_illegal!(out, $module, MFR_SERIAL); + set_if_pmbus_read_illegal!(out, $module, MFR_LOCATION); + set_if_pmbus_read_illegal!(out, $module, MFR_DATE); + set_if_pmbus_read_illegal!(out, $module, IC_DEVICE_ID); + set_if_pmbus_read_illegal!(out, $module, IC_DEVICE_REV); Capabilities(out) }) }; From e29735a4b2cdd32ca5eb1fc36a513850b2e3cdda Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 18 Aug 2026 11:18:17 -0700 Subject: [PATCH 02/28] add thing for reading PMBus VPDs from a device into a buffer --- Cargo.lock | 1 + drv/i2c-devices/Cargo.toml | 1 + drv/i2c-devices/src/lib.rs | 152 +++++++++++++++++++++++++++++++++++++ drv/i2c-types/src/lib.rs | 8 +- 4 files changed, 161 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a306868de6..b07d6a95de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1669,6 +1669,7 @@ dependencies = [ "num-traits", "pmbus", "ringbuf", + "serde", "smbus-pec", "task-power-api", "userlib", diff --git a/drv/i2c-devices/Cargo.toml b/drv/i2c-devices/Cargo.toml index 91d4be7244..7855f80c7a 100644 --- a/drv/i2c-devices/Cargo.toml +++ b/drv/i2c-devices/Cargo.toml @@ -11,6 +11,7 @@ pmbus = { workspace = true } smbus-pec = { workspace = true } zerocopy = { workspace = true } zerocopy-derive = { workspace = true } +serde = { workspace = true, optional = true } derive-idol-err = { path = "../../lib/derive-idol-err" } drv-i2c-api = { path = "../i2c-api" } diff --git a/drv/i2c-devices/src/lib.rs b/drv/i2c-devices/src/lib.rs index 8daa1a76e8..94216aef65 100644 --- a/drv/i2c-devices/src/lib.rs +++ b/drv/i2c-devices/src/lib.rs @@ -411,6 +411,158 @@ impl PmbusStatus { } } +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PmbusVpd<'buf> { + /// `MFR_ID` (PMBus operation 0x99) + pub mfr_id: Option<&'buf [u8]>, + /// `MFR_MODEL` (PMBus operation 0x9A) + pub mfr_model: Option<&'buf [u8]>, + /// `MFR_REVISION` (PMBus operation 0x9B) + pub mfr_revision: Option<&'buf [u8]>, + /// `MFR_LOCATION` (PMBus operation 0x9C) + pub mfr_location: Option<&'buf [u8]>, + /// `MFR_DATE` (PMBus operation 0x9D) + pub mfr_date: Option<&'buf [u8]>, + /// `MFR_SERIAL` (PMBus operation 0x9E) + pub mfr_serial: Option<&'buf [u8]>, + pub ic_device_id: Option<&'buf [u8]>, + pub ic_device_rev: Option<&'buf [u8]>, +} + +#[derive(Copy, Clone, Eq, PartialEq, counters::Count)] +pub enum PmbusVpdError { + /// The device does not support any PMBus VPD registers. + NoVpd, + BadRead { + cmd: Cmd, + #[count(children)] + err: drv_i2c_api::ResponseCode, + }, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + zerocopy_derive::IntoBytes, + zerocopy_derive::Immutable, + counters::Count, +)] +#[repr(u8)] +pub enum PmbusVpdCmd { + MfrId = pmbus::CommandCode::MFR_ID as u8, + MfrModel = pmbus::CommandCode::MFR_MODEL as u8, + MfrRevision = pmbus::CommandCode::MFR_REVISION as u8, + MfrSerial = pmbus::CommandCode::MFR_SERIAL as u8, + MfrLocation = pmbus::CommandCode::MFR_LOCATION as u8, + MfrDate = pmbus::CommandCode::MFR_LOCATION as u8, + IcDeviceId = pmbus::CommandCode::IC_DEVICE_ID as u8, + IcDeviceRev = pmbus::CommandCode::IC_DEVICE_REV as u8, +} + +impl<'buf> PmbusVpd<'buf> { + /// SMBus block reads may not be longer than 32 bytes. + const BLOCK_LEN: usize = 32; + /// Maximum length currently required to read a complete set of VPD + /// registers from a PMBus device. + /// + /// Currently, this is 8 32-byte blocks (one for each register that we may + /// read). If more values are added in the future, this will need to be + /// embiggened. + pub const BUF_LEN: usize = Self::BLOCK_LEN * 8; + + /// Attempt to read a [`PmbusVpd`] from the given device. + pub fn try_read_from( + dev: &I2cDevice, + buf: &'buf mut [u8; Self::BUF_LEN], + device_caps: Capabilities, + ) -> Result { + use core::ops::Range; + + if !device_caps.supports_any(&Capabilities::PmbusVpd) { + return Err(PmbusVpdError::NoVpd); + } + + let read = |cmd: PmbusVpdCmd, + cap: Capabilities, + buf: &mut [u8; PmbusVpd::BUF_LEN], + curr_off: &mut usize| + -> Result>, PmbusVpdError> { + if !device_caps.supports(&cap) { + return Ok(None); + } + let off = *curr_off; + // PMBus block reads may not be longer than 32 bytes. Clamp this + // down as `drv_i2c_api` gets mad if it sees a lease of >255B. + let Some(block) = buf.get_mut(off..off + PmbusIdentity::BLOCK_LEN) + else { + // This shouldn't ever happen as we never call this more than + // BUF_LEN * 8 times... + unreachable!(); + }; + let len = dev + .read_block(cmd, block) + .map_err(|err| PmbusVpdError::I2c { cmd, err })?; + *curr_off += len; + Ok(Some(off..*curr_off)) + }; + + let mut off = 0; + let mfr_range = + read(PmbusVpdCmd::MfrId, Capabilities::MFR_ID, buf, &mut off)?; + let model_range = read( + PmbusVpdCmd::MfrModel, + Capabilities::MFR_MODEL, + buf, + &mut off, + )?; + let rev_range = read( + PmbusVpdCmd::MfrRevision, + Capabilities::MFR_REVISION, + buf, + &mut off, + )?; + let location_range = read( + PmbusVpdCmd::MfrLocation, + Capabilities::MFR_LOCATION, + buf, + &mut off, + )?; + let date_range = + read(PmbusVpdCmd::MfrDate, Capabilities::MFR_DATE, buf, &mut off)?; + let serial_range = read( + PmbusVpdCmd::MfrSerial, + Capabilities::MFR_SERIAL, + buf, + &mut off, + )?; + let ic_id_range = read( + PmbusVpdCmd::IcDeviceId, + Capabilities::IC_DEVICE_ID, + buf, + &mut off, + )?; + let ic_rev_range = read( + PmbusVpdCmd::IcDeviceRev, + Capabilities::IC_DEVICE_REV, + buf, + &mut off, + )?; + + Ok(Self { + mfr_id: mfr_range.map(|r| &buf[r]), + mfr_model: model_range.map(|r| &buf[r]), + mfr_revision: rev_range.map(|r| &buf[r]), + mfr_location: location_range.map(|r| &buf[r]), + mfr_date: date_range.map(|r| &buf[r]), + mfr_serial: serial_range.map(|r| &buf[r]), + ic_device_id: ic_id_range.map(|r| &buf[r]), + ic_device_rev: ic_rev_range.map(|r| &buf[r]), + }) + } +} + pub mod adm127x; pub mod adt7420; pub mod at24csw080; diff --git a/drv/i2c-types/src/lib.rs b/drv/i2c-types/src/lib.rs index 4372425552..c61526a549 100644 --- a/drv/i2c-types/src/lib.rs +++ b/drv/i2c-types/src/lib.rs @@ -308,7 +308,7 @@ pub mod pmbus_status { pub const IC_DEVICE_ID: Self = Self(1 << 16); pub const IC_DEVICE_REV: Self = Self(1 << 17); - /// Bitmask for selecting *all* potential VPD register capability bits. + /// Bitmask for selecting *all* potential VPD register capa pub const ANY_VPD_REGS: Self = Self( // XXX(eliza): this might be less gross if we just used the // bitflags crate for this... @@ -330,5 +330,11 @@ pub mod pmbus_status { pub const fn supports(&self, other: &Self) -> bool { (self.0 & other.0) == other.0 } + + /// Does this capability support *any* capabilities of `other`? + #[inline] + pub const fn supports_any(&self, other: &Self) -> bool { + (self.0 & other.0) != 0 + } } } From dbc18e12018220c690bd9e357b59cc7e4f3149e8 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 18 Aug 2026 17:38:34 -0700 Subject: [PATCH 03/28] wire it up --- Cargo.lock | 5 +- build/i2c/src/lib.rs | 40 ++++++- drv/i2c-devices/Cargo.toml | 1 + drv/i2c-devices/src/lib.rs | 113 +++++++----------- drv/i2c-types/src/lib.rs | 108 +++++++++-------- task/control-plane-agent/Cargo.toml | 2 - task/control-plane-agent/build.rs | 132 ++++----------------- task/control-plane-agent/src/inventory.rs | 21 +++- task/control-plane-agent/src/main.rs | 11 +- task/control-plane-agent/src/mgs_common.rs | 20 +++- task/validate-api/Cargo.toml | 2 + task/validate-api/build.rs | 98 ++++++++++++++- task/validate-api/src/lib.rs | 4 +- 13 files changed, 300 insertions(+), 257 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b07d6a95de..7ae9aca0a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1661,6 +1661,7 @@ name = "drv-i2c-devices" version = "0.1.0" dependencies = [ "bitfield 0.13.2", + "counters", "derive-idol-err", "drv-i2c-api", "drv-i2c-types", @@ -6179,7 +6180,6 @@ dependencies = [ "drv-hf-api", "drv-i2c-api", "drv-i2c-devices", - "drv-i2c-types", "drv-ignition-api", "drv-lpc55-update-api", "drv-monorail-api", @@ -6202,7 +6202,6 @@ dependencies = [ "lpc55-rom-data", "num-traits", "p256", - "pmbus", "ringbuf", "serde", "sha2 0.11.0", @@ -7020,11 +7019,13 @@ dependencies = [ "counters", "derive-idol-err", "drv-i2c-api", + "drv-i2c-types", "gateway-messages", "hubpack", "idol", "idol-runtime", "num-traits", + "pmbus", "serde", "task-sensor-api", "userlib", diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 0d3ab6dfb6..a7f08ee0b9 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -1453,9 +1453,13 @@ impl ConfigGenerator { output: &mut String, ) -> Result<()> { let mut byrail = HashMap::new(); + let mut pmbus_devices = Vec::new(); - for d in &self.devices { + for (device_index, d) in self.devices.iter().enumerate() { if let Some(power) = &d.power { + if which == PowerDevices::PMBus && power.pmbus { + pmbus_devices.push((device_index, d)); + } if power.pmbus && which != PowerDevices::PMBus { continue; } @@ -1498,7 +1502,7 @@ impl ConfigGenerator { } } - if !byrail.is_empty() { + if !byrail.is_empty() || !pmbus_devices.is_empty() { write!( output, r##" @@ -1512,6 +1516,38 @@ impl ConfigGenerator { } )?; + if which == PowerDevices::PMBus { + write!( + &mut self.output, + r##" + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index( + task: TaskId, + index: usize, + ) -> Option {{ + match index {{"##, + )?; + + // These indices are shared with `device_descriptions()` and are + // used to look up the I2C device corresponding to an index in + // the device descriptions array in `task-validate-api`'s + // codegen. + for (index, device) in &pmbus_devices { + let out = self.generate_device(device, 20); + writeln!(&mut self.output, "{index} => Some({out}),")?; + } + + writeln!( + &mut self.output, + r##" + _ => None, + }} + }} +"##, + )?; + } + let mut all: Vec<_> = byrail.iter().collect(); all.sort(); diff --git a/drv/i2c-devices/Cargo.toml b/drv/i2c-devices/Cargo.toml index 7855f80c7a..20e45f46db 100644 --- a/drv/i2c-devices/Cargo.toml +++ b/drv/i2c-devices/Cargo.toml @@ -13,6 +13,7 @@ zerocopy = { workspace = true } zerocopy-derive = { workspace = true } serde = { workspace = true, optional = true } +counters = { path = "../../lib/counters", optional = true } derive-idol-err = { path = "../../lib/derive-idol-err" } drv-i2c-api = { path = "../i2c-api" } drv-i2c-types = { path = "../i2c-types" } diff --git a/drv/i2c-devices/src/lib.rs b/drv/i2c-devices/src/lib.rs index 94216aef65..7895138bf9 100644 --- a/drv/i2c-devices/src/lib.rs +++ b/drv/i2c-devices/src/lib.rs @@ -45,7 +45,7 @@ #![no_std] -use drv_i2c_api::{I2cDevice, ResponseCode, pmbus_status::Capabilities}; +use drv_i2c_api::{I2cDevice, PmbusCapabilities, ResponseCode}; use pmbus::commands::CommandCode; macro_rules! pmbus_read { @@ -337,11 +337,11 @@ impl PmbusStatus { pub fn try_read_from( dev: &I2cDevice, rail_idx: Option, - device_caps: Capabilities, + device_caps: PmbusCapabilities, ) -> Result { - use drv_i2c_types::pmbus_status::Capabilities; // Keep the lines short use CommandCode as Cc; + use PmbusCapabilities as Cap; // These need to be like this to humor the macro invocations use PmbusStatusError as Error; use pmbus::commands::PAGE; @@ -379,33 +379,27 @@ impl PmbusStatus { // Status word *must* succeed, otherwise we don't have reasonable // data to return. We may want to consider making some/all of these // retryable, but for now you either get them or you don't. - status_word: read_u16(Cc::STATUS_WORD, Capabilities::STATUS_WORD)?, - status_vout: read_byte(Cc::STATUS_VOUT, Capabilities::STATUS_VOUT), - status_iout: read_byte(Cc::STATUS_IOUT, Capabilities::STATUS_IOUT), + status_word: read_u16(Cc::STATUS_WORD, Cap::STATUS_WORD)?, + status_vout: read_byte(Cc::STATUS_VOUT, Cap::STATUS_VOUT), + status_iout: read_byte(Cc::STATUS_IOUT, Cap::STATUS_IOUT), status_temperature: read_byte( Cc::STATUS_TEMPERATURE, - Capabilities::STATUS_TEMPERATURE, - ), - status_cml: read_byte(Cc::STATUS_CML, Capabilities::STATUS_CML), - status_other: read_byte( - Cc::STATUS_OTHER, - Capabilities::STATUS_OTHER, - ), - status_input: read_byte( - Cc::STATUS_INPUT, - Capabilities::STATUS_INPUT, + Cap::STATUS_TEMPERATURE, ), + status_cml: read_byte(Cc::STATUS_CML, Cap::STATUS_CML), + status_other: read_byte(Cc::STATUS_OTHER, Cap::STATUS_OTHER), + status_input: read_byte(Cc::STATUS_INPUT, Cap::STATUS_INPUT), status_fans_1_2: read_byte( Cc::STATUS_FANS_1_2, - Capabilities::STATUS_FANS_1_2, + Cap::STATUS_FANS_1_2, ), status_fans_3_4: read_byte( Cc::STATUS_FANS_3_4, - Capabilities::STATUS_FANS_3_4, + Cap::STATUS_FANS_3_4, ), status_mfr_specific: read_byte( Cc::STATUS_MFR_SPECIFIC, - Capabilities::STATUS_MFR_SPECIFIC, + Cap::STATUS_MFR_SPECIFIC, ), }) } @@ -429,13 +423,14 @@ pub struct PmbusVpd<'buf> { pub ic_device_rev: Option<&'buf [u8]>, } -#[derive(Copy, Clone, Eq, PartialEq, counters::Count)] +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "counters", derive(counters::Count))] pub enum PmbusVpdError { /// The device does not support any PMBus VPD registers. NoVpd, BadRead { - cmd: Cmd, - #[count(children)] + cmd: PmbusVpdCmd, + #[cfg_attr(feature = "counters", count(children))] err: drv_i2c_api::ResponseCode, }, } @@ -447,8 +442,8 @@ pub enum PmbusVpdError { PartialEq, zerocopy_derive::IntoBytes, zerocopy_derive::Immutable, - counters::Count, )] +#[cfg_attr(feature = "counters", derive(counters::Count))] #[repr(u8)] pub enum PmbusVpdCmd { MfrId = pmbus::CommandCode::MFR_ID as u8, @@ -456,7 +451,7 @@ pub enum PmbusVpdCmd { MfrRevision = pmbus::CommandCode::MFR_REVISION as u8, MfrSerial = pmbus::CommandCode::MFR_SERIAL as u8, MfrLocation = pmbus::CommandCode::MFR_LOCATION as u8, - MfrDate = pmbus::CommandCode::MFR_LOCATION as u8, + MfrDate = pmbus::CommandCode::MFR_DATE as u8, IcDeviceId = pmbus::CommandCode::IC_DEVICE_ID as u8, IcDeviceRev = pmbus::CommandCode::IC_DEVICE_REV as u8, } @@ -475,17 +470,20 @@ impl<'buf> PmbusVpd<'buf> { /// Attempt to read a [`PmbusVpd`] from the given device. pub fn try_read_from( dev: &I2cDevice, - buf: &'buf mut [u8; Self::BUF_LEN], - device_caps: Capabilities, + buf: &'buf mut [u8; PmbusVpd::BUF_LEN], + device_caps: PmbusCapabilities, ) -> Result { use core::ops::Range; + // Keep the lines short + use PmbusCapabilities as Cap; + use PmbusVpdCmd as Cmd; - if !device_caps.supports_any(&Capabilities::PmbusVpd) { + if !device_caps.supports_any(&Cap::ANY_VPD_REGS) { return Err(PmbusVpdError::NoVpd); } - let read = |cmd: PmbusVpdCmd, - cap: Capabilities, + let read = |cmd: Cmd, + cap: Cap, buf: &mut [u8; PmbusVpd::BUF_LEN], curr_off: &mut usize| -> Result>, PmbusVpdError> { @@ -495,7 +493,7 @@ impl<'buf> PmbusVpd<'buf> { let off = *curr_off; // PMBus block reads may not be longer than 32 bytes. Clamp this // down as `drv_i2c_api` gets mad if it sees a lease of >255B. - let Some(block) = buf.get_mut(off..off + PmbusIdentity::BLOCK_LEN) + let Some(block) = buf.get_mut(off..off + PmbusVpd::BLOCK_LEN) else { // This shouldn't ever happen as we never call this more than // BUF_LEN * 8 times... @@ -503,52 +501,25 @@ impl<'buf> PmbusVpd<'buf> { }; let len = dev .read_block(cmd, block) - .map_err(|err| PmbusVpdError::I2c { cmd, err })?; + .map_err(|err| PmbusVpdError::BadRead { cmd, err })?; *curr_off += len; Ok(Some(off..*curr_off)) }; let mut off = 0; - let mfr_range = - read(PmbusVpdCmd::MfrId, Capabilities::MFR_ID, buf, &mut off)?; - let model_range = read( - PmbusVpdCmd::MfrModel, - Capabilities::MFR_MODEL, - buf, - &mut off, - )?; - let rev_range = read( - PmbusVpdCmd::MfrRevision, - Capabilities::MFR_REVISION, - buf, - &mut off, - )?; - let location_range = read( - PmbusVpdCmd::MfrLocation, - Capabilities::MFR_LOCATION, - buf, - &mut off, - )?; - let date_range = - read(PmbusVpdCmd::MfrDate, Capabilities::MFR_DATE, buf, &mut off)?; - let serial_range = read( - PmbusVpdCmd::MfrSerial, - Capabilities::MFR_SERIAL, - buf, - &mut off, - )?; - let ic_id_range = read( - PmbusVpdCmd::IcDeviceId, - Capabilities::IC_DEVICE_ID, - buf, - &mut off, - )?; - let ic_rev_range = read( - PmbusVpdCmd::IcDeviceRev, - Capabilities::IC_DEVICE_REV, - buf, - &mut off, - )?; + let mfr_range = read(Cmd::MfrId, Cap::MFR_ID, buf, &mut off)?; + let model_range = read(Cmd::MfrModel, Cap::MFR_MODEL, buf, &mut off)?; + let rev_range = + read(Cmd::MfrRevision, Cap::MFR_REVISION, buf, &mut off)?; + let location_range = + read(Cmd::MfrLocation, Cap::MFR_LOCATION, buf, &mut off)?; + let date_range = read(Cmd::MfrDate, Cap::MFR_DATE, buf, &mut off)?; + let serial_range = + read(Cmd::MfrSerial, Cap::MFR_SERIAL, buf, &mut off)?; + let ic_id_range = + read(Cmd::IcDeviceId, Cap::IC_DEVICE_ID, buf, &mut off)?; + let ic_rev_range = + read(Cmd::IcDeviceRev, Cap::IC_DEVICE_REV, buf, &mut off)?; Ok(Self { mfr_id: mfr_range.map(|r| &buf[r]), diff --git a/drv/i2c-types/src/lib.rs b/drv/i2c-types/src/lib.rs index c61526a549..f45b7b53b4 100644 --- a/drv/i2c-types/src/lib.rs +++ b/drv/i2c-types/src/lib.rs @@ -276,65 +276,63 @@ pub enum Segment { S16 = 16, } -pub mod pmbus_status { - /// Type that denotes the STATUS registers supported for a given PMBus - /// device - /// - /// This is typically code-generated at build time using information - /// from the `pmbus` crate. - #[derive(Debug, PartialEq, Clone, Copy)] - pub struct Capabilities(pub u32); +/// Type that denotes the STATUS registers supported for a given PMBus +/// device +/// +/// This is typically code-generated at build time using information +/// from the `pmbus` crate. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct PmbusCapabilities(pub u32); - impl Capabilities { - // --- capability bits for status registers --------------------------- - pub const STATUS_WORD: Self = Self(1 << 0); - pub const STATUS_VOUT: Self = Self(1 << 1); - pub const STATUS_IOUT: Self = Self(1 << 2); - pub const STATUS_TEMPERATURE: Self = Self(1 << 3); - pub const STATUS_CML: Self = Self(1 << 4); - pub const STATUS_OTHER: Self = Self(1 << 5); - pub const STATUS_INPUT: Self = Self(1 << 6); - pub const STATUS_MFR_SPECIFIC: Self = Self(1 << 7); - pub const STATUS_FANS_1_2: Self = Self(1 << 8); - pub const STATUS_FANS_3_4: Self = Self(1 << 9); +impl PmbusCapabilities { + // --- capability bits for status registers --------------------------- + pub const STATUS_WORD: Self = Self(1 << 0); + pub const STATUS_VOUT: Self = Self(1 << 1); + pub const STATUS_IOUT: Self = Self(1 << 2); + pub const STATUS_TEMPERATURE: Self = Self(1 << 3); + pub const STATUS_CML: Self = Self(1 << 4); + pub const STATUS_OTHER: Self = Self(1 << 5); + pub const STATUS_INPUT: Self = Self(1 << 6); + pub const STATUS_MFR_SPECIFIC: Self = Self(1 << 7); + pub const STATUS_FANS_1_2: Self = Self(1 << 8); + pub const STATUS_FANS_3_4: Self = Self(1 << 9); - // --- capability bits for VPD registers ------------------------------ - pub const MFR_ID: Self = Self(1 << 10); - pub const MFR_MODEL: Self = Self(1 << 11); - pub const MFR_REVISION: Self = Self(1 << 12); - pub const MFR_SERIAL: Self = Self(1 << 13); - pub const MFR_LOCATION: Self = Self(1 << 14); - pub const MFR_DATE: Self = Self(1 << 15); - pub const IC_DEVICE_ID: Self = Self(1 << 16); - pub const IC_DEVICE_REV: Self = Self(1 << 17); + // --- capability bits for VPD registers ------------------------------ + pub const MFR_ID: Self = Self(1 << 10); + pub const MFR_MODEL: Self = Self(1 << 11); + pub const MFR_REVISION: Self = Self(1 << 12); + pub const MFR_SERIAL: Self = Self(1 << 13); + pub const MFR_LOCATION: Self = Self(1 << 14); + pub const MFR_DATE: Self = Self(1 << 15); + pub const IC_DEVICE_ID: Self = Self(1 << 16); + pub const IC_DEVICE_REV: Self = Self(1 << 17); - /// Bitmask for selecting *all* potential VPD register capa - pub const ANY_VPD_REGS: Self = Self( - // XXX(eliza): this might be less gross if we just used the - // bitflags crate for this... - Self::MFR_ID.0 - | Self::MFR_MODEL.0 - | Self::MFR_REVISION.0 - | Self::MFR_SERIAL.0 - | Self::MFR_LOCATION.0 - | Self::MFR_DATE.0 - | Self::IC_DEVICE_ID.0 - | Self::IC_DEVICE_REV.0, - ); + /// Bitmask for selecting *all* potential VPD register capa + pub const ANY_VPD_REGS: Self = Self( + // XXX(eliza): this might be less gross if we just used the + // bitflags crate for this... + Self::MFR_ID.0 + | Self::MFR_MODEL.0 + | Self::MFR_REVISION.0 + | Self::MFR_SERIAL.0 + | Self::MFR_LOCATION.0 + | Self::MFR_DATE.0 + | Self::IC_DEVICE_ID.0 + | Self::IC_DEVICE_REV.0, + ); - /// Does this capability support all capabilities of `other`? - /// - /// `self` may support *more* capabilities than `other`, but - /// not the other way around. - #[inline] - pub const fn supports(&self, other: &Self) -> bool { - (self.0 & other.0) == other.0 - } + /// Does this capability support all capabilities of `other`? + /// + /// `self` may support *more* capabilities than `other`, but + /// not the other way around. + #[inline] + pub const fn supports(&self, other: &Self) -> bool { + (self.0 & other.0) == other.0 + } - /// Does this capability support *any* capabilities of `other`? - #[inline] - pub const fn supports_any(&self, other: &Self) -> bool { - (self.0 & other.0) != 0 - } + /// Does this capability support *any* capabilities of `other`? + #[inline] + pub const fn supports_any(&self, other: &Self) -> bool { + (self.0 & other.0) != 0 } } diff --git a/task/control-plane-agent/Cargo.toml b/task/control-plane-agent/Cargo.toml index ecafa0644d..841fc055ca 100644 --- a/task/control-plane-agent/Cargo.toml +++ b/task/control-plane-agent/Cargo.toml @@ -58,9 +58,7 @@ static-cell = { path = "../../lib/static-cell" } anyhow = { workspace = true } build-i2c = { path = "../../build/i2c" } build-util = { path = "../../build/util" } -drv-i2c-types = { path = "../../drv/i2c-types" } idol = { workspace = true } -pmbus = { workspace = true } serde = { workspace = true } ssh-key = { workspace = true } diff --git a/task/control-plane-agent/build.rs b/task/control-plane-agent/build.rs index 18f2b133ab..a692a01c77 100644 --- a/task/control-plane-agent/build.rs +++ b/task/control-plane-agent/build.rs @@ -4,7 +4,6 @@ use anyhow::{Context, Result, bail}; use serde::Deserialize; -use std::collections::HashMap; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; @@ -57,37 +56,29 @@ fn do_pmbus() -> Result<()> { let out = context_create_file(&dest_path)?; let mut file = std::io::BufWriter::new(out); - // Build a mapping from "pmbus device name" to "supported status regs" - let pmbus_status_caps = { - let mut map = HashMap::new(); - for (name, func) in PMBUS_GENERATOR { - map.insert(*name, (func)()); - } - map - }; - let mut pmbus_rail_names = std::collections::BTreeMap::new(); let mut pmbus_rail_dupes = 0; - for dev in build_i2c::device_descriptions() { - // We only need to map PMBus devices + for (device_index, dev) in build_i2c::device_descriptions().enumerate() { let Some(ref pmbus) = dev.pmbus else { continue; }; - // If it is a pmbus device, we need to get its status capabilities - let Some(caps) = pmbus_status_caps.get(dev.device.as_str()) else { - println!( - "cargo::error=unknown pmbus device: {}, add entry to \ - PMBUS_GENERATOR in {} for status register support.", - dev.device, - file!(), - ); - panic!("Unsupported pmbus device: {}", dev.device); - }; - - // Aggregate a list of all PMBus-visible rails - for rail in pmbus.rails.iter() { - if pmbus_rail_names.insert(rail.name.clone(), caps).is_some() { + let single_rail = pmbus.rails.len() == 1; + for (rail_index, rail) in pmbus.rails.iter().enumerate() { + let rail_index = if single_rail { + None + } else { + Some(u8::try_from(rail_index).with_context(|| { + format!( + "PMBus device {:?} has more than 256 rails", + dev.device_id, + ) + })?) + }; + if pmbus_rail_names + .insert(rail.name.clone(), (device_index, rail_index)) + .is_some() + { pmbus_rail_dupes += 1; println!( "cargo::error=PMBus device {} defines a power rail {:?} \ @@ -99,29 +90,24 @@ fn do_pmbus() -> Result<()> { } } - // Create a mapping between rail names and generated accessor functions for - // obtaining the device handle and rail index + // The device indices use the ordering shared by `device_descriptions()` and + // the generated device lookup in `build_i2c`. writeln!(file)?; writeln!( file, "pub const PMBUS_RAIL_TO_I2C_DEVICE_MAP: [PmbusRailBinding; {}] = [", pmbus_rail_names.len() )?; - for (rail, caps) in pmbus_rail_names.iter() { - write!(file, " PmbusRailBinding {{ ")?; - write!(file, "name: \"{rail}\", ")?; - // build_i2c *also* only to-lowercases the rail names to make functions - write!( + for (rail, (device_index, rail_index)) in pmbus_rail_names.iter() { + writeln!( file, - "summon_fn: crate::i2c_config::pmbus::{}, ", - rail.to_lowercase() + " PmbusRailBinding {{ name: \"{rail}\", device_index: \ + {device_index}, rail_index: {rail_index:?} }}," )?; - write!(file, "status_bits: Capabilities(0x{:08x}) ", caps.0)?; - writeln!(file, "}},")?; } writeln!(file, "];")?; - // This is supposed to be caught during I2C generation + // This is supposed to be caught during I2C generation. if pmbus_rail_dupes != 0 { bail!("duplicate PMBus rails: invalid application toml."); } @@ -183,73 +169,3 @@ fn context_create_file(path: &Path) -> Result { File::create(path) .with_context(|| format!("failed to create file '{}'", path.display())) } - -/// Look at the `pmbus` crate metadata to see if a specific command is "Illegal" -/// and set the capability bit if not. -macro_rules! set_if_pmbus_read_illegal { - ($out:ident, $module:ident, $cmd:ident) => {{ - use drv_i2c_types::pmbus_status::Capabilities; - use pmbus::{Command, Operation}; - if pmbus::commands::$module::CommandCode::$cmd.read_op() - != Operation::Illegal - { - $out |= Capabilities::$cmd.0; - } - }}; -} - -/// For a given device, calculate the `Capabilities` for each of the -/// status registers. -/// -/// The pmbus functions are not const, so generate a closure instead. -macro_rules! generator { - ($name:literal, $module:ident) => { - ($name, || { - let mut out = 0u32; - set_if_pmbus_read_illegal!(out, $module, STATUS_WORD); - set_if_pmbus_read_illegal!(out, $module, STATUS_VOUT); - set_if_pmbus_read_illegal!(out, $module, STATUS_IOUT); - set_if_pmbus_read_illegal!(out, $module, STATUS_TEMPERATURE); - set_if_pmbus_read_illegal!(out, $module, STATUS_CML); - set_if_pmbus_read_illegal!(out, $module, STATUS_OTHER); - set_if_pmbus_read_illegal!(out, $module, STATUS_INPUT); - set_if_pmbus_read_illegal!(out, $module, STATUS_MFR_SPECIFIC); - set_if_pmbus_read_illegal!(out, $module, STATUS_FANS_1_2); - set_if_pmbus_read_illegal!(out, $module, STATUS_FANS_3_4); - // VPD bits - set_if_pmbus_read_illegal!(out, $module, MFR_ID); - set_if_pmbus_read_illegal!(out, $module, MFR_MODEL); - set_if_pmbus_read_illegal!(out, $module, MFR_REVISION); - set_if_pmbus_read_illegal!(out, $module, MFR_SERIAL); - set_if_pmbus_read_illegal!(out, $module, MFR_LOCATION); - set_if_pmbus_read_illegal!(out, $module, MFR_DATE); - set_if_pmbus_read_illegal!(out, $module, IC_DEVICE_ID); - set_if_pmbus_read_illegal!(out, $module, IC_DEVICE_REV); - Capabilities(out) - }) - }; -} - -use drv_i2c_types::pmbus_status::Capabilities; -type StatusRow = (&'static str, fn() -> Capabilities); - -// Before you add a pmbus device to this list, you should make sure that you -// have reviewed the pmbus crate to make sure that any unsupported status -// registers are marked as illegal, similar to oxidecomputer/pmbus#35. -// -// Failure to do so could cause CML or OTHER error bits to be set. Just adding -// the device to this list (without accurate `pmbus` crate information) will -// likely make the compilation succeed, but should not be done for production -// devices where this may trigger runtime CML errors. -const PMBUS_GENERATOR: &[StatusRow] = &[ - generator!("adm127x", adm127x), - generator!("bmr491", bmr491), - generator!("isl68224", isl68224), - generator!("lm5066", lm5066), - generator!("lm5066i", lm5066i), - generator!("mwocp67", mwocp67), - generator!("mwocp68", mwocp68), - generator!("raa229618", raa229618), - generator!("raa229620a", raa229620a), - generator!("tps546b24a", tps546b24a), -]; diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 90085e2e85..6dd1d87ab4 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use drv_i2c_api::PmbusCapabilities; use gateway_messages::measurement::{ Measurement, MeasurementError, MeasurementKind, }; @@ -37,6 +38,21 @@ impl Inventory { OUR_DEVICES.len() + VALIDATE_DEVICES.len() } + /// Returns the generated device index and PMBus capabilities for a component. + pub(crate) fn pmbus_device( + &self, + component: &SpComponent, + ) -> Result<(usize, PmbusCapabilities), SpError> { + let Index::ValidateDevice(index) = Index::try_from(component)? else { + return Err(SpError::RequestUnsupportedForComponent); + }; + let Some(capabilities) = VALIDATE_DEVICES[index].pmbus_capabilities + else { + return Err(SpError::RequestUnsupportedForComponent); + }; + Ok((index, capabilities)) + } + pub(crate) fn num_component_details( &self, component: &SpComponent, @@ -123,8 +139,11 @@ impl Inventory { }; let mut capabilities = DeviceCapabilities::empty(); - if device.is_pmbus { + if let Some(pmbus_caps) = device.pmbus_capabilities { capabilities |= DeviceCapabilities::IS_PMBUS; + if pmbus_caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { + capabilities |= DeviceCapabilities::HAS_VPD; + } } if !device.sensors.is_empty() { capabilities |= DeviceCapabilities::HAS_MEASUREMENT_CHANNELS; diff --git a/task/control-plane-agent/src/main.rs b/task/control-plane-agent/src/main.rs index a353708d4a..42fba95882 100644 --- a/task/control-plane-agent/src/main.rs +++ b/task/control-plane-agent/src/main.rs @@ -5,7 +5,6 @@ #![no_std] #![no_main] -use drv_i2c_api::pmbus_status::Capabilities; use drv_sprot_api::SprotError; use gateway_messages::{ IgnitionCommand, MgsError, PowerState, SpComponent, UpdateId, @@ -638,16 +637,10 @@ include!(concat!(env!("OUT_DIR"), "/notifications.rs")); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); pub(crate) mod pmbus { - use super::*; - - /// Type returned by generated pmbus rail functions - pub type SummonFn = - fn(userlib::TaskId) -> (drv_i2c_api::I2cDevice, Option); - pub struct PmbusRailBinding { pub name: &'static str, - pub summon_fn: SummonFn, - pub status_bits: Capabilities, + pub device_index: usize, + pub rail_index: Option, } include!(concat!(env!("OUT_DIR"), "/pmbus_mapping.rs")); diff --git a/task/control-plane-agent/src/mgs_common.rs b/task/control-plane-agent/src/mgs_common.rs index 107dd93061..f454a8f5c5 100644 --- a/task/control-plane-agent/src/mgs_common.rs +++ b/task/control-plane-agent/src/mgs_common.rs @@ -757,7 +757,21 @@ impl MgsCommon { // Yep! Call the i2c-generated function to get back an I2cDevice // and the rail index necessary to call the status function let info = &crate::pmbus::PMBUS_RAIL_TO_I2C_DEVICE_MAP[idx]; - let (device, rail_idx) = (info.summon_fn)(crate::I2C.get_task_id()); + let device = crate::i2c_config::pmbus::device_by_index( + crate::I2C.get_task_id(), + info.device_index, + ) + // This should only fail if the lookup table doesn't contain an entry + // for this device index, which is a codegen bug. + .unwrap_lite(); + let device_caps = task_validate_api::DEVICES + // Use `get()` here so we can have one unique panic site rather than + // two :) + .get(info.device_index) + .and_then(|d| d.pmbus_capabilities) + // Similarly, not having an entry in `task_validate_api::DEVICES`, + // or that device not being PMBus, would also be a codegen bug. + .unwrap_lite(); // Local version of: // `impl From for mgs::PmbusStatusReadError` @@ -781,8 +795,8 @@ impl MgsCommon { // isn't successful. Plumb the errors as necessary if that happens. let status = drv_i2c_devices::PmbusStatus::try_read_from( &device, - rail_idx, - info.status_bits, + info.rail_index, + device_caps, ) .map_err(err_fixer) .map_err(PmbusStatusError::FailedStatusWord) diff --git a/task/validate-api/Cargo.toml b/task/validate-api/Cargo.toml index 550d060dc2..3d66d3a3fd 100644 --- a/task/validate-api/Cargo.toml +++ b/task/validate-api/Cargo.toml @@ -29,8 +29,10 @@ bench = false [build-dependencies] build-i2c = { path = "../../build/i2c" } +drv-i2c-types = { path = "../../drv/i2c-types" } gateway-messages.workspace = true idol.workspace = true +pmbus.workspace = true anyhow.workspace = true [lints] diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index f57ed37e58..474b7eda3e 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use drv_i2c_types::PmbusCapabilities; use std::io::Write; fn main() -> Result<(), Box> { @@ -59,7 +60,25 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { let mut id2idx = std::collections::BTreeMap::new(); for (idx, dev) in devices.into_iter().enumerate() { - let is_pmbus = dev.is_pmbus(); + let pmbus_capabilities = if dev.pmbus.is_some() { + let device_name = &dev.device; + let Some(caps) = + PMBUS_GENERATOR.iter().find_map(|&(name, generate)| { + (name == device_name).then(generate) + }) + else { + println!( + "cargo::error=unknown pmbus device: {device_name}, add an \ + entry to PMBUS_GENERATOR in {} for PMBus status register \ + and VPD support.", + file!(), + ); + panic!("Unsupported pmbus device: {device_name}"); + }; + Some(caps) + } else { + None + }; writeln!(file, " DeviceDescription {{")?; writeln!(file, " device: {:?},", dev.device)?; writeln!(file, " description: {:?},", dev.description)?; @@ -84,7 +103,14 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { ); missing_ids += 1; }; - writeln!(file, " is_pmbus: {is_pmbus:?},")?; + match pmbus_capabilities { + Some(caps) => writeln!( + file, + " pmbus_capabilities: Some(drv_i2c_api::PmbusCapabilities(0x{:08x})),", + caps.0, + )?, + None => writeln!(file, " pmbus_capabilities: None,")?, + } writeln!(file, " sensors: &[")?; for s in dev.sensors { writeln!(file, " SensorDescription {{")?; @@ -131,3 +157,71 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { Ok(()) } + +/// Look at the `pmbus` crate metadata to see if a specific command is "Illegal" +/// and set the capability bit if not. +macro_rules! set_if_pmbus_read_illegal { + ($out:ident, $module:ident, $cmd:ident) => {{ + use pmbus::{Command, Operation}; + if pmbus::commands::$module::CommandCode::$cmd.read_op() + != Operation::Illegal + { + $out |= PmbusCapabilities::$cmd.0; + } + }}; +} + +/// For a given device, calculate the `PmbusCapabilities` for each of the +/// status registers. +/// +/// The pmbus functions are not const, so generate a closure instead. +macro_rules! pmbus_generator { + ($name:literal, $module:ident) => { + ($name, || { + let mut out = 0u32; + set_if_pmbus_read_illegal!(out, $module, STATUS_WORD); + set_if_pmbus_read_illegal!(out, $module, STATUS_VOUT); + set_if_pmbus_read_illegal!(out, $module, STATUS_IOUT); + set_if_pmbus_read_illegal!(out, $module, STATUS_TEMPERATURE); + set_if_pmbus_read_illegal!(out, $module, STATUS_CML); + set_if_pmbus_read_illegal!(out, $module, STATUS_OTHER); + set_if_pmbus_read_illegal!(out, $module, STATUS_INPUT); + set_if_pmbus_read_illegal!(out, $module, STATUS_MFR_SPECIFIC); + set_if_pmbus_read_illegal!(out, $module, STATUS_FANS_1_2); + set_if_pmbus_read_illegal!(out, $module, STATUS_FANS_3_4); + // VPD bits + set_if_pmbus_read_illegal!(out, $module, MFR_ID); + set_if_pmbus_read_illegal!(out, $module, MFR_MODEL); + set_if_pmbus_read_illegal!(out, $module, MFR_REVISION); + set_if_pmbus_read_illegal!(out, $module, MFR_SERIAL); + set_if_pmbus_read_illegal!(out, $module, MFR_LOCATION); + set_if_pmbus_read_illegal!(out, $module, MFR_DATE); + set_if_pmbus_read_illegal!(out, $module, IC_DEVICE_ID); + set_if_pmbus_read_illegal!(out, $module, IC_DEVICE_REV); + PmbusCapabilities(out) + }) + }; +} + +type PmbusDeviceRow = (&'static str, fn() -> PmbusCapabilities); + +// Before you add a pmbus device to this list, you should make sure that you +// have reviewed the pmbus crate to make sure that any unsupported status +// registers are marked as illegal, similar to oxidecomputer/pmbus#35. +// +// Failure to do so could cause CML or OTHER error bits to be set. Just adding +// the device to this list (without accurate `pmbus` crate information) will +// likely make the compilation succeed, but should not be done for production +// devices where this may trigger runtime CML errors. +const PMBUS_GENERATOR: &[PmbusDeviceRow] = &[ + pmbus_generator!("adm127x", adm127x), + pmbus_generator!("bmr491", bmr491), + pmbus_generator!("isl68224", isl68224), + pmbus_generator!("lm5066", lm5066), + pmbus_generator!("lm5066i", lm5066i), + pmbus_generator!("mwocp67", mwocp67), + pmbus_generator!("mwocp68", mwocp68), + pmbus_generator!("raa229618", raa229618), + pmbus_generator!("raa229620a", raa229620a), + pmbus_generator!("tps546b24a", tps546b24a), +]; diff --git a/task/validate-api/src/lib.rs b/task/validate-api/src/lib.rs index c5b8556953..80c80710e9 100644 --- a/task/validate-api/src/lib.rs +++ b/task/validate-api/src/lib.rs @@ -7,7 +7,7 @@ #![no_std] use derive_idol_err::IdolError; -use drv_i2c_api::ResponseCode; +use drv_i2c_api::{PmbusCapabilities, ResponseCode}; use userlib::{FromPrimitive, sys_send}; use zerocopy::{Immutable, IntoBytes, KnownLayout}; @@ -75,7 +75,7 @@ pub struct DeviceDescription { pub description: &'static str, pub sensors: &'static [SensorDescription], pub id: &'static str, - pub is_pmbus: bool, + pub pmbus_capabilities: Option, } include!(concat!(env!("OUT_DIR"), "/device_descriptions.rs")); From b441939070e4034e197c3604af506a7cde996834 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 27 Aug 2026 16:30:42 -0700 Subject: [PATCH 04/28] redo vpd reader to work with oxidecomputer/management-gateway-service#500 --- drv/i2c-devices/src/lib.rs | 135 +++++++++++++------------------------ 1 file changed, 47 insertions(+), 88 deletions(-) diff --git a/drv/i2c-devices/src/lib.rs b/drv/i2c-devices/src/lib.rs index 7895138bf9..5214593e7e 100644 --- a/drv/i2c-devices/src/lib.rs +++ b/drv/i2c-devices/src/lib.rs @@ -405,22 +405,9 @@ impl PmbusStatus { } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PmbusVpd<'buf> { - /// `MFR_ID` (PMBus operation 0x99) - pub mfr_id: Option<&'buf [u8]>, - /// `MFR_MODEL` (PMBus operation 0x9A) - pub mfr_model: Option<&'buf [u8]>, - /// `MFR_REVISION` (PMBus operation 0x9B) - pub mfr_revision: Option<&'buf [u8]>, - /// `MFR_LOCATION` (PMBus operation 0x9C) - pub mfr_location: Option<&'buf [u8]>, - /// `MFR_DATE` (PMBus operation 0x9D) - pub mfr_date: Option<&'buf [u8]>, - /// `MFR_SERIAL` (PMBus operation 0x9E) - pub mfr_serial: Option<&'buf [u8]>, - pub ic_device_id: Option<&'buf [u8]>, - pub ic_device_rev: Option<&'buf [u8]>, +pub struct PmbusVpdReader<'dev> { + dev: &'dev I2cDevice, + caps: PmbusCapabilities, } #[derive(Copy, Clone, Eq, PartialEq)] @@ -456,81 +443,53 @@ pub enum PmbusVpdCmd { IcDeviceRev = pmbus::CommandCode::IC_DEVICE_REV as u8, } -impl<'buf> PmbusVpd<'buf> { - /// SMBus block reads may not be longer than 32 bytes. - const BLOCK_LEN: usize = 32; - /// Maximum length currently required to read a complete set of VPD - /// registers from a PMBus device. - /// - /// Currently, this is 8 32-byte blocks (one for each register that we may - /// read). If more values are added in the future, this will need to be - /// embiggened. - pub const BUF_LEN: usize = Self::BLOCK_LEN * 8; - - /// Attempt to read a [`PmbusVpd`] from the given device. - pub fn try_read_from( - dev: &I2cDevice, - buf: &'buf mut [u8; PmbusVpd::BUF_LEN], - device_caps: PmbusCapabilities, - ) -> Result { - use core::ops::Range; - // Keep the lines short - use PmbusCapabilities as Cap; - use PmbusVpdCmd as Cmd; - - if !device_caps.supports_any(&Cap::ANY_VPD_REGS) { - return Err(PmbusVpdError::NoVpd); +impl PmbusVpdCmd { + fn capability(&self) -> PmbusCapabilities { + match self { + Self::MfrId => PmbusCapabilities::MFR_ID, + Self::MfrModel => PmbusCapabilities::MFR_MODEL, + Self::MfrRevision => PmbusCapabilities::MFR_REVISION, + Self::MfrSerial => PmbusCapabilities::MFR_SERIAL, + Self::MfrLocation => PmbusCapabilities::MFR_LOCATION, + Self::MfrDate => PmbusCapabilities::MFR_DATE, + Self::IcDeviceId => PmbusCapabilities::IC_DEVICE_ID, + Self::IcDeviceRev => PmbusCapabilities::IC_DEVICE_REV, } + } +} - let read = |cmd: Cmd, - cap: Cap, - buf: &mut [u8; PmbusVpd::BUF_LEN], - curr_off: &mut usize| - -> Result>, PmbusVpdError> { - if !device_caps.supports(&cap) { - return Ok(None); - } - let off = *curr_off; - // PMBus block reads may not be longer than 32 bytes. Clamp this - // down as `drv_i2c_api` gets mad if it sees a lease of >255B. - let Some(block) = buf.get_mut(off..off + PmbusVpd::BLOCK_LEN) - else { - // This shouldn't ever happen as we never call this more than - // BUF_LEN * 8 times... - unreachable!(); - }; - let len = dev - .read_block(cmd, block) - .map_err(|err| PmbusVpdError::BadRead { cmd, err })?; - *curr_off += len; - Ok(Some(off..*curr_off)) - }; +impl<'dev> PmbusVpdReader<'dev> { + /// SMBus block reads may not be longer than 32 bytes. + pub const BLOCK_LEN: usize = 32; + + pub fn new( + device: &'dev I2cDevice, + device_capabilities: PmbusCapabilities, + ) -> Self { + Self { + dev: device, + caps: device_capabilities, + } + } - let mut off = 0; - let mfr_range = read(Cmd::MfrId, Cap::MFR_ID, buf, &mut off)?; - let model_range = read(Cmd::MfrModel, Cap::MFR_MODEL, buf, &mut off)?; - let rev_range = - read(Cmd::MfrRevision, Cap::MFR_REVISION, buf, &mut off)?; - let location_range = - read(Cmd::MfrLocation, Cap::MFR_LOCATION, buf, &mut off)?; - let date_range = read(Cmd::MfrDate, Cap::MFR_DATE, buf, &mut off)?; - let serial_range = - read(Cmd::MfrSerial, Cap::MFR_SERIAL, buf, &mut off)?; - let ic_id_range = - read(Cmd::IcDeviceId, Cap::IC_DEVICE_ID, buf, &mut off)?; - let ic_rev_range = - read(Cmd::IcDeviceRev, Cap::IC_DEVICE_REV, buf, &mut off)?; - - Ok(Self { - mfr_id: mfr_range.map(|r| &buf[r]), - mfr_model: model_range.map(|r| &buf[r]), - mfr_revision: rev_range.map(|r| &buf[r]), - mfr_location: location_range.map(|r| &buf[r]), - mfr_date: date_range.map(|r| &buf[r]), - mfr_serial: serial_range.map(|r| &buf[r]), - ic_device_id: ic_id_range.map(|r| &buf[r]), - ic_device_rev: ic_rev_range.map(|r| &buf[r]), - }) + /// Attempt to read a PMBus VPD block register from the given device into + /// the provided buffer, returning the number of bytes read (or `None`, + /// indicating that the device does not support that register). + /// + /// This does not first zero the buffer. If you want it to be zeroed, you + /// must first do that. + pub fn try_read( + &self, + command: PmbusVpdCmd, + buf: &mut [u8; PmbusVpdReader::BLOCK_LEN], + ) -> Result, PmbusVpdError> { + if !self.caps.supports(&cap) { + return Ok(None); + } + self.dev + .read_block(cmd, block) + .map_err(|err| PmbusVpdError::BadRead { cmd, err }) + .map(Some) } } From 96bddd21747146221dbbd053b108054f8a04daf2 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 27 Aug 2026 18:27:27 -0700 Subject: [PATCH 05/28] oops --- drv/i2c-devices/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drv/i2c-devices/src/lib.rs b/drv/i2c-devices/src/lib.rs index 5214593e7e..9df65e1555 100644 --- a/drv/i2c-devices/src/lib.rs +++ b/drv/i2c-devices/src/lib.rs @@ -480,14 +480,14 @@ impl<'dev> PmbusVpdReader<'dev> { /// must first do that. pub fn try_read( &self, - command: PmbusVpdCmd, + cmd: PmbusVpdCmd, buf: &mut [u8; PmbusVpdReader::BLOCK_LEN], ) -> Result, PmbusVpdError> { - if !self.caps.supports(&cap) { + if !self.caps.supports(&cmd.capability()) { return Ok(None); } self.dev - .read_block(cmd, block) + .read_block(cmd, buf) .map_err(|err| PmbusVpdError::BadRead { cmd, err }) .map(Some) } From ac6c20676fd162ba617fed3dcd253529a9aecbfe Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 27 Aug 2026 19:51:24 -0700 Subject: [PATCH 06/28] draw much of the remaining owl --- Cargo.lock | 5 +- build/i2c/src/lib.rs | 17 ++- task/control-plane-agent/Cargo.toml | 1 + task/control-plane-agent/src/inventory.rs | 110 +++++++++++++++--- task/control-plane-agent/src/main.rs | 4 + task/control-plane-agent/src/mgs_common.rs | 14 ++- .../src/mgs_compute_sled.rs | 17 +++ task/control-plane-agent/src/mgs_minibar.rs | 17 +++ task/control-plane-agent/src/mgs_psc.rs | 17 +++ task/control-plane-agent/src/mgs_sidecar.rs | 17 +++ 10 files changed, 200 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ae9aca0a5..f0c793bd67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#1e6041896c216c617c612a08c32586b2d8f06399" +source = "git+https://github.com/oxidecomputer/management-gateway-service#56928eb14fbdba9a4517e371ec8d357ca6088b7a" dependencies = [ "hubpack", "serde", @@ -3330,7 +3330,7 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#1e6041896c216c617c612a08c32586b2d8f06399" +source = "git+https://github.com/oxidecomputer/management-gateway-service#56928eb14fbdba9a4517e371ec8d357ca6088b7a" dependencies = [ "bitflags 2.9.4", "gateway-ereport-messages", @@ -6196,6 +6196,7 @@ dependencies = [ "gateway-messages", "heapless", "host-sp-messages", + "hubpack", "humpty", "idol", "idol-runtime", diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index a7f08ee0b9..62d1c43901 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -1502,11 +1502,19 @@ impl ConfigGenerator { } } - if !byrail.is_empty() || !pmbus_devices.is_empty() { + // + // N.B. that if we are generating PMBus devices, we must always generate + // the `i2c_config::pmbus` module and the `device_by_index` function + // within it, even if we shall generate no actual PMBus devices. In this + // case, that function will just always return `None`, which is the + // right thing for it to do if there are no PMBus thingies on the board. + // + if which == PowerDevices::PMBus || !byrail.is_empty() { write!( output, r##" pub mod {} {{ + #[allow(unused_imports)] use drv_i2c_api::{{I2cDevice, Controller, PortIndex}}; use userlib::TaskId; "##, @@ -1517,6 +1525,12 @@ impl ConfigGenerator { )?; if which == PowerDevices::PMBus { + let must_consume_task = if byrail.is_empty() { + // don't get lmaoed by the linter + "let _ = task;" + } else { + "" + }; write!( &mut self.output, r##" @@ -1526,6 +1540,7 @@ impl ConfigGenerator { task: TaskId, index: usize, ) -> Option {{ + {must_consume_task} match index {{"##, )?; diff --git a/task/control-plane-agent/Cargo.toml b/task/control-plane-agent/Cargo.toml index 841fc055ca..19772846ea 100644 --- a/task/control-plane-agent/Cargo.toml +++ b/task/control-plane-agent/Cargo.toml @@ -8,6 +8,7 @@ cfg-if.workspace = true enum-map.workspace = true gateway-messages.workspace = true heapless.workspace = true +hubpack.workspace = true humpty.workspace = true idol-runtime.workspace = true num-traits.workspace = true diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 6dd1d87ab4..8822fb4efa 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -3,13 +3,17 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use drv_i2c_api::PmbusCapabilities; +use drv_i2c_devices::{PmbusVpdCmd, PmbusVpdError, PmbusVpdReader}; use gateway_messages::measurement::{ Measurement, MeasurementError, MeasurementKind, }; use gateway_messages::sp_impl::{BoundsChecked, DeviceDescription}; use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, + VpdError, + vpd::{PmbusVpd, VpdRef}, }; +use static_cell::ClaimOnceCell; use task_sensor_api::Sensor as SensorTask; use task_sensor_api::SensorError; use task_validate_api::{DEVICES as VALIDATE_DEVICES, Sensor}; @@ -22,15 +26,23 @@ userlib::task_slot!(SENSOR, sensor); pub(crate) struct Inventory { validate_task: Validate, sensor_task: SensorTask, + pmbus_vpd: &'static mut PmbusVpd, } impl Inventory { pub(crate) fn new() -> Self { let () = devices_with_static_validation::ASSERT_EACH_DEVICE_FITS_IN_ONE_PACKET; + // A single static copy of the `PmbusVpd` struct into which we shall + // read the PMBus blocks, and from which we shall serialzie it when + // reading VPD. This keeps the rather big struct off our stack. + static PMBUS_VPD: ClaimOnceCell = + ClaimOnceCell::new(PmbusVpd::EMPTY); + Self { validate_task: Validate::from(VALIDATE.get_task_id()), sensor_task: SensorTask::from(SENSOR.get_task_id()), + pmbus_vpd: PMBUS_VPD.claim(), } } @@ -38,21 +50,6 @@ impl Inventory { OUR_DEVICES.len() + VALIDATE_DEVICES.len() } - /// Returns the generated device index and PMBus capabilities for a component. - pub(crate) fn pmbus_device( - &self, - component: &SpComponent, - ) -> Result<(usize, PmbusCapabilities), SpError> { - let Index::ValidateDevice(index) = Index::try_from(component)? else { - return Err(SpError::RequestUnsupportedForComponent); - }; - let Some(capabilities) = VALIDATE_DEVICES[index].pmbus_capabilities - else { - return Err(SpError::RequestUnsupportedForComponent); - }; - Ok((index, capabilities)) - } - pub(crate) fn num_component_details( &self, component: &SpComponent, @@ -169,6 +166,89 @@ impl Inventory { presence, } } + + pub(crate) fn component_vpd( + &mut self, + component: &SpComponent, + buf: &mut [u8], + ) -> Result { + let Index::ValidateDevice(device_index) = Index::try_from(component)? + else { + return Err(SpError::RequestUnsupportedForComponent); + }; + + // Is this a PMBus device? + let vpd = if let Some(capabilities) = + VALIDATE_DEVICES[device_index].pmbus_capabilities + { + // Does it have any VPD registers? + if !capabilities.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { + return Err(SpError::RequestUnsupportedForComponent); + } + + let device = crate::i2c_config::pmbus::device_by_index( + crate::I2C.get_task_id(), + device_index, + ) + // Inventory and I2C device descriptions share indices, so a PMBus + // inventory entry must have a generated I2C device. + .unwrap_lite(); + let reader = PmbusVpdReader::new(&device, capabilities); + let vpd = &mut *self.pmbus_vpd; + + let map_read_error = |err| match err { + PmbusVpdError::NoVpd => SpError::RequestUnsupportedForComponent, + PmbusVpdError::BadRead { cmd: _, err } => { + SpError::Vpd(match err { + drv_i2c_api::ResponseCode::NoDevice => { + VpdError::NotPresent + } + drv_i2c_api::ResponseCode::NoRegister => { + VpdError::Unavailable + } + drv_i2c_api::ResponseCode::BusLocked + | drv_i2c_api::ResponseCode::BusLockedMux + | drv_i2c_api::ResponseCode::ControllerBusy => { + VpdError::DeviceTimeout + } + _ => VpdError::DeviceError, + }) + } + }; + + vpd.mfr_id + .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrId, buf)) + .map_err(map_read_error)?; + vpd.mfr_model + .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrModel, buf)) + .map_err(map_read_error)?; + vpd.mfr_revision + .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrRevision, buf)) + .map_err(map_read_error)?; + vpd.mfr_location + .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrLocation, buf)) + .map_err(map_read_error)?; + vpd.mfr_date + .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrDate, buf)) + .map_err(map_read_error)?; + vpd.mfr_serial + .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrSerial, buf)) + .map_err(map_read_error)?; + vpd.ic_device_id + .read_into(|buf| reader.try_read(PmbusVpdCmd::IcDeviceId, buf)) + .map_err(map_read_error)?; + vpd.ic_device_rev + .read_into(|buf| reader.try_read(PmbusVpdCmd::IcDeviceRev, buf)) + .map_err(map_read_error)?; + VpdRef::Pmbus(&*vpd) + } else { + // ...for now + return Err(SpError::RequestUnsupportedForComponent); + }; + + hubpack::serialize(buf, &vpd) + .map_err(|_| SpError::Vpd(VpdError::BadBuffer)) + } } // Our parent deals primarily in overall device indices (`0..num_devices()`), diff --git a/task/control-plane-agent/src/main.rs b/task/control-plane-agent/src/main.rs index 42fba95882..5a2b1dd5b7 100644 --- a/task/control-plane-agent/src/main.rs +++ b/task/control-plane-agent/src/main.rs @@ -143,6 +143,7 @@ enum MgsMessage { component: SpComponent, }, GetPowerState, + GetPowerStateWithReason, SetPowerState(PowerState), Inventory, HostPhase2Data { @@ -159,6 +160,9 @@ enum MgsMessage { ComponentDetails { component: SpComponent, }, + ComponentGetVpd { + component: SpComponent, + }, ComponentClearStatus { component: SpComponent, }, diff --git a/task/control-plane-agent/src/mgs_common.rs b/task/control-plane-agent/src/mgs_common.rs index f454a8f5c5..dde70d95d3 100644 --- a/task/control-plane-agent/src/mgs_common.rs +++ b/task/control-plane-agent/src/mgs_common.rs @@ -36,7 +36,7 @@ use task_control_plane_agent_api::OxideIdentity; use task_net_api::MacAddress; use task_packrat_api::Packrat; use task_sensor_api::{Sensor, SensorId}; -use userlib::{kipc, sys_get_timer, task_slot}; +use userlib::{UnwrapLite, kipc, sys_get_timer, task_slot}; task_slot!(SENSOR, sensor); task_slot!(pub PACKRAT, packrat); @@ -237,6 +237,18 @@ impl MgsCommon { } } + pub(crate) fn component_get_vpd( + &mut self, + component: SpComponent, + buf: &mut [u8], + ) -> Result { + ringbuf_entry!(Log::MgsMessage(MgsMessage::ComponentGetVpd { + component + })); + + self.inventory.component_vpd(&component, buf) + } + /// If the targeted component is the SP_ITSELF, then having reset itself, /// it will not be able to respond to the later reset_trigger message. /// diff --git a/task/control-plane-agent/src/mgs_compute_sled.rs b/task/control-plane-agent/src/mgs_compute_sled.rs index 05ced59b5f..e1dccb8a17 100644 --- a/task/control-plane-agent/src/mgs_compute_sled.rs +++ b/task/control-plane-agent/src/mgs_compute_sled.rs @@ -717,6 +717,15 @@ impl SpHandler for MgsHandler { self.power_state_impl() } + fn power_state_with_reason( + &mut self, + ) -> Result { + ringbuf_entry_root!(Log::MgsMessage( + MgsMessage::GetPowerStateWithReason + )); + Err(SpError::RequestUnsupportedForSp) + } + fn set_power_state( &mut self, sender: Sender, @@ -1145,6 +1154,14 @@ impl SpHandler for MgsHandler { .get_component_caboose_value(component, slot, key, buf) } + fn component_get_vpd( + &mut self, + component: SpComponent, + buf: &mut [u8], + ) -> Result { + self.common.component_get_vpd(component, buf) + } + fn reset_component_prepare( &mut self, component: SpComponent, diff --git a/task/control-plane-agent/src/mgs_minibar.rs b/task/control-plane-agent/src/mgs_minibar.rs index 2a1800306c..cdd7179e7f 100644 --- a/task/control-plane-agent/src/mgs_minibar.rs +++ b/task/control-plane-agent/src/mgs_minibar.rs @@ -344,6 +344,15 @@ impl SpHandler for MgsHandler { self.power_state_impl() } + fn power_state_with_reason( + &mut self, + ) -> Result { + ringbuf_entry_root!(Log::MgsMessage( + MgsMessage::GetPowerStateWithReason + )); + Err(SpError::RequestUnsupportedForSp) + } + fn set_power_state( &mut self, sender: Sender, @@ -573,6 +582,14 @@ impl SpHandler for MgsHandler { .get_component_caboose_value(component, slot, key, buf) } + fn component_get_vpd( + &mut self, + component: SpComponent, + buf: &mut [u8], + ) -> Result { + self.common.component_get_vpd(component, buf) + } + fn reset_component_prepare( &mut self, component: SpComponent, diff --git a/task/control-plane-agent/src/mgs_psc.rs b/task/control-plane-agent/src/mgs_psc.rs index 1757fe3aeb..db8e93a8b3 100644 --- a/task/control-plane-agent/src/mgs_psc.rs +++ b/task/control-plane-agent/src/mgs_psc.rs @@ -359,6 +359,15 @@ impl SpHandler for MgsHandler { self.power_state_impl() } + fn power_state_with_reason( + &mut self, + ) -> Result { + ringbuf_entry_root!(Log::MgsMessage( + MgsMessage::GetPowerStateWithReason + )); + Err(SpError::RequestUnsupportedForSp) + } + fn set_power_state( &mut self, sender: Sender, @@ -588,6 +597,14 @@ impl SpHandler for MgsHandler { .get_component_caboose_value(component, slot, key, buf) } + fn component_get_vpd( + &mut self, + component: SpComponent, + buf: &mut [u8], + ) -> Result { + self.common.component_get_vpd(component, buf) + } + fn reset_component_prepare( &mut self, component: SpComponent, diff --git a/task/control-plane-agent/src/mgs_sidecar.rs b/task/control-plane-agent/src/mgs_sidecar.rs index e7d35e0ab1..1acf22bffd 100644 --- a/task/control-plane-agent/src/mgs_sidecar.rs +++ b/task/control-plane-agent/src/mgs_sidecar.rs @@ -785,6 +785,15 @@ impl SpHandler for MgsHandler { self.power_state_impl() } + fn power_state_with_reason( + &mut self, + ) -> Result { + ringbuf_entry_root!(Log::MgsMessage( + MgsMessage::GetPowerStateWithReason + )); + Err(SpError::RequestUnsupportedForSp) + } + fn set_power_state( &mut self, sender: Sender, @@ -1105,6 +1114,14 @@ impl SpHandler for MgsHandler { .get_component_caboose_value(component, slot, key, buf) } + fn component_get_vpd( + &mut self, + component: SpComponent, + buf: &mut [u8], + ) -> Result { + self.common.component_get_vpd(component, buf) + } + fn reset_component_prepare( &mut self, component: SpComponent, From 0dceceb58b08fce02d7b4cc0880c398595c6f7e1 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 10:57:21 -0700 Subject: [PATCH 07/28] wip other VPD types --- Cargo.lock | 2 + build/i2c/src/lib.rs | 22 ++++++ task/control-plane-agent/Cargo.toml | 2 + task/control-plane-agent/src/inventory.rs | 91 +++++++++++++++++------ 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0c793bd67..96bc803626 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6202,6 +6202,7 @@ dependencies = [ "idol-runtime", "lpc55-rom-data", "num-traits", + "oxide-barcode", "p256", "ringbuf", "serde", @@ -6217,6 +6218,7 @@ dependencies = [ "task-sensor-api", "task-validate-api", "task-vpd-api", + "tlvc", "update-buffer", "userlib", "zerocopy 0.8.27", diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 62d1c43901..f762f11a2d 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -577,6 +577,25 @@ impl std::fmt::Display for Sensor { } } +#[derive( + Copy, + Clone, + Deserialize, + Debug, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Default, +)] +#[serde(rename_all = "kebab-case")] +pub enum EepromVpd { + #[default] + SingleBarcode, + FanAssembly, +} + #[derive(PartialEq)] enum PowerDevices { /// PMBus power devices @@ -714,6 +733,9 @@ impl ConfigGenerator { } (_, _) => {} } + if d.vpd_mode != VpdMode::Default { + assert!(d.device == "at24csw080") + } } } diff --git a/task/control-plane-agent/Cargo.toml b/task/control-plane-agent/Cargo.toml index 19772846ea..1c514e1dd7 100644 --- a/task/control-plane-agent/Cargo.toml +++ b/task/control-plane-agent/Cargo.toml @@ -51,6 +51,8 @@ task-net-api = { path = "../net-api", features = ["use-smoltcp"] } task-packrat-api = { path = "../packrat-api" } task-sensor-api = { path = "../sensor-api" } task-validate-api = { path = "../validate-api" } +tlvc = {workspace = true } +oxide-barcode = { path = "../../lib/oxide-barcode" } update-buffer = { path = "../../lib/update-buffer" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } static-cell = { path = "../../lib/static-cell" } diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 8822fb4efa..6d438505be 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -3,15 +3,17 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use drv_i2c_api::PmbusCapabilities; +use drv_i2c_devices::at24csw080::{At24Csw080, Error as EepromError}; use drv_i2c_devices::{PmbusVpdCmd, PmbusVpdError, PmbusVpdReader}; use gateway_messages::measurement::{ Measurement, MeasurementError, MeasurementKind, }; use gateway_messages::sp_impl::{BoundsChecked, DeviceDescription}; +use gateway_messages::vpd::FanAssemblyIdentity; use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, VpdError, - vpd::{PmbusVpd, VpdRef}, + vpd::{PmbusVpd, VpdRef, }, }; use static_cell::ClaimOnceCell; use task_sensor_api::Sensor as SensorTask; @@ -26,23 +28,30 @@ userlib::task_slot!(SENSOR, sensor); pub(crate) struct Inventory { validate_task: Validate, sensor_task: SensorTask, - pmbus_vpd: &'static mut PmbusVpd, + vpd_bufs: &'static mut VpdBufs, +} + +struct VpdBufs { + pmbus: PmbusVpd, + barcode: [u8; oxide_barcode::VpdIdentity::MAX_LEN], } impl Inventory { pub(crate) fn new() -> Self { let () = devices_with_static_validation::ASSERT_EACH_DEVICE_FITS_IN_ONE_PACKET; - // A single static copy of the `PmbusVpd` struct into which we shall + // A single static copy of the VPD structures into which we shall // read the PMBus blocks, and from which we shall serialzie it when // reading VPD. This keeps the rather big struct off our stack. - static PMBUS_VPD: ClaimOnceCell = - ClaimOnceCell::new(PmbusVpd::EMPTY); + static VPD_BUFS: ClaimOnceCell = ClaimOnceCell::new(VpdBufs { + pmbus: PmbusVpd::EMPTY, + barcode: [0u8; oxide_barcode::VpdIdentity::MAX_LEN], + }); Self { validate_task: Validate::from(VALIDATE.get_task_id()), sensor_task: SensorTask::from(SENSOR.get_task_id()), - pmbus_vpd: PMBUS_VPD.claim(), + vpd_bufs: VPD_BUFS.claim(), } } @@ -176,11 +185,12 @@ impl Inventory { else { return Err(SpError::RequestUnsupportedForComponent); }; + let device = VALIDATE_DEVICES + .get(device_index) + .unwrap_or(SpError::RequestUnsupportedForComponent)?; // Is this a PMBus device? - let vpd = if let Some(capabilities) = - VALIDATE_DEVICES[device_index].pmbus_capabilities - { + let vpd = if let Some(capabilities) = device.pmbus_capabilities { // Does it have any VPD registers? if !capabilities.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { return Err(SpError::RequestUnsupportedForComponent); @@ -199,20 +209,7 @@ impl Inventory { let map_read_error = |err| match err { PmbusVpdError::NoVpd => SpError::RequestUnsupportedForComponent, PmbusVpdError::BadRead { cmd: _, err } => { - SpError::Vpd(match err { - drv_i2c_api::ResponseCode::NoDevice => { - VpdError::NotPresent - } - drv_i2c_api::ResponseCode::NoRegister => { - VpdError::Unavailable - } - drv_i2c_api::ResponseCode::BusLocked - | drv_i2c_api::ResponseCode::BusLockedMux - | drv_i2c_api::ResponseCode::ControllerBusy => { - VpdError::DeviceTimeout - } - _ => VpdError::DeviceError, - }) + SpError::Vpd(i2c_error_to_vpd_error(err)) } }; @@ -241,6 +238,25 @@ impl Inventory { .read_into(|buf| reader.try_read(PmbusVpdCmd::IcDeviceRev, buf)) .map_err(map_read_error)?; VpdRef::Pmbus(&*vpd) + } else if device.device == "at24csw080" { + let barcode_buf = &mut self.barcode[..]; + let eeprom = At24Csw080::new(dev); + match drv_oxide_vpd::read_config_nested_from_into( + eeprom, + &[(*b"SASY", 0), (*b"BARC", 0)], + &mut barcode_buf[..], + ) { + Err(drv_oxide_vpd::VpdError::NoSuchChunk(_)) => { + // Not a fan tray EEPROM, read the top level barcode. + todo!() + } + Err(e) => { + return Err(SpError::Vpd(convert_vpd_error(e))); + }, + Ok(n) => { + todo!() + } + } } else { // ...for now return Err(SpError::RequestUnsupportedForComponent); @@ -311,6 +327,35 @@ impl TryFrom<&'_ SpComponent> for Index { Err(SpError::RequestUnsupportedForComponent) } } +) + +fn convert_vpd_error(err: drv_oxide_vpd::VpdError) -> VpdError { + match err { + drv_oxide_vpd::VpdError::ErrorOnBegin(err) + | drv_oxide_vpd::VpdError::ErrorOnRead(err) + | drv_oxide_vpd::VpdError::ErrorOnNext(err) + | drv_oxide_vpd::VpdError::InvalidChecksum(err) => match err { + tlvc::TlvcReadError::User(EepromError::I2cError(err)) => { + i2c_error_to_vpd_error(err) + } + _ => VpdError::BadRead, + }, + _ => VpdError::DeviceFailed, + } +} + +fn i2c_error_to_vpd_error( + err: drv_i2c_api::ResponseCode, +) -> gateway_messages::VpdError { + match err { + drv_i2c_api::ResponseCode::NoDevice => VpdError::NotPresent, + drv_i2c_api::ResponseCode::NoRegister => VpdError::Unavailable, + drv_i2c_api::ResponseCode::BusLocked + | drv_i2c_api::ResponseCode::BusLockedMux + | drv_i2c_api::ResponseCode::ControllerBusy => VpdError::DeviceTimeout, + _ => VpdError::DeviceError, + } +} use devices_with_static_validation::OUR_DEVICES; // We tag this with module `#[allow(dead_code)]` to prevent warnings about the From 586f4dfa83ad3a6e1acea278afec272ff95e6a8c Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 11:02:06 -0700 Subject: [PATCH 08/28] reticulating --- task/control-plane-agent/src/inventory.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 6d438505be..aa869ae12a 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -146,10 +146,15 @@ impl Inventory { let mut capabilities = DeviceCapabilities::empty(); if let Some(pmbus_caps) = device.pmbus_capabilities { + // If this is a PMBus thing, set the PMBus capability bit... capabilities |= DeviceCapabilities::IS_PMBUS; + // ...and if it supports any PMBus VPD commands, set that as well. if pmbus_caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { capabilities |= DeviceCapabilities::HAS_VPD; } + } else if device.device == AT24CSW080 { + // Otherwise, if this is an EEPROM, it also supports VPD. + capabilities |= DeviceCapabilities::HAS_VPD; } if !device.sensors.is_empty() { capabilities |= DeviceCapabilities::HAS_MEASUREMENT_CHANNELS; @@ -238,7 +243,7 @@ impl Inventory { .read_into(|buf| reader.try_read(PmbusVpdCmd::IcDeviceRev, buf)) .map_err(map_read_error)?; VpdRef::Pmbus(&*vpd) - } else if device.device == "at24csw080" { + } else if device.device == "AT24CSW080" { let barcode_buf = &mut self.barcode[..]; let eeprom = At24Csw080::new(dev); match drv_oxide_vpd::read_config_nested_from_into( @@ -267,6 +272,8 @@ impl Inventory { } } +const AT24CSW080: &str = "at24csw080"; + // Our parent deals primarily in overall device indices (`0..num_devices()`), // but internally we partition that into `[OUR_DEVICES | VALIDATE_DEVICES]`. // This enum helps us avoid needing to mix adjustment between partitioned From a610f19835f51a63ef8590aa902475f211852c04 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 15:37:47 -0700 Subject: [PATCH 09/28] normal eeproms work now --- Cargo.lock | 6 +- app/cosmo/base.toml | 1 + app/gimlet/base.toml | 1 + build/i2c/src/lib.rs | 92 ++++---- drv/i2c-devices/src/lib.rs | 19 +- drv/i2c-types/src/lib.rs | 9 +- task/control-plane-agent/Cargo.toml | 4 +- task/control-plane-agent/src/inventory.rs | 253 +++++++++++++-------- task/control-plane-agent/src/mgs_common.rs | 2 +- task/validate-api/build.rs | 20 +- task/validate-api/src/lib.rs | 12 + 11 files changed, 235 insertions(+), 184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96bc803626..27450737f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#56928eb14fbdba9a4517e371ec8d357ca6088b7a" +source = "git+https://github.com/oxidecomputer/management-gateway-service#101c3eb48808bb0d7706a4c31a74436d7215afe4" dependencies = [ "hubpack", "serde", @@ -3330,7 +3330,7 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#56928eb14fbdba9a4517e371ec8d357ca6088b7a" +source = "git+https://github.com/oxidecomputer/management-gateway-service#101c3eb48808bb0d7706a4c31a74436d7215afe4" dependencies = [ "bitflags 2.9.4", "gateway-ereport-messages", @@ -6183,6 +6183,7 @@ dependencies = [ "drv-ignition-api", "drv-lpc55-update-api", "drv-monorail-api", + "drv-oxide-vpd", "drv-rng-api", "drv-sidecar-seq-api", "drv-sprot-api", @@ -6202,7 +6203,6 @@ dependencies = [ "idol-runtime", "lpc55-rom-data", "num-traits", - "oxide-barcode", "p256", "ringbuf", "serde", diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index d9e888b495..83314a3d00 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -968,6 +968,7 @@ mux = 1 segment = 7 address = 0b1010_000 device = "at24csw080" +vpd = "fan-assembly" description = "Fan VPD" refdes = ["J34", "U1"] name = "fan_vpd" diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 1bb9ba64bf..8dc092ac78 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -886,6 +886,7 @@ mux = 1 segment = 3 address = 0b1010_000 device = "at24csw080" +vpd = "fan-assembly" description = "Fan VPD" refdes = ["J180", "U1"] name = "fan_vpd" diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index f762f11a2d..306d58757b 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -74,6 +74,7 @@ struct I2cController { // a controller *and* a named bus), so the validation code should go to // additional lengths to assure that these mistakes are caught in compilation. // + #[derive(Clone, Debug, Deserialize, PartialOrd, Ord, Eq, PartialEq)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] #[allow(dead_code)] @@ -108,6 +109,10 @@ struct I2cDevice { /// description of device description: String, + /// Overrides the default VPD representation for this device. + #[serde(default)] + vpd: EepromVpd, + /// reference designator, if any refdes: Option, @@ -733,8 +738,12 @@ impl ConfigGenerator { } (_, _) => {} } - if d.vpd_mode != VpdMode::Default { - assert!(d.device == "at24csw080") + if d.vpd == EepromVpd::FanAssembly { + assert!( + d.device == "at24csw080", + "device {} declares fan assembly VPD but is not an AT24CSW080", + d.device, + ); } } } @@ -1197,6 +1206,29 @@ impl ConfigGenerator { write!( output, r##" + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index( + task: TaskId, + index: usize, + ) -> Option {{ + match index {{"##, + )?; + + // These indices are shared with `device_descriptions()` and the + // generated validation dispatch. + for (index, device) in self.devices.iter().enumerate() { + let out = self.generate_device(device, 20); + writeln!(&mut self.output, "{index} => Some({out}),")?; + } + + write!( + &mut self.output, + r##" + _ => None, + }} + }} + #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn lookup_controller(index: usize) -> Option {{ @@ -1475,13 +1507,9 @@ impl ConfigGenerator { output: &mut String, ) -> Result<()> { let mut byrail = HashMap::new(); - let mut pmbus_devices = Vec::new(); - for (device_index, d) in self.devices.iter().enumerate() { + for d in &self.devices { if let Some(power) = &d.power { - if which == PowerDevices::PMBus && power.pmbus { - pmbus_devices.push((device_index, d)); - } if power.pmbus && which != PowerDevices::PMBus { continue; } @@ -1524,14 +1552,7 @@ impl ConfigGenerator { } } - // - // N.B. that if we are generating PMBus devices, we must always generate - // the `i2c_config::pmbus` module and the `device_by_index` function - // within it, even if we shall generate no actual PMBus devices. In this - // case, that function will just always return `None`, which is the - // right thing for it to do if there are no PMBus thingies on the board. - // - if which == PowerDevices::PMBus || !byrail.is_empty() { + if !byrail.is_empty() { write!( output, r##" @@ -1546,45 +1567,6 @@ impl ConfigGenerator { } )?; - if which == PowerDevices::PMBus { - let must_consume_task = if byrail.is_empty() { - // don't get lmaoed by the linter - "let _ = task;" - } else { - "" - }; - write!( - &mut self.output, - r##" - #[allow(dead_code)] - #[allow(clippy::match_single_binding)] - pub fn device_by_index( - task: TaskId, - index: usize, - ) -> Option {{ - {must_consume_task} - match index {{"##, - )?; - - // These indices are shared with `device_descriptions()` and are - // used to look up the I2C device corresponding to an index in - // the device descriptions array in `task-validate-api`'s - // codegen. - for (index, device) in &pmbus_devices { - let out = self.generate_device(device, 20); - writeln!(&mut self.output, "{index} => Some({out}),")?; - } - - writeln!( - &mut self.output, - r##" - _ => None, - }} - }} -"##, - )?; - } - let mut all: Vec<_> = byrail.iter().collect(); all.sort(); @@ -2081,6 +2063,7 @@ pub struct I2cDeviceDescription { pub device_id: Option, pub name: Option, pub validate_with_raw_read: bool, + pub vpd: EepromVpd, /// If this is a PMBus device, this field contains additional data about the /// PMBus device to be used for generating PMBus-y code. pub pmbus: Option, @@ -2174,6 +2157,7 @@ pub fn device_descriptions() -> impl Iterator { device_id, name: device.name, validate_with_raw_read: device.validate_with_raw_read, + vpd: device.vpd, pmbus, } }, diff --git a/drv/i2c-devices/src/lib.rs b/drv/i2c-devices/src/lib.rs index 9df65e1555..e0c9ad6ac6 100644 --- a/drv/i2c-devices/src/lib.rs +++ b/drv/i2c-devices/src/lib.rs @@ -410,18 +410,6 @@ pub struct PmbusVpdReader<'dev> { caps: PmbusCapabilities, } -#[derive(Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "counters", derive(counters::Count))] -pub enum PmbusVpdError { - /// The device does not support any PMBus VPD registers. - NoVpd, - BadRead { - cmd: PmbusVpdCmd, - #[cfg_attr(feature = "counters", count(children))] - err: drv_i2c_api::ResponseCode, - }, -} - #[derive( Copy, Clone, @@ -482,14 +470,11 @@ impl<'dev> PmbusVpdReader<'dev> { &self, cmd: PmbusVpdCmd, buf: &mut [u8; PmbusVpdReader::BLOCK_LEN], - ) -> Result, PmbusVpdError> { + ) -> Result, drv_i2c_api::ResponseCode> { if !self.caps.supports(&cmd.capability()) { return Ok(None); } - self.dev - .read_block(cmd, buf) - .map_err(|err| PmbusVpdError::BadRead { cmd, err }) - .map(Some) + self.dev.read_block(cmd, buf).map(Some) } } diff --git a/drv/i2c-types/src/lib.rs b/drv/i2c-types/src/lib.rs index f45b7b53b4..4b46ef3912 100644 --- a/drv/i2c-types/src/lib.rs +++ b/drv/i2c-types/src/lib.rs @@ -276,11 +276,10 @@ pub enum Segment { S16 = 16, } -/// Type that denotes the STATUS registers supported for a given PMBus -/// device +/// Describes the status and VPD registers supported by a PMBus device. /// -/// This is typically code-generated at build time using information -/// from the `pmbus` crate. +/// This is typically code-generated at build time using information from the +/// `pmbus` crate. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct PmbusCapabilities(pub u32); @@ -307,7 +306,7 @@ impl PmbusCapabilities { pub const IC_DEVICE_ID: Self = Self(1 << 16); pub const IC_DEVICE_REV: Self = Self(1 << 17); - /// Bitmask for selecting *all* potential VPD register capa + /// Bitmask for selecting all potential VPD register capabilities. pub const ANY_VPD_REGS: Self = Self( // XXX(eliza): this might be less gross if we just used the // bitflags crate for this... diff --git a/task/control-plane-agent/Cargo.toml b/task/control-plane-agent/Cargo.toml index 1c514e1dd7..def9b569e2 100644 --- a/task/control-plane-agent/Cargo.toml +++ b/task/control-plane-agent/Cargo.toml @@ -31,6 +31,7 @@ drv-i2c-devices = { path = "../../drv/i2c-devices" } drv-ignition-api = { path = "../../drv/ignition-api", optional = true } drv-lpc55-update-api = { path = "../../drv/lpc55-update-api" } drv-monorail-api = { path = "../../drv/monorail-api", optional = true } +drv-oxide-vpd = { path = "../../drv/oxide-vpd" } drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-sprot-api = { path = "../../drv/sprot-api" } drv-stm32h7-update-api = { path = "../../drv/stm32h7-update-api" } @@ -51,8 +52,7 @@ task-net-api = { path = "../net-api", features = ["use-smoltcp"] } task-packrat-api = { path = "../packrat-api" } task-sensor-api = { path = "../sensor-api" } task-validate-api = { path = "../validate-api" } -tlvc = {workspace = true } -oxide-barcode = { path = "../../lib/oxide-barcode" } +tlvc = { workspace = true } update-buffer = { path = "../../lib/update-buffer" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } static-cell = { path = "../../lib/static-cell" } diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index aa869ae12a..8c3a37b626 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -2,24 +2,26 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use drv_i2c_api::I2cDevice; use drv_i2c_api::PmbusCapabilities; use drv_i2c_devices::at24csw080::{At24Csw080, Error as EepromError}; -use drv_i2c_devices::{PmbusVpdCmd, PmbusVpdError, PmbusVpdReader}; +use drv_i2c_devices::{PmbusVpdCmd, PmbusVpdReader}; use gateway_messages::measurement::{ Measurement, MeasurementError, MeasurementKind, }; use gateway_messages::sp_impl::{BoundsChecked, DeviceDescription}; -use gateway_messages::vpd::FanAssemblyIdentity; +#[cfg(feature = "compute-sled")] +use gateway_messages::vpd::FanAssemblyVpd; use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, VpdError, - vpd::{PmbusVpd, VpdRef, }, + vpd::{Barcode, BarcodeReadError, PmbusVpd, VpdRef}, }; use static_cell::ClaimOnceCell; use task_sensor_api::Sensor as SensorTask; use task_sensor_api::SensorError; use task_validate_api::{DEVICES as VALIDATE_DEVICES, Sensor}; -use task_validate_api::{Validate, ValidateError, ValidateOk}; +use task_validate_api::{Validate, ValidateError, ValidateOk, VpdKind}; use userlib::UnwrapLite; userlib::task_slot!(VALIDATE, validate); @@ -28,30 +30,41 @@ userlib::task_slot!(SENSOR, sensor); pub(crate) struct Inventory { validate_task: Validate, sensor_task: SensorTask, - vpd_bufs: &'static mut VpdBufs, + vpd_scratch: &'static mut VpdScratch, } -struct VpdBufs { +struct VpdScratch { pmbus: PmbusVpd, - barcode: [u8; oxide_barcode::VpdIdentity::MAX_LEN], + barcode: Barcode, + // Only compute sleds have nested fan tray VPD. + #[cfg(feature = "compute-sled")] + fan_assembly: FanAssemblyVpd, } impl Inventory { pub(crate) fn new() -> Self { let () = devices_with_static_validation::ASSERT_EACH_DEVICE_FITS_IN_ONE_PACKET; - // A single static copy of the VPD structures into which we shall - // read the PMBus blocks, and from which we shall serialzie it when - // reading VPD. This keeps the rather big struct off our stack. - static VPD_BUFS: ClaimOnceCell = ClaimOnceCell::new(VpdBufs { - pmbus: PmbusVpd::EMPTY, - barcode: [0u8; oxide_barcode::VpdIdentity::MAX_LEN], - }); + // Scratch space to read VPD into and serialize it out of by-reference. + // These are potentially quite large (a fan tray with 5 * 128B barcodes + // is ~640B long), so stuff them in a static so that we need not push + // really deep stacks for them. + static VPD_SCRATCH: ClaimOnceCell = + ClaimOnceCell::new(VpdScratch { + pmbus: PmbusVpd::EMPTY, + barcode: Barcode::EMPTY, + #[cfg(feature = "compute-sled")] + fan_assembly: FanAssemblyVpd { + identity: Barcode::EMPTY, + vpd_board_identity: Barcode::EMPTY, + fans: [Barcode::EMPTY; 3], + }, + }); Self { validate_task: Validate::from(VALIDATE.get_task_id()), sensor_task: SensorTask::from(SENSOR.get_task_id()), - vpd_bufs: VPD_BUFS.claim(), + vpd_scratch: VPD_SCRATCH.claim(), } } @@ -145,15 +158,10 @@ impl Inventory { }; let mut capabilities = DeviceCapabilities::empty(); - if let Some(pmbus_caps) = device.pmbus_capabilities { - // If this is a PMBus thing, set the PMBus capability bit... + if device.pmbus_capabilities.is_some() { capabilities |= DeviceCapabilities::IS_PMBUS; - // ...and if it supports any PMBus VPD commands, set that as well. - if pmbus_caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { - capabilities |= DeviceCapabilities::HAS_VPD; - } - } else if device.device == AT24CSW080 { - // Otherwise, if this is an EEPROM, it also supports VPD. + } + if device.vpd.is_some() { capabilities |= DeviceCapabilities::HAS_VPD; } if !device.sensors.is_empty() { @@ -186,93 +194,134 @@ impl Inventory { component: &SpComponent, buf: &mut [u8], ) -> Result { - let Index::ValidateDevice(device_index) = Index::try_from(component)? - else { + let Index::ValidateDevice(index) = Index::try_from(component)? else { return Err(SpError::RequestUnsupportedForComponent); }; - let device = VALIDATE_DEVICES - .get(device_index) - .unwrap_or(SpError::RequestUnsupportedForComponent)?; - - // Is this a PMBus device? - let vpd = if let Some(capabilities) = device.pmbus_capabilities { - // Does it have any VPD registers? - if !capabilities.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { - return Err(SpError::RequestUnsupportedForComponent); - } + let device = VALIDATE_DEVICES[index]; - let device = crate::i2c_config::pmbus::device_by_index( - crate::I2C.get_task_id(), - device_index, - ) - // Inventory and I2C device descriptions share indices, so a PMBus - // inventory entry must have a generated I2C device. - .unwrap_lite(); - let reader = PmbusVpdReader::new(&device, capabilities); - let vpd = &mut *self.pmbus_vpd; - - let map_read_error = |err| match err { - PmbusVpdError::NoVpd => SpError::RequestUnsupportedForComponent, - PmbusVpdError::BadRead { cmd: _, err } => { - SpError::Vpd(i2c_error_to_vpd_error(err)) - } - }; - - vpd.mfr_id - .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrId, buf)) - .map_err(map_read_error)?; - vpd.mfr_model - .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrModel, buf)) - .map_err(map_read_error)?; - vpd.mfr_revision - .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrRevision, buf)) - .map_err(map_read_error)?; - vpd.mfr_location - .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrLocation, buf)) - .map_err(map_read_error)?; - vpd.mfr_date - .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrDate, buf)) - .map_err(map_read_error)?; - vpd.mfr_serial - .read_into(|buf| reader.try_read(PmbusVpdCmd::MfrSerial, buf)) - .map_err(map_read_error)?; - vpd.ic_device_id - .read_into(|buf| reader.try_read(PmbusVpdCmd::IcDeviceId, buf)) - .map_err(map_read_error)?; - vpd.ic_device_rev - .read_into(|buf| reader.try_read(PmbusVpdCmd::IcDeviceRev, buf)) - .map_err(map_read_error)?; - VpdRef::Pmbus(&*vpd) - } else if device.device == "AT24CSW080" { - let barcode_buf = &mut self.barcode[..]; - let eeprom = At24Csw080::new(dev); - match drv_oxide_vpd::read_config_nested_from_into( - eeprom, - &[(*b"SASY", 0), (*b"BARC", 0)], - &mut barcode_buf[..], - ) { - Err(drv_oxide_vpd::VpdError::NoSuchChunk(_)) => { - // Not a fan tray EEPROM, read the top level barcode. - todo!() + let Some(vpd_kind) = device.vpd else { + return Err(SpError::RequestUnsupportedForComponent); + }; + let dev = crate::i2c_config::devices::device_by_index( + crate::I2C.get_task_id(), + index, + ) + // Inventory and generated I2C accessors share device indices. + .unwrap_lite(); + + let vpd = match vpd_kind { + VpdKind::Pmbus => { + // Either of these not being set would indicate that code + // generation did an oopsie, so we *could* consider this + // unreachable here, but I think it's nicer to not panic. + let Some(caps) = device.pmbus_capabilities else { + return Err(SpError::RequestUnsupportedForComponent); + }; + if !caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) { + return Err(SpError::RequestUnsupportedForComponent); } - Err(e) => { - return Err(SpError::Vpd(convert_vpd_error(e))); - }, - Ok(n) => { - todo!() + + fn read_one( + reader: &PmbusVpdReader<'_>, + cmd: PmbusVpdCmd, + ) -> impl FnOnce(&mut [u8; 32]) -> Result, SpError> + { + move |buf| { + reader.try_read(cmd, buf).map_err(|code| { + SpError::Vpd(i2c_error_to_vpd_error(code)) + }) + } } + + let reader = PmbusVpdReader::new(&dev, caps); + let out = &mut self.vpd_scratch.pmbus; + + out.mfr_id + .read_into(read_one(&reader, PmbusVpdCmd::MfrId))?; + out.mfr_model + .read_into(read_one(&reader, PmbusVpdCmd::MfrModel))?; + out.mfr_revision + .read_into(read_one(&reader, PmbusVpdCmd::MfrRevision))?; + out.mfr_location + .read_into(read_one(&reader, PmbusVpdCmd::MfrLocation))?; + out.mfr_date + .read_into(read_one(&reader, PmbusVpdCmd::MfrDate))?; + out.mfr_serial + .read_into(read_one(&reader, PmbusVpdCmd::MfrSerial))?; + out.ic_device_id + .read_into(read_one(&reader, PmbusVpdCmd::IcDeviceId))?; + out.ic_device_rev + .read_into(read_one(&reader, PmbusVpdCmd::IcDeviceRev))?; + VpdRef::Pmbus(&*out) + } + VpdKind::Barcode => { + let out = &mut self.vpd_scratch.barcode; + read_one_barcode(out, &dev, &[(*b"BARC", 0)])?; + VpdRef::Barcode(&*out) + } + VpdKind::FanAssembly => self.read_fan_tray_vpd(&dev)?, + VpdKind::Tmp117 => { + // TODO(eliza): draw the rest of the tmp117 + return Err(SpError::RequestUnsupportedForComponent); } - } else { - // ...for now - return Err(SpError::RequestUnsupportedForComponent); }; hubpack::serialize(buf, &vpd) .map_err(|_| SpError::Vpd(VpdError::BadBuffer)) } + + #[cfg(feature = "compute-sled")] + fn read_fan_tray_vpd<'a>( + &'a mut self, + dev: &I2cDevice, + ) -> Result, SpError> { + let out = &mut self.vpd_scratch.fan_assembly; + read_one_barcode(&mut out.identity, dev, &[(*b"BARC", 0)])?; + read_one_barcode( + &mut out.vpd_board_identity, + dev, + &[(*b"SASY", 0), (*b"BARC", 0)], + )?; + for (i, out) in out.fans.iter_mut().enumerate() { + let which_fan = i + 1; + read_one_barcode( + out, + dev, + &[(*b"SASY", 0), (*b"BARC", which_fan)], + )?; + } + Ok(VpdRef::FanAssembly(&*out)) + } + + #[cfg(not(feature = "compute-sled"))] + fn read_fan_tray_vpd<'a>( + &'a mut self, + _: &I2cDevice, + ) -> Result, SpError> { + // Only compute sleds should have EEPROMs configured as fan tray VPD. + Err(SpError::RequestUnsupportedForSp) + } } -const AT24CSW080: &str = "at24csw080"; +fn read_one_barcode( + out: &mut Barcode, + dev: &I2cDevice, + path: &[([u8; 4], usize)], +) -> Result<(), SpError> { + let eeprom = At24Csw080::new(*dev); + out.read_into(|buf| { + drv_oxide_vpd::read_config_nested_from_into(eeprom, path, buf) + }) + .map_err(|err| { + SpError::Vpd(match err { + BarcodeReadError::ReadError(e) => convert_vpd_error(e), + BarcodeReadError::NotUtf8 => VpdError::BadRead, + BarcodeReadError::ReadTooLong => VpdError::BadBuffer, // shouldn't happen! + }) + })?; + + Ok(()) +} // Our parent deals primarily in overall device indices (`0..num_devices()`), // but internally we partition that into `[OUR_DEVICES | VALIDATE_DEVICES]`. @@ -334,20 +383,24 @@ impl TryFrom<&'_ SpComponent> for Index { Err(SpError::RequestUnsupportedForComponent) } } -) fn convert_vpd_error(err: drv_oxide_vpd::VpdError) -> VpdError { + use tlvc::TlvcReadError; + match err { drv_oxide_vpd::VpdError::ErrorOnBegin(err) | drv_oxide_vpd::VpdError::ErrorOnRead(err) | drv_oxide_vpd::VpdError::ErrorOnNext(err) | drv_oxide_vpd::VpdError::InvalidChecksum(err) => match err { - tlvc::TlvcReadError::User(EepromError::I2cError(err)) => { + TlvcReadError::User(EepromError::I2cError(err)) => { i2c_error_to_vpd_error(err) } + tlvc::TlvcReadError::Truncated => VpdError::BadBuffer, _ => VpdError::BadRead, }, - _ => VpdError::DeviceFailed, + drv_oxide_vpd::VpdError::NoSuchChunk(_) + | drv_oxide_vpd::VpdError::InvalidChunkSize + | drv_oxide_vpd::VpdError::NoRootChunk => VpdError::BadRead, } } diff --git a/task/control-plane-agent/src/mgs_common.rs b/task/control-plane-agent/src/mgs_common.rs index dde70d95d3..5859bbfaa4 100644 --- a/task/control-plane-agent/src/mgs_common.rs +++ b/task/control-plane-agent/src/mgs_common.rs @@ -769,7 +769,7 @@ impl MgsCommon { // Yep! Call the i2c-generated function to get back an I2cDevice // and the rail index necessary to call the status function let info = &crate::pmbus::PMBUS_RAIL_TO_I2C_DEVICE_MAP[idx]; - let device = crate::i2c_config::pmbus::device_by_index( + let device = crate::i2c_config::devices::device_by_index( crate::I2C.get_task_id(), info.device_index, ) diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index 474b7eda3e..8f61dc0e07 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -79,6 +79,19 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { } else { None }; + let vpd = if pmbus_capabilities.is_some_and(|caps| { + caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) + }) { + Some("Pmbus") + } else if dev.device == "at24csw080" { + Some(match dev.vpd { + build_i2c::EepromVpd::SingleBarcode => "Barcode", + build_i2c::EepromVpd::FanAssembly => "FanAssembly", + }) + } else { + None + }; + writeln!(file, " DeviceDescription {{")?; writeln!(file, " device: {:?},", dev.device)?; writeln!(file, " description: {:?},", dev.description)?; @@ -111,6 +124,10 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { )?, None => writeln!(file, " pmbus_capabilities: None,")?, } + match vpd { + Some(vpd) => writeln!(file, " vpd: Some(VpdKind::{vpd}),")?, + None => writeln!(file, " vpd: None,")?, + } writeln!(file, " sensors: &[")?; for s in dev.sensors { writeln!(file, " SensorDescription {{")?; @@ -171,8 +188,7 @@ macro_rules! set_if_pmbus_read_illegal { }}; } -/// For a given device, calculate the `PmbusCapabilities` for each of the -/// status registers. +/// Calculates the supported PMBus status and VPD registers for a device. /// /// The pmbus functions are not const, so generate a closure instead. macro_rules! pmbus_generator { diff --git a/task/validate-api/src/lib.rs b/task/validate-api/src/lib.rs index 80c80710e9..b10644cb67 100644 --- a/task/validate-api/src/lib.rs +++ b/task/validate-api/src/lib.rs @@ -62,6 +62,17 @@ pub enum Sensor { Speed, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum VpdKind { + Pmbus = 1, + Barcode, + FanAssembly, + Tmp117, +} + +const _: () = assert!(core::mem::size_of::>() == 1); + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct SensorDescription { pub name: Option<&'static str>, @@ -76,6 +87,7 @@ pub struct DeviceDescription { pub sensors: &'static [SensorDescription], pub id: &'static str, pub pmbus_capabilities: Option, + pub vpd: Option, } include!(concat!(env!("OUT_DIR"), "/device_descriptions.rs")); From dc271bed9d246ca937ea84de039eeeada55103b9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 15:45:47 -0700 Subject: [PATCH 10/28] roll pmbus for new stuff --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 27450737f5..9ed6d86715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4921,7 +4921,7 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "pmbus" version = "0.1.7" -source = "git+https://github.com/oxidecomputer/pmbus#3833d72be1e1e0682082eb76f52eec0c25b91512" +source = "git+https://github.com/oxidecomputer/pmbus#706f477731fe743ed53fefe41c01655a7a7eef7c" dependencies = [ "anyhow", "convert_case 0.11.0", From 29f488f136bcbe0e67503b75a102464dd94fc623 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 16:21:00 -0700 Subject: [PATCH 11/28] do the temp sensors --- task/control-plane-agent/src/inventory.rs | 23 ++++++++++++++++++----- task/validate-api/build.rs | 2 ++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 8c3a37b626..7c98581e06 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -15,7 +15,7 @@ use gateway_messages::vpd::FanAssemblyVpd; use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, VpdError, - vpd::{Barcode, BarcodeReadError, PmbusVpd, VpdRef}, + vpd::{Barcode, BarcodeReadError, PmbusVpd, Tmp117Identity, VpdRef}, }; use static_cell::ClaimOnceCell; use task_sensor_api::Sensor as SensorTask; @@ -260,10 +260,7 @@ impl Inventory { VpdRef::Barcode(&*out) } VpdKind::FanAssembly => self.read_fan_tray_vpd(&dev)?, - VpdKind::Tmp117 => { - // TODO(eliza): draw the rest of the tmp117 - return Err(SpError::RequestUnsupportedForComponent); - } + VpdKind::Tmp117 => return read_tmp117_vpd(&dev, buf), }; hubpack::serialize(buf, &vpd) @@ -303,6 +300,22 @@ impl Inventory { } } +fn read_tmp117_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { + let read = |register| { + dev.read_reg(register) + .map_err(i2c_error_to_vpd_error) + .map_err(SpError::Vpd) + }; + let vpd = Tmp117Identity { + id: read(0x0f_u8)?, + eeprom1: read(0x05_u8)?, + eeprom2: read(0x06_u8)?, + eeprom3: read(0x08_u8)?, + }; + hubpack::serialize(buf, &VpdRef::Tmp117(&vpd)) + .map_err(|_| SpError::Vpd(VpdError::BadBuffer)) +} + fn read_one_barcode( out: &mut Barcode, dev: &I2cDevice, diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index 8f61dc0e07..9314a4705d 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -88,6 +88,8 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { build_i2c::EepromVpd::SingleBarcode => "Barcode", build_i2c::EepromVpd::FanAssembly => "FanAssembly", }) + } else if dev.device == "tmp117" { + Some("Tmp117") } else { None }; From 26691f481f267d9af67245c3a274c61a33dc60e9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 17:01:21 -0700 Subject: [PATCH 12/28] post merge fixy-uppy --- build/i2c/src/lib.rs | 5 +- ..._cosmo_rev-b-dev.toml.devices-Sensors.snap | 639 ++++++++++++++ ...gimlet_rev-f-dev.toml.devices-Sensors.snap | 779 ++++++++++++++++++ ...server_rev-a-dev.toml.devices-Sensors.snap | 149 ++++ ...pp_psc_rev-c-dev.toml.devices-Sensors.snap | 149 ++++ ...idecar_rev-d-dev.toml.devices-Sensors.snap | 397 +++++++++ 6 files changed, 2115 insertions(+), 3 deletions(-) diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 306d58757b..79fccf4d75 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -1219,11 +1219,11 @@ impl ConfigGenerator { // generated validation dispatch. for (index, device) in self.devices.iter().enumerate() { let out = self.generate_device(device, 20); - writeln!(&mut self.output, "{index} => Some({out}),")?; + writeln!(output, "{index} => Some({out}),")?; } write!( - &mut self.output, + output, r##" _ => None, }} @@ -1557,7 +1557,6 @@ impl ConfigGenerator { output, r##" pub mod {} {{ - #[allow(unused_imports)] use drv_i2c_api::{{I2cDevice, Controller, PortIndex}}; use userlib::TaskId; "##, diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_cosmo_rev-b-dev.toml.devices-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_cosmo_rev-b-dev.toml.devices-Sensors.snap index 571dc7ce04..f5e02821b4 100644 --- a/build/xtask/tests/snapshots/i2c_codegen__app_cosmo_rev-b-dev.toml.devices-Sensors.snap +++ b/build/xtask/tests/snapshots/i2c_codegen__app_cosmo_rev-b-dev.toml.devices-Sensors.snap @@ -9,6 +9,645 @@ pub mod devices { #[allow(unused_imports)] use userlib::TaskId; + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index(task: TaskId, index: usize) -> Option { + match index { + 0 => Some( + // Front FPGA virtual mux + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + None, + 0x70, + ), + ), + 1 => Some( + // U.2 Sharkfin A VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x50, + ), + ), + 2 => Some( + // U.2 Sharkfin A hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x38, + ), + ), + 3 => Some( + // U.2 A NVMe Basic Management Command + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x6a, + ), + ), + 4 => Some( + // U.2 Sharkfin B VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x50, + ), + ), + 5 => Some( + // U.2 Sharkfin B hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x38, + ), + ), + 6 => Some( + // U.2 B NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x6a, + ), + ), + 7 => Some( + // U.2 Sharkfin C VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x50, + ), + ), + 8 => Some( + // U.2 Sharkfin C hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x38, + ), + ), + 9 => Some( + // U.2 C NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x6a, + ), + ), + 10 => Some( + // U.2 Sharkfin D VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x50, + ), + ), + 11 => Some( + // U.2 Sharkfin D hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x38, + ), + ), + 12 => Some( + // U.2 D NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x6a, + ), + ), + 13 => Some( + // U.2 Sharkfin E VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S5)), + 0x50, + ), + ), + 14 => Some( + // U.2 Sharkfin E hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S5)), + 0x38, + ), + ), + 15 => Some( + // U.2 E NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S5)), + 0x6a, + ), + ), + 16 => Some( + // U.2 Sharkfin F VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S6)), + 0x50, + ), + ), + 17 => Some( + // U.2 Sharkfin F hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S6)), + 0x38, + ), + ), + 18 => Some( + // U.2 F NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S6)), + 0x6a, + ), + ), + 19 => Some( + // U.2 Sharkfin G VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S9)), + 0x50, + ), + ), + 20 => Some( + // U.2 Sharkfin G hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S9)), + 0x38, + ), + ), + 21 => Some( + // U.2 G NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S9)), + 0x6a, + ), + ), + 22 => Some( + // U.2 Sharkfin H VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S10)), + 0x50, + ), + ), + 23 => Some( + // U.2 Sharkfin H hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S10)), + 0x38, + ), + ), + 24 => Some( + // U.2 H NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S10)), + 0x6a, + ), + ), + 25 => Some( + // U.2 Sharkfin I VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S11)), + 0x50, + ), + ), + 26 => Some( + // U.2 Sharkfin I hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S11)), + 0x38, + ), + ), + 27 => Some( + // U.2 I NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S11)), + 0x6a, + ), + ), + 28 => Some( + // U.2 Sharkfin J VPD + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S12)), + 0x50, + ), + ), + 29 => Some( + // U.2 Sharkfin J hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S12)), + 0x38, + ), + ), + 30 => Some( + // U.2 J NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S12)), + 0x6a, + ), + ), + 31 => Some( + // Southwest temperature sensor + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S13)), + 0x48, + ), + ), + 32 => Some( + // South temperature sensor + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S13)), + 0x49, + ), + ), + 33 => Some( + // Southeast temperature sensor + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S13)), + 0x4a, + ), + ), + 34 => Some( + // Main FPGA virtual mux + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x70, + ), + ), + 35 => Some( + // M.2 A NVMe Basic Management Command + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x6a, + ), + ), + 36 => Some( + // M.2 B NVMe Basic Management Command + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x6a, + ), + ), + 37 => Some( + // CPU via SB-RMI + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x3c, + ), + ), + 38 => Some( + // CPU temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x4c, + ), + ), + 39 => Some( + // Fan VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S7)), + 0x50, + ), + ), + 40 => Some( + // T6 temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S8)), + 0x4c, + ), + ), + 41 => Some( + // A2 3.3V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x24, + ), + ), + 42 => Some( + // A2 5V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x27, + ), + ), + 43 => Some( + // A2 1.8V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x29, + ), + ), + 44 => Some( + // M.2 hot plug controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x3a, + ), + ), + 45 => Some( + // 12V MCIO hot plug controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x54, + ), + ), + 46 => Some( + // DIMM GHIJKL hot plug controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x56, + ), + ), + 47 => Some( + // DIMM ABCDEF hot plug controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x55, + ), + ), + 48 => Some( + // South power controller (Core 0, SOC) + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x75, + ), + ), + 49 => Some( + // North power controller (Core 1, VDDIO) + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x76, + ), + ), + 50 => Some( + // SP5 power controller (V1P1, V1P8, V3P3) + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5c, + ), + ), + 51 => Some( + // NIC hot swap + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x39, + ), + ), + 52 => Some( + // T6 power controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x25, + ), + ), + 53 => Some( + // Northwest temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x48, + ), + ), + 54 => Some( + // North temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x49, + ), + ), + 55 => Some( + // Northeast temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x4a, + ), + ), + 56 => Some( + // Fan controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x20, + ), + ), + 57 => Some( + // Intermediate bus converter + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x67, + ), + ), + 58 => Some( + // Cosmo VPD + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x50, + ), + ), + 59 => Some( + // Fan hot swap controller (east) + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x11, + ), + ), + 60 => Some( + // Fan hot swap controller (central) + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x12, + ), + ), + 61 => Some( + // Fan hot swap controller (west) + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x13, + ), + ), + 62 => Some( + // Sled hot swap controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x14, + ), + ), + + _ => None, + } + } + #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn lookup_controller(index: usize) -> Option { diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimlet_rev-f-dev.toml.devices-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimlet_rev-f-dev.toml.devices-Sensors.snap index 67a5f45956..eade77a646 100644 --- a/build/xtask/tests/snapshots/i2c_codegen__app_gimlet_rev-f-dev.toml.devices-Sensors.snap +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimlet_rev-f-dev.toml.devices-Sensors.snap @@ -9,6 +9,785 @@ pub mod devices { #[allow(unused_imports)] use userlib::TaskId; + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index(task: TaskId, index: usize) -> Option { + match index { + 0 => Some( + // Southwest temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + None, + 0x48, + ), + ), + 1 => Some( + // South temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + None, + 0x49, + ), + ), + 2 => Some( + // Southeast temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + None, + 0x4a, + ), + ), + 3 => Some( + // U.2 ABCD mux + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + None, + 0x70, + ), + ), + 4 => Some( + // U.2 EFGH mux + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + None, + 0x71, + ), + ), + 5 => Some( + // U.2 IJ/FRUID mux + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + None, + 0x72, + ), + ), + 6 => Some( + // U.2 Sharkfin A VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x50, + ), + ), + 7 => Some( + // U.2 Sharkfin A hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x38, + ), + ), + 8 => Some( + // U.2 A NVMe Basic Management Command + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x6a, + ), + ), + 9 => Some( + // U.2 Sharkfin B VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x50, + ), + ), + 10 => Some( + // U.2 Sharkfin B hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x38, + ), + ), + 11 => Some( + // U.2 B NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x6a, + ), + ), + 12 => Some( + // U.2 Sharkfin C VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x50, + ), + ), + 13 => Some( + // U.2 Sharkfin C hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x38, + ), + ), + 14 => Some( + // U.2 C NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x6a, + ), + ), + 15 => Some( + // U.2 Sharkfin D VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x50, + ), + ), + 16 => Some( + // U.2 Sharkfin D hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x38, + ), + ), + 17 => Some( + // U.2 D NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x6a, + ), + ), + 18 => Some( + // U.2 Sharkfin E VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S1)), + 0x50, + ), + ), + 19 => Some( + // U.2 Sharkfin E hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S1)), + 0x38, + ), + ), + 20 => Some( + // U.2 E NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S1)), + 0x6a, + ), + ), + 21 => Some( + // U.2 Sharkfin F VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S2)), + 0x50, + ), + ), + 22 => Some( + // U.2 Sharkfin F hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S2)), + 0x38, + ), + ), + 23 => Some( + // U.2 F NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S2)), + 0x6a, + ), + ), + 24 => Some( + // U.2 Sharkfin G VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S3)), + 0x50, + ), + ), + 25 => Some( + // U.2 Sharkfin G hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S3)), + 0x38, + ), + ), + 26 => Some( + // U.2 G NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S3)), + 0x6a, + ), + ), + 27 => Some( + // U.2 Sharkfin H VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S4)), + 0x50, + ), + ), + 28 => Some( + // U.2 Sharkfin H hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S4)), + 0x38, + ), + ), + 29 => Some( + // U.2 H NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M2, drv_i2c_api::Segment::S4)), + 0x6a, + ), + ), + 30 => Some( + // U.2 Sharkfin I VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S1)), + 0x50, + ), + ), + 31 => Some( + // U.2 Sharkfin I hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S1)), + 0x38, + ), + ), + 32 => Some( + // U.2 I NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S1)), + 0x6a, + ), + ), + 33 => Some( + // U.2 Sharkfin J VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S2)), + 0x50, + ), + ), + 34 => Some( + // U.2 Sharkfin J hot swap controller + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S2)), + 0x38, + ), + ), + 35 => Some( + // U.2 J NVMe Basic Management Control + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S2)), + 0x6a, + ), + ), + 36 => Some( + // Gimlet VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(1), + Some((drv_i2c_api::Mux::M3, drv_i2c_api::Segment::S4)), + 0x50, + ), + ), + 37 => Some( + // M.2 mux + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x73, + ), + ), + 38 => Some( + // Fan VPD + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S3)), + 0x50, + ), + ), + 39 => Some( + // T6 temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S4)), + 0x4c, + ), + ), + 40 => Some( + // A2 3.3V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x24, + ), + ), + 41 => Some( + // A0 3.3V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x26, + ), + ), + 42 => Some( + // A2 5V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x27, + ), + ), + 43 => Some( + // A2 1.8V rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x29, + ), + ), + 44 => Some( + // M.2 hot plug controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x3a, + ), + ), + 45 => Some( + // CPU via SB-RMI + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x3c, + ), + ), + 46 => Some( + // CPU temperature sensor + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x4c, + ), + ), + 47 => Some( + // Clock generator + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x58, + ), + ), + 48 => Some( + // CPU power controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5a, + ), + ), + 49 => Some( + // SoC power controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5b, + ), + ), + 50 => Some( + // DIMM/SP3 1.8V A0 power controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5c, + ), + ), + 51 => Some( + // Fan hot swap controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x10, + ), + ), + 52 => Some( + // Sled hot swap controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x14, + ), + ), + 53 => Some( + // Fan controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x20, + ), + ), + 54 => Some( + // T6 power controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x25, + ), + ), + 55 => Some( + // Northeast temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x48, + ), + ), + 56 => Some( + // North temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x49, + ), + ), + 57 => Some( + // Northwest temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x4a, + ), + ), + 58 => Some( + // Intermediate bus converter + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x67, + ), + ), + 59 => Some( + // DIMM A0 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x18, + ), + ), + 60 => Some( + // DIMM A1 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x19, + ), + ), + 61 => Some( + // DIMM B0 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x1a, + ), + ), + 62 => Some( + // DIMM B1 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x1b, + ), + ), + 63 => Some( + // DIMM C0 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x1c, + ), + ), + 64 => Some( + // DIMM C1 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x1d, + ), + ), + 65 => Some( + // DIMM D0 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x1e, + ), + ), + 66 => Some( + // DIMM D1 + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x1f, + ), + ), + 67 => Some( + // DIMM E0 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x18, + ), + ), + 68 => Some( + // DIMM E1 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x19, + ), + ), + 69 => Some( + // DIMM F0 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x1a, + ), + ), + 70 => Some( + // DIMM F1 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x1b, + ), + ), + 71 => Some( + // DIMM G0 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x1c, + ), + ), + 72 => Some( + // DIMM G1 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x1d, + ), + ), + 73 => Some( + // DIMM H0 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x1e, + ), + ), + 74 => Some( + // DIMM H1 + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x1f, + ), + ), + 75 => Some( + // M.2 A NVMe Basic Management Command + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x6a, + ), + ), + 76 => Some( + // M.2 B NVMe Basic Management Command + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x6a, + ), + ), + + _ => None, + } + } + #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn lookup_controller(index: usize) -> Option { diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_observer_rev-a-dev.toml.devices-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_observer_rev-a-dev.toml.devices-Sensors.snap index 8962937d22..1823b20c01 100644 --- a/build/xtask/tests/snapshots/i2c_codegen__app_observer_rev-a-dev.toml.devices-Sensors.snap +++ b/build/xtask/tests/snapshots/i2c_codegen__app_observer_rev-a-dev.toml.devices-Sensors.snap @@ -9,6 +9,155 @@ pub mod devices { #[allow(unused_imports)] use userlib::TaskId; + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index(task: TaskId, index: usize) -> Option { + match index { + 0 => Some( + // Onboard temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x48, + ), + ), + 1 => Some( + // FRU ID EEPROM + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x50, + ), + ), + 2 => Some( + // PSU 0 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x50, + ), + ), + 3 => Some( + // PSU 0 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x58, + ), + ), + 4 => Some( + // PSU 1 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x51, + ), + ), + 5 => Some( + // PSU 1 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x59, + ), + ), + 6 => Some( + // PSU 2 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x52, + ), + ), + 7 => Some( + // PSU 2 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5a, + ), + ), + 8 => Some( + // PSU 3 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x53, + ), + ), + 9 => Some( + // PSU 3 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5b, + ), + ), + 10 => Some( + // PSU 4 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x54, + ), + ), + 11 => Some( + // PSU 4 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5c, + ), + ), + 12 => Some( + // PSU 5 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x55, + ), + ), + 13 => Some( + // PSU 5 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5d, + ), + ), + + _ => None, + } + } + #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn lookup_controller(index: usize) -> Option { diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_psc_rev-c-dev.toml.devices-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_psc_rev-c-dev.toml.devices-Sensors.snap index afdad2f1b0..401bac96c7 100644 --- a/build/xtask/tests/snapshots/i2c_codegen__app_psc_rev-c-dev.toml.devices-Sensors.snap +++ b/build/xtask/tests/snapshots/i2c_codegen__app_psc_rev-c-dev.toml.devices-Sensors.snap @@ -9,6 +9,155 @@ pub mod devices { #[allow(unused_imports)] use userlib::TaskId; + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index(task: TaskId, index: usize) -> Option { + match index { + 0 => Some( + // Temperature sensor + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x48, + ), + ), + 1 => Some( + // FRU ID EEPROM + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x50, + ), + ), + 2 => Some( + // PSU 0 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x50, + ), + ), + 3 => Some( + // PSU 0 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x58, + ), + ), + 4 => Some( + // PSU 1 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x51, + ), + ), + 5 => Some( + // PSU 1 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x59, + ), + ), + 6 => Some( + // PSU 2 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x52, + ), + ), + 7 => Some( + // PSU 2 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5a, + ), + ), + 8 => Some( + // PSU 3 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x53, + ), + ), + 9 => Some( + // PSU 3 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5b, + ), + ), + 10 => Some( + // PSU 4 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x54, + ), + ), + 11 => Some( + // PSU 4 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5c, + ), + ), + 12 => Some( + // PSU 5 EEPROM + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x55, + ), + ), + 13 => Some( + // PSU 5 MCU + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x5d, + ), + ), + + _ => None, + } + } + #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn lookup_controller(index: usize) -> Option { diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_sidecar_rev-d-dev.toml.devices-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_sidecar_rev-d-dev.toml.devices-Sensors.snap index 00c701c422..07f5f6f8df 100644 --- a/build/xtask/tests/snapshots/i2c_codegen__app_sidecar_rev-d-dev.toml.devices-Sensors.snap +++ b/build/xtask/tests/snapshots/i2c_codegen__app_sidecar_rev-d-dev.toml.devices-Sensors.snap @@ -9,6 +9,403 @@ pub mod devices { #[allow(unused_imports)] use userlib::TaskId; + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index(task: TaskId, index: usize) -> Option { + match index { + 0 => Some( + // Fan 1 hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + None, + 0x10, + ), + ), + 1 => Some( + // Fan 0/1 controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + None, + 0x23, + ), + ), + 2 => Some( + // North-northeast temperature sensor + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + None, + 0x49, + ), + ), + 3 => Some( + // TF2 VDD rail + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + None, + 0x63, + ), + ), + 4 => Some( + // Northeast fan mux + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + None, + 0x70, + ), + ), + 5 => Some( + // Fan 0 hot swap controller + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(1), + None, + 0x13, + ), + ), + 6 => Some( + // V3P3_SYS rail + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(1), + None, + 0x1a, + ), + ), + 7 => Some( + // Northeast temperature sensor + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(1), + None, + 0x48, + ), + ), + 8 => Some( + // 54V hot swap controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x16, + ), + ), + 9 => Some( + // V5P0_SYS rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x19, + ), + ), + 10 => Some( + // North-northwest temperature sensor + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x48, + ), + ), + 11 => Some( + // TF2 temperature sensor + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x4c, + ), + ), + 12 => Some( + // TF2 VDDA rail + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x60, + ), + ), + 13 => Some( + // Intermediate bus converter + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(0), + None, + 0x67, + ), + ), + 14 => Some( + // Fan 2 hot swap controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + None, + 0x13, + ), + ), + 15 => Some( + // Fan 3 hot swap controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + None, + 0x10, + ), + ), + 16 => Some( + // Northwest temperature sensor + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + None, + 0x49, + ), + ), + 17 => Some( + // Fan 2/3 controller + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + None, + 0x20, + ), + ), + 18 => Some( + // Northwest fan mux + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + None, + 0x70, + ), + ), + 19 => Some( + // VDD[A]18 rail + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(1), + None, + 0x62, + ), + ), + 20 => Some( + // Front I/O hotswap controller + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(1), + None, + 0x54, + ), + ), + 21 => Some( + // Clock generator + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(1), + None, + 0x58, + ), + ), + 22 => Some( + // South temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(1), + None, + 0x4a, + ), + ), + 23 => Some( + // Southeast temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(1), + None, + 0x48, + ), + ), + 24 => Some( + // Southwest temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(1), + None, + 0x49, + ), + ), + 25 => Some( + // V1P0_MGMT rail + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(2), + None, + 0x1b, + ), + ), + 26 => Some( + // V1P8_SYS rail + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(2), + None, + 0x1c, + ), + ), + 27 => Some( + // VSC7448 temperature sensor + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(2), + None, + 0x4c, + ), + ), + 28 => Some( + // Mainboard FRUID + I2cDevice::new( + task, + Controller::I2C4, + PortIndex(0), + None, + 0x50, + ), + ), + 29 => Some( + // Fan 0 FRUID + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x50, + ), + ), + 30 => Some( + // Fan 1 FRUID + I2cDevice::new( + task, + Controller::I2C1, + PortIndex(0), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x50, + ), + ), + 31 => Some( + // Fan 2 FRUID + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S2)), + 0x50, + ), + ), + 32 => Some( + // Fan 3 FRUID + I2cDevice::new( + task, + Controller::I2C3, + PortIndex(1), + Some((drv_i2c_api::Mux::M1, drv_i2c_api::Segment::S1)), + 0x50, + ), + ), + 33 => Some( + // Front IO board FRUID + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x50, + ), + ), + 34 => Some( + // Front IO GPIO expander + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x73, + ), + ), + 35 => Some( + // Front IO LED driver (left) + I2cDevice::new(task, Controller::I2C2, PortIndex(0), None, 0xa), + ), + 36 => Some( + // Front IO LED driver (right) + I2cDevice::new(task, Controller::I2C2, PortIndex(0), None, 0xb), + ), + 37 => Some( + // Front IO V3P3_SYS_A2 rail + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x1b, + ), + ), + 38 => Some( + // Front IO V3P3_QSFP0_A0 rail + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x19, + ), + ), + 39 => Some( + // Front IO V3P3_QSFP1_A0 rail + I2cDevice::new( + task, + Controller::I2C2, + PortIndex(0), + None, + 0x1a, + ), + ), + + _ => None, + } + } + #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn lookup_controller(index: usize) -> Option { From ecbafac423182f43c57a12b593fb7b7d841dbbe8 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 28 Aug 2026 17:13:13 -0700 Subject: [PATCH 13/28] fix gimletlet --- build/i2c/src/lib.rs | 7 +++- build/xtask/tests/i2c-codegen.rs | 7 ++-- ...p-meanwell.toml.controllers-Initiator.snap | 35 ++++++++++++++++++ ...let_app-meanwell.toml.devices-Sensors.snap | 35 ++++++++++++++++++ ...etlet_app-meanwell.toml.muxes-Sensors.snap | 13 +++++++ ...tlet_app-meanwell.toml.pins-Initiator.snap | 36 +++++++++++++++++++ ...etlet_app-meanwell.toml.ports-Sensors.snap | 22 ++++++++++++ ...pp-meanwell.toml.sensors-Sensors-desc.snap | 10 ++++++ ...let_app-meanwell.toml.sensors-Sensors.snap | 12 +++++++ ...p-meanwell.toml.validation-Validation.snap | 30 ++++++++++++++++ 10 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.controllers-Initiator.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.devices-Sensors.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.muxes-Sensors.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.pins-Initiator.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.ports-Sensors.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors-desc.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors.snap create mode 100644 build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.validation-Validation.snap diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 79fccf4d75..c2e4c9a5be 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -1203,13 +1203,18 @@ impl ConfigGenerator { "## )?; + let task_arg = if self.devices.is_empty() { + "_task" + } else { + "task" + }; write!( output, r##" #[allow(dead_code)] #[allow(clippy::match_single_binding)] pub fn device_by_index( - task: TaskId, + {task_arg}: TaskId, index: usize, ) -> Option {{ match index {{"##, diff --git a/build/xtask/tests/i2c-codegen.rs b/build/xtask/tests/i2c-codegen.rs index 3e04a2c9a9..68375578e8 100644 --- a/build/xtask/tests/i2c-codegen.rs +++ b/build/xtask/tests/i2c-codegen.rs @@ -39,6 +39,9 @@ fn snapshot() { Path::new("app/sidecar/rev-d-dev.toml"), Path::new("app/observer/rev-a-dev.toml"), Path::new("app/psc/rev-c-dev.toml"), + // Generating a Gimletlet image is interesting as Gimletlet is + // representative of boards which have no PMBus devices. + Path::new("app/gimletlet/app-meanwell.toml"), ]; // TODO: Some analysis and generation is gated in either `new_with_config` @@ -90,7 +93,7 @@ fn snapshot() { for (case, disp, f) in funcs { let name = manifest.to_string_lossy().replace("/", "_"); let name = format!("{name}.{case}-{disp:?}"); - snapshot_file::<()>(*manifest, &tempdir, disp, &name, *f); + snapshot_file::<()>(manifest, &tempdir, disp, &name, *f); } // Handle `generate_sensors` separately because it returns data in @@ -101,7 +104,7 @@ fn snapshot() { let name = format!("{name}.{case}-{disp:?}"); let desc = snapshot_file::( - *manifest, + manifest, &tempdir, &disp, &name, diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.controllers-Initiator.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.controllers-Initiator.snap new file mode 100644 index 0000000000..ff1110aacb --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.controllers-Initiator.snap @@ -0,0 +1,35 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +#[allow(dead_code)] +pub const NCONTROLLERS: usize = 3; + +use drv_stm32xx_i2c::I2cController; + +pub fn controllers() -> [I2cController<'static>; NCONTROLLERS] { + use drv_i2c_api::Controller; + use drv_stm32xx_sys_api::Peripheral; + + [ + I2cController { + controller: Controller::I2C2, + peripheral: Peripheral::I2c2, + notification: crate::notifications::I2C2_IRQ_MASK, + registers: unsafe { &*device::I2C2::ptr() }, + }, + I2cController { + controller: Controller::I2C3, + peripheral: Peripheral::I2c3, + notification: crate::notifications::I2C3_IRQ_MASK, + registers: unsafe { &*device::I2C3::ptr() }, + }, + I2cController { + controller: Controller::I2C4, + peripheral: Peripheral::I2c4, + notification: crate::notifications::I2C4_IRQ_MASK, + registers: unsafe { &*device::I2C4::ptr() }, + }, + ] +} diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.devices-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.devices-Sensors.snap new file mode 100644 index 0000000000..97be263a54 --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.devices-Sensors.snap @@ -0,0 +1,35 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +pub mod devices { + #[allow(unused_imports)] + use drv_i2c_api::{Controller, I2cDevice, PortIndex}; + #[allow(unused_imports)] + use userlib::TaskId; + + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn device_by_index(_task: TaskId, index: usize) -> Option { + match index { + _ => None, + } + } + + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn lookup_controller(index: usize) -> Option { + match index { + _ => None, + } + } + + #[allow(dead_code)] + #[allow(clippy::match_single_binding)] + pub fn lookup_port(index: usize) -> Option { + match index { + _ => None, + } + } +} diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.muxes-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.muxes-Sensors.snap new file mode 100644 index 0000000000..a730afa731 --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.muxes-Sensors.snap @@ -0,0 +1,13 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +#[allow(dead_code)] +pub const NMUXEDBUSES: usize = 0; + +use drv_stm32xx_i2c::I2cMux; + +pub fn muxes() -> [I2cMux<'static>; 0] { + [] +} diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.pins-Initiator.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.pins-Initiator.snap new file mode 100644 index 0000000000..57fa4bd81d --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.pins-Initiator.snap @@ -0,0 +1,36 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +#[allow(unused_imports)] +use drv_stm32xx_i2c::{I2cGpio, I2cPins}; + +pub fn pins() -> [I2cPins; 3] { + use drv_i2c_api::{Controller, PortIndex}; + use drv_stm32xx_sys_api::{self as gpio_api, Alternate}; + + [ + I2cPins { + controller: Controller::I2C2, + port: PortIndex(0), + scl: gpio_api::Port::F.pin(1), + sda: gpio_api::Port::F.pin(0), + function: Alternate::AF4, + }, + I2cPins { + controller: Controller::I2C3, + port: PortIndex(0), + scl: gpio_api::Port::A.pin(8), + sda: gpio_api::Port::C.pin(9), + function: Alternate::AF4, + }, + I2cPins { + controller: Controller::I2C4, + port: PortIndex(0), + scl: gpio_api::Port::F.pin(14), + sda: gpio_api::Port::F.pin(15), + function: Alternate::AF4, + }, + ] +} diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.ports-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.ports-Sensors.snap new file mode 100644 index 0000000000..abafa1d214 --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.ports-Sensors.snap @@ -0,0 +1,22 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +pub mod ports { + + #[allow(dead_code)] + pub const fn i2c2_f() -> drv_i2c_api::PortIndex { + drv_i2c_api::PortIndex(0) + } + + #[allow(dead_code)] + pub const fn i2c3_c() -> drv_i2c_api::PortIndex { + drv_i2c_api::PortIndex(0) + } + + #[allow(dead_code)] + pub const fn i2c4_f() -> drv_i2c_api::PortIndex { + drv_i2c_api::PortIndex(0) + } +} diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors-desc.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors-desc.snap new file mode 100644 index 0000000000..02efc474f1 --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors-desc.snap @@ -0,0 +1,10 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: desc.to_string() +--- +by_device: +by_name: +by_refdes: +by_id: +device_sensors: +total_sensors: 0 diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors.snap new file mode 100644 index 0000000000..19da722421 --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.sensors-Sensors.snap @@ -0,0 +1,12 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +pub mod sensors { + #[allow(unused_imports)] + use super::super::SensorId; + + #[allow(dead_code)] + pub const NUM_SENSORS: usize = 0; +} diff --git a/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.validation-Validation.snap b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.validation-Validation.snap new file mode 100644 index 0000000000..233a766e7d --- /dev/null +++ b/build/xtask/tests/snapshots/i2c_codegen__app_gimletlet_app-meanwell.toml.validation-Validation.snap @@ -0,0 +1,30 @@ +--- +source: build/xtask/tests/i2c-codegen.rs +expression: contents +--- + +pub mod validation { + #[allow(unused_imports)] + use drv_i2c_api::{Controller, I2cDevice, PortIndex}; + #[allow(unused_imports)] + use drv_i2c_devices::Validate; + use userlib::TaskId; + + #[allow(dead_code)] + pub enum I2cValidation { + RawReadOk, + Good, + Bad, + } + + #[allow(unused_variables)] + #[allow(clippy::match_single_binding)] + pub fn validate( + task: TaskId, + index: usize, + ) -> Result { + match index { + _ => Err(drv_i2c_api::ResponseCode::BadArg), + } + } +} From 86e8ea05343bc44d9dd0ae1bea228cd47b80e690 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 31 Aug 2026 09:40:37 -0700 Subject: [PATCH 14/28] tidy up a bit --- build/i2c/src/lib.rs | 23 +++++++++++++++++++---- task/validate-api/src/lib.rs | 3 +-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index c2e4c9a5be..3377d94001 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -739,10 +739,15 @@ impl ConfigGenerator { (_, _) => {} } if d.vpd == EepromVpd::FanAssembly { + let refdes = d.refdes.as_ref().map(Refdes::to_component_id); + let refdes = refdes.as_deref().unwrap_or("no refdes"); assert!( d.device == "at24csw080", - "device {} declares fan assembly VPD but is not an AT24CSW080", + "device {} at address {:#x} ({refdes}) is configured \ + with the 'fan-assembly' EEPROM VPD mode, but is not \ + an AT24CSW080", d.device, + d.address, ); } } @@ -1202,8 +1207,20 @@ impl ConfigGenerator { use userlib::TaskId; "## )?; - + // + // Generate a function that looks up an `I2cDevice` based on its index + // in the order returned by `device_descriptions()`. + // + // This is used by the generated code in `task-validate-api` and + // `control-plane-agent`, such as when we construct an `I2cDevice handle + // in order to read VPD or PMBus registers from a device. These indices + // are also referenced by the lookup table of PMBus rail names to + // device indices in `control-plane-agent`. + // let task_arg = if self.devices.is_empty() { + // If we are generating a `device_by_index` function that has no + // devices in it, this argument will be unused, so suppress clippy + // warnings about it. "_task" } else { "task" @@ -1220,8 +1237,6 @@ impl ConfigGenerator { match index {{"##, )?; - // These indices are shared with `device_descriptions()` and the - // generated validation dispatch. for (index, device) in self.devices.iter().enumerate() { let out = self.generate_device(device, 20); writeln!(output, "{index} => Some({out}),")?; diff --git a/task/validate-api/src/lib.rs b/task/validate-api/src/lib.rs index b10644cb67..0723ef8a5c 100644 --- a/task/validate-api/src/lib.rs +++ b/task/validate-api/src/lib.rs @@ -62,6 +62,7 @@ pub enum Sensor { Speed, } +/// How to read VPD from a device. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum VpdKind { @@ -71,8 +72,6 @@ pub enum VpdKind { Tmp117, } -const _: () = assert!(core::mem::size_of::>() == 1); - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct SensorDescription { pub name: Option<&'static str>, From 442ef5ff47d68cdda2eff0167b13c340fbb974c6 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 1 Sep 2026 16:37:33 -0700 Subject: [PATCH 15/28] also use that here --- task/control-plane-agent/src/inventory.rs | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 7c98581e06..37e61b6378 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -301,16 +301,22 @@ impl Inventory { } fn read_tmp117_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { - let read = |register| { - dev.read_reg(register) - .map_err(i2c_error_to_vpd_error) - .map_err(SpError::Vpd) - }; + use drv_i2c_devices::tmp117::{Error, Register, Tmp117}; + + fn to_sp_error(e: Error) -> SpError { + match e { + Error::BadRegisterRead { code, .. } => { + SpError::Vpd(i2c_error_to_vpd_error(code)) + } + } + } + + let dev = Tmp117::new(dev); let vpd = Tmp117Identity { - id: read(0x0f_u8)?, - eeprom1: read(0x05_u8)?, - eeprom2: read(0x06_u8)?, - eeprom3: read(0x08_u8)?, + id: dev.read_reg(Register::DeviceID).map_err(to_sp_error)?, + eeprom1: dev.read_reg(Register::EEPROM1).map_err(to_sp_error)?, + eeprom2: dev.read_reg(Register::EEPROM2).map_err(to_sp_error)?, + eeprom3: dev.read_reg(Register::EEPROM3).map_err(to_sp_error)?, }; hubpack::serialize(buf, &VpdRef::Tmp117(&vpd)) .map_err(|_| SpError::Vpd(VpdError::BadBuffer)) From dd37bff9791cfbbd929e72613baa9e3db481eff9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Sep 2026 09:47:24 -0700 Subject: [PATCH 16/28] make EEPROM VPD config more explicit --- app/cosmo/base.toml | 2 +- app/gimlet/base.toml | 2 +- build/i2c/src/lib.rs | 27 +++++++++++++---------- task/control-plane-agent/src/inventory.rs | 2 +- task/validate-api/build.rs | 7 +++--- task/validate-api/src/lib.rs | 2 +- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 83314a3d00..67ed7dffcd 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -968,7 +968,7 @@ mux = 1 segment = 7 address = 0b1010_000 device = "at24csw080" -vpd = "fan-assembly" +eeprom_vpd = "fan-assembly" description = "Fan VPD" refdes = ["J34", "U1"] name = "fan_vpd" diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 8dc092ac78..d866e8a882 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -886,7 +886,7 @@ mux = 1 segment = 3 address = 0b1010_000 device = "at24csw080" -vpd = "fan-assembly" +eeprom_vpd = "fan-assembly" description = "Fan VPD" refdes = ["J180", "U1"] name = "fan_vpd" diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 3377d94001..88dfb2f758 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -109,9 +109,11 @@ struct I2cDevice { /// description of device description: String, - /// Overrides the default VPD representation for this device. - #[serde(default)] - vpd: EepromVpd, + /// if this is an EEPROM, configures the format for VPD read from this + /// EEPROM. + /// + /// providing a value for this is valid only if `device = "at24csw080"`. + eeprom_vpd: Option, /// reference designator, if any refdes: Option, @@ -676,6 +678,8 @@ fn calculate_validate_drivers() -> Result> { Ok(drivers) } +pub const VPD_EEPROM_DEVICES: &[&str] = &["at24csw080"]; + impl ConfigGenerator { pub fn new_with_config(settings: CodegenSettings, i2c: I2cConfig) -> Self { let mut controllers = vec![]; @@ -738,14 +742,13 @@ impl ConfigGenerator { } (_, _) => {} } - if d.vpd == EepromVpd::FanAssembly { - let refdes = d.refdes.as_ref().map(Refdes::to_component_id); - let refdes = refdes.as_deref().unwrap_or("no refdes"); + if d.eeprom_vpd.is_some() { assert!( - d.device == "at24csw080", - "device {} at address {:#x} ({refdes}) is configured \ - with the 'fan-assembly' EEPROM VPD mode, but is not \ - an AT24CSW080", + VPD_EEPROM_DEVICES.contains(&d.device.as_str()), + "device {} at address {:#x} is configured with an \ + EEPROM VPD format, but it is not a supported EEPROM \ + device (currently, we know about the following \ + EEPROMs: {VPD_EEPROM_DEVICES:?})", d.device, d.address, ); @@ -2082,7 +2085,7 @@ pub struct I2cDeviceDescription { pub device_id: Option, pub name: Option, pub validate_with_raw_read: bool, - pub vpd: EepromVpd, + pub eeprom_vpd: Option, /// If this is a PMBus device, this field contains additional data about the /// PMBus device to be used for generating PMBus-y code. pub pmbus: Option, @@ -2176,7 +2179,7 @@ pub fn device_descriptions() -> impl Iterator { device_id, name: device.name, validate_with_raw_read: device.validate_with_raw_read, - vpd: device.vpd, + eeprom_vpd: device.eeprom_vpd, pmbus, } }, diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 37e61b6378..53665ba167 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -254,7 +254,7 @@ impl Inventory { .read_into(read_one(&reader, PmbusVpdCmd::IcDeviceRev))?; VpdRef::Pmbus(&*out) } - VpdKind::Barcode => { + VpdKind::SingleBarcode => { let out = &mut self.vpd_scratch.barcode; read_one_barcode(out, &dev, &[(*b"BARC", 0)])?; VpdRef::Barcode(&*out) diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index 9314a4705d..0020eeddcb 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -83,11 +83,12 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) }) { Some("Pmbus") - } else if dev.device == "at24csw080" { - Some(match dev.vpd { + } else if build_i2c::VPD_EEPROM_DEVICES.contains(&dev.device.as_str()) { + let vpd_mode = match dev.eeprom_vpd.unwrap_or_default() { build_i2c::EepromVpd::SingleBarcode => "Barcode", build_i2c::EepromVpd::FanAssembly => "FanAssembly", - }) + }; + Some(vpd_mode) } else if dev.device == "tmp117" { Some("Tmp117") } else { diff --git a/task/validate-api/src/lib.rs b/task/validate-api/src/lib.rs index 0723ef8a5c..171ae6e8b9 100644 --- a/task/validate-api/src/lib.rs +++ b/task/validate-api/src/lib.rs @@ -67,7 +67,7 @@ pub enum Sensor { #[repr(u8)] pub enum VpdKind { Pmbus = 1, - Barcode, + SingleBarcode, FanAssembly, Tmp117, } From 1631cf86c444d9f6599a501bf20bf590dd9bd51e Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Sep 2026 09:47:46 -0700 Subject: [PATCH 17/28] fix bonus whitespace --- task/validate-api/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index 0020eeddcb..df6ad35275 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -69,7 +69,7 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { else { println!( "cargo::error=unknown pmbus device: {device_name}, add an \ - entry to PMBUS_GENERATOR in {} for PMBus status register \ + entry to PMBUS_GENERATOR in {} for PMBus status register \ and VPD support.", file!(), ); From 49935cb8f45b2a1029cb848532f027c0ffc198eb Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Sep 2026 09:52:13 -0700 Subject: [PATCH 18/28] oops the toml keys are hyphenated --- app/cosmo/base.toml | 2 +- app/gimlet/base.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 67ed7dffcd..6f99c9db67 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -968,7 +968,7 @@ mux = 1 segment = 7 address = 0b1010_000 device = "at24csw080" -eeprom_vpd = "fan-assembly" +eeprom-vpd = "fan-assembly" description = "Fan VPD" refdes = ["J34", "U1"] name = "fan_vpd" diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index d866e8a882..957089980f 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -886,7 +886,7 @@ mux = 1 segment = 3 address = 0b1010_000 device = "at24csw080" -eeprom_vpd = "fan-assembly" +eeprom-vpd = "fan-assembly" description = "Fan VPD" refdes = ["J180", "U1"] name = "fan_vpd" From b349e0b6335aff451e3ec46c876e443e60dd80e7 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Sep 2026 09:55:27 -0700 Subject: [PATCH 19/28] blarg --- task/validate-api/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index df6ad35275..1614b07a8a 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -85,7 +85,7 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { Some("Pmbus") } else if build_i2c::VPD_EEPROM_DEVICES.contains(&dev.device.as_str()) { let vpd_mode = match dev.eeprom_vpd.unwrap_or_default() { - build_i2c::EepromVpd::SingleBarcode => "Barcode", + build_i2c::EepromVpd::SingleBarcode => "SingleBarcode", build_i2c::EepromVpd::FanAssembly => "FanAssembly", }; Some(vpd_mode) From 4d25c9b1e2603f3e65efd38d955e82de736a78e5 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Sep 2026 10:08:14 -0700 Subject: [PATCH 20/28] update MGS, fix some stuff --- Cargo.lock | 4 +- task/control-plane-agent/src/inventory.rs | 95 ++++++++++++----------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ed6d86715..de810b0f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#101c3eb48808bb0d7706a4c31a74436d7215afe4" +source = "git+https://github.com/oxidecomputer/management-gateway-service#261c679967b6ace36b18d6bd5bcd37d779d0bd98" dependencies = [ "hubpack", "serde", @@ -3330,7 +3330,7 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#101c3eb48808bb0d7706a4c31a74436d7215afe4" +source = "git+https://github.com/oxidecomputer/management-gateway-service#261c679967b6ace36b18d6bd5bcd37d779d0bd98" dependencies = [ "bitflags 2.9.4", "gateway-ereport-messages", diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 53665ba167..0fd0e3d136 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -12,10 +12,13 @@ use gateway_messages::measurement::{ use gateway_messages::sp_impl::{BoundsChecked, DeviceDescription}; #[cfg(feature = "compute-sled")] use gateway_messages::vpd::FanAssemblyVpd; +use gateway_messages::vpd::{ + Barcode, BarcodeReadError, PmbusVpd, SmbusBlock, SmbusReadIntoError, + Tmp117Identity, VpdRef, +}; use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, VpdError, - vpd::{Barcode, BarcodeReadError, PmbusVpd, Tmp117Identity, VpdRef}, }; use static_cell::ClaimOnceCell; use task_sensor_api::Sensor as SensorTask; @@ -211,6 +214,28 @@ impl Inventory { let vpd = match vpd_kind { VpdKind::Pmbus => { + // Don't line-wrap as much... + use PmbusVpdCmd as Cmd; + + fn read_one( + block: &mut SmbusBlock, + reader: &PmbusVpdReader<'_>, + cmd: Cmd, + ) -> Result, SpError> { + block.read_into(|buf| reader.try_read(cmd, buf)).map_err( + |e| match e { + SmbusReadIntoError::ReadError(code) => { + SpError::Vpd(i2c_error_to_vpd_error(code)) + } + SmbusReadIntoError::ReadTooLong => { + // `PmbusVpdReader::try_read` should never + // return a length > 32 bytes. + unreachable!() + } + }, + ) + } + // Either of these not being set would indicate that code // generation did an oopsie, so we *could* consider this // unreachable here, but I think it's nicer to not panic. @@ -221,37 +246,18 @@ impl Inventory { return Err(SpError::RequestUnsupportedForComponent); } - fn read_one( - reader: &PmbusVpdReader<'_>, - cmd: PmbusVpdCmd, - ) -> impl FnOnce(&mut [u8; 32]) -> Result, SpError> - { - move |buf| { - reader.try_read(cmd, buf).map_err(|code| { - SpError::Vpd(i2c_error_to_vpd_error(code)) - }) - } - } - let reader = PmbusVpdReader::new(&dev, caps); let out = &mut self.vpd_scratch.pmbus; - out.mfr_id - .read_into(read_one(&reader, PmbusVpdCmd::MfrId))?; - out.mfr_model - .read_into(read_one(&reader, PmbusVpdCmd::MfrModel))?; - out.mfr_revision - .read_into(read_one(&reader, PmbusVpdCmd::MfrRevision))?; - out.mfr_location - .read_into(read_one(&reader, PmbusVpdCmd::MfrLocation))?; - out.mfr_date - .read_into(read_one(&reader, PmbusVpdCmd::MfrDate))?; - out.mfr_serial - .read_into(read_one(&reader, PmbusVpdCmd::MfrSerial))?; - out.ic_device_id - .read_into(read_one(&reader, PmbusVpdCmd::IcDeviceId))?; - out.ic_device_rev - .read_into(read_one(&reader, PmbusVpdCmd::IcDeviceRev))?; + read_one(&mut out.mfr_id, &reader, Cmd::MfrId)?; + read_one(&mut out.mfr_model, &reader, Cmd::MfrModel)?; + read_one(&mut out.mfr_revision, &reader, Cmd::MfrRevision)?; + read_one(&mut out.mfr_location, &reader, Cmd::MfrLocation)?; + read_one(&mut out.mfr_date, &reader, Cmd::MfrDate)?; + read_one(&mut out.mfr_serial, &reader, Cmd::MfrSerial)?; + read_one(&mut out.ic_device_id, &reader, Cmd::IcDeviceId)?; + read_one(&mut out.ic_device_rev, &reader, Cmd::IcDeviceRev)?; + VpdRef::Pmbus(&*out) } VpdKind::SingleBarcode => { @@ -301,22 +307,23 @@ impl Inventory { } fn read_tmp117_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { - use drv_i2c_devices::tmp117::{Error, Register, Tmp117}; - - fn to_sp_error(e: Error) -> SpError { - match e { - Error::BadRegisterRead { code, .. } => { - SpError::Vpd(i2c_error_to_vpd_error(code)) - } - } - } - - let dev = Tmp117::new(dev); let vpd = Tmp117Identity { - id: dev.read_reg(Register::DeviceID).map_err(to_sp_error)?, - eeprom1: dev.read_reg(Register::EEPROM1).map_err(to_sp_error)?, - eeprom2: dev.read_reg(Register::EEPROM2).map_err(to_sp_error)?, - eeprom3: dev.read_reg(Register::EEPROM3).map_err(to_sp_error)?, + id: dev + .read_reg(0x0Fu8) + .map_err(i2c_error_to_vpd_error) + .map_err(SpError::Vpd)?, + eeprom1: dev + .read_reg(0x05u8) + .map_err(i2c_error_to_vpd_error) + .map_err(SpError::Vpd)?, + eeprom2: dev + .read_reg(0x06u8) + .map_err(i2c_error_to_vpd_error) + .map_err(SpError::Vpd)?, + eeprom3: dev + .read_reg(0x07u8) + .map_err(i2c_error_to_vpd_error) + .map_err(SpError::Vpd)?, }; hubpack::serialize(buf, &VpdRef::Tmp117(&vpd)) .map_err(|_| SpError::Vpd(VpdError::BadBuffer)) From 417731c471bcf8825a87fbd2ca33af0261be428f Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 3 Sep 2026 13:03:25 -0700 Subject: [PATCH 21/28] renaming things, also support tmp116 --- app/cosmo/base.toml | 2 +- app/gimlet/base.toml | 2 +- build/i2c/src/lib.rs | 3 ++- task/control-plane-agent/src/inventory.rs | 4 ++-- task/validate-api/build.rs | 10 +++++----- task/validate-api/src/lib.rs | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 6f99c9db67..2af12bec5a 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -968,7 +968,7 @@ mux = 1 segment = 7 address = 0b1010_000 device = "at24csw080" -eeprom-vpd = "fan-assembly" +eeprom-vpd = "sled-fan-tray" description = "Fan VPD" refdes = ["J34", "U1"] name = "fan_vpd" diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 957089980f..9cc8e04c3d 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -886,7 +886,7 @@ mux = 1 segment = 3 address = 0b1010_000 device = "at24csw080" -eeprom-vpd = "fan-assembly" +eeprom-vpd = "sled-fan-tray" description = "Fan VPD" refdes = ["J180", "U1"] name = "fan_vpd" diff --git a/build/i2c/src/lib.rs b/build/i2c/src/lib.rs index 88dfb2f758..eba54aefbb 100644 --- a/build/i2c/src/lib.rs +++ b/build/i2c/src/lib.rs @@ -600,7 +600,7 @@ impl std::fmt::Display for Sensor { pub enum EepromVpd { #[default] SingleBarcode, - FanAssembly, + SledFanTray, } #[derive(PartialEq)] @@ -679,6 +679,7 @@ fn calculate_validate_drivers() -> Result> { } pub const VPD_EEPROM_DEVICES: &[&str] = &["at24csw080"]; +pub const VPD_TMP11X_DEVICES: &[&str] = &["tmp116", "tmp117"]; impl ConfigGenerator { pub fn new_with_config(settings: CodegenSettings, i2c: I2cConfig) -> Self { diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 0fd0e3d136..51c59eb4a1 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -265,8 +265,8 @@ impl Inventory { read_one_barcode(out, &dev, &[(*b"BARC", 0)])?; VpdRef::Barcode(&*out) } - VpdKind::FanAssembly => self.read_fan_tray_vpd(&dev)?, - VpdKind::Tmp117 => return read_tmp117_vpd(&dev, buf), + VpdKind::SledFanTray => self.read_fan_tray_vpd(&dev)?, + VpdKind::Tmp11x => return read_tmp117_vpd(&dev, buf), }; hubpack::serialize(buf, &vpd) diff --git a/task/validate-api/build.rs b/task/validate-api/build.rs index 1614b07a8a..de6a4666cf 100644 --- a/task/validate-api/build.rs +++ b/task/validate-api/build.rs @@ -60,8 +60,8 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { let mut id2idx = std::collections::BTreeMap::new(); for (idx, dev) in devices.into_iter().enumerate() { + let device_name = dev.device.as_str(); let pmbus_capabilities = if dev.pmbus.is_some() { - let device_name = &dev.device; let Some(caps) = PMBUS_GENERATOR.iter().find_map(|&(name, generate)| { (name == device_name).then(generate) @@ -83,14 +83,14 @@ fn write_pub_device_descriptions() -> anyhow::Result<()> { caps.supports_any(&PmbusCapabilities::ANY_VPD_REGS) }) { Some("Pmbus") - } else if build_i2c::VPD_EEPROM_DEVICES.contains(&dev.device.as_str()) { + } else if build_i2c::VPD_EEPROM_DEVICES.contains(&device_name) { let vpd_mode = match dev.eeprom_vpd.unwrap_or_default() { build_i2c::EepromVpd::SingleBarcode => "SingleBarcode", - build_i2c::EepromVpd::FanAssembly => "FanAssembly", + build_i2c::EepromVpd::SledFanTray => "SledFanTray", }; Some(vpd_mode) - } else if dev.device == "tmp117" { - Some("Tmp117") + } else if build_i2c::VPD_TMP11X_DEVICES.contains(&device_name) { + Some("Tmp11x") } else { None }; diff --git a/task/validate-api/src/lib.rs b/task/validate-api/src/lib.rs index 171ae6e8b9..f52c703d0e 100644 --- a/task/validate-api/src/lib.rs +++ b/task/validate-api/src/lib.rs @@ -68,8 +68,8 @@ pub enum Sensor { pub enum VpdKind { Pmbus = 1, SingleBarcode, - FanAssembly, - Tmp117, + SledFanTray, + Tmp11x, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] From f361de96ac8056340dd4e6a6cc1be4fb0b4a0331 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 10:05:58 -0700 Subject: [PATCH 22/28] update mgs --- Cargo.lock | 4 ++-- task/control-plane-agent/src/inventory.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de810b0f77..72f1df4e4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#261c679967b6ace36b18d6bd5bcd37d779d0bd98" +source = "git+https://github.com/oxidecomputer/management-gateway-service#e32d5cf9f86a4d6ae7dd2107b2c592c75fc74479" dependencies = [ "hubpack", "serde", @@ -3330,7 +3330,7 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#261c679967b6ace36b18d6bd5bcd37d779d0bd98" +source = "git+https://github.com/oxidecomputer/management-gateway-service#e32d5cf9f86a4d6ae7dd2107b2c592c75fc74479" dependencies = [ "bitflags 2.9.4", "gateway-ereport-messages", diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 51c59eb4a1..dbb37fc2b7 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -11,10 +11,10 @@ use gateway_messages::measurement::{ }; use gateway_messages::sp_impl::{BoundsChecked, DeviceDescription}; #[cfg(feature = "compute-sled")] -use gateway_messages::vpd::FanAssemblyVpd; +use gateway_messages::vpd::SledFanTrayVpd; use gateway_messages::vpd::{ Barcode, BarcodeReadError, PmbusVpd, SmbusBlock, SmbusReadIntoError, - Tmp117Identity, VpdRef, + Tmp11xVpd, VpdRef, }; use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, @@ -41,7 +41,7 @@ struct VpdScratch { barcode: Barcode, // Only compute sleds have nested fan tray VPD. #[cfg(feature = "compute-sled")] - fan_assembly: FanAssemblyVpd, + fan_assembly: SledFanTrayVpd, } impl Inventory { @@ -57,7 +57,7 @@ impl Inventory { pmbus: PmbusVpd::EMPTY, barcode: Barcode::EMPTY, #[cfg(feature = "compute-sled")] - fan_assembly: FanAssemblyVpd { + fan_assembly: SledFanTrayVpd { identity: Barcode::EMPTY, vpd_board_identity: Barcode::EMPTY, fans: [Barcode::EMPTY; 3], @@ -307,7 +307,7 @@ impl Inventory { } fn read_tmp117_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { - let vpd = Tmp117Identity { + let vpd = Tmp11xVpd { id: dev .read_reg(0x0Fu8) .map_err(i2c_error_to_vpd_error) From d344334c1707a5836d0c5900ccf41bdab6ea59f9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 10:11:38 -0700 Subject: [PATCH 23/28] use fixed tmp117 driver --- task/control-plane-agent/src/inventory.rs | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index dbb37fc2b7..3b5f0221ad 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -266,7 +266,7 @@ impl Inventory { VpdRef::Barcode(&*out) } VpdKind::SledFanTray => self.read_fan_tray_vpd(&dev)?, - VpdKind::Tmp11x => return read_tmp117_vpd(&dev, buf), + VpdKind::Tmp11x => return read_tmp11x_vpd(&dev, buf), }; hubpack::serialize(buf, &vpd) @@ -306,25 +306,25 @@ impl Inventory { } } -fn read_tmp117_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { +fn read_tmp11x_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { + use drv_i2c_devices::tmp117::{Error, Register, Tmp117}; + + fn to_sp_error(err: Error) -> SpError { + match err { + Error::BadRegisterRead { reg, code } => { + SpError::Vpd(i2c_error_to_vpd_error(code)) + } + } + } + + let tmp11x = Tmp117::new(dev); let vpd = Tmp11xVpd { - id: dev - .read_reg(0x0Fu8) - .map_err(i2c_error_to_vpd_error) - .map_err(SpError::Vpd)?, - eeprom1: dev - .read_reg(0x05u8) - .map_err(i2c_error_to_vpd_error) - .map_err(SpError::Vpd)?, - eeprom2: dev - .read_reg(0x06u8) - .map_err(i2c_error_to_vpd_error) - .map_err(SpError::Vpd)?, - eeprom3: dev - .read_reg(0x07u8) - .map_err(i2c_error_to_vpd_error) - .map_err(SpError::Vpd)?, + id: tmp11x.read_reg(Register::DeviceID).map_err(to_sp_error)?, + eeprom1: tmp11x.read_reg(Register::EEPROM1).map_err(to_sp_error)?, + eeprom2: tmp11x.read_reg(Register::EEPROM2).map_err(to_sp_error)?, + eeprom3: tmp11x.read_reg(Register::EEPROM3).map_err(to_sp_error)?, }; + hubpack::serialize(buf, &VpdRef::Tmp117(&vpd)) .map_err(|_| SpError::Vpd(VpdError::BadBuffer)) } From 494735e9418354a89d7f770fe00c14f97978bdec Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 10:29:53 -0700 Subject: [PATCH 24/28] update to oxidecomputer/pmbus@944715988b5be0173bd357e67d749cdd71dc5d2c --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 72f1df4e4e..09fb0ff86c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4921,7 +4921,7 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "pmbus" version = "0.1.7" -source = "git+https://github.com/oxidecomputer/pmbus#706f477731fe743ed53fefe41c01655a7a7eef7c" +source = "git+https://github.com/oxidecomputer/pmbus#944715988b5be0173bd357e67d749cdd71dc5d2c" dependencies = [ "anyhow", "convert_case 0.11.0", From 28918a9439eb97ffd2141c3056732792e4f4cb3a Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 10:38:57 -0700 Subject: [PATCH 25/28] add a quick ringbuf --- task/control-plane-agent/src/inventory.rs | 53 +++++++++++++++++++++-- task/validate-api/src/lib.rs | 2 +- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 3b5f0221ad..8a864f17bf 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -2,9 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use drv_i2c_api::I2cDevice; use drv_i2c_api::PmbusCapabilities; +use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_i2c_devices::at24csw080::{At24Csw080, Error as EepromError}; +use drv_i2c_devices::tmp117; use drv_i2c_devices::{PmbusVpdCmd, PmbusVpdReader}; use gateway_messages::measurement::{ Measurement, MeasurementError, MeasurementKind, @@ -20,6 +21,7 @@ use gateway_messages::{ ComponentDetails, DeviceCapabilities, DevicePresence, SpComponent, SpError, VpdError, }; +use ringbuf::{counted_ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; use task_sensor_api::Sensor as SensorTask; use task_sensor_api::SensorError; @@ -44,6 +46,35 @@ struct VpdScratch { fan_assembly: SledFanTrayVpd, } +#[derive(Copy, Clone, PartialEq, counters::Count)] +enum VpdTrace { + #[count(skip)] + None, + NoVpdForDevice { + index: usize, + }, + ComponentGetVpd { + index: usize, + #[count(children)] + kind: VpdKind, + }, + Tmp11xVpdError { + reg: tmp117::Register, + #[count(children)] + code: ResponseCode, + }, + PmbusVpdError { + cmd: PmbusVpdCmd, + #[count(children)] + code: ResponseCode, + }, + At24Csw080VpdError { + err: drv_oxide_vpd::VpdError, + }, +} + +counted_ringbuf!(VpdTrace, 8, VpdTrace::None); + impl Inventory { pub(crate) fn new() -> Self { let () = devices_with_static_validation::ASSERT_EACH_DEVICE_FITS_IN_ONE_PACKET; @@ -203,8 +234,14 @@ impl Inventory { let device = VALIDATE_DEVICES[index]; let Some(vpd_kind) = device.vpd else { + ringbuf_entry!(VpdTrace::NoVpdForDevice { index }); return Err(SpError::RequestUnsupportedForComponent); }; + + ringbuf_entry!(VpdTrace::ComponentGetVpd { + index, + kind: vpd_kind + }); let dev = crate::i2c_config::devices::device_by_index( crate::I2C.get_task_id(), index, @@ -225,6 +262,10 @@ impl Inventory { block.read_into(|buf| reader.try_read(cmd, buf)).map_err( |e| match e { SmbusReadIntoError::ReadError(code) => { + ringbuf_entry!(VpdTrace::PmbusVpdError { + cmd, + code + }); SpError::Vpd(i2c_error_to_vpd_error(code)) } SmbusReadIntoError::ReadTooLong => { @@ -302,16 +343,17 @@ impl Inventory { _: &I2cDevice, ) -> Result, SpError> { // Only compute sleds should have EEPROMs configured as fan tray VPD. - Err(SpError::RequestUnsupportedForSp) + Err(SpError::RequestUnsupportedForComponent) } } fn read_tmp11x_vpd(dev: &I2cDevice, buf: &mut [u8]) -> Result { - use drv_i2c_devices::tmp117::{Error, Register, Tmp117}; + use tmp117::{Error, Register, Tmp117}; fn to_sp_error(err: Error) -> SpError { match err { Error::BadRegisterRead { reg, code } => { + ringbuf_entry!(VpdTrace::Tmp11xVpdError { reg, code }); SpError::Vpd(i2c_error_to_vpd_error(code)) } } @@ -340,7 +382,10 @@ fn read_one_barcode( }) .map_err(|err| { SpError::Vpd(match err { - BarcodeReadError::ReadError(e) => convert_vpd_error(e), + BarcodeReadError::ReadError(err) => { + ringbuf_entry!(VpdTrace::At24Csw080VpdError { err }); + convert_vpd_error(err) + } BarcodeReadError::NotUtf8 => VpdError::BadRead, BarcodeReadError::ReadTooLong => VpdError::BadBuffer, // shouldn't happen! }) diff --git a/task/validate-api/src/lib.rs b/task/validate-api/src/lib.rs index f52c703d0e..bb705e4d06 100644 --- a/task/validate-api/src/lib.rs +++ b/task/validate-api/src/lib.rs @@ -63,7 +63,7 @@ pub enum Sensor { } /// How to read VPD from a device. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, counters::Count)] #[repr(u8)] pub enum VpdKind { Pmbus = 1, From 74eb7a72df8e1bc6f7b40e390b547b3cf645299e Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 12:02:19 -0700 Subject: [PATCH 26/28] handle register naks nicer --- task/control-plane-agent/src/inventory.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 8a864f17bf..8ad42e9a8a 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -259,14 +259,28 @@ impl Inventory { reader: &PmbusVpdReader<'_>, cmd: Cmd, ) -> Result, SpError> { - block.read_into(|buf| reader.try_read(cmd, buf)).map_err( + block.read_into(|buf| reader.try_read(cmd, buf)).or_else( |e| match e { + // Because we only read registers that the `pmbus` + // crate marks as supported, we *should* never see a + // register-level NACK, but...let's turn that into a + // "register unsupported" instead of erroring out + // the entire thing, anyway. + SmbusReadIntoError::ReadError( + ResponseCode::NoRegister, + ) => { + ringbuf_entry!(VpdTrace::PmbusVpdError { + cmd, + code: ResponseCode::Noregister + }); + Ok(None) + } SmbusReadIntoError::ReadError(code) => { ringbuf_entry!(VpdTrace::PmbusVpdError { cmd, code }); - SpError::Vpd(i2c_error_to_vpd_error(code)) + Err(SpError::Vpd(i2c_error_to_vpd_error(code))) } SmbusReadIntoError::ReadTooLong => { // `PmbusVpdReader::try_read` should never From 0ac44185b649dfae455c2f5dda0c81700baaaba8 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 12:05:37 -0700 Subject: [PATCH 27/28] you cant type lol --- task/control-plane-agent/src/inventory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/control-plane-agent/src/inventory.rs b/task/control-plane-agent/src/inventory.rs index 8ad42e9a8a..b8b25e8330 100644 --- a/task/control-plane-agent/src/inventory.rs +++ b/task/control-plane-agent/src/inventory.rs @@ -271,7 +271,7 @@ impl Inventory { ) => { ringbuf_entry!(VpdTrace::PmbusVpdError { cmd, - code: ResponseCode::Noregister + code: ResponseCode::NoRegister }); Ok(None) } From 3d985c928157c1b5180b5bc1a1e71ab599b032d9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 4 Sep 2026 13:32:36 -0700 Subject: [PATCH 28/28] oxidecomputer/management-gatway-service@438cd185666d5f164548885873f0f5df9a38eed1 picks up the merged version from `main` --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09fb0ff86c..07f2f65178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#e32d5cf9f86a4d6ae7dd2107b2c592c75fc74479" +source = "git+https://github.com/oxidecomputer/management-gateway-service#438cd185666d5f164548885873f0f5df9a38eed1" dependencies = [ "hubpack", "serde", @@ -3330,7 +3330,7 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service#e32d5cf9f86a4d6ae7dd2107b2c592c75fc74479" +source = "git+https://github.com/oxidecomputer/management-gateway-service#438cd185666d5f164548885873f0f5df9a38eed1" dependencies = [ "bitflags 2.9.4", "gateway-ereport-messages",