aboutsummaryrefslogtreecommitdiff
path: root/target-arm
diff options
context:
space:
mode:
Diffstat (limited to 'target-arm')
-rw-r--r--target-arm/helper.c30
1 files changed, 29 insertions, 1 deletions
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 5b994d5..261d547 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -463,6 +463,26 @@ void cpu_arm_close(CPUARMState *env)
g_free(env);
}
+static int bad_mode_switch(CPUState *env, int mode)
+{
+ /* Return true if it is not valid for us to switch to
+ * this CPU mode (ie all the UNPREDICTABLE cases in
+ * the ARM ARM CPSRWriteByInstr pseudocode).
+ */
+ switch (mode) {
+ case ARM_CPU_MODE_USR:
+ case ARM_CPU_MODE_SYS:
+ case ARM_CPU_MODE_SVC:
+ case ARM_CPU_MODE_ABT:
+ case ARM_CPU_MODE_UND:
+ case ARM_CPU_MODE_IRQ:
+ case ARM_CPU_MODE_FIQ:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
uint32_t cpsr_read(CPUARMState *env)
{
int ZF;
@@ -499,7 +519,15 @@ void cpsr_write(CPUARMState *env, uint32_t val, uint32_t mask)
}
if ((env->uncached_cpsr ^ val) & mask & CPSR_M) {
- switch_mode(env, val & CPSR_M);
+ if (bad_mode_switch(env, val & CPSR_M)) {
+ /* Attempt to switch to an invalid mode: this is UNPREDICTABLE.
+ * We choose to ignore the attempt and leave the CPSR M field
+ * untouched.
+ */
+ mask &= ~CPSR_M;
+ } else {
+ switch_mode(env, val & CPSR_M);
+ }
}
mask &= ~CACHED_CPSR_BITS;
env->uncached_cpsr = (env->uncached_cpsr & ~mask) | (val & mask);