diff --git a/api-ref/source/api-ref-sysinv-v1-config.rst b/api-ref/source/api-ref-sysinv-v1-config.rst index bf5a4d4864..527ba37dee 100644 --- a/api-ref/source/api-ref-sysinv-v1-config.rst +++ b/api-ref/source/api-ref-sysinv-v1-config.rst @@ -5594,6 +5594,7 @@ itemNotFound (404) "description (Optional)", "plain", "xsd:string", "The description of the device image." "image_version (Optional)", "plain", "xsd:string", "The version of the device image." "applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels." + "retimer_included (Optional)", "plain", "xsd:boolean", "This indicates whether the retimer firmware is included in the BMC functional image." "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." "links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage." @@ -5612,6 +5613,7 @@ itemNotFound (404) "description": null, "name": null, "image_version": null, + "retimer_included": false, "applied_labels": { "key1": "value1", @@ -5690,6 +5692,7 @@ itemNotFound (404) "description (Optional)", "plain", "xsd:string", "The description of the device image." "image_version (Optional)", "plain", "xsd:string", "The version of the device image." "applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels." + "retimer_included (Optional)", "plain", "xsd:boolean", "This indicates whether the retimer firmware is included in the BMC functional image." "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." "links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage." @@ -5708,6 +5711,7 @@ itemNotFound (404) "description": null, "name": null, "image_version": null, + "retimer_included": false, "applied_labels": { "key1": "value1", @@ -5746,6 +5750,7 @@ badMediaType (415) "name (Optional)", "plain", "xsd:string", "The name of the device image." "description (Optional)", "plain", "xsd:string", "The description of the device image." "image_version (Optional)", "plain", "xsd:string", "The version of the device image." + "retimer_included (Optional)", "plain", "xsd:boolean", "This indicates whether the retimer firmware is included in the BMC functional image." **Response parameters** @@ -5762,6 +5767,7 @@ badMediaType (415) "name (Optional)", "plain", "xsd:string", "The name of the device image." "description (Optional)", "plain", "xsd:string", "The description of the device image." "image_version (Optional)", "plain", "xsd:string", "The version of the device image." + "retimer_included (Optional)", "plain", "xsd:boolean", "This indicates whether the retimer firmware is included in the BMC functional image." "applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels." "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." @@ -5780,6 +5786,7 @@ badMediaType (415) "description": null, "name": null, "image_version": null, + "retimer_included": false, "applied_labels": null } ] @@ -5830,6 +5837,7 @@ badMediaType (415) "name (Optional)", "plain", "xsd:string", "The name of the device image." "description (Optional)", "plain", "xsd:string", "The description of the device image." "image_version (Optional)", "plain", "xsd:string", "The version of the device image." + "retimer_included (Optional)", "plain", "xsd:boolean", "This indicates whether the retimer firmware is included in the BMC functional image." "applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels." "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." @@ -5848,6 +5856,7 @@ badMediaType (415) "description": null, "name": null, "image_version": null, + "retimer_included": false, "applied_labels": { "key1": "value1" @@ -5902,6 +5911,7 @@ badMediaType (415) "name (Optional)", "plain", "xsd:string", "The name of the device image." "description (Optional)", "plain", "xsd:string", "The description of the device image." "image_version (Optional)", "plain", "xsd:string", "The version of the device image." + "retimer_included (Optional)", "plain", "xsd:boolean", "This indicates whether the retimer firmware is included in the BMC functional image." "applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels." "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." @@ -5920,6 +5930,7 @@ badMediaType (415) "description": null, "name": null, "image_version": null, + "retimer_included": false, "applied_labels": null } ] diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image.py index cfde197af8..295e71c9b9 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image.py @@ -12,7 +12,7 @@ from cgtsclient import exc CREATION_ATTRIBUTES = [ 'bitstream_type', 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', - 'name', 'description', 'image_version', 'uuid'] + 'name', 'description', 'image_version', 'uuid', 'retimer_included'] class DeviceImage(base.Resource): diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image_shell.py index 3f6ef71dc7..2a2d82755f 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/device_image_shell.py @@ -13,7 +13,7 @@ def _print_device_image_show(obj): 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', 'name', 'description', 'image_version', - 'applied', 'applied_labels'] + 'applied', 'applied_labels', 'retimer_included'] if isinstance(obj, dict): data = [(f, obj.get(f, '')) for f in fields] @@ -37,11 +37,11 @@ def do_device_image_list(cc, args): labels = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', - 'name', 'description', 'image_version', + 'name', 'description', 'image_version', 'retimer_included', 'applied', 'applied_labels'] fields = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', - 'name', 'description', 'image_version', + 'name', 'description', 'image_version', 'retimer_included', 'applied', 'applied_labels'] device_images = cc.device_image.list() utils.print_list(device_images, fields, labels, sortby=1) @@ -81,6 +81,9 @@ def do_device_image_list(cc, args): @utils.arg('-u', '--uuid', metavar='', help='UUID of the device image') +@utils.arg('--retimer-included', + metavar='', + help='Retimer firmware included in BMC FW binary') def do_device_image_upload(cc, args): """Upload a device image.""" @@ -90,7 +93,7 @@ def do_device_image_upload(cc, args): field_list = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', - 'name', 'description', 'image_version'] + 'name', 'description', 'image_version', 'retimer_included'] # Prune input fields down to required/expected values user_fields = dict((k, v) for (k, v) in vars(args).items() diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/device_image.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/device_image.py index c6f61eef98..c9bcd7ef85 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/device_image.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/device_image.py @@ -81,6 +81,9 @@ class DeviceImage(base.APIBase): image_version = wtypes.text "The version of the device image" + retimer_included = bool + "Retimer firmware included in BMC firmware binary" + applied = bool "Represent current status: created or applied" @@ -109,7 +112,7 @@ class DeviceImage(base.APIBase): ['id', 'uuid', 'bitstream_type', 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', 'name', 'description', 'image_version', - 'applied', 'applied_labels']) + 'applied', 'applied_labels', 'retimer_included']) # insert applied labels for this device image if they exist device_image = _get_applied_labels(device_image) @@ -246,7 +249,7 @@ class DeviceImageController(rest.RestController): field_list = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device', 'bitstream_id', 'key_signature', 'revoke_key_id', - 'name', 'description', 'image_version'] + 'name', 'description', 'image_version', 'retimer_included'] data = dict((k, v) for (k, v) in pecan.request.POST.items() if k in field_list and not (v is None)) msg = _validate_syntax(data) @@ -431,6 +434,9 @@ def _validate_bitstream_type(dev_img): elif (dev_img['bitstream_type'] == dconstants.BITSTREAM_TYPE_KEY_REVOCATION and 'revoke_key_id' not in dev_img): msg = _("revoke_key_id is required for key revocation bitstream type") + elif (dev_img['bitstream_type'] != dconstants.BITSTREAM_TYPE_FUNCTIONAL and + 'retimer_included' in dev_img.keys()): + msg = _("retimer_included option is only applicable to functional BMC image") return msg @@ -477,6 +483,10 @@ def _validate_syntax(device_image): not cutils.is_uuid_like(device_image['uuid'])): msg = _("uuid must be a valid UUID") return msg + if ('retimer_included' in device_image.keys() and + not cutils.is_valid_boolstr(device_image['retimer_included'])): + msg = _("Parameter retimer_included must be a valid bool string") + return msg msg = _validate_hexadecimal_fields(device_image) if not msg: msg = _validate_bitstream_type(device_image) diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index 98bbace0ff..15057b11bd 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -13705,7 +13705,8 @@ class ConductorManager(service.PeriodicService): (host.hostname, pci_device.pciaddr, filename, device_image_state.id)) fpga_rpcapi = fpga_agent_rpcapi.AgentAPI() fpga_rpcapi.host_device_update_image( - context, host.hostname, pci_device.pciaddr, filename, device_image_state.id) + context, host.hostname, pci_device.pciaddr, filename, device_image_state.id, + device_image.retimer_included) # We've kicked off a device image update, so exit the function. return LOG.info("no more device images to process") diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/119_device_image_retimer.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/119_device_image_retimer.py new file mode 100644 index 0000000000..e87418fdd2 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/119_device_image_retimer.py @@ -0,0 +1,24 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2021 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sqlalchemy import Boolean, Column, MetaData, Table + +ENGINE = 'InnoDB' +CHARSET = 'utf8' + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + dev_img_functional = Table('device_images_functional', meta, autoload=True) + dev_img_functional.create_column(Column('retimer_included', Boolean, default=False)) + + +def downgrade(migrate_engine): + # Downgrade is unsupported in this release. + raise NotImplementedError('SysInv database downgrade is unsupported.') diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index 51731df026..ce64515685 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -1524,6 +1524,7 @@ class DeviceImageFunctional(DeviceImageCommon, DeviceImage): __tablename__ = 'device_images_functional' bitstream_id = Column(String(255), nullable=True) + retimer_included = Column(Boolean, nullable=False, default=False) __mapper_args__ = { 'polymorphic_identity': 'functional', diff --git a/sysinv/sysinv/sysinv/sysinv/fpga_agent/constants.py b/sysinv/sysinv/sysinv/sysinv/fpga_agent/constants.py index f09fe9767f..0fdf408381 100644 --- a/sysinv/sysinv/sysinv/sysinv/fpga_agent/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/fpga_agent/constants.py @@ -35,3 +35,6 @@ OPAE_IMG = "registry.local:9001/docker.io/starlingx/n3000-opae:stx.6.0-v1.0.1" DOCKER_LOGIN_FLAG = "/var/run/docker_login_done" N3000_RESET_FLAG = os.path.join(tsc.VOLATILE_PATH, ".sysinv_n3000_reset") + +# This flag is set if the N3000 requires a second reset +N3000_RETIMER_FLAG = os.path.join(tsc.PLATFORM_CONF_PATH, ".sysinv_n3000_retimer") diff --git a/sysinv/sysinv/sysinv/sysinv/fpga_agent/manager.py b/sysinv/sysinv/sysinv/sysinv/fpga_agent/manager.py index 054490713c..190cef9e8f 100644 --- a/sysinv/sysinv/sysinv/sysinv/fpga_agent/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/fpga_agent/manager.py @@ -551,7 +551,8 @@ class FpgaAgentManager(service.PeriodicService): "this will likely cause problems.") pass - def device_update_image(self, context, pci_addr, filename, transaction_id): + def device_update_image(self, context, pci_addr, filename, transaction_id, + retimer_included): """Write the device image to the device at the specified address. Transaction is the transaction ID as specified by sysinv-conductor. @@ -612,6 +613,9 @@ class FpgaAgentManager(service.PeriodicService): os.remove(local_path) # start the watchdog service again start_watchdog() + # If device image contains c827 retimer firmware, set the retimer flag + if retimer_included: + utils.touch(constants.N3000_RETIMER_FLAG) except exception.SysinvException as exc: LOG.info("setting transaction id %s as failed" % transaction_id) diff --git a/sysinv/sysinv/sysinv/sysinv/fpga_agent/reset_n3000_fpgas.py b/sysinv/sysinv/sysinv/sysinv/fpga_agent/reset_n3000_fpgas.py index f0451eb8a3..1430383d4c 100644 --- a/sysinv/sysinv/sysinv/sysinv/fpga_agent/reset_n3000_fpgas.py +++ b/sysinv/sysinv/sysinv/sysinv/fpga_agent/reset_n3000_fpgas.py @@ -20,6 +20,7 @@ import os import shlex from eventlet.green import subprocess +from glob import glob from oslo_log import log from sysinv.common import utils @@ -30,6 +31,18 @@ from sysinv.fpga_agent import constants # Volatile flag file so we only reset the N3000s once after bootup. LOG = log.getLogger(__name__) +SYSFS_DEVICE_PATH = "/sys/bus/pci/devices/" +FME_PATH = "/fpga/intel-fpga-dev.*/intel-fpga-fme.*/" +SPI_PATH = "spi-altera.*.auto/spi_master/spi*/spi*.*/" + +# These are relative to SPI_PATH +EEPROM_LOAD_PATH = "pkvl/eeprom_load" +EEPROM_UPDATE_STATUS_PATH = "pkvl/eeprom_update_status" + +# The value in eeprom_update_status must be 0x1111 to indicate successful +# update as documented in the Intel FPGA N3000 User Guide +EEPROM_UPDATE_SUCCESS = '0x1111' + def n3000_img_accessible(): cmd = 'docker image list "%s" --format "{{.Repository}}:{{.Tag}}"' % \ @@ -73,6 +86,52 @@ def reset_device_n3000(pci_addr): raise exception.SysinvException(msg) +def get_n3000_sysfs_file(pattern): + """Find a sysfs file related to the N3000. + + The result should be an empty string if the file doesn't exist, + or a single line of text if it does. + """ + + # Convert the pattern to a list of matching filenames + filenames = glob(pattern) + + # If there are no matching files, return an empty string. + if len(filenames) == 0: + return "" + + # If there's more than one filename, complain. + if len(filenames) > 1: + LOG.warn("Pattern %s gave %s matching filenames, using the first." % + (pattern, len(filenames))) + + filename = filenames[0] + return filename + + +def update_device_n3000_retimer(pci_addr): + # Write 1 to the eeprom_load sysfs node of the card + eeprom_load_pattern = (SYSFS_DEVICE_PATH + pci_addr + FME_PATH + + SPI_PATH + EEPROM_LOAD_PATH) + try: + eeprom_load_file = get_n3000_sysfs_file(eeprom_load_pattern) + with open(eeprom_load_file, "w") as writer: + writer.write("1") + except Exception as e: + msg = "Failed to load retimer: %s" % str(e) + LOG.error(msg) + raise exception.SysinvException(msg) + + # Check the eeprom_update_status node for completion + eeprom_update_status_pattern = (SYSFS_DEVICE_PATH + pci_addr + FME_PATH + + SPI_PATH + EEPROM_UPDATE_STATUS_PATH) + eeprom_update_status = get_n3000_sysfs_file(eeprom_update_status_pattern) + with open(eeprom_update_status, 'r') as reader: + status = reader.read() + if EEPROM_UPDATE_SUCCESS not in status: + LOG.error("Failed to update retimer, status=%s" % status) + + def reset_n3000_fpgas(): if not os.path.exists(constants.N3000_RESET_FLAG): # Reset all N3000 FPGAs on the system. @@ -91,9 +150,23 @@ def reset_n3000_fpgas(): except Exception: got_exception = True + if not got_exception and os.path.exists(constants.N3000_RETIMER_FLAG): + # The retimer included flag is set, execute additional steps + fpga_addrs = get_n3000_devices() + for fpga_addr in fpga_addrs: + try: + LOG.info("Updating retimer") + update_device_n3000_retimer(fpga_addr) + LOG.info("Resetting N3000 second time") + reset_device_n3000(fpga_addr) + except Exception: + got_exception = True + LOG.info("Done resetting N3000 FPGAs.") if not got_exception: utils.touch(constants.N3000_RESET_FLAG) + if os.path.exists(constants.N3000_RETIMER_FLAG): + os.remove(constants.N3000_RETIMER_FLAG) return True else: return False diff --git a/sysinv/sysinv/sysinv/sysinv/fpga_agent/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/fpga_agent/rpcapi.py index f97aa121d4..2260ff26bd 100644 --- a/sysinv/sysinv/sysinv/sysinv/fpga_agent/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/fpga_agent/rpcapi.py @@ -52,11 +52,12 @@ class AgentAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): default_version=self.RPC_API_VERSION) def host_device_update_image(self, context, hostname, pci_addr, - filename, transaction_id): + filename, transaction_id, retimer_included): LOG.info("sending device_update_image to host %s" % hostname) topic = '%s.%s' % (self.topic, hostname) return self.cast(context, self.make_msg('device_update_image', pci_addr=pci_addr, filename=filename, - transaction_id=transaction_id), + transaction_id=transaction_id, + retimer_included=retimer_included), topic=topic) diff --git a/sysinv/sysinv/sysinv/sysinv/objects/device_image.py b/sysinv/sysinv/sysinv/sysinv/objects/device_image.py index 28b583c927..e3c3d9b5cc 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/device_image.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/device_image.py @@ -27,6 +27,7 @@ class DeviceImage(base.SysinvObject): 'image_version': utils.str_or_none, 'applied': utils.bool_or_none, 'capabilities': utils.dict_or_none, + 'retimer_included': utils.bool_or_none, } _optional_fields = {'bitstream_id', @@ -34,7 +35,8 @@ class DeviceImage(base.SysinvObject): 'revoke_key_id', 'name', 'description', - 'image_version'} + 'image_version', + 'retimer_included'} @base.remotable_classmethod def get_by_uuid(cls, context, uuid): diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_device_image.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_device_image.py index 2e1d65c2f7..51744a8359 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_device_image.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_device_image.py @@ -276,6 +276,25 @@ class TestPostDeviceImage(TestDeviceImage, dbbase.ControllerHostTestCase): self.assertIn("revoke_key_id is required for key revocation bitstream" " type", str(result)) + def test_create_non_functional_image_with_retimer(self): + # Test creation of device image + bitstream_file = os.path.join(os.path.dirname(__file__), "data", + 'bitstream.bit') + data = { + 'bitstream_type': dconstants.BITSTREAM_TYPE_KEY_REVOCATION, + 'pci_vendor': fpga_constants.N3000_VENDOR, + 'pci_device': fpga_constants.N3000_DEVICE, + 'revoke_key_id': '12345', + 'retimer_included': True, + } + upload_file = [('file', bitstream_file)] + result = self.post_with_files('/device_images', data, + upload_files=upload_file, + headers=self.API_HEADERS, + expect_errors=True) + self.assertIn("retimer_included option is only applicable to" + " functional BMC image", str(result)) + def test_create_bitstream_type_invalid(self): # Test creation of device image bitstream_file = os.path.join(os.path.dirname(__file__), "data",