Skip to content

Commit 052eccd

Browse files
[asic-details] Add lotnum (4-character lot number) to SwitchIdentifiers
This PR adds lotnum (4-character lot number) to SwitchIdentifiers and exposes ASIC details via swadm (and the dpd-client API). The full lot identifier combines fab, lot, and lotnum (e.g., FTCAK7 instead of just T). We do keep the current fields separated however, but update the view for the combination. Adds v1::SwitchIdentifiers for API versions 1-4 (without lotnum), with the latest version (v5+) including the new field. Fixes #200: Want swadm command to print Tofino ASIC details Fixes #202: Not reporting full lot numbers in oximeter statistics
1 parent cf31be9 commit 052eccd

17 files changed

Lines changed: 10327 additions & 11 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aal/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub trait SidecarIdentifiers {
4343
fn asic_backend(&self) -> &str;
4444
fn fab(&self) -> Option<char>;
4545
fn lot(&self) -> Option<char>;
46+
fn lotnum(&self) -> Option<[char; 4]>;
4647
fn wafer(&self) -> Option<u8>;
4748
fn wafer_loc(&self) -> Option<(i16, i16)>;
4849
}

asic/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ pub struct Identifiers {
3636
fab: Option<char>,
3737
/// Lot identifier.
3838
lot: Option<char>,
39+
/// Lot number (4-character identifier within the lot).
40+
///
41+
/// The 4-character size comes from the Tofino ASIC fuse layout, which
42+
/// stores lotnum as four separate character fields (lotnum0-3) in
43+
/// `tofino::fuse::ChipId`.
44+
lotnum: Option<[char; 4]>,
3945
/// Wafer number within the lot.
4046
wafer: Option<u8>,
4147
/// The wafer location as (x, y) coordinates on the wafer, represented as
@@ -50,6 +56,7 @@ impl Default for Identifiers {
5056
asic_backend: "chaos".to_string(),
5157
fab: None,
5258
lot: None,
59+
lotnum: None,
5360
wafer: None,
5461
wafer_loc: None,
5562
}
@@ -73,6 +80,10 @@ impl aal::SidecarIdentifiers for Identifiers {
7380
self.lot
7481
}
7582

83+
fn lotnum(&self) -> Option<[char; 4]> {
84+
self.lotnum
85+
}
86+
7687
fn wafer(&self) -> Option<u8> {
7788
self.wafer
7889
}

asic/src/softnpu/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ impl AsicOps for Handle {
375375
asic_backend: "softnpu".to_string(),
376376
fab: None,
377377
lot: None,
378+
lotnum: None,
378379
wafer: None,
379380
wafer_loc: None,
380381
})

