diff --git a/sonic-xcvrd/tests/test_xcvrd.py b/sonic-xcvrd/tests/test_xcvrd.py index 25c0b9468e37..298b26f3edc7 100644 --- a/sonic-xcvrd/tests/test_xcvrd.py +++ b/sonic-xcvrd/tests/test_xcvrd.py @@ -443,6 +443,27 @@ def test_CmisManagerTask_handle_port_change_event(self): task.on_port_update_event(port_change_event) assert len(task.port_dict) == 1 + + @patch('xcvrd.xcvrd.XcvrTableHelper') + def test_CmisManagerTask_get_configured_freq(self, mock_table_helper): + port_mapping = PortMapping() + task = CmisManagerTask(DEFAULT_NAMESPACE, port_mapping) + cfg_port_tbl = MagicMock() + cfg_port_tbl.get = MagicMock(return_value=(True, (('laser_freq', 193100),))) + mock_table_helper.get_cfg_port_tbl = MagicMock(return_value=cfg_port_tbl) + task.xcvr_table_helper.get_cfg_port_tbl = mock_table_helper.get_cfg_port_tbl + assert task.get_configured_laser_freq_from_db('Ethernet0') == 193100 + + @patch('xcvrd.xcvrd.XcvrTableHelper') + def test_CmisManagerTask_get_configured_tx_power_from_db(self, mock_table_helper): + port_mapping = PortMapping() + task = CmisManagerTask(DEFAULT_NAMESPACE, port_mapping) + cfg_port_tbl = MagicMock() + cfg_port_tbl.get = MagicMock(return_value=(True, (('tx_power', -10),))) + mock_table_helper.get_cfg_port_tbl = MagicMock(return_value=cfg_port_tbl) + 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 + @patch('xcvrd.xcvrd.platform_chassis') @patch('xcvrd.xcvrd_utilities.port_mapping.subscribe_port_update_event', MagicMock(return_value=(None, None))) @patch('xcvrd.xcvrd_utilities.port_mapping.handle_port_update_event', MagicMock()) @@ -469,6 +490,9 @@ def test_CmisManagerTask_task_worker(self, mock_chassis): mock_xcvr_api.set_lpmode = MagicMock(return_value=True) mock_xcvr_api.set_application = MagicMock(return_value=True) mock_xcvr_api.is_flat_memory = MagicMock(return_value=False) + mock_xcvr_api.is_coherent_module = MagicMock(return_value=True) + 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_xcvr_api.get_application_advertisement = MagicMock(return_value={ 1: { @@ -551,6 +575,10 @@ def test_CmisManagerTask_task_worker(self, mock_chassis): task.get_host_tx_status = MagicMock(return_value='true') task.get_port_admin_status = MagicMock(return_value='up') + task.get_configured_tx_power_from_db = MagicMock(return_value=-13) + task.get_configured_laser_freq_from_db = MagicMock(return_value=193100) + task.configure_tx_output_power = MagicMock(return_value=1) + task.configure_laser_frequency = MagicMock(return_value=1) # Case 1: Module Inserted --> DP_DEINIT task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True]) diff --git a/sonic-xcvrd/xcvrd/xcvrd.py b/sonic-xcvrd/xcvrd/xcvrd.py index e5feae753b01..c73ebadb9b9d 100644 --- a/sonic-xcvrd/xcvrd/xcvrd.py +++ b/sonic-xcvrd/xcvrd/xcvrd.py @@ -999,6 +999,11 @@ def on_port_update_event(self, port_change_event): # We dont have better way to check if 'admin_status' is from APPL_DB or STATE_DB so this # check is put temporarily to listen only to APPL_DB's admin_status and ignore that of STATE_DB self.port_dict[lport]['admin_status'] = port_change_event.port_dict['admin_status'] + if 'laser_freq' in port_change_event.port_dict: + self.port_dict[lport]['laser_freq'] = int(port_change_event.port_dict['laser_freq']) + if 'tx_power' in port_change_event.port_dict: + self.port_dict[lport]['tx_power'] = float(port_change_event.port_dict['tx_power']) + self.force_cmis_reinit(lport, 0) else: self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_REMOVED @@ -1121,7 +1126,6 @@ def is_cmis_application_update_required(self, api, channel, speed): skip = False break return (not skip) - return True def force_cmis_reinit(self, lport, retries=0): @@ -1203,6 +1207,32 @@ def check_datapath_state(self, api, channel, states): return done + def get_configured_laser_freq_from_db(self, lport): + """ + Return the Tx power configured by user in CONFIG_DB's PORT table + """ + freq = 0 + asic_index = self.port_mapping.get_asic_id_for_logical_port(lport) + port_tbl = self.xcvr_table_helper.get_cfg_port_tbl(asic_index) + + found, port_info = port_tbl.get(lport) + if found and 'laser_freq' in dict(port_info): + freq = dict(port_info)['laser_freq'] + return int(freq) + + def get_configured_tx_power_from_db(self, lport): + """ + Return the Tx power configured by user in CONFIG_DB's PORT table + """ + power = 0 + asic_index = self.port_mapping.get_asic_id_for_logical_port(lport) + port_tbl = self.xcvr_table_helper.get_cfg_port_tbl(asic_index) + + found, port_info = port_tbl.get(lport) + if found and 'tx_power' in dict(port_info): + power = dict(port_info)['tx_power'] + return float(power) + def get_host_tx_status(self, lport): host_tx_ready = 'false' @@ -1226,11 +1256,31 @@ def get_port_admin_status(self, lport): admin_status = dict(port_info)['admin_status'] return admin_status + def configure_tx_output_power(self, api, lport, tx_power): + min_p, max_p = api.get_supported_power_config() + if tx_power < min_p: + self.log_error("{} configured tx power {} < minimum power {} supported".format(lport, tx_power, min_p)) + if tx_power > max_p: + self.log_error("{} configured tx power {} > maximum power {} supported".format(lport, tx_power, max_p)) + return api.set_tx_power(tx_power) + + def configure_laser_frequency(self, api, lport, freq): + _, _, _, lowf, highf = api.get_supported_freq_config() + if freq < lowf: + self.log_error("{} configured freq:{} GHz is lower than the supported freq:{} GHz".format(lport, freq, lowf)) + if freq > highf: + self.log_error("{} configured freq:{} GHz is higher than the supported freq:{} GHz".format(lport, freq, highf)) + chan = int(round((freq - 193100)/25)) + if chan % 3 != 0: + self.log_error("{} configured freq:{} GHz is NOT in 75GHz grid".format(lport, freq)) + if api.get_tuning_in_progress(): + self.log_error("{} Tuning in progress, channel selection may fail!".format(lport)) + return api.set_laser_freq(freq) + def task_worker(self): self.xcvr_table_helper = XcvrTableHelper(self.namespaces) self.log_notice("Starting...") - print("Starting") # APPL_DB for CONFIG updates, and STATE_DB for insertion/removal sel, asic_context = port_mapping.subscribe_port_update_event(self.namespaces) @@ -1309,6 +1359,12 @@ def task_worker(self): if (type is None) or (type not in self.CMIS_MODULE_TYPES): self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_READY continue + + if api.is_coherent_module(): + if 'tx_power' not in self.port_dict[lport]: + self.port_dict[lport]['tx_power'] = self.get_configured_tx_power_from_db(lport) + if 'laser_freq' not in self.port_dict[lport]: + self.port_dict[lport]['laser_freq'] = self.get_configured_laser_freq_from_db(lport) except AttributeError: # Skip if these essential routines are not available self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_READY @@ -1339,6 +1395,15 @@ def task_worker(self): api.tx_disable_channel(host_lanes, True) self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_READY continue + # Configure the target output power if ZR module + if api.is_coherent_module(): + tx_power = self.port_dict[lport]['tx_power'] + # Prevent configuring same tx power multiple times + if 0 != tx_power and tx_power != api.get_tx_config_power(): + if 1 != self.configure_tx_output_power(api, lport, tx_power): + self.log_error("{} failed to configure Tx power = {}".format(lport, tx_power)) + else: + self.log_notice("{} Successfully configured Tx power = {}".format(lport, tx_power)) appl = self.get_cmis_application_desired(api, host_lanes, host_speed) if appl < 1: @@ -1346,13 +1411,22 @@ def task_worker(self): self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_FAILED continue - has_update = self.is_cmis_application_update_required(api, host_lanes, host_speed) - if not has_update: + need_update = self.is_cmis_application_update_required(api, host_lanes, host_speed) + + # For ZR module, Datapath needes to be re-initlialized on new channel selection + if api.is_coherent_module(): + freq = self.port_dict[lport]['laser_freq'] + # If user requested frequency is NOT the same as configured on the module + # force datapath re-initialization + if 0 != freq and freq != api.get_laser_config_freq(): + need_update = True + + if not need_update: # No application updates self.log_notice("{}: no CMIS application update required...READY".format(lport)) self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_READY continue - + self.log_notice("{}: force Datapath reinit".format(lport)) self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_DP_DEINIT elif state == self.CMIS_STATE_DP_DEINIT: # D.2.2 Software Deinitialization @@ -1382,6 +1456,15 @@ def task_worker(self): self.force_cmis_reinit(lport, retries + 1) continue + if api.is_coherent_module(): + # For ZR module, configure the laser frequency when Datapath is in Deactivated state + freq = self.port_dict[lport]['laser_freq'] + if 0 != freq: + if 1 != self.configure_laser_frequency(api, lport, freq): + self.log_error("{} failed to configure laser frequency {} GHz".format(lport, freq)) + else: + self.log_notice("{} configured laser frequency {} GHz".format(lport, freq)) + # D.1.3 Software Configuration and Initialization appl = self.get_cmis_application_desired(api, host_lanes, host_speed) if appl < 1: @@ -2080,7 +2163,7 @@ def init(self): if multi_asic.is_multi_asic(): # Load the namespace details first from the database_global.json file. swsscommon.SonicDBConfig.initializeGlobalConfig() - # To prevent race condition in get_all_namespaces() we cache the namespaces before + # To prevent race condition in get_all_namespaces() we cache the namespaces before # creating any worker threads self.namespaces = multi_asic.get_front_end_namespaces()