qemu/target/nios2/helper.c
Richard Henderson e84f176844 target/nios2: Advance pc when raising exceptions
The exception return address for nios2 is the instruction
after the one that was executing at the time of the exception.

We have so far implemented this by advancing the pc during the
process of raising the exception.  It is perhaps a little less
confusing to do this advance in the translator (and helpers)
when raising the exception in the first place, so that we may
more closely match kernel sources.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-Id: <20220421151735.31996-58-richard.henderson@linaro.org>
2022-04-26 08:17:05 -07:00

373 lines
11 KiB
C

/*
* Altera Nios II helper routines.
*
* Copyright (c) 2012 Chris Wulff <crwulff@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/lgpl-2.1.html>
*/
#include "qemu/osdep.h"
#include "cpu.h"
#include "qemu/host-utils.h"
#include "exec/exec-all.h"
#include "exec/cpu_ldst.h"
#include "exec/log.h"
#include "exec/helper-proto.h"
#include "semihosting/semihost.h"
static void do_exception(Nios2CPU *cpu, uint32_t exception_addr,
uint32_t tlbmisc_set, bool is_break)
{
CPUNios2State *env = &cpu->env;
CPUState *cs = CPU(cpu);
uint32_t old_status = env->ctrl[CR_STATUS];
uint32_t new_status = old_status;
/* With shadow regs, exceptions are always taken into CRS 0. */
new_status &= ~R_CR_STATUS_CRS_MASK;
env->regs = env->shadow_regs[0];
if ((old_status & CR_STATUS_EH) == 0) {
int r_ea = R_EA, cr_es = CR_ESTATUS;
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;
}
/*
* 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));
}
new_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;
}
static void do_iic_irq(Nios2CPU *cpu)
{
do_exception(cpu, cpu->exception_addr, 0, false);
}
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;
}
env->ctrl[CR_STATUS] = new_status;
nios2_update_crs(env);
env->pc = cpu->rha;
}
void nios2_cpu_do_interrupt(CPUState *cs)
{
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
uint32_t tlbmisc_set = 0;
if (qemu_loglevel_mask(CPU_LOG_INT)) {
const char *name = NULL;
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("Unknown exception %d at pc=0x%08x\n",
cs->exception_index, env->pc);
}
}
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_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);
}
break;
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;
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:
do_exception(cpu, cpu->exception_addr, 0, false);
break;
case EXCP_BREAK:
do_exception(cpu, cpu->exception_addr, 0, true);
break;
case EXCP_SEMIHOST:
do_nios2_semihosting(env);
break;
default:
cpu_abort(cs, "unhandled exception type=%d\n", cs->exception_index);
}
}
hwaddr nios2_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
{
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
target_ulong vaddr, paddr = 0;
Nios2MMULookup lu;
unsigned int hit;
if (cpu->mmu_present && (addr < 0xC0000000)) {
hit = mmu_translate(env, &lu, addr, 0, 0);
if (hit) {
vaddr = addr & TARGET_PAGE_MASK;
paddr = lu.paddr + vaddr - lu.vaddr;
} else {
paddr = -1;
qemu_log("cpu_get_phys_page debug MISS: %#" PRIx64 "\n", addr);
}
} else {
paddr = addr & TARGET_PAGE_MASK;
}
return paddr;
}
void nios2_cpu_do_unaligned_access(CPUState *cs, vaddr addr,
MMUAccessType access_type,
int mmu_idx, uintptr_t retaddr)
{
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
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,
MMUAccessType access_type, int mmu_idx,
bool probe, uintptr_t retaddr)
{
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
unsigned int excp;
target_ulong vaddr, paddr;
Nios2MMULookup lu;
unsigned int hit;
if (!cpu->mmu_present) {
/* No MMU */
address &= TARGET_PAGE_MASK;
tlb_set_page(cs, address, address, PAGE_BITS,
mmu_idx, TARGET_PAGE_SIZE);
return true;
}
if (MMU_SUPERVISOR_IDX == mmu_idx) {
if (address >= 0xC0000000) {
/* Kernel physical page - TLB bypassed */
address &= TARGET_PAGE_MASK;
tlb_set_page(cs, address, address, PAGE_BITS,
mmu_idx, TARGET_PAGE_SIZE);
return true;
}
} else {
if (address >= 0x80000000) {
/* Illegal access from user mode */
if (probe) {
return false;
}
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);
}
}
/* Virtual page. */
hit = mmu_translate(env, &lu, address, access_type, mmu_idx);
if (hit) {
vaddr = address & TARGET_PAGE_MASK;
paddr = lu.paddr + vaddr - lu.vaddr;
if (((access_type == MMU_DATA_LOAD) && (lu.prot & PAGE_READ)) ||
((access_type == MMU_DATA_STORE) && (lu.prot & PAGE_WRITE)) ||
((access_type == MMU_INST_FETCH) && (lu.prot & PAGE_EXEC))) {
tlb_set_page(cs, vaddr, paddr, lu.prot,
mmu_idx, TARGET_PAGE_SIZE);
return true;
}
/* Permission violation */
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;
}
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->ctrl[CR_BADADDR] = address;
nios2_cpu_loop_exit_advance(env, retaddr);
}