[PATCH v4 1/5] ALSA: hda: Serialize codec probe and remove on the same bus

From: Cen Zhang

Date: Sat Oct 31 2026 - 01:15:04 EST


The probe and remove of different codecs on one HD-audio bus can run at
the same time, for example when two codecs are bound and unbound through
sysfs. A codec probe on a registered card calls snd_card_register(),
which walks card->devices in snd_device_register_all(), while the remove
of another codec frees its PCMs and deletes them from the same list.
The list has no lock of its own. The controller probe and the reconfig
sysfs knob also call snd_card_register() after the codecs are bound, so
a codec bound or unbound through sysfs at that time changes the list as
well.

Add a mutex to struct hdac_bus and hold it in hda_codec_driver_probe()
and hda_codec_driver_remove(). Add snd_hda_card_register(), which
registers the card of a bus under the same mutex, and use it in the
controller drivers and in reconfig_codec(). The ASoC ext_ops paths are
unchanged.

Fixes: bcd96557bd0a ("ALSA: hda - Build PCMs and controls at codec driver probe")
Suggested-by: Takashi Iwai <tiwai@xxxxxxx>
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
include/sound/hda_codec.h | 1 +
include/sound/hdaudio.h | 1 +
sound/hda/common/bind.c | 26 ++++++++++++++++++++++++++
sound/hda/common/sysfs.c | 2 +-
sound/hda/controllers/acpi.c | 2 +-
sound/hda/controllers/cix-ipbloq.c | 2 +-
sound/hda/controllers/intel.c | 2 +-
sound/hda/controllers/tegra.c | 2 +-
sound/hda/core/bus.c | 1 +
9 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h
index c7f29ad64e02..084f06488a95 100644
--- a/include/sound/hda_codec.h
+++ b/include/sound/hda_codec.h
@@ -318,6 +318,7 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card,
int snd_hda_codec_configure(struct hda_codec *codec);
int snd_hda_codec_update_widgets(struct hda_codec *codec);
void snd_hda_codec_register(struct hda_codec *codec);
+int snd_hda_card_register(struct hda_bus *bus);
void snd_hda_codec_unregister(struct hda_codec *codec);
void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec);

diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
index 04fd4794bcee..682e10a0f6db 100644
--- a/include/sound/hdaudio.h
+++ b/include/sound/hdaudio.h
@@ -367,6 +367,7 @@ struct hdac_bus {
spinlock_t reg_lock;
struct mutex cmd_mutex;
struct mutex lock;
+ struct mutex codec_bind_lock; /* codec driver bind, card register */

/* DRM component interface */
struct drm_audio_component *audio_component;
diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c
index 957feace0f58..de867f34f290 100644
--- a/sound/hda/common/bind.c
+++ b/sound/hda/common/bind.c
@@ -104,6 +104,12 @@ static int hda_codec_driver_probe(struct device *dev)
if (WARN_ON(!codec->preset))
return -EINVAL;

+ /*
+ * snd_card_register() below walks the device list of the card, which
+ * the removal of another codec changes when it frees its PCMs.
+ */
+ mutex_lock(&codec->bus->core.codec_bind_lock);
+
err = snd_hda_codec_set_name(codec, codec->preset->name);
if (err < 0)
goto error;
@@ -140,6 +146,7 @@ static int hda_codec_driver_probe(struct device *dev)
}

codec->core.lazy_cache = true;
+ mutex_unlock(&codec->bus->core.codec_bind_lock);
return 0;

error_module:
@@ -153,6 +160,7 @@ static int hda_codec_driver_probe(struct device *dev)
error:
snd_hda_codec_cleanup_for_unbind(codec);
codec->preset = NULL;
+ mutex_unlock(&codec->bus->core.codec_bind_lock);
return err;
}

@@ -171,6 +179,7 @@ static int hda_codec_driver_remove(struct device *dev)
return codec->bus->core.ext_ops->hdev_detach(&codec->core);
}

