Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

genirq: Allow per-cpu interrupt sharing for non-overlapping affinities

Interrupt sharing for percpu-devid interrupts is forbidden, and for good
reasons. These are interrupts generated *from* a CPU and handled by itself
(timer, for example). Nobody in their right mind would put two devices on
the same pin (and if they have, they get to keep the pieces...).

But this also prevents more benign cases, where devices are connected
to groups of CPUs, and for which the affinities are not overlapping.
Effectively, the only thing they share is the interrupt number, and
nothing else.

Tweak the definition of IRQF_SHARED applied to percpu_devid interrupts to
allow this particular use case. This results in extra validation at the
point of the interrupt being setup and freed, as well as a tiny bit of
extra complexity for interrupts at handling time (to pick the correct
irqaction).

Signed-off-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Will Deacon <will@kernel.org>
Link: https://patch.msgid.link/20251020122944.3074811-17-maz@kernel.org

authored by

Marc Zyngier and committed by
Thomas Gleixner
bdf4e2ac b9c6aa9e

+61 -14
+6 -2
kernel/irq/chip.c
··· 897 897 void handle_percpu_devid_irq(struct irq_desc *desc) 898 898 { 899 899 struct irq_chip *chip = irq_desc_get_chip(desc); 900 - struct irqaction *action = desc->action; 901 900 unsigned int irq = irq_desc_get_irq(desc); 901 + unsigned int cpu = smp_processor_id(); 902 + struct irqaction *action; 902 903 irqreturn_t res; 903 904 904 905 /* ··· 911 910 if (chip->irq_ack) 912 911 chip->irq_ack(&desc->irq_data); 913 912 913 + for (action = desc->action; action; action = action->next) 914 + if (cpumask_test_cpu(cpu, action->affinity)) 915 + break; 916 + 914 917 if (likely(action)) { 915 918 trace_irq_handler_entry(irq, action); 916 919 res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id)); 917 920 trace_irq_handler_exit(irq, action, res); 918 921 } else { 919 - unsigned int cpu = smp_processor_id(); 920 922 bool enabled = cpumask_test_cpu(cpu, desc->percpu_enabled); 921 923 922 924 if (enabled)
+55 -12
kernel/irq/manage.c
··· 1418 1418 return 0; 1419 1419 } 1420 1420 1421 + static bool valid_percpu_irqaction(struct irqaction *old, struct irqaction *new) 1422 + { 1423 + do { 1424 + if (cpumask_intersects(old->affinity, new->affinity) || 1425 + old->percpu_dev_id == new->percpu_dev_id) 1426 + return false; 1427 + 1428 + old = old->next; 1429 + } while (old); 1430 + 1431 + return true; 1432 + } 1433 + 1421 1434 /* 1422 1435 * Internal function to register an irqaction - typically used to 1423 1436 * allocate special interrupts that are part of the architecture. ··· 1451 1438 struct irqaction *old, **old_ptr; 1452 1439 unsigned long flags, thread_mask = 0; 1453 1440 int ret, nested, shared = 0; 1441 + bool per_cpu_devid; 1454 1442 1455 1443 if (!desc) 1456 1444 return -EINVAL; ··· 1460 1446 return -ENOSYS; 1461 1447 if (!try_module_get(desc->owner)) 1462 1448 return -ENODEV; 1449 + 1450 + per_cpu_devid = irq_settings_is_per_cpu_devid(desc); 1463 1451 1464 1452 new->irq = irq; 1465 1453 ··· 1570 1554 */ 1571 1555 unsigned int oldtype; 1572 1556 1573 - if (irq_is_nmi(desc)) { 1557 + if (irq_is_nmi(desc) && !per_cpu_devid) { 1574 1558 pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n", 1559 + new->name, irq, desc->irq_data.chip->name); 1560 + ret = -EINVAL; 1561 + goto out_unlock; 1562 + } 1563 + 1564 + if (per_cpu_devid && !valid_percpu_irqaction(old, new)) { 1565 + pr_err("Overlapping affinities for %s (irq %d) on irqchip %s.\n", 1575 1566 new->name, irq, desc->irq_data.chip->name); 1576 1567 ret = -EINVAL; 1577 1568 goto out_unlock; ··· 1734 1711 if (!(new->flags & IRQF_NO_AUTOEN) && 1735 1712 irq_settings_can_autoenable(desc)) { 1736 1713 irq_startup(desc, IRQ_RESEND, IRQ_START_COND); 1737 - } else { 1714 + } else if (!per_cpu_devid) { 1738 1715 /* 1739 1716 * Shared interrupts do not go well with disabling 1740 1717 * auto enable. The sharing interrupt might request ··· 2369 2346 static struct irqaction *__free_percpu_irq(unsigned int irq, void __percpu *dev_id) 2370 2347 { 2371 2348 struct irq_desc *desc = irq_to_desc(irq); 2372 - struct irqaction *action; 2349 + struct irqaction *action, **action_ptr; 2373 2350 2374 2351 WARN(in_interrupt(), "Trying to free IRQ %d from IRQ context!\n", irq); 2375 2352 ··· 2377 2354 return NULL; 2378 2355 2379 2356 scoped_guard(raw_spinlock_irqsave, &desc->lock) { 2380 - action = desc->action; 2381 - if (!action || action->percpu_dev_id != dev_id) { 2382 - WARN(1, "Trying to free already-free IRQ %d\n", irq); 2383 - return NULL; 2357 + action_ptr = &desc->action; 2358 + for (;;) { 2359 + action = *action_ptr; 2360 + 2361 + if (!action) { 2362 + WARN(1, "Trying to free already-free IRQ %d\n", irq); 2363 + return NULL; 2364 + } 2365 + 2366 + if (action->percpu_dev_id == dev_id) 2367 + break; 2368 + 2369 + action_ptr = &action->next; 2384 2370 } 2385 2371 2386 - if (!cpumask_empty(desc->percpu_enabled)) { 2387 - WARN(1, "percpu IRQ %d still enabled on CPU%d!\n", 2388 - irq, cpumask_first(desc->percpu_enabled)); 2372 + if (cpumask_intersects(desc->percpu_enabled, action->affinity)) { 2373 + WARN(1, "percpu IRQ %d still enabled on CPU%d!\n", irq, 2374 + cpumask_first_and(desc->percpu_enabled, action->affinity)); 2389 2375 return NULL; 2390 2376 } 2391 2377 2392 2378 /* Found it - now remove it from the list of entries: */ 2393 - desc->action = NULL; 2394 - desc->istate &= ~IRQS_NMI; 2379 + *action_ptr = action->next; 2380 + 2381 + /* Demote from NMI if we killed the last action */ 2382 + if (!desc->action) 2383 + desc->istate &= ~IRQS_NMI; 2395 2384 } 2396 2385 2397 2386 unregister_handler_proc(irq, action); ··· 2496 2461 action->name = devname; 2497 2462 action->percpu_dev_id = dev_id; 2498 2463 action->affinity = affinity; 2464 + 2465 + /* 2466 + * We allow some form of sharing for non-overlapping affinity 2467 + * masks. Obviously, covering all CPUs prevents any sharing in 2468 + * the first place. 2469 + */ 2470 + if (!cpumask_equal(affinity, cpu_possible_mask)) 2471 + action->flags |= IRQF_SHARED; 2499 2472 2500 2473 return action; 2501 2474 }