Skip to content

Commit 5f59229

Browse files
author
Marc Zyngier
committed
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>
1 parent 2f2f7e3 commit 5f59229

1 file changed

Lines changed: 101 additions & 4 deletions

File tree

arch/arm64/kvm/arch_timer.c

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <linux/kvm_host.h>
1010
#include <linux/interrupt.h>
1111
#include <linux/irq.h>
12+
#include <linux/irqdomain.h>
1213
#include <linux/uaccess.h>
1314

1415
#include <clocksource/arm_arch_timer.h>
@@ -973,6 +974,77 @@ static int kvm_timer_dying_cpu(unsigned int cpu)
973974
return 0;
974975
}
975976

977+
static int timer_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu)
978+
{
979+
if (vcpu)
980+
irqd_set_forwarded_to_vcpu(d);
981+
else
982+
irqd_clr_forwarded_to_vcpu(d);
983+
984+
return 0;
985+
}
986+
987+
static int timer_irq_set_irqchip_state(struct irq_data *d,
988+
enum irqchip_irq_state which, bool val)
989+
{
990+
if (which != IRQCHIP_STATE_ACTIVE || !irqd_is_forwarded_to_vcpu(d))
991+
return irq_chip_set_parent_state(d, which, val);
992+
993+
if (val)
994+
irq_chip_mask_parent(d);
995+
else
996+
irq_chip_unmask_parent(d);
997+
998+
return 0;
999+
}
1000+
1001+
static void timer_irq_eoi(struct irq_data *d)
1002+
{
1003+
if (!irqd_is_forwarded_to_vcpu(d))
1004+
irq_chip_eoi_parent(d);
1005+
}
1006+
1007+
static void timer_irq_ack(struct irq_data *d)
1008+
{
1009+
d = d->parent_data;
1010+
if (d->chip->irq_ack)
1011+
d->chip->irq_ack(d);
1012+
}
1013+
1014+
static struct irq_chip timer_chip = {
1015+
.name = "KVM",
1016+
.irq_ack = timer_irq_ack,
1017+
.irq_mask = irq_chip_mask_parent,
1018+
.irq_unmask = irq_chip_unmask_parent,
1019+
.irq_eoi = timer_irq_eoi,
1020+
.irq_set_type = irq_chip_set_type_parent,
1021+
.irq_set_vcpu_affinity = timer_irq_set_vcpu_affinity,
1022+
.irq_set_irqchip_state = timer_irq_set_irqchip_state,
1023+
};
1024+
1025+
static int timer_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
1026+
unsigned int nr_irqs, void *arg)
1027+
{
1028+
irq_hw_number_t hwirq = (uintptr_t)arg;
1029+
1030+
return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
1031+
&timer_chip, NULL);
1032+
}
1033+
1034+
static void timer_irq_domain_free(struct irq_domain *domain, unsigned int virq,
1035+
unsigned int nr_irqs)
1036+
{
1037+
}
1038+
1039+
static const struct irq_domain_ops timer_domain_ops = {
1040+
.alloc = timer_irq_domain_alloc,
1041+
.free = timer_irq_domain_free,
1042+
};
1043+
1044+
static struct irq_ops arch_timer_irq_ops = {
1045+
.get_input_level = kvm_arch_timer_get_input_level,
1046+
};
1047+
9761048
static void kvm_irq_fixup_flags(unsigned int virq, u32 *flags)
9771049
{
9781050
*flags = irq_get_trigger_type(virq);
@@ -985,6 +1057,8 @@ static void kvm_irq_fixup_flags(unsigned int virq, u32 *flags)
9851057

9861058
static int kvm_irq_init(struct arch_timer_kvm_info *info)
9871059
{
1060+
struct irq_domain *domain = NULL;
1061+
9881062
if (info->virtual_irq <= 0) {
9891063
kvm_err("kvm_arch_timer: invalid virtual timer IRQ: %d\n",
9901064
info->virtual_irq);
@@ -994,9 +1068,36 @@ static int kvm_irq_init(struct arch_timer_kvm_info *info)
9941068
host_vtimer_irq = info->virtual_irq;
9951069
kvm_irq_fixup_flags(host_vtimer_irq, &host_vtimer_irq_flags);
9961070

1071+
if (kvm_vgic_global_state.no_hw_deactivation) {
1072+
struct fwnode_handle *fwnode;
1073+
struct irq_data *data;
1074+
1075+
fwnode = irq_domain_alloc_named_fwnode("kvm-timer");
1076+
if (!fwnode)
1077+
return -ENOMEM;
1078+
1079+
/* Assume both vtimer and ptimer in the same parent */
1080+
data = irq_get_irq_data(host_vtimer_irq);
1081+
domain = irq_domain_create_hierarchy(data->domain, 0,
1082+
NR_KVM_TIMERS, fwnode,
1083+
&timer_domain_ops, NULL);
1084+
if (!domain) {
1085+
irq_domain_free_fwnode(fwnode);
1086+
return -ENOMEM;
1087+
}
1088+
1089+
arch_timer_irq_ops.flags |= VGIC_IRQ_SW_RESAMPLE;
1090+
WARN_ON(irq_domain_push_irq(domain, host_vtimer_irq,
1091+
(void *)TIMER_VTIMER));
1092+
}
1093+
9971094
if (info->physical_irq > 0) {
9981095
host_ptimer_irq = info->physical_irq;
9991096
kvm_irq_fixup_flags(host_ptimer_irq, &host_ptimer_irq_flags);
1097+
1098+
if (domain)
1099+
WARN_ON(irq_domain_push_irq(domain, host_ptimer_irq,
1100+
(void *)TIMER_PTIMER));
10001101
}
10011102

10021103
return 0;
@@ -1125,10 +1226,6 @@ bool kvm_arch_timer_get_input_level(int vintid)
11251226
return kvm_timer_should_fire(timer);
11261227
}
11271228

1128-
static struct irq_ops arch_timer_irq_ops = {
1129-
.get_input_level = kvm_arch_timer_get_input_level,
1130-
};
1131-
11321229
int kvm_timer_enable(struct kvm_vcpu *vcpu)
11331230
{
11341231
struct arch_timer_cpu *timer = vcpu_timer(vcpu);

0 commit comments

Comments
 (0)