irqchip/gic-v3: Allow interrupts to be set as pseudo-NMI
authorJulien Thierry <julien.thierry@arm.com>
Thu, 31 Jan 2019 14:58:59 +0000 (14:58 +0000)
committerCatalin Marinas <catalin.marinas@arm.com>
Wed, 6 Feb 2019 10:05:21 +0000 (10:05 +0000)
Implement NMI callbacks for GICv3 irqchip. Install NMI safe handlers
when setting up interrupt line as NMI.

Only SPIs and PPIs are allowed to be set up as NMI.

Signed-off-by: Julien Thierry <julien.thierry@arm.com>
Reviewed-by: Marc Zyngier <marc.zyngier@arm.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
drivers/irqchip/irq-gic-v3.c

index c2767fb..15e55d3 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/percpu.h>
+#include <linux/refcount.h>
 #include <linux/slab.h>
 
 #include <linux/irqchip.h>
@@ -93,6 +94,9 @@ static DEFINE_STATIC_KEY_TRUE(supports_deactivate_key);
  */
 static DEFINE_STATIC_KEY_FALSE(supports_pseudo_nmis);
 
+/* ppi_nmi_refs[n] == number of cpus having ppi[n + 16] set as NMI */
+static refcount_t ppi_nmi_refs[16];
+
 static struct gic_kvm_info gic_v3_kvm_info;
 static DEFINE_PER_CPU(bool, has_rss);
 
@@ -320,6 +324,79 @@ static int gic_irq_get_irqchip_state(struct irq_data *d,
        return 0;
 }
 
+static void gic_irq_set_prio(struct irq_data *d, u8 prio)
+{
+       void __iomem *base = gic_dist_base(d);
+
+       writeb_relaxed(prio, base + GICD_IPRIORITYR + gic_irq(d));
+}
+
+static int gic_irq_nmi_setup(struct irq_data *d)
+{
+       struct irq_desc *desc = irq_to_desc(d->irq);
+
+       if (!gic_supports_nmi())
+               return -EINVAL;
+
+       if (gic_peek_irq(d, GICD_ISENABLER)) {
+               pr_err("Cannot set NMI property of enabled IRQ %u\n", d->irq);
+               return -EINVAL;
+       }
+
+       /*
+        * A secondary irq_chip should be in charge of LPI request,
+        * it should not be possible to get there
+        */
+       if (WARN_ON(gic_irq(d) >= 8192))
+               return -EINVAL;
+
+       /* desc lock should already be held */
+       if (gic_irq(d) < 32) {
+               /* Setting up PPI as NMI, only switch handler for first NMI */
+               if (!refcount_inc_not_zero(&ppi_nmi_refs[gic_irq(d) - 16])) {
+                       refcount_set(&ppi_nmi_refs[gic_irq(d) - 16], 1);
+                       desc->handle_irq = handle_percpu_devid_fasteoi_nmi;
+               }
+       } else {
+               desc->handle_irq = handle_fasteoi_nmi;
+       }
+
+       gic_irq_set_prio(d, GICD_INT_NMI_PRI);
+
+       return 0;
+}
+
+static void gic_irq_nmi_teardown(struct irq_data *d)
+{
+       struct irq_desc *desc = irq_to_desc(d->irq);
+
+       if (WARN_ON(!gic_supports_nmi()))
+               return;
+
+       if (gic_peek_irq(d, GICD_ISENABLER)) {
+               pr_err("Cannot set NMI property of enabled IRQ %u\n", d->irq);
+               return;
+       }
+
+       /*
+        * A secondary irq_chip should be in charge of LPI request,
+        * it should not be possible to get there
+        */
+       if (WARN_ON(gic_irq(d) >= 8192))
+               return;
+
+       /* desc lock should already be held */
+       if (gic_irq(d) < 32) {
+               /* Tearing down NMI, only switch handler for last NMI */
+               if (refcount_dec_and_test(&ppi_nmi_refs[gic_irq(d) - 16]))
+                       desc->handle_irq = handle_percpu_devid_irq;
+       } else {
+               desc->handle_irq = handle_fasteoi_irq;
+       }
+
+       gic_irq_set_prio(d, GICD_INT_DEF_PRI);
+}
+
 static void gic_eoi_irq(struct irq_data *d)
 {
        gic_write_eoir(gic_irq(d));
@@ -964,6 +1041,8 @@ static struct irq_chip gic_chip = {
        .irq_set_affinity       = gic_set_affinity,
        .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
        .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
+       .irq_nmi_setup          = gic_irq_nmi_setup,
+       .irq_nmi_teardown       = gic_irq_nmi_teardown,
        .flags                  = IRQCHIP_SET_TYPE_MASKED |
                                  IRQCHIP_SKIP_SET_WAKE |
                                  IRQCHIP_MASK_ON_SUSPEND,
@@ -979,6 +1058,8 @@ static struct irq_chip gic_eoimode1_chip = {
        .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
        .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
        .irq_set_vcpu_affinity  = gic_irq_set_vcpu_affinity,
+       .irq_nmi_setup          = gic_irq_nmi_setup,
+       .irq_nmi_teardown       = gic_irq_nmi_teardown,
        .flags                  = IRQCHIP_SET_TYPE_MASKED |
                                  IRQCHIP_SKIP_SET_WAKE |
                                  IRQCHIP_MASK_ON_SUSPEND,
@@ -1182,7 +1263,17 @@ static bool gic_enable_quirk_msm8996(void *data)
 
 static void gic_enable_nmi_support(void)
 {
+       int i;
+
+       for (i = 0; i < 16; i++)
+               refcount_set(&ppi_nmi_refs[i], 0);
+
        static_branch_enable(&supports_pseudo_nmis);
+
+       if (static_branch_likely(&supports_deactivate_key))
+               gic_eoimode1_chip.flags |= IRQCHIP_SUPPORTS_NMI;
+       else
+               gic_chip.flags |= IRQCHIP_SUPPORTS_NMI;
 }
 
 static int __init gic_init_bases(void __iomem *dist_base,