diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index 9b686d329c7e7..2035c07144251 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -6,9 +6,10 @@ 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] + [-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] @@ -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 @@ -42,6 +46,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 @@ -106,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 @@ -152,13 +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. -* `--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. +* `--erase` reset a specific field within a partition. The flag may be repeated. + +* `--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 @@ -168,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/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/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) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index e39bd811892c4..09694320711c2 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)) @@ -454,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 { @@ -1049,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; } @@ -1096,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); } @@ -1168,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; } } @@ -1524,7 +1532,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; @@ -2606,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; 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: 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) diff --git a/python/qemu/ot/otp/image.py b/python/qemu/ot/otp/image.py index b42c702782217..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. @@ -360,24 +390,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 +402,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 +700,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..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.""" @@ -201,6 +220,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/python/qemu/ot/spi/spi_device.py b/python/qemu/ot/spi/spi_device.py index fe47d27e78af2..cdc4f6597e86d 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.""" @@ -240,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'] @@ -260,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: diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index 768fdef96243a..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') @@ -79,6 +85,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 +111,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: @@ -119,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): @@ -145,6 +156,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 +216,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: @@ -212,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: diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index 14b591059fb3a..1081f6dd42725 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', @@ -1432,23 +1438,26 @@ 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)) - 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 +1512,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 +1542,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 +1606,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 +1635,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 +1660,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 +1718,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')