diff --git a/CODEOWNERS b/CODEOWNERS index cd88bcaedd92..fff4b36c499c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -706,6 +706,7 @@ /scripts/west_commands/create_board/ @gmarull /scripts/west_commands/sbom/ @nrfconnect/ncs-co-scripts /scripts/west_commands/thingy91x_dfu.py @nrfconnect/ncs-cia +/scripts/west_commands/ncs_ironside_se_update.py @nrfconnect/ncs-aurora /scripts/west_commands/ncs_provision.py @nrfconnect/ncs-pluto /scripts/bootloader/ @nrfconnect/ncs-pluto /scripts/reglock.py @nrfconnect/ncs-pluto diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 8c68bfc898d3..45a222ce2363 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -487,7 +487,8 @@ See the changelog for each library in the :doc:`nrfxlib documentation str: + # First word is semantic versioned 8 bit each + seqnum, patch, minor, major = struct.unpack("bbbb", version_bytes[0:4]) + + extraversion = c_char_p(version_bytes[4:]).value.decode("utf-8") + + return f"{major}.{minor}.{patch}-{extraversion}+{seqnum}" + + def _decode_status(self, status_bytes: bytes) -> str: + status = int.from_bytes(status_bytes, "little") + try: + return f"{hex(status)} - {UPDATE_STATUS_MSG[status]}" + except KeyError: + return f"{hex(status)} - (unknown)" + + def do_run(self, args, unknown_args): + def nrfutil_device(cmd: str) -> str: + cmd = f"nrfutil device {cmd} --serial-number {args.serial}" + self.dbg(cmd) + result = subprocess.run(cmd, shell=True, text=True, capture_output=True) + self.dbg(result.stdout) + return result.stdout + + def nrfutil_read(address: int, num_bytes: int) -> bytes: + json_out = json.loads( + nrfutil_device( + f"x-read --direct --json --skip-overhead --address 0x{address:x} --bytes {num_bytes}" + ) + ) + return bytes(json_out["devices"][0]["memoryData"][0]["values"]) + + def get_version() -> str: + address = ( + IRONSIDE_SE_RECOVERY_VERSION_ADDR + if args.recovery + else IRONSIDE_SE_VERSION_ADDR + ) + return self._decode_version(nrfutil_read(address, 16)) + + def get_status() -> str: + return self._decode_status(nrfutil_read(UPDATE_STATUS_ADDR, 4)) + + def program(hex_file: PosixPath) -> None: + nrfutil_device( + f"program --options chip_erase_mode=ERASE_NONE --firmware {hex_file}" + ) + + if not args.allow_erase: + raise RuntimeError( + "Unable to perform update without erasing the device, set '--allow-erase'" + ) + with TemporaryDirectory() as tmpdir: + with ZipFile(args.zip.name, "r") as zip_ref: + zip_ref.extractall(tmpdir) + update_app = Path(tmpdir, "update/update_application.hex") + if args.recovery: + update_hex = "ironside_se_recovery_update.hex" + else: + update_hex = "ironside_se_update.hex" + + update_to_install = Path(tmpdir, "update", update_hex) + if not update_to_install.exists(): + raise RuntimeError("Unable to locate update hex within zip file") + + self.inf( + f"Version before update: {get_version()}, status: {get_status()}" + ) + + program(update_app) + program(update_to_install) + + self.dbg("Reset to execute update service") + nrfutil_device(f"reset --reset-kind RESET_PIN") + time.sleep(args.wait) + + self.dbg("Reset to trigger update installation") + nrfutil_device(f"reset --reset-kind RESET_PIN") + self.dbg("Waiting for update to complete") + time.sleep(args.wait) + + self.inf( + f"Version after update: {get_version()}, status: {get_status()}" + ) + self.inf( + "The update application is still programmed, please program proper image" + )