diff mbox series

[alsa-lib,1/3] pcm: direct: Improved suspend/resume support

Message ID 20220310091418.1150-1-tiwai@suse.de
State New
Headers show
Series [alsa-lib,1/3] pcm: direct: Improved suspend/resume support | expand

Commit Message

Takashi Iwai March 10, 2022, 9:14 a.m. UTC
The current resume handling in PCM direct plugins don't treat multiple
clients properly: once after the slave PCM gets resumed by one
client, the access from others at a later point is seen as already
running although the internal state isn't updated and becomes
inconsistent.  This may end up a negative size, which eventually hangs
up.

This patch is an attempt to improve the handling for resume.  Now the
suspended state is treated similarly like XRUN; namely, we keep the
slave PCM "recoveries" count that is modified at each time the slave
PCM XRUN happens, so that we can check the inconsistency against the
client's state.  As a differentiation to XRUN, we set the highest bit
of recoveries count when the slave stream hits SUSPENDED state.  This
bit is referred at comparing with clients, and the client's state is
updated to either XRUN or SUSPENDED depending on this bit.

Along with this change, the actual resume is done in
snd_pcm_direct_slave_recover(), and snd_pcm_direct_resume() rather
calls this internally.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 src/pcm/pcm_direct.c | 72 ++++++++++++++++++++++++++------------------
 src/pcm/pcm_dmix.c   |  6 ++--
 src/pcm/pcm_dshare.c |  6 ++--
 src/pcm/pcm_dsnoop.c |  6 ++--
 4 files changed, 52 insertions(+), 38 deletions(-)
diff mbox series

Patch

diff --git a/src/pcm/pcm_direct.c b/src/pcm/pcm_direct.c
index 193dc3e76d49..1ca3b76306b1 100644
--- a/src/pcm/pcm_direct.c
+++ b/src/pcm/pcm_direct.c
@@ -560,8 +560,11 @@  int snd_pcm_direct_timer_stop(snd_pcm_direct_t *dmix)
 	return 0;
 }
 
+#define RECOVERIES_FLAG_SUSPENDED	(1U << 31)
+#define RECOVERIES_MASK			((1U << 31) - 1)
+
 /*
- * Recover slave on XRUN.
+ * Recover slave on XRUN or SUSPENDED.
  * Even if direct plugins disable xrun detection, there might be an xrun
  * raised directly by some drivers.
  * The first client recovers slave pcm.
@@ -569,6 +572,8 @@  int snd_pcm_direct_timer_stop(snd_pcm_direct_t *dmix)
  */
 int snd_pcm_direct_slave_recover(snd_pcm_direct_t *direct)
 {
+	unsigned int recoveries;
+	int state;
 	int ret;
 	int semerr;
 
@@ -579,7 +584,8 @@  int snd_pcm_direct_slave_recover(snd_pcm_direct_t *direct)
 		return semerr;
 	}
 
-	if (snd_pcm_state(direct->spcm) != SND_PCM_STATE_XRUN) {
+	state = snd_pcm_state(direct->spcm);
+	if (state != SND_PCM_STATE_XRUN && state != SND_PCM_STATE_SUSPENDED) {
 		/* ignore... someone else already did recovery */
 		semerr = snd_pcm_direct_semaphore_up(direct,
 						     DIRECT_IPC_SEM_CLIENT);
@@ -590,6 +596,24 @@  int snd_pcm_direct_slave_recover(snd_pcm_direct_t *direct)
 		return 0;
 	}
 
+	recoveries = direct->shmptr->s.recoveries;
+	recoveries = (recoveries + 1) & RECOVERIES_MASK;
+	if (state == SND_PCM_STATE_SUSPENDED)
+		recoveries |= RECOVERIES_FLAG_SUSPENDED;
+	direct->shmptr->s.recoveries = recoveries;
+
+	/* some buggy drivers require the device resumed before prepared;
+	 * when a device has RESUME flag and is in SUSPENDED state, resume
+	 * here but immediately drop to bring it to a sane active state.
+	 */
+	if (state == SND_PCM_STATE_SUSPENDED &&
+	    (direct->spcm->info & SND_PCM_INFO_RESUME)) {
+		snd_pcm_resume(direct->spcm);
+		snd_pcm_drop(direct->spcm);
+		snd_pcm_direct_timer_stop(direct);
+		snd_pcm_direct_clear_timer_queue(direct);
+	}
+
 	ret = snd_pcm_prepare(direct->spcm);
 	if (ret < 0) {
 		SNDERR("recover: unable to prepare slave");
@@ -621,7 +645,6 @@  int snd_pcm_direct_slave_recover(snd_pcm_direct_t *direct)
 		}
 		return ret;
 	}
