From dba17c8d41c25cced683e1b74d38d6c5543ee807 Mon Sep 17 00:00:00 2001 From: Prince George <45705344+prgeor@users.noreply.github.com> Date: Mon, 29 Nov 2021 13:31:35 +0530 Subject: [PATCH] Firmware upgrade CLI support for QSFP-DD transceivers (#244) * Changes to support CMIS firmware download CLI Signed-off-by: Prince George * Address review comments --- .../sonic_xcvr/api/public/cmis.py | 88 ++++++++----------- .../sonic_xcvr/api/public/cmisCDB.py | 37 ++++---- .../sonic_xcvr/sfp_optoe_base.py | 11 ++- tests/sonic_xcvr/test_cmis.py | 54 +----------- 4 files changed, 69 insertions(+), 121 deletions(-) diff --git a/sonic_platform_base/sonic_xcvr/api/public/cmis.py b/sonic_platform_base/sonic_xcvr/api/public/cmis.py index e8cff76b2a8b..78d70c009510 100644 --- a/sonic_platform_base/sonic_xcvr/api/public/cmis.py +++ b/sonic_platform_base/sonic_xcvr/api/public/cmis.py @@ -23,6 +23,8 @@ class CmisApi(XcvrApi): def __init__(self, xcvr_eeprom): super(CmisApi, self).__init__(xcvr_eeprom) + self.vdm = CmisVdmApi(xcvr_eeprom) + self.cdb = CmisCdbApi(xcvr_eeprom) def get_model(self): ''' @@ -684,13 +686,9 @@ def get_active_apsel_hostlane(self): ''' This function returns the application select code that each host lane has ''' - apsel_dict = {} - if self.is_flat_memory(): - for lane in range(1, self.NUM_CHANNELS+1): - apsel_dict["%s%d" % (consts.ACTIVE_APSEL_HOSTLANE, lane)] = 'N/A' - else: - apsel_dict = self.xcvr_eeprom.read(consts.ACTIVE_APSEL_CODE) - return apsel_dict + if (self.is_flat_memory()): + return {'{}{}'.format(consts.ACTIVE_APSEL_HOSTLANE, i) : 'N/A' for i in range(1, self.NUM_CHANNELS+1)} + return self.xcvr_eeprom.read(consts.ACTIVE_APSEL_CODE) def get_tx_config_power(self): ''' @@ -960,23 +958,10 @@ def set_loopback_mode(self, loopback_mode): else: return 'N/A' - - def get_cdb_api(self): - self.cdb = CmisCdbApi(self.xcvr_eeprom) - return self.cdb - - def get_vdm_api(self): - self.vdm = CmisVdmApi(self.xcvr_eeprom) - return self.vdm - def get_vdm(self): ''' This function returns all the VDM items, including real time monitor value, threholds and flags ''' - try: - self.vdm - except AttributeError: - self.get_vdm_api() vdm = self.vdm.get_vdm_allpage() if not self.is_flat_memory() else {} return vdm @@ -1070,17 +1055,13 @@ def get_module_level_flag(self): 'custom_mon_flags': custom_mon_flags} return module_flag - def get_module_fw_upgrade_feature(self, verbose = False): + def get_module_fw_mgmt_feature(self, verbose = False): """ This function obtains CDB features supported by the module from CDB command 0041h, such as start header size, maximum block size, whether extended payload messaging (page 0xA0 - 0xAF) or only local payload is supported. These features are important because the following upgrade with depend on these parameters. """ - try: - self.cdb - except AttributeError: - self.get_cdb_api() txt = '' # get fw upgrade features (CMD 0041h) starttime = time.time() @@ -1121,7 +1102,7 @@ def get_module_fw_upgrade_feature(self, verbose = False): elapsedtime = time.time()-starttime logger.info('Get module FW upgrade features time: %.2f s\n' %elapsedtime) logger.info(txt) - return {'status': True, 'info': txt, 'result': (startLPLsize, maxblocksize, lplonly_flag, autopaging_flag, writelength)} + return {'status': True, 'info': txt, 'feature': (startLPLsize, maxblocksize, lplonly_flag, autopaging_flag, writelength)} def get_module_fw_info(self): """ @@ -1131,14 +1112,8 @@ def get_module_fw_info(self): Administrative Status: 1=committed, 0=uncommitted Validity Status: 1 = invalid, 0 = valid """ - try: - self.cdb - except AttributeError: - self.get_cdb_api() txt = '' # get fw info (CMD 0100h) - starttime = time.time() - txt += 'Get module FW info\n' rpllen, rpl_chkcode, rpl = self.cdb.get_fw_info() # password issue if self.cdb.cdb_chkcode(rpl) != rpl_chkcode: @@ -1172,6 +1147,10 @@ def get_module_fw_info(self): ImageB = "N/A" txt += 'Image B Version: %s\n' %ImageB + if rpllen > 77: + factory_image = '%d.%d.%d' % (rpl[74], rpl[75], ((rpl[76] << 8) | rpl[77])) + txt += 'Factory Image Version: %s\n' %factory_image + if ImageARunning == 1: RunningImage = 'A' elif ImageBRunning == 1: @@ -1184,15 +1163,22 @@ def get_module_fw_info(self): CommittedImage = 'B' else: CommittedImage = 'N/A' - txt += 'Running Image: %s; Committed Image: %s\n' %(RunningImage, CommittedImage) + txt += 'Running Image: %s\n' % (RunningImage) + txt += 'Committed Image: %s\n' % (CommittedImage) + txt += 'Active Firmware: {}\n'.format(self.get_module_active_firmware()) + txt += 'Inactive Firmware: {}\n'.format(self.get_module_inactive_firmware()) else: txt += 'Reply payload check code error\n' return {'status': False, 'info': txt, 'result': None} - elapsedtime = time.time()-starttime - logger.info('Get module FW info time: %.2f s\n' %elapsedtime) - logger.info(txt) return {'status': True, 'info': txt, 'result': (ImageA, ImageARunning, ImageACommitted, ImageAValid, ImageB, ImageBRunning, ImageBCommitted, ImageBValid)} + def cdb_run_firmware(self, mode = 0x01): + # run module FW (CMD 0109h) + return self.cdb.run_fw_image(mode) + + def cdb_commit_firmware(self): + return self.cdb.commit_fw_image() + def module_fw_run(self, mode = 0x01): """ This command is used to start and run a selected image. @@ -1209,10 +1195,6 @@ def module_fw_run(self, mode = 0x01): This function returns True if firmware run successfully completes. Otherwise it will return False. """ - try: - self.cdb - except AttributeError: - self.get_cdb_api() # run module FW (CMD 0109h) txt = '' starttime = time.time() @@ -1244,10 +1226,6 @@ def module_fw_commit(self): This function returns True if firmware commit successfully completes. Otherwise it will return False. """ - try: - self.cdb - except AttributeError: - self.get_cdb_api() txt = '' # commit module FW (CMD 010Ah) starttime = time.time() @@ -1271,6 +1249,22 @@ def module_fw_commit(self): logger.info(txt) return True, txt + def cdb_firmware_download_complete(self): + # complete FW download (CMD 0107h) + return self.cdb.validate_fw_image() + + def cdb_start_firmware_download(self, startLPLsize, startdata, imagesize): + return self.cdb.start_fw_download(startLPLsize, bytearray(startdata), imagesize) + + def cdb_lpl_block_write(self, address, data): + return self.cdb.block_write_lpl(address, data) + + def cdb_epl_block_write(self, address, data, autopaging_flag, writelength): + return self.cdb.block_write_epl(address, data, autopaging_flag, writelength) + + def cdb_enter_host_password(self, password): + return self.cdb.module_enter_password(password) + def module_fw_download(self, startLPLsize, maxblocksize, lplonly_flag, autopaging_flag, writelength, imagepath): """ This function performs the download of a firmware image to module eeprom @@ -1289,10 +1283,6 @@ def module_fw_download(self, startLPLsize, maxblocksize, lplonly_flag, autopagin This function returns True if download successfully completes. Otherwise it will return False where it fails. """ - try: - self.cdb - except AttributeError: - self.get_cdb_api() txt = '' # start fw download (CMD 0101h) starttime = time.time() @@ -1399,7 +1389,7 @@ def module_fw_upgrade(self, imagepath): _, _, _, _, _, _, _, _ = result['result'] except (ValueError, TypeError): return result['status'], result['info'] - result = self.get_module_fw_upgrade_feature() + result = self.get_module_fw_mgmt_feature() try: startLPLsize, maxblocksize, lplonly_flag, autopaging_flag, writelength = result['result'] except (ValueError, TypeError): diff --git a/sonic_platform_base/sonic_xcvr/api/public/cmisCDB.py b/sonic_platform_base/sonic_xcvr/api/public/cmisCDB.py index fb48e7a81def..ad321ad302d7 100644 --- a/sonic_platform_base/sonic_xcvr/api/public/cmisCDB.py +++ b/sonic_platform_base/sonic_xcvr/api/public/cmisCDB.py @@ -27,7 +27,7 @@ def __init__(self, xcvr_eeprom): self.cdb_instance_supported = self.xcvr_eeprom.read(consts.CDB_SUPPORT) self.failed_status_dict = self.xcvr_eeprom.mem_map.codes.CDB_FAIL_STATUS assert self.cdb_instance_supported != 0 - + def cdb1_chkflags(self): ''' This function detects if there is datapath or module firmware fault. @@ -64,14 +64,14 @@ def cdb1_chkflags(self): return False else: return True - + def cdb_chkcode(self, cmd): ''' This function calculates and returns the checksum of a CDB command ''' checksum = 0 for byte in cmd: - checksum += byte + checksum += byte return 0xff - (checksum & 0xff) def cdb1_chkstatus(self): @@ -140,7 +140,7 @@ def write_cdb(self, cmd): def read_cdb(self): ''' This function reads the reply of a CDB command from page 0x9f. - It returns the reply message of a CDB command. + It returns the reply message of a CDB command. rpllen is the length (number of bytes) of rpl rpl_chkcode is the check code of rpl and can be calculated by cdb_chkcode() rpl is the reply message. @@ -162,7 +162,7 @@ def query_cdb_status(self): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Query CDB status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -186,7 +186,7 @@ def module_enter_password(self, psw = 0x00001011): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Enter password status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -206,7 +206,7 @@ def get_module_feature(self): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Get module feature status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -227,7 +227,7 @@ def get_fw_management_features(self): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Get firmware management feature status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -240,17 +240,16 @@ def get_fw_management_features(self): # Get FW info def get_fw_info(self): ''' - This command returns the firmware versions and firmware default running + This command returns the firmware versions and firmware default running images that reside in the module It returns the reply message of this CDB command 0100h. ''' - # self.module_enter_password(0x00000000) cmd = bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00') cmd[133-INIT_OFFSET] = self.cdb_chkcode(cmd) self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Get firmware info status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -281,7 +280,7 @@ def start_fw_download(self, startLPLsize, header, imagesize): time.sleep(2) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Start firmware download status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -304,7 +303,7 @@ def abort_fw_download(self): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Abort firmware download status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -320,7 +319,7 @@ def block_write_lpl(self, addr, data): This command writes one block of the firmware image into the LPL It returns the status of CDB command 0103h ''' - # lpl_len includes 136-139, four bytes, data is 116-byte long. + # lpl_len includes 136-139, four bytes, data is 116-byte long. lpl_len = len(data) + 4 cmd = bytearray(b'\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') cmd[132-INIT_OFFSET] = lpl_len & 0xff @@ -335,7 +334,7 @@ def block_write_lpl(self, addr, data): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'LPL firmware download status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -387,7 +386,7 @@ def block_write_epl(self, addr, data, autopaging_flag, writelength): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'EPL firmware download status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -409,7 +408,7 @@ def validate_fw_image(self): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Firmware download complete status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -437,7 +436,7 @@ def run_fw_image(self, mode = 0x01): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Run firmware status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") @@ -467,7 +466,7 @@ def commit_fw_image(self): self.write_cdb(cmd) status = self.cdb1_chkstatus() if (status != 0x1): - if status > 127: + if status > 127: txt = 'Commit firmware status: Busy' else: status_txt = self.failed_status_dict.get(status & 0x3f, "Unknown") diff --git a/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py b/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py index 8f6095adc855..b1e657ef9fcb 100644 --- a/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py +++ b/sonic_platform_base/sonic_xcvr/sfp_optoe_base.py @@ -139,13 +139,22 @@ def get_lpmode(self): def set_lpmode(self, lpmode): """ - This common API is applicable only for CMIS as Low Power mode can be controlled + This common API is applicable only for CMIS as Low Power mode can be controlled via EEPROM registers.For other media types like QSFP28/QSFP+ etc., platform vendors has to implement accordingly. """ api = self.get_xcvr_api() return api.set_lp_mode(lpmode) if api is not None else None + def set_optoe_write_max(self, write_max): + sys_path = self.get_eeprom_path() + sys_path = sys_path.replace("eeprom", "write_max") + try: + with open(sys_path, mode='w') as f: + f.write(str(write_max)) + except (OSError, IOError): + pass + def read_eeprom(self, offset, num_bytes): try: with open(self.get_eeprom_path(), mode='rb', buffering=0) as f: diff --git a/tests/sonic_xcvr/test_cmis.py b/tests/sonic_xcvr/test_cmis.py index fd788c3582ad..8737ef31546c 100644 --- a/tests/sonic_xcvr/test_cmis.py +++ b/tests/sonic_xcvr/test_cmis.py @@ -810,12 +810,6 @@ def test_set_loopback_mode(self, input_param, mock_response): self.api.get_loopback_capability.return_value = mock_response self.api.set_loopback_mode(input_param) - def test_get_cdb_api(self): - self.api.get_cdb_api() - - def test_get_vdm_api(self): - self.api.get_vdm_api() - @pytest.mark.parametrize("mock_response, expected",[ ( {'Pre-FEC BER Average Media Input': {1: [0.001, 0.0125, 0, 0.01, 0, False, False, False, False]}}, @@ -886,50 +880,6 @@ def test_get_module_level_flag(self, mock_response, expected): result = self.api.get_module_level_flag() assert result == expected - @pytest.mark.parametrize("input_param, mock_response1, mock_response2, expected", [ - ( - False, - [0x77, 0xff], - [18, 35, (0, 7, 112, 255, 255, 16, 0, 0, 19, 136, 0, 100, 3, 232, 19, 136, 58, 152)], - {'status':True, 'info': 'Auto page support: True\nMax write length: 2048\nStart payload size 112\nMax block size 2048\nWrite to EPL supported\nAbort CMD102h supported True\n', 'result': (112, 2048, False, True, 2048)} - ), - ( - False, - [0x77, 0xff], - [18, 35, (0, 7, 112, 255, 255, 1, 0, 0, 19, 136, 0, 100, 3, 232, 19, 136, 58, 152)], - {'status':True, 'info': 'Auto page support: True\nMax write length: 2048\nStart payload size 112\nMax block size 2048\nWrite to LPL supported\nAbort CMD102h supported True\n', 'result': (112, 2048, True, True, 2048)} - ), - ]) - def test_get_module_fw_upgrade_feature(self, input_param, mock_response1, mock_response2, expected): - self.api.xcvr_eeprom.read = MagicMock() - self.api.xcvr_eeprom.read.side_effect = mock_response1 - self.api.cdb = MagicMock() - self.api.cdb.get_fw_management_features = MagicMock() - self.api.cdb.get_fw_management_features.return_value = mock_response2 - self.api.cdb.cdb_chkcode = MagicMock() - self.api.cdb.cdb_chkcode.return_value = mock_response2[1] - result = self.api.get_module_fw_upgrade_feature(input_param) - assert result == expected - - @pytest.mark.parametrize("mock_response, expected", [ - ( - [110, 26, (3, 3, 0, 0, 0, 1, 1, 4, 3, 0, 0, 100, 3, 232, 19, 136, 58, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)], - {'status':True, 'info': 'Get module FW info\nImage A Version: 0.0.1\nImage B Version: 0.0.0\nRunning Image: A; Committed Image: A\n', 'result': ('0.0.1', 1, 1, 0, '0.0.0', 0, 0, 0)} - ), - ( - [110, 26, (48, 3, 0, 0, 0, 1, 1, 4, 3, 0, 0, 100, 3, 232, 19, 136, 58, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)], - {'status':True, 'info': 'Get module FW info\nImage A Version: 0.0.1\nImage B Version: 0.0.0\nRunning Image: B; Committed Image: B\n', 'result': ('0.0.1', 0, 0, 0, '0.0.0', 1, 1, 0)} - ), - ]) - def test_get_module_fw_info(self, mock_response, expected): - self.api.cdb = MagicMock() - self.api.cdb.get_fw_info = MagicMock() - self.api.cdb.get_fw_info.return_value = mock_response - self.api.cdb.cdb_chkcode = MagicMock() - self.api.cdb.cdb_chkcode.return_value = mock_response[1] - result = self.api.get_module_fw_info() - assert result == expected - @pytest.mark.parametrize("input_param, mock_response, expected", [ (1, 1, (True, 'Module FW run: Success\n')), (1, 64, (False, 'Module FW run: Fail\nFW_run_status 64\n')), @@ -977,8 +927,8 @@ def test_module_fw_commit(self, mock_response, expected): def test_module_fw_upgrade(self, input_param, mock_response, expected): self.api.get_module_fw_info = MagicMock() self.api.get_module_fw_info.return_value = mock_response[0] - self.api.get_module_fw_upgrade_feature = MagicMock() - self.api.get_module_fw_upgrade_feature.return_value = mock_response[1] + self.api.get_module_fw_mgmt_feature = MagicMock() + self.api.get_module_fw_mgmt_feature.return_value = mock_response[1] self.api.module_fw_download = MagicMock() self.api.module_fw_download.return_value = mock_response[2] self.api.module_fw_switch = MagicMock()