[PATCH 2/3] media: hws: quiesce interrupts without disabling shared IRQ

From: Ben Hoff

Date: Mon Sep 14 2026 - 21:04:29 EST


HWS requests its legacy interrupt with IRQF_SHARED, but suspend, shutdown,
and removal call disable_irq() on the shared descriptor. This prevents
other devices on the line from being serviced, and removal leaves the
IRQ disable unbalanced.

Mask the HWS interrupt gate, flush the write, and synchronize the handler.
Publish suspended state before draining so callbacks caused by a peer on
the shared line return without accessing HWS registers after suspend.

Keep capture-core initialization from opening the interrupt gate. Resume
restores the core and clears pending causes before publishing live state
and unmasking the device-local gate.

Fixes: ba07fd2f5742 ("media: pci: add AVMatrix HWS capture driver")
Assisted-by: Codex:GPT-6
Signed-off-by: Ben Hoff <hoff.benjamin.k@xxxxxxxxx>
---
drivers/media/pci/hws/hws_irq.c | 12 +++---------
drivers/media/pci/hws/hws_pci.c | 27 +++++++++++++++++----------
drivers/media/pci/hws/hws_video.c | 8 ++------
3 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/drivers/media/pci/hws/hws_irq.c b/drivers/media/pci/hws/hws_irq.c
index 787c9e498799..8d883663617b 100644
--- a/drivers/media/pci/hws/hws_irq.c
+++ b/drivers/media/pci/hws/hws_irq.c
@@ -150,15 +150,9 @@ irqreturn_t hws_irq_handler(int irq, void *info)
struct hws_pcie_dev *pdx = info;
u32 int_state;

- /* Fast path: if suspended, quietly ack and exit */
- if (READ_ONCE(pdx->suspended)) {
- int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
- if (int_state) {
- writel(int_state, pdx->bar0_base + HWS_REG_INT_STATUS);
- (void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
- }
- return int_state ? IRQ_HANDLED : IRQ_NONE;
- }
+ if (!pdx || READ_ONCE(pdx->suspended) || !pdx->bar0_base)
+ return IRQ_NONE;
+
int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
if (!int_state || int_state == 0xFFFFFFFF) {
return IRQ_NONE;
diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c
index 65b32fac6d1a..c9397b13392a 100644
--- a/drivers/media/pci/hws/hws_pci.c
+++ b/drivers/media/pci/hws/hws_pci.c
@@ -325,14 +325,21 @@ static void hws_irq_clear_pending(struct hws_pcie_dev *hws)
static void hws_block_hotpaths(struct hws_pcie_dev *hws)
{
WRITE_ONCE(hws->suspended, true);
- if (hws->irq >= 0)
- disable_irq(hws->irq);
+ /* Publish the stop state before a racing handler can enter MMIO. */
+ smp_mb();

- if (!hws->bar0_base)
- return;
+ if (hws->bar0_base)
+ hws_irq_mask_gate(hws);

- hws_irq_mask_gate(hws);
- hws_irq_clear_pending(hws);
+ /*
+ * Do not disable the shared descriptor. Wait for any invocation of this
+ * handler that raced with the device-local gate instead.
+ */
+ if (hws->irq >= 0)
+ synchronize_irq(hws->irq);
+
+ if (hws->bar0_base)
+ hws_irq_clear_pending(hws);
}

static int hws_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
@@ -661,11 +668,11 @@ static int hws_pm_resume(struct device *dev)
hws_init_video_sys(hws, true);
hws_irq_clear_pending(hws);

- /* IRQs can be re-enabled now that MMIO is sane */
- if (hws->irq >= 0)
- enable_irq(hws->irq);
-
+ /* Make our handler live before reopening only this device's IRQ gate. */
WRITE_ONCE(hws->suspended, false);
+ /* Publish the live state before the device can raise another interrupt. */
+ smp_mb();
+ hws_irq_unmask_gate(hws);

/* vb2: nothing mandatory; userspace will STREAMON again when ready */
hws_video_pm_resume(hws);
diff --git a/drivers/media/pci/hws/hws_video.c b/drivers/media/pci/hws/hws_video.c
index 624c48a63b3b..bdbce09ec3e6 100644
--- a/drivers/media/pci/hws/hws_video.c
+++ b/drivers/media/pci/hws/hws_video.c
@@ -565,7 +565,7 @@ static void hws_ack_all_irqs(struct hws_pcie_dev *hws)
}
}

-static void hws_open_irq_fabric(struct hws_pcie_dev *hws)
+static void hws_configure_irq_fabric(struct hws_pcie_dev *hws)
{
/* Route all sources to vector 0. */
writel(0x00000000, hws->bar0_base + PCIE_INT_DEC_REG_BASE);
@@ -574,10 +574,6 @@ static void hws_open_irq_fabric(struct hws_pcie_dev *hws)
/* Enable the PCIe bridge. */
writel(0x00000001, hws->bar0_base + PCIEBR_EN_REG_BASE);
(void)readl(hws->bar0_base + PCIEBR_EN_REG_BASE);
-
- /* Open the global/bridge gate (legacy 0x3FFFF) */
- writel(HWS_INT_EN_MASK, hws->bar0_base + INT_EN_REG_BASE);
- (void)readl(hws->bar0_base + INT_EN_REG_BASE);
}

void hws_init_video_sys(struct hws_pcie_dev *hws, bool enable)
@@ -604,7 +600,7 @@ void hws_init_video_sys(struct hws_pcie_dev *hws, bool enable)
writel(0x80FFFFFF, hws->bar0_base + HWS_REG_DEC_MODE);
writel(0x13, hws->bar0_base + HWS_REG_DEC_MODE);
hws_ack_all_irqs(hws);
- hws_open_irq_fabric(hws);
+ hws_configure_irq_fabric(hws);
/* 6) record that we're now running */
hws->start_run = true;
}