diff --git a/setup.py b/setup.py index 4bcdcb77c..9edd23073 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,9 @@ 'sonic_platform_base.sonic_xcvr.api.public', 'sonic_platform_base.sonic_xcvr.codes', 'sonic_platform_base.sonic_xcvr.codes.public', + 'sonic_platform_base.sonic_xcvr.api.credo', + 'sonic_platform_base.sonic_xcvr.mem_maps.credo', + 'sonic_platform_base.sonic_xcvr.codes.credo', 'sonic_psu', 'sonic_sfp', 'sonic_thermal', diff --git a/sonic_platform_base/sonic_xcvr/api/credo/aec_800g.py b/sonic_platform_base/sonic_xcvr/api/credo/aec_800g.py new file mode 100644 index 000000000..87fb0a53c --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/api/credo/aec_800g.py @@ -0,0 +1,12 @@ +""" + aec_800g.py + + Implementation of Credo AEC cable specific in addition to the CMIS specification. +""" + +from ...fields import consts +from ..public.cmis import CmisApi + +class CmisAec800gApi(CmisApi): + def set_firmware_download_target_end(self, target): + return self.xcvr_eeprom.write(consts.TARGET_MODE, target) diff --git a/sonic_platform_base/sonic_xcvr/codes/credo/aec_800g.py b/sonic_platform_base/sonic_xcvr/codes/credo/aec_800g.py new file mode 100644 index 000000000..939a3d26a --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/codes/credo/aec_800g.py @@ -0,0 +1,8 @@ +from ..public.cmis import CmisCodes + +class CmisAec800gCodes(CmisCodes): + TARGET_MODE = { + 0: 'local', + 1: 'remote-A', + 2: 'remote-B' + } diff --git a/sonic_platform_base/sonic_xcvr/fields/consts.py b/sonic_platform_base/sonic_xcvr/fields/consts.py index e9e250a32..bf781b29c 100644 --- a/sonic_platform_base/sonic_xcvr/fields/consts.py +++ b/sonic_platform_base/sonic_xcvr/fields/consts.py @@ -477,4 +477,6 @@ CDB_CMD = "CdbCommand" CDB_WRITE_MSG = "CdbWriteMessage" - +#VENDOR SPECIFIC +VENDOR_CUSTOM = "VendorCustom" +TARGET_MODE = "TargetMode" diff --git a/sonic_platform_base/sonic_xcvr/mem_maps/credo/aec_800g.py b/sonic_platform_base/sonic_xcvr/mem_maps/credo/aec_800g.py new file mode 100644 index 000000000..697e47613 --- /dev/null +++ b/sonic_platform_base/sonic_xcvr/mem_maps/credo/aec_800g.py @@ -0,0 +1,28 @@ +""" + aec_800g.py + + Implementation of Credo AEC cable specific XcvrMemMap for CMIS Rev 5.0 +""" + +from ..public.cmis import CmisMemMap +from ...fields.xcvr_field import ( + CodeRegField, + DateField, + HexRegField, + NumberRegField, + RegBitField, + RegGroupField, + StringRegField, +) +from ...fields import consts + +class CmisAec800gMemMap(CmisMemMap): + def __init__(self, codes): + super(CmisAec800gMemMap, self).__init__(codes) + + self.VENDOR_CUSTOM = RegGroupField(consts.VENDOR_CUSTOM, + NumberRegField(consts.TARGET_MODE, self.getaddr(0x0, 64), ro=False) + ) + + def getaddr(self, page, offset, page_size=128): + return page * page_size + offset diff --git a/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py b/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py index f206f3493..6086590e5 100644 --- a/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py +++ b/sonic_platform_base/sonic_xcvr/xcvr_api_factory.py @@ -13,6 +13,10 @@ from .mem_maps.public.cmis import CmisMemMap from .mem_maps.public.c_cmis import CCmisMemMap +from .codes.credo.aec_800g import CmisAec800gCodes +from .api.credo.aec_800g import CmisAec800gApi +from .mem_maps.credo.aec_800g import CmisAec800gMemMap + from .codes.public.sff8436 import Sff8436Codes from .api.public.sff8436 import Sff8436Api from .mem_maps.public.sff8436 import Sff8436MemMap @@ -25,6 +29,11 @@ from .api.public.sff8472 import Sff8472Api from .mem_maps.public.sff8472 import Sff8472MemMap +VENDOR_NAME_OFFSET = 129 +VENDOR_PART_NUM_OFFSET = 148 +VENDOR_NAME_LENGTH = 16 +VENDOR_PART_NUM_LENGTH = 16 + class XcvrApiFactory(object): def __init__(self, reader, writer): self.reader = reader @@ -36,15 +45,38 @@ def _get_id(self): return None return id_byte_raw[0] + def _get_vendor_name(self): + name_data = self.reader(VENDOR_NAME_OFFSET, VENDOR_NAME_LENGTH) + if name_data is None: + return None + vendor_name = name_data.decode() + return vendor_name.strip() + + def _get_vendor_part_num(self): + part_num = self.reader(VENDOR_PART_NUM_OFFSET, VENDOR_PART_NUM_LENGTH) + if part_num is None: + return None + vendor_pn = part_num.decode() + return vendor_pn.strip() + def create_xcvr_api(self): # TODO: load correct classes from id_mapping file id = self._get_id() + vendor_name = self._get_vendor_name() + vendor_pn = self._get_vendor_part_num() # QSFP-DD or OSFP if id == 0x18 or id == 0x19 or id == 0x1e: - codes = CmisCodes - mem_map = CmisMemMap(codes) - xcvr_eeprom = XcvrEeprom(self.reader, self.writer, mem_map) - api = CmisApi(xcvr_eeprom) + if vendor_name == 'Credo' and vendor_pn == 'CAC81X321M2MC1MS': + codes = CmisAec800gCodes + mem_map = CmisAec800gMemMap(CmisAec800gCodes) + xcvr_eeprom = XcvrEeprom(self.reader, self.writer, mem_map) + api = CmisAec800gApi(xcvr_eeprom) + else: + codes = CmisCodes + mem_map = CmisMemMap(codes) + xcvr_eeprom = XcvrEeprom(self.reader, self.writer, mem_map) + api = CmisApi(xcvr_eeprom) + if api.is_coherent_module(): mem_map = CCmisMemMap(codes) xcvr_eeprom = XcvrEeprom(self.reader, self.writer, mem_map) diff --git a/tests/sonic_xcvr/test_xcvr_api_factory.py b/tests/sonic_xcvr/test_xcvr_api_factory.py new file mode 100644 index 000000000..d87be8862 --- /dev/null +++ b/tests/sonic_xcvr/test_xcvr_api_factory.py @@ -0,0 +1,47 @@ +from unittest.mock import patch +from mock import MagicMock +import pytest + +from sonic_platform_base.sonic_xcvr.api.credo.aec_800g import CmisAec800gApi +from sonic_platform_base.sonic_xcvr.mem_maps.credo.aec_800g import CmisAec800gMemMap +from sonic_platform_base.sonic_xcvr.xcvr_eeprom import XcvrEeprom +from sonic_platform_base.sonic_xcvr.codes.credo.aec_800g import CmisAec800gCodes +from sonic_platform_base.sonic_xcvr.fields import consts +from sonic_platform_base.sonic_xcvr.xcvr_api_factory import XcvrApiFactory + +class BytesMock(bytes): + def decode(self, encoding='utf-8', errors='strict'): + return 'DecodedCredo' + +class TestXcvrApiFactory(object): + read_eeprom = MagicMock + write_eeprom = MagicMock + api = XcvrApiFactory(read_eeprom, write_eeprom) + + def test_get_vendor_name(self): + self.api.reader = MagicMock() + self.api.reader.return_value = b'Credo' + with patch.object(BytesMock, 'decode', return_value='DecodedCredo'): + result = self.api._get_vendor_name() + assert result == 'Credo'.strip() + + def test_get_vendor_part_num(self): + self.api.reader = MagicMock() + self.api.reader.return_value = b'CAC81X321M2MC1MS' + with patch.object(BytesMock, 'decode', return_value='DecodedCAC81X321M2MC1MS'): + result = self.api._get_vendor_part_num() + assert result == 'CAC81X321M2MC1MS'.strip() + + def mock_reader(self, start, length): + return bytes([0x18]) + + @patch('sonic_platform_base.sonic_xcvr.xcvr_api_factory.XcvrApiFactory._get_vendor_name', MagicMock(return_value='Credo')) + @patch('sonic_platform_base.sonic_xcvr.xcvr_api_factory.XcvrApiFactory._get_vendor_part_num', MagicMock(return_value='CAC81X321M2MC1MS')) + def test_create_xcvr_api(self): + self.api.reader = self.mock_reader + CmisAec800gCodes = MagicMock() + CmisAec800gMemMap = MagicMock() + XcvrEeprom = MagicMock() + CmisAec800gApi = MagicMock() + self.api.create_xcvr_api() +