Add timer and soft IRQ support to rvcpp. Relevant sw_testcases now pass.
This commit is contained in:
parent
b1be56fe94
commit
a313493371
|
@ -5,7 +5,7 @@ EXECUTABLE:=rvcpp
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all:
|
all:
|
||||||
g++ -std=c++17 -O3 -Wall -Wno-parentheses -I $(PWD)/include $(SRCS) -o $(EXECUTABLE)
|
g++ -std=c++17 -O3 -Wall -Wno-parentheses -I include $(SRCS) -o $(EXECUTABLE)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(EXECUTABLE)
|
rm -f $(EXECUTABLE)
|
||||||
|
|
|
@ -3,6 +3,12 @@
|
||||||
#include "rv_types.h"
|
#include "rv_types.h"
|
||||||
|
|
||||||
class RVCSR {
|
class RVCSR {
|
||||||
|
|
||||||
|
// Latched IRQ signals into core
|
||||||
|
bool irq_t;
|
||||||
|
bool irq_s;
|
||||||
|
bool irq_e;
|
||||||
|
|
||||||
// Current core privilege level (M/S/U)
|
// Current core privilege level (M/S/U)
|
||||||
uint priv;
|
uint priv;
|
||||||
|
|
||||||
|
@ -22,6 +28,11 @@ class RVCSR {
|
||||||
std::optional<ux_t> pending_write_addr;
|
std::optional<ux_t> pending_write_addr;
|
||||||
ux_t pending_write_data;
|
ux_t pending_write_data;
|
||||||
|
|
||||||
|
ux_t get_effective_xip();
|
||||||
|
|
||||||
|
// Internal interface for updating trap state. Returns trap target pc.
|
||||||
|
ux_t trap_enter(uint xcause, ux_t xepc);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -31,12 +42,15 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
RVCSR() {
|
RVCSR() {
|
||||||
|
irq_t = false;
|
||||||
|
irq_s = false;
|
||||||
|
irq_e = false;
|
||||||
priv = 3;
|
priv = 3;
|
||||||
mcycle = 0;
|
mcycle = 0;
|
||||||
mcycleh = 0;
|
mcycleh = 0;
|
||||||
minstret = 0;
|
minstret = 0;
|
||||||
minstreth = 0;
|
minstreth = 0;
|
||||||
mcountinhibit = 0;
|
mcountinhibit = 0x5;
|
||||||
mstatus = 0;
|
mstatus = 0;
|
||||||
mie = 0;
|
mie = 0;
|
||||||
mip = 0;
|
mip = 0;
|
||||||
|
@ -55,13 +69,36 @@ public:
|
||||||
// Returns false on permission/decode fail
|
// Returns false on permission/decode fail
|
||||||
bool write(uint16_t addr, ux_t data, uint op=WRITE);
|
bool write(uint16_t addr, ux_t data, uint op=WRITE);
|
||||||
|
|
||||||
// Update trap state (including change of privilege level), return trap target PC
|
// Determine target privilege level of an exception, update trap state
|
||||||
ux_t trap_enter(uint xcause, ux_t xepc);
|
// (including change of privilege level), return trap target PC
|
||||||
|
ux_t trap_enter_exception(uint xcause, ux_t xepc);
|
||||||
|
|
||||||
|
// If there is currently a pending IRQ that must be entered, then
|
||||||
|
// determine its target privilege level, update trap state, and return
|
||||||
|
// trap target PC. Otherwise return None.
|
||||||
|
std::optional<ux_t> trap_check_enter_irq(ux_t xepc);
|
||||||
|
|
||||||
// Update trap state, return mepc:
|
// Update trap state, return mepc:
|
||||||
ux_t trap_mret();
|
ux_t trap_mret();
|
||||||
|
|
||||||
uint getpriv() {
|
uint get_true_priv() {
|
||||||
return priv;
|
return priv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_irq_t(bool irq) {
|
||||||
|
irq_t = irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_irq_s(bool irq) {
|
||||||
|
irq_s = irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_irq_e(bool irq) {
|
||||||
|
irq_e = irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
ux_t get_xcause() {
|
||||||
|
return mcause;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,21 +78,104 @@ struct TBExitException {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TBMemIO: MemBase32 {
|
struct TBMemIO: MemBase32 {
|
||||||
|
|
||||||
|
enum {
|
||||||
|
IO_PRINT_CHAR = 0x000,
|
||||||
|
IO_PRINT_U32 = 0x004,
|
||||||
|
IO_EXIT = 0x008,
|
||||||
|
IO_SET_SOFTIRQ = 0x010,
|
||||||
|
IO_CLR_SOFTIRQ = 0x014,
|
||||||
|
IO_GLOBMON_EN = 0x018,
|
||||||
|
IO_SET_IRQ = 0x020,
|
||||||
|
IO_CLR_IRQ = 0x030,
|
||||||
|
IO_MTIME = 0x100,
|
||||||
|
IO_MTIMEH = 0x104,
|
||||||
|
IO_MTIMECMP = 0x108,
|
||||||
|
IO_MTIMECMPH = 0x10c,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t mtime;
|
||||||
|
uint64_t mtimecmp;
|
||||||
|
bool softirq;
|
||||||
|
bool trace;
|
||||||
|
|
||||||
|
TBMemIO(bool trace_) {
|
||||||
|
mtime = 0;
|
||||||
|
mtimecmp = 0; // -1 would be better, but match tb and tests
|
||||||
|
softirq = false;
|
||||||
|
trace = trace_;
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool w32(ux_t addr, uint32_t data) {
|
virtual bool w32(ux_t addr, uint32_t data) {
|
||||||
switch (addr) {
|
switch (addr) {
|
||||||
case 0x0:
|
case IO_PRINT_CHAR:
|
||||||
|
if (trace)
|
||||||
|
printf("IO_PRINT_CHAR: %c\n", (char)data);
|
||||||
|
else
|
||||||
printf("%c", (char)data);
|
printf("%c", (char)data);
|
||||||
return true;
|
return true;
|
||||||
case 0x4:
|
case IO_PRINT_U32:
|
||||||
|
if (trace)
|
||||||
|
printf("IO_PRINT_U32: %08x\n", data);
|
||||||
|
else
|
||||||
printf("%08x\n", data);
|
printf("%08x\n", data);
|
||||||
return true;
|
return true;
|
||||||
case 0x8:
|
case IO_EXIT:
|
||||||
throw TBExitException(data);
|
throw TBExitException(data);
|
||||||
return true;
|
return true;
|
||||||
|
case IO_SET_SOFTIRQ:
|
||||||
|
softirq = softirq || (data & 0x1);
|
||||||
|
return true;
|
||||||
|
case IO_CLR_SOFTIRQ:
|
||||||
|
softirq = softirq && !(data & 0x1);
|
||||||
|
return true;
|
||||||
|
case IO_MTIME:
|
||||||
|
mtime = (mtime & 0xffffffff00000000ull) | data;
|
||||||
|
return true;
|
||||||
|
case IO_MTIMEH:
|
||||||
|
mtime = (mtime & 0x00000000ffffffffull) | ((uint64_t)data << 32);
|
||||||
|
return true;
|
||||||
|
case IO_MTIMECMP:
|
||||||
|
mtimecmp = (mtimecmp & 0xffffffff00000000ull) | data;
|
||||||
|
return true;
|
||||||
|
case IO_MTIMECMPH:
|
||||||
|
mtimecmp = (mtimecmp & 0x00000000ffffffffull) | ((uint64_t)data << 32);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual std::optional<uint32_t> r32(ux_t addr) {
|
||||||
|
switch(addr) {
|
||||||
|
case IO_MTIME:
|
||||||
|
return mtime & 0xffffffffull;
|
||||||
|
case IO_MTIMEH:
|
||||||
|
return mtime >> 32;
|
||||||
|
case IO_MTIMECMP:
|
||||||
|
return mtimecmp & 0xffffffffull;
|
||||||
|
case IO_MTIMECMPH:
|
||||||
|
return mtimecmp >> 32;
|
||||||
|
case IO_SET_SOFTIRQ:
|
||||||
|
case IO_CLR_SOFTIRQ:
|
||||||
|
return softirq;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void step() {
|
||||||
|
mtime++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timer_irq_pending() {
|
||||||
|
return mtime >= mtimecmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool soft_irq_pending() {
|
||||||
|
return softirq;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MemMap32: MemBase32 {
|
struct MemMap32: MemBase32 {
|
||||||
|
|
|
@ -106,9 +106,9 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TBMemIO io;
|
TBMemIO io(trace_execution);
|
||||||
MemMap32 mem;
|
MemMap32 mem;
|
||||||
mem.add(0x80000000u, 12, &io);
|
mem.add(0x80000000u, 0x1000, &io);
|
||||||
|
|
||||||
RVCore core(mem, RAM_BASE + 0x40, RAM_BASE, ram_size);
|
RVCore core(mem, RAM_BASE + 0x40, RAM_BASE, ram_size);
|
||||||
|
|
||||||
|
@ -126,8 +126,12 @@ int main(int argc, char **argv) {
|
||||||
int64_t cyc;
|
int64_t cyc;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
try {
|
try {
|
||||||
for (cyc = 0; cyc < max_cycles; ++cyc)
|
for (cyc = 0; cyc < max_cycles; ++cyc) {
|
||||||
core.step(trace_execution);
|
core.step(trace_execution);
|
||||||
|
io.step();
|
||||||
|
core.csr.set_irq_t(io.timer_irq_pending());
|
||||||
|
core.csr.set_irq_s(io.soft_irq_pending());
|
||||||
|
}
|
||||||
if (propagate_return_code)
|
if (propagate_return_code)
|
||||||
rc = -1;
|
rc = -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,10 @@ void RVCore::step(bool trace) {
|
||||||
std::optional<uint> exception_cause;
|
std::optional<uint> exception_cause;
|
||||||
uint regnum_rd = 0;
|
uint regnum_rd = 0;
|
||||||
|
|
||||||
|
std::optional<ux_t> trace_csr_addr;
|
||||||
|
std::optional<ux_t> trace_csr_result;
|
||||||
|
std::optional<uint> trace_priv;
|
||||||
|
|
||||||
std::optional<uint16_t> fetch0 = r16(pc);
|
std::optional<uint16_t> fetch0 = r16(pc);
|
||||||
std::optional<uint16_t> fetch1 = r16(pc + 2);
|
std::optional<uint16_t> fetch1 = r16(pc + 2);
|
||||||
uint32_t instr = *fetch0 | ((uint32_t)*fetch1 << 16);
|
uint32_t instr = *fetch0 | ((uint32_t)*fetch1 << 16);
|
||||||
|
@ -139,7 +143,10 @@ void RVCore::step(bool trace) {
|
||||||
uint funct3 = instr >> 12 & 0x7;
|
uint funct3 = instr >> 12 & 0x7;
|
||||||
uint funct7 = instr >> 25 & 0x7f;
|
uint funct7 = instr >> 25 & 0x7f;
|
||||||
|
|
||||||
if (!fetch0 || ((*fetch0 & 0x3) == 0x3 && !fetch1)) {
|
std::optional<ux_t> irq_target_pc = csr.trap_check_enter_irq(pc);
|
||||||
|
if (irq_target_pc) {
|
||||||
|
// Replace current instruction with IRQ entry
|
||||||
|
} else if (!fetch0 || ((*fetch0 & 0x3) == 0x3 && !fetch1)) {
|
||||||
exception_cause = XCAUSE_INSTR_FAULT;
|
exception_cause = XCAUSE_INSTR_FAULT;
|
||||||
} else if ((instr & 0x3) == 0x3) {
|
} else if ((instr & 0x3) == 0x3) {
|
||||||
// 32-bit instruction
|
// 32-bit instruction
|
||||||
|
@ -547,18 +554,33 @@ void RVCore::step(bool trace) {
|
||||||
else if (funct3 >= 0b101 && funct3 <= 0b111) {
|
else if (funct3 >= 0b101 && funct3 <= 0b111) {
|
||||||
// csrrwi, csrrsi, csrrci
|
// csrrwi, csrrsi, csrrci
|
||||||
uint write_op = funct3 - 0b101;
|
uint write_op = funct3 - 0b101;
|
||||||
if (write_op != RVCSR::WRITE || regnum_rd != 0)
|
if (write_op != RVCSR::WRITE || regnum_rd != 0) {
|
||||||
rd_wdata = csr.read(csr_addr);
|
rd_wdata = csr.read(csr_addr);
|
||||||
if (write_op == RVCSR::WRITE || regnum_rs1 != 0)
|
if (!rd_wdata) {
|
||||||
csr.write(csr_addr, regnum_rs1, write_op);
|
exception_cause = XCAUSE_INSTR_ILLEGAL;
|
||||||
|
}
|
||||||
|
if (trace && !exception_cause) {
|
||||||
|
trace_csr_addr = csr_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (write_op == RVCSR::WRITE || regnum_rs1 != 0) {
|
||||||
|
if (!csr.write(csr_addr, regnum_rs1, write_op)) {
|
||||||
|
exception_cause = XCAUSE_INSTR_ILLEGAL;
|
||||||
|
}
|
||||||
|
if (trace && !exception_cause) {
|
||||||
|
trace_csr_addr = csr_addr;
|
||||||
|
trace_csr_result = csr.read(csr_addr, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (RVOPC_MATCH(instr, MRET)) {
|
} else if (RVOPC_MATCH(instr, MRET)) {
|
||||||
if (csr.getpriv() == PRV_M) {
|
if (csr.get_true_priv() == PRV_M) {
|
||||||
pc_wdata = csr.trap_mret();
|
pc_wdata = csr.trap_mret();
|
||||||
|
trace_priv = csr.get_true_priv();
|
||||||
} else {
|
} else {
|
||||||
exception_cause = XCAUSE_INSTR_ILLEGAL;
|
exception_cause = XCAUSE_INSTR_ILLEGAL;
|
||||||
}
|
}
|
||||||
} else if (RVOPC_MATCH(instr, ECALL)) {
|
} else if (RVOPC_MATCH(instr, ECALL)) {
|
||||||
exception_cause = XCAUSE_ECALL_U + csr.getpriv();
|
exception_cause = XCAUSE_ECALL_U + csr.get_true_priv();
|
||||||
} else if (RVOPC_MATCH(instr, EBREAK)) {
|
} else if (RVOPC_MATCH(instr, EBREAK)) {
|
||||||
exception_cause = XCAUSE_EBREAK;
|
exception_cause = XCAUSE_EBREAK;
|
||||||
} else {
|
} else {
|
||||||
|
@ -774,30 +796,49 @@ void RVCore::step(bool trace) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (trace) {
|
if (trace && !irq_target_pc) {
|
||||||
printf("%08x: ", pc);
|
printf("%08x: ", pc);
|
||||||
if ((instr & 0x3) == 0x3) {
|
if ((instr & 0x3) == 0x3) {
|
||||||
printf("%08x : ", instr);
|
printf("%08x : ", instr);
|
||||||
} else {
|
} else {
|
||||||
printf(" %04x : ", instr & 0xffffu);
|
printf(" %04x : ", instr & 0xffffu);
|
||||||
}
|
}
|
||||||
if (regnum_rd != 0 && rd_wdata) {
|
bool gpr_writeback = regnum_rd != 0 && rd_wdata;
|
||||||
printf("%-3s <- %08x ", friendly_reg_names[regnum_rd], *rd_wdata);
|
if (gpr_writeback) {
|
||||||
|
printf("%-3s <- %08x :\n", friendly_reg_names[regnum_rd], *rd_wdata);
|
||||||
|
} else if (pc_wdata) {
|
||||||
|
printf("pc <- %08x <\n", *pc_wdata);
|
||||||
} else {
|
} else {
|
||||||
printf(" ");
|
printf(" :\n");
|
||||||
}
|
}
|
||||||
if (pc_wdata) {
|
if (pc_wdata && gpr_writeback) {
|
||||||
printf(": pc <- %08x\n", *pc_wdata);
|
printf(" : pc <- %08x <\n", *pc_wdata);
|
||||||
} else {
|
}
|
||||||
printf(":\n");
|
if (trace_csr_result) {
|
||||||
|
printf(" : #%03x <- %08x :\n", *trace_csr_addr, *trace_csr_result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure pending CSR writes are applied before checking IRQ conditions
|
||||||
|
csr.step();
|
||||||
|
|
||||||
if (exception_cause) {
|
if (exception_cause) {
|
||||||
pc_wdata = csr.trap_enter(*exception_cause, pc);
|
pc_wdata = csr.trap_enter_exception(*exception_cause, pc);
|
||||||
if (trace) {
|
if (trace) {
|
||||||
printf("Trap cause %2u: pc <- %08x\n", *exception_cause, *pc_wdata);
|
printf("^^^ Trap : cause <- %-2u :\n", *exception_cause);
|
||||||
|
printf("||| : pc <- %08x <\n", *pc_wdata);
|
||||||
|
trace_priv = csr.get_true_priv();
|
||||||
}
|
}
|
||||||
|
} else if (irq_target_pc) {
|
||||||
|
pc_wdata = irq_target_pc;
|
||||||
|
if (trace) {
|
||||||
|
printf("^^^ IRQ : cause <- IRQ + %-2u :\n", csr.get_xcause() & ((1u << 31) - 1));
|
||||||
|
printf("||| : pc <- %08x <\n", *pc_wdata);
|
||||||
|
trace_priv = csr.get_true_priv();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trace && trace_priv) {
|
||||||
|
printf("||| : priv <- %c :\n", "US.M"[*trace_priv & 0x3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pc_wdata)
|
if (pc_wdata)
|
||||||
|
@ -806,5 +847,4 @@ void RVCore::step(bool trace) {
|
||||||
pc = pc + ((instr & 0x3) == 0x3 ? 4 : 2);
|
pc = pc + ((instr & 0x3) == 0x3 ? 4 : 2);
|
||||||
if (rd_wdata && regnum_rd != 0)
|
if (rd_wdata && regnum_rd != 0)
|
||||||
regs[regnum_rd] = *rd_wdata;
|
regs[regnum_rd] = *rd_wdata;
|
||||||
csr.step();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,14 @@
|
||||||
#define GETBITS(x, msb, lsb) (((x) & BITRANGE(msb, lsb)) >> (lsb))
|
#define GETBITS(x, msb, lsb) (((x) & BITRANGE(msb, lsb)) >> (lsb))
|
||||||
#define GETBIT(x, bit) (((x) >> (bit)) & 1u)
|
#define GETBIT(x, bit) (((x) >> (bit)) & 1u)
|
||||||
|
|
||||||
|
|
||||||
|
ux_t RVCSR::get_effective_xip() {
|
||||||
|
return mip |
|
||||||
|
(irq_s ? MIP_MSIP : 0) |
|
||||||
|
(irq_t ? MIP_MTIP : 0) |
|
||||||
|
(irq_e ? MIP_MEIP : 0);
|
||||||
|
}
|
||||||
|
|
||||||
void RVCSR::step() {
|
void RVCSR::step() {
|
||||||
uint64_t mcycle_64 = ((uint64_t)mcycleh << 32) | mcycle;
|
uint64_t mcycle_64 = ((uint64_t)mcycleh << 32) | mcycle;
|
||||||
uint64_t minstret_64 = ((uint64_t)minstreth << 32) | minstret;
|
uint64_t minstret_64 = ((uint64_t)minstreth << 32) | minstret;
|
||||||
|
@ -66,7 +74,7 @@ std::optional<ux_t> RVCSR::read(uint16_t addr, bool side_effect) {
|
||||||
|
|
||||||
case CSR_MSTATUS: return mstatus;
|
case CSR_MSTATUS: return mstatus;
|
||||||
case CSR_MIE: return mie;
|
case CSR_MIE: return mie;
|
||||||
case CSR_MIP: return mip;
|
case CSR_MIP: return get_effective_xip();
|
||||||
case CSR_MTVEC: return mtvec;
|
case CSR_MTVEC: return mtvec;
|
||||||
case CSR_MSCRATCH: return mscratch;
|
case CSR_MSCRATCH: return mscratch;
|
||||||
case CSR_MEPC: return mepc;
|
case CSR_MEPC: return mepc;
|
||||||
|
@ -126,6 +134,23 @@ bool RVCSR::write(uint16_t addr, ux_t data, uint op) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ux_t RVCSR::trap_enter_exception(uint xcause, ux_t xepc) {
|
||||||
|
assert(xcause < 32);
|
||||||
|
assert(!pending_write_addr);
|
||||||
|
return trap_enter(xcause, xepc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ux_t> RVCSR::trap_check_enter_irq(ux_t xepc) {
|
||||||
|
ux_t m_targeted_irqs = get_effective_xip() & mie;
|
||||||
|
bool take_m_irq = m_targeted_irqs && ((mstatus & MSTATUS_MIE) || priv < PRV_M);
|
||||||
|
if (take_m_irq) {
|
||||||
|
ux_t cause = (1u << 31) | __builtin_ctz(m_targeted_irqs);
|
||||||
|
return trap_enter(cause, xepc);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update trap state (including change of privilege level), return trap target PC
|
// Update trap state (including change of privilege level), return trap target PC
|
||||||
ux_t RVCSR::trap_enter(uint xcause, ux_t xepc) {
|
ux_t RVCSR::trap_enter(uint xcause, ux_t xepc) {
|
||||||
mstatus = (mstatus & ~MSTATUS_MPP) | (priv << 11);
|
mstatus = (mstatus & ~MSTATUS_MPP) | (priv << 11);
|
||||||
|
@ -147,6 +172,9 @@ ux_t RVCSR::trap_enter(uint xcause, ux_t xepc) {
|
||||||
// Update trap state, return mepc:
|
// Update trap state, return mepc:
|
||||||
ux_t RVCSR::trap_mret() {
|
ux_t RVCSR::trap_mret() {
|
||||||
priv = GETBITS(mstatus, 12, 11);
|
priv = GETBITS(mstatus, 12, 11);
|
||||||
|
mstatus &= ~MSTATUS_MPP;
|
||||||
|
if (priv != PRV_M)
|
||||||
|
mstatus &= ~MSTATUS_MPRV;
|
||||||
|
|
||||||
if (mstatus & MSTATUS_MPIE)
|
if (mstatus & MSTATUS_MPIE)
|
||||||
mstatus |= MSTATUS_MIE;
|
mstatus |= MSTATUS_MIE;
|
||||||
|
|
Loading…
Reference in New Issue