summaryrefslogtreecommitdiff
path: root/include/kvm/arm_arch_timer.h
diff options
context:
space:
mode:
Diffstat (limited to 'include/kvm/arm_arch_timer.h')
-rw-r--r--include/kvm/arm_arch_timer.h214
1 files changed, 157 insertions, 57 deletions
diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
index 6d9aeddc09bf..7310841f4512 100644
--- a/include/kvm/arm_arch_timer.h
+++ b/include/kvm/arm_arch_timer.h
@@ -1,19 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2012 ARM Ltd.
* Author: Marc Zyngier <marc.zyngier@arm.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __ASM_ARM_KVM_ARCH_TIMER_H
@@ -21,69 +9,181 @@
#include <linux/clocksource.h>
#include <linux/hrtimer.h>
-#include <linux/workqueue.h>
-struct arch_timer_kvm {
-#ifdef CONFIG_KVM_ARM_TIMER
- /* Is the timer enabled */
- bool enabled;
+enum kvm_arch_timers {
+ TIMER_PTIMER,
+ TIMER_VTIMER,
+ NR_KVM_EL0_TIMERS,
+ TIMER_HVTIMER = NR_KVM_EL0_TIMERS,
+ TIMER_HPTIMER,
+ NR_KVM_TIMERS
+};
- /* Virtual offset */
- cycle_t cntvoff;
-#endif
+enum kvm_arch_timer_regs {
+ TIMER_REG_CNT,
+ TIMER_REG_CVAL,
+ TIMER_REG_TVAL,
+ TIMER_REG_CTL,
+ TIMER_REG_VOFF,
};
-struct arch_timer_cpu {
-#ifdef CONFIG_KVM_ARM_TIMER
- /* Registers: control register, timer value */
- u32 cntv_ctl; /* Saved/restored */
- cycle_t cntv_cval; /* Saved/restored */
+struct arch_timer_offset {
+ /*
+ * If set, pointer to one of the offsets in the kvm's offset
+ * structure. If NULL, assume a zero offset.
+ */
+ u64 *vm_offset;
+ /*
+ * If set, pointer to one of the offsets in the vcpu's sysreg
+ * array. If NULL, assume a zero offset.
+ */
+ u64 *vcpu_offset;
+};
+
+struct arch_timer_vm_data {
+ /* Offset applied to the virtual timer/counter */
+ u64 voffset;
+ /* Offset applied to the physical timer/counter */
+ u64 poffset;
+
+ /* The PPI for each timer, global to the VM */
+ u8 ppi[NR_KVM_TIMERS];
+};
+struct arch_timer_context {
+ /* Emulated Timer (may be unused) */
+ struct hrtimer hrtimer;
+ u64 ns_frac;
+
+ /* Offset for this counter/timer */
+ struct arch_timer_offset offset;
/*
- * Anything that is not used directly from assembly code goes
- * here.
+ * We have multiple paths which can save/restore the timer state onto
+ * the hardware, so we need some way of keeping track of where the
+ * latest state is.
*/
+ bool loaded;
- /* Background timer used when the guest is not running */
- struct hrtimer timer;
+ /* Output level of the timer IRQ */
+ struct {
+ bool level;
+ } irq;
- /* Work queued with the above timer expires */
- struct work_struct expired;
+ /* Who am I? */
+ enum kvm_arch_timers timer_id;
- /* Background timer active */
- bool armed;
+ /* Duplicated state from arch_timer.c for convenience */
+ u32 host_timer_irq;
+};
- /* Timer IRQ */
- const struct kvm_irq_level *irq;
-#endif
+struct timer_map {
+ struct arch_timer_context *direct_vtimer;
+ struct arch_timer_context *direct_ptimer;
+ struct arch_timer_context *emul_vtimer;
+ struct arch_timer_context *emul_ptimer;
+};
+
+void get_timer_map(struct kvm_vcpu *vcpu, struct timer_map *map);
+
+struct arch_timer_cpu {
+ struct arch_timer_context timers[NR_KVM_TIMERS];
+
+ /* Background timer used when the guest is not running */
+ struct hrtimer bg_timer;
+
+ /* Is the timer enabled */
+ bool enabled;
};
-#ifdef CONFIG_KVM_ARM_TIMER
-int kvm_timer_hyp_init(void);
-int kvm_timer_init(struct kvm *kvm);
-void kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu,
- const struct kvm_irq_level *irq);
+int __init kvm_timer_hyp_init(bool has_gic);
+int kvm_timer_enable(struct kvm_vcpu *vcpu);
+void kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu);
void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu);
-void kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu);
-void kvm_timer_sync_hwstate(struct kvm_vcpu *vcpu);
+void kvm_timer_sync_nested(struct kvm_vcpu *vcpu);
+void kvm_timer_sync_user(struct kvm_vcpu *vcpu);
+bool kvm_timer_should_notify_user(struct kvm_vcpu *vcpu);
+void kvm_timer_update_run(struct kvm_vcpu *vcpu);
void kvm_timer_vcpu_terminate(struct kvm_vcpu *vcpu);
-#else
-static inline int kvm_timer_hyp_init(void)
+
+void kvm_timer_init_vm(struct kvm *kvm);
+
+int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+int kvm_arm_timer_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+int kvm_arm_timer_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+
+u64 kvm_phys_timer_read(void);
+
+void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu);
+void kvm_timer_vcpu_put(struct kvm_vcpu *vcpu);
+
+void kvm_timer_init_vhe(void);
+
+#define vcpu_timer(v) (&(v)->arch.timer_cpu)
+#define vcpu_get_timer(v,t) (&vcpu_timer(v)->timers[(t)])
+#define vcpu_vtimer(v) (&(v)->arch.timer_cpu.timers[TIMER_VTIMER])
+#define vcpu_ptimer(v) (&(v)->arch.timer_cpu.timers[TIMER_PTIMER])
+#define vcpu_hvtimer(v) (&(v)->arch.timer_cpu.timers[TIMER_HVTIMER])
+#define vcpu_hptimer(v) (&(v)->arch.timer_cpu.timers[TIMER_HPTIMER])
+
+#define arch_timer_ctx_index(ctx) ((ctx)->timer_id)
+#define timer_context_to_vcpu(ctx) container_of((ctx), struct kvm_vcpu, arch.timer_cpu.timers[(ctx)->timer_id])
+#define timer_vm_data(ctx) (&(timer_context_to_vcpu(ctx)->kvm->arch.timer_data))
+#define timer_irq(ctx) (timer_vm_data(ctx)->ppi[arch_timer_ctx_index(ctx)])
+
+u64 kvm_arm_timer_read_sysreg(struct kvm_vcpu *vcpu,
+ enum kvm_arch_timers tmr,
+ enum kvm_arch_timer_regs treg);
+void kvm_arm_timer_write_sysreg(struct kvm_vcpu *vcpu,
+ enum kvm_arch_timers tmr,
+ enum kvm_arch_timer_regs treg,
+ u64 val);
+
+/* Needed for tracing */
+u32 timer_get_ctl(struct arch_timer_context *ctxt);
+u64 timer_get_cval(struct arch_timer_context *ctxt);
+
+/* CPU HP callbacks */
+void kvm_timer_cpu_up(void);
+void kvm_timer_cpu_down(void);
+
+/* CNTKCTL_EL1 valid bits as of DDI0487J.a */
+#define CNTKCTL_VALID_BITS (BIT(17) | GENMASK_ULL(9, 0))
+
+DECLARE_STATIC_KEY_FALSE(broken_cntvoff_key);
+
+static inline bool has_broken_cntvoff(void)
{
- return 0;
-};
+ return static_branch_unlikely(&broken_cntvoff_key);
+}
-static inline int kvm_timer_init(struct kvm *kvm)
+static inline bool has_cntpoff(void)
{
- return 0;
+ return (has_vhe() && cpus_have_final_cap(ARM64_HAS_ECV_CNTPOFF));
}
-static inline void kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu,
- const struct kvm_irq_level *irq) {}
-static inline void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu) {}
-static inline void kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu) {}
-static inline void kvm_timer_sync_hwstate(struct kvm_vcpu *vcpu) {}
-static inline void kvm_timer_vcpu_terminate(struct kvm_vcpu *vcpu) {}
-#endif
+static inline u64 timer_get_offset(struct arch_timer_context *ctxt)
+{
+ u64 offset = 0;
+
+ if (!ctxt)
+ return 0;
+
+ if (ctxt->offset.vm_offset)
+ offset += *ctxt->offset.vm_offset;
+ if (ctxt->offset.vcpu_offset)
+ offset += *ctxt->offset.vcpu_offset;
+
+ return offset;
+}
+
+static inline void timer_set_offset(struct arch_timer_context *ctxt, u64 offset)
+{
+ if (!ctxt->offset.vm_offset) {
+ WARN(offset, "timer %d\n", arch_timer_ctx_index(ctxt));
+ return;
+ }
+
+ WRITE_ONCE(*ctxt->offset.vm_offset, offset);
+}
#endif