blob: 0daca50f59ebc19a8824278f1aa67c9d9911dc07 [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_test_rig.h"
#include <cstdint>
#include <cstring>
#include <string>
#include "absl/functional/bind_front.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "cheriot/cheriot_register.h"
#include "cheriot/cheriot_state.h"
#include "cheriot/cheriot_test_rig_decoder.h"
#include "cheriot/riscv_cheriot_minstret.h"
#include "cheriot/riscv_cheriot_register_aliases.h"
#include "cheriot/test_rig_packets.h"
#include "mpact/sim/generic/component.h"
#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
#include "mpact/sim/util/memory/tagged_memory_watcher.h"
#include "riscv//riscv_register.h"
#include "riscv//riscv_state.h"
namespace mpact::sim::cheriot {
using EC = ::mpact::sim::riscv::ExceptionCode;
using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
using CheriotEC = ::mpact::sim::cheriot::ExceptionCode;
using ::mpact::sim::util::TaggedFlatDemandMemory;
using ::mpact::sim::util::TaggedMemoryWatcher;
constexpr char kCheriotTestRigName[] = "CheriotTestRig";
CheriotTestRig::CheriotTestRig()
: generic::Component(kCheriotTestRigName),
counter_num_instructions_("num_instructions", 0) {
// Set up memory.
tagged_memory_ = new TaggedFlatDemandMemory(8);
// Set up watcher and add callbacks.
tagged_memory_watcher_ = new TaggedMemoryWatcher(tagged_memory_);
CHECK_OK(tagged_memory_watcher_->SetLoadWatchCallback(
TaggedMemoryWatcher::AddressRange{0, 0x1'0000'0000ULL},
absl::bind_front(&CheriotTestRig::OnLoad, this)));
CHECK_OK(tagged_memory_watcher_->SetStoreWatchCallback(
TaggedMemoryWatcher::AddressRange{0, 0x1'0000'0000ULL},
absl::bind_front(&CheriotTestRig::OnStore, this)));
// Set up sim state.
state_ = new CheriotState(kCheriotTestRigName, tagged_memory_watcher_);
fp_state_ = new RiscVCheriotFPState(state_);
state_->set_rv_fp(fp_state_);
// Initialize pcc to 0x8000'0000.
pcc_ = static_cast<CheriotRegister *>(
state_->registers()->at(CheriotState::kPcName));
pcc_->set_address(0x8000'0000);
cheriot_decoder_ = new CheriotTestRigDecoder(state_);
// Register instruction counter.
CHECK_OK(AddCounter(&counter_num_instructions_))
<< "Failed to register counter";
// Make sure the architectural and abi register aliases are added.
std::string reg_name;
std::string xreg_name;
// Add aliases for capability registers.
for (int i = 0; i < 32; i++) {
reg_name = absl::StrCat(CheriotState::kCregPrefix, i);
// Alias the register with x register names.
// E.g., 'c10' === 'x10'
xreg_name = absl::StrCat(CheriotState::kXregPrefix, i);
CHECK_OK(state_->AddRegisterAlias<CheriotRegister>(reg_name, xreg_name));
// Alias the register with capability abi register names.
// E.g., 'c10' === 'ca0'
CHECK_OK(state_->AddRegisterAlias<CheriotRegister>(reg_name,
kCRegisterAliases[i]));
// Alias the register with abi register names.
// E.g., 'c10' === 'a0'
CHECK_OK(state_->AddRegisterAlias<CheriotRegister>(reg_name,
kXRegisterAliases[i]));
}
for (int i = 0; i < 32; i++) {
reg_name = absl::StrCat(CheriotState::kFregPrefix, i);
(void)state_->AddRegister<riscv::RVFpRegister>(reg_name);
CHECK_OK(state_->AddRegisterAlias<riscv::RVFpRegister>(
reg_name, kFRegisterAliases[i]));
}
// Register trap monitor.
state_->set_on_trap(absl::bind_front(&CheriotTestRig::OnTrap, this));
// Allocate data buffers.
db1_ = db_factory_.Allocate<uint8_t>(1);
db2_ = db_factory_.Allocate<uint16_t>(1);
db4_ = db_factory_.Allocate<uint32_t>(1);
db8_ = db_factory_.Allocate<uint64_t>(1);
// Initialize minstret/minstreth. Bind the instruction counter to those
// registers.
auto minstret_res = state_->csr_set()->GetCsr("minstret");
auto minstreth_res = state_->csr_set()->GetCsr("minstreth");
if (!minstret_res.ok() || !minstreth_res.ok()) {
LOG(ERROR) << "Error while initializing minstret/minstreth";
}
auto *minstret = static_cast<RiscVCheriotMInstret *>(minstret_res.value());
auto *minstreth = static_cast<RiscVCheriotMInstreth *>(minstreth_res.value());
minstret->set_counter(&counter_num_instructions_);
minstreth->set_counter(&counter_num_instructions_);
// Set memory limits according to the memory space for TestRIG.
state_->set_max_physical_address(0x8000'0000ULL + 64 * 1024);
state_->set_min_physical_address(0x8000'0000ULL);
ResetArch();
}
CheriotTestRig::~CheriotTestRig() {
delete cheriot_decoder_;
delete state_;
delete fp_state_;
delete tagged_memory_;
delete tagged_memory_watcher_;
// Deallocate data buffers.
db1_->DecRef();
db2_->DecRef();
db4_->DecRef();
db8_->DecRef();
}
absl::Status CheriotTestRig::Execute(
const test_rig::InstructionPacket &inst_packet, int fd) {
switch (trace_version_) {
case 1:
return ExecuteV1(inst_packet, fd);
break;
case 2:
return ExecuteV2(inst_packet, fd);
break;
default:
return absl::UnimplementedError(
absl::StrCat("Trace version ", trace_version_, " is not supported"));
}
}
absl::Status CheriotTestRig::SetVersion(int version) {
if (version > 2)
return absl::UnimplementedError(
absl::StrCat("Trace version ", version, " is not supported"));
trace_version_ = version;
return absl::OkStatus();
}
absl::Status CheriotTestRig::ExecuteV1(
const test_rig::InstructionPacket &inst_packet, int fd) {
test_rig::ExecutionPacket ep;
uint32_t inst_word = inst_packet.rvfi_insn;
ep.rvfi_halt = 0;
// Clear memory related fields.
mem_addr_ = 0;
mem_r_mask_ = 0;
mem_w_mask_ = 0;
std::memset(mem_r_data_, 0, sizeof(mem_r_data_));
std::memset(mem_w_data_, 0, sizeof(mem_w_data_));
// If trap was set last time around, set indicator for trap handler.
ep.rvfi_intr = trap_set_ ? 1 : 0;
trap_set_ = false;
uint64_t pc = pcc_->address();
ep.rvfi_pc_rdata = pc;
// Decode fills in rd_addr, rs2_addr, rs1_addr.
CheriotTestRigDecoder::DecodeInfo decode_info;
auto *inst = cheriot_decoder_->DecodeInstruction(pc, inst_word, decode_info);
ep.rvfi_rd_addr = decode_info.rd;
ep.rvfi_rs1_addr = decode_info.rs1;
ep.rvfi_rs2_addr = decode_info.rs2;
ep.rvfi_rs1_data = GetRegister(ep.rvfi_rs1_addr);
ep.rvfi_rs2_data = GetRegister(ep.rvfi_rs2_addr);
uint64_t next_pc = pc + inst->size();
// Execute the instruction.
if (!pcc_->HasPermission(PB::kPermitExecute)) {
state_->HandleCheriRegException(
inst, inst->address(), CheriotEC::kCapExPermitExecuteViolation, pcc_);
inst_word = 0;
} else if (!pcc_->IsInBounds(pc, state_->has_compact() ? sizeof(uint16_t)
: sizeof(uint32_t))) {
state_->HandleCheriRegException(inst, inst->address(),
CheriotEC::kCapExBoundsViolation, pcc_);
inst_word = 0;
} else {
inst->Execute(nullptr);
}
// Check for trap.
if (trap_set_) {
next_pc = pcc_->address();
// If there's a trap, clear relevant fields.
ep.rvfi_trap = 1;
ep.rvfi_rd_addr = 0;
ep.rvfi_rs2_addr = 0;
ep.rvfi_rs1_addr = 0;
ep.rvfi_rd_wdata = 0;
ep.rvfi_rs2_data = 0;
ep.rvfi_rs1_data = 0;
ep.rvfi_mem_addr = 0;
ep.rvfi_mem_rdata = 0;
ep.rvfi_mem_wdata = 0;
ep.rvfi_mem_rmask = 0;
ep.rvfi_mem_wmask = 0;
// If the trap was a memory access trap, set the memory address.
auto res = state_->csr_set()->GetCsr("mcause");
auto cause = res.value()->AsUint32();
if (cause == *EC::kLoadAccessFault || cause == *EC::kStoreAccessFault) {
res = state_->csr_set()->GetCsr("mtval");
ep.rvfi_mem_addr = res.value()->AsUint32();
}
} else {
if (state_->branch()) {
next_pc = pcc_->address();
}
ep.rvfi_trap = 0;
// Update register write data.
ep.rvfi_rd_wdata = GetRegister(ep.rvfi_rd_addr);
// Update memory fields.
ep.rvfi_mem_addr = mem_addr_;
ep.rvfi_mem_rdata = mem_r_data_[0];
ep.rvfi_mem_wdata = mem_w_data_[0];
ep.rvfi_mem_rmask = mem_r_mask_;
ep.rvfi_mem_wmask = mem_w_mask_;
}
state_->set_branch(false);
counter_num_instructions_.Increment(1);
ep.rvfi_insn = inst_word;
ep.rvfi_pc_wdata = next_pc;
ep.rvfi_order = counter_num_instructions_.GetValue();
pcc_->set_address(next_pc);
inst->DecRef();
auto res = write(fd, &ep, sizeof(ep));
if (res != sizeof(ep)) {
LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
return absl::InternalError("Error writing to trace socket");
}
return absl::OkStatus();
}
absl::Status CheriotTestRig::ExecuteV2(
const test_rig::InstructionPacket &inst_packet, int fd) {
test_rig::ExecutionPacketExtInteger ep_ext_integer;
test_rig::ExecutionPacketExtMemAccess ep_ext_mem_access;
test_rig::ExecutionPacketV2 ep_v2;
test_rig::ExecutionPacketMetaData &ep_metadata = ep_v2.basic_data;
test_rig::ExecutionPacketPC &ep_pc = ep_v2.pc_data;
uint32_t inst_word = inst_packet.rvfi_insn;
ep_metadata.rvfi_halt = 0;
// Clear memory related fields.
mem_addr_ = 0;
mem_r_mask_ = 0;
mem_w_mask_ = 0;
std::memset(mem_r_data_, 0, sizeof(mem_r_data_));
std::memset(mem_w_data_, 0, sizeof(mem_w_data_));
// If trap was set last time around, set indicator for trap handler.
ep_metadata.rvfi_intr = trap_set_ ? 1 : 0;
trap_set_ = false;
uint64_t pc = pcc_->address();
ep_pc.rvfi_pc_rdata = pc;
// Decode fills in rd_addr, rs2_addr, rs1_addr.
CheriotTestRigDecoder::DecodeInfo decode_info;
auto *inst = cheriot_decoder_->DecodeInstruction(pc, inst_word, decode_info);
ep_ext_integer.rvfi_rd_addr = decode_info.rd;
ep_ext_integer.rvfi_rs1_addr = decode_info.rs1;
ep_ext_integer.rvfi_rs2_addr = decode_info.rs2;
// Get register source values.
ep_ext_integer.rvfi_rs1_rdata = GetRegister(ep_ext_integer.rvfi_rs1_addr);
ep_ext_integer.rvfi_rs2_rdata = GetRegister(ep_ext_integer.rvfi_rs2_addr);
uint64_t next_pc = pc + inst->size();
// Execute the instruction.
if (!pcc_->tag()) {
state_->HandleCheriRegException(inst, inst->address(),
CheriotEC::kCapExTagViolation, pcc_);
inst_word = 0;
} else if (!pcc_->HasPermission(PB::kPermitExecute)) {
state_->HandleCheriRegException(
inst, inst->address(), CheriotEC::kCapExPermitExecuteViolation, pcc_);
inst_word = 0;
} else if (!pcc_->IsInBounds(pc, state_->has_compact() ? sizeof(uint16_t)
: sizeof(uint32_t))) {
state_->HandleCheriRegException(inst, inst->address(),
CheriotEC::kCapExBoundsViolation, pcc_);
inst_word = 0;
} else {
inst->Execute(nullptr);
}
// Check for trap.
if (trap_set_) {
next_pc = pcc_->address();
// If there's a trap, clear relevant fields.
ep_metadata.rvfi_trap = 1;
ep_ext_integer.rvfi_rd_addr = 0;
ep_ext_integer.rvfi_rs2_addr = 0;
ep_ext_integer.rvfi_rs1_addr = 0;
ep_ext_integer.rvfi_rd_wdata = 0;
ep_ext_integer.rvfi_rs2_rdata = 0;
ep_ext_integer.rvfi_rs1_rdata = 0;
ep_ext_mem_access.rvfi_mem_addr = 0;
std::memset(ep_ext_mem_access.rvfi_mem_rdata, 0,
sizeof(ep_ext_mem_access.rvfi_mem_rdata));
std::memset(ep_ext_mem_access.rvfi_mem_wdata, 0,
sizeof(ep_ext_mem_access.rvfi_mem_wdata));
ep_ext_mem_access.rvfi_mem_rmask = 0;
ep_ext_mem_access.rvfi_mem_wmask = 0;
// If the trap was a memory access trap, set the memory address.
auto res = state_->csr_set()->GetCsr("mcause");
auto cause = res.value()->AsUint32();
if (cause == *EC::kLoadAccessFault || cause == *EC::kStoreAccessFault) {
res = state_->csr_set()->GetCsr("mtval");
ep_ext_mem_access.rvfi_mem_addr = res.value()->AsUint32();
}
} else {
if (state_->branch()) {
next_pc = pcc_->address();
}
ep_metadata.rvfi_trap = 0;
// Update register write data.
ep_ext_integer.rvfi_rd_wdata = GetRegister(ep_ext_integer.rvfi_rd_addr);
// Update memory fields.
ep_ext_mem_access.rvfi_mem_addr = mem_addr_;
std::memcpy(ep_ext_mem_access.rvfi_mem_rdata, mem_r_data_,
sizeof(ep_ext_mem_access.rvfi_mem_rdata));
std::memcpy(ep_ext_mem_access.rvfi_mem_wdata, mem_w_data_,
sizeof(ep_ext_mem_access.rvfi_mem_wdata));
ep_ext_mem_access.rvfi_mem_rmask = mem_r_mask_;
ep_ext_mem_access.rvfi_mem_wmask = mem_w_mask_;
}
state_->set_branch(false);
counter_num_instructions_.Increment(1);
ep_metadata.rvfi_mode = test_rig::kMachineMode;
ep_metadata.rvfi_ixl = test_rig::kXL32;
ep_metadata.rvfi_insn = inst_word;
ep_pc.rvfi_pc_wdata = next_pc;
ep_metadata.rvfi_order = counter_num_instructions_.GetValue();
ep_metadata.rvfi_valid = 1;
pcc_->set_address(next_pc);
inst->DecRef();
ep_v2.trace_size = sizeof(ep_v2);
ep_v2.available_fields = 0;
// Check to see if the memory access extension should be added.
if ((ep_ext_mem_access.rvfi_mem_rmask != 0) ||
(ep_ext_mem_access.rvfi_mem_wmask != 0) ||
(ep_ext_mem_access.rvfi_mem_addr != 0)) {
ep_v2.trace_size += sizeof(ep_ext_mem_access);
ep_v2.available_fields |= test_rig::kMemoryAccess;
}
// Check to see if the integer data extension should be added.
if ((ep_ext_integer.rvfi_rd_addr != 0) ||
(ep_ext_integer.rvfi_rs1_addr != 0) ||
(ep_ext_integer.rvfi_rs2_addr != 0)) {
ep_v2.trace_size += sizeof(ep_ext_integer);
ep_v2.available_fields |= test_rig::kIntegerData;
}
// Write out the execution packet.
auto res = write(fd, &ep_v2, sizeof(ep_v2));
if (res != sizeof(ep_v2)) {
LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
return absl::InternalError("Error writing to trace socket");
}
// Write out the extension packets.
if (ep_v2.available_fields & test_rig::kIntegerData) {
auto res = write(fd, &ep_ext_integer, sizeof(ep_ext_integer));
if (res != sizeof(ep_ext_integer)) {
LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
return absl::InternalError("Error writing to trace socket");
}
}
if (ep_v2.available_fields & test_rig::kMemoryAccess) {
auto res = write(fd, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
if (res != sizeof(ep_ext_mem_access)) {
LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
return absl::InternalError("Error writing to trace socket");
}
}
return absl::OkStatus();
}
// Reset state.
void CheriotTestRig::ResetArch() {
// Reset state.
state_->Reset();
// Reset pcc.
pcc_->ResetExecuteRoot();
pcc_->set_address(0x8000'0000);
// Clear 64KB memory.
db8_->Set<uint64_t>(0, 0);
for (uint64_t addr = 0x8000'0000ULL; addr < 0x8001'0000; addr += 8) {
tagged_memory_->Store(addr, db8_);
}
// Reset instruction counter.
counter_num_instructions_.SetValue(0);
// Set all capability registers to memory root capability.
for (auto const &name :
{"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8",
"c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16",
"c17", "c18", "c19", "c20", "c21", "c22", "c23", "c24",
"c25", "c26", "c27", "c28", "c29", "c30", "c31"}) {
auto *cap_reg = state_->GetRegister<CheriotRegister>(name).first;
cap_reg->ResetMemoryRoot();
}
}
absl::Status CheriotTestRig::Reset(uint8_t halt, int fd) {
ResetArch();
trap_set_ = false;
// Write the appropriate trace packet out.
switch (trace_version_) {
case 1:
return ResetV1(halt, fd);
break;
case 2:
return ResetV2(halt, fd);
default:
return absl::UnimplementedError(
absl::StrCat("Trace version ", trace_version_, " is not supported"));
}
}
absl::Status CheriotTestRig::ResetV1(uint8_t halt, int fd) {
test_rig::ExecutionPacket ep;
std::memset(&ep, 0, sizeof(ep));
ep.rvfi_halt = halt;
auto res = write(fd, &ep, sizeof(ep));
if (res != sizeof(ep)) {
LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
return absl::InternalError("Error writing to trace socket");
}
return absl::OkStatus();
}
absl::Status CheriotTestRig::ResetV2(uint8_t halt, int fd) {
test_rig::ExecutionPacketV2 ep_v2;
ep_v2.trace_size = sizeof(ep_v2);
ep_v2.available_fields = 0;
std::memset(&ep_v2.pc_data, 0, sizeof(ep_v2.pc_data));
std::memset(&ep_v2.basic_data, 0, sizeof(ep_v2.basic_data));
ep_v2.basic_data.rvfi_halt = halt;
auto res = write(fd, &ep_v2, sizeof(ep_v2));
if (res != sizeof(ep_v2)) {
LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
return absl::InternalError("Error writing to trace socket");
}
return absl::OkStatus();
}
// Just capture that a trap occurred.
bool CheriotTestRig::OnTrap(bool is_interrupt, uint64_t trap_value,
uint64_t exception_code, uint64_t epc,
const Instruction *inst) {
trap_set_ = true;
return false;
}
// Capture load information.
void CheriotTestRig::OnLoad(uint64_t address, int size) {
mem_addr_ = address;
switch (size) {
case 1:
tagged_memory_->Load(address, db1_, nullptr, nullptr);
mem_r_mask_ = 0x1ULL;
mem_r_data_[0] = db1_->Get<uint8_t>(0);
break;
case 2:
tagged_memory_->Load(address, db2_, nullptr, nullptr);
mem_r_mask_ = 0x3ULL;
mem_r_data_[0] = db2_->Get<uint16_t>(0);
break;
case 4:
tagged_memory_->Load(address, db4_, nullptr, nullptr);
mem_r_mask_ = 0xfULL;
mem_r_data_[0] = db4_->Get<uint32_t>(0);
break;
case 8:
tagged_memory_->Load(address, db8_, nullptr, nullptr);
mem_r_mask_ = 0xffULL;
mem_r_data_[0] = db8_->Get<uint64_t>(0);
break;
default:
break;
}
}
// Capture store information.
void CheriotTestRig::OnStore(uint64_t address, int size) {
mem_addr_ = address;
switch (size) {
case 1:
tagged_memory_->Load(address, db1_, nullptr, nullptr);
mem_w_mask_ = 0x1ULL;
mem_w_data_[0] = db1_->Get<uint8_t>(0);
break;
case 2:
tagged_memory_->Load(address, db2_, nullptr, nullptr);
mem_w_mask_ = 0x3ULL;
mem_w_data_[0] = db2_->Get<uint16_t>(0);
break;
case 4:
tagged_memory_->Load(address, db4_, nullptr, nullptr);
mem_w_mask_ = 0xfULL;
mem_w_data_[0] = db4_->Get<uint32_t>(0);
break;
case 8:
tagged_memory_->Load(address, db8_, nullptr, nullptr);
mem_w_mask_ = 0xffULL;
mem_w_data_[0] = db8_->Get<uint64_t>(0);
break;
default:
break;
}
}
// Get the register value.
uint32_t CheriotTestRig::GetRegister(uint32_t reg_id) {
auto reg_name = absl::StrCat(CheriotState::kXregPrefix, reg_id);
auto ptr = state_->registers()->find(reg_name);
if (ptr == state_->registers()->end()) {
return 0;
} else {
auto *reg = ptr->second;
auto *creg = static_cast<CheriotRegister *>(reg);
return creg->address();
}
}
} // namespace mpact::sim::cheriot