mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3] ALSA: pcm: Drain proc callbacks at device disconnect
@ 2026-10-22 10:58 Cen Zhang
  2026-10-23 13:40 ` Takashi Iwai
  0 siblings, 1 reply; 2+ messages in thread
From: Cen Zhang @ 2026-10-22 10:58 UTC (permalink / raw)
  To: perex, tiwai
  Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming, zzzccc427

Disconnecting an individual PCM leaves its proc files accessible until
snd_pcm_free_stream(). HDA codec-only sysfs unbind can take this path
while the controller and card remain registered. An open proc file pins
the card module, but does not hold a codec PCM reference.

With CONFIG_SND_VERBOSE_PROCFS, a prealloc write is buffered until file
release. Without an attached PCM runtime, its release can replace the
DMA buffer after final PCM cleanup has already freed the preallocation:

  HDA codec unbind                  prealloc file release
  ----------------                  ---------------------
  hda_codec_driver_remove()
    snd_hda_codec_disconnect_pcms()
      snd_device_disconnect()
        snd_pcm_dev_disconnect()
        /* proc nodes remain */
    snd_hda_codec_cleanup_for_unbind()
      codec_release_pcms()
        snd_device_free()
          snd_pcm_free()
            free preallocated buffer
                                    snd_info_text_entry_release()
                                      snd_pcm_lib_preallocate_proc_write()
                                        replace DMA buffer
            remove stream proc nodes
            free substream

The later proc rundown can itself invoke this buffered write and leave
the replacement allocation without a final free. A concurrent release
can also overlap the earlier buffer cleanup.

Extract snd_info_disconnect_entry() from snd_info_free_entry() to remove
proc nodes and drain callbacks without freeing the info entry objects.
Use it for both stream directions at PCM disconnect. Finish the existing
PCM state and device disconnection under open_mutex, then drop that lock
before proc rundown: even an otherwise idle proc file can run its
buffered release callback, which takes open_mutex, during proc_remove().

Keep entry ownership and final cleanup unchanged, including the saved
OSS child entries. As with existing info entry teardown, callers must
serialize registration with disconnection and release.

Suggested-by: Takashi Iwai <tiwai@suse.de>
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
Changes in v3:
- Use the simpler snd_info_disconnect_entry helper suggested by Takashi and
  reuse it from snd_info_free_entry.
- Drop the persistent disconnected flag and ancestor registration checks;
  leave registration/teardown serialization to the caller.
- Complete the existing PCM state and device disconnection before dropping
  open_mutex and draining proc callbacks; export the helper and provide
  valid disabled-config stubs.

Changes in v2:
- Follow Takashi's suggestion to remove and drain stream proc nodes at PCM
  disconnect rather than only moving final buffer cleanup.
- Run proc teardown before taking open_mutex and retain info entries for
  final cleanup, including the saved OSS child entries.
- Prevent later recursive or direct descendant registration from
  republishing a disconnected subtree.

Link to v1: https://lore.kernel.org/r/pm-pmb-2439c851f82c443c3600-v4-9e7df0ea42c36afe6171@gmail.com
Link to v2: https://lore.kernel.org/r/pm-pmb-2439c851f82c443c3600-v6-cf4d8c3d01d51ad32b94@gmail.com

 include/sound/info.h |  2 ++
 sound/core/info.c    | 23 ++++++++++++++----
 sound/core/pcm.c     | 56 ++++++++++++++++++++++++++------------------
 3 files changed, 53 insertions(+), 28 deletions(-)

diff --git a/include/sound/info.h b/include/sound/info.h
index adbc506860d6..f216291e5c41 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -117,6 +117,7 @@ struct snd_info_entry *snd_info_create_module_entry(struct module *module,
 struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
 					     const char *name,
 					     struct snd_info_entry *parent);
+void snd_info_disconnect_entry(struct snd_info_entry *entry);
 void snd_info_free_entry(struct snd_info_entry *entry);
 
 int snd_info_card_create(struct snd_card *card);
@@ -164,6 +165,7 @@ static inline int snd_info_get_line(struct snd_info_buffer *buffer, char *line,
 static inline char *snd_info_get_str(char *dest, char *src, int len) { return NULL; }
 static inline struct snd_info_entry *snd_info_create_module_entry(struct module *module, const char *name, struct snd_info_entry *parent) { return NULL; }
 static inline struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card, const char *name, struct snd_info_entry *parent) { return NULL; }
+static inline void snd_info_disconnect_entry(struct snd_info_entry *entry) { }
 static inline void snd_info_free_entry(struct snd_info_entry *entry) { ; }
 
 static inline int snd_info_card_create(struct snd_card *card) { return 0; }
diff --git a/sound/core/info.c b/sound/core/info.c
index c4d57d0809ad..023b4736aa6d 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -737,6 +737,23 @@ static void snd_info_clear_entries(struct snd_info_entry *entry)
 	entry->p = NULL;
 }
 
+/**
+ * snd_info_disconnect_entry - remove proc nodes and drain their callbacks
+ * @entry: root of the info entry subtree
+ *
+ * Retains the info entries for snd_info_free_entry().  Callers must serialize
+ * registration and destruction, and must not hold locks needed by callbacks.
+ */
+void snd_info_disconnect_entry(struct snd_info_entry *entry)
+{
+	if (entry && entry->p) {
+		proc_remove(entry->p);
+		guard(mutex)(&info_mutex);
+		snd_info_clear_entries(entry);
+	}
+}
+EXPORT_SYMBOL(snd_info_disconnect_entry);
+
 /**
  * snd_info_free_entry - release the info entry
  * @entry: the info entry
@@ -749,11 +766,7 @@ void snd_info_free_entry(struct snd_info_entry * entry)
 
 	if (!entry)
 		return;
-	if (entry->p) {
-		proc_remove(entry->p);
-		guard(mutex)(&info_mutex);
-		snd_info_clear_entries(entry);
-	}
+	snd_info_disconnect_entry(entry);
 
 	/* free all children at first */
 	list_for_each_entry_safe(p, n, &entry->children, list)
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 1e4e0ecb3dbd..064df4cd3b50 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -520,6 +520,11 @@ static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr)
 	return 0;
 }
 
+static void snd_pcm_stream_proc_disconnect(struct snd_pcm_str *pstr)
+{
+	snd_info_disconnect_entry(pstr->proc_root);
+}
+
 static int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr)
 {
 	snd_info_free_entry(pstr->proc_root);
@@ -580,6 +585,7 @@ static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
 
 #else /* !CONFIG_SND_VERBOSE_PROCFS */
 static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; }
+static inline void snd_pcm_stream_proc_disconnect(struct snd_pcm_str *pstr) { }
 static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; }
 static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; }
 #endif /* CONFIG_SND_VERBOSE_PROCFS */
@@ -1094,33 +1100,37 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
 	int cidx;
 
 	guard(mutex)(&register_mutex);
-	guard(mutex)(&pcm->open_mutex);
-	wake_up(&pcm->open_wait);
-	list_del_init(&pcm->list);
-
-	for_each_pcm_substream(pcm, cidx, substream) {
-		snd_pcm_stream_lock_irq(substream);
-		if (substream->runtime) {
-			if (snd_pcm_running(substream))
-				snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
-			/* to be sure, set the state unconditionally */
-			__snd_pcm_set_state(substream->runtime,
-					    SNDRV_PCM_STATE_DISCONNECTED);
-			wake_up(&substream->runtime->sleep);
-			wake_up(&substream->runtime->tsleep);
+	scoped_guard(mutex, &pcm->open_mutex) {
+		wake_up(&pcm->open_wait);
+		list_del_init(&pcm->list);
+
+		for_each_pcm_substream(pcm, cidx, substream) {
+			snd_pcm_stream_lock_irq(substream);
+			if (substream->runtime) {
+				if (snd_pcm_running(substream))
+					snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+				/* to be sure, set the state unconditionally */
+				__snd_pcm_set_state(substream->runtime,
+						    SNDRV_PCM_STATE_DISCONNECTED);
+				wake_up(&substream->runtime->sleep);
+				wake_up(&substream->runtime->tsleep);
+			}
+			snd_pcm_stream_unlock_irq(substream);
 		}
-		snd_pcm_stream_unlock_irq(substream);
-	}
 
-	for_each_pcm_substream(pcm, cidx, substream)
-		snd_pcm_sync_stop(substream, false);
+		for_each_pcm_substream(pcm, cidx, substream)
+			snd_pcm_sync_stop(substream, false);
 
-	pcm_call_notify(pcm, n_disconnect);
-	for (cidx = 0; cidx < 2; cidx++) {
-		if (pcm->streams[cidx].dev)
-			snd_unregister_device(pcm->streams[cidx].dev);
-		free_chmap(&pcm->streams[cidx]);
+		pcm_call_notify(pcm, n_disconnect);
+		for (cidx = 0; cidx < 2; cidx++) {
+			if (pcm->streams[cidx].dev)
+				snd_unregister_device(pcm->streams[cidx].dev);
+			free_chmap(&pcm->streams[cidx]);
+		}
 	}
+	/* Proc rundown can run release callbacks which take open_mutex. */
+	for (cidx = 0; cidx < 2; cidx++)
+		snd_pcm_stream_proc_disconnect(&pcm->streams[cidx]);
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH v3] ALSA: pcm: Drain proc callbacks at device disconnect
  2026-10-22 10:58 [PATCH v3] ALSA: pcm: Drain proc callbacks at device disconnect Cen Zhang
@ 2026-10-23 13:40 ` Takashi Iwai
  0 siblings, 0 replies; 2+ messages in thread
