|  | /* | 
|  | *  MicroBlaze helper routines. | 
|  | * | 
|  | *  Copyright (c) 2009 Edgar E. Iglesias <edgar.iglesias@gmail.com> | 
|  | *  Copyright (c) 2009-2012 PetaLogix Qld Pty Ltd. | 
|  | * | 
|  | * 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 "cpu.h" | 
|  | #include "qemu/host-utils.h" | 
|  |  | 
|  | #define D(x) | 
|  | #define DMMU(x) | 
|  |  | 
|  | #if defined(CONFIG_USER_ONLY) | 
|  |  | 
|  | void do_interrupt (CPUMBState *env) | 
|  | { | 
|  | env->exception_index = -1; | 
|  | env->res_addr = RES_ADDR_NONE; | 
|  | env->regs[14] = env->sregs[SR_PC]; | 
|  | } | 
|  |  | 
|  | int cpu_mb_handle_mmu_fault(CPUMBState * env, target_ulong address, int rw, | 
|  | int mmu_idx) | 
|  | { | 
|  | env->exception_index = 0xaa; | 
|  | cpu_dump_state(env, stderr, fprintf, 0); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | #else /* !CONFIG_USER_ONLY */ | 
|  |  | 
|  | int cpu_mb_handle_mmu_fault (CPUMBState *env, target_ulong address, int rw, | 
|  | int mmu_idx) | 
|  | { | 
|  | unsigned int hit; | 
|  | unsigned int mmu_available; | 
|  | int r = 1; | 
|  | int prot; | 
|  |  | 
|  | mmu_available = 0; | 
|  | if (env->pvr.regs[0] & PVR0_USE_MMU) { | 
|  | mmu_available = 1; | 
|  | if ((env->pvr.regs[0] & PVR0_PVR_FULL_MASK) | 
|  | && (env->pvr.regs[11] & PVR11_USE_MMU) != PVR11_USE_MMU) { | 
|  | mmu_available = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Translate if the MMU is available and enabled.  */ | 
|  | if (mmu_available && (env->sregs[SR_MSR] & MSR_VM)) { | 
|  | target_ulong vaddr, paddr; | 
|  | struct microblaze_mmu_lookup lu; | 
|  |  | 
|  | hit = mmu_translate(&env->mmu, &lu, address, rw, mmu_idx); | 
|  | if (hit) { | 
|  | vaddr = address & TARGET_PAGE_MASK; | 
|  | paddr = lu.paddr + vaddr - lu.vaddr; | 
|  |  | 
|  | DMMU(qemu_log("MMU map mmu=%d v=%x p=%x prot=%x\n", | 
|  | mmu_idx, vaddr, paddr, lu.prot)); | 
|  | tlb_set_page(env, vaddr, paddr, lu.prot, mmu_idx, TARGET_PAGE_SIZE); | 
|  | r = 0; | 
|  | } else { | 
|  | env->sregs[SR_EAR] = address; | 
|  | DMMU(qemu_log("mmu=%d miss v=%x\n", mmu_idx, address)); | 
|  |  | 
|  | switch (lu.err) { | 
|  | case ERR_PROT: | 
|  | env->sregs[SR_ESR] = rw == 2 ? 17 : 16; | 
|  | env->sregs[SR_ESR] |= (rw == 1) << 10; | 
|  | break; | 
|  | case ERR_MISS: | 
|  | env->sregs[SR_ESR] = rw == 2 ? 19 : 18; | 
|  | env->sregs[SR_ESR] |= (rw == 1) << 10; | 
|  | break; | 
|  | default: | 
|  | abort(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (env->exception_index == EXCP_MMU) { | 
|  | cpu_abort(env, "recursive faults\n"); | 
|  | } | 
|  |  | 
|  | /* TLB miss.  */ | 
|  | env->exception_index = EXCP_MMU; | 
|  | } | 
|  | } else { | 
|  | /* MMU disabled or not available.  */ | 
|  | address &= TARGET_PAGE_MASK; | 
|  | prot = PAGE_BITS; | 
|  | tlb_set_page(env, address, address, prot, mmu_idx, TARGET_PAGE_SIZE); | 
|  | r = 0; | 
|  | } | 
|  | return r; | 
|  | } | 
|  |  | 
|  | void do_interrupt(CPUMBState *env) | 
|  | { | 
|  | uint32_t t; | 
|  |  | 
|  | /* IMM flag cannot propagate across a branch and into the dslot.  */ | 
|  | assert(!((env->iflags & D_FLAG) && (env->iflags & IMM_FLAG))); | 
|  | assert(!(env->iflags & (DRTI_FLAG | DRTE_FLAG | DRTB_FLAG))); | 
|  | /*    assert(env->sregs[SR_MSR] & (MSR_EE)); Only for HW exceptions.  */ | 
|  | env->res_addr = RES_ADDR_NONE; | 
|  | switch (env->exception_index) { | 
|  | case EXCP_HW_EXCP: | 
|  | if (!(env->pvr.regs[0] & PVR0_USE_EXC_MASK)) { | 
|  | qemu_log("Exception raised on system without exceptions!\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | env->regs[17] = env->sregs[SR_PC] + 4; | 
|  | env->sregs[SR_ESR] &= ~(1 << 12); | 
|  |  | 
|  | /* Exception breaks branch + dslot sequence?  */ | 
|  | if (env->iflags & D_FLAG) { | 
|  | env->sregs[SR_ESR] |= 1 << 12 ; | 
|  | env->sregs[SR_BTR] = env->btarget; | 
|  | } | 
|  |  | 
|  | /* Disable the MMU.  */ | 
|  | t = (env->sregs[SR_MSR] & (MSR_VM | MSR_UM)) << 1; | 
|  | env->sregs[SR_MSR] &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM); | 
|  | env->sregs[SR_MSR] |= t; | 
|  | /* Exception in progress.  */ | 
|  | env->sregs[SR_MSR] |= MSR_EIP; | 
|  |  | 
|  | qemu_log_mask(CPU_LOG_INT, | 
|  | "hw exception at pc=%x ear=%x esr=%x iflags=%x\n", | 
|  | env->sregs[SR_PC], env->sregs[SR_EAR], | 
|  | env->sregs[SR_ESR], env->iflags); | 
|  | log_cpu_state_mask(CPU_LOG_INT, env, 0); | 
|  | env->iflags &= ~(IMM_FLAG | D_FLAG); | 
|  | env->sregs[SR_PC] = 0x20; | 
|  | break; | 
|  |  | 
|  | case EXCP_MMU: | 
|  | env->regs[17] = env->sregs[SR_PC]; | 
|  |  | 
|  | env->sregs[SR_ESR] &= ~(1 << 12); | 
|  | /* Exception breaks branch + dslot sequence?  */ | 
|  | if (env->iflags & D_FLAG) { | 
|  | D(qemu_log("D_FLAG set at exception bimm=%d\n", env->bimm)); | 
|  | env->sregs[SR_ESR] |= 1 << 12 ; | 
|  | env->sregs[SR_BTR] = env->btarget; | 
|  |  | 
|  | /* Reexecute the branch.  */ | 
|  | env->regs[17] -= 4; | 
|  | /* was the branch immprefixed?.  */ | 
|  | if (env->bimm) { | 
|  | qemu_log_mask(CPU_LOG_INT, | 
|  | "bimm exception at pc=%x iflags=%x\n", | 
|  | env->sregs[SR_PC], env->iflags); | 
|  | env->regs[17] -= 4; | 
|  | log_cpu_state_mask(CPU_LOG_INT, env, 0); | 
|  | } | 
|  | } else if (env->iflags & IMM_FLAG) { | 
|  | D(qemu_log("IMM_FLAG set at exception\n")); | 
|  | env->regs[17] -= 4; | 
|  | } | 
|  |  | 
|  | /* Disable the MMU.  */ | 
|  | t = (env->sregs[SR_MSR] & (MSR_VM | MSR_UM)) << 1; | 
|  | env->sregs[SR_MSR] &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM); | 
|  | env->sregs[SR_MSR] |= t; | 
|  | /* Exception in progress.  */ | 
|  | env->sregs[SR_MSR] |= MSR_EIP; | 
|  |  | 
|  | qemu_log_mask(CPU_LOG_INT, | 
|  | "exception at pc=%x ear=%x iflags=%x\n", | 
|  | env->sregs[SR_PC], env->sregs[SR_EAR], env->iflags); | 
|  | log_cpu_state_mask(CPU_LOG_INT, env, 0); | 
|  | env->iflags &= ~(IMM_FLAG | D_FLAG); | 
|  | env->sregs[SR_PC] = 0x20; | 
|  | break; | 
|  |  | 
|  | case EXCP_IRQ: | 
|  | assert(!(env->sregs[SR_MSR] & (MSR_EIP | MSR_BIP))); | 
|  | assert(env->sregs[SR_MSR] & MSR_IE); | 
|  | assert(!(env->iflags & D_FLAG)); | 
|  |  | 
|  | t = (env->sregs[SR_MSR] & (MSR_VM | MSR_UM)) << 1; | 
|  |  | 
|  | #if 0 | 
|  | #include "disas/disas.h" | 
|  |  | 
|  | /* Useful instrumentation when debugging interrupt issues in either | 
|  | the models or in sw.  */ | 
|  | { | 
|  | const char *sym; | 
|  |  | 
|  | sym = lookup_symbol(env->sregs[SR_PC]); | 
|  | if (sym | 
|  | && (!strcmp("netif_rx", sym) | 
|  | || !strcmp("process_backlog", sym))) { | 
|  |  | 
|  | qemu_log( | 
|  | "interrupt at pc=%x msr=%x %x iflags=%x sym=%s\n", | 
|  | env->sregs[SR_PC], env->sregs[SR_MSR], t, env->iflags, | 
|  | sym); | 
|  |  | 
|  | log_cpu_state(env, 0); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | qemu_log_mask(CPU_LOG_INT, | 
|  | "interrupt at pc=%x msr=%x %x iflags=%x\n", | 
|  | env->sregs[SR_PC], env->sregs[SR_MSR], t, env->iflags); | 
|  |  | 
|  | env->sregs[SR_MSR] &= ~(MSR_VMS | MSR_UMS | MSR_VM \ | 
|  | | MSR_UM | MSR_IE); | 
|  | env->sregs[SR_MSR] |= t; | 
|  |  | 
|  | env->regs[14] = env->sregs[SR_PC]; | 
|  | env->sregs[SR_PC] = 0x10; | 
|  | //log_cpu_state_mask(CPU_LOG_INT, env, 0); | 
|  | break; | 
|  |  | 
|  | case EXCP_BREAK: | 
|  | case EXCP_HW_BREAK: | 
|  | assert(!(env->iflags & IMM_FLAG)); | 
|  | assert(!(env->iflags & D_FLAG)); | 
|  | t = (env->sregs[SR_MSR] & (MSR_VM | MSR_UM)) << 1; | 
|  | qemu_log_mask(CPU_LOG_INT, | 
|  | "break at pc=%x msr=%x %x iflags=%x\n", | 
|  | env->sregs[SR_PC], env->sregs[SR_MSR], t, env->iflags); | 
|  | log_cpu_state_mask(CPU_LOG_INT, env, 0); | 
|  | env->sregs[SR_MSR] &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM); | 
|  | env->sregs[SR_MSR] |= t; | 
|  | env->sregs[SR_MSR] |= MSR_BIP; | 
|  | if (env->exception_index == EXCP_HW_BREAK) { | 
|  | env->regs[16] = env->sregs[SR_PC]; | 
|  | env->sregs[SR_MSR] |= MSR_BIP; | 
|  | env->sregs[SR_PC] = 0x18; | 
|  | } else | 
|  | env->sregs[SR_PC] = env->btarget; | 
|  | break; | 
|  | default: | 
|  | cpu_abort(env, "unhandled exception type=%d\n", | 
|  | env->exception_index); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | hwaddr cpu_get_phys_page_debug(CPUMBState * env, target_ulong addr) | 
|  | { | 
|  | target_ulong vaddr, paddr = 0; | 
|  | struct microblaze_mmu_lookup lu; | 
|  | unsigned int hit; | 
|  |  | 
|  | if (env->sregs[SR_MSR] & MSR_VM) { | 
|  | hit = mmu_translate(&env->mmu, &lu, addr, 0, 0); | 
|  | if (hit) { | 
|  | vaddr = addr & TARGET_PAGE_MASK; | 
|  | paddr = lu.paddr + vaddr - lu.vaddr; | 
|  | } else | 
|  | paddr = 0; /* ???.  */ | 
|  | } else | 
|  | paddr = addr & TARGET_PAGE_MASK; | 
|  |  | 
|  | return paddr; | 
|  | } | 
|  | #endif |