From 58b43ad3cc953548e40796efe059828e9307c0f7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Dec 2024 12:01:28 +0100 Subject: [PATCH 01/12] [ot] scripts/opentitan: pyot.py: add support for virtual test definitions Virtual tests may be used to define multiple tests using the same binary with different execution options/settings Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 15 +++++++++++ scripts/opentitan/pyot.py | 57 ++++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index f2b7c0a9a4bbb..b175bd0558997 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -288,6 +288,10 @@ Sample config for running some non-OpenTitan tests: timeout: 3 machine: ot-earlgrey,no_epmp_cfg=true } + virtual: + { + ot-flash: ${BASEDIR}/flash.raw + } # It would be nice if ELF files could have an .elf extension w/ Cargo # Let's include everything and exclude non-ELF files include: @@ -395,6 +399,15 @@ Sample config for running some non-OpenTitan tests: The option names are the same ones as the script option switches, please refer to the Usage section for details. +* `virtual` + This section defines virtual tests, as a mapping. + Each entry defines the name of the virtual test to create and the actual test binary to execute. + + This enables testing the same binary in multiple configurations, using different options and + settings. + + Virtual tests can be filtered and configured as any regular tests. + * `include` This section contains the list of tests to be run. @@ -422,6 +435,8 @@ Sample config for running some non-OpenTitan tests: Note that `include_from` (and `exclude_from`) do not support globalization patterns (`*` and `?`). + Virtual tests cannot be specified in `include_from`, nor `exclude_from` files. + * `exclude` This section contains the list of tests not to be run. diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index 14b591059fb3a..651b5d6f90527 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -1179,6 +1179,7 @@ def __init__(self, qfm: QEMUFileManager, config: dict[str, any], self._argdict: dict[str, Any] = {} self._qemu_cmd: list[str] = [] self._suffixes = [] + self._virtual_tests: dict[str, str] = {} if hasattr(self._args, 'opts'): setattr(self._args, 'global_opts', getattr(self._args, 'opts')) setattr(self._args, 'opts', []) @@ -1380,7 +1381,7 @@ def _cleanup_temp_files(self, storage: dict[str, set[str]]) -> None: delete_file(filename) def _build_qemu_fw_args(self, args: Namespace) \ - -> tuple[str, str, list[str]]: + -> tuple[str, Optional[str], list[str], Optional[str]]: rom_exec = bool(args.rom_exec) roms = args.rom or [] multi_rom = (len(roms) + int(rom_exec)) > 1 @@ -1397,6 +1398,7 @@ def _build_qemu_fw_args(self, args: Namespace) \ if x) except ValueError: self._log.warning('Unknown variant syntax %s', variant) + rom_counts: list[int] = [0] for chip_id in range(chiplet_count): rom_count = 0 for rom in roms: @@ -1416,9 +1418,13 @@ def _build_qemu_fw_args(self, args: Namespace) \ rom_opt = f'ot-rom_img,id={rom_id},file={rom_path}' fw_args.extend(('-object', rom_opt)) rom_count += 1 + rom_counts.append(rom_count) + rom_count = max(rom_counts) xtype = None if args.exec: - exec_path = self.abspath(args.exec) + exec_path = self._virtual_tests.get(args.exec) + if not exec_path: + exec_path = self.abspath(args.exec) xtype = self.guess_test_type(exec_path) if xtype == 'spiflash': fw_args.extend(('-drive', @@ -1448,7 +1454,9 @@ def _build_qemu_fw_args(self, args: Namespace) \ rom_count += 1 else: fw_args.extend(('-kernel', exec_path)) - return machine, xtype, fw_args + else: + exec_path = None + return machine, xtype, fw_args, exec_path def _build_qemu_log_sources(self, args: Namespace) -> list[str]: if not args.log: @@ -1503,7 +1511,7 @@ def _build_qemu_command(self, args: Namespace, """ if args.qemu is None: raise ValueError('QEMU path is not defined') - machine, xtype, fw_args = self._build_qemu_fw_args(args) + machine, xtype, fw_args, xexec = self._build_qemu_fw_args(args) qemu_args = [args.qemu, '-M', machine] if args.otcfg: qemu_args.extend(('-readconfig', self.abspath(args.otcfg))) @@ -1533,13 +1541,13 @@ def _build_qemu_command(self, args: Namespace, flash_path = self.abspath(args.flash) qemu_args.extend(('-drive', f'if=mtd,bus=1,file={flash_path},' f'format=raw')) - elif any((args.exec, args.boot)): - if args.exec and not isfile(args.exec): - raise ValueError(f'No such exec file: {args.exec}') + elif any((xexec, args.boot)): + if xexec and not isfile(xexec): + raise ValueError(f'No such exec file: {xexec}') if args.boot and not isfile(args.boot): raise ValueError(f'No such bootloader file: {args.boot}') if args.embedded_flash: - flash_file = self._qfm.create_eflash_image(args.exec, args.boot) + flash_file = self._qfm.create_eflash_image(xexec, args.boot) temp_files['flash'].add(flash_file) qemu_args.extend(('-drive', f'if=mtd,bus=1,file={flash_file},' f'format=raw')) @@ -1597,6 +1605,22 @@ def _build_test_list(self, alphasort: bool = True) -> list[str]: tfilters = ['*'] + pfilters else: tfilters = list(pfilters) + virttests = self._config.get('virtual', {}) + if not isinstance(virttests, dict): + raise ValueError('Invalid virtual tests definition') + vtests = {} + for vname, vpath in virttests.items(): + if not isinstance(vname, str): + raise ValueError(f"Invalid virtual test definition '{vname}'") + if sep in vname: + raise ValueError(f"Virtual test name cannot contain directory " + f"specifier: '{vname}'") + rpath = normpath(self._qfm.interpolate(vpath)) + if not isfile(rpath): + raise ValueError(f"Invalid virtual test '{vname}': " + f"missing file '{rpath}'") + vtests[vname] = rpath + self._virtual_tests.update(vtests) inc_filters = self._build_config_list('include') if inc_filters: self._log.debug('Searching for tests from %s dir', testdir) @@ -1610,17 +1634,21 @@ def _build_test_list(self, alphasort: bool = True) -> list[str]: if fnmatchcase(self.get_test_radix(path), tfilter): pathnames.add(path) break + for vpath in vtests: + for tfilter in tfilters: + if fnmatchcase(self.get_test_radix(vpath), tfilter): + pathnames.add(vpath) + break for testfile in self._enumerate_from('include_from'): if not isfile(testfile): raise ValueError(f'Unable to locate test file ' f'"{testfile}"') for tfilter in tfilters: - if fnmatchcase(self.get_test_radix(testfile), - tfilter): + if fnmatchcase(self.get_test_radix(testfile), tfilter): pathnames.add(testfile) if not pathnames: return [] - roms = self._argdict.get('rom') + roms = self._argdict.get('rom', []) pathnames -= {normpath(rom) for rom in roms} xtfilters = [f[1:].strip() for f in cfilters if f.startswith('!')] exc_filters = self._build_config_list('exclude') @@ -1631,6 +1659,11 @@ def _build_test_list(self, alphasort: bool = True) -> list[str]: path_filter = joinpath(testdir, path_filter) paths = set(glob(path_filter, recursive=True)) pathnames -= paths + vdiscards: set[str] = set() + for vpath in vtests: + if fnmatchcase(vpath, basename(path_filter)): + vdiscards.add(vpath) + pathnames -= vdiscards pathnames -= set(self._enumerate_from('exclude_from')) if alphasort: return sorted(pathnames, key=basename) @@ -1684,7 +1717,7 @@ def _build_config_list(self, config_entry: str) -> list: return cfglist def _build_test_args(self, test_name: str) \ - -> tuple[Namespace, list[str], int]: + -> tuple[Namespace, list[str], int, int]: tests_cfg = self._config.get('tests', {}) if not isinstance(tests_cfg, dict): raise ValueError('Invalid tests sub-section') From 000f6175a0aa351351bb3d364a57664b90e83298 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Dec 2024 20:00:26 +0100 Subject: [PATCH 02/12] [ot] scripts/opentitan: pyot.py: enable same app to run every hart Signed-off-by: Emmanuel Blot --- scripts/opentitan/pyot.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index 651b5d6f90527..1081f6dd42725 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -1438,19 +1438,20 @@ def _build_qemu_fw_args(self, args: Namespace) \ raise ValueError(f'No support for test type: {xtype} ' f'({basename(exec_path)})') if rom_exec: - # generate ROM option for the application itself - rom_ids = [] - if args.first_soc: - if chiplet_count == 1: - rom_ids.append(f'{args.first_soc}.') - else: - rom_ids.append(f'{args.first_soc}0.') - rom_ids.append('rom') - if multi_rom: - rom_ids.append(f'{rom_count}') - rom_id = ''.join(rom_ids) - rom_opt = f'ot-rom_img,id={rom_id},file={exec_path}' - fw_args.extend(('-object', rom_opt)) + # generate ROM option(s) for the application itself + for chip in range(chiplet_count): + rom_id_parts = [] + if args.first_soc: + if chiplet_count == 1: + rom_id_parts.append(f'{args.first_soc}.') + else: + rom_id_parts.append(f'{args.first_soc}{chip}.') + rom_id_parts.append('rom') + if multi_rom: + rom_id_parts.append(f'{rom_count}') + rom_id = ''.join(rom_id_parts) + rom_opt = f'ot-rom_img,id={rom_id},file={exec_path}' + fw_args.extend(('-object', rom_opt)) rom_count += 1 else: fw_args.extend(('-kernel', exec_path)) From fe5331cd19a53e71f6738bfe7258c5dd7d63ea45 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Dec 2024 19:06:15 +0100 Subject: [PATCH 03/12] [ot] python/qemu: ot.lc_ctrl.lcdmi: add an observer to expose the DTM Signed-off-by: Emmanuel Blot --- python/qemu/ot/lc_ctrl/lcdmi.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/qemu/ot/lc_ctrl/lcdmi.py b/python/qemu/ot/lc_ctrl/lcdmi.py index bcc0c5fb2931a..84bd7ffef3ef2 100644 --- a/python/qemu/ot/lc_ctrl/lcdmi.py +++ b/python/qemu/ot/lc_ctrl/lcdmi.py @@ -110,6 +110,11 @@ def status(self) -> dict[str, bool]: for b in range(len(self.STATUS))} return status + @property + def dtm(self) -> DebugTransportModule: + """Return the DTM""" + return self._dtm + def disable_claim_transition_if_regwen(self): """Disable claim transition interface.""" self._write_reg('claim_transition_if_regwen', 0) From 71b29558a073da15d8711b23d6bc5fd17433b3d9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Dec 2024 20:16:46 +0100 Subject: [PATCH 04/12] [ot] python/qemu: ot.devproxy: shorten binary debug log messages Signed-off-by: Emmanuel Blot --- python/qemu/ot/devproxy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/qemu/ot/devproxy.py b/python/qemu/ot/devproxy.py index 532d6daebf20d..af0461717ab52 100644 --- a/python/qemu/ot/devproxy.py +++ b/python/qemu/ot/devproxy.py @@ -1665,7 +1665,8 @@ def _receive(self): if len(buffer) < length: continue packet = bytes(buffer[:length]) - self._log.debug('RX payload:%s', self.to_str(packet)) + self._log.debug('RX payload:%s%s', self.to_str(packet)[:80], + '...' if len(packet) > 80 else '') buffer = buffer[length:] if resp: if self._tx_uid != uid: From 85358f86f9dd1f85e67d63292785ba83ba722c9e Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Dec 2024 17:18:14 +0100 Subject: [PATCH 05/12] [ot] hw/opentitan: ot_spi_device: remove a left over debug trace Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_spi_device.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index e39bd811892c4..d7800eef1569b 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1524,7 +1524,6 @@ static void ot_spi_device_flash_decode_sw_command(OtSPIDeviceState *s) f->len = 1u; FLASH_CHANGE_STATE(f, UP_DUMMY); } else if (ot_spi_device_flash_has_input_payload(f->cmd_info)) { - qemu_log("%s INPUT PAYLOAD as %08x\n", __func__, f->cmd_info); ot_spi_device_flash_init_payload(s); } else { s->spi_regs[R_UPLOAD_STATUS2] = 0; From 0111ddb8a122cf6bd94471ab79a379b223c8952e Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Dec 2024 16:08:43 +0100 Subject: [PATCH 06/12] [ot] hw/jtag: tap_ctrl_rbb: rename variable to be more coherent with other files Signed-off-by: Emmanuel Blot --- hw/jtag/tap_ctrl_rbb.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/jtag/tap_ctrl_rbb.c b/hw/jtag/tap_ctrl_rbb.c index 342dce5cd5405..1a8363bc56cc2 100644 --- a/hw/jtag/tap_ctrl_rbb.c +++ b/hw/jtag/tap_ctrl_rbb.c @@ -235,18 +235,18 @@ static void tap_ctrl_rbb_tap_reset(TapCtrlRbbState *tap) static void tap_ctrl_rbb_system_reset(TapCtrlRbbState *tap) { - Object *mc = qdev_get_machine(); - ObjectClass *oc = object_get_class(mc); + Object *ms = qdev_get_machine(); + ObjectClass *mc = object_get_class(ms); (void)tap; - if (!object_class_dynamic_cast(oc, TYPE_RESETTABLE_INTERFACE)) { + if (!object_class_dynamic_cast(mc, TYPE_RESETTABLE_INTERFACE)) { qemu_log_mask(LOG_UNIMP, "%s: Machine %s is not resettable\n", __func__, - object_get_typename(mc)); + object_get_typename(ms)); return; } trace_tap_ctrl_rbb_system_reset(); - resettable_reset(mc, RESET_TYPE_COLD); + resettable_reset(ms, RESET_TYPE_COLD); } static TAPState tap_ctrl_rbb_get_next_state(TapCtrlRbbState *tap, bool tms) From 10c2cac52a0a2baa42efc9e374d17220d54cab66 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 11 Dec 2024 18:57:30 +0100 Subject: [PATCH 07/12] [ot] hw/opentitan: ot_spi_device: fix indentation of register fields Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_spi_device.c | 236 ++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 116 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index d7800eef1569b..02b243a1877d4 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -56,111 +56,113 @@ #define PARAM_NUM_ALERTS 1u #define PARAM_REG_WIDTH 32u +/* clang-format off */ + /* SPI device registers */ REG32(INTR_STATE, 0x0u) -SHARED_FIELD(INTR_GENERIC_RX_FULL, 0u, 1u) -SHARED_FIELD(INTR_GENERIC_RX_WATERMARK, 1u, 1u) -SHARED_FIELD(INTR_GENERIC_TX_WATERMARK, 2u, 1u) -SHARED_FIELD(INTR_GENERIC_RX_ERROR, 3u, 1u) -SHARED_FIELD(INTR_GENERIC_RX_OVERFLOW, 4u, 1u) -SHARED_FIELD(INTR_GENERIC_TX_UNDERFLOW, 5u, 1u) -SHARED_FIELD(INTR_UPLOAD_CMDFIFO_NOT_EMPTY, 6u, 1u) -SHARED_FIELD(INTR_UPLOAD_PAYLOAD_NOT_EMPTY, 7u, 1u) -SHARED_FIELD(INTR_UPLOAD_PAYLOAD_OVERFLOW, 8u, 1u) -SHARED_FIELD(INTR_READBUF_WATERMARK, 9u, 1u) -SHARED_FIELD(INTR_READBUF_FLIP, 10u, 1u) -SHARED_FIELD(INTR_TPM_HEADER_NOT_EMPTY, 11u, 1u) + SHARED_FIELD(INTR_GENERIC_RX_FULL, 0u, 1u) + SHARED_FIELD(INTR_GENERIC_RX_WATERMARK, 1u, 1u) + SHARED_FIELD(INTR_GENERIC_TX_WATERMARK, 2u, 1u) + SHARED_FIELD(INTR_GENERIC_RX_ERROR, 3u, 1u) + SHARED_FIELD(INTR_GENERIC_RX_OVERFLOW, 4u, 1u) + SHARED_FIELD(INTR_GENERIC_TX_UNDERFLOW, 5u, 1u) + SHARED_FIELD(INTR_UPLOAD_CMDFIFO_NOT_EMPTY, 6u, 1u) + SHARED_FIELD(INTR_UPLOAD_PAYLOAD_NOT_EMPTY, 7u, 1u) + SHARED_FIELD(INTR_UPLOAD_PAYLOAD_OVERFLOW, 8u, 1u) + SHARED_FIELD(INTR_READBUF_WATERMARK, 9u, 1u) + SHARED_FIELD(INTR_READBUF_FLIP, 10u, 1u) + SHARED_FIELD(INTR_TPM_HEADER_NOT_EMPTY, 11u, 1u) REG32(INTR_ENABLE, 0x4u) REG32(INTR_TEST, 0x8u) REG32(ALERT_TEST, 0xcu) -FIELD(ALERT_TEST, FATAL_FAULT, 0u, 1u) + FIELD(ALERT_TEST, FATAL_FAULT, 0u, 1u) REG32(CONTROL, 0x10u) -FIELD(CONTROL, ABORT, 0u, 1u) -FIELD(CONTROL, MODE, 4u, 2u) -FIELD(CONTROL, RST_TXFIFO, 16u, 1u) -FIELD(CONTROL, RST_RXFIFO, 17u, 1u) -FIELD(CONTROL, SRAM_CLK_EN, 31u, 1u) + FIELD(CONTROL, ABORT, 0u, 1u) + FIELD(CONTROL, MODE, 4u, 2u) + FIELD(CONTROL, RST_TXFIFO, 16u, 1u) + FIELD(CONTROL, RST_RXFIFO, 17u, 1u) + FIELD(CONTROL, SRAM_CLK_EN, 31u, 1u) REG32(CFG, 0x14u) -FIELD(CFG, CPOL, 0u, 1u) -FIELD(CFG, CPHA, 1u, 1u) -FIELD(CFG, TX_ORDER, 2u, 1u) -FIELD(CFG, RX_ORDER, 3u, 1u) -FIELD(CFG, TIMER_V, 8u, 8u) -FIELD(CFG, ADDR_4B_EN, 16u, 1u) -FIELD(CFG, MAILBOX_EN, 24u, 1u) + FIELD(CFG, CPOL, 0u, 1u) + FIELD(CFG, CPHA, 1u, 1u) + FIELD(CFG, TX_ORDER, 2u, 1u) + FIELD(CFG, RX_ORDER, 3u, 1u) + FIELD(CFG, TIMER_V, 8u, 8u) + FIELD(CFG, ADDR_4B_EN, 16u, 1u) + FIELD(CFG, MAILBOX_EN, 24u, 1u) REG32(FIFO_LEVEL, 0x18u) -FIELD(FIFO_LEVEL, RXLVL, 0u, 16u) -FIELD(FIFO_LEVEL, TXLVL, 16u, 16u) + FIELD(FIFO_LEVEL, RXLVL, 0u, 16u) + FIELD(FIFO_LEVEL, TXLVL, 16u, 16u) REG32(ASYNC_FIFO_LEVEL, 0x1cu) -FIELD(ASYNC_FIFO_LEVEL, RXLVL, 0u, 8u) -FIELD(ASYNC_FIFO_LEVEL, TXLVL, 16, 8u) + FIELD(ASYNC_FIFO_LEVEL, RXLVL, 0u, 8u) + FIELD(ASYNC_FIFO_LEVEL, TXLVL, 16, 8u) REG32(STATUS, 0x20u) -FIELD(STATUS, RXF_FULL, 0u, 1u) -FIELD(STATUS, RXF_EMPTY, 1u, 1u) -FIELD(STATUS, TXF_FULL, 2u, 1u) -FIELD(STATUS, TXF_EMPTY, 3u, 1u) -FIELD(STATUS, ABORT_DONE, 4u, 1u) -FIELD(STATUS, CSB, 5u, 1u) -FIELD(STATUS, TPM_CSB, 6u, 1u) + FIELD(STATUS, RXF_FULL, 0u, 1u) + FIELD(STATUS, RXF_EMPTY, 1u, 1u) + FIELD(STATUS, TXF_FULL, 2u, 1u) + FIELD(STATUS, TXF_EMPTY, 3u, 1u) + FIELD(STATUS, ABORT_DONE, 4u, 1u) + FIELD(STATUS, CSB, 5u, 1u) + FIELD(STATUS, TPM_CSB, 6u, 1u) REG32(RXF_PTR, 0x24u) -FIELD(RXF_PTR, RPTR, 0u, 16u) -FIELD(RXF_PTR, WPTR, 16u, 16u) + FIELD(RXF_PTR, RPTR, 0u, 16u) + FIELD(RXF_PTR, WPTR, 16u, 16u) REG32(TXF_PTR, 0x28u) -FIELD(TXF_PTR, RPTR, 0u, 16u) -FIELD(TXF_PTR, WPTR, 16u, 16u) + FIELD(TXF_PTR, RPTR, 0u, 16u) + FIELD(TXF_PTR, WPTR, 16u, 16u) REG32(RXF_ADDR, 0x2cu) -FIELD(RXF_ADDR, BASE, 0u, 16u) -FIELD(RXF_ADDR, LIMIT, 16u, 16u) + FIELD(RXF_ADDR, BASE, 0u, 16u) + FIELD(RXF_ADDR, LIMIT, 16u, 16u) REG32(TXF_ADDR, 0x30u) -FIELD(TXF_ADDR, BASE, 0u, 16u) -FIELD(TXF_ADDR, LIMIT, 16u, 16u) + FIELD(TXF_ADDR, BASE, 0u, 16u) + FIELD(TXF_ADDR, LIMIT, 16u, 16u) REG32(INTERCEPT_EN, 0x34u) -FIELD(INTERCEPT_EN, STATUS, 0u, 1u) -FIELD(INTERCEPT_EN, JEDEC, 1u, 1u) -FIELD(INTERCEPT_EN, SFDP, 2u, 1u) -FIELD(INTERCEPT_EN, MBX, 3u, 1u) + FIELD(INTERCEPT_EN, STATUS, 0u, 1u) + FIELD(INTERCEPT_EN, JEDEC, 1u, 1u) + FIELD(INTERCEPT_EN, SFDP, 2u, 1u) + FIELD(INTERCEPT_EN, MBX, 3u, 1u) REG32(LAST_READ_ADDR, 0x38u) REG32(FLASH_STATUS, 0x3cu) -FIELD(FLASH_STATUS, BUSY, 0u, 1u) -FIELD(FLASH_STATUS, WEL, 1u, 1u) -FIELD(FLASH_STATUS, BP0, 2u, 1u) -FIELD(FLASH_STATUS, BP1, 3u, 1u) -FIELD(FLASH_STATUS, BP2, 4u, 1u) -FIELD(FLASH_STATUS, TB, 5u, 1u) /* beware actual bits depend on emulated dev. */ -FIELD(FLASH_STATUS, SEC, 6u, 1u) -FIELD(FLASH_STATUS, SRP0, 7u, 1u) -FIELD(FLASH_STATUS, SRP1, 8u, 1u) -FIELD(FLASH_STATUS, QE, 9u, 1u) -FIELD(FLASH_STATUS, LB1, 11u, 1u) -FIELD(FLASH_STATUS, LB2, 12u, 1u) -FIELD(FLASH_STATUS, LB3, 13u, 1u) -FIELD(FLASH_STATUS, CMP, 14u, 1u) -FIELD(FLASH_STATUS, SUS, 15u, 1u) -FIELD(FLASH_STATUS, WPS, 18u, 1u) -FIELD(FLASH_STATUS, DRV0, 21u, 1u) -FIELD(FLASH_STATUS, DRV1, 22u, 1u) -FIELD(FLASH_STATUS, HOLD_NRST, 23u, 1u) + FIELD(FLASH_STATUS, BUSY, 0u, 1u) + FIELD(FLASH_STATUS, WEL, 1u, 1u) + FIELD(FLASH_STATUS, BP0, 2u, 1u) + FIELD(FLASH_STATUS, BP1, 3u, 1u) + FIELD(FLASH_STATUS, BP2, 4u, 1u) + FIELD(FLASH_STATUS, TB, 5u, 1u) /* beware actual bits depend on emulated dev. */ + FIELD(FLASH_STATUS, SEC, 6u, 1u) + FIELD(FLASH_STATUS, SRP0, 7u, 1u) + FIELD(FLASH_STATUS, SRP1, 8u, 1u) + FIELD(FLASH_STATUS, QE, 9u, 1u) + FIELD(FLASH_STATUS, LB1, 11u, 1u) + FIELD(FLASH_STATUS, LB2, 12u, 1u) + FIELD(FLASH_STATUS, LB3, 13u, 1u) + FIELD(FLASH_STATUS, CMP, 14u, 1u) + FIELD(FLASH_STATUS, SUS, 15u, 1u) + FIELD(FLASH_STATUS, WPS, 18u, 1u) + FIELD(FLASH_STATUS, DRV0, 21u, 1u) + FIELD(FLASH_STATUS, DRV1, 22u, 1u) + FIELD(FLASH_STATUS, HOLD_NRST, 23u, 1u) REG32(JEDEC_CC, 0x40u) -FIELD(JEDEC_CC, CC, 0u, 8u) -FIELD(JEDEC_CC, NUM_CC, 8u, 8u) + FIELD(JEDEC_CC, CC, 0u, 8u) + FIELD(JEDEC_CC, NUM_CC, 8u, 8u) REG32(JEDEC_ID, 0x44u) -FIELD(JEDEC_ID, ID, 0u, 16u) -FIELD(JEDEC_ID, MF, 16u, 8u) + FIELD(JEDEC_ID, ID, 0u, 16u) + FIELD(JEDEC_ID, MF, 16u, 8u) REG32(READ_THRESHOLD, 0x48u) -FIELD(READ_THRESHOLD, THRESHOLD, 0u, 10u) + FIELD(READ_THRESHOLD, THRESHOLD, 0u, 10u) REG32(MAILBOX_ADDR, 0x4cu) -FIELD(MAILBOX_ADDR, LOWER, 0u, 9u) -FIELD(MAILBOX_ADDR, UPPER, 10u, 22u) + FIELD(MAILBOX_ADDR, LOWER, 0u, 9u) + FIELD(MAILBOX_ADDR, UPPER, 10u, 22u) REG32(UPLOAD_STATUS, 0x50u) -FIELD(UPLOAD_STATUS, CMDFIFO_DEPTH, 0u, 5u) -FIELD(UPLOAD_STATUS, CMDFIFO_NOTEMPTY, 7u, 1u) -FIELD(UPLOAD_STATUS, ADDRFIFO_DEPTH, 8u, 5u) -FIELD(UPLOAD_STATUS, ADDRFIFO_NOTEMPTY, 15u, 1u) + FIELD(UPLOAD_STATUS, CMDFIFO_DEPTH, 0u, 5u) + FIELD(UPLOAD_STATUS, CMDFIFO_NOTEMPTY, 7u, 1u) + FIELD(UPLOAD_STATUS, ADDRFIFO_DEPTH, 8u, 5u) + FIELD(UPLOAD_STATUS, ADDRFIFO_NOTEMPTY, 15u, 1u) REG32(UPLOAD_STATUS2, 0x54u) -FIELD(UPLOAD_STATUS2, PAYLOAD_DEPTH, 0u, 9u) -FIELD(UPLOAD_STATUS2, PAYLOAD_START_IDX, 16u, 8u) + FIELD(UPLOAD_STATUS2, PAYLOAD_DEPTH, 0u, 9u) + FIELD(UPLOAD_STATUS2, PAYLOAD_START_IDX, 16u, 8u) REG32(UPLOAD_CMDFIFO, 0x58u) -FIELD(UPLOAD_CMDFIFO, DATA, 0u, 8u) + FIELD(UPLOAD_CMDFIFO, DATA, 0u, 8u) REG32(UPLOAD_ADDRFIFO, 0x5cu) REG32(CMD_FILTER_0, 0x60u) REG32(CMD_FILTER_1, 0x64u) @@ -175,18 +177,18 @@ REG32(ADDR_SWAP_DATA, 0x84u) REG32(PAYLOAD_SWAP_MASK, 0x88u) REG32(PAYLOAD_SWAP_DATA, 0x8cu) REG32(CMD_INFO_0, 0x90u) /* ReadStatus1 */ -SHARED_FIELD(CMD_INFO_OPCODE, 0u, 8u) -SHARED_FIELD(CMD_INFO_ADDR_MODE, 8u, 2u) -SHARED_FIELD(CMD_INFO_ADDR_SWAP_EN, 10u, 1u) /* not used in Flash mode */ -SHARED_FIELD(CMD_INFO_MBYTE_EN, 11u, 1u) -SHARED_FIELD(CMD_INFO_DUMMY_SIZE, 12u, 3u) /* limited to bits, ignore in QEMU */ -SHARED_FIELD(CMD_INFO_DUMMY_EN, 15u, 1u) /* only use this bit for dummy cfg */ -SHARED_FIELD(CMD_INFO_PAYLOAD_EN, 16u, 4u) -SHARED_FIELD(CMD_INFO_PAYLOAD_DIR, 20u, 1u) /* not used in Flash mode (guess) */ -SHARED_FIELD(CMD_INFO_PAYLOAD_SWAP_EN, 21u, 1u) /* not used in Flash mode */ -SHARED_FIELD(CMD_INFO_UPLOAD, 24u, 1u) -SHARED_FIELD(CMD_INFO_BUSY, 25u, 1u) -SHARED_FIELD(CMD_INFO_VALID, 31u, 1u) + SHARED_FIELD(CMD_INFO_OPCODE, 0u, 8u) + SHARED_FIELD(CMD_INFO_ADDR_MODE, 8u, 2u) + SHARED_FIELD(CMD_INFO_ADDR_SWAP_EN, 10u, 1u) /* not used in Flash mode */ + SHARED_FIELD(CMD_INFO_MBYTE_EN, 11u, 1u) + SHARED_FIELD(CMD_INFO_DUMMY_SIZE, 12u, 3u) /* limited to bits, ignore in QEMU */ + SHARED_FIELD(CMD_INFO_DUMMY_EN, 15u, 1u) /* only use this bit for dummy cfg */ + SHARED_FIELD(CMD_INFO_PAYLOAD_EN, 16u, 4u) + SHARED_FIELD(CMD_INFO_PAYLOAD_DIR, 20u, 1u) /* not used in Flash mode (guess) */ + SHARED_FIELD(CMD_INFO_PAYLOAD_SWAP_EN, 21u, 1u) /* not used in Flash mode */ + SHARED_FIELD(CMD_INFO_UPLOAD, 24u, 1u) + SHARED_FIELD(CMD_INFO_BUSY, 25u, 1u) + SHARED_FIELD(CMD_INFO_VALID, 31u, 1u) REG32(CMD_INFO_1, 0x94u) /* ReadStatus2 */ REG32(CMD_INFO_2, 0x98u) /* ReadStatus3 */ REG32(CMD_INFO_3, 0x9cu) /* ReadJedecId */ @@ -217,43 +219,45 @@ REG32(CMD_INFO_WRDI, 0xfcu) /* TPM registers */ REG32(TPM_CAP, 0x00u) -FIELD(TPM_CAP, REV, 0u, 8u) -FIELD(TPM_CAP, LOCALITY, 8u, 1u) -FIELD(TPM_CAP, MAX_WR_SIZE, 16u, 3u) -FIELD(TPM_CAP, MAX_RD_SIZE, 20u, 3u) + FIELD(TPM_CAP, REV, 0u, 8u) + FIELD(TPM_CAP, LOCALITY, 8u, 1u) + FIELD(TPM_CAP, MAX_WR_SIZE, 16u, 3u) + FIELD(TPM_CAP, MAX_RD_SIZE, 20u, 3u) REG32(TPM_CFG, 0x04u) -FIELD(TPM_CFG, EN, 0u, 1u) -FIELD(TPM_CFG, TPM_MODE, 1u, 1u) -FIELD(TPM_CFG, HW_REG_DIS, 2u, 1u) -FIELD(TPM_CFG, TPM_REG_CHK_DIS, 3u, 1u) -FIELD(TPM_CFG, INVALID_LOCALITY, 4u, 1u) + FIELD(TPM_CFG, EN, 0u, 1u) + FIELD(TPM_CFG, TPM_MODE, 1u, 1u) + FIELD(TPM_CFG, HW_REG_DIS, 2u, 1u) + FIELD(TPM_CFG, TPM_REG_CHK_DIS, 3u, 1u) + FIELD(TPM_CFG, INVALID_LOCALITY, 4u, 1u) REG32(TPM_STATUS, 0x08u) -FIELD(TPM_STATUS, CMDADDR_NOTEMPTY, 0u, 1u) -FIELD(TPM_STATUS, WRFIFO_DEPTH, 16u, 7u) + FIELD(TPM_STATUS, CMDADDR_NOTEMPTY, 0u, 1u) + FIELD(TPM_STATUS, WRFIFO_DEPTH, 16u, 7u) REG32(TPM_ACCESS_0, 0x0cu) -FIELD(TPM_ACCESS_0, ACCESS_0, 0u, 8u) -FIELD(TPM_ACCESS_0, ACCESS_1, 8u, 8u) -FIELD(TPM_ACCESS_0, ACCESS_2, 16u, 8u) -FIELD(TPM_ACCESS_0, ACCESS_3, 24u, 8u) + FIELD(TPM_ACCESS_0, ACCESS_0, 0u, 8u) + FIELD(TPM_ACCESS_0, ACCESS_1, 8u, 8u) + FIELD(TPM_ACCESS_0, ACCESS_2, 16u, 8u) + FIELD(TPM_ACCESS_0, ACCESS_3, 24u, 8u) REG32(TPM_ACCESS_1, 0x10u) -FIELD(TPM_ACCESS_1, ACCESS_4, 0u, 8u) + FIELD(TPM_ACCESS_1, ACCESS_4, 0u, 8u) REG32(TPM_STS, 0x14u) REG32(TPM_INTF_CAPABILITY, 0x18u) REG32(TPM_INT_ENABLE, 0x1cu) REG32(TPM_INT_VECTOR, 0x20u) -FIELD(TPM_INT_VECTOR, INT_VECTOR, 0u, 8u) + FIELD(TPM_INT_VECTOR, INT_VECTOR, 0u, 8u) REG32(TPM_INT_STATUS, 0x24u) REG32(TPM_DID_VID, 0x28u) -FIELD(TPM_DID_VID, VID, 0u, 16u) -FIELD(TPM_DID_VID, DID, 16u, 16u) + FIELD(TPM_DID_VID, VID, 0u, 16u) + FIELD(TPM_DID_VID, DID, 16u, 16u) REG32(TPM_RID, 0x2cu) -FIELD(TPM_RID, RID, 0u, 8u) + FIELD(TPM_RID, RID, 0u, 8u) REG32(TPM_CMD_ADDR, 0x30u) -FIELD(TPM_CMD_ADDR, ADDR, 0u, 24u) -FIELD(TPM_CMD_ADDR, CMD, 24u, 8u) + FIELD(TPM_CMD_ADDR, ADDR, 0u, 24u) + FIELD(TPM_CMD_ADDR, CMD, 24u, 8u) REG32(TPM_READ_FIFO, 0x34u) REG32(TPM_WRITE_FIFO, 0x38u) -FIELD(TPM_WRITE_FIFO, VALUE, 0u, 8u) + FIELD(TPM_WRITE_FIFO, VALUE, 0u, 8u) + +/* clang-format on */ #define SPI_BUS_PROTO_VER 0 #define SPI_BUS_HEADER_SIZE (2u * sizeof(uint32_t)) From dd781daf5d7e8d7cb756af280ed89b0c744ef889 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Dec 2024 11:44:52 +0100 Subject: [PATCH 08/12] [ot] hw/opentitan: ot_spi_device: fix upload command FIFO status. Interrupt state for upload command FIFO should not be set if no new command has been pushed, even if the CMD FIFO is not empty. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_spi_device.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 02b243a1877d4..09694320711c2 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -458,6 +458,7 @@ typedef struct { QEMUTimer *irq_timer; /* Timer to resume processing after a READBUF_* IRQ */ bool loop; /* Keep reading the buffer if end is reached */ bool watermark; /* Read watermark hit, used as flip-flop */ + bool new_cmd; /* New command has been pushed in current SPI transaction */ } SpiDeviceFlash; typedef struct { @@ -1053,7 +1054,7 @@ static void ot_spi_device_release_cs(OtSPIDeviceState *s) bool update_irq = false; switch (ot_spi_device_get_mode(s)) { case CTRL_MODE_FLASH: - if (!fifo8_is_empty(&f->cmd_fifo)) { + if (!fifo8_is_empty(&f->cmd_fifo) && f->new_cmd) { s->spi_regs[R_INTR_STATE] |= INTR_UPLOAD_CMDFIFO_NOT_EMPTY_MASK; update_irq = true; } @@ -1100,6 +1101,8 @@ static void ot_spi_device_release_cs(OtSPIDeviceState *s) break; } + f->new_cmd = false; + if (update_irq) { ot_spi_device_update_irqs(s); } @@ -1172,6 +1175,7 @@ static void ot_spi_device_flash_decode_command(OtSPIDeviceState *s, uint8_t cmd) } trace_ot_spi_device_flash_upload(f->slot, f->cmd_info, set_busy); fifo8_push(&f->cmd_fifo, COMMAND_OPCODE(f->cmd_info)); + f->new_cmd = true; } } @@ -2609,6 +2613,7 @@ static void ot_spi_device_reset(DeviceState *dev) ot_spi_device_release_cs(s); f->watermark = false; + f->new_cmd = false; s->spi_regs[R_CONTROL] = 0x80000010u; s->spi_regs[R_CFG] = 0x7f00u; s->spi_regs[R_FIFO_LEVEL] = 0x80u; From 520f791c7b70f107634518ce0724b4db2bf3a17d Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Dec 2024 15:30:13 +0100 Subject: [PATCH 09/12] [ot] python/qemu: ot.spi.spi_device: change the read_jedec_id API return a tuple with the page along with the JEDEC ID code. Signed-off-by: Emmanuel Blot --- python/qemu/ot/spi/spi_device.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/spi/spi_device.py b/python/qemu/ot/spi/spi_device.py index fe47d27e78af2..587d59013f12b 100644 --- a/python/qemu/ot/spi/spi_device.py +++ b/python/qemu/ot/spi/spi_device.py @@ -164,17 +164,19 @@ def wait_idle(self, timeout: float = 1.0, pace: float = 0.0005): raise TimeoutError('Flash stuck to busy') sleep(pace) - def read_jedec_id(self) -> bytes: + def read_jedec_id(self) -> tuple[int, bytes]: """Read out the flash device JEDEC ID.""" jedec = bytearray() self.transmit(self.COMMANDS['READ_JEDEC_ID'], release=False) + page = 0 while True: manuf = self.transmit(out_len=1, release=False)[0] if manuf != 0x7f: jedec.append(manuf) break + page += 1 jedec.extend(self.transmit(out_len=2)) - return jedec + return page, jedec def read_sfdp(self, address: int = 0) -> bytes: """Read out the flash device SFTP descriptor.""" From b8aae398ef888b7b6a9dc366f43a2328d4e3970c Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 16 Dec 2024 14:49:17 +0100 Subject: [PATCH 10/12] [ot] python/qemu: ot.spi.spi_device: add support for multi-chunk reads Signed-off-by: Emmanuel Blot --- python/qemu/ot/spi/spi_device.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/spi/spi_device.py b/python/qemu/ot/spi/spi_device.py index 587d59013f12b..cdc4f6597e86d 100644 --- a/python/qemu/ot/spi/spi_device.py +++ b/python/qemu/ot/spi/spi_device.py @@ -242,12 +242,14 @@ def reset(self): # self.transmit(self.COMMANDS['RESET1']) self.transmit(self.COMMANDS['RESET2']) - def read(self, address: int, length: int, fast: bool = False) -> bytes: + def read(self, address: int, length: int, fast: bool = False, + release: bool = True) -> bytes: """Read out from the flash device. :param address: the address of the first byte to read :param length: how many bytes to read :param fast: whether to use the fast SPI read command + :param release: whether to release /CS line """ if fast: cmd = self.COMMANDS['FAST_READ'] @@ -262,7 +264,21 @@ def read(self, address: int, length: int, fast: bool = False) -> bytes: addr = addr[1:] if dummy: addr.append(0) - return self.transmit(cmd, addr, length) + return self.transmit(cmd, addr, length, release) + + def read_cont(self, length: int, release: bool = True) -> bytes: + """Continue a read transation. + + :param length: how many bytes to read + :param release: whether to release /CS line + """ + return self.transmit(None, None, length, release) + + def release(self) -> None: + """Release /CS line.""" + data = self._build_cs_header(0, True) + self._log.debug('/CS') + self._socket.send(data) @staticmethod def rev8(num: int) -> int: From e25e6daad09cfe55b5a6f24ede018ac792c5d5e5 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Dec 2024 12:28:43 +0100 Subject: [PATCH 11/12] [ot] scripts/opentitan: otptool.py: add an option to reset specific fields Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 7 +++-- python/qemu/ot/otp/image.py | 49 +++++++++++++++++++++------------ python/qemu/ot/otp/partition.py | 20 ++++++++++++++ scripts/opentitan/otptool.py | 15 +++++++++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index 9b686d329c7e7..f2197e3e3cc9b 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -9,8 +9,8 @@ controller virtual device. usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o C] [-r RAW] [-k {auto,otp,fuz}] [-e BITS] [-C CONFIG] [-c INT] [-i INT] [-w] [-n] [-s] [-E] [-D] [-U] [--empty PARTITION] - [--clear-bit CLEAR_BIT] [--set-bit SET_BIT] - [--toggle-bit TOGGLE_BIT] [--fix-ecc] + [--erase PART:FIELD] [--clear-bit CLEAR_BIT] + [--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [--fix-ecc] [-G {LCVAL,LCTPL,PARTS,REGS}] [-v] [-d] QEMU OT tool to manage OTP files. @@ -42,6 +42,7 @@ Commands: -U, --update update RAW file after ECC recovery or bit changes --empty PARTITION reset the content of a whole partition, including its digest if any + --erase PART:FIELD clear out an OTP field --clear-bit CLEAR_BIT clear a bit at specified location --set-bit SET_BIT set a bit at specified location @@ -156,6 +157,8 @@ Fuse RAW images only use the v1 type. intended for test purposes. This flag may be repeated. Partition(s) can be specified either by their index or their name. +* `--erase` reset a specific field within a partition. The flag may be repeated. + * `--clear-bit` clears the specified bit in the OTP data. This flag may be repeated. This option is only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit. diff --git a/python/qemu/ot/otp/image.py b/python/qemu/ot/otp/image.py index b42c702782217..f162e8b66b8f2 100644 --- a/python/qemu/ot/otp/image.py +++ b/python/qemu/ot/otp/image.py @@ -360,24 +360,7 @@ def empty_partition(self, partition: Union[int, str]) -> None: :param partition: the partition to empty, either specified as an index or as the partition name """ - part = None - partix = None - if isinstance(partition, int): - try: - part = self._partitions[partition] - partix = partition - except IndexError: - pass - elif isinstance(partition, str): - partname = partition.lower() - try: - partix, part = {(i, p) for i, p in enumerate(self._partitions) - if p.__class__.__name__[:-4].lower() == - partname}.pop() - except KeyError: - pass - if not part: - raise ValueError(f"Unknown partition '{partition}'") + partix, part = self._retrieve_partition(partition) part.empty() if not part.is_empty: raise RuntimeError(f"Unable to empty partition '{partition}'") @@ -389,6 +372,14 @@ def empty_partition(self, partition: Union[int, str]) -> None: self._data[offset:offset+length] = data self._ecc[offset // 2:(offset+length)//2] = bytes(length//2) + def erase_field(self, partition: Union[int, str], field: str) -> None: + """Erase (reset) the content of a field within a partition. + + :param field: the name of the field to erase + """ + part = self._retrieve_partition(partition)[1] + part.erase_field(field) + @staticmethod def bit_parity(data: int) -> int: """Compute the bit parity of an integer, i.e. reduce the vector to a @@ -679,3 +670,25 @@ def _get_partition_at_offset(self, off: int) -> Optional[OtpPartition]: if off < end: return part return None + + def _retrieve_partition(self, partition: Union[int, str]) \ + -> tuple[int, OtpPartition]: + part = None + partix = None + if isinstance(partition, int): + try: + part = self._partitions[partition] + partix = partition + except IndexError: + pass + elif isinstance(partition, str): + partname = partition.lower() + try: + partix, part = {(i, p) for i, p in enumerate(self._partitions) + if p.__class__.__name__[:-4].lower() == + partname}.pop() + except KeyError: + pass + if not part or partix is None: + raise ValueError(f"Unknown partition '{partition}'") + return partix, part diff --git a/python/qemu/ot/otp/partition.py b/python/qemu/ot/otp/partition.py index a4af3f812792a..f6164a1412ff0 100644 --- a/python/qemu/ot/otp/partition.py +++ b/python/qemu/ot/otp/partition.py @@ -201,6 +201,26 @@ def empty(self) -> None: if self.has_digest: self._digest_bytes = bytes(self.DIGEST_SIZE) + def erase_field(self, field: str) -> None: + """Erase (reset) the content of a field. + + :param field: the name of the field to erase + """ + is_digest = self.has_digest and field.upper() == 'DIGEST' + if not is_digest and field not in self.items: + raise ValueError(f"No such field: '{field}'") + offset = 0 + itsize = 0 + for itname, itdef in self.items.items(): + itsize = itdef['size'] + if itname == field: + break + offset += itsize + end = offset + itsize + self._log.info('Erasing 0x%x..0x%x from %s', offset, end, self.name) + self._data = b''.join((self._data[:offset], bytes(itsize), + self._data[end:])) + class OtpLifecycleExtension(OtpLifecycle, OtpPartitionDecoder): """Decoder for Lifecyle bytes sequences. diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index 768fdef96243a..d77e78555c627 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -79,6 +79,8 @@ def main(): default=[], help='reset the content of a whole partition, ' 'including its digest if any') + commands.add_argument('--erase', action='append', metavar='PART:FIELD', + help='clear out an OTP field') commands.add_argument('--clear-bit', action='append', default=[], help='clear a bit at specified location') commands.add_argument('--set-bit', action='append', default=[], @@ -103,7 +105,7 @@ def main(): if not (args.vmem or args.raw): if any((args.show, args.digest, args.ecc_recover, args.clear_bit, - args.set_bit, args.toggle_bit)): + args.set_bit, args.toggle_bit, args.erase)): argparser.error('At least one raw or vmem file is required') if not args.vmem and args.kind: @@ -145,6 +147,8 @@ def main(): argparser.error('Cannot verify OTP digests without an OTP map') if args.empty: argparser.error('Cannot empty OTP partition without an OTP map') + if args.erase: + argparser.error('Cannot erase an OTP field without an OTP map') else: otpmap = OtpMap() otpmap.load(args.otp_map) @@ -203,6 +207,15 @@ def main(): if args.empty: for part in args.empty: otp.empty_partition(part) + for field_desc in args.erase or []: + try: + part, field = field_desc.split(':') + if not isinstance(part, str): + raise ValueError() + except ValueError: + argparser.error('Invalid field specifier, should follow ' + ': syntax') + otp.erase_field(part, field) if args.config: otp.load_config(args.config) if args.iv: From bca5161754718fecb5e33ef0383e3f5a8c3fca36 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 13 Dec 2024 17:29:45 +0100 Subject: [PATCH 12/12] [ot] scripts/opentitan: otptool.py: add a partition filter option - add a new option to select which partition(s) and fields(s) to show when decoding an OTP image file - add a new option to hide the OTP image file version Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 31 +++++++++++++++++---------- python/qemu/ot/otp/image.py | 38 +++++++++++++++++++++++++++++---- python/qemu/ot/otp/partition.py | 31 +++++++++++++++++++++------ scripts/opentitan/otptool.py | 16 +++++++++++--- 4 files changed, 92 insertions(+), 24 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index f2197e3e3cc9b..2035c07144251 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -6,11 +6,12 @@ controller virtual device. ## Usage ````text -usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o C] [-r RAW] +usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o FILE] [-r RAW] [-k {auto,otp,fuz}] [-e BITS] [-C CONFIG] [-c INT] [-i INT] - [-w] [-n] [-s] [-E] [-D] [-U] [--empty PARTITION] - [--erase PART:FIELD] [--clear-bit CLEAR_BIT] - [--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [--fix-ecc] + [-w] [-n] [-f PART:FIELD] [--no-version] [-s] [-E] [-D] [-U] + [--empty PARTITION] [--erase PART:FIELD] + [--clear-bit CLEAR_BIT] [--set-bit SET_BIT] + [--toggle-bit TOGGLE_BIT] [--fix-ecc] [-G {LCVAL,LCTPL,PARTS,REGS}] [-v] [-d] QEMU OT tool to manage OTP files. @@ -22,7 +23,7 @@ Files: -j, --otp-map HJSON input OTP controller memory map file -m, --vmem VMEM input VMEM file -l, --lifecycle SV input lifecycle system verilog file - -o, --output C output filename for C file generation + -o, --output FILE output filename (default to stdout) -r, --raw RAW QEMU OTP raw image file Parameters: @@ -34,6 +35,9 @@ Parameters: -i, --iv INT initialization vector for Present scrambler -w, --wide use wide output, non-abbreviated content -n, --no-decode do not attempt to decode OTP fields + -f, --filter PART:FIELD + filter which OTP fields are shown + --no-version do not report the OTP image version Commands: -s, --show show the OTP content @@ -107,6 +111,9 @@ Fuse RAW images only use the v1 type. * `-e` specify how many bits are used in the VMEM file to store ECC information. Note that ECC information is not stored in the QEMU RAW file for now. +* `-f` select which partition(s) and partition field(s) should be shown when option `-s` is used. + When not specified, all partitions and fields are reported. + * `-i` specify the initialization vector for the Present scrambler used for partition digests. This value is "usually" found within the `hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv` OT file, from the last entry of `RndCnstDigestIV` array, _i.e._ item 0. It is used along with option @@ -153,15 +160,20 @@ Fuse RAW images only use the v1 type. contain long sequence of bytes. If repeated, the empty long fields are also printed in full, as a sequence of empty bytes. +* `--clear-bit` clears the specified bit in the OTP data. This flag may be repeated. This option is + only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such + a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit. + * `--empty` reset a whole parition, including its digest if any and ECC bits. This option is only intended for test purposes. This flag may be repeated. Partition(s) can be specified either by their index or their name. * `--erase` reset a specific field within a partition. The flag may be repeated. -* `--clear-bit` clears the specified bit in the OTP data. This flag may be repeated. This option is - only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such - a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit. +* `--no-version` disable OTP image version reporting when `-s` is used. + +* `--fix-ecc` may be used to rebuild the ECC values for all slots that have been modified using the + ECC modification operations, and any detected error. * `--set-bit` sets the specified bit in the OTP data. This flag may be repeated. This option is only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such @@ -171,9 +183,6 @@ Fuse RAW images only use the v1 type. is only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit. -* `--fix-ecc` may be used to rebuild the ECC values for all slots that have been modified using the - ECC modification operations, and any detected error. - All modification features can only be performed on RAW image, VMEM images are never modified. To modify RAW file content, either a VMEM file is required in addition to the RAW file as the data source, or the `-U` is required to tell that the RAW file should be read, modified and written back. diff --git a/python/qemu/ot/otp/image.py b/python/qemu/ot/otp/image.py index f162e8b66b8f2..9e8ad728ae0d7 100644 --- a/python/qemu/ot/otp/image.py +++ b/python/qemu/ot/otp/image.py @@ -311,17 +311,47 @@ def verify(self, show: bool = False) -> bool: return not any(r is False for r in results.values()) def decode(self, decode: bool = True, wide: int = 0, - ofp: Optional[TextIO] = None) -> None: - """Decode the content of the image, one partition at a time.""" + ofp: Optional[TextIO] = None, decode_version: bool = True, + field_filters: Optional[list[str]] = None) -> None: + """Decode the content of the image, one partition at a time. + + :param decode: whether to attempt to decode value whose encoding/ + meaning is known + :param wide: whether to use compact, truncated long value or emit + the whole value, possibly generating very long lines + :param decode_version: whether to decode and report the version + :param field_filters: optional filter to select which partitions/ + fields to report as a list of : strings, joker + char '*' is supported. + """ version = self.version - if version: + if version and decode_version: print(f'OTP image v{version}') if version > 1: print(f' * present iv {self._digest_iv:016x}') print(f' * present constant {self._digest_constant:032x}') + part_filters: dict[str, str] = {} + partnames = {p.name for p in self._partitions} + for filt in field_filters or []: + parts = filt.split(':') + if len(parts) > 2: + raise ValueError(f"Invalid filter '{filt}'") + part_re = f'^{parts[0].replace("*", ".*")}$' + field = parts[1] if len(parts) > 1 else '*' + for partname in partnames: + if not re.match(part_re, partname, re.IGNORECASE): + continue + if not partname in part_filters: + part_filters[partname] = set() + if field == '*': + # any field would match, discard any existing one + part_filters[partname].clear() + part_filters[partname].add(field) for part in self._partitions: + if part_filters and part.name not in part_filters: + continue base = self._get_partition_bounds(part)[0] - part.decode(base, decode, wide, ofp) + part.decode(base, decode, wide, ofp, part_filters.get(part.name)) def clear_bits(self, bitdefs: Sequence[tuple[int, int]]) -> None: """Clear one or more bits. diff --git a/python/qemu/ot/otp/partition.py b/python/qemu/ot/otp/partition.py index f6164a1412ff0..f0d70bf31fa09 100644 --- a/python/qemu/ot/otp/partition.py +++ b/python/qemu/ot/otp/partition.py @@ -9,7 +9,8 @@ from binascii import hexlify, unhexlify, Error as hexerror from io import BytesIO from logging import getLogger -from typing import BinaryIO, Optional, TextIO +from re import IGNORECASE, match +from typing import BinaryIO, Optional, Sequence, TextIO from .lifecycle import OtpLifecycle @@ -145,7 +146,8 @@ def set_decoder(self, decoder: OtpPartitionDecoder) -> None: self._decoder = decoder def decode(self, base: Optional[int], decode: bool = True, wide: int = 0, - ofp: Optional[TextIO] = None) -> None: + ofp: Optional[TextIO] = None, + filters = Optional[Sequence[str]]) -> None: """Decode the content of the partition.""" buf = BytesIO(self._data) if ofp: @@ -155,6 +157,12 @@ def emit(fmt, *args): emit = self._log.info pname = self.name offset = 0 + soff = 0 + if filters: + fre = '|'.join(f.replace('*', '.*') for f in filters) + filter_re = f'^({fre})$' + else: + filter_re = r'.*' for itname, itdef in self.items.items(): itsize = itdef['size'] itvalue = buf.read(itsize) @@ -164,6 +172,8 @@ def emit(fmt, *args): name = f'{pname}:{itname[len(pname)+1:]}' else: name = f'{pname}:{itname}' + if not match(filter_re, itname, IGNORECASE): + continue if itsize > 8: rvalue = bytes(reversed(itvalue)) sval = hexlify(rvalue).decode() @@ -172,12 +182,16 @@ def emit(fmt, *args): if dval is not None: emit('%-48s %s (decoded) %s', name, soff, dval) continue + ssize = f'{{{itsize}}}' if not sum(itvalue) and wide < 2: - emit('%-48s %s {%d} 0...', name, soff, itsize) + if decode: + emit('%-48s %s %5s (empty)', name, soff, ssize) + else: + emit('%-48s %s %5s 0...', name, soff, ssize) else: if not wide and itsize > self.MAX_DATA_WIDTH: sval = f'{sval[:self.MAX_DATA_WIDTH*2]}...' - emit('%-48s %s {%d} %s', name, soff, itsize, sval) + emit('%-48s %s %5s %s', name, soff, ssize, sval) else: ival = int.from_bytes(itvalue, 'little') if decode: @@ -192,8 +206,13 @@ def emit(fmt, *args): continue emit('%-48s %s %x', name, soff, ival) if self._digest_bytes is not None: - emit('%-48s %s %s', f'{pname}:DIGEST', soff, - hexlify(self._digest_bytes).decode()) + if match(filter_re, 'DIGEST', IGNORECASE): + if not sum(self._digest_bytes) and decode: + val = '(empty)' + else: + val = hexlify(self._digest_bytes).decode() + ssize = f'{{{len(self._digest_bytes)}}}' + emit('%-48s %s %5s %s', f'{pname}:DIGEST', soff, ssize, val) def empty(self) -> None: """Empty the partition, including its digest if any.""" diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index d77e78555c627..fdb9823ab3775 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -40,8 +40,9 @@ def main(): files.add_argument('-l', '--lifecycle', type=FileType('rt'), metavar='SV', help='input lifecycle system verilog file') - files.add_argument('-o', '--output', metavar='C', type=FileType('wt'), - help='output filename for C file generation') + files.add_argument('-o', '--output', metavar='FILE', + type=FileType('wt'), + help='output filename (default to stdout)') files.add_argument('-r', '--raw', help='QEMU OTP raw image file') params = argparser.add_argument_group(title='Parameters') @@ -65,6 +66,11 @@ def main(): params.add_argument('-n', '--no-decode', action='store_true', default=False, help='do not attempt to decode OTP fields') + params.add_argument('-f', '--filter', action='append', + metavar='PART:FIELD', + help='filter which OTP fields are shown') + params.add_argument('--no-version', action='store_true', + help='do not report the OTP image version') commands = argparser.add_argument_group(title='Commands') commands.add_argument('-s', '--show', action='store_true', help='show the OTP content') @@ -121,6 +127,9 @@ def main(): if args.vmem: argparser.error('RAW update mutually exclusive with VMEM') + if args.filter and not args.show: + argparser.error('Filter only apply to the show command') + bit_actions = ('clear', 'set', 'toggle') alter_bits: list[list[tuple[int, int]]] = [] for slot, bitact in enumerate(bit_actions): @@ -225,7 +234,8 @@ def main(): if lcext: otp.load_lifecycle(lcext) if args.show: - otp.decode(not args.no_decode, args.wide, sys.stdout) + otp.decode(not args.no_decode, args.wide, output, + not args.no_version, args.filter) if args.digest: if not otp.has_present_constants: if args.raw and otp.version == 1: