QEMU uses a fixed page size for the CPU TLB. If the guest uses large pages then we effectively split these into multiple smaller pages, and populate the corresponding TLB entries on demand. When the guest invalidates the TLB by virtual address we must invalidate all entries covered by the large page. However the address used to invalidate the entry may not be present in the QEMU TLB, so we do not know which regions to clear. Implementing a full vaiable size TLB is hard and slow, so just keep a simple address/mask pair to record which addresses may have been mapped by large pages. If the guest invalidates this region then flush the whole TLB. Signed-off-by: Paul Brook <paul@codesourcery.com>
260 lines
6.4 KiB
C
260 lines
6.4 KiB
C
/*
|
|
* CRIS helper routines.
|
|
*
|
|
* Copyright (c) 2007 AXIS Communications AB
|
|
* Written by Edgar E. Iglesias.
|
|
*
|
|
* 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 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/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "config.h"
|
|
#include "cpu.h"
|
|
#include "mmu.h"
|
|
#include "exec-all.h"
|
|
#include "host-utils.h"
|
|
|
|
|
|
//#define CRIS_HELPER_DEBUG
|
|
|
|
|
|
#ifdef CRIS_HELPER_DEBUG
|
|
#define D(x) x
|
|
#define D_LOG(...) qemu_log(__VA__ARGS__)
|
|
#else
|
|
#define D(x)
|
|
#define D_LOG(...) do { } while (0)
|
|
#endif
|
|
|
|
#if defined(CONFIG_USER_ONLY)
|
|
|
|
void do_interrupt (CPUState *env)
|
|
{
|
|
env->exception_index = -1;
|
|
env->pregs[PR_ERP] = env->pc;
|
|
}
|
|
|
|
int cpu_cris_handle_mmu_fault(CPUState * env, target_ulong address, int rw,
|
|
int mmu_idx, int is_softmmu)
|
|
{
|
|
env->exception_index = 0xaa;
|
|
env->pregs[PR_EDA] = address;
|
|
cpu_dump_state(env, stderr, fprintf, 0);
|
|
return 1;
|
|
}
|
|
|
|
#else /* !CONFIG_USER_ONLY */
|
|
|
|
|
|
static void cris_shift_ccs(CPUState *env)
|
|
{
|
|
uint32_t ccs;
|
|
/* Apply the ccs shift. */
|
|
ccs = env->pregs[PR_CCS];
|
|
ccs = ((ccs & 0xc0000000) | ((ccs << 12) >> 2)) & ~0x3ff;
|
|
env->pregs[PR_CCS] = ccs;
|
|
}
|
|
|
|
int cpu_cris_handle_mmu_fault (CPUState *env, target_ulong address, int rw,
|
|
int mmu_idx, int is_softmmu)
|
|
{
|
|
struct cris_mmu_result res;
|
|
int prot, miss;
|
|
int r = -1;
|
|
target_ulong phy;
|
|
|
|
D(printf ("%s addr=%x pc=%x rw=%x\n", __func__, address, env->pc, rw));
|
|
miss = cris_mmu_translate(&res, env, address & TARGET_PAGE_MASK,
|
|
rw, mmu_idx);
|
|
if (miss)
|
|
{
|
|
if (env->exception_index == EXCP_BUSFAULT)
|
|
cpu_abort(env,
|
|
"CRIS: Illegal recursive bus fault."
|
|
"addr=%x rw=%d\n",
|
|
address, rw);
|
|
|
|
env->pregs[PR_EDA] = address;
|
|
env->exception_index = EXCP_BUSFAULT;
|
|
env->fault_vector = res.bf_vec;
|
|
r = 1;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Mask off the cache selection bit. The ETRAX busses do not
|
|
* see the top bit.
|
|
*/
|
|
phy = res.phy & ~0x80000000;
|
|
prot = res.prot;
|
|
tlb_set_page(env, address & TARGET_PAGE_MASK, phy,
|
|
prot | PAGE_EXEC, mmu_idx, TARGET_PAGE_SIZE);
|
|
r = 0;
|
|
}
|
|
if (r > 0)
|
|
D_LOG("%s returns %d irqreq=%x addr=%x"
|
|
" phy=%x ismmu=%d vec=%x pc=%x\n",
|
|
__func__, r, env->interrupt_request,
|
|
address, res.phy, is_softmmu, res.bf_vec, env->pc);
|
|
return r;
|
|
}
|
|
|
|
static void do_interruptv10(CPUState *env)
|
|
{
|
|
int ex_vec = -1;
|
|
|
|
D_LOG( "exception index=%d interrupt_req=%d\n",
|
|
env->exception_index,
|
|
env->interrupt_request);
|
|
|
|
assert(!(env->pregs[PR_CCS] & PFIX_FLAG));
|
|
switch (env->exception_index)
|
|
{
|
|
case EXCP_BREAK:
|
|
/* These exceptions are genereated by the core itself.
|
|
ERP should point to the insn following the brk. */
|
|
ex_vec = env->trap_vector;
|
|
env->pregs[PR_ERP] = env->pc;
|
|
break;
|
|
|
|
case EXCP_NMI:
|
|
/* NMI is hardwired to vector zero. */
|
|
ex_vec = 0;
|
|
env->pregs[PR_CCS] &= ~M_FLAG;
|
|
env->pregs[PR_NRP] = env->pc;
|
|
break;
|
|
|
|
case EXCP_BUSFAULT:
|
|
assert(0);
|
|
break;
|
|
|
|
default:
|
|
/* The interrupt controller gives us the vector. */
|
|
ex_vec = env->interrupt_vector;
|
|
/* Normal interrupts are taken between
|
|
TB's. env->pc is valid here. */
|
|
env->pregs[PR_ERP] = env->pc;
|
|
break;
|
|
}
|
|
|
|
if (env->pregs[PR_CCS] & U_FLAG) {
|
|
/* Swap stack pointers. */
|
|
env->pregs[PR_USP] = env->regs[R_SP];
|
|
env->regs[R_SP] = env->ksp;
|
|
}
|
|
|
|
/* Now that we are in kernel mode, load the handlers address. */
|
|
env->pc = ldl_code(env->pregs[PR_EBP] + ex_vec * 4);
|
|
env->locked_irq = 1;
|
|
|
|
qemu_log_mask(CPU_LOG_INT, "%s isr=%x vec=%x ccs=%x pid=%d erp=%x\n",
|
|
__func__, env->pc, ex_vec,
|
|
env->pregs[PR_CCS],
|
|
env->pregs[PR_PID],
|
|
env->pregs[PR_ERP]);
|
|
}
|
|
|
|
void do_interrupt(CPUState *env)
|
|
{
|
|
int ex_vec = -1;
|
|
|
|
if (env->pregs[PR_VR] < 32)
|
|
return do_interruptv10(env);
|
|
|
|
D_LOG( "exception index=%d interrupt_req=%d\n",
|
|
env->exception_index,
|
|
env->interrupt_request);
|
|
|
|
switch (env->exception_index)
|
|
{
|
|
case EXCP_BREAK:
|
|
/* These exceptions are genereated by the core itself.
|
|
ERP should point to the insn following the brk. */
|
|
ex_vec = env->trap_vector;
|
|
env->pregs[PR_ERP] = env->pc;
|
|
break;
|
|
|
|
case EXCP_NMI:
|
|
/* NMI is hardwired to vector zero. */
|
|
ex_vec = 0;
|
|
env->pregs[PR_CCS] &= ~M_FLAG;
|
|
env->pregs[PR_NRP] = env->pc;
|
|
break;
|
|
|
|
case EXCP_BUSFAULT:
|
|
ex_vec = env->fault_vector;
|
|
env->pregs[PR_ERP] = env->pc;
|
|
break;
|
|
|
|
default:
|
|
/* The interrupt controller gives us the vector. */
|
|
ex_vec = env->interrupt_vector;
|
|
/* Normal interrupts are taken between
|
|
TB's. env->pc is valid here. */
|
|
env->pregs[PR_ERP] = env->pc;
|
|
break;
|
|
}
|
|
|
|
/* Fill in the IDX field. */
|
|
env->pregs[PR_EXS] = (ex_vec & 0xff) << 8;
|
|
|
|
if (env->dslot) {
|
|
D_LOG("excp isr=%x PC=%x ds=%d SP=%x"
|
|
" ERP=%x pid=%x ccs=%x cc=%d %x\n",
|
|
ex_vec, env->pc, env->dslot,
|
|
env->regs[R_SP],
|
|
env->pregs[PR_ERP], env->pregs[PR_PID],
|
|
env->pregs[PR_CCS],
|
|
env->cc_op, env->cc_mask);
|
|
/* We loose the btarget, btaken state here so rexec the
|
|
branch. */
|
|
env->pregs[PR_ERP] -= env->dslot;
|
|
/* Exception starts with dslot cleared. */
|
|
env->dslot = 0;
|
|
}
|
|
|
|
if (env->pregs[PR_CCS] & U_FLAG) {
|
|
/* Swap stack pointers. */
|
|
env->pregs[PR_USP] = env->regs[R_SP];
|
|
env->regs[R_SP] = env->ksp;
|
|
}
|
|
|
|
/* Apply the CRIS CCS shift. Clears U if set. */
|
|
cris_shift_ccs(env);
|
|
|
|
/* Now that we are in kernel mode, load the handlers address. */
|
|
env->pc = ldl_code(env->pregs[PR_EBP] + ex_vec * 4);
|
|
|
|
D_LOG("%s isr=%x vec=%x ccs=%x pid=%d erp=%x\n",
|
|
__func__, env->pc, ex_vec,
|
|
env->pregs[PR_CCS],
|
|
env->pregs[PR_PID],
|
|
env->pregs[PR_ERP]);
|
|
}
|
|
|
|
target_phys_addr_t cpu_get_phys_page_debug(CPUState * env, target_ulong addr)
|
|
{
|
|
uint32_t phy = addr;
|
|
struct cris_mmu_result res;
|
|
int miss;
|
|
miss = cris_mmu_translate(&res, env, addr, 0, 0);
|
|
if (!miss)
|
|
phy = res.phy;
|
|
D(fprintf(stderr, "%s %x -> %x\n", __func__, addr, phy));
|
|
return phy;
|
|
}
|
|
#endif
|