]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ALSA: usb-audio: Fix races at disconnection
authorTakashi Iwai <tiwai@suse.de>
Fri, 12 Oct 2012 13:12:55 +0000 (15:12 +0200)
committerBen Hutchings <ben@decadent.org.uk>
Fri, 16 Nov 2012 16:46:48 +0000 (16:46 +0000)
commit 978520b75f0a1ce82b17e1e8186417250de6d545 upstream.

Close some races at disconnection of a USB audio device by adding the
chip->shutdown_mutex and chip->shutdown check at appropriate places.

The spots to put bandaids are:
- PCM prepare, hw_params and hw_free
- where the usb device is accessed for communication or get speed, in
 mixer.c and others; the device speed is now cached in subs->speed
 instead of accessing to chip->dev

The accesses in PCM open and close don't need the mutex protection
because these are already handled in the core PCM disconnection code.

The autosuspend/autoresume codes are still uncovered by this patch
because of possible mutex deadlocks.  They'll be covered by the
upcoming change to rwsem.

Also the mixer codes are untouched, too.  These will be fixed in
another patch, too.

Reported-by: Matthieu CASTET <matthieu.castet@parrot.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
sound/usb/card.h
sound/usb/endpoint.c
sound/usb/mixer.c
sound/usb/pcm.c
sound/usb/proc.c

index a39edcc32a93f0986c87498de66ca37206b38915..665e29734ab9afca5fcfc11cba59ec2a89cbc35c 100644 (file)
@@ -86,6 +86,7 @@ struct snd_usb_substream {
        struct snd_urb_ctx syncurb[SYNC_URBS];  /* sync urb table */
        char *syncbuf;                          /* sync buffer for all sync URBs */
        dma_addr_t sync_dma;                    /* DMA address of syncbuf */
+       unsigned int speed;             /* USB_SPEED_XXX */
 
        u64 formats;                    /* format bitmasks (all or'ed) */
        unsigned int num_formats;               /* number of supported audio formats (list) */
index 08dcce53720bad66499f48709c553b04e6ea6821..24c511491b4529255bcf0fe8b7817d6189b42fe4 100644 (file)
@@ -148,8 +148,10 @@ void snd_usb_release_substream_urbs(struct snd_usb_substream *subs, int force)
        int i;
 
        /* stop urbs (to be sure) */
-       deactivate_urbs(subs, force, 1);
-       wait_clear_urbs(subs);
+       if (!subs->stream->chip->shutdown) {
+               deactivate_urbs(subs, force, 1);
+               wait_clear_urbs(subs);
+       }
 
        for (i = 0; i < MAX_URBS; i++)
                release_urb_ctx(&subs->dataurb[i]);
@@ -895,7 +897,8 @@ void snd_usb_init_substream(struct snd_usb_stream *as,
        subs->dev = as->chip->dev;
        subs->txfr_quirk = as->chip->txfr_quirk;
        subs->ops = audio_urb_ops[stream];
-       if (snd_usb_get_speed(subs->dev) >= USB_SPEED_HIGH)
+       subs->speed = snd_usb_get_speed(subs->dev);
+       if (subs->speed >= USB_SPEED_HIGH)
                subs->ops.prepare_sync = prepare_capture_sync_urb_hs;
 
        snd_usb_set_pcm_ops(as->pcm, stream);
index ab23869c01bb6734073b65c6c397fe177224c217..51a81ef6ceb31408fcae93f6638cf9ba15643514 100644 (file)
@@ -287,25 +287,32 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v
        unsigned char buf[2];
        int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
        int timeout = 10;
-       int err;
+       int idx = 0, err;
 
        err = snd_usb_autoresume(cval->mixer->chip);
        if (err < 0)
                return -EIO;
+       mutex_lock(&chip->shutdown_mutex);
        while (timeout-- > 0) {
+               if (chip->shutdown)
+                       break;
+               idx = snd_usb_ctrl_intf(chip) | (cval->id << 8);
                if (snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
                                    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
-                                   validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
-                                   buf, val_len) >= val_len) {
+                                   validx, idx, buf, val_len) >= val_len) {
                        *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
-                       snd_usb_autosuspend(cval->mixer->chip);
-                       return 0;
+                       err = 0;
+                       goto out;
                }
        }
