xen/arm: Release IRQ routed to a domain when it's destroying
authorJulien Grall <julien.grall@linaro.org>
Tue, 28 Apr 2015 14:32:27 +0000 (15:32 +0100)
committerIan Campbell <ian.campbell@citrix.com>
Fri, 8 May 2015 14:41:58 +0000 (15:41 +0100)
Xen has to release IRQ routed to a domain in order to reuse later.
Currently only SPIs can be routed to the guest so we only need to
browse SPIs for a specific domain.

Furthermore, a guest can crash and leave the IRQ in an incorrect state
(i.e has not been EOIed). Xen will have to reset the IRQ in order to
be able to reuse the IRQ later.

Introduce 2 new functions for release an IRQ routed to a domain:
    - release_guest_irq: upper level to retrieve the IRQ, call the GIC
    code and release the action
    - gic_remove_guest_irq: Check if we can remove the IRQ, and reset
    it if necessary

Signed-off-by: Julien Grall <julien.grall@linaro.org>
Acked-by: Ian Campbell <ian.campbell@citrix.com>
xen/arch/arm/gic.c
xen/arch/arm/irq.c
xen/arch/arm/vgic.c
xen/include/asm-arm/gic.h
xen/include/asm-arm/irq.h

index 5f349977a62f084eac78f34503f4df0d2aa5cf94..f023e4f7b8d994f46adf420a5509992629c0e7bc 100644 (file)
@@ -163,6 +163,51 @@ out:
     return res;
 }
 
+/* This function only works with SPIs for now */
+int gic_remove_irq_from_guest(struct domain *d, unsigned int virq,
+                              struct irq_desc *desc)
+{
+    struct vcpu *v_target = vgic_get_target_vcpu(d->vcpu[0], virq);
+    struct vgic_irq_rank *rank = vgic_rank_irq(v_target, virq);
+    struct pending_irq *p = irq_to_pending(v_target, virq);
+    unsigned long flags;
+
+    ASSERT(spin_is_locked(&desc->lock));
+    ASSERT(test_bit(_IRQ_GUEST, &desc->status));
+    ASSERT(p->desc == desc);
+
+    vgic_lock_rank(v_target, rank, flags);
+
+    if ( d->is_dying )
+    {
+        desc->handler->shutdown(desc);
+
+        /* EOI the IRQ it it has not been done by the guest */
+        if ( test_bit(_IRQ_INPROGRESS, &desc->status) )
+            gic_hw_ops->deactivate_irq(desc);
+        clear_bit(_IRQ_INPROGRESS, &desc->status);
+    }
+    else
+    {
+        /*
+         * TODO: Handle eviction from LRs For now, deny
+         * remove if the IRQ is inflight or not disabled.
+         */
+        if ( test_bit(_IRQ_INPROGRESS, &desc->status) ||
+             !test_bit(_IRQ_DISABLED, &desc->status) )
+            return -EBUSY;
+    }
+
+    clear_bit(_IRQ_GUEST, &desc->status);
+    desc->handler = &no_irq_type;
+
+    p->desc = NULL;
+
+    vgic_unlock_rank(v_target, rank, flags);
+
+    return 0;
+}
+
 int gic_irq_xlate(const u32 *intspec, unsigned int intsize,
                   unsigned int *out_hwirq,
                   unsigned int *out_type)
index b2ddf6b48b77b99f8c8044e99976591d64efa6c2..376c9f234823ee3274967070a7e067361921f06b 100644 (file)
@@ -513,6 +513,52 @@ free_info:
     return retval;
 }
 
+int release_guest_irq(struct domain *d, unsigned int virq)
+{
+    struct irq_desc *desc;
+    struct irq_guest *info;
+    unsigned long flags;
+    struct pending_irq *p;
+    int ret;
+
+    /* Only SPIs are supported */
+    if ( virq < NR_LOCAL_IRQS || virq >= vgic_num_irqs(d) )
+        return -EINVAL;
+
+    p = spi_to_pending(d, virq);
+    if ( !p->desc )
+        return -EINVAL;
+
+    desc = p->desc;
+
+    spin_lock_irqsave(&desc->lock, flags);
+
+    ret = -EINVAL;
+    if ( !test_bit(_IRQ_GUEST, &desc->status) )
+        goto unlock;
+
+    info = irq_get_guest_info(desc);
+    ret = -EINVAL;
+    if ( d != info->d )
+        goto unlock;
+
+    ret = gic_remove_irq_from_guest(d, virq, desc);
+    if ( ret )
+        goto unlock;
+
+    spin_unlock_irqrestore(&desc->lock, flags);
+
+    release_irq(desc->irq, info);
+    xfree(info);
+
+    return 0;
+
+unlock:
+    spin_unlock_irqrestore(&desc->lock, flags);
+
+    return ret;
+}
+
 /*
  * pirq event channels. We don't use these on ARM, instead we use the
  * features of the GIC to inject virtualised normal interrupts.
index c1fa4bbd329cbc487b612e00c08d5c42b14fcf74..73a6f7eddd0d16e7bd72ab9962270ecf91ebed64 100644 (file)
@@ -135,6 +135,22 @@ void register_vgic_ops(struct domain *d, const struct vgic_ops *ops)
 
 void domain_vgic_free(struct domain *d)
 {
+    int i;
+    int ret;
+
+    for ( i = 0; i < (d->arch.vgic.nr_spis); i++ )
+    {
+        struct pending_irq *p = spi_to_pending(d, i + 32);
+
+        if ( p->desc )
+        {
+            ret = release_guest_irq(d, p->irq);
+            if ( ret )
+                dprintk(XENLOG_G_WARNING, "d%u: Failed to release virq %u ret = %d\n",
+                        d->domain_id, p->irq, ret);
+        }
+    }
+
     xfree(d->arch.vgic.shared_irqs);
     xfree(d->arch.vgic.pending_irqs);
     xfree(d->arch.vgic.allocated_irqs);
index ef4bf9a43e30a2dc41b2df7b163e9aea809999ad..9e2acb7309f30afccc9ce4123cb940005b79d809 100644 (file)
@@ -220,6 +220,10 @@ extern int gic_route_irq_to_guest(struct domain *, unsigned int virq,
                                   struct irq_desc *desc,
                                   unsigned int priority);
 
+/* Remove an IRQ passthrough to a guest */
+int gic_remove_irq_from_guest(struct domain *d, unsigned int virq,
+                              struct irq_desc *desc);
+
 extern void gic_inject(void);
 extern void gic_clear_pending_irqs(struct vcpu *v);
 extern int gic_events_need_delivery(void);
index 71b39e707966bf7f8069d633f87a7d1998077ef0..34b492bf7a24b8183d6e824196d64c5fff178744 100644 (file)
@@ -44,6 +44,8 @@ void init_secondary_IRQ(void);
 
 int route_irq_to_guest(struct domain *d, unsigned int virq,
                        unsigned int irq, const char *devname);
+int release_guest_irq(struct domain *d, unsigned int irq);
+
 void arch_move_irqs(struct vcpu *v);
 
 /* Set IRQ type for an SPI */