diff --git a/.github/buildomat/common.sh b/.github/buildomat/common.sh index 322ddcd..5e9b526 100644 --- a/.github/buildomat/common.sh +++ b/.github/buildomat/common.sh @@ -11,9 +11,9 @@ TOFINO_STAGES=20 # These describe which version of the SDE to download and where to find it -SDE_COMMIT=53519b8cf74fe832cc7838ea92683564ce4026f2 -SDE_PKG_SHA256=ed783a1e7c8d59c392e8cc89114fb0d495b5475373b762068a719e0fb215f5a0 -SDE_DEB_SHA256=90a18b65a6c65f4d15d5f75a00e42ae55a27ffaff2066061aa95feefbe85e163 +SDE_COMMIT=4c7f6924d47ec3ac65312e142586d919c05ad5ec +SDE_PKG_SHA256=f2dd0bfabad0e60f92d9972cfcfbbd685e37036ab3135e72c4b82b333418983c +SDE_DEB_SHA256=59a40e1d4f753c2d15f4949403f3c2d2cf4c3f199bfc30ccf688b9e48c6bce21 [ `uname -s` == "SunOS" ] && SERIES=illumos [ `uname -s` == "SunOS" ] || SERIES=linux diff --git a/aal/src/lib.rs b/aal/src/lib.rs index 8106d29..57a1b85 100644 --- a/aal/src/lib.rs +++ b/aal/src/lib.rs @@ -212,6 +212,13 @@ pub trait AsicOps { mode: PortPrbsMode, ) -> AsicResult<()>; + /// Get a port's PRBS error counts + fn port_prbs_get_err( + &self, + port_hdl: PortHdl, + ms: u32, + ) -> AsicResult>; + /// "Add" a port to the ASIC. This carves out a collection of lanes on a /// physical connector and instructs the ASIC to start managing them as a /// single logical port. diff --git a/asic/src/chaos/mod.rs b/asic/src/chaos/mod.rs index d06ee84..345eb6a 100644 --- a/asic/src/chaos/mod.rs +++ b/asic/src/chaos/mod.rs @@ -21,6 +21,7 @@ use common::ports::{PortFec, PortId, PortMedia, PortPrbsMode, PortSpeed}; use crate::Identifiers; pub use crate::faux_fsm::FsmState; +pub use crate::faux_fsm::FsmType; pub use crate::faux_fsm::PortFsmState; pub mod table; @@ -157,6 +158,7 @@ pub struct AsicConfig { pub port_enable_get: Chaos, pub port_enable_set: Chaos, pub port_prbs_set: Chaos, + pub port_prbs_get_err: Chaos, pub port_add: Chaos, pub port_delete: Chaos, pub register_port_update_handler: Chaos, @@ -190,6 +192,7 @@ impl AsicConfig { port_enable_get: Chaos::new(v), port_enable_set: Chaos::new(v), port_prbs_set: Chaos::new(v), + port_prbs_get_err: Chaos::new(v), port_add: Chaos::new(v), port_delete: Chaos::new(v), register_port_update_handler: Chaos::new(v), @@ -250,6 +253,7 @@ impl AsicConfig { port_autoneg_set: Chaos::new(v), port_enable_set: Chaos::new(v), port_prbs_set: Chaos::new(v), + port_prbs_get_err: Chaos::new(v), port_add: Chaos::new(v), port_delete: Chaos::new(v), // TODO this can cause dpd to fail to start @@ -510,6 +514,15 @@ impl AsicOps for Handle { Ok(()) } + fn port_prbs_get_err( + &self, + _port_hdl: PortHdl, + _ms: u32, + ) -> AsicResult> { + unfurl!(self, port_prbs_get_err); + Ok(Vec::new()) + } + fn port_add( &self, connector: Connector, diff --git a/asic/src/lib.rs b/asic/src/lib.rs index 8300c73..e3a9e5e 100644 --- a/asic/src/lib.rs +++ b/asic/src/lib.rs @@ -180,48 +180,53 @@ pub mod tofino_asic; #[cfg(feature = "tofino_asic")] mod plat { - pub use super::tofino_asic::FsmState; - pub use super::tofino_asic::Handle; - pub use super::tofino_asic::PortFsmState; - pub use super::tofino_asic::stats::AsicLinkStats; - pub use super::tofino_asic::table::Table; + pub use crate::tofino_asic::FsmState; + pub use crate::tofino_asic::FsmType; + pub use crate::tofino_asic::Handle; + pub use crate::tofino_asic::PortFsmState; + pub use crate::tofino_asic::stats::AsicLinkStats; + pub use crate::tofino_asic::table::Table; } #[cfg(feature = "tofino_stub")] pub mod tofino_stub; #[cfg(feature = "tofino_stub")] mod plat { - pub use super::tofino_stub::AsicLinkStats; - pub use super::tofino_stub::FsmState; - pub use super::tofino_stub::PortFsmState; - pub use super::tofino_stub::StubHandle as Handle; - pub use super::tofino_stub::table::Table; + pub use crate::tofino_stub::AsicLinkStats; + pub use crate::tofino_stub::FsmState; + pub use crate::tofino_stub::FsmType; + pub use crate::tofino_stub::PortFsmState; + pub use crate::tofino_stub::StubHandle as Handle; + pub use crate::tofino_stub::table::Table; } #[cfg(feature = "softnpu")] pub mod softnpu; #[cfg(feature = "softnpu")] mod plat { - pub use super::softnpu::AsicLinkStats; - pub use super::softnpu::FsmState; - pub use super::softnpu::Handle; - pub use super::softnpu::PortFsmState; - pub use super::softnpu::table::Table; + pub use crate::softnpu::AsicLinkStats; + pub use crate::softnpu::FsmState; + pub use crate::softnpu::FsmType; + pub use crate::softnpu::Handle; + pub use crate::softnpu::PortFsmState; + pub use crate::softnpu::table::Table; } #[cfg(feature = "chaos")] pub mod chaos; #[cfg(feature = "chaos")] mod plat { - pub use super::chaos::AsicLinkStats; - pub use super::chaos::FsmState; - pub use super::chaos::Handle; - pub use super::chaos::PortFsmState; - pub use super::chaos::table::Table; + pub use crate::chaos::AsicLinkStats; + pub use crate::chaos::FsmState; + pub use crate::chaos::FsmType; + pub use crate::chaos::Handle; + pub use crate::chaos::PortFsmState; + pub use crate::chaos::table::Table; } pub use plat::AsicLinkStats; pub use plat::FsmState; +pub use plat::FsmType; pub use plat::Handle; pub use plat::PortFsmState; pub use plat::Table; diff --git a/asic/src/softnpu/mod.rs b/asic/src/softnpu/mod.rs index 0c9a945..cfb497a 100644 --- a/asic/src/softnpu/mod.rs +++ b/asic/src/softnpu/mod.rs @@ -312,6 +312,14 @@ impl AsicOps for Handle { Ok(()) } + fn port_prbs_get_err( + &self, + _port_hdl: PortHdl, + _ms: u32, + ) -> AsicResult> { + Ok(Vec::new()) + } + fn port_add( &self, connector: Connector, diff --git a/asic/src/tofino_asic/imported_bf_functions b/asic/src/tofino_asic/imported_bf_functions index 0b97f67..547abe3 100644 --- a/asic/src/tofino_asic/imported_bf_functions +++ b/asic/src/tofino_asic/imported_bf_functions @@ -81,7 +81,7 @@ bf_rt_operations_counter_sync_set bf_rt_begin_batch bf_rt_end_batch -# pipe manager calls +# port manager calls bf_pm_port_add bf_pm_port_delete bf_pm_port_delete_all @@ -96,23 +96,24 @@ bf_pm_port_kr_mode_get bf_pm_port_autoneg_set bf_pm_port_autoneg_get bf_pm_port_prbs_mode_set +bf_pm_port_prbs_mode_clear +bf_pm_port_prbs_mode_stats_get bf_pm_port_ber_get +bf_pm_port_media_type_get +bf_pm_port_front_panel_port_get_first +bf_pm_port_front_panel_port_get_next +bf_pm_port_front_panel_port_to_dev_port_get +bf_pm_port_valid_speed_and_channel bf_eth_cpu_port_get -bf_port_oper_state_get +bf_port_oper_state_get bf_port_mac_stats_hw_sync_get bf_port_pcs_counters_get bf_port_rs_fec_status_and_counters_get bf_port_encoding_mode_get bf_port_speed_get -bf_pm_port_media_type_get -bf_pm_port_front_panel_port_get_first -bf_pm_port_front_panel_port_get_next -bf_pm_port_front_panel_port_to_dev_port_get -bf_pm_port_valid_speed_and_channel - bf_pm_serdes_tx_eq_override_set # Platform functions diff --git a/asic/src/tofino_asic/imported_bf_types b/asic/src/tofino_asic/imported_bf_types index b18afa9..7ea02f8 100644 --- a/asic/src/tofino_asic/imported_bf_types +++ b/asic/src/tofino_asic/imported_bf_types @@ -1,7 +1,9 @@ bf_rmon_counter_t bf_rmon_counter_array_t bf_pm_fsm_st +bf_pm_port_info_t pm_intf_fsm_states_t +bf_port_sds_prbs_stats_t bf_port_ber_t qsfp_fsm_state_t qsfp_fsm_ch_en_state_t diff --git a/asic/src/tofino_asic/link_fsm.rs b/asic/src/tofino_asic/link_fsm.rs index 246f388..0423959 100644 --- a/asic/src/tofino_asic/link_fsm.rs +++ b/asic/src/tofino_asic/link_fsm.rs @@ -16,16 +16,52 @@ use aal::AsicResult; /// The set of finite state machines we track #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum FsmType { - Port, + PortDfe, + PortAn, + PortPRBS, + PortPipeLoopback, + PortMacNearLoopback, + PortMacFarLoopback, + PortPcsLoopback, + PortSwModel, + PortTxMode, + PortEmulator, Media, Qsfp, QsfpChannel, } +impl FsmType { + pub fn is_port_fsm(&self) -> bool { + matches!( + self, + FsmType::PortDfe + | FsmType::PortAn + | FsmType::PortPRBS + | FsmType::PortPipeLoopback + | FsmType::PortMacNearLoopback + | FsmType::PortMacFarLoopback + | FsmType::PortPcsLoopback + | FsmType::PortSwModel + | FsmType::PortTxMode + | FsmType::PortEmulator + ) + } +} + impl fmt::Display for FsmType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FsmType::Port => write!(f, "Port"), + FsmType::PortDfe => write!(f, "Port (Dfe)"), + FsmType::PortAn => write!(f, "Port (An)"), + FsmType::PortPRBS => write!(f, "Port (PRBS)"), + FsmType::PortPipeLoopback => write!(f, "Port (PipeLoopback)"), + FsmType::PortMacNearLoopback => write!(f, "Port (MacNearLoopback)"), + FsmType::PortMacFarLoopback => write!(f, "Port (MacFarLoopback)"), + FsmType::PortPcsLoopback => write!(f, "Port (PcsLoopback)"), + FsmType::PortSwModel => write!(f, "Port (SwModel)"), + FsmType::PortTxMode => write!(f, "Port (TxMode)"), + FsmType::PortEmulator => write!(f, "Port (Emulator)"), FsmType::Media => write!(f, "Media"), FsmType::Qsfp => write!(f, "Qsfp"), FsmType::QsfpChannel => write!(f, "QsfpChannel"), @@ -38,7 +74,28 @@ impl TryFrom for FsmType { fn try_from(t: genpd::bf_fsm_type_t) -> AsicResult { match t { - genpd::bf_fsm_type_t_BF_FSM_PORT => Ok(FsmType::Port), + genpd::bf_fsm_type_t_BF_FSM_PORT_DFE => Ok(FsmType::PortDfe), + genpd::bf_fsm_type_t_BF_FSM_PORT_AUTONEG => Ok(FsmType::PortAn), + genpd::bf_fsm_type_t_BF_FSM_PORT_PRBS => Ok(FsmType::PortPRBS), + genpd::bf_fsm_type_t_BF_FSM_PORT_PIPE_LOOPBACK => { + Ok(FsmType::PortPipeLoopback) + } + genpd::bf_fsm_type_t_BF_FSM_PORT_MAC_NEAR_LOOPBACK => { + Ok(FsmType::PortMacNearLoopback) + } + genpd::bf_fsm_type_t_BF_FSM_PORT_MAC_FAR_LOOPBACK => { + Ok(FsmType::PortMacFarLoopback) + } + genpd::bf_fsm_type_t_BF_FSM_PORT_PCS_LOOPBACK => { + Ok(FsmType::PortPcsLoopback) + } + genpd::bf_fsm_type_t_BF_FSM_PORT_SW_MODEL => { + Ok(FsmType::PortSwModel) + } + genpd::bf_fsm_type_t_BF_FSM_PORT_TX_MODE => Ok(FsmType::PortTxMode), + genpd::bf_fsm_type_t_BF_FSM_PORT_EMULATOR => { + Ok(FsmType::PortEmulator) + } genpd::bf_fsm_type_t_BF_FSM_MEDIA => Ok(FsmType::Media), genpd::bf_fsm_type_t_BF_FSM_QSFP => Ok(FsmType::Qsfp), genpd::bf_fsm_type_t_BF_FSM_QSFP_CHANNEL => { @@ -63,7 +120,6 @@ impl FsmState { /// instance pub fn new(fsm: u32, state: u32) -> AsicResult { match FsmType::try_from(fsm)? { - FsmType::Port => Ok(FsmState::Port(PortFsmState::try_from(state)?)), FsmType::Media => { Ok(FsmState::Media(MediaFsmState::try_from(state)?)) } @@ -71,16 +127,10 @@ impl FsmState { FsmType::QsfpChannel => { Ok(FsmState::QsfpChannel(QsfpChannelFsmState::try_from(state)?)) } - } - } - - /// Given an FsmState, return the name of the FSM to which it belongs - pub fn fsm(&self) -> FsmType { - match self { - FsmState::Port(_) => FsmType::Port, - FsmState::Media(_) => FsmType::Media, - FsmState::Qsfp(_) => FsmType::Qsfp, - FsmState::QsfpChannel(_) => FsmType::QsfpChannel, + x => { + assert!(x.is_port_fsm()); + Ok(FsmState::Port(PortFsmState::try_from(state)?)) + } } } diff --git a/asic/src/tofino_asic/mod.rs b/asic/src/tofino_asic/mod.rs index fcb0d86..8d44c48 100644 --- a/asic/src/tofino_asic/mod.rs +++ b/asic/src/tofino_asic/mod.rs @@ -35,6 +35,7 @@ use aal::{ AsicError, AsicOps, AsicResult, Connector, PortHdl, SidecarIdentifiers, }; pub use link_fsm::FsmState; +pub use link_fsm::FsmType; pub use link_fsm::PortFsmState; /// There are three generations of the Tofino ASIC, which the SDE refers to as @@ -173,6 +174,14 @@ impl AsicOps for Handle { ports::set_prbs(self, port_hdl, mode) } + fn port_prbs_get_err( + &self, + port_hdl: PortHdl, + ms: u32, + ) -> AsicResult> { + ports::get_err_prbs(self, port_hdl, ms) + } + fn port_ber_get(&self, port_hdl: PortHdl) -> AsicResult { ports::get_ber(self, port_hdl) } diff --git a/asic/src/tofino_asic/ports.rs b/asic/src/tofino_asic/ports.rs index a1cf262..f7bf367 100644 --- a/asic/src/tofino_asic/ports.rs +++ b/asic/src/tofino_asic/ports.rs @@ -378,24 +378,59 @@ pub fn set_prbs( let bf_mode = match mode { PortPrbsMode::Mode31 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_31, - PortPrbsMode::Mode23 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_23, PortPrbsMode::Mode15 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_15, PortPrbsMode::Mode13 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_13, - PortPrbsMode::Mode11 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_11, PortPrbsMode::Mode9 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_9, - PortPrbsMode::Mode7 => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_7, PortPrbsMode::Mission => bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_NONE, }; let mut fp = FrontPortHandle::from_port_hdl(hdl, port_hdl)?; - unsafe { bf_pm_port_prbs_mode_set(hdl.dev_id, fp.ptr(), 1, bf_mode) } - .check_error("setting prbs mode")?; + if bf_mode == bf_port_prbs_mode_e_BF_PORT_PRBS_MODE_NONE { + unsafe { bf_pm_port_prbs_mode_clear(hdl.dev_id, fp.ptr(), 1) } + .check_error("clearing prbs mode")?; + } else { + unsafe { bf_pm_port_prbs_mode_set(hdl.dev_id, fp.ptr(), 1, bf_mode) } + .check_error("setting prbs mode")?; + } config.prbs = mode; Ok(()) } const MAX_N_LANES: usize = 16; +pub fn get_err_prbs( + hdl: &Handle, + port_hdl: PortHdl, + ms: u32, +) -> AsicResult> { + let mut phys_ports = hdl.phys_ports.lock().unwrap(); + let config = phys_ports.get_tofino_port_mut(port_hdl)?; + + if !config.enabled || config.prbs == PortPrbsMode::Mission { + return Err(AsicError::InvalidArg( + "port must be enabled for PRBS before monitoring errors" + .to_string(), + )); + } + + let dev = hdl.dev_id; + let mut fp = FrontPortHandle::from_port_hdl(hdl, port_hdl)?; + let n_lanes = config.channels.len(); + let mut stats: bf_port_sds_prbs_stats_t = unsafe { std::mem::zeroed() }; + let errors = unsafe { + bf_pm_port_prbs_mode_stats_get(dev, fp.ptr(), &mut stats, ms) + .check_error("bf_pm_port_prbs_stats_get")?; + stats + .prbs_stats + .tof2_channel + .iter() + .take(n_lanes) + .map(|c| c.errors) + .collect::>() + }; + Ok(errors) +} + const fn empty_ber() -> bf_port_ber_t { bf_port_ber_t { ber: bf_port_ber_t_ber_union_ { diff --git a/asic/src/tofino_stub/mod.rs b/asic/src/tofino_stub/mod.rs index 9044c2b..8f4224c 100644 --- a/asic/src/tofino_stub/mod.rs +++ b/asic/src/tofino_stub/mod.rs @@ -202,6 +202,14 @@ impl AsicOps for StubHandle { Err(AsicError::OperationUnsupported) } + fn port_prbs_get_err( + &self, + _port_hdl: PortHdl, + _ms: u32, + ) -> AsicResult> { + Err(AsicError::OperationUnsupported) + } + fn port_add( &self, connector: Connector, diff --git a/common/src/ports.rs b/common/src/ports.rs index 6f33007..2d7fccf 100644 --- a/common/src/ports.rs +++ b/common/src/ports.rs @@ -2,7 +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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::convert::TryFrom; use std::fmt; @@ -512,12 +512,9 @@ pub struct PortSpeedFec { )] pub enum PortPrbsMode { Mode31, - Mode23, Mode15, Mode13, - Mode11, Mode9, - Mode7, Mission, // i.e. PRBS disabled } @@ -525,12 +522,9 @@ impl fmt::Display for PortPrbsMode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PortPrbsMode::Mode31 => write!(f, "31"), - PortPrbsMode::Mode23 => write!(f, "23"), PortPrbsMode::Mode15 => write!(f, "15"), PortPrbsMode::Mode13 => write!(f, "13"), - PortPrbsMode::Mode11 => write!(f, "11"), PortPrbsMode::Mode9 => write!(f, "9"), - PortPrbsMode::Mode7 => write!(f, "7"), PortPrbsMode::Mission => write!(f, "Off"), } } @@ -541,12 +535,10 @@ impl FromStr for PortPrbsMode { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "Mode31" | "mode31" | "31" => Ok(PortPrbsMode::Mode31), - "Mode23" | "mode23" | "23" => Ok(PortPrbsMode::Mode23), - "Mode15" | "mode15" | "15" => Ok(PortPrbsMode::Mode15), - "Mode11" | "mode11" | "11" => Ok(PortPrbsMode::Mode11), - "Mode9" | "mode9" | "9" => Ok(PortPrbsMode::Mode9), - "Mode7" | "mode7" | "7" => Ok(PortPrbsMode::Mode7), + "mode31" | "31" => Ok(PortPrbsMode::Mode31), + "mode13" | "13" => Ok(PortPrbsMode::Mode13), + "mode15" | "15" => Ok(PortPrbsMode::Mode15), + "mode9" | "9" => Ok(PortPrbsMode::Mode9), "off" | "none" | "mission" => Ok(PortPrbsMode::Mission), _ => Err("invalid prbs mode"), } diff --git a/dpd-api/src/lib.rs b/dpd-api/src/lib.rs index f26f1ac..5e8c689 100644 --- a/dpd-api/src/lib.rs +++ b/dpd-api/src/lib.rs @@ -7,6 +7,7 @@ //! DPD endpoint definitions. pub mod v1; +pub mod v10; pub mod v2; pub mod v7; @@ -63,6 +64,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (11, PRBS_IMPROVEMENT), (10, ASIC_DETAILS), (9, SNAPSHOT), (8, MCAST_STRICT_UNDERLAY), @@ -815,6 +817,19 @@ pub trait DpdApi { /// Get an existing link by ID. #[endpoint { method = GET, + versions = ..VERSION_PRBS_IMPROVEMENT, + path = "/ports/{port_id}/links/{link_id}" + }] + async fn link_get_v10( + rqctx: RequestContext, + path: Path, + ) -> Result, HttpError> { + Self::link_get(rqctx, path).await.map(|resp| resp.map(Into::into)) + } + /// Get an existing link by ID. + #[endpoint { + method = GET, + versions = VERSION_PRBS_IMPROVEMENT.., path = "/ports/{port_id}/links/{link_id}" }] async fn link_get( @@ -835,6 +850,24 @@ pub trait DpdApi { /// List the links within a single switch port. #[endpoint { method = GET, + versions = ..VERSION_PRBS_IMPROVEMENT, + path = "/ports/{port_id}/links", + }] + async fn link_list_v10( + rqctx: RequestContext, + path: Path, + ) -> Result>, HttpError> { + Self::link_list(rqctx, path).await.map(|resp| { + resp.map(|list| { + list.into_iter().map(Into::into).collect::>() + }) + }) + } + + /// List the links within a single switch port. + #[endpoint { + method = GET, + versions = VERSION_PRBS_IMPROVEMENT.., path = "/ports/{port_id}/links", }] async fn link_list( @@ -845,6 +878,23 @@ pub trait DpdApi { /// List all links, on all switch ports. #[endpoint { method = GET, + versions = ..VERSION_PRBS_IMPROVEMENT, + path = "/links", + }] + async fn link_list_all_v10( + rqctx: RequestContext, + query: Query, + ) -> Result>, HttpError> { + Self::link_list_all(rqctx, query).await.map(|resp| { + resp.map(|list| { + list.into_iter().map(Into::into).collect::>() + }) + }) + } + /// List all links, on all switch ports. + #[endpoint { + method = GET, + versions = VERSION_PRBS_IMPROVEMENT.., path = "/links", }] async fn link_list_all( @@ -947,6 +997,25 @@ pub trait DpdApi { /// Set a link's PRBS speed and mode. #[endpoint { method = PUT, + versions = ..VERSION_PRBS_IMPROVEMENT, + path = "/ports/{port_id}/links/{link_id}/prbs", + }] + async fn link_prbs_set_v10( + rqctx: RequestContext, + path: Path, + body: TypedBody, + ) -> Result { + let mode = PortPrbsMode::try_from(body.into_inner()).map_err(|e| { + HttpError::for_bad_request(None, format!("bad PRBS mode: {e}")) + })?; + + Self::link_prbs_set(rqctx, path, TypedBody::from(mode)).await + } + + /// Set a link's PRBS speed and mode. + #[endpoint { + method = PUT, + versions = VERSION_PRBS_IMPROVEMENT.., path = "/ports/{port_id}/links/{link_id}/prbs", }] async fn link_prbs_set( @@ -962,6 +1031,24 @@ pub trait DpdApi { /// underlying circuitry (such as filter gains). #[endpoint { method = GET, + versions = ..VERSION_PRBS_IMPROVEMENT, + path = "/ports/{port_id}/links/{link_id}/prbs", + }] + async fn link_prbs_get_v10( + rqctx: RequestContext, + path: Path, + ) -> Result, HttpError> { + Self::link_prbs_get(rqctx, path).await.map(|resp| resp.map(Into::into)) + } + + /// Return the link's PRBS speed and mode. + /// + /// During link training, a pseudorandom bit sequence (PRBS) is used to allow + /// each side to synchronize their clocks and set various parameters on the + /// underlying circuitry (such as filter gains). + #[endpoint { + method = GET, + versions = VERSION_PRBS_IMPROVEMENT.., path = "/ports/{port_id}/links/{link_id}/prbs", }] async fn link_prbs_get( @@ -2564,6 +2651,21 @@ pub trait DpdApi { path: Path, ) -> Result, HttpError>; + /** + * Return the measured bit-error rate for a link with an active PRBS + * connection to another switch. + */ + #[endpoint { + method = GET, + path = "/ports/{port_id}/links/{link_id}/prbs_get_err", + versions = VERSION_PRBS_IMPROVEMENT.., + }] + async fn link_prbs_get_err( + rqctx: RequestContext, + path: Path, + body: TypedBody, + ) -> Result>, HttpError>; + /// Capture a PHV snapshot: create snapshot, set triggers, arm, wait /// for trigger, read capture, decode fields, and clean up. #[endpoint { @@ -2588,6 +2690,13 @@ pub trait DpdApi { ) -> Result>, HttpError>; } +/// Duration in milliseconds +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MsDuration { + /// Duration in milliseconds + pub ms: u32, +} + /// Parameter used to create a port. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct PortCreateParams { diff --git a/dpd-api/src/v10.rs b/dpd-api/src/v10.rs new file mode 100644 index 0000000..0bacfd4 --- /dev/null +++ b/dpd-api/src/v10.rs @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Types from API version 10 (ASIC_DETAILS) that changed in +//! version 11 (PRBS_IMPROVEMENT). +//! +//! Dropped API support for PRBS modes not supported by the Tofino ASIC. + +use std::convert::TryFrom; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use common::network::MacAddr; +use common::ports::{PortFec, PortId, PortMedia, PortSpeed}; +use dpd_types::link::{LinkId, LinkState}; + +/// Legal PRBS modes +#[derive( + Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize, JsonSchema, +)] +pub enum PortPrbsMode { + Mode31, + Mode23, + Mode15, + Mode13, + Mode11, + Mode9, + Mode7, + Mission, // i.e. PRBS disabled +} + +impl TryFrom for common::ports::PortPrbsMode { + type Error = String; + + fn try_from(x: PortPrbsMode) -> Result { + match x { + PortPrbsMode::Mode9 => Ok(common::ports::PortPrbsMode::Mode9), + PortPrbsMode::Mode13 => Ok(common::ports::PortPrbsMode::Mode13), + PortPrbsMode::Mode15 => Ok(common::ports::PortPrbsMode::Mode15), + PortPrbsMode::Mode31 => Ok(common::ports::PortPrbsMode::Mode31), + PortPrbsMode::Mission => Ok(common::ports::PortPrbsMode::Mission), + x => Err(format!("{x:?} is not a supported PRBS mode")), + } + } +} + +impl From for PortPrbsMode { + fn from(x: common::ports::PortPrbsMode) -> Self { + match x { + common::ports::PortPrbsMode::Mode9 => PortPrbsMode::Mode9, + common::ports::PortPrbsMode::Mode13 => PortPrbsMode::Mode13, + common::ports::PortPrbsMode::Mode15 => PortPrbsMode::Mode15, + common::ports::PortPrbsMode::Mode31 => PortPrbsMode::Mode31, + common::ports::PortPrbsMode::Mission => PortPrbsMode::Mission, + } + } +} + +/// An Ethernet-capable link within a switch port. +// +// NOTE: This is a view onto `crate::link::Link`. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct Link { + /// The switch port on which this link exists. + pub port_id: PortId, + /// The `LinkId` within the switch port for this link. + pub link_id: LinkId, + /// The Tofino connector number associated with this link. + pub tofino_connector: u16, + /// The lower-level ASIC ID used to refer to this object in the switch + /// driver software. + pub asic_id: u16, + /// True if the transceiver module has detected a media presence. + pub presence: bool, + /// True if this link is in KR mode, i.e., is on a cabled backplane. + pub kr: bool, + /// True if this link is configured to autonegotiate with its peer. + pub autoneg: bool, + /// Current state in the autonegotiation/link-training finite state machine + pub fsm_state: String, + /// The speed of the link. + pub speed: PortSpeed, + /// The error-correction scheme for this link. + pub fec: Option, + /// The physical media underlying this link. + pub media: PortMedia, + /// True if this link is enabled. + pub enabled: bool, + /// The PRBS mode. + pub prbs: PortPrbsMode, + /// The state of the Ethernet link. + pub link_state: LinkState, + /// The MAC address for the link. + pub address: MacAddr, + /// The link is configured for IPv6 use + pub ipv6_enabled: bool, +} + +impl From for Link { + fn from(value: dpd_types::views::Link) -> Self { + Self { + port_id: value.port_id, + link_id: value.link_id, + tofino_connector: value.tofino_connector, + asic_id: value.asic_id, + presence: value.presence, + kr: value.kr, + autoneg: value.autoneg, + fsm_state: value.fsm_state, + speed: value.speed, + fec: value.fec, + media: value.media, + enabled: value.enabled, + prbs: value.prbs.into(), + link_state: value.link_state, + address: value.address, + ipv6_enabled: value.ipv6_enabled, + } + } +} diff --git a/dpd-client/src/lib.rs b/dpd-client/src/lib.rs index 13d5ade..2dab3bb 100644 --- a/dpd-client/src/lib.rs +++ b/dpd-client/src/lib.rs @@ -190,12 +190,9 @@ impl From for ports::PortPrbsMode { fn from(p: types::PortPrbsMode) -> Self { match p { types::PortPrbsMode::Mode31 => ports::PortPrbsMode::Mode31, - types::PortPrbsMode::Mode23 => ports::PortPrbsMode::Mode23, types::PortPrbsMode::Mode15 => ports::PortPrbsMode::Mode15, types::PortPrbsMode::Mode13 => ports::PortPrbsMode::Mode13, - types::PortPrbsMode::Mode11 => ports::PortPrbsMode::Mode11, types::PortPrbsMode::Mode9 => ports::PortPrbsMode::Mode9, - types::PortPrbsMode::Mode7 => ports::PortPrbsMode::Mode7, types::PortPrbsMode::Mission => ports::PortPrbsMode::Mission, } } @@ -205,12 +202,9 @@ impl From for types::PortPrbsMode { fn from(p: ports::PortPrbsMode) -> Self { match p { ports::PortPrbsMode::Mode31 => types::PortPrbsMode::Mode31, - ports::PortPrbsMode::Mode23 => types::PortPrbsMode::Mode23, ports::PortPrbsMode::Mode15 => types::PortPrbsMode::Mode15, ports::PortPrbsMode::Mode13 => types::PortPrbsMode::Mode13, - ports::PortPrbsMode::Mode11 => types::PortPrbsMode::Mode11, ports::PortPrbsMode::Mode9 => types::PortPrbsMode::Mode9, - ports::PortPrbsMode::Mode7 => types::PortPrbsMode::Mode7, ports::PortPrbsMode::Mission => types::PortPrbsMode::Mission, } } diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index ce1870d..d16143e 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -2819,6 +2819,22 @@ impl DpdApi for DpdApiImpl { )) } + async fn link_prbs_get_err( + rqctx: RequestContext, + path: Path, + body: TypedBody, + ) -> Result>, HttpError> { + let switch: &Switch = rqctx.context(); + let path = path.into_inner(); + let port_id = path.port_id; + let link_id = path.link_id; + let duration = body.into_inner(); + switch + .link_prbs_get_err(port_id, link_id, duration.ms) + .map(HttpResponseOk) + .map_err(|e| e.into()) + } + #[cfg(feature = "tofino_asic")] async fn snapshot_capture( rqctx: RequestContext, diff --git a/dpd/src/link.rs b/dpd/src/link.rs index 4dadb56..187f42c 100644 --- a/dpd/src/link.rs +++ b/dpd/src/link.rs @@ -846,13 +846,17 @@ impl Switch { let port_fsm_state = match update { PortUpdate::FSM { fsm, state, .. } => { let fsm_state = asic::FsmState::new(*fsm, *state)?; + let fsm = asic::FsmType::try_from(*fsm)?; let channel = match fsm_state { #[cfg(feature = "tofino_asic")] asic::FsmState::QsfpChannel(_) => Some(link_id.into()), _ => None, }; - self.record_event(asic_port_id, Event::Fsm(channel, fsm_state)); + self.record_event( + asic_port_id, + Event::Fsm(channel, fsm, fsm_state), + ); match fsm_state { asic::FsmState::Port(s) => Some(s), // If we're handling an update for anything other than the @@ -1281,6 +1285,28 @@ impl Switch { })? } + /// Return the measured error counts for a PRBS-enabled link over a + /// specified time period. + pub fn link_prbs_get_err( + &self, + port_id: PortId, + link_id: LinkId, + ms: u32, + ) -> DpdResult> { + self.link_fetch(port_id, link_id, |link| { + if !link.plumbed.enabled + || link.plumbed.prbs == PortPrbsMode::Mission + { + return Err(DpdError::Invalid(String::from( + "PRBS errors can only be counted when a link is enabled\n\ + and has PRBS configured", + ))); + } + self.asic_hdl + .port_prbs_get_err(link.port_hdl, ms) + .map_err(DpdError::from) + })? + } /// Return whether a link is enabled. pub fn link_enabled( &self, @@ -1385,11 +1411,19 @@ impl Switch { link_id: LinkId, prbs: PortPrbsMode, ) -> DpdResult<()> { - self.link_update(port_id, link_id, |link| { - link.config.prbs = prbs; - self.reconciler.trigger(port_id, link_id); - Ok(()) - }) + if prbs != PortPrbsMode::Mission + && self.link_enabled(port_id, link_id)? + { + Err(DpdError::Invalid( + "PRBS cannot be set on an enabled port".into(), + )) + } else { + self.link_update(port_id, link_id, |link| { + link.config.prbs = prbs; + self.reconciler.trigger(port_id, link_id); + Ok(()) + }) + } } /// Return whether a link is configured to drop non-nat traffic @@ -1687,6 +1721,7 @@ fn plumb_link( link.plumbed.speed = link.config.speed; link.plumbed.fec = fec; link.plumbed.enabled = false; + link.plumbed.prbs = PortPrbsMode::Mission; link.plumbed.lane_cnt = switch.asic_hdl.port_get_lane_cnt(port_hdl)?; // Set the autonegotiation value @@ -1794,7 +1829,7 @@ async fn reconcile_link( link.plumbed.autoneg ); true - } else if !link.plumbed.tx_eq_pushed { + } else if link.config.enabled && !link.plumbed.tx_eq_pushed { debug!(log, "tx-eq needs an update, tearing down link",); true } else { diff --git a/dpd/src/ports.rs b/dpd/src/ports.rs index 0783f90..41bb7b6 100644 --- a/dpd/src/ports.rs +++ b/dpd/src/ports.rs @@ -43,7 +43,7 @@ pub enum AdminEvent { pub enum Event { Admin(AdminEvent), Error(String), - Fsm(Option, asic::FsmState), + Fsm(Option, asic::FsmType, asic::FsmState), } // Record a maximum of 1024 events @@ -84,10 +84,10 @@ impl From<&EventRecord> for LinkEvent { subclass: "LinkConfig".to_string(), details: Some(e.clone()), }, - Event::Fsm(channel, fsm) => LinkEvent { + Event::Fsm(channel, type_, fsm) => LinkEvent { timestamp: record.timestamp, channel: *channel, - class: format!("{}FSM", fsm.fsm()), + class: format!("{type_} FSM"), subclass: fsm.state_name(), details: None, }, diff --git a/openapi/dpd/dpd-10.0.0-57485f.json.gitstub b/openapi/dpd/dpd-10.0.0-57485f.json.gitstub new file mode 100644 index 0000000..4ce17ea --- /dev/null +++ b/openapi/dpd/dpd-10.0.0-57485f.json.gitstub @@ -0,0 +1 @@ +78fa4e7c745169e48c7a1d4a0aaa5e9ac47d8057:openapi/dpd/dpd-10.0.0-57485f.json diff --git a/openapi/dpd/dpd-10.0.0-57485f.json b/openapi/dpd/dpd-11.0.0-cc8d70.json similarity index 99% rename from openapi/dpd/dpd-10.0.0-57485f.json rename to openapi/dpd/dpd-11.0.0-cc8d70.json index 2059350..69e898b 100644 --- a/openapi/dpd/dpd-10.0.0-57485f.json +++ b/openapi/dpd/dpd-11.0.0-cc8d70.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "10.0.0" + "version": "11.0.0" }, "paths": { "/all-settings": { @@ -4025,6 +4025,67 @@ } } }, + "/ports/{port_id}/links/{link_id}/prbs_get_err": { + "get": { + "summary": "Return the measured bit-error rate for a link with an active PRBS", + "description": "connection to another switch.", + "operationId": "link_prbs_get_err", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MsDuration" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_uint32", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/ports/{port_id}/links/{link_id}/serdes/adapt": { "get": { "summary": "Get the per-lane adaptation counts for each lane on this link", @@ -8306,6 +8367,21 @@ } } }, + "MsDuration": { + "description": "Duration in milliseconds", + "type": "object", + "properties": { + "ms": { + "description": "Duration in milliseconds", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "ms" + ] + }, "MulticastGroupCreateExternalEntry": { "description": "A multicast group configuration for POST requests for external (to the rack) groups.", "type": "object", @@ -8810,12 +8886,9 @@ "type": "string", "enum": [ "Mode31", - "Mode23", "Mode15", "Mode13", - "Mode11", "Mode9", - "Mode7", "Mission" ] }, diff --git a/openapi/dpd/dpd-latest.json b/openapi/dpd/dpd-latest.json index 79a120a..3eca588 120000 --- a/openapi/dpd/dpd-latest.json +++ b/openapi/dpd/dpd-latest.json @@ -1 +1 @@ -dpd-10.0.0-57485f.json \ No newline at end of file +dpd-11.0.0-cc8d70.json \ No newline at end of file diff --git a/swadm/src/link.rs b/swadm/src/link.rs index a8d31ca..37dcb0c 100644 --- a/swadm/src/link.rs +++ b/swadm/src/link.rs @@ -113,6 +113,16 @@ pub enum LinkCounters { /// The link to fetch the BER for. link: LinkPath, }, + + /// Fetch the per-lane error counts over a specific period of time for a + /// PRBS-enabled link. + Prbs { + /// The link to fetch the PRBS error stats for. + link: LinkPath, + /// The time, specified in milliseconds, in which to count errors + #[clap(default_value = "10")] + ms: u32, + }, } /// Manage faults on ethernet links @@ -570,7 +580,7 @@ pub enum SetLinkProp { /// Set whether the link is configured for IPv6 #[clap(visible_alias = "ipv6")] Ipv6Enabled { enabled: OnOff }, - /// Set the PRBS mode for the link. (7, 9, 11, 15, 23, 31, or mission/off) + /// Set the PRBS mode for the link. (9, 13, 15, 31, or mission/off) Prbs { prbs: PortPrbsMode }, } @@ -751,6 +761,28 @@ async fn link_up_counters( tw.flush().map_err(|e| e.into()) } +async fn link_prbs_err( + client: &Client, + link: LinkPath, + ms: u32, +) -> anyhow::Result<()> { + let errors = client + .link_prbs_get_err( + &link.port_id, + &link.link_id, + &types::MsDuration { ms }, + ) + .await + .map(|r| r.into_inner()) + .context("failed to get PRBS error counts")?; + let mut tw = TabWriter::new(stdout()); + writeln!(tw, "{}\t{}", "Lane".underline(), "Errors".underline(),)?; + for (lane, e) in errors.iter().enumerate() { + writeln!(tw, "{}\t{}", lane, e)?; + } + tw.flush().map_err(|e| e.into()) +} + async fn link_ber(client: &Client, link: LinkPath) -> anyhow::Result<()> { let ber = client .link_ber_get(&link.port_id, &link.link_id) @@ -1925,6 +1957,9 @@ pub async fn link_cmd(client: &Client, link: Link) -> anyhow::Result<()> { LinkCounters::Ber { link } => link_ber(client, link) .await .context("failed to fetch link BER")?, + LinkCounters::Prbs { link, ms } => { + link_prbs_err(client, link, ms).await? + } }, Link::Serdes { cmd: serdes } => match serdes { Serdes::Get { cmd: get } => match get {