diff --git a/sonic_platform_base/sonic_xcvr/api/public/cmis.py b/sonic_platform_base/sonic_xcvr/api/public/cmis.py index 576154f3c..7d89137ca 100644 --- a/sonic_platform_base/sonic_xcvr/api/public/cmis.py +++ b/sonic_platform_base/sonic_xcvr/api/public/cmis.py @@ -111,6 +111,20 @@ class CmisApi(XcvrApi): LowPwrRequestSW = 4 LowPwrAllowRequestHW = 6 + LPO_HOST_ELECTRICAL_INTERFACE_IDS = [ + Sff8024.HOST_ELECTRICAL_INTERFACE[32], + Sff8024.HOST_ELECTRICAL_INTERFACE[33], + Sff8024.HOST_ELECTRICAL_INTERFACE[34], + Sff8024.HOST_ELECTRICAL_INTERFACE[35] + ] + + LPO_SM_MEDIA_INTERFACE_IDS = [ + Sff8024.SM_MEDIA_INTERFACE[151], + Sff8024.SM_MEDIA_INTERFACE[152], + Sff8024.SM_MEDIA_INTERFACE[153], + Sff8024.SM_MEDIA_INTERFACE[154] + ] + # Default caching enabled; control via classmethod cache_enabled = True @@ -592,6 +606,25 @@ def is_copper(self): media_intf = self.get_module_media_type() return media_intf == "passive_copper_media_interface" if media_intf else None + def is_lpo(self): + ''' + Returns True if the module is LPO, False otherwise + ''' + appl_advt = self.get_application_advertisement() + if not appl_advt: + return False + + for appl_dict in appl_advt.values(): + host_intf = appl_dict.get('host_electrical_interface_id') + media_intf = appl_dict.get('module_media_interface_id') + if ( + host_intf in self.LPO_HOST_ELECTRICAL_INTERFACE_IDS or + media_intf in self.LPO_SM_MEDIA_INTERFACE_IDS + ): + return True + + return False + @read_only_cached_api_return def is_flat_memory(self): return self.xcvr_eeprom.read(consts.FLAT_MEM_FIELD) is not False diff --git a/sonic_platform_base/sonic_xcvr/api/xcvr_api.py b/sonic_platform_base/sonic_xcvr/api/xcvr_api.py index 9d3e99f9a..f0879c7c8 100644 --- a/sonic_platform_base/sonic_xcvr/api/xcvr_api.py +++ b/sonic_platform_base/sonic_xcvr/api/xcvr_api.py @@ -685,3 +685,12 @@ def set_high_power_class(self, power_class, enable): bool: True if the provision succeeds, False if it fails """ raise NotImplementedError + + def is_lpo(self): + """ + Check if the module is Linear Pluggable Optics (LPO) + + Returns: + A Boolean, True if the module is LPO, False otherwise + """ + raise NotImplementedError diff --git a/tests/sonic_xcvr/test_cmis.py b/tests/sonic_xcvr/test_cmis.py index 98f3e98b2..37457cafe 100755 --- a/tests/sonic_xcvr/test_cmis.py +++ b/tests/sonic_xcvr/test_cmis.py @@ -201,6 +201,111 @@ def test_is_copper(self): self.api.get_module_media_type.return_value = "sm_media_interface" assert not self.api.is_copper() + @pytest.mark.parametrize("mock_response, expected", [ + # Test case 1: No application advertisement + (None, False), + # Test case 2: Empty application advertisement + ({}, False), + # Test case 3: Non-LPO host electrical interface + ({ + 1: { + 'host_electrical_interface_id': '400GAUI-8 C2M (Annex 120E)', # ID 17, not LPO + 'module_media_interface_id': '400GBASE-DR4 (Cl 124)' + } + }, False), + # Test case 4: LPO host electrical interface LEI-100G-PAM4-1 (ID 32) + ({ + 1: { + 'host_electrical_interface_id': 'LEI-100G-PAM4-1', # LPO + 'module_media_interface_id': '100GBASE-DR (Cl 140)' + } + }, True), + # Test case 5: LPO host electrical interface LEI-200G-PAM4-2 (ID 33) + ({ + 1: { + 'host_electrical_interface_id': 'LEI-200G-PAM4-2', # LPO + 'module_media_interface_id': '200GBASE-DR2 (Clause 138)' + } + }, True), + # Test case 6: LPO host electrical interface LEI-400G-PAM4-4 (ID 34) + ({ + 1: { + 'host_electrical_interface_id': 'LEI-400G-PAM4-4', # LPO + 'module_media_interface_id': '400GBASE-DR4 (Cl 124)' + } + }, True), + # Test case 7: LPO host electrical interface LEI-800G-PAM4-8 (ID 35) + ({ + 1: { + 'host_electrical_interface_id': 'LEI-800G-PAM4-8', # LPO + 'module_media_interface_id': '800GBASE-DR8 (placeholder)' + } + }, True), + # Test case 8: LPO SM media interface 100G-DR1-LPO (ID 151) + ({ + 1: { + 'host_electrical_interface_id': '100GAUI-2 C2M (Annex 135G)', + 'module_media_interface_id': '100G-DR1-LPO' # LPO + } + }, True), + # Test case 9: LPO SM media interface 200G-DR2-LPO (ID 152) + ({ + 1: { + 'host_electrical_interface_id': '200GAUI-4 C2M (Annex 120E)', + 'module_media_interface_id': '200G-DR2-LPO' # LPO + } + }, True), + # Test case 10: LPO SM media interface 400G-DR4-LPO (ID 153) + ({ + 1: { + 'host_electrical_interface_id': '400GAUI-8 C2M (Annex 120E)', + 'module_media_interface_id': '400G-DR4-LPO' # LPO + } + }, True), + # Test case 11: LPO SM media interface 800G-DR8-LPO (ID 154) + ({ + 1: { + 'host_electrical_interface_id': '800GAUI-16 C2M (Annex 120C)', + 'module_media_interface_id': '800G-DR8-LPO' # LPO + } + }, True), + # Test case 12: Multiple applications, one with LPO + ({ + 1: { + 'host_electrical_interface_id': '400GAUI-8 C2M (Annex 120E)', # Non-LPO + 'module_media_interface_id': '400GBASE-DR4 (Cl 124)' + }, + 2: { + 'host_electrical_interface_id': 'LEI-200G-PAM4-2', # LPO + 'module_media_interface_id': '200GBASE-DR2 (Clause 138)' + } + }, True), + # Test case 13: Both host and media interfaces match LPO + ({ + 1: { + 'host_electrical_interface_id': 'LEI-400G-PAM4-4', # LPO + 'module_media_interface_id': '400G-DR4-LPO' # LPO + } + }, True), + # Test case 14: Application with missing keys + ({ + 1: { + 'host_electrical_interface_id': '100GAUI-2 C2M (Annex 135G)', + # Missing module_media_interface_id + }, + 2: { + # Missing host_electrical_interface_id + 'module_media_interface_id': '100GBASE-DR (Cl 140)' + } + }, False), + ]) + def test_is_lpo(self, mock_response, expected): + """Test is_lpo() method with various application advertisement scenarios""" + with patch.object(self.api, 'get_application_advertisement') as mock_get_app_adv: + mock_get_app_adv.return_value = mock_response + result = self.api.is_lpo() + assert result == expected + @pytest.mark.parametrize("mock_response, expected", [ (False, False) ])