summaryrefslogtreecommitdiffstats
path: root/kernel/sound/core/pcm_native.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/sound/core/pcm_native.c')
-rw-r--r--kernel/sound/core/pcm_native.c58
1 files changed, 34 insertions, 24 deletions
diff --git a/kernel/sound/core/pcm_native.c b/kernel/sound/core/pcm_native.c
index ec8486c45..34e501868 100644
--- a/kernel/sound/core/pcm_native.c
+++ b/kernel/sound/core/pcm_native.c
@@ -74,6 +74,18 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
static DEFINE_RWLOCK(snd_pcm_link_rwlock);
static DECLARE_RWSEM(snd_pcm_link_rwsem);
+/* Writer in rwsem may block readers even during its waiting in queue,
+ * and this may lead to a deadlock when the code path takes read sem
+ * twice (e.g. one in snd_pcm_action_nonatomic() and another in
+ * snd_pcm_stream_lock()). As a (suboptimal) workaround, let writer to
+ * spin until it gets the lock.
+ */
+static inline void down_write_nonblock(struct rw_semaphore *lock)
+{
+ while (!down_write_trylock(lock))
+ cond_resched();
+}
+
/**
* snd_pcm_stream_lock - Lock the PCM stream
* @substream: PCM substream
@@ -486,6 +498,16 @@ static void snd_pcm_set_state(struct snd_pcm_substream *substream, int state)
snd_pcm_stream_unlock_irq(substream);
}
+static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
+ int event)
+{
+#ifdef CONFIG_SND_PCM_TIMER
+ if (substream->timer)
+ snd_timer_notify(substream->timer, event,
+ &substream->runtime->trigger_tstamp);
+#endif
+}
+
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
@@ -650,7 +672,8 @@ static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
}
snd_pcm_stream_unlock_irq(substream);
- if (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
+ if (params->tstamp_mode < 0 ||
+ params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
return -EINVAL;
if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12) &&
params->tstamp_type > SNDRV_PCM_TSTAMP_TYPE_LAST)
@@ -1042,9 +1065,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state)
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, ULONG_MAX);
- if (substream->timer)
- snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART,
- &runtime->trigger_tstamp);
+ snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
}
static struct action_ops snd_pcm_action_start = {
@@ -1092,9 +1113,7 @@ static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state)
if (runtime->status->state != state) {
snd_pcm_trigger_tstamp(substream);
runtime->status->state = state;
- if (substream->timer)
- snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP,
- &runtime->trigger_tstamp);
+ snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
}
wake_up(&runtime->sleep);
wake_up(&runtime->tsleep);
@@ -1208,18 +1227,12 @@ static void snd_pcm_post_pause(struct snd_pcm_substream *substream, int push)
snd_pcm_trigger_tstamp(substream);
if (push) {
runtime->status->state = SNDRV_PCM_STATE_PAUSED;
- if (substream->timer)
- snd_timer_notify(substream->timer,
- SNDRV_TIMER_EVENT_MPAUSE,
- &runtime->trigger_tstamp);
+ snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MPAUSE);
wake_up(&runtime->sleep);
wake_up(&runtime->tsleep);
} else {
runtime->status->state = SNDRV_PCM_STATE_RUNNING;
- if (substream->timer)
- snd_timer_notify(substream->timer,
- SNDRV_TIMER_EVENT_MCONTINUE,
- &runtime->trigger_tstamp);
+ snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MCONTINUE);
}
}
@@ -1267,9 +1280,7 @@ static void snd_pcm_post_suspend(struct snd_pcm_substream *substream, int state)
snd_pcm_trigger_tstamp(substream);
runtime->status->suspended_state = runtime->status->state;
runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
- if (substream->timer)
- snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSUSPEND,
- &runtime->trigger_tstamp);
+ snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSUSPEND);
wake_up(&runtime->sleep);
wake_up(&runtime->tsleep);
}
@@ -1373,9 +1384,7 @@ static void snd_pcm_post_resume(struct snd_pcm_substream *substream, int state)
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_trigger_tstamp(substream);
runtime->status->state = runtime->status->suspended_state;
- if (substream->timer)
- snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MRESUME,
- &runtime->trigger_tstamp);
+ snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
}
static struct action_ops snd_pcm_action_resume = {
@@ -1816,7 +1825,7 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
res = -ENOMEM;
goto _nolock;
}
- down_write(&snd_pcm_link_rwsem);
+ down_write_nonblock(&snd_pcm_link_rwsem);
write_lock_irq(&snd_pcm_link_rwlock);
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
substream->runtime->status->state != substream1->runtime->status->state ||
@@ -1863,7 +1872,7 @@ static int snd_pcm_unlink(struct snd_pcm_substream *substream)
struct snd_pcm_substream *s;
int res = 0;
- down_write(&snd_pcm_link_rwsem);
+ down_write_nonblock(&snd_pcm_link_rwsem);
write_lock_irq(&snd_pcm_link_rwlock);
if (!snd_pcm_stream_linked(substream)) {
res = -EALREADY;
@@ -2226,7 +2235,8 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream)
snd_pcm_drop(substream);
if (substream->hw_opened) {
- if (substream->ops->hw_free != NULL)
+ if (substream->ops->hw_free &&
+ substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
substream->ops->hw_free(substream);
substream->ops->close(substream);
substream->hw_opened = 0;