aece90d85d
The M68K_FEATURE_M68000 feature is misleading in that its name suggests the feature is defined just for Motorola 68000 CPUs, whilst in fact it is defined for all Motorola 680X0 CPUs. In order to avoid confusion with the other M68K_FEATURE_M680X0 constants which define the features available for specific Motorola CPU models, rename M68K_FEATURE_M68000 to M68K_FEATURE_M68K and add comments to clarify its usage. Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-Id: <20220917112515.83905-2-mark.cave-ayland@ilande.co.uk> Signed-off-by: Laurent Vivier <laurent@vivier.eu>
1127 lines
30 KiB
C
1127 lines
30 KiB
C
/*
|
|
* M68K helper routines
|
|
*
|
|
* Copyright (c) 2007 CodeSourcery
|
|
*
|
|
* 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/>.
|
|
*/
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "cpu.h"
|
|
#include "exec/helper-proto.h"
|
|
#include "exec/exec-all.h"
|
|
#include "exec/cpu_ldst.h"
|
|
#include "semihosting/semihost.h"
|
|
|
|
#if !defined(CONFIG_USER_ONLY)
|
|
|
|
static void cf_rte(CPUM68KState *env)
|
|
{
|
|
uint32_t sp;
|
|
uint32_t fmt;
|
|
|
|
sp = env->aregs[7];
|
|
fmt = cpu_ldl_mmuidx_ra(env, sp, MMU_KERNEL_IDX, 0);
|
|
env->pc = cpu_ldl_mmuidx_ra(env, sp + 4, MMU_KERNEL_IDX, 0);
|
|
sp |= (fmt >> 28) & 3;
|
|
env->aregs[7] = sp + 8;
|
|
|
|
cpu_m68k_set_sr(env, fmt);
|
|
}
|
|
|
|
static void m68k_rte(CPUM68KState *env)
|
|
{
|
|
uint32_t sp;
|
|
uint16_t fmt;
|
|
uint16_t sr;
|
|
|
|
sp = env->aregs[7];
|
|
throwaway:
|
|
sr = cpu_lduw_mmuidx_ra(env, sp, MMU_KERNEL_IDX, 0);
|
|
sp += 2;
|
|
env->pc = cpu_ldl_mmuidx_ra(env, sp, MMU_KERNEL_IDX, 0);
|
|
sp += 4;
|
|
if (m68k_feature(env, M68K_FEATURE_QUAD_MULDIV)) {
|
|
/* all except 68000 */
|
|
fmt = cpu_lduw_mmuidx_ra(env, sp, MMU_KERNEL_IDX, 0);
|
|
sp += 2;
|
|
switch (fmt >> 12) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
env->aregs[7] = sp;
|
|
cpu_m68k_set_sr(env, sr);
|
|
goto throwaway;
|
|
case 2:
|
|
case 3:
|
|
sp += 4;
|
|
break;
|
|
case 4:
|
|
sp += 8;
|
|
break;
|
|
case 7:
|
|
sp += 52;
|
|
break;
|
|
}
|
|
}
|
|
env->aregs[7] = sp;
|
|
cpu_m68k_set_sr(env, sr);
|
|
}
|
|
|
|
static const char *m68k_exception_name(int index)
|
|
{
|
|
switch (index) {
|
|
case EXCP_ACCESS:
|
|
return "Access Fault";
|
|
case EXCP_ADDRESS:
|
|
return "Address Error";
|
|
case EXCP_ILLEGAL:
|
|
return "Illegal Instruction";
|
|
case EXCP_DIV0:
|
|
return "Divide by Zero";
|
|
case EXCP_CHK:
|
|
return "CHK/CHK2";
|
|
case EXCP_TRAPCC:
|
|
return "FTRAPcc, TRAPcc, TRAPV";
|
|
case EXCP_PRIVILEGE:
|
|
return "Privilege Violation";
|
|
case EXCP_TRACE:
|
|
return "Trace";
|
|
case EXCP_LINEA:
|
|
return "A-Line";
|
|
case EXCP_LINEF:
|
|
return "F-Line";
|
|
case EXCP_DEBEGBP: /* 68020/030 only */
|
|
return "Copro Protocol Violation";
|
|
case EXCP_FORMAT:
|
|
return "Format Error";
|
|
case EXCP_UNINITIALIZED:
|
|
return "Uninitialized Interrupt";
|
|
case EXCP_SPURIOUS:
|
|
return "Spurious Interrupt";
|
|
case EXCP_INT_LEVEL_1:
|
|
return "Level 1 Interrupt";
|
|
case EXCP_INT_LEVEL_1 + 1:
|
|
return "Level 2 Interrupt";
|
|
case EXCP_INT_LEVEL_1 + 2:
|
|
return "Level 3 Interrupt";
|
|
case EXCP_INT_LEVEL_1 + 3:
|
|
return "Level 4 Interrupt";
|
|
case EXCP_INT_LEVEL_1 + 4:
|
|
return "Level 5 Interrupt";
|
|
case EXCP_INT_LEVEL_1 + 5:
|
|
return "Level 6 Interrupt";
|
|
case EXCP_INT_LEVEL_1 + 6:
|
|
return "Level 7 Interrupt";
|
|
case EXCP_TRAP0:
|
|
return "TRAP #0";
|
|
case EXCP_TRAP0 + 1:
|
|
return "TRAP #1";
|
|
case EXCP_TRAP0 + 2:
|
|
return "TRAP #2";
|
|
case EXCP_TRAP0 + 3:
|
|
return "TRAP #3";
|
|
case EXCP_TRAP0 + 4:
|
|
return "TRAP #4";
|
|
case EXCP_TRAP0 + 5:
|
|
return "TRAP #5";
|
|
case EXCP_TRAP0 + 6:
|
|
return "TRAP #6";
|
|
case EXCP_TRAP0 + 7:
|
|
return "TRAP #7";
|
|
case EXCP_TRAP0 + 8:
|
|
return "TRAP #8";
|
|
case EXCP_TRAP0 + 9:
|
|
return "TRAP #9";
|
|
case EXCP_TRAP0 + 10:
|
|
return "TRAP #10";
|
|
case EXCP_TRAP0 + 11:
|
|
return "TRAP #11";
|
|
case EXCP_TRAP0 + 12:
|
|
return "TRAP #12";
|
|
case EXCP_TRAP0 + 13:
|
|
return "TRAP #13";
|
|
case EXCP_TRAP0 + 14:
|
|
return "TRAP #14";
|
|
case EXCP_TRAP0 + 15:
|
|
return "TRAP #15";
|
|
case EXCP_FP_BSUN:
|
|
return "FP Branch/Set on unordered condition";
|
|
case EXCP_FP_INEX:
|
|
return "FP Inexact Result";
|
|
case EXCP_FP_DZ:
|
|
return "FP Divide by Zero";
|
|
case EXCP_FP_UNFL:
|
|
return "FP Underflow";
|
|
case EXCP_FP_OPERR:
|
|
return "FP Operand Error";
|
|
case EXCP_FP_OVFL:
|
|
return "FP Overflow";
|
|
case EXCP_FP_SNAN:
|
|
return "FP Signaling NAN";
|
|
case EXCP_FP_UNIMP:
|
|
return "FP Unimplemented Data Type";
|
|
case EXCP_MMU_CONF: /* 68030/68851 only */
|
|
return "MMU Configuration Error";
|
|
case EXCP_MMU_ILLEGAL: /* 68851 only */
|
|
return "MMU Illegal Operation";
|
|
case EXCP_MMU_ACCESS: /* 68851 only */
|
|
return "MMU Access Level Violation";
|
|
case 64 ... 255:
|
|
return "User Defined Vector";
|
|
}
|
|
return "Unassigned";
|
|
}
|
|
|
|
static void cf_interrupt_all(CPUM68KState *env, int is_hw)
|
|
{
|
|
CPUState *cs = env_cpu(env);
|
|
uint32_t sp;
|
|
uint32_t sr;
|
|
uint32_t fmt;
|
|
uint32_t retaddr;
|
|
uint32_t vector;
|
|
|
|
fmt = 0;
|
|
retaddr = env->pc;
|
|
|
|
if (!is_hw) {
|
|
switch (cs->exception_index) {
|
|
case EXCP_RTE:
|
|
/* Return from an exception. */
|
|
cf_rte(env);
|
|
return;
|
|
case EXCP_HALT_INSN:
|
|
if (semihosting_enabled((env->sr & SR_S) == 0)
|
|
&& (env->pc & 3) == 0
|
|
&& cpu_lduw_code(env, env->pc - 4) == 0x4e71
|
|
&& cpu_ldl_code(env, env->pc) == 0x4e7bf000) {
|
|
env->pc += 4;
|
|
do_m68k_semihosting(env, env->dregs[0]);
|
|
return;
|
|
}
|
|
cs->halted = 1;
|
|
cs->exception_index = EXCP_HLT;
|
|
cpu_loop_exit(cs);
|
|
return;
|
|
}
|
|
}
|
|
|
|
vector = cs->exception_index << 2;
|
|
|
|
sr = env->sr | cpu_m68k_get_ccr(env);
|
|
if (qemu_loglevel_mask(CPU_LOG_INT)) {
|
|
static int count;
|
|
qemu_log("INT %6d: %s(%#x) pc=%08x sp=%08x sr=%04x\n",
|
|
++count, m68k_exception_name(cs->exception_index),
|
|
vector, env->pc, env->aregs[7], sr);
|
|
}
|
|
|
|
fmt |= 0x40000000;
|
|
fmt |= vector << 16;
|
|
fmt |= sr;
|
|
|
|
env->sr |= SR_S;
|
|
if (is_hw) {
|
|
env->sr = (env->sr & ~SR_I) | (env->pending_level << SR_I_SHIFT);
|
|
env->sr &= ~SR_M;
|
|
}
|
|
m68k_switch_sp(env);
|
|
sp = env->aregs[7];
|
|
fmt |= (sp & 3) << 28;
|
|
|
|
/* ??? This could cause MMU faults. */
|
|
sp &= ~3;
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, retaddr, MMU_KERNEL_IDX, 0);
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, fmt, MMU_KERNEL_IDX, 0);
|
|
env->aregs[7] = sp;
|
|
/* Jump to vector. */
|
|
env->pc = cpu_ldl_mmuidx_ra(env, env->vbr + vector, MMU_KERNEL_IDX, 0);
|
|
}
|
|
|
|
static inline void do_stack_frame(CPUM68KState *env, uint32_t *sp,
|
|
uint16_t format, uint16_t sr,
|
|
uint32_t addr, uint32_t retaddr)
|
|
{
|
|
if (m68k_feature(env, M68K_FEATURE_QUAD_MULDIV)) {
|
|
/* all except 68000 */
|
|
CPUState *cs = env_cpu(env);
|
|
switch (format) {
|
|
case 4:
|
|
*sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, *sp, env->pc, MMU_KERNEL_IDX, 0);
|
|
*sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, *sp, addr, MMU_KERNEL_IDX, 0);
|
|
break;
|
|
case 3:
|
|
case 2:
|
|
*sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, *sp, addr, MMU_KERNEL_IDX, 0);
|
|
break;
|
|
}
|
|
*sp -= 2;
|
|
cpu_stw_mmuidx_ra(env, *sp, (format << 12) + (cs->exception_index << 2),
|
|
MMU_KERNEL_IDX, 0);
|
|
}
|
|
*sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, *sp, retaddr, MMU_KERNEL_IDX, 0);
|
|
*sp -= 2;
|
|
cpu_stw_mmuidx_ra(env, *sp, sr, MMU_KERNEL_IDX, 0);
|
|
}
|
|
|
|
static void m68k_interrupt_all(CPUM68KState *env, int is_hw)
|
|
{
|
|
CPUState *cs = env_cpu(env);
|
|
uint32_t sp;
|
|
uint32_t vector;
|
|
uint16_t sr, oldsr;
|
|
|
|
if (!is_hw) {
|
|
switch (cs->exception_index) {
|
|
case EXCP_RTE:
|
|
/* Return from an exception. */
|
|
m68k_rte(env);
|
|
return;
|
|
}
|
|
}
|
|
|
|
vector = cs->exception_index << 2;
|
|
|
|
sr = env->sr | cpu_m68k_get_ccr(env);
|
|
if (qemu_loglevel_mask(CPU_LOG_INT)) {
|
|
static int count;
|
|
qemu_log("INT %6d: %s(%#x) pc=%08x sp=%08x sr=%04x\n",
|
|
++count, m68k_exception_name(cs->exception_index),
|
|
vector, env->pc, env->aregs[7], sr);
|
|
}
|
|
|
|
/*
|
|
* MC68040UM/AD, chapter 9.3.10
|
|
*/
|
|
|
|
/* "the processor first make an internal copy" */
|
|
oldsr = sr;
|
|
/* "set the mode to supervisor" */
|
|
sr |= SR_S;
|
|
/* "suppress tracing" */
|
|
sr &= ~SR_T;
|
|
/* "sets the processor interrupt mask" */
|
|
if (is_hw) {
|
|
sr |= (env->sr & ~SR_I) | (env->pending_level << SR_I_SHIFT);
|
|
}
|
|
cpu_m68k_set_sr(env, sr);
|
|
sp = env->aregs[7];
|
|
|
|
if (!m68k_feature(env, M68K_FEATURE_UNALIGNED_DATA)) {
|
|
sp &= ~1;
|
|
}
|
|
|
|
switch (cs->exception_index) {
|
|
case EXCP_ACCESS:
|
|
if (env->mmu.fault) {
|
|
cpu_abort(cs, "DOUBLE MMU FAULT\n");
|
|
}
|
|
env->mmu.fault = true;
|
|
/* push data 3 */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* push data 2 */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* push data 1 */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 1 / push data 0 */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 1 address */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 2 data */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 2 address */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 3 data */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 3 address */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
|
|
/* fault address */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
|
|
/* write back 1 status */
|
|
sp -= 2;
|
|
cpu_stw_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 2 status */
|
|
sp -= 2;
|
|
cpu_stw_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* write back 3 status */
|
|
sp -= 2;
|
|
cpu_stw_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
|
|
/* special status word */
|
|
sp -= 2;
|
|
cpu_stw_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX, 0);
|
|
/* effective address */
|
|
sp -= 4;
|
|
cpu_stl_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
|
|
|
|
do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
|
|
env->mmu.fault = false;
|
|
if (qemu_loglevel_mask(CPU_LOG_INT)) {
|
|
qemu_log(" "
|
|
"ssw: %08x ea: %08x sfc: %d dfc: %d\n",
|
|
env->mmu.ssw, env->mmu.ar, env->sfc, env->dfc);
|
|
}
|
|
break;
|
|
|
|
case EXCP_ILLEGAL:
|
|
do_stack_frame(env, &sp, 0, oldsr, 0, env->pc);
|
|
break;
|
|
|
|
case EXCP_ADDRESS:
|
|
do_stack_frame(env, &sp, 2, oldsr, 0, env->pc);
|
|
break;
|
|
|
|
case EXCP_CHK:
|
|
case EXCP_DIV0:
|
|
case EXCP_TRACE:
|
|
case EXCP_TRAPCC:
|
|
do_stack_frame(env, &sp, 2, oldsr, env->mmu.ar, env->pc);
|
|
break;
|
|
|
|
case EXCP_SPURIOUS ... EXCP_INT_LEVEL_7:
|
|
if (is_hw && (oldsr & SR_M)) {
|
|
do_stack_frame(env, &sp, 0, oldsr, 0, env->pc);
|
|
oldsr = sr;
|
|
env->aregs[7] = sp;
|
|
cpu_m68k_set_sr(env, sr & ~SR_M);
|
|
sp = env->aregs[7];
|
|
if (!m68k_feature(env, M68K_FEATURE_UNALIGNED_DATA)) {
|
|
sp &= ~1;
|
|
}
|
|
do_stack_frame(env, &sp, 1, oldsr, 0, env->pc);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
|
|
default:
|
|
do_stack_frame(env, &sp, 0, oldsr, 0, env->pc);
|
|
break;
|
|
}
|
|
|
|
env->aregs[7] = sp;
|
|
/* Jump to vector. */
|
|
env->pc = cpu_ldl_mmuidx_ra(env, env->vbr + vector, MMU_KERNEL_IDX, 0);
|
|
}
|
|
|
|
static void do_interrupt_all(CPUM68KState *env, int is_hw)
|
|
{
|
|
if (m68k_feature(env, M68K_FEATURE_M68K)) {
|
|
m68k_interrupt_all(env, is_hw);
|
|
return;
|
|
}
|
|
cf_interrupt_all(env, is_hw);
|
|
}
|
|
|
|
void m68k_cpu_do_interrupt(CPUState *cs)
|
|
{
|
|
M68kCPU *cpu = M68K_CPU(cs);
|
|
CPUM68KState *env = &cpu->env;
|
|
|
|
do_interrupt_all(env, 0);
|
|
}
|
|
|
|
static inline void do_interrupt_m68k_hardirq(CPUM68KState *env)
|
|
{
|
|
do_interrupt_all(env, 1);
|
|
}
|
|
|
|
void m68k_cpu_transaction_failed(CPUState *cs, hwaddr physaddr, vaddr addr,
|
|
unsigned size, MMUAccessType access_type,
|
|
int mmu_idx, MemTxAttrs attrs,
|
|
MemTxResult response, uintptr_t retaddr)
|
|
{
|
|
M68kCPU *cpu = M68K_CPU(cs);
|
|
CPUM68KState *env = &cpu->env;
|
|
|
|
cpu_restore_state(cs, retaddr, true);
|
|
|
|
if (m68k_feature(env, M68K_FEATURE_M68040)) {
|
|
env->mmu.mmusr = 0;
|
|
|
|
/*
|
|
* According to the MC68040 users manual the ATC bit of the SSW is
|
|
* used to distinguish between ATC faults and physical bus errors.
|
|
* In the case of a bus error e.g. during nubus read from an empty
|
|
* slot this bit should not be set
|
|
*/
|
|
if (response != MEMTX_DECODE_ERROR) {
|
|
env->mmu.ssw |= M68K_ATC_040;
|
|
}
|
|
|
|
/* FIXME: manage MMU table access error */
|
|
env->mmu.ssw &= ~M68K_TM_040;
|
|
if (env->sr & SR_S) { /* SUPERVISOR */
|
|
env->mmu.ssw |= M68K_TM_040_SUPER;
|
|
}
|
|
if (access_type == MMU_INST_FETCH) { /* instruction or data */
|
|
env->mmu.ssw |= M68K_TM_040_CODE;
|
|
} else {
|
|
env->mmu.ssw |= M68K_TM_040_DATA;
|
|
}
|
|
env->mmu.ssw &= ~M68K_BA_SIZE_MASK;
|
|
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 != MMU_DATA_STORE) {
|
|
env->mmu.ssw |= M68K_RW_040;
|
|
}
|
|
|
|
env->mmu.ar = addr;
|
|
|
|
cs->exception_index = EXCP_ACCESS;
|
|
cpu_loop_exit(cs);
|
|
}
|
|
}
|
|
|
|
bool m68k_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
|
|
{
|
|
M68kCPU *cpu = M68K_CPU(cs);
|
|
CPUM68KState *env = &cpu->env;
|
|
|
|
if (interrupt_request & CPU_INTERRUPT_HARD
|
|
&& ((env->sr & SR_I) >> SR_I_SHIFT) < env->pending_level) {
|
|
/*
|
|
* Real hardware gets the interrupt vector via an IACK cycle
|
|
* at this point. Current emulated hardware doesn't rely on
|
|
* this, so we provide/save the vector when the interrupt is
|
|
* first signalled.
|
|
*/
|
|
cs->exception_index = env->pending_vector;
|
|
do_interrupt_m68k_hardirq(env);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif /* !CONFIG_USER_ONLY */
|
|
|
|
G_NORETURN static void
|
|
raise_exception_ra(CPUM68KState *env, int tt, uintptr_t raddr)
|
|
{
|
|
CPUState *cs = env_cpu(env);
|
|
|
|
cs->exception_index = tt;
|
|
cpu_loop_exit_restore(cs, raddr);
|
|
}
|
|
|
|
G_NORETURN static void raise_exception(CPUM68KState *env, int tt)
|
|
{
|
|
raise_exception_ra(env, tt, 0);
|
|
}
|
|
|
|
void HELPER(raise_exception)(CPUM68KState *env, uint32_t tt)
|
|
{
|
|
raise_exception(env, tt);
|
|
}
|
|
|
|
G_NORETURN static void
|
|
raise_exception_format2(CPUM68KState *env, int tt, int ilen, uintptr_t raddr)
|
|
{
|
|
CPUState *cs = env_cpu(env);
|
|
|
|
cs->exception_index = tt;
|
|
|
|
/* Recover PC and CC_OP for the beginning of the insn. */
|
|
cpu_restore_state(cs, raddr, true);
|
|
|
|
/* Flags are current in env->cc_*, or are undefined. */
|
|
env->cc_op = CC_OP_FLAGS;
|
|
|
|
/*
|
|
* Remember original pc in mmu.ar, for the Format 2 stack frame.
|
|
* Adjust PC to end of the insn.
|
|
*/
|
|
env->mmu.ar = env->pc;
|
|
env->pc += ilen;
|
|
|
|
cpu_loop_exit(cs);
|
|
}
|
|
|
|
void HELPER(divuw)(CPUM68KState *env, int destr, uint32_t den, int ilen)
|
|
{
|
|
uint32_t num = env->dregs[destr];
|
|
uint32_t quot, rem;
|
|
|
|
env->cc_c = 0; /* always cleared, even if div0 */
|
|
|
|
if (den == 0) {
|
|
raise_exception_format2(env, EXCP_DIV0, ilen, GETPC());
|
|
}
|
|
quot = num / den;
|
|
rem = num % den;
|
|
|
|
if (quot > 0xffff) {
|
|
env->cc_v = -1;
|
|
/*
|
|
* real 68040 keeps N and unset Z on overflow,
|
|
* whereas documentation says "undefined"
|
|
*/
|
|
env->cc_z = 1;
|
|
return;
|
|
}
|
|
env->dregs[destr] = deposit32(quot, 16, 16, rem);
|
|
env->cc_z = (int16_t)quot;
|
|
env->cc_n = (int16_t)quot;
|
|
env->cc_v = 0;
|
|
}
|
|
|
|
void HELPER(divsw)(CPUM68KState *env, int destr, int32_t den, int ilen)
|
|
{
|
|
int32_t num = env->dregs[destr];
|
|
uint32_t quot, rem;
|
|
|
|
env->cc_c = 0; /* always cleared, even if overflow/div0 */
|
|
|
|
if (den == 0) {
|
|
raise_exception_format2(env, EXCP_DIV0, ilen, GETPC());
|
|
}
|
|
quot = num / den;
|
|
rem = num % den;
|
|
|
|
if (quot != (int16_t)quot) {
|
|
env->cc_v = -1;
|
|
/* nothing else is modified */
|
|
/*
|
|
* real 68040 keeps N and unset Z on overflow,
|
|
* whereas documentation says "undefined"
|
|
*/
|
|
env->cc_z = 1;
|
|
return;
|
|
}
|
|
env->dregs[destr] = deposit32(quot, 16, 16, rem);
|
|
env->cc_z = (int16_t)quot;
|
|
env->cc_n = (int16_t)quot;
|
|
env->cc_v = 0;
|
|
}
|
|
|
|
void HELPER(divul)(CPUM68KState *env, int numr, int regr,
|
|
uint32_t den, int ilen)
|
|
{
|
|
uint32_t num = env->dregs[numr];
|
|
uint32_t quot, rem;
|
|
|
|
env->cc_c = 0; /* always cleared, even if div0 */
|
|
|
|
if (den == 0) {
|
|
raise_exception_format2(env, EXCP_DIV0, ilen, GETPC());
|
|
}
|
|
quot = num / den;
|
|
rem = num % den;
|
|
|
|
env->cc_z = quot;
|
|
env->cc_n = quot;
|
|
env->cc_v = 0;
|
|
|
|
if (m68k_feature(env, M68K_FEATURE_CF_ISA_A)) {
|
|
if (numr == regr) {
|
|
env->dregs[numr] = quot;
|
|
} else {
|
|
env->dregs[regr] = rem;
|
|
}
|
|
} else {
|
|
env->dregs[regr] = rem;
|
|
env->dregs[numr] = quot;
|
|
}
|
|
}
|
|
|
|
void HELPER(divsl)(CPUM68KState *env, int numr, int regr,
|
|
int32_t den, int ilen)
|
|
{
|
|
int32_t num = env->dregs[numr];
|
|
int32_t quot, rem;
|
|
|
|
env->cc_c = 0; /* always cleared, even if overflow/div0 */
|
|
|
|
if (den == 0) {
|
|
raise_exception_format2(env, EXCP_DIV0, ilen, GETPC());
|
|
}
|
|
quot = num / den;
|
|
rem = num % den;
|
|
|
|
env->cc_z = quot;
|
|
env->cc_n = quot;
|
|
env->cc_v = 0;
|
|
|
|
if (m68k_feature(env, M68K_FEATURE_CF_ISA_A)) {
|
|
if (numr == regr) {
|
|
env->dregs[numr] = quot;
|
|
} else {
|
|
env->dregs[regr] = rem;
|
|
}
|
|
} else {
|
|
env->dregs[regr] = rem;
|
|
env->dregs[numr] = quot;
|
|
}
|
|
}
|
|
|
|
void HELPER(divull)(CPUM68KState *env, int numr, int regr,
|
|
uint32_t den, int ilen)
|
|
{
|
|
uint64_t num = deposit64(env->dregs[numr], 32, 32, env->dregs[regr]);
|
|
uint64_t quot;
|
|
uint32_t rem;
|
|
|
|
env->cc_c = 0; /* always cleared, even if overflow/div0 */
|
|
|
|
if (den == 0) {
|
|
raise_exception_format2(env, EXCP_DIV0, ilen, GETPC());
|
|
}
|
|
quot = num / den;
|
|
rem = num % den;
|
|
|
|
if (quot > 0xffffffffULL) {
|
|
env->cc_v = -1;
|
|
/*
|
|
* real 68040 keeps N and unset Z on overflow,
|
|
* whereas documentation says "undefined"
|
|
*/
|
|
env->cc_z = 1;
|
|
return;
|
|
}
|
|
env->cc_z = quot;
|
|
env->cc_n = quot;
|
|
env->cc_v = 0;
|
|
|
|
/*
|
|
* If Dq and Dr are the same, the quotient is returned.
|
|
* therefore we set Dq last.
|
|
*/
|
|
|
|
env->dregs[regr] = rem;
|
|
env->dregs[numr] = quot;
|
|
}
|
|
|
|
void HELPER(divsll)(CPUM68KState *env, int numr, int regr,
|
|
int32_t den, int ilen)
|
|
{
|
|
int64_t num = deposit64(env->dregs[numr], 32, 32, env->dregs[regr]);
|
|
int64_t quot;
|
|
int32_t rem;
|
|
|
|
env->cc_c = 0; /* always cleared, even if overflow/div0 */
|
|
|
|
if (den == 0) {
|
|
raise_exception_format2(env, EXCP_DIV0, ilen, GETPC());
|
|
}
|
|
quot = num / den;
|
|
rem = num % den;
|
|
|
|
if (quot != (int32_t)quot) {
|
|
env->cc_v = -1;
|
|
/*
|
|
* real 68040 keeps N and unset Z on overflow,
|
|
* whereas documentation says "undefined"
|
|
*/
|
|
env->cc_z = 1;
|
|
return;
|
|
}
|
|
env->cc_z = quot;
|
|
env->cc_n = quot;
|
|
env->cc_v = 0;
|
|
|
|
/*
|
|
* If Dq and Dr are the same, the quotient is returned.
|
|
* therefore we set Dq last.
|
|
*/
|
|
|
|
env->dregs[regr] = rem;
|
|
env->dregs[numr] = quot;
|
|
}
|
|
|
|
/* We're executing in a serial context -- no need to be atomic. */
|
|
void HELPER(cas2w)(CPUM68KState *env, uint32_t regs, uint32_t a1, uint32_t a2)
|
|
{
|
|
uint32_t Dc1 = extract32(regs, 9, 3);
|
|
uint32_t Dc2 = extract32(regs, 6, 3);
|
|
uint32_t Du1 = extract32(regs, 3, 3);
|
|
uint32_t Du2 = extract32(regs, 0, 3);
|
|
int16_t c1 = env->dregs[Dc1];
|
|
int16_t c2 = env->dregs[Dc2];
|
|
int16_t u1 = env->dregs[Du1];
|
|
int16_t u2 = env->dregs[Du2];
|
|
int16_t l1, l2;
|
|
uintptr_t ra = GETPC();
|
|
|
|
l1 = cpu_lduw_data_ra(env, a1, ra);
|
|
l2 = cpu_lduw_data_ra(env, a2, ra);
|
|
if (l1 == c1 && l2 == c2) {
|
|
cpu_stw_data_ra(env, a1, u1, ra);
|
|
cpu_stw_data_ra(env, a2, u2, ra);
|
|
}
|
|
|
|
if (c1 != l1) {
|
|
env->cc_n = l1;
|
|
env->cc_v = c1;
|
|
} else {
|
|
env->cc_n = l2;
|
|
env->cc_v = c2;
|
|
}
|
|
env->cc_op = CC_OP_CMPW;
|
|
env->dregs[Dc1] = deposit32(env->dregs[Dc1], 0, 16, l1);
|
|
env->dregs[Dc2] = deposit32(env->dregs[Dc2], 0, 16, l2);
|
|
}
|
|
|
|
static void do_cas2l(CPUM68KState *env, uint32_t regs, uint32_t a1, uint32_t a2,
|
|
bool parallel)
|
|
{
|
|
uint32_t Dc1 = extract32(regs, 9, 3);
|
|
uint32_t Dc2 = extract32(regs, 6, 3);
|
|
uint32_t Du1 = extract32(regs, 3, 3);
|
|
uint32_t Du2 = extract32(regs, 0, 3);
|
|
uint32_t c1 = env->dregs[Dc1];
|
|
uint32_t c2 = env->dregs[Dc2];
|
|
uint32_t u1 = env->dregs[Du1];
|
|
uint32_t u2 = env->dregs[Du2];
|
|
uint32_t l1, l2;
|
|
uintptr_t ra = GETPC();
|
|
#if defined(CONFIG_ATOMIC64)
|
|
int mmu_idx = cpu_mmu_index(env, 0);
|
|
MemOpIdx oi = make_memop_idx(MO_BEUQ, mmu_idx);
|
|
#endif
|
|
|
|
if (parallel) {
|
|
/* We're executing in a parallel context -- must be atomic. */
|
|
#ifdef CONFIG_ATOMIC64
|
|
uint64_t c, u, l;
|
|
if ((a1 & 7) == 0 && a2 == a1 + 4) {
|
|
c = deposit64(c2, 32, 32, c1);
|
|
u = deposit64(u2, 32, 32, u1);
|
|
l = cpu_atomic_cmpxchgq_be_mmu(env, a1, c, u, oi, ra);
|
|
l1 = l >> 32;
|
|
l2 = l;
|
|
} else if ((a2 & 7) == 0 && a1 == a2 + 4) {
|
|
c = deposit64(c1, 32, 32, c2);
|
|
u = deposit64(u1, 32, 32, u2);
|
|
l = cpu_atomic_cmpxchgq_be_mmu(env, a2, c, u, oi, ra);
|
|
l2 = l >> 32;
|
|
l1 = l;
|
|
} else
|
|
#endif
|
|
{
|
|
/* Tell the main loop we need to serialize this insn. */
|
|
cpu_loop_exit_atomic(env_cpu(env), ra);
|
|
}
|
|
} else {
|
|
/* We're executing in a serial context -- no need to be atomic. */
|
|
l1 = cpu_ldl_data_ra(env, a1, ra);
|
|
l2 = cpu_ldl_data_ra(env, a2, ra);
|
|
if (l1 == c1 && l2 == c2) {
|
|
cpu_stl_data_ra(env, a1, u1, ra);
|
|
cpu_stl_data_ra(env, a2, u2, ra);
|
|
}
|
|
}
|
|
|
|
if (c1 != l1) {
|
|
env->cc_n = l1;
|
|
env->cc_v = c1;
|
|
} else {
|
|
env->cc_n = l2;
|
|
env->cc_v = c2;
|
|
}
|
|
env->cc_op = CC_OP_CMPL;
|
|
env->dregs[Dc1] = l1;
|
|
env->dregs[Dc2] = l2;
|
|
}
|
|
|
|
void HELPER(cas2l)(CPUM68KState *env, uint32_t regs, uint32_t a1, uint32_t a2)
|
|
{
|
|
do_cas2l(env, regs, a1, a2, false);
|
|
}
|
|
|
|
void HELPER(cas2l_parallel)(CPUM68KState *env, uint32_t regs, uint32_t a1,
|
|
uint32_t a2)
|
|
{
|
|
do_cas2l(env, regs, a1, a2, true);
|
|
}
|
|
|
|
struct bf_data {
|
|
uint32_t addr;
|
|
uint32_t bofs;
|
|
uint32_t blen;
|
|
uint32_t len;
|
|
};
|
|
|
|
static struct bf_data bf_prep(uint32_t addr, int32_t ofs, uint32_t len)
|
|
{
|
|
int bofs, blen;
|
|
|
|
/* Bound length; map 0 to 32. */
|
|
len = ((len - 1) & 31) + 1;
|
|
|
|
/* Note that ofs is signed. */
|
|
addr += ofs / 8;
|
|
bofs = ofs % 8;
|
|
if (bofs < 0) {
|
|
bofs += 8;
|
|
addr -= 1;
|
|
}
|
|
|
|
/*
|
|
* Compute the number of bytes required (minus one) to
|
|
* satisfy the bitfield.
|
|
*/
|
|
blen = (bofs + len - 1) / 8;
|
|
|
|
/*
|
|
* Canonicalize the bit offset for data loaded into a 64-bit big-endian
|
|
* word. For the cases where BLEN is not a power of 2, adjust ADDR so
|
|
* that we can use the next power of two sized load without crossing a
|
|
* page boundary, unless the field itself crosses the boundary.
|
|
*/
|
|
switch (blen) {
|
|
case 0:
|
|
bofs += 56;
|
|
break;
|
|
case 1:
|
|
bofs += 48;
|
|
break;
|
|
case 2:
|
|
if (addr & 1) {
|
|
bofs += 8;
|
|
addr -= 1;
|
|
}
|
|
/* fallthru */
|
|
case 3:
|
|
bofs += 32;
|
|
break;
|
|
case 4:
|
|
if (addr & 3) {
|
|
bofs += 8 * (addr & 3);
|
|
addr &= -4;
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return (struct bf_data){
|
|
.addr = addr,
|
|
.bofs = bofs,
|
|
.blen = blen,
|
|
.len = len,
|
|
};
|
|
}
|
|
|
|
static uint64_t bf_load(CPUM68KState *env, uint32_t addr, int blen,
|
|
uintptr_t ra)
|
|
{
|
|
switch (blen) {
|
|
case 0:
|
|
return cpu_ldub_data_ra(env, addr, ra);
|
|
case 1:
|
|
return cpu_lduw_data_ra(env, addr, ra);
|
|
case 2:
|
|
case 3:
|
|
return cpu_ldl_data_ra(env, addr, ra);
|
|
case 4:
|
|
return cpu_ldq_data_ra(env, addr, ra);
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
|
|
static void bf_store(CPUM68KState *env, uint32_t addr, int blen,
|
|
uint64_t data, uintptr_t ra)
|
|
{
|
|
switch (blen) {
|
|
case 0:
|
|
cpu_stb_data_ra(env, addr, data, ra);
|
|
break;
|
|
case 1:
|
|
cpu_stw_data_ra(env, addr, data, ra);
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
cpu_stl_data_ra(env, addr, data, ra);
|
|
break;
|
|
case 4:
|
|
cpu_stq_data_ra(env, addr, data, ra);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
|
|
uint32_t HELPER(bfexts_mem)(CPUM68KState *env, uint32_t addr,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
|
|
return (int64_t)(data << d.bofs) >> (64 - d.len);
|
|
}
|
|
|
|
uint64_t HELPER(bfextu_mem)(CPUM68KState *env, uint32_t addr,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
|
|
/*
|
|
* Put CC_N at the top of the high word; put the zero-extended value
|
|
* at the bottom of the low word.
|
|
*/
|
|
data <<= d.bofs;
|
|
data >>= 64 - d.len;
|
|
data |= data << (64 - d.len);
|
|
|
|
return data;
|
|
}
|
|
|
|
uint32_t HELPER(bfins_mem)(CPUM68KState *env, uint32_t addr, uint32_t val,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
uint64_t mask = -1ull << (64 - d.len) >> d.bofs;
|
|
|
|
data = (data & ~mask) | (((uint64_t)val << (64 - d.len)) >> d.bofs);
|
|
|
|
bf_store(env, d.addr, d.blen, data, ra);
|
|
|
|
/* The field at the top of the word is also CC_N for CC_OP_LOGIC. */
|
|
return val << (32 - d.len);
|
|
}
|
|
|
|
uint32_t HELPER(bfchg_mem)(CPUM68KState *env, uint32_t addr,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
uint64_t mask = -1ull << (64 - d.len) >> d.bofs;
|
|
|
|
bf_store(env, d.addr, d.blen, data ^ mask, ra);
|
|
|
|
return ((data & mask) << d.bofs) >> 32;
|
|
}
|
|
|
|
uint32_t HELPER(bfclr_mem)(CPUM68KState *env, uint32_t addr,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
uint64_t mask = -1ull << (64 - d.len) >> d.bofs;
|
|
|
|
bf_store(env, d.addr, d.blen, data & ~mask, ra);
|
|
|
|
return ((data & mask) << d.bofs) >> 32;
|
|
}
|
|
|
|
uint32_t HELPER(bfset_mem)(CPUM68KState *env, uint32_t addr,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
uint64_t mask = -1ull << (64 - d.len) >> d.bofs;
|
|
|
|
bf_store(env, d.addr, d.blen, data | mask, ra);
|
|
|
|
return ((data & mask) << d.bofs) >> 32;
|
|
}
|
|
|
|
uint32_t HELPER(bfffo_reg)(uint32_t n, uint32_t ofs, uint32_t len)
|
|
{
|
|
return (n ? clz32(n) : len) + ofs;
|
|
}
|
|
|
|
uint64_t HELPER(bfffo_mem)(CPUM68KState *env, uint32_t addr,
|
|
int32_t ofs, uint32_t len)
|
|
{
|
|
uintptr_t ra = GETPC();
|
|
struct bf_data d = bf_prep(addr, ofs, len);
|
|
uint64_t data = bf_load(env, d.addr, d.blen, ra);
|
|
uint64_t mask = -1ull << (64 - d.len) >> d.bofs;
|
|
uint64_t n = (data & mask) << d.bofs;
|
|
uint32_t ffo = helper_bfffo_reg(n >> 32, ofs, d.len);
|
|
|
|
/*
|
|
* Return FFO in the low word and N in the high word.
|
|
* Note that because of MASK and the shift, the low word
|
|
* is already zero.
|
|
*/
|
|
return n | ffo;
|
|
}
|
|
|
|
void HELPER(chk)(CPUM68KState *env, int32_t val, int32_t ub)
|
|
{
|
|
/*
|
|
* From the specs:
|
|
* X: Not affected, C,V,Z: Undefined,
|
|
* N: Set if val < 0; cleared if val > ub, undefined otherwise
|
|
* We implement here values found from a real MC68040:
|
|
* X,V,Z: Not affected
|
|
* N: Set if val < 0; cleared if val >= 0
|
|
* C: if 0 <= ub: set if val < 0 or val > ub, cleared otherwise
|
|
* if 0 > ub: set if val > ub and val < 0, cleared otherwise
|
|
*/
|
|
env->cc_n = val;
|
|
env->cc_c = 0 <= ub ? val < 0 || val > ub : val > ub && val < 0;
|
|
|
|
if (val < 0 || val > ub) {
|
|
raise_exception_format2(env, EXCP_CHK, 2, GETPC());
|
|
}
|
|
}
|
|
|
|
void HELPER(chk2)(CPUM68KState *env, int32_t val, int32_t lb, int32_t ub)
|
|
{
|
|
/*
|
|
* From the specs:
|
|
* X: Not affected, N,V: Undefined,
|
|
* Z: Set if val is equal to lb or ub
|
|
* C: Set if val < lb or val > ub, cleared otherwise
|
|
* We implement here values found from a real MC68040:
|
|
* X,N,V: Not affected
|
|
* Z: Set if val is equal to lb or ub
|
|
* C: if lb <= ub: set if val < lb or val > ub, cleared otherwise
|
|
* if lb > ub: set if val > ub and val < lb, cleared otherwise
|
|
*/
|
|
env->cc_z = val != lb && val != ub;
|
|
env->cc_c = lb <= ub ? val < lb || val > ub : val > ub && val < lb;
|
|
|
|
if (env->cc_c) {
|
|
raise_exception_format2(env, EXCP_CHK, 4, GETPC());
|
|
}
|
|
}
|