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:
Jui-min Lee
2021-11-03 16:39:39 +08:00
parent 636783856f
commit 20e045a05f
6 changed files with 79 additions and 12 deletions

View File

@@ -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);

View File

@@ -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:

View File

@@ -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(); }

View File

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

View File

@@ -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;

View File

@@ -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
};