Skip to content

Commit

Permalink
ALSA: usb-audio: Avoid nested autoresume calls
Browse files Browse the repository at this point in the history
After the recent fix of runtime PM for USB-audio driver, we got a
lockdep warning like:

  =============================================
  [ INFO: possible recursive locking detected ]
  4.2.0-rc8+ torvalds#61 Not tainted
  ---------------------------------------------
  pulseaudio/980 is trying to acquire lock:
   (&chip->shutdown_rwsem){.+.+.+}, at: [<ffffffffa0355dac>] snd_usb_autoresume+0x1d/0x52 [snd_usb_audio]
  but task is already holding lock:
   (&chip->shutdown_rwsem){.+.+.+}, at: [<ffffffffa0355dac>] snd_usb_autoresume+0x1d/0x52 [snd_usb_audio]

This comes from snd_usb_autoresume() invoking down_read() and it's
used in a nested way.  Although it's basically safe, per se (as these
are read locks), it's better to reduce such spurious warnings.

The read lock is needed to guarantee the execution of "shutdown"
(cleanup at disconnection) task after all concurrent tasks are
finished.  This can be implemented in another better way.

Also, the current check of chip->in_pm isn't good enough for
protecting the racy execution of multiple auto-resumes.

This patch rewrites the logic of snd_usb_autoresume() & co; namely,
- The recursive call of autopm is avoided by the new refcount,
  chip->active.  The chip->in_pm flag is removed accordingly.
- Instead of rwsem, another refcount, chip->usage_count, is introduced
  for tracking the period to delay the shutdown procedure.  At
  the last clear of this refcount, wake_up() to the shutdown waiter is
  called.
- The shutdown flag is replaced with shutdown atomic count; this is
  for reducing the lock.
- Two new helpers are introduced to simplify the management of these
  refcounts; snd_usb_lock_shutdown() increases the usage_count, checks
  the shutdown state, and does autoresume.  snd_usb_unlock_shutdown()
  does the opposite.  Most of mixer and other codes just need this,
  and simply returns an error if it receives an error from lock.

Fixes: 9003ebb ('ALSA: usb-audio: Fix runtime PM unbalance')
Reported-and-tested-by: Alexnader Kuleshov <kuleshovmail@gmail.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
  • Loading branch information
tiwai committed Aug 26, 2015
1 parent b25cf30 commit 47ab154
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 143 deletions.
74 changes: 51 additions & 23 deletions sound/usb/card.c
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,15 @@ static int snd_usb_audio_create(struct usb_interface *intf,
}

mutex_init(&chip->mutex);
init_rwsem(&chip->shutdown_rwsem);
init_waitqueue_head(&chip->shutdown_wait);
chip->index = idx;
chip->dev = dev;
chip->card = card;
chip->setup = device_setup[idx];
chip->autoclock = autoclock;
chip->probing = 1;
atomic_set(&chip->usage_count, 0);
atomic_set(&chip->shutdown, 0);

chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
Expand Down Expand Up @@ -495,7 +497,7 @@ static int usb_audio_probe(struct usb_interface *intf,
mutex_lock(&register_mutex);
for (i = 0; i < SNDRV_CARDS; i++) {
if (usb_chip[i] && usb_chip[i]->dev == dev) {
if (usb_chip[i]->shutdown) {
if (atomic_read(&usb_chip[i]->shutdown)) {
dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
err = -EIO;
goto __error;
Expand Down Expand Up @@ -585,23 +587,23 @@ static void usb_audio_disconnect(struct usb_interface *intf)
struct snd_usb_audio *chip = usb_get_intfdata(intf);
struct snd_card *card;
struct list_head *p;
bool was_shutdown;

if (chip == (void *)-1L)
return;

card = chip->card;
down_write(&chip->shutdown_rwsem);
was_shutdown = chip->shutdown;
chip->shutdown = 1;
up_write(&chip->shutdown_rwsem);

mutex_lock(&register_mutex);
if (!was_shutdown) {
if (atomic_inc_return(&chip->shutdown) == 1) {
struct snd_usb_stream *as;
struct snd_usb_endpoint *ep;
struct usb_mixer_interface *mixer;

/* wait until all pending tasks done;
* they are protected by snd_usb_lock_shutdown()
*/
wait_event(chip->shutdown_wait,
!atomic_read(&chip->usage_count));
snd_card_disconnect(card);
/* release the pcm resources */
list_for_each_entry(as, &chip->pcm_list, list) {
Expand Down Expand Up @@ -631,28 +633,54 @@ static void usb_audio_disconnect(struct usb_interface *intf)
}
}

#ifdef CONFIG_PM

int snd_usb_autoresume(struct snd_usb_audio *chip)
/* lock the shutdown (disconnect) task and autoresume */
int snd_usb_lock_shutdown(struct snd_usb_audio *chip)
{
int err = -ENODEV;
int err;

down_read(&chip->shutdown_rwsem);
if (chip->probing || chip->in_pm)
err = 0;
else if (!chip->shutdown)
err = usb_autopm_get_interface(chip->pm_intf);
up_read(&chip->shutdown_rwsem);
atomic_inc(&chip->usage_count);
if (atomic_read(&chip->shutdown)) {
err = -EIO;
goto error;
}
err = snd_usb_autoresume(chip);
if (err < 0)
goto error;
return 0;

error:
if (atomic_dec_and_test(&chip->usage_count))
wake_up(&chip->shutdown_wait);
return err;
}

/* autosuspend and unlock the shutdown */
void snd_usb_unlock_shutdown(struct snd_usb_audio *chip)
{
snd_usb_autosuspend(chip);
if (atomic_dec_and_test(&chip->usage_count))
wake_up(&chip->shutdown_wait);
}

#ifdef CONFIG_PM

int snd_usb_autoresume(struct snd_usb_audio *chip)
{
if (atomic_read(&chip->shutdown))
return -EIO;
if (chip->probing)
return 0;
if (atomic_inc_return(&chip->active) == 1)
return usb_autopm_get_interface(chip->pm_intf);
return 0;
}

void snd_usb_autosuspend(struct snd_usb_audio *chip)
{
down_read(&chip->shutdown_rwsem);
if (!chip->shutdown && !chip->probing && !chip->in_pm)
if (chip->probing)
return;
if (atomic_dec_and_test(&chip->active))
usb_autopm_put_interface(chip->pm_intf);
up_read(&chip->shutdown_rwsem);
}

static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
Expand Down Expand Up @@ -705,7 +733,7 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
if (--chip->num_suspended_intf)
return 0;

chip->in_pm = 1;
atomic_inc(&chip->active); /* avoid autopm */
/*
* ALSA leaves material resumption to user space
* we just notify and restart the mixers
Expand All @@ -725,7 +753,7 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
chip->autosuspended = 0;

err_out:
chip->in_pm = 0;
atomic_dec(&chip->active); /* allow autopm after this point */
return err;
}

Expand Down
10 changes: 6 additions & 4 deletions sound/usb/endpoint.c
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,10 @@ static void snd_complete_urb(struct urb *urb)
if (unlikely(urb->status == -ENOENT || /* unlinked */
urb->status == -ENODEV || /* device removed */
urb->status == -ECONNRESET || /* unlinked */
urb->status == -ESHUTDOWN || /* device disabled */
ep->chip->shutdown)) /* device disconnected */
urb->status == -ESHUTDOWN)) /* device disabled */
goto exit_clear;
/* device disconnected */
if (unlikely(atomic_read(&ep->chip->shutdown)))
goto exit_clear;

if (usb_pipeout(ep->pipe)) {
Expand Down Expand Up @@ -529,7 +531,7 @@ static int deactivate_urbs(struct snd_usb_endpoint *ep, bool force)
{
unsigned int i;

if (!force && ep->chip->shutdown) /* to be sure... */
if (!force && atomic_read(&ep->chip->shutdown)) /* to be sure... */
return -EBADFD;

clear_bit(EP_FLAG_RUNNING, &ep->flags);
Expand Down Expand Up @@ -868,7 +870,7 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep)
int err;
unsigned int i;

if (ep->chip->shutdown)
if (atomic_read(&ep->chip->shutdown))
return -EBADFD;

/* already running? */
Expand Down
32 changes: 10 additions & 22 deletions sound/usb/mixer.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,11 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
int timeout = 10;
int idx = 0, err;

err = snd_usb_autoresume(chip);
err = snd_usb_lock_shutdown(chip);
if (err < 0)
return -EIO;

down_read(&chip->shutdown_rwsem);
while (timeout-- > 0) {
if (chip->shutdown)
break;
idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
if (snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
Expand All @@ -334,8 +331,7 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
err = -EINVAL;

out:
up_read(&chip->shutdown_rwsem);
snd_usb_autosuspend(chip);
snd_usb_unlock_shutdown(chip);
return err;
}

Expand All @@ -358,21 +354,15 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,

memset(buf, 0, sizeof(buf));

ret = snd_usb_autoresume(chip) ? -EIO : 0;
ret = snd_usb_lock_shutdown(chip) ? -EIO : 0;
if (ret)
goto error;

down_read(&chip->shutdown_rwsem);
if (chip->shutdown) {
ret = -ENODEV;
} else {
idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
validx, idx, buf, size);
}
up_read(&chip->shutdown_rwsem);
snd_usb_autosuspend(chip);
snd_usb_unlock_shutdown(chip);

if (ret < 0) {
error:
Expand Down Expand Up @@ -485,13 +475,12 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
buf[1] = (value_set >> 8) & 0xff;
buf[2] = (value_set >> 16) & 0xff;
buf[3] = (value_set >> 24) & 0xff;
err = snd_usb_autoresume(chip);

err = snd_usb_lock_shutdown(chip);
if (err < 0)
return -EIO;
down_read(&chip->shutdown_rwsem);

while (timeout-- > 0) {
if (chip->shutdown)
break;
idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
if (snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), request,
Expand All @@ -506,8 +495,7 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
err = -EINVAL;

out:
up_read(&chip->shutdown_rwsem);
snd_usb_autosuspend(chip);
snd_usb_unlock_shutdown(chip);
return err;
}

Expand Down
Loading

0 comments on commit 47ab154

Please sign in to comment.