Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub trait SidecarIdentifiers {
fn asic_backend(&self) -> &str;
fn fab(&self) -> Option<char>;
fn lot(&self) -> Option<char>;
fn lotnum(&self) -> Option<[char; 4]>;
fn wafer(&self) -> Option<u8>;
fn wafer_loc(&self) -> Option<(i16, i16)>;
}
Expand Down
11 changes: 11 additions & 0 deletions asic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub struct Identifiers {
fab: Option<char>,
/// Lot identifier.
lot: Option<char>,
/// Lot number (4-character identifier within the lot).
///
/// The 4-character size comes from the Tofino ASIC fuse layout, which
/// stores lotnum as four separate character fields (lotnum0-3) in
/// `tofino::fuse::ChipId`.
lotnum: Option<[char; 4]>,
/// Wafer number within the lot.
wafer: Option<u8>,
/// The wafer location as (x, y) coordinates on the wafer, represented as
Expand All @@ -50,6 +56,7 @@ impl Default for Identifiers {
asic_backend: "chaos".to_string(),
fab: None,
lot: None,
lotnum: None,
wafer: None,
wafer_loc: None,
}
Expand All @@ -73,6 +80,10 @@ impl aal::SidecarIdentifiers for Identifiers {
self.lot
}

fn lotnum(&self) -> Option<[char; 4]> {
self.lotnum
}

fn wafer(&self) -> Option<u8> {
self.wafer
}
Expand Down
1 change: 1 addition & 0 deletions asic/src/softnpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ impl AsicOps for Handle {
asic_backend: "softnpu".to_string(),
fab: None,
lot: None,
lotnum: None,
wafer: None,
wafer_loc: None,
})
Expand Down
6 changes: 6 additions & 0 deletions asic/src/tofino_asic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ impl AsicOps for Handle {
asic_backend: "tofino_asic".to_string(),
fab: Some(chip_id.fab),
lot: Some(chip_id.lot),
lotnum: Some([
chip_id.lotnum0,
chip_id.lotnum1,
chip_id.lotnum2,
chip_id.lotnum3,
]),
wafer: Some(chip_id.wafer),
wafer_loc: Some(wafer_loc_from_coords(
chip_id.xsign,
Expand Down
1 change: 1 addition & 0 deletions asic/src/tofino_stub/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ impl AsicOps for StubHandle {
asic_backend: "tofino_stub".to_string(),
fab: None,
lot: None,
lotnum: None,
wafer: None,
wafer_loc: None,
})
Expand Down
1 change: 1 addition & 0 deletions dpd-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ oxnet.workspace = true
schemars.workspace = true
serde.workspace = true
transceiver-controller.workspace = true
uuid.workspace = true
20 changes: 20 additions & 0 deletions dpd-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ api_versions!([
// | example for the next person.
// v
// (next_int, IDENT),
(5, SWITCH_IDENTIFIERS_LOTNUM),
(4, V4_OVER_V6_ROUTES),
(3, ATTACHED_SUBNETS),
(2, DUAL_STACK_NAT_WORKFLOW),
Expand Down Expand Up @@ -1478,11 +1479,30 @@ pub trait DpdApi {
#[endpoint {
method = GET,
path = "/switch/identifiers",
versions = VERSION_SWITCH_IDENTIFIERS_LOTNUM..,
}]
async fn switch_identifiers(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseOk<SwitchIdentifiers>, HttpError>;

/// Get switch identifiers.
///
/// Returns identifying information for the switch and its ASIC, including
/// the sidecar ID, fabrication details, and SP metadata. Does not include
/// the `lotnum` field.
#[endpoint {
method = GET,
path = "/switch/identifiers",
operation_id = "switch_identifiers",
versions = ..VERSION_SWITCH_IDENTIFIERS_LOTNUM,
}]
async fn switch_identifiers_v1(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseOk<v1::SwitchIdentifiers>, HttpError> {
let result = Self::switch_identifiers(rqctx).await?.0;
Ok(HttpResponseOk(result.into()))
}

/// Collect the link data consumed by `tfportd`. This app-specific convenience
/// routine is meant to reduce the time and traffic expended on this once-per-
/// second operation, by consolidating multiple per-link requests into a single
Expand Down
53 changes: 53 additions & 0 deletions dpd-api/src/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
//
// Copyright 2026 Oxide Computer Company

//! Types from API version 1 (INITIAL) that changed in later versions.

use dpd_types::route::Ipv4Route;
use oxnet::Ipv4Net;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Represents all mappings of an IPv4 subnet to a its nexthop target(s).
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
Expand All @@ -18,3 +21,53 @@ pub struct Ipv4Routes {
/// All RouteTargets associated with this CIDR
pub targets: Vec<Ipv4Route>,
}

/// Identifiers for a switch.
///
/// Does not include the `lotnum` field, which was added in
/// SWITCH_IDENTIFIERS_LOTNUM.
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
pub struct SwitchIdentifiers {
/// Unique identifier for the chip.
pub sidecar_id: Uuid,
/// Asic backend (compiler target) responsible for these identifiers.
pub asic_backend: String,
/// Fabrication plant identifier.
pub fab: Option<char>,
/// Lot identifier.
pub lot: Option<char>,
/// Wafer number within the lot.
pub wafer: Option<u8>,
/// The wafer location as (x, y) coordinates on the wafer, represented as
/// an array due to the lack of tuple support in OpenAPI.
pub wafer_loc: Option<[i16; 2]>,
/// The model number of the switch being managed.
pub model: String,
/// The revision number of the switch being managed.
pub revision: u32,
/// The serial number of the switch being managed.
pub serial: String,
/// The slot number of the switch being managed.
///
/// MGS uses u16 for this internally.
pub slot: u16,
}

impl From<dpd_types::switch_identifiers::SwitchIdentifiers>
for SwitchIdentifiers
{
fn from(latest: dpd_types::switch_identifiers::SwitchIdentifiers) -> Self {
Self {
sidecar_id: latest.sidecar_id,
asic_backend: latest.asic_backend,
fab: latest.fab,
lot: latest.lot,
wafer: latest.wafer,
wafer_loc: latest.wafer_loc,
model: latest.model,
revision: latest.revision,
serial: latest.serial,
slot: latest.slot,
}
}
}
104 changes: 104 additions & 0 deletions dpd-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,25 @@ impl fmt::Display for types::TfportData {
}
}

impl types::SwitchIdentifiers {
/// Returns the full lot identifier by combining `fab`, `lot`, and `lotnum`.
pub fn full_lot_id(&self) -> Option<String> {
let mut lot = String::new();
let has_lot_data = self.lot.is_some() || self.lotnum.is_some();
if has_lot_data && let Some(fab) = &self.fab {
lot.push_str(fab.as_str());
}
if let Some(lot_char) = &self.lot {
lot.push_str(lot_char.as_str());
}
if let Some(lotnum) = &self.lotnum {
lot.extend(lotnum.iter().map(|c| c.as_str()));
}

if lot.is_empty() { None } else { Some(lot) }
}
}

impl fmt::Display for types::SffComplianceCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Expand Down Expand Up @@ -388,8 +407,93 @@ impl fmt::Display for types::MediaInterfaceId {

#[cfg(test)]
mod tests {
use super::types::SwitchIdentifiers;
use common::ports::PortId;

#[test]
fn test_full_lot_id_both_present() {
let idents: SwitchIdentifiers = serde_json::from_str(
r#"{
"sidecar_id": "00000000-0000-0000-0000-000000000000",
"asic_backend": "test",
"fab": "F",
"lot": "T",
"lotnum": ["C", "A", "K", "7"],
"wafer": 1,
"wafer_loc": [10, 20],
"model": "test-model",
"revision": 1,
"serial": "test-serial",
"slot": 0
}"#,
)
.unwrap();
assert_eq!(idents.full_lot_id(), Some("FTCAK7".to_string()));
}

#[test]
fn test_full_lot_id_lot_only() {
let idents: SwitchIdentifiers = serde_json::from_str(
r#"{
"sidecar_id": "00000000-0000-0000-0000-000000000000",
"asic_backend": "test",
"fab": "F",
"lot": "T",
"lotnum": null,
"wafer": 1,
"wafer_loc": [10, 20],
"model": "test-model",
"revision": 1,
"serial": "test-serial",
"slot": 0
}"#,
)
.unwrap();
assert_eq!(idents.full_lot_id(), Some("FT".to_string()));
}

#[test]
fn test_full_lot_id_lotnum_only() {
let idents: SwitchIdentifiers = serde_json::from_str(
r#"{
"sidecar_id": "00000000-0000-0000-0000-000000000000",
"asic_backend": "test",
"fab": null,
"lot": null,
"lotnum": ["C", "A", "K", "7"],
"wafer": 1,
"wafer_loc": [10, 20],
"model": "test-model",
"revision": 1,
"serial": "test-serial",
"slot": 0
}"#,
)
.unwrap();
assert_eq!(idents.full_lot_id(), Some("CAK7".to_string()));
}

#[test]
fn test_full_lot_id_neither_present() {
let idents: SwitchIdentifiers = serde_json::from_str(
r#"{
"sidecar_id": "00000000-0000-0000-0000-000000000000",
"asic_backend": "test",
"fab": null,
"lot": null,
"lotnum": null,
"wafer": 1,
"wafer_loc": [10, 20],
"model": "test-model",
"revision": 1,
"serial": "test-serial",
"slot": 0
}"#,
)
.unwrap();
assert_eq!(idents.full_lot_id(), None);
}

