* [PATCH v2 0/2] genirq, PCI/MSI: mask without flushing
@ 2026-11-05 19:15 Keith Busch
2026-11-05 19:15 ` [PATCH v2 1/2] genirq: Add disable_irq_noflush() Keith Busch
2026-11-05 19:15 ` [PATCH v2 2/2] PCI/MSI: Implement irq_mask_noflush() for MSI-x Keith Busch
0 siblings, 2 replies; 3+ messages in thread
From: Keith Busch @ 2026-11-05 19:15 UTC (permalink / raw)
To: tglx, bhelgaas
Cc: maz, lrizzo, hch, changfengnan, namjain, linux-kernel, linux-pci,
linux-nvme, Keith Busch
From: Keith Busch <kbusch@kernel.org>
This adds a new irq api, disable_irq_noflush(), backed by an optional
irq_chip irq_mask_noflush() callback, and implemented for PCI MSI-x. It
writes the mask without reading back, and is for callers where an extra
interrupt after returning is harmless compared to the added latency of
flushing the masking write to the device.
There are no users in this series; the hybrid irq/polling work from nvme
is expected to use it when switching between MSI-x interrupts and
polling, so these are posted separately as prep patches that are
hopefully generally useful.
RFC with the nvme usage:
https://lore.kernel.org/linux-nvme/20261013223543.395054-1-kbusch@meta.com/
Changes since v1:
- Keep the channel offline check guarding the flush read in
pci_msix_mask() (Sashiko)
v1: https://lore.kernel.org/all/20261105182706.3700908-1-kbusch@meta.com/
Keith Busch (2):
genirq: Add disable_irq_noflush()
PCI/MSI: Implement irq_mask_noflush() for MSI-x
drivers/irqchip/irq-msi-lib.c | 1 +
drivers/pci/msi/irqdomain.c | 6 ++++++
drivers/pci/msi/msi.h | 10 ++++++++--
include/linux/interrupt.h | 1 +
include/linux/irq.h | 3 +++
kernel/irq/chip.c | 31 +++++++++++++++++++++++++++++++
kernel/irq/internals.h | 1 +
kernel/irq/manage.c | 21 +++++++++++++++++++++
8 files changed, 72 insertions(+), 2 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/2] genirq: Add disable_irq_noflush()
2026-11-05 19:15 [PATCH v2 0/2] genirq, PCI/MSI: mask without flushing Keith Busch
@ 2026-11-05 19:15 ` Keith Busch
2026-11-05 19:15 ` [PATCH v2 2/2] PCI/MSI: Implement irq_mask_noflush() for MSI-x Keith Busch
1 sibling, 0 replies; 3+ messages in thread
From: Keith Busch @ 2026-11-05 19:15 UTC (permalink / raw)
To: tglx, bhelgaas
Cc: maz, lrizzo, hch, changfengnan, namjain, linux-kernel, linux-pci,
linux-nvme, Keith Busch
From: Keith Busch <kbusch@kernel.org>
Drivers that switch between interrupt and polled completions don't need
the mask write flushed to the device; the disabled state already
keeps their handler from running and a late interrupt is replayed on
enable. Add an optional irq_chip::irq_mask_noflush() callback, falling
back to irq_mask(), and disable_irq_noflush() to mask through it right
away rather than lazily.
Suggested-by: Thomas Gleixner <tglx@kernel.org>
Signed-off-by: Keith Busch <kbusch@kernel.org>
---
include/linux/interrupt.h | 1 +
include/linux/irq.h | 3 +++
kernel/irq/chip.c | 31 +++++++++++++++++++++++++++++++
kernel/irq/internals.h | 1 +
kernel/irq/manage.c | 21 +++++++++++++++++++++
5 files changed, 57 insertions(+)
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
index ee2dd5349697d..6cfa1f8f3d400 100644
--- a/include/linux/interrupt.h
+++ b/include/linux/interrupt.h
@@ -233,6 +233,7 @@ extern void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id);
bool irq_has_action(unsigned int irq);
extern void disable_irq_nosync(unsigned int irq);
+extern void disable_irq_noflush(unsigned int irq);
extern bool disable_hardirq(unsigned int irq);
extern void disable_irq(unsigned int irq);
extern void disable_percpu_irq(unsigned int irq);
diff --git a/include/linux/irq.h b/include/linux/irq.h
index f485369b1b4f7..3a428dad6666e 100644
--- a/include/linux/irq.h
+++ b/include/linux/irq.h
@@ -455,6 +455,8 @@ static inline irq_hw_number_t irqd_to_hwirq(struct irq_data *d)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
+ * @irq_mask_noflush: mask an interrupt source without flushing the write
+ * to the hardware (optional, falls back to @irq_mask)
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
@@ -505,6 +507,7 @@ struct irq_chip {
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
+ void (*irq_mask_noflush)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c
index de754db414d1d..28285d7274e69 100644
--- a/kernel/irq/chip.c
+++ b/kernel/irq/chip.c
@@ -399,6 +399,37 @@ void irq_disable(struct irq_desc *desc)
__irq_disable(desc, irq_settings_disable_unlazy(desc));
}
+static void mask_irq_noflush(struct irq_desc *desc)
+{
+ if (irqd_irq_masked(&desc->irq_data))
+ return;
+
+ if (desc->irq_data.chip->irq_mask_noflush) {
+ desc->irq_data.chip->irq_mask_noflush(&desc->irq_data);
+ irq_state_set_masked(desc);
+ } else {
+ mask_irq(desc);
+ }
+}
+
+/**
+ * irq_disable_noflush - Mark interrupt disabled and mask it without flushing
+ * @desc: irq descriptor which should be disabled
+ *
+ * Like irq_disable() with lazy disable turned off, but the hardware may
+ * still deliver an interrupt after this returns. The flow handler deals
+ * with that the same way it does for a lazily disabled interrupt.
+ */
+void irq_disable_noflush(struct irq_desc *desc)
+{
+ if (desc->irq_data.chip->irq_disable) {
+ __irq_disable(desc, true);
+ return;
+ }
+ irq_state_set_disabled(desc);
+ mask_irq_noflush(desc);
+}
+
void irq_percpu_enable(struct irq_desc *desc, unsigned int cpu)
{
if (desc->irq_data.chip->irq_enable)
diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h
index 703a062be8783..7995970babb77 100644
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -101,6 +101,7 @@ extern void irq_startup_managed(struct irq_desc *desc);
extern void irq_shutdown(struct irq_desc *desc);
extern void irq_shutdown_and_deactivate(struct irq_desc *desc);
extern void irq_disable(struct irq_desc *desc);
+extern void irq_disable_noflush(struct irq_desc *desc);
extern void irq_percpu_enable(struct irq_desc *desc, unsigned int cpu);
extern void irq_percpu_disable(struct irq_desc *desc, unsigned int cpu);
extern void mask_irq(struct irq_desc *desc);
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index ec3ecb0e48cec..152dabea3102b 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -706,6 +706,27 @@ void disable_irq_nosync(unsigned int irq)
}
EXPORT_SYMBOL(disable_irq_nosync);
+/**
+ * disable_irq_noflush - mask an irq without flushing the mask to the hardware
+ * @irq: Interrupt to disable
+ *
+ * Like disable_irq_nosync(), but masks the interrupt at the chip right away
+ * instead of lazily, and does not flush the mask write to the hardware. An
+ * interrupt that still arrives is held pending and replayed by enable_irq().
+ * Use this when an extra interrupt is cheaper than flushing the mask, e.g.
+ * switching from interrupts to polling.
+ *
+ * This function may be called from IRQ context.
+ */
+void disable_irq_noflush(unsigned int irq)
+{
+ scoped_irqdesc_get_and_buslock(irq, IRQ_GET_DESC_CHECK_GLOBAL) {
+ if (!scoped_irqdesc->depth++)
+ irq_disable_noflush(scoped_irqdesc);
+ }
+}
+EXPORT_SYMBOL_GPL(disable_irq_noflush);
+
/**
* disable_irq - disable an irq and wait for completion
* @irq: Interrupt to disable
--
2.52.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 2/2] PCI/MSI: Implement irq_mask_noflush() for MSI-x
2026-11-05 19:15 [PATCH v2 0/2] genirq, PCI/MSI: mask without flushing Keith Busch
2026-11-05 19:15 ` [PATCH v2 1/2] genirq: Add disable_irq_noflush() Keith Busch
@ 2026-11-05 19:15 ` Keith Busch
1 sibling, 0 replies; 3+ messages in thread
From: Keith Busch @ 2026-11-05 19:15 UTC (permalink / raw)
To: tglx, bhelgaas
Cc: maz, lrizzo, hch, changfengnan, namjain, linux-kernel, linux-pci,
linux-nvme, Keith Busch
From: Keith Busch <kbusch@kernel.org>
Skip the read-back that flushes the MSI-x mask write. Parent domains
that mask on behalf of PCI/MSI keep using their own irq_mask().
Suggested-by: Thomas Gleixner <tglx@kernel.org>
Signed-off-by: Keith Busch <kbusch@kernel.org>
---
drivers/irqchip/irq-msi-lib.c | 1 +
drivers/pci/msi/irqdomain.c | 6 ++++++
drivers/pci/msi/msi.h | 10 ++++++++--
3 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c
index 45e0ed3134ce1..97bc95d592122 100644
--- a/drivers/irqchip/irq-msi-lib.c
+++ b/drivers/irqchip/irq-msi-lib.c
@@ -126,6 +126,7 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
*/
if (info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT) {
chip->irq_mask = irq_chip_mask_parent;
+ chip->irq_mask_noflush = NULL;
chip->irq_unmask = irq_chip_unmask_parent;
}
diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c
index 6e65f0f44112e..2b9a5a0c305fb 100644
--- a/drivers/pci/msi/irqdomain.c
+++ b/drivers/pci/msi/irqdomain.c
@@ -162,6 +162,11 @@ static void pci_irq_mask_msix(struct irq_data *data)
pci_msix_mask(irq_data_get_msi_desc(data));
}
+static void pci_irq_mask_noflush_msix(struct irq_data *data)
+{
+ pci_msix_mask_noflush(irq_data_get_msi_desc(data));
+}
+
static void pci_irq_unmask_msix(struct irq_data *data)
{
pci_msix_unmask(irq_data_get_msi_desc(data));
@@ -182,6 +187,7 @@ static const struct msi_domain_template pci_msix_template = {
.irq_startup = pci_irq_startup_msix,
.irq_shutdown = pci_irq_shutdown_msix,
.irq_mask = pci_irq_mask_msix,
+ .irq_mask_noflush = pci_irq_mask_noflush_msix,
.irq_unmask = pci_irq_unmask_msix,
.irq_write_msi_msg = pci_msi_domain_write_msg,
.flags = IRQCHIP_ONESHOT_SAFE,
diff --git a/drivers/pci/msi/msi.h b/drivers/pci/msi/msi.h
index f987cf897264e..c40d24e5ca901 100644
--- a/drivers/pci/msi/msi.h
+++ b/drivers/pci/msi/msi.h
@@ -44,14 +44,20 @@ static inline void pci_msix_write_vector_ctrl(struct msi_desc *desc, u32 ctrl)
writel(ctrl, desc_addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
}
-static inline void pci_msix_mask(struct msi_desc *desc)
+/* Posted write only: the device may still send an interrupt after return */
+static inline void pci_msix_mask_noflush(struct msi_desc *desc)
{
desc->pci.msix_ctrl |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
+ pci_msix_write_vector_ctrl(desc, desc->pci.msix_ctrl);
+}
+
+static inline void pci_msix_mask(struct msi_desc *desc)
+{
+ pci_msix_mask_noflush(desc);
if (pci_channel_offline(msi_desc_to_pci_dev(desc)))
return;
- pci_msix_write_vector_ctrl(desc, desc->pci.msix_ctrl);
/* Flush write to device */
readl(desc->pci.mask_base);
}
--
2.52.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-05 19:19 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 19:15 [PATCH v2 0/2] genirq, PCI/MSI: mask without flushing Keith Busch
2026-11-05 19:15 ` [PATCH v2 1/2] genirq: Add disable_irq_noflush() Keith Busch
2026-11-05 19:15 ` [PATCH v2 2/2] PCI/MSI: Implement irq_mask_noflush() for MSI-x Keith Busch
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®