From db023c81a5196ac2de74c721b5962ac7708c13b6 Mon Sep 17 00:00:00 2001 From: Diego Nehab <1635557+diegonehab@users.noreply.github.com> Date: Sat, 22 Feb 2025 20:57:23 +0000 Subject: [PATCH] test commit --- .gitattributes | 1 + src/Makefile | 65 +- ...ge-descr.h => address-range-description.h} | 12 +- src/address-range.h | 392 ++++++++ ...shadow-uarch-state.cpp => assert-printf.h} | 19 +- src/{clint.cpp => clint-address-range.cpp} | 30 +- src/clint-address-range.h | 70 ++ src/clint-factory.cpp | 32 - src/clint-factory.h | 34 - src/clint.h | 50 - src/clua-i-virtual-machine.cpp | 8 +- src/dump.h | 10 +- src/find-pma-entry.h | 37 +- src/{htif.cpp => htif-address-range.cpp} | 17 +- src/htif-address-range.h | 72 ++ src/{htif.h => htif-constants.h} | 25 +- src/htif-factory.cpp | 32 - src/htif-factory.h | 31 - src/i-state-access.h | 17 +- src/i-virtual-machine.h | 8 +- src/interpret.cpp | 30 +- src/json-util.cpp | 20 +- src/json-util.h | 24 +- src/jsonrpc-discover.json | 18 +- src/jsonrpc-machine-c-api.cpp | 1 - src/jsonrpc-remote-machine.cpp | 8 +- src/jsonrpc-virtual-machine.cpp | 8 +- src/jsonrpc-virtual-machine.h | 4 +- src/machine-c-api.cpp | 25 +- src/machine-c-api.h | 6 +- src/machine-config.cpp | 1 - src/machine-reg.h | 4 +- src/machine-state.h | 17 +- src/machine.cpp | 872 ++++++++++-------- src/machine.h | 255 ++--- src/memory-address-range.cpp | 90 ++ src/memory-address-range.h | 143 +++ src/meta.h | 6 + src/mock-address-range.h | 108 +++ src/mock-pma-entry.h | 169 ---- src/{plic.cpp => plic-address-range.cpp} | 35 +- src/plic-address-range.h | 80 ++ src/{plic.h => plic-constants.h} | 19 +- src/plic-factory.cpp | 32 - src/plic-factory.h | 34 - src/pma-constants.h | 67 +- src/pma-driver.cpp | 35 - src/pma-driver.h | 67 -- src/pma.cpp | 220 ----- src/pma.h | 603 ++---------- src/pristine-address-range.h | 59 ++ src/record-send-cmio-state-access.h | 22 +- src/record-step-state-access.h | 16 +- src/replay-send-cmio-state-access.h | 7 - src/replay-step-state-access.h | 30 +- src/riscv-constants.h | 65 +- src/shadow-peek.h | 19 +- ...actory.cpp => shadow-pmas-address-range.h} | 25 +- src/shadow-pmas-factory.h | 49 - src/shadow-pmas.h | 2 - ...ory.cpp => shadow-state-address-range.cpp} | 24 +- src/shadow-state-address-range.h | 72 ++ src/shadow-state-factory.h | 37 - src/shadow-state.cpp | 25 - src/shadow-state.h | 9 +- ...ctory.cpp => shadow-tlb-address-range.cpp} | 25 +- src/shadow-tlb-address-range.h | 72 ++ src/shadow-tlb-factory.h | 2 - src/shadow-tlb.cpp | 25 - src/shadow-tlb.h | 3 - ...p => shadow-uarch-state-address-range.cpp} | 23 +- src/shadow-uarch-state-address-range.h | 74 ++ src/shadow-uarch-state-factory.h | 37 - src/shadow-uarch-state.h | 6 +- src/state-access.h | 12 +- src/tlb.h | 1 - src/translate-virtual-address.h | 8 +- src/uarch-record-state-access.h | 2 - src/uarch-replay-state-access.h | 1 - src/uarch-state-access.h | 23 +- src/uarch-state.h | 8 +- src/unique-c-ptr.h | 10 +- ...io-device.cpp => virtio-address-range.cpp} | 82 +- ...virtio-device.h => virtio-address-range.h} | 56 +- ...e.cpp => virtio-console-address-range.cpp} | 29 +- ...nsole.h => virtio-console-address-range.h} | 23 +- src/virtio-factory.cpp | 36 - src/virtio-factory.h | 37 - ...o-net.cpp => virtio-net-address-range.cpp} | 37 +- ...irtio-net.h => virtio-net-address-range.h} | 74 +- src/virtio-net-carrier-tuntap.h | 60 -- ...pp => virtio-net-tuntap-address-range.cpp} | 20 +- src/virtio-net-tuntap-address-range.h | 67 ++ ....cpp => virtio-net-user-address-range.cpp} | 29 +- ...lirp.h => virtio-net-user-address-range.h} | 39 +- ...p9fs.cpp => virtio-p9fs-address-range.cpp} | 71 +- ...tio-p9fs.h => virtio-p9fs-address-range.h} | 30 +- src/virtio-serializer.h | 2 +- src/virtual-machine.cpp | 6 +- src/virtual-machine.h | 4 +- tests/lua/cartesi/tests/util.lua | 2 +- tests/lua/create-machines.lua | 6 +- tests/lua/machine-bind.lua | 26 +- tests/lua/machine-test.lua | 20 +- tests/misc/Makefile | 14 +- tests/misc/test-machine-c-api.cpp | 15 +- tests/misc/test-utils.h | 4 +- uarch/Makefile | 14 +- uarch/machine-uarch-bridge-state-access.h | 26 +- uarch/uarch-run.cpp | 6 +- uarch/uarch-runtime.cpp | 4 + uarch/uarch-runtime.h | 18 +- 112 files changed, 2713 insertions(+), 2900 deletions(-) create mode 100644 .gitattributes rename src/{machine-memory-range-descr.h => address-range-description.h} (74%) create mode 100644 src/address-range.h rename src/{shadow-uarch-state.cpp => assert-printf.h} (76%) rename src/{clint.cpp => clint-address-range.cpp} (78%) create mode 100644 src/clint-address-range.h delete mode 100644 src/clint-factory.cpp delete mode 100644 src/clint-factory.h delete mode 100644 src/clint.h rename src/{htif.cpp => htif-address-range.cpp} (91%) create mode 100644 src/htif-address-range.h rename src/{htif.h => htif-constants.h} (81%) delete mode 100644 src/htif-factory.cpp delete mode 100644 src/htif-factory.h create mode 100644 src/memory-address-range.cpp create mode 100644 src/memory-address-range.h create mode 100644 src/mock-address-range.h delete mode 100644 src/mock-pma-entry.h rename src/{plic.cpp => plic-address-range.cpp} (91%) create mode 100644 src/plic-address-range.h rename src/{plic.h => plic-constants.h} (86%) delete mode 100644 src/plic-factory.cpp delete mode 100644 src/plic-factory.h delete mode 100644 src/pma-driver.cpp delete mode 100644 src/pma-driver.h delete mode 100644 src/pma.cpp create mode 100644 src/pristine-address-range.h rename src/{shadow-pmas-factory.cpp => shadow-pmas-address-range.h} (63%) delete mode 100644 src/shadow-pmas-factory.h rename src/{shadow-state-factory.cpp => shadow-state-address-range.cpp} (63%) create mode 100644 src/shadow-state-address-range.h delete mode 100644 src/shadow-state-factory.h delete mode 100644 src/shadow-state.cpp rename src/{shadow-tlb-factory.cpp => shadow-tlb-address-range.cpp} (64%) create mode 100644 src/shadow-tlb-address-range.h delete mode 100644 src/shadow-tlb.cpp rename src/{shadow-uarch-state-factory.cpp => shadow-uarch-state-address-range.cpp} (66%) create mode 100644 src/shadow-uarch-state-address-range.h delete mode 100644 src/shadow-uarch-state-factory.h rename src/{virtio-device.cpp => virtio-address-range.cpp} (91%) rename src/{virtio-device.h => virtio-address-range.h} (93%) rename src/{virtio-console.cpp => virtio-console-address-range.cpp} (82%) rename src/{virtio-console.h => virtio-console-address-range.h} (76%) delete mode 100644 src/virtio-factory.cpp delete mode 100644 src/virtio-factory.h rename src/{virtio-net.cpp => virtio-net-address-range.cpp} (77%) rename src/{virtio-net.h => virtio-net-address-range.h} (73%) delete mode 100644 src/virtio-net-carrier-tuntap.h rename src/{virtio-net-carrier-tuntap.cpp => virtio-net-tuntap-address-range.cpp} (90%) create mode 100644 src/virtio-net-tuntap-address-range.h rename src/{virtio-net-carrier-slirp.cpp => virtio-net-user-address-range.cpp} (92%) rename src/{virtio-net-carrier-slirp.h => virtio-net-user-address-range.h} (61%) rename src/{virtio-p9fs.cpp => virtio-p9fs-address-range.cpp} (96%) rename src/{virtio-p9fs.h => virtio-p9fs-address-range.h} (94%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..d58d5c6c6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +third-party/** linguist-vendored diff --git a/src/Makefile b/src/Makefile index f69dcb85a..f0e818cde 100644 --- a/src/Makefile +++ b/src/Makefile @@ -133,8 +133,8 @@ ifeq ($(slirp),yes) # Workaround for building with macports lua-luarocks installation machine.o: INCS+=$(SLIRP_INC) machine.clang-tidy: INCS+=$(SLIRP_INC) -virtio-net-carrier-slirp.o: INCS+=$(SLIRP_INC) -virtio-net-carrier-slirp.clang-tidy: INCS+=$(SLIRP_INC) +virtio-net-user-address-range.o: INCS+=$(SLIRP_INC) +virtio-net-user-address-range.clang-tidy: INCS+=$(SLIRP_INC) #INCS+=$(SLIRP_INC) LIBCARTESI_COMMON_LIBS+=$(SLIRP_LIB) else @@ -345,48 +345,39 @@ c-api: $(LIBCARTESI) $(LIBCARTESI_MERKLE_TREE) $(LIBCARTESI_JSONRPC) .PHONY: all generate use clean lint format format-lua check-format check-format-lua luacartesi hash c-api compile_flags.txt LIBCARTESI_OBJS:= \ - pma-driver.o \ - clint.o \ - clint-factory.o \ - plic.o \ - plic-factory.o \ - virtio-factory.o \ - virtio-device.o \ - virtio-console.o \ - virtio-p9fs.o \ - virtio-net.o \ - virtio-net-carrier-tuntap.o \ - virtio-net-carrier-slirp.o \ - dtb.o \ - os.o \ - htif.o \ - htif-factory.o \ - shadow-state.o \ - shadow-state-factory.o \ - shadow-pmas-factory.o \ - shadow-tlb.o \ - shadow-tlb-factory.o \ - shadow-uarch-state.o \ - shadow-uarch-state-factory.o \ - pma.o \ - machine.o \ - machine-config.o \ - json-util.o \ base64.o \ + clint-address-range.o \ + dtb.o \ + htif-address-range.o \ interpret.o \ - virtual-machine.o \ - sha3.o \ + json-util.o \ + machine-c-api.o \ + machine-config.o \ machine-merkle-tree.o \ + machine.o \ + memory-address-range.o \ + os.o \ + plic-address-range.o \ pristine-merkle-tree.o \ - machine-c-api.o \ + replay-step-state-access-interop.o \ + send-cmio-response.o \ + sha3.o \ + shadow-state-address-range.o \ + shadow-tlb-address-range.o \ + shadow-uarch-state-address-range.o \ + uarch-pristine-hash.o \ uarch-pristine-ram.o \ uarch-pristine-state-hash.o \ - uarch-pristine-hash.o \ - uarch-interpret.o \ - uarch-step.o \ uarch-reset-state.o \ - send-cmio-response.o \ - replay-step-state-access-interop.o + uarch-step.o \ + virtual-machine.o \ + uarch-interpret.o \ + virtio-address-range.o \ + virtio-console-address-range.o \ + virtio-p9fs-address-range.o \ + virtio-net-address-range.o \ + virtio-net-tuntap-address-range.o \ + virtio-net-user-address-range.o CARTESI_CLUA_OBJS:= \ clua.o \ diff --git a/src/machine-memory-range-descr.h b/src/address-range-description.h similarity index 74% rename from src/machine-memory-range-descr.h rename to src/address-range-description.h index 787275b9d..325425b98 100644 --- a/src/machine-memory-range-descr.h +++ b/src/address-range-description.h @@ -14,8 +14,8 @@ // with this program (see COPYING). If not, see . // -#ifndef MACHINE_MEMORY_RANGE_DESCR_H -#define MACHINE_MEMORY_RANGE_DESCR_H +#ifndef ADDRESS_RANGE_DESCRIPTION_H +#define ADDRESS_RANGE_DESCRIPTION_H #include #include @@ -23,15 +23,15 @@ namespace cartesi { -/// \brief Description of memory range used for introspection (i.e., get_memory_ranges()) -struct machine_memory_range_descr { +/// \brief Description of an address range used for introspection (i.e., get_address_ranges()) +struct address_range_description { uint64_t start = 0; ///< Start of memory range uint64_t length = 0; ///< Length of memory range std::string description; ///< User-friendly description for memory range }; -/// \brief List of memory range descriptions used for introspection (i.e., get_memory_ranges()) -using machine_memory_range_descrs = std::vector; +/// \brief List of address range descriptions used for introspection (i.e., get_address_ranges()) +using address_range_descriptions = std::vector; } // namespace cartesi diff --git a/src/address-range.h b/src/address-range.h new file mode 100644 index 000000000..0500f0ec9 --- /dev/null +++ b/src/address-range.h @@ -0,0 +1,392 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef ADDRESS_RANGE_H +#define ADDRESS_RANGE_H + +#include +#include +#include +#include +#include + +#include "assert-printf.h" +#include "i-device-state-access.h" +#include "interpret.h" +#include "pma.h" + +namespace cartesi { + +// Forward declarations +class machine; + +/// \file +/// \brief Physical address range + +/// \brief Physical Address Range. +/// \details The target's physical address layout is described by an array of specializations of such ranges. +class address_range { + + std::array m_description; ///< Description of address range for use in error messages. + uint64_t m_start; ///< Target physical address where range starts. + uint64_t m_length; ///< Length of range, in bytes. + uint64_t m_length_bit_ceil; ///< Smallest power of 2 that is not smaller than length, in bytes. + pma_flags m_flags; ///< Physical memory attribute flags for range. + +public: + /// \brief Noexcept constexpr constructor for empty ranges with description + /// \detail Can be used to initialize a constexpr empty range + template + explicit constexpr address_range(const char (&description)[N]) noexcept : + m_description{}, + m_start{0}, + m_length{0}, + m_length_bit_ceil{0}, + m_flags{} { + m_flags.E = true; + for (unsigned i = 0; i < std::min(N, m_description.size() - 1); ++i) { + m_description[i] = description[i]; + } + } + + address_range(const address_range &other) = default; + address_range &operator=(const address_range &other) = default; + address_range(address_range &&other) = default; + address_range &operator=(address_range &&other) = default; + constexpr virtual ~address_range() = default; + + template + [[noreturn]] static void ABRTF(ABRT abrt, const char (&fmt)[N], ARGS... args) { + char buf[256]{}; + std::ignore = snprintf(buf, std::size(buf), fmt, args...); + abrt(buf); + __builtin_trap(); + } + + /// \brief Constructor + /// \tparam ABRT type of function used to abort and report errors + /// \param description Description of address range for use in error messages (will be copied) + /// \param start Target physical address where range starts + /// \param length Length of range, in bytes + /// \param f Phyical memory attribute flags for range + template + address_range(const char *description, uint64_t start, uint64_t length, const pma_flags &flags, ABRT abrt) : + m_description{}, + m_start{start}, + m_length{length}, + m_length_bit_ceil{(length >> 63) == 0 ? std::bit_ceil(length) : 0}, + m_flags{flags} { + // Non-empty description is mandatory + if (description == nullptr || *description == '\0') { + ABRTF(abrt, "address range 0x%" PRIx64 ":0x%" PRIx64 " has empty description", m_start, m_length); + } + for (unsigned i = 0; i < m_description.size() - 1 && description[i] != '\0'; ++i) { + m_description[i] = description[i]; + } + // All address ranges must be page-aligned + if ((m_length & ~PMA_ISTART_START_MASK) != 0) { + ABRTF(abrt, "length must be multiple of page size when initializing %s", m_description); + } + if ((m_start & ~PMA_ISTART_START_MASK) != 0) { + ABRTF(abrt, "start of %s (0x%" PRIx64 ") must be aligned to page boundary of %d bytes", m_description, + start, PMA_PAGE_SIZE); + } + // It must be possible to round length up to the next power of two + if (m_length_bit_ceil == 0) { + ABRTF(abrt, "range too long when initializing %s", m_description); + } + // Empty range must really be empty + if (m_length == 0) { + if (m_start != 0) { + ABRTF(abrt, "range with length 0 must start at 0 when initializing %s", m_description); + } + if (!m_flags.E) { + ABRTF(abrt, "range with length 0 must be flagged empty when initializing %s", m_description); + } + if (m_flags.M) { + ABRTF(abrt, "memory range cannot be empty when initializing %s", m_description); + } + if (m_flags.IO) { + ABRTF(abrt, "device range cannot be empty when initializing %s", m_description); + } + } + // Non-empty range must either be memory or device + if (static_cast(m_flags.M) + static_cast(m_flags.IO) + static_cast(m_flags.E) != 1) { + ABRTF(abrt, "range must be one of empty, memory, or device when initializing %s", m_description); + } + } + + /// \brief Checks if a range of addresses is entirely contained within this range + /// \param offset Start of range of interest, relative to start of this range + /// \param length Length of range of interest, in bytes + /// \returns True if and only if range of interest is entirely contained within this range + bool contains_relative(uint64_t offset, uint64_t length) const noexcept { + return get_length() >= length && offset <= get_length() - length; + } + + /// \brief Checks if a range of addresses is entirely contained within this range + /// \param start Target phyisical address of start of range of interest + /// \param length Length of range of interest, in bytes + /// \returns True if and only if range of interest is entirely contained within this range + bool contains_absolute(uint64_t start, uint64_t length) const noexcept { + return start >= get_start() && contains_relative(start - get_start(), length); + } + + /// \brief Returns PMA flags used during construction + /// \returns Flags + const pma_flags &get_flags() const noexcept { + return m_flags; + } + + /// \brief Returns description of address range for use in error messages. + /// \returns Description + const char *get_description() const noexcept { + return m_description.data(); + } + + /// \brief Returns target physical address where range starts. + /// \returns Start of range + uint64_t get_start() const noexcept { + return m_start; + } + + /// \brief Returns length of range, in bytes. + /// \returns Length of range + uint64_t get_length() const noexcept { + return m_length; + } + + /// \brief Returns smallest power of 2 that is not smaller than range length, in bytes + /// \returns Bit-ceil of length of range + uint64_t get_length_bit_ceil() const noexcept { + return m_length_bit_ceil; + } + + /// \brief Test if address range is occupied by memory + /// \returns True if and only if range is occupied by memory + bool is_memory() const noexcept { + return m_flags.M; + } + + /// \brief Test if address range is occupied by a device + /// \returns True if and only if range is occupied by a device + bool is_device() const noexcept { + return m_flags.IO; + } + + /// \brief Test if address range is empty + /// \returns True if and only if range is empty + bool is_empty() const noexcept { + return m_flags.E; + } + + /// \brief Tests if range is readable + /// \returns True if and only if range is readable + bool is_readable() const noexcept { + return m_flags.R; + } + + /// \brief Tests if range is writeable + /// \returns True if and only if range is writeable + bool is_writeable() const noexcept { + return m_flags.W; + } + + /// \brief Tests if range is executable + /// \returns True if and only if range is executable + bool is_executable() const noexcept { + return m_flags.X; + } + + /// \brief Tests if range is read-idempotent + /// \returns True if and only if what is read from range remains there until written to + bool is_read_idempotent() const noexcept { + return m_flags.IR; + } + + /// \brief Tests if range is write-idempotent + /// \returns True if and only if what is written to range remains there and can be read until written to again + bool is_write_idempotent() const noexcept { + return m_flags.IW; + } + + /// \brief Returns driver ID associated to range + /// \returns Teh driver ID + PMA_ISTART_DID get_driver_id() const noexcept { + return m_flags.DID; + } + + /// \brief Returns packed address range istart field as per whitepaper + /// \returns Packed address range istart + uint64_t get_istart() const noexcept { + return pack_pma_istart(m_flags, m_start); + } + + /// \brief Returns encoded addres range ilength field as per whitepaper + /// \returns Packed address range ilength + /// \details This currently contains only the length itself + uint64_t get_ilength() const noexcept { + return get_length(); + } + + /// \brief Read contents from address range with, no side-effects. + /// \param m Reference to machine. + /// \param offset Offset within range to start reading. + /// \param length Number of bytes to read. + /// \param data Receives pointer to start of data, or nullptr if data is constant *and* pristine (filled with + /// zeros). + /// \param scratch Pointer to memory buffer that must be able to hold \p length bytes. + /// \returns True if operation succeeded, false otherwise. + bool peek(const machine &m, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char *scratch) const noexcept { + return do_peek(m, offset, length, data, scratch); + }; + + // ----- + // These are only for device ranges + // ----- + + /// \brief Reads a word from a device + /// \param da State access object through which the machine state can be accessed. + /// \param offset Where to start reading, relative to start of this range. + /// \param log2_size Log2 of size of value to read (0=uint8_t, 1=uint16_t, 2=uint32_t, 3=uint64_t). + /// \param pval Pointer to word where value will be stored. + /// \returns True if operation succeeded, false otherwise. + bool read_device(i_device_state_access *da, uint64_t offset, int log2_size, uint64_t *pval) const noexcept { + return do_read_device(da, offset, log2_size, pval); + } + + /// \brief Writes a word to a device + /// \param da State access object through which the machine state can be accessed. + /// \param offset Where to start reading, relative to start of this range. + /// \param log2_size Log2 of size of value to write (0=uint8_t, 1=uint16_t, 2=uint32_t, 3=uint64_t). + /// \param val Value to write. + /// \returns execute::failure if operation failed, otherwise a success code if operation succeeded. + execute_status write_device(i_device_state_access *da, uint64_t offset, int log2_size, uint64_t val) noexcept { + return do_write_device(da, offset, log2_size, val); + } + + // ----- + // These are only for memory ranges + // ----- + + /// \brief Returns start of associated memory region in host + /// \returns Pointer to memory + const unsigned char *get_host_memory() const noexcept { + return do_get_host_memory(); + } + + /// \brief Returns start of associated memory region in host + /// \returns Pointer to memory + unsigned char *get_host_memory() noexcept { + return do_get_host_memory(); + } + + /// \brief Mark a given page as dirty + /// \param offset Any offset in range within desired page + void mark_dirty_page(uint64_t offset) noexcept { + do_mark_dirty_page(offset); + } + + /// \brief Mark all pages in a range of interest as dirty + /// \param offset Start of range of interest, relative to start of this range + /// \param length Length of range of interest, in bytes + void mark_dirty_pages(uint64_t offset, uint64_t length) noexcept { + auto offset_aligned = offset &= ~(PMA_PAGE_SIZE - 1); + const auto length_aligned = length + (offset - offset_aligned); + for (; offset_aligned < length_aligned; offset_aligned += PMA_PAGE_SIZE) { + mark_dirty_page(offset_aligned); + } + } + + /// \brief Mark a given page as clean + /// \param offset Any offset in range within desired page + void mark_clean_page(uint64_t offset) noexcept { + do_mark_clean_page(offset); + } + + /// \brief Marks all pages in range as clean + void mark_pages_clean() noexcept { + do_mark_pages_clean(); + } + + /// \brief Tests if a given page is dirty + /// \param offset Any offset in range within desired page + /// \returns True if and only if page is marked dirty + bool is_page_marked_dirty(uint64_t offset) const noexcept { + return do_is_page_marked_dirty(offset); + } + +private: + // Default implementation of peek() always fails + virtual bool do_peek(const machine & /*m*/, uint64_t /*offset*/, uint64_t /*length*/, + const unsigned char ** /*data*/, unsigned char * /*scratch*/) const noexcept { + return false; + } + + // Default implementation of read_device() for non-device ranges always fails + virtual bool do_read_device(i_device_state_access * /*a*/, uint64_t /*offset*/, int /*log2_size*/, + uint64_t * /*val*/) const noexcept { + return false; + } + + // Default implementation of write_device() for non-device ranges always fails + virtual execute_status do_write_device(i_device_state_access * /*a*/, uint64_t /*offset*/, int /* log2_size */, + uint64_t /*val*/) noexcept { + return execute_status::failure; + } + + // Default implementation of get_host_memory() for non-memory ranges returns nullptr + virtual const unsigned char *do_get_host_memory() const noexcept { + return nullptr; + } + + virtual unsigned char *do_get_host_memory() noexcept { + return nullptr; + } + + // Defaul implemenation always assumes every page is always dirty + virtual void do_mark_dirty_page(uint64_t /*offset*/) noexcept { + ; + } + + virtual void do_mark_clean_page(uint64_t /*offset*/) noexcept { + ; + } + + virtual void do_mark_pages_clean() noexcept { + ; + } + + virtual bool do_is_page_marked_dirty(uint64_t /*offset*/) const noexcept { + return true; + } +}; + +template +constexpr static auto make_empty_address_range(const char (&description)[N]) { + return address_range{description}; +} + +template +static inline auto make_address_range(const char *description, uint64_t start, uint64_t length, pma_flags f, + ABRT abrt) { + return address_range{description, start, length, f, abrt}; +} + +} // namespace cartesi + +#endif // OCCUPIED_ADDRESS_RANGE_H diff --git a/src/shadow-uarch-state.cpp b/src/assert-printf.h similarity index 76% rename from src/shadow-uarch-state.cpp rename to src/assert-printf.h index 1cb3d25e3..1bf16b675 100644 --- a/src/shadow-uarch-state.cpp +++ b/src/assert-printf.h @@ -14,14 +14,17 @@ // with this program (see COPYING). If not, see . // -#include "shadow-uarch-state.h" +#ifndef PRINTF_ASSERT_H +#define PRINTF_ASSERT_H -#include "pma-driver.h" +/// \file +/// \brief Microarchitecture-dependent includes for printf and assert -namespace cartesi { +#ifdef MICROARCHITECTURE +#include "uarch-runtime.h" +#else +#include +#include +#endif -const pma_driver shadow_uarch_state_driver = {.name = "SHADOW UARCH", - .read = device_read_error, - .write = device_write_error}; - -} // namespace cartesi +#endif diff --git a/src/clint.cpp b/src/clint-address-range.cpp similarity index 78% rename from src/clint.cpp rename to src/clint-address-range.cpp index 55dbfa200..4622fa42e 100644 --- a/src/clint.cpp +++ b/src/clint-address-range.cpp @@ -14,19 +14,34 @@ // with this program (see COPYING). If not, see . // -#include "clint.h" +#include "clint-address-range.h" #include #include "i-device-state-access.h" #include "interpret.h" #include "pma-constants.h" -#include "pma-driver.h" #include "riscv-constants.h" #include "rtc.h" namespace cartesi { +/// \brief Mapping between CSRs and their relative addresses in CLINT memory +enum class clint_csr { + msip0 = UINT64_C(0), // Machine software interrupt for hart 0 + mtimecmp = UINT64_C(0x4000), + mtime = UINT64_C(0xbff8) +}; + +/// \brief Obtains the relative address of the msip0 CSR in HTIF memory. +static constexpr auto clint_msip0_rel_addr = static_cast(clint_csr::msip0); + +/// \brief Obtains the relative address of the mtime CSR in HTIF memory. +static constexpr auto clint_mtime_rel_addr = static_cast(clint_csr::mtime); + +/// \brief Obtains the relative address of the mtimecmp CSR in HTIF memory. +constexpr auto clint_mtimecmp_rel_addr = static_cast(clint_csr::mtimecmp); + static constexpr uint64_t base(uint64_t v) { return v - (v % PMA_PAGE_SIZE); } @@ -61,8 +76,8 @@ static bool clint_read_mtimecmp(i_device_state_access *a, uint64_t *val, int log return false; } -/// \brief CLINT device read callback. See ::pma_read. -static bool clint_read(void * /*context*/, i_device_state_access *a, uint64_t offset, uint64_t *val, int log2_size) { +bool clint_address_range::do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *val) const noexcept { switch (offset) { case clint_msip0_rel_addr: return clint_read_msip(a, val, log2_size); @@ -76,9 +91,8 @@ static bool clint_read(void * /*context*/, i_device_state_access *a, uint64_t of } } -/// \brief CLINT device read callback. See ::pma_write. -static execute_status clint_write(void * /*context*/, i_device_state_access *a, uint64_t offset, uint64_t val, - int log2_size) { +execute_status clint_address_range::do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept { switch (offset) { case clint_msip0_rel_addr: if (log2_size == 2) { @@ -108,6 +122,4 @@ static execute_status clint_write(void * /*context*/, i_device_state_access *a, } } -const pma_driver clint_driver = {.name = "CLINT", .read = clint_read, .write = clint_write}; - } // namespace cartesi diff --git a/src/clint-address-range.h b/src/clint-address-range.h new file mode 100644 index 000000000..b1d46aef0 --- /dev/null +++ b/src/clint-address-range.h @@ -0,0 +1,70 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef CLINT_ADDRESS_RANGE_H +#define CLINT_ADDRESS_RANGE_H + +#include + +#include "pristine-address-range.h" + +/// \file +/// \brief Core-Local Interruptor device. + +namespace cartesi { + +class clint_address_range final : public pristine_address_range { + + static constexpr pma_flags m_clint_flags{ + .M = false, + .IO = true, + .E = false, + .R = true, + .W = true, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::CLINT, + }; + +public: + template + clint_address_range(uint64_t start, uint64_t length, ABRT abrt) : + pristine_address_range("CLINT device", start, length, m_clint_flags, abrt) { + ; + } + + clint_address_range(const clint_address_range &other) = default; + clint_address_range &operator=(const clint_address_range &other) = default; + clint_address_range(clint_address_range &&other) = default; + clint_address_range &operator=(clint_address_range &&other) = default; + ~clint_address_range() override = default; + +private: + bool do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept override; + execute_status do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept override; +}; + +template +static inline clint_address_range make_clint_address_range(uint64_t start, uint64_t length, ABRT abrt) { + return clint_address_range{start, length, abrt}; +} + +} // namespace cartesi + +#endif diff --git a/src/clint-factory.cpp b/src/clint-factory.cpp deleted file mode 100644 index 0dd9ba3e7..000000000 --- a/src/clint-factory.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "clint-factory.h" - -#include - -#include "clint.h" -#include "pma-constants.h" -#include "pma.h" - -namespace cartesi { - -pma_entry make_clint_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = true, .W = true, .X = false, .IR = false, .IW = false, .DID = PMA_ISTART_DID::CLINT}; - return make_device_pma_entry("CLINT device", start, length, pma_peek_pristine, &clint_driver).set_flags(f); -} - -} // namespace cartesi diff --git a/src/clint-factory.h b/src/clint-factory.h deleted file mode 100644 index 1910bfd0e..000000000 --- a/src/clint-factory.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef CLINT_FACTORY_H -#define CLINT_FACTORY_H - -#include - -#include "pma.h" - -namespace cartesi { - -/// \brief Creates a PMA entry for the CLINT device -/// \param start Start address for memory range. -/// \param length Length of memory range. -/// \returns Corresponding PMA entry -pma_entry make_clint_pma_entry(uint64_t start, uint64_t length); - -} // namespace cartesi - -#endif diff --git a/src/clint.h b/src/clint.h deleted file mode 100644 index 04d228885..000000000 --- a/src/clint.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef CLINT_H -#define CLINT_H - -#include - -#include "pma-driver.h" - -/// \file -/// \brief Clock interruptor device. - -namespace cartesi { - -/// \brief Global CLINT device driver instance -extern const pma_driver clint_driver; - -/// \brief Mapping between CSRs and their relative addresses in CLINT memory -enum class clint_csr { - msip0 = UINT64_C(0), // Machine software interrupt for hart 0 - mtimecmp = UINT64_C(0x4000), - mtime = UINT64_C(0xbff8) -}; - -/// \brief Obtains the relative address of the msip0 CSR in HTIF memory. -static constexpr auto clint_msip0_rel_addr = static_cast(clint_csr::msip0); - -/// \brief Obtains the relative address of the mtime CSR in HTIF memory. -static constexpr auto clint_mtime_rel_addr = static_cast(clint_csr::mtime); - -/// \brief Obtains the relative address of the mtimecmp CSR in HTIF memory. -constexpr auto clint_mtimecmp_rel_addr = static_cast(clint_csr::mtimecmp); - -} // namespace cartesi - -#endif diff --git a/src/clua-i-virtual-machine.cpp b/src/clua-i-virtual-machine.cpp index 121a4a944..8c03c4225 100644 --- a/src/clua-i-virtual-machine.cpp +++ b/src/clua-i-virtual-machine.cpp @@ -687,12 +687,12 @@ static int machine_obj_index_reset_uarch(lua_State *L) { return 0; } -/// \brief This is the machine:get_memory_ranges() method implementation. +/// \brief This is the machine:get_address_ranges() method implementation. /// \param L Lua state. -static int machine_obj_index_get_memory_ranges(lua_State *L) { +static int machine_obj_index_get_address_ranges(lua_State *L) { auto &m = clua_check>(L, 1); const char *ranges = nullptr; - if (cm_get_memory_ranges(m.get(), &ranges) != 0) { + if (cm_get_address_ranges(m.get(), &ranges) != 0) { return luaL_error(L, "%s", cm_get_last_error_message()); } clua_push_json_table(L, ranges); @@ -1054,7 +1054,7 @@ static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ {"destroy", machine_obj_index_destroy}, {"get_default_config", machine_obj_index_get_default_config}, {"get_initial_config", machine_obj_index_get_initial_config}, - {"get_memory_ranges", machine_obj_index_get_memory_ranges}, + {"get_address_ranges", machine_obj_index_get_address_ranges}, {"get_proof", machine_obj_index_get_proof}, {"get_reg_address", machine_obj_index_get_reg_address}, {"get_root_hash", machine_obj_index_get_root_hash}, diff --git a/src/dump.h b/src/dump.h index f7c00c538..4cfc8e2c8 100644 --- a/src/dump.h +++ b/src/dump.h @@ -18,17 +18,11 @@ #include -#ifdef MICROARCHITECTURE -template -static inline void D_PRINTF(const char (&fmt)[N], ARGS... args) { - std::ignore = printf(fmt, args...); -} -#else -#include +#include "assert-printf.h" + template static inline auto D_PRINTF(const char (&fmt)[N], ARGS... args) { std::ignore = fprintf(stderr, fmt, args...); } -#endif #endif // DUMP_H diff --git a/src/find-pma-entry.h b/src/find-pma-entry.h index 1cb857ca4..73cb36503 100644 --- a/src/find-pma-entry.h +++ b/src/find-pma-entry.h @@ -14,15 +14,17 @@ // with this program (see COPYING). If not, see . // -#ifndef FIND_PMA_ENTRY_H -#define FIND_PMA_ENTRY_H +#ifndef FIND_ADDRESS_RANGE_H +#define FIND_ADDRESS_RANGE_H -#include "compiler-defines.h" #include +#include "address-range.h" +#include "compiler-defines.h" + namespace cartesi { -/// \brief Returns PMAs entry where a word falls. +/// \brief Returns address range associated to the PMA entry where a word falls. /// \tparam T uint8_t, uint16_t, uint32_t, or uint64_t. /// \tparam STATE_ACCESS Class of machine state accessor object. /// \param a Machine state accessor object. @@ -30,41 +32,34 @@ namespace cartesi { /// \param index Receives index where PMA entry was found. /// \returns PMA entry where word falls, or empty sentinel. template -auto &find_pma_entry(STATE_ACCESS &a, uint64_t paddr, uint64_t &index) { - [[maybe_unused]] auto note = a.make_scoped_note("find_pma_entry"); +address_range &find_pma(const STATE_ACCESS a, uint64_t paddr, uint64_t &index) { + [[maybe_unused]] auto note = a.make_scoped_note("find_pma"); index = 0; while (true) { - auto &pma = a.read_pma_entry(index); - const auto length = pma.get_length(); + auto &ar = a.read_pma(index); // The pmas array always contain a sentinel. // It is an entry with zero length. // If we hit it, return it - if (unlikely(length == 0)) { - return pma; + if (unlikely(ar.get_length() == 0)) { + return ar; } - // Otherwise, if we found an entry where the access fits, return it - // Note the "strange" order of arithmetic operations. - // This is to ensure there is no overflow. - // Since we know paddr >= start, there is no chance of overflow in the first subtraction. - // Since length is at least 4096 (an entire page), there is no chance of overflow in the second subtraction. - const auto start = pma.get_start(); - if (paddr >= start && paddr - start <= length - sizeof(T)) { - return pma; + if (ar.contains_absolute(paddr, sizeof(T))) { + return ar; } ++index; } } -/// \brief Returns PMAs entry where a word falls. +/// \brief Returns address range associated to the PMA entry where a word falls. /// \tparam T uint8_t, uint16_t, uint32_t, or uint64_t. /// \tparam STATE_ACCESS Class of machine state accessor object. /// \param a Machine state accessor object. /// \param paddr Target physical address of word. /// \returns PMA entry where word falls, or empty sentinel. template -FORCE_INLINE auto &find_pma_entry(STATE_ACCESS &a, uint64_t paddr) { +FORCE_INLINE auto &find_pma(const STATE_ACCESS a, uint64_t paddr) { uint64_t index = 0; - return find_pma_entry(a, paddr, index); + return find_pma(a, paddr, index); } } // namespace cartesi diff --git a/src/htif.cpp b/src/htif-address-range.cpp similarity index 91% rename from src/htif.cpp rename to src/htif-address-range.cpp index 1a17d1e02..4af6019ce 100644 --- a/src/htif.cpp +++ b/src/htif-address-range.cpp @@ -14,13 +14,12 @@ // with this program (see COPYING). If not, see . // -#include "htif.h" +#include "htif-address-range.h" #include +#include "htif-constants.h" #include "i-device-state-access.h" -#include "interpret.h" -#include "pma-driver.h" namespace cartesi { @@ -30,8 +29,8 @@ static constexpr auto htif_ihalt_rel_addr = static_cast(htif_csr::ihal static constexpr auto htif_iconsole_rel_addr = static_cast(htif_csr::iconsole); static constexpr auto htif_iyield_rel_addr = static_cast(htif_csr::iyield); -/// \brief HTIF device read callback. See ::pma_read. -static bool htif_read(void * /*context*/, i_device_state_access *a, uint64_t offset, uint64_t *pval, int log2_size) { +bool htif_address_range::do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept { // Our HTIF only supports 64-bit reads if (log2_size != 3) { return false; @@ -129,9 +128,9 @@ static execute_status htif_write_tohost(i_device_state_access *a, uint64_t tohos } } -/// \brief HTIF device write callback. See ::pma_write. -static execute_status htif_write(void * /*context*/, i_device_state_access *a, uint64_t offset, uint64_t val, - int log2_size) { +execute_status htif_address_range::do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept { + // Our HTIF only supports 64-bit writes if (log2_size != 3) { return execute_status::failure; @@ -150,6 +149,4 @@ static execute_status htif_write(void * /*context*/, i_device_state_access *a, u } } -const pma_driver htif_driver{.name = "HTIF", .read = htif_read, .write = htif_write}; - } // namespace cartesi diff --git a/src/htif-address-range.h b/src/htif-address-range.h new file mode 100644 index 000000000..d7fd5cde8 --- /dev/null +++ b/src/htif-address-range.h @@ -0,0 +1,72 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef HTIF_ADDRESS_RANGE_H +#define HTIF_ADDRESS_RANGE_H + +#include + +#include "i-device-state-access.h" +#include "pma-constants.h" +#include "pristine-address-range.h" + +/// \file +/// \brief Host-Target InterFace device. + +namespace cartesi { + +class htif_address_range final : public pristine_address_range { + + static constexpr pma_flags m_htif_flags{ + .M = false, + .IO = true, + .E = false, + .R = true, + .W = true, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::HTIF, + }; + +public: + template + htif_address_range(uint64_t start, uint64_t length, ABRT abrt) : + pristine_address_range("HTIF device", start, length, m_htif_flags, abrt) { + ; + } + + htif_address_range(const htif_address_range &other) = default; + htif_address_range &operator=(const htif_address_range &other) = default; + htif_address_range(htif_address_range &&other) = default; + htif_address_range &operator=(htif_address_range &&other) = default; + ~htif_address_range() override = default; + +private: + bool do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept override; + execute_status do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept override; +}; + +template +static inline htif_address_range make_htif_address_range(uint64_t start, uint64_t length, ABRT abrt) { + return htif_address_range{start, length, abrt}; +} + +} // namespace cartesi + +#endif diff --git a/src/htif.h b/src/htif-constants.h similarity index 81% rename from src/htif.h rename to src/htif-constants.h index b4cb6afc5..1a57a13f6 100644 --- a/src/htif.h +++ b/src/htif-constants.h @@ -14,23 +14,23 @@ // with this program (see COPYING). If not, see . // -#ifndef HTIF_H -#define HTIF_H +#ifndef HTIF_CONSTANTS_H +#define HTIF_CONSTANTS_H #include #include "htif-defines.h" -#include "machine-c-api.h" -#include "pma-defines.h" -#include "pma-driver.h" /// \file -/// \brief Host-Target interface device. +/// \brief Host-Target InterFace device. namespace cartesi { -/// \brief Global HTIF device driver instance -extern const pma_driver htif_driver; +// helper for using UINT64_C with defines +#ifndef EXPAND_UINT64_C +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define EXPAND_UINT64_C(a) UINT64_C(a) +#endif // Forward declarations /// \brief HTIF shifts @@ -122,15 +122,6 @@ enum class htif_csr { iyield = UINT64_C(0x20) }; -static_assert(HTIF_YIELD_AUTOMATIC_REASON_PROGRESS_DEF == CM_CMIO_YIELD_AUTOMATIC_REASON_PROGRESS); -static_assert(HTIF_YIELD_AUTOMATIC_REASON_TX_OUTPUT_DEF == CM_CMIO_YIELD_AUTOMATIC_REASON_TX_OUTPUT); -static_assert(HTIF_YIELD_AUTOMATIC_REASON_TX_REPORT_DEF == CM_CMIO_YIELD_AUTOMATIC_REASON_TX_REPORT); -static_assert(HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED_DEF == CM_CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED); -static_assert(HTIF_YIELD_MANUAL_REASON_RX_REJECTED_DEF == CM_CMIO_YIELD_MANUAL_REASON_RX_REJECTED); -static_assert(HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION_DEF == CM_CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION); -static_assert(HTIF_YIELD_REASON_ADVANCE_STATE_DEF == CM_CMIO_YIELD_REASON_ADVANCE_STATE); -static_assert(HTIF_YIELD_REASON_INSPECT_STATE_DEF == CM_CMIO_YIELD_REASON_INSPECT_STATE); - } // namespace cartesi #endif diff --git a/src/htif-factory.cpp b/src/htif-factory.cpp deleted file mode 100644 index f09b2fb39..000000000 --- a/src/htif-factory.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "htif-factory.h" - -#include - -#include "htif.h" -#include "pma-constants.h" -#include "pma.h" - -namespace cartesi { - -pma_entry make_htif_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = true, .W = true, .X = false, .IR = false, .IW = false, .DID = PMA_ISTART_DID::HTIF}; - return make_device_pma_entry("HTIF device", start, length, pma_peek_pristine, &htif_driver).set_flags(f); -} - -} // namespace cartesi diff --git a/src/htif-factory.h b/src/htif-factory.h deleted file mode 100644 index 1bba5844c..000000000 --- a/src/htif-factory.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef HTIF_FACTORY_H -#define HTIF_FACTORY_H - -#include - -#include "pma.h" - -namespace cartesi { - -/// \brief Creates a PMA entry for the HTIF device -pma_entry make_htif_pma_entry(uint64_t start, uint64_t length); - -} // namespace cartesi - -#endif diff --git a/src/i-state-access.h b/src/i-state-access.h index 6e7be2874..4fc9bbb25 100644 --- a/src/i-state-access.h +++ b/src/i-state-access.h @@ -36,12 +36,6 @@ namespace cartesi { // Forward declarations enum class bracket_type; -// Type trait that should return the pma_entry type for a state access class -template -struct i_state_access_pma_entry {}; -template -using i_state_access_pma_entry_t = typename i_state_access_pma_entry::type; - // Type trait that should return the fast_addr type for a state access class template struct i_state_access_fast_addr {}; @@ -121,7 +115,6 @@ class i_state_access { // CRTP } public: - using pma_entry = i_state_access_pma_entry_t; using fast_addr = i_state_access_fast_addr_t; /// \brief Works as printf if we are dumping state accesses, otherwise does nothing @@ -274,11 +267,11 @@ class i_state_access { // CRTP /// \brief Reads PMA entry at a given index. /// \param index Index of PMA - pma_entry &read_pma_entry(uint64_t index) const { - auto &pma = derived().do_read_pma_entry(index); - DSA_PRINTF("%s::read_pma_entry(%" PRIu64 ") = {%s, 0x%" PRIx64 ", 0x%" PRIx64 "}\n", get_name(), index, - pma_get_DID_name(pma.get_istart_DID()), pma.get_start(), pma.get_length()); - return pma; + address_range &read_pma(uint64_t index) const { + auto &ar = derived().do_read_pma(index); + DSA_PRINTF("%s::read_address_range(%" PRIu64 ") = {%s, 0x%" PRIx64 ", 0x%" PRIx64 "}\n", get_name(), index, + pma_get_DID_name(ar.get_driver_id()), ar.get_start(), ar.get_length()); + return ar; } /// \brief Converts a target physical address to the implementation-defined fast address diff --git a/src/i-virtual-machine.h b/src/i-virtual-machine.h index 2ea1dd3cd..b307d8d6d 100644 --- a/src/i-virtual-machine.h +++ b/src/i-virtual-machine.h @@ -21,9 +21,9 @@ #include #include "access-log.h" +#include "address-range-description.h" #include "interpret.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine.h" #include "uarch-interpret.h" @@ -200,8 +200,8 @@ class i_virtual_machine { } /// \brief Returns a list of descriptions for all PMA entries registered in the machine, sorted by start - virtual machine_memory_range_descrs get_memory_ranges() const { - return do_get_memory_ranges(); + virtual address_range_descriptions get_address_ranges() const { + return do_get_address_ranges(); } /// \brief Sends cmio response. @@ -283,7 +283,7 @@ class i_virtual_machine { virtual void do_reset_uarch() = 0; virtual access_log do_log_reset_uarch(const access_log::type &log_type) = 0; virtual uarch_interpreter_break_reason do_run_uarch(uint64_t uarch_cycle_end) = 0; - virtual machine_memory_range_descrs do_get_memory_ranges() const = 0; + virtual address_range_descriptions do_get_address_ranges() const = 0; virtual void do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) = 0; virtual access_log do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) = 0; diff --git a/src/interpret.cpp b/src/interpret.cpp index 02a272b65..282344f4b 100644 --- a/src/interpret.cpp +++ b/src/interpret.cpp @@ -995,22 +995,21 @@ static NO_INLINE std::pair read_virtual_memory_slow(const STATE_ return {false, pc}; } uint64_t pma_index = 0; - const auto &pma = find_pma_entry(a, paddr, pma_index); - if (likely(pma.get_istart_R())) { - if (likely(pma.get_istart_M())) { + const auto &ar = find_pma(a, paddr, pma_index); + if (likely(ar.is_readable())) { + if (likely(ar.is_memory())) { [[maybe_unused]] auto note = a.make_scoped_note("read memory"); const auto faddr = replace_tlb_entry(a, vaddr, paddr, pma_index); a.template read_memory_word(faddr, pma_index, pval); return {true, pc}; } - if (likely(pma.get_istart_IO())) { + if (likely(ar.is_device())) { [[maybe_unused]] auto note = a.make_scoped_note("read device"); - const uint64_t offset = paddr - pma.get_start(); + const uint64_t offset = paddr - ar.get_start(); uint64_t val{}; device_state_access da(a, mcycle); // If we do not know how to read, we treat this as a PMA violation - const bool status = pma.get_device_noexcept().get_driver()->read(pma.get_device_noexcept().get_context(), - &da, offset, &val, log2_size_v); + const bool status = ar.read_device(&da, offset, log2_size_v, &val); if (likely(status)) { *pval = static_cast(val); // device logs its own state accesses @@ -1087,18 +1086,17 @@ static NO_INLINE std::pair write_virtual_memory_slow(c return {execute_status::failure, pc}; } uint64_t pma_index = 0; - auto &pma = find_pma_entry(a, paddr, pma_index); - if (likely(pma.get_istart_W())) { - if (likely(pma.get_istart_M())) { + auto &ar = find_pma(a, paddr, pma_index); + if (likely(ar.is_writeable())) { + if (likely(ar.is_memory())) { const auto faddr = replace_tlb_entry(a, vaddr, paddr, pma_index); a.write_memory_word(faddr, pma_index, static_cast(val64)); return {execute_status::success, pc}; } - if (likely(pma.get_istart_IO())) { - const uint64_t offset = paddr - pma.get_start(); + if (likely(ar.is_device())) { + const uint64_t offset = paddr - ar.get_start(); device_state_access da(a, mcycle); - auto status = pma.get_device_noexcept().get_driver()->write(pma.get_device_noexcept().get_context(), &da, - offset, static_cast(static_cast(val64)), log2_size_v); + auto status = ar.write_device(&da, offset, log2_size_v, static_cast(static_cast(val64))); // If we do not know how to write, we treat this as a PMA violation if (likely(status != execute_status::failure)) { return {status, pc}; @@ -5396,10 +5394,10 @@ static FORCE_INLINE fetch_status fetch_translate_pc_slow(const STATE_ACCESS a, u return fetch_status::exception; } // Walk memory map to find the range that contains the physical address - const auto &pma = find_pma_entry(a, paddr, pma_index); + const auto &ar = find_pma(a, paddr, pma_index); // We only execute directly from RAM (as in "random access memory") // If the range is not memory or not executable, this as a PMA violation - if (unlikely(!pma.get_istart_M() || !pma.get_istart_X())) { + if (unlikely(!ar.is_memory() || !ar.is_executable())) { pc = raise_exception(a, pc, MCAUSE_INSN_ACCESS_FAULT, vaddr); return fetch_status::exception; } diff --git a/src/json-util.cpp b/src/json-util.cpp index 3df07f1ff..e59cf5f6c 100644 --- a/src/json-util.cpp +++ b/src/json-util.cpp @@ -33,12 +33,12 @@ #include #include "access-log.h" +#include "address-range-description.h" #include "base64.h" #include "bracket-note.h" #include "interpret.h" #include "jsonrpc-fork-result.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-runtime-config.h" #include "machine.h" @@ -1639,7 +1639,7 @@ template void ju_get_opt_field(const nlohmann::json &j, const std:: const std::string &path); template -void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_memory_range_descr &value, +void ju_get_opt_field(const nlohmann::json &j, const K &key, address_range_description &value, const std::string &path) { if (!contains(j, key)) { return; @@ -1651,23 +1651,23 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_memory_rang ju_get_opt_field(jconfig, "description"s, value.description, new_path); } -template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, - machine_memory_range_descr &value, const std::string &path); +template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, address_range_description &value, + const std::string &path); template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, - machine_memory_range_descr &value, const std::string &path); + address_range_description &value, const std::string &path); template -void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_memory_range_descrs &value, +void ju_get_opt_field(const nlohmann::json &j, const K &key, address_range_descriptions &value, const std::string &path) { ju_get_opt_vector_like_field(j, key, value, path); } template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, - machine_memory_range_descrs &value, const std::string &path); + address_range_descriptions &value, const std::string &path); template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, - machine_memory_range_descrs &value, const std::string &path); + address_range_descriptions &value, const std::string &path); template void ju_get_opt_field(const nlohmann::json &j, const K &key, fork_result &value, const std::string &path) { @@ -1997,11 +1997,11 @@ void to_json(nlohmann::json &j, const machine_runtime_config &runtime) { }; } -void to_json(nlohmann::json &j, const machine_memory_range_descr &mrd) { +void to_json(nlohmann::json &j, const address_range_description &mrd) { j = nlohmann::json{{"length", mrd.length}, {"start", mrd.start}, {"description", mrd.description}}; } -void to_json(nlohmann::json &j, const machine_memory_range_descrs &mrds) { +void to_json(nlohmann::json &j, const address_range_descriptions &mrds) { j = nlohmann::json::array(); std::transform(mrds.cbegin(), mrds.cend(), std::back_inserter(j), [](const auto &a) -> nlohmann::json { return a; }); diff --git a/src/json-util.h b/src/json-util.h index 8a67c377e..5764a3dff 100644 --- a/src/json-util.h +++ b/src/json-util.h @@ -28,11 +28,11 @@ #include #include "access-log.h" +#include "address-range-description.h" #include "bracket-note.h" #include "interpret.h" #include "jsonrpc-fork-result.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-runtime-config.h" #include "machine.h" @@ -474,24 +474,24 @@ template void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_config &value, const std::string &path = "params/"); -/// \brief Attempts to load a machine_memory_range_descr object from a field in a JSON object +/// \brief Attempts to load a address_range_description object from a field in a JSON object /// \tparam K Key type (explicit extern declarations for uint64_t and std::string are provided) /// \param j JSON object to load from /// \param key Key to load value from /// \param value Object to store value /// \param path Path to j template -void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_memory_range_descr &value, +void ju_get_opt_field(const nlohmann::json &j, const K &key, address_range_description &value, const std::string &path = "params/"); -/// \brief Attempts to load a machine_memory_range_descrs object from a field in a JSON object +/// \brief Attempts to load a address_range_descriptions object from a field in a JSON object /// \tparam K Key type (explicit extern declarations for uint64_t and std::string are provided) /// \param j JSON object to load from /// \param key Key to load value from /// \param value Object to store value /// \param path Path to j template -void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_memory_range_descrs &value, +void ju_get_opt_field(const nlohmann::json &j, const K &key, address_range_descriptions &value, const std::string &path = "params/"); /// \brief Attempts to load a fork_result object from a field in a JSON object @@ -625,8 +625,8 @@ void to_json(nlohmann::json &j, const concurrency_runtime_config &config); void to_json(nlohmann::json &j, const htif_runtime_config &config); void to_json(nlohmann::json &j, const machine_runtime_config &runtime); void to_json(nlohmann::json &j, const machine::reg ®); -void to_json(nlohmann::json &j, const machine_memory_range_descr &mrd); -void to_json(nlohmann::json &j, const machine_memory_range_descrs &mrds); +void to_json(nlohmann::json &j, const address_range_description &mrd); +void to_json(nlohmann::json &j, const address_range_descriptions &mrds); void to_json(nlohmann::json &j, const fork_result &fork_result); void to_json(nlohmann::json &j, const semantic_version &version); @@ -787,14 +787,14 @@ extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &k const std::string &base = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, machine_config &value, const std::string &base = "params/"); -extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, machine_memory_range_descr &value, +extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, address_range_description &value, const std::string &base = "params/"); -extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, - machine_memory_range_descr &value, const std::string &base = "params/"); -extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, machine_memory_range_descrs &value, +extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, address_range_description &value, + const std::string &base = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, address_range_descriptions &value, const std::string &base = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, - machine_memory_range_descrs &value, const std::string &base = "params/"); + address_range_descriptions &value, const std::string &base = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, fork_result &value, const std::string &base = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, fork_result &value, diff --git a/src/jsonrpc-discover.json b/src/jsonrpc-discover.json index 05b4e9fdc..7eb352224 100644 --- a/src/jsonrpc-discover.json +++ b/src/jsonrpc-discover.json @@ -712,14 +712,14 @@ } }, { - "name": "machine.get_memory_ranges", - "summary": "Returns a list with descriptions for all of the machine's memory ranges", + "name": "machine.get_address_ranges", + "summary": "Returns a list with descriptions for all of the machine's address ranges", "params": [], "result": { "name": "ranges", - "description": "Array of memory range descriptions", + "description": "Array of address range descriptions", "schema": { - "$ref": "#/components/schemas/MemoryRangeDescriptionArray" + "$ref": "#/components/schemas/AddressRangeDescriptionArray" } } }, @@ -1991,8 +1991,8 @@ "htif_fromhost_data" ] }, - "MemoryRangeDescription": { - "title": "MemoryRangeDescription", + "AddressRangeDescription": { + "title": "AddressRangeDescription", "type": "object", "properties": { "start": { @@ -2006,11 +2006,11 @@ } } }, - "MemoryRangeDescriptionArray": { - "title": "MemoryRangeDescriptionArray", + "AddressRangeDescriptionArray": { + "title": "AddressRangeDescriptionArray", "type": "array", "items": { - "$ref": "#/components/schemas/MemoryRangeDescription" + "$ref": "#/components/schemas/AddressRangeDescription" } } } diff --git a/src/jsonrpc-machine-c-api.cpp b/src/jsonrpc-machine-c-api.cpp index e5fb86863..646331089 100644 --- a/src/jsonrpc-machine-c-api.cpp +++ b/src/jsonrpc-machine-c-api.cpp @@ -16,7 +16,6 @@ #include "jsonrpc-machine-c-api.h" -#include #include #include #include diff --git a/src/jsonrpc-remote-machine.cpp b/src/jsonrpc-remote-machine.cpp index 59484deab..09b0b5172 100644 --- a/src/jsonrpc-remote-machine.cpp +++ b/src/jsonrpc-remote-machine.cpp @@ -1334,16 +1334,16 @@ static json jsonrpc_machine_verify_dirty_page_maps_handler(const json &j, return jsonrpc_response_ok(j, session->handler->machine->verify_dirty_page_maps()); } -/// \brief JSONRPC handler for the machine.get_memory_ranges method +/// \brief JSONRPC handler for the machine.get_address_ranges method /// \param j JSON request object /// \param session HTTP session /// \returns JSON response object -static json jsonrpc_machine_get_memory_ranges_handler(const json &j, const std::shared_ptr &session) { +static json jsonrpc_machine_get_address_ranges_handler(const json &j, const std::shared_ptr &session) { if (!session->handler->machine) { return jsonrpc_response_invalid_request(j, "no machine"); } jsonrpc_check_no_params(j); - return jsonrpc_response_ok(j, session->handler->machine->get_memory_ranges()); + return jsonrpc_response_ok(j, session->handler->machine->get_address_ranges()); } /// \brief JSONRPC handler for the machine.send_cmio_response method @@ -1483,7 +1483,7 @@ static json jsonrpc_dispatch_method(const json &j, const std::shared_ptr #include "access-log.h" +#include "address-range-description.h" #include "base64.h" #include "i-virtual-machine.h" #include "interpret.h" @@ -58,7 +59,6 @@ #include "jsonrpc-fork-result.h" #include "jsonrpc-version.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-runtime-config.h" #include "os-features.h" @@ -760,9 +760,9 @@ uarch_interpreter_break_reason jsonrpc_virtual_machine::do_run_uarch(uint64_t ua return result; } -machine_memory_range_descrs jsonrpc_virtual_machine::do_get_memory_ranges() const { - machine_memory_range_descrs result; - request("machine.get_memory_ranges", std::tie(), result); +address_range_descriptions jsonrpc_virtual_machine::do_get_address_ranges() const { + address_range_descriptions result; + request("machine.get_address_ranges", std::tie(), result); return result; } diff --git a/src/jsonrpc-virtual-machine.h b/src/jsonrpc-virtual-machine.h index d7f3c9592..164133299 100644 --- a/src/jsonrpc-virtual-machine.h +++ b/src/jsonrpc-virtual-machine.h @@ -22,11 +22,11 @@ #include #include "access-log.h" +#include "address-range-description.h" #include "i-virtual-machine.h" #include "interpret.h" #include "jsonrpc-fork-result.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-runtime-config.h" #include "semantic-version.h" @@ -123,7 +123,7 @@ class jsonrpc_virtual_machine final : public i_virtual_machine { uint64_t do_read_word(uint64_t address) const override; bool do_verify_merkle_tree() const override; uarch_interpreter_break_reason do_run_uarch(uint64_t uarch_cycle_end) override; - machine_memory_range_descrs do_get_memory_ranges() const override; + address_range_descriptions do_get_address_ranges() const override; void do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) override; access_log do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) override; diff --git a/src/machine-c-api.cpp b/src/machine-c-api.cpp index 6f57d0468..2209b22e0 100644 --- a/src/machine-c-api.cpp +++ b/src/machine-c-api.cpp @@ -32,21 +32,36 @@ #include #include "access-log.h" -#include "htif.h" +#include "address-range-description.h" +#include "htif-constants.h" #include "i-virtual-machine.h" #include "interpret.h" #include "json-util.h" #include "machine-c-api-internal.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-reg.h" #include "machine-runtime-config.h" #include "machine.h" #include "os-features.h" -#include "pma-constants.h" +#include "pma-defines.h" #include "virtual-machine.h" +static_assert(PMA_CMIO_RX_BUFFER_START_DEF == CM_PMA_CMIO_RX_BUFFER_START); +static_assert(PMA_CMIO_RX_BUFFER_LOG2_SIZE_DEF == CM_PMA_CMIO_RX_BUFFER_LOG2_SIZE); +static_assert(PMA_CMIO_TX_BUFFER_START_DEF == CM_PMA_CMIO_TX_BUFFER_START); +static_assert(PMA_CMIO_TX_BUFFER_LOG2_SIZE_DEF == CM_PMA_CMIO_TX_BUFFER_LOG2_SIZE); +static_assert(PMA_RAM_START_DEF == CM_PMA_RAM_START); + +static_assert(HTIF_YIELD_AUTOMATIC_REASON_PROGRESS_DEF == CM_CMIO_YIELD_AUTOMATIC_REASON_PROGRESS); +static_assert(HTIF_YIELD_AUTOMATIC_REASON_TX_OUTPUT_DEF == CM_CMIO_YIELD_AUTOMATIC_REASON_TX_OUTPUT); +static_assert(HTIF_YIELD_AUTOMATIC_REASON_TX_REPORT_DEF == CM_CMIO_YIELD_AUTOMATIC_REASON_TX_REPORT); +static_assert(HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED_DEF == CM_CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED); +static_assert(HTIF_YIELD_MANUAL_REASON_RX_REJECTED_DEF == CM_CMIO_YIELD_MANUAL_REASON_RX_REJECTED); +static_assert(HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION_DEF == CM_CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION); +static_assert(HTIF_YIELD_REASON_ADVANCE_STATE_DEF == CM_CMIO_YIELD_REASON_ADVANCE_STATE); +static_assert(HTIF_YIELD_REASON_INSPECT_STATE_DEF == CM_CMIO_YIELD_REASON_INSPECT_STATE); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static THREAD_LOCAL std::string last_err_msg; @@ -973,12 +988,12 @@ cm_error cm_destroy(cm_machine *m) try { return cm_result_failure(); } -cm_error cm_get_memory_ranges(const cm_machine *m, const char **ranges) try { +cm_error cm_get_address_ranges(const cm_machine *m, const char **ranges) try { if (ranges == nullptr) { throw std::invalid_argument("invalid memory range output"); } const auto *cpp_m = convert_from_c(m); - const cartesi::machine_memory_range_descrs cpp_ranges = cpp_m->get_memory_ranges(); + const cartesi::address_range_descriptions cpp_ranges = cpp_m->get_address_ranges(); *ranges = cm_set_temp_string(cartesi::to_json(cpp_ranges).dump()); return cm_result_success(); } catch (...) { diff --git a/src/machine-c-api.h b/src/machine-c-api.h index 54b09739c..515863261 100644 --- a/src/machine-c-api.h +++ b/src/machine-c-api.h @@ -472,12 +472,12 @@ CM_API cm_error cm_replace_memory_range(cm_machine *m, uint64_t start, uint64_t /// \returns 0 for success, non zero code for error. CM_API cm_error cm_get_initial_config(const cm_machine *m, const char **config); -/// \brief Returns a list with all memory ranges in the machine. +/// \brief Returns a list with all address ranges in the machine. /// \param m Pointer to a non-empty machine object (holds a machine instance). -/// \param ranges Receives the memory ranges as a JSON object in a string, +/// \param ranges Receives the address ranges as a JSON object in a string, /// guaranteed to remain valid only until the next CM_API function is called from the same thread. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_get_memory_ranges(const cm_machine *m, const char **ranges); +CM_API cm_error cm_get_address_ranges(const cm_machine *m, const char **ranges); /// \brief Obtains the root hash of the Merkle tree. /// \param m Pointer to a non-empty machine object (holds a machine instance). diff --git a/src/machine-config.cpp b/src/machine-config.cpp index df6d4e931..c52a6118d 100644 --- a/src/machine-config.cpp +++ b/src/machine-config.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/src/machine-reg.h b/src/machine-reg.h index 48f868b5c..d8865c4a9 100644 --- a/src/machine-reg.h +++ b/src/machine-reg.h @@ -17,8 +17,8 @@ #ifndef MACHINE_REG_H #define MACHINE_REG_H -#include "shadow-state.h" -#include "shadow-uarch-state.h" +#include "shadow-state-address-range.h" +#include "shadow-uarch-state-address-range.h" /// \file /// \brief Cartesi machine registers diff --git a/src/machine-state.h b/src/machine-state.h index 2d3ae4dbd..8fcfa1c6e 100644 --- a/src/machine-state.h +++ b/src/machine-state.h @@ -14,21 +14,19 @@ // with this program (see COPYING). If not, see . // -#ifndef STATE_H -#define STATE_H +#ifndef MACHINE_STATE_H +#define MACHINE_STATE_H /// \file /// \brief Cartesi machine state structure definition. #include #include +#include -#include - -#include "pma-constants.h" -#include "pma.h" +#include "address-range.h" #include "riscv-constants.h" -#include "shadow-tlb.h" +#include "tlb.h" namespace cartesi { @@ -116,10 +114,7 @@ struct machine_state { /// Soft yield bool soft_yield{}; - /// Map of physical memory ranges - boost::container::static_vector pmas; - - pma_entry empty_pma; ///< fallback to PMA for empty range + std::vector pmas; ///< Indices of address ranges that interpret can find }; } // namespace cartesi diff --git a/src/machine.cpp b/src/machine.cpp index d4f37911a..64aa4fc7b 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -32,29 +33,28 @@ #include #include #include - -#include +#include #include "access-log.h" -#include "clint-factory.h" +#include "address-range-description.h" +#include "address-range.h" +#include "clint-address-range.h" #include "compiler-defines.h" #include "device-state-access.h" #include "dtb.h" #include "host-addr.h" -#include "htif-factory.h" -#include "htif.h" +#include "htif-address-range.h" +#include "htif-constants.h" #include "i-device-state-access.h" #include "i-hasher.h" #include "interpret.h" #include "is-pristine.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-reg.h" #include "machine-runtime-config.h" -#include "plic-factory.h" +#include "memory-address-range.h" +#include "plic-address-range.h" #include "pma-constants.h" -#include "pma-defines.h" -#include "pma.h" #include "record-send-cmio-state-access.h" #include "record-step-state-access.h" #include "replay-send-cmio-state-access.h" @@ -62,14 +62,10 @@ #include "riscv-constants.h" #include "rtc.h" #include "send-cmio-response.h" -#include "shadow-pmas-factory.h" -#include "shadow-pmas.h" -#include "shadow-state-factory.h" -#include "shadow-state.h" -#include "shadow-tlb-factory.h" -#include "shadow-tlb.h" -#include "shadow-uarch-state-factory.h" -#include "shadow-uarch-state.h" +#include "shadow-pmas-address-range.h" +#include "shadow-state-address-range.h" +#include "shadow-tlb-address-range.h" +#include "shadow-uarch-state-address-range.h" #include "state-access.h" #include "strict-aliasing.h" #include "tlb.h" @@ -85,13 +81,10 @@ #include "uarch-state-access.h" #include "uarch-step.h" #include "unique-c-ptr.h" -#include "virtio-console.h" -#include "virtio-device.h" -#include "virtio-factory.h" -#include "virtio-net-carrier-slirp.h" -#include "virtio-net-carrier-tuntap.h" -#include "virtio-net.h" -#include "virtio-p9fs.h" +#include "virtio-console-address-range.h" +#include "virtio-net-tuntap-address-range.h" +#include "virtio-net-user-address-range.h" +#include "virtio-p9fs-address-range.h" /// \file /// \brief Cartesi machine implementation @@ -100,115 +93,55 @@ namespace cartesi { using namespace std::string_literals; -const pma_entry::flags machine::m_ram_flags{.R = true, - .W = true, - .X = true, - .IR = true, - .IW = true, - .DID = PMA_ISTART_DID::memory}; - -// When we pass a RNG seed in a FDT stored in DTB, -// Linux will wipe out its contents as a security measure, -// therefore we need to make DTB writable, otherwise boot will hang. -const pma_entry::flags machine::m_dtb_flags{.R = true, - .W = true, - .X = true, - .IR = true, - .IW = true, - .DID = PMA_ISTART_DID::memory}; - -const pma_entry::flags machine::m_flash_drive_flags{.R = true, - .W = true, - .X = false, - .IR = true, - .IW = true, - .DID = PMA_ISTART_DID::flash_drive}; - -const pma_entry::flags machine::m_cmio_rx_buffer_flags{.R = true, - .W = false, - .X = false, - .IR = true, - .IW = true, - .DID = PMA_ISTART_DID::cmio_rx_buffer}; - -const pma_entry::flags machine::m_cmio_tx_buffer_flags{.R = true, - .W = true, - .X = false, - .IR = true, - .IW = true, - .DID = PMA_ISTART_DID::cmio_tx_buffer}; - -pma_entry machine::make_memory_range_pma_entry(const std::string &description, const memory_range_config &c) { - if (c.image_filename.empty()) { - return make_callocd_memory_pma_entry(description, c.start, c.length); - } - return make_mmapd_memory_pma_entry(description, c.start, c.length, c.image_filename, c.shared); -} - -pma_entry machine::make_flash_drive_pma_entry(const std::string &description, const memory_range_config &c) { - return make_memory_range_pma_entry(description, c).set_flags(m_flash_drive_flags); -} - -pma_entry machine::make_cmio_rx_buffer_pma_entry(const cmio_buffer_config &c) { - const auto description = "cmio rx buffer memory range"s; - if (!c.image_filename.empty()) { - return make_mmapd_memory_pma_entry(description, PMA_CMIO_RX_BUFFER_START, PMA_CMIO_RX_BUFFER_LENGTH, - c.image_filename, c.shared) - .set_flags(m_cmio_rx_buffer_flags); - } - return make_callocd_memory_pma_entry(description, PMA_CMIO_RX_BUFFER_START, PMA_CMIO_RX_BUFFER_LENGTH) - .set_flags(m_cmio_rx_buffer_flags); -} - -pma_entry machine::make_cmio_tx_buffer_pma_entry(const cmio_buffer_config &c) { - const auto description = "cmio tx buffer memory range"s; - if (!c.image_filename.empty()) { - return make_mmapd_memory_pma_entry(description, PMA_CMIO_TX_BUFFER_START, PMA_CMIO_TX_BUFFER_LENGTH, - c.image_filename, c.shared) - .set_flags(m_cmio_tx_buffer_flags); - } - return make_callocd_memory_pma_entry(description, PMA_CMIO_TX_BUFFER_START, PMA_CMIO_TX_BUFFER_LENGTH) - .set_flags(m_cmio_tx_buffer_flags); -} - -pma_entry &machine::register_pma_entry(pma_entry &&pma) { - if (decltype(m_s.pmas)::capacity() <= m_s.pmas.size()) { - throw std::runtime_error{"too many PMAs when adding "s + pma.get_description()}; - } - auto start = pma.get_start(); - if ((start & (PMA_PAGE_SIZE - 1)) != 0) { - throw std::invalid_argument{"start of "s + pma.get_description() + " ("s + std::to_string(start) + - ") must be aligned to page boundary of "s + std::to_string(PMA_PAGE_SIZE) + " bytes"s}; - } - auto length = pma.get_length(); - if ((length & (PMA_PAGE_SIZE - 1)) != 0) { - throw std::invalid_argument{"length of "s + pma.get_description() + " ("s + std::to_string(length) + - ") must be multiple of page size "s + std::to_string(PMA_PAGE_SIZE)}; - } - // Check PMA range, when not the sentinel PMA entry - if (length != 0 || start != 0) { - if (length == 0) { - throw std::invalid_argument{"length of "s + pma.get_description() + " cannot be zero"s}; - } - // Checks if PMA is in addressable range, safe unsigned overflows - if (start > PMA_ADDRESSABLE_MASK || (length - 1) > (PMA_ADDRESSABLE_MASK - start)) { - throw std::invalid_argument{ - "range of "s + pma.get_description() + " must use at most 56 bits to be addressable"s}; - } +static const auto throw_invalid_argument = [](const char *err) { throw std::invalid_argument{err}; }; + +/// \brief Creates a memory address range. +/// \param d Description of address range for use in error messages. +/// \param start Target physical address where range starts. +/// \param length Length of range, in bytes. +/// \param f Flags for address range. +/// \param image_filename Path to backing file. +/// \param shared If true, changes to memory range reflect in backing file. +/// \returns New address range with flags already set. +/// \details If \p image_filename is non-empty, return a memory-mapped range, otherwise use calloc. +static inline auto make_memory_address_range(const std::string &d, uint64_t start, uint64_t length, pma_flags flags, + const std::string &image_filename, bool shared) { + if (image_filename.empty() && shared) { + throw std::invalid_argument{"shared address range requires non-empty image filename when initializing " + d}; + } + if (image_filename.empty() || length > static_cast(os_get_file_length(image_filename.c_str()))) { + return make_callocd_memory_address_range(d, start, length, flags, image_filename); } + return make_mmapd_memory_address_range(d, start, length, flags, image_filename, shared); +} + +void machine::check_address_range(const address_range &ar, register_where where) { + if (!where.interpret && !where.merkle) { + throw std::runtime_error{"address range "s + ar.get_description() + " must be registered somwhere"s}; + } + if (where.interpret && m_s.pmas.size() >= PMA_MAX) { + throw std::runtime_error{"too many address ranges when adding "s + ar.get_description()}; + } + const auto start = ar.get_start(); + const auto length = ar.get_length(); + // Checks if new range is machine addressable space (safe unsigned overflows) + if (start > PMA_ADDRESSABLE_MASK || (length > 0 && (length - 1) > (PMA_ADDRESSABLE_MASK - start))) { + throw std::invalid_argument{ + "address range of "s + ar.get_description() + " must use at most 56 bits to be addressable"s}; + } + const auto length_bit_ceil = ar.get_length_bit_ceil(); // Range A overlaps with B if A starts before B ends and A ends after B starts - for (const auto &existing_pma : m_s.pmas) { - if (start < existing_pma.get_start() + existing_pma.get_length() && start + length > existing_pma.get_start()) { - throw std::invalid_argument{"range of "s + pma.get_description() + " overlaps with range of existing "s + - existing_pma.get_description()}; + for (const auto &existing : m_ars) { + const auto existing_start = existing->get_start(); + const auto existing_length_bit_ceil = existing->get_length_bit_ceil(); + if (start < existing_start + existing_length_bit_ceil && start + length_bit_ceil > existing_start) { + throw std::invalid_argument{"address range of "s + ar.get_description() + + " overlaps with address range of existing "s + existing->get_description()}; } } - pma.set_index(static_cast(m_s.pmas.size())); - m_s.pmas.push_back(std::move(pma)); - return m_s.pmas.back(); } -static bool DID_is_protected(PMA_ISTART_DID DID) { +static bool is_protected(PMA_ISTART_DID DID) { switch (DID) { case PMA_ISTART_DID::memory: case PMA_ISTART_DID::flash_drive: @@ -220,15 +153,16 @@ static bool DID_is_protected(PMA_ISTART_DID DID) { } } -void machine::replace_memory_range(const memory_range_config &range) { - for (auto &pma : m_s.pmas) { - if (pma.get_start() == range.start && pma.get_length() == range.length) { - const auto curr = pma.get_istart_DID(); - if (pma.get_length() == 0 || DID_is_protected(curr)) { - throw std::invalid_argument{"attempt to replace a protected range "s + pma.get_description()}; +void machine::replace_memory_range(const memory_range_config &config) { + for (auto &existing : m_ars) { + if (existing->get_start() == config.start && existing->get_length() == config.length) { + if (!existing->is_memory() || is_protected(existing->get_driver_id())) { + throw std::invalid_argument{"attempt to replace a protected range "s + existing->get_description()}; } - // replace range preserving original flags - pma = make_memory_range_pma_entry(pma.get_description(), range).set_flags(pma.get_flags()); + // Replace range, preserving original flags. + // This will automatically start with all pages dirty. + existing = make_moved_unique(make_memory_address_range(existing->get_description(), existing->get_start(), + existing->get_length(), existing->get_flags(), config.image_filename, config.shared)); return; } } @@ -245,22 +179,35 @@ void machine::init_uarch(const uarch_config &c) { write_reg(machine_reg_enum(reg::uarch_x0, i), c.processor.x[i]); } // Register shadow state - m_us.shadow_state = make_shadow_uarch_state_pma_entry(PMA_SHADOW_UARCH_STATE_START, PMA_SHADOW_UARCH_STATE_LENGTH); + m_us.shadow_state = ®ister_address_range(make_shadow_uarch_state_address_range(PMA_SHADOW_UARCH_STATE_START, + PMA_SHADOW_UARCH_STATE_LENGTH, throw_invalid_argument), + register_where{.merkle = true, .interpret = false}); // Register RAM + if (uarch_pristine_ram_len > PMA_UARCH_RAM_LENGTH) { + throw std::runtime_error("embedded uarch RAM image does not fit in uarch memory"); + } + static constexpr pma_flags uram_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, + .W = true, + .X = true, + .IR = true, + .IW = true, + .DID = PMA_ISTART_DID::memory, + }; constexpr auto ram_description = "uarch RAM"; - if (!c.ram.image_filename.empty()) { - // Load RAM image from file - m_us.ram = - make_callocd_memory_pma_entry(ram_description, PMA_UARCH_RAM_START, UARCH_RAM_LENGTH, c.ram.image_filename) - .set_flags(m_ram_flags); + if (c.ram.image_filename.empty()) { + m_us.ram = ®ister_address_range( + make_callocd_memory_address_range(ram_description, PMA_UARCH_RAM_START, UARCH_RAM_LENGTH, uram_flags), + register_where{.merkle = true, .interpret = false}); + memcpy(m_us.ram->get_host_memory(), uarch_pristine_ram, uarch_pristine_ram_len); } else { - // Load embedded pristine RAM image - m_us.ram = make_callocd_memory_pma_entry(ram_description, PMA_UARCH_RAM_START, PMA_UARCH_RAM_LENGTH) - .set_flags(m_ram_flags); - if (uarch_pristine_ram_len > m_us.ram.get_length()) { - throw std::runtime_error("embedded uarch RAM image does not fit in uarch ram PMA"); - } - memcpy(m_us.ram.get_memory().get_host_memory(), uarch_pristine_ram, uarch_pristine_ram_len); + m_us.ram = + ®ister_address_range(make_memory_address_range(ram_description, PMA_UARCH_RAM_START, UARCH_RAM_LENGTH, + uram_flags, c.ram.image_filename, false /* not shared */), + register_where{.merkle = true, .interpret = false}); } } @@ -334,19 +281,47 @@ void machine::init_processor(processor_config &p, const machine_runtime_config & write_reg(reg::iunrep, p.iunrep); } -void machine::init_ram_pma(const ram_config &ram) { - register_pma_entry( - make_callocd_memory_pma_entry("RAM"s, PMA_RAM_START, ram.length, ram.image_filename).set_flags(m_ram_flags)); -} - -void machine::init_flash_drive_pmas(flash_drive_configs &flash_drive) { +void machine::init_ram_ar(const ram_config &ram) { + // Flags for RAM + static constexpr pma_flags ram_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, + .W = true, + .X = true, + .IR = true, + .IW = true, + .DID = PMA_ISTART_DID::memory, + }; + if (ram.length == 0) { + throw std::invalid_argument("RAM length cannot be zero"); + } + register_address_range( + make_callocd_memory_address_range("RAM"s, PMA_RAM_START, ram.length, ram_flags, ram.image_filename), + register_where{.merkle = true, .interpret = true}); +} + +void machine::init_flash_drive_ars(flash_drive_configs &flash_drive) { + // Flags for flash drives + static const pma_flags flash_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, + .W = true, + .X = false, + .IR = true, + .IW = true, + .DID = PMA_ISTART_DID::flash_drive, + }; // Register all flash drives int i = 0; // NOLINT(misc-const-correctness) for (auto &f : flash_drive) { const std::string flash_description = "flash drive "s + std::to_string(i); // Auto detect flash drive start address if (f.start == UINT64_C(-1)) { - f.start = PMA_DRIVE_START + PMA_DRIVE_OFFSET_DEF * i; + f.start = PMA_DRIVE_START + PMA_DRIVE_OFFSET * i; } // Auto detect flash drive image length if (f.length == UINT64_C(-1)) { @@ -364,69 +339,80 @@ void machine::init_flash_drive_pmas(flash_drive_configs &flash_drive) { } f.length = length; } - register_pma_entry(make_flash_drive_pma_entry(flash_description, f)); + register_address_range( + make_memory_address_range(flash_description, f.start, f.length, flash_flags, f.image_filename, f.shared), + register_where{.merkle = true, .interpret = true}); i++; } } -void machine::init_virtio_pmas(const virtio_configs &v, uint64_t iunrep) { +void machine::init_virtio_ars(const virtio_configs &cs, uint64_t iunrep) { // Initialize VirtIO devices - if (!v.empty()) { - // VirtIO devices are disallowed in unreproducible mode - if (iunrep == 0) { - throw std::invalid_argument{"virtio devices are only supported in unreproducible machines"}; - } - for (const auto &vdev_config_entry : v) { - std::visit( - [&](const auto &vdev_config) { - using T = std::decay_t; - std::string pma_name = "VirtIO device"; // NOLINT(misc-const-correctness): // no, can't be const - std::unique_ptr vdev; - if constexpr (std::is_same_v) { - pma_name = "VirtIO Console"; - vdev = std::make_unique(m_vdevs.size()); - } else if constexpr (std::is_same_v) { + if (cs.empty()) { + return; + } + // VirtIO devices are disallowed in unreproducible mode + if (iunrep == 0) { + throw std::invalid_argument{"virtio devices are only supported in unreproducible machines"}; + } + uint32_t virtio_idx = 0; + for (const auto &c : cs) { + const auto where = register_where{.merkle = false, .interpret = true}; + const auto visitor = overloads{ + [this, virtio_idx, where](const virtio_console_config &) { + const auto start = PMA_FIRST_VIRTIO_START + (virtio_idx * PMA_VIRTIO_LENGTH); + register_address_range(make_virtio_console_address_range(start, PMA_VIRTIO_LENGTH, virtio_idx), where); + }, + [this, virtio_idx, where](const virtio_p9fs_config &c) { #ifdef HAVE_POSIX_FS - pma_name = "VirtIO 9P"; - vdev = std::make_unique(m_vdevs.size(), vdev_config.tag, - vdev_config.host_directory); + const auto start = PMA_FIRST_VIRTIO_START + (virtio_idx * PMA_VIRTIO_LENGTH); + register_address_range( + make_virtio_p9fs_address_range(start, PMA_VIRTIO_LENGTH, virtio_idx, c.tag, c.host_directory), + where); #else - throw std::invalid_argument("virtio 9p device is unsupported in this platform"); + (void) c; + (void) this; + (void) virtio_idx; + (void) where; + throw std::invalid_argument{"virtio 9p device is unsupported in this platform"}; #endif - } else if constexpr (std::is_same_v) { -#ifdef HAVE_SLIRP - pma_name = "VirtIO Net User"; - vdev = std::make_unique(m_vdevs.size(), - std::make_unique(vdev_config)); + }, + [this, virtio_idx, where](const virtio_net_tuntap_config &c) { +#ifdef HAVE_TUNTAP + const auto start = PMA_FIRST_VIRTIO_START + (virtio_idx * PMA_VIRTIO_LENGTH); + register_address_range( + make_virtio_net_tuntap_address_range(start, PMA_VIRTIO_LENGTH, virtio_idx, c.iface), where); #else - throw std::invalid_argument("virtio network user device is unsupported in this platform"); - + (void) c; + (void) this; + (void) virtio_idx; + (void) where; + throw std::invalid_argument("virtio network TUN/TAP device is unsupported in this platform"); #endif - } else if constexpr (std::is_same_v) { -#ifdef HAVE_TUNTAP - pma_name = "VirtIO Net TUN/TAP"; - vdev = std::make_unique(m_vdevs.size(), - std::make_unique(vdev_config.iface)); + }, + [this, virtio_idx, where](const virtio_net_user_config &c) { +#ifdef HAVE_SLIRP + const auto start = PMA_FIRST_VIRTIO_START + (virtio_idx * PMA_VIRTIO_LENGTH); + register_address_range(make_virtio_net_user_address_range(start, PMA_VIRTIO_LENGTH, virtio_idx, c), + where); #else - - throw std::invalid_argument("virtio network TUN/TAP device is unsupported in this platform"); + (void) c; + (void) this; + (void) virtio_idx; + (void) where; + throw std::invalid_argument("virtio network user device is unsupported in this platform"); #endif - } else { - throw std::invalid_argument("invalid virtio device configuration"); - } - register_pma_entry( - make_virtio_pma_entry(PMA_FIRST_VIRTIO_START + (vdev->get_virtio_index() * PMA_VIRTIO_LENGTH), - PMA_VIRTIO_LENGTH, pma_name, &virtio_driver, vdev.get())); - m_vdevs.push_back(std::move(vdev)); - }, - vdev_config_entry); - } + }, + [](const auto &) { throw std::invalid_argument("invalid virtio device configuration"); }}; + std::visit(visitor, c); + ++virtio_idx; } } -void machine::init_htif_pma(const htif_config &h, const htif_runtime_config &r, uint64_t iunrep) { +void machine::init_htif_ar(const htif_config &h) { // Register HTIF device - register_pma_entry(make_htif_pma_entry(PMA_HTIF_START, PMA_HTIF_LENGTH)); + register_address_range(make_htif_address_range(PMA_HTIF_START, PMA_HTIF_LENGTH, throw_invalid_argument), + register_where{.merkle = false, .interpret = true}); // Copy HTIF state to from config to machine write_reg(reg::htif_tohost, h.tohost); write_reg(reg::htif_fromhost, h.fromhost); @@ -439,78 +425,93 @@ void machine::init_htif_pma(const htif_config &h, const htif_runtime_config &r, const uint64_t htif_iyield = static_cast(h.yield_manual) << HTIF_YIELD_CMD_MANUAL | static_cast(h.yield_automatic) << HTIF_YIELD_CMD_AUTOMATIC; write_reg(reg::htif_iyield, htif_iyield); - // Initialize TTY if console input is enabled - if (h.console_getchar || has_virtio_console()) { - if (iunrep == 0) { - throw std::invalid_argument{"TTY stdin is only supported in unreproducible machines"}; - } - os_open_tty(); - } - os_silence_putchar(r.no_console_putchar); -} - -void machine::init_cmio_pmas(const cmio_config &c) { - // Register cmio memory ranges - register_pma_entry(make_cmio_tx_buffer_pma_entry(c.tx_buffer)); - register_pma_entry(make_cmio_rx_buffer_pma_entry(c.rx_buffer)); } -void machine::init_merkle_pmas() { - // Include machine PMAs in set considered by the Merkle tree. - for (auto &pma : m_s.pmas) { - if (pma.get_length() != 0) { - m_merkle_pmas.push_back(&pma); - } - } - m_merkle_pmas.push_back(&m_us.shadow_state); - m_merkle_pmas.push_back(&m_us.ram); - // Sort it by increasing start address - std::ranges::sort(m_merkle_pmas, [](const auto *a, const auto *b) { return a->get_start() < b->get_start(); }); -} - -void machine::init_memory_range_descrs() { - // Initialize memory range descriptions returned by get_memory_ranges method - for (const auto *pma : m_merkle_pmas) { - if (pma->get_length() != 0) { - m_mrds.push_back(machine_memory_range_descr{.start = pma->get_start(), - .length = pma->get_length(), - .description = pma->get_description()}); - } - } +void machine::init_cmio_ars(const cmio_config &c) { + static const pma_flags tx_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, + .W = true, + .X = false, + .IR = true, + .IW = true, + .DID = PMA_ISTART_DID::cmio_tx_buffer, + }; + static const pma_flags rx_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, + .W = false, + .X = false, + .IR = true, + .IW = true, + .DID = PMA_ISTART_DID::cmio_rx_buffer, + }; + register_address_range(make_memory_address_range("CMIO tx buffer memory range"s, PMA_CMIO_TX_BUFFER_START, + PMA_CMIO_TX_BUFFER_LENGTH, tx_flags, c.tx_buffer.image_filename, c.tx_buffer.shared), + register_where{.merkle = true, .interpret = true}); + register_address_range(make_memory_address_range("CMIO rx buffer memory range"s, PMA_CMIO_RX_BUFFER_START, + PMA_CMIO_RX_BUFFER_LENGTH, rx_flags, c.rx_buffer.image_filename, c.rx_buffer.shared), + register_where{.merkle = true, .interpret = true}); +} + +void machine::init_merkle_ars() { + // Sort indices by the starting address of the range they point to in the m_ars array + std::ranges::sort( + m_merkle_ars, [](const auto &a, const auto &b) { return a.get_start() < b.get_start(); }, + [this](const auto i) { return *m_ars[i]; }); +} + +void machine::init_ars_descriptions() { + // Initialize memory range descriptions returned by get_address_ranges method + auto src = m_ars | std::views::filter([](auto &ar) { return ar->get_length() != 0; }) | + std::views::transform([](auto &ar) { + return address_range_description{.start = ar->get_start(), + .length = ar->get_length(), + .description = ar->get_description()}; + }); + std::ranges::copy(src, std::back_inserter(m_ards)); + std::ranges::sort(m_ards, [](auto &a, auto &b) { return a.start < b.start; }); } -void machine::init_clint_pma(const clint_config &c) { +void machine::init_clint_ar(const clint_config &c) { // Register CLINT device - register_pma_entry(make_clint_pma_entry(PMA_CLINT_START, PMA_CLINT_LENGTH)); + register_address_range(make_clint_address_range(PMA_CLINT_START, PMA_CLINT_LENGTH, throw_invalid_argument), + register_where{.merkle = false, .interpret = true}); // Copy CLINT state to from config to machine write_reg(reg::clint_mtimecmp, c.mtimecmp); } -void machine::init_plic_pma(const plic_config &p) { +void machine::init_plic_ar(const plic_config &p) { // Register PLIC device - register_pma_entry(make_plic_pma_entry(PMA_PLIC_START, PMA_PLIC_LENGTH)); + register_address_range(make_plic_address_range(PMA_PLIC_START, PMA_PLIC_LENGTH, throw_invalid_argument), + register_where{.merkle = false, .interpret = true}); // Copy PLIC state from config to machine write_reg(reg::plic_girqpend, p.girqpend); write_reg(reg::plic_girqsrvd, p.girqsrvd); } -void machine::init_sentinel_pmas() { +void machine::init_sentinel_ars() { // Last, add empty sentinels until we reach capacity (need at least one sentinel) - register_pma_entry(make_empty_pma_entry("sentinel"s, 0, 0)); - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - if (m_s.pmas.capacity() != PMA_MAX) { - throw std::logic_error{"PMAs array must be able to hold at least PMA_MAX entries"}; - } + // ??D I will remove these sentinels from the list + register_address_range(make_empty_address_range("sentinel"), register_where{.merkle = false, .interpret = true}); while (m_s.pmas.size() < PMA_MAX) { - register_pma_entry(make_empty_pma_entry("sentinel"s, 0, 0)); + register_address_range(make_empty_address_range("sentinel"), + register_where{.merkle = false, .interpret = true}); } } -void machine::init_shadow_pmas_contents(pma_entry &shadow_pmas) const { - // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) - shadow_pmas_init(m_s.pmas, - reinterpret_cast(shadow_pmas.get_memory_noexcept().get_host_memory())); - // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) +void machine::init_shadow_pmas_contents(memory_address_range &shadow_pmas) const { + static_assert(sizeof(shadow_pmas_state) == PMA_MAX * 2 * sizeof(uint64_t), "inconsistent shadow PMAs length"); + static_assert(PMA_SHADOW_PMAS_LENGTH >= sizeof(shadow_pmas_state), "shadow PMAs not long enough"); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto &dest = *reinterpret_cast(shadow_pmas.get_host_memory()); + std::ranges::transform(m_s.pmas, dest.begin(), [this](auto i) { + return shadow_pmas_entry{.istart = m_ars[i]->get_istart(), .ilength = m_ars[i]->get_ilength()}; + }); } void machine::init_tlb_contents(const std::string &image_filename) { @@ -535,9 +536,28 @@ void machine::init_tlb_contents(const std::string &image_filename) { } } -void machine::init_dtb_contents(const machine_config &c, pma_entry &dtb) { +address_range &machine::init_dtb_ar(const dtb_config &c) { + // When we pass a RNG seed in a FDT stored in DTB, Linux will wipe out its contents as a security measure, + // therefore we need to make DTB writable, otherwise boot will hang. + static constexpr pma_flags dtb_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, + .W = true, + .X = true, + .IR = true, + .IW = true, + .DID = PMA_ISTART_DID::memory, + }; + return register_address_range(make_memory_address_range("DTB"s, PMA_DTB_START, PMA_DTB_LENGTH, dtb_flags, + c.image_filename, false /* not shared */), + register_where{.merkle = true, .interpret = true}); +} + +void machine::init_dtb_contents(const machine_config &c, address_range &dtb) { if (c.dtb.image_filename.empty()) { - dtb_init(c, dtb.get_memory().get_host_memory(), PMA_DTB_LENGTH); + dtb_init(c, dtb.get_host_memory(), PMA_DTB_LENGTH); } } @@ -547,23 +567,26 @@ machine::machine(machine_config c, machine_runtime_config r) : m_c{std::move(c)} init_uarch(m_c.uarch); init_processor(m_c.processor, m_r); m_s.soft_yield = m_r.soft_yield; - init_ram_pma(m_c.ram); + init_ram_ar(m_c.ram); // Will populate when initialization of PMAs is done - pma_entry &dtb = - register_pma_entry(make_callocd_memory_pma_entry("DTB"s, PMA_DTB_START, PMA_DTB_LENGTH, m_c.dtb.image_filename) - .set_flags(m_dtb_flags)); - init_flash_drive_pmas(m_c.flash_drive); - init_cmio_pmas(m_c.cmio); - init_htif_pma(m_c.htif, m_r.htif, m_c.processor.iunrep); - init_clint_pma(m_c.clint); - init_plic_pma(m_c.plic); + auto &dtb = init_dtb_ar(m_c.dtb); + init_flash_drive_ars(m_c.flash_drive); + init_cmio_ars(m_c.cmio); + init_htif_ar(m_c.htif); + init_clint_ar(m_c.clint); + init_plic_ar(m_c.plic); // Will populate when initialization of PMAs is done - register_pma_entry(make_shadow_tlb_pma_entry(PMA_SHADOW_TLB_START, PMA_SHADOW_TLB_LENGTH)); - register_pma_entry(make_shadow_state_pma_entry(PMA_SHADOW_STATE_START, PMA_SHADOW_STATE_LENGTH)); + register_address_range( + make_shadow_tlb_address_range(PMA_SHADOW_TLB_START, PMA_SHADOW_TLB_LENGTH, throw_invalid_argument), + register_where{.merkle = true, .interpret = false}); + register_address_range( + make_shadow_state_address_range(PMA_SHADOW_STATE_START, PMA_SHADOW_STATE_LENGTH, throw_invalid_argument), + register_where{.merkle = true, .interpret = false}); // Will populate when initialization of PMAs is done - pma_entry &shpmas = register_pma_entry(make_shadow_pmas_pma_entry(PMA_SHADOW_PMAS_START, PMA_SHADOW_PMAS_LENGTH)); - init_virtio_pmas(m_c.virtio, m_c.processor.iunrep); - init_sentinel_pmas(); + auto &shpmas = register_address_range(make_shadow_pmas_address_range(PMA_SHADOW_PMAS_START, PMA_SHADOW_PMAS_LENGTH), + register_where{.merkle = true, .interpret = true}); + init_virtio_ars(m_c.virtio, m_c.processor.iunrep); + init_sentinel_ars(); // Populate shadow PMAs contents. // This must be done after all PMA entries are already registered, so we encode them into the shadow init_shadow_pmas_contents(shpmas); @@ -573,14 +596,26 @@ machine::machine(machine_config c, machine_runtime_config r) : m_c{std::move(c)} // Initialize DTB contents. // This must be done after all PMA entries are already registered, so we can lookup flash drive parameters init_dtb_contents(m_c, dtb); - init_merkle_pmas(); - init_memory_range_descrs(); + init_merkle_ars(); + init_ars_descriptions(); + init_tty(m_c.htif, m_r.htif, m_c.processor.iunrep); // Disable SIGPIPE handler, because this signal can be raised and terminate the emulator process // when calling write() on closed file descriptors. // This can happen with the stdout console file descriptors or network file descriptors. os_disable_sigpipe(); } +void machine::init_tty(const htif_config &h, const htif_runtime_config &r, uint64_t iunrep) const { + // Initialize TTY if console input is enabled + if (h.console_getchar || has_virtio_console()) { + if (iunrep == 0) { + throw std::invalid_argument{"TTY stdin is only supported in unreproducible machines"}; + } + os_open_tty(); + } + os_silence_putchar(r.no_console_putchar); +} + static void load_hash(const std::string &dir, machine::hash_type &h) { auto name = dir + "/hash"; auto fp = make_unique_fopen(name.c_str(), "rb"); @@ -608,16 +643,16 @@ machine::machine(const std::string &dir, machine_runtime_config r) : machine{mac } void machine::prepare_virtio_devices_select(select_fd_sets *fds, uint64_t *timeout_us) { - for (auto &vdev : m_vdevs) { - vdev->prepare_select(fds, timeout_us); + for (auto *v : m_virtio_ars) { + v->prepare_select(fds, timeout_us); } } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) bool machine::poll_selected_virtio_devices(int select_ret, select_fd_sets *fds, i_device_state_access *da) { bool interrupt_requested = false; // NOLINT(misc-const-correctness) - for (auto &vdev : m_vdevs) { - interrupt_requested |= vdev->poll_selected(select_ret, fds, da); + for (auto *v : m_virtio_ars) { + interrupt_requested |= v->poll_selected(select_ret, fds, da); } return interrupt_requested; } @@ -631,13 +666,13 @@ bool machine::poll_virtio_devices(uint64_t *timeout_us, i_device_state_access *d } bool machine::has_virtio_devices() const { - return !m_vdevs.empty(); + return !m_virtio_ars.empty(); } bool machine::has_virtio_console() const { // When present, the console device is guaranteed to be the first VirtIO device, // therefore we only need to check the first device. - return !m_vdevs.empty() && m_vdevs[0]->get_device_id() == VIRTIO_DEVICE_CONSOLE; + return has_virtio_devices() && m_virtio_ars[0]->get_device_id() == VIRTIO_DEVICE_CONSOLE; } bool machine::has_htif_console() const { @@ -743,18 +778,16 @@ machine_config machine::get_serialization_config() const { return c; } -static void store_device_pma(const machine &m, const pma_entry &pma, const std::string &dir) { - if (!pma.get_istart_IO()) { - throw std::runtime_error{"attempt to save non-device PMA"}; +static void store_device_address_range(const machine &m, const address_range &ar, const std::string &dir) { + if (!ar.is_device()) { + throw std::runtime_error{"attempt to save non-device address range "s + ar.get_description()}; } auto scratch = make_unique_calloc(PMA_PAGE_SIZE); // will throw if it fails - auto name = machine_config::get_image_filename(dir, pma.get_start(), pma.get_length()); - auto fp = make_unique_fopen(name.c_str(), "wb"); - for (uint64_t page_start_in_range = 0; page_start_in_range < pma.get_length(); - page_start_in_range += PMA_PAGE_SIZE) { + auto name = machine_config::get_image_filename(dir, ar.get_start(), ar.get_length()); + auto fp = make_unique_fopen(name.c_str(), "wb"); // will throw if it fails + for (uint64_t offset = 0; offset < ar.get_length(); offset += PMA_PAGE_SIZE) { const unsigned char *page_data = nullptr; - auto peek = pma.get_peek(); - if (!peek(pma, m, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { + if (!ar.peek(m, offset, PMA_PAGE_SIZE, &page_data, scratch.get())) { throw std::runtime_error{"peek failed"}; } if (page_data == nullptr) { @@ -767,14 +800,13 @@ static void store_device_pma(const machine &m, const pma_entry &pma, const std:: } } -static void store_memory_pma(const pma_entry &pma, const std::string &dir) { - if (!pma.get_istart_M()) { - throw std::runtime_error{"attempt to save non-memory PMA"}; +static void store_memory_address_range(const address_range &ar, const std::string &dir) { + if (!ar.is_memory()) { + throw std::runtime_error{"attempt to save non-memory address-range "s + ar.get_description()}; } - auto name = machine_config::get_image_filename(dir, pma.get_start(), pma.get_length()); + auto name = machine_config::get_image_filename(dir, ar.get_start(), ar.get_length()); auto fp = make_unique_fopen(name.c_str(), "wb"); - const pma_memory &mem = pma.get_memory(); - if (fwrite(mem.get_host_memory(), 1, pma.get_length(), fp.get()) != pma.get_length()) { + if (fwrite(ar.get_host_memory(), 1, ar.get_length(), fp.get()) != ar.get_length()) { throw std::runtime_error{"error writing to '" + name + "'"}; } } @@ -788,8 +820,8 @@ host_addr machine::get_host_addr(uint64_t paddr, uint64_t pma_index) const { } void machine::mark_dirty_page(uint64_t paddr, uint64_t pma_index) { - auto &pma = m_s.pmas[static_cast(pma_index)]; - pma.mark_dirty_page(paddr - pma.get_start()); + auto &ar = read_pma(pma_index); + ar.mark_dirty_page(paddr - ar.get_start()); } void machine::mark_dirty_page(host_addr haddr, uint64_t pma_index) { @@ -832,8 +864,8 @@ void machine::check_shadow_tlb(TLB_set_index set_index, uint64_t slot_index, uin if (pma_index >= m_s.pmas.size()) { throw std::domain_error{prefix + "pma_index is out of range"s}; } - const auto &pma = m_s.pmas[pma_index]; - if (pma.get_length() == 0 || !pma.get_istart_M()) { + const auto &ar = read_pma(pma_index); + if (ar.get_length() == 0 || !ar.is_memory()) { throw std::invalid_argument{prefix + "pma_index does not point to memory range"s}; } if ((vaddr_page & PAGE_OFFSET_MASK) != 0) { @@ -843,8 +875,8 @@ void machine::check_shadow_tlb(TLB_set_index set_index, uint64_t slot_index, uin if ((paddr_page & PAGE_OFFSET_MASK) != 0) { throw std::invalid_argument{prefix + "vp_offset is not aligned"s}; } - const auto pma_end = pma.get_start() + (pma.get_length() - PMA_PAGE_SIZE); - if (paddr_page < pma.get_start() || paddr_page > pma_end) { + const auto pma_end = ar.get_start() + (ar.get_length() - PMA_PAGE_SIZE); + if (paddr_page < ar.get_start() || paddr_page > pma_end) { throw std::invalid_argument{prefix + "vp_offset is inconsistent with pma_index"s}; } } else if (pma_index != TLB_INVALID_PMA_INDEX || vp_offset != 0) { @@ -871,48 +903,40 @@ void machine::write_shadow_tlb(TLB_set_index set_index, uint64_t slot_index, uin } host_addr machine::get_hp_offset(uint64_t pma_index) const { - if (pma_index >= m_s.pmas.size()) { - throw std::domain_error{"PMA index is out of range (" + std::to_string(pma_index) + ")"}; - } - const auto &pma = m_s.pmas[static_cast(pma_index)]; - if (!pma.get_istart_M()) { - throw std::domain_error{"PMA is not memory (" + pma.get_description() + ")"}; + const auto &ar = read_pma(pma_index); + if (!ar.is_memory()) { + throw std::domain_error{"PMA index is not of memory range ("s + ar.get_description() + ")"}; } - auto haddr = cast_ptr_to_host_addr(pma.get_memory().get_host_memory()); - auto paddr = pma.get_start(); + auto haddr = cast_ptr_to_host_addr(ar.get_host_memory()); + auto paddr = ar.get_start(); return paddr - haddr; } -//??D now that m_merkle_pmas is sorted by start, maybe change this to a binary search? -const pma_entry &machine::find_pma_entry(uint64_t paddr, uint64_t length) const { - const static auto sentinel = make_empty_pma_entry("sentinel", 0, 0); - for (const auto *pma : m_merkle_pmas) { - // Check if data is in range - if (paddr >= pma->get_start() && pma->get_length() >= length && - paddr - pma->get_start() <= pma->get_length() - length) { - return *pma; +//??D now that m_merkle_ars is sorted by start, maybe change this to a binary search? +const address_range &machine::find_address_range(uint64_t paddr, uint64_t length) const noexcept { + static constexpr auto sentinel = make_empty_address_range("sentinel"); + for (const auto &ar : m_ars) { + if (ar->contains_absolute(paddr, length)) { + return *ar; } } return sentinel; } -void machine::store_pmas(const machine_config &c, const std::string &dir) const { +void machine::store_address_ranges(const machine_config &c, const std::string &dir) const { if (read_reg(reg::iunrep) != 0) { throw std::runtime_error{"cannot store PMAs of unreproducible machines"}; } - store_memory_pma(find_pma_entry(PMA_DTB_START), dir); - store_memory_pma(find_pma_entry(PMA_RAM_START), dir); - store_device_pma(*this, find_pma_entry(PMA_SHADOW_TLB_START), dir); - // Could iterate over PMAs checking for those with a drive DID - // but this is easier + store_memory_address_range(find_address_range(PMA_DTB_START), dir); + store_memory_address_range(find_address_range(PMA_RAM_START), dir); + store_device_address_range(*this, find_address_range(PMA_SHADOW_TLB_START), dir); + // Could iterate over PMAs checking for those with a drive DID but this is easier for (const auto &f : c.flash_drive) { - store_memory_pma(find_pma_entry(f.start), dir); - } - store_memory_pma(find_pma_entry(PMA_CMIO_RX_BUFFER_START), dir); - store_memory_pma(find_pma_entry(PMA_CMIO_TX_BUFFER_START), dir); - if (!m_us.ram.get_istart_E()) { - store_memory_pma(m_us.ram, dir); + store_memory_address_range(find_address_range(f.start), dir); } + store_memory_address_range(find_address_range(PMA_CMIO_RX_BUFFER_START), dir); + store_memory_address_range(find_address_range(PMA_CMIO_TX_BUFFER_START), dir); + store_memory_address_range(find_address_range(PMA_UARCH_RAM_START), dir); } static void store_hash(const machine::hash_type &h, const std::string &dir) { @@ -937,7 +961,7 @@ void machine::store(const std::string &dir) const { } auto c = get_serialization_config(); c.store(dir); - store_pmas(c, dir); + store_address_ranges(c, dir); } void machine::dump_insn_hist() { @@ -1782,15 +1806,16 @@ void machine::mark_write_tlb_dirty_pages() const { if (hot_slot.vaddr_page != TLB_INVALID_PAGE) { auto haddr_page = hot_slot.vaddr_page + hot_slot.vh_offset; const auto &cold_slot = cold_set[slot_index]; - if (cold_slot.pma_index >= m_s.pmas.size()) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto &ar = const_cast(read_pma(cold_slot.pma_index)); + if (ar.get_length() == 0 || !ar.is_memory()) { throw std::runtime_error{"could not mark dirty page for a TLB entry: TLB is corrupt"}; } auto paddr_page = get_paddr(haddr_page, cold_slot.pma_index); - pma_entry &pma = m_s.pmas[cold_slot.pma_index]; - if (!pma.contains(paddr_page, PMA_PAGE_SIZE)) { + if (!ar.contains_absolute(paddr_page, PMA_PAGE_SIZE)) { throw std::runtime_error{"could not mark dirty page for a TLB entry: TLB is corrupt"}; } - pma.mark_dirty_page(paddr_page - pma.get_start()); + ar.mark_dirty_page(paddr_page - ar.get_start()); } } } @@ -1807,19 +1832,17 @@ bool machine::verify_dirty_page_maps() const { // Go over the write TLB and mark as dirty all pages currently there mark_write_tlb_dirty_pages(); // Now go over all memory PMAs verifying that all dirty pages are marked - for (const auto &pma : m_s.pmas) { - auto peek = pma.get_peek(); - for (uint64_t page_start_in_range = 0; page_start_in_range < pma.get_length(); - page_start_in_range += PMA_PAGE_SIZE) { - const uint64_t page_address = pma.get_start() + page_start_in_range; - if (pma.get_istart_M()) { + for (const auto &ar : m_ars) { + for (uint64_t offset = 0; offset < ar->get_length(); offset += PMA_PAGE_SIZE) { + const uint64_t page_address = ar->get_start() + offset; + if (ar->is_memory()) { const unsigned char *page_data = nullptr; - peek(pma, *this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get()); + ar->peek(*this, offset, PMA_PAGE_SIZE, &page_data, scratch.get()); hash_type stored; hash_type real; m_t.get_page_node_hash(page_address, stored); m_t.get_page_node_hash(h, page_data, real); - const bool marked_dirty = pma.is_page_marked_dirty(page_start_in_range); + const bool marked_dirty = ar->is_page_marked_dirty(offset); const bool is_dirty = (real != stored); if (is_dirty && !marked_dirty) { broken = true; @@ -1829,8 +1852,8 @@ bool machine::verify_dirty_page_maps() const { std::cerr << " got " << real << '\n'; break; } - } else if (pma.get_istart_IO()) { - if (!pma.is_page_marked_dirty(page_start_in_range)) { + } else if (ar->is_device()) { + if (!ar->is_page_marked_dirty(offset)) { broken = true; std::cerr << std::setfill('0') << std::setw(8) << std::hex << page_address << " should have been dirty\n"; @@ -1856,10 +1879,9 @@ bool machine::update_merkle_tree() const { mark_write_tlb_dirty_pages(); // Now go over all PMAs and updating the Merkle tree m_t.begin_update(); - for (const auto &pma : m_merkle_pmas) { - auto peek = pma->get_peek(); + for (auto &ar : m_merkle_ars | std::views::transform([this](auto i) -> address_range & { return *m_ars[i]; })) { // Each PMA has a number of pages - auto pages_in_range = (pma->get_length() + PMA_PAGE_SIZE - 1) / PMA_PAGE_SIZE; + auto pages_in_range = (ar.get_length() + PMA_PAGE_SIZE - 1) / PMA_PAGE_SIZE; // For each PMA, we launch as many threads (n) as defined on concurrency // runtime config or as the hardware supports. const uint64_t n = get_task_concurrency(m_r.concurrency.update_merkle_tree); @@ -1872,15 +1894,15 @@ bool machine::update_merkle_tree() const { // Thread j is responsible for page i if i % n == j. for (uint64_t i = j; i < pages_in_range; i += n) { const uint64_t page_start_in_range = i * PMA_PAGE_SIZE; - const uint64_t page_address = pma->get_start() + page_start_in_range; + const uint64_t page_address = ar.get_start() + page_start_in_range; const unsigned char *page_data = nullptr; // Skip any clean pages - if (!pma->is_page_marked_dirty(page_start_in_range)) { + if (!ar.is_page_marked_dirty(page_start_in_range)) { continue; } // If the peek failed, or if it returned a page for update but // we failed updating it, the entire process failed - if (!peek(*pma, *this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { + if (!ar.peek(*this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { return false; } if (page_data != nullptr) { @@ -1914,7 +1936,7 @@ bool machine::update_merkle_tree() const { return false; } // Otherwise, mark all pages in PMA as clean and move on to next - pma->mark_pages_clean(); + ar.mark_pages_clean(); } const bool ret = m_t.end_update(gh); return ret; @@ -1925,8 +1947,8 @@ bool machine::update_merkle_tree_page(uint64_t address) { "PMA and machine_merkle_tree page sizes must match"); // Align address to beginning of page address &= ~(PMA_PAGE_SIZE - 1); - auto &pma = find_pma_entry(address); - const uint64_t page_start_in_range = address - pma.get_start(); + auto &ar = find_address_range(address); + const uint64_t page_start_in_range = address - ar.get_start(); machine_merkle_tree::hasher_type h; auto scratch = make_unique_calloc(PMA_PAGE_SIZE, std::nothrow_t{}); if (!scratch) { @@ -1934,13 +1956,12 @@ bool machine::update_merkle_tree_page(uint64_t address) { } m_t.begin_update(); const unsigned char *page_data = nullptr; - auto peek = pma.get_peek(); - if (!peek(pma, *this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { + if (!ar.peek(*this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { m_t.end_update(h); return false; } if (page_data != nullptr) { - const uint64_t page_address = pma.get_start() + page_start_in_range; + const uint64_t page_address = ar.get_start() + page_start_in_range; hash_type hash; m_t.get_page_node_hash(h, page_data, hash); if (!m_t.update_page_node_hash(page_address, hash)) { @@ -1948,7 +1969,7 @@ bool machine::update_merkle_tree_page(uint64_t address) { return false; } } - pma.mark_clean_page(page_start_in_range); + ar.mark_clean_page(page_start_in_range); return m_t.end_update(h); } @@ -2038,17 +2059,16 @@ machine_merkle_tree::proof_type machine::get_proof(uint64_t address, int log2_si // or entirely outside it. if (log2_size < machine_merkle_tree::get_log2_page_size()) { const uint64_t length = UINT64_C(1) << log2_size; - const auto &pma = find_pma_entry(address, length); + const auto &ar = find_address_range(address, length); auto scratch = make_unique_calloc(PMA_PAGE_SIZE); const unsigned char *page_data = nullptr; // If the PMA range is empty, we know the desired range is // entirely outside of any non-pristine PMA. // Therefore, the entire page where it lies is also pristine // Otherwise, the entire desired range is inside it. - if (!pma.get_istart_E()) { - const uint64_t page_start_in_range = (address - pma.get_start()) & (~(PMA_PAGE_SIZE - 1)); - auto peek = pma.get_peek(); - if (!peek(pma, *this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { + if (ar.get_length() != 0) { + const uint64_t page_start_in_range = (address - ar.get_start()) & (~(PMA_PAGE_SIZE - 1)); + if (!ar.peek(*this, page_start_in_range, PMA_PAGE_SIZE, &page_data, scratch.get())) { throw std::runtime_error{"PMA peek failed"}; } } @@ -2066,6 +2086,27 @@ machine_merkle_tree::proof_type machine::get_proof(uint64_t address, int log2_si return get_proof(address, log2_size, skip_merkle_tree_update); } +template +static inline void foreach_aligned_chunk(uint64_t start, uint64_t length, uint64_t alignment, F f) { + // Optional first chunk brings start to alignment + if (const auto rem = start % alignment; rem != 0) { + const auto first_length = std::min(length, alignment - rem); + f(start, first_length); + start += first_length; + length -= first_length; + } + // Intermediate chunks start aligned and cover exactly alignment bytes + while (length >= alignment) { + f(start, alignment); + start += alignment; + length -= alignment; + } + // Last chunk completes the span + if (length != 0) { + f(start, length); + } +} + void machine::read_memory(uint64_t paddr, unsigned char *data, uint64_t length) const { if (length == 0) { return; @@ -2073,6 +2114,8 @@ void machine::read_memory(uint64_t paddr, unsigned char *data, uint64_t length) if (data == nullptr) { throw std::invalid_argument{"invalid data buffer"}; } + //??D this loop can be much improved and simplified... I don't remember why I am going page + //??D by page when inside an occupied address range... Will fix. // Compute the distance between the initial paddr and the first page boundary const uint64_t align_paddr = (paddr & PAGE_OFFSET_MASK) != 0 ? (paddr | PAGE_OFFSET_MASK) + 1 : paddr; uint64_t align_length = align_paddr - paddr; @@ -2080,25 +2123,24 @@ void machine::read_memory(uint64_t paddr, unsigned char *data, uint64_t length) align_length = (align_length == 0) ? page_size : align_length; // First peek goes at most to the next page boundary, or up to length uint64_t peek_length = std::min(align_length, length); - // The outer loop finds the PMA for all peeks performed by the inner loop - // The inner loop peeks at most min(page_size, length) from the PMA per iteration + // The outer loop finds the address range for all peeks performed by the inner loop + // The inner loop peeks at most min(page_size, length) from the range per iteration // All peeks but the absolute first peek start at a page boundary. // That first peek reads at most up to the next page boundary. // So the inner loop iterations never cross page boundaries. for (;;) { - const auto &pma = find_pma_entry(paddr, peek_length); - const auto peek = pma.get_peek(); - const auto pma_start = pma.get_start(); - const auto pma_empty = pma.get_istart_E(); - const auto pma_length = pma.get_length(); - // If the PMA is empty, the inner loop will break after a single iteration. + const auto &ar = find_address_range(paddr, peek_length); + const auto pma_start = ar.get_start(); + const auto occupied = ar.get_length() != 0; + const auto pma_length = ar.get_length(); + // If we are in an unoccupied range, the inner loop will break after a single iteration. // But it is safe to return pristine data for that one iteration, without even peeking. // This is because the inner iteration never reads past a page boundary, and the next - // non-empty PMA starts at the earliest on the next page boundary after paddr. + // occupied range starts at the earliest on the next page boundary after paddr. for (;;) { const unsigned char *peek_data = nullptr; - // If non-empty PMA, peek, otherwise leave peek_data as nullptr (i.e. pristine) - if (!pma_empty && !peek(pma, *this, paddr - pma_start, peek_length, &peek_data, data)) { + // If we found an occupied range, peek, otherwise leave peek_data as nullptr (i.e. pristine) + if (occupied && !ar.peek(*this, paddr - pma_start, peek_length, &peek_data, data)) { throw std::runtime_error{"peek failed"}; } // If the chunk is pristine, copy zero data to buffer @@ -2117,13 +2159,13 @@ void machine::read_memory(uint64_t paddr, unsigned char *data, uint64_t length) paddr += peek_length; data += peek_length; peek_length = std::min(page_size, length); - // If the PMA was empty, break to check if next read is in another PMA - if (pma_empty) { + // If we are outside any valid range, break to check if next read hits a valid range + if (!occupied) { break; } - // If the next read does not fit in current PMA, break to get the next one + // If the next read does not fit in current range, break to get the next one // There can be no overflow in the condition. - // Since the PMA is non-empty, (paddr-pma_start) >= 0. + // Since the range is occupied, (paddr-pma_start) >= 0. // Moreover, pma_length >= page_size. // Since, peek_length <= page_size, we get (pma_length-peek_length) >= 0. if (paddr - pma_start >= pma_length - peek_length) { @@ -2140,28 +2182,51 @@ void machine::write_memory(uint64_t paddr, const unsigned char *data, uint64_t l if (data == nullptr) { throw std::invalid_argument{"invalid data buffer"}; } - auto &pma = find_pma_entry(paddr, length); - if (pma.get_istart_IO()) { + auto &ar = find_address_range(paddr, length); + if (ar.is_device()) { throw std::invalid_argument{"attempted write to device memory range"}; } - if (!pma.get_istart_M() || pma.get_istart_E()) { - throw std::invalid_argument{"address range not entirely in single memory range"}; + if (ar.get_length() == 0 || !ar.is_memory()) { + throw std::invalid_argument{"address range to write is not entirely in single memory range"}; } - if (DID_is_protected(pma.get_istart_DID())) { + if (is_protected(ar.get_driver_id())) { throw std::invalid_argument{"attempt to write to protected memory range"}; } - pma.write_memory(paddr, data, length); + foreach_aligned_chunk(paddr, length, PMA_PAGE_SIZE, [&ar, paddr, data](auto chunk_start, auto chunk_length) { + const auto *src = data + (chunk_start - paddr); + const auto offset = chunk_start - ar.get_start(); + auto *dest = ar.get_host_memory() + offset; + if (memcmp(dest, src, chunk_length) != 0) { + // Page is different, we have to copy memory + memcpy(dest, src, chunk_length); + ar.mark_dirty_page(offset); + } + }); } -void machine::fill_memory(uint64_t address, uint8_t data, uint64_t length) { +void machine::fill_memory(uint64_t paddr, uint8_t val, uint64_t length) { if (length == 0) { return; } - auto &pma = find_pma_entry(address, length); - if (!pma.get_istart_M() || pma.get_istart_E()) { - throw std::invalid_argument{"address range not entirely in memory PMA"}; + auto &ar = find_address_range(paddr, length); + if (ar.is_device()) { + throw std::invalid_argument{"attempted fill to device memory range"}; + } + if (ar.get_length() == 0 || !ar.is_memory()) { + throw std::invalid_argument{"address range to fill is not entirely in memory PMA"}; } - pma.fill_memory(address, data, length); + if (is_protected(ar.get_driver_id())) { + throw std::invalid_argument{"attempt fill to protected memory range"}; + } + // The case of filling a range with zeros is special and optimized for uarch reset + foreach_aligned_chunk(paddr, length, PMA_PAGE_SIZE, [&ar, val](auto chunk_start, auto chunk_length) { + const auto offset = chunk_start - ar.get_start(); + const auto dest = ar.get_host_memory() + offset; + if (val != 0 || !is_pristine(dest, chunk_length)) { + memset(dest, val, chunk_length); + ar.mark_dirty_page(offset); + } + }); } void machine::read_virtual_memory(uint64_t vaddr_start, unsigned char *data, uint64_t length) { @@ -2273,21 +2338,22 @@ void machine::write_word(uint64_t paddr, uint64_t val) { return; } // Otherwise, try the slow path - auto &pma = find_pma_entry(paddr, sizeof(uint64_t)); - if (pma.get_istart_E() || !pma.get_istart_M()) { + auto &ar = find_address_range(paddr, sizeof(uint64_t)); + if (ar.get_length() == 0 || !ar.is_memory() || ar.get_host_memory() == nullptr) { std::ostringstream err; - err << "attempted memory write to " << pma.get_description() << " at address 0x" << std::hex << paddr << "(" + err << "attempted memory write to " << ar.get_description() << " at address 0x" << std::hex << paddr << "(" << std::dec << paddr << ")"; throw std::runtime_error{err.str()}; } - if (!pma.get_istart_W()) { + if (!ar.is_writeable()) { std::ostringstream err; - err << "attempted memory write to (non-writeable) " << pma.get_description() << " at address 0x" << std::hex - << paddr << "(" << std::dec << paddr << ")"; + err << "attempted memory write to read-only " << ar.get_description() << " at address 0x" << std::hex << paddr + << "(" << std::dec << paddr << ")"; throw std::runtime_error{err.str()}; } - const auto offset = paddr - pma.get_start(); - aliased_aligned_write(pma.get_memory().get_host_memory() + offset, val); + const auto offset = paddr - ar.get_start(); + aliased_aligned_write(ar.get_host_memory() + offset, val); + ar.mark_dirty_page(offset); } void machine::send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) { @@ -2339,12 +2405,13 @@ void machine::reset_uarch() { write_reg(machine_reg_enum(reg::uarch_x0, i), UARCH_X_INIT); } // Load embedded pristine RAM image - if (uarch_pristine_ram_len > m_us.ram.get_length()) { - throw std::runtime_error("embedded uarch ram image does not fit in uarch ram pma"); - } + const auto uram_length = m_us.ram->get_length(); + const auto uram_start = m_us.ram->get_start(); // Reset RAM to initial state - m_us.ram.fill_memory(m_us.ram.get_start(), 0, m_us.ram.get_length()); - m_us.ram.write_memory(m_us.ram.get_start(), uarch_pristine_ram, uarch_pristine_ram_len); + write_memory(uram_start, uarch_pristine_ram, uarch_pristine_ram_len); + if (uram_length > uarch_pristine_ram_len) { + fill_memory(uram_start + uarch_pristine_ram_len, 0, uram_length - uarch_pristine_ram_len); + } } access_log machine::log_reset_uarch(const access_log::type &log_type) { @@ -2384,8 +2451,8 @@ void machine::verify_reset_uarch(const hash_type &root_hash_before, const access extern template UArchStepStatus uarch_step(uarch_record_state_access &a); access_log machine::log_step_uarch(const access_log::type &log_type) { - if (m_us.ram.get_istart_E()) { - throw std::runtime_error("microarchitecture RAM is not present"); + if (read_reg(reg::iunrep) != 0) { + throw std::runtime_error("microarchitecture cannot be used with unreproducible machines"); } hash_type root_hash_before; get_root_hash(root_hash_before); @@ -2430,9 +2497,6 @@ uarch_interpreter_break_reason machine::run_uarch(uint64_t uarch_cycle_end) { if (read_reg(reg::iunrep) != 0) { throw std::runtime_error("microarchitecture cannot be used with unreproducible machines"); } - if (m_us.ram.get_istart_E()) { - throw std::runtime_error("microarchitecture RAM is not present"); - } const uarch_state_access a(*this); return uarch_interpret(a, uarch_cycle_end); } diff --git a/src/machine.h b/src/machine.h index 03ab519d5..34cfe8164 100644 --- a/src/machine.h +++ b/src/machine.h @@ -26,26 +26,25 @@ #include #include -#include #include #include "access-log.h" +#include "address-range-description.h" +#include "address-range.h" #include "host-addr.h" #include "i-device-state-access.h" #include "interpret.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-reg.h" #include "machine-runtime-config.h" #include "machine-state.h" #include "os.h" #include "pma-constants.h" -#include "pma.h" #include "shadow-tlb.h" #include "uarch-interpret.h" #include "uarch-state.h" -#include "virtio-device.h" +#include "virtio-address-range.h" namespace cartesi { @@ -61,55 +60,74 @@ constexpr skip_merkle_tree_update_t skip_merkle_tree_update; /// \brief Cartesi Machine implementation class machine final { private: - mutable machine_state m_s; ///< Big machine state - mutable uarch_state m_us; ///< Microarchitecture state - mutable machine_merkle_tree m_t; ///< Merkle tree of state - std::vector m_merkle_pmas; ///< PMAs considered by the Merkle tree: from big machine and uarch - machine_config m_c; ///< Copy of initialization config - machine_runtime_config m_r; ///< Copy of initialization runtime config - machine_memory_range_descrs m_mrds; ///< List of memory ranges returned by get_memory_ranges(). - - boost::container::static_vector, VIRTIO_MAX> m_vdevs; ///< Array of VirtIO devices - - static const pma_entry::flags m_dtb_flags; ///< PMA flags used for DTB - static const pma_entry::flags m_ram_flags; ///< PMA flags used for RAM - static const pma_entry::flags m_flash_drive_flags; ///< PMA flags used for flash drives - static const pma_entry::flags m_cmio_rx_buffer_flags; ///< PMA flags used for cmio rx buffer - static const pma_entry::flags m_cmio_tx_buffer_flags; ///< PMA flags used for cmio tx buffer - - std::unordered_map m_counters; - - /// \brief Allocates a new PMA entry. - /// \param pma PMA entry to add to machine. - /// \returns Reference to corresponding entry in machine state. - pma_entry ®ister_pma_entry(pma_entry &&pma); - - /// \brief Creates a new PMA entry reflecting a memory range configuration. - /// \param description Informative description of PMA entry for use in error messages - /// \param c Memory range configuration. - /// \returns New PMA entry (with default flags). - static pma_entry make_memory_range_pma_entry(const std::string &description, const memory_range_config &c); - - /// \brief Creates a new flash drive PMA entry. - /// \param description Informative description of PMA entry for use in error messages - /// \param c Memory range configuration. - /// \returns New PMA entry with flash drive flags already set. - static pma_entry make_flash_drive_pma_entry(const std::string &description, const memory_range_config &c); - - /// \brief Creates a new cmio rx buffer PMA entry. - // \param c cmio buffer configuration for rx buffer. - /// \returns New PMA entry with rx buffer flags already set. - static pma_entry make_cmio_rx_buffer_pma_entry(const cmio_buffer_config &c); - - /// \brief Creates a new cmio tx buffer PMA entry. - // \param c cmio buffer configuration for tx buffer. - /// \returns New PMA entry with tx buffer flags already set. - static pma_entry make_cmio_tx_buffer_pma_entry(const cmio_buffer_config &c); - - /// \brief Saves PMAs into files for serialization + mutable machine_state m_s; ///< Big machine state + mutable uarch_state m_us; ///< Microarchitecture state + mutable std::vector> m_ars; ///< All address ranges + mutable machine_merkle_tree m_t; ///< Merkle tree of state + machine_config m_c; ///< Copy of initialization config + machine_runtime_config m_r; ///< Copy of initialization runtime config + std::vector m_merkle_ars; ///< Indices of address ranges that the Mekrle tree can find + std::vector m_virtio_ars; ///< VirtIO address ranges + address_range_descriptions m_ards; ///< Address range descriptions listed by get_address_ranges() + std::unordered_map m_counters; ///< Counters used for statistics collection + + ///< Where to register an address range + struct register_where { + bool merkle; //< Register with Merkle tree, so it appears in the root hash + bool interpret; //< Register so interpret can see (and it also appears as a PMA entries in memory) + }; + + /// \brief Checks if address range can be registered. + /// \param ar Address range object to register. + /// \param where Where to register the address range. + void check_address_range(const address_range &ar, register_where where); + + /// \brief Registers a new address range. + /// \tparam AR An address range or derived type. + /// \param ar The address range object to register (as an r-value). + /// \param where Where to register the address range. + /// \returns Reference to address range object after it is moved inside the machine. + /// \details The r-value address range is moved to the heap, and the pointer holding it is added to a container. + /// Once the address range is moved to the heap, its address will remain valid until it is replaced by + /// a call to replace_memory_range(), or until the machine is destroyed. + /// This means pointers to address ranges remain valid even after subsequent calls to register_address_range(), + /// but may be invalidated by calls to replace_address_range(). + /// For a stronger guarantee, when an address range is replaced, the pointer to the new address range + /// overwrites the pointer to the old address range at the same index in the container. + /// This means the an index into the container that owns all address ranges will always refers to same address range + /// after subsequent calls to register_address_range() and calls to replace_address_range() as well. + /// \details Besides the container that stores the address ranges, the machine maintains subsets of address ranges. + /// The "merkle" address range container lists the indices of the address ranges taht will be considered by + /// the Merkle tree during the computation of the state hash. + /// The "interpret" address range container lists the indices of the address ranges that will be visible from within + /// the interpreter. + /// When registering an address range with the machine, one must specify \p where else to register it. + /// The "virtio" address range container holds pointers to every virtio address range that has been registered. + template + AR ®ister_address_range(AR &&ar, register_where where) + requires std::is_rvalue_reference_v && std::derived_from + { + check_address_range(ar, where); // Check if we can register it + auto ptr = make_moved_unique(std::forward(ar)); // Move object to heap, now owned by ptr + AR &ar_ref = *ptr; // Get reference to object, already in heap, to return later + const auto index = m_ars.size(); // Get index new address range will occupy + m_ars.push_back(std::move(ptr)); // Move ptr to list of address ranges + if (where.interpret) { // Register with interpreter + m_s.pmas.push_back(index); + } + if (where.merkle) { // Register with Merkle tree + m_merkle_ars.push_back(index); + } + if constexpr (std::is_convertible_v) { // Register with VirtIO + m_virtio_ars.push_back(&ar_ref); + } + return ar_ref; // Return reference to object in heap + } + + /// \brief Saves address ranges into files for serialization /// \param config Machine config to be stored - /// \param directory Directory where PMAs will be stored - void store_pmas(const machine_config &config, const std::string &directory) const; + /// \param directory Directory where address ranges will be stored + void store_address_ranges(const machine_config &config, const std::string &directory) const; /// \brief Returns offset that converts between machine host addresses and target physical addresses /// \param pma_index Index of the memory PMA for the desired offset @@ -124,62 +142,70 @@ class machine final { /// \param r Machine runtime configuration void init_processor(processor_config &p, const machine_runtime_config &r); - /// \brief Initializes RAM PMA + /// \brief Initializes RAM address range /// \param ram RAM configuration - void init_ram_pma(const ram_config &ram); + void init_ram_ar(const ram_config &ram); /// \brief Initializes flash drive PMAs /// \param flash_drive Flash drive configurations - void init_flash_drive_pmas(flash_drive_configs &flash_drive); + void init_flash_drive_ars(flash_drive_configs &flash_drive); /// \brief Initializes VirtIO device PMAs - /// \param v VirtIO configurations + /// \param cs VirtIO configurations /// \param iunrep Initial value of iunrep CSR - void init_virtio_pmas(const virtio_configs &v, uint64_t iunrep); + void init_virtio_ars(const virtio_configs &cs, uint64_t iunrep); + + /// \brief Initializes HTIF device address range + /// \param h HTIF configuration + void init_htif_ar(const htif_config &h); - /// \brief Initializes HTIF device PMA + /// \brief Initializes TTY if needed /// \param h HTIF configuration /// \param r HTIF runtime configuration /// \param iunrep Initial value of iunrep CSR - void init_htif_pma(const htif_config &h, const htif_runtime_config &r, uint64_t iunrep); + void init_tty(const htif_config &h, const htif_runtime_config &r, uint64_t iunrep) const; - /// \brief Initializes CLINT device PMA + /// \brief Initializes CLINT device address range /// \param c CLINT configuration - void init_clint_pma(const clint_config &c); + void init_clint_ar(const clint_config &c); - /// \brief Initializes PLIC device PMA + /// \brief Initializes PLIC device address range /// \param p PLIC configuration - void init_plic_pma(const plic_config &p); + void init_plic_ar(const plic_config &p); - /// \brief Initializes CMIO PMAs + /// \brief Initializes CMIO address ranges /// \param c CMIO configuration - void init_cmio_pmas(const cmio_config &c); + void init_cmio_ars(const cmio_config &c); - /// \brief Initializes the PMAs used to compute the Merkle tree - /// \detail This can only be called after all PMAs have been added - void init_merkle_pmas(); + /// \brief Initializes the address ranges involced in the Merkle tree + /// \detail This can only be called after all address ranges have been registerd + void init_merkle_ars(); - /// \brief Initializes the PMAs descriptions returned by get_memory_ranges() - /// \detail This can only be called after the Merkle tree PMAs have been initialized - void init_memory_range_descrs(); + /// \brief Initializes the address range descriptions returned by get_address_ranges() + /// \detail This can only be called after all address ranges have been registered + void init_ars_descriptions(); - /// \brief Fill up PMA list with sentinel empty PMAs - void init_sentinel_pmas(); + /// \brief Fill up address range list with sentinels + void init_sentinel_ars(); /// \brief Initializes contents of the shadow PMAs memory /// \param shadow_pmas PMA entry for the shadow PMAs /// \detail This can only be called after all PMAs have been added - void init_shadow_pmas_contents(pma_entry &shadow_pmas) const; + void init_shadow_pmas_contents(memory_address_range &shadow_pmas) const; /// \brief Initializes contents of machine TLB, from image in disk or with default values /// \param image_filename File containing image, or empty for default values /// \detail This can only be called after all PMAs have been added void init_tlb_contents(const std::string &image_filename); + /// \brief Initializes DTB address range + /// \param c DTB configuration + address_range &init_dtb_ar(const dtb_config &c); + /// \brief Initializes contents of machine DTB, if image was not available /// \param c Machine configuration /// \param dtb PMA entry for the shadow PMAs - static void init_dtb_contents(const machine_config &c, pma_entry &dtb); + static void init_dtb_contents(const machine_config &c, address_range &dtb); /// \brief Dumps statistics void dump_stats(); @@ -303,8 +329,8 @@ class machine final { } /// \brief Returns a list of descriptions for all PMA entries registered in the machine, sorted by start - machine_memory_range_descrs get_memory_ranges() const { - return m_mrds; + address_range_descriptions get_address_ranges() const { + return m_ards; } /// \brief Wait for external interrupts requests. @@ -439,16 +465,18 @@ class machine final { /// \brief Writes a chunk of data to machine memory, by its target physical address and length. /// \param paddr Target physical address to start writing to. /// \param data Buffer that contains data to write. Must be at least \p length bytes long. - /// \param length Number of bytes to write starting from \p data to \p paddr. + /// \param length Number of bytes to write from \p data to \p paddr. /// \details Unlike read_memory(), the entire chunk of data, from \p paddr to \p paddr + \p length, - /// must reside entirely in the same memory range. Moreover, it cannot be mapped to a device. + /// must reside entirely in the same address range. Moreover, it cannot be mapped to a device. void write_memory(uint64_t paddr, const unsigned char *data, uint64_t length); /// \brief Fills a memory range with a single byte. - /// \param address Physical address to start filling. - /// \param data Byte to fill memory with. - /// \param length Size of memory range to fill. - void fill_memory(uint64_t address, uint8_t data, uint64_t length); + /// \param paddr Target physical address to start filling. + /// \param val Byte to fill memory with. + /// \param length Number of bytes to write starting at \p paddr. + /// \details Unlike read_memory(), the entire chunk of data, from \p paddr to \p paddr + \p length, + /// must reside entirely in the same address range. Moreover, it cannot be mapped to a device. + void fill_memory(uint64_t paddr, uint8_t val, uint64_t length); /// \brief Reads a chunk of data from the machine virtual memory. /// \param vaddr_start Virtual address to start reading. @@ -467,33 +495,51 @@ class machine final { /// \returns The corresponding physical address. uint64_t translate_virtual_address(uint64_t vaddr); - /// \brief Obtain PMA entry from the machine state that covers a given physical memory region - /// \brief Microarchitecture PMAs are not considered. - /// \param s Pointer to machine state. - /// \param paddr Start of physical memory region. - /// \param length Length of physical memory region. - /// \returns Corresponding entry if found, or a sentinel entry - /// for an empty range. - const pma_entry &find_pma_entry(uint64_t paddr, uint64_t length) const; + /// \brief Returns the address range associated to the PMA at a given index + /// \param index Index of desired address range + /// \returns Desired address range, or an empty sentinel if index is out of bounds + const address_range &read_pma(uint64_t index) const noexcept { + if (index >= m_s.pmas.size()) { + static constexpr address_range sentinel{"sentinel"}; + return sentinel; + } + // NOLINTNEXTLINE(bugprone-narrowing-conversions) + return *m_ars[static_cast(m_s.pmas[static_cast(index)])]; + } + + /// \brief Returns the address range associated to the PMA at a given index + /// \param index Index of desired address range + /// \returns Desired address range, or an empty sentinel if index is out of bounds + address_range &read_pma(uint64_t index) noexcept { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return const_cast(std::as_const(*this).read_pma(index)); + } + + /// \brief Obtain address range from the machine state that covers a given physical memory region + /// \param paddr Target physical address of start of region. + /// \param length Length of region, in bytes. + /// \returns Corresponding address range if found, or an empty sentinel otherwise. + /// \warning Microarchitecture address ranges are not considered in the search. + const address_range &find_address_range(uint64_t paddr, uint64_t length) const noexcept; - pma_entry &find_pma_entry(uint64_t paddr, uint64_t length) { + address_range &find_address_range(uint64_t paddr, uint64_t length) noexcept { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return const_cast(std::as_const(*this).find_pma_entry(paddr, length)); + return const_cast(std::as_const(*this).find_address_range(paddr, length)); } - /// \brief Obtain PMA entry covering a physical memory word + /// \brief Obtain address range from the machine state that covers a given word in physical memory /// \tparam T Type of word. - /// \param s Pointer to machine state. - /// \param paddr Target physical address. - /// \returns Corresponding entry if found, or a sentinel entry for an empty range. + /// \param paddr Target physical address of word. + /// \returns Corresponding address range if found, or an empty sentinel otherwise. + /// \warning Microarchitecture address ranges are not considered in the search. template - const pma_entry &find_pma_entry(uint64_t paddr) const { - return find_pma_entry(paddr, sizeof(T)); + const address_range &find_address_range(uint64_t paddr) const { + return find_address_range(paddr, sizeof(T)); } template - pma_entry &find_pma_entry(uint64_t paddr) { - return find_pma_entry(paddr, sizeof(T)); + address_range &find_address_range(uint64_t paddr) { + return find_address_range(paddr, sizeof(T)); } /// \brief Go over the write TLB and mark as dirty all pages currently there. @@ -519,10 +565,9 @@ class machine final { void set_runtime_config(machine_runtime_config r); /// \brief Replaces a memory range. - /// \param range Configuration of the new memory range. - /// \details The machine must contain an existing memory range - /// matching the start and length specified in range. - void replace_memory_range(const memory_range_config &range); + /// \param config Configuration of the new memory range. + /// \details The machine must contain an existing memory range matching the start and length specified in range. + void replace_memory_range(const memory_range_config &config); /// \brief Sends cmio response /// \param reason Reason for sending response. diff --git a/src/memory-address-range.cpp b/src/memory-address-range.cpp new file mode 100644 index 000000000..a8d05ec63 --- /dev/null +++ b/src/memory-address-range.cpp @@ -0,0 +1,90 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#include "memory-address-range.h" + +namespace cartesi { + +using namespace std::string_literals; + +class base_error : public std::invalid_argument { +public: + explicit base_error(const char *err) : std::invalid_argument{err} { + ; + } +}; + +memory_address_range::memory_address_range(const std::string &description, uint64_t start, uint64_t length, + const pma_flags &flags, const std::string &image_filename, const mmapd &m) try : + address_range(description.c_str(), start, length, flags, [](const char *err) { throw base_error{err}; }), + m_ptr{make_unique_mmap(image_filename.c_str(), length, m.shared)}, + m_host_memory{std::get(m_ptr).get()} { + if (!is_memory()) { + throw std::invalid_argument{"memory range must be flagged memory when initializing "s + description}; + } + m_dirty_page_map.resize((get_length() / (8 * PMA_PAGE_SIZE)) + 1, 0xff); +} catch (base_error &b) { + throw; // already contains the description +} catch (std::exception &e) { + throw std::invalid_argument{e.what() + " when initializing "s + description}; +} catch (...) { + throw std::invalid_argument{"unknown exception when initializing "s + description}; +} + +memory_address_range::memory_address_range(const std::string &description, uint64_t start, uint64_t length, + const pma_flags &flags, const std::string &image_filename, const callocd & /*c*/) try : + address_range(description.c_str(), start, length, flags, [](const char *err) { throw base_error{err}; }), + m_ptr{make_unique_calloc(length)}, + m_host_memory{std::get(m_ptr).get()} { + if (!is_memory()) { + throw std::invalid_argument{"memory range must be flagged memory when initializing "s + description}; + } + m_dirty_page_map.resize((length / (8 * PMA_PAGE_SIZE)) + 1, 0xff); + // Try to load image file, if any + if (!image_filename.empty()) { + auto fp = make_unique_fopen(image_filename.c_str(), "rb", std::nothrow_t{}); + if (!fp) { + throw std::system_error{errno, std::generic_category(), "error opening image file '"s + image_filename}; + } + // Get file size + if (fseek(fp.get(), 0, SEEK_END) != 0) { + throw std::system_error{errno, std::generic_category(), + "error obtaining length of image file '"s + image_filename}; + } + const auto file_length = static_cast(ftello(fp.get())); + if (fseek(fp.get(), 0, SEEK_SET) != 0) { + throw std::system_error{errno, std::generic_category(), + "error obtaining length of image file '"s + image_filename}; + } + // Check against PMA range size + if (file_length > length) { + throw std::runtime_error{"image file '"s + image_filename + "' is too large for range"s}; + } + // Read to host memory + const auto read_length = static_cast(fread(m_host_memory, 1, file_length, fp.get())); + if (read_length != file_length) { + throw std::runtime_error{"error reading from image file '"s + image_filename}; + } + } +} catch (base_error &b) { + throw; // already contains the description +} catch (std::exception &e) { + throw std::invalid_argument{e.what() + " when initializing "s + description}; +} catch (...) { + throw std::invalid_argument{"unknown exception when initializing "s + description}; +} + +} // namespace cartesi diff --git a/src/memory-address-range.h b/src/memory-address-range.h new file mode 100644 index 000000000..7beb6a56f --- /dev/null +++ b/src/memory-address-range.h @@ -0,0 +1,143 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef MEMORY_ADDRESS_RANGE_H +#define MEMORY_ADDRESS_RANGE_H + +#include +#include + +#include "address-range.h" +#include "unique-c-ptr.h" + +namespace cartesi { + +// Forward declarations +class machine; + +/// \file +/// \brief An address range occupied by memory + +class memory_address_range : public address_range { + + using callocd_ptr = unique_calloc_ptr; + using mmapd_ptr = unique_mmap_ptr; + + std::variant + m_ptr; + + unsigned char *m_host_memory; ///< Start of associated memory region in host. + std::vector m_dirty_page_map; ///< Map of dirty pages. + +public: + using ptr_type = std::unique_ptr; + + /// \brief Mmap'd range data (shared or not). + struct mmapd { + bool shared; + }; + + /// \brief Constructor for mmap'd ranges. + /// \param description Description of address range for use in error messages + /// \param start Start of address range + /// \param length Length of address range + /// \param flags Range flags + /// \param image_filename Path to backing file. + /// \param m Mmap'd range data (shared or not). + memory_address_range(const std::string &description, uint64_t start, uint64_t length, const pma_flags &flags, + const std::string &image_filename, const mmapd &m); + + /// \brief Calloc'd range data (just a tag). + struct callocd {}; + + /// \brief Constructor for calloc'd ranges. + /// \param description Description of address range for use in error messages + /// \param start Start of address range + /// \param length Length of address range + /// \param flags Range flags + /// \param image_filename Path to backing file. + /// \param c Calloc'd range data (just a tag). + memory_address_range(const std::string &description, uint64_t start, uint64_t length, const pma_flags &flags, + const std::string &image_filename, const callocd & /*c*/); + + memory_address_range(const memory_address_range &) = delete; + memory_address_range &operator=(const memory_address_range &) = delete; + memory_address_range &operator=(memory_address_range &&) noexcept = delete; + + ~memory_address_range() override = default; + memory_address_range(memory_address_range &&) noexcept = default; + +private: + unsigned char *do_get_host_memory() noexcept override { + return m_host_memory; + } + + const unsigned char *do_get_host_memory() const noexcept override { + return m_host_memory; + } + + void do_mark_dirty_page(uint64_t offset) noexcept override { + auto page_index = offset >> PMA_constants::PMA_PAGE_SIZE_LOG2; + auto map_index = page_index >> 3; + assert(map_index < m_dirty_page_map.size()); + m_dirty_page_map[map_index] |= (1 << (page_index & 7)); + } + + void do_mark_clean_page(uint64_t offset) noexcept override { + auto page_index = offset >> PMA_constants::PMA_PAGE_SIZE_LOG2; + auto map_index = page_index >> 3; + assert(map_index < m_dirty_page_map.size()); + m_dirty_page_map[map_index] &= ~(1 << (page_index & 7)); + } + + void do_mark_pages_clean() noexcept override { + std::fill(m_dirty_page_map.begin(), m_dirty_page_map.end(), 0); + } + + bool do_is_page_marked_dirty(uint64_t offset) const noexcept override { + auto page_index = offset >> PMA_constants::PMA_PAGE_SIZE_LOG2; + auto map_index = page_index >> 3; + assert(map_index < m_dirty_page_map.size()); + return (m_dirty_page_map[map_index] & (1 << (page_index & 7))) != 0; + } + + bool do_peek(const machine & /*m*/, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char * /*scratch*/) const noexcept override { + if (contains_relative(offset, length)) { + *data = get_host_memory() + offset; + return true; + } + *data = nullptr; + return false; + } +}; + +static inline auto make_callocd_memory_address_range(const std::string &description, uint64_t start, uint64_t length, + pma_flags flags, const std::string &image_filename = {}) { + return memory_address_range{description, start, length, flags, image_filename, memory_address_range::callocd{}}; +} + +static inline auto make_mmapd_memory_address_range(const std::string &description, uint64_t start, uint64_t length, + pma_flags flags, const std::string &image_filename, bool shared) { + return memory_address_range{description, start, length, flags, image_filename, memory_address_range::mmapd{shared}}; +} + +} // namespace cartesi + +#endif diff --git a/src/meta.h b/src/meta.h index 1a1f28a10..1bf94a03c 100644 --- a/src/meta.h +++ b/src/meta.h @@ -85,6 +85,12 @@ struct log2_size { static constexpr int value = 3; }; +// helper type for visitor +template +struct overloads : Ts... { + using Ts::operator()...; +}; + /// \endcond } // namespace cartesi diff --git a/src/mock-address-range.h b/src/mock-address-range.h new file mode 100644 index 000000000..30b3cc5a9 --- /dev/null +++ b/src/mock-address-range.h @@ -0,0 +1,108 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef MOCK_ADDRESS_RANGE_H +#define MOCK_ADDRESS_RANGE_H + +#include +#include +#include + +#include "address-range.h" +#include "clint-address-range.h" +#include "htif-address-range.h" +#include "plic-address-range.h" +#include "pma.h" +#include "shadow-state-address-range.h" +#include "shadow-tlb-address-range.h" + +namespace cartesi { + +using mock_address_range = std::variant; + +using mock_address_ranges = std::array; + +template +static inline mock_address_range check_mock_flags(AR &&ar, const pma_flags &flags, ABRT abrt) + requires std::is_rvalue_reference_v && std::derived_from +{ + if (ar.get_flags() != flags) { + abrt("incompatible flags in mock address range"); + __builtin_trap(); + return std::monostate{}; + } + return std::forward(ar); +} + +template +static inline mock_address_range make_mock_address_range(uint64_t istart, uint64_t ilength, ABRT abrt) { + uint64_t start{}; + auto flags = unpack_pma_istart(istart, start); + if (flags.M) { + return make_address_range(pma_get_DID_name(flags.DID), start, ilength, flags, abrt); + } + if (flags.E) { + return make_address_range("empty", start, ilength, flags, abrt); + } + switch (flags.DID) { + case PMA_ISTART_DID::shadow_state: + return check_mock_flags(make_shadow_state_address_range(start, ilength, abrt), flags, abrt); + case PMA_ISTART_DID::shadow_TLB: + return check_mock_flags(make_shadow_tlb_address_range(start, ilength, abrt), flags, abrt); + case PMA_ISTART_DID::CLINT: + return check_mock_flags(make_clint_address_range(start, ilength, abrt), flags, abrt); + case PMA_ISTART_DID::PLIC: + return check_mock_flags(make_plic_address_range(start, ilength, abrt), flags, abrt); + case PMA_ISTART_DID::HTIF: + return check_mock_flags(make_htif_address_range(start, ilength, abrt), flags, abrt); + default: + abrt("unhandled mock address range"); + __builtin_trap(); + return std::monostate{}; + } +}; + +template +address_range &get_mock_address_range(mock_address_range &mock, ABRT abrt) { + //??D I'm hoping the compiler optimizes this to what amounts to an if and a cast + static_assert(std::is_same_v, std::monostate>); + switch (mock.index()) { + case 1: + return std::get<1>(mock); + case 2: + return std::get<2>(mock); + case 3: + return std::get<3>(mock); + case 4: + return std::get<4>(mock); + case 5: + return std::get<5>(mock); + case 6: + return std::get<6>(mock); + default: { + static auto unhandled = make_empty_address_range("unhandled mock address range"); + abrt("unhandled mock address range"); + __builtin_trap(); + return unhandled; + } + } + static_assert(std::variant_size_v == 7); +} + +} // namespace cartesi + +#endif diff --git a/src/mock-pma-entry.h b/src/mock-pma-entry.h deleted file mode 100644 index 75cda69cb..000000000 --- a/src/mock-pma-entry.h +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef MOCK_PMA_ENTRY_H -#define MOCK_PMA_ENTRY_H - -#include "clint.h" -#include "htif.h" -#include "plic.h" -#include "pma-constants.h" -#include "shadow-state.h" -#include "shadow-tlb.h" - -namespace cartesi { - -class mock_pma_entry { -public: - struct flags { - bool M; - bool IO; - bool E; - bool R; - bool W; - bool X; - bool IR; - bool IW; - PMA_ISTART_DID DID; - }; - -private: - uint64_t m_pma_index; - uint64_t m_start; - uint64_t m_length; - flags m_flags; - const pma_driver *m_driver{nullptr}; - - static constexpr flags split_flags(uint64_t istart) { - flags f{}; - f.M = ((istart & PMA_ISTART_M_MASK) >> PMA_ISTART_M_SHIFT) != 0; - f.IO = ((istart & PMA_ISTART_IO_MASK) >> PMA_ISTART_IO_SHIFT) != 0; - f.E = ((istart & PMA_ISTART_E_MASK) >> PMA_ISTART_E_SHIFT) != 0; - f.R = ((istart & PMA_ISTART_R_MASK) >> PMA_ISTART_R_SHIFT) != 0; - f.W = ((istart & PMA_ISTART_W_MASK) >> PMA_ISTART_W_SHIFT) != 0; - f.X = ((istart & PMA_ISTART_X_MASK) >> PMA_ISTART_X_SHIFT) != 0; - f.IR = ((istart & PMA_ISTART_IR_MASK) >> PMA_ISTART_IR_SHIFT) != 0; - f.IW = ((istart & PMA_ISTART_IW_MASK) >> PMA_ISTART_IW_SHIFT) != 0; - f.DID = static_cast((istart & PMA_ISTART_DID_MASK) >> PMA_ISTART_DID_SHIFT); - return f; - } - -public: - template - mock_pma_entry(uint64_t pma_index, uint64_t istart, uint64_t ilength, ERR_F errf) : - m_pma_index{pma_index}, - m_start{istart & PMA_ISTART_START_MASK}, - m_length{ilength}, - m_flags{split_flags(istart)} { - if (m_flags.IO) { - switch (m_flags.DID) { - case PMA_ISTART_DID::shadow_state: - m_driver = &shadow_state_driver; - break; - case PMA_ISTART_DID::shadow_TLB: - m_driver = &shadow_tlb_driver; - break; - case PMA_ISTART_DID::CLINT: - m_driver = &clint_driver; - break; - case PMA_ISTART_DID::PLIC: - m_driver = &plic_driver; - break; - case PMA_ISTART_DID::HTIF: - m_driver = &htif_driver; - break; - default: - errf("unsupported device in mock_pma_entry"); - break; - } - } - } - - uint64_t get_index() const { - return m_pma_index; - } - - flags get_flags() const { - return m_flags; - } - - uint64_t get_start() const { - return m_start; - } - - uint64_t get_length() const { - return m_length; - } - - bool get_istart_M() const { - return m_flags.M; - } - - bool get_istart_IO() const { - return m_flags.IO; - } - - bool get_istart_E() const { - return m_flags.E; - } - - bool get_istart_R() const { - return m_flags.R; - } - - bool get_istart_W() const { - return m_flags.W; - } - - bool get_istart_X() const { - return m_flags.X; - } - - bool get_istart_IR() const { - return m_flags.IR; - } - - PMA_ISTART_DID get_istart_DID() const { - return m_flags.DID; - } - - const auto *get_driver() const { - return m_driver; - } - - const auto &get_device_noexcept() const { - return *this; - } - - static void *get_context() { - return nullptr; - } - - // NOLINTNEXTLINE(readability-convert-member-functions-to-static) - void mark_dirty_page(uint64_t address_in_range) { - (void) address_in_range; - // Dummy implementation. - } -}; - -template -static inline mock_pma_entry make_mock_pma_entry(uint64_t index, uint64_t istart, uint64_t ilength, ERR_F errf) { - return mock_pma_entry{index, istart, ilength, errf}; -} - -} // namespace cartesi - -#endif diff --git a/src/plic.cpp b/src/plic-address-range.cpp similarity index 91% rename from src/plic.cpp rename to src/plic-address-range.cpp index 3eac5b174..cd8148561 100644 --- a/src/plic.cpp +++ b/src/plic-address-range.cpp @@ -14,17 +14,14 @@ // with this program (see COPYING). If not, see . // -#include "plic.h" +#include "plic-address-range.h" -#include +#include "assert-printf.h" #include -#include #include "i-device-state-access.h" #include "interpret.h" #include "pma-constants.h" -#include "pma-defines.h" -#include "pma-driver.h" #include "riscv-constants.h" // Enable these defines to debug PLIC @@ -52,7 +49,7 @@ static uint32_t plic_read_pending(i_device_state_access *a) { } /// \brief Called only by the driver when it begins serving a pending interrupt request. -static bool plic_read_claim_complete(i_device_state_access *a, uint64_t *val) { +static bool plic_read_claim_complete(i_device_state_access *a, uint64_t *pval) { const uint32_t girqpend = a->read_plic_girqpend(); uint32_t girqsrvd = a->read_plic_girqsrvd(); uint32_t ipmask = girqpend & ~girqsrvd; @@ -68,7 +65,7 @@ static bool plic_read_claim_complete(i_device_state_access *a, uint64_t *val) { girqsrvd |= irq_mask; a->write_plic_girqsrvd(girqsrvd); // The PLIC will then return the interrupt source id to the target - *val = irq_id; + *pval = irq_id; // If all pending interrupts have been served, reset mip. ipmask = girqpend & ~girqsrvd; if (ipmask == 0) { @@ -76,10 +73,10 @@ static bool plic_read_claim_complete(i_device_state_access *a, uint64_t *val) { } } else { // The PLIC will return an id of zero, if there were no pending interrupts for the target - *val = 0; + *pval = 0; } #ifdef DEBUG_PLIC - std::ignore = fprintf(stderr, "plic: claim irq_id=%d\n", (int) *val); + std::ignore = fprintf(stderr, "plic: claim irq_id=%d\n", (int) *pval); #endif return true; } @@ -108,8 +105,8 @@ static execute_status plic_write_claim_complete(i_device_state_access *a, uint32 return execute_status::success; } -/// \brief PLIC device read callback. See ::pma_read. -static bool plic_read(void * /*context*/, i_device_state_access *a, uint64_t offset, uint64_t *val, int log2_size) { +bool plic_address_range::do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept { #ifdef DEBUG_PLIC_MMIO std::ignore = fprintf(stderr, "plic: mmio read offset=0x%lx log2_size=%d\n", (long) offset, log2_size); #endif @@ -153,28 +150,28 @@ static bool plic_read(void * /*context*/, i_device_state_access *a, uint64_t off case plic_csr_rel_addr::priority31: // A valid implementation can hardwire all input priority levels. // We hardwire all supported interrupt sources to the lowest priority - *val = PLIC_LOWEST_IRQ_PRIORITY; + *pval = PLIC_LOWEST_IRQ_PRIORITY; return true; case plic_csr_rel_addr::pending: - *val = plic_read_pending(a); + *pval = plic_read_pending(a); return true; case plic_csr_rel_addr::enabled: // A valid implementation can hardwire interrupt routing to a fixed hart context. // We hardwire all supported interrupt source to be always enabled in context 0. - *val = PLIC_ENABLED_IRQ_MASK; + *pval = PLIC_ENABLED_IRQ_MASK; return true; case plic_csr_rel_addr::claim_complete: - return plic_read_claim_complete(a, val); + return plic_read_claim_complete(a, pval); default: // Other PLIC CSRs are WARL hardwired to 0 - *val = 0; + *pval = 0; return true; } } /// \brief PLIC device read callback. See ::pma_write. -static execute_status plic_write(void * /*context*/, i_device_state_access *a, uint64_t offset, uint64_t val, - int log2_size) { +execute_status plic_address_range::do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept { #ifdef DEBUG_PLIC_MMIO std::ignore = fprintf(stderr, "plic: mmio write offset=0x%lx log2_size=%d val=0x%x\n", (long) offset, log2_size, (int) val); @@ -220,6 +217,4 @@ void plic_reset_pending_irq(i_device_state_access *a, uint32_t irq_id) { } } -const pma_driver plic_driver = {.name = "PLIC", .read = plic_read, .write = plic_write}; - } // namespace cartesi diff --git a/src/plic-address-range.h b/src/plic-address-range.h new file mode 100644 index 000000000..e430a1974 --- /dev/null +++ b/src/plic-address-range.h @@ -0,0 +1,80 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef PLIC_ADDRESS_RANGE_H +#define PLIC_ADDRESS_RANGE_H + +#include + +#include "i-device-state-access.h" +#include "plic-constants.h" +#include "pristine-address-range.h" + +/// \file +/// \brief Platform-Level Interrupt Controller address range. + +namespace cartesi { + +/// \brief Sets a new pending interrupt request. +/// \details This is called only by devices to notify an external interrupt. +void plic_set_pending_irq(i_device_state_access *a, uint32_t irq_id); + +/// \brief Clears a pending interrupt request. +/// \details This is called only by devices to remove an external interrupt notification. +void plic_reset_pending_irq(i_device_state_access *a, uint32_t irq_id); + +class plic_address_range final : public pristine_address_range { + + static constexpr pma_flags m_plic_flags{ + .M = false, + .IO = true, + .E = false, + .R = true, + .W = true, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::PLIC, + }; + +public: + template + plic_address_range(uint64_t start, uint64_t length, ABRT abrt) : + pristine_address_range("PLIC device", start, length, m_plic_flags, abrt) { + ; + } + + plic_address_range(const plic_address_range &other) = default; + plic_address_range &operator=(const plic_address_range &other) = default; + plic_address_range(plic_address_range &&other) = default; + plic_address_range &operator=(plic_address_range &&other) = default; + ~plic_address_range() override = default; + +private: + bool do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept override; + execute_status do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept override; +}; + +template +static inline plic_address_range make_plic_address_range(uint64_t start, uint64_t length, ABRT abrt) { + return plic_address_range{start, length, abrt}; +} + +} // namespace cartesi + +#endif diff --git a/src/plic.h b/src/plic-constants.h similarity index 86% rename from src/plic.h rename to src/plic-constants.h index a719e1402..d6eda8311 100644 --- a/src/plic.h +++ b/src/plic-constants.h @@ -14,15 +14,13 @@ // with this program (see COPYING). If not, see . // -#ifndef PLIC_H -#define PLIC_H +#ifndef PLIC_CONSTANTS_H +#define PLIC_CONSTANTS_H #include -#include "pma-driver.h" - /// \file -/// \brief Clock interruptor device. +/// \brief Platform-Level Interrupt Controller constants. namespace cartesi { @@ -74,17 +72,6 @@ enum plic_csr_rel_addr : uint64_t { // .. Interrupt threshold and claim_complete for other sources and contexts (unsupported) }; -/// \brief Sets a new pending interrupt request. -/// \details This is called only by devices to notify an external interrupt. -void plic_set_pending_irq(i_device_state_access *a, uint32_t irq_id); - -/// \brief Clears a pending interrupt request. -/// \details This is called only by devices to remove an external interrupt notification. -void plic_reset_pending_irq(i_device_state_access *a, uint32_t irq_id); - -/// \brief Global PLIC device driver instance -extern const pma_driver plic_driver; - } // namespace cartesi #endif diff --git a/src/plic-factory.cpp b/src/plic-factory.cpp deleted file mode 100644 index 01bb24686..000000000 --- a/src/plic-factory.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "plic-factory.h" - -#include - -#include "plic.h" -#include "pma-constants.h" -#include "pma.h" - -namespace cartesi { - -pma_entry make_plic_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = true, .W = true, .X = false, .IR = false, .IW = false, .DID = PMA_ISTART_DID::PLIC}; - return make_device_pma_entry("PLIC device", start, length, pma_peek_pristine, &plic_driver).set_flags(f); -} - -} // namespace cartesi diff --git a/src/plic-factory.h b/src/plic-factory.h deleted file mode 100644 index 5ac9e26d6..000000000 --- a/src/plic-factory.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef PLIC_FACTORY_H -#define PLIC_FACTORY_H - -#include - -#include "pma.h" - -namespace cartesi { - -/// \brief Creates a PMA entry for the PLIC device -/// \param start Start address for memory range. -/// \param length Length of memory range. -/// \returns Corresponding PMA entry -pma_entry make_plic_pma_entry(uint64_t start, uint64_t length); - -} // namespace cartesi - -#endif diff --git a/src/pma-constants.h b/src/pma-constants.h index 38d9df885..723437296 100644 --- a/src/pma-constants.h +++ b/src/pma-constants.h @@ -19,14 +19,10 @@ #include -#include "machine-c-api.h" #include "pma-defines.h" namespace cartesi { -/// \file -/// \brief Physical memory attributes constants. - /// \brief Fixed PMA ranges. enum PMA_ranges : uint64_t { PMA_SHADOW_STATE_START = EXPAND_UINT64_C(PMA_SHADOW_STATE_START_DEF), ///< Start of processor shadow range @@ -93,7 +89,7 @@ enum PMA_ISTART_shifts { PMA_ISTART_IR_SHIFT = 6, PMA_ISTART_IW_SHIFT = 7, PMA_ISTART_DID_SHIFT = 8, - PMA_ISTART_START_SHIFT = 12, + PMA_ISTART_START_SHIFT = PMA_PAGE_SIZE_LOG2_DEF }; /// \brief PMA istart masks @@ -112,57 +108,20 @@ enum PMA_ISTART_masks : uint64_t { /// \brief PMA device ids enum class PMA_ISTART_DID { - memory = PMA_MEMORY_DID_DEF, ///< DID for memory - shadow_state = PMA_SHADOW_STATE_DID_DEF, ///< DID for shadow device - shadow_pmas = PMA_SHADOW_PMAS_DID_DEF, ///< DID for shadow pma array device - shadow_TLB = PMA_SHADOW_TLB_DID_DEF, ///< DID for shadow TLB device - flash_drive = PMA_FLASH_DRIVE_DID_DEF, ///< DID for drive device - CLINT = PMA_CLINT_DID_DEF, ///< DID for CLINT device - PLIC = PMA_PLIC_DID_DEF, ///< DID for PLIC device - HTIF = PMA_HTIF_DID_DEF, ///< DID for HTIF device - VIRTIO = PMA_VIRTIO_DID_DEF, ///< DID for VirtIO devices - cmio_rx_buffer = PMA_CMIO_RX_BUFFER_DID_DEF, ///< DID for cmio receive buffer - cmio_tx_buffer = PMA_CMIO_TX_BUFFER_DID_DEF, ///< DID for cmio transmit buffer - shadow_uarch = PMA_SHADOW_UARCH_STATE_DID_DEF, ///< DID for shadow uarch state device + memory = PMA_MEMORY_DID_DEF, ///< DID for memory + shadow_state = PMA_SHADOW_STATE_DID_DEF, ///< DID for shadow device + shadow_pmas = PMA_SHADOW_PMAS_DID_DEF, ///< DID for shadow pma array device + shadow_TLB = PMA_SHADOW_TLB_DID_DEF, ///< DID for shadow TLB device + flash_drive = PMA_FLASH_DRIVE_DID_DEF, ///< DID for drive device + CLINT = PMA_CLINT_DID_DEF, ///< DID for CLINT device + PLIC = PMA_PLIC_DID_DEF, ///< DID for PLIC device + HTIF = PMA_HTIF_DID_DEF, ///< DID for HTIF device + VIRTIO = PMA_VIRTIO_DID_DEF, ///< DID for VirtIO devices + cmio_rx_buffer = PMA_CMIO_RX_BUFFER_DID_DEF, ///< DID for cmio receive buffer + cmio_tx_buffer = PMA_CMIO_TX_BUFFER_DID_DEF, ///< DID for cmio transmit buffer + shadow_uarch_state = PMA_SHADOW_UARCH_STATE_DID_DEF, ///< DID for shadow uarch state device }; -static constexpr const char *pma_get_DID_name(PMA_ISTART_DID did) { - switch (did) { - case PMA_ISTART_DID::memory: - return "DID.memory"; - case PMA_ISTART_DID::shadow_state: - return "DID.shadow_state"; - case PMA_ISTART_DID::shadow_pmas: - return "DID.shadow_pmas"; - case PMA_ISTART_DID::shadow_TLB: - return "DID.shadow_TLB"; - case PMA_ISTART_DID::flash_drive: - return "DID.flash_drive"; - case PMA_ISTART_DID::CLINT: - return "DID.CLINT"; - case PMA_ISTART_DID::PLIC: - return "DID.PLIC"; - case PMA_ISTART_DID::HTIF: - return "DID.HTIF"; - case PMA_ISTART_DID::VIRTIO: - return "DID.VIRTIO"; - case PMA_ISTART_DID::cmio_rx_buffer: - return "DID.cmio_rx_buffer"; - case PMA_ISTART_DID::cmio_tx_buffer: - return "DID.cmio_tx_buffer"; - case PMA_ISTART_DID::shadow_uarch: - return "DID.shadow_uarch"; - default: - return "DID.unknown"; - } -} - -static_assert(PMA_CMIO_RX_BUFFER_START_DEF == CM_PMA_CMIO_RX_BUFFER_START); -static_assert(PMA_CMIO_RX_BUFFER_LOG2_SIZE_DEF == CM_PMA_CMIO_RX_BUFFER_LOG2_SIZE); -static_assert(PMA_CMIO_TX_BUFFER_START_DEF == CM_PMA_CMIO_TX_BUFFER_START); -static_assert(PMA_CMIO_TX_BUFFER_LOG2_SIZE_DEF == CM_PMA_CMIO_TX_BUFFER_LOG2_SIZE); -static_assert(PMA_RAM_START_DEF == CM_PMA_RAM_START); - } // namespace cartesi #endif diff --git a/src/pma-driver.cpp b/src/pma-driver.cpp deleted file mode 100644 index cc5b38e9e..000000000 --- a/src/pma-driver.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "pma-driver.h" - -#include - -#include "interpret.h" - -namespace cartesi { - -bool device_read_error(void * /*context*/, i_device_state_access * /*a*/, uint64_t /*offset*/, uint64_t * /*val*/, - int /*log2_size*/) { - return false; -} - -execute_status device_write_error(void * /*context*/, i_device_state_access * /*a*/, uint64_t /*offset*/, - uint64_t /*val*/, int /*log2_size*/) { - return execute_status::failure; -} - -} // namespace cartesi diff --git a/src/pma-driver.h b/src/pma-driver.h deleted file mode 100644 index c3534610b..000000000 --- a/src/pma-driver.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef PMA_DRIVER_H -#define PMA_DRIVER_H - -#include - -#include "interpret.h" - -namespace cartesi { - -/// \file -/// \brief Declares pma_driver, which provides callback functions for reading and writing to device memory ranges - -// Forward declarations -class i_device_state_access; - -/// \brief Prototype for callback invoked when machine wants to read from a device range. -/// \param context Device-specific context -/// \param da Object through which the machine state can be accessed. -/// \param offset Offset of requested value from range base address. -/// \param val Pointer to word where value will be stored. -/// \param log2_size log2 of size of value to read (0 = uint8_t, 1 = uint16_t, 2 = uint32_t, 3 = uint64_t). -/// \returns True if operation succeeded, false otherwise. -using device_read = bool (*)(void *context, i_device_state_access *da, uint64_t offset, uint64_t *val, int log2_size); - -/// \brief Default read callback issues error on reads. -bool device_read_error(void *context, i_device_state_access *da, uint64_t offset, uint64_t *val, int log2_size); - -/// \brief Prototype for callback invoked when machine wants to write to a range. -/// \param context Device-specific context -/// \param da Object through which the machine state can be accessed. -/// \param offset Offset of requested value from range base address. -/// \param val Word to be written at \p offset. -/// \param log2_size log2 of size of value to read (0 = uint8_t, 1 = uint16_t, 2 = uint32_t, 3 = uint64_t). -/// \returns execute::failure if operation failed, otherwise other success enumeration if operation succeeded. -using device_write = execute_status (*)(void *context, i_device_state_access *da, uint64_t offset, uint64_t val, - int log2_size); - -/// \brief Default write callback issues error on write. -execute_status device_write_error(void *context, i_device_state_access *da, uint64_t offset, uint64_t val, - int log2_size); - -/// \brief Driver for device memory ranges. -struct pma_driver final { - const char *name{""}; - device_read read{device_read_error}; - device_write write{device_write_error}; -}; - -} // namespace cartesi - -#endif diff --git a/src/pma.cpp b/src/pma.cpp deleted file mode 100644 index 6f566cec5..000000000 --- a/src/pma.cpp +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "pma.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "is-pristine.h" -#include "os.h" -#include "pma-constants.h" -#include "pma-driver.h" -#include "unique-c-ptr.h" - -namespace cartesi { - -using namespace std::string_literals; - -pma_memory::pma_memory(const std::string &description, uint64_t length, const callocd & /*c*/) try : - m_ptr{make_unique_calloc(length)}, - m_host_memory{std::get(m_ptr).get()} { -} catch (std::exception &e) { - throw std::runtime_error{e.what() + " when initializing "s + description}; -} - -pma_memory::pma_memory(const std::string &description, uint64_t length, const std::string &path, const callocd &c) : - pma_memory{description, length, c} { - // Try to load image file, if any - if (!path.empty()) { - auto fp = make_unique_fopen(path.c_str(), "rb", std::nothrow_t{}); - if (!fp) { - throw std::system_error{errno, std::generic_category(), - "error opening image file '"s + path + "' when initializing "s + description}; - } - // Get file size - if (fseek(fp.get(), 0, SEEK_END) != 0) { - throw std::system_error{errno, std::generic_category(), - "error obtaining length of image file '"s + path + "' when initializing "s + description}; - } - const auto file_length = static_cast(ftello(fp.get())); - if (fseek(fp.get(), 0, SEEK_SET) != 0) { - throw std::system_error{errno, std::generic_category(), - "error obtaining length of image file '"s + path + "' when initializing "s + description}; - } - // Check against PMA range size - if (file_length > length) { - throw std::runtime_error{"image file '"s + path + "' of "s + description + " is too large for range"s}; - } - // Read to host memory - const auto read_length = static_cast(fread(m_host_memory, 1, length, fp.get())); - if (read_length != file_length) { - throw std::runtime_error{"error reading from image file '"s + path + "' when initializing "s + description}; - } - } -} - -pma_memory::pma_memory(const std::string &description, uint64_t length, const std::string &path, const mmapd &m) try : - m_ptr{make_unique_mmap(path.c_str(), length, m.shared)}, - m_host_memory{std::get(m_ptr).get()} { -} catch (std::exception &e) { - throw std::runtime_error{e.what() + " when initializing "s + description}; -} - -uint64_t pma_entry::get_istart() const { - uint64_t istart = m_start; - istart |= (static_cast(get_istart_M()) << PMA_ISTART_M_SHIFT); - istart |= (static_cast(get_istart_IO()) << PMA_ISTART_IO_SHIFT); - istart |= (static_cast(get_istart_E()) << PMA_ISTART_E_SHIFT); - istart |= (static_cast(get_istart_R()) << PMA_ISTART_R_SHIFT); - istart |= (static_cast(get_istart_W()) << PMA_ISTART_W_SHIFT); - istart |= (static_cast(get_istart_X()) << PMA_ISTART_X_SHIFT); - istart |= (static_cast(get_istart_IR()) << PMA_ISTART_IR_SHIFT); - istart |= (static_cast(get_istart_IW()) << PMA_ISTART_IW_SHIFT); - istart |= (static_cast(get_istart_DID()) << PMA_ISTART_DID_SHIFT); - return istart; -} - -uint64_t pma_entry::get_ilength() const { - return m_length; -} - -void pma_entry::write_memory(uint64_t paddr, const unsigned char *data, uint64_t size) { - if (!get_istart_M() || get_istart_E()) { - throw std::invalid_argument{"address range not entirely in memory PMA"}; - } - if (!contains(paddr, size)) { - throw std::invalid_argument{"range not contained in pma"}; - } - if (data == nullptr) { - throw std::invalid_argument{"invalid data buffer"}; - } - // The case of writing a large range chunk is special and optimized for uarch reset - if (size > PMA_PAGE_SIZE) { - // Copy in chunks of page size, to avoid marking dirty pages unnecessarily - for (uint64_t offset = 0; offset < size; offset += PMA_PAGE_SIZE) { - const uint64_t paddr_offset = paddr + offset; - const uint64_t chunk_len = std::min(PMA_PAGE_SIZE, size - offset); - const unsigned char *src = data + offset; - unsigned char *dest = get_memory().get_host_memory() + (paddr_offset - get_start()); - if (memcmp(dest, src, chunk_len) != 0) { - // Page is different, we have to copy memory - memcpy(dest, src, chunk_len); - mark_dirty_pages(paddr + offset, chunk_len); - } - } - } else { - memcpy(get_memory().get_host_memory() + (paddr - get_start()), data, size); - mark_dirty_pages(paddr, size); - } -} - -void pma_entry::fill_memory(uint64_t paddr, unsigned char value, uint64_t size) { - if (!get_istart_M() || get_istart_E()) { - throw std::invalid_argument{"address range not entirely in memory PMA"}; - } - if (!contains(paddr, size)) { - throw std::invalid_argument{"range not contained in pma"}; - } - // The case of filling a large range with zeros is special and optimized for uarch reset - if (value == 0 && size > PMA_PAGE_SIZE) { - // Fill in chunks of page size, to avoid marking dirty pages unnecessarily - for (uint64_t offset = 0; offset < size; offset += PMA_PAGE_SIZE) { - const uint64_t paddr_offset = paddr + offset; - const uint64_t chunk_len = std::min(PMA_PAGE_SIZE, size - offset); - unsigned char *dest = get_memory().get_host_memory() + (paddr_offset - get_start()); - if (!is_pristine(dest, chunk_len)) { - // Page is different, we have to fill memory - memset(dest, 0, chunk_len); - mark_dirty_pages(paddr + offset, chunk_len); - } - } - } else { - memset(get_memory().get_host_memory() + (paddr - get_start()), value, size); - mark_dirty_pages(paddr, size); - } -} - -bool pma_peek_error(const pma_entry & /*pma*/, const machine & /*m*/, uint64_t /*offset*/, uint64_t /*length*/, - const unsigned char ** /*page_data*/, unsigned char * /*scratch*/) { - return false; -} - -bool pma_peek_pristine(const pma_entry &pma, const machine & /*m*/, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char * /*scratch*/) { - *data = nullptr; - return length <= pma.get_length() && offset <= pma.get_length() - length; -} - -/// \brief Memory range peek callback. See pma_peek. -static bool memory_peek(const pma_entry &pma, const machine & /*m*/, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char * /*scratch*/) { - // If desired data does not fit in range, return error - if (length > pma.get_length() || offset > pma.get_length() - length) { - *data = nullptr; - return false; - } - // Otherwise, return pointer directly into host memory - *data = pma.get_memory().get_host_memory() + offset; - return true; -} - -pma_entry make_mmapd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length, - const std::string &path, bool shared) { - if (length == 0) { - throw std::invalid_argument{description + " length cannot be zero"s}; - } - return pma_entry{description, start, length, pma_memory{description, length, path, pma_memory::mmapd{shared}}, - memory_peek}; -} - -pma_entry make_callocd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length) { - if (length == 0) { - throw std::invalid_argument{description + " length cannot be zero"s}; - } - return pma_entry{description, start, length, pma_memory{description, length, pma_memory::callocd{}}, memory_peek}; -} - -pma_entry make_callocd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length, - const std::string &path) { - if (length == 0) { - throw std::invalid_argument{description + " length cannot be zero"s}; - } - return pma_entry{description, start, length, pma_memory{description, length, path, pma_memory::callocd{}}, - memory_peek}; -} - -pma_entry make_device_pma_entry(const std::string &description, uint64_t start, uint64_t length, pma_peek peek, - const pma_driver *driver, void *context) { - if (length == 0) { - throw std::invalid_argument{description + " length cannot be zero"s}; - } - return pma_entry{description, start, length, pma_device{description, driver, context}, peek}; -} - -pma_entry make_empty_pma_entry(const std::string &description, uint64_t start, uint64_t length) { - return pma_entry{description, start, length}; -} - -} // namespace cartesi diff --git a/src/pma.h b/src/pma.h index f30b2e720..eef556cb0 100644 --- a/src/pma.h +++ b/src/pma.h @@ -17,545 +17,88 @@ #ifndef PMA_H #define PMA_H -#include -#include #include -#include -#include -#include -#include -#include #include "pma-constants.h" -#include "pma-driver.h" -#include "unique-c-ptr.h" namespace cartesi { -// Forward declarations -class pma_entry; -class machine; - /// \file -/// \brief Physical memory attributes interface - -/// \brief Prototype for callback invoked when machine wants to peek into a range with no side-effects. -/// \param pma Reference to corresponding PMA entry. -/// \param m Reference to associated machine. -/// \param offset Offset within range to start reading. -/// \param length Number of bytes to read. -/// \param data Receives pointer to start of data, or nullptr if data is constant *and* pristine. -/// \param scratch Pointer to memory buffer that must be able to hold \p length bytes. -/// \returns True if operation succeeded, false otherwise. -using pma_peek = bool (*)(const pma_entry &pma, const machine &m, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char *scratch); - -/// \brief Default peek callback issues error on peeks. -bool pma_peek_error(const pma_entry & /*pma*/, const machine & /*m*/, uint64_t /*offset*/, uint64_t /*length*/, - const unsigned char ** /*data*/, unsigned char * /*scratch*/); - -/// \brief Default peek callback for pristine ranges. -bool pma_peek_pristine(const pma_entry & /*pma*/, const machine & /*m*/, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char * /*scratch*/); - -/// \brief Data for IO ranges. -class pma_device final { - - const pma_driver *m_driver; ///< Driver with callbacks. - void *m_context; ///< Context to pass to callbacks. - -public: - /// \brief Constructor from entries. - /// \param description Informative description of PMA entry for use in error messages - /// \param context Context to pass to callbacks. - /// \param driver Pointer to driver with callbacks. - pma_device(const std::string & /*description*/, const pma_driver *driver, void *context) : - m_driver{driver}, - m_context{context} {} - ~pma_device() = default; - - pma_device(const pma_device &other) = delete; - pma_device(pma_device &&other) = default; - pma_device &operator=(const pma_device &other) = delete; - pma_device &operator=(pma_device &&other) = default; - - /// \brief Returns context to pass to callbacks. - void *get_context() { - return m_context; - } - - /// \brief Returns context to pass to callbacks. - void *get_context() const { - return m_context; - } - - /// \brief Returns pointer to driver with callbacks - const pma_driver *get_driver() const { - return m_driver; - } -}; - -// For performance reasons, we can't possibly invoke a -// function every time we want to read from a memory -// range, so memory ranges do not use a driver like -// IO ranges do. So we use naked pointers to host memory. - -/// \brief Data for memory ranges. -class pma_memory final { - - using callocd_ptr = unique_calloc_ptr; - using mmapd_ptr = unique_mmap_ptr; - - std::variant - m_ptr; - - unsigned char *m_host_memory; ///< Start of associated memory region in host. - -public: - /// \brief Mmap'd range data (shared or not). - struct mmapd { - bool shared; - }; - - /// \brief Constructor for mmap'd ranges. - /// \param description Informative description of PMA entry for use in error messages - /// \param length Length of range. - /// \param path Path for backing file. - /// \param m Mmap'd range data (shared or not). - pma_memory(const std::string &description, uint64_t length, const std::string &path, const mmapd &m); - - /// \brief Calloc'd range data (just a tag). - struct callocd {}; - - /// \brief Constructor for calloc'd ranges. - /// \param description Informative description of PMA entry for use in error messages - /// \param length Length of range. - /// \param path Path for backing file. - /// \param c Calloc'd range data (just a tag). - pma_memory(const std::string &description, uint64_t length, const std::string &path, const callocd &c); - - /// \brief Constructor for calloc'd ranges. - /// \param description Informative description of PMA entry for use in error messages - /// \param length Length of range. - /// \param c Calloc'd range data (just a tag). - pma_memory(const std::string &description, uint64_t length, const callocd &c); - - /// \brief No copy constructor - pma_memory(const pma_memory &other) = delete; - - /// \brief No copy assignment - pma_memory &operator=(const pma_memory &other) = delete; - - /// \brief Move constructor - pma_memory(pma_memory &&other) noexcept = default; - - /// \brief Move assignment - pma_memory &operator=(pma_memory &&other) noexcept = default; - - /// \brief Default destructor - ~pma_memory() = default; - - /// \brief Returns start of associated memory region in host - unsigned char *get_host_memory() { - return m_host_memory; - } - - /// \brief Returns start of associated memory region in host - const unsigned char *get_host_memory() const { - return m_host_memory; - } -}; - -/// \brief Physical Memory Attribute entry. -/// \details The target's physical memory layout is described by an -/// array of PMA entries. -class pma_entry final { -public: - ///< Exploded flags for PMA entry. - struct flags { - // bool M = std::holds_alternative(data); - // bool IO = std::holds_alternative(data); - // bool E = std::holds_alternative(data); - bool R; - bool W; - bool X; - bool IR; - bool IW; - PMA_ISTART_DID DID; - }; - -private: - std::string m_description; ///< Informative description of PMA entry for use in error messages. - uint64_t m_start; ///< Start of physical memory range in target. - uint64_t m_length; ///< Length of physical memory range in target. - int m_index; ///< PMA entry index in target. - flags m_flags; ///< PMA Flags - - pma_peek m_peek; ///< Callback for peek operations. - - std::vector m_dirty_page_map; ///< Map of dirty pages. - - std::variant - m_data; - -public: - /// \brief No copy constructor - pma_entry(const pma_entry &) = delete; - /// \brief No copy assignment - pma_entry &operator=(const pma_entry &) = delete; - /// \brief Default move constructor - pma_entry(pma_entry &&) = default; - /// \brief Default move assignment - pma_entry &operator=(pma_entry &&) = default; - /// \bried Default destructor - ~pma_entry() = default; - - /// \brief Constructor for empty entry - /// \param description Informative description of PMA entry for use in error messages - /// \param start Start of range. - /// \param length Length of range. - explicit pma_entry(std::string description, uint64_t start, uint64_t length) : - m_description{std::move(description)}, - m_start{start}, - m_length{length}, - m_index{PMA_MAX}, - m_flags{}, - m_peek{pma_peek_error}, - m_data{std::monostate{}} { - ; - } - - /// \brief Default constructor creates an empty entry spanning an empty range - /// \param description Informative description of PMA entry for use in error messages - explicit pma_entry(std::string description = "empty") : pma_entry{std::move(description), 0, 0} { - ; - } - - /// \brief Constructor for memory entry - /// \param description Informative description of PMA entry for use in error messages - /// \param start Start of range. - /// \param length Length of range. - /// \param memory Memory PMA holding range data - /// \param peek Function used to extract a page of data from the range - explicit pma_entry(std::string description, uint64_t start, uint64_t length, pma_memory &&memory, - pma_peek peek = pma_peek_error) : - m_description{std::move(description)}, - m_start{start}, - m_length{length}, - m_index{PMA_MAX}, - m_flags{}, - m_peek{peek}, - m_data{std::move(memory)} { - // allocate dirty page map and mark all pages as dirty - m_dirty_page_map.resize((length / (8 * PMA_PAGE_SIZE)) + 1, 0xff); - } - - /// \brief Constructor for device entry - /// \param description Informative description of PMA entry for use in error messages - /// \param start Start of range. - /// \param length Length of range. - /// \param device Device PMA controlling range data - /// \param peek Function used to extract a page of data from the range - explicit pma_entry(std::string description, uint64_t start, uint64_t length, pma_device &&device, - pma_peek peek = pma_peek_error) : - m_description{std::move(description)}, - m_start{start}, - m_length{length}, - m_index{PMA_MAX}, - m_flags{}, - m_peek{peek}, - m_data{std::move(device)} { - ; - } - - /// \brief Set PMA entry index in target. - void set_index(int index) { - m_index = index; - } - - /// \brief Returns PMA entry index in target. - int get_index() const { - return m_index; - } - - /// \brief Set flags for lvalue references - /// \param f New flags. - pma_entry &set_flags(flags f) & { - m_flags = f; - return *this; - } - - /// \brief Set flags for rvalue references - /// \param f New flags. - pma_entry &&set_flags(flags f) && { - m_flags = f; - return std::move(*this); - } - - /// \brief Get flags - /// \return Flags - flags get_flags() const { - return m_flags; - } - - /// \brief Returns the peek callback for the range. - pma_peek get_peek() const { - return m_peek; - } - - /// \returns data specific to M ranges - const pma_memory &get_memory() const { - return std::get(m_data); - } - - /// \returns data specific to M ranges - pma_memory &get_memory() { - return std::get(m_data); - } - - /// \returns data specific to memory ranges (cannot throw exceptions, but will crash if not a memory range). - pma_memory &get_memory_noexcept() { - return *std::get_if(&m_data); - } - - /// \returns data specific to memory ranges (cannot throw exceptions, but will crash if not a memory range). - const pma_memory &get_memory_noexcept() const { - return *std::get_if(&m_data); - } - - /// \returns data specific to IO ranges - const pma_device &get_device() const { - return std::get(m_data); - } - - /// \returns data specific to IO ranges - pma_device &get_device() { - return std::get(m_data); - } - - /// \returns data specific to IO ranges (cannot throw exceptions, but will crash if not an IO range). - const pma_device &get_device_noexcept() const { - return *std::get_if(&m_data); - } - - /// \returns data specific to IO ranges (cannot throw exceptions, but will crash if not an IO range). - pma_device &get_device_noexcept() { - return *std::get_if(&m_data); - } - - /// \brief Returns packed PMA istart field as per whitepaper - uint64_t get_istart() const; - - /// \brief Returns start of physical memory range in target. - uint64_t get_start() const { - return m_start; - } - /// \brief Returns length of physical memory range in target. - uint64_t get_length() const { - return m_length; - } - - /// \brief Returns encoded PMA ilength field as per whitepaper - uint64_t get_ilength() const; - - /// \brief Tells if PMA is a memory range - bool get_istart_M() const { - return std::holds_alternative(m_data); - } - - /// \brief Tells if PMA is a device range - bool get_istart_IO() const { - return std::holds_alternative(m_data); - } - - /// \brief Tells if PMA is an empty range - bool get_istart_E() const { - return std::holds_alternative(m_data); - } - - /// \brief Tells if PMA range is readable - bool get_istart_R() const { - return m_flags.R; - } - - /// \brief Tells if PMA range is writable - bool get_istart_W() const { - return m_flags.W; - } - - /// \brief Tells if PMA range is executable - bool get_istart_X() const { - return m_flags.X; - } - - /// \brief Tells if reads to PMA range are idempotent - bool get_istart_IR() const { - return m_flags.IR; - } - - /// \brief Tells if writes to PMA range are idempotent - bool get_istart_IW() const { - return m_flags.IW; - } - - /// \brief Returns the id of the device that owns the range - PMA_ISTART_DID get_istart_DID() const { - return m_flags.DID; - } - - /// \brief Mark a given page as dirty - /// \param offset_in_range Any offset in range within desired page - void mark_dirty_page(uint64_t offset_in_range) { - if (!m_dirty_page_map.empty()) { - auto page_index = offset_in_range >> PMA_constants::PMA_PAGE_SIZE_LOG2; - auto map_index = page_index >> 3; - assert(map_index < m_dirty_page_map.size()); - m_dirty_page_map[map_index] |= (1 << (page_index & 7)); - } - } - - /// \brief Mark all pages in rage as dirty - /// \param address Start address - /// \param size Size of range - void mark_dirty_pages(uint64_t address, uint64_t size) { - if (m_dirty_page_map.empty()) { - return; - } - if (!get_istart_M() || get_istart_E()) { - throw std::invalid_argument{"address range not entirely in memory PMA"}; - } - if (!contains(address, size)) { - throw std::invalid_argument{"range not contained in pma"}; - } - constexpr const auto log2_page_size = PMA_constants::PMA_PAGE_SIZE_LOG2; - uint64_t page_in_range = ((address - get_start()) >> log2_page_size) << log2_page_size; - constexpr const auto page_size = PMA_constants::PMA_PAGE_SIZE; - auto npages = (size + page_size - 1) / page_size; - for (decltype(npages) i = 0; i < npages; ++i) { - mark_dirty_page(page_in_range); - page_in_range += page_size; - } - } - - /// \brief Mark a given page as clean - /// \param offset_in_range Any offset in range within desired page - void mark_clean_page(uint64_t offset_in_range) { - if (!m_dirty_page_map.empty()) { - auto page_index = offset_in_range >> PMA_constants::PMA_PAGE_SIZE_LOG2; - auto map_index = page_index >> 3; - assert(map_index < m_dirty_page_map.size()); - m_dirty_page_map[map_index] &= ~(1 << (page_index & 7)); - } - } - - /// \brief Checks if a given page is marked dirty - /// \param offset_in_range Any offset in range within desired page - /// \returns true if dirty, false if clean - bool is_page_marked_dirty(uint64_t offset_in_range) const { - if (!m_dirty_page_map.empty()) { - auto page_index = offset_in_range >> PMA_constants::PMA_PAGE_SIZE_LOG2; - auto map_index = page_index >> 3; - assert(map_index < m_dirty_page_map.size()); - return (m_dirty_page_map[map_index] & (1 << (page_index & 7))) != 0; - } - return true; - } - - /// \brief Marks all pages in range as clean - void mark_pages_clean() { - std::fill(m_dirty_page_map.begin(), m_dirty_page_map.end(), 0); - } - - /// \brief Returns PMA description as a string - /// \returns Description - const std::string &get_description() const { - return m_description; - } - - /// \brief Checks if a memory range is within this PMA boundaries - /// \param address Address - /// \param length Length - /// \return true if this PMA contains the given range - bool contains(uint64_t address, uint64_t length) const { - if (get_istart_E()) { - return false; - } - return address >= get_start() && get_length() >= length && address - get_start() <= get_length() - length; - } - - /// \brief Writes data to pma memory - /// \param paddr Destination address within pma range - /// \param data Source data - /// \param size Data size - void write_memory(uint64_t paddr, const unsigned char *data, uint64_t size); - - /// \brief Fills pma memory with a given value - /// \param paddr Destination address within pma range - /// \param value Value to write - /// \param size Data size - void fill_memory(uint64_t paddr, unsigned char value, uint64_t size); +/// \brief Physical memory attributes. + +static constexpr const char *pma_get_DID_name(PMA_ISTART_DID did) { + switch (did) { + case PMA_ISTART_DID::memory: + return "DID.memory"; + case PMA_ISTART_DID::shadow_state: + return "DID.shadow_state"; + case PMA_ISTART_DID::shadow_pmas: + return "DID.shadow_pmas"; + case PMA_ISTART_DID::shadow_TLB: + return "DID.shadow_TLB"; + case PMA_ISTART_DID::flash_drive: + return "DID.flash_drive"; + case PMA_ISTART_DID::CLINT: + return "DID.CLINT"; + case PMA_ISTART_DID::PLIC: + return "DID.PLIC"; + case PMA_ISTART_DID::HTIF: + return "DID.HTIF"; + case PMA_ISTART_DID::VIRTIO: + return "DID.VIRTIO"; + case PMA_ISTART_DID::cmio_rx_buffer: + return "DID.cmio_rx_buffer"; + case PMA_ISTART_DID::cmio_tx_buffer: + return "DID.cmio_tx_buffer"; + case PMA_ISTART_DID::shadow_uarch_state: + return "DID.shadow_uarch"; + default: + return "DID.unknown"; + } +} + +///< Unpacked attribute flags +struct pma_flags { + bool M; ///< is memory + bool IO; ///< is device + bool E; ///< is empty + bool R; ///< is readable + bool W; ///< is writeable + bool X; ///< is executable + bool IR; ///< is read-idempotent + bool IW; ///< is write-idempotent + PMA_ISTART_DID DID; ///< driver id + + // Defaulted comparison operator + bool operator==(const pma_flags &) const = default; }; -/// \brief Creates a PMA entry for a new memory range initially filled with zeros. -/// \param description Informative description of PMA entry for use in error messages -/// \param start Start of PMA range. -/// \param length Length of PMA range. -/// \returns Corresponding PMA entry -pma_entry make_callocd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length); - -/// \brief Creates a PMA entry for a new memory range initially filled with the contents of a backing file. -/// \param description Informative description of PMA entry for use in error messages -/// \param start Start of PMA range. -/// \param length Length of PMA range. -/// \param path Path to backing file. -/// \returns Corresponding PMA entry -pma_entry make_callocd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length, - const std::string &path); - -/// \brief Creates a PMA entry for a new memory region using the host's -/// mmap functionality. -/// \param description Informative description of PMA entry for use in error messages -/// \param start Start of physical memory range in the target address -/// space on which to map the memory region. -/// \param length Length of physical memory range in the -/// target address space on which to map the memory region. -/// \param path Reference to a string containing the filename -/// for the backing file in the host with the contents of the memory region. -/// \param shared Whether target modifications to the memory region are -/// reflected in the host's backing file. -/// \returns Corresponding PMA entry -/// \details \p length must match the size of the backing file. -/// This function is typically used to map flash drives. -pma_entry make_mmapd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length, - const std::string &path, bool shared); - -/// \brief Creates a PMA entry for a new memory-mapped IO device. -/// \param description Informative description of PMA entry for use in error messages -/// \param start Start of physical memory range in the target address -/// space on which to map the device. -/// \param length Length of physical memory range in the -/// target address space on which to map the device. -/// \param peek Peek callback for the range. -/// \param driver Pointer to driver with callbacks. -/// \param context Pointer to context to be passed to callbacks. -/// \returns Corresponding PMA entry -pma_entry make_device_pma_entry(const std::string &description, uint64_t start, uint64_t length, pma_peek peek, - const pma_driver *driver, void *context = nullptr); - -/// \brief Creates an empty PMA entry. -/// \param description Informative description of PMA entry for use in error messages -/// \param start Start of physical memory range in the target address -/// space on which to map the device. -/// \param length Length of physical memory range in the -/// target address space on which to map the device. -/// \returns Corresponding PMA entry -pma_entry make_empty_pma_entry(const std::string &description, uint64_t start, uint64_t length); +static constexpr pma_flags unpack_pma_istart(uint64_t istart, uint64_t &start) { + start = istart & PMA_ISTART_START_MASK; + return pma_flags{.M = ((istart & PMA_ISTART_M_MASK) >> PMA_ISTART_M_SHIFT) != 0, + .IO = ((istart & PMA_ISTART_IO_MASK) >> PMA_ISTART_IO_SHIFT) != 0, + .E = ((istart & PMA_ISTART_E_MASK) >> PMA_ISTART_E_SHIFT) != 0, + .R = ((istart & PMA_ISTART_R_MASK) >> PMA_ISTART_R_SHIFT) != 0, + .W = ((istart & PMA_ISTART_W_MASK) >> PMA_ISTART_W_SHIFT) != 0, + .X = ((istart & PMA_ISTART_X_MASK) >> PMA_ISTART_X_SHIFT) != 0, + .IR = ((istart & PMA_ISTART_IR_MASK) >> PMA_ISTART_IR_SHIFT) != 0, + .IW = ((istart & PMA_ISTART_IW_MASK) >> PMA_ISTART_IW_SHIFT) != 0, + .DID = static_cast((istart & PMA_ISTART_DID_MASK) >> PMA_ISTART_DID_SHIFT)}; +} + +static constexpr uint64_t pack_pma_istart(const pma_flags &flags, uint64_t start) { + uint64_t istart = start; + istart |= (static_cast(flags.M) << PMA_ISTART_M_SHIFT); + istart |= (static_cast(flags.IO) << PMA_ISTART_IO_SHIFT); + istart |= (static_cast(flags.E) << PMA_ISTART_E_SHIFT); + istart |= (static_cast(flags.R) << PMA_ISTART_R_SHIFT); + istart |= (static_cast(flags.W) << PMA_ISTART_W_SHIFT); + istart |= (static_cast(flags.X) << PMA_ISTART_X_SHIFT); + istart |= (static_cast(flags.IR) << PMA_ISTART_IR_SHIFT); + istart |= (static_cast(flags.IW) << PMA_ISTART_IW_SHIFT); + istart |= (static_cast(flags.DID) << PMA_ISTART_DID_SHIFT); + return istart; +} } // namespace cartesi diff --git a/src/pristine-address-range.h b/src/pristine-address-range.h new file mode 100644 index 000000000..f59a6aae5 --- /dev/null +++ b/src/pristine-address-range.h @@ -0,0 +1,59 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef PRISTINE_ADDRESS_RANGE_H +#define PRISTINE_ADDRESS_RANGE_H + +#include "address-range.h" + +namespace cartesi { + +// Forward declarations +class machine; + +/// \file +/// \brief An address range that appears pristine in the state + +class pristine_address_range : public address_range { +public: + /// \brief Constructor + /// \param description Description of address range for use in error messages + /// \param start Target physical address where range starts + /// \param length Length of range, in bytes + /// \param f Phyical memory attribute flags for range + template + pristine_address_range(const char *description, uint64_t start, uint64_t length, pma_flags f, ABRT abrt) : + address_range{description, start, length, f, abrt} { + ; + } + + pristine_address_range(const pristine_address_range &other) = default; + pristine_address_range &operator=(const pristine_address_range &other) = default; + pristine_address_range(pristine_address_range &&other) = default; + pristine_address_range &operator=(pristine_address_range &&other) = default; + ~pristine_address_range() override = default; + +private: + bool do_peek(const machine & /*m*/, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char * /*scratch*/) const noexcept override { + *data = nullptr; + return contains_relative(offset, length); + } +}; + +} // namespace cartesi + +#endif diff --git a/src/record-send-cmio-state-access.h b/src/record-send-cmio-state-access.h index 1262d5974..9b365ccf2 100644 --- a/src/record-send-cmio-state-access.h +++ b/src/record-send-cmio-state-access.h @@ -35,18 +35,12 @@ #include "machine-state.h" #include "machine.h" #include "meta.h" -#include "pma.h" #include "shadow-state.h" namespace cartesi { class record_send_cmio_state_access; -// Type trait that should return the pma_entry type for a state access class -template <> -struct i_state_access_pma_entry { - using type = pma_entry; -}; // Type trait that should return the fast_addr type for a state access class template <> struct i_state_access_fast_addr { @@ -218,9 +212,9 @@ class record_send_cmio_state_access : throw std::invalid_argument("write_length is less than data_length"); } // We need to compute the hash of the existing data before writing - // Find the target pma entry - auto &pma = m_m.find_pma_entry(paddr, write_length); - if (pma.get_istart_E()) { + // Find the target address range + auto &ar = m_m.find_address_range(paddr, write_length); + if (!ar.is_memory()) { throw std::invalid_argument("address range not entirely in memory PMA"); } access a{}; @@ -236,7 +230,7 @@ class record_send_cmio_state_access : a.set_read_hash(proof.get_target_hash()); if (m_log.get_log_type().has_large_data()) { access_data &data = a.get_read().emplace(write_length); - memcpy(data.data(), pma.get_memory().get_host_memory(), write_length); + memcpy(data.data(), ar.get_host_memory(), write_length); } // We just store the sibling hashes in the access because this is the only missing piece of data needed to @@ -245,20 +239,22 @@ class record_send_cmio_state_access : // write data to memory m_m.write_memory(paddr, data, data_length); + if (write_length > data_length) { m_m.fill_memory(paddr + data_length, 0, write_length - data_length); } // we have to update the merkle tree after every write m_m.update_merkle_tree(); + // log hash and written data // NOLINTBEGIN(bugprone-unchecked-optional-access) a.get_written_hash().emplace(); hasher_type hasher{}; - get_merkle_tree_hash(hasher, pma.get_memory().get_host_memory(), write_length, - machine_merkle_tree::get_word_size(), a.get_written_hash().value()); + get_merkle_tree_hash(hasher, ar.get_host_memory(), write_length, machine_merkle_tree::get_word_size(), + a.get_written_hash().value()); if (m_log.get_log_type().has_large_data()) { access_data &data = a.get_written().emplace(write_length); - memcpy(data.data(), pma.get_memory().get_host_memory(), write_length); + memcpy(data.data(), ar.get_host_memory(), write_length); } // NOLINTEND(bugprone-unchecked-optional-access) m_log.push_access(a, "cmio rx buffer"); diff --git a/src/record-step-state-access.h b/src/record-step-state-access.h index d747ed7ff..710b3328a 100644 --- a/src/record-step-state-access.h +++ b/src/record-step-state-access.h @@ -17,6 +17,7 @@ #ifndef RECORD_STEP_STATE_ACCESS_H #define RECORD_STEP_STATE_ACCESS_H +#include #include #include #include @@ -26,7 +27,7 @@ #include "i-prefer-shadow-state.h" #include "i-state-access.h" #include "machine.h" -#include "shadow-pmas.h" +#include "shadow-pmas-address-range.h" #include "shadow-tlb.h" #include "unique-c-ptr.h" @@ -34,11 +35,6 @@ namespace cartesi { class record_step_state_access; -// Type trait that should return the pma_entry type for a state access class -template <> -struct i_state_access_pma_entry { - using type = pma_entry; -}; // Type trait that should return the fast_addr type for a state access class template <> struct i_state_access_fast_addr { @@ -131,7 +127,6 @@ class record_step_state_access : } private: - using pma_entry_type = pma_entry; using fast_addr_type = host_addr; /// \brief Mark a page as touched and save its contents @@ -235,15 +230,14 @@ class record_step_state_access : throw std::runtime_error("unexpected call to record_step_state_access::write_memory"); } - pma_entry &do_read_pma_entry(uint64_t index) const { + address_range &do_read_pma(uint64_t index) const { assert(index < PMA_MAX); - // replay_step_state_access reconstructs a mock_pma_entry from the + // replay_step_state_access reconstructs a mock_address_range from the // corresponding istart and ilength fields in the shadow pmas // so we mark the page where they live here touch_page(shadow_pmas_get_pma_abs_addr(index, shadow_pmas_what::istart)); touch_page(shadow_pmas_get_pma_abs_addr(index, shadow_pmas_what::ilength)); - // NOLINTNEXTLINE(bugprone-narrowing-conversions) - return m_m.get_state().pmas[static_cast(index)]; + return m_m.read_pma(index); } template diff --git a/src/replay-send-cmio-state-access.h b/src/replay-send-cmio-state-access.h index 57354d237..f22449b05 100644 --- a/src/replay-send-cmio-state-access.h +++ b/src/replay-send-cmio-state-access.h @@ -20,7 +20,6 @@ /// \file /// \brief State access implementation that replays recorded state accesses -#include #include #include #include @@ -34,7 +33,6 @@ #include "i-state-access.h" #include "machine-merkle-tree.h" #include "meta.h" -#include "mock-pma-entry.h" #include "riscv-constants.h" #include "shadow-state.h" #include "unique-c-ptr.h" @@ -43,11 +41,6 @@ namespace cartesi { class replay_send_cmio_state_access; -// Type trait that should return the pma_entry type for a state access class -template <> -struct i_state_access_pma_entry { - using type = mock_pma_entry; -}; // Type trait that should return the fast_addr type for a state access class template <> struct i_state_access_fast_addr { diff --git a/src/replay-step-state-access.h b/src/replay-step-state-access.h index 338965c3c..baf0e6ec6 100644 --- a/src/replay-step-state-access.h +++ b/src/replay-step-state-access.h @@ -17,6 +17,7 @@ #ifndef REPLAY_STEP_STATE_ACCESS_H #define REPLAY_STEP_STATE_ACCESS_H +#include #include #include @@ -25,11 +26,11 @@ #include "i-accept-scoped-notes.h" #include "i-prefer-shadow-state.h" #include "i-state-access.h" -#include "mock-pma-entry.h" +#include "mock-address-range.h" #include "pma-constants.h" #include "replay-step-state-access-interop.h" #include "riscv-constants.h" -#include "shadow-pmas.h" +#include "shadow-pmas-address-range.h" #include "shadow-state.h" #include "shadow-tlb.h" #include "shadow-uarch-state.h" @@ -44,11 +45,6 @@ namespace cartesi { class replay_step_state_access; -// Type trait that should return the pma_entry type for a state access class -template <> -struct i_state_access_pma_entry { - using type = mock_pma_entry; -}; // Type trait that should return the fast_addr type for a state access class template <> struct i_state_access_fast_addr { @@ -92,11 +88,11 @@ class replay_step_state_access : }; struct context { - uint64_t page_count{0}; ///< Number of pages in the step log - page_type *pages{nullptr}; ///< Array of page data - uint64_t sibling_count{0}; ///< Number of sibling hashes in the step log - hash_type *sibling_hashes{nullptr}; ///< Array of sibling hashes - std::array, PMA_MAX> pmas{}; ///< Array of PMA entries + uint64_t page_count{0}; ///< Number of pages in the step log + page_type *pages{nullptr}; ///< Array of page data + uint64_t sibling_count{0}; ///< Number of sibling hashes in the step log + hash_type *sibling_hashes{nullptr}; ///< Array of sibling hashes + mock_address_ranges ars{}; ///< Array of address ranges }; private: @@ -464,7 +460,7 @@ class replay_step_state_access : return false; } - mock_pma_entry &do_read_pma_entry(uint64_t index) const { + address_range &do_read_pma(uint64_t index) const { assert(index < PMA_MAX); // record_step_state_access will have recorded the access to istart and // ilength in its implementation of read_pma_entry. @@ -472,12 +468,12 @@ class replay_step_state_access : const uint64_t ilength = read_pma_ilength(index); // NOLINTNEXTLINE(bugprone-narrowing-conversions) const int i = static_cast(index); - if (!m_context.pmas[i]) { - m_context.pmas[i] = - make_mock_pma_entry(index, istart, ilength, [](const char *err) { interop_throw_runtime_error(err); }); + const auto abrt = [](const char *err) { interop_throw_runtime_error(err); }; + if (std::holds_alternative(m_context.ars[i])) { + m_context.ars[i] = make_mock_address_range(istart, ilength, abrt); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - return m_context.pmas[index].value(); + return get_mock_address_range(m_context.ars[index], abrt); } template diff --git a/src/riscv-constants.h b/src/riscv-constants.h index c298ac510..8d2ffca82 100644 --- a/src/riscv-constants.h +++ b/src/riscv-constants.h @@ -21,7 +21,6 @@ #include "machine-c-version.h" #include "pma-constants.h" -#include "pma-defines.h" /// \file /// \brief RISC-V constants @@ -441,38 +440,38 @@ enum CARTESI_init : uint64_t { MTVAL_INIT = UINT64_C(0), ///< Initial value for mtval MISA_INIT = (MISA_MXL_VALUE << MISA_MXL_SHIFT) | MISA_EXT_S_MASK | MISA_EXT_U_MASK | MISA_EXT_I_MASK | MISA_EXT_M_MASK | MISA_EXT_A_MASK | MISA_EXT_F_MASK | MISA_EXT_D_MASK | - MISA_EXT_C_MASK, ///< Initial value for misa - MIE_INIT = UINT64_C(0), ///< Initial value for mie - MIP_INIT = UINT64_C(0), ///< Initial value for mip - MEDELEG_INIT = UINT64_C(0), ///< Initial value for medeleg - MIDELEG_INIT = UINT64_C(0), ///< Initial value for mideleg - MCOUNTEREN_INIT = UINT64_C(0), ///< Initial value for mcounteren - STVEC_INIT = UINT64_C(0), ///< Initial value for stvec - SSCRATCH_INIT = UINT64_C(0), ///< Initial value for sscratch - SEPC_INIT = UINT64_C(0), ///< Initial value for sepc - SCAUSE_INIT = UINT64_C(0), ///< Initial value for scause - STVAL_INIT = UINT64_C(0), ///< Initial value for stval - SATP_INIT = UINT64_C(0), ///< Initial value for satp - SCOUNTEREN_INIT = UINT64_C(0), ///< Initial value for scounteren - ILRSC_INIT = UINT64_C(-1), ///< Initial value for ilrsc - IPRV_INIT = PRV_M, ///< Initial value for iprv - IFLAGS_X_INIT = UINT64_C(0), ///< Initial value for iflags_X - IFLAGS_Y_INIT = UINT64_C(0), ///< Initial value for iflags_Y - IFLAGS_H_INIT = UINT64_C(0), ///< Initial value for iflags_H - IUNREP_INIT = UINT64_C(0), ///< Initial value for iunrep - MTIMECMP_INIT = UINT64_C(0), ///< Initial value for mtimecmp - GIRQPEND_INIT = UINT64_C(0), ///< Initial value for girqpend - GIRQSRVD_INIT = UINT64_C(0), ///< Initial value for girqsrvd - FROMHOST_INIT = UINT64_C(0), ///< Initial value for fromhost - TOHOST_INIT = UINT64_C(0), ///< Initial value for tohost - MENVCFG_INIT = UINT64_C(0), ///< Initial value for menvcfg - SENVCFG_INIT = UINT64_C(0), ///< Initial value for senvcfg - UARCH_HALT_FLAG_INIT = UINT64_C(0), ///< Initial value for microarchitecture halt flag - UARCH_X_INIT = UINT64_C(0), ///< Initial value for microarchitecture general purpose register x - UARCH_PC_INIT = EXPAND_UINT64_C(PMA_UARCH_RAM_START_DEF), ///< Initial value for microarchitecture pc - UARCH_CYCLE_INIT = UINT64_C(0), ///< Initial value for microarchitecture cycle - MHARTID_INIT = UINT64_C(0), ///< Initial mhartid - FDTADDR_INIT = PMA_DTB_START, ///< Initial FDT address + MISA_EXT_C_MASK, ///< Initial value for misa + MIE_INIT = UINT64_C(0), ///< Initial value for mie + MIP_INIT = UINT64_C(0), ///< Initial value for mip + MEDELEG_INIT = UINT64_C(0), ///< Initial value for medeleg + MIDELEG_INIT = UINT64_C(0), ///< Initial value for mideleg + MCOUNTEREN_INIT = UINT64_C(0), ///< Initial value for mcounteren + STVEC_INIT = UINT64_C(0), ///< Initial value for stvec + SSCRATCH_INIT = UINT64_C(0), ///< Initial value for sscratch + SEPC_INIT = UINT64_C(0), ///< Initial value for sepc + SCAUSE_INIT = UINT64_C(0), ///< Initial value for scause + STVAL_INIT = UINT64_C(0), ///< Initial value for stval + SATP_INIT = UINT64_C(0), ///< Initial value for satp + SCOUNTEREN_INIT = UINT64_C(0), ///< Initial value for scounteren + ILRSC_INIT = UINT64_C(-1), ///< Initial value for ilrsc + IPRV_INIT = PRV_M, ///< Initial value for iprv + IFLAGS_X_INIT = UINT64_C(0), ///< Initial value for iflags_X + IFLAGS_Y_INIT = UINT64_C(0), ///< Initial value for iflags_Y + IFLAGS_H_INIT = UINT64_C(0), ///< Initial value for iflags_H + IUNREP_INIT = UINT64_C(0), ///< Initial value for iunrep + MTIMECMP_INIT = UINT64_C(0), ///< Initial value for mtimecmp + GIRQPEND_INIT = UINT64_C(0), ///< Initial value for girqpend + GIRQSRVD_INIT = UINT64_C(0), ///< Initial value for girqsrvd + FROMHOST_INIT = UINT64_C(0), ///< Initial value for fromhost + TOHOST_INIT = UINT64_C(0), ///< Initial value for tohost + MENVCFG_INIT = UINT64_C(0), ///< Initial value for menvcfg + SENVCFG_INIT = UINT64_C(0), ///< Initial value for senvcfg + UARCH_HALT_FLAG_INIT = UINT64_C(0), ///< Initial value for microarchitecture halt flag + UARCH_X_INIT = UINT64_C(0), ///< Initial value for microarchitecture general purpose register x + UARCH_PC_INIT = PMA_UARCH_RAM_START, ///< Initial value for microarchitecture pc + UARCH_CYCLE_INIT = UINT64_C(0), ///< Initial value for microarchitecture cycle + MHARTID_INIT = UINT64_C(0), ///< Initial mhartid + FDTADDR_INIT = PMA_DTB_START, ///< Initial FDT address // Registers REG_X0 = UINT64_C(0), //< zero - hardwired zero diff --git a/src/shadow-peek.h b/src/shadow-peek.h index 9275edfda..8d094bc80 100644 --- a/src/shadow-peek.h +++ b/src/shadow-peek.h @@ -18,35 +18,34 @@ #define SHADOW_PEEK_H #include -#include - -#include "machine-reg.h" -#include "machine.h" -#include "pma.h" namespace cartesi { +// Forward declarations +class machine; +class address_range; + /// \file /// \brief Peeks part of a shadow template -static bool shadow_peek(READ_REG_F read_reg, const pma_entry &pma, const machine &m, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char *scratch) { +static bool shadow_peek(READ_REG_F read_reg, const address_range &ar, const machine &m, uint64_t offset, + uint64_t length, const unsigned char **data, unsigned char *scratch) { // Must be inside range - if (!pma.contains(pma.get_start() + offset, length)) { + if (!ar.contains_relative(offset, length)) { *data = nullptr; return false; } // Initial, potentially partial register read const uint64_t offset_aligned = offset & ~(sizeof(uint64_t) - 1); if (offset > offset_aligned) { - const auto val = read_reg(m, pma.get_start() + offset_aligned); + const auto val = read_reg(m, ar.get_start() + offset_aligned); const auto partial = offset - offset_aligned; memcpy(scratch, &val, partial); length -= partial; offset += partial; } // Now we are aligned, do all complete registers - const uint64_t paddr_start = pma.get_start() + offset; + const uint64_t paddr_start = ar.get_start() + offset; const uint64_t length_aligned = length & ~(sizeof(uint64_t) - 1); const uint64_t paddr_end = paddr_start + length_aligned; for (uint64_t paddr = paddr_start; paddr < paddr_end; paddr += sizeof(uint64_t)) { diff --git a/src/shadow-pmas-factory.cpp b/src/shadow-pmas-address-range.h similarity index 63% rename from src/shadow-pmas-factory.cpp rename to src/shadow-pmas-address-range.h index 251660213..fd380a342 100644 --- a/src/shadow-pmas-factory.cpp +++ b/src/shadow-pmas-address-range.h @@ -14,23 +14,36 @@ // with this program (see COPYING). If not, see . // -#include "shadow-pmas-factory.h" +#ifndef SHADOW_PMAS_ADDRESS_RANGE_H +#define SHADOW_PMAS_ADDRESS_RANGE_H #include +#include +#include "memory-address-range.h" #include "pma-constants.h" -#include "pma.h" +#include "shadow-pmas.h" + +/// \file +/// \brief Shadow device. namespace cartesi { -pma_entry make_shadow_pmas_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = true, +static inline auto make_shadow_pmas_address_range(uint64_t start, uint64_t length) { + static constexpr pma_flags shadow_pmas_flags{ + .M = true, + .IO = false, + .E = false, + .R = true, .W = false, .X = false, .IR = false, .IW = false, - .DID = PMA_ISTART_DID::shadow_pmas}; - return make_callocd_memory_pma_entry("shadow PMAs", start, length).set_flags(f); + .DID = PMA_ISTART_DID::shadow_pmas, + }; + return make_callocd_memory_address_range("shadow PMAs", start, length, shadow_pmas_flags); } } // namespace cartesi + +#endif diff --git a/src/shadow-pmas-factory.h b/src/shadow-pmas-factory.h deleted file mode 100644 index f911a8ec6..000000000 --- a/src/shadow-pmas-factory.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef SHADOW_PMAS_FACTORY_H -#define SHADOW_PMAS_FACTORY_H - -#include - -/// \file -/// \brief Shadow device. - -#include "pma.h" -#include "shadow-pmas.h" - -namespace cartesi { - -pma_entry make_shadow_pmas_pma_entry(uint64_t start, uint64_t length); - -template -void shadow_pmas_init(const PMAS &pmas, shadow_pmas_state *shadow) { - static_assert(sizeof(shadow_pmas_state) == PMA_MAX * 2 * sizeof(uint64_t), "inconsistent shadow PMAs length"); - static_assert(PMA_SHADOW_PMAS_LENGTH >= sizeof(shadow_pmas_state), "shadow PMAs not long enough"); - if (pmas.size() > PMA_MAX) { - throw std::invalid_argument{"more PMAs than shadow PMAs can hold"}; - } - unsigned index = 0; - for (const auto &pma : pmas) { - (*shadow)[index].istart = pma.get_istart(); - (*shadow)[index].ilength = pma.get_ilength(); - ++index; - } -} - -} // namespace cartesi - -#endif diff --git a/src/shadow-pmas.h b/src/shadow-pmas.h index 95f5837e7..6e7ea2fca 100644 --- a/src/shadow-pmas.h +++ b/src/shadow-pmas.h @@ -18,12 +18,10 @@ #define SHADOW_PMAS_H #include -#include #include #include "compiler-defines.h" #include "pma-constants.h" -#include "pma-driver.h" /// \file /// \brief Shadow device. diff --git a/src/shadow-state-factory.cpp b/src/shadow-state-address-range.cpp similarity index 63% rename from src/shadow-state-factory.cpp rename to src/shadow-state-address-range.cpp index 959e745e3..d2944946c 100644 --- a/src/shadow-state-factory.cpp +++ b/src/shadow-state-address-range.cpp @@ -14,26 +14,22 @@ // with this program (see COPYING). If not, see . // -#include "shadow-state-factory.h" +#include "shadow-state-address-range.h" #include #include "machine-reg.h" #include "machine.h" -#include "pma-constants.h" -#include "pma.h" #include "shadow-peek.h" -#include "shadow-state.h" namespace cartesi { -/// \brief Shadow device peek callback. See ::pma_peek. -static bool shadow_state_peek(const pma_entry &pma, const machine &m, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char *scratch) { +bool shadow_state_address_range::do_peek(const machine &m, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char *scratch) const noexcept { // If past useful range if (offset >= sizeof(shadow_state)) { *data = nullptr; - return length <= pma.get_length() && offset <= pma.get_length() - length; + return contains_relative(offset, length); } // Otherwise, copy and return register data return shadow_peek( @@ -45,17 +41,7 @@ static bool shadow_state_peek(const pma_entry &pma, const machine &m, uint64_t o } return val; }, - pma, m, offset, length, data, scratch); -} - -pma_entry make_shadow_state_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = false, - .W = false, - .X = false, - .IR = false, - .IW = false, - .DID = PMA_ISTART_DID::shadow_state}; - return make_device_pma_entry("shadow state", start, length, shadow_state_peek, &shadow_state_driver).set_flags(f); + *this, m, offset, length, data, scratch); } } // namespace cartesi diff --git a/src/shadow-state-address-range.h b/src/shadow-state-address-range.h new file mode 100644 index 000000000..59b350601 --- /dev/null +++ b/src/shadow-state-address-range.h @@ -0,0 +1,72 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef SHADOW_STATE_ADDRESS_RANGE_H +#define SHADOW_STATE_ADDRESS_RANGE_H + +#include +#include + +#include "address-range.h" +#include "shadow-state.h" + +/// \file +/// \brief Shadow state address range. + +namespace cartesi { + +class shadow_state_address_range final : public address_range { + + static constexpr pma_flags m_shadow_state_flags{ + .M = false, + .IO = true, + .E = false, + .R = false, + .W = false, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::shadow_state, + }; + +public: + template + shadow_state_address_range(uint64_t start, uint64_t length, ABRT abrt) : + address_range("shadow state", start, length, m_shadow_state_flags, abrt) { + ; + } + + shadow_state_address_range(const shadow_state_address_range &) = default; + shadow_state_address_range &operator=(const shadow_state_address_range &) = default; + shadow_state_address_range(shadow_state_address_range &&) noexcept = default; + shadow_state_address_range &operator=(shadow_state_address_range &&) noexcept = default; + ~shadow_state_address_range() override = default; + +private: +#ifndef MICROARCHITECTURE + bool do_peek(const machine &m, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char *scratch) const noexcept override; +#endif +}; + +template +static inline shadow_state_address_range make_shadow_state_address_range(uint64_t start, uint64_t length, ABRT abrt) { + return shadow_state_address_range{start, length, abrt}; +} + +} // namespace cartesi + +#endif diff --git a/src/shadow-state-factory.h b/src/shadow-state-factory.h deleted file mode 100644 index 6fef5f66f..000000000 --- a/src/shadow-state-factory.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef SHADOW_STATE_FACTORY_H -#define SHADOW_STATE_FACTORY_H - -#include - -/// \file -/// \brief Shadow device. - -#include "pma.h" - -namespace cartesi { - -/// \brief Creates a PMA entry for the shadow device -/// \param start Start address for memory range. -/// \param length Length of memory range. -/// \returns Corresponding PMA entry -pma_entry make_shadow_state_pma_entry(uint64_t start, uint64_t length); - -} // namespace cartesi - -#endif diff --git a/src/shadow-state.cpp b/src/shadow-state.cpp deleted file mode 100644 index 6cc463e29..000000000 --- a/src/shadow-state.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "shadow-state.h" - -#include "pma-driver.h" - -namespace cartesi { - -const pma_driver shadow_state_driver = {.name = "SHADOW STATE", .read = device_read_error, .write = device_write_error}; - -} // namespace cartesi diff --git a/src/shadow-state.h b/src/shadow-state.h index 4efbc3b00..e47c4de7d 100644 --- a/src/shadow-state.h +++ b/src/shadow-state.h @@ -17,17 +17,16 @@ #ifndef SHADOW_STATE_H #define SHADOW_STATE_H -#include #include #include +#include "address-range.h" #include "compiler-defines.h" #include "pma-constants.h" -#include "pma-driver.h" #include "riscv-constants.h" /// \file -/// \brief Shadow device. +/// \brief Shadow state definitions. namespace cartesi { @@ -194,6 +193,7 @@ static constexpr shadow_state_what shadow_state_get_what(uint64_t paddr) { (paddr & (sizeof(uint64_t) - 1)) != 0) { return shadow_state_what::unknown_; } + //NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(paddr); } @@ -427,9 +427,6 @@ static constexpr const char *shadow_state_get_what_name(shadow_state_what what) return "state.unknown_"; } -/// \brief Global instance of the processor shadow device driver. -extern const pma_driver shadow_state_driver; - } // namespace cartesi #endif diff --git a/src/shadow-tlb-factory.cpp b/src/shadow-tlb-address-range.cpp similarity index 64% rename from src/shadow-tlb-factory.cpp rename to src/shadow-tlb-address-range.cpp index e3cefa319..d9f52fc96 100644 --- a/src/shadow-tlb-factory.cpp +++ b/src/shadow-tlb-address-range.cpp @@ -14,26 +14,21 @@ // with this program (see COPYING). If not, see . // -#include "shadow-tlb-factory.h" +#include "shadow-tlb-address-range.h" #include #include "machine.h" -#include "pma-constants.h" -#include "pma.h" #include "shadow-peek.h" -#include "shadow-tlb.h" -#include "tlb.h" namespace cartesi { -/// \brief TLB device peek callback. See ::pma_peek. -static bool shadow_tlb_peek(const pma_entry &pma, const machine &m, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char *scratch) { +bool shadow_tlb_address_range::do_peek(const machine &m, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char *scratch) const noexcept { // If past useful range if (offset >= sizeof(shadow_tlb_state)) { *data = nullptr; - return length <= pma.get_length() && offset <= pma.get_length() - length; + return contains_relative(offset, length); } // Otherwise, copy and return register data return shadow_peek( @@ -47,17 +42,7 @@ static bool shadow_tlb_peek(const pma_entry &pma, const machine &m, uint64_t off } return val; }, - pma, m, offset, length, data, scratch); -} - -pma_entry make_shadow_tlb_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = false, - .W = false, - .X = false, - .IR = false, - .IW = false, - .DID = PMA_ISTART_DID::shadow_TLB}; - return make_device_pma_entry("shadow TLB", start, length, shadow_tlb_peek, &shadow_tlb_driver).set_flags(f); + *this, m, offset, length, data, scratch); } } // namespace cartesi diff --git a/src/shadow-tlb-address-range.h b/src/shadow-tlb-address-range.h new file mode 100644 index 000000000..1956ba99b --- /dev/null +++ b/src/shadow-tlb-address-range.h @@ -0,0 +1,72 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef SHADOW_TLB_ADDRESS_RANGE_H +#define SHADOW_TLB_ADDRESS_RANGE_H + +#include +#include + +#include "address-range.h" +#include "shadow-state.h" + +/// \file +/// \brief Shadow state address range. + +namespace cartesi { + +class shadow_tlb_address_range final : public address_range { + + static constexpr pma_flags m_shadow_tlb_flags{ + .M = false, + .IO = true, + .E = false, + .R = false, + .W = false, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::shadow_TLB, + }; + +public: + template + shadow_tlb_address_range(uint64_t start, uint64_t length, ABRT abrt) : + address_range("shadow TLB", start, length, m_shadow_tlb_flags, abrt) { + ; + } + + shadow_tlb_address_range(const shadow_tlb_address_range &) = default; + shadow_tlb_address_range &operator=(const shadow_tlb_address_range &) = default; + shadow_tlb_address_range(shadow_tlb_address_range &&) noexcept = default; + shadow_tlb_address_range &operator=(shadow_tlb_address_range &&) noexcept = default; + ~shadow_tlb_address_range() override = default; + +private: +#ifndef MICROARCHITECTURE + bool do_peek(const machine &m, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char *scratch) const noexcept override; +#endif +}; + +template +static inline shadow_tlb_address_range make_shadow_tlb_address_range(uint64_t start, uint64_t length, ABRT abrt) { + return shadow_tlb_address_range{start, length, abrt}; +} + +} // namespace cartesi + +#endif diff --git a/src/shadow-tlb-factory.h b/src/shadow-tlb-factory.h index 6c699a9e9..b372d7a67 100644 --- a/src/shadow-tlb-factory.h +++ b/src/shadow-tlb-factory.h @@ -22,8 +22,6 @@ #include -#include "pma.h" - namespace cartesi { /// \brief Creates a PMA entry for the TLB device diff --git a/src/shadow-tlb.cpp b/src/shadow-tlb.cpp deleted file mode 100644 index f883c9287..000000000 --- a/src/shadow-tlb.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "shadow-tlb.h" - -#include "pma-driver.h" - -namespace cartesi { - -const pma_driver shadow_tlb_driver = {.name = "SHADOW TLB", .read = device_read_error, .write = device_write_error}; - -} // namespace cartesi diff --git a/src/shadow-tlb.h b/src/shadow-tlb.h index 508682e63..c7b6df025 100644 --- a/src/shadow-tlb.h +++ b/src/shadow-tlb.h @@ -26,7 +26,6 @@ #include #include "compiler-defines.h" -#include "pma-driver.h" #include "tlb.h" namespace cartesi { @@ -61,8 +60,6 @@ using shadow_tlb_state = std::array; // one set f static_assert(PMA_SHADOW_TLB_LENGTH >= sizeof(shadow_tlb_state), "TLB state must fit in TLB shadow"); -extern const pma_driver shadow_tlb_driver; - /// \brief List of field types enum class shadow_tlb_what : uint64_t { vaddr_page = offsetof(shadow_tlb_slot, vaddr_page), diff --git a/src/shadow-uarch-state-factory.cpp b/src/shadow-uarch-state-address-range.cpp similarity index 66% rename from src/shadow-uarch-state-factory.cpp rename to src/shadow-uarch-state-address-range.cpp index bc5284c13..b4d19b54c 100644 --- a/src/shadow-uarch-state-factory.cpp +++ b/src/shadow-uarch-state-address-range.cpp @@ -14,26 +14,25 @@ // with this program (see COPYING). If not, see . // -#include "shadow-uarch-state-factory.h" +#include "shadow-uarch-state-address-range.h" #include #include "machine-reg.h" #include "machine.h" #include "pma-constants.h" -#include "pma.h" #include "shadow-peek.h" #include "shadow-uarch-state.h" namespace cartesi { /// \brief Shadow uarch state device peek callback. See ::pma_peek. -static bool shadow_uarch_state_peek(const pma_entry &pma, const machine &m, uint64_t offset, uint64_t length, - const unsigned char **data, unsigned char *scratch) { +bool shadow_uarch_state_address_range::do_peek(const machine &m, uint64_t offset, uint64_t length, + const unsigned char **data, unsigned char *scratch) const noexcept { // If past useful range if (offset >= sizeof(shadow_uarch_state)) { *data = nullptr; - return length <= pma.get_length() && offset <= pma.get_length() - length; + return contains_relative(offset, length); } // Otherwise, copy and return register data return shadow_peek( @@ -45,19 +44,7 @@ static bool shadow_uarch_state_peek(const pma_entry &pma, const machine &m, uint } return val; }, - pma, m, offset, length, data, scratch); -} - -pma_entry make_shadow_uarch_state_pma_entry(uint64_t start, uint64_t length) { - const pma_entry::flags f{.R = false, - .W = false, - .X = false, - .IR = false, - .IW = false, - .DID = PMA_ISTART_DID::shadow_uarch}; - return make_device_pma_entry("shadow uarch state", start, length, shadow_uarch_state_peek, - &shadow_uarch_state_driver) - .set_flags(f); + *this, m, offset, length, data, scratch); } } // namespace cartesi diff --git a/src/shadow-uarch-state-address-range.h b/src/shadow-uarch-state-address-range.h new file mode 100644 index 000000000..f9b286111 --- /dev/null +++ b/src/shadow-uarch-state-address-range.h @@ -0,0 +1,74 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef SHADOW_UARCH_STATE_ADDRESS_RANGE_H +#define SHADOW_UARCH_STATE_ADDRESS_RANGE_H + +#include +#include + +#include "address-range.h" +#include "shadow-uarch-state.h" + +/// \file +/// \brief Shadow uarch state address range. + +namespace cartesi { + +class shadow_uarch_state_address_range final : public address_range { + + static constexpr pma_flags m_shadow_uarch_state_flags{ + .M = false, + .IO = true, + .E = false, + .R = false, + .W = false, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::shadow_uarch_state, + }; + +public: + template + shadow_uarch_state_address_range(uint64_t start, uint64_t length, ABRT abrt) : + address_range("shadow uarch state", start, length, m_shadow_uarch_state_flags, abrt) { + ; + } + + shadow_uarch_state_address_range(const shadow_uarch_state_address_range &) = delete; + shadow_uarch_state_address_range &operator=(const shadow_uarch_state_address_range &) = delete; + shadow_uarch_state_address_range &operator=(shadow_uarch_state_address_range &&) noexcept = delete; + + shadow_uarch_state_address_range(shadow_uarch_state_address_range &&) noexcept = default; + ~shadow_uarch_state_address_range() override = default; + +private: +#ifndef MICROARCHITECTURE + bool do_peek(const machine &m, uint64_t offset, uint64_t length, const unsigned char **data, + unsigned char *scratch) const noexcept override; +#endif +}; + +template +static inline shadow_uarch_state_address_range make_shadow_uarch_state_address_range(uint64_t start, uint64_t length, + ABRT abrt) { + return shadow_uarch_state_address_range{start, length, abrt}; +} + +} // namespace cartesi + +#endif diff --git a/src/shadow-uarch-state-factory.h b/src/shadow-uarch-state-factory.h deleted file mode 100644 index 6c8e450b4..000000000 --- a/src/shadow-uarch-state-factory.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef SHADOW_UARCH_STATE_FACTORY_H -#define SHADOW_UARCH_STATE_FACTORY_H - -#include - -/// \file -/// \brief Shadow uarch state device. - -#include "pma.h" - -namespace cartesi { - -/// \brief Creates a PMA entry for the shadow uarch state device -/// \param start Start address for memory range. -/// \param length Length of memory range. -/// \returns Corresponding PMA entry -pma_entry make_shadow_uarch_state_pma_entry(uint64_t start, uint64_t length); - -} // namespace cartesi - -#endif diff --git a/src/shadow-uarch-state.h b/src/shadow-uarch-state.h index d161dcd27..85d24c0bb 100644 --- a/src/shadow-uarch-state.h +++ b/src/shadow-uarch-state.h @@ -17,12 +17,10 @@ #ifndef SHADOW_UARCH_STATE_H #define SHADOW_UARCH_STATE_H -#include #include #include #include "compiler-defines.h" -#include "pma-driver.h" #include "riscv-constants.h" /// \file @@ -82,6 +80,7 @@ static constexpr shadow_uarch_state_what shadow_uarch_state_get_what(uint64_t pa (paddr & (sizeof(uint64_t) - 1)) != 0) { return shadow_uarch_state_what::unknown_; } + //NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(paddr); } @@ -173,9 +172,6 @@ static constexpr const char *shadow_uarch_state_get_what_name(shadow_uarch_state return "uarch.unknown_"; } -/// \brief Global instance of theprocessor shadow uarch state device driver. -extern const pma_driver shadow_uarch_state_driver; - } // namespace cartesi #endif diff --git a/src/state-access.h b/src/state-access.h index 0bae8585b..86c4c883f 100644 --- a/src/state-access.h +++ b/src/state-access.h @@ -37,7 +37,6 @@ #include "machine.h" #include "os.h" #include "pma-constants.h" -#include "pma.h" #include "riscv-constants.h" #include "shadow-tlb.h" #include "strict-aliasing.h" @@ -46,11 +45,6 @@ namespace cartesi { class state_access; -// Type trait that should return the pma_entry type for a state access class -template <> -struct i_state_access_pma_entry { - using type = pma_entry; -}; // Type trait that should return the fast_addr type for a state access class template <> struct i_state_access_fast_addr { @@ -433,10 +427,8 @@ class state_access : } } - pma_entry &do_read_pma_entry(uint64_t index) const { - assert(index < PMA_MAX); - // NOLINTNEXTLINE(bugprone-narrowing-conversions) - return m_m.get_state().pmas[static_cast(index)]; + address_range &do_read_pma(uint64_t index) const { + return m_m.read_pma(index); } void do_write_memory_with_padding(uint64_t paddr, const unsigned char *data, uint64_t data_length, diff --git a/src/tlb.h b/src/tlb.h index 2baa96918..0383e7da1 100644 --- a/src/tlb.h +++ b/src/tlb.h @@ -27,7 +27,6 @@ #include "host-addr.h" #include "pma-constants.h" -#include "pma-driver.h" #include "riscv-constants.h" namespace cartesi { diff --git a/src/translate-virtual-address.h b/src/translate-virtual-address.h index 5a740a29a..3d8b536f4 100644 --- a/src/translate-virtual-address.h +++ b/src/translate-virtual-address.h @@ -60,8 +60,8 @@ namespace cartesi { template static inline bool write_ram_uint64(STATE_ACCESS a, uint64_t paddr, uint64_t val) { uint64_t pma_index = 0; - auto &pma = find_pma_entry(a, paddr, pma_index); - if (unlikely(!pma.get_istart_M() || !pma.get_istart_W())) { + auto &ar = find_pma(a, paddr, pma_index); + if (unlikely(!ar.is_memory() || !ar.is_writeable())) { return false; } const auto faddr = a.get_faddr(paddr, pma_index); @@ -81,8 +81,8 @@ static inline bool write_ram_uint64(STATE_ACCESS a, uint64_t paddr, uint64_t val template static inline bool read_ram_uint64(STATE_ACCESS a, uint64_t paddr, uint64_t *pval) { uint64_t pma_index = 0; - auto &pma = find_pma_entry(a, paddr, pma_index); - if (unlikely(!pma.get_istart_M() || !pma.get_istart_R())) { + auto &ar = find_pma(a, paddr, pma_index); + if (unlikely(!ar.is_memory() || !ar.is_readable())) { return false; } const auto faddr = a.get_faddr(paddr, pma_index); diff --git a/src/uarch-record-state-access.h b/src/uarch-record-state-access.h index 0a32243fd..498c4681f 100644 --- a/src/uarch-record-state-access.h +++ b/src/uarch-record-state-access.h @@ -19,7 +19,6 @@ /// \file /// \brief State access implementation that record and logs all accesses -#include #include #include #include @@ -35,7 +34,6 @@ #include "machine-merkle-tree.h" #include "machine.h" #include "meta.h" -#include "pma.h" #include "riscv-constants.h" #include "shadow-tlb.h" #include "shadow-uarch-state.h" diff --git a/src/uarch-replay-state-access.h b/src/uarch-replay-state-access.h index a9ef1a92c..c4fe6b0b5 100644 --- a/src/uarch-replay-state-access.h +++ b/src/uarch-replay-state-access.h @@ -20,7 +20,6 @@ /// \file /// \brief State access implementation that replays recorded state accesses -#include #include #include #include diff --git a/src/uarch-state-access.h b/src/uarch-state-access.h index ef52ba558..fc1b7ef45 100644 --- a/src/uarch-state-access.h +++ b/src/uarch-state-access.h @@ -46,8 +46,8 @@ class uarch_state_access : /// \param m Reference to machine state. explicit uarch_state_access(machine &m) : m_m(m) { const auto &uram = m_m.get_uarch_state().ram; - const auto haddr = cast_ptr_to_host_addr(uram.get_memory_noexcept().get_host_memory()); - const auto paddr = uram.get_start(); + const auto haddr = cast_ptr_to_host_addr(uram->get_host_memory()); + const auto paddr = uram->get_start(); // initialize translation cache from paddr in uarch RAM to host address m_uram_ph_offset = haddr - paddr; } @@ -92,30 +92,11 @@ class uarch_state_access : } uint64_t do_read_word(uint64_t paddr) const { - if ((paddr & (sizeof(uint64_t) - 1)) != 0) { - throw std::runtime_error("misaligned read from uarch"); - } - // If the word is in UARCH_RAM, read it directly - // ??D This is an optimization that makes 15% difference in run_uarch tests - if (m_m.get_uarch_state().ram.contains(paddr, sizeof(uint64_t))) { - auto haddr = m_uram_ph_offset + paddr; - return aliased_aligned_read(haddr); - } // Forward to machine return m_m.read_word(paddr); } void do_write_word(uint64_t paddr, uint64_t val) const { - if ((paddr & (sizeof(uint64_t) - 1)) != 0) { - throw std::runtime_error("misaligned read from uarch"); - } - // If the word is in UARCH_RAM, write it directly - // ??D This is an optimization that makes 15% difference in run_uarch tests - if (m_m.get_uarch_state().ram.contains(paddr, sizeof(uint64_t))) { - auto haddr = m_uram_ph_offset + paddr; - aliased_aligned_write(haddr, val); - return; - } // Forward to machine m_m.write_word(paddr, val); } diff --git a/src/uarch-state.h b/src/uarch-state.h index f028c5027..51c0db901 100644 --- a/src/uarch-state.h +++ b/src/uarch-state.h @@ -22,8 +22,9 @@ #include #include +#include -#include "pma.h" +#include "memory-address-range.h" #include "riscv-constants.h" namespace cartesi { @@ -42,9 +43,8 @@ struct uarch_state { std::array x{}; ///< Register file. uint64_t cycle{}; ///< Cycles counter uint64_t halt_flag{}; - pma_entry shadow_state; ///< Shadow uarch state - pma_entry ram; ///< Memory range for micro RAM - pma_entry empty_pma; ///< Empty range fallback + address_range *shadow_state{}; ///< Shadow uarch state + memory_address_range *ram{}; ///< Memory range for uarch RAM }; } // namespace cartesi diff --git a/src/unique-c-ptr.h b/src/unique-c-ptr.h index d2ee9368f..965ab7523 100644 --- a/src/unique-c-ptr.h +++ b/src/unique-c-ptr.h @@ -17,7 +17,6 @@ #ifndef UNIQUE_C_PTR_H #define UNIQUE_C_PTR_H -#include "os.h" #include #include #include @@ -27,6 +26,8 @@ #include #include +#include "os.h" + namespace cartesi { namespace detail { @@ -102,6 +103,13 @@ static inline auto make_unique_mmap(const char *pathname, size_t nmemb, bool sha return unique_mmap_ptr(ptr, detail::mmap_deleter{size}); } +template +static auto inline make_moved_unique(T &&t) + requires std::is_rvalue_reference_v +{ + return std::make_unique(std::forward(t)); +} + } // namespace cartesi #endif diff --git a/src/virtio-device.cpp b/src/virtio-address-range.cpp similarity index 91% rename from src/virtio-device.cpp rename to src/virtio-address-range.cpp index d3a5d90ca..67335103c 100644 --- a/src/virtio-device.cpp +++ b/src/virtio-address-range.cpp @@ -19,17 +19,17 @@ // #define DEBUG_VIRTIO_MMIO // #define DEBUG_VIRTIO_ERRORS -#include "virtio-device.h" +#include "virtio-address-range.h" #include #include #include +#include #include "i-device-state-access.h" #include "interpret.h" #include "os.h" -#include "plic.h" -#include "pma-driver.h" +#include "plic-address-range.h" #include "strict-aliasing.h" namespace cartesi { @@ -303,14 +303,16 @@ bool virtq::consume_desc(i_device_state_access *a, uint16_t desc_idx, uint32_t w return true; } -virtio_device::virtio_device(uint32_t virtio_idx, uint32_t device_id, uint64_t device_features, - uint32_t config_space_size) : +virtio_address_range::virtio_address_range(const char *description, uint64_t start, uint64_t length, + uint32_t virtio_idx, uint32_t device_id, uint64_t device_features, uint32_t config_space_size) : + pristine_address_range(description, start, length, m_virtio_flags, + [](const char *err) { throw std::invalid_argument{err}; }), virtio_idx(virtio_idx), device_id(device_id), device_features(device_features | VIRTIO_F_VERSION_1), config_space_size(config_space_size) {} -void virtio_device::reset(i_device_state_access *a) { +void virtio_address_range::reset(i_device_state_access *a) { on_device_reset(); // The device MUST initialize device status to 0 upon reset. device_status = 0; @@ -336,7 +338,7 @@ void virtio_device::reset(i_device_state_access *a) { reset_irq(a, VIRTIO_INT_STATUS_USED_BUFFER | VIRTIO_INT_STATUS_CONFIG_CHANGE); } -void virtio_device::set_irq(i_device_state_access *a, uint32_t add_int_status) { +void virtio_address_range::set_irq(i_device_state_access *a, uint32_t add_int_status) { int_status |= add_int_status; #ifdef DEBUG_VIRTIO std::ignore = fprintf(stderr, "virtio[%d]: set_irq int_status=%d\n", virtio_idx, int_status); @@ -347,7 +349,7 @@ void virtio_device::set_irq(i_device_state_access *a, uint32_t add_int_status) { } } -void virtio_device::reset_irq(i_device_state_access *a, uint32_t rem_int_status) { +void virtio_address_range::reset_irq(i_device_state_access *a, uint32_t rem_int_status) { int_status &= ~rem_int_status; #ifdef DEBUG_VIRTIO std::ignore = fprintf(stderr, "virtio[%d]: reset_irq int_status=%d\n", virtio_idx, int_status); @@ -361,7 +363,7 @@ void virtio_device::reset_irq(i_device_state_access *a, uint32_t rem_int_status) } } -void virtio_device::notify_queue_used(i_device_state_access *a) { +void virtio_address_range::notify_queue_used(i_device_state_access *a) { #if defined(DEBUG_VIRTIO) std::ignore = fprintf(stderr, "virtio[%d]: notify_queue_used\n", virtio_idx); #endif @@ -371,7 +373,7 @@ void virtio_device::notify_queue_used(i_device_state_access *a) { } } -void virtio_device::notify_device_needs_reset(i_device_state_access *a) { +void virtio_address_range::notify_device_needs_reset(i_device_state_access *a) { // A fatal failure happened while processing a queue. #if defined(DEBUG_VIRTIO) || defined(DEBUG_VIRTIO_ERRORS) std::ignore = fprintf(stderr, "virtio[%d]: notify_device_needs_reset\n", virtio_idx); @@ -383,7 +385,7 @@ void virtio_device::notify_device_needs_reset(i_device_state_access *a) { notify_config_change(a); } -void virtio_device::notify_config_change(i_device_state_access *a) { +void virtio_address_range::notify_config_change(i_device_state_access *a) { // Whenever device changes the configuration, we MUST changed its config generation, // so the driver knows that it should re-read its configuration. config_generation++; @@ -397,7 +399,7 @@ void virtio_device::notify_config_change(i_device_state_access *a) { } } -bool virtio_device::prepare_queue_write(i_device_state_access *a, uint32_t queue_idx, uint16_t *pdesc_idx, +bool virtio_address_range::prepare_queue_write(i_device_state_access *a, uint32_t queue_idx, uint16_t *pdesc_idx, uint32_t *pwrite_avail_len) const { *pdesc_idx = 0; *pwrite_avail_len = 0; @@ -439,8 +441,8 @@ bool virtio_device::prepare_queue_write(i_device_state_access *a, uint32_t queue return true; } -bool virtio_device::consume_queue(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, uint32_t written_len, - uint16_t used_flags) { +bool virtio_address_range::consume_queue(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t written_len, uint16_t used_flags) { // A device MUST NOT consume buffers or send any used buffer notifications to the driver before DRIVER_OK. assert(driver_ok); assert(queue_idx < VIRTIO_QUEUE_COUNT); @@ -454,7 +456,7 @@ bool virtio_device::consume_queue(i_device_state_access *a, uint32_t queue_idx, return vq.consume_desc(a, desc_idx, written_len, used_flags); } -void virtio_device::on_device_queue_notify(i_device_state_access *a, uint32_t queue_idx) { +void virtio_address_range::on_device_queue_notify(i_device_state_access *a, uint32_t queue_idx) { // The device MUST NOT consume buffers or notify the driver before DRIVER_OK if (!driver_ok) { return; @@ -504,13 +506,13 @@ void virtio_device::on_device_queue_notify(i_device_state_access *a, uint32_t qu } } -void virtio_device::prepare_select(select_fd_sets * /*fds*/, uint64_t * /*timeout_us*/) {} +void virtio_address_range::prepare_select(select_fd_sets * /*fds*/, uint64_t * /*timeout_us*/) {} -bool virtio_device::poll_selected(int /*select_ret*/, select_fd_sets * /*fds*/, i_device_state_access * /*da*/) { +bool virtio_address_range::poll_selected(int /*select_ret*/, select_fd_sets * /*fds*/, i_device_state_access * /*da*/) { return false; }; -bool virtio_device::poll_nowait(i_device_state_access *da) { +bool virtio_address_range::poll_nowait(i_device_state_access *da) { uint64_t timeout_us = 0; return os_select_fds( [&](select_fd_sets *fds, uint64_t *timeout_us) -> void { this->prepare_select(fds, timeout_us); }, @@ -518,17 +520,18 @@ bool virtio_device::poll_nowait(i_device_state_access *da) { &timeout_us); } -uint64_t virtio_device::read_shm_base(uint32_t /*shm_sel*/) { +uint64_t virtio_address_range::read_shm_base(uint32_t /*shm_sel*/) const { // Reading from a non-existent region results in a base of 0xffffffffffffffff. return UINT64_C(-1); } -uint64_t virtio_device::read_shm_length(uint32_t /*shm_sel*/) { +uint64_t virtio_address_range::read_shm_length(uint32_t /*shm_sel*/) const { // Reading from a non-existent region results in a length of 0xffffffffffffffff. return UINT64_C(-1); } -bool virtio_device::mmio_read_config(i_device_state_access * /*a*/, uint64_t offset, uint32_t *pval, int log2_size) { +bool virtio_address_range::mmio_read_config(i_device_state_access * /*a*/, uint64_t offset, uint32_t *pval, + int log2_size) const { const int size = 1 << log2_size; // Only accept aligned reads if ((offset & (size - 1)) != 0) { @@ -556,7 +559,7 @@ bool virtio_device::mmio_read_config(i_device_state_access * /*a*/, uint64_t off } } -execute_status virtio_device::mmio_write_config(i_device_state_access * /*a*/, uint64_t offset, uint32_t val, +execute_status virtio_address_range::mmio_write_config(i_device_state_access * /*a*/, uint64_t offset, uint32_t val, int log2_size) { const int size = 1 << log2_size; // Only accept aligned writes @@ -585,7 +588,7 @@ execute_status virtio_device::mmio_write_config(i_device_state_access * /*a*/, u } } -bool virtio_device::mmio_read(i_device_state_access *a, uint64_t offset, uint32_t *pval, int log2_size) { +bool virtio_address_range::mmio_read(i_device_state_access *a, uint64_t offset, uint32_t *pval, int log2_size) const { // If offset is equal or greater than VIRTIO_MMIO_CONFIG, the driver is actually reading a device config if (offset >= VIRTIO_MMIO_CONFIG) { return mmio_read_config(a, offset - VIRTIO_MMIO_CONFIG, pval, log2_size); @@ -662,7 +665,8 @@ bool virtio_device::mmio_read(i_device_state_access *a, uint64_t offset, uint32_ } } -execute_status virtio_device::mmio_write(i_device_state_access *a, uint64_t offset, uint32_t val, int log2_size) { +execute_status virtio_address_range::mmio_write(i_device_state_access *a, uint64_t offset, uint32_t val, + int log2_size) { // If offset is equal or greater than VIRTIO_MMIO_CONFIG, the driver is actually writing a device config if (offset >= VIRTIO_MMIO_CONFIG) { return mmio_write_config(a, offset - VIRTIO_MMIO_CONFIG, val, log2_size); @@ -802,45 +806,41 @@ execute_status virtio_device::mmio_write(i_device_state_access *a, uint64_t offs } } -/// \brief VirtIO device read callback. See ::pma_read. -static bool virtio_read(void *context, i_device_state_access *a, uint64_t offset, uint64_t *pval, int log2_size) { - auto *vdev = static_cast(context); +bool virtio_address_range::do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept { uint32_t val32 = 0; - const bool status = vdev->mmio_read(a, offset, &val32, log2_size); + const bool status = mmio_read(a, offset, &val32, log2_size); if (status) { *pval = val32; } #ifdef DEBUG_VIRTIO_MMIO - std::ignore = fprintf(stderr, "virtio[%d]: mmio_read offset=0x%03lx (%s) value=%d size=%d\n", - vdev->get_virtio_index(), offset, get_virtio_mmio_offset_name(offset), val32, 1 << log2_size); + std::ignore = fprintf(stderr, "virtio[%d]: mmio_read offset=0x%03lx (%s) value=%d size=%d\n", get_virtio_index(), + offset, get_virtio_mmio_offset_name(offset), val32, 1 << log2_size); #endif #if defined(DEBUG_VIRTIO_MMIO) || defined(DEBUG_VIRTIO_ERRORS) if (!status) { - std::ignore = fprintf(stderr, "virtio[%d]: mmio_read FAILED! offset=0x%03lx(%s) size=%d\n", - vdev->get_virtio_index(), offset, get_virtio_mmio_offset_name(offset), 1 << log2_size); + std::ignore = fprintf(stderr, "virtio[%d]: mmio_read FAILED! offset=0x%03lx(%s) size=%d\n", get_virtio_index(), + offset, get_virtio_mmio_offset_name(offset), 1 << log2_size); } #endif return status; } /// \brief VirtIO device read callback. See ::pma_write. -static execute_status virtio_write(void *context, i_device_state_access *a, uint64_t offset, uint64_t val, - int log2_size) { - auto *vdev = static_cast(context); +execute_status virtio_address_range::do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept { #ifdef DEBUG_VIRTIO_MMIO - std::ignore = fprintf(stderr, "virtio[%d]: mmio_write offset=0x%03lx (%s) value=%ld size=%d\n", - vdev->get_virtio_index(), offset, get_virtio_mmio_offset_name(offset), val, 1 << log2_size); + std::ignore = fprintf(stderr, "virtio[%d]: mmio_write offset=0x%03lx (%s) value=%ld size=%d\n", get_virtio_index(), + offset, get_virtio_mmio_offset_name(offset), val, 1 << log2_size); #endif - const execute_status status = vdev->mmio_write(a, offset, val, log2_size); + const execute_status status = mmio_write(a, offset, val, log2_size); #if defined(DEBUG_VIRTIO_MMIO) || defined(DEBUG_VIRTIO_ERRORS) if (status == execute_status::failure) { std::ignore = fprintf(stderr, "virtio[%d]: mmio_write FAILED! offset=0x%03lx (%s) value=%ld size=%d\n", - vdev->get_virtio_index(), offset, get_virtio_mmio_offset_name(offset), val, 1 << log2_size); + get_virtio_index(), offset, get_virtio_mmio_offset_name(offset), val, 1 << log2_size); } #endif return status; } -const pma_driver virtio_driver = {.name = "VirtIO", .read = virtio_read, .write = virtio_write}; - } // namespace cartesi diff --git a/src/virtio-device.h b/src/virtio-address-range.h similarity index 93% rename from src/virtio-device.h rename to src/virtio-address-range.h index d7ab76cee..f3f90bc93 100644 --- a/src/virtio-device.h +++ b/src/virtio-address-range.h @@ -14,17 +14,15 @@ // with this program (see COPYING). If not, see . // -#ifndef VIRTIO_DEVICE_H -#define VIRTIO_DEVICE_H +#ifndef VIRTIO_ADDRESS_RANGE_H +#define VIRTIO_ADDRESS_RANGE_H #include #include #include -#include "i-device-state-access.h" -#include "interpret.h" #include "os.h" -#include "pma-driver.h" +#include "pristine-address-range.h" namespace cartesi { @@ -245,7 +243,7 @@ struct virtq { }; /// \brief VirtIO device common interface -class virtio_device { +class virtio_address_range : public pristine_address_range { uint32_t virtio_idx = 0; ///< VirtIO device index uint32_t int_status = 0; ///< Interrupt status mask (see virtio_status) uint32_t device_id = 0; ///< Device id (see virtio_devices) @@ -259,6 +257,18 @@ class virtio_device { uint32_t config_generation = 0; ///< Configuration generation counter uint32_t config_space_size = 0; ///< Configuration size + static constexpr pma_flags m_virtio_flags{ + .M = false, + .IO = true, + .E = false, + .R = true, + .W = true, + .X = false, + .IR = false, + .IW = false, + .DID = PMA_ISTART_DID::VIRTIO, + }; + protected: // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes) bool driver_ok = false; ///< True when the device was successfully initialized by the driver @@ -270,14 +280,15 @@ class virtio_device { // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes) public: - explicit virtio_device(uint32_t virtio_idx, uint32_t device_id, uint64_t device_features, - uint32_t config_space_size); - virtio_device() = delete; - virtual ~virtio_device() = default; - virtio_device(const virtio_device &other) = delete; - virtio_device(virtio_device &&other) = delete; - virtio_device &operator=(const virtio_device &other) = delete; - virtio_device &operator=(virtio_device &&other) = delete; + explicit virtio_address_range(const char *description, uint64_t start, uint64_t length, uint32_t virtio_idx, + uint32_t device_id, uint64_t device_features, uint32_t config_space_size); + + virtio_address_range(const virtio_address_range &other) = delete; + virtio_address_range &operator=(const virtio_address_range &other) = delete; + virtio_address_range &operator=(virtio_address_range &&other) = delete; + + virtio_address_range(virtio_address_range &&other) = default; + ~virtio_address_range() override = default; /// \brief Reset device to uninitialize state, cleaning all its internal state. /// \details This is only requested by the driver when a fatal failure occurs @@ -360,18 +371,18 @@ class virtio_device { /// \brief Reads device's shared memory base address. /// \returns Guest a valid physical address, or -1 in case shared memory is not supported by the device. - virtual uint64_t read_shm_base(uint32_t shm_sel); + virtual uint64_t read_shm_base(uint32_t shm_sel) const; /// \brief Reads device's shared memory length. /// \returns Length in bytes, or -1 in case shared memory is not supported by the device. - virtual uint64_t read_shm_length(uint32_t shm_sel); + virtual uint64_t read_shm_length(uint32_t shm_sel) const; /// \brief Reads a value from device's configuration space. /// \param offset Offset to be read. /// \param pval Receives the value. /// \param log2_size log2 of size of value to read. /// \returns True if there are no errors, false otherwise. - bool mmio_read_config(i_device_state_access *a, uint64_t offset, uint32_t *pval, int log2_size); + bool mmio_read_config(i_device_state_access *a, uint64_t offset, uint32_t *pval, int log2_size) const; /// \brief Writes a value to device's configuration space. /// \param offset Offset to write to. @@ -385,7 +396,7 @@ class virtio_device { /// \param pval Receives the value. /// \param log2_size log2 of size of value to read. /// \returns True if there are no errors, false otherwise. - bool mmio_read(i_device_state_access *a, uint64_t offset, uint32_t *pval, int log2_size); + bool mmio_read(i_device_state_access *a, uint64_t offset, uint32_t *pval, int log2_size) const; /// \brief Writes a value to the device. /// \param offset Offset to be written. @@ -408,10 +419,13 @@ class virtio_device { uint32_t get_device_id() const { return device_id; } -}; -/// \brief Global VirtIO driver instance -extern const pma_driver virtio_driver; +private: + bool do_read_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t *pval) const noexcept override; + execute_status do_write_device(i_device_state_access *a, uint64_t offset, int log2_size, + uint64_t val) noexcept override; +}; } // namespace cartesi diff --git a/src/virtio-console.cpp b/src/virtio-console-address-range.cpp similarity index 82% rename from src/virtio-console.cpp rename to src/virtio-console-address-range.cpp index 86f7b3c4d..d0b585554 100644 --- a/src/virtio-console.cpp +++ b/src/virtio-console-address-range.cpp @@ -14,7 +14,7 @@ // with this program (see COPYING). If not, see . // -#include "virtio-console.h" +#include "virtio-console-address-range.h" #include #include @@ -22,24 +22,24 @@ #include "i-device-state-access.h" #include "os.h" -#include "virtio-device.h" namespace cartesi { -virtio_console::virtio_console(uint32_t virtio_idx) : - virtio_device(virtio_idx, VIRTIO_DEVICE_CONSOLE, VIRTIO_CONSOLE_F_SIZE, sizeof(virtio_console_config_space)) {} +virtio_console_address_range::virtio_console_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx) : + virtio_address_range("VirtIO Console", start, length, virtio_idx, VIRTIO_DEVICE_CONSOLE, VIRTIO_CONSOLE_F_SIZE, + sizeof(virtio_console_config_space)) {} -void virtio_console::on_device_reset() { +void virtio_console_address_range::on_device_reset() { m_stdin_ready = false; } -void virtio_console::on_device_ok(i_device_state_access *a) { +void virtio_console_address_range::on_device_ok(i_device_state_access *a) { // Upon initialization, we need to notify the initial console size notify_console_size_to_guest(a); } -bool virtio_console::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t read_avail_len, uint32_t /*write_avail_len*/) { +bool virtio_console_address_range::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, + uint16_t desc_idx, uint32_t read_avail_len, uint32_t /*write_avail_len*/) { if (queue_idx == VIRTIO_CONSOLE_RECEIVEQ) { // Guest has a new slot available in the write queue // Do nothing, host stdin characters will be written to the guest in the next poll return false; @@ -52,8 +52,8 @@ bool virtio_console::on_device_queue_available(i_device_state_access *a, uint32_ return false; } -bool virtio_console::write_next_chars_to_host(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t read_avail_len) { +bool virtio_console_address_range::write_next_chars_to_host(i_device_state_access *a, uint32_t queue_idx, + uint16_t desc_idx, uint32_t read_avail_len) { const virtq &vq = queue[queue_idx]; // Read stdout characters from queue buffer in chunks std::array chunk{}; @@ -75,7 +75,7 @@ bool virtio_console::write_next_chars_to_host(i_device_state_access *a, uint32_t return true; } -bool virtio_console::write_next_chars_to_guest(i_device_state_access *a) { +bool virtio_console_address_range::write_next_chars_to_guest(i_device_state_access *a) { if (!driver_ok) { return false; } @@ -115,7 +115,7 @@ bool virtio_console::write_next_chars_to_guest(i_device_state_access *a) { return true; } -bool virtio_console::notify_console_size_to_guest(i_device_state_access *a) { +bool virtio_console_address_range::notify_console_size_to_guest(i_device_state_access *a) { // Get current console size uint16_t cols{}; uint16_t rows{}; @@ -131,7 +131,7 @@ bool virtio_console::notify_console_size_to_guest(i_device_state_access *a) { return true; } -void virtio_console::prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { +void virtio_console_address_range::prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { // Ignore if driver is not initialized if (!driver_ok) { return; @@ -154,8 +154,9 @@ void virtio_console::prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { os_prepare_tty_select(fds); } -bool virtio_console::poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) { +bool virtio_console_address_range::poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) { // Ignore if driver is not initialized or stdin is not ready + if (!driver_ok || !m_stdin_ready) { return false; } diff --git a/src/virtio-console.h b/src/virtio-console-address-range.h similarity index 76% rename from src/virtio-console.h rename to src/virtio-console-address-range.h index 757a9a892..3f68c410c 100644 --- a/src/virtio-console.h +++ b/src/virtio-console-address-range.h @@ -14,14 +14,12 @@ // with this program (see COPYING). If not, see . // -#ifndef VIRTIO_CONSOLE_H -#define VIRTIO_CONSOLE_H +#ifndef VIRTIO_CONSOLE_ADDRESS_RANGE_H +#define VIRTIO_CONSOLE_ADDRESS_RANGE_H #include -#include "i-device-state-access.h" -#include "os.h" -#include "virtio-device.h" +#include "virtio-address-range.h" namespace cartesi { @@ -47,11 +45,18 @@ struct virtio_console_config_space { }; /// \brief VirtIO console device -class virtio_console final : public virtio_device { +class virtio_console_address_range final : public virtio_address_range { bool m_stdin_ready = false; public: - explicit virtio_console(uint32_t virtio_idx); + explicit virtio_console_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx); + + virtio_console_address_range(const virtio_console_address_range &other) = delete; + virtio_console_address_range &operator=(const virtio_console_address_range &other) = delete; + virtio_console_address_range &operator=(virtio_console_address_range &&other) = delete; + + virtio_console_address_range(virtio_console_address_range &&other) = default; + ~virtio_console_address_range() override = default; void on_device_reset() override; void on_device_ok(i_device_state_access *a) override; @@ -72,6 +77,10 @@ class virtio_console final : public virtio_device { } }; +static inline auto make_virtio_console_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx) { + return virtio_console_address_range{start, length, virtio_idx}; +} + } // namespace cartesi #endif diff --git a/src/virtio-factory.cpp b/src/virtio-factory.cpp deleted file mode 100644 index 762033d6c..000000000 --- a/src/virtio-factory.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include "virtio-factory.h" - -#include -#include - -#include "pma-constants.h" -#include "pma-driver.h" -#include "pma.h" - -namespace cartesi { - -pma_entry make_virtio_pma_entry(uint64_t start, uint64_t length, const std::string &description, - const pma_driver *driver, void *context) { - const pma_entry::flags f{.R = true, .W = true, .X = false, .IR = false, .IW = false, .DID = PMA_ISTART_DID::VIRTIO}; - // VirtIO devices are not verifiable yet, - // therefore peek will always fail and cause an runtime error when updating the Merkle tree. - return make_device_pma_entry(description, start, length, pma_peek_error, driver, context).set_flags(f); -} - -} // namespace cartesi diff --git a/src/virtio-factory.h b/src/virtio-factory.h deleted file mode 100644 index 51899096c..000000000 --- a/src/virtio-factory.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef VIRTIO_FACTORY_H -#define VIRTIO_FACTORY_H - -#include -#include - -#include "pma-driver.h" -#include "pma.h" - -namespace cartesi { - -/// \brief Creates a PMA entry for a VirtIO device -/// \param start Start address for memory range. -/// \param length Length of memory range. -/// \returns Corresponding PMA entry -pma_entry make_virtio_pma_entry(uint64_t start, uint64_t length, const std::string &description, - const pma_driver *driver, void *context); - -} // namespace cartesi - -#endif diff --git a/src/virtio-net.cpp b/src/virtio-net-address-range.cpp similarity index 77% rename from src/virtio-net.cpp rename to src/virtio-net-address-range.cpp index f114f7489..653881973 100644 --- a/src/virtio-net.cpp +++ b/src/virtio-net-address-range.cpp @@ -14,7 +14,7 @@ // along with the machine-emulator. If not, see http://www.gnu.org/licenses/. // -#include "virtio-net.h" +#include "virtio-net-address-range.h" #if defined(HAVE_SLIRP) || defined(HAVE_TUNTAP) @@ -24,24 +24,23 @@ #include "i-device-state-access.h" #include "os.h" -#include "virtio-device.h" namespace cartesi { -virtio_net::virtio_net(uint32_t virtio_idx, std::unique_ptr &&carrier) : - virtio_device(virtio_idx, VIRTIO_DEVICE_NETWORK, 0, 0), - m_carrier(std::move(carrier)) {} +virtio_net_address_range::virtio_net_address_range(const char *description, uint64_t start, uint64_t length, + uint32_t virtio_idx) : + virtio_address_range(description, start, length, virtio_idx, VIRTIO_DEVICE_NETWORK, 0, 0) {} -void virtio_net::on_device_reset() { - m_carrier->reset(); +void virtio_net_address_range::on_device_reset() { + net_reset(); } -void virtio_net::on_device_ok(i_device_state_access * /*a*/) { +void virtio_net_address_range::on_device_ok(i_device_state_access * /*a*/) { // Nothing to do. } -bool virtio_net::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t read_avail_len, uint32_t /*write_avail_len*/) { +bool virtio_net_address_range::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, + uint16_t desc_idx, uint32_t read_avail_len, uint32_t /*write_avail_len*/) { if (queue_idx == VIRTIO_NET_RECEIVEQ) { // Guest has a new slot available in the write queue // Write any pending packets from host to guest return poll_nowait(a); @@ -60,19 +59,19 @@ bool virtio_net::on_device_queue_available(i_device_state_access *a, uint32_t qu return false; } -void virtio_net::prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { +void virtio_net_address_range::prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { if (!driver_ok) { return; } - m_carrier->do_prepare_select(fds, timeout_us); + net_prepare_select(fds, timeout_us); } -bool virtio_net::poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) { +bool virtio_net_address_range::poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) { if (!driver_ok) { return false; } // Is there any pending to be read from the host? - if (!m_carrier->do_poll_selected(select_ret, fds)) { + if (!net_poll_selected(select_ret, fds)) { return false; } bool interrupt_requested = false; @@ -84,12 +83,12 @@ bool virtio_net::poll_selected(int select_ret, select_fd_sets *fds, i_device_sta return interrupt_requested; } -bool virtio_net::write_next_packet_to_host(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t read_avail_len) { +bool virtio_net_address_range::write_next_packet_to_host(i_device_state_access *a, uint32_t queue_idx, + uint16_t desc_idx, uint32_t read_avail_len) { virtq &vq = queue[queue_idx]; // Write a single packet to the network interface uint32_t read_len{}; - if (!m_carrier->write_packet_to_host(a, vq, desc_idx, read_avail_len, &read_len)) { + if (!net_write_packet_to_host(a, vq, desc_idx, read_avail_len, &read_len)) { notify_device_needs_reset(a); return false; } @@ -101,7 +100,7 @@ bool virtio_net::write_next_packet_to_host(i_device_state_access *a, uint32_t qu return true; } -bool virtio_net::read_next_packet_from_host(i_device_state_access *a) { +bool virtio_net_address_range::read_next_packet_from_host(i_device_state_access *a) { // Bytes from host must be written to queue 0 constexpr uint32_t queue_idx = VIRTIO_NET_RECEIVEQ; virtq &vq = queue[queue_idx]; @@ -119,7 +118,7 @@ bool virtio_net::read_next_packet_from_host(i_device_state_access *a) { } // Read a single packet from the network interface uint32_t written_len{}; - if (!m_carrier->read_packet_from_host(a, vq, desc_idx, write_avail_len, &written_len)) { + if (!net_read_packet_from_host(a, vq, desc_idx, write_avail_len, &written_len)) { notify_device_needs_reset(a); return false; } diff --git a/src/virtio-net.h b/src/virtio-net-address-range.h similarity index 73% rename from src/virtio-net.h rename to src/virtio-net-address-range.h index b17c09aab..806b53311 100644 --- a/src/virtio-net.h +++ b/src/virtio-net-address-range.h @@ -14,8 +14,8 @@ // along with the machine-emulator. If not, see http://www.gnu.org/licenses/. // -#ifndef VIRTIO_NET_H -#define VIRTIO_NET_H +#ifndef VIRTIO_NET_ADDRESS_RANGE_H +#define VIRTIO_NET_ADDRESS_RANGE_H #include "os-features.h" @@ -26,7 +26,7 @@ #include "i-device-state-access.h" #include "os.h" -#include "virtio-device.h" +#include "virtio-address-range.h" namespace cartesi { @@ -53,26 +53,39 @@ enum virtio_net_virtq : uint32_t { VIRTIO_NET_TRANSMITQ = 1, ///< Queue of packets from guest to host }; -/// \brief Generic interface for a network carrier on the host. -/// \details The sole purpose of a network carrier -/// is to carry incoming or outgoing packets between the host and the guest. -class virtio_net_carrier { +/// \brief VirtIO net device +class virtio_net_address_range : public virtio_address_range { public: - virtio_net_carrier() = default; - virtual ~virtio_net_carrier() = default; - virtio_net_carrier(const virtio_net_carrier &other) = delete; - virtio_net_carrier(virtio_net_carrier &&other) = delete; - virtio_net_carrier &operator=(const virtio_net_carrier &other) = delete; - virtio_net_carrier &operator=(virtio_net_carrier &&other) = delete; + virtio_net_address_range(const char *description, uint64_t start, uint64_t length, uint32_t virtio_idx); + + virtio_net_address_range(const virtio_net_address_range &other) = delete; + virtio_net_address_range &operator=(const virtio_net_address_range &other) = delete; + virtio_net_address_range &operator=(virtio_net_address_range &&other) = delete; + + virtio_net_address_range(virtio_net_address_range &&other) = default; + ~virtio_net_address_range() override = default; + + void on_device_reset() override; + void on_device_ok(i_device_state_access *a) override; + bool on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t read_avail_len, uint32_t write_avail_len) override; + void prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; + bool poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) override; + + bool write_next_packet_to_host(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t read_avail_len); + bool read_next_packet_from_host(i_device_state_access *a); + +protected: /// \brief Reset carrier internal state, discarding all network state. - virtual void reset() = 0; + virtual void net_reset() = 0; /// \brief Fill file descriptors to be polled by select(). - virtual void do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) = 0; + virtual void net_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) = 0; /// \brief Poll file descriptors that were marked as ready by select(). - virtual bool do_poll_selected(int select_ret, select_fd_sets *fds) = 0; + virtual bool net_poll_selected(int select_ret, select_fd_sets *fds) = 0; /// \brief Called for carrying outgoing packets from the guest to the host. /// \param vq Queue reference. @@ -82,8 +95,8 @@ class virtio_net_carrier { /// \returns True on success, false otherwise. /// \details This function will return true even if when the write queue is full, /// pread_len will be set to 0 in this case and the packet dropped. - virtual bool write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, - uint32_t *pread_len) = 0; + virtual bool net_write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, + uint32_t read_avail_len, uint32_t *pread_len) = 0; /// \brief Called for carrying incoming packets from the host to the guest. /// \param vq Queue reference. @@ -93,29 +106,8 @@ class virtio_net_carrier { /// \returns True on success, false otherwise. /// \details This function will true even if when there are no more packets to write, /// pwritten_len will be set to 0 in this case. - virtual bool read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, - uint32_t *pwritten_len) = 0; -}; - -/// \brief VirtIO net device -class virtio_net final : public virtio_device { -public: - virtio_net(uint32_t virtio_idx, std::unique_ptr &&carrier); - - void on_device_reset() override; - void on_device_ok(i_device_state_access *a) override; - bool on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t read_avail_len, uint32_t write_avail_len) override; - - void prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; - bool poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) override; - - bool write_next_packet_to_host(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t read_avail_len); - bool read_next_packet_from_host(i_device_state_access *a); - -private: - std::unique_ptr m_carrier; + virtual bool net_read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, + uint32_t write_avail_len, uint32_t *pwritten_len) = 0; }; } // namespace cartesi diff --git a/src/virtio-net-carrier-tuntap.h b/src/virtio-net-carrier-tuntap.h deleted file mode 100644 index 392cdd86e..000000000 --- a/src/virtio-net-carrier-tuntap.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef VIRTIO_NET_CARRIER_TUNTAP_H -#define VIRTIO_NET_CARRIER_TUNTAP_H - -#include "os-features.h" - -#ifdef HAVE_TUNTAP - -#include -#include - -#include "i-device-state-access.h" -#include "os.h" -#include "virtio-device.h" -#include "virtio-net.h" - -namespace cartesi { - -class virtio_net_carrier_tuntap final : public virtio_net_carrier { - int m_tapfd = -1; - -public: - explicit virtio_net_carrier_tuntap(const std::string &tap_name); - ~virtio_net_carrier_tuntap() override; - virtio_net_carrier_tuntap(const virtio_net_carrier_tuntap &other) = delete; - virtio_net_carrier_tuntap(virtio_net_carrier_tuntap &&other) = delete; - virtio_net_carrier_tuntap &operator=(const virtio_net_carrier_tuntap &other) = delete; - virtio_net_carrier_tuntap &operator=(virtio_net_carrier_tuntap &&other) = delete; - - void reset() override; - - void do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; - bool do_poll_selected(int select_ret, select_fd_sets *fds) override; - - bool write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, - uint32_t *pread_len) override; - bool read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, - uint32_t *pwritten_len) override; -}; - -} // namespace cartesi - -#endif // HAVE_TUNTAP - -#endif diff --git a/src/virtio-net-carrier-tuntap.cpp b/src/virtio-net-tuntap-address-range.cpp similarity index 90% rename from src/virtio-net-carrier-tuntap.cpp rename to src/virtio-net-tuntap-address-range.cpp index 4da7a3188..68aaa596c 100644 --- a/src/virtio-net-carrier-tuntap.cpp +++ b/src/virtio-net-tuntap-address-range.cpp @@ -52,7 +52,7 @@ // #define DEBUG_VIRTIO_ERRORS -#include "virtio-net-carrier-tuntap.h" +#include "virtio-net-tuntap-address-range.h" #ifdef HAVE_TUNTAP @@ -74,8 +74,6 @@ #include "i-device-state-access.h" #include "os.h" -#include "virtio-device.h" -#include "virtio-net.h" // Include TUN/TAP headers #ifdef __linux__ @@ -90,7 +88,9 @@ constexpr const char NET_TUN_DEV[] = "/dev/tun"; namespace cartesi { -virtio_net_carrier_tuntap::virtio_net_carrier_tuntap(const std::string &tap_name) { +virtio_net_tuntap_address_range::virtio_net_tuntap_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const std::string &tap_name) : + virtio_net_address_range("VirtIO Net TUN/TAP", start, length, virtio_idx) { // Open the tun device const int flags = O_RDWR | // Read/write O_NONBLOCK | // Read/write should never block @@ -113,30 +113,30 @@ virtio_net_carrier_tuntap::virtio_net_carrier_tuntap(const std::string &tap_name m_tapfd = fd; } -virtio_net_carrier_tuntap::~virtio_net_carrier_tuntap() { +virtio_net_tuntap_address_range::~virtio_net_tuntap_address_range() { if (m_tapfd != -1) { close(m_tapfd); } } -void virtio_net_carrier_tuntap::reset() { +void virtio_net_tuntap_address_range::net_reset() { // Nothing to do. } -void virtio_net_carrier_tuntap::do_prepare_select(select_fd_sets *fds, uint64_t * /*timeout_us*/) { +void virtio_net_tuntap_address_range::net_prepare_select(select_fd_sets *fds, uint64_t * /*timeout_us*/) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto *readfds = reinterpret_cast(fds->readfds); FD_SET(m_tapfd, readfds); fds->maxfd = std::max(m_tapfd, fds->maxfd); } -bool virtio_net_carrier_tuntap::do_poll_selected(int select_ret, select_fd_sets *fds) { +bool virtio_net_tuntap_address_range::net_poll_selected(int select_ret, select_fd_sets *fds) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto *readfds = reinterpret_cast(fds->readfds); return select_ret > 0 && FD_ISSET(m_tapfd, readfds); } -bool virtio_net_carrier_tuntap::write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, +bool virtio_net_tuntap_address_range::net_write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, uint32_t *pread_len) { // Determinate packet size const uint32_t packet_len = read_avail_len - VIRTIO_NET_ETHERNET_FRAME_OFFSET; @@ -182,7 +182,7 @@ bool virtio_net_carrier_tuntap::write_packet_to_host(i_device_state_access *a, v return true; } -bool virtio_net_carrier_tuntap::read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, +bool virtio_net_tuntap_address_range::net_read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, uint32_t *pwritten_len) { // Write network to queue buffer in chunks std::array packet_buf{}; diff --git a/src/virtio-net-tuntap-address-range.h b/src/virtio-net-tuntap-address-range.h new file mode 100644 index 000000000..cd3328dbe --- /dev/null +++ b/src/virtio-net-tuntap-address-range.h @@ -0,0 +1,67 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef VIRTIO_NET_TUNTAP_ADDRESS_RANGE_H +#define VIRTIO_NET_TUNTAP_ADDRESS_RANGE_H + +#include "os-features.h" + +#ifdef HAVE_TUNTAP + +#include +#include + +#include "i-device-state-access.h" +#include "os.h" +#include "virtio-net-address-range.h" + +namespace cartesi { + +class virtio_net_tuntap_address_range final : public virtio_net_address_range { + int m_tapfd = -1; + +public: + virtio_net_tuntap_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, const std::string &tap_name); + + virtio_net_tuntap_address_range(const virtio_net_tuntap_address_range &other) = delete; + virtio_net_tuntap_address_range &operator=(const virtio_net_tuntap_address_range &other) = delete; + virtio_net_tuntap_address_range &operator=(virtio_net_tuntap_address_range &&other) = delete; + + virtio_net_tuntap_address_range(virtio_net_tuntap_address_range &&other) = default; + ~virtio_net_tuntap_address_range() override; + +protected: + void net_reset() override; + + void net_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; + bool net_poll_selected(int select_ret, select_fd_sets *fds) override; + + bool net_write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, + uint32_t *pread_len) override; + bool net_read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, + uint32_t *pwritten_len) override; +}; + +static inline auto make_virtio_net_tuntap_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const std::string &tap_name) { + return virtio_net_tuntap_address_range{start, length, virtio_idx, tap_name}; +} + +} // namespace cartesi + +#endif // HAVE_TUNTAP + +#endif diff --git a/src/virtio-net-carrier-slirp.cpp b/src/virtio-net-user-address-range.cpp similarity index 92% rename from src/virtio-net-carrier-slirp.cpp rename to src/virtio-net-user-address-range.cpp index 4c51b1849..d5b03ec2b 100644 --- a/src/virtio-net-carrier-slirp.cpp +++ b/src/virtio-net-user-address-range.cpp @@ -53,7 +53,7 @@ // #define DEBUG_VIRTIO_ERRORS -#include "virtio-net-carrier-slirp.h" +#include "virtio-net-user-address-range.h" #ifdef HAVE_SLIRP @@ -74,8 +74,6 @@ #include "i-device-state-access.h" #include "machine-config.h" #include "os.h" -#include "virtio-device.h" -#include "virtio-net.h" using namespace std::string_literals; @@ -84,7 +82,7 @@ namespace cartesi { //??D why ssize_t? static ssize_t slirp_send_packet(const void *buf, size_t len, void *opaque) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - auto *carrier = reinterpret_cast(opaque); + auto *carrier = reinterpret_cast(opaque); if (carrier->send_packets.size() >= SLIRP_MAX_PENDING_PACKETS) { // Too many send_packets in the write queue, we can just drop it. // Network re-transmission can recover from this. @@ -130,7 +128,7 @@ static int64_t slirp_clock_get_ns(void * /*opaque*/) { static void *slirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - auto *carrier = reinterpret_cast(opaque); + auto *carrier = reinterpret_cast(opaque); try { auto *timer = new slirp_timer; timer->cb = cb; @@ -145,7 +143,7 @@ static void *slirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) { static void slirp_timer_free(void *timer_ptr, void *opaque) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - auto *carrier = reinterpret_cast(opaque); + auto *carrier = reinterpret_cast(opaque); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto *timer = reinterpret_cast(timer_ptr); if (timer != nullptr) { @@ -159,7 +157,7 @@ static void slirp_timer_free(void *timer_ptr, void *opaque) { static void slirp_timer_mod(void *timer_ptr, int64_t expire_timer_msec, void *opaque) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - auto *carrier = reinterpret_cast(opaque); + auto *carrier = reinterpret_cast(opaque); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto *timer = reinterpret_cast(timer_ptr); if ((timer != nullptr) && carrier->timers.find(timer) != carrier->timers.end()) { @@ -179,7 +177,10 @@ static void slirp_notify(void * /*opaque*/) { // Nothing to do } -virtio_net_carrier_slirp::virtio_net_carrier_slirp(const cartesi::virtio_net_user_config &config) { +virtio_net_user_address_range::virtio_net_user_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const cartesi::virtio_net_user_config &config) : + virtio_net_address_range("VirtIO Net User", start, length, virtio_idx) { + // Configure slirp slirp_cfg.version = std::min(SLIRP_CONFIG_VERSION_MAX, SLIRP_VERSION); slirp_cfg.restricted = 0; // Don't isolate the guest from the host @@ -226,7 +227,7 @@ virtio_net_carrier_slirp::virtio_net_carrier_slirp(const cartesi::virtio_net_use } } -virtio_net_carrier_slirp::~virtio_net_carrier_slirp() { +virtio_net_user_address_range::~virtio_net_user_address_range() { // Cleanup slirp if (slirp != nullptr) { slirp_cleanup(slirp); @@ -239,7 +240,7 @@ virtio_net_carrier_slirp::~virtio_net_carrier_slirp() { timers.clear(); } -void virtio_net_carrier_slirp::reset() { +void virtio_net_user_address_range::net_reset() { // Nothing to do, we don't want to reset slirp to not lose network state. } @@ -282,7 +283,7 @@ static int slirp_get_revents_cb(int fd, void *opaque) { return event; } -void virtio_net_carrier_slirp::do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { +void virtio_net_user_address_range::net_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { // Did device reset and slirp failed to reinitialize? if (slirp == nullptr) { return; @@ -305,7 +306,7 @@ void virtio_net_carrier_slirp::do_prepare_select(select_fd_sets *fds, uint64_t * } } -bool virtio_net_carrier_slirp::do_poll_selected(int select_ret, select_fd_sets *fds) { +bool virtio_net_user_address_range::net_poll_selected(int select_ret, select_fd_sets *fds) { // Did device reset and slirp failed to reinitialize? if (slirp == nullptr) { return false; @@ -332,7 +333,7 @@ bool virtio_net_carrier_slirp::do_poll_selected(int select_ret, select_fd_sets * return !send_packets.empty(); } -bool virtio_net_carrier_slirp::write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, +bool virtio_net_user_address_range::net_write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, uint32_t *pread_len) { // Did device reset and slirp failed to reinitialize? if (slirp == nullptr) { @@ -361,7 +362,7 @@ bool virtio_net_carrier_slirp::write_packet_to_host(i_device_state_access *a, vi return true; } -bool virtio_net_carrier_slirp::read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, +bool virtio_net_user_address_range::net_read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, uint32_t *pwritten_len) { // If no packet was send by slirp, we can just ignore. if (send_packets.empty()) { diff --git a/src/virtio-net-carrier-slirp.h b/src/virtio-net-user-address-range.h similarity index 61% rename from src/virtio-net-carrier-slirp.h rename to src/virtio-net-user-address-range.h index c71aa3b49..9798ad66f 100644 --- a/src/virtio-net-carrier-slirp.h +++ b/src/virtio-net-user-address-range.h @@ -14,8 +14,8 @@ // with this program (see COPYING). If not, see . // -#ifndef VIRTIO_NET_CARRIER_SLIRP_H -#define VIRTIO_NET_CARRIER_SLIRP_H +#ifndef VIRTIO_NET_USER_ADDRESS_RANGE_H +#define VIRTIO_NET_USER_ADDRESS_RANGE_H #include "os-features.h" @@ -35,8 +35,7 @@ #include "i-device-state-access.h" #include "machine-config.h" #include "os.h" -#include "virtio-device.h" -#include "virtio-net.h" +#include "virtio-net-address-range.h" /// \brief Slirp constants enum slirp_constants { @@ -66,7 +65,7 @@ struct slirp_packet { std::array buf{}; }; -class virtio_net_carrier_slirp final : public virtio_net_carrier { +class virtio_net_user_address_range final : public virtio_net_address_range { public: Slirp *slirp = nullptr; SlirpConfig slirp_cfg{}; @@ -74,24 +73,32 @@ class virtio_net_carrier_slirp final : public virtio_net_carrier { std::list send_packets; std::unordered_set timers; - explicit virtio_net_carrier_slirp(const cartesi::virtio_net_user_config &config); - ~virtio_net_carrier_slirp() override; - virtio_net_carrier_slirp(const virtio_net_carrier_slirp &other) = delete; - virtio_net_carrier_slirp(virtio_net_carrier_slirp &&other) = delete; - virtio_net_carrier_slirp &operator=(const virtio_net_carrier_slirp &other) = delete; - virtio_net_carrier_slirp &operator=(virtio_net_carrier_slirp &&other) = delete; + virtio_net_user_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const virtio_net_user_config &config); - void reset() override; + virtio_net_user_address_range(const virtio_net_user_address_range &other) = delete; + virtio_net_user_address_range &operator=(const virtio_net_user_address_range &other) = delete; + virtio_net_user_address_range &operator=(virtio_net_user_address_range &&other) = delete; - void do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; - bool do_poll_selected(int select_ret, select_fd_sets *fds) override; + virtio_net_user_address_range(virtio_net_user_address_range &&other) = default; + ~virtio_net_user_address_range() override; - bool write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, + void net_reset() override; + + void net_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; + bool net_poll_selected(int select_ret, select_fd_sets *fds) override; + + bool net_write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, uint32_t *pread_len) override; - bool read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, + bool net_read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, uint32_t *pwritten_len) override; }; +static inline auto make_virtio_net_user_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const virtio_net_user_config &config) { + return virtio_net_user_address_range{start, length, virtio_idx, config}; +} + } // namespace cartesi #endif // HAVE_SLIRP diff --git a/src/virtio-p9fs.cpp b/src/virtio-p9fs-address-range.cpp similarity index 96% rename from src/virtio-p9fs.cpp rename to src/virtio-p9fs-address-range.cpp index cba680a68..728066b30 100644 --- a/src/virtio-p9fs.cpp +++ b/src/virtio-p9fs-address-range.cpp @@ -32,7 +32,7 @@ // Enable this define to debug VirtIO Plan 9 filesystem operations // #define DEBUG_VIRTIO_P9FS -#include "virtio-p9fs.h" +#include "virtio-p9fs-address-range.h" #ifdef HAVE_POSIX_FS @@ -68,7 +68,7 @@ #include #include "i-device-state-access.h" -#include "virtio-device.h" +#include "virtio-address-range.h" #include "virtio-serializer.h" #ifdef _WIN32 @@ -1056,9 +1056,10 @@ static bool is_name_legal(const std::string &name) { return true; } -virtio_p9fs_device::virtio_p9fs_device(uint32_t virtio_idx, const std::string &mount_tag, - const std::string &root_path) : - virtio_device(virtio_idx, VIRTIO_DEVICE_9P, VIRTIO_9P_F_MOUNT_TAG, mount_tag.length() + sizeof(uint16_t)), +virtio_p9fs_address_range::virtio_p9fs_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const std::string &mount_tag, const std::string &root_path) : + virtio_address_range("VirtIO 9P", start, length, virtio_idx, VIRTIO_DEVICE_9P, VIRTIO_9P_F_MOUNT_TAG, + mount_tag.length() + sizeof(uint16_t)), m_msize(P9_MAX_MSIZE), m_root_path(root_path) { if (root_path.length() + 1 >= P9_ROOT_PATH_MAX) { @@ -1077,7 +1078,7 @@ virtio_p9fs_device::virtio_p9fs_device(uint32_t virtio_idx, const std::string &m config->mount_tag_len = mount_tag.length(); } -virtio_p9fs_device::~virtio_p9fs_device() { +virtio_p9fs_address_range::~virtio_p9fs_address_range() { // Close all file descriptors for (auto &it : m_fids) { p9_fid_state *fidp = &it.second; @@ -1086,7 +1087,7 @@ virtio_p9fs_device::~virtio_p9fs_device() { m_fids.clear(); } -void virtio_p9fs_device::on_device_reset() { +void virtio_p9fs_address_range::on_device_reset() { m_msize = P9_MAX_MSIZE; // Close all file descriptors for (auto &it : m_fids) { @@ -1096,12 +1097,12 @@ void virtio_p9fs_device::on_device_reset() { m_fids.clear(); } -void virtio_p9fs_device::on_device_ok(i_device_state_access * /*a*/) { +void virtio_p9fs_address_range::on_device_ok(i_device_state_access * /*a*/) { // Nothing to do. } -bool virtio_p9fs_device::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, - uint32_t /*read_avail_len*/, uint32_t /*write_avail_len*/) { +bool virtio_p9fs_address_range::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, + uint16_t desc_idx, uint32_t /*read_avail_len*/, uint32_t /*write_avail_len*/) { // We are only interested in queue 0 notifications if (queue_idx != 0) { return false; @@ -1194,7 +1195,7 @@ bool virtio_p9fs_device::on_device_queue_available(i_device_state_access *a, uin } } -bool virtio_p9fs_device::op_statfs(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_statfs(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; if (!msg.unpack(&fid)) { @@ -1248,7 +1249,7 @@ bool virtio_p9fs_device::op_statfs(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_lopen(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_lopen(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint32_t flags{}; @@ -1324,7 +1325,7 @@ bool virtio_p9fs_device::op_lopen(virtq_unserializer &&mmsg, uint16_t tag) { return true; } -bool virtio_p9fs_device::op_lcreate(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_lcreate(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint32_t flags{}; @@ -1392,7 +1393,7 @@ bool virtio_p9fs_device::op_lcreate(virtq_unserializer &&mmsg, uint16_t tag) { return true; } -bool virtio_p9fs_device::op_symlink(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_symlink(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t dfid{}; uint32_t gid{}; @@ -1447,7 +1448,7 @@ bool virtio_p9fs_device::op_symlink(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_mknod(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_mknod(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t dfid{}; uint32_t mode{}; @@ -1505,7 +1506,7 @@ bool virtio_p9fs_device::op_mknod(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_setattr(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_setattr(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint32_t mask{}; @@ -1646,7 +1647,7 @@ bool virtio_p9fs_device::op_setattr(virtq_unserializer &&mmsg, uint16_t tag) { return send_ok(msg, tag, P9_RSETATTR); } -bool virtio_p9fs_device::op_readlink(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_readlink(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; if (!msg.unpack(&fid)) { @@ -1679,7 +1680,7 @@ bool virtio_p9fs_device::op_readlink(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_getattr(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_getattr(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint64_t mask{}; @@ -1789,7 +1790,7 @@ bool virtio_p9fs_device::op_getattr(virtq_unserializer &&mmsg, uint16_t tag) { return send_reply(std::move(out_msg), tag, P9_RGETATTR); } -bool virtio_p9fs_device::op_lock(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_lock(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint8_t type{}; @@ -1847,7 +1848,7 @@ bool virtio_p9fs_device::op_lock(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_getlock(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_getlock(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint8_t type{}; @@ -1891,7 +1892,7 @@ bool virtio_p9fs_device::op_getlock(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_readdir(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_readdir(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint64_t offset{}; @@ -2000,7 +2001,7 @@ bool virtio_p9fs_device::op_readdir(virtq_unserializer &&mmsg, uint16_t tag) { return send_reply(std::move(out_msg), tag, P9_RREADDIR); } -bool virtio_p9fs_device::op_fsync(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_fsync(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; if (!msg.unpack(&fid)) { @@ -2022,7 +2023,7 @@ bool virtio_p9fs_device::op_fsync(virtq_unserializer &&mmsg, uint16_t tag) { return send_ok(msg, tag, P9_RFSYNC); } -bool virtio_p9fs_device::op_link(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_link(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t dfid{}; uint32_t fid{}; @@ -2060,7 +2061,7 @@ bool virtio_p9fs_device::op_link(virtq_unserializer &&mmsg, uint16_t tag) { #endif } -bool virtio_p9fs_device::op_mkdir(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_mkdir(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t dfid{}; uint32_t mode{}; @@ -2116,7 +2117,7 @@ bool virtio_p9fs_device::op_mkdir(virtq_unserializer &&mmsg, uint16_t tag) { return true; } -bool virtio_p9fs_device::op_renameat(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_renameat(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t oldfid{}; uint32_t newfid{}; @@ -2162,7 +2163,7 @@ bool virtio_p9fs_device::op_renameat(virtq_unserializer &&mmsg, uint16_t tag) { return true; } -bool virtio_p9fs_device::op_unlinkat(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_unlinkat(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t dfid{}; uint32_t flags{}; @@ -2196,7 +2197,7 @@ bool virtio_p9fs_device::op_unlinkat(virtq_unserializer &&mmsg, uint16_t tag) { return send_ok(msg, tag, P9_RUNLINKAT); } -bool virtio_p9fs_device::op_version(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_version(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); char version[32]{}; uint32_t msize{}; @@ -2217,7 +2218,7 @@ bool virtio_p9fs_device::op_version(virtq_unserializer &&mmsg, uint16_t tag) { return send_reply(std::move(out_msg), tag, P9_RVERSION); } -bool virtio_p9fs_device::op_attach(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_attach(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint32_t afid{}; @@ -2259,7 +2260,7 @@ bool virtio_p9fs_device::op_attach(virtq_unserializer &&mmsg, uint16_t tag) { return true; } -bool virtio_p9fs_device::op_walk(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_walk(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint32_t newfid{}; @@ -2356,7 +2357,7 @@ bool virtio_p9fs_device::op_walk(virtq_unserializer &&mmsg, uint16_t tag) { return true; } -bool virtio_p9fs_device::op_read(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_read(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint64_t offset{}; @@ -2391,7 +2392,7 @@ bool virtio_p9fs_device::op_read(virtq_unserializer &&mmsg, uint16_t tag) { return send_reply(std::move(out_msg), tag, P9_RREAD); } -bool virtio_p9fs_device::op_write(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_write(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; uint64_t offset{}; @@ -2426,7 +2427,7 @@ bool virtio_p9fs_device::op_write(virtq_unserializer &&mmsg, uint16_t tag) { return send_reply(std::move(out_msg), tag, P9_RREAD); } -bool virtio_p9fs_device::op_clunk(virtq_unserializer &&mmsg, uint16_t tag) { +bool virtio_p9fs_address_range::op_clunk(virtq_unserializer &&mmsg, uint16_t tag) { virtq_unserializer msg = std::move(mmsg); uint32_t fid{}; if (!msg.unpack(&fid)) { @@ -2452,7 +2453,7 @@ bool virtio_p9fs_device::op_clunk(virtq_unserializer &&mmsg, uint16_t tag) { return send_ok(msg, tag, P9_RCLUNK); } -bool virtio_p9fs_device::send_reply(virtq_serializer &&mout_msg, uint16_t tag, p9_opcode opcode) { +bool virtio_p9fs_address_range::send_reply(virtq_serializer &&mout_msg, uint16_t tag, p9_opcode opcode) { virtq_serializer out_msg = std::move(mout_msg); #ifdef DEBUG_VIRTIO_P9FS if (opcode != P9_RLERROR) { @@ -2477,12 +2478,12 @@ bool virtio_p9fs_device::send_reply(virtq_serializer &&mout_msg, uint16_t tag, p return true; } -bool virtio_p9fs_device::send_ok(const virtq_unserializer &in_msg, uint16_t tag, p9_opcode opcode) { +bool virtio_p9fs_address_range::send_ok(const virtq_unserializer &in_msg, uint16_t tag, p9_opcode opcode) { virtq_serializer out_msg(in_msg.a, in_msg.vq, in_msg.queue_idx, in_msg.desc_idx, P9_OUT_MSG_OFFSET); return send_reply(std::move(out_msg), tag, opcode); } -bool virtio_p9fs_device::send_error(const virtq_unserializer &in_msg, uint16_t tag, p9_error error) { +bool virtio_p9fs_address_range::send_error(const virtq_unserializer &in_msg, uint16_t tag, p9_error error) { #ifdef DEBUG_VIRTIO_P9FS if (error == P9_EPROTO) { std::ignore = fprintf(stderr, "p9fs PROTOCOL ERROR: tag=%d\n", tag); diff --git a/src/virtio-p9fs.h b/src/virtio-p9fs-address-range.h similarity index 94% rename from src/virtio-p9fs.h rename to src/virtio-p9fs-address-range.h index 761727a31..1fe94b9ac 100644 --- a/src/virtio-p9fs.h +++ b/src/virtio-p9fs-address-range.h @@ -14,8 +14,8 @@ // with this program (see COPYING). If not, see . // -#ifndef VIRTIO_P9FS_H -#define VIRTIO_P9FS_H +#ifndef VIRTIO_P9FS_ADDRESS_RANGE_H +#define VIRTIO_P9FS_ADDRESS_RANGE_H #include "os-features.h" @@ -29,7 +29,7 @@ #include "compiler-defines.h" #include "i-device-state-access.h" -#include "virtio-device.h" +#include "virtio-address-range.h" #include "virtio-serializer.h" namespace cartesi { @@ -396,18 +396,21 @@ struct virtio_p9fs_config_space { }; /// \brief VirtIO Plan 9 filesystem device -class virtio_p9fs_device final : public virtio_device { +class virtio_p9fs_address_range final : public virtio_address_range { uint32_t m_msize = 0; std::string m_root_path; std::unordered_map m_fids; public: - virtio_p9fs_device(uint32_t virtio_idx, const std::string &mount_tag, const std::string &root_path); - ~virtio_p9fs_device() override; - virtio_p9fs_device(const virtio_p9fs_device &other) = delete; - virtio_p9fs_device(virtio_p9fs_device &&other) = delete; - virtio_p9fs_device &operator=(const virtio_p9fs_device &other) = delete; - virtio_p9fs_device &operator=(virtio_p9fs_device &&other) = delete; + virtio_p9fs_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, const std::string &mount_tag, + const std::string &root_path); + + virtio_p9fs_address_range(const virtio_p9fs_address_range &other) = delete; + virtio_p9fs_address_range &operator=(const virtio_p9fs_address_range &other) = delete; + virtio_p9fs_address_range &operator=(virtio_p9fs_address_range &&other) = delete; + + virtio_p9fs_address_range(virtio_p9fs_address_range &&other) = default; + ~virtio_p9fs_address_range() override; void on_device_reset() override; void on_device_ok(i_device_state_access *a) override; @@ -456,8 +459,13 @@ class virtio_p9fs_device final : public virtio_device { } }; +static inline auto make_virtio_p9fs_address_range(uint64_t start, uint64_t length, uint32_t virtio_idx, + const std::string &mount_tag, const std::string &root_path) { + return virtio_p9fs_address_range{start, length, virtio_idx, mount_tag, root_path}; +} + } // namespace cartesi #endif // HAVE_POSIX_FS -#endif +#endif // VIRTIO_P9FS_ADDRESS_RANGE_H diff --git a/src/virtio-serializer.h b/src/virtio-serializer.h index c5624ecd8..32942c85c 100644 --- a/src/virtio-serializer.h +++ b/src/virtio-serializer.h @@ -22,7 +22,7 @@ #include #include "i-device-state-access.h" -#include "virtio-device.h" +#include "virtio-address-range.h" namespace cartesi { diff --git a/src/virtual-machine.cpp b/src/virtual-machine.cpp index 0c4d4f3c3..b2ff88abd 100644 --- a/src/virtual-machine.cpp +++ b/src/virtual-machine.cpp @@ -20,10 +20,10 @@ #include #include "access-log.h" +#include "address-range-description.h" #include "i-virtual-machine.h" #include "interpret.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-runtime-config.h" #include "machine.h" @@ -163,8 +163,8 @@ uarch_interpreter_break_reason virtual_machine::do_run_uarch(uint64_t uarch_cycl return get_machine()->run_uarch(uarch_cycle_end); } -machine_memory_range_descrs virtual_machine::do_get_memory_ranges() const { - return get_machine()->get_memory_ranges(); +address_range_descriptions virtual_machine::do_get_address_ranges() const { + return get_machine()->get_address_ranges(); } void virtual_machine::do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) { diff --git a/src/virtual-machine.h b/src/virtual-machine.h index 64e9340c7..b3096af03 100644 --- a/src/virtual-machine.h +++ b/src/virtual-machine.h @@ -21,10 +21,10 @@ #include #include "access-log.h" +#include "address-range-description.h" #include "i-virtual-machine.h" #include "interpret.h" #include "machine-config.h" -#include "machine-memory-range-descr.h" #include "machine-merkle-tree.h" #include "machine-runtime-config.h" #include "machine.h" @@ -72,7 +72,7 @@ class virtual_machine : public i_virtual_machine { void do_reset_uarch() override; access_log do_log_reset_uarch(const access_log::type &log_type) override; uarch_interpreter_break_reason do_run_uarch(uint64_t uarch_cycle_end) override; - machine_memory_range_descrs do_get_memory_ranges() const override; + address_range_descriptions do_get_address_ranges() const override; void do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) override; access_log do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) override; diff --git a/tests/lua/cartesi/tests/util.lua b/tests/lua/cartesi/tests/util.lua index b63eac9b6..b5398d0f7 100644 --- a/tests/lua/cartesi/tests/util.lua +++ b/tests/lua/cartesi/tests/util.lua @@ -249,7 +249,7 @@ test_util.merkle_hash = merkle_hash function test_util.calculate_emulator_hash(machine) local tree = test_util.new_back_merkle_tree(64, PAGE_LOG2_SIZE) local last = 0 - for _, v in ipairs(machine:get_memory_ranges()) do + for _, v in ipairs(machine:get_address_ranges()) do tree:pad_back((v.start - last) >> PAGE_LOG2_SIZE) local finish = v.start + v.length for j = v.start, finish - 1, PAGE_SIZE do diff --git a/tests/lua/create-machines.lua b/tests/lua/create-machines.lua index 58c155193..11a11607d 100755 --- a/tests/lua/create-machines.lua +++ b/tests/lua/create-machines.lua @@ -213,9 +213,11 @@ rollup-init bash /home/dapp/s.sh ) -- Should not work with shared buffers -create_machine("shared-rx-buffer-machine", "rollup accept", function(config) +local ret, err = pcall(create_machine, "shared-rx-buffer-machine", "rollup accept", function(config) config.cmio.rx_buffer.shared = true end) -create_machine("shared-tx-buffer-machine", "rollup accept", function(config) +assert(not ret, "should have failed") +ret, err = pcall(create_machine, "shared-tx-buffer-machine", "rollup accept", function(config) config.cmio.tx_buffer.shared = true end) +assert(not ret, "should have failed") diff --git a/tests/lua/machine-bind.lua b/tests/lua/machine-bind.lua index 28c2cfbc3..448b2e68d 100755 --- a/tests/lua/machine-bind.lua +++ b/tests/lua/machine-bind.lua @@ -25,7 +25,7 @@ local remote_address local test_path = "./" local concurrency_update_merkle_tree = util.parse_number(os.getenv("CARTESI_CONCURRENCY_UPDATE_MERKLE_TREE")) or 0 --- local lua_cmd = arg[-1] .. " -e " +local lua_cmd = arg[-1] .. " -e " -- There is no UINT64_MAX in Lua, so we have to use the signed representation local MAX_MCYCLE = -1 @@ -642,9 +642,12 @@ end) print("\n\n check memory reading/writing") do_test("written and read values should match", function(machine) -- Check mem write and mem read - machine:write_memory(0x800000FF, "mydataol12345678", 0x10) - local memory_read = machine:read_memory(0x800000FF, 0x10) - assert(memory_read == "mydataol12345678") + local written = "mydataol12345678" + machine:write_memory(0x800000FF, written) + local read = machine:read_memory(0x800000FF, written:len()) + if read ~= written then + error(string.format("expected %q (got %q)", written, read)) + end end) print("\n\n dump step log to console") @@ -1101,7 +1104,6 @@ test_util.make_do_test(build_machine, machine_type, { check_error_find(err, "illegal instruction") end) ---[==[ do_test("uarch ecall putchar should print char to console", function() local lua_code = [[ " local cartesi = require 'cartesi' @@ -1110,9 +1112,9 @@ do_test("uarch ecall putchar should print char to console", function() local initial_reg_values = {} local program = { (cartesi.UARCH_ECALL_FN_PUTCHAR << 20) | 0x00893, -- li a7,putchar - 0x05800813, -- li a6,'X'' + 0x05800513, -- li a0,'X' 0x00000073, -- ecall - } + } local uarch_ram_path = test_util.create_test_uarch_program(program) local machine = cartesi.machine { processor = initial_reg_values, @@ -1134,7 +1136,6 @@ do_test("uarch ecall putchar should print char to console", function() print("--------------------------") assert(output == expected_output, "Output does not match expected output:\n" .. expected_output) end) ---]==] print("\n\ntesting send cmio response ") @@ -1172,6 +1173,14 @@ local function assert_access(accesses, index, expected_key_and_values) end end +local function dump_pmas(machine) + for _, v in ipairs(machine:get_address_ranges()) do + local filename = string.format("%016x--%016x.bin", v.start, v.length) + local file = assert(io.open(filename, "w")) + assert(file:write(machine:read_memory(v.start, v.length))) + end +end + local function test_send_cmio_input_with_different_arguments() local data = string.rep("a", 1 << cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE) local reason = 1 @@ -1213,6 +1222,7 @@ local function test_send_cmio_input_with_different_arguments() | (large_data and cartesi.ACCESS_LOG_TYPE_LARGE_DATA or 0) assert_before_cmio_response_sent(machine) local root_hash_before = machine:get_root_hash() + dump_pmas(machine) local log = machine:log_send_cmio_response(reason, data, log_type) assert_after_cmio_response_sent(machine) local root_hash_after = machine:get_root_hash() diff --git a/tests/lua/machine-test.lua b/tests/lua/machine-test.lua index dde120216..046f88474 100755 --- a/tests/lua/machine-test.lua +++ b/tests/lua/machine-test.lua @@ -197,7 +197,7 @@ do_test("proof check should pass", function(machine) -- Find ram memory range local ram - for _, v in ipairs(machine:get_memory_ranges()) do + for _, v in ipairs(machine:get_address_ranges()) do if v.description == "RAM" then ram = v end @@ -350,26 +350,28 @@ test_util.make_do_test(build_machine, machine_type, { }, }, })("should replace flash drive and read something", function(machine) - local rootfs_length = machine:get_initial_config().flash_drive[1].length + local rootfs = machine:get_initial_config().flash_drive[1] -- Create temp flash file local input_path = test_path .. "input.raw" - local command = "echo 'test data 1234567890' > " + local replaced_data = 'test data 1234567890' + local command = string.format("echo '%s' > ", replaced_data) .. input_path .. " && truncate -s " - .. tostring(rootfs_length) + .. tostring(rootfs.length) .. " " .. input_path local p = assert(io.popen(command)) p:close() - local flash_address_start = 0x80000000000000 + machine:read_memory(rootfs.start, 20) - machine:read_memory(flash_address_start, 20) + machine:replace_memory_range(rootfs.start, rootfs.length, true, input_path) - machine:replace_memory_range(flash_address_start, rootfs_length, true, input_path) + local read_data = machine:read_memory(rootfs.start, 20) - local flash_data = machine:read_memory(flash_address_start, 20) - assert(flash_data == "test data 1234567890", "data read from replaced flash failed") + if (read_data ~= replaced_data) then + error(string.format("expected to read %q from replaced drive (got %q)", replaced_data, read_data)) + end os.remove(input_path) end) diff --git a/tests/misc/Makefile b/tests/misc/Makefile index f4a2eeffb..84b6a8f4f 100644 --- a/tests/misc/Makefile +++ b/tests/misc/Makefile @@ -95,7 +95,7 @@ CLANG_FORMAT=clang-format CLANG_FORMAT_FILES:=$(wildcard *.cpp) $(wildcard *.h) INCS=-I../../src -I../../third-party/tiny_sha3 -I../../third-party/nlohmann-json -I../../third-party/downloads -WARNS=-Wall -Wpedantic +WARNS=-Wall -Wpedantic $(BOOST_INC) CXXFLAGS+=-O2 -g -std=gnu++20 -fvisibility=hidden $(INCS) $(UBFLAGS) $(WARNS) @@ -109,11 +109,17 @@ all: $(BUILDDIR)/test-merkle-tree-hash $(BUILDDIR)/test-machine-c-api $(info libcartesi.a and/or libcartesi_merkle_tree.a were not found! Build them first.) @exit 1 -$(BUILDDIR)/test-merkle-tree-hash: test-merkle-tree-hash.cpp ../../src/libcartesi.a ../../src/libcartesi_merkle_tree.a +$(BUILDDIR)/test-merkle-tree-hash: test-merkle-tree-hash.o ../../src/libcartesi.a ../../src/libcartesi_merkle_tree.a $(CXX) -o $@ $^ $(CXXFLAGS) -$(BUILDDIR)/test-machine-c-api: test-machine-c-api.cpp ../../src/libcartesi.a ../../src/libcartesi_merkle_tree.a - $(CXX) -o $@ $^ $(CXXFLAGS) $(BOOST_INC) $(LIBCARTESI_LIBS) +$(BUILDDIR)/test-machine-c-api: test-machine-c-api.o ../../src/libcartesi.a ../../src/libcartesi_merkle_tree.a + $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBCARTESI_LIBS) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< %.clang-tidy: %.cpp @$(CLANG_TIDY) --header-filter='$(CLANG_TIDY_HEADER_FILTER)' $(CLANG_TIDY_FLAGS) $< -- $(CXXFLAGS) $(BOOST_INC) 2>/dev/null diff --git a/tests/misc/test-machine-c-api.cpp b/tests/misc/test-machine-c-api.cpp index 1518bb469..11ab4f23f 100644 --- a/tests/misc/test-machine-c-api.cpp +++ b/tests/misc/test-machine-c-api.cpp @@ -146,16 +146,13 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(create_machine_null_machine_test, incomplete_mach } BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_pma_overlapping_test, incomplete_machine_fixture) { - _machine_config["flash_drive"] = - nlohmann::json{nlohmann::json{nlohmann::json{"start", 0x80000000000000}, nlohmann::json{"length", 0x3c00000}, - nlohmann::json{"shared", true}}, - nlohmann::json{nlohmann::json{"start", 0x7ffffffffff000}, nlohmann::json{"length", 0x2000}, - nlohmann::json{"shared", true}}}; + _machine_config["flash_drive"] = nlohmann::json{{{"start", 0x80000000000000}, {"length", 0x3c00000}}, + {{"start", 0x7ffffffffff000}, {"length", 0x2000}}}; cm_error error_code = cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); - std::string origin("range of flash drive 1 overlaps with range of existing flash drive 0"); + std::string origin("address range of flash drive 1 overlaps with address range of existing flash drive 0"); BOOST_CHECK_EQUAL(origin, result); } @@ -163,7 +160,7 @@ class machine_flash_simple_fixture : public incomplete_machine_fixture { public: machine_flash_simple_fixture() { _machine_config["flash_drive"] = { - {{"start", 0x80000000000000}, {"length", 0x3c00000}, {"shared", true}, {"image_filename", ""}}}; + {{"start", 0x80000000000000}, {"length", 0x3c00000}, {"shared", false}, {"image_filename", ""}}}; } machine_flash_simple_fixture(const machine_flash_simple_fixture &other) = delete; @@ -179,7 +176,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_invalid_alignment_test, mach BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); - std::string origin("start of flash drive 0 (36028797018963967) must be aligned to page boundary of 4096 bytes"); + std::string origin("start of flash drive 0 (0x7fffffffffffff) must be aligned to page boundary of 4096 bytes"); BOOST_CHECK_EQUAL(origin, result); } @@ -191,7 +188,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_not_addressable_test, machin BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); - std::string origin("range of flash drive 0 must use at most 56 bits to be addressable"); + std::string origin("address range of flash drive 0 must use at most 56 bits to be addressable"); BOOST_CHECK_EQUAL(origin, result); } diff --git a/tests/misc/test-utils.h b/tests/misc/test-utils.h index 660ada53e..9934c0fd4 100644 --- a/tests/misc/test-utils.h +++ b/tests/misc/test-utils.h @@ -92,10 +92,10 @@ static hash_type calculate_emulator_hash(cm_machine *machine) { std::string page; page.resize(detail::MERKLE_PAGE_SIZE); const char *ranges_jsonstr{}; - if (cm_get_memory_ranges(machine, &ranges_jsonstr) != 0) { + if (cm_get_address_ranges(machine, &ranges_jsonstr) != 0) { throw std::runtime_error{cm_get_last_error_message()}; } - const auto mrds = cartesi::from_json(ranges_jsonstr); + const auto mrds = cartesi::from_json(ranges_jsonstr); uint64_t last = 0; for (auto m : mrds) { tree.pad_back((m.start - last) >> detail::MERKLE_PAGE_LOG2_SIZE); diff --git a/uarch/Makefile b/uarch/Makefile index 6b93c1d7a..9fe7fa4ec 100644 --- a/uarch/Makefile +++ b/uarch/Makefile @@ -58,7 +58,9 @@ CFLAGS := -march=rv64i -mabi=lp64 -Wl,--gc-sections $(OPTFLAGS) $(UBFLAGS) $(WAR $(UARCH_DEFS) \ $(TOOLCHAIN_INCS) -CXXFLAGS := -std=c++20 -fno-rtti +CXXFLAGS := -std=c++20 -fno-rtti \ + -fno-threadsafe-statics \ + -fno-use-cxa-atexit UARCH_SOURCES=\ third-party/printf/printf.c \ @@ -68,13 +70,9 @@ UARCH_SOURCES=\ EMULATOR_SOURCES=\ $(EMULATOR_SRC_DIR)/interpret.cpp \ - $(EMULATOR_SRC_DIR)/pma-driver.cpp \ - $(EMULATOR_SRC_DIR)/htif.cpp \ - $(EMULATOR_SRC_DIR)/shadow-tlb.cpp \ - $(EMULATOR_SRC_DIR)/shadow-state.cpp \ - $(EMULATOR_SRC_DIR)/shadow-uarch-state.cpp \ - $(EMULATOR_SRC_DIR)/plic.cpp \ - $(EMULATOR_SRC_DIR)/clint.cpp + $(EMULATOR_SRC_DIR)/htif-address-range.cpp \ + $(EMULATOR_SRC_DIR)/plic-address-range.cpp \ + $(EMULATOR_SRC_DIR)/clint-address-range.cpp COMPUTE_SOURCES=\ compute-uarch-pristine-hash.cpp \ diff --git a/uarch/machine-uarch-bridge-state-access.h b/uarch/machine-uarch-bridge-state-access.h index 793b04c4d..bcf842a8d 100644 --- a/uarch/machine-uarch-bridge-state-access.h +++ b/uarch/machine-uarch-bridge-state-access.h @@ -26,10 +26,11 @@ #include "i-prefer-shadow-state.h" #include "i-state-access.h" #include "machine-reg.h" -#include "mock-pma-entry.h" +#include "mock-address-range.h" #include "pma-constants.h" #include "riscv-constants.h" #include "shadow-pmas.h" +#include "shadow-tlb.h" #include "uarch-constants.h" #include "uarch-defines.h" #include "uarch-ecall.h" @@ -39,11 +40,6 @@ namespace cartesi { class machine_uarch_bridge_state_access; -// Type trait that should return the pma_entry type for a state access class -template <> -struct i_state_access_pma_entry { - using type = mock_pma_entry; -}; // Type trait that should return the fast_addr type for a state access class template <> struct i_state_access_fast_addr { @@ -57,10 +53,10 @@ class machine_uarch_bridge_state_access : public i_prefer_shadow_state { // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - std::array, PMA_MAX> &m_pmas; + mock_address_ranges &m_ars; public: - machine_uarch_bridge_state_access(std::array, PMA_MAX> &pmas) : m_pmas(pmas) {} + machine_uarch_bridge_state_access(std::array &ars) : m_ars(ars) {} machine_uarch_bridge_state_access(const machine_uarch_bridge_state_access &other) = default; machine_uarch_bridge_state_access(machine_uarch_bridge_state_access &&other) = default; machine_uarch_bridge_state_access &operator=(const machine_uarch_bridge_state_access &other) = delete; @@ -118,26 +114,32 @@ class machine_uarch_bridge_state_access : bool do_read_memory(uint64_t /*paddr*/, unsigned char * /*data*/, uint64_t /*length*/) const { // This is not implemented yet because it's not being used + assert(false && "read_memory() unexpectedly called"); abort(); return false; } bool do_write_memory(uint64_t /*paddr*/, const unsigned char * /*data*/, uint64_t /*length*/) const { // This is not implemented yet because it's not being used + assert(false && "write_memory() unexpectedly called"); abort(); return false; } - mock_pma_entry &do_read_pma_entry(uint64_t index) const { + address_range &do_read_pma(uint64_t index) const { + constexpr const auto throw_abort = [](const char * /*err*/) { + assert(false && "read_address_range() failed"); + abort(); + }; const uint64_t istart = bridge_read_pma_istart(index); const uint64_t ilength = bridge_read_pma_ilength(index); // NOLINTNEXTLINE(bugprone-narrowing-conversions) int i = static_cast(index); - if (!m_pmas[i]) { - m_pmas[i] = make_mock_pma_entry(index, istart, ilength, [](const char * /*err*/) { abort(); }); + if (std::holds_alternative(m_ars[i])) { + m_ars[i] = make_mock_address_range(istart, ilength, throw_abort); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - return m_pmas[i].value(); + return get_mock_address_range(m_ars[i], throw_abort); } uint64_t do_get_faddr(uint64_t paddr, uint64_t /* pma_index */) const { diff --git a/uarch/uarch-run.cpp b/uarch/uarch-run.cpp index d1b54dfa0..354a2f069 100644 --- a/uarch/uarch-run.cpp +++ b/uarch/uarch-run.cpp @@ -19,7 +19,7 @@ #include "compiler-defines.h" #include "interpret.h" #include "machine-uarch-bridge-state-access.h" -#include "mock-pma-entry.h" +#include "mock-address-range.h" #include "uarch-constants.h" #include "uarch-ecall.h" @@ -31,7 +31,7 @@ using namespace cartesi; // Let the state accessor be on static memory storage to speed up uarch initialization // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -std::array, PMA_MAX> pmas; +mock_address_ranges ars; namespace cartesi { @@ -43,7 +43,7 @@ extern template interpreter_break_reason interpret(machine_uarch_bridge_state_ac /// \brief Advances one mcycle by executing the "big machine interpreter" compiled to the microarchitecture /// \return This function never returns extern "C" NO_RETURN void interpret_next_mcycle_with_uarch() { - machine_uarch_bridge_state_access a(pmas); + machine_uarch_bridge_state_access a(ars); const uint64_t mcycle_end = a.read_mcycle() + 1; interpret(a, mcycle_end); // Finished executing a whole mcycle: halt the microarchitecture diff --git a/uarch/uarch-runtime.cpp b/uarch/uarch-runtime.cpp index 932544cef..0fa655c1e 100644 --- a/uarch/uarch-runtime.cpp +++ b/uarch/uarch-runtime.cpp @@ -26,6 +26,10 @@ extern "C" void __cxa_pure_virtual() { abort(); } +extern "C" int atexit(void (*)(void)) { + return 0; +} + // NOLINTNEXTLINE(cert-dcl54-cpp,misc-new-delete-overloads,hicpp-new-delete-operators) void operator delete(void * /*ptr*/) {} diff --git a/uarch/uarch-runtime.h b/uarch/uarch-runtime.h index dc28fdbfb..30fc05178 100644 --- a/uarch/uarch-runtime.h +++ b/uarch/uarch-runtime.h @@ -23,12 +23,22 @@ #include #include +#ifndef NDEBUG // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define assert(a) \ - if (!(a)) { \ - printf("Assertion failed\n"); \ - abort(); \ - } + do { \ + if (!(a)) { \ + printf("Assertion failed: %s, %s:%d, %s\n", #a, __FILE__, __LINE__, __func__); \ + abort(); \ + } \ + } while (0) +#else +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define assert(a) +#endif + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define fprintf(f, ...) printf(__VA_ARGS__) extern "C" NO_RETURN void abort();