aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--target-arm/cpu.h2
-rw-r--r--target-arm/helper.c34
-rw-r--r--target-arm/internals.h25
-rw-r--r--target-arm/kvm64.c37
-rw-r--r--target-arm/machine.c7
-rw-r--r--target-arm/op_helper.c2
6 files changed, 100 insertions, 7 deletions
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index ecdd7a73a6..28b9bda218 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -163,6 +163,7 @@ typedef struct CPUARMState {
uint64_t daif; /* exception masks, in the bits they are in in PSTATE */
uint64_t elr_el1; /* AArch64 ELR_EL1 */
+ uint64_t sp_el[2]; /* AArch64 banked stack pointers */
/* System control coprocessor (cp15) */
struct {
@@ -434,6 +435,7 @@ int arm_cpu_handle_mmu_fault(CPUState *cpu, vaddr address, int rw,
* Only these are valid when in AArch64 mode; in
* AArch32 mode SPSRs are basically CPSR-format.
*/
+#define PSTATE_SP (1U)
#define PSTATE_M (0xFU)
#define PSTATE_nRW (1U << 4)
#define PSTATE_F (1U << 6)
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 276ecf28dc..27a3dc2a75 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -1783,6 +1783,27 @@ static uint64_t aa64_dczid_read(CPUARMState *env, const ARMCPRegInfo *ri)
return cpu->dcz_blocksize | dzp_bit;
}
+static CPAccessResult sp_el0_access(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ if (!env->pstate & PSTATE_SP) {
+ /* Access to SP_EL0 is undefined if it's being used as
+ * the stack pointer.
+ */
+ return CP_ACCESS_TRAP_UNCATEGORIZED;
+ }
+ return CP_ACCESS_OK;
+}
+
+static uint64_t spsel_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ return env->pstate & PSTATE_SP;
+}
+
+static void spsel_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t val)
+{
+ update_spsel(env, val);
+}
+
static const ARMCPRegInfo v8_cp_reginfo[] = {
/* Minimal set of EL0-visible registers. This will need to be expanded
* significantly for system emulation of AArch64 CPUs.
@@ -1915,6 +1936,19 @@ static const ARMCPRegInfo v8_cp_reginfo[] = {
.type = ARM_CP_NO_MIGRATE,
.opc0 = 3, .opc1 = 0, .crn = 4, .crm = 0, .opc2 = 1,
.access = PL1_RW, .fieldoffset = offsetof(CPUARMState, elr_el1) },
+ /* We rely on the access checks not allowing the guest to write to the
+ * state field when SPSel indicates that it's being used as the stack
+ * pointer.
+ */
+ { .name = "SP_EL0", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 0, .crn = 4, .crm = 1, .opc2 = 0,
+ .access = PL1_RW, .accessfn = sp_el0_access,
+ .type = ARM_CP_NO_MIGRATE,
+ .fieldoffset = offsetof(CPUARMState, sp_el[0]) },
+ { .name = "SPSel", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 0, .crn = 4, .crm = 2, .opc2 = 0,
+ .type = ARM_CP_NO_MIGRATE,
+ .access = PL1_RW, .readfn = spsel_read, .writefn = spsel_write },
REGINFO_SENTINEL
};
diff --git a/target-arm/internals.h b/target-arm/internals.h
index a527f02ff5..de79dfc316 100644
--- a/target-arm/internals.h
+++ b/target-arm/internals.h
@@ -60,6 +60,31 @@ enum arm_fprounding {
int arm_rmode_to_sf(int rmode);
+static inline void update_spsel(CPUARMState *env, uint32_t imm)
+{
+ /* Update PSTATE SPSel bit; this requires us to update the
+ * working stack pointer in xregs[31].
+ */
+ if (!((imm ^ env->pstate) & PSTATE_SP)) {
+ return;
+ }
+ env->pstate = deposit32(env->pstate, 0, 1, imm);
+
+ /* EL0 has no access rights to update SPSel, and this code
+ * assumes we are updating SP for EL1 while running as EL1.
+ */
+ assert(arm_current_pl(env) == 1);
+ if (env->pstate & PSTATE_SP) {
+ /* Switch from using SP_EL0 to using SP_ELx */
+ env->sp_el[0] = env->xregs[31];
+ env->xregs[31] = env->sp_el[1];
+ } else {
+ /* Switch from SP_EL0 to SP_ELx */
+ env->sp_el[1] = env->xregs[31];
+ env->xregs[31] = env->sp_el[0];
+ }
+}
+
/* Valid Syndrome Register EC field values */
enum arm_exception_class {
EC_UNCATEGORIZED = 0x00,
diff --git a/target-arm/kvm64.c b/target-arm/kvm64.c
index ee727487b8..39c4364593 100644
--- a/target-arm/kvm64.c
+++ b/target-arm/kvm64.c
@@ -121,8 +121,24 @@ int kvm_arch_put_registers(CPUState *cs, int level)
}
}
+ /* KVM puts SP_EL0 in regs.sp and SP_EL1 in regs.sp_el1. On the
+ * QEMU side we keep the current SP in xregs[31] as well.
+ */
+ if (env->pstate & PSTATE_SP) {
+ env->sp_el[1] = env->xregs[31];
+ } else {
+ env->sp_el[0] = env->xregs[31];
+ }
+
reg.id = AARCH64_CORE_REG(regs.sp);
- reg.addr = (uintptr_t) &env->xregs[31];
+ reg.addr = (uintptr_t) &env->sp_el[0];
+ ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &reg);
+ if (ret) {
+ return ret;
+ }
+
+ reg.id = AARCH64_CORE_REG(sp_el1);
+ reg.addr = (uintptr_t) &env->sp_el[1];
ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &reg);
if (ret) {
return ret;
@@ -152,7 +168,6 @@ int kvm_arch_put_registers(CPUState *cs, int level)
}
/* TODO:
- * SP_EL1
* SPSR[]
* FP state
* system registers
@@ -180,7 +195,14 @@ int kvm_arch_get_registers(CPUState *cs)
}
reg.id = AARCH64_CORE_REG(regs.sp);
- reg.addr = (uintptr_t) &env->xregs[31];
+ reg.addr = (uintptr_t) &env->sp_el[0];
+ ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &reg);
+ if (ret) {
+ return ret;
+ }
+
+ reg.id = AARCH64_CORE_REG(sp_el1);
+ reg.addr = (uintptr_t) &env->sp_el[1];
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &reg);
if (ret) {
return ret;
@@ -194,6 +216,15 @@ int kvm_arch_get_registers(CPUState *cs)
}
pstate_write(env, val);
+ /* KVM puts SP_EL0 in regs.sp and SP_EL1 in regs.sp_el1. On the
+ * QEMU side we keep the current SP in xregs[31] as well.
+ */
+ if (env->pstate & PSTATE_SP) {
+ env->xregs[31] = env->sp_el[1];
+ } else {
+ env->xregs[31] = env->sp_el[0];
+ }
+
reg.id = AARCH64_CORE_REG(regs.pc);
reg.addr = (uintptr_t) &env->pc;
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &reg);
diff --git a/target-arm/machine.c b/target-arm/machine.c
index 42b1c90ac3..c2c0780d4f 100644
--- a/target-arm/machine.c
+++ b/target-arm/machine.c
@@ -222,9 +222,9 @@ static int cpu_post_load(void *opaque, int version_id)
const VMStateDescription vmstate_arm_cpu = {
.name = "cpu",
- .version_id = 15,
- .minimum_version_id = 15,
- .minimum_version_id_old = 15,
+ .version_id = 16,
+ .minimum_version_id = 16,
+ .minimum_version_id_old = 16,
.pre_save = cpu_pre_save,
.post_load = cpu_post_load,
.fields = (VMStateField[]) {
@@ -244,6 +244,7 @@ const VMStateDescription vmstate_arm_cpu = {
VMSTATE_UINT32_ARRAY(env.usr_regs, ARMCPU, 5),
VMSTATE_UINT32_ARRAY(env.fiq_regs, ARMCPU, 5),
VMSTATE_UINT64(env.elr_el1, ARMCPU),
+ VMSTATE_UINT64_ARRAY(env.sp_el, ARMCPU, 2),
/* The length-check must come before the arrays to avoid
* incoming data possibly overflowing the array.
*/
diff --git a/target-arm/op_helper.c b/target-arm/op_helper.c
index 51edd9076c..64a33dd449 100644
--- a/target-arm/op_helper.c
+++ b/target-arm/op_helper.c
@@ -371,7 +371,7 @@ void HELPER(msr_i_pstate)(CPUARMState *env, uint32_t op, uint32_t imm)
switch (op) {
case 0x05: /* SPSel */
- env->pstate = deposit32(env->pstate, 0, 1, imm);
+ update_spsel(env, imm);
break;
case 0x1e: /* DAIFSet */
env->daif |= (imm << 6) & PSTATE_DAIF;