| /* |
| * Copyright 2024 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef MPACT_CHERIOT__CHERIOT_STATE_H_ |
| #define MPACT_CHERIOT__CHERIOT_STATE_H_ |
| |
| #include <any> |
| #include <cstdint> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "mpact/sim/generic/arch_state.h" |
| #include "mpact/sim/generic/data_buffer.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/operand_interface.h" |
| #include "mpact/sim/generic/ref_count.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "mpact/sim/util/memory/memory_interface.h" |
| #include "mpact/sim/util/memory/tagged_memory_interface.h" |
| #include "riscv//riscv_csr.h" |
| #include "riscv//riscv_misa.h" |
| #include "riscv//riscv_state.h" |
| #include "riscv//riscv_xip_xie.h" |
| #include "riscv//riscv_xstatus.h" |
| |
| // This file defines the mpact_sim architectural state class for RiscV CHERIoT. |
| // It is very similar to the RiscVState class defined in mpact_riscv, but due |
| // to the changes to the register architecture driven by CHERIoT, a new class |
| // was defined instead of attempting to inherit from RiscVState. That being |
| // said, several types from mpact_riscv are re-used here. |
| |
| namespace mpact { |
| namespace sim { |
| namespace cheriot { |
| |
| using ::mpact::sim::generic::DataBuffer; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::generic::ReferenceCount; |
| using ::mpact::sim::riscv::InterruptCode; |
| using ::mpact::sim::riscv::IsaExtension; |
| using ::mpact::sim::riscv::PrivilegeMode; |
| using ::mpact::sim::riscv::RiscVCsrInterface; |
| using ::mpact::sim::riscv::RiscVCsrSet; |
| using ::mpact::sim::riscv::RiscVMIe; |
| using ::mpact::sim::riscv::RiscVMIp; |
| using ::mpact::sim::riscv::RiscVMIsa; |
| using ::mpact::sim::riscv::RiscVMStatus; |
| using ::mpact::sim::riscv::RiscVSimpleCsr; |
| |
| // Forward declare the CHERIoT register type. |
| class CheriotRegister; |
| class RiscVCheriotFPState; |
| |
| // CHERIoT exception codes. These are used in addition to the ones defined for |
| // vanilla RiscV. |
| |
| enum class ExceptionCode : uint32_t { |
| kCapExBoundsViolation = 0x01, |
| kCapExTagViolation = 0x02, |
| kCapExSealViolation = 0x03, |
| kCapExPermitExecuteViolation = 0x11, |
| kCapExPermitLoadViolation = 0x12, |
| kCapExPermitStoreViolation = 0x13, |
| kCapExPermitStoreCapabilityViolation = 0x15, |
| kCapExPermitStoreLocalCapabilityViolation = 0x16, |
| kCapExPermitAccessSystemRegistersViolation = 0x18, |
| }; |
| |
| // Load context used for capability tag loads. See below for the context type |
| // for capability loads. |
| struct CapabilityTagsLoadContext32 : public generic::ReferenceCount { |
| CapabilityTagsLoadContext32(DataBuffer *tags, CheriotRegister *dest) |
| : tags(tags), dest(dest) {} |
| ~CapabilityTagsLoadContext32() override { |
| if (tags != nullptr) tags->DecRef(); |
| } |
| |
| void OnRefCountIsZero() override { |
| if (tags != nullptr) tags->DecRef(); |
| tags = nullptr; |
| generic::ReferenceCount::OnRefCountIsZero(); |
| } |
| // Data buffer for the tags. One tag bit is stored in each byte. |
| DataBuffer *tags; |
| // The destination register. |
| CheriotRegister *dest; |
| }; |
| |
| // Load context used for capability loads. |
| struct CapabilityLoadContext32 : public generic::ReferenceCount { |
| CapabilityLoadContext32(DataBuffer *db, DataBuffer *tag_db, |
| uint32_t permissions, bool clear_tag) |
| : db(db), |
| tag_db(tag_db), |
| permissions(permissions), |
| clear_tag(clear_tag) {} |
| ~CapabilityLoadContext32() override { |
| if (db != nullptr) db->DecRef(); |
| if (tag_db != nullptr) tag_db->DecRef(); |
| } |
| |
| void OnRefCountIsZero() override { |
| if (db != nullptr) db->DecRef(); |
| db = nullptr; |
| if (tag_db != nullptr) tag_db->DecRef(); |
| tag_db = nullptr; |
| generic::ReferenceCount::OnRefCountIsZero(); |
| } |
| |
| // Data buffer for the memory content. |
| DataBuffer *db; |
| // Data buffer for the tags. One tag bit is stored in each byte. |
| DataBuffer *tag_db; |
| // The permissions of the capability used for the load. |
| uint32_t permissions; |
| // If true, clear the tag upon writing the result to the capability register. |
| bool clear_tag; |
| }; |
| |
| class CheriotState; |
| |
| // Forward declare a template function defined in the .cc file that is |
| // a friend of the state class. |
| template <typename T> |
| void CreateCsrs(CheriotState *, std::vector<RiscVCsrInterface *> &); |
| |
| class RiscVCheri32PcSourceOperand; |
| |
| using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error). |
| |
| class CheriotState : public generic::ArchState { |
| public: |
| static int constexpr kCapRegQueueSizeMask = 0x11; |
| static constexpr uint32_t kCheriExceptionCode = 0x1c; |
| static constexpr char kCregPrefix[] = "c"; |
| static constexpr char kFregPrefix[] = "f"; |
| static constexpr char kXregPrefix[] = "x"; |
| static constexpr char kCsrName[] = "csr"; |
| friend void CreateCsrs<uint32_t>(CheriotState *, |
| std::vector<RiscVCsrInterface *> &); |
| friend void CreateCsrs<uint64_t>(CheriotState *, |
| std::vector<RiscVCsrInterface *> &); |
| // Memory footprint of a capability register. |
| static constexpr int kCapabilitySizeInBytes = 8; |
| // Pc name. |
| static constexpr char kPcName[] = "pcc"; |
| // Constructors and destructor. |
| CheriotState(std::string_view id, util::TaggedMemoryInterface *memory, |
| util::AtomicMemoryOpInterface *atomic_memory); |
| CheriotState(std::string_view id, util::TaggedMemoryInterface *memory) |
| : CheriotState(id, memory, nullptr) {} |
| explicit CheriotState(std::string_view id) |
| : CheriotState(id, nullptr, nullptr) {} |
| ~CheriotState() override; |
| |
| // Deleted constructors and operators. |
| CheriotState(const CheriotState &) = delete; |
| CheriotState &operator=(const CheriotState &) = delete; |
| CheriotState(CheriotState &&) = delete; |
| CheriotState &operator=(CheriotState &&) = delete; |
| |
| // Reset all registers and CSRs to initial values. |
| void Reset(); |
| // Return a pair consisting of pointer to the named register and a bool that |
| // is true if the register had to be created, and false if it was found |
| // in the register map (or if nullptr is returned). |
| template <typename RegisterType> |
| std::pair<RegisterType *, bool> GetRegister(absl::string_view name) { |
| // If the register already exists, return a pointer to the register. |
| auto ptr = registers()->find(std::string(name)); |
| if (ptr != registers()->end()) |
| return std::make_pair(static_cast<RegisterType *>(ptr->second), false); |
| // Create a new register and return a pointer to the object. |
| return std::make_pair(AddRegister<RegisterType>(name), true); |
| } |
| |
| // Add register alias. |
| template <typename RegisterType> |
| absl::Status AddRegisterAlias(absl::string_view current_name, |
| absl::string_view new_name) { |
| auto ptr = registers()->find(std::string(current_name)); |
| if (ptr == registers()->end()) { |
| return absl::NotFoundError( |
| absl::StrCat("Register '", current_name, "' does not exist.")); |
| } |
| AddRegister(new_name, ptr->second); |
| return absl::OkStatus(); |
| } |
| // This is called by instruction semantic functions to register a CHERIoT |
| // specific exception. |
| void HandleCheriRegException(const Instruction *inst, uint64_t epc, |
| ExceptionCode code, const CheriotRegister *reg); |
| |
| // Methods called by instruction semantic functions to load from memory. |
| void LoadMemory(const Instruction *inst, uint64_t address, DataBuffer *db, |
| Instruction *child_inst, ReferenceCount *context); |
| void LoadMemory(const Instruction *inst, DataBuffer *address_db, |
| DataBuffer *mask_db, int el_size, DataBuffer *db, |
| Instruction *child_inst, ReferenceCount *context); |
| // Methods called by instruction semantic functions to store to memory. |
| void StoreMemory(const Instruction *inst, uint64_t address, DataBuffer *db); |
| void StoreMemory(const Instruction *inst, DataBuffer *address_db, |
| DataBuffer *mask_db, int el_size, DataBuffer *db); |
| // Methods called by instruction semantic functions to load and store |
| // capabilities. |
| void LoadCapability(const Instruction *instruction, uint32_t address, |
| DataBuffer *db, DataBuffer *tags, Instruction *child, |
| CapabilityLoadContext32 *context); |
| void StoreCapability(const Instruction *instruction, uint32_t address, |
| DataBuffer *db, DataBuffer *tags); |
| |
| // Debug memory methods. |
| void DbgLoadMemory(uint64_t address, DataBuffer *db); |
| // Called by the fence instruction semantic function to signal a fence |
| // operation. |
| void Fence(const Instruction *inst, int fm, int predecessor, int successor); |
| // Synchronize instruction and data streams. |
| void FenceI(const Instruction *inst); |
| // System call. |
| void ECall(const Instruction *inst); |
| // Breakpoint. |
| void EBreak(const Instruction *inst); |
| // WFI |
| void WFI(const Instruction *inst); |
| // Ceases execution on the core. This is a non-standard instruction that |
| // quiesces traffic for embedded cores before halting. The core must be reset |
| // to come out of this state. |
| void Cease(const Instruction *inst); |
| // This method is called to trigger a RiscV trap. |
| void Trap(bool is_interrupt, uint64_t trap_value, uint64_t exception_code, |
| uint64_t epc, const Instruction *inst); |
| // Add ebreak handler. |
| void AddEbreakHandler(absl::AnyInvocable<bool(const Instruction *)> handler) { |
| on_ebreak_.emplace_back(std::move(handler)); |
| } |
| // This function is called after any event that may have caused an interrupt |
| // to be registered as pending or enabled. If the interrupt can be taken |
| // it registers it as available. |
| void CheckForInterrupt() override; |
| // This function is called when the return pc for the available interrupt |
| // is known. If there is no available interrupt, it just returns. |
| void TakeAvailableInterrupt(uint64_t epc); |
| |
| // Indicates that the program has returned from handling an interrupt. This |
| // decrements the interrupt handler depth and should be called by the |
| // implementations of mret, sret, and uret. |
| void SignalReturnFromInterrupt() { --interrupt_handler_depth_; } |
| |
| // Returns the depth of the interrupt handler currently being executed, or |
| // zero if no interrupt handler is being executed. |
| int InterruptHandlerDepth() const { return interrupt_handler_depth_; } |
| |
| // Returns true if a capability register with the given base should be |
| // revoked. |
| bool MustRevoke(uint32_t address) const; |
| |
| // Accessors. |
| // Returns true if an interrupt is available for the core to take or false |
| // otherwise. |
| inline bool is_interrupt_available() const { return is_interrupt_available_; } |
| // Resets the is_interrupt_available flag to false. This should only be called |
| // when resetting the RISCV core, as 'is_interrupt_available' is Normally |
| // reset during the interrupt handling flow. |
| inline void reset_is_interrupt_available() { |
| is_interrupt_available_ = false; |
| } |
| void set_memory(util::TaggedMemoryInterface *tagged_memory) { |
| tagged_memory_ = tagged_memory; |
| } |
| util::TaggedMemoryInterface *tagged_memory() const { return tagged_memory_; } |
| util::AtomicMemoryOpInterface *atomic_tagged_memory() const { |
| return atomic_tagged_memory_; |
| } |
| void set_atomic_tagged_memory( |
| util::AtomicMemoryOpInterface *atomic_tagged_memory) { |
| atomic_tagged_memory_ = atomic_tagged_memory; |
| } |
| |
| void set_branch(bool value) { branch_ = value; } |
| bool branch() const { return branch_; } |
| |
| void set_max_physical_address(uint64_t max_physical_address); |
| uint64_t max_physical_address() const { return max_physical_address_; } |
| void set_min_physical_address(uint64_t min_physical_address); |
| uint64_t min_physical_address() const { return min_physical_address_; } |
| // These root capabilities are clean versions of each type of capability with |
| // maximum permissions. |
| const CheriotRegister *executable_root() const { return executable_root_; } |
| const CheriotRegister *sealing_root() const { return sealing_root_; } |
| const CheriotRegister *memory_root() const { return memory_root_; } |
| // Floating point state. |
| RiscVCheriotFPState *rv_fp() const { return rv_fp_; } |
| void set_rv_fp(RiscVCheriotFPState *value) { rv_fp_ = value; } |
| // Special capability registers. Pcc replaces the pc. Cgp is a global pointer |
| // capability that is aliased with c3. |
| CheriotRegister *pcc() const { return pcc_; } |
| CheriotRegister *cgp() const { return cgp_; } |
| // True if the misa register encodes support for compact instructions. |
| bool has_compact() const { |
| return (misa_->AsUint64() & *IsaExtension::kCompressed) != 0; |
| } |
| // Returns the number of tags that can be loaded in a single load tags |
| // instruction. |
| int num_tags_per_load() const { return num_tags_per_load_; } |
| // Provides access to the set of CSRs of this architectural state instance. |
| RiscVCsrSet *csr_set() { return csr_set_; } |
| // Setters for handlers for ecall, and trap. The handler returns true |
| // if the instruction/event was handled, and false otherwise. |
| |
| void set_on_ecall(absl::AnyInvocable<bool(const Instruction *)> callback) { |
| on_ecall_ = std::move(callback); |
| } |
| |
| void set_on_wfi(absl::AnyInvocable<bool(const Instruction *)> callback) { |
| on_wfi_ = std::move(callback); |
| } |
| |
| void set_on_cease(absl::AnyInvocable<bool(const Instruction *)> callback) { |
| on_cease_ = std::move(callback); |
| } |
| |
| void set_on_trap( |
| absl::AnyInvocable<bool(bool /*is_interrupt*/, uint64_t /*trap_value*/, |
| uint64_t /*exception_code*/, uint64_t /*epc*/, |
| const Instruction *)> |
| callback) { |
| on_trap_ = std::move(callback); |
| } |
| |
| RiscVMStatus *mstatus() { return mstatus_; } |
| RiscVMIsa *misa() { return misa_; } |
| RiscVMIp *mip() { return mip_; } |
| RiscVMIe *mie() { return mie_; } |
| CheriotRegister *mtcc() { return mtcc_; } |
| CheriotRegister *mepcc() { return mepcc_; } |
| CheriotRegister *mscratchc() { return mscratchc_; } |
| CheriotRegister *mtdc() { return mtdc_; } |
| CheriotRegister *temp_reg() { return temp_reg_; } |
| RiscVCsrInterface *mcause() { return mcause_; } |
| RiscVCheri32PcSourceOperand *pc_src_operand() { return pc_src_operand_; } |
| |
| uint64_t revocation_mem_base() const { return revocation_mem_base_; } |
| uint64_t revocation_ram_base() const { return revocation_ram_base_; } |
| |
| // Tracing accessors. |
| bool tracing_active() const { return tracing_active_; } |
| void set_tracing_active(bool active) { tracing_active_ = active; } |
| uint64_t load_address() const { return load_address_; } |
| DataBuffer *load_db() const { return load_db_; } |
| void set_load_db(DataBuffer *db) { load_db_ = db; } |
| DataBuffer *load_tags() const { return load_tags_; } |
| void set_load_tags(DataBuffer *tags) { load_tags_ = tags; } |
| uint64_t store_address() const { return store_address_; } |
| DataBuffer *store_db() const { return store_db_; } |
| void set_store_db(DataBuffer *db) { store_db_ = db; } |
| DataBuffer *store_tags() const { return store_tags_; } |
| void set_store_tags(DataBuffer *tags) { store_tags_ = tags; } |
| |
| private: |
| InterruptCode PickInterrupt(uint32_t interrupts); |
| // A map from register name to entry in the mtval register. |
| absl::flat_hash_map<std::string, uint32_t> cap_index_map_; |
| // These are root capabilities |
| CheriotRegister *executable_root_ = nullptr; |
| CheriotRegister *sealing_root_ = nullptr; |
| CheriotRegister *memory_root_ = nullptr; |
| // Special capability registers. |
| CheriotRegister *pcc_ = nullptr; |
| CheriotRegister *cgp_ = nullptr; |
| bool branch_ = false; |
| uint64_t max_physical_address_; |
| uint64_t min_physical_address_ = 0; |
| RiscVCheriotFPState *rv_fp_ = nullptr; |
| int num_tags_per_load_; |
| util::TaggedMemoryInterface *owned_tagged_memory_ = nullptr; |
| util::TaggedMemoryInterface *tagged_memory_; |
| util::AtomicMemoryOpInterface *atomic_tagged_memory_; |
| RiscVCsrSet *csr_set_; |
| std::vector<absl::AnyInvocable<bool(const Instruction *)>> on_ebreak_; |
| absl::AnyInvocable<bool(const Instruction *)> on_ecall_; |
| absl::AnyInvocable<bool(bool, uint64_t, uint64_t, uint64_t, |
| const Instruction *)> |
| on_trap_; |
| absl::AnyInvocable<bool(const Instruction *)> on_wfi_; |
| absl::AnyInvocable<bool(const Instruction *)> on_cease_; |
| std::vector<RiscVCsrInterface *> csr_vec_; |
| // For interrupt handling. |
| bool is_interrupt_available_ = false; |
| int interrupt_handler_depth_ = 0; |
| InterruptCode available_interrupt_code_ = InterruptCode::kNone; |
| // By default, execute in machine mode. |
| PrivilegeMode privilege_mode_ = PrivilegeMode::kMachine; |
| // Handles to frequently used CSRs. |
| RiscVMStatus *mstatus_ = nullptr; |
| RiscVMIsa *misa_ = nullptr; |
| RiscVMIp *mip_ = nullptr; |
| RiscVMIe *mie_ = nullptr; |
| RiscVSimpleCsr<uint32_t> *mshwm_ = nullptr; |
| RiscVSimpleCsr<uint32_t> *mshwmb_ = nullptr; |
| CheriotRegister *mtcc_ = nullptr; |
| CheriotRegister *mepcc_ = nullptr; |
| CheriotRegister *mscratchc_ = nullptr; |
| CheriotRegister *mtdc_ = nullptr; |
| CheriotRegister *temp_reg_ = nullptr; |
| RiscVCsrInterface *mtval_ = nullptr; |
| RiscVCsrInterface *mcause_ = nullptr; |
| RiscVCheri32PcSourceOperand *pc_src_operand_ = nullptr; |
| // DataBuffer and info used to check for revocation. |
| DataBuffer *revocation_db_ = nullptr; |
| uint64_t revocation_mem_base_; |
| uint64_t revocation_ram_base_; |
| // Active tracing flag. |
| bool tracing_active_ = false; |
| // Members for collecting trace data. |
| uint64_t load_address_; |
| DataBuffer *load_db_ = nullptr; |
| DataBuffer *load_tags_ = nullptr; |
| uint64_t store_address_; |
| DataBuffer *store_db_ = nullptr; |
| DataBuffer *store_tags_ = nullptr; |
| }; |
| |
| // This class implements the source operand interface on top of a capability |
| // register so that its value (contained address) can be read as an operand. |
| class RiscVCheri32PcSourceOperand : public generic::SourceOperandInterface { |
| public: |
| explicit RiscVCheri32PcSourceOperand(CheriotState *state) : state_(state) {} |
| RiscVCheri32PcSourceOperand() = delete; |
| RiscVCheri32PcSourceOperand(const RiscVCheri32PcSourceOperand &) = delete; |
| RiscVCheri32PcSourceOperand &operator=(const RiscVCheri32PcSourceOperand &) = |
| delete; |
| ~RiscVCheri32PcSourceOperand() override = default; |
| // Methods for accessing the nth value element. |
| bool AsBool(int index) override { return static_cast<bool>(GetPC()); } |
| int8_t AsInt8(int index) override { return static_cast<int8_t>(GetPC()); } |
| uint8_t AsUint8(int index) override { return static_cast<uint8_t>(GetPC()); } |
| int16_t AsInt16(int index) override { return static_cast<int16_t>(GetPC()); } |
| uint16_t AsUint16(int) override { return static_cast<uint16_t>(GetPC()); } |
| int32_t AsInt32(int index) override { return static_cast<int32_t>(GetPC()); } |
| uint32_t AsUint32(int index) override { |
| return static_cast<uint32_t>(GetPC()); |
| } |
| int64_t AsInt64(int index) override { return static_cast<int64_t>(GetPC()); } |
| uint64_t AsUint64(int index) override { return GetPC(); } |
| |
| // Return a pointer to the object instance that implements the state in |
| // question (or nullptr) if no such object "makes sense". This is used if |
| // the object requires additional manipulation - such as a fifo that needs |
| // to be popped. If no such manipulation is required, nullptr should be |
| // returned. |
| std::any GetObject() const override { return std::any(state_->pcc()); } |
| // Return the shape of the operand (the number of elements in each dimension). |
| // For instance {1} indicates a scalar quantity, whereas {128} indicates an |
| // 128 element vector quantity. |
| std::vector<int> shape() const override { return {1}; }; |
| // Return a string representation of the operand suitable for display in |
| // disassembly. |
| std::string AsString() const override { return "PC"; }; |
| |
| private: |
| uint64_t GetPC(); |
| CheriotState *state_; |
| }; |
| |
| } // namespace cheriot |
| } // namespace sim |
| } // namespace mpact |
| |
| #endif // MPACT_CHERIOT__CHERIOT_STATE_H_ |