/* * m68k op helpers * * Copyright (c) 2006-2007 CodeSourcery * Written by Paul Brook * * 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 . */ #include "qemu/osdep.h" #include "cpu.h" #include "exec/exec-all.h" #include "exec/gdbstub.h" #include "exec/helper-proto.h" #include "fpu/softfloat.h" #include "qemu/qemu-print.h" #define SIGNBIT (1u << 31) /* Sort alphabetically, except for "any". */ static gint m68k_cpu_list_compare(gconstpointer a, gconstpointer b) { ObjectClass *class_a = (ObjectClass *)a; ObjectClass *class_b = (ObjectClass *)b; const char *name_a, *name_b; name_a = object_class_get_name(class_a); name_b = object_class_get_name(class_b); if (strcmp(name_a, "any-" TYPE_M68K_CPU) == 0) { return 1; } else if (strcmp(name_b, "any-" TYPE_M68K_CPU) == 0) { return -1; } else { return strcasecmp(name_a, name_b); } } static void m68k_cpu_list_entry(gpointer data, gpointer user_data) { ObjectClass *c = data; const char *typename; char *name; typename = object_class_get_name(c); name = g_strndup(typename, strlen(typename) - strlen("-" TYPE_M68K_CPU)); qemu_printf("%s\n", name); g_free(name); } void m68k_cpu_list(void) { GSList *list; list = object_class_get_list(TYPE_M68K_CPU, false); list = g_slist_sort(list, m68k_cpu_list_compare); g_slist_foreach(list, m68k_cpu_list_entry, NULL); g_slist_free(list); } static int cf_fpu_gdb_get_reg(CPUM68KState *env, GByteArray *mem_buf, int n) { if (n < 8) { float_status s; return gdb_get_reg64(mem_buf, floatx80_to_float64(env->fregs[n].d, &s)); } switch (n) { case 8: /* fpcontrol */ return gdb_get_reg32(mem_buf, env->fpcr); case 9: /* fpstatus */ return gdb_get_reg32(mem_buf, env->fpsr); case 10: /* fpiar, not implemented */ return gdb_get_reg32(mem_buf, 0); } return 0; } static int cf_fpu_gdb_set_reg(CPUM68KState *env, uint8_t *mem_buf, int n) { if (n < 8) { float_status s; env->fregs[n].d = float64_to_floatx80(ldfq_p(mem_buf), &s); return 8; } switch (n) { case 8: /* fpcontrol */ cpu_m68k_set_fpcr(env, ldl_p(mem_buf)); return 4; case 9: /* fpstatus */ env->fpsr = ldl_p(mem_buf); return 4; case 10: /* fpiar, not implemented */ return 4; } return 0; } static int m68k_fpu_gdb_get_reg(CPUM68KState *env, GByteArray *mem_buf, int n) { if (n < 8) { int len = gdb_get_reg16(mem_buf, env->fregs[n].l.upper); len += gdb_get_reg16(mem_buf + len, 0); len += gdb_get_reg64(mem_buf + len, env->fregs[n].l.lower); return len; } switch (n) { case 8: /* fpcontrol */ return gdb_get_reg32(mem_buf, env->fpcr); case 9: /* fpstatus */ return gdb_get_reg32(mem_buf, env->fpsr); case 10: /* fpiar, not implemented */ return gdb_get_reg32(mem_buf, 0); } return 0; } static int m68k_fpu_gdb_set_reg(CPUM68KState *env, uint8_t *mem_buf, int n) { if (n < 8) { env->fregs[n].l.upper = lduw_be_p(mem_buf); env->fregs[n].l.lower = ldq_be_p(mem_buf + 4); return 12; } switch (n) { case 8: /* fpcontrol */ cpu_m68k_set_fpcr(env, ldl_p(mem_buf)); return 4; case 9: /* fpstatus */ env->fpsr = ldl_p(mem_buf); return 4; case 10: /* fpiar, not implemented */ return 4; } return 0; } void m68k_cpu_init_gdb(M68kCPU *cpu) { CPUState *cs = CPU(cpu); CPUM68KState *env = &cpu->env; if (m68k_feature(env, M68K_FEATURE_CF_FPU)) { gdb_register_coprocessor(cs, cf_fpu_gdb_get_reg, cf_fpu_gdb_set_reg, 11, "cf-fp.xml", 18); } else if (m68k_feature(env, M68K_FEATURE_FPU)) { gdb_register_coprocessor(cs, m68k_fpu_gdb_get_reg, m68k_fpu_gdb_set_reg, 11, "m68k-fp.xml", 18); } /* TODO: Add [E]MAC registers. */ } void HELPER(cf_movec_to)(CPUM68KState *env, uint32_t reg, uint32_t val) { switch (reg) { case M68K_CR_CACR: env->cacr = val; m68k_switch_sp(env); break; case M68K_CR_ACR0: case M68K_CR_ACR1: case M68K_CR_ACR2: case M68K_CR_ACR3: /* TODO: Implement Access Control Registers. */ break; case M68K_CR_VBR: env->vbr = val; break; /* TODO: Implement control registers. */ default: cpu_abort(env_cpu(env), "Unimplemented control register write 0x%x = 0x%x\n", reg, val); } } void HELPER(m68k_movec_to)(CPUM68KState *env, uint32_t reg, uint32_t val) { switch (reg) { /* MC680[1234]0 */ case M68K_CR_SFC: env->sfc = val & 7; return; case M68K_CR_DFC: env->dfc = val & 7; return; case M68K_CR_VBR: env->vbr = val; return; /* MC680[2346]0 */ case M68K_CR_CACR: if (m68k_feature(env, M68K_FEATURE_M68020)) { env->cacr = val & 0x0000000f; } else if (m68k_feature(env, M68K_FEATURE_M68030)) { env->cacr = val & 0x00003f1f; } else if (m68k_feature(env, M68K_FEATURE_M68040)) { env->cacr = val & 0x80008000; } else if (m68k_feature(env, M68K_FEATURE_M68060)) { env->cacr = val & 0xf8e0e000; } m68k_switch_sp(env); return; /* MC680[34]0 */ case M68K_CR_TC: env->mmu.tcr = val; return; case M68K_CR_MMUSR: env->mmu.mmusr = val; return; case M68K_CR_SRP: env->mmu.srp = val; return; case M68K_CR_URP: env->mmu.urp = val; return; case M68K_CR_USP: env->sp[M68K_USP] = val; return; case M68K_CR_MSP: env->sp[M68K_SSP] = val; return; case M68K_CR_ISP: env->sp[M68K_ISP] = val; return; /* MC68040/MC68LC040 */ case M68K_CR_ITT0: env->mmu.ttr[M68K_ITTR0] = val; return; case M68K_CR_ITT1: env->mmu.ttr[M68K_ITTR1] = val; return; case M68K_CR_DTT0: env->mmu.ttr[M68K_DTTR0] = val; return; case M68K_CR_DTT1: env->mmu.ttr[M68K_DTTR1] = val; return; } cpu_abort(env_cpu(env), "Unimplemented control register write 0x%x = 0x%x\n", reg, val); } uint32_t HELPER(m68k_movec_from)(CPUM68KState *env, uint32_t reg) { switch (reg) { /* MC680[1234]0 */ case M68K_CR_SFC: return env->sfc; case M68K_CR_DFC: return env->dfc; case M68K_CR_VBR: return env->vbr; /* MC680[234]0 */ case M68K_CR_CACR: return env->cacr; /* MC680[34]0 */ case M68K_CR_TC: return env->mmu.tcr; case M68K_CR_MMUSR: return env->mmu.mmusr; case M68K_CR_SRP: return env->mmu.srp; case M68K_CR_USP: return env->sp[M68K_USP]; case M68K_CR_MSP: return env->sp[M68K_SSP]; case M68K_CR_ISP: return env->sp[M68K_ISP]; /* MC68040/MC68LC040 */ case M68K_CR_URP: return env->mmu.urp; case M68K_CR_ITT0: return env->mmu.ttr[M68K_ITTR0]; case M68K_CR_ITT1: return env->mmu.ttr[M68K_ITTR1]; case M68K_CR_DTT0: return env->mmu.ttr[M68K_DTTR0]; case M68K_CR_DTT1: return env->mmu.ttr[M68K_DTTR1]; } cpu_abort(env_cpu(env), "Unimplemented control register read 0x%x\n", reg); } void HELPER(set_macsr)(CPUM68KState *env, uint32_t val) { uint32_t acc; int8_t exthigh; uint8_t extlow; uint64_t regval; int i; if ((env->macsr ^ val) & (MACSR_FI | MACSR_SU)) { for (i = 0; i < 4; i++) { regval = env->macc[i]; exthigh = regval >> 40; if (env->macsr & MACSR_FI) { acc = regval >> 8; extlow = regval; } else { acc = regval; extlow = regval >> 32; } if (env->macsr & MACSR_FI) { regval = (((uint64_t)acc) << 8) | extlow; regval |= ((int64_t)exthigh) << 40; } else if (env->macsr & MACSR_SU) { regval = acc | (((int64_t)extlow) << 32); regval |= ((int64_t)exthigh) << 40; } else { regval = acc | (((uint64_t)extlow) << 32); regval |= ((uint64_t)(uint8_t)exthigh) << 40; } env->macc[i] = regval; } } env->macsr = val; } void m68k_switch_sp(CPUM68KState *env) { int new_sp; env->sp[env->current_sp] = env->aregs[7]; if (m68k_feature(env, M68K_FEATURE_M68000)) { if (env->sr & SR_S) { if (env->sr & SR_M) { new_sp = M68K_SSP; } else { new_sp = M68K_ISP; } } else { new_sp = M68K_USP; } } else { new_sp = (env->sr & SR_S && env->cacr & M68K_CACR_EUSP) ? M68K_SSP : M68K_USP; } env->aregs[7] = env->sp[new_sp]; env->current_sp = new_sp; } #if !defined(CONFIG_USER_ONLY) /* MMU: 68040 only */ static void print_address_zone(uint32_t logical, uint32_t physical, uint32_t size, int attr) { qemu_printf("%08x - %08x -> %08x - %08x %c ", logical, logical + size - 1, physical, physical + size - 1, attr & 4 ? 'W' : '-'); size >>= 10; if (size < 1024) { qemu_printf("(%d KiB)\n", size); } else { size >>= 10; if (size < 1024) { qemu_printf("(%d MiB)\n", size); } else { size >>= 10; qemu_printf("(%d GiB)\n", size); } } } static void dump_address_map(CPUM68KState *env, uint32_t root_pointer) { int i, j, k; int tic_size, tic_shift; uint32_t tib_mask; uint32_t tia, tib, tic; uint32_t logical = 0xffffffff, physical = 0xffffffff; uint32_t first_logical = 0xffffffff, first_physical = 0xffffffff; uint32_t last_logical, last_physical; int32_t size; int last_attr = -1, attr = -1; CPUState *cs = env_cpu(env); MemTxResult txres; if (env->mmu.tcr & M68K_TCR_PAGE_8K) { /* 8k page */ tic_size = 32; tic_shift = 13; tib_mask = M68K_8K_PAGE_MASK; } else { /* 4k page */ tic_size = 64; tic_shift = 12; tib_mask = M68K_4K_PAGE_MASK; } for (i = 0; i < M68K_ROOT_POINTER_ENTRIES; i++) { tia = address_space_ldl(cs->as, M68K_POINTER_BASE(root_pointer) + i * 4, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK || !M68K_UDT_VALID(tia)) { continue; } for (j = 0; j < M68K_ROOT_POINTER_ENTRIES; j++) { tib = address_space_ldl(cs->as, M68K_POINTER_BASE(tia) + j * 4, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK || !M68K_UDT_VALID(tib)) { continue; } for (k = 0; k < tic_size; k++) { tic = address_space_ldl(cs->as, (tib & tib_mask) + k * 4, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK || !M68K_PDT_VALID(tic)) { continue; } if (M68K_PDT_INDIRECT(tic)) { tic = address_space_ldl(cs->as, M68K_INDIRECT_POINTER(tic), MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { continue; } } last_logical = logical; logical = (i << M68K_TTS_ROOT_SHIFT) | (j << M68K_TTS_POINTER_SHIFT) | (k << tic_shift); last_physical = physical; physical = tic & ~((1 << tic_shift) - 1); last_attr = attr; attr = tic & ((1 << tic_shift) - 1); if ((logical != (last_logical + (1 << tic_shift))) || (physical != (last_physical + (1 << tic_shift))) || (attr & 4) != (last_attr & 4)) { if (first_logical != 0xffffffff) { size = last_logical + (1 << tic_shift) - first_logical; print_address_zone(first_logical, first_physical, size, last_attr); } first_logical = logical; first_physical = physical; } } } } if (first_logical != logical || (attr & 4) != (last_attr & 4)) { size = logical + (1 << tic_shift) - first_logical; print_address_zone(first_logical, first_physical, size, last_attr); } } #define DUMP_CACHEFLAGS(a) \ switch (a & M68K_DESC_CACHEMODE) { \ case M68K_DESC_CM_WRTHRU: /* cachable, write-through */ \ qemu_printf("T"); \ break; \ case M68K_DESC_CM_COPYBK: /* cachable, copyback */ \ qemu_printf("C"); \ break; \ case M68K_DESC_CM_SERIAL: /* noncachable, serialized */ \ qemu_printf("S"); \ break; \ case M68K_DESC_CM_NCACHE: /* noncachable */ \ qemu_printf("N"); \ break; \ } static void dump_ttr(uint32_t ttr) { if ((ttr & M68K_TTR_ENABLED) == 0) { qemu_printf("disabled\n"); return; } qemu_printf("Base: 0x%08x Mask: 0x%08x Control: ", ttr & M68K_TTR_ADDR_BASE, (ttr & M68K_TTR_ADDR_MASK) << M68K_TTR_ADDR_MASK_SHIFT); switch (ttr & M68K_TTR_SFIELD) { case M68K_TTR_SFIELD_USER: qemu_printf("U"); break; case M68K_TTR_SFIELD_SUPER: qemu_printf("S"); break; default: qemu_printf("*"); break; } DUMP_CACHEFLAGS(ttr); if (ttr & M68K_DESC_WRITEPROT) { qemu_printf("R"); } else { qemu_printf("W"); } qemu_printf(" U: %d\n", (ttr & M68K_DESC_USERATTR) >> M68K_DESC_USERATTR_SHIFT); } void dump_mmu(CPUM68KState *env) { if ((env->mmu.tcr & M68K_TCR_ENABLED) == 0) { qemu_printf("Translation disabled\n"); return; } qemu_printf("Page Size: "); if (env->mmu.tcr & M68K_TCR_PAGE_8K) { qemu_printf("8kB\n"); } else { qemu_printf("4kB\n"); } qemu_printf("MMUSR: "); if (env->mmu.mmusr & M68K_MMU_B_040) { qemu_printf("BUS ERROR\n"); } else { qemu_printf("Phy=%08x Flags: ", env->mmu.mmusr & 0xfffff000); /* flags found on the page descriptor */ if (env->mmu.mmusr & M68K_MMU_G_040) { qemu_printf("G"); /* Global */ } else { qemu_printf("."); } if (env->mmu.mmusr & M68K_MMU_S_040) { qemu_printf("S"); /* Supervisor */ } else { qemu_printf("."); } if (env->mmu.mmusr & M68K_MMU_M_040) { qemu_printf("M"); /* Modified */ } else { qemu_printf("."); } if (env->mmu.mmusr & M68K_MMU_WP_040) { qemu_printf("W"); /* Write protect */ } else { qemu_printf("."); } if (env->mmu.mmusr & M68K_MMU_T_040) { qemu_printf("T"); /* Transparent */ } else { qemu_printf("."); } if (env->mmu.mmusr & M68K_MMU_R_040) { qemu_printf("R"); /* Resident */ } else { qemu_printf("."); } qemu_printf(" Cache: "); DUMP_CACHEFLAGS(env->mmu.mmusr); qemu_printf(" U: %d\n", (env->mmu.mmusr >> 8) & 3); qemu_printf("\n"); } qemu_printf("ITTR0: "); dump_ttr(env->mmu.ttr[M68K_ITTR0]); qemu_printf("ITTR1: "); dump_ttr(env->mmu.ttr[M68K_ITTR1]); qemu_printf("DTTR0: "); dump_ttr(env->mmu.ttr[M68K_DTTR0]); qemu_printf("DTTR1: "); dump_ttr(env->mmu.ttr[M68K_DTTR1]); qemu_printf("SRP: 0x%08x\n", env->mmu.srp); dump_address_map(env, env->mmu.srp); qemu_printf("URP: 0x%08x\n", env->mmu.urp); dump_address_map(env, env->mmu.urp); } static int check_TTR(uint32_t ttr, int *prot, target_ulong addr, int access_type) { uint32_t base, mask; /* check if transparent translation is enabled */ if ((ttr & M68K_TTR_ENABLED) == 0) { return 0; } /* check mode access */ switch (ttr & M68K_TTR_SFIELD) { case M68K_TTR_SFIELD_USER: /* match only if user */ if ((access_type & ACCESS_SUPER) != 0) { return 0; } break; case M68K_TTR_SFIELD_SUPER: /* match only if supervisor */ if ((access_type & ACCESS_SUPER) == 0) { return 0; } break; default: /* all other values disable mode matching (FC2) */ break; } /* check address matching */ base = ttr & M68K_TTR_ADDR_BASE; mask = (ttr & M68K_TTR_ADDR_MASK) ^ M68K_TTR_ADDR_MASK; mask <<= M68K_TTR_ADDR_MASK_SHIFT; if ((addr & mask) != (base & mask)) { return 0; } *prot = PAGE_READ | PAGE_EXEC; if ((ttr & M68K_DESC_WRITEPROT) == 0) { *prot |= PAGE_WRITE; } return 1; } static int get_physical_address(CPUM68KState *env, hwaddr *physical, int *prot, target_ulong address, int access_type, target_ulong *page_size) { CPUState *cs = env_cpu(env); uint32_t entry; uint32_t next; target_ulong page_mask; bool debug = access_type & ACCESS_DEBUG; int page_bits; int i; MemTxResult txres; /* Transparent Translation (physical = logical) */ for (i = 0; i < M68K_MAX_TTR; i++) { if (check_TTR(env->mmu.TTR(access_type, i), prot, address, access_type)) { if (access_type & ACCESS_PTEST) { /* Transparent Translation Register bit */ env->mmu.mmusr = M68K_MMU_T_040 | M68K_MMU_R_040; } *physical = address & TARGET_PAGE_MASK; *page_size = TARGET_PAGE_SIZE; return 0; } } /* Page Table Root Pointer */ *prot = PAGE_READ | PAGE_WRITE; if (access_type & ACCESS_CODE) { *prot |= PAGE_EXEC; } if (access_type & ACCESS_SUPER) { next = env->mmu.srp; } else { next = env->mmu.urp; } /* Root Index */ entry = M68K_POINTER_BASE(next) | M68K_ROOT_INDEX(address); next = address_space_ldl(cs->as, entry, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } if (!M68K_UDT_VALID(next)) { return -1; } if (!(next & M68K_DESC_USED) && !debug) { address_space_stl(cs->as, entry, next | M68K_DESC_USED, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } } if (next & M68K_DESC_WRITEPROT) { if (access_type & ACCESS_PTEST) { env->mmu.mmusr |= M68K_MMU_WP_040; } *prot &= ~PAGE_WRITE; if (access_type & ACCESS_STORE) { return -1; } } /* Pointer Index */ entry = M68K_POINTER_BASE(next) | M68K_POINTER_INDEX(address); next = address_space_ldl(cs->as, entry, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } if (!M68K_UDT_VALID(next)) { return -1; } if (!(next & M68K_DESC_USED) && !debug) { address_space_stl(cs->as, entry, next | M68K_DESC_USED, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } } if (next & M68K_DESC_WRITEPROT) { if (access_type & ACCESS_PTEST) { env->mmu.mmusr |= M68K_MMU_WP_040; } *prot &= ~PAGE_WRITE; if (access_type & ACCESS_STORE) { return -1; } } /* Page Index */ if (env->mmu.tcr & M68K_TCR_PAGE_8K) { entry = M68K_8K_PAGE_BASE(next) | M68K_8K_PAGE_INDEX(address); } else { entry = M68K_4K_PAGE_BASE(next) | M68K_4K_PAGE_INDEX(address); } next = address_space_ldl(cs->as, entry, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } if (!M68K_PDT_VALID(next)) { return -1; } if (M68K_PDT_INDIRECT(next)) { next = address_space_ldl(cs->as, M68K_INDIRECT_POINTER(next), MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } } if (access_type & ACCESS_STORE) { if (next & M68K_DESC_WRITEPROT) { if (!(next & M68K_DESC_USED) && !debug) { address_space_stl(cs->as, entry, next | M68K_DESC_USED, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } } } else if ((next & (M68K_DESC_MODIFIED | M68K_DESC_USED)) != (M68K_DESC_MODIFIED | M68K_DESC_USED) && !debug) { address_space_stl(cs->as, entry, next | (M68K_DESC_MODIFIED | M68K_DESC_USED), MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } } } else { if (!(next & M68K_DESC_USED) && !debug) { address_space_stl(cs->as, entry, next | M68K_DESC_USED, MEMTXATTRS_UNSPECIFIED, &txres); if (txres != MEMTX_OK) { goto txfail; } } } if (env->mmu.tcr & M68K_TCR_PAGE_8K) { page_bits = 13; } else { page_bits = 12; } *page_size = 1 << page_bits; page_mask = ~(*page_size - 1); *physical = next & page_mask; if (access_type & ACCESS_PTEST) { env->mmu.mmusr |= next & M68K_MMU_SR_MASK_040; env->mmu.mmusr |= *physical & 0xfffff000; env->mmu.mmusr |= M68K_MMU_R_040; } if (next & M68K_DESC_WRITEPROT) { *prot &= ~PAGE_WRITE; if (access_type & ACCESS_STORE) { return -1; } } if (next & M68K_DESC_SUPERONLY) { if ((access_type & ACCESS_SUPER) == 0) { return -1; } } return 0; txfail: /* * A page table load/store failed. TODO: we should really raise a * suitable guest fault here if this is not a debug access. * For now just return that the translation failed. */ return -1; } hwaddr m68k_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) { M68kCPU *cpu = M68K_CPU(cs); CPUM68KState *env = &cpu->env; hwaddr phys_addr; int prot; int access_type; target_ulong page_size; if ((env->mmu.tcr & M68K_TCR_ENABLED) == 0) { /* MMU disabled */ return addr; } access_type = ACCESS_DATA | ACCESS_DEBUG; if (env->sr & SR_S) { access_type |= ACCESS_SUPER; } if (get_physical_address(env, &phys_addr, &prot, addr, access_type, &page_size) != 0) { return -1; } return phys_addr; } /* * Notify CPU of a pending interrupt. Prioritization and vectoring should * be handled by the interrupt controller. Real hardware only requests * the vector when the interrupt is acknowledged by the CPU. For * simplicity we calculate it when the interrupt is signalled. */ void m68k_set_irq_level(M68kCPU *cpu, int level, uint8_t vector) { CPUState *cs = CPU(cpu); CPUM68KState *env = &cpu->env; env->pending_level = level; env->pending_vector = vector; if (level) { cpu_interrupt(cs, CPU_INTERRUPT_HARD); } else { cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); } } #endif bool m68k_cpu_tlb_fill(CPUState *cs, vaddr address, int size, MMUAccessType qemu_access_type, int mmu_idx, bool probe, uintptr_t retaddr) { M68kCPU *cpu = M68K_CPU(cs); CPUM68KState *env = &cpu->env; #ifndef CONFIG_USER_ONLY hwaddr physical; int prot; int access_type; int ret; target_ulong page_size; if ((env->mmu.tcr & M68K_TCR_ENABLED) == 0) { /* MMU disabled */ tlb_set_page(cs, address & TARGET_PAGE_MASK, address & TARGET_PAGE_MASK, PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx, TARGET_PAGE_SIZE); return true; } if (qemu_access_type == MMU_INST_FETCH) { access_type = ACCESS_CODE; } else { access_type = ACCESS_DATA; if (qemu_access_type == MMU_DATA_STORE) { access_type |= ACCESS_STORE; } } if (mmu_idx != MMU_USER_IDX) { access_type |= ACCESS_SUPER; } ret = get_physical_address(&cpu->env, &physical, &prot, address, access_type, &page_size); if (likely(ret == 0)) { address &= TARGET_PAGE_MASK; physical += address & (page_size - 1); tlb_set_page(cs, address, physical, prot, mmu_idx, TARGET_PAGE_SIZE); return true; } if (probe) { return false; } /* page fault */ env->mmu.ssw = M68K_ATC_040; switch (size) { case 1: env->mmu.ssw |= M68K_BA_SIZE_BYTE; break; case 2: env->mmu.ssw |= M68K_BA_SIZE_WORD; break; case 4: env->mmu.ssw |= M68K_BA_SIZE_LONG; break; } if (access_type & ACCESS_SUPER) { env->mmu.ssw |= M68K_TM_040_SUPER; } if (access_type & ACCESS_CODE) { env->mmu.ssw |= M68K_TM_040_CODE; } else { env->mmu.ssw |= M68K_TM_040_DATA; } if (!(access_type & ACCESS_STORE)) { env->mmu.ssw |= M68K_RW_040; } #endif cs->exception_index = EXCP_ACCESS; env->mmu.ar = address; cpu_loop_exit_restore(cs, retaddr); } uint32_t HELPER(bitrev)(uint32_t x) { x = ((x >> 1) & 0x55555555u) | ((x << 1) & 0xaaaaaaaau); x = ((x >> 2) & 0x33333333u) | ((x << 2) & 0xccccccccu); x = ((x >> 4) & 0x0f0f0f0fu) | ((x << 4) & 0xf0f0f0f0u); return bswap32(x); } uint32_t HELPER(ff1)(uint32_t x) { int n; for (n = 32; x; n--) x >>= 1; return n; } uint32_t HELPER(sats)(uint32_t val, uint32_t v) { /* The result has the opposite sign to the original value. */ if ((int32_t)v < 0) { val = (((int32_t)val) >> 31) ^ SIGNBIT; } return val; } void cpu_m68k_set_sr(CPUM68KState *env, uint32_t sr) { env->sr = sr & 0xffe0; cpu_m68k_set_ccr(env, sr); m68k_switch_sp(env); } void HELPER(set_sr)(CPUM68KState *env, uint32_t val) { cpu_m68k_set_sr(env, val); } /* MAC unit. */ /* * FIXME: The MAC unit implementation is a bit of a mess. Some helpers * take values, others take register numbers and manipulate the contents * in-place. */ void HELPER(mac_move)(CPUM68KState *env, uint32_t dest, uint32_t src) { uint32_t mask; env->macc[dest] = env->macc[src]; mask = MACSR_PAV0 << dest; if (env->macsr & (MACSR_PAV0 << src)) env->macsr |= mask; else env->macsr &= ~mask; } uint64_t HELPER(macmuls)(CPUM68KState *env, uint32_t op1, uint32_t op2) { int64_t product; int64_t res; product = (uint64_t)op1 * op2; res = (product << 24) >> 24; if (res != product) { env->macsr |= MACSR_V; if (env->macsr & MACSR_OMC) { /* Make sure the accumulate operation overflows. */ if (product < 0) res = ~(1ll << 50); else res = 1ll << 50; } } return res; } uint64_t HELPER(macmulu)(CPUM68KState *env, uint32_t op1, uint32_t op2) { uint64_t product; product = (uint64_t)op1 * op2; if (product & (0xffffffull << 40)) { env->macsr |= MACSR_V; if (env->macsr & MACSR_OMC) { /* Make sure the accumulate operation overflows. */ product = 1ll << 50; } else { product &= ((1ull << 40) - 1); } } return product; } uint64_t HELPER(macmulf)(CPUM68KState *env, uint32_t op1, uint32_t op2) { uint64_t product; uint32_t remainder; product = (uint64_t)op1 * op2; if (env->macsr & MACSR_RT) { remainder = product & 0xffffff; product >>= 24; if (remainder > 0x800000) product++; else if (remainder == 0x800000) product += (product & 1); } else { product >>= 24; } return product; } void HELPER(macsats)(CPUM68KState *env, uint32_t acc) { int64_t tmp; int64_t result; tmp = env->macc[acc]; result = ((tmp << 16) >> 16); if (result != tmp) { env->macsr |= MACSR_V; } if (env->macsr & MACSR_V) { env->macsr |= MACSR_PAV0 << acc; if (env->macsr & MACSR_OMC) { /* * The result is saturated to 32 bits, despite overflow occurring * at 48 bits. Seems weird, but that's what the hardware docs * say. */ result = (result >> 63) ^ 0x7fffffff; } } env->macc[acc] = result; } void HELPER(macsatu)(CPUM68KState *env, uint32_t acc) { uint64_t val; val = env->macc[acc]; if (val & (0xffffull << 48)) { env->macsr |= MACSR_V; } if (env->macsr & MACSR_V) { env->macsr |= MACSR_PAV0 << acc; if (env->macsr & MACSR_OMC) { if (val > (1ull << 53)) val = 0; else val = (1ull << 48) - 1; } else { val &= ((1ull << 48) - 1); } } env->macc[acc] = val; } void HELPER(macsatf)(CPUM68KState *env, uint32_t acc) { int64_t sum; int64_t result; sum = env->macc[acc]; result = (sum << 16) >> 16; if (result != sum) { env->macsr |= MACSR_V; } if (env->macsr & MACSR_V) { env->macsr |= MACSR_PAV0 << acc; if (env->macsr & MACSR_OMC) { result = (result >> 63) ^ 0x7fffffffffffll; } } env->macc[acc] = result; } void HELPER(mac_set_flags)(CPUM68KState *env, uint32_t acc) { uint64_t val; val = env->macc[acc]; if (val == 0) { env->macsr |= MACSR_Z; } else if (val & (1ull << 47)) { env->macsr |= MACSR_N; } if (env->macsr & (MACSR_PAV0 << acc)) { env->macsr |= MACSR_V; } if (env->macsr & MACSR_FI) { val = ((int64_t)val) >> 40; if (val != 0 && val != -1) env->macsr |= MACSR_EV; } else if (env->macsr & MACSR_SU) { val = ((int64_t)val) >> 32; if (val != 0 && val != -1) env->macsr |= MACSR_EV; } else { if ((val >> 32) != 0) env->macsr |= MACSR_EV; } } #define EXTSIGN(val, index) ( \ (index == 0) ? (int8_t)(val) : ((index == 1) ? (int16_t)(val) : (val)) \ ) #define COMPUTE_CCR(op, x, n, z, v, c) { \ switch (op) { \ case CC_OP_FLAGS: \ /* Everything in place. */ \ break; \ case CC_OP_ADDB: \ case CC_OP_ADDW: \ case CC_OP_ADDL: \ res = n; \ src2 = v; \ src1 = EXTSIGN(res - src2, op - CC_OP_ADDB); \ c = x; \ z = n; \ v = (res ^ src1) & ~(src1 ^ src2); \ break; \ case CC_OP_SUBB: \ case CC_OP_SUBW: \ case CC_OP_SUBL: \ res = n; \ src2 = v; \ src1 = EXTSIGN(res + src2, op - CC_OP_SUBB); \ c = x; \ z = n; \ v = (res ^ src1) & (src1 ^ src2); \ break; \ case CC_OP_CMPB: \ case CC_OP_CMPW: \ case CC_OP_CMPL: \ src1 = n; \ src2 = v; \ res = EXTSIGN(src1 - src2, op - CC_OP_CMPB); \ n = res; \ z = res; \ c = src1 < src2; \ v = (res ^ src1) & (src1 ^ src2); \ break; \ case CC_OP_LOGIC: \ c = v = 0; \ z = n; \ break; \ default: \ cpu_abort(env_cpu(env), "Bad CC_OP %d", op); \ } \ } while (0) uint32_t cpu_m68k_get_ccr(CPUM68KState *env) { uint32_t x, c, n, z, v; uint32_t res, src1, src2; x = env->cc_x; n = env->cc_n; z = env->cc_z; v = env->cc_v; c = env->cc_c; COMPUTE_CCR(env->cc_op, x, n, z, v, c); n = n >> 31; z = (z == 0); v = v >> 31; return x * CCF_X + n * CCF_N + z * CCF_Z + v * CCF_V + c * CCF_C; } uint32_t HELPER(get_ccr)(CPUM68KState *env) { return cpu_m68k_get_ccr(env); } void cpu_m68k_set_ccr(CPUM68KState *env, uint32_t ccr) { env->cc_x = (ccr & CCF_X ? 1 : 0); env->cc_n = (ccr & CCF_N ? -1 : 0); env->cc_z = (ccr & CCF_Z ? 0 : 1); env->cc_v = (ccr & CCF_V ? -1 : 0); env->cc_c = (ccr & CCF_C ? 1 : 0); env->cc_op = CC_OP_FLAGS; } void HELPER(set_ccr)(CPUM68KState *env, uint32_t ccr) { cpu_m68k_set_ccr(env, ccr); } void HELPER(flush_flags)(CPUM68KState *env, uint32_t cc_op) { uint32_t res, src1, src2; COMPUTE_CCR(cc_op, env->cc_x, env->cc_n, env->cc_z, env->cc_v, env->cc_c); env->cc_op = CC_OP_FLAGS; } uint32_t HELPER(get_macf)(CPUM68KState *env, uint64_t val) { int rem; uint32_t result; if (env->macsr & MACSR_SU) { /* 16-bit rounding. */ rem = val & 0xffffff; val = (val >> 24) & 0xffffu; if (rem > 0x800000) val++; else if (rem == 0x800000) val += (val & 1); } else if (env->macsr & MACSR_RT) { /* 32-bit rounding. */ rem = val & 0xff; val >>= 8; if (rem > 0x80) val++; else if (rem == 0x80) val += (val & 1); } else { /* No rounding. */ val >>= 8; } if (env->macsr & MACSR_OMC) { /* Saturate. */ if (env->macsr & MACSR_SU) { if (val != (uint16_t) val) { result = ((val >> 63) ^ 0x7fff) & 0xffff; } else { result = val & 0xffff; } } else { if (val != (uint32_t)val) { result = ((uint32_t)(val >> 63) & 0x7fffffff); } else { result = (uint32_t)val; } } } else { /* No saturation. */ if (env->macsr & MACSR_SU) { result = val & 0xffff; } else { result = (uint32_t)val; } } return result; } uint32_t HELPER(get_macs)(uint64_t val) { if (val == (int32_t)val) { return (int32_t)val; } else { return (val >> 61) ^ ~SIGNBIT; } } uint32_t HELPER(get_macu)(uint64_t val) { if ((val >> 32) == 0) { return (uint32_t)val; } else { return 0xffffffffu; } } uint32_t HELPER(get_mac_extf)(CPUM68KState *env, uint32_t acc) { uint32_t val; val = env->macc[acc] & 0x00ff; val |= (env->macc[acc] >> 32) & 0xff00; val |= (env->macc[acc + 1] << 16) & 0x00ff0000; val |= (env->macc[acc + 1] >> 16) & 0xff000000; return val; } uint32_t HELPER(get_mac_exti)(CPUM68KState *env, uint32_t acc) { uint32_t val; val = (env->macc[acc] >> 32) & 0xffff; val |= (env->macc[acc + 1] >> 16) & 0xffff0000; return val; } void HELPER(set_mac_extf)(CPUM68KState *env, uint32_t val, uint32_t acc) { int64_t res; int32_t tmp; res = env->macc[acc] & 0xffffffff00ull; tmp = (int16_t)(val & 0xff00); res |= ((int64_t)tmp) << 32; res |= val & 0xff; env->macc[acc] = res; res = env->macc[acc + 1] & 0xffffffff00ull; tmp = (val & 0xff000000); res |= ((int64_t)tmp) << 16; res |= (val >> 16) & 0xff; env->macc[acc + 1] = res; } void HELPER(set_mac_exts)(CPUM68KState *env, uint32_t val, uint32_t acc) { int64_t res; int32_t tmp; res = (uint32_t)env->macc[acc]; tmp = (int16_t)val; res |= ((int64_t)tmp) << 32; env->macc[acc] = res; res = (uint32_t)env->macc[acc + 1]; tmp = val & 0xffff0000; res |= (int64_t)tmp << 16; env->macc[acc + 1] = res; } void HELPER(set_mac_extu)(CPUM68KState *env, uint32_t val, uint32_t acc) { uint64_t res; res = (uint32_t)env->macc[acc]; res |= ((uint64_t)(val & 0xffff)) << 32; env->macc[acc] = res; res = (uint32_t)env->macc[acc + 1]; res |= (uint64_t)(val & 0xffff0000) << 16; env->macc[acc + 1] = res; } #if defined(CONFIG_SOFTMMU) void HELPER(ptest)(CPUM68KState *env, uint32_t addr, uint32_t is_read) { hwaddr physical; int access_type; int prot; int ret; target_ulong page_size; access_type = ACCESS_PTEST; if (env->dfc & 4) { access_type |= ACCESS_SUPER; } if ((env->dfc & 3) == 2) { access_type |= ACCESS_CODE; } if (!is_read) { access_type |= ACCESS_STORE; } env->mmu.mmusr = 0; env->mmu.ssw = 0; ret = get_physical_address(env, &physical, &prot, addr, access_type, &page_size); if (ret == 0) { addr &= TARGET_PAGE_MASK; physical += addr & (page_size - 1); tlb_set_page(env_cpu(env), addr, physical, prot, access_type & ACCESS_SUPER ? MMU_KERNEL_IDX : MMU_USER_IDX, page_size); } } void HELPER(pflush)(CPUM68KState *env, uint32_t addr, uint32_t opmode) { CPUState *cs = env_cpu(env); switch (opmode) { case 0: /* Flush page entry if not global */ case 1: /* Flush page entry */ tlb_flush_page(cs, addr); break; case 2: /* Flush all except global entries */ tlb_flush(cs); break; case 3: /* Flush all entries */ tlb_flush(cs); break; } } void HELPER(reset)(CPUM68KState *env) { /* FIXME: reset all except CPU */ } #endif