All of lore.kernel.org
 help / color / mirror / Atom feed
From: Keith Busch <kbusch@kernel.org>
To: Thomas Gleixner <tglx@linutronix.de>
Cc: Christoph Hellwig <hch@lst.de>,
	santhosh kumar <santhu.linux@gmail.com>,
	linux-kernel@vger.kernel.org, Ming Lei <ming.lei@redhat.com>,
	x86@kernel.org
Subject: Re: [RFC] PCI_IRQ_AFFINITY limits MSI-X allocation on 384 CPU / 1000+ NVMe system
Date: Mon, 10 Aug 2026 15:11:18 -0600	[thread overview]
Message-ID: <ano-dj3zZpzeJKCg@kbusch-mbp> (raw)
In-Reply-To: <871pcdfufr.ffs@fw13>

On Tue, Aug 04, 2026 at 11:27:04PM +0200, Thomas Gleixner wrote:
> 
> We could come up with some less restrictive mechanism, but that would at
> the end run into the vector limitation on hotplug/hibernate because you
> can't fit more than ~200 vectors into the last online CPU.
> 
> OTOH. With 1000 devices which consume also one non-managed interrupt for
> their management queues, i.e. a total of 1000, that's not going to work
> anyway.

I think we'd have to at some point declare that extremely mismatched
setups just can't be reasonably supported. :)

My understanding is you've got the managed IRQ's allocating a vector up
front for every possible CPU they could be migrated to primarily to
ensure CPU unplug can always move the effective affinity to an active
CPU. I totally get that not having this guarantee can cause a device to
appear unresponsive, but maybe this is unlikely enough to accept? Or
maybe not, I am not sure; it just seems like a fringe case that trying
to mitigate causes a more likely problem.

Anyway, if we can tolerate dynamic vector allocation that only happens
on effective CPU assigment, here's a PoC I did some basic sanity testing
with:

---
diff --git a/arch/x86/kernel/apic/vector.c b/arch/x86/kernel/apic/vector.c
index bddc544653999..314c764cb5456 100644
--- a/arch/x86/kernel/apic/vector.c
+++ b/arch/x86/kernel/apic/vector.c
@@ -199,19 +199,15 @@ static void vector_assign_managed_shutdown(struct irq_data *irqd)
 	apic_update_irq_cfg(irqd, MANAGED_IRQ_SHUTDOWN_VECTOR, cpu);
 }
 
-static int reserve_managed_vector(struct irq_data *irqd)
+static void mark_managed_vector(struct irq_data *irqd)
 {
-	const struct cpumask *affmsk = irq_data_get_affinity_mask(irqd);
 	struct apic_chip_data *apicd = apic_chip_data(irqd);
 	unsigned long flags;
-	int ret;
 
 	raw_spin_lock_irqsave(&vector_lock, flags);
 	apicd->is_managed = true;
-	ret = irq_matrix_reserve_managed(vector_matrix, affmsk);
 	raw_spin_unlock_irqrestore(&vector_lock, flags);
-	trace_vector_reserve_managed(irqd->irq, ret);
-	return ret;
+	trace_vector_reserve_managed(irqd->irq, 0);
 }
 
 static void reserve_irq_vector_locked(struct irq_data *irqd)
