diff --git a/sonic_platform_base/sonic_xcvr/api/public/sff8636.py b/sonic_platform_base/sonic_xcvr/api/public/sff8636.py index 800367581..655e35f87 100644 --- a/sonic_platform_base/sonic_xcvr/api/public/sff8636.py +++ b/sonic_platform_base/sonic_xcvr/api/public/sff8636.py @@ -126,6 +126,51 @@ def get_transceiver_status_flags(self): return trans_status_flags + def get_transceiver_dom_flags(self): + """ + Retrieves the DOM flags for this xcvr + + Reads the clear on read latched free side monitor interrupt flags. + Refer to: SFF-8636 Rev 2.12 Table 6-6, lower page 00h bytes 6-7 + + Field names match the CMIS get_transceiver_dom_flags keys, so consumers of the + TRANSCEIVER_DOM_FLAG table see the same key names across module + types. + + Every flag in Table 6-6 is optional (only L-Temp High Alarm is + required, and only for separable modules), and the spec provides no + per-flag advertisement. We gate each flag group behind its associated monitor. + + A group's keys are omitted rather than reported as False whenever its + data is not trustworthy: the monitor is not advertised, the + advertisement could not be read, or the flag byte itself could not be + read. Consumers render an absent flag as N/A, which is the honest + answer in all three cases. + + Each flag byte is a single field whose RegBitField children are + decoded from one read (bitdecode), so the clear-on-read latch is + consumed exactly once per byte and the read already returns the + {flag name: bool} dict in the CMIS key schema. + + Returns: + Dictionary of boolean flags, containing only the groups that + yielded trustworthy data, and empty if neither did -- in which + case xcvrd posts no DOM flags for the port + """ + dom_flags = {} + + if self.get_temperature_support(): + temp_flags = self.xcvr_eeprom.read(consts.TEMP_FLAGS_FIELD) + if temp_flags is not None: + dom_flags.update(temp_flags) + + if self.get_voltage_support(): + vcc_flags = self.xcvr_eeprom.read(consts.VCC_FLAGS_FIELD) + if vcc_flags is not None: + dom_flags.update(vcc_flags) + + return dom_flags + def get_transceiver_dom_real_value(self): """ Retrieves DOM sensor values for this transceiver diff --git a/sonic_platform_base/sonic_xcvr/fields/consts.py b/sonic_platform_base/sonic_xcvr/fields/consts.py index c4881da34..6a6ced94b 100644 --- a/sonic_platform_base/sonic_xcvr/fields/consts.py +++ b/sonic_platform_base/sonic_xcvr/fields/consts.py @@ -44,6 +44,7 @@ TEMP_LOW_ALARM_FIELD = "TempLowAlarm" TEMP_HIGH_WARNING_FIELD = "TempHighWarning" TEMP_LOW_WARNING_FIELD = "TempLowWarning" +TEMP_FLAGS_FIELD = "TempFlags" THRESHOLDS_FIELD = "Thresholds" @@ -78,6 +79,7 @@ VOLTAGE_FIELD = "Voltage" VOLTAGE_SUPPORT_FIELD = "Supply Voltage Monitoring Implemented" VOLTAGE_THRESHOLDS_FIELD = "VoltageThresholds" +VCC_FLAGS_FIELD = "VccFlags" VOLTAGE_HIGH_ALARM_FIELD = "VoltageHighAlarm" VOLTAGE_LOW_ALARM_FIELD = "VoltageLowAlarm" VOLTAGE_HIGH_WARNING_FIELD = "VoltageHighWarning" diff --git a/sonic_platform_base/sonic_xcvr/mem_maps/public/sff8636.py b/sonic_platform_base/sonic_xcvr/mem_maps/public/sff8636.py index af5c30731..b69075e56 100644 --- a/sonic_platform_base/sonic_xcvr/mem_maps/public/sff8636.py +++ b/sonic_platform_base/sonic_xcvr/mem_maps/public/sff8636.py @@ -100,6 +100,31 @@ def __init__(self, codes): for channel, bitpos in zip(range(1, 5), range(0, 4))) ) + # Latched free side monitor interrupt flag bytes (SFF-8636 + # Rev 2.12 Table 6-6): byte 6 holds temperature alarm/warning + # flags, byte 7 holds supply voltage alarm/warning flags. The + # latches clear on read, so each byte is read whole and decoded + # into its four flags in one access (bitdecode); the bit names + # match the CMIS TRANSCEIVER_DOM_FLAG keys. In byte 6, bits 3-2 + # are reserved and bits 1-0 are the TC readiness and + # initialization complete flags; in byte 7, bits 3-0 are + # reserved. + self.TEMP_FLAGS = NumberRegField(consts.TEMP_FLAGS_FIELD, self.get_addr(0, 6), + RegBitField("tempHAlarm", 7), + RegBitField("tempLAlarm", 6), + RegBitField("tempHWarn", 5), + RegBitField("tempLWarn", 4), + size=1, bitdecode=True + ) + + self.VCC_FLAGS = NumberRegField(consts.VCC_FLAGS_FIELD, self.get_addr(0, 7), + RegBitField("vccHAlarm", 7), + RegBitField("vccLAlarm", 6), + RegBitField("vccHWarn", 5), + RegBitField("vccLWarn", 4), + size=1, bitdecode=True + ) + self.TX_DISABLE = NumberRegField(consts.TX_DISABLE_FIELD, self.get_addr(0, 86), *(RegBitField("Tx%dDisable" % channel, bitpos, ro=False) for channel, bitpos in zip(range(1, 5), range(0, 4))), diff --git a/tests/sonic_xcvr/test_sff8636.py b/tests/sonic_xcvr/test_sff8636.py index 67133e4d3..8baa44455 100644 --- a/tests/sonic_xcvr/test_sff8636.py +++ b/tests/sonic_xcvr/test_sff8636.py @@ -257,6 +257,167 @@ def test_get_transceiver_status_flags(self, mock_response, expected): result = self.api.get_transceiver_status_flags() assert result == expected + # SFF-8636 Rev 2.12 Table 6-6 bit layout, shared by both flag bytes: + # bit 7 = L-High Alarm + # bit 6 = L-Low Alarm + # bit 5 = L-High Warning + # bit 4 = L-Low Warning + # Byte 6 bits 3-2 are reserved and bits 1-0 are TC readiness / + # initialization complete; byte 7 bits 3-0 are reserved. + @pytest.mark.parametrize( + "temp_support, vcc_support, eeprom, expected, expected_reads", + [ + ( + # both monitors advertised (byte 220 bits 5 and 4 set) + True, True, + { + consts.TEMP_FLAGS_FIELD: 0b1010_0000, # high alarm + high warning + consts.VCC_FLAGS_FIELD: 0b0101_0000, # low alarm + low warning + }, + { + "tempHAlarm": True, + "tempLAlarm": False, + "tempHWarn": True, + "tempLWarn": False, + "vccHAlarm": False, + "vccLAlarm": True, + "vccHWarn": False, + "vccLWarn": True, + }, + [consts.TEMP_FLAGS_FIELD, consts.VCC_FLAGS_FIELD], + ), + ( + # both monitors advertised, no flags asserted: 0x00 is a real + # "no excursion" result and must still report all eight keys + True, True, + {consts.TEMP_FLAGS_FIELD: 0b0000_0000, consts.VCC_FLAGS_FIELD: 0b0000_0000}, + { + "tempHAlarm": False, + "tempLAlarm": False, + "tempHWarn": False, + "tempLWarn": False, + "vccHAlarm": False, + "vccLAlarm": False, + "vccHWarn": False, + "vccLWarn": False, + }, + [consts.TEMP_FLAGS_FIELD, consts.VCC_FLAGS_FIELD], + ), + ( + # temperature monitoring not implemented (byte 220 bit 5 clear): + # the temp flag byte must not be read or reported at all + False, True, + {consts.VCC_FLAGS_FIELD: 0b1000_0000}, + { + "vccHAlarm": True, + "vccLAlarm": False, + "vccHWarn": False, + "vccLWarn": False, + }, + [consts.VCC_FLAGS_FIELD], + ), + ( + # supply voltage monitoring not implemented (byte 220 bit 4 clear) + True, False, + {consts.TEMP_FLAGS_FIELD: 0b0001_0000}, + { + "tempHAlarm": False, + "tempLAlarm": False, + "tempHWarn": False, + "tempLWarn": True, + }, + [consts.TEMP_FLAGS_FIELD], + ), + # neither monitor implemented (e.g. a copper cable): nothing is + # read and nothing is claimed, so xcvrd posts no DOM flags + (False, False, {}, {}, []), + # EEPROM read failure of a flag byte drops only that group; the + # other group is still reported and the absent keys render as N/A + # rather than as a False that was never measured + ( + True, True, + {consts.TEMP_FLAGS_FIELD: None, consts.VCC_FLAGS_FIELD: 0}, + { + "vccHAlarm": False, + "vccLAlarm": False, + "vccHWarn": False, + "vccLWarn": False, + }, + [consts.TEMP_FLAGS_FIELD, consts.VCC_FLAGS_FIELD], + ), + ( + True, True, + {consts.TEMP_FLAGS_FIELD: 0, consts.VCC_FLAGS_FIELD: None}, + { + "tempHAlarm": False, + "tempLAlarm": False, + "tempHWarn": False, + "tempLWarn": False, + }, + [consts.TEMP_FLAGS_FIELD, consts.VCC_FLAGS_FIELD], + ), + # read failure of the monitor advertisement itself is treated as + # "not implemented": that group is skipped, its flag byte is never + # read, and both cases render as N/A, so an unreadable + # advertisement cannot be mistaken for a measured in-limits result + ( + None, True, + {consts.VCC_FLAGS_FIELD: 0b0000_0000}, + { + "vccHAlarm": False, + "vccLAlarm": False, + "vccHWarn": False, + "vccLWarn": False, + }, + [consts.VCC_FLAGS_FIELD], + ), + ( + # a latched temperature alarm survives an unrelated failure of + # the voltage advertisement: no group is discarded on account + # of another group's failure + True, None, + {consts.TEMP_FLAGS_FIELD: 0b1000_0000}, + { + "tempHAlarm": True, + "tempLAlarm": False, + "tempHWarn": False, + "tempLWarn": False, + }, + [consts.TEMP_FLAGS_FIELD], + ), + ], + ) + def test_get_transceiver_dom_flags(self, temp_support, vcc_support, eeprom, + expected, expected_reads): + self.api.get_temperature_support = MagicMock(return_value=temp_support) + self.api.get_voltage_support = MagicMock(return_value=vcc_support) + + # Key the mock on the field name rather than call order, so a swapped + # or mis-mapped field would fail instead of silently passing. Raw + # bytes are decoded through the real mem map field so the bitdecode + # bit positions are exercised, not just the API plumbing. + def read_field(field): + raw = eeprom[field] + if raw is None: + return None + return self.mem_map.get_field(field).decode(bytearray([raw])) + self.api.xcvr_eeprom.read = MagicMock(side_effect=read_field) + + result = self.api.get_transceiver_dom_flags() + + assert result == expected + # The flag latches clear on read: each advertised byte must be read + # exactly once per call, in a single whole-byte access, and a byte + # whose monitor is not advertised must not be read at all. + assert [c.args[0] for c in self.api.xcvr_eeprom.read.call_args_list] == expected_reads + + def test_dom_flag_fields_map_to_table_6_6_bytes(self): + """TempFlags/VccFlags must resolve to lower page 00h bytes 6 and 7.""" + assert self.mem_map.get_field(consts.TEMP_FLAGS_FIELD).get_offset() == 6 + assert self.mem_map.get_field(consts.TEMP_FLAGS_FIELD).get_size() == 1 + assert self.mem_map.get_field(consts.VCC_FLAGS_FIELD).get_offset() == 7 + assert self.mem_map.get_field(consts.VCC_FLAGS_FIELD).get_size() == 1 + @pytest.mark.parametrize("mock_response, expected",[ ( [