Commit 9e376b14 authored by Mark Brown's avatar Mark Brown

ASoC : soc-pcm: fix trigger race conditions with shared BE

Merge series from Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>:

We've been adding a 'deep buffer' PCM device to several SOF topologies
in order to reduce power consumption. The typical use-case would be
music playback over a headset: this additional PCM device provides
more buffering and longer latencies, leaving the rest of the system
sleep for longer periods. Notifications and 'regular' low-latency
audio playback would still use the 'normal' PCM device and be mixed
with the 'deep buffer' before rendering on the headphone endpoint. The
tentative direction would be to expose this alternate device to
PulseAudio/PipeWire/CRAS via the UCM SectionModifier definitions.

That seemed a straightforward topology change until our automated
validation stress tests started reporting issues on SoundWire
platforms, when e.g. two START triggers might be send and conversely
the STOP trigger is never sent. The SoundWire stream state management
flagged inconsistent states when the two 'normal' and 'deep buffer'
devices are used concurrently with rapid play/stop/pause monkey
testing.

Looking at the soc-pcm.c code, it seems that the BE state
management needs a lot of love.

a) there is no consistent protection for the BE state. In some parts
of the code, the state updates are protected by a spinlock but in the
trigger they are not. When we open/play/close the two PCM devices in
stress tests, we end-up testing a state that is being modified. That
can't be good.

b) there is a conceptual deadlock: on stop we check the FE states to
see if a shared BE can be stopped, but since we trigger the BE first
the FE states have not been modified yet, so the TRIGGER_STOP is never
sent.

This patchset suggests the removal of the dedicated 'dpcm_lock' and
follows the design suggested by Takashi Iwai.  By default the
protection relies on the 'pcm_mutex', except for the FE and BE
triggers where the mutex cannot be used.  In this case, the FE PCM
lock is used instead. In the cases where a BE is added/removed, the
pcm_mutex and FE PCM lock are both taken.  In addition, the BE PCM
lock is used to serialize access to a shared BE.

With these patches I am able to run our entire validation suite
without any issues with this new 'deep buffer' topology, and no
regressions on existing solutions [1]. The tests were reproduced by
Bard Liao for SoundWire devices.

One might ask 'how come we didn't see this earlier'? The answer is
probably that the .trigger callbacks in most implementations seems to
perform DAPM operations, and sending the triggers multiple times is
not an issue. In the case of SoundWire, we do use the .trigger
callback to reconfigure the bus using the 'bank switch' mechanism. It
could be acceptable to tolerate a trigger multiple times, but the
deadlock on stop cannot be fixed at the SoundWire level alone.

Opens:

1) The issues reported by Nvidia on the RFCv3 may or may not be
present. We'd need test results to make sure the locking update does
not introduce a regression on Tegra.

2) There are other reports of kernel oopses [2] that seem related to
the lack of protection. I'd be good to confirm if this patchset solve
these problems as well.

[1] https://github.com/thesofproject/linux/pull/3146
[2] https://lore.kernel.org/alsa-devel/002f01d7b4f5$c030f4a0$4092dde0$@samsung.com/

changes since RFCv3:
Used two patches from Takashi. We now use the pcm_mutex, the FE stream
lock when adding and deleting a BE, and the BE stream lock to handle
concurrency between streams using the same BE.
Added a patch to use GFP_ATOMIC for the DPCM structure.
Fixed PAUSE_RELEASE transition (GitHub comment from Kai Vehmanen)

changes since RFCv2:
Removal of dpcm_lock to use FE PCM locks (credits to Takashi Iwai for
the suggestion). The FE PCM lock is now used before each use of
for_each_dpcm_be() - with the exception of the trigger where the lock
is already taken. This change is also applied in drivers which make
use of this loop (compress, SH, FSL).
Addition of BE PCM lock to deal with mutual exclusion between triggers
for the same BE.
Alignment of the BE atomicity on the FE on connections, this is
required to avoid sleeping in atomic context.
Additional cleanups (indentation, static functions)

changes since RFC v1:
Removed unused function
Removed exported symbols only used in soc-pcm.c, used static instead
Use a mutex instead of a spinlock
Protect all for_each_dpcm_be() loops
Fix bugs introduced in the refcount

Pierre-Louis Bossart (4):
  ASoC: soc-pcm: use GFP_ATOMIC for dpcm structure
  ASoC: soc-pcm: align BE 'atomicity' with that of the FE
  ASoC: soc-pcm: test refcount before triggering
  ASoC: soc-pcm: fix BE handling of PAUSE_RELEASE

Takashi Iwai (2):
  ASoC: soc-pcm: Fix and cleanup DPCM locking
  ASoC: soc-pcm: serialize BE triggers

 include/sound/soc-dpcm.h |   2 +
 include/sound/soc.h      |   2 -
 sound/soc/soc-core.c     |   1 -
 sound/soc/soc-pcm.c      | 351 +++++++++++++++++++++++++++------------
 4 files changed, 246 insertions(+), 110 deletions(-)

