diff --git a/sonic-xcvrd/tests/test_xcvrd.py b/sonic-xcvrd/tests/test_xcvrd.py index e5b7d3180..248d3ccd0 100644 --- a/sonic-xcvrd/tests/test_xcvrd.py +++ b/sonic-xcvrd/tests/test_xcvrd.py @@ -1741,6 +1741,70 @@ def test_CmisManagerTask_get_configured_tx_power_from_db(self, mock_table_helper task.xcvr_table_helper.get_cfg_port_tbl = mock_table_helper.get_cfg_port_tbl assert task.get_configured_tx_power_from_db('Ethernet0') == -10 + @pytest.mark.parametrize("appl, host_assign, vendor, expected", [ + (1, 0x1, 'Credo', True), + (2, 0x11, 'Credo', False), + (1, 0x1, 'Molex', False) + ]) + def test_CmisManagerTask_need_lp_mode_for_dpdeinit(self, appl, host_assign, vendor, expected): + port_mapping = PortMapping() + stop_event = threading.Event() + task = CmisManagerTask(DEFAULT_NAMESPACE, port_mapping, stop_event) + + mock_xcvr_api = MagicMock() + mock_xcvr_api.get_manufacturer = MagicMock(return_value=vendor) + mock_xcvr_api.get_host_lane_assignment_option = MagicMock(return_value=host_assign) + assert task.need_lp_mode_for_dpdeinit(mock_xcvr_api, appl) == expected + + @patch('xcvrd.xcvrd.XcvrTableHelper.get_status_tbl') + @patch('xcvrd.xcvrd.platform_chassis') + @patch('xcvrd.xcvrd.is_fast_reboot_enabled', MagicMock(return_value=(False))) + @patch('xcvrd.xcvrd.PortChangeObserver', MagicMock(handle_port_update_event=MagicMock())) + @patch('xcvrd.xcvrd._wrapper_get_sfp_type', MagicMock(return_value='QSFP_DD')) + @patch('xcvrd.xcvrd.CmisManagerTask.wait_for_port_config_done', MagicMock()) + @patch('xcvrd.xcvrd.is_cmis_api', MagicMock(return_value=True)) + def test_CmisManagerTask_need_lp_mode_for_dpdeinit_set_power_expired(self, mock_chassis, mock_get_status_tbl): + # set low power mode expired + mock_get_status_tbl = Table("STATE_DB", TRANSCEIVER_STATUS_TABLE) + mock_xcvr_api = MagicMock() + mock_xcvr_api.is_flat_memory = MagicMock(return_value=False) + mock_xcvr_api.is_coherent_module = MagicMock(return_value=False) + mock_xcvr_api.get_tx_config_power = MagicMock(return_value=0) + mock_xcvr_api.get_laser_config_freq = MagicMock(return_value=0) + mock_xcvr_api.get_module_type_abbreviation = MagicMock(return_value='QSFP-DD') + + mock_sfp = MagicMock() + mock_sfp.get_presence = MagicMock(return_value=True) + mock_sfp.get_xcvr_api = MagicMock(return_value=mock_xcvr_api) + mock_chassis.get_all_sfps = MagicMock(return_value=[mock_sfp]) + mock_chassis.get_sfp = MagicMock(return_value=mock_sfp) + + port_mapping = PortMapping() + stop_event = threading.Event() + task = CmisManagerTask(DEFAULT_NAMESPACE, port_mapping, stop_event) + task.xcvr_table_helper = XcvrTableHelper(DEFAULT_NAMESPACE) + task.xcvr_table_helper.get_status_tbl.return_value = mock_get_status_tbl + + port_change_event = PortChangeEvent('PortConfigDone', -1, 0, PortChangeEvent.PORT_SET) + task.on_port_update_event(port_change_event) + assert task.isPortConfigDone + port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET, + {'speed':'400000', 'lanes':'1,2,3,4,5,6,7,8'}) + task.on_port_update_event(port_change_event) + assert len(task.port_dict) == 1 + task.port_mapping.logical_port_list = MagicMock() + task.get_host_tx_status = MagicMock(return_value='true') + task.get_port_admin_status = MagicMock(return_value='up') + task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True]) + task.update_port_transceiver_status_table_sw_cmis_state("Ethernet0", CMIS_STATE_DP_DEINIT) + mock_xcvr_api.get_module_state = MagicMock(return_value='ModulePwrDn') + task.need_lp_mode_for_dpdeinit = MagicMock(return_value=True) + task.port_dict['Ethernet0']['host_lanes_mask'] = 0xff + task.port_dict['Ethernet0']['appl'] = 1 + task.port_dict['Ethernet0']['cmis_expired'] = datetime.datetime.now() - datetime.timedelta(100) + task.task_worker() + assert get_cmis_state_from_state_db('Ethernet0', task.xcvr_table_helper.get_status_tbl(task.port_mapping.get_asic_id_for_logical_port('Ethernet0'))) == CMIS_STATE_INSERTED + @patch('xcvrd.xcvrd.platform_chassis') @patch('xcvrd.xcvrd.is_fast_reboot_enabled', MagicMock(return_value=(False))) @patch('xcvrd.xcvrd.PortChangeObserver', MagicMock(handle_port_update_event=MagicMock())) @@ -2031,6 +2095,7 @@ def test_CmisManagerTask_task_worker(self, mock_chassis, mock_get_status_tbl): mock_xcvr_api.get_module_type_abbreviation = MagicMock(return_value='QSFP-DD') mock_xcvr_api.get_datapath_init_duration = MagicMock(return_value=60000.0) mock_xcvr_api.get_module_pwr_up_duration = MagicMock(return_value=70000.0) + mock_xcvr_api.get_module_pwr_down_duration = MagicMock(return_value=70000.0) mock_xcvr_api.get_datapath_deinit_duration = MagicMock(return_value=600000.0) mock_xcvr_api.get_cmis_rev = MagicMock(return_value='5.0') mock_xcvr_api.get_supported_freq_config = MagicMock(return_value=(0xA0,0,0,191300,196100)) @@ -2062,7 +2127,6 @@ def test_CmisManagerTask_task_worker(self, mock_chassis, mock_get_status_tbl): 'media_lane_assignment_options': 15 } }) - mock_xcvr_api.get_module_state = MagicMock(return_value='ModuleReady') mock_xcvr_api.get_config_datapath_hostlane_status = MagicMock(return_value={ 'ConfigStatusLane1': 'ConfigSuccess', 'ConfigStatusLane2': 'ConfigSuccess', @@ -2213,17 +2277,21 @@ def test_CmisManagerTask_task_worker(self, mock_chassis, mock_get_status_tbl): # Case 1: Module Inserted --> DP_DEINIT task.is_appl_reconfigure_required = MagicMock(return_value=True) mock_xcvr_api.decommission_all_datapaths = MagicMock(return_value=True) + task.need_lp_mode_for_dpdeinit = MagicMock(return_value=True) task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True]) task.task_worker() assert get_cmis_state_from_state_db('Ethernet0', task.xcvr_table_helper.get_status_tbl(task.port_mapping.get_asic_id_for_logical_port('Ethernet0'))) == CMIS_STATE_DP_DEINIT + assert mock_xcvr_api.set_lpmode.call_count == 1 + mock_xcvr_api.get_module_state = MagicMock(return_value='ModuleLowPwr') task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True]) task.task_worker() assert mock_xcvr_api.set_datapath_deinit.call_count == 1 assert mock_xcvr_api.tx_disable_channel.call_count == 1 - assert mock_xcvr_api.set_lpmode.call_count == 1 + assert mock_xcvr_api.set_lpmode.call_count == 2 assert get_cmis_state_from_state_db('Ethernet0', task.xcvr_table_helper.get_status_tbl(task.port_mapping.get_asic_id_for_logical_port('Ethernet0'))) == CMIS_STATE_AP_CONF # Case 2: DP_DEINIT --> AP Configured + mock_xcvr_api.get_module_state = MagicMock(return_value='ModuleReady') task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True]) task.task_worker() assert mock_xcvr_api.set_application.call_count == 1 diff --git a/sonic-xcvrd/xcvrd/xcvrd.py b/sonic-xcvrd/xcvrd/xcvrd.py index 0c6e9dded..89d760190 100644 --- a/sonic-xcvrd/xcvrd/xcvrd.py +++ b/sonic-xcvrd/xcvrd/xcvrd.py @@ -1144,6 +1144,31 @@ def wait_for_port_config_done(self, namespace): if key in ["PortConfigDone", "PortInitDone"]: break + def need_lp_mode_for_dpdeinit(self, api, appl): + """ + Determine whether the cmis transceiver needs to enter + low power mode when the state is changed to DPDeinit. + """ + host_assign = api.get_host_lane_assignment_option(appl) + vendor = api.get_manufacturer().strip() + + if vendor == "Credo" and host_assign == 0x1: + """ + Credo AEC 400G needs this operation to work correctly on + firmware v4.1. Only one application mode 400GAUI-8 is supported + on firmware v4.1. When 'host_assign' is 1, it means the + application mode 400GAUI-8 is adopted. + Firmware v5.1 supports more application modes like 200GAUI-4, + It is not required to set low power mode when entering + DPDeinit on v5.1, and it also works fine when set low power + mode is set. In conclusion, set low power mode is only + required on Credo AEC 400G when host_assign is 0x1 to + ensure it works correctly. + """ + return True + else: + return False + def task_worker(self): self.xcvr_table_helper = XcvrTableHelper(self.namespaces) @@ -1357,7 +1382,19 @@ def task_worker(self): continue self.log_notice("{}: force Datapath reinit".format(lport)) self.update_port_transceiver_status_table_sw_cmis_state(lport, CMIS_STATE_DP_DEINIT) + if self.need_lp_mode_for_dpdeinit(api, appl): + api.set_lpmode(True) + now = datetime.datetime.now() + modulePwrDownDuration = self.get_cmis_module_power_down_duration_secs(api) + self.log_notice("{}: ModulePwrDown duration {} secs".format(lport, modulePwrDownDuration)) + self.port_dict[lport]['cmis_expired'] = now + datetime.timedelta(seconds=modulePwrDownDuration) elif state == CMIS_STATE_DP_DEINIT: + if self.need_lp_mode_for_dpdeinit(api, appl): + if not self.check_module_state(api, ['ModuleLowPwr']): + if (expired is not None) and (expired <= now): + self.log_notice("{}: timeout for 'ModuleLowPwr'".format(lport)) + self.force_cmis_reinit(lport, retries + 1) + continue # D.2.2 Software Deinitialization api.set_datapath_deinit(host_lanes_mask)