diff --git a/disas/meson.build b/disas/meson.build index 815523ab85e0..96eb6f73f61f 100644 --- a/disas/meson.build +++ b/disas/meson.build @@ -9,7 +9,8 @@ common_ss.add(when: 'CONFIG_NIOS2_DIS', if_true: files('nios2.c')) common_ss.add(when: 'CONFIG_RISCV_DIS', if_true: files( 'riscv.c', 'riscv-xthead.c', - 'riscv-xventana.c' + 'riscv-xventana.c', + 'riscv-zbr.c' )) common_ss.add(when: 'CONFIG_SH4_DIS', if_true: files('sh4.c')) common_ss.add(when: 'CONFIG_SPARC_DIS', if_true: files('sparc.c')) diff --git a/disas/riscv-zbr.c b/disas/riscv-zbr.c new file mode 100644 index 000000000000..79b0db34dd48 --- /dev/null +++ b/disas/riscv-zbr.c @@ -0,0 +1,76 @@ +/* + * QEMU RISC-V Disassembler for Zbr + * + * Copyright (c) 2023 Rivos Inc + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "disas/riscv.h" +#include "disas/riscv-zbr.h" + +typedef enum { + /* 0 is reserved for rv_op_illegal. */ + rv_op_crc32_b = 1, + rv_op_crc32_h = 2, + rv_op_crc32_w = 3, + rv_op_crc32_d = 4, + rv_op_crc32c_b = 5, + rv_op_crc32c_h = 6, + rv_op_crc32c_w = 7, + rv_op_crc32c_d = 8, +} rv_zbr_op; + +const rv_opcode_data rv_zbr_opcode_data[] = { + { "illegal", rv_codec_illegal, rv_fmt_none, NULL, 0, 0, 0 }, + { "crc32.b", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32.h", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32.w", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32.d", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.b", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.h", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.w", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.d", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, +}; + +void decode_zbr(rv_decode *dec, rv_isa isa) +{ + rv_inst inst = dec->inst; + rv_opcode op = rv_op_illegal; + + switch ((inst >> 0) & 0b1111111) { + case 0b0010011: + switch ((inst >> 12) & 0b111) { + case 0b001: + switch ((inst >> 20 & 0b111111111111)) { + case 0b011000010000: + op = rv_op_crc32_b; + break; + case 0b011000010001: + op = rv_op_crc32_h; + break; + case 0b011000010010: + op = rv_op_crc32_w; + break; + case 0b011000010011: + op = rv_op_crc32_d; + break; + case 0b011000011000: + op = rv_op_crc32c_b; + break; + case 0b011000011001: + op = rv_op_crc32c_h; + break; + case 0b011000011010: + op = rv_op_crc32c_w; + break; + case 0b011000011011: + op = rv_op_crc32c_d; + break; + } + break; + } + break; + } + dec->op = op; +} diff --git a/disas/riscv-zbr.h b/disas/riscv-zbr.h new file mode 100644 index 000000000000..366040ed53c2 --- /dev/null +++ b/disas/riscv-zbr.h @@ -0,0 +1,18 @@ +/* + * QEMU RISC-V Disassembler for Zbr + * + * Copyright (c) 2023 Rivos Inc + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DISAS_RISCV_ZBR_H +#define DISAS_RISCV_ZBR_H + +#include "disas/riscv.h" + +extern const rv_opcode_data rv_zbr_opcode_data[]; + +void decode_zbr(rv_decode *, rv_isa); + +#endif /* DISAS_RISCV_ZBR_H */ diff --git a/disas/riscv.c b/disas/riscv.c index 769d1f1cc722..8ac3ed966d51 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -27,6 +27,9 @@ #include "disas/riscv-xthead.h" #include "disas/riscv-xventana.h" +/* Extensions that are not yet upstream */ +#include "disas/riscv-zbr.h" + typedef enum { /* 0 is reserved for rv_op_illegal. */ rv_op_lui = 1, @@ -862,14 +865,6 @@ typedef enum { rv_op_fltq_q = 831, rv_op_fleq_h = 832, rv_op_fltq_h = 833, - rv_op_crc32_b = 834, - rv_op_crc32_h = 835, - rv_op_crc32_w = 836, - rv_op_crc32_d = 837, - rv_op_crc32c_b = 838, - rv_op_crc32c_h = 839, - rv_op_crc32c_w = 840, - rv_op_crc32c_d = 841, } rv_op; /* register names */ @@ -2016,14 +2011,6 @@ const rv_opcode_data rvi_opcode_data[] = { { "fltq.q", rv_codec_r, rv_fmt_rd_frs1_frs2, NULL, 0, 0, 0 }, { "fleq.h", rv_codec_r, rv_fmt_rd_frs1_frs2, NULL, 0, 0, 0 }, { "fltq.h", rv_codec_r, rv_fmt_rd_frs1_frs2, NULL, 0, 0, 0 }, - { "crc32.b", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32.h", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32.w", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32.d", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32c.b", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32c.h", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32c.w", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, - { "crc32c.d", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 } }; /* CSR names */ @@ -2620,12 +2607,6 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) /* 0b0000011 */ case 0b0000100: op = rv_op_sext_b; break; case 0b0000101: op = rv_op_sext_h; break; - case 0b0010000: op = rv_op_crc32_b; break; - case 0b0010001: op = rv_op_crc32_h; break; - case 0b0010010: op = rv_op_crc32_w; break; - case 0b0011000: op = rv_op_crc32c_b; break; - case 0b0011001: op = rv_op_crc32c_h; break; - case 0b0011010: op = rv_op_crc32c_w; break; } break; } @@ -5004,6 +4985,9 @@ disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_inst inst, { has_xtheadmempair_p, xthead_opcode_data, decode_xtheadmempair }, { has_xtheadsync_p, xthead_opcode_data, decode_xtheadsync }, { has_XVentanaCondOps_p, ventana_opcode_data, decode_xventanacondops }, + + /* Instructions that are not yet upstream */ + { has_zbr_p, rv_zbr_opcode_data, decode_zbr }, }; for (size_t i = 0; i < ARRAY_SIZE(decoders); i++) { diff --git a/docs/opentitan/devproxy.md b/docs/opentitan/devproxy.md index ed1f396d40e0..bf21057e09c3 100644 --- a/docs/opentitan/devproxy.md +++ b/docs/opentitan/devproxy.md @@ -782,7 +782,6 @@ This is the last command, as QEMU should exit upon receiving this request. Route one or more device output interrupt to the proxy (vs. the internal PLIC) -* `Role` is the initiator role to use to access the device * `Device` is the device to access (see [Enumerate](#enumerate-devices)) * `Interrupt mask` define which interrupt should be routed (1 bit per interrupt) @@ -819,7 +818,6 @@ Route one or more device output interrupt to the proxy (vs. the internal PLIC) Revert any previous interception, reconnecting selected IRQ to their original destination device. -* `Role` is the initiator role to use to access the device * `Device` is the device to access (see [Enumerate](#enumerate-devices)) * `Interrupt mask` define which interrupt should be released (1 bit per interrupt) @@ -851,6 +849,117 @@ destination device. +---------------+---------------+---------------+---------------+ ``` +#### Signal Interrupt [signal-interrupt] + +Set or Reset an input interrupt line. + +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `GID` the identifier of the IRQ group. + * The group identifier can be retrieved using the [Enumerate Device Interrupt](#enumerate-irq) + API. +* `Interrupt line` the number of the interrupt line to signal within the group. The interrupt line + should range between 0 and the input IRQ count for this group. +* `Level` the new interrupt line level. Usually `1` to assert/set (1) or `0` to deassert/release, + even if any 32-bit value is accepted. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'IS' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| GID | Device | - | ++---------------+---------------+---------------+---------------+ +| Interrupt line | - | ++---------------+---------------+---------------+---------------+ +| Level | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'is' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +#### Enumerate Device Interrupt [enumerate-irq] + +Enumerate can be called by the Application to retrieve the list of interrupt +group of a supported device. The group position in the response can be further +use with the [Signal Interrupt API](#signal-interrupt) to set the level of +each individual IRQ line. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'IE' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| - | Device | - | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ie' | 0..20N | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| IRQ input count | IRQ output count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| IRQ input count | IRQ output count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| IRQ input count | IRQ output count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +``` +Reponse contains 0 up to N interrupt groups, each group is described with a 20-byte entry, where: + +* `IRQ input count` is the count of the input interrupts for this group, +* `IRQ output count` is the count of the output interrupts for this group, +* `Identifier` is an arbitrary 16-character string that describes this group. + +The count of address spaces can be retrieved from the `LENGTH` field. + #### Intercept arbitrary MMIO region It is possible to get intercept access to a memory region. Any intercepted region cannot diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index 55c17e116bbf..8ef44b853515 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -5,7 +5,7 @@ ## Usage ````text -usage: pyot.py [-h] [-c JSON] [-w CSV] [-k SECONDS] [-v] [-d] [-q QEMU] +usage: pyot.py [-h] [-c JSON] [-w CSV] [-R] [-k SECONDS] [-v] [-d] [-q QEMU] [-Q OPTS] [-m MACHINE] [-p DEVICE] [-L LOG_FILE] [-M LOG] [-t TRACE] [-i N] [-r ELF] [-O RAW] [-o VMEM] [-f RAW] [-x file] [-b file] @@ -17,6 +17,7 @@ options: -c JSON, --config JSON path to configuration file -w CSV, --result CSV path to output result file + -R, --summary show a result summary -k SECONDS, --timeout SECONDS exit after the specified seconds (default: 60 secs) -F TEST, --filter TEST @@ -39,6 +40,8 @@ Virtual machine: trace event definition file -i N, --icount N virtual instruction counter with 2^N clock ticks per inst. -s, --singlestep enable "single stepping" QEMU execution mode + -T SECS, --timeout-factor SECS + timeout factor -U, --muxserial enable multiple virtual UARTs to be muxed into same host output channel Files: @@ -64,6 +67,7 @@ This tool may be used in two ways, which can be combined: [Configuration](#Configurationfile) section for details. * `-w` / `--result` specify an output CSV report file where the result of all the QEMU sessions, one per test, are reported. +* `-R` / `--summary` show a execution result summary on exit * `-k` / `--timeout` define the maximal duration of each QEMU session. QEMU is terminated or killed after this delay if the executed test has not completed in time. * `-F` / `--filter` when used, only tests whose filenames match one of the selected filter are @@ -103,6 +107,8 @@ This tool may be used in two ways, which can be combined: matches the expected FPGA-based lowRISC CPU. Note that this option slows down the execution of guest applications. * `-s` / `--singlestep` enable QEMU "single stepping" mode. +* `-T` / `--timeout-factor` apply a multiplier factor to all timeouts. Specified as a real number, + it can be greater to increase timeouts or lower than 1 to decrease timeouts. * `-U` / `--muxserial` enable muxing QEMU VCP. This option is required when several virtual UARTs are routed to the same host output channel. @@ -389,15 +395,20 @@ Sample config for running some non-OpenTitan tests: * `aliases` This section may be used to define string aliases to simplify further definitions. - Note that the resulting aliases are always uppercased. + Note that the resulting aliases are always uppercased. To use an alias, use the `${ALIAS}` syntax. + Environment variables may also be used as aliases. - To use an alias, use the `${ALIAS}` syntax. Environment variables may also be used as aliases. Several special variables are automatically defined: * `${CONFIG}` refers to the path of the configuration file itself, * `${TESTDIR}` refers to the default test path (see `testdir` below). * `${QEMU_SRC_DIR}` refers to the path to the QEMU source directory * `${QEMU_BIN_DIR}` refers to the directory than contains the QEMU executable. + Moreover, the following special variables are defined for each executed test: + * `${UTPATH}` absolute path to the executed OT test + * `${UTDIR}` absolute path to the directory containing the executed OT test + * `${UTFILE}` file name of the executed OT test (without directory specifier) + * `testdir` This section may be used to define the default path where to look for tests to run. @@ -418,6 +429,11 @@ Sample config for running some non-OpenTitan tests: It is possible to exclude some tests from this list with the `exclude` and `exclude_from` sections. + It is possible to define sub-sections as items of the list. Each subsection should be a map, where + the sub-section is only evaluated if an environment variable exists and evaluates to true. This + enables configuring lists based on environment variables, such as running is some specific + contexts such as a CI environment. + * `include_from` This section contains a list of files defining the tests to be run. diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 0be8bebcaebe..1eb55292b254 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -118,6 +118,9 @@ config OT_RSTMGR config OT_SENSOR bool +config OT_SOC_PROXY + bool + config OT_SPI_DEVICE bool diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build index bc2e943ca8e5..f44f9a9d7bd5 100644 --- a/hw/opentitan/meson.build +++ b/hw/opentitan/meson.build @@ -39,6 +39,7 @@ system_ss.add(when: 'CONFIG_OT_RANDOM_SRC', if_true: files('ot_random_src.c')) system_ss.add(when: 'CONFIG_OT_ROM_CTRL', if_true: files('ot_rom_ctrl.c', 'ot_rom_ctrl_img.c')) system_ss.add(when: 'CONFIG_OT_RSTMGR', if_true: files('ot_rstmgr.c')) system_ss.add(when: 'CONFIG_OT_SENSOR', if_true: files('ot_sensor.c')) +system_ss.add(when: 'CONFIG_OT_SOC_PROXY', if_true: files('ot_soc_proxy.c')) system_ss.add(when: 'CONFIG_OT_SPI_DEVICE', if_true: files('ot_spi_device.c')) system_ss.add(when: 'CONFIG_OT_SPI_HOST', if_true: files('ot_spi_host.c')) system_ss.add(when: 'CONFIG_OT_SRAM_CTRL', if_true: files('ot_sram_ctrl.c')) diff --git a/hw/opentitan/ot_address_space.c b/hw/opentitan/ot_address_space.c index ecd03fb6d25c..a6782f54d542 100644 --- a/hw/opentitan/ot_address_space.c +++ b/hw/opentitan/ot_address_space.c @@ -79,4 +79,4 @@ static void ot_address_space_register_types(void) type_register_static(&ot_address_space_info); } -type_init(ot_address_space_register_types) +type_init(ot_address_space_register_types); diff --git a/hw/opentitan/ot_aes.c b/hw/opentitan/ot_aes.c index e55a226a70c3..e2bf23e3ce96 100644 --- a/hw/opentitan/ot_aes.c +++ b/hw/opentitan/ot_aes.c @@ -1306,4 +1306,4 @@ static void ot_aes_register_types(void) type_register_static(&ot_aes_info); } -type_init(ot_aes_register_types) +type_init(ot_aes_register_types); diff --git a/hw/opentitan/ot_alert_darjeeling.c b/hw/opentitan/ot_alert_darjeeling.c index 4deb2a05e5a9..56575a5e2b3a 100644 --- a/hw/opentitan/ot_alert_darjeeling.c +++ b/hw/opentitan/ot_alert_darjeeling.c @@ -688,4 +688,4 @@ static void ot_alert_dj_register_types(void) type_register_static(&ot_alert_dj_info); } -type_init(ot_alert_dj_register_types) +type_init(ot_alert_dj_register_types); diff --git a/hw/opentitan/ot_alert_earlgrey.c b/hw/opentitan/ot_alert_earlgrey.c index e034ca887cb6..97d5463f9170 100644 --- a/hw/opentitan/ot_alert_earlgrey.c +++ b/hw/opentitan/ot_alert_earlgrey.c @@ -688,4 +688,4 @@ static void ot_alert_eg_register_types(void) type_register_static(&ot_alert_eg_info); } -type_init(ot_alert_eg_register_types) +type_init(ot_alert_eg_register_types); diff --git a/hw/opentitan/ot_aon_timer.c b/hw/opentitan/ot_aon_timer.c index 7f40c332714e..58b1d655cbc9 100644 --- a/hw/opentitan/ot_aon_timer.c +++ b/hw/opentitan/ot_aon_timer.c @@ -531,4 +531,4 @@ static void ot_aon_timer_register_types(void) type_register_static(&ot_aon_timer_info); } -type_init(ot_aon_timer_register_types) +type_init(ot_aon_timer_register_types); diff --git a/hw/opentitan/ot_ast_darjeeling.c b/hw/opentitan/ot_ast_darjeeling.c index 1b81c088264c..72f8769b0264 100644 --- a/hw/opentitan/ot_ast_darjeeling.c +++ b/hw/opentitan/ot_ast_darjeeling.c @@ -460,4 +460,4 @@ static void ot_ast_dj_register_types(void) type_register_static(&ot_ast_dj_info); } -type_init(ot_ast_dj_register_types) +type_init(ot_ast_dj_register_types); diff --git a/hw/opentitan/ot_ast_earlgrey.c b/hw/opentitan/ot_ast_earlgrey.c index 28dece13f888..e6765855c5b8 100644 --- a/hw/opentitan/ot_ast_earlgrey.c +++ b/hw/opentitan/ot_ast_earlgrey.c @@ -385,4 +385,4 @@ static void ot_ast_eg_register_types(void) type_register_static(&ot_ast_eg_info); } -type_init(ot_ast_eg_register_types) +type_init(ot_ast_eg_register_types); diff --git a/hw/opentitan/ot_clkmgr.c b/hw/opentitan/ot_clkmgr.c index e198266aa427..225d86184a30 100644 --- a/hw/opentitan/ot_clkmgr.c +++ b/hw/opentitan/ot_clkmgr.c @@ -618,4 +618,4 @@ static void ot_clkmgr_register_types(void) type_register_static(&ot_clkmgr_info); } -type_init(ot_clkmgr_register_types) +type_init(ot_clkmgr_register_types); diff --git a/hw/opentitan/ot_csrng.c b/hw/opentitan/ot_csrng.c index abe031545eab..1204c2143f68 100644 --- a/hw/opentitan/ot_csrng.c +++ b/hw/opentitan/ot_csrng.c @@ -1877,4 +1877,4 @@ static void ot_csrng_register_types(void) type_register_static(&ot_csrng_info); } -type_init(ot_csrng_register_types) +type_init(ot_csrng_register_types); diff --git a/hw/opentitan/ot_dev_proxy.c b/hw/opentitan/ot_dev_proxy.c index b142619fe06d..c7aeaf656011 100644 --- a/hw/opentitan/ot_dev_proxy.c +++ b/hw/opentitan/ot_dev_proxy.c @@ -39,6 +39,7 @@ #include "hw/irq.h" #include "hw/opentitan/ot_dev_proxy.h" #include "hw/opentitan/ot_mbx.h" +#include "hw/opentitan/ot_soc_proxy.h" #include "hw/opentitan/ot_sram_ctrl.h" #include "hw/qdev-properties-system.h" #include "hw/qdev-properties.h" @@ -308,6 +309,11 @@ static void ot_dev_proxy_enumerate_devices(OtDevProxyState *s) const char *oid = object_property_get_str(item->obj, "id", &error_fatal); memcpy(&entry->desc[4u], oid, 4u); + } else if (object_dynamic_cast(item->obj, TYPE_OT_SOC_PROXY)) { + memcpy(&entry->desc[0u], item->prefix, 4u); + const char *oid = + object_property_get_str(item->obj, "id", &error_fatal); + memcpy(&entry->desc[4u], oid, 4u); } else if (object_dynamic_cast(item->obj, TYPE_OT_SRAM_CTRL)) { memcpy(&entry->desc[0u], item->prefix, 2u); const char *oid = @@ -928,6 +934,118 @@ static void ot_dev_proxy_intercept_interrupts(OtDevProxyState *s, bool enable) 0); } +static void ot_dev_proxy_signal_interrupt(OtDevProxyState *s) +{ + if (s->rx_hdr.length != 3u * sizeof(uint32_t)) { + ot_dev_proxy_reply_error(s, PE_INVALID_COMMAND_LENGTH, NULL); + return; + } + + unsigned devix = (s->rx_buffer[0] >> 16u) & 0x3ffu; + unsigned gid = s->rx_buffer[0u] & 0xffffu; + + if (devix >= s->dev_count) { + ot_dev_proxy_reply_error(s, PE_INVALID_DEVICE_ID, NULL); + return; + } + + OtDevProxyItem *item = &s->items[devix]; + + if (!object_dynamic_cast(item->obj, TYPE_DEVICE)) { + ot_dev_proxy_reply_error(s, PE_UNSUPPORTED_DEVICE, NULL); + return; + } + + DeviceState *dev = DEVICE(item->obj); + + unsigned irq_num = s->rx_buffer[1u] & 0xffffu; + int irq_level = (int)s->rx_buffer[2u]; + + NamedGPIOList *gl = NULL; + NamedGPIOList *ngl; + QLIST_FOREACH(ngl, &dev->gpios, node) { + if (!gid) { + gl = ngl; + break; + } + gid--; + } + + if (!gl) { + ot_dev_proxy_reply_error(s, PE_INVALID_SPECIFIER_ID, "no such group"); + return; + } + + if (irq_num >= gl->num_in) { + ot_dev_proxy_reply_error(s, PE_INVALID_IRQ, "no such irq"); + return; + } + + qemu_irq irq = gl->in[irq_num]; + qemu_set_irq(irq, irq_level); + + ot_dev_proxy_reply_payload(s, PROXY_COMMAND('i', 's'), NULL, 0); +} + +static void ot_dev_proxy_enumerate_interrupts(OtDevProxyState *s) +{ + if (s->rx_hdr.length != 1u * sizeof(uint32_t)) { + ot_dev_proxy_reply_error(s, PE_INVALID_COMMAND_LENGTH, NULL); + return; + } + + unsigned devix = (s->rx_buffer[0] >> 16u) & 0x3ffu; + + if (devix >= s->dev_count) { + ot_dev_proxy_reply_error(s, PE_INVALID_DEVICE_ID, NULL); + return; + } + + OtDevProxyItem *item = &s->items[devix]; + + if (!object_dynamic_cast(item->obj, TYPE_DEVICE)) { + ot_dev_proxy_reply_error(s, PE_UNSUPPORTED_DEVICE, NULL); + return; + } + + DeviceState *dev = DEVICE(item->obj); + + unsigned group_count = 0; + NamedGPIOList *ngl; + QLIST_FOREACH(ngl, &dev->gpios, node) { + group_count++; + } + + struct irq_id { + uint16_t in_count; + uint16_t out_count; + char name[16u]; + }; + static_assert(sizeof(struct irq_id) == 5 * sizeof(uint32_t), + "invalid struct irq_id, need packing"); + + struct irq_id *entries; + + if (group_count) { + entries = g_new0(struct irq_id, group_count); + struct irq_id *irq_id = entries; + QLIST_FOREACH(ngl, &dev->gpios, node) { + irq_id->in_count = ngl->num_in; + irq_id->out_count = ngl->num_out; + if (ngl->name) { + strncpy(irq_id->name, ngl->name, sizeof(irq_id->name)); + } + irq_id++; + } + } else { + entries = NULL; + } + + ot_dev_proxy_reply_payload(s, PROXY_COMMAND('i', 'e'), entries, + group_count * sizeof(struct irq_id)); + g_free(entries); +} + static void ot_dev_proxy_intercept_mmio(OtDevProxyState *s) { if (s->rx_hdr.length != 3u * sizeof(uint32_t)) { @@ -1155,6 +1273,12 @@ static void ot_dev_proxy_dispatch_request(OtDevProxyState *s) case PROXY_COMMAND('I', 'R'): ot_dev_proxy_intercept_interrupts(s, false); break; + case PROXY_COMMAND('I', 'S'): + ot_dev_proxy_signal_interrupt(s); + break; + case PROXY_COMMAND('I', 'E'): + ot_dev_proxy_enumerate_interrupts(s); + break; case PROXY_COMMAND('M', 'I'): ot_dev_proxy_intercept_mmio(s); break; @@ -1313,6 +1437,17 @@ static int ot_dev_proxy_discover_device(Object *child, void *opaque) item->caps.irq_mask = 0; /* no IRQ on sys side */ item->prefix = "MBS/"; g_array_append_val(array, item); + } else if (object_dynamic_cast(child, TYPE_OT_SOC_PROXY)) { + OtDevProxyItem *item = g_new0(OtDevProxyItem, 1); + object_ref(child); + item->obj = child; + SysBusDevice *sysdev = SYS_BUS_DEVICE(child); + g_assert(sysdev->num_mmio == 1u); + item->caps.mr = sysdev->mmio[0u].memory; + item->caps.reg_count = OT_SOC_PROXY_REGS_COUNT; /* per slot */ + item->caps.irq_mask = UINT32_MAX; /* all IRQs can be routed */ + item->prefix = "SOC/"; + g_array_append_val(array, item); } else if (object_dynamic_cast(child, TYPE_OT_SRAM_CTRL)) { OtDevProxyItem *item = g_new0(OtDevProxyItem, 1); object_ref(child); @@ -1550,16 +1685,14 @@ static void ot_dev_proxy_register_types(void) type_register_static(&ot_dev_proxy_info); } -type_init(ot_dev_proxy_register_types) +type_init(ot_dev_proxy_register_types); - /* ------------------------------------------------------------------------ */ - /* OtDevProxyStateWatcher */ - /* ------------------------------------------------------------------------ */ +/* ------------------------------------------------------------------------ */ +/* OtDevProxyStateWatcher */ +/* ------------------------------------------------------------------------ */ - static MemTxResult - ot_dev_proxy_watcher_read_with_attrs(void *opaque, hwaddr addr, - uint64_t *val64, unsigned size, - MemTxAttrs attrs) +static MemTxResult ot_dev_proxy_watcher_read_with_attrs( + void *opaque, hwaddr addr, uint64_t *val64, unsigned size, MemTxAttrs attrs) { OtDevProxyWatcherState *s = opaque; @@ -1667,4 +1800,4 @@ static void ot_dev_proxy_watcher_register_types(void) type_register_static(&ot_dev_proxy_watcher_info); } -type_init(ot_dev_proxy_watcher_register_types) +type_init(ot_dev_proxy_watcher_register_types); diff --git a/hw/opentitan/ot_dma.c b/hw/opentitan/ot_dma.c index 425d25a5aec0..021e34fb2d1d 100644 --- a/hw/opentitan/ot_dma.c +++ b/hw/opentitan/ot_dma.c @@ -1167,4 +1167,4 @@ static void ot_dma_register_types(void) type_register_static(&ot_dma_info); } -type_init(ot_dma_register_types) +type_init(ot_dma_register_types); diff --git a/hw/opentitan/ot_edn.c b/hw/opentitan/ot_edn.c index 3051fc13ef17..9e1fb4c7fc48 100644 --- a/hw/opentitan/ot_edn.c +++ b/hw/opentitan/ot_edn.c @@ -1348,4 +1348,4 @@ static void ot_edn_register_types(void) type_register_static(&ot_edn_info); } -type_init(ot_edn_register_types) +type_init(ot_edn_register_types); diff --git a/hw/opentitan/ot_entropy_src.c b/hw/opentitan/ot_entropy_src.c index cae1082be522..e5bcdafefe87 100644 --- a/hw/opentitan/ot_entropy_src.c +++ b/hw/opentitan/ot_entropy_src.c @@ -1655,4 +1655,4 @@ static void ot_entropy_src_register_types(void) type_register_static(&ot_entropy_src_info); } -type_init(ot_entropy_src_register_types) +type_init(ot_entropy_src_register_types); diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c index fe5e565c40ac..682f296385fb 100644 --- a/hw/opentitan/ot_flash.c +++ b/hw/opentitan/ot_flash.c @@ -1860,4 +1860,4 @@ static void ot_flash_register_types(void) type_register_static(&ot_flash_info); } -type_init(ot_flash_register_types) +type_init(ot_flash_register_types); diff --git a/hw/opentitan/ot_gpio.c b/hw/opentitan/ot_gpio.c index a520fd3cfaec..55c6d07328f3 100644 --- a/hw/opentitan/ot_gpio.c +++ b/hw/opentitan/ot_gpio.c @@ -534,4 +534,4 @@ static void ot_gpio_register_types(void) type_register_static(&ot_gpio_info); } -type_init(ot_gpio_register_types) +type_init(ot_gpio_register_types); diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 3fb37fbd93a5..8aa0c8a7f711 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -664,4 +664,4 @@ static void ot_hmac_register_types(void) type_register_static(&ot_hmac_info); } -type_init(ot_hmac_register_types) +type_init(ot_hmac_register_types); diff --git a/hw/opentitan/ot_ibex_wrapper_darjeeling.c b/hw/opentitan/ot_ibex_wrapper_darjeeling.c index a6c231729c60..7f837633431c 100644 --- a/hw/opentitan/ot_ibex_wrapper_darjeeling.c +++ b/hw/opentitan/ot_ibex_wrapper_darjeeling.c @@ -1467,4 +1467,4 @@ static void ot_ibex_wrapper_dj_register_types(void) type_register_static(&ot_ibex_wrapper_dj_info); } -type_init(ot_ibex_wrapper_dj_register_types) +type_init(ot_ibex_wrapper_dj_register_types); diff --git a/hw/opentitan/ot_ibex_wrapper_earlgrey.c b/hw/opentitan/ot_ibex_wrapper_earlgrey.c index 84bb408d99ab..08fcc6e9ff8a 100644 --- a/hw/opentitan/ot_ibex_wrapper_earlgrey.c +++ b/hw/opentitan/ot_ibex_wrapper_earlgrey.c @@ -917,4 +917,4 @@ static void ot_ibex_wrapper_eg_register_types(void) type_register_static(&ot_ibex_wrapper_eg_info); } -type_init(ot_ibex_wrapper_eg_register_types) +type_init(ot_ibex_wrapper_eg_register_types); diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index a72059b832e2..0fb3bfffb2c1 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -1682,4 +1682,4 @@ static void ot_kmac_register_types(void) type_register_static(&ot_kmac_info); } -type_init(ot_kmac_register_types) +type_init(ot_kmac_register_types); diff --git a/hw/opentitan/ot_lifecycle.c b/hw/opentitan/ot_lifecycle.c index 5998f8c47be8..1bcd0106c94c 100644 --- a/hw/opentitan/ot_lifecycle.c +++ b/hw/opentitan/ot_lifecycle.c @@ -578,4 +578,4 @@ static void ot_lifecycle_register_types(void) type_register_static(&ot_lifecycle_info); } -type_init(ot_lifecycle_register_types) +type_init(ot_lifecycle_register_types); diff --git a/hw/opentitan/ot_mbx.c b/hw/opentitan/ot_mbx.c index 6f795596ccfd..3cab4fcf1b68 100644 --- a/hw/opentitan/ot_mbx.c +++ b/hw/opentitan/ot_mbx.c @@ -800,4 +800,4 @@ static void ot_mbx_register_types(void) type_register_static(&ot_mbx_info); } -type_init(ot_mbx_register_types) +type_init(ot_mbx_register_types); diff --git a/hw/opentitan/ot_otbn.c b/hw/opentitan/ot_otbn.c index e76cb0bc8180..4100c25efd38 100644 --- a/hw/opentitan/ot_otbn.c +++ b/hw/opentitan/ot_otbn.c @@ -714,4 +714,4 @@ static void ot_otbn_register_types(void) type_register_static(&ot_otbn_info); } -type_init(ot_otbn_register_types) +type_init(ot_otbn_register_types); diff --git a/hw/opentitan/ot_otp_darjeeling.c b/hw/opentitan/ot_otp_darjeeling.c index 41ba585ed230..f2df58522737 100644 --- a/hw/opentitan/ot_otp_darjeeling.c +++ b/hw/opentitan/ot_otp_darjeeling.c @@ -2232,4 +2232,4 @@ static void ot_otp_dj_register_types(void) type_register_static(&ot_otp_dj_info); } -type_init(ot_otp_dj_register_types) +type_init(ot_otp_dj_register_types); diff --git a/hw/opentitan/ot_otp_earlgrey.c b/hw/opentitan/ot_otp_earlgrey.c index 3e11e7636076..e2ee2a140a55 100644 --- a/hw/opentitan/ot_otp_earlgrey.c +++ b/hw/opentitan/ot_otp_earlgrey.c @@ -1485,4 +1485,4 @@ static void ot_otp_eg_register_types(void) type_register_static(&ot_otp_eg_info); } -type_init(ot_otp_eg_register_types) +type_init(ot_otp_eg_register_types); diff --git a/hw/opentitan/ot_pinmux.c b/hw/opentitan/ot_pinmux.c index 6191fcb95cbf..00a384ddff7e 100644 --- a/hw/opentitan/ot_pinmux.c +++ b/hw/opentitan/ot_pinmux.c @@ -494,4 +494,4 @@ static void ot_pinmux_register_types(void) type_register_static(&ot_pinmux_info); } -type_init(ot_pinmux_register_types) +type_init(ot_pinmux_register_types); diff --git a/hw/opentitan/ot_pwrmgr.c b/hw/opentitan/ot_pwrmgr.c index eff331561043..4d1f813c7165 100644 --- a/hw/opentitan/ot_pwrmgr.c +++ b/hw/opentitan/ot_pwrmgr.c @@ -598,4 +598,4 @@ static void ot_pwrmgr_register_types(void) type_register_static(&ot_pwrmgr_info); } -type_init(ot_pwrmgr_register_types) +type_init(ot_pwrmgr_register_types); diff --git a/hw/opentitan/ot_random_src.c b/hw/opentitan/ot_random_src.c index 276b767cd39b..c637a86085ac 100644 --- a/hw/opentitan/ot_random_src.c +++ b/hw/opentitan/ot_random_src.c @@ -39,4 +39,4 @@ static void ot_random_src_register_types(void) type_register_static(&ot_random_src_info); } -type_init(ot_random_src_register_types) +type_init(ot_random_src_register_types); diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index cb85ee5d11ab..14e4b8b768f4 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -617,4 +617,4 @@ static void ot_rom_ctrl_register_types(void) type_register_static(&ot_rom_ctrl_info); } -type_init(ot_rom_ctrl_register_types) +type_init(ot_rom_ctrl_register_types); diff --git a/hw/opentitan/ot_rstmgr.c b/hw/opentitan/ot_rstmgr.c index 1cf33bed85df..317efc558952 100644 --- a/hw/opentitan/ot_rstmgr.c +++ b/hw/opentitan/ot_rstmgr.c @@ -517,4 +517,4 @@ static void ot_rstmgr_register_types(void) type_register_static(&ot_rstmgr_info); } -type_init(ot_rstmgr_register_types) +type_init(ot_rstmgr_register_types); diff --git a/hw/opentitan/ot_sensor.c b/hw/opentitan/ot_sensor.c index 1fd5ef4e7379..18a937b005ab 100644 --- a/hw/opentitan/ot_sensor.c +++ b/hw/opentitan/ot_sensor.c @@ -317,4 +317,4 @@ static void ot_sensor_register_types(void) type_register_static(&ot_sensor_info); } -type_init(ot_sensor_register_types) +type_init(ot_sensor_register_types); diff --git a/hw/opentitan/ot_soc_proxy.c b/hw/opentitan/ot_soc_proxy.c new file mode 100644 index 000000000000..968c566f9764 --- /dev/null +++ b/hw/opentitan/ot_soc_proxy.c @@ -0,0 +1,297 @@ +/* + * QEMU OpenTitan SocProxy + * + * Copyright (c) 2023 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMIT_ADDRED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Note that system-side interrupts are not managed by the DOE Mailbox. + * Registers dedicated to system-side interrupt management are only storage + * space that the guest software (called the host side to get more confusing, + * not related to the VM host) should read and act accordingly, using other + * devices to signal the requester that a response is ready to be read. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "exec/memory.h" +#include "hw/hw.h" +#include "hw/irq.h" +#include "hw/opentitan/ot_alert.h" +#include "hw/opentitan/ot_common.h" +#include "hw/opentitan/ot_soc_proxy.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibex_irq.h" +#include "trace.h" + + +/* ------------------------------------------------------------------------ */ +/* Register definitions */ +/* ------------------------------------------------------------------------ */ + +#define PARAM_NUM_EXTERNAL_IRQS 32u +#define PARAM_NUM_ALERTS 29u + +/* clang-format off */ + +REG32(INTR_STATE, 0x0) +REG32(INTR_ENABLE, 0x4u) +REG32(INTR_TEST, 0x8u) +REG32(ALERT_TEST, 0xcu) + +#define ALERT_TEST_FATAL_ALERT_INTG 0 +#define ALERT_TEST_FATAL_ALERT_EXTERNAL_BASE 1u +#define ALERT_TEST_FATAL_ALERT_EXTERNAL_COUNT 24u +#define ALERT_TEST_RECOV_ALERT_EXTERNAL_BASE 25u +#define ALERT_TEST_RECOV_ALERT_EXTERNAL_COUNT 4u + +#define ALERT_TEST_MASK ((1u << PARAM_NUM_ALERTS) - 1u) + +static_assert(1u + ALERT_TEST_FATAL_ALERT_EXTERNAL_COUNT + ALERT_TEST_RECOV_ALERT_EXTERNAL_COUNT + == PARAM_NUM_ALERTS, "Invalid external IRQ configuration"); + +/* clang-format on */ + +#define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) + +#define R_LAST_REG (R_ALERT_TEST) +#define REGS_COUNT (R_LAST_REG + 1u) +#define REGS_SIZE (REGS_COUNT * sizeof(uint32_t)) +#define INTR_COUNT PARAM_NUM_EXTERNAL_IRQS + +static_assert(OT_SOC_PROXY_REGS_COUNT == REGS_COUNT, "Invalid regs"); + +#define REG_NAME(_reg_) \ + ((((_reg_) <= REGS_COUNT) && REG_NAMES[_reg_]) ? REG_NAMES[_reg_] : "?") + +#define REG_NAME_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) + +/* clang-format off */ +static const char *REG_NAMES[REGS_COUNT] = { + REG_NAME_ENTRY(INTR_STATE), + REG_NAME_ENTRY(INTR_ENABLE), + REG_NAME_ENTRY(INTR_TEST), + REG_NAME_ENTRY(ALERT_TEST), +}; +/* clang-format on */ + +#undef REG_NAME_ENTRY + +enum { + ALERT_RECOVERABLE, + ALERT_FATAL, +}; + +typedef struct { +} OtMbxHost; + +struct OtSoCProxyState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + IbexIRQ irqs[INTR_COUNT]; + IbexIRQ alerts[PARAM_NUM_ALERTS]; + uint32_t regs[REGS_COUNT]; + + char *soc_proxy_id; +}; + +static void ot_soc_proxy_update_irqs(OtSoCProxyState *s) +{ + uint32_t levels = s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE]; + + for (unsigned ix = 0; ix < ARRAY_SIZE(s->irqs); ix++) { + int level = (int)(bool)(levels & (1u << ix)); + if (level != ibex_irq_get_level(&s->irqs[ix])) { + trace_ot_soc_proxy_update_irq(s->soc_proxy_id, ix, + ibex_irq_get_level(&s->irqs[ix]), + level); + } + ibex_irq_set(&s->irqs[ix], level); + } +} + +static void ot_soc_proxy_ingress_irq(void *opaque, int n, int level) +{ + OtSoCProxyState *s = opaque; + + g_assert(n < INTR_COUNT); + + trace_ot_soc_proxy_ingress_irq(s->soc_proxy_id, (unsigned)n, (bool)level); + + uint32_t bm = 1u << n; + if (level) { /* RW1S */ + s->regs[R_INTR_STATE] |= bm; + ot_soc_proxy_update_irqs(s); + } +} + +static uint64_t ot_soc_proxy_regs_read(void *opaque, hwaddr addr, unsigned size) +{ + OtSoCProxyState *s = opaque; + (void)size; + uint32_t val32; + + hwaddr reg = R32_OFF(addr); + + switch (reg) { + case R_INTR_STATE: + case R_INTR_ENABLE: + val32 = s->regs[reg]; + break; + case R_INTR_TEST: + case R_ALERT_TEST: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, + addr, REG_NAME(reg)); + val32 = 0; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0; + break; + } + + uint64_t pc = ibex_get_current_pc(); + trace_ot_soc_proxy_io_read_out(s->soc_proxy_id, (unsigned)addr, + REG_NAME(reg), (uint64_t)val32, pc); + + return (uint64_t)val32; +}; + +static void ot_soc_proxy_regs_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned size) +{ + OtSoCProxyState *s = opaque; + (void)size; + uint32_t val32 = (uint32_t)val64; + + hwaddr reg = R32_OFF(addr); + + uint64_t pc = ibex_get_current_pc(); + trace_ot_soc_proxy_io_write(s->soc_proxy_id, (unsigned)addr, REG_NAME(reg), + val64, pc); + + switch (reg) { + case R_INTR_STATE: + s->regs[reg] &= ~val32; /* RW1C */ + ot_soc_proxy_update_irqs(s); + break; + case R_INTR_ENABLE: + s->regs[reg] = val32; + ot_soc_proxy_update_irqs(s); + break; + case R_INTR_TEST: + s->regs[R_INTR_STATE] |= val32; + ot_soc_proxy_update_irqs(s); + break; + case R_ALERT_TEST: + val32 &= ALERT_TEST_MASK; + if (val32) { + for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { + ibex_irq_set(&s->alerts[ix], (int)((val32 >> ix) & 0x1u)); + } + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static Property ot_soc_proxy_properties[] = { + DEFINE_PROP_STRING("id", OtSoCProxyState, soc_proxy_id), + DEFINE_PROP_END_OF_LIST(), +}; + +static const MemoryRegionOps ot_soc_proxy_regs_ops = { + .read = &ot_soc_proxy_regs_read, + .write = &ot_soc_proxy_regs_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4u, + .impl.max_access_size = 4u, +}; + +static void ot_soc_proxy_reset(DeviceState *dev) +{ + OtSoCProxyState *s = OT_SOC_PROXY(dev); + + g_assert(s->soc_proxy_id); + + memset(s->regs, 0, sizeof(s->regs)); + + ot_soc_proxy_update_irqs(s); + for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { + ibex_irq_set(&s->alerts[ix], 0); + } +} + +static void ot_soc_proxy_init(Object *obj) +{ + OtSoCProxyState *s = OT_SOC_PROXY(obj); + + memory_region_init_io(&s->mmio, obj, &ot_soc_proxy_regs_ops, s, + TYPE_OT_SOC_PROXY, REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); + + for (unsigned ix = 0; ix < ARRAY_SIZE(s->irqs); ix++) { + ibex_sysbus_init_irq(obj, &s->irqs[ix]); + } + + for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { + ibex_qdev_init_irq(obj, &s->alerts[ix], OPENTITAN_DEVICE_ALERT); + } + + qdev_init_gpio_in_named_with_opaque(DEVICE(s), &ot_soc_proxy_ingress_irq, s, + NULL, INTR_COUNT); +} + +static void ot_soc_proxy_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + (void)data; + + dc->reset = &ot_soc_proxy_reset; + device_class_set_props(dc, ot_soc_proxy_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo ot_soc_proxy_info = { + .name = TYPE_OT_SOC_PROXY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OtSoCProxyState), + .instance_init = &ot_soc_proxy_init, + .class_init = &ot_soc_proxy_class_init, +}; + +static void ot_soc_proxy_register_types(void) +{ + type_register_static(&ot_soc_proxy_info); +} + +type_init(ot_soc_proxy_register_types); diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 62b050415bf3..953c6f28d15c 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -2702,4 +2702,4 @@ static void ot_spi_device_register_types(void) type_register_static(&ot_spi_device_info); } -type_init(ot_spi_device_register_types) +type_init(ot_spi_device_register_types); diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index d278d4811f3a..060fa27378e9 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -1307,4 +1307,4 @@ static void ot_spi_host_register_types(void) type_register_static(&ot_spi_host_info); } -type_init(ot_spi_host_register_types) +type_init(ot_spi_host_register_types); diff --git a/hw/opentitan/ot_sram_ctrl.c b/hw/opentitan/ot_sram_ctrl.c index 98486b37309e..413ea2e49301 100644 --- a/hw/opentitan/ot_sram_ctrl.c +++ b/hw/opentitan/ot_sram_ctrl.c @@ -292,4 +292,4 @@ static void ot_sram_ctrl_register_types(void) type_register_static(&ot_sram_ctrl_info); } -type_init(ot_sram_ctrl_register_types) +type_init(ot_sram_ctrl_register_types); diff --git a/hw/opentitan/ot_timer.c b/hw/opentitan/ot_timer.c index 578aec9e0cb6..0631eefe97ab 100644 --- a/hw/opentitan/ot_timer.c +++ b/hw/opentitan/ot_timer.c @@ -398,4 +398,4 @@ static void ot_timer_register_types(void) type_register_static(&ot_timer_info); } -type_init(ot_timer_register_types) +type_init(ot_timer_register_types); diff --git a/hw/opentitan/ot_uart.c b/hw/opentitan/ot_uart.c index 24bbb1d9be77..c69c4cbbd2c2 100644 --- a/hw/opentitan/ot_uart.c +++ b/hw/opentitan/ot_uart.c @@ -631,4 +631,4 @@ static void ot_uart_register_types(void) type_register_static(&ot_uart_info); } -type_init(ot_uart_register_types) +type_init(ot_uart_register_types); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 953ab77c4003..bca58a531e9d 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -263,6 +263,13 @@ ot_rstmgr_sw_rst(const char *path, bool reset) "%s: reset:%u" ot_sensor_io_read_out(unsigned int addr, const char * regname, uint64_t val, uint64_t pc) "addr=0x%02x (%s), val=0x%" PRIx64 ", pc=0x%" PRIx64 ot_sensor_io_write(unsigned int addr, const char * regname, uint64_t val, uint64_t pc) "addr=0x%02x (%s), val=0x%" PRIx64 ", pc=0x%" PRIx64 +# ot_soc_proxy.c + +ot_soc_proxy_io_read_out(const char * mid, unsigned int addr, const char * regname, uint64_t val, uint64_t pc) "%s: addr=0x%02x (%s), val=0x%" PRIx64 ", pc=0x%" PRIx64 +ot_soc_proxy_io_write(const char * mid, unsigned int addr, const char * regname, uint64_t val, uint64_t pc) "%s: addr=0x%02x (%s), val=0x%" PRIx64 ", pc=0x%" PRIx64 +ot_soc_proxy_ingress_irq(const char * mid, unsigned n, bool level) "%s: #%u: %u" +ot_soc_proxy_update_irq(const char * mid, unsigned n, int prev, int next) "%s: #%u %d -> %d" + # ot_spi_device.c ot_spi_device_buf_read_out(unsigned int addr, unsigned int size, uint64_t val, uint64_t pc) "addr=0x%02x, sz=%u, val=0x%" PRIx64 ", pc=0x%" PRIx64 diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 91ff26a9945a..f708e1d73aac 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -13,6 +13,7 @@ config OT_DARJEELING bool select IBEX select IBEX_COMMON + select OT_ADDRESS_SPACE select OT_AES select OT_ALERT_DARJEELING select OT_AON_TIMER @@ -20,6 +21,7 @@ config OT_DARJEELING select OT_CLKMGR select OT_CSRNG select OT_DEV_PROXY + select OT_DMA select OT_EDN select OT_ENTROPY_SRC select OT_GPIO @@ -27,12 +29,14 @@ config OT_DARJEELING select OT_IBEX_WRAPPER_DARJEELING select OT_KMAC select OT_LIFECYCLE + select OT_MBX select OT_OTBN select OT_OTP_DARJEELING select OT_PINMUX select OT_PWRMGR select OT_ROM_CTRL select OT_SENSOR + select OT_SOC_PROXY select OT_SPI_DEVICE select OT_SPI_HOST select OT_SRAM_CTRL diff --git a/hw/riscv/ibex_common.c b/hw/riscv/ibex_common.c index 3000eb5a9040..46c612a6b98a 100644 --- a/hw/riscv/ibex_common.c +++ b/hw/riscv/ibex_common.c @@ -23,6 +23,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qemu/log.h" +#include "qapi/error.h" #include "qom/object.h" #include "cpu.h" #include "disas/disas.h" @@ -85,9 +86,7 @@ void ibex_link_remote_devices(DeviceState **devices, const IbexDeviceDef *defs, unsigned count, DeviceState ***remotes) { /* Link devices */ - if (!remotes) { - remotes = &devices; - } + DeviceState ***targets = remotes ?: &devices; for (unsigned idx = 0; idx < count; idx++) { DeviceState *dev = devices[idx]; if (!dev) { @@ -98,18 +97,77 @@ void ibex_link_remote_devices(DeviceState **devices, const IbexDeviceDef *defs, while (link->propname) { unsigned rix = IBEX_DEVLINK_REMOTE(link->index); unsigned dix = IBEX_DEVLINK_DEVICE(link->index); - DeviceState **tdevices = remotes[rix]; + if (rix && !remotes) { + /* + * if no remote devices are specified, only local links + * can be performed, skip any remote definition. + */ + link++; + continue; + } + if (!rix && remotes) { + /* + * if remote devices are specified, only remote links + * should be performed, skip any local definition. + */ + link++; + continue; + } + DeviceState **tdevices = targets[rix]; g_assert(tdevices); DeviceState *target = tdevices[dix]; g_assert(target); object_property_set_link(OBJECT(dev), link->propname, OBJECT(target), &error_fatal); + g_autofree char *plink; + plink = object_property_get_str(OBJECT(dev), link->propname, + &error_fatal); + if (!plink || *plink == '\0') { + /* + * unfortunately, if an object is not parented, it is not + * possible to create a link (its canonical being NULL), but + * the `object_property_set_link` silently fails. Read back + * the property to ensure it has been really set. + */ + error_setg(&error_fatal, "%s: cannot create %s link", + __func__, link->propname); + } link++; } } } } +void ibex_apply_device_props(Object *obj, const IbexDevicePropDef *prop) +{ + if (prop) { + while (prop->propname) { + switch (prop->type) { + case IBEX_PROP_TYPE_BOOL: + object_property_set_bool(obj, prop->propname, prop->b, + &error_fatal); + break; + case IBEX_PROP_TYPE_INT: + object_property_set_int(obj, prop->propname, prop->i, + &error_fatal); + break; + case IBEX_PROP_TYPE_UINT: + object_property_set_uint(obj, prop->propname, prop->u, + &error_fatal); + break; + case IBEX_PROP_TYPE_STR: + object_property_set_str(obj, prop->propname, prop->s, + &error_fatal); + break; + default: + g_assert_not_reached(); + break; + } + prop++; + } + } +} + void ibex_define_device_props(DeviceState **devices, const IbexDeviceDef *defs, unsigned count) { @@ -118,33 +176,7 @@ void ibex_define_device_props(DeviceState **devices, const IbexDeviceDef *defs, if (!dev) { continue; } - const IbexDevicePropDef *prop = defs[idx].prop; - if (prop) { - while (prop->propname) { - switch (prop->type) { - case IBEX_PROP_TYPE_BOOL: - object_property_set_bool(OBJECT(dev), prop->propname, - prop->b, &error_fatal); - break; - case IBEX_PROP_TYPE_INT: - object_property_set_int(OBJECT(dev), prop->propname, - prop->i, &error_fatal); - break; - case IBEX_PROP_TYPE_UINT: - object_property_set_uint(OBJECT(dev), prop->propname, - prop->u, &error_fatal); - break; - case IBEX_PROP_TYPE_STR: - object_property_set_str(OBJECT(dev), prop->propname, - prop->s, &error_fatal); - break; - default: - g_assert_not_reached(); - break; - } - prop++; - } - } + ibex_apply_device_props(OBJECT(dev), defs[idx].prop); } } @@ -517,13 +549,13 @@ void ibex_unimp_configure(DeviceState *dev, const IbexDeviceDef *def, qdev_prop_set_uint64(dev, "size", def->memmap->size); } -void ibex_load_kernel(AddressSpace *as) +uint32_t ibex_load_kernel(AddressSpace *as) { MachineState *ms = MACHINE(qdev_get_machine()); + uint64_t kernel_entry; /* load kernel if provided */ if (ms->kernel_filename) { - uint64_t kernel_entry; if (load_elf_ram_sym(ms->kernel_filename, NULL, NULL, NULL, &kernel_entry, NULL, NULL, NULL, 0, EM_RISCV, 1, 0, as, true, &rust_demangle_fn) <= 0) { @@ -531,15 +563,25 @@ void ibex_load_kernel(AddressSpace *as) exit(EXIT_FAILURE); } + if (((uint32_t)kernel_entry & 0xFFu) != 0x80u) { + qemu_log("%s: invalid kernel entry address 0x%08x\n", __func__, + (uint32_t)kernel_entry); + } + + kernel_entry &= ~0xFFull; CPUState *cpu; /* NOLINTNEXTLINE */ CPU_FOREACH(cpu) { if (!as || cpu->as == as) { - CPURISCVState *env = &RISCV_CPU(cpu)->env; - env->resetvec = (target_ulong)kernel_entry; + RISCV_CPU(cpu)->env.resetvec = kernel_entry | 0x80ull; + RISCV_CPU(cpu)->cfg.mtvec = kernel_entry | 0b1ull; } } + } else { + kernel_entry = UINT64_MAX; } + + return (uint32_t)kernel_entry; } uint64_t ibex_get_current_pc(void) @@ -615,4 +657,4 @@ static void ibex_register_types(void) monitor_register_hmp("ibex", true, &hmp_info_ibex); } -type_init(ibex_register_types) +type_init(ibex_register_types); diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 30ad039df923..b91a42aa0d5e 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -33,12 +33,15 @@ #include "hw/boards.h" #include "hw/intc/sifive_plic.h" #include "hw/misc/unimp.h" +#include "hw/opentitan/ot_address_space.h" #include "hw/opentitan/ot_aes.h" #include "hw/opentitan/ot_alert_darjeeling.h" #include "hw/opentitan/ot_aon_timer.h" #include "hw/opentitan/ot_ast_darjeeling.h" #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_csrng.h" +#include "hw/opentitan/ot_dev_proxy.h" +#include "hw/opentitan/ot_dma.h" #include "hw/opentitan/ot_edn.h" #include "hw/opentitan/ot_entropy_src.h" #include "hw/opentitan/ot_gpio.h" @@ -46,6 +49,7 @@ #include "hw/opentitan/ot_ibex_wrapper_darjeeling.h" #include "hw/opentitan/ot_kmac.h" #include "hw/opentitan/ot_lifecycle.h" +#include "hw/opentitan/ot_mbx.h" #include "hw/opentitan/ot_otbn.h" #include "hw/opentitan/ot_otp_darjeeling.h" #include "hw/opentitan/ot_pinmux.h" @@ -53,6 +57,7 @@ #include "hw/opentitan/ot_rom_ctrl.h" #include "hw/opentitan/ot_rstmgr.h" #include "hw/opentitan/ot_sensor.h" +#include "hw/opentitan/ot_soc_proxy.h" #include "hw/opentitan/ot_spi_device.h" #include "hw/opentitan/ot_spi_host.h" #include "hw/opentitan/ot_sram_ctrl.h" @@ -89,9 +94,13 @@ static void ot_darjeeling_soc_uart_configure( /* Darjeeling AON clock is 62.5 MHz */ #define OT_DARJEELING_AON_CLK_HZ 62500000u +/* CTN address space */ +#define OT_DARJEELING_CTN_REGION_OFFSET 0x40000000u +#define OT_DARJEELING_CTN_REGION_SIZE (1u << 30u) + /* CTN RAM (1MB) */ -#define OT_DARJEELING_CTN_RAM_ADDR 0x41000000u -#define OT_DARJEELING_CTN_RAM_SIZE (1u << 20u) +#define OT_DARJEELING_CTN_RAM_ADDR 0x01000000u +#define OT_DARJEELING_CTN_RAM_SIZE (2u << 20u) enum OtDarjeelingSocDevice { OT_DARJEELING_SOC_DEV_AES, @@ -144,7 +153,7 @@ enum OtDarjeelingSocDevice { enum OtDarjeelingMemoryRegion { OT_DARJEELING_DEFAULT_MEMORY_REGION, - OT_DARJEELING_XPORT_MEMORY_REGION, + OT_DARJEELING_CTN_MEMORY_REGION, }; #define OT_DARJEELING_SOC_GPIO(_irq_, _target_, _num_) \ @@ -169,12 +178,23 @@ enum OtDarjeelingMemoryRegion { } \ } +#define OT_DARJEELING_SOC_DEV_MBX(_ix_, _addr_, _irq_) \ + .type = TYPE_OT_MBX, .instance = (_ix_), \ + .memmap = MEMMAPENTRIES({ (_addr_), 0x100u }), \ + .gpio = \ + IBEXGPIOCONNDEFS(OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(0, PLIC, (_irq_)), \ + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(1, PLIC, \ + (_irq_) + 1u), \ + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(2, PLIC, \ + (_irq_) + 2u)), \ + .prop = IBEXDEVICEPROPDEFS(IBEX_DEV_STRING_PROP("id", stringify(_ix_))) + #define OT_DARJEELING_SOC_CLKMGR_HINT(_num_) \ OT_DARJEELING_SOC_SIGNAL(OPENTITAN_CLOCK_ACTIVE, 0, CLKMGR, \ OPENTITAN_CLKMGR_HINT, _num_) #define OT_DARJEELING_XPORT_MEMORY(_addr_) \ - IBEX_MEMMAP_MAKE_REG((_addr_), OT_DARJEELING_XPORT_MEMORY_REGION) + IBEX_MEMMAP_MAKE_REG((_addr_), OT_DARJEELING_CTN_MEMORY_REGION) /* * MMIO/interrupt mapping as per: @@ -425,110 +445,94 @@ static const IbexDeviceDef ot_darjeeling_soc_devices[] = { ), }, [OT_DARJEELING_SOC_DEV_MBX0] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 0, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000000u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(0, 0x22000000u, 134), }, [OT_DARJEELING_SOC_DEV_MBX1] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 1, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000100u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(1, 0x22000100u, 137), }, [OT_DARJEELING_SOC_DEV_MBX2] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 2, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000200u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(2, 0x22000200u, 140), }, [OT_DARJEELING_SOC_DEV_MBX3] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 3, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000300u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(3, 0x22000300u, 143), }, [OT_DARJEELING_SOC_DEV_MBX4] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 4, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000400u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(4, 0x22000400u, 146), }, [OT_DARJEELING_SOC_DEV_MBX5] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 5, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000500u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(5, 0x22000500u, 149), }, [OT_DARJEELING_SOC_DEV_MBX6] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 6, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000600u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(6, 0x22000600u, 152), }, [OT_DARJEELING_SOC_DEV_MBX_JTAG] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 7, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22000800u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(7, 0x22000800u, 155), }, [OT_DARJEELING_SOC_DEV_DMA] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-dma", - .cfg = &ibex_unimp_configure, + .type = TYPE_OT_DMA, .memmap = MEMMAPENTRIES( { 0x22010000u, 0x1000u } ), + .gpio = IBEXGPIOCONNDEFS( + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 131), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 132), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 133) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("ot_as_name", "ot-dma"), + IBEX_DEV_STRING_PROP("ctn_as_name", "ctn-dma"), + IBEX_DEV_STRING_PROP("id", "0") + ) }, + [OT_DARJEELING_SOC_DEV_SOC_PROXY] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-soc_proxy", - .cfg = &ibex_unimp_configure, + .type = TYPE_OT_SOC_PROXY, .memmap = MEMMAPENTRIES( { 0x22030000u, 0x1000u } ), + .gpio = IBEXGPIOCONNDEFS( + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 83), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 84), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(2, PLIC, 85), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(3, PLIC, 86), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(4, PLIC, 87), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(5, PLIC, 88), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(6, PLIC, 89), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(7, PLIC, 90), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(8, PLIC, 91), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(9, PLIC, 92), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(10, PLIC, 93), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(11, PLIC, 94), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(12, PLIC, 95), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(13, PLIC, 96), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(14, PLIC, 97), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(15, PLIC, 98), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(16, PLIC, 99), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(17, PLIC, 100), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(18, PLIC, 101), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(19, PLIC, 102), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(20, PLIC, 103), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(21, PLIC, 104), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(22, PLIC, 105), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(23, PLIC, 106), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(24, PLIC, 107), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(25, PLIC, 108), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(26, PLIC, 109), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(27, PLIC, 110), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(28, PLIC, 111), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(29, PLIC, 112), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(30, PLIC, 113), + OT_DARJEELING_SOC_GPIO_SYSBUS_IRQ(31, PLIC, 114) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("id", "0") + ), }, [OT_DARJEELING_SOC_DEV_MBX_PCIE0] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 8, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22040000u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(8, 0x22040000u, 158), }, [OT_DARJEELING_SOC_DEV_MBX_PCIE1] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-mbx", - .instance = 9, - .cfg = &ibex_unimp_configure, - .memmap = MEMMAPENTRIES( - { 0x22040100u, 0x40u } - ), + OT_DARJEELING_SOC_DEV_MBX(9, 0x22040100u, 161), }, [OT_DARJEELING_SOC_DEV_PLIC] = { .type = TYPE_SIFIVE_PLIC, @@ -853,6 +857,7 @@ static const uint32_t ot_darjeeling_pmp_addrs[] = { enum OtDarjeelingBoardDevice { OT_DARJEELING_BOARD_DEV_SOC, OT_DARJEELING_BOARD_DEV_FLASH, + OT_DARJEELING_BOARD_DEV_DEV_PROXY, _OT_DARJEELING_BOARD_DEV_COUNT, }; @@ -870,8 +875,6 @@ struct OtDarjeelingSoCState { SysBusDevice parent_obj; DeviceState **devices; - MemoryRegion *sys; /* local memory region */ - MemoryRegion *xport; /* external port */ }; struct OtDarjeelingBoardState { @@ -986,7 +989,7 @@ static void ot_darjeeling_soc_realize(DeviceState *dev, Error **errp) (void)errp; CPUState *cpu = CPU(s->devices[OT_DARJEELING_SOC_DEV_HART]); - cpu->memory = s->sys; + cpu->memory = get_system_memory(); cpu->cpu_index = 0; /* Link, define properties and realize devices, then connect GPIOs */ @@ -994,13 +997,50 @@ static void ot_darjeeling_soc_realize(DeviceState *dev, Error **errp) ot_darjeeling_soc_devices, ARRAY_SIZE(ot_darjeeling_soc_devices)); + Object *oas; + + oas = object_property_get_link(OBJECT(s)->parent, "ctn-as", errp); + g_assert(oas); + AddressSpace *ctn_as = ot_address_space_get(OT_ADDRESS_SPACE(oas)); + MemoryRegion *mrs[IBEX_MEMMAP_REGIDX_COUNT] = { - [OT_DARJEELING_DEFAULT_MEMORY_REGION] = s->sys, - [OT_DARJEELING_XPORT_MEMORY_REGION] = s->xport, + [OT_DARJEELING_DEFAULT_MEMORY_REGION] = cpu->memory, + [OT_DARJEELING_CTN_MEMORY_REGION] = ctn_as->root, }; ibex_map_devices(s->devices, mrs, ot_darjeeling_soc_devices, ARRAY_SIZE(ot_darjeeling_soc_devices)); + oas = object_new(TYPE_OT_ADDRESS_SPACE); + object_property_add_child(OBJECT(dev), "ot-dma", oas); + ot_address_space_set(OT_ADDRESS_SPACE(oas), cpu->as); + + /* + * create a new root region to map the CTN for the DMA, viewed as an + * elevated region, which means the address range below the elevated CTN + * range is kept empty + */ + MemoryRegion *ctn_dma_mr = MEMORY_REGION(object_new(TYPE_MEMORY_REGION)); + memory_region_init(ctn_dma_mr, OBJECT(dev), "ctn-dma", + OT_DARJEELING_CTN_REGION_OFFSET + + OT_DARJEELING_CTN_REGION_SIZE); + + /* create an AS view for this new root region */ + AddressSpace *ctn_dma_as = g_new0(AddressSpace, 1u); + address_space_init(ctn_dma_as, ctn_dma_mr, "ctn-dma-as"); + + /* create and map an alias to the CTN MR into the elevated region */ + MemoryRegion *ctn_amr = MEMORY_REGION(object_new(TYPE_MEMORY_REGION)); + memory_region_init_alias(ctn_amr, OBJECT(dev), "ctn-dma-alias", + ctn_as->root, 0u, + (uint64_t)OT_DARJEELING_CTN_REGION_SIZE); + memory_region_add_subregion(ctn_dma_mr, + (hwaddr)OT_DARJEELING_CTN_REGION_OFFSET, + ctn_amr); + + oas = object_new(TYPE_OT_ADDRESS_SPACE); + object_property_add_child(OBJECT(dev), "ctn-dma", oas); + ot_address_space_set(OT_ADDRESS_SPACE(oas), ctn_dma_as); + /* load kernel if provided */ ibex_load_kernel(cpu->as); } @@ -1055,16 +1095,36 @@ static void ot_darjeeling_board_realize(DeviceState *dev, Error **errp) DeviceState *soc = board->devices[OT_DARJEELING_BOARD_DEV_SOC]; object_property_add_child(OBJECT(board), "soc", OBJECT(soc)); + /* CTN memory region */ + MemoryRegion *ctn_mr = MEMORY_REGION(object_new(TYPE_MEMORY_REGION)); + memory_region_init(ctn_mr, OBJECT(dev), "ctn-xbar", + (uint64_t)OT_DARJEELING_CTN_REGION_SIZE); + + /* CTN address space */ + AddressSpace *ctn_as = g_new0(AddressSpace, 1); + address_space_init(ctn_as, ctn_mr, "ctn-as"); + Object *oas = object_new(TYPE_OT_ADDRESS_SPACE); + object_property_add_child(OBJECT(dev), ctn_as->name, oas); + ot_address_space_set(OT_ADDRESS_SPACE(oas), ctn_as); + OtDarjeelingSoCState *s = RISCV_OT_DARJEELING_SOC(soc); - s->sys = get_system_memory(); - s->xport = NULL; /* to be filled */ BusState *bus = sysbus_get_default(); qdev_realize_and_unref(DEVICE(soc), bus, &error_fatal); /* CTN RAM */ - MachineState *ms = MACHINE(qdev_get_machine()); - memory_region_add_subregion(s->sys, OT_DARJEELING_CTN_RAM_ADDR, ms->ram); + MemoryRegion *ctn_ram = MEMORY_REGION(object_new(TYPE_MEMORY_REGION)); + memory_region_init_ram_nomigrate(ctn_ram, OBJECT(s), "ctn-ram", + OT_DARJEELING_CTN_RAM_SIZE, errp); + memory_region_add_subregion(ctn_mr, OT_DARJEELING_CTN_RAM_ADDR, ctn_ram); + + /* CTN aliased memory in CPU address space */ + MemoryRegion *ctn_alias_mr = MEMORY_REGION(object_new(TYPE_MEMORY_REGION)); + memory_region_init_alias(ctn_alias_mr, OBJECT(dev), "ctn-alias", ctn_mr, 0u, + (uint64_t)OT_DARJEELING_CTN_REGION_SIZE); + memory_region_add_subregion(get_system_memory(), + (hwaddr)OT_DARJEELING_CTN_REGION_OFFSET, + ctn_alias_mr); DeviceState *spihost = s->devices[OT_DARJEELING_SOC_DEV_SPI_HOST0]; DeviceState *flash = board->devices[OT_DARJEELING_BOARD_DEV_FLASH]; @@ -1081,6 +1141,10 @@ static void ot_darjeeling_board_realize(DeviceState *dev, Error **errp) qemu_irq cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0); qdev_connect_gpio_out_named(spihost, SSI_GPIO_CS, 0, cs); + + DeviceState *devproxy = board->devices[OT_DARJEELING_BOARD_DEV_DEV_PROXY]; + object_property_add_child(OBJECT(board), "devproxy", OBJECT(devproxy)); + qdev_realize_and_unref(devproxy, NULL, errp); } static void ot_darjeeling_board_init(Object *obj) @@ -1091,6 +1155,7 @@ static void ot_darjeeling_board_init(Object *obj) s->devices[OT_DARJEELING_BOARD_DEV_SOC] = qdev_new(TYPE_RISCV_OT_DARJEELING_SOC); s->devices[OT_DARJEELING_BOARD_DEV_FLASH] = qdev_new("is25wp128"); + s->devices[OT_DARJEELING_BOARD_DEV_DEV_PROXY] = qdev_new(TYPE_OT_DEV_PROXY); } static void ot_darjeeling_board_class_init(ObjectClass *oc, void *data) @@ -1166,8 +1231,6 @@ static void ot_darjeeling_machine_class_init(ObjectClass *oc, void *data) mc->init = ot_darjeeling_machine_init; mc->max_cpus = 1u; mc->default_cpus = 1u; - mc->default_ram_id = "ctn-ram"; - mc->default_ram_size = OT_DARJEELING_CTN_RAM_SIZE; } static const TypeInfo ot_darjeeling_machine_type_info = { diff --git a/include/hw/opentitan/ot_random_src.h b/include/hw/opentitan/ot_random_src.h index d15420104157..86da50834558 100644 --- a/include/hw/opentitan/ot_random_src.h +++ b/include/hw/opentitan/ot_random_src.h @@ -30,7 +30,6 @@ #include "qom/object.h" -/* Implemented by devices that can be plugged on Conventional PCI buses */ #define TYPE_OT_RANDOM_SRC_IF "ot-random_src-interface" typedef struct OtRandomSrcIfClass OtRandomSrcIfClass; DECLARE_CLASS_CHECKERS(OtRandomSrcIfClass, OT_RANDOM_SRC_IF, diff --git a/include/hw/opentitan/ot_soc_proxy.h b/include/hw/opentitan/ot_soc_proxy.h new file mode 100644 index 000000000000..55694f5b2af2 --- /dev/null +++ b/include/hw/opentitan/ot_soc_proxy.h @@ -0,0 +1,38 @@ +/* + * QEMU OpenTitan SocProxy + * + * Copyright (c) 2023 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HW_OPENTITAN_OT_SOC_PROXY_H +#define HW_OPENTITAN_OT_SOC_PROXY_H + +#include "qom/object.h" + +#define TYPE_OT_SOC_PROXY "ot-soc_proxy" +OBJECT_DECLARE_SIMPLE_TYPE(OtSoCProxyState, OT_SOC_PROXY) + +#define OT_SOC_PROXY_REGS_COUNT 4u + +#endif /* HW_OPENTITAN_OT_SOC_PROXY_H */ diff --git a/include/hw/riscv/ibex_common.h b/include/hw/riscv/ibex_common.h index ffe77b2a8a2a..35cf2fa4b4a9 100644 --- a/include/hw/riscv/ibex_common.h +++ b/include/hw/riscv/ibex_common.h @@ -385,6 +385,7 @@ DeviceState **ibex_create_devices(const IbexDeviceDef *defs, unsigned count, ibex_link_remote_devices(_devs_, _defs_, _cnt_, NULL) void ibex_link_remote_devices(DeviceState **devices, const IbexDeviceDef *defs, unsigned count, DeviceState ***remotes); +void ibex_apply_device_props(Object *obj, const IbexDevicePropDef *prop); void ibex_define_device_props(DeviceState **devices, const IbexDeviceDef *defs, unsigned count); void ibex_realize_system_devices(DeviceState **devices, @@ -426,8 +427,10 @@ void ibex_unimp_configure(DeviceState *dev, const IbexDeviceDef *def, * Load an ELF application into a CPU address space. * @as the address space to load the application into, maybe NULL to use the * default address space + * + * @return the Ibex-constrained ELF entry point (or -1 on error) */ -void ibex_load_kernel(AddressSpace *as); +uint32_t ibex_load_kernel(AddressSpace *as); /** * Helper for device debugging: report the current guest PC, if any. diff --git a/scripts/opentitan/devproxy.py b/scripts/opentitan/devproxy.py index d0aac99df077..47609efbe04e 100644 --- a/scripts/opentitan/devproxy.py +++ b/scripts/opentitan/devproxy.py @@ -12,7 +12,8 @@ from sys import modules from threading import Event, Thread, get_ident from time import time as now -from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple, Union +from typing import (Any, Callable, Dict, Iterator, List, NamedTuple, Optional, + Tuple, Union) try: from serial import Serial, serial_for_url @@ -207,6 +208,7 @@ def __init__(self, proxy: 'Proxy', name: str, devid: int, addr: int, self._regcount = count self._offset = offset * 4 self._end = offset + count * 4 + self._interrupts: Optional[Dict[str, Tuple[int, int, int]]] = None self._new() @property @@ -343,6 +345,51 @@ def capture_interrupt(self, irq: int, enable: bool): else: self.release_interrupts(mask) + def enumerate_interrupts(self, out: bool) -> Iterator[Tuple[str, int]]: + """Enumerate supported interrupt lines. + + :param out: True to enumerate output IRQ lines, False for input ones + :yield: enumerated tuples that contain the IRQ group name, and the + count of interrupt line in this group. + """ + if self._interrupts is None: + self._enumerate_interrupts() + for name, (_, gin, gout) in self._interrupts.items(): + if not out: + if gin: + yield name, gin + else: + if gout: + yield name, gout + + def signal_interrupt(self, group: str, irq: int, level: int | bool) -> None: + """Set the level of an input interrupt line. + + :param group: the name of the group + :param irq: the IRQ line to signal + :param level: the new level for this IRQ line + + The group name may be retrieved with the #enumerate_interrupts API. + """ + if self._interrupts is None: + self._enumerate_interrupts() + if group not in self._interrupts: + raise ValueError(f'No such interrupt group "{group}"') + grp = self._interrupts[group] + grp_num, irq_count = grp[0:2] + if irq >= irq_count: + raise ValueError(f'No such interrupt {irq} in {group}') + level = int(level) + if level >= (1 << 32): + raise ValueError(f'Invalied interrupt level {level}') + request = spack(' int: if not isinstance(device, int) or not 0 <= device <= 0xfff: @@ -355,6 +402,35 @@ def _new(self): self._log.debug('Device %s @ 0x%08x: %s', str(self), self._addr, self._name) + def _enumerate_interrupts(self) -> None: + # lazy initialization interrupt enumeration is rarely used + request = spack(' bool: """Lock address range (base and limit registers). """ res = self.read_word(self._role, self.REGS['ADDRESS_RANGE_REGWEN']) \ - != self.MB4_TRUE + != self.MB4_TRUE self._log.debug('%d', res) return res @@ -853,6 +929,85 @@ def set_interrupt_data(self, data: int) -> None: self.write_word(self._role, self.REGS['INTR_MSG_DATA'], data) +class SoCProxy(DeviceProxy): + """SoC proxy. + + Specialized DeviceProxy that helps managing an SOC PROXY device. + + :param args: forwarded as is to the parent, see #DevicePRoxy + :param role: optional access role + """ + + DEVICE_ID = 'soc' + + REGS = { + 'INTR_STATE': 0x00, + 'INTR_ENABLE': 0x04, + 'INTR_TEST': 0x08, + 'ALERT_TEST': 0x0c, + } + """Supported registers.""" + + def __init__(self, *args, role: Optional[int] = None): + super().__init__(*args) + self._role = 0xff # ensure it should be defined through set_role + if role is not None: + self.set_role(role) + + def set_role(self, role: int): + """Set the control access role to read/write remote registers.""" + if not isinstance(role, int) or role > 0xf: + raise ValueError('Invalid role') + self._role = role + self._log.debug('%d', role) + + @property + def interrupt_state(self) -> int: + """Report which interrupts are active. + + :return: interrupt state bitfield + """ + return self.read_word(self._role, self.REGS['INTR_STATE']) + + def enable_interrupts(self, intrs: int) -> None: + """Enable interrupts. + + :param intrs: the bitfield of interrupts to enable + """ + self._log.debug('0x%08x', intrs) + self.write_word(self._role, self.REGS['INTR_ENABLE'], intrs) + + @property + def enabled_interrupts(self) -> int: + """Get enabled interrupt channels. + """ + return self.read_word(self._role, self.REGS['INTR_ENABLE']) + + def clear_interrupts(self, intrs: int) -> None: + """Clear interrupts. + + :param intrs: the bitfield of interrupts to clear + """ + self._log.debug('0x%08x', intrs) + self.write_word(self._role, self.REGS['INTR_STATE'], intrs) + + def test_interrupts(self, intrs: int) -> None: + """Test interrupts. + + :param intrs: the bitfield of interrupts to test + """ + self._log.debug('0x%08x', intrs) + self.write_word(self._role, self.REGS['INTR_TEST'], intrs) + + def test_alerts(self, alerts: int) -> None: + """Test alerts + + :param alerts: the bitfield of interrupts to test + """ + self._log.debug('0x%08x', alerts) + self.write_word(self._role, self.REGS['ALERT_TEST'], alerts) + + class MemProxy(DeviceProxy): """Memroy device proxy. @@ -1091,6 +1246,18 @@ def discover_memory_spaces(self) -> None: continue self._mroots[mspc] = MemoryRoot(mrname, address, size) + def enumerate_devices(self) -> Iterator[str]: + """Provide an iterator on discovered devices. + """ + for name in self._devices.keys(): + yield name + + def enumerate_memory_spaces(self) -> Iterator[str]: + """Provide an iterator on discovered memory spaces. + """ + for name in self._mroots.keys(): + yield name + def get_device_by_name(self, name: str) -> Optional[DeviceProxy]: """Retrieve a device proxy from its name.""" return self._devices.get(name.lower()) diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index 366c41628cb4..1f9094668e1e 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -9,7 +9,7 @@ from argparse import ArgumentParser, FileType, Namespace from atexit import register from collections import defaultdict -from csv import writer as csv_writer +from csv import reader as csv_reader, writer as csv_writer from fnmatch import fnmatchcase from glob import glob try: @@ -20,9 +20,10 @@ from json import load as jload from logging import (Formatter, StreamHandler, CRITICAL, DEBUG, INFO, ERROR, WARNING, getLogger) -from os import close, curdir, environ, getcwd, isatty, linesep, sep, unlink -from os.path import (basename, dirname, isabs, isdir, isfile, join as joinpath, - normpath, relpath) +from os import (close, curdir, environ, getcwd, isatty, linesep, pardir, sep, + unlink) +from os.path import (abspath, basename, dirname, isabs, isdir, isfile, + join as joinpath, normpath, relpath) from re import Match, compile as re_compile, sub as re_sub from shutil import rmtree from socket import socket, timeout as LegacyTimeoutError @@ -36,11 +37,12 @@ Tuple) -#pylint: disable=too-many-lines +# pylint: disable=too-many-lines -DEFAULT_MACHINE ='ot-earlgrey' +DEFAULT_MACHINE = 'ot-earlgrey' DEFAULT_DEVICE = 'localhost:8000' DEFAULT_TIMEOUT = 60 # seconds +DEFAULT_TIMEOUT_FACTOR = 1.0 class ExecTime(float): @@ -95,6 +97,48 @@ def format(self, record): return formatter.format(record) +class ResultFormatter: + """Format a result CSV file as a simple result table.""" + + def __init__(self): + self._results = [] + + def load(self, csvpath: str) -> None: + """Load a CSV file (generated with QEMUExecuter) and parse it. + + :param csvpath: the path to the CSV file. + """ + with open(csvpath, 'rt', encoding='utf-8') as cfp: + csv = csv_reader(cfp) + for row in csv: + self._results.append(row) + + def show(self, spacing: bool = False) -> None: + """Print a simple formatted ASCII table with loaded CSV results. + + :param spacing: add an empty line before and after the table + """ + if spacing: + print('') + widths = [max(len(x) for x in col) for col in zip(*self._results)] + self._show_line(widths, '-') + self._show_row(widths, self._results[0]) + self._show_line(widths, '=') + for row in self._results[1:]: + self._show_row(widths, row) + self._show_line(widths, '-') + if spacing: + print('') + + def _show_line(self, widths: List[int], csep: str) -> None: + print(f'+{"+".join([csep * (w+2) for w in widths])}+') + + def _show_row(self, widths: List[int], cols: List[str]) -> None: + line = '|'.join([f' {c:{">" if p else "<"}{w}s} ' + for p, (w, c) in enumerate(zip(widths, cols))]) + print(f'|{line}|') + + class QEMUWrapper: """A small engine to run tests with QEMU. @@ -102,7 +146,7 @@ class QEMUWrapper: Virtual Com Port of QEMU first UART :param debug: whether running in debug mode """ - #pylint: disable=too-few-public-methods + # pylint: disable=too-few-public-methods EXIT_ON = rb'(PASS|FAIL)!\r' """Matching strings to search for in guest output. @@ -121,7 +165,7 @@ class QEMUWrapper: NO_MATCH_RETURN_CODE = 100 """Return code when no matching string is found in guest output.""" - LOG_LEVELS = { 'D': DEBUG, 'I': INFO, 'E': ERROR } + LOG_LEVELS = {'D': DEBUG, 'I': INFO, 'E': ERROR} """OpenTitan log levels.""" def __init__(self, tcpdev: Tuple[str, int], debug: bool): @@ -144,9 +188,9 @@ def run(self, qemu_args: List[str], timeout: int, name: str, :param ctx: execution context, if any :return: a 3-uple of exit code, execution time, and last guest error """ - #pylint: disable=too-many-locals - #pylint: disable=too-many-branches - #pylint: disable=too-many-statements + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements # stdout and stderr belongs to QEMU VM # OT's UART0 is redirected to a TCP stream that can be accessed through @@ -160,12 +204,12 @@ def run(self, qemu_args: List[str], timeout: int, name: str, xstart = None xend = None log = self._log - last_guest_error = '' - #pylint: disable=too-many-nested-blocks + last_error = '' + # pylint: disable=too-many-nested-blocks try: workdir = dirname(qemu_args[0]) - log.info('Executing QEMU as %s', ' '.join(qemu_args)) - #pylint: disable=consider-using-with + log.debug('Executing QEMU as %s', ' '.join(qemu_args)) + # pylint: disable=consider-using-with proc = Popen(qemu_args, bufsize=1, cwd=workdir, stdout=PIPE, stderr=PIPE, encoding='utf-8', errors='ignore', text=True) @@ -205,7 +249,17 @@ def run(self, qemu_args: List[str], timeout: int, name: str, Thread(target=self._qemu_logger, name='qemu_err_logger', args=(proc, log_q, False)).start() if ctx: - ctx.execute('with') + try: + ctx.execute('with') + except OSError as exc: + ret = exc.errno + last_error = exc.strerror + raise + # pylint: disable=broad-except + except Exception as exc: + ret = 126 + last_error = str(exc) + raise xstart = now() abstimeout = float(timeout) + xstart while now() < abstimeout: @@ -213,16 +267,23 @@ def run(self, qemu_args: List[str], timeout: int, name: str, err, qline = log_q.popleft() if err: if qline.find('info: ') > 0: - self._qlog.info(qline) + if qline.startswith('qemu-system-'): + # qemu chardev info is parasiting logs + self._qlog.debug(qline) + else: + self._qlog.info(qline) elif qline.find('warning: ') > 0: self._qlog.warning(qline) else: self._qlog.error(qline) else: self._qlog.info(qline) - if ctx.check_error(): - ret = 126 - raise OSError() + if ctx: + wret = ctx.check_error() + if wret: + ret = wret + last_error = 'Fail to execute worker' + raise OSError(wret, last_error) xret = proc.poll() if xret is not None: if xend is None: @@ -251,7 +312,7 @@ def run(self, qemu_args: List[str], timeout: int, name: str, log.info("Exit sequence detected: '%s' -> %d", exit_word, ret) if ret == 0: - last_guest_error = '' + last_error = '' break sline = line.decode('utf-8', errors='ignore').rstrip() lmo = lre.search(sline) @@ -261,7 +322,7 @@ def run(self, qemu_args: List[str], timeout: int, name: str, err = re_sub(r'^.*:\d+]', '', sline).lstrip() # be sure not to preserve comma as this char is # used as a CSV separator. - last_guest_error = err.strip('"').replace(',', ';') + last_error = err.strip('"').replace(',', ';') else: level = DEBUG # fall back when no prefix is found self._otlog.log(level, sline) @@ -276,7 +337,7 @@ def run(self, qemu_args: List[str], timeout: int, name: str, except (OSError, ValueError) as exc: if ret is None: log.error('Unable to execute QEMU: %s', exc) - ret = proc.resultcode if proc.poll() is not None else 125 + ret = proc.returncode if proc.poll() is not None else 125 finally: if xend is None: xend = now() @@ -304,7 +365,7 @@ def run(self, qemu_args: List[str], timeout: int, name: str, if line: logger(line) xtime = ExecTime(xend-xstart) if xstart and xend else 0.0 - return abs(ret) or 0, xtime, last_guest_error + return abs(ret) or 0, xtime, last_error def _qemu_logger(self, proc: Popen, queue: Deque, err: bool): # worker thread, blocking on VM stdout/stderr @@ -346,7 +407,7 @@ class QEMUFileManager: :param keep_temp: do not automatically discard generated files on exit """ - #pylint: disable=too-few-public-methods + # pylint: disable=too-few-public-methods DEFAULT_OTP_ECC_BITS = 6 @@ -356,6 +417,7 @@ def __init__(self, keep_temp: bool = False): self._in_fly: Set[str] = set() self._otp_files: Dict[str, Tuple[str, int]] = {} self._env: Dict[str, str] = {} + self._transient_vars: Set[str] = set() self._dirs: Dict[str, str] = {} register(self._cleanup) @@ -373,14 +435,14 @@ def set_qemu_src_dir(self, path: str) -> None: :param path: the path to the QEMU source directory """ - self._env['QEMU_SRC_DIR'] = normpath(path) + self._env['QEMU_SRC_DIR'] = abspath(path) def set_qemu_bin_dir(self, path: str) -> None: """Set the QEMU executable directory. :param path: the path to the QEMU binary directory """ - self._env['QEMU_BIN_DIR'] = normpath(path) + self._env['QEMU_BIN_DIR'] = abspath(path) def set_config_dir(self, path: str) -> None: """Assign the configuration directory. @@ -388,7 +450,7 @@ def set_config_dir(self, path: str) -> None: :param path: the directory that contains the input configuration file """ - self._env['CONFIG'] = normpath(path) + self._env['CONFIG'] = abspath(path) def interpolate(self, value: Any) -> str: """Interpolate a ${...} marker with shell substitutions or local @@ -427,6 +489,26 @@ def replace(smo: Match) -> str: self._env[name.upper()] = value self._log.debug('Store %s as %s', name.upper(), value) + def define_transient(self, aliases: Dict[str, Any]) -> None: + """Add short-lived aliases that are all discarded when cleanup_transient + is called. + + :param aliases: a dict of aliases + """ + for name in aliases: + name = name.upper() + # be sure not to make an existing non-transient variable transient + if name not in self._env: + self._transient_vars.add(name) + self.define(aliases) + + def cleanup_transient(self) -> None: + """Remove all transient variables.""" + for name in self._transient_vars: + if name in self._env: + del self._env[name] + self._transient_vars.clear() + def interpolate_dirs(self, value: str, default: str) -> str: """Resolve temporary directories, creating ones whenever required. @@ -477,7 +559,7 @@ def create_flash_image(self, app: Optional[str] = None, :param bootloader: optional path to a bootloader :return: the full path to the temporary flash file """ - #pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel from flashgen import FlashGen gen = FlashGen(FlashGen.CHIP_ROM_EXT_SIZE_MAX if bool(bootloader) else 0, True) @@ -507,7 +589,7 @@ def create_otp_image(self, vmem: str) -> str: :param vmem: path to the VMEM source file :return: the full path to the temporary OTP file """ - #pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel if vmem in self._otp_files: otp_file, ref_count = self._otp_files[vmem] self._log.debug('Use existing %s', basename(otp_file)) @@ -575,7 +657,7 @@ def _configure_logger(self, tool) -> None: def _cleanup(self) -> None: """Remove a generated, temporary flash image file. """ - #pylint: disable=too-many-branches + # pylint: disable=too-many-branches removed: Set[str] = set() for tmpfile in self._in_fly: if not isfile(tmpfile): @@ -691,11 +773,12 @@ def _run(self): try: # leave 1 second for QEMU to cleanly complete... proc.wait(1.0) + self._ret = 0 except TimeoutExpired: # otherwise kill it self._log.error('Force-killing command "%s"', self.command) proc.kill() - self._ret = proc.returncode + self._ret = proc.returncode # retrieve the remaining log messages stdlog = self._log.info if self._ret else self._log.debug for sfp, logger in zip(proc.communicate(timeout=0.1), @@ -728,7 +811,7 @@ class QEMUContext: def __init__(self, test_name: str, qfm: QEMUFileManager, qemu_cmd: List[str], context: Dict[str, List[str]], env: Optional[Dict[str, str]] = None): - #pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments self._clog = getLogger('pyot.ctx') self._test_name = test_name self._qfm = qfm @@ -748,9 +831,9 @@ def execute(self, ctx_name: str, code: int = 0) -> None: :param code: a previous error completion code, if any """ - #pylint: disable=too-many-branches - #pylint: disable=too-many-locals - #pylint: disable=too-many-nested-blocks + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-nested-blocks ctx = self._context.get(ctx_name, None) if ctx_name == 'post' and code: self._clog.info("Discard execution of '%s' commands after failure " @@ -767,52 +850,62 @@ def execute(self, ctx_name: str, code: int = 0) -> None: raise ValueError(f"Cannot execute background command " f"in [{ctx_name}] context for " f"'{self._test_name}'") - cmd = cmd[:-1].rstrip() - self._clog.debug('Execute "%s" in backgrorund for [%s] ' - 'context', cmd, ctx_name) + cmd = normpath(cmd[:-1].rstrip()) + rcmd = relpath(cmd) + if rcmd.startswith(pardir): + rcmd = cmd + self._clog.debug('Execute "%s" in background for [%s] ' + 'context', rcmd, ctx_name) worker = QEMUContextWorker(cmd, env) worker.run() self._workers.append(worker) else: + cmd = normpath(cmd.rstrip()) + rcmd = relpath(cmd) + if rcmd.startswith(pardir): + rcmd = cmd self._clog.debug('Execute "%s" in sync for [%s] context', - cmd, ctx_name) - #pylint: disable=consider-using-with + rcmd, ctx_name) + # pylint: disable=consider-using-with proc = Popen(cmd, bufsize=1, stdout=PIPE, stderr=PIPE, shell=True, env=env, encoding='utf-8', errors='ignore', text=True) + ret = 0 try: outs, errs = proc.communicate(timeout=5) - fail = bool(proc.returncode) + ret = proc.returncode except TimeoutExpired: proc.kill() outs, errs = proc.communicate() - fail = True - for sfp, logger in zip((outs, errs), + ret = proc.returncode + for sfp, logger in zip( + (outs, errs), (self._clog.debug, - self._clog.error if fail else self._clog.info)): + self._clog.error if ret else self._clog.info)): for line in sfp.split('\n'): line = line.strip() if line: logger(line) - if fail: + if ret: self._clog.error("Fail to execute '%s' command for " "'%s'", cmd, self._test_name) - raise ValueError(f"Cannot execute [{ctx_name}] command") + raise OSError(ret, + f'Cannot execute [{ctx_name}] command') if ctx_name == 'post': self._qfm.delete_default_dir(self._test_name) - def check_error(self) -> bool: + def check_error(self) -> int: """Check if any background worker exited in error. - :return: True if any worker has failed + :return: a non-zero value on error """ for worker in self._workers: ret = worker.exit_code() if not ret: continue self._clog.error("%s exited with %d", worker.command, ret) - return True - return False + return ret + return 0 def finalize(self) -> None: """Terminate any running background command, in reverse order. @@ -832,12 +925,13 @@ class QEMUExecuter: :param config: configuration dictionary :param args: parsed arguments """ - #pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-instance-attributes RESULT_MAP = { 0: 'PASS', 1: 'ERROR', 6: 'ABORT', + 11: 'CRASH', QEMUWrapper.GUEST_ERROR_OFFSET + 1: 'FAIL', 124: 'TIMEOUT', 125: 'DEADLOCK', @@ -874,13 +968,13 @@ def run(self, debug: bool) -> int: :return: success or the code of the first encountered error """ - #pylint: disable=too-many-locals + # pylint: disable=too-many-locals qot = QEMUWrapper(self._vcp, debug) ret = 0 results = defaultdict(int) result_file = self._argdict.get('result') - #pylint: disable=consider-using-with - cfp = open(result_file, 'wt',encoding='utf-8') if result_file else None + # pylint: disable=consider-using-with + cfp = open(result_file, 'wt', encoding='utf-8') if result_file else None try: csv = csv_writer(cfp) if cfp else None if csv: @@ -888,9 +982,10 @@ def run(self, debug: bool) -> int: app = self._argdict.get('exec') if app: assert 'timeout' in self._argdict - self._log.info('Execute %s', basename(self._argdict['exec'])) - ret, xtime, err = qot.run(self._qemu_cmd, - self._argdict['timeout'], + timeout = int(float(self._argdict.get('timeout') * + float(self._argdict.get('timeout_factor', 1.0)))) + self._log.debug('Execute %s', basename(self._argdict['exec'])) + ret, xtime, err = qot.run(self._qemu_cmd, timeout, self.get_test_radix(app), None) results[ret] += 1 sret = self.RESULT_MAP.get(ret, ret) @@ -905,13 +1000,22 @@ def run(self, debug: bool) -> int: for tpos, test in enumerate(tests, start=1): self._log.info('[TEST %s] (%d/%d)', self.get_test_radix(test), tpos, tcount) - qemu_cmd, targs, timeout, temp_files, ctx = \ - self._build_qemu_test_command(test) - test_name = self.get_test_radix(test) - ctx.execute('pre') - tret, xtime, err = qot.run(qemu_cmd, timeout, test_name, ctx) - ctx.finalize() - ctx.execute('post', tret) + try: + self._qfm.define_transient({ + 'UTPATH': test, + 'UTDIR': normpath(dirname(test)), + 'UTFILE': basename(test), + }) + qemu_cmd, targs, timeout, temp_files, ctx = \ + self._build_qemu_test_command(test) + test_name = self.get_test_radix(test) + ctx.execute('pre') + tret, xtime, err = qot.run(qemu_cmd, timeout, test_name, + ctx) + ctx.finalize() + ctx.execute('post', tret) + finally: + self._qfm.cleanup_transient() results[tret] += 1 sret = self.RESULT_MAP.get(tret, tret) icount = self.get_namespace_arg(targs, 'icount') @@ -988,7 +1092,7 @@ def _cleanup_temp_files(self, storage: Dict[str, Set[str]]) -> None: def _build_qemu_command(self, args: Namespace, opts: Optional[List[str]] = None) \ - -> Tuple[List[str], Tuple[str, int], Dict[str, Set[str]]]: + -> Tuple[List[str], Tuple[str, int], Dict[str, Set[str]]]: """Build QEMU command line from argparser values. :param args: the parsed arguments @@ -997,9 +1101,9 @@ def _build_qemu_command(self, args: Namespace, the TCP device descriptor to connect to the QEMU VCP, and a dictionary of generated temporary files """ - #pylint: disable=too-many-branches - #pylint: disable=too-many-locals - #pylint: disable=too-many-statements + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-statements if args.qemu is None: raise ValueError('QEMU path is not defined') qemu_args = [ @@ -1042,7 +1146,7 @@ def _build_qemu_command(self, args: Namespace, raise ValueError(f'No such flash file: {args.flash}') if any((args.exec, args.boot)): raise ValueError('Flash file argument is mutually exclusive with' - ' bootloader or rom extension') + ' bootloader or rom extension') flash_path = self.abspath(args.flash) qemu_args.extend(('-drive', f'if=mtd,bus=1,file={flash_path},' f'format=raw')) @@ -1090,8 +1194,7 @@ def _build_qemu_command(self, args: Namespace, return qemu_args, tcpdev, temp_files def _build_qemu_test_command(self, filename: str) -> \ - Tuple[List[str], Namespace, int, Dict[str, Set[str]], \ - QEMUContext]: + Tuple[List[str], Namespace, int, Dict[str, Set[str]], QEMUContext]: test_name = self.get_test_radix(filename) args, opts, timeout = self._build_test_args(test_name) setattr(args, 'exec', filename) @@ -1100,20 +1203,17 @@ def _build_qemu_test_command(self, filename: str) -> \ return qemu_cmd, args, timeout, temp_files, ctx def _build_test_list(self, alphasort: bool = True) -> List[str]: - #pylint: disable=too-many-branches - #pylint: disable=too-many-locals - #pylint: disable=too-many-nested-blocks + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-nested-blocks pathnames = set() testdir = normpath(self._qfm.interpolate(self._config.get('testdir', - curdir))) + curdir))) self._qfm.define({'testdir': testdir}) tfilters = self._args.filter or ['*'] - inc_filters = self._config.get('include') + inc_filters = self._build_config_list('include') if inc_filters: self._log.debug('Searching for tests from %s dir', testdir) - if not isinstance(inc_filters, list): - raise ValueError('Invalid configuration file: ' - '"include" is not a list') for path_filter in filter(None, inc_filters): if testdir: path_filter = joinpath(testdir, path_filter) @@ -1134,11 +1234,8 @@ def _build_test_list(self, alphasort: bool = True) -> List[str]: pathnames.add(testfile) if not pathnames: return [] - exc_filters = self._config.get('exclude') + exc_filters = self._build_config_list('exclude') if exc_filters: - if not isinstance(exc_filters, list): - raise ValueError('Invalid configuration file: ' - '"exclude" is not a list') for path_filter in filter(None, exc_filters): if testdir: path_filter = joinpath(testdir, path_filter) @@ -1150,11 +1247,8 @@ def _build_test_list(self, alphasort: bool = True) -> List[str]: return list(pathnames) def _enumerate_from(self, config_entry: str) -> Iterator[str]: - incf_filters = self._config.get(config_entry) + incf_filters = self._build_config_list(config_entry) if incf_filters: - if not isinstance(incf_filters, list): - raise ValueError(f'Invalid configuration file: ' - f'"{config_entry}" is not a list') for incf in incf_filters: incf = normpath(self._qfm.interpolate(incf)) if not isfile(incf): @@ -1171,6 +1265,35 @@ def _enumerate_from(self, config_entry: str) -> Iterator[str]: testfile = joinpath(incf_dir, testfile) yield normpath(testfile) + def _build_config_list(self, config_entry: str) -> List: + cfglist = [] + items = self._config.get(config_entry) + if not items: + return cfglist + if not isinstance(items, list): + raise ValueError(f'Invalid configuration file: ' + f'"{config_entry}" is not a list') + # pylint: disable=too-many-nested-blocks + for item in items: + if isinstance(item, str): + cfglist.append(item) + continue + if isinstance(item, dict): + for dname, dval in item.items(): + try: + cond = bool(int(environ.get(dname, '0'))) + except (ValueError, TypeError): + cond = False + if not cond: + continue + if isinstance(dval, str): + dval = [dval] + if isinstance(dval, list): + for sitem in dval: + if isinstance(sitem, str): + cfglist.append(sitem) + return cfglist + def _build_test_args(self, test_name: str) \ -> Tuple[Namespace, List[str], int]: tests_cfg = self._config.get('tests', {}) @@ -1200,8 +1323,10 @@ def _build_test_args(self, test_name: str) \ raise ValueError('fInvalid QEMU options for {test_name}') opts = self.flatten([opt.split(' ') for opt in opts]) opts = [self._qfm.interpolate_dirs(opt, test_name) for opt in opts] - timeout = int(kwargs.get('timeout', DEFAULT_TIMEOUT)) - return Namespace(**kwargs), opts or [], timeout + timeout = float(kwargs.get('timeout', DEFAULT_TIMEOUT)) + tmfactor = float(kwargs.get('timeout_factor', DEFAULT_TIMEOUT_FACTOR)) + itimeout = int(timeout * tmfactor) + return Namespace(**kwargs), opts or [], itimeout def _build_test_context(self, test_name: str) -> QEMUContext: context = defaultdict(list) @@ -1234,15 +1359,16 @@ def _build_test_context(self, test_name: str) -> QEMUContext: def main(): """Main routine""" - #pylint: disable=too-many-branches - #pylint: disable=too-many-locals - #pylint: disable=too-many-statements - #pylint: disable=too-many-nested-blocks + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-statements + # pylint: disable=too-many-nested-blocks debug = True qemu_dir = normpath(joinpath(dirname(dirname(dirname(__file__))))) qemu_path = normpath(joinpath(qemu_dir, 'build', 'qemu-system-riscv32')) if not isfile(qemu_path): qemu_path = None + tmp_result: Optional[str] = None try: args: Optional[Namespace] = None argparser = ArgumentParser(description=modules[__name__].__doc__) @@ -1251,6 +1377,8 @@ def main(): help='path to configuration file') argparser.add_argument('-w', '--result', metavar='CSV', help='path to output result file') + argparser.add_argument('-R', '--summary', action='store_true', + help='show a result summary') argparser.add_argument('-k', '--timeout', metavar='SECONDS', type=int, help=f'exit after the specified seconds ' f'(default: {DEFAULT_TIMEOUT} secs)') @@ -1290,6 +1418,9 @@ def main(): qvm.add_argument('-s', '--singlestep', action='store_true', default=None, help='enable "single stepping" QEMU execution mode') + qvm.add_argument('-T', '--timeout-factor', type=float, metavar='SECS', + default=DEFAULT_TIMEOUT_FACTOR, + help='timeout factor') qvm.add_argument('-U', '--muxserial', action='store_true', default=None, help='enable multiple virtual UARTs to be muxed into ' @@ -1316,6 +1447,10 @@ def main(): opts = [] args = argparser.parse_args(sargv) debug = args.debug + if args.summary and not args.result: + tmpfd, tmp_result = mkstemp(suffix='.csv') + close(tmpfd) + args.result = tmp_result if opts: qopts = getattr(args, 'opts') qopts.extend(opts) @@ -1361,7 +1496,7 @@ def main(): jargs.append(val) if jargs: jwargs = argparser.parse_args(jargs) - #pylint: disable=protected-access + # pylint: disable=protected-access for name, val in jwargs._get_kwargs(): if not hasattr(args, name): argparser.error(f'Unknown config file default: {name}') @@ -1391,9 +1526,13 @@ def main(): print(format_exc(chain=False), file=stderr) argparser.error(str(exc)) ret = qexc.run(args.debug) + if args.summary: + rfmt = ResultFormatter() + rfmt.load(args.result) + rfmt.show(True) log.debug('End of execution with code %d', ret or 0) sysexit(ret) - #pylint: disable=broad-except + # pylint: disable=broad-except except Exception as exc: print(f'{linesep}Error: {exc}', file=stderr) if debug: @@ -1401,6 +1540,9 @@ def main(): sysexit(1) except KeyboardInterrupt: sysexit(2) + finally: + if tmp_result and isfile(tmp_result): + unlink(tmp_result) if __name__ == '__main__': diff --git a/target/riscv/cpu_cfg.h b/target/riscv/cpu_cfg.h index 077f90ff6da8..f6e984af1bc4 100644 --- a/target/riscv/cpu_cfg.h +++ b/target/riscv/cpu_cfg.h @@ -183,4 +183,7 @@ MATERIALISE_EXT_PREDICATE(xtheadmempair) MATERIALISE_EXT_PREDICATE(xtheadsync) MATERIALISE_EXT_PREDICATE(XVentanaCondOps) +/* Extensions that are not yet upstream */ +MATERIALISE_EXT_PREDICATE(zbr); + #endif diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index 8585ad37b9ae..a12088a242d0 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -149,20 +149,32 @@ static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) static bool pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) { if (pmp_index < MAX_RISCV_PMPS) { + if (env->pmp_state.pmp[pmp_index].cfg_reg == val) { + /* no change */ + return false; + } + if (!pmp_is_writable(env, pmp_index)) { - qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - locked\n"); + qemu_log_mask(LOG_GUEST_ERROR, + "ignoring pmpcfg[%u] write - locked" + " - current:0x%02x new:0x%02x\n", + pmp_index, env->pmp_state.pmp[pmp_index].cfg_reg, + val); } else if (riscv_cpu_cfg(env)->epmp && !pmp_is_valid_epmp_cfg(env, val)) { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpcfg write - invalid\n"); - } else if (env->pmp_state.pmp[pmp_index].cfg_reg != val) { + "ignoring pmpcfg[%u] write - invalid" + " - current:0x%02x new:0x%02x\n", + pmp_index, env->pmp_state.pmp[pmp_index].cfg_reg, + val); + } else { env->pmp_state.pmp[pmp_index].cfg_reg = val; pmp_update_rule_addr(env, pmp_index); return true; } } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpcfg write - out of bounds\n"); + "ignoring pmpcfg[%u] write - out of bounds\n", pmp_index); } return false; @@ -362,7 +374,9 @@ bool pmp_hart_has_privs(CPURISCVState *env, target_ulong addr, /* partially inside */ if ((s + e) == 1) { qemu_log_mask(LOG_GUEST_ERROR, - "pmp violation - access is partially inside\n"); + "pmp violation at 0x" TARGET_FMT_lx "+" TARGET_FMT_lu + " - access is partially inside pmp[%u]\n", + addr, size, i); *allowed_privs = 0; return false; } @@ -528,6 +542,11 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, bool is_next_cfg_tor = false; if (addr_index < MAX_RISCV_PMPS) { + if (env->pmp_state.pmp[addr_index].addr_reg == val) { + /* no change */ + return; + } + /* * In TOR mode, need to check the lock bit of the next pmp * (if there is a next). @@ -538,27 +557,34 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, if (!pmp_is_writable(env, addr_index + 1) && is_next_cfg_tor) { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr write - pmpcfg + 1 locked\n"); + "ignoring pmpaddr[%u] write - pmpcfg+1 locked" + " - prev:0x" TARGET_FMT_lx " new:0x" TARGET_FMT_lx + "\n", + addr_index, + env->pmp_state.pmp[addr_index].addr_reg, val); return; } } if (pmp_is_writable(env, addr_index)) { - if (env->pmp_state.pmp[addr_index].addr_reg != val) { - env->pmp_state.pmp[addr_index].addr_reg = val; - pmp_update_rule_addr(env, addr_index); - if (is_next_cfg_tor) { - pmp_update_rule_addr(env, addr_index + 1); - } - tlb_flush(env_cpu(env)); + env->pmp_state.pmp[addr_index].addr_reg = val; + pmp_update_rule_addr(env, addr_index); + if (is_next_cfg_tor) { + pmp_update_rule_addr(env, addr_index + 1); } + tlb_flush(env_cpu(env)); } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr write - locked\n"); + "ignoring pmpaddr[%u] write - locked" + " - prev:0x" TARGET_FMT_lx " new:0x" TARGET_FMT_lx + "\n", + addr_index, env->pmp_state.pmp[addr_index].addr_reg, + val); } } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr write - out of bounds\n"); + "ignoring pmpaddr[%u] write - out of bounds\n", + addr_index); } } @@ -575,7 +601,8 @@ target_ulong pmpaddr_csr_read(CPURISCVState *env, uint32_t addr_index) trace_pmpaddr_csr_read(env->mhartid, addr_index, val); } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr read - out of bounds\n"); + "ignoring pmpaddr[%u] read - out of bounds\n", + addr_index); } return val;