mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
KVM: arm64: timer: Add support for SW-based deactivation
In order to deal with the lack of active state, we need to use the mask/unmask primitives (after all, the active state is just an additional mask on top of the normal one). To avoid adding a bunch of ugly conditionals in the timer and vgic code, let's use a timer-specific irqdomain to deal with the state conversion. Yes, this is an unexpected use of irqdomains, but there is no reason not to be just as creative as the designers of the HW... This involves overloading the vcpu_affinity, set_irqchip_state and eoi callbacks so that the rest of the KVM code can continue ignoring the oddities of the underlying platform. Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
parent
2f2f7e39db
commit
5f59229680
@ -9,6 +9,7 @@
|
||||
#include <linux/kvm_host.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <clocksource/arm_arch_timer.h>
|
||||
@ -973,6 +974,77 @@ static int kvm_timer_dying_cpu(unsigned int cpu)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int timer_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu)
|
||||
{
|
||||
if (vcpu)
|
||||
irqd_set_forwarded_to_vcpu(d);
|
||||
else
|
||||
irqd_clr_forwarded_to_vcpu(d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int timer_irq_set_irqchip_state(struct irq_data *d,
|
||||
enum irqchip_irq_state which, bool val)
|
||||
{
|
||||
if (which != IRQCHIP_STATE_ACTIVE || !irqd_is_forwarded_to_vcpu(d))
|
||||
return irq_chip_set_parent_state(d, which, val);
|
||||
|
||||
if (val)
|
||||
irq_chip_mask_parent(d);
|
||||
else
|
||||
irq_chip_unmask_parent(d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void timer_irq_eoi(struct irq_data *d)
|
||||
{
|
||||
if (!irqd_is_forwarded_to_vcpu(d))
|
||||
irq_chip_eoi_parent(d);
|
||||
}
|
||||
|
||||
static void timer_irq_ack(struct irq_data *d)
|
||||
{
|
||||
d = d->parent_data;
|
||||
if (d->chip->irq_ack)
|
||||
d->chip->irq_ack(d);
|
||||
}
|
||||
|
||||
static struct irq_chip timer_chip = {
|
||||
.name = "KVM",
|
||||
.irq_ack = timer_irq_ack,
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_eoi = timer_irq_eoi,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_set_vcpu_affinity = timer_irq_set_vcpu_affinity,
|
||||
.irq_set_irqchip_state = timer_irq_set_irqchip_state,
|
||||
};
|
||||
|
||||
static int timer_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
irq_hw_number_t hwirq = (uintptr_t)arg;
|
||||
|
||||
return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
|
||||
&timer_chip, NULL);
|
||||
}
|
||||
|
||||
static void timer_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops timer_domain_ops = {
|
||||
.alloc = timer_irq_domain_alloc,
|
||||
.free = timer_irq_domain_free,
|
||||
};
|
||||
|
||||
static struct irq_ops arch_timer_irq_ops = {
|
||||
.get_input_level = kvm_arch_timer_get_input_level,
|
||||
};
|
||||
|
||||
static void kvm_irq_fixup_flags(unsigned int virq, u32 *flags)
|
||||
{
|
||||
*flags = irq_get_trigger_type(virq);
|
||||
@ -985,6 +1057,8 @@ static void kvm_irq_fixup_flags(unsigned int virq, u32 *flags)
|
||||
|
||||
static int kvm_irq_init(struct arch_timer_kvm_info *info)
|
||||
{
|
||||
struct irq_domain *domain = NULL;
|
||||
|
||||
if (info->virtual_irq <= 0) {
|
||||
kvm_err("kvm_arch_timer: invalid virtual timer IRQ: %d\n",
|
||||
info->virtual_irq);
|
||||
@ -994,9 +1068,36 @@ static int kvm_irq_init(struct arch_timer_kvm_info *info)
|
||||
host_vtimer_irq = info->virtual_irq;
|
||||
kvm_irq_fixup_flags(host_vtimer_irq, &host_vtimer_irq_flags);
|
||||
|
||||
if (kvm_vgic_global_state.no_hw_deactivation) {
|
||||
struct fwnode_handle *fwnode;
|
||||
struct irq_data *data;
|
||||
|
||||
fwnode = irq_domain_alloc_named_fwnode("kvm-timer");
|
||||
if (!fwnode)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Assume both vtimer and ptimer in the same parent */
|
||||
data = irq_get_irq_data(host_vtimer_irq);
|
||||
domain = irq_domain_create_hierarchy(data->domain, 0,
|
||||
NR_KVM_TIMERS, fwnode,
|
||||
&timer_domain_ops, NULL);
|
||||
if (!domain) {
|
||||
irq_domain_free_fwnode(fwnode);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
arch_timer_irq_ops.flags |= VGIC_IRQ_SW_RESAMPLE;
|
||||
WARN_ON(irq_domain_push_irq(domain, host_vtimer_irq,
|
||||
(void *)TIMER_VTIMER));
|
||||
}
|
||||
|
||||
if (info->physical_irq > 0) {
|
||||
host_ptimer_irq = info->physical_irq;
|
||||
kvm_irq_fixup_flags(host_ptimer_irq, &host_ptimer_irq_flags);
|
||||
|
||||
if (domain)
|
||||
WARN_ON(irq_domain_push_irq(domain, host_ptimer_irq,
|
||||
(void *)TIMER_PTIMER));
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1125,10 +1226,6 @@ bool kvm_arch_timer_get_input_level(int vintid)
|
||||
return kvm_timer_should_fire(timer);
|
||||
}
|
||||
|
||||
static struct irq_ops arch_timer_irq_ops = {
|
||||
.get_input_level = kvm_arch_timer_get_input_level,
|
||||
};
|
||||
|
||||
int kvm_timer_enable(struct kvm_vcpu *vcpu)
|
||||
{
|
||||
struct arch_timer_cpu *timer = vcpu_timer(vcpu);
|
||||
|
Loading…
Reference in New Issue
Block a user