blob: 6dc3ca0d881d5793059dcf246ea59111cd0cdf0a [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.
#include "cheriot/cheriot_state.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <new>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "cheriot/cheriot_register.h"
#include "cheriot/riscv_cheriot_csr_enum.h"
#include "mpact/sim/generic/arch_state.h"
#include "mpact/sim/generic/type_helpers.h"
#include "mpact/sim/util/memory/memory_interface.h"
#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
#include "mpact/sim/util/memory/tagged_memory_interface.h"
#include "riscv//riscv_counter_csr.h"
#include "riscv//riscv_csr.h"
#include "riscv//riscv_misa.h"
#include "riscv//riscv_pmp.h"
#include "riscv//riscv_state.h"
ABSL_FLAG(uint64_t, revocation_ram_base, 0x8000'0000,
"Ram base for revocation.");
ABSL_FLAG(uint64_t, revocation_mem_base, 0x8300'0000,
"Revocation memory base.");
namespace mpact {
namespace sim {
namespace cheriot {
using EC = ::mpact::sim::riscv::ExceptionCode;
using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error).
using ::mpact::sim::riscv::IsaExtension;
using ::mpact::sim::riscv::RiscVCounterCsr;
using ::mpact::sim::riscv::RiscVCounterCsrHigh;
using ::mpact::sim::riscv::RiscVCsrEnum;
using ::mpact::sim::riscv::RiscVCsrInterface;
using ::mpact::sim::riscv::RiscVPmp;
using ::mpact::sim::riscv::RiscVSimpleCsr;
using ::mpact::sim::riscv::RiscVXlen;
// These helper templates are used to store information about the CSR registers
// used in CHERIoT RiscV (32 bits).
template <typename T>
struct CsrInfo {};
template <>
struct CsrInfo<uint32_t> {
using T = uint32_t;
static constexpr T kMhartidRMask = std::numeric_limits<T>::max();
static constexpr T kMhartidWMask = 0;
static constexpr T kMstatusInitialValue = 0x1800;
static constexpr T kMisaInitialValue =
(*RiscVXlen::RV32 << 30) | *IsaExtension::kIntegerMulDiv |
*IsaExtension::kRVIBaseIsa | *IsaExtension::kGExtension |
*IsaExtension::kSinglePrecisionFp | *IsaExtension::kDoublePrecisionFp |
*IsaExtension::kCompressed | *IsaExtension::kAtomic |
*IsaExtension::kSupervisorMode;
static constexpr T kMisaRMask = 0xc3ff'ffff;
static constexpr T kMisaWMask = 0x0;
};
// Three templated helper functions used to create individual CSRs.
// This creates the CSR and assigns it to a pointer in the state object. Type
// can be inferred from the state object pointer.
template <typename T, typename... Ps>
T *CreateCsr(CheriotState *state, T *&ptr,
std::vector<RiscVCsrInterface *> &csr_vec, Ps... pargs) {
auto *csr = new T(pargs...);
auto result = state->csr_set()->AddCsr(csr);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
"': ", result.message());
delete csr;
return nullptr;
}
csr_vec.push_back(csr);
ptr = csr;
return csr;
}
// This creates the CSR and assigns it to a pointer in the state object, however
// that pointer is of abstract type, so the CSR type cannot be inferred, but
// has to be specified in the call.
template <typename T, typename... Ps>
T *CreateCsr(CheriotState *state, RiscVCsrInterface *&ptr,
std::vector<RiscVCsrInterface *> &csr_vec, Ps... pargs) {
auto *csr = new T(pargs...);
auto result = state->csr_set()->AddCsr(csr);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
"': ", result.message());
delete csr;
return nullptr;
}
csr_vec.push_back(csr);
ptr = csr;
return csr;
}
// This creates the CSR, but does not assign it to a pointer in the state
// object. That means the type cannot be inferred, but has to be specified
// in the call.
template <typename T, typename... Ps>
T *CreateCsr(CheriotState *state, std::vector<RiscVCsrInterface *> &csr_vec,
Ps... pargs) {
auto *csr = new T(pargs...);
auto result = state->csr_set()->AddCsr(csr);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
"': ", result.message());
delete csr;
return nullptr;
}
csr_vec.push_back(csr);
return csr;
}
// Templated helper function that is used to create the set of CSRs needed
// for simulation.
template <typename T>
void CreateCsrs(CheriotState *state,
std::vector<RiscVCsrInterface *> &csr_vec) {
absl::Status result;
// Create CSRs.
// misa
auto *misa = CreateCsr(state, state->misa_, csr_vec,
CsrInfo<T>::kMisaInitialValue, state);
CHECK_NE(misa, nullptr);
// mtvec is replaced by mtcc
// mcause
CHECK_NE(
CreateCsr<RiscVSimpleCsr<T>>(state, state->mcause_, csr_vec, "mcause",
RiscVCsrEnum::kMCause, 0, state),
nullptr);
// Mip and Mie are always 32 bit.
// mip
auto *mip = CreateCsr(state, state->mip_, csr_vec, 0, state);
CHECK_NE(mip, nullptr);
// mie
auto *mie = CreateCsr(state, state->mie_, csr_vec, 0, state);
CHECK_NE(mie, nullptr);
// mhartid
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(
state, csr_vec, "mhartid", *RiscVCheriotCsrEnum::kMHartId, 0,
CsrInfo<T>::kMhartidRMask, CsrInfo<T>::kMhartidWMask, state),
nullptr);
// mepc is replaced by mepcc
// mscratch
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "mscratch",
*RiscVCsrEnum::kMScratch, 0, state),
nullptr);
// medeleg - machine mode exception delegation register. Not used.
// mideleg - machine mode interrupt delegation register. Not used.
// mstatus
auto *mstatus =
CreateCsr(state, state->mstatus_, csr_vec,
CsrInfo<uint32_t>::kMstatusInitialValue, state, misa);
CHECK_NE(mstatus, nullptr);
// mtval
auto *mtval = CreateCsr<RiscVSimpleCsr<T>>(
state, state->mtval_, csr_vec, "mtval", *RiscVCsrEnum::kMTval, 0, state);
CHECK_NE(mtval, nullptr);
// minstret/minstreth
auto *minstret = CreateCsr<RiscVCounterCsr<T, CheriotState>>(
state, csr_vec, "minstret", RiscVCsrEnum ::kMInstret, state);
CHECK_NE(minstret, nullptr);
if (sizeof(T) == sizeof(uint32_t)) {
CHECK_NE(CreateCsr<RiscVCounterCsrHigh<CheriotState>>(
state, csr_vec, "minstreth", RiscVCsrEnum::kMInstretH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, CheriotState> *>(
minstret)),
nullptr);
}
// mcycle/mcycleh
auto *mcycle = CreateCsr<RiscVCounterCsr<T, CheriotState>>(
state, csr_vec, "mcycle", RiscVCsrEnum::kMCycle, state);
CHECK_NE(mcycle, nullptr);
if (sizeof(T) == sizeof(uint32_t)) {
CHECK_NE(CreateCsr<RiscVCounterCsrHigh<CheriotState>>(
state, csr_vec, "mcycleh", RiscVCsrEnum::kMCycleH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, CheriotState> *>(
mcycle)),
nullptr);
}
// Stack high water mark CSRs. Mshwm gets updated automatically during
// execution. mshwm
auto *mshwm = CreateCsr<RiscVSimpleCsr<T>>(
state, state->mshwm_, csr_vec, "mshwm", *RiscVCheriotCsrEnum::kMshwm,
/*initial_value=*/0,
/*read_mask=*/0xffff'fff0, /*write_mask=*/0xffff'fff0, state);
CHECK_NE(mshwm, nullptr);
// mshwmb
auto *mshwmb = CreateCsr<RiscVSimpleCsr<T>>(
state, state->mshwmb_, csr_vec, "mshwmb", *RiscVCheriotCsrEnum::kMshwmb,
/*initial_value=*/0,
/*read_mask=*/0xffff'fff0, /*write_mask=*/0xffff'fff0, state);
CHECK_NE(mshwmb, nullptr);
// mccsr.
auto *mccsr = CreateCsr<RiscVSimpleCsr<T>>(
state, csr_vec, "mccsr", *RiscVCheriotCsrEnum::kMCcsr,
/*initial_value=*/0x3,
/*read_mask=*/0x3, /*write_mask=*/0x2, state);
CHECK_NE(mccsr, nullptr);
// Supervisor level CSRs
// None in CHERIoT.
// User level CSRs
// None in CHERIoT.
// PMP CSRs
state->pmp_ = new RiscVPmp(state);
state->pmp_->CreatePmpCsrs<T, RiscVCheriotCsrEnum>(state->csr_set());
// Simulator CSRs
// Access current privilege mode. Omitted.
}
// This value is in the RV32ISA manual to support MMU, although in "BARE" mode
// only the bottom 32-bit is valid.
constexpr uint64_t kRiscv32MaxMemorySize = 0x3f'ffff'ffffULL;
CheriotState::CheriotState(std::string_view id,
util::TaggedMemoryInterface *memory,
util::AtomicMemoryOpInterface *atomic_memory)
: generic::ArchState(id),
tagged_memory_(memory),
atomic_tagged_memory_(atomic_memory),
counter_interrupts_taken_("interrupts_taken", 0),
counter_interrupt_returns_("interrupt_returns", 0) {
for (auto &[name, index] : std::vector<std::pair<std::string, unsigned>>{
{"c0", 0b0'00000}, {"c1", 0b0'00001}, {"c2", 0b0'00010},
{"c3", 0b0'00011}, {"c4", 0b0'00100}, {"c5", 0b0'00101},
{"c6", 0b0'00110}, {"c7", 0b0'00111}, {"c8", 0b0'01000},
{"c9", 0b0'01001}, {"c10", 0b0'01010}, {"c11", 0b0'01011},
{"c12", 0b0'01100}, {"c13", 0b0'01101}, {"c14", 0b0'01110},
{"c15", 0b0'01111}, {"c16", 0b0'10000}, {"c17", 0b0'10001},
{"c18", 0b0'10010}, {"c19", 0b0'10011}, {"c20", 0b0'10100},
{"c21", 0b0'10101}, {"c22", 0b0'10110}, {"c23", 0b0'10111},
{"c24", 0b0'11000}, {"c25", 0b0'11001}, {"c26", 0b0'11010},
{"c27", 0b0'11011}, {"c28", 0b0'11100}, {"c29", 0b0'11101},
{"c30", 0b0'11110}, {"c31", 0b0'11111}, {"pcc", 0b1'00000},
{"mtcc", 0b1'11100}, {"mtdc", 0b1'11101}, {"mscratchc", 0b1'11110},
{"mepcc", 0b1'11111}}) {
cap_index_map_.emplace(name, index);
}
CHECK_OK(AddCounter(&counter_interrupts_taken_));
CHECK_OK(AddCounter(&counter_interrupt_returns_));
// Create root capabilities and the special capability CSRs.
executable_root_ = new CheriotRegister(this, "executable_root");
executable_root_->ResetExecuteRoot();
sealing_root_ = new CheriotRegister(this, "sealing_root");
sealing_root_->ResetSealingRoot();
memory_root_ = new CheriotRegister(this, "memory_root");
memory_root_->ResetMemoryRoot();
mtcc_ = AddRegister<CheriotRegister>("mtcc");
mtcc_->ResetExecuteRoot();
mepcc_ = AddRegister<CheriotRegister>("mepcc");
mepcc_->ResetExecuteRoot();
mtdc_ = AddRegister<CheriotRegister>("mtdc");
mtdc_->ResetMemoryRoot();
mscratchc_ = AddRegister<CheriotRegister>("mscratchc");
mscratchc_->ResetSealingRoot();
pcc_ = AddRegister<CheriotRegister>("pcc");
auto status = AddRegisterAlias<CheriotRegister>("pcc", "pc");
if (!status.ok()) {
LOG(ERROR) << "Error creating pc alias of 'pcc': " << status.message();
return;
}
pcc_->ResetExecuteRoot();
temp_reg_ = new CheriotRegister(this, "temp_reg");
// Add the general capability registers.
for (int i = 0; i < 32; i++) {
AddRegister<CheriotRegister>(absl::StrCat("c", i));
}
status = AddRegisterAlias<CheriotRegister>("c3", "cgp");
if (!status.ok()) {
LOG(ERROR) << "Error creating cgp alias of 'c3': " << status.message();
return;
}
auto [cgp_reg, unused] = GetRegister<CheriotRegister>("cgp");
cgp_ = cgp_reg;
// Create the other CSRs.
csr_set_ = new RiscVCsrSet();
CreateCsrs<uint32_t>(this, csr_vec_);
pc_src_operand_ = new RiscVCheri32PcSourceOperand(this);
set_pc_operand(pc_src_operand_);
// Create the revocation data buffer.
revocation_db_ = db_factory()->Allocate<uint8_t>(1);
revocation_ram_base_ = absl::GetFlag(FLAGS_revocation_ram_base);
revocation_mem_base_ = absl::GetFlag(FLAGS_revocation_mem_base);
set_max_physical_address(kRiscv32MaxMemorySize);
}
CheriotState::~CheriotState() {
delete executable_root_;
delete sealing_root_;
delete memory_root_;
delete pc_src_operand_;
for (auto *csr : csr_vec_) delete csr;
delete csr_set_;
delete pmp_;
delete temp_reg_;
revocation_db_->DecRef();
}
void CheriotState::Reset() {
for (auto &[unused, reg_ptr] : *registers()) {
reg_ptr->data_buffer()->Set<uint32_t>(0, 0);
}
// Reset CSRs.
pcc_->ResetExecuteRoot();
mtcc_->ResetExecuteRoot();
mepcc_->ResetExecuteRoot();
mtdc_->ResetMemoryRoot();
mscratchc_->ResetSealingRoot();
mstatus_->Set(CsrInfo<uint32_t>::kMstatusInitialValue);
mtval_->Set(0UL);
mshwm_->Set(0UL);
mshwmb_->Set(0UL);
mip_->Set(0UL);
mie_->Set(0UL);
csr_set_->GetCsr("minstret").value()->Set(0UL);
csr_set_->GetCsr("minstreth").value()->Set(0UL);
csr_set_->GetCsr("mcause").value()->Set(0UL);
csr_set_->GetCsr("misa").value()->Set(CsrInfo<uint32_t>::kMisaInitialValue);
}
void CheriotState::HandleCheriRegException(const Instruction *instruction,
uint64_t epc, ExceptionCode code,
const CheriotRegister *reg) {
// Map the CHERIoT exception to a RiscV trap.
unsigned mcause = kCheriExceptionCode;
uint32_t mtval = *code & 0b1'1111;
auto const &name = reg->name();
auto iter = cap_index_map_.find(name);
uint32_t cap_index = 0x1f; // Set to a default value.
if (iter != cap_index_map_.end()) {
cap_index = iter->second;
}
mtval |= cap_index << 5;
Trap(/*is_interrupt*/ false, mtval, mcause, epc, instruction);
}
void CheriotState::set_max_physical_address(uint64_t max_physical_address) {
max_physical_address_ = std::min(max_physical_address, kRiscv32MaxMemorySize);
}
void CheriotState::set_min_physical_address(uint64_t min_physical_address) {
min_physical_address_ = std::min(min_physical_address, max_physical_address_);
}
void CheriotState::LoadCapability(const Instruction *instruction,
uint32_t address, DataBuffer *db,
DataBuffer *tags, Instruction *child,
CapabilityLoadContext32 *context) {
// Check for alignment.
uint64_t mask = db->size<uint8_t>() - 1;
if ((address & mask) != 0) {
Trap(/*is_interrupt*/ false, address, *EC::kLoadAddressMisaligned,
instruction == nullptr ? 0 : instruction->address(), instruction);
return;
}
// Check for physical address violation.
if (address < min_physical_address_ || address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *EC::kLoadAccessFault,
instruction == nullptr ? 0 : instruction->address(), instruction);
return;
}
// Forward the load.
tagged_memory_->Load(address, db, tags, child, context);
if (!tracing_active_) return;
load_address_ = address;
load_db_ = db;
load_db_->IncRef();
load_tags_ = tags;
load_tags_->IncRef();
}
void CheriotState::StoreCapability(const Instruction *instruction,
uint32_t address, DataBuffer *db,
DataBuffer *tags) {
// Check for alignment.
uint64_t mask = db->size<uint8_t>() - 1;
if ((address & mask) != 0) {
Trap(/*is_interrupt*/ false, address, *EC::kStoreAddressMisaligned,
instruction == nullptr ? 0 : instruction->address(), instruction);
return;
}
// Check for physical address violation.
if (address < min_physical_address_ || address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *EC::kStoreAccessFault,
instruction == nullptr ? 0 : instruction->address(), instruction);
return;
}
// Check for stack accesses relative to mshwm/mshwmb.
if ((address >= mshwmb_->GetUint32()) && (address < mshwm_->GetUint32())) {
mshwm_->Set(address);
}
// Forward the store.
tagged_memory_->Store(address, db, tags);
if (!tracing_active_) return;
store_address_ = address;
store_db_ = db;
store_db_->IncRef();
store_tags_ = tags;
store_tags_->IncRef();
}
void CheriotState::LoadMemory(const Instruction *inst, uint64_t address,
DataBuffer *db, Instruction *child_inst,
ReferenceCount *context) {
// Check for alignment.
uint64_t mask = db->size<uint8_t>() - 1;
if ((address & mask) != 0) {
Trap(/*is_interrupt*/ false, address, *EC::kLoadAddressMisaligned,
inst == nullptr ? 0 : inst->address(), inst);
return;
}
// Check for physical address violation.
if (address < min_physical_address_ || address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *EC::kLoadAccessFault,
inst == nullptr ? 0 : inst->address(), inst);
return;
}
// Forward the load.
tagged_memory_->Load(address, db, child_inst, context);
if (!tracing_active_) return;
load_address_ = address;
load_db_ = db;
load_db_->IncRef();
load_tags_ = nullptr;
}
void CheriotState::LoadMemory(const Instruction *inst, DataBuffer *address_db,
DataBuffer *mask_db, int el_size, DataBuffer *db,
Instruction *child_inst,
ReferenceCount *context) {
// For now, we don't check for alignment on vector memory accesses.
// Check for physical address violation.
for (auto address : address_db->Get<uint64_t>()) {
if (address < min_physical_address_ || address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *EC::kLoadAccessFault,
inst == nullptr ? 0 : inst->address(), inst);
return;
}
}
// Forward the load.
tagged_memory_->Load(address_db, mask_db, el_size, db, child_inst, context);
}
void CheriotState::StoreMemory(const Instruction *inst, uint64_t address,
DataBuffer *db) {
// Check for alignment.
uint64_t mask = db->size<uint8_t>() - 1;
if ((address & mask) != 0) {
Trap(/*is_interrupt*/ false, address, *EC::kStoreAddressMisaligned,
inst == nullptr ? 0 : inst->address(), inst);
return;
}
// Check for physical address violation.
if (address < min_physical_address_ || address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *EC::kStoreAccessFault,
inst == nullptr ? 0 : inst->address(), inst);
return;
}
// Check for stack accesses relative to mshwm/mshwmb.
uint32_t address32 = static_cast<uint32_t>(address);
if ((address32 >= mshwmb_->GetUint32()) &&
(address32 < mshwm_->GetUint32())) {
mshwm_->Set(address32);
}
// Forward the store.
tagged_memory_->Store(address, db);
if (!tracing_active_) return;
store_address_ = address;
store_db_ = db;
store_db_->IncRef();
store_tags_ = nullptr;
}
void CheriotState::StoreMemory(const Instruction *inst, DataBuffer *address_db,
DataBuffer *mask_db, int el_size,
DataBuffer *db) {
// Ignore alignment check for vector memory accesses.
// Check for physical address violation.
for (auto address : address_db->Get<uint64_t>()) {
if (address < min_physical_address_ || address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *EC::kStoreAccessFault,
inst == nullptr ? 0 : inst->address(), inst);
return;
}
}
// Check for stack accesses relative to mshwm/mshwmb.
for (auto address : address_db->Get<uint64_t>()) {
uint32_t address32 = static_cast<uint32_t>(address);
if ((address32 >= mshwmb_->GetUint32()) &&
(address32 < mshwm_->GetUint32())) {
mshwm_->Set(address32);
}
}
// Forward the store.
tagged_memory_->Store(address_db, mask_db, el_size, db);
}
void CheriotState::DbgStoreMemory(uint64_t address, DataBuffer *db) {
tagged_memory_->Store(address, db);
}
void CheriotState::DbgLoadMemory(uint64_t address, DataBuffer *db) {
tagged_memory_->Load(address, db, nullptr, nullptr);
}
void CheriotState::Fence(const Instruction *inst, int fm, int predecessor,
int successor) {
// TODO: Add fence operation once operations have non-zero latency.
}
void CheriotState::FenceI(const Instruction *inst) {
// TODO: Add instruction fence operation when needed.
}
void CheriotState::ECall(const Instruction *inst) {
// If there is a handler, call it.
if (on_ecall_ != nullptr) {
auto res = on_ecall_(inst);
// If the handler returns true, the ecall has been handled, just return.
if (res) return;
}
// Otherwise trap.
std::string where = (inst != nullptr)
? absl::StrCat(absl::Hex(inst->address()))
: "unknown location";
EC code;
code = EC::kEnvCallFromMMode;
uint64_t epc = inst->address();
Trap(/*is_interrupt*/ false, 0, *code, epc, inst);
}
void CheriotState::EBreak(const Instruction *inst) {
// Call the handlers.
for (auto &handler : on_ebreak_) {
bool res = handler(inst);
// If a handler returns true, the ebreak has been handled. Just return.
if (res) return;
}
// Otherwise trap.
// Set the return address to the current instruction.
auto epc = (inst != nullptr) ? inst->address() : 0;
Trap(/*is_interrupt=*/false, /*trap_value=*/epc, 3, epc, inst);
}
void CheriotState::WFI(const Instruction *inst) {
// Call the handler.
if (on_wfi_ != nullptr) {
bool res = on_wfi_(inst);
// If the handler returns true, the wfi has been handled. Just return.
if (res) return;
}
// If no handler is specified, or if no handlers returns true, treat it
// as a nop.
std::string where = (inst != nullptr)
? absl::StrCat(absl::Hex(inst->address()))
: "unknown location";
LOG(INFO) << "No handler for wfi: treating as nop: " << where;
}
void CheriotState::Cease(const Instruction *inst) {
// Call the handler.
if (on_cease_ != nullptr) {
const bool res = on_cease_(inst);
if (res) return;
}
// If no handler is specified, then CEASE is treated as an infinite loop.
// TODO(torerik): set next pc to the right value.
const std::string where = (inst != nullptr)
? absl::StrCat(absl::Hex(inst->address()))
: "unknown location";
LOG(INFO) << "No handler for cease: treating as an infinite loop: " << where;
}
void CheriotState::Trap(bool is_interrupt, uint64_t trap_value,
uint64_t exception_code, uint64_t epc,
const Instruction *inst) {
// LOG(INFO) << "Trap: " << std::hex << is_interrupt << " " << trap_value
// << " " << exception_code << " " << epc; // Call the handler.
if (on_trap_ != nullptr) {
bool res = on_trap_(is_interrupt, trap_value, exception_code, epc, inst);
// If the handler returns true, the trap has been handled. Just return.
if (res) return;
}
// Get trap destination.
int trap_vector_mode = mtcc_->address() & 0x3ULL;
uint64_t trap_target = mtcc_->address() & ~0x3ULL;
if (trap_vector_mode == 1) {
trap_target += 4 * exception_code;
}
// Set mepc by copying pcc to mepc and setting the address to epc.
mepcc_->CopyFrom(*pcc());
mepcc_->set_address(epc);
// Set xcause.
mcause_->Set(exception_code);
if (is_interrupt) {
mcause_->SetBits(static_cast<uint32_t>(0x8000'0000));
}
// Set mstatus bits accordingly.
// Set the privilege mode to return to after the interrupt.
mstatus_->set_mpp(*PrivilegeMode::kMachine);
// Save the current interrupt enable to mpie.
mstatus_->set_mpie(mstatus_->mie());
// Disable further interrupts.
mstatus_->set_mie(0);
// Advance data buffer delay line until empty. Flush pending writes to
// register and possibly pc.
while (!data_buffer_delay_line()->IsEmpty()) {
data_buffer_delay_line()->Advance();
}
// Set mtval.
mtval_->Write(trap_value);
// Update the PC from the mtvec_ capability. Update the address in case of
// vectored mode.
pcc()->CopyFrom(*mtcc_);
pcc()->set_address(trap_target);
set_branch(true);
// TODO(torerik): set next pc
mstatus_->Submit();
// Set up interrupt info before incrementing the interrupt counter. That way
// any code that is triggered by the interrupt counter will see the updated
// interrupt info.
InterruptInfo info;
info.is_interrupt = is_interrupt;
info.cause = mcause_->GetUint32();
info.tval = mtval_->GetUint32();
info.epc = epc;
interrupt_info_list_.push_back(info);
counter_interrupts_taken_.Increment(1);
}
// Called upon returning from an interrupt or exception.
void CheriotState::SignalReturnFromInterrupt() {
// First increment the interrupt return counter. Then pop the interrupt info.
// This way any code that is triggered by the interrupt return counter will
// be able to access the interrupt info.
counter_interrupt_returns_.Increment(1);
interrupt_info_list_.pop_back();
}
// CheckForInterrupt is called whenever any relevant bits in the interrupt
// enable and set bits are changed. It should always be scheduled to execute
// from the function_delay_line, that way it is executed after an instruction
// has completed execution.
void CheriotState::CheckForInterrupt() {
// Get global interrupts enable bit.
bool mie = mstatus_->mie();
// No interrupts can be taken.
if (!mie) return;
// Get pending and enabled interrupts.
uint32_t interrupts = mip_->AsUint32() & mie_->AsUint32();
// If there are no enabled pending interrupts, just return.
if (interrupts == 0) return;
InterruptCode code = PickInterrupt(interrupts);
available_interrupt_code_ = code;
is_interrupt_available_ = true;
}
// Take the interrupt that is pending.
void CheriotState::TakeAvailableInterrupt(uint64_t epc) {
// Make sure an interrupt is set as pending by CheckForInterrupt.
if (!is_interrupt_available_) return;
// Initiate the interrupt.
Trap(/*is_interrupt*/ true, 0, *available_interrupt_code_, epc, nullptr);
// Clear pending interrupt.
is_interrupt_available_ = false;
available_interrupt_code_ = InterruptCode::kNone;
}
// The priority order of the interrupts are as follows:
// mei, msi, mti, sei, ssi, sti, uei, usi, uti.
InterruptCode CheriotState::PickInterrupt(uint32_t interrupts) {
if (interrupts & (1 << *InterruptCode::kMachineExternalInterrupt))
return InterruptCode::kMachineExternalInterrupt;
if (interrupts & (1 << *InterruptCode::kMachineSoftwareInterrupt))
return InterruptCode::kMachineSoftwareInterrupt;
if (interrupts & (1 << *InterruptCode::kMachineTimerInterrupt))
return InterruptCode::kMachineTimerInterrupt;
// No supervisor or user mode.
return InterruptCode::kNone;
}
// Check if the address is for a capability that has been revoked. If so,
// return true;
bool CheriotState::MustRevoke(uint32_t address) const {
uint64_t revocation_address =
address & ~(static_cast<uint64_t>(kCapabilitySizeInBytes) - 1ULL);
// If the address is less than the revocation memory base, return false.
if (revocation_address < revocation_ram_base()) return false;
uint64_t offset = (revocation_address - revocation_ram_base());
uint64_t revocation_offset = offset >> 6;
tagged_memory_->Load(revocation_mem_base() + revocation_offset,
revocation_db_, nullptr, nullptr);
uint8_t revocation_bits = revocation_db_->Get<uint8_t>(0);
int bit_offset = (offset >> 3) & 0b111;
return revocation_bits & (1 << bit_offset);
}
uint64_t RiscVCheri32PcSourceOperand::GetPC() {
auto *pcc = state_->pcc();
// PCC should always be a valid capability, otherwise an exception would
// have been taken. It should also have execute permissions. The only thing
// to check for is that the address is within bounds.
if (!pcc->IsInBounds(pcc->address(), state_->has_compact() ? 2 : 4)) {
state_->HandleCheriRegException(nullptr, pcc->address(),
ExceptionCode::kCapExBoundsViolation, pcc);
}
return pcc->address();
}
} // namespace cheriot
} // namespace sim
} // namespace mpact