Commit 8ff1c16c authored by Bibo Mao's avatar Bibo Mao Committed by Thomas Gleixner
Browse files

irqchip/loongson-eiointc: Add multiple interrupt pin routing support



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: default avatarBibo Mao <maobibo@loongson.cn>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20250804081946.1456573-3-maobibo@loongson.cn
parent 7fb83eb6
Loading
Loading
Loading
Loading
+78 −10
Original line number Diff line number Diff line
@@ -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)

@@ -59,6 +60,14 @@
#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
			 */
			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
				 */
			bit = BIT(eiointc_priv[index]->parent_hwirq - INT_HWI0);
			} 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);