All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/10] ALSA: aloop: Support selection of snd_timer
@ 2019-03-25 16:00 ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel

With the following patch set any sound timer can be used as timer source
for the aloop devices if selected. It can be select by the additional
timer_source kernel module parameter:
-2   the jiffies timer will be used as done without this patch (default)
-1   the sound timer will be selected depending on the linked sound cards
     (snd_pcm_link()). The sound timer of the first linked card which is not
     a aloop device nor a dummy device will be used.
0..N Select the sound timer by the card number.

This patch set is required for forwarding audio data between a HW sound card
and an aloop device without the usage of an asynchronous sample rate converter.


Best regards,

Timo


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

* [PATCH 00/10] ALSA: aloop: Support selection of snd_timer
@ 2019-03-25 16:00 ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel

With the following patch set any sound timer can be used as timer source
for the aloop devices if selected. It can be select by the additional
timer_source kernel module parameter:
-2   the jiffies timer will be used as done without this patch (default)
-1   the sound timer will be selected depending on the linked sound cards
     (snd_pcm_link()). The sound timer of the first linked card which is not
     a aloop device nor a dummy device will be used.
0..N Select the sound timer by the card number.

This patch set is required for forwarding audio data between a HW sound card
and an aloop device without the usage of an asynchronous sample rate converter.


Best regards,

Timo

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

* [PATCH 01/10] ALSA: aloop: Describe units of variables
  2019-03-25 16:00 ` twischer
@ 2019-03-25 16:00   ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

Describe the unit of the variables used to calculate the hw pointer
depending on jiffies ticks.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 8c3fbe1..70145e6 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -116,8 +116,10 @@ struct loopback_pcm {
 	/* flags */
 	unsigned int period_update_pending :1;
 	/* timer stuff */
-	unsigned int irq_pos;		/* fractional IRQ position */
-	unsigned int period_size_frac;
+	unsigned int irq_pos;		/* fractional IRQ position in jiffies
+					 * ticks
+					 */
+	unsigned int period_size_frac;	/* period size in jiffies ticks */
 	unsigned int last_drift;
 	unsigned long last_jiffies;
 	struct timer_list timer;
-- 
2.7.4


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

* [PATCH 01/10] ALSA: aloop: Describe units of variables
@ 2019-03-25 16:00   ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

Describe the unit of the variables used to calculate the hw pointer
depending on jiffies ticks.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 8c3fbe1..70145e6 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -116,8 +116,10 @@ struct loopback_pcm {
 	/* flags */
 	unsigned int period_update_pending :1;
 	/* timer stuff */
-	unsigned int irq_pos;		/* fractional IRQ position */
-	unsigned int period_size_frac;
+	unsigned int irq_pos;		/* fractional IRQ position in jiffies
+					 * ticks
+					 */
+	unsigned int period_size_frac;	/* period size in jiffies ticks */
 	unsigned int last_drift;
 	unsigned long last_jiffies;
 	struct timer_list timer;
-- 
2.7.4

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

* [PATCH 02/10] ALSA: aloop: loopback_timer_start: Support return of error code
  2019-03-25 16:00 ` twischer
@ 2019-03-25 16:00   ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This is required for additional timer implementations which could detect
errors and want to throw them.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 70145e6..f0c7b5b 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -169,7 +169,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static void loopback_timer_start(struct loopback_pcm *dpcm)
+static int loopback_timer_start(struct loopback_pcm *dpcm)
 {
 	unsigned long tick;
 	unsigned int rate_shift = get_rate_shift(dpcm);
@@ -185,6 +185,8 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
 	tick = dpcm->period_size_frac - dpcm->irq_pos;
 	tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
 	mod_timer(&dpcm->timer, jiffies + tick);
+
+	return 0;
 }
 
 /* call in cable->lock */
@@ -265,7 +267,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct loopback_pcm *dpcm = runtime->private_data;
 	struct loopback_cable *cable = dpcm->cable;
-	int err, stream = 1 << substream->stream;
+	int err = 0, stream = 1 << substream->stream;
 
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
@@ -278,7 +280,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock(&cable->lock);	
 		cable->running |= stream;
 		cable->pause &= ~stream;
-		loopback_timer_start(dpcm);
+		err = loopback_timer_start(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -306,7 +308,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock(&cable->lock);
 		dpcm->last_jiffies = jiffies;
 		cable->pause &= ~stream;
-		loopback_timer_start(dpcm);
+		err = loopback_timer_start(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -314,7 +316,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	default:
 		return -EINVAL;
 	}
-	return 0;
+	return err;
 }
 
 static void params_change(struct snd_pcm_substream *substream)
-- 
2.7.4


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

* [PATCH 02/10] ALSA: aloop: loopback_timer_start: Support return of error code
@ 2019-03-25 16:00   ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This is required for additional timer implementations which could detect
errors and want to throw them.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 70145e6..f0c7b5b 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -169,7 +169,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static void loopback_timer_start(struct loopback_pcm *dpcm)
+static int loopback_timer_start(struct loopback_pcm *dpcm)
 {
 	unsigned long tick;
 	unsigned int rate_shift = get_rate_shift(dpcm);
@@ -185,6 +185,8 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
 	tick = dpcm->period_size_frac - dpcm->irq_pos;
 	tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
 	mod_timer(&dpcm->timer, jiffies + tick);
+
+	return 0;
 }
 
 /* call in cable->lock */
@@ -265,7 +267,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct loopback_pcm *dpcm = runtime->private_data;
 	struct loopback_cable *cable = dpcm->cable;
-	int err, stream = 1 << substream->stream;
+	int err = 0, stream = 1 << substream->stream;
 
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
@@ -278,7 +280,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock(&cable->lock);	
 		cable->running |= stream;
 		cable->pause &= ~stream;
-		loopback_timer_start(dpcm);
+		err = loopback_timer_start(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -306,7 +308,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock(&cable->lock);
 		dpcm->last_jiffies = jiffies;
 		cable->pause &= ~stream;
-		loopback_timer_start(dpcm);
+		err = loopback_timer_start(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -314,7 +316,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	default:
 		return -EINVAL;
 	}
-	return 0;
+	return err;
 }
 
 static void params_change(struct snd_pcm_substream *substream)
-- 
2.7.4

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

* [PATCH 03/10] ALSA: aloop: loopback_timer_stop: Support return of error code
  2019-03-25 16:00 ` twischer
@ 2019-03-25 16:00   ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This is required for additional timer implementations which could detect
errors and want to throw them.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index f0c7b5b..11e8ed6 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -190,15 +190,19 @@ static int loopback_timer_start(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
+static inline int loopback_timer_stop(struct loopback_pcm *dpcm)
 {
 	del_timer(&dpcm->timer);
 	dpcm->timer.expires = 0;
+
+	return 0;
 }
 
-static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm)
+static inline int loopback_timer_stop_sync(struct loopback_pcm *dpcm)
 {
 	del_timer_sync(&dpcm->timer);
+
+	return 0;
 }
 
 #define CABLE_VALID_PLAYBACK	(1 << SNDRV_PCM_STREAM_PLAYBACK)
@@ -289,7 +293,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock(&cable->lock);	
 		cable->running &= ~stream;
 		cable->pause &= ~stream;
-		loopback_timer_stop(dpcm);
+		err = loopback_timer_stop(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -298,7 +302,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	case SNDRV_PCM_TRIGGER_SUSPEND:
 		spin_lock(&cable->lock);	
 		cable->pause |= stream;
-		loopback_timer_stop(dpcm);
+		err = loopback_timer_stop(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -337,9 +341,11 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct loopback_pcm *dpcm = runtime->private_data;
 	struct loopback_cable *cable = dpcm->cable;
-	int bps, salign;
+	int err, bps, salign;
 
-	loopback_timer_stop_sync(dpcm);
+	err = loopback_timer_stop_sync(dpcm);
+	if (err < 0)
+		return err;
 
 	salign = (snd_pcm_format_width(runtime->format) *
 						runtime->channels) / 8;
-- 
2.7.4


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

* [PATCH 03/10] ALSA: aloop: loopback_timer_stop: Support return of error code
@ 2019-03-25 16:00   ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This is required for additional timer implementations which could detect
errors and want to throw them.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index f0c7b5b..11e8ed6 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -190,15 +190,19 @@ static int loopback_timer_start(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
+static inline int loopback_timer_stop(struct loopback_pcm *dpcm)
 {
 	del_timer(&dpcm->timer);
 	dpcm->timer.expires = 0;
+
+	return 0;
 }
 
-static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm)
+static inline int loopback_timer_stop_sync(struct loopback_pcm *dpcm)
 {
 	del_timer_sync(&dpcm->timer);
+
+	return 0;
 }
 
 #define CABLE_VALID_PLAYBACK	(1 << SNDRV_PCM_STREAM_PLAYBACK)
@@ -289,7 +293,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock(&cable->lock);	
 		cable->running &= ~stream;
 		cable->pause &= ~stream;
-		loopback_timer_stop(dpcm);
+		err = loopback_timer_stop(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -298,7 +302,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	case SNDRV_PCM_TRIGGER_SUSPEND:
 		spin_lock(&cable->lock);	
 		cable->pause |= stream;
-		loopback_timer_stop(dpcm);
+		err = loopback_timer_stop(dpcm);
 		spin_unlock(&cable->lock);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -337,9 +341,11 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct loopback_pcm *dpcm = runtime->private_data;
 	struct loopback_cable *cable = dpcm->cable;
-	int bps, salign;
+	int err, bps, salign;
 
-	loopback_timer_stop_sync(dpcm);
+	err = loopback_timer_stop_sync(dpcm);
+	if (err < 0)
+		return err;
 
 	salign = (snd_pcm_format_width(runtime->format) *
 						runtime->channels) / 8;
-- 
2.7.4

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

* [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
  2019-03-25 16:00 ` twischer
@ 2019-03-25 16:00   ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

to allow the usage of timer callbacks from interrupt context.
For example the sound timer.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 11e8ed6..c6217c4 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	struct loopback_pcm *dpcm = runtime->private_data;
 	struct loopback_cable *cable = dpcm->cable;
 	int err = 0, stream = 1 << substream->stream;
+	unsigned long flags;
 
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
@@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		dpcm->last_jiffies = jiffies;
 		dpcm->pcm_rate_shift = 0;
 		dpcm->last_drift = 0;
