]> xenbits.xensource.com Git - xen.git/commitdiff
arm: vgic: fix race between evtchn upcall and evtchnop_send
authorIan Campbell <ian.campbell@citrix.com>
Wed, 6 Mar 2013 08:54:34 +0000 (08:54 +0000)
committerIan Campbell <ian.campbell@citrix.com>
Thu, 11 Apr 2013 08:31:52 +0000 (09:31 +0100)
On ARM the evtchn upcall is done by using a local PPI interrupt. However the
guest will clear the evtchn_upcall_pending bit before it EOIs that PPI (which
happens late). This means vgic_vcpu_inject_irq (called via
vcpu_mark_events_pending) sees the PPI as in flight and ends up not reinjecting
it, if this happens after the guest has finished its event channel processing
loop but before the EOI then we have lost the upcall.

To fix this we need to check if an evtchn upcall is pending when returning to
the guest and if so reinject the PPI.

We therefore also need to call gic_restore_pending_irqs on the exit to guest
path in order to pickup any newly inject IRQ and propagate it into a free LR.
This doesn't currently support bumping a lower priority interrupt out of the
LRs in order to inject a new higher priority interrupt. We don't yet implement
interrupt prioritisation (and guests don't use it either) so this will do for
now.

Since gic_restore_pending_irqs is now called in the return to guest path it is
called with interrupts disabled and accordingly must use the
irqsave/irqrestore spinlock primitives.

Signed-off-by: Ian Campbell <ian.campbell@citrix.com>
Acked-by: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
xen/arch/arm/gic.c

index 5a380ca1f711084d6bdf29b5051e6a25e74a0d69..4124b1d2977b71a40de7b8be8fa849c83fd45b2d 100644 (file)
@@ -513,17 +513,18 @@ static void gic_restore_pending_irqs(struct vcpu *v)
 {
     int i;
     struct pending_irq *p, *t;
+    unsigned long flags;
 
     list_for_each_entry_safe ( p, t, &v->arch.vgic.lr_pending, lr_queue )
     {
         i = find_first_zero_bit(&this_cpu(lr_mask), nr_lrs);
         if ( i >= nr_lrs ) return;
 
-        spin_lock_irq(&gic.lock);
+        spin_lock_irqsave(&gic.lock, flags);
         gic_set_lr(i, p->irq, GICH_LR_PENDING, p->priority);
         list_del_init(&p->lr_queue);
         set_bit(i, &this_cpu(lr_mask));
-        spin_unlock_irq(&gic.lock);
+        spin_unlock_irqrestore(&gic.lock, flags);
     }
 
 }
@@ -546,6 +547,10 @@ static void gic_inject_irq_stop(void)
 
 void gic_inject(void)
 {
+    if ( vcpu_info(current, evtchn_upcall_pending) )
+        vgic_vcpu_inject_irq(current, VGIC_IRQ_EVTCHN_CALLBACK, 1);
+
+    gic_restore_pending_irqs(current);
     if (!this_cpu(lr_mask))
         gic_inject_irq_stop();
     else