* [PATCH v4 1/2] firmware_loader: Add cancel helper for async requests
2026-05-05 11:18 [PATCH v4 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Cássio Gabriel
@ 2026-05-05 11:18 ` Cássio Gabriel
2026-05-05 11:18 ` [PATCH v4 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind Cássio Gabriel
2026-05-06 8:05 ` [PATCH v4 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Takashi Iwai
2 siblings, 0 replies; 6+ messages in thread
From: Cássio Gabriel @ 2026-05-05 11:18 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Danilo Krummrich,
Greg Kroah-Hartman, Rafael J. Wysocki, Takashi Iwai,
Shenghao Ding, Kevin Lu, Baojun Xu, Jaroslav Kysela
Cc: driver-core, linux-kernel, linux-sound, Cássio Gabriel
request_firmware_nowait() keeps the callback module pinned and holds
a device reference until the firmware work completes.
Callers still have no way to cancel or synchronize the queued callback
before tearing down their driver-private state.
Track scheduled async firmware work in an internal list and add
request_firmware_nowait_cancel(). The helper cancels work matching the
device, callback context and callback function. It cancels work that has
not started yet and waits for an already-running callback to return. If
the request has already completed, it is a no-op.
Keep the existing request_firmware_nowait() lifetime model manual. A
devres-managed variant can be layered on top separately if needed.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
drivers/base/firmware_loader/main.c | 68 ++++++++++++++++++++++++++++++++++---
include/linux/firmware.h | 10 ++++++
2 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index a11b30dda23b..78c5f05a2ec1 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -1132,6 +1132,7 @@ EXPORT_SYMBOL(release_firmware);
/* Async support */
struct firmware_work {
struct work_struct work;
+ struct list_head list;
struct module *module;
const char *name;
struct device *device;
@@ -1140,6 +1141,17 @@ struct firmware_work {
u32 opt_flags;
};
+static LIST_HEAD(firmware_work_list);
+static DEFINE_SPINLOCK(firmware_work_lock);
+
+static void firmware_work_free(struct firmware_work *fw_work)
+{
+ put_device(fw_work->device); /* taken in request_firmware_nowait() */
+ module_put(fw_work->module);
+ kfree_const(fw_work->name);
+ kfree(fw_work);
+}
+
static void request_firmware_work_func(struct work_struct *work)
{
struct firmware_work *fw_work;
@@ -1150,11 +1162,15 @@ static void request_firmware_work_func(struct work_struct *work)
_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 0,
fw_work->opt_flags);
fw_work->cont(fw, fw_work->context);
- put_device(fw_work->device); /* taken in request_firmware_nowait() */
- module_put(fw_work->module);
- kfree_const(fw_work->name);
- kfree(fw_work);
+ spin_lock_irq(&firmware_work_lock);
+ if (!list_empty(&fw_work->list)) {
+ list_del_init(&fw_work->list);
+ spin_unlock_irq(&firmware_work_lock);
+ firmware_work_free(fw_work);
+ return;
+ }
+ spin_unlock_irq(&firmware_work_lock);
}
@@ -1164,6 +1180,7 @@ static int _request_firmware_nowait(
void (*cont)(const struct firmware *fw, void *context), bool nowarn)
{
struct firmware_work *fw_work;
+ unsigned long flags;
fw_work = kzalloc_obj(struct firmware_work, gfp);
if (!fw_work)
@@ -1196,7 +1213,12 @@ static int _request_firmware_nowait(
get_device(fw_work->device);
INIT_WORK(&fw_work->work, request_firmware_work_func);
+
+ spin_lock_irqsave(&firmware_work_lock, flags);
+ list_add_tail(&fw_work->list, &firmware_work_list);
schedule_work(&fw_work->work);
+ spin_unlock_irqrestore(&firmware_work_lock, flags);
+
return 0;
}
@@ -1259,6 +1281,44 @@ int firmware_request_nowait_nowarn(
}
EXPORT_SYMBOL_GPL(firmware_request_nowait_nowarn);
+/**
+ * request_firmware_nowait_cancel() - cancel an async firmware request
+ * @device: device for which the firmware is being loaded
+ * @context: context passed to request_firmware_nowait()
+ * @cont: callback passed to request_firmware_nowait()
+ *
+ * Cancel a pending request_firmware_nowait() request for @device, @context
+ * and @cont. If the associated work has already started, this function waits
+ * until the callback has returned. If the callback has already completed, this
+ * function does nothing.
+ *
+ * This function may sleep.
+ */
+void request_firmware_nowait_cancel(struct device *device, void *context,
+ void (*cont)(const struct firmware *fw,
+ void *context))
+{
+ struct firmware_work *fw_work = NULL;
+ struct firmware_work *tmp;
+
+ spin_lock_irq(&firmware_work_lock);
+ list_for_each_entry_reverse(tmp, &firmware_work_list, list) {
+ if (tmp->device == device && tmp->context == context &&
+ tmp->cont == cont) {
+ fw_work = tmp;
+ list_del_init(&fw_work->list);
+ break;
+ }
+ }
+ spin_unlock_irq(&firmware_work_lock);
+
+ if (!fw_work)
+ return;
+ cancel_work_sync(&fw_work->work);
+ firmware_work_free(fw_work);
+}
+EXPORT_SYMBOL_GPL(request_firmware_nowait_cancel);
+
#ifdef CONFIG_FW_CACHE
static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index aae1b85ffc10..0fa3b027f02f 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -110,6 +110,9 @@ int request_firmware_nowait(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context));
+void request_firmware_nowait_cancel(struct device *device, void *context,
+ void (*cont)(const struct firmware *fw,
+ void *context));
int request_firmware_direct(const struct firmware **fw, const char *name,
struct device *device);
int request_firmware_into_buf(const struct firmware **firmware_p,
@@ -157,6 +160,13 @@ static inline int request_firmware_nowait(
return -EINVAL;
}
+static inline void request_firmware_nowait_cancel(struct device *device,
+ void *context,
+ void (*cont)(const struct firmware *fw,
+ void *context))
+{
+}
+
static inline void release_firmware(const struct firmware *fw)
{
}
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v4 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind
2026-05-05 11:18 [PATCH v4 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Cássio Gabriel
2026-05-05 11:18 ` [PATCH v4 1/2] firmware_loader: Add cancel helper for async requests Cássio Gabriel
@ 2026-05-05 11:18 ` Cássio Gabriel
2026-05-06 8:05 ` [PATCH v4 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Takashi Iwai
2 siblings, 0 replies; 6+ messages in thread
From: Cássio Gabriel @ 2026-05-05 11:18 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Danilo Krummrich,
Greg Kroah-Hartman, Rafael J. Wysocki, Takashi Iwai,
Shenghao Ding, Kevin Lu, Baojun Xu, Jaroslav Kysela
Cc: driver-core, linux-kernel, linux-sound, Cássio Gabriel,
stable
TAS2781 HDA I2C and SPI queue RCA firmware loading from component
bind with request_firmware_nowait(). The firmware loader keeps the
callback module pinned and holds a device reference, but the callback
still uses driver-private HDA state.
Component unbind removes controls and DSP state immediately. Later
device removal tears down the TAS2781 private data, including
codec_lock. If the async firmware callback runs after unbind has
started, it can operate on state that is being torn down.
Cancel or synchronize the async firmware request before removing
controls and DSP state. A queued callback is cancelled, and an
already-running callback is allowed to finish before unbind continues.
Fixes: 5be27f1e3ec9 ("ALSA: hda/tas2781: Add tas2781 HDA driver")
Fixes: bb5f86ea50ff ("ALSA: hda/tas2781: Add tas2781 hda SPI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 3 +++
sound/hda/codecs/side-codecs/tas2781_hda_spi.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
index 67240ce184e1..dd1b0cc63ad6 100644
--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
+++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
@@ -588,6 +588,9 @@ static void tas2781_hda_unbind(struct device *dev,
comp->playback_hook = NULL;
}
+ request_firmware_nowait_cancel(tas_hda->priv->dev, tas_hda->priv,
+ tasdev_fw_ready);
+
tas2781_hda_remove_controls(tas_hda);
tasdevice_config_info_remove(tas_hda->priv);
diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c
index 0e4f3553f273..d243baff95a7 100644
--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c
+++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c
@@ -750,6 +750,9 @@ static void tas2781_hda_unbind(struct device *dev, struct device *master,
comp->playback_hook = NULL;
}
+ request_firmware_nowait_cancel(tas_priv->dev, tas_priv,
+ tasdev_fw_ready);
+
tas2781_hda_remove_controls(tas_hda);
tasdevice_config_info_remove(tas_priv);
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v4 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown
2026-05-05 11:18 [PATCH v4 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Cássio Gabriel
2026-05-05 11:18 ` [PATCH v4 1/2] firmware_loader: Add cancel helper for async requests Cássio Gabriel
2026-05-05 11:18 ` [PATCH v4 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind Cássio Gabriel
@ 2026-05-06 8:05 ` Takashi Iwai
2026-05-06 9:34 ` Danilo Krummrich
2 siblings, 1 reply; 6+ messages in thread
From: Takashi Iwai @ 2026-05-06 8:05 UTC (permalink / raw)
To: Cássio Gabriel
Cc: Luis Chamberlain, Russ Weight, Danilo Krummrich,
Greg Kroah-Hartman, Rafael J. Wysocki, Takashi Iwai,
Shenghao Ding, Kevin Lu, Baojun Xu, Jaroslav Kysela, driver-core,
linux-kernel, linux-sound, stable
On Tue, 05 May 2026 13:18:15 +0200,
Cássio Gabriel wrote:
>
> TAS2781 HDA I2C and SPI queue RCA firmware loading with
> request_firmware_nowait() during component bind. The firmware loader
> keeps the callback module pinned and holds a device reference, but it
> does not provide a way for drivers to cancel or synchronize the queued
> callback before tearing down driver-private state.
>
> Add a small firmware-loader helper to cancel or synchronize async firmware
> requests, then use it from TAS2781 HDA unbind before controls and DSP state
> are removed.
>
> No hardware runtime test was available.
>
> Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
> ---
> Changes in v4:
> - Use spin_lock_irq() in the worker and cancel paths, which run from
> sleepable contexts.
> - Fold kfree(fw_work) into firmware_work_free().
> - Keep irqsave in the request path so GFP_ATOMIC callers do not depend on
> IRQ state assumptions.
> - Link to v3: https://patch.msgid.link/20260501-alsa-hda-tas2781-fw-callback-teardown-v3-0-8d9f873b97bd@gmail.com
>
> Changes in v3:
> - Keep request_firmware_nowait() manually managed instead of making the
> existing API implicitly devres-managed.
> - Track scheduled async firmware work in an internal list protected by a
> spinlock so request_firmware_nowait_cancel() can find and synchronize a
> pending request without weakening the GFP_ATOMIC caller contract.
> - Match pending requests by device, callback context and callback function
> instead of matching by callback alone.
> - Avoid the devres_add() / schedule_work() ordering race pointed out in
> review.
> - Leave devres-managed support for a separate devm_request_firmware_nowait()
> API if needed.
> - Link to v2: https://patch.msgid.link/20260430-alsa-hda-tas2781-fw-callback-teardown-v2-0-2c7d89cb3175@gmail.com
>
> Changes in v2:
> - Add request_firmware_nowait_cancel() in the firmware loader instead of
> tracking the callback lifetime locally in the TAS2781 HDA driver.
> - Keep the TAS2781 change to a cancel/sync call in I2C and SPI unbind.
> - Drop the unrelated cached kcontrol pointer cleanup from the previous
> local-driver version.
> - Link to v1: https://patch.msgid.link/20260430-alsa-hda-tas2781-fw-callback-teardown-v1-1-874367d6b41b@gmail.com
>
> ---
> Cássio Gabriel (2):
> firmware_loader: Add cancel helper for async requests
> ALSA: hda/tas2781: Cancel async firmware request at unbind
I guess this could go via driver tree? Or I can take both if I get an
ack, too.
In anyway, for the series:
Reviewed-by: Takashi Iwai <tiwai@suse.de>
thanks,
Takashi
^ permalink raw reply [flat|nested] 6+ messages in thread