asic/src/tofino_asic/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ impl AsicOps for Handle {
224224
asic_backend: "tofino_asic".to_string(),
225225
fab: Some(chip_id.fab),
226226
lot: Some(chip_id.lot),
227+
lotnum: Some([
228+
chip_id.lotnum0,
229+
chip_id.lotnum1,
230+
chip_id.lotnum2,
231+
chip_id.lotnum3,
232+
]),
227233
wafer: Some(chip_id.wafer),
228234
wafer_loc: Some(wafer_loc_from_coords(
229235
chip_id.xsign,

asic/src/tofino_stub/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ impl AsicOps for StubHandle {
226226
asic_backend: "tofino_stub".to_string(),
227227
fab: None,
228228
lot: None,
229+
lotnum: None,
229230
wafer: None,
230231
wafer_loc: None,
231232
})

dpd-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ oxnet.workspace = true
1212
schemars.workspace = true
1313
serde.workspace = true
1414
transceiver-controller.workspace = true
15+
uuid.workspace = true

dpd-api/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ api_versions!([
5959
// | example for the next person.
6060
// v
6161
// (next_int, IDENT),
62+
(5, SWITCH_IDENTIFIERS_LOTNUM),
6263
(4, V4_OVER_V6_ROUTES),
6364
(3, ATTACHED_SUBNETS),
6465
(2, DUAL_STACK_NAT_WORKFLOW),
@@ -1478,11 +1479,30 @@ pub trait DpdApi {
14781479
#[endpoint {
14791480
method = GET,
14801481
path = "/switch/identifiers",
1482+
versions = VERSION_SWITCH_IDENTIFIERS_LOTNUM..,
14811483
}]
14821484
async fn switch_identifiers(
14831485
rqctx: RequestContext<Self::Context>,
14841486
) -> Result<HttpResponseOk<SwitchIdentifiers>, HttpError>;
14851487

1488+
/// Get switch identifiers.
1489+
///
1490+
/// Returns identifying information for the switch and its ASIC, including
1491+
/// the sidecar ID, fabrication details, and SP metadata. Does not include
1492+
/// the `lotnum` field.
1493+
#[endpoint {
1494+
method = GET,
1495+
path = "/switch/identifiers",
1496+
operation_id = "switch_identifiers",
1497+
versions = ..VERSION_SWITCH_IDENTIFIERS_LOTNUM,
1498+
}]
1499+
async fn switch_identifiers_v1(
1500+
rqctx: RequestContext<Self::Context>,
1501+
) -> Result<HttpResponseOk<v1::SwitchIdentifiers>, HttpError> {
1502+
let result = Self::switch_identifiers(rqctx).await?.0;
1503+
Ok(HttpResponseOk(result.into()))
1504+
}
1505+
14861506
/// Collect the link data consumed by `tfportd`. This app-specific convenience
14871507
/// routine is meant to reduce the time and traffic expended on this once-per-
14881508
/// second operation, by consolidating multiple per-link requests into a single

dpd-api/src/v1.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
//
55
// Copyright 2026 Oxide Computer Company
66

7+
//! Types from API version 1 (INITIAL) that changed in later versions.
8+
79
use dpd_types::route::Ipv4Route;
810
use oxnet::Ipv4Net;
911
use schemars::JsonSchema;
1012
use serde::{Deserialize, Serialize};
13+
use uuid::Uuid;
1114

1215
/// Represents all mappings of an IPv4 subnet to a its nexthop target(s).
1316
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
@@ -18,3 +21,53 @@ pub struct Ipv4Routes {
1821
/// All RouteTargets associated with this CIDR
1922
pub targets: Vec<Ipv4Route>,
2023
}
24+
25+
/// Identifiers for a switch.
26+
///
27+
/// Does not include the `lotnum` field, which was added in
28+
/// SWITCH_IDENTIFIERS_LOTNUM.
29+
#[derive(Clone, Debug, JsonSchema, Serialize)]
30+
pub struct SwitchIdentifiers {
31+
/// Unique identifier for the chip.
32+
pub sidecar_id: Uuid,
33+
/// Asic backend (compiler target) responsible for these identifiers.
34+
pub asic_backend: String,
35+
/// Fabrication plant identifier.
36+
pub fab: Option<char>,
37+
/// Lot identifier.
38+
pub lot: Option<char>,
39+
/// Wafer number within the lot.
40+
pub wafer: Option<u8>,
41+
/// The wafer location as (x, y) coordinates on the wafer, represented as
42+
/// an array due to the lack of tuple support in OpenAPI.
43+
pub wafer_loc: Option<[i16; 2]>,
44+
/// The model number of the switch being managed.
45+
pub model: String,
46+
/// The revision number of the switch being managed.
47+
pub revision: u32,
48+
/// The serial number of the switch being managed.
49+
pub serial: String,
50+
/// The slot number of the switch being managed.
51+
///
52+
/// MGS uses u16 for this internally.
53+
pub slot: u16,
54+
}
55+
56+
impl From<dpd_types::switch_identifiers::SwitchIdentifiers>
57+
for SwitchIdentifiers
58+
{
59+
fn from(latest: dpd_types::switch_identifiers::SwitchIdentifiers) -> Self {
60+
Self {
61+
sidecar_id: latest.sidecar_id,
62+
asic_backend: latest.asic_backend,
63+
fab: latest.fab,
64+
lot: latest.lot,
65+
wafer: latest.wafer,
66+
wafer_loc: latest.wafer_loc,
67+
model: latest.model,
68+
revision: latest.revision,
69+
serial: latest.serial,
70+
slot: latest.slot,
71+
}
72+
}
73+
}

dpd-client/src/lib.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,29 @@ impl fmt::Display for types::TfportData {
359359
}
360360
}
361361

