From: Pim van Pelt Date: Wed, 13 Aug 2025 22:02:34 +0000 (+0200) Subject: vnet: add SFF8472 and SFF8636 diagnostics X-Git-Tag: v26.02-rc0~61 X-Git-Url: https://gerrit.fd.io/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F44%2F43544%2F16;p=vpp.git vnet: add SFF8472 and SFF8636 diagnostics Using device-class.eeprom_read_function, extract the type of EEPROM, and create a parser for SFF8472 (SFP+/SFP/SFP28) and SFF8636 (QSFP+/QSFP28/QSFP-DD) to show module diagnostics. When available, read EEPROM page A2h and report on DDM values: show int transceiver eeprom show int transceiver module [verbose] show int transceiver diag [verbose] Type: improvement Change-Id: Iae41b9753f31bc1a8d32b2c42d396cd743864147 Signed-off-by: pim@ipng.nl --- diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt index e3abe93da25..03dbe0370d6 100644 --- a/src/vnet/CMakeLists.txt +++ b/src/vnet/CMakeLists.txt @@ -160,6 +160,8 @@ list(APPEND VNET_SOURCES ethernet/node.c ethernet/pg.c ethernet/sfp.c + ethernet/sfp_sff8472.c + ethernet/sfp_sff8636.c ethernet/p2p_ethernet.c ethernet/p2p_ethernet_input.c ethernet/p2p_ethernet_api.c @@ -177,6 +179,8 @@ list(APPEND VNET_HEADERS ethernet/packet.h ethernet/types.def ethernet/sfp.h + ethernet/sfp_sff8472.h + ethernet/sfp_sff8636.h ethernet/p2p_ethernet.h ethernet/arp_packet.h ) diff --git a/src/vnet/ethernet/sfp.c b/src/vnet/ethernet/sfp.c index 182fdbf1d55..44de49b0969 100644 --- a/src/vnet/ethernet/sfp.c +++ b/src/vnet/ethernet/sfp.c @@ -14,6 +14,8 @@ */ #include +#include +#include static u8 * format_space_terminated (u8 * s, va_list * args) @@ -28,8 +30,8 @@ format_space_terminated (u8 * s, va_list * args) return s; } -static u8 * -format_sfp_id (u8 * s, va_list * args) +u8 * +format_sfp_id (u8 *s, va_list *args) { u32 id = va_arg (*args, u32); char *t = 0; @@ -44,6 +46,42 @@ format_sfp_id (u8 * s, va_list * args) return format (s, "%s", t); } +u8 * +format_sfp_connector (u8 *s, va_list *args) +{ + u32 connector = va_arg (*args, u32); + char *t = 0; + switch (connector) + { +#define _(v, str) \ + case v: \ + t = str; \ + break; + foreach_sfp_connector +#undef _ + default : return format (s, "unknown 0x%x", connector); + } + return format (s, "%s", t); +} + +u8 * +format_sfp_encoding (u8 *s, va_list *args) +{ + u32 encoding = va_arg (*args, u32); + char *t = 0; + switch (encoding) + { +#define _(v, str) \ + case v: \ + t = str; \ + break; + foreach_sfp_encoding +#undef _ + default : return format (s, "unknown 0x%x", encoding); + } + return format (s, "%s", t); +} + static u8 * format_sfp_compatibility (u8 * s, va_list * args) { @@ -60,8 +98,8 @@ format_sfp_compatibility (u8 * s, va_list * args) return format (s, "%s", t); } -u32 -sfp_is_comatible (sfp_eeprom_t * e, sfp_compatibility_t c) +static u32 +sfp_is_compatible (sfp_eeprom_t *e, sfp_compatibility_t c) { static struct { @@ -88,7 +126,7 @@ format_sfp_eeprom (u8 * s, va_list * args) s = format (s, "compatibility:"); for (i = 0; i < SFP_N_COMPATIBILITY; i++) - if (sfp_is_comatible (e, i)) + if (sfp_is_compatible (e, i)) s = format (s, " %U", format_sfp_compatibility, i); s = format (s, "\n%Uvendor: %U, part %U", @@ -111,6 +149,131 @@ format_sfp_eeprom (u8 * s, va_list * args) return s; } +void +sfp_eeprom_decode_base (vlib_main_t *vm, sfp_eeprom_t *se, u8 is_terse, + vnet_interface_eeprom_type_t eeprom_type) +{ + u8 vendor_name[17] = { 0 }; + u8 vendor_pn[17] = { 0 }; + u8 vendor_rev[5] = { 0 }; + u8 vendor_sn[17] = { 0 }; + u8 date_code[9] = { 0 }; + u16 wavelength; + + vlib_cli_output (vm, " Module Base Information:"); + /* Vendor information */ + clib_memcpy (vendor_name, se->vendor_name, 16); + /* Trim trailing spaces */ + for (int i = 15; i >= 0 && vendor_name[i] == ' '; i--) + vendor_name[i] = '\0'; + vlib_cli_output (vm, " Vendor Name: %s", vendor_name); + + vlib_cli_output (vm, " Vendor OUI: %02x:%02x:%02x", se->vendor_oui[0], + se->vendor_oui[1], se->vendor_oui[2]); + + clib_memcpy (vendor_pn, se->vendor_part_number, 16); + /* Trim trailing spaces */ + for (int i = 15; i >= 0 && vendor_pn[i] == ' '; i--) + vendor_pn[i] = '\0'; + vlib_cli_output (vm, " Vendor Part Number: %s", vendor_pn); + + clib_memcpy (vendor_sn, se->vendor_serial_number, 16); + /* Trim trailing spaces */ + for (int i = 15; i >= 0 && vendor_sn[i] == ' '; i--) + vendor_sn[i] = '\0'; + vlib_cli_output (vm, " Vendor Serial Number: %s", vendor_sn); + + if (is_terse) + return; + + vlib_cli_output (vm, " Identifier: 0x%02x (%U)", se->id, format_sfp_id, + se->id); + vlib_cli_output (vm, " Extended Identifier: 0x%02x", se->extended_id); + vlib_cli_output (vm, " Connector: 0x%02x (%U)", se->connector_type, + format_sfp_connector, se->connector_type); + vlib_cli_output (vm, " Encoding: 0x%02x (%U)", se->encoding, + format_sfp_encoding, se->encoding); + vlib_cli_output (vm, " Nominal Bit Rate: %u00 Mbps", + se->nominal_bit_rate_100mbits_per_sec); + + /* Length information */ + if (se->length[0]) + vlib_cli_output (vm, " Length (SMF): %u km", se->length[0]); + if (se->length[1]) + vlib_cli_output (vm, " Length (SMF): %u00 m", se->length[1]); + if (se->length[2]) + vlib_cli_output (vm, " Length (OM2 50um): %u0 m", se->length[2]); + if (se->length[3]) + vlib_cli_output (vm, " Length (OM1 62.5um): %u0 m", se->length[3]); + if (se->length[4]) + vlib_cli_output (vm, " Length (Copper/OM3): %u m", se->length[4]); + + /* NOTE(pim): SFF8472 specifies a 4 byte vendor revision followed by a 2-byte + * wavelength_or_att. However SFF8636 specifies a 2 byte vendor revision + * followed by a 2 byte wavelength_or_att, followed by a 2 byte + * wavelength_tolerance */ + if (eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8472) + { + clib_memcpy (vendor_rev, se->vendor_revision, 4); + /* Trim trailing spaces */ + for (int i = 3; i >= 0 && vendor_rev[i] == ' '; i--) + vendor_rev[i] = '\0'; + vlib_cli_output (vm, " Vendor Revision: %s", vendor_rev); + + wavelength = clib_net_to_host_u16 (se->wavelength_or_att); + if (wavelength) + vlib_cli_output (vm, " Wavelength: %u nm", wavelength); + } + if (eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8636 || + eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8436) + { + + clib_memcpy (vendor_rev, se->vendor_revision, 2); + /* Trim trailing spaces */ + for (int i = 1; i >= 0 && vendor_rev[i] == ' '; i--) + vendor_rev[i] = '\0'; + vlib_cli_output (vm, " Vendor Revision: %s", vendor_rev); + + u16 sff8636_wavelength_or_att = + (se->vendor_revision[2] << 8) + se->vendor_revision[3]; + f64 wl_nm = ((f64) sff8636_wavelength_or_att) * 0.05; + vlib_cli_output (vm, " Wavelength: %.3f nm", wl_nm); + } + + clib_memcpy (date_code, se->vendor_date_code, 8); + vlib_cli_output (vm, " Date Code: %.8s", date_code); +} + +void +sfp_eeprom_module (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + sfp_eeprom_t *se = (sfp_eeprom_t *) eeprom->eeprom_raw; + if (eeprom->eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8636 || + eeprom->eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8436) + { + se = (sfp_eeprom_t *) (eeprom->eeprom_raw + 0x80); + } + + return sfp_eeprom_decode_base (vm, se, is_terse, eeprom->eeprom_type); +} + +void +sfp_eeprom_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + switch (eeprom->eeprom_type) + { + case VNET_INTERFACE_EEPROM_TYPE_SFF8472: + return sff8472_decode_diagnostics (vm, eeprom, is_terse); + case VNET_INTERFACE_EEPROM_TYPE_SFF8436: + case VNET_INTERFACE_EEPROM_TYPE_SFF8636: + return sff8636_decode_diagnostics (vm, eeprom, is_terse); + default: + vlib_cli_output (vm, " Module Diagnostics: not availalbe"); + } +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/ethernet/sfp.h b/src/vnet/ethernet/sfp.h index f4c62aaa0ca..b19cba3171b 100644 --- a/src/vnet/ethernet/sfp.h +++ b/src/vnet/ethernet/sfp.h @@ -17,6 +17,7 @@ #define included_vnet_optics_sfp_h #include +#include #define foreach_sfp_id \ _ (UNKNOWN, "unknown") \ @@ -66,18 +67,24 @@ typedef struct u8 ext_module_codes; u8 vendor_oui[3]; u8 vendor_part_number[16]; - u8 vendor_revision[2]; - /* 16 bit value network byte order. */ - u8 wavelength_or_att[2]; - u8 wavelength_tolerance_or_att[2]; - u8 max_case_temp; - u8 cc_base; - - u8 link_codes; - u8 options[3]; + + /* NOTE: SFF8472 defines vendor revision as 4 bytes, followed by 2 bytes of + * wavelength_or_att SFF8636 defines vendor revision as 2 bytes, followed by + * u16 wavelength_or_att, then u16 wavelength_tolerance + */ + u8 vendor_revision[4]; /* SFF8636 has wavelength in vendor_revision[2+3] */ + u16 wavelength_or_att; /* SFF8472 has wavelength here; SFF8636 has + wavelength_tolerance here */ + + u8 reserved_62; /* Byte 62: Reserved */ + u8 cc_base; /* Byte 63: checksum for first 64 bytes */ + u8 option_values[2]; + u8 signalling_rate_max; + u8 signalling_rate_min; u8 vendor_serial_number[16]; u8 vendor_date_code[8]; - u8 reserved92[3]; + u8 diag_monitoring_type; /* Byte 92 */ + u8 reserved93[2]; u8 checksum_63_to_94; u8 vendor_specific[32]; u8 reserved128[384]; @@ -96,6 +103,16 @@ sfp_eeprom_is_valid (sfp_eeprom_t * e) return sum == e->cc_base; } +/* Show the EEPROM module information */ +void sfp_eeprom_module (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse); +void sfp_eeprom_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse); + +/* Base SFP EEPROM decoding function */ +void sfp_eeprom_decode_base (vlib_main_t *vm, sfp_eeprom_t *se, u8 is_terse, + vnet_interface_eeprom_type_t eeprom_type); + /* _ (byte_index, bit_index, name) */ #define foreach_sfp_compatibility \ _ (0, 0, 40g_active_cable) \ @@ -128,9 +145,58 @@ typedef enum SFP_N_COMPATIBILITY, } sfp_compatibility_t; -u32 sfp_is_comatible (sfp_eeprom_t * e, sfp_compatibility_t c); +#define foreach_sfp_encoding \ + _ (0x01, "8B/10B") \ + _ (0x02, "4B/5B") \ + _ (0x03, "NRZ") \ + _ (0x04, "4B/5B (FC-100)") \ + _ (0x05, "Manchester") \ + _ (0x06, "64B/66B") \ + _ (0x07, "256B/257B") \ + _ (0x08, "PAM4") + +typedef enum +{ +#define _(v, s) SFP_ENCODING_##v = v, + foreach_sfp_encoding +#undef _ +} sfp_encoding_t; + +#define foreach_sfp_connector \ + _ (0x01, "SC") \ + _ (0x02, "Fibre Channel Style 1 copper") \ + _ (0x03, "Fibre Channel Style 2 copper") \ + _ (0x04, "BNC/TNC") \ + _ (0x05, "Fibre Channel coaxial") \ + _ (0x06, "Fiber Jack") \ + _ (0x07, "LC") \ + _ (0x08, "MT-RJ") \ + _ (0x09, "MU") \ + _ (0x0A, "SG") \ + _ (0x0B, "Optical pigtail") \ + _ (0x0C, "MPO 1x12 Parallel Optic") \ + _ (0x0D, "MPO 2x16 Parallel Optic") \ + _ (0x20, "HSSDC II") \ + _ (0x21, "Copper pigtail") \ + _ (0x22, "RJ45") \ + _ (0x23, "No separable connector") \ + _ (0x24, "MXC 2x16") \ + _ (0x25, "CS optical connector") \ + _ (0x26, "SN optical connector") \ + _ (0x27, "MPO 2x12 Parallel Optic") \ + _ (0x28, "MPO 1x16 Parallel Optic") + +typedef enum +{ +#define _(v, s) SFP_CONNECTOR_##v = v, + foreach_sfp_connector +#undef _ +} sfp_connector_t; format_function_t format_sfp_eeprom; +format_function_t format_sfp_id; +format_function_t format_sfp_encoding; +format_function_t format_sfp_connector; #endif /* included_vnet_optics_sfp_h */ diff --git a/src/vnet/ethernet/sfp_sff8472.c b/src/vnet/ethernet/sfp_sff8472.c new file mode 100644 index 00000000000..a4467162ad3 --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8472.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +static f64 +sff8472_convert_temperature (u16 raw_temp) +{ + i16 temp = (i16) raw_temp; + return (f64) temp / 256.0; +} + +static f64 +sff8472_convert_voltage (u16 raw_voltage) +{ + return (f64) raw_voltage / 10000.0; +} + +static f64 +sff8472_convert_current (u16 raw_current) +{ + return (f64) raw_current * 2.0 / 1000.0; +} + +static f64 +sff8472_convert_power (u16 raw_power) +{ + return (f64) raw_power / 10000.0; +} + +static f64 +sff8472_mw_to_dbm (f64 power_mw) +{ + if (power_mw <= 0.0) + return -40.0; /* Use -40 dBm for zero/negative power to avoid log(0) */ + return 10.0 * log10 (power_mw); +} + +static f64 +sff8472_ieee754_to_f64 (u32 ieee754_be) +{ + union + { + u32 u; + f32 f; + } converter; + converter.u = clib_net_to_host_u32 (ieee754_be); + return (f64) converter.f; +} + +static void +sff8472_apply_calibration (sff8472_diag_t *diag, f64 *temp, f64 *vcc, + f64 *tx_bias, f64 *tx_power, f64 *rx_power) +{ + f64 temp_slope, temp_offset; + f64 vcc_slope, vcc_offset; + f64 bias_slope, bias_offset; + f64 txpwr_slope, txpwr_offset; + f64 rx_raw; + f64 rxpwr_cal[5]; + int i; + + /* Extract calibration constants */ + temp_slope = (f64) clib_net_to_host_u16 (diag->temp_slope) / 256.0; + temp_offset = (f64) (i16) clib_net_to_host_u16 (diag->temp_offset); + *temp = (*temp * temp_slope) + temp_offset; + + vcc_slope = (f64) clib_net_to_host_u16 (diag->voltage_slope) / 256.0; + vcc_offset = (f64) (i16) clib_net_to_host_u16 (diag->voltage_offset); + *vcc = (*vcc * vcc_slope) + vcc_offset; + + bias_slope = (f64) clib_net_to_host_u16 (diag->tx_bias_slope) / 256.0; + bias_offset = (f64) (i16) clib_net_to_host_u16 (diag->tx_bias_offset); + *tx_bias = (*tx_bias * bias_slope) + bias_offset; + + txpwr_slope = (f64) clib_net_to_host_u16 (diag->tx_power_slope) / 256.0; + txpwr_offset = (f64) (i16) clib_net_to_host_u16 (diag->tx_power_offset); + *tx_power = (*tx_power * txpwr_slope) + txpwr_offset; + + /* Apply polynomial calibration for RX Power + * SFF-8472 section 9.3 External Calibration + */ + for (i = 0; i < 5; i++) + { + rxpwr_cal[i] = sff8472_ieee754_to_f64 (diag->rx_power_cal[i]); + } + + rx_raw = *rx_power; + *rx_power = rxpwr_cal[0] + (rx_raw * rxpwr_cal[1]) + + (rx_raw * rx_raw * rxpwr_cal[2]) + + (rx_raw * rx_raw * rx_raw * rxpwr_cal[3]) + + (rx_raw * rx_raw * rx_raw * rx_raw * rxpwr_cal[4]); +} + +void +sff8472_decode_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + f64 temp, vcc, tx_bias, tx_power, rx_power; + f64 temp_high_alarm, temp_low_alarm, temp_high_warn, temp_low_warn; + f64 vcc_high_alarm, vcc_low_alarm, vcc_high_warn, vcc_low_warn; + f64 bias_high_alarm, bias_low_alarm, bias_high_warn, bias_low_warn; + f64 tx_power_high_alarm, tx_power_low_alarm, tx_power_high_warn, + tx_power_low_warn; + f64 rx_power_high_alarm, rx_power_low_alarm, rx_power_high_warn, + rx_power_low_warn; + + vlib_cli_output (vm, " Module Diagnostics:"); + if (eeprom->eeprom_len <= 256) + { + vlib_cli_output (vm, " Not supported (no A2h data)"); + return; + } + + u8 *eeprom_data_a2 = eeprom->eeprom_raw; + /* Check if we have A0h+A2h (512 bytes) */ + if (eeprom->eeprom_len >= 512) + { + eeprom_data_a2 = eeprom->eeprom_raw + 256; /* A2 starts at byte 256 */ + } + + sff8472_diag_t *diag = (sff8472_diag_t *) eeprom_data_a2; + temp = + sff8472_convert_temperature (clib_net_to_host_u16 (diag->temperature)); + vcc = sff8472_convert_voltage (clib_net_to_host_u16 (diag->vcc)); + tx_bias = sff8472_convert_current (clib_net_to_host_u16 (diag->tx_bias)); + tx_power = sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power)); + rx_power = sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power)); + + /* Check if external calibration is required (A0 page byte 92, bit 4) */ + if (eeprom->eeprom_len >= 512 && (eeprom->eeprom_raw[92] & 0x10)) + { + sff8472_apply_calibration (diag, &temp, &vcc, &tx_bias, &tx_power, + &rx_power); + } + + vlib_cli_output (vm, " Current Values:"); + vlib_cli_output (vm, " Temperature: %.2f °C", temp); + vlib_cli_output (vm, " Supply Voltage: %.4f V", vcc); + vlib_cli_output (vm, " TX Bias Current: %.2f mA", tx_bias); + vlib_cli_output (vm, " TX Average Power: %.4f mW (%.2f dBm)", tx_power, + sff8472_mw_to_dbm (tx_power)); + vlib_cli_output (vm, " RX Average Power: %.4f mW (%.2f dBm)", rx_power, + sff8472_mw_to_dbm (rx_power)); + + if (is_terse || eeprom->eeprom_len <= 256 + sizeof (sff8472_diag_t)) + { + return; + } + + temp_high_alarm = + sff8472_convert_temperature (clib_net_to_host_u16 (diag->temp_high_alarm)); + temp_low_alarm = + sff8472_convert_temperature (clib_net_to_host_u16 (diag->temp_low_alarm)); + temp_high_warn = sff8472_convert_temperature ( + clib_net_to_host_u16 (diag->temp_high_warning)); + temp_low_warn = sff8472_convert_temperature ( + clib_net_to_host_u16 (diag->temp_low_warning)); + + vcc_high_alarm = + sff8472_convert_voltage (clib_net_to_host_u16 (diag->voltage_high_alarm)); + vcc_low_alarm = + sff8472_convert_voltage (clib_net_to_host_u16 (diag->voltage_low_alarm)); + vcc_high_warn = sff8472_convert_voltage ( + clib_net_to_host_u16 (diag->voltage_high_warning)); + vcc_low_warn = + sff8472_convert_voltage (clib_net_to_host_u16 (diag->voltage_low_warning)); + + bias_high_alarm = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_high_alarm)); + bias_low_alarm = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_low_alarm)); + bias_high_warn = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_high_warning)); + bias_low_warn = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_low_warning)); + + tx_power_high_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_high_alarm)); + tx_power_low_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_low_alarm)); + tx_power_high_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_high_warning)); + tx_power_low_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_low_warning)); + + rx_power_high_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_high_alarm)); + rx_power_low_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_low_alarm)); + rx_power_high_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_high_warning)); + rx_power_low_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_low_warning)); + + vlib_cli_output (vm, " Alarm Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_alarm, temp_low_alarm); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_alarm, vcc_low_alarm); + vlib_cli_output (vm, " Bias Current High: %.2f mA, Low: %.2f mA", + bias_high_alarm, bias_low_alarm); + vlib_cli_output ( + vm, " TX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + tx_power_high_alarm, sff8472_mw_to_dbm (tx_power_high_alarm), + tx_power_low_alarm, sff8472_mw_to_dbm (tx_power_low_alarm)); + vlib_cli_output ( + vm, " RX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + rx_power_high_alarm, sff8472_mw_to_dbm (rx_power_high_alarm), + rx_power_low_alarm, sff8472_mw_to_dbm (rx_power_low_alarm)); + + vlib_cli_output (vm, " Warning Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_warn, temp_low_warn); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_warn, vcc_low_warn); + vlib_cli_output (vm, " Bias Current High: %.2f mA, Low: %.2f mA", + bias_high_warn, bias_low_warn); + vlib_cli_output ( + vm, " TX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + tx_power_high_warn, sff8472_mw_to_dbm (tx_power_high_warn), + tx_power_low_warn, sff8472_mw_to_dbm (tx_power_low_warn)); + vlib_cli_output ( + vm, " RX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + rx_power_high_warn, sff8472_mw_to_dbm (rx_power_high_warn), + rx_power_low_warn, sff8472_mw_to_dbm (rx_power_low_warn)); +} diff --git a/src/vnet/ethernet/sfp_sff8472.h b/src/vnet/ethernet/sfp_sff8472.h new file mode 100644 index 00000000000..e54005a3cf4 --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8472.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_ethernet_sff8472_h__ +#define __included_ethernet_sff8472_h__ + +#include +#include +#include + +/* SFF-8472 A2 page - Diagnostic fields */ +typedef struct +{ + /* Alarm and warning thresholds (bytes 0-55) */ + u16 temp_high_alarm; /* 0-1: Temperature high alarm */ + u16 temp_low_alarm; /* 2-3: Temperature low alarm */ + u16 temp_high_warning; /* 4-5: Temperature high warning */ + u16 temp_low_warning; /* 6-7: Temperature low warning */ + u16 voltage_high_alarm; /* 8-9: Voltage high alarm */ + u16 voltage_low_alarm; /* 10-11: Voltage low alarm */ + u16 voltage_high_warning; /* 12-13: Voltage high warning */ + u16 voltage_low_warning; /* 14-15: Voltage low warning */ + u16 bias_high_alarm; /* 16-17: Bias high alarm */ + u16 bias_low_alarm; /* 18-19: Bias low alarm */ + u16 bias_high_warning; /* 20-21: Bias high warning */ + u16 bias_low_warning; /* 22-23: Bias low warning */ + u16 tx_power_high_alarm; /* 24-25: TX power high alarm */ + u16 tx_power_low_alarm; /* 26-27: TX power low alarm */ + u16 tx_power_high_warning; /* 28-29: TX power high warning */ + u16 tx_power_low_warning; /* 30-31: TX power low warning */ + u16 rx_power_high_alarm; /* 32-33: RX power high alarm */ + u16 rx_power_low_alarm; /* 34-35: RX power low alarm */ + u16 rx_power_high_warning; /* 36-37: RX power high warning */ + u16 rx_power_low_warning; /* 38-39: RX power low warning */ + u8 reserved_40_55[16]; /* 40-55: Reserved/Other fields */ + /* Calibration constants for external calibration (bytes 56-95) */ + u32 rx_power_cal[5]; /* 56-75: RX power calibration coefficients (IEEE 754 + float) */ + u16 tx_bias_slope; /* 76-77: TX bias slope calibration */ + u16 tx_bias_offset; /* 78-79: TX bias offset calibration */ + u16 tx_power_slope; /* 80-81: TX power slope calibration */ + u16 tx_power_offset; /* 82-83: TX power offset calibration */ + u16 temp_slope; /* 84-85: Temperature slope calibration */ + u16 temp_offset; /* 86-87: Temperature offset calibration */ + u16 voltage_slope; /* 88-89: Voltage slope calibration */ + u16 voltage_offset; /* 90-91: Voltage offset calibration */ + u8 reserved_92_95[4]; /* 92-95: Reserved */ + /* Real-time diagnostic values (bytes 96-105) */ + u16 temperature; /* 96-97: Temperature */ + u16 vcc; /* 98-99: Supply voltage */ + u16 tx_bias; /* 100-101: TX bias current */ + u16 tx_power; /* 102-103: TX average optical power */ + u16 rx_power; /* 104-105: RX average optical power */ + u8 reserved_106_109[4]; /* 106-109: Reserved */ + u8 status_control[2]; /* 110-111: Status/Control */ + u8 alarm1; /* 112: Temp, VCC, TX Bias, TX Power alarms */ + u8 alarm2; /* 113: Rx, Laser Temp, TEC current alarms */ + u8 tx_input_equalization; /* 114: Tx Input equalization HIGH / LOW */ + u8 rx_output_emphasis; /* 115: Rx output emphasis HIGH / LOW */ + u8 warning1; /* 116: Temp, VCC, TX Bias, TX power warnings */ + u8 warning2; /* 117: Rx, Laser Temp, TEC current warning */ + u8 emc_status[2]; /* 118-119: Extended Module Control status */ + u8 reserved_120_126[7]; /* 120-126: Vendor specific Locations */ + u8 page_select; /* 127: Page Select */ +} sff8472_diag_t; + +STATIC_ASSERT (sizeof (sff8472_diag_t) == 128, + "sff8472_diag_t must be 128 bytes"); + +/* Function declarations */ +void sff8472_decode_diagnostics (vlib_main_t *vm, + vnet_interface_eeprom_t *eeprom, u8 is_terse); + +#endif /* __included_ethernet_sff8472_h__ */ diff --git a/src/vnet/ethernet/sfp_sff8636.c b/src/vnet/ethernet/sfp_sff8636.c new file mode 100644 index 00000000000..7c13dd99ed3 --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8636.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +static f64 +sff8636_convert_temperature (u16 raw_temp) +{ + i16 temp = (i16) raw_temp; + return (f64) temp / 256.0; +} + +static f64 +sff8636_convert_voltage (u16 raw_voltage) +{ + /* SFF-8636: 100 µV per LSB */ + return (f64) raw_voltage / 10000.0; +} + +static f64 +sff8636_convert_current (u16 raw_current) +{ + /* SFF-8636: 2 µA per LSB */ + return (f64) raw_current * 2.0 / 1000.0; +} + +static f64 +sff8636_convert_power (u16 raw_power) +{ + /* SFF-8636: 0.1 µW per LSB */ + return (f64) raw_power * 0.1 / 1000.0; +} + +static f64 +sff8636_mw_to_dbm (f64 power_mw) +{ + if (power_mw <= 0.0) + return -40.0; /* Use -40 dBm for zero/negative power to avoid log(0) */ + return 10.0 * log10 (power_mw); +} + +void +sff8636_decode_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + f64 temp, vcc; + f64 temp_high_alarm, temp_low_alarm, temp_high_warn, temp_low_warn; + f64 vcc_high_alarm, vcc_low_alarm, vcc_high_warn, vcc_low_warn; + int i; + + vlib_cli_output (vm, " Module Diagnostics:"); + + if (eeprom->eeprom_len < sizeof (sff8636_eeprom_t)) + { + vlib_cli_output (vm, " Not supported (insufficient data)"); + return; + } + + sff8636_eeprom_t *diag = (sff8636_eeprom_t *) eeprom->eeprom_raw; + temp = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temperature)); + vcc = sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc)); + + vlib_cli_output (vm, " Current Values:"); + vlib_cli_output (vm, " Temperature: %.2f °C", temp); + vlib_cli_output (vm, " Supply Voltage: %.4f V", vcc); + + /* Per-lane values */ + for (i = 0; i < 4; i++) + { + f64 tx_bias, tx_power, rx_power; + tx_bias = + sff8636_convert_current (clib_net_to_host_u16 (diag->tx_bias[i])); + rx_power = + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power[i])); + tx_power = + sff8636_convert_power (clib_net_to_host_u16 (diag->tx_power[i])); + + vlib_cli_output (vm, " Lane %d:", i + 1); + vlib_cli_output (vm, " TX Bias Current: %.2f mA", tx_bias); + vlib_cli_output (vm, " TX Average Power: %.4f mW (%.2f dBm)", + tx_power, sff8636_mw_to_dbm (tx_power)); + vlib_cli_output (vm, " RX Average Power: %.4f mW (%.2f dBm)", + rx_power, sff8636_mw_to_dbm (rx_power)); + } + + if (is_terse) + { + return; + } + + /* Temperature thresholds at bytes 512-519 */ + temp_high_alarm = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_high_alarm)); + temp_low_alarm = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_low_alarm)); + temp_high_warn = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_high_warn)); + temp_low_warn = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_low_warn)); + + /* Voltage thresholds at bytes 520-527 */ + vcc_high_alarm = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_high_alarm)); + vcc_low_alarm = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_low_alarm)); + vcc_high_warn = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_high_warn)); + vcc_low_warn = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_low_warn)); + + vlib_cli_output (vm, ""); + vlib_cli_output (vm, " Alarm Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_alarm, temp_low_alarm); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_alarm, vcc_low_alarm); + + vlib_cli_output ( + vm, " Bias Current High: %.2f mA, Low: %.2f mA", + sff8636_convert_current (clib_net_to_host_u16 (diag->tx_bias_high_alarm)), + sff8636_convert_current (clib_net_to_host_u16 (diag->tx_bias_low_alarm))); + vlib_cli_output ( + vm, + " TX Power High: %.4f mW (%.2f dBm), Low: " + "%.4f mW (%.2f dBm)", + sff8636_convert_power (clib_net_to_host_u16 (diag->tx_power_high_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->tx_power_high_alarm))), + sff8636_convert_power (clib_net_to_host_u16 (diag->tx_power_low_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->tx_power_low_alarm)))); + vlib_cli_output ( + vm, + " RX Power High: %.4f mW (%.2f dBm), Low: " + "%.4f mW (%.2f dBm)", + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_high_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->rx_power_high_alarm))), + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_low_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->rx_power_low_alarm)))); + + vlib_cli_output (vm, ""); + vlib_cli_output (vm, " Warning Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_warn, temp_low_warn); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_warn, vcc_low_warn); + vlib_cli_output ( + vm, + " RX Power High: %.4f mW (%.2f dBm), Low: " + "%.4f mW (%.2f dBm)", + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_high_warn)), + sff8636_mw_to_dbm ( + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_high_warn))), + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_low_warn)), + sff8636_mw_to_dbm ( + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_low_warn)))); +} diff --git a/src/vnet/ethernet/sfp_sff8636.h b/src/vnet/ethernet/sfp_sff8636.h new file mode 100644 index 00000000000..9adf90df561 --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8636.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_ethernet_sff8636_h__ +#define __included_ethernet_sff8636_h__ + +#include +#include + +/* SFF-8636 is a 640 byte EEPROM. + * Any SFF-8436-compliant module will generally respond correctly to an + * SFF-8636 reader, because the lower and upper Page 00h structures are largely + * preserved. + */ + +typedef struct +{ + u8 identifier; // Byte 0 + u8 status[2]; // Bytes 1-2 (see Table 6-2) + u8 interrupt_flags[19]; // Bytes 3-21 (Tables 6-4, 6-5, 6-6) + + // ---------------- Device Free Side Monitors ---------------- + u16 temperature; // Bytes 22-23 (1/256 °C per LSB) + u8 reserved_fsm[2]; // Bytes 24-25 (vendor-specific / reserved) + u16 vcc; // Bytes 26-27 (100 µV per LSB) + u8 reserved_fsm2[6]; // Bytes 28-33 (vendor-specific / reserved) + + // ---------------- Per-lane channel monitors ---------------- + // Lane 1–4, each has 4 bytes: TX bias, TX power, RX power, reserved + u16 rx_power[4]; // Bytes 34-41 (2 bytes per lane, 0.1 µW scaling) + u16 tx_bias[4]; // Bytes 42-49 (2 bytes per lane, µA scaling) + u16 tx_power[4]; // Bytes 50-57 (2 bytes per lane, 0.1 µW scaling) + u16 reserved_lane[4]; // Bytes 58-65 (reserved/future use) + u8 reserved_66_85[20]; // Bytes 66-85 + u8 control[13]; // Bytes 86-98 (Table 6-9; includes App Select bytes per + // Table 6-12) + u8 reserved_99; // Byte 99 + u8 hw_int_mask[7]; // Bytes 100-106 (Table 6-14) + u8 reserved_107; // Byte 107 + u8 device_props[7]; // Bytes 108-114 (Table 6-15; incl. PCIe use at 111-112 + // per Fig 6-1) + u8 reserved_115_118[4]; // Bytes 115-118 + u8 password_change[4]; // Bytes 119-122 (optional, write-only in device) + u8 password_entry[4]; // Bytes 123-126 (optional, write-only in device) + u8 page_select; // Byte 127 (Page Select) + + // 128–255 (Base ID portion) + u8 base_id[128]; // this is the standard sfp_eeprom_t at offset 0x80 + + // 256-511 (page01, page02) + u8 reserved_page01[128]; // 256-383 + u8 reserved_page02[128]; // 384-511 + + // 512–559 : Free-Side Device Thresholds and Channel Thresholds + // Typically: temp high alarm/warn, low warn/alarm; Vcc high/low thresholds, + // etc. + u16 temp_high_alarm; // 512-513 (0.00390625 °C/LSB) + u16 temp_low_alarm; // 514-515 + u16 temp_high_warn; // 516-517 + u16 temp_low_warn; // 518-519 + u8 reserved_fsdct[8]; // 520-527 + + u16 vcc_high_alarm; // 528-529 (100 µV/LSB) + u16 vcc_low_alarm; // 530-531 + u16 vcc_high_warn; // 532-533 + u16 vcc_low_warn; // 534-535 + u8 reserved_fsdct2[8]; // 536-543 + + u8 reserved_device_thresh[16]; // 544-559 + + // 560–607 : Channel Thresholds (48 bytes) + // Per-channel: TX bias, TX power, RX power alarms/warns + u16 rx_power_high_alarm; // 560-56 (0.1 µW/LSB) + u16 rx_power_low_alarm; // 562-563 + u16 rx_power_high_warn; // 564-565 + u16 rx_power_low_warn; // 566-567 + + u16 tx_bias_high_alarm; // 568-569 (µA/LSB) + u16 tx_bias_low_alarm; // 570-571 + u16 tx_bias_high_warn; // 572-573 + u16 tx_bias_low_warn; // 574-575 + // + u16 tx_power_high_alarm; // 576-577 (0.1 µW/LSB) + u16 tx_power_low_alarm; // 578-579 + u16 tx_power_high_warn; // 580-581 + u16 tx_power_low_warn; // 582-583 + + u8 reserved_ct[24]; // 584-607 + + // 608–613 : TX EQ / RX Output / Temp Control (6 bytes) + u8 tx_eq_settings[4]; // 608-611 (per-channel EQ/emphasis) + u8 rx_output_settings; // 612 + u8 temp_control_settings; // 613 + + // 614–625 : Channel Controls (12 bytes) + u8 channel_controls[12]; // 614-625 (enable, polarity, squelch, etc.) + + // 626–635 : Channel Monitor Masks (10 bytes) + u8 channel_monitor_masks[10]; // 626-635 (interrupt mask bits per + // channel/parameter) + + // 636–639 : Reserved + u8 reserved_636_639[4]; // 636-639 +} sff8636_eeprom_t; + +STATIC_ASSERT (sizeof (sff8636_eeprom_t) == 640, + "sff8636_eeprom_t must be 640 bytes"); + +/* Function declarations */ +void sff8636_decode_diagnostics (vlib_main_t *vm, + vnet_interface_eeprom_t *eeprom, u8 is_terse); + +#endif /* __included_ethernet_sff8636_h__ */ diff --git a/src/vnet/interface.c b/src/vnet/interface.c index 5fb2ff65fa2..b6c48dc28f2 100644 --- a/src/vnet/interface.c +++ b/src/vnet/interface.c @@ -1794,6 +1794,24 @@ default_build_rewrite (vnet_main_t * vnm, return (NULL); } +u8 * +format_vnet_interface_eeprom_type (u8 *s, va_list *args) +{ + u32 eeprom_type = va_arg (*args, u32); + char *t = 0; + switch (eeprom_type) + { +#define _(n, v, str) \ + case v: \ + t = str; \ + break; + foreach_vnet_interface_eeprom_type +#undef _ + default : return format (s, "unknown 0x%x", eeprom_type); + } + return format (s, "%s", t); +} + void default_update_adjacency (vnet_main_t * vnm, u32 sw_if_index, u32 ai) { diff --git a/src/vnet/interface.h b/src/vnet/interface.h index c9778f9bac4..93df4f096d8 100644 --- a/src/vnet/interface.h +++ b/src/vnet/interface.h @@ -98,10 +98,25 @@ typedef clib_error_t *(vnet_interface_rss_queues_set_t) (struct vnet_main_t * vnm, struct vnet_hw_interface_t * hi, clib_bitmap_t * bitmap); +/* Interface EEPROM types */ +#define foreach_vnet_interface_eeprom_type \ + _ (UNKNOWN, 0x00, "unknown") \ + _ (SFF8079, 0x01, "SFF-8079") \ + _ (SFF8472, 0x02, "SFF-8472") \ + _ (SFF8636, 0x03, "SFF-8636") \ + _ (SFF8436, 0x04, "SFF-8436") + +typedef enum +{ +#define _(n, v, s) VNET_INTERFACE_EEPROM_TYPE_##n = v, + foreach_vnet_interface_eeprom_type +#undef _ +} vnet_interface_eeprom_type_t; + /* EEPROM structure for physical network devices */ typedef struct { - u32 eeprom_type; /* from linux/ethtool.h */ + vnet_interface_eeprom_type_t eeprom_type; /* from linux/ethtool.h */ u32 eeprom_len; u8 eeprom_raw[1024]; } vnet_interface_eeprom_t; @@ -1137,6 +1152,8 @@ typedef struct int vnet_pcap_dispatch_trace_configure (vnet_pcap_dispatch_trace_args_t *); +u8 *format_vnet_interface_eeprom_type (u8 *s, va_list *args); + extern vlib_node_registration_t vnet_interface_output_node; extern vlib_node_registration_t vnet_interface_output_arc_end_node; diff --git a/src/vnet/interface_cli.c b/src/vnet/interface_cli.c index 4b1828dc850..b49ad24c0de 100644 --- a/src/vnet/interface_cli.c +++ b/src/vnet/interface_cli.c @@ -56,6 +56,7 @@ #include #include #include +#include static int compare_interface_names (void *a1, void *a2) @@ -2689,6 +2690,8 @@ show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, } vlib_cli_output (vm, "Interface: %v", hi->name); + vlib_cli_output (vm, " EEPROM Type: 0x%02x (%U)", eeprom->eeprom_type, + format_vnet_interface_eeprom_type, eeprom->eeprom_type); /* Default to module if none are set */ if (!show_module && !show_diag && !show_eeprom) @@ -2696,7 +2699,6 @@ show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, if (show_eeprom) { - vlib_cli_output (vm, " EEPROM Type: 0x%x", eeprom->eeprom_type); vlib_cli_output (vm, " EEPROM Length: %u bytes", eeprom->eeprom_len); vlib_cli_output (vm, " EEPROM Data:"); @@ -2739,12 +2741,12 @@ show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, if (show_module) { - vlib_cli_output (vm, " module: not implemented yet"); + sfp_eeprom_module (vm, eeprom, is_terse); } if (show_diag) { - vlib_cli_output (vm, " diag: not implemented yet"); + sfp_eeprom_diagnostics (vm, eeprom, is_terse); } done: