mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 01:34:14 +08:00
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>
This commit is contained in:
parent
9ae9e2535d
commit
53e724067a
85
arch/arm/include/asm/kvm_arch_timer.h
Normal file
85
arch/arm/include/asm/kvm_arch_timer.h
Normal file
@ -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
|
@ -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
|
||||
|
271
arch/arm/kvm/arch_timer.c
Normal file
271
arch/arm/kvm/arch_timer.c
Normal file
@ -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;
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user