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
62 changes: 62 additions & 0 deletions sonic_platform_base/sonic_xcvr/api/public/sff8636.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ class Sff8636Api(XcvrApi):
NUM_CHANNELS = 4
POWER_CLASS_PATTERN = r'^Power Class ([1-8])'

# Bit layout shared by the temperature (byte 6) and Vcc (byte 7)
# free side monitor interrupt flag bytes (SFF-8636 Rev 2.12
# Table 6-6). 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.
FLAG_HIGH_ALARM_BITPOS = 7
FLAG_LOW_ALARM_BITPOS = 6
FLAG_HIGH_WARN_BITPOS = 5
FLAG_LOW_WARN_BITPOS = 4

def __init__(self, xcvr_eeprom):
super(Sff8636Api, self).__init__(xcvr_eeprom)
self._temp_support = None
Expand Down Expand Up @@ -126,6 +136,58 @@ 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. Note the flag bytes must be compared
against None and not tested for truth: 0x00 is the normal state of a
healthy module and its four flags are a real "no excursion" result.

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({
"tempHAlarm": bool(temp_flags & (1 << self.FLAG_HIGH_ALARM_BITPOS)),
"tempLAlarm": bool(temp_flags & (1 << self.FLAG_LOW_ALARM_BITPOS)),
"tempHWarn": bool(temp_flags & (1 << self.FLAG_HIGH_WARN_BITPOS)),
"tempLWarn": bool(temp_flags & (1 << self.FLAG_LOW_WARN_BITPOS)),
})

if self.get_voltage_support():
vcc_flags = self.xcvr_eeprom.read(consts.VCC_FLAGS_FIELD)
if vcc_flags is not None:
dom_flags.update({
"vccHAlarm": bool(vcc_flags & (1 << self.FLAG_HIGH_ALARM_BITPOS)),
"vccLAlarm": bool(vcc_flags & (1 << self.FLAG_LOW_ALARM_BITPOS)),
"vccHWarn": bool(vcc_flags & (1 << self.FLAG_HIGH_WARN_BITPOS)),
"vccLWarn": bool(vcc_flags & (1 << self.FLAG_LOW_WARN_BITPOS)),
})

return dom_flags

def get_transceiver_dom_real_value(self):
"""
Retrieves DOM sensor values for this transceiver
Expand Down
2 changes: 2 additions & 0 deletions sonic_platform_base/sonic_xcvr/fields/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions sonic_platform_base/sonic_xcvr/mem_maps/public/sff8636.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ 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 must be read whole (like
# CMIS MODULE_FLAG_BYTE1) and decoded by the caller; RegBitField
# children would trigger one clearing read per bit.
self.TEMP_FLAGS = NumberRegField(consts.TEMP_FLAGS_FIELD, self.get_addr(0, 6), size=1)

self.VCC_FLAGS = NumberRegField(consts.VCC_FLAGS_FIELD, self.get_addr(0, 7), size=1)

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))),
Expand Down
153 changes: 153 additions & 0 deletions tests/sonic_xcvr/test_sff8636.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,159 @@ 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.
self.api.xcvr_eeprom.read = MagicMock(side_effect=lambda field: eeprom[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",[
(
[
Expand Down
Loading