#[test]
fn test_parse_client_port_id() {
assert!("rear3".parse::<PortId>().is_ok());
Expand Down
2 changes: 2 additions & 0 deletions dpd-types/src/switch_identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct SwitchIdentifiers {
pub fab: Option<char>,
/// Lot identifier.
pub lot: Option<char>,
/// Lot number (4-character identifier within the lot).
pub lotnum: Option<[char; 4]>,
/// Wafer number within the lot.
pub wafer: Option<u8>,
/// The wafer location as (x, y) coordinates on the wafer, represented as
Expand Down
35 changes: 25 additions & 10 deletions dpd/src/oxstats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ use switch_table::{
/// The maximum Dropshot request size for the metrics server.
const METRIC_REQUEST_MAX_SIZE: usize = 1024 * 1024;

/// Construct the full lot identifier from the fab, lot character, and lotnum
/// array.
///
/// The identifier is a concatenation of the fab, the lot character, and the
/// four-character lot number. If none of these are available, fall back to the
/// ASIC backend name.
fn full_lot_id(idents: &SwitchIdentifiers) -> String {
let mut lot = String::new();
let has_lot_data = idents.lot.is_some() || idents.lotnum.is_some();
if has_lot_data && let Some(fab) = idents.fab {
lot.push(fab);
}
if let Some(lot_char) = idents.lot {
lot.push(lot_char);
}
if let Some(lotnum) = idents.lotnum {
lot.extend(lotnum);
}

if lot.is_empty() { idents.asic_backend.clone() } else { lot }
}

/// Kind category for the the data link.
const LINK_KIND: &str = "switch-port";
/// Network type for the data link.
Expand Down Expand Up @@ -234,11 +256,7 @@ impl OximeterTargets {
.map(|c| c.to_string())
.unwrap_or(switch_identifiers.asic_backend.clone())
.into(),
asic_lot: switch_identifiers
.lot
.map(|c| c.to_string())
.unwrap_or(switch_identifiers.asic_backend.clone())
.into(),
asic_lot: full_lot_id(switch_identifiers).into(),
asic_wafer: switch_identifiers.wafer.unwrap_or(0),
asic_wafer_loc_x: switch_identifiers
.wafer_loc
Expand Down Expand Up @@ -306,11 +324,7 @@ impl Oxstats {
.map(|c| c.to_string())
.unwrap_or(switch_identifiers.asic_backend.clone())
.into(),
asic_lot: switch_identifiers
.lot
.map(|c| c.to_string())
.unwrap_or(switch_identifiers.asic_backend.clone())
.into(),
asic_lot: full_lot_id(switch_identifiers).into(),
asic_wafer: switch_identifiers.wafer.unwrap_or(0),
asic_wafer_loc_x: switch_identifiers
.wafer_loc
Expand Down Expand Up @@ -593,6 +607,7 @@ async fn wait_for_switch_identifiers(
asic_backend: switch_identifiers.asic_backend.clone(),
fab: switch_identifiers.fab,
lot: switch_identifiers.lot,
lotnum: switch_identifiers.lotnum,
wafer: switch_identifiers.wafer,
wafer_loc: switch_identifiers.wafer_loc,
model: switch_identifiers.model.clone(),
Expand Down
1 change: 1 addition & 0 deletions dpd/src/switch_identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub(crate) async fn fetch_switch_identifiers_loop(
asic_backend: sidecar_idents.asic_backend().to_string(),
fab: sidecar_idents.fab(),
lot: sidecar_idents.lot(),
lotnum: sidecar_idents.lotnum(),
wafer: sidecar_idents.wafer(),
wafer_loc: sidecar_idents.wafer_loc().map(|(x, y)| [x, y]),
model: sp.model,
Expand Down
Loading