362+
impl types::SwitchIdentifiers {
363+
/// Returns the full lot identifier by combining `fab`, `lot`, and `lotnum`.
364+
pub fn full_lot_id(&self) -> Option<String> {
365+
let mut lot = String::new();
366+
let has_lot_data = self.lot.is_some() || self.lotnum.is_some();
367+
if has_lot_data
368+
&& let Some(fab) = &self.fab
369+
{
370+
lot.push_str(fab.as_str());
371+
}
372+
if let Some(lot_char) = &self.lot {
373+
lot.push_str(lot_char.as_str());
374+
}
375+
if let Some(lotnum) = &self.lotnum {
376+
lot.push_str(
377+
lotnum.iter().map(|c| c.as_str()).collect::<String>().as_str(),
378+
);
379+
}
380+
381+
if lot.is_empty() { None } else { Some(lot) }
382+
}
383+
}
384+
362385
impl fmt::Display for types::SffComplianceCode {
363386
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
364387
match self {
@@ -388,8 +411,93 @@ impl fmt::Display for types::MediaInterfaceId {
388411

389412
#[cfg(test)]
390413
mod tests {
414+
use super::types::SwitchIdentifiers;
391415
use common::ports::PortId;
392416

417+
#[test]
418+
fn test_full_lot_id_both_present() {
419+
let idents: SwitchIdentifiers = serde_json::from_str(
420+
r#"{
421+
"sidecar_id": "00000000-0000-0000-0000-000000000000",
422+
"asic_backend": "test",
423+
"fab": "F",
424+
"lot": "T",
425+
"lotnum": ["C", "A", "K", "7"],
426+
"wafer": 1,
427+
"wafer_loc": [10, 20],
428+
"model": "test-model",
429+
"revision": 1,
430+
"serial": "test-serial",
431+
"slot": 0
432+
}"#,
433+
)
434+
.unwrap();
435+
assert_eq!(idents.full_lot_id(), Some("FTCAK7".to_string()));
436+
}
437+
438+
#[test]
439+
fn test_full_lot_id_lot_only() {
440+
let idents: SwitchIdentifiers = serde_json::from_str(
441+
r#"{
442+
"sidecar_id": "00000000-0000-0000-0000-000000000000",
443+
"asic_backend": "test",
444+
"fab": "F",
445+
"lot": "T",
446+
"lotnum": null,
447+
"wafer": 1,
448+
"wafer_loc": [10, 20],
449+
"model": "test-model",
450+
"revision": 1,
451+
"serial": "test-serial",
452+
"slot": 0
453+
}"#,
454+
)
455+
.unwrap();
456+
assert_eq!(idents.full_lot_id(), Some("FT".to_string()));
457+
}
458+
459+
#[test]
460+
fn test_full_lot_id_lotnum_only() {
461+
let idents: SwitchIdentifiers = serde_json::from_str(
462+
r#"{
463+
"sidecar_id": "00000000-0000-0000-0000-000000000000",
464+
"asic_backend": "test",
465+
"fab": null,
466+
"lot": null,
467+
"lotnum": ["C", "A", "K", "7"],
468+
"wafer": 1,
469+
"wafer_loc": [10, 20],
470+
"model": "test-model",
471+
"revision": 1,
472+
"serial": "test-serial",
473+
"slot": 0
474+
}"#,
475+
)
476+
.unwrap();
477+
assert_eq!(idents.full_lot_id(), Some("CAK7".to_string()));
478+
}
479+
480+
#[test]
481+
fn test_full_lot_id_neither_present() {
482+
let idents: SwitchIdentifiers = serde_json::from_str(
483+
r#"{
484+
"sidecar_id": "00000000-0000-0000-0000-000000000000",
485+
"asic_backend": "test",
486+
"fab": null,
487+
"lot": null,
488+
"lotnum": null,
489+
"wafer": 1,
490+
"wafer_loc": [10, 20],
491+
"model": "test-model",
492+
"revision": 1,
493+
"serial": "test-serial",
494+
"slot": 0
495+
}"#,
496+
)
497+
.unwrap();
498+
assert_eq!(idents.full_lot_id(), None);
499+
}
500+
393501
#[test]
394502
fn test_parse_client_port_id() {
395503
assert!("rear3".parse::<PortId>().is_ok());

0 commit comments

Comments
 (0)