From 7b81d90548ab674e5e8da1687fc0f74fb7457241 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 10 Sep 2024 15:18:44 +0200 Subject: [PATCH 01/14] [ot] hw/opentitan: ot_dev_proxy: add a new API to change/query log channels Signed-off-by: Emmanuel Blot --- docs/opentitan/devproxy.md | 44 +++++++++++- hw/opentitan/ot_dev_proxy.c | 39 ++++++++++- scripts/opentitan/ot/devproxy.py | 112 ++++++++++++++++++++++++++++++- 3 files changed, 192 insertions(+), 3 deletions(-) diff --git a/docs/opentitan/devproxy.md b/docs/opentitan/devproxy.md index 903a209854feb..64891ae76acba 100644 --- a/docs/opentitan/devproxy.md +++ b/docs/opentitan/devproxy.md @@ -186,10 +186,52 @@ Only initiated by the application. +---------------+---------------+---------------+---------------+ ``` -The current version for this documentation is v0.14. +The current version for this documentation is v0.15. Note that semantic versionning does not apply for v0 series. +#### Logmask + +Logmask can be used to change the qemu_log_mask bitmap at runtime, so log +settings can be altered for specific runtime ranges, for a specific test for +example + +##### Request +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | ++---------------+---------------+---------------+---------------+ +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'HL' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---+-----------+---------------+---------------+---------------+ +| Op| Log mask | ++---+-----------+---------------+---------------+---------------+ +``` + +* `Op`: Log operation, among: + * `0`: change nothing, only read back the current log levels + * `1`: add new log channels from the log mask + * `2`: clear log channels from the log mask + * `3`: apply the log mask as is, overridding previous log channel settings + +##### Response +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | ++---------------+---------------+---------------+---------------+ +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'hl' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---+-----------+---------------+---------------+---------------+ +| 0 | Previous log mask | ++---+-----------+---------------+---------------+---------------+ +``` + #### Enumerate Devices [enumerate-devices] Enumerate should be called by the Application to retrieve the list of remote devices that can be diff --git a/hw/opentitan/ot_dev_proxy.c b/hw/opentitan/ot_dev_proxy.c index 767249a800c5f..b459418e20a83 100644 --- a/hw/opentitan/ot_dev_proxy.c +++ b/hw/opentitan/ot_dev_proxy.c @@ -183,7 +183,7 @@ enum OtDevProxyErr { }; #define PROXY_VER_MAJ 0 -#define PROXY_VER_MIN 14u +#define PROXY_VER_MIN 15u #define PROXY_IRQ_INTERCEPT_COUNT 32u #define PROXY_IRQ_INTERCEPT_NAME "irq-intercept" @@ -195,6 +195,11 @@ enum OtDevProxyErr { #define PROXY_MAKE_UID(_uid_, _req_) \ (((_uid_) & ~(1u << 31u)) | (((uint32_t)(bool)(_req_)) << 31u)) +#define LOG_OP_SHIFT 30u +#define LOG_MASK ((1u << LOG_OP_SHIFT) - 1u) + +enum { LOG_MASK_READ, LOG_MASK_ADD, LOG_MASK_REMOVE, LOG_MASK_DIRECT }; + static void ot_dev_proxy_reg_mr(GArray *array, Object *obj); static void ot_dev_proxy_reg_mbx(GArray *array, Object *obj); static void ot_dev_proxy_reg_soc_proxy(GArray *array, Object *obj); @@ -301,6 +306,35 @@ static void ot_dev_proxy_handshake(OtDevProxyState *s) sizeof(payload)); } +static void ot_dev_proxy_logmask(OtDevProxyState *s) +{ + if (s->rx_hdr.length != sizeof(uint32_t)) { + ot_dev_proxy_reply_error(s, PE_INVALID_COMMAND_LENGTH, NULL); + return; + } + + uint32_t prevlogmask = ((uint32_t)qemu_loglevel) & LOG_MASK; + uint32_t logmask = s->rx_buffer[0] & LOG_MASK; + unsigned op = s->rx_buffer[0] >> 30u; + switch (op) { + case LOG_MASK_ADD: + qemu_loglevel = (int)(prevlogmask | logmask); + break; + case LOG_MASK_REMOVE: + qemu_loglevel = (int)(prevlogmask & ~logmask); + break; + case LOG_MASK_DIRECT: + qemu_loglevel = (int)logmask; + break; + case LOG_MASK_READ: + default: + break; + } + + ot_dev_proxy_reply_payload(s, PROXY_COMMAND('h', 'l'), &prevlogmask, + sizeof(prevlogmask)); +} + static void ot_dev_proxy_enumerate_devices(OtDevProxyState *s) { if (s->rx_hdr.length != 0) { @@ -1446,6 +1480,9 @@ static void ot_dev_proxy_dispatch_request(OtDevProxyState *s) case PROXY_COMMAND('H', 'S'): ot_dev_proxy_handshake(s); break; + case PROXY_COMMAND('H', 'L'): + ot_dev_proxy_logmask(s); + break; case PROXY_COMMAND('E', 'D'): ot_dev_proxy_enumerate_devices(s); break; diff --git a/scripts/opentitan/ot/devproxy.py b/scripts/opentitan/ot/devproxy.py index c356960ceba0e..92526c125f3d3 100644 --- a/scripts/opentitan/ot/devproxy.py +++ b/scripts/opentitan/ot/devproxy.py @@ -8,6 +8,7 @@ from binascii import hexlify, unhexlify from collections import deque +from enum import IntEnum from logging import getLogger from socket import create_connection, socket, SHUT_RDWR from struct import calcsize as scalc, pack as spack, unpack as sunpack @@ -1187,7 +1188,7 @@ class ProxyEngine: """Tool to access and remotely drive devices and memories. """ - VERSION = (0, 14) + VERSION = (0, 15) """Protocol version.""" TIMEOUT = 2.0 @@ -1221,6 +1222,39 @@ class ProxyEngine: } """Notification dispatch map.""" + LOG_CHANNELS = ( + 'tb_out_asm', + 'tb_in_asm', + 'tb_op', + 'tb_op_opt', + 'int', + 'exec', + 'pcall', + 'tb_cpu', + 'reset', + 'unimp', + 'guest_error', + 'mmu', + 'tb_nochain', + 'page', + 'trace', + 'tb_op_ind', + 'tb_fpu', + 'plugin', + 'strace', + 'per_thread', + 'tb_vpu', + 'tb_op_plugin', + ) + """Supported QEMU log channels.""" + + class LogOp(IntEnum): + """Log operations.""" + READ = 0 + ADD = 1 + REMOVE = 2 + SET = 3 + def __init__(self): self._log = getLogger('proxy.proxy') self._socket: Optional[socket] = None @@ -1463,6 +1497,59 @@ def register_request_handler(self, handler: RequestHandler, *args) -> None: self._request_handler = handler self._request_args = args + def change_log_mask(self, logop: 'ProxyEngine.LogOp', mask: int) -> int: + """Change the QEMU log mask. + + :param op: the log modification operation + :param mask: the log mask to apply + :return: the previous log mask + """ + if not 0 <= logop <= 3: + raise ValueError('Invalid log operation') + if not 0 <= mask < (1 << 30): + raise ValueError('Invalid log mask') + req = spack(' tuple[int, list[str]]: + """Add new log sources. + + :param sources: should be one or more from LOG_CHANNELS + :return the previous sources + """ + mask = self._convert_log_to_mask(*sources) + oldmask = self.change_log_mask(self.LogOp.ADD, mask) + return oldmask, self._convert_mask_to_log(oldmask) + + def remove_log_sources(self, *sources) -> tuple[int, list[str]]: + """Remove new log sources. + + :param sources: should be one or more from LOG_CHANNELS + :return the previous sources + """ + mask = self._convert_log_to_mask(*sources) + oldmask = self.change_log_mask(self.LogOp.REMOVE, mask) + return oldmask, self._convert_mask_to_log(oldmask) + + def set_log_sources(self, *sources) -> tuple[int, list[str]]: + """Set log sources. + + :param sources: should be one or more from LOG_CHANNELS + :return the previous sources + """ + mask = self._convert_log_to_mask(*sources) + oldmask = self.change_log_mask(self.LogOp.SET, mask) + return oldmask, self._convert_mask_to_log(oldmask) + def exchange(self, command: str, payload: Optional[bytes] = None) -> bytes: """Execute a communication trip with the remote target. @@ -1709,6 +1796,27 @@ def _discover_proxies(cls) -> dict[str, DeviceProxy]: proxymap[devid] = class_ return proxymap + @classmethod + def _convert_log_to_mask(cls, *srcs) -> int: + channels = {l: i for i, l in enumerate(cls.LOG_CHANNELS)} + try: + mask = sum(1 << channels[s.lower()] for s in srcs) + except KeyError as exc: + raise ValueError("Unknown log channel {exc}") from exc + return mask + + @classmethod + def _convert_mask_to_log(cls, logmask: int) -> list[str]: + srcs = [] + pos = 0 + while logmask: + mask = 1 << pos + if logmask & mask: + srcs.append(cls.LOG_CHANNELS[pos]) + logmask &= ~mask + pos += 1 + return srcs + @classmethod def to_str(cls, data: Union[bytes, bytearray]) -> str: """Convert a byte sequence into an hexadecimal string. @@ -1729,6 +1837,8 @@ def to_bytes(cls, data: str) -> bytes: def _main(): + # pylint: disable=unknown-option-value + # pylint: disable=possibly-used-before-assignment debug = False try: desc = modules[__name__].__doc__.split('.', 1)[0].strip() From 307fa493fc534279ca678a1363cc6730f5d71493 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 13 Sep 2024 11:24:08 +0200 Subject: [PATCH 02/14] [ot] hw/opentitan: ot_spi_host: add traces when IRQs are updated Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_spi_host.c | 6 ++++++ hw/opentitan/trace-events | 1 + 2 files changed, 7 insertions(+) diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index fe73f6c32c78d..0d20bd33e3da0 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -648,6 +648,9 @@ static bool ot_spi_host_update_event(OtSPIHostState *s) /* now update the IRQ signal (event could have been already signalled) */ bool event_level = (bool)(s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE] & INTR_SPI_EVENT_MASK); + if (event_level != (bool)ibex_irq_get_level(&s->irqs[IRQ_SPI_EVENT])) { + trace_ot_spi_host_update_irq(s->ot_id, "event", event_level); + } ibex_irq_set(&s->irqs[IRQ_SPI_EVENT], event_level); return event; @@ -666,6 +669,9 @@ static bool ot_spi_host_update_error(OtSPIHostState *s) bool error = (bool)(s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE] & INTR_ERROR_MASK); + if (error != (bool)ibex_irq_get_level(&s->irqs[IRQ_ERROR])) { + trace_ot_spi_host_update_irq(s->ot_id, "error", error); + } ibex_irq_set(&s->irqs[IRQ_ERROR], error); return error; diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index b05f84ef17212..6596bdcb1b9eb 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -444,6 +444,7 @@ ot_spi_host_reject(const char *id, const char *msg) "%s: %s" ot_spi_host_reset(const char *id, const char *msg) "%s: %s" ot_spi_host_status(const char *id, const char *msg, uint32_t status, const char *str, unsigned cmd, unsigned rxd, unsigned txd) "%s: %s 0x%08x s:%s cq:%u rq:%u tq:%u" ot_spi_host_transfer(const char *id, uint32_t tx_data, uint32_t rx_data) "%s: tx_data: 0x%02x rx_data: 0x%02x" +ot_spi_host_update_irq(const char *id, const char *channel, int level) "%s: %s: %d" # ot_sram_ctrl.c From d9234941858c83802cc07d1474527dd869cbce2e Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 13 Sep 2024 11:24:20 +0200 Subject: [PATCH 03/14] [ot] hw/opentitan: ot_timer: do not reconfigure timer when max time is reached INT64_MAX is mostly computed when the guess OS perform a read-modify-write sequence of the 64-bit comparator with successive 32-bit register access. It first writes max time to timer hi register so that updating timer lo does not trigger a spurious timer alarm/timeout, then update a second time timer hi with the real value. This sequences leads to reconfigure the QEMU timer three times in a row, the two first configs only leads to reconfigure the timer with a near-infinite time. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_timer.c | 22 +++++++++++++++++++--- hw/opentitan/trace-events | 3 ++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/ot_timer.c b/hw/opentitan/ot_timer.c index 07ff2ab493f6d..0e85cb0596556 100644 --- a/hw/opentitan/ot_timer.c +++ b/hw/opentitan/ot_timer.c @@ -163,6 +163,8 @@ static void ot_timer_update_irqs(OtTimerState *s) static void ot_timer_rearm(OtTimerState *s, bool reset_origin) { + timer_del(s->timer); + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); if (reset_origin) { @@ -171,7 +173,6 @@ static void ot_timer_rearm(OtTimerState *s, bool reset_origin) uint32_t step = FIELD_EX32(s->regs[R_CFG0], CFG0, STEP); if (!ot_timer_is_active(s) || !step) { - timer_del(s->timer); return; } @@ -181,11 +182,13 @@ static void ot_timer_rearm(OtTimerState *s, bool reset_origin) if (mtime >= mtimecmp) { s->regs[R_INTR_STATE0] |= INTR_CMP0_MASK; - timer_del(s->timer); } else { int64_t delta = ot_timer_ticks_to_ns(s, mtimecmp - mtime); int64_t next = ot_timer_compute_next_timeout(s, now, delta); - timer_mod(s->timer, next); + if (next < INT64_MAX) { + trace_ot_timer_timer_mod(s->ot_id, now, next, false); + timer_mod(s->timer, next); + } } ot_timer_update_irqs(s); @@ -300,6 +303,7 @@ static void ot_timer_write(void *opaque, hwaddr addr, uint64_t value, */ int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); int64_t next = ot_timer_compute_next_timeout(s, now, 0); + trace_ot_timer_timer_mod(s->ot_id, now, next, true); timer_mod_anticipate(s->timer, next); break; } @@ -369,6 +373,17 @@ static void ot_timer_reset(DeviceState *dev) ot_timer_update_alert(s); } +static void ot_timer_realize(DeviceState *dev, Error **errp) +{ + (void)errp; + + OtTimerState *s = OT_TIMER(dev); + if (!s->ot_id) { + s->ot_id = + g_strdup(object_get_canonical_path_component(OBJECT(s)->parent)); + } +} + static void ot_timer_init(Object *obj) { OtTimerState *s = OT_TIMER(obj); @@ -390,6 +405,7 @@ static void ot_timer_class_init(ObjectClass *klass, void *data) (void)data; dc->reset = &ot_timer_reset; + dc->realize = &ot_timer_realize; device_class_set_props(dc, ot_timer_properties); } diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 6596bdcb1b9eb..efba61ae46abb 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -467,8 +467,9 @@ ot_sram_ctrl_switch_mem(const char *id, const char *dest) "%s: to %s" # ot_timer.c ot_timer_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_timer_update_irq(const char *id, bool level) "%s: %d" ot_timer_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_timer_timer_mod(const char *id, int64_t now, int64_t next, bool anticipate) "%s: @ %" PRId64 ": %" PRId64 " ant: %u" +ot_timer_update_irq(const char *id, bool level) "%s: %d" # ot_uart.c From b2b059e7d695e2fc5d45285d51f5d0d1b21f66fa Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 16 Sep 2024 16:41:09 +0200 Subject: [PATCH 04/14] [ot] hw/opentitan: ot_hmac: rework FIFO management. * Do not use a timer anymore to avoid introducing too much latency for computing hash of large content. * Suppress the bug that led to retard each HMAC computation whenever new data were added * Add OpenTitan standard identifier to trace messages * Tweak interrupt management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_aon_timer.c | 52 +++++++++++++------ hw/opentitan/ot_hmac.c | 101 ++++++++++++++++++------------------ hw/opentitan/trace-events | 17 +++--- 3 files changed, 97 insertions(+), 73 deletions(-) diff --git a/hw/opentitan/ot_aon_timer.c b/hw/opentitan/ot_aon_timer.c index 5fe93f12815f4..e8c0ee29b9e2a 100644 --- a/hw/opentitan/ot_aon_timer.c +++ b/hw/opentitan/ot_aon_timer.c @@ -96,8 +96,8 @@ static const char REG_NAMES[REGS_COUNT][20u] = { struct OtAonTimerState { SysBusDevice parent_obj; - QEMUTimer *wkup_timer; - QEMUTimer *wdog_timer; + + MemoryRegion mmio; IbexIRQ irq_wkup; IbexIRQ irq_bark; @@ -106,14 +106,17 @@ struct OtAonTimerState { IbexIRQ pwrmgr_bite; IbexIRQ alert; - MemoryRegion mmio; + QEMUTimer *wkup_timer; + QEMUTimer *wdog_timer; uint32_t regs[REGS_COUNT]; - uint32_t pclk; int64_t wkup_origin_ns; int64_t wdog_origin_ns; bool wdog_bite; + + char *ot_id; + uint32_t pclk; }; static uint32_t @@ -190,7 +193,7 @@ static void ot_aon_timer_update_irqs(OtAonTimerState *s) bool wkup = (bool)(s->regs[R_INTR_STATE] & INTR_WKUP_TIMER_EXPIRED_MASK); bool bark = (bool)(s->regs[R_INTR_STATE] & INTR_WDOG_TIMER_BARK_MASK); - trace_ot_aon_timer_irqs(wkup, bark, s->wdog_bite); + trace_ot_aon_timer_irqs(s->ot_id, wkup, bark, s->wdog_bite); ibex_irq_set(&s->irq_wkup, wkup); ibex_irq_set(&s->irq_bark, bark); @@ -201,6 +204,8 @@ static void ot_aon_timer_update_irqs(OtAonTimerState *s) static void ot_aon_timer_rearm_wkup(OtAonTimerState *s, bool reset_origin) { + timer_del(s->wkup_timer); + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); if (reset_origin) { @@ -209,7 +214,6 @@ static void ot_aon_timer_rearm_wkup(OtAonTimerState *s, bool reset_origin) /* if not enabled, ignore threshold */ if (!ot_aon_timer_is_wkup_enabled(s)) { - timer_del(s->wkup_timer); ot_aon_timer_update_irqs(s); return; } @@ -219,14 +223,15 @@ static void ot_aon_timer_rearm_wkup(OtAonTimerState *s, bool reset_origin) if (count >= threshold) { s->regs[R_INTR_STATE] |= INTR_WKUP_TIMER_EXPIRED_MASK; - timer_del(s->wkup_timer); } else { uint32_t prescaler = FIELD_EX32(s->regs[R_WKUP_CTRL], WKUP_CTRL, PRESCALER); int64_t delta = ot_aon_timer_ticks_to_ns(s, prescaler, threshold - count); int64_t next = ot_aon_timer_compute_next_timeout(s, now, delta); - timer_mod(s->wkup_timer, next); + if (next < INT64_MAX) { + timer_mod(s->wkup_timer, next); + } } ot_aon_timer_update_irqs(s); @@ -275,13 +280,15 @@ static void ot_aon_timer_rearm_wdog(OtAonTimerState *s, bool reset_origin) pending = true; } - if (!pending) { - timer_del(s->wdog_timer); - } else { + timer_del(s->wdog_timer); + + if (pending) { int64_t delta = ot_aon_timer_ticks_to_ns(s, 0u, threshold - count); int64_t next = ot_aon_timer_compute_next_timeout(s, now, delta); - trace_ot_aon_timer_set_wdog(now, next); - timer_mod(s->wdog_timer, next); + if (next < INT64_MAX) { + trace_ot_aon_timer_set_wdog(s->ot_id, now, next); + timer_mod(s->wdog_timer, next); + } } ot_aon_timer_update_irqs(s); @@ -340,7 +347,8 @@ static uint64_t ot_aon_timer_read(void *opaque, hwaddr addr, unsigned size) } uint32_t pc = ibex_get_current_pc(); - trace_ot_aon_timer_read_out((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_aon_timer_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); return (uint64_t)val32; } @@ -355,7 +363,8 @@ static void ot_aon_timer_write(void *opaque, hwaddr addr, uint64_t value, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_aon_timer_write((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_aon_timer_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); switch (reg) { case R_ALERT_TEST: @@ -474,6 +483,7 @@ static const MemoryRegionOps ot_aon_timer_ops = { }; static Property ot_aon_timer_properties[] = { + DEFINE_PROP_STRING("ot_id", OtAonTimerState, ot_id), DEFINE_PROP_UINT32("pclk", OtAonTimerState, pclk, 0u), DEFINE_PROP_END_OF_LIST(), }; @@ -495,6 +505,17 @@ static void ot_aon_timer_reset(DeviceState *dev) ot_aon_timer_update_alert(s); } +static void ot_aon_timer_realize(DeviceState *dev, Error **errp) +{ + (void)errp; + + OtAonTimerState *s = OT_AON_TIMER(dev); + if (!s->ot_id) { + s->ot_id = + g_strdup(object_get_canonical_path_component(OBJECT(s)->parent)); + } +} + static void ot_aon_timer_init(Object *obj) { OtAonTimerState *s = OT_AON_TIMER(obj); @@ -520,6 +541,7 @@ static void ot_aon_timer_class_init(ObjectClass *klass, void *data) (void)data; dc->reset = ot_aon_timer_reset; + dc->realize = ot_aon_timer_realize; device_class_set_props(dc, ot_aon_timer_properties); } diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 139e9be290dfd..271bd151d162c 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -30,7 +30,6 @@ #include "qemu/fifo8.h" #include "qemu/log.h" #include "qemu/module.h" -#include "qemu/timer.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" @@ -51,8 +50,7 @@ /* HMAC key length is 32 bytes (256 bits) */ #define OT_HMAC_KEY_LENGTH 32u -/* Delay FIFO ingestion and compute by 100ns */ -#define FIFO_TRIGGER_DELAY_NS 100u +#define PARAM_NUM_IRQS 3u /* clang-format off */ REG32(INTR_STATE, 0x00u) @@ -174,40 +172,31 @@ struct OtHMACContext { typedef struct OtHMACContext OtHMACContext; struct OtHMACState { - /* */ SysBusDevice parent_obj; - /* */ MemoryRegion mmio; MemoryRegion regs_mmio; MemoryRegion fifo_mmio; - IbexIRQ irq_done; - IbexIRQ irq_fifo_empty; - IbexIRQ irq_hmac_err; + IbexIRQ irqs[PARAM_NUM_IRQS]; IbexIRQ alert; IbexIRQ clkmgr; OtHMACRegisters *regs; OtHMACContext *ctx; - Fifo8 input_fifo; - QEMUTimer *fifo_trigger_handle; + + char *ot_id; }; static void ot_hmac_update_irqs(OtHMACState *s) { - uint32_t irq_masked = s->regs->intr_state & s->regs->intr_enable; - bool level; - - level = irq_masked & INTR_HMAC_DONE_MASK; - ibex_irq_set(&s->irq_done, level); - - level = irq_masked & INTR_FIFO_EMPTY_MASK; - ibex_irq_set(&s->irq_fifo_empty, level); - - level = irq_masked & INTR_HMAC_ERR_MASK; - ibex_irq_set(&s->irq_hmac_err, level); + uint32_t levels = s->regs->intr_state & s->regs->intr_enable; + trace_ot_hmac_irqs(s->ot_id, s->regs->intr_state, s->regs->intr_enable, + levels); + for (unsigned ix = 0; ix < PARAM_NUM_IRQS; ix++) { + ibex_irq_set(&s->irqs[ix], (int)((levels >> ix) & 0x1u)); + } } static void ot_hmac_update_alert(OtHMACState *s) @@ -225,7 +214,7 @@ static void ot_hmac_report_error(OtHMACState *s, uint32_t error) static void ot_hmac_compute_digest(OtHMACState *s) { - trace_ot_hmac_debug("ot_hmac_compute_digest"); + trace_ot_hmac_debug(s->ot_id, __func__); /* HMAC mode, perform outer hash */ if (s->regs->cfg & R_CFG_HMAC_EN_MASK) { @@ -235,7 +224,7 @@ static void ot_hmac_compute_digest(OtHMACState *s) memset(opad, 0, sizeof(opad)); memcpy(opad, s->regs->key, sizeof(s->regs->key)); for (unsigned i = 0; i < ARRAY_SIZE(opad); i++) { - opad[i] ^= 0x5c5c5c5c5c5c5c5cu; + opad[i] ^= 0x5c5c5c5c5c5c5c5cull; } sha256_init(&s->ctx->state); sha256_process(&s->ctx->state, (const uint8_t *)opad, sizeof(opad)); @@ -246,11 +235,9 @@ static void ot_hmac_compute_digest(OtHMACState *s) sha256_done(&s->ctx->state, (uint8_t *)s->regs->digest); } -static void ot_hmac_fifo_trigger_update(void *opaque) +static void ot_hmac_process_fifo(OtHMACState *s) { - OtHMACState *s = opaque; - - trace_ot_hmac_debug("ot_hmac_fifo_trigger_update"); + trace_ot_hmac_debug(s->ot_id, __func__); if (!fifo8_is_empty(&s->input_fifo)) { while (!fifo8_is_empty(&s->input_fifo)) { @@ -362,7 +349,8 @@ static uint64_t ot_hmac_regs_read(void *opaque, hwaddr addr, unsigned size) } uint32_t pc = ibex_get_current_pc(); - trace_ot_hmac_io_read_out((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_hmac_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); return (uint64_t)val32; } @@ -377,7 +365,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_hmac_io_write((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_hmac_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, pc); switch (reg) { case R_INTR_STATE: @@ -461,11 +449,8 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, s->regs->cmd |= R_CMD_HASH_PROCESS_MASK; /* trigger delayed processing of FIFO */ - timer_del(s->fifo_trigger_handle); ibex_irq_set(&s->clkmgr, true); - timer_mod(s->fifo_trigger_handle, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + - FIFO_TRIGGER_DELAY_NS); + ot_hmac_process_fifo(s); } break; case R_WIPE_SECRET: @@ -528,7 +513,8 @@ static void ot_hmac_fifo_write(void *opaque, hwaddr addr, uint64_t value, OtHMACState *s = OT_HMAC(opaque); uint32_t pc = ibex_get_current_pc(); - trace_ot_hmac_fifo_write((uint32_t)addr, (uint32_t)value, size, pc); + trace_ot_hmac_fifo_write(s->ot_id, (uint32_t)addr, (uint32_t)value, size, + pc); if (!s->regs->cmd) { ot_hmac_report_error(s, R_ERR_CODE_PUSH_MSG_WHEN_DISALLOWED); @@ -552,23 +538,31 @@ static void ot_hmac_fifo_write(void *opaque, hwaddr addr, uint64_t value, for (unsigned i = 0; i < size; i++) { uint8_t b = value; - if (fifo8_is_full(&s->input_fifo)) { - /* FIFO full. Should stall but cannot be done in QEMU? */ - ot_hmac_fifo_trigger_update(s); - } + g_assert(!fifo8_is_full(&s->input_fifo)); fifo8_push(&s->input_fifo, b); value >>= 8u; } s->regs->msg_length += (uint64_t)size * 8u; - /* trigger delayed processing of FIFO */ - timer_del(s->fifo_trigger_handle); - timer_mod(s->fifo_trigger_handle, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + FIFO_TRIGGER_DELAY_NS); + /* + * Note: real HW may stall the bus till some room is available in the input + * FIFO. In QEMU, we do not want to stall the I/O thread to emulate this + * feature. The workaround is to let the FIFO fill up with an arbitrary + * length, always smaller than the FIFO capacity, here half the size of the + * FIFO then process the whole FIFO content in one step. This let the FIFO + * depth register to update on each call as the real HW. However the FIFO + * can never be full, which is not supposed to occur on the real HW anyway + * since the HMAC is reportedly faster than the Ibex capability to fill in + * the FIFO. Could be different with DMA access though. + */ + if (fifo8_num_used(&s->input_fifo) >= OT_HMAC_FIFO_LENGTH / 2u) { + ot_hmac_process_fifo(s); + } } static Property ot_hmac_properties[] = { + DEFINE_PROP_STRING("ot_id", OtHMACState, ot_id), DEFINE_PROP_END_OF_LIST(), }; @@ -599,9 +593,9 @@ static void ot_hmac_init(Object *obj) s->regs = g_new0(OtHMACRegisters, 1u); s->ctx = g_new(OtHMACContext, 1u); - ibex_sysbus_init_irq(obj, &s->irq_done); - ibex_sysbus_init_irq(obj, &s->irq_fifo_empty); - ibex_sysbus_init_irq(obj, &s->irq_hmac_err); + for (unsigned ix = 0; ix < PARAM_NUM_IRQS; ix++) { + ibex_sysbus_init_irq(obj, &s->irqs[ix]); + } ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); ibex_qdev_init_irq(obj, &s->clkmgr, OT_CLOCK_ACTIVE); @@ -616,19 +610,25 @@ static void ot_hmac_init(Object *obj) TYPE_OT_HMAC ".fifo", OT_HMAC_FIFO_SIZE); memory_region_add_subregion(&s->mmio, OT_HMAC_FIFO_BASE, &s->fifo_mmio); - /* setup FIFO Interrupt Timer */ - s->fifo_trigger_handle = - timer_new_ns(OT_VIRTUAL_CLOCK, &ot_hmac_fifo_trigger_update, s); - /* FIFO sizes as per OT Spec */ fifo8_create(&s->input_fifo, OT_HMAC_FIFO_LENGTH); } +static void ot_hmac_realize(DeviceState *dev, Error **errp) +{ + (void)errp; + + OtHMACState *s = OT_HMAC(dev); + if (!s->ot_id) { + s->ot_id = + g_strdup(object_get_canonical_path_component(OBJECT(s)->parent)); + } +} + static void ot_hmac_reset(DeviceState *dev) { OtHMACState *s = OT_HMAC(dev); - timer_del(s->fifo_trigger_handle); ibex_irq_set(&s->clkmgr, false); memset(s->ctx, 0, sizeof(*(s->ctx))); @@ -646,6 +646,7 @@ static void ot_hmac_class_init(ObjectClass *klass, void *data) (void)data; dc->reset = &ot_hmac_reset; + dc->realize = &ot_hmac_realize; device_class_set_props(dc, ot_hmac_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); } diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index efba61ae46abb..81cc5dba984f8 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -33,10 +33,10 @@ ot_alert_skip_active(const char *id, char cls, const char *stname) "%s: class %c # ot_aon_timer.c -ot_aon_timer_irqs(bool wakeup, bool bark, bool bite) "wkup:%u bark:%u bite:%u" -ot_aon_timer_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_aon_timer_set_wdog(int64_t now, int64_t next) "now %" PRId64 ", next %" PRId64 -ot_aon_timer_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_aon_timer_irqs(const char *id, bool wakeup, bool bark, bool bite) "%s: wkup:%u bark:%u bite:%u" +ot_aon_timer_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_aon_timer_set_wdog(const char *id, int64_t now, int64_t next) "%s: now %" PRId64 ", next %" PRId64 +ot_aon_timer_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" # ot_ast.c @@ -200,10 +200,11 @@ ot_gpio_update_out_line(const char *id, unsigned ix, int level) "%s: [%u]: %d" # ot_hmac.c -ot_hmac_debug(const char *msg) "%s" -ot_hmac_fifo_write(uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "addr=0x%02x, val=0x%x (%u), pc=0x%x" -ot_hmac_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_hmac_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_hmac_debug(const char *id, const char *msg) "%s: %s" +ot_hmac_fifo_write(const char *id, uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "%s: addr=0x%02x, val=0x%x (%u), pc=0x%x" +ot_hmac_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_hmac_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_hmac_irqs(const char *id, uint32_t active, uint32_t mask, uint32_t eff) "%s: act:0x%01x msk:0x%01x eff:0x%01x" # ot_i2c_dj.c From 55b23e8e695fc6b9f3d317aa158c003daca716b2 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 17 Sep 2024 13:03:04 +0200 Subject: [PATCH 05/14] [ot] hw/opentitan: ot_otp_dj: fix HW digest computation Partitions with an odd number of 64-bit blocks needs a special treatment Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 4fb1bd5c7c6d8..3cef82da661d7 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1800,21 +1800,22 @@ static uint64_t ot_otp_dj_compute_partition_digest( { OtPresentState *ps = ot_present_new(); + g_assert((size & (sizeof(uint64_t) - 1u)) == 0); + uint8_t buf[sizeof(uint64_t) * 2u]; uint64_t state = s->digest_iv; uint64_t out; for (unsigned off = 0; off < size; off += sizeof(buf)) { - const uint8_t *chunk; - if (off + sizeof(buf) > size) { - memcpy(buf, base + off, sizeof(uint64_t)); + memcpy(buf, base + off, sizeof(uint64_t)); + if (off + sizeof(uint64_t) != size) { memcpy(&buf[sizeof(uint64_t)], base + off + sizeof(uint64_t), sizeof(uint64_t)); - chunk = buf; } else { - chunk = base + off; + /* special case, duplicate last block if block number is odd */ + memcpy(&buf[sizeof(uint64_t)], base + off, sizeof(uint64_t)); } - ot_present_init(ps, chunk); + ot_present_init(ps, buf); ot_present_encrypt(ps, state, &out); state ^= out; } @@ -1895,8 +1896,8 @@ static void ot_otp_dj_check_partition_integrity(OtOTPDjState *s, unsigned ix) trace_ot_otp_mismatch_digest(s->ot_id, PART_NAME(ix), ix, digest, pctrl->buffer.digest); - TRACE_OTP("compute digest %016llx from %s\n", digest, - ot_otp_hexdump(pctrl->buffer.data, part_size)); + TRACE_OTP("compute digest of %s: %016llx from %s\n", PART_NAME(ix), + digest, ot_otp_hexdump(pctrl->buffer.data, part_size)); pctrl->failed = true; /* this is a fatal error */ From 963d19e404dd11669f7cd8528ad40de40c6688df Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Sep 2024 14:33:50 +0200 Subject: [PATCH 06/14] [ot] hw/opentitan: ot_otp_dj: update LC definition, byte ordering Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 130 +++++++++++++++++++------------------- hw/opentitan/ot_otp_eg.c | 14 +++- hw/opentitan/trace-events | 3 +- 3 files changed, 78 insertions(+), 69 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 3cef82da661d7..8c0c73127aa17 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1047,97 +1047,91 @@ static const char *ERR_CODE_NAMES[] = { #define B(_n_) ((LC_STATE_B) | (_n_)) /* clang-format off */ - -/* - * note: this array follows RTL definitions, which means everything is inverted - * end should be read the other way around, i.e. first element is the last - * one for each state - */ static const uint8_t LC_STATES_TPL[LC_STATE_VALID_COUNT][LC_STATE_SLOT_COUNT] = { [LC_STATE_RAW] = { - ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, - ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO + ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, + ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, ZRO, }, [LC_STATE_TESTUNLOCKED0] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), A(6), A(5), A(4), A(3), A(2), A(1), B(0) + B(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED0] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), A(6), A(5), A(4), A(3), A(2), B(1), B(0) + B(0), B(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED1] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), A(6), A(5), A(4), A(3), B(2), B(1), B(0) + B(0), B(1), B(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED1] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), A(6), A(5), A(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), A(4), A(5), A(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED2] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), A(6), A(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), A(5), A(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED2] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), A(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), A(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED3] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), A(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), A(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED3] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), A(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), A(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED4] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - A(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), A(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED4] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),A(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED5] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),A(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED5] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),A(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED6] = { - A(19),A(18),A(17),A(16),A(15),A(14),A(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTLOCKED6] = { - A(19),A(18),A(17),A(16),A(15),A(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), A(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_TESTUNLOCKED7] = { - A(19),A(18),A(17),A(16),A(15),B(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), B(14), A(15), A(16), A(17), A(18), A(19), }, [LC_STATE_DEV] = { - A(19),A(18),A(17),A(16),B(15),B(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), B(14), B(15), A(16), A(17), A(18), A(19), }, [LC_STATE_PROD] = { - A(19),A(18),A(17),B(16),A(15),B(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), B(14), A(15), B(16), A(17), A(18), A(19), }, [LC_STATE_PRODEND] = { - A(19),A(18),B(17),A(16),A(15),B(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), B(14), A(15), A(16), B(17), A(18), A(19), }, [LC_STATE_RMA] = { - B(19),B(18),A(17),B(16),B(15),B(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), B(14), B(15), B(16), A(17), B(18), B(19), }, [LC_STATE_SCRAP] = { - B(19),B(18),B(17),B(16),B(15),B(14),B(13),B(12),B(11),B(10), - B(9), B(8), B(7), B(6), B(5), B(4), B(3), B(2), B(1), B(0) + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), + B(10), B(11), B(12), B(13), B(14), B(15), B(16), B(17), B(18), B(19), }, }; /* clang-format on */ @@ -1878,9 +1872,9 @@ static void ot_otp_dj_check_partition_integrity(OtOTPDjState *s, unsigned ix) { OtOTPPartController *pctrl = &s->partctrls[ix]; - if (!OtOTPPartDescs[ix].hw_digest || pctrl->buffer.digest == 0) { + if (pctrl->buffer.digest == 0) { trace_ot_otp_skip_digest(s->ot_id, PART_NAME(ix), ix); - s->partctrls[ix].locked = false; + pctrl->locked = false; return; } @@ -1912,7 +1906,7 @@ static void ot_otp_dj_check_partition_integrity(OtOTPDjState *s, unsigned ix) static void ot_otp_dj_initialize_partitions(OtOTPDjState *s) { for (unsigned ix = 0; ix < OTP_PART_COUNT; ix++) { - if (ot_otp_dj_is_ecc_enabled(s) && ix != OTP_PART_VENDOR_TEST) { + if (ot_otp_dj_is_ecc_enabled(s) && OtOTPPartDescs[ix].integrity) { if (ot_otp_dj_apply_ecc(s, ix)) { continue; } @@ -1926,7 +1920,9 @@ static void ot_otp_dj_initialize_partitions(OtOTPDjState *s) if (OtOTPPartDescs[ix].buffered) { ot_otp_dj_bufferize_partition(s, ix); - ot_otp_dj_check_partition_integrity(s, ix); + if (OtOTPPartDescs[ix].hw_digest) { + ot_otp_dj_check_partition_integrity(s, ix); + } continue; } } @@ -3197,7 +3193,8 @@ static void ot_otp_dj_decode_lc_partition(OtOTPDjState *s) } } - trace_ot_otp_initial_lifecycle(s->ot_id, lci->lc.state, lci->lc.tcount); + trace_ot_otp_initial_lifecycle(s->ot_id, lci->lc.tcount, lci->lc.state, + LC_STATE_BITS(lci->lc.state)); } static void ot_otp_dj_load_hw_cfg(OtOTPDjState *s) @@ -3284,6 +3281,11 @@ static void ot_otp_dj_get_lc_info( OT_MULTIBITBOOL_LC4_TRUE : OT_MULTIBITBOOL_LC4_FALSE; } + trace_ot_otp_lc_info(ds->ot_id, LC_STATE_BITS(lci->lc.state), + lci->lc.tcount, + !ds->partctrls[OTP_PART_SECRET0].failed, + !ds->partctrls[OTP_PART_SECRET2].failed, + !ds->partctrls[OTP_PART_LIFE_CYCLE].failed); if (tokens) { *tokens = ds->tokens; } @@ -3631,16 +3633,15 @@ static void ot_otp_dj_lci_write_word(void *opaque) * a life cycle state with ECC correctable errors in some words can * still be scrapped." */ - new_val |= cur_val; } - lc_dst[lci->hpos] = new_val; + lc_dst[lci->hpos] |= new_val; if (ot_otp_dj_is_ecc_enabled(s)) { uint8_t *lc_edst = (uint8_t *)&s->otp->ecc[lcdesc->offset / (2u * sizeof(uint32_t))]; uint8_t cur_ecc = lc_edst[lci->hpos]; - uint8_t new_ecc = ot_otp_dj_compute_ecc_u16(new_val); + uint8_t new_ecc = ot_otp_dj_compute_ecc_u16(lc_dst[lci->hpos]); trace_ot_otp_lci_write_ecc(s->ot_id, lci->hpos, cur_ecc, new_ecc); @@ -3651,10 +3652,9 @@ static void ot_otp_dj_lci_write_word(void *opaque) if (lci->error == OTP_NO_ERROR) { lci->error = OTP_MACRO_WRITE_BLANK_ERROR; } - new_ecc |= cur_ecc; } - lc_edst[lci->hpos] = new_ecc; + lc_edst[lci->hpos] |= new_ecc; } lci->hpos += 1; @@ -3876,15 +3876,13 @@ static void ot_otp_dj_configure_lc_states(OtOTPDjState *s, Error **errp) uint16_t *lcval = &s->lc_states[lcix][0]; const uint8_t *tpl = LC_STATES_TPL[lcix]; for (unsigned pos = 0; pos < LC_STATE_SLOT_COUNT; pos++) { - // @todo redefine macros to avoid runtime computation - unsigned rpos = LC_STATE_SLOT_COUNT - 1u - pos; - unsigned slot = LC_STATE_SLOT(tpl[rpos]); + unsigned slot = LC_STATE_SLOT(tpl[pos]); g_assert(slot < LC_STATE_SLOT_COUNT); - if (LC_STATE_A_SLOT(tpl[rpos])) { + if (LC_STATE_A_SLOT(tpl[pos])) { lcval[pos] = first[slot]; - } else if (LC_STATE_B_SLOT(tpl[rpos])) { + } else if (LC_STATE_B_SLOT(tpl[pos])) { lcval[pos] = last[slot]; - } else if (LC_STATE_ZERO_SLOT(tpl[rpos])) { + } else if (LC_STATE_ZERO_SLOT(tpl[pos])) { lcval[pos] = 0u; } else { g_assert_not_reached(); @@ -3951,7 +3949,7 @@ static void ot_otp_dj_configure_lc_transitions(OtOTPDjState *s, Error **errp) memset(s->lc_transitions[tix++], 0, sizeof(OtOTPTransitionValue)); for (; tix < LC_TRANSITION_COUNT; tix++) { uint16_t *lcval = s->lc_transitions[tix]; - memcpy(&lcval[0u], &last[0], tix * sizeof(uint16_t)); + memcpy(&lcval[0], &last[0], tix * sizeof(uint16_t)); memcpy(&lcval[tix], &first[tix], sizeof(OtOTPTransitionValue) - tix * sizeof(uint16_t)); } @@ -3995,6 +3993,8 @@ static void ot_otp_dj_load(OtOTPDjState *s, Error **errp) uint8_t digest_constant[16u]; }; + static_assert(sizeof(struct otp_header) == 48u, "Invalid header size"); + /* data following header should always be 64-bit aligned */ static_assert((sizeof(struct otp_header) % sizeof(uint64_t)) == 0, "invalid header definition"); diff --git a/hw/opentitan/ot_otp_eg.c b/hw/opentitan/ot_otp_eg.c index 3481a26a9bae2..b52c836156b03 100644 --- a/hw/opentitan/ot_otp_eg.c +++ b/hw/opentitan/ot_otp_eg.c @@ -407,9 +407,16 @@ static const OtOTPTokens OT_OTP_EG_TOKENS; #include "ot_otp_eg_lcvalues.c" #define LC_TRANSITION_COUNT_MAX 24u +#define LC_STATE_BIT_WIDTH 5u #define LC_ENCODE_STATE(_x_) \ - (((_x_) << 0u) | ((_x_) << 5u) | ((_x_) << 10u) | ((_x_) << 15u) | \ - ((_x_) << 20u) | ((_x_) << 25u)) + (((_x_) << (LC_STATE_BIT_WIDTH * 0)) | \ + ((_x_) << (LC_STATE_BIT_WIDTH * 1u)) | \ + ((_x_) << (LC_STATE_BIT_WIDTH * 2u)) | \ + ((_x_) << (LC_STATE_BIT_WIDTH * 3u)) | \ + ((_x_) << (LC_STATE_BIT_WIDTH * 4u)) | \ + ((_x_) << (LC_STATE_BIT_WIDTH * 5u))) +#define LC_STATE_BITS(_elc_) ((_elc_) & ((1u << LC_STATE_BIT_WIDTH) - 1u)) + static void ot_otp_eg_update_irqs(OtOTPEgState *s) { @@ -1016,7 +1023,8 @@ static void ot_otp_eg_decode_lc_partition(OtOTPEgState *s) break; } } - trace_ot_otp_initial_lifecycle(s->ot_id, s->lc.state, s->lc.tcount); + trace_ot_otp_initial_lifecycle(s->ot_id, s->lc.tcount, s->lc.state, + LC_STATE_BITS(s->lc.state)); } static void ot_otp_eg_load_hw_cfg(OtOTPEgState *s) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 81cc5dba984f8..3b6c25c59e980 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -310,7 +310,7 @@ ot_otp_ecc_mismatch(const char * id, unsigned address, uint32_t secc, uint32_t l ot_otp_ecc_parity_error(const char * id, uint32_t d_i, uint32_t ecc) "%s: 0x%04x, ECC 0x%02x" ot_otp_ecc_recovered_error(const char * id, uint32_t d_i, uint32_t d_o) "%s: 0x%04x -> 0x%04x" ot_otp_ecc_unrecoverable_error(const char * id, uint32_t d_i) "%s: 0x%04x" -ot_otp_initial_lifecycle(const char * id, uint32_t lc_state, unsigned tcount) "%s: lifecyle 0x%x, transition count %u" +ot_otp_initial_lifecycle(const char * id, unsigned tcount, uint32_t lc_state, unsigned stix) "%s: transcount %u, lifecyle 0x%x [%u]" ot_otp_initialize(const char * id) "%s" ot_otp_integrity_report(const char * id, const char* part, unsigned pix, const char *msg) "%s: partition %s (#%u) %s" ot_otp_io_reg_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" @@ -318,6 +318,7 @@ ot_otp_io_reg_write(const char * id, uint32_t addr, const char * regname, uint32 ot_otp_io_swcfg_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_otp_keygen_entropy(const char * id, unsigned slot, bool resched) "%s: %u slots, resched: %u" ot_otp_lc_broadcast(const char * id, unsigned sig, bool level) "%s: bcast %u, level %u" +ot_otp_lc_info(const char * id, unsigned st, unsigned tc, bool secret0, bool secret2, bool lc) "%s: st:%u tc:%u s0:%u s1:%u lc:%u" ot_otp_lci_change_state(const char * id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_otp_lci_write(const char * id, unsigned pos, uint16_t cval, uint16_t nval) "%s: @ %u 0x%04x -> 0x%04x" ot_otp_lci_write_ecc(const char * id, unsigned pos, uint8_t cval, uint8_t nval) "%s: @ %u 0x%02x -> 0x%02x" From 65825c0772c5cea669019b314cf4aba6492c5147 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Sep 2024 14:34:55 +0200 Subject: [PATCH 07/14] [ot] hw/opentitan: ot_lc_ctrl: fix trace messages Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_lc_ctrl.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index 9353c2e26d9f7..1d3fa0507daea 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -955,8 +955,7 @@ static void ot_lc_ctrl_handle_otp_ack(void *opaque, bool ack) break; case ST_TRANS_PROG: if (ack) { - trace_ot_lc_ctrl_info(s->ot_id, - "Succesful transition programmation"); + trace_ot_lc_ctrl_info(s->ot_id, "Succesful transition update"); s->regs[R_STATUS] |= R_STATUS_TRANSITION_SUCCESSFUL_MASK; } else { trace_ot_lc_ctrl_info(s->ot_id, "Failed to program transition"); @@ -1216,7 +1215,9 @@ static void ot_lc_ctrl_initialize(OtLcCtrlState *s) } if (!ot_lc_ctrl_is_known_state(enc_state)) { - trace_ot_lc_ctrl_error(s->ot_id, "LC unknown state"); + if (enc_state != UINT32_MAX) { + trace_ot_lc_ctrl_error(s->ot_id, "LC unknown state"); + } s->state_invalid_error_bm |= 1u << 1u; } else { s->lc_state = ot_lc_ctrl_convert_code_to_state(enc_state); From 7aa4cdf4b407e85e1a15c180da72c91ee07a0b9f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Sep 2024 11:45:23 +0200 Subject: [PATCH 08/14] [ot] system: fix invalid option definition for OT devices Signed-off-by: Emmanuel Blot --- system/vl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/system/vl.c b/system/vl.c index 330637fd539a7..1d7b8f6477a8b 100644 --- a/system/vl.c +++ b/system/vl.c @@ -508,6 +508,7 @@ static QemuOptsList qemu_ot_device_opts = { .implied_opt_name = "ot_id", .head = QTAILQ_HEAD_INITIALIZER(qemu_ot_device_opts.head), .desc = { + { /* end of list */ } }, }; From d4dd3919c503895ca90e77edf27c71a978b5e1a2 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 17 Sep 2024 17:20:50 +0200 Subject: [PATCH 09/14] [ot] scripts/opentitan: otptool.py: add a new option to reset the content of a partition. Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 14 ++++++++--- scripts/opentitan/ot/otp/image.py | 36 +++++++++++++++++++++++++++ scripts/opentitan/ot/otp/partition.py | 22 ++++++++++++++++ scripts/opentitan/otptool.py | 9 +++++++ 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index 296fe10113588..29108e3071fb3 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -8,9 +8,9 @@ controller virtual device. ````text usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o C] [-r RAW] [-k {auto,otp,fuz}] [-e BITS] [-c INT] [-i INT] [-w] [-n] - [-s] [-E] [-D] [-U] [--clear-bit CLEAR_BIT] - [--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [-L | -P | -R] - [-v] [-d] + [-s] [-E] [-D] [-U] [--empty PARTITION] + [--clear-bit CLEAR_BIT] [--set-bit SET_BIT] + [--toggle-bit TOGGLE_BIT] [-L | -P | -R] [-v] [-d] QEMU OT tool to manage OTP files. @@ -40,7 +40,9 @@ Commands: -s, --show show the OTP content -E, --ecc-recover attempt to recover errors with ECC -D, --digest check the OTP HW partition digest - -U, --update force-update QEMU OTP raw file after ECC recovery + -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 --clear-bit CLEAR_BIT clear a bit at specified location --set-bit SET_BIT set a bit at specified location @@ -150,6 +152,10 @@ 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. +* `--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. diff --git a/scripts/opentitan/ot/otp/image.py b/scripts/opentitan/ot/otp/image.py index 1743da4a1ba84..ed32781c55866 100644 --- a/scripts/opentitan/ot/otp/image.py +++ b/scripts/opentitan/ot/otp/image.py @@ -303,6 +303,42 @@ def toggle_bits(self, bitdefs: Sequence[tuple[int, int]]) -> None: """ self._change_bits(bitdefs, None) + def empty_partition(self, partition: Union[int, str]) -> None: + """Empty the whole content of a partition, including its digest if any, + and its ECC bits if any. + + :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}'") + part.empty() + if not part.is_empty: + raise RuntimeError(f"Unable to empty partition '{partition}'") + content = BytesIO() + part.save(content) + data = content.getvalue() + length = len(data) + offset = self._part_offsets[partix] + self._data[offset:offset+length] = data + self._ecc[offset // 2:(offset+length)//2] = bytes(length//2) + @staticmethod def bit_parity(data: int) -> int: """Compute the bit parity of an integer, i.e. reduce the vector to a diff --git a/scripts/opentitan/ot/otp/partition.py b/scripts/opentitan/ot/otp/partition.py index 7d1db8e41e352..eef57f817f424 100644 --- a/scripts/opentitan/ot/otp/partition.py +++ b/scripts/opentitan/ot/otp/partition.py @@ -59,6 +59,13 @@ def is_locked(self) -> bool: return (self.has_digest and self._digest_bytes and self._digest_bytes != bytes(self.DIGEST_SIZE)) + @property + def is_empty(self) -> bool: + """Report if the partition is empty.""" + if self._digest_bytes and sum(self._digest_bytes): + return False + return sum(self._data) == 0 + def __repr__(self) -> str: return repr(self.__dict__) @@ -72,6 +79,15 @@ def load(self, bfp: BinaryIO) -> None: self._digest_bytes = digest self._data = data + def save(self, bfp: BinaryIO) -> None: + """Save the content of the partition to a binary stream.""" + pos = bfp.tell() + bfp.write(self._data) + bfp.write(self._digest_bytes) + size = bfp.tell() - pos + if size != self.size: + raise RuntimeError(f"Failed to save partition {self.name} content") + def verify(self, digest_iv: int, digest_constant: int) -> Optional[bool]: """Verify if the digest matches the content of the partition, if any. """ @@ -179,6 +195,12 @@ def emit(fmt, *args): emit('%-48s %s %s', f'{pname}:DIGEST', soff, hexlify(self._digest_bytes).decode()) + def empty(self) -> None: + """Empty the partition, including its digest if any.""" + self._data = bytes(len(self._data)) + if self.has_digest: + self._digest_bytes = bytes(self.DIGEST_SIZE) + class OtpLifecycleExtension(OtpLifecycle, OtpPartitionDecoder): """Decoder for Lifecyle bytes sequences. diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index 913819d87080e..4372eda3c79f2 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -73,6 +73,10 @@ def main(): commands.add_argument('-U', '--update', action='store_true', help='update RAW file after ECC recovery or bit ' 'changes') + commands.add_argument('--empty', metavar='PARTITION', action='append', + default=[], + help='reset the content of a whole partition, ' + 'including its digest if any') commands.add_argument('--clear-bit', action='append', default=[], help='clear a bit at specified location') commands.add_argument('--set-bit', action='append', default=[], @@ -136,6 +140,8 @@ def main(): argparser.error('Cannot decode OTP values without an OTP map') if args.digest: argparser.error('Cannot verify OTP digests without an OTP map') + if args.empty: + argparser.error('Cannot empty OTP partition without an OTP map') else: otpmap = OtpMap() otpmap.load(args.otp_map) @@ -187,6 +193,9 @@ def main(): if args.show: argparser.error('Showing content of non-OpenTitan image is ' 'not supported') + if args.empty: + for part in args.empty: + otp.empty_partition(part) if args.iv: otp.set_digest_iv(args.iv) if args.constant: From 0a4d4452724a45799c312e9455d436ad1250866f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 17 Sep 2024 18:27:35 +0200 Subject: [PATCH 10/14] [ot] scripts/opentitan: otptool.py: add a new option to read Present constants from QEMU config file Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 40 +++++++++++++++++--------- scripts/opentitan/ot/otp/image.py | 48 +++++++++++++++++++++++++++++++ scripts/opentitan/ot/util/misc.py | 6 ++-- scripts/opentitan/otptool.py | 4 +++ 4 files changed, 83 insertions(+), 15 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index 29108e3071fb3..b86dea6715298 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -7,8 +7,8 @@ controller virtual device. ````text usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o C] [-r RAW] - [-k {auto,otp,fuz}] [-e BITS] [-c INT] [-i INT] [-w] [-n] - [-s] [-E] [-D] [-U] [--empty PARTITION] + [-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] [-L | -P | -R] [-v] [-d] @@ -30,6 +30,8 @@ Parameters: -k {auto,otp,fuz}, --kind {auto,otp,fuz} kind of content in VMEM input file, default: auto -e BITS, --ecc BITS ECC bit count + -C CONFIG, --config CONFIG + read Present constants from QEMU config file -c INT, --constant INT finalization constant for Present scrambler -i INT, --iv INT initialization vector for Present scrambler @@ -87,12 +89,18 @@ Fuse RAW images only use the v1 type. `-D` to verify partition digests, and stored in the optional QEMU OTP RAW image file for use by the virtual OTP controller when used along with the `-r` option. -* `-c` specify the register file, which is only useful to decode OTP content (see `-s` option). - This option is required when `-D` Present digest checking is used. +* `-C` specify a QEMU [configuration file](otcfg.md) from which to read the Present constants that + are required for digest computation. It is a convenience switch to replace both `-i` and options. + See [`cfggen.py`](cfggen.md) tool to generate such a file. + +* `-c` specify the initialization constant for the Present scrambler used for partition digests. + This option is required when `-D` Present digest checking is used. See also `-i` option switch. + Override option `-C` if any. * `-D` performs a partition digest checks for all partitions with a defined digest. The Present constant should be defined to perform digest verification. They can be specified with the `-c` and - `-i` options switches, or when using a QEMU OTP RAW v2 file that stores these constants. + `-i` options switches, or when using a QEMU OTP RAW v2 file that stores these constants, or when + a QEMU configuration file is specified with the `-C` option. * `-d` only useful to debug the script, reports any Python traceback to the standard error stream. @@ -101,6 +109,12 @@ 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. +* `-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 + `-D` to verify partition digests, and stored in the optional output OTP image file for use by + the virtual OTP controller when used along with the `-o` option. Override option `-C` if any. + * `-j` specify the path to the HJSON OTP controller map file, usually stored in OT `hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson`. This file is required with many options when the OTP image file needs to be interpreted, such as digest verification, content dump, C file generation, @@ -117,12 +131,6 @@ Fuse RAW images only use the v1 type. states. This option is not required to generate a RAW image file, but required when the `-L` option switch is used. -* `-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 - `-D` to verify partition digests, and stored in the optional output OTP image file for use by - the virtual OTP controller when used along with the `-o` option. - * `-m` specify the input VMEM file that contains the OTP fuse content. See also the `-k` option. * `-n` tell the script not to attempt to decode the content of encoded fields, such as the hardened @@ -170,8 +178,8 @@ Fuse RAW images only use the v1 type. #### Note -Earlgrey OTP virtual device has not been updated to support Present scrambler, so neither `-C` nor -`-I` option should be used to generate an Earlgrey-compatible RAW image. +Earlgrey OTP virtual device has not been updated to support Present scrambler, so neither `-c` nor +`-i` option should be used to generate an Earlgrey-compatible RAW image. ### Bit position specifier [#bit-syntax] @@ -206,6 +214,12 @@ scripts/opentitan/otptool.py -m img_rma.24.vmem -r otp.raw \ -i 0x0123456789abcdef -c 0x00112233445566778899aabbccddeeff ```` +Generate a QEMU RAW v2 image for the virtual OTP controller, here with an RMA OTP configuration, +load Present constants from a QEMU configuration file. +````sh +scripts/opentitan/otptool.py -m img_rma.24.vmem -r otp.raw -i ot.cfg +```` + Decode the content of an OTP VMEM file: ````sh scripts/opentitan/otptool.py -m img_rma.24.vmem -j otp_ctrl_mmap.hjson -s diff --git a/scripts/opentitan/ot/otp/image.py b/scripts/opentitan/ot/otp/image.py index ed32781c55866..e1c1340d7685f 100644 --- a/scripts/opentitan/ot/otp/image.py +++ b/scripts/opentitan/ot/otp/image.py @@ -7,6 +7,7 @@ """ from binascii import unhexlify +from configparser import ConfigParser, NoOptionError from io import BytesIO from logging import getLogger from re import match as re_match, sub as re_sub @@ -52,6 +53,9 @@ class OtpImage: 0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x34, 0x35) ) + DEFAULT_OTP_DEVICE = 'ot-otp-dj' + """Default OTP device name in configuration file.""" + def __init__(self, ecc_bits: Optional[int] = None): self._log = getLogger('otp.img') self._header: dict[str, Any] = {} @@ -202,6 +206,50 @@ def load_lifecycle(self, lcext: OtpLifecycleExtension) -> None: if part.name == 'LIFE_CYCLE': part.set_decoder(lcext) + def load_config(self, cfp: TextIO, section: Optional[str] = None) -> None: + """Load Present constant from a QEMU configuration file. + + :param cfp: the configuration stream + :param section: the section name where to find the constants, + default to DEFAULT_OTP_DEVICE. + """ + cfg = ConfigParser() + cfg.read_file(cfp) + if not section: + section = self.DEFAULT_OTP_DEVICE + sectnames = set() + for sectname in cfg.sections(): + if not sectname.startswith('ot_device '): + continue + try: + devname = sectname.strip().split('"')[1] + except IndexError: + continue + if devname != section and not devname.startswith(f'{section}.'): + continue + sectnames.add(sectname) + self._log.info('Found OTP candidate section %s', devname) + if not sectnames: + raise ValueError(f"Cannot find OTP device section '{section}'") + # accept multiple sections with Present constants definitions as long + # as they do match + constants = set() + ivs = set() + for sectname in sectnames: + try: + constants.add(cfg.get(sectname, 'digest_const') + .strip('"').lower()) + ivs.add(cfg.get(sectname, 'digest_iv') + .strip('"').lower()) + except NoOptionError: + pass + if not constants or not ivs: + raise ValueError('Incomplete configuration file') + if len(constants) > 1 or len(ivs) > 1: + raise ValueError(f"Too many OTP device sections '{section}'") + self._digest_constant = HexInt.parse(constants.pop(), 16) + self._digest_iv = HexInt.parse(ivs.pop(), 16) + # pylint: disable=invalid-name def set_digest_iv(self, iv: int) -> None: """Set the Present digest initialization 64-bit vector.""" diff --git a/scripts/opentitan/ot/util/misc.py b/scripts/opentitan/ot/util/misc.py index a60de0a7a038d..8a1d7af5b4253 100644 --- a/scripts/opentitan/ot/util/misc.py +++ b/scripts/opentitan/ot/util/misc.py @@ -32,11 +32,13 @@ def __repr__(self) -> str: return f'0x{self:x}' @staticmethod - def parse(val: Optional[str]) -> Optional[int]: + def parse(val: Optional[str], base: Optional[int] = None) -> Optional[int]: """Simple helper to support hexadecimal integer in argument parser.""" if val is None: return None - return int(val, val.startswith('0x') and 16 or 10) + if base is not None: + return HexInt(int(val, base)) + return HexInt(int(val, val.startswith('0x') and 16 or 10)) class EasyDict(dict): diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index 4372eda3c79f2..cf4661c06cb6f 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -53,6 +53,8 @@ def main(): params.add_argument('-e', '--ecc', type=int, default=OtpImage.DEFAULT_ECC_BITS, metavar='BITS', help='ECC bit count') + params.add_argument('-C', '--config', type=FileType('rt'), + help='read Present constants from QEMU config file') params.add_argument('-c', '--constant', type=HexInt.parse, metavar='INT', help='finalization constant for Present scrambler') @@ -196,6 +198,8 @@ def main(): if args.empty: for part in args.empty: otp.empty_partition(part) + if args.config: + otp.load_config(args.config) if args.iv: otp.set_digest_iv(args.iv) if args.constant: From aff3b52ee0ec4749ff222807c27a03c3501d5757 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Sep 2024 11:38:01 +0200 Subject: [PATCH 11/14] [ot] scripts/opentitan: otptool.py: add LC template generation Rework all generation-related option to simplify the syntax. Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 45 +++++++++++++++++---------- scripts/opentitan/cfggen.py | 4 +-- scripts/opentitan/ot/otp/lifecycle.py | 36 ++++++++++++++++++--- scripts/opentitan/otptool.py | 37 +++++++++++----------- 4 files changed, 79 insertions(+), 43 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index b86dea6715298..3efc32a770642 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -10,7 +10,8 @@ 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] [-L | -P | -R] [-v] [-d] + [--toggle-bit TOGGLE_BIT] [-G {LCVAL,LCTPL,PARTS,REGS}] [-v] + [-d] QEMU OT tool to manage OTP files. @@ -23,7 +24,7 @@ Files: -m VMEM, --vmem VMEM input VMEM file -l SV, --lifecycle SV input lifecycle system verilog file - -o C, --output C output C file + -o C, --output C output filename for C file generation -r RAW, --raw RAW QEMU OTP raw image file Parameters: @@ -50,9 +51,8 @@ Commands: --set-bit SET_BIT set a bit at specified location --toggle-bit TOGGLE_BIT toggle a bit at specified location - -L, --generate-lc generate lc_ctrl C arrays - -P, --generate-parts generate partition descriptor C arrays - -R, --generate-regs generate partition register C definitions + -G {LCVAL,LCTPL,PARTS,REGS}, --generate {LCVAL,LCTPL,PARTS,REGS} + generate C code, see doc for options Extras: -v, --verbose increase verbosity @@ -124,8 +124,9 @@ Fuse RAW images only use the v1 type. the kind of the input VMEM file from its content when this option is not specified or set to `auto`. It is fails to detect the file kind or if the kind needs to be enforced, use this option. -* `-L` generate a file describing the LifeCycle contants as C arrays. Mutually exclusive with the - `-P` and `-R` option switches. See `-o` to specify an output file. +* `-G` can be used to generate C code for QEMU, from OTP and LifeCycle known definitions. See the + [Generation](#generation) section for details. See option `-o` to specify the path to the file to + generate * `-l` specify the life cycle system verilog file that defines the encoding of the life cycle states. This option is not required to generate a RAW image file, but required when the `-L` @@ -136,14 +137,8 @@ Fuse RAW images only use the v1 type. * `-n` tell the script not to attempt to decode the content of encoded fields, such as the hardened booleans values. When used, the raw value of each field is printed out. -* `-o` specify the path to the output C file to generate, see `-L`, `-P` and `-R` option switches. - Defaults to the standard output. - -* `-P` generate a file describing the OTP partition properties as C arrays. Mutually exclusive with - the `-L` and `-R` option switches. See `-o` to specify an output file. - -* `-R` generate a file describing the OTP partition registers as C defintion. Mutually exclusive - with the `-L` and `-P` option switches. See `-o` to specify an output file. +* `-o` specify the path to the output C file to generate, see `-G` option. If not specified, the + generated file is emitted on the standard output. * `-r` specify the path to the QEMU OTP RAW image file. When used with the `-m` option switch, a new QEMU OTP image file is generated. Otherwise, the QEMU OTP RAW image file should exist and is @@ -194,6 +189,22 @@ If the bit is larger than the data slot, it indicates the location with the ECC fuses are organized as 16-bit slots wtih 6-bit ECC, bit 0 to 15 indicates a bit into the data slot, while bit 16 to 21 indicates an ECC bit. +### Generation [#generation] + +The generation feature may be used to generate part of the OTP and LifeCycle QEMU implementation, +based on known definitions from the OpenTitan constants. This option accepts on of the following +argument: + +* `LCVAL` generates a file describing the LifeCycle constants as C arrays. Requires `-l` option. +* `LCTPL` generates a file describing the LifeCycle State encoding as a C array. . Requires `-l` + option. +* `PARTS` generates a file describing the OTP partition properties as C arrays. Requires `-j` + option. +* `REGS` generates a file describing the OTP partition registers as C definitions. Requires `-j` + option. + + See `-o` to specify an output file for the generated file. + ### Examples Generate a QEMU RAW v1 image for the virtual OTP controller, here with an RMA OTP configuration: @@ -248,10 +259,10 @@ scripts/opentitan/otptool.py -r otp.raw -j otp_ctrl_mmap.hjson -D \ Generate a C source file with LifeCycle constant definitions: ````sh -scripts/opentitan/otptool.py -L -l lc_ctrl_state_pkg.sv -o lc_state.c +scripts/opentitan/otptool.py -G LCVAL -l lc_ctrl_state_pkg.sv -o lc_state.c ```` Generates a C source file with OTP partition properties: ````sh -scripts/opentitan/otptool.py -j otp_ctrl_mmap.hjson -P -o otp_part.c +scripts/opentitan/otptool.py -j otp_ctrl_mmap.hjson -G PARTS -o otp_part.c ```` diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index 9f00e18df42f8..4f774dbcfc773 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -13,7 +13,7 @@ from logging import getLogger from os.path import isdir, isfile, join as joinpath, normpath from re import match, search -from sys import exit as sysexit, modules, stderr +from sys import exit as sysexit, modules, stderr, stdout from traceback import format_exc from typing import Optional @@ -107,7 +107,7 @@ def save(self, socid: Optional[str] = None, count: Optional[int] = 1, with open(outpath, 'wt') as ofp: cfg.write(ofp) else: - cfg.write(stderr) + cfg.write(stdout) @classmethod def add_pair(cls, data: dict[str, str], kname: str, value: str) -> None: diff --git a/scripts/opentitan/ot/otp/lifecycle.py b/scripts/opentitan/ot/otp/lifecycle.py index 9456ee3259bcc..dd0ffd85ff42a 100644 --- a/scripts/opentitan/ot/otp/lifecycle.py +++ b/scripts/opentitan/ot/otp/lifecycle.py @@ -10,11 +10,11 @@ from io import StringIO from logging import getLogger from os.path import basename -from re import finditer, match +from re import finditer, match, sub from textwrap import fill from typing import TextIO -from ot.util.misc import camel_to_snake_case +from ot.util.misc import camel_to_snake_case, group class OtpLifecycle: @@ -31,6 +31,7 @@ class OtpLifecycle: def __init__(self): self._log = getLogger('otp.lc') + self._sequences: dict[str, list[str]] = {} self._tables: dict[str, dict[str, str]] = {} self._tokens: dict[str, str] = {} @@ -55,6 +56,7 @@ def load(self, svp: TextIO): name = abmo.group(1) sval = abmo.group(2) val = int(sval[1:], 2 if sval.startswith('b') else 16) + val = ((val >> 8) & 0xff) | ((val & 0xff) << 8) if name in codes: self._log.error('Redefinition of %s', name) continue @@ -65,7 +67,7 @@ def load(self, svp: TextIO): kind = smo.group(1).lower() name = smo.group(2) seq = smo.group(3) - items = [x.strip() for x in seq.split(',')] + items = [x.strip() for x in reversed(seq.split(','))] inv = [it for it in items if it not in codes] if inv: self._log.error('Unknown state seq: %s', ', '.join(inv)) @@ -73,6 +75,7 @@ def load(self, svp: TextIO): sequences[kind] = {} sequences[kind][name] = items continue + self._sequences = sequences svp.seek(0) for tmo in finditer(r"\s+parameter\s+lc_token_t\s+(\w+)\s+=" r"\s+\{\s+128'h([0-9A-F]+)\s+\};", @@ -89,13 +92,21 @@ def load(self, svp: TextIO): seq = ''.join((f'{x:04x}'for x in map(codes.get, seq))) self._tables[mkind][seq] = conv(ref) - def save(self, cfp: TextIO): + def save(self, cfp: TextIO, data_mode: bool) -> None: """Save OTP life cycle definitions as a C file. :param cfp: output text stream + :param data_mode: whether to output data or template """ print(f'/* Section auto-generated with {basename(__file__)} ' f'script */', file=cfp) + if data_mode: + self._save_data(cfp) + else: + self._save_template(cfp) + print('/* End of auto-generated section */', file=cfp) + + def _save_data(self, cfp: TextIO) -> None: for kind, table in self._tables.items(): enum_io = StringIO() array_io = StringIO() @@ -125,7 +136,22 @@ def save(self, cfp: TextIO): # likely to be moved to a header file print(f'enum {kind.lower()} {{\n{enum_str}}};\n', file=cfp) print(f'{array_io.getvalue()}', file=cfp) - print('/* End of auto-generated section */', file=cfp) + + def _save_template(self, cfp: TextIO) -> None: + print('/* clang-format off */', file=cfp) + states = self._sequences.get('st') or {} + print('static const uint8_t', file=cfp) + print('LC_STATES_TPL[LC_STATE_VALID_COUNT][LC_STATE_SLOT_COUNT] = {', + file=cfp) + for stname, stwords in states.items(): + print(f' [LC_STATE_{stname.upper()}] = {{', file=cfp) + for wgrp in group(stwords, len(stwords)//2): + items = (sub(r'(\d+)', r'(\1)', wg) for wg in wgrp) + stws = ' '.join(f'{w:<6s}' for w in (f'{i},' for i in items)) + print(f' {stws.rstrip()}', file=cfp) + print(' },', file=cfp) + print('};', file=cfp) + print('/* clang-format on */', file=cfp) def get_configuration(self, name: str) -> dict[str, str]: """Provide a dictionary of configurable elements for QEMU.""" diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index cf4661c06cb6f..8903b1ba4d05f 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -28,6 +28,7 @@ def main(): """Main routine""" debug = True + genfmts = 'LCVAL LCTPL PARTS REGS'.split() try: desc = modules[__name__].__doc__.split('.', 1)[0].strip() argparser = ArgumentParser(description=f'{desc}.') @@ -41,7 +42,7 @@ def main(): metavar='SV', help='input lifecycle system verilog file') files.add_argument('-o', '--output', metavar='C', type=FileType('wt'), - help='output C file') + help='output filename for C file generation') files.add_argument('-r', '--raw', help='QEMU OTP raw image file') params = argparser.add_argument_group(title='Parameters') @@ -85,13 +86,8 @@ def main(): help='set a bit at specified location') commands.add_argument('--toggle-bit', action='append', default=[], help='toggle a bit at specified location') - generate = commands.add_mutually_exclusive_group() - generate.add_argument('-L', '--generate-lc', action='store_true', - help='generate lc_ctrl C arrays') - generate.add_argument('-P', '--generate-parts', action='store_true', - help='generate partition descriptor C arrays') - generate.add_argument('-R', '--generate-regs', action='store_true', - help='generate partition register C definitions') + commands.add_argument('-G', '--generate', choices=genfmts, + help='generate C code, see doc for options') extra = argparser.add_argument_group(title='Extras') extra.add_argument('-v', '--verbose', action='count', help='increase verbosity') @@ -136,7 +132,7 @@ def main(): partdesc: Optional[OTPPartitionDesc] = None if not args.otp_map: - if args.generate_parts or args.generate_regs: + if args.generate in ('PARTS', 'REGS'): argparser.error('Generator requires an OTP map') if args.show: argparser.error('Cannot decode OTP values without an OTP map') @@ -151,22 +147,26 @@ def main(): if args.lifecycle: lcext = OtpLifecycleExtension() lcext.load(args.lifecycle) + elif args.generate in ('LCVAL', 'LCTPL'): + argparser.error('Cannot generate LC array w/o a lifecycle file') output = stdout if not args.output else args.output - if args.generate_parts: + if not args.generate: + pass + elif args.generate == 'PARTS': partdesc = OTPPartitionDesc(otpmap) partdesc.save(basename(args.otp_map.name), basename(argv[0]), output) - - if args.generate_regs: + elif args.generate == 'REGS': regdef = OTPRegisterDef(otpmap) regdef.save(basename(args.otp_map.name), basename(argv[0]), output) - - if args.generate_lc: - if not lcext: - argparser.error('Cannot generate LC array w/o a lifecycle file') - lcext.save(output) + elif args.generate == 'LCVAL': + lcext.save(output, True) + elif args.generate == 'LCTPL': + lcext.save(output, False) + else: + argparser.error(f'Unsupported generation: {args.generate}') if args.vmem: otp.load_vmem(args.vmem, args.kind) @@ -186,8 +186,7 @@ def main(): if otp.loaded: if not otp.is_opentitan: - ot_opts = ('iv', 'constant', 'digest', 'generate_parts', - 'generate_regs', 'generate_lc', 'otp_map', + ot_opts = ('iv', 'constant', 'digest', 'generate', 'otp_map', 'lifecycle') if any(getattr(args, a) for a in ot_opts): argparser.error('Selected option only applies to OpenTitan ' From 3481d6ad78871d96440d61ab7e789e97e1c109c4 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Sep 2024 12:18:24 +0200 Subject: [PATCH 12/14] [ot] scripts/opentitan: pyot.py: add a quieter log option Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 6 ++++-- scripts/opentitan/pyot.py | 14 +++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index 8d9044ce8d003..a8f06c746be1b 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -10,8 +10,8 @@ usage: pyot.py [-h] [-D DELAY] [-i ICOUNT] [-L LOG_FILE] [-M VARIANT] [-N LOG] [-t TRACE] [-S FIRST_SOC] [-s] [-U] [-b file] [-c JSON] [-e] [-f RAW] [-g file] [-K] [-l file] [-O RAW] [-o VMEM] [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] [-k SECONDS] [-z] [-R] - [-T FACTOR] [-Z] [-v] [-V] [-d] [--log-time] [--debug LOGGER] - [--info LOGGER] [--warn LOGGER] + [-T FACTOR] [-Z] [-v] [-V] [-d] [--quiet] [--log-time] + [--debug LOGGER] [--info LOGGER] [--warn LOGGER] OpenTitan QEMU unit test sequencer. @@ -82,6 +82,7 @@ Extras: -v, --verbose increase verbosity -V, --vcp-verbose increase verbosity of QEMU virtual comm ports -d enable debug mode + --quiet quiet logging: only be verbose on errors --log-time show local time in log messages --debug LOGGER assign debug level to logger(s) --info LOGGER assign info level to logger(s) @@ -182,6 +183,7 @@ This tool may be used in two ways, which can be combined: * `-V` / `--vcp-verbose` can be repeated to increase verbosity of the QEMU virtual comm ports * `-v` / `--verbose` can be repeated to increase verbosity of the script, mostly for debug purpose. * `-d` only useful to debug the script, reports any Python traceback to the standard error stream. +* `--quiet` only emit verbose log traces if an error is detected * `--log-time` show local time before each logged message * `--debug` enable the debug level for the selected logger, may be repeated * `--info` enable the info level for the selected logger, may be repeated diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index c2c768f49fc35..c4a507cb95ee0 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -288,8 +288,9 @@ def trig_match(bline): vcplogname = 'pyot.vcp' while connect_map: if now() > timeout: - raise TimeoutError(f'Cannot connect to QEMU VCPs ' - f'{", ".join(connect_map)}') + minfo = ', '.join(f'{d} @ {r[0]}:{r[1]}' + for d, r in connect_map.items()) + raise TimeoutError(f'Cannot connect to QEMU VCPs: {minfo}') connected = [] for vcpid, (host, port) in connect_map.items(): try: @@ -1778,7 +1779,7 @@ def main(): exe.add_argument('-F', '--filter', metavar='TEST', action='append', help='run tests with matching filter, prefix with "!" ' 'to exclude matching tests') - exe.add_argument('-k', '--timeout', metavar='SECONDS', type=int, + exe.add_argument('-k', '--timeout', metavar='SECONDS', type=float, help=f'exit after the specified seconds ' f'(default: {DEFAULT_TIMEOUT} secs)') exe.add_argument('-z', '--list', action='store_true', @@ -1797,6 +1798,8 @@ def main(): help='increase verbosity of QEMU virtual comm ports') extra.add_argument('-d', action='store_true', help='enable debug mode') + extra.add_argument('--quiet', action='store_true', + help='quiet logging: only be verbose on errors') extra.add_argument('--log-time', action='store_true', help='show local time in log messages') extra.add_argument('--debug', action='append', metavar='LOGGER', @@ -1826,8 +1829,9 @@ def main(): log = configure_loggers(args.verbose, 'pyot', args.vcp_verbose or 0, 'pyot.vcp', name_width=16, - ms=args.log_time, debug=args.debug, - info=args.info, warning=args.warn)[0] + ms=args.log_time, quiet=args.quiet, + debug=args.debug, info=args.info, + warning=args.warn)[0] qfm = QEMUFileManager(args.keep_tmp) qfm.set_qemu_src_dir(qemu_dir) From 3d60b68782c6ab0d8b5a878965c75dc60a39bddf Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Sep 2024 14:58:35 +0200 Subject: [PATCH 13/14] [ot] scripts/opentitan: cfggen.py: ensure keys are alpha-sorted Preserving order help tracking changes across sessions. Signed-off-by: Emmanuel Blot --- scripts/opentitan/cfggen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index 4f774dbcfc773..8c9935dec963c 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -155,7 +155,7 @@ def _generate_roms(self, cfg: ConfigParser, socid: Optional[str] = None, nameargs.append(f'rom{rom}') romname = '.'.join(nameargs) romdata = {} - for kname, val in data.items(): + for kname, val in sorted(data.items()): self.add_pair(romdata, kname, val) cfg[f'ot_device "{romname}"'] = romdata @@ -172,6 +172,7 @@ def _generate_otp(self, cfg: ConfigParser, socid: Optional[str] = None) \ self.add_pair(otpdata, 'lc_trscnt_last', self._lc_transitions[-1]) for kname, val in self._otp.items(): self.add_pair(otpdata, kname, val) + otpdata = dict(sorted(otpdata.items())) cfg[f'ot_device "{otpname}"'] = otpdata def _generate_life_cycle(self, cfg: ConfigParser, From 8428805758f9c40afb1bf8a3d012af80a7340fac Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Sep 2024 16:27:24 +0200 Subject: [PATCH 14/14] [ot] scripts/opentitan: remove deprecated, unmaintained scripts Signed-off-by: Emmanuel Blot --- docs/opentitan/tools.md | 10 +- scripts/opentitan/otboot.sh | 198 ---------------------------- scripts/opentitan/otphelp.py | 168 ------------------------ scripts/opentitan/otrun.sh | 245 ----------------------------------- scripts/opentitan/ottests.sh | 166 ------------------------ 5 files changed, 2 insertions(+), 785 deletions(-) delete mode 100755 scripts/opentitan/otboot.sh delete mode 100755 scripts/opentitan/otphelp.py delete mode 100755 scripts/opentitan/otrun.sh delete mode 100755 scripts/opentitan/ottests.sh diff --git a/docs/opentitan/tools.md b/docs/opentitan/tools.md index 5d6062dce8918..553179583f415 100644 --- a/docs/opentitan/tools.md +++ b/docs/opentitan/tools.md @@ -2,18 +2,12 @@ All the OpenTitan tools and associated files are stored in the `scripts/opentitan` directory. -## Execution wrappers +## Execution wrapper Launching a QEMU VM with the right option switches may rapidly become complex due to the number -of options and the available features. Several helper tools are provided in the `scripts/opentitan` -directory to help with these tasks. +of options and the available features. * [`pyot.py`](pyot.md) can be used to run unit tests and OpenTitan tests -* `otboot.sh` is a wrapper script to build image files and execute a ROM/ROM_EXT/BL0 execution - session. -* `otrun.sh` is a wrapper script to build image files and execute an OpenTitan test. -* `ottests.sh` is a wrapper script to execute many OpenTitan tests and report a list of successes - and failures. ## Companion file management diff --git a/scripts/opentitan/otboot.sh b/scripts/opentitan/otboot.sh deleted file mode 100755 index b755e65678dc3..0000000000000 --- a/scripts/opentitan/otboot.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/sh - -# Copyright (c) 2023-2024 Rivos, Inc. -# SPDX-License-Identifier: Apache2 - -# Die with an error message -die() { - echo >&2 "$*" - exit 1 -} - -# Show usage information -usage() { - NAME=$(basename $0) -cat < [-- [QEMU_options ...]] - - Execute an OpenTitan boot up sequence with ROM/ROM_EXT/BL0 binaries - - -h Print this help message - -b Build QEMU - -f Use a flat tree (files are stored in the same dir) - -U Run flashgen.py in unsafe mode (no ELF checks) - -v Verbose mode, show executed commands - -- All following options are forwarded to QEMU - - ot_dir OpenTitan top directory or flat dir with packaged file (see -F) -EOT -} - -OT_DIR="" -BUILD_QEMU=0 -FLASHGEN_OPTS="" -FLAT_TREE=0 -VERBOSE=0 - -# Parse options -while [ $# -gt 0 ]; do - case "$1" in - -h) - usage - exit 0 - ;; - -b) - BUILD_QEMU=1 - ;; - -f) - FLAT_TREE=1 - ;; - -v) - VERBOSE=1 - ;; - -U) - FLASHGEN_OPTS="${FLASHGEN_OPTS} -U" - ;; - --) - shift - break - ;; - -*) - die "Unknown option $1" - ;; - *) - [ -z "${OT_DIR}" ] || die "ot_dir already specified" - OT_DIR="$1" - ;; - esac - shift -done - -if [ -z "${OT_DIR}" ]; then - usage - exit 0 -fi - -SCRIPT_DIR="$(dirname $0)" -QEMU_DIR="$(realpath ${SCRIPT_DIR}/../..)" -QEMU_BUILD_DIR="${QEMU_DIR}/build" - -if [ ${FLAT_TREE} -gt 0 ]; then - # flat tree mode expects all OT artifacts to be packaged in a single directory - # with no subtree. This mode is useful when OT is built on a machine and - # run on another one (if for some reason bazel fails, which never happens...) - if [ ! -f "${OT_DIR}/rom_with_fake_keys_fpga_cw310.bin" ]; then - echo >&2 "Invalid ot_pack_dir ${OT_DIR}" - exit 1 - fi - OT_OTP_VMEM="${OT_DIR}/img_rma.24.vmem" - OT_ROM_BIN="${OT_DIR}/rom_with_fake_keys_fpga_cw310.bin" - OT_ROM_EXT_BIN="${OT_DIR}/rom_ext_slot_virtual_fpga_cw310.fake_rsa_test_key_0.signed.bin" - OT_BL0_TEST_BIN="${OT_DIR}/bare_metal_slot_virtual_fpga_cw310.fake_rsa_rom_ext_test_key_0.signed.bin" -else - [ -x "${OT_DIR}/bazelisk.sh" ] || \ - die "${OT_DIR} is not a top-level OpenTitan directory" - GIT="$(which git 2>/dev/null)" - if [ -n "${GIT}" ]; then - if [ -f "${QEMU_DIR}/hw/opentitan/ot_ref.log" ]; then - . "${QEMU_DIR}/hw/opentitan/ot_ref.log" - if [ -n "${GIT_COMMIT}" ]; then - OT_GIT_COMMIT="$(cd ${OT_DIR} && ${GIT} rev-parse HEAD)" - if [ "${OT_GIT_COMMIT}" != "${GIT_COMMIT}" ]; then - echo >&2 "Warning: OpenTitan repo differs from QEMU supported version" - fi - fi - fi - fi - # default mode, use bazel to retrieve the path of artifacts - # Bazel setup is out of scope of this script, please refer to OpenTitan - # official installation instructions first - # Do not forget to run this script from your OT venv if you use one. - # Note: no idea on how to retrieve ELF files in this case (prefer flatdir option) - OT_OTP_VMEM=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery //hw/ip/otp_ctrl/data:img_rma) || \ - die "Bazel in trouble" - OT_OTP_VMEM="$(realpath ${OT_DIR}/${OT_OTP_VMEM})" - [ -f "${OT_OTP_VMEM}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build //hw/ip/otp_ctrl/data:img_rma) || \ - die "Cannot build $(basename ${OT_OTP_VMEM})" - OT_ROM_BIN=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery \ - //sw/device/silicon_creator/rom:rom_with_fake_keys_fpga_cw310) || \ - die "Bazel in trouble" - OT_ROM_BIN="$(realpath ${OT_DIR}/${OT_ROM_BIN})" - [ -f "${OT_ROM_BIN}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build //sw/device/silicon_creator/rom:rom_with_fake_keys_fpga_cw310) || \ - die "Cannot build $(basename ${OT_ROM_BIN})" - OT_ROM_EXT_BIN=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery \ - //sw/device/silicon_creator/rom_ext:rom_ext_slot_virtual_fpga_cw310_bin_signed_fake_rsa_test_key_0) || \ - die "Bazel in trouble" - OT_ROM_EXT_BIN="$(realpath ${OT_DIR}/${OT_ROM_EXT_BIN})" - [ -f "${OT_ROM_EXT_BIN}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build \ - //sw/device/silicon_creator/rom_ext:rom_ext_slot_virtual_fpga_cw310_bin_signed_fake_rsa_test_key_0) || \ - die "Cannot build $(basename ${OT_ROM_EXT_BIN})" - OT_BL0_TEST_BIN=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery \ - //sw/device/silicon_owner/bare_metal:bare_metal_slot_virtual_fpga_cw310_bin_signed_fake_rsa_rom_ext_test_key_0) || \ - die "Bazel in trouble" - OT_BL0_TEST_BIN="$(realpath ${OT_DIR}/${OT_BL0_TEST_BIN})" - [ -f "${OT_BL0_TEST_BIN}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build \ - //sw/device/silicon_owner/bare_metal:bare_metal_slot_virtual_fpga_cw310_bin_signed_fake_rsa_rom_ext_test_key_0) || \ - die "Cannot build $(basename ${OT_BL0_TEST_BIN})" -fi - -# sanity checks -[ -f "${OT_OTP_VMEM}" ] || die "Unable to find OTP image $(basename ${OT_OTP_VMEM})" -[ -f "${OT_ROM_BIN}" ] || die "Unable to find ROM binary $(basename ${OT_ROM_BIN})" -[ -f "${OT_ROM_EXT_BIN}" ] || die "Unable to find ROM_EXT binary $(basename ${OT_ROM_EXT_BIN})" -[ -f "${OT_BL0_TEST_BIN}" ] || die "Unable to find BL0 test binary $(basename ${OT_BL0_TEST_BIN})" - -if [ ${BUILD_QEMU} -gt 0 ]; then - if [ ! -d ${QEMU_BUILD_DIR} ]; then - mkdir ${QEMU_BUILD_DIR} || die "Cannot create QEMU build directory" - fi - if [ ! -f "${QEMU_BUILD_DIR}/build.ninja" ]; then - if [ "$(uname -s)" = "Darwin" ]; then - # Cocoa deprecated APIs not yet fixed... - QEMU_BUILD_OPT="--extra-cflags=-Wno-deprecated-declarations" - else - QEMU_BUILD_OPT="--without-default-features --enable-tcg --enable-trace-backends=log" - fi - # note: Meson does not seem to cope well with symlinks - (cd ${QEMU_BUILD_DIR} && - ${QEMU_DIR}/configure --target-list=riscv32-softmmu \ - ${QEMU_BUILD_OPT} --enable-debug) || \ - die "Cannot configure QEMU" - fi - (cd ${QEMU_BUILD_DIR} && ninja) || die "Cannot build QEMU" -fi - -[ -x "${QEMU_BUILD_DIR}/qemu-system-riscv32" ] || die "QEMU has not been build yet" - -# QEMU loads the ROM image using the ELF information to locate and execute the -# ROM. This is useful to get debugging symbols -OT_ROM_ELF="${OT_ROM_BIN%.*}.elf" -[ -f "${OT_ROM_ELF}" ] || die "Unable to find ROM ELF file for $(basename ${OT_ROM_BIN})" -QEMU_GUEST_OPT="-object ot-rom_img,id=rom,file=${OT_ROM_ELF}" - -if [ ${VERBOSE} -gt 0 ];then - set -x -fi - -${SCRIPT_DIR}/otptool.py -v -m "${OT_OTP_VMEM}" -r otp.raw || die "otptool.py failed" - -# note: it is recommended to place the original ELF file from which the binary -# files have been generated from. If flashgen.py locates the matching ELF file, -# the ROM_EXT and BL0 symbols can be automatically loaded by QEMU, which helps -# debugging -${SCRIPT_DIR}/flashgen.py -v -D -x "${OT_ROM_EXT_BIN}" -b "${OT_BL0_TEST_BIN}" \ - flash.raw ${FLASHGEN_OPTS} || die "flashgen.py failed" - -echo "Use [Ctrl-A] + x to quit QEMU" -(cd ${QEMU_BUILD_DIR} && ./qemu-system-riscv32 \ - -M ot-earlgrey -display none -serial mon:stdio ${QEMU_GUEST_OPT} \ - -drive if=pflash,file=${QEMU_DIR}/otp.raw,format=raw \ - -drive if=mtd,bus=1,file=${QEMU_DIR}/flash.raw,format=raw $*) diff --git a/scripts/opentitan/otphelp.py b/scripts/opentitan/otphelp.py deleted file mode 100755 index 32150116960f0..0000000000000 --- a/scripts/opentitan/otphelp.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (c) 2023-2024 Rivos, Inc. -# SPDX-License-Identifier: Apache2 - -"""OpenTitan OTP HJSON helper. - - :note: this script is deprecated and should be merged into otptool.py - - :author: Emmanuel Blot -""" - - -from argparse import ArgumentParser, FileType -try: - # try to use HJSON if available - from hjson import load as jload -except ImportError as _exc: - raise ImportError("HJSON Python module required") from _exc -from logging import getLogger -from os import linesep -from pprint import pprint -from sys import exit as sysexit, modules, stderr -from traceback import format_exc -from typing import BinaryIO - -from ot.util.log import configure_loggers - -# pylint: disable=missing-docstring - - -class OtpHelper: - - def __init__(self): - self._log = getLogger('otp.help') - self._data = {} - self._params = {} - self._registers = {} - self._regwens = {} - self._readonlys = [] - self._writeonlys = [] - - def load(self, hfp: BinaryIO) -> None: - self._data = jload(hfp, object_pairs_hook=dict) - for param in self._data.get('param_list', []): - if param['type'] == 'int': - self._params[param['name']] = int(param['default']) - for regdef in self._data['registers']['core']: - multireg = regdef.get('multireg') - if multireg: - countparam = multireg.get('count') - if countparam in self._params: - count = self._params[countparam] - regdef = multireg - else: - count = 1 - name = regdef.get('name') - if not name: - continue - regs = [name] if count == 1 else [f'{name}_{p}' - for p in range(count)] - regwen = regdef.get('regwen') - if regwen: - if regwen not in self._regwens: - self._regwens[regwen] = [] - for regname in regs: - self._regwens[regwen].append(regname) - swaccess = regdef.get('swaccess') - if swaccess == 'ro': - for regname in regs: - self._readonlys.append(regname) - elif swaccess == 'wo': - for regname in regs: - self._writeonlys.append(regname) - - def make_read(self, c_code: bool = False, indent=4): - if not self._writeonlys: - return - if not c_code: - pprint(self._writeonlys) - return - lns = [] - lns.append('/* READ HELPERS */') - lns.append('switch(reg) {') - idt = ' ' * indent - if self._writeonlys: - for reg in self._writeonlys: - lns.append(f'case R_{reg}:') - lns.append(' qemu_log_mask(LOG_GUEST_ERROR, "%s: W/O register' - ' 0x%03" HWADDR_PRIx " (%s)\\n",') - lns.append(' __func__, addr, REG_NAME(reg));') - lns.append(' return;') - lns.append('default:') - lns.append(' break;') - lns.append('}') - print('\n'.join((f'{idt}{ln}' for ln in lns))) - - def make_write(self, c_code: bool = False, indent=4): - if not self._regwens and not self._readonlys: - return - if not c_code: - pprint(self._regwens) - return - lns = [] - lns.append('/* WRITE HELPERS */') - lns.append('switch(reg) {') - idt = ' ' * indent - for regwen, regs in self._regwens.items(): - for reg in regs: - lns.append(f'case R_{reg}:') - lns.append(f' if (s->regs[R_{regwen}] & R_{regwen}_REGWEN_MASK) ' - f'{{') - lns.append(' qemu_log_mask(LOG_GUEST_ERROR, "%s: %s is not ' - 'enabled, %s is protected\\n",') - lns.append(f' __func__, REG_NAME(R_{regwen}), ' - f'REG_NAME(reg));') - lns.append(' return;') - lns.append(' }') - lns.append(' break;') - if self._readonlys: - for reg in self._readonlys: - lns.append(f'case R_{reg}:') - lns.append(' qemu_log_mask(LOG_GUEST_ERROR, "%s: R/O register ' - '0x%03" HWADDR_PRIx " (%s)\\n",') - lns.append(' __func__, addr, REG_NAME(reg));') - lns.append(' return;') - lns.append('default:') - lns.append(' break;') - lns.append('}') - print('\n'.join((f'{idt}{ln}' for ln in lns))) - - -def main(): - """Main routine""" - debug = True - try: - desc = modules[__name__].__doc__.split('.', 1)[0].strip() - argparser = ArgumentParser(description=f'{desc}.') - argparser.add_argument('-c', '--config', metavar='JSON', - type=FileType('rt', encoding='utf-8'), - help='path to configuration file') - argparser.add_argument('-v', '--verbose', action='count', - help='increase verbosity') - argparser.add_argument('-d', '--debug', action='store_true', - help='enable debug mode') - - args = argparser.parse_args() - debug = args.debug - - configure_loggers('otp', args.verbose, name_width=12, lineno=True) - - otp = OtpHelper() - otp.load(args.config) - otp.make_read(True) - otp.make_write(True) - sysexit(0) - # pylint: disable=broad-except - except Exception as exc: - print(f'{linesep}Error: {exc}', file=stderr) - if debug: - print(format_exc(chain=False), file=stderr) - sysexit(1) - except KeyboardInterrupt: - sysexit(2) - - -if __name__ == '__main__': - main() diff --git a/scripts/opentitan/otrun.sh b/scripts/opentitan/otrun.sh deleted file mode 100755 index ba30cab896f82..0000000000000 --- a/scripts/opentitan/otrun.sh +++ /dev/null @@ -1,245 +0,0 @@ -#!/bin/sh - -# Copyright (c) 2023-2024 Rivos, Inc. -# SPDX-License-Identifier: Apache2 - -# Die with an error message -die() { - echo >&2 "$*" - exit 1 -} - -# Show usage information -usage() { - NAME=$(basename $0) -cat < [-- [QEMU_options ...]] - - Execute an OpenTitan boot up sequence with TEST_ROM and a Test application - - -h Print this help message - -b Build QEMU - -f Use a flat tree (files are stored in the same dir) - -o Log OTBN execution traces into otbn.log - -S Redirect guest stdout to stdout.log - -t second Maximum time to execute, after which QEMU instance is killed - -T trace QEMU trace file, output traces are stored in qemu.log - -v Verbose mode, show executed commands - -- All following options are forwarded to QEMU - - ot_dir OT top directory or flat dir with packaged file (see -F) - ot_test OT test application to execute -EOT -} - -OT_DIR="" -OT_TEST="" -BUILD_QEMU=0 -FLAT_TREE=0 -OTBN_LOG=0 -VERBOSE=0 -TIMEOUT=0 -GUEST_STDOUT="" -TRACE="" -LOGFILE="qemu.log" -SUFFIX="prog_fpga_cw310.rsa_fake_test_key_0.signed.bin" -BZLSFX="prog_fpga_cw310_bin_signed_rsa_fake_test_key_0" - -# Parse options -while [ $# -gt 0 ]; do - case "$1" in - -h) - usage - exit 0 - ;; - -b) - BUILD_QEMU=1 - ;; - -f) - FLAT_TREE=1 - ;; - -o) - OTBN_LOG=1 - ;; - -S) - GUEST_STDOUT="stdout.log" - ;; - -T) - shift - TRACE="$1" - ;; - -t) - shift - TIMEOUT="$1" - ;; - -v) - VERBOSE=1 - ;; - --) - shift - break - ;; - -*) - die "Unknown option $1" - ;; - *) - if [ -z "${OT_DIR}" ]; then - OT_DIR="$1" - [ -d "${OT_DIR}" ] || die "Invalid OT directory: ${OT_DIR}" - elif [ -z "${OT_TEST}" ]; then - OT_TEST="$1" - else - die "Unknown argument: $1" - fi - ;; - esac - shift -done - -if [ -z "${OT_DIR}" ]; then - usage - exit 0 -fi - -if [ -n "${TIMEOUT}" ]; then - QEMU_TIMEOUT="timeout --foreground ${TIMEOUT}" -fi - -if [ -n "${TRACE}" ]; then - [ -r ${TRACE} ] || die "No such QEMU trace file ${TRACE}" - TRACEOPTS="-D ${LOGFILE} -trace events=${TRACE}" -fi - -if [ -z "${GUEST_STDOUT}" ]; then - QEMU_SERIAL0="-serial mon:stdio" -else - rm -f ${GUEST_STDOUT} - QEMU_SERIAL0="-chardev file,id=gstdout,path=${GUEST_STDOUT} -serial chardev:gstdout" -fi - -SCRIPT_DIR="$(dirname $0)" -QEMU_DIR="$(realpath ${SCRIPT_DIR}/../..)" -QEMU_BUILD_DIR="${QEMU_DIR}/build" - -if [ ${FLAT_TREE} -gt 0 ]; then - # flat tree mode expects all OT artifacts to be packaged in a single directory - # with no subtree. This mode is useful when OT is built on a machine and - # run on another one (if for some reason bazel fails, which never happens...) - if [ ! -f "${OT_DIR}/img_rma.24.vmem" ]; then - echo >&2 "Invalid ot_pack_dir ${OT_DIR}" - exit 1 - fi - OT_OTP_VMEM="${OT_DIR}/img_rma.24.vmem" - OT_TEST_ROM_BIN="${OT_DIR}/test_rom_fpga_cw310.bin" - OT_TEST_ROM_ELF="${OT_TEST_ROM_BIN%.*}.elf" - OT_TEST_BIN="${OT_DIR}/${OT_TEST}_${SUFFIX}" - [ -s "${OT_TEST_BIN}" ] || die "No such app: ${OT_TEST_BIN}" -else - [ -x "${OT_DIR}/bazelisk.sh" ] || \ - die "${OT_DIR} is not a top-level OpenTitan directory" - GIT="$(which git 2>/dev/null)" - if [ -n "${GIT}" ]; then - if [ -f "${QEMU_DIR}/hw/opentitan/ot_ref.log" ]; then - . "${QEMU_DIR}/hw/opentitan/ot_ref.log" - if [ -n "${GIT_COMMIT}" ]; then - OT_GIT_COMMIT="$(cd ${OT_DIR} && ${GIT} rev-parse HEAD)" - if [ "${OT_GIT_COMMIT}" != "${GIT_COMMIT}" ]; then - echo >&2 "Warning: OpenTitan repo differs from QEMU supported version" - fi - fi - fi - fi - # default mode, use bazel to retrieve the path of artifacts - # Bazel setup is out of scope of this script, please refer to OpenTitan - # official installation instructions first - # Do not forget to run this script from your OT venv if you use one. - OT_OTP_VMEM=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery //hw/ip/otp_ctrl/data:img_rma) || \ - die "Bazel in trouble" - OT_OTP_VMEM="$(realpath ${OT_DIR}/${OT_OTP_VMEM})" - [ -f "${OT_OTP_VMEM}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build //hw/ip/otp_ctrl/data:img_rma) || \ - die "Cannot build $(basename ${OT_OTP_VMEM})" - OT_TEST_ROM_ELF=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery --config riscv32 \ - //sw/device/lib/testing/test_rom:test_rom_fpga_cw310.elf) || \ - die "Bazel in trouble" - OT_TEST_ROM_ELF="$(realpath ${OT_DIR}/${OT_TEST_ROM_ELF})" - [ -f "${OT_TEST_ROM_ELF}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build --config riscv32 \ - //sw/device/lib/testing/test_rom:test_rom_fpga_cw310.elf) || \ - die "Cannot build $(basename ${OT_TEST_ROM_BIN})" - OT_TEST_BIN=$(cd "${OT_DIR}" && \ - ./bazelisk.sh outquery \ - //sw/device/tests:${OT_TEST}_${BZLSFX} ) || \ - die "No such test application ${OT_TEST}" - OT_TEST_BIN="$(realpath ${OT_DIR}/${OT_TEST_BIN})" - [ -f "${OT_TEST_BIN}" ] || \ - (cd "${OT_DIR}" && ./bazelisk.sh build //sw/device/tests:${OT_TEST}_${BZLSFX}) || \ - die "Cannot build $(basename ${OT_TEST_BIN})" -fi - -# sanity checks -[ -f "${OT_OTP_VMEM}" ] || die "Unable to find OTP image" -[ -f "${OT_TEST_ROM_BIN}" ] || die "Unable to find ROM binary" -[ -f "${OT_TEST_BIN}" ] || die "Unable to find test application binary" - -if [ ${BUILD_QEMU} -gt 0 ]; then - if [ ! -d ${QEMU_BUILD_DIR} ]; then - mkdir ${QEMU_BUILD_DIR} || die "Cannot create QEMU build directory" - fi - if [ ! -f "${QEMU_BUILD_DIR}/build.ninja" ]; then - if [ "$(uname -s)" = "Darwin" ]; then - # Cocoa deprecated APIs not yet fixed... - QEMU_BUILD_OPT="--extra-cflags=-Wno-deprecated-declarations" - else - QEMU_BUILD_OPT="--without-default-features --enable-tcg --enable-trace-backends=log" - fi - # note: Meson does not seem to cope well with symlinks - (cd ${QEMU_BUILD_DIR} && - ${QEMU_DIR}/configure --target-list=riscv32-softmmu \ - ${QEMU_BUILD_OPT} --enable-debug) || \ - die "Cannot configure QEMU" - fi - (cd ${QEMU_BUILD_DIR} && ninja) || die "Cannot build QEMU" -fi - -[ -x "${QEMU_BUILD_DIR}/qemu-system-riscv32" ] || die "QEMU has not been build yet" - -# QEMU loads the ROM image using the ELF information to locate and execute the -# ROM. This is useful to get debugging symbols -[ -f "${OT_TEST_ROM_ELF}" ] || die "Unable to find ROM ELF file" -QEMU_GUEST_OPT="-object ot-rom_img,id=rom,file=${OT_TEST_ROM_ELF}" - -if [ ${OTBN_LOG} -gt 0 ]; then - QEMU_GUEST_OPT="${QEMU_GUEST_OPT} -global ot-otbn.logfile=otbn.log" -fi - -echo "Use [Ctrl-A] + x to quit QEMU" - -if [ ${VERBOSE} -gt 0 ];then - set -x -fi - -${SCRIPT_DIR}/otptool.py -v -m "${OT_OTP_VMEM}" -r otp.raw || \ - die "Cannot generate OTP image" - -# note: it is recommended to place the original ELF file from which the binary -# files have been generated from. If flashgen.py locates the matching ELF file, -# the ROM_EXT and BL0 symbols can be automatically loaded by QEMU, which helps -# debugging -${SCRIPT_DIR}/flashgen.py -v -D -x "${OT_TEST_BIN}" flash.raw || \ - die "Cannot generate flash image" - -${QEMU_TIMEOUT} ${QEMU_BUILD_DIR}/qemu-system-riscv32 \ - -M ot-earlgrey -display none ${QEMU_SERIAL0} ${QEMU_GUEST_OPT} \ - -drive if=pflash,file=otp.raw,format=raw \ - -drive if=mtd,bus=1,file=flash.raw,format=raw ${TRACEOPTS} $* -QRES=$? -if [ -n "${GUEST_STDOUT}" ]; then - cat ${GUEST_STDOUT} -fi -if [ ${QRES} -eq 124 ]; then - echo >&2 "Timeout reached" -fi -exit ${QRES} diff --git a/scripts/opentitan/ottests.sh b/scripts/opentitan/ottests.sh deleted file mode 100755 index 28d1128d706cd..0000000000000 --- a/scripts/opentitan/ottests.sh +++ /dev/null @@ -1,166 +0,0 @@ -#!/bin/sh - -# Copyright (c) 2023-2024 Rivos, Inc. -# SPDX-License-Identifier: Apache2 - -# Die with an error message -die() { - echo >&2 "$*" - exit 1 -} - -# Show usage information -usage() { - NAME=$(basename $0) -cat < [-- [QEMU_options ...]] - - Execute OpenTitan test applications and reports results - - -h Print this help message - -- All following options are forwarded to QEMU - - ot_dir OT top directory - pattern Regular expression pattern to select the tests to execute -EOT -} - -# note: this script use recent CLI tools such as 'fd' and 'rg' -# it could easily be adapted to use 'find' and 'grep' -which rg >/dev/null || die "rg not found" -which fd >/dev/null || die "fd not found" - -OT_SCRIPTS=$(dirname $0) -OT_DIR="" -TPATTERN="" -OTBN_TIMEOUT=10 -DEFAULT_TIMEOUT=2 - -# Parse options -while [ $# -gt 0 ]; do - case "$1" in - -h) - usage - exit 0 - ;; - --) - shift - break - ;; - -*) - die "Unknown option $1" - ;; - *) - if [ -z "${OT_DIR}" ]; then - OT_DIR="$1" - [ -d "${OT_DIR}" ] || die "Invalid OT directory: ${OT_DIR}" - elif [ -z "${TPATTERN}" ]; then - TPATTERN="$1" - else - die "Unknown argument: $1" - fi - ;; - esac - shift -done - -if [ -z "${OT_DIR}" ]; then - usage - exit 0 -fi - -success=0 -fail=0 -fatal=0 -timeout=0 -total=0 - -if [ ! -s spi.raw ]; then - # assume that if the flash.raw backend file already exist, it has been - # properly generated (otherwise, remove the file and restart this script) - (cd build && ninja qemu-img all) - ./build/qemu-img create -f raw spi.raw 16M -fi - -QEMU_LOG="build/qemu.log" - -echo "Test name,Result" > qemu_ottest_res.csv -#for tfile in $(fd -e elf . ${OT_DIR} | rg "${TPATTERN}"); do -for tfile in $(fd -e signed.bin . ${OT_DIR} | rg "${TPATTERN}"); do - echo "" - test_name=$(basename ${tfile%%.*}) - echo "${test_name}" - test_radix=$(echo ${test_name} | sed -E 's/^(.*)_prog_.*$/\1/') || \ - die "Cannot extract test radix" - echo "${test_radix}" | rg -q '^manuf' - if [ $? -eq 0 ]; then - echo "Skipping ${test_radix}" - fi - rm -f ${QEMU_LOG} - echo "${test_radix}" | rg -q otbn - if [ $? -eq 0 ]; then - # OTBN simulator is slow, so timeout should be increased to avoid - # kill the test while it has not completed - but not stalled - TIMEOUT=${OTBN_TIMEOUT} - else - TIMEOUT=${DEFAULT_TIMEOUT} - fi - echo "${test_radix}" | rg -q '_wycheproof' - if [ $? -eq 0 ]; then - # disable vCPU slow down for them, since they are usually not time - # sensitive - ICOUNT=0 - # we really need a inline parser rather than relying on timeout values - # but shell scripts are not a proper solution for this - TIMEOUT=40 - else - # vCPU clock < 15MHz - ICOUNT=6 - echo "${test_radix}" | rg -q 'edn_concurrency' - # some tests may take quite some time - if [ $? -eq 0 ]; then - TIMEOUT=30 - fi - fi - EXTRA_OPTS="" - echo "${test_radix}" | rg -q '^aes_' - if [ $? -eq 0 ]; then - # Some AES tests need better scheduling accuracy - EXTRA_OPTS="-global ot-aes.fast-mode=false" - fi - ${OT_SCRIPTS}/otrun.sh -t ${TIMEOUT} -f ${OT_DIR} ${test_radix} \ - -- -d unimp,guest_errors -icount ${ICOUNT} ${EXTRA_OPTS} > ${QEMU_LOG} - TRES=$? - RESULT=$(tail -1 ${QEMU_LOG}) - total=$(( total + 1 )) - echo ${RESULT} | rg -q 'PASS!' - if [ $? -eq 0 ]; then - echo "--- Ok" - success=$(( success + 1 )) - echo "${test_radix},Ok" >> qemu_ottest_res.csv - continue - fi - cat ${QEMU_LOG} - rm -f ${QEMU_LOG} - echo ${RESULT} | rg -q 'FAIL!' - if [ $? -eq 0 ]; then - echo "--- Error" - fail=$(( fail + 1 )) - echo "${test_radix},Error" >> qemu_ottest_res.csv - continue - fi - if [ ${TRES} -eq 124 ]; then - echo "--- Timeout" - timeout=$(( timeout + 1 )) - echo "${test_radix},Timeout" >> qemu_ottest_res.csv - else - echo "--- Crash" - fatal=$(( fatal + 1 )) - echo "${test_radix},Crash" >> qemu_ottest_res.csv - fi -done - -rm -f ${QEMU_LOG} -echo "TOTAL $total, PASS $success, FAIL $fail, TIMEOUT $timeout, CRASH $fatal" -echo "" -cat qemu_ottest_res.csv