diff options
| author | Marc Zyngier <marc.zyngier@arm.com> | 2013-01-23 13:21:58 -0500 | 
|---|---|---|
| committer | Marc Zyngier <marc.zyngier@arm.com> | 2013-02-11 19:05:11 +0000 | 
| commit | 53e724067a4ee9373972079e225d0d5f683b9c5a (patch) | |
| tree | de8cd94b10d2cd768e990a38a95d8a40ab3dac0a | |
| parent | 9ae9e2535d7dd1c21d6a7db1a7f2fc507a5e4080 (diff) | |
| download | olio-linux-3.10-53e724067a4ee9373972079e225d0d5f683b9c5a.tar.xz olio-linux-3.10-53e724067a4ee9373972079e225d0d5f683b9c5a.zip  | |
ARM: KVM: arch_timers: Add guest timer core support
Add some the architected timer related infrastructure, and support timer
interrupt injection, which can happen as a resultof three possible
events:
- The virtual timer interrupt has fired while we were still
  executing the guest
- The timer interrupt hasn't fired, but it expired while we
  were doing the world switch
- A hrtimer we programmed earlier has fired
Reviewed-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Christoffer Dall <c.dall@virtualopensystems.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
| -rw-r--r-- | arch/arm/include/asm/kvm_arch_timer.h | 85 | ||||
| -rw-r--r-- | arch/arm/include/asm/kvm_host.h | 5 | ||||
| -rw-r--r-- | arch/arm/kvm/arch_timer.c | 271 | ||||
| -rw-r--r-- | arch/arm/kvm/interrupts.S | 2 | ||||
| -rw-r--r-- | arch/arm/kvm/interrupts_head.S | 31 | 
5 files changed, 394 insertions, 0 deletions
diff --git a/arch/arm/include/asm/kvm_arch_timer.h b/arch/arm/include/asm/kvm_arch_timer.h new file mode 100644 index 00000000000..68cb9e1dfb8 --- /dev/null +++ b/arch/arm/include/asm/kvm_arch_timer.h @@ -0,0 +1,85 @@ +/* + * 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 +#define __ASM_ARM_KVM_ARCH_TIMER_H + +#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; + +	/* Virtual offset */ +	cycle_t			cntvoff; +#endif +}; + +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 */ + +	/* +	 * Anything that is not used directly from assembly code goes +	 * here. +	 */ + +	/* Background timer used when the guest is not running */ +	struct hrtimer			timer; + +	/* Work queued with the above timer expires */ +	struct work_struct		expired; + +	/* Background timer active */ +	bool				armed; + +	/* Timer IRQ */ +	const struct kvm_irq_level	*irq; +#endif +}; + +#ifdef CONFIG_KVM_ARM_TIMER +int kvm_timer_hyp_init(void); +int kvm_timer_init(struct kvm *kvm); +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_vcpu_terminate(struct kvm_vcpu *vcpu); +#else +static inline int kvm_timer_hyp_init(void) +{ +	return 0; +}; + +static inline int kvm_timer_init(struct kvm *kvm) +{ +	return 0; +} + +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 + +#endif diff --git a/arch/arm/include/asm/kvm_host.h b/arch/arm/include/asm/kvm_host.h index 6791c888b9f..dfe98866a99 100644 --- a/arch/arm/include/asm/kvm_host.h +++ b/arch/arm/include/asm/kvm_host.h @@ -23,6 +23,7 @@  #include <asm/kvm_asm.h>  #include <asm/kvm_mmio.h>  #include <asm/fpstate.h> +#include <asm/kvm_arch_timer.h>  #define KVM_MAX_VCPUS CONFIG_KVM_ARM_MAX_VCPUS  #define KVM_MEMORY_SLOTS 32 @@ -49,6 +50,9 @@ struct kvm_arch {  	/* VTTBR value associated with below pgd and vmid */  	u64    vttbr; +	/* Timer */ +	struct arch_timer_kvm	timer; +  	/*  	 * Anything that is not used directly from assembly code goes  	 * here. @@ -99,6 +103,7 @@ struct kvm_vcpu_arch {  	/* VGIC state */  	struct vgic_cpu vgic_cpu; +	struct arch_timer_cpu timer_cpu;  	/*  	 * Anything that is not used directly from assembly code goes diff --git a/arch/arm/kvm/arch_timer.c b/arch/arm/kvm/arch_timer.c new file mode 100644 index 00000000000..6ac938d4629 --- /dev/null +++ b/arch/arm/kvm/arch_timer.c @@ -0,0 +1,271 @@ +/* + * 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 + */ + +#include <linux/cpu.h> +#include <linux/of_irq.h> +#include <linux/kvm.h> +#include <linux/kvm_host.h> +#include <linux/interrupt.h> + +#include <asm/arch_timer.h> + +#include <asm/kvm_vgic.h> +#include <asm/kvm_arch_timer.h> + +static struct timecounter *timecounter; +static struct workqueue_struct *wqueue; +static struct kvm_irq_level timer_irq = { +	.level	= 1, +}; + +static cycle_t kvm_phys_timer_read(void) +{ +	return timecounter->cc->read(timecounter->cc); +} + +static bool timer_is_armed(struct arch_timer_cpu *timer) +{ +	return timer->armed; +} + +/* timer_arm: as in "arm the timer", not as in ARM the company */ +static void timer_arm(struct arch_timer_cpu *timer, u64 ns) +{ +	timer->armed = true; +	hrtimer_start(&timer->timer, ktime_add_ns(ktime_get(), ns), +		      HRTIMER_MODE_ABS); +} + +static void timer_disarm(struct arch_timer_cpu *timer) +{ +	if (timer_is_armed(timer)) { +		hrtimer_cancel(&timer->timer); +		cancel_work_sync(&timer->expired); +		timer->armed = false; +	} +} + +static void kvm_timer_inject_irq(struct kvm_vcpu *vcpu) +{ +	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu; + +	timer->cntv_ctl |= 1 << 1; /* Mask the interrupt in the guest */ +	kvm_vgic_inject_irq(vcpu->kvm, vcpu->vcpu_id, +			    vcpu->arch.timer_cpu.irq->irq, +			    vcpu->arch.timer_cpu.irq->level); +} + +static irqreturn_t kvm_arch_timer_handler(int irq, void *dev_id) +{ +	struct kvm_vcpu *vcpu = *(struct kvm_vcpu **)dev_id; + +	/* +	 * We disable the timer in the world switch and let it be +	 * handled by kvm_timer_sync_hwstate(). Getting a timer +	 * interrupt at this point is a sure sign of some major +	 * breakage. +	 */ +	pr_warn("Unexpected interrupt %d on vcpu %p\n", irq, vcpu); +	return IRQ_HANDLED; +} + +static void kvm_timer_inject_irq_work(struct work_struct *work) +{ +	struct kvm_vcpu *vcpu; + +	vcpu = container_of(work, struct kvm_vcpu, arch.timer_cpu.expired); +	vcpu->arch.timer_cpu.armed = false; +	kvm_timer_inject_irq(vcpu); +} + +static enum hrtimer_restart kvm_timer_expire(struct hrtimer *hrt) +{ +	struct arch_timer_cpu *timer; +	timer = container_of(hrt, struct arch_timer_cpu, timer); +	queue_work(wqueue, &timer->expired); +	return HRTIMER_NORESTART; +} + +/** + * kvm_timer_flush_hwstate - prepare to move the virt timer to the cpu + * @vcpu: The vcpu pointer + * + * Disarm any pending soft timers, since the world-switch code will write the + * virtual timer state back to the physical CPU. + */ +void kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu) +{ +	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu; + +	/* +	 * We're about to run this vcpu again, so there is no need to +	 * keep the background timer running, as we're about to +	 * populate the CPU timer again. +	 */ +	timer_disarm(timer); +} + +/** + * kvm_timer_sync_hwstate - sync timer state from cpu + * @vcpu: The vcpu pointer + * + * Check if the virtual timer was armed and either schedule a corresponding + * soft timer or inject directly if already expired. + */ +void kvm_timer_sync_hwstate(struct kvm_vcpu *vcpu) +{ +	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu; +	cycle_t cval, now; +	u64 ns; + +	/* Check if the timer is enabled and unmasked first */ +	if ((timer->cntv_ctl & 3) != 1) +		return; + +	cval = timer->cntv_cval; +	now = kvm_phys_timer_read() - vcpu->kvm->arch.timer.cntvoff; + +	BUG_ON(timer_is_armed(timer)); + +	if (cval <= now) { +		/* +		 * Timer has already expired while we were not +		 * looking. Inject the interrupt and carry on. +		 */ +		kvm_timer_inject_irq(vcpu); +		return; +	} + +	ns = cyclecounter_cyc2ns(timecounter->cc, cval - now); +	timer_arm(timer, ns); +} + +void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu) +{ +	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu; + +	INIT_WORK(&timer->expired, kvm_timer_inject_irq_work); +	hrtimer_init(&timer->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); +	timer->timer.function = kvm_timer_expire; +	timer->irq = &timer_irq; +} + +static void kvm_timer_init_interrupt(void *info) +{ +	enable_percpu_irq(timer_irq.irq, 0); +} + + +static int kvm_timer_cpu_notify(struct notifier_block *self, +				unsigned long action, void *cpu) +{ +	switch (action) { +	case CPU_STARTING: +	case CPU_STARTING_FROZEN: +		kvm_timer_init_interrupt(NULL); +		break; +	case CPU_DYING: +	case CPU_DYING_FROZEN: +		disable_percpu_irq(timer_irq.irq); +		break; +	} + +	return NOTIFY_OK; +} + +static struct notifier_block kvm_timer_cpu_nb = { +	.notifier_call = kvm_timer_cpu_notify, +}; + +static const struct of_device_id arch_timer_of_match[] = { +	{ .compatible	= "arm,armv7-timer",	}, +	{}, +}; + +int kvm_timer_hyp_init(void) +{ +	struct device_node *np; +	unsigned int ppi; +	int err; + +	timecounter = arch_timer_get_timecounter(); +	if (!timecounter) +		return -ENODEV; + +	np = of_find_matching_node(NULL, arch_timer_of_match); +	if (!np) { +		kvm_err("kvm_arch_timer: can't find DT node\n"); +		return -ENODEV; +	} + +	ppi = irq_of_parse_and_map(np, 2); +	if (!ppi) { +		kvm_err("kvm_arch_timer: no virtual timer interrupt\n"); +		err = -EINVAL; +		goto out; +	} + +	err = request_percpu_irq(ppi, kvm_arch_timer_handler, +				 "kvm guest timer", kvm_get_running_vcpus()); +	if (err) { +		kvm_err("kvm_arch_timer: can't request interrupt %d (%d)\n", +			ppi, err); +		goto out; +	} + +	timer_irq.irq = ppi; + +	err = register_cpu_notifier(&kvm_timer_cpu_nb); +	if (err) { +		kvm_err("Cannot register timer CPU notifier\n"); +		goto out_free; +	} + +	wqueue = create_singlethread_workqueue("kvm_arch_timer"); +	if (!wqueue) { +		err = -ENOMEM; +		goto out_free; +	} + +	kvm_info("%s IRQ%d\n", np->name, ppi); +	on_each_cpu(kvm_timer_init_interrupt, NULL, 1); + +	goto out; +out_free: +	free_percpu_irq(ppi, kvm_get_running_vcpus()); +out: +	of_node_put(np); +	return err; +} + +void kvm_timer_vcpu_terminate(struct kvm_vcpu *vcpu) +{ +	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu; + +	timer_disarm(timer); +} + +int kvm_timer_init(struct kvm *kvm) +{ +	if (timecounter && wqueue) { +		kvm->arch.timer.cntvoff = kvm_phys_timer_read(); +		kvm->arch.timer.enabled = 1; +	} + +	return 0; +} diff --git a/arch/arm/kvm/interrupts.S b/arch/arm/kvm/interrupts.S index 5f113bedfae..8ca87ab0919 100644 --- a/arch/arm/kvm/interrupts.S +++ b/arch/arm/kvm/interrupts.S @@ -95,6 +95,7 @@ ENTRY(__kvm_vcpu_run)  	save_host_regs  	restore_vgic_state +	restore_timer_state  	@ Store hardware CP15 state and load guest state  	read_cp15_state store_to_vcpu = 0 @@ -189,6 +190,7 @@ after_vfp_restore:  	read_cp15_state store_to_vcpu = 1  	write_cp15_state read_from_vcpu = 0 +	save_timer_state  	save_vgic_state  	restore_host_regs diff --git a/arch/arm/kvm/interrupts_head.S b/arch/arm/kvm/interrupts_head.S index 8c875d54a08..06f251395be 100644 --- a/arch/arm/kvm/interrupts_head.S +++ b/arch/arm/kvm/interrupts_head.S @@ -453,6 +453,37 @@ vcpu	.req	r0		@ vcpu pointer always in r0  #endif  .endm +#define CNTHCTL_PL1PCTEN	(1 << 0) +#define CNTHCTL_PL1PCEN		(1 << 1) + +/* + * Save the timer state onto the VCPU and allow physical timer/counter access + * for the host. + * + * Assumes vcpu pointer in vcpu reg + */ +.macro save_timer_state +	@ Allow physical timer/counter access for the host +	mrc	p15, 4, r2, c14, c1, 0	@ CNTHCTL +	orr	r2, r2, #(CNTHCTL_PL1PCEN | CNTHCTL_PL1PCTEN) +	mcr	p15, 4, r2, c14, c1, 0	@ CNTHCTL +.endm + +/* + * Load the timer state from the VCPU and deny physical timer/counter access + * for the host. + * + * Assumes vcpu pointer in vcpu reg + */ +.macro restore_timer_state +	@ Disallow physical timer access for the guest +	@ Physical counter access is allowed +	mrc	p15, 4, r2, c14, c1, 0	@ CNTHCTL +	orr	r2, r2, #CNTHCTL_PL1PCTEN +	bic	r2, r2, #CNTHCTL_PL1PCEN +	mcr	p15, 4, r2, c14, c1, 0	@ CNTHCTL +.endm +  .equ vmentry,	0  .equ vmexit,	1  |