blob: 16649b9f8280f36a31c2f527555629827a316624 [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/riscv_cheriot_zicsr_instructions.h"
#include <any>
#include <cstdint>
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "cheriot/cheriot_register.h"
#include "cheriot/cheriot_state.h"
#include "cheriot/riscv_cheriot_csr_enum.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/generic/register.h"
#include "mpact/sim/generic/type_helpers.h"
#include "riscv//riscv_csr.h"
#include "riscv//riscv_state.h"
namespace mpact {
namespace sim {
namespace cheriot {
using ::mpact::sim::generic::operator*; // NOLINT: is used below (clang error).
using ::mpact::sim::riscv::PrivilegeMode;
using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
using RV_EC = ::mpact::sim::riscv::ExceptionCode;
using CH_EC = ::mpact::sim::cheriot::ExceptionCode;
using CapReg = ::mpact::sim::cheriot::CheriotRegister;
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::generic::RegisterBase;
// Helper to get capability destination registers.
static inline CapReg *GetCapDest(const Instruction *instruction, int i) {
return static_cast<CapReg *>(
std::any_cast<RegisterBase *>(instruction->Destination(i)->GetObject()));
}
// Writing an integer result requires invalidating the capability and setting
// it to null.
template <typename Result>
static inline void WriteCapIntResult(const Instruction *instruction, int i,
Result value) {
auto *cap_reg = GetCapDest(instruction, i);
cap_reg->data_buffer()->Set<Result>(0, value);
cap_reg->Invalidate();
cap_reg->set_is_null();
}
template <typename T>
inline T ReadCsr(RiscVCsrInterface *) {}
template <>
inline uint32_t ReadCsr<uint32_t>(RiscVCsrInterface *csr) {
return csr->AsUint32();
}
template <>
inline uint64_t ReadCsr<uint64_t>(RiscVCsrInterface *csr) {
return csr->AsUint64();
}
// Helper function to check that the CSR permission is valid. If not, throw
// an illegal instruction exception.
bool CheckCsrPermission(int csr_index, Instruction *instruction,
bool is_write) {
auto required_mode = (csr_index >> 8) & 0x3;
auto *state = static_cast<CheriotState *>(instruction->state());
auto current_mode = PrivilegeMode::kMachine;
// If the register isn't available in CHERIoT, throw an exception.
if (required_mode == *PrivilegeMode::kSupervisor) {
state->Trap(/*is_interrupt*/ false, 0, *RV_EC::kIllegalInstruction,
instruction->address(), instruction);
return false;
}
// If the privilege mode is too low, throw an exception.
if (*current_mode < required_mode) {
state->Trap(/*is_interrupt*/ false, 0, *RV_EC::kIllegalInstruction,
instruction->address(), instruction);
return false;
}
// Accesses to fflags, frm, and fcsr are all ok.
if ((csr_index >= *RiscVCheriotCsrEnum::kFFlags) &&
(csr_index <= *RiscVCheriotCsrEnum::kFCsr)) {
return true;
}
// Reads to MCycle, MInstret, MHpmcounterN are all ok.
if (!is_write && (((csr_index >= *RiscVCheriotCsrEnum::kMCycle) &&
(csr_index <= *RiscVCheriotCsrEnum::kMHpmcounter31)) ||
((csr_index >= *RiscVCheriotCsrEnum::kMCycleH) &&
(csr_index <= *RiscVCheriotCsrEnum::kMHpmcounter31H)))) {
return true;
}
// Check pcc capability.
if ((required_mode != *PrivilegeMode::kUser) &&
(!state->pcc()->HasPermission(PB::kPermitAccessSystemRegisters))) {
state->HandleCheriRegException(
instruction, instruction->address(),
CH_EC::kCapExPermitAccessSystemRegistersViolation, state->pcc());
return false;
}
return true;
}
// Templated helper functions.
// Read the CSR, write a new value back.
template <typename T>
static inline void RVZiCsrrw(Instruction *instruction) {
// Get a handle to the state instance.
auto state = static_cast<CheriotState *>(instruction->state());
// Get the csr index.
int csr_index = instruction->Source(1)->AsInt32(0);
if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
// Read the csr.
auto result = state->csr_set()->GetCsr(csr_index);
if (!result.ok()) {
// Signal error if it failed.
LOG(ERROR) << absl::StrCat("Instruction at address 0x",
absl::Hex(instruction->address()),
" failed to read CSR 0x", absl::Hex(csr_index),
": ", result.status().message());
return;
}
// Get the new value.
T new_value = generic::GetInstructionSource<T>(instruction, 0);
auto *csr = result.value();
// Update the register.
auto csr_val = ReadCsr<T>(csr);
WriteCapIntResult(instruction, 0, csr_val);
// Write the new value to the csr.
csr->Write(new_value);
}
// Read the CSR, set the bits specified by the new value and write back.
template <typename T>
static inline void RVZiCsrrs(Instruction *instruction) {
// Get a handle to the state instance.
auto state = static_cast<CheriotState *>(instruction->state());
// Get the csr index.
int csr_index = instruction->Source(1)->AsInt32(0);
if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
// Read the csr.
auto result = state->csr_set()->GetCsr(csr_index);
if (!result.ok()) {
// Signal error if it failed.
LOG(ERROR) << absl::StrCat("Instruction at address 0x",
absl::Hex(instruction->address()),
" failed to read CSR 0x", absl::Hex(csr_index),
": ", result.status().message());
return;
}
// Get the new value.
T new_value = generic::GetInstructionSource<T>(instruction, 0);
auto *csr = result.value();
// Update the register.
auto csr_val = ReadCsr<T>(csr);
WriteCapIntResult(instruction, 0, csr_val);
// Write the new value to the csr.
csr->SetBits(new_value);
}
// Read the CSR, clear the bits specified by the new value and write back.
template <typename T>
static inline void RVZiCsrrc(Instruction *instruction) {
// Get a handle to the state instance.
auto state = static_cast<CheriotState *>(instruction->state());
// Get the csr index.
int csr_index = instruction->Source(1)->AsInt32(0);
if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
// Read the csr.
auto result = state->csr_set()->GetCsr(csr_index);
if (!result.ok()) {
// Signal error if it failed.
LOG(ERROR) << absl::StrCat("Instruction at address 0x",
absl::Hex(instruction->address()),
" failed to read CSR 0x", absl::Hex(csr_index),
": ", result.status().message());
return;
}
// Get the new value.
T new_value = generic::GetInstructionSource<T>(instruction, 0);
auto *csr = result.value();
// Write the current value of the CSR to the destination register.
auto csr_val = ReadCsr<T>(csr);
WriteCapIntResult(instruction, 0, csr_val);
// Write the new value to the csr.
csr->ClearBits(new_value);
}
// Do not read the CSR, just write the new value back.
template <typename T>
static inline void RVZiCsrrwNr(Instruction *instruction) {
// Get a handle to the state instance.
auto state = static_cast<CheriotState *>(instruction->state());
// Get the csr index.
int csr_index = instruction->Source(1)->AsInt32(0);
if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
// Read the csr.
auto result = state->csr_set()->GetCsr(csr_index);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Instruction at address 0x",
absl::Hex(instruction->address()),
" failed to write CSR 0x", absl::Hex(csr_index),
": ", result.status().message());
return;
}
auto *csr = result.value();
// Write the new value to the csr.
T new_value = generic::GetInstructionSource<T>(instruction, 0);
csr->Write(new_value);
}
// Do not write a value back to the CSR, just read it.
template <typename T>
static inline void RVZiCsrrNw(Instruction *instruction) {
// Get a handle to the state instance.
auto state = static_cast<CheriotState *>(instruction->state());
// Get the csr index.
int csr_index = instruction->Source(0)->AsInt32(0);
if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/false)) return;
// Read the csr.
auto result = state->csr_set()->GetCsr(csr_index);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Instruction at address 0x",
absl::Hex(instruction->address()),
" failed to read CSR 0x", absl::Hex(csr_index),
": ", result.status().message());
return;
}
// Get the CSR object.
auto *csr = result.value();
auto csr_val = ReadCsr<T>(csr);
WriteCapIntResult(instruction, 0, csr_val);
}
using RegisterType = CheriotRegister;
using UintReg = RegisterType::ValueType;
// Read the CSR, write a new value back.
void RiscVZiCsrrw(Instruction *instruction) { RVZiCsrrw<UintReg>(instruction); }
// Read the CSR, set the bits specified by the new value and write back.
void RiscVZiCsrrs(Instruction *instruction) { RVZiCsrrs<UintReg>(instruction); }
// Read the CSR, clear the bits specified by the new value and write back.
void RiscVZiCsrrc(Instruction *instruction) { RVZiCsrrc<UintReg>(instruction); }
// Do not read the CSR, just write the new value back.
void RiscVZiCsrrwNr(Instruction *instruction) {
RVZiCsrrwNr<UintReg>(instruction);
}
// Do not write a value back to the CSR, just read it.
void RiscVZiCsrrNw(Instruction *instruction) {
RVZiCsrrNw<UintReg>(instruction);
}
} // namespace cheriot
} // namespace sim
} // namespace mpact