diff options
Diffstat (limited to 'target/arm')
-rw-r--r-- | target/arm/cpu.c | 50 | ||||
-rw-r--r-- | target/arm/cpu.h | 62 | ||||
-rw-r--r-- | target/arm/helper.c | 160 | ||||
-rw-r--r-- | target/arm/internals.h | 7 | ||||
-rw-r--r-- | target/arm/machine.c | 12 | ||||
-rw-r--r-- | target/arm/translate.c | 20 |
6 files changed, 228 insertions, 83 deletions
diff --git a/target/arm/cpu.c b/target/arm/cpu.c index a941f6611b..e9f10f7747 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -179,15 +179,27 @@ static void arm_cpu_reset(CPUState *s) /* SVC mode with interrupts disabled. */ env->uncached_cpsr = ARM_CPU_MODE_SVC; env->daif = PSTATE_D | PSTATE_A | PSTATE_I | PSTATE_F; - /* On ARMv7-M the CPSR_I is the value of the PRIMASK register, and is - * clear at reset. Initial SP and PC are loaded from ROM. - */ - if (IS_M(env)) { + + if (arm_feature(env, ARM_FEATURE_M)) { uint32_t initial_msp; /* Loaded from 0x0 */ uint32_t initial_pc; /* Loaded from 0x4 */ uint8_t *rom; - env->daif &= ~PSTATE_I; + /* For M profile we store FAULTMASK and PRIMASK in the + * PSTATE F and I bits; these are both clear at reset. + */ + env->daif &= ~(PSTATE_I | PSTATE_F); + + /* The reset value of this bit is IMPDEF, but ARM recommends + * that it resets to 1, so QEMU always does that rather than making + * it dependent on CPU model. + */ + env->v7m.ccr = R_V7M_CCR_STKALIGN_MASK; + + /* Unlike A/R profile, M profile defines the reset LR value */ + env->regs[14] = 0xffffffff; + + /* Load the initial SP and PC from the vector table at address 0 */ rom = rom_ptr(0); if (rom) { /* Address zero is covered by ROM which hasn't yet been @@ -292,6 +304,33 @@ bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request) } #if !defined(CONFIG_USER_ONLY) || !defined(TARGET_AARCH64) +static void arm_v7m_unassigned_access(CPUState *cpu, hwaddr addr, + bool is_write, bool is_exec, int opaque, + unsigned size) +{ + ARMCPU *arm = ARM_CPU(cpu); + CPUARMState *env = &arm->env; + + /* ARMv7-M interrupt return works by loading a magic value into the PC. + * On real hardware the load causes the return to occur. The qemu + * implementation performs the jump normally, then does the exception + * return by throwing a special exception when when the CPU tries to + * execute code at the magic address. + */ + if (env->v7m.exception != 0 && addr >= 0xfffffff0 && is_exec) { + cpu->exception_index = EXCP_EXCEPTION_EXIT; + cpu_loop_exit(cpu); + } + + /* In real hardware an attempt to access parts of the address space + * with nothing there will usually cause an external abort. + * However our QEMU board models are often missing device models where + * the guest can boot anyway with the default read-as-zero/writes-ignored + * behaviour that you get without a QEMU unassigned_access hook. + * So just return here to retain that default behaviour. + */ +} + static bool arm_v7m_cpu_exec_interrupt(CPUState *cs, int interrupt_request) { CPUClass *cc = CPU_GET_CLASS(cs); @@ -1016,6 +1055,7 @@ static void arm_v7m_class_init(ObjectClass *oc, void *data) cc->do_interrupt = arm_v7m_cpu_do_interrupt; #endif + cc->do_unassigned_access = arm_v7m_unassigned_access; cc->cpu_exec_interrupt = arm_v7m_cpu_exec_interrupt; } diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 151a5d754e..39bff86daf 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -21,6 +21,7 @@ #define ARM_CPU_H #include "kvm-consts.h" +#include "hw/registerfields.h" #if defined(TARGET_AARCH64) /* AArch64 definitions */ @@ -52,6 +53,7 @@ #define EXCP_VIRQ 14 #define EXCP_VFIQ 15 #define EXCP_SEMIHOST 16 /* semihosting call */ +#define EXCP_NOCP 17 /* v7M NOCP UsageFault */ #define ARMV7M_EXCP_RESET 1 #define ARMV7M_EXCP_NMI 2 @@ -405,7 +407,12 @@ typedef struct CPUARMState { uint32_t vecbase; uint32_t basepri; uint32_t control; - int current_sp; + uint32_t ccr; /* Configuration and Control */ + uint32_t cfsr; /* Configurable Fault Status */ + uint32_t hfsr; /* HardFault Status */ + uint32_t dfsr; /* Debug Fault Status Register */ + uint32_t mmfar; /* MemManage Fault Address */ + uint32_t bfar; /* BusFault Address */ int exception; } v7m; @@ -1087,6 +1094,53 @@ enum arm_cpu_mode { #define ARM_IWMMXT_wCGR2 10 #define ARM_IWMMXT_wCGR3 11 +/* V7M CCR bits */ +FIELD(V7M_CCR, NONBASETHRDENA, 0, 1) +FIELD(V7M_CCR, USERSETMPEND, 1, 1) +FIELD(V7M_CCR, UNALIGN_TRP, 3, 1) +FIELD(V7M_CCR, DIV_0_TRP, 4, 1) +FIELD(V7M_CCR, BFHFNMIGN, 8, 1) +FIELD(V7M_CCR, STKALIGN, 9, 1) +FIELD(V7M_CCR, DC, 16, 1) +FIELD(V7M_CCR, IC, 17, 1) + +/* V7M CFSR bits for MMFSR */ +FIELD(V7M_CFSR, IACCVIOL, 0, 1) +FIELD(V7M_CFSR, DACCVIOL, 1, 1) +FIELD(V7M_CFSR, MUNSTKERR, 3, 1) +FIELD(V7M_CFSR, MSTKERR, 4, 1) +FIELD(V7M_CFSR, MLSPERR, 5, 1) +FIELD(V7M_CFSR, MMARVALID, 7, 1) + +/* V7M CFSR bits for BFSR */ +FIELD(V7M_CFSR, IBUSERR, 8 + 0, 1) +FIELD(V7M_CFSR, PRECISERR, 8 + 1, 1) +FIELD(V7M_CFSR, IMPRECISERR, 8 + 2, 1) +FIELD(V7M_CFSR, UNSTKERR, 8 + 3, 1) +FIELD(V7M_CFSR, STKERR, 8 + 4, 1) +FIELD(V7M_CFSR, LSPERR, 8 + 5, 1) +FIELD(V7M_CFSR, BFARVALID, 8 + 7, 1) + +/* V7M CFSR bits for UFSR */ +FIELD(V7M_CFSR, UNDEFINSTR, 16 + 0, 1) +FIELD(V7M_CFSR, INVSTATE, 16 + 1, 1) +FIELD(V7M_CFSR, INVPC, 16 + 2, 1) +FIELD(V7M_CFSR, NOCP, 16 + 3, 1) +FIELD(V7M_CFSR, UNALIGNED, 16 + 8, 1) +FIELD(V7M_CFSR, DIVBYZERO, 16 + 9, 1) + +/* V7M HFSR bits */ +FIELD(V7M_HFSR, VECTTBL, 1, 1) +FIELD(V7M_HFSR, FORCED, 30, 1) +FIELD(V7M_HFSR, DEBUGEVT, 31, 1) + +/* V7M DFSR bits */ +FIELD(V7M_DFSR, HALTED, 0, 1) +FIELD(V7M_DFSR, BKPT, 1, 1) +FIELD(V7M_DFSR, DWTTRAP, 2, 1) +FIELD(V7M_DFSR, VCATCH, 3, 1) +FIELD(V7M_DFSR, EXTERNAL, 4, 1) + /* If adding a feature bit which corresponds to a Linux ELF * HWCAP bit, remember to update the feature-bit-to-hwcap * mapping in linux-user/elfload.c:get_elf_hwcap(). @@ -1763,12 +1817,6 @@ bool write_list_to_cpustate(ARMCPU *cpu); */ bool write_cpustate_to_list(ARMCPU *cpu); -/* Does the core conform to the "MicroController" profile. e.g. Cortex-M3. - Note the M in older cores (eg. ARM7TDMI) stands for Multiply. These are - conventional cores (ie. Application or Realtime profile). */ - -#define IS_M(env) arm_feature(env, ARM_FEATURE_M) - #define ARM_CPUID_TI915T 0x54029152 #define ARM_CPUID_TI925T 0x54029252 diff --git a/target/arm/helper.c b/target/arm/helper.c index 7111c8cf18..c23df1b133 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -5947,14 +5947,19 @@ static uint32_t v7m_pop(CPUARMState *env) } /* Switch to V7M main or process stack pointer. */ -static void switch_v7m_sp(CPUARMState *env, int process) +static void switch_v7m_sp(CPUARMState *env, bool new_spsel) { uint32_t tmp; - if (env->v7m.current_sp != process) { + bool old_spsel = env->v7m.control & R_V7M_CONTROL_SPSEL_MASK; + + if (old_spsel != new_spsel) { tmp = env->v7m.other_sp; env->v7m.other_sp = env->regs[13]; env->regs[13] = tmp; - env->v7m.current_sp = process; + + env->v7m.control = deposit32(env->v7m.control, + R_V7M_CONTROL_SPSEL_SHIFT, + R_V7M_CONTROL_SPSEL_LENGTH, new_spsel); } } @@ -5964,8 +5969,13 @@ static void do_v7m_exception_exit(CPUARMState *env) uint32_t xpsr; type = env->regs[15]; - if (env->v7m.exception != 0) + if (env->v7m.exception != ARMV7M_EXCP_NMI) { + /* Auto-clear FAULTMASK on return from other than NMI */ + env->daif &= ~PSTATE_F; + } + if (env->v7m.exception != 0) { armv7m_nvic_complete_irq(env->nvic, env->v7m.exception); + } /* Switch to the target stack. */ switch_v7m_sp(env, (type & 4) != 0); @@ -6014,6 +6024,30 @@ static void arm_log_exception(int idx) } } +static uint32_t arm_v7m_load_vector(ARMCPU *cpu) + +{ + CPUState *cs = CPU(cpu); + CPUARMState *env = &cpu->env; + MemTxResult result; + hwaddr vec = env->v7m.vecbase + env->v7m.exception * 4; + uint32_t addr; + + addr = address_space_ldl(cs->as, vec, + MEMTXATTRS_UNSPECIFIED, &result); + if (result != MEMTX_OK) { + /* Architecturally this should cause a HardFault setting HSFR.VECTTBL, + * which would then be immediately followed by our failing to load + * the entry vector for that HardFault, which is a Lockup case. + * Since we don't model Lockup, we just report this guest error + * via cpu_abort(). + */ + cpu_abort(cs, "Failed to read from exception vector table " + "entry %08x\n", (unsigned)vec); + } + return addr; +} + void arm_v7m_cpu_do_interrupt(CPUState *cs) { ARMCPU *cpu = ARM_CPU(cs); @@ -6025,8 +6059,9 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs) arm_log_exception(cs->exception_index); lr = 0xfffffff1; - if (env->v7m.current_sp) + if (env->v7m.control & R_V7M_CONTROL_SPSEL_MASK) { lr |= 4; + } if (env->v7m.exception == 0) lr |= 8; @@ -6037,6 +6072,11 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs) switch (cs->exception_index) { case EXCP_UDEF: armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE); + env->v7m.cfsr |= R_V7M_CFSR_UNDEFINSTR_MASK; + return; + case EXCP_NOCP: + armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE); + env->v7m.cfsr |= R_V7M_CFSR_NOCP_MASK; return; case EXCP_SWI: /* The PC already points to the next instruction. */ @@ -6075,10 +6115,8 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs) return; /* Never happens. Keep compiler happy. */ } - /* Align stack pointer. */ - /* ??? Should only do this if Configuration Control Register - STACKALIGN bit is set. */ - if (env->regs[13] & 4) { + /* Align stack pointer if the guest wants that */ + if ((env->regs[13] & 4) && (env->v7m.ccr & R_V7M_CCR_STKALIGN_MASK)) { env->regs[13] -= 4; xpsr |= 0x200; } @@ -6095,7 +6133,7 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs) /* Clear IT bits */ env->condexec_bits = 0; env->regs[14] = lr; - addr = ldl_phys(cs->as, env->v7m.vecbase + env->v7m.exception * 4); + addr = arm_v7m_load_vector(cpu); env->regs[15] = addr & 0xfffffffe; env->thumb = addr & 1; } @@ -6660,7 +6698,7 @@ void arm_cpu_do_interrupt(CPUState *cs) CPUARMState *env = &cpu->env; unsigned int new_el = env->exception.target_el; - assert(!IS_M(env)); + assert(!arm_feature(env, ARM_FEATURE_M)); arm_log_exception(cs->exception_index); qemu_log_mask(CPU_LOG_INT, "...from EL%d to EL%d\n", arm_current_el(env), @@ -8243,27 +8281,38 @@ hwaddr arm_cpu_get_phys_page_attrs_debug(CPUState *cs, vaddr addr, uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg) { - ARMCPU *cpu = arm_env_get_cpu(env); + uint32_t mask; + unsigned el = arm_current_el(env); + + /* First handle registers which unprivileged can read */ + + switch (reg) { + case 0 ... 7: /* xPSR sub-fields */ + mask = 0; + if ((reg & 1) && el) { + mask |= 0x000001ff; /* IPSR (unpriv. reads as zero) */ + } + if (!(reg & 4)) { + mask |= 0xf8000000; /* APSR */ + } + /* EPSR reads as zero */ + return xpsr_read(env) & mask; + break; + case 20: /* CONTROL */ + return env->v7m.control; + } + + if (el == 0) { + return 0; /* unprivileged reads others as zero */ + } switch (reg) { - case 0: /* APSR */ - return xpsr_read(env) & 0xf8000000; - case 1: /* IAPSR */ - return xpsr_read(env) & 0xf80001ff; - case 2: /* EAPSR */ - return xpsr_read(env) & 0xff00fc00; - case 3: /* xPSR */ - return xpsr_read(env) & 0xff00fdff; - case 5: /* IPSR */ - return xpsr_read(env) & 0x000001ff; - case 6: /* EPSR */ - return xpsr_read(env) & 0x0700fc00; - case 7: /* IEPSR */ - return xpsr_read(env) & 0x0700edff; case 8: /* MSP */ - return env->v7m.current_sp ? env->v7m.other_sp : env->regs[13]; + return (env->v7m.control & R_V7M_CONTROL_SPSEL_MASK) ? + env->v7m.other_sp : env->regs[13]; case 9: /* PSP */ - return env->v7m.current_sp ? env->regs[13] : env->v7m.other_sp; + return (env->v7m.control & R_V7M_CONTROL_SPSEL_MASK) ? + env->regs[13] : env->v7m.other_sp; case 16: /* PRIMASK */ return (env->daif & PSTATE_I) != 0; case 17: /* BASEPRI */ @@ -8271,52 +8320,40 @@ uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg) return env->v7m.basepri; case 19: /* FAULTMASK */ return (env->daif & PSTATE_F) != 0; - case 20: /* CONTROL */ - return env->v7m.control; default: - /* ??? For debugging only. */ - cpu_abort(CPU(cpu), "Unimplemented system register read (%d)\n", reg); + qemu_log_mask(LOG_GUEST_ERROR, "Attempt to read unknown special" + " register %d\n", reg); return 0; } } void HELPER(v7m_msr)(CPUARMState *env, uint32_t reg, uint32_t val) { - ARMCPU *cpu = arm_env_get_cpu(env); + if (arm_current_el(env) == 0 && reg > 7) { + /* only xPSR sub-fields may be written by unprivileged */ + return; + } switch (reg) { - case 0: /* APSR */ - xpsr_write(env, val, 0xf8000000); - break; - case 1: /* IAPSR */ - xpsr_write(env, val, 0xf8000000); - break; - case 2: /* EAPSR */ - xpsr_write(env, val, 0xfe00fc00); - break; - case 3: /* xPSR */ - xpsr_write(env, val, 0xfe00fc00); - break; - case 5: /* IPSR */ - /* IPSR bits are readonly. */ - break; - case 6: /* EPSR */ - xpsr_write(env, val, 0x0600fc00); - break; - case 7: /* IEPSR */ - xpsr_write(env, val, 0x0600fc00); + case 0 ... 7: /* xPSR sub-fields */ + /* only APSR is actually writable */ + if (reg & 4) { + xpsr_write(env, val, 0xf8000000); /* APSR */ + } break; case 8: /* MSP */ - if (env->v7m.current_sp) + if (env->v7m.control & R_V7M_CONTROL_SPSEL_MASK) { env->v7m.other_sp = val; - else + } else { env->regs[13] = val; + } break; case 9: /* PSP */ - if (env->v7m.current_sp) + if (env->v7m.control & R_V7M_CONTROL_SPSEL_MASK) { env->regs[13] = val; - else + } else { env->v7m.other_sp = val; + } break; case 16: /* PRIMASK */ if (val & 1) { @@ -8341,12 +8378,13 @@ void HELPER(v7m_msr)(CPUARMState *env, uint32_t reg, uint32_t val) } break; case 20: /* CONTROL */ - env->v7m.control = val & 3; - switch_v7m_sp(env, (val & 2) != 0); + switch_v7m_sp(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0); + env->v7m.control = val & (R_V7M_CONTROL_SPSEL_MASK | + R_V7M_CONTROL_NPRIV_MASK); break; default: - /* ??? For debugging only. */ - cpu_abort(CPU(cpu), "Unimplemented system register write (%d)\n", reg); + qemu_log_mask(LOG_GUEST_ERROR, "Attempt to write unknown special" + " register %d\n", reg); return; } } diff --git a/target/arm/internals.h b/target/arm/internals.h index 3cae5ff3b5..2e65bc12fa 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -25,6 +25,8 @@ #ifndef TARGET_ARM_INTERNALS_H #define TARGET_ARM_INTERNALS_H +#include "hw/registerfields.h" + /* register banks for CPU modes */ #define BANK_USRSYS 0 #define BANK_SVC 1 @@ -75,6 +77,11 @@ static const char * const excnames[] = { */ #define GTIMER_SCALE 16 +/* Bit definitions for the v7M CONTROL register */ +FIELD(V7M_CONTROL, NPRIV, 0, 1) +FIELD(V7M_CONTROL, SPSEL, 1, 1) +FIELD(V7M_CONTROL, FPCA, 2, 1) + /* * For AArch64, map a given EL to an index in the banked_spsr array. * Note that this mapping and the AArch32 mapping defined in bank_number() diff --git a/target/arm/machine.c b/target/arm/machine.c index 487320db1d..fa5ec76090 100644 --- a/target/arm/machine.c +++ b/target/arm/machine.c @@ -99,15 +99,19 @@ static bool m_needed(void *opaque) static const VMStateDescription vmstate_m = { .name = "cpu/m", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 3, + .minimum_version_id = 3, .needed = m_needed, .fields = (VMStateField[]) { - VMSTATE_UINT32(env.v7m.other_sp, ARMCPU), VMSTATE_UINT32(env.v7m.vecbase, ARMCPU), VMSTATE_UINT32(env.v7m.basepri, ARMCPU), VMSTATE_UINT32(env.v7m.control, ARMCPU), - VMSTATE_INT32(env.v7m.current_sp, ARMCPU), + VMSTATE_UINT32(env.v7m.ccr, ARMCPU), + VMSTATE_UINT32(env.v7m.cfsr, ARMCPU), + VMSTATE_UINT32(env.v7m.hfsr, ARMCPU), + VMSTATE_UINT32(env.v7m.dfsr, ARMCPU), + VMSTATE_UINT32(env.v7m.mmfar, ARMCPU), + VMSTATE_UINT32(env.v7m.bfar, ARMCPU), VMSTATE_INT32(env.v7m.exception, ARMCPU), VMSTATE_END_OF_LIST() } diff --git a/target/arm/translate.c b/target/arm/translate.c index c9186b6195..493c627bcf 100644 --- a/target/arm/translate.c +++ b/target/arm/translate.c @@ -10217,6 +10217,14 @@ static int disas_thumb2_insn(CPUARMState *env, DisasContext *s, uint16_t insn_hw break; case 6: case 7: case 14: case 15: /* Coprocessor. */ + if (arm_dc_feature(s, ARM_FEATURE_M)) { + /* We don't currently implement M profile FP support, + * so this entire space should give a NOCP fault. + */ + gen_exception_insn(s, 4, EXCP_NOCP, syn_uncategorized(), + default_exception_el(s)); + break; + } if (((insn >> 24) & 3) == 3) { /* Translate into the equivalent ARM encoding. */ insn = (insn & 0xe2ffffff) | ((insn & (1 << 28)) >> 4) | (1 << 28); @@ -11719,12 +11727,12 @@ void gen_intermediate_code(CPUARMState *env, TranslationBlock *tb) break; } #else - if (dc->pc >= 0xfffffff0 && arm_dc_feature(dc, ARM_FEATURE_M)) { - /* We always get here via a jump, so know we are not in a - conditional execution block. */ - gen_exception_internal(EXCP_EXCEPTION_EXIT); - dc->is_jmp = DISAS_EXC; - break; + if (arm_dc_feature(dc, ARM_FEATURE_M)) { + /* Branches to the magic exception-return addresses should + * already have been caught via the arm_v7m_unassigned_access hook, + * and never get here. + */ + assert(dc->pc < 0xfffffff0); } #endif |