Use lookup_and_goto_tb.

Cleanup and fill in VMStateDescription.
 -----BEGIN PGP SIGNATURE-----
 
 iQFRBAABCgA7FiEEekgeeIaLTbaoWgXAZN846K9+IV8FAl9WkbMdHHJpY2hhcmQu
 aGVuZGVyc29uQGxpbmFyby5vcmcACgkQZN846K9+IV/fvQf/UvUOipiXP7vafHI1
 Qx3NZ3nJHOMRz58eBDLidSkWgQM7+zHjBo1V5CvtM6Ajywpsn4IFe+4SJb7MVAYq
 6BSj2VDMq5fCboL52i3xJyBHTE7yqlb4bV3uNSk7dXwf5QQs0sT9PLYp6TuxjSj5
 SLicEron3uCc6Y0Z1tX1yKPjl2Lz5PoZ4Z98m6wZhd/pQbbc23+hMlz91fjyVAs2
 d9ZDnfxL71XQeTUb5tOLC2OK0rQJDQzzMSAO4Ilnrg/w6k0LGlP/kvYsHI+qya1q
 Rm+iBRGZQoItzkzkL1sWXP5StF9xLPRK60cET0N7vMnwN6sbpd3fOOWhE9EDtDWB
 tK0wxQ==
 =1+dD
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/rth/tags/pull-mb-20200907-2' into staging

Use lookup_and_goto_tb.
Cleanup and fill in VMStateDescription.

# gpg: Signature made Mon 07 Sep 2020 21:01:55 BST
# gpg:                using RSA key 7A481E78868B4DB6A85A05C064DF38E8AF7E215F
# gpg:                issuer "richard.henderson@linaro.org"
# gpg: Good signature from "Richard Henderson <richard.henderson@linaro.org>" [full]
# Primary key fingerprint: 7A48 1E78 868B 4DB6 A85A  05C0 64DF 38E8 AF7E 215F

* remotes/rth/tags/pull-mb-20200907-2:
  configure: Do not set TARGET_ABI32 for microblaze
  target/microblaze: Put MicroBlazeCPUConfig into DisasContext
  target/microblaze: Fill in VMStateDescription for cpu
  target/microblaze: Move mmu parameters to MicroBlazeCPUConfig
  target/microblaze: Treat pvr_regs as constant
  target/microblaze: Move pvr regs to MicroBlazeCPUConfig
  target/microblaze: Reorg MicroBlazeCPUConfig to minimize holes
  target/microblaze: Split out MicroBlazeCPUConfig
  target/microblaze: Diagnose invalid insns in delay slots
  target/microblaze: Use tcg_gen_lookup_and_goto_ptr
  target/microblaze: Force rtid, rted, rtbd to exit
  target/microblaze: Handle DISAS_EXIT_NEXT in delay slot
  target/microblaze: Replace cpustate_changed with DISAS_EXIT_NEXT
  target/microblaze: Introduce DISAS_EXIT_NEXT, DISAS_EXIT_JUMP
  target/microblaze: Rename DISAS_UPDATE to DISAS_EXIT
  target/microblaze: Rename mmu structs
  target/microblaze: Cleanup mb_cpu_do_interrupt
  target/microblaze: Renumber D_FLAG

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
master
Peter Maydell 2020-09-08 15:26:13 +01:00
commit 00942071a0
12 changed files with 463 additions and 320 deletions

1
configure vendored
View File

@ -7608,7 +7608,6 @@ case "$target_name" in
TARGET_SYSTBL_ABI=common TARGET_SYSTBL_ABI=common
mttcg="yes" mttcg="yes"
bflt="yes" bflt="yes"
echo "TARGET_ABI32=y" >> $config_target_mak
;; ;;
mips|mipsel) mips|mipsel)
mttcg="yes" mttcg="yes"

View File

