[PATCH] ALSA: usb-audio: Release components on probe errors

From: Cen Zhang

Date: Thu Jun 18 2026 - 13:03:58 EST


usb_audio_probe() can create USB-audio component resources before the
first interface is recorded in chip->num_interfaces. If a later probe
step fails, the error path drops chip->active and frees the card directly
when no interfaces have been registered.

Normal disconnect first releases USB-audio components in a fixed order
before the card is freed. The first-interface probe error path skipped
that sequence, so partially initialized PCM, endpoint, MIDI, media, or
mixer resources could be left for the card private_free path without their
disconnect handling having run.

Move the existing normal-disconnect component release sequence into a
helper and call it from the zero-interface probe error path before
snd_card_free(). Keep the normal disconnect ordering unchanged: PCM
streams, endpoint resources, MIDI 1.0 resources, MIDI 2.0 resources, media
device cleanup, then mixer resources.

Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
sound/usb/card.c | 70 ++++++++++++++++++++++++++----------------------
1 file changed, 38 insertions(+), 32 deletions(-)

diff --git a/sound/usb/card.c b/sound/usb/card.c
index 6a3b576fb067..b36f513dccb9 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -905,6 +905,40 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum)
return 0;
}

+static void usb_audio_disconnect_components(struct snd_usb_audio *chip)
+{
+ struct snd_usb_stream *as;
+ struct snd_usb_endpoint *ep;
+ struct usb_mixer_interface *mixer;
+ struct list_head *p;
+
+ /* release the pcm resources */
+ list_for_each_entry(as, &chip->pcm_list, list) {
+ snd_usb_stream_disconnect(as);
+ }
+ /* release the endpoint resources */
+ list_for_each_entry(ep, &chip->ep_list, list) {
+ snd_usb_endpoint_release(ep);
+ }
+ /* release the midi resources */
+ list_for_each(p, &chip->midi_list) {
+ snd_usbmidi_disconnect(p);
+ }
+ snd_usb_midi_v2_disconnect_all(chip);
+ /*
+ * Nice to check quirk && quirk->shares_media_device and
+ * then call the snd_media_device_delete(). Don't have
+ * access to the quirk here. snd_media_device_delete()
+ * accesses mixer_list
+ */
+ snd_media_device_delete(chip);
+
+ /* release mixer resources */
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
+ snd_usb_mixer_disconnect(mixer);
+ }
+}
+
/*
* probe the active usb device
*
@@ -1077,8 +1111,10 @@ static int usb_audio_probe(struct usb_interface *intf,
* decrement before memory is possibly returned.
*/
atomic_dec(&chip->active);
- if (!chip->num_interfaces)
+ if (!chip->num_interfaces) {
+ usb_audio_disconnect_components(chip);
snd_card_free(chip->card);
+ }
}
return err;
}
@@ -1091,48 +1127,18 @@ static bool __usb_audio_disconnect(struct usb_interface *intf,
struct snd_usb_audio *chip,
struct snd_card *card)
{
- struct list_head *p;
-
guard(mutex)(&register_mutex);

if (platform_ops && platform_ops->disconnect_cb)
platform_ops->disconnect_cb(chip);

if (atomic_inc_return(&chip->shutdown) == 1) {
- struct snd_usb_stream *as;
- struct snd_usb_endpoint *ep;
- struct usb_mixer_interface *mixer;
-
/* wait until all pending tasks done;
* they are protected by snd_usb_lock_shutdown()
*/
snd_refcount_sync(&chip->usage_count);
snd_card_disconnect(card);
- /* release the pcm resources */
- list_for_each_entry(as, &chip->pcm_list, list) {
- snd_usb_stream_disconnect(as);
- }
- /* release the endpoint resources */
- list_for_each_entry(ep, &chip->ep_list, list) {
- snd_usb_endpoint_release(ep);
- }
- /* release the midi resources */
- list_for_each(p, &chip->midi_list) {
- snd_usbmidi_disconnect(p);
- }
- snd_usb_midi_v2_disconnect_all(chip);
- /*
- * Nice to check quirk && quirk->shares_media_device and
- * then call the snd_media_device_delete(). Don't have
- * access to the quirk here. snd_media_device_delete()
- * accesses mixer_list
- */
- snd_media_device_delete(chip);
-
- /* release mixer resources */
- list_for_each_entry(mixer, &chip->mixer_list, list) {
- snd_usb_mixer_disconnect(mixer);
- }
+ usb_audio_disconnect_components(chip);
}

if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
--
2.43.0