summaryrefslogtreecommitdiffstats
path: root/target/nios2/helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/nios2/helper.c')
-rw-r--r--target/nios2/helper.c363
1 files changed, 210 insertions, 153 deletions
diff --git a/target/nios2/helper.c b/target/nios2/helper.c
index e5c98650e1..bb3b09e5a7 100644
--- a/target/nios2/helper.c
+++ b/target/nios2/helper.c
@@ -28,176 +28,234 @@
#include "exec/helper-proto.h"
#include "semihosting/semihost.h"
-#if defined(CONFIG_USER_ONLY)
-void nios2_cpu_do_interrupt(CPUState *cs)
+static void do_exception(Nios2CPU *cpu, uint32_t exception_addr,
+ uint32_t tlbmisc_set, bool is_break)
{
- Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- cs->exception_index = -1;
- env->regs[R_EA] = env->regs[R_PC] + 4;
-}
+ CPUState *cs = CPU(cpu);
+ uint32_t old_status = env->ctrl[CR_STATUS];
+ uint32_t new_status = old_status;
-void nios2_cpu_record_sigsegv(CPUState *cs, vaddr addr,
- MMUAccessType access_type,
- bool maperr, uintptr_t retaddr)
-{
- /* FIXME: Disentangle kuser page from linux-user sigsegv handling. */
- cs->exception_index = 0xaa;
- cpu_loop_exit_restore(cs, retaddr);
-}
+ /* With shadow regs, exceptions are always taken into CRS 0. */
+ new_status &= ~R_CR_STATUS_CRS_MASK;
+ env->regs = env->shadow_regs[0];
-#else /* !CONFIG_USER_ONLY */
+ if ((old_status & CR_STATUS_EH) == 0) {
+ int r_ea = R_EA, cr_es = CR_ESTATUS;
-void nios2_cpu_do_interrupt(CPUState *cs)
-{
- Nios2CPU *cpu = NIOS2_CPU(cs);
- CPUNios2State *env = &cpu->env;
+ if (is_break) {
+ r_ea = R_BA;
+ cr_es = CR_BSTATUS;
+ }
+ env->ctrl[cr_es] = old_status;
+ env->regs[r_ea] = env->pc;
+
+ if (cpu->mmu_present) {
+ new_status |= CR_STATUS_EH;
+
+ /*
+ * There are 4 bits that are always written.
+ * Explicitly clear them, to be set via the argument.
+ */
+ env->ctrl[CR_TLBMISC] &= ~(CR_TLBMISC_D |
+ CR_TLBMISC_PERM |
+ CR_TLBMISC_BAD |
+ CR_TLBMISC_DBL);
+ env->ctrl[CR_TLBMISC] |= tlbmisc_set;
+ }
- switch (cs->exception_index) {
- case EXCP_IRQ:
- assert(env->regs[CR_STATUS] & CR_STATUS_PIE);
+ /*
+ * With shadow regs, and EH == 0, PRS is set from CRS.
+ * At least, so says Table 3-9, and some other text,
+ * though Table 3-38 says otherwise.
+ */
+ new_status = FIELD_DP32(new_status, CR_STATUS, PRS,
+ FIELD_EX32(old_status, CR_STATUS, CRS));
+ }
- qemu_log_mask(CPU_LOG_INT, "interrupt at pc=%x\n", env->regs[R_PC]);
+ new_status &= ~(CR_STATUS_PIE | CR_STATUS_U);
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[CR_STATUS] |= CR_STATUS_IH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
+ env->ctrl[CR_STATUS] = new_status;
+ if (!is_break) {
+ env->ctrl[CR_EXCEPTION] = FIELD_DP32(0, CR_EXCEPTION, CAUSE,
+ cs->exception_index);
+ }
+ env->pc = exception_addr;
+}
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
+static void do_iic_irq(Nios2CPU *cpu)
+{
+ do_exception(cpu, cpu->exception_addr, 0, false);
+}
- env->regs[R_EA] = env->regs[R_PC] + 4;
- env->regs[R_PC] = cpu->exception_addr;
- break;
+static void do_eic_irq(Nios2CPU *cpu)
+{
+ CPUNios2State *env = &cpu->env;
+ uint32_t old_status = env->ctrl[CR_STATUS];
+ uint32_t new_status = old_status;
+ uint32_t old_rs = FIELD_EX32(old_status, CR_STATUS, CRS);
+ uint32_t new_rs = cpu->rrs;
+
+ new_status = FIELD_DP32(new_status, CR_STATUS, CRS, new_rs);
+ new_status = FIELD_DP32(new_status, CR_STATUS, IL, cpu->ril);
+ new_status = FIELD_DP32(new_status, CR_STATUS, NMI, cpu->rnmi);
+ new_status &= ~(CR_STATUS_RSIE | CR_STATUS_U);
+ new_status |= CR_STATUS_IH;
+
+ if (!(new_status & CR_STATUS_EH)) {
+ new_status = FIELD_DP32(new_status, CR_STATUS, PRS, old_rs);
+ if (new_rs == 0) {
+ env->ctrl[CR_ESTATUS] = old_status;
+ } else {
+ if (new_rs != old_rs) {
+ old_status |= CR_STATUS_SRS;
+ }
+ env->shadow_regs[new_rs][R_SSTATUS] = old_status;
+ }
+ env->shadow_regs[new_rs][R_EA] = env->pc;
+ }
- case EXCP_TLBD:
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- qemu_log_mask(CPU_LOG_INT, "TLB MISS (fast) at pc=%x\n",
- env->regs[R_PC]);
+ env->ctrl[CR_STATUS] = new_status;
+ nios2_update_crs(env);
- /* Fast TLB miss */
- /* Variation from the spec. Table 3-35 of the cpu reference shows
- * estatus not being changed for TLB miss but this appears to
- * be incorrect. */
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
+ env->pc = cpu->rha;
+}
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
+void nios2_cpu_do_interrupt(CPUState *cs)
+{
+ Nios2CPU *cpu = NIOS2_CPU(cs);
+ CPUNios2State *env = &cpu->env;
+ uint32_t tlbmisc_set = 0;
- env->regs[CR_TLBMISC] &= ~CR_TLBMISC_DBL;
- env->regs[CR_TLBMISC] |= CR_TLBMISC_WR;
+ if (qemu_loglevel_mask(CPU_LOG_INT)) {
+ const char *name = NULL;
- env->regs[R_EA] = env->regs[R_PC] + 4;
- env->regs[R_PC] = cpu->fast_tlb_miss_addr;
+ switch (cs->exception_index) {
+ case EXCP_IRQ:
+ name = "interrupt";
+ break;
+ case EXCP_TLB_X:
+ case EXCP_TLB_D:
+ if (env->ctrl[CR_STATUS] & CR_STATUS_EH) {
+ name = "TLB MISS (double)";
+ } else {
+ name = "TLB MISS (fast)";
+ }
+ break;
+ case EXCP_PERM_R:
+ case EXCP_PERM_W:
+ case EXCP_PERM_X:
+ name = "TLB PERM";
+ break;
+ case EXCP_SUPERA_X:
+ case EXCP_SUPERA_D:
+ name = "SUPERVISOR (address)";
+ break;
+ case EXCP_SUPERI:
+ name = "SUPERVISOR (insn)";
+ break;
+ case EXCP_ILLEGAL:
+ name = "ILLEGAL insn";
+ break;
+ case EXCP_UNALIGN:
+ name = "Misaligned (data)";
+ break;
+ case EXCP_UNALIGND:
+ name = "Misaligned (destination)";
+ break;
+ case EXCP_DIV:
+ name = "DIV error";
+ break;
+ case EXCP_TRAP:
+ name = "TRAP insn";
+ break;
+ case EXCP_BREAK:
+ name = "BREAK insn";
+ break;
+ case EXCP_SEMIHOST:
+ name = "SEMIHOST insn";
+ break;
+ }
+ if (name) {
+ qemu_log("%s at pc=0x%08x\n", name, env->pc);
} else {
- qemu_log_mask(CPU_LOG_INT, "TLB MISS (double) at pc=%x\n",
- env->regs[R_PC]);
-
- /* Double TLB miss */
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- env->regs[CR_TLBMISC] |= CR_TLBMISC_DBL;
+ qemu_log("Unknown exception %d at pc=0x%08x\n",
+ cs->exception_index, env->pc);
+ }
+ }
- env->regs[R_PC] = cpu->exception_addr;
+ switch (cs->exception_index) {
+ case EXCP_IRQ:
+ /* Note that PC is advanced for interrupts as well. */
+ env->pc += 4;
+ if (cpu->eic_present) {
+ do_eic_irq(cpu);
+ } else {
+ do_iic_irq(cpu);
}
break;
- case EXCP_TLBR:
- case EXCP_TLBW:
- case EXCP_TLBX:
- qemu_log_mask(CPU_LOG_INT, "TLB PERM at pc=%x\n", env->regs[R_PC]);
-
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_TLBMISC] |= CR_TLBMISC_WR;
+ case EXCP_TLB_D:
+ tlbmisc_set = CR_TLBMISC_D;
+ /* fall through */
+ case EXCP_TLB_X:
+ if (env->ctrl[CR_STATUS] & CR_STATUS_EH) {
+ tlbmisc_set |= CR_TLBMISC_DBL;
+ /*
+ * Normally, we don't write to tlbmisc unless !EH,
+ * so do it manually for the double-tlb miss exception.
+ */
+ env->ctrl[CR_TLBMISC] &= ~(CR_TLBMISC_D |
+ CR_TLBMISC_PERM |
+ CR_TLBMISC_BAD);
+ env->ctrl[CR_TLBMISC] |= tlbmisc_set;
+ do_exception(cpu, cpu->exception_addr, 0, false);
+ } else {
+ tlbmisc_set |= CR_TLBMISC_WE;
+ do_exception(cpu, cpu->fast_tlb_miss_addr, tlbmisc_set, false);
}
-
- env->regs[R_EA] = env->regs[R_PC] + 4;
- env->regs[R_PC] = cpu->exception_addr;
break;
- case EXCP_SUPERA:
- case EXCP_SUPERI:
- case EXCP_SUPERD:
- qemu_log_mask(CPU_LOG_INT, "SUPERVISOR exception at pc=%x\n",
- env->regs[R_PC]);
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[R_EA] = env->regs[R_PC] + 4;
+ case EXCP_PERM_R:
+ case EXCP_PERM_W:
+ tlbmisc_set = CR_TLBMISC_D;
+ /* fall through */
+ case EXCP_PERM_X:
+ tlbmisc_set |= CR_TLBMISC_PERM;
+ if (!(env->ctrl[CR_STATUS] & CR_STATUS_EH)) {
+ tlbmisc_set |= CR_TLBMISC_WE;
}
+ do_exception(cpu, cpu->exception_addr, tlbmisc_set, false);
+ break;
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- env->regs[R_PC] = cpu->exception_addr;
+ case EXCP_SUPERA_D:
+ case EXCP_UNALIGN:
+ tlbmisc_set = CR_TLBMISC_D;
+ /* fall through */
+ case EXCP_SUPERA_X:
+ case EXCP_UNALIGND:
+ tlbmisc_set |= CR_TLBMISC_BAD;
+ do_exception(cpu, cpu->exception_addr, tlbmisc_set, false);
break;
+ case EXCP_SUPERI:
case EXCP_ILLEGAL:
+ case EXCP_DIV:
case EXCP_TRAP:
- qemu_log_mask(CPU_LOG_INT, "TRAP exception at pc=%x\n",
- env->regs[R_PC]);
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[R_EA] = env->regs[R_PC] + 4;
- }
-
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- env->regs[R_PC] = cpu->exception_addr;
+ do_exception(cpu, cpu->exception_addr, 0, false);
break;
case EXCP_BREAK:
- qemu_log_mask(CPU_LOG_INT, "BREAK exception at pc=%x\n",
- env->regs[R_PC]);
- /* The semihosting instruction is "break 1". */
- if (semihosting_enabled() &&
- cpu_ldl_code(env, env->regs[R_PC]) == 0x003da07a) {
- qemu_log_mask(CPU_LOG_INT, "Entering semihosting\n");
- env->regs[R_PC] += 4;
- do_nios2_semihosting(env);
- break;
- }
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_BSTATUS] = env->regs[CR_STATUS];
- env->regs[R_BA] = env->regs[R_PC] + 4;
- }
-
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
+ do_exception(cpu, cpu->exception_addr, 0, true);
+ break;
- env->regs[R_PC] = cpu->exception_addr;
+ case EXCP_SEMIHOST:
+ do_nios2_semihosting(env);
break;
default:
- cpu_abort(cs, "unhandled exception type=%d\n",
- cs->exception_index);
- break;
+ cpu_abort(cs, "unhandled exception type=%d\n", cs->exception_index);
}
}
@@ -232,9 +290,9 @@ void nios2_cpu_do_unaligned_access(CPUState *cs, vaddr addr,
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- env->regs[CR_BADADDR] = addr;
- env->regs[CR_EXCEPTION] = EXCP_UNALIGN << 2;
- helper_raise_exception(env, EXCP_UNALIGN);
+ env->ctrl[CR_BADADDR] = addr;
+ cs->exception_index = EXCP_UNALIGN;
+ nios2_cpu_loop_exit_advance(env, retaddr);
}
bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
@@ -243,7 +301,7 @@ bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
{
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- unsigned int excp = EXCP_TLBD;
+ unsigned int excp;
target_ulong vaddr, paddr;
Nios2MMULookup lu;
unsigned int hit;
@@ -270,9 +328,10 @@ bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
if (probe) {
return false;
}
- cs->exception_index = EXCP_SUPERA;
- env->regs[CR_BADADDR] = address;
- cpu_loop_exit_restore(cs, retaddr);
+ cs->exception_index = (access_type == MMU_INST_FETCH
+ ? EXCP_SUPERA_X : EXCP_SUPERA_D);
+ env->ctrl[CR_BADADDR] = address;
+ nios2_cpu_loop_exit_advance(env, retaddr);
}
}
@@ -291,25 +350,23 @@ bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
}
/* Permission violation */
- excp = (access_type == MMU_DATA_LOAD ? EXCP_TLBR :
- access_type == MMU_DATA_STORE ? EXCP_TLBW : EXCP_TLBX);
+ excp = (access_type == MMU_DATA_LOAD ? EXCP_PERM_R :
+ access_type == MMU_DATA_STORE ? EXCP_PERM_W : EXCP_PERM_X);
+ } else {
+ excp = (access_type == MMU_INST_FETCH ? EXCP_TLB_X: EXCP_TLB_D);
}
if (probe) {
return false;
}
- if (access_type == MMU_INST_FETCH) {
- env->regs[CR_TLBMISC] &= ~CR_TLBMISC_D;
- } else {
- env->regs[CR_TLBMISC] |= CR_TLBMISC_D;
- }
- env->regs[CR_PTEADDR] &= CR_PTEADDR_PTBASE_MASK;
- env->regs[CR_PTEADDR] |= (address >> 10) & CR_PTEADDR_VPN_MASK;
- env->mmu.pteaddr_wr = env->regs[CR_PTEADDR];
+ env->ctrl[CR_TLBMISC] = FIELD_DP32(env->ctrl[CR_TLBMISC], CR_TLBMISC, D,
+ access_type != MMU_INST_FETCH);
+ env->ctrl[CR_PTEADDR] = FIELD_DP32(env->ctrl[CR_PTEADDR], CR_PTEADDR, VPN,
+ address >> TARGET_PAGE_BITS);
+ env->mmu.pteaddr_wr = env->ctrl[CR_PTEADDR];
cs->exception_index = excp;
- env->regs[CR_BADADDR] = address;
- cpu_loop_exit_restore(cs, retaddr);
+ env->ctrl[CR_BADADDR] = address;
+ nios2_cpu_loop_exit_advance(env, retaddr);
}
-#endif /* !CONFIG_USER_ONLY */