@ -200,9 +200,9 @@ petalogix_ml605_init(MachineState *machine)
} }
/* setup PVR to match kernel settings */ /* setup PVR to match kernel settings */
cpu->env.pvr.regs[4] = 0xc56b8000; cpu->cfg.pvr_regs[4] = 0xc56b8000;
cpu->env.pvr.regs[5] = 0xc56be000; cpu->cfg.pvr_regs[5] = 0xc56be000;
cpu->env.pvr.regs[10] = 0x0e000000; /* virtex 6 */ cpu->cfg.pvr_regs[10] = 0x0e000000; /* virtex 6 */
microblaze_load_kernel(cpu, MEMORY_BASEADDR, ram_size, microblaze_load_kernel(cpu, MEMORY_BASEADDR, ram_size,
machine->initrd_filename, machine->initrd_filename,

View File

@ -26,7 +26,6 @@
#include "cpu.h" #include "cpu.h"
#include "qemu/module.h" #include "qemu/module.h"
#include "hw/qdev-properties.h" #include "hw/qdev-properties.h"
#include "migration/vmstate.h"
#include "exec/exec-all.h" #include "exec/exec-all.h"
#include "fpu/softfloat-helpers.h" #include "fpu/softfloat-helpers.h"
@ -135,10 +134,6 @@ static void mb_cpu_reset(DeviceState *dev)
#else #else
mb_cpu_write_msr(env, 0); mb_cpu_write_msr(env, 0);
mmu_init(&env->mmu); mmu_init(&env->mmu);
env->mmu.c_mmu = 3;
env->mmu.c_mmu_tlb_access = 3;
env->mmu.c_mmu_zones = 16;
env->mmu.c_addr_mask = MAKE_64BIT_MASK(0, cpu->cfg.addr_size);
#endif #endif
} }
@ -153,7 +148,6 @@ static void mb_cpu_realizefn(DeviceState *dev, Error **errp)
CPUState *cs = CPU(dev); CPUState *cs = CPU(dev);
MicroBlazeCPUClass *mcc = MICROBLAZE_CPU_GET_CLASS(dev); MicroBlazeCPUClass *mcc = MICROBLAZE_CPU_GET_CLASS(dev);
MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs); MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
CPUMBState *env = &cpu->env;
uint8_t version_code = 0; uint8_t version_code = 0;
const char *version; const char *version;
int i = 0; int i = 0;
@ -173,16 +167,6 @@ static void mb_cpu_realizefn(DeviceState *dev, Error **errp)
qemu_init_vcpu(cs); qemu_init_vcpu(cs);
env->pvr.regs[0] = PVR0_USE_EXC_MASK
| PVR0_USE_ICACHE_MASK
| PVR0_USE_DCACHE_MASK;
env->pvr.regs[2] = PVR2_D_OPB_MASK
| PVR2_D_LMB_MASK
| PVR2_I_OPB_MASK
| PVR2_I_LMB_MASK
| PVR2_FPU_EXC_MASK
| 0;
version = cpu->cfg.version ? cpu->cfg.version : DEFAULT_CPU_VERSION; version = cpu->cfg.version ? cpu->cfg.version : DEFAULT_CPU_VERSION;
for (i = 0; mb_cpu_lookup[i].name && version; i++) { for (i = 0; mb_cpu_lookup[i].name && version; i++) {
if (strcmp(mb_cpu_lookup[i].name, version) == 0) { if (strcmp(mb_cpu_lookup[i].name, version) == 0) {
@ -195,46 +179,58 @@ static void mb_cpu_realizefn(DeviceState *dev, Error **errp)
qemu_log("Invalid MicroBlaze version number: %s\n", cpu->cfg.version); qemu_log("Invalid MicroBlaze version number: %s\n", cpu->cfg.version);
} }
env->pvr.regs[0] |= (cpu->cfg.stackprot ? PVR0_SPROT_MASK : 0) | cpu->cfg.pvr_regs[0] =
(cpu->cfg.use_fpu ? PVR0_USE_FPU_MASK : 0) | (PVR0_USE_EXC_MASK |
(cpu->cfg.use_hw_mul ? PVR0_USE_HW_MUL_MASK : 0) | PVR0_USE_ICACHE_MASK |
(cpu->cfg.use_barrel ? PVR0_USE_BARREL_MASK : 0) | PVR0_USE_DCACHE_MASK |
(cpu->cfg.use_div ? PVR0_USE_DIV_MASK : 0) | (cpu->cfg.stackprot ? PVR0_SPROT_MASK : 0) |
(cpu->cfg.use_mmu ? PVR0_USE_MMU_MASK : 0) | (cpu->cfg.use_fpu ? PVR0_USE_FPU_MASK : 0) |
(cpu->cfg.endi ? PVR0_ENDI_MASK : 0) | (cpu->cfg.use_hw_mul ? PVR0_USE_HW_MUL_MASK : 0) |
(version_code << PVR0_VERSION_SHIFT) | (cpu->cfg.use_barrel ? PVR0_USE_BARREL_MASK : 0) |
(cpu->cfg.pvr == C_PVR_FULL ? PVR0_PVR_FULL_MASK : 0) | (cpu->cfg.use_div ? PVR0_USE_DIV_MASK : 0) |
cpu->cfg.pvr_user1; (cpu->cfg.use_mmu ? PVR0_USE_MMU_MASK : 0) |
(cpu->cfg.endi ? PVR0_ENDI_MASK : 0) |
(version_code << PVR0_VERSION_SHIFT) |
(cpu->cfg.pvr == C_PVR_FULL ? PVR0_PVR_FULL_MASK : 0) |
cpu->cfg.pvr_user1);
env->pvr.regs[1] = cpu->cfg.pvr_user2; cpu->cfg.pvr_regs[1] = cpu->cfg.pvr_user2;
env->pvr.regs[2] |= (cpu->cfg.use_fpu ? PVR2_USE_FPU_MASK : 0) |
(cpu->cfg.use_fpu > 1 ? PVR2_USE_FPU2_MASK : 0) |
(cpu->cfg.use_hw_mul ? PVR2_USE_HW_MUL_MASK : 0) |
(cpu->cfg.use_hw_mul > 1 ? PVR2_USE_MUL64_MASK : 0) |
(cpu->cfg.use_barrel ? PVR2_USE_BARREL_MASK : 0) |
(cpu->cfg.use_div ? PVR2_USE_DIV_MASK : 0) |
(cpu->cfg.use_msr_instr ? PVR2_USE_MSR_INSTR : 0) |
(cpu->cfg.use_pcmp_instr ? PVR2_USE_PCMP_INSTR : 0) |
(cpu->cfg.dopb_bus_exception ?
PVR2_DOPB_BUS_EXC_MASK : 0) |
(cpu->cfg.iopb_bus_exception ?
PVR2_IOPB_BUS_EXC_MASK : 0) |
(cpu->cfg.div_zero_exception ?
PVR2_DIV_ZERO_EXC_MASK : 0) |
(cpu->cfg.illegal_opcode_exception ?
PVR2_ILL_OPCODE_EXC_MASK : 0) |
(cpu->cfg.unaligned_exceptions ?
PVR2_UNALIGNED_EXC_MASK : 0) |
(cpu->cfg.opcode_0_illegal ?
PVR2_OPCODE_0x0_ILL_MASK : 0);
env->pvr.regs[5] |= cpu->cfg.dcache_writeback ? cpu->cfg.pvr_regs[2] =
PVR5_DCACHE_WRITEBACK_MASK : 0; (PVR2_D_OPB_MASK |
PVR2_D_LMB_MASK |
PVR2_I_OPB_MASK |
PVR2_I_LMB_MASK |
PVR2_FPU_EXC_MASK |
(cpu->cfg.use_fpu ? PVR2_USE_FPU_MASK : 0) |
(cpu->cfg.use_fpu > 1 ? PVR2_USE_FPU2_MASK : 0) |
(cpu->cfg.use_hw_mul ? PVR2_USE_HW_MUL_MASK : 0) |
(cpu->cfg.use_hw_mul > 1 ? PVR2_USE_MUL64_MASK : 0) |
(cpu->cfg.use_barrel ? PVR2_USE_BARREL_MASK : 0) |
(cpu->cfg.use_div ? PVR2_USE_DIV_MASK : 0) |
(cpu->cfg.use_msr_instr ? PVR2_USE_MSR_INSTR : 0) |
(cpu->cfg.use_pcmp_instr ? PVR2_USE_PCMP_INSTR : 0) |
(cpu->cfg.dopb_bus_exception ? PVR2_DOPB_BUS_EXC_MASK : 0) |
(cpu->cfg.iopb_bus_exception ? PVR2_IOPB_BUS_EXC_MASK : 0) |
(cpu->cfg.div_zero_exception ? PVR2_DIV_ZERO_EXC_MASK : 0) |
(cpu->cfg.illegal_opcode_exception ? PVR2_ILL_OPCODE_EXC_MASK : 0) |
(cpu->cfg.unaligned_exceptions ? PVR2_UNALIGNED_EXC_MASK : 0) |
(cpu->cfg.opcode_0_illegal ? PVR2_OPCODE_0x0_ILL_MASK : 0));
env->pvr.regs[10] = 0x0c000000 | /* Default to spartan 3a dsp family. */ cpu->cfg.pvr_regs[5] |=
(cpu->cfg.addr_size - 32) << PVR10_ASIZE_SHIFT; cpu->cfg.dcache_writeback ? PVR5_DCACHE_WRITEBACK_MASK : 0;
env->pvr.regs[11] = (cpu->cfg.use_mmu ? PVR11_USE_MMU : 0) |
16 << 17; cpu->cfg.pvr_regs[10] =
(0x0c000000 | /* Default to spartan 3a dsp family. */
(cpu->cfg.addr_size - 32) << PVR10_ASIZE_SHIFT);
cpu->cfg.pvr_regs[11] = ((cpu->cfg.use_mmu ? PVR11_USE_MMU : 0) |
16 << 17);
cpu->cfg.mmu = 3;
cpu->cfg.mmu_tlb_access = 3;
cpu->cfg.mmu_zones = 16;
cpu->cfg.addr_mask = MAKE_64BIT_MASK(0, cpu->cfg.addr_size);
mcc->parent_realize(dev, errp); mcc->parent_realize(dev, errp);
} }
@ -254,11 +250,6 @@ static void mb_cpu_initfn(Object *obj)
#endif #endif
} }
static const VMStateDescription vmstate_mb_cpu = {
.name = "cpu",
.unmigratable = 1,
};
static Property mb_properties[] = { static Property mb_properties[] = {
DEFINE_PROP_UINT32("base-vectors", MicroBlazeCPU, cfg.base_vectors, 0), DEFINE_PROP_UINT32("base-vectors", MicroBlazeCPU, cfg.base_vectors, 0),
DEFINE_PROP_BOOL("use-stack-protection", MicroBlazeCPU, cfg.stackprot, DEFINE_PROP_BOOL("use-stack-protection", MicroBlazeCPU, cfg.stackprot,
@ -338,8 +329,8 @@ static void mb_cpu_class_init(ObjectClass *oc, void *data)
#ifndef CONFIG_USER_ONLY #ifndef CONFIG_USER_ONLY
cc->do_transaction_failed = mb_cpu_transaction_failed; cc->do_transaction_failed = mb_cpu_transaction_failed;
cc->get_phys_page_debug = mb_cpu_get_phys_page_debug; cc->get_phys_page_debug = mb_cpu_get_phys_page_debug;
#endif
dc->vmsd = &vmstate_mb_cpu; dc->vmsd = &vmstate_mb_cpu;
#endif
device_class_set_props(dc, mb_properties); device_class_set_props(dc, mb_properties);
cc->gdb_num_core_regs = 32 + 27; cc->gdb_num_core_regs = 32 + 27;

View File

@ -264,10 +264,10 @@ struct CPUMBState {
/* MSR_UM (1 << 11) */ /* MSR_UM (1 << 11) */
/* MSR_VM (1 << 13) */ /* MSR_VM (1 << 13) */
/* ESR_ESS_MASK [11:5] -- unwind into iflags for unaligned excp */ /* ESR_ESS_MASK [11:5] -- unwind into iflags for unaligned excp */
#define D_FLAG (1 << 12) /* Bit in ESR. */
#define DRTI_FLAG (1 << 16) #define DRTI_FLAG (1 << 16)
#define DRTE_FLAG (1 << 17) #define DRTE_FLAG (1 << 17)
#define DRTB_FLAG (1 << 18) #define DRTB_FLAG (1 << 18)
#define D_FLAG (1 << 19) /* Bit in ESR. */
/* TB dependent CPUMBState. */ /* TB dependent CPUMBState. */
#define IFLAGS_TB_MASK (D_FLAG | BIMM_FLAG | IMM_FLAG | \ #define IFLAGS_TB_MASK (D_FLAG | BIMM_FLAG | IMM_FLAG | \
@ -278,19 +278,54 @@ struct CPUMBState {
#if !defined(CONFIG_USER_ONLY) #if !defined(CONFIG_USER_ONLY)
/* Unified MMU. */ /* Unified MMU. */
struct microblaze_mmu mmu; MicroBlazeMMU mmu;
#endif #endif
/* Fields up to this point are cleared by a CPU reset */ /* Fields up to this point are cleared by a CPU reset */
struct {} end_reset_fields; struct {} end_reset_fields;
/* These fields are preserved on reset. */ /* These fields are preserved on reset. */
struct {
uint32_t regs[13];
} pvr;
}; };
/*
* Microblaze Configuration Settings
*
* Note that the structure is sorted by type and size to minimize holes.
*/
typedef struct {
char *version;
uint64_t addr_mask;
uint32_t base_vectors;
uint32_t pvr_user2;
uint32_t pvr_regs[13];
uint8_t addr_size;
uint8_t use_fpu;
uint8_t use_hw_mul;
uint8_t pvr_user1;
uint8_t pvr;
uint8_t mmu;
uint8_t mmu_tlb_access;
uint8_t mmu_zones;
bool stackprot;
bool use_barrel;
bool use_div;
bool use_msr_instr;
bool use_pcmp_instr;
bool use_mmu;
bool dcache_writeback;
bool endi;
bool dopb_bus_exception;
bool iopb_bus_exception;
bool illegal_opcode_exception;
bool opcode_0_illegal;
bool div_zero_exception;
bool unaligned_exceptions;
} MicroBlazeCPUConfig;
/** /**
* MicroBlazeCPU: * MicroBlazeCPU:
* @env: #CPUMBState * @env: #CPUMBState
@ -305,32 +340,7 @@ struct MicroBlazeCPU {
CPUNegativeOffsetState neg; CPUNegativeOffsetState neg;
CPUMBState env; CPUMBState env;
MicroBlazeCPUConfig cfg;
/* Microblaze Configuration Settings */
struct {
bool stackprot;
uint32_t base_vectors;
uint8_t addr_size;
uint8_t use_fpu;
uint8_t use_hw_mul;
bool use_barrel;
bool use_div;
bool use_msr_instr;
bool use_pcmp_instr;
bool use_mmu;
bool dcache_writeback;
bool endi;
bool dopb_bus_exception;
bool iopb_bus_exception;
bool illegal_opcode_exception;
bool opcode_0_illegal;
bool div_zero_exception;
bool unaligned_exceptions;
uint8_t pvr_user1;
uint32_t pvr_user2;
char *version;
uint8_t pvr;
} cfg;
}; };
@ -419,4 +429,8 @@ static inline int cpu_mmu_index(CPUMBState *env, bool ifetch)
return MMU_KERNEL_IDX; return MMU_KERNEL_IDX;
} }
#ifndef CONFIG_USER_ONLY
extern const VMStateDescription vmstate_mb_cpu;
#endif
#endif #endif

