arch-riscv: Add NMI support
NMI is platform dependent according to riscv spec, so we intentionally propose a very minimal design that doesn't give user any new accessible register to interact with the NMI mechanism. In current design, the NMI reset vector is fixed to 0x0 and always set mcause to zero. mret from NMI handler is still possible, but it's up to the user to detect whether a M-mode trap handler is interrupted and to recover from it (if at all possible). 1. Add new fault type to represent NMI fault 2. Add non-standard registers to save the status of NMI a. nmivec[63:2] = NMI reset vector address b. nmie[0:0] = is NMI enabled = not in NMI handler c. nmip[0:0] = is NMI pending 3. Add new function in RiscvInterrupts to raise/clear NMI Bug: 200169094 Test: None Change-Id: Ia81e1c9589bc02f0690d094fff5f13412846acbe Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/52363 Reviewed-by: Yu-hsin Wang <yuhsingw@google.com> Reviewed-by: Jason Lowe-Power <power.jg@gmail.com> Maintainer: Jason Lowe-Power <power.jg@gmail.com> Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
@@ -67,8 +67,17 @@ RiscvFault::invoke(ThreadContext *tc, const StaticInstPtr &inst)
|
||||
PrivilegeMode prv = PRV_M;
|
||||
STATUS status = tc->readMiscReg(MISCREG_STATUS);
|
||||
|
||||
// According to riscv-privileged-v1.11, if a NMI occurs at the middle
|
||||
// of a M-mode trap handler, the state (epc/cause) will be overwritten
|
||||
// and is not necessary recoverable. There's nothing we can do here so
|
||||
// we'll just warn our user that the CPU state might be broken.
|
||||
warn_if(isNonMaskableInterrupt() && pp == PRV_M && status.mie == 0,
|
||||
"NMI overwriting M-mode trap handler state");
|
||||
|
||||
// Set fault handler privilege mode
|
||||
if (isInterrupt()) {
|
||||
if (isNonMaskableInterrupt()) {
|
||||
prv = PRV_M;
|
||||
} else if (isInterrupt()) {
|
||||
if (pp != PRV_M &&
|
||||
bits(tc->readMiscReg(MISCREG_MIDELEG), _code) != 0) {
|
||||
prv = PRV_S;
|
||||
@@ -113,7 +122,7 @@ RiscvFault::invoke(ThreadContext *tc, const StaticInstPtr &inst)
|
||||
case PRV_M:
|
||||
cause = MISCREG_MCAUSE;
|
||||
epc = MISCREG_MEPC;
|
||||
tvec = MISCREG_MTVEC;
|
||||
tvec = isNonMaskableInterrupt() ? MISCREG_NMIVEC : MISCREG_MTVEC;
|
||||
tval = MISCREG_MTVAL;
|
||||
|
||||
status.mpp = pp;
|
||||
@@ -136,6 +145,12 @@ RiscvFault::invoke(ThreadContext *tc, const StaticInstPtr &inst)
|
||||
tc->setMiscReg(tval, trap_value());
|
||||
tc->setMiscReg(MISCREG_PRV, prv);
|
||||
tc->setMiscReg(MISCREG_STATUS, status);
|
||||
// Temporarily mask NMI while we're in NMI handler. Otherweise, the
|
||||
// checkNonMaskableInterrupt will always return true and we'll be
|
||||
// stucked in an infinite loop.
|
||||
if (isNonMaskableInterrupt()) {
|
||||
tc->setMiscReg(MISCREG_NMIE, 0);
|
||||
}
|
||||
|
||||
// Set PC to fault handler address
|
||||
Addr addr = mbits(tc->readMiscReg(tvec), 63, 2);
|
||||
|
||||
@@ -96,19 +96,30 @@ enum ExceptionCode : uint64_t
|
||||
NumInterruptTypes
|
||||
};
|
||||
|
||||
enum class FaultType
|
||||
{
|
||||
INTERRUPT,
|
||||
NON_MASKABLE_INTERRUPT,
|
||||
OTHERS,
|
||||
};
|
||||
|
||||
class RiscvFault : public FaultBase
|
||||
{
|
||||
protected:
|
||||
const FaultName _name;
|
||||
const bool _interrupt;
|
||||
const FaultType _fault_type;
|
||||
ExceptionCode _code;
|
||||
|
||||
RiscvFault(FaultName n, bool i, ExceptionCode c)
|
||||
: _name(n), _interrupt(i), _code(c)
|
||||
RiscvFault(FaultName n, FaultType ft, ExceptionCode c)
|
||||
: _name(n), _fault_type(ft), _code(c)
|
||||
{}
|
||||
|
||||
FaultName name() const override { return _name; }
|
||||
bool isInterrupt() const { return _interrupt; }
|
||||
bool isInterrupt() const { return _fault_type == FaultType::INTERRUPT; }
|
||||
bool isNonMaskableInterrupt() const
|
||||
{
|
||||
return _fault_type == FaultType::NON_MASKABLE_INTERRUPT;
|
||||
}
|
||||
ExceptionCode exception() const { return _code; }
|
||||
virtual RegVal trap_value() const { return 0; }
|
||||
|
||||
@@ -132,10 +143,22 @@ class Reset : public FaultBase
|
||||
class InterruptFault : public RiscvFault
|
||||
{
|
||||
public:
|
||||
InterruptFault(ExceptionCode c) : RiscvFault("interrupt", true, c) {}
|
||||
InterruptFault(ExceptionCode c)
|
||||
: RiscvFault("interrupt", FaultType::INTERRUPT, c)
|
||||
{}
|
||||
InterruptFault(int c) : InterruptFault(static_cast<ExceptionCode>(c)) {}
|
||||
};
|
||||
|
||||
class NonMaskableInterruptFault : public RiscvFault
|
||||
{
|
||||
public:
|
||||
NonMaskableInterruptFault()
|
||||
: RiscvFault("non_maskable_interrupt",
|
||||
FaultType::NON_MASKABLE_INTERRUPT,
|
||||
static_cast<ExceptionCode>(0))
|
||||
{}
|
||||
};
|
||||
|
||||
class InstFault : public RiscvFault
|
||||
{
|
||||
protected:
|
||||
@@ -143,7 +166,7 @@ class InstFault : public RiscvFault
|
||||
|
||||
public:
|
||||
InstFault(FaultName n, const ExtMachInst inst)
|
||||
: RiscvFault(n, false, INST_ILLEGAL), _inst(inst)
|
||||
: RiscvFault(n, FaultType::OTHERS, INST_ILLEGAL), _inst(inst)
|
||||
{}
|
||||
|
||||
RegVal trap_value() const override { return _inst; }
|
||||
@@ -208,7 +231,7 @@ class AddressFault : public RiscvFault
|
||||
|
||||
public:
|
||||
AddressFault(const Addr addr, ExceptionCode code)
|
||||
: RiscvFault("Address", false, code), _addr(addr)
|
||||
: RiscvFault("Address", FaultType::OTHERS, code), _addr(addr)
|
||||
{}
|
||||
|
||||
RegVal trap_value() const override { return _addr; }
|
||||
@@ -221,7 +244,7 @@ class BreakpointFault : public RiscvFault
|
||||
|
||||
public:
|
||||
BreakpointFault(const PCState &pc)
|
||||
: RiscvFault("Breakpoint", false, BREAKPOINT), pcState(pc)
|
||||
: RiscvFault("Breakpoint", FaultType::OTHERS, BREAKPOINT), pcState(pc)
|
||||
{}
|
||||
|
||||
RegVal trap_value() const override { return pcState.pc(); }
|
||||
@@ -232,7 +255,7 @@ class SyscallFault : public RiscvFault
|
||||
{
|
||||
public:
|
||||
SyscallFault(PrivilegeMode prv)
|
||||
: RiscvFault("System call", false, ECALL_USER)
|
||||
: RiscvFault("System call", FaultType::OTHERS, ECALL_USER)
|
||||
{
|
||||
switch (prv) {
|
||||
case PRV_U:
|
||||
|
||||
@@ -105,16 +105,24 @@ class Interrupts : public BaseInterrupts
|
||||
return std::bitset<NumInterruptTypes>(mask);
|
||||
}
|
||||
|
||||
bool
|
||||
checkNonMaskableInterrupt() const
|
||||
{
|
||||
return tc->readMiscReg(MISCREG_NMIP) & tc->readMiscReg(MISCREG_NMIE);
|
||||
}
|
||||
|
||||
bool checkInterrupt(int num) const { return ip[num] && ie[num]; }
|
||||
bool checkInterrupts() const
|
||||
{
|
||||
return (ip & ie & globalMask()).any();
|
||||
return checkNonMaskableInterrupt() || (ip & ie & globalMask()).any();
|
||||
}
|
||||
|
||||
Fault
|
||||
getInterrupt()
|
||||
{
|
||||
assert(checkInterrupts());
|
||||
if (checkNonMaskableInterrupt())
|
||||
return std::make_shared<NonMaskableInterruptFault>();
|
||||
std::bitset<NumInterruptTypes> mask = globalMask();
|
||||
const std::vector<int> interrupt_order {
|
||||
INT_EXT_MACHINE, INT_TIMER_MACHINE, INT_SOFTWARE_MACHINE,
|
||||
@@ -143,11 +151,15 @@ class Interrupts : public BaseInterrupts
|
||||
ip[int_num] = false;
|
||||
}
|
||||
|
||||
void postNMI() { tc->setMiscReg(MISCREG_NMIP, 1); }
|
||||
void clearNMI() { tc->setMiscReg(MISCREG_NMIP, 0); }
|
||||
|
||||
void
|
||||
clearAll()
|
||||
{
|
||||
DPRINTF(Interrupt, "All interrupts cleared\n");
|
||||
ip = 0;
|
||||
clearNMI();
|
||||
}
|
||||
|
||||
uint64_t readIP() const { return (uint64_t)ip.to_ulong(); }
|
||||
|
||||
@@ -185,6 +185,10 @@ namespace RiscvISA
|
||||
[MISCREG_UTVAL] = "UTVAL",
|
||||
[MISCREG_FFLAGS] = "FFLAGS",
|
||||
[MISCREG_FRM] = "FRM",
|
||||
|
||||
[MISCREG_NMIVEC] = "NMIVEC",
|
||||
[MISCREG_NMIE] = "NMIE",
|
||||
[MISCREG_NMIP] = "NMIP",
|
||||
}};
|
||||
|
||||
ISA::ISA(const Params &p) : BaseISA(p)
|
||||
@@ -232,6 +236,8 @@ void ISA::clear()
|
||||
// don't set it to zero; software may try to determine the supported
|
||||
// triggers, starting at zero. simply set a different value here.
|
||||
miscRegFile[MISCREG_TSELECT] = 1;
|
||||
// NMI is always enabled.
|
||||
miscRegFile[MISCREG_NMIE] = 1;
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -1420,6 +1420,7 @@ decode QUADRANT default Unknown::unknown() {
|
||||
} else {
|
||||
STATUS status = xc->readMiscReg(MISCREG_STATUS);
|
||||
xc->setMiscReg(MISCREG_PRV, status.mpp);
|
||||
xc->setMiscReg(MISCREG_NMIE, 1);
|
||||
status.mie = status.mpie;
|
||||
status.mpie = 1;
|
||||
status.mpp = PRV_U;
|
||||
|
||||
@@ -187,6 +187,16 @@ enum MiscRegIndex
|
||||
MISCREG_FFLAGS,
|
||||
MISCREG_FRM,
|
||||
|
||||
// These registers are not in the standard, hence does not exist in the
|
||||
// CSRData map. These are mainly used to provide a minimal implementation
|
||||
// for non-maskable-interrupt in our simple cpu.
|
||||
// non-maskable-interrupt-vector-base-address: NMI version of xTVEC
|
||||
MISCREG_NMIVEC,
|
||||
// non-maskable-interrupt-enable: NMI version of xIE
|
||||
MISCREG_NMIE,
|
||||
// non-maskable-interrupt-pending: NMI version of xIP
|
||||
MISCREG_NMIP,
|
||||
|
||||
NUM_MISCREGS
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user