Commit 1d0f9530 authored by Ioan-Adrian Ratiu's avatar Ioan-Adrian Ratiu Committed by Takashi Iwai

ALSA: usb-audio: Fix irq/process data synchronization

Commit 16200948 ("ALSA: usb-audio: Fix race at stopping the stream") was
incomplete causing another more severe kernel panic, so it got reverted.
This fixes both the original problem and its fallout kernel race/crash.

The original fix is to move the endpoint member NULL clearing logic inside
wait_clear_urbs() so the irq triggering the urb completion doesn't call
retire_capture/playback_urb() after the NULL clearing and generate a panic.

However this creates a new race between snd_usb_endpoint_start()'s call
to wait_clear_urbs() and the irq urb completion handler which again calls
retire_capture/playback_urb() leading to a new NULL dereference.

We keep the EP deactivation code in snd_usb_endpoint_start() because
removing it will break the EP reference counting (see [1] [2] for info),
however we don't need the "can_sleep" mechanism anymore because a new
function was introduced (snd_usb_endpoint_sync_pending_stop()) which
synchronizes pending stops and gets called inside the pcm prepare callback.

It also makes sense to remove can_sleep because it was also removed from
deactivate_urbs() signature in [3] so we benefit from more simplification.

[1] commit 015618b9 ("ALSA: snd-usb: Fix URB cancellation at stream start")
[2] commit e9ba389c ("ALSA: usb-audio: Fix scheduling-while-atomic bug in PCM capture stream")
[3] commit ccc1696d ("ALSA: usb-audio: simplify endpoint deactivation code")

Fixes: f8114f85 ("Revert "ALSA: usb-audio: Fix race at stopping the stream"")
Signed-off-by: default avatarIoan-Adrian Ratiu <adi@adirat.com>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent c7efff92
...@@ -534,6 +534,11 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep) ...@@ -534,6 +534,11 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep)
alive, ep->ep_num); alive, ep->ep_num);
clear_bit(EP_FLAG_STOPPING, &ep->flags); clear_bit(EP_FLAG_STOPPING, &ep->flags);
ep->data_subs = NULL;
ep->sync_slave = NULL;
ep->retire_data_urb = NULL;
ep->prepare_data_urb = NULL;
return 0; return 0;
} }
...@@ -913,8 +918,6 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, ...@@ -913,8 +918,6 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
* snd_usb_endpoint_start: start an snd_usb_endpoint * snd_usb_endpoint_start: start an snd_usb_endpoint
* *
* @ep: the endpoint to start * @ep: the endpoint to start
* @can_sleep: flag indicating whether the operation is executed in
* non-atomic context
* *
* A call to this function will increment the use count of the endpoint. * A call to this function will increment the use count of the endpoint.
* In case it is not already running, the URBs for this endpoint will be * In case it is not already running, the URBs for this endpoint will be
...@@ -924,7 +927,7 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, ...@@ -924,7 +927,7 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
* *
* Returns an error if the URB submission failed, 0 in all other cases. * Returns an error if the URB submission failed, 0 in all other cases.
*/ */
int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep) int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
{ {
int err; int err;
unsigned int i; unsigned int i;
...@@ -938,8 +941,6 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep) ...@@ -938,8 +941,6 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep)
/* just to be sure */ /* just to be sure */
deactivate_urbs(ep, false); deactivate_urbs(ep, false);
if (can_sleep)
wait_clear_urbs(ep);
ep->active_mask = 0; ep->active_mask = 0;
ep->unlink_mask = 0; ep->unlink_mask = 0;
...@@ -1020,10 +1021,6 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep) ...@@ -1020,10 +1021,6 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep)
if (--ep->use_count == 0) { if (--ep->use_count == 0) {
deactivate_urbs(ep, false); deactivate_urbs(ep, false);
ep->data_subs = NULL;
ep->sync_slave = NULL;
ep->retire_data_urb = NULL;
ep->prepare_data_urb = NULL;
set_bit(EP_FLAG_STOPPING, &ep->flags); set_bit(EP_FLAG_STOPPING, &ep->flags);
} }
} }
......
...@@ -18,7 +18,7 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, ...@@ -18,7 +18,7 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
struct audioformat *fmt, struct audioformat *fmt,
struct snd_usb_endpoint *sync_ep); struct snd_usb_endpoint *sync_ep);
int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep); int snd_usb_endpoint_start(struct snd_usb_endpoint *ep);
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep); void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep);
void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep); void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep);
int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep); int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep);
......
...@@ -218,7 +218,7 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, ...@@ -218,7 +218,7 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
} }
} }
static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep) static int start_endpoints(struct snd_usb_substream *subs)
{ {
int err; int err;
...@@ -231,7 +231,7 @@ static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep) ...@@ -231,7 +231,7 @@ static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep)
dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep); dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep);
ep->data_subs = subs; ep->data_subs = subs;
err = snd_usb_endpoint_start(ep, can_sleep); err = snd_usb_endpoint_start(ep);
if (err < 0) { if (err < 0) {
clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags); clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags);
return err; return err;
...@@ -260,7 +260,7 @@ static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep) ...@@ -260,7 +260,7 @@ static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep)
dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep); dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep);
ep->sync_slave = subs->data_endpoint; ep->sync_slave = subs->data_endpoint;
err = snd_usb_endpoint_start(ep, can_sleep); err = snd_usb_endpoint_start(ep);
if (err < 0) { if (err < 0) {
clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags); clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
return err; return err;
...@@ -850,7 +850,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) ...@@ -850,7 +850,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
/* for playback, submit the URBs now; otherwise, the first hwptr_done /* for playback, submit the URBs now; otherwise, the first hwptr_done
* updates for all URBs would happen at the same time when starting */ * updates for all URBs would happen at the same time when starting */
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
ret = start_endpoints(subs, true); ret = start_endpoints(subs);
unlock: unlock:
snd_usb_unlock_shutdown(subs->stream->chip); snd_usb_unlock_shutdown(subs->stream->chip);
...@@ -1666,7 +1666,7 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream ...@@ -1666,7 +1666,7 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
err = start_endpoints(subs, false); err = start_endpoints(subs);
if (err < 0) if (err < 0)
return err; return err;
......
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