View File

@ -78,7 +78,7 @@ int mb_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n)
break; break;
case GDB_PVR0 ... GDB_PVR11: case GDB_PVR0 ... GDB_PVR11:
/* PVR12 is intentionally skipped */ /* PVR12 is intentionally skipped */
val = env->pvr.regs[n - GDB_PVR0]; val = cpu->cfg.pvr_regs[n - GDB_PVR0];
break; break;
case GDB_EDR: case GDB_EDR:
val = env->edr; val = env->edr;
@ -132,10 +132,6 @@ int mb_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n)
case GDB_BTR: case GDB_BTR:
env->btr = tmp; env->btr = tmp;
break; break;
case GDB_PVR0 ... GDB_PVR11:
/* PVR12 is intentionally skipped */
env->pvr.regs[n - GDB_PVR0] = tmp;
break;
case GDB_EDR: case GDB_EDR:
env->edr = tmp; env->edr = tmp;
break; break;

View File

@ -52,7 +52,7 @@ bool mb_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
{ {
MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs); MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
CPUMBState *env = &cpu->env; CPUMBState *env = &cpu->env;
struct microblaze_mmu_lookup lu; MicroBlazeMMULookup lu;
unsigned int hit; unsigned int hit;
int prot; int prot;
@ -64,7 +64,7 @@ bool mb_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
return true; return true;
} }
hit = mmu_translate(&env->mmu, &lu, address, access_type, mmu_idx); hit = mmu_translate(cpu, &lu, address, access_type, mmu_idx);
if (likely(hit)) { if (likely(hit)) {
uint32_t vaddr = address & TARGET_PAGE_MASK; uint32_t vaddr = address & TARGET_PAGE_MASK;
uint32_t paddr = lu.paddr + vaddr - lu.vaddr; uint32_t paddr = lu.paddr + vaddr - lu.vaddr;
@ -111,6 +111,7 @@ void mb_cpu_do_interrupt(CPUState *cs)
MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs); MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
CPUMBState *env = &cpu->env; CPUMBState *env = &cpu->env;
uint32_t t, msr = mb_cpu_read_msr(env); uint32_t t, msr = mb_cpu_read_msr(env);
bool set_esr;
/* IMM flag cannot propagate across a branch and into the dslot. */ /* IMM flag cannot propagate across a branch and into the dslot. */
assert((env->iflags & (D_FLAG | IMM_FLAG)) != (D_FLAG | IMM_FLAG)); assert((env->iflags & (D_FLAG | IMM_FLAG)) != (D_FLAG | IMM_FLAG));
@ -118,142 +119,114 @@ void mb_cpu_do_interrupt(CPUState *cs)
assert((env->iflags & (D_FLAG | BIMM_FLAG)) != BIMM_FLAG); assert((env->iflags & (D_FLAG | BIMM_FLAG)) != BIMM_FLAG);
/* RTI flags are private to translate. */ /* RTI flags are private to translate. */
assert(!(env->iflags & (DRTI_FLAG | DRTE_FLAG | DRTB_FLAG))); assert(!(env->iflags & (DRTI_FLAG | DRTE_FLAG | DRTB_FLAG)));
env->res_addr = RES_ADDR_NONE;
switch (cs->exception_index) { switch (cs->exception_index) {
case EXCP_HW_EXCP: case EXCP_HW_EXCP:
if (!(env->pvr.regs[0] & PVR0_USE_EXC_MASK)) { if (!(cpu->cfg.pvr_regs[0] & PVR0_USE_EXC_MASK)) {
qemu_log_mask(LOG_GUEST_ERROR, "Exception raised on system without exceptions!\n"); qemu_log_mask(LOG_GUEST_ERROR,
return; "Exception raised on system without exceptions!\n");
} return;
}
env->regs[17] = env->pc + 4; qemu_log_mask(CPU_LOG_INT,
env->esr &= ~(1 << 12); "INT: HWE at pc=%08x msr=%08x iflags=%x\n",
env->pc, msr, env->iflags);
/* Exception breaks branch + dslot sequence? */ /* Exception breaks branch + dslot sequence? */
if (env->iflags & D_FLAG) { set_esr = true;
env->esr |= 1 << 12 ; env->esr &= ~D_FLAG;
env->btr = env->btarget; if (env->iflags & D_FLAG) {
} env->esr |= D_FLAG;
env->btr = env->btarget;
}
/* Disable the MMU. */ /* Exception in progress. */
t = (msr & (MSR_VM | MSR_UM)) << 1; msr |= MSR_EIP;
msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM); env->regs[17] = env->pc + 4;
msr |= t; env->pc = cpu->cfg.base_vectors + 0x20;
/* Exception in progress. */ break;
msr |= MSR_EIP;
mb_cpu_write_msr(env, msr);
qemu_log_mask(CPU_LOG_INT, case EXCP_MMU:
"hw exception at pc=%x ear=%" PRIx64 " " qemu_log_mask(CPU_LOG_INT,
"esr=%x iflags=%x\n", "INT: MMU at pc=%08x msr=%08x "
env->pc, env->ear, "ear=%" PRIx64 " iflags=%x\n",
env->esr, env->iflags); env->pc, msr, env->ear, env->iflags);
log_cpu_state_mask(CPU_LOG_INT, cs, 0);
env->iflags = 0;
env->pc = cpu->cfg.base_vectors + 0x20;
break;
case EXCP_MMU: /* Exception breaks branch + dslot sequence? */
set_esr = true;
env->esr &= ~D_FLAG;
if (env->iflags & D_FLAG) {
env->esr |= D_FLAG;
env->btr = env->btarget;
/* Reexecute the branch. */
env->regs[17] = env->pc - (env->iflags & BIMM_FLAG ? 8 : 4);
} else if (env->iflags & IMM_FLAG) {
/* Reexecute the imm. */
env->regs[17] = env->pc - 4;
} else {
env->regs[17] = env->pc; env->regs[17] = env->pc;
}
qemu_log_mask(CPU_LOG_INT, /* Exception in progress. */
"MMU exception at pc=%x iflags=%x ear=%" PRIx64 "\n", msr |= MSR_EIP;
env->pc, env->iflags, env->ear); env->pc = cpu->cfg.base_vectors + 0x20;
break;
env->esr &= ~(1 << 12); case EXCP_IRQ:
/* Exception breaks branch + dslot sequence? */ assert(!(msr & (MSR_EIP | MSR_BIP)));
if (env->iflags & D_FLAG) { assert(msr & MSR_IE);
env->esr |= 1 << 12 ; assert(!(env->iflags & (D_FLAG | IMM_FLAG)));
env->btr = env->btarget;
/* Reexecute the branch. */ qemu_log_mask(CPU_LOG_INT,
env->regs[17] -= 4; "INT: DEV at pc=%08x msr=%08x iflags=%x\n",
/* was the branch immprefixed?. */ env->pc, msr, env->iflags);
if (env->iflags & BIMM_FLAG) { set_esr = false;
env->regs[17] -= 4;
log_cpu_state_mask(CPU_LOG_INT, cs, 0);
}
} else if (env->iflags & IMM_FLAG) {
env->regs[17] -= 4;
}
/* Disable the MMU. */ /* Disable interrupts. */
t = (msr & (MSR_VM | MSR_UM)) << 1; msr &= ~MSR_IE;
msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM); env->regs[14] = env->pc;
msr |= t; env->pc = cpu->cfg.base_vectors + 0x10;
/* Exception in progress. */ break;
msr |= MSR_EIP;
mb_cpu_write_msr(env, msr);
qemu_log_mask(CPU_LOG_INT, case EXCP_HW_BREAK:
"exception at pc=%x ear=%" PRIx64 " iflags=%x\n", assert(!(env->iflags & (D_FLAG | IMM_FLAG)));
env->pc, env->ear, env->iflags);
log_cpu_state_mask(CPU_LOG_INT, cs, 0);
env->iflags = 0;
env->pc = cpu->cfg.base_vectors + 0x20;
break;
case EXCP_IRQ: qemu_log_mask(CPU_LOG_INT,
assert(!(msr & (MSR_EIP | MSR_BIP))); "INT: BRK at pc=%08x msr=%08x iflags=%x\n",
assert(msr & MSR_IE); env->pc, msr, env->iflags);
assert(!(env->iflags & (D_FLAG | IMM_FLAG))); set_esr = false;
t = (msr & (MSR_VM | MSR_UM)) << 1; /* Break in progress. */
msr |= MSR_BIP;
env->regs[16] = env->pc;
env->pc = cpu->cfg.base_vectors + 0x18;
break;
#if 0 default:
#include "disas/disas.h" cpu_abort(cs, "unhandled exception type=%d\n", cs->exception_index);
/* not reached */
}
/* Useful instrumentation when debugging interrupt issues in either /* Save previous mode, disable mmu, disable user-mode. */
the models or in sw. */ t = (msr & (MSR_VM | MSR_UM)) << 1;
{ msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM);
const char *sym; msr |= t;
mb_cpu_write_msr(env, msr);
sym = lookup_symbol(env->pc); env->res_addr = RES_ADDR_NONE;
if (sym env->iflags = 0;
&& (!strcmp("netif_rx", sym)
|| !strcmp("process_backlog", sym))) {
qemu_log("interrupt at pc=%x msr=%x %x iflags=%x sym=%s\n", if (!set_esr) {
env->pc, msr, t, env->iflags, sym); qemu_log_mask(CPU_LOG_INT,
" to pc=%08x msr=%08x\n", env->pc, msr);
log_cpu_state(cs, 0); } else if (env->esr & D_FLAG) {
} qemu_log_mask(CPU_LOG_INT,
} " to pc=%08x msr=%08x esr=%04x btr=%08x\n",
#endif env->pc, msr, env->esr, env->btr);
qemu_log_mask(CPU_LOG_INT, } else {
"interrupt at pc=%x msr=%x %x iflags=%x\n", qemu_log_mask(CPU_LOG_INT,
env->pc, msr, t, env->iflags); " to pc=%08x msr=%08x esr=%04x\n",
env->pc, msr, env->esr);
msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM | MSR_IE);
msr |= t;
mb_cpu_write_msr(env, msr);
env->regs[14] = env->pc;
env->iflags = 0;
env->pc = cpu->cfg.base_vectors + 0x10;
//log_cpu_state_mask(CPU_LOG_INT, cs, 0);
break;
case EXCP_HW_BREAK:
assert(!(env->iflags & (D_FLAG | IMM_FLAG)));
t = (msr & (MSR_VM | MSR_UM)) << 1;
qemu_log_mask(CPU_LOG_INT,
"break at pc=%x msr=%x %x iflags=%x\n",
env->pc, msr, t, env->iflags);
log_cpu_state_mask(CPU_LOG_INT, cs, 0);
msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM);
msr |= t;
msr |= MSR_BIP;
env->regs[16] = env->pc;
env->iflags = 0;
env->pc = cpu->cfg.base_vectors + 0x18;
mb_cpu_write_msr(env, msr);
break;
default:
cpu_abort(cs, "unhandled exception type=%d\n",
cs->exception_index);
break;
} }
} }
@ -262,12 +235,12 @@ hwaddr mb_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs); MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
CPUMBState *env = &cpu->env; CPUMBState *env = &cpu->env;
target_ulong vaddr, paddr = 0; target_ulong vaddr, paddr = 0;
struct microblaze_mmu_lookup lu; MicroBlazeMMULookup lu;
int mmu_idx = cpu_mmu_index(env, false); int mmu_idx = cpu_mmu_index(env, false);
unsigned int hit; unsigned int hit;
if (mmu_idx != MMU_NOMMU_IDX) { if (mmu_idx != MMU_NOMMU_IDX) {
hit = mmu_translate(&env->mmu, &lu, addr, 0, 0); hit = mmu_translate(cpu, &lu, addr, 0, 0);
if (hit) { if (hit) {
vaddr = addr & TARGET_PAGE_MASK; vaddr = addr & TARGET_PAGE_MASK;
paddr = lu.paddr + vaddr - lu.vaddr; paddr = lu.paddr + vaddr - lu.vaddr;

106
target/microblaze/machine.c Normal file
View File

@ -0,0 +1,106 @@
/*
* Microblaze VMState for qemu.
*
* Copyright (c) 2020 Linaro, 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 "qemu/osdep.h"
#include "cpu.h"
#include "migration/cpu.h"
static VMStateField vmstate_mmu_fields[] = {
VMSTATE_UINT64_2DARRAY(rams, MicroBlazeMMU, 2, TLB_ENTRIES),
VMSTATE_UINT8_ARRAY(tids, MicroBlazeMMU, TLB_ENTRIES),
VMSTATE_UINT32_ARRAY(regs, MicroBlazeMMU, 3),
VMSTATE_END_OF_LIST()
};
static const VMStateDescription vmstate_mmu = {
.name = "mmu",
.version_id = 0,
.minimum_version_id = 0,
.fields = vmstate_mmu_fields,
};
static int get_msr(QEMUFile *f, void *opaque, size_t size,
const VMStateField *field)
{
CPUMBState *env = container_of(opaque, CPUMBState, msr);
mb_cpu_write_msr(env, qemu_get_be32(f));
return 0;
}
static int put_msr(QEMUFile *f, void *opaque, size_t size,
const VMStateField *field, QJSON *vmdesc)
{
CPUMBState *env = container_of(opaque, CPUMBState, msr);
qemu_put_be32(f, mb_cpu_read_msr(env));
return 0;
}
static const VMStateInfo vmstate_msr = {
.name = "msr",
.get = get_msr,
.put = put_msr,
};
static VMStateField vmstate_env_fields[] = {
VMSTATE_UINT32_ARRAY(regs, CPUMBState, 32),
VMSTATE_UINT32(pc, CPUMBState),
VMSTATE_SINGLE(msr, CPUMBState, 0, vmstate_msr, uint32_t),
VMSTATE_UINT32(esr, CPUMBState),
VMSTATE_UINT32(fsr, CPUMBState),
VMSTATE_UINT32(btr, CPUMBState),
VMSTATE_UINT32(edr, CPUMBState),
VMSTATE_UINT32(slr, CPUMBState),
VMSTATE_UINT32(shr, CPUMBState),
VMSTATE_UINT64(ear, CPUMBState),
VMSTATE_UINT32(btarget, CPUMBState),
VMSTATE_UINT32(imm, CPUMBState),
VMSTATE_UINT32(iflags, CPUMBState),
VMSTATE_UINT32(res_val, CPUMBState),
VMSTATE_UINTTL(res_addr, CPUMBState),
VMSTATE_STRUCT(mmu, CPUMBState, 0, vmstate_mmu, MicroBlazeMMU),
VMSTATE_END_OF_LIST()
};
static const VMStateDescription vmstate_env = {
.name = "env",
.version_id = 0,
.minimum_version_id = 0,
.fields = vmstate_env_fields,
};
static VMStateField vmstate_cpu_fields[] = {
VMSTATE_CPU(),
VMSTATE_STRUCT(env, MicroBlazeCPU, 1, vmstate_env, CPUMBState),
VMSTATE_END_OF_LIST()
};
const VMStateDescription vmstate_mb_cpu = {
.name = "cpu",
.version_id = 0,
.minimum_version_id = 0,
.fields = vmstate_cpu_fields,
};

View File

@ -11,7 +11,10 @@ microblaze_ss.add(files(
)) ))
microblaze_softmmu_ss = ss.source_set() microblaze_softmmu_ss = ss.source_set()
microblaze_softmmu_ss.add(files('mmu.c')) microblaze_softmmu_ss.add(files(
'mmu.c',
'machine.c',
))
target_arch += {'microblaze': microblaze_ss} target_arch += {'microblaze': microblaze_ss}
target_softmmu_arch += {'microblaze': microblaze_softmmu_ss} target_softmmu_arch += {'microblaze': microblaze_softmmu_ss}

View File

@ -35,7 +35,7 @@ static unsigned int tlb_decode_size(unsigned int f)
static void mmu_flush_idx(CPUMBState *env, unsigned int idx) static void mmu_flush_idx(CPUMBState *env, unsigned int idx)
{ {
CPUState *cs = env_cpu(env); CPUState *cs = env_cpu(env);
struct microblaze_mmu *mmu = &env->mmu; MicroBlazeMMU *mmu = &env->mmu;
unsigned int tlb_size; unsigned int tlb_size;
uint32_t tlb_tag, end, t; uint32_t tlb_tag, end, t;
@ -55,7 +55,7 @@ static void mmu_flush_idx(CPUMBState *env, unsigned int idx)
static void mmu_change_pid(CPUMBState *env, unsigned int newpid) static void mmu_change_pid(CPUMBState *env, unsigned int newpid)
{ {
struct microblaze_mmu *mmu = &env->mmu; MicroBlazeMMU *mmu = &env->mmu;
unsigned int i; unsigned int i;
uint32_t t; uint32_t t;
@ -73,10 +73,10 @@ static void mmu_change_pid(CPUMBState *env, unsigned int newpid)
} }
/* rw - 0 = read, 1 = write, 2 = fetch. */ /* rw - 0 = read, 1 = write, 2 = fetch. */
unsigned int mmu_translate(struct microblaze_mmu *mmu, unsigned int mmu_translate(MicroBlazeCPU *cpu, MicroBlazeMMULookup *lu,
struct microblaze_mmu_lookup *lu,
target_ulong vaddr, int rw, int mmu_idx) target_ulong vaddr, int rw, int mmu_idx)
{ {
MicroBlazeMMU *mmu = &cpu->env.mmu;
unsigned int i, hit = 0; unsigned int i, hit = 0;
unsigned int tlb_ex = 0, tlb_wr = 0, tlb_zsel; unsigned int tlb_ex = 0, tlb_wr = 0, tlb_zsel;
uint64_t tlb_tag, tlb_rpn, mask; uint64_t tlb_tag, tlb_rpn, mask;
@ -115,13 +115,13 @@ unsigned int mmu_translate(struct microblaze_mmu *mmu,
t0 = mmu->regs[MMU_R_ZPR] >> (30 - (tlb_zsel * 2)); t0 = mmu->regs[MMU_R_ZPR] >> (30 - (tlb_zsel * 2));
t0 &= 0x3; t0 &= 0x3;
if (tlb_zsel > mmu->c_mmu_zones) { if (tlb_zsel > cpu->cfg.mmu_zones) {
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"tlb zone select out of range! %d\n", tlb_zsel); "tlb zone select out of range! %d\n", tlb_zsel);
t0 = 1; /* Ignore. */ t0 = 1; /* Ignore. */
} }
if (mmu->c_mmu == 1) { if (cpu->cfg.mmu == 1) {
t0 = 1; /* Zones are disabled. */ t0 = 1; /* Zones are disabled. */
} }
@ -158,7 +158,7 @@ unsigned int mmu_translate(struct microblaze_mmu *mmu,
tlb_rpn = d & TLB_RPN_MASK; tlb_rpn = d & TLB_RPN_MASK;
lu->vaddr = tlb_tag; lu->vaddr = tlb_tag;
lu->paddr = tlb_rpn & mmu->c_addr_mask; lu->paddr = tlb_rpn & cpu->cfg.addr_mask;
lu->size = tlb_size; lu->size = tlb_size;
lu->err = ERR_HIT; lu->err = ERR_HIT;
lu->idx = i; lu->idx = i;
@ -176,10 +176,11 @@ done:
/* Writes/reads to the MMU's special regs end up here. */ /* Writes/reads to the MMU's special regs end up here. */
uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn) uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
{ {
MicroBlazeCPU *cpu = env_archcpu(env);
unsigned int i; unsigned int i;
uint32_t r = 0; uint32_t r = 0;
if (env->mmu.c_mmu < 2 || !env->mmu.c_mmu_tlb_access) { if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n"); qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
return 0; return 0;
} }
@ -192,7 +193,7 @@ uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
/* Reads to HI/LO trig reads from the mmu rams. */ /* Reads to HI/LO trig reads from the mmu rams. */
case MMU_R_TLBLO: case MMU_R_TLBLO:
case MMU_R_TLBHI: case MMU_R_TLBHI:
if (!(env->mmu.c_mmu_tlb_access & 1)) { if (!(cpu->cfg.mmu_tlb_access & 1)) {
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"Invalid access to MMU reg %d\n", rn); "Invalid access to MMU reg %d\n", rn);
return 0; return 0;
@ -205,7 +206,7 @@ uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
break; break;
case MMU_R_PID: case MMU_R_PID:
case MMU_R_ZPR: case MMU_R_ZPR:
if (!(env->mmu.c_mmu_tlb_access & 1)) { if (!(cpu->cfg.mmu_tlb_access & 1)) {
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"Invalid access to MMU reg %d\n", rn); "Invalid access to MMU reg %d\n", rn);
return 0; return 0;
@ -228,12 +229,14 @@ uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v) void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
{ {
MicroBlazeCPU *cpu = env_archcpu(env);
uint64_t tmp64; uint64_t tmp64;
unsigned int i; unsigned int i;
qemu_log_mask(CPU_LOG_MMU, qemu_log_mask(CPU_LOG_MMU,
"%s rn=%d=%x old=%x\n", __func__, rn, v, env->mmu.regs[rn]); "%s rn=%d=%x old=%x\n", __func__, rn, v, env->mmu.regs[rn]);
if (env->mmu.c_mmu < 2 || !env->mmu.c_mmu_tlb_access) { if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n"); qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
return; return;
} }
@ -259,7 +262,7 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
env->mmu.rams[rn & 1][i] = deposit64(tmp64, ext * 32, 32, v); env->mmu.rams[rn & 1][i] = deposit64(tmp64, ext * 32, 32, v);
break; break;
case MMU_R_ZPR: case MMU_R_ZPR:
if (env->mmu.c_mmu_tlb_access <= 1) { if (cpu->cfg.mmu_tlb_access <= 1) {
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"Invalid access to MMU reg %d\n", rn); "Invalid access to MMU reg %d\n", rn);
return; return;
@ -273,7 +276,7 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
env->mmu.regs[rn] = v; env->mmu.regs[rn] = v;
break; break;
case MMU_R_PID: case MMU_R_PID:
if (env->mmu.c_mmu_tlb_access <= 1) { if (cpu->cfg.mmu_tlb_access <= 1) {
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"Invalid access to MMU reg %d\n", rn); "Invalid access to MMU reg %d\n", rn);
return; return;
@ -290,17 +293,17 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
break; break;
case MMU_R_TLBSX: case MMU_R_TLBSX:
{ {
struct microblaze_mmu_lookup lu; MicroBlazeMMULookup lu;
int hit; int hit;
if (env->mmu.c_mmu_tlb_access <= 1) { if (cpu->cfg.mmu_tlb_access <= 1) {
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"Invalid access to MMU reg %d\n", rn); "Invalid access to MMU reg %d\n", rn);
return; return;
} }
hit = mmu_translate(&env->mmu, &lu, hit = mmu_translate(cpu, &lu, v & TLB_EPN_MASK,
v & TLB_EPN_MASK, 0, cpu_mmu_index(env, false)); 0, cpu_mmu_index(env, false));
if (hit) { if (hit) {
env->mmu.regs[MMU_R_TLBX] = lu.idx; env->mmu.regs[MMU_R_TLBX] = lu.idx;
} else { } else {
@ -314,7 +317,7 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
} }
} }
void mmu_init(struct microblaze_mmu *mmu) void mmu_init(MicroBlazeMMU *mmu)
{ {
int i; int i;
for (i = 0; i < ARRAY_SIZE(mmu->regs); i++) { for (i = 0; i < ARRAY_SIZE(mmu->regs); i++) {

View File

@ -63,23 +63,16 @@
#define TLB_ENTRIES 64 #define TLB_ENTRIES 64
struct microblaze_mmu typedef struct {
{
/* Data and tag brams. */ /* Data and tag brams. */
uint64_t rams[2][TLB_ENTRIES]; uint64_t rams[2][TLB_ENTRIES];
/* We keep a separate ram for the tids to avoid the 48 bit tag width. */ /* We keep a separate ram for the tids to avoid the 48 bit tag width. */
uint8_t tids[TLB_ENTRIES]; uint8_t tids[TLB_ENTRIES];
/* Control flops. */ /* Control flops. */
uint32_t regs[3]; uint32_t regs[3];
} MicroBlazeMMU;
int c_mmu; typedef struct {
int c_mmu_tlb_access;
int c_mmu_zones;
uint64_t c_addr_mask; /* Mask to apply to physical addresses. */
};
struct microblaze_mmu_lookup
{
uint32_t paddr; uint32_t paddr;
uint32_t vaddr; uint32_t vaddr;
unsigned int size; unsigned int size;
@ -88,13 +81,12 @@ struct microblaze_mmu_lookup
enum { enum {
ERR_PROT, ERR_MISS, ERR_HIT ERR_PROT, ERR_MISS, ERR_HIT
} err; } err;
}; } MicroBlazeMMULookup;
unsigned int mmu_translate(struct microblaze_mmu *mmu, unsigned int mmu_translate(MicroBlazeCPU *cpu, MicroBlazeMMULookup *lu,
struct microblaze_mmu_lookup *lu,
target_ulong vaddr, int rw, int mmu_idx); target_ulong vaddr, int rw, int mmu_idx);
uint32_t mmu_read(CPUMBState *env, bool ea, uint32_t rn); uint32_t mmu_read(CPUMBState *env, bool ea, uint32_t rn);
void mmu_write(CPUMBState *env, bool ea, uint32_t rn, uint32_t v); void mmu_write(CPUMBState *env, bool ea, uint32_t rn, uint32_t v);
void mmu_init(struct microblaze_mmu *mmu); void mmu_init(MicroBlazeMMU *mmu);
#endif #endif

View File

@ -134,7 +134,7 @@ static void update_fpu_flags(CPUMBState *env, int flags, uintptr_t ra)
raise = 1; raise = 1;
} }
if (raise if (raise
&& (env->pvr.regs[2] & PVR2_FPU_EXC_MASK) && (env_archcpu(env)->cfg.pvr_regs[2] & PVR2_FPU_EXC_MASK)
&& (env->msr & MSR_EE)) { && (env->msr & MSR_EE)) {
raise_fpu_exception(env, ra); raise_fpu_exception(env, ra);
} }

View File

@ -37,7 +37,12 @@
/* is_jmp field values */ /* is_jmp field values */
#define DISAS_JUMP DISAS_TARGET_0 /* only pc was modified dynamically */ #define DISAS_JUMP DISAS_TARGET_0 /* only pc was modified dynamically */
#define DISAS_UPDATE DISAS_TARGET_1 /* cpu state was modified dynamically */ #define DISAS_EXIT DISAS_TARGET_1 /* all cpu state modified dynamically */
/* cpu state besides pc was modified dynamically; update pc to next */
#define DISAS_EXIT_NEXT DISAS_TARGET_2
/* cpu state besides pc was modified dynamically; update pc to btarget */
#define DISAS_EXIT_JUMP DISAS_TARGET_3
static TCGv_i32 cpu_R[32]; static TCGv_i32 cpu_R[32];
static TCGv_i32 cpu_pc; static TCGv_i32 cpu_pc;
@ -55,7 +60,7 @@ static TCGv_i32 cpu_res_val;
/* This is the state at translation time. */ /* This is the state at translation time. */
typedef struct DisasContext { typedef struct DisasContext {
DisasContextBase base; DisasContextBase base;
MicroBlazeCPU *cpu; const MicroBlazeCPUConfig *cfg;
/* TCG op of the current insn_start. */ /* TCG op of the current insn_start. */
TCGOp *insn_start; TCGOp *insn_start;
@ -65,7 +70,6 @@ typedef struct DisasContext {
/* Decoder. */ /* Decoder. */
uint32_t ext_imm; uint32_t ext_imm;
unsigned int cpustate_changed;
unsigned int tb_flags; unsigned int tb_flags;
unsigned int tb_flags_to_set; unsigned int tb_flags_to_set;
int mem_index; int mem_index;
@ -143,7 +147,7 @@ static void gen_goto_tb(DisasContext *dc, int n, target_ulong dest)
tcg_gen_exit_tb(dc->base.tb, n); tcg_gen_exit_tb(dc->base.tb, n);
} else { } else {
tcg_gen_movi_i32(cpu_pc, dest); tcg_gen_movi_i32(cpu_pc, dest);
tcg_gen_exit_tb(NULL, 0); tcg_gen_lookup_and_goto_ptr();
} }
dc->base.is_jmp = DISAS_NORETURN; dc->base.is_jmp = DISAS_NORETURN;
} }
@ -155,7 +159,7 @@ static void gen_goto_tb(DisasContext *dc, int n, target_ulong dest)
static bool trap_illegal(DisasContext *dc, bool cond) static bool trap_illegal(DisasContext *dc, bool cond)
{ {
if (cond && (dc->tb_flags & MSR_EE) if (cond && (dc->tb_flags & MSR_EE)
&& dc->cpu->cfg.illegal_opcode_exception) { && dc->cfg->illegal_opcode_exception) {
gen_raise_hw_excp(dc, ESR_EC_ILLEGAL_OP); gen_raise_hw_excp(dc, ESR_EC_ILLEGAL_OP);
} }
return cond; return cond;
@ -175,6 +179,21 @@ static bool trap_userspace(DisasContext *dc, bool cond)
return cond_user; return cond_user;
} }
/*
* Return true, and log an error, if the current insn is
* within a delay slot.
*/
static bool invalid_delay_slot(DisasContext *dc, const char *insn_type)
{
if (dc->tb_flags & D_FLAG) {
qemu_log_mask(LOG_GUEST_ERROR,
"Invalid insn in delay slot: %s at %08x\n",
insn_type, (uint32_t)dc->base.pc_next);
return true;
}
return false;
}
static TCGv_i32 reg_for_read(DisasContext *dc, int reg) static TCGv_i32 reg_for_read(DisasContext *dc, int reg)
{ {
if (likely(reg != 0)) { if (likely(reg != 0)) {
@ -272,7 +291,7 @@ static bool do_typeb_val(DisasContext *dc, arg_typeb *arg, bool side_effects,
#define DO_TYPEA_CFG(NAME, CFG, SE, FN) \ #define DO_TYPEA_CFG(NAME, CFG, SE, FN) \
static bool trans_##NAME(DisasContext *dc, arg_typea *a) \ static bool trans_##NAME(DisasContext *dc, arg_typea *a) \
{ return dc->cpu->cfg.CFG && do_typea(dc, a, SE, FN); } { return dc->cfg->CFG && do_typea(dc, a, SE, FN); }
#define DO_TYPEA0(NAME, SE, FN) \ #define DO_TYPEA0(NAME, SE, FN) \
static bool trans_##NAME(DisasContext *dc, arg_typea0 *a) \ static bool trans_##NAME(DisasContext *dc, arg_typea0 *a) \
@ -280,7 +299,7 @@ static bool do_typeb_val(DisasContext *dc, arg_typeb *arg, bool side_effects,
#define DO_TYPEA0_CFG(NAME, CFG, SE, FN) \ #define DO_TYPEA0_CFG(NAME, CFG, SE, FN) \
static bool trans_##NAME(DisasContext *dc, arg_typea0 *a) \ static bool trans_##NAME(DisasContext *dc, arg_typea0 *a) \
{ return dc->cpu->cfg.CFG && do_typea0(dc, a, SE, FN); } { return dc->cfg->CFG && do_typea0(dc, a, SE, FN); }
#define DO_TYPEBI(NAME, SE, FNI) \ #define DO_TYPEBI(NAME, SE, FNI) \
static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \ static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \
@ -288,7 +307,7 @@ static bool do_typeb_val(DisasContext *dc, arg_typeb *arg, bool side_effects,
#define DO_TYPEBI_CFG(NAME, CFG, SE, FNI) \ #define DO_TYPEBI_CFG(NAME, CFG, SE, FNI) \
static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \ static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \
{ return dc->cpu->cfg.CFG && do_typeb_imm(dc, a, SE, FNI); } { return dc->cfg->CFG && do_typeb_imm(dc, a, SE, FNI); }
#define DO_TYPEBV(NAME, SE, FN) \ #define DO_TYPEBV(NAME, SE, FN) \
static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \ static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \
@ -496,6 +515,9 @@ DO_TYPEA_CFG(idivu, use_div, true, gen_idivu)
static bool trans_imm(DisasContext *dc, arg_imm *arg) static bool trans_imm(DisasContext *dc, arg_imm *arg)
{ {
if (invalid_delay_slot(dc, "imm")) {
return true;
}
dc->ext_imm = arg->imm << 16; dc->ext_imm = arg->imm << 16;
tcg_gen_movi_i32(cpu_imm, dc->ext_imm); tcg_gen_movi_i32(cpu_imm, dc->ext_imm);
dc->tb_flags_to_set = IMM_FLAG; dc->tb_flags_to_set = IMM_FLAG;
@ -661,7 +683,7 @@ static TCGv compute_ldst_addr_typea(DisasContext *dc, int ra, int rb)
tcg_gen_movi_tl(ret, 0); tcg_gen_movi_tl(ret, 0);
} }
if ((ra == 1 || rb == 1) && dc->cpu->cfg.stackprot) { if ((ra == 1 || rb == 1) && dc->cfg->stackprot) {
gen_helper_stackprot(cpu_env, ret); gen_helper_stackprot(cpu_env, ret);
} }
return ret; return ret;
@ -681,7 +703,7 @@ static TCGv compute_ldst_addr_typeb(DisasContext *dc, int ra, int imm)
tcg_gen_movi_tl(ret, (uint32_t)imm); tcg_gen_movi_tl(ret, (uint32_t)imm);
} }
if (ra == 1 && dc->cpu->cfg.stackprot) { if (ra == 1 && dc->cfg->stackprot) {
gen_helper_stackprot(cpu_env, ret); gen_helper_stackprot(cpu_env, ret);
} }
return ret; return ret;
@ -690,7 +712,7 @@ static TCGv compute_ldst_addr_typeb(DisasContext *dc, int ra, int imm)
#ifndef CONFIG_USER_ONLY #ifndef CONFIG_USER_ONLY
static TCGv compute_ldst_addr_ea(DisasContext *dc, int ra, int rb) static TCGv compute_ldst_addr_ea(DisasContext *dc, int ra, int rb)
{ {
int addr_size = dc->cpu->cfg.addr_size; int addr_size = dc->cfg->addr_size;
TCGv ret = tcg_temp_new(); TCGv ret = tcg_temp_new();
if (addr_size == 32 || ra == 0) { if (addr_size == 32 || ra == 0) {
@ -750,7 +772,7 @@ static bool do_load(DisasContext *dc, int rd, TCGv addr, MemOp mop,
if (size > MO_8 && if (size > MO_8 &&
(dc->tb_flags & MSR_EE) && (dc->tb_flags & MSR_EE) &&
dc->cpu->cfg.unaligned_exceptions) { dc->cfg->unaligned_exceptions) {
record_unaligned_ess(dc, rd, size, false); record_unaligned_ess(dc, rd, size, false);
mop |= MO_ALIGN; mop |= MO_ALIGN;
} }
@ -896,7 +918,7 @@ static bool do_store(DisasContext *dc, int rd, TCGv addr, MemOp mop,
if (size > MO_8 && if (size > MO_8 &&
(dc->tb_flags & MSR_EE) && (dc->tb_flags & MSR_EE) &&
dc->cpu->cfg.unaligned_exceptions) { dc->cfg->unaligned_exceptions) {
record_unaligned_ess(dc, rd, size, true); record_unaligned_ess(dc, rd, size, true);
mop |= MO_ALIGN; mop |= MO_ALIGN;
} }
@ -1063,6 +1085,9 @@ static bool do_branch(DisasContext *dc, int dest_rb, int dest_imm,
{ {
uint32_t add_pc; uint32_t add_pc;
if (invalid_delay_slot(dc, "branch")) {
return true;
}
if (delay) { if (delay) {
setup_dslot(dc, dest_rb < 0); setup_dslot(dc, dest_rb < 0);
} }
@ -1102,6 +1127,9 @@ static bool do_bcc(DisasContext *dc, int dest_rb, int dest_imm,
{ {
TCGv_i32 zero, next; TCGv_i32 zero, next;
if (invalid_delay_slot(dc, "bcc")) {
return true;
}
if (delay) { if (delay) {
setup_dslot(dc, dest_rb < 0); setup_dslot(dc, dest_rb < 0);
} }
@ -1154,6 +1182,10 @@ static bool trans_brk(DisasContext *dc, arg_typea_br *arg)
if (trap_userspace(dc, true)) { if (trap_userspace(dc, true)) {
return true; return true;
} }
if (invalid_delay_slot(dc, "brk")) {
return true;
}
tcg_gen_mov_i32(cpu_pc, reg_for_read(dc, arg->rb)); tcg_gen_mov_i32(cpu_pc, reg_for_read(dc, arg->rb));
if (arg->rd) { if (arg->rd) {
tcg_gen_movi_i32(cpu_R[arg->rd], dc->base.pc_next); tcg_gen_movi_i32(cpu_R[arg->rd], dc->base.pc_next);
@ -1161,7 +1193,7 @@ static bool trans_brk(DisasContext *dc, arg_typea_br *arg)
tcg_gen_ori_i32(cpu_msr, cpu_msr, MSR_BIP); tcg_gen_ori_i32(cpu_msr, cpu_msr, MSR_BIP);
tcg_gen_movi_tl(cpu_res_addr, -1); tcg_gen_movi_tl(cpu_res_addr, -1);
dc->base.is_jmp = DISAS_UPDATE; dc->base.is_jmp = DISAS_EXIT;
return true; return true;
} }
@ -1172,6 +1204,10 @@ static bool trans_brki(DisasContext *dc, arg_typeb_br *arg)
if (trap_userspace(dc, imm != 0x8 && imm != 0x18)) { if (trap_userspace(dc, imm != 0x8 && imm != 0x18)) {
return true; return true;
} }
if (invalid_delay_slot(dc, "brki")) {
return true;
}
tcg_gen_movi_i32(cpu_pc, imm); tcg_gen_movi_i32(cpu_pc, imm);
if (arg->rd) { if (arg->rd) {
tcg_gen_movi_i32(cpu_R[arg->rd], dc->base.pc_next); tcg_gen_movi_i32(cpu_R[arg->rd], dc->base.pc_next);
@ -1202,7 +1238,7 @@ static bool trans_brki(DisasContext *dc, arg_typeb_br *arg)
~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM)); ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM));
} }
tcg_gen_ori_i32(cpu_msr, cpu_msr, msr_to_set); tcg_gen_ori_i32(cpu_msr, cpu_msr, msr_to_set);
dc->base.is_jmp = DISAS_UPDATE; dc->base.is_jmp = DISAS_EXIT;
#endif #endif
return true; return true;
@ -1212,6 +1248,11 @@ static bool trans_mbar(DisasContext *dc, arg_mbar *arg)
{ {
int mbar_imm = arg->imm; int mbar_imm = arg->imm;
/* Note that mbar is a specialized branch instruction. */
if (invalid_delay_slot(dc, "mbar")) {
return true;
}
/* Data access memory barrier. */ /* Data access memory barrier. */
if ((mbar_imm & 2) == 0) { if ((mbar_imm & 2) == 0) {
tcg_gen_mb(TCG_BAR_SC | TCG_MO_ALL); tcg_gen_mb(TCG_BAR_SC | TCG_MO_ALL);
@ -1250,7 +1291,7 @@ static bool trans_mbar(DisasContext *dc, arg_mbar *arg)
* *
* Therefore, choose to end the TB always. * Therefore, choose to end the TB always.
*/ */
dc->cpustate_changed = 1; dc->base.is_jmp = DISAS_EXIT_NEXT;
return true; return true;
} }
@ -1259,6 +1300,10 @@ static bool do_rts(DisasContext *dc, arg_typeb_bc *arg, int to_set)
if (trap_userspace(dc, to_set)) { if (trap_userspace(dc, to_set)) {
return true; return true;
} }
if (invalid_delay_slot(dc, "rts")) {
return true;
}
dc->tb_flags_to_set |= to_set; dc->tb_flags_to_set |= to_set;
setup_dslot(dc, true); setup_dslot(dc, true);
@ -1280,7 +1325,7 @@ DO_RTS(rtsd, 0)
static bool trans_zero(DisasContext *dc, arg_zero *arg) static bool trans_zero(DisasContext *dc, arg_zero *arg)
{ {
/* If opcode_0_illegal, trap. */ /* If opcode_0_illegal, trap. */
if (dc->cpu->cfg.opcode_0_illegal) { if (dc->cfg->opcode_0_illegal) {
trap_illegal(dc, true); trap_illegal(dc, true);
return true; return true;
} }
@ -1302,19 +1347,6 @@ static void msr_read(DisasContext *dc, TCGv_i32 d)
tcg_temp_free_i32(t); tcg_temp_free_i32(t);
} }
#ifndef CONFIG_USER_ONLY
static void msr_write(DisasContext *dc, TCGv_i32 v)
{
dc->cpustate_changed = 1;
/* Install MSR_C. */
tcg_gen_extract_i32(cpu_msr_c, v, 2, 1);
/* Clear MSR_C and MSR_CC; MSR_PVR is not writable, and is always clear. */
tcg_gen_andi_i32(cpu_msr, v, ~(MSR_C | MSR_CC | MSR_PVR));
}
#endif
static bool do_msrclrset(DisasContext *dc, arg_type_msr *arg, bool set) static bool do_msrclrset(DisasContext *dc, arg_type_msr *arg, bool set)
{ {
uint32_t imm = arg->imm; uint32_t imm = arg->imm;
@ -1347,7 +1379,7 @@ static bool do_msrclrset(DisasContext *dc, arg_type_msr *arg, bool set)
} else { } else {
tcg_gen_andi_i32(cpu_msr, cpu_msr, ~imm); tcg_gen_andi_i32(cpu_msr, cpu_msr, ~imm);
} }
dc->cpustate_changed = 1; dc->base.is_jmp = DISAS_EXIT_NEXT;
} }
return true; return true;
} }
@ -1380,7 +1412,13 @@ static bool trans_mts(DisasContext *dc, arg_mts *arg)
TCGv_i32 src = reg_for_read(dc, arg->ra); TCGv_i32 src = reg_for_read(dc, arg->ra);
switch (arg->rs) { switch (arg->rs) {
case SR_MSR: case SR_MSR:
msr_write(dc, src); /* Install MSR_C. */
tcg_gen_extract_i32(cpu_msr_c, src, 2, 1);
/*
* Clear MSR_C and MSR_CC;
* MSR_PVR is not writable, and is always clear.
*/
tcg_gen_andi_i32(cpu_msr, src, ~(MSR_C | MSR_CC | MSR_PVR));
break; break;
case SR_FSR: case SR_FSR:
tcg_gen_st_i32(src, cpu_env, offsetof(CPUMBState, fsr)); tcg_gen_st_i32(src, cpu_env, offsetof(CPUMBState, fsr));
@ -1412,7 +1450,7 @@ static bool trans_mts(DisasContext *dc, arg_mts *arg)
qemu_log_mask(LOG_GUEST_ERROR, "Invalid mts reg 0x%x\n", arg->rs); qemu_log_mask(LOG_GUEST_ERROR, "Invalid mts reg 0x%x\n", arg->rs);
return true; return true;
} }
dc->cpustate_changed = 1; dc->base.is_jmp = DISAS_EXIT_NEXT;
return true; return true;
#endif #endif
} }
@ -1501,7 +1539,8 @@ static bool trans_mfs(DisasContext *dc, arg_mfs *arg)
case 0x2000 ... 0x200c: case 0x2000 ... 0x200c:
tcg_gen_ld_i32(dest, cpu_env, tcg_gen_ld_i32(dest, cpu_env,
offsetof(CPUMBState, pvr.regs[arg->rs - 0x2000])); offsetof(MicroBlazeCPU, cfg.pvr_regs[arg->rs - 0x2000])
- offsetof(MicroBlazeCPU, env));
break; break;
default: default:
qemu_log_mask(LOG_GUEST_ERROR, "Invalid mfs reg 0x%x\n", arg->rs); qemu_log_mask(LOG_GUEST_ERROR, "Invalid mfs reg 0x%x\n", arg->rs);
@ -1521,7 +1560,6 @@ static void do_rti(DisasContext *dc)
tcg_gen_or_i32(cpu_msr, cpu_msr, tmp); tcg_gen_or_i32(cpu_msr, cpu_msr, tmp);
tcg_temp_free_i32(tmp); tcg_temp_free_i32(tmp);
dc->tb_flags &= ~DRTI_FLAG;
} }
static void do_rtb(DisasContext *dc) static void do_rtb(DisasContext *dc)
@ -1534,7 +1572,6 @@ static void do_rtb(DisasContext *dc)
tcg_gen_or_i32(cpu_msr, cpu_msr, tmp); tcg_gen_or_i32(cpu_msr, cpu_msr, tmp);
tcg_temp_free_i32(tmp); tcg_temp_free_i32(tmp);
dc->tb_flags &= ~DRTB_FLAG;
} }
static void do_rte(DisasContext *dc) static void do_rte(DisasContext *dc)
@ -1548,7 +1585,6 @@ static void do_rte(DisasContext *dc)
tcg_gen_or_i32(cpu_msr, cpu_msr, tmp); tcg_gen_or_i32(cpu_msr, cpu_msr, tmp);
tcg_temp_free_i32(tmp); tcg_temp_free_i32(tmp);
dc->tb_flags &= ~DRTE_FLAG;
} }
/* Insns connected to FSL or AXI stream attached devices. */ /* Insns connected to FSL or AXI stream attached devices. */
@ -1622,9 +1658,8 @@ static void mb_tr_init_disas_context(DisasContextBase *dcb, CPUState *cs)
MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs); MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
int bound; int bound;
dc->cpu = cpu; dc->cfg = &cpu->cfg;
dc->tb_flags = dc->base.tb->flags; dc->tb_flags = dc->base.tb->flags;
dc->cpustate_changed = 0;
dc->ext_imm = dc->base.tb->cs_base; dc->ext_imm = dc->base.tb->cs_base;
dc->r0 = NULL; dc->r0 = NULL;
dc->r0_set = false; dc->r0_set = false;
@ -1700,20 +1735,47 @@ static void mb_tr_translate_insn(DisasContextBase *dcb, CPUState *cs)
dc->base.pc_next += 4; dc->base.pc_next += 4;
if (dc->jmp_cond != TCG_COND_NEVER && !(dc->tb_flags & D_FLAG)) { if (dc->jmp_cond != TCG_COND_NEVER && !(dc->tb_flags & D_FLAG)) {
if (dc->tb_flags & DRTI_FLAG) { /*
do_rti(dc); * Finish any return-from branch.
} else if (dc->tb_flags & DRTB_FLAG) { */
do_rtb(dc); uint32_t rt_ibe = dc->tb_flags & (DRTI_FLAG | DRTB_FLAG | DRTE_FLAG);
} else if (dc->tb_flags & DRTE_FLAG) { if (unlikely(rt_ibe != 0)) {
do_rte(dc); dc->tb_flags &= ~(DRTI_FLAG | DRTB_FLAG | DRTE_FLAG);
if (rt_ibe & DRTI_FLAG) {
do_rti(dc);
} else if (rt_ibe & DRTB_FLAG) {
do_rtb(dc);
} else {
do_rte(dc);
}
} }
dc->base.is_jmp = DISAS_JUMP;
}
/* Force an exit if the per-tb cpu state has changed. */ /* Complete the branch, ending the TB. */
if (dc->base.is_jmp == DISAS_NEXT && dc->cpustate_changed) { switch (dc->base.is_jmp) {
dc->base.is_jmp = DISAS_UPDATE; case DISAS_NORETURN:
tcg_gen_movi_i32(cpu_pc, dc->base.pc_next); /*
* E.g. illegal insn in a delay slot. We've already exited
* and will handle D_FLAG in mb_cpu_do_interrupt.
*/
break;
case DISAS_NEXT:
/*
* Normal insn a delay slot.
* However, the return-from-exception type insns should
* return to the main loop, as they have adjusted MSR.
*/
dc->base.is_jmp = (rt_ibe ? DISAS_EXIT_JUMP : DISAS_JUMP);
break;
case DISAS_EXIT_NEXT:
/*
* E.g. mts insn in a delay slot. Continue with btarget,
* but still return to the main loop.
*/
dc->base.is_jmp = DISAS_EXIT_JUMP;
break;
default:
g_assert_not_reached();
}
} }
} }
@ -1733,13 +1795,15 @@ static void mb_tr_tb_stop(DisasContextBase *dcb, CPUState *cs)
gen_goto_tb(dc, 0, dc->base.pc_next); gen_goto_tb(dc, 0, dc->base.pc_next);
return; return;
case DISAS_UPDATE: case DISAS_EXIT:
if (unlikely(cs->singlestep_enabled)) { break;
gen_raise_exception(dc, EXCP_DEBUG); case DISAS_EXIT_NEXT:
} else { tcg_gen_movi_i32(cpu_pc, dc->base.pc_next);
tcg_gen_exit_tb(NULL, 0); break;
} case DISAS_EXIT_JUMP:
return; tcg_gen_mov_i32(cpu_pc, cpu_btarget);
tcg_gen_discard_i32(cpu_btarget);
break;
case DISAS_JUMP: case DISAS_JUMP:
if (dc->jmp_dest != -1 && !cs->singlestep_enabled) { if (dc->jmp_dest != -1 && !cs->singlestep_enabled) {
@ -1774,13 +1838,20 @@ static void mb_tr_tb_stop(DisasContextBase *dcb, CPUState *cs)
if (unlikely(cs->singlestep_enabled)) { if (unlikely(cs->singlestep_enabled)) {
gen_raise_exception(dc, EXCP_DEBUG); gen_raise_exception(dc, EXCP_DEBUG);
} else { } else {
tcg_gen_exit_tb(NULL, 0); tcg_gen_lookup_and_goto_ptr();
} }
return; return;
default: default:
g_assert_not_reached(); g_assert_not_reached();
} }
/* Finish DISAS_EXIT_* */
if (unlikely(cs->singlestep_enabled)) {
gen_raise_exception(dc, EXCP_DEBUG);
} else {
tcg_gen_exit_tb(NULL, 0);
}
} }
static void mb_tr_disas_log(const DisasContextBase *dcb, CPUState *cs) static void mb_tr_disas_log(const DisasContextBase *dcb, CPUState *cs)
@ -1848,11 +1919,6 @@ void mb_cpu_dump_state(CPUState *cs, FILE *f, int flags)
env->esr, env->fsr, env->btr, env->edr, env->esr, env->fsr, env->btr, env->edr,
env->ear, env->slr, env->shr); env->ear, env->slr, env->shr);
for (i = 0; i < 12; i++) {
qemu_fprintf(f, "rpvr%-2d=%08x%c",
i, env->pvr.regs[i], i % 4 == 3 ? '\n' : ' ');
}
for (i = 0; i < 32; i++) { for (i = 0; i < 32; i++) {
qemu_fprintf(f, "r%2.2d=%08x%c", qemu_fprintf(f, "r%2.2d=%08x%c",
i, env->regs[i], i % 4 == 3 ? '\n' : ' '); i, env->regs[i], i % 4 == 3 ? '\n' : ' ');