-	direct->shmptr->s.recoveries++;
 	semerr = snd_pcm_direct_semaphore_up(direct,
 						 DIRECT_IPC_SEM_CLIENT);
 	if (semerr < 0) {
@@ -632,11 +655,15 @@  int snd_pcm_direct_slave_recover(snd_pcm_direct_t *direct)
 }
 
 /*
- * enter xrun state, if slave xrun occurred
- * @return: 0 for no xrun or a negative error code for xrun
+ * enter xrun or suspended state, if slave xrun occurred or suspended
+ * @return: 0 for no xrun/suspend or a negative error code for xrun/suspend
  */
 int snd_pcm_direct_client_chk_xrun(snd_pcm_direct_t *direct, snd_pcm_t *pcm)
 {
+	if (direct->state == SND_PCM_STATE_XRUN)
+		return -EPIPE;
+	else if (direct->state == SND_PCM_STATE_SUSPENDED)
+		return -ESTRPIPE;
 	if (direct->shmptr->s.recoveries != direct->recoveries) {
 		/* no matter how many xruns we missed -
 		 * so don't increment but just update to actual counter
@@ -649,10 +676,14 @@  int snd_pcm_direct_client_chk_xrun(snd_pcm_direct_t *direct, snd_pcm_t *pcm)
 		 * if slave already entered xrun again the event is lost.
 		 * snd_pcm_direct_clear_timer_queue(direct);
 		 */
-		direct->state = SND_PCM_STATE_XRUN;
+		if (direct->recoveries & RECOVERIES_FLAG_SUSPENDED) {
+			direct->state = SND_PCM_STATE_SUSPENDED;
+			return -ESTRPIPE;
+		} else {
+			direct->state = SND_PCM_STATE_XRUN;
+			return -EPIPE;
+		}
 	}
-	if (direct->state == SND_PCM_STATE_XRUN)
-		return -EPIPE;
 	return 0;
 }
 
@@ -721,13 +752,13 @@  timer_changed:
 	}
 	switch (snd_pcm_state(dmix->spcm)) {
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		/* recover slave and update client state to xrun
 		 * before returning POLLERR
 		 */
 		snd_pcm_direct_slave_recover(dmix);
 		snd_pcm_direct_client_chk_xrun(dmix, pcm);
 		/* fallthrough */
-	case SND_PCM_STATE_SUSPENDED:
 	case SND_PCM_STATE_SETUP:
 		events |= POLLERR;
 		break;
@@ -1074,27 +1105,10 @@  int snd_pcm_direct_prepare(snd_pcm_t *pcm)
 int snd_pcm_direct_resume(snd_pcm_t *pcm)
 {
 	snd_pcm_direct_t *dmix = pcm->private_data;
-	snd_pcm_t *spcm = dmix->spcm;
+	int err;
 
-	snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
-	/* some buggy drivers require the device resumed before prepared;
-	 * when a device has RESUME flag and is in SUSPENDED state, resume
-	 * here but immediately drop to bring it to a sane active state.
-	 */
-	if ((spcm->info & SND_PCM_INFO_RESUME) &&
-	    snd_pcm_state(spcm) == SND_PCM_STATE_SUSPENDED) {
-		snd_pcm_resume(spcm);
-		snd_pcm_drop(spcm);
-		snd_pcm_direct_timer_stop(dmix);
-		snd_pcm_direct_clear_timer_queue(dmix);
-		snd_pcm_areas_silence(snd_pcm_mmap_areas(spcm), 0,
-				      spcm->channels, spcm->buffer_size,
-				      spcm->format);
-		snd_pcm_prepare(spcm);
-		snd_pcm_start(spcm);
-	}
-	snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
-	return -ENOSYS;
+	err = snd_pcm_direct_slave_recover(dmix);
+	return err < 0 ? err : -ENOSYS;
 }
 
 #define COPY_SLAVE(field) (dmix->shmptr->s.field = spcm->field)
diff --git a/src/pcm/pcm_dmix.c b/src/pcm/pcm_dmix.c
index dfff18b992c5..e3bf49269f0c 100644
--- a/src/pcm/pcm_dmix.c
+++ b/src/pcm/pcm_dmix.c
@@ -431,6 +431,7 @@  static int snd_pcm_dmix_sync_ptr(snd_pcm_t *pcm)
 		dmix->state = SND_PCM_STATE_DISCONNECTED;
 		return -ENODEV;
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dmix)) < 0)
 			return err;
 		break;
