summaryrefslogtreecommitdiffstats
path: root/target
diff options
context:
space:
mode:
Diffstat (limited to 'target')
-rw-r--r--target/arm/cpu.h3
-rw-r--r--target/arm/helper.c13
-rw-r--r--target/arm/kvm.c51
3 files changed, 64 insertions, 3 deletions
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 16a1e59615..102c58afac 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -706,6 +706,9 @@ struct ARMCPU {
void *el_change_hook_opaque;
int32_t node_id; /* NUMA node this CPU belongs to */
+
+ /* Used to synchronize KVM and QEMU in-kernel device levels */
+ uint8_t device_irq_level;
};
static inline ARMCPU *arm_env_get_cpu(CPUARMState *env)
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 2594faa9b8..4ed32c56b8 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -8768,9 +8768,16 @@ void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
}
break;
case 20: /* CONTROL */
- 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);
+ /* Writing to the SPSEL bit only has an effect if we are in
+ * thread mode; other bits can be updated by any privileged code.
+ * switch_v7m_sp() deals with updating the SPSEL bit in
+ * env->v7m.control, so we only need update the others.
+ */
+ if (env->v7m.exception == 0) {
+ switch_v7m_sp(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
+ }
+ env->v7m.control &= ~R_V7M_CONTROL_NPRIV_MASK;
+ env->v7m.control |= val & R_V7M_CONTROL_NPRIV_MASK;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "Attempt to write unknown special"
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index 45554682f2..7c17f0d629 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -174,6 +174,12 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
*/
kvm_async_interrupts_allowed = true;
+ /*
+ * PSCI wakes up secondary cores, so we always need to
+ * have vCPUs waiting in kernel space
+ */
+ kvm_halt_in_kernel_allowed = true;
+
cap_has_mp_state = kvm_check_extension(s, KVM_CAP_MP_STATE);
type_register_static(&host_arm_cpu_type_info);
@@ -528,6 +534,51 @@ void kvm_arch_pre_run(CPUState *cs, struct kvm_run *run)
MemTxAttrs kvm_arch_post_run(CPUState *cs, struct kvm_run *run)
{
+ ARMCPU *cpu;
+ uint32_t switched_level;
+
+ if (kvm_irqchip_in_kernel()) {
+ /*
+ * We only need to sync timer states with user-space interrupt
+ * controllers, so return early and save cycles if we don't.
+ */
+ return MEMTXATTRS_UNSPECIFIED;
+ }
+
+ cpu = ARM_CPU(cs);
+
+ /* Synchronize our shadowed in-kernel device irq lines with the kvm ones */
+ if (run->s.regs.device_irq_level != cpu->device_irq_level) {
+ switched_level = cpu->device_irq_level ^ run->s.regs.device_irq_level;
+
+ qemu_mutex_lock_iothread();
+
+ if (switched_level & KVM_ARM_DEV_EL1_VTIMER) {
+ qemu_set_irq(cpu->gt_timer_outputs[GTIMER_VIRT],
+ !!(run->s.regs.device_irq_level &
+ KVM_ARM_DEV_EL1_VTIMER));
+ switched_level &= ~KVM_ARM_DEV_EL1_VTIMER;
+ }
+
+ if (switched_level & KVM_ARM_DEV_EL1_PTIMER) {
+ qemu_set_irq(cpu->gt_timer_outputs[GTIMER_PHYS],
+ !!(run->s.regs.device_irq_level &
+ KVM_ARM_DEV_EL1_PTIMER));
+ switched_level &= ~KVM_ARM_DEV_EL1_PTIMER;
+ }
+
+ /* XXX PMU IRQ is missing */
+
+ if (switched_level) {
+ qemu_log_mask(LOG_UNIMP, "%s: unhandled in-kernel device IRQ %x\n",
+ __func__, switched_level);
+ }
+
+ /* We also mark unknown levels as processed to not waste cycles */
+ cpu->device_irq_level = run->s.regs.device_irq_level;
+ qemu_mutex_unlock_iothread();
+ }
+
return MEMTXATTRS_UNSPECIFIED;
}