Blob Blame History Raw
From 3809db6430bf6a725d234e6eec9a6f6be6b8c1ea Mon Sep 17 00:00:00 2001
From: Takashi Iwai <tiwai@suse.de>
Date: Fri, 17 Jun 2022 16:40:51 +0200
Subject: [PATCH] ALSA: rawmidi: Take buffer refcount while draining output
Git-commit: 3809db6430bf6a725d234e6eec9a6f6be6b8c1ea
Patch-mainline: v6.0-rc1
References: jsc#PED-850

Although snd_rawmidi_drain_output() may take some long time, it has no
protection and intrusive operations like the buffer resize may happen
meanwhile.  For making the operation a bit more robust, this patch
takes the buffer refcount for blocking the buffer resize.

Also, as this function is exported, in theory, it might be called
asynchronously from the stream open/close state.  For avoiding the
missing refcount, now the close call checks the buffer refcount, too.

Link: https://lore.kernel.org/r/20220617144051.18985-6-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>

---
 sound/core/rawmidi.c | 45 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 41 insertions(+), 4 deletions(-)

diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
index 889fa4747dad..6963d5a487b3 100644
--- a/sound/core/rawmidi.c
+++ b/sound/core/rawmidi.c
@@ -140,6 +140,23 @@ static inline void snd_rawmidi_buffer_unref(struct snd_rawmidi_runtime *runtime)
 	runtime->buffer_ref--;
 }
 
+static void snd_rawmidi_buffer_ref_sync(struct snd_rawmidi_substream *substream)
+{
+	int loop = HZ;
+
+	spin_lock_irq(&substream->lock);
+	while (substream->runtime->buffer_ref) {
+		spin_unlock_irq(&substream->lock);
+		if (!--loop) {
+			rmidi_err(substream->rmidi, "Buffer ref sync timeout\n");
+			return;
+		}
+		schedule_timeout_uninterruptible(1);
+		spin_lock_irq(&substream->lock);
+	}
+	spin_unlock_irq(&substream->lock);
+}
+
 static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream)
 {
 	struct snd_rawmidi_runtime *runtime;
@@ -222,15 +239,27 @@ EXPORT_SYMBOL(snd_rawmidi_drop_output);
 
 int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream)
 {
-	int err;
+	int err = 0;
 	long timeout;
-	struct snd_rawmidi_runtime *runtime = substream->runtime;
+	struct snd_rawmidi_runtime *runtime;
+
+	spin_lock_irq(&substream->lock);
+	runtime = substream->runtime;
+	if (!substream->opened || !runtime || !runtime->buffer) {
+		err = -EINVAL;
+	} else {
+		snd_rawmidi_buffer_ref(runtime);
+		runtime->drain = 1;
+	}
+	spin_unlock_irq(&substream->lock);
+	if (err < 0)
+		return err;
 
-	err = 0;
-	runtime->drain = 1;
 	timeout = wait_event_interruptible_timeout(runtime->sleep,
 				(runtime->avail >= runtime->buffer_size),
 				10*HZ);
+
+	spin_lock_irq(&substream->lock);
 	if (signal_pending(current))
 		err = -ERESTARTSYS;
 	if (runtime->avail < runtime->buffer_size && !timeout) {
@@ -240,6 +269,8 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream)
 		err = -EIO;
 	}
 	runtime->drain = 0;
+	spin_unlock_irq(&substream->lock);
+
 	if (err != -ERESTARTSYS) {
 		/* we need wait a while to make sure that Tx FIFOs are empty */
 		if (substream->ops->drain)
@@ -248,6 +279,11 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream)
 			msleep(50);
 		snd_rawmidi_drop_output(substream);
 	}
+
+	spin_lock_irq(&substream->lock);
+	snd_rawmidi_buffer_unref(runtime);
+	spin_unlock_irq(&substream->lock);
+
 	return err;
 }
 EXPORT_SYMBOL(snd_rawmidi_drain_output);
@@ -522,6 +558,7 @@ static void close_substream(struct snd_rawmidi *rmidi,
 			if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS)
 				snd_rawmidi_output_trigger(substream, 0);
 		}
+		snd_rawmidi_buffer_ref_sync(substream);
 	}
 	spin_lock_irq(&substream->lock);
 	substream->opened = 0;
-- 
2.35.3