@@ -315,8 +311,10 @@ static int assign_irq_vector_any_locked(struct irq_data *irqd)
 static int
 assign_irq_vector_policy(struct irq_data *irqd, struct irq_alloc_info *info)
 {
-	if (irqd_affinity_is_managed(irqd))
-		return reserve_managed_vector(irqd);
+	if (irqd_affinity_is_managed(irqd)) {
+		mark_managed_vector(irqd);
+		return 0;
+	}
 	if (info->mask)
 		return assign_irq_vector(irqd, info->mask);
 	/*
@@ -483,7 +481,6 @@ static int x86_vector_activate(struct irq_domain *dom, struct irq_data *irqd,
 
 static void vector_free_reserved_and_managed(struct irq_data *irqd)
 {
-	const struct cpumask *dest = irq_data_get_affinity_mask(irqd);
 	struct apic_chip_data *apicd = apic_chip_data(irqd);
 
 	trace_vector_teardown(irqd->irq, apicd->is_managed,
@@ -491,8 +488,6 @@ static void vector_free_reserved_and_managed(struct irq_data *irqd)
 
 	if (apicd->has_reserved)
 		irq_matrix_remove_reserved(vector_matrix);
-	if (apicd->is_managed)
-		irq_matrix_remove_managed(vector_matrix, dest);
 }
 
 static void x86_vector_free_irqs(struct irq_domain *domain,
diff --git a/include/linux/irq.h b/include/linux/irq.h
index f485369b1b4f7..60e7573606c44 100644
--- a/include/linux/irq.h
+++ b/include/linux/irq.h
@@ -1251,8 +1251,6 @@ struct irq_matrix *irq_alloc_matrix(unsigned int matrix_bits,
 void irq_matrix_online(struct irq_matrix *m);
 void irq_matrix_offline(struct irq_matrix *m);
 void irq_matrix_assign_system(struct irq_matrix *m, unsigned int bit, bool replace);
-int irq_matrix_reserve_managed(struct irq_matrix *m, const struct cpumask *msk);
-void irq_matrix_remove_managed(struct irq_matrix *m, const struct cpumask *msk);
 int irq_matrix_alloc_managed(struct irq_matrix *m, const struct cpumask *msk,
 				unsigned int *mapped_cpu);
 void irq_matrix_reserve(struct irq_matrix *m);
diff --git a/kernel/irq/matrix.c b/kernel/irq/matrix.c
index faafb43a4e611..fab44ce35732f 100644
--- a/kernel/irq/matrix.c
+++ b/kernel/irq/matrix.c
@@ -164,7 +164,9 @@ static unsigned int matrix_find_best_cpu_managed(struct irq_matrix *m,
 	for_each_cpu(cpu, msk) {
 		cm = per_cpu_ptr(m->maps, cpu);
 
-		if (!cm->online || cm->managed_allocated > allocated)
+		if (!cm->online || !cm->available)
+			continue;
+		if (cm->managed_allocated > allocated)
 			continue;
 
 		best_cpu = cpu;
@@ -204,95 +206,28 @@ void irq_matrix_assign_system(struct irq_matrix *m, unsigned int bit,
 	trace_irq_matrix_assign_system(bit, m);
 }
 
-/**
- * irq_matrix_reserve_managed - Reserve a managed interrupt in a CPU map
- * @m:		Matrix pointer
- * @msk:	On which CPUs the bits should be reserved.
- *
- * Can be called for offline CPUs. Note, this will only reserve one bit
- * on all CPUs in @msk, but it's not guaranteed that the bits are at the
- * same offset on all CPUs
- */
-int irq_matrix_reserve_managed(struct irq_matrix *m, const struct cpumask *msk)
-{
-	unsigned int cpu, failed_cpu;
-
-	for_each_cpu(cpu, msk) {
-		struct cpumap *cm = per_cpu_ptr(m->maps, cpu);
-		unsigned int bit;
-
-		bit = matrix_alloc_area(m, cm, 1, true);
-		if (bit >= m->alloc_end)
-			goto cleanup;
-		cm->managed++;
-		if (cm->online) {
-			cm->available--;
-			m->global_available--;
-		}
-		trace_irq_matrix_reserve_managed(bit, cpu, m, cm);
-	}
-	return 0;
-cleanup:
-	failed_cpu = cpu;
-	for_each_cpu(cpu, msk) {
-		if (cpu == failed_cpu)
-			break;
-		irq_matrix_remove_managed(m, cpumask_of(cpu));
-	}
-	return -ENOSPC;
-}
-
-/**
- * irq_matrix_remove_managed - Remove managed interrupts in a CPU map
- * @m:		Matrix pointer
- * @msk:	On which CPUs the bits should be removed
- *
- * Can be called for offline CPUs
- *
- * This removes not allocated managed interrupts from the map. It does
- * not matter which one because the managed interrupts free their
- * allocation when they shut down. If not, the accounting is screwed,
- * but all what can be done at this point is warn about it.
- */
-void irq_matrix_remove_managed(struct irq_matrix *m, const struct cpumask *msk)
-{
-	unsigned int cpu;
-
-	for_each_cpu(cpu, msk) {
-		struct cpumap *cm = per_cpu_ptr(m->maps, cpu);
-		unsigned int bit, end = m->alloc_end;
-
-		if (WARN_ON_ONCE(!cm->managed))
-			continue;
-
-		/* Get managed bit which are not allocated */
-		bitmap_andnot(m->scratch_map, cm->managed_map, cm->alloc_map, end);
-
-		bit = find_first_bit(m->scratch_map, end);
-		if (WARN_ON_ONCE(bit >= end))
-			continue;
-
-		clear_bit(bit, cm->managed_map);
-
-		cm->managed--;
-		if (cm->online) {
-			cm->available++;
-			m->global_available++;
-		}
-		trace_irq_matrix_remove_managed(bit, cpu, m, cm);
-	}
-}
-
 /**
  * irq_matrix_alloc_managed - Allocate a managed interrupt in a CPU map
  * @m:		Matrix pointer
  * @msk:	Which CPUs to search in
  * @mapped_cpu:	Pointer to store the CPU for which the irq was allocated
+ *
+ * Managed interrupts used to reserve a vector on every CPU of their affinity
+ * mask before any of them was used, so that migrating one on CPU offline could
+ * never fail. That costs one vector on every CPU of the mask for each
+ * interrupt, which does not scale with the number of devices: a mask spread
+ * over all CPUs consumes a vector on every CPU no matter how few interrupts
+ * the device asked for.
+ *
+ * The vector is therefore taken only on the CPU the interrupt is assigned to,
+ * and released again when that vector is freed. Migration has to allocate a
+ * new vector and can fail, which is why lapic_can_unplug_cpu() has to account
+ * for managed interrupts before letting a CPU go down.
  */
 int irq_matrix_alloc_managed(struct irq_matrix *m, const struct cpumask *msk,
 			     unsigned int *mapped_cpu)
 {
-	unsigned int bit, cpu, end;
+	unsigned int bit, cpu;
 	struct cpumap *cm;
 
 	if (cpumask_empty(msk))
@@ -303,16 +238,17 @@ int irq_matrix_alloc_managed(struct irq_matrix *m, const struct cpumask *msk,
 		return -ENOSPC;
 
 	cm = per_cpu_ptr(m->maps, cpu);
-	end = m->alloc_end;
-	/* Get managed bit which are not allocated */
-	bitmap_andnot(m->scratch_map, cm->managed_map, cm->alloc_map, end);
-	bit = find_first_bit(m->scratch_map, end);
-	if (bit >= end)
+	bit = matrix_alloc_area(m, cm, 1, true);
+	if (bit >= m->alloc_end)
 		return -ENOSPC;
+
+	cm->managed++;
 	set_bit(bit, cm->alloc_map);
 	cm->allocated++;
 	cm->managed_allocated++;
+	cm->available--;
 	m->total_allocated++;
+	m->global_available--;
 	*mapped_cpu = cpu;
 	trace_irq_matrix_alloc_managed(bit, cpu, m, cm);
 	return bit;
@@ -433,17 +369,20 @@ void irq_matrix_free(struct irq_matrix *m, unsigned int cpu,
 		return;
 
 	cm->allocated--;
-	if(managed)
+	if (managed) {
+		/* The vector reservation was taken along with the vector */
+		clear_bit(bit, cm->managed_map);
+		cm->managed--;
 		cm->managed_allocated--;
+	}
 
 	if (cm->online)
 		m->total_allocated--;
 
-	if (!managed) {
-		cm->available++;
-		if (cm->online)
-			m->global_available++;
-	}
+	cm->available++;
+	if (cm->online)
+		m->global_available++;
+
 	trace_irq_matrix_free(bit, cpu, m, cm);
 }
 
@@ -472,16 +411,18 @@ unsigned int irq_matrix_reserved(struct irq_matrix *m)
 }
 
 /**
- * irq_matrix_allocated - Get the number of allocated non-managed irqs on the local CPU
+ * irq_matrix_allocated - Get the number of allocated irqs on the local CPU
  * @m:		Pointer to the matrix to search
  *
- * This returns number of allocated non-managed interrupts.
+ * This returns the number of allocated interrupts, managed ones included.
+ * Managed interrupts hold a vector only on the CPU they are assigned to, so
+ * they have to be relocated on CPU offline just like the regular ones.
  */
 unsigned int irq_matrix_allocated(struct irq_matrix *m)
 {
 	struct cpumap *cm = this_cpu_ptr(m->maps);
 
-	return cm->allocated - cm->managed_allocated;
+	return cm->allocated;
 }
 
 #ifdef CONFIG_GENERIC_IRQ_DEBUGFS
--

  reply	other threads:[~2026-08-10 21:11 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-28  6:42 [RFC] PCI_IRQ_AFFINITY limits MSI-X allocation on 384 CPU / 1000+ NVMe system santhosh kumar
2026-07-28 22:52 ` Thomas Gleixner
2026-07-29  6:29   ` santhosh kumar
2026-07-30 13:16     ` Thomas Gleixner
2026-07-30 13:28       ` Keith Busch
2026-07-30 19:44         ` Thomas Gleixner
2026-08-03 15:43           ` Keith Busch
2026-08-04 14:10             ` Christoph Hellwig
2026-08-04 14:56               ` Keith Busch
2026-08-04 21:27                 ` Thomas Gleixner
2026-08-10 21:11                   ` Keith Busch [this message]
2026-08-14  0:52                     ` Thomas Gleixner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ano-dj3zZpzeJKCg@kbusch-mbp \
    --to=kbusch@kernel.org \
    --cc=hch@lst.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ming.lei@redhat.com \
    --cc=santhu.linux@gmail.com \
    --cc=tglx@linutronix.de \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.