Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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