Skip to content

Commit 78fa4e7

Browse files
[asic-details] Add lotnum (4-character lot number) to SwitchIdentifiers (#203)
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 348adbb commit 78fa4e7

27 files changed

Lines changed: 934 additions & 25 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/fuse.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/
4+
//
5+
// Copyright 2026 Oxide Computer Company
6+
7+
//! Fuse data types for Tofino ASICs.
8+
//!
9+
//! Chip revision info is exposed in oximeter metrics via the `asic_lot` field
10+
//! (e.g., "FL1234-B1").
11+
//!
12+
//! TODO: Add a dedicated `asic_chip_rev` field to oximeter timeseries when
13+
//! reconfigurator supports oximeter schema migration. Currently there is no
14+
//! window during online updates where all switches stop producing data, so the
15+
//! old schema cannot be safely updated.
16+
17+
use schemars::JsonSchema;
18+
use serde::Serialize;
19+
20+
/// Chip revision derived from device_id and rev_num fuse fields.
21+
#[derive(Clone, Debug, JsonSchema, Serialize)]
22+
pub struct ChipRevision {
23+
/// Computed revision string (e.g., "A0", "B0", "B1").
24+
pub rev: String,
25+
/// Raw device ID from fuse.
26+
pub device_id: u16,
27+
/// Raw revision number from fuse.
28+
pub rev_num: u8,
29+
}
30+
31+
impl ChipRevision {
32+
/// Compute chip revision from device_id and rev_num.
33+
pub fn from_fuse(device_id: u16, rev_num: u8) -> Self {
34+
let rev = match device_id {
35+
0x0100 => "A0".to_string(),
36+
0x0110 => match rev_num {
37+
0 => "B0".to_string(),
38+
2 => "B1".to_string(),
39+
_ => format!("{:04x}", device_id),
40+
},
41+
_ => format!("{:04x}", device_id),
42+
};
43+
Self { rev, device_id, rev_num }
44+
}
45+
}
46+
47+
/// Part identification from fuse data.
48+
#[derive(Clone, Debug, JsonSchema, Serialize)]
49+
pub struct PartInfo {
50+
/// Part number (13 bits).
51+
pub part_num: u16,
52+
/// Package ID (2 bits).
53+
pub pkg_id: u8,
54+
/// Fuse version (2 bits).
55+
pub version: u8,
56+
}
57+
58+
/// Features disabled via fuse programming.
59+
#[derive(Clone, Debug, JsonSchema, Serialize)]
60+
pub struct DisabledFeatures {
61+
/// Disabled pipes (4-bit bitmap).
62+
pub pipes: u8,
63+
/// Disabled ports (40-bit bitmap).
64+
pub ports: u64,
65+
/// Disabled speeds (64-bit bitmap).
66+
pub speeds: u64,
67+
/// Disabled MAUs per pipe (21 bits each).
68+
pub mau: [u32; 4],
69+
/// Disabled traffic manager memory (32-bit bitmap).
70+
pub tm_mem: u32,
71+
/// Buffer sync disabled.
72+
pub bsync: bool,
73+
/// Packet generator disabled.
74+
pub pgen: bool,
75+
/// Resubmit disabled.
76+
pub resub: bool,
77+
}
78+
79+
/// Frequency settings from fuse data.
80+
#[derive(Clone, Debug, JsonSchema, Serialize)]
81+
pub struct FrequencySettings {
82+
/// Frequency disabled.
83+
pub disabled: bool,
84+
/// Backplane port speed frequency (2 bits).
85+
pub bps: u8,
86+
/// Packet processing speed frequency (2 bits).
87+
pub pps: u8,
88+
/// Extended backplane frequency (4 bits).
89+
pub bps_ext: u8,
90+
/// Extended packet speed frequency (4 bits).
91+
pub pps_ext: u8,
92+
/// PCIe disabled (2 bits).
93+
pub pcie_dis: u8,
94+
/// CPU speed disabled (2 bits).
95+
pub cpu_speed_dis: u8,
96+
}
97+
98+
/// Manufacturing and repair data from fuse.
99+
#[derive(Clone, Debug, JsonSchema, Serialize)]
100+
pub struct ManufacturingData {
101+
/// Voltage scaling value (12 bits).
102+
pub voltage_scaling: u16,
103+
/// PMRO and skew value (12 bits).
104+
pub pmro_and_skew: u16,
105+
/// Die rotation.
106+
pub die_rotation: bool,
107+
/// Silent spin (2 bits).
108+
pub silent_spin: u8,
109+
/// Wafer core repair applied.
110+
pub wf_core_repair: bool,
111+
/// Core repair applied.
112+
pub core_repair: bool,
113+
/// Tile repair applied.
114+
pub tile_repair: bool,
115+
/// Soft pipe disable (4 bits).
116+
pub soft_pipe_dis: u8,
117+
}
118+
119+
/// Organized fuse data from the Tofino ASIC.
120+
#[derive(Clone, Debug, JsonSchema, Serialize)]
121+
pub struct FuseData {
122+
/// Chip revision information.
123+
pub chip_rev: ChipRevision,
124+
/// Part identification.
125+
pub part: PartInfo,
126+
/// Disabled features.
127+
pub disabled: DisabledFeatures,
128+
/// Frequency settings.
129+
pub frequency: FrequencySettings,
130+
/// Manufacturing and repair data.
131+
pub manufacturing: ManufacturingData,
132+
}
133+
134+
#[cfg(test)]
135+
mod tests {
136+
use super::*;
137+
138+
#[test]
139+
fn chip_revision_a0() {
140+
let rev = ChipRevision::from_fuse(0x0100, 0);
141+
assert_eq!(rev.rev, "A0");
142+
assert_eq!(rev.device_id, 0x0100);
143+
assert_eq!(rev.rev_num, 0);
144+
}
145+
146+
#[test]
147+
fn chip_revision_b0() {
148+
let rev = ChipRevision::from_fuse(0x0110, 0);
149+
assert_eq!(rev.rev, "B0");
150+
assert_eq!(rev.device_id, 0x0110);
151+
assert_eq!(rev.rev_num, 0);
152+
}
153+
154+
#[test]
155+
fn chip_revision_b1() {
156+
let rev = ChipRevision::from_fuse(0x0110, 2);
157+
assert_eq!(rev.rev, "B1");
158+
assert_eq!(rev.device_id, 0x0110);
159+
assert_eq!(rev.rev_num, 2);
160+
}
161+
162+
#[test]
163+
fn chip_revision_unknown_rev_num() {
164+
let rev = ChipRevision::from_fuse(0x0110, 5);
165+
assert_eq!(rev.rev, "0110");
166+
assert_eq!(rev.device_id, 0x0110);
167+
assert_eq!(rev.rev_num, 5);
168+
}
169+
170+
#[test]
171+
fn chip_revision_unknown_device_id() {
172+
let rev = ChipRevision::from_fuse(0x0200, 0);
173+
assert_eq!(rev.rev, "0200");
174+
assert_eq!(rev.device_id, 0x0200);
175+
assert_eq!(rev.rev_num, 0);
176+
}
177+
}

aal/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use thiserror::Error;
1313

1414
use common::ports::{PortFec, PortMedia, PortPrbsMode, PortSpeed, TxEq};
1515

16+
mod fuse;
17+
pub use fuse::*;
18+
1619
mod match_action;
1720
pub use match_action::*;
1821

@@ -43,8 +46,10 @@ pub trait SidecarIdentifiers {
4346
fn asic_backend(&self) -> &str;
4447
fn fab(&self) -> Option<char>;
4548
fn lot(&self) -> Option<char>;
49+
fn lotnum(&self) -> Option<[char; 4]>;
4650
fn wafer(&self) -> Option<u8>;
4751
fn wafer_loc(&self) -> Option<(i16, i16)>;
52+
fn fuse_info(&self) -> Option<FuseData>;
4853
}
4954

5055
/// Error type conveying additional information about ASIC errors

asic/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/
44
//
5-
// Copyright 2025 Oxide Computer Company
5+
// Copyright 2026 Oxide Computer Company
66

77
use std::env;
88
use std::fs::File;

asic/src/chaos/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,10 @@ impl AsicOps for Handle {
566566

567567
fn get_sidecar_identifiers(&self) -> AsicResult<impl SidecarIdentifiers> {
568568
unfurl!(self, get_sidecar_identifiers);
569-
Ok(Identifiers::default())
569+
Ok(Identifiers {
570+
fuse: Some(crate::faux_fuse_data()),
571+
..Default::default()
572+
})
570573
}
571574
}
572575

asic/src/lib.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,44 @@ use uuid::Uuid;
2222
#[cfg(not(feature = "tofino_asic"))]
2323
mod faux_fsm;
2424

25+
/// Returns faux fuse data for non-hardware backends.
26+
#[cfg(not(feature = "tofino_asic"))]
27+
pub(crate) fn faux_fuse_data() -> aal::FuseData {
28+
aal::FuseData {
29+
chip_rev: aal::ChipRevision::from_fuse(0x0110, 2), // B1
30+
part: aal::PartInfo { part_num: 0x1234, pkg_id: 1, version: 2 },
31+
disabled: aal::DisabledFeatures {
32+
pipes: 0,
33+
ports: 0,
34+
speeds: 0,
35+
mau: [0; 4],
36+
tm_mem: 0,
37+
bsync: false,
38+
pgen: false,
39+
resub: false,
40+
},
41+
frequency: aal::FrequencySettings {
42+
disabled: false,
43+
bps: 3,
44+
pps: 3,
45+
bps_ext: 0,
46+
pps_ext: 0,
47+
pcie_dis: 0,
48+
cpu_speed_dis: 0,
49+
},
50+
manufacturing: aal::ManufacturingData {
51+
voltage_scaling: 0,
52+
pmro_and_skew: 0,
53+
die_rotation: false,
54+
silent_spin: 0,
55+
wf_core_repair: false,
56+
core_repair: false,
57+
tile_repair: false,
58+
soft_pipe_dis: 0,
59+
},
60+
}
61+
}
62+
2563
/// Identifiers are used to uniquely identify an ASIC.
2664
///
2765
/// This includes identifiers the sidecar idfor the fab, lot, wafer, and
@@ -36,11 +74,19 @@ pub struct Identifiers {
3674
fab: Option<char>,
3775
/// Lot identifier.
3876
lot: Option<char>,
77+
/// Lot number (4-character identifier within the lot).
78+
///
79+
/// The 4-character size comes from the Tofino ASIC fuse layout, which
80+
/// stores lotnum as four separate character fields (lotnum0-3) in
81+
/// `tofino::fuse::ChipId`.
82+
lotnum: Option<[char; 4]>,
3983
/// Wafer number within the lot.
4084
wafer: Option<u8>,
4185
/// The wafer location as (x, y) coordinates on the wafer, represented as
4286
/// an array due to the lack of tuple support in OpenAPI.
4387
wafer_loc: Option<(i16, i16)>,
88+
/// Organized fuse data from the ASIC.
89+
fuse: Option<aal::FuseData>,
4490
}
4591

4692
impl Default for Identifiers {
@@ -50,8 +96,10 @@ impl Default for Identifiers {
5096
asic_backend: "chaos".to_string(),
5197
fab: None,
5298
lot: None,
99+
lotnum: None,
53100
wafer: None,
54101
wafer_loc: None,
102+
fuse: None,
55103
}
56104
}
57105
}
@@ -73,13 +121,21 @@ impl aal::SidecarIdentifiers for Identifiers {
73121
self.lot
74122
}
75123

124+
fn lotnum(&self) -> Option<[char; 4]> {
125+
self.lotnum
126+
}
127+
76128
fn wafer(&self) -> Option<u8> {
77129
self.wafer
78130
}
79131

80132
fn wafer_loc(&self) -> Option<(i16, i16)> {
81133
self.wafer_loc
82134
}
135+
136+
fn fuse_info(&self) -> Option<aal::FuseData> {
137+
self.fuse.clone()
138+
}
83139
}
84140

85141
/// A collections of counters reflecting the number of times each

asic/src/softnpu/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,10 @@ impl AsicOps for Handle {
379379
asic_backend: "softnpu".to_string(),
380380
fab: None,
381381
lot: None,
382+
lotnum: None,
382383
wafer: None,
383384
wafer_loc: None,
385+
fuse: Some(crate::faux_fuse_data()),
384386
})
385387
}
386388

0 commit comments

Comments
 (0)