From 0bdaf19f0dccfee70f48541e0e28d504cbb6bd74 Mon Sep 17 00:00:00 2001 From: James Wainwright Date: Tue, 6 Jan 2026 16:23:43 +0000 Subject: [PATCH 01/18] [ot] Misc and admin changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These changes are specific to our fork and are either being dropped by open pull requests or will not be needed upstream. Co-authored-by: Emmanuel Blot Co-authored-by: Loïc Lefort Signed-off-by: James Wainwright --- .gitignore | 6 +++++- MAINTAINERS | 31 +++++++++++++++++++++++++++++-- README.md | 19 +++++++++++++++++++ README.rst => README_QEMU.rst | 0 include/exec/poison.h | 3 +++ 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 README.md rename README.rst => README_QEMU.rst (100%) diff --git a/.gitignore b/.gitignore index 61fa39967b542..bda3992c1873e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ .sdk .stgit-* .git-submodule-status -.clang-format .gdb_history cscope.* tags @@ -20,3 +19,8 @@ GTAGS *.swp *.patch *.gcov +subprojects/*-*/ +subprojects/packagecache/ +!subprojects/packagefiles/**/*.patch +.clangd +.zed diff --git a/MAINTAINERS b/MAINTAINERS index 63e9ba521bcc5..e714850420e16 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1680,14 +1680,41 @@ F: pc-bios/vof* RISC-V Machines --------------- + +EarlGrey and Darjeeling OpenTitan platforms +M: Emmanuel Blot +M: Loïc Lefort +S: Supported +F: hw/opentitan/* +F: include/hw/opentitan/* +F: scripts/opentitan/* +F: hw/riscv/ot_darjeeling.c +F: hw/riscv/ot_earlgrey.c +F: include/hw/riscv/ot_darjeeling.h +F: include/hw/riscv/ot_earlgrey.h + +Ibex platforms and utilities +M: Emmanuel Blot +M: Loïc Lefort +S: Supported +F: hw/riscv/ibex_common.c +F: include/hw/riscv/ibex_common.h +F: include/hw/riscv/ibex_irq.h +F: hw/ibexdemo/* +F: include/hw/ibexdemo/* + OpenTitan M: Alistair Francis L: qemu-riscv@nongnu.org S: Supported F: hw/riscv/opentitan.c -F: hw/*/ibex_*.c +F: hw/ssi/ibex_spi_host.c +F: hw/timer/ibex_timer.c +F: hw/char/ibex_uart.c F: include/hw/riscv/opentitan.h -F: include/hw/*/ibex_*.h +F: include/hw/ssi/ibex_spi_host.h +F: include/hw/timer/ibex_timer.h +F: include/hw/char/ibex_uart.h Microchip PolarFire SoC Icicle Kit L: qemu-riscv@nongnu.org diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..11e299d9ce13a --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# QEMU OpenTitan README + +[![.github/workflows/build_test.yaml](https://github.com/lowRISC/qemu/actions/workflows/build_test.yaml/badge.svg?branch=ot-10.1.0)](https://github.com/lowRISC/qemu/actions/workflows/build_test.yaml) + +QEMU is a generic and open source machine & userspace emulator and virtualizer. + +QEMU is capable of emulating a complete machine in software without any need for hardware +virtualization support. By using dynamic translation, it achieves very good performance. + +This branch contains a fork of QEMU 10.1.0 dedicated to support lowRISC Ibex platforms: + * [OpenTitan](https://opentitan.org) [EarlGrey](docs/opentitan/ot_earlgrey.md), based on + [1.0.0](https://github.com/lowRISC/opentitan/tree/earlgrey_1.0.0). + * [OpenTitan](https://opentitan.org) [Darjeeling](docs/opentitan/ot_darjeeling.md), based on + [development version](https://github.com/lowRISC/opentitan/tree/master). + * [lowRISC](https://github.com/lowRISC/ibex-demo-system) [IbexDemo](docs/opentitan/ibexdemo.md). + +See [installation instructions](docs/opentitan/index.md) + +See also original [QEMU README file](README_QEMU.rst) diff --git a/README.rst b/README_QEMU.rst similarity index 100% rename from README.rst rename to README_QEMU.rst diff --git a/include/exec/poison.h b/include/exec/poison.h index a779adbb7a6cb..693d9b214aed9 100644 --- a/include/exec/poison.h +++ b/include/exec/poison.h @@ -25,6 +25,9 @@ #pragma GCC poison TARGET_PPC #pragma GCC poison TARGET_PPC64 #pragma GCC poison TARGET_ABI32 +#pragma GCC poison TARGET_RISCV +#pragma GCC poison TARGET_RISCV32 +#pragma GCC poison TARGET_RISCV64 #pragma GCC poison TARGET_RX #pragma GCC poison TARGET_S390X #pragma GCC poison TARGET_SH4 From 0dad0b5b788dec7b1f44ce3345de616163559a11 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 Jan 2026 11:45:24 +0000 Subject: [PATCH 02/18] [ot] hw/misc: unimp: add an option to only warn once on each access type The option is disabled by default. Signed-off-by: Emmanuel Blot --- hw/misc/unimp.c | 14 ++++++++++++++ include/hw/misc/unimp.h | 3 +++ 2 files changed, 17 insertions(+) diff --git a/hw/misc/unimp.c b/hw/misc/unimp.c index 4370c14ef1667..89dc49d056767 100644 --- a/hw/misc/unimp.c +++ b/hw/misc/unimp.c @@ -22,9 +22,16 @@ static uint64_t unimp_read(void *opaque, hwaddr offset, unsigned size) { UnimplementedDeviceState *s = UNIMPLEMENTED_DEVICE(opaque); + if (s->warn_once && s->r_warned) { + return 0; + } + qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read " "(size %d, offset 0x%0*" HWADDR_PRIx ")\n", s->name, size, s->offset_fmt_width, offset); + + s->r_warned = true; + return 0; } @@ -33,10 +40,16 @@ static void unimp_write(void *opaque, hwaddr offset, { UnimplementedDeviceState *s = UNIMPLEMENTED_DEVICE(opaque); + if (s->warn_once && s->w_warned) { + return; + } + qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write " "(size %d, offset 0x%0*" HWADDR_PRIx ", value 0x%0*" PRIx64 ")\n", s->name, size, s->offset_fmt_width, offset, size << 1, value); + + s->w_warned = true; } static const MemoryRegionOps unimp_ops = { @@ -73,6 +86,7 @@ static void unimp_realize(DeviceState *dev, Error **errp) static const Property unimp_properties[] = { DEFINE_PROP_UINT64("size", UnimplementedDeviceState, size, 0), DEFINE_PROP_STRING("name", UnimplementedDeviceState, name), + DEFINE_PROP_BOOL("warn-once", UnimplementedDeviceState, warn_once, false), }; static void unimp_class_init(ObjectClass *klass, const void *data) diff --git a/include/hw/misc/unimp.h b/include/hw/misc/unimp.h index 518d627dc5dc8..61710239b0e77 100644 --- a/include/hw/misc/unimp.h +++ b/include/hw/misc/unimp.h @@ -23,6 +23,9 @@ struct UnimplementedDeviceState { unsigned offset_fmt_width; char *name; uint64_t size; + bool warn_once; + bool r_warned; + bool w_warned; }; /** From f85899fa2f38a3aa90dfccf13c3e156c8e5c1918 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 6 Jan 2026 16:52:15 +0000 Subject: [PATCH 03/18] [ot] hw/core: loader: add Rust demangler Demangler code taken from GNU libiberty. Signed-off-by: Emmanuel Blot --- hw/core/loader.c | 79 +- hw/core/meson.build | 1 + hw/core/rust_demangle.c | 1751 +++++++++++++++++++++++++++++++ include/hw/core/rust_demangle.h | 58 + include/hw/elf_ops.h.inc | 69 +- include/hw/loader.h | 23 + 6 files changed, 1976 insertions(+), 5 deletions(-) create mode 100644 hw/core/rust_demangle.c create mode 100644 include/hw/core/rust_demangle.h diff --git a/hw/core/loader.c b/hw/core/loader.c index 590c5b02aa1d8..0b6b3e5fc44ff 100644 --- a/hw/core/loader.c +++ b/hw/core/loader.c @@ -448,7 +448,6 @@ ssize_t load_elf_as(const char *filename, true, NULL); } -/* return < 0 if error, otherwise the number of bytes loaded in memory */ ssize_t load_elf_ram_sym(const char *filename, uint64_t (*elf_note_fn)(void *, void *, bool), uint64_t (*translate_fn)(void *, uint64_t), @@ -457,6 +456,23 @@ ssize_t load_elf_ram_sym(const char *filename, uint32_t *pflags, int elf_data_order, int elf_machine, int clear_lsb, int data_swab, AddressSpace *as, bool load_rom, symbol_fn_t sym_cb) +{ + return load_elf_ram_sym_nosz(filename, elf_note_fn, translate_fn, + translate_opaque, pentry, lowaddr, highaddr, + pflags, elf_data_order, elf_machine, clear_lsb, + data_swab, as, load_rom, sym_cb, true); +} + +/* return < 0 if error, otherwise the number of bytes loaded in memory */ +ssize_t load_elf_ram_sym_nosz(const char *filename, + uint64_t (*elf_note_fn)(void *, void *, bool), + uint64_t (*translate_fn)(void *, uint64_t), + void *translate_opaque, uint64_t *pentry, + uint64_t *lowaddr, uint64_t *highaddr, + uint32_t *pflags, int elf_data_order, + int elf_machine, int clear_lsb, int data_swab, + AddressSpace *as, bool load_rom, + symbol_fn_t sym_cb, bool skip_nosz) { const int host_data_order = HOST_BIG_ENDIAN ? ELFDATA2MSB : ELFDATA2LSB; int fd, must_swab; @@ -490,12 +506,14 @@ ssize_t load_elf_ram_sym(const char *filename, ret = load_elf64(filename, fd, elf_note_fn, translate_fn, translate_opaque, must_swab, pentry, lowaddr, highaddr, pflags, elf_machine, - clear_lsb, data_swab, as, load_rom, sym_cb); + clear_lsb, data_swab, as, load_rom, sym_cb, + skip_nosz); } else { ret = load_elf32(filename, fd, elf_note_fn, translate_fn, translate_opaque, must_swab, pentry, lowaddr, highaddr, pflags, elf_machine, - clear_lsb, data_swab, as, load_rom, sym_cb); + clear_lsb, data_swab, as, load_rom, sym_cb, + skip_nosz); } if (ret > 0) { @@ -507,6 +525,61 @@ ssize_t load_elf_ram_sym(const char *filename, return ret; } +/* return < 0 if error, otherwise 0 */ +int load_elf_sym(const char *filename, int big_endian, int elf_machine, + int clear_lsb) +{ + int fd, data_order, target_data_order, must_swab; + ssize_t ret = ELF_LOAD_FAILED; + uint8_t e_ident[EI_NIDENT]; + + fd = open(filename, O_RDONLY | O_BINARY); + if (fd < 0) { + perror(filename); + return -1; + } + if (read(fd, e_ident, sizeof(e_ident)) != sizeof(e_ident)) { + goto fail; + } + if (e_ident[0] != ELFMAG0 || + e_ident[1] != ELFMAG1 || + e_ident[2] != ELFMAG2 || + e_ident[3] != ELFMAG3) { + ret = ELF_LOAD_NOT_ELF; + goto fail; + } +#if HOST_BIG_ENDIAN + data_order = ELFDATA2MSB; +#else + data_order = ELFDATA2LSB; +#endif + must_swab = data_order != e_ident[EI_DATA]; + if (big_endian) { + target_data_order = ELFDATA2MSB; + } else { + target_data_order = ELFDATA2LSB; + } + + if (target_data_order != e_ident[EI_DATA]) { + ret = ELF_LOAD_WRONG_ENDIAN; + goto fail; + } + + lseek(fd, 0, SEEK_SET); + if (e_ident[EI_CLASS] == ELFCLASS64) { + ret = load_elf_symbols64(filename, fd, must_swab, elf_machine, + clear_lsb); + } else { + ret = load_elf_symbols32(filename, fd, must_swab, elf_machine, + clear_lsb); + } + + fail: + close(fd); + return ret; +} + + static void bswap_uboot_header(uboot_image_header_t *hdr) { #if !HOST_BIG_ENDIAN diff --git a/hw/core/meson.build b/hw/core/meson.build index b5a545a0edd63..9f4c9e708bd3b 100644 --- a/hw/core/meson.build +++ b/hw/core/meson.build @@ -42,6 +42,7 @@ system_ss.add(files( 'qdev-hotplug.c', 'qdev-properties-system.c', 'reset.c', + 'rust_demangle.c', 'sysbus.c', 'vm-change-state-handler.c', 'clock-vmstate.c', diff --git a/hw/core/rust_demangle.c b/hw/core/rust_demangle.c new file mode 100644 index 0000000000000..fad33f09b8cb0 --- /dev/null +++ b/hw/core/rust_demangle.c @@ -0,0 +1,1751 @@ +/* Demangler for the Rust programming language + Copyright (C) 2016-2023 Free Software Foundation, Inc. + Written by David Tolnay (dtolnay@gmail.com). + Rewritten by Eduard-Mihai Burtescu (eddyb@lyken.rs) for v0 support. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +In addition to the permissions in the GNU Library General Public +License, the Free Software Foundation gives you unlimited permission +to link the compiled version of this file into combinations with other +programs, and to distribute those combinations without any restriction +coming from the use of this file. (The Library Public License +restrictions do apply in other respects; for example, they cover +modification of the file, and distribution when not linked into a +combined executable.) + +Libiberty is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. +If not, see . */ + + +#include "qemu/osdep.h" +#include +#include "hw/core/rust_demangle.h" + + +struct rust_demangler +{ + const char *sym; + size_t sym_len; + + void *callback_opaque; + demangle_callbackref callback; + + /* Position of the next character to read from the symbol. */ + size_t next; + + /* Non-zero if any error occurred. */ + int errored; + + /* Non-zero if nothing should be printed. */ + int skipping_printing; + + /* Non-zero if printing should be verbose (e.g. include hashes). */ + int verbose; + + /* Rust mangling version, with legacy mangling being -1. */ + int version; + + /* Recursion depth. */ + unsigned int recursion; + /* Maximum number of times demangle_path may be called recursively. */ +#define RUST_MAX_RECURSION_COUNT 1024 +#define RUST_NO_RECURSION_LIMIT ((unsigned int) -1) + + uint64_t bound_lifetime_depth; +}; + +/* Determine host character set. */ +#define HOST_CHARSET_UNKNOWN 0 +#define HOST_CHARSET_ASCII 1 +#define HOST_CHARSET_EBCDIC 2 + +#if '\n' == 0x0A && ' ' == 0x20 && '0' == 0x30 \ + && 'A' == 0x41 && 'a' == 0x61 && '!' == 0x21 +# define HOST_CHARSET HOST_CHARSET_ASCII +#else +# if '\n' == 0x15 && ' ' == 0x40 && '0' == 0xF0 \ + && 'A' == 0xC1 && 'a' == 0x81 && '!' == 0x5A +# define HOST_CHARSET HOST_CHARSET_EBCDIC +# else +# define HOST_CHARSET HOST_CHARSET_UNKNOWN +# endif +#endif + +/* Categories. */ + +enum { + /* In C99 */ + _sch_isblank = 0x0001, /* space \t */ + _sch_iscntrl = 0x0002, /* nonprinting characters */ + _sch_isdigit = 0x0004, /* 0-9 */ + _sch_islower = 0x0008, /* a-z */ + _sch_isprint = 0x0010, /* any printing character including ' ' */ + _sch_ispunct = 0x0020, /* all punctuation */ + _sch_isspace = 0x0040, /* space \t \n \r \f \v */ + _sch_isupper = 0x0080, /* A-Z */ + _sch_isxdigit = 0x0100, /* 0-9A-Fa-f */ + + /* Extra categories useful to cpplib. */ + _sch_isidst = 0x0200, /* A-Za-z_ */ + _sch_isvsp = 0x0400, /* \n \r */ + _sch_isnvsp = 0x0800, /* space \t \f \v \0 */ + + /* Combinations of the above. */ + _sch_isalpha = _sch_isupper|_sch_islower, /* A-Za-z */ + _sch_isalnum = _sch_isalpha|_sch_isdigit, /* A-Za-z0-9 */ + _sch_isidnum = _sch_isidst|_sch_isdigit, /* A-Za-z0-9_ */ + _sch_isgraph = _sch_isalnum|_sch_ispunct, /* isprint and not space */ + _sch_iscppsp = _sch_isvsp|_sch_isnvsp, /* isspace + \0 */ + _sch_isbasic = _sch_isprint|_sch_iscppsp /* basic charset of ISO C + (plus ` and @) */ +}; + +/* Character classification. */ + +/* Shorthand */ +#define bl _sch_isblank +#define cn _sch_iscntrl +#define di _sch_isdigit +#define is _sch_isidst +#define lo _sch_islower +#define nv _sch_isnvsp +#define pn _sch_ispunct +#define pr _sch_isprint +#define sp _sch_isspace +#define up _sch_isupper +#define vs _sch_isvsp +#define xd _sch_isxdigit + +/* Masks. */ +#define L (const unsigned short) (lo|is |pr) /* lower case letter */ +#define XL (const unsigned short) (lo|is|xd|pr) /* lowercase hex digit */ +#define U (const unsigned short) (up|is |pr) /* upper case letter */ +#define XU (const unsigned short) (up|is|xd|pr) /* uppercase hex digit */ +#define D (const unsigned short) (di |xd|pr) /* decimal digit */ +#define P (const unsigned short) (pn |pr) /* punctuation */ +#define _ (const unsigned short) (pn|is |pr) /* underscore */ + +#define C (const unsigned short) ( cn) /* control character */ +#define Z (const unsigned short) (nv |cn) /* NUL */ +#define M (const unsigned short) (nv|sp |cn) /* cursor movement: \f \v */ +#define V (const unsigned short) (vs|sp |cn) /* vertical space: \r \n */ +#define T (const unsigned short) (nv|sp|bl|cn) /* tab */ +#define S (const unsigned short) (nv|sp|bl|pr) /* space */ + +const unsigned short _sch_istable[256] = +{ + Z, C, C, C, C, C, C, C, /* NUL SOH STX ETX EOT ENQ ACK BEL */ + C, T, V, M, M, V, C, C, /* BS HT LF VT FF CR SO SI */ + C, C, C, C, C, C, C, C, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB */ + C, C, C, C, C, C, C, C, /* CAN EM SUB ESC FS GS RS US */ + S, P, P, P, P, P, P, P, /* SP ! " # $ % & ' */ + P, P, P, P, P, P, P, P, /* ( ) * + , - . / */ + D, D, D, D, D, D, D, D, /* 0 1 2 3 4 5 6 7 */ + D, D, P, P, P, P, P, P, /* 8 9 : ; < = > ? */ + P, XU, XU, XU, XU, XU, XU, U, /* @ A B C D E F G */ + U, U, U, U, U, U, U, U, /* H I J K L M N O */ + U, U, U, U, U, U, U, U, /* P Q R S T U V W */ + U, U, U, P, P, P, P, _, /* X Y Z [ \ ] ^ _ */ + P, XL, XL, XL, XL, XL, XL, L, /* ` a b c d e f g */ + L, L, L, L, L, L, L, L, /* h i j k l m n o */ + L, L, L, L, L, L, L, L, /* p q r s t u v w */ + L, L, L, P, P, P, P, C, /* x y z { | } ~ DEL */ + + /* high half of unsigned char is locale-specific, so all tests are + false in "C" locale */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#define _sch_test(c, bit) (_sch_istable[(c) & 0xff] & (unsigned short)(bit)) + +#define ISALPHA(c) _sch_test(c, _sch_isalpha) +#define ISALNUM(c) _sch_test(c, _sch_isalnum) +#define ISBLANK(c) _sch_test(c, _sch_isblank) +#define ISCNTRL(c) _sch_test(c, _sch_iscntrl) +#define ISDIGIT(c) _sch_test(c, _sch_isdigit) +#define ISGRAPH(c) _sch_test(c, _sch_isgraph) +#define ISLOWER(c) _sch_test(c, _sch_islower) +#define ISPRINT(c) _sch_test(c, _sch_isprint) +#define ISPUNCT(c) _sch_test(c, _sch_ispunct) +#define ISSPACE(c) _sch_test(c, _sch_isspace) +#define ISUPPER(c) _sch_test(c, _sch_isupper) +#define ISXDIGIT(c) _sch_test(c, _sch_isxdigit) + +#define ISIDNUM(c) _sch_test(c, _sch_isidnum) +#define ISIDST(c) _sch_test(c, _sch_isidst) +#define IS_ISOBASIC(c) _sch_test(c, _sch_isbasic) +#define IS_VSPACE(c) _sch_test(c, _sch_isvsp) +#define IS_NVSPACE(c) _sch_test(c, _sch_isnvsp) +#define IS_SPACE_OR_NUL(c) _sch_test(c, _sch_iscppsp) + +/* Character transformation. */ +extern const unsigned char _sch_toupper[256]; +extern const unsigned char _sch_tolower[256]; +#define TOUPPER(c) _sch_toupper[(c) & 0xff] +#define TOLOWER(c) _sch_tolower[(c) & 0xff] + +/* Parsing functions. */ + +static char +peek (const struct rust_demangler *rdm) +{ + if (rdm->next < rdm->sym_len) + return rdm->sym[rdm->next]; + return 0; +} + +static int +eat (struct rust_demangler *rdm, char c) +{ + if (peek (rdm) == c) + { + rdm->next++; + return 1; + } + else + return 0; +} + +static char +next (struct rust_demangler *rdm) +{ + char c = peek (rdm); + if (!c) + rdm->errored = 1; + else + rdm->next++; + return c; +} + +static uint64_t +parse_integer_62 (struct rust_demangler *rdm) +{ + char c; + uint64_t x; + + if (eat (rdm, '_')) + return 0; + + x = 0; + while (!eat (rdm, '_') && !rdm->errored) + { + c = next (rdm); + x *= 62; + if (ISDIGIT (c)) + x += c - '0'; + else if (ISLOWER (c)) + x += 10 + (c - 'a'); + else if (ISUPPER (c)) + x += 10 + 26 + (c - 'A'); + else + { + rdm->errored = 1; + return 0; + } + } + return x + 1; +} + +static uint64_t +parse_opt_integer_62 (struct rust_demangler *rdm, char tag) +{ + if (!eat (rdm, tag)) + return 0; + return 1 + parse_integer_62 (rdm); +} + +static uint64_t +parse_disambiguator (struct rust_demangler *rdm) +{ + return parse_opt_integer_62 (rdm, 's'); +} + +static size_t +parse_hex_nibbles (struct rust_demangler *rdm, uint64_t *value) +{ + char c; + size_t hex_len; + + hex_len = 0; + *value = 0; + + while (!eat (rdm, '_')) + { + *value <<= 4; + + c = next (rdm); + if (ISDIGIT (c)) + *value |= c - '0'; + else if (c >= 'a' && c <= 'f') + *value |= 10 + (c - 'a'); + else + { + rdm->errored = 1; + return 0; + } + hex_len++; + } + + return hex_len; +} + +struct rust_mangled_ident +{ + /* ASCII part of the identifier. */ + const char *ascii; + size_t ascii_len; + + /* Punycode insertion codes for Unicode codepoints, if any. */ + const char *punycode; + size_t punycode_len; +}; + +static struct rust_mangled_ident +parse_ident (struct rust_demangler *rdm) +{ + char c; + size_t start, len; + int is_punycode = 0; + struct rust_mangled_ident ident; + + ident.ascii = NULL; + ident.ascii_len = 0; + ident.punycode = NULL; + ident.punycode_len = 0; + + if (rdm->version != -1) + is_punycode = eat (rdm, 'u'); + + c = next (rdm); + if (!ISDIGIT (c)) + { + rdm->errored = 1; + return ident; + } + len = c - '0'; + + if (c != '0') + while (ISDIGIT (peek (rdm))) + len = len * 10 + (next (rdm) - '0'); + + /* Skip past the optional `_` separator (v0). */ + if (rdm->version != -1) + eat (rdm, '_'); + + start = rdm->next; + rdm->next += len; + /* Check for overflows. */ + if ((start > rdm->next) || (rdm->next > rdm->sym_len)) + { + rdm->errored = 1; + return ident; + } + + ident.ascii = rdm->sym + start; + ident.ascii_len = len; + + if (is_punycode) + { + ident.punycode_len = 0; + while (ident.ascii_len > 0) + { + ident.ascii_len--; + + /* The last '_' is a separator between ascii & punycode. */ + if (ident.ascii[ident.ascii_len] == '_') + break; + + ident.punycode_len++; + } + if (!ident.punycode_len) + { + rdm->errored = 1; + return ident; + } + ident.punycode = ident.ascii + (len - ident.punycode_len); + } + + if (ident.ascii_len == 0) + ident.ascii = NULL; + + return ident; +} + +/* Printing functions. */ + +static void +print_str (struct rust_demangler *rdm, const char *data, size_t len) +{ + if (!rdm->errored && !rdm->skipping_printing) + rdm->callback (data, len, rdm->callback_opaque); +} + +#define PRINT(s) print_str (rdm, s, strlen (s)) + +static void +print_uint64 (struct rust_demangler *rdm, uint64_t x) +{ + char s[21]; + snprintf (s, 21, "%" PRIu64, x); + PRINT (s); +} + +static void +print_uint64_hex (struct rust_demangler *rdm, uint64_t x) +{ + char s[17]; + snprintf (s, 17, "%" PRIx64, x); + PRINT (s); +} + +/* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */ +static int +decode_lower_hex_nibble (char nibble) +{ + if ('0' <= nibble && nibble <= '9') + return nibble - '0'; + if ('a' <= nibble && nibble <= 'f') + return 0xa + (nibble - 'a'); + return -1; +} + +/* Return the unescaped character for a "$...$" escape, or 0 if invalid. */ +static char +decode_legacy_escape (const char *e, size_t len, size_t *out_len) +{ + char c = 0; + size_t escape_len = 0; + int lo_nibble = -1, hi_nibble = -1; + + if (len < 3 || e[0] != '$') + return 0; + + e++; + len--; + + if (e[0] == 'C') + { + escape_len = 1; + + c = ','; + } + else if (len > 2) + { + escape_len = 2; + + if (e[0] == 'S' && e[1] == 'P') + c = '@'; + else if (e[0] == 'B' && e[1] == 'P') + c = '*'; + else if (e[0] == 'R' && e[1] == 'F') + c = '&'; + else if (e[0] == 'L' && e[1] == 'T') + c = '<'; + else if (e[0] == 'G' && e[1] == 'T') + c = '>'; + else if (e[0] == 'L' && e[1] == 'P') + c = '('; + else if (e[0] == 'R' && e[1] == 'P') + c = ')'; + else if (e[0] == 'u' && len > 3) + { + escape_len = 3; + + hi_nibble = decode_lower_hex_nibble (e[1]); + if (hi_nibble < 0) + return 0; + lo_nibble = decode_lower_hex_nibble (e[2]); + if (lo_nibble < 0) + return 0; + + /* Only allow non-control ASCII characters. */ + if (hi_nibble > 7) + return 0; + c = (hi_nibble << 4) | lo_nibble; + if (c < 0x20) + return 0; + } + } + + if (!c || len <= escape_len || e[escape_len] != '$') + return 0; + + *out_len = 2 + escape_len; + return c; +} + +static void +print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident) +{ + char unescaped; + uint8_t *out, *p, d; + size_t len, cap, punycode_pos, j; + /* Punycode parameters and state. */ + uint32_t c; + size_t base, t_min, t_max, skew, damp, bias, i; + size_t delta, w, k, t; + + if (rdm->errored || rdm->skipping_printing) + return; + + if (rdm->version == -1) + { + /* Ignore leading underscores preceding escape sequences. + The mangler inserts an underscore to make sure the + identifier begins with a XID_Start character. */ + if (ident.ascii_len >= 2 && ident.ascii[0] == '_' + && ident.ascii[1] == '$') + { + ident.ascii++; + ident.ascii_len--; + } + + while (ident.ascii_len > 0) + { + /* Handle legacy escape sequences ("$...$", ".." or "."). */ + if (ident.ascii[0] == '$') + { + unescaped + = decode_legacy_escape (ident.ascii, ident.ascii_len, &len); + if (unescaped) + print_str (rdm, &unescaped, 1); + else + { + /* Unexpected escape sequence, print the rest verbatim. */ + print_str (rdm, ident.ascii, ident.ascii_len); + return; + } + } + else if (ident.ascii[0] == '.') + { + if (ident.ascii_len >= 2 && ident.ascii[1] == '.') + { + /* ".." becomes "::" */ + PRINT ("::"); + len = 2; + } + else + { + PRINT ("."); + len = 1; + } + } + else + { + /* Print everything before the next escape sequence, at once. */ + for (len = 0; len < ident.ascii_len; len++) + if (ident.ascii[len] == '$' || ident.ascii[len] == '.') + break; + + print_str (rdm, ident.ascii, len); + } + + ident.ascii += len; + ident.ascii_len -= len; + } + + return; + } + + if (!ident.punycode) + { + print_str (rdm, ident.ascii, ident.ascii_len); + return; + } + + len = 0; + cap = 4; + while (cap < ident.ascii_len) + { + cap *= 2; + /* Check for overflows. */ + if ((cap * 4) / 4 != cap) + { + rdm->errored = 1; + return; + } + } + + /* Store the output codepoints as groups of 4 UTF-8 bytes. */ + out = (uint8_t *)malloc (cap * 4); + if (!out) + { + rdm->errored = 1; + return; + } + + /* Populate initial output from ASCII fragment. */ + for (len = 0; len < ident.ascii_len; len++) + { + p = out + 4 * len; + p[0] = 0; + p[1] = 0; + p[2] = 0; + p[3] = ident.ascii[len]; + } + + /* Punycode parameters and initial state. */ + base = 36; + t_min = 1; + t_max = 26; + skew = 38; + damp = 700; + bias = 72; + i = 0; + c = 0x80; + + punycode_pos = 0; + while (punycode_pos < ident.punycode_len) + { + /* Read one delta value. */ + delta = 0; + w = 1; + k = 0; + do + { + k += base; + t = k < bias ? 0 : (k - bias); + if (t < t_min) + t = t_min; + if (t > t_max) + t = t_max; + + if (punycode_pos >= ident.punycode_len) + goto cleanup; + d = ident.punycode[punycode_pos++]; + + if (ISLOWER (d)) + d = d - 'a'; + else if (ISDIGIT (d)) + d = 26 + (d - '0'); + else + { + rdm->errored = 1; + goto cleanup; + } + + delta += d * w; + w *= base - t; + } + while (d >= t); + + /* Compute the new insert position and character. */ + len++; + i += delta; + c += i / len; + i %= len; + + /* Ensure enough space is available. */ + if (cap < len) + { + cap *= 2; + /* Check for overflows. */ + if ((cap * 4) / 4 != cap || cap < len) + { + rdm->errored = 1; + goto cleanup; + } + } + p = (uint8_t *)realloc (out, cap * 4); + if (!p) + { + rdm->errored = 1; + goto cleanup; + } + out = p; + + /* Move the characters after the insert position. */ + p = out + i * 4; + memmove (p + 4, p, (len - i - 1) * 4); + + /* Insert the new character, as UTF-8 bytes. */ + p[0] = c >= 0x10000 ? 0xf0 | (c >> 18) : 0; + p[1] = c >= 0x800 ? (c < 0x10000 ? 0xe0 : 0x80) | ((c >> 12) & 0x3f) : 0; + p[2] = (c < 0x800 ? 0xc0 : 0x80) | ((c >> 6) & 0x3f); + p[3] = 0x80 | (c & 0x3f); + + /* If there are no more deltas, decoding is complete. */ + if (punycode_pos == ident.punycode_len) + break; + + i++; + + /* Perform bias adaptation. */ + delta /= damp; + damp = 2; + + delta += delta / len; + k = 0; + while (delta > ((base - t_min) * t_max) / 2) + { + delta /= base - t_min; + k += base; + } + bias = k + ((base - t_min + 1) * delta) / (delta + skew); + } + + /* Remove all the 0 bytes to leave behind an UTF-8 string. */ + for (i = 0, j = 0; i < len * 4; i++) + if (out[i] != 0) + out[j++] = out[i]; + + print_str (rdm, (const char *)out, j); + +cleanup: + free (out); +} + +/* Print the lifetime according to the previously decoded index. + An index of `0` always refers to `'_`, but starting with `1`, + indices refer to late-bound lifetimes introduced by a binder. */ +static void +print_lifetime_from_index (struct rust_demangler *rdm, uint64_t lt) +{ + char c; + uint64_t depth; + + PRINT ("'"); + if (lt == 0) + { + PRINT ("_"); + return; + } + + depth = rdm->bound_lifetime_depth - lt; + /* Try to print lifetimes alphabetically first. */ + if (depth < 26) + { + c = 'a' + depth; + print_str (rdm, &c, 1); + } + else + { + /* Use `'_123` after running out of letters. */ + PRINT ("_"); + print_uint64 (rdm, depth); + } +} + +/* Demangling functions. */ + +static void demangle_binder (struct rust_demangler *rdm); +static void demangle_path (struct rust_demangler *rdm, int in_value); +static void demangle_generic_arg (struct rust_demangler *rdm); +static void demangle_type (struct rust_demangler *rdm); +static int demangle_path_maybe_open_generics (struct rust_demangler *rdm); +static void demangle_dyn_trait (struct rust_demangler *rdm); +static void demangle_const (struct rust_demangler *rdm); +static void demangle_const_uint (struct rust_demangler *rdm); +static void demangle_const_int (struct rust_demangler *rdm); +static void demangle_const_bool (struct rust_demangler *rdm); +static void demangle_const_char (struct rust_demangler *rdm); + +/* Optionally enter a binder ('G') for late-bound lifetimes, + printing e.g. `for<'a, 'b> `, and make those lifetimes visible + to the caller (via depth level, which the caller should reset). */ +static void +demangle_binder (struct rust_demangler *rdm) +{ + uint64_t i, bound_lifetimes; + + if (rdm->errored) + return; + + bound_lifetimes = parse_opt_integer_62 (rdm, 'G'); + if (bound_lifetimes > 0) + { + PRINT ("for<"); + for (i = 0; i < bound_lifetimes; i++) + { + if (i > 0) + PRINT (", "); + rdm->bound_lifetime_depth++; + print_lifetime_from_index (rdm, 1); + } + PRINT ("> "); + } +} + +static void +demangle_path (struct rust_demangler *rdm, int in_value) +{ + char tag, ns; + int was_skipping_printing; + size_t i, backref, old_next; + uint64_t dis; + struct rust_mangled_ident name; + + if (rdm->errored) + return; + + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + { + ++ rdm->recursion; + if (rdm->recursion > RUST_MAX_RECURSION_COUNT) + /* FIXME: There ought to be a way to report + that the recursion limit has been reached. */ + goto fail_return; + } + + switch (tag = next (rdm)) + { + case 'C': + dis = parse_disambiguator (rdm); + name = parse_ident (rdm); + + print_ident (rdm, name); + if (rdm->verbose) + { + PRINT ("["); + print_uint64_hex (rdm, dis); + PRINT ("]"); + } + break; + case 'N': + ns = next (rdm); + if (!ISLOWER (ns) && !ISUPPER (ns)) + goto fail_return; + + demangle_path (rdm, in_value); + + dis = parse_disambiguator (rdm); + name = parse_ident (rdm); + + if (ISUPPER (ns)) + { + /* Special namespaces, like closures and shims. */ + PRINT ("::{"); + switch (ns) + { + case 'C': + PRINT ("closure"); + break; + case 'S': + PRINT ("shim"); + break; + default: + print_str (rdm, &ns, 1); + } + if (name.ascii || name.punycode) + { + PRINT (":"); + print_ident (rdm, name); + } + PRINT ("#"); + print_uint64 (rdm, dis); + PRINT ("}"); + } + else + { + /* Implementation-specific/unspecified namespaces. */ + + if (name.ascii || name.punycode) + { + PRINT ("::"); + print_ident (rdm, name); + } + } + break; + case 'M': + case 'X': + /* Ignore the `impl`'s own path.*/ + parse_disambiguator (rdm); + was_skipping_printing = rdm->skipping_printing; + rdm->skipping_printing = 1; + demangle_path (rdm, in_value); + rdm->skipping_printing = was_skipping_printing; + /* fallthrough */ + case 'Y': + PRINT ("<"); + demangle_type (rdm); + if (tag != 'M') + { + PRINT (" as "); + demangle_path (rdm, 0); + } + PRINT (">"); + break; + case 'I': + demangle_path (rdm, in_value); + if (in_value) + PRINT ("::"); + PRINT ("<"); + for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++) + { + if (i > 0) + PRINT (", "); + demangle_generic_arg (rdm); + } + PRINT (">"); + break; + case 'B': + backref = parse_integer_62 (rdm); + if (!rdm->skipping_printing) + { + old_next = rdm->next; + rdm->next = backref; + demangle_path (rdm, in_value); + rdm->next = old_next; + } + break; + default: + goto fail_return; + } + goto pass_return; + + fail_return: + rdm->errored = 1; + pass_return: + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + -- rdm->recursion; +} + +static void +demangle_generic_arg (struct rust_demangler *rdm) +{ + uint64_t lt; + if (eat (rdm, 'L')) + { + lt = parse_integer_62 (rdm); + print_lifetime_from_index (rdm, lt); + } + else if (eat (rdm, 'K')) + demangle_const (rdm); + else + demangle_type (rdm); +} + +static const char * +basic_type (char tag) +{ + switch (tag) + { + case 'b': + return "bool"; + case 'c': + return "char"; + case 'e': + return "str"; + case 'u': + return "()"; + case 'a': + return "i8"; + case 's': + return "i16"; + case 'l': + return "i32"; + case 'x': + return "i64"; + case 'n': + return "i128"; + case 'i': + return "isize"; + case 'h': + return "u8"; + case 't': + return "u16"; + case 'm': + return "u32"; + case 'y': + return "u64"; + case 'o': + return "u128"; + case 'j': + return "usize"; + case 'f': + return "f32"; + case 'd': + return "f64"; + case 'z': + return "!"; + case 'p': + return "_"; + case 'v': + return "..."; + + default: + return NULL; + } +} + +static void +demangle_type (struct rust_demangler *rdm) +{ + char tag; + size_t i, old_next, backref; + uint64_t lt, old_bound_lifetime_depth; + const char *basic; + struct rust_mangled_ident abi; + + if (rdm->errored) + return; + + tag = next (rdm); + + basic = basic_type (tag); + if (basic) + { + PRINT (basic); + return; + } + + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + { + ++ rdm->recursion; + if (rdm->recursion > RUST_MAX_RECURSION_COUNT) + /* FIXME: There ought to be a way to report + that the recursion limit has been reached. */ + { + rdm->errored = 1; + -- rdm->recursion; + return; + } + } + + switch (tag) + { + case 'R': + case 'Q': + PRINT ("&"); + if (eat (rdm, 'L')) + { + lt = parse_integer_62 (rdm); + if (lt) + { + print_lifetime_from_index (rdm, lt); + PRINT (" "); + } + } + if (tag != 'R') + PRINT ("mut "); + demangle_type (rdm); + break; + case 'P': + case 'O': + PRINT ("*"); + if (tag != 'P') + PRINT ("mut "); + else + PRINT ("const "); + demangle_type (rdm); + break; + case 'A': + case 'S': + PRINT ("["); + demangle_type (rdm); + if (tag == 'A') + { + PRINT ("; "); + demangle_const (rdm); + } + PRINT ("]"); + break; + case 'T': + PRINT ("("); + for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++) + { + if (i > 0) + PRINT (", "); + demangle_type (rdm); + } + if (i == 1) + PRINT (","); + PRINT (")"); + break; + case 'F': + old_bound_lifetime_depth = rdm->bound_lifetime_depth; + demangle_binder (rdm); + + if (eat (rdm, 'U')) + PRINT ("unsafe "); + + if (eat (rdm, 'K')) + { + if (eat (rdm, 'C')) + { + abi.ascii = "C"; + abi.ascii_len = 1; + } + else + { + abi = parse_ident (rdm); + if (!abi.ascii || abi.punycode) + { + rdm->errored = 1; + goto restore; + } + } + + PRINT ("extern \""); + + /* If the ABI had any `-`, they were replaced with `_`, + so the parts between `_` have to be re-joined with `-`. */ + for (i = 0; i < abi.ascii_len; i++) + { + if (abi.ascii[i] == '_') + { + print_str (rdm, abi.ascii, i); + PRINT ("-"); + abi.ascii += i + 1; + abi.ascii_len -= i + 1; + i = 0; + } + } + print_str (rdm, abi.ascii, abi.ascii_len); + + PRINT ("\" "); + } + + PRINT ("fn("); + for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++) + { + if (i > 0) + PRINT (", "); + demangle_type (rdm); + } + PRINT (")"); + + if (eat (rdm, 'u')) + { + /* Skip printing the return type if it's 'u', i.e. `()`. */ + } + else + { + PRINT (" -> "); + demangle_type (rdm); + } + + /* Restore `bound_lifetime_depth` to outside the binder. */ + restore: + rdm->bound_lifetime_depth = old_bound_lifetime_depth; + break; + case 'D': + PRINT ("dyn "); + + old_bound_lifetime_depth = rdm->bound_lifetime_depth; + demangle_binder (rdm); + + for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++) + { + if (i > 0) + PRINT (" + "); + demangle_dyn_trait (rdm); + } + + /* Restore `bound_lifetime_depth` to outside the binder. */ + rdm->bound_lifetime_depth = old_bound_lifetime_depth; + + if (!eat (rdm, 'L')) + { + rdm->errored = 1; + return; + } + lt = parse_integer_62 (rdm); + if (lt) + { + PRINT (" + "); + print_lifetime_from_index (rdm, lt); + } + break; + case 'B': + backref = parse_integer_62 (rdm); + if (!rdm->skipping_printing) + { + old_next = rdm->next; + rdm->next = backref; + demangle_type (rdm); + rdm->next = old_next; + } + break; + default: + /* Go back to the tag, so `demangle_path` also sees it. */ + rdm->next--; + demangle_path (rdm, 0); + } + + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + -- rdm->recursion; +} + +/* A trait in a trait object may have some "existential projections" + (i.e. associated type bindings) after it, which should be printed + in the `<...>` of the trait, e.g. `dyn Trait`. + To this end, this method will keep the `<...>` of an 'I' path + open, by omitting the `>`, and return `Ok(true)` in that case. */ +static int +demangle_path_maybe_open_generics (struct rust_demangler *rdm) +{ + int open; + size_t i, old_next, backref; + + open = 0; + + if (rdm->errored) + return open; + + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + { + ++ rdm->recursion; + if (rdm->recursion > RUST_MAX_RECURSION_COUNT) + { + /* FIXME: There ought to be a way to report + that the recursion limit has been reached. */ + rdm->errored = 1; + goto end_of_func; + } + } + + if (eat (rdm, 'B')) + { + backref = parse_integer_62 (rdm); + if (!rdm->skipping_printing) + { + old_next = rdm->next; + rdm->next = backref; + open = demangle_path_maybe_open_generics (rdm); + rdm->next = old_next; + } + } + else if (eat (rdm, 'I')) + { + demangle_path (rdm, 0); + PRINT ("<"); + open = 1; + for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++) + { + if (i > 0) + PRINT (", "); + demangle_generic_arg (rdm); + } + } + else + demangle_path (rdm, 0); + + end_of_func: + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + -- rdm->recursion; + + return open; +} + +static void +demangle_dyn_trait (struct rust_demangler *rdm) +{ + int open; + struct rust_mangled_ident name; + + if (rdm->errored) + return; + + open = demangle_path_maybe_open_generics (rdm); + + while (eat (rdm, 'p')) + { + if (!open) + PRINT ("<"); + else + PRINT (", "); + open = 1; + + name = parse_ident (rdm); + print_ident (rdm, name); + PRINT (" = "); + demangle_type (rdm); + } + + if (open) + PRINT (">"); +} + +static void +demangle_const (struct rust_demangler *rdm) +{ + char ty_tag; + size_t old_next, backref; + + if (rdm->errored) + return; + + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + { + ++ rdm->recursion; + if (rdm->recursion > RUST_MAX_RECURSION_COUNT) + /* FIXME: There ought to be a way to report + that the recursion limit has been reached. */ + goto fail_return; + } + + if (eat (rdm, 'B')) + { + backref = parse_integer_62 (rdm); + if (!rdm->skipping_printing) + { + old_next = rdm->next; + rdm->next = backref; + demangle_const (rdm); + rdm->next = old_next; + } + goto pass_return; + } + + ty_tag = next (rdm); + switch (ty_tag) + { + /* Placeholder. */ + case 'p': + PRINT ("_"); + goto pass_return; + + /* Unsigned integer types. */ + case 'h': + case 't': + case 'm': + case 'y': + case 'o': + case 'j': + demangle_const_uint (rdm); + break; + + /* Signed integer types. */ + case 'a': + case 's': + case 'l': + case 'x': + case 'n': + case 'i': + demangle_const_int (rdm); + break; + + /* Boolean. */ + case 'b': + demangle_const_bool (rdm); + break; + + /* Character. */ + case 'c': + demangle_const_char (rdm); + break; + + default: + goto fail_return; + } + + if (!rdm->errored && rdm->verbose) + { + PRINT (": "); + PRINT (basic_type (ty_tag)); + } + goto pass_return; + + fail_return: + rdm->errored = 1; + pass_return: + if (rdm->recursion != RUST_NO_RECURSION_LIMIT) + -- rdm->recursion; +} + +static void +demangle_const_uint (struct rust_demangler *rdm) +{ + size_t hex_len; + uint64_t value; + + if (rdm->errored) + return; + + hex_len = parse_hex_nibbles (rdm, &value); + + if (hex_len > 16) + { + /* Print anything that doesn't fit in `uint64_t` verbatim. */ + PRINT ("0x"); + print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len); + } + else if (hex_len > 0) + print_uint64 (rdm, value); + else + rdm->errored = 1; +} + +static void +demangle_const_int (struct rust_demangler *rdm) +{ + if (eat (rdm, 'n')) + PRINT ("-"); + demangle_const_uint (rdm); +} + +static void +demangle_const_bool (struct rust_demangler *rdm) +{ + uint64_t value; + + if (parse_hex_nibbles (rdm, &value) != 1) + { + rdm->errored = 1; + return; + } + + if (value == 0) + PRINT ("false"); + else if (value == 1) + PRINT ("true"); + else + rdm->errored = 1; +} + +static void +demangle_const_char (struct rust_demangler *rdm) +{ + size_t hex_len; + uint64_t value; + + hex_len = parse_hex_nibbles (rdm, &value); + + if (hex_len == 0 || hex_len > 8) + { + rdm->errored = 1; + return; + } + + /* Match Rust's character "debug" output as best as we can. */ + PRINT ("'"); + if (value == '\t') + PRINT ("\\t"); + else if (value == '\r') + PRINT ("\\r"); + else if (value == '\n') + PRINT ("\\n"); + else if (value > ' ' && value < '~') + { + /* Rust also considers many non-ASCII codepoints to be printable, but + that logic is not easily ported to C. */ + char c = value; + print_str (rdm, &c, 1); + } + else + { + PRINT ("\\u{"); + print_uint64_hex (rdm, value); + PRINT ("}"); + } + PRINT ("'"); +} + +/* A legacy hash is the prefix "h" followed by 16 lowercase hex digits. + The hex digits must contain at least 5 distinct digits. */ +static int +is_legacy_prefixed_hash (struct rust_mangled_ident ident) +{ + uint16_t seen; + int nibble; + size_t i, count; + + if (ident.ascii_len != 17 || ident.ascii[0] != 'h') + return 0; + + seen = 0; + for (i = 0; i < 16; i++) + { + nibble = decode_lower_hex_nibble (ident.ascii[1 + i]); + if (nibble < 0) + return 0; + seen |= (uint16_t)1 << nibble; + } + + /* Count how many distinct digits were seen. */ + count = 0; + while (seen) + { + if (seen & 1) + count++; + seen >>= 1; + } + + return count >= 5; +} + +int +rust_demangle_callback (const char *mangled, int options, + demangle_callbackref callback, void *opaque) +{ + const char *p; + struct rust_demangler rdm; + struct rust_mangled_ident ident; + + rdm.sym = mangled; + rdm.sym_len = 0; + + rdm.callback_opaque = opaque; + rdm.callback = callback; + + rdm.next = 0; + rdm.errored = 0; + rdm.skipping_printing = 0; + rdm.verbose = (options & DMGL_VERBOSE) != 0; + rdm.version = 0; + rdm.recursion = (options & DMGL_NO_RECURSE_LIMIT) ? RUST_NO_RECURSION_LIMIT : 0; + rdm.bound_lifetime_depth = 0; + + /* Rust symbols always start with _R (v0) or _ZN (legacy). */ + if (rdm.sym[0] == '_' && rdm.sym[1] == 'R') + rdm.sym += 2; + else if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N') + { + rdm.sym += 3; + rdm.version = -1; + } + else + return 0; + + /* Paths (v0) always start with uppercase characters. */ + if (rdm.version != -1 && !ISUPPER (rdm.sym[0])) + return 0; + + /* Rust symbols (v0) use only [_0-9a-zA-Z] characters. */ + for (p = rdm.sym; *p; p++) + { + /* Rust v0 symbols can have '.' suffixes, ignore those. */ + if (rdm.version == 0 && *p == '.') + break; + + rdm.sym_len++; + + if (*p == '_' || ISALNUM (*p)) + continue; + + /* Legacy Rust symbols can also contain [.:$] characters. + Or @ in the .suffix (which will be skipped, see below). */ + if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':' + || *p == '@')) + continue; + + return 0; + } + + /* Legacy Rust symbols need to be handled separately. */ + if (rdm.version == -1) + { + /* Legacy Rust symbols always end with E. But can be followed by a + .suffix (which we want to ignore). */ + int dot_suffix = 1; + while (rdm.sym_len > 0 && + !(dot_suffix && rdm.sym[rdm.sym_len - 1] == 'E')) + { + dot_suffix = rdm.sym[rdm.sym_len - 1] == '.'; + rdm.sym_len--; + } + + if (!(rdm.sym_len > 0 && rdm.sym[rdm.sym_len - 1] == 'E')) + return 0; + rdm.sym_len--; + + /* Legacy Rust symbols also always end with a path segment + that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'. + This early check, before any parse_ident calls, should + quickly filter out most C++ symbols unrelated to Rust. */ + if (!(rdm.sym_len > 19 + && !memcmp (&rdm.sym[rdm.sym_len - 19], "17h", 3))) + return 0; + + do + { + ident = parse_ident (&rdm); + if (rdm.errored || !ident.ascii) + return 0; + } + while (rdm.next < rdm.sym_len); + + /* The last path segment should be the hash. */ + if (!is_legacy_prefixed_hash (ident)) + return 0; + + /* Reset the state for a second pass, to print the symbol. */ + rdm.next = 0; + if (!rdm.verbose && rdm.sym_len > 19) + { + /* Hide the last segment, containing the hash, if not verbose. */ + rdm.sym_len -= 19; + } + + do + { + if (rdm.next > 0) + print_str (&rdm, "::", 2); + + ident = parse_ident (&rdm); + print_ident (&rdm, ident); + } + while (rdm.next < rdm.sym_len); + } + else + { + demangle_path (&rdm, 1); + + /* Skip instantiating crate. */ + if (!rdm.errored && rdm.next < rdm.sym_len) + { + rdm.skipping_printing = 1; + demangle_path (&rdm, 0); + } + + /* It's an error to not reach the end. */ + rdm.errored |= rdm.next != rdm.sym_len; + } + + return !rdm.errored; +} + +/* Growable string buffers. */ +struct str_buf +{ + char *ptr; + size_t len; + size_t cap; + int errored; +}; + +static void +str_buf_reserve (struct str_buf *buf, size_t extra) +{ + size_t available, min_new_cap, new_cap; + char *new_ptr; + + /* Allocation failed before. */ + if (buf->errored) + return; + + available = buf->cap - buf->len; + + if (extra <= available) + return; + + min_new_cap = buf->cap + (extra - available); + + /* Check for overflows. */ + if (min_new_cap < buf->cap) + { + buf->errored = 1; + return; + } + + new_cap = buf->cap; + + if (new_cap == 0) + new_cap = 4; + + /* Double capacity until sufficiently large. */ + while (new_cap < min_new_cap) + { + new_cap *= 2; + + /* Check for overflows. */ + if (new_cap < buf->cap) + { + buf->errored = 1; + return; + } + } + + new_ptr = (char *)realloc (buf->ptr, new_cap); + if (new_ptr == NULL) + { + free (buf->ptr); + buf->ptr = NULL; + buf->len = 0; + buf->cap = 0; + buf->errored = 1; + } + else + { + buf->ptr = new_ptr; + buf->cap = new_cap; + } +} + +static void +str_buf_append (struct str_buf *buf, const char *data, size_t len) +{ + str_buf_reserve (buf, len); + if (buf->errored) + return; + + memcpy (buf->ptr + buf->len, data, len); + buf->len += len; +} + +static void +str_buf_demangle_callback (const char *data, size_t len, void *opaque) +{ + str_buf_append ((struct str_buf *)opaque, data, len); +} + +char * +rust_demangle (const char *mangled, int options) +{ + struct str_buf out; + int success; + + out.ptr = NULL; + out.len = 0; + out.cap = 0; + out.errored = 0; + + success = rust_demangle_callback (mangled, options, + str_buf_demangle_callback, &out); + + if (!success) + { + free (out.ptr); + return NULL; + } + + str_buf_append (&out, "\0", 1); + return out.ptr; +} + +void +rust_demangle_replace (char *mangled) +{ + struct str_buf out; + int success; + + out.ptr = NULL; + out.len = 0; + out.cap = 0; + out.errored = 0; + + success = rust_demangle_callback(mangled, 0, str_buf_demangle_callback, &out); + + if (!success) { + free (out.ptr); + return; + } + + str_buf_append (&out, "\0", 1); + + size_t len = strlen(mangled); + if (len > out.len) { + memcpy(mangled, out.ptr, out.len); + free(out.ptr); + } +} diff --git a/include/hw/core/rust_demangle.h b/include/hw/core/rust_demangle.h new file mode 100644 index 0000000000000..8aa759907ea0b --- /dev/null +++ b/include/hw/core/rust_demangle.h @@ -0,0 +1,58 @@ +/* Demangler for the Rust programming language + Copyright (C) 2016-2019 Free Software Foundation, Inc. + Written by David Tolnay (dtolnay@gmail.com). + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +In addition to the permissions in the GNU Library General Public +License, the Free Software Foundation gives you unlimited permission +to link the compiled version of this file into combinations with other +programs, and to distribute those combinations without any restriction +coming from the use of this file. (The Library Public License +restrictions do apply in other respects; for example, they cover +modification of the file, and distribution when not linked into a +combined executable.) + +Libiberty is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. +If not, see . */ + +#ifndef QEMU_RUST_DEMANGLE_H +#define QEMU_RUST_DEMANGLE_H + +#define DMGL_NO_OPTS 0 +#define DMGL_PARAMS (1 << 0) +#define DMGL_ANSI (1 << 1) +#define DMGL_JAVA (1 << 2) +#define DMGL_VERBOSE (1 << 3) +#define DMGL_TYPES (1 << 4) +#define DMGL_RET_POSTFIX (1 << 5) +#define DMGL_RET_DROP (1 << 6) +#define DMGL_AUTO (1 << 8) +#define DMGL_GNU_V3 (1 << 14) +#define DMGL_GNAT (1 << 15) +#define DMGL_DLANG (1 << 16) +#define DMGL_RUST (1 << 17) +#define DMGL_STYLE_MASK (DMGL_AUTO|DMGL_GNU_V3|DMGL_JAVA|DMGL_GNAT|DMGL_DLANG|DMGL_RUST) +#define DMGL_NO_RECURSE_LIMIT (1 << 18) +#define DEMANGLE_RECURSION_LIMIT 2048 + +/* Callback typedef for allocation-less demangler interfaces. */ +typedef void (*demangle_callbackref)(const char *, size_t, void *); + +int rust_demangle_callback(const char *mangled, int options, + demangle_callbackref callback, void *opaque); + +char * rust_demangle(const char *mangled, int options); +void rust_demangle_replace(char *mangled); + +#endif // QEMU_RUST_DEMANGLE_H diff --git a/include/hw/elf_ops.h.inc b/include/hw/elf_ops.h.inc index 9c35d1b9da6c3..bf758ac27ec0a 100644 --- a/include/hw/elf_ops.h.inc +++ b/include/hw/elf_ops.h.inc @@ -321,7 +321,7 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd, uint32_t *pflags, int elf_machine, int clear_lsb, int data_swab, AddressSpace *as, bool load_rom, - symbol_fn_t sym_cb) + symbol_fn_t sym_cb, bool skip_nosz) { struct elfhdr ehdr; struct elf_phdr *phdr = NULL, *ph; @@ -560,7 +560,7 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd, * We need to zero'ify the space that is not copied * from file */ - if (file_size < mem_size) { + if (file_size < mem_size && !skip_nosz) { res = address_space_set(as ? as : &address_space_memory, addr + file_size, 0, mem_size - file_size, @@ -625,3 +625,68 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd, g_free(phdr); return ret; } + +static int glue(load_elf_symbols, SZ)(const char *name, int fd, int must_swab, + int elf_machine, int clear_lsb) +{ + struct elfhdr ehdr; + int ret = ELF_LOAD_FAILED; + + if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) { + goto fail; + } + if (must_swab) { + glue(bswap_ehdr, SZ)(&ehdr); + } + + if (elf_machine <= EM_NONE) { + /* The caller didn't specify an ARCH, we can figure it out */ + elf_machine = ehdr.e_machine; + } + + switch (elf_machine) { + case EM_PPC64: + if (ehdr.e_machine != EM_PPC64) { + if (ehdr.e_machine != EM_PPC) { + ret = ELF_LOAD_WRONG_ARCH; + goto fail; + } + } + break; + case EM_X86_64: + if (ehdr.e_machine != EM_X86_64) { + if (ehdr.e_machine != EM_386) { + ret = ELF_LOAD_WRONG_ARCH; + goto fail; + } + } + break; + case EM_MICROBLAZE: + if (ehdr.e_machine != EM_MICROBLAZE) { + if (ehdr.e_machine != EM_MICROBLAZE_OLD) { + ret = ELF_LOAD_WRONG_ARCH; + goto fail; + } + } + break; + case EM_MIPS: + case EM_NANOMIPS: + if ((ehdr.e_machine != EM_MIPS) && + (ehdr.e_machine != EM_NANOMIPS)) { + ret = ELF_LOAD_WRONG_ARCH; + goto fail; + } + break; + default: + if (elf_machine != ehdr.e_machine) { + ret = ELF_LOAD_WRONG_ARCH; + goto fail; + } + } + + glue(load_symbols, SZ)(&ehdr, fd, must_swab, clear_lsb, NULL); + ret = 0; + +fail: + return ret; +} diff --git a/include/hw/loader.h b/include/hw/loader.h index d035e72748a98..9dea108b8a495 100644 --- a/include/hw/loader.h +++ b/include/hw/loader.h @@ -133,6 +133,8 @@ const char *load_elf_strerror(ssize_t error); * is used if nothing is supplied here. * @load_rom : Load ELF binary as ROM * @sym_cb: Callback function for symbol table entries + * @skip_nosz: whether to initialize section area not stored in ELF file such as + * NOBITS sections. * * Load an ELF file's contents to the emulated system's address space. * Clients may optionally specify a callback to perform address @@ -148,6 +150,19 @@ const char *load_elf_strerror(ssize_t error); typedef void (*symbol_fn_t)(const char *st_name, int st_info, uint64_t st_value, uint64_t st_size); +ssize_t load_elf_ram_sym_nosz(const char *filename, + uint64_t (*elf_note_fn)(void *, void *, bool), + uint64_t (*translate_fn)(void *, uint64_t), + void *translate_opaque, uint64_t *pentry, + uint64_t *lowaddr, uint64_t *highaddr, + uint32_t *pflags, int big_endian, int elf_machine, + int clear_lsb, int data_swab, + AddressSpace *as, bool load_rom, + symbol_fn_t sym_cb, bool skip_nosz); + +/** load_elf_ram_sym: + * Same as load_elf_ram_sym_nosz(), with skip_nosz disabled + */ ssize_t load_elf_ram_sym(const char *filename, uint64_t (*elf_note_fn)(void *, void *, bool), uint64_t (*translate_fn)(void *, uint64_t), @@ -191,6 +206,9 @@ ssize_t load_elf(const char *filename, */ void load_elf_hdr(const char *filename, void *hdr, bool *is64, Error **errp); +int load_elf_sym(const char *filename, int big_endian, int elf_machine, + int clear_lsb); + ssize_t load_aout(const char *filename, hwaddr addr, int max_sz, bool big_endian, hwaddr target_page_size); @@ -359,4 +377,9 @@ typedef struct RomGap { */ RomGap rom_find_largest_gap_between(hwaddr base, size_t size); +/** + * rom_load: Load all registered ROMs + */ +void rom_load(void); + #endif From 23358ef8652d4ec845f5b31d622fdf206d3123fb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 19 Jan 2026 14:49:07 +0000 Subject: [PATCH 04/18] [ot] target/riscv: add support for impl-defined initial PMP config Signed-off-by: Emmanuel Blot --- target/riscv/cpu.c | 24 ++++++++++++++++++++++++ target/riscv/cpu_cfg_fields.h.inc | 11 +++++++++-- target/riscv/csr.c | 27 ++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 73d4280d7c84a..5f42ef0134220 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -795,6 +795,25 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type) if (kvm_enabled()) { kvm_riscv_reset_vcpu(cpu); } + + /* default physical memory protection configuration */ + const RISCVCPUConfig *cfg = &cpu->cfg; + g_assert(cfg->pmp_cfg_count <= MAX_RISCV_PMPS); + g_assert(cfg->pmp_addr_count <= MAX_RISCV_PMPS); + for (i = 0; i < MAX_RISCV_PMPS; i++) { + env->pmp_state.pmp[i].cfg_reg = + i < cfg->pmp_cfg_count ? cfg->pmp_cfg[i] : 0; + } + for (i = 0; i < MAX_RISCV_PMPS; i++) { + env->pmp_state.pmp[i].addr_reg = + i < cfg->pmp_addr_count ? (target_ulong)cfg->pmp_addr[i] : 0; + } + for (i = 0; i < MAX_RISCV_PMPS; i++) { + pmp_update_rule_addr(env, i); + } + pmp_update_rule_nums(env); + + env->mseccfg = (target_ulong)cfg->mseccfg; #endif } @@ -2662,6 +2681,11 @@ static const Property riscv_cpu_properties[] = { #ifndef CONFIG_USER_ONLY DEFINE_PROP_UINT64("resetvec", RISCVCPU, env.resetvec, DEFAULT_RSTVEC), + DEFINE_PROP_UINT64("mseccfg", RISCVCPU, cfg.mseccfg, 0u), + DEFINE_PROP_ARRAY("pmp_cfg", RISCVCPU, cfg.pmp_cfg_count, cfg.pmp_cfg, + qdev_prop_uint8, uint8_t), + DEFINE_PROP_ARRAY("pmp_addr", RISCVCPU, cfg.pmp_addr_count, cfg.pmp_addr, + qdev_prop_uint64, uint64_t), DEFINE_PROP_UINT64("rnmi-interrupt-vector", RISCVCPU, env.rnmi_irqvec, DEFAULT_RNMI_IRQVEC), DEFINE_PROP_UINT64("rnmi-exception-vector", RISCVCPU, env.rnmi_excpvec, diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index a154ecdc792b3..0a027156f3bc7 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -165,8 +165,15 @@ TYPED_FIELD(uint16_t, elen, 0) TYPED_FIELD(uint16_t, cbom_blocksize, 0) TYPED_FIELD(uint16_t, cbop_blocksize, 0) TYPED_FIELD(uint16_t, cboz_blocksize, 0) -TYPED_FIELD(uint8_t, pmp_regions, 0) -TYPED_FIELD(uint32_t, pmp_granularity, 0) + +/* physical memory protection HW configuration */ +TYPED_FIELD(uint8_t, pmp_regions, 0) +TYPED_FIELD(uint32_t, pmp_granularity, 0) +TYPED_FIELD(uint8_t *, pmp_cfg, NULL) +TYPED_FIELD(uint32_t, pmp_cfg_count, 0) +TYPED_FIELD(uint64_t *, pmp_addr, NULL) +TYPED_FIELD(uint32_t, pmp_addr_count, 0) +TYPED_FIELD(uint64_t, mseccfg, 0) TYPED_FIELD(int8_t, max_satp_mode, -1) diff --git a/target/riscv/csr.c b/target/riscv/csr.c index 5c91658c3dc41..c54f1777e1eaf 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -775,6 +775,15 @@ static RISCVException have_mseccfg(CPURISCVState *env, int csrno) return RISCV_EXCP_ILLEGAL_INST; } +static RISCVException have_mseccfg32(CPURISCVState *env, int csrno) +{ + if (riscv_cpu_mxl(env) != MXL_RV32) { + return RISCV_EXCP_ILLEGAL_INST; + } + + return have_mseccfg(env, csrno); +} + static RISCVException debug(CPURISCVState *env, int csrno) { if (riscv_cpu_cfg(env)->debug) { @@ -5296,6 +5305,20 @@ static RISCVException write_mseccfg(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +static RISCVException read_mseccfgh(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = 0u; + return RISCV_EXCP_NONE; +} + +static RISCVException write_mseccfgh(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + /* WARL: ignore all bits */ + return RISCV_EXCP_NONE; +} + static RISCVException read_pmpcfg(CPURISCVState *env, int csrno, target_ulong *val) { @@ -6164,7 +6187,9 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_VSIPH] = { "vsiph", aia_hmode32, NULL, NULL, rmw_vsiph }, /* Physical Memory Protection */ - [CSR_MSECCFG] = { "mseccfg", have_mseccfg, read_mseccfg, write_mseccfg, + [CSR_MSECCFG] = { "mseccfg", have_mseccfg, read_mseccfg, write_mseccfg, + .min_priv_ver = PRIV_VERSION_1_11_0 }, + [CSR_MSECCFGH] = { "mseccfgh", have_mseccfg32, read_mseccfgh, write_mseccfgh, .min_priv_ver = PRIV_VERSION_1_11_0 }, [CSR_PMPCFG0] = { "pmpcfg0", pmp, read_pmpcfg, write_pmpcfg }, [CSR_PMPCFG1] = { "pmpcfg1", pmp, read_pmpcfg, write_pmpcfg }, From 4efe4e7b1a2f4cd20e1e8167f07f32af5b5d4bf8 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 31 Mar 2025 17:32:19 +0200 Subject: [PATCH 05/18] [ot] target/riscv: cpu: make get_physical_address a virtual function. This changes enables providing custom address translation engine. OpenTitan does not have an MMU, but support a custom virtual remapper. Signed-off-by: Emmanuel Blot --- target/riscv/cpu.c | 1 + target/riscv/cpu.h | 18 ++++++++++ target/riscv/cpu_helper.c | 71 ++++++++++++++++++++++----------------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 5f42ef0134220..386692778c843 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2765,6 +2765,7 @@ static void riscv_cpu_common_class_init(ObjectClass *c, const void *data) #ifndef CONFIG_USER_ONLY cc->sysemu_ops = &riscv_sysemu_ops; cc->get_arch_id = riscv_get_arch_id; + mcc->riscv_get_physical_address = riscv_get_physical_address; #endif cc->gdb_arch_name = riscv_gdb_arch_name; #ifdef CONFIG_TCG diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 36e7f100374d8..b7bcc718cdfd1 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -407,6 +407,10 @@ struct CPUArchState { * translation active. */ bool two_stage_lookup; + + /* Whether to use virtual address for PMP (default: physical address) */ + bool vaddr_pmp; + /* * Signals whether the current exception occurred while doing two-stage * address translation for the VS-stage page table walk. @@ -580,6 +584,14 @@ struct RISCVCPUClass { DeviceRealize parent_realize; ResettablePhases parent_phases; + + int (*riscv_get_physical_address)(CPURISCVState *env, hwaddr *physical, + int *ret_prot, vaddr addr, + target_ulong *fault_pte_addr, + int access_type, int mmu_idx, + bool first_stage, bool two_stage, + bool is_debug, bool is_probe); + RISCVCPUDef *def; }; @@ -626,6 +638,12 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, char *riscv_isa_string(RISCVCPU *cpu); int riscv_cpu_max_xlen(RISCVCPUClass *mcc); bool riscv_cpu_option_set(const char *optname); +int riscv_get_physical_address(CPURISCVState *env, hwaddr *physical, + int *ret_prot, vaddr addr, + target_ulong *fault_pte_addr, + int access_type, int mmu_idx, + bool first_stage, bool two_stage, + bool is_debug, bool is_probe); #ifndef CONFIG_USER_ONLY void riscv_cpu_do_interrupt(CPUState *cpu); diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index c4fb68b5de8a3..65f8193db41a2 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -1163,7 +1163,8 @@ static bool check_svukte_addr(CPURISCVState *env, vaddr addr) } /* - * get_physical_address - get the physical address for this virtual address + * riscv_get_physical_address - get the physical address for this virtual + * address * * Do a page table walk to obtain the physical address corresponding to a * virtual address. Returns 0 if the translation was successful @@ -1184,12 +1185,12 @@ static bool check_svukte_addr(CPURISCVState *env, vaddr addr) * @two_stage: Are we going to perform two stage translation * @is_debug: Is this access from a debugger or the monitor? */ -static int get_physical_address(CPURISCVState *env, hwaddr *physical, - int *ret_prot, vaddr addr, - target_ulong *fault_pte_addr, - int access_type, int mmu_idx, - bool first_stage, bool two_stage, - bool is_debug, bool is_probe) +int riscv_get_physical_address(CPURISCVState *env, hwaddr *physical, + int *ret_prot, vaddr addr, + target_ulong *fault_pte_addr, + int access_type, int mmu_idx, + bool first_stage, bool two_stage, + bool is_debug, bool is_probe) { /* * NOTE: the env->pc value visible here will not be @@ -1339,10 +1340,10 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, hwaddr vbase; /* Do the second stage translation on the base PTE address. */ - int vbase_ret = get_physical_address(env, &vbase, &vbase_prot, - base, NULL, MMU_DATA_LOAD, - MMUIdx_U, false, true, - is_debug, false); + int vbase_ret = riscv_get_physical_address(env, &vbase, &vbase_prot, + base, NULL, MMU_DATA_LOAD, + MMUIdx_U, false, true, + is_debug, false); if (vbase_ret != TRANSLATE_SUCCESS) { if (fault_pte_addr) { @@ -1660,19 +1661,22 @@ static void raise_mmu_exception(CPURISCVState *env, target_ulong address, hwaddr riscv_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) { RISCVCPU *cpu = RISCV_CPU(cs); + RISCVCPUClass *cc = RISCV_CPU_CLASS(cs->cc); CPURISCVState *env = &cpu->env; hwaddr phys_addr; int prot; int mmu_idx = riscv_env_mmu_index(&cpu->env, false); - if (get_physical_address(env, &phys_addr, &prot, addr, NULL, 0, mmu_idx, - true, env->virt_enabled, true, false)) { + if (cc->riscv_get_physical_address(env, &phys_addr, &prot, addr, NULL, 0, + mmu_idx, true, env->virt_enabled, true, + false)) { return -1; } if (env->virt_enabled) { - if (get_physical_address(env, &phys_addr, &prot, phys_addr, NULL, - 0, MMUIdx_U, false, true, true, false)) { + if (cc->riscv_get_physical_address(env, &phys_addr, &prot, phys_addr, + NULL, 0, MMUIdx_U, false, true, true, + false)) { return -1; } } @@ -1763,6 +1767,7 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, bool probe, uintptr_t retaddr) { RISCVCPU *cpu = RISCV_CPU(cs); + RISCVCPUClass *cc = RISCV_CPU_CLASS(cs->cc); CPURISCVState *env = &cpu->env; vaddr im_address; hwaddr pa = 0; @@ -1784,9 +1789,10 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, pmu_tlb_fill_incr_ctr(cpu, access_type); if (two_stage_lookup) { /* Two stage lookup */ - ret = get_physical_address(env, &pa, &prot, address, - &env->guest_phys_fault_addr, access_type, - mmu_idx, true, true, false, probe); + ret = cc->riscv_get_physical_address(env, &pa, &prot, address, + &env->guest_phys_fault_addr, + access_type, mmu_idx, true, true, + false, probe); /* * A G-stage exception may be triggered during two state lookup. @@ -1807,9 +1813,10 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, /* Second stage lookup */ im_address = pa; - ret = get_physical_address(env, &pa, &prot2, im_address, NULL, - access_type, MMUIdx_U, false, true, - false, probe); + ret = cc->riscv_get_physical_address(env, &pa, &prot2, im_address, + NULL, access_type, MMUIdx_U, + false, true, + false, probe); qemu_log_mask(CPU_LOG_MMU, "%s 2nd-stage address=%" VADDR_PRIx @@ -1820,14 +1827,16 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, prot &= prot2; if (ret == TRANSLATE_SUCCESS) { - ret = get_physical_address_pmp(env, &prot_pmp, pa, + hwaddr pmp_addr = env->vaddr_pmp ? address : pa; + + ret = get_physical_address_pmp(env, &prot_pmp, pmp_addr, size, access_type, mode); - tlb_size = pmp_get_tlb_size(env, pa); + tlb_size = pmp_get_tlb_size(env, pmp_addr); qemu_log_mask(CPU_LOG_MMU, "%s PMP address=" HWADDR_FMT_plx " ret %d prot" " %d tlb_size %" HWADDR_PRIu "\n", - __func__, pa, ret, prot_pmp, tlb_size); + __func__, pmp_addr, ret, prot_pmp, tlb_size); prot &= prot_pmp; } else { @@ -1845,9 +1854,9 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, } } else { /* Single stage lookup */ - ret = get_physical_address(env, &pa, &prot, address, NULL, - access_type, mmu_idx, true, false, false, - probe); + ret = cc->riscv_get_physical_address(env, &pa, &prot, address, NULL, + access_type, mmu_idx, true, false, + false, probe); qemu_log_mask(CPU_LOG_MMU, "%s address=%" VADDR_PRIx " ret %d physical " @@ -1855,14 +1864,16 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, __func__, address, ret, pa, prot); if (ret == TRANSLATE_SUCCESS) { - ret = get_physical_address_pmp(env, &prot_pmp, pa, + hwaddr pmp_addr = env->vaddr_pmp ? address : pa; + + ret = get_physical_address_pmp(env, &prot_pmp, pmp_addr, size, access_type, mode); - tlb_size = pmp_get_tlb_size(env, pa); + tlb_size = pmp_get_tlb_size(env, pmp_addr); qemu_log_mask(CPU_LOG_MMU, "%s PMP address=" HWADDR_FMT_plx " ret %d prot" " %d tlb_size %" HWADDR_PRIu "\n", - __func__, pa, ret, prot_pmp, tlb_size); + __func__, pmp_addr, ret, prot_pmp, tlb_size); prot &= prot_pmp; } From 948e46dac76597ec77e0176c9c87ed858c23a425 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 7 Jan 2026 12:19:21 +0000 Subject: [PATCH 06/18] [ot] hw/intc: make sifive PLIC aware of edge-triggered interrupts Signed-off-by: Emmanuel Blot --- hw/intc/sifive_plic.c | 50 ++++++++++++++++++++++++++++++----- hw/intc/trace-events | 7 +++++ hw/riscv/microchip_pfsoc.c | 3 ++- hw/riscv/shakti_c.c | 3 ++- hw/riscv/sifive_e.c | 3 ++- hw/riscv/sifive_u.c | 3 ++- hw/riscv/virt.c | 3 ++- include/hw/intc/sifive_plic.h | 5 +++- 8 files changed, 65 insertions(+), 12 deletions(-) diff --git a/hw/intc/sifive_plic.c b/hw/intc/sifive_plic.c index 3160b216fdc97..80b6a4e131960 100644 --- a/hw/intc/sifive_plic.c +++ b/hw/intc/sifive_plic.c @@ -31,6 +31,7 @@ #include "migration/vmstate.h" #include "hw/irq.h" #include "system/kvm.h" +#include "trace.h" static bool addr_between(uint32_t addr, uint32_t base, uint32_t num) { @@ -62,14 +63,32 @@ static uint32_t atomic_set_masked(uint32_t *a, uint32_t mask, uint32_t value) return old; } +static void sifive_plic_set_level(SiFivePLICState *plic, int irq, bool level) +{ + atomic_set_masked(&plic->level[irq >> 5], 1 << (irq & 31), -!!level); +} + +static bool sifive_plic_get_level(SiFivePLICState *plic, int irq) +{ + return (plic->level[irq >> 5] & (1 << (irq & 31))) != 0; +} + static void sifive_plic_set_pending(SiFivePLICState *plic, int irq, bool level) { - atomic_set_masked(&plic->pending[irq >> 5], 1 << (irq & 31), -!!level); + uint32_t old; + old = atomic_set_masked(&plic->pending[irq >> 5], 1 << (irq & 31), -level); + if ((old >> (irq & 31)) ^ level) { + trace_sifive_plic_set_pending(irq, level); + } } static void sifive_plic_set_claimed(SiFivePLICState *plic, int irq, bool level) { - atomic_set_masked(&plic->claimed[irq >> 5], 1 << (irq & 31), -!!level); + uint32_t old; + old = atomic_set_masked(&plic->claimed[irq >> 5], 1 << (irq & 31), -level); + if ((old >> (irq & 31)) ^ level) { + trace_sifive_plic_set_claimed(irq, level); + } } static uint32_t sifive_plic_claimed(SiFivePLICState *plic, uint32_t addrid) @@ -124,9 +143,11 @@ static void sifive_plic_update(SiFivePLICState *plic) switch (mode) { case PLICMode_M: + trace_sifive_plic_update(hartid, 'M', level); qemu_set_irq(plic->m_external_irqs[hartid - plic->hartid_base], level); break; case PLICMode_S: + trace_sifive_plic_update(hartid, 'S', level); qemu_set_irq(plic->s_external_irqs[hartid - plic->hartid_base], level); break; default: @@ -202,9 +223,11 @@ static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value, * out the access to unsupported priority bits. */ plic->source_priority[irq] = value % (plic->num_priorities + 1); + trace_sifive_plic_set_priority(irq, plic->source_priority[irq]); sifive_plic_update(plic); } else if (value <= plic->num_priorities) { plic->source_priority[irq] = value; + trace_sifive_plic_set_priority(irq, plic->source_priority[irq]); sifive_plic_update(plic); } } else if (addr_between(addr, plic->pending_base, @@ -219,6 +242,7 @@ static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value, if (wordid < plic->bitfield_words) { plic->enable[addrid * plic->bitfield_words + wordid] = value; + sifive_plic_update(plic); } else { qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid enable write 0x%" HWADDR_PRIx "\n", @@ -280,6 +304,7 @@ static void sifive_plic_reset(DeviceState *dev) memset(s->pending, 0, sizeof(uint32_t) * s->bitfield_words); memset(s->claimed, 0, sizeof(uint32_t) * s->bitfield_words); memset(s->enable, 0, sizeof(uint32_t) * s->num_enables); + memset(s->level, 0, sizeof(uint32_t) * s->bitfield_words); for (i = 0; i < s->num_harts; i++) { qemu_set_irq(s->m_external_irqs[i], 0); @@ -354,10 +379,19 @@ static void sifive_plic_irq_request(void *opaque, int irq, int level) { SiFivePLICState *s = opaque; - if (level > 0) { - sifive_plic_set_pending(s, irq, true); - sifive_plic_update(s); + assert(irq < s->num_sources); + + if (s->edge_triggered) { + if (level && !sifive_plic_get_level(s, irq)) { + sifive_plic_set_pending(s, irq, true); + } + sifive_plic_set_level(s, irq, !!level); + } else { + if (level > 0) { + sifive_plic_set_pending(s, irq, true); + } } + sifive_plic_update(s); } static void sifive_plic_realize(DeviceState *dev, Error **errp) @@ -383,6 +417,7 @@ static void sifive_plic_realize(DeviceState *dev, Error **errp) s->pending = g_new0(uint32_t, s->bitfield_words); s->claimed = g_new0(uint32_t, s->bitfield_words); s->enable = g_new0(uint32_t, s->num_enables); + s->level = g_new0(uint32_t, s->bitfield_words); qdev_init_gpio_in(dev, sifive_plic_irq_request, s->num_sources); @@ -444,6 +479,7 @@ static const Property sifive_plic_properties[] = { DEFINE_PROP_UINT32("context-base", SiFivePLICState, context_base, 0), DEFINE_PROP_UINT32("context-stride", SiFivePLICState, context_stride, 0), DEFINE_PROP_UINT32("aperture-size", SiFivePLICState, aperture_size, 0), + DEFINE_PROP_BOOL("edge-triggered", SiFivePLICState, edge_triggered, false), }; static void sifive_plic_class_init(ObjectClass *klass, const void *data) @@ -479,7 +515,8 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, uint32_t num_priorities, uint32_t priority_base, uint32_t pending_base, uint32_t enable_base, uint32_t enable_stride, uint32_t context_base, - uint32_t context_stride, uint32_t aperture_size) + uint32_t context_stride, uint32_t aperture_size, + bool edge_triggered) { DeviceState *dev = qdev_new(TYPE_SIFIVE_PLIC); int i; @@ -498,6 +535,7 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, qdev_prop_set_uint32(dev, "context-base", context_base); qdev_prop_set_uint32(dev, "context-stride", context_stride); qdev_prop_set_uint32(dev, "aperture-size", aperture_size); + qdev_prop_set_bit(dev, "edge-triggered", edge_triggered); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 018c609ca5eb9..ce406fd8ae939 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -330,3 +330,10 @@ loongarch_msi_set_irq(int irq_num) "set msi irq %d" loongarch_extioi_setirq(int irq, int level) "set extirq irq %d level %d" loongarch_extioi_readw(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64 "val: 0x%" PRIx64 loongarch_extioi_writew(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64 "val: 0x%" PRIx64 + +# sifive_plic.c + +sifive_plic_set_claimed(int irq, bool level) "irq %d: %u" +sifive_plic_set_priority(uint32_t irq, uint32_t prio) "irq %u: %u" +sifive_plic_set_pending(int irq, bool level) "irq %d: %u" +sifive_plic_update(uint32_t hartid, int mode, bool level) "hart %u %c: %u" diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c index a17f62cd082dd..9fcdd73daf82a 100644 --- a/hw/riscv/microchip_pfsoc.c +++ b/hw/riscv/microchip_pfsoc.c @@ -287,7 +287,8 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp) MICROCHIP_PFSOC_PLIC_ENABLE_STRIDE, MICROCHIP_PFSOC_PLIC_CONTEXT_BASE, MICROCHIP_PFSOC_PLIC_CONTEXT_STRIDE, - memmap[MICROCHIP_PFSOC_PLIC].size); + memmap[MICROCHIP_PFSOC_PLIC].size, + false); g_free(plic_hart_config); /* DMA */ diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c index 3e7f4411727d6..0a3550c41b0b4 100644 --- a/hw/riscv/shakti_c.c +++ b/hw/riscv/shakti_c.c @@ -118,7 +118,8 @@ static void shakti_c_soc_state_realize(DeviceState *dev, Error **errp) SHAKTI_C_PLIC_ENABLE_STRIDE, SHAKTI_C_PLIC_CONTEXT_BASE, SHAKTI_C_PLIC_CONTEXT_STRIDE, - shakti_c_memmap[SHAKTI_C_PLIC].size); + shakti_c_memmap[SHAKTI_C_PLIC].size, + false); riscv_aclint_swi_create(shakti_c_memmap[SHAKTI_C_CLINT].base, 0, 1, false); diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c index 7baed1958e05d..fe09da15fe9f8 100644 --- a/hw/riscv/sifive_e.c +++ b/hw/riscv/sifive_e.c @@ -219,7 +219,8 @@ static void sifive_e_soc_realize(DeviceState *dev, Error **errp) SIFIVE_E_PLIC_ENABLE_STRIDE, SIFIVE_E_PLIC_CONTEXT_BASE, SIFIVE_E_PLIC_CONTEXT_STRIDE, - memmap[SIFIVE_E_DEV_PLIC].size); + memmap[SIFIVE_E_DEV_PLIC].size, + false); riscv_aclint_swi_create(memmap[SIFIVE_E_DEV_CLINT].base, 0, ms->smp.cpus, false); riscv_aclint_mtimer_create(memmap[SIFIVE_E_DEV_CLINT].base + diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c index a7492aa27a46c..7de4dbe6077a8 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -842,7 +842,8 @@ static void sifive_u_soc_realize(DeviceState *dev, Error **errp) SIFIVE_U_PLIC_ENABLE_STRIDE, SIFIVE_U_PLIC_CONTEXT_BASE, SIFIVE_U_PLIC_CONTEXT_STRIDE, - memmap[SIFIVE_U_DEV_PLIC].size); + memmap[SIFIVE_U_DEV_PLIC].size, + false); g_free(plic_hart_config); sifive_uart_create(system_memory, memmap[SIFIVE_U_DEV_UART0].base, serial_hd(0), qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_U_UART0_IRQ)); diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 17909206c7ef8..4ad9c3feeaf37 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -1299,7 +1299,8 @@ static DeviceState *virt_create_plic(const MemMapEntry *memmap, int socket, VIRT_PLIC_ENABLE_BASE, VIRT_PLIC_ENABLE_STRIDE, VIRT_PLIC_CONTEXT_BASE, VIRT_PLIC_CONTEXT_STRIDE, - memmap[VIRT_PLIC].size); + memmap[VIRT_PLIC].size, + false); } static DeviceState *virt_create_aia(RISCVVirtAIAType aia_type, int aia_guests, diff --git a/include/hw/intc/sifive_plic.h b/include/hw/intc/sifive_plic.h index d3f45ec248167..f59a8a9fc8936 100644 --- a/include/hw/intc/sifive_plic.h +++ b/include/hw/intc/sifive_plic.h @@ -58,6 +58,7 @@ struct SiFivePLICState { uint32_t *pending; uint32_t *claimed; uint32_t *enable; + uint32_t *level; /* config */ char *hart_config; @@ -71,6 +72,7 @@ struct SiFivePLICState { uint32_t context_base; uint32_t context_stride; uint32_t aperture_size; + bool edge_triggered; qemu_irq *m_external_irqs; qemu_irq *s_external_irqs; @@ -82,6 +84,7 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, uint32_t num_priorities, uint32_t priority_base, uint32_t pending_base, uint32_t enable_base, uint32_t enable_stride, uint32_t context_base, - uint32_t context_stride, uint32_t aperture_size); + uint32_t context_stride, uint32_t aperture_size, + bool edge_triggered); #endif From a135a8dfd7b8f56b7403c3a96ee6d8e0a9e89773 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 19 Jan 2026 15:02:50 +0000 Subject: [PATCH 07/18] [ot] hw/riscv: ibex: rework Ibex to be configurable Signed-off-by: James Wainwright --- hmp-commands-info.hx | 12 + hw/char/Kconfig | 3 + hw/char/meson.build | 2 +- hw/riscv/Kconfig | 12 + hw/riscv/ibex_clock_src.c | 42 ++ hw/riscv/ibex_common.c | 883 ++++++++++++++++++++++++++++++ hw/riscv/ibex_gpio.c | 169 ++++++ hw/riscv/opentitan.c | 24 +- hw/riscv/trace-events | 4 + hw/ssi/Kconfig | 4 + hw/ssi/meson.build | 2 +- hw/ssi/ssi.c | 3 - hw/timer/Kconfig | 3 + hw/timer/ibex_timer.c | 23 +- hw/timer/meson.build | 2 +- include/hw/riscv/ibex_clock_src.h | 58 ++ include/hw/riscv/ibex_common.h | 637 +++++++++++++++++++++ include/hw/riscv/ibex_gpio.h | 111 ++++ include/hw/riscv/ibex_irq.h | 103 ++++ include/hw/riscv/opentitan.h | 2 +- include/hw/ssi/ssi.h | 3 + target/riscv/cpu-qom.h | 2 +- target/riscv/cpu.c | 18 +- target/riscv/cpu.h | 7 + target/riscv/cpu_cfg_fields.h.inc | 2 + target/riscv/cpu_helper.c | 6 +- target/riscv/csr.c | 13 +- target/riscv/ibex_csr.c | 148 +++++ target/riscv/meson.build | 1 + 29 files changed, 2257 insertions(+), 42 deletions(-) create mode 100644 hw/riscv/ibex_clock_src.c create mode 100644 hw/riscv/ibex_common.c create mode 100644 hw/riscv/ibex_gpio.c create mode 100644 include/hw/riscv/ibex_clock_src.h create mode 100644 include/hw/riscv/ibex_common.h create mode 100644 include/hw/riscv/ibex_gpio.h create mode 100644 include/hw/riscv/ibex_irq.h create mode 100644 target/riscv/ibex_csr.c diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index 41674dcbe1eb2..da3bfb5c847dd 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -998,6 +998,18 @@ SRST Show the crypto devices. ERST + { + .name = "ibex", + .args_type = "", + .params = "", + .help = "Show Ibex vCPU info", + }, + +SRST + ``info ibex`` + Show Ibex vCPU information. +ERST + { .name = "firmware-log", .args_type = "max-size:o?", diff --git a/hw/char/Kconfig b/hw/char/Kconfig index 020c0a84bb6e5..0cbbaedb2f189 100644 --- a/hw/char/Kconfig +++ b/hw/char/Kconfig @@ -95,3 +95,6 @@ config IP_OCTAL_232 bool default y depends on IPACK + +config IBEX_UART + bool diff --git a/hw/char/meson.build b/hw/char/meson.build index a9e1dc26c0fa1..f47fa11bbe023 100644 --- a/hw/char/meson.build +++ b/hw/char/meson.build @@ -2,7 +2,7 @@ system_ss.add(when: 'CONFIG_CADENCE', if_true: files('cadence_uart.c')) system_ss.add(when: 'CONFIG_CMSDK_APB_UART', if_true: files('cmsdk-apb-uart.c')) system_ss.add(when: 'CONFIG_ESCC', if_true: files('escc.c')) system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_apbuart.c')) -system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_uart.c')) +system_ss.add(when: 'CONFIG_IBEX_UART', if_true: files('ibex_uart.c')) system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_serial.c')) system_ss.add(when: 'CONFIG_IP_OCTAL_232', if_true: files('ipoctal232.c')) system_ss.add(when: 'CONFIG_ISA_BUS', if_true: files('parallel-isa.c')) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index fc9c35bd981ea..a076ea7fdfc92 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -7,6 +7,15 @@ config RISCV_NUMA config IBEX bool +config IBEX_CLOCK_SRC + bool + +config IBEX_COMMON + bool + +config IBEX_GPIO + bool + # RISC-V machines in alphabetical order config MICROCHIP_PFSOC @@ -38,6 +47,9 @@ config OPENTITAN default y depends on RISCV32 select IBEX + select IBEX_SPI_HOST + select IBEX_TIMER + select IBEX_UART select SIFIVE_PLIC select UNIMP diff --git a/hw/riscv/ibex_clock_src.c b/hw/riscv/ibex_clock_src.c new file mode 100644 index 0000000000000..92ef0fc7ea7c5 --- /dev/null +++ b/hw/riscv/ibex_clock_src.c @@ -0,0 +1,42 @@ +/* + * QEMU Ibex Clock Source interface + * + * Copyright (c) 2025 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. + */ + +#include "qemu/osdep.h" +#include "hw/riscv/ibex_clock_src.h" + +static const TypeInfo ibex_clock_src_info = { + .name = TYPE_IBEX_CLOCK_SRC_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(IbexClockSrcIfClass), +}; + +static void ibex_clock_src_register_types(void) +{ + type_register_static(&ibex_clock_src_info); +} + +type_init(ibex_clock_src_register_types); diff --git a/hw/riscv/ibex_common.c b/hw/riscv/ibex_common.c new file mode 100644 index 0000000000000..c41f7854ddde8 --- /dev/null +++ b/hw/riscv/ibex_common.c @@ -0,0 +1,883 @@ +/* + * QEMU RISC-V Helpers for LowRISC Ibex Demo System & OpenTitan EarlGrey + * + * Copyright (c) 2022-2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * Loïc Lefort + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "qemu/osdep.h" +#ifdef CONFIG_POSIX +#include +#endif +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "chardev/chardev-internal.h" +/* NOLINTBEGIN(misc-header-include-cycle) */ +#include "cpu.h" +/* NOLINTEND(misc-header-include-cycle) */ +#include "disas/disas.h" +#include "elf.h" +#include "exec/hwaddr.h" +#include "hw/boards.h" +#include "hw/core/rust_demangle.h" +#include "hw/loader.h" +#include "hw/misc/unimp.h" +#include "hw/qdev-core.h" +#include "hw/qdev-properties.h" +#include "hw/riscv/ibex_clock_src.h" +#include "hw/riscv/ibex_common.h" +#include "monitor/monitor.h" +#include "system/runstate.h" +#include "trace.h" + +static void rust_demangle_fn(const char *st_name, int st_info, + uint64_t st_value, uint64_t st_size); + +void ibex_mmio_map_device(SysBusDevice *dev, MemoryRegion *mr, unsigned nr, + hwaddr addr, int priority) +{ + g_assert(nr < dev->num_mmio); + g_assert(dev->mmio[nr].addr == (hwaddr)-1); + dev->mmio[nr].addr = addr; + if (priority) { + memory_region_add_subregion_overlap(mr, addr, dev->mmio[nr].memory, + priority); + } else { + memory_region_add_subregion(mr, addr, dev->mmio[nr].memory); + } +} + +DeviceState **ibex_create_devices(const IbexDeviceDef *defs, unsigned count, + DeviceState *parent) +{ + DeviceState **devices = g_new0(DeviceState *, count); + GHashTable *chtable = + g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + const char *pname = object_get_typename(OBJECT(parent)); + + for (unsigned idx = 0; idx < count; idx++) { + const IbexDeviceDef *def = &defs[idx]; + if (!def->type) { + devices[idx] = NULL; + continue; + } + devices[idx] = qdev_new(def->type); + + const char *tname; + if (!strcmp(def->type, TYPE_UNIMPLEMENTED_DEVICE)) { + tname = def->name ? def->name : TYPE_UNIMPLEMENTED_DEVICE; + } else { + tname = def->type; + } + + char *name; + if (!IBEX_HAS_INSTANCE_NUM(def)) { + gpointer sibling = g_hash_table_lookup(chtable, (gpointer)tname); + unsigned *chcount; + if (!sibling) { + chcount = g_new(unsigned, 1u); + *chcount = 0; + g_hash_table_insert(chtable, (gpointer)tname, + (gpointer)chcount); + } else { + chcount = (unsigned *)sibling; + *chcount = (*chcount) + 1u; + } + name = g_strdup_printf("%s.%u", tname, *chcount); + } else { + name = g_strdup_printf("%s[%u]", tname, IBEX_GET_INSTANCE_NUM(def)); + } + trace_ibex_create_device(pname, name); + object_property_add_child(OBJECT(parent), name, OBJECT(devices[idx])); + g_free(name); + } + g_hash_table_destroy(chtable); + return devices; +} + +void ibex_link_remote_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count, DeviceState ***remotes) +{ + /* Link devices */ + DeviceState ***targets = remotes ?: &devices; + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexDeviceLinkDef *link = defs[idx].link; + if (link) { + while (link->propname) { + unsigned rix = IBEX_DEVLINK_REMOTE(link->index); + unsigned dix = IBEX_DEVLINK_DEVICE(link->index); + 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, "cannot create %s link", + 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) +{ + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + ibex_apply_device_props(OBJECT(dev), defs[idx].prop); + } +} + +void ibex_realize_system_devices(DeviceState **devices, + const IbexDeviceDef *defs, unsigned count) +{ + BusState *bus = sysbus_get_default(); + + ibex_realize_devices(devices, bus, defs, count); + + MemoryRegion *mrs[] = { get_system_memory(), NULL, NULL, NULL }; + + ibex_map_devices(devices, mrs, defs, count); +} + +void ibex_realize_devices(DeviceState **devices, BusState *bus, + const IbexDeviceDef *defs, unsigned count) +{ + /* Realize devices */ + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexDeviceDef *def = &defs[idx]; + + if (def->cfg) { + def->cfg(dev, def, DEVICE(OBJECT(dev)->parent)); + } + + if (def->memmap) { + SysBusDevice *busdev = + (SysBusDevice *)object_dynamic_cast(OBJECT(dev), + TYPE_SYS_BUS_DEVICE); + if (!busdev) { + /* non-sysbus devices are not supported for now */ + g_assert_not_reached(); + } + + qdev_realize_and_unref(DEVICE(busdev), bus, &error_fatal); + } else { + /* device is not connected to a bus */ + qdev_realize_and_unref(dev, NULL, &error_fatal); + } + } +} + +void ibex_map_devices_mask_offset(DeviceState **devices, MemoryRegion **mrs, + const IbexDeviceDef *defs, unsigned count, + uint32_t region_mask, uint32_t offset) +{ + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexDeviceDef *def = &defs[idx]; + + if (def->memmap) { + SysBusDevice *busdev = + (SysBusDevice *)object_dynamic_cast(OBJECT(dev), + TYPE_SYS_BUS_DEVICE); + if (busdev) { + const IbexMemMapEntry *memmap = def->memmap; + unsigned mem = 0; + while (!IBEX_MEMMAP_IS_LAST(memmap)) { + unsigned region = IBEX_MEMMAP_GET_REGIDX(memmap->base); + if (region_mask & (1u << region)) { + MemoryRegion *mr = mrs[region]; + if (mr) { + ibex_mmio_map_device(busdev, mr, mem, + IBEX_MEMMAP_GET_ADDRESS( + memmap->base) + + offset, + memmap->priority); + } + } + mem++; + memmap++; + } + } + } + } +} + +void ibex_map_devices_ext_mask_offset( + DeviceState *dev, MemoryRegion **mrs, const IbexDeviceMapDef *defs, + unsigned count, uint32_t region_mask, uint32_t offset) +{ + for (unsigned ix = 0; ix < count; ix++) { + const IbexDeviceMapDef *def = &defs[ix]; + g_assert(def->type); + g_assert(def->memmap); + + if (!IBEX_HAS_INSTANCE_NUM(def)) { + error_setg(&error_fatal, "Device %s @ %u instance is not known", + def->type, ix); + return; + } + + char *name = + g_strdup_printf("%s[%u]", def->type, IBEX_GET_INSTANCE_NUM(def)); + Object *child; + child = object_property_get_link(OBJECT(dev), name, &error_fatal); + SysBusDevice *sdev; + sdev = OBJECT_CHECK(SysBusDevice, child, TYPE_SYS_BUS_DEVICE); + g_free(name); + + const IbexMemMapEntry *memmap = def->memmap; + unsigned mem = 0; + while (!IBEX_MEMMAP_IS_LAST(memmap)) { + if (!IBEX_MEMMAP_IGNORE(memmap)) { + unsigned region = IBEX_MEMMAP_GET_REGIDX(memmap->base); + if (region_mask & (1u << region)) { + MemoryRegion *mr = mrs[region]; + if (mr) { + ibex_mmio_map_device(sdev, mr, mem, + IBEX_MEMMAP_GET_ADDRESS( + memmap->base) + + offset, + memmap->priority); + } + } + } + mem++; + memmap++; + } + } +} + +void ibex_connect_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count) +{ + /* Connect GPIOs (in particular, IRQs) */ + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexDeviceDef *def = &defs[idx]; + + if (def->gpio) { + const IbexGpioConnDef *conn = def->gpio; + while (conn->out.num >= 0 && conn->in.num >= 0) { + if (conn->in.index >= 0) { + unsigned in_ix = IBEX_GPIO_GET_IDX(conn->in.index); + g_assert(devices[in_ix]); + qemu_irq in_gpio = + qdev_get_gpio_in_named(devices[in_ix], conn->in.name, + conn->in.num); + if (!in_gpio) { + error_setg(&error_fatal, "no such GPIO '%s.%s[%d]'\n", + object_get_typename(OBJECT(devices[in_ix])), + conn->in.name, conn->in.num); + } + qdev_connect_gpio_out_named(dev, conn->out.name, + conn->out.num, in_gpio); + } + conn++; + } + } + } +} + +void ibex_clock_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count) +{ + IbexClockSrcIfClass *ic = NULL; + IbexClockSrcIf *is = NULL; + int ii = -1; + + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexClockConnDef *clock = defs[idx].clock; + if (!clock) { + continue; + } + + while (clock->out.name && clock->in.name) { + if (clock->out.index != ii) { + /* new clock source */ + DeviceState *cs = devices[clock->out.index]; + ic = IBEX_CLOCK_SRC_IF_GET_CLASS(cs); + is = IBEX_CLOCK_SRC_IF(cs); + ii = clock->out.index; + } + + qemu_irq clk_irq = + qdev_get_gpio_in_named(dev, clock->in.name, clock->in.num); + if (!clk_irq) { + error_setg(&error_fatal, "no such clock '%s.%s[%d]'\n", + object_get_typename(OBJECT(dev)), clock->in.name, + clock->in.num); + } + + g_assert(ic && ic->get_clock_source); + const char *out_name = + ic->get_clock_source(is, clock->out.name, dev, &error_fatal); + qdev_connect_gpio_out_named(dev, out_name, 0, clk_irq); + clock++; + } + } +} + +/* List of exported GPIOs */ +typedef QLIST_HEAD(, NamedGPIOList) IbexXGPIOList; + +static NamedGPIOList *ibex_xgpio_list(IbexXGPIOList *xgpios, const char *name) +{ + /* + * qdev_get_named_gpio_list is not a public API. + * Use a clone implementation to manage a list of GPIOs + */ + NamedGPIOList *ngl; + + QLIST_FOREACH(ngl, xgpios, node) { + /* NULL is a valid and matchable name. */ + if (g_strcmp0(name, ngl->name) == 0) { + return ngl; + } + } + + ngl = g_malloc0(sizeof(*ngl)); + ngl->name = g_strdup(name); + QLIST_INSERT_HEAD(xgpios, ngl, node); + return ngl; +} + +void ibex_export_gpios(DeviceState **devices, DeviceState *parent, + const IbexDeviceDef *defs, unsigned count) +{ + /* + * Use IbexXGPIOList as a circomvoluted way to obtain IRQ information that + * may not be exposed through public APIs. + */ + IbexXGPIOList pgpios = { 0 }; + + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexDeviceDef *def = &defs[idx]; + + if (def->gpio_export) { + const IbexGpioExportDef *export; + + /* loop once to compute how the number of GPIO for each GPIO list */ + export = def->gpio_export; + while (export->device.num >= 0 && export->parent.num >= 0) { + const char *pname = export->parent.name; + NamedGPIOList *pngl = ibex_xgpio_list(&pgpios, pname); + + pngl->num_in = MAX(pngl->num_in, export->parent.num); + + export ++; + } + + NamedGPIOList *ngl, *ntmp; + QLIST_FOREACH_SAFE(ngl, &pgpios, node, ntmp) { + NamedGPIOList *pngl; + QLIST_FOREACH(pngl, &parent->gpios, node) { + if (!g_strcmp0(ngl->name, pngl->name)) { + qemu_log("%s: duplicate GPIO export list %s for %s\n", + __func__, ngl->name, + object_get_typename(OBJECT(parent))); + g_assert_not_reached(); + } + } + /* num_in is the max index, i.e. n-1 */ + ngl->num_in += 1u; + ngl->in = g_new(qemu_irq, ngl->num_in); + QLIST_REMOVE(ngl, node); + QLIST_INSERT_HEAD(&parent->gpios, ngl, node); + } + + /* + * Now the count of IRQ slots per IRQ name is known. + * Allocate the required slots to store IRQs + * Create alias from parent to devices + * Shallow copy device IRQ into parent's slots, as we do not want + * to alter the device IRQ list. + */ + export = def->gpio_export; + ngl = NULL; + while (export->device.num >= 0 && export->parent.num >= 0) { + const char *defname = "unnamed-gpio-in"; + const char *dname = export->device.name; + const char *dm = dname ? dname : defname; + char *dpname = + g_strdup_printf("%s[%d]", dm, export->device.num); + const char *pname = export->parent.name; + const char *pm = pname ? pname : defname; + char *ppname = + g_strdup_printf("%s[%d]", pm, export->parent.num); + qemu_irq devirq; + devirq = qdev_get_gpio_in_named(dev, export->device.name, + export->device.num); + if (!ngl || g_strcmp0(ngl->name, pname)) { + ngl = NULL; + NamedGPIOList *pngl; + QLIST_FOREACH(pngl, &parent->gpios, node) { + if (!g_strcmp0(pngl->name, pname)) { + ngl = pngl; + break; + } + } + } + + assert(ngl && ngl->in); + ngl->in[export->parent.num] = devirq; + (void)object_ref(OBJECT(devirq)); + object_property_add_alias(OBJECT(parent), ppname, OBJECT(dev), + dpname); + g_free(ppname); + g_free(dpname); + export ++; + } + } + } +} + +void ibex_connect_soc_devices(DeviceState **soc_devices, DeviceState **devices, + const IbexDeviceDef *defs, unsigned count) +{ + for (unsigned ix = 0; ix < count; ix++) { + DeviceState *dev = devices[ix]; + if (!dev) { + continue; + } + const IbexDeviceDef *def = &defs[ix]; + if (!def->type || !def->gpio) { + continue; + } + + const IbexGpioConnDef *conn = def->gpio; + while (conn->out.num >= 0 && conn->in.num >= 0) { + if (conn->in.index < 0) { + unsigned grp = IBEX_GPIO_GET_GRP(conn->in.num); + if (!(grp < count)) { + g_assert_not_reached(); + } + DeviceState *socdev = soc_devices[grp]; + unsigned in_ix = IBEX_GPIO_GET_IDX(conn->in.num); + qemu_irq in_gpio = + qdev_get_gpio_in_named(socdev, conn->in.name, (int)in_ix); + if (!in_gpio) { + error_setg( + &error_fatal, + "cannot connect %s.%s[%d], no such IRQ '%s.%s[%d]'\n", + object_get_typename(OBJECT(dev)), conn->out.name, + conn->out.num, object_get_typename(OBJECT(socdev)), + conn->in.name, in_ix); + } + qdev_connect_gpio_out_named(dev, conn->out.name, conn->out.num, + in_gpio); + } + conn++; + } + } +} + +void ibex_identify_devices(DeviceState **devices, const char *id_prop, + const char *id_value, bool id_prepend, + unsigned count) +{ + for (unsigned ix = 0; ix < count; ix++) { + DeviceState *dev = devices[ix]; + if (!dev) { + continue; + } + Object *obj = OBJECT(dev); + /* check if the device defines an identifcation string property */ + char *value = object_property_get_str(obj, id_prop, NULL); + if (!value) { + continue; + } + + bool is_set = (bool)strcmp(value, ""); + if (is_set && !id_prepend) { + /* do not override already defined property */ + g_free(value); + continue; + } + + bool res; + if (is_set && id_prepend) { + char *pvalue = g_strconcat(id_value, ".", value, NULL); + res = object_property_set_str(obj, id_prop, pvalue, NULL); + g_free(pvalue); + } else { + res = object_property_set_str(obj, id_prop, id_value, NULL); + } + g_free(value); + if (!res) { + error_report("%s: cannot apply identifier to %s\n", __func__, + object_get_typename(obj)); + } + } +} + +void ibex_configure_devices_with_id(DeviceState **devices, BusState *bus, + const char *id_prop, const char *id_value, + bool id_prepend, const IbexDeviceDef *defs, + unsigned count) +{ + ibex_link_devices(devices, defs, count); + ibex_define_device_props(devices, defs, count); + if (id_prop && id_value) { + ibex_identify_devices(devices, id_prop, id_value, id_prepend, count); + } + ibex_realize_devices(devices, bus, defs, count); + ibex_clock_devices(devices, defs, count); + ibex_connect_devices(devices, defs, count); +} + +void ibex_configure_devices(DeviceState **devices, BusState *bus, + const IbexDeviceDef *defs, unsigned count) +{ + ibex_configure_devices_with_id(devices, bus, NULL, NULL, false, defs, + count); +} + +typedef struct { + Object *child; + const char *typename; + unsigned instance; +} IbexChildMatch; + +static int ibex_match_device(Object *child, void *opaque) +{ + IbexChildMatch *match = opaque; + + if (!object_dynamic_cast(child, match->typename)) { + return 0; + } + if (match->instance) { + match->instance -= 1; + return 0; + } + + match->child = child; + return 1; +} + +DeviceState *ibex_get_child_device(DeviceState *s, const char *typename, + unsigned instance) +{ + IbexChildMatch match = { + .child = NULL, + .typename = typename, + .instance = instance, + }; + + if (!object_child_foreach(OBJECT(s), &ibex_match_device, &match)) { + return NULL; + } + + if (!object_dynamic_cast(match.child, TYPE_DEVICE)) { + return NULL; + } + + return DEVICE(match.child); +} + +typedef struct { + Chardev *chr; + const char *label; +} IbexChrMatch; + +static int ibex_match_chardev(Object *child, void *opaque) +{ + IbexChrMatch *match = opaque; + Chardev *chr = CHARDEV(child); + + if (strcmp(match->label, chr->label) != 0) { + return 0; + } + + match->chr = chr; + return 1; +} + +Chardev *ibex_get_chardev_by_id(const char *chrid) +{ + IbexChrMatch match = { + .chr = NULL, + .label = chrid, + }; + + /* "chardev-internal.h" inclusion is required for get_chardevs_root() */ + if (!object_child_foreach(get_chardevs_root(), &ibex_match_chardev, + &match)) { + return NULL; + } + + return match.chr; +} + +void ibex_unimp_configure(DeviceState *dev, const IbexDeviceDef *def, + DeviceState *parent) +{ + (void)parent; + + if (def->name) { + qdev_prop_set_string(dev, "name", def->name); + } + g_assert(def->memmap != NULL); +} + +uint32_t ibex_load_kernel(CPUState *cpu) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + + uint64_t kernel_entry; + /* load kernel if provided */ + if (ms->kernel_filename) { + AddressSpace *as = NULL; + if (!cpu) { + CPUState *cs; + /* NOLINTNEXTLINE */ + CPU_FOREACH(cs) { + if (cs->as) { + as = cs->as; + break; + } + } + } else { + as = cpu->as; + } + g_assert(as); + 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) { + error_report("Cannot load ELF kernel %s", ms->kernel_filename); + qemu_system_shutdown_request_with_code(SHUTDOWN_CAUSE_HOST_ERROR, + 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; + Error *errp = NULL; + bool no_set_pc = + object_property_get_bool(OBJECT(ms), "ignore-elf-entry", &errp); + if (!no_set_pc) { + if (!cpu) { + CPUState *cs; + /* NOLINTNEXTLINE */ + CPU_FOREACH(cs) { + RISCV_CPU(cs)->env.resetvec = kernel_entry | 0x80ull; + RISCV_CPU(cs)->cfg.mtvec = kernel_entry | 0b1ull; + } + } else { + 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; +} + +const char *ibex_common_get_func_name_by_addr(void *fn) +{ +#ifdef CONFIG_POSIX + Dl_info info; + if (dladdr(fn, &info)) { + return info.dli_sname; + } +#endif + + return NULL; +} + +uint32_t ibex_get_current_pc(void) +{ + CPUState *cs = current_cpu; + + return cs && cs->cc->get_pc ? (uint32_t)cs->cc->get_pc(cs) : 0u; +} + +int ibex_get_current_cpu(void) +{ + CPUState *cs = current_cpu; + + return cs ? cs->cpu_index : -1; +} + +/* x0 is replaced with PC */ +static const char ibex_ireg_names[32u][4u] = { + "pc", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", + "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", + "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6", +}; + +void ibex_log_vcpu_registers(uint64_t regbm) +{ + CPURISCVState *env = &RISCV_CPU(current_cpu)->env; + qemu_log_mask(CPU_LOG_TB_IN_ASM, "\n....\n"); + if (regbm & 0x1u) { + qemu_log_mask(CPU_LOG_TB_IN_ASM, "%4s: 0x" TARGET_FMT_lx "\n", + ibex_ireg_names[0], env->pc); + } + for (unsigned gix = 1u; gix < 32u; gix++) { + uint64_t mask = 1u << gix; + if (regbm & mask) { + qemu_log_mask(CPU_LOG_TB_IN_ASM, "%4s: 0x" TARGET_FMT_lx "\n", + ibex_ireg_names[gix], env->gpr[gix]); + } + } +} + +static void rust_demangle_fn(const char *st_name, int st_info, + uint64_t st_value, uint64_t st_size) +{ + (void)st_info; + (void)st_value; + + if (!st_size) { + return; + } + + rust_demangle_replace((char *)st_name); +} + +/* + * Note: this is not specific to Ibex, and might apply to any vCPU. + */ +static void hmp_info_ibex(Monitor *mon, const QDict *qdict) +{ + CPUState *cpu; + (void)qdict; + + /* NOLINTNEXTLINE */ + CPU_FOREACH(cpu) { + vaddr pc; + const char *symbol; + const char *cpu_state; + if (cpu->cc->get_pc) { + pc = cpu->cc->get_pc(cpu); + symbol = lookup_symbol(pc); + } else { + pc = -1; + symbol = "?"; + } + if (cpu->halted && cpu->disabled) { + cpu_state = " [HD]"; + } else if (cpu->halted) { + cpu_state = " [H]"; + } else if (cpu->disabled) { + cpu_state = " [D]"; + } else { + cpu_state = ""; + } + monitor_printf(mon, "* CPU #%d%s: 0x%" PRIx64 " in '%s'\n", + cpu->cpu_index, cpu_state, (uint64_t)pc, symbol); + } +} + +static void ibex_register_types(void) +{ + monitor_register_hmp("ibex", true, &hmp_info_ibex); +} + +type_init(ibex_register_types); diff --git a/hw/riscv/ibex_gpio.c b/hw/riscv/ibex_gpio.c new file mode 100644 index 0000000000000..869b28d45db05 --- /dev/null +++ b/hw/riscv/ibex_gpio.c @@ -0,0 +1,169 @@ +/* + * QEMU Ibex GPIOs + * + * Copyright (c) 2024 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qapi/qmp/qerror.h" +#include "qapi/visitor.h" +#include "hw/riscv/ibex_gpio.h" +#include "trace.h" + +ibex_gpio ibex_gpio_combine(const ibex_gpio *levels, unsigned count) +{ + bool oweak = true; + bool odefined = false; + bool olevel = false; + + while (count--) { + ibex_gpio ilevel = *levels++; + ibex_gpio_assert(ilevel); + if (ibex_gpio_is_hiz(ilevel)) { + continue; + } + + bool weak = ibex_gpio_is_weak(ilevel); + bool level = ibex_gpio_level(ilevel); + + if (!odefined) { + olevel = level; + oweak = weak; + odefined = true; + continue; + } + + if (oweak) { + if (!weak) { + /* strong signal always overrides weak */ + olevel = level; + oweak = weak; + } else if (level != olevel) { + qemu_log("%s: level conflict between weak signals\n", __func__); + } + } else if (!weak && (level != olevel)) { + qemu_log("%s: level conflict between strong signals\n", __func__); + } + } + + if (!odefined) { + return IBEX_GPIO_INIT; + } + + return oweak ? IBEX_GPIO_FROM_WEAK_SIG(olevel) : + IBEX_GPIO_FROM_ACTIVE_SIG(olevel); +} + +bool ibex_gpio_parse_level(const char *name, const char *value, ibex_gpio *obj, + Error **errp) +{ + if (g_str_equal(value, "on") || g_str_equal(value, "hi") || + g_str_equal(value, "1") || g_str_equal(value, "high")) { + *obj = IBEX_GPIO_HIGH; + return true; + } + + if (g_str_equal(value, "off") || g_str_equal(value, "lo") || + g_str_equal(value, "0") || g_str_equal(value, "low")) { + *obj = IBEX_GPIO_LOW; + return true; + } + + if (g_str_equal(value, "pu") || g_str_equal(value, "pullup")) { + *obj = IBEX_GPIO_PULL_UP; + return true; + } + + if (g_str_equal(value, "pd") || g_str_equal(value, "pulldown")) { + *obj = IBEX_GPIO_PULL_DOWN; + return true; + } + + if (g_str_equal(value, "hiz") || g_str_equal(value, "z")) { + *obj = IBEX_GPIO_HIZ; + return true; + } + + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name, + "'high' or 'low' or 'pu' or 'pd' or 'hiz'"); + return false; +} + +typedef struct { + ibex_gpio *value; +} GpioLevelProperty; + +static void ibex_gpio_property_get(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + (void)obj; + GpioLevelProperty *prop = opaque; + char *value; + + g_assert(prop->value); + ibex_gpio val = *prop->value; + if (!ibex_gpio_check(val)) { + error_setg(errp, "Invalid IbexGPIO"); + return; + } + if (ibex_gpio_is_hiz(val)) { + value = g_strdup("hiz"); + } else if (ibex_gpio_is_weak(val)) { + value = g_strdup(ibex_gpio_level(val) ? "pu" : "pd"); + } else { + value = g_strdup(ibex_gpio_level(val) ? "high" : "low"); + } + + visit_type_str(v, name, &value, errp); + g_free(value); +} + +static void ibex_gpio_property_set(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + (void)obj; + GpioLevelProperty *prop = opaque; + char *value; + + if (!visit_type_str(v, name, &value, errp)) { + return; + } + + ibex_gpio_parse_level(name, value, prop->value, errp); + g_free(value); +} + +static void +ibex_gpio_property_release_data(Object *obj, const char *name, void *opaque) +{ + (void)obj; + (void)name; + g_free(opaque); +} + +ObjectProperty * +object_property_add_ibex_gpio(Object *obj, const char *name, ibex_gpio *value) +{ + GpioLevelProperty *prop = g_new0(GpioLevelProperty, 1u); + prop->value = value; + + return object_property_add(obj, name, "string", &ibex_gpio_property_get, + &ibex_gpio_property_set, + &ibex_gpio_property_release_data, prop); +} diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c index d369a8a7dcd18..f5f8ce0a51ff9 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -119,7 +119,7 @@ static void opentitan_machine_class_init(ObjectClass *oc, const void *data) mc->desc = "RISC-V Board compatible with OpenTitan"; mc->init = opentitan_machine_init; mc->max_cpus = 1; - mc->default_cpu_type = TYPE_RISCV_CPU_IBEX; + mc->default_cpu_type = TYPE_RISCV_CPU_LOWRISC_IBEX; mc->default_ram_id = "riscv.lowrisc.ibex.ram"; mc->default_ram_size = ibex_memmap[IBEX_DEV_RAM].size; } @@ -152,13 +152,17 @@ static void lowrisc_ibex_soc_realize(DeviceState *dev_soc, Error **errp) MemoryRegion *sys_mem = get_system_memory(); int i; - object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type, - &error_abort); - object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus, - &error_abort); - object_property_set_int(OBJECT(&s->cpus), "resetvec", s->resetvec, - &error_abort); - sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_fatal); + Object *cpu = OBJECT(&s->cpus.harts[0]); + object_property_set_int(cpu, "resetvec", s->resetvec, + &error_fatal); + object_property_set_bool(cpu, "m", true, &error_fatal); + object_property_set_bool(cpu, "pmp", true, &error_fatal); + object_property_set_bool(cpu, "zba", true, &error_fatal); + object_property_set_bool(cpu, "zbb", true, &error_fatal); + object_property_set_bool(cpu, "zbc", true, &error_fatal); + object_property_set_bool(cpu, "zbs", true, &error_fatal); + object_property_set_bool(cpu, "smepmp", true, &error_fatal); + qdev_realize(DEVICE(&s->cpus), NULL, &error_fatal); /* Boot ROM */ memory_region_init_rom(&s->rom, OBJECT(dev_soc), "riscv.lowrisc.ibex.rom", @@ -194,10 +198,10 @@ static void lowrisc_ibex_soc_realize(DeviceState *dev_soc, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->plic), 0, memmap[IBEX_DEV_PLIC].base); for (i = 0; i < ms->smp.cpus; i++) { - CPUState *cpu = qemu_get_cpu(i); + CPUState *cpu_state = qemu_get_cpu(i); qdev_connect_gpio_out(DEVICE(&s->plic), ms->smp.cpus + i, - qdev_get_gpio_in(DEVICE(cpu), IRQ_M_EXT)); + qdev_get_gpio_in(DEVICE(cpu_state), IRQ_M_EXT)); } /* UART */ diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index b50b14a65422c..61bcf28054564 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -24,3 +24,7 @@ riscv_iommu_hpm_incr_ctr(uint64_t cntr_val) "cntr_val 0x%"PRIx64 riscv_iommu_hpm_iocntinh_cy(bool prev_cy_inh) "prev_cy_inh %d" riscv_iommu_hpm_cycle_write(uint32_t ovf, uint64_t val) "ovf 0x%x val 0x%"PRIx64 riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx 0x%x ovf 0x%x val 0x%"PRIx64 + +# ibex_common.c + +ibex_create_device(const char *pname, const char *name) "%s.%s" diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1e0c..bd193700edc14 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -21,6 +21,10 @@ config STM32F2XX_SPI bool select SSI +config IBEX_SPI_HOST + bool + select SSI + config BCM2835_SPI bool select SSI diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 6afb1ea200121..a3ba319280d6d 100644 --- a/hw/ssi/meson.build +++ b/hw/ssi/meson.build @@ -10,6 +10,6 @@ system_ss.add(when: 'CONFIG_XILINX_SPI', if_true: files('xilinx_spi.c')) system_ss.add(when: 'CONFIG_XILINX_SPIPS', if_true: files('xilinx_spips.c')) system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-ospi.c')) system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c')) -system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c')) +system_ss.add(when: 'CONFIG_IBEX_SPI_HOST', if_true: files('ibex_spi_host.c')) system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c')) system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c')) diff --git a/hw/ssi/ssi.c b/hw/ssi/ssi.c index d0de640fe6428..5462977885919 100644 --- a/hw/ssi/ssi.c +++ b/hw/ssi/ssi.c @@ -24,9 +24,6 @@ struct SSIBus { BusState parent_obj; }; -#define TYPE_SSI_BUS "SSI" -OBJECT_DECLARE_SIMPLE_TYPE(SSIBus, SSI_BUS) - DeviceState *ssi_get_cs(SSIBus *bus, uint8_t cs_index) { BusState *b = BUS(bus); diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index b3d823ce2c394..2fdefd0d1fcfd 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig @@ -65,3 +65,6 @@ config STELLARIS_GPTM config AVR_TIMER16 bool + +config IBEX_TIMER + bool diff --git a/hw/timer/ibex_timer.c b/hw/timer/ibex_timer.c index c7320ef30fabe..431f37a9852c7 100644 --- a/hw/timer/ibex_timer.c +++ b/hw/timer/ibex_timer.c @@ -31,27 +31,26 @@ #include "hw/timer/ibex_timer.h" #include "hw/irq.h" #include "hw/qdev-properties.h" -#include "target/riscv/cpu.h" +#include "hw/registerfields.h" #include "migration/vmstate.h" REG32(ALERT_TEST, 0x00) FIELD(ALERT_TEST, FATAL_FAULT, 0, 1) REG32(CTRL, 0x04) FIELD(CTRL, ACTIVE, 0, 1) -REG32(CFG0, 0x100) - FIELD(CFG0, PRESCALE, 0, 12) - FIELD(CFG0, STEP, 16, 8) -REG32(LOWER0, 0x104) -REG32(UPPER0, 0x108) -REG32(COMPARE_LOWER0, 0x10C) -REG32(COMPARE_UPPER0, 0x110) -REG32(INTR_ENABLE, 0x114) +REG32(INTR_ENABLE, 0x100) FIELD(INTR_ENABLE, IE_0, 0, 1) -REG32(INTR_STATE, 0x118) +REG32(INTR_STATE, 0x104) FIELD(INTR_STATE, IS_0, 0, 1) -REG32(INTR_TEST, 0x11C) +REG32(INTR_TEST, 0x108) FIELD(INTR_TEST, T_0, 0, 1) - +REG32(CFG0, 0x10c) + FIELD(CFG0, PRESCALE, 0, 12) + FIELD(CFG0, STEP, 16, 8) +REG32(LOWER0, 0x110) +REG32(UPPER0, 0x114) +REG32(COMPARE_LOWER0, 0x118) +REG32(COMPARE_UPPER0, 0x11c) static uint64_t cpu_riscv_read_rtc(uint32_t timebase_freq) { return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), diff --git a/hw/timer/meson.build b/hw/timer/meson.build index 178321c029c8d..ebf46f641b40d 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build @@ -30,7 +30,7 @@ system_ss.add(when: 'CONFIG_SSE_TIMER', if_true: files('sse-timer.c')) system_ss.add(when: 'CONFIG_STELLARIS_GPTM', if_true: files('stellaris-gptm.c')) system_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c')) system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c')) -specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c')) +specific_ss.add(when: 'CONFIG_IBEX_TIMER', if_true: files('ibex_timer.c')) system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c')) specific_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c')) diff --git a/include/hw/riscv/ibex_clock_src.h b/include/hw/riscv/ibex_clock_src.h new file mode 100644 index 0000000000000..6c1fbd5cccfce --- /dev/null +++ b/include/hw/riscv/ibex_clock_src.h @@ -0,0 +1,58 @@ +/* + * QEMU Ibex Clock Source interface + * + * Copyright (c) 2025 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_RISCV_IBEX_CLOCK_SRC_H +#define HW_RISCV_IBEX_CLOCK_SRC_H + +#include "qom/object.h" + +#define TYPE_IBEX_CLOCK_SRC_IF "ibex-clock_src_if" +typedef struct IbexClockSrcIfClass IbexClockSrcIfClass; +DECLARE_CLASS_CHECKERS(IbexClockSrcIfClass, IBEX_CLOCK_SRC_IF, + TYPE_IBEX_CLOCK_SRC_IF) +#define IBEX_CLOCK_SRC_IF(_obj_) \ + INTERFACE_CHECK(IbexClockSrcIf, (_obj_), TYPE_IBEX_CLOCK_SRC_IF) + +typedef struct IbexClockSrcIf IbexClockSrcIf; + +struct IbexClockSrcIfClass { + InterfaceClass parent_class; + + /* + * Get a clock source by its type/name, to connect to the specified sink + * device. The clock line may already be connected or not. + * + * @name clock name + * @sink the sink device for the clock line + * @errp the error to use for reporting an invalid request + * @return the name of IRQ line to carry the clock information + */ + const char *(*get_clock_source)(IbexClockSrcIf *ifd, const char *name, + const DeviceState *sink, Error **errp); +}; + +#endif /* HW_RISCV_IBEX_CLOCK_SRC_H */ diff --git a/include/hw/riscv/ibex_common.h b/include/hw/riscv/ibex_common.h new file mode 100644 index 0000000000000..459f011a244f0 --- /dev/null +++ b/include/hw/riscv/ibex_common.h @@ -0,0 +1,637 @@ +/* + * QEMU RISC-V Helpers for LowRISC Ibex Demo System & OpenTitan EarlGrey + * + * Copyright (c) 2022-2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * Loïc Lefort + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef HW_RISCV_IBEX_COMMON_H +#define HW_RISCV_IBEX_COMMON_H + +#include "qom/object.h" +#include "exec/hwaddr.h" +#include "hw/qdev-core.h" +#include "hw/sysbus.h" + +/* ------------------------------------------------------------------------ */ +/* PMP configuration */ +/* ------------------------------------------------------------------------ */ + +#define IBEX_PMP_CFG(_l_, _a_, _x_, _w_, _r_) \ + ((uint8_t)(((_l_) << 7u) | ((_a_) << 3u) | ((_x_) << 2u) | ((_w_) << 1u) | \ + ((_r_)))) +#define IBEX_PMP_ADDR(_a_) ((_a_) >> 2u) + +#define IBEX_MSECCFG(_rlb_, _mmwp_, _mml_) \ + (((_rlb_) << 2u) | ((_mmwp_) << 1u) | ((_mml_))) + + +/* clang-format off */ + +enum { + IBEX_PMP_MODE_OFF, + IBEX_PMP_MODE_TOR, + IBEX_PMP_MODE_NA4, + IBEX_PMP_MODE_NAPOT +}; + +/* clang-format on */ + +/* ------------------------------------------------------------------------ */ +/* JTAG */ +/* ------------------------------------------------------------------------ */ + +#define IBEX_JTAG_PART_NUM(_part_, _tap_) \ + ((((_part_) & 0xfffu) << 4u) | ((_tap_) & 0xfu)) + +#define LOWRISC_JEDEC_MANUFACTURER_ID 0x6fu /* MSB is parity bit, ignored */ +#define LOWRISC_JEDEC_TABLE 13u +#define IBEX_TAP_IR_LENGTH 5u + +#define LOWRISC_JEDEC_MID \ + JEDEC_MANUFACTURER_ID(LOWRISC_JEDEC_TABLE, LOWRISC_JEDEC_MANUFACTURER_ID) +#define IBEX_JTAG_IDCODE(_part_, _tap_, _ver_) \ + JTAG_IDCODE(LOWRISC_JEDEC_MID, IBEX_JTAG_PART_NUM(_part_, _tap_), _ver_) + +/* ------------------------------------------------------------------------ */ +/* Devices & GPIOs */ +/* ------------------------------------------------------------------------ */ + +#define IBEX_MAX_MMIO_ENTRIES 4u +#define IBEX_MAX_GPIO_ENTRIES 16u + +typedef struct IbexDeviceDef IbexDeviceDef; + +typedef void (*ibex_dev_cfg_fn)(DeviceState *dev, const IbexDeviceDef *def, + DeviceState *parent); + +/* + * Structure defining a GPIO connection (in particular, IRQs) from the current + * device to a target device + */ +typedef struct { + /* Source GPIO */ + struct { + /* Name of source GPIO array or NULL for unnamed */ + const char *name; + /* Index of source output GPIO */ + int num; + } out; + + /* Target GPIO */ + struct { + /* Target device index */ + int index; + /* Name of target input GPIO array or NULL for unnamed */ + const char *name; + /* Index of target input GPIO */ + int num; + } in; +} IbexGpioConnDef; + +/* + * Structure defining the export of a device GPIO connection to the parent level + */ +typedef struct { + /* Device GPIO */ + struct { + /* Name of device GPIO array or NULL for unnamed */ + const char *name; + /* Index of device GPIO */ + int num; + } device; + + /* Parent GPIO */ + struct { + /* Name of parent GPIO array or NULL for unnamed */ + const char *name; + /* Index of parent GPIO */ + int num; + } parent; +} IbexGpioExportDef; + +typedef struct { + /* Name of the property to assign the linked device to */ + const char *propname; + /* Linked device index */ + int index; +} IbexDeviceLinkDef; + +typedef struct { + /* Clock source */ + struct { + /* Clock source device index */ + int index; + /* Clock name */ + const char *name; + } out; + /* Clock sink */ + struct { + /* Name of target input clock */ + const char *name; + /* Index of target input clock */ + int num; + } in; +} IbexClockConnDef; + +/* Type of device property */ +typedef enum { + IBEX_PROP_TYPE_BOOL, + IBEX_PROP_TYPE_INT, + IBEX_PROP_TYPE_UINT, + IBEX_PROP_TYPE_STR, +} IbexPropertyType; + +typedef struct { + /* Name of the property */ + const char *propname; + /* Type of property */ + IbexPropertyType type; + /* Value */ + union { + bool b; + int64_t i; + uint64_t u; + const char *s; + }; +} IbexDevicePropDef; + +typedef enum { + IBEX_MEM_MAP_ENTRY_FLAG_LAST, + IBEX_MEM_MAP_ENTRY_FLAG_SKIP, + IBEX_MEM_MAP_ENTRY_FLAG_COUNT +} IbexMemMapEntryFlags; + +typedef struct IbexMemMapEntry { + hwaddr base; + int8_t priority; + uint8_t flags; /* bitfield of IbexMemMapEntryFlags */ +} IbexMemMapEntry; + +#define IBEX_MEM_MAP_ENTRY_FLAG(_f_) (1u << (IBEX_MEM_MAP_ENTRY_FLAG_##_f_)) + +#define IBEX_INSTANCE_FLAG (1u << 31u) +#define IBEX_MAKE_INSTANCE_NUM(_ix_) (IBEX_INSTANCE_FLAG | (_ix_)) +#define IBEX_HAS_INSTANCE_NUM(_def_) \ + ((bool)(((_def_)->instance & IBEX_INSTANCE_FLAG))) +#define IBEX_GET_INSTANCE_NUM(_def_) \ + (IBEX_HAS_INSTANCE_NUM(_def_) ? \ + ((_def_)->instance & ~IBEX_INSTANCE_FLAG) : \ + UINT32_MAX) + +/* Device definition */ +struct IbexDeviceDef { + /* Registered type of the device */ + const char *type; + /* Optional name, may be NULL */ + const char *name; + /* + * Instance number, default to auto-numbering, using a monotonic incremental + * value following the declaration order. Use IBEX_MAKE_INSTANCE_NUM macro + * to specify a unique instance of the type, when an instance needs to be + * explictly referenced by its instance number. + */ + unsigned instance; + /* Optional configuration function */ + ibex_dev_cfg_fn cfg; + /* Array of memory map */ + const IbexMemMapEntry *memmap; + /* Array of GPIO connections */ + const IbexGpioConnDef *gpio; + /* Array of linked devices */ + const IbexDeviceLinkDef *link; + /* Array of properties */ + const IbexDevicePropDef *prop; + /* Array of clock sources */ + const IbexClockConnDef *clock; + /* Array of GPIO export */ + const IbexGpioExportDef *gpio_export; +}; + +/* Additional device mapping for external buses */ +typedef struct { + /* Registered type of the device */ + const char *type; + /* Instance number, default to 0 */ + int instance; + /* Array of memory map */ + const IbexMemMapEntry *memmap; +} IbexDeviceMapDef; + +/* + * Special memory address marked to flag a special MemMapEntry. + * Flagged MemMapEntry are used to select a memory region while mem mapping + * devices. There could be up to 4 different regions. + */ +#define IBEX_MEMMAP_REGIDX_COUNT 4u +#define IBEX_MEMMAP_REGIDX_MASK \ + ((IBEX_MEMMAP_REGIDX_COUNT) - 1u) /* address are always word-aligned */ +#define IBEX_MEMMAP_MAKE_REG(_addr_, _flag_) \ + ((_addr_) | (((uint32_t)_flag_) & IBEX_MEMMAP_REGIDX_MASK)) +#define IBEX_MEMMAP_MAKE_REG_MASK(_flag_) (1u << (_flag_)) +#define IBEX_MEMMAP_DEFAULT_REG_MASK (1u << 0u) +#define IBEX_MEMMAP_GET_REGIDX(_addr_) ((_addr_) & IBEX_MEMMAP_REGIDX_MASK) +#define IBEX_MEMMAP_GET_ADDRESS(_addr_) ((_addr_) & ~IBEX_MEMMAP_REGIDX_MASK) + +#define IBEX_GPIO_GRP_BITS 5u +#define IBEX_GPIO_GRP_COUNT (1u << (IBEX_GPIO_GRP_BITS)) +#define IBEX_GPIO_GRP_SHIFT (32u - IBEX_GPIO_GRP_BITS) +#define IBEX_GPIO_GRP_MASK ((IBEX_GPIO_GRP_COUNT) - 1u) << (IBEX_GPIO_GRP_SHIFT) +#define IBEX_GPIO_IDX_MASK (~(IBEX_GPIO_GRP_MASK)) +#define IBEX_GPIO_GET_IDX(_idx_) ((_idx_) & IBEX_GPIO_IDX_MASK) +#define IBEX_GPIO_GET_GRP(_idx_) \ + (((_idx_) & (IBEX_GPIO_GRP_MASK)) >> IBEX_GPIO_GRP_SHIFT) +#define IBEX_GPIO_MAKE_GRPIDX(_grp_, _ix_) \ + (((_grp_) << IBEX_GPIO_GRP_SHIFT) | ((_ix_) & IBEX_GPIO_IDX_MASK)) + +#define IBEX_DEVLINK_RMT_BITS 8u +#define IBEX_DEVLINK_RMT_COUNT (1u << (IBEX_DEVLINK_RMT_BITS)) +#define IBEX_DEVLINK_RMT_SHIFT (32u - IBEX_DEVLINK_RMT_BITS) +#define IBEX_DEVLINK_RMT_MASK \ + ((IBEX_DEVLINK_RMT_COUNT) - 1u) << (IBEX_DEVLINK_RMT_SHIFT) +#define IBEX_DEVLINK_IDX_MASK (~(IBEX_DEVLINK_RMT_MASK)) +#define IBEX_DEVLINK_DEVICE(_idx_) ((_idx_) & IBEX_DEVLINK_IDX_MASK) +#define IBEX_DEVLINK_REMOTE(_idx_) \ + (((_idx_) & (IBEX_DEVLINK_RMT_MASK)) >> IBEX_DEVLINK_RMT_SHIFT) +#define IBEX_DEVLINK_MAKE_RMTDEV(_par_, _ix_) \ + (((_par_) << IBEX_DEVLINK_RMT_SHIFT) | ((_ix_) & IBEX_DEVLINK_IDX_MASK)) + +/* MemMapEntry that should be ignored (i.e. skipped, not mapped) */ +#define IBEX_MEMMAP_LAST { .flags = IBEX_MEM_MAP_ENTRY_FLAG(LAST) } +#define IBEX_MEMMAP_SKIP { .flags = IBEX_MEM_MAP_ENTRY_FLAG(SKIP) } +#define IBEX_MEMMAP_IS_LAST(_mmap_) \ + ((bool)((_mmap_)->flags & IBEX_MEM_MAP_ENTRY_FLAG(LAST))) +#define IBEX_MEMMAP_IGNORE(_mmap_) \ + ((bool)((_mmap_)->flags & IBEX_MEM_MAP_ENTRY_FLAG(SKIP))) + +/* + * Create memory map entries, each arg is MemMapEntry definition + */ +#define MEMMAPENTRIES(...) \ + (const IbexMemMapEntry[]) \ + { \ + __VA_ARGS__, IBEX_MEMMAP_LAST \ + } + +/* + * Create GPIO connection entries, each arg is IbexGpioConnDef definition + */ +#define IBEXGPIOCONNDEFS(...) \ + (const IbexGpioConnDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .out = { .num = -1 } \ + } \ + } + +/* + * Create device link entries, each arg is IbexDeviceLinkDef definition + */ +#define IBEXDEVICELINKDEFS(...) \ + (const IbexDeviceLinkDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .propname = NULL \ + } \ + } + +/* + * Create clock connection entries, each arg is IbexClockConnDef definition + */ +#define IBEXCLOCKCONNDEFS(...) \ + (const IbexClockConnDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .out.name = NULL \ + } \ + } + +/* + * Create device property entries, each arg is IbexDevicePropDef definition + */ +#define IBEXDEVICEPROPDEFS(...) \ + (const IbexDevicePropDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .propname = NULL \ + } \ + } + +/* + * Create device additional map entries, each arg is IbexDeviceMapDef definition + */ +#define IBEXDEVICEMAPDEFS(...) \ + (const IbexDeviceMapDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .type = NULL \ + } \ + } + +/* + * Create device gpio export property entries, each arg is IbexGpioExportDef + * definition + */ +#define IBEXGPIOEXPORTDEFS(...) \ + (const IbexGpioExportDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .device = { .num = -1 }, .parent = { .num = -1 }, \ + } \ + } + +/* + * Create a IbexGpioConnDef to connect two unnamed GPIOs + */ +#define IBEX_GPIO(_irq_, _in_idx_, _num_) \ + { \ + .out = { \ + .num = (_irq_), \ + }, \ + .in = { \ + .index = (_in_idx_), \ + .num = (_num_), \ + } \ + } + +/* + * Create a IbexGpioConnDef to connect a SysBus IRQ to an unnamed GPIO + */ +#define IBEX_GPIO_SYSBUS_IRQ(_irq_, _in_idx_, _num_) \ + { \ + .out = { \ + .name = SYSBUS_DEVICE_GPIO_IRQ, \ + .num = (_irq_), \ + }, \ + .in = { \ + .index = (_in_idx_), \ + .num = (_num_), \ + } \ + } + +/* + * Create a IbexLinkDeviceDef to link one device to another + */ +#define IBEX_DEVLINK(_pname_, _idx_) \ + { \ + .propname = (_pname_), \ + .index = (_idx_), \ + } + +/* + * Create a IbexClockConnDef to connect a clock output to a clock input + */ +#define IBEX_CLOCK_CONN(_out_idx_, _out_type_, _out_name_, _in_name_, \ + _in_idx_) \ + { \ + .out = { \ + .index = (_out_idx_), \ + .type = (_out_type_), \ + .name = (_out_name_), \ + }, \ + .in = { \ + .name = (_in_name_), \ + .num = (_in_idx_), \ + } \ + } + +/* + * Create a IbexGpioExportDef to export a GPIO + */ +#define IBEX_EXPORT_GPIO(_dname_, _dnum_, _pname_, _pnum_) \ + { \ + .device = { \ + .name = (_dname_), \ + .num = (_dnum_), \ + }, \ + .parent = { \ + .name = (_pname_), \ + .num = (_pnum_), \ + }, \ + } + +/* + * Create a IbexGpioExportDef to export a SysBus IRQ + */ +#define IBEX_EXPORT_SYSBUS_IRQ(_dnum_, _pname_, _pnum_) \ + IBEX_EXPORT_GPIO(NULL, _dnum_, _pname_, _pnum_) + +/* + * Create a boolean device property + */ +#define IBEX_DEV_BOOL_PROP(_pname_, _b_) \ + { \ + .propname = (_pname_), \ + .type = IBEX_PROP_TYPE_BOOL, \ + .b = (_b_), \ + } + +/* + * Create a signed integer device property + */ +#define IBEX_DEV_INT_PROP(_pname_, _i_) \ + { \ + .propname = (_pname_), \ + .type = IBEX_PROP_TYPE_INT, \ + .i = (_i_), \ + } + +/* + * Create an unsigned integer device property + */ +#define IBEX_DEV_UINT_PROP(_pname_, _u_) \ + { \ + .propname = (_pname_), \ + .type = IBEX_PROP_TYPE_UINT, \ + .u = (_u_), \ + } + +/* + * Create a string device property + */ +#define IBEX_DEV_STRING_PROP(_pname_, _s_) \ + { \ + .propname = (_pname_), \ + .type = IBEX_PROP_TYPE_STR, \ + .s = (_s_), \ + } + +void ibex_mmio_map_device(SysBusDevice *dev, MemoryRegion *mr, unsigned nr, + hwaddr addr, int priority); +DeviceState **ibex_create_devices(const IbexDeviceDef *defs, unsigned count, + DeviceState *parent); +#define ibex_link_devices(_devs_, _defs_, _cnt_) \ + 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, + const IbexDeviceDef *defs, unsigned count); +void ibex_realize_devices(DeviceState **devices, BusState *bus, + const IbexDeviceDef *defs, unsigned count); +void ibex_clock_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count); +void ibex_connect_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count); +#define ibex_map_devices(_devs_, _mrs_, _defs_, _cnt_) \ + ibex_map_devices_offset(_devs_, _mrs_, _defs_, _cnt_, 0u) +#define ibex_map_devices_offset(_devs_, _mrs_, _defs_, _cnt_, _off_) \ + ibex_map_devices_mask_offset(_devs_, _mrs_, _defs_, _cnt_, \ + IBEX_MEMMAP_DEFAULT_REG_MASK, _off_) +#define ibex_map_devices_mask(_devs_, _mrs_, _defs_, _cnt_, _msk_) \ + ibex_map_devices_mask_offset(_devs_, _mrs_, _defs_, _cnt_, _msk_, 0u) +void ibex_map_devices_mask_offset(DeviceState **devices, MemoryRegion **mrs, + const IbexDeviceDef *defs, unsigned count, + uint32_t region_mask, uint32_t offset); +#define ibex_map_devices_ext_mask(_dev_, _mrs_, _defs_, _cnt_, _msk_) \ + ibex_map_devices_ext_mask_offset(_dev_, _mrs_, _defs_, _cnt_, _msk_, 0u) +void ibex_map_devices_ext_mask_offset( + DeviceState *dev, MemoryRegion **mrs, const IbexDeviceMapDef *defs, + unsigned count, uint32_t region_mask, uint32_t offset); +void ibex_configure_devices(DeviceState **devices, BusState *bus, + const IbexDeviceDef *defs, unsigned count); +void ibex_identify_devices(DeviceState **devices, const char *id_prop, + const char *id_value, bool id_prepend, + unsigned count); +void ibex_configure_devices_with_id(DeviceState **devices, BusState *bus, + const char *id_prop, const char *id_value, + bool id_prepend, const IbexDeviceDef *defs, + unsigned count); +void ibex_export_gpios(DeviceState **devices, DeviceState *parent, + const IbexDeviceDef *defs, unsigned count); +void ibex_connect_soc_devices(DeviceState **soc_devices, DeviceState **devices, + const IbexDeviceDef *defs, unsigned count); +DeviceState *ibex_get_child_device(DeviceState *s, const char *typename, + unsigned instance); +/* + * Utility function to configure unimplemented device. + * The Ibex device definition should have one defined memory entry, and an + * optional name. + */ +void ibex_unimp_configure(DeviceState *dev, const IbexDeviceDef *def, + DeviceState *parent); + +/* ------------------------------------------------------------------------ */ +/* CPU */ +/* ------------------------------------------------------------------------ */ + +/* + * Load an ELF application into a CPU address space. + * + * @cpu the CPU to load the application for; maybe NULL in which case the + * first valid CPU address space is used. Except if the machine defines + * and sets a "ignore-elf-entry" boolean property, the PC of the specified + * vCPU - or all vCPUs if not specified - is assigned the entry point of + * the ELF file. + * + * @return the Ibex-constrained ELF entry point (or -1 on error) + */ +uint32_t ibex_load_kernel(CPUState *cpu); + +/* + * Helper for device debugging: report the current guest PC, if any. + * + * If a HW access is performed from another device but the CPU, reported PC + * is 0. + */ +uint32_t ibex_get_current_pc(void); + +/* + * Helper for device debugging: report the current guest CPU index, if any. + * + * If a HW access is performed from another device but the CPU, reported CPU + * is -1. + */ +int ibex_get_current_cpu(void); + +enum { + RV_GPR_PC = (1u << 0u), + RV_GPR_RA = (1u << 1u), + RV_GPR_SP = (1u << 2u), + RV_GPR_GP = (1u << 3u), + RV_GPR_TP = (1u << 4u), + RV_GPR_T0 = (1u << 5u), + RV_GPR_T1 = (1u << 6u), + RV_GPR_T2 = (1u << 7u), + RV_GPR_S0 = (1u << 8u), + RV_GPR_S1 = (1u << 9u), + RV_GPR_A0 = (1u << 10u), + RV_GPR_A1 = (1u << 11u), + RV_GPR_A2 = (1u << 12u), + RV_GPR_A3 = (1u << 13u), + RV_GPR_A4 = (1u << 14u), + RV_GPR_A5 = (1u << 15u), + RV_GPR_A6 = (1u << 16u), + RV_GPR_A7 = (1u << 17u), + RV_GPR_S2 = (1u << 18u), + RV_GPR_S3 = (1u << 19u), + RV_GPR_S4 = (1u << 20u), + RV_GPR_S5 = (1u << 21u), + RV_GPR_S6 = (1u << 22u), + RV_GPR_S7 = (1u << 23u), + RV_GPR_S8 = (1u << 24u), + RV_GPR_S9 = (1u << 25u), + RV_GPR_S10 = (1u << 26u), + RV_GPR_S11 = (1u << 27u), + RV_GPR_T3 = (1u << 28u), + RV_GPR_T4 = (1u << 29u), + RV_GPR_T5 = (1u << 30u), + RV_GPR_T6 = (1u << 31u), +}; + +/* + * Log current vCPU registers. + * + * @regbm is a bitmap of registers to be dumped [x1..t6], pc replace x0 + */ +void ibex_log_vcpu_registers(uint64_t regbm); + +/* ------------------------------------------------------------------------ */ +/* Miscellaneous utilities */ +/* ------------------------------------------------------------------------ */ + +/* + * Find a host function name by its address. + * + * @fn the address of the function + * @return the function name, or NULL if not found or not supported + */ +const char *ibex_common_get_func_name_by_addr(void *fn); + +/* ------------------------------------------------------------------------ */ +/* CharDev utilities */ +/* ------------------------------------------------------------------------ */ + +/* + * Find a char device by its id, e.g. "-chardev type,id=,...`" + * + * @chrid the id of the char device + * @return the char device if found, @c NULL otherwise. + */ +Chardev *ibex_get_chardev_by_id(const char *chrid); + + +#endif /* HW_RISCV_IBEX_COMMON_H */ diff --git a/include/hw/riscv/ibex_gpio.h b/include/hw/riscv/ibex_gpio.h new file mode 100644 index 0000000000000..866b437fafd39 --- /dev/null +++ b/include/hw/riscv/ibex_gpio.h @@ -0,0 +1,111 @@ +/* + * QEMU Ibex GPIOs + * + * Copyright (c) 2024 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef HW_RISCV_IBEX_GPIO_H +#define HW_RISCV_IBEX_GPIO_H + +#include "hw/registerfields.h" +#include "hw/riscv/ibex_irq.h" + +/* GPIO multi-level storage */ +typedef int ibex_gpio; + +/* set if GPIO is not Hi-Z */ +SHARED_FIELD(IBEX_GPIO_ACTIVE, 0u, 1u) +/* GPIO signal level */ +SHARED_FIELD(IBEX_GPIO_LEVEL, 1u, 1u) +/* GPIO is applying a strong signed (vs. weak pull-up or pull-down) */ +SHARED_FIELD(IBEX_GPIO_STRENGTH, 2u, 1u) +/* always 0 (reserved for strength extension) */ +SHARED_FIELD(IBEX_GPIO_SBZ, 3u, 5u) +/* Ibex GPIO 'G' marker (sanity check) */ +SHARED_FIELD(IBEX_GPIO_FLAG, 8u, 8u) + +/* default initialization: Hi-Z */ +#define IBEX_GPIO_INIT ('G' << IBEX_GPIO_FLAG_SHIFT) +#define IBEX_GPIO_HIZ IBEX_GPIO_INIT +#define IBEX_GPIO_FROM_ACTIVE_SIG(_lvl_) \ + (IBEX_GPIO_INIT | IBEX_GPIO_ACTIVE_MASK | IBEX_GPIO_STRENGTH_MASK | \ + (((int)(bool)(_lvl_)) << IBEX_GPIO_LEVEL_SHIFT)) +#define IBEX_GPIO_FROM_WEAK_SIG(_lvl_) \ + (IBEX_GPIO_INIT | IBEX_GPIO_ACTIVE_MASK | \ + (((int)(bool)(_lvl_)) << IBEX_GPIO_LEVEL_SHIFT)) +#define IBEX_GPIO_LOW IBEX_GPIO_FROM_ACTIVE_SIG(false) +#define IBEX_GPIO_HIGH IBEX_GPIO_FROM_ACTIVE_SIG(true) +#define IBEX_GPIO_PULL_DOWN IBEX_GPIO_FROM_WEAK_SIG(false) +#define IBEX_GPIO_PULL_UP IBEX_GPIO_FROM_WEAK_SIG(true) + + +static inline bool ibex_gpio_level(ibex_gpio level) +{ + return (bool)(level & IBEX_GPIO_LEVEL_MASK); +} + +static inline bool ibex_gpio_is_hiz(ibex_gpio level) +{ + return !(level & IBEX_GPIO_ACTIVE_MASK); +} + +static inline bool ibex_gpio_is_weak(ibex_gpio level) +{ + return !(level & IBEX_GPIO_STRENGTH_MASK); +} + +static inline bool ibex_gpio_check(ibex_gpio level) +{ + return (level & (IBEX_GPIO_FLAG_MASK | IBEX_GPIO_SBZ_MASK)) == + ('G' << IBEX_GPIO_FLAG_SHIFT); +} + +/* + * Debug representation of an Ibex GPIO signal. + * + * 'X': invalid signal (not a valid Ibex IO) + * 'z': Hi-Z + * 'H': active high + * 'L': active low + * 'h': weak high / pull up + * 'l': weak low / pull down + */ +static inline char ibex_gpio_repr(ibex_gpio level) +{ + return ibex_gpio_check(level) ? + (!ibex_gpio_is_hiz(level) ? + (ibex_gpio_level(level) ? 'H' : 'L') + + (ibex_gpio_is_weak(level) ? 0x20 : 0) : + 'z') : + 'X'; +} + +static inline void ibex_gpio_assert(ibex_gpio level) +{ + g_assert(ibex_gpio_check(level)); +} + +ibex_gpio ibex_gpio_combine(const ibex_gpio *levels, unsigned count); + +bool ibex_gpio_parse_level(const char *name, const char *value, ibex_gpio *obj, + Error **errp); + +ObjectProperty * +object_property_add_ibex_gpio(Object *obj, const char *name, ibex_gpio *value); + +#endif /* HW_RISCV_IBEX_GPIO_H */ diff --git a/include/hw/riscv/ibex_irq.h b/include/hw/riscv/ibex_irq.h new file mode 100644 index 0000000000000..957a88c20338b --- /dev/null +++ b/include/hw/riscv/ibex_irq.h @@ -0,0 +1,103 @@ +/* + * QEMU lowRISC Ibex IRQ wrapper + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_RISCV_IBEX_IRQ_H +#define HW_RISCV_IBEX_IRQ_H + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/qdev-core.h" +#include "hw/sysbus.h" + +/** Simple IRQ wrapper to limit propagation of no-change calls */ +typedef struct { + qemu_irq irq; + int level; +} IbexIRQ; + +static inline bool ibex_irq_is_connected(const IbexIRQ *ibex_irq) +{ + return qemu_irq_is_connected(ibex_irq->irq); +} + +static inline int ibex_irq_get_level(const IbexIRQ *ibex_irq) +{ + return ibex_irq->level; +} + +static inline bool ibex_irq_set(IbexIRQ *ibex_irq, int level) +{ + if (level != ibex_irq->level) { + ibex_irq->level = level; + qemu_set_irq(ibex_irq->irq, level); + return true; + } + + return false; +} + +static inline bool ibex_irq_raise(IbexIRQ *irq) +{ + return ibex_irq_set(irq, 1); +} + +static inline bool ibex_irq_lower(IbexIRQ *irq) +{ + return ibex_irq_set(irq, 0); +} + +static inline void ibex_qdev_init_irq_default(Object *obj, IbexIRQ *irq, + const char *name, int level) +{ + irq->level = level; + qdev_init_gpio_out_named(DEVICE(obj), &irq->irq, name, 1); +} + +static inline void ibex_qdev_init_irq(Object *obj, IbexIRQ *irq, + const char *name) +{ + ibex_qdev_init_irq_default(obj, irq, name, 0); +} + +static inline void ibex_qdev_init_irqs_default( + Object *obj, IbexIRQ *irqs, const char *name, unsigned count, int level) +{ + for (unsigned ix = 0; ix < count; ix++) { + ibex_qdev_init_irq_default(obj, &irqs[ix], name, level); + } +} + +static inline void ibex_qdev_init_irqs(Object *obj, IbexIRQ *irqs, + const char *name, unsigned count) +{ + ibex_qdev_init_irqs_default(obj, irqs, name, count, 0); +} + +static inline void ibex_sysbus_init_irq(Object *obj, IbexIRQ *irq) +{ + irq->level = 0; + sysbus_init_irq(SYS_BUS_DEVICE(obj), &irq->irq); +} + +#endif /* HW_RISCV_IBEX_IRQ_H */ diff --git a/include/hw/riscv/opentitan.h b/include/hw/riscv/opentitan.h index 609473d07b400..2cbf3b42f0425 100644 --- a/include/hw/riscv/opentitan.h +++ b/include/hw/riscv/opentitan.h @@ -19,13 +19,13 @@ #ifndef HW_OPENTITAN_H #define HW_OPENTITAN_H -#include "hw/riscv/riscv_hart.h" #include "hw/intc/sifive_plic.h" #include "hw/char/ibex_uart.h" #include "hw/timer/ibex_timer.h" #include "hw/ssi/ibex_spi_host.h" #include "hw/boards.h" #include "qom/object.h" +#include "include/hw/riscv/riscv_hart.h" #define TYPE_RISCV_IBEX_SOC "riscv.lowrisc.ibex.soc" OBJECT_DECLARE_SIMPLE_TYPE(LowRISCIbexSoCState, RISCV_IBEX_SOC) diff --git a/include/hw/ssi/ssi.h b/include/hw/ssi/ssi.h index 2ad8033d8f5b7..60e4e2f21889f 100644 --- a/include/hw/ssi/ssi.h +++ b/include/hw/ssi/ssi.h @@ -18,6 +18,9 @@ typedef enum SSICSMode SSICSMode; +#define TYPE_SSI_BUS "SSI" +OBJECT_DECLARE_SIMPLE_TYPE(SSIBus, SSI_BUS) + #define TYPE_SSI_PERIPHERAL "ssi-peripheral" OBJECT_DECLARE_TYPE(SSIPeripheral, SSIPeripheralClass, SSI_PERIPHERAL) diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h index 75f4e434085a7..58b0f71122bb2 100644 --- a/target/riscv/cpu-qom.h +++ b/target/riscv/cpu-qom.h @@ -34,6 +34,7 @@ #define TYPE_RISCV_CPU_BASE32 RISCV_CPU_TYPE_NAME("rv32") #define TYPE_RISCV_CPU_BASE64 RISCV_CPU_TYPE_NAME("rv64") #define TYPE_RISCV_CPU_BASE128 RISCV_CPU_TYPE_NAME("x-rv128") +#define TYPE_RISCV_CPU_LOWRISC_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") #define TYPE_RISCV_CPU_RV32I RISCV_CPU_TYPE_NAME("rv32i") #define TYPE_RISCV_CPU_RV32E RISCV_CPU_TYPE_NAME("rv32e") #define TYPE_RISCV_CPU_RV64I RISCV_CPU_TYPE_NAME("rv64i") @@ -42,7 +43,6 @@ #define TYPE_RISCV_CPU_RVA22S64 RISCV_CPU_TYPE_NAME("rva22s64") #define TYPE_RISCV_CPU_RVA23U64 RISCV_CPU_TYPE_NAME("rva23u64") #define TYPE_RISCV_CPU_RVA23S64 RISCV_CPU_TYPE_NAME("rva23s64") -#define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") #define TYPE_RISCV_CPU_SHAKTI_C RISCV_CPU_TYPE_NAME("shakti-c") #define TYPE_RISCV_CPU_SIFIVE_E RISCV_CPU_TYPE_NAME("sifive-e") #define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31") diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 386692778c843..b73eae4b29a43 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -1066,7 +1066,8 @@ static void riscv_cpu_set_irq(void *opaque, int irq, int level) } break; default: - g_assert_not_reached(); + /* Handle platform / custom local interrupts */ + riscv_cpu_update_mip(env, 1ULL << irq, BOOL_TO_MASK(level)); } } else if (irq < (IRQ_LOCAL_MAX + IRQ_LOCAL_GUEST_MAX)) { /* Require H-extension for handling guest local interrupts */ @@ -2681,6 +2682,7 @@ static const Property riscv_cpu_properties[] = { #ifndef CONFIG_USER_ONLY DEFINE_PROP_UINT64("resetvec", RISCVCPU, env.resetvec, DEFAULT_RSTVEC), + DEFINE_PROP_UINT64("mtvec", RISCVCPU, cfg.mtvec, 0u), DEFINE_PROP_UINT64("mseccfg", RISCVCPU, cfg.mseccfg, 0u), DEFINE_PROP_ARRAY("pmp_cfg", RISCVCPU, cfg.pmp_cfg_count, cfg.pmp_cfg, qdev_prop_uint8, uint8_t), @@ -3064,20 +3066,20 @@ static const TypeInfo riscv_cpu_type_infos[] = { .misa_mxl_max = MXL_RV32, ), - DEFINE_RISCV_CPU(TYPE_RISCV_CPU_IBEX, TYPE_RISCV_VENDOR_CPU, + DEFINE_ABSTRACT_RISCV_CPU(TYPE_RISCV_CPU_LOWRISC_IBEX, TYPE_RISCV_VENDOR_CPU, .misa_mxl_max = MXL_RV32, .misa_ext = RVI | RVM | RVC | RVU, .priv_spec = PRIV_VERSION_1_12_0, .cfg.max_satp_mode = VM_1_10_MBARE, .cfg.ext_zifencei = true, .cfg.ext_zicsr = true, - .cfg.pmp = true, - .cfg.ext_smepmp = true, - .cfg.ext_zba = true, - .cfg.ext_zbb = true, - .cfg.ext_zbc = true, - .cfg.ext_zbs = true + .cfg.marchid = 0x16u, + .cfg.mtvec = 0x00000001u, + +#ifndef CONFIG_USER_ONLY + .custom_csrs = ibex_csr_list, +#endif ), DEFINE_RISCV_CPU(TYPE_RISCV_CPU_SIFIVE_E31, TYPE_RISCV_CPU_SIFIVE_E, diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index b7bcc718cdfd1..1ca9fbe410525 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -491,6 +491,10 @@ struct CPUArchState { uint64_t hstateen[SMSTATEEN_MAX_COUNT]; uint64_t sstateen[SMSTATEEN_MAX_COUNT]; uint64_t henvcfg; + + /* Ibex custom CSRs */ + target_ulong cpuctrlsts; + target_ulong secureseed; #endif /* Fields from here on are preserved across CPU reset. */ @@ -1000,6 +1004,9 @@ target_ulong riscv_new_csr_seed(target_ulong new_value, const char *satp_mode_str(uint8_t satp_mode, bool is_32_bit); +/* In ibex_csr.c */ +extern const RISCVCSR ibex_csr_list[]; + /* In th_csr.c */ extern const RISCVCSR th_csr_list[]; diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index 0a027156f3bc7..0f21adab45e99 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -155,6 +155,8 @@ BOOL_FIELD(misa_w) BOOL_FIELD(short_isa_string) +TYPED_FIELD(uint64_t, mtvec, 0) + TYPED_FIELD(uint32_t, mvendorid, 0) TYPED_FIELD(uint64_t, marchid, 0) TYPED_FIELD(uint64_t, mimpid, 0) diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 65f8193db41a2..279db790c6dc1 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -2291,10 +2291,10 @@ void riscv_cpu_do_interrupt(CPUState *cs) riscv_cpu_get_trap_name(cause, async)); qemu_log_mask(CPU_LOG_INT, - "%s: hart:"TARGET_FMT_ld", async:%d, cause:"TARGET_FMT_lx", " + "%s: hart:%d/"TARGET_FMT_ld", async:%d, cause:"TARGET_FMT_lx", " "epc:0x"TARGET_FMT_lx", tval:0x"TARGET_FMT_lx", desc=%s\n", - __func__, env->mhartid, async, cause, env->pc, tval, - riscv_cpu_get_trap_name(cause, async)); + __func__, cs->cpu_index, env->mhartid, async, cause, env->pc, + tval, riscv_cpu_get_trap_name(cause, async)); mode = env->priv <= PRV_S && cause < 64 && (((deleg >> cause) & 1) || s_injected || vs_injected) ? PRV_S : PRV_M; diff --git a/target/riscv/csr.c b/target/riscv/csr.c index c54f1777e1eaf..e8c2fbfd5612c 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -1031,7 +1031,18 @@ static RISCVException write_vcsr(CPURISCVState *env, int csrno, /* User Timers and Counters */ static target_ulong get_ticks(bool shift) { - int64_t val = cpu_get_host_ticks(); + int64_t val; + +#if !defined(CONFIG_USER_ONLY) + if (icount_enabled()) { + val = icount_get_raw(); + } else { + val = cpu_get_host_ticks(); + } +#else + val = cpu_get_host_ticks(); +#endif + target_ulong result = shift ? val >> 32 : val; return result; diff --git a/target/riscv/ibex_csr.c b/target/riscv/ibex_csr.c new file mode 100644 index 0000000000000..b3c1ef34fd28a --- /dev/null +++ b/target/riscv/ibex_csr.c @@ -0,0 +1,148 @@ +/* + * QEMU LowRisc Ibex core features + * + * Copyright (c) 2023-2024 Rivos, Inc. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +/* NOLINTBEGIN(misc-header-include-cycle) */ +#include "cpu.h" +/* NOLINTEND(misc-header-include-cycle) */ + +#if !defined(CONFIG_USER_ONLY) + +/* Custom CSRs */ +#define CSR_CPUCTRLSTS 0x7c0 +#define CSR_SECURESEED 0x7c1 + +#define CPUCTRLSTS_ICACHE_ENABLE 0x000 +#define CPUCTRLSTS_DATA_IND_TIMING 0x001 +#define CPUCTRLSTS_DUMMY_INSTR_EN 0x002 +#define CPUCTRLSTS_DUMMY_INSTR_MASK 0x038 +#define CPUCTRLSTS_SYNC_EXC_SEEN 0x040 +#define CPUCTRLSTS_DOUBLE_FAULT_SEEN 0x080 +#define CPUCTRLSTS_IC_SCR_KEY_VALID 0x100 + +static RISCVException read_cpuctrlsts(CPURISCVState *env, int csrno, + target_ulong *val) +{ + (void)csrno; + *val = CPUCTRLSTS_IC_SCR_KEY_VALID | env->cpuctrlsts; + return RISCV_EXCP_NONE; +} + +static RISCVException write_cpuctrlsts(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + (void)csrno; + (void)ra; + /* b7 can only be cleared */ + env->cpuctrlsts &= ~0xbf; + /* b6 should be cleared on mret */ + env->cpuctrlsts |= val & 0x3f; + return RISCV_EXCP_NONE; +} + +static RISCVException read_secureseed(CPURISCVState *env, int csrno, + target_ulong *val) +{ + (void)env; + (void)csrno; + /* + * "Seed values are not actually stored in a register and so reads to this + * register will always return zero." + */ + *val = 0; + return RISCV_EXCP_NONE; +} + +static RISCVException write_secureseed(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + (void)env; + (void)csrno; + (void)val; + (void)ra; + return RISCV_EXCP_NONE; +} + +static RISCVException any(CPURISCVState *env, int csrno) +{ + (void)env; + (void)csrno; + /* + * unfortunately, this predicate is not public, so duplicate the standard + * implementation + */ + return RISCV_EXCP_NONE; +} + +static RISCVException read_mtvec(CPURISCVState *env, int csrno, + target_ulong *val) +{ + (void)csrno; + *val = env->mtvec; + + return RISCV_EXCP_NONE; +} + +static RISCVException write_mtvec(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + (void)csrno; + (void)ra; + /* bits [1:0] encode mode; Ibex only supports 1 = vectored */ + if ((val & 3u) != 1u) { + qemu_log_mask(LOG_UNIMP, + "CSR_MTVEC: reserved mode not supported 0x" TARGET_FMT_lx + "\n", + val); + /* WARL, Ibex will tie any invalid mode writes to 0b01 (vectored) */ + val &= ~3u; + val |= 1u; + } + + /* bits [7:2] are always 0, address should be aligned in 256 bytes */ + env->mtvec = val & ~0xFCu; + + return RISCV_EXCP_NONE; +} + +#endif /* !defined(CONFIG_USER_ONLY) */ + +const RISCVCSR ibex_csr_list[] = { +#if !defined(CONFIG_USER_ONLY) + { + .csrno = CSR_MTVEC, + .csr_ops = { "mtvec", any, &read_mtvec, &write_mtvec }, + }, + { + .csrno = CSR_CPUCTRLSTS, + .csr_ops = { "cpuctrlsts", any, &read_cpuctrlsts, &write_cpuctrlsts }, + }, + { + .csrno = CSR_SECURESEED, + .csr_ops = { "secureseed", any, &read_secureseed, &write_secureseed }, + }, +#endif /* !defined(CONFIG_USER_ONLY) */ + {}, +}; diff --git a/target/riscv/meson.build b/target/riscv/meson.build index fdefe88ccdd3a..a071852f4c4bd 100644 --- a/target/riscv/meson.build +++ b/target/riscv/meson.build @@ -16,6 +16,7 @@ riscv_ss.add(files( 'cpu.c', 'cpu_helper.c', 'csr.c', + 'ibex_csr.c', 'fpu_helper.c', 'gdbstub.c', 'op_helper.c', From e62fe2ef7d2f78a71756340a8a8fd806400fcc3a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 8 Jan 2026 14:16:07 +0000 Subject: [PATCH 08/18] [ot] hw/core: Use resettable API to maintain CPU on reset Add the capability to maintain CPU in reset. The Resettable API has added a new way to manage reset, where resetting a device not only trigger a transient reset, but enable devices to be held in reset and manage a reset tree. This commit adds this resettable feature to CPUs, so they can be held in reset and released once other devices in the machine are released. The CPU reset is no longer a transient state but a stable state. The CPU reset state can be tracked with a new attribute: held_in_reset. This feature is useful to better emulate SoC like OpenTitan where the CPU is actively held in Reset while other devices complete their own initialization, such as the power manager and ROM controller. It also enables to update the CPU configuration and attributes while the CPUs are held in reset. On RISC-V CPUs, reset vector and mtvec registers for example can be updated before releasing the harts from reset. Signed-off-by: Emmanuel Blot --- accel/tcg/cpu-exec.c | 4 ++++ hw/core/cpu-common.c | 11 +++++++++++ include/hw/core/cpu.h | 2 ++ target/riscv/cpu.c | 23 +++++++++++++++++++++-- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index 7c20d9db122e5..17d9ca9b0d79a 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -653,6 +653,10 @@ static inline void tb_add_jump(TranslationBlock *tb, int n, static inline bool cpu_handle_halt(CPUState *cpu) { #ifndef CONFIG_USER_ONLY + if (unlikely(cpu->disabled)) { + return true; + } + if (cpu->halted) { const TCGCPUOps *tcg_ops = cpu->cc->tcg_ops; bool leave_halt = tcg_ops->cpu_exec_halt(cpu); diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 8c306c89e4530..8758ecba957d8 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -104,6 +104,16 @@ void cpu_reset(CPUState *cpu) trace_cpu_reset(cpu->cpu_index); } +static void cpu_common_reset_enter(Object *obj, ResetType type) +{ + CPUState *cpu = CPU(obj); + + if (qemu_loglevel_mask(CPU_LOG_RESET)) { + qemu_log("CPU Reset Enter (CPU %d)\n", cpu->cpu_index); + log_cpu_state(cpu, cpu->cc->reset_dump_flags); + } +} + static void cpu_common_reset_hold(Object *obj, ResetType type) { CPUState *cpu = CPU(obj); @@ -379,6 +389,7 @@ static void cpu_common_class_init(ObjectClass *klass, const void *data) set_bit(DEVICE_CATEGORY_CPU, dc->categories); dc->realize = cpu_common_realizefn; dc->unrealize = cpu_common_unrealizefn; + rc->phases.enter = cpu_common_reset_enter; rc->phases.hold = cpu_common_reset_hold; rc->phases.exit = cpu_common_reset_exit; cpu_class_init_props(dc); diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 9615051774d75..b0482c96ba91b 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -499,6 +499,8 @@ struct CPUState { /* Should CPU start in powered-off state? */ bool start_powered_off; + /* Is CPU currently currently disabled? */ + bool disabled; bool unplug; bool crash_occurred; diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index b73eae4b29a43..9673448be7b7b 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -721,7 +721,6 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type) } env->mcause = 0; env->miclaim = MIP_SGEIP; - env->pc = env->resetvec; env->bins = 0; env->two_stage_lookup = false; @@ -817,6 +816,25 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type) #endif } +static void riscv_cpu_reset_exit(Object *obj, ResetType type) +{ + CPUState *cs = CPU(obj); + RISCVCPU *cpu = RISCV_CPU(cs); + RISCVCPUClass *mcc = RISCV_CPU_GET_CLASS(cpu); + +#ifndef CONFIG_USER_ONLY + CPURISCVState *env = &cpu->env; + + /* reset vector and mtvec may be updated while hart is in reset */ + env->pc = env->resetvec; + env->mtvec = cpu->cfg.mtvec; +#endif + + if (mcc->parent_phases.exit) { + mcc->parent_phases.exit(obj, type); + } +} + static void riscv_cpu_disas_set_info(CPUState *s, disassemble_info *info) { RISCVCPU *cpu = RISCV_CPU(s); @@ -2753,7 +2771,8 @@ static void riscv_cpu_common_class_init(ObjectClass *c, const void *data) device_class_set_parent_realize(dc, riscv_cpu_realize, &mcc->parent_realize); - resettable_class_set_parent_phases(rc, NULL, riscv_cpu_reset_hold, NULL, + resettable_class_set_parent_phases(rc, NULL, riscv_cpu_reset_hold, + riscv_cpu_reset_exit, &mcc->parent_phases); cc->class_by_name = riscv_cpu_class_by_name; From 7b8f427390d510d1a35a4c11206b4e6fe642e437 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 7 Jan 2026 11:04:17 +0000 Subject: [PATCH 09/18] [ot] hw/jtag: jtag: add JTAG interface with remote-bitbang support --- hw/Kconfig | 3 + hw/jtag/Kconfig | 8 + hw/jtag/meson.build | 2 + hw/jtag/tap_ctrl.c | 42 ++ hw/jtag/tap_ctrl_rbb.c | 723 +++++++++++++++++++++++++++++++++ hw/jtag/trace-events | 13 + hw/jtag/trace.h | 1 + hw/meson.build | 2 + include/hw/jtag/tap_ctrl.h | 92 +++++ include/hw/jtag/tap_ctrl_rbb.h | 35 ++ meson.build | 1 + 11 files changed, 922 insertions(+) create mode 100644 hw/jtag/Kconfig create mode 100644 hw/jtag/meson.build create mode 100644 hw/jtag/tap_ctrl.c create mode 100644 hw/jtag/tap_ctrl_rbb.c create mode 100644 hw/jtag/trace-events create mode 100644 hw/jtag/trace.h create mode 100644 include/hw/jtag/tap_ctrl.h create mode 100644 include/hw/jtag/tap_ctrl_rbb.h diff --git a/hw/Kconfig b/hw/Kconfig index 9e6c789ae7ee5..49caf6df272a4 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -69,6 +69,9 @@ source sparc64/Kconfig source tricore/Kconfig source xtensa/Kconfig +# JTAG devices +source jtag/Kconfig + # Symbols used by multiple targets config TEST_DEVICES bool diff --git a/hw/jtag/Kconfig b/hw/jtag/Kconfig new file mode 100644 index 0000000000000..1e3eddbf965e8 --- /dev/null +++ b/hw/jtag/Kconfig @@ -0,0 +1,8 @@ +# JTAG devices + +config TAP_CTRL + bool + +config TAP_CTRL_RBB + select TAP_CTRL + bool diff --git a/hw/jtag/meson.build b/hw/jtag/meson.build new file mode 100644 index 0000000000000..677ea188ee5e2 --- /dev/null +++ b/hw/jtag/meson.build @@ -0,0 +1,2 @@ +system_ss.add(when: 'CONFIG_TAP_CTRL', if_true: files('tap_ctrl.c')) +system_ss.add(when: 'CONFIG_TAP_CTRL_RBB', if_true: files('tap_ctrl_rbb.c')) diff --git a/hw/jtag/tap_ctrl.c b/hw/jtag/tap_ctrl.c new file mode 100644 index 0000000000000..e335e584045e7 --- /dev/null +++ b/hw/jtag/tap_ctrl.c @@ -0,0 +1,42 @@ +/* + * QEMU JTAG TAP controller interface + * + * 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. + */ + +#include "qemu/osdep.h" +#include "hw/jtag/tap_ctrl.h" + +static const TypeInfo tap_ctrl_info = { + .name = TYPE_TAP_CTRL_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(TapCtrlIfClass), +}; + +static void tap_ctrl_register_types(void) +{ + type_register_static(&tap_ctrl_info); +} + +type_init(tap_ctrl_register_types); diff --git a/hw/jtag/tap_ctrl_rbb.c b/hw/jtag/tap_ctrl_rbb.c new file mode 100644 index 0000000000000..4554b2065b1ae --- /dev/null +++ b/hw/jtag/tap_ctrl_rbb.c @@ -0,0 +1,723 @@ +/* + * QEMU JTAG TAP controller for the OpenOCD/Spike Remote BigBang protocol + * + * Copyright (c) 2022-2024 Rivos, Inc. + * Author(s): + * Emmanuel Blot + * + * For details check the documentation here: + * https://github.com/openocd-org/openocd/blob/master/ + * doc/manual/jtag/drivers/remote_bitbang.txt + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "chardev/char-fe.h" +#include "chardev/char-socket.h" +#include "chardev/char.h" +#include "hw/jtag/tap_ctrl.h" +#include "hw/jtag/tap_ctrl_rbb.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/resettable.h" +#include "system/runstate.h" +#include "trace.h" + + +typedef enum { + TEST_LOGIC_RESET, + RUN_TEST_IDLE, + SELECT_DR_SCAN, + CAPTURE_DR, + SHIFT_DR, + EXIT1_DR, + PAUSE_DR, + EXIT2_DR, + UPDATE_DR, + SELECT_IR_SCAN, + CAPTURE_IR, + SHIFT_IR, + EXIT1_IR, + PAUSE_IR, + EXIT2_IR, + UPDATE_IR, + _TAP_STATE_COUNT +} TAPState; + +typedef TapDataHandler *tap_ctrl_data_reg_extender_t(uint64_t value); + +typedef struct TapCtrlRbbState { + DeviceState parent; + + TAPState state; /* Current state */ + + /* signals */ + bool trst; /* TAP controller reset */ + bool srst; /* System reset */ + bool tck; /* JTAG clock */ + bool tms; /* JTAG state machine selector */ + bool tdi; /* Register input */ + bool tdo; /* Register output */ + + /* registers */ + uint64_t ir; /* instruction register value */ + uint64_t ir_hold; /* IR hold register */ + uint64_t dr; /* current data register value */ + size_t dr_len; /* count of meaningful bits in dr */ + + /* handlers */ + TapDataHandler *tdh; /* Current data register handler */ + GHashTable *tdhtable; /* Registered handlers */ + + guint watch_tag; /* tracker for comm device change */ + + /* properties */ + CharFrontend chr; + uint32_t idcode; /* TAP controller identifier */ + uint8_t ir_length; /* count of meaningful bits in ir */ + uint8_t idcode_inst; /* instruction to get ID code */ + bool enable_quit; /* whether VM quit can be remotely triggered */ +} TapCtrlRbbState; + +typedef struct _TAPRegisterState { + int base_reg; + int num_regs; + struct TAPRegisterState *next; +} TAPRegisterState; + +typedef struct _TAPProcess { + uint32_t pid; + bool attached; +} TAPProcess; + +#define STRINGIFY_(_val_) #_val_ +#define STRINGIFY(_val_) STRINGIFY_(_val_) +#define NAME_FSMSTATE(_st_) [_st_] = STRINGIFY(_st_) + +#define DEFAULT_JTAG_BITBANG_PORT "3335" +#define MAX_PACKET_LENGTH 4096u + +#define TAP_CTRL_BYPASS_INST 0u + +/* + * TAP controller state machine state/event matrix + * + * Current state -> Next States for either TMS == 0 or TMS == 1 + */ +static const TAPState TAPFSM[_TAP_STATE_COUNT][2] = { + [TEST_LOGIC_RESET] = { RUN_TEST_IDLE, TEST_LOGIC_RESET }, + [RUN_TEST_IDLE] = { RUN_TEST_IDLE, SELECT_DR_SCAN }, + [SELECT_DR_SCAN] = { CAPTURE_DR, SELECT_IR_SCAN }, + [CAPTURE_DR] = { SHIFT_DR, EXIT1_DR }, + [SHIFT_DR] = { SHIFT_DR, EXIT1_DR }, + [EXIT1_DR] = { PAUSE_DR, UPDATE_DR }, + [PAUSE_DR] = { PAUSE_DR, EXIT2_DR }, + [EXIT2_DR] = { SHIFT_DR, UPDATE_DR }, + [UPDATE_DR] = { RUN_TEST_IDLE, SELECT_DR_SCAN }, + [SELECT_IR_SCAN] = { CAPTURE_IR, TEST_LOGIC_RESET }, + [CAPTURE_IR] = { SHIFT_IR, EXIT1_IR }, + [SHIFT_IR] = { SHIFT_IR, EXIT1_IR }, + [EXIT1_IR] = { PAUSE_IR, UPDATE_IR }, + [PAUSE_IR] = { PAUSE_IR, EXIT2_IR }, + [EXIT2_IR] = { SHIFT_IR, UPDATE_IR }, + [UPDATE_IR] = { RUN_TEST_IDLE, SELECT_DR_SCAN } +}; + +static const char TAPFSM_NAMES[_TAP_STATE_COUNT][18U] = { + NAME_FSMSTATE(TEST_LOGIC_RESET), NAME_FSMSTATE(RUN_TEST_IDLE), + NAME_FSMSTATE(SELECT_DR_SCAN), NAME_FSMSTATE(CAPTURE_DR), + NAME_FSMSTATE(SHIFT_DR), NAME_FSMSTATE(EXIT1_DR), + NAME_FSMSTATE(PAUSE_DR), NAME_FSMSTATE(EXIT2_DR), + NAME_FSMSTATE(UPDATE_DR), NAME_FSMSTATE(SELECT_IR_SCAN), + NAME_FSMSTATE(CAPTURE_IR), NAME_FSMSTATE(SHIFT_IR), + NAME_FSMSTATE(EXIT1_IR), NAME_FSMSTATE(PAUSE_IR), + NAME_FSMSTATE(EXIT2_IR), NAME_FSMSTATE(UPDATE_IR), +}; + +static void tap_ctrl_rbb_idcode_capture(TapDataHandler *tdh); + +/* Common TAP instructions */ +static const TapDataHandler TAP_CTRL_RBB_BYPASS = { + .name = "bypass", + .length = 1, + .value = 0, +}; + +static const TapDataHandler TAP_CTRL_RBB_IDCODE = { + .name = "idcode", + .length = 32, + .capture = &tap_ctrl_rbb_idcode_capture, +}; + +/* + * TAP State Machine implementation + */ + +static void tap_ctrl_rbb_dump_register(const char *msg, const char *iname, + uint64_t value, size_t length) +{ + char buf[80]; + if (length > 64u) { + length = 64u; + } + unsigned ix = 0; + while (ix < length) { + buf[ix] = (char)('0' + ((value >> (length - ix - 1)) & 0b1)); + ix++; + } + buf[ix] = '\0'; + + if (iname) { + trace_tap_ctrl_rbb_idump_register(msg, iname, value, length, buf); + } else { + trace_tap_ctrl_rbb_dump_register(msg, value, length, buf); + } +} + +static bool tap_ctrl_rbb_has_data_handler(TapCtrlRbbState *tap, unsigned code) +{ + return (bool)g_hash_table_contains(tap->tdhtable, GINT_TO_POINTER(code)); +} + +static TapDataHandler * +tap_ctrl_rbb_get_data_handler(TapCtrlRbbState *tap, unsigned code) +{ + TapDataHandler *tdh; + tdh = (TapDataHandler *)g_hash_table_lookup(tap->tdhtable, + GINT_TO_POINTER(code)); + return tdh; +} + +static void tap_ctrl_rbb_idcode_capture(TapDataHandler *tdh) +{ + /* special case for ID code: opaque contains the ID code value */ + tdh->value = (uint64_t)(uintptr_t)tdh->opaque; +} + +static void tap_ctrl_rbb_tap_reset(TapCtrlRbbState *tap) +{ + tap->state = TEST_LOGIC_RESET; + tap->trst = false; + tap->srst = false; + tap->tck = false; + tap->tms = false; + tap->tdi = false; + tap->tdo = false; + tap->ir = tap->idcode_inst; + tap->ir_hold = tap->idcode_inst; + tap->dr = 0u; + tap->dr_len = 0u; + tap->tdh = tap_ctrl_rbb_get_data_handler(tap, tap->idcode_inst); + g_assert(tap->tdh); +} + +static void tap_ctrl_rbb_system_reset(TapCtrlRbbState *tap) +{ + Object *ms = qdev_get_machine(); + ObjectClass *mc = object_get_class(ms); + (void)tap; + + if (!object_class_dynamic_cast(mc, TYPE_RESETTABLE_INTERFACE)) { + qemu_log_mask(LOG_UNIMP, "%s: Machine %s is not resettable\n", __func__, + object_get_typename(ms)); + return; + } + + trace_tap_ctrl_rbb_system_reset(); + resettable_reset(ms, RESET_TYPE_COLD); +} + +static TAPState tap_ctrl_rbb_get_next_state(TapCtrlRbbState *tap, bool tms) +{ + tap->state = TAPFSM[tap->state][(unsigned)tms]; + return tap->state; +} + +static void tap_ctrl_rbb_capture_ir(TapCtrlRbbState *tap) +{ + tap->ir = tap->idcode_inst; +} + +static void tap_ctrl_rbb_shift_ir(TapCtrlRbbState *tap, bool tdi) +{ + tap->ir >>= 1u; + tap->ir |= ((uint64_t)(tdi)) << (tap->ir_length - 1u); +} + +static void tap_ctrl_rbb_update_ir(TapCtrlRbbState *tap) +{ + tap->ir_hold = tap->ir; + tap_ctrl_rbb_dump_register("Update IR", NULL, tap->ir_hold, tap->ir_length); +} + +static void tap_ctrl_rbb_capture_dr(TapCtrlRbbState *tap) +{ + TapDataHandler *prev = tap->tdh; + + if (tap->ir_hold >= (1 << tap->ir_length)) { + /* internal error, should never happen */ + error_setg(&error_fatal, "Invalid IR 0x%02x\n", (unsigned)tap->ir_hold); + g_assert_not_reached(); + } + + TapDataHandler *tdh = tap_ctrl_rbb_get_data_handler(tap, tap->ir_hold); + if (!tdh) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown IR 0x%02x\n", __func__, + (unsigned)tap->ir_hold); + tap->dr = 0; + return; + } + + if (tdh != prev) { + trace_tap_ctrl_rbb_select_dr(tdh->name, tap->ir_hold); + } + + tap->tdh = tdh; + tap->dr_len = tdh->length; + + if (tdh->capture) { + tdh->capture(tdh); + } + tap->dr = tdh->value; + tap_ctrl_rbb_dump_register("Capture DR", tap->tdh->name, tap->dr, + tap->dr_len); +} + +static void tap_ctrl_rbb_shift_dr(TapCtrlRbbState *tap, bool tdi) +{ + tap->dr >>= 1u; + tap->dr |= ((uint64_t)(tdi)) << (tap->dr_len - 1u); +} + +static void tap_ctrl_rbb_update_dr(TapCtrlRbbState *tap) +{ + tap_ctrl_rbb_dump_register("Update DR", tap->tdh->name, tap->dr, + tap->dr_len); + TapDataHandler *tdh = tap->tdh; + tdh->value = tap->dr; + if (tdh->update) { + tdh->update(tdh); + } +} + +static void tap_ctrl_rbb_step(TapCtrlRbbState *tap, bool tck, bool tms, + bool tdi) +{ + trace_tap_ctrl_rbb_step(tck, tms, tdi); + + if (tap->trst) { + return; + } + + if (!tap->tck && tck) { + /* Rising clock edge */ + if (tap->state == SHIFT_IR) { + tap_ctrl_rbb_shift_ir(tap, tap->tdi); + } else if (tap->state == SHIFT_DR) { + tap_ctrl_rbb_shift_dr(tap, tap->tdi); + } + TAPState prev = tap->state; + TAPState new = tap_ctrl_rbb_get_next_state(tap, tms); + if (prev != new) { + trace_tap_ctrl_rbb_change_state(TAPFSM_NAMES[prev], + TAPFSM_NAMES[new]); + } + } else { + /* Falling clock edge */ + switch (tap->state) { + case RUN_TEST_IDLE: + /* do nothing */ + break; + case TEST_LOGIC_RESET: + tap_ctrl_rbb_tap_reset(tap); + break; + case CAPTURE_DR: + tap_ctrl_rbb_capture_dr(tap); + break; + case SHIFT_DR: + tap->tdo = tap->dr & 0b1; + break; + case UPDATE_DR: + tap_ctrl_rbb_update_dr(tap); + break; + case CAPTURE_IR: + tap_ctrl_rbb_capture_ir(tap); + break; + case SHIFT_IR: + tap->tdo = tap->ir & 0b1; + break; + case UPDATE_IR: + tap_ctrl_rbb_update_ir(tap); + break; + default: + /* nothing to do on the other state transition */ + break; + } + } + tap->tck = tck; + tap->tdi = tdi; + tap->tms = tms; +} + +static void tap_ctrl_rbb_blink(TapCtrlRbbState *tap, bool light) {} + +static void tap_ctrl_rbb_read(TapCtrlRbbState *tap) +{ + trace_tap_ctrl_rbb_read(tap->tdo); +} + +static void tap_ctrl_rbb_quit(TapCtrlRbbState *tap) +{ + (void)tap; + + if (tap->enable_quit) { + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); + } else { + info_report("%s: JTAG termination disabled\n", __func__); + } +} + +static void tap_ctrl_rbb_write(TapCtrlRbbState *tap, bool tck, bool tms, + bool tdi) +{ + tap_ctrl_rbb_step(tap, tck, tms, tdi); +} + +static void tap_ctrl_rbb_reset_tap(TapCtrlRbbState *tap, bool trst, bool srst) +{ + trace_tap_ctrl_rbb_reset(trst, srst); + if (trst) { + tap_ctrl_rbb_tap_reset(tap); + } + if (srst) { + tap_ctrl_rbb_system_reset(tap); + } + tap->trst = trst; + tap->srst = srst; +} + +/* + * TAP Server implementation + */ + +static bool tap_ctrl_rbb_read_byte(TapCtrlRbbState *tap, uint8_t ch) +{ + switch ((char)ch) { + case 'B': + tap_ctrl_rbb_blink(tap, true); + break; + case 'b': + tap_ctrl_rbb_blink(tap, false); + break; + case 'R': + tap_ctrl_rbb_read(tap); + break; + case 'Q': + tap_ctrl_rbb_quit(tap); + break; + case '0': + tap_ctrl_rbb_write(tap, false, false, false); + break; + case '1': + tap_ctrl_rbb_write(tap, false, false, true); + break; + case '2': + tap_ctrl_rbb_write(tap, false, true, false); + break; + case '3': + tap_ctrl_rbb_write(tap, false, true, true); + break; + case '4': + tap_ctrl_rbb_write(tap, true, false, false); + break; + case '5': + tap_ctrl_rbb_write(tap, true, false, true); + break; + case '6': + tap_ctrl_rbb_write(tap, true, true, false); + break; + case '7': + tap_ctrl_rbb_write(tap, true, true, true); + break; + case 'r': + tap_ctrl_rbb_reset_tap(tap, false, false); + break; + case 's': + tap_ctrl_rbb_reset_tap(tap, false, true); + break; + case 't': + tap_ctrl_rbb_reset_tap(tap, true, false); + break; + case 'u': + tap_ctrl_rbb_reset_tap(tap, true, true); + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Unknown TAP code 0x%02x\n", __func__, + (unsigned)ch); + break; + } + + /* true if TDO level should be sent to the peer */ + return (int)ch == 'R'; +} + +static int tap_ctrl_rbb_chr_can_receive(void *opaque) +{ + TapCtrlRbbState *tap = (TapCtrlRbbState *)opaque; + + /* do not accept any input till a TAP controller is available */ + return qemu_chr_fe_backend_connected(&tap->chr) ? MAX_PACKET_LENGTH : 0; +} + +static void tap_ctrl_rbb_chr_receive(void *opaque, const uint8_t *buf, int size) +{ + TapCtrlRbbState *tap = (TapCtrlRbbState *)opaque; + + for (unsigned ix = 0; ix < size; ix++) { + if (tap_ctrl_rbb_read_byte(tap, buf[ix])) { + uint8_t outbuf[1] = { '0' + (unsigned)tap->tdo }; + qemu_chr_fe_write_all(&tap->chr, outbuf, (int)sizeof(outbuf)); + } + } +} + +static void tap_ctrl_rbb_chr_event_hander(void *opaque, QEMUChrEvent event) +{ + TapCtrlRbbState *tap = opaque; + + if (event == CHR_EVENT_OPENED) { + if (!qemu_chr_fe_backend_connected(&tap->chr)) { + return; + } + + Object *sock; + sock = object_dynamic_cast(OBJECT(tap->chr.chr), TYPE_CHARDEV_SOCKET); + if (sock) { + qio_channel_set_delay(SOCKET_CHARDEV(sock)->ioc, false); + } + + tap_ctrl_rbb_tap_reset(tap); + } +} + +static gboolean +tap_ctrl_rbb_chr_watch_cb(void *do_not_use, GIOCondition cond, void *opaque) +{ + TapCtrlRbbState *tap = opaque; + (void)do_not_use; + (void)cond; + + tap->watch_tag = 0; + + return FALSE; +} + +static int tap_ctrl_rbb_chr_be_change(void *opaque) +{ + TapCtrlRbbState *tap = opaque; + + qemu_chr_fe_set_handlers(&tap->chr, &tap_ctrl_rbb_chr_can_receive, + &tap_ctrl_rbb_chr_receive, + &tap_ctrl_rbb_chr_event_hander, + &tap_ctrl_rbb_chr_be_change, tap, NULL, true); + + tap_ctrl_rbb_tap_reset(tap); + + if (tap->watch_tag > 0) { + g_source_remove(tap->watch_tag); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + tap->watch_tag = qemu_chr_fe_add_watch(&tap->chr, G_IO_OUT | G_IO_HUP, + &tap_ctrl_rbb_chr_watch_cb, tap); + } + + return 0; +} + +static void tap_ctrl_rbb_verify_handler(const TapCtrlRbbState *tap, + unsigned code, const char *name) +{ + if (code >= (1 << tap->ir_length)) { + error_setg(&error_fatal, "JTAG: Invalid IR code: 0x%x for %s", code, + name); + g_assert_not_reached(); + } +} + +static void +tap_ctrl_rbb_verify_handler_fn(gpointer key, gpointer value, gpointer user_data) +{ + unsigned code = GPOINTER_TO_INT(key); + const TapDataHandler *tdh = (const TapDataHandler *)value; + const TapCtrlRbbState *tap = (const TapCtrlRbbState *)user_data; + + tap_ctrl_rbb_verify_handler(tap, code, tdh->name); +} + +static void tap_ctrl_rbb_register_handler(TapCtrlRbbState *tap, unsigned code, + const TapDataHandler *tdh, bool check) +{ + if (check) { + tap_ctrl_rbb_verify_handler(tap, code, tdh->name); + } + + if (tap_ctrl_rbb_has_data_handler(tap, code)) { + warn_report("JTAG: IR code already registered: 0x%x", code); + /* resume and override */ + } + + TapDataHandler *ltdh = g_new0(TapDataHandler, 1u); + memcpy(ltdh, tdh, sizeof(*tdh)); + ltdh->name = g_strdup(tdh->name); + g_hash_table_insert(tap->tdhtable, GINT_TO_POINTER(code), ltdh); + + trace_tap_ctrl_rbb_register(code, ltdh->name); +} + +static void tap_ctrl_rbb_free_data_handler(gpointer entry) +{ + TapDataHandler *tdh = entry; + if (!entry) { + return; + } + g_free((char *)tdh->name); + g_free(tdh); +} + +static bool tap_ctrl_rbb_is_enabled(TapCtrlIf *dev) +{ + TapCtrlRbbState *tap = TAP_CTRL_RBB(dev); + + return qemu_chr_fe_backend_connected(&tap->chr); +} + +static int tap_ctrl_rbb_register_instruction(TapCtrlIf *dev, unsigned code, + const TapDataHandler *tdh) +{ + TapCtrlRbbState *tap = TAP_CTRL_RBB(dev); + + tap_ctrl_rbb_register_handler(tap, code, tdh, DEVICE(tap)->realized); + + return 0; +} + +static const Property tap_ctrl_rbb_properties[] = { + DEFINE_PROP_UINT32("idcode", TapCtrlRbbState, idcode, 0), + DEFINE_PROP_UINT8("ir_length", TapCtrlRbbState, ir_length, 0), + DEFINE_PROP_UINT8("idcode_inst", TapCtrlRbbState, idcode_inst, 1u), + DEFINE_PROP_BOOL("quit", TapCtrlRbbState, enable_quit, true), + DEFINE_PROP_CHR("chardev", TapCtrlRbbState, chr), +}; + +static void tap_ctrl_rbb_realize(DeviceState *dev, Error **errp) +{ + TapCtrlRbbState *tap = TAP_CTRL_RBB(dev); + + if (tap->ir_length == 0 || tap->ir_length > 8u) { + error_setg(errp, "Unsupported IR length: %u", tap->ir_length); + return; + } + + if (tap->idcode == 0u) { + error_setg(errp, "Invalid IDCODE: 0x%x", tap->idcode); + return; + } + + if (tap->idcode_inst == TAP_CTRL_BYPASS_INST) { + error_setg(errp, "Invalid IDCODE instruction: 0x%x", tap->idcode_inst); + return; + } + + trace_tap_ctrl_rbb_realize(tap->ir_length, tap->idcode); + + /* + * Handlers may be registered before the TAP controller is configured. + * Need to check their configuration once the configuration is known + */ + g_hash_table_foreach(tap->tdhtable, &tap_ctrl_rbb_verify_handler_fn, + (gpointer)tap); + + size_t irslots = 1u << tap->ir_length; + tap_ctrl_rbb_register_handler(tap, TAP_CTRL_BYPASS_INST, + &TAP_CTRL_RBB_BYPASS, true); + tap_ctrl_rbb_register_handler(tap, tap->idcode_inst, &TAP_CTRL_RBB_IDCODE, + true); + tap_ctrl_rbb_register_handler(tap, irslots - 1u, &TAP_CTRL_RBB_BYPASS, + true); + /* special case for ID code: opaque store the constant idcode value */ + TapDataHandler *tdh = tap_ctrl_rbb_get_data_handler(tap, tap->idcode_inst); + g_assert(tdh); + tdh->opaque = (void *)(uintptr_t)tap->idcode; + + qemu_chr_fe_set_handlers(&tap->chr, &tap_ctrl_rbb_chr_can_receive, + &tap_ctrl_rbb_chr_receive, + &tap_ctrl_rbb_chr_event_hander, + &tap_ctrl_rbb_chr_be_change, tap, NULL, true); + + tap_ctrl_rbb_tap_reset(tap); + + qemu_chr_fe_accept_input(&tap->chr); +} + +static void tap_ctrl_rbb_init(Object *obj) +{ + TapCtrlRbbState *tap = TAP_CTRL_RBB(obj); + + tap->tdhtable = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, + tap_ctrl_rbb_free_data_handler); +} + +static void tap_ctrl_rbb_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + (void)data; + + dc->realize = &tap_ctrl_rbb_realize; + device_class_set_props(dc, tap_ctrl_rbb_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + TapCtrlIfClass *tcc = TAP_CTRL_IF_CLASS(klass); + tcc->is_enabled = &tap_ctrl_rbb_is_enabled; + tcc->register_instruction = &tap_ctrl_rbb_register_instruction; +} + +static const TypeInfo tap_ctrl_rbb_info = { + .name = TYPE_TAP_CTRL_RBB, + .parent = TYPE_DEVICE, + .instance_size = sizeof(TapCtrlRbbState), + .instance_init = &tap_ctrl_rbb_init, + .class_init = &tap_ctrl_rbb_class_init, + .interfaces = + (InterfaceInfo[]){ + { TYPE_TAP_CTRL_IF }, + {}, + }, +}; + +static void register_types(void) +{ + type_register_static(&tap_ctrl_rbb_info); +} + +type_init(register_types); diff --git a/hw/jtag/trace-events b/hw/jtag/trace-events new file mode 100644 index 0000000000000..ab419c3454759 --- /dev/null +++ b/hw/jtag/trace-events @@ -0,0 +1,13 @@ +# tap_ctrl_rbb.c + +tap_ctrl_rbb_change(const char *msg, const char *value) "%s %s" +tap_ctrl_rbb_change_state(const char *prev, const char *new) "%s -> %s" +tap_ctrl_rbb_dump_register(const char *msg, uint64_t val, size_t len, const char *buf) "%s: 0x%" PRIx64 "/%zu [b%s]" +tap_ctrl_rbb_idump_register(const char *msg, const char *iname, uint64_t val, size_t len, const char *buf) "%s (%s): 0x%" PRIx64 "/%zu [b%s]" +tap_ctrl_rbb_read(bool tdo) "tdo %u" +tap_ctrl_rbb_realize(size_t irlen, uint32_t ircode) "irlength %zu, idcode 0x%08x" +tap_ctrl_rbb_register(unsigned code, const char *name) "register 0x%x: %s" +tap_ctrl_rbb_reset(bool tap, bool sys) "tap: %u, system: %u" +tap_ctrl_rbb_select_dr(const char *name, uint64_t value) "Select DR %s 0x%02" PRIx64 +tap_ctrl_rbb_step(bool tck, bool tms, bool tdi) "tck:%u tms:%u tdi:%u" +tap_ctrl_rbb_system_reset(void) "SYSTEM RESET" diff --git a/hw/jtag/trace.h b/hw/jtag/trace.h new file mode 100644 index 0000000000000..b0a55dce5901c --- /dev/null +++ b/hw/jtag/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_jtag.h" diff --git a/hw/meson.build b/hw/meson.build index 1022bdb8069a1..0f3ff77872a76 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -21,6 +21,8 @@ subdir('sparc64') subdir('tricore') subdir('xtensa') +subdir('jtag') + subdir('9pfs') subdir('acpi') subdir('adc') diff --git a/include/hw/jtag/tap_ctrl.h b/include/hw/jtag/tap_ctrl.h new file mode 100644 index 0000000000000..4f40f9f2dab13 --- /dev/null +++ b/include/hw/jtag/tap_ctrl.h @@ -0,0 +1,92 @@ +/* + * QEMU JTAG TAP controller + * + * Copyright (c) 2022-2024 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_JTAG_TAP_CTRL_H +#define HW_JTAG_TAP_CTRL_H + +#include "qom/object.h" + +struct _TapDataHandler; +typedef struct _TapDataHandler TapDataHandler; + +struct _TapDataHandler { + const char *name; /**< Name */ + size_t length; /**< Data register length */ + uint64_t value; /**< Capture/Update value */ + void *opaque; /**< Arbitrary data */ + void (*capture)(TapDataHandler *tdh); + void (*update)(TapDataHandler *tdh); +}; + +/* + * Create TAP IDCODE + * + * @_mfid_ Manufacturer ID + * @_pnum_ Part number + * @_ver_ Version + */ +#define JTAG_IDCODE(_mfid_, _pnum_, _ver_) \ + ((((_ver_) & 0xfu) << 28u) | (((_pnum_) & 0xffffu) << 12u) | \ + (((_mfid_) & 0x7ffu) << 1u) | 0b1) + +/* + * Create JEDEC Manufacturer ID + * + * @_tbl_ JEDEC table + * @_id_ Entry in JEDEC table + */ +#define JEDEC_MANUFACTURER_ID(_tbl_, _id_) \ + (((((_tbl_) - 1u) & 0xfu) << 7u) | ((_id_) & 0x7fu)) + +#define TYPE_TAP_CTRL_IF "tap-ctrl-interface" +typedef struct TapCtrlIfClass TapCtrlIfClass; +DECLARE_CLASS_CHECKERS(TapCtrlIfClass, TAP_CTRL_IF, TYPE_TAP_CTRL_IF) +#define TAP_CTRL_IF(_obj_) INTERFACE_CHECK(TapCtrlIf, (_obj_), TYPE_TAP_CTRL_IF) + +typedef struct TapCtrlIf TapCtrlIf; + +struct TapCtrlIfClass { + InterfaceClass parent_class; + + /** + * Report whether TAP controller is enabled. + * + * @return @c true if the TAP can be used. + */ + bool (*is_enabled)(TapCtrlIf *dev); + + /* + * Register instruction support on the TAP controller + * + * @code instruction code for which to register the handler + * @tdh TAP data handler to register + * @return non-zero on error + */ + int (*register_instruction)(TapCtrlIf *dev, unsigned code, + const TapDataHandler *tdh); +}; + +#endif // HW_JTAG_TAP_CTRL_H diff --git a/include/hw/jtag/tap_ctrl_rbb.h b/include/hw/jtag/tap_ctrl_rbb.h new file mode 100644 index 0000000000000..a08caaa4f0b3d --- /dev/null +++ b/include/hw/jtag/tap_ctrl_rbb.h @@ -0,0 +1,35 @@ +/* + * QEMU JTAG TAP controller for the OpenOCD/Spike Remote BigBang protocol + * + * Copyright (c) 2024 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_JTAG_TAP_CTRL_RBB_H +#define HW_JTAG_TAP_CTRL_RBB_H + +#include "qom/object.h" + +#define TYPE_TAP_CTRL_RBB "tap-ctrl-rbb" +OBJECT_DECLARE_SIMPLE_TYPE(TapCtrlRbbState, TAP_CTRL_RBB) + +#endif /* HW_JTAG_TAP_CTRL_RBB_H */ diff --git a/meson.build b/meson.build index d9293294d8e76..fd9a231e9e852 100644 --- a/meson.build +++ b/meson.build @@ -3643,6 +3643,7 @@ if have_system 'hw/input', 'hw/intc', 'hw/isa', + 'hw/jtag', 'hw/mem', 'hw/mips', 'hw/misc', From a7e7e89690f152c83a774cf5ee6a0ca741e4c3e7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 7 Jan 2026 12:03:44 +0000 Subject: [PATCH 10/18] [ot] hw/riscv: debug: add Pulp RISC-V debug module Signed-off-by: James Wainwright --- accel/tcg/cpu-exec.c | 11 + disas/riscv.c | 11 + gdbstub/gdbstub.c | 24 +- hw/misc/Kconfig | 4 + hw/misc/meson.build | 3 + hw/misc/pulp_rv_dm.c | 555 ++++ hw/misc/trace-events | 4 + hw/riscv/Kconfig | 11 + hw/riscv/debug.c | 56 + hw/riscv/dm.c | 2794 +++++++++++++++++ hw/riscv/dtm.c | 590 ++++ hw/riscv/meson.build | 3 + hw/riscv/trace-events | 42 + include/disas/dis-asm.h | 1 + include/hw/core/cpu.h | 2 + include/hw/misc/pulp_rv_dm.h | 70 + include/hw/riscv/debug.h | 77 + include/hw/riscv/dm.h | 62 + include/hw/riscv/dtm.h | 66 + system/cpus.c | 12 +- target/riscv/cpu.c | 57 +- target/riscv/cpu.h | 17 +- target/riscv/cpu_bits.h | 28 +- target/riscv/cpu_helper.c | 107 +- target/riscv/csr.c | 66 +- target/riscv/debug.c | 245 +- target/riscv/debug.h | 5 +- target/riscv/helper.h | 1 + target/riscv/insn32.decode | 1 + .../riscv/insn_trans/trans_privileged.c.inc | 17 + target/riscv/op_helper.c | 46 +- target/riscv/pmu.c | 3 + target/riscv/translate.c | 14 +- 33 files changed, 4932 insertions(+), 73 deletions(-) create mode 100644 hw/misc/pulp_rv_dm.c create mode 100644 hw/riscv/debug.c create mode 100644 hw/riscv/dm.c create mode 100644 hw/riscv/dtm.c create mode 100644 include/hw/misc/pulp_rv_dm.h create mode 100644 include/hw/riscv/debug.h create mode 100644 include/hw/riscv/dm.h create mode 100644 include/hw/riscv/dtm.h diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index 17d9ca9b0d79a..2db0decc25d61 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -707,6 +707,10 @@ static inline bool cpu_handle_exception(CPUState *cpu, int *ret) *ret = cpu->exception_index; if (*ret == EXCP_DEBUG) { cpu_handle_debug_exception(cpu); + if (cpu->exception_index < 0) { + /* the handler has cleared the exception */ + return false; + } } cpu->exception_index = -1; return true; @@ -1002,6 +1006,13 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc) tb_add_jump(last_tb, tb_exit, tb); } + if (unlikely((s.cflags != -1) && (s.cflags & CF_SINGLE_STEP))) { + CPUClass *cc = cpu->cc; + if (cc->debug_enable_singlestep) { + cc->debug_enable_singlestep(cpu, s.pc); + } + } + cpu_loop_exec_tb(cpu, tb, s.pc, &last_tb, &tb_exit); /* Try to align the host and virtual clocks diff --git a/disas/riscv.c b/disas/riscv.c index 85cd2a9c2aefe..9b44e0859220f 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -5529,3 +5529,14 @@ int print_insn_riscv128(bfd_vma memaddr, struct disassemble_info *info) { return print_insn_riscv(memaddr, info, rv128); } + +const char *get_riscv_debug_reg_name(int regno) +{ + switch (regno) { + case 0x0000 ... 0x0fff: return csr_name(regno); + case 0x1000 ... 0x101f: return rv_ireg_name_sym[regno-0x1000]; + case 0x1020 ... 0x103f: return rv_freg_name_sym[regno-0x1020]; + /* TODO: need to add vector regs (not part of 0.13.2 debug spec) */ + default: return NULL; + } +} diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 5b2fc06e58dff..e8136b0132594 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -2309,18 +2309,20 @@ static int gdb_handle_packet(const char *line_buf) void gdb_set_stop_cpu(CPUState *cpu) { - GDBProcess *p = gdb_get_cpu_process(cpu); - - if (!p->attached) { - /* - * Having a stop CPU corresponding to a process that is not attached - * confuses GDB. So we ignore the request. - */ - return; + if (gdbserver_state.init) { + GDBProcess *p = gdb_get_cpu_process(cpu); + + if (!p->attached) { + /* + * Having a stop CPU corresponding to a process that is not attached + * confuses GDB. So we ignore the request. + */ + return; + } + + gdbserver_state.c_cpu = cpu; + gdbserver_state.g_cpu = cpu; } - - gdbserver_state.c_cpu = cpu; - gdbserver_state.g_cpu = cpu; } void gdb_read_byte(uint8_t ch) diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index fccd735c24c12..7b50a5b22ce7d 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -240,4 +240,8 @@ config IOSB config XLNX_VERSAL_TRNG bool +config PULP_RV_DM + select RISCV_DM + bool + source macio/Kconfig diff --git a/hw/misc/meson.build b/hw/misc/meson.build index b1d8d8e5d2a93..f40294a4b208b 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -35,6 +35,9 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c')) system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c')) system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c')) +# Pulp devices +system_ss.add(when: 'CONFIG_PULP_RV_DM', if_true: files('pulp_rv_dm.c')) + subdir('macio') # ivshmem devices diff --git a/hw/misc/pulp_rv_dm.c b/hw/misc/pulp_rv_dm.c new file mode 100644 index 0000000000000..8350b75a4474c --- /dev/null +++ b/hw/misc/pulp_rv_dm.c @@ -0,0 +1,555 @@ +/* + * QEMU Pulp Debug Module device + * + * Copyright (c) 2022-2024 Rivos, Inc. + * Author(s): + * Emmanuel Blot + * + * For details check the documentation here: + * https://docs.opentitan.org/hw/ip/rv_dm/doc/ + * + * 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. + * + * This file should be splitted so that RISCV info belong to target/riscv + */ + +#include "qemu/osdep.h" +#include "qemu/bitmap.h" +#include "qemu/guest-random.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qemu/timer.h" +#include "qemu/typedefs.h" +#include "qapi/error.h" +#include "exec/memattrs.h" +#include "hw/boards.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/jtag/tap_ctrl.h" +#include "hw/loader.h" +#include "hw/misc/pulp_rv_dm.h" +#include "hw/opentitan/ot_alert.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/dm.h" +#include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibex_irq.h" +#include "hw/sysbus.h" +#include "trace.h" + + +/* + * Configuration + */ + +#define DISCARD_REPEATED_IO_TRACES +#define DISTANCE_ACCESS_IO_TRACES 40u + +/* + * Register definitions + */ + +/* clang-format off */ + +/* MMIO Regs */ +REG32(ALERT_TEST, 0x0u) + FIELD(ALERT_TEST, FATAL_FAULT, 0u, 1u) + +/* MMIO Mem (Actions) */ +REG32(HALTED, RISCV_DM_HALTED_OFFSET) +REG32(GOING, RISCV_DM_GOING_OFFSET) +REG32(RESUMING, RISCV_DM_RESUMING_OFFSET) +REG32(EXCEPTION, RISCV_DM_EXCEPTION_OFFSET) + +/* Shared Mem (R/W access from debugger, R/X from Hart) */ +REG32(WHERETO, 0x300u) +/* + * Abstract cmd registers are used as a private program buffer to implement + * abstract commands as semi-hardcoded SW, i.e. not in the debug ROM, w/ + * PULP_RV_DM_ABSTRACTCMD_COUNT slots +*/ +REG32(ABSTRACTCMD_0, 0x338u) +/* + * Program buffer registers are used to execute short code sequence and may be + * uploaded from an external debugger, w/ PULP_RV_DM_PROGRAM_BUFFER_COUNT slots + */ +REG32(PROGRAM_BUFFER_0, PULP_RV_DM_PROGRAM_BUFFER_OFFSET) +/* + * Data address registers is a view to the abstract data used w/ abstract + * commands + */ +REG32(DATAADDR_0, PULP_RV_DM_DATAADDR_OFFSET) + +/* MMIO mem (flags) */ +REG32(FLAGS, RISCV_DM_FLAGS_OFFSET) + FIELD(FLAGS, FLAG_GO, 0u, 1u) + FIELD(FLAGS, FLAG_RESUME, 1u, 1u) + +/* clang-format on */ + +/* + * Macros + */ + +#define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) +#define MEM_OFF(_r_) ((_r_) - R_WHERETO) + +#define PULP_RV_DM_DMACT_BASE (A_HALTED) +#define PULP_RV_DM_DMACT_SIZE (A_EXCEPTION - A_HALTED + sizeof(uint32_t)) +#define PULP_RV_DM_PROG_BASE (A_WHERETO) +#define PULP_RV_DM_PROG_SIZE 0x100u +#define PULP_RV_DM_DMFLAG_BASE (A_FLAGS) +#define PULP_RV_DM_DMFLAG_SIZE (PULP_RV_DM_FLAGS_COUNT * sizeof(uint32_t)) + +/* + * Type definitions + */ + +struct PulpRVDMState { + SysBusDevice parent_obj; + + MemoryRegion regs; /* MMIO */ + MemoryRegion mem; /* Container for the following: */ + MemoryRegion dmact; /* MMIO */ + MemoryRegion prog; /* ROM device */ + MemoryRegion dmflag; /* MMIO */ + MemoryRegion rom; /* ROM */ + + qemu_irq *ack_out; + IbexIRQ alert; + + uint32_t dmflag_regs[PULP_RV_DM_DMFLAG_SIZE / sizeof(uint32_t)]; + + unsigned hart_count; + uint64_t idle_bm; +}; + +#ifdef DISCARD_REPEATED_IO_TRACES +typedef struct { + uint64_t pc; + uint32_t addr; + uint32_t value; + size_t count; +} TraceCache; +#endif /* DISCARD_REPEATED_IO_TRACES */ + +/* + * Constants + */ + +#define R_ABSTRACTCMD_LAST (R_ABSTRACTCMD_0 + PULP_RV_DM_ABSTRACTCMD_COUNT - 1u) +#define R_PROGRAM_BUFFER_LAST \ + (R_PROGRAM_BUFFER_0 + PULP_RV_DM_PROGRAM_BUFFER_COUNT - 1u) +#define R_DATAADDR_LAST (R_DATAADDR_0 + PULP_RV_DM_DATA_COUNT - 1u) +#define R_FLAGS_0 R_FLAGS +#define R_FLAGS_LAST (R_FLAGS_0 + PULP_RV_DM_FLAGS_COUNT - 1u) +#define PULP_RV_DM_MEM_WORDS \ + ((R_FLAGS_0 + PULP_RV_DM_FLAGS_COUNT) - R_WHERETO + sizeof(uint32_t)) + +/** + * Debug ROM blob for 2 debug scratch registers. + * + * Note that entry points should match ROM defined constants, namely: + * - PULP_RV_DM_HALT_OFFSET + * - PULP_RV_DM_RESUME_OFFSET + * - PULP_RV_DM_EXCEPTION_OFFSET + * - PULP_RV_DM_WHERETO_OFFSET + */ +static const uint32_t DEBUG_ROM[] = { + /* clang-format off */ + /* entry: HALT_OFFSET */ + /* 800 */ 0x00c0006fu, /* j 80c <_entry> */ + /* resume: RESUME_OFFSET */ + /* 804 */ 0x07c0006fu, /* j 880 <_resume> */ + /* exception: EXCEPTION */ + /* 808 */ 0x04c0006fu, /* j 854 <_exception> */ + /*_entry: */ + /* 80c */ 0x0ff0000fu, /* fence */ + /* 810 */ 0x7b241073u, /* csrw dscratch0,s0 */ + /* 814 */ 0x7b351073u, /* csrw dscratch1,a0 */ + /* 818 */ 0x00000517u, /* auipc a0,0x0 */ + /* 81c */ 0x00c55513u, /* srl a0,a0,0xc */ + /* 820 */ 0x00c51513u, /* sll a0,a0,0xc */ + /* entry_loop: */ + /* 824 */ 0xf1402473u, /* csrr s0,mhartid */ + /* 828 */ 0x10852023u, /* sw s0,256(a0) # HALTED */ + /* 82c */ 0x00a40433u, /* add s0,s0,a0 */ + /* 830 */ 0x40044403u, /* lbu s0,1024(s0) # FLAGS */ + /* 834 */ 0x00147413u, /* and s0,s0,1 */ + /* 838 */ 0x02041c63u, /* bnez s0,870 */ + /* 83c */ 0xf1402473u, /* csrr s0,mhartid */ + /* 840 */ 0x00a40433u, /* add s0,s0,a0 */ + /* 844 */ 0x40044403u, /* lbu s0,1024(s0) # FLAGS */ + /* 848 */ 0x00247413u, /* and s0,s0,2 */ + /* 84c */ 0xfa041ce3u, /* bnez s0,804 */ + /* 850 */ 0xfd5ff06fu, /* j 824 */ + /* _exception: */ + /* 854 */ 0x00000517u, /* auipc a0,0x0 */ + /* 858 */ 0x00c55513u, /* srl a0,a0,0xc */ + /* 85c */ 0x00c51513u, /* sll a0,a0,0xc */ + /* 860 */ 0x10052623u, /* sw zero,268(a0) # EXCEPTION */ + /* 864 */ 0x7b302573u, /* csrr a0,dscratch1 */ + /* 868 */ 0x7b202473u, /* csrr s0,dscratch0 */ + /* 86c */ 0x00100073u, /* ebreak */ + /* going: */ + /* 870 */ 0x10052223u, /* sw zero,260(a0) # GOING */ + /* 874 */ 0x7b302573u, /* csrr a0,dscratch1 */ + /* 878 */ 0x7b202473u, /* csrr s0,dscratch0 */ + /* 87c */ 0xa85ff06fu, /* j 300 # WHERETO */ + /* _resume: */ + /* 880 */ 0xf1402473u, /* csrr s0,mhartid */ + /* 884 */ 0x10852423u, /* sw s0,264(a0) # RESUMING */ + /* 888 */ 0x7b302573u, /* csrr a0,dscratch1 */ + /* 88c */ 0x7b202473u, /* csrr s0,dscratch0 */ + /* 890 */ 0x7b200073u, /* dret */ + /* clang-format on */ +}; + +/* + * Device implementation + */ + +static void pulp_rv_dm_load_rom(PulpRVDMState *s) +{ + /* do not use rom_add_blob_fixed_as as absolute address is not yet known */ + void *rom = memory_region_get_ram_ptr(&s->rom); + if (!rom) { + error_setg(&error_fatal, "cannot load debug ROM"); + /* linter may not know error_fatal never returns */ + abort(); + } + memcpy(rom, DEBUG_ROM, sizeof(DEBUG_ROM)); +} + +/* NOLINTNEXTLINE */ +static uint64_t pulp_rv_dm_regs_read(void *opaque, hwaddr addr, unsigned size) +{ + (void)opaque; + (void)size; + /* the unique register is W/O */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: W/O register 0x%" HWADDR_PRIx "\n", + __func__, addr); + + return 0u; +}; + +static void pulp_rv_dm_regs_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned size) +{ + PulpRVDMState *s = opaque; + uint32_t val32 = (uint32_t)val64; + (void)size; + + switch (R32_OFF(addr)) { + case R_ALERT_TEST: + val32 &= R_ALERT_TEST_FATAL_FAULT_MASK; + ibex_irq_set(&s->alert, (int)(bool)val32); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +}; + +static MemTxResult pulp_rv_dm_dmact_read_with_attrs( + void *opaque, hwaddr addr, uint64_t *val64, unsigned size, MemTxAttrs attrs) +{ + uint32_t val32; + MemTxResult res; + (void)size; + (void)opaque; + (void)attrs; + + addr += PULP_RV_DM_DMACT_BASE; + + if (addr & 0x1u) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad alignment 0x%" HWADDR_PRIx "\n", + __func__, addr); + return MEMTX_ERROR; + } + + switch (R32_OFF(addr)) { + case R_HALTED: + case R_GOING: + case R_RESUMING: + case R_EXCEPTION: + qemu_log_mask(LOG_GUEST_ERROR, "%s: W/O register 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0u; + res = MEMTX_OK; + break; + default: + res = MEMTX_DECODE_ERROR; + val32 = 0; + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } + + if (MEMTX_OK == res) { + *val64 = val32; + } + + return res; +}; + +static MemTxResult pulp_rv_dm_dmact_write_with_attrs( + void *opaque, hwaddr addr, uint64_t val64, unsigned size, MemTxAttrs attrs) +{ + PulpRVDMState *s = opaque; + uint32_t val32 = (uint32_t)val64; + MemTxResult res; + uint64_t pc = attrs.unspecified ? ibex_get_current_pc() : 0u; + (void)size; + + addr += PULP_RV_DM_DMACT_BASE; + +#ifdef DISCARD_REPEATED_IO_TRACES + static TraceCache trace_cache; + + if (ABS((int)(trace_cache.pc) - (int)(pc)) >= DISTANCE_ACCESS_IO_TRACES || + trace_cache.addr != addr || trace_cache.value != val32) { +#endif /* DISCARD_REPEATED_IO_TRACES */ + trace_pulp_rv_dm_mem_write((unsigned int)addr, val32, pc); +#ifdef DISCARD_REPEATED_IO_TRACES + trace_cache.count = 1; + } else { + trace_cache.count += 1; + } + trace_cache.pc = pc; + trace_cache.addr = addr; + trace_cache.value = val32; +#endif /* DISCARD_REPEATED_IO_TRACES */ + + switch (R32_OFF(addr)) { + case R_HALTED: + if (val32 < s->hart_count) { + if (!(s->idle_bm & (1u << val32))) { + /* + * use a local cache to avoid flooding the DM with the park loop + * running crazy + */ + qemu_set_irq(s->ack_out[ACK_HALTED], (int)val32); + s->idle_bm |= (1u << val32); + } + } + res = MEMTX_OK; + break; + case R_GOING: + s->idle_bm &= ~(1u << val32); + qemu_set_irq(s->ack_out[ACK_GOING], 1u); + res = MEMTX_OK; + break; + case R_RESUMING: + if (val32 < s->hart_count) { + s->idle_bm &= ~(1u << val32); + qemu_set_irq(s->ack_out[ACK_RESUMING], (int)val32); + } + res = MEMTX_OK; + break; + case R_EXCEPTION: + qemu_set_irq(s->ack_out[ACK_EXCEPTION], 1u); + res = MEMTX_OK; + break; + default: + res = MEMTX_DECODE_ERROR; + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } + + return res; +}; + +static MemTxResult pulp_rv_dm_dmflag_read_with_attrs( + void *opaque, hwaddr addr, uint64_t *val64, unsigned size, MemTxAttrs attrs) +{ + PulpRVDMState *s = opaque; + uint32_t val32; + MemTxResult res; + (void)size; + (void)attrs; + + addr += PULP_RV_DM_DMFLAG_BASE; + + unsigned reg = R32_OFF(addr); + + /* NOLINTNEXTLINE */ + switch (reg) { + case R_FLAGS_0 ... R_FLAGS_LAST: + val32 = s->dmflag_regs[reg - R_FLAGS_0]; + res = MEMTX_OK; + break; + default: + res = MEMTX_DECODE_ERROR; + val32 = 0; + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } + + if (MEMTX_OK == res) { + *val64 = val32; + } + + return res; +}; + +static MemTxResult pulp_rv_dm_dmflag_write_with_attrs( + void *opaque, hwaddr addr, uint64_t val64, unsigned size, MemTxAttrs attrs) +{ + PulpRVDMState *s = opaque; + uint32_t val32 = (uint32_t)val64; + MemTxResult res; + (void)size; + + addr += PULP_RV_DM_DMFLAG_BASE; + + unsigned reg = R32_OFF(addr); + + /* NOLINTNEXTLINE */ + switch (reg) { + case R_FLAGS_0 ... R_FLAGS_LAST: + if ((!attrs.unspecified) && + (attrs.requester_id == PULP_RV_DM_REQUESTER_ID)) { + /* dm_access */ + s->dmflag_regs[reg - R_FLAGS_0] = val32; + } else { + /* other (hart...) access */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: R/O register 0x%" HWADDR_PRIx "\n", __func__, + addr); + } + res = MEMTX_OK; + break; + default: + res = MEMTX_DECODE_ERROR; + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } + + return res; +}; + +static const MemoryRegionOps pulp_rv_dm_regs_ops = { + .read = &pulp_rv_dm_regs_read, + .write = &pulp_rv_dm_regs_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static const MemoryRegionOps pulp_rv_dm_dmact_ops = { + .read_with_attrs = &pulp_rv_dm_dmact_read_with_attrs, + .write_with_attrs = &pulp_rv_dm_dmact_write_with_attrs, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static const MemoryRegionOps pulp_rv_dm_dmflag_ops = { + .read_with_attrs = &pulp_rv_dm_dmflag_read_with_attrs, + .write_with_attrs = &pulp_rv_dm_dmflag_write_with_attrs, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static void pulp_rv_dm_reset(DeviceState *dev) +{ + PulpRVDMState *s = PULP_RV_DM(dev); + + ibex_irq_set(&s->alert, false); + + memset(memory_region_get_ram_ptr(&s->prog), 0, PULP_RV_DM_PROG_SIZE); + memset(s->dmflag_regs, 0, sizeof(s->dmflag_regs)); + + s->idle_bm = 0; +} + +static void pulp_rv_dm_init(Object *obj) +{ + PulpRVDMState *s = PULP_RV_DM(obj); + + MachineState *ms = MACHINE(qdev_get_machine()); + unsigned max_cpus = ms->smp.max_cpus; + s->hart_count = MIN(max_cpus, 64u); + + /* Top-level container */ + memory_region_init(&s->mem, obj, TYPE_PULP_RV_DM, 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mem); + + /* Top-level MMIO */ + memory_region_init_io(&s->regs, obj, &pulp_rv_dm_regs_ops, s, + TYPE_PULP_RV_DM ".regs", PULP_RV_DM_REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->regs); + + /* Mem container content */ + memory_region_init_io(&s->dmact, obj, &pulp_rv_dm_dmact_ops, s, + TYPE_PULP_RV_DM ".act", PULP_RV_DM_DMACT_SIZE); + memory_region_add_subregion(&s->mem, PULP_RV_DM_DMACT_BASE, &s->dmact); + + memory_region_init_ram_nomigrate(&s->prog, obj, TYPE_PULP_RV_DM ".prog", + PULP_RV_DM_PROG_SIZE, &error_fatal); + memory_region_add_subregion(&s->mem, PULP_RV_DM_PROG_BASE, &s->prog); + + memory_region_init_io(&s->dmflag, obj, &pulp_rv_dm_dmflag_ops, s, + TYPE_PULP_RV_DM ".flag", PULP_RV_DM_DMFLAG_SIZE); + memory_region_add_subregion(&s->mem, PULP_RV_DM_DMFLAG_BASE, &s->dmflag); + s->dmflag.disable_reentrancy_guard = true; + + memory_region_init_rom_nomigrate(&s->rom, obj, TYPE_PULP_RV_DM ".rom", + PULP_RV_DM_ROM_SIZE, &error_abort); + memory_region_add_subregion(&s->mem, PULP_RV_DM_ROM_BASE, &s->rom); + + s->ack_out = g_new0(qemu_irq, ACK_COUNT); + qdev_init_gpio_out_named(DEVICE(obj), s->ack_out, PULP_RV_DM_ACK_OUT_LINES, + ACK_COUNT); + + pulp_rv_dm_load_rom(s); + + ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); +} + +static void pulp_rv_dm_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + (void)data; + + device_class_set_legacy_reset(dc, &pulp_rv_dm_reset); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo pulp_rv_dm_info = { + .name = TYPE_PULP_RV_DM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PulpRVDMState), + .instance_init = &pulp_rv_dm_init, + .class_init = &pulp_rv_dm_class_init, +}; + +static void pulp_rv_dm_register_types(void) +{ + type_register_static(&pulp_rv_dm_info); +} + +type_init(pulp_rv_dm_register_types); diff --git a/hw/misc/trace-events b/hw/misc/trace-events index eeb9243898e9c..cc21b7b489eb4 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -409,3 +409,7 @@ ivshmem_flat_interrupt_peer(uint16_t peer_id, uint16_t vector_id) "Interrupting i2c_echo_event(const char *id, const char *event) "%s: %s" i2c_echo_recv(const char *id, uint8_t data) "%s: recv 0x%02" PRIx8 i2c_echo_send(const char *id, uint8_t data) "%s: send 0x%02" PRIx8 + +# pulp_rv_dm.c +pulp_rv_dm_mem_read_out(unsigned int addr, uint64_t val, uint64_t pc) "addr=0x%02x, val=0x%" PRIx64 ", pc=0x%" PRIx64 +pulp_rv_dm_mem_write(unsigned int addr, uint64_t val, uint64_t pc) "addr=0x%02x, val=0x%" PRIx64 ", pc=0x%" PRIx64 diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index a076ea7fdfc92..b95f89dda1f16 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -81,6 +81,17 @@ config RISCV_VIRT select ACPI select ACPI_PCI +config RISCV_DEBUG + bool + +config RISCV_DM + select RISCV_DEBUG + bool + +config RISCV_DTM + select TAP_CTRL + bool + config SHAKTI_C bool default y diff --git a/hw/riscv/debug.c b/hw/riscv/debug.c new file mode 100644 index 0000000000000..ed3b7b07239eb --- /dev/null +++ b/hw/riscv/debug.c @@ -0,0 +1,56 @@ +/* + * QEMU RISC-V Debug + * + * 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. + * + * Generated code for absract commands has been extracted from the PULP Debug + * module whose file (dm_mem.sv) contains the following copyright. This piece of + * code is self contained within the `riscv_dmi_dm_access_register` function: + * + * Copyright and related rights are licensed under the Solderpad Hardware + * License, Version 0.51 (the “License”); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law + * or agreed to in writing, software, hardware and materials distributed under + * this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +#include "qemu/osdep.h" +#include "hw/riscv/debug.h" + +static const TypeInfo riscv_debug_device_info = { + .name = TYPE_RISCV_DEBUG_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(RISCVDebugDeviceState), + .class_size = sizeof(RISCVDebugDeviceClass), + .abstract = true, +}; + +static void riscv_debug_register_types(void) +{ + type_register_static(&riscv_debug_device_info); +} + +type_init(riscv_debug_register_types); diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c new file mode 100644 index 0000000000000..c8ef571ec6fdb --- /dev/null +++ b/hw/riscv/dm.c @@ -0,0 +1,2794 @@ +/* + * QEMU Debug Module Interface and Controller + * + * Copyright (c) 2022-2025 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. + * + * Generated code for absract commands has been extracted from the PULP Debug + * module whose file (dm_mem.sv) contains the following copyright. This piece of + * code is self contained within the `riscv_dm_access_register` function: + * + * Copyright and related rights are licensed under the Solderpad Hardware + * License, Version 0.51 (the “License”); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law + * or agreed to in writing, software, hardware and materials distributed under + * this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * Limitations: + * - Unsupported features: + * - PMP management + * - DCSR.STEPIE + * - Cancellation of outstanding halt request + * - Halt on reset + * - Not tested: + * - User mode debugging + */ + +#include "qemu/osdep.h" +#include "qemu/compiler.h" +#include "qemu/log.h" +#include "qemu/typedefs.h" +#include "qapi/error.h" +#include "accel/tcg/cpu-ldst.h" +#include "disas/dis-asm.h" +#include "hw/boards.h" +#include "hw/core/cpu.h" +#include "hw/jtag/tap_ctrl.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/debug.h" +#include "hw/riscv/dm.h" +#include "hw/riscv/dtm.h" +#include "system/hw_accel.h" +#include "system/runstate.h" +#include "target/riscv/cpu.h" +#include "trace.h" + + +#undef TRACE_CPU_STATES + +/* + * Register definitions + */ + +#define RISCV_DEBUG_DM_VERSION 2u /* Debug Module v0.13.x */ +#define RISCV_DEBUG_SB_VERSION 1u /* System Bus v1.0 */ +#define RISCVDM_ABSTRACTDATA_SLOTS 10u +#define ADDRESS_BITS 7u + +/* clang-format off */ + +/* Debug Module registers */ +REG32(DATA0, 0x04u) +REG32(DATA1, 0x05u) +REG32(DATA2, 0x06u) +REG32(DATA3, 0x07u) +REG32(DATA4, 0x08u) +REG32(DATA5, 0x09u) +REG32(DATA6, 0x0au) +REG32(DATA7, 0x0bu) +REG32(DATA8, 0x0cu) +REG32(DATA9, 0x0du) +REG32(DATA10, 0x0eu) +REG32(DATA11, 0x0fu) +REG32(DMCONTROL, 0x10u) + FIELD(DMCONTROL, DMACTIVE, 0u, 1u) + FIELD(DMCONTROL, NDMRESET, 1u, 1u) + FIELD(DMCONTROL, CLRRESETHALTREQ, 2u, 1u) + FIELD(DMCONTROL, SETRESETHALTREQ, 3u, 1u) + FIELD(DMCONTROL, HARTSELHI, 6u, 10u) + FIELD(DMCONTROL, HARTSELLO, 16u, 10u) + FIELD(DMCONTROL, HASEL, 26u, 1u) + FIELD(DMCONTROL, ACKHAVERESET, 28u, 1u) + FIELD(DMCONTROL, HARTRESET, 29u, 1u) + FIELD(DMCONTROL, RESUMEREQ, 30u, 1u) + FIELD(DMCONTROL, HALTREQ, 31u, 1u) +REG32(DMSTATUS, 0x11u) + FIELD(DMSTATUS, VERSION, 0u, 4u) + FIELD(DMSTATUS, CONFSTRPTRVALID, 4u, 1u) + FIELD(DMSTATUS, HASRESETHALTREQ, 5u, 1u) + FIELD(DMSTATUS, AUTHBUSY, 6u, 1u) + FIELD(DMSTATUS, AUTHENTICATED, 7u, 1u) + FIELD(DMSTATUS, ANYHALTED, 8u, 1u) + FIELD(DMSTATUS, ALLHALTED, 9u, 1u) + FIELD(DMSTATUS, ANYRUNNING, 10u, 1u) + FIELD(DMSTATUS, ALLRUNNING, 11u, 1u) + FIELD(DMSTATUS, ANYUNAVAIL, 12u, 1u) + FIELD(DMSTATUS, ALLUNAVAIL, 13u, 1u) + FIELD(DMSTATUS, ANYNONEXISTENT, 14u, 1u) + FIELD(DMSTATUS, ALLNONEXISTENT, 15u, 1u) + FIELD(DMSTATUS, ANYRESUMEACK, 16u, 1u) + FIELD(DMSTATUS, ALLRESUMEACK, 17u, 1u) + FIELD(DMSTATUS, ANYHAVERESET, 18u, 1u) + FIELD(DMSTATUS, ALLHAVERESET, 19u, 1u) + FIELD(DMSTATUS, IMPEBREAK, 22u, 1u) +REG32(HARTINFO, 0x12u) + FIELD(HARTINFO, DATAADDR, 0u, 12u) + FIELD(HARTINFO, DATASIZE, 12u, 4u) + FIELD(HARTINFO, DATAACCESS, 16u, 1u) + FIELD(HARTINFO, NSCRATCH, 20u, 4u) +REG32(ABSTRACTCS, 0x16u) + FIELD(ABSTRACTCS, DATACOUNT, 0u, 4u) + FIELD(ABSTRACTCS, CMDERR, 8u, 3u) + FIELD(ABSTRACTCS, BUSY, 12u, 1u) + FIELD(ABSTRACTCS, PROGBUFSIZE, 24u, 5u) +REG32(COMMAND, 0x17u) + /* muxed fields: access register */ + FIELD(COMMAND, CONTROL, 0u, 24u) + FIELD(COMMAND, CMDTYPE, 24u, 8u) + FIELD(COMMAND, REG_REGNO, 0u, 16u) + FIELD(COMMAND, REG_WRITE, 16u, 1u) + FIELD(COMMAND, REG_TRANSFER, 17u, 1u) + FIELD(COMMAND, REG_POSTEXEC, 18u, 1u) + FIELD(COMMAND, REG_AARPOSTINCREMENT, 19u, 1u) + FIELD(COMMAND, REG_AARSIZE, 20u, 3u) + /* muxed fields: access memory */ + FIELD(COMMAND, MEM_WRITE, 16u, 1u) + FIELD(COMMAND, MEM_AAMPOSTINCREMENT, 19u, 1u) + FIELD(COMMAND, REG_AAMSIZE, 20u, 3u) + FIELD(COMMAND, MEM_AAMVIRTUAL, 23u, 1u) +REG32(ABSTRACTAUTO, 0x18u) + FIELD(ABSTRACTAUTO, AUTOEXECDATA, 0u, 12u) + FIELD(ABSTRACTAUTO, AUTOEXECPROGBUF, 16u, 16u) +REG32(NEXTDM, 0x1d) +REG32(PROGBUF0, 0x20u) +REG32(PROGBUF1, 0x21u) +REG32(PROGBUF2, 0x22u) +REG32(PROGBUF3, 0x23u) +REG32(PROGBUF4, 0x24u) +REG32(PROGBUF5, 0x25u) +REG32(PROGBUF6, 0x26u) +REG32(PROGBUF7, 0x27u) +REG32(PROGBUF8, 0x28u) +REG32(PROGBUF9, 0x29u) +REG32(PROGBUF10, 0x2au) +REG32(PROGBUF11, 0x2bu) +REG32(PROGBUF12, 0x2cu) +REG32(PROGBUF13, 0x2du) +REG32(PROGBUF14, 0x2eu) +REG32(PROGBUF15, 0x2fu) +REG32(SBCS, 0x38u) + FIELD(SBCS, SBACCESS8, 0u, 1u) + FIELD(SBCS, SBACCESS16, 1u, 1u) + FIELD(SBCS, SBACCESS32, 2u, 1u) + FIELD(SBCS, SBACCESS64, 3u, 1u) + FIELD(SBCS, SBACCESS128, 4u, 1u) + FIELD(SBCS, SBASIZE, 5u, 7u) + FIELD(SBCS, SBERROR, 12u, 3u) + FIELD(SBCS, SBREADONDATA, 15u, 1u) + FIELD(SBCS, SBAUTOINCREMENT, 16u, 1u) + FIELD(SBCS, SBACCESS, 17u, 3u) + FIELD(SBCS, SBREADONADDR, 20u, 1u) + FIELD(SBCS, SBBUSY, 21u, 1u) + FIELD(SBCS, SBBUSYERROR, 22u, 1u) + FIELD(SBCS, SBVERSION, 29u, 3u) +REG32(SBADDRESS0, 0x39u) +REG32(SBADDRESS1, 0x3au) +REG32(SBDATA0, 0x3cu) +REG32(SBDATA1, 0x3du) +REG32(HALTSUM0, 0x40u) + +#define A_FIRST A_DATA0 +#define A_LAST A_HALTSUM0 + +/* Debug CSRs */ +REG32(DCSR, CSR_DCSR) + FIELD(DCSR, PRV, 0u, 2u) + FIELD(DCSR, STEP, 2u, 1u) + FIELD(DCSR, NMIP, 3u, 1u) + FIELD(DCSR, MPRVEN, 4u, 1u) + FIELD(DCSR, CAUSE, 6u, 3u) + FIELD(DCSR, STOPTIME, 9u, 1u) + FIELD(DCSR, STOPCOUNT, 10u, 1u) + FIELD(DCSR, STEPIE, 11u, 1u) + FIELD(DCSR, EBREAKU, 12u, 1u) + FIELD(DCSR, EBREAKS, 13u, 1u) + FIELD(DCSR, EBREAKM, 15u, 1u) + FIELD(DCSR, XDEBUGVER, 28u, 4u) + +/* Debug module remote data */ +REG32(HALTED, RISCV_DM_HALTED_OFFSET) + FIELD(HALTED, HALTED, 0u, 1u) +REG32(GOING, RISCV_DM_GOING_OFFSET) + FIELD(GOING, GOING, 0u, 1u) +REG32(RESUMING, RISCV_DM_RESUMING_OFFSET) + FIELD(RESUMING, RESUMING, 0u, 1u) +REG32(EXCEPTION, RISCV_DM_EXCEPTION_OFFSET) + FIELD(EXCEPTION, EXCEPTION, 0u, 1u) +REG32(FLAGS, RISCV_DM_FLAGS_OFFSET) + FIELD(FLAGS, FLAG_GO, 0u, 1u) + FIELD(FLAGS, FLAG_RESUME, 1u, 1u) +/* clang-format on */ + +/* + * Macros + */ + +/* clang-format off */ +#define SBCS_WRITE_MASK (R_SBCS_SBERROR_MASK | \ + R_SBCS_SBREADONDATA_MASK | \ + R_SBCS_SBAUTOINCREMENT_MASK | \ + R_SBCS_SBACCESS_MASK | \ + R_SBCS_SBREADONADDR_MASK | \ + R_SBCS_SBBUSYERROR_MASK) +/* clang-format on */ + +#define GPR_ZERO 0u /* zero = x0 */ +#define GPR_S0 8u /* s0 = x8 */ +#define GPR_A0 10u /* a0 = x10 */ + +static_assert((A_LAST - A_FIRST) < 64u, "too many registers"); + +#define REG_BIT(_addr_) \ + (((_addr_) >= (uint32_t)A_FIRST) ? (1ull << ((_addr_) - A_FIRST)) : 0u) +#define REG_BIT_DEF(_reg_) REG_BIT(A_##_reg_) + +#define DM_REG_COUNT (1u << (ADDRESS_BITS)) +#define xtrace_riscv_dm_error(_soc_, _msg_) \ + trace_riscv_dm_error(_soc_, __func__, __LINE__, _msg_) +#define xtrace_riscv_dm_info(_soc_, _msg_, _val_) \ + trace_riscv_dm_info(_soc_, __func__, __LINE__, _msg_, _val_) +#define xtrace_reg(_soc_, _msg_, _reg_, _off_) \ + trace_riscv_dm_access_register(_soc_, _msg_, \ + get_riscv_debug_reg_name(_reg_), \ + (_reg_) - (_off_)); + +#define RISCVDM_DEFAULT_MTA 0x100000000ull /* "MEMTXATTRS_UNSPECIFIED" */ + +/* + * Type definitions + */ + +/** Debug Module command errors */ +typedef enum RISCVDMCmdErr { + CMD_ERR_NONE = 0, + CMD_ERR_BUSY = 1, + CMD_ERR_NOT_SUPPORTED = 2, + CMD_ERR_EXCEPTION = 3, + CMD_ERR_HALT_RESUME = 4, + CMD_ERR_BUS = 5, + _CMD_ERR_RSV1 = 6, + CMD_ERR_OTHER = 7, +} RISCVDMCmdErr; + +/* For debug purpose only, used to only dump traces on any change */ +typedef struct RISCVDMStateCache { + struct { + unsigned ix; + bool halted; + bool stopped; + bool running; + } cpu; + struct { + unsigned halted; + unsigned running; + unsigned unavail; + unsigned nonexistent; + unsigned resumeack; + unsigned havereset; + } dm; +} RISCVDMStateCache; + +typedef struct RISCVDMHartState { + RISCVCPU *cpu; /* Associated hart */ + target_ulong hartid; /** Hart ID */ + bool halted; /* Hart has halted execution */ + bool resumed; /* Hart has resumed execution */ + bool have_reset; /* Hart has reset, not yet supported */ + bool unlock_reset; /* Whether DM may reset CPU */ +#ifdef TRACE_CPU_STATES + RISCVDMStateCache dbgcache; +#endif +} RISCVDMHartState; + +typedef struct RISCVDMConfig { + unsigned nscratch; + unsigned progbuf_count; + unsigned data_count; + unsigned abstractcmd_count; + uint32_t dmi_addr; /* note: next_dm imposes that DM use 32-bit only addr. */ + uint32_t dmi_next; /* next_dm */ + hwaddr dm_phyaddr; + hwaddr rom_phyaddr; + hwaddr whereto_phyaddr; + hwaddr data_phyaddr; + hwaddr progbuf_phyaddr; + uint64_t mta_dm; /* MemTxAttrs */ + uint64_t mta_sba; /* MemTxAttrs */ + uint16_t resume_offset; + bool sysbus_access; + bool abstractauto; + bool enable; +} RISCVDMConfig; + +/** Debug Module */ +struct RISCVDMState { + RISCVDebugDeviceState parent; + + RISCVDMCmdErr cmd_err; /* Command result */ + RISCVDMHartState *hart; /* Currently selected hart for debug, if any */ + RISCVDMHartState *harts; /* Hart states */ + AddressSpace *as; /* Hart address space */ + const char *soc; /* Subsystem name, for debug */ + uint64_t nonexistent_bm; /* Selected harts that are not existent */ + uint64_t unavailable_bm; /* Selected harts that are not available */ + uint64_t haltreq_bm; /* Selected harts that have a pending halt request */ + uint64_t reset_bm; /* Selected harts that have an ongoing reset request */ + uint64_t to_go_bm; /* Harts that have been flagged for debug exec */ + uint32_t address; /* DM register addr: only bADDRESS_BITS..b0 are used */ + uint32_t *regs; /* Debug module register values */ + uint64_t sbdata; /* Last sysbus data */ + MemTxAttrs mta_dm; /* MemTxAttrs to access debug module implementation */ + MemTxAttrs mta_sba; /* MemTxAttrs to access system bus devices */ + bool cmd_busy; /* A command is being executed */ + bool dtm_ok; /* DTM is available */ + + /* config */ + RISCVDTMState *dtm; + RISCVDMConfig cfg; + uint32_t hart_count; /* Count of harts */ + uint32_t *cpu_idx; /* array of hart_count CPU index */ +}; + +struct RISCVDMClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; +}; + +typedef RISCVDMCmdErr CmdErr; + +/** Abstract command types */ +typedef enum RISCVDMAbstractCommand { + CMD_ACCESS_REGISTER = 0, + CMD_QUICK_ACCESS = 1, + CMD_ACCESS_MEMORY = 2, +} RISCVDMAbstractCommand; + +/** System bus access error */ +typedef enum RISCVDMSysbusError { + SYSBUS_NONE, + SYSBUS_TIMEOUT, + SYSBUS_BADADDR, + SYSBUS_BADALIGN, + SYSBUS_ASIZE, + SYSBUS_OTHER = 7, +} RISCVDMSysbusError; + +/** Debug Module register */ +struct RISCVDMDMReg; +typedef struct RISCVDMDMReg RISCVDMDMReg; + +/** Handlers for a Debug Module register */ +struct RISCVDMDMReg { + const char *name; /* register name, for debugging */ + const uint32_t value; /* preset bits */ + CmdErr (*read)(RISCVDMState *dm, uint32_t *value); + CmdErr (*write)(RISCVDMState *dm, uint32_t value); +}; + +/* + * Forward declarations + */ + +static bool riscv_dm_cond_autoexec(RISCVDMState *dm, bool prgbf, + unsigned regix); +static CmdErr riscv_dm_read_absdata(RISCVDMState *dm, unsigned woffset, + unsigned wcount, hwaddr *value); +static CmdErr riscv_dm_write_absdata(RISCVDMState *dm, unsigned woffset, + unsigned wcount, hwaddr value); +static CmdErr riscv_dm_read_progbuf(RISCVDMState *dm, unsigned woffset, + hwaddr *value); +static CmdErr riscv_dm_write_progbuf(RISCVDMState *dm, unsigned woffset, + hwaddr value); +static CmdErr riscv_dm_exec_command(RISCVDMState *dm, uint32_t value); + +static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_command_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_abstractauto_read(RISCVDMState *dm, uint32_t *value); +static CmdErr riscv_dm_abstractauto_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value); +static CmdErr riscv_dm_sbcs_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_sbaddress0_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_sbaddress1_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_sbdata0_read(RISCVDMState *dm, uint32_t *value); +static CmdErr riscv_dm_sbdata0_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_sbdata1_read(RISCVDMState *dm, uint32_t *value); +static CmdErr riscv_dm_sbdata1_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_hartinfo_read(RISCVDMState *dm, uint32_t *value); +static CmdErr riscv_dm_abstractcs_read(RISCVDMState *dm, uint32_t *value); +static CmdErr riscv_dm_abstractcs_write(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_haltsum0_read(RISCVDMState *dm, uint32_t *value); + +static CmdErr riscv_dm_access_register(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_access_memory(RISCVDMState *dm, uint32_t value); +static CmdErr riscv_dm_quick_access(RISCVDMState *dm, uint32_t value); + +static void riscv_dm_ensure_running(RISCVDMState *dm); +static void riscv_dm_halt_hart(RISCVDMState *dm, unsigned hartsel); +static void riscv_dm_resume_hart(RISCVDMState *dm, unsigned hartsel); + +/* + * Constants + */ + +/* DM update/capture registers that should not be traced in trace log */ +static const uint64_t RISCVDM_REG_IGNORE_TRACES = + /* the remote debugger keeps polling dmstatus (to get hart status) */ + REG_BIT_DEF(DMSTATUS) | + /* the remote debugger polls abstractcs quite often (to get busy/cmderr) */ + REG_BIT_DEF(ABSTRACTCS); + +static const RISCVDMDMReg RISCVDM_DMS[DM_REG_COUNT] = { + [A_DMCONTROL] = { + .name = "dmcontrol", + .write = &riscv_dm_dmcontrol_write, + }, + [A_DMSTATUS] = { + .name = "dmstatus", + .value = (RISCV_DEBUG_DM_VERSION << R_DMSTATUS_VERSION_SHIFT) | + (1u << R_DMSTATUS_AUTHENTICATED_SHIFT), + .read = &riscv_dm_dmstatus_read, + }, + [A_HARTINFO] = { + .name = "hartinfo", + .read = &riscv_dm_hartinfo_read, + }, + [A_ABSTRACTCS] = { + .name = "abstractcs", + .read = &riscv_dm_abstractcs_read, + .write = &riscv_dm_abstractcs_write, + }, + [A_COMMAND] = { + .name = "command", + .write = &riscv_dm_command_write, + }, + [A_ABSTRACTAUTO] = { + .name = "abstractauto", + .read = &riscv_dm_abstractauto_read, + .write = &riscv_dm_abstractauto_write, + }, + [A_NEXTDM] = { + .name = "nextdm", + }, + [A_SBCS] = { + .name = "sbcs", + .write = &riscv_dm_sbcs_write, + }, + [A_SBADDRESS0] = { + .name = "sbaddress0", + .write = &riscv_dm_sbaddress0_write, + }, + [A_SBADDRESS1] = { + .name = "sbaddress1", + .write = &riscv_dm_sbaddress1_write, + }, + [A_SBDATA0] = { + .name = "sbdata0", + .read = &riscv_dm_sbdata0_read, + .write = &riscv_dm_sbdata0_write, + }, + [A_SBDATA1] = { + .name = "sbdata1", + .read = &riscv_dm_sbdata1_read, + .write = &riscv_dm_sbdata1_write, + }, + [A_HALTSUM0] = { + .name = "haltsum0", + .read = &riscv_dm_haltsum0_read, + }, +}; + +#define MAKE_NAME_ENTRY(_pfx_, _ent_) [_pfx_##_##_ent_] = stringify(_ent_) +static const char *DCSR_CAUSE_NAMES[8u] = { + /* clang-format off */ + MAKE_NAME_ENTRY(DCSR_CAUSE, NONE), + MAKE_NAME_ENTRY(DCSR_CAUSE, EBREAK), + MAKE_NAME_ENTRY(DCSR_CAUSE, BREAKPOINT), + MAKE_NAME_ENTRY(DCSR_CAUSE, HALTREQ), + MAKE_NAME_ENTRY(DCSR_CAUSE, STEP), + MAKE_NAME_ENTRY(DCSR_CAUSE, RESETHALTREQ), + /* clang-format on */ +}; +#undef MAKE_NAME_ENTRY + +#define MAKE_REG_ENTRY(_ent_) stringify(_ent_) +static const char *RISCVDM_DM_DATA_NAMES[12u] = { + /* clang-format off */ + MAKE_REG_ENTRY(data0), + MAKE_REG_ENTRY(data1), + MAKE_REG_ENTRY(data2), + MAKE_REG_ENTRY(data3), + MAKE_REG_ENTRY(data4), + MAKE_REG_ENTRY(data5), + MAKE_REG_ENTRY(data6), + MAKE_REG_ENTRY(data7), + MAKE_REG_ENTRY(data8), + MAKE_REG_ENTRY(data9), + MAKE_REG_ENTRY(data10), + MAKE_REG_ENTRY(data11), + /* clang-format on */ +}; + +static const char *RISCVDM_DM_PROGBUF_NAMES[16u] = { + /* clang-format off */ + MAKE_REG_ENTRY(progbuf0), + MAKE_REG_ENTRY(progbuf1), + MAKE_REG_ENTRY(progbuf2), + MAKE_REG_ENTRY(progbuf3), + MAKE_REG_ENTRY(progbuf4), + MAKE_REG_ENTRY(progbuf5), + MAKE_REG_ENTRY(progbuf6), + MAKE_REG_ENTRY(progbuf7), + MAKE_REG_ENTRY(progbuf8), + MAKE_REG_ENTRY(progbuf9), + MAKE_REG_ENTRY(progbuf10), + MAKE_REG_ENTRY(progbuf11), + MAKE_REG_ENTRY(progbuf12), + MAKE_REG_ENTRY(progbuf13), + MAKE_REG_ENTRY(progbuf14), + MAKE_REG_ENTRY(progbuf15), + /* clang-format on */ +}; +#undef MAKE_REG_ENTRY + +static const char *riscv_dm_get_reg_name(unsigned addr); + +/* -------------------------------------------------------------------------- */ +/* DMI interface implementation */ +/* -------------------------------------------------------------------------- */ + +static RISCVDebugResult +riscv_dm_write_rq(RISCVDebugDeviceState *dev, uint32_t addr, uint32_t value) +{ + RISCVDMState *dm = RISCV_DM(dev); + + if (resettable_is_in_reset(OBJECT(dev))) { + if (addr != A_DMCONTROL) { + xtrace_riscv_dm_error(dm->soc, "write rejected: DM in reset"); + return RISCV_DEBUG_FAILED; + } + } + + CmdErr ret; + bool autoexec = false; + + /* store address for next read back */ + dm->address = addr; + + if ((addr >= A_DATA0) && (addr < (A_DATA0 + dm->cfg.data_count))) { + unsigned dix = addr - A_DATA0; + if (!(ret = riscv_dm_write_absdata(dm, dix, 1u, (hwaddr)value))) { + dm->regs[addr] = value; + autoexec = riscv_dm_cond_autoexec(dm, false, dix); + } + } else if ((addr >= A_PROGBUF0) && + (addr < (A_PROGBUF0 + dm->cfg.progbuf_count))) { + unsigned pbix = addr - A_PROGBUF0; + if (!(ret = riscv_dm_write_progbuf(dm, pbix, (hwaddr)value))) { + dm->regs[addr] = value; + autoexec = riscv_dm_cond_autoexec(dm, true, pbix); + } + } else if (RISCVDM_DMS[addr].write) { + ret = RISCVDM_DMS[addr].write(dm, value); + } else { + xtrace_riscv_dm_info(dm->soc, "write request ignored @", addr); + ret = CMD_ERR_NONE; + } + if (ret != CMD_ERR_NONE) { + xtrace_riscv_dm_error(dm->soc, "fail to write"); + } + + if ((ret == CMD_ERR_NONE) && autoexec) { + xtrace_riscv_dm_info(dm->soc, "autoexec last command", + dm->regs[A_COMMAND]); + ret = riscv_dm_exec_command(dm, dm->regs[A_COMMAND]); + } + + /* do not override a previous error, which should be explicitly cleared */ + if (dm->cmd_err == CMD_ERR_NONE) { + dm->cmd_err = ret; + } + + if ((addr >= A_FIRST) && !(RISCVDM_REG_IGNORE_TRACES & REG_BIT(addr))) { + trace_riscv_dm_reg_update(dm->soc, riscv_dm_get_reg_name(addr), addr, + value, "write", ret); + } + + return RISCV_DEBUG_NOERR; +} + +static RISCVDebugResult +riscv_dm_read_rq(RISCVDebugDeviceState *dev, uint32_t addr) +{ + RISCVDMState *dm = RISCV_DM(dev); + + CmdErr ret; + bool autoexec = false; + uint32_t value = 0; + + /* store address for next read back */ + dm->address = addr; + + if ((addr >= A_DATA0) && (addr < (A_DATA0 + dm->cfg.data_count))) { + unsigned dix = addr - A_DATA0; + hwaddr val = 0u; + if (!(ret = riscv_dm_read_absdata(dm, dix, 1u, &val))) { + dm->regs[addr] = (uint32_t)val; + autoexec = riscv_dm_cond_autoexec(dm, false, dix); + } + } else if ((addr >= A_PROGBUF0) && + (addr < (A_PROGBUF0 + dm->cfg.progbuf_count))) { + unsigned pbix = addr - A_PROGBUF0; + hwaddr val; + if (!(ret = riscv_dm_read_progbuf(dm, pbix, &val))) { + dm->regs[addr] = (uint32_t)val; + autoexec = riscv_dm_cond_autoexec(dm, true, pbix); + } + } else if (RISCVDM_DMS[addr].read) { + ret = RISCVDM_DMS[addr].read(dm, &value); + } else { + ret = CMD_ERR_NONE; + value = dm->regs[addr]; + } + + if (ret != CMD_ERR_NONE) { + xtrace_riscv_dm_error(dm->soc, "fail to read"); + } else if (autoexec) { + xtrace_riscv_dm_info(dm->soc, "autoexec last command", + dm->regs[A_COMMAND]); + ret = riscv_dm_exec_command(dm, dm->regs[A_COMMAND]); + } + + if ((addr >= A_FIRST) && !(RISCVDM_REG_IGNORE_TRACES & REG_BIT(addr))) { + trace_riscv_dm_reg_update(dm->soc, riscv_dm_get_reg_name(addr), addr, + value, "read", ret); + } + + /* do not override a previous error, which should be explicitly cleared */ + if (dm->cmd_err == CMD_ERR_NONE) { + dm->cmd_err = ret; + } + + return RISCV_DEBUG_NOERR; +} + +static uint32_t riscv_dm_read_value(RISCVDebugDeviceState *dev) +{ + RISCVDMState *dm = RISCV_DM(dev); + + if (dm->address >= DM_REG_COUNT) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid address: 0x%x\n", __func__, + dm->address); + + return 0; + } + + uint32_t value = dm->regs[dm->address]; + + if (!(RISCVDM_REG_IGNORE_TRACES & REG_BIT(dm->address))) { + trace_riscv_dm_reg_capture(dm->soc, riscv_dm_get_reg_name(dm->address), + dm->address, value); + } + + return value; +} + +static void riscv_dm_set_next_dm(RISCVDebugDeviceState *dev, uint32_t addr) +{ + RISCVDMState *dm = RISCV_DM(dev); + + dm->regs[A_NEXTDM] = addr; +} + +/* -------------------------------------------------------------------------- */ +/* DM implementation */ +/* -------------------------------------------------------------------------- */ + +static const char *riscv_dm_get_reg_name(unsigned addr) +{ + if ((addr >= A_DATA0) && (addr <= A_DATA11)) { + return RISCVDM_DM_DATA_NAMES[addr - A_DATA0]; + } + + if ((addr >= A_PROGBUF0) && (addr <= A_PROGBUF15)) { + return RISCVDM_DM_PROGBUF_NAMES[addr - A_PROGBUF0]; + } + + if (addr < DM_REG_COUNT) { + return RISCVDM_DMS[addr].name; + } + + return "INVALID"; +} + + +static bool riscv_dm_cond_autoexec(RISCVDMState *dm, bool prgbf, unsigned regix) +{ + uint32_t autoexec = + prgbf ? + FIELD_EX32(dm->regs[A_ABSTRACTAUTO], ABSTRACTAUTO, + AUTOEXECPROGBUF) : + FIELD_EX32(dm->regs[A_ABSTRACTAUTO], ABSTRACTAUTO, AUTOEXECDATA); + return (bool)(autoexec & (1u << regix)); +} + + +static CmdErr riscv_dm_read_absdata(RISCVDMState *dm, unsigned woffset, + unsigned wcount, hwaddr *value) +{ + if (!dm->cfg.data_phyaddr) { + /* CSR-shadowed implementation is not supported */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + if ((woffset + wcount > dm->cfg.data_count) || (wcount > 2u)) { + xtrace_riscv_dm_error(dm->soc, "invalid arg"); + return CMD_ERR_OTHER; + } + + /* use a memory location to store abstract data */ + MemTxResult res; + res = address_space_rw(dm->as, dm->cfg.data_phyaddr + (woffset << 2u), + dm->mta_dm, value, wcount << 2u, false); + trace_riscv_dm_absdata(dm->soc, "read", woffset, wcount, *value, res); + if (res != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memtx"); + return CMD_ERR_BUS; + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_write_absdata(RISCVDMState *dm, unsigned woffset, + unsigned wcount, hwaddr value) +{ + if (!dm->cfg.data_phyaddr) { + /* CSR-shadowed implementation is not supported */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + if ((woffset + wcount > dm->cfg.data_count) || (wcount > 2u)) { + xtrace_riscv_dm_error(dm->soc, "invalid arg"); + return CMD_ERR_OTHER; + } + + /* use a memory location to store abstract data */ + MemTxResult res; + res = address_space_rw(dm->as, dm->cfg.data_phyaddr + (woffset << 2u), + dm->mta_dm, &value, wcount << 2u, true); + trace_riscv_dm_absdata(dm->soc, "write", woffset, wcount, value, res); + if (res != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memtx"); + return CMD_ERR_BUS; + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_read_progbuf(RISCVDMState *dm, unsigned woffset, + hwaddr *value) +{ + if (!dm->cfg.progbuf_phyaddr) { + /* CSR-shadowed implementation is not supported */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + if (woffset >= dm->cfg.progbuf_count) { + xtrace_riscv_dm_error(dm->soc, "invalid arg"); + return CMD_ERR_OTHER; + } + + /* use a memory location to store abstract data */ + MemTxResult res; + res = address_space_rw(dm->as, dm->cfg.progbuf_phyaddr + (woffset << 2u), + dm->mta_dm, value, sizeof(uint32_t), false); + trace_riscv_dm_progbuf(dm->soc, "read", woffset, *value, res); + if (res != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memtx"); + return CMD_ERR_BUS; + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_write_progbuf(RISCVDMState *dm, unsigned woffset, + hwaddr value) +{ + if (!dm->cfg.progbuf_phyaddr) { + /* CSR-shadowed implementation is not supported */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + if (woffset >= dm->cfg.progbuf_count) { + xtrace_riscv_dm_error(dm->soc, "invalid arg"); + return CMD_ERR_OTHER; + } + + /* use a memory location to store abstract data */ + MemTxResult res; + res = address_space_rw(dm->as, dm->cfg.progbuf_phyaddr + (woffset << 2u), + dm->mta_dm, &value, sizeof(uint32_t), true); + trace_riscv_dm_progbuf(dm->soc, "write", woffset, value, res); + if (res != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memtx"); + return CMD_ERR_BUS; + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_write_whereto(RISCVDMState *dm, uint32_t value) +{ + /* use a memory location to store the where-to-jump location */ + if (address_space_rw(dm->as, dm->cfg.whereto_phyaddr, dm->mta_dm, &value, + sizeof(value), true) != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memtx"); + return CMD_ERR_BUS; + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_update_flags(RISCVDMState *dm, unsigned hartnum, + bool set, uint32_t flag_mask) +{ + if (!dm->cfg.dm_phyaddr) { + /* CSR-shadowed implementation is not supported */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + if (hartnum >= dm->hart_count) { + xtrace_riscv_dm_error(dm->soc, "internal error"); + return CMD_ERR_OTHER; + } + + /* + * optional second scratch register is used in Debug ROM to use a different + * location for each hart + */ + unsigned foffset = dm->cfg.nscratch > 1u ? hartnum * sizeof(uint32_t) : 0u; + + /* + * note: not sure whether this read-modify-write sequence is required, + * as it seems that flag values (GO/RESUME) are exclusive; a simple write + * might be enough + */ + MemTxResult res; + uint32_t flag_bm; + hwaddr flagaddr = dm->cfg.dm_phyaddr + A_FLAGS + foffset; + res = address_space_rw(dm->as, flagaddr, dm->mta_dm, &flag_bm, + sizeof(flag_bm), false); + if (res != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memrx"); + return CMD_ERR_BUS; + } + if (set) { + flag_bm |= flag_mask; + } else { + flag_bm &= ~flag_mask; + } + res = address_space_rw(dm->as, flagaddr, dm->mta_dm, &flag_bm, + sizeof(flag_bm), true); + if (res != MEMTX_OK) { + xtrace_riscv_dm_error(dm->soc, "memtx"); + return CMD_ERR_BUS; + } + + return CMD_ERR_NONE; +} + +/* + * DM status acknowledgement + */ + +static RISCVDMHartState * +riscv_dm_get_hart_from_id(RISCVDMState *dm, unsigned hartid) +{ + /* hart debugger index is not equivalent to the hartid */ + unsigned hix = 0; + while (hix < dm->hart_count) { + if (dm->harts[hix].hartid == (target_ulong)hartid) { + return &dm->harts[hix]; + } + hix++; + } + return NULL; +} + +static void riscv_dm_set_busy(RISCVDMState *dm, bool busy) +{ + dm->cmd_busy = busy; + trace_riscv_dm_busy(dm->soc, busy); +} + +static void riscv_dm_set_cs(RISCVDMState *dm, bool enable) +{ + dm->hart->cpu->env.debug_cs = enable; + trace_riscv_dm_cs(dm->soc, enable); +} + +static uint32_t riscv_dmi_get_debug_cause(RISCVCPU *cpu) +{ + return FIELD_EX32(cpu->env.dcsr, DCSR, CAUSE); +} + +static const char *riscv_dmi_get_debug_cause_name(RISCVCPU *cpu) +{ + return DCSR_CAUSE_NAMES[riscv_dmi_get_debug_cause(cpu)]; +} + +static void riscv_dm_acknowledge(void *opaque, int irq, int level) +{ + /* + * Note: this function is called from the VCPU thread, whereas the other + * functions are run from the main/iothread. + * Nevertheless, all runs w/ iothread_locked, so there should not be race + * conditions (TBC...) + */ + RISCVDMState *dm = opaque; + RISCVDMHartState *hart; + unsigned hartnum; + + assert(bql_locked()); + + switch (irq) { + case ACK_HALTED: + hartnum = (unsigned)level; + if ((hart = riscv_dm_get_hart_from_id(dm, hartnum))) { + hart->halted = true; + uint64_t hbm = 1u << hartnum; + if (dm->unavailable_bm & hbm) { + qemu_log("%s: %s: an unavailable hart should not be halted\n", + __func__, dm->soc); + /* ensure hart can only be a single state */ + dm->unavailable_bm &= ~hbm; + } + riscv_dm_set_busy(dm, false); + trace_riscv_dm_halted(dm->soc, hart - &dm->harts[0], + hart->cpu->env.dpc, + riscv_dmi_get_debug_cause_name(hart->cpu)); + } + break; + case ACK_GOING: + /* level value is meaningless */ + if (!dm->to_go_bm) { + /* internal error */ + xtrace_riscv_dm_error(dm->soc, "Go ack w/o action"); + hart = NULL; + break; + } + while (dm->to_go_bm) { + hartnum = __builtin_ctz(dm->to_go_bm); + if (hartnum >= dm->hart_count) { + /* internal error, should never occur */ + xtrace_riscv_dm_error(dm->soc, "incoherent go bitmap"); + hart = NULL; + } else { + hart = &dm->harts[hartnum]; + if (riscv_dm_update_flags(dm, hartnum, false, + R_FLAGS_FLAG_GO_MASK)) { + /* nothing we can do here */ + xtrace_riscv_dm_error(dm->soc, + "unable to lower going flag"); + hart = NULL; + } + } + dm->to_go_bm &= ~(1u << hartnum); + } + trace_riscv_dm_hart_state(dm->soc, hartnum, "debug ongoing"); + break; + case ACK_RESUMING: + hartnum = (unsigned)level; + if ((hart = riscv_dm_get_hart_from_id(dm, hartnum))) { + if (riscv_dm_update_flags(dm, (unsigned)level, false, + R_FLAGS_FLAG_RESUME_MASK)) { + /* nothing we can do here */ + xtrace_riscv_dm_error(dm->soc, "unable to lower resume flag"); + } + hart->halted = false; + hart->resumed = true; + uint64_t hbm = 1u << hartnum; + if (dm->unavailable_bm & hbm) { + qemu_log("%s: %s: an unavailable hart should not be resumed\n", + __func__, dm->soc); + /* ensure hart can only be a single state */ + dm->unavailable_bm &= ~hbm; + } + bool sstep = (bool)FIELD_EX32(hart->cpu->env.dcsr, DCSR, STEP); + riscv_dm_set_cs(dm, sstep); + trace_riscv_dm_hart_state(dm->soc, hartnum, "has resumed"); + } + break; + case ACK_EXCEPTION: + /* level value is meaningless */ + hart = dm->hart; + if (!hart) { + /* likely a more serious issue */ + qemu_log("%s: no selected hart\n", __func__); + break; + } + dm->cmd_err = CMD_ERR_EXCEPTION; + riscv_dm_set_cs(dm, false); + riscv_dm_set_busy(dm, false); + trace_riscv_dm_hart_state(dm->soc, hart - &dm->harts[0], + "exception in debug"); + break; + default: + xtrace_riscv_dm_error(dm->soc, "unknown ack line"); + return; + } + + if (!hart) { + xtrace_riscv_dm_error(dm->soc, "no hart to acknowledge"); + return; + } +} + +/* + * Instruction generation + */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +static uint32_t riscv_dm_rm(uint32_t reg) +{ + return reg & 0x1fu; +} + +static uint32_t riscv_dm_csr(uint32_t reg) +{ + return reg & 0xfffu; +} + +static uint32_t riscv_dm_r3(uint32_t reg) +{ + return reg & 0x7u; +} + +static uint32_t riscv_dm_bm(uint32_t bit) +{ + return 1u << bit; +} + +static uint32_t riscv_dm_bmr(uint32_t msb, uint32_t lsb) +{ + return ((1u << msb) - 1u) & ~((1u << lsb) - 1u); +} + +static uint32_t riscv_dm_insn_jal(uint32_t rd, uint32_t imm) +{ + return (get_field(imm, riscv_dm_bm(20)) << 31u) | + (get_field(imm, riscv_dm_bmr(10, 1)) << 21u) | + (get_field(imm, riscv_dm_bm(11)) << 20u) | + (get_field(imm, riscv_dm_bmr(19, 12)) << 12u) | + (riscv_dm_rm(rd) << 7u) | 0x6fu; +} + +static uint32_t riscv_dm_insn_jalr(uint32_t rd, uint32_t rs1, uint32_t offset) +{ + return (get_field(offset, riscv_dm_bmr(11, 0)) << 20u) | + (riscv_dm_rm(rs1) << 15u) | (0b000u << 12u) | + (riscv_dm_rm(rd) << 7u) | 0x67u; +} + +static uint32_t riscv_dm_insn_andi(uint32_t rd, uint32_t rs1, uint32_t imm) +{ + return (get_field(imm, riscv_dm_bmr(11, 0)) << 20u) | + (riscv_dm_rm(rs1) << 15u) | (0b111u << 12u) | + (riscv_dm_rm(rd) << 7u) | 0x13u; +} + +static uint32_t riscv_dm_insn_slli(uint32_t rd, uint32_t rs1, uint32_t shamt) +{ + return (get_field(shamt, riscv_dm_bmr(5, 0)) << 20u) | + (riscv_dm_rm(rs1) << 15u) | (0b001u << 12u) | + (riscv_dm_rm(rd) << 7u) | 0x13u; +} + +static uint32_t riscv_dm_insn_srli(uint32_t rd, uint32_t rs1, uint32_t shamt) +{ + return (get_field(shamt, riscv_dm_bmr(5, 0)) << 20u) | + (riscv_dm_rm(rs1) << 15u) | (0b101u << 12u) | + (riscv_dm_rm(rd) << 7u) | 0x13u; +} + +static uint32_t riscv_dm_insn_load(uint32_t size, uint32_t dst, uint32_t base, + uint32_t offset) +{ + return (get_field(offset, riscv_dm_bmr(11, 0)) << 20u) | + (riscv_dm_rm(base) << 15u) | (riscv_dm_r3(size) << 12u) | + (riscv_dm_rm(dst) << 7u) | 0x03u; +} + +static uint32_t riscv_dm_insn_auipc(uint32_t rd, uint32_t imm) +{ + return (get_field(imm, riscv_dm_bm(20)) << 31u) | + (get_field(imm, riscv_dm_bmr(10, 1)) << 21u) | + (get_field(imm, riscv_dm_bm(11)) << 20u) | + (get_field(imm, riscv_dm_bmr(19, 12)) << 12u) | + (riscv_dm_rm(rd) << 7u) | 0x17u; +} + +static uint32_t riscv_dm_insn_store(uint32_t size, uint32_t src, uint32_t base, + uint32_t offset) +{ + return (get_field(offset, riscv_dm_bmr(11, 5)) << 25u) | + (riscv_dm_rm(src) << 20u) | (riscv_dm_rm(base) << 15u) | + (riscv_dm_r3(size) << 12u) | + (get_field(offset, riscv_dm_bmr(4, 0)) << 7u) | 0x23u; +} + +static uint32_t riscv_dm_insn_float_load(uint32_t size, uint32_t dst, + uint32_t base, uint32_t offset) +{ + return (get_field(offset, riscv_dm_bmr(11, 0)) << 20u) | + (riscv_dm_rm(base) << 15u) | (riscv_dm_r3(size) << 10u) | + (riscv_dm_rm(dst) << 7u) | 0b0000111u; +} + +static uint32_t riscv_dm_insn_float_store(uint32_t size, uint32_t src, + uint32_t base, uint32_t offset) +{ + return (get_field(offset, riscv_dm_bmr(11, 5)) << 25u) | + (riscv_dm_rm(src) << 20u) | (riscv_dm_rm(base) << 15u) | + (riscv_dm_r3(size) << 12u) | + (get_field(offset, riscv_dm_bmr(4, 0)) << 7u) | 0b0100111u; +} + +static uint32_t riscv_dm_insn_csrw(uint32_t csr, uint32_t rs1) +{ + return (riscv_dm_csr(csr) << 20u) | (riscv_dm_rm(rs1) << 15u) | + (0b001u << 12u) | (riscv_dm_csr(0) << 7u) | 0x73u; +} + +static uint32_t riscv_dm_insn_csrr(uint32_t csr, uint32_t dst) +{ + return (riscv_dm_csr(csr) << 20u) | (riscv_dm_rm(0) << 15u) | + (0b010u << 12u) | (riscv_dm_csr(dst) << 7u) | 0x73u; +} + +static uint32_t riscv_dm_insn_branch(uint32_t src2, uint32_t src1, + uint32_t funct3, uint32_t offset) +{ + return (get_field(offset, riscv_dm_bm(11)) << 31u) | + (get_field(offset, riscv_dm_bmr(9, 4)) << 25u) | + (riscv_dm_rm(src2) << 20u) | (riscv_dm_rm(src1) << 15u) | + (riscv_dm_r3(funct3) << 12u) | + (get_field(offset, riscv_dm_bmr(3, 0)) << 8u) | + (get_field(offset, riscv_dm_bm(10)) << 7u) | 0b1100011u; +} + +static uint16_t riscv_dm_insn_c_ebreak(void) +{ + return 0x9002u; +} + +static uint32_t riscv_dm_insn_ebreak(void) +{ + return 0x00100073u; +} + +static uint32_t riscv_dm_insn_wfi(void) +{ + return 0x10500073u; +} + +static uint32_t riscv_dm_insn_nop(void) +{ + return 0x00000013u; +} + +static uint32_t riscv_dm_insn_illegal(void) +{ + return 0x00000000u; +} + +#pragma GCC diagnostic pop + +/* + * DM register implementation + */ + +static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) +{ + if (unlikely(!FIELD_EX32(value, DMCONTROL, DMACTIVE))) { + /* Debug Module reset */ + if (!resettable_is_in_reset(OBJECT(dm))) { + trace_riscv_dm_reset(dm->soc, "debugger requested DM reset"); + resettable_assert_reset(OBJECT(dm), RESET_TYPE_COLD); + } + + /* + * "the dmactive bit is the only bit which can be written to something + * other than its reset value)." + * if 0, reset the debug module itself. Do not exit from reset until + * DMCONTROL.dmactive is not set. + */ + dm->regs[A_DMCONTROL] &= ~R_DMCONTROL_DMACTIVE_MASK; + + /* do not update DMCONTROL while it is maintained in reset */ + return CMD_ERR_NONE; + } + + if (unlikely(resettable_is_in_reset(OBJECT(dm)))) { + trace_riscv_dm_reset(dm->soc, "debugger released DM reset"); + resettable_release_reset(OBJECT(dm), RESET_TYPE_COLD); + } + + bool hasel = (bool)FIELD_EX32(value, DMCONTROL, HASEL); + + uint32_t hartsel = FIELD_EX32(value, DMCONTROL, HARTSELLO) | + (FIELD_EX32(value, DMCONTROL, HARTSELHI) + << R_DMCONTROL_HARTSELLO_LENGTH); + + /* mask any bits that cannot be used for hart selection */ + hartsel &= dm->hart_count - 1u; /* index start @ 0 */ + + dm->hart = NULL; + + /* hart array not supported, ignore any request that uses it */ + if (!hasel) { + uint64_t hbit = 1u << hartsel; + if (!(hartsel < dm->hart_count)) { + /* max supported harts: 64 */ + dm->nonexistent_bm |= hbit; + /* ensure hart can only be in one state */ + dm->unavailable_bm &= ~hbit; + } else { + RISCVDMHartState *hart = &dm->harts[hartsel]; + dm->hart = hart; + g_assert(hart->cpu); + CPUState *cs = CPU(hart->cpu); + + if (value & R_DMCONTROL_HARTRESET_MASK) { + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "assert"); + if (!cs->disabled && hart->unlock_reset) { + /* + * if hart is started in active reset, prevent from + * resetting it since it should not be released from + * reset (see below). Allowing reset w/ blocking reset + * release would leave the Resettable API count with + * a forever-locked reset count. + */ + resettable_assert_reset(OBJECT(cs), RESET_TYPE_COLD); + dm->unavailable_bm |= hbit; + dm->reset_bm |= hbit; + } + } else if (dm->reset_bm & hbit) { + if (cs->disabled && hart->unlock_reset) { + /* + * if hart is started in active reset, prevent from + * releasing it from reset, otherwise it may start + * executing guest code not yet loaded, leading to an + * exception. It is up to the guest code to manage the + * initial out-of-reset sequence. Not sure how real HW + * manages this corner case. + */ + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "release"); + resettable_release_reset(OBJECT(cs), RESET_TYPE_COLD); + dm->reset_bm &= ~hbit; + } + } + + if (dm->unavailable_bm & hbit) { + if (!cs->disabled) { + /* hart exited from reset, became available */ + dm->unavailable_bm &= ~hbit; + hart->have_reset = true; + hart->halted = false; + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "exited"); + } + } + } + + if (value & R_DMCONTROL_ACKHAVERESET_MASK) { + unsigned hix = 0; + while (hix < dm->hart_count) { + dm->harts->have_reset = false; + hix++; + } + } + } + + + RISCVDMCmdErr ret = CMD_ERR_NONE; + + if (unlikely(value & R_DMCONTROL_NDMRESET_MASK)) { + /* full system reset (but the Debug Module) */ + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); + } else if (dm->hart) { + if (!hasel && (hartsel < dm->hart_count)) { + uint64_t hartbit = 1u << hartsel; + if (value & R_DMCONTROL_HALTREQ_MASK) { + if (dm->unavailable_bm & hartbit) { + trace_riscv_dm_unavailable_hart_control(dm->soc, hartsel, + "halt"); + ret = CMD_ERR_HALT_RESUME; + /* flag the haltreq as pending while unavailable */ + dm->haltreq_bm |= hartbit; + } else { + riscv_dm_halt_hart(dm, hartsel); + } + } else { + if (dm->hart->halted) { + /* + * resumereq is explicitly ignored if haltreq is set, by the + * specs + */ + if (value & R_DMCONTROL_RESUMEREQ_MASK) { + if (dm->unavailable_bm & hartbit) { + trace_riscv_dm_unavailable_hart_control(dm->soc, + hartsel, + "resume"); + ret = CMD_ERR_HALT_RESUME; + } else { + /* + * it also clears the resume ack bit for those harts + */ + dm->hart->resumed = false; + riscv_dm_resume_hart(dm, hartsel); + } + } + } + } + } + } + + dm->regs[A_DMCONTROL] &= + ~(R_DMCONTROL_HARTSELLO_MASK | R_DMCONTROL_HARTSELHI_MASK | + R_DMCONTROL_NDMRESET_MASK | R_DMCONTROL_DMACTIVE_MASK | + R_DMCONTROL_HARTRESET_MASK); + value &= R_DMCONTROL_NDMRESET_MASK | R_DMCONTROL_DMACTIVE_MASK | + R_DMCONTROL_HARTRESET_MASK; + /* HARTSELHI never used, since HARTSELLO already encodes up to 1K harts */ + dm->regs[A_DMCONTROL] = FIELD_DP32(value, DMCONTROL, HARTSELLO, hartsel); + + return ret; +} + +static CmdErr riscv_dm_exec_command(RISCVDMState *dm, uint32_t value) +{ + if (!dm->hart) { + /* no hart has been selected for debugging */ + xtrace_riscv_dm_error(dm->soc, "no hart"); + return CMD_ERR_OTHER; + } + + if (!dm->cfg.data_phyaddr) { + /* + * CSR-shadowed implementation is not supported + * abstract command slots are required + */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + if (dm->cmd_busy) { + xtrace_riscv_dm_error(dm->soc, "already busy"); + return CMD_ERR_BUSY; + } + + if (!dm->hart->halted) { + xtrace_riscv_dm_error(dm->soc, "cannot exec command if not halted"); + return CMD_ERR_HALT_RESUME; + } + + /* "This bit is set as soon as command is written" */ + riscv_dm_set_busy(dm, true); + + int cmdtype = (int)FIELD_EX32(value, COMMAND, CMDTYPE); + CmdErr ret; + switch (cmdtype) { + case CMD_ACCESS_REGISTER: + ret = riscv_dm_access_register(dm, value); + break; + case CMD_QUICK_ACCESS: + ret = riscv_dm_quick_access(dm, value); + break; + case CMD_ACCESS_MEMORY: + ret = riscv_dm_access_memory(dm, value); + break; + default: + ret = CMD_ERR_NOT_SUPPORTED; + break; + } + + if (ret != CMD_ERR_NONE) { + xtrace_riscv_dm_error(dm->soc, "cmd exec failed"); + /* "and [this bit] is not cleared until that command has completed." */ + riscv_dm_set_busy(dm, false); + } + + return ret; +} + +static CmdErr riscv_dm_command_write(RISCVDMState *dm, uint32_t value) +{ + if (dm->cmd_err != CMD_ERR_NONE) { + /* if cmderr is non-zero, writes to this register are ignored. */ + return CMD_ERR_NONE; + } + + /* save command as it may be repeated w/ abstractauto command */ + dm->regs[A_COMMAND] = value; + + /* busy status is asserted in riscv_dm_command_write */ + return riscv_dm_exec_command(dm, value); +} + +static CmdErr riscv_dm_abstractauto_read(RISCVDMState *dm, uint32_t *value) +{ + *value = dm->regs[A_ABSTRACTAUTO]; + + /* + * this function is only for debug, to be removed since simple read out + * does not need a dedicated handler + */ + xtrace_riscv_dm_info(dm->soc, "abstract auto read back", *value); + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_abstractauto_write(RISCVDMState *dm, uint32_t value) +{ + if (!dm->cfg.abstractauto) { + xtrace_riscv_dm_info(dm->soc, "abstractauto support is disabled", + value); + /* + * Peer should check the content of ABSTRACTAUTO (which is initialized + * and stuck to 0) to discover the feature is not supported. + * + * It seems OpenOCD does not perform this check and resume anyway. + */ + return CMD_ERR_NONE; + } + + if (dm->cmd_busy) { + xtrace_riscv_dm_error(dm->soc, "already busy"); + return CMD_ERR_BUSY; + } + + xtrace_riscv_dm_info(dm->soc, "abstractauto attempt", value); + + uint32_t mask = (((1u << dm->cfg.data_count) - 1u) + << R_ABSTRACTAUTO_AUTOEXECDATA_SHIFT) | + (((1u << dm->cfg.progbuf_count) - 1u) + << R_ABSTRACTAUTO_AUTOEXECPROGBUF_SHIFT); + + dm->regs[A_ABSTRACTAUTO] = value & mask; + + if (dm->regs[A_ABSTRACTAUTO] != value) { + xtrace_riscv_dm_info(dm->soc, "abstractauto selected", value & mask); + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) +{ + unsigned halted = 0u; + unsigned running = 0u; + unsigned unavail = 0u; + unsigned nonexistent = 0u; + unsigned resumeack = 0u; + unsigned havereset = 0u; + + unsigned hcount = dm->hart_count; + /* + * "3.4 Hart States + * Every hart that can be selected is in exactly one of the following four + * DM states: non-existent, unavailable, running, or halted." + */ + unsigned hix = 0u; + uint64_t mask = 1u; + for (; hix < hcount; hix++, mask <<= 1u) { + RISCVDMHartState *hart = &dm->harts[hix]; + CPUState *cs; + g_assert(hart->cpu); + cs = CPU(hart->cpu); + if (dm->nonexistent_bm & mask) { + nonexistent += 1; + trace_riscv_dm_status(dm->soc, cs->cpu_index, "nonexistent"); + continue; + } + /* + * The hart may have been started since last poll. There is no way + * for the hart to inform the DM in this case, so rely on polling + * for now. + */ + if (cs->disabled) { + hart->resumed = false; + hart->halted = false; + dm->unavailable_bm |= mask; + dm->nonexistent_bm &= ~mask; + unavail += 1; + trace_riscv_dm_status(dm->soc, cs->cpu_index, "unavailable"); + continue; + } + if (dm->unavailable_bm & mask) { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "became available"); + /* clear the unavailability flag and resume w/ "regular" states */ + dm->unavailable_bm &= ~mask; + + /* + * If a pending haltreq exists for this hart, enter debug mode. + * + * @todo: This is a workaround for the fact that the current + * implementation does not conform to the debug specification. See + * e.g. section C.1.4: "When a hart comes out of reset and haltreq + * is set, the hart will immediately enter Debug Mode". To support + * this the CPU would need to query the DM when it begins executing + * instructions, and hence would need a reference to the DM, which + * is not ideal. + * + * For now, we can support most real use cases by exploiting the + * fact that reasonable SW will usually send a haltreq and then + * repeatedly poll `dmstatus` to see the hart(s) halt. Hence, we + * latch haltreqs for unresponsive cores and halt any hart that is + * polled as newly available/responsive on a `dmstatus` read. + */ + if (dm->haltreq_bm & mask) { + riscv_dm_halt_hart(dm, hix); + } + } + if (hart->resumed) { + resumeack += 1; + trace_riscv_dm_status(dm->soc, cs->cpu_index, "resumed"); + } + if (hart->have_reset) { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "have reset"); + havereset += 1; + } + if (hart->halted) { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "halted"); + halted += 1; + } else { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "running"); + running += 1; + } + mask <<= 1u; + hix++; + +#ifdef TRACE_CPU_STATES + RISCVDMStateCache current = { + .cpu = { + .ix = cs->cpu_index, + .halted = cs->halted, + .stopped = cs->stopped, + .running = cs->running, + }, + .dm = { + .halted = halted, + .running = running, + .unavail = unavail, + .nonexistent = nonexistent, + .resumeack = resumeack, + .havereset = havereset, + }, + }; + /* NOLINTNEXTLINE */ + if (memcmp(¤t, &hart->dbgcache, sizeof(RISCVDMStateCache))) { + qemu_log("%s: %s [%u/%u] [H:%u S:%u R:%u] " + "DM [h:%u r:%u u:%u x:%u a:%u z:%u]\n", + __func__, dm->soc, hart->hartid, hcount, cs->halted, + cs->stopped, cs->running, halted, running, unavail, + nonexistent, resumeack, havereset); + hart->dbgcache = current; + } +#endif /* #ifdef TRACE_CPU_STATES */ + } + + uint32_t val = dm->regs[A_DMSTATUS]; + val = FIELD_DP32(val, DMSTATUS, ANYHALTED, (halted ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ANYRUNNING, (running ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ANYUNAVAIL, (unavail ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ANYNONEXISTENT, (nonexistent ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ANYRESUMEACK, (resumeack ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ANYHAVERESET, (havereset ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ALLHALTED, (halted == hcount ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ALLRUNNING, (running == hcount ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ALLUNAVAIL, (unavail == hcount ? 1 : 0)); + val = FIELD_DP32(val, DMSTATUS, ALLNONEXISTENT, + (nonexistent == hcount ? 1 : 0)); + val = + FIELD_DP32(val, DMSTATUS, ALLRESUMEACK, (resumeack == hcount ? 1 : 0)); + val = + FIELD_DP32(val, DMSTATUS, ALLHAVERESET, (havereset == hcount ? 1 : 0)); + + if (val != dm->regs[A_DMSTATUS]) { + /* @todo update for multiple harts */ + RISCVCPU *cpu = dm->harts[0].cpu; + g_assert(cpu); + CPUState *cs = CPU(cpu); + trace_riscv_dm_dmstatus_read(dm->soc, val, unavail, halted, cs->halted, + running, cs->running, resumeack, + cs->stopped, (uint32_t)cpu->env.pc); + } + + *value = dm->regs[A_DMSTATUS] = val; + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_sbcs_write(RISCVDMState *dm, uint32_t value) +{ + /* mask out the preset, R/O bits */ + value &= SBCS_WRITE_MASK; + + /* clear error bits (if flagged as W1C) */ + value &= ~(value & (R_SBCS_SBERROR_MASK | R_SBCS_SBBUSYERROR_MASK)); + + dm->regs[A_SBCS] &= ~SBCS_WRITE_MASK; + dm->regs[A_SBCS] |= value; + + if (trace_event_get_state(TRACE_RISCV_DM_SBCS_WRITE)) { + bool err = (bool)(value & R_SBCS_SBERROR_MASK); + bool rdondata = (bool)(value & R_SBCS_SBREADONDATA_MASK); + bool autoinc = (bool)(value & R_SBCS_SBAUTOINCREMENT_MASK); + bool rdonaddr = (bool)(value & R_SBCS_SBREADONADDR_MASK); + bool busyerr = (bool)(value & R_SBCS_SBBUSYERROR_MASK); + unsigned access = 1u << FIELD_EX32(value, SBCS, SBACCESS); + trace_riscv_dm_sbcs_write(dm->soc, err, busyerr, access, rdonaddr, + rdondata, autoinc); + } + + return CMD_ERR_NONE; +} + +static uint32_t riscv_dm_sysbus_get_byte_count(RISCVDMState *dm) +{ + uint32_t size = 1u << FIELD_EX32(dm->regs[A_SBCS], SBCS, SBACCESS); + /* LSBs of A_SBCS define supported sizes as a bitmap */ + if (!(dm->regs[A_SBCS] & size)) { + dm->regs[A_SBCS] = + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBERROR, SYSBUS_ASIZE); + xtrace_riscv_dm_error(dm->soc, "asize"); + return 0; + } + return size; +} + +static void riscv_dm_sysbus_set_busy(RISCVDMState *dm, bool busy) +{ + dm->regs[A_SBCS] = + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSY, (uint32_t)busy); +} + +static void riscv_dm_sysbus_increment_address(RISCVDMState *dm) +{ + uint32_t size; + if (!(size = riscv_dm_sysbus_get_byte_count(dm))) { + /* invalid size case has already been handled by the caller */ + return; + } + + uint32_t old = dm->regs[A_SBADDRESS0]; + dm->regs[A_SBADDRESS0] += size; + if (old > dm->regs[A_SBADDRESS0] && + dm->hart->cpu->env.misa_mxl > MXL_RV32) { + dm->regs[A_SBADDRESS1] += 1u; + } +} + +static CmdErr riscv_dm_sysbus_read(RISCVDMState *dm) +{ + uint32_t size; + if (!(size = riscv_dm_sysbus_get_byte_count(dm))) { + /* + * note: the spec is fuzzy about how sysbus errors should be managed: + * should cmderr always be flagged, or is sberror enough? TBC.. + */ + return CMD_ERR_NONE; + } + + riscv_dm_sysbus_set_busy(dm, true); + + CmdErr ret = CMD_ERR_NONE; + MemTxResult res; + hwaddr address = (hwaddr)dm->regs[A_SBADDRESS0]; + if (dm->hart->cpu->env.misa_mxl > MXL_RV32) { + address |= ((hwaddr)dm->regs[A_SBADDRESS1]) << 32u; + } + + if (address & (size - 1u)) { + dm->regs[A_SBCS] = + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBERROR, SYSBUS_BADALIGN); + xtrace_riscv_dm_error(dm->soc, "align"); + ret = CMD_ERR_BUS; + goto end; + } + + /* + * if the width of the read access is less than the width of sbdata, the + * contents of the remaining high bits may take on any value + */ + uint64_t val64 = 0; /* however 0 is easier for debugging */ + res = address_space_rw(dm->as, address, dm->mta_sba, &val64, size, false); + trace_riscv_dm_sysbus_data_read(dm->soc, address, size, val64, res); + if (res != MEMTX_OK) { + dm->regs[A_SBCS] = + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBERROR, SYSBUS_BADADDR); + xtrace_riscv_dm_error(dm->soc, "memtx"); + ret = CMD_ERR_BUS; + goto end; + } + dm->sbdata = val64; +end: + riscv_dm_sysbus_set_busy(dm, false); + + return ret; +} + +static CmdErr riscv_dm_sbaddress0_write(RISCVDMState *dm, uint32_t value) +{ + if (!dm->cfg.sysbus_access) { + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBERROR)) { + xtrace_riscv_dm_error(dm->soc, "sberror"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSY)) { + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSYERROR, 1u); + xtrace_riscv_dm_error(dm->soc, "sbbusy"); + return CMD_ERR_NONE; + } + + dm->regs[A_SBADDRESS0] = value; + trace_riscv_dm_sbaddr_write(dm->soc, 0, value); + + /* + * "When 1, every write to sbaddress0 automatically triggers a system bus + * read at the new address." + */ + if (!FIELD_EX32(dm->regs[A_SBCS], SBCS, SBREADONADDR)) { + return CMD_ERR_NONE; + } + + CmdErr ret; + + ret = riscv_dm_sysbus_read(dm); + /* + * "If the read succeeded and sbautoincrement is set, + * increment sbaddress." + */ + if ((ret == CMD_ERR_NONE) && + FIELD_EX32(dm->regs[A_SBCS], SBCS, SBAUTOINCREMENT)) { + riscv_dm_sysbus_increment_address(dm); + } + + return ret; +} + +static CmdErr riscv_dm_sbaddress1_write(RISCVDMState *dm, uint32_t value) +{ + if ((!dm->cfg.sysbus_access) || (dm->hart->cpu->env.misa_mxl < MXL_RV64)) { + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBERROR)) { + xtrace_riscv_dm_error(dm->soc, "sberror"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSY)) { + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSYERROR, 1u); + xtrace_riscv_dm_error(dm->soc, "sbbusy"); + return CMD_ERR_NONE; + } + + dm->regs[A_SBADDRESS1] = value; + trace_riscv_dm_sbaddr_write(dm->soc, 1, value); + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_sbdata0_read(RISCVDMState *dm, uint32_t *value) +{ + if (!dm->cfg.sysbus_access) { + xtrace_riscv_dm_error(dm->soc, "no support"); + *value = 0; + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBERROR) || + FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSYERROR)) { + xtrace_riscv_dm_error(dm->soc, "sberror"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSY)) { + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSYERROR, 1u); + xtrace_riscv_dm_error(dm->soc, "sbbusy"); + return CMD_ERR_NONE; + } + + /* + * "Reads from this register start the following: + * 1. Return the data. + * i.e. the actual content has been read from the previous call, hence the + * sbdata cache + */ + *value = dm->regs[A_SBDATA0] = (uint32_t)dm->sbdata; + trace_riscv_dm_sbdata_read(dm->soc, 0, *value); + + CmdErr ret = CMD_ERR_NONE; + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBREADONDATA)) { + ret = riscv_dm_sysbus_read(dm); + } + + /* + * "When 1, sbaddress is incremented by the access size (in bytes) selected + * in sbaccess after every system bus access." + */ + if ((ret == CMD_ERR_NONE) && + FIELD_EX32(dm->regs[A_SBCS], SBCS, SBAUTOINCREMENT)) { + riscv_dm_sysbus_increment_address(dm); + } + + return ret; +} + +static CmdErr riscv_dm_sbdata0_write(RISCVDMState *dm, uint32_t value) +{ + if (!dm->cfg.sysbus_access) { + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBERROR) || + FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSYERROR)) { + xtrace_riscv_dm_error(dm->soc, "sberror"); + return CMD_ERR_NONE; + } + + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSY)) { + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSYERROR, 1u); + xtrace_riscv_dm_error(dm->soc, "sbbusy"); + return CMD_ERR_NONE; + } + + uint32_t size; + if (!(size = riscv_dm_sysbus_get_byte_count(dm))) { + return CMD_ERR_BUS; + } + + CmdErr ret = CMD_ERR_NONE; + + riscv_dm_sysbus_set_busy(dm, true); + MemTxResult res; + hwaddr address = (hwaddr)dm->regs[A_SBADDRESS0]; + if (dm->hart->cpu->env.misa_mxl > MXL_RV32) { + address |= ((hwaddr)dm->regs[A_SBADDRESS1]) << 32u; + } + if (address & (size - 1u)) { + dm->regs[A_SBCS] = + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBERROR, SYSBUS_BADALIGN); + xtrace_riscv_dm_error(dm->soc, "asize"); + goto end; + } + dm->regs[A_SBDATA0] = value; + /* + * If the width of the read access is less than the width of sbdata, the + * contents of the remaining high bits may take on any value + */ + uint64_t val64; + val64 = (uint64_t)dm->regs[A_SBDATA0]; + if (size > sizeof(uint32_t)) { + val64 = ((uint64_t)dm->regs[A_SBDATA1]) << 32u; + } + res = address_space_rw(dm->as, address, dm->mta_sba, &val64, size, true); + trace_riscv_dm_sysbus_data_write(dm->soc, address, size, val64, res); + if (res != MEMTX_OK) { + dm->regs[A_SBCS] = + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBERROR, SYSBUS_BADADDR); + xtrace_riscv_dm_error(dm->soc, "memtx"); + ret = CMD_ERR_BUS; + } +end: + riscv_dm_sysbus_set_busy(dm, false); + + if ((ret == CMD_ERR_NONE) && + FIELD_EX32(dm->regs[A_SBCS], SBCS, SBAUTOINCREMENT)) { + riscv_dm_sysbus_increment_address(dm); + } + + return ret; +} + +static CmdErr riscv_dm_sbdata1_read(RISCVDMState *dm, uint32_t *value) +{ + if ((!dm->cfg.sysbus_access) || (dm->hart->cpu->env.misa_mxl < MXL_RV64)) { + *value = 0; + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NONE; + } + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSY)) { + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSYERROR, 1u); + xtrace_riscv_dm_error(dm->soc, "sbbusy"); + return CMD_ERR_NONE; + } + + *value = dm->regs[A_SBDATA1] = (uint32_t)(dm->sbdata >> 32u); + trace_riscv_dm_sbdata_read(dm->soc, 1, *value); + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_sbdata1_write(RISCVDMState *dm, uint32_t value) +{ + if ((!dm->cfg.sysbus_access) || (dm->hart->cpu->env.misa_mxl < MXL_RV64)) { + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NONE; + } + if (FIELD_EX32(dm->regs[A_SBCS], SBCS, SBBUSY)) { + FIELD_DP32(dm->regs[A_SBCS], SBCS, SBBUSYERROR, 1u); + xtrace_riscv_dm_error(dm->soc, "sbbusy"); + return CMD_ERR_NONE; + } + + dm->regs[A_SBDATA1] = value; + trace_riscv_dm_sbdata_write(dm->soc, 1, value); + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_hartinfo_read(RISCVDMState *dm, uint32_t *value) +{ + uint32_t val; + + /* note that CSR-shadowing mode is not supported (data access == 0) */ + val = FIELD_DP32(0, HARTINFO, DATAADDR, dm->cfg.data_phyaddr); + val = FIELD_DP32(val, HARTINFO, DATASIZE, dm->cfg.data_count); + val = FIELD_DP32(val, HARTINFO, DATAACCESS, dm->cfg.data_phyaddr != 0u); + val = FIELD_DP32(val, HARTINFO, NSCRATCH, dm->cfg.nscratch); + + *value = dm->regs[A_HARTINFO] = val; + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_abstractcs_read(RISCVDMState *dm, uint32_t *value) +{ + uint32_t val; + + val = FIELD_DP32(0, ABSTRACTCS, DATACOUNT, dm->cfg.data_count); + val = FIELD_DP32(val, ABSTRACTCS, PROGBUFSIZE, dm->cfg.progbuf_count); + val = FIELD_DP32(val, ABSTRACTCS, BUSY, (uint32_t)dm->cmd_busy); + val = FIELD_DP32(val, ABSTRACTCS, CMDERR, (uint32_t)dm->cmd_err); + + *value = dm->regs[A_ABSTRACTCS] = val; + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_abstractcs_write(RISCVDMState *dm, uint32_t value) +{ + if (dm->cmd_busy) { + xtrace_riscv_dm_error(dm->soc, "already busy"); + return CMD_ERR_BUSY; + } + + /* + * The bits in this field remain set until they are cleared by writing 1 to + * them -> it is not clear whether any bit clears all cmderr bits or if + * the error code may be changed when only some of them are cleared out... + */ + uint32_t cmderr_mask = (value & R_ABSTRACTCS_CMDERR_MASK); + value &= ~cmderr_mask; + + dm->regs[A_ABSTRACTCS] = value; + dm->cmd_err &= ~(cmderr_mask >> R_ABSTRACTCS_CMDERR_SHIFT); + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_haltsum0_read(RISCVDMState *dm, uint32_t *value) +{ + unsigned hix = 0; + unsigned halted_bm = 0; + + while (hix < dm->hart_count) { + if (dm->harts[hix].halted) { + halted_bm = 1u << hix; + } + } + + *value = dm->regs[A_HALTSUM0] = halted_bm; + return CMD_ERR_NONE; +} + +/* this function contains code retrieved from PUL dm_mem.sv file */ +static CmdErr riscv_dm_access_register(RISCVDMState *dm, uint32_t value) +{ + /* + * for now, only LE-RISC-V and LE-hosts are supported, + * RV128 are not supp. + */ + RISCVCPU *cpu = dm->hart->cpu; + CPURISCVState *env = &cpu->env; + + if (!dm->cfg.progbuf_phyaddr || + dm->cfg.abstractcmd_count < RISCVDM_ABSTRACTDATA_SLOTS) { + /* abstract command slots and progbuf address are required */ + xtrace_riscv_dm_error(dm->soc, "no support"); + return CMD_ERR_NOT_SUPPORTED; + } + + unsigned regno = (unsigned)FIELD_EX32(value, COMMAND, REG_REGNO); + bool write = (bool)FIELD_EX32(value, COMMAND, REG_WRITE); + bool transfer = (bool)FIELD_EX32(value, COMMAND, REG_TRANSFER); + bool postexec = (bool)FIELD_EX32(value, COMMAND, REG_POSTEXEC); + bool aarpostinc = (bool)FIELD_EX32(value, COMMAND, REG_AARPOSTINCREMENT); + unsigned aarsize = (unsigned)FIELD_EX32(value, COMMAND, REG_AARSIZE); + unsigned maxarr = env->misa_mxl + 1u; + + if (transfer) { + if (aarsize > maxarr) { + /* + * If aarsize specifies a size larger than the register’s actual + * size, then the access must fail. + */ + trace_riscv_dm_aarsize_error(dm->soc, aarsize); + return CMD_ERR_NOT_SUPPORTED; + } + } + + uint32_t abscmd[RISCVDM_ABSTRACTDATA_SLOTS]; + memset(abscmd, 0u, sizeof(abscmd)); /* fill up the buf with illegal insns */ + + /* + * if ac_ar.transfer is not set then we can take a shortcut to the program + * buffer, load debug module base address into a0, this is shared among all + * commands + */ + abscmd[1u] = (dm->cfg.nscratch > 1u) ? riscv_dm_insn_auipc(GPR_A0, 0) : + riscv_dm_insn_nop(); + /* clr lowest 12b -> DM base offset */ + abscmd[2u] = (dm->cfg.nscratch > 1u) ? + riscv_dm_insn_srli(GPR_A0, GPR_A0, 12u) : + riscv_dm_insn_nop(); + abscmd[3u] = (dm->cfg.nscratch > 1u) ? + riscv_dm_insn_slli(GPR_A0, GPR_A0, 12u) : + riscv_dm_insn_nop(); + abscmd[4u] = riscv_dm_insn_nop(); + abscmd[5u] = riscv_dm_insn_nop(); + abscmd[6u] = riscv_dm_insn_nop(); + abscmd[7u] = riscv_dm_insn_nop(); + abscmd[8u] = (dm->cfg.nscratch > 1u) ? + riscv_dm_insn_csrr(CSR_DSCRATCH1, GPR_A0) : + riscv_dm_insn_nop(); + abscmd[9u] = riscv_dm_insn_ebreak(); + + bool unsupported = false; + /* + * Depending on whether we are at the zero page or not we either use `x0` or + * `x10/a0` + */ + uint32_t regaddr = dm->cfg.dm_phyaddr == 0u ? GPR_ZERO : GPR_A0; + + if ((aarsize <= maxarr) && transfer) { + if (write) { + /* store a0 in dscratch1 */ + abscmd[0u] = (dm->cfg.nscratch > 1u) ? + riscv_dm_insn_csrw(CSR_DSCRATCH1, GPR_A0) : + riscv_dm_insn_nop(); + /* this range is reserved */ + if (regno >= 0xc000) { + abscmd[0u] = riscv_dm_insn_ebreak(); /* leave asap */ + unsupported = true; + /* + * A0 access needs to be handled separately, as we use A0 to + * load the DM address offset need to access DSCRATCH1 in this + * case + */ + } else if ((dm->cfg.nscratch > 1u) && (regno == 0x1000u + GPR_A0)) { + xtrace_reg(dm->soc, "write GPR", regno, 0x1000u); + /* store s0 in dscratch */ + abscmd[4u] = riscv_dm_insn_csrw(CSR_DSCRATCH0, GPR_S0); + /* load from data register */ + abscmd[5u] = riscv_dm_insn_load(aarsize, GPR_S0, regaddr, + dm->cfg.data_phyaddr); + /* and store it in the corresponding CSR */ + abscmd[6u] = riscv_dm_insn_csrw(CSR_DSCRATCH1, GPR_S0); + /* restore s0 again from dscratch */ + abscmd[7u] = riscv_dm_insn_csrr(CSR_DSCRATCH0, GPR_S0); + /* GPR/FPR access */ + } else if (regno & 0x1000u) { + /* + * determine whether we want to access the floating point + * register or not + */ + if (regno & 0x20u) { + xtrace_reg(dm->soc, "write FPR", regno, 0x1020u); + abscmd[4u] = + riscv_dm_insn_float_load(aarsize, riscv_dm_rm(regno), + regaddr, dm->cfg.data_phyaddr); + } else { + xtrace_reg(dm->soc, "write GPR", regno, 0x1000u); + abscmd[4u] = + riscv_dm_insn_load(aarsize, riscv_dm_rm(regno), regaddr, + dm->cfg.data_phyaddr); + } + /* CSR access */ + } else { + /* data register to CSR */ + xtrace_reg(dm->soc, "write CSR", regno, 0u); + /* store s0 in dscratch */ + abscmd[4u] = riscv_dm_insn_csrw(CSR_DSCRATCH0, GPR_S0); + /* load from data register */ + abscmd[5u] = riscv_dm_insn_load(aarsize, GPR_S0, regaddr, + dm->cfg.data_phyaddr); + /* and store it in the corresponding CSR */ + abscmd[6u] = riscv_dm_insn_csrw(riscv_dm_csr(regno), GPR_S0); + /* restore s0 again from dscratch */ + abscmd[7u] = riscv_dm_insn_csrr(CSR_DSCRATCH0, GPR_S0); + } + } else { /* read */ + + /* store a0 in dscratch1 */ + abscmd[0u] = (dm->cfg.nscratch > 1u) ? + riscv_dm_insn_csrw(CSR_DSCRATCH1, regaddr) : + riscv_dm_insn_nop(); + /* this range is reserved */ + if (regno >= 0xc000) { + abscmd[0u] = riscv_dm_insn_ebreak(); /* leave asap */ + unsupported = true; + /* A0 access needs to be handled separately, as we use A0 to + * load the DM address offset need to access DSCRATCH1 in this + * case + */ + } else if ((dm->cfg.nscratch > 1u) && (regno == 0x1000u + GPR_A0)) { + xtrace_reg(dm->soc, "read GPR", regno, 0x1000u); + /* store s0 in dscratch */ + abscmd[4u] = riscv_dm_insn_csrw(CSR_DSCRATCH0, GPR_S0); + /* read value from CSR into s0 */ + abscmd[5u] = riscv_dm_insn_csrr(CSR_DSCRATCH1, GPR_S0); + /* and store s0 into data section */ + abscmd[6u] = riscv_dm_insn_store(aarsize, GPR_S0, regaddr, + dm->cfg.data_phyaddr); + /* restore s0 again from dscratch */ + abscmd[7u] = riscv_dm_insn_csrr(CSR_DSCRATCH0, GPR_S0); + /* GPR/FPR access */ + } else if (regno & 0x1000u) { + /* + * determine whether we want to access the floating point + * register or not + */ + if (regno & 0x20u) { + xtrace_reg(dm->soc, "read FPR", regno, 0x1020u); + abscmd[4u] = + riscv_dm_insn_float_store(aarsize, riscv_dm_rm(regno), + regaddr, + dm->cfg.data_phyaddr); + } else { + xtrace_reg(dm->soc, "read GPR", regno, 0x1000u); + abscmd[4u] = + riscv_dm_insn_store(aarsize, riscv_dm_rm(regno), + regaddr, dm->cfg.data_phyaddr); + } + /* CSR access */ + } else { + /* CSR register to data */ + xtrace_reg(dm->soc, "read CSR", regno, 0u); + /* store s0 in dscratch */ + abscmd[4u] = riscv_dm_insn_csrw(CSR_DSCRATCH0, GPR_S0); + /* read value from CSR into s0 */ + abscmd[5u] = riscv_dm_insn_csrr(riscv_dm_csr(regno), GPR_S0); + /* and store s0 into data section */ + abscmd[6u] = riscv_dm_insn_store(aarsize, GPR_S0, regaddr, + dm->cfg.data_phyaddr); + /* restore s0 again from dscratch */ + abscmd[7u] = riscv_dm_insn_csrr(CSR_DSCRATCH0, GPR_S0); + } + } + } else if ((aarsize > maxarr) || aarpostinc) { + /* + * this should happend when e.g. aarsize > maxaar + * Openocd will try to do an access with aarsize=64 bits + * first before falling back to 32 bits. + */ + abscmd[0u] = riscv_dm_insn_ebreak(); /* leave asap */ + unsupported = true; + } + if (postexec && !unsupported) { + /* issue a nop, we will automatically run into the program buffer */ + abscmd[9u] = riscv_dm_insn_nop(); + } + + if (unsupported) { + xtrace_riscv_dm_error(dm->soc, "unsupported abstract command"); + } + + /* copy the abstract command opcode into executable memory */ + hwaddr abscmd_addr = dm->cfg.progbuf_phyaddr - sizeof(abscmd); + + if (MEMTX_OK != address_space_rw(dm->as, abscmd_addr, dm->mta_dm, + &abscmd[0], sizeof(abscmd), true)) { + xtrace_riscv_dm_error(dm->soc, "write to abtract commands to mem"); + return CMD_ERR_BUS; + } + + for (unsigned ix = 0; ix < RISCVDM_ABSTRACTDATA_SLOTS; ix++) { + trace_riscv_dm_abstract_cmd(dm->soc, + abscmd_addr + ix * sizeof(uint32_t), + abscmd[ix]); + } + + /* generate the "whereto" instruction */ + uint32_t offset; + if (!transfer && postexec) { + offset = dm->cfg.progbuf_phyaddr - dm->cfg.whereto_phyaddr; + } else { + offset = abscmd_addr - dm->cfg.whereto_phyaddr; + } + uint32_t whereto = riscv_dm_insn_jal(GPR_ZERO, offset); + + CmdErr res; + if ((res = riscv_dm_write_whereto(dm, whereto))) { + return res; + }; + + /* now kick off execution */ + unsigned hartsel = (unsigned)(dm->hart - &dm->harts[0]); + CPUState *cs = CPU(dm->hart->cpu); + trace_riscv_dm_change_hart(dm->soc, "GO", hartsel, cs->halted, cs->running, + cs->stopped, dm->hart->resumed); + dm->to_go_bm |= 1u << hartsel; + res = riscv_dm_update_flags(dm, hartsel, true, R_FLAGS_FLAG_GO_MASK); + if (res != CMD_ERR_NONE) { + xtrace_riscv_dm_error(dm->soc, "cannot go"); + dm->to_go_bm &= ~(1u << hartsel); + return res; + } + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_access_memory(RISCVDMState *dm, uint32_t value) +{ + /* + * Arg Width | arg0/ret val | arg1 | arg2 | + * ----------+--------------+----------+-----------+ + * 32 | data0 | data1 | data2 | + * 64 | data0..1 | data2..3 | data4..5 | + * 128 | data0..3 | data4..7 | data8..11 | + * The Argument Width of the Access Memory abstract command is determined by + * DXLEN, and not by aamsize. + * - arg0 is the data + * - arg1 is the address + */ + RISCVCPU *cpu = dm->hart->cpu; + CPURISCVState *env = &cpu->env; + + bool write = (bool)FIELD_EX32(value, COMMAND, MEM_WRITE); + bool aampostinc = (bool)FIELD_EX32(value, COMMAND, MEM_AAMPOSTINCREMENT); + unsigned aamsize = (unsigned)FIELD_EX32(value, COMMAND, REG_AAMSIZE); + bool virt = (bool)FIELD_EX32(value, COMMAND, MEM_AAMVIRTUAL); + hwaddr size = 1u << aamsize; + + if (aamsize > env->misa_mxl + 1u) { + xtrace_riscv_dm_error(dm->soc, "ammsize"); + return CMD_ERR_NOT_SUPPORTED; + } + + unsigned argwidth = env->misa_mxl; /* in 32-bit word count */ + unsigned datawcount = aamsize > 2u ? 2u : 1u; + /* zero-init is only useful to help w/ debug on RV32 */ + hwaddr val = 0u; + hwaddr addr = 0u; + CmdErr res; + if ((res = riscv_dm_read_absdata(dm, argwidth, argwidth, &addr))) { + xtrace_riscv_dm_error(dm->soc, "read mem address (arg1)"); + return res; + } + if (virt) { + hwaddr phyaddr; + phyaddr = riscv_cpu_get_phys_page_debug(CPU(cpu), addr); + if (phyaddr == -1) { + xtrace_riscv_dm_error(dm->soc, "virtual mem"); + return CMD_ERR_BUS; + } + addr = phyaddr; + } + if (write) { + /* read value from arg0 */ + if ((res = riscv_dm_read_absdata(dm, 0, datawcount, &val))) { + xtrace_riscv_dm_error(dm->soc, "read mem data (arg0)"); + return res; + } + /* store value into main memory */ + if (MEMTX_OK != + address_space_rw(dm->as, addr, dm->mta_sba, &val, size, true)) { + xtrace_riscv_dm_error(dm->soc, "write to mem"); + return CMD_ERR_BUS; + } + } else { + /* read value from main memory */ + if (MEMTX_OK != + address_space_rw(dm->as, addr, dm->mta_sba, &val, size, false)) { + xtrace_riscv_dm_error(dm->soc, "read from mem"); + return CMD_ERR_BUS; + } + /* write value to arg0 */ + if ((res = riscv_dm_write_absdata(dm, 0, datawcount, val))) { + xtrace_riscv_dm_error(dm->soc, "write mem data (arg0)"); + return res; + } + } + + if (aampostinc) { + addr += argwidth << 2u; /* convert to bytes */ + if (riscv_dm_write_absdata(dm, argwidth, argwidth, addr)) { + xtrace_riscv_dm_error(dm->soc, "address postinc"); + } + } + + riscv_dm_set_busy(dm, false); + + return CMD_ERR_NONE; +} + +static CmdErr riscv_dm_quick_access(RISCVDMState *dm, uint32_t value) +{ + (void)dm; + (void)value; + + return CMD_ERR_NOT_SUPPORTED; +} + +/* + * Debugger implementation + */ + +static void riscv_dm_ensure_running(RISCVDMState *dm) +{ + /* + * Hang on: "halted" has many different meanings, depending on the context. + * + * There are -at least- three indicators that a hart is not running: + * 1. CPU may be halted: for example, it enters a WFI: `CPUState.halted` + * 2. CPU may be stopped: `CPUState.stopped`, which differs from + * `CPUState.stop` + * 3. VM may be not running: global VM state `current_run_state` != + * `RUN_STATE_RUNNING`, which is common to all vCPUs. + * + * Debug module adds just another "halted" state which means the CPU is ... + * actively running the park loop of the Debug ROM. This is the only state + * in this file that is considered as the "halted" state of a hart. + * + * As the debug module needs the vCPU to be actively running to execute + * Debug ROM code, ensure that the VM is running and that the vCPU is + * running whenever the remote debugger requests to "halt" (run the park + * loop) or "resume" (run the guest code). + */ + + if (runstate_needs_reset()) { + xtrace_riscv_dm_error(dm->soc, "cannot change VM now"); + return; + } + + RISCVCPU *cpu = dm->hart->cpu; + CPUState *cs = CPU(cpu); + + cpu_synchronize_state(cs); + + if (!runstate_is_running()) { + /* + * the VM may be stopped + * (for example, at startup, waiting for debugger initial request) + */ + xtrace_riscv_dm_info(dm->soc, "(re)starting the VM", 0); + vm_prepare_start(false); + vm_start(); + } + + if (cs->stopped && !cs->disabled) { + cpu_resume(cs); + } +} + + +static void riscv_dm_halt_hart(RISCVDMState *dm, unsigned hartsel) +{ + RISCVCPU *cpu = dm->harts[hartsel].cpu; + CPUState *cs = CPU(cpu); + + trace_riscv_dm_change_hart(dm->soc, "HALT", hartsel, cs->halted, + cs->running, cs->stopped, dm->hart->resumed); + + /* Note: NMI are not yet supported */ + cpu_exit(cs); + dm->haltreq_bm &= ~(1u << hartsel); + /* not sure if the real HW clear this flag on halt */ + dm->hart->resumed = false; + riscv_dm_set_cs(dm, true); + riscv_cpu_store_debug_cause(cs, DCSR_CAUSE_HALTREQ); + cpu_interrupt(cs, CPU_INTERRUPT_DEBUG); + /* vCPU should always be "running" - halt mode runs the park loop */ + riscv_dm_ensure_running(dm); +} + +static void riscv_dm_resume_hart(RISCVDMState *dm, unsigned hartsel) +{ + RISCVCPU *cpu = dm->harts[hartsel].cpu; + CPUState *cs = CPU(cpu); + + trace_riscv_dm_change_hart(dm->soc, "RESUME", hartsel, cs->halted, + cs->running, cs->stopped, dm->hart->resumed); + + /* generate "whereto" opcode */ + uint32_t offset = + dm->cfg.rom_phyaddr + dm->cfg.resume_offset - dm->cfg.whereto_phyaddr; + uint32_t whereto = riscv_dm_insn_jal(GPR_ZERO, offset); + if (MEMTX_OK != + address_space_rw(dm->as, dm->cfg.whereto_phyaddr, dm->mta_dm, &whereto, + sizeof(whereto), true)) { + xtrace_riscv_dm_error(dm->soc, "write whereto to mem"); + return; + } + + CPURISCVState *env = &cpu->env; + bool sstep = (bool)FIELD_EX32(env->dcsr, DCSR, STEP); + + if (sstep) { + /* + * it is not possible to single-step on an ebreak instruction + * disable single stepping on such an error condition + * note that the debugger is in charge of updating DPC on the next + * instruction whenever an ebreak instruction is reached. + */ + uint32_t insn = cpu_ldl_code(env, env->dpc); + if ((insn == riscv_dm_insn_ebreak()) || + (((uint16_t)insn) == riscv_dm_insn_c_ebreak())) { + /* cannot single-step an ebreak/c.break instruction */ + xtrace_riscv_dm_error(dm->soc, "clear single-step on ebreak"); + env->dcsr = FIELD_DP32(env->dcsr, DCSR, STEP, 0); + } + } + + if (riscv_dm_update_flags(dm, hartsel, true, R_FLAGS_FLAG_RESUME_MASK)) { + xtrace_riscv_dm_error(dm->soc, "cannot resume"); + } + + cpu_exit(cs); + cpu_reset_interrupt(cs, CPU_INTERRUPT_DEBUG); + + const char *cause = riscv_dmi_get_debug_cause_name(cpu); + trace_riscv_dm_resume_hart(dm->soc, sstep, cause); + + riscv_dm_ensure_running(dm); +} + +static int riscv_dm_discover_cpus(RISCVDMState *dm) +{ + unsigned hartix = 0; + CPUState *cs; + /* NOLINTNEXTLINE */ + CPU_FOREACH(cs) { + /* skips CPUs/harts that are not associated to this DM */ + bool skip = true; + for (unsigned ix = 0; ix < dm->hart_count; ix++) { + if (cs->cpu_index == dm->cpu_idx[ix]) { + skip = false; + break; + } + } + if (skip) { + continue; + } + if (hartix >= dm->hart_count) { + error_setg(&error_fatal, "Incoherent hart count"); + } + RISCVDMHartState *hart = &dm->harts[hartix]; + RISCVCPU *cpu = RISCV_CPU(cs); + if (!hart->cpu) { + hart->cpu = cpu; + } else { + /* associated CPU should be invariant across resets */ + g_assert(hart->cpu == cpu); + } + hart->hartid = hart->cpu->env.mhartid; + hart->unlock_reset = !cs->disabled; + if (!dm->as) { + /* address space is unknown till first hart is realized */ + dm->as = cs->as; + } else if (dm->as != cs->as) { + /* for now, all harts should share the same address space */ + error_setg(&error_fatal, "Incoherent address spaces"); + } + hartix++; + } + + return hartix ? 0 : -1; +} + +static const Property riscv_dm_properties[] = { + DEFINE_PROP_LINK("dtm", RISCVDMState, dtm, TYPE_RISCV_DTM, RISCVDTMState *), + DEFINE_PROP_ARRAY("hart", RISCVDMState, hart_count, cpu_idx, + qdev_prop_uint32, uint32_t), + DEFINE_PROP_UINT32("dmi_addr", RISCVDMState, cfg.dmi_addr, 0), + DEFINE_PROP_UINT32("dmi_next", RISCVDMState, cfg.dmi_next, 0), + DEFINE_PROP_UINT32("nscratch", RISCVDMState, cfg.nscratch, 1u), + DEFINE_PROP_UINT32("progbuf_count", RISCVDMState, cfg.progbuf_count, 0), + DEFINE_PROP_UINT32("data_count", RISCVDMState, cfg.data_count, 2u), + DEFINE_PROP_UINT32("abstractcmd_count", RISCVDMState, cfg.abstractcmd_count, + 0), + DEFINE_PROP_UINT64("dm_phyaddr", RISCVDMState, cfg.dm_phyaddr, 0), + DEFINE_PROP_UINT64("rom_phyaddr", RISCVDMState, cfg.rom_phyaddr, 0), + DEFINE_PROP_UINT64("whereto_phyaddr", RISCVDMState, cfg.whereto_phyaddr, 0), + DEFINE_PROP_UINT64("data_phyaddr", RISCVDMState, cfg.data_phyaddr, 0), + DEFINE_PROP_UINT64("progbuf_phyaddr", RISCVDMState, cfg.progbuf_phyaddr, 0), + DEFINE_PROP_UINT16("resume_offset", RISCVDMState, cfg.resume_offset, 0), + DEFINE_PROP_BOOL("sysbus_access", RISCVDMState, cfg.sysbus_access, true), + /* beware that OpenOCD (RISC-V 2024/04) assumes this is always supported */ + DEFINE_PROP_BOOL("abstractauto", RISCVDMState, cfg.abstractauto, true), + DEFINE_PROP_UINT64("mta_dm", RISCVDMState, cfg.mta_dm, RISCVDM_DEFAULT_MTA), + DEFINE_PROP_UINT64("mta_sba", RISCVDMState, cfg.mta_sba, + RISCVDM_DEFAULT_MTA), + DEFINE_PROP_BOOL("enable", RISCVDMState, cfg.enable, true), +}; + +static void riscv_dm_reset_enter(Object *obj, ResetType type) +{ + RISCVDMClass *c = RISCV_DM_GET_CLASS(obj); + RISCVDMState *dm = RISCV_DM(obj); + + trace_riscv_dm_reset(dm->soc, "enter"); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } + + g_assert(dm->dtm != NULL); + RISCVDTMClass *dtmc = RISCV_DTM_GET_CLASS(OBJECT(dm->dtm)); + dm->dtm_ok = + (*dtmc->register_dm)(DEVICE(dm->dtm), RISCV_DEBUG_DEVICE(obj), + dm->cfg.dmi_addr, DM_REG_COUNT, dm->cfg.enable); + + for (unsigned ix = 0; ix < DM_REG_COUNT; ix++) { + if (ix == A_DMCONTROL) { + dm->regs[ix] = RISCVDM_DMS[ix].value & ~R_DMCONTROL_DMACTIVE_MASK; + continue; + } + if (ix == A_NEXTDM) { + continue; + } + dm->regs[ix] = RISCVDM_DMS[ix].value; + } + + /* Hart statuses are updated on reset_exit */ + dm->nonexistent_bm = 0; + dm->unavailable_bm = 0; + dm->haltreq_bm = 0; + dm->reset_bm = 0; + dm->address = 0; + dm->to_go_bm = 0; + for (unsigned ix = 0; ix < dm->hart_count; ix++) { + RISCVDMHartState *hart = &dm->harts[ix]; + /* + * preserve CPU reference, as DM may be queried while it is maintained + * for current status. Hart association never changes anyway. + */ + RISCVCPU *cpu = hart->cpu; + memset(hart, 0, sizeof(RISCVDMHartState)); + hart->cpu = cpu; + } +} + +static void riscv_dm_reset_exit(Object *obj, ResetType type) +{ + RISCVDMClass *c = RISCV_DM_GET_CLASS(obj); + RISCVDMState *dm = RISCV_DM(obj); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + trace_riscv_dm_reset(dm->soc, "exit"); + + /* generate hart association */ + if (riscv_dm_discover_cpus(dm)) { + error_setg(&error_fatal, "Cannot identify harts"); + } + + riscv_dm_set_busy(dm, false); + + bool is_running = runstate_is_running(); + + for (unsigned ix = 0; ix < dm->hart_count; ix++) { + RISCVDMHartState *hart = &dm->harts[ix]; + RISCVCPU *cpu = hart->cpu; + if (!cpu) { + continue; + } + + CPURISCVState *env = &cpu->env; + if (!env) { + continue; + } + + /* + * inform the hart a remote debugger/debugger module is + * available, as it changes how debug exceptions and trigger CSRs + * behave + */ + env->debug_dm = dm->dtm_ok; + + /* External debug support exists */ + env->dcsr = FIELD_DP32(env->dcsr, DCSR, XDEBUGVER, 4u); + /* No support for MPRV */ + env->dcsr = FIELD_DP32(env->dcsr, DCSR, MPRVEN, 0u); + /* Initial value */ + env->dcsr = FIELD_DP32(env->dcsr, DCSR, STOPTIME, 0u); + env->dcsr = FIELD_DP32(env->dcsr, DCSR, STOPCOUNT, 0u); + + CPUState *cs = CPU(cpu); + if (cs->halted) { + if (cs->disabled) { + dm->unavailable_bm |= 1u << ix; + trace_riscv_dm_unavailable(dm->soc, cs->cpu_index); + /* a hart cannot be halted and unavailable at once */ + hart->halted = false; + } else { + /* hart not explicitly halted, ready to run in parked mode */ + hart->halted = false; + } + } + + /* + * fix DCSR at VM initialization: + * 1. if the VM is started as soon as QEMU is started, do nothing + * 2. if the VM is idled when QEMU is started, flag all harts as + * "halt-on-reset" as the debugger requires a reason for the + * harts being initially stopped + */ + if (is_running) { + /* called from vm_state change, running */ + if (riscv_dmi_get_debug_cause(cpu) == DCSR_CAUSE_RESETHALTREQ) { + env->dcsr = FIELD_DP32(env->dcsr, DCSR, CAUSE, DCSR_CAUSE_NONE); + } + } else { + /* called from DMI reset */ + if (riscv_dmi_get_debug_cause(cpu) == DCSR_CAUSE_NONE) { + env->dcsr = + FIELD_DP32(env->dcsr, DCSR, CAUSE, DCSR_CAUSE_RESETHALTREQ); + } + } + + xtrace_riscv_dm_info(dm->soc, "cause", riscv_dmi_get_debug_cause(cpu)); + } + + /* TODO: should we clear progbug, absdata, ...? */ + + /* consider all harts for this DM share the same capabilities */ + CPURISCVState *env = &dm->harts[0u].cpu->env; + + uint32_t value = 0u; + if (dm->cfg.sysbus_access && env) { + value = FIELD_DP32(value, SBCS, SBVERSION, RISCV_DEBUG_SB_VERSION); + value = FIELD_DP32(value, SBCS, SBASIZE, 1u << (4u + env->misa_mxl)); + value = + FIELD_DP32(value, SBCS, SBACCESS64, (uint32_t)(env->misa_mxl > 1u)); + value = FIELD_DP32(value, SBCS, SBACCESS32, 1u); + value = FIELD_DP32(value, SBCS, SBACCESS16, 1u); + value = FIELD_DP32(value, SBCS, SBACCESS8, 1u); + } + + dm->regs[A_SBCS] = value; + + /* set dmactive once ready */ + dm->regs[A_DMCONTROL] |= R_DMCONTROL_DMACTIVE_MASK; +} + +static void riscv_dm_realize(DeviceState *dev, Error **errp) +{ + RISCVDMState *dm = RISCV_DM(dev); + + MachineState *ms = MACHINE(qdev_get_machine()); + unsigned max_cpus = ms->smp.max_cpus; + g_assert((dm->hart_count > 0) && (dm->hart_count <= max_cpus)); + dm->harts = g_new0(RISCVDMHartState, dm->hart_count); + dm->regs = g_new0(uint32_t, DM_REG_COUNT); + dm->as = NULL; + + if (dm->cfg.data_count > R_ABSTRACTAUTO_AUTOEXECDATA_LENGTH) { + error_setg(errp, "Invalid data count property"); + return; + } + if (dm->cfg.progbuf_count > R_ABSTRACTAUTO_AUTOEXECPROGBUF_LENGTH) { + error_setg(errp, "Invalid progbuf count property"); + return; + } + + qdev_init_gpio_in_named(dev, &riscv_dm_acknowledge, RISCV_DM_ACK_LINES, + ACK_COUNT); + + dm->soc = object_get_canonical_path_component(OBJECT(dev)->parent); + dm->unavailable_bm = (1u << dm->hart_count) - 1u; + dm->regs[A_NEXTDM] = dm->cfg.dmi_next; + + dm->mta_dm = ((RISCVDMMemAttrs){ .value = dm->cfg.mta_dm }).attrs; + dm->mta_sba = ((RISCVDMMemAttrs){ .value = dm->cfg.mta_sba }).attrs; +} + +static void riscv_dm_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + (void)data; + + dc->realize = &riscv_dm_realize; + device_class_set_props(dc, riscv_dm_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + ResettableClass *rc = RESETTABLE_CLASS(klass); + RISCVDMClass *cc = RISCV_DM_CLASS(klass); + resettable_class_set_parent_phases(rc, &riscv_dm_reset_enter, NULL, + &riscv_dm_reset_exit, + &cc->parent_phases); + + RISCVDebugDeviceClass *dmc = RISCV_DEBUG_DEVICE_CLASS(klass); + dmc->write_rq = &riscv_dm_write_rq; + dmc->read_rq = &riscv_dm_read_rq; + dmc->read_value = &riscv_dm_read_value; + dmc->set_next_dm = &riscv_dm_set_next_dm; + + /* + * unfortunately, MemTxtAttrs is a bitfield and there is no built-time way + * to define nor check its contents vs. an integral value. Run a quick + * sanity check at runtime. + */ + RISCVDMMemAttrs mta = { .attrs = MEMTXATTRS_UNSPECIFIED }; + g_assert(mta.value == RISCVDM_DEFAULT_MTA); +} + +static const TypeInfo riscv_dm_info = { + .name = TYPE_RISCV_DM, + .parent = TYPE_RISCV_DEBUG_DEVICE, + .instance_size = sizeof(RISCVDMState), + .class_size = sizeof(RISCVDMClass), + .class_init = &riscv_dm_class_init, +}; + +static void riscv_dm_register_types(void) +{ + type_register_static(&riscv_dm_info); +} + +type_init(riscv_dm_register_types); diff --git a/hw/riscv/dtm.c b/hw/riscv/dtm.c new file mode 100644 index 0000000000000..2259869a4aa78 --- /dev/null +++ b/hw/riscv/dtm.c @@ -0,0 +1,590 @@ +/* + * QEMU Debug Transport Module + * + * Copyright (c) 2022-2025 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. + * + * Generated code for absract commands has been extracted from the PULP Debug + * module whose file (dm_mem.sv) contains the following copyright. This piece of + * code is self contained within the `riscv_dtm_dm_access_register` function: + * + * Copyright and related rights are licensed under the Solderpad Hardware + * License, Version 0.51 (the “License”); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law + * or agreed to in writing, software, hardware and materials distributed under + * this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +#include "qemu/osdep.h" +#include "qemu/compiler.h" +#include "qemu/log.h" +#include "qemu/typedefs.h" +#include "qapi/error.h" +#include "hw/jtag/tap_ctrl.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/debug.h" +#include "hw/riscv/dtm.h" +#include "system/runstate.h" +#include "trace.h" + +/* clang-format off */ + +/* Debug Module Interface and Control */ +REG32(DTMCS, 0x10u) + FIELD(DTMCS, VERSION, 0u, 4u) + FIELD(DTMCS, ABITS, 4u, 6u) + FIELD(DTMCS, DMISTAT, 10u, 2u) + FIELD(DTMCS, IDLE, 12u, 3u) + FIELD(DTMCS, DMIRESET, 16u, 1u) + FIELD(DTMCS, DMIHARDRESET, 17u, 1u) +REG64(DMI, 0x11u) + FIELD(DMI, OP, 0u, 2u) + FIELD(DMI, DATA, 2u, 32u) + FIELD(DMI, ADDRESS, 34u, 64u-34u) /* real width is a runtime property */ + +/* clang-format on */ + +#define xtrace_riscv_dtm_error(_msg_) \ + trace_riscv_dtm_error(__func__, __LINE__, _msg_) +#define xtrace_riscv_dtm_info(_msg_, _val_) \ + trace_riscv_dtm_info(__func__, __LINE__, _msg_, _val_) + +/* + * DMI register operations + * see RISC-V debug spec secttion 6.1.5 Debug Module Interface Access + */ +typedef enum { + DMI_IGNORE, + DMI_READ, + DMI_WRITE, + DMI_RESERVED, +} RISCVDMIOperation; + +typedef QLIST_HEAD(, RISCVDebugModule) RISCVDebugModuleList; + +typedef struct RISCVDebugModule { + QLIST_ENTRY(RISCVDebugModule) entry; + RISCVDebugDeviceState *dev; + RISCVDebugDeviceClass *dc; + char *path; + uint32_t base; + uint32_t size; + bool enabled; +} RISCVDebugModule; + +/** Debug Module Interface */ +struct RISCVDTMState { + DeviceState parent; + + RISCVDebugModuleList dms; + RISCVDebugModule *last_dm; /* last selected DM */ + + uint32_t address; /* last updated address */ + RISCVDebugResult dmistat; /* Operation result */ + bool cmd_busy; /* A command is being executed */ + + /* properties */ + DeviceState *tap_ctrl; + unsigned abits; /* address bit count */ +}; + +static RISCVDebugModule *riscv_dtm_get_dm(RISCVDTMState *s, uint32_t addr); +static void riscv_dtm_sort_dms(RISCVDTMState *s); +static void riscv_dtm_activate_dms(RISCVDTMState *s); + +static void riscv_dtm_tap_dtmcs_capture(TapDataHandler *tdh); +static void riscv_dtm_tap_dtmcs_update(TapDataHandler *tdh); +static void riscv_dtm_tap_dmi_capture(TapDataHandler *tdh); +static void riscv_dtm_tap_dmi_update(TapDataHandler *tdh); + +#define RISCV_DEBUG_DMI_VERSION 1u /* RISC-V Debug spec 0.13.x & 1.0 */ +#define RISCVDMI_DTMCS_IR 0x10u +#define RISCVDMI_DMI_IR 0x11u + +static const TapDataHandler RISCVDMI_DTMCS = { + .name = "dtmcs", + .length = 32u, + .value = RISCV_DEBUG_DMI_VERSION, /* abits updated at runtime */ + .capture = &riscv_dtm_tap_dtmcs_capture, + .update = &riscv_dtm_tap_dtmcs_update, +}; + +static const TapDataHandler RISCVDMI_DMI = { + .name = "dmi", + /* data, op; abits updated at runtime */ + .length = R_DMI_OP_LENGTH + R_DMI_DATA_LENGTH, + .capture = &riscv_dtm_tap_dmi_capture, + .update = &riscv_dtm_tap_dmi_update, +}; + +#define MAKE_RUNSTATE_ENTRY(_ent_) [RUN_STATE_##_ent_] = stringify(_ent_) +static const char *RISCVDMI_RUNSTATE_NAMES[] = { + /* clang-format off */ + MAKE_RUNSTATE_ENTRY(DEBUG), + MAKE_RUNSTATE_ENTRY(INMIGRATE), + MAKE_RUNSTATE_ENTRY(INTERNAL_ERROR), + MAKE_RUNSTATE_ENTRY(IO_ERROR), + MAKE_RUNSTATE_ENTRY(PAUSED), + MAKE_RUNSTATE_ENTRY(POSTMIGRATE), + MAKE_RUNSTATE_ENTRY(PRELAUNCH), + MAKE_RUNSTATE_ENTRY(FINISH_MIGRATE), + MAKE_RUNSTATE_ENTRY(RESTORE_VM), + MAKE_RUNSTATE_ENTRY(RUNNING), + MAKE_RUNSTATE_ENTRY(SAVE_VM), + MAKE_RUNSTATE_ENTRY(SHUTDOWN), + MAKE_RUNSTATE_ENTRY(SUSPENDED), + MAKE_RUNSTATE_ENTRY(WATCHDOG), + MAKE_RUNSTATE_ENTRY(GUEST_PANICKED), + MAKE_RUNSTATE_ENTRY(COLO), + /* clang-format on */ +}; +#undef MAKE_RUNSTATE_ENTRY +#define RUNSTATE_NAME(_reg_) \ + ((((_reg_) <= ARRAY_SIZE(RISCVDMI_RUNSTATE_NAMES)) && \ + RISCVDMI_RUNSTATE_NAMES[_reg_]) ? \ + RISCVDMI_RUNSTATE_NAMES[_reg_] : \ + "?") + +/* -------------------------------------------------------------------------- */ +/* DTM API */ +/* -------------------------------------------------------------------------- */ + +static bool riscv_dtm_register_dm(DeviceState *dev, + RISCVDebugDeviceState *dbgdev, + hwaddr base_addr, hwaddr size, bool enable) +{ + RISCVDTMState *s = RISCV_DTM(dev); + + g_assert(dev->realized); + + if ((base_addr + size - 1u) > (1u << s->abits)) { + error_setg(&error_fatal, + "DM address range cannot be encoded in %u address bits", + s->abits); + } + + if (!s->tap_ctrl) { + xtrace_riscv_dtm_info("TAP controller not available", 0); + return false; + } + + TapCtrlIfClass *tapcls = TAP_CTRL_IF_GET_CLASS(s->tap_ctrl); + TapCtrlIf *tap = TAP_CTRL_IF(s->tap_ctrl); + + /* may fail if TAP controller is not active */ + bool tap_ok = tapcls->is_enabled(tap); + + RISCVDebugModule *node; + unsigned count = 0; + QLIST_FOREACH(node, &s->dms, entry) { + count += 1u; + if ((node->dev == dbgdev) && (node->base == base_addr) && + (node->size == size)) { + /* already registered */ + return tap_ok; + } + if (base_addr > (node->base + node->size - 1u)) { + continue; + } + if ((base_addr + size - 1u) < node->base) { + continue; + } + + error_setg(&error_fatal, "Debug Module overlap\n"); + } + + object_ref(OBJECT(dbgdev)); + + RISCVDebugModule *dm = g_new(RISCVDebugModule, 1u); + dm->dev = dbgdev; + dm->dc = RISCV_DEBUG_DEVICE_GET_CLASS(OBJECT(dbgdev)); + dm->base = base_addr; + dm->size = size; + dm->enabled = enable; + dm->path = + g_strdup_printf("%s/%s", + object_get_canonical_path_component( + OBJECT(dbgdev)->parent), + object_get_canonical_path_component(OBJECT(dbgdev))); + + QLIST_INSERT_HEAD(&s->dms, dm, entry); + s->last_dm = dm; + + trace_riscv_dtm_register_dm(dm->path, count, base_addr, + base_addr + size - 1u, enable, tap_ok); + + riscv_dtm_sort_dms(s); + riscv_dtm_activate_dms(s); + + return tap_ok; +} + +static void riscv_dtm_enable_dm(DeviceState *dev, RISCVDebugDeviceState *dbgdev, + bool enable) +{ + RISCVDTMState *s = RISCV_DTM(dev); + + RISCVDebugModule *node; + bool update = false; + QLIST_FOREACH(node, &s->dms, entry) { + if (node->dev == dbgdev) { + update = node->enabled != enable; + node->enabled = enable; + trace_riscv_dtm_enable_dm(object_get_canonical_path_component( + OBJECT(dbgdev)), + enable, update); + break; + } + } + + if (update) { + riscv_dtm_activate_dms(s); + } +} + +/* -------------------------------------------------------------------------- */ +/* DTMCS/DMI implementation */ +/* -------------------------------------------------------------------------- */ + +static void riscv_dtm_tap_dtmcs_capture(TapDataHandler *tdh) +{ + RISCVDTMState *s = tdh->opaque; + + tdh->value = (s->abits << 4u) | (RISCV_DEBUG_DMI_VERSION << 0u) | + ((uint64_t)s->dmistat << 10u); /* see DMI op result */ +} + +static void riscv_dtm_tap_dtmcs_update(TapDataHandler *tdh) +{ + RISCVDTMState *s = tdh->opaque; + if (tdh->value & (1u << 16u)) { + /* dmireset */ + trace_riscv_dtm_dtmcs_reset(); + s->dmistat = RISCV_DEBUG_NOERR; + } + if (tdh->value & (1u << 17u)) { + /* dmi hardreset */ + qemu_log_mask(LOG_UNIMP, "%s: DMI hard reset\n", __func__); + } +} + +static void riscv_dtm_tap_dmi_capture(TapDataHandler *tdh) +{ + RISCVDTMState *s = tdh->opaque; + + uint32_t addr = s->address; + uint32_t value = 0; + + if (s->dmistat == RISCV_DEBUG_NOERR) { + unsigned op = (unsigned)(tdh->value & 0b11); + if (op == DMI_READ) { + RISCVDebugModule *dm = riscv_dtm_get_dm(s, addr); + if (!dm) { + s->dmistat = RISCV_DEBUG_FAILED; + value = 0; + qemu_log_mask(LOG_UNIMP, "%s: Unknown DM address 0x%x\n", + __func__, addr); + } else { + trace_riscv_dtm_tap_dmi_capture(addr); + value = dm->dc->read_value(dm->dev); + } + } + } + + /* + * In Capture-DR, the DTM updates data with the result from [the previous + * update] operation, updating op if the current op isn’t sticky. + */ + tdh->value = (((uint64_t)addr) << (32u + 2u)) | (((uint64_t)value) << 2u) | + ((uint64_t)(s->dmistat & 0b11)); +} + +static void riscv_dtm_tap_dmi_update(TapDataHandler *tdh) +{ + RISCVDTMState *s = tdh->opaque; + + uint32_t value; + uint32_t addr = + (uint32_t)extract64(tdh->value, R_DMI_ADDRESS_SHIFT, (int)s->abits); + unsigned op = (unsigned)(tdh->value & 0b11); + + if (op == DMI_IGNORE) { + /* + * Don’t send anything over the DMI during Update-DR. This operation + * should never result in a busy or error response. The address and data + * reported in the following Capture-DR are undefined. + */ + return; + } + + /* store address for next read back */ + s->address = addr; + + RISCVDebugModule *dm = riscv_dtm_get_dm(s, addr); + if (!dm) { + s->dmistat = RISCV_DEBUG_FAILED; + qemu_log_mask(LOG_UNIMP, "%s: Unknown DM address 0x%x, op %u\n", + __func__, addr, op); + return; + } + + /* + * In Update-DR, the DTM starts the operation specified in op unless the + * current status reported in op is sticky. + */ + switch (op) { + case DMI_IGNORE: /* NOP */ + g_assert_not_reached(); + return; + case DMI_READ: + trace_riscv_dtm_tap_dmi_update(addr, "read"); + s->dmistat = dm->dc->read_rq(dm->dev, addr - dm->base); + break; + case DMI_WRITE: + trace_riscv_dtm_tap_dmi_update(addr, "write"); + value = (uint32_t)FIELD_EX64(tdh->value, DMI, DATA); + s->dmistat = dm->dc->write_rq(dm->dev, addr - dm->base, value); + break; + case DMI_RESERVED: + default: + s->dmistat = RISCV_DEBUG_FAILED; + qemu_log_mask(LOG_UNIMP, "%s: Unknown operation %u\n", __func__, op); + } +} + +static void riscv_dtm_register_tap_handlers(RISCVDTMState *s) +{ + if (!s->tap_ctrl) { + return; + } + + TapCtrlIfClass *tapcls = TAP_CTRL_IF_GET_CLASS(s->tap_ctrl); + TapCtrlIf *tap = TAP_CTRL_IF(s->tap_ctrl); + + /* + * copy the template to update the opaque value + * no lifetime issue as the data handler is copied by the TAP controller + */ + TapDataHandler tdh; + + memcpy(&tdh, &RISCVDMI_DTMCS, sizeof(TapDataHandler)); + tdh.value |= s->abits << 4u; /* add address bit count */ + tdh.opaque = s; + if (tapcls->register_instruction(tap, RISCVDMI_DTMCS_IR, &tdh)) { + xtrace_riscv_dtm_error("cannot register DMTCS"); + return; + } + + memcpy(&tdh, &RISCVDMI_DMI, sizeof(TapDataHandler)); + tdh.length += s->abits; /* add address bit count */ + tdh.opaque = s; + /* the data handler is copied by the TAP controller */ + if (tapcls->register_instruction(tap, RISCVDMI_DMI_IR, &tdh)) { + xtrace_riscv_dtm_error("cannot register DMI"); + return; + } +} + +static RISCVDebugModule *riscv_dtm_get_dm(RISCVDTMState *s, uint32_t addr) +{ + RISCVDebugModule *dm = s->last_dm; + + if (dm && (addr >= dm->base) && (addr < dm->base + dm->size)) { + return dm; + } + + QLIST_FOREACH(dm, &s->dms, entry) { + if ((addr >= dm->base) && (addr < dm->base + dm->size)) { + s->last_dm = dm; + return dm; + } + } + + s->last_dm = NULL; + return NULL; +} + +static void riscv_dtm_vm_state_change(void *opaque, bool running, + RunState state) +{ + (void)opaque; + (void)running; + trace_riscv_dtm_vm_state_change(RUNSTATE_NAME(state), state); +} + +static int riscv_dtm_order_dm(const void *pdm1, const void *pdm2) +{ + return ((int)(*(RISCVDebugModule **)pdm1)->base) - + ((int)(*(RISCVDebugModule **)pdm2)->base); +} + +static void riscv_dtm_sort_dms(RISCVDTMState *s) +{ + RISCVDebugModule *dm; + + /* iterate once to get the count of managed DMs */ + unsigned count = 0; + QLIST_FOREACH(dm, &s->dms, entry) { + count += 1; + } + + /* create an array of sortable DM references */ + RISCVDebugModule **dma = g_new(RISCVDebugModule *, count); + RISCVDebugModule **cdm = dma; + QLIST_FOREACH(dm, &s->dms, entry) { + *cdm++ = dm; + } + + /* sort DM reference by increasing base address */ + qsort(dma, count, sizeof(RISCVDebugModule *), &riscv_dtm_order_dm); + + /* create a new list of ordered, managed DMs */ + RISCVDebugModuleList sorted; + QLIST_INIT(&sorted); + RISCVDebugModule *cur = QLIST_FIRST(&sorted); + for (unsigned ix = 0; ix < count; ix++) { + QLIST_REMOVE(dma[ix], entry); + if (!cur) { + QLIST_INSERT_HEAD(&sorted, dma[ix], entry); + } else { + QLIST_INSERT_AFTER(cur, dma[ix], entry); + } + cur = dma[ix]; + } + /* replace current list head with the new ordered one */ + s->dms = sorted; + + g_free(dma); +} + +static void riscv_dtm_update_next_dm(RISCVDebugModule *dm, + const RISCVDebugModule *next_dm) +{ + if (dm->dc->set_next_dm) { + uint32_t next_addr = next_dm ? next_dm->base : 0u; + trace_riscv_dtm_set_next_dm(dm->path, dm->base, + next_dm ? next_dm->path : "end", next_addr); + (*dm->dc->set_next_dm)(dm->dev, next_addr); + } +} + +static void riscv_dtm_activate_dms(RISCVDTMState *s) +{ + RISCVDebugModule *dm; + RISCVDebugModule *prev_dm = NULL; + QLIST_FOREACH(dm, &s->dms, entry) { + /* + * devices that do not implement set_next_dm are not part of the next_dm + * chain and therefore ignored + */ + if (dm->enabled && dm->dc->set_next_dm) { + /* do not bother updating a disabled DM */ + if (prev_dm && prev_dm->enabled) { + riscv_dtm_update_next_dm(prev_dm, dm); + } + prev_dm = dm; + } + } + + if (prev_dm) { + riscv_dtm_update_next_dm(prev_dm, NULL); + } +} + +static const Property riscv_dtm_properties[] = { + DEFINE_PROP_UINT32("abits", RISCVDTMState, abits, 0x7u), + DEFINE_PROP_LINK("tap-ctrl", RISCVDTMState, tap_ctrl, TYPE_DEVICE, + DeviceState *), +}; + +static void riscv_dtm_reset_enter(Object *obj, ResetType type) +{ + RISCVDTMClass *c = RISCV_DTM_GET_CLASS(obj); + RISCVDTMState *s = RISCV_DTM(obj); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } + + s->address = 0; + s->last_dm = NULL; +} + +static void riscv_dtm_realize(DeviceState *dev, Error **errp) +{ + RISCVDTMState *s = RISCV_DTM(dev); + + if (s->abits < 7u || s->abits > 30u) { + error_setg(errp, "Invalid address bit count"); + return; + } + + riscv_dtm_register_tap_handlers(s); +} + +static void riscv_dtm_init(Object *obj) +{ + RISCVDTMState *s = RISCV_DTM(obj); + + qemu_add_vm_change_state_handler(&riscv_dtm_vm_state_change, s); + + QLIST_INIT(&s->dms); +} + +static void riscv_dtm_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + (void)data; + + dc->realize = &riscv_dtm_realize; + device_class_set_props(dc, riscv_dtm_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + ResettableClass *rc = RESETTABLE_CLASS(klass); + RISCVDTMClass *tc = RISCV_DTM_CLASS(klass); + resettable_class_set_parent_phases(rc, &riscv_dtm_reset_enter, NULL, NULL, + &tc->parent_phases); + + RISCVDTMClass *dmc = RISCV_DTM_CLASS(klass); + dmc->register_dm = &riscv_dtm_register_dm; + dmc->enable_dm = &riscv_dtm_enable_dm; +} + +static const TypeInfo riscv_dtm_info = { + .name = TYPE_RISCV_DTM, + .parent = TYPE_DEVICE, + .instance_size = sizeof(RISCVDTMState), + .instance_init = &riscv_dtm_init, + .class_size = sizeof(RISCVDTMClass), + .class_init = &riscv_dtm_class_init, +}; + +static void riscv_dtm_register_types(void) +{ + type_register_static(&riscv_dtm_info); +} + +type_init(riscv_dtm_register_types); diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index 2a8d5b136cc40..6d626c3bd3726 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -14,5 +14,8 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files( 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c')) riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c')) riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c')) +riscv_ss.add(when: 'CONFIG_RISCV_DEBUG', if_true: files('debug.c')) +riscv_ss.add(when: 'CONFIG_RISCV_DM', if_true: files('dm.c')) +riscv_ss.add(when: 'CONFIG_RISCV_DTM', if_true: files('dtm.c')) hw_arch += {'riscv': riscv_ss} diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 61bcf28054564..2ae2269cf3d05 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -25,6 +25,48 @@ riscv_iommu_hpm_iocntinh_cy(bool prev_cy_inh) "prev_cy_inh %d" riscv_iommu_hpm_cycle_write(uint32_t ovf, uint64_t val) "ovf 0x%x val 0x%"PRIx64 riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx 0x%x ovf 0x%x val 0x%"PRIx64 +# dm.c + +riscv_dm_aarsize_error(const char *soc, unsigned aarsize) "%s: aarsize %u not supported" +riscv_dm_absdata(const char *soc, const char *op, unsigned woffset, unsigned wcount, uint64_t value, uint32_t res) "%s: %s: @ %u[%u] = 0x%08" PRIx64 ": res %u" +riscv_dm_abstract_cmd(const char *soc, uint32_t addr, uint32_t inst) "%s: [%x]: %08x" +riscv_dm_access_register(const char *soc, const char *msg, const char *regname, uint32_t value) "%s: %s: %s (%x)" +riscv_dm_busy(const char *soc, bool busy) "%s: busy: %u" +riscv_dm_change_hart(const char *soc, const char *msg, unsigned hart, bool cpuhalted, bool cpurunning, bool cpustopped, bool resack) "%s: [%s] hart#%u h:%u r:%u s:%u A:%u" +riscv_dm_cs(const char *soc, bool enable) "%s: debug_cs: %u" +riscv_dm_dmstatus_read(const char *soc, uint32_t val, bool unavail, bool halted, bool cpuhalted, bool running, bool cpurunning, bool resack, bool cpustopped, uint32_t pc) "%s: 0x%08x U:%u H:%u(%u) R:%u(%u) A:%u S:%u @ %08x" +riscv_dm_error(const char *soc, const char *func, int line, const char *msg) "%s: %s:%d %s" +riscv_dm_halted(const char *soc, unsigned hart, uint64_t pc, const char *cause) "%s hart #%u halted @ 0x%08" PRIx64 " as %s" +riscv_dm_hart_reset(const char *soc, unsigned cpuid, unsigned hartid, const char *msg) "%s {%u}: hartid:%u: %s" +riscv_dm_hart_state(const char *soc, unsigned hartsel, const char *msg) "%s: hart #%u %s" +riscv_dm_info(const char *soc, const char *func, int line, const char *msg, uint32_t val) "%s: %s:%d %s 0x%08x" +riscv_dm_progbuf(const char *soc, const char *op, unsigned woffset, uint64_t value, uint32_t res) "%s: %s: @ %u = 0x%08" PRIx64 ": res %u" +riscv_dm_reg_capture(const char *soc, const char *name, uint64_t addr, uint32_t value) "%s: %s @ %02" PRIx64 " = 0x%08x" +riscv_dm_reg_update(const char *soc, const char *name, uint32_t addr, uint32_t value, const char * op, int ret) "%s: %s @ %02x = 0x%08x, %s res %d" +riscv_dm_reset(const char *soc, const char *msg) "%s: %s" +riscv_dm_resume_hart(const char *soc, bool sstep, const char *cause) "%s: single-step: %u, cause %s" +riscv_dm_sbcs_write(const char *soc, bool err, bool busyerr, unsigned access, bool rdonaddr, bool rdondata, bool autoinc) "%s: err#:%u busy#:%u %uB rdonaddr:%u rdondata: %u autoinc:%u" +riscv_dm_sbaddr_write(const char *soc, unsigned slot, uint32_t address) "%s: @[%u] 0x%08x" +riscv_dm_sbdata_read(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0x%08x" +riscv_dm_sbdata_write(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0x%08x" +riscv_dm_status(const char *soc, int cpuid, const char *status) "%s {%d}: %s" +riscv_dm_sysbus_data_read(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] <- %08" PRIx64 ": res %u" +riscv_dm_sysbus_data_write(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] -> %08" PRIx64 ": res %u" +riscv_dm_unavailable(const char *soc, int cpuid) "%s {%d}" +riscv_dm_unavailable_hart_control(const char *soc, unsigned hartsel, const char *action) "%s: #%u cannot %s as unavailable" + +# dtm.c + +riscv_dtm_dtmcs_reset(void) "" +riscv_dtm_enable_dm(const char *cls, bool enable, bool update) "%s: en:%u upd:%u" +riscv_dtm_error(const char *func, int line, const char *msg) "%s:%d %s" +riscv_dtm_info(const char *func, int line, const char *msg, uint32_t val) "%s:%d %s 0x%08x" +riscv_dtm_register_dm(const char *cls, unsigned count, uint64_t first, uint64_t last, bool enabled, bool ok) "%s: #%u 0x%" PRIx64 "..0x%" PRIx64 ": enabled:%u tap:%u" +riscv_dtm_set_next_dm(const char *fromcls, uint32_t fromaddr, const char *tocls, uint32_t toaddr) "%s @ 0x%x next_dm %s @ 0x%x" +riscv_dtm_tap_dmi_capture(uint32_t addr) "0x%x" +riscv_dtm_tap_dmi_update(uint32_t addr, const char *dir) "0x%x %s" +riscv_dtm_vm_state_change(const char *name, unsigned state) "VM state: %s[%u]" + # ibex_common.c ibex_create_device(const char *pname, const char *name) "%s.%s" diff --git a/include/disas/dis-asm.h b/include/disas/dis-asm.h index 3b50ecfb5409d..e8a68ae57a17f 100644 --- a/include/disas/dis-asm.h +++ b/include/disas/dis-asm.h @@ -453,6 +453,7 @@ int print_insn_riscv128 (bfd_vma, disassemble_info*); int print_insn_rx(bfd_vma, disassemble_info *); int print_insn_hexagon(bfd_vma, disassemble_info *); int print_insn_loongarch(bfd_vma, disassemble_info *); +const char *get_riscv_debug_reg_name(int regno); #ifdef CONFIG_CAPSTONE bool cap_disas_target(disassemble_info *info, uint64_t pc, size_t size); diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index b0482c96ba91b..3e249ec350127 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -167,6 +167,8 @@ struct CPUClass { int (*gdb_read_register)(CPUState *cpu, GByteArray *buf, int reg); int (*gdb_write_register)(CPUState *cpu, uint8_t *buf, int reg); vaddr (*gdb_adjust_breakpoint)(CPUState *cpu, vaddr addr); + bool (*debug_request)(CPUState *cpu); + void (*debug_enable_singlestep)(CPUState *cpu, vaddr addr); const char *gdb_core_xml_file; const char * (*gdb_arch_name)(CPUState *cpu); diff --git a/include/hw/misc/pulp_rv_dm.h b/include/hw/misc/pulp_rv_dm.h new file mode 100644 index 0000000000000..c833efba4502f --- /dev/null +++ b/include/hw/misc/pulp_rv_dm.h @@ -0,0 +1,70 @@ +/* + * QEMU OpenTitan Debug Module device + * + * Copyright (c) 2022-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_PULP_RV_DM_H +#define HW_PULP_RV_DM_H + +#include "qemu/osdep.h" +#include "qom/object.h" +#include "hw/irq.h" +#include "hw/sysbus.h" + +#define TYPE_PULP_RV_DM "pulp-rv-dm" +OBJECT_DECLARE_SIMPLE_TYPE(PulpRVDMState, PULP_RV_DM) + +#define PULP_RV_DM_ACK_OUT_LINES TYPE_PULP_RV_DM ".ack-out" + +/* Configuration */ +#define PULP_RV_DM_NSCRATCH_COUNT 2u +#define PULP_RV_DM_DATA_COUNT 2u +#define PULP_RV_DM_PROGRAM_BUFFER_COUNT 8u +#define PULP_RV_DM_ABSTRACTCMD_COUNT 10u +#define PULP_RV_DM_FLAGS_COUNT 1u +#define PULP_RV_DM_WHERETO_OFFSET 0x300u +#define PULP_RV_DM_PROGRAM_BUFFER_OFFSET 0x360u +#define PULP_RV_DM_DATAADDR_OFFSET 0x380u + +/* ROM entry points */ +#define PULP_RV_DM_HALT_OFFSET 0x0000 +#define PULP_RV_DM_RESUME_OFFSET 0x0004 +#define PULP_RV_DM_EXCEPTION_OFFSET 0x0008 + +/* Memory regions */ +#define PULP_RV_DM_REGS_BASE 0x0u +#define PULP_RV_DM_REGS_SIZE sizeof(uint32_t) +#define PULP_RV_DM_MEM_BASE 0x100u +#define PULP_RV_DM_MEM_SIZE 0x700u +#define PULP_RV_DM_ROM_BASE 0x800u +#define PULP_RV_DM_ROM_SIZE 0x800u + +/* + * Special MemTxAttrs requester identifier so that debug module can identified + * a debugger-initiated request (vs. a regular hart-initiated request). + */ +/* Not sure how to define this at system level */ +#define PULP_RV_DM_REQUESTER_ID UINT16_MAX + +#endif /* HW_PULP_RV_DM_H */ diff --git a/include/hw/riscv/debug.h b/include/hw/riscv/debug.h new file mode 100644 index 0000000000000..67925c2bd87c4 --- /dev/null +++ b/include/hw/riscv/debug.h @@ -0,0 +1,77 @@ +/* + * QEMU RISC-V Debug + * + * Copyright (c) 2023-2024 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_RISCV_DEBUG_H +#define HW_RISCV_DEBUG_H + +#include "qom/object.h" +#include "hw/qdev-core.h" +#include "hw/sysbus.h" + +#define RISCV_DEBUG_PREFIX "riscv-debug" + +#define TYPE_RISCV_DEBUG_DEVICE RISCV_DEBUG_PREFIX "_device" +OBJECT_DECLARE_TYPE(RISCVDebugDeviceState, RISCVDebugDeviceClass, + RISCV_DEBUG_DEVICE) + +typedef enum RISCVDebugResult { + RISCV_DEBUG_NOERR, /* Previous operation completed successfully */ + RISCV_DEBUG_RSV, /* Reserved value, eq. to FAILED */ + RISCV_DEBUG_FAILED, /* Previous operation failed */ + RISCV_DEBUG_BUSY, /* New op. while a DMI request is still in progress */ +} RISCVDebugResult; + +/* Debug Module Interface access */ +struct RISCVDebugDeviceClass { + DeviceClass parent_class; + + /* + * Debugger request to write to address. + */ + RISCVDebugResult (*write_rq)(RISCVDebugDeviceState *dev, uint32_t addr, + uint32_t value); + + /* + * Debugger request to read from address. + */ + RISCVDebugResult (*read_rq)(RISCVDebugDeviceState *dev, uint32_t addr); + + /* + * Read back value. + */ + uint32_t (*read_value)(RISCVDebugDeviceState *dev); + + /* + * Set next DM address + */ + void (*set_next_dm)(RISCVDebugDeviceState *dev, uint32_t addr); +}; + +struct RISCVDebugDeviceState { + DeviceState parent_obj; +}; + +#endif /* HW_RISCV_DEBUG_H */ diff --git a/include/hw/riscv/dm.h b/include/hw/riscv/dm.h new file mode 100644 index 0000000000000..845697fff5113 --- /dev/null +++ b/include/hw/riscv/dm.h @@ -0,0 +1,62 @@ +/* + * QEMU RISC-V Debug Module + * + * Copyright (c) 2022-2025 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_RISCV_DM_H +#define HW_RISCV_DM_H + +#include "qom/object.h" +#include "exec/memattrs.h" + +#define TYPE_RISCV_DM "riscv-dm" +OBJECT_DECLARE_TYPE(RISCVDMState, RISCVDMClass, RISCV_DM) + +#define RISCV_DM_ACK_LINES TYPE_RISCV_DM ".ack" + +/* + * Note: these offsets depends on the debug module implementation, so they + * should be better defined as yet another configurable properties + */ +#define RISCV_DM_HALTED_OFFSET 0x100u +#define RISCV_DM_GOING_OFFSET 0x104u +#define RISCV_DM_RESUMING_OFFSET 0x108u +#define RISCV_DM_EXCEPTION_OFFSET 0x10cu +#define RISCV_DM_FLAGS_OFFSET 0x400u + +enum RISCVDMAckInterface { + ACK_HALTED, /* HartID of the hart that has been halted */ + ACK_GOING, /* A hart has started execution */ + ACK_RESUMING, /* HardID of the hart that has resumed non-debug op. */ + ACK_EXCEPTION, /* An exception has occured in debug mode */ + ACK_COUNT, /* Count of acknownledgement lines */ +}; + +/* Simple helper to define MemTxAttrs properties as uint64_t values */ +typedef union { + MemTxAttrs attrs; + uint64_t value; +} RISCVDMMemAttrs; + +#endif /* HW_RISCV_DM_H */ diff --git a/include/hw/riscv/dtm.h b/include/hw/riscv/dtm.h new file mode 100644 index 0000000000000..35d53e5db356f --- /dev/null +++ b/include/hw/riscv/dtm.h @@ -0,0 +1,66 @@ +/* + * QEMU RISC-V Debug Tranport Module + * + * Copyright (c) 2022-2025 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_RISCV_DTM_H +#define HW_RISCV_DTM_H + +#include "qom/object.h" +#include "exec/hwaddr.h" +#include "hw/resettable.h" +#include "hw/riscv/debug.h" + +#define TYPE_RISCV_DTM "riscv.dtm" +OBJECT_DECLARE_TYPE(RISCVDTMState, RISCVDTMClass, RISCV_DTM) + +struct RISCVDTMClass { + DeviceClass parent_class; + ResettablePhases parent_phases; + + /* + * Register a debug module on the Debug Transport Module. + * It is valid to register the same module multiple time, as long as + * base_addr and size are not modified. + * + * @dev the DTM instance + * @dmif the DM to register + * @base_addr the address of the first DM register + * @size the count of DM registers + * @enable whether the DM should be immediately enabled or not + * @return @c true if DTM is enabled, @c false otherwise + */ + bool (*register_dm)(DeviceState *dev, RISCVDebugDeviceState *dbgdev, + hwaddr base_addr, hwaddr size, bool enable); + + /* + * Change the activation state of an already registered Debug Module. + * When disabled, the Debug Module can no longer be accessed from the DTM, + * and is removed from the "next_dm" chain. + */ + void (*enable_dm)(DeviceState *dev, RISCVDebugDeviceState *dbgdev, + bool enable); +}; + +#endif /* HW_RISCV_DTM_H */ diff --git a/system/cpus.c b/system/cpus.c index ef2d2f241faaa..b574a03d135e0 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -330,7 +330,7 @@ int vm_shutdown(void) bool cpu_can_run(CPUState *cpu) { - if (cpu->stop) { + if (cpu->stop || unlikely(cpu->disabled)) { return false; } if (cpu_is_stopped(cpu)) { @@ -353,9 +353,12 @@ void cpu_handle_guest_debug(CPUState *cpu) cpu_single_step(cpu, 0); } } else { - gdb_set_stop_cpu(cpu); - qemu_system_debug_request(); - cpu->stopped = true; + CPUClass *cc = CPU_GET_CLASS(cpu); + if (!cc->debug_request || !cc->debug_request(cpu)) { + gdb_set_stop_cpu(cpu); + qemu_system_debug_request(); + cpu->stopped = true; + } } } @@ -916,4 +919,3 @@ void qmp_inject_nmi(Error **errp) { nmi_monitor_handle(monitor_get_cpu_index(monitor_cur()), errp); } - diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 9673448be7b7b..1e52f8e8d8897 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -676,7 +676,8 @@ bool riscv_cpu_has_work(CPUState *cs) */ return riscv_cpu_all_pending(env) != 0 || riscv_cpu_sirq_pending(env) != RISCV_EXCP_NONE || - riscv_cpu_vsirq_pending(env) != RISCV_EXCP_NONE; + riscv_cpu_vsirq_pending(env) != RISCV_EXCP_NONE || + env->debug_cs; } #endif /* !CONFIG_USER_ONLY */ @@ -961,6 +962,56 @@ void riscv_cpu_finalize_features(RISCVCPU *cpu, Error **errp) } } +static bool riscv_cpu_debug_request(CPUState *cs) +{ +#ifndef CONFIG_USER_ONLY + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + + if (!env->debug_dm) { + return false; + } + + if (!get_field(env->dcsr, DCSR_STEP)) { + return false; + } + + if (!env->debugger) { + env->dcsr = set_field(env->dcsr, DCSR_CAUSE, DCSR_CAUSE_STEP); + env->dcsr = set_field(env->dcsr, DCSR_PRV, env->priv); + env->dpc = env->pc; + env->debugger = true; + env->priv = PRV_M; + env->pc = env->dmhaltvec; + cs->singlestep_enabled = 0; + cs->exception_index = -1; + } + + return true; +#else + return false; +#endif +} + +static void riscv_cpu_debug_enable_singlestep(CPUState *s, vaddr pc) +{ + (void)pc; + +#ifndef CONFIG_USER_ONLY + RISCVCPU *cpu = RISCV_CPU(s); + CPURISCVState *env = &cpu->env; + if (!env->debug_dm) { + return; + } + s->singlestep_enabled = SSTEP_ENABLE | SSTEP_NOIRQ; + if (get_field(env->dcsr, DCSR_STOPTIME)) { + s->singlestep_enabled |= SSTEP_NOTIMER; + } +#else + s->singlestep_enabled = SSTEP_ENABLE; +#endif +} + static void riscv_cpu_realize(DeviceState *dev, Error **errp) { CPUState *cs = CPU(dev); @@ -2706,6 +2757,8 @@ static const Property riscv_cpu_properties[] = { qdev_prop_uint8, uint8_t), DEFINE_PROP_ARRAY("pmp_addr", RISCVCPU, cfg.pmp_addr_count, cfg.pmp_addr, qdev_prop_uint64, uint64_t), + DEFINE_PROP_UINT64("dmhaltvec", RISCVCPU, env.dmhaltvec, 0), + DEFINE_PROP_UINT64("dmexcpvec", RISCVCPU, env.dmexcpvec, 0), DEFINE_PROP_UINT64("rnmi-interrupt-vector", RISCVCPU, env.rnmi_irqvec, DEFAULT_RNMI_IRQVEC), DEFINE_PROP_UINT64("rnmi-exception-vector", RISCVCPU, env.rnmi_excpvec, @@ -2782,6 +2835,8 @@ static void riscv_cpu_common_class_init(ObjectClass *c, const void *data) cc->gdb_read_register = riscv_cpu_gdb_read_register; cc->gdb_write_register = riscv_cpu_gdb_write_register; cc->gdb_stop_before_watchpoint = true; + cc->debug_request = riscv_cpu_debug_request; + cc->debug_enable_singlestep = riscv_cpu_debug_enable_singlestep; cc->disas_set_info = riscv_cpu_disas_set_info; #ifndef CONFIG_USER_ONLY cc->sysemu_ops = &riscv_sysemu_ops; diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 1ca9fbe410525..53d7739da98c7 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -464,6 +464,11 @@ struct CPUArchState { int64_t last_icount; bool itrigger_enabled; + /* debug module */ + uint32_t dcsr; + target_ulong dpc; + target_ulong dscratch[RV_MAX_DSCRATCH]; + /* machine specific rdtime callback */ uint64_t (*rdtime_fn)(void *); void *rdtime_fn_arg; @@ -484,8 +489,15 @@ struct CPUArchState { target_ulong *val, target_ulong new_val, target_ulong write_mask); void *aia_ireg_rmw_fn_arg[4]; - /* True if in debugger mode. */ - bool debugger; + /* + * Debug support + */ + bool debugger; /* True if in debugger mode. */ + bool debug_cs; /* Debugger critical section w/o HW IRQ nor WFI */ + bool debug_dm; /* Debug module is available */ + unsigned debug_cause; /* Reason for entering debug */ + uint64_t dmhaltvec; /* Address of halt handler */ + uint64_t dmexcpvec; /* Address of exception handler */ uint64_t mstateen[SMSTATEEN_MAX_COUNT]; uint64_t hstateen[SMSTATEEN_MAX_COUNT]; @@ -657,6 +669,7 @@ void riscv_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr, MMUAccessType access_type, int mmu_idx, MemTxAttrs attrs, MemTxResult response, uintptr_t retaddr); +void riscv_cpu_store_debug_cause(CPUState *cs, unsigned cause); hwaddr riscv_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr); bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request); void riscv_cpu_swap_hypervisor_regs(CPURISCVState *env); diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index b62dd82fe7c0c..70ac21acd57f4 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -455,7 +455,7 @@ #define CSR_MNCAUSE 0x742 #define CSR_MNSTATUS 0x744 -/* Debug/Trace Registers (shared with Debug Mode) */ +/* Trace Registers (shared with Debug Mode) */ #define CSR_TSELECT 0x7a0 #define CSR_TDATA1 0x7a1 #define CSR_TDATA2 0x7a2 @@ -466,7 +466,8 @@ /* Debug Mode Registers */ #define CSR_DCSR 0x7b0 #define CSR_DPC 0x7b1 -#define CSR_DSCRATCH 0x7b2 +#define CSR_DSCRATCH0 0x7b2 +#define CSR_DSCRATCH1 0x7b3 /* Performance Counters */ #define CSR_MHPMCOUNTER3 0xb03 @@ -693,6 +694,29 @@ typedef enum { /* vsstatus CSR bits */ #define VSSTATUS64_UXL 0x0000000300000000ULL +/* DCSR CSR bits */ +#define DCSR_PRV 0x00000003 +#define DCSR_STEP 0x00000004 +#define DCSR_NMIP 0x00000008 +#define DCSR_MPRVEN 0x00000010 +#define DCSR_CAUSE 0x000001C0 +#define DCSR_STOPTIME 0x00000200 +#define DCSR_STOPCOUNT 0x00000400 +#define DCSR_STEPIE 0x00000800 +#define DCSR_EBREAKU 0x00001000 +#define DCSR_EBREAKS 0x00002000 +#define DCSR_EBREAKH 0x00004000 +#define DCSR_EBREAKM 0x00008000 +#define DCSR_XDEBUGVER 0xF0000000 +#define DCSR_EBREAK_MASK 0x0000F000 + +#define DCSR_CAUSE_NONE 0 +#define DCSR_CAUSE_EBREAK 1 +#define DCSR_CAUSE_BREAKPOINT 2 +#define DCSR_CAUSE_HALTREQ 3 +#define DCSR_CAUSE_STEP 4 +#define DCSR_CAUSE_RESETHALTREQ 5 + /* Privilege modes */ #define PRV_U 0 #define PRV_S 1 diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 279db790c6dc1..69d73ac59d0f0 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -51,7 +51,9 @@ int riscv_env_mmu_index(CPURISCVState *env, bool ifetch) uint64_t status = env->mstatus; if (mode == PRV_M && get_field(status, MSTATUS_MPRV)) { - mode = get_field(env->mstatus, MSTATUS_MPP); + if (!env->debugger || get_field(env->dcsr, DCSR_MPRVEN)) { + mode = get_field(env->mstatus, MSTATUS_MPP); + } virt = get_field(env->mstatus, MSTATUS_MPV) && (mode != PRV_M); if (virt) { @@ -551,6 +553,10 @@ bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request) if (interrupt_request & mask) { RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; + if (unlikely(env->debug_dm && + (env->debugger || get_field(env->dcsr, DCSR_STEP)))) { + return false; + } int interruptno = riscv_cpu_local_irq_pending(env); if (interruptno >= 0) { cs->exception_index = RISCV_EXCP_INT_FLAG | interruptno; @@ -1658,6 +1664,94 @@ static void raise_mmu_exception(CPURISCVState *env, target_ulong address, env->two_stage_indirect_lookup = two_stage_indirect; } +static bool riscv_cpu_handle_breakpoint(CPUState *cs) +{ + /* + * EBREAK/C.BREAK causes the receiving privilege mode’s epc register to be + * set to the address of the EBREAK instruction itself, not the address of + * the following instruction. As ECALL and EBREAK cause synchronous + * exceptions, they are not considered to retire, and should not increment + * the minstret CSR. + + * Instruction address breakpoints have the same cause value as, but + * different priority than, data address breakpoints (a.k.a. watchpoints) + * and environment break exceptions (which are raised by the EBREAK + * instruction). + + * DCSR ebreakX fields: + * 0: ebreak instructions in X-mode behave as described in the Priv. Spec. + * a. the core enters the exception handler routine located at mtvec + * (Debug Mode is not entered) + * b. mepc and mcause are updated + * 1: ebreak instructions in X-mode enter Debug Mode. + * a. the core enters Debug Mode and starts executing debug code located + * at dmhaltvec (exception routine not called) + * b. dpc and dcsr are updated + */ + CPURISCVState *env = &RISCV_CPU(cs)->env; + target_ulong ebreak = get_field(env->dcsr, DCSR_EBREAK_MASK); + if (!(ebreak & (1u << env->priv)) && !env->debugger) { + /* breakpoint not handled, fall back to default exception handling */ + return false; + } + + /* clear current exception */ + cs->exception_index = -1; + cs->interrupt_request |= CPU_INTERRUPT_DEBUG; + /* the current exception has been handled */ + return true; +} + +static bool riscv_cpu_handle_debug_exception(CPUState *cs, target_ulong cause) +{ + /* + * Note: RISCV_EXCP_BREAKPOINT && RISCV_EXCP_SEMIHOST already handled + * + * Exceptions don’t update any registers. That includes cause, epc, tval, + * dpc, and mstatus. + * To resume execution, the debug module sets a flag which causes the hart + * to execute a dret. When dret is executed, pc is restored from dpc and + * normal execution resumes at the privilege set by prv. + */ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + + if (env->debug_dm) { + env->pc = env->dmexcpvec; + /* clear current exception */ + cs->exception_index = -1; + return true; + } + + return false; +} + +void riscv_cpu_store_debug_cause(CPUState *cs, unsigned cause) +{ + /* + * Priorities from debug spec 0.13.2 + * "4.8.1 Debug Control and Status (dcsr, at 0x7b0)" + */ + static const uint8_t debug_cause_priority[] = { + [DCSR_CAUSE_NONE] = 0, + [DCSR_CAUSE_EBREAK] = 3, + [DCSR_CAUSE_BREAKPOINT] = 4, + [DCSR_CAUSE_HALTREQ] = 1, + [DCSR_CAUSE_STEP] = 0, + [DCSR_CAUSE_RESETHALTREQ] = 2, + }; + + CPURISCVState *env = &RISCV_CPU((cs))->env; + + uint8_t new_prio = cause < ARRAY_SIZE(debug_cause_priority) ? + debug_cause_priority[cause] : 0u; + uint8_t cur_prio = debug_cause_priority[env->debug_cause]; + + if ((env->debug_cause == DCSR_CAUSE_NONE) || (new_prio > cur_prio)) { + env->debug_cause = cause; + } +} + hwaddr riscv_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) { RISCVCPU *cpu = RISCV_CPU(cs); @@ -2259,6 +2353,11 @@ void riscv_cpu_do_interrupt(CPUState *cs) tval = env->bins; break; case RISCV_EXCP_BREAKPOINT: + if (env->debug_dm) { + if (riscv_cpu_handle_breakpoint(cs)) { + return; + } + } tval = env->badaddr; if (cs->watchpoint_hit) { tval = cs->watchpoint_hit->hitaddr; @@ -2285,6 +2384,12 @@ void riscv_cpu_do_interrupt(CPUState *cs) cause = RISCV_EXCP_U_ECALL; } } + + if (env->debug_dm && env->debugger) { + if (riscv_cpu_handle_debug_exception(cs, cause)) { + return; + } + } } trace_riscv_trap(env->mhartid, async, cause, env->pc, tval, diff --git a/target/riscv/csr.c b/target/riscv/csr.c index e8c2fbfd5612c..be147d1eef034 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -5436,6 +5436,58 @@ static RISCVException write_mcontext(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +static RISCVException read_dcsr(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->dcsr; + return RISCV_EXCP_NONE; +} + +static RISCVException write_dcsr(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + /* STEPIE feature is not supported */ + target_ulong wmask = DCSR_PRV | DCSR_STEP | DCSR_STOPCOUNT | DCSR_STOPTIME | + DCSR_EBREAKM; + if (riscv_has_ext(env, RVS)) { + wmask |= DCSR_EBREAKS; + } + if (riscv_has_ext(env, RVU)) { + wmask |= DCSR_EBREAKU; + } + env->dcsr &= ~wmask; + env->dcsr |= val & wmask; + return RISCV_EXCP_NONE; +} + +static RISCVException read_dpc(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->dpc; + return RISCV_EXCP_NONE; +} + +static RISCVException write_dpc(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + env->dpc = val; + return RISCV_EXCP_NONE; +} + +static RISCVException read_dscratch(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->dscratch[csrno - CSR_DSCRATCH0]; + return RISCV_EXCP_NONE; +} + +static RISCVException write_dscratch(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + env->dscratch[csrno - CSR_DSCRATCH0] = val; + return RISCV_EXCP_NONE; +} + static RISCVException read_mnscratch(CPURISCVState *env, int csrno, target_ulong *val) { @@ -5800,7 +5852,7 @@ RISCVException riscv_csrrw_i128(CPURISCVState *env, int csrno, /* * Debugger support. If not in user mode, set env->debugger before the - * riscv_csrrw call and clear it after the call. + * riscv_csrrw call and restore it after the call. */ RISCVException riscv_csrrw_debug(CPURISCVState *env, int csrno, target_ulong *ret_value, @@ -5809,6 +5861,7 @@ RISCVException riscv_csrrw_debug(CPURISCVState *env, int csrno, { RISCVException ret; #if !defined(CONFIG_USER_ONLY) + bool debugger = env->debugger; env->debugger = true; #endif if (!write_mask) { @@ -5817,7 +5870,7 @@ RISCVException riscv_csrrw_debug(CPURISCVState *env, int csrno, ret = riscv_csrrw(env, csrno, ret_value, new_value, write_mask, 0); } #if !defined(CONFIG_USER_ONLY) - env->debugger = false; + env->debugger = debugger; #endif return ret; } @@ -6343,7 +6396,7 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_PMPADDR63] = { "pmpaddr63", pmp, read_pmpaddr, write_pmpaddr, .min_priv_ver = PRIV_VERSION_1_12_0 }, - /* Debug CSRs */ + /* Trigger CSRs */ [CSR_TSELECT] = { "tselect", debug, read_tselect, write_tselect }, [CSR_TDATA1] = { "tdata1", debug, read_tdata, write_tdata }, [CSR_TDATA2] = { "tdata2", debug, read_tdata, write_tdata }, @@ -6351,6 +6404,13 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_TINFO] = { "tinfo", debug, read_tinfo, write_ignore }, [CSR_MCONTEXT] = { "mcontext", debug, read_mcontext, write_mcontext }, + /* Debug CSRs */ + [CSR_DCSR] = { "dcsr", debug, read_dcsr, write_dcsr }, + [CSR_DPC] = { "dpc", debug, read_dpc, write_dpc }, + [CSR_DSCRATCH0] = { "dscratch0", debug, read_dscratch, write_dscratch }, + [CSR_DSCRATCH1] = { "dscratch1", debug, read_dscratch, write_dscratch }, + + /* Control Transfer Records */ [CSR_MCTRCTL] = { "mctrctl", ctr_mmode, NULL, NULL, rmw_xctrctl }, [CSR_SCTRCTL] = { "sctrctl", ctr_smode, NULL, NULL, rmw_xctrctl }, [CSR_VSCTRCTL] = { "vsctrctl", ctr_smode, NULL, NULL, rmw_xctrctl }, diff --git a/target/riscv/debug.c b/target/riscv/debug.c index 56644667497c3..38364f4de51c7 100644 --- a/target/riscv/debug.c +++ b/target/riscv/debug.c @@ -31,6 +31,7 @@ #include "exec/helper-proto.h" #include "exec/watchpoint.h" #include "system/cpu-timers.h" +#include "system/cpus.h" #include "exec/icount.h" /* @@ -113,13 +114,13 @@ static trigger_action_t get_trigger_action(CPURISCVState *env, case TRIGGER_TYPE_INT: case TRIGGER_TYPE_EXCP: case TRIGGER_TYPE_EXT_SRC: - qemu_log_mask(LOG_UNIMP, "trigger type: %d is not supported\n", - trigger_type); + qemu_log_mask(LOG_UNIMP, "%s: trigger type: %d is not supported\n", + __func__, trigger_type); break; case TRIGGER_TYPE_NO_EXIST: case TRIGGER_TYPE_UNAVAIL: - qemu_log_mask(LOG_GUEST_ERROR, "trigger type: %d does not exit\n", - trigger_type); + qemu_log_mask(LOG_GUEST_ERROR, "%s: trigger type: %d does not exit\n", + __func__, trigger_type); break; default: g_assert_not_reached(); @@ -176,22 +177,24 @@ void tselect_csr_write(CPURISCVState *env, target_ulong val) } } -static target_ulong tdata1_validate(CPURISCVState *env, target_ulong val, - trigger_type_t t) +static target_ulong tdata1_validate(CPURISCVState *env, target_ulong index, + target_ulong val, trigger_type_t t) { - uint32_t type, dmode; + uint32_t type, dmode, cdmode; target_ulong tdata1; switch (riscv_cpu_mxl(env)) { case MXL_RV32: type = extract32(val, 28, 4); dmode = extract32(val, 27, 1); + cdmode = extract32(env->tdata1[index], 27, 1); tdata1 = RV32_TYPE(t); break; case MXL_RV64: case MXL_RV128: type = extract64(val, 60, 4); dmode = extract64(val, 59, 1); + cdmode = extract64(env->tdata1[index], 59, 1); tdata1 = RV64_TYPE(t); break; default: @@ -200,11 +203,15 @@ static target_ulong tdata1_validate(CPURISCVState *env, target_ulong val, if (type != t) { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring type write to tdata1 register\n"); + "%s: ignoring type write to tdata1 register\n", __func__); } - if (dmode != 0) { - qemu_log_mask(LOG_UNIMP, "debug mode is not supported\n"); + if (!(env->debug_dm && env->debugger)) { + if (dmode != cdmode) { + qemu_log_mask(LOG_UNIMP, + "%s: debug mode can only be changed from debugger\n", + __func__); + } } return tdata1; @@ -214,7 +221,7 @@ static inline void warn_always_zero_bit(target_ulong val, target_ulong mask, const char *msg) { if (val & mask) { - qemu_log_mask(LOG_UNIMP, "%s bit is always zero\n", msg); + qemu_log_mask(LOG_UNIMP, "%s: %s bit is always zero\n", __func__, msg); } } @@ -278,24 +285,31 @@ static target_ulong textra_validate(CPURISCVState *env, target_ulong tdata3) return textra; } -static void do_trigger_action(CPURISCVState *env, target_ulong trigger_index) +static void do_trigger_action(CPURISCVState *env, trigger_action_t action) { - trigger_action_t action = get_trigger_action(env, trigger_index); - switch (action) { case DBG_ACTION_NONE: break; case DBG_ACTION_BP: + riscv_cpu_store_debug_cause(env_cpu(env), DCSR_CAUSE_EBREAK); riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0); break; case DBG_ACTION_DBG_MODE: + if (!env->debug_dm) { + cpu_handle_guest_debug(env_cpu(env)); + } else { + riscv_cpu_store_debug_cause(env_cpu(env), DCSR_CAUSE_BREAKPOINT); + riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0); + } + break; case DBG_ACTION_TRACE0: case DBG_ACTION_TRACE1: case DBG_ACTION_TRACE2: case DBG_ACTION_TRACE3: case DBG_ACTION_EXT_DBG0: case DBG_ACTION_EXT_DBG1: - qemu_log_mask(LOG_UNIMP, "action: %d is not supported\n", action); + qemu_log_mask(LOG_UNIMP, "%s: action: %d is not supported\n", + __func__, action); break; default: g_assert_not_reached(); @@ -437,27 +451,30 @@ static inline bool type2_breakpoint_enabled(target_ulong ctrl) } static target_ulong type2_mcontrol_validate(CPURISCVState *env, + target_ulong index, target_ulong ctrl) { target_ulong val; uint32_t size; /* validate the generic part first */ - val = tdata1_validate(env, ctrl, TRIGGER_TYPE_AD_MATCH); + val = tdata1_validate(env, index, ctrl, TRIGGER_TYPE_AD_MATCH); /* validate unimplemented (always zero) bits */ warn_always_zero_bit(ctrl, TYPE2_MATCH, "match"); warn_always_zero_bit(ctrl, TYPE2_CHAIN, "chain"); - warn_always_zero_bit(ctrl, TYPE2_ACTION, "action"); warn_always_zero_bit(ctrl, TYPE2_TIMING, "timing"); warn_always_zero_bit(ctrl, TYPE2_SELECT, "select"); warn_always_zero_bit(ctrl, TYPE2_HIT, "hit"); + warn_always_zero_bit(ctrl, !env->debug_dm ? TYPE2_ACTION : TYPE2_ACT_TRACE, + "action"); /* validate size encoding */ size = type2_breakpoint_size(env, ctrl); if (access_size[size] == -1) { - qemu_log_mask(LOG_UNIMP, "access size %d is not supported, using " - "SIZE_ANY\n", size); + qemu_log_mask(LOG_UNIMP, + "%s: access size %d is not supported, using SIZE_ANY\n", + __func__, size); } else { val |= (ctrl & TYPE2_SIZELO); if (riscv_cpu_mxl(env) == MXL_RV64) { @@ -466,8 +483,23 @@ static target_ulong type2_mcontrol_validate(CPURISCVState *env, } /* keep the mode and attribute bits */ - val |= (ctrl & (TYPE2_U | TYPE2_S | TYPE2_M | - TYPE2_LOAD | TYPE2_STORE | TYPE2_EXEC)); + target_ulong mask = (TYPE2_U | TYPE2_S | TYPE2_M | + TYPE2_LOAD | TYPE2_STORE | TYPE2_EXEC); + if (env->debug_dm && env->debugger) { + mask |= TYPE2_ACT_DEBUG; + switch (riscv_cpu_mxl(env)) { + case MXL_RV32: + mask |= RV32_DMODE; + break; + case MXL_RV64: + case MXL_RV128: + mask |= RV64_DMODE; + break; + default: + g_assert_not_reached(); + } + } + val |= ctrl & mask; return val; } @@ -532,7 +564,7 @@ static void type2_reg_write(CPURISCVState *env, target_ulong index, switch (tdata_index) { case TDATA1: - new_val = type2_mcontrol_validate(env, val); + new_val = type2_mcontrol_validate(env, index, val); if (new_val != env->tdata1[index]) { env->tdata1[index] = new_val; type2_breakpoint_remove(env, index); @@ -565,13 +597,14 @@ static inline bool type6_breakpoint_enabled(target_ulong ctrl) } static target_ulong type6_mcontrol6_validate(CPURISCVState *env, + target_ulong index, target_ulong ctrl) { target_ulong val; uint32_t size; /* validate the generic part first */ - val = tdata1_validate(env, ctrl, TRIGGER_TYPE_AD_MATCH6); + val = tdata1_validate(env, index, ctrl, TRIGGER_TYPE_AD_MATCH6); /* validate unimplemented (always zero) bits */ warn_always_zero_bit(ctrl, TYPE6_MATCH, "match"); @@ -584,8 +617,9 @@ static target_ulong type6_mcontrol6_validate(CPURISCVState *env, /* validate size encoding */ size = extract32(ctrl, 16, 4); if (access_size[size] == -1) { - qemu_log_mask(LOG_UNIMP, "access size %d is not supported, using " - "SIZE_ANY\n", size); + qemu_log_mask(LOG_UNIMP, + "%s: access size %d is not supported, using SIZE_ANY\n", + __func__, size); } else { val |= (ctrl & TYPE6_SIZE); } @@ -646,7 +680,7 @@ static void type6_reg_write(CPURISCVState *env, target_ulong index, switch (tdata_index) { case TDATA1: - new_val = type6_mcontrol6_validate(env, val); + new_val = type6_mcontrol6_validate(env, index, val); if (new_val != env->tdata1[index]) { env->tdata1[index] = new_val; type6_breakpoint_remove(env, index); @@ -734,7 +768,7 @@ void helper_itrigger_match(CPURISCVState *env) itrigger_set_count(env, i, count--); if (!count) { env->itrigger_enabled = riscv_itrigger_enabled(env); - do_trigger_action(env, i); + do_trigger_action(env, get_trigger_action(env, i)); } } } @@ -771,7 +805,7 @@ static void riscv_itrigger_update_count(CPURISCVState *env) executed = current_icount - last_icount; itrigger_set_count(env, i, count - executed); if (count == executed) { - do_trigger_action(env, i); + do_trigger_action(env, get_trigger_action(env, i)); } } else { /* @@ -795,13 +829,13 @@ void riscv_itrigger_update_priv(CPURISCVState *env) riscv_itrigger_update_count(env); } -static target_ulong itrigger_validate(CPURISCVState *env, +static target_ulong itrigger_validate(CPURISCVState *env, target_ulong index, target_ulong ctrl) { target_ulong val; /* validate the generic part first */ - val = tdata1_validate(env, ctrl, TRIGGER_TYPE_INST_CNT); + val = tdata1_validate(env, index, ctrl, TRIGGER_TYPE_INST_CNT); /* validate unimplemented (always zero) bits */ warn_always_zero_bit(ctrl, ITRIGGER_ACTION, "action"); @@ -823,7 +857,7 @@ static void itrigger_reg_write(CPURISCVState *env, target_ulong index, switch (tdata_index) { case TDATA1: /* set timer for icount */ - new_val = itrigger_validate(env, val); + new_val = itrigger_validate(env, index, val); if (new_val != env->tdata1[index]) { env->tdata1[index] = new_val; if (icount_enabled()) { @@ -838,7 +872,8 @@ static void itrigger_reg_write(CPURISCVState *env, target_ulong index, break; case TDATA2: qemu_log_mask(LOG_UNIMP, - "tdata2 is not supported for icount trigger\n"); + "%s: tdata2 is not supported for icount trigger\n", + __func__); break; case TDATA3: env->tdata3[index] = textra_validate(env, val); @@ -858,6 +893,35 @@ static int itrigger_get_adjust_count(CPURISCVState *env) return count; } +static void tdata1_clear(CPURISCVState *env) +{ + target_ulong tselect = env->trigger_cur; + CPUState *cs = env_cpu(env); + if (cs->watchpoint_hit && + cs->watchpoint_hit == env->cpu_watchpoint[tselect]) { + cs->watchpoint_hit = NULL; + } + int old_trig_type; + old_trig_type = extract_trigger_type(env, env->tdata1[tselect]); + switch (old_trig_type) { + case TRIGGER_TYPE_AD_MATCH: + type2_breakpoint_remove(env, tselect); + break; + case TRIGGER_TYPE_AD_MATCH6: + type6_breakpoint_remove(env, tselect); + break; + case TRIGGER_TYPE_NO_EXIST: + case TRIGGER_TYPE_UNAVAIL: + break; + default: + break; + } + /* only clear trigger config, not HW features */ + env->tdata1[tselect] &= ~(riscv_cpu_mxl(env) == MXL_RV32 ? + (1 << (32-12)) - 1 : (1ll << (64-12)) - 1ll); + +} + target_ulong tdata_csr_read(CPURISCVState *env, int tdata_index) { int trigger_type; @@ -902,13 +966,19 @@ void tdata_csr_write(CPURISCVState *env, int tdata_index, target_ulong val) case TRIGGER_TYPE_INT: case TRIGGER_TYPE_EXCP: case TRIGGER_TYPE_EXT_SRC: - qemu_log_mask(LOG_UNIMP, "trigger type: %d is not supported\n", - trigger_type); + qemu_log_mask(LOG_UNIMP, "%s: trigger type: %d is not supported\n", + __func__, trigger_type); break; case TRIGGER_TYPE_NO_EXIST: + if (!val && tdata_index == TDATA1) { + /* Debugger clears TDATA1 to temporarily disable BP/WP */ + tdata1_clear(env); + break; + } + /* fallthrough */ case TRIGGER_TYPE_UNAVAIL: - qemu_log_mask(LOG_GUEST_ERROR, "trigger type: %d does not exit\n", - trigger_type); + qemu_log_mask(LOG_GUEST_ERROR, "%s: trigger type: %d does not exit\n", + __func__, trigger_type); break; default: g_assert_not_reached(); @@ -922,18 +992,108 @@ target_ulong tinfo_csr_read(CPURISCVState *env) BIT(TRIGGER_TYPE_AD_MATCH6); } +static inline const CPUBreakpoint* cpu_breakpoint_check(CPUState *cs, vaddr pc, + int mask) +{ + CPUBreakpoint *bp; + + if (unlikely(!QTAILQ_EMPTY(&cs->breakpoints))) { + QTAILQ_FOREACH(bp, &cs->breakpoints, entry) { + if (bp->pc == pc && (bp->flags & mask)) { + return bp; + } + } + } + return NULL; +} + +static inline unsigned cpu_breakpoint_index(CPUState *cs, + const CPUBreakpoint* bp) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + + unsigned ix=0; + for (; ixcpu_breakpoint[ix]) { + break; + } + } + + return ix; +} + +static inline unsigned cpu_watchpoint_index(CPUState *cs, + const CPUWatchpoint* wp) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + + unsigned ix=0; + for (; ixcpu_watchpoint[ix]) { + break; + } + } + + return ix; +} + void riscv_cpu_debug_excp_handler(CPUState *cs) { RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; + if (env->debug_dm) { + switch (env->debug_cause) { + case DCSR_CAUSE_EBREAK: /* 1 */ + case DCSR_CAUSE_BREAKPOINT: /* 2 */ + case DCSR_CAUSE_HALTREQ: /* 3 */ + case DCSR_CAUSE_STEP: /* 4 */ + case DCSR_CAUSE_RESETHALTREQ: /* 5 */ + /* + * When ebreak is executed in Debug Mode, ebreak halts the hart + * again but without updating dpc or dcsr (otherwise the debugged + * program return address is lost) + */ + if (!env->debugger) { + env->dcsr = set_field(env->dcsr, DCSR_CAUSE, env->debug_cause); + env->dcsr = set_field(env->dcsr, DCSR_PRV, env->priv); + env->dpc = env->pc; + env->debugger = true; + } + env->priv = PRV_M; + env->pc = env->dmhaltvec; + env->debug_cause = DCSR_CAUSE_NONE; + /* + * clear out the exception, otherwise it would be handled over + * to the debug system which in turn stops the VM, while we need to + * execute the halt handler + */ + cs->exception_index = -1; + return; + case DCSR_CAUSE_NONE: /* 0 */ + default: + break; + } + } + if (cs->watchpoint_hit) { if (cs->watchpoint_hit->flags & BP_CPU) { - do_trigger_action(env, DBG_ACTION_BP); + unsigned trig_idx = cpu_watchpoint_index(cs, cs->watchpoint_hit); + trigger_action_t action; + action = (trig_idx < RV_MAX_TRIGGERS) ? + get_trigger_action(env, trig_idx) : DBG_ACTION_BP; + do_trigger_action(env, action); } } else { - if (cpu_breakpoint_test(cs, env->pc, BP_CPU)) { - do_trigger_action(env, DBG_ACTION_BP); + const CPUBreakpoint *bp = cpu_breakpoint_check(cs, env->pc, BP_CPU); + if (bp) { + unsigned trig_idx = cpu_breakpoint_index(cs, bp); + trigger_action_t action; + action = (trig_idx < RV_MAX_TRIGGERS) ? + get_trigger_action(env, trig_idx) : DBG_ACTION_BP; + do_trigger_action(env, action); } } } @@ -1056,7 +1216,8 @@ void riscv_trigger_realize(CPURISCVState *env) void riscv_trigger_reset_hold(CPURISCVState *env) { - target_ulong tdata1 = build_tdata1(env, TRIGGER_TYPE_AD_MATCH, 0, 0); + target_ulong tdata1 = build_tdata1(env, TRIGGER_TYPE_AD_MATCH, + env->debug_dm, 0); int i; /* init to type 2 triggers */ @@ -1079,7 +1240,9 @@ void riscv_trigger_reset_hold(CPURISCVState *env) env->tdata3[i] = 0; env->cpu_breakpoint[i] = NULL; env->cpu_watchpoint[i] = NULL; - timer_del(env->itrigger_timer[i]); + if (env->itrigger_timer[i]) { + timer_del(env->itrigger_timer[i]); + } } env->mcontext = 0; diff --git a/target/riscv/debug.h b/target/riscv/debug.h index f76b8f944a2ef..abffaffae5645 100644 --- a/target/riscv/debug.h +++ b/target/riscv/debug.h @@ -24,7 +24,8 @@ #include "exec/breakpoint.h" -#define RV_MAX_TRIGGERS 2 +#define RV_MAX_TRIGGERS 4 +#define RV_MAX_DSCRATCH 2 /* register index of tdata CSRs */ enum { @@ -81,6 +82,8 @@ typedef enum { #define TYPE2_MATCH (0xf << 7) #define TYPE2_CHAIN BIT(11) #define TYPE2_ACTION (0xf << 12) +#define TYPE2_ACT_DEBUG (0x1 << 12) +#define TYPE2_ACT_TRACE (0xe << 12) #define TYPE2_SIZELO (0x3 << 16) #define TYPE2_TIMING BIT(18) #define TYPE2_SELECT BIT(19) diff --git a/target/riscv/helper.h b/target/riscv/helper.h index b785456ee08d1..0cd1421926aca 100644 --- a/target/riscv/helper.h +++ b/target/riscv/helper.h @@ -132,6 +132,7 @@ DEF_HELPER_6(csrrw_i128, tl, env, int, tl, tl, tl, tl) DEF_HELPER_1(sret, tl, env) DEF_HELPER_1(mret, tl, env) DEF_HELPER_1(mnret, tl, env) +DEF_HELPER_1(dret, tl, env) DEF_HELPER_1(ctr_clear, void, env) DEF_HELPER_1(wfi, void, env) DEF_HELPER_1(wrs_nto, void, env) diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index cd23b1f3a9b82..318d330c57903 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -118,6 +118,7 @@ sctrclr 000100000100 00000 000 00000 1110011 uret 0000000 00010 00000 000 00000 1110011 sret 0001000 00010 00000 000 00000 1110011 mret 0011000 00010 00000 000 00000 1110011 +dret 0111101 10010 00000 000 00000 1110011 wfi 0001000 00101 00000 000 00000 1110011 sfence_vma 0001001 ..... ..... 000 00000 1110011 @sfence_vma diff --git a/target/riscv/insn_trans/trans_privileged.c.inc b/target/riscv/insn_trans/trans_privileged.c.inc index 8a62b4cfcd9f9..bbb738749d5fa 100644 --- a/target/riscv/insn_trans/trans_privileged.c.inc +++ b/target/riscv/insn_trans/trans_privileged.c.inc @@ -68,6 +68,9 @@ static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a) if (pre == 0x01f01013 && ebreak == 0x00100073 && post == 0x40705013) { generate_exception(ctx, RISCV_EXCP_SEMIHOST); } else { +#ifndef CONFIG_USER_ONLY + riscv_cpu_store_debug_cause(ctx->cs, DCSR_CAUSE_EBREAK); +#endif tcg_gen_st_tl(tcg_constant_tl(ebreak_addr), tcg_env, offsetof(CPURISCVState, badaddr)); generate_exception(ctx, RISCV_EXCP_BREAKPOINT); @@ -139,6 +142,20 @@ static bool trans_mnret(DisasContext *ctx, arg_mnret *a) #endif } +static bool trans_dret(DisasContext *ctx, arg_dret *a) +{ +#ifndef CONFIG_USER_ONLY + decode_save_opc(ctx, 0); + translator_io_start(&ctx->base); + gen_helper_dret(cpu_pc, tcg_env); + exit_tb(ctx); /* no chaining */ + ctx->base.is_jmp = DISAS_NORETURN; + return true; +#else + return false; +#endif +} + static bool trans_wfi(DisasContext *ctx, arg_wfi *a) { #ifndef CONFIG_USER_ONLY diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c index 6ccc127c30431..275e6687cf49f 100644 --- a/target/riscv/op_helper.c +++ b/target/riscv/op_helper.c @@ -26,6 +26,7 @@ #include "accel/tcg/probe.h" #include "exec/helper-proto.h" #include "exec/tlb-flags.h" +#include "exec/translation-block.h" #include "trace.h" #ifndef CONFIG_USER_ONLY @@ -502,6 +503,43 @@ target_ulong helper_mnret(CPURISCVState *env) return retpc; } +/* need access to internal TCG API */ +extern uint32_t curr_cflags(CPUState *cpu); + +target_ulong helper_dret(CPURISCVState *env) +{ + if (!(env->priv >= PRV_M) || !(env->debugger)) { + riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); + } + + target_ulong retpc = env->dpc; + if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) { + riscv_raise_exception(env, RISCV_EXCP_INST_ADDR_MIS, GETPC()); + } + + /* there is likely more to do here */ + target_ulong prev_priv = get_field(env->dcsr, DCSR_PRV); + target_ulong prev_virt = env->virt_enabled; + + if (riscv_has_ext(env, RVH) && prev_virt) { + riscv_cpu_swap_hypervisor_regs(env); + } + riscv_cpu_set_mode(env, prev_priv, prev_virt); + + CPUState *cs = env_cpu(env); + if (get_field(env->dcsr, DCSR_STEP)) { + cs->cflags_next_tb = curr_cflags(cs) | CF_NO_GOTO_TB | + CF_NO_GOTO_PTR | CF_SINGLE_STEP | 1; + } else { + cs->singlestep_enabled = 0; + } + + env->debugger = false; + env->debug_cause = DCSR_CAUSE_NONE; + + return retpc; +} + void helper_ctr_add_entry(CPURISCVState *env, target_ulong src, target_ulong dest, target_ulong type) { @@ -552,9 +590,11 @@ void helper_wfi(CPURISCVState *env) (prv_u || (prv_s && get_field(env->hstatus, HSTATUS_VTW)))) { riscv_raise_exception(env, RISCV_EXCP_VIRT_INSTRUCTION_FAULT, GETPC()); } else { - cs->halted = 1; - cs->exception_index = EXCP_HLT; - cpu_loop_exit(cs); + if (!unlikely(env->debugger || env->debug_cs)) { + cs->halted = 1; + cs->exception_index = EXCP_HLT; + cpu_loop_exit(cs); + } } } diff --git a/target/riscv/pmu.c b/target/riscv/pmu.c index a68809eef32b4..7a23636026f60 100644 --- a/target/riscv/pmu.c +++ b/target/riscv/pmu.c @@ -282,6 +282,9 @@ int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx) if (!cpu->cfg.pmu_mask) { return 0; } + if (get_field(env->dcsr, DCSR_STOPCOUNT)) { + return 0; + } value = g_hash_table_lookup(cpu->pmu_event_ctr_map, GUINT_TO_POINTER(event_idx)); if (!value) { diff --git a/target/riscv/translate.c b/target/riscv/translate.c index e1f4dc5ffd04a..82d24ea421fda 100644 --- a/target/riscv/translate.c +++ b/target/riscv/translate.c @@ -121,6 +121,8 @@ typedef struct DisasContext { bool bcfi_enabled; } DisasContext; +#define DISAS_SSTEP DISAS_TARGET_0 + static inline bool has_ext(DisasContext *ctx, uint32_t ext) { return ctx->misa_ext & ext; @@ -1363,6 +1365,9 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) decode_opc(env, ctx); ctx->base.pc_next += ctx->cur_insn_len; + if (unlikely(ctx->cs->singlestep_enabled)) { + ctx->base.is_jmp = DISAS_SSTEP; + } /* * If 'fcfi_lp_expected' is still true after processing the instruction, * then we did not see an 'lpad' instruction, and must raise an exception. @@ -1370,7 +1375,7 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) * code the insn may have emitted will be deleted as dead code following * the noreturn exception */ - if (ctx->fcfi_lp_expected) { + else if (ctx->fcfi_lp_expected) { /* Emit after insn_start, i.e. before the op following insn_start. */ tcg_ctx->emit_before_op = QTAILQ_NEXT(ctx->base.insn_start, link); tcg_gen_st_tl(tcg_constant_tl(RISCV_EXCP_SW_CHECK_FCFI_TVAL), @@ -1389,8 +1394,7 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) unsigned page_ofs = ctx->base.pc_next & ~TARGET_PAGE_MASK; if (page_ofs > TARGET_PAGE_SIZE - MAX_INSN_LEN) { - uint16_t next_insn = - translator_lduw(env, &ctx->base, ctx->base.pc_next); + uint16_t next_insn = cpu_lduw_code(env, ctx->base.pc_next); int len = insn_len(next_insn); if (!translator_is_same_page(&ctx->base, ctx->base.pc_next + len - 1)) { @@ -1406,6 +1410,10 @@ static void riscv_tr_tb_stop(DisasContextBase *dcbase, CPUState *cpu) DisasContext *ctx = container_of(dcbase, DisasContext, base); switch (ctx->base.is_jmp) { + case DISAS_SSTEP: + ctx->pc_save = ctx->base.pc_first; + gen_goto_tb(ctx, 0, 0); + break; case DISAS_TOO_MANY: gen_goto_tb(ctx, 0, 0); break; From 32e73b01e5aaba05b0b86c3b8cea56b2db31f2e5 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 16 Jan 2026 10:48:32 +0000 Subject: [PATCH 11/18] [ot] target/riscv: cpu: add support for Zbr0p93 Signed-off-by: James Wainwright --- disas/meson.build | 3 +- disas/riscv-zbr.c | 78 ++++++++++++++ disas/riscv-zbr.h | 18 ++++ disas/riscv.c | 6 ++ target/riscv/bitmanip_helper.c | 138 ++++++++++++++++++++++++ target/riscv/cpu.c | 8 +- target/riscv/cpu.h | 1 + target/riscv/cpu_cfg.h | 3 + target/riscv/cpu_cfg_fields.h.inc | 1 + target/riscv/helper.h | 2 + target/riscv/insn32.decode | 12 +++ target/riscv/insn_trans/trans_rvb.c.inc | 37 +++++++ 12 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 disas/riscv-zbr.c create mode 100644 disas/riscv-zbr.h diff --git a/disas/meson.build b/disas/meson.build index bbfa11978352e..d549b9b072ebf 100644 --- a/disas/meson.build +++ b/disas/meson.build @@ -7,7 +7,8 @@ common_ss.add(when: 'CONFIG_MIPS_DIS', if_true: files('mips.c', 'nanomips.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 0000000000000..1f03752ec3518 --- /dev/null +++ b/disas/riscv-zbr.c @@ -0,0 +1,78 @@ +/* + * QEMU RISC-V Disassembler for Zbr + * + * Copyright (c) 2023 Rivos Inc + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#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 0000000000000..366040ed53c23 --- /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 9b44e0859220f..a6185a1017a43 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, @@ -5434,6 +5437,9 @@ static GString *disasm_inst(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/target/riscv/bitmanip_helper.c b/target/riscv/bitmanip_helper.c index e9c8d7f77803c..52ac2dad21aae 100644 --- a/target/riscv/bitmanip_helper.c +++ b/target/riscv/bitmanip_helper.c @@ -129,3 +129,141 @@ target_ulong HELPER(xperm8)(target_ulong rs1, target_ulong rs2) { return do_xperm(rs1, rs2, 3); } + +/* + * this table is already defined in hw/pc/pcnet.c, as well as likely implemented + * in zlib. It would make sense to move pcnet implementation into util/ as the + * latter already embeds crc32c table - and stop refering to zlib.h for CRC32 + */ +static uint32_t zbr_crc32_table[256u] = { + 0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, + 0x706af48fu, 0xe963a535u, 0x9e6495a3u, 0x0edb8832u, 0x79dcb8a4u, + 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, + 0x90bf1d91u, 0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, + 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, 0x136c9856u, + 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu, 0x14015c4fu, 0x63066cd9u, + 0xfa0f3d63u, 0x8d080df5u, 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, + 0xa2677172u, 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, + 0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u, + 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, 0x26d930acu, 0x51de003au, + 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, + 0xb8bda50fu, 0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, + 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu, 0xb6662d3du, 0x76dc4190u, + 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu, + 0x9fbfe4a5u, 0xe8b8d433u, 0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, + 0xe10e9818u, 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u, + 0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu, 0x6c0695edu, + 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, 0x65b0d9c6u, 0x12b7e950u, + 0x8bbeb8eau, 0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, + 0xfbd44c65u, 0x4db26158u, 0x3ab551ceu, 0xa3bc0074u, 0xd4bb30e2u, + 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, 0x4369e96au, + 0x346ed9fcu, 0xad678846u, 0xda60b8d0u, 0x44042d73u, 0x33031de5u, + 0xaa0a4c5fu, 0xdd0d7cc9u, 0x5005713cu, 0x270241aau, 0xbe0b1010u, + 0xc90c2086u, 0x5768b525u, 0x206f85b3u, 0xb966d409u, 0xce61e49fu, + 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, + 0x2eb40d81u, 0xb7bd5c3bu, 0xc0ba6cadu, 0xedb88320u, 0x9abfb3b6u, + 0x03b6e20cu, 0x74b1d29au, 0xead54739u, 0x9dd277afu, 0x04db2615u, + 0x73dc1683u, 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, + 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u, 0xf00f9344u, + 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, 0xf762575du, 0x806567cbu, + 0x196c3671u, 0x6e6b06e7u, 0xfed41b76u, 0x89d32be0u, 0x10da7a5au, + 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, + 0xd6d6a3e8u, 0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, + 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu, 0xd80d2bdau, 0xaf0a1b4cu, + 0x36034af6u, 0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, + 0x4669be79u, 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, + 0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu, 0xc5ba3bbeu, + 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u, 0xb5d0cf31u, + 0x2cd99e8bu, 0x5bdeae1du, 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, + 0x026d930au, 0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u, + 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, 0x92d28e9bu, + 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u, 0x86d3d2d4u, 0xf1d4e242u, + 0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, + 0x18b74777u, 0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, + 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u, 0xa00ae278u, + 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, + 0x4969474du, 0x3e6e77dbu, 0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, + 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u, + 0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, 0xbad03605u, + 0xcdd70693u, 0x54de5729u, 0x23d967bfu, 0xb3667a2eu, 0xc4614ab8u, + 0x5d681b02u, 0x2a6f2b94u, 0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, + 0x2d02ef8du +}; + +/* + * this table is already defined in util/crc32c.c, but the latter does not + * export a direct access function. See also comment in the previous table, it + * would make sense to group all CRC32* implementations into util/ directory. + */ +static uint32_t zbr_crc32c_table[256u] = { + 0x00000000u, 0xf26b8303u, 0xe13b70f7u, 0x1350f3f4u, 0xc79a971fu, + 0x35f1141cu, 0x26a1e7e8u, 0xd4ca64ebu, 0x8ad958cfu, 0x78b2dbccu, + 0x6be22838u, 0x9989ab3bu, 0x4d43cfd0u, 0xbf284cd3u, 0xac78bf27u, + 0x5e133c24u, 0x105ec76fu, 0xe235446cu, 0xf165b798u, 0x030e349bu, + 0xd7c45070u, 0x25afd373u, 0x36ff2087u, 0xc494a384u, 0x9a879fa0u, + 0x68ec1ca3u, 0x7bbcef57u, 0x89d76c54u, 0x5d1d08bfu, 0xaf768bbcu, + 0xbc267848u, 0x4e4dfb4bu, 0x20bd8edeu, 0xd2d60dddu, 0xc186fe29u, + 0x33ed7d2au, 0xe72719c1u, 0x154c9ac2u, 0x061c6936u, 0xf477ea35u, + 0xaa64d611u, 0x580f5512u, 0x4b5fa6e6u, 0xb93425e5u, 0x6dfe410eu, + 0x9f95c20du, 0x8cc531f9u, 0x7eaeb2fau, 0x30e349b1u, 0xc288cab2u, + 0xd1d83946u, 0x23b3ba45u, 0xf779deaeu, 0x05125dadu, 0x1642ae59u, + 0xe4292d5au, 0xba3a117eu, 0x4851927du, 0x5b016189u, 0xa96ae28au, + 0x7da08661u, 0x8fcb0562u, 0x9c9bf696u, 0x6ef07595u, 0x417b1dbcu, + 0xb3109ebfu, 0xa0406d4bu, 0x522bee48u, 0x86e18aa3u, 0x748a09a0u, + 0x67dafa54u, 0x95b17957u, 0xcba24573u, 0x39c9c670u, 0x2a993584u, + 0xd8f2b687u, 0x0c38d26cu, 0xfe53516fu, 0xed03a29bu, 0x1f682198u, + 0x5125dad3u, 0xa34e59d0u, 0xb01eaa24u, 0x42752927u, 0x96bf4dccu, + 0x64d4cecfu, 0x77843d3bu, 0x85efbe38u, 0xdbfc821cu, 0x2997011fu, + 0x3ac7f2ebu, 0xc8ac71e8u, 0x1c661503u, 0xee0d9600u, 0xfd5d65f4u, + 0x0f36e6f7u, 0x61c69362u, 0x93ad1061u, 0x80fde395u, 0x72966096u, + 0xa65c047du, 0x5437877eu, 0x4767748au, 0xb50cf789u, 0xeb1fcbadu, + 0x197448aeu, 0x0a24bb5au, 0xf84f3859u, 0x2c855cb2u, 0xdeeedfb1u, + 0xcdbe2c45u, 0x3fd5af46u, 0x7198540du, 0x83f3d70eu, 0x90a324fau, + 0x62c8a7f9u, 0xb602c312u, 0x44694011u, 0x5739b3e5u, 0xa55230e6u, + 0xfb410cc2u, 0x092a8fc1u, 0x1a7a7c35u, 0xe811ff36u, 0x3cdb9bddu, + 0xceb018deu, 0xdde0eb2au, 0x2f8b6829u, 0x82f63b78u, 0x709db87bu, + 0x63cd4b8fu, 0x91a6c88cu, 0x456cac67u, 0xb7072f64u, 0xa457dc90u, + 0x563c5f93u, 0x082f63b7u, 0xfa44e0b4u, 0xe9141340u, 0x1b7f9043u, + 0xcfb5f4a8u, 0x3dde77abu, 0x2e8e845fu, 0xdce5075cu, 0x92a8fc17u, + 0x60c37f14u, 0x73938ce0u, 0x81f80fe3u, 0x55326b08u, 0xa759e80bu, + 0xb4091bffu, 0x466298fcu, 0x1871a4d8u, 0xea1a27dbu, 0xf94ad42fu, + 0x0b21572cu, 0xdfeb33c7u, 0x2d80b0c4u, 0x3ed04330u, 0xccbbc033u, + 0xa24bb5a6u, 0x502036a5u, 0x4370c551u, 0xb11b4652u, 0x65d122b9u, + 0x97baa1bau, 0x84ea524eu, 0x7681d14du, 0x2892ed69u, 0xdaf96e6au, + 0xc9a99d9eu, 0x3bc21e9du, 0xef087a76u, 0x1d63f975u, 0x0e330a81u, + 0xfc588982u, 0xb21572c9u, 0x407ef1cau, 0x532e023eu, 0xa145813du, + 0x758fe5d6u, 0x87e466d5u, 0x94b49521u, 0x66df1622u, 0x38cc2a06u, + 0xcaa7a905u, 0xd9f75af1u, 0x2b9cd9f2u, 0xff56bd19u, 0x0d3d3e1au, + 0x1e6dcdeeu, 0xec064eedu, 0xc38d26c4u, 0x31e6a5c7u, 0x22b65633u, + 0xd0ddd530u, 0x0417b1dbu, 0xf67c32d8u, 0xe52cc12cu, 0x1747422fu, + 0x49547e0bu, 0xbb3ffd08u, 0xa86f0efcu, 0x5a048dffu, 0x8ecee914u, + 0x7ca56a17u, 0x6ff599e3u, 0x9d9e1ae0u, 0xd3d3e1abu, 0x21b862a8u, + 0x32e8915cu, 0xc083125fu, 0x144976b4u, 0xe622f5b7u, 0xf5720643u, + 0x07198540u, 0x590ab964u, 0xab613a67u, 0xb831c993u, 0x4a5a4a90u, + 0x9e902e7bu, 0x6cfbad78u, 0x7fab5e8cu, 0x8dc0dd8fu, 0xe330a81au, + 0x115b2b19u, 0x020bd8edu, 0xf0605beeu, 0x24aa3f05u, 0xd6c1bc06u, + 0xc5914ff2u, 0x37faccf1u, 0x69e9f0d5u, 0x9b8273d6u, 0x88d28022u, + 0x7ab90321u, 0xae7367cau, 0x5c18e4c9u, 0x4f48173du, 0xbd23943eu, + 0xf36e6f75u, 0x0105ec76u, 0x12551f82u, 0xe03e9c81u, 0x34f4f86au, + 0xc69f7b69u, 0xd5cf889du, 0x27a40b9eu, 0x79b737bau, 0x8bdcb4b9u, + 0x988c474du, 0x6ae7c44eu, 0xbe2da0a5u, 0x4c4623a6u, 0x5f16d052u, + 0xad7d5351u +}; + +target_ulong HELPER(crc32)(target_ulong rs1, target_ulong sz) +{ + for (target_ulong i = 0; i < sz; i++) { + rs1 = zbr_crc32_table[rs1 & 0xFF] ^ (rs1 >> 8); + } + + return rs1; +} + +target_ulong HELPER(crc32c)(target_ulong rs1, target_ulong sz) +{ + for (target_ulong i = 0; i < sz; i++) { + rs1 = zbr_crc32c_table[rs1 & 0xFF] ^ (rs1 >> 8); + } + + return rs1; +} diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 1e52f8e8d8897..987339b5557b5 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -41,7 +41,7 @@ /* RISC-V CPU definitions */ static const char riscv_single_letter_exts[] = "IEMAFDQCBPVH"; const uint32_t misa_bits[] = {RVI, RVE, RVM, RVA, RVF, RVD, RVV, - RVC, RVS, RVU, RVH, RVG, RVB, 0}; + RVC, RVS, RVU, RVH, RVG, RVB, RVX, 0}; /* * From vector_helper.c @@ -1260,7 +1260,8 @@ static const MISAExtInfo misa_ext_info_arr[] = { MISA_EXT_INFO(RVH, "h", "Hypervisor"), MISA_EXT_INFO(RVV, "v", "Vector operations"), MISA_EXT_INFO(RVG, "g", "General purpose (IMAFD_Zicsr_Zifencei)"), - MISA_EXT_INFO(RVB, "b", "Bit manipulation (Zba_Zbb_Zbs)") + MISA_EXT_INFO(RVB, "b", "Bit manipulation (Zba_Zbb_Zbs)"), + MISA_EXT_INFO(RVX, "x", "Non-standard extensions") }; static void riscv_cpu_validate_misa_mxl(RISCVCPUClass *mcc) @@ -1462,6 +1463,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_vendor_exts[] = { /* These are experimental so mark with 'x-' */ const RISCVCPUMultiExtConfig riscv_cpu_experimental_exts[] = { MULTI_EXT_CFG_BOOL("x-svukte", ext_svukte, false), + MULTI_EXT_CFG_BOOL("x-zbr", ext_zbr, false), { }, }; @@ -3142,7 +3144,7 @@ static const TypeInfo riscv_cpu_type_infos[] = { DEFINE_ABSTRACT_RISCV_CPU(TYPE_RISCV_CPU_LOWRISC_IBEX, TYPE_RISCV_VENDOR_CPU, .misa_mxl_max = MXL_RV32, - .misa_ext = RVI | RVM | RVC | RVU, + .misa_ext = RVI | RVM | RVC | RVU | RVX, .priv_spec = PRIV_VERSION_1_12_0, .cfg.max_satp_mode = VM_1_10_MBARE, .cfg.ext_zifencei = true, diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 53d7739da98c7..faf8a905ac812 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -69,6 +69,7 @@ typedef struct CPUArchState CPURISCVState; #define RVH RV('H') #define RVG RV('G') #define RVB RV('B') +#define RVX RV('X') extern const uint32_t misa_bits[]; const char *riscv_get_misa_ext_name(uint32_t bit); diff --git a/target/riscv/cpu_cfg.h b/target/riscv/cpu_cfg.h index aa28dc8d7e616..29e02979179a9 100644 --- a/target/riscv/cpu_cfg.h +++ b/target/riscv/cpu_cfg.h @@ -65,4 +65,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/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index 0f21adab45e99..4e240376f1dc7 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -11,6 +11,7 @@ BOOL_FIELD(ext_zbc) BOOL_FIELD(ext_zbkb) BOOL_FIELD(ext_zbkc) BOOL_FIELD(ext_zbkx) +BOOL_FIELD(ext_zbr) BOOL_FIELD(ext_zbs) BOOL_FIELD(ext_zca) BOOL_FIELD(ext_zcb) diff --git a/target/riscv/helper.h b/target/riscv/helper.h index 0cd1421926aca..86bc3f42fa4cc 100644 --- a/target/riscv/helper.h +++ b/target/riscv/helper.h @@ -84,6 +84,8 @@ DEF_HELPER_FLAGS_1(unzip, TCG_CALL_NO_RWG_SE, tl, tl) DEF_HELPER_FLAGS_1(zip, TCG_CALL_NO_RWG_SE, tl, tl) DEF_HELPER_FLAGS_2(xperm4, TCG_CALL_NO_RWG_SE, tl, tl, tl) DEF_HELPER_FLAGS_2(xperm8, TCG_CALL_NO_RWG_SE, tl, tl, tl) +DEF_HELPER_FLAGS_2(crc32, TCG_CALL_NO_RWG_SE, tl, tl, tl) +DEF_HELPER_FLAGS_2(crc32c, TCG_CALL_NO_RWG_SE, tl, tl, tl) /* Floating Point - Half Precision */ DEF_HELPER_FLAGS_3(fadd_h, TCG_CALL_NO_RWG, i64, env, i64, i64) diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index 318d330c57903..bdf825877ee02 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -840,6 +840,18 @@ binvi 01101. ........... 001 ..... 0010011 @sh bset 0010100 .......... 001 ..... 0110011 @r bseti 00101. ........... 001 ..... 0010011 @sh +# *** RV32 Zbr Experimental Extension *** +crc32_b 0110000 10000 ..... 001 ..... 0010011 @r2 +crc32_h 0110000 10001 ..... 001 ..... 0010011 @r2 +crc32_w 0110000 10010 ..... 001 ..... 0010011 @r2 +crc32c_b 0110000 11000 ..... 001 ..... 0010011 @r2 +crc32c_h 0110000 11001 ..... 001 ..... 0010011 @r2 +crc32c_w 0110000 11010 ..... 001 ..... 0010011 @r2 + +# *** RV64 Zbr Experimental Extension (in addition to RV32 Zbr) *** +crc32_d 0110000 10011 ..... 001 ..... 0010011 @r2 +crc32c_d 0110000 11011 ..... 001 ..... 0010011 @r2 + # *** Zfa Standard Extension *** fli_s 1111000 00001 ..... 000 ..... 1010011 @r2 fli_d 1111001 00001 ..... 000 ..... 1010011 @r2 diff --git a/target/riscv/insn_trans/trans_rvb.c.inc b/target/riscv/insn_trans/trans_rvb.c.inc index e4dcc7c991314..59db8b047fe0e 100644 --- a/target/riscv/insn_trans/trans_rvb.c.inc +++ b/target/riscv/insn_trans/trans_rvb.c.inc @@ -36,6 +36,12 @@ } \ } while (0) +#define REQUIRE_ZBR(ctx) do { \ + if (!ctx->cfg_ptr->ext_zbr) { \ + return false; \ + } \ +} while (0) + #define REQUIRE_ZBS(ctx) do { \ if (!ctx->cfg_ptr->ext_zbs) { \ return false; \ @@ -569,3 +575,34 @@ static bool trans_xperm8(DisasContext *ctx, arg_xperm8 *a) REQUIRE_ZBKX(ctx); return gen_arith(ctx, a, EXT_NONE, gen_helper_xperm8, NULL); } + +static bool gen_crc(DisasContext *ctx, arg_r2 *a, + void (*func)(TCGv, TCGv, TCGv), TCGv tsz) +{ + REQUIRE_ZBR(ctx); + TCGv dest = dest_gpr(ctx, a->rd); + TCGv src1 = get_gpr(ctx, a->rs1, EXT_NONE); + + func(dest, src1, tsz); + gen_set_gpr(ctx, a->rd, dest); + + return true; +} + +#define TRANS_CRC32(NAME, SIZE) \ + static bool trans_crc32_##NAME(DisasContext *ctx, arg_r2 *a) \ + { if (SIZE == 8) { REQUIRE_64BIT(ctx); }; \ + return gen_crc(ctx, a, gen_helper_crc32, tcg_constant_tl(SIZE)); } +#define TRANS_CRC32C(NAME, SIZE) \ + static bool trans_crc32c_##NAME(DisasContext *ctx, arg_r2 *a) \ + { if (SIZE == 8) { REQUIRE_64BIT(ctx); }; \ + return gen_crc(ctx, a, gen_helper_crc32c, tcg_constant_tl(SIZE)); } + +TRANS_CRC32(b, 1); +TRANS_CRC32(h, 2); +TRANS_CRC32(w, 4); +TRANS_CRC32(d, 8); +TRANS_CRC32C(b, 1); +TRANS_CRC32C(h, 2); +TRANS_CRC32C(w, 4); +TRANS_CRC32C(d, 8); From 8006c7e8e569f7cd9c2f9d2687023424560bfdae Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 Jan 2026 11:44:32 +0000 Subject: [PATCH 12/18] [ot] hw: ibexdemo: add Ibex Demo System machine This machine emulates the Ibex Demo System [0] and includes GPIO, SPI device, a timer, UART, and a debug module. There is an st7735 LCD display connected to the SPI device. [0]: https://github.com/lowRISC/ibex-demo-system --- configs/devices/riscv32-softmmu/default.mak | 1 + hw/Kconfig | 3 + hw/display/Kconfig | 3 + hw/display/meson.build | 1 + hw/display/st7735.c | 622 ++++++++++++++++++++ hw/display/trace-events | 5 + hw/ibexdemo/Kconfig | 17 + hw/ibexdemo/ibexdemo_gpio.c | 221 +++++++ hw/ibexdemo/ibexdemo_simctrl.c | 133 +++++ hw/ibexdemo/ibexdemo_spi.c | 145 +++++ hw/ibexdemo/ibexdemo_timer.c | 227 +++++++ hw/ibexdemo/ibexdemo_uart.c | 295 ++++++++++ hw/ibexdemo/meson.build | 7 + hw/ibexdemo/trace-events | 10 + hw/ibexdemo/trace.h | 1 + hw/meson.build | 1 + hw/riscv/Kconfig | 17 + hw/riscv/ibexdemo.c | 541 +++++++++++++++++ hw/riscv/meson.build | 4 + include/hw/display/st7735.h | 45 ++ include/hw/ibexdemo/ibexdemo_gpio.h | 41 ++ include/hw/ibexdemo/ibexdemo_simctrl.h | 35 ++ include/hw/ibexdemo/ibexdemo_spi.h | 35 ++ include/hw/ibexdemo/ibexdemo_timer.h | 34 ++ include/hw/ibexdemo/ibexdemo_uart.h | 35 ++ include/hw/riscv/ibexdemo.h | 33 ++ meson.build | 1 + target/riscv/cpu-qom.h | 2 + target/riscv/cpu.c | 3 + 29 files changed, 2518 insertions(+) create mode 100644 hw/display/st7735.c create mode 100644 hw/ibexdemo/Kconfig create mode 100644 hw/ibexdemo/ibexdemo_gpio.c create mode 100644 hw/ibexdemo/ibexdemo_simctrl.c create mode 100644 hw/ibexdemo/ibexdemo_spi.c create mode 100644 hw/ibexdemo/ibexdemo_timer.c create mode 100644 hw/ibexdemo/ibexdemo_uart.c create mode 100644 hw/ibexdemo/meson.build create mode 100644 hw/ibexdemo/trace-events create mode 100644 hw/ibexdemo/trace.h create mode 100644 hw/riscv/ibexdemo.c create mode 100644 include/hw/display/st7735.h create mode 100644 include/hw/ibexdemo/ibexdemo_gpio.h create mode 100644 include/hw/ibexdemo/ibexdemo_simctrl.h create mode 100644 include/hw/ibexdemo/ibexdemo_spi.h create mode 100644 include/hw/ibexdemo/ibexdemo_timer.h create mode 100644 include/hw/ibexdemo/ibexdemo_uart.h create mode 100644 include/hw/riscv/ibexdemo.h diff --git a/configs/devices/riscv32-softmmu/default.mak b/configs/devices/riscv32-softmmu/default.mak index c2cd86ce05f02..0b73cf220025f 100644 --- a/configs/devices/riscv32-softmmu/default.mak +++ b/configs/devices/riscv32-softmmu/default.mak @@ -10,3 +10,4 @@ # CONFIG_SIFIVE_U=n # CONFIG_RISCV_VIRT=n # CONFIG_OPENTITAN=n +# CONFIG_IBEXDEMO=n diff --git a/hw/Kconfig b/hw/Kconfig index 49caf6df272a4..bb44fc411fcbc 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -69,6 +69,9 @@ source sparc64/Kconfig source tricore/Kconfig source xtensa/Kconfig +# Ibex Demo System devices +source ibexdemo/Kconfig + # JTAG devices source jtag/Kconfig diff --git a/hw/display/Kconfig b/hw/display/Kconfig index 1e95ab28ef405..20ae0db824d4c 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -37,6 +37,9 @@ config SSD0303 config SSD0323 bool +config ST7735 + bool + config VGA_PCI bool default y if PCI_DEVICES diff --git a/hw/display/meson.build b/hw/display/meson.build index 90e6c041bdbc1..60bfa5698570e 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -14,6 +14,7 @@ system_ss.add(when: 'CONFIG_PL110', if_true: files('pl110.c')) system_ss.add(when: 'CONFIG_SII9022', if_true: files('sii9022.c')) system_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c')) system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c')) +system_ss.add(when: 'CONFIG_ST7735', if_true: files('st7735.c')) system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xenfb.c')) system_ss.add(when: 'CONFIG_VGA_PCI', if_true: files('vga-pci.c')) diff --git a/hw/display/st7735.c b/hw/display/st7735.c new file mode 100644 index 0000000000000..5c8b0a6edbc05 --- /dev/null +++ b/hw/display/st7735.c @@ -0,0 +1,622 @@ +/* + * QEMU Sitronix ST7735 controller device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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. + * + * Note: only a small subset of the ST7735 are supported. + * - inversion/rotation/... are ignored + * - gamma settings are ignored + * - read back functions are not supported + * - ... + * Moreover, only host consoles with 24bpp/32bpp are supported. + */ + +#include "qemu/osdep.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/display/st7735.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/ssi/ssi.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "ui/console.h" + +/* clang-format off */ +REG8(NOP, 0x00u) +REG8(SWRESET, 0x01u) +REG8(RDDID, 0x04u) +REG8(RDDST, 0x09u) +REG8(RDDPM, 0x0au) +REG8(RDDMADCTL, 0x0bu) +REG8(RDDCOLMOD, 0x0cu) +REG8(RDDIM, 0x0du) +REG8(RDDSM, 0x0eu) +REG8(SLPIN, 0x10u) +REG8(SLPOUT, 0x11u) +REG8(PTLON, 0x12u) +REG8(NORON, 0x13u) +REG8(INVOFF, 0x20u) +REG8(INVON, 0x21u) +REG8(GAMSET, 0x26u) +REG8(DISPOFF, 0x28u) +REG8(DISPON, 0x29u) +REG8(CASET, 0x2au) +REG8(RASET, 0x2bu) +REG8(RAMWR, 0x2cu) +REG8(RAMRD, 0x2eu) +REG8(PTLAR, 0x30u) +REG8(TEOFF, 0x34u) +REG8(TEON, 0x35u) +REG8(MADCTL, 0x36u) + FIELD(MADCTL, MH, 2u, 1u) + FIELD(MADCTL, RGB, 2u, 1u) + FIELD(MADCTL, ML, 4u, 1u) + FIELD(MADCTL, MV, 5u, 1u) + FIELD(MADCTL, MX, 6u, 1u) + FIELD(MADCTL, MY, 7u, 1u) +REG8(IDMOFF, 0x38u) +REG8(IDMON, 0x39u) +REG8(COLMOD, 0x3au) +REG8(FRMCTR1, 0xb1u) +REG8(FRMCTR2, 0xb2u) +REG8(FRMCTR3, 0xb3u) +REG8(INVCTR , 0xb4u) + FIELD(INVCTR, NLC, 0u, 1u) + FIELD(INVCTR, NLB, 1u, 1u) + FIELD(INVCTR, NLA, 2u, 1u) +REG8(DISSET5, 0xb6u) +REG8(PWCTR1, 0xc0u) +REG8(PWCTR2, 0xc1u) +REG8(PWCTR3, 0xc2u) +REG8(PWCTR4, 0xc3u) +REG8(PWCTR5, 0xc4u) +REG8(VMCTR1, 0xc5u) +REG8(VMOFCTR, 0xc7u) +REG8(WRID2, 0xd1u) +REG8(NVFCTR1, 0xd9u) +REG8(NVFCTR2, 0xdeu) +REG8(NVFCTR3, 0xdfu) +REG8(RDID1, 0xdau) +REG8(RDID2, 0xdbu) +REG8(RDID3, 0xdcu) +REG8(GMCTRP1, 0xe0u) +REG8(GMCTRN1, 0xe1u) +REG8(EXTCTRL, 0xf0u) +REG8(VCOM4L, 0xffu) +REG8(PWCTR6, 0xfcu) +/* clang-format on */ + +#define ST7735_DEFAULT_WIDTH 162u +#define ST7735_DEFAULT_HEIGHT 132u + +#define ST7735_BUFFER_LEN 16u /* max SSI payload (except. gfx data) */ + +#define ST7735_FB_PIXEL uint32_t /* 32 bpp (24 bpp real)*/ +#define ST7735_FB_BPP sizeof(ST7735_FB_PIXEL) +#define ST7735_FB_PIXELS(_s_) ((_s_)->width * (_s_)->height) + +enum St7735SsiState { + STATE_IDLE, + STATE_COLLECTING_DATA, + STATE_WRITING_MEMORY, +}; + +typedef enum St7735PixelMode { + PX_INV = 0u, /* invalid mode */ + PX_444 = 0x3u, /* 12-bit pixel */ + PX_565 = 0x5u, /* 16-bit pixel */ + PX_666 = 0x6u, /* 18-bit pixel */ +} St7735PixelMode; + +struct St7735State { + SSIPeripheral ssidev; + QemuConsole *con; + + uint8_t state; /* SM state */ + uint8_t command; /* command in progress */ + uint32_t length; /* payload length for the current command */ + uint32_t pos; /* count of received payload bytes */ + uint8_t buffer[ST7735_BUFFER_LEN]; + bool dc; /* false: handling command, true: handling data/payload */ + bool nreset; /* reset is active low */ + + bool redraw; + ST7735_FB_PIXEL *fb; /* framebuffer (may be NULL) */ + uint8_t col; /* current column in FB */ + uint8_t row; /* current row in FB */ + + uint16_t width; + uint16_t height; + + struct { + St7735PixelMode pixmode; + uint8_t madctl; + uint8_t invctr; + uint16_t xs; + uint16_t xe; + uint16_t ys; + uint16_t ye; + } disp; +}; + +static void st7735_sw_reset(St7735State *s) +{ + trace_st7735_reset(""); + + s->command = R_NOP; + s->pos = 0; + s->length = 0; + s->col = 0; + s->row = 0; +} + +static void st7735_reset(DeviceState *dev) +{ + St7735State *s = ST7735(dev); + + trace_st7735_reset("hw"); + + memset(&s->disp, 0, sizeof(s->disp)); + + st7735_sw_reset(s); + + /* note: FB is not cleared even on HW reset as per the datasheet. */ +} + +static void st7735_sleep(St7735State *s, bool on) +{ + trace_st7735_set("sleep", on); +} + +static void st7735_partial_mode(St7735State *s, bool on) +{ + trace_st7735_set("partial mode", on); +} + +static void st7735_invert(St7735State *s, bool on) +{ + trace_st7735_set("invert", on); +} + +static void st7735_idle(St7735State *s, bool on) +{ + trace_st7735_set("idle", on); +} + +static void st7735_display(St7735State *s, bool on) +{ + trace_st7735_set("display", on); +} + +static void st7735_gpio_event(void *opaque, int irq, int level) +{ + St7735State *s = opaque; + bool value = (bool)level; + + + switch (irq) { + case ST7735_IO_RESET: + trace_st7735_gpio("reset", value); + if (!value && s->nreset) { + st7735_reset(DEVICE(s)); + } + s->nreset = value; + break; + case ST7735_IO_D_C: + trace_st7735_gpio("d/c", value); + s->dc = value; + break; + default: + break; + } +} + +static void st7735_decode_command(St7735State *s, uint8_t cmd) +{ + s->pos = 0; + + s->command = cmd; + + switch (cmd) { + case R_NOP: + break; + case R_SWRESET: + st7735_sw_reset(s); + break; + case R_SLPIN: + case R_SLPOUT: + st7735_sleep(s, !(cmd - R_SLPIN)); + break; + case R_PTLON: + case R_NORON: + st7735_partial_mode(s, !(cmd - R_PTLON)); + break; + case R_INVOFF: + case R_INVON: + st7735_invert(s, (bool)(cmd - R_INVOFF)); + break; + case R_DISPOFF: + case R_DISPON: + st7735_display(s, (bool)(cmd - R_DISPOFF)); + break; + case R_CASET: + case R_RASET: + s->length = 4u; + break; + case R_RAMWR: + s->state = STATE_WRITING_MEMORY; + s->col = s->disp.xs; + s->row = s->disp.ys; + break; + case R_MADCTL: + s->length = 1u; + break; + case R_IDMOFF: + case R_IDMON: + st7735_idle(s, !(cmd - R_IDMOFF)); + break; + case R_COLMOD: + s->length = 1u; + break; + case R_FRMCTR1: + case R_FRMCTR2: + s->length = 3u; + break; + case R_FRMCTR3: + s->length = 6u; + break; + case R_DISSET5: + s->length = 2u; + break; + case R_INVCTR: + s->length = 1u; + break; + case R_PWCTR1: + case R_PWCTR3: + case R_PWCTR4: + case R_PWCTR5: + case R_PWCTR6: + s->length = 2u; + break; + case R_PWCTR2: + s->length = 1u; + break; + case R_VMCTR1: + s->length = 2u; + break; + case R_GMCTRP1: + case R_GMCTRN1: + s->length = 16u; + break; + case R_GAMSET: + case R_RAMRD: + case R_PTLAR: + case R_TEOFF: + case R_TEON: + case R_RDDID: + case R_RDDST: + case R_RDDPM: + case R_RDDMADCTL: + case R_RDDCOLMOD: + case R_RDDIM: + case R_RDDSM: + case R_VMOFCTR: + case R_WRID2: + case R_NVFCTR1: + case R_NVFCTR2: + case R_NVFCTR3: + case R_RDID1: + case R_RDID2: + case R_RDID3: + case R_EXTCTRL: + case R_VCOM4L: + default: + qemu_log_mask(LOG_UNIMP, "%s: Command not supported 0x%02x\n", __func__, + cmd); + break; + } +} + +static void st7735_execute_command(St7735State *s) +{ + switch (s->command) { + case R_COLMOD: + switch (s->buffer[0]) { + case PX_444: + case PX_565: + case PX_666: + s->disp.pixmode = (St7735PixelMode)s->buffer[0]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown pixel mode %u\n", + __func__, s->buffer[0]); + break; + } + break; + case R_MADCTL: + s->disp.madctl = s->buffer[0]; + break; + case R_INVCTR: + s->disp.invctr = s->buffer[0]; + break; + case R_CASET: /* See .10 Memory Data Write/ Read Direction */ + s->disp.xs = MIN(lduw_be_p(&s->buffer[0u]), s->width); + s->disp.xe = MIN(lduw_be_p(&s->buffer[2u]), s->width); + s->row = s->disp.ys; + trace_st7735_cursor("X", s->disp.xs, s->disp.xe); + break; + case R_RASET: + s->disp.ys = MIN(lduw_be_p(&s->buffer[0u]), s->height); + s->disp.ye = MIN(lduw_be_p(&s->buffer[2u]), s->height); + s->col = s->disp.xs; + trace_st7735_cursor("Y", s->disp.xs, s->disp.xe); + break; + case R_FRMCTR1: /* don't care */ + case R_FRMCTR2: + case R_FRMCTR3: + case R_DISSET5: + case R_PWCTR1: + case R_PWCTR2: + case R_PWCTR3: + case R_PWCTR4: + case R_PWCTR5: + case R_PWCTR6: + case R_VMCTR1: + case R_GMCTRP1: /* gamma control */ + case R_GMCTRN1: + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Command not supported 0x%02x\n", __func__, + s->command); + break; + } +} + +static inline uint32_t st7735_make_pixel(uint8_t r, uint8_t g, uint8_t b) +{ + /* PIXMAN_x8r8g8b8 encoding */ + return (((uint32_t)r) << 16u) | (((uint32_t)g) << 8u) | ((uint32_t)b << 0u); +} + +static ST7735_FB_PIXEL *st7735_get_pixel(St7735State *s) +{ + if (s->col > s->disp.xe) { + s->col = s->disp.xs; + s->row++; + } + if (s->row > s->disp.ye) { + s->row = s->disp.ys; + } + + return &s->fb[s->col + s->row * s->width]; +} + +static void st7735_write_memory(St7735State *s, uint8_t byte) +{ + s->buffer[s->pos++] = byte; + ST7735_FB_PIXEL *pixel; + uint8_t r, g, b; + switch (s->disp.pixmode) { + case PX_565: + if (s->pos == 2u) { + s->pos = 0u; + pixel = st7735_get_pixel(s); + uint16_t px16 = lduw_be_p(&s->buffer[0u]); + r = (uint8_t)((px16 >> 8u) & 0xf8u); + g = (uint8_t)((px16 >> 3u) & 0xfcu); + b = (uint8_t)((px16 << 3u) & 0xf8u); + pixel[0u] = st7735_make_pixel(r, g, b); + s->col += 1u; + break; + } + return; + case PX_666: + if (s->pos == 3u) { + s->pos = 0u; + pixel = st7735_get_pixel(s); + r = (uint8_t)(s->buffer[0u] & 0xfcu); + g = (uint8_t)(s->buffer[1u] & 0xfcu); + b = (uint8_t)(s->buffer[2u] & 0xfcu); + pixel[0u] = st7735_make_pixel(r, g, b); + s->col += 1u; + break; + }; + return; + case PX_444: + if (s->pos == 3u) { + s->pos = 0u; + pixel = st7735_get_pixel(s); + r = (uint8_t)(s->buffer[0u] & 0xf0u); + g = (uint8_t)(s->buffer[0u] << 4u); + b = (uint8_t)(s->buffer[1u] & 0xf0u); + pixel[0u] = st7735_make_pixel(r, g, b); + r = (uint8_t)(s->buffer[1u] << 4u); + g = (uint8_t)(s->buffer[2u] & 0xf0u); + b = (uint8_t)(s->buffer[2u] << 4u); + pixel[1u] = st7735_make_pixel(r, g, b); + s->col += 2u; + break; + } + return; + default: + /* be sure to reset the pixel buffer if not handled */ + s->pos = 0; + return; + } +} + +static uint32_t st7735_transfer(SSIPeripheral *dev, uint32_t rx) +{ + St7735State *s = ST7735(dev); + + if (!s->nreset) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Ignoring SSI in HW reset\n", + __func__); + return 0u; + } + + switch (s->state) { + case STATE_WRITING_MEMORY: + if (s->dc && s->fb) { + st7735_write_memory(s, rx); + return 0; + } + break; + case STATE_IDLE: + if (!s->dc) { + s->length = 0; + st7735_decode_command(s, (uint8_t)rx); + if (s->length) { + s->state = STATE_COLLECTING_DATA; + } + return 0; + } + break; + case STATE_COLLECTING_DATA: + if (s->dc) { + if (s->pos < ST7735_BUFFER_LEN) { + s->buffer[s->pos++] = (uint8_t)rx; + if (s->pos == s->length) { + st7735_execute_command(s); + } + } else { + s->pos++; + } + return 0; + } + break; + } + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Ignoring SSI data, D/C: %u, state %d\n", + __func__, s->dc, s->state); + + return 0u; +} + +static int st7735_set_cs(SSIPeripheral *dev, bool select) +{ + St7735State *s = ST7735(dev); + + if (select) { + if (s->state == STATE_WRITING_MEMORY) { + s->redraw = true; + } + s->pos = 0; + s->length = 0; + s->state = STATE_IDLE; + } + + return 0; +} + +static void st7735_invalidate_display(void *opaque) +{ + St7735State *s = opaque; + + s->redraw = true; +} + +static void st7735_update_display(void *opaque) +{ + St7735State *s = opaque; + + if (!s->redraw || !s->fb) { + return; + } + + DisplaySurface *surface = qemu_console_surface(s->con); + void *dest = surface_data(surface); + memcpy(dest, s->fb, ST7735_FB_PIXELS(s) * ST7735_FB_BPP); + dpy_gfx_update(s->con, 0, 0, s->width, s->height); + s->redraw = false; +} + +static const Property st7735_properties[] = { + DEFINE_PROP_UINT16("width", St7735State, width, ST7735_DEFAULT_WIDTH), + DEFINE_PROP_UINT16("height", St7735State, height, ST7735_DEFAULT_HEIGHT), +}; + +static const GraphicHwOps st7735_ui_ops = { + .invalidate = &st7735_invalidate_display, + .gfx_update = &st7735_update_display, +}; + +static void st7735_realize(SSIPeripheral *dev, Error **errp) +{ + St7735State *s = ST7735(dev); + + s->con = graphic_console_init(DEVICE(dev), 0, &st7735_ui_ops, s); + qemu_console_resize(s->con, s->width, s->height); + + qdev_init_gpio_in_named(DEVICE(dev), &st7735_gpio_event, ST7735_IO_LINES, + ST7735_IO_COUNT); + + DisplaySurface *surface = qemu_console_surface(s->con); + int bpp = surface_bits_per_pixel(surface); + if (bpp >= 24) { + s->fb = g_new0(ST7735_FB_PIXEL, ST7735_FB_PIXELS(s)); + } else { + qemu_log_mask(LOG_UNIMP, "%s: No support for display w/ %d bpp\n", + __func__, bpp); + s->fb = NULL; + } +} + +static void st7735_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass); + + device_class_set_legacy_reset(dc, &st7735_reset); + k->realize = &st7735_realize; + k->transfer = &st7735_transfer; + k->set_cs = &st7735_set_cs; + k->cs_polarity = SSI_CS_LOW; + + device_class_set_props(dc, st7735_properties); + set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); +} + +static const TypeInfo st7735_info = { + .name = TYPE_ST7735, + .parent = TYPE_SSI_PERIPHERAL, + .instance_size = sizeof(St7735State), + .class_init = &st7735_class_init, +}; + +static void st7735_register_types(void) +{ + type_register_static(&st7735_info); +} + +type_init(st7735_register_types); + +void st7735_configure(DeviceState *dev, hwaddr addr) +{ + SysBusDevice *busdev = SYS_BUS_DEVICE(dev); + + sysbus_realize_and_unref(busdev, &error_fatal); + sysbus_mmio_map(busdev, 0, addr); +} diff --git a/hw/display/trace-events b/hw/display/trace-events index e323a82cff24b..992e90360229f 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -226,3 +226,8 @@ apple_gfx_iosfc_unmap_memory(void *a, void *b, void *c, void *d, void *e, void * apple_gfx_iosfc_unmap_memory_region(void* mem, void *region) "unmapping @ %p from memory region %p" apple_gfx_iosfc_raise_irq(uint32_t vector) "vector=0x%x" +# st7735 +st7735_reset(const char *msg) "%s" +st7735_set(const char *msg, bool enable) "%s: %u" +st7735_gpio(const char *line, bool enable) "%s: %u" +st7735_cursor(const char *axis, uint16_t start, uint16_t end) "%s: %hu..%hu" diff --git a/hw/ibexdemo/Kconfig b/hw/ibexdemo/Kconfig new file mode 100644 index 0000000000000..12583294118c1 --- /dev/null +++ b/hw/ibexdemo/Kconfig @@ -0,0 +1,17 @@ +# Ibex Demo System + +config IBEXDEMO_GPIO + bool + +config IBEXDEMO_SIMCTRL + bool + +config IBEXDEMO_SPI + bool + select SSI + +config IBEXDEMO_TIMER + bool + +config IBEXDEMO_UART + bool diff --git a/hw/ibexdemo/ibexdemo_gpio.c b/hw/ibexdemo/ibexdemo_gpio.c new file mode 100644 index 0000000000000..0978df872f38c --- /dev/null +++ b/hw/ibexdemo/ibexdemo_gpio.c @@ -0,0 +1,221 @@ +/* + * QEMU lowRISC Ibex Demo GPIO device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/ibexdemo/ibexdemo_gpio.h" +#include "hw/irq.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/sysbus.h" +#include "trace.h" + +/* clang-format off */ +REG32(OUT, 0x00u) +REG32(IN, 0x04u) +REG32(IN_DBNC, 0x08u) +REG32(OUT_SHIFT, 0x0cu) +/* clang-format on */ + +struct IbexDemoGPIOState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + + uint32_t in_count; + uint32_t out_count; + + uint32_t input; + uint32_t output; + uint32_t output_level; + + qemu_irq *gpo; +}; + +static void ibexdemo_gpio_update_output(IbexDemoGPIOState *s) +{ + trace_ibexdemo_gpio_output(s->output); + + uint32_t output = s->output; + uint32_t change = output ^ s->output_level; + + for (unsigned ix = 0; ix < s->out_count; ix++) { + if (change & 0b1) { + qemu_set_irq(s->gpo[ix], (int)(output & 0b1)); + } + output >>= 1u; + change >>= 1u; + } + + s->output_level = s->output; +} + +static void ibexdemo_gpio_reset(DeviceState *dev) +{ + IbexDemoGPIOState *s = IBEXDEMO_GPIO(dev); + + s->input = 0; + s->output = 0; + s->output_level = UINT32_MAX; /* be sure to update all output on reset */ + + ibexdemo_gpio_update_output(s); +} + +static uint64_t ibexdemo_gpio_read(void *opaque, hwaddr addr, unsigned int size) +{ + IbexDemoGPIOState *s = opaque; + uint32_t val32; + + switch (addr >> 2u) { + case R_OUT: + val32 = s->output; + break; + case R_IN: + case R_IN_DBNC: + val32 = s->input; + break; + case R_OUT_SHIFT: + qemu_log_mask(LOG_GUEST_ERROR, "%s: not supported\n", __func__); + val32 = 0; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0; + break; + } + + return (uint64_t)val32; +} + +static void ibexdemo_gpio_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned int size) +{ + IbexDemoGPIOState *s = opaque; + + switch (addr >> 2u) { + case R_OUT: + s->output = val64 & ((1u << s->out_count) - 1u); + ibexdemo_gpio_update_output(s); + break; + case R_IN: + case R_IN_DBNC: + qemu_log_mask(LOG_GUEST_ERROR, "%s: W/O registers\n", __func__); + break; + case R_OUT_SHIFT: + qemu_log_mask(LOG_GUEST_ERROR, "%s: not supported\n", __func__); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static void ibexdemo_gpio_input_event(void *opaque, int irq, int level) +{ + IbexDemoGPIOState *s = opaque; + + if (irq < s->in_count) { + if (level) { + s->input |= 1u << irq; + } else { + s->input &= ~(1u << irq); + } + } + + trace_ibexdemo_gpio_input(s->input); +} + +static const MemoryRegionOps ibexdemo_gpio_ops = { + .read = &ibexdemo_gpio_read, + .write = &ibexdemo_gpio_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static const Property ibexdemo_gpio_properties[] = { + DEFINE_PROP_UINT32("in_count", IbexDemoGPIOState, in_count, + IBEXDEMO_GPIO_IN_MAX), + DEFINE_PROP_UINT32("out_count", IbexDemoGPIOState, out_count, + IBEXDEMO_GPIO_IN_MAX), +}; + +static void ibexdemo_gpio_realize(DeviceState *dev, Error **errp) +{ + IbexDemoGPIOState *s = IBEXDEMO_GPIO(dev); + + if (s->in_count > IBEXDEMO_GPIO_IN_MAX) { + s->in_count = IBEXDEMO_GPIO_IN_MAX; + } + if (s->out_count > IBEXDEMO_GPIO_OUT_MAX) { + s->out_count = IBEXDEMO_GPIO_OUT_MAX; + } + + qdev_init_gpio_in_named(dev, &ibexdemo_gpio_input_event, + IBEXDEMO_GPIO_IN_LINES, s->in_count); +} + +static void ibexdemo_gpio_init(Object *obj) +{ + IbexDemoGPIOState *s = IBEXDEMO_GPIO(obj); + + memory_region_init_io(&s->mmio, obj, &ibexdemo_gpio_ops, s, + TYPE_IBEXDEMO_GPIO, 0x1000u); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + + s->gpo = g_new0(qemu_irq, IBEXDEMO_GPIO_OUT_MAX); + + qdev_init_gpio_out_named(DEVICE(obj), s->gpo, IBEXDEMO_GPIO_OUT_LINES, + IBEXDEMO_GPIO_OUT_MAX); +} + +static void ibexdemo_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, &ibexdemo_gpio_reset); + dc->realize = &ibexdemo_gpio_realize; + device_class_set_props(dc, ibexdemo_gpio_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo ibexdemo_gpio_info = { + .name = TYPE_IBEXDEMO_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IbexDemoGPIOState), + .instance_init = &ibexdemo_gpio_init, + .class_init = &ibexdemo_gpio_class_init, +}; + +static void ibexdemo_gpio_register_types(void) +{ + type_register_static(&ibexdemo_gpio_info); +} + +type_init(ibexdemo_gpio_register_types); diff --git a/hw/ibexdemo/ibexdemo_simctrl.c b/hw/ibexdemo/ibexdemo_simctrl.c new file mode 100644 index 0000000000000..98e753ac2730f --- /dev/null +++ b/hw/ibexdemo/ibexdemo_simctrl.c @@ -0,0 +1,133 @@ +/* + * QEMU lowRISC Ibex Demo Sim Control device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/ibexdemo/ibexdemo_simctrl.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/sysbus.h" +#include "system/runstate.h" +#include "trace.h" + +/* clang-format off */ +REG32(OUT, 0x00u) +REG32(CTRL, 0x08u) +/* clang-format on */ + +struct IbexDemoSimCtrlState { + SysBusDevice parent_obj; + + MemoryRegion mmio; +}; + +static uint64_t ibexdemo_simctrl_read(void *opaque, hwaddr addr, + unsigned int size) +{ + uint32_t val32; + + switch (addr >> 2u) { + case R_OUT: + case R_CTRL: + qemu_log_mask(LOG_GUEST_ERROR, "%s: wdata is write only\n", __func__); + val32 = 0; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0; + break; + } + + return (uint64_t)val32; +} + +static void ibexdemo_simctrl_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned int size) +{ + switch (addr >> 2u) { + case R_OUT: + putc((int)(uint8_t)val64, stderr); + break; + case R_CTRL: + if (val64 & 1u) { + /* as a QEMU extension, bits [7:1] are used as exit code */ + qemu_system_shutdown_request_with_code( + SHUTDOWN_CAUSE_GUEST_SHUTDOWN, (val64 >> 1u) & 0x7fu); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static const MemoryRegionOps ibexdemo_simctrl_ops = { + .read = &ibexdemo_simctrl_read, + .write = &ibexdemo_simctrl_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 1, + .impl.max_access_size = 4, +}; + +static void ibexdemo_simctrl_init(Object *obj) +{ + IbexDemoSimCtrlState *s = IBEXDEMO_SIMCTRL(obj); + + memory_region_init_io(&s->mmio, obj, &ibexdemo_simctrl_ops, s, + TYPE_IBEXDEMO_SIMCTRL, 0x400u); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static void ibexdemo_simctrl_realize(DeviceState *dev, Error **errp) +{ + /* empty */ +} + +static void ibexdemo_simctrl_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = &ibexdemo_simctrl_realize; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo ibexdemo_simctrl_info = { + .name = TYPE_IBEXDEMO_SIMCTRL, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IbexDemoSimCtrlState), + .instance_init = &ibexdemo_simctrl_init, + .class_init = &ibexdemo_simctrl_class_init, +}; + +static void ibexdemo_simctrl_register_types(void) +{ + type_register_static(&ibexdemo_simctrl_info); +} + +type_init(ibexdemo_simctrl_register_types); diff --git a/hw/ibexdemo/ibexdemo_spi.c b/hw/ibexdemo/ibexdemo_spi.c new file mode 100644 index 0000000000000..c26816af0f26c --- /dev/null +++ b/hw/ibexdemo/ibexdemo_spi.c @@ -0,0 +1,145 @@ +/* + * QEMU lowRISC Ibex Demo SPI host device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/ibexdemo/ibexdemo_spi.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/ssi/ssi.h" +#include "hw/sysbus.h" +#include "trace.h" + +/* clang-format off */ +REG32(TX, 0x00u) + FIELD(TX, DATA, 0u, 8u) +REG32(STATUS, 0x04u) + FIELD(STATUS, TX_FULL, 0u, 1u) + FIELD(STATUS, TX_EMPTY, 1u, 1u) +/* clang-format on */ + +struct IbexDemoSPIState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + SSIBus *ssi; +}; + +static void ibexdemo_spi_reset(DeviceState *dev) +{ + /* empty */ +} + +static uint64_t ibexdemo_spi_read(void *opaque, hwaddr addr, unsigned int size) +{ + uint32_t val32; + + switch (addr >> 2u) { + case R_TX: + qemu_log_mask(LOG_GUEST_ERROR, "%s: wdata is write only\n", __func__); + val32 = 0; + break; + case R_STATUS: + val32 = FIELD_DP32(0, STATUS, TX_EMPTY, 1u); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0; + break; + } + + return (uint64_t)val32; +} + +static void ibexdemo_spi_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned int size) +{ + IbexDemoSPIState *s = opaque; + + switch (addr >> 2u) { + case R_STATUS: + qemu_log_mask(LOG_GUEST_ERROR, "%s: reg is read only\n", __func__); + break; + case R_TX: + trace_ibexdemo_spi_output((uint8_t)val64); + (void)ssi_transfer(s->ssi, (uint8_t)val64); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static const MemoryRegionOps ibexdemo_spi_ops = { + .read = &ibexdemo_spi_read, + .write = &ibexdemo_spi_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static void ibexdemo_spi_init(Object *obj) +{ + IbexDemoSPIState *s = IBEXDEMO_SPI(obj); + + memory_region_init_io(&s->mmio, obj, &ibexdemo_spi_ops, s, + TYPE_IBEXDEMO_SPI, 0x400u); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + + s->ssi = ssi_create_bus(DEVICE(obj), "spi0"); +} + +static void ibexdemo_spi_realize(DeviceState *dev, Error **errp) +{ + /* empty */ +} + +static void ibexdemo_spi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, &ibexdemo_spi_reset); + dc->realize = &ibexdemo_spi_realize; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo ibexdemo_spi_info = { + .name = TYPE_IBEXDEMO_SPI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IbexDemoSPIState), + .instance_init = &ibexdemo_spi_init, + .class_init = &ibexdemo_spi_class_init, +}; + +static void ibexdemo_spi_register_types(void) +{ + type_register_static(&ibexdemo_spi_info); +} + +type_init(ibexdemo_spi_register_types); diff --git a/hw/ibexdemo/ibexdemo_timer.c b/hw/ibexdemo/ibexdemo_timer.c new file mode 100644 index 0000000000000..7cb1364746478 --- /dev/null +++ b/hw/ibexdemo/ibexdemo_timer.c @@ -0,0 +1,227 @@ +/* + * QEMU lowRISC Ibex Demo Timer device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * based on ibex_timer.c from Western Digital Copyright (c) 2021 + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "hw/ibexdemo/ibexdemo_timer.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/ibex_irq.h" +#include "hw/sysbus.h" + + +REG32(MTIME, 0x00u) +REG32(MTIMEH, 0x04u) +REG32(MTIMECMP, 0x08u) +REG32(MTIMECMPH, 0x0cu) + +struct IbexDemoTimerState { + SysBusDevice parent_obj; + QEMUTimer *timer; + + MemoryRegion mmio; + + uint64_t mtimecmp; + uint64_t timer_compare; + uint32_t timebase_freq; + + IbexIRQ irq; +}; + +static uint64_t ibexdemo_timer_read_rtc(IbexDemoTimerState *s) +{ + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), s->timebase_freq, + NANOSECONDS_PER_SECOND); +} + +static void ibexdemo_timer_update(IbexDemoTimerState *s) +{ + uint64_t now = ibexdemo_timer_read_rtc(s); + + if (now >= s->mtimecmp) { + qemu_log_mask(CPU_LOG_EXEC, + "[Timer IRQ] %08" PRIx64 " >= %08" PRIx64 "\n", now, + s->mtimecmp); + ibex_irq_set(&s->irq, true); + return; + } + + ibex_irq_set(&s->irq, false); + + uint64_t next, diff; + + diff = s->mtimecmp - now; + next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(diff, NANOSECONDS_PER_SECOND, s->timebase_freq); + + if (next < qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) { + timer_mod(s->timer, INT64_MAX); + } else { + timer_mod(s->timer, next); + } +} + +static void ibexdemo_timer_cb(void *opaque) +{ + IbexDemoTimerState *s = opaque; + + ibex_irq_set(&s->irq, true); +} + +static void ibexdemo_timer_reset(DeviceState *dev) +{ + IbexDemoTimerState *s = IBEXDEMO_TIMER(dev); + + timer_del(s->timer); + s->mtimecmp = 0u; + ibex_irq_set(&s->irq, false); + + ibexdemo_timer_update(s); +} + +static uint64_t ibexdemo_timer_read(void *opaque, hwaddr addr, unsigned size) +{ + IbexDemoTimerState *s = opaque; + uint32_t val32; + + switch (addr >> 2u) { + case R_MTIME: + val32 = (uint32_t)ibexdemo_timer_read_rtc(s); + break; + case R_MTIMEH: + val32 = (uint32_t)(ibexdemo_timer_read_rtc(s) >> 32u); + break; + case R_MTIMECMP: + val32 = (uint32_t)s->mtimecmp; + break; + case R_MTIMECMPH: + val32 = (uint32_t)(s->mtimecmp >> 32u); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0; + break; + } + + return (uint64_t)val32; +} + +static void ibexdemo_timer_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned size) +{ + IbexDemoTimerState *s = opaque; + uint64_t tmp; + + switch (addr >> 2) { + case R_MTIME: + case R_MTIMEH: + /* todo: how to support write to mtime? */ + qemu_log_mask(LOG_UNIMP, "Changing timer value is not supported\n"); + break; + case R_MTIMECMP: + tmp = s->mtimecmp >> 32u; + tmp <<= 32u; + s->mtimecmp = tmp | (uint32_t)val64; + ibexdemo_timer_update(s); + break; + case R_MTIMECMPH: + tmp = s->mtimecmp << 32u; + tmp >>= 32u; + val64 <<= 32u; + s->mtimecmp = val64 | tmp; + ibexdemo_timer_update(s); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static const MemoryRegionOps ibexdemo_timer_ops = { + .read = ibexdemo_timer_read, + .write = ibexdemo_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static const Property ibexdemo_timer_properties[] = { + DEFINE_PROP_UINT32("timebase-freq", IbexDemoTimerState, timebase_freq, + 50000000), +}; + +static void ibexdemo_timer_init(Object *obj) +{ + IbexDemoTimerState *s = IBEXDEMO_TIMER(obj); + + memory_region_init_io(&s->mmio, obj, &ibexdemo_timer_ops, s, + TYPE_IBEXDEMO_TIMER, 0x1000u); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + + ibex_sysbus_init_irq(obj, &s->irq); + + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &ibexdemo_timer_cb, s); + + /* + * todo: need to check whether mtime CSR is supported. If so, see + * void riscv_cpu_set_rdtime_fn(); + */ +} + +static void ibexdemo_timer_realize(DeviceState *dev, Error **errp) +{ + IbexDemoTimerState *s = IBEXDEMO_TIMER(dev); + + qdev_init_gpio_out(dev, &s->irq.irq, 1); +} + +static void ibexdemo_timer_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, ibexdemo_timer_reset); + dc->realize = ibexdemo_timer_realize; + device_class_set_props(dc, ibexdemo_timer_properties); +} + +static const TypeInfo ibexdemo_timer_info = { + .name = TYPE_IBEXDEMO_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IbexDemoTimerState), + .instance_init = ibexdemo_timer_init, + .class_init = ibexdemo_timer_class_init, +}; + +static void ibexdemo_timer_register_types(void) +{ + type_register_static(&ibexdemo_timer_info); +} + +type_init(ibexdemo_timer_register_types); diff --git a/hw/ibexdemo/ibexdemo_uart.c b/hw/ibexdemo/ibexdemo_uart.c new file mode 100644 index 0000000000000..73552a0d54c88 --- /dev/null +++ b/hw/ibexdemo/ibexdemo_uart.c @@ -0,0 +1,295 @@ +/* + * QEMU lowRISC Ibex Demo UART device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/fifo8.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "chardev/char-fe.h" +#include "hw/ibexdemo/ibexdemo_uart.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/ibex_irq.h" +#include "hw/sysbus.h" + +/* clang-format off */ +REG32(RX, 0x00u) + FIELD(RX, DATA, 0u, 8u) +REG32(TX, 0x04u) + FIELD(TX, DATA, 0u, 8u) +REG32(STATUS, 0x08u) + FIELD(STATUS, RX_FIFO_EMPTY, 0u, 1u) + FIELD(STATUS, TX_FIFO_FULL, 1u, 1u) +/* clang-format on */ + +#define IBEXDEMO_UART_RX_FIFO_SIZE 128u +#define IBEXDEMO_UART_TX_FIFO_SIZE 128u + +struct IbexDemoUARTState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + IbexIRQ irq; + + CharFrontend chr; + guint watch_tag; + + Fifo8 rx_fifo; + Fifo8 tx_fifo; + bool irq_level; +}; + +static void ibexdemo_uart_update_irq(IbexDemoUARTState *s) +{ + bool irq_level = !fifo8_is_empty(&s->rx_fifo); + ibex_irq_set(&s->irq, irq_level); +} + +static int ibexdemo_uart_can_receive(void *opaque) +{ + IbexDemoUARTState *s = opaque; + + return (int)fifo8_num_free(&s->rx_fifo); +} + +static void ibexdemo_uart_receive(void *opaque, const uint8_t *buf, int size) +{ + IbexDemoUARTState *s = opaque; + + while (size && !fifo8_is_full(&s->rx_fifo)) { + fifo8_push(&s->rx_fifo, *buf++); + size--; + } + + if (size) { + qemu_log_mask(LOG_GUEST_ERROR, "ibexdemo_uart: RX FIFO overflow"); + } + + ibexdemo_uart_update_irq(s); +} + +static void ibexdemo_uart_xmit(IbexDemoUARTState *s) +{ + /* drain the fifo when there's no back-end */ + if (!qemu_chr_fe_backend_connected(&s->chr)) { + fifo8_reset(&s->tx_fifo); + return; + } + + const uint8_t *buf; + uint32_t size; + int ret; + + buf = fifo8_peek_bufptr(&s->tx_fifo, fifo8_num_used(&s->tx_fifo), &size); + ret = qemu_chr_fe_write(&s->chr, buf, size); + + if (ret > 0) { + fifo8_drop(&s->tx_fifo, ret); + } +} + +static gboolean ibexdemo_uart_watch_cb(void *do_not_use, GIOCondition cond, + void *opaque) +{ + IbexDemoUARTState *s = opaque; + + s->watch_tag = 0; + ibexdemo_uart_xmit(s); + + return FALSE; +} + +static void ibexdemo_uart_tx_write(IbexDemoUARTState *s, uint8_t val) +{ + if (!qemu_chr_fe_backend_connected(&s->chr)) { + return; + } + + if (fifo8_is_full(&s->tx_fifo)) { + qemu_log_mask(LOG_GUEST_ERROR, "ibexdemo_uart: TX FIFO overflow"); + return; + } + + fifo8_push(&s->tx_fifo, val); + + ibexdemo_uart_xmit(s); +} + +static void ibexdemo_uart_reset(DeviceState *dev) +{ + IbexDemoUARTState *s = IBEXDEMO_UART(dev); + + fifo8_reset(&s->rx_fifo); + fifo8_reset(&s->tx_fifo); + + ibexdemo_uart_update_irq(s); + + qemu_chr_fe_accept_input(&s->chr); +} + +static uint64_t ibexdemo_uart_read(void *opaque, hwaddr addr, unsigned int size) +{ + IbexDemoUARTState *s = opaque; + uint32_t val32; + bool rx_full; + + switch (addr >> 2u) { + case R_RX: + rx_full = fifo8_is_full(&s->rx_fifo); + if (!fifo8_is_empty(&s->rx_fifo)) { + val32 = fifo8_pop(&s->rx_fifo); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "ibexdemo_uart: RX FIFO underflow"); + val32 = 0; + } + if (rx_full) { + /* + * RX was full and was not accepting any new input; now that one + * byte has been popped out, get ready to receive more + */ + qemu_chr_fe_accept_input(&s->chr); + } + ibexdemo_uart_update_irq(s); + break; + case R_TX: + qemu_log_mask(LOG_GUEST_ERROR, "%s: wdata is write only\n", __func__); + val32 = 0; + break; + case R_STATUS: + val32 = FIELD_DP32(0, STATUS, RX_FIFO_EMPTY, + (uint32_t)fifo8_is_empty(&s->rx_fifo)); + val32 = FIELD_DP32(val32, STATUS, TX_FIFO_FULL, + (uint32_t)fifo8_is_full(&s->tx_fifo)); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + val32 = 0; + break; + } + + return (uint64_t)val32; +} + +static void ibexdemo_uart_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned int size) +{ + IbexDemoUARTState *s = opaque; + + switch (addr >> 2u) { + case R_RX: + case R_STATUS: + qemu_log_mask(LOG_GUEST_ERROR, "%s: reg is read only\n", __func__); + break; + case R_TX: + ibexdemo_uart_tx_write(s, (uint8_t)val64); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static const MemoryRegionOps ibexdemo_uart_ops = { + .read = &ibexdemo_uart_read, + .write = &ibexdemo_uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static const Property ibexdemo_uart_properties[] = { + DEFINE_PROP_CHR("chardev", IbexDemoUARTState, chr), +}; + +static int ibexdemo_uart_be_change(void *opaque) +{ + IbexDemoUARTState *s = opaque; + + qemu_chr_fe_set_handlers(&s->chr, &ibexdemo_uart_can_receive, + &ibexdemo_uart_receive, NULL, + &ibexdemo_uart_be_change, s, NULL, true); + + if (s->watch_tag > 0) { + g_source_remove(s->watch_tag); + s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, + ibexdemo_uart_watch_cb, s); + } + + return 0; +} + +static void ibexdemo_uart_realize(DeviceState *dev, Error **errp) +{ + IbexDemoUARTState *s = IBEXDEMO_UART(dev); + + fifo8_create(&s->tx_fifo, IBEXDEMO_UART_TX_FIFO_SIZE); + + qemu_chr_fe_set_handlers(&s->chr, &ibexdemo_uart_can_receive, + &ibexdemo_uart_receive, NULL, + &ibexdemo_uart_be_change, s, NULL, true); +} + +static void ibexdemo_uart_init(Object *obj) +{ + IbexDemoUARTState *s = IBEXDEMO_UART(obj); + + ibex_sysbus_init_irq(obj, &s->irq); + + memory_region_init_io(&s->mmio, obj, &ibexdemo_uart_ops, s, + TYPE_IBEXDEMO_UART, 0x1000u); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + + fifo8_create(&s->rx_fifo, IBEXDEMO_UART_RX_FIFO_SIZE); + fifo8_create(&s->tx_fifo, IBEXDEMO_UART_TX_FIFO_SIZE); +} + +static void ibexdemo_uart_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, &ibexdemo_uart_reset); + dc->realize = &ibexdemo_uart_realize; + device_class_set_props(dc, ibexdemo_uart_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo ibexdemo_uart_info = { + .name = TYPE_IBEXDEMO_UART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IbexDemoUARTState), + .instance_init = &ibexdemo_uart_init, + .class_init = &ibexdemo_uart_class_init, +}; + +static void ibexdemo_uart_register_types(void) +{ + type_register_static(&ibexdemo_uart_info); +} + +type_init(ibexdemo_uart_register_types); diff --git a/hw/ibexdemo/meson.build b/hw/ibexdemo/meson.build new file mode 100644 index 0000000000000..4a7223adae976 --- /dev/null +++ b/hw/ibexdemo/meson.build @@ -0,0 +1,7 @@ +# Ibex Demo System + +system_ss.add(when: 'CONFIG_IBEXDEMO_GPIO', if_true: files('ibexdemo_gpio.c')) +system_ss.add(when: 'CONFIG_IBEXDEMO_SIMCTRL', if_true: files('ibexdemo_simctrl.c')) +system_ss.add(when: 'CONFIG_IBEXDEMO_SPI', if_true: files('ibexdemo_spi.c')) +system_ss.add(when: 'CONFIG_IBEXDEMO_TIMER', if_true: files('ibexdemo_timer.c')) +system_ss.add(when: 'CONFIG_IBEXDEMO_UART', if_true: files('ibexdemo_uart.c')) diff --git a/hw/ibexdemo/trace-events b/hw/ibexdemo/trace-events new file mode 100644 index 0000000000000..52b20f8b42d4b --- /dev/null +++ b/hw/ibexdemo/trace-events @@ -0,0 +1,10 @@ +# IbexDemo Trace Events + +# ibexdemo_gpio + +ibexdemo_gpio_input(uint32_t gpios) "in: [0x%08x]" +ibexdemo_gpio_output(uint32_t gpios) "out: [0x%08x]" + +# ibexdemo_spi.c + +ibexdemo_spi_output(uint8_t byte) "[0x%02x]" diff --git a/hw/ibexdemo/trace.h b/hw/ibexdemo/trace.h new file mode 100644 index 0000000000000..74ceb8745b56d --- /dev/null +++ b/hw/ibexdemo/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_ibexdemo.h" diff --git a/hw/meson.build b/hw/meson.build index 0f3ff77872a76..8b43644977c8c 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -21,6 +21,7 @@ subdir('sparc64') subdir('tricore') subdir('xtensa') +subdir('ibexdemo') subdir('jtag') subdir('9pfs') diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index b95f89dda1f16..75055a378d1d9 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -18,6 +18,23 @@ config IBEX_GPIO # RISC-V machines in alphabetical order +config IBEXDEMO + bool + default y + select IBEX + select IBEX_CLOCK_SRC + select IBEX_COMMON + select IBEXDEMO_GPIO + select IBEXDEMO_SIMCTRL + select IBEXDEMO_SPI + select IBEXDEMO_TIMER + select IBEXDEMO_UART + select RISCV_DM + select RISCV_DTM + select ST7735 + select TAP_CTRL_RBB + select UNIMP + config MICROCHIP_PFSOC bool default y diff --git a/hw/riscv/ibexdemo.c b/hw/riscv/ibexdemo.c new file mode 100644 index 0000000000000..1d16e622448c2 --- /dev/null +++ b/hw/riscv/ibexdemo.c @@ -0,0 +1,541 @@ +/* + * QEMU RISC-V Board Compatible with Ibex Demo System FPGA platform + * + * Copyright (c) 2022-2025 Rivos, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * Notes: GPIO output, SIMCTRL, SPI, TIMER, UART and ST7735 devices are + * supported. PWM is only a dummy device, GPIO inputs are not supported. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "chardev/chardev-internal.h" +#include "cpu.h" +#include "hw/boards.h" +#include "hw/display/st7735.h" +#include "hw/ibexdemo/ibexdemo_gpio.h" +#include "hw/ibexdemo/ibexdemo_simctrl.h" +#include "hw/ibexdemo/ibexdemo_spi.h" +#include "hw/ibexdemo/ibexdemo_timer.h" +#include "hw/ibexdemo/ibexdemo_uart.h" +#include "hw/jtag/tap_ctrl.h" +#include "hw/jtag/tap_ctrl_rbb.h" +#include "hw/misc/pulp_rv_dm.h" +#include "hw/misc/unimp.h" +#include "hw/qdev-properties.h" +#include "hw/riscv/dm.h" +#include "hw/riscv/dtm.h" +#include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibexdemo.h" +#include "hw/ssi/ssi.h" +#include "qobject/qlist.h" +#include "system/address-spaces.h" +#include "system/system.h" + +/* ------------------------------------------------------------------------ */ +/* Forward Declarations */ +/* ------------------------------------------------------------------------ */ + +static void ibexdemo_soc_dm_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent); +static void ibexdemo_soc_gpio_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent); +static void ibexdemo_soc_hart_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent); +static void ibexdemo_soc_tap_ctrl_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent); +static void ibexdemo_soc_uart_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent); + +/* ------------------------------------------------------------------------ */ +/* Constants */ +/* ------------------------------------------------------------------------ */ + +/* clang-format off */ +static const uint32_t IBEXDEMO_BOOT[] = { + /* Exception vectors */ + 0x0840006fu, 0x0800006fu, 0x07c0006fu, 0x0780006fu, 0x0740006fu, + 0x0700006fu, 0x06c0006fu, 0x0680006fu, 0x0640006fu, 0x0600006fu, + 0x05c0006fu, 0x0580006fu, 0x0540006fu, 0x0500006fu, 0x04c0006fu, + 0x0480006fu, 0x0440006fu, 0x0400006fu, 0x03c0006fu, 0x0380006fu, + 0x0340006fu, 0x0300006fu, 0x02c0006fu, 0x0280006fu, 0x0240006fu, + 0x0200006fu, 0x01c0006fu, 0x0180006fu, 0x0140006fu, 0x0100006fu, + 0x00c0006fu, 0x0080006fu, + /* reset vector */ + 0x0040006fu, + /* blank_loop */ + 0x10500073u, /* wfi */ + 0x0000bff5u, /* j blank_loop */ +}; +/* clang-format on */ + +enum IbexDemoSocDevice { + IBEXDEMO_SOC_DEV_DM, + IBEXDEMO_SOC_DEV_DTM, + IBEXDEMO_SOC_DEV_GPIO, + IBEXDEMO_SOC_DEV_HART, + IBEXDEMO_SOC_DEV_PWM, + IBEXDEMO_SOC_DEV_RV_DM, + IBEXDEMO_SOC_DEV_SIM_CTRL, + IBEXDEMO_SOC_DEV_SPI, + IBEXDEMO_SOC_DEV_TAP_CTRL, + IBEXDEMO_SOC_DEV_TIMER, + IBEXDEMO_SOC_DEV_UART, +}; + +enum IbexDemoBoardDevice { + IBEXDEMO_BOARD_DEV_SOC, + IBEXDEMO_BOARD_DEV_DISPLAY, + _IBEXDEMO_BOARD_DEV_COUNT +}; + +#define IBEXDEMO_SOC_DEVLINK(_pname_, _target_) \ + IBEX_DEVLINK(_pname_, IBEXDEMO_SOC_DEV_##_target_) + +/* + * Ibex Demo System RV DM + * see https://github.com/lowRISC/part-number-registry/blob/main/jtag_partno.md + */ +#define IBEXDEMO_TAP_IDCODE IBEX_JTAG_IDCODE(256, 1, 0) + +#define PULP_DM_BASE 0x00010000u +#define SRAM_MAIN_BASE 0x100000u +#define SRAM_MAIN_SIZE 0x10000u + +#define NUM_PWM_MODULES 12u +#define PWM_SIZE (NUM_PWM_MODULES * 8u) + +#define IBEXDEMO_DM_CONNECTION(_dst_dev_, _num_) \ + { \ + .out = { \ + .name = PULP_RV_DM_ACK_OUT_LINES, \ + .num = (_num_), \ + }, \ + .in = { \ + .name = RISCV_DM_ACK_LINES, \ + .index = (_dst_dev_), \ + .num = (_num_), \ + } \ + } + +static const IbexDeviceDef ibexdemo_soc_devices[] = { + /* clang-format off */ + [IBEXDEMO_SOC_DEV_HART] = { + .type = TYPE_RISCV_CPU_LOWRISC_IBEXDEMO, + .cfg = &ibexdemo_soc_hart_configure, + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("mtvec", 0x00100001u), + IBEX_DEV_UINT_PROP("dmhaltvec", PULP_DM_BASE + + PULP_RV_DM_ROM_BASE + PULP_RV_DM_HALT_OFFSET), + IBEX_DEV_UINT_PROP("dmexcpvec", PULP_DM_BASE + + PULP_RV_DM_ROM_BASE + PULP_RV_DM_EXCEPTION_OFFSET) + ), + }, + [IBEXDEMO_SOC_DEV_TAP_CTRL] = { + .type = TYPE_TAP_CTRL_RBB, + .cfg = &ibexdemo_soc_tap_ctrl_configure, + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("ir_length", IBEX_TAP_IR_LENGTH), + IBEX_DEV_UINT_PROP("idcode", IBEXDEMO_TAP_IDCODE) + ), + }, + [IBEXDEMO_SOC_DEV_DTM] = { + .type = TYPE_RISCV_DTM, + .link = IBEXDEVICELINKDEFS( + IBEXDEMO_SOC_DEVLINK("tap-ctrl", TAP_CTRL) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("abits", 7u) + ), + }, + [IBEXDEMO_SOC_DEV_DM] = { + .type = TYPE_RISCV_DM, + .cfg = &ibexdemo_soc_dm_configure, + .link = IBEXDEVICELINKDEFS( + IBEXDEMO_SOC_DEVLINK("dtm", DTM) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("nscratch", PULP_RV_DM_NSCRATCH_COUNT), + IBEX_DEV_UINT_PROP("progbuf_count", + PULP_RV_DM_PROGRAM_BUFFER_COUNT), + IBEX_DEV_UINT_PROP("data_count", PULP_RV_DM_DATA_COUNT), + IBEX_DEV_UINT_PROP("abstractcmd_count", + PULP_RV_DM_ABSTRACTCMD_COUNT), + IBEX_DEV_UINT_PROP("dm_phyaddr", PULP_DM_BASE), + IBEX_DEV_UINT_PROP("rom_phyaddr", + PULP_DM_BASE + PULP_RV_DM_ROM_BASE), + IBEX_DEV_UINT_PROP("whereto_phyaddr", + PULP_DM_BASE + PULP_RV_DM_WHERETO_OFFSET), + IBEX_DEV_UINT_PROP("data_phyaddr", + PULP_DM_BASE + PULP_RV_DM_DATAADDR_OFFSET), + IBEX_DEV_UINT_PROP("progbuf_phyaddr", + PULP_DM_BASE + PULP_RV_DM_PROGRAM_BUFFER_OFFSET), + IBEX_DEV_UINT_PROP("resume_offset", PULP_RV_DM_RESUME_OFFSET), + IBEX_DEV_BOOL_PROP("sysbus_access", true), + IBEX_DEV_BOOL_PROP("abstractauto", false) + ), + }, + [IBEXDEMO_SOC_DEV_RV_DM] = { + .type = TYPE_PULP_RV_DM, + .memmap = MEMMAPENTRIES( + { .base = 0x00000000u } + ), + .gpio = IBEXGPIOCONNDEFS( + IBEXDEMO_DM_CONNECTION(IBEXDEMO_SOC_DEV_DM, 0), + IBEXDEMO_DM_CONNECTION(IBEXDEMO_SOC_DEV_DM, 1), + IBEXDEMO_DM_CONNECTION(IBEXDEMO_SOC_DEV_DM, 2), + IBEXDEMO_DM_CONNECTION(IBEXDEMO_SOC_DEV_DM, 3) + ), + }, + [IBEXDEMO_SOC_DEV_SIM_CTRL] = { + .type = TYPE_IBEXDEMO_SIMCTRL, + .memmap = MEMMAPENTRIES( + { .base = 0x00020000u } + ), + }, + [IBEXDEMO_SOC_DEV_GPIO] = { + .type = TYPE_IBEXDEMO_GPIO, + .cfg = &ibexdemo_soc_gpio_configure, + .memmap = MEMMAPENTRIES( + { .base = 0x80000000u } + ), + }, + [IBEXDEMO_SOC_DEV_UART] = { + .type = TYPE_IBEXDEMO_UART, + .cfg = &ibexdemo_soc_uart_configure, + .memmap = MEMMAPENTRIES( + { .base = 0x80001000u } + ), + .gpio = IBEXGPIOCONNDEFS( + IBEX_GPIO_SYSBUS_IRQ(0, IBEXDEMO_SOC_DEV_HART, 16) + ), + }, + [IBEXDEMO_SOC_DEV_TIMER] = { + .type = TYPE_IBEXDEMO_TIMER, + .memmap = MEMMAPENTRIES( + { .base = 0x80002000u } + ), + .gpio = IBEXGPIOCONNDEFS( + IBEX_GPIO_SYSBUS_IRQ(0, IBEXDEMO_SOC_DEV_HART, IRQ_M_TIMER) + ), + }, + [IBEXDEMO_SOC_DEV_PWM] = { + .type = TYPE_UNIMPLEMENTED_DEVICE, + .name = "ibexdemo-pwm", + .cfg = &ibex_unimp_configure, + .memmap = MEMMAPENTRIES( + { .base = 0x80003000u } + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("size", PWM_SIZE) + ), + }, + [IBEXDEMO_SOC_DEV_SPI] = { + .type = TYPE_IBEXDEMO_SPI, + .memmap = MEMMAPENTRIES( + { .base = 0x80004000u } + ), + }, + /* clang-format on */ +}; + +/* ------------------------------------------------------------------------ */ +/* Type definitions */ +/* ------------------------------------------------------------------------ */ + +struct IbexDemoSoCState { + SysBusDevice parent_obj; + + DeviceState **devices; + + /* properties */ + uint32_t resetvec; +}; + +struct IbexDemoBoardState { + DeviceState parent_obj; + + DeviceState **devices; +}; + +struct IbexDemoMachineState { + MachineState parent_obj; + + char *rv_exts; +}; + +/* ------------------------------------------------------------------------ */ +/* Device Configuration */ +/* ------------------------------------------------------------------------ */ + +static void ibexdemo_soc_dm_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) +{ + (void)def; + (void)parent; + + QList *hart = qlist_new(); + qlist_append_int(hart, 0); + qdev_prop_set_array(dev, "hart", hart); + + RISCVDMMemAttrs pulp_attrs = { + .attrs = { + .requester_id = PULP_RV_DM_REQUESTER_ID, + }, + }; + qdev_prop_set_uint64(dev, "mta_dm", pulp_attrs.value); +} + + +static void ibexdemo_soc_gpio_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) +{ + (void)def; + (void)parent; + qdev_prop_set_uint32(dev, "in_count", IBEXDEMO_GPIO_IN_MAX); + qdev_prop_set_uint32(dev, "out_count", IBEXDEMO_GPIO_OUT_MAX); +} + +static void ibexdemo_soc_hart_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) +{ + (void)def; + (void)parent; + IbexDemoSoCState *s = RISCV_IBEXDEMO_SOC(parent); + + qdev_prop_set_uint64(dev, "resetvec", s->resetvec); +} + +static void ibexdemo_soc_tap_ctrl_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) +{ + (void)parent; + (void)def; + + Chardev *chr; + + chr = ibex_get_chardev_by_id("taprbb"); + if (chr) { + qdev_prop_set_chr(dev, "chardev", chr); + } +} + +static void ibexdemo_soc_uart_configure( + DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) +{ + (void)parent; + qdev_prop_set_chr(dev, "chardev", serial_hd(def->instance)); +} + +/* ------------------------------------------------------------------------ */ +/* SoC */ +/* ------------------------------------------------------------------------ */ + +static void ibexdemo_soc_load_boot(void) +{ + /* do not use rom_add_blob_fixed_as as absolute address is not yet known */ + MachineState *ms = MACHINE(qdev_get_machine()); + void *ram = memory_region_get_ram_ptr(ms->ram); + if (!ram) { + error_setg(&error_fatal, "no main RAM"); + /* linter cannot detect error_fatal prevents from returning */ + abort(); + } + memcpy(ram, IBEXDEMO_BOOT, sizeof(IBEXDEMO_BOOT)); +} + +static void ibexdemo_soc_reset(DeviceState *dev) +{ + IbexDemoSoCState *s = RISCV_IBEXDEMO_SOC(dev); + + device_cold_reset(s->devices[IBEXDEMO_SOC_DEV_DTM]); + device_cold_reset(s->devices[IBEXDEMO_SOC_DEV_DM]); + + cpu_reset(CPU(s->devices[IBEXDEMO_SOC_DEV_HART])); +} + +static void ibexdemo_soc_realize(DeviceState *dev, Error **errp) +{ + (void)errp; + + IbexDemoSoCState *s = RISCV_IBEXDEMO_SOC(dev); + + MachineState *ms = MACHINE(qdev_get_machine()); + MemoryRegion *sys_mem = get_system_memory(); + memory_region_add_subregion(sys_mem, SRAM_MAIN_BASE, ms->ram); + + ibex_link_devices(s->devices, ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices)); + ibex_define_device_props(s->devices, ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices)); + ibex_realize_system_devices(s->devices, ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices)); + ibex_clock_devices(s->devices, ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices)); + ibex_connect_devices(s->devices, ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices)); + + ibexdemo_soc_load_boot(); + + /* load application if provided */ + ibex_load_kernel(NULL); +} + +static void ibexdemo_soc_init(Object *obj) +{ + IbexDemoSoCState *s = RISCV_IBEXDEMO_SOC(obj); + + s->devices = + ibex_create_devices(ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices), DEVICE(s)); +} + +static const Property ibexdemo_soc_props[] = { + DEFINE_PROP_UINT32("resetvec", IbexDemoSoCState, resetvec, 0x00100080u), +}; + +static void ibexdemo_soc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + (void)data; + + device_class_set_props(dc, ibexdemo_soc_props); + device_class_set_legacy_reset(dc, &ibexdemo_soc_reset); + dc->realize = &ibexdemo_soc_realize; + dc->user_creatable = false; +} + +static const TypeInfo ibexdemo_soc_type_info = { + .name = TYPE_RISCV_IBEXDEMO_SOC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IbexDemoSoCState), + .instance_init = &ibexdemo_soc_init, + .class_init = &ibexdemo_soc_class_init, +}; + +static void ibexdemo_soc_register_types(void) +{ + type_register_static(&ibexdemo_soc_type_info); +} + +type_init(ibexdemo_soc_register_types); + +/* ------------------------------------------------------------------------ */ +/* Board */ +/* ------------------------------------------------------------------------ */ + +static void ibexdemo_board_realize(DeviceState *dev, Error **errp) +{ + IbexDemoBoardState *board = RISCV_IBEXDEMO_BOARD(dev); + (void)errp; + + IbexDemoSoCState *soc = + RISCV_IBEXDEMO_SOC(board->devices[IBEXDEMO_BOARD_DEV_SOC]); + + sysbus_realize_and_unref(SYS_BUS_DEVICE(soc), &error_fatal); + + BusState *spibus = + qdev_get_child_bus(DEVICE(soc->devices[IBEXDEMO_SOC_DEV_SPI]), "spi0"); + g_assert(spibus); + + board->devices[IBEXDEMO_BOARD_DEV_DISPLAY] = + DEVICE(ST7735(ssi_create_peripheral(SSI_BUS(spibus), TYPE_ST7735))); + + qemu_irq cs, dc, rst; + + dev = board->devices[IBEXDEMO_BOARD_DEV_DISPLAY]; + cs = qdev_get_gpio_in_named(dev, SSI_GPIO_CS, 0); + dc = qdev_get_gpio_in_named(dev, ST7735_IO_LINES, ST7735_IO_D_C); + rst = qdev_get_gpio_in_named(dev, ST7735_IO_LINES, ST7735_IO_RESET); + + dev = soc->devices[IBEXDEMO_SOC_DEV_GPIO]; + qdev_connect_gpio_out_named(dev, IBEXDEMO_GPIO_OUT_LINES, 0, cs); + qdev_connect_gpio_out_named(dev, IBEXDEMO_GPIO_OUT_LINES, 1, rst); + qdev_connect_gpio_out_named(dev, IBEXDEMO_GPIO_OUT_LINES, 2, dc); +} + +static void ibexdemo_board_instance_init(Object *obj) +{ + IbexDemoBoardState *s = RISCV_IBEXDEMO_BOARD(obj); + + s->devices = g_new0(DeviceState *, _IBEXDEMO_BOARD_DEV_COUNT); + s->devices[IBEXDEMO_BOARD_DEV_SOC] = qdev_new(TYPE_RISCV_IBEXDEMO_SOC); + + object_property_add_child(obj, "soc", + OBJECT(s->devices[IBEXDEMO_BOARD_DEV_SOC])); +} + +static void ibexdemo_board_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + (void)data; + + dc->realize = &ibexdemo_board_realize; +} + +static const TypeInfo ibexdemo_board_type_info = { + .name = TYPE_RISCV_IBEXDEMO_BOARD, + .parent = TYPE_DEVICE, + .instance_size = sizeof(IbexDemoBoardState), + .instance_init = &ibexdemo_board_instance_init, + .class_init = &ibexdemo_board_class_init, +}; + +static void ibexdemo_board_register_types(void) +{ + type_register_static(&ibexdemo_board_type_info); +} + +type_init(ibexdemo_board_register_types); + +/* ------------------------------------------------------------------------ */ +/* Machine */ +/* ------------------------------------------------------------------------ */ + +static void ibexdemo_machine_init(MachineState *state) +{ + DeviceState *dev = qdev_new(TYPE_RISCV_IBEXDEMO_BOARD); + + object_property_add_child(OBJECT(state), "board", OBJECT(dev)); + + qdev_realize(dev, NULL, &error_fatal); +} + +static void ibexdemo_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + (void)data; + + mc->desc = "RISC-V Board compatible with IbexDemo"; + mc->init = ibexdemo_machine_init; + mc->max_cpus = 1u; + mc->default_cpu_type = ibexdemo_soc_devices[IBEXDEMO_SOC_DEV_HART].type; + mc->default_ram_id = "ibexdemo.ram"; + mc->default_ram_size = SRAM_MAIN_SIZE; +} + +static const TypeInfo ibexdemo_machine_type_info = { + .name = TYPE_RISCV_IBEXDEMO_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(IbexDemoMachineState), + .class_init = &ibexdemo_machine_class_init, +}; + +static void ibexdemo_machine_register_types(void) +{ + type_register_static(&ibexdemo_machine_type_info); +} + +type_init(ibexdemo_machine_register_types); diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index 6d626c3bd3726..a71cf5e81ff3a 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -2,7 +2,11 @@ riscv_ss = ss.source_set() riscv_ss.add(files('boot.c')) riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c')) riscv_ss.add(files('riscv_hart.c')) +riscv_ss.add(when: 'CONFIG_IBEXDEMO', if_true: files('ibexdemo.c')) riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c')) +riscv_ss.add(when: 'CONFIG_IBEX_CLOCK_SRC', if_true: files('ibex_clock_src.c')) +riscv_ss.add(when: 'CONFIG_IBEX_COMMON', if_true: files('ibex_common.c')) +riscv_ss.add(when: 'CONFIG_IBEX_GPIO', if_true: files('ibex_gpio.c')) riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c')) riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c')) riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c')) diff --git a/include/hw/display/st7735.h b/include/hw/display/st7735.h new file mode 100644 index 0000000000000..9ed02a4550e6e --- /dev/null +++ b/include/hw/display/st7735.h @@ -0,0 +1,45 @@ +/* + * QEMU Sitronix ST7735 controller device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_DISPLAY_ST7735_H +#define HW_DISPLAY_ST7735_H + +#include "qemu/osdep.h" +#include "qom/object.h" +#include "exec/hwaddr.h" + +#define TYPE_ST7735 "st7735" +OBJECT_DECLARE_SIMPLE_TYPE(St7735State, ST7735) + +#define ST7735_IO_LINES TYPE_ST7735 ".io" + +enum { + ST7735_IO_RESET, + ST7735_IO_D_C, + ST7735_IO_COUNT, +}; + +void st7735_configure(DeviceState *dev, hwaddr addr); + +#endif /* HW_DISPLAY_ST7735_H */ diff --git a/include/hw/ibexdemo/ibexdemo_gpio.h b/include/hw/ibexdemo/ibexdemo_gpio.h new file mode 100644 index 0000000000000..2d25bafae535d --- /dev/null +++ b/include/hw/ibexdemo/ibexdemo_gpio.h @@ -0,0 +1,41 @@ +/* + * QEMU lowRISC Ibex Demo SPI host device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_GPIO_IBEXDEMO_GPIO_H +#define HW_GPIO_IBEXDEMO_GPIO_H + +#include "qemu/osdep.h" +#include "qom/object.h" +#include "exec/hwaddr.h" + +#define TYPE_IBEXDEMO_GPIO "ibexdemo-gpio" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoGPIOState, IBEXDEMO_GPIO) + +#define IBEXDEMO_GPIO_IN_MAX 8u +#define IBEXDEMO_GPIO_OUT_MAX 16u + +#define IBEXDEMO_GPIO_IN_LINES TYPE_IBEXDEMO_GPIO ".in" +#define IBEXDEMO_GPIO_OUT_LINES TYPE_IBEXDEMO_GPIO ".out" + +#endif /* HW_GPIO_IBEXDEMO_GPIO_H */ diff --git a/include/hw/ibexdemo/ibexdemo_simctrl.h b/include/hw/ibexdemo/ibexdemo_simctrl.h new file mode 100644 index 0000000000000..a80f363b78671 --- /dev/null +++ b/include/hw/ibexdemo/ibexdemo_simctrl.h @@ -0,0 +1,35 @@ +/* + * QEMU lowRISC Ibex Demo Sim Control device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_MISC_IBEXDEMO_SIMCTRL_H +#define HW_MISC_IBEXDEMO_SIMCTRL_H + +#include "qemu/osdep.h" +#include "qom/object.h" +#include "exec/hwaddr.h" + +#define TYPE_IBEXDEMO_SIMCTRL "ibexdemo-simctrl" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoSimCtrlState, IBEXDEMO_SIMCTRL) + +#endif /* HW_MISC_IBEXDEMO_SIMCTRL_H */ diff --git a/include/hw/ibexdemo/ibexdemo_spi.h b/include/hw/ibexdemo/ibexdemo_spi.h new file mode 100644 index 0000000000000..3a675f95b7efb --- /dev/null +++ b/include/hw/ibexdemo/ibexdemo_spi.h @@ -0,0 +1,35 @@ +/* + * QEMU lowRISC Ibex Demo SPI host device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_SSI_IBEXDEMO_SPI_H +#define HW_SSI_IBEXDEMO_SPI_H + +#include "qemu/osdep.h" +#include "qom/object.h" +#include "exec/hwaddr.h" + +#define TYPE_IBEXDEMO_SPI "ibexdemo-spi" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoSPIState, IBEXDEMO_SPI) + +#endif /* HW_SSI_IBEXDEMO_SPI_H */ diff --git a/include/hw/ibexdemo/ibexdemo_timer.h b/include/hw/ibexdemo/ibexdemo_timer.h new file mode 100644 index 0000000000000..9101a55f1c7f0 --- /dev/null +++ b/include/hw/ibexdemo/ibexdemo_timer.h @@ -0,0 +1,34 @@ +/* + * QEMU lowRISC Ibex Demo Timer device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_TIMER_IBEXDEMO_TIMER_H +#define HW_TIMER_IBEXDEMO_TIMER_H + +#include "qemu/osdep.h" +#include "qom/object.h" + +#define TYPE_IBEXDEMO_TIMER "ibexdemo-timer" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoTimerState, IBEXDEMO_TIMER) + +#endif /* HW_TIMER_IBEXDEMO_TIMER_H */ diff --git a/include/hw/ibexdemo/ibexdemo_uart.h b/include/hw/ibexdemo/ibexdemo_uart.h new file mode 100644 index 0000000000000..efd3839e1cea0 --- /dev/null +++ b/include/hw/ibexdemo/ibexdemo_uart.h @@ -0,0 +1,35 @@ +/* + * QEMU lowRISC Ibex Demo UART device + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * 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_CHAR_IBEXDEMO_UART_H +#define HW_CHAR_IBEXDEMO_UART_H + +#include "qemu/osdep.h" +#include "qom/object.h" +#include "exec/hwaddr.h" + +#define TYPE_IBEXDEMO_UART "ibexdemo-uart" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoUARTState, IBEXDEMO_UART) + +#endif /* HW_CHAR_IBEXDEMO_UART_H */ diff --git a/include/hw/riscv/ibexdemo.h b/include/hw/riscv/ibexdemo.h new file mode 100644 index 0000000000000..1060e01708f07 --- /dev/null +++ b/include/hw/riscv/ibexdemo.h @@ -0,0 +1,33 @@ +/* + * QEMU RISC-V Board Compatible with Ibex Demo FPGA platform + * + * Copyright (c) 2022-2023 Rivos, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef HW_RISCV_IBEXDEMO_H +#define HW_RISCV_IBEXDEMO_H + +#include "qom/object.h" + +#define TYPE_RISCV_IBEXDEMO_MACHINE MACHINE_TYPE_NAME("ibexdemo") +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoMachineState, RISCV_IBEXDEMO_MACHINE) + +#define TYPE_RISCV_IBEXDEMO_BOARD "riscv.ibexdemo.board" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoBoardState, RISCV_IBEXDEMO_BOARD) + +#define TYPE_RISCV_IBEXDEMO_SOC "riscv.ibexdemo.soc" +OBJECT_DECLARE_SIMPLE_TYPE(IbexDemoSoCState, RISCV_IBEXDEMO_SOC) + +#endif /* HW_RISCV_IBEXDEMO_H */ diff --git a/meson.build b/meson.build index fd9a231e9e852..67fd0a873645c 100644 --- a/meson.build +++ b/meson.build @@ -3635,6 +3635,7 @@ if have_system 'hw/dma', 'hw/fsi', 'hw/hyperv', + 'hw/ibexdemo', 'hw/i2c', 'hw/i386', 'hw/i386/xen', diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h index 58b0f71122bb2..a25e4daded652 100644 --- a/target/riscv/cpu-qom.h +++ b/target/riscv/cpu-qom.h @@ -35,6 +35,8 @@ #define TYPE_RISCV_CPU_BASE64 RISCV_CPU_TYPE_NAME("rv64") #define TYPE_RISCV_CPU_BASE128 RISCV_CPU_TYPE_NAME("x-rv128") #define TYPE_RISCV_CPU_LOWRISC_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") +#define TYPE_RISCV_CPU_LOWRISC_IBEXDEMO RISCV_CPU_TYPE_NAME("lowrisc-ibexdemo") +#define TYPE_RISCV_CPU_LOWRISC_OPENTITAN RISCV_CPU_TYPE_NAME("lowrisc-opentitan") #define TYPE_RISCV_CPU_RV32I RISCV_CPU_TYPE_NAME("rv32i") #define TYPE_RISCV_CPU_RV32E RISCV_CPU_TYPE_NAME("rv32e") #define TYPE_RISCV_CPU_RV64I RISCV_CPU_TYPE_NAME("rv64i") diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 987339b5557b5..e0149351b3cfa 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -3158,6 +3158,9 @@ static const TypeInfo riscv_cpu_type_infos[] = { #endif ), + DEFINE_RISCV_CPU(TYPE_RISCV_CPU_LOWRISC_IBEXDEMO, TYPE_RISCV_CPU_LOWRISC_IBEX, + ), + DEFINE_RISCV_CPU(TYPE_RISCV_CPU_SIFIVE_E31, TYPE_RISCV_CPU_SIFIVE_E, .misa_mxl_max = MXL_RV32 ), From 4f8046554cb71c4f3292150a89d3ea29d0604c22 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 Jan 2026 14:53:40 +0000 Subject: [PATCH 13/18] [ot] hw/opentitan: otbn: add OTBN emulator The emulator is written in Rust and based on Greg Chadwick's RRS RISC-V simulator. Signed-off-by: James Wainwright --- .gitignore | 2 + hw/opentitan/meson.build | 1 + hw/opentitan/otbn/Kconfig | 2 + hw/opentitan/otbn/LICENSE | 191 +++ hw/opentitan/otbn/README.md | 7 + hw/opentitan/otbn/meson.build | 29 + hw/opentitan/otbn/otbn/Cargo.toml | 17 + hw/opentitan/otbn/otbn/meson.build | 7 + hw/opentitan/otbn/otbn/src/comm.rs | 35 + hw/opentitan/otbn/otbn/src/csrs.rs | 556 +++++++ hw/opentitan/otbn/otbn/src/insn_decode.rs | 290 ++++ hw/opentitan/otbn/otbn/src/insn_disasm.rs | 478 ++++++ hw/opentitan/otbn/otbn/src/insn_exec.rs | 1316 +++++++++++++++++ hw/opentitan/otbn/otbn/src/insn_format.rs | 220 +++ hw/opentitan/otbn/otbn/src/insn_proc.rs | 100 ++ hw/opentitan/otbn/otbn/src/key.rs | 57 + hw/opentitan/otbn/otbn/src/lib.rs | 85 ++ hw/opentitan/otbn/otbn/src/memory.rs | 135 ++ hw/opentitan/otbn/otbn/src/otbn.rs | 556 +++++++ hw/opentitan/otbn/otbn/src/proxy.rs | 573 +++++++ hw/opentitan/otbn/otbn/src/random.rs | 213 +++ hw/opentitan/otbn/otbn/src/xoshiro256pp.rs | 123 ++ meson.build | 2 +- subprojects/bitflags.wrap | 8 + subprojects/ethnum-rs.wrap | 7 + subprojects/packagefiles/bitflags/0001.patch | 13 + subprojects/packagefiles/ethnum-rs/0001.patch | 14 + subprojects/packagefiles/paste/0001.patch | 13 + subprojects/paste.wrap | 8 + 29 files changed, 5057 insertions(+), 1 deletion(-) create mode 100644 hw/opentitan/meson.build create mode 100644 hw/opentitan/otbn/Kconfig create mode 100644 hw/opentitan/otbn/LICENSE create mode 100644 hw/opentitan/otbn/README.md create mode 100644 hw/opentitan/otbn/meson.build create mode 100644 hw/opentitan/otbn/otbn/Cargo.toml create mode 100644 hw/opentitan/otbn/otbn/meson.build create mode 100644 hw/opentitan/otbn/otbn/src/comm.rs create mode 100644 hw/opentitan/otbn/otbn/src/csrs.rs create mode 100644 hw/opentitan/otbn/otbn/src/insn_decode.rs create mode 100644 hw/opentitan/otbn/otbn/src/insn_disasm.rs create mode 100644 hw/opentitan/otbn/otbn/src/insn_exec.rs create mode 100644 hw/opentitan/otbn/otbn/src/insn_format.rs create mode 100644 hw/opentitan/otbn/otbn/src/insn_proc.rs create mode 100644 hw/opentitan/otbn/otbn/src/key.rs create mode 100644 hw/opentitan/otbn/otbn/src/lib.rs create mode 100644 hw/opentitan/otbn/otbn/src/memory.rs create mode 100644 hw/opentitan/otbn/otbn/src/otbn.rs create mode 100644 hw/opentitan/otbn/otbn/src/proxy.rs create mode 100644 hw/opentitan/otbn/otbn/src/random.rs create mode 100644 hw/opentitan/otbn/otbn/src/xoshiro256pp.rs create mode 100644 subprojects/bitflags.wrap create mode 100644 subprojects/ethnum-rs.wrap create mode 100644 subprojects/packagefiles/bitflags/0001.patch create mode 100644 subprojects/packagefiles/ethnum-rs/0001.patch create mode 100644 subprojects/packagefiles/paste/0001.patch create mode 100644 subprojects/paste.wrap diff --git a/.gitignore b/.gitignore index bda3992c1873e..0da66420614c4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,7 @@ GTAGS subprojects/*-*/ subprojects/packagecache/ !subprojects/packagefiles/**/*.patch +hw/opentitan/otbn/otbn/Cargo.lock +hw/opentitan/otbn/otbn/target/ .clangd .zed diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build new file mode 100644 index 0000000000000..42efa1dfc4f1d --- /dev/null +++ b/hw/opentitan/meson.build @@ -0,0 +1 @@ +subdir('otbn') diff --git a/hw/opentitan/otbn/Kconfig b/hw/opentitan/otbn/Kconfig new file mode 100644 index 0000000000000..c861aa0ddf9fc --- /dev/null +++ b/hw/opentitan/otbn/Kconfig @@ -0,0 +1,2 @@ +config OT_BIGNUMBER + bool diff --git a/hw/opentitan/otbn/LICENSE b/hw/opentitan/otbn/LICENSE new file mode 100644 index 0000000000000..d0c55eec33113 --- /dev/null +++ b/hw/opentitan/otbn/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions of +this Software are embedded into an Object form of such source code, you may +redistribute such embedded portions in such Object form without complying with +the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a court +of competent jurisdiction determines that the patent provision (Section 3), the +indemnity provision (Section 9) or other Section of the License conflicts with +the conditions of the GPLv2, you may retroactively and prospectively choose to +deem waived or otherwise exclude such Section(s) of the License, but only in +their entirety and only with respect to the Combined Software. + + END OF TERMS AND CONDITIONS diff --git a/hw/opentitan/otbn/README.md b/hw/opentitan/otbn/README.md new file mode 100644 index 0000000000000..d0785a91ee7ee --- /dev/null +++ b/hw/opentitan/otbn/README.md @@ -0,0 +1,7 @@ +# Rust OpenTitan BigNumber Simulator (otbn-rs) + +A fork of the https://github.com/GregAC/rrs.git Rust RISC-V simulator, dedicated +to the OTBN accelerator. + +RRS and the OTBN simulator are released under the Apache License Version 2.0, +with LLVM Exceptions, see LICENSE for details. diff --git a/hw/opentitan/otbn/meson.build b/hw/opentitan/otbn/meson.build new file mode 100644 index 0000000000000..a743e17bab0d1 --- /dev/null +++ b/hw/opentitan/otbn/meson.build @@ -0,0 +1,29 @@ +bitflags_dep = subproject('bitflags').get_variable('bitflags_dep') +paste_dep = subproject('paste').get_variable('paste_dep') +ethnum_dep = subproject('ethnum-rs').get_variable('ethnum_dep') + +if build_machine.system() == 'linux' + dl_dep = cc.find_library('dl', required: false) +else + dl_dep = dependency('', required: false) +endif + +if build_machine.system() == 'linux' + dl_dep = cc.find_library('dl', required: false) +else + dl_dep = dependency('', required: false) +endif + +otbn_lib = static_library('otbn', + files('otbn/src/lib.rs'), + dependencies: [ + bitflags_dep, + paste_dep, + ethnum_dep, + dl_dep], + rust_crate_type: 'staticlib') + +otbn_dep = declare_dependency(link_with: otbn_lib) + +system_ss.add(when: 'CONFIG_OT_BIGNUMBER', + if_true: otbn_dep) diff --git a/hw/opentitan/otbn/otbn/Cargo.toml b/hw/opentitan/otbn/otbn/Cargo.toml new file mode 100644 index 0000000000000..8620eebe2a7da --- /dev/null +++ b/hw/opentitan/otbn/otbn/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "otbn-lib" +version = "0.1.0" +authors = ["Greg Chadwick ", + "Emmanuel Blot "] +edition = "2018" +license = "Apache-2.0 WITH LLVM-exception" +keywords = ["opentitan", "emulator"] +categories = ["emulators"] +description = "A library for building OpenTitan Big Number simulators" + +# Heavily based on RSS work from Greg Chadwick https://gregchadwick.co.uk/blog/building-rrs-pt1/ + +[dependencies] +paste = "1" +bitflags = "1" +ethnum = "1" diff --git a/hw/opentitan/otbn/otbn/meson.build b/hw/opentitan/otbn/otbn/meson.build new file mode 100644 index 0000000000000..82a02d1072675 --- /dev/null +++ b/hw/opentitan/otbn/otbn/meson.build @@ -0,0 +1,7 @@ +project('otbn', 'rust', version: '0.1.0') + +otbn_lib = static_library('otbn', + 'src/lib.rs', + install: true) + +otbn_dep = declare_dependency(link_with: otbn_lib) diff --git a/hw/opentitan/otbn/otbn/src/comm.rs b/hw/opentitan/otbn/otbn/src/comm.rs new file mode 100644 index 0000000000000..e134f34df6086 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/comm.rs @@ -0,0 +1,35 @@ +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::fs::File; +use std::io; +use std::sync::mpsc; +use std::thread; + +/// Commands the client may send to the executer +pub enum Command { + SetTestMode(bool), + LogTo(Box>), + Execute(bool), + WipeDMem, + WipeIMem, + Terminate, +} + +/// Replies the executer may send back to the client +pub enum Response { + Active(thread::ThreadId), + Ack, + Error(String), +} + +/// Channel to send commands from the client to the executer and to receive response back from it +pub type DownChannel = (mpsc::Sender, mpsc::Receiver); +/// Channel to receive commands from the client and to send it back responses +pub type UpChannel = (mpsc::Receiver, mpsc::Sender); + +/// A callback trait for core signalling +pub trait Callback: Send { + fn signal(&mut self); +} diff --git a/hw/opentitan/otbn/otbn/src/csrs.rs b/hw/opentitan/otbn/otbn/src/csrs.rs new file mode 100644 index 0000000000000..46ae6515887f3 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/csrs.rs @@ -0,0 +1,556 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::cell::Cell; +use std::convert::TryFrom; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; + +use ethnum::{u256, U256}; + +use super::insn_proc; +use super::key; +use super::otbn::FlagMode; +use super::random; +use super::{CSR, WSR}; +use crate::{ExceptionCause, CSRNG, PRNG}; + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[repr(u32)] +#[allow(non_camel_case_types)] +pub enum CSRAddr { + // OTBN custom CSRs + fg0 = 0x7c0, + fg1 = 0x7c1, + flags = 0x7c8, + mod0 = 0x7d0, + mod1 = 0x7d1, + mod2 = 0x7d2, + mod3 = 0x7d3, + mod4 = 0x7d4, + mod5 = 0x7d5, + mod6 = 0x7d6, + mod7 = 0x7d7, + rnd_prefetch = 0x7d8, + rnd = 0xfc0, // CSRNG + urnd = 0xfc1, // PRNG +} + +impl TryFrom for CSRAddr { + type Error = u32; + + fn try_from(value: u32) -> Result { + match value { + 0x7c0 => Ok(Self::fg0), + 0x7c1 => Ok(Self::fg1), + 0x7c8 => Ok(Self::flags), + 0x7d0 => Ok(Self::mod0), + 0x7d1 => Ok(Self::mod1), + 0x7d2 => Ok(Self::mod2), + 0x7d3 => Ok(Self::mod3), + 0x7d4 => Ok(Self::mod4), + 0x7d5 => Ok(Self::mod5), + 0x7d6 => Ok(Self::mod6), + 0x7d7 => Ok(Self::mod7), + 0x7d8 => Ok(Self::rnd_prefetch), + 0xfc0 => Ok(Self::rnd), + 0xfc1 => Ok(Self::urnd), + _ => Err(value), + } + } +} + +#[allow(clippy::from_over_into)] +impl Into for CSRAddr { + fn into(self) -> u32 { + self as u32 + } +} + +impl CSRAddr { + pub fn string_name(csr_addr: u32) -> String { + match Self::try_from(csr_addr) { + Ok(csr) => format!("{:?}", csr), + Err(_) => format!("0x{:03x}", csr_addr), + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[repr(u32)] +#[allow(non_camel_case_types)] +pub enum WSRAddr { + r#mod = 0x0, + rnd = 0x1, + urnd = 0x2, + acc = 0x3, + key_s0_l = 0x4, + key_s0_h = 0x5, + key_s1_l = 0x6, + key_s1_h = 0x7, +} + +impl TryFrom for WSRAddr { + type Error = u32; + + fn try_from(value: u32) -> Result { + match value { + 0x0 => Ok(Self::r#mod), + 0x1 => Ok(Self::rnd), + 0x2 => Ok(Self::urnd), + 0x3 => Ok(Self::acc), + 0x4 => Ok(Self::key_s0_l), + 0x5 => Ok(Self::key_s0_h), + 0x6 => Ok(Self::key_s1_l), + 0x7 => Ok(Self::key_s1_h), + _ => Err(value), + } + } +} + +#[allow(clippy::from_over_into)] +impl Into for WSRAddr { + fn into(self) -> u32 { + self as u32 + } +} + +impl WSRAddr { + pub fn string_name(wsr_addr: u32) -> String { + match Self::try_from(wsr_addr) { + Ok(wsr) => format!("{:?}", wsr), + Err(_) => format!("0x{:03x}", wsr_addr), + } + } +} + +#[derive(Default)] +struct CSRFlagGroup { + flags: SharedFlags, + mode: Option, +} + +impl CSRFlagGroup { + pub fn plug(&mut self, flags: &SharedFlags, mode: FlagMode) { + self.flags = Rc::clone(flags); + self.mode = Some(mode); + } +} + +impl CSR for CSRFlagGroup { + fn read(&self) -> Result { + let val8 = match self.mode.as_ref().unwrap() { + FlagMode::Fg0 => self.flags.get()[0].bits(), + FlagMode::Fg1 => self.flags.get()[1].bits(), + FlagMode::Flags => self.flags.get()[0].bits() | self.flags.get()[1].bits() << 4, + }; + Ok(val8 as u32) + } + + fn write(&mut self, val: u32) -> Result<(), ExceptionCause> { + let val8 = val as u8; + let mut flags = self.flags.get(); + match self.mode.as_ref().unwrap() { + FlagMode::Fg0 => flags[0] = insn_proc::Flags::from_bits_truncate(val8 & 0x0f), + FlagMode::Fg1 => flags[1] = insn_proc::Flags::from_bits_truncate(val8 & 0x0f), + FlagMode::Flags => { + flags[0] = insn_proc::Flags::from_bits_truncate(val8 & 0x0f); + flags[1] = insn_proc::Flags::from_bits_truncate(val8 & 0x0f); + } + }; + self.flags.set(flags); + Ok(()) + } +} + +type SharedFlags = Rc>; + +#[derive(Default)] +struct CSRMod { + wsg: Rc>, + pos: Option, +} + +impl CSR for CSRMod { + fn read(&self) -> Result { + let mut val256: u256 = self.wsg.get(); + val256 >>= self.pos.unwrap() * 32; + val256 &= U256::from(u32::MAX); + Ok(val256.as_u32()) + } + + fn write(&mut self, val: u32) -> Result<(), ExceptionCause> { + let mut val256: u256 = self.wsg.get(); + let shift = self.pos.unwrap() * 32; + val256 &= !(U256::from(u32::MAX).wrapping_shl(shift)); + val256 |= U256::from(val).wrapping_shl(shift); + self.wsg.set(val256); + Ok(()) + } +} + +impl CSRMod { + fn plug(&mut self, wsg: &CSRWideSharedGeneric, pos: usize) { + self.wsg = Rc::clone(&wsg.val); + self.pos = Some(pos as u32); + } +} + +/// CryptoSecure Random generator +struct CSRRnd { + rng: Arc, +} + +impl CSRRnd { + pub fn new(rng: Arc) -> Self { + Self { rng } + } +} + +impl CSR for CSRRnd { + fn read(&self) -> Result { + let (val, fips, repeat) = self.rng.get_csrng_u32(); + if !fips { + Err(ExceptionCause::ERndFipsChkFail) + } else if repeat { + Err(ExceptionCause::ERndRepChkFail) + } else { + Ok(val) + } + } + + fn write(&mut self, _val: u32) -> Result<(), ExceptionCause> { + // as per OTBN definition, do not generate an error for R/O CSR + Ok(()) + } +} + +/// CryptoSecure Random generator +struct CSRWideRnd { + rng: Arc, +} + +impl CSRWideRnd { + pub fn new(rng: Arc) -> Self { + Self { rng } + } +} + +impl WSR for CSRWideRnd { + fn read(&self) -> Result { + let (val, fips, repeat) = self.rng.get_csrng_u256(); + if !fips { + Err(ExceptionCause::ERndFipsChkFail) + } else if repeat { + Err(ExceptionCause::ERndRepChkFail) + } else { + Ok(val) + } + } + + fn write(&mut self, _val: u256) -> Result<(), ExceptionCause> { + // as per OTBN definition, do not generate an error for R/O CSR + Ok(()) + } +} + +/// Pseudo Random generator +struct CSRUrnd { + prng: Arc>, +} + +impl CSRUrnd { + pub fn new(prng: Arc>) -> Self { + Self { prng } + } +} + +impl CSR for CSRUrnd { + fn read(&self) -> Result { + Ok(self.prng.lock().unwrap().get_prng_u32()) + } + + fn write(&mut self, _val: u32) -> Result<(), ExceptionCause> { + // as per OTBN definition, do not generate an error for R/O CSR + Ok(()) + } +} + +/// Pseudo Random generator +struct CSRWideUrnd { + prng: Arc>, + test_mode: bool, +} + +impl CSRWideUrnd { + pub fn new(prng: Arc>) -> Self { + Self { + prng, + test_mode: false, + } + } +} + +impl WSR for CSRWideUrnd { + fn read(&self) -> Result { + if !self.test_mode { + Ok(self.prng.lock().unwrap().get_prng_u256()) + } else { + Ok(U256::from_str_radix( + "AAAAAAAA99999999AAAAAAAA99999999AAAAAAAA99999999AAAAAAAA99999999", + 16, + ) + .unwrap()) + } + } + + fn write(&mut self, _val: u256) -> Result<(), ExceptionCause> { + // as per OTBN definition, do not generate an error for R/O CSR + Ok(()) + } +} + +struct CSRRndPrefetcher { + rng: Arc, +} + +impl CSRRndPrefetcher { + pub fn new(rng: Arc) -> Self { + Self { rng } + } +} + +impl CSR for CSRRndPrefetcher { + fn read(&self) -> Result { + Ok(0) // always 0 + } + + fn write(&mut self, _val: u32) -> Result<(), ExceptionCause> { + // "Writing any value to the RND_PREFETCH CSR initiates a prefetch." + self.rng.prefetch(); + Ok(()) + } +} + +/// CryptoSecure Random generator +struct CSRWideKey { + key: Arc, + share: u8, + high: bool, +} + +impl CSRWideKey { + pub fn new(key: Arc, share: u8, high: bool) -> Self { + Self { key, share, high } + } +} + +impl WSR for CSRWideKey { + fn read(&self) -> Result { + let store = self.key.store.lock().unwrap(); + if !store.valid { + return Err(ExceptionCause::EKeyInvalid); + } + let share = if self.share == 0 { store.lo } else { store.hi }; + let value = share[self.high as usize]; + Ok(value) + } + + fn write(&mut self, _val: u256) -> Result<(), ExceptionCause> { + // as per OTBN definition, do not generate an error for R/O CSR + Ok(()) + } +} + +#[derive(Default)] +struct CSRWideGeneric { + pub val: u256, +} + +impl WSR for CSRWideGeneric { + fn read(&self) -> Result { + Ok(self.val) + } + + fn write(&mut self, val: u256) -> Result<(), ExceptionCause> { + self.val = val; + Ok(()) + } +} + +#[derive(Default)] +struct CSRWideSharedGeneric { + // TBC: why can't I use an Option>> here (Copy trait)? + pub val: Rc>, +} + +impl WSR for CSRWideSharedGeneric { + fn read(&self) -> Result { + Ok(self.val.get()) + } + + fn write(&mut self, val: u256) -> Result<(), ExceptionCause> { + self.val.set(val); + Ok(()) + } +} + +pub struct CSRSet { + fg0: CSRFlagGroup, + fg1: CSRFlagGroup, + flags: CSRFlagGroup, + mods: [CSRMod; 8], + rndprefetch: CSRRndPrefetcher, + rnd: CSRRnd, + urnd: CSRUrnd, + + r#mod: CSRWideSharedGeneric, + wrnd: CSRWideRnd, + wurnd: CSRWideUrnd, + acc: CSRWideGeneric, + key_s0_l: CSRWideKey, + key_s0_h: CSRWideKey, + key_s1_l: CSRWideKey, + key_s1_h: CSRWideKey, + + shared_flags: SharedFlags, +} + +impl CSRSet { + pub fn new(urnd: Arc>, rnd: Arc, key: Arc) -> Self { + let mut csrs = Self { + fg0: CSRFlagGroup::default(), + fg1: CSRFlagGroup::default(), + flags: CSRFlagGroup::default(), + mods: <[CSRMod; 8]>::default(), + rndprefetch: CSRRndPrefetcher::new(rnd.clone()), + rnd: CSRRnd::new(rnd.clone()), + urnd: CSRUrnd::new(urnd.clone()), + r#mod: CSRWideSharedGeneric::default(), + wrnd: CSRWideRnd::new(rnd), + wurnd: CSRWideUrnd::new(urnd.clone()), + acc: CSRWideGeneric::default(), + key_s0_l: CSRWideKey::new(key.clone(), 0, false), + key_s0_h: CSRWideKey::new(key.clone(), 0, true), + key_s1_l: CSRWideKey::new(key.clone(), 1, false), + key_s1_h: CSRWideKey::new(key.clone(), 1, true), + shared_flags: SharedFlags::default(), + }; + csrs.fg0.plug(&csrs.shared_flags, FlagMode::Fg0); + csrs.fg1.plug(&csrs.shared_flags, FlagMode::Fg1); + csrs.flags.plug(&csrs.shared_flags, FlagMode::Flags); + for (pos, otmod) in csrs.mods.iter_mut().enumerate() { + otmod.plug(&csrs.r#mod, pos) + } + csrs + } + + pub fn get_flags(&self, fg: usize) -> insn_proc::Flags { + self.shared_flags.get()[fg] + } + + pub fn set_flags(&mut self, fg: usize, value: insn_proc::Flags) { + let mut flags = self.shared_flags.get(); + flags[fg] = value; + self.shared_flags.set(flags); + } + + pub fn get_csr(&self, addr: u32) -> Option<&dyn CSR> { + let csr_addr = CSRAddr::try_from(addr).ok()?; + + // note: we do not want to use num_enum here that draws to many dependencies + Some(match csr_addr { + CSRAddr::fg0 => &self.fg0, + CSRAddr::fg1 => &self.fg1, + CSRAddr::flags => &self.flags, + CSRAddr::mod0 => &self.mods[0], + CSRAddr::mod1 => &self.mods[1], + CSRAddr::mod2 => &self.mods[2], + CSRAddr::mod3 => &self.mods[3], + CSRAddr::mod4 => &self.mods[4], + CSRAddr::mod5 => &self.mods[5], + CSRAddr::mod6 => &self.mods[6], + CSRAddr::mod7 => &self.mods[7], + CSRAddr::rnd_prefetch => &self.rndprefetch, + CSRAddr::rnd => &self.rnd, + CSRAddr::urnd => &self.urnd, + }) + } + + pub fn get_csr_mut(&mut self, addr: u32) -> Option<&mut dyn CSR> { + let csr_addr = CSRAddr::try_from(addr).ok()?; + + // note: we do not want to use num_enum here that draws to many dependencies + Some(match csr_addr { + CSRAddr::fg0 => &mut self.fg0, + CSRAddr::fg1 => &mut self.fg1, + CSRAddr::flags => &mut self.flags, + CSRAddr::mod0 => &mut self.mods[0], + CSRAddr::mod1 => &mut self.mods[1], + CSRAddr::mod2 => &mut self.mods[2], + CSRAddr::mod3 => &mut self.mods[3], + CSRAddr::mod4 => &mut self.mods[4], + CSRAddr::mod5 => &mut self.mods[5], + CSRAddr::mod6 => &mut self.mods[6], + CSRAddr::mod7 => &mut self.mods[7], + CSRAddr::rnd_prefetch => &mut self.rndprefetch, + CSRAddr::rnd => &mut self.rnd, + CSRAddr::urnd => &mut self.urnd, + }) + } + + pub fn get_wsr(&self, addr: u32) -> Option<&dyn WSR> { + let wsr_addr = WSRAddr::try_from(addr).ok()?; + + Some(match wsr_addr { + WSRAddr::r#mod => &self.r#mod, + WSRAddr::rnd => &self.wrnd, + WSRAddr::urnd => &self.wurnd, + WSRAddr::acc => &self.acc, + WSRAddr::key_s0_l => &self.key_s0_l, + WSRAddr::key_s0_h => &self.key_s0_h, + WSRAddr::key_s1_l => &self.key_s1_l, + WSRAddr::key_s1_h => &self.key_s1_h, + }) + } + + pub fn get_wsr_mut(&mut self, addr: u32) -> Option<&mut dyn WSR> { + let wsr_addr = WSRAddr::try_from(addr).ok()?; + + Some(match wsr_addr { + WSRAddr::r#mod => &mut self.r#mod, + WSRAddr::rnd => &mut self.wrnd, + WSRAddr::urnd => &mut self.wurnd, + WSRAddr::acc => &mut self.acc, + WSRAddr::key_s0_l => &mut self.key_s0_l, + WSRAddr::key_s0_h => &mut self.key_s0_h, + WSRAddr::key_s1_l => &mut self.key_s1_l, + WSRAddr::key_s1_h => &mut self.key_s1_h, + }) + } + + pub fn wipe_internal(&mut self, prng: &Arc>) { + /* + * real HW performs a two-step process: + * - wipe with PRNG randomness + * - zero + */ + for flag in self.shared_flags.get().iter_mut() { + *flag = insn_proc::Flags::empty(); + } + + let mut prng = prng.lock().unwrap(); + + let _ = self.acc.write(U256::from(0u32)); + let _ = self.r#mod.write(U256::from(0u32)); + + let _ = self.acc.write(prng.get_prng_u256()); + let _ = self.r#mod.write(prng.get_prng_u256()); + } + + pub fn set_test_mode(&mut self, enable: bool) { + self.wurnd.test_mode = enable; + } +} diff --git a/hw/opentitan/otbn/otbn/src/insn_decode.rs b/hw/opentitan/otbn/otbn/src/insn_decode.rs new file mode 100644 index 0000000000000..4c2a5a76e0ab5 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/insn_decode.rs @@ -0,0 +1,290 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use super::insn_format; +use super::insn_proc; + +fn process_opcode_op( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::RType::new(insn_bits); + + match dec_insn.funct3 { + 0b000 => match dec_insn.funct7 { + 0b000_0000 => Some(processor.process_add(dec_insn)), + 0b010_0000 => Some(processor.process_sub(dec_insn)), + _ => None, + }, + 0b001 => match dec_insn.funct7 { + 0b000_0000 => Some(processor.process_sll(dec_insn)), + _ => None, + }, + 0b100 => match dec_insn.funct7 { + 0b000_0000 => Some(processor.process_xor(dec_insn)), + _ => None, + }, + 0b101 => match dec_insn.funct7 { + 0b000_0000 => Some(processor.process_srl(dec_insn)), + 0b010_0000 => Some(processor.process_sra(dec_insn)), + _ => None, + }, + 0b110 => match dec_insn.funct7 { + 0b000_0000 => Some(processor.process_or(dec_insn)), + _ => None, + }, + 0b111 => match dec_insn.funct7 { + 0b000_0000 => Some(processor.process_and(dec_insn)), + _ => None, + }, + _ => None, + } +} + +fn process_opcode_op_imm( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::IType::new(insn_bits); + + match dec_insn.funct3 { + 0b000 => Some(processor.process_addi(dec_insn)), + 0b001 => Some(processor.process_slli(insn_format::ITypeShamt::new(insn_bits))), + 0b100 => Some(processor.process_xori(dec_insn)), + 0b101 => { + let dec_insn_shamt = insn_format::ITypeShamt::new(insn_bits); + match dec_insn_shamt.funct7 { + 0b000_0000 => Some(processor.process_srli(dec_insn_shamt)), + 0b010_0000 => Some(processor.process_srai(dec_insn_shamt)), + _ => None, + } + } + 0b110 => Some(processor.process_ori(dec_insn)), + 0b111 => Some(processor.process_andi(dec_insn)), + _ => None, + } +} + +fn process_opcode_op32( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::RType::new(insn_bits); + + let (b30, b29) = ( + (dec_insn.funct7 >> 4 & 0b10) != 0, + (dec_insn.funct7 >> 4 & 0b01) != 0, + ); + match (b30, b29) { + (false, false) => Some(processor.process_bn_mulqacc(dec_insn)), + (false, true) => Some(processor.process_bn_mulqacc_wo(dec_insn)), + (true, _) => Some(processor.process_bn_mulqacc_so(dec_insn)), + } +} + +fn process_opcode_branch( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::BType::new(insn_bits); + + match dec_insn.funct3 { + 0b000 => Some(processor.process_beq(dec_insn)), + 0b001 => Some(processor.process_bne(dec_insn)), + _ => None, + } +} + +fn process_opcode_load( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::IType::new(insn_bits); + + match dec_insn.funct3 { + 0b010 => Some(processor.process_lw(dec_insn)), + _ => None, + } +} + +fn process_opcode_custom0( + processor: &mut T, + insn_bits: u32, +) -> Option { + match (insn_bits >> 12) & 0x7 { + 0b000..=0b011 => { + let dec_insn = insn_format::RType::new(insn_bits); + match dec_insn.funct3 { + 0b000 => Some(processor.process_bn_sel(dec_insn)), + 0b001 => Some(processor.process_bn_cmp(dec_insn)), + 0b011 => Some(processor.process_bn_cmpb(dec_insn)), + _ => None, + } + } + 0b100..=0b101 => { + let dec_insn = insn_format::WidType::new(insn_bits); + match dec_insn.funct3 { + 0b100 => Some(processor.process_bn_lid(dec_insn)), + 0b101 => Some(processor.process_bn_sid(dec_insn)), + _ => None, + } + } + 0b110 => match insn_bits >> 31 & 0b1 { + 0b0 => { + let dec_insn = insn_format::IType::new(insn_bits); + Some(processor.process_bn_mov(dec_insn)) + } + 0b1 => { + let dec_insn = insn_format::SType::new(insn_bits); + Some(processor.process_bn_movr(dec_insn)) + } + _ => None, + }, + 0b111 => { + let dec_insn = insn_format::IType::new(insn_bits); + + match insn_bits >> 31 { + 0b0 => Some(processor.process_bn_wsrr(dec_insn)), + 0b1 => Some(processor.process_bn_wsrw(dec_insn)), + _ => None, + } + } + _ => None, + } +} + +fn process_opcode_store( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::SType::new(insn_bits); + + match dec_insn.funct3 { + 0b010 => Some(processor.process_sw(dec_insn)), + _ => None, + } +} + +fn process_opcode_custom1( + processor: &mut T, + insn_bits: u32, +) -> Option { + match (insn_bits >> 12) & 0x7 { + 0b100 => { + let dec_insn = insn_format::IType::new(insn_bits); + match insn_bits >> 30 & 0b1 { + 0 => Some(processor.process_bn_addi(dec_insn)), + 1 => Some(processor.process_bn_subi(dec_insn)), + _ => None, + } + } + _ => { + let dec_insn = insn_format::RType::new(insn_bits); + match dec_insn.funct3 { + 0b000 => Some(processor.process_bn_add(dec_insn)), + 0b001 => Some(processor.process_bn_sub(dec_insn)), + 0b010 => Some(processor.process_bn_addc(dec_insn)), + 0b011 => Some(processor.process_bn_subb(dec_insn)), + 0b101 => match insn_bits >> 30 & 0b1 { + 0b0 => Some(processor.process_bn_addm(dec_insn)), + 0b1 => Some(processor.process_bn_subm(dec_insn)), + _ => None, + }, + _ => None, + } + } + } +} + +fn process_opcode_system( + processor: &mut T, + insn_bits: u32, +) -> Option { + let dec_insn = insn_format::ITypeCSR::new(insn_bits); + + match dec_insn.funct3 { + 0b000 => { + if dec_insn.rd != 0 || dec_insn.rs1 != 0 || dec_insn.csr != 0 { + None + } else { + Some(processor.process_ecall()) + } + } + 0b001 => Some(processor.process_csrrw(dec_insn)), + 0b010 => Some(processor.process_csrrs(dec_insn)), + _ => None, + } +} + +fn process_opcode_custom3( + processor: &mut T, + insn_bits: u32, +) -> Option { + match (insn_bits >> 12) & 0x7 { + 0b000..=0b001 => { + let dec_insn = insn_format::IType::new(insn_bits); + match dec_insn.funct3 { + 0b000 => Some(processor.process_loop(dec_insn)), + 0b001 => Some(processor.process_loopi(dec_insn)), + _ => None, + } + } + 0b010..=0b111 => { + let dec_insn = insn_format::RType::new(insn_bits); + match dec_insn.funct3 { + 0b010 => Some(processor.process_bn_and(dec_insn)), + 0b011 => Some(processor.process_bn_rshi(dec_insn)), + 0b100 => Some(processor.process_bn_or(dec_insn)), + 0b101 => Some(processor.process_bn_not(dec_insn)), + 0b110 => Some(processor.process_bn_xor(dec_insn)), + 0b111 => Some(processor.process_bn_rshi(dec_insn)), + _ => None, + } + } + _ => None, + } +} + +/// Decodes instruction in `insn_bits` calling the appropriate function in `processor` returning +/// the result it produces. +/// +/// Returns `None` if instruction doesn't decode into a valid instruction. +pub fn decoder( + processor: &mut T, + insn_bits: u32, +) -> Option { + let opcode: u32 = insn_bits & 0x7f; + + match opcode { + // LOAD + 0b000_0011 => process_opcode_load(processor, insn_bits), + // custom-0 + 0b000_1011 => process_opcode_custom0(processor, insn_bits), + // OP_IMM + 0b001_0011 => process_opcode_op_imm(processor, insn_bits), + // STORE + 0b010_0011 => process_opcode_store(processor, insn_bits), + // custom-1 + 0b010_1011 => process_opcode_custom1(processor, insn_bits), + // OP + 0b011_0011 => process_opcode_op(processor, insn_bits), + // LUI + 0b011_0111 => Some(processor.process_lui(insn_format::UType::new(insn_bits))), + // OP32 + // this one is suspicious, should not it be custom-2 (0b10_110_11) instead? + 0b011_1011 => process_opcode_op32(processor, insn_bits), + // BRANCH + 0b110_0011 => process_opcode_branch(processor, insn_bits), + // JALR + 0b110_0111 => Some(processor.process_jalr(insn_format::IType::new(insn_bits))), + // JAL + 0b110_1111 => Some(processor.process_jal(insn_format::JType::new(insn_bits))), + // SYSTEM + 0b111_0011 => process_opcode_system(processor, insn_bits), + // custom-3 + 0b111_1011 => process_opcode_custom3(processor, insn_bits), + _ => None, + } +} diff --git a/hw/opentitan/otbn/otbn/src/insn_disasm.rs b/hw/opentitan/otbn/otbn/src/insn_disasm.rs new file mode 100644 index 0000000000000..6ca0334182658 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/insn_disasm.rs @@ -0,0 +1,478 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use paste::paste; + +use super::csrs; +use super::insn_format; +use super::insn_proc; + +pub struct InstructionStringOutputter { + /// PC of the instruction being output. Used to generate disassembly of + /// instructions with PC relative fields (such as BEQ and JAL). + pub insn_pc: u32, +} + +// Macros to produce string outputs for various different instruction types +macro_rules! string_out_for_alu_reg_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + format!("{} x{}, x{}, x{}", stringify!($name), dec_insn.rd, dec_insn.rs1, + dec_insn.rs2) + } + } + }; +} + +macro_rules! string_out_for_alu_imm_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::IType + ) -> Self::InstructionResult { + format!("{}i x{}, x{}, {}", stringify!($name), dec_insn.rd, dec_insn.rs1, + dec_insn.imm) + } + } + }; +} + +macro_rules! string_out_for_alu_imm_shamt_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::ITypeShamt + ) -> Self::InstructionResult { + format!("{}i x{}, x{}, {}", stringify!($name), dec_insn.rd, dec_insn.rs1, + dec_insn.shamt) + } + } + }; +} + +macro_rules! string_out_for_alu_ops { + ($($name:ident),*) => { + $( + string_out_for_alu_reg_op! {$name} + string_out_for_alu_imm_op! {$name} + )* + } +} + +macro_rules! string_out_for_shift_ops { + ($($name:ident),*) => { + $( + string_out_for_alu_reg_op! {$name} + string_out_for_alu_imm_shamt_op! {$name} + )* + } +} + +macro_rules! string_out_for_branch_ops { + ($($name:ident),*) => { + $( + paste! { + fn []( + &mut self, + dec_insn: insn_format::BType + ) -> Self::InstructionResult { + let branch_pc = self.insn_pc.wrapping_add(dec_insn.imm as u32); + + format!("{} x{}, x{}, 0x{:08x}", stringify!($name), dec_insn.rs1, dec_insn.rs2, + branch_pc) + } + } + )* + } +} + +macro_rules! string_out_for_load_ops { + ($($name:ident),*) => { + $( + paste! { + fn []( + &mut self, + dec_insn: insn_format::IType + ) -> Self::InstructionResult { + format!("{} x{}, {}(x{})", stringify!($name), dec_insn.rd, dec_insn.imm, + dec_insn.rs1) + } + } + )* + } +} + +macro_rules! string_out_for_store_ops { + ($($name:ident),*) => { + $( + paste! { + fn []( + &mut self, + dec_insn: insn_format::SType + ) -> Self::InstructionResult { + format!("{} x{}, {}(x{})", stringify!($name), dec_insn.rs2, dec_insn.imm, + dec_insn.rs1) + } + } + )* + } +} + +macro_rules! string_out_for_bn_alu_imm_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::IType + ) -> Self::InstructionResult { + let imm = dec_insn.imm & 0x3ff; + let fg = (dec_insn.imm >> 11) & 0b1; + format!("{}i x{}, x{}, {}, FG{}", stringify!($name), dec_insn.rd, dec_insn.rs1, + imm, fg) + } + } + }; +} + +macro_rules! string_out_for_csr_rr_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::ITypeCSR + ) -> Self::InstructionResult { + format!("{} x{}, {}, x{}", stringify!($name), dec_insn.rd, + csrs::CSRAddr::string_name(dec_insn.csr), + dec_insn.rs1) + } + } + }; +} + +macro_rules! string_out_for_csr_ops { + ($($name:ident),*) => { + $( + string_out_for_csr_rr_op! {$name} + )* + }; +} + +// Macros to produce string outputs for various different BN instruction types +macro_rules! string_out_for_alu_reg_bn_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + let sb_val = (dec_insn.funct7 & 0b0011111) << 3; + let sb = if sb_val != 0 { + let st = if dec_insn.funct7 & 0b010_0000 != 0 { + ">>" + } else { + "<<" + }; + format!(" {} {}", st, sb_val) + } else { + "".to_owned() + }; + let fg = (dec_insn.funct7 >> 6) & 0b1; + format!("bn.{} w{}, w{}, w{}{}, FG{}", stringify!($name), dec_insn.rd, dec_insn.rs1, + dec_insn.rs2, sb, fg) + } + } + }; +} + +// Macros to produce string outputs for various different BN instruction types +macro_rules! string_out_for_alu_reg_bn_mod_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + format!("bn.{}m w{}, w{}, w{}", stringify!($name), dec_insn.rd, dec_insn.rs1, + dec_insn.rs2) + } + } + }; +} + +macro_rules! string_out_for_alu_reg_bn_cmp_op { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + let sb_val = (dec_insn.funct7 & 0b0011111) << 3; + let sb = if sb_val != 0 { + let st = if dec_insn.funct7 & 0b010_0000 != 0 { + ">>" + } else { + "<<" + }; + format!(" {} {}", st, sb_val) + } else { + "".to_owned() + }; + let fg = (dec_insn.funct7 >> 6) & 0b1; + format!("bn.{} w{}, w{}{}, FG{}", stringify!($name), + dec_insn.rs1, dec_insn.rs2, sb, fg) + } + } + }; +} + +macro_rules! string_out_for_alu_reg_bn_ops { + ($($name:ident),*) => { + $( + string_out_for_alu_reg_bn_op! {$name} + )* + }; +} + +macro_rules! string_out_for_alu_reg_bn_mod_ops { + ($($name:ident),*) => { + $( + string_out_for_alu_reg_bn_mod_op! {$name} + )* + }; +} + +macro_rules! string_out_for_alu_reg_bn_cmp_ops { + ($($name:ident),*) => { + $( + string_out_for_alu_reg_bn_cmp_op! {$name} + )* + }; +} + +impl insn_proc::InstructionProcessor for InstructionStringOutputter { + type InstructionResult = String; + + // TODO: Make one macro that takes all names as arguments and generates all the functions + // together + string_out_for_alu_ops! {add, xor, or, and} + string_out_for_alu_reg_op! {sub} + string_out_for_shift_ops! {sll, srl, sra} + + fn process_lui(&mut self, dec_insn: insn_format::UType) -> Self::InstructionResult { + format!("lui x{}, 0x{:08x}", dec_insn.rd, dec_insn.imm) + } + + string_out_for_branch_ops! {beq, bne} + string_out_for_load_ops! {lw} + string_out_for_store_ops! {sw} + + string_out_for_bn_alu_imm_op! {add} + string_out_for_bn_alu_imm_op! {sub} + + fn process_jal(&mut self, dec_insn: insn_format::JType) -> Self::InstructionResult { + let target_pc = self.insn_pc.wrapping_add(dec_insn.imm as u32); + format!("jal x{}, 0x{:08x}", dec_insn.rd, target_pc) + } + + fn process_jalr(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + format!( + "jalr x{}, 0x{:03x}(x{})", + dec_insn.rd, dec_insn.imm, dec_insn.rs1 + ) + } + + string_out_for_csr_ops! {csrrs} + + fn process_csrrw(&mut self, dec_insn: insn_format::ITypeCSR) -> Self::InstructionResult { + format!( + "csrrw x{}, {}, x{}", + dec_insn.rd, + csrs::CSRAddr::string_name(dec_insn.csr), + dec_insn.rs1 + ) + } + + fn process_ecall(&mut self) -> Self::InstructionResult { + String::from("ecall") + } + + fn process_loop(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + format!("loop x{}, {}", dec_insn.rs1, dec_insn.imm + 1) + } + + fn process_loopi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + format!( + "loopi {}, {}", + (dec_insn.rs1 << 5) | dec_insn.rd, + dec_insn.imm + 1 + ) + } + + fn process_bn_lid(&mut self, dec_insn: insn_format::WidType) -> Self::InstructionResult { + let grs1 = dec_insn.rs1; + let grd = dec_insn.rs2; + let offset = dec_insn.imm << 5; + + let grs1_inc = if (dec_insn.inc & 0b10) != 0 { "++" } else { "" }; + let grd_inc = if (dec_insn.inc & 0b01) != 0 { "++" } else { "" }; + + format!( + "bn.lid x{}{}, {}(x{}{})", + grd, grd_inc, offset, grs1, grs1_inc + ) + } + + fn process_bn_sid(&mut self, dec_insn: insn_format::WidType) -> Self::InstructionResult { + let grs1 = dec_insn.rs1; + let grs2 = dec_insn.rs2; + let offset = dec_insn.imm << 5; + + let grs1_inc = if (dec_insn.inc & 0b10) != 0 { "++" } else { "" }; + let grs2_inc = if (dec_insn.inc & 0b01) != 0 { "++" } else { "" }; + + format!( + "bn.sid x{}{}, {}(x{}{})", + grs2, grs2_inc, offset, grs1, grs1_inc + ) + } + + fn process_bn_sel(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let fg = (dec_insn.funct7 >> 6) & 0b1; + let selbit = 1 << (dec_insn.funct7 & 0x3); + let flag = match insn_proc::Flags::from_bits_truncate(selbit as u8) { + insn_proc::Flags::CARRY => "C", + insn_proc::Flags::MSB => "M", + insn_proc::Flags::LSB => "L", + insn_proc::Flags::ZERO => "Z", + _ => "", // never happens. Is there a better way to handle this? + }; + format!( + "bn.sel w{}, w{}, w{}, FG{}.{}", + dec_insn.rd, dec_insn.rs1, dec_insn.rs2, fg, flag + ) + } + + string_out_for_alu_reg_bn_cmp_ops! {cmp, cmpb} + + fn process_bn_mov(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + format!("bn.mov w{}, w{}", dec_insn.rd, dec_insn.rs1) + } + + fn process_bn_movr(&mut self, dec_insn: insn_format::SType) -> Self::InstructionResult { + let grd_inc = if dec_insn.imm & 0b0001 != 0 { "++" } else { "" }; + let grs_inc = if dec_insn.imm & 0b0100 != 0 { "++" } else { "" }; + + format!( + "bn.movr x{}{}, x{}{}", + dec_insn.rs2, grd_inc, dec_insn.rs1, grs_inc + ) + } + + fn process_bn_wsrr(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let wsr = dec_insn.imm & 0xff; + + format!("bn.wsrr {}, {}", dec_insn.rd, wsr) + } + + fn process_bn_wsrw(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let wsr = dec_insn.imm & 0xff; + + format!("bn.wsrw {}, {}", wsr, dec_insn.rs1) + } + + fn process_bn_mulqacc(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let zero_acc = if dec_insn.funct3 & 0b001 != 0 { + ".z" + } else { + "" + }; + let acc_shift = 64 * (dec_insn.funct3 >> 1); + let qwsel1 = dec_insn.funct7 & 0b11; + let qwsel2 = dec_insn.funct7 >> 2 & 0b11; + + format!( + "bn.mulqacc{} w{}.{}, w{}.{}, {}", + zero_acc, dec_insn.rs1, qwsel1, dec_insn.rs2, qwsel2, acc_shift + ) + } + + fn process_bn_mulqacc_wo(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let zero_acc = if dec_insn.funct3 & 0b001 != 0 { + ".z" + } else { + "" + }; + let acc_shift = 64 * (dec_insn.funct3 >> 1); + let fg = (dec_insn.funct7 >> 6) & 0b1; + let qwsel1 = dec_insn.funct7 & 0b11; + let qwsel2 = dec_insn.funct7 >> 2 & 0b11; + + format!( + "bn.mulqacc.wo{} w{}, w{}.{}, w{}.{}, {} FG{}", + zero_acc, dec_insn.rd, dec_insn.rs1, qwsel1, dec_insn.rs2, qwsel2, acc_shift, fg + ) + } + + fn process_bn_mulqacc_so(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let zero_acc = if dec_insn.funct3 & 0b001 != 0 { + ".z" + } else { + "" + }; + let acc_shift = 64 * (dec_insn.funct3 >> 1); + let fg = (dec_insn.funct7 >> 6) & 0b1; + let qwsel1 = dec_insn.funct7 & 0b11; + let qwsel2 = dec_insn.funct7 >> 2 & 0b11; + let hwsel = if dec_insn.funct7 & 0b001_0000 != 0 { + "u" + } else { + "l" + }; + + format!( + "bn.mulqacc.so{} w{}.{}, w{}.{}, w{}.{}, {} FG{}", + zero_acc, dec_insn.rd, hwsel, dec_insn.rs1, qwsel1, dec_insn.rs2, qwsel2, acc_shift, fg + ) + } + + string_out_for_alu_reg_bn_ops! {and, or, xor, add, addc, sub, subb} + string_out_for_alu_reg_bn_mod_ops! {add, sub} + + fn process_bn_not(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let sb_val = (dec_insn.funct7 & 0b0011111) << 3; + let sb = if sb_val != 0 { + let st = if dec_insn.funct7 & 0b010_0000 != 0 { + ">>" + } else { + "<<" + }; + format!(" {} {}", st, sb_val) + } else { + "".to_owned() + }; + let fg = (dec_insn.funct7 >> 6) & 0b1; + format!("bn.not w{}, w{}{} FG{}", dec_insn.rd, dec_insn.rs2, sb, fg) + } + + fn process_bn_rshi(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let imm = (dec_insn.funct7 << 1) | (dec_insn.funct3 >> 2); + let shimm = if imm != 0 { + format!(" >> {}", imm) + } else { + "".to_owned() + }; + + format!( + "bn.rshi w{}, w{}, w{}{}", + dec_insn.rd, dec_insn.rs1, dec_insn.rs2, shimm + ) + } +} diff --git a/hw/opentitan/otbn/otbn/src/insn_exec.rs b/hw/opentitan/otbn/otbn/src/insn_exec.rs new file mode 100644 index 0000000000000..661f1dbb5ba74 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/insn_exec.rs @@ -0,0 +1,1316 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::convert::TryInto; +use std::fmt; +use std::mem::size_of; +use std::sync::{Arc, Mutex}; + +use ethnum::{u256, AsU256, U256}; +use paste::paste; + +use super::csrs; +use super::insn_decode; +use super::insn_format; +use super::insn_proc; +use super::key; +use super::random; +use super::Memory; +use crate::{ExceptionCause, PRNG}; + +/// OTBN wide register width +const WLEN: usize = size_of::() * 8; + +/// Different traps that can occur during instruction execution +#[derive(Debug, PartialEq, Eq)] +pub enum InstructionTrap { + /// Trap is a synchronous exception, with a particular cause. + Exception(ExceptionCause, Option), +} + +/// HW loop tuple +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Loop { + /// remaining loop to execute + pub count: u32, + /// absolute start address of the loop + pub start: u32, + /// absolute end address of the loop + pub end: u32, +} + +impl fmt::Debug for Loop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Loop") + .field("count", &self.count) + .field("start", &format_args!("0x{:04x}", self.start)) + .field("end", &format_args!("0x{:04x}", self.end)) + .finish() + } +} + +/// Track updated hart state, for debugging purpose only +#[derive(Default)] +pub struct StateTracker { + pub gpr: Option, // GPR, if any + pub wgpr: Option, // wide GPR, if any + pub csr: Option<(bool, u32)>, // CSR/WCSR if any + pub loophead: Option<(usize, u32)>, // head of loopstack +} + +impl StateTracker { + fn clear(&mut self) { + self.gpr = None; + self.wgpr = None; + self.csr = None; + self.loophead = None; + } +} + +/// State of a single OTBN hart +pub struct HartState { + /// x1 - x31 register values. The contents of index 0 (the x0 zero register) are ignored. + pub registers: [u32; 32], + /// w0 - w31 wide register values + pub wregisters: [u256; 32], + /// Program counter + pub pc: u32, + /// 8-loop deep HW loop stack for use with loop/loopi instructions + pub loopstack: Vec, + /// 8-address deep HW stack for use with x1 GPR + pub hwstack: Vec, + /// Special resisters (narrow and wide) + pub csr_set: csrs::CSRSet, + /// Track state changes (debug) + pub updated: StateTracker, +} + +impl HartState { + pub fn new(urnd: Arc>, rnd: Arc, key: Arc) -> Self { + HartState { + registers: [0; 32], + wregisters: [0.as_u256(); 32], + pc: 0, + loopstack: Vec::with_capacity(8), + hwstack: Vec::with_capacity(8), + updated: StateTracker::default(), + csr_set: csrs::CSRSet::new(urnd, rnd, key), + } + } + + /// Write a register in the hart state. Used by executing instructions for correct zero + /// register handling + fn write_register(&mut self, reg_index: usize, data: u32) -> Result<(), InstructionTrap> { + if reg_index == 0 { + return Ok(()); + } + if reg_index == 1 { + if self.hwstack.len() >= self.hwstack.capacity() { + return Err(InstructionTrap::Exception(ExceptionCause::ECallStack, None)); + } + self.hwstack.push(data); + } + + self.registers[reg_index] = data; + self.updated.gpr = Some(reg_index); + Ok(()) + } + + /// Read a register from the hart state. Used by executing instructions for correct zero + /// register handling + fn read_register(&mut self, reg_index: usize) -> Result { + if reg_index == 0 { + Ok(0) + } else if reg_index == 1 { + match self.hwstack.pop() { + Some(x) => { + self.registers[reg_index] = x; + Ok(x) + } + None => Err(InstructionTrap::Exception(ExceptionCause::ECallStack, None)), + } + } else { + Ok(self.registers[reg_index]) + } + } + + /// Write a register in the hart state. Used by executing instructions for correct zero + /// register handling + fn write_wide_register(&mut self, reg_index: usize, data: u256) -> Result<(), InstructionTrap> { + self.wregisters[reg_index] = data; + self.updated.wgpr = Some(reg_index); + Ok(()) + } + + /// Read a register from the hart state. Used by executing instructions for correct zero + /// register handling + fn read_wide_register(&mut self, reg_index: usize) -> Result { + Ok(self.wregisters[reg_index]) + } + + fn write_csr(&mut self, csr_addr: u32, data: u32) -> Result<(), InstructionTrap> { + let csr = self + .csr_set + .get_csr_mut(csr_addr) + .ok_or(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + Some(csr_addr), + ))?; + + if let Err(exc) = csr.write(data) { + return Err(InstructionTrap::Exception(exc, Some(csr_addr))); + } + self.updated.csr = Some((false, csr_addr)); + Ok(()) + } + + fn read_csr(&mut self, csr_addr: u32) -> Result { + let csr = self + .csr_set + .get_csr(csr_addr) + .ok_or(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + Some(csr_addr), + ))?; + + csr.read() + .map_err(|exc| InstructionTrap::Exception(exc, Some(csr_addr))) + } + + fn write_wsr(&mut self, wsr_addr: u32, data: u256) -> Result<(), InstructionTrap> { + let wsr = self + .csr_set + .get_wsr_mut(wsr_addr) + .ok_or(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + Some(wsr_addr), + ))?; + + if let Err(exc) = wsr.write(data) { + return Err(InstructionTrap::Exception(exc, Some(wsr_addr))); + } + self.updated.csr = Some((true, wsr_addr)); + Ok(()) + } + + fn read_wsr(&mut self, wsr_addr: u32) -> Result { + let wsr = self + .csr_set + .get_wsr(wsr_addr) + .ok_or(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + Some(wsr_addr), + ))?; + + wsr.read() + .map_err(|exc| InstructionTrap::Exception(exc, Some(wsr_addr))) + } + + fn set_mlz_wide_flags(&mut self, fg: usize, carry: bool, value: u256) { + let mut flags = if carry { + insn_proc::Flags::CARRY + } else { + insn_proc::Flags::default() + }; + if (value >> 255u32).as_u32() != 0 { + flags |= insn_proc::Flags::MSB; + } + if (value & U256::from(1u32)) != 0 { + flags |= insn_proc::Flags::LSB; + } + if value == 0 { + flags |= insn_proc::Flags::ZERO; + } + self.csr_set.set_flags(fg, flags); + } + + fn update_mlz_wide_flags(&mut self, fg: usize, value: u256) { + let mut flags = self.csr_set.get_flags(fg) & insn_proc::Flags::CARRY; + if (value >> 255u32).as_u32() != 0 { + flags |= insn_proc::Flags::MSB; + } + if (value & U256::from(1u32)) != 0 { + flags |= insn_proc::Flags::LSB; + } + if value == 0 { + flags |= insn_proc::Flags::ZERO; + } + self.csr_set.set_flags(fg, flags); + } + + pub fn wipe_internal(&mut self, prng: &Arc>) { + { + let mut prng = prng.lock().unwrap(); + + // GPRs and WDRs + for reg in self.registers.iter_mut() { + *reg = prng.get_prng_u32(); + } + for reg in self.wregisters.iter_mut() { + *reg = prng.get_prng_u256(); + } + } + + // Accumulator + // Flags + // Modulus + self.csr_set.wipe_internal(prng); + + self.loopstack.clear(); + self.hwstack.clear(); + self.updated.clear(); + } + + pub fn set_test_mode(&mut self, enable: bool) { + self.csr_set.set_test_mode(enable) + } +} + +/// An `InstructionProcessor` that execute instructions, updating `hart_state` as appropriate. +pub struct InstructionExecutor<'a, M: Memory> { + /// Instruction memory used by fetch instructions + pub imem: &'a mut M, + /// Data memory used by load and store instructions + pub dmem: &'a mut M, + pub hart_state: &'a mut HartState, +} + +impl<'a, M: Memory> InstructionExecutor<'a, M> { + fn execute_reg_reg_op( + &mut self, + dec_insn: insn_format::RType, + op: F, + ) -> Result<(), InstructionTrap> + where + F: Fn(u32, u32) -> u32, + { + let a = self.hart_state.read_register(dec_insn.rs1)?; + let b = self.hart_state.read_register(dec_insn.rs2)?; + let result = op(a, b); + self.hart_state.write_register(dec_insn.rd, result) + } + + fn execute_reg_imm_op( + &mut self, + dec_insn: insn_format::IType, + op: F, + ) -> Result<(), InstructionTrap> + where + F: Fn(u32, u32) -> u32, + { + let a = self.hart_state.read_register(dec_insn.rs1)?; + let b = dec_insn.imm as u32; + let result = op(a, b); + self.hart_state.write_register(dec_insn.rd, result) + } + + fn execute_reg_imm_shamt_op( + &mut self, + dec_insn: insn_format::ITypeShamt, + op: F, + ) -> Result<(), InstructionTrap> + where + F: Fn(u32, u32) -> u32, + { + let a = self.hart_state.read_register(dec_insn.rs1)?; + let result = op(a, dec_insn.shamt); + self.hart_state.write_register(dec_insn.rd, result) + } + + fn execute_reg_imm_bn_op( + &mut self, + dec_insn: insn_format::IType, + op: F, + ) -> Result<(), InstructionTrap> + where + F: Fn(u256, u256) -> (u256, bool), + { + let a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = dec_insn.imm as u32 & 0x3ff; + let fg = ((dec_insn.imm >> 11) & 0b1) as usize; + let (res, carry) = op(a, U256::from(b)); + self.hart_state.write_wide_register(dec_insn.rd, res)?; + self.hart_state.set_mlz_wide_flags(fg, carry, res); + Ok(()) + } + + fn execute_reg_reg_bn_op( + &mut self, + dec_insn: insn_format::RType, + op: F, + ) -> Result<(), InstructionTrap> + where + F: Fn(u256, u256) -> u256, + { + let a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + let st = dec_insn.funct7 & 0b010_0000 != 0; + let sbits = (dec_insn.funct7 & 0b001_1111) << 3; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + + let b = if st { + b.wrapping_shr(sbits) + } else { + b.wrapping_shl(sbits) + }; + + let res = op(a, b); + + self.hart_state.write_wide_register(dec_insn.rd, res)?; + self.hart_state.update_mlz_wide_flags(fg, res); + + Ok(()) + } + + fn execute_reg_reg_bn_of_op( + &mut self, + dec_insn: insn_format::RType, + op: F, + bc: bool, + ) -> Result<(), InstructionTrap> + where + F: Fn(u256, u256) -> (u256, bool), + { + let mut a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + let st = dec_insn.funct7 & 0b010_0000 != 0; + let sbits = (dec_insn.funct7 & 0b001_1111) << 3; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + let b = if st { + b.wrapping_shr(sbits) + } else { + b.wrapping_shl(sbits) + }; + + // TBC: not sure if carry implementation works for all op + let mut carry = false; + if bc { + let flags = self.hart_state.csr_set.get_flags(fg); + if !(flags & insn_proc::Flags::CARRY).is_empty() { + (a, carry) = op(a, U256::from(1u32)); + } + } + let (res, carry2) = op(a, b); + carry |= carry2; + self.hart_state.write_wide_register(dec_insn.rd, res)?; + self.hart_state.set_mlz_wide_flags(fg, carry, res); + Ok(()) + } + + fn execute_csr_op( + &mut self, + dec_insn: insn_format::ITypeCSR, + use_imm: bool, + op: F, + ) -> Result<(), InstructionTrap> + where + F: Fn(u32, u32) -> u32, + { + let old_csr = self.hart_state.read_csr(dec_insn.csr)?; + + let a = if use_imm { + dec_insn.rs1 as u32 + } else { + self.hart_state.read_register(dec_insn.rs1)? + }; + + let new_csr = op(old_csr, a); + + self.hart_state.write_csr(dec_insn.csr, new_csr)?; + + self.hart_state.write_register(dec_insn.rd, old_csr) + } + + // Returns true if branch succeeds + fn execute_branch( + &mut self, + dec_insn: insn_format::BType, + cond: F, + ) -> Result + where + F: Fn(u32, u32) -> bool, + { + let a = self.hart_state.read_register(dec_insn.rs1)?; + let b = self.hart_state.read_register(dec_insn.rs2)?; + + let lstack = &self.hart_state.loopstack; + if let Some(hwloop) = lstack.last() { + if self.hart_state.pc == hwloop.end { + return Err(InstructionTrap::Exception(ExceptionCause::ELoop, None)); + } + } + if cond(a, b) { + let new_pc = self.hart_state.pc.wrapping_add(dec_insn.imm as u32); + self.hart_state.pc = new_pc; + Ok(true) + } else { + Ok(false) + } + } + + fn execute_load( + &mut self, + dec_insn: insn_format::IType, + signed: bool, + ) -> Result<(), InstructionTrap> { + let addr = self + .hart_state + .read_register(dec_insn.rs1)? + .wrapping_add(dec_insn.imm as u32); + + if (addr & 0x03) != 0 { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(addr), + )); + } + + // Attempt to read data from memory, returning a LoadAccessFault as an error if it is not. + let mut load_data = match self.dmem.read_mem(addr) { + Some(d) => d, + None => { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(addr), + )); + } + }; + + // Sign extend loaded data if required + if signed { + load_data = (load_data as i32) as u32; + } + + // Write load data to destination register + self.hart_state.write_register(dec_insn.rd, load_data) + } + + fn execute_store(&mut self, dec_insn: insn_format::SType) -> Result<(), InstructionTrap> { + let addr = self + .hart_state + .read_register(dec_insn.rs1)? + .wrapping_add(dec_insn.imm as u32); + let data = self.hart_state.read_register(dec_insn.rs2)?; + + // Determine if address is aligned to size + if (addr & 0x3) != 0x0 { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(addr), + )); + } + + // Write store data to memory + if self.dmem.write_mem(addr, data) { + Ok(()) + } else { + Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(addr), + )) + } + } + + pub fn wipe_internal(&mut self, prng: &Arc>) { + // "If OTBN cannot complete a secure wipe of the internal state (e.g., due to failing to + // obtain the required randomness), it immediately becomes locked." + // for now, wiping emulation does not involve random source, but this may be added if + // a remote random source gets implemented. @todo + self.hart_state.wipe_internal(prng); + } + + pub fn reset(&mut self) { + self.hart_state.pc = 0; + } + + /// Execute instruction pointed to by `hart_state.pc` + /// + /// Returns `Ok` where instruction execution was successful. `Err` with the relevant + /// [InstructionTrap] is returned when the instruction execution causes a trap. + pub fn step(&mut self) -> Result<(), InstructionTrap> { + self.hart_state.updated.clear(); + + if let Some(next_insn) = self.imem.read_mem(self.hart_state.pc) { + // Fetch next instruction from memory and eecute the instruction if fetch was + // successful + let step_result = insn_decode::decoder(self, next_insn); + + match step_result { + Some(Ok(pc_updated)) => { + if !pc_updated { + let lstack = &mut self.hart_state.loopstack; + let loopdepth = lstack.len(); + let loop_to = match lstack.last_mut() { + Some(hwloop) => { + if hwloop.end == self.hart_state.pc { + // one less iteration to go + hwloop.count -= 1; + self.hart_state.updated.loophead = + Some((loopdepth, hwloop.count)); + if hwloop.count == 0 { + // loop exhausted, should be removed + lstack.pop(); + // resume after the last loop instruction + None + } else { + // restart from the first instruction of the loop + Some(hwloop.start) + } + } else { + // current PC is not the last instruction of the loop + None + } + } + // no HW loop is active + _ => None, + }; + self.hart_state.pc = match loop_to { + Some(pc) => pc, + _ => self.hart_state.pc + 4, + }; + } + Ok(()) + } + // Instruction produced an illegal instruction error or decode failed so return an + // IllegalInstruction as an error, supplying instruction bits + Some(Err(InstructionTrap::Exception(ExceptionCause::EIllegalInsn, _))) | None => { + Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + Some(next_insn), + )) + } + // Instruction produced an error so return it + Some(Err(e)) => Err(e), + } + } else { + // FetchError + Err(InstructionTrap::Exception( + ExceptionCause::EBadInsnAddr, + Some(self.hart_state.pc), + )) + } + } +} + +// Macros to implement various repeated operations (e.g. ALU reg op reg instructions). +macro_rules! make_alu_op_reg_fn { + ($name:ident, $op_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + self.execute_reg_reg_op(dec_insn, $op_fn)?; + Ok(false) + } + } + }; +} + +// Macros to implement various repeated operations (on wide registers). +macro_rules! make_alu_bn_op_reg_fn { + ($name:ident, $op_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + self.execute_reg_reg_bn_op(dec_insn, $op_fn)?; + Ok(false) + } + } + }; +} + +macro_rules! make_alu_bn_of_op_reg_fn { + ($name:ident, $op_fn:expr, $bc:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::RType + ) -> Self::InstructionResult { + self.execute_reg_reg_bn_of_op(dec_insn, $op_fn, $bc)?; + Ok(false) + } + } + }; +} + +macro_rules! make_alu_op_imm_fn { + ($name:ident, $op_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::IType + ) -> Self::InstructionResult { + self.execute_reg_imm_op(dec_insn, $op_fn)?; + Ok(false) + } + } + }; +} + +macro_rules! make_alu_op_imm_shamt_fn { + ($name:ident, $op_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::ITypeShamt + ) -> Self::InstructionResult { + self.execute_reg_imm_shamt_op(dec_insn, $op_fn)?; + Ok(false) + } + } + }; +} + +macro_rules! make_alu_op_fns { + ($name:ident, $op_fn:expr) => { + make_alu_op_reg_fn! {$name, $op_fn} + make_alu_op_imm_fn! {$name, $op_fn} + }; +} + +macro_rules! make_shift_op_fns { + ($name:ident, $op_fn:expr) => { + make_alu_op_reg_fn! {$name, $op_fn} + make_alu_op_imm_shamt_fn! {$name, $op_fn} + }; +} + +macro_rules! make_branch_op_fn { + ($name:ident, $cond_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::BType + ) -> Self::InstructionResult { + self.execute_branch(dec_insn, $cond_fn) + } + } + }; +} + +macro_rules! make_load_op_fn_inner { + ($name:ident, $signed: expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::IType + ) -> Self::InstructionResult { + self.execute_load(dec_insn, $signed)?; + Ok(false) + } + } + }; +} + +macro_rules! make_load_op_fn { + ($name:ident, signed) => { + make_load_op_fn_inner! {$name, true} + }; + ($name:ident, unsigned) => { + make_load_op_fn_inner! {$name, false} + }; +} + +macro_rules! make_store_op_fn { + ($name:ident) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::SType + ) -> Self::InstructionResult { + self.execute_store(dec_insn)?; + Ok(false) + } + } + }; +} + +macro_rules! make_alu_bn_op_imm_fn { + ($name:ident, $op_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::IType + ) -> Self::InstructionResult { + self.execute_reg_imm_bn_op(dec_insn, $op_fn)?; + Ok(false) + } + } + }; +} + +macro_rules! make_csr_op_fns { + ($name:ident, $op_fn:expr) => { + paste! { + fn []( + &mut self, + dec_insn: insn_format::ITypeCSR + ) -> Self::InstructionResult { + self.execute_csr_op(dec_insn, false, $op_fn)?; + Ok(false) + } + } + }; +} + +impl<'a, M: Memory> insn_proc::InstructionProcessor for InstructionExecutor<'a, M> { + /// Result is `Ok` when instruction execution is successful. `Ok(true) indicates the + /// instruction updated the PC and Ok(false) indicates it did not (so the PC must be + /// incremented to execute the next instruction). + type InstructionResult = Result; + + make_alu_op_fns! {add, |a, b| a.wrapping_add(b)} + make_alu_op_reg_fn! {sub, |a, b| a.wrapping_sub(b)} + make_alu_op_fns! {or, |a, b| a | b} + make_alu_op_fns! {and, |a, b| a & b} + make_alu_op_fns! {xor, |a, b| a ^ b} + + make_shift_op_fns! {sll, |a, b| a << (b & 0x1f)} + make_shift_op_fns! {srl, |a, b| a >> (b & 0x1f)} + make_shift_op_fns! {sra, |a, b| ((a as i32) >> (b & 0x1f)) as u32} + + fn process_lui(&mut self, dec_insn: insn_format::UType) -> Self::InstructionResult { + self.hart_state + .write_register(dec_insn.rd, dec_insn.imm as u32)?; + Ok(false) + } + + make_branch_op_fn! {beq, |a, b| a == b} + make_branch_op_fn! {bne, |a, b| a != b} + + make_load_op_fn! {lw, unsigned} + make_store_op_fn! {sw} + + fn process_jal(&mut self, dec_insn: insn_format::JType) -> Self::InstructionResult { + let target_pc = self.hart_state.pc.wrapping_add(dec_insn.imm as u32); + self.hart_state + .write_register(dec_insn.rd, self.hart_state.pc + 4)?; + self.hart_state.pc = target_pc; + Ok(true) + } + + fn process_jalr(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let target_pc = self + .hart_state + .read_register(dec_insn.rs1)? + .wrapping_add(dec_insn.imm as u32) + & !0b11; + self.hart_state + .write_register(dec_insn.rd, self.hart_state.pc + 4)?; + self.hart_state.pc = target_pc; + Ok(true) + } + + make_csr_op_fns! {csrrw, |_old_csr, a| a} + make_csr_op_fns! {csrrs, |old_csr, a| old_csr | a} + + fn process_ecall(&mut self) -> Self::InstructionResult { + Err(InstructionTrap::Exception( + ExceptionCause::ECallMMode, + Some(0), + )) + } + + fn process_loop(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let bodysize = dec_insn.imm as u32 + 1; + let hwloop = Loop { + count: self.hart_state.read_register(dec_insn.rs1)?, + start: self.hart_state.pc + 4, // next instruction + end: self.hart_state.pc + bodysize * 4, + }; + if hwloop.count == 0 { + // to be handled properly + return Err(InstructionTrap::Exception(ExceptionCause::ELoop, None)); + } + let lstack = &mut self.hart_state.loopstack; + if lstack.len() == lstack.capacity() { + return Err(InstructionTrap::Exception(ExceptionCause::ELoop, None)); + } + if let Some(hwloop) = lstack.last() { + if self.hart_state.pc == hwloop.end { + return Err(InstructionTrap::Exception(ExceptionCause::ELoop, None)); + } + } + // need to check this instruction is not a last instruction in an existing loop + lstack.push(hwloop); + self.hart_state.updated.loophead = Some((lstack.len(), hwloop.count)); + Ok(false) + } + + fn process_loopi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let bodysize = dec_insn.imm as u32 + 1; + let hwloop = Loop { + count: ((dec_insn.rs1 << 5) + dec_insn.rd) as u32, + start: self.hart_state.pc + 4, // next instruction + end: self.hart_state.pc + bodysize * 4, + }; + if self.hart_state.loopstack.len() == self.hart_state.loopstack.capacity() { + // to be handled properly + return Err(InstructionTrap::Exception(ExceptionCause::ELoop, None)); + } + if hwloop.count == 0 { + return Err(InstructionTrap::Exception(ExceptionCause::ELoop, None)); + } + // need to check this instruction is not a last instruction in an existing loop + self.hart_state.loopstack.push(hwloop); + self.hart_state.updated.loophead = Some((self.hart_state.loopstack.len(), hwloop.count)); + Ok(false) + } + + fn process_bn_lid(&mut self, dec_insn: insn_format::WidType) -> Self::InstructionResult { + let grs1_inc = (dec_insn.inc & 0b10) != 0; + let grd_inc = (dec_insn.inc & 0b01) != 0; + + if grs1_inc && grd_inc { + return Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + None, + )); + } + + // wide register index + let wri = self.hart_state.read_register(dec_insn.rs2)?; + if wri > 31 { + return Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + None, + )); + } + + let addr = self.hart_state.read_register(dec_insn.rs1)?; + if (addr as usize & (WLEN / 8 - 1)) != 0 { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(addr), + )); + } + + let offset = dec_insn.imm << 5; + + if (offset as usize & (WLEN / 8 - 1)) != 0 { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(offset as u32), + )); + } + + // check is there is a less stupid way to do this... + let src = if offset >= 0 { + addr.wrapping_add(offset as u32) + } else { + addr.wrapping_sub(-offset as u32) + }; + + let mut bytes = [0; size_of::()]; + const RSIZE: usize = size_of::(); + for (pos, bchunk) in (0..bytes.len()).step_by(RSIZE).zip(bytes.chunks_mut(RSIZE)) { + let word = self.dmem.read_mem(src + pos as u32).unwrap(); + bchunk.copy_from_slice(&word.to_le_bytes()[..]); + } + let wvalue = U256::from_le_bytes(bytes); + + if grd_inc { + self.hart_state.write_register(dec_insn.rs2, wri + 1)?; + } + + if grs1_inc { + let addr = addr.wrapping_add(size_of::() as u32); + self.hart_state.write_register(dec_insn.rs1, addr)?; + } + + self.hart_state.write_wide_register(wri as usize, wvalue)?; + + Ok(false) + } + + fn process_bn_sid(&mut self, dec_insn: insn_format::WidType) -> Self::InstructionResult { + let grs1_inc = (dec_insn.inc & 0b10) != 0; + let grs2_inc = (dec_insn.inc & 0b01) != 0; + + if grs1_inc && grs2_inc { + return Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + None, + )); + } + + // wide register index + let wri = self.hart_state.read_register(dec_insn.rs2)?; + if wri > 31 { + return Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + None, + )); + } + + let addr = self.hart_state.read_register(dec_insn.rs1)?; + if (addr as usize & (WLEN / 8 - 1)) != 0 { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(addr), + )); + } + + let offset = dec_insn.imm << 5; + + if (offset as usize & (WLEN / 8 - 1)) != 0 { + return Err(InstructionTrap::Exception( + ExceptionCause::EBadDataAddr, + Some(offset as u32), + )); + } + + // check is there is a less stupid way to do this... + let dst = if offset >= 0 { + addr.wrapping_add(offset as u32) + } else { + addr.wrapping_sub(-offset as u32) + }; + + if grs1_inc { + let addr = addr.wrapping_add(size_of::() as u32); + self.hart_state.write_register(dec_insn.rs1, addr)?; + } + + if grs2_inc { + self.hart_state.write_register(dec_insn.rs2, wri + 1)?; + } + + let wvalue = self.hart_state.read_wide_register(wri as usize)?; + let bytes = wvalue.to_le_bytes(); + const RSIZE: usize = size_of::(); + for pos in (0..bytes.len()).step_by(RSIZE) { + let word = u32::from_le_bytes(bytes[pos..pos + RSIZE].try_into().unwrap()); + self.dmem.write_mem(dst + pos as u32, word); + } + + Ok(false) + } + + fn process_bn_sel(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let fg = (dec_insn.funct7 >> 6) & 0b1; + let flag = insn_proc::Flags::from_bits_truncate(1 << (dec_insn.funct7 & 0b11) as u8); + let flags = self.hart_state.csr_set.get_flags(fg as usize); + + let val = if (flags & flag).is_empty() { + self.hart_state.read_wide_register(dec_insn.rs2)? + } else { + self.hart_state.read_wide_register(dec_insn.rs1)? + }; + + self.hart_state.write_wide_register(dec_insn.rd, val)?; + + Ok(false) + } + + fn process_bn_cmp(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + let st = dec_insn.funct7 & 0b010_0000 != 0; + let sbits = (dec_insn.funct7 & 0b001_1111) << 3; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + + let b = if st { + b.wrapping_shr(sbits) + } else { + b.wrapping_shl(sbits) + }; + + let (res, carry) = a.overflowing_sub(b); + + self.hart_state.set_mlz_wide_flags(fg, carry, res); + + Ok(false) + } + + fn process_bn_cmpb(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let mut a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + let st = dec_insn.funct7 & 0b010_0000 != 0; + let sbits = (dec_insn.funct7 & 0b001_1111) << 3; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + + let b = if st { + b.wrapping_shr(sbits) + } else { + b.wrapping_shl(sbits) + }; + + // TBC: not sure if carry implementation works + let mut carry = false; + let flags = self.hart_state.csr_set.get_flags(fg); + if !(flags & insn_proc::Flags::CARRY).is_empty() { + (a, carry) = a.overflowing_sub(U256::from(1u32)); + } + let (res, carry2) = a.overflowing_sub(b); + carry |= carry2; + + self.hart_state.set_mlz_wide_flags(fg, carry, res); + + Ok(false) + } + + fn process_bn_mov(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let val = self.hart_state.read_wide_register(dec_insn.rs1)?; + + self.hart_state.write_wide_register(dec_insn.rd, val)?; + + Ok(false) + } + + fn process_bn_movr(&mut self, dec_insn: insn_format::SType) -> Self::InstructionResult { + let grd_inc = dec_insn.imm & 0b0001 != 0; + let grs_inc = dec_insn.imm & 0b0100 != 0; + + if grd_inc && grs_inc { + return Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + None, + )); + } + + let grd_val = self.hart_state.read_register(dec_insn.rs2)?; + let grs_val = self.hart_state.read_register(dec_insn.rs1)?; + + if grd_val > 31 || grs_val > 31 { + return Err(InstructionTrap::Exception( + ExceptionCause::EIllegalInsn, + None, + )); + } + + if grd_inc { + self.hart_state.write_register(dec_insn.rs2, grd_val + 1)?; + } + + if grs_inc { + self.hart_state.write_register(dec_insn.rs1, grs_val + 1)?; + } + + let val = self.hart_state.read_wide_register(grs_val as usize)?; + self.hart_state.write_wide_register(grd_val as usize, val)?; + + Ok(false) + } + + fn process_bn_wsrr(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let wsr = dec_insn.imm as u32 & 0xff; + let wval = self.hart_state.read_wsr(wsr)?; + + self.hart_state.write_wide_register(dec_insn.rd, wval)?; + + Ok(false) + } + + fn process_bn_wsrw(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult { + let wsr = dec_insn.imm as u32 & 0xff; + let val = self.hart_state.read_wide_register(dec_insn.rs1)?; + + self.hart_state.write_wsr(wsr, val)?; + + Ok(false) + } + + fn process_bn_addm(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + let modv = self + .hart_state + .read_wsr(csrs::WSRAddr::r#mod.into()) + .unwrap(); + + let (mut res, over) = a.overflowing_add(b); + if over || res > modv { + res = res.wrapping_sub(modv) + } + + self.hart_state.write_wide_register(dec_insn.rd, res)?; + + Ok(false) + } + + fn process_bn_subm(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + let modv = self + .hart_state + .read_wsr(csrs::WSRAddr::r#mod.into()) + .unwrap(); + + let (mut res, over) = a.overflowing_sub(b); + if over { + res = res.wrapping_add(modv) + } + + self.hart_state.write_wide_register(dec_insn.rd, res)?; + + Ok(false) + } + + fn process_bn_mulqacc(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let zero_acc = dec_insn.funct3 & 0b001 != 0; + let acc_shift = 64 * (dec_insn.funct3 >> 1); + let qwsel1 = dec_insn.funct7 & 0b11; + let qwsel2 = dec_insn.funct7 >> 2 & 0b11; + + let a_val = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b_val = self.hart_state.read_wide_register(dec_insn.rs2)?; + + let a_qw = a_val >> (64 * qwsel1) & U256::from(u64::MAX); + let b_qw = b_val >> (64 * qwsel2) & U256::from(u64::MAX); + + let mut mul_res = (a_qw).wrapping_mul(b_qw); + + let acc = if zero_acc { + U256::from(0u32) + } else { + self.hart_state.read_wsr(csrs::WSRAddr::acc.into()).unwrap() + }; + + mul_res = mul_res.wrapping_shl(acc_shift); + + // add and truncate + self.hart_state + .write_wsr(csrs::WSRAddr::acc.into(), acc.wrapping_add(mul_res)) + .and(Ok(false)) + } + + fn process_bn_mulqacc_wo(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let zero_acc = dec_insn.funct3 & 0b001 != 0; + let acc_shift = 64 * (dec_insn.funct3 >> 1); + let qwsel1 = dec_insn.funct7 & 0b11; + let qwsel2 = dec_insn.funct7 >> 2 & 0b11; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + + let a_val = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b_val = self.hart_state.read_wide_register(dec_insn.rs2)?; + + let a_qw = a_val >> (64 * qwsel1) & U256::from(u64::MAX); + let b_qw = b_val >> (64 * qwsel2) & U256::from(u64::MAX); + + let mut mul_res = (a_qw).wrapping_mul(b_qw); + + let acc: u256 = if zero_acc { + U256::from(0u32) + } else { + self.hart_state.read_wsr(csrs::WSRAddr::acc.into()).unwrap() + }; + + mul_res = mul_res.wrapping_shl(acc_shift); + let truncated = acc.wrapping_add(mul_res); + + self.hart_state + .write_wide_register(dec_insn.rd, truncated)?; + self.hart_state + .write_wsr(csrs::WSRAddr::acc.into(), truncated)?; + + self.hart_state.update_mlz_wide_flags(fg, truncated); + Ok(false) + } + + fn process_bn_mulqacc_so(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let zero_acc = dec_insn.funct3 & 0b001 != 0; + let acc_shift = 64 * (dec_insn.funct3 >> 1); + let qwsel1 = dec_insn.funct7 & 0b11; + let qwsel2 = dec_insn.funct7 >> 2 & 0b11; + let hwsel = dec_insn.funct7 >> 4 & 0b1; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + + let a_val = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b_val = self.hart_state.read_wide_register(dec_insn.rs2)?; + let d_val = self.hart_state.read_wide_register(dec_insn.rd)?; + + let a_qw = a_val >> (64 * qwsel1) & U256::from(u64::MAX); + let b_qw = b_val >> (64 * qwsel2) & U256::from(u64::MAX); + + let mut mul_res = a_qw.wrapping_mul(b_qw); + + let acc: u256 = if zero_acc { + U256::from(0u32) + } else { + self.hart_state.read_wsr(csrs::WSRAddr::acc.into()).unwrap() + }; + + mul_res = mul_res.wrapping_shl(acc_shift); + let truncated = acc.wrapping_add(mul_res); + + let lo_part = U256::from(*truncated.low()); + let hi_part = U256::from(*truncated.high()); + let hw_shift = 128 * hwsel; + let hw_mask = U256::from(u128::MAX) << hw_shift; + let wrd = (d_val & !hw_mask) | (lo_part << hw_shift); + + self.hart_state.write_wide_register(dec_insn.rd, wrd)?; + self.hart_state + .write_wsr(csrs::WSRAddr::acc.into(), hi_part)?; + + let flags = self.hart_state.csr_set.get_flags(fg); + let mut new_flags; + if hwsel != 0 { + new_flags = flags & (insn_proc::Flags::CARRY | insn_proc::Flags::LSB); + if ((lo_part >> 127u32).as_u32() & 0b1) != 0 { + new_flags |= insn_proc::Flags::MSB; + } + if lo_part == 0 { + new_flags |= flags & insn_proc::Flags::ZERO; + } + } else { + new_flags = flags & (insn_proc::Flags::CARRY | insn_proc::Flags::MSB); + if (lo_part & 0b1) != 0 { + new_flags |= insn_proc::Flags::LSB; + } + if lo_part == 0 { + new_flags |= insn_proc::Flags::ZERO; + } + } + + self.hart_state.csr_set.set_flags(fg, new_flags); + + Ok(false) + } + + make_alu_bn_op_reg_fn! {and, |a, b| a & b} + make_alu_bn_op_reg_fn! {or, |a, b| a | b} + make_alu_bn_op_reg_fn! {xor, |a, b| a ^ b} + make_alu_bn_op_imm_fn! {add, |a, b| a.overflowing_add(b)} + make_alu_bn_op_imm_fn! {sub, |a, b| a.overflowing_sub(b)} + make_alu_bn_of_op_reg_fn! {add, |a, b| a.overflowing_add(b), false} + make_alu_bn_of_op_reg_fn! {sub, |a, b| a.overflowing_sub(b), false} + make_alu_bn_of_op_reg_fn! {addc, |a, b| a.overflowing_add(b), true} + make_alu_bn_of_op_reg_fn! {subb, |a, b| a.overflowing_sub(b), true} + + fn process_bn_not(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let a = self.hart_state.read_wide_register(dec_insn.rs2)?; + let st = dec_insn.funct7 & 0b010_0000 != 0; + let sbits = (dec_insn.funct7 & 0b001_1111) << 3; + let a = if st { + a.wrapping_shr(sbits) + } else { + a.wrapping_shl(sbits) + }; + let fg = ((dec_insn.funct7 >> 6) & 0b1) as usize; + + let res = !a; + self.hart_state.write_wide_register(dec_insn.rd, res)?; + self.hart_state.update_mlz_wide_flags(fg, res); + + Ok(false) + } + + fn process_bn_rshi(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult { + let imm = (dec_insn.funct7 << 1) | (dec_insn.funct3 >> 2); + let a = self.hart_state.read_wide_register(dec_insn.rs1)?; + let b = self.hart_state.read_wide_register(dec_insn.rs2)?; + + let res = a.wrapping_shl(256 - imm) | b.wrapping_shr(imm); + + self.hart_state.write_wide_register(dec_insn.rd, res)?; + + Ok(false) + } +} diff --git a/hw/opentitan/otbn/otbn/src/insn_format.rs b/hw/opentitan/otbn/otbn/src/insn_format.rs new file mode 100644 index 0000000000000..1764f33ca9ad9 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/insn_format.rs @@ -0,0 +1,220 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//! Structures for instruction decoding + +#[derive(Debug, PartialEq, Eq)] +pub struct RType { + pub funct7: u32, + pub rs2: usize, + pub rs1: usize, + pub funct3: u32, + pub rd: usize, +} + +impl RType { + pub fn new(insn: u32) -> RType { + RType { + funct7: (insn >> 25) & 0x7f, + rs2: ((insn >> 20) & 0x1f) as usize, + rs1: ((insn >> 15) & 0x1f) as usize, + funct3: (insn >> 12) & 0x7, + rd: ((insn >> 7) & 0x1f) as usize, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct IType { + pub imm: i32, + pub rs1: usize, + pub funct3: u32, + pub rd: usize, +} + +impl IType { + pub fn new(insn: u32) -> IType { + let uimm: i32 = ((insn >> 20) & 0x7ff) as i32; + + let imm: i32 = if (insn & 0x8000_0000) != 0 { + uimm - (1 << 11) + } else { + uimm + }; + + IType { + imm, + rs1: ((insn >> 15) & 0x1f) as usize, + funct3: (insn >> 12) & 0x7, + rd: ((insn >> 7) & 0x1f) as usize, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ITypeShamt { + pub funct7: u32, + pub shamt: u32, + pub rs1: usize, + pub funct3: u32, + pub rd: usize, +} + +impl ITypeShamt { + pub fn new(insn: u32) -> ITypeShamt { + let itype = IType::new(insn); + + ITypeShamt { + funct7: (insn >> 25) & 0x7f, + shamt: (itype.imm as u32) & 0x1f, + rs1: itype.rs1, + funct3: itype.funct3, + rd: itype.rd, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct WidType { + pub imm: i32, + pub rs2: usize, + pub rs1: usize, + pub funct3: u32, + pub inc: u32, +} + +impl WidType { + pub fn new(insn: u32) -> WidType { + let uimm: i32 = (((insn >> 25) & 0x7f) | ((insn >> 2) & 0x380)) as i32; + + let imm: i32 = if (insn & 0x800) != 0 { + uimm - (1 << 10) + } else { + uimm + }; + + WidType { + imm, + rs2: ((insn >> 20) & 0x1f) as usize, + rs1: ((insn >> 15) & 0x1f) as usize, + funct3: (insn >> 12) & 0x7, + inc: (insn >> 7) & 0x3, + } + } +} + +pub struct ITypeCSR { + pub csr: u32, + pub rs1: usize, + pub funct3: u32, + pub rd: usize, +} + +impl ITypeCSR { + pub fn new(insn: u32) -> ITypeCSR { + let csr: u32 = (insn >> 20) & 0xfff; + + ITypeCSR { + csr, + rs1: ((insn >> 15) & 0x1f) as usize, + funct3: (insn >> 12) & 0x7, + rd: ((insn >> 7) & 0x1f) as usize, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SType { + pub imm: i32, + pub rs2: usize, + pub rs1: usize, + pub funct3: u32, +} + +impl SType { + pub fn new(insn: u32) -> SType { + let uimm: i32 = (((insn >> 20) & 0x7e0) | ((insn >> 7) & 0x1f)) as i32; + + let imm: i32 = if (insn & 0x8000_0000) != 0 { + uimm - (1 << 11) + } else { + uimm + }; + + SType { + imm, + rs2: ((insn >> 20) & 0x1f) as usize, + rs1: ((insn >> 15) & 0x1f) as usize, + funct3: (insn >> 12) & 0x7, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct BType { + pub imm: i32, + pub rs2: usize, + pub rs1: usize, + pub funct3: u32, +} + +impl BType { + pub fn new(insn: u32) -> BType { + let uimm: i32 = + (((insn >> 20) & 0x7e0) | ((insn >> 7) & 0x1e) | ((insn & 0x80) << 4)) as i32; + + let imm: i32 = if (insn & 0x8000_0000) != 0 { + uimm - (1 << 12) + } else { + uimm + }; + + BType { + imm, + rs2: ((insn >> 20) & 0x1f) as usize, + rs1: ((insn >> 15) & 0x1f) as usize, + funct3: (insn >> 12) & 0x7, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct UType { + pub imm: i32, + pub rd: usize, +} + +impl UType { + pub fn new(insn: u32) -> UType { + UType { + imm: (insn & 0xffff_f000) as i32, + rd: ((insn >> 7) & 0x1f) as usize, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct JType { + pub imm: i32, + pub rd: usize, +} + +impl JType { + pub fn new(insn: u32) -> JType { + let uimm: i32 = + ((insn & 0xff000) | ((insn & 0x100000) >> 9) | ((insn >> 20) & 0x7fe)) as i32; + + let imm: i32 = if (insn & 0x8000_0000) != 0 { + uimm - (1 << 20) + } else { + uimm + }; + + JType { + imm, + rd: ((insn >> 7) & 0x1f) as usize, + } + } +} diff --git a/hw/opentitan/otbn/otbn/src/insn_proc.rs b/hw/opentitan/otbn/otbn/src/insn_proc.rs new file mode 100644 index 0000000000000..931857a5dc2f0 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/insn_proc.rs @@ -0,0 +1,100 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use bitflags::bitflags; + +use super::insn_format; + +/// A trait for objects which do something with OTBN instructions. +/// +/// There is one function per OTBN instruction. Each function takes the appropriate struct from +/// [insn_format] giving access to the decoded fields of the instruction. All functions +/// return the [InstructionProcessor::InstructionResult] associated type. +pub trait InstructionProcessor { + type InstructionResult; + + fn process_add(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_sub(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_sll(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_xor(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_srl(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_sra(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_or(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_and(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + + fn process_addi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_slli(&mut self, dec_insn: insn_format::ITypeShamt) -> Self::InstructionResult; + fn process_xori(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_srli(&mut self, dec_insn: insn_format::ITypeShamt) -> Self::InstructionResult; + fn process_srai(&mut self, dec_insn: insn_format::ITypeShamt) -> Self::InstructionResult; + fn process_ori(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_andi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + + fn process_lui(&mut self, dec_insn: insn_format::UType) -> Self::InstructionResult; + + fn process_beq(&mut self, dec_insn: insn_format::BType) -> Self::InstructionResult; + fn process_bne(&mut self, dec_insn: insn_format::BType) -> Self::InstructionResult; + + fn process_lw(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + + fn process_sw(&mut self, dec_insn: insn_format::SType) -> Self::InstructionResult; + + fn process_jal(&mut self, dec_insn: insn_format::JType) -> Self::InstructionResult; + fn process_jalr(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + + fn process_csrrw(&mut self, dec_insn: insn_format::ITypeCSR) -> Self::InstructionResult; + fn process_csrrs(&mut self, dec_insn: insn_format::ITypeCSR) -> Self::InstructionResult; + + fn process_ecall(&mut self) -> Self::InstructionResult; + + // custom-0 + fn process_bn_lid(&mut self, dec_insn: insn_format::WidType) -> Self::InstructionResult; + fn process_bn_sid(&mut self, dec_insn: insn_format::WidType) -> Self::InstructionResult; + fn process_bn_sel(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + + fn process_bn_cmp(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_cmpb(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + + fn process_bn_mov(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_bn_movr(&mut self, dec_insn: insn_format::SType) -> Self::InstructionResult; + + fn process_bn_wsrr(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_bn_wsrw(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + + // custom-1 + fn process_bn_add(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_addc(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_addm(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_addi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_bn_sub(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_subb(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_subm(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_subi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + + // op-32 + fn process_bn_mulqacc(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_mulqacc_wo(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_mulqacc_so(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + + // custom-3 + fn process_loop(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + fn process_loopi(&mut self, dec_insn: insn_format::IType) -> Self::InstructionResult; + + fn process_bn_and(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_or(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_not(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_xor(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; + fn process_bn_rshi(&mut self, dec_insn: insn_format::RType) -> Self::InstructionResult; +} + +bitflags! { + #[derive(Default)] + pub struct Flags: u8 { + const CARRY = 0b0001; + const MSB = 0b0010; + const LSB = 0b0100; + const ZERO = 0b1000; + } +} diff --git a/hw/opentitan/otbn/otbn/src/key.rs b/hw/opentitan/otbn/otbn/src/key.rs new file mode 100644 index 0000000000000..6df160970eae4 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/key.rs @@ -0,0 +1,57 @@ +// Copyright 2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::convert::TryInto; +use std::sync::Mutex; + +use ethnum::{u256, U256}; + +#[derive(Default)] +pub struct KeyStore { + pub lo: [u256; 2], + pub hi: [u256; 2], + pub valid: bool, +} + +pub struct Key { + pub store: Mutex, +} + +impl Default for Key { + fn default() -> Self { + let store = KeyStore::default(); + Self { + store: Mutex::new(store), + } + } +} + +impl Key { + pub fn new() -> Self { + Self::default() + } + + pub fn clear(&mut self) { + /* called from OTBN proxy */ + let mut store = self.store.lock().unwrap(); + store.lo = [U256::from(0u32), U256::from(0u32)]; + store.hi = [U256::from(0u32), U256::from(0u32)]; + store.valid = false; + } + + pub fn fill(&self, share0: &[u8; 48], share1: &[u8; 48], valid: bool) { + /* called from OTBN proxy */ + let lo0 = U256::from_le_bytes(share0[0..32].try_into().unwrap()); + let lo1 = U256::from_words(0, u128::from_le_bytes(share0[32..48].try_into().unwrap())); + + let hi0 = U256::from_le_bytes(share1[0..32].try_into().unwrap()); + let hi1 = U256::from_words(0, u128::from_le_bytes(share1[32..48].try_into().unwrap())); + + let mut store = self.store.lock().unwrap(); + + store.lo = [lo0, lo1]; + store.hi = [hi0, hi1]; + store.valid = valid; + } +} diff --git a/hw/opentitan/otbn/otbn/src/lib.rs b/hw/opentitan/otbn/otbn/src/lib.rs new file mode 100644 index 0000000000000..6492798c1a02a --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/lib.rs @@ -0,0 +1,85 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//! OTBN instruction set simulator library + +extern crate bitflags; +extern crate ethnum; +extern crate paste; + +use ethnum::u256; + +pub mod comm; +pub mod csrs; +pub mod insn_decode; +pub mod insn_disasm; +pub mod insn_exec; +pub mod insn_format; +pub mod insn_proc; +pub mod key; +pub mod memory; +pub mod otbn; +pub mod proxy; +pub mod random; +pub mod xoshiro256pp; + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[repr(u32)] +pub enum ExceptionCause { + EBadDataAddr = 1 << 0, + EBadInsnAddr = 1 << 1, + ECallStack = 1 << 2, + EIllegalInsn = 1 << 3, + ELoop = 1 << 4, + EKeyInvalid = 1 << 5, + ERndRepChkFail = 1 << 6, + ERndFipsChkFail = 1 << 7, + EFatal = 1 << 20, + ECallMMode = 1 << 31, +} + +/// Special purpose register (narrow, 32-bit) +pub trait CSR { + fn read(&self) -> Result; + fn write(&mut self, val: u32) -> Result<(), ExceptionCause>; +} + +/// Special purpose register (wide, 256-bit) +pub trait WSR { + fn read(&self) -> Result; + fn write(&mut self, val: u256) -> Result<(), ExceptionCause>; +} + +/// Cryptograph-secure random generator trait +pub trait CSRNG { + fn get_csrng_u32(&self) -> (u32, bool, bool); + fn get_csrng_u256(&self) -> (u256, bool, bool); +} + +/// Pseudo random generator trait +pub trait PRNG { + fn get_prng_u32(&mut self) -> u32; + fn get_prng_u64(&mut self) -> u64; + fn get_prng_u256(&mut self) -> u256; +} + +/// A trait for objects which implement memory operations +pub trait Memory: Send { + /// Read `size` bytes from `addr`. + /// + /// `addr` must be aligned to `size`. + /// Returns `None` if `addr` doesn't exist in this memory. + fn read_mem(&mut self, addr: u32) -> Option; + + /// Write `size` bytes of `store_data` to `addr` + /// + /// `addr` must be aligned to `size`. + /// Returns `true` if write succeeds. + fn write_mem(&mut self, addr: u32, store_data: u32) -> bool; + + fn wipe(&mut self, prng: &mut dyn PRNG); + + fn update_from_slice(&mut self, src: &[u32]); +} diff --git a/hw/opentitan/otbn/otbn/src/memory.rs b/hw/opentitan/otbn/otbn/src/memory.rs new file mode 100644 index 0000000000000..3d55de616d632 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/memory.rs @@ -0,0 +1,135 @@ +// Copyright 2021 Gregory Chadwick +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use crate::PRNG; +use std::io::{self, ErrorKind, Read}; + +use super::Memory; + +/// Read bytes from an [std::io::Read] into a [Memory] starting at the given address +pub fn read_to_memory( + reader: &mut impl Read, + memory: &mut impl Memory, + size: usize, +) -> io::Result<()> { + let mut addr: usize = 0; + loop { + let mut buffer = [0; 4]; + if let Err(error) = reader.read_exact(&mut buffer) { + match error.kind() { + ErrorKind::UnexpectedEof => break, + _ => return Err(error), + } + } + let word = u32::from_le_bytes(buffer); + if !memory.write_mem(addr as u32, word) { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Could not write byte at address 0x{:08x}", addr), + )); + } + addr += 4; + if addr >= size { + break; + } + } + + Ok(()) +} + +/// [Vec] backed memory. +/// +/// The [Vec] uses `u32` as the base type. Any read or write that falls out of the [Vec]s size will +/// result in a failed read or write. +pub struct VecMemory { + pub mem: Vec, +} + +impl VecMemory { + pub fn new(size: usize) -> Self { + Self { + mem: vec![0u32; size / 4], + } + } + + fn random_wipe(&mut self, prng: &mut dyn PRNG) { + for cell in self.mem.iter_mut() { + *cell = prng.get_prng_u32(); + } + } +} + +impl Memory for VecMemory { + fn read_mem(&mut self, addr: u32) -> Option { + if (addr & 0x3) != 0 { + panic!("Memory read must be aligned"); + } + + // Calculate vector index required data is contained in + let word_addr = addr >> 2; + + // Read data from vector + let read_data = self.mem.get(word_addr as usize).copied()?; + + // Apply mask and shift to extract required data from word + Some(read_data) + } + + fn write_mem(&mut self, addr: u32, store_data: u32) -> bool { + // Calculate a mask and shift needed to update 32-bit word + if (addr & 0x3) != 0 { + panic!("Memory write must be aligned"); + } + + // Calculate vector index data to update is contained in + let word_addr = (addr >> 2) as usize; + + self.mem[word_addr] = store_data; + true + } + + fn update_from_slice(&mut self, src: &[u32]) { + if src.len() < self.mem.len() { + let (head, _) = self.mem.split_at_mut(src.len()); + head.copy_from_slice(src); + } else { + self.mem.copy_from_slice(src); + } + } + + fn wipe(&mut self, prng: &mut dyn PRNG) { + self.random_wipe(prng); + } +} + +pub struct MemoryRegion { + memory: Box, +} + +impl MemoryRegion { + pub fn new(size: usize) -> Self { + Self { + memory: Box::new(VecMemory::new(size)), + } + } +} + +impl Memory for MemoryRegion { + fn read_mem(&mut self, addr: u32) -> Option { + self.memory.read_mem(addr) + } + + fn write_mem(&mut self, addr: u32, store_data: u32) -> bool { + self.memory.write_mem(addr, store_data) + } + + fn update_from_slice(&mut self, src: &[u32]) { + self.memory.update_from_slice(src); + } + + fn wipe(&mut self, prng: &mut dyn PRNG) { + self.memory.wipe(prng) + } +} diff --git a/hw/opentitan/otbn/otbn/src/otbn.rs b/hw/opentitan/otbn/otbn/src/otbn.rs new file mode 100644 index 0000000000000..a49e4d789941c --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/otbn.rs @@ -0,0 +1,556 @@ +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +extern crate ethnum; + +use std::fs::File; +use std::io; +use std::io::Write; +use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use bitflags::bitflags; + +use ethnum::U256; + +use super::comm; +use super::csrs; +use super::insn_decode; +use super::insn_disasm; +use super::insn_exec; +use super::key; +use super::memory; +use super::random; +use super::Memory; +use crate::{ExceptionCause, PRNG}; + +/// Instruction memory size +pub const IMEM_SIZE: usize = 8 << 10; +/// Data memory size +pub const DMEM_SIZE: usize = 4 << 10; +/// Data public memory size +pub const DMEM_PUB_SIZE: usize = 3 << 10; + +bitflags! { + /// List of bits in the ERR_BITS register + #[derive(Default)] + pub struct ErrBits: u32 { + const BAD_DATA_ADDR = 1 << 0; + const BAD_INSN_ADDR = 1 << 1; + const CALL_STACK = 1 << 2; + const ILLEGAL_INSN = 1 << 3; + const LOOP = 1 << 4; + const KEY_INVALID = 1 << 5; + const RND_REP_CHK_FAIL = 1 << 6; + const RND_FIPS_CHK_FAIL = 1 << 7; + const IMEM_INTG_VIOLATION = 1 << 16; + const DMEM_INTG_VIOLATION = 1 << 17; + const REG_INTG_VIOLATION = 1 << 18; + const BUS_INTG_VIOLATION = 1 << 19; + const BAD_INTERNAL_STATE = 1 << 20; + const ILLEGAL_BUS_ACCESS = 1 << 21; + const LIFECYCLE_ESCALATION = 1 << 22; + const FATAL_SOFTWARE = 1 << 23; + } +} + +pub enum FlagMode { + Fg0 = 0x0, + Fg1 = 0x1, + Flags = 0x2, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum Status { + #[default] + Idle = 0, + BusyExecute = 1, + BusySecWipeDMem = 2, + BusySecWipeIMem = 3, + BusySecWipeInt = 4, + Locked = 0xff, +} + +impl Status { + pub fn from_u32(val: u32) -> Self { + match val { + val if val == Status::Idle as u32 => Status::Idle, + val if val == Status::BusyExecute as u32 => Status::BusyExecute, + val if val == Status::BusySecWipeDMem as u32 => Status::BusySecWipeDMem, + val if val == Status::BusySecWipeIMem as u32 => Status::BusySecWipeIMem, + val if val == Status::BusySecWipeInt as u32 => Status::BusySecWipeInt, + _ => Status::Locked, + } + } +} + +#[derive(Default)] +pub struct Registers { + /// Status bitfield + pub status: Arc, + /// ErrBits bitfield + pub err_bits: Arc, + /// ErrBits bitfield, + pub fatal_bits: Arc, + pub ctrl: Arc, + pub insn_count: Arc, +} + +/// OTBN executer +/// Run from a worker thread +/// Use two channels to communicate w/ the proxy and shared registers +pub struct Executer { + hart_state: insn_exec::HartState, + imem: Arc>, + dmem: Arc>, + channel: comm::UpChannel, + registers: Arc, + syncurnd: Arc, + on_complete: Option>, + log_file: Option>, + log_asm: bool, +} + +impl Executer { + #[allow(clippy::too_many_arguments)] + fn new( + channel: comm::UpChannel, + registers: Arc, + imem: Arc>, + dmem: Arc>, + syncurnd: Arc, + rnd: Arc, + key: Arc, + on_complete: Option>, + log_name: Option, + log_asm: bool, + ) -> Self { + let log_file: Option>; + if let Some(logname) = log_name { + if logname == "stderr" { + log_file = Some(Box::new(io::stderr())); + } else { + log_file = Some(Box::new(File::create(logname).unwrap())); + } + } else { + log_file = None; + } + Self { + hart_state: insn_exec::HartState::new(syncurnd.urnd(), rnd, key), + imem, + dmem, + channel, + registers, + syncurnd, + on_complete, + log_file, + log_asm, + } + } + + #[allow(clippy::too_many_arguments)] + pub fn run( + channel: comm::UpChannel, + registers: Arc, + imem: Arc>, + dmem: Arc>, + urnd: Arc, + rnd: Arc, + key: Arc, + on_complete: Option>, + log_name: Option, + log_asm: bool, + ) { + Self::new( + channel, + registers, + imem, + dmem, + urnd, + rnd, + key, + on_complete, + log_name, + log_asm, + ) + .enter(); + } + + fn enter(&mut self) { + self.set_status(Status::Idle); + let threadid = thread::current().id(); + self.channel + .1 + .send(comm::Response::Active(threadid)) + .unwrap(); + loop { + let cmd = self.channel.0.recv().unwrap(); + let state = self.get_status(); + match state { + Status::Idle => self.handle_comm(cmd), + Status::BusyExecute + | Status::BusySecWipeDMem + | Status::BusySecWipeIMem + | Status::BusySecWipeInt => { + self.channel + .1 + .send(comm::Response::Error("busy".to_string())) + .unwrap(); + } + Status::Locked => { + self.channel + .1 + .send(comm::Response::Error("locked".to_string())) + .unwrap(); + } + } + } + } + + fn get_status(&self) -> Status { + let val = self.registers.status.load(Ordering::Relaxed); + Status::from_u32(val) + } + + fn set_status(&mut self, status: Status) { + self.registers + .status + .store(status as u32, Ordering::Relaxed); + } + + /// Handle requests from the proxy + fn handle_comm(&mut self, cmd: comm::Command) { + match cmd { + comm::Command::SetTestMode(enable) => { + self.hart_state.set_test_mode(enable); + self.channel.1.send(comm::Response::Ack).unwrap(); + self.signal_completion(); + } + comm::Command::LogTo(logfile) => { + self.log_file = Some(logfile); + self.channel.1.send(comm::Response::Ack).unwrap(); + self.signal_completion(); + } + comm::Command::Execute(dump) => { + // always update the state before replying + self.set_status(Status::BusyExecute); + self.channel.1.send(comm::Response::Ack).unwrap(); + self.execute(dump); + self.signal_completion(); + } + comm::Command::WipeDMem => { + // always update the state before replying + self.set_status(Status::BusySecWipeDMem); + self.channel.1.send(comm::Response::Ack).unwrap(); + self.wipe_memory(false); + self.signal_completion(); + } + comm::Command::WipeIMem => { + // always update the state before replying + self.set_status(Status::BusySecWipeIMem); + self.channel.1.send(comm::Response::Ack).unwrap(); + self.wipe_memory(true); + self.signal_completion(); + } + comm::Command::Terminate => { + self.set_status(Status::Locked); + self.channel.1.send(comm::Response::Ack).unwrap(); + self.signal_completion(); + } + } + } + + /// Execute the uploaded OTBN program + fn execute(&mut self, dump: bool) { + let mut fatal; + // "Each new execution of OTBN will reseed the URND PRNG." + // Stall OTBN execution till entropy is injected + self.syncurnd.sync_reseed(); + match self.do_execute(dump) { + insn_exec::InstructionTrap::Exception(cause, _val) => { + let mut error: u32; + // let mut registers = self.registers.lock().unwrap(); + (fatal, error) = match cause { + ExceptionCause::ECallMMode => (false, ErrBits::empty().bits()), + ExceptionCause::EBadDataAddr => (false, ErrBits::BAD_DATA_ADDR.bits()), + ExceptionCause::EBadInsnAddr => (false, ErrBits::BAD_INSN_ADDR.bits()), + ExceptionCause::ECallStack => (false, ErrBits::CALL_STACK.bits()), + ExceptionCause::EIllegalInsn => (false, ErrBits::ILLEGAL_INSN.bits()), + ExceptionCause::ELoop => (false, ErrBits::LOOP.bits()), + ExceptionCause::EKeyInvalid => (false, ErrBits::KEY_INVALID.bits()), + ExceptionCause::ERndRepChkFail => (false, ErrBits::RND_REP_CHK_FAIL.bits()), + ExceptionCause::ERndFipsChkFail => (false, ErrBits::RND_FIPS_CHK_FAIL.bits()), + ExceptionCause::EFatal => { + (true, self.registers.fatal_bits.load(Ordering::Relaxed)) + } + }; + + // ctrl: "Controls the reaction to software errors. + // When set software errors produce fatal errors, rather than recoverable errors." + if error != 0 && self.registers.ctrl.load(Ordering::Relaxed) { + fatal = true; + error |= ErrBits::FATAL_SOFTWARE.bits(); + } + // update error bits + self.registers.err_bits.fetch_or(error, Ordering::Relaxed); + } + } + if fatal { + let x: Arc> = self.syncurnd.urnd(); + self.dmem.try_lock().unwrap().wipe(&mut *x.lock().unwrap()); + self.imem.try_lock().unwrap().wipe(&mut *x.lock().unwrap()); + self.registers.insn_count.store(0, Ordering::Relaxed); + self.set_status(Status::Locked); + }; + // note: it is required that the OTBN client calls + // Proxy::acknowledge_execution() to reset the OTBN status once the + // operation on its side is over. Busy status is updated on that ack. + } + + fn do_execute(&mut self, dump: bool) -> insn_exec::InstructionTrap { + let mut executor = insn_exec::InstructionExecutor { + hart_state: &mut self.hart_state, + imem: &mut *self.imem.try_lock().unwrap(), + dmem: &mut *self.dmem.try_lock().unwrap(), + }; + + executor.reset(); + + self.registers + .err_bits + .store(ErrBits::empty().bits(), Ordering::Relaxed); + self.registers.insn_count.store(0, Ordering::Relaxed); + + loop { + // Debug/traces + if self.log_asm { + if let Some(log_file) = &mut self.log_file { + // Output current instruction disassembly to log + if let Some(insn_bits) = executor.imem.read_mem(executor.hart_state.pc) { + let mut outputter = insn_disasm::InstructionStringOutputter { + insn_pc: executor.hart_state.pc, + }; + if let Some(inst) = insn_decode::decoder(&mut outputter, insn_bits) { + writeln!( + log_file, + "{:04x}: {:08x} {}", + executor.hart_state.pc, insn_bits, inst + ) + .expect("Log file write failed"); + } else { + let base = (insn_bits >> 2) & 0b11111; + let funct3 = (insn_bits >> 12) & 0b111; + writeln!( + log_file, + "Unable to decode instruction @ {:x}: {:x} [{:03b}..{:05b}]", + executor.hart_state.pc, insn_bits, funct3, base + ) + .expect("Log file write failed"); + } + } else { + writeln!(log_file, "Could not read PC {:08x}", executor.hart_state.pc) + .expect("Log file write failed"); + } + } + } + + let fatalbits = self.registers.fatal_bits.load(Ordering::Relaxed); + let result = if fatalbits == 0 { + // Execute instruction + executor.step() + } else { + Err(insn_exec::InstructionTrap::Exception( + ExceptionCause::EFatal, + Some(fatalbits), + )) + }; + if let Err(trap) = result { + if let Some(log_file) = &mut self.log_file { + let log_line = match trap { + insn_exec::InstructionTrap::Exception(cause, val) => { + if let Some(val) = val { + format!("[{:?} Exception, value:{:08x}]", cause, val) + } else { + format!("[{:?} Exception]", cause) + } + } + }; + + writeln!(log_file, "{} @ PC {:08x}", log_line, executor.hart_state.pc) + .expect("Log file write failed"); + } + + if dump { + Self::dump_hart_state(executor.hart_state, true); + } + + // on a fatal error + // on recoverable error + // on operation completion + self.registers + .status + .store(Status::BusySecWipeInt as u32, Ordering::Relaxed); + + // "The wiping procedure is a two-step process: + // Overwrite the state with randomness from URND and request a reseed of URND. + // Overwrite the state with randomness from reseeded URND." + let prng: Arc> = self.syncurnd.urnd(); + executor.wipe_internal(&prng); + self.syncurnd.sync_reseed(); + executor.wipe_internal(&prng); + + let insn_exec::InstructionTrap::Exception(cause, _) = trap; + if cause == ExceptionCause::ECallMMode { + // if the exception has been triggered by an ecall instruction, + // the actual instruction has been executed; otherwise the instruction failed + // to execute + self.registers.insn_count.fetch_add(1, Ordering::Relaxed); + } else { + executor.hart_state.pc = 0; + } + + return trap; + } + + self.registers.insn_count.fetch_add(1, Ordering::Relaxed); + + if self.log_asm { + if let Some(log_file) = &mut self.log_file { + Executer::log_changes(executor.hart_state, log_file); + } + } + } + } + + fn wipe_memory(&mut self, doi: bool) { + let memory = if doi { &mut self.imem } else { &mut self.dmem }; + + memory + .try_lock() + .unwrap() + .wipe(&mut *self.syncurnd.urnd().lock().unwrap()); + + // simulate a "long lasting" op + thread::sleep(Duration::from_micros(200)); + + // note: it is expected that the OTBN client calls + // Proxy::acknowledge_execution() to reset the OTBN status once the + // operation on its side is over + } + + fn signal_completion(&mut self) { + if let Some(cb) = &mut self.on_complete { + cb.signal(); + } + } + + /// Set the log file to which execution traces should be dumped - if any + fn log_changes(hart_state: &insn_exec::HartState, log_file: &mut Box) { + let state = &hart_state.updated; + if let Some(reg_index) = state.gpr { + writeln!( + log_file, + "\t\t\tx{} = 0x{:08x}", + reg_index, hart_state.registers[reg_index] + ) + .expect("Log file write failed"); + } + if let Some(reg_index) = state.wgpr { + writeln!( + log_file, + "\t\t\tw{} = 0x{:064x}", + reg_index, hart_state.wregisters[reg_index] + ) + .expect("Log file write failed"); + } + if let Some((wide_reg, reg_addr)) = state.csr { + // Output special register written by instruction to log if it wrote to one + if !wide_reg { + let csr = hart_state.csr_set.get_csr(reg_addr); + match csr.unwrap().read() { + Ok(val) => writeln!( + log_file, + "\t\t\tcsr:{} = 0x{:08x}", + csrs::CSRAddr::string_name(reg_addr), + val + ), + Err(exc) => writeln!( + log_file, + "\t\t\tcsr:{} => {:?}", + csrs::CSRAddr::string_name(reg_addr), + exc + ), + } + .expect("Log file write failed"); + } else { + let wsr = hart_state.csr_set.get_wsr(reg_addr); + match wsr.unwrap().read() { + Ok(val) => writeln!( + log_file, + "\t\t\tcsr:{} = 0x{:064x}", + csrs::WSRAddr::string_name(reg_addr), + val + ), + Err(exc) => writeln!( + log_file, + "\t\t\tcsr:{} => {:?}", + csrs::WSRAddr::string_name(reg_addr), + exc + ), + } + .expect("Log file write failed"); + } + } + if let Some((depth, count)) = state.loophead { + writeln!(log_file, "\t\t\tloop[{}] = {}", depth, count).expect("Log file write failed"); + } + } + + /// Dump current content of OTBN register (RISC-V and Wide) + /// compact: only dump register whose content differ than zero + fn dump_hart_state(hart_state: &insn_exec::HartState, compact: bool) { + println!(); + + if !compact || !hart_state.hwstack.is_empty() { + println!("Call Stack:"); + println!("-----------"); + for addr in hart_state.hwstack.iter() { + println!("0x{:08x}", addr); + } + println!(); + } + + println!("Final Base Register Values:"); + println!("Reg | Value"); + println!("----------------"); + for (reg, val) in hart_state.registers[2..].iter().enumerate() { + if compact && (*val == 0) { + continue; + } + println!("x{:<2} | 0x{:08x}", reg + 2, val); + } + println!(); + + println!("Final Bignum Register Values:"); + println!("Reg | Value"); + println!("-------------------------------------------------------------------------------"); + for (reg, val) in hart_state.wregisters.iter().enumerate() { + if compact && (*val == U256::from(0u32)) { + continue; + } + let mut w: Vec = Vec::new(); + let mut val256 = *val; + for _ in (0..256).step_by(32) { + w.push(val256.as_u32()); + val256 = val256.wrapping_shr(32); + } + println!( + "w{:<2} | 0x{:08x}_{:08x}_{:08x}_{:08x}_{:08x}_{:08x}_{:08x}_{:08x}", + reg, w[7], w[6], w[5], w[4], w[3], w[2], w[1], w[0] + ); + } + } +} diff --git a/hw/opentitan/otbn/otbn/src/proxy.rs b/hw/opentitan/otbn/otbn/src/proxy.rs new file mode 100644 index 0000000000000..42307dd3c14f9 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/proxy.rs @@ -0,0 +1,573 @@ +// Copyright 2022-2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::convert::TryFrom; +use std::ffi::CStr; +use std::fs::File; +use std::io; +use std::os::raw::{c_char, c_int, c_void}; +use std::slice; +use std::sync::atomic::Ordering; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread; + +use super::comm; +use super::key; +use super::memory; +use super::otbn; +use super::random; +use super::Memory; + +/// Run from the "main" thread +pub struct Proxy { + /// Instruction memory + imem: Arc>, + + /// Data memory + dmem: Arc>, + + /// Shared registers with the core + registers: Arc, + + /// Thread handle of the core executer + join_handle: Option>, + + /// Communication channel with the core + channel: Option, + + /// thread identifier of the core runner + core_id: Option, + + /// Pesudo random generator, reseeded from EDN + syncurnd: Arc, + + /// True random generator from EDN + rnd: Arc, + + /// Sideload key + key: Arc, + + /// Transient optional callback to invoke on completion + on_complete: Option>, +} + +impl Default for Proxy { + fn default() -> Self { + Self { + imem: Arc::new(Mutex::new(memory::MemoryRegion::new(otbn::IMEM_SIZE))), + dmem: Arc::new(Mutex::new(memory::MemoryRegion::new(otbn::DMEM_SIZE))), + join_handle: None, + channel: None, + registers: Arc::new(otbn::Registers::default()), + core_id: None, + syncurnd: Arc::new(random::SyncUrnd::new()), + rnd: Arc::new(random::Rnd::new()), + key: Arc::new(key::Key::new()), + on_complete: None, + } + } +} + +impl Proxy { + pub fn new() -> Self { + Default::default() + } + + /// Report the state of the core executer + pub fn get_status(&mut self) -> otbn::Status { + otbn::Status::from_u32(self.registers.status.load(Ordering::Relaxed)) + } + + /// Tell the core executer the operation completion has been acknowledged + /// All registers are managed by the executer, however with QEMU sycnhronous + /// implementation we need a way to defer execution completion to better + /// simulate hardware behavior. When an operation is over, it is signalled + /// through the C callback (see on_complete), which is then handled at C + /// level through a QEMU timer that defer actual completion. The operational + /// status of the OTBN core is not changed till this timer is exhausted, + /// which gave back execution to the QEMU vCPU where the guest code can poll + /// the status of the OTBN registers. Without this hack, the guest code + /// would only see the OTBN idle since the executed command would likely + /// complete before the vCPU has a chance to get scheduled. + pub fn acknowledge_execution(&mut self) -> bool { + if self.get_status() == otbn::Status::Locked { + return false; + } + self.registers + .status + .store(otbn::Status::Idle as u32, Ordering::Relaxed); + true + } + + /// Register a callback for requesting entropy from EDN + pub fn register_entropy_req_cb( + &mut self, + urnd_entropy_req: Box, + rnd_entropy_req: Box, + ) { + self.syncurnd.register_entropy_req_cb(urnd_entropy_req); + self.rnd.register_entropy_req_cb(rnd_entropy_req); + } + + /// Register a callback for signalling a client on completion + pub fn register_signal_cb(&mut self, on_complete: Box) { + self.on_complete = Some(on_complete); + } + + /// Kick off the core executer, and create the communication channels + pub fn start(&mut self, test_mode: bool, log_name: Option<&str>, log_asm: bool) { + if self.core_id.is_some() { + // already started, only reset + // hartstate is not reset, as it is done after each execution + match self.get_status() { + otbn::Status::Idle | otbn::Status::Locked => { + let regs = &self.registers; + regs.status + .store(otbn::Status::Idle as u32, Ordering::Relaxed); + regs.err_bits.store(0, Ordering::Relaxed); + regs.fatal_bits.store(0, Ordering::Relaxed); + regs.ctrl.store(false, Ordering::Relaxed); + regs.insn_count.store(0, Ordering::Relaxed); + } + _ => (), + } + return; + } + + let (cmdtx, cmdrx) = mpsc::channel::(); + let (resptx, resprx) = mpsc::channel::(); + self.channel = Some((cmdtx, resprx)); + if self.channel.is_some() { + let registers = Arc::clone(&self.registers); + let imem = Arc::clone(&self.imem); + let dmem = Arc::clone(&self.dmem); + let executer = thread::Builder::new().name("otbn_exec".into()); + let on_complete = self.on_complete.take(); + let urnd = self.syncurnd.clone(); + let rnd = Arc::clone(&self.rnd); + let key = Arc::clone(&self.key); + let log_name: Option = log_name.map(|l| l.into()); + self.join_handle = Some( + executer + .spawn(move || { + otbn::Executer::run( + (cmdrx, resptx), + registers, + imem, + dmem, + urnd, + rnd, + key, + on_complete, + log_name, + log_asm, + ) + }) + .unwrap(), + ); + self.identify(); + } + self.set_test_mode(test_mode); + } + + /// Attach a log file to trace core execution + pub fn log_to(&mut self, logfile: Box>) { + self.check_request(); + let channel = self.get_channel(); + channel.0.send(comm::Command::LogTo(logfile)).unwrap(); + match channel.1.recv().unwrap() { + comm::Response::Ack => (), + comm::Response::Error(e) => panic!("Error: {}", e), + _ => panic!("Unexpected response"), + } + } + + /// Read one 32-bit from memory + fn read_memory(&mut self, doi: bool, addr: u32) -> u32 { + match self.get_status() { + otbn::Status::Idle => (), + otbn::Status::Locked => return 0, // Reads return zero in Locked state + _ => { + self.registers + .fatal_bits + .fetch_or(otbn::ErrBits::ILLEGAL_BUS_ACCESS.bits(), Ordering::Relaxed); + return 0; + } + } + + let mem = if doi { + &self.imem + } else { + if addr as usize >= otbn::DMEM_PUB_SIZE { + return 0; + } + &self.dmem + }; + + if let Some(data) = mem.lock().unwrap().read_mem(addr) { + data + } else { + 0 + } + } + + /// Write one 32-bit to memory + fn write_memory(&mut self, doi: bool, addr: u32, data: u32) -> bool { + match self.get_status() { + otbn::Status::Idle => (), + otbn::Status::Locked => return false, // Writes have no effect in Locked state + _ => { + self.registers + .fatal_bits + .fetch_or(otbn::ErrBits::ILLEGAL_BUS_ACCESS.bits(), Ordering::Relaxed); + return false; + } + } + + let mem = if doi { + &self.imem + } else { + if addr as usize >= otbn::DMEM_PUB_SIZE { + return false; + } + &self.dmem + }; + + mem.lock().unwrap().write_mem(addr, data) + } + + /// Push a 256-bit entropy buffer + pub fn push_entropy(&mut self, rndix: usize, seed: &[u8], fips: bool) -> bool { + if seed.len() != 32 { + return false; + } + if let Ok(seed) = <&[u8; 32]>::try_from(seed) { + match rndix { + /* Pseudo URND */ + 0 => { + self.syncurnd.fill(seed, fips); + /* URND has been reseeded, OTBN engine can now start */ + } + /* Secure RND */ + 1 => self.rnd.fill(seed, fips), + _ => return false, + } + return true; + } + + false + } + + /// Push a 384-bit key buffer + pub fn push_key(&mut self, share0: &[u8], share1: &[u8], valid: bool) -> bool { + if share0.len() != 48 && share1.len() != 48 { + return false; + } + if let (Ok(share0), Ok(share1)) = + (<&[u8; 48]>::try_from(share0), <&[u8; 48]>::try_from(share1)) + { + self.key.fill(share0, share1, valid); + return true; + } + + false + } + + /// Execute the loaded code + pub fn execute(&mut self, dump: bool) -> bool { + self.check_request(); + if self.join_handle.is_some() { + let channel = self.get_channel(); + channel.0.send(comm::Command::Execute(dump)).unwrap(); + match channel.1.recv().unwrap() { + comm::Response::Ack => true, + comm::Response::Error(_) => false, + _ => panic!("Unexpected response"), + } + } else { + false + } + } + + /// Wipe memory + pub fn wipe_memory(&mut self, doi: bool) -> bool { + self.check_request(); + if self.join_handle.is_some() { + let channel = self.get_channel(); + let command = if doi { + comm::Command::WipeIMem + } else { + comm::Command::WipeDMem + }; + channel.0.send(command).unwrap(); + match channel.1.recv().unwrap() { + comm::Response::Ack => true, + comm::Response::Error(_) => false, + _ => panic!("Unexpected response"), + } + } else { + false + } + } + + /// Shutdown the core executer. + /// Note: should only be called once, i.e. not for each execution session. + pub fn terminate(&mut self) { + self.check_request(); + let channel = self.get_channel(); + channel.0.send(comm::Command::Terminate).unwrap(); + match channel.1.recv().unwrap() { + comm::Response::Ack => (), + comm::Response::Error(e) => panic!("Error: {}", e), + _ => panic!("Unexpected response"), + } + self.join(); + } + + /// Helper function to use the communication channel w/ the executer + fn get_channel(&mut self) -> &mut comm::DownChannel { + match &mut self.channel { + Some(channel) => channel, + // programming error + _ => panic!("No communication channel"), + } + } + + /// Enable test mode (predefined RND values, ...) + fn set_test_mode(&mut self, enable: bool) { + let channel = self.get_channel(); + channel.0.send(comm::Command::SetTestMode(enable)).unwrap(); + match channel.1.recv().unwrap() { + comm::Response::Ack => (), + comm::Response::Error(e) => panic!("Error: {}", e), + _ => panic!("Unexpected response"), + } + } + + /// Store identification of the runner thread + fn identify(&mut self) { + let channel = self.get_channel(); + // runner starts with pushing a message w/o prior solicitation from + // the parent, i.e. no explicit command + match channel.1.recv().unwrap() { + comm::Response::Active(threadid) => self.core_id = Some(threadid), + comm::Response::Ack => panic!("Unexpected ack"), + comm::Response::Error(e) => panic!("Error: {}", e), + } + } + + fn check_request(&self) { + if let Some(core_id) = self.core_id { + if thread::current().id() == core_id { + panic!("Command requested from runner thread"); + } + } + } + + /// Sync on executer thread completion + fn join(&mut self) { + self.join_handle.take().map(thread::JoinHandle::join); + } +} + +//-------------------------------------------------------------------------------------------------- +// C API +//-------------------------------------------------------------------------------------------------- + +type CCallbackArg = *mut c_void; +type CCallbackFunc = unsafe extern "C" fn(arg: CCallbackArg); + +/// Proxy callback for C implementation +pub struct CCallback { + /// C callback + cb: CCallbackFunc, + /// optional argument to the C callback, default to C NULL + arg: Arc>, +} + +impl CCallback { + pub fn new(cb: CCallbackFunc, arg: CCallbackArg) -> Self { + Self { + cb, + arg: Arc::new(Mutex::new(arg)), + } + } +} + +unsafe impl Send for CCallback {} + +impl comm::Callback for CCallback { + fn signal(&mut self) { + unsafe { + (self.cb)(*self.arg.lock().unwrap()); + } + } +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_new( + urnd_entropy_req: CCallbackFunc, + urnd_opaque: CCallbackArg, + rnd_entropy_req: CCallbackFunc, + rnd_opaque: CCallbackArg, + on_complete: CCallbackFunc, + on_comp_opaque: CCallbackArg, +) -> Box { + let mut proxy = Box::new(Proxy::new()); + proxy.register_entropy_req_cb( + Box::new(CCallback::new(urnd_entropy_req, urnd_opaque)), + Box::new(CCallback::new(rnd_entropy_req, rnd_opaque)), + ); + proxy.register_signal_cb(Box::new(CCallback::new(on_complete, on_comp_opaque))); + proxy +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn ot_otbn_proxy_start( + proxy: Option<&mut Proxy>, + test_mode: bool, + log_name: *const c_char, + log_asm: bool, +) { + let log_name: Option<&str> = if !log_name.is_null() { + Some(CStr::from_ptr(log_name).to_str().unwrap()) + } else { + None + }; + proxy.unwrap().start(test_mode, log_name, log_asm); +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_terminate(proxy: Option<&mut Proxy>) { + proxy.unwrap().terminate(); +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn ot_otbn_proxy_push_entropy( + proxy: Option<&mut Proxy>, + rndix: u32, + seed: *const u8, + len: u32, + fips: bool, +) -> bool { + assert!(!seed.is_null()); + let rust_seed = slice::from_raw_parts(seed, len as usize); + proxy.unwrap().push_entropy(rndix as usize, rust_seed, fips) +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn ot_otbn_proxy_push_key( + proxy: Option<&mut Proxy>, + share0: *const u8, + share1: *const u8, + len: u32, + valid: bool, +) -> bool { + assert!(!share0.is_null()); + assert!(!share1.is_null()); + let rust_share0 = slice::from_raw_parts(share0, len as usize); + let rust_share1 = slice::from_raw_parts(share1, len as usize); + proxy.unwrap().push_key(rust_share0, rust_share1, valid) +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_execute(proxy: Option<&mut Proxy>, dump: bool) -> c_int { + if proxy.unwrap().execute(dump) { + 0 + } else { + -1 + } +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_wipe_memory(proxy: Option<&mut Proxy>, doi: bool) -> c_int { + if proxy.unwrap().wipe_memory(doi) { + 0 + } else { + -1 + } +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_acknowledge_execution(proxy: Option<&mut Proxy>) -> bool { + proxy.unwrap().acknowledge_execution() +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_read_memory( + proxy: Option<&mut Proxy>, + doi: bool, + addr: u32, +) -> u32 { + proxy.unwrap().read_memory(doi, addr) +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_write_memory( + proxy: Option<&mut Proxy>, + doi: bool, + addr: u32, + val: u32, +) -> bool { + proxy.unwrap().write_memory(doi, addr, val) +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_get_status(proxy: Option<&mut Proxy>) -> c_int { + proxy.unwrap().get_status() as c_int +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_get_instruction_count(proxy: Option<&mut Proxy>) -> u32 { + let proxy = proxy.unwrap(); + if proxy.get_status() != otbn::Status::Locked { + proxy.registers.insn_count.load(Ordering::Relaxed) as u32 + } else { + 0 + } +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_set_instruction_count(proxy: Option<&mut Proxy>, value: u32) { + let proxy = proxy.unwrap(); + if proxy.get_status() == otbn::Status::Idle { + proxy + .registers + .insn_count + .store(value as usize, Ordering::Relaxed) + } +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_get_err_bits(proxy: Option<&mut Proxy>) -> u32 { + proxy.unwrap().registers.err_bits.load(Ordering::Relaxed) +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_set_err_bits(proxy: Option<&mut Proxy>, value: u32) { + let proxy = proxy.unwrap(); + if proxy.get_status() == otbn::Status::Idle { + proxy.registers.err_bits.store( + otbn::ErrBits::from_bits_truncate(value).bits(), + Ordering::Relaxed, + ) + } +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_get_ctrl(proxy: Option<&mut Proxy>) -> bool { + proxy.unwrap().registers.ctrl.load(Ordering::Relaxed) +} + +#[no_mangle] +pub extern "C" fn ot_otbn_proxy_set_ctrl(proxy: Option<&mut Proxy>, value: bool) { + let proxy = proxy.unwrap(); + if proxy.get_status() == otbn::Status::Idle { + proxy.registers.ctrl.store(value, Ordering::Relaxed) + } +} diff --git a/hw/opentitan/otbn/otbn/src/random.rs b/hw/opentitan/otbn/otbn/src/random.rs new file mode 100644 index 0000000000000..71c290ae842c9 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/random.rs @@ -0,0 +1,213 @@ +// Copyright 2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +use ethnum::{u256, U256}; + +use super::comm; +use super::xoshiro256pp::Xoshiro256PlusPlus; +use crate::{CSRNG, PRNG}; + +#[derive(Default)] +pub struct RndCache { + value: u256, + available: bool, + fips: bool, + repeat: bool, +} + +pub struct Rnd { + cache: Mutex, + wait: Condvar, + entropy_req: Mutex>>, +} + +impl Default for Rnd { + fn default() -> Self { + let cache = RndCache::default(); + Self { + cache: Mutex::new(cache), + wait: Condvar::new(), + entropy_req: Mutex::new(None), + } + } +} + +impl Rnd { + pub fn new() -> Self { + Self::default() + } + + pub fn register_entropy_req_cb(&self, entropy_req: Box) { + let mut func = self.entropy_req.lock().unwrap(); + *func = Some(entropy_req); + } + + pub fn clear(&mut self) { + /* called from OTBN proxy */ + let mut cache = self.cache.lock().unwrap(); + cache.value = U256::from(0u32); + cache.available = false; + cache.fips = false; + cache.repeat = false; + } + + pub fn fill(&self, values: &[u8; 32], fips: bool) { + /* called from OTBN proxy */ + let val = U256::from_le_bytes(*values); + let mut cache = self.cache.lock().unwrap(); + cache.repeat = cache.value == val; + cache.value = val; + cache.available = true; + cache.fips = fips; + self.wait.notify_one(); + } + + pub fn prefetch(&self) { + /* called from OTBN processor (CSR) */ + let cache = self.cache.lock().unwrap(); + if cache.available { + /* cache is already loaded, nothing to do */ + return; + } + self.fetch(); + } + + fn fetch(&self) { + let mut func = self.entropy_req.lock().unwrap(); + if let Some(req) = &mut *func { + req.signal(); + } + } +} + +impl CSRNG for Rnd { + fn get_csrng_u32(&self) -> (u32, bool, bool) { + // "A read from the RND CSR returns the bottom 32b; the other 192b are discarded." + let (val, fips, repeat) = self.get_csrng_u256(); + (val.as_u32(), fips, repeat) + } + + fn get_csrng_u256(&self) -> (u256, bool, bool) { + let mut cache = self.cache.lock().unwrap(); + let mut fetch = false; + loop { + let result = self + .wait + .wait_timeout(cache, Duration::from_millis(50)) + .unwrap(); + cache = result.0; + if cache.available { + break; + } + if !fetch { + self.fetch(); + fetch = true; + } + } + + let (val, fips, repeat) = (cache.value, cache.fips, cache.repeat); + cache.available = false; + cache.fips = false; + cache.repeat = false; + (val, fips, repeat) + } +} + +#[derive(Default)] +pub struct Urnd { + xoshiro: Xoshiro256PlusPlus, +} + +impl PRNG for Urnd { + fn get_prng_u32(&mut self) -> u32 { + self.xoshiro.next_u32() + } + + fn get_prng_u64(&mut self) -> u64 { + self.xoshiro.next_u64() + } + + fn get_prng_u256(&mut self) -> u256 { + self.xoshiro.next_u256() + } +} + +impl Urnd { + pub fn new() -> Self { + Self::default() + } + + pub fn reseed(&mut self, seed: [u8; 32]) { + self.xoshiro.reseed(seed) + } +} + +pub struct SyncUrnd { + urnd: Arc>, + sync: Mutex, + wait: Condvar, + entropy_req: Mutex>>, +} + +impl Default for SyncUrnd { + fn default() -> Self { + Self { + sync: Mutex::new(false), + wait: Condvar::new(), + urnd: Arc::new(Mutex::new(Urnd::default())), + entropy_req: Mutex::new(None), + } + } +} + +impl SyncUrnd { + pub fn new() -> Self { + Self::default() + } + + pub fn register_entropy_req_cb(&self, entropy_req: Box) { + let mut func = self.entropy_req.lock().unwrap(); + *func = Some(entropy_req); + } + + pub fn urnd(&self) -> Arc> { + self.urnd.clone() + } + + pub fn request_reseed(&self) { + let mut func = self.entropy_req.lock().unwrap(); + if let Some(req) = &mut *func { + req.signal(); + } + } + + pub fn wait_reseed(&self) { + loop { + let sync = self.sync.lock().unwrap(); + let mut go = self + .wait + .wait_timeout(sync, Duration::from_millis(5)) + .unwrap(); + if *go.0 { + *go.0 = false; + break; + } + } + } + + pub fn sync_reseed(&self) { + self.request_reseed(); + self.wait_reseed(); + } + + pub fn fill(&self, seed: &[u8; 32], _fips: bool) { + /* should fips be validated for URND? */ + self.urnd.lock().unwrap().reseed(*seed); + *self.sync.lock().unwrap() = true; + self.wait.notify_one(); + } +} diff --git a/hw/opentitan/otbn/otbn/src/xoshiro256pp.rs b/hw/opentitan/otbn/otbn/src/xoshiro256pp.rs new file mode 100644 index 0000000000000..6d4842a4469de --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/xoshiro256pp.rs @@ -0,0 +1,123 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::convert::TryInto; + +use ethnum::{u256, U256}; + +/// A xoshiro256++ random number generator. +/// +/// The xoshiro256++ algorithm is not suitable for cryptographic purposes, but +/// is very fast and has excellent statistical properties. +/// +/// The algorithm used here is translated from [the `xoshiro256plusplus.c` +/// reference source code](http://xoshiro.di.unimi.it/xoshiro256plusplus.c) by +/// David Blackman and Sebastiano Vigna. + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Xoshiro256PlusPlus { + s: [u64; 4], +} + +/// Reads unsigned 64 bit integers from `src` into `dst`. +#[inline] +pub fn read_u64_into(src: &[u8], dst: &mut [u64]) { + assert!(src.len() >= 8 * dst.len()); + for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(8)) { + *out = u64::from_le_bytes(chunk.try_into().unwrap()); + } +} + +impl Default for Xoshiro256PlusPlus { + /// Create a new `Xoshiro256PlusPlus`. + fn default() -> Self { + // the generator is *not* initialized here. + Xoshiro256PlusPlus::from_seed([0u8; 32]) + } +} + +impl Xoshiro256PlusPlus { + #[inline] + pub fn from_seed(seed: [u8; 32]) -> Xoshiro256PlusPlus { + let mut state = [0; 4]; + read_u64_into(&seed, &mut state); + Xoshiro256PlusPlus { s: state } + } + + pub fn reseed(&mut self, seed: [u8; 32]) { + let mut state = [0; 4]; + read_u64_into(&seed, &mut state); + self.s = state; + } + + #[inline] + pub fn next_u32(&mut self) -> u32 { + // The lowest bits have some linear dependencies, so we use the + // upper bits instead. + (self.next_u64() >> 32) as u32 + } + + #[inline] + pub fn next_u64(&mut self) -> u64 { + let result_plusplus = self.s[0] + .wrapping_add(self.s[3]) + .rotate_left(23) + .wrapping_add(self.s[0]); + + let t = self.s[1] << 17; + + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; + + self.s[2] ^= t; + + self.s[3] = self.s[3].rotate_left(45); + + result_plusplus + } + + pub fn next_u256(&mut self) -> u256 { + let mut val = U256::from(0u32); + for _ in 0..4 { + val = val.wrapping_shl(64); + val |= U256::from(self.next_u64()); + } + val + } + + /// Implement `fill_bytes` via `next_u64` and `next_u32`, little-endian order. + /// + /// The fastest way to fill a slice is usually to work as long as possible with + /// integers. That is why this method mostly uses `next_u64`, and only when + /// there are 4 or less bytes remaining at the end of the slice it uses + /// `next_u32` once. + pub fn fill_bytes_via_next(&mut self, dest: &mut [u8]) { + let mut left = dest; + while left.len() >= 8 { + let (l, r) = { left }.split_at_mut(8); + left = r; + let chunk: [u8; 8] = self.next_u64().to_le_bytes(); + l.copy_from_slice(&chunk); + } + let n = left.len(); + if n > 4 { + let chunk: [u8; 8] = self.next_u64().to_le_bytes(); + left.copy_from_slice(&chunk[..n]); + } else if n > 0 { + let chunk: [u8; 4] = self.next_u32().to_le_bytes(); + left.copy_from_slice(&chunk[..n]); + } + } + + #[inline] + pub fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes_via_next(dest); + } +} diff --git a/meson.build b/meson.build index 67fd0a873645c..3136a5ea4fc05 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('qemu', ['c'], meson_version: '>=1.5.0', +project('qemu', ['c', 'rust'], meson_version: '>=1.5.0', default_options: ['warning_level=1', 'c_std=gnu11', 'cpp_std=gnu++11', 'b_colorout=auto', 'b_staticpic=false', 'stdsplit=false', 'optimization=2', 'b_pie=true'], version: files('VERSION')) diff --git a/subprojects/bitflags.wrap b/subprojects/bitflags.wrap new file mode 100644 index 0000000000000..35239f84419b4 --- /dev/null +++ b/subprojects/bitflags.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = bitflags-1.3.2 + +source_url = https://github.com/bitflags/bitflags/archive/refs/tags/1.3.2.tar.gz +source_filename = bitflags-1.3.2.tar.gz +source_hash = 52ab9ccde868e856b38d4033a6bca0340422cb6460fd587fb26bf46659eb6e34 + +diff_files = bitflags/0001.patch diff --git a/subprojects/ethnum-rs.wrap b/subprojects/ethnum-rs.wrap new file mode 100644 index 0000000000000..ab7346cd44acb --- /dev/null +++ b/subprojects/ethnum-rs.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = ethnum-rs-1.3.0 +source_url = https://github.com/nlordell/ethnum-rs/archive/refs/tags/v1.3.0.tar.gz +source_filename = ethnum-rs-1.3.0.tar.gz +source_hash = 65d2f954d998565cfdd5ac107862e13c9ac2ed62cc68c454c5690c0982b9e02a + +diff_files = ethnum-rs/0001.patch diff --git a/subprojects/packagefiles/bitflags/0001.patch b/subprojects/packagefiles/bitflags/0001.patch new file mode 100644 index 0000000000000..f8c337d059f05 --- /dev/null +++ b/subprojects/packagefiles/bitflags/0001.patch @@ -0,0 +1,13 @@ +diff --git a/meson.build b/meson.build +new file mode 100644 +index 0000000000..792c240684 +--- /dev/null ++++ b/meson.build +@@ -0,0 +1,7 @@ ++project('bitflags', 'rust', version: '1.3.2') ++ ++bitflags_lib = static_library('bitflags', ++ 'src/lib.rs', ++ install: true) ++ ++bitflags_dep = declare_dependency(link_with: bitflags_lib) diff --git a/subprojects/packagefiles/ethnum-rs/0001.patch b/subprojects/packagefiles/ethnum-rs/0001.patch new file mode 100644 index 0000000000000..286ba307cb2f4 --- /dev/null +++ b/subprojects/packagefiles/ethnum-rs/0001.patch @@ -0,0 +1,14 @@ +diff --git a/meson.build b/meson.build +new file mode 100644 +index 0000000000..1f7c0456e8 +--- /dev/null ++++ b/meson.build +@@ -0,0 +1,8 @@ ++project('ethnum', 'rust', version: '1.3.2') ++ ++ethnum_lib = static_library('ethnum', ++ 'src/lib.rs', ++ override_options: ['rust_std=2021'], ++ install: true) ++ ++ethnum_dep = declare_dependency(link_with: ethnum_lib) diff --git a/subprojects/packagefiles/paste/0001.patch b/subprojects/packagefiles/paste/0001.patch new file mode 100644 index 0000000000000..e4145f5c9d306 --- /dev/null +++ b/subprojects/packagefiles/paste/0001.patch @@ -0,0 +1,13 @@ +diff --git a/meson.build b/meson.build +new file mode 100644 +index 0000000000..f6c6338b22 +--- /dev/null ++++ b/meson.build +@@ -0,0 +1,7 @@ ++project('paste', 'rust', version: '1.0.49') ++ ++paste_lib = shared_library('paste', ++ 'src/lib.rs', ++ rust_crate_type: 'proc-macro') ++ ++paste_dep = declare_dependency(link_with: paste_lib) diff --git a/subprojects/paste.wrap b/subprojects/paste.wrap new file mode 100644 index 0000000000000..48f228a52c846 --- /dev/null +++ b/subprojects/paste.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = paste-1.0.9 + +source_url = https://github.com/dtolnay/paste/archive/refs/tags/1.0.9.tar.gz +source_filename = paste-1.0.9.tar.gz +source_hash = 37e94fb1b322b938159cf84361a479484d4eed1cec5346eaa0b9d3fe4be5fb8f + +diff_files = paste/0001.patch From f7d497d5067ff5a2d4a6c2410dc9ba8b67775afb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 Jan 2026 11:38:37 +0000 Subject: [PATCH 14/18] [ot] hw: block: Add SFDP codes and fix reset properties of flashes Signed-off-by: James Wainwright --- hw/block/m25p80.c | 90 ++++++++++++++++++++++++++++++++++++------ hw/block/m25p80_sfdp.c | 72 +++++++++++++++++++++++++++++++++ hw/block/m25p80_sfdp.h | 2 + hw/block/trace-events | 2 +- 4 files changed, 153 insertions(+), 13 deletions(-) diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index a5336d92ff9fc..d5acd2319f8f8 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -224,7 +224,8 @@ static const FlashPartInfo known_devices[] = { { INFO("is25lp256", 0x9d6019, 0, 64 << 10, 512, ER_4K) }, { INFO("is25wp032", 0x9d7016, 0, 64 << 10, 64, ER_4K) }, { INFO("is25wp064", 0x9d7017, 0, 64 << 10, 128, ER_4K) }, - { INFO("is25wp128", 0x9d7018, 0, 64 << 10, 256, ER_4K) }, + { INFO("is25wp128", 0x9d7018, 0, 64 << 10, 256, ER_4K), + .sfdp_read = m25p80_sfdp_is25wp128 }, { INFO("is25wp256", 0x9d7019, 0, 64 << 10, 512, ER_4K), .sfdp_read = m25p80_sfdp_is25wp256 }, @@ -364,6 +365,8 @@ static const FlashPartInfo known_devices[] = { .sfdp_read = m25p80_sfdp_w25q512jv }, { INFO("w25q01jvq", 0xef4021, 0, 64 << 10, 2048, ER_4K), .sfdp_read = m25p80_sfdp_w25q01jvq }, + { INFO("w25q512nw", 0xef6020, 0, 64 << 10, 1024, ER_4K | ER_32K), + .sfdp_read = m25p80_sfdp_w25q512nw }, /* Microchip */ { INFO("25csm04", 0x29cc00, 0x100, 64 << 10, 8, 0) }, @@ -403,6 +406,7 @@ typedef enum { DPP = 0xa2, QPP = 0x32, QPP_4 = 0x34, + PP_4 = 0x38, RDID_90 = 0x90, RDID_AB = 0xab, AAI_WP = 0xad, @@ -456,6 +460,7 @@ typedef enum { STATE_COLLECTING_VAR_LEN_DATA, STATE_READING_DATA, STATE_READING_SFDP, + STATE_RESET, } CMDState; typedef enum { @@ -518,6 +523,7 @@ struct Flash { bool block_protect3; bool top_bottom_bit; bool status_register_write_disabled; + bool hw_reset; /* HW reset status */ uint8_t ear; int64_t dirty_page; @@ -528,6 +534,7 @@ struct Flash { struct M25P80Class { SSIPeripheralClass parent_class; + ResettablePhases parent_phases; const FlashPartInfo *pi; }; @@ -646,6 +653,7 @@ static void flash_erase(Flash *s, int offset, FlashCMD cmd) return; } memset(s->storage + offset, 0xff, len); + s->write_enable = false; flash_sync_area(s, offset, len); } @@ -759,6 +767,7 @@ static void complete_collecting_data(Flash *s) case PP: case PP4: case PP4_4: + case PP_4: s->state = STATE_PAGE_PROGRAM; break; case AAI_WP: @@ -953,8 +962,6 @@ static void reset_memory(Flash *s) default: break; } - - trace_m25p80_reset_done(s); } static uint8_t numonyx_mode(Flash *s) @@ -1001,17 +1008,13 @@ static void decode_fast_read_cmd(Flash *s) s->needed_bytes += 1; break; case MAN_WINBOND: - s->needed_bytes += 8; + s->needed_bytes += 1; break; case MAN_NUMONYX: s->needed_bytes += numonyx_extract_cfg_num_dummies(s); break; case MAN_MACRONIX: - if (extract32(s->volatile_cfg, 6, 2) == 1) { - s->needed_bytes += 6; - } else { - s->needed_bytes += 8; - } + s->needed_bytes += 1; break; case MAN_SPANSION: s->needed_bytes += extract32(s->spansion_cr2v, @@ -1173,6 +1176,7 @@ static void decode_new_cmd(Flash *s, uint32_t value) case ERASE4_SECTOR: case PP: case PP4: + case PP_4: case DIE_ERASE: case RDID_90: case RDID_AB: @@ -1546,6 +1550,10 @@ static int m25p80_cs(SSIPeripheral *ss, bool select) { Flash *s = M25P80(ss); + if (s->state == STATE_RESET) { + return 0; + } + if (select) { if (s->state == STATE_COLLECTING_VAR_LEN_DATA) { complete_collecting_data(s); @@ -1572,6 +1580,9 @@ static uint32_t m25p80_transfer8(SSIPeripheral *ss, uint32_t tx) switch (s->state) { + case STATE_RESET: + break; + case STATE_PAGE_PROGRAM: trace_m25p80_page_program(s, s->cur_addr, (uint8_t)tx); flash_write8(s, s->cur_addr, (uint8_t)tx); @@ -1662,6 +1673,32 @@ static void m25p80_write_protect_pin_irq_handler(void *opaque, int n, int level) s->wp_level = !!level; } +static void m25p80_hw_reset(void *opaque, int n, int level) +{ + Flash *s = opaque; + + g_assert(n == 0); + + bool nreset = (bool)level; /* RESET# */ + + if (nreset == !s->hw_reset) { + /* + * ignore IRQ request without change, as Resettable API tracks count of + * reset calls. HW signal is only a level. + */ + return; + } + + s->hw_reset = !nreset; + + if (s->hw_reset) { + resettable_assert_reset(OBJECT(s), RESET_TYPE_COLD); + } else { + resettable_release_reset(OBJECT(s), RESET_TYPE_COLD); + } +} + + static void m25p80_realize(SSIPeripheral *ss, Error **errp) { Flash *s = M25P80(ss); @@ -1694,13 +1731,22 @@ static void m25p80_realize(SSIPeripheral *ss, Error **errp) memset(s->storage, 0xFF, s->size); } + qdev_init_gpio_in_named(DEVICE(s), &m25p80_hw_reset, "RESET#", 1); + qdev_init_gpio_in_named(DEVICE(s), m25p80_write_protect_pin_irq_handler, "WP#", 1); } -static void m25p80_reset(DeviceState *d) +static void m25p80_reset_enter(Object *obj, ResetType type) { - Flash *s = M25P80(d); + M25P80Class *c = M25P80_GET_CLASS(obj); + Flash *s = M25P80(obj); + + trace_m25p80_reset(s, "enter"); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } s->wp_level = true; s->status_register_write_disabled = false; @@ -1711,6 +1757,22 @@ static void m25p80_reset(DeviceState *d) s->top_bottom_bit = false; reset_memory(s); + + s->state = STATE_RESET; +} + +static void m25p80_reset_exit(Object *obj, ResetType type) +{ + M25P80Class *c = M25P80_GET_CLASS(obj); + Flash *s = M25P80(obj); + + trace_m25p80_reset(s, "exit"); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + s->state = STATE_IDLE; } static int m25p80_pre_save(void *opaque) @@ -1869,10 +1931,14 @@ static void m25p80_class_init(ObjectClass *klass, const void *data) k->cs_polarity = SSI_CS_LOW; dc->vmsd = &vmstate_m25p80; device_class_set_props(dc, m25p80_properties); - device_class_set_legacy_reset(dc, m25p80_reset); set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); mc->pi = data; dc->desc = "Serial Flash"; + ResettableClass *rc = RESETTABLE_CLASS(dc); + M25P80Class *fc = M25P80_CLASS(klass); + resettable_class_set_parent_phases(rc, &m25p80_reset_enter, NULL, + &m25p80_reset_exit, + &fc->parent_phases); } static const TypeInfo m25p80_info = { diff --git a/hw/block/m25p80_sfdp.c b/hw/block/m25p80_sfdp.c index a03a291a09b5f..90e69576d42f0 100644 --- a/hw/block/m25p80_sfdp.c +++ b/hw/block/m25p80_sfdp.c @@ -440,10 +440,82 @@ static const uint8_t sfdp_w25q80bl[] = { }; define_sfdp_read(w25q80bl); +static const uint8_t sfdp_w25q512nw[] = { + 0x53, 0x46, 0x44, 0x50, 0x06, 0x01, 0x01, 0xff, + 0x00, 0x06, 0x01, 0x10, 0x80, 0x00, 0x00, 0xff, + 0x84, 0x00, 0x01, 0x02, 0xd0, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe5, 0x20, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x1f, + 0x44, 0xeb, 0x08, 0x6b, 0x08, 0x3b, 0x42, 0xbb, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0xff, 0x40, 0xeb, 0x0c, 0x20, 0x0f, 0x52, + 0x10, 0xd8, 0x00, 0x00, 0x33, 0x02, 0xa6, 0x00, + 0x81, 0xe7, 0x14, 0xd9, 0xe9, 0x63, 0x76, 0x33, + 0x7a, 0x75, 0x7a, 0x75, 0xf7, 0xbd, 0xd5, 0x5c, + 0x19, 0xf7, 0x5d, 0xff, 0xe9, 0x70, 0xf9, 0xa5, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0a, 0xf0, 0xff, 0x21, 0xff, 0xdc, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; +define_sfdp_read(w25q512nw); + /* * Integrated Silicon Solution (ISSI) */ +static const uint8_t sfdp_is25wp128[] = { + 0x53, 0x46, 0x44, 0x50, 0x06, 0x01, 0x01, 0xff, + 0x00, 0x06, 0x01, 0x10, 0x30, 0x00, 0x00, 0xff, + 0x9d, 0x05, 0x01, 0x03, 0x80, 0x00, 0x00, 0x02, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe5, 0x20, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x07, + 0x44, 0xeb, 0x08, 0x6b, 0x08, 0x3b, 0x80, 0xbb, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, + 0xff, 0xff, 0x44, 0xeb, 0x0c, 0x20, 0x0f, 0x52, + 0x10, 0xd8, 0x00, 0xff, 0x23, 0x4a, 0xc9, 0x00, + 0x82, 0xd8, 0x11, 0xc7, 0xcc, 0xcd, 0x68, 0x46, + 0x7a, 0x75, 0x7a, 0x75, 0xf7, 0xa2, 0xd5, 0x5c, + 0x4a, 0x42, 0x2c, 0xff, 0xf0, 0x30, 0xc0, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x50, 0x19, 0x50, 0x16, 0x9e, 0xf9, 0xc0, 0x64, + 0x8f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; +define_sfdp_read(is25wp128); + static const uint8_t sfdp_is25wp256[] = { 0x53, 0x46, 0x44, 0x50, 0x06, 0x01, 0x01, 0xff, 0x00, 0x06, 0x01, 0x10, 0x30, 0x00, 0x00, 0xff, diff --git a/hw/block/m25p80_sfdp.h b/hw/block/m25p80_sfdp.h index 35785686a0ec3..33f560bb9cc6e 100644 --- a/hw/block/m25p80_sfdp.h +++ b/hw/block/m25p80_sfdp.h @@ -27,7 +27,9 @@ uint8_t m25p80_sfdp_w25q256(uint32_t addr); uint8_t m25p80_sfdp_w25q512jv(uint32_t addr); uint8_t m25p80_sfdp_w25q80bl(uint32_t addr); uint8_t m25p80_sfdp_w25q01jvq(uint32_t addr); +uint8_t m25p80_sfdp_w25q512nw(uint32_t addr); +uint8_t m25p80_sfdp_is25wp128(uint32_t addr); uint8_t m25p80_sfdp_is25wp256(uint32_t addr); #endif diff --git a/hw/block/trace-events b/hw/block/trace-events index cc9a9f2460394..d1fba1d65e8a6 100644 --- a/hw/block/trace-events +++ b/hw/block/trace-events @@ -80,7 +80,7 @@ xen_block_device_destroy(unsigned int number) "%u" # m25p80.c m25p80_flash_erase(void *s, int offset, uint32_t len) "[%p] offset = 0x%"PRIx32", len = %u" m25p80_programming_zero_to_one(void *s, uint32_t addr, uint8_t prev, uint8_t data) "[%p] programming zero to one! addr=0x%"PRIx32" 0x%"PRIx8" -> 0x%"PRIx8 -m25p80_reset_done(void *s) "[%p] Reset done." +m25p80_reset(void *s, const char *msg) "[%p] %s" m25p80_command_decoded(void *s, uint32_t cmd) "[%p] new command:0x%"PRIx32 m25p80_complete_collecting(void *s, uint32_t cmd, int n, uint8_t ear, uint32_t cur_addr) "[%p] decode cmd: 0x%"PRIx32" len %d ear 0x%"PRIx8" addr 0x%"PRIx32 m25p80_populated_jedec(void *s) "[%p] populated jedec code" From 2427fdb4f33c10025420056a634795a4e60c36c7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 Jan 2026 13:22:06 +0000 Subject: [PATCH 15/18] [ot] subprojects: libtomcrypt: add cryptography library dependency Signed-off-by: James Wainwright --- .gitignore | 1 + subprojects/libtomcrypt.wrap | 8 + .../packagefiles/libtomcrypt/0001.patch | 129 ++++++++++++++++ .../packagefiles/libtomcrypt/0002.patch | 73 +++++++++ .../packagefiles/libtomcrypt/0003.patch | 141 ++++++++++++++++++ 5 files changed, 352 insertions(+) create mode 100644 subprojects/libtomcrypt.wrap create mode 100644 subprojects/packagefiles/libtomcrypt/0001.patch create mode 100644 subprojects/packagefiles/libtomcrypt/0002.patch create mode 100644 subprojects/packagefiles/libtomcrypt/0003.patch diff --git a/.gitignore b/.gitignore index 0da66420614c4..f23f42e170371 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ GTAGS *.patch *.gcov subprojects/*-*/ +subprojects/libtomcrypt/ subprojects/packagecache/ !subprojects/packagefiles/**/*.patch hw/opentitan/otbn/otbn/Cargo.lock diff --git a/subprojects/libtomcrypt.wrap b/subprojects/libtomcrypt.wrap new file mode 100644 index 0000000000000..339de7e5dc359 --- /dev/null +++ b/subprojects/libtomcrypt.wrap @@ -0,0 +1,8 @@ +[wrap-git] +url=https://github.com/libtom/libtomcrypt.git +revision=v1.18.2 +depth=1 +diff_files = libtomcrypt/0001.patch, libtomcrypt/0002.patch, libtomcrypt/0003.patch + +[provide] +dependency_names = libtomcrypt diff --git a/subprojects/packagefiles/libtomcrypt/0001.patch b/subprojects/packagefiles/libtomcrypt/0001.patch new file mode 100644 index 0000000000000..aa0e8f0bf56e5 --- /dev/null +++ b/subprojects/packagefiles/libtomcrypt/0001.patch @@ -0,0 +1,129 @@ +diff --git a/src/headers/tomcrypt_custom.h b/src/headers/tomcrypt_custom.h +index 2d5cfec..a9b3a78 100644 +--- a/src/headers/tomcrypt_custom.h ++++ b/src/headers/tomcrypt_custom.h +@@ -10,6 +10,10 @@ + #ifndef TOMCRYPT_CUSTOM_H_ + #define TOMCRYPT_CUSTOM_H_ + ++#define LTC_NO_TEST ++#define LTC_EASY ++#define LTC_NO_PROTOTYPES ++ + /* macros for various libc functions you can change for embedded targets */ + #ifndef XMALLOC + #define XMALLOC malloc +@@ -45,6 +49,12 @@ + #ifndef XSTRCMP + #define XSTRCMP strcmp + #endif ++#ifndef XSTRLEN ++#define XSTRLEN strlen ++#endif ++#ifndef XSTRNCPY ++#define XSTRNCPY strncpy ++#endif + + #ifndef XCLOCK + #define XCLOCK clock +@@ -76,63 +86,65 @@ + #ifdef LTC_EASY + #define LTC_NO_CIPHERS + #define LTC_RIJNDAEL +- #define LTC_BLOWFISH +- #define LTC_DES +- #define LTC_CAST5 ++ #undef LTC_BLOWFISH ++ #undef LTC_DES ++ #undef LTC_CAST5 + +- #define LTC_NO_MODES ++ #undef LTC_NO_MODES + #define LTC_ECB_MODE + #define LTC_CBC_MODE + #define LTC_CTR_MODE + + #define LTC_NO_HASHES +- #define LTC_SHA1 ++ #undef LTC_SHA1 + #define LTC_SHA3 + #define LTC_SHA512 + #define LTC_SHA384 + #define LTC_SHA256 +- #define LTC_SHA224 +- #define LTC_HASH_HELPERS ++ #undef LTC_SHA224 ++ #undef LTC_HASH_HELPERS + + #define LTC_NO_MACS +- #define LTC_HMAC +- #define LTC_OMAC ++ #undef LTC_HMAC ++ #undef LTC_OMAC + #define LTC_CCM_MODE ++ #define LTC_GCM_MODE + + #define LTC_NO_PRNGS +- #define LTC_SPRNG +- #define LTC_YARROW +- #define LTC_DEVRANDOM +- #define LTC_TRY_URANDOM_FIRST +- #define LTC_RNG_GET_BYTES +- #define LTC_RNG_MAKE_PRNG ++ #undef LTC_SPRNG ++ #undef LTC_YARROW ++ #undef LTC_DEVRANDOM ++ #undef LTC_TRY_URANDOM_FIRST ++ #undef LTC_RNG_GET_BYTES ++ #undef LTC_RNG_MAKE_PRNG + + #define LTC_NO_PK +- #define LTC_MRSA +- #define LTC_MECC ++ #define LTC_NO_PKCS ++ #undef LTC_MRSA ++ #undef LTC_MECC + + #define LTC_NO_MISC +- #define LTC_BASE64 ++ #undef LTC_BASE64 + #endif + + /* The minimal set of functionality to run the tests */ + #ifdef LTC_MINIMAL + #define LTC_RIJNDAEL +- #define LTC_SHA256 +- #define LTC_YARROW +- #define LTC_CTR_MODE ++ #undef LTC_SHA256 ++ #undef LTC_YARROW ++ #undef LTC_CTR_MODE + +- #define LTC_RNG_MAKE_PRNG +- #define LTC_RNG_GET_BYTES +- #define LTC_DEVRANDOM +- #define LTC_TRY_URANDOM_FIRST ++ #undef LTC_RNG_MAKE_PRNG ++ #undef LTC_RNG_GET_BYTES ++ #undef LTC_DEVRANDOM ++ #undef LTC_TRY_URANDOM_FIRST + + #undef LTC_NO_FILE + #endif + + /* Enable self-test test vector checking */ + #ifndef LTC_NO_TEST +- #define LTC_TEST ++ #undef LTC_TEST + #endif + /* Enable extended self-tests */ + /* #define LTC_TEST_EXT */ +@@ -392,7 +404,7 @@ + /* #define LTC_MKAT */ + + /* Digital Signature Algorithm */ +-#define LTC_MDSA ++#undef LTC_MDSA + + /* ECC */ + #define LTC_MECC diff --git a/subprojects/packagefiles/libtomcrypt/0002.patch b/subprojects/packagefiles/libtomcrypt/0002.patch new file mode 100644 index 0000000000000..9bc60ea0798b4 --- /dev/null +++ b/subprojects/packagefiles/libtomcrypt/0002.patch @@ -0,0 +1,73 @@ +diff --git a/meson.build b/meson.build +new file mode 100644 +index 0000000..cdef610 +--- /dev/null ++++ b/meson.build +@@ -0,0 +1,67 @@ ++project('libtomcrypt', 'c') ++ ++libtomcrypt = static_library('tomcrypt', ++ files('src/ciphers/aes/aes.c', ++ 'src/ciphers/aes/aes_tab.c', ++ 'src/encauth/ccm/ccm_add_aad.c', ++ 'src/encauth/ccm/ccm_add_nonce.c', ++ 'src/encauth/ccm/ccm_done.c', ++ 'src/encauth/ccm/ccm_init.c', ++ 'src/encauth/ccm/ccm_memory.c', ++ 'src/encauth/ccm/ccm_process.c', ++ 'src/encauth/ccm/ccm_reset.c', ++ 'src/encauth/ccm/ccm_test.c', ++ 'src/encauth/gcm/gcm_add_aad.c', ++ 'src/encauth/gcm/gcm_add_iv.c', ++ 'src/encauth/gcm/gcm_done.c', ++ 'src/encauth/gcm/gcm_gf_mult.c', ++ 'src/encauth/gcm/gcm_init.c', ++ 'src/encauth/gcm/gcm_memory.c', ++ 'src/encauth/gcm/gcm_mult_h.c', ++ 'src/encauth/gcm/gcm_process.c', ++ 'src/encauth/gcm/gcm_reset.c', ++ 'src/encauth/gcm/gcm_test.c', ++ 'src/hashes/sha2/sha256.c', ++ 'src/hashes/sha2/sha384.c', ++ 'src/hashes/sha2/sha512.c', ++ 'src/hashes/sha3.c', ++ 'src/hashes/sha3_test.c', ++ 'src/misc/crypt/crypt_argchk.c', ++ 'src/misc/crypt/crypt_cipher_descriptor.c', ++ 'src/misc/crypt/crypt_cipher_is_valid.c', ++ 'src/misc/crypt/crypt_register_cipher.c', ++ 'src/misc/mem_neq.c', ++ 'src/misc/zeromem.c', ++ 'src/modes/cbc/cbc_decrypt.c', ++ 'src/modes/cbc/cbc_done.c', ++ 'src/modes/cbc/cbc_encrypt.c', ++ 'src/modes/cbc/cbc_getiv.c', ++ 'src/modes/cbc/cbc_setiv.c', ++ 'src/modes/cbc/cbc_start.c', ++ 'src/modes/cfb/cfb_decrypt.c', ++ 'src/modes/cfb/cfb_done.c', ++ 'src/modes/cfb/cfb_encrypt.c', ++ 'src/modes/cfb/cfb_getiv.c', ++ 'src/modes/cfb/cfb_setiv.c', ++ 'src/modes/cfb/cfb_start.c', ++ 'src/modes/ctr/ctr_decrypt.c', ++ 'src/modes/ctr/ctr_done.c', ++ 'src/modes/ctr/ctr_encrypt.c', ++ 'src/modes/ctr/ctr_getiv.c', ++ 'src/modes/ctr/ctr_setiv.c', ++ 'src/modes/ctr/ctr_start.c', ++ 'src/modes/ctr/ctr_test.c', ++ 'src/modes/ecb/ecb_decrypt.c', ++ 'src/modes/ecb/ecb_done.c', ++ 'src/modes/ecb/ecb_encrypt.c', ++ 'src/modes/ecb/ecb_start.c', ++ 'src/modes/ofb/ofb_decrypt.c', ++ 'src/modes/ofb/ofb_done.c', ++ 'src/modes/ofb/ofb_encrypt.c', ++ 'src/modes/ofb/ofb_getiv.c', ++ 'src/modes/ofb/ofb_setiv.c', ++ 'src/modes/ofb/ofb_start.c'), ++ include_directories: ['src/headers']) ++ ++libtomcrypt_dep = declare_dependency(link_with: libtomcrypt, ++ include_directories: include_directories('src/headers')) diff --git a/subprojects/packagefiles/libtomcrypt/0003.patch b/subprojects/packagefiles/libtomcrypt/0003.patch new file mode 100644 index 0000000000000..a39fc8efc8527 --- /dev/null +++ b/subprojects/packagefiles/libtomcrypt/0003.patch @@ -0,0 +1,141 @@ +diff --git a/src/hashes/sha3.c b/src/hashes/sha3.c +index c6faa0b..d596bd0 100644 +--- a/src/hashes/sha3.c ++++ b/src/hashes/sha3.c +@@ -174,6 +174,102 @@ int sha3_shake_init(hash_state *md, int num) + if (num != 128 && num != 256) return CRYPT_INVALID_ARG; + XMEMSET(&md->sha3, 0, sizeof(md->sha3)); + md->sha3.capacity_words = (unsigned short)(2 * num / (8 * sizeof(ulong64))); ++ md->sha3.suffix = 0x1F; ++ return CRYPT_OK; ++} ++ ++static unsigned long cshake_left_encode(unsigned long num, unsigned char *buf) ++{ ++ unsigned int n, i; ++ size_t v; ++ ++ for (v = num, n = 0; v && (n < sizeof(unsigned long)); n++, v >>= 8) { ++ /* empty */ ++ } ++ if (n == 0) ++ n = 1; ++ ++ for (i = 1; i <= n; i++) ++ { ++ buf[i] = (unsigned char)(num >> (8 * (n - i))); ++ } ++ buf[0] = (unsigned char)n; ++ ++ return n + 1; ++} ++ ++static unsigned long cshake_left_encode_and_process_str(hash_state *md, ++ const unsigned char *data, unsigned long datalen) ++{ ++ unsigned char buf[sizeof(unsigned long) + 1]; ++ unsigned long len = 0; ++ ++ len = cshake_left_encode(datalen * 8, buf); ++ sha3_process(md, buf, len); ++ if (datalen) ++ sha3_process(md, data, datalen); ++ len += datalen; ++ ++ return len; ++} ++ ++int sha3_cshake_init(hash_state *md, int num, ++ const unsigned char *func, unsigned long funclen, ++ const unsigned char *custom, unsigned long customlen) ++{ ++ unsigned char buf[168]; ++ unsigned long len, total = 0; ++ unsigned long rate; ++ LTC_ARGCHK(md != NULL); ++ if (num != 128 && num != 256) return CRYPT_INVALID_ARG; ++ rate = (num == 128) ? 168 : 136; ++ XMEMSET(&md->sha3, 0, sizeof(md->sha3)); ++ md->sha3.capacity_words = (unsigned short)(2 * num / (8 * sizeof(ulong64))); ++ ++ if (funclen == 0 && customlen == 0) { ++ /* no function, no customization => regular SHAKE */ ++ md->sha3.suffix = 0x1F; ++ } else { ++ md->sha3.suffix = 0x04; ++ ++ /* start bytepad */ ++ len = cshake_left_encode(rate, buf); ++ sha3_process(md, buf, len); ++ total += len; ++ ++ /* process function name */ ++ total += cshake_left_encode_and_process_str(md, func, funclen); ++ ++ /* process customization string */ ++ total += cshake_left_encode_and_process_str(md, custom, customlen); ++ ++ /* finish bytepad */ ++ XMEMSET(buf, 0, sizeof(buf)); ++ sha3_process(md, buf, rate - (total % rate)); ++ } ++ return CRYPT_OK; ++} ++ ++int sha3_process_kmac_key(hash_state * md, const unsigned char *key, unsigned long keylen) ++{ ++ unsigned char buf[168]; ++ unsigned long len, total = 0; ++ unsigned long rate; ++ ++ rate = (md->sha3.capacity_words == 4) ? 168 : 136; ++ ++ /* start bytepad */ ++ len = cshake_left_encode(rate, buf); ++ sha3_process(md, buf, len); ++ total += len; ++ ++ /* process key */ ++ total += cshake_left_encode_and_process_str(md, key, keylen); ++ ++ /* finish bytepad */ ++ XMEMSET(buf, 0, sizeof(buf)); ++ sha3_process(md, buf, rate - (total % rate)); ++ + return CRYPT_OK; + } + +@@ -261,7 +357,7 @@ int sha3_shake_done(hash_state *md, unsigned char *out, unsigned long outlen) + + if (!md->sha3.xof_flag) { + /* shake_xof operation must be done only once */ +- md->sha3.s[md->sha3.word_index] ^= (md->sha3.saved ^ (CONST64(0x1F) << (md->sha3.byte_index * 8))); ++ md->sha3.s[md->sha3.word_index] ^= (md->sha3.saved ^ ((ulong64)md->sha3.suffix << (md->sha3.byte_index * 8))); + md->sha3.s[SHA3_KECCAK_SPONGE_WORDS - md->sha3.capacity_words - 1] ^= CONST64(0x8000000000000000); + keccakf(md->sha3.s); + /* store sha3.s[] as little-endian bytes into sha3.sb */ +diff --git a/src/headers/tomcrypt_hash.h b/src/headers/tomcrypt_hash.h +index ef494f7..b0565d8 100644 +--- a/src/headers/tomcrypt_hash.h ++++ b/src/headers/tomcrypt_hash.h +@@ -17,6 +17,7 @@ struct sha3_state { + unsigned short word_index; /* 0..24--the next word to integrate input (starts from 0) */ + unsigned short capacity_words; /* the double size of the hash output in words (e.g. 16 for Keccak 512) */ + unsigned short xof_flag; ++ unsigned char suffix; /* suffix for SHAKE/cSHAKE */ + }; + #endif + +@@ -285,6 +286,12 @@ int sha3_shake_init(hash_state *md, int num); + int sha3_shake_done(hash_state *md, unsigned char *out, unsigned long outlen); + int sha3_shake_test(void); + int sha3_shake_memory(int num, const unsigned char *in, unsigned long inlen, unsigned char *out, unsigned long *outlen); ++int sha3_cshake_init(hash_state *md, int num, ++ const unsigned char *func, unsigned long funclen, ++ const unsigned char *custom, unsigned long customlen); ++#define sha3_cshake_process(a,b,c) sha3_process(a,b,c) ++#define sha3_cshake_done(a,b,c) sha3_shake_done(a,b,c) ++int sha3_process_kmac_key(hash_state * md, const unsigned char *key, unsigned long keylen); + #endif + + #ifdef LTC_SHA512 From 50f6381b77a2277a74942b687eedc0ec707af3ac Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 6 Jan 2026 16:35:49 +0000 Subject: [PATCH 16/18] [ot] hw: opentitan: add `ot_earlgrey` and `ot_darjeeling` machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These machines come with many new blocks, scripts, and documentation. Co-authored-by: Loïc Lefort Signed-off-by: James Wainwright --- configs/devices/riscv32-softmmu/default.mak | 2 + docs/config/opentitan/darjeeling.cfg | 69 + docs/config/opentitan/earlgrey.cfg | 71 + docs/opentitan/cfggen.md | 96 + docs/opentitan/checkregs.md | 72 + docs/opentitan/debug.md | 51 + docs/opentitan/devproxy.md | 1181 +++++++ docs/opentitan/dtm.md | 170 + docs/opentitan/flashgen.md | 108 + docs/opentitan/gdbreplay.md | 87 + docs/opentitan/gpiodev.md | 78 + docs/opentitan/i2c_host_proxy.md | 52 + docs/opentitan/ibex_hart.md | 30 + docs/opentitan/ibexdemo.md | 22 + docs/opentitan/index.md | 41 + docs/opentitan/jtag-dm.md | 157 + docs/opentitan/jtagmbx.md | 244 ++ docs/opentitan/keymgr-dpe.md | 244 ++ docs/opentitan/lc_ctrl_dmi.md | 70 + docs/opentitan/ot_aes.md | 7 + docs/opentitan/ot_darjeeling.md | 147 + docs/opentitan/ot_earlgrey.md | 141 + docs/opentitan/ot_eg_pad_ring.md | 33 + docs/opentitan/ot_flash.md | 20 + docs/opentitan/ot_gpio.md | 136 + docs/opentitan/ot_i2c.md | 8 + docs/opentitan/ot_ibex_wrapper.md | 39 + docs/opentitan/ot_keymgr.md | 14 + docs/opentitan/ot_machine.md | 24 + docs/opentitan/ot_otbn.md | 9 + docs/opentitan/ot_otp.md | 10 + docs/opentitan/ot_rom_ctrl.md | 101 + docs/opentitan/ot_rstmgr.md | 9 + docs/opentitan/ot_soc_dbg_ctrl.md | 11 + docs/opentitan/ot_spi_device.md | 131 + docs/opentitan/ot_spi_host.md | 44 + docs/opentitan/ot_uart.md | 39 + docs/opentitan/ot_usbdev.md | 321 ++ docs/opentitan/otcfg.md | 109 + docs/opentitan/otpdm.md | 139 + docs/opentitan/otptool.md | 359 ++ docs/opentitan/pymod.md | 29 + docs/opentitan/pyot.md | 593 ++++ docs/opentitan/regressions.md | 33 + docs/opentitan/romtool.md | 83 + docs/opentitan/spidevflash.md | 77 + docs/opentitan/tools.md | 82 + docs/opentitan/uartmux.md | 65 + docs/opentitan/verilate.md | 162 + hw/Kconfig | 3 + hw/core/loader.c | 5 + hw/meson.build | 1 + hw/opentitan/Kconfig | 228 ++ hw/opentitan/meson.build | 66 + hw/opentitan/ot_address_space.c | 77 + hw/opentitan/ot_aes.c | 1521 ++++++++ hw/opentitan/ot_alert.c | 1240 +++++++ hw/opentitan/ot_aon_timer.c | 675 ++++ hw/opentitan/ot_ast_dj.c | 621 ++++ hw/opentitan/ot_ast_eg.c | 617 ++++ hw/opentitan/ot_clkmgr.c | 1447 ++++++++ hw/opentitan/ot_clock_ctrl.c | 42 + hw/opentitan/ot_common.c | 433 +++ hw/opentitan/ot_csrng.c | 2100 +++++++++++ hw/opentitan/ot_dev_proxy.c | 2080 +++++++++++ hw/opentitan/ot_dm_tl.c | 246 ++ hw/opentitan/ot_dma.c | 1510 ++++++++ hw/opentitan/ot_edn.c | 1598 +++++++++ hw/opentitan/ot_eg_pad_ring.c | 205 ++ hw/opentitan/ot_entropy_src.c | 1982 +++++++++++ hw/opentitan/ot_flash.c | 3438 +++++++++++++++++++ hw/opentitan/ot_gpio_dj.c | 954 +++++ hw/opentitan/ot_gpio_eg.c | 837 +++++ hw/opentitan/ot_hmac.c | 1362 ++++++++ hw/opentitan/ot_i2c.c | 1438 ++++++++ hw/opentitan/ot_i2c_host_proxy.c | 373 ++ hw/opentitan/ot_ibex_wrapper.c | 1568 +++++++++ hw/opentitan/ot_key_sink.c | 42 + hw/opentitan/ot_keymgr.c | 2558 ++++++++++++++ hw/opentitan/ot_keymgr_dpe.c | 2214 ++++++++++++ hw/opentitan/ot_kmac.c | 1991 +++++++++++ hw/opentitan/ot_lc_ctrl.c | 2436 +++++++++++++ hw/opentitan/ot_lc_ctrl_matrix.c | 220 ++ hw/opentitan/ot_mbx.c | 851 +++++ hw/opentitan/ot_noise_src.c | 42 + hw/opentitan/ot_otbn.c | 905 +++++ hw/opentitan/ot_otp_be_if.c | 42 + hw/opentitan/ot_otp_dj.c | 1342 ++++++++ hw/opentitan/ot_otp_dj_parts.c | 378 ++ hw/opentitan/ot_otp_eg.c | 1227 +++++++ hw/opentitan/ot_otp_eg_parts.c | 213 ++ hw/opentitan/ot_otp_engine.c | 2986 ++++++++++++++++ hw/opentitan/ot_otp_if.c | 42 + hw/opentitan/ot_otp_impl_if.c | 43 + hw/opentitan/ot_otp_ot_be.c | 300 ++ hw/opentitan/ot_pinmux_dj.c | 577 ++++ hw/opentitan/ot_pinmux_eg.c | 580 ++++ hw/opentitan/ot_plic_ext.c | 302 ++ hw/opentitan/ot_present.c | 226 ++ hw/opentitan/ot_prince.c | 181 + hw/opentitan/ot_prng.c | 81 + hw/opentitan/ot_pwrmgr.c | 1159 +++++++ hw/opentitan/ot_rom_ctrl.c | 1321 +++++++ hw/opentitan/ot_rom_ctrl_img.c | 235 ++ hw/opentitan/ot_rstmgr.c | 713 ++++ hw/opentitan/ot_sensor_eg.c | 454 +++ hw/opentitan/ot_soc_dbg_ctrl.c | 1009 ++++++ hw/opentitan/ot_soc_proxy.c | 313 ++ hw/opentitan/ot_spi_device.c | 3016 ++++++++++++++++ hw/opentitan/ot_spi_host.c | 1591 +++++++++ hw/opentitan/ot_sram_ctrl.c | 914 +++++ hw/opentitan/ot_timer.c | 481 +++ hw/opentitan/ot_uart.c | 781 +++++ hw/opentitan/ot_unimp.c | 319 ++ hw/opentitan/ot_usbdev.c | 2923 ++++++++++++++++ hw/opentitan/ot_vmapper.c | 1194 +++++++ hw/opentitan/trace-events | 693 ++++ hw/opentitan/trace.h | 1 + hw/riscv/Kconfig | 100 + hw/riscv/meson.build | 2 + hw/riscv/ot_darjeeling.c | 2170 ++++++++++++ hw/riscv/ot_earlgrey.c | 2199 ++++++++++++ include/hw/opentitan/ot_address_space.h | 44 + include/hw/opentitan/ot_aes.h | 38 + include/hw/opentitan/ot_alert.h | 41 + include/hw/opentitan/ot_aon_timer.h | 40 + include/hw/opentitan/ot_ast_dj.h | 36 + include/hw/opentitan/ot_ast_eg.h | 36 + include/hw/opentitan/ot_clkmgr.h | 59 + include/hw/opentitan/ot_clock_ctrl.h | 71 + include/hw/opentitan/ot_common.h | 406 +++ include/hw/opentitan/ot_csrng.h | 132 + include/hw/opentitan/ot_dev_proxy.h | 39 + include/hw/opentitan/ot_dm_tl.h | 36 + include/hw/opentitan/ot_dma.h | 36 + include/hw/opentitan/ot_edn.h | 72 + include/hw/opentitan/ot_eg_pad_ring.h | 74 + include/hw/opentitan/ot_entropy_src.h | 65 + include/hw/opentitan/ot_fifo32.h | 125 + include/hw/opentitan/ot_flash.h | 84 + include/hw/opentitan/ot_gpio.h | 37 + include/hw/opentitan/ot_gpio_dj.h | 38 + include/hw/opentitan/ot_gpio_eg.h | 36 + include/hw/opentitan/ot_hmac.h | 36 + include/hw/opentitan/ot_i2c.h | 42 + include/hw/opentitan/ot_i2c_host_proxy.h | 36 + include/hw/opentitan/ot_ibex_wrapper.h | 48 + include/hw/opentitan/ot_key_sink.h | 58 + include/hw/opentitan/ot_keymgr.h | 40 + include/hw/opentitan/ot_keymgr_dpe.h | 40 + include/hw/opentitan/ot_kmac.h | 130 + include/hw/opentitan/ot_lc_ctrl.h | 96 + include/hw/opentitan/ot_mbx.h | 44 + include/hw/opentitan/ot_noise_src.h | 61 + include/hw/opentitan/ot_otbn.h | 38 + include/hw/opentitan/ot_otp_be_if.h | 68 + include/hw/opentitan/ot_otp_dj.h | 35 + include/hw/opentitan/ot_otp_eg.h | 35 + include/hw/opentitan/ot_otp_engine.h | 416 +++ include/hw/opentitan/ot_otp_if.h | 198 ++ include/hw/opentitan/ot_otp_impl_if.h | 119 + include/hw/opentitan/ot_otp_ot_be.h | 34 + include/hw/opentitan/ot_pinmux.h | 65 + include/hw/opentitan/ot_pinmux_dj.h | 36 + include/hw/opentitan/ot_pinmux_eg.h | 36 + include/hw/opentitan/ot_plic_ext.h | 36 + include/hw/opentitan/ot_present.h | 21 + include/hw/opentitan/ot_prince.h | 16 + include/hw/opentitan/ot_prng.h | 43 + include/hw/opentitan/ot_pwrmgr.h | 99 + include/hw/opentitan/ot_rom_ctrl.h | 54 + include/hw/opentitan/ot_rom_ctrl_img.h | 53 + include/hw/opentitan/ot_rstmgr.h | 69 + include/hw/opentitan/ot_sensor_eg.h | 36 + include/hw/opentitan/ot_soc_dbg_ctrl.h | 74 + include/hw/opentitan/ot_soc_proxy.h | 38 + include/hw/opentitan/ot_spi_device.h | 42 + include/hw/opentitan/ot_spi_host.h | 64 + include/hw/opentitan/ot_sram_ctrl.h | 39 + include/hw/opentitan/ot_timer.h | 36 + include/hw/opentitan/ot_uart.h | 36 + include/hw/opentitan/ot_unimp.h | 36 + include/hw/opentitan/ot_usbdev.h | 37 + include/hw/opentitan/ot_vmapper.h | 71 + include/hw/opentitan/otbn/otbnproxy.h | 92 + include/hw/riscv/ot_darjeeling.h | 37 + include/hw/riscv/ot_earlgrey.h | 39 + meson.build | 3 +- qapi/qom.json | 15 + system/vl.c | 10 + target/riscv/cpu.c | 11 + 191 files changed, 78216 insertions(+), 1 deletion(-) create mode 100644 docs/config/opentitan/darjeeling.cfg create mode 100644 docs/config/opentitan/earlgrey.cfg create mode 100644 docs/opentitan/cfggen.md create mode 100644 docs/opentitan/checkregs.md create mode 100644 docs/opentitan/debug.md create mode 100644 docs/opentitan/devproxy.md create mode 100644 docs/opentitan/dtm.md create mode 100644 docs/opentitan/flashgen.md create mode 100644 docs/opentitan/gdbreplay.md create mode 100644 docs/opentitan/gpiodev.md create mode 100644 docs/opentitan/i2c_host_proxy.md create mode 100644 docs/opentitan/ibex_hart.md create mode 100644 docs/opentitan/ibexdemo.md create mode 100644 docs/opentitan/index.md create mode 100644 docs/opentitan/jtag-dm.md create mode 100644 docs/opentitan/jtagmbx.md create mode 100644 docs/opentitan/keymgr-dpe.md create mode 100644 docs/opentitan/lc_ctrl_dmi.md create mode 100644 docs/opentitan/ot_aes.md create mode 100644 docs/opentitan/ot_darjeeling.md create mode 100644 docs/opentitan/ot_earlgrey.md create mode 100644 docs/opentitan/ot_eg_pad_ring.md create mode 100644 docs/opentitan/ot_flash.md create mode 100644 docs/opentitan/ot_gpio.md create mode 100644 docs/opentitan/ot_i2c.md create mode 100644 docs/opentitan/ot_ibex_wrapper.md create mode 100644 docs/opentitan/ot_keymgr.md create mode 100644 docs/opentitan/ot_machine.md create mode 100644 docs/opentitan/ot_otbn.md create mode 100644 docs/opentitan/ot_otp.md create mode 100644 docs/opentitan/ot_rom_ctrl.md create mode 100644 docs/opentitan/ot_rstmgr.md create mode 100644 docs/opentitan/ot_soc_dbg_ctrl.md create mode 100644 docs/opentitan/ot_spi_device.md create mode 100644 docs/opentitan/ot_spi_host.md create mode 100644 docs/opentitan/ot_uart.md create mode 100644 docs/opentitan/ot_usbdev.md create mode 100644 docs/opentitan/otcfg.md create mode 100644 docs/opentitan/otpdm.md create mode 100644 docs/opentitan/otptool.md create mode 100644 docs/opentitan/pymod.md create mode 100644 docs/opentitan/pyot.md create mode 100644 docs/opentitan/regressions.md create mode 100644 docs/opentitan/romtool.md create mode 100644 docs/opentitan/spidevflash.md create mode 100644 docs/opentitan/tools.md create mode 100644 docs/opentitan/uartmux.md create mode 100644 docs/opentitan/verilate.md create mode 100644 hw/opentitan/Kconfig create mode 100644 hw/opentitan/ot_address_space.c create mode 100644 hw/opentitan/ot_aes.c create mode 100644 hw/opentitan/ot_alert.c create mode 100644 hw/opentitan/ot_aon_timer.c create mode 100644 hw/opentitan/ot_ast_dj.c create mode 100644 hw/opentitan/ot_ast_eg.c create mode 100644 hw/opentitan/ot_clkmgr.c create mode 100644 hw/opentitan/ot_clock_ctrl.c create mode 100644 hw/opentitan/ot_common.c create mode 100644 hw/opentitan/ot_csrng.c create mode 100644 hw/opentitan/ot_dev_proxy.c create mode 100644 hw/opentitan/ot_dm_tl.c create mode 100644 hw/opentitan/ot_dma.c create mode 100644 hw/opentitan/ot_edn.c create mode 100644 hw/opentitan/ot_eg_pad_ring.c create mode 100644 hw/opentitan/ot_entropy_src.c create mode 100644 hw/opentitan/ot_flash.c create mode 100644 hw/opentitan/ot_gpio_dj.c create mode 100644 hw/opentitan/ot_gpio_eg.c create mode 100644 hw/opentitan/ot_hmac.c create mode 100644 hw/opentitan/ot_i2c.c create mode 100644 hw/opentitan/ot_i2c_host_proxy.c create mode 100644 hw/opentitan/ot_ibex_wrapper.c create mode 100644 hw/opentitan/ot_key_sink.c create mode 100644 hw/opentitan/ot_keymgr.c create mode 100644 hw/opentitan/ot_keymgr_dpe.c create mode 100644 hw/opentitan/ot_kmac.c create mode 100644 hw/opentitan/ot_lc_ctrl.c create mode 100644 hw/opentitan/ot_lc_ctrl_matrix.c create mode 100644 hw/opentitan/ot_mbx.c create mode 100644 hw/opentitan/ot_noise_src.c create mode 100644 hw/opentitan/ot_otbn.c create mode 100644 hw/opentitan/ot_otp_be_if.c create mode 100644 hw/opentitan/ot_otp_dj.c create mode 100644 hw/opentitan/ot_otp_dj_parts.c create mode 100644 hw/opentitan/ot_otp_eg.c create mode 100644 hw/opentitan/ot_otp_eg_parts.c create mode 100644 hw/opentitan/ot_otp_engine.c create mode 100644 hw/opentitan/ot_otp_if.c create mode 100644 hw/opentitan/ot_otp_impl_if.c create mode 100644 hw/opentitan/ot_otp_ot_be.c create mode 100644 hw/opentitan/ot_pinmux_dj.c create mode 100644 hw/opentitan/ot_pinmux_eg.c create mode 100644 hw/opentitan/ot_plic_ext.c create mode 100644 hw/opentitan/ot_present.c create mode 100644 hw/opentitan/ot_prince.c create mode 100644 hw/opentitan/ot_prng.c create mode 100644 hw/opentitan/ot_pwrmgr.c create mode 100644 hw/opentitan/ot_rom_ctrl.c create mode 100644 hw/opentitan/ot_rom_ctrl_img.c create mode 100644 hw/opentitan/ot_rstmgr.c create mode 100644 hw/opentitan/ot_sensor_eg.c create mode 100644 hw/opentitan/ot_soc_dbg_ctrl.c create mode 100644 hw/opentitan/ot_soc_proxy.c create mode 100644 hw/opentitan/ot_spi_device.c create mode 100644 hw/opentitan/ot_spi_host.c create mode 100644 hw/opentitan/ot_sram_ctrl.c create mode 100644 hw/opentitan/ot_timer.c create mode 100644 hw/opentitan/ot_uart.c create mode 100644 hw/opentitan/ot_unimp.c create mode 100644 hw/opentitan/ot_usbdev.c create mode 100644 hw/opentitan/ot_vmapper.c create mode 100644 hw/opentitan/trace-events create mode 100644 hw/opentitan/trace.h create mode 100644 hw/riscv/ot_darjeeling.c create mode 100644 hw/riscv/ot_earlgrey.c create mode 100644 include/hw/opentitan/ot_address_space.h create mode 100644 include/hw/opentitan/ot_aes.h create mode 100644 include/hw/opentitan/ot_alert.h create mode 100644 include/hw/opentitan/ot_aon_timer.h create mode 100644 include/hw/opentitan/ot_ast_dj.h create mode 100644 include/hw/opentitan/ot_ast_eg.h create mode 100644 include/hw/opentitan/ot_clkmgr.h create mode 100644 include/hw/opentitan/ot_clock_ctrl.h create mode 100644 include/hw/opentitan/ot_common.h create mode 100644 include/hw/opentitan/ot_csrng.h create mode 100644 include/hw/opentitan/ot_dev_proxy.h create mode 100644 include/hw/opentitan/ot_dm_tl.h create mode 100644 include/hw/opentitan/ot_dma.h create mode 100644 include/hw/opentitan/ot_edn.h create mode 100644 include/hw/opentitan/ot_eg_pad_ring.h create mode 100644 include/hw/opentitan/ot_entropy_src.h create mode 100644 include/hw/opentitan/ot_fifo32.h create mode 100644 include/hw/opentitan/ot_flash.h create mode 100644 include/hw/opentitan/ot_gpio.h create mode 100644 include/hw/opentitan/ot_gpio_dj.h create mode 100644 include/hw/opentitan/ot_gpio_eg.h create mode 100644 include/hw/opentitan/ot_hmac.h create mode 100644 include/hw/opentitan/ot_i2c.h create mode 100644 include/hw/opentitan/ot_i2c_host_proxy.h create mode 100644 include/hw/opentitan/ot_ibex_wrapper.h create mode 100644 include/hw/opentitan/ot_key_sink.h create mode 100644 include/hw/opentitan/ot_keymgr.h create mode 100644 include/hw/opentitan/ot_keymgr_dpe.h create mode 100644 include/hw/opentitan/ot_kmac.h create mode 100644 include/hw/opentitan/ot_lc_ctrl.h create mode 100644 include/hw/opentitan/ot_mbx.h create mode 100644 include/hw/opentitan/ot_noise_src.h create mode 100644 include/hw/opentitan/ot_otbn.h create mode 100644 include/hw/opentitan/ot_otp_be_if.h create mode 100644 include/hw/opentitan/ot_otp_dj.h create mode 100644 include/hw/opentitan/ot_otp_eg.h create mode 100644 include/hw/opentitan/ot_otp_engine.h create mode 100644 include/hw/opentitan/ot_otp_if.h create mode 100644 include/hw/opentitan/ot_otp_impl_if.h create mode 100644 include/hw/opentitan/ot_otp_ot_be.h create mode 100644 include/hw/opentitan/ot_pinmux.h create mode 100644 include/hw/opentitan/ot_pinmux_dj.h create mode 100644 include/hw/opentitan/ot_pinmux_eg.h create mode 100644 include/hw/opentitan/ot_plic_ext.h create mode 100644 include/hw/opentitan/ot_present.h create mode 100644 include/hw/opentitan/ot_prince.h create mode 100644 include/hw/opentitan/ot_prng.h create mode 100644 include/hw/opentitan/ot_pwrmgr.h create mode 100644 include/hw/opentitan/ot_rom_ctrl.h create mode 100644 include/hw/opentitan/ot_rom_ctrl_img.h create mode 100644 include/hw/opentitan/ot_rstmgr.h create mode 100644 include/hw/opentitan/ot_sensor_eg.h create mode 100644 include/hw/opentitan/ot_soc_dbg_ctrl.h create mode 100644 include/hw/opentitan/ot_soc_proxy.h create mode 100644 include/hw/opentitan/ot_spi_device.h create mode 100644 include/hw/opentitan/ot_spi_host.h create mode 100644 include/hw/opentitan/ot_sram_ctrl.h create mode 100644 include/hw/opentitan/ot_timer.h create mode 100644 include/hw/opentitan/ot_uart.h create mode 100644 include/hw/opentitan/ot_unimp.h create mode 100644 include/hw/opentitan/ot_usbdev.h create mode 100644 include/hw/opentitan/ot_vmapper.h create mode 100644 include/hw/opentitan/otbn/otbnproxy.h create mode 100644 include/hw/riscv/ot_darjeeling.h create mode 100644 include/hw/riscv/ot_earlgrey.h diff --git a/configs/devices/riscv32-softmmu/default.mak b/configs/devices/riscv32-softmmu/default.mak index 0b73cf220025f..8122e3247503f 100644 --- a/configs/devices/riscv32-softmmu/default.mak +++ b/configs/devices/riscv32-softmmu/default.mak @@ -10,4 +10,6 @@ # CONFIG_SIFIVE_U=n # CONFIG_RISCV_VIRT=n # CONFIG_OPENTITAN=n +# CONFIG_OT_DARJEELING=n +# CONFIG_OT_EARLGREY=n # CONFIG_IBEXDEMO=n diff --git a/docs/config/opentitan/darjeeling.cfg b/docs/config/opentitan/darjeeling.cfg new file mode 100644 index 0000000000000..e7b9802d8b9e3 --- /dev/null +++ b/docs/config/opentitan/darjeeling.cfg @@ -0,0 +1,69 @@ +# Generated from OpenTitan commit: c5507b4cdc + +[ot_device "ot-rom_ctrl.rom0"] + key = "30ae84156d37cc68063276f9e85faee1" + nonce = "0a553e45bab60fd5" + +[ot_device "ot-rom_ctrl.rom1"] + key = "9649127ec56c7b0e9645bf231b5b37b8" + nonce = "29ae80f5b440dc77" + +[ot_device "ot-otp-dj"] + digest_const = "09c87e42745452d8a010a9f0ef3221d5" + digest_iv = "a0806e02a1fba55b" + inv_default_part_15 = "58b183f3d37975b4a9524de21084a9d64bba835c10b5e29043022273f7afbf68e78e601c1704c34a6dfd043e96e1ef76d15c0798ef406091d605165216fd3f856fb88c3b4fd535bd" + inv_default_part_16 = "00000000696900001f2a6bd606d55f72" + inv_default_part_17 = "1fb5110b0618183cbf722f142ef9facfb5742826d2ce8d8bb874ddd1dbca5322ddf650f1a00008ee0000000000000000" + inv_default_part_18 = "2552a6aa7830346413591b15ed2553188688686a7d26f94a0000000000000000" + inv_default_part_19 = "eff32b59c0a86294d9767fdcb47456994c8fd64171edb20835aef32cf20b0c620e9af6c53593daec8e3caa2e495a897602e2904aa8a7090712cf9a372be9c2b9c1fbff3b68368c5afedcdee44f5d8d84276d9c42b4b5c6539c73a2705a4682baa1885457797963c3980bff063fc8bc645a7f3e2373a78aa20000000000000000" + inv_default_part_20 = "9c47222c695c123916e90f1bdde834c31d3eef689e998822eddeb20732f666faba37deb973d827e00000000000000000" + inv_default_part_21 = "6149b9ff4f5979607aead63a44f896431df745a52c5af5fdf86d2ce9fa1041c43f145a8bf5be7640d7aaf2481067180fd08f694a5f790581d728bd369d03f8087a60a7f8ed956442c9cff0f99e594c7307f376e7b2b2ff8c" + scrmbl_key = "d6780626a12b5904a9b37439f8177d19d04d3fad040ed02909a9ea4b5429b9e6" + secret0_scramble_key = "688a9a20b68e0d35660e593f560f6866" + secret1_scramble_key = "a1ad90a50942397740ab78c5737a2379" + secret2_scramble_key = "c2ede5b25ec5514b680ccaab361c3f85" + secret3_scramble_key = "8e1c5cccc121a2c95d21294d190ab75c" + sram_const = "4dcba329ff2f7d4b8a3acdb325087fab" + sram_iv = "9bd623e40aec9b61" + +[ot_device "ot-lc_ctrl"] + dev = "d2fd2987e3062f6119c7560acbbe9f6f" + invalid = "0d20b07721f7ff507ad2b9fb9cc7ff06" + lc_state_first = "eebd62140286d28089f20c8f78baa23d9210b65583a165a35342d646189f81d751c346984482681b" + lc_state_last = "eebde6d52eb6dbd5b9f79dcf7ebfbe3fb672bff5dfe577afd36efe6f99ffe1ff73e3779ec4fa7a9b" + lc_trscnt_first = "ff595454ab481e0fd02144da44cc929a4149279a202522dcf2352590ec602b24d82b42968a94dec42469cc17190a4e65" + lc_trscnt_last = "ff59557fbb79decfd2b776fb6fcfdf9b51f9affbad277bfcfab7a7b1ef70ebafdcebd6bebadeffc7a66fff17993a7e75" + ownership_first = "ea846e2884d9198c4a42348a61406249" + ownership_last = "fecc6faaeef93dcccefef68f696f6adf" + production = "ffc2171a49199bb53d8e0daa8f0578db" + raw_unlock_token = "e4225dc332ea1fda63b4c524556ed4d4" + rma = "aa5f022791480b4e709e0f80976e0966" + soc_dbg_first = "6b36fc2c" + soc_dbg_last = "ebf6fc7f" + test_unlocked = "e4c72c47d9e15fa8ff9f82833e32c151" + +[ot_device "ot-keymgr_dpe"] + aes_seed = "9a885419a9d57e36519fa42d20efa348cb77f5089a8381b62d9717b3647e5ce0" + hard_output_seed = "4d57ac27843ea2a0163efeda5b24d93f97d62d99c960542f4a1f2595192e9a96" + kmac_seed = "796ca68aa29523d3f94f27c74acae76b3e145326741f804e3c3a3451469c6118" + lfsr_seed = "488f3ffa7700ec4c" + none_seed = "49fcb59874cddc9cbc645d9e02a96b9980911844d3c74de85e05164b829d484b" + otbn_seed = "fa23bc32e428ad2be2584fe8d5491fa8e7f19aaa4133ad0d2704702ae2ff9d98" + revision_seed = "7b416b49b114f994a969864a57c3482ab3bdcbd9fe825728f7ba40a22c9719c5" + soft_output_seed = "d7c160f492d0e8757631ebf752a1e5604e2d6171f5dba9ec4249052dcc7a4d58" + +[ot_device "ot-ast-dj"] + topclocks = "main:1000000000,io:1000000000,aon:62500000" + aonclocks = "aon" + +[ot_device "ot-clkmgr"] + topclocks = "main:16,io:16,aon:1" + refclock = "aon" + subclocks = "io_div2:io:2,io_div4:io:4,aes:main:1,hmac:main:1,kmac:main:1,otbn:main:1" + groups = "powerup:aon+io+io_div2+io_div4+main,trans:aes+hmac+kmac+otbn,infra:aon+io_div4+main,secure:io_div4+main,peri:aon+io_div2+io_div4,timers:aon+io_div4" + swcg = "peri" + hint = "trans" + +[ot_device "ot-pwrmgr"] + clocks = "main,io" + diff --git a/docs/config/opentitan/earlgrey.cfg b/docs/config/opentitan/earlgrey.cfg new file mode 100644 index 0000000000000..0836d865872be --- /dev/null +++ b/docs/config/opentitan/earlgrey.cfg @@ -0,0 +1,71 @@ +# Generated from OpenTitan commit: 011b6ea1fe + +[ot_device "ot-rom_ctrl"] + key = "663c291739ff0e7d644758fee1c58564" + nonce = "fee457dee82b6e06" + +[ot_device "ot-otp-eg"] + digest_const = "0e95f517cb98955b4d5a89aa9109294a" + digest_iv = "bead91d5fa4e0915" + flash_addr_const = "d60822e1faec5c7290c7f21f6224f027" + flash_addr_iv = "0b7474d640f8a7f5" + flash_data_const = "277195fc471e4b26b6641214b61d1b43" + flash_data_iv = "e048b657396b4b83" + inv_default_part_5 = "7edc64361898f6ff7023461f42b4a5b3f7f71ea9a9507acf17c456385a48b963a629408e450e8458f3cd636e3c9a1140ae2905da9b31bb7ac60dd16b888838df2737bacf95ed7bf8" + inv_default_part_6 = "6969690000000000f254e78568a7f4bb" + inv_default_part_7 = "024c5fee0f5c8bb2c3080ad0531facb5370ad4f5b61d71b62203a5595f131d71a060cae9543819be" + inv_default_part_8 = "31dc5b08ced196d3b50ff2274384b9dbdd0d4e5d6bd0177ec2dce29f8327c8dbca99e674d3996d4260d2a0790332d0552ab6973f142947235a0c88a3ea33571096c8ddc72428751c29709bbd80960ee0a80ddce593c569c4" + inv_default_part_9 = "8a1cd4f5518b4d2a1978b594ed3dcd94671685f41086d5d3f6f8a097aad0585e4d857adfdebd0d2c0ece8ed011c5bad01988d871b8d25f316e627ccc51fc79029f45333ca4988068edfed1b3f0968cd628a94cbb02adbb8c" + inv_default_part_10 = "be6f2b4a67801b852a4529345af1cdbf37e97ab260e717e8e3c3363a666c130154d936fd1163e401fadd9f8c0ee9d1a056a2719fc2c345a4873c594f465e723e64ba4e17c79665cccb7943e751f0059633fbb917e41db693" + scrmbl_key = "55d70063277642b5309a163990b966cd494444c3bcdf8087b7facd65e9654cd8" + secret0_scramble_key = "3ba121c5e097ddeb7768b4c666e9c3da" + secret1_scramble_key = "effa6d736c5eff49ae7b70f9c46e5a62" + secret2_scramble_key = "85a9e830bc059ba9286d6e2856a05cc3" + sram_const = "4a22d4b78fe0266fbee3958332f2939b" + sram_iv = "f98c48b1f9377284" + +[ot_device "ot-lc_ctrl"] + dev = "90f9c5c06f733dc911a321dd4c0ac240" + invalid = "5bc66d10208c4fb51b97bbf4d12c378c" + lc_state_first = "ee75b407d2314d2ef84185ac8c990f536071632c086d4c924070be92d2948d6228b2711e9b2d8c4d" + lc_state_last = "ee75fe0ffe7b6f3ffc5f9ffd9ff96fdb7f736f6c9e6fdcd35277fef2d3bdcd6ffbb2f59fdf3fbedd" + lc_trscnt_first = "dfb6c45a241f85ce9f42229e8627462fdb02c6701242f14b41891180045c09c26c52744267c04aa055921b9461bb07da" + lc_trscnt_last = "dfb6f4fabf1fefcebf5ba2ffc677c6afdbabcefeb672f36b4fbdb3988dfe1be67e7e77ca77c76af7ddde3b9e7fbfe7de" + ownership_first = "3c61398d626885931da660ed2ab18a0f" + ownership_last = "bc757dbdeaf9b7d35de7e9efabfbbecf" + production = "6d467215dab3f2b54a52e6728678af07" + raw_unlock_token = "ea2b3f32cbe77554e43c8ea7ebf197c2" + rma = "18068e344d2eae4d73c8fa54de8aa4a4" + soc_dbg_first = "440af80d" + soc_dbg_last = "6c9ef9cf" + test_unlocked = "cfd6a5a5bf3032db51fa1eb92f6ce10e" + +[ot_device "ot-keymgr"] + aes_seed = "c9e662e1e1b4982b3e8eff63890dbeae926fd468a77efde3de5b4caf4776a247" + cdi_seed = "516c144b34c96dfabb6f6e79208a74f35c79f397c4e4c7e22b7581848a90a125" + creator_identity_seed = "70251696fbb4ba169a6cd82fad46a0a5c04009bb934c7ef7b83b6e40610b4309" + hard_output_seed = "baf4410f06dcc036fcd16fcde97d171891105dd895e3a1d0a19a16d6dbd8b20f" + kmac_seed = "baba4c9908ed16bc5415ec16d28c53551312fcedcf2832a66ceacf8ed4d5b616" + lfsr_seed = "6ca8a8225c8e1705" + none_seed = "4b2276bec9fc1a7a5722a9aaca4db84a32e5c6985a1a6435118b5f5f3fd3b931" + otbn_seed = "177dd43b56fa754e1f53ad658193b563d9bdc6d2aee84852f0cc7371ed3a6faa" + owner_identity_seed = "a4a7bdb05fe921615bf2ff984540f7d43ece76b4eb13363774ed2ed45545e927" + owner_int_identity_seed = "fcb9dc54d4276137f9901d09a76aeaa19de5c579fd38bdf38f0c21d6a52226b6" + revision_seed = "20bdaf59fe2ae209b8325d2c8c35fc960679b106a87e59e6aeb1b5302d334010" + soft_output_seed = "f6d9e4abac398d42c745eef646c1464dca86dafd7c7c71e6058ddfd871c51cac" + +[ot_device "ot-ast-eg"] + topclocks = "main:100000000,io:96000000,usb:48000000,aon:200000" + aonclocks = "aon" + +[ot_device "ot-clkmgr"] + topclocks = "main:500,io:480,usb:240,aon:1" + refclock = "aon" + subclocks = "io_div2:io:2,io_div4:io:4,aes:main:1,hmac:main:1,kmac:main:1,otbn:main:1" + groups = "powerup:aon+io+io_div2+io_div4+main+usb,trans:aes+hmac+kmac+otbn,infra:io+io_div2+io_div4+main+usb,secure:aon+io_div4+main,peri:aon+io+io_div2+io_div4+usb,timers:aon+io_div4" + swcg = "peri" + hint = "trans" + +[ot_device "ot-pwrmgr"] + clocks = "main,io,usb" + diff --git a/docs/opentitan/cfggen.md b/docs/opentitan/cfggen.md new file mode 100644 index 0000000000000..60fb06e867135 --- /dev/null +++ b/docs/opentitan/cfggen.md @@ -0,0 +1,96 @@ +# `cfggen.py` + +`cfggen.py` is a helper tool that can generate a QEMU OT configuration file, +for use with QEMU's `-readconfig` option, populated with sensitive data for +the ROM controller(s), the OTP controller, the Life Cycle controller, etc. + +It heurastically parses configuration and generated RTL files to extract from +them the required keys, seeds, nonces and other tokens that are not stored in +the QEMU binary. + +## Usage + +````text +usage: cfggen.py [-h] [-T {darjeeling,earlgrey}] [-o CFG] [-c SV] [-l SV] + [-S HJSON] [-t HJSON] [-s SOCID] [-C COUNT] + [-a {config,clock}] [-x DEVICE.NAME] [-v] [-d] + [OTDIR] + +OpenTitan QEMU configuration file generator. + +options: + -h, --help show this help message and exit + +Files: + OTDIR OpenTitan root directory + -T, --top {darjeeling,earlgrey} + OpenTitan top name + -o, --out CFG Filename of the config file to generate + -c, --otpconst SV OTP Constant SV file (default: auto) + -l, --lifecycle SV LifeCycle SV file (default: auto) + -S, --secrets HJSON Secret HJSON file (default: auto) + -t, --topcfg HJSON OpenTitan top HJSON config file (default: auto) + +Modifiers: + -s, --socid SOCID SoC identifier, if any + -C, --count COUNT SoC count (default: 1) + +Actions: + -a, --action {config,clock} + Action(s) to perform, default: config + -x, --exclude DEVICE.NAME + Discard any property from DEVICE that starts with NAME + (may be repeated) + +Extras: + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +`OTDIR` is a required positional argument which should point to the root directory of the OpenTitan +repository to analyze. It is used to generate the path towards the required files to parse, each of +which can be overridden with options `-c`, `-l` and `-t`. + +* `-a` specify one or more actions to execute. Default is to generate a configuration file. It is + also possible to emit the list of module input clocks in a plain text format. + +* `-C` specify how many SoCs are used on the platform + +* `-c` alternative path to the `otp_ctrl_part_pkg.sv` file + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-l` alternative path to the `lc_ctrl_state_pkg.sv.sv` file + +* `-o` the filename of the configuration file to generate. It not specified, the generated content + is printed out to the standard output. + +* `-S` path to the generated file that contains all the "secret" constants of the _top_. + +* `-s` specify a SoC identifier for OT platforms with multiple SoCs + +* `-T` specify the OpenTitan _top_ name, such as `darjeeling`, `earlgrey`, ... This option is + mandatory if `-t` is not specified. An OTDIR root directory should be specified with this option. + +* `-t` path to the `top_.gen.hjson` file. This option is mandatory is `-T` is not specified. + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +* `-x` can be used to prevent some device properties from being emitted in the output stream. This + can be useful while implementing a device, whose emulation does not support the comprehensive + feature set of its HW counterpart. + +### Examples + +````sh +./scripts/opentitan/cfggen.py ../opentitan -T earlgrey -o opentitan.cfg +```` + +````sh +# do not emit any 'flash*' properties for the EarlGrey OTP controller +./scripts/opentitan/cfggen.py -o opentitan.cfg \ + -t ../opentitan/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson + -x ot-otp-eg.flash +```` diff --git a/docs/opentitan/checkregs.md b/docs/opentitan/checkregs.md new file mode 100644 index 0000000000000..d16fcf5a86ab6 --- /dev/null +++ b/docs/opentitan/checkregs.md @@ -0,0 +1,72 @@ +# `checkregs.py` + +`checkregs.py` checks whether QEMU register definitions for OpenTitan match the generated OpenTitan +`*_regs.h` files. This enables to spot major differences whenever OpenTitan definitions are updated. + +Note that only register addresses are checked for now, the bit assignment in each register is not +validated. + +## Usage + +````text +usage: checkregs.py [-h] [-q dir] [-a] [-k] [-M] [-v] [-d] file [file ...] + +Verify register definitions. + +positional arguments: + file register header file + +options: + -h, --help show this help message and exit + -q dir, --qemu dir QEMU directory to seek + -a, --all list all discrepancies + -k, --keep keep verifying if QEMU impl. is not found + -M, --no-map do not convert regs into QEMU impl. path + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +* `-a` list all discrepancies rather than only reporting their count. + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-k` keep searching for discrepancies if some error is detected. + +* `-M` do not try to map radix of input register files into their QEMU counter part + +* `-q dir` the directory to look for OpenTitan devices to check. + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +### Examples + +* All EarlGrey register files have been copied into `opentitan/regs-251rc1` + + ````sh + scripts/opentitan/checkregs.py -k -q hw/opentitan ~/opentitan/regs-251rc1/*.h + ```` + reports + ```` + ERROR ot.regs ot_hmac.c: 1 discrepancies + ERROR ot.regs ot_otbn.c: 2 discrepancies + ERROR ot.regs ot_otp.c: 69 discrepancies + ERROR ot.regs ot_pinmux.c: 585 discrepancies + ERROR ot.regs ot_ibex_wrapper.c: 1 discrepancies + 658 differences + ```` + `pinmux` and `otp` registers in QEMU OT are not implemented as a flat list of registers, which + explain the large amount of discrepancies. + + ````sh + scripts/opentitan/checkregs.py -a -k -q hw/opentitan ~/opentitan/regs-251rc1/rv_core_ibex_regs.h -v + ```` + reports + ```` + WARNING ot.regs QEMU ot_ibex_wrapper.c is missing 1 defs + WARNING ot.regs .. DV_SIM_WINDOW (0x80) + ERROR ot.regs ot_ibex_wrapper.c: 1 discrepancies + 1 differences + ```` + which is Ok as DV_SIM_WINDOW is not supported on QEMU. diff --git a/docs/opentitan/debug.md b/docs/opentitan/debug.md new file mode 100644 index 0000000000000..b378dfc60c887 --- /dev/null +++ b/docs/opentitan/debug.md @@ -0,0 +1,51 @@ +## Useful debugging options + +### Device log traces + +Most OpenTitan virtual devices can emit log traces. To select which traces should be logged, a plain +text file can be used along with QEMU `-trace` option. + +To populate this file, the easiest way is to dump all available traces and filter them with a +pattern, for example to get all OpenTitan trace messages: + +````sh +qemu-system-riscv32 -trace help | grep -E '^ot_' > ot_trace.log +qemu-system-riscv32 -trace events=ot_trace.log -D qemu.log ... +```` + +* It is *highly* recommended to use the `-D` option switch when any `-trace` or `-d` (see below) is +selected, to avoid saturating the standard output stream with traces and redirect them into the +specified log file. + +### QEMU log traces + +QEMU provides another way of logging execution of the virtual machine using the `-d` option. Those +log messages are not tied to a specific device but rather to QEMU features. `-d help` can be used +to enumerate these log features, however the most useful ones are enumerated here: + + * `unimp` reports log messages for unimplemented features, _e.g._ when the vCPU attempts to + read from or write into a memory mapped device that has not been implemented. + * `guest_errors` reports log messages of invalid guest software requests, _e.g._ attempts to + perform an invalid configuration. + * `int` reports all interruptions *and* exceptions handled by the vCPU. It may be quite verbose + but also very useful to track down an invalid memory or I/O access for example. This is the + first option to use if the virtual machine seems to stall on start up. + * `in_asm` reports the decoded vCPU instructions that are translated by the QEMU TCG, _i.e._ here + the RISC-V instructions. Note that transcoded instructions are cached and handled by blocks, + so the flow of transcoded instruction do not exactly reflect the stream of the executed guest + instruction, e.g. may only appear once in a loop. Use the next log option, `exec`, to get + more detailed but also much more verbose log traces. + * `exec` reports the vCPU execution stream. + +Those options should be combined with a comma separator, _e.g._ `-d unimp,guest_errors,int` + +`in_asm` option may be able to report the name of the guest executed function, as long as the guest +application symbols have been loaded. This is the case when the `-kernel` option is used to load +an ELF non-stripped file. Unfortunately, this feature is not available for guest applications that +are loaded from a raw binary file (`.bin`, `.signed.bin`, ...). However the +[`flashgen.py`](flashgen.md) script implements a workaround for this feature, please refer to this +script for more details. + +Finally, a Rust demangler has been added to QEMU, which enables the QEMU integrated disassembler to +emit the demangled names of the Rust symbols for Rust-written guest applications rather than their +mangled versions as stored in the ELF file. diff --git a/docs/opentitan/devproxy.md b/docs/opentitan/devproxy.md new file mode 100644 index 0000000000000..82b72c1f4e6c0 --- /dev/null +++ b/docs/opentitan/devproxy.md @@ -0,0 +1,1181 @@ +## DevProxy Protocol + +### Header + +#### DevProxy header + +``` ++---------------+---------------+---------------+---------------+ +| 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| ++---------------+---------------+---------------+---------------+ +| COMMAND | LENGTH | ++---------------+---------------+---------------+---------------+ +| UID |I| ++---------------+---------------+---------------+---------------+ +``` + +#### DOE PCIe header [doe-pcie-header] + +``` ++---------------+---------------+---------------+---------------+ +| 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| ++---------------+---------------+---------------+---------------+ +| VendorID |ObjectDataType | - | ++---------------+---------------+---------------+---------------+ +| Length | - | ++---------------+---------------+---------------+---------------+ +``` + +#### Length fields + +##### Notes + * Values are encoded using the LE (litle endian) format + * `LENGTH` is the length in bytes excluding the first 8 bytes of the MBXPROXY protocol header, + coded on 16 bits. Payload is therefore < 64KB. + * This field is different from the `Length` is the length in 32-bit words (DW per Intel + terminology) of the Data Object itself, including the 2 first 32-bit words of the DOE header, + coded on 18 bits. Theoritical DOE payload is therefore ~1MB, which is capped by the MBX protocol. + It is not expected to have DOE packets larger than 4KB anyway. + +#### UID and Initiator fields + +DevProxy header is not related whatsoever to the content of the DOE packet, it is a simplistic TLV +protocol, extended with a 31+1 bit UID. This UID is not accounted for in the `LENGTH` field, and is +always present. The UID is a monotonic increasing integer. The initiator of a request should +increment the UID for each request. The receiver should reply once with the same UID. The receiver +should always acknowledge a request, whether it is successfully handled or not, in which case it +should reply with a error code, using the UID of the failed command. The MSB of the 32-bit field +that contains the UID is used to distinguish the peer. The QEMU peer is using `1`, the remote peer +should use `0`. There are therefore two independent UID sequences: each UID is managed and +incremented by the initiator, the receiver never modifies the UID. UIDs are used as sanity check to +ensure sync is not lost between requests and responses. It is a fatal error to miss one UID or to +reuse one. Roll over cases are not managed (2G requests). DevProxy protocol can be used with any +kind of payload, i.e. not DOE payloads. This is the case for the first requests performed on the +communication link for example (see below). + +#### Command fields + +Commands are coded with a 16-bit field. It is expected to only use ASCII bytes for commands, i.e. +two characters that should be enough to encode many commands. Moreover, the initiator of a request +should use UPPERCASE command, and the receiver should reply with the same command using only +lowercased character. This allows to disambiguate initiators from responders, and use of ASCII +character helps tracking and debugging packets. If a command cannot be executed on the receiver +side, the receiver should reply with a `xx` response command (lowercased). The UID enables to track +which command has failed. There is no other cases where the initiator command and the response +command differ (case-insensitive). `XX` is therefore a reserved command. + +#### Role fields + +`Role` field encode an access control list role to use to access a register or a memory location. +Valid values depends on the remote proxy, however the highest (role = 0xF) is reserved to specify +that role field should be ignored, and role-less accessor should be used by the remote proxy. + +### Request/Response packets + +#### Error + +##### Request + +Not applicable + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'xx' | 4..4+N | ++---------------+---------------+---------------+---------------+ +| UID |S| ++---------------+---------------+---------------+---------------+ +| Address | Device | - | ++---------------+---------------+---------------+---------------+ +| Error Code | ++---------------+---------------+---------------+---------------+ +| | +| | +| (Error Message) | +| | +| | ++---------------+---------------+---------------+---------------+ +``` +* `Error Code` is a 32-bit error code that is always present + +``` ++------------+---------------+----------------------------------+ +| Error Code | Error Type | Error Description | ++------------+---------------+----------------------------------+ +| 0x0 | | No error | ++------------+---------------+----------------------------------+ +| 0x1 | Undefined | Unknown | ++------------+---------------+----------------------------------+ +| 0x101 | Request | Invalid command length | ++------------+---------------+----------------------------------+ +| 0x102 | Request | Invalid command code | ++------------+---------------+----------------------------------+ +| 0x103 | Request | Invalid request identifier | ++------------+---------------+----------------------------------+ +| 0x104 | Request | Invalid specifier identifier | ++------------+---------------+----------------------------------+ +| 0x105 | Request | Invalid device identifier | ++------------+---------------+----------------------------------+ +| 0x106 | Request | Invalid request | ++------------+---------------+----------------------------------+ +| 0x107 | Request | Invalid address/register address | ++------------+---------------+----------------------------------+ +| 0x201 | State | Device in error | ++------------+---------------+----------------------------------+ +| 0x401 | Local | Cannot read device | ++------------+---------------+----------------------------------+ +| 0x402 | Local | Cannot write device | ++------------+---------------+----------------------------------+ +| 0x403 | Local | Truncated response | ++------------+---------------+----------------------------------+ +| 0x404 | Local | Incomplete write | ++------------+---------------+----------------------------------+ +| 0x405 | Local | Out of resources | ++------------+---------------+----------------------------------+ +| 0x801 | Internal | Unsupported device | ++------------+---------------+----------------------------------+ +| 0x802 | Internal | Duplicated unique identifier | ++------------+---------------+----------------------------------+ +``` + +* `Error Message` is an optional error message to help diagnose the actual error. The length of the + error message string can be retrieved from the `LENGTH` field. + +Any request may be responded with an error packet. + +#### Handshake + +Handshake is the first command exchanged over the communication link. It enables sanity check of the +link, and to retrieve the version of the protocol the QEMU peer implement. It is the application +responsability to ensure it can successfully communicate with the QEMU proxy. + +Only initiated by the application. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'HS' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'hs' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Version Min | Version Maj | | ++---------------+---------------+---------------+---------------+ +``` + +The current version for this documentation is v0.15. + +Note that semantic versionning does not apply for v0 series. + +#### Logmask + +Logmask can be used to change the qemu_log_mask bitmap at runtime, so log +settings can be altered for specific runtime ranges, for a specific test for +example + +##### Request +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | ++---------------+---------------+---------------+---------------+ +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'HL' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---+-----------+---------------+---------------+---------------+ +| Op| Log mask | ++---+-----------+---------------+---------------+---------------+ +``` + +* `Op`: Log operation, among: + * `0`: change nothing, only read back the current log levels + * `1`: add new log channels from the log mask + * `2`: clear log channels from the log mask + * `3`: apply the log mask as is, overridding previous log channel settings + +##### Response +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | ++---------------+---------------+---------------+---------------+ +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'hl' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---+-----------+---------------+---------------+---------------+ +| 0 | Previous log mask | ++---+-----------+---------------+---------------+---------------+ +``` + +#### Enumerate Devices [enumerate-devices] + +Enumerate should be called by the Application to retrieve the list of remote devices that can be +driven from the Application over the communication link. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ED' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ed' | 0..4*7N | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Offset | Device | - | ++---------------+---------------+---------------+---------------+ +| Base Address | ++---------------+---------------+---------------+---------------+ +| Word Count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| Offset | Device | - | ++---------------+---------------+---------------+---------------+ +| Base Address | ++---------------+---------------+---------------+---------------+ +| Word Count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Offset | Device | - | ++---------------+---------------+---------------+---------------+ +| Base Address | ++---------------+---------------+---------------+---------------+ +| Word Count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +``` +Reponse contains 0 up to N devices, each device is described with a 28-byte entry, where: + +* `Device` is a unique device identifier that should be used to select the device for all further + requests to the device. +* `Offset` is the relative base address of the first register than can be accessed on the device, + expressed in 32-bit word count +* `RegCount` is the count of 32-bit registers that can be accessed, starting from the base address, + only if b0 is zero. +* `WordCount` is the count of 32-bit slots for a memory-type device +* `Base Address` is the base address of the device in the address space as seen from the local CPU. +* `Identifier` is an arbitrary 16-character string that describes the device. + +The count of device entries can be retrieved from the `LENGTH` field. + +#### Enumerate Memory Spaces [enumerate-mr-spaces] + +Enumerate should be called by the Application to retrieve the list of remote memory spaces that can +be used from the Application over the communication link. + +Each memory space is a top-level memory region, _i.e._ a root memory region. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ES' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'es' | 0..11*4N | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| - | MSpc | ++---------------+---------------+---------------+---------------+ +| Start Address | ++---------------+---------------+---------------+---------------+ +| Size | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| - | MSpc | ++---------------+---------------+---------------+---------------+ +| Start Address | ++---------------+---------------+---------------+---------------+ +| Size | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| - | MSpc | ++---------------+---------------+---------------+---------------+ +| Start Address | ++---------------+---------------+---------------+---------------+ +| Size | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +``` +Reponse contains 0 up to N devices, each device is described with a 44-byte entry, where: + +* `MSpc` is a unique address space identifier than can be used as a selector in other proxy + commands. +* `Start Address` is the lowest valid address in the address space. +* `Size` is the size (in bytes) of the address space +* `Identifier` is an arbitrary 32-character string that describes the memory space. + +The count of address spaces can be retrieved from the `LENGTH` field. + +#### Register Read + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'RW' | 8 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Address | Device | Role | ++---------------+---------------+---------------+---------------+ +``` + +Read the content of a 32-bit word register, where + +* `Address` is the register index, i.e. the relative address in bytes / 4. +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'rw' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Value | ++---------------+---------------+---------------+---------------+ +``` + +#### Register Write + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'WW' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Address | Device | Role | ++---------------+---------------+---------------+---------------+ +| Value | ++---------------+---------------+---------------+---------------+ +| Mask | ++---------------+---------------+---------------+---------------+ +``` + +Write the content of a 32-bit word register, where + +* `Address` is the register index, i.e. the relative address in bytes / 4. +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `Value` is the (optionally masked) 32-bit value to write +* `Mask` is the mask of bits to be written. Masked bits (0) are left unmodified, unmasked bits (1) + are replaced with the bits from the value field. The remote device use read-modify-write sequence + if the destination device does not support atomic set. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ww' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +#### Read Buffer + +Read the content of several subsequence 32-bit registers, where + +* `Address` is the index of the first register, i.e. the relative address in bytes / 4. The address + is automatically incremented by the receiver side to read each register +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `Count` is the number of the register to read. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'RS' | 8 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Address | Device | Role | ++---------------+---------------+---------------+---------------+ +| Count | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'rs' | 4..4*Count | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Value #0 | ++---------------+---------------+---------------+---------------+ +| Value #1 | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Value #N-1 | ++---------------+---------------+---------------+---------------+ +``` + +#### Write Buffer + +Write the content of several subsequence 32-bit registers, where + +* `Address` is the index of the first register, i.e. the relative address in bytes / 4. The address + is automatically incremented by the receiver side to write each register +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `Value` are the values to write in each register. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'WS' | 4 + 4*Count | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Address | Device | Role | ++---------------+---------------+---------------+---------------+ +| Value #0 | ++---------------+---------------+---------------+---------------+ +| Value #1 | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Value #N-1 | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ws' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Count | ++---------------+---------------+---------------+---------------+ +``` + +#### Read Mailbox + +Read the content of a DOE mailbox, where + +* `Address` is the index of the mailbox register, i.e. the relative address in bytes / 4. +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `Count` is the number of the 32-bit words to read out of the mailbox + +The content of the mailbox is read till either `Count` 32-bit words are read _or_ the Data Object +has been fully read. Each read word is automatically flagged as read in the remote mailbox (see DOE +protocol for detail). + +It is the responsability of the Application to ensure that the remote mailbox is ready to be read +before initiating this command. If an error occurs during the read out of the mailbox, an error +response should be returned. It is not an error to read less words than requests, _i.e._ it is safe +to specify a high count of words to read - as long as the response may fit into a DevProxy packet. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'RX' | 8 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Address | Device | Role | ++---------------+---------------+---------------+---------------+ +| Count | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'rx' | 4..4*Count | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Value #0 | ++---------------+---------------+---------------+---------------+ +| Value #1 | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Value #Count-1 | ++---------------+---------------+---------------+---------------+ +``` + +#### Write Mailbox + +Write a message into the DOE mailbox, where + +* `Address` is the index of the mailbox register, i.e. the relative address in bytes / 4. +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `Value` are the values to write to the mailbox + +It is the responsability of the Application to ensure that the remote mailbox is reday to be written +before initiating this command. If an error occurs during the write to the mailbox, an error +response should be returned. + +The responder side automatically triggers the GO bit once all values have been written to the +destination mailbox (if no error occurs). + +The Application is in charge of formatting the payload to ensure that it contains a valid DOE object +(including formatting the header, see [DOE PCIe header](#doe-pcie-header)) + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'WX' | 4 + 4*Count | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Address | Device | Role | ++---------------+---------------+---------------+---------------+ +| Value #0 | ++---------------+---------------+---------------+---------------+ +| Value #1 | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Value #N-1 | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'wx' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Count | ++---------------+---------------+---------------+---------------+ +``` + +#### Read Memory + +Read the content of a memory device, where + +* `Address` is the address in bytes of the first 32-bit word +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `Count` is the number of 32-bit word to be read. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'RM' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| - | Device | Role | ++---------------+---------------+---------------+---------------+ +| Address | ++---------------+---------------+---------------+---------------+ +| Count | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'rm' | 4..4*Count | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Value #0 | ++---------------+---------------+---------------+---------------+ +| Value #1 | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Value #N-1 | ++---------------+---------------+---------------+---------------+ +``` + +#### Write Memory + +Write a buffer to a memory device + +* `Address` is the address in bytes of the first 32-bit word to be written +* `Role` is the initiator role to use to access the device +* `Device` is the device to access (see [Enumerate Devices](#enumerate-devices)) +* `Value` are the values to write in memory word. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'WM' | 8 + 4*Count | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| - | Device | Role | ++---------------+---------------+---------------+---------------+ +| Address | ++---------------+---------------+---------------+---------------+ +| Value #0 | ++---------------+---------------+---------------+---------------+ +| Value #1 | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| Value #N-1 | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'wm' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Count | ++---------------+---------------+---------------+---------------+ +``` + +#### Resume VM execution + +Resume execution if the VM is currently stopped. + +When VM is started in stod mode `-S`, proxy can be used to configure the VM before kicking off the +vCPUs. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'CX' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'cx' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +#### Quit + +Stop QEMU emulation and return an error code to the caller. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'QT' | 8 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Error code | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'qt' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +This is the last command, as QEMU should exit upon receiving this request. + +#### Enumerate Device Interrupt [enumerate-irq] + +Enumerate can be called by the Application to retrieve the list of interrupt groups of a supported +device. The group in the response can be further used with the [Signal Interrupt API](#signal-interrupt), +[Intercept Interrupts](#intercept-interrupt) and [Release Interrupts](#release-interrupt). + +##### 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..36N | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| IRQ count | Group |O| - | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| IRQ count | Group |O| - | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| IRQ count | Group |O| - | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +``` +Reponse contains 0 up to N interrupt groups, each group is described with a 36-byte entry, where: + +* `IRQ count` is the count of the input interrupts for this group, +* `O` is set if IRQ in this group are output interrupts or or not set if they are input interrupts, +* `Group` is the interrupt group identifier identifier. +* `Identifier` defines the interrupt group name + +The count of address spaces can be retrieved from the `LENGTH` field. + +#### Intercept Interrupts [intercept-interrupt] + +Route one or more device output interrupts to the proxy (vs. the internal PLIC) + +* `Device` is the device to access (see [Enumerate Device](#enumerate-devices)) +* `Group` is the IRQ group in the device (see [Enumerate Interrupt](#enumerate-irq)) +* `Interrupt mask` define which interrupts should be released (1 bit per interrupt). The count of + 32-bit interrupt mask word can be retrieved from the header length. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'II' | 8.. | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Group | - | Device | - | ++---------------+---------------+---------------+---------------+ +| | +| Interrupt mask | +| | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ii' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +#### Release Interrupts [release-interrupt] + +Revert any previous interception, reconnecting selected IRQ to their original +destination device. + +* `Device` is the device to access (see [Enumerate Device](#enumerate-devices)) +* `Group` is the IRQ group in the device (see [Enumerate Interrupt](#enumerate-irq)) +* `Interrupt mask` define which interrupts should be released (1 bit per interrupt). The count of + 32-bit interrupt mask word can be retrieved from the header length. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'IR' | 8.. | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| Group | - | Device | - | ++---------------+---------------+---------------+---------------+ +| | +| Interrupt mask | +| | ++---------------+---------------+---------------+---------------+ +``` + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'ir' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +#### Signal Interrupt [signal-interrupt] + +Set or Reset an input interrupt line. + +* `Device` is the device to access (see [Enumerate Device](#enumerate-devices)), +* `Group` the identifier of the IRQ group (see [Enumerate Interrupt](#enumerate-irq)), +* `Interrupt line` the index 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| ++---------------+---------------+---------------+---------------+ +| Group | 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| ++---------------+---------------+---------------+---------------+ +``` + +#### Intercept arbitrary MMIO region [intercept-region] + +It is possible to get intercept access to a memory region. Any intercepted region cannot +be any longer accessed by the remote vCPU. + +Use with care, as it may generate a very high traffic on the communication link. Width of +intercepted location should be kept small. + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'MI' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +|R|W| Priority | - | Stop | - | MSpc | ++---------------+---------------+---------------+---------------+ +| Address | ++---------------+---------------+---------------+---------------+ +| Size | ++---------------+---------------+---------------+---------------+ +``` + +* `R` whether to notify on read access +* `W` whether to notify on write access +* `Priority` is the priority order (increasing order). 0 is reserved. If not sure, use 1. +* `Stop` auto-stop count. If non-zero, only the specified count of notifications are reported, + after which the MMIO location intercepter is automatically discarded. +* `MSpc` is the memory space of the region to select (see [Enumerate Memory Spaces](#enumerate-mr-spaces)) +* `Address` is the byte address of the first byte to intercept in the selected memory space +* `Size` is the width in bytes of the region to intercept + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'mi' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| 0 | Region | - | ++---------------+---------------+---------------+---------------+ +``` + +* `Region` is the new intercepter number that describes the intercepting region + +#### Release intercepted MMIO locations + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'MR' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| - | Region | - | ++---------------+---------------+---------------+---------------+ +``` + +* `Region` is the number of the intercepter to release + +##### 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| ++---------------+---------------+---------------+---------------+ +| 'mr' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +* `Device` is the new device number that describes the intercepting region + +### Interrupt signalling + +Interrupt signalling are messages that never expect a response and that may be sent/received at any +time. Each receiver should be prepared to handle many interrupt messages without any prior +request/command. + +An [intercept interrupt](#intercept-interrupt) command should have been first used to select which +interrupt(s) to receive. + +#### "Wired" Interrupt +``` ++---------------+---------------+---------------+---------------+ +| 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| ++---------------+---------------+---------------+---------------+ +| '^W' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |1| ++---------------+---------------+---------------+---------------+ +| - | Device | - | ++---------------+---------------+---------------+---------------+ +| Channel | Group |O| - | ++---------------+---------------+---------------+---------------+ +| Value | ++---------------+---------------+---------------+---------------+ +``` + +* `Device` that triggered the interrupt +* `Group` which interrupt group has been triggered +* `Channel` which interrupt channel for the group has been triggered +* `Value` the new interrupt value. For binary IRQs, `1` when IRQ is raised, `0` when it is lowered. + +#### MSI Interrupt + +To be defined. + +``` ++---------------+---------------+---------------+---------------+ +| 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| ++---------------+---------------+---------------+---------------+ +| '^M' | 8 | ++---------------+---------------+---------------+---------------+ +| UID |1| ++---------------+---------------+---------------+---------------+ +| - | Device | Role | ++---------------+---------------+---------------+---------------+ +| Value | ++---------------+---------------+---------------+---------------+ +``` + +#### Region Access + +See [Intercept arbitrary MMIO region](#intercept-region) to register a watcher + +``` ++---------------+---------------+---------------+---------------+ +| 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| ++---------------+---------------+---------------+---------------+ +| '^R' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |1| ++---------------+---------------+---------------+---------------+ +|R|W| | Width | - | Region | Role | ++---------------+---------------+---------------+---------------+ +| Address | ++---------------+---------------+---------------+---------------+ +| Value | ++---------------+---------------+---------------+---------------+ +``` + +* `Region` the region watcher identifier, as returned in the interception registration response +* `R` read access +* `W` write access +* `Width` the width in byte of the MMIO access (1/2/4) +* `Address` the absolute address of the MMIO access +* `Value` the value if memory location was written, 0 otherwise diff --git a/docs/opentitan/dtm.md b/docs/opentitan/dtm.md new file mode 100644 index 0000000000000..706dc8d117d04 --- /dev/null +++ b/docs/opentitan/dtm.md @@ -0,0 +1,170 @@ +# `dtm.py` + +`dtm.py` checks that the JTAG/DTM/DM stack is up and running and demonstrates how to use the Debug +Module to access the Ibex core. + +## Usage + +````text +usage: dtm.py [-h] [-S SOCKET] [-t] [-w DELAY] [-l IR_LENGTH] + [--idcode IDCODE] [--dtmcs DTMCS] [--dmi DMI] [-b BASE] [-I] + [-c CSR] [-C CSR_CHECK] [-x] [-X] [-a ADDRESS] [-m {read,write}] + [-s SIZE] [-f FILE] [-D DATA] [-e ELF] [-F] [-v] [-d] + +Debug Transport Module tiny demo + +options: + -h, --help show this help message and exit + +Virtual machine: + -S, --socket SOCKET connection (default tcp:localhost:3335) + -t, --terminate terminate QEMU when done + -w, --idle DELAY stay idle before interacting with DTM + +DMI: + -l, --ir-length IR_LENGTH + bit length of the IR register (default: 5) + --idcode IDCODE define the ID code (default: 1) + --dtmcs DTMCS define an alternative DTMCS register (default: 0x10) + --dmi DMI define an alternative DMI register (default: 0x11) + -b, --base BASE define DMI base address (default: 0x0) + +Info: + -I, --info report JTAG ID code and DTM configuration + -c, --csr CSR read CSR value from hart + -C, --csr-check CSR_CHECK + check CSR value matches + +Actions: + -x, --execute update the PC from a loaded ELF file + -X, --no-exec does not resume hart execution + +Memory: + -a, --address ADDRESS + address of the first byte to access + -m, --mem {read,write} + access memory using System Bus + -s, --size SIZE size in bytes of memory to access + -f, --file FILE file to read/write data for memory access + -D, --data DATA data to write using memory access + -e, --elf ELF load ELF file into memory + -F, --fast-mode do not check system bus status while transferring + +Extras: + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +* `-a` specify the memory address where data is loaded or stored. Useful with the `--mem` option. + See also the `--size` option. Note that only 32-bit aligned addresses are supported for now. + +* `-b` specify the DMI base address for the RISC-V Debug Module + +* `-C` compare a CSR value to the specified value. Requires option `--csr`. + +* `-c` read and report a CSR from the Ibex core. + +* `-D` data to write, useful with `--mem` option in write mode. Mutually exclusive with `--file`. + Data may be specified as an decimal or hexadecimal integer, limited to 32-bit integers. It is also + possible to use `:` followed with a string of hexadecimal nibbles (without 0x prefix). + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-e` specify an ELF32 application file to upload into memory. See also the `--exec` option. + +* `-F` assume System Bus can cope with received data pace. This feature increases transfer data + rate by bypassing SB status check. However it may cause the transfer to fail in case System Bus + becomes busy while data are transferred. + +* `-f` specify the file to read from or write to when option `--mem` is used. Mutually exclusive + with option `--data`. + +* `-I` report the JTAG ID code and the DTM configuration. + +* `-l` specify the length of the TAP instruction register length. + +* `-m ` specify a memory operation to perform. See also `--address`, `--size` and + `--file` options. With `read` operation, if no `--file` is specified, the content of the selected + memory segment is dumped to stdout, with a similar format as the output of `hexdump -C`. If a file + is supplied, the content of the segment is written as binary data into this file. With `write` + operation, `--file` argument is mandatory. The content of the binary file is copied into the + memory, starting at the `--address`. See also the `--elf` option for uploading applications. + +* `-S` specify the socket info to connect to the remote device. + * for QEMU VM JTAG server, TCP and Unix sockets are supported, _i.e._ `tcp:host:port` or + `unix:path/to/socket`. + * For TCP, this should follow the setting of the + `-chardev socket,id=taprbb,host=,port=,...` option for invoking QEMU. + * for Unix sockets, host/port are replaced with the unix socket path specified instead: + `-chardev socket,id=taprbb,path=`. + * for FTDI *232H USB-JTAG interfaces, PyFtdi v0.59+ Python module should be installed, and + pyftdi URL should be used to specify the FTDI device: _e.g._ `ftdi://ftdi:2232/1` + +* `-s` specify the number of bytes to read from or write to memory. Useful with the `--mem` option. + See also the `--address` option. This option may be omitted for the `write` memory operation, in + which case the size of the specified file is used. Note that only sizes multiple of 4 bytes are + supported for now. + +* `-t` send QEMU a request for termination when this script exits. + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +* `-w` pause execution on start up before communication with the remote DTM. This enables QEMU VM + to initialize and boot the guest SW before attempting a communication. It can be repeated once, + the second value being used to delay the termination of the VM when the `-t` option is used. + +* `-X` do not attempt to resume normal execution of the hart once DTM operation have been completed. + This can be useful for example when the QEMU VM is started with `-S` and no application code has + been loaded in memory: once the DTM operations are completed, the default behavior is to resume + the hart execution, would start execution code from the current PC and cause an immediate + exception. The `-x` option can nevertheless be executed, as it is the last action that the script + performs. + +* `-x` execute the loaded ELF application from its entry point. Requires the `--elf` option. + Application is executed even with `-X` defined. + +* `--idcode` specify an alternate IDCODE value + +* `--dmi` specify an alternate address for the DMI register + +* `--dtmcs` specify an alternate address for the DTMCS register + +### Examples + +Running QEMU VM with the `-chardev socket,id=taprbb,host=localhost,port=3335,server=on,wait=off` +option: + +* Retrieve JTAG/DTM/DM information and `mtvec` CSR value + ````sh + ./scripts/opentitan/dtm.py -I -c mtvec + ```` + +* Check that the MISA CSR matches the expected Ibex core value: + ````sh + ./scripts/opentitan/dtm.py -c misa -C 0x401411ad + ```` + +* Load (fast mode) and execute an application + ````sh + ./scripts/opentitan/dtm.py -e .../helloworld -x -F + ```` + +* Dump a memory segment to stdout + ````sh + ./scripts/opentitan/dtm.py -m read -a 0x1000_0080 -s 0x100 + ```` + +* Upload a file into memory and leave the Ibex core halted + ````sh + ./scripts/opentitan/dtm.py -m write -a 0x1000_0000 -f file.dat -X + + ```` + +* Write a 32-bit integer into memory + ````sh + ./scripts/opentitan/dtm.py -m write -a 0x1000_0000 -D 0x1234abcd + # or + ./scripts/opentitan/dtm.py -m write -a 0x1000_0000 -D :1234abcd + ```` diff --git a/docs/opentitan/flashgen.md b/docs/opentitan/flashgen.md new file mode 100644 index 0000000000000..a58b762fdb554 --- /dev/null +++ b/docs/opentitan/flashgen.md @@ -0,0 +1,108 @@ +# `flashgen.py` + +`flashgen.py` populates a QEMU RAW image file which can be loaded by the OpenTitan integrated +flash controller virtual device. + +## Usage + +````text +usage: flashgen.py [-h] [-D] [-a {0,1}] [-s OFFSET] [-x file] [-X elf] + [-b file] [-B elf] [-t OTDESC] [-T] [-U] [-A] [-v] [-d] + flash + +Create/update an OpenTitan backend flash file. + +options: + -h, --help show this help message and exit + +Image: + flash virtual flash file + -D, --discard Discard any previous flash file content + -a, --bank {0,1} flash bank for data (default: 0) + -s, --offset OFFSET offset of the BL0 file (default: 0x10000) + +Files: + -x, --exec file rom extension or application + -X, --exec-elf elf ELF file for rom extension or application, for symbol + tracking (default: auto) + -b, --boot file bootloader 0 file + -B, --boot-elf elf ELF file for bootloader, for symbol tracking (default: + auto) + -t, --otdesc OTDESC OpenTitan style file descriptor, may be repeated + -T, --ignore-time Discard time checking on ELF files + -U, --unsafe-elf Discard sanity checking on ELF files + -A, --accept-invalid Blindly accept invalid input files + +Extra: + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +The (signed) binary files contain no symbols, which can make low-level debugging in QEMU difficult +when using the `-d in_asm` and/or `-d exec` option switches. + +If an ELF file with the same radix as a binary file is located in the directory of the binary +file, or its location is specified in the command line, the path to the ELF file is encoded +into the flash image within a dedicated debug section. + +The OT flash controller emulation, when the stored ELF file exists, attempts to load the symbols +from this ELF file into QEMU disassembler. This enables the QEMU disassembler - used with the +`in_asm`/`exec` QEMU options - to output the name of each executed function as an addition to the +guest PC value. + +It is therefore recommended to ensure the ELF files are located in the same directory as their +matching signed binary files to help with debugging. + +### Arguments + +* `-A` accept any kind of input files, discarding all sanity checks. This option should only be used + to check the behavior of the VM and guest code when invalid files are stored in the embedded flash + device. + +* `-a bank` specify the data partition to store the binary file into, mutually exclusive with `-t`. + +* `-B elf` specify an alternative path to the BL0 ELF file. If not specified, the ELF path file is + reconstructed from the specified binary file (from the same directory). The ELF file is only used + as a hint for QEMU loader. Requires option `-b`, mutually exclusive with `-t`. + +* `-b file` specify the BL0 (signed) binary file to store in the data partition of the flash + image file. The Boot Loader 0 binary file is stored in the data partition at the offset + specified with the -s option, mutually exclusive with `-t`. + +* `-D` discard any flash content that may exist in the QEMU RAW image file. + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-s offset` the offset of the BootLoader image file in the data partition. Note that this offset + may also be hardcoded in the ROM_EXT application. Changing the default offset may cause the + ROM_EXT to fail to load the BL0 application. + +* `-t binfile@address` specify a binary file to store into the data partition of the flash. + This is a compatibility option introduced to support the original `opentitantool image assemble` + syntax. The provided address should be specified in hexadecimal format. Currently, it is not + possible to specify a matching ELF file as the flash debug trailer format is hardcoded to only + support ROM EXT and bootloader binaries in both banks, and does not support arbitrary ELFs. + This option may be repeated to specify multiple files such as the ROM EXT and a bootloader for + example. This option is mutually exclusive with `-b`, `-B`, `-x`, `-X` and `-a`. + +* `-X elf` specify an alternative path to the ROM_EXT ELF file. If not specified, the ELF path file + is reconstructed from the specified binary file (from the same directory). The ELF file is only + used as a hint for QEMU loader. Requires option `-x`, mutually exclusive with `-t`. + +* `-x file` specify the ROM_EXT (signed) binary file or the application to store into the data + partition of the flash image file. Alternatively this file can be any (signed) binary file such as + an OpenTitan test application. Note that if a BL0 binary file is used, the ROM_EXT/test binary + file should be smaller than the selected offset for the bootloader location, mutually exclusive + with `-t`. + +* `-T` tell the script to ignore any time discrepancy between a binary and an ELF file. A binary + file being generated from an ELF file, the former is not expected to be older than the latter. + +* `-U` tell the script to ignore any discrepancies found between a binary file and an ELF file. If + the path to an ELF file is stored in the flash image file, the ELF content is validated against + the binary file. Its code position and size, overall size and entry point should be coherent. + This option implies `-T` option, _i.e._ also discards time comparison between BIN and ELF files. + Note that contents of both files are not compared, as the binary file may be amended after the ELF + file creation. + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. diff --git a/docs/opentitan/gdbreplay.md b/docs/opentitan/gdbreplay.md new file mode 100644 index 0000000000000..71af7867bb1d1 --- /dev/null +++ b/docs/opentitan/gdbreplay.md @@ -0,0 +1,87 @@ +# `gdbreplay.py` + +`gdbreplay.py` uses QEMU log files containing `exec` messages to help a GDB client to replay +execution of a guest application. + +It parses QEMU log files, and creates a GDB server serving GDB remote requests. GDB client can +add breakpoints and follow executed instructions by the guest, stepping or running till a +breakpoint is encountered. It is possible to run execution backward. + +Supported GDB commands: + + * step/step instruction + * reverse step/step instruction + * continue + * hardware breakpoint + * memory dump (code only), requires to provide the application(s) with the help of ELF or binary + files + +QEMU traces do not contain register values so it is not possible to observe any register content or +data. + +## Usage + +````text +usage: gdbreplay.py [-h] [-t LOG] [-e ELF] [-a ADDRESS] [-b BIN] [-g GDB] [-v] [-d] + +QEMU GDB replay. + +options: + -h, --help show this help message and exit + -t LOG, --trace LOG QEMU execution trace log + -e ELF, --elf ELF ELF application + -a ADDRESS, --address ADDRESS + Address to load each specified binary + -b BIN, --bin BIN Binary application + -g GDB, --gdb GDB GDB server (default to localhost:3333) + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +* `-a` specify an address where to load the matching RAW binary application, see `-b` option for + details. May be repeated. + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-b` specify a RAW binary application. May be repeated. For each `-b` argument, there should be + a matching `-a` argument. + +* `-e` specify a ELF application to load. May be repeated. + +* `-g` specify an alternative interface/port to listen for GDB client + +* `-t` specify the QEMU log to parse for `exec`ution traces (see `-d exec` QEMU option) + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +### Examples + +* From one terminal, starts the GDB replay tool + ````sh + # Load two ELFs here, one for the ROM, one for the application to debug + ./scripts/opentitan/gdbreplay.py -vv -t qemu.log \ + -e test_rom_fpga_cw310.elf -e csrng_kat_test_prog_fpga_cw310.elf + ```` + +* From another terminal, starts a regular GDB client + ````sh + riscv64-unknown-elf-gdb + ```` + then + ```` + # Load the ELF file on GDB side so that it knows the symbols + file csrng_kat_test_prog_fpga_cw310.elf + # Connect to gdbreplay + target remote :3333 + # Add a HW breakpoint to the entry function of the test + hb test_main + # Execute till breakpoint + c + ... + # Move execution back to the `test_main` caller + rsi + # Move execution point to the very first instruction + rc + ```` diff --git a/docs/opentitan/gpiodev.md b/docs/opentitan/gpiodev.md new file mode 100644 index 0000000000000..0c160d5245f15 --- /dev/null +++ b/docs/opentitan/gpiodev.md @@ -0,0 +1,78 @@ +# `gpiodev.py` + +`gpiodev.py` acts as a GPIO CharDev backend to record and verify GPIO updates. + +It can be used to run regression tests and verify that the guest emits a predefined sequence of +GPIO updates. + +## Usage + +````text +usage: gpiodev.py [-h] [-p PORT] [-c CHECK] [-r RECORD] [-e END] [-q] [-s] [-t] [-v] [-d] + +GPIO device tiny simulator. + +options: + -h, --help show this help message and exit + -p PORT, --port PORT remote host TCP port (defaults to 8007) + -c CHECK, --check CHECK + input file to check command sequence + -r RECORD, --record RECORD + output file to record command sequence + -e END, --end END emit the specified value to trigger remote exit on last received command + -q, --quit-on-error exit on first error + -s, --single run once: terminate once first remote disconnects + -t, --log-time emit time in log messages + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-p` specify an alternative TCP port to listen for incoming QEMU CharDev socket requests + +* `-c` specify a pre-recorded GPIO backend text file - which only contains ASCII characters and + compare that incoming GPIO requests against the recorded data + +* `-r` specify a file to record incoming GPIO requests + +* `-e` emit a pre-defined GPIO input command - sent to the QEMU GPIO CharDev backend - that may be + recognized by the QEMU guest code as a special marker to end a test sequence. Requires the _check_ + option for detecting the last expected GPIO request. + +* `-q` close the QEMU socket connection on first error or mismatched GPIO request when used with + the _check_ option. + +* `-s` quite the script on the first QEMU socket disconnection. Default is to listen for a new QEMU + CharDev socket connection, restarting from a fresh state. + +* `-t` add time to log messages + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +### Exit Status + +This script returns 0 if no error has been detected, or 1 if an error has been detected or the +GPIO command stream has not been validated against the check file if any. + +### Examples + +With the following examples: + +`-chardev socket,id=gpio,host=localhost,port=8007 -global ot-gpio-$OTMACHINE.chardev=gpio` has been +added to the QEMU command line to connect the GPIO backend to the incoming socket opened from the +script. See the [GPIO documentation](ot_gpio.md) for details and the role of the `OTMACHINE` +environment variable. + +* Record a GPIO request sequence + ````sh + ./scripts/opentitan/gpiodev.py -vv -r gpio.txt -s + ```` + +* Check a GPIO request sequence against a previously record one, then triggers a VM guest app + triggered termination by setting GPIO 31 to a high level. + ````sh + ./scripts/opentitan/gpiodev.py -vv -c gpio.txt -s -e 0x8000_0000 + ```` diff --git a/docs/opentitan/i2c_host_proxy.md b/docs/opentitan/i2c_host_proxy.md new file mode 100644 index 0000000000000..8d100e33d400b --- /dev/null +++ b/docs/opentitan/i2c_host_proxy.md @@ -0,0 +1,52 @@ +# OpenTitan I2C Host Proxy + +The `ot-i2c-host-proxy` device provides a way to execute I2C transactions with QEMU I2C bus devices +through an externally driven `chardev` interface. + +The device can perform read or write transactions with bus devices and is intended to be used to +drive the Opentitan I2C device Target mode, though it can be used to interact with other devices on +the QEMU I2C buses. + +## Configuration + +A chardev and proxy device can be created in QEMU with the following arguments: + +`-chardev pty,i2cN -device ot-i2c-host-proxy,bus=,chardev=i2cN` + +Where `bus` is any of the system's I2C buses. On Earl Grey for example, these are `ot-i2c0`, +`ot-i2c1`, and `ot-i2c2`. + +## Protocol + +The protocol over the chardev mirrors the I2C specification closely. + +Before each transaction, the protocol version must be sent. This is sent as an `i` byte (for I2C), +followed by the version number byte (currently 1). The device will respond with an ACK byte +(see below) if the version matches the implementation, and a NACK byte otherwise. + +A start and repeated start condition is signalled with an `S` byte, followed by a byte containing +the 7-bit target address of the transaction and the read/write bit as the least significant bit +(0 indicates a write transfer, 1 indicates a read transfer). + +On a repeated start condition, the I2C target address of the first transfer will be used, and the +provided address is ignored (transfer direction can be changed). This is a limitation of QEMU. + +When a read transfer is active, a `R` byte can be sent to read a byte of data from the target I2C +device. + +When a write transfer is active, a `W` byte, followed by a data byte, can be sent to write that +byte to the target I2C device. + +A stop condition to end the transaction is signalled with a `P` byte. + +Following a start/repeated start condition and the address byte, as well as after every byte +written during a write transfer, an ACK is signalled with a `.` byte, and a NACK is signalled with +a `!` byte. If the start of a transfer is NACKed, the transaction should be terminated with a stop +condition. + +On a parser error, a `x` byte will be returned, and the parser will not accept any more command +bytes until reset. + +The implementation will throttle processing of written bytes to allow control to return to the +vCPU, so that OT I2C may process the current transaction and prepare response bytes for upcoming +reads. diff --git a/docs/opentitan/ibex_hart.md b/docs/opentitan/ibex_hart.md new file mode 100644 index 0000000000000..ec5101166cbff --- /dev/null +++ b/docs/opentitan/ibex_hart.md @@ -0,0 +1,30 @@ +# IBEX Hart + +## Supported features + +* `RV32I` v2.1 Base Integer Instruction Set with `B` (`Zba`, `Zbb`, `Zbc` and `Zbs`) `C`, `M` + extensions +* `Zicsr` and `Zifencei` extensions +* `Smepmp` extension (ePMP) +* `Zbr` v0.93 unratified ISA extension (crc32 instructions) + +## Unsupported features + +* Extensions not defined in RISC-V standard and/or not ratified are not supported (except `Zbr`) + * `Zbe`, `Zbf`, `Zbp` and `Zbt` +* Ibex NMI +* custom CSRs: `cpuctrlsts`, `secureseed` +* `mret` extension (double fault management) + +## Useful options + +* `-icount 6` reduces the execution speed of the vCPU (Ibex core) to 1GHz >> 6, _i.e._ ~15MHz, + which should roughly match the expected speed of the Ibex core running on the CW310 FPGA, which + is set to 10 MHz. This option is very useful/mandatory to run many OpenTitan tests that rely on + time or CPU cycle to validate features. Using `-icount` option slows down execution speed though, + so it is not recommended to use it when the main goal is to develop SW to run on the virtual + machine. An alternative is to use `-icount shift=auto`, which offers fastest emulation execution, + while preserving an accurate ratio between the vCPU clock and the virtual devices. + +* `-cpu lowrisc-ibex,x-zbr=false` can be used to force disable the Zbr experimental-and-deprecated + RISC-V bitmap extension for CRC32 extension. diff --git a/docs/opentitan/ibexdemo.md b/docs/opentitan/ibexdemo.md new file mode 100644 index 0000000000000..68911ef8d1fac --- /dev/null +++ b/docs/opentitan/ibexdemo.md @@ -0,0 +1,22 @@ +# IbexDemo on Arty A7 + +Ibex Demo is a very lightweight platform that demonstrate the Ibex core features. + +* Hello Word application +````sh +qemu-system-riscv32 -M ibexdemo -nographic -kernel .../hello_world/demo +```` + +* Mandelbrot demo +````sh +qemu-system-riscv32 -M ibexdemo -display -kernel .../demo/lcd_st7735/lcd_st7735 +```` + +## Useful execution options + +#### vCPU + +* `-icount N` reduces the execution speed of the vCPU (Ibex core) to 1GHz >> N + +* `-cpu lowrisc-ibex,x-zbr=true` can be used to force enable (IbexDemo platform) the Zbr + experimental-and-deprecated RISC-V bitmap extension for CRC32 extension. diff --git a/docs/opentitan/index.md b/docs/opentitan/index.md new file mode 100644 index 0000000000000..3da481270c7c4 --- /dev/null +++ b/docs/opentitan/index.md @@ -0,0 +1,41 @@ +# Ibex SoC emulation + +## Requirements + +1. A native toolchain for C and Rust +2. Ninja build tool + +Note: never tested on Windows hosts. + +## Building + +````sh +mkdir build +cd build +../configure --target-list=riscv32-softmmu --without-default-features --enable-tcg \ + --enable-tools --enable-trace-backends=log \ + [--enable-gtk --enable-pixman | --enable-cocoa] +ninja +ninja qemu-img +```` + +* `--enable-gtk --enable-pixman` and `--enable-cocoa` are only useful when using a graphical + display, such as the IbexDemo platform. It is mosly useless with the OpenTitan platform. + + * `--enable-gtk --enable-pixman` should be used on Linux hosts + * `--enable-cocoa` should be used on macOS hosts + +* `--extra-cflags=-Wno-deprecated-declarations` and + `--extra-ldflags=-Wl,-no_warn_duplicate_libraries` may be required to build on recent releases of + macOS (QEMU issues which are not related to the OpenTitan port) + +### Useful build options + + * `--enable-debug` + * `--enable-sanitizers` + +## Supported platforms + + * [IbexDemo](ibexdemo.md) built for Digilent Arty7 board + * [Darjeeling](ot_darjeeling.md) build for CW310 "Bergen" board + * [EarlGrey](ot_earlgrey.md) build for CW310 "Bergen" board diff --git a/docs/opentitan/jtag-dm.md b/docs/opentitan/jtag-dm.md new file mode 100644 index 0000000000000..a5636edc296b0 --- /dev/null +++ b/docs/opentitan/jtag-dm.md @@ -0,0 +1,157 @@ +# JTAG and RISC-V Debug Module support for OpenTitan + +RISC-V Debug Module is implemented as an alternative to the default QEMU GDB server. + +It can be used when low-level features are required, such as prototyping JTAG manufacturing and +provisioning software development, or emulating JTAG Debug Authentication. + +This initial implementation of the RISC-V Debug Module and Pulp DM supports the following features: + +- Ibex core debugging (RV32 only); not tested with multiple harts +- system bus memory access: read and write guest memory +- guest code debugging +- single stepping +- HW breakpoints +- HW watchpoints + +## Communicating with RISC-V DM through a JTAG connection + +In QEMU machines for OpenTitan, any Debug Module can register on the Debug Transport Module, which +dispatches requests to Debug Module depending on the received DMI address. + +See also [JTAG mailbox](jtagmbx.md) and [Life Controller](lc_ctrl_dmi.md) for other Debug Modules. + +``` ++--------------------------+ +| Host (OpenOCD or Python) | ++--------------------------+ + | + | TCP connection ("bitbang mode") + | ++-----|-----------------------------------------------------------------------------------+ +| | QEMU | +| v +---------+ | +| +-------------+ +-----+ +-----------+ | PULP-DM | +------+ | +| | JTAG server |---->| DTM |---->| RISC-V DM |<---->+ ------- +<=======>| Hart | | +| +-------------+ +-----+ +-----------+ | ROM/RAM | || +------+ | +| || +---------+ || | +| || || +--------------+ | +| ================================>| I/O, RAM, ...| | +| +--------------+ | ++-----------------------------------------------------------------------------------------+ +``` + +### Remote bitbang protocol + +The JTAG server supports the Remote Bitbang Protocol which is compatible with other tools such as +Spike and [OpenOCD](https://github.com/riscv/riscv-openocd). + +QEMU should be started with an option such as: +`-chardev socket,id=taprbb,host=localhost,port=3335,server=on,wait=off` so that the JTAG server is +instantiated and listens for incoming connection on TCP port 3335. + +#### Remote termination feature + +The remote Remote Bitbang Protocol supports a "_quit_" feature which enables the remote client to +trigger the end of execution of the QEMU VM. This feature can be useful to run automated tests for +example. + +It is nevertheless possible to disable this feature and ignore the remote exit request so that +multiple JTAG sessions can be run without terminating the QEMU VM. + +To disable this feature, use the `quit` option: `-global tap-ctrl-rbb,quit=off`. + +#### Implementation + +* JTAG server is implemented with `jtag/jtag_bitbang.c` +* DTM is implemented with `hw/riscv/dtm.c` +* RISC-V DM is implemented with `hw/riscv/dm.c` +* Pulp-DM is implemented with `hw/misc/pulp_rv_dm.c` + +See also other supported DM modules on OpenTitan: + +* [JTAG mailbox DMI](jtagmbx.md) +* [LifeCycle DMI](lc_ctrl_dmi.md) + +## Communicating with JTAG server using OpenOCD + +OpenOCD running on a host can connect to the embedded JTAG server, using a configuration script such +as + +```tcl +adapter driver remote_bitbang +remote_bitbang host localhost +remote_bitbang port 3335 + +transport select jtag + +set _CHIPNAME riscv +# part 1, ver 0, lowRISC; actual TAP id depends on the QEMU machine +set _CPUTAPID 0x00011cdf + +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id $_CPUTAPID + +# Hart debug base also depends on the QEMU machine +set DM_BASE 0x00 + +target create $_CHIPNAME riscv -chain-position $_TARGETNAME rtos hwthread -dbgbase $DM_BASE +``` + +## Connecting GDB to OpenOCD + +In another terminal, `riscv{32,64}-unknown-elf-gdb` can be used to connect to OpenOCD: + +``` +# 64-bit version support 32-bit targets +riscv64-unknown-elf-gdb +``` + +A basic `$HOME/.gdbinit` as the following should connect GDB to the running OpenOCD instance: +``` +target remote :3333 +``` + +## Communicating with JTAG server using Python + +`scripts/opentitan/ot` directory contains Python modules that provide several APIs to test the +JTAG/DTM/DM stack. + +A demo application is available from [`scripts/opentitan/dtm.py`](dtm.md) that can report basic +information about this stack and demonstrate how to use the Debug Module to access the Ibex core. + +The [`scripts/opentitan/otpdm.py`](otpdm.md) also use the same stack to access the cells of the OTP +controller. + +### Troubleshooting [#troubleshooting] + +A common issue with initial JTAG configuration is to use the wrong Instruction Register length. +The IR length depends on the actual implementation of the TAP controller and therefore may be +different across HW implementations, here across OpenTitan instantiations. + +Unfortunately, there is no reliable way to automatically detect the IR length, this needs to be +a setting that should be provided to the JTAG tool. Scripts in `scripts/opentitan` use the `-l` / +`--ir-length` option to specify the IR length. The default value may be obtained with the `-h` +option, which is IR length = 5 bits for default EarlGrey and Darjeeling machines. + +Whenever the wrong IR length is specified, or the default IR length is used with a HW/VM machine +that uses a non-default length, the first instruction that is stored in the TAP instruction register +is misinterpreted, which may cause different errors with the same root cause: a wrong IR length +setting. + +It is recommended to query the DMI address bits with for example [`scripts/opentitan/dtm.py`](dtm.md) +which is a basic command that needs a valid IR length setting to complete. Use `dtm.py -I` to query +some low-level information from the remote peer. + +It should report something like: +```` +IDCODE: 0x11cdf +DTM: v0.13, 12 bits +```` + +* If the DTM bit count is stuck to 0, the IR length is likely wrong, +* If an error message such as `Invalid reported address bits` is returned, the IR length is likely + wrong. + +Another common issue is to use a machine with multiple Debug Modules but fail to specify its base +address. The default DMI base address is always `0x0`. Use `-b` / `--base` option to select the +proper Debug Module base address. diff --git a/docs/opentitan/jtagmbx.md b/docs/opentitan/jtagmbx.md new file mode 100644 index 0000000000000..7501140834f87 --- /dev/null +++ b/docs/opentitan/jtagmbx.md @@ -0,0 +1,244 @@ +# Darjeeling JTAG Mailbox + +The JTAG mailbox host side (responder) is connected to the private OT address space, while the +system side (requester) is connected to a Debug Tile Link bus. + +## Communicating with the JTAG Mailbox through a JTAG connection + +In QEMU, a bridge between the Debug Transport Module (DTM) and the JTAG Mailbox is implemented +as Debug Module bridge. + +``` ++----------------+ +| Host (OpenOCD) | ++----------------+ + | + | TCP connection ("bitbang mode") + | ++-----|-----------------------------------------------------------------------------------+ +| v | +| +-------------+ +-----+ +----------+ +---------+ +------+ | +| | JTAG server |---->| DTM |---->| ot_dm_tl |====D====|S> MBX > 2) + +# See ot.mailbox.sysmbox.SysMbox for mailbox communication API +```` + +## Communicating with JTAG server using OpenOCD + +It is possible from OpenOCD running a host to connect to the embedded JTAG server using the +`remote_bitbang` protocol, using a configuration script such as + +```tcl +adapter driver remote_bitbang +remote_bitbang host localhost +remote_bitbang port 3335 + +transport select jtag + +set _CHIPNAME riscv +# part 1, ver 0, lowRISC; actual TAP id depends on the QEMU machine +set _CPUTAPID 0x00011cdf + +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id $_CPUTAPID + +# Hart debug base also depends on the QEMU machine +set DM_BASE 0x00 + +target create $_CHIPNAME riscv -chain-position $_TARGETNAME rtos hwthread -dbgbase $DM_BASE +``` + +From here, it is possible reach the JTAG dmi through the DMI device: + +```tcl +# Mailbox system register addresses +set MBX_SYS_BASE 0x200 + +set SYS_INTR_MSG_ADDR $($MBX_SYS_BASE + 0x0) +set SYS_INTR_MSG_DATA $($MBX_SYS_BASE + 0x1) +set SYS_CONTROL $($MBX_SYS_BASE + 0x2) +set SYS_STATUS $($MBX_SYS_BASE + 0x3) +set SYS_WRITE_DATA $($MBX_SYS_BASE + 0x4) +set SYS_READ_DATA $($MBX_SYS_BASE + 0x5) + +# Mailbox system register bits +set SYS_CONTROL_ABORT 0x1 +set SYS_CONTROL_SYS_INT_EN 0x2 +set SYS_CONTROL_GO 0x80000000 + +set SYS_STATUS_BUSY 0x1 +set SYS_STATUS_INT 0x2 +set SYS_STATUS_ERROR 0x4 +set SYS_STATUS_READY 0x80000000 + +# read the STATUS register +dmi_read $SYS_STATUS + +# write the GO bit of the control register +dmi_write $SYS_CONTROL $SYS_CONTROL_GO +``` + +Unfortunalely the current version of OpenOCD (v0.12+) does not support a JTAG-DMI communication if +no RISC-V DM module is connected to the DMI device, _i.e._ + +``` ++----------------+ +| Host (OpenOCD) | ++----------------+ + | + | TCP connection ("bitbang mode") + | ++-----|-----------------------------------------------------------------------------------+ +| v | +| +-------------+ +-----+ +----------+ +---------+ +------+ | +| | JTAG server |---->| DTM |-+-->| ot_dm_tl |====D====|S> MBX | DM |---->| PulpDM |============== | +| +----+ +---------+ | +| | ROM/RAM | | +| +---------+ | +| | +| QEMU| ++-----------------------------------------------------------------------------------------+ +``` + +If no RISC-V compatible DM is detected at `$DM_BASE`, OpenOCD enters an infinite loop seeking a +valid DM module. + +DM / PulpDM should be part of an upcoming delivery. Meanwhile it is required to tweak OpenOCD so +that it accepts staying connected to the DMI even if it cannot locate a valud DM: + +```diff +diff --git a/src/target/riscv/riscv-013.c b/src/target/riscv/riscv-013.c +index 2ab28deac..0e84418d9 100644 +--- a/src/target/riscv/riscv-013.c ++++ b/src/target/riscv/riscv-013.c +@@ -1882,6 +1882,8 @@ static int examine(struct target *target) + return ERROR_FAIL; + } + ++ target->state = TARGET_UNAVAILABLE; ++ return ERROR_OK; + /* Reset the Debug Module. */ + dm013_info_t *dm = get_dm(target); + if (!dm) +diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c +index 5bae01d5f..786f2520a 100644 +--- a/src/target/riscv/riscv.c ++++ b/src/target/riscv/riscv.c +@@ -3167,6 +3167,8 @@ int riscv_openocd_poll(struct target *target) + { + LOG_TARGET_DEBUG_IO(target, "Polling all harts."); + ++ return ERROR_OK; ++ + struct list_head *targets; + + LIST_HEAD(single_target_list); +``` + +## Communicating with the JTAG Mailbox through a DevProxy connection + +DevProxy is a communication tool that enables to control QEMU devices from a remote host, over a +TCP connection. + +It can access QEMU `SysBusDevice` through their `MemoryRegion` API, intercepts or generate IRQs on +those devices, intercepts accesses to plain RAM region and read or modify their content. See the +[devproxy.md](devproxy.md) document for information about the DevProxy communication protocol and +supported features. + +The `devproxy.py` Python module implements the DevProxy protocol and can be used on a host to +remotely control selected devices in the QEMU machine. It includes support for the Mailbox devices +for both the Host and System interfaces. + +``` ++--------------------+ +| Host (Python, ...) | ++--------------------+ + | + | TCP connection + | ++-----|-----------------------------------------+ +| v | +| +-----------------+ | +| | DevProxy server |------ | +| +-----------------+ | | +| | | | +| CTN Private | +| | | | +| | +---------+ | | +| +--->|S> MBX |S> MBX | MBX RAM | | +| | +---------+ | +---------+ | +| +--->|S> MBX | RAM | | +| | | +-----+ | +| ... | | +| | +---------+ | | +| +--->| IPI | | | +| | +---------+ | | +| ... ... | +| QEMU| ++-----------------------------------------------+ +``` + +QEMU should be started with a option pair to enable communication with the DevProxy server: +* `-chardev socket,id=devproxy,host=localhost,port=8003,server=on,wait=off +* `-global ot-dev_proxy.chardev=devproxy` + +Subsequently, a Python script importing the `devproxy.py` module can be used to communicate with +the JTAG mailbox. + +Note: `devproxy.py` needs to be found within the Python path, using for example +```sh +export PYTHONPATH=${QEMU_SOURCE_PATH}/scripts/opentitan +``` + +### Troubleshooting + +See the [Troubleshooting](jtag-dm.md#troubleshooting) section for details. diff --git a/docs/opentitan/keymgr-dpe.md b/docs/opentitan/keymgr-dpe.md new file mode 100644 index 0000000000000..4fed6eb529acc --- /dev/null +++ b/docs/opentitan/keymgr-dpe.md @@ -0,0 +1,244 @@ +# `keymgr-dpe.py` + +`keymgr-dpe.py` is a helper tool that can be used to generate or verify Key Manager DPE keys, using +the same parameters as the QEMU machine and the real HW, for debugging purposes. + +## Usage + +````text +usage: keymgr-dpe.py [-h] -c CFG -j HJSON [-l SV] [-m VMEM] [-R RAW] [-r ROM] + [-e BITS] [-z SIZE] [-v] [-d] + {generate,execute,verify} ... + +QEMU OT tool to generate Key Manager DPE keys. + +positional arguments: + {generate,execute,verify} + Execution mode + generate generate a key + execute execute sqeuence + verify verify execution log + +options: + -h, --help show this help message and exit + +Files: + -c, --config CFG input QEMU OT config file + -j, --otp-map HJSON input OTP controller memory map file + -l, --lifecycle SV input lifecycle system verilog file + -m, --vmem VMEM input VMEM file + -R, --raw RAW input QEMU OTP raw image file + -r, --rom ROM input ROM image file, may be repeated + +Parameters: + -e, --ecc BITS ECC bit count (default: 6) + -z, --rom-size SIZE ROM image size in bytes, may be repeated + +Extras: + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +* `-c` QEMU OT config file, which can be generated with the [`cfggen.py`](cfggen.md) script. + See also predefined files from the `docs/config/opentitan` directory. + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-e` specify how many bits are used in the HEX and VMEM files to store ECC information. + +* `-j` specify the path to the HJSON OTP controller map file, usually stored in OT + `hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson`, required to decode any OTP file. + +* `-l` specify the life cycle system verilog file that defines the encoding of the life cycle + states, required to decode the Life Cycle state, which is stored in the OTP file. + +* `-m` specify the input VMEM file that contains the OTP fuse content, mutually exclusive with `-R` + +* `-r` specify the path to a ROM file. This option should be repeated for each ROM file. The ROM + file may either be a ELF or a binary file, in which case the same count of `-z` ROM size option, + specified in the same order as the ROM file, is required. When such a ROM file format is used, the + ROM digest is computed from the ROM file content. The ROM file can also be specified a `HEX` + scrambled file, in which case the digest is read from the file itself. `VMEM` format is not yet + supported. + +* `-R` specify the path to the QEMU OTP RAW image file, which can be generated with the + [`otptool.py`](otptool.md), mutually exclusive with option `-m`. + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +* `-z` specify the size of each ROM file in bytes. It should be repeated for each specified ROM + file, except if all ROM files are specified as HEX formatted files. It can either be specified as + an integer or an integer with an SI-suffix (Ki, Mi), such as `-z 64Ki`. + +Depending on the execution mode, the following options are available: + +## Generate options + +This mode can be used to generate a single output key, which can be stored into an output file. + +``` +usage: keymgr-dpe.py [-h] -c CFG -j HJSON [-l SV] [-m VMEM] [-R RAW] [-r ROM] + [-e BITS] [-z SIZE] [-v] [-d] + {generate,execute,verify} ... + +QEMU OT tool to generate Key Manager DPE keys. + +positional arguments: + {generate,execute,verify} + Execution mode + generate generate a key + execute execute sequence + verify verify execution log + +options: + -h, --help show this help message and exit + +Files: + -c, --config CFG input QEMU OT config file + -j, --otp-map HJSON input OTP controller memory map file + -l, --lifecycle SV input lifecycle system verilog file + -m, --vmem VMEM input VMEM file + -R, --raw RAW input QEMU OTP raw image file + -r, --rom ROM input ROM image file, may be repeated + +Parameters: + -e, --ecc BITS ECC bit count (default: 6) + -z, --rom-size SIZE ROM image size in bytes, may be repeated + +Extras: + -v, --verbose increase verbosity + -d, --debug enable debug mode +``` + +### Arguments + +* `-b` specify the software binding for an _advance_ stage of the key manager. It should be repeated + for each new stage to execute. Note the first stage does not require a software binding value and + is always executed. The expected format is a hexa-decimal encoded string (without 0x prefix). It + is automatically padded with zero bytes up to the maximum software binding size supported by the + HW. + +* `-c` specify a QEMU [configuration file](otcfg.md) from which to read all the cryptographic + constants. See the [`cfggen.py`](cfggen.md) tool to generate such a file. + +* `-g` specify the kind of generation to perform. If not specified, it is inferred from the `-t` + target option. + +* `-j` specify the path to the HJSON OTP controller map file, usually named `otp_ctrl_mmap.hjson`. + +* `-l` specify the life cycle system verilog file, usually named `lc_ctrl_state_pkg.sv`, that + defines the encoding of the life cycle states. + +* `-k` the version of the key to generate + +* `-o` specify the output file path for the generated key + +* `-R` when specified, the Key Manager output slot is encoded as a Rust constant, whose + name is specified with this option. Default is to print the output slot in plain text. + +* `-s` specify the salt for the _generate_ stage of the key manager. The expected format is a hexa- + decimal encoded string (without 0x prefix). It is automatically padded with zero bytes up to the + maximum salt size supported by the HW. + +* `-t` specify the destination for the _generate_ stage. + +### Examples + +````sh +keymgr-dpe.py -vv -c docs/config/opentitan/darjeeling.cfg \ + -j otp_ctrl_mmap.hjson -m img_otp.vmem -l lc_ctrl_state_pkg.sv \ + -r base_rom.39.scr.hex -r second_rom.39.scr.hex -b 0 -b 0 -t AES -k 0 -s 0 +```` + +````sh +keymgr-dpe.py -vv -c docs/config/opentitan/darjeeling.cfg \ + -j otp_ctrl_mmap.hjson -m img_otp.vmem -l lc_ctrl_state_pkg.sv \ + -r rom0.elf -r keymgr-dpe-basic-test -z 64Ki -z 32Ki -t SW -k 0 -s 0 +```` + +## Execute options [execute-options] + +This mode can be used to execute a sequence of steps, with multiple key generations. + +It requires an input file containing the sequence of steps to execute, which follows the INI file +format. Each section in the file represents a step in the sequence, and the options within each +section specify the parameters for that step. + +``` +usage: keymgr-dpe.py execute [-h] -s INI + +options: + -h, --help show this help message and exit + -s, --sequence INI execution sequence definition +``` + +### Arguments + +* `-s` specify an INI-like configuration file containing the sequence of steps to execute + + Typical input file: + + ````ini + [step_1] + mode = initialize + dst = 0 + + [step_2] + mode = advance + binding = [0] + src = 0 + dst = 1 + allow_child = true + exportable = true + retain_parent = true + + [step_3] + mode = generate + src = 1 + dst = otbn + key_version = 0 + salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" + + [step_4] + mode = generate + src = 1 + dst = none + key_version = 0 + salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" + ```` + +## Verify options + +This mode can be used to verify the execution of a unit test, which is expected to print out the +input parameters it sends to the KeyManger DPE and the output key which is generated by the latter. + +Note that as the generated sideloaded key cannot usually be accessed by the Ibex core, the following +tricks are used: + +* for AES sideloaded key, the AES key is used to encrypt a zeroed plaintext, and the verification is +performed on the ciphertext +* for KMAC sideloaded key, the KMAC key is used to perform a hash on a zeroed input buffer, and the +verification is performed on the resulting hash value. +* for OTBN sideloaded key, the OTBN key is read by the OTBN core and replicated into its data +memory, which is then read back by the Ibex core. In this cas, the direct key is verified. + +``` +usage: keymgr-dpe.py verify [-h] -l LOG + +options: + -h, --help show this help message and exit + -l, --exec-log LOG execution log to verify +``` + +### Arguments + +* `-l` specify the execution log to verify. The execution log is expected to contain the output of + a test that has run on the OpenTitan platform. It should emit a syntax identical to the format + described in the [Execute options](#execute-options) section, _i.e._ an INI-like syntax. To + distinguish INI syntax from any other log output, each line of interest should be prefixed with a + `T> ` marker. + + [`pyot.py`](pyot.md) script may be used to generate the log file, see `--log-file` option or the + `log_file` test parameter. diff --git a/docs/opentitan/lc_ctrl_dmi.md b/docs/opentitan/lc_ctrl_dmi.md new file mode 100644 index 0000000000000..d30137aa4febc --- /dev/null +++ b/docs/opentitan/lc_ctrl_dmi.md @@ -0,0 +1,70 @@ +# LifeCycle Controller over DTM + +## Communicating with the Life Cycle Controller through a JTAG connection + +In QEMU, a bridge between the Debug Transport Module (DTM) and the Life Cycle Controller is +implemented as a Debug Module bridge. + +``` ++----------------+ +| Host (OpenOCD) | ++----------------+ + | + | TCP connection ("bitbang mode") + | ++-----|-----------------------------------------------------------------------------------+ +| v | +| +-------------+ +-----+ +----------+ +---------+ +------+ | +| | JTAG server |---->| DTM |---->| ot_dm_tl |====D====| LC Ctrl |====P====| Hart | | +| +-------------+ +-----+ +----------+ +---------+ +------+ | +| QEMU| ++-----------------------------------------------------------------------------------------+ +``` + +where: + `P` is the private OT bus + `D` is the debug bus + +In Darjeeling, a top-level debug crossbar multiplexes debug access to several devices, including +the Life Cycle Controller. Therefore, there is a single DTM and Debug Module bridge. In the QEMU +implementation of Darjeeling we thus have a single JTAG server, reachable over the `taprbb` +character device. For that machine, QEMU should be started with an option such as: +`-chardev socket,id=taprbb,host=localhost,port=3335,server=on,wait=off` so that the JTAG server is +instantiated and listens for incoming connections on TCP port 3335. + +In Earlgrey, we have multiple JTAG TAPs, including one for the Life Cycle Controller. The Life Cycle +Controller instantiates its own DTM and Debug Module bridge. Therefore, in the QEMU implementation +of Earlgrey we use a separate character device for that JTAG server, named instead `taprbb-lc-ctrl`. + +## Communicating with JTAG server and Life Cycle controller using Python + +OpenTitan implementation provides JTAG/DTM/DMI/Mailbox stack available as Python modules: + +* jtag/tap module is available from `python/qemu/jtag` directory +* dtm/dmi and jtag mailbox modules are available from `scripts/opentitan` directory + +Python snippet to create a communication channel with the VM JTAG mailbox: + +````py +from socket import create_connection +from jtag.bitbang import JtagBitbangController +from jtag.jtag import JtagEngine +from ot.dtm import DebugTransportModule +from ot.lc_ctrl.lcdmi import LifeCycleController + +sock = create_connection(('localhost', 3335), timeout=1.0) +ctrl = JtagBitbangController(sock) +eng = JtagEngine(ctrl) +ctrl.tap_reset(True) +dtm = DebugTransportModule(eng, 5) # IR length depends on the actual machine +version, abits = dtm['dtmcs'].dmi_version, dtm['dtmcs'].abits +print(f'DTM: v{version[0]}.{version[1]}, {abits} bits') +dtm['dtmcs'].dmireset() +lc_ctrl = LifeCycleController(dtm, 0x3000 >> 2) + +# See LifeCycleController for LC controller communication API +```` + +### Troubleshooting + +See the [Troubleshooting](jtag-dm.md#troubleshooting) section for details. diff --git a/docs/opentitan/ot_aes.md b/docs/opentitan/ot_aes.md new file mode 100644 index 0000000000000..16ad9a48f0b88 --- /dev/null +++ b/docs/opentitan/ot_aes.md @@ -0,0 +1,7 @@ +# OpenTitan AES + +* `-global ot-aes.fast-mode=false` can be used to better emulate AES HW IP, as some OT tests expect + the Ibex core to execute while the HW is performing AES rounds. Without this option, the virtual + HW may only give back execution to the vCPU once the AES operation is complete, which make those + OT tests to fail. Disabling fast mode better emulates the HW to the expense of higher AES latency + and throughput. diff --git a/docs/opentitan/ot_darjeeling.md b/docs/opentitan/ot_darjeeling.md new file mode 100644 index 0000000000000..c3980084ade24 --- /dev/null +++ b/docs/opentitan/ot_darjeeling.md @@ -0,0 +1,147 @@ +# Darjeeling + +## Supported version + +Please check out the `ot_darjeeling.c` file header. + +## Supported devices + +### Near feature-complete devices + +* [AES](ot_aes.md) +* Alert controller + * ping mechanism is not supported +* AON Timer +* CSRNG +* EDN +* HMAC +* [IBEX CPU](ibex_cpu.md) +* [JTAG](jtag-dm.md) (compatible with OpenOCD/Spike "remote bitbang" protocol) +* Key manager DPE + * Almost feature complete + * Missing entropy reseeding, and support for KMAC masking (when available) +* Mailbox + * [JTAG mailbox](jtagmbx.md) can be accessed through JTAG using a DM-TL bridge +* [OTBN](ot_otbn.md) +* [OTP controller](ot_otp.md) + * read and write features are supported, + * Present scrambling is supported with digest checks, + * ECC (detection and correction) is supported + * zero-ization is not yet supported +* [RISC-V Debug Module](jtag-dm.md) and Pulp Debug Module +* [ROM controller](ot_rom_ctrl.md) +* [SoC debug controller documentation](ot_soc_dbg_ctrl.md) +* SPI data flash (from QEMU upstream w/ fixes) +* [SPI Host controller](ot_spi_host.md) + * HW bus config is ignored (SPI mode, speed, ...) +* [SPI Device](ot_spi_device.md) +* Timer +* [UART](ot_uart.md) + * missing RX timeout, TX break not supported + * bitrate is not paced vs. selected baurate + +### Partially implemented devices + +Devices in this group implement subset(s) of the real HW. + +* Clock Manager + * Runtime-configurable device, through properties + * Manage clock dividers, groups, hints, software configurable clocks + * Propagate clock signals from source (AST, ...) to devices + * Hint management and measurement are not implemented +* DMA + * Only memory-to-memory transfers (inc. hashing) are supported, Handshake modes are not supported +* Entropy Src + * test/health features are not supported +* [GPIO](ot_gpio.md) + * A CharDev backend can be used to get GPIO outputs and update GPIO inputs +* [I2C controller](ot_i2c.md) + * Supports only one target mode address - ADDRESS1 and MASK1 are not implemented + * Timing features are not implemented + * Loopback mode is not implemented +* [Ibex wrapper](ot_ibex_wrapper.md) + * random source (connected to CSR), FPGA version, virtual remapper, fetch enable can be controlled + from Power Manager +* KMAC + * Masking is not supported +* Lifecycle controller + * [LC controller](lc_ctrl_dmi.md) can be accessed through JTAG using a DM-TL bridge + * Escalation is not supported +* [ROM controller](ot_rom_ctrl.md) +* SRAM controller + * Initialization and scrambling from OTP key supported + * Wait for init completion (bus stall) emulated +* SoC Proxy only supports IRQ routing/gating + +### Sparsely implemented devices + +In this group, device CSRs are supported (w/ partial or full access control & masking) but only some +features are implemented. + +* Analog Sensor Top + * noise source only (from host source) + * configurable clock sources +* Pinmux + * Basic features (pull up/down, open drain) are supported for GPIO pin only +* Power Manager + * Fast FSM is partially supported, Slow FSM is bypassed + * Interactions with other devices (such as the Reset Manager) are limited +* [Reset Manager](ot_rstmgr.md) + * HW and SW reset requests are supported + +### Dummy devices + +Devices in this group are mostly implemented with a RAM backend or real CSRs but do not implement +any useful feature (only allow guest test code to execute as expected). + +* Sensor + +### Additional devices + +* [DevProxy](devproxy.md) is a CharDev-enabled component that can be remotely controlled to enable + communication with the system-side buses of the mailboxes and DMA devices. A Python library is + available as `scripts/opentitan/devproxy.py` and provide an API to remote drive the devproxy + communication interface. + +## Running the virtual machine + +See [OpenTitan machine](ot_machine.md) documentation for options. + +### Arbitrary application + +````sh +qemu-system-riscv32 -M ot-darjeeling,no_epmp_cfg=true -display none -serial mon:stdio \ + -readconfig docs/config/opentitan/darjeeling.cfg \ + -global ot-ibex_wrapper.lc-ignore=on -kernel hello.elf +```` + +### Boot sequence ROM, ROM_EXT, BLO + +````sh +qemu-system-riscv32 -M ot-darjeeling -display none -serial mon:stdio \ + -readconfig docs/config/opentitan/darjeeling.cfg \ + -object ot-rom_img,id=rom,file=rom_with_fake_keys_fpga_cw310.elf \ + -drive if=pflash,file=otp-rma.raw,format=raw \ + -drive if=mtd,bus=0,file=flash.raw,format=raw +```` + +where `otp-rma.raw` contains the RMA OTP image and `flash.raw` contains the signed binary file of +the ROM_EXT and the BL0. See [`otptool.py`](otptool.md) and [`flashgen.py`](flashgen.md) tools to +generate the `.raw` image files. + +## Buses + +Darjeeling emulation supports the following buses: + +| **Type** | **Num** | **Usage** | +| -------- | ------- | -----------------------------------------------------------| +| `mtd` | 0 | [SPI host](ot_spi_host.md), [SPI device](ot_spi_device.md) | +| `pflash` | 0 | [OTP](ot_otp.md) | + +## Tools + +See [`tools.md`](tools.md) + +## Useful debugging options + +See [debug option](debug.md) for details. diff --git a/docs/opentitan/ot_earlgrey.md b/docs/opentitan/ot_earlgrey.md new file mode 100644 index 0000000000000..61489efd39ef5 --- /dev/null +++ b/docs/opentitan/ot_earlgrey.md @@ -0,0 +1,141 @@ +# EarlGrey + +## Supported version + +[Earlgrey 1.0.0](https://github.com/lowRISC/opentitan/tree/earlgrey_1.0.0) + +## Supported devices + +### Near feature-complete devices + +* [AES](ot_aes.md) +* Alert controller + * ping mechanism is not supported +* AON Timer +* CSRNG +* EDN +* [Flash controller](ot_flash.md) + * largely functional but without ECCs/ICVs, scrambling functionality & alerts + * no modelling of erase suspend or RMA entry + * lc_ctrl NVM debug signal not implemented, escalation partially implemented +* HMAC +* [IBEX CPU](ibex_cpu.md) +* [Key manager](ot_keymgr.md) + * Almost feature complete + * Missing entropy reseeding, and support for KMAC masking (when available) +* [OTBN](ot_otbn.md) +* [OTP controller](ot_otp.md) + * read and write features are supported, + * Present scrambling is supported with digest checks, + * ECC (detection and correction) is supported +* [ROM controller](ot_rom_ctrl.md) +* SPI data flash (from QEMU upstream w/ fixes) +* [SPI Host controller](ot_spi_host.md) + * HW bus config is ignored (SPI mode, speed, ...) +* [SPI Device](ot_spi_device.md) +* Timer +* [UART](ot_uart.md) + * missing RX timeout, TX break not supported + * bitrate is not paced vs. selected baurate + +### Partially implemented devices + +Devices in this group implement subset(s) of the real HW. + +* Clock Manager + * Runtime-configurable device, through properties + * Manage clock dividers, groups, hints, software configurable clocks + * Propagate clock signals from source (AST, ...) to devices + * Hint management and measurement are not implemented +* Entropy Src + * test/health features are not supported +* [I2C controller](ot_i2c.md) + * Supports only one target mode address - ADDRESS1 and MASK1 are not implemented + * Timing features are not implemented + * Loopback mode is not implemented +* [Ibex wrapper](ot_ibex_wrapper.md) + * random source (connected to CSR), FPGA version, virtual remapper, fetch enable can be controlled + from Power Manager +* KMAC + * Masking is not supported +* Lifecycle controller + * [LC controller](lc_ctrl_dmi.md) can be accessed through JTAG using a DM-TL bridge +* [ROM controller](ot_rom_ctrl.md) +* SRAM controller + * Initialization and scrambling from OTP key supported + * Wait for init completion (bus stall) emulated +* [USB Device](ot_usbdev.md) + +### Sparsely implemented devices + +In this group, device CSRs are supported (w/ partial or full access control & masking) but only some +features are implemented. + +* Analog Sensor Top + * noise source only (from host source) + * configurable clock sources +* [GPIO](ot_gpio.md) + * Connections with pinmux not implemented (need to be ported from [Darjeeling](ot_darjeeling.md) + version) +* Power Manager + * Fast FSM is partially supported, Slow FSM is bypassed + * Interactions with other devices (such as the Reset Manager) are limited +* [Reset Manager](ot_rstmgr.md) + * HW and SW reset requests are supported + +### Dummy devices + +Devices in this group are mostly implemented with a RAM backend or real CSRs but do not implement +any useful feature (only allow guest test code to execute as expected). +Some just use generic `UNIMP` devices to define a memory region. + +* Pattern Generator +* Pinmux +* PWM +* Sensor Control +* System Reset Controller + +## Running the virtual machine + +See [OpenTitan machine](ot_machine.md) documentation for options. + +### Arbitrary application + +````sh +qemu-system-riscv32 -M ot-earlgrey,no_epmp_cfg=true -display none -serial mon:stdio \ + -readconfig docs/config/opentitan/earlgrey.cfg \ + -global ot-ibex_wrapper.lc-ignore=on -kernel hello.elf +```` + +### Boot sequence ROM, ROM_EXT, BLO + +````sh +qemu-system-riscv32 -M ot-earlgrey -display none -serial mon:stdio \ + -readconfig docs/config/opentitan/earlgrey.cfg \ + -object ot-rom_img,id=rom,file=rom_with_fake_keys_fpga_cw310.elf \ + -drive if=pflash,file=otp-rma.raw,format=raw \ + -drive if=mtd,bus=2,file=flash.raw,format=raw +```` + +where `otp-rma.raw` contains the RMA OTP image and `flash.raw` contains the signed binary file of +the ROM_EXT and the BL0. See [`otptool.py`](otptool.md) and [`flashgen.py`](flashgen.md) tools to +generate the `.raw` image files. + +## Buses + +EarlGrey emulation supports the following buses: + +| **Type** | **Num** | **Usage** | +| -------- | ------- | -------------------------------------------------------------| +| `mtd` | 0 | [SPI host 0](ot_spi_host.md), [SPI device](ot_spi_device.md) | +| `mtd` | 1 | [SPI host 1](ot_spi_host.md) | +| `mtd` | 2 | [Embedded Flash](ot_flash.md) | +| `pflash` | 0 | [OTP](ot_otp.md) | + +## Tools + +See [`tools.md`](tools.md) + +## Useful debugging options + +See [debug option](debug.md) for details. diff --git a/docs/opentitan/ot_eg_pad_ring.md b/docs/opentitan/ot_eg_pad_ring.md new file mode 100644 index 0000000000000..8e7301a8a58dd --- /dev/null +++ b/docs/opentitan/ot_eg_pad_ring.md @@ -0,0 +1,33 @@ +# OpenTitan Earl Grey Pad Ring + +## Power-on Reset (POR) + +The Earl Grey Pad Ring currently only supports the negated Power-on Reset (PoR) pad, which can +be signalled by setting the `por_n` property and resetting the VM to perform a Power-on Reset. +This can for example be done with the QEMU Monitor via the following command sequence: +``` +> qom-set ot-eg-pad-ring.0 por_n low +> system_reset +> qom-set ot-eg-pad-ring.0 por_n high +``` + +Equivalent QMP JSON commands can also be used. + +Note that the current implementation directly invokes a reset request on any reset where a falling +edge is detected (i.e. the reset strapping is asserted), and it is not well supported to "hold" the +device in reset. If it is desired to emulate this time, you should stop and resume the VM for +for the duration of the reset, e.g.: +``` +/* Asserting the POR signal */ +> stop +> qom-set ot-eg-pad-ring.0 por_n low +> system_reset +/* ... wait for the duration of the reset ... */ +/* De-asserting the POR signal */ +> qom-set ot-eg-pad-ring.0 por_n high +> cont +``` + +## MIO Pads + +Currently, Earl Grey's MIO pads are not connected in the Pad Ring / Pinmux. diff --git a/docs/opentitan/ot_flash.md b/docs/opentitan/ot_flash.md new file mode 100644 index 0000000000000..0b223488832f6 --- /dev/null +++ b/docs/opentitan/ot_flash.md @@ -0,0 +1,20 @@ +# OpenTitan embedded flash + +* `-drive if=mtd,id=eflash,bus=,file=,format=raw` should be used to specify a path to + a QEMU RAW image file used as the OpenTitan internal flash controller image. This _RAW_ file + should have been generated with the [`flashgen.py`](flashgen.md) tool. + +## Machine specific options + +### EarlGrey Machine + +MTD bus 2 is assigned to the internal controller with the embedded flash storage, _i.e._ the command +line option should be written `-drive if=mtd,id=eflash,bus=2,file=,format=raw`. + +### Darjeeling Machine + +[Darjeeling](ot_darjeeling.md) machine does not support an embedded flash device. + +## Notes + +For external SPI data flash support, see [SPI host](ot_spi_host.md) documentation. diff --git a/docs/opentitan/ot_gpio.md b/docs/opentitan/ot_gpio.md new file mode 100644 index 0000000000000..67a68785d5130 --- /dev/null +++ b/docs/opentitan/ot_gpio.md @@ -0,0 +1,136 @@ +# OpenTitan GPIO + +## Foreword + +There are two variations of the GPIO device: one for EarlGrey, one for Darjeeling variants. + +In the following document, `OTMACHINE` should be defined as `eg` for EarlGrey machine or as `dj` +for Darjeeling one. + +## Options + +### Darjeeling machine + +When using multiple GPIO IPs, traces may become highly verbose, coming from multiple source. +It is possible to limit the trace to a single GPIO IP, using the following option: + +`-global ot-gpio-dj.log_id=` where _ot_id_ is the OpenTitan identifier of the GPIO device. + +When no `log_id` option is specified, all GPIO IP may emit trace messages. + +#### Note + +This option is not supported on the [EarlGrey](ot_earlgrey.md) machine, since only one GPIO device +instance is available. + +## Initial configuration + +It is possible to configure initial values of the GPIO pins: + +OpenTitan GPIO driver accept global options: + +- `ot-gpio-$OTMACHINE.in` defines the input values of the GPIO port as a 32-bit value +- `ot-gpio-$OTMACHINE.out` defines the output values of the GPIO port as a 32-bit value +- `ot-gpio-$OTMACHINE.oe` defines the output enable of the GPIO port as a 32-bit value + +All values default to zero, that is all GPIOs as input/hi-z, read 0. + +### Example + +``` +-global ot-gpio-dj.in=0x00ffff00 -global ot-gpio-dj.oe=0x1 -global ot-gpio-dj.out=0x1 +``` + +Note: If there were several GPIO instances in a machine, these configurations would apply to all +GPIO instances. + +## Character Device + +OpenTitan GPIO driver can be connected to any CharDev device to expose as a very simple ASCII data +stream the GPIO input and output pins. + +This CharDev device can be used to stimulate the GPIO and perform unit tests. + +To connect the GPIO to its optional character device, use the following QEMU option + +``` +-chardev type,id=gpio -global ot-gpio-$OTMACHINE.chardev=gpio +``` + +where type is one of the supported QEMU chardev type, such as + +- serial to connect to an existing serial communication device +- socket to use a TCP connection +- pipe to use a local pipe +- ... + +`id` is an arbitrary string that should always match the value defined with the `-global` option. + +## Protocol + +The communication protocol is ASCII based and very simple. +Each frame follows the following format: + +### Format + +``` + : +``` + +where: + +1. `TYPE` is a single uppercase char +2. `HEXVALUE` is a 32-bit hex encoded lowercase value +3. `CR` is the carriage return character (0x0d) +4. `LF` is the line feed character, or end-of-line (0x0a) + +Each frame is delimited with `LF` characters. `CR` are ignored and accepted to ease compatibility +with some terminals but are useless. + +The hex value represents the 32-bit GPIO values. + +#### Supported frame types + +A type describes the meaning of the hex value. Supported types are: + +* `C` clear/reset host (at QEMU start up) +* `D` direction, _i.e._ Output Enable in OpenTitan terminology (QEMU -> host) +* `I` input GPIO values (host -> QEMU) +* `M` mask GPIO input values, i.e. non connected input pins (host -> QEMU) +* `O` output GPIO values (QEMU -> host) +* `P` output GPIO pull up/down values (QEMU -> host) +* `Q` query input (QEMU -> host). QEMU may emit this frame, so that the host replies with a new + input `I` frame (hexvalue of `Q` is ignored) +* `R` repeat (host -> QEMU). The host may ask QEMU to repeat the last `D` and `O` frames +* `Y` forwards the GPIO input that QEMU has read to the host (QEMU -> host) for tracking purposes +* `Z` output pull up/down GPIO HiZ values (QEMU -> host). 1 means HiZ, 0 means + pull up/down values apply. + +Frames are only emitted whenever the state of either peer (QEMU, host) change. QEMU should emit `D` +and `O` frames whenever the GPIO configuration or output values are updated. The host should emit +a `I` frame whenever its own input lines change. + +`Q` and `R` are only emitted when a host connects to QEMU or when one side resets its internal +state. + +### Example + +The `scripts/opentitan/trellis` directory contains two Python files that may be copied to an +an Adafruit NeoTrellis M4 Express card initialized with Circuit Python 8.0+ + +These scripts provide a physical, visual interface to the virtual GPIO pins, which is connected to +the QEMU machine over a serial port (a USB CDC VCP in this case). + +To connect to the NeoTrellis board, use a configuration such as: + +``` +-chardev serial,id=gpio,path=$SERIALDEV -global ot-gpio-$OTMACHINE.chardev=gpio +``` + +where `SERIALDEV` is the data serial port of the Neotreillis board, _e.g._ `/dev/ttyACM1` + +Note: the first serial port of the board is reserved to its debug console. + +### Testing + +See the [gpiodev.py](gpiodev.md) script. diff --git a/docs/opentitan/ot_i2c.md b/docs/opentitan/ot_i2c.md new file mode 100644 index 0000000000000..5013a508da905 --- /dev/null +++ b/docs/opentitan/ot_i2c.md @@ -0,0 +1,8 @@ +# OpenTitan I2C + +* `-device ,bus=,address=
` can be used to attach devices at a specific address + to one of the three I2C buses. + + Bus names depends on the platform. They are usually named as `ot-i2c0` ... `ot-i2cN`. + +See also [I2C host proxy](i2c_host_proxy.md) diff --git a/docs/opentitan/ot_ibex_wrapper.md b/docs/opentitan/ot_ibex_wrapper.md new file mode 100644 index 0000000000000..8a45fab0741c3 --- /dev/null +++ b/docs/opentitan/ot_ibex_wrapper.md @@ -0,0 +1,39 @@ +# OpenTitan Ibex Wrapper + +* `-global ot-ibex_wrapper.lc-ignore=on` should be used whenever no OTP image is provided, or if + the current LifeCycle state stored in the OTP image does not allow the Ibex core to fetch data. + This switch forces the Ibex core to execute whatever the LifeCycle broadcasted signal, which + departs from the HW behavior but maybe helpful to run the machine without a full OTP set up. The + alternative to allow the Ibex core to execute guest code is to provide a valid OTP image with one + of the expected LifeCycle state, such as TestUnlock*, Dev, Prod or RMA. + +* `-global ot-ibex_wrapper.lc-ignore-ids=` acts as `lc-ignore`, enabling the selection of + specific ibex wrapper instance based on their unique identifiers. See `ot_id` property in the + machine definition file for a list of valid identifiers. `` should be defined as a comma- + separated list of valid identifiers. It is only possible to ignore LifeCycle states with this + option, not to enforce them. + +The `FPGA_INFO` register of the Ibex Wrapper device is used to report that the HW platform is a QEMU +virtual machine. It contains three ASCII chars `QMU` followed with a configurable _version_ field in +the MSB, whose meaning is not defined. It can be any 8-byte value, and defaults to 0x0. To configure +this version field, use the `qemu_version` property of the Ibex Wrapper device. + +The `DV_SIM_STATUS` register (address 0 of the `DV_SIM_WINDOW`) can be used to exit QEMU with a +passing or failing status code. The lower half of the word is written either `900d` (good) or `baad` +(bad). If an error code is written to the upper half of the word, QEMU will exit with that status +code for failures. The `-global ot-ibex_wrapper.dv-sim-status-exit=[on|off]` can be used to control +whether QEMU shuts down when this register is written. + +There are two modes to handle address remapping, with different limitations: + +- default mode: use an MMU-like implementation (via ot_vmapper) to remap addresses. This mode + enables to remap instruction accesses and data accesses independently, as the real HW. However, + due to QEMU limitations, addresses and mapped region sizes should be aligned and multiple of 4096 + bytes, i.e. a standard MMU page size. This is the recommended mode. + +- legacy mode: This mode has no address nor size limitations, however it cannot distinguish + instruction accesses from data accesses, which means that both kind of accesses must be defined + for each active remapping slot for the remapping to be enabled. Moreover it relies on MemoryRegion + aliasing and may not be as robust as the default mode. It is recommended to use the default mode + whenever possible. To enable this legacy mode, set the `alias-mode` property to true: + `-global ot-ibex_wrapper.alias-mode=true` diff --git a/docs/opentitan/ot_keymgr.md b/docs/opentitan/ot_keymgr.md new file mode 100644 index 0000000000000..f36eb1b8aa7bb --- /dev/null +++ b/docs/opentitan/ot_keymgr.md @@ -0,0 +1,14 @@ +# OpenTitan Key Manager support + +## Properties + +- `-global ot-keymgr.disable-flash-seed-check=true` can be used to disable the +data validity check in the Keymgr for loaded flash secrets (the owner and +creator seed). This validity check ensures that the loaded key is not all-zero +or all-one (and thus probably uninitialized). When emulating OpenTitan, it may +be useful to be able to advance using uninitialized keys due to a lack of flash +info splicing, to bypass the need to run through an entire provisioning flow. + - Note also that the fatal Keymgr alert caused by failing this check should + not appear for unprovisioned flash if flash scrambling is implemented (and + enabled). This is because the garbage unscrambled data that is read will not + pass this check. diff --git a/docs/opentitan/ot_machine.md b/docs/opentitan/ot_machine.md new file mode 100644 index 0000000000000..146a9a3be9079 --- /dev/null +++ b/docs/opentitan/ot_machine.md @@ -0,0 +1,24 @@ +# OpenTitan machine + +### Machine configuration + +To start up an OpenTitan virtual machine, a configuration file is required. See [OT config](otcfg.md) +documentation for details. The configuration file should be specified with the `-readconfig` option. + +### `-M ` optional arguments + +* `no_epmp_cfg=true` can be appended to the machine option switch, _i.e._ + `-M ot-earlgrey,no_epmp_cfg=true` to disable the initial ePMP configuration, which can be very + useful to execute arbitrary code on the Ibex core without requiring an OT ROM image to boot up. + +* `ignore_elf_entry=true` can be appended to the machine option switch, _i.e._ + `-M ot-earlgrey,ignore_elf_entry=true` to prevent the ELF entry point of a loaded application to + update the vCPU reset vector at startup. When this option is used, with `-kernel` option for + example, the application is loaded in memory but the default machine reset vector is used. + +* `verilator=true` can be appended to the machine option switch, to select Verilator lowered clocks: + _i.e._ `-M ot-earlgrey,verilator=true` to select Verilator reduced clock rates. It enables to run + FW which has been built for the Verilator target in OpenTitan main repository. + +See also [ot-ibex_wrapper.lc-ignore option](ot_ibex_wrapper.md) to enable VM start up without a +valid [OTP](ot_otp.md) image file. diff --git a/docs/opentitan/ot_otbn.md b/docs/opentitan/ot_otbn.md new file mode 100644 index 0000000000000..f06ee1683392c --- /dev/null +++ b/docs/opentitan/ot_otbn.md @@ -0,0 +1,9 @@ +# OpenTitan OTBN + +* `-global ot-otbn.logfile=` output OTBN execution message to the specified logfile. When + _logasm_ option (see below) is not enabled, only execution termination and error messages are + logged. `stderr` can be used to log the messages to the standard error stream instead of a file. + +* `-global ot-otbn.logasm=` dumps executed instructions on OTBN core into the _logfile_ + filename. Beware that this further slows down execution speed, which could likely result in the + guest application on the Ibex core to time out. diff --git a/docs/opentitan/ot_otp.md b/docs/opentitan/ot_otp.md new file mode 100644 index 0000000000000..0b03a138a0faf --- /dev/null +++ b/docs/opentitan/ot_otp.md @@ -0,0 +1,10 @@ +# OpenTitan OTP + +* `-drive if=pflash,file=otp.raw,format=raw` should be used to specify a path to a QEMU RAW image + file used as the OpenTitan OTP image. This _RAW_ file should have been generated with the + [`otptool.py`](otptool.md) tool. + +* on LC escalate reception, it is possible to early abort VM execution. Specify + `-global ot-otp-.fatal_escalate=true` to enable this feature. + where `top` should be defined as `eg` for the [EarlGrey](ot_earlgrey.md) machine, or `dj` for the + [Darjeeling](ot_darjeeling.md) machine. diff --git a/docs/opentitan/ot_rom_ctrl.md b/docs/opentitan/ot_rom_ctrl.md new file mode 100644 index 0000000000000..fc7d36c19bb5f --- /dev/null +++ b/docs/opentitan/ot_rom_ctrl.md @@ -0,0 +1,101 @@ +# OpenTitan ROM Controller + +The OpenTitan ROM Controller computes a digest of the ROM content on boot, using one of the +application interfaces of the KMAC. When the ROM check is complete, the ROM Controller sends a +signal to the Power Manager. This triggers CPU startup if the ROM check was successful. + +On real hardware, the ROM digest is computed on the ROM content _including ECC_ and the expected +digest value is stored at the end of the scrambled ROM (with invalid ECC to make sure it's not +accessible by software). + +On the QEMU emulated ROM Controller, if the ROM controller is submitted a scrambled ROM image file, +its digest is used and verified. Otherwise, a fake digest is generated, i.e. no actual ROM content +validation is performed. + +## QEMU ROM Options + +### Supported image file formats + +The type of a ROM image file format is automatically detected from its content. + +The ROM file digest can be provided on the QEMU command line using this option: + +``` +-object ot-rom_img,id=,file=/path/to/rom +``` + +#### ELF ROM file + +ELF32 RISC-V file can be used as a ROM controller image file. Such file format neither supports +digest nor ECC. Digest is faked in this case. + +#### VMEM ROM file + +Two kinds of VMEM files are supported: + +* 32-bit VMEM files, _i.e._ VMEM file without ECC. Such file should contain non-scrambled data. +* 39-bit VMEM files, _i.e._ VMEM file with 7-bit ECC and scrambled data. + +39-bit scrambled ECC VMEM files are only supported when the QEMU machine has instantiated the ROM +controller with `key` and `nonce` arguments. + +#### HEX ROM file + +39-bit HEX files, _i.e._ HEX file with 7-bit ECC and scrambled data. + +39-bit scrambled ECC HEX files are only supported when the QEMU machine has instantiated the ROM +controller with `key` and `nonce` arguments. + +Note that HEX file format differs from IHEX file format. The former only contains hexadecimal- +encoded data, where the two first digits contain the 7-bit ECC value, and the remaining digits +contains the 32-bit data value. + +#### Binary ROM file + +Flat, raw binary file can be used as a ROM controller image file. Such file format neither supports +digest nor ECC. Digest is faked in this case. + +### ROM identifiers [#romid] + +The ROM image ID may depend on the SoC. + +* for EarlGrey which has a single ROM, the ID is expected to be `rom`. +* for a SoC with two ROMs, the IDs would be expected to be `rom0` and `rom1`. +* for a machine with multiple SoCs, the IDs would be additionally prefixed with the SoC name and a + full stop, _e.g._ `soc0.rom0` + +### ROM unscrambling constants + +Each machine and each ROM controller uses a different pair of (key, nonce) constants to unscramble +the ROM content. + +These constants may be defined in the machine, or in an external configuration file so that these +constants are present neither in the QEMU source code nor the QEMU binary. The standard QEMU +`-readconfig ` may be used to load those constants. + +See [OpenTitan configuration file](otcfg.md) for details. + +## Booting with and without ROM + +### With ROM + +This mode is useful to implement the standard OpenTitan boot flow. + +In this mode, the ROM digest is always checked against the expected digest value if a scrambled ROM +image file is submitted. + +### Without ROM + +This mode is useful when starting an application using `-kernel` QEMU option: the application starts +directly at CPU reset. + +If no ROM image is specified on QEMU command line (or if the ROM image does not have the expected ID +for the machine), it does not prevent the CPU from starting: +- ROM Controller provides an empty ROM region +- ROM Controller activates _fake digest_ mode: digest is computed on the empty ROM region and + expected digest value is faked. ROM is "valid" so signal is sent to Power Manager to start the +CPU. + +When the `-kernel` option is used, the default `resetvec` of the machine is overridden with the +entry point defined in the ELF file. The default `mtvec` is not modified - it is expected the ELF +early code updates the `mtvec` with a reachable location if no ROM is used. diff --git a/docs/opentitan/ot_rstmgr.md b/docs/opentitan/ot_rstmgr.md new file mode 100644 index 0000000000000..b825c8c9b7741 --- /dev/null +++ b/docs/opentitan/ot_rstmgr.md @@ -0,0 +1,9 @@ +# OpenTitan Reset Manager + +It is possible to limit the number of times the VM reboots the guest. This option may be useful +during the development process when an issue in the early FW stages - such as the ROM - causes an +endless reboot cycles of the guest. + +To limit the reboot cycles, use the `-global ot-rstmgr.fatal_reset=` option, where `N` is an +unsigned integer. This option forces the QEMU VM to exit the N^th^ time the reset manager receives +a reset request, rather than rebooting the whole machine endlessly as the default behavior. diff --git a/docs/opentitan/ot_soc_dbg_ctrl.md b/docs/opentitan/ot_soc_dbg_ctrl.md new file mode 100644 index 0000000000000..e0fbcc1b1aa05 --- /dev/null +++ b/docs/opentitan/ot_soc_dbg_ctrl.md @@ -0,0 +1,11 @@ +# OpenTitan SoC Debug controller + +SoC debug controller manages SoC debug policies based on external signals - such as GPIO, Power +Manager states and LifeCycle states, the later being defined from the OTP content. If no OTP image +is provided, or a RAW (blank) image is provided, or if the OTP image defines a LifeCycle in any of +TEST* or RMA states, a Darjeeling machine that features a SoC Debug controller may enter the DFT +("Debug For Test") execution mode, where the Ibex core may not resume execution till a JTAG debugger +triggers it. + +To force QEMU to execute an application despite this feature, bypassing the DFT mode, use +`-global ot-socdbg_ctrl.dft-ignore=on` QEMU option. diff --git a/docs/opentitan/ot_spi_device.md b/docs/opentitan/ot_spi_device.md new file mode 100644 index 0000000000000..e5907a639b0fe --- /dev/null +++ b/docs/opentitan/ot_spi_device.md @@ -0,0 +1,131 @@ +# OpenTitan SPI Device + +## Supported modes + +### Flash mode + +This mode is fully supported (to the extent of the understanding of the HW...). + +### Passthrough mode + +This mode is supported, with minor deficiencies: The two-stage read pipeline, and dummy cycle +counts of 1 to 7 are not supported as SPI transfers are modelled with byte-granularity (see the +[CharDev protocol](#spi-device-chardev-protocol)). For commands with 1 to 7 dummy cycles specified, +the maximum 8 dummy cycles (1 dummy byte) will be used. + +### TPM + +This mode is partially supported, TPM commands handled by hw are not supported yet. The CharDev +protocol doesn't support a distinct chip select for TPM, therefore it is sharing the same CS with +the other modes. If CS is asserted and TPM is enabled, the TPM will have priority. + +## Connection with a SPI Host + +### CharDev simple bus header + +To communicate with the SPI device emulation, it is possible to create and use any QEMU CharDev. + +The CharDev is expected to used as a full bi-directional, stream based, asynchronous communication +channel. + +CharDev always output as many payload bytes as it receives, like a regular SPI bus. + +### Creating QEMU SPI Device CharDev + +The most common/useful CharDev for SPI device is to use a TCP communication stream, which can be +instantiated this way from the command line: + +```` +-chardev socket,id=spidev,host=localhost,port=8004,server=on,wait=off +```` + +Note that `opentitantool` and association library do support this protocol when the `qemu` backend +is used: + +```` +opentitantool --interface qemu --help +A tool for interacting with OpenTitan chips. + +Usage: opentitantool [OPTIONS] + +Commands: + ... + spi Commands for interacting with a SPI EEPROM + ... + --qemu-host [default: localhost] + --qemu-spidev-port [default: 8004] +```` + +IT is also possible to use [`spidevflash.py`](spidevflash.md) tool to upload a binary using the same +protocol. + +### SPI Device CharDev protocol + +SPI clock is not emulated, but each byte exchanged over the communication channel represent 8-bit +SPI data. Dual and Quad lines are not emulated, all communications happen over a regular byte +stream - which does not prevent from using dual or quad SPI flash commands. + +As some out-of-band or metadata is required, for example the status of the /CS line, a small header +is inserted by the SPI host for each SPI communication packet in the MOSI stream. There is no such +header inserted into the stream for the MISO channel. Moreover as the SPI header contains the length +of the payload, it also acts as a SPI packet delimiter. Each time the SPI host peer is disconnected, +the /CS line and the SPI device state machine are reset so it is possible to recover from a +corrupted communication w/o exiting QEMU. + +A packet is limited to a payload of 65536 bytes, however an SPI transaction may extend over several +SPI communication packets. + +A CharDev communication is always initiated by the remote SPI host. It transmits the 8-byte SPI +device header described below, where the first word can be considered as a magic number, and +the last word describes the payload that comes right after the header. The payload should contain +the count of bytes (SPI MOSI data). The SPI device should start receiving the very same + count of bytes (SPI MISO data). In other words, the payload in both direction have always +the same amount of bytes. + +The host-to-device stream always starts with a 8-byte header, and there is no header, i.e. only +payload in the device-to-host stream. The header bit defines whether the host releases the /CS +line once the SPI transfer is over, or whether the SPI device should expect a continuation SPI +transfer, which is always prefixed with another header. The last SPI transfer of a SPI transaction +should release the /CS line, i.e. should be 0. + +```` + Time --> + + MOSI: | Header1 | TX Payload1 | Header2 | TX Payload2 | + \ \ \ \ + MISO: | RX Payload1 | | RX Payload2 | +```` + +```` + +---------------+---------------+---------------+---------------+ + | 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| + 0x00 -+---------------+---------------+---------------+---------------+ + | '/' | 'C' | 'S' | version | + 0x04 -+---------------+---------------+---------------+---------------+ + |p|a|t|r| - |c| - | length (Byte count) | + 0x08 -+---------------+---------------+---------------+---------------+ + | | | | | + 0x0c -+---------------+---------------+---------------+---------------+ + | . . . | + -+---------------+---------------+---------------+---------------+ + | | + -+---------------+ +```` + + - `version`: protocol version, current version uses 0. + - `length`: count of payload bytes (does not need to be a multiple of 4) + - `p`: polarity, should match `CFG.CPOL` (not yet supported) + - `a`: phase, should match `CFG.CPHA` (not yet supported) + - `t`: tx order, see `CFG.TX_ORDER` (not yet supported) + - `r`: rx order, see `CFG.RX_ORDER` (not yet supported) + - `c`: whether to keep _/CS_ low (=1) or release _/CS_ (=0) when payload has been processed. Any + SPI transaction should end with C=0 packet. However it is possible to use several SPI device + CharDev packets to handle a single SPI transaction: example: JEDEC ID w/ continuation code, + polling for BUSY bit, ... + +Note that SPI device support bit reversing for both TX and RX, the bit configuration in the SPI +device header is only used for debugging (tracking configuration mismatch between the host and the +device). Polarity and Phase are not emulated however SPI stream is explicitly corrupted (bit +inversion) if CPOL/CPHA do not match between host and device. diff --git a/docs/opentitan/ot_spi_host.md b/docs/opentitan/ot_spi_host.md new file mode 100644 index 0000000000000..569e18703bafb --- /dev/null +++ b/docs/opentitan/ot_spi_host.md @@ -0,0 +1,44 @@ +# OpenTitan SPI Host + +* `-drive if=mtd,bus=,file=,format=raw` should be used to specify a path to a QEMU RAW + image file used as the SPI data flash backend file. This _RAW_ file should have been created with + the qemu-img tool. There is no dedicated tool to populate this image file for now. + + ````sh + qemu-img create -f raw spi.raw 16M + ```` + + See the machine section for supported `` values. + +* `-global ot-spi_host.start-delay=