@@ -457,11 +458,11 @@  static snd_pcm_state_t snd_pcm_dmix_state(snd_pcm_t *pcm)
 	snd_pcm_state_t state;
 	state = snd_pcm_state(dmix->spcm);
 	switch (state) {
-	case SND_PCM_STATE_SUSPENDED:
 	case SND_PCM_STATE_DISCONNECTED:
 		dmix->state = state;
 		return state;
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dmix)) < 0)
 			return err;
 		break;
@@ -833,11 +834,10 @@  static snd_pcm_sframes_t snd_pcm_dmix_mmap_commit(snd_pcm_t *pcm,
 
 	switch (snd_pcm_state(dmix->spcm)) {
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dmix)) < 0)
 			return err;
 		break;
-	case SND_PCM_STATE_SUSPENDED:
-		return -ESTRPIPE;
 	default:
 		break;
 	}
diff --git a/src/pcm/pcm_dshare.c b/src/pcm/pcm_dshare.c
index d63c86bd4044..5a52ae9173be 100644
--- a/src/pcm/pcm_dshare.c
+++ b/src/pcm/pcm_dshare.c
@@ -206,6 +206,7 @@  static int snd_pcm_dshare_sync_ptr(snd_pcm_t *pcm)
 		dshare->state = SNDRV_PCM_STATE_DISCONNECTED;
 		return -ENODEV;
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dshare)) < 0)
 			return err;
 		break;
@@ -260,11 +261,11 @@  static snd_pcm_state_t snd_pcm_dshare_state(snd_pcm_t *pcm)
 	snd_pcm_state_t state;
 	state = snd_pcm_state(dshare->spcm);
 	switch (state) {
-	case SND_PCM_STATE_SUSPENDED:
 	case SND_PCM_STATE_DISCONNECTED:
 		dshare->state = state;
 		return state;
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dshare)) < 0)
 			return err;
 		break;
@@ -532,11 +533,10 @@  static snd_pcm_sframes_t snd_pcm_dshare_mmap_commit(snd_pcm_t *pcm,
 
 	switch (snd_pcm_state(dshare->spcm)) {
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dshare)) < 0)
 			return err;
 		break;
-	case SND_PCM_STATE_SUSPENDED:
-		return -ESTRPIPE;
 	default:
 		break;
 	}
diff --git a/src/pcm/pcm_dsnoop.c b/src/pcm/pcm_dsnoop.c
index 77ffbada931f..1653f6fba86c 100644
--- a/src/pcm/pcm_dsnoop.c
+++ b/src/pcm/pcm_dsnoop.c
@@ -139,6 +139,7 @@  static int snd_pcm_dsnoop_sync_ptr(snd_pcm_t *pcm)
 		dsnoop->state = SNDRV_PCM_STATE_DISCONNECTED;
 		return -ENODEV;
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dsnoop)) < 0)
 			return err;
 		break;
@@ -211,11 +212,11 @@  static snd_pcm_state_t snd_pcm_dsnoop_state(snd_pcm_t *pcm)
 	snd_pcm_state_t state;
 	state = snd_pcm_state(dsnoop->spcm);
 	switch (state) {
-	case SND_PCM_STATE_SUSPENDED:
 	case SND_PCM_STATE_DISCONNECTED:
 		dsnoop->state = state;
 		return state;
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dsnoop)) < 0)
 			return err;
 		break;
@@ -423,11 +424,10 @@  static snd_pcm_sframes_t snd_pcm_dsnoop_mmap_commit(snd_pcm_t *pcm,
 
 	switch (snd_pcm_state(dsnoop->spcm)) {
 	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
 		if ((err = snd_pcm_direct_slave_recover(dsnoop)) < 0)
 			return err;
 		break;
-	case SND_PCM_STATE_SUSPENDED:
-		return -ESTRPIPE;
 	default:
 		break;
 	}