[PATCH 3/3] media: hws: serialize video quiesce with queue state

From: Ben Hoff

Date: Mon Sep 14 2026 - 21:06:28 EST


Suspend and shutdown call vb2_streamoff() without taking the state mutex
used by the video device and its vb2 queue. This can race userspace queue
operations and violates the locking requirement in hws_stop_streaming().

Hold the channel state mutex around the streaming check and streamoff.
Serialize monitor passes with lifecycle quiescence, and recheck suspended
state after acquiring the monitor mutex so a delayed pass cannot enter
hardware access after teardown has drained it.

Reject readiness checks once suspension begins. If the core is not ready,
return an error instead of resetting shared hardware while another
channel may still own capture buffers.

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.h | 2 ++
drivers/media/pci/hws/hws_pci.c | 11 +++++++++--
drivers/media/pci/hws/hws_video.c | 11 +++++++++--
3 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/drivers/media/pci/hws/hws.h b/drivers/media/pci/hws/hws.h
index d87d52674b69..01a6b00dcca6 100644
--- a/drivers/media/pci/hws/hws.h
+++ b/drivers/media/pci/hws/hws.h
@@ -8,6 +8,7 @@
#include <linux/kthread.h>
#include <linux/pci.h>
#include <linux/list.h>
+#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/sizes.h>
#include <linux/atomic.h>
@@ -161,6 +162,7 @@ struct hws_pcie_dev {

/* Kernel thread */
struct task_struct *main_task;
+ struct mutex monitor_lock; /* serializes monitor and lifecycle changes */
struct hws_scratch_dma scratch_vid[MAX_VID_CHANNELS];

bool suspended;
diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c
index c9397b13392a..7fdb1087d247 100644
--- a/drivers/media/pci/hws/hws_pci.c
+++ b/drivers/media/pci/hws/hws_pci.c
@@ -177,8 +177,10 @@ static int main_ks_thread_handle(void *data)
continue;
}

- /* avoid MMIO when suspended (guarded above) */
- check_video_format(pdx);
+ mutex_lock(&pdx->monitor_lock);
+ if (!READ_ONCE(pdx->suspended))
+ check_video_format(pdx);
+ mutex_unlock(&pdx->monitor_lock);

try_to_freeze(); /* cooperate with freezer each loop */

@@ -338,6 +340,10 @@ static void hws_block_hotpaths(struct hws_pcie_dev *hws)
if (hws->irq >= 0)
synchronize_irq(hws->irq);

+ /* Wait for a monitor pass that started before suspended was set. */
+ mutex_lock(&hws->monitor_lock);
+ mutex_unlock(&hws->monitor_lock);
+
if (hws->bar0_base)
hws_irq_clear_pending(hws);
}
@@ -357,6 +363,7 @@ static int hws_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
hws->pdev = pdev;
hws->irq = -1;
hws->suspended = false;
+ mutex_init(&hws->monitor_lock);
pci_set_drvdata(pdev, hws);

/* 1) Enable device + bus mastering (managed) */
diff --git a/drivers/media/pci/hws/hws_video.c b/drivers/media/pci/hws/hws_video.c
index bdbce09ec3e6..8e029b71b5b5 100644
--- a/drivers/media/pci/hws/hws_video.c
+++ b/drivers/media/pci/hws/hws_video.c
@@ -611,6 +611,8 @@ int hws_check_card_status(struct hws_pcie_dev *hws)

if (!hws || !hws->bar0_base)
return -ENODEV;
+ if (READ_ONCE(hws->suspended))
+ return -EBUSY;

status = readl(hws->bar0_base + HWS_REG_SYS_STATUS);

@@ -621,9 +623,12 @@ int hws_check_card_status(struct hws_pcie_dev *hws)
return -ENODEV;
}

- /* If RUN/READY bit (bit0) is not set, reinitialize the video core. */
+ /* Runtime reset would invalidate every active channel's DMA ownership. */
if (!(status & BIT(0))) {
- hws_init_video_sys(hws, true);
+ dev_warn_ratelimited(&hws->pdev->dev,
+ "SYS_STATUS not ready (0x%08x); runtime core reset refused\n",
+ status);
+ return -EIO;
}

return 0;
@@ -1349,6 +1354,7 @@ int hws_video_quiesce(struct hws_pcie_dev *hws, const char *reason)
continue;
}

+ mutex_lock(&vid->state_lock);
streaming = vb2_is_streaming(q);
if (streaming) {
/* Stop via vb2, which runs .stop_streaming. */
@@ -1357,6 +1363,7 @@ int hws_video_quiesce(struct hws_pcie_dev *hws, const char *reason)
if (r && !ret)
ret = r;
}
+ mutex_unlock(&vid->state_lock);
}
return ret;
}