+ guard(mutex)(&codec->bus->core.codec_bind_lock);
snd_hda_codec_disconnect_pcms(codec);
snd_hda_jack_tbl_disconnect(codec);
snd_refcount_sync(&codec->pcm_ref);
@@ -184,6 +193,23 @@ static int hda_codec_driver_remove(struct device *dev)
return 0;
}

+/**
+ * snd_hda_card_register - register the card of an HD-audio bus
+ * @bus: HD-audio bus
+ *
+ * snd_card_register() walks the device list of the card, which codec driver
+ * probe and remove on the same bus change. Register the card with them
+ * excluded.
+ *
+ * Return: zero or a negative error code.
+ */
+int snd_hda_card_register(struct hda_bus *bus)
+{
+ guard(mutex)(&bus->core.codec_bind_lock);
+ return snd_card_register(bus->card);
+}
+EXPORT_SYMBOL_GPL(snd_hda_card_register);
+
static void hda_codec_driver_shutdown(struct device *dev)
{
snd_hda_codec_shutdown(dev_to_hda_codec(dev));
diff --git a/sound/hda/common/sysfs.c b/sound/hda/common/sysfs.c
index bf65223fde10..ac08ee6f9c10 100644
--- a/sound/hda/common/sysfs.c
+++ b/sound/hda/common/sysfs.c
@@ -144,7 +144,7 @@ static int reconfig_codec(struct hda_codec *codec)
err = device_reprobe(hda_codec_dev(codec));
if (err < 0)
return err;
- return snd_card_register(codec->card);
+ return snd_hda_card_register(codec->bus);
}

/*
diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c
index 04b261dde22e..87e5f3f14a2b 100644
--- a/sound/hda/controllers/acpi.c
+++ b/sound/hda/controllers/acpi.c
@@ -166,7 +166,7 @@ static void hda_acpi_probe_work(struct work_struct *work)
if (err < 0)
return;

- err = snd_card_register(chip->card);
+ err = snd_hda_card_register(&chip->bus);
if (err < 0)
return;

diff --git a/sound/hda/controllers/cix-ipbloq.c b/sound/hda/controllers/cix-ipbloq.c
index c1084a915c13..c193dc1428c2 100644
--- a/sound/hda/controllers/cix-ipbloq.c
+++ b/sound/hda/controllers/cix-ipbloq.c
@@ -80,7 +80,7 @@ static int cix_ipbloq_hda_probe_codec(struct cix_ipbloq_hda *hda)
return err;
}

- err = snd_card_register(chip->card);
+ err = snd_hda_card_register(&chip->bus);
if (err < 0) {
dev_err(hda->dev, "card register failed: %d\n", err);
return err;
diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
index 29679fe7f401..0a62723b3454 100644
--- a/sound/hda/controllers/intel.c
+++ b/sound/hda/controllers/intel.c
@@ -2434,7 +2434,7 @@ static int azx_probe_continue(struct azx *chip, bool free_on_error)
}
}

- err = snd_card_register(chip->card);
+ err = snd_hda_card_register(&chip->bus);
if (err < 0)
goto out_free;

diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c
index 674cc0f79c3c..d601aa3db8b4 100644
--- a/sound/hda/controllers/tegra.c
+++ b/sound/hda/controllers/tegra.c
@@ -607,7 +607,7 @@ static void hda_tegra_probe_work(struct work_struct *work)
if (err < 0)
return;

- err = snd_card_register(chip->card);
+ err = snd_hda_card_register(&chip->bus);
if (err < 0)
return;

diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c
index 02462bd035e7..304110c9a6a7 100644
--- a/sound/hda/core/bus.c
+++ b/sound/hda/core/bus.c
@@ -44,6 +44,7 @@ int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
spin_lock_init(&bus->reg_lock);
mutex_init(&bus->cmd_mutex);
mutex_init(&bus->lock);
+ mutex_init(&bus->codec_bind_lock);
INIT_LIST_HEAD(&bus->hlink_list);
init_waitqueue_head(&bus->rirb_wq);
bus->irq = -1;