| // 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 |