--
2.25.1
parents 59716aa3 3aa1e96a
...@@ -101,6 +101,8 @@ struct snd_soc_dpcm_runtime { ...@@ -101,6 +101,8 @@ struct snd_soc_dpcm_runtime {
enum snd_soc_dpcm_state state; enum snd_soc_dpcm_state state;
int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */ int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
int be_start; /* refcount protected by BE stream pcm lock */
}; };
#define for_each_dpcm_fe(be, stream, _dpcm) \ #define for_each_dpcm_fe(be, stream, _dpcm) \
......
...@@ -893,8 +893,6 @@ struct snd_soc_card { ...@@ -893,8 +893,6 @@ struct snd_soc_card {
struct mutex pcm_mutex; struct mutex pcm_mutex;
enum snd_soc_pcm_subclass pcm_subclass; enum snd_soc_pcm_subclass pcm_subclass;
spinlock_t dpcm_lock;
int (*probe)(struct snd_soc_card *card); int (*probe)(struct snd_soc_card *card);
int (*late_probe)(struct snd_soc_card *card); int (*late_probe)(struct snd_soc_card *card);
int (*remove)(struct snd_soc_card *card); int (*remove)(struct snd_soc_card *card);
......
...@@ -2315,7 +2315,6 @@ int snd_soc_register_card(struct snd_soc_card *card) ...@@ -2315,7 +2315,6 @@ int snd_soc_register_card(struct snd_soc_card *card)
mutex_init(&card->mutex); mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex); mutex_init(&card->dapm_mutex);
mutex_init(&card->pcm_mutex); mutex_init(&card->pcm_mutex);
spin_lock_init(&card->dpcm_lock);
return snd_soc_bind_card(card); return snd_soc_bind_card(card);
} }
......
...@@ -27,6 +27,37 @@ ...@@ -27,6 +27,37 @@
#include <sound/soc-link.h> #include <sound/soc-link.h>
#include <sound/initval.h> #include <sound/initval.h>
static inline void snd_soc_dpcm_mutex_lock(struct snd_soc_pcm_runtime *rtd)
{
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
}
static inline void snd_soc_dpcm_mutex_unlock(struct snd_soc_pcm_runtime *rtd)
{
mutex_unlock(&rtd->card->pcm_mutex);
}
#define snd_soc_dpcm_mutex_assert_held(rtd) \
lockdep_assert_held(&(rtd)->card->pcm_mutex)
static inline void snd_soc_dpcm_stream_lock_irq(struct snd_soc_pcm_runtime *rtd,
int stream)
{
snd_pcm_stream_lock_irq(snd_soc_dpcm_get_substream(rtd, stream));
}
#define snd_soc_dpcm_stream_lock_irqsave(rtd, stream, flags) \
snd_pcm_stream_lock_irqsave(snd_soc_dpcm_get_substream(rtd, stream), flags)
static inline void snd_soc_dpcm_stream_unlock_irq(struct snd_soc_pcm_runtime *rtd,
int stream)
{
snd_pcm_stream_unlock_irq(snd_soc_dpcm_get_substream(rtd, stream));
}
#define snd_soc_dpcm_stream_unlock_irqrestore(rtd, stream, flags) \
snd_pcm_stream_unlock_irqrestore(snd_soc_dpcm_get_substream(rtd, stream), flags)
#define DPCM_MAX_BE_USERS 8 #define DPCM_MAX_BE_USERS 8
static inline const char *soc_cpu_dai_name(struct snd_soc_pcm_runtime *rtd) static inline const char *soc_cpu_dai_name(struct snd_soc_pcm_runtime *rtd)
...@@ -73,7 +104,6 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, ...@@ -73,7 +104,6 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params;
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
ssize_t offset = 0; ssize_t offset = 0;
unsigned long flags;
/* FE state */ /* FE state */
offset += scnprintf(buf + offset, size - offset, offset += scnprintf(buf + offset, size - offset,
...@@ -101,7 +131,6 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, ...@@ -101,7 +131,6 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
goto out; goto out;
} }
spin_lock_irqsave(&fe->card->dpcm_lock, flags);
for_each_dpcm_be(fe, stream, dpcm) { for_each_dpcm_be(fe, stream, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_soc_pcm_runtime *be = dpcm->be;
params = &dpcm->hw_params; params = &dpcm->hw_params;
...@@ -122,7 +151,6 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, ...@@ -122,7 +151,6 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
params_channels(params), params_channels(params),
params_rate(params)); params_rate(params));
} }
spin_unlock_irqrestore(&fe->card->dpcm_lock, flags);
out: out:
return offset; return offset;
} }
...@@ -145,11 +173,13 @@ static ssize_t dpcm_state_read_file(struct file *file, char __user *user_buf, ...@@ -145,11 +173,13 @@ static ssize_t dpcm_state_read_file(struct file *file, char __user *user_buf,
if (!buf) if (!buf)
return -ENOMEM; return -ENOMEM;
snd_soc_dpcm_mutex_lock(fe);
for_each_pcm_streams(stream) for_each_pcm_streams(stream)
if (snd_soc_dai_stream_valid(asoc_rtd_to_cpu(fe, 0), stream)) if (snd_soc_dai_stream_valid(asoc_rtd_to_cpu(fe, 0), stream))
offset += dpcm_show_state(fe, stream, offset += dpcm_show_state(fe, stream,
buf + offset, buf + offset,
out_count - offset); out_count - offset);
snd_soc_dpcm_mutex_unlock(fe);
ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset); ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset);
...@@ -221,14 +251,14 @@ static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe, ...@@ -221,14 +251,14 @@ static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe,
struct snd_pcm_substream *substream = struct snd_pcm_substream *substream =
snd_soc_dpcm_get_substream(fe, stream); snd_soc_dpcm_get_substream(fe, stream);
snd_pcm_stream_lock_irq(substream); snd_soc_dpcm_stream_lock_irq(fe, stream);
if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) { if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) {
dpcm_fe_dai_do_trigger(substream, dpcm_fe_dai_do_trigger(substream,
fe->dpcm[stream].trigger_pending - 1); fe->dpcm[stream].trigger_pending - 1);
fe->dpcm[stream].trigger_pending = 0; fe->dpcm[stream].trigger_pending = 0;
} }
fe->dpcm[stream].runtime_update = state; fe->dpcm[stream].runtime_update = state;
snd_pcm_stream_unlock_irq(substream); snd_soc_dpcm_stream_unlock_irq(fe, stream);
} }
static void dpcm_set_be_update_state(struct snd_soc_pcm_runtime *be, static void dpcm_set_be_update_state(struct snd_soc_pcm_runtime *be,
...@@ -256,7 +286,7 @@ void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, ...@@ -256,7 +286,7 @@ void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *dai; struct snd_soc_dai *dai;
int i; int i;
lockdep_assert_held(&rtd->card->pcm_mutex); snd_soc_dpcm_mutex_assert_held(rtd);
for_each_rtd_dais(rtd, i, dai) for_each_rtd_dais(rtd, i, dai)
snd_soc_dai_action(dai, stream, action); snd_soc_dai_action(dai, stream, action);
...@@ -309,6 +339,8 @@ int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, ...@@ -309,6 +339,8 @@ int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir,
{ {
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
snd_soc_dpcm_mutex_assert_held(fe);
for_each_dpcm_be(fe, dir, dpcm) { for_each_dpcm_be(fe, dir, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_soc_pcm_runtime *be = dpcm->be;
...@@ -646,14 +678,14 @@ static int soc_pcm_components_close(struct snd_pcm_substream *substream, ...@@ -646,14 +678,14 @@ static int soc_pcm_components_close(struct snd_pcm_substream *substream,
return ret; return ret;
} }
static int soc_pcm_clean(struct snd_pcm_substream *substream, int rollback) static int soc_pcm_clean(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream, int rollback)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_component *component; struct snd_soc_component *component;
struct snd_soc_dai *dai; struct snd_soc_dai *dai;
int i; int i;
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); snd_soc_dpcm_mutex_assert_held(rtd);
if (!rollback) if (!rollback)
snd_soc_runtime_deactivate(rtd, substream->stream); snd_soc_runtime_deactivate(rtd, substream->stream);
...@@ -665,9 +697,6 @@ static int soc_pcm_clean(struct snd_pcm_substream *substream, int rollback) ...@@ -665,9 +697,6 @@ static int soc_pcm_clean(struct snd_pcm_substream *substream, int rollback)
soc_pcm_components_close(substream, rollback); soc_pcm_components_close(substream, rollback);
mutex_unlock(&rtd->card->pcm_mutex);
snd_soc_pcm_component_pm_runtime_put(rtd, substream, rollback); snd_soc_pcm_component_pm_runtime_put(rtd, substream, rollback);
for_each_rtd_components(rtd, i, component) for_each_rtd_components(rtd, i, component)
...@@ -682,9 +711,21 @@ static int soc_pcm_clean(struct snd_pcm_substream *substream, int rollback) ...@@ -682,9 +711,21 @@ static int soc_pcm_clean(struct snd_pcm_substream *substream, int rollback)
* freed here. The cpu DAI, codec DAI, machine and components are also * freed here. The cpu DAI, codec DAI, machine and components are also
* shutdown. * shutdown.
*/ */
static int __soc_pcm_close(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream)
{
return soc_pcm_clean(rtd, substream, 0);
}
/* PCM close ops for non-DPCM streams */
static int soc_pcm_close(struct snd_pcm_substream *substream) static int soc_pcm_close(struct snd_pcm_substream *substream)
{ {
return soc_pcm_clean(substream, 0); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
snd_soc_dpcm_mutex_lock(rtd);
soc_pcm_clean(rtd, substream, 0);
snd_soc_dpcm_mutex_unlock(rtd);
return 0;
} }
static int soc_hw_sanity_check(struct snd_pcm_substream *substream) static int soc_hw_sanity_check(struct snd_pcm_substream *substream)
...@@ -730,21 +771,21 @@ static int soc_hw_sanity_check(struct snd_pcm_substream *substream) ...@@ -730,21 +771,21 @@ static int soc_hw_sanity_check(struct snd_pcm_substream *substream)
* then initialized and any private data can be allocated. This also calls * then initialized and any private data can be allocated. This also calls
* startup for the cpu DAI, component, machine and codec DAI. * startup for the cpu DAI, component, machine and codec DAI.
*/ */
static int soc_pcm_open(struct snd_pcm_substream *substream) static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_component *component; struct snd_soc_component *component;
struct snd_soc_dai *dai; struct snd_soc_dai *dai;
int i, ret = 0; int i, ret = 0;
snd_soc_dpcm_mutex_assert_held(rtd);
for_each_rtd_components(rtd, i, component) for_each_rtd_components(rtd, i, component)
pinctrl_pm_select_default_state(component->dev); pinctrl_pm_select_default_state(component->dev);
ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream); ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream);
if (ret < 0) if (ret < 0)
goto pm_err; goto err;
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
ret = soc_pcm_components_open(substream); ret = soc_pcm_components_open(substream);
if (ret < 0) if (ret < 0)
...@@ -791,16 +832,26 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) ...@@ -791,16 +832,26 @@ static int soc_pcm_open(struct snd_pcm_substream *substream)
snd_soc_runtime_activate(rtd, substream->stream); snd_soc_runtime_activate(rtd, substream->stream);
ret = 0; ret = 0;
err: err:
mutex_unlock(&rtd->card->pcm_mutex);
pm_err:
if (ret < 0) { if (ret < 0) {
soc_pcm_clean(substream, 1); soc_pcm_clean(rtd, substream, 1);
dev_err(rtd->dev, "%s() failed (%d)", __func__, ret); dev_err(rtd->dev, "%s() failed (%d)", __func__, ret);
} }
return ret; return ret;
} }
/* PCM open ops for non-DPCM streams */
static int soc_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_open(rtd, substream);
snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
static void codec2codec_close_delayed_work(struct snd_soc_pcm_runtime *rtd) static void codec2codec_close_delayed_work(struct snd_soc_pcm_runtime *rtd)
{ {
/* /*
...@@ -816,13 +867,13 @@ static void codec2codec_close_delayed_work(struct snd_soc_pcm_runtime *rtd) ...@@ -816,13 +867,13 @@ static void codec2codec_close_delayed_work(struct snd_soc_pcm_runtime *rtd)
* rate, etc. This function is non atomic and can be called multiple times, * rate, etc. This function is non atomic and can be called multiple times,
* it can refer to the runtime info. * it can refer to the runtime info.
*/ */
static int soc_pcm_prepare(struct snd_pcm_substream *substream) static int __soc_pcm_prepare(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_dai *dai; struct snd_soc_dai *dai;
int i, ret = 0; int i, ret = 0;
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); snd_soc_dpcm_mutex_assert_held(rtd);
ret = snd_soc_link_prepare(substream); ret = snd_soc_link_prepare(substream);
if (ret < 0) if (ret < 0)
...@@ -850,14 +901,24 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) ...@@ -850,14 +901,24 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream)
snd_soc_dai_digital_mute(dai, 0, substream->stream); snd_soc_dai_digital_mute(dai, 0, substream->stream);
out: out:
mutex_unlock(&rtd->card->pcm_mutex);
if (ret < 0) if (ret < 0)
dev_err(rtd->dev, "ASoC: %s() failed (%d)\n", __func__, ret); dev_err(rtd->dev, "ASoC: %s() failed (%d)\n", __func__, ret);
return ret; return ret;
} }
/* PCM prepare ops for non-DPCM streams */
static int soc_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_prepare(rtd, substream);
snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params,
unsigned int mask) unsigned int mask)
{ {
...@@ -869,13 +930,13 @@ static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, ...@@ -869,13 +930,13 @@ static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params,
interval->max = channels; interval->max = channels;
} }
static int soc_pcm_hw_clean(struct snd_pcm_substream *substream, int rollback) static int soc_pcm_hw_clean(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream, int rollback)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_dai *dai; struct snd_soc_dai *dai;
int i; int i;
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); snd_soc_dpcm_mutex_assert_held(rtd);
/* clear the corresponding DAIs parameters when going to be inactive */ /* clear the corresponding DAIs parameters when going to be inactive */
for_each_rtd_dais(rtd, i, dai) { for_each_rtd_dais(rtd, i, dai) {
...@@ -900,16 +961,28 @@ static int soc_pcm_hw_clean(struct snd_pcm_substream *substream, int rollback) ...@@ -900,16 +961,28 @@ static int soc_pcm_hw_clean(struct snd_pcm_substream *substream, int rollback)
if (snd_soc_dai_stream_valid(dai, substream->stream)) if (snd_soc_dai_stream_valid(dai, substream->stream))
snd_soc_dai_hw_free(dai, substream, rollback); snd_soc_dai_hw_free(dai, substream, rollback);
mutex_unlock(&rtd->card->pcm_mutex);
return 0; return 0;
} }
/* /*
* Frees resources allocated by hw_params, can be called multiple times * Frees resources allocated by hw_params, can be called multiple times
*/ */
static int __soc_pcm_hw_free(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream)
{
return soc_pcm_hw_clean(rtd, substream, 0);
}
/* hw_free PCM ops for non-DPCM streams */
static int soc_pcm_hw_free(struct snd_pcm_substream *substream) static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
{ {
return soc_pcm_hw_clean(substream, 0); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_hw_free(rtd, substream);
snd_soc_dpcm_mutex_unlock(rtd);
return ret;
} }
/* /*
...@@ -917,15 +990,15 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) ...@@ -917,15 +990,15 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
* function can also be called multiple times and can allocate buffers * function can also be called multiple times and can allocate buffers
* (using snd_pcm_lib_* ). It's non-atomic. * (using snd_pcm_lib_* ). It's non-atomic.
*/ */
static int soc_pcm_hw_params(struct snd_pcm_substream *substream, static int __soc_pcm_hw_params(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params) struct snd_pcm_hw_params *params)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai; struct snd_soc_dai *cpu_dai;
struct snd_soc_dai *codec_dai; struct snd_soc_dai *codec_dai;
int i, ret = 0; int i, ret = 0;
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); snd_soc_dpcm_mutex_assert_held(rtd);
ret = soc_pcm_params_symmetry(substream, params); ret = soc_pcm_params_symmetry(substream, params);
if (ret) if (ret)
...@@ -997,16 +1070,27 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -997,16 +1070,27 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
ret = snd_soc_pcm_component_hw_params(substream, params); ret = snd_soc_pcm_component_hw_params(substream, params);
out: out:
mutex_unlock(&rtd->card->pcm_mutex);
if (ret < 0) { if (ret < 0) {
soc_pcm_hw_clean(substream, 1); soc_pcm_hw_clean(rtd, substream, 1);
dev_err(rtd->dev, "ASoC: %s() failed (%d)\n", __func__, ret); dev_err(rtd->dev, "ASoC: %s() failed (%d)\n", __func__, ret);
} }
return ret; return ret;
} }
/* hw_params PCM ops for non-DPCM streams */
static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_hw_params(rtd, substream, params);
snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
...@@ -1104,8 +1188,11 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) ...@@ -1104,8 +1188,11 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
struct snd_soc_pcm_runtime *be, int stream) struct snd_soc_pcm_runtime *be, int stream)
{ {
struct snd_pcm_substream *fe_substream;
struct snd_pcm_substream *be_substream;
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
unsigned long flags;
snd_soc_dpcm_mutex_assert_held(fe);
/* only add new dpcms */ /* only add new dpcms */
for_each_dpcm_be(fe, stream, dpcm) { for_each_dpcm_be(fe, stream, dpcm) {
...@@ -1113,7 +1200,21 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, ...@@ -1113,7 +1200,21 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
return 0; return 0;
} }
dpcm = kzalloc(sizeof(struct snd_soc_dpcm), GFP_KERNEL); fe_substream = snd_soc_dpcm_get_substream(fe, stream);
be_substream = snd_soc_dpcm_get_substream(be, stream);
if (!fe_substream->pcm->nonatomic && be_substream->pcm->nonatomic) {
dev_err(be->dev, "%s: FE is atomic but BE is nonatomic, invalid configuration\n",
__func__);
return -EINVAL;
}
if (fe_substream->pcm->nonatomic && !be_substream->pcm->nonatomic) {
dev_warn(be->dev, "%s: FE is nonatomic but BE is not, forcing BE as nonatomic\n",
__func__);
be_substream->pcm->nonatomic = 1;
}
dpcm = kzalloc(sizeof(struct snd_soc_dpcm), GFP_ATOMIC);
if (!dpcm) if (!dpcm)
return -ENOMEM; return -ENOMEM;
...@@ -1121,10 +1222,10 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, ...@@ -1121,10 +1222,10 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
dpcm->fe = fe; dpcm->fe = fe;
be->dpcm[stream].runtime = fe->dpcm[stream].runtime; be->dpcm[stream].runtime = fe->dpcm[stream].runtime;
dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW; dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW;
spin_lock_irqsave(&fe->card->dpcm_lock, flags); snd_soc_dpcm_stream_lock_irq(fe, stream);
list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients); list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients);
list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients); list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients);
spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); snd_soc_dpcm_stream_unlock_irq(fe, stream);
dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n", dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n",
stream ? "capture" : "playback", fe->dai_link->name, stream ? "capture" : "playback", fe->dai_link->name,
...@@ -1167,8 +1268,10 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, ...@@ -1167,8 +1268,10 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe,
void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
{ {
struct snd_soc_dpcm *dpcm, *d; struct snd_soc_dpcm *dpcm, *d;
unsigned long flags;
snd_soc_dpcm_mutex_assert_held(fe);
snd_soc_dpcm_stream_lock_irq(fe, stream);
for_each_dpcm_be_safe(fe, stream, dpcm, d) { for_each_dpcm_be_safe(fe, stream, dpcm, d) {
dev_dbg(fe->dev, "ASoC: BE %s disconnect check for %s\n", dev_dbg(fe->dev, "ASoC: BE %s disconnect check for %s\n",
stream ? "capture" : "playback", stream ? "capture" : "playback",
...@@ -1186,12 +1289,11 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -1186,12 +1289,11 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
dpcm_remove_debugfs_state(dpcm); dpcm_remove_debugfs_state(dpcm);
spin_lock_irqsave(&fe->card->dpcm_lock, flags);
list_del(&dpcm->list_be); list_del(&dpcm->list_be);
list_del(&dpcm->list_fe); list_del(&dpcm->list_fe);
spin_unlock_irqrestore(&fe->card->dpcm_lock, flags);
kfree(dpcm); kfree(dpcm);
} }
snd_soc_dpcm_stream_unlock_irq(fe, stream);
} }
/* get BE for DAI widget and stream */ /* get BE for DAI widget and stream */
...@@ -1415,12 +1517,9 @@ int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, ...@@ -1415,12 +1517,9 @@ int dpcm_process_paths(struct snd_soc_pcm_runtime *fe,
void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream)
{ {
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
unsigned long flags;
spin_lock_irqsave(&fe->card->dpcm_lock, flags);
for_each_dpcm_be(fe, stream, dpcm) for_each_dpcm_be(fe, stream, dpcm)
dpcm_set_be_update_state(dpcm->be, stream, SND_SOC_DPCM_UPDATE_NO); dpcm_set_be_update_state(dpcm->be, stream, SND_SOC_DPCM_UPDATE_NO);
spin_unlock_irqrestore(&fe->card->dpcm_lock, flags);
} }
void dpcm_be_dai_stop(struct snd_soc_pcm_runtime *fe, int stream, void dpcm_be_dai_stop(struct snd_soc_pcm_runtime *fe, int stream,
...@@ -1456,12 +1555,12 @@ void dpcm_be_dai_stop(struct snd_soc_pcm_runtime *fe, int stream, ...@@ -1456,12 +1555,12 @@ void dpcm_be_dai_stop(struct snd_soc_pcm_runtime *fe, int stream,
continue; continue;
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) { if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) {
soc_pcm_hw_free(be_substream); __soc_pcm_hw_free(be, be_substream);
be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;
} }
} }
soc_pcm_close(be_substream); __soc_pcm_close(be, be_substream);
be_substream->runtime = NULL; be_substream->runtime = NULL;
be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE;
} }
...@@ -1509,7 +1608,7 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -1509,7 +1608,7 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream)
stream ? "capture" : "playback", be->dai_link->name); stream ? "capture" : "playback", be->dai_link->name);
be_substream->runtime = be->dpcm[stream].runtime; be_substream->runtime = be->dpcm[stream].runtime;
err = soc_pcm_open(be_substream); err = __soc_pcm_open(be, be_substream);
if (err < 0) { if (err < 0) {
be->dpcm[stream].users--; be->dpcm[stream].users--;
if (be->dpcm[stream].users < 0) if (be->dpcm[stream].users < 0)
...@@ -1520,7 +1619,7 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -1520,7 +1619,7 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream)
be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE;
goto unwind; goto unwind;
} }
be->dpcm[stream].be_start = 0;
be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN;
count++; count++;
} }
...@@ -1753,7 +1852,7 @@ static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) ...@@ -1753,7 +1852,7 @@ static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
dev_dbg(fe->dev, "ASoC: open FE %s\n", fe->dai_link->name); dev_dbg(fe->dev, "ASoC: open FE %s\n", fe->dai_link->name);
/* start the DAI frontend */ /* start the DAI frontend */
ret = soc_pcm_open(fe_substream); ret = __soc_pcm_open(fe, fe_substream);
if (ret < 0) if (ret < 0)
goto unwind; goto unwind;
...@@ -1784,6 +1883,8 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) ...@@ -1784,6 +1883,8 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream);
int stream = substream->stream; int stream = substream->stream;
snd_soc_dpcm_mutex_assert_held(fe);
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
/* shutdown the BEs */ /* shutdown the BEs */
...@@ -1792,7 +1893,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) ...@@ -1792,7 +1893,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
dev_dbg(fe->dev, "ASoC: close FE %s\n", fe->dai_link->name); dev_dbg(fe->dev, "ASoC: close FE %s\n", fe->dai_link->name);
/* now shutdown the frontend */ /* now shutdown the frontend */
soc_pcm_close(substream); __soc_pcm_close(fe, substream);
/* run the stream stop event */ /* run the stream stop event */
dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP);
...@@ -1837,7 +1938,7 @@ void dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -1837,7 +1938,7 @@ void dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream)
dev_dbg(be->dev, "ASoC: hw_free BE %s\n", dev_dbg(be->dev, "ASoC: hw_free BE %s\n",
be->dai_link->name); be->dai_link->name);
soc_pcm_hw_free(be_substream); __soc_pcm_hw_free(be, be_substream);
be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;
} }
...@@ -1848,13 +1949,13 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) ...@@ -1848,13 +1949,13 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream);
int stream = substream->stream; int stream = substream->stream;
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); snd_soc_dpcm_mutex_lock(fe);
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name); dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name);
/* call hw_free on the frontend */ /* call hw_free on the frontend */
soc_pcm_hw_free(substream); soc_pcm_hw_clean(fe, substream, 0);
/* only hw_params backends that are either sinks or sources /* only hw_params backends that are either sinks or sources
* to this frontend DAI */ * to this frontend DAI */
...@@ -1863,7 +1964,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) ...@@ -1863,7 +1964,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
mutex_unlock(&fe->card->mutex); snd_soc_dpcm_mutex_unlock(fe);
return 0; return 0;
} }
...@@ -1907,7 +2008,7 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -1907,7 +2008,7 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
dev_dbg(be->dev, "ASoC: hw_params BE %s\n", dev_dbg(be->dev, "ASoC: hw_params BE %s\n",
be->dai_link->name); be->dai_link->name);
ret = soc_pcm_hw_params(be_substream, &dpcm->hw_params); ret = __soc_pcm_hw_params(be, be_substream, &dpcm->hw_params);
if (ret < 0) if (ret < 0)
goto unwind; goto unwind;
...@@ -1937,7 +2038,7 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -1937,7 +2038,7 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
continue; continue;
soc_pcm_hw_free(be_substream); __soc_pcm_hw_free(be, be_substream);
} }
return ret; return ret;
...@@ -1949,7 +2050,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, ...@@ -1949,7 +2050,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream);
int ret, stream = substream->stream; int ret, stream = substream->stream;
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); snd_soc_dpcm_mutex_lock(fe);
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
memcpy(&fe->dpcm[stream].hw_params, params, memcpy(&fe->dpcm[stream].hw_params, params,
...@@ -1963,7 +2064,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, ...@@ -1963,7 +2064,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
params_channels(params), params_format(params)); params_channels(params), params_format(params));
/* call hw_params on the frontend */ /* call hw_params on the frontend */
ret = soc_pcm_hw_params(substream, params); ret = __soc_pcm_hw_params(fe, substream, params);
if (ret < 0) if (ret < 0)
dpcm_be_dai_hw_free(fe, stream); dpcm_be_dai_hw_free(fe, stream);
else else
...@@ -1971,7 +2072,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, ...@@ -1971,7 +2072,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
out: out:
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
mutex_unlock(&fe->card->mutex); snd_soc_dpcm_mutex_unlock(fe);
if (ret < 0) if (ret < 0)
dev_err(fe->dev, "ASoC: %s failed (%d)\n", __func__, ret); dev_err(fe->dev, "ASoC: %s failed (%d)\n", __func__, ret);
...@@ -1984,6 +2085,7 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, ...@@ -1984,6 +2085,7 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
{ {
struct snd_soc_pcm_runtime *be; struct snd_soc_pcm_runtime *be;
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
unsigned long flags;
int ret = 0; int ret = 0;
for_each_dpcm_be(fe, stream, dpcm) { for_each_dpcm_be(fe, stream, dpcm) {
...@@ -1992,89 +2094,128 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, ...@@ -1992,89 +2094,128 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
be = dpcm->be; be = dpcm->be;
be_substream = snd_soc_dpcm_get_substream(be, stream); be_substream = snd_soc_dpcm_get_substream(be, stream);
snd_soc_dpcm_stream_lock_irqsave(be, stream, flags);
/* is this op for this BE ? */ /* is this op for this BE ? */
if (!snd_soc_dpcm_be_can_update(fe, be, stream)) if (!snd_soc_dpcm_be_can_update(fe, be, stream))
continue; goto next;
dev_dbg(be->dev, "ASoC: trigger BE %s cmd %d\n", dev_dbg(be->dev, "ASoC: trigger BE %s cmd %d\n",
be->dai_link->name, cmd); be->dai_link->name, cmd);
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && if (!be->dpcm[stream].be_start &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED))
continue; goto next;
be->dpcm[stream].be_start++;
if (be->dpcm[stream].be_start != 1)
goto next;
ret = soc_pcm_trigger(be_substream, cmd); ret = soc_pcm_trigger(be_substream, cmd);
if (ret) if (ret) {
goto end; be->dpcm[stream].be_start--;
goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; be->dpcm[stream].state = SND_SOC_DPCM_STATE_START;
break; break;
case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_RESUME:
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND))
continue; goto next;
be->dpcm[stream].be_start++;
if (be->dpcm[stream].be_start != 1)
goto next;
ret = soc_pcm_trigger(be_substream, cmd); ret = soc_pcm_trigger(be_substream, cmd);
if (ret) if (ret) {
goto end; be->dpcm[stream].be_start--;
goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; be->dpcm[stream].state = SND_SOC_DPCM_STATE_START;
break; break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) if (!be->dpcm[stream].be_start &&
continue; (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED))
goto next;
be->dpcm[stream].be_start++;
if (be->dpcm[stream].be_start != 1)
goto next;
ret = soc_pcm_trigger(be_substream, cmd); ret = soc_pcm_trigger(be_substream, cmd);
if (ret) if (ret) {
goto end; be->dpcm[stream].be_start--;
goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; be->dpcm[stream].state = SND_SOC_DPCM_STATE_START;
break; break;
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_STOP:
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) && if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED))
continue; goto next;
if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START)
continue; be->dpcm[stream].be_start--;
if (be->dpcm[stream].be_start != 0)
goto next;
ret = soc_pcm_trigger(be_substream, cmd); ret = soc_pcm_trigger(be_substream, cmd);
if (ret) if (ret) {
goto end; if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START)
be->dpcm[stream].be_start++;
goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP;
break; break;
case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_SUSPEND:
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START)
continue; goto next;
if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) be->dpcm[stream].be_start--;
continue; if (be->dpcm[stream].be_start != 0)
goto next;
ret = soc_pcm_trigger(be_substream, cmd); ret = soc_pcm_trigger(be_substream, cmd);
if (ret) if (ret) {
goto end; be->dpcm[stream].be_start++;
goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND; be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND;
break; break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START)
continue; goto next;
if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) be->dpcm[stream].be_start--;
continue; if (be->dpcm[stream].be_start != 0)
goto next;
ret = soc_pcm_trigger(be_substream, cmd); ret = soc_pcm_trigger(be_substream, cmd);
if (ret) if (ret) {
goto end; be->dpcm[stream].be_start++;
goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED;
break; break;
} }
next:
snd_soc_dpcm_stream_unlock_irqrestore(be, stream, flags);
if (ret)
break;
} }
end:
if (ret < 0) if (ret < 0)
dev_err(fe->dev, "ASoC: %s() failed at %s (%d)\n", dev_err(fe->dev, "ASoC: %s() failed at %s (%d)\n",
__func__, be->dai_link->name, ret); __func__, be->dai_link->name, ret);
...@@ -2242,7 +2383,7 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -2242,7 +2383,7 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream)
dev_dbg(be->dev, "ASoC: prepare BE %s\n", dev_dbg(be->dev, "ASoC: prepare BE %s\n",
be->dai_link->name); be->dai_link->name);
ret = soc_pcm_prepare(be_substream); ret = __soc_pcm_prepare(be, be_substream);
if (ret < 0) if (ret < 0)
break; break;
...@@ -2260,7 +2401,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) ...@@ -2260,7 +2401,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream);
int stream = substream->stream, ret = 0; int stream = substream->stream, ret = 0;
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); snd_soc_dpcm_mutex_lock(fe);
dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name); dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name);
...@@ -2279,7 +2420,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) ...@@ -2279,7 +2420,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
goto out; goto out;
/* call prepare on the frontend */ /* call prepare on the frontend */
ret = soc_pcm_prepare(substream); ret = __soc_pcm_prepare(fe, substream);
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -2287,7 +2428,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) ...@@ -2287,7 +2428,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
out: out:
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
mutex_unlock(&fe->card->mutex); snd_soc_dpcm_mutex_unlock(fe);
if (ret < 0) if (ret < 0)
dev_err(fe->dev, "ASoC: %s() failed (%d)\n", __func__, ret); dev_err(fe->dev, "ASoC: %s() failed (%d)\n", __func__, ret);
...@@ -2338,7 +2479,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -2338,7 +2479,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream)
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream];
int ret = 0; int ret = 0;
unsigned long flags;
dev_dbg(fe->dev, "ASoC: runtime %s open on FE %s\n", dev_dbg(fe->dev, "ASoC: runtime %s open on FE %s\n",
stream ? "capture" : "playback", fe->dai_link->name); stream ? "capture" : "playback", fe->dai_link->name);
...@@ -2407,7 +2547,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -2407,7 +2547,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream)
dpcm_be_dai_shutdown(fe, stream); dpcm_be_dai_shutdown(fe, stream);
disconnect: disconnect:
/* disconnect any pending BEs */ /* disconnect any pending BEs */
spin_lock_irqsave(&fe->card->dpcm_lock, flags);
for_each_dpcm_be(fe, stream, dpcm) { for_each_dpcm_be(fe, stream, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_soc_pcm_runtime *be = dpcm->be;
...@@ -2419,7 +2558,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) ...@@ -2419,7 +2558,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream)
be->dpcm[stream].state == SND_SOC_DPCM_STATE_NEW) be->dpcm[stream].state == SND_SOC_DPCM_STATE_NEW)
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
} }
spin_unlock_irqrestore(&fe->card->dpcm_lock, flags);
if (ret < 0) if (ret < 0)
dev_err(fe->dev, "ASoC: %s() failed (%d)\n", __func__, ret); dev_err(fe->dev, "ASoC: %s() failed (%d)\n", __func__, ret);
...@@ -2494,7 +2632,7 @@ int snd_soc_dpcm_runtime_update(struct snd_soc_card *card) ...@@ -2494,7 +2632,7 @@ int snd_soc_dpcm_runtime_update(struct snd_soc_card *card)
struct snd_soc_pcm_runtime *fe; struct snd_soc_pcm_runtime *fe;
int ret = 0; int ret = 0;
mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); mutex_lock_nested(&card->pcm_mutex, card->pcm_subclass);
/* shutdown all old paths first */ /* shutdown all old paths first */
for_each_card_rtds(card, fe) { for_each_card_rtds(card, fe) {
ret = soc_dpcm_fe_runtime_update(fe, 0); ret = soc_dpcm_fe_runtime_update(fe, 0);
...@@ -2510,7 +2648,7 @@ int snd_soc_dpcm_runtime_update(struct snd_soc_card *card) ...@@ -2510,7 +2648,7 @@ int snd_soc_dpcm_runtime_update(struct snd_soc_card *card)
} }
out: out:
mutex_unlock(&card->mutex); mutex_unlock(&card->pcm_mutex);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(snd_soc_dpcm_runtime_update); EXPORT_SYMBOL_GPL(snd_soc_dpcm_runtime_update);
...@@ -2521,6 +2659,8 @@ static void dpcm_fe_dai_cleanup(struct snd_pcm_substream *fe_substream) ...@@ -2521,6 +2659,8 @@ static void dpcm_fe_dai_cleanup(struct snd_pcm_substream *fe_substream)
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
int stream = fe_substream->stream; int stream = fe_substream->stream;
snd_soc_dpcm_mutex_assert_held(fe);
/* mark FE's links ready to prune */ /* mark FE's links ready to prune */
for_each_dpcm_be(fe, stream, dpcm) for_each_dpcm_be(fe, stream, dpcm)
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
...@@ -2535,12 +2675,12 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) ...@@ -2535,12 +2675,12 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream)
struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(fe_substream); struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(fe_substream);
int ret; int ret;
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); snd_soc_dpcm_mutex_lock(fe);
ret = dpcm_fe_dai_shutdown(fe_substream); ret = dpcm_fe_dai_shutdown(fe_substream);
dpcm_fe_dai_cleanup(fe_substream); dpcm_fe_dai_cleanup(fe_substream);
mutex_unlock(&fe->card->mutex); snd_soc_dpcm_mutex_unlock(fe);
return ret; return ret;
} }
...@@ -2551,7 +2691,7 @@ static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) ...@@ -2551,7 +2691,7 @@ static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream)
int ret; int ret;
int stream = fe_substream->stream; int stream = fe_substream->stream;
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); snd_soc_dpcm_mutex_lock(fe);
fe->dpcm[stream].runtime = fe_substream->runtime; fe->dpcm[stream].runtime = fe_substream->runtime;
ret = dpcm_path_get(fe, stream, &list); ret = dpcm_path_get(fe, stream, &list);
...@@ -2568,7 +2708,7 @@ static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) ...@@ -2568,7 +2708,7 @@ static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream)
dpcm_clear_pending_state(fe, stream); dpcm_clear_pending_state(fe, stream);
dpcm_path_put(&list); dpcm_path_put(&list);
open_end: open_end:
mutex_unlock(&fe->card->mutex); snd_soc_dpcm_mutex_unlock(fe);
return ret; return ret;
} }
...@@ -2829,10 +2969,8 @@ static int snd_soc_dpcm_check_state(struct snd_soc_pcm_runtime *fe, ...@@ -2829,10 +2969,8 @@ static int snd_soc_dpcm_check_state(struct snd_soc_pcm_runtime *fe,
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
int state; int state;
int ret = 1; int ret = 1;
unsigned long flags;
int i; int i;
spin_lock_irqsave(&fe->card->dpcm_lock, flags);
for_each_dpcm_fe(be, stream, dpcm) { for_each_dpcm_fe(be, stream, dpcm) {
if (dpcm->fe == fe) if (dpcm->fe == fe)
...@@ -2846,7 +2984,6 @@ static int snd_soc_dpcm_check_state(struct snd_soc_pcm_runtime *fe, ...@@ -2846,7 +2984,6 @@ static int snd_soc_dpcm_check_state(struct snd_soc_pcm_runtime *fe,
} }
} }
} }
spin_unlock_irqrestore(&fe->card->dpcm_lock, flags);
/* it's safe to do this BE DAI */ /* it's safe to do this BE DAI */
return ret; return ret;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment