Skip to content

Commit df635c5

Browse files
committed
KVM: arm/arm64: Support VGIC dist pend/active changes for mapped IRQs
For mapped IRQs (with the HW bit set in the LR) we have to follow some rules of the architecture. One of these rules is that VM must not be allowed to deactivate a virtual interrupt with the HW bit set unless the physical interrupt is also active. This works fine when injecting mapped interrupts, because we leave it up to the injector to either set EOImode==1 or manually set the active state of the physical interrupt. However, the guest can set virtual interrupt to be pending or active by writing to the virtual distributor, which could lead to deactivating a virtual interrupt with the HW bit set without the physical interrupt being active. We could set the physical interrupt to active whenever we are about to enter the VM with a HW interrupt either pending or active, but that would be really slow, especially on GICv2. So we take the long way around and do the hard work when needed, which is expected to be extremely rare. When the VM sets the pending state for a HW interrupt on the virtual distributor we set the active state on the physical distributor, because the virtual interrupt can become active and then the guest can deactivate it. When the VM clears the pending state we also clear it on the physical side, because the injector might otherwise raise the interrupt. We also clear the physical active state when the virtual interrupt is not active, since otherwise a SPEND/CPEND sequence from the guest would prevent signaling of future interrupts. Changing the state of mapped interrupts from userspace is not supported, and it's expected that userspace unmaps devices from VFIO before attempting to set the interrupt state, because the interrupt state is driven by hardware. Reviewed-by: Marc Zyngier <[email protected]> Reviewed-by: Eric Auger <[email protected]> Signed-off-by: Christoffer Dall <[email protected]>
1 parent b6909a6 commit df635c5

File tree

3 files changed

+73
-6
lines changed

3 files changed

+73
-6
lines changed

virt/kvm/arm/vgic/vgic-mmio.c

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <linux/kvm.h>
1717
#include <linux/kvm_host.h>
1818
#include <kvm/iodev.h>
19+
#include <kvm/arm_arch_timer.h>
1920
#include <kvm/arm_vgic.h>
2021

2122
#include "vgic.h"
@@ -143,10 +144,22 @@ static struct kvm_vcpu *vgic_get_mmio_requester_vcpu(void)
143144
return vcpu;
144145
}
145146

147+
/* Must be called with irq->irq_lock held */
148+
static void vgic_hw_irq_spending(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
149+
bool is_uaccess)
150+
{
151+
if (is_uaccess)
152+
return;
153+
154+
irq->pending_latch = true;
155+
vgic_irq_set_phys_active(irq, true);
156+
}
157+
146158
void vgic_mmio_write_spending(struct kvm_vcpu *vcpu,
147159
gpa_t addr, unsigned int len,
148160
unsigned long val)
149161
{
162+
bool is_uaccess = !vgic_get_mmio_requester_vcpu();
150163
u32 intid = VGIC_ADDR_TO_INTID(addr, 1);
151164
int i;
152165
unsigned long flags;
@@ -155,17 +168,45 @@ void vgic_mmio_write_spending(struct kvm_vcpu *vcpu,
155168
struct vgic_irq *irq = vgic_get_irq(vcpu->kvm, vcpu, intid + i);
156169

157170
spin_lock_irqsave(&irq->irq_lock, flags);
158-
irq->pending_latch = true;
159-
171+
if (irq->hw)
172+
vgic_hw_irq_spending(vcpu, irq, is_uaccess);
173+
else
174+
irq->pending_latch = true;
160175
vgic_queue_irq_unlock(vcpu->kvm, irq, flags);
161176
vgic_put_irq(vcpu->kvm, irq);
162177
}
163178
}
164179

180+
/* Must be called with irq->irq_lock held */
181+
static void vgic_hw_irq_cpending(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
182+
bool is_uaccess)
183+
{
184+
if (is_uaccess)
185+
return;
186+
187+
irq->pending_latch = false;
188+
189+
/*
190+
* We don't want the guest to effectively mask the physical
191+
* interrupt by doing a write to SPENDR followed by a write to
192+
* CPENDR for HW interrupts, so we clear the active state on
193+
* the physical side if the virtual interrupt is not active.
194+
* This may lead to taking an additional interrupt on the
195+
* host, but that should not be a problem as the worst that
196+
* can happen is an additional vgic injection. We also clear
197+
* the pending state to maintain proper semantics for edge HW
198+
* interrupts.
199+
*/
200+
vgic_irq_set_phys_pending(irq, false);
201+
if (!irq->active)
202+
vgic_irq_set_phys_active(irq, false);
203+
}
204+
165205
void vgic_mmio_write_cpending(struct kvm_vcpu *vcpu,
166206
gpa_t addr, unsigned int len,
167207
unsigned long val)
168208
{
209+
bool is_uaccess = !vgic_get_mmio_requester_vcpu();
169210
u32 intid = VGIC_ADDR_TO_INTID(addr, 1);
170211
int i;
171212
unsigned long flags;
@@ -175,7 +216,10 @@ void vgic_mmio_write_cpending(struct kvm_vcpu *vcpu,
175216

176217
spin_lock_irqsave(&irq->irq_lock, flags);
177218

178-
irq->pending_latch = false;
219+
if (irq->hw)
220+
vgic_hw_irq_cpending(vcpu, irq, is_uaccess);
221+
else
222+
irq->pending_latch = false;
179223

180224
spin_unlock_irqrestore(&irq->irq_lock, flags);
181225
vgic_put_irq(vcpu->kvm, irq);
@@ -202,8 +246,19 @@ unsigned long vgic_mmio_read_active(struct kvm_vcpu *vcpu,
202246
return value;
203247
}
204248

249+
/* Must be called with irq->irq_lock held */
250+
static void vgic_hw_irq_change_active(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
251+
bool active, bool is_uaccess)
252+
{
253+
if (is_uaccess)
254+
return;
255+
256+
irq->active = active;
257+
vgic_irq_set_phys_active(irq, active);
258+
}
259+
205260
static void vgic_mmio_change_active(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
206-
bool new_active_state)
261+
bool active)
207262
{
208263
unsigned long flags;
209264
struct kvm_vcpu *requester_vcpu = vgic_get_mmio_requester_vcpu();
@@ -231,8 +286,12 @@ static void vgic_mmio_change_active(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
231286
irq->vcpu->cpu != -1) /* VCPU thread is running */
232287
cond_resched_lock(&irq->irq_lock);
233288

234-
irq->active = new_active_state;
235-
if (new_active_state)
289+
if (irq->hw)
290+
vgic_hw_irq_change_active(vcpu, irq, active, !requester_vcpu);
291+
else
292+
irq->active = active;
293+
294+
if (irq->active)
236295
vgic_queue_irq_unlock(vcpu->kvm, irq, flags);
237296
else
238297
spin_unlock_irqrestore(&irq->irq_lock, flags);

virt/kvm/arm/vgic/vgic.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
144144
kfree(irq);
145145
}
146146

147+
void vgic_irq_set_phys_pending(struct vgic_irq *irq, bool pending)
148+
{
149+
WARN_ON(irq_set_irqchip_state(irq->host_irq,
150+
IRQCHIP_STATE_PENDING,
151+
pending));
152+
}
153+
147154
bool vgic_get_phys_line_level(struct vgic_irq *irq)
148155
{
149156
bool line_level;

virt/kvm/arm/vgic/vgic.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ struct vgic_irq *vgic_get_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
146146
u32 intid);
147147
void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq);
148148
bool vgic_get_phys_line_level(struct vgic_irq *irq);
149+
void vgic_irq_set_phys_pending(struct vgic_irq *irq, bool pending);
149150
void vgic_irq_set_phys_active(struct vgic_irq *irq, bool active);
150151
bool vgic_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
151152
unsigned long flags);

0 commit comments

Comments
 (0)