-       snd_usb_autosuspend(cval->mixer->chip);
        snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
-                   request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type);
-       return -EINVAL;
+                   request, validx, idx, cval->val_type);
+       err = -EINVAL;
+
+ out:
+       mutex_unlock(&chip->shutdown_mutex);
+       snd_usb_autosuspend(cval->mixer->chip);
+       return err;
 }
 
 static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
@@ -313,7 +320,7 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v
        struct snd_usb_audio *chip = cval->mixer->chip;
        unsigned char buf[2 + 3*sizeof(__u16)]; /* enough space for one range */
        unsigned char *val;
-       int ret, size;
+       int idx = 0, ret, size;
        __u8 bRequest;
 
        if (request == UAC_GET_CUR) {
@@ -330,16 +337,22 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v
        if (ret)
                goto error;
 
-       ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
+       mutex_lock(&chip->shutdown_mutex);
+       if (chip->shutdown)
+               ret = -ENODEV;
+       else {
+               idx = snd_usb_ctrl_intf(chip) | (cval->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, snd_usb_ctrl_intf(chip) | (cval->id << 8),
-                             buf, size);
+                             validx, idx, buf, size);
+       }
+       mutex_unlock(&chip->shutdown_mutex);
        snd_usb_autosuspend(chip);
 
        if (ret < 0) {
 error:
                snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
-                          request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type);
+                          request, validx, idx, cval->val_type);
                return ret;
        }
 
@@ -417,7 +430,7 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
 {
        struct snd_usb_audio *chip = cval->mixer->chip;
        unsigned char buf[2];
-       int val_len, err, timeout = 10;
+       int idx = 0, val_len, err, timeout = 10;
 
        if (cval->mixer->protocol == UAC_VERSION_1) {
                val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
@@ -440,19 +453,27 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
        err = snd_usb_autoresume(chip);
        if (err < 0)
                return -EIO;
-       while (timeout-- > 0)
+       mutex_lock(&chip->shutdown_mutex);
+       while (timeout-- > 0) {
+               if (chip->shutdown)
+                       break;
+               idx = snd_usb_ctrl_intf(chip) | (cval->id << 8);
                if (snd_usb_ctl_msg(chip->dev,
                                    usb_sndctrlpipe(chip->dev, 0), request,
                                    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
-                                   validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
-                                   buf, val_len) >= 0) {
-                       snd_usb_autosuspend(chip);
-                       return 0;
+                                   validx, idx, buf, val_len) >= 0) {
+                       err = 0;
+                       goto out;
                }
-       snd_usb_autosuspend(chip);
+       }
        snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n",
-                   request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type, buf[0], buf[1]);
-       return -EINVAL;
+                   request, validx, idx, cval->val_type, buf[0], buf[1]);
+       err = -EINVAL;
+
+ out:
+       mutex_unlock(&chip->shutdown_mutex);
+       snd_usb_autosuspend(chip);
+       return err;
 }
 
 static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value)
index 839165fb7e5f97d1a4ddd84b2a7b72c2fa51bd33..9a11fae0b215fecd79ba1ec7e78742c678c46f21 100644 (file)
@@ -67,6 +67,8 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream
        unsigned int hwptr_done;
 
        subs = (struct snd_usb_substream *)substream->runtime->private_data;
