irqchip/loongson-eiointc: Add multiple interrupt pin routing support
authorBibo Mao <maobibo@loongson.cn>
Mon, 4 Aug 2025 08:19:46 +0000 (16:19 +0800)
committerThomas Gleixner <tglx@linutronix.de>
Sun, 24 Aug 2025 10:51:04 +0000 (12:51 +0200)
The eiointc interrupt controller supports 256 interrupt vectors at most,
and the interrupt handler gets the interrupt status from the base register
group EIOINTC_REG_ISR at the interrupt specific offset.

It needs to read the register group EIOINTC_REG_ISR four times to get all
256 interrupt vectors status.

Eiointc registers including EIOINTC_REG_ISR are software emulated for
VMs, so there will be VM-exits when accessing eiointc registers.

Introduce a method to make the eiointc interrupt controller route
to different CPU interrupt pins for every 64 interrupt vectors.

The interrupt handler can then reduce the read to one specific
EIOINTC_REG_ISR register instead of all four, which reduces VM exits.

[ tglx: Massage change log ]

Signed-off-by: Bibo Mao <maobibo@loongson.cn>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20250804081946.1456573-3-maobibo@loongson.cn
drivers/irqchip/irq-loongson-eiointc.c

index baa4069..39e5a72 100644 (file)
@@ -46,6 +46,7 @@
 #define EIOINTC_ALL_ENABLE_VEC_MASK(vector)    (EIOINTC_ALL_ENABLE & ~BIT(vector & 0x1f))
 #define EIOINTC_REG_ENABLE_VEC(vector)         (EIOINTC_REG_ENABLE + ((vector >> 5) << 2))
 #define EIOINTC_USE_CPU_ENCODE                 BIT(0)
+#define EIOINTC_ROUTE_MULT_IP                  BIT(1)
 
 #define MAX_EIO_NODES          (NR_CPUS / CORES_PER_EIO_NODE)
 
 #define EIOINTC_REG_ROUTE_VEC_MASK(vector)     (0xff << EIOINTC_REG_ROUTE_VEC_SHIFT(vector))
 
 static int nr_pics;
+struct eiointc_priv;
+
+struct eiointc_ip_route {
+       struct eiointc_priv     *priv;
+       /* Offset Routed destination IP */
+       int                     start;
+       int                     end;
+};
 
 struct eiointc_priv {
        u32                     node;
@@ -69,6 +78,7 @@ struct eiointc_priv {
        struct irq_domain       *eiointc_domain;
        int                     flags;
        irq_hw_number_t         parent_hwirq;
+       struct eiointc_ip_route route_info[VEC_REG_COUNT];
 };
 
 static struct eiointc_priv *eiointc_priv[MAX_IO_PICS];
@@ -189,6 +199,7 @@ static int eiointc_router_init(unsigned int cpu)
 {
        int i, bit, cores, index, node;
        unsigned int data;
+       int hwirq, mask;
 
        node = cpu_to_eio_node(cpu);
        index = eiointc_index(node);
@@ -198,6 +209,13 @@ static int eiointc_router_init(unsigned int cpu)
                return -EINVAL;
        }
 
+       /* Enable cpu interrupt pin from eiointc */
+       hwirq = eiointc_priv[index]->parent_hwirq;
+       mask = BIT(hwirq);
+       if (eiointc_priv[index]->flags & EIOINTC_ROUTE_MULT_IP)
+               mask |= BIT(hwirq + 1) | BIT(hwirq + 2) | BIT(hwirq + 3);
+       set_csr_ecfg(mask);
+
        if (!(eiointc_priv[index]->flags & EIOINTC_USE_CPU_ENCODE))
                cores = CORES_PER_EIO_NODE;
        else
@@ -215,10 +233,28 @@ static int eiointc_router_init(unsigned int cpu)
                        /*
                         * Route to interrupt pin, relative offset used here
                         * Offset 0 means routing to IP0 and so on
-                        * Every 32 vector routing to one interrupt pin
+                        *
+                        * If flags is set with EIOINTC_ROUTE_MULT_IP,
+                        * every 64 vector routes to different consecutive
+                        * IPs, otherwise all vector routes to the same IP
                         */
-                       bit = BIT(eiointc_priv[index]->parent_hwirq - INT_HWI0);
-                       data = bit | (bit << 8) | (bit << 16) | (bit << 24);
+                       if (eiointc_priv[index]->flags & EIOINTC_ROUTE_MULT_IP) {
+                               /* The first 64 vectors route to hwirq */
+                               bit = BIT(hwirq++ - INT_HWI0);
+                               data = bit | (bit << 8);
+
+                               /* The second 64 vectors route to hwirq + 1 */
+                               bit = BIT(hwirq++ - INT_HWI0);
+                               data |= (bit << 16) | (bit << 24);
+
+                               /*
+                                * Route to hwirq + 2/hwirq + 3 separately
+                                * in next loop
+                                */
+                       } else  {
+                               bit = BIT(hwirq - INT_HWI0);
+                               data = bit | (bit << 8) | (bit << 16) | (bit << 24);
+                       }
                        iocsr_write32(data, EIOINTC_REG_IPMAP + i * 4);
                }
 
@@ -247,15 +283,22 @@ static int eiointc_router_init(unsigned int cpu)
 
 static void eiointc_irq_dispatch(struct irq_desc *desc)
 {
-       int i;
-       u64 pending;
-       bool handled = false;
+       struct eiointc_ip_route *info = irq_desc_get_handler_data(desc);
        struct irq_chip *chip = irq_desc_get_chip(desc);
-       struct eiointc_priv *priv = irq_desc_get_handler_data(desc);
+       bool handled = false;
+       u64 pending;
+       int i;
 
        chained_irq_enter(chip, desc);
 
-       for (i = 0; i < eiointc_priv[0]->vec_count / VEC_COUNT_PER_REG; i++) {
+       /*
+        * If EIOINTC_ROUTE_MULT_IP is set, every 64 interrupt vectors in
+        * eiointc interrupt controller routes to different cpu interrupt pins
+        *
+        * Every cpu interrupt pin has its own irq handler, it is ok to
+        * read ISR for these 64 interrupt vectors rather than all vectors
+        */
+       for (i = info->start; i < info->end; i++) {
                pending = iocsr_read64(EIOINTC_REG_ISR + (i << 3));
 
                /* Skip handling if pending bitmap is zero */
@@ -268,7 +311,7 @@ static void eiointc_irq_dispatch(struct irq_desc *desc)
                        int bit = __ffs(pending);
                        int irq = bit + VEC_COUNT_PER_REG * i;
 
-                       generic_handle_domain_irq(priv->eiointc_domain, irq);
+                       generic_handle_domain_irq(info->priv->eiointc_domain, irq);
                        pending &= ~BIT(bit);
                        handled = true;
                }
@@ -468,8 +511,33 @@ static int __init eiointc_init(struct eiointc_priv *priv, int parent_irq,
        }
 
        eiointc_priv[nr_pics++] = priv;
+       /*
+        * Only the first eiointc device on VM supports routing to
+        * different CPU interrupt pins. The later eiointc devices use
+        * generic method if there are multiple eiointc devices in future
+        */
+       if (cpu_has_hypervisor && (nr_pics == 1)) {
+               priv->flags |= EIOINTC_ROUTE_MULT_IP;
+               priv->parent_hwirq = INT_HWI0;
+       }
+
+       if (priv->flags & EIOINTC_ROUTE_MULT_IP) {
+               for (i = 0; i < priv->vec_count / VEC_COUNT_PER_REG; i++) {
+                       priv->route_info[i].start  = priv->parent_hwirq - INT_HWI0 + i;
+                       priv->route_info[i].end    = priv->route_info[i].start + 1;
+                       priv->route_info[i].priv   = priv;
+                       parent_irq = get_percpu_irq(priv->parent_hwirq + i);
+                       irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch,
+                                                        &priv->route_info[i]);
+               }
+       } else {
+               priv->route_info[0].start  = 0;
+               priv->route_info[0].end    = priv->vec_count / VEC_COUNT_PER_REG;
+               priv->route_info[0].priv   = priv;
+               irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch,
+                                                &priv->route_info[0]);
+       }
        eiointc_router_init(0);
-       irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch, priv);
 
        if (nr_pics == 1) {
                register_syscore_ops(&eiointc_syscore_ops);