Skip to content

Commit

Permalink
Firmware upgrade CLI support for QSFP-DD transceivers (#244)
Browse files Browse the repository at this point in the history
* Changes to support CMIS firmware download CLI

Signed-off-by: Prince George <prgeor@microsoft.com>

* Address review comments
  • Loading branch information
prgeor committed Nov 29, 2021
1 parent cd69212 commit dba17c8
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 121 deletions.
88 changes: 39 additions & 49 deletions sonic_platform_base/sonic_xcvr/api/public/cmis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
'''
Expand Down Expand Up @@ -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):
'''
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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):
Expand Down
37 changes: 18 additions & 19 deletions sonic_platform_base/sonic_xcvr/api/public/cmisCDB.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
11 changes: 10 additions & 1 deletion sonic_platform_base/sonic_xcvr/sfp_optoe_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading

0 comments on commit dba17c8

Please sign in to comment.