From a09f5a3678ce422213cfb257168d21884d66843b Mon Sep 17 00:00:00 2001 From: andywongarista <78833093+andywongarista@users.noreply.github.com> Date: Fri, 27 Aug 2021 09:38:21 -0700 Subject: [PATCH] [sfp-refactor] Add new sonic_xcvr package for common transceiver logic (#201) First-cut changes to provide platform independent support for different transceivers in SONiC platforms. --- setup.py | 8 + sonic_platform_base/sfp_base.py | 20 ++ sonic_platform_base/sonic_xcvr/__init__.py | 0 .../sonic_xcvr/api/__init__.py | 0 .../sonic_xcvr/api/public/__init__.py | 0 .../sonic_xcvr/api/public/cmis.py | 20 ++ .../sonic_xcvr/api/xcvr_api.py | 285 ++++++++++++++++++ .../sonic_xcvr/codes/__init__.py | 0 .../sonic_xcvr/codes/public/__init__.py | 0 .../sonic_xcvr/codes/public/sff8024.py | 44 +++ .../sonic_xcvr/codes/xcvr_codes.py | 8 + .../sonic_xcvr/fields/__init__.py | 0 .../sonic_xcvr/fields/consts.py | 27 ++ .../sonic_xcvr/fields/xcvr_field.py | 230 ++++++++++++++ .../sonic_xcvr/mem_maps/__init__.py | 0 .../sonic_xcvr/mem_maps/public/__init__.py | 0 .../sonic_xcvr/mem_maps/public/cmis.py | 48 +++ .../sonic_xcvr/mem_maps/xcvr_mem_map.py | 25 ++ .../sonic_xcvr/sfp_optoe_base.py | 104 +++++++ .../sonic_xcvr/xcvr_api_factory.py | 28 ++ sonic_platform_base/sonic_xcvr/xcvr_eeprom.py | 45 +++ tests/sonic_xcvr/__init__.py | 0 tests/sonic_xcvr/test_cmis.py | 21 ++ tests/sonic_xcvr/test_xcvr_field.py | 225 ++++++++++++++ 24 files changed, 1138 insertions(+) create mode 100644 sonic_platform_base/sonic_xcvr/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/api/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/api/public/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/api/public/cmis.py create mode 100644 sonic_platform_base/sonic_xcvr/api/xcvr_api.py create mode 100644 sonic_platform_base/sonic_xcvr/codes/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/codes/public/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/codes/public/sff8024.py create mode 100644 sonic_platform_base/sonic_xcvr/codes/xcvr_codes.py create mode 100644 sonic_platform_base/sonic_xcvr/fields/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/fields/consts.py create mode 100644 sonic_platform_base/sonic_xcvr/fields/xcvr_field.py create mode 100644 sonic_platform_base/sonic_xcvr/mem_maps/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/mem_maps/public/__init__.py create mode 100644 sonic_platform_base/sonic_xcvr/mem_maps/public/cmis.py create mode 100644 sonic_platform_base/sonic_xcvr/mem_maps/xcvr_mem_map.py create mode 100644 sonic_platform_base/sonic_xcvr/sfp_optoe_base.py create mode 100644 sonic_platform_base/sonic_xcvr/xcvr_api_factory.py create mode 100644 sonic_platform_base/sonic_xcvr/xcvr_eeprom.py create mode 100644 tests/sonic_xcvr/__init__.py create mode 100644 tests/sonic_xcvr/test_cmis.py create mode 100644 tests/sonic_xcvr/test_xcvr_field.py diff --git a/setup.py b/setup.py index a9ececf48fbb..94d422b89cc9 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,14 @@ 'sonic_platform_base.sonic_ssd', 'sonic_platform_base.sonic_pcie', 'sonic_platform_base.sonic_thermal_control', + 'sonic_platform_base.sonic_xcvr', + 'sonic_platform_base.sonic_xcvr.fields', + 'sonic_platform_base.sonic_xcvr.mem_maps', + 'sonic_platform_base.sonic_xcvr.mem_maps.public', + 'sonic_platform_base.sonic_xcvr.api', + 'sonic_platform_base.sonic_xcvr.api.public', + 'sonic_platform_base.sonic_xcvr.codes', + 'sonic_platform_base.sonic_xcvr.codes.public', 'sonic_psu', 'sonic_sfp', 'sonic_thermal', diff --git a/sonic_platform_base/sfp_base.py b/sonic_platform_base/sfp_base.py index b3e6e9f488d4..780695b1d6fe 100644 --- a/sonic_platform_base/sfp_base.py +++ b/sonic_platform_base/sfp_base.py @@ -8,6 +8,7 @@ import sys from . import device_base +from .sonic_xcvr.xcvr_api_factory import XcvrApiFactory class SfpBase(device_base.DeviceBase): """ @@ -55,6 +56,8 @@ def __init__(self): # List of ThermalBase-derived objects representing all thermals # available on the SFP self._thermal_list = [] + self._xcvr_api_factory = XcvrApiFactory(self.read_eeprom, self.write_eeprom) + self._xcvr_api = None def get_num_thermals(self): """ @@ -429,3 +432,20 @@ def get_error_description(self): like: "Bad EEPROM|Unsupported cable" """ raise NotImplementedError + + def refresh_xcvr_api(self): + """ + Updates the XcvrApi associated with this SFP + """ + self._xcvr_api = self._xcvr_api_factory.create_xcvr_api() + + def get_xcvr_api(self): + """ + Retrieves the XcvrApi associated with this SFP + + Returns: + An object derived from XcvrApi that corresponds to the SFP + """ + if self._xcvr_api is None: + self.refresh_xcvr_api() + return self._xcvr_api diff --git a/sonic_platform_base/sonic_xcvr/__init__.py b/sonic_platform_base/sonic_xcvr/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/api/__init__.py b/sonic_platform_base/sonic_xcvr/api/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/api/public/__init__.py b/sonic_platform_base/sonic_xcvr/api/public/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/api/public/cmis.py b/sonic_platform_base/sonic_xcvr/api/public/cmis.py new file mode 100644 index 000000000000..f8ce14857fa9 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/api/public/cmis.py @@ -0,0 +1,20 @@ +""" + cmis.py + + Implementation of XcvrApi that corresponds to CMIS +""" + +from ...fields import consts +from ..xcvr_api import XcvrApi + +class CmisApi(XcvrApi): + NUM_CHANNELS = 8 + + def __init__(self, xcvr_eeprom): + super(CmisApi, self).__init__(xcvr_eeprom) + + def get_model(self): + return self.xcvr_eeprom.read(consts.VENDOR_PART_NO_FIELD) + + # TODO: other XcvrApi methods + diff --git a/sonic_platform_base/sonic_xcvr/api/xcvr_api.py b/sonic_platform_base/sonic_xcvr/api/xcvr_api.py new file mode 100644 index 000000000000..0904e4868bd2 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/api/xcvr_api.py @@ -0,0 +1,285 @@ +""" + xcvr_api.py + + Abstract base class for platform-independent APIs used to interact with + xcvrs in SONiC +""" + +class XcvrApi(object): + def __init__(self, xcvr_eeprom): + self.xcvr_eeprom = xcvr_eeprom + + def get_model(self): + """ + Retrieves the model (part number) of the xcvr + + Returns: + A string, the model/part number of the xcvr + """ + raise NotImplementedError + + def get_serial(self): + """ + Retrieves the serial number of the xcvr + + Returns: + A string, the serial number of the xcvr + """ + raise NotImplementedError + + def get_transceiver_info(self): + """ + Retrieves general info about this xcvr + + Returns: + A dict containing the following keys/values : + ================================================================================ + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + type |string |type of xcvr + type_abbrv_name |string |type of SFP, abbreviated + hardware_rev |string |hardware version of xcvr + serial |string |serial number of the xcvr + manufacturer |string |xcvr vendor name + model |string |xcvr model name + connector |string |connector information + encoding |string |encoding information + ext_identifier |string |extend identifier + ext_rateselect_compliance |string |extended rateSelect compliance + cable_length |int |cable length in m + nominal_bit_rate |int |nominal bit rate by 100Mbs + specification_compliance |string |specification compliance + vendor_date |string |vendor date + vendor_oui |string |vendor OUI + application_advertisement |string |supported applications advertisement + ================================================================================ + """ + raise NotImplementedError + + def get_transceiver_bulk_status(self): + """ + Retrieves bulk status info for this xcvr + + Returns: + A dict containing the following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + rx_los |bool |RX loss-of-signal status, True if has RX los, False if not. + tx_fault |bool |TX fault status, True if has TX fault, False if not. + tx_disable |bool |TX disable status, True TX disabled, False if not. + tx_disabled_channel |int |disabled TX channels in hex, bits 0 to 3 represent channel 0 + | |to channel 3 (for example). + temperature |float |module temperature in Celsius + voltage |float |supply voltage in mV + txbias |float |TX Bias Current in mA, n is the channel number, + | |for example, tx2bias stands for tx bias of channel 2. + rxpower |float |received optical power in mW, n is the channel number, + | |for example, rx2power stands for rx power of channel 2. + txpower |float |TX output power in mW, n is the channel number, + | |for example, tx2power stands for tx power of channel 2. + ======================================================================== + """ + raise NotImplementedError + + def get_transceiver_threshold_info(self): + """ + Retrieves threshold info for this xcvr + + Returns: + A dict containing the following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + temphighalarm |FLOAT |High Alarm Threshold value of temperature in Celsius. + templowalarm |FLOAT |Low Alarm Threshold value of temperature in Celsius. + temphighwarning |FLOAT |High Warning Threshold value of temperature in Celsius. + templowwarning |FLOAT |Low Warning Threshold value of temperature in Celsius. + vcchighalarm |FLOAT |High Alarm Threshold value of supply voltage in mV. + vcclowalarm |FLOAT |Low Alarm Threshold value of supply voltage in mV. + vcchighwarning |FLOAT |High Warning Threshold value of supply voltage in mV. + vcclowwarning |FLOAT |Low Warning Threshold value of supply voltage in mV. + rxpowerhighalarm |FLOAT |High Alarm Threshold value of received power in dBm. + rxpowerlowalarm |FLOAT |Low Alarm Threshold value of received power in dBm. + rxpowerhighwarning |FLOAT |High Warning Threshold value of received power in dBm. + rxpowerlowwarning |FLOAT |Low Warning Threshold value of received power in dBm. + txpowerhighalarm |FLOAT |High Alarm Threshold value of transmit power in dBm. + txpowerlowalarm |FLOAT |Low Alarm Threshold value of transmit power in dBm. + txpowerhighwarning |FLOAT |High Warning Threshold value of transmit power in dBm. + txpowerlowwarning |FLOAT |Low Warning Threshold value of transmit power in dBm. + txbiashighalarm |FLOAT |High Alarm Threshold value of tx Bias Current in mA. + txbiaslowalarm |FLOAT |Low Alarm Threshold value of tx Bias Current in mA. + txbiashighwarning |FLOAT |High Warning Threshold value of tx Bias Current in mA. + txbiaslowwarning |FLOAT |Low Warning Threshold value of tx Bias Current in mA. + ======================================================================== + """ + raise NotImplementedError + + def get_rx_los(self): + """ + Retrieves the RX LOS (loss-of-signal) status of this xcvr + + Returns: + A list of boolean values, representing the RX LOS status + of each available channel, value is True if xcvr channel + has RX LOS, False if not. + E.g., for a tranceiver with four channels: [False, False, True, False] + + If Rx LOS status is unsupported on the xcvr, each list element should be "N/A" instead. + """ + raise NotImplementedError + + def get_tx_fault(self): + """ + Retrieves the TX fault status of this xcvr + + Returns: + A list of boolean values, representing the TX fault status + of each available channel, value is True if xcvr channel + has TX fault, False if not. + E.g., for a tranceiver with four channels: [False, False, True, False] + + If TX fault status is unsupported on the xcvr, each list element should be "N/A" instead. + """ + raise NotImplementedError + + def get_tx_disable(self): + """ + Retrieves the TX disabled channels in this xcvr + + Returns: + A hex of 4 bits (bit 0 to bit 3 as channel 0 to channel 3) to represent + TX channels which have been disabled in this xcvr. + As an example, a returned value of 0x5 indicates that channel 0 + and channel 2 have been disabled. + """ + raise NotImplementedError + + def get_tx_disable_channel(self): + """ + Retrieves the TX disabled channels in this xcvr + + Returns: + A hex of 4 bits (bit 0 to bit 3 as channel 0 to channel 3) to represent + TX channels which have been disabled in this xcvr. + As an example, a returned value of 0x5 indicates that channel 0 + and channel 2 have been disabled. + """ + raise NotImplementedError + + def get_temperature(self): + """ + Retrieves the temperature of this xcvr + + Returns: + A float representing the current temperature in Celsius, or "N/A" if temperature + measurements are unsupported on the xcvr. + """ + raise NotImplementedError + + def get_voltage(self): + """ + Retrieves the supply voltage of this xcvr + + Returns: + A float representing the supply voltage in mV, or "N/A" if voltage measurements are + unsupported on the xcvr. + """ + raise NotImplementedError + + def get_tx_bias(self): + """ + Retrieves the TX bias current of all xcvr channels + + Returns: + A list of floats, representing TX bias in mA + for each available channel + E.g., for a tranceiver with four channels: ['110.09', '111.12', '108.21', '112.09'] + + If TX bias is unsupported on the xcvr, each list element should be "N/A" instead. + """ + raise NotImplementedError + + def get_rx_power(self): + """ + Retrieves the received optical power of all xcvr channels + + Returns: + A list of floats, representing received optical + power in mW for each available channel + E.g., for a tranceiver with four channels: ['1.77', '1.71', '1.68', '1.70'] + + If RX power is unsupported on the xcvr, each list element should be "N/A" instead. + """ + raise NotImplementedError + + def get_tx_power(self): + """ + Retrieves the TX power of all xcvr channels + + Returns: + A list of floats, representing TX power in mW + for each available channel + E.g., for a tranceiver with four channels: ['1.86', '1.86', '1.86', '1.86'] + + If TX power is unsupported on the xcvr, each list element should be "N/A" instead. + """ + raise NotImplementedError + + def tx_disable(self, tx_disable): + """ + Disable xcvr TX for all channels + + Args: + tx_disable : A Boolean, True to enable tx_disable mode, False to disable + tx_disable mode. + + Returns: + A boolean, True if tx_disable is set successfully, False if not + """ + raise NotImplementedError + + def tx_disable_channel(self, channel, disable): + """ + Sets the tx_disable for specified xcvr channels + + Args: + channel : A hex of 4 bits (bit 0 to bit 3) which represent channel 0 to 3, + e.g. 0x5 for channel 0 and channel 2. + disable : A boolean, True to disable TX channels specified in channel, + False to enable + + Returns: + A boolean, True if successful, False if not + """ + raise NotImplementedError + + def get_power_override(self): + """ + Retrieves the power-override status of this xcvr + + Returns: + A boolean, True if power-override is enabled, False if disabled + """ + raise NotImplementedError + + def set_power_override(self, power_override, power_set): + """ + Sets xcvr power level using power_override and power_set + + Args: + power_override : + A Boolean, True to override set_lpmode and use power_set + to control xcvr power, False to disable xcvr power control + through power_override/power_set and use set_lpmode + to control xcvr power. + power_set : + Only valid when power_override is True. + A Boolean, True to set xcvr to low power mode, False to set + xcvr to high power mode. + + Returns: + A boolean, True if power-override and power_set are set successfully, + False if not + """ + raise NotImplementedError diff --git a/sonic_platform_base/sonic_xcvr/codes/__init__.py b/sonic_platform_base/sonic_xcvr/codes/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/codes/public/__init__.py b/sonic_platform_base/sonic_xcvr/codes/public/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/codes/public/sff8024.py b/sonic_platform_base/sonic_xcvr/codes/public/sff8024.py new file mode 100644 index 000000000000..5948b1af2f0a --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/codes/public/sff8024.py @@ -0,0 +1,44 @@ +""" + sff8024.py + + Implementation of SFF-8024 Rev 4.8a +""" + +from ..xcvr_codes import XcvrCodes + +class Sff8024(XcvrCodes): + XCVR_IDENTIFIERS = { + 0: 'Unknown or unspecified', + 1: 'GBIC', + 2: 'Module/connector soldered to motherboard', + 3: 'SFP/SFP+/SFP28', + 4: '300 pin XBI', + 5: 'XENPAK', + 6: 'XFP', + 7: 'XFF', + 8: 'XFP-E', + 9: 'XPAK', + 10: 'X2', + 11: 'DWDM-SFP/SFP+', + 12: 'QSFP', + 13: 'QSFP+ or later with SFF-8636 or SFF-8436', + 14: 'CXP or later', + 15: 'Shielded Mini Multilane HD 4X', + 16: 'Shielded Mini Multilane HD 8X', + 17: 'QSFP28 or later', + 18: 'CXP2 (aka CXP28) or later', + 19: 'CDFP (Style 1/Style2)', + 20: 'Shielded Mini Multilane HD 4X Fanout Cable', + 21: 'Shielded Mini Multilane HD 8X Fanout Cable', + 22: 'CDFP (Style 3)', + 23: 'microQSFP', + 24: 'QSFP-DD Double Density 8X Pluggable Transceiver', + 25: 'OSFP 8X Pluggable Transceiver', + 26: 'SFP-DD Double Density 2X Pluggable Transceiver', + 27: 'DSFP Dual Small Form Factor Pluggable Transceiver', + 28: 'x4 MiniLink/OcuLink', + 29: 'x8 MiniLink', + 30: 'QSFP+ or later with CMIS' + } + + # TODO: Add other codes diff --git a/sonic_platform_base/sonic_xcvr/codes/xcvr_codes.py b/sonic_platform_base/sonic_xcvr/codes/xcvr_codes.py new file mode 100644 index 000000000000..27257beec8c3 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/codes/xcvr_codes.py @@ -0,0 +1,8 @@ +""" + xcvr_codes.py + + Base class for representing codes used in xcvr memory maps +""" + +class XcvrCodes(object): + pass diff --git a/sonic_platform_base/sonic_xcvr/fields/__init__.py b/sonic_platform_base/sonic_xcvr/fields/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/fields/consts.py b/sonic_platform_base/sonic_xcvr/fields/consts.py new file mode 100644 index 000000000000..088b8ad10514 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/fields/consts.py @@ -0,0 +1,27 @@ +# COMMON + +ID_FIELD = "Identifier" + +VENDOR_NAME_FIELD = "VendorName" +VENDOR_OUI_FIELD = "VendorOUI" +VENDOR_PART_NO_FIELD = "VendorPN" + +# SFF-8436 + +# CMIS + +ADMIN_INFO_FIELD = "AdminInfo" + +# C-CMIS + +## Media Lane FEC Performance Monitoring +MEDIA_LANE_FEC_PM_FIELD = "Media Lane FEC Performance Monitoring" +MEDIA_LANE_LINK_PM_FIELD = "Media Lane Link Performance Monitoring" +RX_BITS_PM_FIELD = "rxBitsPm" +RX_BITS_SUB_INT_PM_FIELD = "rxBitsSubIntPm" + +## Media Lane Link Performance Monitoring +RX_AVG_CD_PM_FIELD = "rxAvgCdPm" +RX_MIN_CD_PM_FIELD = "rxMinCdPm" +RX_MAX_CD_PM_FIELD = "rxMaxCdPm" +RX_AVG_DGD_PM_FIELD = "rxAvgDgdPm" diff --git a/sonic_platform_base/sonic_xcvr/fields/xcvr_field.py b/sonic_platform_base/sonic_xcvr/fields/xcvr_field.py new file mode 100644 index 000000000000..178bf8934f3f --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/fields/xcvr_field.py @@ -0,0 +1,230 @@ +""" + xcvr_field.py + + Classes for representing types of fields found in xcvr memory maps. +""" + +import struct + +class XcvrField(object): + """ + Base class for representing fields in xcvr memory maps. + + Args: + name: string, denoting the name of the field. Must be unique for a particular XcvrMemMap. + offset: integer, the absolute offset of the field in a memory map, assuming a linear address space + ro: boolean, True if the field is read-only and False otherwise + """ + def __init__(self, name, offset, ro): + self.name = name + self.offset = offset + self.ro = ro + + def get_fields(self): + """ + Return: dict containing all fields nested within this field + """ + fields = {} + if hasattr(self, "fields"): + for field in self.fields: + fields[field.name] = field + fields.update(field.get_fields()) + return fields + + def get_offset(self): + """ + Return: absolute byte offset of field in memory map + """ + return self.offset + + def get_size(self): + """ + Return: size of field in bytes (min. 1) + """ + raise NotImplementedError + + def read_before_write(self): + """ + Return: True if a field needs to be read before written to, False otherwise + """ + raise NotImplementedError + + def decode(self, raw_data): + """ + raw_data: bytearray of length equal to size of field + Return: decoded data (high level meaning) + """ + raise NotImplementedError + + def encode(self, val, raw_state=None): + """ + val: data with high level meaning + raw_state: bytearray denoting the current state of memory corresponding to this field + Return: bytearray of length equal to size of field + + Not implemented if not appropriate for the field (e.g. read-only) + """ + raise NotImplementedError + + +class RegBitField(XcvrField): + """ + Field denoting a single bit. Must be defined under a parent RegField + + Args: + bitpos: the bit position of this field relative to its parent's offset + """ + def __init__(self, name, bitpos, ro=True, offset=None): + super(RegBitField, self).__init__(name, offset, ro) + assert bitpos < 64 + self.bitpos = bitpos + + def get_size(self): + return 1 + + def read_before_write(self): + return True + + def decode(self, raw_data): + return bool((raw_data[0] >> self.bitpos) & 1) + + def encode(self, val, raw_state=None): + assert not self.ro and raw_state is not None + curr_state = raw_state[0] + if val: + curr_state |= (1 << self.bitpos) + else: + curr_state &= ~(1 << self.bitpos) + return bytearray([curr_state]) + + +class RegField(XcvrField): + """ + Field denoting one or more bytes, but logically interpreted as one unit (e.g. a 4-byte integer) + """ + def __init__(self, name, offset, *fields, **kwargs): + super(RegField, self).__init__(name, offset, kwargs.get("ro", True)) + self.fields = fields + self.size = kwargs.get("size", 1) + self.start_bitpos = self.size * 8 - 1 # max bitpos + self._update_bit_offsets() + + def _update_bit_offsets(self): + for field in self.fields: + assert 0 <= field.bitpos < self.size * 8 + field.offset = self.offset + field.bitpos // 8 + self.start_bitpos = min(field.bitpos, self.start_bitpos) + + def get_bitmask(self): + if not self.fields: + return None + mask = 0 + for field in self.fields: + mask |= 1 << field.bitpos + return mask + + def get_size(self): + return self.size + + def read_before_write(self): + return False + +class NumberRegField(RegField): + """ + Interprets byte(s) as a number + """ + def __init__(self, name, offset, *fields, **kwargs): + super(NumberRegField, self).__init__( name, offset, *fields, **kwargs) + self.scale = kwargs.get("scale") + self.format = kwargs.get("format", "B") + + def decode(self, raw_data): + decoded = struct.unpack(self.format, raw_data)[0] + mask = self.get_bitmask() + if mask is not None: + decoded &= mask + decoded >>= self.start_bitpos + if self.scale is not None: + return decoded / self.scale + return decoded + + def encode(self, val, raw_state=None): + assert not self.ro + if self.scale is not None: + return bytearray(struct.pack(self.format, val * self.scale)) + return bytearray(struct.pack(self.format, val)) + +class StringRegField(RegField): + """ + Interprets byte(s) as a string + """ + def __init__(self, name, offset, *fields, **kwargs): + super(StringRegField, self).__init__(name, offset, *fields, **kwargs) + self.encoding = kwargs.get("encoding", "ascii") + self.format = kwargs.get("format", ">%ds" % self.size) + + def decode(self, raw_data): + return struct.unpack(self.format, raw_data)[0].decode(self.encoding) + +class CodeRegField(RegField): + """ + Interprets byte(s) as a code + """ + def __init__(self, name, offset, code_dict, *fields, **kwargs): + super(CodeRegField, self).__init__(name, offset, *fields, **kwargs) + self.code_dict = code_dict + self.format = kwargs.get("format", "B") + + def decode(self, raw_data): + code = struct.unpack(self.format, raw_data)[0] + mask = self.get_bitmask() + if mask is not None: + code &= mask + code >>= self.start_bitpos + return self.code_dict.get(code, "Unknown") + +class HexRegField(RegField): + """ + Interprets bytes as a series of hex pairs + """ + def __init__(self, name, offset, *fields, **kwargs): + super(HexRegField, self).__init__(name, offset, *fields, **kwargs) + + def decode(self, raw_data): + return '-'.join([ "%02x" % byte for byte in raw_data]) + +class RegGroupField(XcvrField): + """ + Field denoting one or more bytes, logically interpreted as one or more RegFields + (e.g. a 4-byte integer followed by a 16-byte string) or RegGroupFields. + + The offset of a RegGroupField is the offset of its first member XcvrField. + + The member fields need not be contiguous, but the first field must be the one with the smallest offset. + """ + def __init__(self, name, *fields, **kwargs): + super(RegGroupField, self).__init__( + name, fields[0].get_offset(), kwargs.get("ro", True)) + self.fields = fields + + def get_size(self): + start = self.offset + end = start + for field in self.fields: + end = max(end, field.get_offset() + field.get_size()) + return end - start + + def read_before_write(self): + return False + + def decode(self, raw_data): + """ + Return: a dict mapping member field names to their decoded results + """ + result = {} + start = self.offset + for field in self.fields: + offset = field.get_offset() + result[field.name] = field.decode( + raw_data[offset - start: offset + field.get_size() - start]) + return result diff --git a/sonic_platform_base/sonic_xcvr/mem_maps/__init__.py b/sonic_platform_base/sonic_xcvr/mem_maps/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/mem_maps/public/__init__.py b/sonic_platform_base/sonic_xcvr/mem_maps/public/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_xcvr/mem_maps/public/cmis.py b/sonic_platform_base/sonic_xcvr/mem_maps/public/cmis.py new file mode 100644 index 000000000000..8f7275fc9b2e --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/mem_maps/public/cmis.py @@ -0,0 +1,48 @@ +""" + cmis.py + + Implementation of XcvrMemMap for CMIS Rev 4.0 +""" + +from ..xcvr_mem_map import XcvrMemMap +from ...fields.xcvr_field import ( + CodeRegField, + HexRegField, + NumberRegField, + RegGroupField, + StringRegField, +) +from ...fields import consts + +PAGE_SIZE = 128 + +def get_addr(page, offset): + return PAGE_SIZE * page + offset + +class CmisMemMap(XcvrMemMap): + def __init__(self, codes): + super(CmisMemMap, self).__init__(codes) + + self.ADMIN_INFO = RegGroupField(consts.ADMIN_INFO_FIELD, + CodeRegField(consts.ID_FIELD, get_addr(0x0, 128), self.codes.XCVR_IDENTIFIERS), + StringRegField(consts.VENDOR_NAME_FIELD, get_addr(0x0, 129), size=16), + HexRegField(consts.VENDOR_OUI_FIELD, get_addr(0x0, 145), size=3), + StringRegField(consts.VENDOR_PART_NO_FIELD, get_addr(0x0, 148), size=16), + # TODO: add remaining admin fields + ) + + self.MEDIA_LANE_FEC_PM = RegGroupField(consts.MEDIA_LANE_FEC_PM_FIELD, + NumberRegField(consts.RX_BITS_PM_FIELD, get_addr(0x34, 128), format=">Q", size=8), + NumberRegField(consts.RX_BITS_SUB_INT_PM_FIELD, get_addr(0x34, 136), format=">Q", size=8), + # TODO: add other PMs... + ) + + self.MEDIA_LANE_LINK_PM = RegGroupField(consts.MEDIA_LANE_LINK_PM_FIELD, + NumberRegField(consts.RX_AVG_CD_PM_FIELD, get_addr(0x35, 128), format=">i", size=4), + NumberRegField(consts.RX_MIN_CD_PM_FIELD, get_addr(0x35, 132), format=">i", size=4), + NumberRegField(consts.RX_MAX_CD_PM_FIELD, get_addr(0x35, 136), format=">i", size=4), + NumberRegField(consts.RX_AVG_DGD_PM_FIELD, get_addr(0x35, 140), format=">H", size=2, scale=100) + # TODO: add others PMs... + ) + + # TODO: add remaining fields diff --git a/sonic_platform_base/sonic_xcvr/mem_maps/xcvr_mem_map.py b/sonic_platform_base/sonic_xcvr/mem_maps/xcvr_mem_map.py new file mode 100644 index 000000000000..80d748d9381f --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/mem_maps/xcvr_mem_map.py @@ -0,0 +1,25 @@ +""" + xcvr_mem_map.py + + Base class for representing xcvr memory maps in SONiC +""" + +from ..fields.xcvr_field import XcvrField + +class XcvrMemMap(object): + def __init__(self, codes): + self.codes = codes + self._fields = None + + def _get_all_fields(self): + if self._fields is None: + self._fields = {} + for key in dir(self): + attr = getattr(self, key) + if isinstance(attr, XcvrField): + self._fields[attr.name] = attr + self._fields.update(attr.get_fields()) + return self._fields + + def get_field(self, field_name): + return self._get_all_fields()[field_name] diff --git a/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py b/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py new file mode 100644 index 000000000000..9ece872bcce4 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py @@ -0,0 +1,104 @@ +""" + sfp_optoe_base.py + + Platform-independent class with which to interact with a SFP module + in SONiC +""" + +from ..sfp_base import SfpBase + +class SfpOptoeBase(SfpBase): + def __init__(self): + SfpBase.__init__(self) + + def get_model(self): + api = self.get_xcvr_api() + return api.get_model() if api is not None else None + + def get_serial(self): + api = self.get_xcvr_api() + return api.get_serial() if api is not None else None + + def get_transceiver_info(self): + api = self.get_xcvr_api() + return api.get_transceiver_info() if api is not None else None + + def get_transceiver_bulk_status(self): + api = self.get_xcvr_api() + return api.get_transceiver_bulk_status() if api is not None else None + + def get_transceiver_threshold_info(self): + api = self.get_xcvr_api() + return api.get_transceiver_threshold_info() if api is not None else None + + def get_rx_los(self): + api = self.get_xcvr_api() + return api.get_rx_los() if api is not None else None + + def get_tx_fault(self): + api = self.get_xcvr_api() + return api.get_tx_fault() if api is not None else None + + def get_tx_disable(self): + api = self.get_xcvr_api() + return api.get_tx_disable() if api is not None else None + + def get_tx_disable_channel(self): + api = self.get_xcvr_api() + return api.get_tx_disable_channel() if api is not None else None + + def get_temperature(self): + api = self.get_xcvr_api() + return api.get_temperature() if api is not None else None + + def get_voltage(self): + api = self.get_xcvr_api() + return api.get_voltage() if api is not None else None + + def get_tx_bias(self): + api = self.get_xcvr_api() + return api.get_tx_bias() if api is not None else None + + def get_rx_power(self): + api = self.get_xcvr_api() + return api.get_rx_power() if api is not None else None + + def get_tx_power(self): + api = self.get_xcvr_api() + return api.get_tx_power() if api is not None else None + + def tx_disable(self, tx_disable): + api = self.get_xcvr_api() + return api.tx_disable(tx_disable) if api is not None else None + + def tx_disable_channel(self, channel, disable): + api = self.get_xcvr_api() + return api.tx_disable_channel(channel, disable) if api is not None else None + + def get_power_override(self): + api = self.get_xcvr_api() + return api.get_power_override() if api is not None else None + + def set_power_override(self, power_override, power_set): + api = self.get_xcvr_api() + return api.set_power_override(power_override, power_set) if api is not None else None + + def get_eeprom_path(self): + raise NotImplementedError + + def read_eeprom(self, offset, num_bytes): + try: + with open(self.get_eeprom_path(), mode='rb', buffering=0) as f: + f.seek(offset) + return bytearray(f.read(num_bytes)) + except (OSError, IOError): + return None + + def write_eeprom(self, offset, num_bytes, write_buffer): + try: + with open(self.get_eeprom_path(), mode='r+b', buffering=0) as f: + f.seek(offset) + f.write(write_buffer[0:num_bytes]) + except (OSError, IOError): + return False + return True diff --git a/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py b/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py new file mode 100644 index 000000000000..ce9c33350ad1 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py @@ -0,0 +1,28 @@ +""" + xcvr_api_factory.py + + Factory class responsible for instantiating the appropriate XcvrApi + implementation for a xcvr module in SONiC +""" + +from .xcvr_eeprom import XcvrEeprom +# TODO: remove the following imports +from .codes.public.sff8024 import Sff8024 +from .api.public.cmis import CmisApi +from .mem_maps.public.cmis import CmisMemMap + +class XcvrApiFactory(object): + def __init__(self, reader, writer): + self.reader = reader + self.writer = writer + + def _get_id(self): + # TODO: read ID from eeprom + pass + + def create_xcvr_api(self): + # TODO: load correct classes from id_mapping file + codes = Sff8024 + mem_map = CmisMemMap(codes) + xcvr_eeprom = XcvrEeprom(self.reader, self.writer, mem_map) + return CmisApi(xcvr_eeprom) diff --git a/sonic_platform_base/sonic_xcvr/xcvr_eeprom.py b/sonic_platform_base/sonic_xcvr/xcvr_eeprom.py new file mode 100644 index 000000000000..4902be56a963 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/xcvr_eeprom.py @@ -0,0 +1,45 @@ +""" + xcvr_eeprom.py + + Common API used by all XcvrApis to read and write to various fields that can be found in a xcvr EEPROM +""" + +class XcvrEeprom(object): + def __init__(self, reader, writer, mem_map): + self.reader = reader + self.writer = writer + self.mem_map = mem_map + + def read(self, field_name): + """ + Read a value from a field in EEPROM + + Args: + field_name: a string denoting the XcvrField to read from + + Returns: + The value of the field, if the read is successful and None otherwise + """ + field = self.mem_map.get_field(field_name) + raw_data = self.reader(field.get_offset(), field.get_size()) + return field.decode(raw_data) if raw_data is not None else None + + def write(self, field_name, value): + """ + Write a value to a field in EEPROM + + Args: + field_name: a string denoting the XcvrField to write to + + value: + The value to write to the EEPROM, appropriate for the given field_name + + Returns: + Boolean, True if the write is successful and False otherwise + """ + field = self.mem_map.get_field(field_name) + if field.read_before_write(): + encoded_data = field.encode(value, self.reader(field.get_offset(), field.get_size())) + else: + encoded_data = field.encode(value) + return self.writer(field.get_offset(), field.get_size(), encoded_data) diff --git a/tests/sonic_xcvr/__init__.py b/tests/sonic_xcvr/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/sonic_xcvr/test_cmis.py b/tests/sonic_xcvr/test_cmis.py new file mode 100644 index 000000000000..60e34efeff3e --- /dev/null +++ b/tests/sonic_xcvr/test_cmis.py @@ -0,0 +1,21 @@ +from mock import MagicMock + +from sonic_platform_base.sonic_xcvr.api.public.cmis import CmisApi +from sonic_platform_base.sonic_xcvr.mem_maps.public.cmis import CmisMemMap +from sonic_platform_base.sonic_xcvr.xcvr_eeprom import XcvrEeprom +from sonic_platform_base.sonic_xcvr.codes.public.sff8024 import Sff8024 + +class TestCmis(object): + codes = Sff8024 + mem_map = CmisMemMap(codes) + reader = MagicMock(return_value=None) + writer = MagicMock() + eeprom = XcvrEeprom(reader, writer, mem_map) + api = CmisApi(eeprom) + + def test_api(self): + """ + Verify all api access valid fields + """ + self.api.get_model() + # TODO: call other methods in the api diff --git a/tests/sonic_xcvr/test_xcvr_field.py b/tests/sonic_xcvr/test_xcvr_field.py new file mode 100644 index 000000000000..2ee827b470a7 --- /dev/null +++ b/tests/sonic_xcvr/test_xcvr_field.py @@ -0,0 +1,225 @@ +from sonic_platform_base.sonic_xcvr.fields.xcvr_field import ( + CodeRegField, + HexRegField, + NumberRegField, + RegBitField, + RegGroupField, + StringRegField, +) +from sonic_platform_base.sonic_xcvr.codes.xcvr_codes import XcvrCodes +from sonic_platform_base.sonic_xcvr.mem_maps.xcvr_mem_map import XcvrMemMap + +class MockXcvrCodes(XcvrCodes): + CODE_DICT = { + 0: "Code0", + 1: "Code1" + + } + + SHIFTED_CODE_DICT = { + 0: "Code0", + 1: "Code1", + 2: "Code2", + 3: "Code3", + } + +class MockXcvrMemMap(XcvrMemMap): + def __init__(self, codes): + super(MockXcvrMemMap, self).__init__(codes) + + self.CODE_REG = CodeRegField("CodeReg", 5, self.codes.CODE_DICT) + self.SHIFTED_CODE_REG = CodeRegField("ShiftedCodeReg", 50, self.codes.SHIFTED_CODE_DICT, + RegBitField("CodeBit7", bitpos=7), + RegBitField("CodeBit8", bitpos=8), + size=2, format=">H") + self.NUM_REG = NumberRegField("NumReg", 100, format=">Q", size=8, ro=False) + self.SCALE_NUM_REG = NumberRegField("ScaleNumReg", 120, format=">i", size=4, scale=100, ro=False) + self.STRING_REG = StringRegField("StringReg", 12, size=15) + self.HEX_REG = HexRegField("HexReg", 30, size=3) + self.REG_GROUP = RegGroupField("RegGroup", + NumberRegField("Field0", 6, ro=False), + NumberRegField("Field1", 7, + RegBitField("BitField0", bitpos=0, ro=False), + RegBitField("BitField1", bitpos=1, ro=False), + ro=False + ), + NumberRegField("Field2", 7, format=">I", size=4), + ) + + self.REG_GROUP_NESTED = RegGroupField("RegGroupNested", + RegGroupField("RegGroupNestedInner", + NumberRegField("NestedField0", 9), + NumberRegField( "NestedField1", 10), + ), + NumberRegField("NestedField2", 11) + ) + + self.REG_GROUP_NON_CONTIGUOUS = RegGroupField("RegGroupNonContiguous", + NumberRegField("Field3", 50, format="