[PATCH v3] media: cx231xx-audio: drain wq_trigger before audio fini
From: Fan Wu
Date: Wed Jul 29 2026 - 03:39:56 EST
audio_trigger() is deferred work (dev->wq_trigger) armed from
snd_cx231xx_capture_trigger() on every PCM START/STOP; it dereferences
dev->adev state and may free the URBs via cx231xx_isoc_audio_deinit().
cx231xx_audio_fini() tore down that state without draining wq_trigger,
so work armed before or racing fini ran against freed state.
Use disable_work_sync() in fini to drain the work and prevent further
queueing. Initialize the work, lock and stream_started counter at the
top of cx231xx_audio_init(), before any fallible allocation, and clear
the partially-built audio state on its error path, so fini is safe even
if a later step fails.
This issue was found by an in-house static analysis tool.
Fixes: 61b04cb24a12 ("[media] cx231xx-audio: fix some locking issues")
Cc: stable@xxxxxxxxxxxxxxx # v6.10+
Link: https://lore.kernel.org/r/8c7e1294-b906-4636-890c-b64d03b0e1d0@xxxxxxxxxx
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@xxxxxxxxxx>
---
Changes in v2: use disable_work_sync() instead of a teardown flag +
cancel_work_sync(), as suggested by Hans Verkuil. The init-ordering
fix and error-path cleanup from v1 are retained.
Changes in v3: v2 was mailed with an unrelated ti/vpe diff in its body
(an internal packaging error); this version restores the intended
cx231xx-audio.c change. The commit message and trailers are unchanged
from v2.
---
drivers/media/usb/cx231xx/cx231xx-audio.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/media/usb/cx231xx/cx231xx-audio.c b/drivers/media/usb/cx231xx/cx231xx-audio.c
index 9c71b32552df..1179cb0334f4 100644
--- a/drivers/media/usb/cx231xx/cx231xx-audio.c
+++ b/drivers/media/usb/cx231xx/cx231xx-audio.c
@@ -576,12 +576,19 @@ static int cx231xx_audio_init(struct cx231xx *dev)
dev_dbg(dev->dev,
"probing for cx231xx non standard usbaudio\n");
+ /*
+ * Extension init errors are ignored by the cx231xx core, so fini()
+ * must be safe even if initialization fails part way through.
+ */
+ spin_lock_init(&adev->slock);
+ INIT_WORK(&dev->wq_trigger, audio_trigger);
+ atomic_set(&dev->stream_started, 0);
+
err = snd_card_new(dev->dev, index[devnr], "Cx231xx Audio",
THIS_MODULE, 0, &card);
if (err < 0)
return err;
- spin_lock_init(&adev->slock);
err = snd_pcm_new(card, "Cx231xx Audio", 0, 0, 1, &pcm);
if (err < 0)
goto err_free_card;
@@ -596,8 +603,6 @@ static int cx231xx_audio_init(struct cx231xx *dev)
strscpy(card->shortname, "Cx231xx Audio", sizeof(card->shortname));
strscpy(card->longname, "Conexant cx231xx Audio", sizeof(card->longname));
- INIT_WORK(&dev->wq_trigger, audio_trigger);
-
err = snd_card_register(card);
if (err < 0)
goto err_free_card;
@@ -651,8 +656,10 @@ static int cx231xx_audio_init(struct cx231xx *dev)
err_free_pkt_size:
kfree(adev->alt_max_pkt_size);
+ adev->alt_max_pkt_size = NULL;
err_free_card:
snd_card_free(card);
+ adev->sndcard = NULL;
return err;
}
@@ -669,6 +676,8 @@ static int cx231xx_audio_fini(struct cx231xx *dev)
return 0;
}
+ disable_work_sync(&dev->wq_trigger);
+
if (dev->adev.sndcard) {
snd_card_free_when_closed(dev->adev.sndcard);
kfree(dev->adev.alt_max_pkt_size);
--
2.34.1