blob: 42d22c5fc58c3da9ac512693700a7a27885b4c3c [file] [log] [blame]
/*
* 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_