-		spin_lock(&cable->lock);	
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->running |= stream;
 		cable->pause &= ~stream;
 		err = loopback_timer_start(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
 	case SNDRV_PCM_TRIGGER_STOP:
-		spin_lock(&cable->lock);	
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->running &= ~stream;
 		cable->pause &= ~stream;
 		err = loopback_timer_stop(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
-		spin_lock(&cable->lock);	
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->pause |= stream;
 		err = loopback_timer_stop(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 	case SNDRV_PCM_TRIGGER_RESUME:
-		spin_lock(&cable->lock);
+		spin_lock_irqsave(&cable->lock, flags);
 		dpcm->last_jiffies = jiffies;
 		cable->pause &= ~stream;
 		err = loopback_timer_start(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
@@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct loopback_pcm *dpcm = runtime->private_data;
+	unsigned long flags;
 	snd_pcm_uframes_t pos;
 
-	spin_lock(&dpcm->cable->lock);
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
 	loopback_pos_update(dpcm->cable);
 	pos = dpcm->buf_pos;
-	spin_unlock(&dpcm->cable->lock);
+	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 	return bytes_to_frames(runtime, pos);
 }
 
@@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
 	if (!cable)
 		return;
 	if (cable->streams[!substream->stream]) {
+		unsigned long flags;
+
 		/* other stream is still alive */
-		spin_lock_irq(&cable->lock);
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->streams[substream->stream] = NULL;
-		spin_unlock_irq(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 	} else {
 		/* free the cable */
 		loopback->cables[substream->number][dev] = NULL;
@@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	struct loopback_cable *cable = NULL;
 	int err = 0;
 	int dev = get_cable_index(substream);
+	unsigned long flags;
 
 	mutex_lock(&loopback->cable_lock);
 	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
@@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	else
 		runtime->hw = cable->hw;
 
-	spin_lock_irq(&cable->lock);
+	spin_lock_irqsave(&cable->lock, flags);
 	cable->streams[substream->stream] = dpcm;
-	spin_unlock_irq(&cable->lock);
+	spin_unlock_irqrestore(&cable->lock, flags);
 
  unlock:
 	if (err < 0) {
-- 
2.7.4


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

* [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
@ 2019-03-25 16:00   ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-25 16:00 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

to allow the usage of timer callbacks from interrupt context.
For example the sound timer.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 11e8ed6..c6217c4 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	struct loopback_pcm *dpcm = runtime->private_data;
 	struct loopback_cable *cable = dpcm->cable;
 	int err = 0, stream = 1 << substream->stream;
+	unsigned long flags;
 
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
@@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		dpcm->last_jiffies = jiffies;
 		dpcm->pcm_rate_shift = 0;
 		dpcm->last_drift = 0;
-		spin_lock(&cable->lock);	
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->running |= stream;
 		cable->pause &= ~stream;
 		err = loopback_timer_start(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
 	case SNDRV_PCM_TRIGGER_STOP:
-		spin_lock(&cable->lock);	
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->running &= ~stream;
 		cable->pause &= ~stream;
 		err = loopback_timer_stop(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
-		spin_lock(&cable->lock);	
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->pause |= stream;
 		err = loopback_timer_stop(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 	case SNDRV_PCM_TRIGGER_RESUME:
-		spin_lock(&cable->lock);
+		spin_lock_irqsave(&cable->lock, flags);
 		dpcm->last_jiffies = jiffies;
 		cable->pause &= ~stream;
 		err = loopback_timer_start(dpcm);
-		spin_unlock(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
 		break;
@@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct loopback_pcm *dpcm = runtime->private_data;
+	unsigned long flags;
 	snd_pcm_uframes_t pos;
 
-	spin_lock(&dpcm->cable->lock);
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
 	loopback_pos_update(dpcm->cable);
 	pos = dpcm->buf_pos;
-	spin_unlock(&dpcm->cable->lock);
+	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 	return bytes_to_frames(runtime, pos);
 }
 
@@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
 	if (!cable)
 		return;
 	if (cable->streams[!substream->stream]) {
+		unsigned long flags;
+
 		/* other stream is still alive */
-		spin_lock_irq(&cable->lock);
+		spin_lock_irqsave(&cable->lock, flags);
 		cable->streams[substream->stream] = NULL;
-		spin_unlock_irq(&cable->lock);
+		spin_unlock_irqrestore(&cable->lock, flags);
 	} else {
 		/* free the cable */
 		loopback->cables[substream->number][dev] = NULL;
@@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	struct loopback_cable *cable = NULL;
 	int err = 0;
 	int dev = get_cable_index(substream);
+	unsigned long flags;
 
 	mutex_lock(&loopback->cable_lock);
 	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
@@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	else
 		runtime->hw = cable->hw;
 
-	spin_lock_irq(&cable->lock);
+	spin_lock_irqsave(&cable->lock, flags);
 	cable->streams[substream->stream] = dpcm;
-	spin_unlock_irq(&cable->lock);
+	spin_unlock_irqrestore(&cable->lock, flags);
 
  unlock:
 	if (err < 0) {
-- 
2.7.4

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
  2019-03-25 16:00   ` twischer
@ 2019-03-25 16:07     ` Takashi Iwai
  -1 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-25 16:07 UTC (permalink / raw)
  To: twischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Mon, 25 Mar 2019 17:00:38 +0100,
<twischer@de.adit-jv.com> wrote:
> 
> From: Timo Wischer <twischer@de.adit-jv.com>
> 
> to allow the usage of timer callbacks from interrupt context.
> For example the sound timer.

The trigger callback is already irq-disabled.  And, open/close must
not be irq-disabled OTOH.  So these changes must be superfluous.


thanks,

Takashi


> 
> Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
> ---
>  sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>  1 file changed, 19 insertions(+), 14 deletions(-)
> 
> diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
> index 11e8ed6..c6217c4 100644
> --- a/sound/drivers/aloop.c
> +++ b/sound/drivers/aloop.c
> @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>  	struct loopback_pcm *dpcm = runtime->private_data;
>  	struct loopback_cable *cable = dpcm->cable;
>  	int err = 0, stream = 1 << substream->stream;
> +	unsigned long flags;
>  
>  	switch (cmd) {
>  	case SNDRV_PCM_TRIGGER_START:
> @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>  		dpcm->last_jiffies = jiffies;
>  		dpcm->pcm_rate_shift = 0;
>  		dpcm->last_drift = 0;
> -		spin_lock(&cable->lock);	
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->running |= stream;
>  		cable->pause &= ~stream;
>  		err = loopback_timer_start(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
>  	case SNDRV_PCM_TRIGGER_STOP:
> -		spin_lock(&cable->lock);	
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->running &= ~stream;
>  		cable->pause &= ~stream;
>  		err = loopback_timer_stop(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
>  	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>  	case SNDRV_PCM_TRIGGER_SUSPEND:
> -		spin_lock(&cable->lock);	
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->pause |= stream;
>  		err = loopback_timer_stop(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
>  	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>  	case SNDRV_PCM_TRIGGER_RESUME:
> -		spin_lock(&cable->lock);
> +		spin_lock_irqsave(&cable->lock, flags);
>  		dpcm->last_jiffies = jiffies;
>  		cable->pause &= ~stream;
>  		err = loopback_timer_start(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
> @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>  {
>  	struct snd_pcm_runtime *runtime = substream->runtime;
>  	struct loopback_pcm *dpcm = runtime->private_data;
> +	unsigned long flags;
>  	snd_pcm_uframes_t pos;
>  
> -	spin_lock(&dpcm->cable->lock);
> +	spin_lock_irqsave(&dpcm->cable->lock, flags);
>  	loopback_pos_update(dpcm->cable);
>  	pos = dpcm->buf_pos;
> -	spin_unlock(&dpcm->cable->lock);
> +	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>  	return bytes_to_frames(runtime, pos);
>  }
>  
> @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>  	if (!cable)
>  		return;
>  	if (cable->streams[!substream->stream]) {
> +		unsigned long flags;
> +
>  		/* other stream is still alive */
> -		spin_lock_irq(&cable->lock);
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->streams[substream->stream] = NULL;
> -		spin_unlock_irq(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  	} else {
>  		/* free the cable */
>  		loopback->cables[substream->number][dev] = NULL;
> @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>  	struct loopback_cable *cable = NULL;
>  	int err = 0;
>  	int dev = get_cable_index(substream);
> +	unsigned long flags;
>  
>  	mutex_lock(&loopback->cable_lock);
>  	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
> @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>  	else
>  		runtime->hw = cable->hw;
>  
> -	spin_lock_irq(&cable->lock);
> +	spin_lock_irqsave(&cable->lock, flags);
>  	cable->streams[substream->stream] = dpcm;
> -	spin_unlock_irq(&cable->lock);
> +	spin_unlock_irqrestore(&cable->lock, flags);
>  
>   unlock:
>  	if (err < 0) {
> -- 
> 2.7.4
> 
> 

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
@ 2019-03-25 16:07     ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-25 16:07 UTC (permalink / raw)
  To: twischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Mon, 25 Mar 2019 17:00:38 +0100,
<twischer@de.adit-jv.com> wrote:
> 
> From: Timo Wischer <twischer@de.adit-jv.com>
> 
> to allow the usage of timer callbacks from interrupt context.
> For example the sound timer.

The trigger callback is already irq-disabled.  And, open/close must
not be irq-disabled OTOH.  So these changes must be superfluous.


thanks,

Takashi


> 
> Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
> ---
>  sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>  1 file changed, 19 insertions(+), 14 deletions(-)
> 
> diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
> index 11e8ed6..c6217c4 100644
> --- a/sound/drivers/aloop.c
> +++ b/sound/drivers/aloop.c
> @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>  	struct loopback_pcm *dpcm = runtime->private_data;
>  	struct loopback_cable *cable = dpcm->cable;
>  	int err = 0, stream = 1 << substream->stream;
> +	unsigned long flags;
>  
>  	switch (cmd) {
>  	case SNDRV_PCM_TRIGGER_START:
> @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>  		dpcm->last_jiffies = jiffies;
>  		dpcm->pcm_rate_shift = 0;
>  		dpcm->last_drift = 0;
> -		spin_lock(&cable->lock);	
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->running |= stream;
>  		cable->pause &= ~stream;
>  		err = loopback_timer_start(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
>  	case SNDRV_PCM_TRIGGER_STOP:
> -		spin_lock(&cable->lock);	
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->running &= ~stream;
>  		cable->pause &= ~stream;
>  		err = loopback_timer_stop(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
>  	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>  	case SNDRV_PCM_TRIGGER_SUSPEND:
> -		spin_lock(&cable->lock);	
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->pause |= stream;
>  		err = loopback_timer_stop(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
>  	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>  	case SNDRV_PCM_TRIGGER_RESUME:
> -		spin_lock(&cable->lock);
> +		spin_lock_irqsave(&cable->lock, flags);
>  		dpcm->last_jiffies = jiffies;
>  		cable->pause &= ~stream;
>  		err = loopback_timer_start(dpcm);
> -		spin_unlock(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  			loopback_active_notify(dpcm);
>  		break;
> @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>  {
>  	struct snd_pcm_runtime *runtime = substream->runtime;
>  	struct loopback_pcm *dpcm = runtime->private_data;
> +	unsigned long flags;
>  	snd_pcm_uframes_t pos;
>  
> -	spin_lock(&dpcm->cable->lock);
> +	spin_lock_irqsave(&dpcm->cable->lock, flags);
>  	loopback_pos_update(dpcm->cable);
>  	pos = dpcm->buf_pos;
> -	spin_unlock(&dpcm->cable->lock);
> +	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>  	return bytes_to_frames(runtime, pos);
>  }
>  
> @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>  	if (!cable)
>  		return;
>  	if (cable->streams[!substream->stream]) {
> +		unsigned long flags;
> +
>  		/* other stream is still alive */
> -		spin_lock_irq(&cable->lock);
> +		spin_lock_irqsave(&cable->lock, flags);
>  		cable->streams[substream->stream] = NULL;
> -		spin_unlock_irq(&cable->lock);
> +		spin_unlock_irqrestore(&cable->lock, flags);
>  	} else {
>  		/* free the cable */
>  		loopback->cables[substream->number][dev] = NULL;
> @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>  	struct loopback_cable *cable = NULL;
>  	int err = 0;
>  	int dev = get_cable_index(substream);
> +	unsigned long flags;
>  
>  	mutex_lock(&loopback->cable_lock);
>  	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
> @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>  	else
>  		runtime->hw = cable->hw;
>  
> -	spin_lock_irq(&cable->lock);
> +	spin_lock_irqsave(&cable->lock, flags);
>  	cable->streams[substream->stream] = dpcm;
> -	spin_unlock_irq(&cable->lock);
> +	spin_unlock_irqrestore(&cable->lock, flags);
>  
>   unlock:
>  	if (err < 0) {
> -- 
> 2.7.4
> 
> 

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
  2019-03-25 16:07     ` Takashi Iwai
  (?)
@ 2019-03-25 16:40     ` Timo Wischer
  2019-03-25 16:58         ` Takashi Iwai
  -1 siblings, 1 reply; 50+ messages in thread
From: Timo Wischer @ 2019-03-25 16:40 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, broonie, linux-kernel


Best regards
*Timo Wischer*
Engineering Software Base (ADITG/ESB)

Tel. +49 5121 49 6938
On 3/25/19 17:07, Takashi Iwai wrote:
> On Mon, 25 Mar 2019 17:00:38 +0100,
> <twischer@de.adit-jv.com> wrote:
>> From: Timo Wischer <twischer@de.adit-jv.com>
>>
>> to allow the usage of timer callbacks from interrupt context.
>> For example the sound timer.
> The trigger callback is already irq-disabled.  And, open/close must
> not be irq-disabled OTOH.  So these changes must be superfluous.

Hello Takashi,

could you explain why open/close must not be irq-disabled?
I see a potential deadlock in case of free_cable() uses only spin_lock() 
instead of spin_lock_irqsave().
For example the following will be executed:

loopback_close()
free_cable()
spin_lock(&dpcm->cable->lock)
 >> Interrupted by jiffies timer IRQ before calling spin_unlock()

loopback_jiffies_timer_function()
spin_lock_irqsave(&dpcm->cable->lock)
 >> DEADLOCK due to dpcm->cable->lock is already locked

Do you also see this deadlock or do you see any reason why this could 
not happen?

Best regards

Timo

>
>
> thanks,
>
> Takashi
>
>
>> Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>> ---
>>   sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>>   1 file changed, 19 insertions(+), 14 deletions(-)
>>
>> diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
>> index 11e8ed6..c6217c4 100644
>> --- a/sound/drivers/aloop.c
>> +++ b/sound/drivers/aloop.c
>> @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>>   	struct loopback_pcm *dpcm = runtime->private_data;
>>   	struct loopback_cable *cable = dpcm->cable;
>>   	int err = 0, stream = 1 << substream->stream;
>> +	unsigned long flags;
>>   
>>   	switch (cmd) {
>>   	case SNDRV_PCM_TRIGGER_START:
>> @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>>   		dpcm->last_jiffies = jiffies;
>>   		dpcm->pcm_rate_shift = 0;
>>   		dpcm->last_drift = 0;
>> -		spin_lock(&cable->lock);	
>> +		spin_lock_irqsave(&cable->lock, flags);
>>   		cable->running |= stream;
>>   		cable->pause &= ~stream;
>>   		err = loopback_timer_start(dpcm);
>> -		spin_unlock(&cable->lock);
>> +		spin_unlock_irqrestore(&cable->lock, flags);
>>   		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>   			loopback_active_notify(dpcm);
>>   		break;
>>   	case SNDRV_PCM_TRIGGER_STOP:
>> -		spin_lock(&cable->lock);	
>> +		spin_lock_irqsave(&cable->lock, flags);
>>   		cable->running &= ~stream;
>>   		cable->pause &= ~stream;
>>   		err = loopback_timer_stop(dpcm);
>> -		spin_unlock(&cable->lock);
>> +		spin_unlock_irqrestore(&cable->lock, flags);
>>   		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>   			loopback_active_notify(dpcm);
>>   		break;
>>   	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>>   	case SNDRV_PCM_TRIGGER_SUSPEND:
>> -		spin_lock(&cable->lock);	
>> +		spin_lock_irqsave(&cable->lock, flags);
>>   		cable->pause |= stream;
>>   		err = loopback_timer_stop(dpcm);
>> -		spin_unlock(&cable->lock);
>> +		spin_unlock_irqrestore(&cable->lock, flags);
>>   		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>   			loopback_active_notify(dpcm);
>>   		break;
>>   	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>>   	case SNDRV_PCM_TRIGGER_RESUME:
>> -		spin_lock(&cable->lock);
>> +		spin_lock_irqsave(&cable->lock, flags);
>>   		dpcm->last_jiffies = jiffies;
>>   		cable->pause &= ~stream;
>>   		err = loopback_timer_start(dpcm);
>> -		spin_unlock(&cable->lock);
>> +		spin_unlock_irqrestore(&cable->lock, flags);
>>   		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>   			loopback_active_notify(dpcm);
>>   		break;
>> @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>>   {
>>   	struct snd_pcm_runtime *runtime = substream->runtime;
>>   	struct loopback_pcm *dpcm = runtime->private_data;
>> +	unsigned long flags;
>>   	snd_pcm_uframes_t pos;
>>   
>> -	spin_lock(&dpcm->cable->lock);
>> +	spin_lock_irqsave(&dpcm->cable->lock, flags);
>>   	loopback_pos_update(dpcm->cable);
>>   	pos = dpcm->buf_pos;
>> -	spin_unlock(&dpcm->cable->lock);
>> +	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>>   	return bytes_to_frames(runtime, pos);
>>   }
>>   
>> @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>>   	if (!cable)
>>   		return;
>>   	if (cable->streams[!substream->stream]) {
>> +		unsigned long flags;
>> +
>>   		/* other stream is still alive */
>> -		spin_lock_irq(&cable->lock);
>> +		spin_lock_irqsave(&cable->lock, flags);
>>   		cable->streams[substream->stream] = NULL;
>> -		spin_unlock_irq(&cable->lock);
>> +		spin_unlock_irqrestore(&cable->lock, flags);
>>   	} else {
>>   		/* free the cable */
>>   		loopback->cables[substream->number][dev] = NULL;
>> @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>>   	struct loopback_cable *cable = NULL;
>>   	int err = 0;
>>   	int dev = get_cable_index(substream);
>> +	unsigned long flags;
>>   
>>   	mutex_lock(&loopback->cable_lock);
>>   	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
>> @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>>   	else
>>   		runtime->hw = cable->hw;
>>   
>> -	spin_lock_irq(&cable->lock);
>> +	spin_lock_irqsave(&cable->lock, flags);
>>   	cable->streams[substream->stream] = dpcm;
>> -	spin_unlock_irq(&cable->lock);
>> +	spin_unlock_irqrestore(&cable->lock, flags);
>>   
>>    unlock:
>>   	if (err < 0) {
>> -- 
>> 2.7.4
>>
>>

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
  2019-03-25 16:40     ` Timo Wischer
@ 2019-03-25 16:58         ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-25 16:58 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Mon, 25 Mar 2019 17:40:23 +0100,
Timo Wischer wrote:
> 
> Best regards
> Timo Wischer
> Engineering Software Base (ADITG/ESB)
> 
> Tel. +49 5121 49 6938
> On 3/25/19 17:07, Takashi Iwai wrote:
> 
>     On Mon, 25 Mar 2019 17:00:38 +0100,
>     <twischer@de.adit-jv.com> wrote:
>     
>         From: Timo Wischer <twischer@de.adit-jv.com>
>         
>         to allow the usage of timer callbacks from interrupt context.
>         For example the sound timer.
>         
>     The trigger callback is already irq-disabled.  And, open/close must
>     not be irq-disabled OTOH.  So these changes must be superfluous.
>     
> Hello Takashi,
> 
> could you explain why open/close must not be irq-disabled?

The open/close callbacks get called always in sleepable context where
you can use mutex & co gracefully.  The only non-sleepable (atomic)
context is pointer and trigger callbacks.  So it's safe to use
spin_lock() without irq in these callbacks.
OTOH, the rest (open, close,hw_params, hw_free, prepare) callbacks are
always sleepable, hence it's safe to use spin_lock_irq() (not
save/restore) variant.

When a driver declares as non-atomic via PCM flag, all callbacks
become sleepable, but this isn't the case for aloop driver.

> I see a potential deadlock in case of free_cable() uses only spin_lock()
> instead of spin_lock_irqsave().
> For example the following will be executed:
> 
> loopback_close()
> free_cable()
> spin_lock(&dpcm->cable->lock)

Which spin_lock() call?  The free_cable() calls spin_lock_irq().

> >> Interrupted by jiffies timer IRQ before calling spin_unlock()
> 
> loopback_jiffies_timer_function()
> spin_lock_irqsave(&dpcm->cable->lock)
> >> DEADLOCK due to dpcm->cable->lock is already locked
> 
> Do you also see this deadlock or do you see any reason why this could not
> happen?

If spin_lock_irq() is used in free_cable(), it must be OK.


Takashi


> Best regards
> 
> Timo
> 
>     thanks,
>     
>     Takashi
> 
>         Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>         ---
>          sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>          1 file changed, 19 insertions(+), 14 deletions(-)
>         
>         diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
>         index 11e8ed6..c6217c4 100644
>         --- a/sound/drivers/aloop.c
>         +++ b/sound/drivers/aloop.c
>         @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>               struct loopback_pcm *dpcm = runtime->private_data;
>               struct loopback_cable *cable = dpcm->cable;
>               int err = 0, stream = 1 << substream->stream;
>         +     unsigned long flags;
>          
>               switch (cmd) {
>               case SNDRV_PCM_TRIGGER_START:
>         @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>                                     dpcm->last_jiffies = jiffies;
>                                     dpcm->pcm_rate_shift = 0;
>                                     dpcm->last_drift = 0;
>         -                           spin_lock(&cable->lock);                    
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->running |= stream;
>                                     cable->pause &= ~stream;
>                                     err = loopback_timer_start(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>               case SNDRV_PCM_TRIGGER_STOP:
>         -                           spin_lock(&cable->lock);                    
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->running &= ~stream;
>                                     cable->pause &= ~stream;
>                                     err = loopback_timer_stop(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>               case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>               case SNDRV_PCM_TRIGGER_SUSPEND:
>         -                           spin_lock(&cable->lock);                    
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->pause |= stream;
>                                     err = loopback_timer_stop(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>               case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>               case SNDRV_PCM_TRIGGER_RESUME:
>         -                           spin_lock(&cable->lock);
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     dpcm->last_jiffies = jiffies;
>                                     cable->pause &= ~stream;
>                                     err = loopback_timer_start(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>         @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>          {
>               struct snd_pcm_runtime *runtime = substream->runtime;
>               struct loopback_pcm *dpcm = runtime->private_data;
>         +     unsigned long flags;
>               snd_pcm_uframes_t pos;
>          
>         -     spin_lock(&dpcm->cable->lock);
>         +     spin_lock_irqsave(&dpcm->cable->lock, flags);
>               loopback_pos_update(dpcm->cable);
>               pos = dpcm->buf_pos;
>         -     spin_unlock(&dpcm->cable->lock);
>         +     spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>               return bytes_to_frames(runtime, pos);
>          }
>          
>         @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>               if (!cable)
>                                     return;
>               if (cable->streams[!substream->stream]) {
>         +                           unsigned long flags;
>         +
>                                     /* other stream is still alive */
>         -                           spin_lock_irq(&cable->lock);
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->streams[substream->stream] = NULL;
>         -                           spin_unlock_irq(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>               } else {
>                                     /* free the cable */
>                                     loopback->cables[substream->number][dev] = NULL;
>         @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>               struct loopback_cable *cable = NULL;
>               int err = 0;
>               int dev = get_cable_index(substream);
>         +     unsigned long flags;
>          
>               mutex_lock(&loopback->cable_lock);
>               dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
>         @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>               else
>                                     runtime->hw = cable->hw;
>          
>         -     spin_lock_irq(&cable->lock);
>         +     spin_lock_irqsave(&cable->lock, flags);
>               cable->streams[substream->stream] = dpcm;
>         -     spin_unlock_irq(&cable->lock);
>         +     spin_unlock_irqrestore(&cable->lock, flags);
>          
>           unlock:
>               if (err < 0) {
>         -- 
>         2.7.4
> 
> 

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
@ 2019-03-25 16:58         ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-25 16:58 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Mon, 25 Mar 2019 17:40:23 +0100,
Timo Wischer wrote:
> 
> Best regards
> Timo Wischer
> Engineering Software Base (ADITG/ESB)
> 
> Tel. +49 5121 49 6938
> On 3/25/19 17:07, Takashi Iwai wrote:
> 
>     On Mon, 25 Mar 2019 17:00:38 +0100,
>     <twischer@de.adit-jv.com> wrote:
>     
>         From: Timo Wischer <twischer@de.adit-jv.com>
>         
>         to allow the usage of timer callbacks from interrupt context.
>         For example the sound timer.
>         
>     The trigger callback is already irq-disabled.  And, open/close must
>     not be irq-disabled OTOH.  So these changes must be superfluous.
>     
> Hello Takashi,
> 
> could you explain why open/close must not be irq-disabled?

The open/close callbacks get called always in sleepable context where
you can use mutex & co gracefully.  The only non-sleepable (atomic)
context is pointer and trigger callbacks.  So it's safe to use
spin_lock() without irq in these callbacks.
OTOH, the rest (open, close,hw_params, hw_free, prepare) callbacks are
always sleepable, hence it's safe to use spin_lock_irq() (not
save/restore) variant.

When a driver declares as non-atomic via PCM flag, all callbacks
become sleepable, but this isn't the case for aloop driver.

> I see a potential deadlock in case of free_cable() uses only spin_lock()
> instead of spin_lock_irqsave().
> For example the following will be executed:
> 
> loopback_close()
> free_cable()
> spin_lock(&dpcm->cable->lock)

Which spin_lock() call?  The free_cable() calls spin_lock_irq().

> >> Interrupted by jiffies timer IRQ before calling spin_unlock()
> 
> loopback_jiffies_timer_function()
> spin_lock_irqsave(&dpcm->cable->lock)
> >> DEADLOCK due to dpcm->cable->lock is already locked
> 
> Do you also see this deadlock or do you see any reason why this could not
> happen?

If spin_lock_irq() is used in free_cable(), it must be OK.


Takashi


> Best regards
> 
> Timo
> 
>     thanks,
>     
>     Takashi
> 
>         Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>         ---
>          sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>          1 file changed, 19 insertions(+), 14 deletions(-)
>         
>         diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
>         index 11e8ed6..c6217c4 100644
>         --- a/sound/drivers/aloop.c
>         +++ b/sound/drivers/aloop.c
>         @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>               struct loopback_pcm *dpcm = runtime->private_data;
>               struct loopback_cable *cable = dpcm->cable;
>               int err = 0, stream = 1 << substream->stream;
>         +     unsigned long flags;
>          
>               switch (cmd) {
>               case SNDRV_PCM_TRIGGER_START:
>         @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>                                     dpcm->last_jiffies = jiffies;
>                                     dpcm->pcm_rate_shift = 0;
>                                     dpcm->last_drift = 0;
>         -                           spin_lock(&cable->lock);                    
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->running |= stream;
>                                     cable->pause &= ~stream;
>                                     err = loopback_timer_start(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>               case SNDRV_PCM_TRIGGER_STOP:
>         -                           spin_lock(&cable->lock);                    
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->running &= ~stream;
>                                     cable->pause &= ~stream;
>                                     err = loopback_timer_stop(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>               case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>               case SNDRV_PCM_TRIGGER_SUSPEND:
>         -                           spin_lock(&cable->lock);                    
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->pause |= stream;
>                                     err = loopback_timer_stop(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>               case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>               case SNDRV_PCM_TRIGGER_RESUME:
>         -                           spin_lock(&cable->lock);
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     dpcm->last_jiffies = jiffies;
>                                     cable->pause &= ~stream;
>                                     err = loopback_timer_start(dpcm);
>         -                           spin_unlock(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>                                     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>                                                           loopback_active_notify(dpcm);
>                                     break;
>         @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>          {
>               struct snd_pcm_runtime *runtime = substream->runtime;
>               struct loopback_pcm *dpcm = runtime->private_data;
>         +     unsigned long flags;
>               snd_pcm_uframes_t pos;
>          
>         -     spin_lock(&dpcm->cable->lock);
>         +     spin_lock_irqsave(&dpcm->cable->lock, flags);
>               loopback_pos_update(dpcm->cable);
>               pos = dpcm->buf_pos;
>         -     spin_unlock(&dpcm->cable->lock);
>         +     spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>               return bytes_to_frames(runtime, pos);
>          }
>          
>         @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>               if (!cable)
>                                     return;
>               if (cable->streams[!substream->stream]) {
>         +                           unsigned long flags;
>         +
>                                     /* other stream is still alive */
>         -                           spin_lock_irq(&cable->lock);
>         +                           spin_lock_irqsave(&cable->lock, flags);
>                                     cable->streams[substream->stream] = NULL;
>         -                           spin_unlock_irq(&cable->lock);
>         +                           spin_unlock_irqrestore(&cable->lock, flags);
>               } else {
>                                     /* free the cable */
>                                     loopback->cables[substream->number][dev] = NULL;
>         @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>               struct loopback_cable *cable = NULL;
>               int err = 0;
>               int dev = get_cable_index(substream);
>         +     unsigned long flags;
>          
>               mutex_lock(&loopback->cable_lock);
>               dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
>         @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>               else
>                                     runtime->hw = cable->hw;
>          
>         -     spin_lock_irq(&cable->lock);
>         +     spin_lock_irqsave(&cable->lock, flags);
>               cable->streams[substream->stream] = dpcm;
>         -     spin_unlock_irq(&cable->lock);
>         +     spin_unlock_irqrestore(&cable->lock, flags);
>          
>           unlock:
>               if (err < 0) {
>         -- 
>         2.7.4
> 
> 

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

* [PATCH 05/10] ALSA: aloop: Use callback functions for timer specific implementations
  2019-03-25 16:00 ` twischer
@ 2019-03-26  7:49   ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This commit only refectors the implementation. It does not change the
behaviour.
It is required to support other timers (e.g sound timer).

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 113 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 94 insertions(+), 19 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index c6217c4..422d130 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -69,8 +69,39 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change
 
 #define NO_PITCH 100000
 
+struct loopback_cable;
 struct loopback_pcm;
 
+struct loopback_ops {
+	/* optional
+	 * call in loopback->cable_lock
+	 */
+	int (*open)(struct loopback_pcm *dpcm);
+	/* required
+	 * call in cable->lock
+	 */
+	int (*start)(struct loopback_pcm *dpcm);
+	/* required
+	 * call in cable->lock
+	 */
+	int (*stop)(struct loopback_pcm *dpcm);
+	/* optional */
+	int (*stop_sync)(struct loopback_pcm *dpcm);
+	/* optional */
+	int (*close_substream)(struct loopback_pcm *dpcm);
+	/* optional
+	 * call in loopback->cable_lock
+	 */
+	int (*close_cable)(struct loopback_pcm *dpcm);
+	/* optional
+	 * call in cable->lock
+	 */
+	unsigned int (*pos_update)(struct loopback_cable *cable);
+	/* optional */
+	void (*dpcm_info)(struct loopback_pcm *dpcm,
+			  struct snd_info_buffer *buffer);
+};
+
 struct loopback_cable {
 	spinlock_t lock;
 	struct loopback_pcm *streams[2];
@@ -79,6 +110,8 @@ struct loopback_cable {
 	unsigned int valid;
 	unsigned int running;
 	unsigned int pause;
+	/* timer specific */
+	struct loopback_ops *ops;
 };
 
 struct loopback_setup {
@@ -285,7 +318,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock_irqsave(&cable->lock, flags);
 		cable->running |= stream;
 		cable->pause &= ~stream;
-		err = loopback_timer_start(dpcm);
+		err = cable->ops->start(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -294,7 +327,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock_irqsave(&cable->lock, flags);
 		cable->running &= ~stream;
 		cable->pause &= ~stream;
-		err = loopback_timer_stop(dpcm);
+		err = cable->ops->stop(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -303,7 +336,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	case SNDRV_PCM_TRIGGER_SUSPEND:
 		spin_lock_irqsave(&cable->lock, flags);
 		cable->pause |= stream;
-		err = loopback_timer_stop(dpcm);
+		err = cable->ops->stop(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -313,7 +346,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock_irqsave(&cable->lock, flags);
 		dpcm->last_jiffies = jiffies;
 		cable->pause &= ~stream;
-		err = loopback_timer_start(dpcm);
+		err = cable->ops->start(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -344,9 +377,11 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
 	struct loopback_cable *cable = dpcm->cable;
 	int err, bps, salign;
 
-	err = loopback_timer_stop_sync(dpcm);
-	if (err < 0)
-		return err;
+	if (cable->ops->stop_sync) {
+		err = cable->ops->stop_sync(dpcm);
+		if (err < 0)
+			return err;
+	}
 
 	salign = (snd_pcm_format_width(runtime->format) *
 						runtime->channels) / 8;
@@ -554,6 +589,18 @@ static void loopback_timer_function(struct timer_list *t)
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 }
 
+static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
+					     struct snd_info_buffer *buffer)
+{
+	snd_iprintf(buffer, "    update_pending:\t%u\n",
+		    dpcm->period_update_pending);
+	snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
+	snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
+	snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
+		    dpcm->last_jiffies, jiffies);
+	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+}
+
 static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -562,7 +609,8 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 	snd_pcm_uframes_t pos;
 
 	spin_lock_irqsave(&dpcm->cable->lock, flags);
-	loopback_pos_update(dpcm->cable);
+	if (dpcm->cable->ops->pos_update)
+		dpcm->cable->ops->pos_update(dpcm->cable);
 	pos = dpcm->buf_pos;
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 	return bytes_to_frames(runtime, pos);
@@ -688,12 +736,33 @@ static void free_cable(struct snd_pcm_substream *substream)
 		cable->streams[substream->stream] = NULL;
 		spin_unlock_irqrestore(&cable->lock, flags);
 	} else {
+		struct loopback_pcm *dpcm = substream->runtime->private_data;
+
+		if (cable->ops && cable->ops->close_cable && dpcm)
+			cable->ops->close_cable(dpcm);
 		/* free the cable */
 		loopback->cables[substream->number][dev] = NULL;
 		kfree(cable);
 	}
 }
 
+static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
+{
+	timer_setup(&dpcm->timer, loopback_timer_function, 0);
+
+	return 0;
+}
+
+static struct loopback_ops loopback_jiffies_timer_ops = {
+	.open = loopback_jiffies_timer_open,
+	.start = loopback_timer_start,
+	.stop = loopback_timer_stop,
+	.stop_sync = loopback_timer_stop_sync,
+	.close_substream = loopback_timer_stop_sync,
+	.pos_update = loopback_pos_update,
+	.dpcm_info = loopback_jiffies_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -712,7 +781,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	}
 	dpcm->loopback = loopback;
 	dpcm->substream = substream;
-	timer_setup(&dpcm->timer, loopback_timer_function, 0);
 
 	cable = loopback->cables[substream->number][dev];
 	if (!cable) {
@@ -723,9 +791,17 @@ static int loopback_open(struct snd_pcm_substream *substream)
 		}
 		spin_lock_init(&cable->lock);
 		cable->hw = loopback_pcm_hardware;
+		cable->ops = &loopback_jiffies_timer_ops;
 		loopback->cables[substream->number][dev] = cable;
 	}
 	dpcm->cable = cable;
+	runtime->private_data = dpcm;
+
+	if (cable->ops->open) {
+		err = cable->ops->open(dpcm);
+		if (err < 0)
+			goto unlock;
+	}
 
 	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 
@@ -751,7 +827,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	if (err < 0)
 		goto unlock;
 
-	runtime->private_data = dpcm;
+	/* loopback_runtime_free() has not to be called if kfree(dpcm) was
+	 * already called here. Otherwise it will end up with a double free.
+	 */
 	runtime->private_free = loopback_runtime_free;
 	if (get_notify(dpcm))
 		runtime->hw = loopback_pcm_hardware;
@@ -775,12 +853,14 @@ static int loopback_close(struct snd_pcm_substream *substream)
 {
 	struct loopback *loopback = substream->private_data;
 	struct loopback_pcm *dpcm = substream->runtime->private_data;
+	int err = 0;
 
-	loopback_timer_stop_sync(dpcm);
+	if (dpcm->cable->ops->close_substream)
+		err = dpcm->cable->ops->close_substream(dpcm);
 	mutex_lock(&loopback->cable_lock);
 	free_cable(substream);
 	mutex_unlock(&loopback->cable_lock);
-	return 0;
+	return err;
 }
 
 static const struct snd_pcm_ops loopback_pcm_ops = {
@@ -1103,13 +1183,8 @@ static void print_dpcm_info(struct snd_info_buffer *buffer,
 	snd_iprintf(buffer, "    bytes_per_sec:\t%u\n", dpcm->pcm_bps);
 	snd_iprintf(buffer, "    sample_align:\t%u\n", dpcm->pcm_salign);
 	snd_iprintf(buffer, "    rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
-	snd_iprintf(buffer, "    update_pending:\t%u\n",
-						dpcm->period_update_pending);
-	snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
-	snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
-	snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
-					dpcm->last_jiffies, jiffies);
-	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+	if (dpcm->cable->ops->dpcm_info)
+		dpcm->cable->ops->dpcm_info(dpcm, buffer);
 }
 
 static void print_substream_info(struct snd_info_buffer *buffer,
-- 
2.7.4


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

* [PATCH 05/10] ALSA: aloop: Use callback functions for timer specific implementations
@ 2019-03-26  7:49   ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This commit only refectors the implementation. It does not change the
behaviour.
It is required to support other timers (e.g sound timer).

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 113 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 94 insertions(+), 19 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index c6217c4..422d130 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -69,8 +69,39 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change
 
 #define NO_PITCH 100000
 
+struct loopback_cable;
 struct loopback_pcm;
 
+struct loopback_ops {
+	/* optional
+	 * call in loopback->cable_lock
+	 */
+	int (*open)(struct loopback_pcm *dpcm);
+	/* required
+	 * call in cable->lock
+	 */
+	int (*start)(struct loopback_pcm *dpcm);
+	/* required
+	 * call in cable->lock
+	 */
+	int (*stop)(struct loopback_pcm *dpcm);
+	/* optional */
+	int (*stop_sync)(struct loopback_pcm *dpcm);
+	/* optional */
+	int (*close_substream)(struct loopback_pcm *dpcm);
+	/* optional
+	 * call in loopback->cable_lock
+	 */
+	int (*close_cable)(struct loopback_pcm *dpcm);
+	/* optional
+	 * call in cable->lock
+	 */
+	unsigned int (*pos_update)(struct loopback_cable *cable);
+	/* optional */
+	void (*dpcm_info)(struct loopback_pcm *dpcm,
+			  struct snd_info_buffer *buffer);
+};
+
 struct loopback_cable {
 	spinlock_t lock;
 	struct loopback_pcm *streams[2];
@@ -79,6 +110,8 @@ struct loopback_cable {
 	unsigned int valid;
 	unsigned int running;
 	unsigned int pause;
+	/* timer specific */
+	struct loopback_ops *ops;
 };
 
 struct loopback_setup {
@@ -285,7 +318,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock_irqsave(&cable->lock, flags);
 		cable->running |= stream;
 		cable->pause &= ~stream;
-		err = loopback_timer_start(dpcm);
+		err = cable->ops->start(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -294,7 +327,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock_irqsave(&cable->lock, flags);
 		cable->running &= ~stream;
 		cable->pause &= ~stream;
-		err = loopback_timer_stop(dpcm);
+		err = cable->ops->stop(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -303,7 +336,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 	case SNDRV_PCM_TRIGGER_SUSPEND:
 		spin_lock_irqsave(&cable->lock, flags);
 		cable->pause |= stream;
-		err = loopback_timer_stop(dpcm);
+		err = cable->ops->stop(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -313,7 +346,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
 		spin_lock_irqsave(&cable->lock, flags);
 		dpcm->last_jiffies = jiffies;
 		cable->pause &= ~stream;
-		err = loopback_timer_start(dpcm);
+		err = cable->ops->start(dpcm);
 		spin_unlock_irqrestore(&cable->lock, flags);
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			loopback_active_notify(dpcm);
@@ -344,9 +377,11 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
 	struct loopback_cable *cable = dpcm->cable;
 	int err, bps, salign;
 
-	err = loopback_timer_stop_sync(dpcm);
-	if (err < 0)
-		return err;
+	if (cable->ops->stop_sync) {
+		err = cable->ops->stop_sync(dpcm);
+		if (err < 0)
+			return err;
+	}
 
 	salign = (snd_pcm_format_width(runtime->format) *
 						runtime->channels) / 8;
@@ -554,6 +589,18 @@ static void loopback_timer_function(struct timer_list *t)
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 }
 
+static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
+					     struct snd_info_buffer *buffer)
+{
+	snd_iprintf(buffer, "    update_pending:\t%u\n",
+		    dpcm->period_update_pending);
+	snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
+	snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
+	snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
+		    dpcm->last_jiffies, jiffies);
+	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+}
+
 static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -562,7 +609,8 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 	snd_pcm_uframes_t pos;
 
 	spin_lock_irqsave(&dpcm->cable->lock, flags);
-	loopback_pos_update(dpcm->cable);
+	if (dpcm->cable->ops->pos_update)
+		dpcm->cable->ops->pos_update(dpcm->cable);
 	pos = dpcm->buf_pos;
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 	return bytes_to_frames(runtime, pos);
@@ -688,12 +736,33 @@ static void free_cable(struct snd_pcm_substream *substream)
 		cable->streams[substream->stream] = NULL;
 		spin_unlock_irqrestore(&cable->lock, flags);
 	} else {
+		struct loopback_pcm *dpcm = substream->runtime->private_data;
+
+		if (cable->ops && cable->ops->close_cable && dpcm)
+			cable->ops->close_cable(dpcm);
 		/* free the cable */
 		loopback->cables[substream->number][dev] = NULL;
 		kfree(cable);
 	}
 }
 
+static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
+{
+	timer_setup(&dpcm->timer, loopback_timer_function, 0);
+
+	return 0;
+}
+
+static struct loopback_ops loopback_jiffies_timer_ops = {
+	.open = loopback_jiffies_timer_open,
+	.start = loopback_timer_start,
+	.stop = loopback_timer_stop,
+	.stop_sync = loopback_timer_stop_sync,
+	.close_substream = loopback_timer_stop_sync,
+	.pos_update = loopback_pos_update,
+	.dpcm_info = loopback_jiffies_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -712,7 +781,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	}
 	dpcm->loopback = loopback;
 	dpcm->substream = substream;
-	timer_setup(&dpcm->timer, loopback_timer_function, 0);
 
 	cable = loopback->cables[substream->number][dev];
 	if (!cable) {
@@ -723,9 +791,17 @@ static int loopback_open(struct snd_pcm_substream *substream)
 		}
 		spin_lock_init(&cable->lock);
 		cable->hw = loopback_pcm_hardware;
+		cable->ops = &loopback_jiffies_timer_ops;
 		loopback->cables[substream->number][dev] = cable;
 	}
 	dpcm->cable = cable;
+	runtime->private_data = dpcm;
+
+	if (cable->ops->open) {
+		err = cable->ops->open(dpcm);
+		if (err < 0)
+			goto unlock;
+	}
 
 	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 
@@ -751,7 +827,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	if (err < 0)
 		goto unlock;
 
-	runtime->private_data = dpcm;
+	/* loopback_runtime_free() has not to be called if kfree(dpcm) was
+	 * already called here. Otherwise it will end up with a double free.
+	 */
 	runtime->private_free = loopback_runtime_free;
 	if (get_notify(dpcm))
 		runtime->hw = loopback_pcm_hardware;
@@ -775,12 +853,14 @@ static int loopback_close(struct snd_pcm_substream *substream)
 {
 	struct loopback *loopback = substream->private_data;
 	struct loopback_pcm *dpcm = substream->runtime->private_data;
+	int err = 0;
 
-	loopback_timer_stop_sync(dpcm);
+	if (dpcm->cable->ops->close_substream)
+		err = dpcm->cable->ops->close_substream(dpcm);
 	mutex_lock(&loopback->cable_lock);
 	free_cable(substream);
 	mutex_unlock(&loopback->cable_lock);
-	return 0;
+	return err;
 }
 
 static const struct snd_pcm_ops loopback_pcm_ops = {
@@ -1103,13 +1183,8 @@ static void print_dpcm_info(struct snd_info_buffer *buffer,
 	snd_iprintf(buffer, "    bytes_per_sec:\t%u\n", dpcm->pcm_bps);
 	snd_iprintf(buffer, "    sample_align:\t%u\n", dpcm->pcm_salign);
 	snd_iprintf(buffer, "    rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
-	snd_iprintf(buffer, "    update_pending:\t%u\n",
-						dpcm->period_update_pending);
-	snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
-	snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
-	snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
-					dpcm->last_jiffies, jiffies);
-	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+	if (dpcm->cable->ops->dpcm_info)
+		dpcm->cable->ops->dpcm_info(dpcm, buffer);
 }
 
 static void print_substream_info(struct snd_info_buffer *buffer,
-- 
2.7.4

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

* [PATCH 06/10] ALSA: aloop: Rename all jiffies timer specific functions
  2019-03-26  7:49   ` twischer
@ 2019-03-26  7:49     ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This commit does not change the behaviour. It only seperates the jiffies
timer specific implementation from the generic part.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 422d130..3105757 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -202,7 +202,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static int loopback_timer_start(struct loopback_pcm *dpcm)
+static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
 {
 	unsigned long tick;
 	unsigned int rate_shift = get_rate_shift(dpcm);
@@ -223,7 +223,7 @@ static int loopback_timer_start(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static inline int loopback_timer_stop(struct loopback_pcm *dpcm)
+static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 {
 	del_timer(&dpcm->timer);
 	dpcm->timer.expires = 0;
@@ -231,7 +231,7 @@ static inline int loopback_timer_stop(struct loopback_pcm *dpcm)
 	return 0;
 }
 
-static inline int loopback_timer_stop_sync(struct loopback_pcm *dpcm)
+static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 {
 	del_timer_sync(&dpcm->timer);
 
@@ -517,7 +517,8 @@ static inline void bytepos_finish(struct loopback_pcm *dpcm,
 }
 
 /* call in cable->lock */
-static unsigned int loopback_pos_update(struct loopback_cable *cable)
+static unsigned int loopback_jiffies_timer_pos_update
+		(struct loopback_cable *cable)
 {
 	struct loopback_pcm *dpcm_play =
 			cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
@@ -570,14 +571,15 @@ static unsigned int loopback_pos_update(struct loopback_cable *cable)
 	return running;
 }
 
-static void loopback_timer_function(struct timer_list *t)
+static void loopback_jiffies_timer_function(struct timer_list *t)
 {
 	struct loopback_pcm *dpcm = from_timer(dpcm, t, timer);
 	unsigned long flags;
 
 	spin_lock_irqsave(&dpcm->cable->lock, flags);
-	if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) {
-		loopback_timer_start(dpcm);
+	if (loopback_jiffies_timer_pos_update(dpcm->cable) &
+			(1 << dpcm->substream->stream)) {
+		loopback_jiffies_timer_start(dpcm);
 		if (dpcm->period_update_pending) {
 			dpcm->period_update_pending = 0;
 			spin_unlock_irqrestore(&dpcm->cable->lock, flags);
@@ -748,18 +750,18 @@ static void free_cable(struct snd_pcm_substream *substream)
 
 static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
 {
-	timer_setup(&dpcm->timer, loopback_timer_function, 0);
+	timer_setup(&dpcm->timer, loopback_jiffies_timer_function, 0);
 
 	return 0;
 }
 
 static struct loopback_ops loopback_jiffies_timer_ops = {
 	.open = loopback_jiffies_timer_open,
-	.start = loopback_timer_start,
-	.stop = loopback_timer_stop,
-	.stop_sync = loopback_timer_stop_sync,
-	.close_substream = loopback_timer_stop_sync,
-	.pos_update = loopback_pos_update,
+	.start = loopback_jiffies_timer_start,
+	.stop = loopback_jiffies_timer_stop,
+	.stop_sync = loopback_jiffies_timer_stop_sync,
+	.close_substream = loopback_jiffies_timer_stop_sync,
+	.pos_update = loopback_jiffies_timer_pos_update,
 	.dpcm_info = loopback_jiffies_timer_dpcm_info,
 };
 
-- 
2.7.4


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

* [PATCH 06/10] ALSA: aloop: Rename all jiffies timer specific functions
@ 2019-03-26  7:49     ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

This commit does not change the behaviour. It only seperates the jiffies
timer specific implementation from the generic part.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 422d130..3105757 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -202,7 +202,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static int loopback_timer_start(struct loopback_pcm *dpcm)
+static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
 {
 	unsigned long tick;
 	unsigned int rate_shift = get_rate_shift(dpcm);
@@ -223,7 +223,7 @@ static int loopback_timer_start(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static inline int loopback_timer_stop(struct loopback_pcm *dpcm)
+static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 {
 	del_timer(&dpcm->timer);
 	dpcm->timer.expires = 0;
@@ -231,7 +231,7 @@ static inline int loopback_timer_stop(struct loopback_pcm *dpcm)
 	return 0;
 }
 
-static inline int loopback_timer_stop_sync(struct loopback_pcm *dpcm)
+static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 {
 	del_timer_sync(&dpcm->timer);
 
@@ -517,7 +517,8 @@ static inline void bytepos_finish(struct loopback_pcm *dpcm,
 }
 
 /* call in cable->lock */
-static unsigned int loopback_pos_update(struct loopback_cable *cable)
+static unsigned int loopback_jiffies_timer_pos_update
+		(struct loopback_cable *cable)
 {
 	struct loopback_pcm *dpcm_play =
 			cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
@@ -570,14 +571,15 @@ static unsigned int loopback_pos_update(struct loopback_cable *cable)
 	return running;
 }
 
-static void loopback_timer_function(struct timer_list *t)
+static void loopback_jiffies_timer_function(struct timer_list *t)
 {
 	struct loopback_pcm *dpcm = from_timer(dpcm, t, timer);
 	unsigned long flags;
 
 	spin_lock_irqsave(&dpcm->cable->lock, flags);
-	if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) {
-		loopback_timer_start(dpcm);
+	if (loopback_jiffies_timer_pos_update(dpcm->cable) &
+			(1 << dpcm->substream->stream)) {
+		loopback_jiffies_timer_start(dpcm);
 		if (dpcm->period_update_pending) {
 			dpcm->period_update_pending = 0;
 			spin_unlock_irqrestore(&dpcm->cable->lock, flags);
@@ -748,18 +750,18 @@ static void free_cable(struct snd_pcm_substream *substream)
 
 static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
 {
-	timer_setup(&dpcm->timer, loopback_timer_function, 0);
+	timer_setup(&dpcm->timer, loopback_jiffies_timer_function, 0);
 
 	return 0;
 }
 
 static struct loopback_ops loopback_jiffies_timer_ops = {
 	.open = loopback_jiffies_timer_open,
-	.start = loopback_timer_start,
-	.stop = loopback_timer_stop,
-	.stop_sync = loopback_timer_stop_sync,
-	.close_substream = loopback_timer_stop_sync,
-	.pos_update = loopback_pos_update,
+	.start = loopback_jiffies_timer_start,
+	.stop = loopback_jiffies_timer_stop,
+	.stop_sync = loopback_jiffies_timer_stop_sync,
+	.close_substream = loopback_jiffies_timer_stop_sync,
+	.pos_update = loopback_jiffies_timer_pos_update,
 	.dpcm_info = loopback_jiffies_timer_dpcm_info,
 };
 
-- 
2.7.4

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

* [PATCH 07/10] ALSA: aloop: Move CABLE_VALID_BOTH to the top of file
  2019-03-26  7:49   ` twischer
@ 2019-03-26  7:49     ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

so all functions can use the same.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 3105757..85a1519 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -69,6 +69,10 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change
 
 #define NO_PITCH 100000
 
+#define CABLE_VALID_PLAYBACK	BIT(SNDRV_PCM_STREAM_PLAYBACK)
+#define CABLE_VALID_CAPTURE	BIT(SNDRV_PCM_STREAM_CAPTURE)
+#define CABLE_VALID_BOTH	(CABLE_VALID_PLAYBACK | CABLE_VALID_CAPTURE)
+
 struct loopback_cable;
 struct loopback_pcm;
 
@@ -238,10 +242,6 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 	return 0;
 }
 
-#define CABLE_VALID_PLAYBACK	(1 << SNDRV_PCM_STREAM_PLAYBACK)
-#define CABLE_VALID_CAPTURE	(1 << SNDRV_PCM_STREAM_CAPTURE)
-#define CABLE_VALID_BOTH	(CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
-
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
 	struct snd_pcm_runtime *runtime, *cruntime;
-- 
2.7.4


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

* [PATCH 07/10] ALSA: aloop: Move CABLE_VALID_BOTH to the top of file
@ 2019-03-26  7:49     ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

so all functions can use the same.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 3105757..85a1519 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -69,6 +69,10 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change
 
 #define NO_PITCH 100000
 
+#define CABLE_VALID_PLAYBACK	BIT(SNDRV_PCM_STREAM_PLAYBACK)
+#define CABLE_VALID_CAPTURE	BIT(SNDRV_PCM_STREAM_CAPTURE)
+#define CABLE_VALID_BOTH	(CABLE_VALID_PLAYBACK | CABLE_VALID_CAPTURE)
+
 struct loopback_cable;
 struct loopback_pcm;
 
@@ -238,10 +242,6 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 	return 0;
 }
 
-#define CABLE_VALID_PLAYBACK	(1 << SNDRV_PCM_STREAM_PLAYBACK)
-#define CABLE_VALID_CAPTURE	(1 << SNDRV_PCM_STREAM_CAPTURE)
-#define CABLE_VALID_BOTH	(CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
-
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
 	struct snd_pcm_runtime *runtime, *cruntime;
-- 
2.7.4

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

* [PATCH 08/10] ALSA: aloop: Support selection of snd_timer instead of jiffies
  2019-03-26  7:49   ` twischer
@ 2019-03-26  7:49     ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

to do synchronous audio forwarding between hardware sound card and aloop
devices. Such an audio route could look like the following:
Sound card -> Loopback application -> ALSA loop device -> arecord

In this case the loopback device should use the sound timer of the sound
card. Without this patch the loopback application has to implement an
adaptive sample rate converter to align the different clocks of the
different ALSA devices.

The used sound card can be selected when loading the module:
$ modprobe snd_aloop enable=1 timer_source=[card]
[card] can be replaced by the name of the sound card (e.g. I82801AAICH) or
a card number (e.g. 0).

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 410 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 409 insertions(+), 1 deletion(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 85a1519..a411aeb 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -36,12 +36,14 @@
 #include <linux/wait.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/stringify.h>
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/info.h>
 #include <sound/initval.h>
+#include <sound/timer.h>
 
 MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
 MODULE_DESCRIPTION("A loopback soundcard");
@@ -49,12 +51,15 @@ MODULE_LICENSE("GPL");
 MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");
 
 #define MAX_PCM_SUBSTREAMS	8
+#define TIMER_SRC_JIFFIES	-1
 
 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
 static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
 static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
 static int pcm_notify[SNDRV_CARDS];
+static char *timer_source[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] =
+					  __stringify(TIMER_SRC_JIFFIES)};
 
 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
@@ -67,6 +72,12 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
 module_param_array(pcm_notify, int, NULL, 0444);
 MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
 
+/* This parameter has to be kept read only because switch between jiffies and
+ * sound timer is not supported.
+ */
+module_param_array(timer_source, charp, NULL, 0444);
+MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default].");
+
 #define NO_PITCH 100000
 
 #define CABLE_VALID_PLAYBACK	BIT(SNDRV_PCM_STREAM_PLAYBACK)
@@ -116,6 +127,13 @@ struct loopback_cable {
 	unsigned int pause;
 	/* timer specific */
 	struct loopback_ops *ops;
+	/* When loopback.timer_source > TIMER_SRC_JIFFIES */
+	struct {
+		int owner;
+		struct snd_timer_id id;
+		struct tasklet_struct event_tasklet;
+		struct snd_timer_instance *instance;
+	} snd_timer;
 };
 
 struct loopback_setup {
@@ -136,6 +154,8 @@ struct loopback {
 	struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
 	struct snd_pcm *pcm[2];
 	struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
+	char *timer_source_name;
+	int timer_source;
 };
 
 struct loopback_pcm {
@@ -159,6 +179,7 @@ struct loopback_pcm {
 	unsigned int period_size_frac;	/* period size in jiffies ticks */
 	unsigned int last_drift;
 	unsigned long last_jiffies;
+	/* When loopback.timer_source <= TIMER_SRC_JIFFIES */
 	struct timer_list timer;
 };
 
@@ -227,6 +248,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
+static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
+{
+	int err;
+
+	/* Loopback device has to use same period as timer card. Therefore
+	 * wake up for each snd_pcm_period_elapsed() call of timer card.
+	 */
+	err = snd_timer_start(dpcm->cable->snd_timer.instance, 1);
+	if (err < 0) {
+		/* do not report error if trying to start but already
+		 * running. For example called by opposit substream
+		 * of the same cable
+		 */
+		if (err == -EBUSY)
+			return 0;
+
+		pcm_err(dpcm->substream->pcm, "snd_timer_start(card %d) failed with %d",
+			dpcm->cable->snd_timer.id.card, err);
+		return err;
+	}
+
+	return 0;
+}
+
+/* call in cable->lock */
 static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 {
 	del_timer(&dpcm->timer);
@@ -235,6 +281,24 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 	return 0;
 }
 
+/* call in cable->lock */
+static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
+{
+	int err;
+
+	/* only stop if both devices (playback and capture) are not running */
+	if (dpcm->cable->running)
+		return 0;
+	err = snd_timer_stop(dpcm->cable->snd_timer.instance);
+	if (err < 0) {
+		pcm_err(dpcm->substream->pcm, "snd_timer_stop(card %d) failed with %d",
+			dpcm->cable->snd_timer.id.card, err);
+		return err;
+	}
+
+	return 0;
+}
+
 static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 {
 	del_timer_sync(&dpcm->timer);
@@ -242,6 +306,29 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 	return 0;
 }
 
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+{
+	int err;
+
+	/* wait till drain tasklet has finished if requested */
+	tasklet_kill(&dpcm->cable->snd_timer.event_tasklet);
+
+	/* will only be called from free_cable() when other stream was
+	 * already closed. Other stream cannot be reopened as long as
+	 * loopback->cable_lock is locked. Therefore no need to lock
+	 * cable->lock;
+	 */
+	err = snd_timer_close(dpcm->cable->snd_timer.instance);
+	if (err < 0) {
+		pcm_err(dpcm->substream->pcm, "snd_timer_close(card %d) = %d",
+			dpcm->cable->snd_timer.id.card, err);
+	}
+	memset(&dpcm->cable->snd_timer, 0, sizeof(dpcm->cable->snd_timer));
+
+	return err;
+}
+
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
 	struct snd_pcm_runtime *runtime, *cruntime;
@@ -368,6 +455,13 @@ static void params_change(struct snd_pcm_substream *substream)
 	cable->hw.rate_max = runtime->rate;
 	cable->hw.channels_min = runtime->channels;
 	cable->hw.channels_max = runtime->channels;
+
+	if (dpcm->loopback->timer_source > TIMER_SRC_JIFFIES) {
+		cable->hw.period_bytes_min =
+				frames_to_bytes(runtime, runtime->period_size);
+		cable->hw.period_bytes_max = cable->hw.period_bytes_min;
+	}
+
 }
 
 static int loopback_prepare(struct snd_pcm_substream *substream)
@@ -591,6 +685,160 @@ static void loopback_jiffies_timer_function(struct timer_list *t)
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 }
 
+/* call in cable->lock */
+static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime,
+					       unsigned long resolution)
+{
+	if (resolution != runtime->timer_resolution) {
+		struct loopback_pcm *dpcm = runtime->private_data;
+		struct loopback_cable *cable = dpcm->cable;
+		/* Worst case estimation of possible values for resolution
+		 * resolution <= (512 * 1024) frames / 8kHz in nsec
+		 * resolution <= 65.536.000.000 nsec
+		 *
+		 * period_size <= 65.536.000.000 nsec / 1000nsec/usec * 192kHz +
+		 *  500.000
+		 * period_size <= 12.582.912.000.000  <64bit
+		 *  / 1.000.000 usec/sec
+		 */
+		const snd_pcm_uframes_t period_size_usec = resolution / 1000 *
+				runtime->rate;
+		/* round to nearest sample rate */
+		const snd_pcm_uframes_t period_size = (period_size_usec + 500 *
+						       1000) / (1000 * 1000);
+
+		pcm_err(dpcm->substream->pcm, "Period size (%lu frames) of loopback device is not corresponding to timer resolution (%lu nsec = %lu frames) of card %d. Use period size of %lu frames for loopback device.",
+			runtime->period_size, resolution,
+			period_size, cable->snd_timer.id.card, period_size);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void loopback_snd_timer_period_elapsed
+		(struct loopback_cable * const cable, const int event,
+		 const unsigned long resolution)
+{
+	struct loopback_pcm *dpcm_play =
+			cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	struct loopback_pcm *dpcm_capt =
+			cable->streams[SNDRV_PCM_STREAM_CAPTURE];
+	struct snd_pcm_runtime *valid_runtime;
+	unsigned int running, elapsed_bytes;
+	unsigned long flags;
+
+	spin_lock_irqsave(&cable->lock, flags);
+	running = cable->running ^ cable->pause;
+	/* no need to do anything if no stream is running */
+	if (!running) {
+		spin_unlock_irqrestore(&cable->lock, flags);
+		return;
+	}
+
+	if (event == SNDRV_TIMER_EVENT_MSTOP) {
+		if (!dpcm_play || !dpcm_play->substream ||
+		    !dpcm_play->substream->runtime ||
+		    !dpcm_play->substream->runtime->status ||
+		    dpcm_play->substream->runtime->status->state !=
+		    SNDRV_PCM_STATE_DRAINING) {
+			spin_unlock_irqrestore(&cable->lock, flags);
+			return;
+		}
+	}
+
+	valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
+				dpcm_play->substream->runtime :
+				dpcm_capt->substream->runtime;
+
+	/* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */
+	if (event == SNDRV_TIMER_EVENT_TICK) {
+		/* The hardware rules guarantee that playback and capture period
+		 * are the same. Therefore only one device has to be checked
+		 * here.
+		 */
+		if (loopback_snd_timer_check_resolution(valid_runtime,
+							resolution) < 0) {
+			spin_unlock_irqrestore(&cable->lock, flags);
+			if (cable->running & (1 << SNDRV_PCM_STREAM_PLAYBACK))
+				snd_pcm_stop_xrun(dpcm_play->substream);
+			if (cable->running & (1 << SNDRV_PCM_STREAM_CAPTURE))
+				snd_pcm_stop_xrun(dpcm_capt->substream);
+			return;
+		}
+	}
+
+	elapsed_bytes = frames_to_bytes(valid_runtime,
+					valid_runtime->period_size);
+	/* The same timer interrupt is used for playback and capture device */
+	if ((running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
+	    (running & (1 << SNDRV_PCM_STREAM_CAPTURE))) {
+		copy_play_buf(dpcm_play, dpcm_capt, elapsed_bytes);
+		bytepos_finish(dpcm_play, elapsed_bytes);
+		bytepos_finish(dpcm_capt, elapsed_bytes);
+	} else if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
+		bytepos_finish(dpcm_play, elapsed_bytes);
+	} else if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
+		clear_capture_buf(dpcm_capt, elapsed_bytes);
+		bytepos_finish(dpcm_capt, elapsed_bytes);
+	}
+	spin_unlock_irqrestore(&cable->lock, flags);
+
+	if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK))
+		snd_pcm_period_elapsed(dpcm_play->substream);
+	if (running & (1 << SNDRV_PCM_STREAM_CAPTURE))
+		snd_pcm_period_elapsed(dpcm_capt->substream);
+}
+
+static void loopback_snd_timer_function(struct snd_timer_instance *timeri,
+					unsigned long resolution,
+					unsigned long ticks)
+{
+	struct loopback_cable *cable = timeri->callback_data;
+
+	loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_TICK,
+					  resolution);
+}
+
+static void loopback_snd_timer_tasklet(unsigned long arg)
+{
+	struct snd_timer_instance *timeri = (struct snd_timer_instance *)arg;
+	struct loopback_cable *cable = timeri->callback_data;
+
+	loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_MSTOP, 0);
+}
+
+static void loopback_snd_timer_event(struct snd_timer_instance * const timeri,
+				     const int event,
+				     struct timespec * const tstamp,
+				     const unsigned long resolution)
+{
+	/* Do not lock cable->lock here because timer->lock is already hold.
+	 * There are other functions which first lock cable->lock and than
+	 * timer->lock e.g.
+	 * loopback_trigger()
+	 * spin_lock(&cable->lock)
+	 * loopback_snd_timer_start()
+	 * snd_timer_start()
+	 * spin_lock(&timer->lock)
+	 * Therefore when using the oposit order of locks here it could result
+	 * in a deadlock.
+	 */
+
+	if (event == SNDRV_TIMER_EVENT_MSTOP) {
+		struct loopback_cable *cable = timeri->callback_data;
+
+		/* sound card of the timer was stopped. Therefore there will not
+		 * be any further timer callbacks. Due to this forward audio
+		 * data from here if in draining state. When still in running
+		 * state the streaming will be aborted by the usual timeout. It
+		 * should not be aborted here because may be the timer sound
+		 * card does only a recovery and the timer is back soon.
+		 * This tasklet triggers loopback_snd_timer_tasklet()
+		 */
+		tasklet_schedule(&cable->snd_timer.event_tasklet);
+	}
+}
+
 static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
 					     struct snd_info_buffer *buffer)
 {
@@ -603,6 +851,18 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
 	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
 }
 
+static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm,
+					 struct snd_info_buffer *buffer)
+{
+	snd_iprintf(buffer, "    sound timer:\thw:%d,%d,%d\n",
+		    dpcm->cable->snd_timer.id.card,
+		    dpcm->cable->snd_timer.id.device,
+		    dpcm->cable->snd_timer.id.subdevice);
+	snd_iprintf(buffer, "    owner:\t\t%s\n",
+		    (dpcm->cable->snd_timer.owner == SNDRV_PCM_STREAM_CAPTURE) ?
+			    "capture" : "playback");
+}
+
 static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -721,6 +981,23 @@ static int rule_channels(struct snd_pcm_hw_params *params,
 	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
 }
 
+static int rule_period_bytes(struct snd_pcm_hw_params *params,
+			     struct snd_pcm_hw_rule *rule)
+{
+	struct loopback_pcm *dpcm = rule->private;
+	struct loopback_cable *cable = dpcm->cable;
+	struct snd_interval t;
+
+	mutex_lock(&dpcm->loopback->cable_lock);
+	t.min = cable->hw.period_bytes_min;
+	t.max = cable->hw.period_bytes_max;
+	mutex_unlock(&dpcm->loopback->cable_lock);
+	t.openmin = 0;
+	t.openmax = 0;
+	t.integer = 0;
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
 static void free_cable(struct snd_pcm_substream *substream)
 {
 	struct loopback *loopback = substream->private_data;
@@ -765,6 +1042,112 @@ static struct loopback_ops loopback_jiffies_timer_ops = {
 	.dpcm_info = loopback_jiffies_timer_dpcm_info,
 };
 
+static int loopback_timer_source_by_name(const char * const name,
+					 int * const number)
+{
+	return kstrtoint(name, 10, number);
+}
+
+static int loopback_snd_card_by_name(const char * const name)
+{
+	int number;
+
+	if (loopback_timer_source_by_name(name, &number) >= 0)
+		return number;
+
+	for (number = 0; number < ARRAY_SIZE(snd_cards); number++) {
+		const struct snd_card * const card = snd_cards[number];
+
+		if (card && strcmp(card->id, name) == 0)
+			return number;
+	}
+
+	return -EINVAL;
+}
+
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+{
+	int err = 0;
+	unsigned long flags;
+	struct snd_timer_id tid = {
+		.dev_class = SNDRV_TIMER_CLASS_PCM,
+		.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
+		.card = loopback_snd_card_by_name
+				(dpcm->loopback->timer_source_name),
+		.device = 0,
+		.subdevice = 0,
+	};
+	struct snd_timer_instance *timer = NULL;
+
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
+	dpcm->cable->snd_timer.owner = dpcm->substream->stream;
+	dpcm->cable->snd_timer.id = tid;
+
+	/* check if timer was already opened. It is only opened once
+	 * per playback and capture subdevice (aka cable).
+	 */
+	if (dpcm->cable->snd_timer.instance)
+		goto unlock;
+
+	/* snd_timer_close() and snd_timer_open() should not be called with
+	 * locked spinlock because both functions can block on a mutex. The
+	 * mutex loopback->cable_lock is kept locked. Therefore snd_timer_open()
+	 * cannot be called a second time by the other device of the same cable.
+	 * Therefore the following issue cannot happen:
+	 * [proc1] Call loopback_timer_open() ->
+	 *	   Unlock cable->lock for snd_timer_close/open() call
+	 * [proc2] Call loopback_timer_open() -> snd_timer_open(),
+	 *	   snd_timer_start()
+	 * [proc1] Call snd_timer_open() and overwrite running timer
+	 *	   instance
+	 */
+	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
+	err = snd_timer_open(&timer, dpcm->loopback->card->id,
+			     &dpcm->cable->snd_timer.id,
+			     current->pid);
+	if (err < 0) {
+		pcm_err(dpcm->substream->pcm, "Opening sound timer of card %d failed with %d",
+			dpcm->cable->snd_timer.id.card, err);
+		return err;
+	}
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
+
+	/* The callback has to be called from another tasklet. If
+	 * SNDRV_TIMER_IFLG_FAST is specified it will be called from the
+	 * snd_pcm_period_elapsed() call of the selected sound card.
+	 * snd_pcm_period_elapsed() helds snd_pcm_stream_lock_irqsave().
+	 * Due to our callback loopback_snd_timer_function() also calls
+	 * snd_pcm_period_elapsed() which calls snd_pcm_stream_lock_irqsave().
+	 * This would end up in a dead lock.
+	 */
+	timer->flags |= SNDRV_TIMER_IFLG_AUTO;
+	timer->callback = loopback_snd_timer_function;
+	timer->callback_data = (void *)dpcm->cable;
+	timer->ccallback = loopback_snd_timer_event;
+	dpcm->cable->snd_timer.instance = timer;
+
+	/* initialise a tasklet used for draining */
+	tasklet_init(&dpcm->cable->snd_timer.event_tasklet,
+		     loopback_snd_timer_tasklet, (unsigned long)timer);
+
+unlock:
+	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
+
+	return err;
+}
+
+/* stop_sync() is not required for sound timer because it does not need to be
+ * restarted in loopback_prepare() on Xrun recovery
+ */
+static struct loopback_ops loopback_snd_timer_ops = {
+	.open = loopback_snd_timer_open,
+	.start = loopback_snd_timer_start,
+	.stop = loopback_snd_timer_stop,
+	.close_cable = loopback_snd_timer_close_cable,
+	.dpcm_info = loopback_snd_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -793,7 +1176,10 @@ static int loopback_open(struct snd_pcm_substream *substream)
 		}
 		spin_lock_init(&cable->lock);
 		cable->hw = loopback_pcm_hardware;
-		cable->ops = &loopback_jiffies_timer_ops;
+		if (loopback->timer_source <= TIMER_SRC_JIFFIES)
+			cable->ops = &loopback_jiffies_timer_ops;
+		else
+			cable->ops = &loopback_snd_timer_ops;
 		loopback->cables[substream->number][dev] = cable;
 	}
 	dpcm->cable = cable;
@@ -829,6 +1215,19 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	if (err < 0)
 		goto unlock;
 
+	/* In case of sound timer the period time of both devices of the same
+	 * loop has to be the same. This rule only takes effect if a sound timer
+	 * was chosen via the kernel parameter.
+	 */
+	if (loopback->timer_source > TIMER_SRC_JIFFIES) {
+		err = snd_pcm_hw_rule_add(runtime, 0,
+					  SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					  rule_period_bytes, dpcm,
+					  SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1);
+		if (err < 0)
+			goto unlock;
+	}
+
 	/* loopback_runtime_free() has not to be called if kfree(dpcm) was
 	 * already called here. Otherwise it will end up with a double free.
 	 */
@@ -1250,6 +1649,15 @@ static int loopback_probe(struct platform_device *devptr)
 		pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
 	
 	loopback->card = card;
+	loopback->timer_source_name = timer_source[dev];
+	/* If it was not a number a sound card name was specified. Therefore a
+	 * the sound timer of this card should be used. The right card name will
+	 * be converted in loopback_snd_timer_open()
+	 */
+	if (loopback_timer_source_by_name(loopback->timer_source_name,
+					  &loopback->timer_source) < 0)
+		loopback->timer_source = 0;
+
 	mutex_init(&loopback->cable_lock);
 
 	err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
-- 
2.7.4


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

* [PATCH 08/10] ALSA: aloop: Support selection of snd_timer instead of jiffies
@ 2019-03-26  7:49     ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

to do synchronous audio forwarding between hardware sound card and aloop
devices. Such an audio route could look like the following:
Sound card -> Loopback application -> ALSA loop device -> arecord

In this case the loopback device should use the sound timer of the sound
card. Without this patch the loopback application has to implement an
adaptive sample rate converter to align the different clocks of the
different ALSA devices.

The used sound card can be selected when loading the module:
$ modprobe snd_aloop enable=1 timer_source=[card]
[card] can be replaced by the name of the sound card (e.g. I82801AAICH) or
a card number (e.g. 0).

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 410 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 409 insertions(+), 1 deletion(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 85a1519..a411aeb 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -36,12 +36,14 @@
 #include <linux/wait.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/stringify.h>
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/info.h>
 #include <sound/initval.h>
+#include <sound/timer.h>
 
 MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
 MODULE_DESCRIPTION("A loopback soundcard");
@@ -49,12 +51,15 @@ MODULE_LICENSE("GPL");
 MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");
 
 #define MAX_PCM_SUBSTREAMS	8
+#define TIMER_SRC_JIFFIES	-1
 
 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
 static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
 static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
 static int pcm_notify[SNDRV_CARDS];
+static char *timer_source[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] =
+					  __stringify(TIMER_SRC_JIFFIES)};
 
 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
@@ -67,6 +72,12 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
 module_param_array(pcm_notify, int, NULL, 0444);
 MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
 
+/* This parameter has to be kept read only because switch between jiffies and
+ * sound timer is not supported.
+ */
+module_param_array(timer_source, charp, NULL, 0444);
+MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default].");
+
 #define NO_PITCH 100000
 
 #define CABLE_VALID_PLAYBACK	BIT(SNDRV_PCM_STREAM_PLAYBACK)
@@ -116,6 +127,13 @@ struct loopback_cable {
 	unsigned int pause;
 	/* timer specific */
 	struct loopback_ops *ops;
+	/* When loopback.timer_source > TIMER_SRC_JIFFIES */
+	struct {
+		int owner;
+		struct snd_timer_id id;
+		struct tasklet_struct event_tasklet;
+		struct snd_timer_instance *instance;
+	} snd_timer;
 };
 
 struct loopback_setup {
@@ -136,6 +154,8 @@ struct loopback {
 	struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
 	struct snd_pcm *pcm[2];
 	struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
+	char *timer_source_name;
+	int timer_source;
 };
 
 struct loopback_pcm {
@@ -159,6 +179,7 @@ struct loopback_pcm {
 	unsigned int period_size_frac;	/* period size in jiffies ticks */
 	unsigned int last_drift;
 	unsigned long last_jiffies;
+	/* When loopback.timer_source <= TIMER_SRC_JIFFIES */
 	struct timer_list timer;
 };
 
@@ -227,6 +248,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
+static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
+{
+	int err;
+
+	/* Loopback device has to use same period as timer card. Therefore
+	 * wake up for each snd_pcm_period_elapsed() call of timer card.
+	 */
+	err = snd_timer_start(dpcm->cable->snd_timer.instance, 1);
+	if (err < 0) {
+		/* do not report error if trying to start but already
+		 * running. For example called by opposit substream
+		 * of the same cable
+		 */
+		if (err == -EBUSY)
+			return 0;
+
+		pcm_err(dpcm->substream->pcm, "snd_timer_start(card %d) failed with %d",
+			dpcm->cable->snd_timer.id.card, err);
+		return err;
+	}
+
+	return 0;
+}
+
+/* call in cable->lock */
 static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 {
 	del_timer(&dpcm->timer);
@@ -235,6 +281,24 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 	return 0;
 }
 
+/* call in cable->lock */
+static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
+{
+	int err;
+
+	/* only stop if both devices (playback and capture) are not running */
+	if (dpcm->cable->running)
+		return 0;
+	err = snd_timer_stop(dpcm->cable->snd_timer.instance);
+	if (err < 0) {
+		pcm_err(dpcm->substream->pcm, "snd_timer_stop(card %d) failed with %d",
+			dpcm->cable->snd_timer.id.card, err);
+		return err;
+	}
+
+	return 0;
+}
+
 static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 {
 	del_timer_sync(&dpcm->timer);
@@ -242,6 +306,29 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 	return 0;
 }
 
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+{
+	int err;
+
+	/* wait till drain tasklet has finished if requested */
+	tasklet_kill(&dpcm->cable->snd_timer.event_tasklet);
+
+	/* will only be called from free_cable() when other stream was
+	 * already closed. Other stream cannot be reopened as long as
+	 * loopback->cable_lock is locked. Therefore no need to lock
+	 * cable->lock;
+	 */
+	err = snd_timer_close(dpcm->cable->snd_timer.instance);
+	if (err < 0) {
+		pcm_err(dpcm->substream->pcm, "snd_timer_close(card %d) = %d",
+			dpcm->cable->snd_timer.id.card, err);
+	}
+	memset(&dpcm->cable->snd_timer, 0, sizeof(dpcm->cable->snd_timer));
+
+	return err;
+}
+
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
 	struct snd_pcm_runtime *runtime, *cruntime;
@@ -368,6 +455,13 @@ static void params_change(struct snd_pcm_substream *substream)
 	cable->hw.rate_max = runtime->rate;
 	cable->hw.channels_min = runtime->channels;
 	cable->hw.channels_max = runtime->channels;
+
+	if (dpcm->loopback->timer_source > TIMER_SRC_JIFFIES) {
+		cable->hw.period_bytes_min =
+				frames_to_bytes(runtime, runtime->period_size);
+		cable->hw.period_bytes_max = cable->hw.period_bytes_min;
+	}
+
 }
 
 static int loopback_prepare(struct snd_pcm_substream *substream)
@@ -591,6 +685,160 @@ static void loopback_jiffies_timer_function(struct timer_list *t)
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 }
 
+/* call in cable->lock */
+static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime,
+					       unsigned long resolution)
+{
+	if (resolution != runtime->timer_resolution) {
+		struct loopback_pcm *dpcm = runtime->private_data;
+		struct loopback_cable *cable = dpcm->cable;
+		/* Worst case estimation of possible values for resolution
+		 * resolution <= (512 * 1024) frames / 8kHz in nsec
+		 * resolution <= 65.536.000.000 nsec
+		 *
+		 * period_size <= 65.536.000.000 nsec / 1000nsec/usec * 192kHz +
+		 *  500.000
+		 * period_size <= 12.582.912.000.000  <64bit
+		 *  / 1.000.000 usec/sec
+		 */
+		const snd_pcm_uframes_t period_size_usec = resolution / 1000 *
+				runtime->rate;
+		/* round to nearest sample rate */
+		const snd_pcm_uframes_t period_size = (period_size_usec + 500 *
+						       1000) / (1000 * 1000);
+
+		pcm_err(dpcm->substream->pcm, "Period size (%lu frames) of loopback device is not corresponding to timer resolution (%lu nsec = %lu frames) of card %d. Use period size of %lu frames for loopback device.",
+			runtime->period_size, resolution,
+			period_size, cable->snd_timer.id.card, period_size);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void loopback_snd_timer_period_elapsed
+		(struct loopback_cable * const cable, const int event,
+		 const unsigned long resolution)
+{
+	struct loopback_pcm *dpcm_play =
+			cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	struct loopback_pcm *dpcm_capt =
+			cable->streams[SNDRV_PCM_STREAM_CAPTURE];
+	struct snd_pcm_runtime *valid_runtime;
+	unsigned int running, elapsed_bytes;
+	unsigned long flags;
+
+	spin_lock_irqsave(&cable->lock, flags);
+	running = cable->running ^ cable->pause;
+	/* no need to do anything if no stream is running */
+	if (!running) {
+		spin_unlock_irqrestore(&cable->lock, flags);
+		return;
+	}
+
+	if (event == SNDRV_TIMER_EVENT_MSTOP) {
+		if (!dpcm_play || !dpcm_play->substream ||
+		    !dpcm_play->substream->runtime ||
+		    !dpcm_play->substream->runtime->status ||
+		    dpcm_play->substream->runtime->status->state !=
+		    SNDRV_PCM_STATE_DRAINING) {
+			spin_unlock_irqrestore(&cable->lock, flags);
+			return;
+		}
+	}
+
+	valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
+				dpcm_play->substream->runtime :
+				dpcm_capt->substream->runtime;
+
+	/* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */
+	if (event == SNDRV_TIMER_EVENT_TICK) {
+		/* The hardware rules guarantee that playback and capture period
+		 * are the same. Therefore only one device has to be checked
+		 * here.
+		 */
+		if (loopback_snd_timer_check_resolution(valid_runtime,
+							resolution) < 0) {
+			spin_unlock_irqrestore(&cable->lock, flags);
+			if (cable->running & (1 << SNDRV_PCM_STREAM_PLAYBACK))
+				snd_pcm_stop_xrun(dpcm_play->substream);
+			if (cable->running & (1 << SNDRV_PCM_STREAM_CAPTURE))
+				snd_pcm_stop_xrun(dpcm_capt->substream);
+			return;
+		}
+	}
+
+	elapsed_bytes = frames_to_bytes(valid_runtime,
+					valid_runtime->period_size);
+	/* The same timer interrupt is used for playback and capture device */
+	if ((running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
+	    (running & (1 << SNDRV_PCM_STREAM_CAPTURE))) {
+		copy_play_buf(dpcm_play, dpcm_capt, elapsed_bytes);
+		bytepos_finish(dpcm_play, elapsed_bytes);
+		bytepos_finish(dpcm_capt, elapsed_bytes);
+	} else if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
+		bytepos_finish(dpcm_play, elapsed_bytes);
+	} else if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
+		clear_capture_buf(dpcm_capt, elapsed_bytes);
+		bytepos_finish(dpcm_capt, elapsed_bytes);
+	}
+	spin_unlock_irqrestore(&cable->lock, flags);
+
+	if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK))
+		snd_pcm_period_elapsed(dpcm_play->substream);
+	if (running & (1 << SNDRV_PCM_STREAM_CAPTURE))
+		snd_pcm_period_elapsed(dpcm_capt->substream);
+}
+
+static void loopback_snd_timer_function(struct snd_timer_instance *timeri,
+					unsigned long resolution,
+					unsigned long ticks)
+{
+	struct loopback_cable *cable = timeri->callback_data;
+
+	loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_TICK,
+					  resolution);
+}
+
+static void loopback_snd_timer_tasklet(unsigned long arg)
+{
+	struct snd_timer_instance *timeri = (struct snd_timer_instance *)arg;
+	struct loopback_cable *cable = timeri->callback_data;
+
+	loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_MSTOP, 0);
+}
+
+static void loopback_snd_timer_event(struct snd_timer_instance * const timeri,
+				     const int event,
+				     struct timespec * const tstamp,
+				     const unsigned long resolution)
+{
+	/* Do not lock cable->lock here because timer->lock is already hold.
+	 * There are other functions which first lock cable->lock and than
+	 * timer->lock e.g.
+	 * loopback_trigger()
+	 * spin_lock(&cable->lock)
+	 * loopback_snd_timer_start()
+	 * snd_timer_start()
+	 * spin_lock(&timer->lock)
+	 * Therefore when using the oposit order of locks here it could result
+	 * in a deadlock.
+	 */
+
+	if (event == SNDRV_TIMER_EVENT_MSTOP) {
+		struct loopback_cable *cable = timeri->callback_data;
+
+		/* sound card of the timer was stopped. Therefore there will not
+		 * be any further timer callbacks. Due to this forward audio
+		 * data from here if in draining state. When still in running
+		 * state the streaming will be aborted by the usual timeout. It
+		 * should not be aborted here because may be the timer sound
+		 * card does only a recovery and the timer is back soon.
+		 * This tasklet triggers loopback_snd_timer_tasklet()
+		 */
+		tasklet_schedule(&cable->snd_timer.event_tasklet);
+	}
+}
+
 static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
 					     struct snd_info_buffer *buffer)
 {
@@ -603,6 +851,18 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
 	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
 }
 
+static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm,
+					 struct snd_info_buffer *buffer)
+{
+	snd_iprintf(buffer, "    sound timer:\thw:%d,%d,%d\n",
+		    dpcm->cable->snd_timer.id.card,
+		    dpcm->cable->snd_timer.id.device,
+		    dpcm->cable->snd_timer.id.subdevice);
+	snd_iprintf(buffer, "    owner:\t\t%s\n",
+		    (dpcm->cable->snd_timer.owner == SNDRV_PCM_STREAM_CAPTURE) ?
+			    "capture" : "playback");
+}
+
 static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -721,6 +981,23 @@ static int rule_channels(struct snd_pcm_hw_params *params,
 	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
 }
 
+static int rule_period_bytes(struct snd_pcm_hw_params *params,
+			     struct snd_pcm_hw_rule *rule)
+{
+	struct loopback_pcm *dpcm = rule->private;
+	struct loopback_cable *cable = dpcm->cable;
+	struct snd_interval t;
+
+	mutex_lock(&dpcm->loopback->cable_lock);
+	t.min = cable->hw.period_bytes_min;
+	t.max = cable->hw.period_bytes_max;
+	mutex_unlock(&dpcm->loopback->cable_lock);
+	t.openmin = 0;
+	t.openmax = 0;
+	t.integer = 0;
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
 static void free_cable(struct snd_pcm_substream *substream)
 {
 	struct loopback *loopback = substream->private_data;
@@ -765,6 +1042,112 @@ static struct loopback_ops loopback_jiffies_timer_ops = {
 	.dpcm_info = loopback_jiffies_timer_dpcm_info,
 };
 
+static int loopback_timer_source_by_name(const char * const name,
+					 int * const number)
+{
+	return kstrtoint(name, 10, number);
+}
+
+static int loopback_snd_card_by_name(const char * const name)
+{
+	int number;
+
+	if (loopback_timer_source_by_name(name, &number) >= 0)
+		return number;
+
+	for (number = 0; number < ARRAY_SIZE(snd_cards); number++) {
+		const struct snd_card * const card = snd_cards[number];
+
+		if (card && strcmp(card->id, name) == 0)
+			return number;
+	}
+
+	return -EINVAL;
+}
+
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+{
+	int err = 0;
+	unsigned long flags;
+	struct snd_timer_id tid = {
+		.dev_class = SNDRV_TIMER_CLASS_PCM,
+		.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
+		.card = loopback_snd_card_by_name
+				(dpcm->loopback->timer_source_name),
+		.device = 0,
+		.subdevice = 0,
+	};
+	struct snd_timer_instance *timer = NULL;
+
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
+	dpcm->cable->snd_timer.owner = dpcm->substream->stream;
+	dpcm->cable->snd_timer.id = tid;
+
+	/* check if timer was already opened. It is only opened once
+	 * per playback and capture subdevice (aka cable).
+	 */
+	if (dpcm->cable->snd_timer.instance)
+		goto unlock;
+
+	/* snd_timer_close() and snd_timer_open() should not be called with
+	 * locked spinlock because both functions can block on a mutex. The
+	 * mutex loopback->cable_lock is kept locked. Therefore snd_timer_open()
+	 * cannot be called a second time by the other device of the same cable.
+	 * Therefore the following issue cannot happen:
+	 * [proc1] Call loopback_timer_open() ->
+	 *	   Unlock cable->lock for snd_timer_close/open() call
+	 * [proc2] Call loopback_timer_open() -> snd_timer_open(),
+	 *	   snd_timer_start()
+	 * [proc1] Call snd_timer_open() and overwrite running timer
+	 *	   instance
+	 */
+	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
+	err = snd_timer_open(&timer, dpcm->loopback->card->id,
+			     &dpcm->cable->snd_timer.id,
+			     current->pid);
+	if (err < 0) {
+		pcm_err(dpcm->substream->pcm, "Opening sound timer of card %d failed with %d",
+			dpcm->cable->snd_timer.id.card, err);
+		return err;
+	}
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
+
+	/* The callback has to be called from another tasklet. If
+	 * SNDRV_TIMER_IFLG_FAST is specified it will be called from the
+	 * snd_pcm_period_elapsed() call of the selected sound card.
+	 * snd_pcm_period_elapsed() helds snd_pcm_stream_lock_irqsave().
+	 * Due to our callback loopback_snd_timer_function() also calls
+	 * snd_pcm_period_elapsed() which calls snd_pcm_stream_lock_irqsave().
+	 * This would end up in a dead lock.
+	 */
+	timer->flags |= SNDRV_TIMER_IFLG_AUTO;
+	timer->callback = loopback_snd_timer_function;
+	timer->callback_data = (void *)dpcm->cable;
+	timer->ccallback = loopback_snd_timer_event;
+	dpcm->cable->snd_timer.instance = timer;
+
+	/* initialise a tasklet used for draining */
+	tasklet_init(&dpcm->cable->snd_timer.event_tasklet,
+		     loopback_snd_timer_tasklet, (unsigned long)timer);
+
+unlock:
+	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
+
+	return err;
+}
+
+/* stop_sync() is not required for sound timer because it does not need to be
+ * restarted in loopback_prepare() on Xrun recovery
+ */
+static struct loopback_ops loopback_snd_timer_ops = {
+	.open = loopback_snd_timer_open,
+	.start = loopback_snd_timer_start,
+	.stop = loopback_snd_timer_stop,
+	.close_cable = loopback_snd_timer_close_cable,
+	.dpcm_info = loopback_snd_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -793,7 +1176,10 @@ static int loopback_open(struct snd_pcm_substream *substream)
 		}
 		spin_lock_init(&cable->lock);
 		cable->hw = loopback_pcm_hardware;
-		cable->ops = &loopback_jiffies_timer_ops;
+		if (loopback->timer_source <= TIMER_SRC_JIFFIES)
+			cable->ops = &loopback_jiffies_timer_ops;
+		else
+			cable->ops = &loopback_snd_timer_ops;
 		loopback->cables[substream->number][dev] = cable;
 	}
 	dpcm->cable = cable;
@@ -829,6 +1215,19 @@ static int loopback_open(struct snd_pcm_substream *substream)
 	if (err < 0)
 		goto unlock;
 
+	/* In case of sound timer the period time of both devices of the same
+	 * loop has to be the same. This rule only takes effect if a sound timer
+	 * was chosen via the kernel parameter.
+	 */
+	if (loopback->timer_source > TIMER_SRC_JIFFIES) {
+		err = snd_pcm_hw_rule_add(runtime, 0,
+					  SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					  rule_period_bytes, dpcm,
+					  SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1);
+		if (err < 0)
+			goto unlock;
+	}
+
 	/* loopback_runtime_free() has not to be called if kfree(dpcm) was
 	 * already called here. Otherwise it will end up with a double free.
 	 */
@@ -1250,6 +1649,15 @@ static int loopback_probe(struct platform_device *devptr)
 		pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
 	
 	loopback->card = card;
+	loopback->timer_source_name = timer_source[dev];
+	/* If it was not a number a sound card name was specified. Therefore a
+	 * the sound timer of this card should be used. The right card name will
+	 * be converted in loopback_snd_timer_open()
+	 */
+	if (loopback_timer_source_by_name(loopback->timer_source_name,
+					  &loopback->timer_source) < 0)
+		loopback->timer_source = 0;
+
 	mutex_init(&loopback->cable_lock);
 
 	err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
-- 
2.7.4

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

* [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26  7:49   ` twischer
@ 2019-03-26  7:49     ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

snd_pcm_link() can be called by the user as long as the device is not
yet started. Therefore currently a driver which wants to iterate over
the linked substreams has to do this at the start trigger. But the start
trigger should not block for a long time. Therefore there is no callback
which can be used to iterate over the linked substreams without delaying
the start trigger.
This patch introduces a new callback function which will be called after
the linked substream list was updated by snd_pcm_link(). This callback
function is allowed to block for a longer time without interfering the
synchronized start up of linked substreams.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 include/sound/pcm.h     |  1 +
 sound/core/pcm_native.c | 29 +++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 18bd8c3..a7e5dd2 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -90,6 +90,7 @@ struct snd_pcm_ops {
 			     unsigned long offset);
 	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
 	int (*ack)(struct snd_pcm_substream *substream);
+	int (*link_changed)(struct snd_pcm_substream *substream);
 };
 
 /*
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index f731f90..57a8a66 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -1981,6 +1981,27 @@ static bool is_pcm_file(struct file *file)
 /*
  * PCM link handling
  */
+/* Note: call with snd_pcm_link_rwsem locked */
+static int  snd_pcm_link_changed(struct snd_pcm_substream * const substream)
+{
+	struct snd_pcm_substream *s;
+
+	/* snd_pcm_link_rwsem is down whenever the link_list is changed.
+	 * Therefore this lock is sufficent for the iteration.
+	 */
+	snd_pcm_group_for_each_entry(s, substream) {
+		int err;
+
+		if (!s->ops->link_changed)
+			continue;
+		err = s->ops->link_changed(s);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
 static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
 {
 	int res = 0;
@@ -2030,6 +2051,9 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
 	snd_pcm_group_assign(substream1, target_group);
 	snd_pcm_stream_unlock(substream1);
 	snd_pcm_group_unlock_irq(target_group, nonatomic);
+
+	if (res >= 0)
+		res = snd_pcm_link_changed(substream);
  _end:
 	up_write(&snd_pcm_link_rwsem);
  _nolock:
@@ -2076,6 +2100,11 @@ static int snd_pcm_unlink(struct snd_pcm_substream *substream)
 	snd_pcm_group_unlock_irq(group, nonatomic);
 	if (do_free)
 		kfree(group);
+	if (res >= 0)
+		res = snd_pcm_link_changed(substream);
+	/* Also signal substream which was removed */
+	if (res >= 0 && substream->ops->link_changed)
+		res = substream->ops->link_changed(substream);
 
        _end:
 	up_write(&snd_pcm_link_rwsem);
-- 
2.7.4


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

* [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-26  7:49     ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:49 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

snd_pcm_link() can be called by the user as long as the device is not
yet started. Therefore currently a driver which wants to iterate over
the linked substreams has to do this at the start trigger. But the start
trigger should not block for a long time. Therefore there is no callback
which can be used to iterate over the linked substreams without delaying
the start trigger.
This patch introduces a new callback function which will be called after
the linked substream list was updated by snd_pcm_link(). This callback
function is allowed to block for a longer time without interfering the
synchronized start up of linked substreams.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 include/sound/pcm.h     |  1 +
 sound/core/pcm_native.c | 29 +++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 18bd8c3..a7e5dd2 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -90,6 +90,7 @@ struct snd_pcm_ops {
 			     unsigned long offset);
 	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
 	int (*ack)(struct snd_pcm_substream *substream);
+	int (*link_changed)(struct snd_pcm_substream *substream);
 };
 
 /*
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index f731f90..57a8a66 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -1981,6 +1981,27 @@ static bool is_pcm_file(struct file *file)
 /*
  * PCM link handling
  */
+/* Note: call with snd_pcm_link_rwsem locked */
+static int  snd_pcm_link_changed(struct snd_pcm_substream * const substream)
+{
+	struct snd_pcm_substream *s;
+
+	/* snd_pcm_link_rwsem is down whenever the link_list is changed.
+	 * Therefore this lock is sufficent for the iteration.
+	 */
+	snd_pcm_group_for_each_entry(s, substream) {
+		int err;
+
+		if (!s->ops->link_changed)
+			continue;
+		err = s->ops->link_changed(s);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
 static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
 {
 	int res = 0;
@@ -2030,6 +2051,9 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
 	snd_pcm_group_assign(substream1, target_group);
 	snd_pcm_stream_unlock(substream1);
 	snd_pcm_group_unlock_irq(target_group, nonatomic);
+
+	if (res >= 0)
+		res = snd_pcm_link_changed(substream);
  _end:
 	up_write(&snd_pcm_link_rwsem);
  _nolock:
@@ -2076,6 +2100,11 @@ static int snd_pcm_unlink(struct snd_pcm_substream *substream)
 	snd_pcm_group_unlock_irq(group, nonatomic);
 	if (do_free)
 		kfree(group);
+	if (res >= 0)
+		res = snd_pcm_link_changed(substream);
+	/* Also signal substream which was removed */
+	if (res >= 0 && substream->ops->link_changed)
+		res = substream->ops->link_changed(substream);
 
        _end:
 	up_write(&snd_pcm_link_rwsem);
-- 
2.7.4

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

* [PATCH 10/10] ALSA: aloop: Use timer of linked card if chosen
  2019-03-25 16:00 ` twischer
@ 2019-03-26  7:52   ` twischer
  -1 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:52 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

If there is a hardware sound card linked to the loopback device
the sound timer of the hardware sound card will be used for this
loopback device. Such a link will be created when snd_pcm_link() was
called.
Linked dummy and loopback devices will be ignored.

This feature can be enabled when loading the module with the following
command:
$ modprobe snd_aloop enable=1 timer_source=-1

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 172 insertions(+), 15 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index a411aeb..e2f1d64 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -51,7 +51,8 @@ MODULE_LICENSE("GPL");
 MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");
 
 #define MAX_PCM_SUBSTREAMS	8
-#define TIMER_SRC_JIFFIES	-1
+#define TIMER_SRC_JIFFIES	-2
+#define TIMER_SRC_AUTODETECT	-1
 
 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
@@ -76,7 +77,7 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change
  * sound timer is not supported.
  */
 module_param_array(timer_source, charp, NULL, 0444);
-MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default].");
+MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default]. -1 auto detect sound timer.");
 
 #define NO_PITCH 100000
 
@@ -252,6 +253,14 @@ static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
 {
 	int err;
 
+	/* only in auto detect mode the sound timer will not be opened when the
+	 * device was opened. It will be opened on the first snd_pcm_link() call
+	 */
+	if (!dpcm->cable->snd_timer.instance) {
+		pcm_err(dpcm->substream->pcm, "Sound timer not auto detected. At least one application of the same loopback cable has to call snd_pcm_link() to detect the sound timer.");
+		return -EINVAL;
+	}
+
 	/* Loopback device has to use same period as timer card. Therefore
 	 * wake up for each snd_pcm_period_elapsed() call of timer card.
 	 */
@@ -286,6 +295,10 @@ static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
 {
 	int err;
 
+	/* no need to stop the timer if it was not yet opened */
+	if (!dpcm->cable->snd_timer.instance)
+		return 0;
+
 	/* only stop if both devices (playback and capture) are not running */
 	if (dpcm->cable->running)
 		return 0;
@@ -307,10 +320,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 }
 
 /* call in loopback->cable_lock */
-static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+static int loopback_snd_timer_close(struct loopback_pcm *dpcm,
+				    struct snd_timer_instance * const timer)
 {
 	int err;
 
+	/* no need to close the timer if it was not yet opened */
+	if (!timer)
+		return 0;
+
 	/* wait till drain tasklet has finished if requested */
 	tasklet_kill(&dpcm->cable->snd_timer.event_tasklet);
 
@@ -319,7 +337,7 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
 	 * loopback->cable_lock is locked. Therefore no need to lock
 	 * cable->lock;
 	 */
-	err = snd_timer_close(dpcm->cable->snd_timer.instance);
+	err = snd_timer_close(timer);
 	if (err < 0) {
 		pcm_err(dpcm->substream->pcm, "snd_timer_close(card %d) = %d",
 			dpcm->cable->snd_timer.id.card, err);
@@ -329,6 +347,12 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
 	return err;
 }
 
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+{
+	return loopback_snd_timer_close(dpcm, dpcm->cable->snd_timer.instance);
+}
+
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
 	struct snd_pcm_runtime *runtime, *cruntime;
@@ -1065,11 +1089,11 @@ static int loopback_snd_card_by_name(const char * const name)
 	return -EINVAL;
 }
 
-/* call in loopback->cable_lock */
-static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+/* call in cable->lock */
+static int loopback_snd_timer_source_update(struct loopback_pcm *dpcm)
 {
-	int err = 0;
-	unsigned long flags;
+	int changed = 0;
+	struct snd_pcm_substream *s;
 	struct snd_timer_id tid = {
 		.dev_class = SNDRV_TIMER_CLASS_PCM,
 		.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
@@ -1078,18 +1102,109 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
 		.device = 0,
 		.subdevice = 0,
 	};
-	struct snd_timer_instance *timer = NULL;
 
-	spin_lock_irqsave(&dpcm->cable->lock, flags);
+	/* Enforce kernel module parameter if not auto detect */
+	if (tid.card != TIMER_SRC_AUTODETECT) {
+		dpcm->cable->snd_timer.owner = dpcm->substream->stream;
+		dpcm->cable->snd_timer.id = tid;
+		return 0;
+	}
+
+	/* find the first HW device which is linked to this loop device */
+	snd_pcm_group_for_each_entry(s, dpcm->substream) {
+		/* ignore all linked devices also using jiffies timer */
+		if (strcmp(s->pcm->card->driver, "Loopback") == 0)
+			continue;
+		if (strcmp(s->pcm->card->driver, "Dummy") == 0)
+			continue;
+
+		tid.card = s->pcm->card->number;
+		tid.device = s->pcm->device;
+		tid.subdevice = s->number;
+		break;
+	}
+
+	/* check if a sound timer could already be detected */
+	if (tid.card < 0)
+		return -ENODEV;
+
+	/* check if timer source has changed */
+	changed = (dpcm->cable->snd_timer.id.card != tid.card ||
+		   dpcm->cable->snd_timer.id.device != tid.device ||
+		   dpcm->cable->snd_timer.id.subdevice != tid.subdevice);
+	/* Do not change anything if we are the second device
+	 * which calls snd_pcm_link()
+	 */
+	if (changed) {
+		if (dpcm->cable->snd_timer.owner > 0 &&
+		    dpcm->cable->snd_timer.owner != dpcm->substream->stream) {
+			pcm_warn(dpcm->substream->pcm, "Both devices of the same loopback cable are requesting different sound timers. May be the wrong one is used. (used hw:%d,%d,%d reqested hw:%d,%d,%d)",
+				 dpcm->cable->snd_timer.id.card,
+				 dpcm->cable->snd_timer.id.device,
+				 dpcm->cable->snd_timer.id.subdevice, tid.card,
+				 tid.device, tid.subdevice);
+			return 0;
+		} else if (dpcm->cable->running) {
+			pcm_warn(dpcm->substream->pcm, "Another sound timer was requested but at least one device is already running. May be the wrong one is used. (used hw:%d,%d,%d reqested hw:%d,%d,%d)",
+				 dpcm->cable->snd_timer.id.card,
+				 dpcm->cable->snd_timer.id.device,
+				 dpcm->cable->snd_timer.id.subdevice, tid.card,
+				 tid.device, tid.subdevice);
+			return 0;
+		}
+	}
+
+	/* always return a valid  timer id with defined classes. Also in case
+	 * when it looks like card has not changed because hw:0,0,0 should be
+	 * used
+	 */
 	dpcm->cable->snd_timer.owner = dpcm->substream->stream;
 	dpcm->cable->snd_timer.id = tid;
 
-	/* check if timer was already opened. It is only opened once
-	 * per playback and capture subdevice (aka cable).
+	return changed;
+}
+
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+{
+	int err = 0;
+	unsigned long flags;
+	struct snd_timer_instance *timer = NULL;
+
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
+	err = loopback_snd_timer_source_update(dpcm);
+	if (err < 0) {
+		/* Could not yet detect the right sound timer because no valid
+		 * snd_pcm_link() exists. This should not be handled as an error
+		 * because the right timer will be opened with the call to
+		 * snd_pcm_link().
+		 */
+		if (err == -ENODEV)
+			err = 0;
+		goto unlock;
+	}
+	/* do not reopen the timer if it is already opened and nothing has
+	 * changed
 	 */
-	if (dpcm->cable->snd_timer.instance)
+	if (dpcm->cable->snd_timer.instance && err < 1)
 		goto unlock;
 
+	/* Avoids that any function accesses an invalid timer instance when
+	 * reopening the sound timer. Reopening the sound timer is only
+	 * supported in TIMER_SRC_AUTODETECT mode. snd_pcm_start() will fail on
+	 * the second device when the first device currently reopens the_timer:
+	 * [proc1] Calls snd_pcm_link() -> loopback_timer_open() ->
+	 *	   Unlock cable->lock for snd_timer_close/open() call
+	 * [proc2] Calls snd_pcm_start() when timer reopening is in progress
+	 * But this is fine because snd_pcm_start() would also fail if
+	 * snd_pcm_link() was not called from any device of the same cable in
+	 * TIMER_SRC_AUTODETECT mode. Therefore the user has to guarantee in
+	 * TIMER_SRC_AUTODETECT mode that snd_pcm_link() is called before anyone
+	 * calls snd_pcm_start() of the same cable.
+	 */
+	timer = dpcm->cable->snd_timer.instance;
+	dpcm->cable->snd_timer.instance = NULL;
+
 	/* snd_timer_close() and snd_timer_open() should not be called with
 	 * locked spinlock because both functions can block on a mutex. The
 	 * mutex loopback->cable_lock is kept locked. Therefore snd_timer_open()
@@ -1103,6 +1218,10 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
 	 *	   instance
 	 */
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
+	/* close timer if there is already something open */
+	err = loopback_snd_timer_close(dpcm, timer);
+	if (err < 0)
+		return err;
 	err = snd_timer_open(&timer, dpcm->loopback->card->id,
 			     &dpcm->cable->snd_timer.id,
 			     current->pid);
@@ -1137,6 +1256,19 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
 	return err;
 }
 
+static int loopback_snd_timer_link_changed(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct loopback_pcm *dpcm = substream->runtime->private_data;
+
+	mutex_lock(&dpcm->loopback->cable_lock);
+	/* Try to reopen the timer here if the link_list has changed. */
+	err = loopback_snd_timer_open(dpcm);
+	mutex_unlock(&dpcm->loopback->cable_lock);
+
+	return err;
+}
+
 /* stop_sync() is not required for sound timer because it does not need to be
  * restarted in loopback_prepare() on Xrun recovery
  */
@@ -1148,6 +1280,13 @@ static struct loopback_ops loopback_snd_timer_ops = {
 	.dpcm_info = loopback_snd_timer_dpcm_info,
 };
 
+static struct loopback_ops loopback_snd_timer_auto_ops = {
+	.start = loopback_snd_timer_start,
+	.stop = loopback_snd_timer_stop,
+	.close_cable = loopback_snd_timer_close_cable,
+	.dpcm_info = loopback_snd_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -1178,6 +1317,8 @@ static int loopback_open(struct snd_pcm_substream *substream)
 		cable->hw = loopback_pcm_hardware;
 		if (loopback->timer_source <= TIMER_SRC_JIFFIES)
 			cable->ops = &loopback_jiffies_timer_ops;
+		else if (loopback->timer_source == TIMER_SRC_AUTODETECT)
+			cable->ops = &loopback_snd_timer_auto_ops;
 		else
 			cable->ops = &loopback_snd_timer_ops;
 		loopback->cables[substream->number][dev] = cable;
@@ -1276,18 +1417,34 @@ static const struct snd_pcm_ops loopback_pcm_ops = {
 	.page =		snd_pcm_lib_get_vmalloc_page,
 };
 
+static const struct snd_pcm_ops loopback_pcm_auto_ops = {
+	.open =		loopback_open,
+	.close =	loopback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	loopback_hw_params,
+	.hw_free =	loopback_hw_free,
+	.prepare =	loopback_prepare,
+	.trigger =	loopback_trigger,
+	.pointer =	loopback_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.link_changed =	loopback_snd_timer_link_changed,
+};
+
 static int loopback_pcm_new(struct loopback *loopback,
 			    int device, int substreams)
 {
 	struct snd_pcm *pcm;
 	int err;
+	const struct snd_pcm_ops * const ops =
+			(loopback->timer_source == TIMER_SRC_AUTODETECT) ?
+				&loopback_pcm_auto_ops : &loopback_pcm_ops;
 
 	err = snd_pcm_new(loopback->card, "Loopback PCM", device,
 			  substreams, substreams, &pcm);
 	if (err < 0)
 		return err;
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops);
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops);
 
 	pcm->private_data = loopback;
 	pcm->info_flags = 0;
-- 
2.7.4


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

* [PATCH 10/10] ALSA: aloop: Use timer of linked card if chosen
@ 2019-03-26  7:52   ` twischer
  0 siblings, 0 replies; 50+ messages in thread
From: twischer @ 2019-03-26  7:52 UTC (permalink / raw)
  To: patch, broonie, perex, tiwai; +Cc: alsa-devel, linux-kernel, Timo Wischer

From: Timo Wischer <twischer@de.adit-jv.com>

If there is a hardware sound card linked to the loopback device
the sound timer of the hardware sound card will be used for this
loopback device. Such a link will be created when snd_pcm_link() was
called.
Linked dummy and loopback devices will be ignored.

This feature can be enabled when loading the module with the following
command:
$ modprobe snd_aloop enable=1 timer_source=-1

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---
 sound/drivers/aloop.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 172 insertions(+), 15 deletions(-)

diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index a411aeb..e2f1d64 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -51,7 +51,8 @@ MODULE_LICENSE("GPL");
 MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");
 
 #define MAX_PCM_SUBSTREAMS	8
-#define TIMER_SRC_JIFFIES	-1
+#define TIMER_SRC_JIFFIES	-2
+#define TIMER_SRC_AUTODETECT	-1
 
 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
@@ -76,7 +77,7 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change
  * sound timer is not supported.
  */
 module_param_array(timer_source, charp, NULL, 0444);
-MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default].");
+MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default]. -1 auto detect sound timer.");
 
 #define NO_PITCH 100000
 
@@ -252,6 +253,14 @@ static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
 {
 	int err;
 
+	/* only in auto detect mode the sound timer will not be opened when the
+	 * device was opened. It will be opened on the first snd_pcm_link() call
+	 */
+	if (!dpcm->cable->snd_timer.instance) {
+		pcm_err(dpcm->substream->pcm, "Sound timer not auto detected. At least one application of the same loopback cable has to call snd_pcm_link() to detect the sound timer.");
+		return -EINVAL;
+	}
+
 	/* Loopback device has to use same period as timer card. Therefore
 	 * wake up for each snd_pcm_period_elapsed() call of timer card.
 	 */
@@ -286,6 +295,10 @@ static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
 {
 	int err;
 
+	/* no need to stop the timer if it was not yet opened */
+	if (!dpcm->cable->snd_timer.instance)
+		return 0;
+
 	/* only stop if both devices (playback and capture) are not running */
 	if (dpcm->cable->running)
 		return 0;
@@ -307,10 +320,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 }
 
 /* call in loopback->cable_lock */
-static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+static int loopback_snd_timer_close(struct loopback_pcm *dpcm,
+				    struct snd_timer_instance * const timer)
 {
 	int err;
 
+	/* no need to close the timer if it was not yet opened */
+	if (!timer)
+		return 0;
+
 	/* wait till drain tasklet has finished if requested */
 	tasklet_kill(&dpcm->cable->snd_timer.event_tasklet);
 
@@ -319,7 +337,7 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
 	 * loopback->cable_lock is locked. Therefore no need to lock
 	 * cable->lock;
 	 */
-	err = snd_timer_close(dpcm->cable->snd_timer.instance);
+	err = snd_timer_close(timer);
 	if (err < 0) {
 		pcm_err(dpcm->substream->pcm, "snd_timer_close(card %d) = %d",
 			dpcm->cable->snd_timer.id.card, err);
@@ -329,6 +347,12 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
 	return err;
 }
 
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+{
+	return loopback_snd_timer_close(dpcm, dpcm->cable->snd_timer.instance);
+}
+
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
 	struct snd_pcm_runtime *runtime, *cruntime;
@@ -1065,11 +1089,11 @@ static int loopback_snd_card_by_name(const char * const name)
 	return -EINVAL;
 }
 
-/* call in loopback->cable_lock */
-static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+/* call in cable->lock */
+static int loopback_snd_timer_source_update(struct loopback_pcm *dpcm)
 {
-	int err = 0;
-	unsigned long flags;
+	int changed = 0;
+	struct snd_pcm_substream *s;
 	struct snd_timer_id tid = {
 		.dev_class = SNDRV_TIMER_CLASS_PCM,
 		.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
@@ -1078,18 +1102,109 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
 		.device = 0,
 		.subdevice = 0,
 	};
-	struct snd_timer_instance *timer = NULL;
 
-	spin_lock_irqsave(&dpcm->cable->lock, flags);
+	/* Enforce kernel module parameter if not auto detect */
+	if (tid.card != TIMER_SRC_AUTODETECT) {
+		dpcm->cable->snd_timer.owner = dpcm->substream->stream;
+		dpcm->cable->snd_timer.id = tid;
+		return 0;
+	}
+
+	/* find the first HW device which is linked to this loop device */
+	snd_pcm_group_for_each_entry(s, dpcm->substream) {
+		/* ignore all linked devices also using jiffies timer */
+		if (strcmp(s->pcm->card->driver, "Loopback") == 0)
+			continue;
+		if (strcmp(s->pcm->card->driver, "Dummy") == 0)
+			continue;
+
+		tid.card = s->pcm->card->number;
+		tid.device = s->pcm->device;
+		tid.subdevice = s->number;
+		break;
+	}
+
+	/* check if a sound timer could already be detected */
+	if (tid.card < 0)
+		return -ENODEV;
+
+	/* check if timer source has changed */
+	changed = (dpcm->cable->snd_timer.id.card != tid.card ||
+		   dpcm->cable->snd_timer.id.device != tid.device ||
+		   dpcm->cable->snd_timer.id.subdevice != tid.subdevice);
+	/* Do not change anything if we are the second device
+	 * which calls snd_pcm_link()
+	 */
+	if (changed) {
+		if (dpcm->cable->snd_timer.owner > 0 &&
+		    dpcm->cable->snd_timer.owner != dpcm->substream->stream) {
+			pcm_warn(dpcm->substream->pcm, "Both devices of the same loopback cable are requesting different sound timers. May be the wrong one is used. (used hw:%d,%d,%d reqested hw:%d,%d,%d)",
+				 dpcm->cable->snd_timer.id.card,
+				 dpcm->cable->snd_timer.id.device,
+				 dpcm->cable->snd_timer.id.subdevice, tid.card,
+				 tid.device, tid.subdevice);
+			return 0;
+		} else if (dpcm->cable->running) {
+			pcm_warn(dpcm->substream->pcm, "Another sound timer was requested but at least one device is already running. May be the wrong one is used. (used hw:%d,%d,%d reqested hw:%d,%d,%d)",
+				 dpcm->cable->snd_timer.id.card,
+				 dpcm->cable->snd_timer.id.device,
+				 dpcm->cable->snd_timer.id.subdevice, tid.card,
+				 tid.device, tid.subdevice);
+			return 0;
+		}
+	}
+
+	/* always return a valid  timer id with defined classes. Also in case
+	 * when it looks like card has not changed because hw:0,0,0 should be
+	 * used
+	 */
 	dpcm->cable->snd_timer.owner = dpcm->substream->stream;
 	dpcm->cable->snd_timer.id = tid;
 
-	/* check if timer was already opened. It is only opened once
-	 * per playback and capture subdevice (aka cable).
+	return changed;
+}
+
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+{
+	int err = 0;
+	unsigned long flags;
+	struct snd_timer_instance *timer = NULL;
+
+	spin_lock_irqsave(&dpcm->cable->lock, flags);
+	err = loopback_snd_timer_source_update(dpcm);
+	if (err < 0) {
+		/* Could not yet detect the right sound timer because no valid
+		 * snd_pcm_link() exists. This should not be handled as an error
+		 * because the right timer will be opened with the call to
+		 * snd_pcm_link().
+		 */
+		if (err == -ENODEV)
+			err = 0;
+		goto unlock;
+	}
+	/* do not reopen the timer if it is already opened and nothing has
+	 * changed
 	 */
-	if (dpcm->cable->snd_timer.instance)
+	if (dpcm->cable->snd_timer.instance && err < 1)
 		goto unlock;
 
+	/* Avoids that any function accesses an invalid timer instance when
+	 * reopening the sound timer. Reopening the sound timer is only
+	 * supported in TIMER_SRC_AUTODETECT mode. snd_pcm_start() will fail on
+	 * the second device when the first device currently reopens the_timer:
+	 * [proc1] Calls snd_pcm_link() -> loopback_timer_open() ->
+	 *	   Unlock cable->lock for snd_timer_close/open() call
+	 * [proc2] Calls snd_pcm_start() when timer reopening is in progress
+	 * But this is fine because snd_pcm_start() would also fail if
+	 * snd_pcm_link() was not called from any device of the same cable in
+	 * TIMER_SRC_AUTODETECT mode. Therefore the user has to guarantee in
+	 * TIMER_SRC_AUTODETECT mode that snd_pcm_link() is called before anyone
+	 * calls snd_pcm_start() of the same cable.
+	 */
+	timer = dpcm->cable->snd_timer.instance;
+	dpcm->cable->snd_timer.instance = NULL;
+
 	/* snd_timer_close() and snd_timer_open() should not be called with
 	 * locked spinlock because both functions can block on a mutex. The
 	 * mutex loopback->cable_lock is kept locked. Therefore snd_timer_open()
@@ -1103,6 +1218,10 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
 	 *	   instance
 	 */
 	spin_unlock_irqrestore(&dpcm->cable->lock, flags);
+	/* close timer if there is already something open */
+	err = loopback_snd_timer_close(dpcm, timer);
+	if (err < 0)
+		return err;
 	err = snd_timer_open(&timer, dpcm->loopback->card->id,
 			     &dpcm->cable->snd_timer.id,
 			     current->pid);
@@ -1137,6 +1256,19 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
 	return err;
 }
 
+static int loopback_snd_timer_link_changed(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct loopback_pcm *dpcm = substream->runtime->private_data;
+
+	mutex_lock(&dpcm->loopback->cable_lock);
+	/* Try to reopen the timer here if the link_list has changed. */
+	err = loopback_snd_timer_open(dpcm);
+	mutex_unlock(&dpcm->loopback->cable_lock);
+
+	return err;
+}
+
 /* stop_sync() is not required for sound timer because it does not need to be
  * restarted in loopback_prepare() on Xrun recovery
  */
@@ -1148,6 +1280,13 @@ static struct loopback_ops loopback_snd_timer_ops = {
 	.dpcm_info = loopback_snd_timer_dpcm_info,
 };
 
+static struct loopback_ops loopback_snd_timer_auto_ops = {
+	.start = loopback_snd_timer_start,
+	.stop = loopback_snd_timer_stop,
+	.close_cable = loopback_snd_timer_close_cable,
+	.dpcm_info = loopback_snd_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -1178,6 +1317,8 @@ static int loopback_open(struct snd_pcm_substream *substream)
 		cable->hw = loopback_pcm_hardware;
 		if (loopback->timer_source <= TIMER_SRC_JIFFIES)
 			cable->ops = &loopback_jiffies_timer_ops;
+		else if (loopback->timer_source == TIMER_SRC_AUTODETECT)
+			cable->ops = &loopback_snd_timer_auto_ops;
 		else
 			cable->ops = &loopback_snd_timer_ops;
 		loopback->cables[substream->number][dev] = cable;
@@ -1276,18 +1417,34 @@ static const struct snd_pcm_ops loopback_pcm_ops = {
 	.page =		snd_pcm_lib_get_vmalloc_page,
 };
 
+static const struct snd_pcm_ops loopback_pcm_auto_ops = {
+	.open =		loopback_open,
+	.close =	loopback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	loopback_hw_params,
+	.hw_free =	loopback_hw_free,
+	.prepare =	loopback_prepare,
+	.trigger =	loopback_trigger,
+	.pointer =	loopback_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.link_changed =	loopback_snd_timer_link_changed,
+};
+
 static int loopback_pcm_new(struct loopback *loopback,
 			    int device, int substreams)
 {
 	struct snd_pcm *pcm;
 	int err;
+	const struct snd_pcm_ops * const ops =
+			(loopback->timer_source == TIMER_SRC_AUTODETECT) ?
+				&loopback_pcm_auto_ops : &loopback_pcm_ops;
 
 	err = snd_pcm_new(loopback->card, "Loopback PCM", device,
 			  substreams, substreams, &pcm);
 	if (err < 0)
 		return err;
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops);
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops);
 
 	pcm->private_data = loopback;
 	pcm->info_flags = 0;
-- 
2.7.4

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
  2019-03-25 16:58         ` Takashi Iwai
@ 2019-03-26  8:12           ` Timo Wischer
  -1 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-26  8:12 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/25/19 17:58, Takashi Iwai wrote:
> On Mon, 25 Mar 2019 17:40:23 +0100,
> Timo Wischer wrote:
>> Best regards
>> Timo Wischer
>> Engineering Software Base (ADITG/ESB)
>>
>> Tel. +49 5121 49 6938
>> On 3/25/19 17:07, Takashi Iwai wrote:
>>
>>      On Mon, 25 Mar 2019 17:00:38 +0100,
>>      <twischer@de.adit-jv.com> wrote:
>>      
>>          From: Timo Wischer <twischer@de.adit-jv.com>
>>          
>>          to allow the usage of timer callbacks from interrupt context.
>>          For example the sound timer.
>>          
>>      The trigger callback is already irq-disabled.  And, open/close must
>>      not be irq-disabled OTOH.  So these changes must be superfluous.
>>      
>> Hello Takashi,
>>
>> could you explain why open/close must not be irq-disabled?
> The open/close callbacks get called always in sleepable context where
> you can use mutex & co gracefully.  The only non-sleepable (atomic)
> context is pointer and trigger callbacks.  So it's safe to use
> spin_lock() without irq in these callbacks.
> OTOH, the rest (open, close,hw_params, hw_free, prepare) callbacks are
> always sleepable, hence it's safe to use spin_lock_irq() (not
> save/restore) variant.
>
> When a driver declares as non-atomic via PCM flag, all callbacks
> become sleepable, but this isn't the case for aloop driver.
>
>> I see a potential deadlock in case of free_cable() uses only spin_lock()
>> instead of spin_lock_irqsave().
>> For example the following will be executed:
>>
>> loopback_close()
>> free_cable()
>> spin_lock(&dpcm->cable->lock)
> Which spin_lock() call?  The free_cable() calls spin_lock_irq().
>
>>>> Interrupted by jiffies timer IRQ before calling spin_unlock()
>> loopback_jiffies_timer_function()
>> spin_lock_irqsave(&dpcm->cable->lock)
>>>> DEADLOCK due to dpcm->cable->lock is already locked
>> Do you also see this deadlock or do you see any reason why this could not
>> happen?
> If spin_lock_irq() is used in free_cable(), it must be OK.
>
>
> Takashi
Thanks for the detailed explanation.

@all:
Please simple ignore this patch.

Best regards

Timo

>
>
>> Best regards
>>
>> Timo
>>
>>      thanks,
>>      
>>      Takashi
>>
>>          Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>>          ---
>>           sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>>           1 file changed, 19 insertions(+), 14 deletions(-)
>>          
>>          diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
>>          index 11e8ed6..c6217c4 100644
>>          --- a/sound/drivers/aloop.c
>>          +++ b/sound/drivers/aloop.c
>>          @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>>                struct loopback_pcm *dpcm = runtime->private_data;
>>                struct loopback_cable *cable = dpcm->cable;
>>                int err = 0, stream = 1 << substream->stream;
>>          +     unsigned long flags;
>>           
>>                switch (cmd) {
>>                case SNDRV_PCM_TRIGGER_START:
>>          @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>>                                      dpcm->last_jiffies = jiffies;
>>                                      dpcm->pcm_rate_shift = 0;
>>                                      dpcm->last_drift = 0;
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->running |= stream;
>>                                      cable->pause &= ~stream;
>>                                      err = loopback_timer_start(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>                case SNDRV_PCM_TRIGGER_STOP:
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->running &= ~stream;
>>                                      cable->pause &= ~stream;
>>                                      err = loopback_timer_stop(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>                case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>>                case SNDRV_PCM_TRIGGER_SUSPEND:
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->pause |= stream;
>>                                      err = loopback_timer_stop(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>                case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>>                case SNDRV_PCM_TRIGGER_RESUME:
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      dpcm->last_jiffies = jiffies;
>>                                      cable->pause &= ~stream;
>>                                      err = loopback_timer_start(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>          @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>>           {
>>                struct snd_pcm_runtime *runtime = substream->runtime;
>>                struct loopback_pcm *dpcm = runtime->private_data;
>>          +     unsigned long flags;
>>                snd_pcm_uframes_t pos;
>>           
>>          -     spin_lock(&dpcm->cable->lock);
>>          +     spin_lock_irqsave(&dpcm->cable->lock, flags);
>>                loopback_pos_update(dpcm->cable);
>>                pos = dpcm->buf_pos;
>>          -     spin_unlock(&dpcm->cable->lock);
>>          +     spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>>                return bytes_to_frames(runtime, pos);
>>           }
>>           
>>          @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>>                if (!cable)
>>                                      return;
>>                if (cable->streams[!substream->stream]) {
>>          +                           unsigned long flags;
>>          +
>>                                      /* other stream is still alive */
>>          -                           spin_lock_irq(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->streams[substream->stream] = NULL;
>>          -                           spin_unlock_irq(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                } else {
>>                                      /* free the cable */
>>                                      loopback->cables[substream->number][dev] = NULL;
>>          @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>>                struct loopback_cable *cable = NULL;
>>                int err = 0;
>>                int dev = get_cable_index(substream);
>>          +     unsigned long flags;
>>           
>>                mutex_lock(&loopback->cable_lock);
>>                dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
>>          @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>>                else
>>                                      runtime->hw = cable->hw;
>>           
>>          -     spin_lock_irq(&cable->lock);
>>          +     spin_lock_irqsave(&cable->lock, flags);
>>                cable->streams[substream->stream] = dpcm;
>>          -     spin_unlock_irq(&cable->lock);
>>          +     spin_unlock_irqrestore(&cable->lock, flags);
>>           
>>            unlock:
>>                if (err < 0) {
>>          --
>>          2.7.4
>>
>>

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

* Re: [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock
@ 2019-03-26  8:12           ` Timo Wischer
  0 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-26  8:12 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/25/19 17:58, Takashi Iwai wrote:
> On Mon, 25 Mar 2019 17:40:23 +0100,
> Timo Wischer wrote:
>> Best regards
>> Timo Wischer
>> Engineering Software Base (ADITG/ESB)
>>
>> Tel. +49 5121 49 6938
>> On 3/25/19 17:07, Takashi Iwai wrote:
>>
>>      On Mon, 25 Mar 2019 17:00:38 +0100,
>>      <twischer@de.adit-jv.com> wrote:
>>      
>>          From: Timo Wischer <twischer@de.adit-jv.com>
>>          
>>          to allow the usage of timer callbacks from interrupt context.
>>          For example the sound timer.
>>          
>>      The trigger callback is already irq-disabled.  And, open/close must
>>      not be irq-disabled OTOH.  So these changes must be superfluous.
>>      
>> Hello Takashi,
>>
>> could you explain why open/close must not be irq-disabled?
> The open/close callbacks get called always in sleepable context where
> you can use mutex & co gracefully.  The only non-sleepable (atomic)
> context is pointer and trigger callbacks.  So it's safe to use
> spin_lock() without irq in these callbacks.
> OTOH, the rest (open, close,hw_params, hw_free, prepare) callbacks are
> always sleepable, hence it's safe to use spin_lock_irq() (not
> save/restore) variant.
>
> When a driver declares as non-atomic via PCM flag, all callbacks
> become sleepable, but this isn't the case for aloop driver.
>
>> I see a potential deadlock in case of free_cable() uses only spin_lock()
>> instead of spin_lock_irqsave().
>> For example the following will be executed:
>>
>> loopback_close()
>> free_cable()
>> spin_lock(&dpcm->cable->lock)
> Which spin_lock() call?  The free_cable() calls spin_lock_irq().
>
>>>> Interrupted by jiffies timer IRQ before calling spin_unlock()
>> loopback_jiffies_timer_function()
>> spin_lock_irqsave(&dpcm->cable->lock)
>>>> DEADLOCK due to dpcm->cable->lock is already locked
>> Do you also see this deadlock or do you see any reason why this could not
>> happen?
> If spin_lock_irq() is used in free_cable(), it must be OK.
>
>
> Takashi
Thanks for the detailed explanation.

@all:
Please simple ignore this patch.

Best regards

Timo

>
>
>> Best regards
>>
>> Timo
>>
>>      thanks,
>>      
>>      Takashi
>>
>>          Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>>          ---
>>           sound/drivers/aloop.c | 33 +++++++++++++++++++--------------
>>           1 file changed, 19 insertions(+), 14 deletions(-)
>>          
>>          diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
>>          index 11e8ed6..c6217c4 100644
>>          --- a/sound/drivers/aloop.c
>>          +++ b/sound/drivers/aloop.c
>>          @@ -272,6 +272,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>>                struct loopback_pcm *dpcm = runtime->private_data;
>>                struct loopback_cable *cable = dpcm->cable;
>>                int err = 0, stream = 1 << substream->stream;
>>          +     unsigned long flags;
>>           
>>                switch (cmd) {
>>                case SNDRV_PCM_TRIGGER_START:
>>          @@ -281,39 +282,39 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
>>                                      dpcm->last_jiffies = jiffies;
>>                                      dpcm->pcm_rate_shift = 0;
>>                                      dpcm->last_drift = 0;
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->running |= stream;
>>                                      cable->pause &= ~stream;
>>                                      err = loopback_timer_start(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>                case SNDRV_PCM_TRIGGER_STOP:
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->running &= ~stream;
>>                                      cable->pause &= ~stream;
>>                                      err = loopback_timer_stop(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>                case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>>                case SNDRV_PCM_TRIGGER_SUSPEND:
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->pause |= stream;
>>                                      err = loopback_timer_stop(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>                case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>>                case SNDRV_PCM_TRIGGER_RESUME:
>>          -                           spin_lock(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      dpcm->last_jiffies = jiffies;
>>                                      cable->pause &= ~stream;
>>                                      err = loopback_timer_start(dpcm);
>>          -                           spin_unlock(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                                      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>                                                            loopback_active_notify(dpcm);
>>                                      break;
>>          @@ -557,12 +558,13 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
>>           {
>>                struct snd_pcm_runtime *runtime = substream->runtime;
>>                struct loopback_pcm *dpcm = runtime->private_data;
>>          +     unsigned long flags;
>>                snd_pcm_uframes_t pos;
>>           
>>          -     spin_lock(&dpcm->cable->lock);
>>          +     spin_lock_irqsave(&dpcm->cable->lock, flags);
>>                loopback_pos_update(dpcm->cable);
>>                pos = dpcm->buf_pos;
>>          -     spin_unlock(&dpcm->cable->lock);
>>          +     spin_unlock_irqrestore(&dpcm->cable->lock, flags);
>>                return bytes_to_frames(runtime, pos);
>>           }
>>           
>>          @@ -679,10 +681,12 @@ static void free_cable(struct snd_pcm_substream *substream)
>>                if (!cable)
>>                                      return;
>>                if (cable->streams[!substream->stream]) {
>>          +                           unsigned long flags;
>>          +
>>                                      /* other stream is still alive */
>>          -                           spin_lock_irq(&cable->lock);
>>          +                           spin_lock_irqsave(&cable->lock, flags);
>>                                      cable->streams[substream->stream] = NULL;
>>          -                           spin_unlock_irq(&cable->lock);
>>          +                           spin_unlock_irqrestore(&cable->lock, flags);
>>                } else {
>>                                      /* free the cable */
>>                                      loopback->cables[substream->number][dev] = NULL;
>>          @@ -698,6 +702,7 @@ static int loopback_open(struct snd_pcm_substream *substream)
>>                struct loopback_cable *cable = NULL;
>>                int err = 0;
>>                int dev = get_cable_index(substream);
>>          +     unsigned long flags;
>>           
>>                mutex_lock(&loopback->cable_lock);
>>                dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
>>          @@ -753,9 +758,9 @@ static int loopback_open(struct snd_pcm_substream *substream)
>>                else
>>                                      runtime->hw = cable->hw;
>>           
>>          -     spin_lock_irq(&cable->lock);
>>          +     spin_lock_irqsave(&cable->lock, flags);
>>                cable->streams[substream->stream] = dpcm;
>>          -     spin_unlock_irq(&cable->lock);
>>          +     spin_unlock_irqrestore(&cable->lock, flags);
>>           
>>            unlock:
>>                if (err < 0) {
>>          --
>>          2.7.4
>>
>>

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26  7:49     ` twischer
@ 2019-03-26  8:35       ` Takashi Iwai
  -1 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-26  8:35 UTC (permalink / raw)
  To: twischer; +Cc: patch, broonie, perex, alsa-devel, linux-kernel

On Tue, 26 Mar 2019 08:49:33 +0100,
<twischer@de.adit-jv.com> wrote:
> 
> From: Timo Wischer <twischer@de.adit-jv.com>
> 
> snd_pcm_link() can be called by the user as long as the device is not
> yet started. Therefore currently a driver which wants to iterate over
> the linked substreams has to do this at the start trigger. But the start
> trigger should not block for a long time. Therefore there is no callback
> which can be used to iterate over the linked substreams without delaying
> the start trigger.
> This patch introduces a new callback function which will be called after
> the linked substream list was updated by snd_pcm_link(). This callback
> function is allowed to block for a longer time without interfering the
> synchronized start up of linked substreams.
> 
> Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>

Well, the idea appears interesting, but I'm afraid that the
implementation is still racy.  The place you're calling the new
callback isn't protected, hence the stream can be triggered while
calling it.  That is, even during operating your loopback link_changed
callback, another thread is able to start the stream.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-26  8:35       ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-26  8:35 UTC (permalink / raw)
  To: twischer; +Cc: patch, broonie, perex, alsa-devel, linux-kernel

On Tue, 26 Mar 2019 08:49:33 +0100,
<twischer@de.adit-jv.com> wrote:
> 
> From: Timo Wischer <twischer@de.adit-jv.com>
> 
> snd_pcm_link() can be called by the user as long as the device is not
> yet started. Therefore currently a driver which wants to iterate over
> the linked substreams has to do this at the start trigger. But the start
> trigger should not block for a long time. Therefore there is no callback
> which can be used to iterate over the linked substreams without delaying
> the start trigger.
> This patch introduces a new callback function which will be called after
> the linked substream list was updated by snd_pcm_link(). This callback
> function is allowed to block for a longer time without interfering the
> synchronized start up of linked substreams.
> 
> Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>

Well, the idea appears interesting, but I'm afraid that the
implementation is still racy.  The place you're calling the new
callback isn't protected, hence the stream can be triggered while
calling it.  That is, even during operating your loopback link_changed
callback, another thread is able to start the stream.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26  8:35       ` Takashi Iwai
  (?)
@ 2019-03-26 11:25       ` Timo Wischer
  2019-03-26 14:23           ` Takashi Iwai
  -1 siblings, 1 reply; 50+ messages in thread
From: Timo Wischer @ 2019-03-26 11:25 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: linux-kernel, broonie, patch, alsa-devel

On 3/26/19 09:35, Takashi Iwai wrote:
> On Tue, 26 Mar 2019 08:49:33 +0100,
> <twischer@de.adit-jv.com> wrote:
>> From: Timo Wischer <twischer@de.adit-jv.com>
>>
>> snd_pcm_link() can be called by the user as long as the device is not
>> yet started. Therefore currently a driver which wants to iterate over
>> the linked substreams has to do this at the start trigger. But the start
>> trigger should not block for a long time. Therefore there is no callback
>> which can be used to iterate over the linked substreams without delaying
>> the start trigger.
>> This patch introduces a new callback function which will be called after
>> the linked substream list was updated by snd_pcm_link(). This callback
>> function is allowed to block for a longer time without interfering the
>> synchronized start up of linked substreams.
>>
>> Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
> Well, the idea appears interesting, but I'm afraid that the
> implementation is still racy.  The place you're calling the new
> callback isn't protected, hence the stream can be triggered while
> calling it.  That is, even during operating your loopback link_changed
> callback, another thread is able to start the stream.

Hi Takashi,

As far as I got you mean the following scenario:

  * snd_pcm_link() is called for a HW sound card
      o loopback_snd_timer_link_changed()
      o loopback_snd_timer_open()
      o spin_lock_irqsave(&dpcm->cable->lock, flags);
  * snd_pcm_start() called for aloop sound card
      o loopback_trigger()
      o spin_lock(&cable->lock) -> has to wait till
        loopback_snd_timer_open() calls spin_unlock_irqrestore()

So far snd_pcm_start() has to wait for loopback_snd_timer_open().

  * loopback_snd_timer_open() will continue with
      o dpcm->cable->snd_timer.instance = NULL;
      o spin_unlock_irqrestore()
  * loopback_trigger() can enter the lock
      o loopback_snd_timer_start() will fail with -EINVAL due to
        (loopback_trigger == NULL)

At least this will not result into memory corruption due to race or any 
other wired behavior.
But my expectation is that snd_pcm_link(hw, aloop) or 
snd_pcm_link(aloop, hw) is only called by the application calling 
snd_pcm_start(aloop)
because the same aloop device cannot be opened by multiple applications 
at the same time.

Do you see an use case where one application would call snd_pcm_start() 
in parallel with snd_pcm_link() (somehow configuring the device)?
May be we should add an additional synchronization mechanism in 
pcm_native.c to avoid call of snd_pcm_link() in parallel with 
snd_pcm_start().

>
> thanks,
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26 11:25       ` Timo Wischer
@ 2019-03-26 14:23           ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-26 14:23 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Tue, 26 Mar 2019 12:25:37 +0100,
Timo Wischer wrote:
> 
> On 3/26/19 09:35, Takashi Iwai wrote:
> 
>     On Tue, 26 Mar 2019 08:49:33 +0100,
>     <twischer@de.adit-jv.com> wrote:
>     
>         From: Timo Wischer <twischer@de.adit-jv.com>
>         
>         snd_pcm_link() can be called by the user as long as the device is not
>         yet started. Therefore currently a driver which wants to iterate over
>         the linked substreams has to do this at the start trigger. But the start
>         trigger should not block for a long time. Therefore there is no callback
>         which can be used to iterate over the linked substreams without delaying
>         the start trigger.
>         This patch introduces a new callback function which will be called after
>         the linked substream list was updated by snd_pcm_link(). This callback
>         function is allowed to block for a longer time without interfering the
>         synchronized start up of linked substreams.
>         
>         Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>         
>     Well, the idea appears interesting, but I'm afraid that the
>     implementation is still racy.  The place you're calling the new
>     callback isn't protected, hence the stream can be triggered while
>     calling it.  That is, even during operating your loopback link_changed
>     callback, another thread is able to start the stream.
>     
> Hi Takashi,
> 
> As far as I got you mean the following scenario:
> 
>   * snd_pcm_link() is called for a HW sound card
>       + loopback_snd_timer_link_changed()

The start may happen at this point.

>       + loopback_snd_timer_open()
>       + spin_lock_irqsave(&dpcm->cable->lock, flags);
>   * snd_pcm_start() called for aloop sound card
>       + loopback_trigger()
>       + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>         calls spin_unlock_irqrestore()
> 
> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> 
>   * loopback_snd_timer_open() will continue with
>       + dpcm->cable->snd_timer.instance = NULL;
>       + spin_unlock_irqrestore()
>   * loopback_trigger() can enter the lock
>       + loopback_snd_timer_start() will fail with -EINVAL due to
>         (loopback_trigger == NULL)
> 
> At least this will not result into memory corruption due to race or any other
> wired behavior.

I don't expect the memory corruption, but my point is that dealing
with linked streams is still tricky.  It was considered for the
lightweight coupled start/stop operation, and something intensively
depending on the linked status was out of the original design...

> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> is only called by the application calling snd_pcm_start(aloop)
> because the same aloop device cannot be opened by multiple applications at the
> same time.
> 
> Do you see an use case where one application would call snd_pcm_start() in
> parallel with snd_pcm_link() (somehow configuring the device)?

It's not about the actual application usages but rather against the
malicious attacks.  Especially aloop is a virtual device that is
available allover the places, it may be deployed / attacked easily.

> May be we should add an additional synchronization mechanism in pcm_native.c
> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().

If it really matters...  Honestly speaking, I'm not fully convinced
whether we want to deal with this using the PCM link mechanism.

What's the motivation for using the linked streams at the first place?
That's one of the biggest missing piece in the whole picture.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-26 14:23           ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-26 14:23 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Tue, 26 Mar 2019 12:25:37 +0100,
Timo Wischer wrote:
> 
> On 3/26/19 09:35, Takashi Iwai wrote:
> 
>     On Tue, 26 Mar 2019 08:49:33 +0100,
>     <twischer@de.adit-jv.com> wrote:
>     
>         From: Timo Wischer <twischer@de.adit-jv.com>
>         
>         snd_pcm_link() can be called by the user as long as the device is not
>         yet started. Therefore currently a driver which wants to iterate over
>         the linked substreams has to do this at the start trigger. But the start
>         trigger should not block for a long time. Therefore there is no callback
>         which can be used to iterate over the linked substreams without delaying
>         the start trigger.
>         This patch introduces a new callback function which will be called after
>         the linked substream list was updated by snd_pcm_link(). This callback
>         function is allowed to block for a longer time without interfering the
>         synchronized start up of linked substreams.
>         
>         Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>         
>     Well, the idea appears interesting, but I'm afraid that the
>     implementation is still racy.  The place you're calling the new
>     callback isn't protected, hence the stream can be triggered while
>     calling it.  That is, even during operating your loopback link_changed
>     callback, another thread is able to start the stream.
>     
> Hi Takashi,
> 
> As far as I got you mean the following scenario:
> 
>   * snd_pcm_link() is called for a HW sound card
>       + loopback_snd_timer_link_changed()

The start may happen at this point.

>       + loopback_snd_timer_open()
>       + spin_lock_irqsave(&dpcm->cable->lock, flags);
>   * snd_pcm_start() called for aloop sound card
>       + loopback_trigger()
>       + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>         calls spin_unlock_irqrestore()
> 
> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> 
>   * loopback_snd_timer_open() will continue with
>       + dpcm->cable->snd_timer.instance = NULL;
>       + spin_unlock_irqrestore()
>   * loopback_trigger() can enter the lock
>       + loopback_snd_timer_start() will fail with -EINVAL due to
>         (loopback_trigger == NULL)
> 
> At least this will not result into memory corruption due to race or any other
> wired behavior.

I don't expect the memory corruption, but my point is that dealing
with linked streams is still tricky.  It was considered for the
lightweight coupled start/stop operation, and something intensively
depending on the linked status was out of the original design...

> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> is only called by the application calling snd_pcm_start(aloop)
> because the same aloop device cannot be opened by multiple applications at the
> same time.
> 
> Do you see an use case where one application would call snd_pcm_start() in
> parallel with snd_pcm_link() (somehow configuring the device)?

It's not about the actual application usages but rather against the
malicious attacks.  Especially aloop is a virtual device that is
available allover the places, it may be deployed / attacked easily.

> May be we should add an additional synchronization mechanism in pcm_native.c
> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().

If it really matters...  Honestly speaking, I'm not fully convinced
whether we want to deal with this using the PCM link mechanism.

What's the motivation for using the linked streams at the first place?
That's one of the biggest missing piece in the whole picture.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26 14:23           ` Takashi Iwai
@ 2019-03-26 15:16             ` Timo Wischer
  -1 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-26 15:16 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/26/19 15:23, Takashi Iwai wrote:
> On Tue, 26 Mar 2019 12:25:37 +0100,
> Timo Wischer wrote:
>> On 3/26/19 09:35, Takashi Iwai wrote:
>>
>>      On Tue, 26 Mar 2019 08:49:33 +0100,
>>      <twischer@de.adit-jv.com> wrote:
>>      
>>          From: Timo Wischer <twischer@de.adit-jv.com>
>>          
>>          snd_pcm_link() can be called by the user as long as the device is not
>>          yet started. Therefore currently a driver which wants to iterate over
>>          the linked substreams has to do this at the start trigger. But the start
>>          trigger should not block for a long time. Therefore there is no callback
>>          which can be used to iterate over the linked substreams without delaying
>>          the start trigger.
>>          This patch introduces a new callback function which will be called after
>>          the linked substream list was updated by snd_pcm_link(). This callback
>>          function is allowed to block for a longer time without interfering the
>>          synchronized start up of linked substreams.
>>          
>>          Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>>          
>>      Well, the idea appears interesting, but I'm afraid that the
>>      implementation is still racy.  The place you're calling the new
>>      callback isn't protected, hence the stream can be triggered while
>>      calling it.  That is, even during operating your loopback link_changed
>>      callback, another thread is able to start the stream.
>>      
>> Hi Takashi,
>>
>> As far as I got you mean the following scenario:
>>
>>    * snd_pcm_link() is called for a HW sound card
>>        + loopback_snd_timer_link_changed()
> The start may happen at this point.

In this case the last link status will be used and aloop will print a 
warning "Another sound timer was requested but at least one device is 
already running...".

Without this patch set a similar issue already exists. When calling 
snd_pcm_start() before snd_pcm_link() was done the additional device 
linked by the snd_pcm_link() will not be started.
Therefore the application has already to take care about the order of 
the calls.

>
>>        + loopback_snd_timer_open()
>>        + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>    * snd_pcm_start() called for aloop sound card
>>        + loopback_trigger()
>>        + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>          calls spin_unlock_irqrestore()
>>
>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>
>>    * loopback_snd_timer_open() will continue with
>>        + dpcm->cable->snd_timer.instance = NULL;
>>        + spin_unlock_irqrestore()
>>    * loopback_trigger() can enter the lock
>>        + loopback_snd_timer_start() will fail with -EINVAL due to
>>          (loopback_trigger == NULL)
>>
>> At least this will not result into memory corruption due to race or any other
>> wired behavior.
> I don't expect the memory corruption, but my point is that dealing
> with linked streams is still tricky.  It was considered for the
> lightweight coupled start/stop operation, and something intensively
> depending on the linked status was out of the original design...
>
>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>> is only called by the application calling snd_pcm_start(aloop)
>> because the same aloop device cannot be opened by multiple applications at the
>> same time.
>>
>> Do you see an use case where one application would call snd_pcm_start() in
>> parallel with snd_pcm_link() (somehow configuring the device)?
> It's not about the actual application usages but rather against the
> malicious attacks.  Especially aloop is a virtual device that is
> available allover the places, it may be deployed / attacked easily.
The attack we are identifying here can only be done by the application 
opening the aloop device.
An application allowed to open the aloop device is anyway able to 
manipulate the audio streaming.
Or do you see an attack which would influence any other device/stream 
not opened by this application?
>
>> May be we should add an additional synchronization mechanism in pcm_native.c
>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> If it really matters...  Honestly speaking, I'm not fully convinced
> whether we want to deal with this using the PCM link mechanism.
>
> What's the motivation for using the linked streams at the first place?
> That's one of the biggest missing piece in the whole picture.
In general when the user uses snd_pcm_link() it expects that the linked 
devices are somehow synchronized.
Any applications already using snd_pcm_link() do not need to be adapted 
to use the new feature of aloop (for example JACK or ALSA multi plugin)

But when linking a HW sound card and aloop without this patch set, both 
devices will be started in sync but
the snd_pcm_period_eleapsed() calls of the different devices will drift. 
To avoid this the aloop plugin can automatically use the right timer.
If this feature is not implemented the user has to use snd_pcm_link() to 
trigger snd_pcm_start() in sync but also has to configure the aloop 
plugin to use the right sound timer.
May be the linked cards can change during runtime of the system. Without 
this feature the aloop kernel driver has to be loaded with different 
kernel parameters
to select the right timer.

ALSA controls cannot be used easily. Selecting the sound timer by the 
card number could be error prone because the card ID could change 
between system starts.
Therefore an ALSA control supporting the card name should be used. This 
could be for example done via an ALSA enum control. But in this case the 
names of all sound cards of the system has to be available
on aloop probe() call. But at this point in time the sound cards probed 
after aloop are not available. Therefore only the sound timers of the 
sound cards probed before aloop are selectable.

>
>
> thanks,
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-26 15:16             ` Timo Wischer
  0 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-26 15:16 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/26/19 15:23, Takashi Iwai wrote:
> On Tue, 26 Mar 2019 12:25:37 +0100,
> Timo Wischer wrote:
>> On 3/26/19 09:35, Takashi Iwai wrote:
>>
>>      On Tue, 26 Mar 2019 08:49:33 +0100,
>>      <twischer@de.adit-jv.com> wrote:
>>      
>>          From: Timo Wischer <twischer@de.adit-jv.com>
>>          
>>          snd_pcm_link() can be called by the user as long as the device is not
>>          yet started. Therefore currently a driver which wants to iterate over
>>          the linked substreams has to do this at the start trigger. But the start
>>          trigger should not block for a long time. Therefore there is no callback
>>          which can be used to iterate over the linked substreams without delaying
>>          the start trigger.
>>          This patch introduces a new callback function which will be called after
>>          the linked substream list was updated by snd_pcm_link(). This callback
>>          function is allowed to block for a longer time without interfering the
>>          synchronized start up of linked substreams.
>>          
>>          Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
>>          
>>      Well, the idea appears interesting, but I'm afraid that the
>>      implementation is still racy.  The place you're calling the new
>>      callback isn't protected, hence the stream can be triggered while
>>      calling it.  That is, even during operating your loopback link_changed
>>      callback, another thread is able to start the stream.
>>      
>> Hi Takashi,
>>
>> As far as I got you mean the following scenario:
>>
>>    * snd_pcm_link() is called for a HW sound card
>>        + loopback_snd_timer_link_changed()
> The start may happen at this point.

In this case the last link status will be used and aloop will print a 
warning "Another sound timer was requested but at least one device is 
already running...".

Without this patch set a similar issue already exists. When calling 
snd_pcm_start() before snd_pcm_link() was done the additional device 
linked by the snd_pcm_link() will not be started.
Therefore the application has already to take care about the order of 
the calls.

>
>>        + loopback_snd_timer_open()
>>        + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>    * snd_pcm_start() called for aloop sound card
>>        + loopback_trigger()
>>        + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>          calls spin_unlock_irqrestore()
>>
>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>
>>    * loopback_snd_timer_open() will continue with
>>        + dpcm->cable->snd_timer.instance = NULL;
>>        + spin_unlock_irqrestore()
>>    * loopback_trigger() can enter the lock
>>        + loopback_snd_timer_start() will fail with -EINVAL due to
>>          (loopback_trigger == NULL)
>>
>> At least this will not result into memory corruption due to race or any other
>> wired behavior.
> I don't expect the memory corruption, but my point is that dealing
> with linked streams is still tricky.  It was considered for the
> lightweight coupled start/stop operation, and something intensively
> depending on the linked status was out of the original design...
>
>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>> is only called by the application calling snd_pcm_start(aloop)
>> because the same aloop device cannot be opened by multiple applications at the
>> same time.
>>
>> Do you see an use case where one application would call snd_pcm_start() in
>> parallel with snd_pcm_link() (somehow configuring the device)?
> It's not about the actual application usages but rather against the
> malicious attacks.  Especially aloop is a virtual device that is
> available allover the places, it may be deployed / attacked easily.
The attack we are identifying here can only be done by the application 
opening the aloop device.
An application allowed to open the aloop device is anyway able to 
manipulate the audio streaming.
Or do you see an attack which would influence any other device/stream 
not opened by this application?
>
>> May be we should add an additional synchronization mechanism in pcm_native.c
>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> If it really matters...  Honestly speaking, I'm not fully convinced
> whether we want to deal with this using the PCM link mechanism.
>
> What's the motivation for using the linked streams at the first place?
> That's one of the biggest missing piece in the whole picture.
In general when the user uses snd_pcm_link() it expects that the linked 
devices are somehow synchronized.
Any applications already using snd_pcm_link() do not need to be adapted 
to use the new feature of aloop (for example JACK or ALSA multi plugin)

But when linking a HW sound card and aloop without this patch set, both 
devices will be started in sync but
the snd_pcm_period_eleapsed() calls of the different devices will drift. 
To avoid this the aloop plugin can automatically use the right timer.
If this feature is not implemented the user has to use snd_pcm_link() to 
trigger snd_pcm_start() in sync but also has to configure the aloop 
plugin to use the right sound timer.
May be the linked cards can change during runtime of the system. Without 
this feature the aloop kernel driver has to be loaded with different 
kernel parameters
to select the right timer.

ALSA controls cannot be used easily. Selecting the sound timer by the 
card number could be error prone because the card ID could change 
between system starts.
Therefore an ALSA control supporting the card name should be used. This 
could be for example done via an ALSA enum control. But in this case the 
names of all sound cards of the system has to be available
on aloop probe() call. But at this point in time the sound cards probed 
after aloop are not available. Therefore only the sound timers of the 
sound cards probed before aloop are selectable.

>
>
> thanks,
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26 15:16             ` Timo Wischer
@ 2019-03-26 16:00               ` Takashi Iwai
  -1 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-26 16:00 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Tue, 26 Mar 2019 16:16:54 +0100,
Timo Wischer wrote:
> 
> On 3/26/19 15:23, Takashi Iwai wrote:
> > On Tue, 26 Mar 2019 12:25:37 +0100,
> > Timo Wischer wrote:
> >> On 3/26/19 09:35, Takashi Iwai wrote:
> >>
> >>      On Tue, 26 Mar 2019 08:49:33 +0100,
> >>      <twischer@de.adit-jv.com> wrote:
> >>               From: Timo Wischer <twischer@de.adit-jv.com>
> >>                   snd_pcm_link() can be called by the user as long
> >> as the device is not
> >>          yet started. Therefore currently a driver which wants to iterate over
> >>          the linked substreams has to do this at the start trigger. But the start
> >>          trigger should not block for a long time. Therefore there is no callback
> >>          which can be used to iterate over the linked substreams without delaying
> >>          the start trigger.
> >>          This patch introduces a new callback function which will be called after
> >>          the linked substream list was updated by snd_pcm_link(). This callback
> >>          function is allowed to block for a longer time without interfering the
> >>          synchronized start up of linked substreams.
> >>                   Signed-off-by: Timo Wischer
> >> <twischer@de.adit-jv.com>
> >>               Well, the idea appears interesting, but I'm afraid
> >> that the
> >>      implementation is still racy.  The place you're calling the new
> >>      callback isn't protected, hence the stream can be triggered while
> >>      calling it.  That is, even during operating your loopback link_changed
> >>      callback, another thread is able to start the stream.
> >>      Hi Takashi,
> >>
> >> As far as I got you mean the following scenario:
> >>
> >>    * snd_pcm_link() is called for a HW sound card
> >>        + loopback_snd_timer_link_changed()
> > The start may happen at this point.
> 
> In this case the last link status will be used and aloop will print a
> warning "Another sound timer was requested but at least one device is
> already running...".
> 
> Without this patch set a similar issue already exists. When calling
> snd_pcm_start() before snd_pcm_link() was done the additional device
> linked by the snd_pcm_link() will not be started.
> Therefore the application has already to take care about the order of
> the calls.

Yes, but it doesn't matter for now, just because other drivers do care
the PCM links only for trigger callback.  Now you're trying to add
something new but in an incomplete manner.

> 
> >
> >>        + loopback_snd_timer_open()
> >>        + spin_lock_irqsave(&dpcm->cable->lock, flags);
> >>    * snd_pcm_start() called for aloop sound card
> >>        + loopback_trigger()
> >>        + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
> >>          calls spin_unlock_irqrestore()
> >>
> >> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> >>
> >>    * loopback_snd_timer_open() will continue with
> >>        + dpcm->cable->snd_timer.instance = NULL;
> >>        + spin_unlock_irqrestore()
> >>    * loopback_trigger() can enter the lock
> >>        + loopback_snd_timer_start() will fail with -EINVAL due to
> >>          (loopback_trigger == NULL)
> >>
> >> At least this will not result into memory corruption due to race or any other
> >> wired behavior.
> > I don't expect the memory corruption, but my point is that dealing
> > with linked streams is still tricky.  It was considered for the
> > lightweight coupled start/stop operation, and something intensively
> > depending on the linked status was out of the original design...
> >
> >> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> >> is only called by the application calling snd_pcm_start(aloop)
> >> because the same aloop device cannot be opened by multiple applications at the
> >> same time.
> >>
> >> Do you see an use case where one application would call snd_pcm_start() in
> >> parallel with snd_pcm_link() (somehow configuring the device)?
> > It's not about the actual application usages but rather against the
> > malicious attacks.  Especially aloop is a virtual device that is
> > available allover the places, it may be deployed / attacked easily.
> The attack we are identifying here can only be done by the application
> opening the aloop device.
> An application allowed to open the aloop device is anyway able to
> manipulate the audio streaming.

Right, and if it such a racy access may lead to a driver misbehavior,
it's a big concern.  The proposed callback usage is racy, so some
other implementation might be broken easily in future.

> Or do you see an attack which would influence any other device/stream
> not opened by this application?
>
> >> May be we should add an additional synchronization mechanism in pcm_native.c
> >> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> > If it really matters...  Honestly speaking, I'm not fully convinced
> > whether we want to deal with this using the PCM link mechanism.
> >
> > What's the motivation for using the linked streams at the first place?
> > That's one of the biggest missing piece in the whole picture.
> In general when the user uses snd_pcm_link() it expects that the
> linked devices are somehow synchronized.
> Any applications already using snd_pcm_link() do not need to be
> adapted to use the new feature of aloop (for example JACK or ALSA
> multi plugin)
> 
> But when linking a HW sound card and aloop without this patch set,
> both devices will be started in sync but
> the snd_pcm_period_eleapsed() calls of the different devices will
> drift. To avoid this the aloop plugin can automatically use the right
> timer.
> If this feature is not implemented the user has to use snd_pcm_link()
> to trigger snd_pcm_start() in sync but also has to configure the aloop
> plugin to use the right sound timer.
> May be the linked cards can change during runtime of the
> system. Without this feature the aloop kernel driver has to be loaded
> with different kernel parameters
> to select the right timer.
> 
> ALSA controls cannot be used easily. Selecting the sound timer by the
> card number could be error prone because the card ID could change
> between system starts.
> Therefore an ALSA control supporting the card name should be
> used. This could be for example done via an ALSA enum control. But in
> this case the names of all sound cards of the system has to be
> available
> on aloop probe() call. But at this point in time the sound cards
> probed after aloop are not available. Therefore only the sound timers
> of the sound cards probed before aloop are selectable.

Hm.  For me this patch series looks very hackish.  As mentioned, the
PCM link usage is rather just a synchronous trigger start/stop for
multiple streams belonging to the same hardware; in that sense, it'd
be possible to adapt some mechanism for aloop, but at most it should
be much less intrusive change, e.g. just doing the multiple
loopback_timer_start() in a single loop.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-26 16:00               ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-26 16:00 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Tue, 26 Mar 2019 16:16:54 +0100,
Timo Wischer wrote:
> 
> On 3/26/19 15:23, Takashi Iwai wrote:
> > On Tue, 26 Mar 2019 12:25:37 +0100,
> > Timo Wischer wrote:
> >> On 3/26/19 09:35, Takashi Iwai wrote:
> >>
> >>      On Tue, 26 Mar 2019 08:49:33 +0100,
> >>      <twischer@de.adit-jv.com> wrote:
> >>               From: Timo Wischer <twischer@de.adit-jv.com>
> >>                   snd_pcm_link() can be called by the user as long
> >> as the device is not
> >>          yet started. Therefore currently a driver which wants to iterate over
> >>          the linked substreams has to do this at the start trigger. But the start
> >>          trigger should not block for a long time. Therefore there is no callback
> >>          which can be used to iterate over the linked substreams without delaying
> >>          the start trigger.
> >>          This patch introduces a new callback function which will be called after
> >>          the linked substream list was updated by snd_pcm_link(). This callback
> >>          function is allowed to block for a longer time without interfering the
> >>          synchronized start up of linked substreams.
> >>                   Signed-off-by: Timo Wischer
> >> <twischer@de.adit-jv.com>
> >>               Well, the idea appears interesting, but I'm afraid
> >> that the
> >>      implementation is still racy.  The place you're calling the new
> >>      callback isn't protected, hence the stream can be triggered while
> >>      calling it.  That is, even during operating your loopback link_changed
> >>      callback, another thread is able to start the stream.
> >>      Hi Takashi,
> >>
> >> As far as I got you mean the following scenario:
> >>
> >>    * snd_pcm_link() is called for a HW sound card
> >>        + loopback_snd_timer_link_changed()
> > The start may happen at this point.
> 
> In this case the last link status will be used and aloop will print a
> warning "Another sound timer was requested but at least one device is
> already running...".
> 
> Without this patch set a similar issue already exists. When calling
> snd_pcm_start() before snd_pcm_link() was done the additional device
> linked by the snd_pcm_link() will not be started.
> Therefore the application has already to take care about the order of
> the calls.

Yes, but it doesn't matter for now, just because other drivers do care
the PCM links only for trigger callback.  Now you're trying to add
something new but in an incomplete manner.

> 
> >
> >>        + loopback_snd_timer_open()
> >>        + spin_lock_irqsave(&dpcm->cable->lock, flags);
> >>    * snd_pcm_start() called for aloop sound card
> >>        + loopback_trigger()
> >>        + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
> >>          calls spin_unlock_irqrestore()
> >>
> >> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> >>
> >>    * loopback_snd_timer_open() will continue with
> >>        + dpcm->cable->snd_timer.instance = NULL;
> >>        + spin_unlock_irqrestore()
> >>    * loopback_trigger() can enter the lock
> >>        + loopback_snd_timer_start() will fail with -EINVAL due to
> >>          (loopback_trigger == NULL)
> >>
> >> At least this will not result into memory corruption due to race or any other
> >> wired behavior.
> > I don't expect the memory corruption, but my point is that dealing
> > with linked streams is still tricky.  It was considered for the
> > lightweight coupled start/stop operation, and something intensively
> > depending on the linked status was out of the original design...
> >
> >> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> >> is only called by the application calling snd_pcm_start(aloop)
> >> because the same aloop device cannot be opened by multiple applications at the
> >> same time.
> >>
> >> Do you see an use case where one application would call snd_pcm_start() in
> >> parallel with snd_pcm_link() (somehow configuring the device)?
> > It's not about the actual application usages but rather against the
> > malicious attacks.  Especially aloop is a virtual device that is
> > available allover the places, it may be deployed / attacked easily.
> The attack we are identifying here can only be done by the application
> opening the aloop device.
> An application allowed to open the aloop device is anyway able to
> manipulate the audio streaming.

Right, and if it such a racy access may lead to a driver misbehavior,
it's a big concern.  The proposed callback usage is racy, so some
other implementation might be broken easily in future.

> Or do you see an attack which would influence any other device/stream
> not opened by this application?
>
> >> May be we should add an additional synchronization mechanism in pcm_native.c
> >> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> > If it really matters...  Honestly speaking, I'm not fully convinced
> > whether we want to deal with this using the PCM link mechanism.
> >
> > What's the motivation for using the linked streams at the first place?
> > That's one of the biggest missing piece in the whole picture.
> In general when the user uses snd_pcm_link() it expects that the
> linked devices are somehow synchronized.
> Any applications already using snd_pcm_link() do not need to be
> adapted to use the new feature of aloop (for example JACK or ALSA
> multi plugin)
> 
> But when linking a HW sound card and aloop without this patch set,
> both devices will be started in sync but
> the snd_pcm_period_eleapsed() calls of the different devices will
> drift. To avoid this the aloop plugin can automatically use the right
> timer.
> If this feature is not implemented the user has to use snd_pcm_link()
> to trigger snd_pcm_start() in sync but also has to configure the aloop
> plugin to use the right sound timer.
> May be the linked cards can change during runtime of the
> system. Without this feature the aloop kernel driver has to be loaded
> with different kernel parameters
> to select the right timer.
> 
> ALSA controls cannot be used easily. Selecting the sound timer by the
> card number could be error prone because the card ID could change
> between system starts.
> Therefore an ALSA control supporting the card name should be
> used. This could be for example done via an ALSA enum control. But in
> this case the names of all sound cards of the system has to be
> available
> on aloop probe() call. But at this point in time the sound cards
> probed after aloop are not available. Therefore only the sound timers
> of the sound cards probed before aloop are selectable.

Hm.  For me this patch series looks very hackish.  As mentioned, the
PCM link usage is rather just a synchronous trigger start/stop for
multiple streams belonging to the same hardware; in that sense, it'd
be possible to adapt some mechanism for aloop, but at most it should
be much less intrusive change, e.g. just doing the multiple
loopback_timer_start() in a single loop.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-26 16:00               ` Takashi Iwai
@ 2019-03-27  8:34                 ` Timo Wischer
  -1 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-27  8:34 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/26/19 17:00, Takashi Iwai wrote:
> On Tue, 26 Mar 2019 16:16:54 +0100,
> Timo Wischer wrote:
>> On 3/26/19 15:23, Takashi Iwai wrote:
>>> On Tue, 26 Mar 2019 12:25:37 +0100,
>>> Timo Wischer wrote:
>>>> On 3/26/19 09:35, Takashi Iwai wrote:
>>>>
>>>>       On Tue, 26 Mar 2019 08:49:33 +0100,
>>>>       <twischer@de.adit-jv.com> wrote:
>>>>                From: Timo Wischer <twischer@de.adit-jv.com>
>>>>                    snd_pcm_link() can be called by the user as long
>>>> as the device is not
>>>>           yet started. Therefore currently a driver which wants to iterate over
>>>>           the linked substreams has to do this at the start trigger. But the start
>>>>           trigger should not block for a long time. Therefore there is no callback
>>>>           which can be used to iterate over the linked substreams without delaying
>>>>           the start trigger.
>>>>           This patch introduces a new callback function which will be called after
>>>>           the linked substream list was updated by snd_pcm_link(). This callback
>>>>           function is allowed to block for a longer time without interfering the
>>>>           synchronized start up of linked substreams.
>>>>                    Signed-off-by: Timo Wischer
>>>> <twischer@de.adit-jv.com>
>>>>                Well, the idea appears interesting, but I'm afraid
>>>> that the
>>>>       implementation is still racy.  The place you're calling the new
>>>>       callback isn't protected, hence the stream can be triggered while
>>>>       calling it.  That is, even during operating your loopback link_changed
>>>>       callback, another thread is able to start the stream.
>>>>       Hi Takashi,
>>>>
>>>> As far as I got you mean the following scenario:
>>>>
>>>>     * snd_pcm_link() is called for a HW sound card
>>>>         + loopback_snd_timer_link_changed()
>>> The start may happen at this point.
>> In this case the last link status will be used and aloop will print a
>> warning "Another sound timer was requested but at least one device is
>> already running...".
>>
>> Without this patch set a similar issue already exists. When calling
>> snd_pcm_start() before snd_pcm_link() was done the additional device
>> linked by the snd_pcm_link() will not be started.
>> Therefore the application has already to take care about the order of
>> the calls.
> Yes, but it doesn't matter for now, just because other drivers do care
> the PCM links only for trigger callback.  Now you're trying to add
> something new but in an incomplete manner.
>
>>>>         + loopback_snd_timer_open()
>>>>         + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>>>     * snd_pcm_start() called for aloop sound card
>>>>         + loopback_trigger()
>>>>         + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>>>           calls spin_unlock_irqrestore()
>>>>
>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>>>
>>>>     * loopback_snd_timer_open() will continue with
>>>>         + dpcm->cable->snd_timer.instance = NULL;
>>>>         + spin_unlock_irqrestore()
>>>>     * loopback_trigger() can enter the lock
>>>>         + loopback_snd_timer_start() will fail with -EINVAL due to
>>>>           (loopback_trigger == NULL)
>>>>
>>>> At least this will not result into memory corruption due to race or any other
>>>> wired behavior.
>>> I don't expect the memory corruption, but my point is that dealing
>>> with linked streams is still tricky.  It was considered for the
>>> lightweight coupled start/stop operation, and something intensively
>>> depending on the linked status was out of the original design...
>>>
>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>>>> is only called by the application calling snd_pcm_start(aloop)
>>>> because the same aloop device cannot be opened by multiple applications at the
>>>> same time.
>>>>
>>>> Do you see an use case where one application would call snd_pcm_start() in
>>>> parallel with snd_pcm_link() (somehow configuring the device)?
>>> It's not about the actual application usages but rather against the
>>> malicious attacks.  Especially aloop is a virtual device that is
>>> available allover the places, it may be deployed / attacked easily.
>> The attack we are identifying here can only be done by the application
>> opening the aloop device.
>> An application allowed to open the aloop device is anyway able to
>> manipulate the audio streaming.
> Right, and if it such a racy access may lead to a driver misbehavior,
> it's a big concern.  The proposed callback usage is racy, so some
> other implementation might be broken easily in future.
>
>> Or do you see an attack which would influence any other device/stream
>> not opened by this application?
>>
>>>> May be we should add an additional synchronization mechanism in pcm_native.c
>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
>>> If it really matters...  Honestly speaking, I'm not fully convinced
>>> whether we want to deal with this using the PCM link mechanism.
>>>
>>> What's the motivation for using the linked streams at the first place?
>>> That's one of the biggest missing piece in the whole picture.
>> In general when the user uses snd_pcm_link() it expects that the
>> linked devices are somehow synchronized.
>> Any applications already using snd_pcm_link() do not need to be
>> adapted to use the new feature of aloop (for example JACK or ALSA
>> multi plugin)
>>
>> But when linking a HW sound card and aloop without this patch set,
>> both devices will be started in sync but
>> the snd_pcm_period_eleapsed() calls of the different devices will
>> drift. To avoid this the aloop plugin can automatically use the right
>> timer.
>> If this feature is not implemented the user has to use snd_pcm_link()
>> to trigger snd_pcm_start() in sync but also has to configure the aloop
>> plugin to use the right sound timer.
>> May be the linked cards can change during runtime of the
>> system. Without this feature the aloop kernel driver has to be loaded
>> with different kernel parameters
>> to select the right timer.
>>
>> ALSA controls cannot be used easily. Selecting the sound timer by the
>> card number could be error prone because the card ID could change
>> between system starts.
>> Therefore an ALSA control supporting the card name should be
>> used. This could be for example done via an ALSA enum control. But in
>> this case the names of all sound cards of the system has to be
>> available
>> on aloop probe() call. But at this point in time the sound cards
>> probed after aloop are not available. Therefore only the sound timers
>> of the sound cards probed before aloop are selectable.
> Hm.  For me this patch series looks very hackish.  As mentioned, the
> PCM link usage is rather just a synchronous trigger start/stop for
> multiple streams belonging to the same hardware; in that sense, it'd
> be possible to adapt some mechanism for aloop, but at most it should
> be much less intrusive change, e.g. just doing the multiple
> loopback_timer_start() in a single loop.

What do you mean by "just doing the multiple loopback_timer_start() in a 
single loop."?

The snd_pcm_link() call information are mainly used to select the right 
sound timer. Starting the timer in sync is a nice add-on.
Therefore by simple starting all timers in a for loop I would not select 
the right sound timer.
(May be I misunderstood your example)


I could call the link_changed() callback inside 
snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
But an application could still call snd_pcm_link() and snd_pcm_start() 
in parallel e.g.:

snd_pcm_common_ioctl(IOCTL_LINK)
 > task switch
snd_pcm_common_ioctl(IOCTL_START)
snd_pcm_start()
done
 > continue previous task

The same could for example happen with snd_pcm_start() and snd_hw_free():

snd_pcm_common_ioctl(IOCTL_START)
 > task switch
snd_pcm_common_ioctl(IOCTL_HW_FREE)
snd_hw_free()
Done
 > continue previous task
snd_pcm_start() will fail with an error code

Therefore I do not think we have to synchronize the IOCTL calls (this 
can only be done in the user application or ALSAlib). We only have to 
return an proper error code in case of misusage.

As far as I understand ALSA was not designed for multithread usage. So 
the user (or ALSAlib) has to be aware of calling the functions in the 
right order.
Otherwise the user could expect returned error codes.

In case of my patch set snd_pcm_start() will always fail with a proper 
error code as long as no sound timer is running. This could be the case 
if snd_pcm_start() is called before the first snd_pcm_link() call.
This is also the case if snd_pcm_start() is called when the 
snd_pcm_link() call has closed the old timer but not yet opened the new one.
In case of snd_pcm_link() is called after snd_pcm_start() aloop will 
write a proper warning (that the snd_pcm_link() call will have no effect 
because the stream is already running).
All member variables used in loopback_snd_timer_open(), 
loopback_snd_timer_source_update() and loopback_snd_timer_start() are 
always protected by cable->lock. Therefore no one can read an invalid 
state of these variables and the misusage can always be detected.

Therefore I am not sure what I should change. May be you could reference 
to a source code line which looks hackish for you?

Thanks

Timo

>
>
> thanks,
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-27  8:34                 ` Timo Wischer
  0 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-27  8:34 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/26/19 17:00, Takashi Iwai wrote:
> On Tue, 26 Mar 2019 16:16:54 +0100,
> Timo Wischer wrote:
>> On 3/26/19 15:23, Takashi Iwai wrote:
>>> On Tue, 26 Mar 2019 12:25:37 +0100,
>>> Timo Wischer wrote:
>>>> On 3/26/19 09:35, Takashi Iwai wrote:
>>>>
>>>>       On Tue, 26 Mar 2019 08:49:33 +0100,
>>>>       <twischer@de.adit-jv.com> wrote:
>>>>                From: Timo Wischer <twischer@de.adit-jv.com>
>>>>                    snd_pcm_link() can be called by the user as long
>>>> as the device is not
>>>>           yet started. Therefore currently a driver which wants to iterate over
>>>>           the linked substreams has to do this at the start trigger. But the start
>>>>           trigger should not block for a long time. Therefore there is no callback
>>>>           which can be used to iterate over the linked substreams without delaying
>>>>           the start trigger.
>>>>           This patch introduces a new callback function which will be called after
>>>>           the linked substream list was updated by snd_pcm_link(). This callback
>>>>           function is allowed to block for a longer time without interfering the
>>>>           synchronized start up of linked substreams.
>>>>                    Signed-off-by: Timo Wischer
>>>> <twischer@de.adit-jv.com>
>>>>                Well, the idea appears interesting, but I'm afraid
>>>> that the
>>>>       implementation is still racy.  The place you're calling the new
>>>>       callback isn't protected, hence the stream can be triggered while
>>>>       calling it.  That is, even during operating your loopback link_changed
>>>>       callback, another thread is able to start the stream.
>>>>       Hi Takashi,
>>>>
>>>> As far as I got you mean the following scenario:
>>>>
>>>>     * snd_pcm_link() is called for a HW sound card
>>>>         + loopback_snd_timer_link_changed()
>>> The start may happen at this point.
>> In this case the last link status will be used and aloop will print a
>> warning "Another sound timer was requested but at least one device is
>> already running...".
>>
>> Without this patch set a similar issue already exists. When calling
>> snd_pcm_start() before snd_pcm_link() was done the additional device
>> linked by the snd_pcm_link() will not be started.
>> Therefore the application has already to take care about the order of
>> the calls.
> Yes, but it doesn't matter for now, just because other drivers do care
> the PCM links only for trigger callback.  Now you're trying to add
> something new but in an incomplete manner.
>
>>>>         + loopback_snd_timer_open()
>>>>         + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>>>     * snd_pcm_start() called for aloop sound card
>>>>         + loopback_trigger()
>>>>         + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>>>           calls spin_unlock_irqrestore()
>>>>
>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>>>
>>>>     * loopback_snd_timer_open() will continue with
>>>>         + dpcm->cable->snd_timer.instance = NULL;
>>>>         + spin_unlock_irqrestore()
>>>>     * loopback_trigger() can enter the lock
>>>>         + loopback_snd_timer_start() will fail with -EINVAL due to
>>>>           (loopback_trigger == NULL)
>>>>
>>>> At least this will not result into memory corruption due to race or any other
>>>> wired behavior.
>>> I don't expect the memory corruption, but my point is that dealing
>>> with linked streams is still tricky.  It was considered for the
>>> lightweight coupled start/stop operation, and something intensively
>>> depending on the linked status was out of the original design...
>>>
>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>>>> is only called by the application calling snd_pcm_start(aloop)
>>>> because the same aloop device cannot be opened by multiple applications at the
>>>> same time.
>>>>
>>>> Do you see an use case where one application would call snd_pcm_start() in
>>>> parallel with snd_pcm_link() (somehow configuring the device)?
>>> It's not about the actual application usages but rather against the
>>> malicious attacks.  Especially aloop is a virtual device that is
>>> available allover the places, it may be deployed / attacked easily.
>> The attack we are identifying here can only be done by the application
>> opening the aloop device.
>> An application allowed to open the aloop device is anyway able to
>> manipulate the audio streaming.
> Right, and if it such a racy access may lead to a driver misbehavior,
> it's a big concern.  The proposed callback usage is racy, so some
> other implementation might be broken easily in future.
>
>> Or do you see an attack which would influence any other device/stream
>> not opened by this application?
>>
>>>> May be we should add an additional synchronization mechanism in pcm_native.c
>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
>>> If it really matters...  Honestly speaking, I'm not fully convinced
>>> whether we want to deal with this using the PCM link mechanism.
>>>
>>> What's the motivation for using the linked streams at the first place?
>>> That's one of the biggest missing piece in the whole picture.
>> In general when the user uses snd_pcm_link() it expects that the
>> linked devices are somehow synchronized.
>> Any applications already using snd_pcm_link() do not need to be
>> adapted to use the new feature of aloop (for example JACK or ALSA
>> multi plugin)
>>
>> But when linking a HW sound card and aloop without this patch set,
>> both devices will be started in sync but
>> the snd_pcm_period_eleapsed() calls of the different devices will
>> drift. To avoid this the aloop plugin can automatically use the right
>> timer.
>> If this feature is not implemented the user has to use snd_pcm_link()
>> to trigger snd_pcm_start() in sync but also has to configure the aloop
>> plugin to use the right sound timer.
>> May be the linked cards can change during runtime of the
>> system. Without this feature the aloop kernel driver has to be loaded
>> with different kernel parameters
>> to select the right timer.
>>
>> ALSA controls cannot be used easily. Selecting the sound timer by the
>> card number could be error prone because the card ID could change
>> between system starts.
>> Therefore an ALSA control supporting the card name should be
>> used. This could be for example done via an ALSA enum control. But in
>> this case the names of all sound cards of the system has to be
>> available
>> on aloop probe() call. But at this point in time the sound cards
>> probed after aloop are not available. Therefore only the sound timers
>> of the sound cards probed before aloop are selectable.
> Hm.  For me this patch series looks very hackish.  As mentioned, the
> PCM link usage is rather just a synchronous trigger start/stop for
> multiple streams belonging to the same hardware; in that sense, it'd
> be possible to adapt some mechanism for aloop, but at most it should
> be much less intrusive change, e.g. just doing the multiple
> loopback_timer_start() in a single loop.

What do you mean by "just doing the multiple loopback_timer_start() in a 
single loop."?

The snd_pcm_link() call information are mainly used to select the right 
sound timer. Starting the timer in sync is a nice add-on.
Therefore by simple starting all timers in a for loop I would not select 
the right sound timer.
(May be I misunderstood your example)


I could call the link_changed() callback inside 
snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
But an application could still call snd_pcm_link() and snd_pcm_start() 
in parallel e.g.:

snd_pcm_common_ioctl(IOCTL_LINK)
 > task switch
snd_pcm_common_ioctl(IOCTL_START)
snd_pcm_start()
done
 > continue previous task

The same could for example happen with snd_pcm_start() and snd_hw_free():

snd_pcm_common_ioctl(IOCTL_START)
 > task switch
snd_pcm_common_ioctl(IOCTL_HW_FREE)
snd_hw_free()
Done
 > continue previous task
snd_pcm_start() will fail with an error code

Therefore I do not think we have to synchronize the IOCTL calls (this 
can only be done in the user application or ALSAlib). We only have to 
return an proper error code in case of misusage.

As far as I understand ALSA was not designed for multithread usage. So 
the user (or ALSAlib) has to be aware of calling the functions in the 
right order.
Otherwise the user could expect returned error codes.

In case of my patch set snd_pcm_start() will always fail with a proper 
error code as long as no sound timer is running. This could be the case 
if snd_pcm_start() is called before the first snd_pcm_link() call.
This is also the case if snd_pcm_start() is called when the 
snd_pcm_link() call has closed the old timer but not yet opened the new one.
In case of snd_pcm_link() is called after snd_pcm_start() aloop will 
write a proper warning (that the snd_pcm_link() call will have no effect 
because the stream is already running).
All member variables used in loopback_snd_timer_open(), 
loopback_snd_timer_source_update() and loopback_snd_timer_start() are 
always protected by cable->lock. Therefore no one can read an invalid 
state of these variables and the misusage can always be detected.

Therefore I am not sure what I should change. May be you could reference 
to a source code line which looks hackish for you?

Thanks

Timo

>
>
> thanks,
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-27  8:34                 ` Timo Wischer
@ 2019-03-27  9:11                   ` Takashi Iwai
  -1 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-27  9:11 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Wed, 27 Mar 2019 09:34:40 +0100,
Timo Wischer wrote:
> 
> On 3/26/19 17:00, Takashi Iwai wrote:
> > On Tue, 26 Mar 2019 16:16:54 +0100,
> > Timo Wischer wrote:
> >> On 3/26/19 15:23, Takashi Iwai wrote:
> >>> On Tue, 26 Mar 2019 12:25:37 +0100,
> >>> Timo Wischer wrote:
> >>>> On 3/26/19 09:35, Takashi Iwai wrote:
> >>>>
> >>>>       On Tue, 26 Mar 2019 08:49:33 +0100,
> >>>>       <twischer@de.adit-jv.com> wrote:
> >>>>                From: Timo Wischer <twischer@de.adit-jv.com>
> >>>>                    snd_pcm_link() can be called by the user as long
> >>>> as the device is not
> >>>>           yet started. Therefore currently a driver which wants to iterate over
> >>>>           the linked substreams has to do this at the start trigger. But the start
> >>>>           trigger should not block for a long time. Therefore there is no callback
> >>>>           which can be used to iterate over the linked substreams without delaying
> >>>>           the start trigger.
> >>>>           This patch introduces a new callback function which will be called after
> >>>>           the linked substream list was updated by snd_pcm_link(). This callback
> >>>>           function is allowed to block for a longer time without interfering the
> >>>>           synchronized start up of linked substreams.
> >>>>                    Signed-off-by: Timo Wischer
> >>>> <twischer@de.adit-jv.com>
> >>>>                Well, the idea appears interesting, but I'm afraid
> >>>> that the
> >>>>       implementation is still racy.  The place you're calling the new
> >>>>       callback isn't protected, hence the stream can be triggered while
> >>>>       calling it.  That is, even during operating your loopback link_changed
> >>>>       callback, another thread is able to start the stream.
> >>>>       Hi Takashi,
> >>>>
> >>>> As far as I got you mean the following scenario:
> >>>>
> >>>>     * snd_pcm_link() is called for a HW sound card
> >>>>         + loopback_snd_timer_link_changed()
> >>> The start may happen at this point.
> >> In this case the last link status will be used and aloop will print a
> >> warning "Another sound timer was requested but at least one device is
> >> already running...".
> >>
> >> Without this patch set a similar issue already exists. When calling
> >> snd_pcm_start() before snd_pcm_link() was done the additional device
> >> linked by the snd_pcm_link() will not be started.
> >> Therefore the application has already to take care about the order of
> >> the calls.
> > Yes, but it doesn't matter for now, just because other drivers do care
> > the PCM links only for trigger callback.  Now you're trying to add
> > something new but in an incomplete manner.
> >
> >>>>         + loopback_snd_timer_open()
> >>>>         + spin_lock_irqsave(&dpcm->cable->lock, flags);
> >>>>     * snd_pcm_start() called for aloop sound card
> >>>>         + loopback_trigger()
> >>>>         + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
> >>>>           calls spin_unlock_irqrestore()
> >>>>
> >>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> >>>>
> >>>>     * loopback_snd_timer_open() will continue with
> >>>>         + dpcm->cable->snd_timer.instance = NULL;
> >>>>         + spin_unlock_irqrestore()
> >>>>     * loopback_trigger() can enter the lock
> >>>>         + loopback_snd_timer_start() will fail with -EINVAL due to
> >>>>           (loopback_trigger == NULL)
> >>>>
> >>>> At least this will not result into memory corruption due to race or any other
> >>>> wired behavior.
> >>> I don't expect the memory corruption, but my point is that dealing
> >>> with linked streams is still tricky.  It was considered for the
> >>> lightweight coupled start/stop operation, and something intensively
> >>> depending on the linked status was out of the original design...
> >>>
> >>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> >>>> is only called by the application calling snd_pcm_start(aloop)
> >>>> because the same aloop device cannot be opened by multiple applications at the
> >>>> same time.
> >>>>
> >>>> Do you see an use case where one application would call snd_pcm_start() in
> >>>> parallel with snd_pcm_link() (somehow configuring the device)?
> >>> It's not about the actual application usages but rather against the
> >>> malicious attacks.  Especially aloop is a virtual device that is
> >>> available allover the places, it may be deployed / attacked easily.
> >> The attack we are identifying here can only be done by the application
> >> opening the aloop device.
> >> An application allowed to open the aloop device is anyway able to
> >> manipulate the audio streaming.
> > Right, and if it such a racy access may lead to a driver misbehavior,
> > it's a big concern.  The proposed callback usage is racy, so some
> > other implementation might be broken easily in future.
> >
> >> Or do you see an attack which would influence any other device/stream
> >> not opened by this application?
> >>
> >>>> May be we should add an additional synchronization mechanism in pcm_native.c
> >>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> >>> If it really matters...  Honestly speaking, I'm not fully convinced
> >>> whether we want to deal with this using the PCM link mechanism.
> >>>
> >>> What's the motivation for using the linked streams at the first place?
> >>> That's one of the biggest missing piece in the whole picture.
> >> In general when the user uses snd_pcm_link() it expects that the
> >> linked devices are somehow synchronized.
> >> Any applications already using snd_pcm_link() do not need to be
> >> adapted to use the new feature of aloop (for example JACK or ALSA
> >> multi plugin)
> >>
> >> But when linking a HW sound card and aloop without this patch set,
> >> both devices will be started in sync but
> >> the snd_pcm_period_eleapsed() calls of the different devices will
> >> drift. To avoid this the aloop plugin can automatically use the right
> >> timer.
> >> If this feature is not implemented the user has to use snd_pcm_link()
> >> to trigger snd_pcm_start() in sync but also has to configure the aloop
> >> plugin to use the right sound timer.
> >> May be the linked cards can change during runtime of the
> >> system. Without this feature the aloop kernel driver has to be loaded
> >> with different kernel parameters
> >> to select the right timer.
> >>
> >> ALSA controls cannot be used easily. Selecting the sound timer by the
> >> card number could be error prone because the card ID could change
> >> between system starts.
> >> Therefore an ALSA control supporting the card name should be
> >> used. This could be for example done via an ALSA enum control. But in
> >> this case the names of all sound cards of the system has to be
> >> available
> >> on aloop probe() call. But at this point in time the sound cards
> >> probed after aloop are not available. Therefore only the sound timers
> >> of the sound cards probed before aloop are selectable.
> > Hm.  For me this patch series looks very hackish.  As mentioned, the
> > PCM link usage is rather just a synchronous trigger start/stop for
> > multiple streams belonging to the same hardware; in that sense, it'd
> > be possible to adapt some mechanism for aloop, but at most it should
> > be much less intrusive change, e.g. just doing the multiple
> > loopback_timer_start() in a single loop.
> 
> What do you mean by "just doing the multiple loopback_timer_start() in
> a single loop."?
> 
> The snd_pcm_link() call information are mainly used to select the
> right sound timer. Starting the timer in sync is a nice add-on.
> Therefore by simple starting all timers in a for loop I would not
> select the right sound timer.
> (May be I misunderstood your example)

My example is only about the system timer.

> I could call the link_changed() callback inside
> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
> But an application could still call snd_pcm_link() and snd_pcm_start()
> in parallel e.g.:
> 
> snd_pcm_common_ioctl(IOCTL_LINK)
> > task switch
> snd_pcm_common_ioctl(IOCTL_START)
> snd_pcm_start()
> done
> > continue previous task
> 
> The same could for example happen with snd_pcm_start() and snd_hw_free():
> 
> snd_pcm_common_ioctl(IOCTL_START)
> > task switch
> snd_pcm_common_ioctl(IOCTL_HW_FREE)
> snd_hw_free()
> Done
> > continue previous task
> snd_pcm_start() will fail with an error code
> 
> Therefore I do not think we have to synchronize the IOCTL calls (this
> can only be done in the user application or ALSAlib). We only have to
> return an proper error code in case of misusage.
> 
> As far as I understand ALSA was not designed for multithread usage. So
> the user (or ALSAlib) has to be aware of calling the functions in the
> right order.
> Otherwise the user could expect returned error codes.

Well, your assumption is plain wrong.  ALSA is designed for
multithread usage.  And the kernel works for multithread.

It's not about the right usage.  It's about the robustness, about
whether any malicious code may lead to a serious defunct.  The opened
race in calls may easily introduce such a failure.

> In case of my patch set snd_pcm_start() will always fail with a proper
> error code as long as no sound timer is running. This could be the
> case if snd_pcm_start() is called before the first snd_pcm_link()
> call.
> This is also the case if snd_pcm_start() is called when the
> snd_pcm_link() call has closed the old timer but not yet opened the
> new one.
> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
> write a proper warning (that the snd_pcm_link() call will have no
> effect because the stream is already running).
> All member variables used in loopback_snd_timer_open(),
> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
> always protected by cable->lock. Therefore no one can read an invalid
> state of these variables and the misusage can always be detected.
> 
> Therefore I am not sure what I should change. May be you could
> reference to a source code line which looks hackish for you?

In principle, the PCM ops is supposed to be safe for operating a
certain stuff.  If a state change may happen during the operation,
this should be called inside PCM stream lock.  So the design of the
new callback itself is fragile.

then, it comes to a point to re-setup the timer at the link change.
The idea is interesting, but it's a wrong usage of PCM link feature,
to be honest.

That said, I find the changes up to patch 8 are acceptable (with some
fixes expected), but the link feature isn't.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-27  9:11                   ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-27  9:11 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Wed, 27 Mar 2019 09:34:40 +0100,
Timo Wischer wrote:
> 
> On 3/26/19 17:00, Takashi Iwai wrote:
> > On Tue, 26 Mar 2019 16:16:54 +0100,
> > Timo Wischer wrote:
> >> On 3/26/19 15:23, Takashi Iwai wrote:
> >>> On Tue, 26 Mar 2019 12:25:37 +0100,
> >>> Timo Wischer wrote:
> >>>> On 3/26/19 09:35, Takashi Iwai wrote:
> >>>>
> >>>>       On Tue, 26 Mar 2019 08:49:33 +0100,
> >>>>       <twischer@de.adit-jv.com> wrote:
> >>>>                From: Timo Wischer <twischer@de.adit-jv.com>
> >>>>                    snd_pcm_link() can be called by the user as long
> >>>> as the device is not
> >>>>           yet started. Therefore currently a driver which wants to iterate over
> >>>>           the linked substreams has to do this at the start trigger. But the start
> >>>>           trigger should not block for a long time. Therefore there is no callback
> >>>>           which can be used to iterate over the linked substreams without delaying
> >>>>           the start trigger.
> >>>>           This patch introduces a new callback function which will be called after
> >>>>           the linked substream list was updated by snd_pcm_link(). This callback
> >>>>           function is allowed to block for a longer time without interfering the
> >>>>           synchronized start up of linked substreams.
> >>>>                    Signed-off-by: Timo Wischer
> >>>> <twischer@de.adit-jv.com>
> >>>>                Well, the idea appears interesting, but I'm afraid
> >>>> that the
> >>>>       implementation is still racy.  The place you're calling the new
> >>>>       callback isn't protected, hence the stream can be triggered while
> >>>>       calling it.  That is, even during operating your loopback link_changed
> >>>>       callback, another thread is able to start the stream.
> >>>>       Hi Takashi,
> >>>>
> >>>> As far as I got you mean the following scenario:
> >>>>
> >>>>     * snd_pcm_link() is called for a HW sound card
> >>>>         + loopback_snd_timer_link_changed()
> >>> The start may happen at this point.
> >> In this case the last link status will be used and aloop will print a
> >> warning "Another sound timer was requested but at least one device is
> >> already running...".
> >>
> >> Without this patch set a similar issue already exists. When calling
> >> snd_pcm_start() before snd_pcm_link() was done the additional device
> >> linked by the snd_pcm_link() will not be started.
> >> Therefore the application has already to take care about the order of
> >> the calls.
> > Yes, but it doesn't matter for now, just because other drivers do care
> > the PCM links only for trigger callback.  Now you're trying to add
> > something new but in an incomplete manner.
> >
> >>>>         + loopback_snd_timer_open()
> >>>>         + spin_lock_irqsave(&dpcm->cable->lock, flags);
> >>>>     * snd_pcm_start() called for aloop sound card
> >>>>         + loopback_trigger()
> >>>>         + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
> >>>>           calls spin_unlock_irqrestore()
> >>>>
> >>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> >>>>
> >>>>     * loopback_snd_timer_open() will continue with
> >>>>         + dpcm->cable->snd_timer.instance = NULL;
> >>>>         + spin_unlock_irqrestore()
> >>>>     * loopback_trigger() can enter the lock
> >>>>         + loopback_snd_timer_start() will fail with -EINVAL due to
> >>>>           (loopback_trigger == NULL)
> >>>>
> >>>> At least this will not result into memory corruption due to race or any other
> >>>> wired behavior.
> >>> I don't expect the memory corruption, but my point is that dealing
> >>> with linked streams is still tricky.  It was considered for the
> >>> lightweight coupled start/stop operation, and something intensively
> >>> depending on the linked status was out of the original design...
> >>>
> >>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> >>>> is only called by the application calling snd_pcm_start(aloop)
> >>>> because the same aloop device cannot be opened by multiple applications at the
> >>>> same time.
> >>>>
> >>>> Do you see an use case where one application would call snd_pcm_start() in
> >>>> parallel with snd_pcm_link() (somehow configuring the device)?
> >>> It's not about the actual application usages but rather against the
> >>> malicious attacks.  Especially aloop is a virtual device that is
> >>> available allover the places, it may be deployed / attacked easily.
> >> The attack we are identifying here can only be done by the application
> >> opening the aloop device.
> >> An application allowed to open the aloop device is anyway able to
> >> manipulate the audio streaming.
> > Right, and if it such a racy access may lead to a driver misbehavior,
> > it's a big concern.  The proposed callback usage is racy, so some
> > other implementation might be broken easily in future.
> >
> >> Or do you see an attack which would influence any other device/stream
> >> not opened by this application?
> >>
> >>>> May be we should add an additional synchronization mechanism in pcm_native.c
> >>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> >>> If it really matters...  Honestly speaking, I'm not fully convinced
> >>> whether we want to deal with this using the PCM link mechanism.
> >>>
> >>> What's the motivation for using the linked streams at the first place?
> >>> That's one of the biggest missing piece in the whole picture.
> >> In general when the user uses snd_pcm_link() it expects that the
> >> linked devices are somehow synchronized.
> >> Any applications already using snd_pcm_link() do not need to be
> >> adapted to use the new feature of aloop (for example JACK or ALSA
> >> multi plugin)
> >>
> >> But when linking a HW sound card and aloop without this patch set,
> >> both devices will be started in sync but
> >> the snd_pcm_period_eleapsed() calls of the different devices will
> >> drift. To avoid this the aloop plugin can automatically use the right
> >> timer.
> >> If this feature is not implemented the user has to use snd_pcm_link()
> >> to trigger snd_pcm_start() in sync but also has to configure the aloop
> >> plugin to use the right sound timer.
> >> May be the linked cards can change during runtime of the
> >> system. Without this feature the aloop kernel driver has to be loaded
> >> with different kernel parameters
> >> to select the right timer.
> >>
> >> ALSA controls cannot be used easily. Selecting the sound timer by the
> >> card number could be error prone because the card ID could change
> >> between system starts.
> >> Therefore an ALSA control supporting the card name should be
> >> used. This could be for example done via an ALSA enum control. But in
> >> this case the names of all sound cards of the system has to be
> >> available
> >> on aloop probe() call. But at this point in time the sound cards
> >> probed after aloop are not available. Therefore only the sound timers
> >> of the sound cards probed before aloop are selectable.
> > Hm.  For me this patch series looks very hackish.  As mentioned, the
> > PCM link usage is rather just a synchronous trigger start/stop for
> > multiple streams belonging to the same hardware; in that sense, it'd
> > be possible to adapt some mechanism for aloop, but at most it should
> > be much less intrusive change, e.g. just doing the multiple
> > loopback_timer_start() in a single loop.
> 
> What do you mean by "just doing the multiple loopback_timer_start() in
> a single loop."?
> 
> The snd_pcm_link() call information are mainly used to select the
> right sound timer. Starting the timer in sync is a nice add-on.
> Therefore by simple starting all timers in a for loop I would not
> select the right sound timer.
> (May be I misunderstood your example)

My example is only about the system timer.

> I could call the link_changed() callback inside
> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
> But an application could still call snd_pcm_link() and snd_pcm_start()
> in parallel e.g.:
> 
> snd_pcm_common_ioctl(IOCTL_LINK)
> > task switch
> snd_pcm_common_ioctl(IOCTL_START)
> snd_pcm_start()
> done
> > continue previous task
> 
> The same could for example happen with snd_pcm_start() and snd_hw_free():
> 
> snd_pcm_common_ioctl(IOCTL_START)
> > task switch
> snd_pcm_common_ioctl(IOCTL_HW_FREE)
> snd_hw_free()
> Done
> > continue previous task
> snd_pcm_start() will fail with an error code
> 
> Therefore I do not think we have to synchronize the IOCTL calls (this
> can only be done in the user application or ALSAlib). We only have to
> return an proper error code in case of misusage.
> 
> As far as I understand ALSA was not designed for multithread usage. So
> the user (or ALSAlib) has to be aware of calling the functions in the
> right order.
> Otherwise the user could expect returned error codes.

Well, your assumption is plain wrong.  ALSA is designed for
multithread usage.  And the kernel works for multithread.

It's not about the right usage.  It's about the robustness, about
whether any malicious code may lead to a serious defunct.  The opened
race in calls may easily introduce such a failure.

> In case of my patch set snd_pcm_start() will always fail with a proper
> error code as long as no sound timer is running. This could be the
> case if snd_pcm_start() is called before the first snd_pcm_link()
> call.
> This is also the case if snd_pcm_start() is called when the
> snd_pcm_link() call has closed the old timer but not yet opened the
> new one.
> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
> write a proper warning (that the snd_pcm_link() call will have no
> effect because the stream is already running).
> All member variables used in loopback_snd_timer_open(),
> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
> always protected by cable->lock. Therefore no one can read an invalid
> state of these variables and the misusage can always be detected.
> 
> Therefore I am not sure what I should change. May be you could
> reference to a source code line which looks hackish for you?

In principle, the PCM ops is supposed to be safe for operating a
certain stuff.  If a state change may happen during the operation,
this should be called inside PCM stream lock.  So the design of the
new callback itself is fragile.

then, it comes to a point to re-setup the timer at the link change.
The idea is interesting, but it's a wrong usage of PCM link feature,
to be honest.

That said, I find the changes up to patch 8 are acceptable (with some
fixes expected), but the link feature isn't.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-27  9:11                   ` Takashi Iwai
@ 2019-03-27  9:26                     ` Timo Wischer
  -1 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-27  9:26 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/27/19 10:11, Takashi Iwai wrote:
> On Wed, 27 Mar 2019 09:34:40 +0100,
> Timo Wischer wrote:
>> On 3/26/19 17:00, Takashi Iwai wrote:
>>> On Tue, 26 Mar 2019 16:16:54 +0100,
>>> Timo Wischer wrote:
>>>> On 3/26/19 15:23, Takashi Iwai wrote:
>>>>> On Tue, 26 Mar 2019 12:25:37 +0100,
>>>>> Timo Wischer wrote:
>>>>>> On 3/26/19 09:35, Takashi Iwai wrote:
>>>>>>
>>>>>>        On Tue, 26 Mar 2019 08:49:33 +0100,
>>>>>>        <twischer@de.adit-jv.com> wrote:
>>>>>>                 From: Timo Wischer <twischer@de.adit-jv.com>
>>>>>>                     snd_pcm_link() can be called by the user as long
>>>>>> as the device is not
>>>>>>            yet started. Therefore currently a driver which wants to iterate over
>>>>>>            the linked substreams has to do this at the start trigger. But the start
>>>>>>            trigger should not block for a long time. Therefore there is no callback
>>>>>>            which can be used to iterate over the linked substreams without delaying
>>>>>>            the start trigger.
>>>>>>            This patch introduces a new callback function which will be called after
>>>>>>            the linked substream list was updated by snd_pcm_link(). This callback
>>>>>>            function is allowed to block for a longer time without interfering the
>>>>>>            synchronized start up of linked substreams.
>>>>>>                     Signed-off-by: Timo Wischer
>>>>>> <twischer@de.adit-jv.com>
>>>>>>                 Well, the idea appears interesting, but I'm afraid
>>>>>> that the
>>>>>>        implementation is still racy.  The place you're calling the new
>>>>>>        callback isn't protected, hence the stream can be triggered while
>>>>>>        calling it.  That is, even during operating your loopback link_changed
>>>>>>        callback, another thread is able to start the stream.
>>>>>>        Hi Takashi,
>>>>>>
>>>>>> As far as I got you mean the following scenario:
>>>>>>
>>>>>>      * snd_pcm_link() is called for a HW sound card
>>>>>>          + loopback_snd_timer_link_changed()
>>>>> The start may happen at this point.
>>>> In this case the last link status will be used and aloop will print a
>>>> warning "Another sound timer was requested but at least one device is
>>>> already running...".
>>>>
>>>> Without this patch set a similar issue already exists. When calling
>>>> snd_pcm_start() before snd_pcm_link() was done the additional device
>>>> linked by the snd_pcm_link() will not be started.
>>>> Therefore the application has already to take care about the order of
>>>> the calls.
>>> Yes, but it doesn't matter for now, just because other drivers do care
>>> the PCM links only for trigger callback.  Now you're trying to add
>>> something new but in an incomplete manner.
>>>
>>>>>>          + loopback_snd_timer_open()
>>>>>>          + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>>>>>      * snd_pcm_start() called for aloop sound card
>>>>>>          + loopback_trigger()
>>>>>>          + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>>>>>            calls spin_unlock_irqrestore()
>>>>>>
>>>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>>>>>
>>>>>>      * loopback_snd_timer_open() will continue with
>>>>>>          + dpcm->cable->snd_timer.instance = NULL;
>>>>>>          + spin_unlock_irqrestore()
>>>>>>      * loopback_trigger() can enter the lock
>>>>>>          + loopback_snd_timer_start() will fail with -EINVAL due to
>>>>>>            (loopback_trigger == NULL)
>>>>>>
>>>>>> At least this will not result into memory corruption due to race or any other
>>>>>> wired behavior.
>>>>> I don't expect the memory corruption, but my point is that dealing
>>>>> with linked streams is still tricky.  It was considered for the
>>>>> lightweight coupled start/stop operation, and something intensively
>>>>> depending on the linked status was out of the original design...
>>>>>
>>>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>>>>>> is only called by the application calling snd_pcm_start(aloop)
>>>>>> because the same aloop device cannot be opened by multiple applications at the
>>>>>> same time.
>>>>>>
>>>>>> Do you see an use case where one application would call snd_pcm_start() in
>>>>>> parallel with snd_pcm_link() (somehow configuring the device)?
>>>>> It's not about the actual application usages but rather against the
>>>>> malicious attacks.  Especially aloop is a virtual device that is
>>>>> available allover the places, it may be deployed / attacked easily.
>>>> The attack we are identifying here can only be done by the application
>>>> opening the aloop device.
>>>> An application allowed to open the aloop device is anyway able to
>>>> manipulate the audio streaming.
>>> Right, and if it such a racy access may lead to a driver misbehavior,
>>> it's a big concern.  The proposed callback usage is racy, so some
>>> other implementation might be broken easily in future.
>>>
>>>> Or do you see an attack which would influence any other device/stream
>>>> not opened by this application?
>>>>
>>>>>> May be we should add an additional synchronization mechanism in pcm_native.c
>>>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
>>>>> If it really matters...  Honestly speaking, I'm not fully convinced
>>>>> whether we want to deal with this using the PCM link mechanism.
>>>>>
>>>>> What's the motivation for using the linked streams at the first place?
>>>>> That's one of the biggest missing piece in the whole picture.
>>>> In general when the user uses snd_pcm_link() it expects that the
>>>> linked devices are somehow synchronized.
>>>> Any applications already using snd_pcm_link() do not need to be
>>>> adapted to use the new feature of aloop (for example JACK or ALSA
>>>> multi plugin)
>>>>
>>>> But when linking a HW sound card and aloop without this patch set,
>>>> both devices will be started in sync but
>>>> the snd_pcm_period_eleapsed() calls of the different devices will
>>>> drift. To avoid this the aloop plugin can automatically use the right
>>>> timer.
>>>> If this feature is not implemented the user has to use snd_pcm_link()
>>>> to trigger snd_pcm_start() in sync but also has to configure the aloop
>>>> plugin to use the right sound timer.
>>>> May be the linked cards can change during runtime of the
>>>> system. Without this feature the aloop kernel driver has to be loaded
>>>> with different kernel parameters
>>>> to select the right timer.
>>>>
>>>> ALSA controls cannot be used easily. Selecting the sound timer by the
>>>> card number could be error prone because the card ID could change
>>>> between system starts.
>>>> Therefore an ALSA control supporting the card name should be
>>>> used. This could be for example done via an ALSA enum control. But in
>>>> this case the names of all sound cards of the system has to be
>>>> available
>>>> on aloop probe() call. But at this point in time the sound cards
>>>> probed after aloop are not available. Therefore only the sound timers
>>>> of the sound cards probed before aloop are selectable.
>>> Hm.  For me this patch series looks very hackish.  As mentioned, the
>>> PCM link usage is rather just a synchronous trigger start/stop for
>>> multiple streams belonging to the same hardware; in that sense, it'd
>>> be possible to adapt some mechanism for aloop, but at most it should
>>> be much less intrusive change, e.g. just doing the multiple
>>> loopback_timer_start() in a single loop.
>> What do you mean by "just doing the multiple loopback_timer_start() in
>> a single loop."?
>>
>> The snd_pcm_link() call information are mainly used to select the
>> right sound timer. Starting the timer in sync is a nice add-on.
>> Therefore by simple starting all timers in a for loop I would not
>> select the right sound timer.
>> (May be I misunderstood your example)
> My example is only about the system timer.
>
>> I could call the link_changed() callback inside
>> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
>> But an application could still call snd_pcm_link() and snd_pcm_start()
>> in parallel e.g.:
>>
>> snd_pcm_common_ioctl(IOCTL_LINK)
>>> task switch
>> snd_pcm_common_ioctl(IOCTL_START)
>> snd_pcm_start()
>> done
>>> continue previous task
>> The same could for example happen with snd_pcm_start() and snd_hw_free():
>>
>> snd_pcm_common_ioctl(IOCTL_START)
>>> task switch
>> snd_pcm_common_ioctl(IOCTL_HW_FREE)
>> snd_hw_free()
>> Done
>>> continue previous task
>> snd_pcm_start() will fail with an error code
>>
>> Therefore I do not think we have to synchronize the IOCTL calls (this
>> can only be done in the user application or ALSAlib). We only have to
>> return an proper error code in case of misusage.
>>
>> As far as I understand ALSA was not designed for multithread usage. So
>> the user (or ALSAlib) has to be aware of calling the functions in the
>> right order.
>> Otherwise the user could expect returned error codes.
> Well, your assumption is plain wrong.  ALSA is designed for
> multithread usage.  And the kernel works for multithread.
>
> It's not about the right usage.  It's about the robustness, about
> whether any malicious code may lead to a serious defunct.  The opened
> race in calls may easily introduce such a failure.
>
>> In case of my patch set snd_pcm_start() will always fail with a proper
>> error code as long as no sound timer is running. This could be the
>> case if snd_pcm_start() is called before the first snd_pcm_link()
>> call.
>> This is also the case if snd_pcm_start() is called when the
>> snd_pcm_link() call has closed the old timer but not yet opened the
>> new one.
>> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
>> write a proper warning (that the snd_pcm_link() call will have no
>> effect because the stream is already running).
>> All member variables used in loopback_snd_timer_open(),
>> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
>> always protected by cable->lock. Therefore no one can read an invalid
>> state of these variables and the misusage can always be detected.
>>
>> Therefore I am not sure what I should change. May be you could
>> reference to a source code line which looks hackish for you?
> In principle, the PCM ops is supposed to be safe for operating a
> certain stuff.  If a state change may happen during the operation,
> this should be called inside PCM stream lock.  So the design of the
> new callback itself is fragile.
>
> then, it comes to a point to re-setup the timer at the link change.
> The idea is interesting, but it's a wrong usage of PCM link feature,
> to be honest.
>
> That said, I find the changes up to patch 8 are acceptable (with some
> fixes expected), but the link feature isn't.
Thanks for clarification. Do you think there is a chance to get an 
acceptable snd_pcm_link() feature when using PCM stream lock for 
snd_pcm_link()?
In this case I think snd_timer_open() has to be adapted to use 
non-blocking calls only (do not use mutexs).

Or do I have to use another API to configure the sound timer at runtime 
(e.g. ALSA controls or RW kernel module parameters)?


Best regards

Timo


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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-27  9:26                     ` Timo Wischer
  0 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-03-27  9:26 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/27/19 10:11, Takashi Iwai wrote:
> On Wed, 27 Mar 2019 09:34:40 +0100,
> Timo Wischer wrote:
>> On 3/26/19 17:00, Takashi Iwai wrote:
>>> On Tue, 26 Mar 2019 16:16:54 +0100,
>>> Timo Wischer wrote:
>>>> On 3/26/19 15:23, Takashi Iwai wrote:
>>>>> On Tue, 26 Mar 2019 12:25:37 +0100,
>>>>> Timo Wischer wrote:
>>>>>> On 3/26/19 09:35, Takashi Iwai wrote:
>>>>>>
>>>>>>        On Tue, 26 Mar 2019 08:49:33 +0100,
>>>>>>        <twischer@de.adit-jv.com> wrote:
>>>>>>                 From: Timo Wischer <twischer@de.adit-jv.com>
>>>>>>                     snd_pcm_link() can be called by the user as long
>>>>>> as the device is not
>>>>>>            yet started. Therefore currently a driver which wants to iterate over
>>>>>>            the linked substreams has to do this at the start trigger. But the start
>>>>>>            trigger should not block for a long time. Therefore there is no callback
>>>>>>            which can be used to iterate over the linked substreams without delaying
>>>>>>            the start trigger.
>>>>>>            This patch introduces a new callback function which will be called after
>>>>>>            the linked substream list was updated by snd_pcm_link(). This callback
>>>>>>            function is allowed to block for a longer time without interfering the
>>>>>>            synchronized start up of linked substreams.
>>>>>>                     Signed-off-by: Timo Wischer
>>>>>> <twischer@de.adit-jv.com>
>>>>>>                 Well, the idea appears interesting, but I'm afraid
>>>>>> that the
>>>>>>        implementation is still racy.  The place you're calling the new
>>>>>>        callback isn't protected, hence the stream can be triggered while
>>>>>>        calling it.  That is, even during operating your loopback link_changed
>>>>>>        callback, another thread is able to start the stream.
>>>>>>        Hi Takashi,
>>>>>>
>>>>>> As far as I got you mean the following scenario:
>>>>>>
>>>>>>      * snd_pcm_link() is called for a HW sound card
>>>>>>          + loopback_snd_timer_link_changed()
>>>>> The start may happen at this point.
>>>> In this case the last link status will be used and aloop will print a
>>>> warning "Another sound timer was requested but at least one device is
>>>> already running...".
>>>>
>>>> Without this patch set a similar issue already exists. When calling
>>>> snd_pcm_start() before snd_pcm_link() was done the additional device
>>>> linked by the snd_pcm_link() will not be started.
>>>> Therefore the application has already to take care about the order of
>>>> the calls.
>>> Yes, but it doesn't matter for now, just because other drivers do care
>>> the PCM links only for trigger callback.  Now you're trying to add
>>> something new but in an incomplete manner.
>>>
>>>>>>          + loopback_snd_timer_open()
>>>>>>          + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>>>>>      * snd_pcm_start() called for aloop sound card
>>>>>>          + loopback_trigger()
>>>>>>          + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>>>>>            calls spin_unlock_irqrestore()
>>>>>>
>>>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>>>>>
>>>>>>      * loopback_snd_timer_open() will continue with
>>>>>>          + dpcm->cable->snd_timer.instance = NULL;
>>>>>>          + spin_unlock_irqrestore()
>>>>>>      * loopback_trigger() can enter the lock
>>>>>>          + loopback_snd_timer_start() will fail with -EINVAL due to
>>>>>>            (loopback_trigger == NULL)
>>>>>>
>>>>>> At least this will not result into memory corruption due to race or any other
>>>>>> wired behavior.
>>>>> I don't expect the memory corruption, but my point is that dealing
>>>>> with linked streams is still tricky.  It was considered for the
>>>>> lightweight coupled start/stop operation, and something intensively
>>>>> depending on the linked status was out of the original design...
>>>>>
>>>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>>>>>> is only called by the application calling snd_pcm_start(aloop)
>>>>>> because the same aloop device cannot be opened by multiple applications at the
>>>>>> same time.
>>>>>>
>>>>>> Do you see an use case where one application would call snd_pcm_start() in
>>>>>> parallel with snd_pcm_link() (somehow configuring the device)?
>>>>> It's not about the actual application usages but rather against the
>>>>> malicious attacks.  Especially aloop is a virtual device that is
>>>>> available allover the places, it may be deployed / attacked easily.
>>>> The attack we are identifying here can only be done by the application
>>>> opening the aloop device.
>>>> An application allowed to open the aloop device is anyway able to
>>>> manipulate the audio streaming.
>>> Right, and if it such a racy access may lead to a driver misbehavior,
>>> it's a big concern.  The proposed callback usage is racy, so some
>>> other implementation might be broken easily in future.
>>>
>>>> Or do you see an attack which would influence any other device/stream
>>>> not opened by this application?
>>>>
>>>>>> May be we should add an additional synchronization mechanism in pcm_native.c
>>>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
>>>>> If it really matters...  Honestly speaking, I'm not fully convinced
>>>>> whether we want to deal with this using the PCM link mechanism.
>>>>>
>>>>> What's the motivation for using the linked streams at the first place?
>>>>> That's one of the biggest missing piece in the whole picture.
>>>> In general when the user uses snd_pcm_link() it expects that the
>>>> linked devices are somehow synchronized.
>>>> Any applications already using snd_pcm_link() do not need to be
>>>> adapted to use the new feature of aloop (for example JACK or ALSA
>>>> multi plugin)
>>>>
>>>> But when linking a HW sound card and aloop without this patch set,
>>>> both devices will be started in sync but
>>>> the snd_pcm_period_eleapsed() calls of the different devices will
>>>> drift. To avoid this the aloop plugin can automatically use the right
>>>> timer.
>>>> If this feature is not implemented the user has to use snd_pcm_link()
>>>> to trigger snd_pcm_start() in sync but also has to configure the aloop
>>>> plugin to use the right sound timer.
>>>> May be the linked cards can change during runtime of the
>>>> system. Without this feature the aloop kernel driver has to be loaded
>>>> with different kernel parameters
>>>> to select the right timer.
>>>>
>>>> ALSA controls cannot be used easily. Selecting the sound timer by the
>>>> card number could be error prone because the card ID could change
>>>> between system starts.
>>>> Therefore an ALSA control supporting the card name should be
>>>> used. This could be for example done via an ALSA enum control. But in
>>>> this case the names of all sound cards of the system has to be
>>>> available
>>>> on aloop probe() call. But at this point in time the sound cards
>>>> probed after aloop are not available. Therefore only the sound timers
>>>> of the sound cards probed before aloop are selectable.
>>> Hm.  For me this patch series looks very hackish.  As mentioned, the
>>> PCM link usage is rather just a synchronous trigger start/stop for
>>> multiple streams belonging to the same hardware; in that sense, it'd
>>> be possible to adapt some mechanism for aloop, but at most it should
>>> be much less intrusive change, e.g. just doing the multiple
>>> loopback_timer_start() in a single loop.
>> What do you mean by "just doing the multiple loopback_timer_start() in
>> a single loop."?
>>
>> The snd_pcm_link() call information are mainly used to select the
>> right sound timer. Starting the timer in sync is a nice add-on.
>> Therefore by simple starting all timers in a for loop I would not
>> select the right sound timer.
>> (May be I misunderstood your example)
> My example is only about the system timer.
>
>> I could call the link_changed() callback inside
>> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
>> But an application could still call snd_pcm_link() and snd_pcm_start()
>> in parallel e.g.:
>>
>> snd_pcm_common_ioctl(IOCTL_LINK)
>>> task switch
>> snd_pcm_common_ioctl(IOCTL_START)
>> snd_pcm_start()
>> done
>>> continue previous task
>> The same could for example happen with snd_pcm_start() and snd_hw_free():
>>
>> snd_pcm_common_ioctl(IOCTL_START)
>>> task switch
>> snd_pcm_common_ioctl(IOCTL_HW_FREE)
>> snd_hw_free()
>> Done
>>> continue previous task
>> snd_pcm_start() will fail with an error code
>>
>> Therefore I do not think we have to synchronize the IOCTL calls (this
>> can only be done in the user application or ALSAlib). We only have to
>> return an proper error code in case of misusage.
>>
>> As far as I understand ALSA was not designed for multithread usage. So
>> the user (or ALSAlib) has to be aware of calling the functions in the
>> right order.
>> Otherwise the user could expect returned error codes.
> Well, your assumption is plain wrong.  ALSA is designed for
> multithread usage.  And the kernel works for multithread.
>
> It's not about the right usage.  It's about the robustness, about
> whether any malicious code may lead to a serious defunct.  The opened
> race in calls may easily introduce such a failure.
>
>> In case of my patch set snd_pcm_start() will always fail with a proper
>> error code as long as no sound timer is running. This could be the
>> case if snd_pcm_start() is called before the first snd_pcm_link()
>> call.
>> This is also the case if snd_pcm_start() is called when the
>> snd_pcm_link() call has closed the old timer but not yet opened the
>> new one.
>> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
>> write a proper warning (that the snd_pcm_link() call will have no
>> effect because the stream is already running).
>> All member variables used in loopback_snd_timer_open(),
>> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
>> always protected by cable->lock. Therefore no one can read an invalid
>> state of these variables and the misusage can always be detected.
>>
>> Therefore I am not sure what I should change. May be you could
>> reference to a source code line which looks hackish for you?
> In principle, the PCM ops is supposed to be safe for operating a
> certain stuff.  If a state change may happen during the operation,
> this should be called inside PCM stream lock.  So the design of the
> new callback itself is fragile.
>
> then, it comes to a point to re-setup the timer at the link change.
> The idea is interesting, but it's a wrong usage of PCM link feature,
> to be honest.
>
> That said, I find the changes up to patch 8 are acceptable (with some
> fixes expected), but the link feature isn't.
Thanks for clarification. Do you think there is a chance to get an 
acceptable snd_pcm_link() feature when using PCM stream lock for 
snd_pcm_link()?
In this case I think snd_timer_open() has to be adapted to use 
non-blocking calls only (do not use mutexs).

Or do I have to use another API to configure the sound timer at runtime 
(e.g. ALSA controls or RW kernel module parameters)?


Best regards

Timo

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-27  9:26                     ` Timo Wischer
@ 2019-03-27  9:38                       ` Takashi Iwai
  -1 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-27  9:38 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Wed, 27 Mar 2019 10:26:56 +0100,
Timo Wischer wrote:
> 
> On 3/27/19 10:11, Takashi Iwai wrote:
> > On Wed, 27 Mar 2019 09:34:40 +0100,
> > Timo Wischer wrote:
> >> On 3/26/19 17:00, Takashi Iwai wrote:
> >>> On Tue, 26 Mar 2019 16:16:54 +0100,
> >>> Timo Wischer wrote:
> >>>> On 3/26/19 15:23, Takashi Iwai wrote:
> >>>>> On Tue, 26 Mar 2019 12:25:37 +0100,
> >>>>> Timo Wischer wrote:
> >>>>>> On 3/26/19 09:35, Takashi Iwai wrote:
> >>>>>>
> >>>>>>        On Tue, 26 Mar 2019 08:49:33 +0100,
> >>>>>>        <twischer@de.adit-jv.com> wrote:
> >>>>>>                 From: Timo Wischer <twischer@de.adit-jv.com>
> >>>>>>                     snd_pcm_link() can be called by the user as long
> >>>>>> as the device is not
> >>>>>>            yet started. Therefore currently a driver which wants to iterate over
> >>>>>>            the linked substreams has to do this at the start trigger. But the start
> >>>>>>            trigger should not block for a long time. Therefore there is no callback
> >>>>>>            which can be used to iterate over the linked substreams without delaying
> >>>>>>            the start trigger.
> >>>>>>            This patch introduces a new callback function which will be called after
> >>>>>>            the linked substream list was updated by snd_pcm_link(). This callback
> >>>>>>            function is allowed to block for a longer time without interfering the
> >>>>>>            synchronized start up of linked substreams.
> >>>>>>                     Signed-off-by: Timo Wischer
> >>>>>> <twischer@de.adit-jv.com>
> >>>>>>                 Well, the idea appears interesting, but I'm afraid
> >>>>>> that the
> >>>>>>        implementation is still racy.  The place you're calling the new
> >>>>>>        callback isn't protected, hence the stream can be triggered while
> >>>>>>        calling it.  That is, even during operating your loopback link_changed
> >>>>>>        callback, another thread is able to start the stream.
> >>>>>>        Hi Takashi,
> >>>>>>
> >>>>>> As far as I got you mean the following scenario:
> >>>>>>
> >>>>>>      * snd_pcm_link() is called for a HW sound card
> >>>>>>          + loopback_snd_timer_link_changed()
> >>>>> The start may happen at this point.
> >>>> In this case the last link status will be used and aloop will print a
> >>>> warning "Another sound timer was requested but at least one device is
> >>>> already running...".
> >>>>
> >>>> Without this patch set a similar issue already exists. When calling
> >>>> snd_pcm_start() before snd_pcm_link() was done the additional device
> >>>> linked by the snd_pcm_link() will not be started.
> >>>> Therefore the application has already to take care about the order of
> >>>> the calls.
> >>> Yes, but it doesn't matter for now, just because other drivers do care
> >>> the PCM links only for trigger callback.  Now you're trying to add
> >>> something new but in an incomplete manner.
> >>>
> >>>>>>          + loopback_snd_timer_open()
> >>>>>>          + spin_lock_irqsave(&dpcm->cable->lock, flags);
> >>>>>>      * snd_pcm_start() called for aloop sound card
> >>>>>>          + loopback_trigger()
> >>>>>>          + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
> >>>>>>            calls spin_unlock_irqrestore()
> >>>>>>
> >>>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> >>>>>>
> >>>>>>      * loopback_snd_timer_open() will continue with
> >>>>>>          + dpcm->cable->snd_timer.instance = NULL;
> >>>>>>          + spin_unlock_irqrestore()
> >>>>>>      * loopback_trigger() can enter the lock
> >>>>>>          + loopback_snd_timer_start() will fail with -EINVAL due to
> >>>>>>            (loopback_trigger == NULL)
> >>>>>>
> >>>>>> At least this will not result into memory corruption due to race or any other
> >>>>>> wired behavior.
> >>>>> I don't expect the memory corruption, but my point is that dealing
> >>>>> with linked streams is still tricky.  It was considered for the
> >>>>> lightweight coupled start/stop operation, and something intensively
> >>>>> depending on the linked status was out of the original design...
> >>>>>
> >>>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> >>>>>> is only called by the application calling snd_pcm_start(aloop)
> >>>>>> because the same aloop device cannot be opened by multiple applications at the
> >>>>>> same time.
> >>>>>>
> >>>>>> Do you see an use case where one application would call snd_pcm_start() in
> >>>>>> parallel with snd_pcm_link() (somehow configuring the device)?
> >>>>> It's not about the actual application usages but rather against the
> >>>>> malicious attacks.  Especially aloop is a virtual device that is
> >>>>> available allover the places, it may be deployed / attacked easily.
> >>>> The attack we are identifying here can only be done by the application
> >>>> opening the aloop device.
> >>>> An application allowed to open the aloop device is anyway able to
> >>>> manipulate the audio streaming.
> >>> Right, and if it such a racy access may lead to a driver misbehavior,
> >>> it's a big concern.  The proposed callback usage is racy, so some
> >>> other implementation might be broken easily in future.
> >>>
> >>>> Or do you see an attack which would influence any other device/stream
> >>>> not opened by this application?
> >>>>
> >>>>>> May be we should add an additional synchronization mechanism in pcm_native.c
> >>>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> >>>>> If it really matters...  Honestly speaking, I'm not fully convinced
> >>>>> whether we want to deal with this using the PCM link mechanism.
> >>>>>
> >>>>> What's the motivation for using the linked streams at the first place?
> >>>>> That's one of the biggest missing piece in the whole picture.
> >>>> In general when the user uses snd_pcm_link() it expects that the
> >>>> linked devices are somehow synchronized.
> >>>> Any applications already using snd_pcm_link() do not need to be
> >>>> adapted to use the new feature of aloop (for example JACK or ALSA
> >>>> multi plugin)
> >>>>
> >>>> But when linking a HW sound card and aloop without this patch set,
> >>>> both devices will be started in sync but
> >>>> the snd_pcm_period_eleapsed() calls of the different devices will
> >>>> drift. To avoid this the aloop plugin can automatically use the right
> >>>> timer.
> >>>> If this feature is not implemented the user has to use snd_pcm_link()
> >>>> to trigger snd_pcm_start() in sync but also has to configure the aloop
> >>>> plugin to use the right sound timer.
> >>>> May be the linked cards can change during runtime of the
> >>>> system. Without this feature the aloop kernel driver has to be loaded
> >>>> with different kernel parameters
> >>>> to select the right timer.
> >>>>
> >>>> ALSA controls cannot be used easily. Selecting the sound timer by the
> >>>> card number could be error prone because the card ID could change
> >>>> between system starts.
> >>>> Therefore an ALSA control supporting the card name should be
> >>>> used. This could be for example done via an ALSA enum control. But in
> >>>> this case the names of all sound cards of the system has to be
> >>>> available
> >>>> on aloop probe() call. But at this point in time the sound cards
> >>>> probed after aloop are not available. Therefore only the sound timers
> >>>> of the sound cards probed before aloop are selectable.
> >>> Hm.  For me this patch series looks very hackish.  As mentioned, the
> >>> PCM link usage is rather just a synchronous trigger start/stop for
> >>> multiple streams belonging to the same hardware; in that sense, it'd
> >>> be possible to adapt some mechanism for aloop, but at most it should
> >>> be much less intrusive change, e.g. just doing the multiple
> >>> loopback_timer_start() in a single loop.
> >> What do you mean by "just doing the multiple loopback_timer_start() in
> >> a single loop."?
> >>
> >> The snd_pcm_link() call information are mainly used to select the
> >> right sound timer. Starting the timer in sync is a nice add-on.
> >> Therefore by simple starting all timers in a for loop I would not
> >> select the right sound timer.
> >> (May be I misunderstood your example)
> > My example is only about the system timer.
> >
> >> I could call the link_changed() callback inside
> >> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
> >> But an application could still call snd_pcm_link() and snd_pcm_start()
> >> in parallel e.g.:
> >>
> >> snd_pcm_common_ioctl(IOCTL_LINK)
> >>> task switch
> >> snd_pcm_common_ioctl(IOCTL_START)
> >> snd_pcm_start()
> >> done
> >>> continue previous task
> >> The same could for example happen with snd_pcm_start() and snd_hw_free():
> >>
> >> snd_pcm_common_ioctl(IOCTL_START)
> >>> task switch
> >> snd_pcm_common_ioctl(IOCTL_HW_FREE)
> >> snd_hw_free()
> >> Done
> >>> continue previous task
> >> snd_pcm_start() will fail with an error code
> >>
> >> Therefore I do not think we have to synchronize the IOCTL calls (this
> >> can only be done in the user application or ALSAlib). We only have to
> >> return an proper error code in case of misusage.
> >>
> >> As far as I understand ALSA was not designed for multithread usage. So
> >> the user (or ALSAlib) has to be aware of calling the functions in the
> >> right order.
> >> Otherwise the user could expect returned error codes.
> > Well, your assumption is plain wrong.  ALSA is designed for
> > multithread usage.  And the kernel works for multithread.
> >
> > It's not about the right usage.  It's about the robustness, about
> > whether any malicious code may lead to a serious defunct.  The opened
> > race in calls may easily introduce such a failure.
> >
> >> In case of my patch set snd_pcm_start() will always fail with a proper
> >> error code as long as no sound timer is running. This could be the
> >> case if snd_pcm_start() is called before the first snd_pcm_link()
> >> call.
> >> This is also the case if snd_pcm_start() is called when the
> >> snd_pcm_link() call has closed the old timer but not yet opened the
> >> new one.
> >> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
> >> write a proper warning (that the snd_pcm_link() call will have no
> >> effect because the stream is already running).
> >> All member variables used in loopback_snd_timer_open(),
> >> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
> >> always protected by cable->lock. Therefore no one can read an invalid
> >> state of these variables and the misusage can always be detected.
> >>
> >> Therefore I am not sure what I should change. May be you could
> >> reference to a source code line which looks hackish for you?
> > In principle, the PCM ops is supposed to be safe for operating a
> > certain stuff.  If a state change may happen during the operation,
> > this should be called inside PCM stream lock.  So the design of the
> > new callback itself is fragile.
> >
> > then, it comes to a point to re-setup the timer at the link change.
> > The idea is interesting, but it's a wrong usage of PCM link feature,
> > to be honest.
> >
> > That said, I find the changes up to patch 8 are acceptable (with some
> > fixes expected), but the link feature isn't.
> Thanks for clarification. Do you think there is a chance to get an
> acceptable snd_pcm_link() feature when using PCM stream lock for
> snd_pcm_link()?
> In this case I think snd_timer_open() has to be adapted to use
> non-blocking calls only (do not use mutexs).

This needs a good amount of consideration: currently the PCM link
feature is designed for the synchronous start/stop, especially for the
streams *on the same hardware*.  The link between different devices is
merely a best-effort.  This is the current design.

Your target is obviously above that level.  So we have to consider
either extending the usage and the implementation, or introducing
another API.  Both are things we'd like to avoid as much as possible
from the maintainability POV.

> Or do I have to use another API to configure the sound timer at
> runtime (e.g. ALSA controls or RW kernel module parameters)?

The kernel parameter would be handy.  An alternative would be a
configuration via proc or sysfs file.  Even configfs or debugfs is
another possibility, but the proc file would be easiest, I suppose.

Of course, the disadvantage of these is that it's usually writable
only by root.


Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-03-27  9:38                       ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-03-27  9:38 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Wed, 27 Mar 2019 10:26:56 +0100,
Timo Wischer wrote:
> 
> On 3/27/19 10:11, Takashi Iwai wrote:
> > On Wed, 27 Mar 2019 09:34:40 +0100,
> > Timo Wischer wrote:
> >> On 3/26/19 17:00, Takashi Iwai wrote:
> >>> On Tue, 26 Mar 2019 16:16:54 +0100,
> >>> Timo Wischer wrote:
> >>>> On 3/26/19 15:23, Takashi Iwai wrote:
> >>>>> On Tue, 26 Mar 2019 12:25:37 +0100,
> >>>>> Timo Wischer wrote:
> >>>>>> On 3/26/19 09:35, Takashi Iwai wrote:
> >>>>>>
> >>>>>>        On Tue, 26 Mar 2019 08:49:33 +0100,
> >>>>>>        <twischer@de.adit-jv.com> wrote:
> >>>>>>                 From: Timo Wischer <twischer@de.adit-jv.com>
> >>>>>>                     snd_pcm_link() can be called by the user as long
> >>>>>> as the device is not
> >>>>>>            yet started. Therefore currently a driver which wants to iterate over
> >>>>>>            the linked substreams has to do this at the start trigger. But the start
> >>>>>>            trigger should not block for a long time. Therefore there is no callback
> >>>>>>            which can be used to iterate over the linked substreams without delaying
> >>>>>>            the start trigger.
> >>>>>>            This patch introduces a new callback function which will be called after
> >>>>>>            the linked substream list was updated by snd_pcm_link(). This callback
> >>>>>>            function is allowed to block for a longer time without interfering the
> >>>>>>            synchronized start up of linked substreams.
> >>>>>>                     Signed-off-by: Timo Wischer
> >>>>>> <twischer@de.adit-jv.com>
> >>>>>>                 Well, the idea appears interesting, but I'm afraid
> >>>>>> that the
> >>>>>>        implementation is still racy.  The place you're calling the new
> >>>>>>        callback isn't protected, hence the stream can be triggered while
> >>>>>>        calling it.  That is, even during operating your loopback link_changed
> >>>>>>        callback, another thread is able to start the stream.
> >>>>>>        Hi Takashi,
> >>>>>>
> >>>>>> As far as I got you mean the following scenario:
> >>>>>>
> >>>>>>      * snd_pcm_link() is called for a HW sound card
> >>>>>>          + loopback_snd_timer_link_changed()
> >>>>> The start may happen at this point.
> >>>> In this case the last link status will be used and aloop will print a
> >>>> warning "Another sound timer was requested but at least one device is
> >>>> already running...".
> >>>>
> >>>> Without this patch set a similar issue already exists. When calling
> >>>> snd_pcm_start() before snd_pcm_link() was done the additional device
> >>>> linked by the snd_pcm_link() will not be started.
> >>>> Therefore the application has already to take care about the order of
> >>>> the calls.
> >>> Yes, but it doesn't matter for now, just because other drivers do care
> >>> the PCM links only for trigger callback.  Now you're trying to add
> >>> something new but in an incomplete manner.
> >>>
> >>>>>>          + loopback_snd_timer_open()
> >>>>>>          + spin_lock_irqsave(&dpcm->cable->lock, flags);
> >>>>>>      * snd_pcm_start() called for aloop sound card
> >>>>>>          + loopback_trigger()
> >>>>>>          + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
> >>>>>>            calls spin_unlock_irqrestore()
> >>>>>>
> >>>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
> >>>>>>
> >>>>>>      * loopback_snd_timer_open() will continue with
> >>>>>>          + dpcm->cable->snd_timer.instance = NULL;
> >>>>>>          + spin_unlock_irqrestore()
> >>>>>>      * loopback_trigger() can enter the lock
> >>>>>>          + loopback_snd_timer_start() will fail with -EINVAL due to
> >>>>>>            (loopback_trigger == NULL)
> >>>>>>
> >>>>>> At least this will not result into memory corruption due to race or any other
> >>>>>> wired behavior.
> >>>>> I don't expect the memory corruption, but my point is that dealing
> >>>>> with linked streams is still tricky.  It was considered for the
> >>>>> lightweight coupled start/stop operation, and something intensively
> >>>>> depending on the linked status was out of the original design...
> >>>>>
> >>>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
> >>>>>> is only called by the application calling snd_pcm_start(aloop)
> >>>>>> because the same aloop device cannot be opened by multiple applications at the
> >>>>>> same time.
> >>>>>>
> >>>>>> Do you see an use case where one application would call snd_pcm_start() in
> >>>>>> parallel with snd_pcm_link() (somehow configuring the device)?
> >>>>> It's not about the actual application usages but rather against the
> >>>>> malicious attacks.  Especially aloop is a virtual device that is
> >>>>> available allover the places, it may be deployed / attacked easily.
> >>>> The attack we are identifying here can only be done by the application
> >>>> opening the aloop device.
> >>>> An application allowed to open the aloop device is anyway able to
> >>>> manipulate the audio streaming.
> >>> Right, and if it such a racy access may lead to a driver misbehavior,
> >>> it's a big concern.  The proposed callback usage is racy, so some
> >>> other implementation might be broken easily in future.
> >>>
> >>>> Or do you see an attack which would influence any other device/stream
> >>>> not opened by this application?
> >>>>
> >>>>>> May be we should add an additional synchronization mechanism in pcm_native.c
> >>>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
> >>>>> If it really matters...  Honestly speaking, I'm not fully convinced
> >>>>> whether we want to deal with this using the PCM link mechanism.
> >>>>>
> >>>>> What's the motivation for using the linked streams at the first place?
> >>>>> That's one of the biggest missing piece in the whole picture.
> >>>> In general when the user uses snd_pcm_link() it expects that the
> >>>> linked devices are somehow synchronized.
> >>>> Any applications already using snd_pcm_link() do not need to be
> >>>> adapted to use the new feature of aloop (for example JACK or ALSA
> >>>> multi plugin)
> >>>>
> >>>> But when linking a HW sound card and aloop without this patch set,
> >>>> both devices will be started in sync but
> >>>> the snd_pcm_period_eleapsed() calls of the different devices will
> >>>> drift. To avoid this the aloop plugin can automatically use the right
> >>>> timer.
> >>>> If this feature is not implemented the user has to use snd_pcm_link()
> >>>> to trigger snd_pcm_start() in sync but also has to configure the aloop
> >>>> plugin to use the right sound timer.
> >>>> May be the linked cards can change during runtime of the
> >>>> system. Without this feature the aloop kernel driver has to be loaded
> >>>> with different kernel parameters
> >>>> to select the right timer.
> >>>>
> >>>> ALSA controls cannot be used easily. Selecting the sound timer by the
> >>>> card number could be error prone because the card ID could change
> >>>> between system starts.
> >>>> Therefore an ALSA control supporting the card name should be
> >>>> used. This could be for example done via an ALSA enum control. But in
> >>>> this case the names of all sound cards of the system has to be
> >>>> available
> >>>> on aloop probe() call. But at this point in time the sound cards
> >>>> probed after aloop are not available. Therefore only the sound timers
> >>>> of the sound cards probed before aloop are selectable.
> >>> Hm.  For me this patch series looks very hackish.  As mentioned, the
> >>> PCM link usage is rather just a synchronous trigger start/stop for
> >>> multiple streams belonging to the same hardware; in that sense, it'd
> >>> be possible to adapt some mechanism for aloop, but at most it should
> >>> be much less intrusive change, e.g. just doing the multiple
> >>> loopback_timer_start() in a single loop.
> >> What do you mean by "just doing the multiple loopback_timer_start() in
> >> a single loop."?
> >>
> >> The snd_pcm_link() call information are mainly used to select the
> >> right sound timer. Starting the timer in sync is a nice add-on.
> >> Therefore by simple starting all timers in a for loop I would not
> >> select the right sound timer.
> >> (May be I misunderstood your example)
> > My example is only about the system timer.
> >
> >> I could call the link_changed() callback inside
> >> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
> >> But an application could still call snd_pcm_link() and snd_pcm_start()
> >> in parallel e.g.:
> >>
> >> snd_pcm_common_ioctl(IOCTL_LINK)
> >>> task switch
> >> snd_pcm_common_ioctl(IOCTL_START)
> >> snd_pcm_start()
> >> done
> >>> continue previous task
> >> The same could for example happen with snd_pcm_start() and snd_hw_free():
> >>
> >> snd_pcm_common_ioctl(IOCTL_START)
> >>> task switch
> >> snd_pcm_common_ioctl(IOCTL_HW_FREE)
> >> snd_hw_free()
> >> Done
> >>> continue previous task
> >> snd_pcm_start() will fail with an error code
> >>
> >> Therefore I do not think we have to synchronize the IOCTL calls (this
> >> can only be done in the user application or ALSAlib). We only have to
> >> return an proper error code in case of misusage.
> >>
> >> As far as I understand ALSA was not designed for multithread usage. So
> >> the user (or ALSAlib) has to be aware of calling the functions in the
> >> right order.
> >> Otherwise the user could expect returned error codes.
> > Well, your assumption is plain wrong.  ALSA is designed for
> > multithread usage.  And the kernel works for multithread.
> >
> > It's not about the right usage.  It's about the robustness, about
> > whether any malicious code may lead to a serious defunct.  The opened
> > race in calls may easily introduce such a failure.
> >
> >> In case of my patch set snd_pcm_start() will always fail with a proper
> >> error code as long as no sound timer is running. This could be the
> >> case if snd_pcm_start() is called before the first snd_pcm_link()
> >> call.
> >> This is also the case if snd_pcm_start() is called when the
> >> snd_pcm_link() call has closed the old timer but not yet opened the
> >> new one.
> >> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
> >> write a proper warning (that the snd_pcm_link() call will have no
> >> effect because the stream is already running).
> >> All member variables used in loopback_snd_timer_open(),
> >> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
> >> always protected by cable->lock. Therefore no one can read an invalid
> >> state of these variables and the misusage can always be detected.
> >>
> >> Therefore I am not sure what I should change. May be you could
> >> reference to a source code line which looks hackish for you?
> > In principle, the PCM ops is supposed to be safe for operating a
> > certain stuff.  If a state change may happen during the operation,
> > this should be called inside PCM stream lock.  So the design of the
> > new callback itself is fragile.
> >
> > then, it comes to a point to re-setup the timer at the link change.
> > The idea is interesting, but it's a wrong usage of PCM link feature,
> > to be honest.
> >
> > That said, I find the changes up to patch 8 are acceptable (with some
> > fixes expected), but the link feature isn't.
> Thanks for clarification. Do you think there is a chance to get an
> acceptable snd_pcm_link() feature when using PCM stream lock for
> snd_pcm_link()?
> In this case I think snd_timer_open() has to be adapted to use
> non-blocking calls only (do not use mutexs).

This needs a good amount of consideration: currently the PCM link
feature is designed for the synchronous start/stop, especially for the
streams *on the same hardware*.  The link between different devices is
merely a best-effort.  This is the current design.

Your target is obviously above that level.  So we have to consider
either extending the usage and the implementation, or introducing
another API.  Both are things we'd like to avoid as much as possible
from the maintainability POV.

> Or do I have to use another API to configure the sound timer at
> runtime (e.g. ALSA controls or RW kernel module parameters)?

The kernel parameter would be handy.  An alternative would be a
configuration via proc or sysfs file.  Even configfs or debugfs is
another possibility, but the proc file would be easiest, I suppose.

Of course, the disadvantage of these is that it's usually writable
only by root.


Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-03-27  9:38                       ` Takashi Iwai
@ 2019-04-04 10:18                         ` Timo Wischer
  -1 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-04-04 10:18 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/27/19 10:38, Takashi Iwai wrote:
> On Wed, 27 Mar 2019 10:26:56 +0100,
> Timo Wischer wrote:
>> On 3/27/19 10:11, Takashi Iwai wrote:
>>> On Wed, 27 Mar 2019 09:34:40 +0100,
>>> Timo Wischer wrote:
>>>> On 3/26/19 17:00, Takashi Iwai wrote:
>>>>> On Tue, 26 Mar 2019 16:16:54 +0100,
>>>>> Timo Wischer wrote:
>>>>>> On 3/26/19 15:23, Takashi Iwai wrote:
>>>>>>> On Tue, 26 Mar 2019 12:25:37 +0100,
>>>>>>> Timo Wischer wrote:
>>>>>>>> On 3/26/19 09:35, Takashi Iwai wrote:
>>>>>>>>
>>>>>>>>         On Tue, 26 Mar 2019 08:49:33 +0100,
>>>>>>>>         <twischer@de.adit-jv.com> wrote:
>>>>>>>>                  From: Timo Wischer <twischer@de.adit-jv.com>
>>>>>>>>                      snd_pcm_link() can be called by the user as long
>>>>>>>> as the device is not
>>>>>>>>             yet started. Therefore currently a driver which wants to iterate over
>>>>>>>>             the linked substreams has to do this at the start trigger. But the start
>>>>>>>>             trigger should not block for a long time. Therefore there is no callback
>>>>>>>>             which can be used to iterate over the linked substreams without delaying
>>>>>>>>             the start trigger.
>>>>>>>>             This patch introduces a new callback function which will be called after
>>>>>>>>             the linked substream list was updated by snd_pcm_link(). This callback
>>>>>>>>             function is allowed to block for a longer time without interfering the
>>>>>>>>             synchronized start up of linked substreams.
>>>>>>>>                      Signed-off-by: Timo Wischer
>>>>>>>> <twischer@de.adit-jv.com>
>>>>>>>>                  Well, the idea appears interesting, but I'm afraid
>>>>>>>> that the
>>>>>>>>         implementation is still racy.  The place you're calling the new
>>>>>>>>         callback isn't protected, hence the stream can be triggered while
>>>>>>>>         calling it.  That is, even during operating your loopback link_changed
>>>>>>>>         callback, another thread is able to start the stream.
>>>>>>>>         Hi Takashi,
>>>>>>>>
>>>>>>>> As far as I got you mean the following scenario:
>>>>>>>>
>>>>>>>>       * snd_pcm_link() is called for a HW sound card
>>>>>>>>           + loopback_snd_timer_link_changed()
>>>>>>> The start may happen at this point.
>>>>>> In this case the last link status will be used and aloop will print a
>>>>>> warning "Another sound timer was requested but at least one device is
>>>>>> already running...".
>>>>>>
>>>>>> Without this patch set a similar issue already exists. When calling
>>>>>> snd_pcm_start() before snd_pcm_link() was done the additional device
>>>>>> linked by the snd_pcm_link() will not be started.
>>>>>> Therefore the application has already to take care about the order of
>>>>>> the calls.
>>>>> Yes, but it doesn't matter for now, just because other drivers do care
>>>>> the PCM links only for trigger callback.  Now you're trying to add
>>>>> something new but in an incomplete manner.
>>>>>
>>>>>>>>           + loopback_snd_timer_open()
>>>>>>>>           + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>>>>>>>       * snd_pcm_start() called for aloop sound card
>>>>>>>>           + loopback_trigger()
>>>>>>>>           + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>>>>>>>             calls spin_unlock_irqrestore()
>>>>>>>>
>>>>>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>>>>>>>
>>>>>>>>       * loopback_snd_timer_open() will continue with
>>>>>>>>           + dpcm->cable->snd_timer.instance = NULL;
>>>>>>>>           + spin_unlock_irqrestore()
>>>>>>>>       * loopback_trigger() can enter the lock
>>>>>>>>           + loopback_snd_timer_start() will fail with -EINVAL due to
>>>>>>>>             (loopback_trigger == NULL)
>>>>>>>>
>>>>>>>> At least this will not result into memory corruption due to race or any other
>>>>>>>> wired behavior.
>>>>>>> I don't expect the memory corruption, but my point is that dealing
>>>>>>> with linked streams is still tricky.  It was considered for the
>>>>>>> lightweight coupled start/stop operation, and something intensively
>>>>>>> depending on the linked status was out of the original design...
>>>>>>>
>>>>>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>>>>>>>> is only called by the application calling snd_pcm_start(aloop)
>>>>>>>> because the same aloop device cannot be opened by multiple applications at the
>>>>>>>> same time.
>>>>>>>>
>>>>>>>> Do you see an use case where one application would call snd_pcm_start() in
>>>>>>>> parallel with snd_pcm_link() (somehow configuring the device)?
>>>>>>> It's not about the actual application usages but rather against the
>>>>>>> malicious attacks.  Especially aloop is a virtual device that is
>>>>>>> available allover the places, it may be deployed / attacked easily.
>>>>>> The attack we are identifying here can only be done by the application
>>>>>> opening the aloop device.
>>>>>> An application allowed to open the aloop device is anyway able to
>>>>>> manipulate the audio streaming.
>>>>> Right, and if it such a racy access may lead to a driver misbehavior,
>>>>> it's a big concern.  The proposed callback usage is racy, so some
>>>>> other implementation might be broken easily in future.
>>>>>
>>>>>> Or do you see an attack which would influence any other device/stream
>>>>>> not opened by this application?
>>>>>>
>>>>>>>> May be we should add an additional synchronization mechanism in pcm_native.c
>>>>>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
>>>>>>> If it really matters...  Honestly speaking, I'm not fully convinced
>>>>>>> whether we want to deal with this using the PCM link mechanism.
>>>>>>>
>>>>>>> What's the motivation for using the linked streams at the first place?
>>>>>>> That's one of the biggest missing piece in the whole picture.
>>>>>> In general when the user uses snd_pcm_link() it expects that the
>>>>>> linked devices are somehow synchronized.
>>>>>> Any applications already using snd_pcm_link() do not need to be
>>>>>> adapted to use the new feature of aloop (for example JACK or ALSA
>>>>>> multi plugin)
>>>>>>
>>>>>> But when linking a HW sound card and aloop without this patch set,
>>>>>> both devices will be started in sync but
>>>>>> the snd_pcm_period_eleapsed() calls of the different devices will
>>>>>> drift. To avoid this the aloop plugin can automatically use the right
>>>>>> timer.
>>>>>> If this feature is not implemented the user has to use snd_pcm_link()
>>>>>> to trigger snd_pcm_start() in sync but also has to configure the aloop
>>>>>> plugin to use the right sound timer.
>>>>>> May be the linked cards can change during runtime of the
>>>>>> system. Without this feature the aloop kernel driver has to be loaded
>>>>>> with different kernel parameters
>>>>>> to select the right timer.
>>>>>>
>>>>>> ALSA controls cannot be used easily. Selecting the sound timer by the
>>>>>> card number could be error prone because the card ID could change
>>>>>> between system starts.
>>>>>> Therefore an ALSA control supporting the card name should be
>>>>>> used. This could be for example done via an ALSA enum control. But in
>>>>>> this case the names of all sound cards of the system has to be
>>>>>> available
>>>>>> on aloop probe() call. But at this point in time the sound cards
>>>>>> probed after aloop are not available. Therefore only the sound timers
>>>>>> of the sound cards probed before aloop are selectable.
>>>>> Hm.  For me this patch series looks very hackish.  As mentioned, the
>>>>> PCM link usage is rather just a synchronous trigger start/stop for
>>>>> multiple streams belonging to the same hardware; in that sense, it'd
>>>>> be possible to adapt some mechanism for aloop, but at most it should
>>>>> be much less intrusive change, e.g. just doing the multiple
>>>>> loopback_timer_start() in a single loop.
>>>> What do you mean by "just doing the multiple loopback_timer_start() in
>>>> a single loop."?
>>>>
>>>> The snd_pcm_link() call information are mainly used to select the
>>>> right sound timer. Starting the timer in sync is a nice add-on.
>>>> Therefore by simple starting all timers in a for loop I would not
>>>> select the right sound timer.
>>>> (May be I misunderstood your example)
>>> My example is only about the system timer.
>>>
>>>> I could call the link_changed() callback inside
>>>> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
>>>> But an application could still call snd_pcm_link() and snd_pcm_start()
>>>> in parallel e.g.:
>>>>
>>>> snd_pcm_common_ioctl(IOCTL_LINK)
>>>>> task switch
>>>> snd_pcm_common_ioctl(IOCTL_START)
>>>> snd_pcm_start()
>>>> done
>>>>> continue previous task
>>>> The same could for example happen with snd_pcm_start() and snd_hw_free():
>>>>
>>>> snd_pcm_common_ioctl(IOCTL_START)
>>>>> task switch
>>>> snd_pcm_common_ioctl(IOCTL_HW_FREE)
>>>> snd_hw_free()
>>>> Done
>>>>> continue previous task
>>>> snd_pcm_start() will fail with an error code
>>>>
>>>> Therefore I do not think we have to synchronize the IOCTL calls (this
>>>> can only be done in the user application or ALSAlib). We only have to
>>>> return an proper error code in case of misusage.
>>>>
>>>> As far as I understand ALSA was not designed for multithread usage. So
>>>> the user (or ALSAlib) has to be aware of calling the functions in the
>>>> right order.
>>>> Otherwise the user could expect returned error codes.
>>> Well, your assumption is plain wrong.  ALSA is designed for
>>> multithread usage.  And the kernel works for multithread.
>>>
>>> It's not about the right usage.  It's about the robustness, about
>>> whether any malicious code may lead to a serious defunct.  The opened
>>> race in calls may easily introduce such a failure.
>>>
>>>> In case of my patch set snd_pcm_start() will always fail with a proper
>>>> error code as long as no sound timer is running. This could be the
>>>> case if snd_pcm_start() is called before the first snd_pcm_link()
>>>> call.
>>>> This is also the case if snd_pcm_start() is called when the
>>>> snd_pcm_link() call has closed the old timer but not yet opened the
>>>> new one.
>>>> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
>>>> write a proper warning (that the snd_pcm_link() call will have no
>>>> effect because the stream is already running).
>>>> All member variables used in loopback_snd_timer_open(),
>>>> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
>>>> always protected by cable->lock. Therefore no one can read an invalid
>>>> state of these variables and the misusage can always be detected.
>>>>
>>>> Therefore I am not sure what I should change. May be you could
>>>> reference to a source code line which looks hackish for you?
>>> In principle, the PCM ops is supposed to be safe for operating a
>>> certain stuff.  If a state change may happen during the operation,
>>> this should be called inside PCM stream lock.  So the design of the
>>> new callback itself is fragile.
>>>
>>> then, it comes to a point to re-setup the timer at the link change.
>>> The idea is interesting, but it's a wrong usage of PCM link feature,
>>> to be honest.
>>>
>>> That said, I find the changes up to patch 8 are acceptable (with some
>>> fixes expected), but the link feature isn't.

Hi Takashi,

could you provide your feedback to the other patches?

I would like to apply these 8 patches first and then I would provide an 
addition patch to get an ALSA control to select the sound timer.
I fear this would be the best idea (replacing the snd_pcm_link() 
feature) because the ALSA control is accessible by any audio user and
we could use ALSA hooks to select the right sound timer when opening an 
ALSA device.

>> Thanks for clarification. Do you think there is a chance to get an
>> acceptable snd_pcm_link() feature when using PCM stream lock for
>> snd_pcm_link()?
>> In this case I think snd_timer_open() has to be adapted to use
>> non-blocking calls only (do not use mutexs).
> This needs a good amount of consideration: currently the PCM link
> feature is designed for the synchronous start/stop, especially for the
> streams *on the same hardware*.  The link between different devices is
> merely a best-effort.  This is the current design.
>
> Your target is obviously above that level.  So we have to consider
> either extending the usage and the implementation, or introducing
> another API.  Both are things we'd like to avoid as much as possible
> from the maintainability POV.
>
>> Or do I have to use another API to configure the sound timer at
>> runtime (e.g. ALSA controls or RW kernel module parameters)?
> The kernel parameter would be handy.  An alternative would be a
> configuration via proc or sysfs file.  Even configfs or debugfs is
> another possibility, but the proc file would be easiest, I suppose.
>
> Of course, the disadvantage of these is that it's usually writable
> only by root.
>
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-04-04 10:18                         ` Timo Wischer
  0 siblings, 0 replies; 50+ messages in thread
From: Timo Wischer @ 2019-04-04 10:18 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: broonie, perex, alsa-devel, linux-kernel

On 3/27/19 10:38, Takashi Iwai wrote:
> On Wed, 27 Mar 2019 10:26:56 +0100,
> Timo Wischer wrote:
>> On 3/27/19 10:11, Takashi Iwai wrote:
>>> On Wed, 27 Mar 2019 09:34:40 +0100,
>>> Timo Wischer wrote:
>>>> On 3/26/19 17:00, Takashi Iwai wrote:
>>>>> On Tue, 26 Mar 2019 16:16:54 +0100,
>>>>> Timo Wischer wrote:
>>>>>> On 3/26/19 15:23, Takashi Iwai wrote:
>>>>>>> On Tue, 26 Mar 2019 12:25:37 +0100,
>>>>>>> Timo Wischer wrote:
>>>>>>>> On 3/26/19 09:35, Takashi Iwai wrote:
>>>>>>>>
>>>>>>>>         On Tue, 26 Mar 2019 08:49:33 +0100,
>>>>>>>>         <twischer@de.adit-jv.com> wrote:
>>>>>>>>                  From: Timo Wischer <twischer@de.adit-jv.com>
>>>>>>>>                      snd_pcm_link() can be called by the user as long
>>>>>>>> as the device is not
>>>>>>>>             yet started. Therefore currently a driver which wants to iterate over
>>>>>>>>             the linked substreams has to do this at the start trigger. But the start
>>>>>>>>             trigger should not block for a long time. Therefore there is no callback
>>>>>>>>             which can be used to iterate over the linked substreams without delaying
>>>>>>>>             the start trigger.
>>>>>>>>             This patch introduces a new callback function which will be called after
>>>>>>>>             the linked substream list was updated by snd_pcm_link(). This callback
>>>>>>>>             function is allowed to block for a longer time without interfering the
>>>>>>>>             synchronized start up of linked substreams.
>>>>>>>>                      Signed-off-by: Timo Wischer
>>>>>>>> <twischer@de.adit-jv.com>
>>>>>>>>                  Well, the idea appears interesting, but I'm afraid
>>>>>>>> that the
>>>>>>>>         implementation is still racy.  The place you're calling the new
>>>>>>>>         callback isn't protected, hence the stream can be triggered while
>>>>>>>>         calling it.  That is, even during operating your loopback link_changed
>>>>>>>>         callback, another thread is able to start the stream.
>>>>>>>>         Hi Takashi,
>>>>>>>>
>>>>>>>> As far as I got you mean the following scenario:
>>>>>>>>
>>>>>>>>       * snd_pcm_link() is called for a HW sound card
>>>>>>>>           + loopback_snd_timer_link_changed()
>>>>>>> The start may happen at this point.
>>>>>> In this case the last link status will be used and aloop will print a
>>>>>> warning "Another sound timer was requested but at least one device is
>>>>>> already running...".
>>>>>>
>>>>>> Without this patch set a similar issue already exists. When calling
>>>>>> snd_pcm_start() before snd_pcm_link() was done the additional device
>>>>>> linked by the snd_pcm_link() will not be started.
>>>>>> Therefore the application has already to take care about the order of
>>>>>> the calls.
>>>>> Yes, but it doesn't matter for now, just because other drivers do care
>>>>> the PCM links only for trigger callback.  Now you're trying to add
>>>>> something new but in an incomplete manner.
>>>>>
>>>>>>>>           + loopback_snd_timer_open()
>>>>>>>>           + spin_lock_irqsave(&dpcm->cable->lock, flags);
>>>>>>>>       * snd_pcm_start() called for aloop sound card
>>>>>>>>           + loopback_trigger()
>>>>>>>>           + spin_lock(&cable->lock) -> has to wait till loopback_snd_timer_open()
>>>>>>>>             calls spin_unlock_irqrestore()
>>>>>>>>
>>>>>>>> So far snd_pcm_start() has to wait for loopback_snd_timer_open().
>>>>>>>>
>>>>>>>>       * loopback_snd_timer_open() will continue with
>>>>>>>>           + dpcm->cable->snd_timer.instance = NULL;
>>>>>>>>           + spin_unlock_irqrestore()
>>>>>>>>       * loopback_trigger() can enter the lock
>>>>>>>>           + loopback_snd_timer_start() will fail with -EINVAL due to
>>>>>>>>             (loopback_trigger == NULL)
>>>>>>>>
>>>>>>>> At least this will not result into memory corruption due to race or any other
>>>>>>>> wired behavior.
>>>>>>> I don't expect the memory corruption, but my point is that dealing
>>>>>>> with linked streams is still tricky.  It was considered for the
>>>>>>> lightweight coupled start/stop operation, and something intensively
>>>>>>> depending on the linked status was out of the original design...
>>>>>>>
>>>>>>>> But my expectation is that snd_pcm_link(hw, aloop) or snd_pcm_link(aloop, hw)
>>>>>>>> is only called by the application calling snd_pcm_start(aloop)
>>>>>>>> because the same aloop device cannot be opened by multiple applications at the
>>>>>>>> same time.
>>>>>>>>
>>>>>>>> Do you see an use case where one application would call snd_pcm_start() in
>>>>>>>> parallel with snd_pcm_link() (somehow configuring the device)?
>>>>>>> It's not about the actual application usages but rather against the
>>>>>>> malicious attacks.  Especially aloop is a virtual device that is
>>>>>>> available allover the places, it may be deployed / attacked easily.
>>>>>> The attack we are identifying here can only be done by the application
>>>>>> opening the aloop device.
>>>>>> An application allowed to open the aloop device is anyway able to
>>>>>> manipulate the audio streaming.
>>>>> Right, and if it such a racy access may lead to a driver misbehavior,
>>>>> it's a big concern.  The proposed callback usage is racy, so some
>>>>> other implementation might be broken easily in future.
>>>>>
>>>>>> Or do you see an attack which would influence any other device/stream
>>>>>> not opened by this application?
>>>>>>
>>>>>>>> May be we should add an additional synchronization mechanism in pcm_native.c
>>>>>>>> to avoid call of snd_pcm_link() in parallel with snd_pcm_start().
>>>>>>> If it really matters...  Honestly speaking, I'm not fully convinced
>>>>>>> whether we want to deal with this using the PCM link mechanism.
>>>>>>>
>>>>>>> What's the motivation for using the linked streams at the first place?
>>>>>>> That's one of the biggest missing piece in the whole picture.
>>>>>> In general when the user uses snd_pcm_link() it expects that the
>>>>>> linked devices are somehow synchronized.
>>>>>> Any applications already using snd_pcm_link() do not need to be
>>>>>> adapted to use the new feature of aloop (for example JACK or ALSA
>>>>>> multi plugin)
>>>>>>
>>>>>> But when linking a HW sound card and aloop without this patch set,
>>>>>> both devices will be started in sync but
>>>>>> the snd_pcm_period_eleapsed() calls of the different devices will
>>>>>> drift. To avoid this the aloop plugin can automatically use the right
>>>>>> timer.
>>>>>> If this feature is not implemented the user has to use snd_pcm_link()
>>>>>> to trigger snd_pcm_start() in sync but also has to configure the aloop
>>>>>> plugin to use the right sound timer.
>>>>>> May be the linked cards can change during runtime of the
>>>>>> system. Without this feature the aloop kernel driver has to be loaded
>>>>>> with different kernel parameters
>>>>>> to select the right timer.
>>>>>>
>>>>>> ALSA controls cannot be used easily. Selecting the sound timer by the
>>>>>> card number could be error prone because the card ID could change
>>>>>> between system starts.
>>>>>> Therefore an ALSA control supporting the card name should be
>>>>>> used. This could be for example done via an ALSA enum control. But in
>>>>>> this case the names of all sound cards of the system has to be
>>>>>> available
>>>>>> on aloop probe() call. But at this point in time the sound cards
>>>>>> probed after aloop are not available. Therefore only the sound timers
>>>>>> of the sound cards probed before aloop are selectable.
>>>>> Hm.  For me this patch series looks very hackish.  As mentioned, the
>>>>> PCM link usage is rather just a synchronous trigger start/stop for
>>>>> multiple streams belonging to the same hardware; in that sense, it'd
>>>>> be possible to adapt some mechanism for aloop, but at most it should
>>>>> be much less intrusive change, e.g. just doing the multiple
>>>>> loopback_timer_start() in a single loop.
>>>> What do you mean by "just doing the multiple loopback_timer_start() in
>>>> a single loop."?
>>>>
>>>> The snd_pcm_link() call information are mainly used to select the
>>>> right sound timer. Starting the timer in sync is a nice add-on.
>>>> Therefore by simple starting all timers in a for loop I would not
>>>> select the right sound timer.
>>>> (May be I misunderstood your example)
>>> My example is only about the system timer.
>>>
>>>> I could call the link_changed() callback inside
>>>> snd_pcm_stream_lock_irq() same lock also hold by snd_pcm_start().
>>>> But an application could still call snd_pcm_link() and snd_pcm_start()
>>>> in parallel e.g.:
>>>>
>>>> snd_pcm_common_ioctl(IOCTL_LINK)
>>>>> task switch
>>>> snd_pcm_common_ioctl(IOCTL_START)
>>>> snd_pcm_start()
>>>> done
>>>>> continue previous task
>>>> The same could for example happen with snd_pcm_start() and snd_hw_free():
>>>>
>>>> snd_pcm_common_ioctl(IOCTL_START)
>>>>> task switch
>>>> snd_pcm_common_ioctl(IOCTL_HW_FREE)
>>>> snd_hw_free()
>>>> Done
>>>>> continue previous task
>>>> snd_pcm_start() will fail with an error code
>>>>
>>>> Therefore I do not think we have to synchronize the IOCTL calls (this
>>>> can only be done in the user application or ALSAlib). We only have to
>>>> return an proper error code in case of misusage.
>>>>
>>>> As far as I understand ALSA was not designed for multithread usage. So
>>>> the user (or ALSAlib) has to be aware of calling the functions in the
>>>> right order.
>>>> Otherwise the user could expect returned error codes.
>>> Well, your assumption is plain wrong.  ALSA is designed for
>>> multithread usage.  And the kernel works for multithread.
>>>
>>> It's not about the right usage.  It's about the robustness, about
>>> whether any malicious code may lead to a serious defunct.  The opened
>>> race in calls may easily introduce such a failure.
>>>
>>>> In case of my patch set snd_pcm_start() will always fail with a proper
>>>> error code as long as no sound timer is running. This could be the
>>>> case if snd_pcm_start() is called before the first snd_pcm_link()
>>>> call.
>>>> This is also the case if snd_pcm_start() is called when the
>>>> snd_pcm_link() call has closed the old timer but not yet opened the
>>>> new one.
>>>> In case of snd_pcm_link() is called after snd_pcm_start() aloop will
>>>> write a proper warning (that the snd_pcm_link() call will have no
>>>> effect because the stream is already running).
>>>> All member variables used in loopback_snd_timer_open(),
>>>> loopback_snd_timer_source_update() and loopback_snd_timer_start() are
>>>> always protected by cable->lock. Therefore no one can read an invalid
>>>> state of these variables and the misusage can always be detected.
>>>>
>>>> Therefore I am not sure what I should change. May be you could
>>>> reference to a source code line which looks hackish for you?
>>> In principle, the PCM ops is supposed to be safe for operating a
>>> certain stuff.  If a state change may happen during the operation,
>>> this should be called inside PCM stream lock.  So the design of the
>>> new callback itself is fragile.
>>>
>>> then, it comes to a point to re-setup the timer at the link change.
>>> The idea is interesting, but it's a wrong usage of PCM link feature,
>>> to be honest.
>>>
>>> That said, I find the changes up to patch 8 are acceptable (with some
>>> fixes expected), but the link feature isn't.

Hi Takashi,

could you provide your feedback to the other patches?

I would like to apply these 8 patches first and then I would provide an 
addition patch to get an ALSA control to select the sound timer.
I fear this would be the best idea (replacing the snd_pcm_link() 
feature) because the ALSA control is accessible by any audio user and
we could use ALSA hooks to select the right sound timer when opening an 
ALSA device.

>> Thanks for clarification. Do you think there is a chance to get an
>> acceptable snd_pcm_link() feature when using PCM stream lock for
>> snd_pcm_link()?
>> In this case I think snd_timer_open() has to be adapted to use
>> non-blocking calls only (do not use mutexs).
> This needs a good amount of consideration: currently the PCM link
> feature is designed for the synchronous start/stop, especially for the
> streams *on the same hardware*.  The link between different devices is
> merely a best-effort.  This is the current design.
>
> Your target is obviously above that level.  So we have to consider
> either extending the usage and the implementation, or introducing
> another API.  Both are things we'd like to avoid as much as possible
> from the maintainability POV.
>
>> Or do I have to use another API to configure the sound timer at
>> runtime (e.g. ALSA controls or RW kernel module parameters)?
> The kernel parameter would be handy.  An alternative would be a
> configuration via proc or sysfs file.  Even configfs or debugfs is
> another possibility, but the proc file would be easiest, I suppose.
>
> Of course, the disadvantage of these is that it's usually writable
> only by root.
>
>
> Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
  2019-04-04 10:18                         ` Timo Wischer
@ 2019-04-05 15:21                           ` Takashi Iwai
  -1 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-04-05 15:21 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Thu, 04 Apr 2019 12:18:33 +0200,
Timo Wischer wrote:
> 
> >>> In principle, the PCM ops is supposed to be safe for operating a
> >>> certain stuff.  If a state change may happen during the operation,
> >>> this should be called inside PCM stream lock.  So the design of the
> >>> new callback itself is fragile.
> >>>
> >>> then, it comes to a point to re-setup the timer at the link change.
> >>> The idea is interesting, but it's a wrong usage of PCM link feature,
> >>> to be honest.
> >>>
> >>> That said, I find the changes up to patch 8 are acceptable (with some
> >>> fixes expected), but the link feature isn't.
> 
> Hi Takashi,
> 
> could you provide your feedback to the other patches?

The patch 4 must be superfluous, at least.  The trigger callback is
called within the stream lock, so you really don't need to save
flags.

The patch 8 needs more consideration.  I'm not fond of the current way
to select the timer source.  For example, the ALSA timer source isn't
necessarily bound with cards.  It can be a hrtimer, or system timer
backend ALSA global timer, too.  This can't be chosen in the suggested
way.  Also, the use of tasklet is superfluous.  The timer instance can
be called via tasklet by itself if you pass the flag properly.

> I would like to apply these 8 patches first and then I would provide
> an addition patch to get an ALSA control to select the sound timer.
> I fear this would be the best idea (replacing the snd_pcm_link()
> feature) because the ALSA control is accessible by any audio user and
> we could use ALSA hooks to select the right sound timer when opening
> an ALSA device.

It depends on the implementation.  Usually you "lock" such a control
that is bound with a PCM stream at opening, so that other process
won't be able to change.  Such a trick is used for the dynamic routing
via control element for emu10k1, for example.

But, whether it's really usable, I don't know.  Let's see.


thanks,

Takashi

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

* Re: [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link()
@ 2019-04-05 15:21                           ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2019-04-05 15:21 UTC (permalink / raw)
  To: Timo Wischer; +Cc: broonie, perex, alsa-devel, linux-kernel

On Thu, 04 Apr 2019 12:18:33 +0200,
Timo Wischer wrote:
> 
> >>> In principle, the PCM ops is supposed to be safe for operating a
> >>> certain stuff.  If a state change may happen during the operation,
> >>> this should be called inside PCM stream lock.  So the design of the
> >>> new callback itself is fragile.
> >>>
> >>> then, it comes to a point to re-setup the timer at the link change.
> >>> The idea is interesting, but it's a wrong usage of PCM link feature,
> >>> to be honest.
> >>>
> >>> That said, I find the changes up to patch 8 are acceptable (with some
> >>> fixes expected), but the link feature isn't.
> 
> Hi Takashi,
> 
> could you provide your feedback to the other patches?

The patch 4 must be superfluous, at least.  The trigger callback is
called within the stream lock, so you really don't need to save
flags.

The patch 8 needs more consideration.  I'm not fond of the current way
to select the timer source.  For example, the ALSA timer source isn't
necessarily bound with cards.  It can be a hrtimer, or system timer
backend ALSA global timer, too.  This can't be chosen in the suggested
way.  Also, the use of tasklet is superfluous.  The timer instance can
be called via tasklet by itself if you pass the flag properly.

> I would like to apply these 8 patches first and then I would provide
> an addition patch to get an ALSA control to select the sound timer.
> I fear this would be the best idea (replacing the snd_pcm_link()
> feature) because the ALSA control is accessible by any audio user and
> we could use ALSA hooks to select the right sound timer when opening
> an ALSA device.

It depends on the implementation.  Usually you "lock" such a control
that is bound with a PCM stream at opening, so that other process
won't be able to change.  Such a trick is used for the dynamic routing
via control element for emu10k1, for example.

But, whether it's really usable, I don't know.  Let's see.


thanks,

Takashi

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

end of thread, other threads:[~2019-04-05 15:21 UTC | newest]

Thread overview: 50+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-25 16:00 [PATCH 00/10] ALSA: aloop: Support selection of snd_timer twischer
2019-03-25 16:00 ` twischer
2019-03-25 16:00 ` [PATCH 01/10] ALSA: aloop: Describe units of variables twischer
2019-03-25 16:00   ` twischer
2019-03-25 16:00 ` [PATCH 02/10] ALSA: aloop: loopback_timer_start: Support return of error code twischer
2019-03-25 16:00   ` twischer
2019-03-25 16:00 ` [PATCH 03/10] ALSA: aloop: loopback_timer_stop: " twischer
2019-03-25 16:00   ` twischer
2019-03-25 16:00 ` [PATCH 04/10] ALSA: aloop: Use always spin_lock_irqsave() for cable->lock twischer
2019-03-25 16:00   ` twischer
2019-03-25 16:07   ` Takashi Iwai
2019-03-25 16:07     ` Takashi Iwai
2019-03-25 16:40     ` Timo Wischer
2019-03-25 16:58       ` Takashi Iwai
2019-03-25 16:58         ` Takashi Iwai
2019-03-26  8:12         ` Timo Wischer
2019-03-26  8:12           ` Timo Wischer
2019-03-26  7:49 ` [PATCH 05/10] ALSA: aloop: Use callback functions for timer specific implementations twischer
2019-03-26  7:49   ` twischer
2019-03-26  7:49   ` [PATCH 06/10] ALSA: aloop: Rename all jiffies timer specific functions twischer
2019-03-26  7:49     ` twischer
2019-03-26  7:49   ` [PATCH 07/10] ALSA: aloop: Move CABLE_VALID_BOTH to the top of file twischer
2019-03-26  7:49     ` twischer
2019-03-26  7:49   ` [PATCH 08/10] ALSA: aloop: Support selection of snd_timer instead of jiffies twischer
2019-03-26  7:49     ` twischer
2019-03-26  7:49   ` [PATCH 09/10] ALSA: pcm: Add snd_pcm_ops for snd_pcm_link() twischer
2019-03-26  7:49     ` twischer
2019-03-26  8:35     ` Takashi Iwai
2019-03-26  8:35       ` Takashi Iwai
2019-03-26 11:25       ` Timo Wischer
2019-03-26 14:23         ` Takashi Iwai
2019-03-26 14:23           ` Takashi Iwai
2019-03-26 15:16           ` Timo Wischer
2019-03-26 15:16             ` Timo Wischer
2019-03-26 16:00             ` Takashi Iwai
2019-03-26 16:00               ` Takashi Iwai
2019-03-27  8:34               ` Timo Wischer
2019-03-27  8:34                 ` Timo Wischer
2019-03-27  9:11                 ` Takashi Iwai
2019-03-27  9:11                   ` Takashi Iwai
2019-03-27  9:26                   ` Timo Wischer
2019-03-27  9:26                     ` Timo Wischer
2019-03-27  9:38                     ` Takashi Iwai
2019-03-27  9:38                       ` Takashi Iwai
2019-04-04 10:18                       ` Timo Wischer
2019-04-04 10:18                         ` Timo Wischer
2019-04-05 15:21                         ` Takashi Iwai
2019-04-05 15:21                           ` Takashi Iwai
2019-03-26  7:52 ` [PATCH 10/10] ALSA: aloop: Use timer of linked card if chosen twischer
2019-03-26  7:52   ` twischer

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.