From: Takashi Iwai @ 2026-10-23 13:40 UTC (permalink / raw)
  To: Cen Zhang
  Cc: perex, tiwai, linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming

On Thu, 22 Oct 2026 12:58:20 +0200,
Cen Zhang wrote:
> 
> Disconnecting an individual PCM leaves its proc files accessible until
> snd_pcm_free_stream(). HDA codec-only sysfs unbind can take this path
> while the controller and card remain registered. An open proc file pins
> the card module, but does not hold a codec PCM reference.
> 
> With CONFIG_SND_VERBOSE_PROCFS, a prealloc write is buffered until file
> release. Without an attached PCM runtime, its release can replace the
> DMA buffer after final PCM cleanup has already freed the preallocation:
> 
>   HDA codec unbind                  prealloc file release
>   ----------------                  ---------------------
>   hda_codec_driver_remove()
>     snd_hda_codec_disconnect_pcms()
>       snd_device_disconnect()
>         snd_pcm_dev_disconnect()
>         /* proc nodes remain */
>     snd_hda_codec_cleanup_for_unbind()
>       codec_release_pcms()
>         snd_device_free()
>           snd_pcm_free()
>             free preallocated buffer
>                                     snd_info_text_entry_release()
>                                       snd_pcm_lib_preallocate_proc_write()
>                                         replace DMA buffer
>             remove stream proc nodes
>             free substream
> 
> The later proc rundown can itself invoke this buffered write and leave
> the replacement allocation without a final free. A concurrent release
> can also overlap the earlier buffer cleanup.
> 
> Extract snd_info_disconnect_entry() from snd_info_free_entry() to remove
> proc nodes and drain callbacks without freeing the info entry objects.
> Use it for both stream directions at PCM disconnect. Finish the existing
> PCM state and device disconnection under open_mutex, then drop that lock
> before proc rundown: even an otherwise idle proc file can run its
> buffered release callback, which takes open_mutex, during proc_remove().
> 
> Keep entry ownership and final cleanup unchanged, including the saved
> OSS child entries. As with existing info entry teardown, callers must
> serialize registration with disconnection and release.
> 
> Suggested-by: Takashi Iwai <tiwai@suse.de>
> Assisted-by: LLM
> Signed-off-by: Cen Zhang <zzzccc427@gmail.com>

Applied now.  Although we still may have some issues about the
concurrent codec release and probe, this should be treated better in
the upper layer.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-23 13:40 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 10:58 [PATCH v3] ALSA: pcm: Drain proc callbacks at device disconnect Cen Zhang
2026-10-23 13:40 ` Takashi Iwai

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®