* [PATCH v3 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown
@ 2026-05-01 10:22 Cássio Gabriel
2026-05-01 10:22 ` [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests Cássio Gabriel
2026-05-01 10:22 ` [PATCH v3 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind Cássio Gabriel
0 siblings, 2 replies; 5+ messages in thread
From: Cássio Gabriel @ 2026-05-01 10:22 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 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.
---
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
To: Luis Chamberlain <mcgrof@kernel.org>
To: Russ Weight <russ.weight@linux.dev>
To: Danilo Krummrich <dakr@kernel.org>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: "Rafael J. Wysocki" <rafael@kernel.org>
To: Takashi Iwai <tiwai@suse.com>
To: Shenghao Ding <shenghao-ding@ti.com>
To: Kevin Lu <kevin-lu@ti.com>
To: Baojun Xu <baojun.xu@ti.com>
To: Jaroslav Kysela <perex@perex.cz>
Cc: driver-core@lists.linux.dev
Cc: linux-kernel@vger.kernel.org
Cc: linux-sound@vger.kernel.org
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
Cássio Gabriel (2):
firmware_loader: Add cancel helper for async requests
ALSA: hda/tas2781: Cancel async firmware request at unbind
drivers/base/firmware_loader/main.c | 71 ++++++++++++++++++++++++--
include/linux/firmware.h | 10 ++++
sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 3 ++
sound/hda/codecs/side-codecs/tas2781_hda_spi.c | 3 ++
4 files changed, 83 insertions(+), 4 deletions(-)
---
base-commit: 1bc46462f4c09f8d429ae8ec17f92886d604659f
change-id: 20260421-alsa-hda-tas2781-fw-callback-teardown-3b76404bb928
Best regards,
--
Cássio Gabriel <cassiogabrielcontato@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests 2026-05-01 10:22 [PATCH v3 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Cássio Gabriel @ 2026-05-01 10:22 ` Cássio Gabriel 2026-05-04 15:25 ` Takashi Iwai 2026-05-01 10:22 ` [PATCH v3 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind Cássio Gabriel 1 sibling, 1 reply; 5+ messages in thread From: Cássio Gabriel @ 2026-05-01 10:22 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 | 71 ++++++++++++++++++++++++++++++++++--- include/linux/firmware.h | 10 ++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index a11b30dda23b..bd890f103b2f 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,21 +1141,37 @@ 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); +} + static void request_firmware_work_func(struct work_struct *work) { struct firmware_work *fw_work; const struct firmware *fw; + unsigned long flags; fw_work = container_of(work, struct firmware_work, 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_irqsave(&firmware_work_lock, flags); + if (!list_empty(&fw_work->list)) { + list_del_init(&fw_work->list); + spin_unlock_irqrestore(&firmware_work_lock, flags); + firmware_work_free(fw_work); + kfree(fw_work); + return; + } + spin_unlock_irqrestore(&firmware_work_lock, flags); } @@ -1164,6 +1181,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 +1214,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 +1282,46 @@ 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; + unsigned long flags; + + spin_lock_irqsave(&firmware_work_lock, flags); + 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_irqrestore(&firmware_work_lock, flags); + + if (!fw_work) + return; + cancel_work_sync(&fw_work->work); + firmware_work_free(fw_work); + kfree(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] 5+ messages in thread
* Re: [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests 2026-05-01 10:22 ` [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests Cássio Gabriel @ 2026-05-04 15:25 ` Takashi Iwai 2026-05-04 22:40 ` Cássio Gabriel Monteiro Pires 0 siblings, 1 reply; 5+ messages in thread From: Takashi Iwai @ 2026-05-04 15:25 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 On Fri, 01 May 2026 12:22:12 +0200, Cássio Gabriel wrote: > +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); > +} > + > static void request_firmware_work_func(struct work_struct *work) > { > struct firmware_work *fw_work; > const struct firmware *fw; > + unsigned long flags; > > fw_work = container_of(work, struct firmware_work, 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_irqsave(&firmware_work_lock, flags); This is a sleepable context, so you can use spin_lock_irq(). > + if (!list_empty(&fw_work->list)) { > + list_del_init(&fw_work->list); > + spin_unlock_irqrestore(&firmware_work_lock, flags); > + firmware_work_free(fw_work); > + kfree(fw_work); Can kfree(fw_work) be in firmware_work_free(), too? All callers free it in anyway. (snip) > +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; > + unsigned long flags; > + > + spin_lock_irqsave(&firmware_work_lock, flags); Here you can use spin_lock_irq() as well. thanks, Takashi ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests 2026-05-04 15:25 ` Takashi Iwai @ 2026-05-04 22:40 ` Cássio Gabriel Monteiro Pires 0 siblings, 0 replies; 5+ messages in thread From: Cássio Gabriel Monteiro Pires @ 2026-05-04 22:40 UTC (permalink / raw) To: Takashi Iwai 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 [-- Attachment #1.1: Type: text/plain, Size: 2329 bytes --] On 5/4/26 12:25, Takashi Iwai wrote: > On Fri, 01 May 2026 12:22:12 +0200, > Cássio Gabriel wrote: >> +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); >> +} >> + >> static void request_firmware_work_func(struct work_struct *work) >> { >> struct firmware_work *fw_work; >> const struct firmware *fw; >> + unsigned long flags; >> >> fw_work = container_of(work, struct firmware_work, 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_irqsave(&firmware_work_lock, flags); > > This is a sleepable context, so you can use spin_lock_irq(). Thanks, I'll fold those in v4. > >> + if (!list_empty(&fw_work->list)) { >> + list_del_init(&fw_work->list); >> + spin_unlock_irqrestore(&firmware_work_lock, flags); >> + firmware_work_free(fw_work); >> + kfree(fw_work); > > Can kfree(fw_work) be in firmware_work_free(), too? All callers free > it in anyway. The worker and cancel paths are both sleepable contexts, so we can switch those two sites from spin_lock_irqsave() to spin_lock_irq(). I think we need to keep irqsave in _request_firmware_nowait(), since that path still supports GFP_ATOMIC callers and should not assume IRQs are enabled. > > (snip) >> +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; >> + unsigned long flags; >> + >> + spin_lock_irqsave(&firmware_work_lock, flags); > > Here you can use spin_lock_irq() as well. Thanks, I'll change it in v4. We can also move kfree(fw_work) into firmware_work_free(), since the successful completion and cancel paths both free the object after releasing the device, module and firmware-name references. > thanks, > > Takashi Thanks, Cássio [-- Attachment #2: OpenPGP digital signature --] [-- Type: application/pgp-signature, Size: 236 bytes --] ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind 2026-05-01 10:22 [PATCH v3 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Cássio Gabriel 2026-05-01 10:22 ` [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests Cássio Gabriel @ 2026-05-01 10:22 ` Cássio Gabriel 1 sibling, 0 replies; 5+ messages in thread From: Cássio Gabriel @ 2026-05-01 10:22 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] 5+ messages in thread
end of thread, other threads:[~2026-05-04 22:40 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-01 10:22 [PATCH v3 0/2] firmware_loader/ALSA: Fix TAS2781 async firmware teardown Cássio Gabriel 2026-05-01 10:22 ` [PATCH v3 1/2] firmware_loader: Add cancel helper for async requests Cássio Gabriel 2026-05-04 15:25 ` Takashi Iwai 2026-05-04 22:40 ` Cássio Gabriel Monteiro Pires 2026-05-01 10:22 ` [PATCH v3 2/2] ALSA: hda/tas2781: Cancel async firmware request at unbind Cássio Gabriel
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox