blob: 6508ee549e1967e28e8a5bd959e808a03e9848e3 [file]
// Copyright 2023 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
//
// https://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 "riscv/riscv_priv_instructions.h"
#include <type_traits>
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "mpact/sim/generic/data_buffer.h"
#include "mpact/sim/generic/type_helpers.h"
#include "riscv/riscv_csr.h"
#include "riscv/riscv_misa.h"
#include "riscv/riscv_register.h"
#include "riscv/riscv_state.h"
#include "riscv/riscv_xstatus.h"
namespace mpact {
namespace sim {
namespace riscv {
using ::mpact::sim::generic::operator*; // NOLINT: is used below (clang error).
namespace RV32 {
using RegisterType = RV32Register;
using UIntReg =
typename std::make_unsigned<typename RegisterType::ValueType>::type;
void RiscVPrivURet(const Instruction* inst) {
// TODO Fill in semantics.
}
void RiscVPrivSRet(const Instruction* inst) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
if (state->privilege_mode() != PrivilegeMode::kSupervisor) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
if (state->mstatus()->tsr()) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
auto res = state->csr_set()->GetCsr(*RiscVCsrEnum::kSEpc);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" sret: cannot access sepc");
return;
}
auto* sepc = *res;
// Get db for PC.
auto* db = inst->Destination(0)->AllocateDataBuffer();
// Write the contents of mepc to the pc.
db->SetSubmit<UIntReg>(0, sepc->AsUint32());
state->set_branch(true);
// Need to update new privilege level.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" sret: cannot access mstatus");
return;
}
auto* mstatus = static_cast<RiscVMStatus*>(*res);
// Get misa too.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" sret: cannot access isa");
return;
}
// Set mstatus:mpp to new privilege mode as per RiscV Privileged Architectures
// Arch V20190608-Priv-MSU-Ratified page 21:
// When executing an xRet instruction, supposing xPP holds the value y, xIE
// is set to xPIE; the privilege mode is changed to y; xPIE is set to 1; and
// xPP is set to U (or M if user-mode is not supported).
auto target_mode = mstatus->spp();
// Set mstatus:mie to the value of mstatus:mpie.
mstatus->set_sie(mstatus->spie());
// Set mstatus:mpie to 1.
mstatus->set_spie(1);
mstatus->set_spp(*PrivilegeMode::kUser & 0b1);
state->set_privilege_mode(static_cast<PrivilegeMode>(target_mode));
state->SignalReturnFromInterrupt();
mstatus->Submit();
}
void RiscVPrivMRet(const Instruction* inst) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
if (state->privilege_mode() != PrivilegeMode::kMachine) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
auto res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMEpc);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access mepc");
return;
}
auto* mepc = *res;
// Get db for PC.
auto* db = inst->Destination(0)->AllocateDataBuffer();
// Write the contents of mepc to the pc.
db->SetSubmit<UIntReg>(0, mepc->AsUint32());
state->set_branch(true);
// Need to update new privilege level.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access mstatus");
return;
}
auto* mstatus = static_cast<RiscVMStatus*>(*res);
// Get misa too.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access isa");
return;
}
auto* misa = static_cast<RiscVMIsa*>(*res);
// Set mstatus:mpp to new privilege mode as per RiscV Privileged Architectures
// Arch V20190608-Priv-MSU-Ratified page 21:
// When executing an xRet instruction, supposing xPP holds the value y, xIE
// is set to xPIE; the privilege mode is changed to y; xPIE is set to 1; and
// xPP is set to U (or M if user-mode is not supported).
auto target_mode = mstatus->mpp();
// Set mstatus:mie to the value of mstatus:mpie.
mstatus->set_mie(mstatus->mpie());
// Set mstatus:mpie to 1.
mstatus->set_mpie(1);
if (misa->HasUserMode()) {
mstatus->set_mpp(*PrivilegeMode::kUser);
} else {
mstatus->set_mpp(*PrivilegeMode::kMachine);
}
state->set_privilege_mode(static_cast<PrivilegeMode>(target_mode));
state->SignalReturnFromInterrupt();
mstatus->Submit();
}
} // namespace RV32
namespace RV64 {
using RegisterType = RV64Register;
using UIntReg =
typename std::make_unsigned<typename RegisterType::ValueType>::type;
void RiscVPrivURet(const Instruction* inst) {
// TODO Fill in semantics.
}
void RiscVPrivSRet(const Instruction* inst) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
if (*state->privilege_mode() < *PrivilegeMode::kSupervisor) {
LOG(ERROR) << absl::StrCat(
"sret executed when not in Supervisor mode at pc = 0x",
absl::Hex(inst->address()));
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
if (state->mstatus()->tsr()) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
auto res = state->csr_set()->GetCsr(*RiscVCsrEnum::kSEpc);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" sret: cannot access sepc");
return;
}
auto* sepc = *res;
// Get db for PC.
auto* db = inst->Destination(0)->AllocateDataBuffer();
// Write the contents of mepc to the pc.
db->SetSubmit<UIntReg>(0, sepc->AsUint64());
state->set_branch(true);
// Need to update new privilege level.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" sret: cannot access mstatus");
return;
}
auto* mstatus = static_cast<RiscVMStatus*>(*res);
// Get misa too.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" sret: cannot access isa");
return;
}
// Set mstatus:mpp to new privilege mode as per RiscV Privileged Architectures
// Arch V20190608-Priv-MSU-Ratified page 21:
// When executing an xRet instruction, supposing xPP holds the value y, xIE
// is set to xPIE; the privilege mode is changed to y; xPIE is set to 1; and
// xPP is set to U (or M if user-mode is not supported).
auto target_mode = mstatus->spp();
// Set mstatus:mie to the value of mstatus:mpie.
mstatus->set_sie(mstatus->spie());
// Set mstatus:mpie to 1.
mstatus->set_spie(1);
// Get misa too.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access isa");
return;
}
auto* misa = static_cast<RiscVMIsa*>(*res);
if (misa->HasUserMode()) {
mstatus->set_spp(*PrivilegeMode::kUser);
} else {
mstatus->set_spp(*PrivilegeMode::kMachine);
}
state->set_privilege_mode(static_cast<PrivilegeMode>(target_mode));
state->SignalReturnFromInterrupt();
mstatus->Submit();
}
void RiscVPrivMRet(const Instruction* inst) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
if (state->privilege_mode() != PrivilegeMode::kMachine) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
auto res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMEpc);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access mepc");
return;
}
auto* mepc = *res;
// Get db for PC.
auto* db = inst->Destination(0)->AllocateDataBuffer();
// Write the contents of mepc to the pc.
db->SetSubmit<UIntReg>(0, mepc->AsUint64());
state->set_branch(true);
// Need to update new privilege level.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access mstatus");
return;
}
auto* mstatus = static_cast<RiscVMStatus*>(*res);
// Get misa too.
res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
if (!res.ok()) {
LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
" mret: cannot access isa");
return;
}
auto* misa = static_cast<RiscVMIsa*>(*res);
// Set mstatus:mpp to new privilege mode as per RiscV Privileged Architectures
// Arch V20190608-Priv-MSU-Ratified page 21:
// When executing an xRet instruction, supposing xPP holds the value y, xIE
// is set to xPIE; the privilege mode is changed to y; xPIE is set to 1; and
// xPP is set to U (or M if user-mode is not supported).
auto target_mode = mstatus->mpp();
// Set mstatus:mie to the value of mstatus:mpie.
mstatus->set_mie(mstatus->mpie());
// Set mstatus:mpie to 1.
mstatus->set_mpie(1);
if (misa->HasUserMode()) {
mstatus->set_mpp(*PrivilegeMode::kUser);
} else {
mstatus->set_mpp(*PrivilegeMode::kMachine);
}
state->set_privilege_mode(static_cast<PrivilegeMode>(target_mode));
state->SignalReturnFromInterrupt();
mstatus->Submit();
}
} // namespace RV64
void RiscVPrivWfi(const Instruction* inst) {
// WFI is treated as a no-op, unless the user sets a callback.
RiscVState* state = static_cast<RiscVState*>(inst->state());
state->WFI(inst);
}
void RiscVPrivSFenceVmaZZ(const Instruction* inst) {
auto* state = static_cast<RiscVState*>(inst->state());
PrivilegeMode mode = state->privilege_mode();
auto* mstatus = state->mstatus();
if ((mode == PrivilegeMode::kUser) ||
((mode == PrivilegeMode::kSupervisor) && mstatus->tvm())) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
// TODO Fill in semantics.
}
void RiscVPrivSFenceVmaZN(const Instruction* inst) {
auto* state = static_cast<RiscVState*>(inst->state());
PrivilegeMode mode = state->privilege_mode();
auto* mstatus = state->mstatus();
if ((mode == PrivilegeMode::kUser) ||
((mode == PrivilegeMode::kSupervisor) && mstatus->tvm())) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
// TODO Fill in semantics.
}
void RiscVPrivSFenceVmaNZ(const Instruction* inst) {
auto* state = static_cast<RiscVState*>(inst->state());
PrivilegeMode mode = state->privilege_mode();
auto* mstatus = state->mstatus();
if ((mode == PrivilegeMode::kUser) ||
((mode == PrivilegeMode::kSupervisor) && mstatus->tvm())) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
// TODO Fill in semantics.
}
void RiscVPrivSFenceVmaNN(const Instruction* inst) {
auto* state = static_cast<RiscVState*>(inst->state());
PrivilegeMode mode = state->privilege_mode();
auto* mstatus = state->mstatus();
if ((mode == PrivilegeMode::kUser) ||
((mode == PrivilegeMode::kSupervisor) && mstatus->tvm())) {
state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
*ExceptionCode::kIllegalInstruction, inst->address(), inst);
return;
}
// TODO Fill in semantics.
}
} // namespace riscv
} // namespace sim
} // namespace mpact