+       if (subs->stream->chip->shutdown)
+               return SNDRV_PCM_POS_XRUN;
        spin_lock(&subs->lock);
        hwptr_done = subs->hwptr_done;
        substream->runtime->delay = snd_usb_pcm_delay(subs,
@@ -373,8 +375,14 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
        changed = subs->cur_audiofmt != fmt ||
                subs->period_bytes != params_period_bytes(hw_params) ||
                subs->cur_rate != rate;
+
+       mutex_lock(&subs->stream->chip->shutdown_mutex);
+       if (subs->stream->chip->shutdown) {
+               ret = -ENODEV;
+               goto unlock;
+       }
        if ((ret = set_format(subs, fmt)) < 0)
-               return ret;
+               goto unlock;
 
        if (subs->cur_rate != rate) {
                struct usb_host_interface *alts;
@@ -383,12 +391,11 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
                alts = &iface->altsetting[fmt->altset_idx];
                ret = snd_usb_init_sample_rate(subs->stream->chip, subs->interface, alts, fmt, rate);
                if (ret < 0)
-                       return ret;
+                       goto unlock;
                subs->cur_rate = rate;
        }
 
        if (changed) {
-               mutex_lock(&subs->stream->chip->shutdown_mutex);
                /* format changed */
                snd_usb_release_substream_urbs(subs, 0);
                /* influenced: period_bytes, channels, rate, format, */
@@ -396,9 +403,10 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
                                                  params_rate(hw_params),
                                                  snd_pcm_format_physical_width(params_format(hw_params)) *
                                                        params_channels(hw_params));
-               mutex_unlock(&subs->stream->chip->shutdown_mutex);
        }
 
+unlock:
+       mutex_unlock(&subs->stream->chip->shutdown_mutex);
        return ret;
 }
 
@@ -429,12 +437,18 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_usb_substream *subs = runtime->private_data;
+       int ret = 0;
 
        if (! subs->cur_audiofmt) {
                snd_printk(KERN_ERR "usbaudio: no format is specified!\n");
                return -ENXIO;
        }
 
+       mutex_lock(&subs->stream->chip->shutdown_mutex);
+       if (subs->stream->chip->shutdown) {
+               ret = -ENODEV;
+               goto unlock;
+       }
        /* some unit conversions in runtime */
        subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize);
        subs->curframesize = bytes_to_frames(runtime, subs->curpacksize);
@@ -447,7 +461,10 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
        subs->last_frame_number = 0;
        runtime->delay = 0;
 
-       return snd_usb_substream_prepare(subs, runtime);
+       ret = snd_usb_substream_prepare(subs, runtime);
+ unlock:
+       mutex_unlock(&subs->stream->chip->shutdown_mutex);
+       return ret;
 }
 
 static struct snd_pcm_hardware snd_usb_hardware =
@@ -500,7 +517,7 @@ static int hw_check_valid_format(struct snd_usb_substream *subs,
                return 0;
        }
        /* check whether the period time is >= the data packet interval */
-       if (snd_usb_get_speed(subs->dev) != USB_SPEED_FULL) {
+       if (subs->speed != USB_SPEED_FULL) {
                ptime = 125 * (1 << fp->datainterval);
                if (ptime > pt->max || (ptime == pt->max && pt->openmax)) {
                        hwc_debug("   > check: ptime %u > max %u\n", ptime, pt->max);
@@ -776,7 +793,7 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
                return err;
 
        param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME;
-       if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+       if (subs->speed == USB_SPEED_FULL)
                /* full speed devices have fixed data packet interval */
                ptmin = 1000;
        if (ptmin == 1000)
index 961c9a2506865b9a84cb337606e80b21705f2b04..aef03db3870d5c2deee82d407ecad748cfc06384 100644 (file)
@@ -107,7 +107,7 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s
                        }
                        snd_iprintf(buffer, "\n");
                }
-               if (snd_usb_get_speed(subs->dev) != USB_SPEED_FULL)
+               if (subs->speed != USB_SPEED_FULL)
                        snd_iprintf(buffer, "    Data packet interval: %d us\n",
                                    125 * (1 << fp->datainterval));
                // snd_iprintf(buffer, "    Max Packet Size = %d\n", fp->maxpacksize);
@@ -128,7 +128,7 @@ static void proc_dump_substream_status(struct snd_usb_substream *subs, struct sn
                snd_iprintf(buffer, "]\n");
                snd_iprintf(buffer, "    Packet Size = %d\n", subs->curpacksize);
                snd_iprintf(buffer, "    Momentary freq = %u Hz (%#x.%04x)\n",
-                           snd_usb_get_speed(subs->dev) == USB_SPEED_FULL
+                           subs->speed == USB_SPEED_FULL
                            ? get_full_speed_hz(subs->freqm)
                            : get_high_speed_hz(subs->freqm),
                            subs->freqm >> 16, subs->freqm & 0xffff);