diff options
Diffstat (limited to 'sound/usb/pcm.c')
| -rw-r--r-- | sound/usb/pcm.c | 358 |
1 files changed, 201 insertions, 157 deletions
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 15b151ed489..c62a1659106 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -166,8 +166,8 @@ static int init_pitch_v1(struct snd_usb_audio *chip, int iface, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, data, sizeof(data))) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n", - dev->devnum, iface, ep); + usb_audio_err(chip, "%d:%d: cannot set enable PITCH\n", + iface, ep); return err; } @@ -187,8 +187,8 @@ static int init_pitch_v2(struct snd_usb_audio *chip, int iface, USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, UAC2_EP_CS_PITCH << 8, 0, data, sizeof(data))) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH (v2)\n", - dev->devnum, iface, fmt->altsetting); + usb_audio_err(chip, "%d:%d: cannot set enable PITCH (v2)\n", + iface, fmt->altsetting); return err; } @@ -226,7 +226,7 @@ static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep) if (!test_and_set_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) { struct snd_usb_endpoint *ep = subs->data_endpoint; - snd_printdd(KERN_DEBUG "Starting data EP @%p\n", ep); + dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep); ep->data_subs = subs; err = snd_usb_endpoint_start(ep, can_sleep); @@ -241,21 +241,21 @@ static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep) struct snd_usb_endpoint *ep = subs->sync_endpoint; if (subs->data_endpoint->iface != subs->sync_endpoint->iface || - subs->data_endpoint->alt_idx != subs->sync_endpoint->alt_idx) { + subs->data_endpoint->altsetting != subs->sync_endpoint->altsetting) { err = usb_set_interface(subs->dev, subs->sync_endpoint->iface, - subs->sync_endpoint->alt_idx); + subs->sync_endpoint->altsetting); if (err < 0) { - snd_printk(KERN_ERR - "%d:%d:%d: cannot set interface (%d)\n", - subs->dev->devnum, + clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags); + dev_err(&subs->dev->dev, + "%d:%d: cannot set interface (%d)\n", subs->sync_endpoint->iface, - subs->sync_endpoint->alt_idx, err); + subs->sync_endpoint->altsetting, err); return -EIO; } } - snd_printdd(KERN_DEBUG "Starting sync EP @%p\n", ep); + dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep); ep->sync_slave = subs->data_endpoint; err = snd_usb_endpoint_start(ep, can_sleep); @@ -282,22 +282,6 @@ static void stop_endpoints(struct snd_usb_substream *subs, bool wait) } } -static int deactivate_endpoints(struct snd_usb_substream *subs) -{ - int reta, retb; - - reta = snd_usb_endpoint_deactivate(subs->sync_endpoint); - retb = snd_usb_endpoint_deactivate(subs->data_endpoint); - - if (reta < 0) - return reta; - - if (retb < 0) - return retb; - - return 0; -} - static int search_roland_implicit_fb(struct usb_device *dev, int ifnum, unsigned int altsetting, struct usb_host_interface **alts, @@ -327,6 +311,139 @@ static int search_roland_implicit_fb(struct usb_device *dev, int ifnum, return 0; } +static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, + struct usb_device *dev, + struct usb_interface_descriptor *altsd, + unsigned int attr) +{ + struct usb_host_interface *alts; + struct usb_interface *iface; + unsigned int ep; + + /* Implicit feedback sync EPs consumers are always playback EPs */ + if (subs->direction != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + switch (subs->stream->chip->usb_id) { + case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ + case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ + ep = 0x81; + iface = usb_ifnum_to_if(dev, 3); + + if (!iface || iface->num_altsetting == 0) + return -EINVAL; + + alts = &iface->altsetting[1]; + goto add_sync_ep; + break; + case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */ + case USB_ID(0x0763, 0x2081): + ep = 0x81; + iface = usb_ifnum_to_if(dev, 2); + + if (!iface || iface->num_altsetting == 0) + return -EINVAL; + + alts = &iface->altsetting[1]; + goto add_sync_ep; + } + if (attr == USB_ENDPOINT_SYNC_ASYNC && + altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && + altsd->bInterfaceProtocol == 2 && + altsd->bNumEndpoints == 1 && + USB_ID_VENDOR(subs->stream->chip->usb_id) == 0x0582 /* Roland */ && + search_roland_implicit_fb(dev, altsd->bInterfaceNumber + 1, + altsd->bAlternateSetting, + &alts, &ep) >= 0) { + goto add_sync_ep; + } + + /* No quirk */ + return 0; + +add_sync_ep: + subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, + alts, ep, !subs->direction, + SND_USB_ENDPOINT_TYPE_DATA); + if (!subs->sync_endpoint) + return -EINVAL; + + subs->data_endpoint->sync_master = subs->sync_endpoint; + + return 0; +} + +static int set_sync_endpoint(struct snd_usb_substream *subs, + struct audioformat *fmt, + struct usb_device *dev, + struct usb_host_interface *alts, + struct usb_interface_descriptor *altsd) +{ + int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int ep, attr; + bool implicit_fb; + int err; + + /* we need a sync pipe in async OUT or adaptive IN mode */ + /* check the number of EP, since some devices have broken + * descriptors which fool us. if it has only one EP, + * assume it as adaptive-out or sync-in. + */ + attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; + + err = set_sync_ep_implicit_fb_quirk(subs, dev, altsd, attr); + if (err < 0) + return err; + + if (altsd->bNumEndpoints < 2) + return 0; + + if ((is_playback && attr != USB_ENDPOINT_SYNC_ASYNC) || + (!is_playback && attr != USB_ENDPOINT_SYNC_ADAPTIVE)) + return 0; + + /* check sync-pipe endpoint */ + /* ... and check descriptor size before accessing bSynchAddress + because there is a version of the SB Audigy 2 NX firmware lacking + the audio fields in the endpoint descriptors */ + if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC || + (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 1)->bSynchAddress != 0)) { + dev_err(&dev->dev, + "%d:%d : invalid sync pipe. bmAttributes %02x, bLength %d, bSynchAddress %02x\n", + fmt->iface, fmt->altsetting, + get_endpoint(alts, 1)->bmAttributes, + get_endpoint(alts, 1)->bLength, + get_endpoint(alts, 1)->bSynchAddress); + return -EINVAL; + } + ep = get_endpoint(alts, 1)->bEndpointAddress; + if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + ((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || + (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { + dev_err(&dev->dev, + "%d:%d : invalid sync pipe. is_playback %d, ep %02x, bSynchAddress %02x\n", + fmt->iface, fmt->altsetting, + is_playback, ep, get_endpoint(alts, 0)->bSynchAddress); + return -EINVAL; + } + + implicit_fb = (get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_USAGE_MASK) + == USB_ENDPOINT_USAGE_IMPLICIT_FB; + + subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, + alts, ep, !subs->direction, + implicit_fb ? + SND_USB_ENDPOINT_TYPE_DATA : + SND_USB_ENDPOINT_TYPE_SYNC); + if (!subs->sync_endpoint) + return -EINVAL; + + subs->data_endpoint->sync_master = subs->sync_endpoint; + + return 0; +} + /* * find a matching format and set up the interface */ @@ -336,9 +453,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; struct usb_interface *iface; - unsigned int ep, attr; - int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; - int err, implicit_fb = 0; + int err; iface = usb_ifnum_to_if(dev, fmt->iface); if (WARN_ON(!iface)) @@ -355,8 +470,9 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) if (subs->interface >= 0 && subs->interface != fmt->iface) { err = usb_set_interface(subs->dev, subs->interface, 0); if (err < 0) { - snd_printk(KERN_ERR "%d:%d:%d: return to setting 0 failed (%d)\n", - dev->devnum, fmt->iface, fmt->altsetting, err); + dev_err(&dev->dev, + "%d:%d: return to setting 0 failed (%d)\n", + fmt->iface, fmt->altsetting, err); return -EIO; } subs->interface = -1; @@ -368,12 +484,13 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) subs->altset_idx != fmt->altset_idx) { err = usb_set_interface(dev, fmt->iface, fmt->altsetting); if (err < 0) { - snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed (%d)\n", - dev->devnum, fmt->iface, fmt->altsetting, err); + dev_err(&dev->dev, + "%d:%d: usb_set_interface failed (%d)\n", + fmt->iface, fmt->altsetting, err); return -EIO; } - snd_printdd(KERN_INFO "setting usb interface %d:%d\n", - fmt->iface, fmt->altsetting); + dev_dbg(&dev->dev, "setting usb interface %d:%d\n", + fmt->iface, fmt->altsetting); subs->interface = fmt->iface; subs->altset_idx = fmt->altset_idx; @@ -383,118 +500,22 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip, alts, fmt->endpoint, subs->direction, SND_USB_ENDPOINT_TYPE_DATA); + if (!subs->data_endpoint) return -EINVAL; - /* we need a sync pipe in async OUT or adaptive IN mode */ - /* check the number of EP, since some devices have broken - * descriptors which fool us. if it has only one EP, - * assume it as adaptive-out or sync-in. - */ - attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; - - switch (subs->stream->chip->usb_id) { - case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ - case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ - if (is_playback) { - implicit_fb = 1; - ep = 0x81; - iface = usb_ifnum_to_if(dev, 3); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - goto add_sync_ep; - } - break; - case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */ - case USB_ID(0x0763, 0x2081): - if (is_playback) { - implicit_fb = 1; - ep = 0x81; - iface = usb_ifnum_to_if(dev, 2); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - goto add_sync_ep; - } - } - if (is_playback && - attr == USB_ENDPOINT_SYNC_ASYNC && - altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && - altsd->bInterfaceProtocol == 2 && - altsd->bNumEndpoints == 1 && - USB_ID_VENDOR(subs->stream->chip->usb_id) == 0x0582 /* Roland */ && - search_roland_implicit_fb(dev, altsd->bInterfaceNumber + 1, - altsd->bAlternateSetting, - &alts, &ep) >= 0) { - implicit_fb = 1; - goto add_sync_ep; - } - - if (((is_playback && attr == USB_ENDPOINT_SYNC_ASYNC) || - (!is_playback && attr == USB_ENDPOINT_SYNC_ADAPTIVE)) && - altsd->bNumEndpoints >= 2) { - /* check sync-pipe endpoint */ - /* ... and check descriptor size before accessing bSynchAddress - because there is a version of the SB Audigy 2 NX firmware lacking - the audio fields in the endpoint descriptors */ - if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC || - (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - get_endpoint(alts, 1)->bSynchAddress != 0 && - !implicit_fb)) { - snd_printk(KERN_ERR "%d:%d:%d : invalid sync pipe. bmAttributes %02x, bLength %d, bSynchAddress %02x\n", - dev->devnum, fmt->iface, fmt->altsetting, - get_endpoint(alts, 1)->bmAttributes, - get_endpoint(alts, 1)->bLength, - get_endpoint(alts, 1)->bSynchAddress); - return -EINVAL; - } - ep = get_endpoint(alts, 1)->bEndpointAddress; - if (!implicit_fb && - get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - (( is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || - (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { - snd_printk(KERN_ERR "%d:%d:%d : invalid sync pipe. is_playback %d, ep %02x, bSynchAddress %02x\n", - dev->devnum, fmt->iface, fmt->altsetting, - is_playback, ep, get_endpoint(alts, 0)->bSynchAddress); - return -EINVAL; - } - - implicit_fb = (get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_USAGE_MASK) - == USB_ENDPOINT_USAGE_IMPLICIT_FB; - -add_sync_ep: - subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, - alts, ep, !subs->direction, - implicit_fb ? - SND_USB_ENDPOINT_TYPE_DATA : - SND_USB_ENDPOINT_TYPE_SYNC); - if (!subs->sync_endpoint) - return -EINVAL; - - subs->data_endpoint->sync_master = subs->sync_endpoint; - } + err = set_sync_endpoint(subs, fmt, dev, alts, altsd); + if (err < 0) + return err; - if ((err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt)) < 0) + err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt); + if (err < 0) return err; subs->cur_audiofmt = fmt; snd_usb_set_format_quirk(subs, fmt); -#if 0 - printk(KERN_DEBUG - "setting done: format = %d, rate = %d..%d, channels = %d\n", - fmt->format, fmt->rate_min, fmt->rate_max, fmt->channels); - printk(KERN_DEBUG - " datapipe = 0x%0x, syncpipe = 0x%0x\n", - subs->datapipe, subs->syncpipe); -#endif - return 0; } @@ -505,20 +526,23 @@ add_sync_ep: * - Requested PCM format is not supported. * - Requested sample rate is not supported. */ -static int match_endpoint_audioformats(struct audioformat *fp, - struct audioformat *match, int rate, - snd_pcm_format_t pcm_format) +static int match_endpoint_audioformats(struct snd_usb_substream *subs, + struct audioformat *fp, + struct audioformat *match, int rate, + snd_pcm_format_t pcm_format) { int i; int score = 0; if (fp->channels < 1) { - snd_printdd("%s: (fmt @%p) no channels\n", __func__, fp); + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) no channels\n", __func__, fp); return 0; } if (!(fp->formats & pcm_format_to_bits(pcm_format))) { - snd_printdd("%s: (fmt @%p) no match for format %d\n", __func__, + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) no match for format %d\n", __func__, fp, pcm_format); return 0; } @@ -530,7 +554,8 @@ static int match_endpoint_audioformats(struct audioformat *fp, } } if (!score) { - snd_printdd("%s: (fmt @%p) no match for rate %d\n", __func__, + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) no match for rate %d\n", __func__, fp, rate); return 0; } @@ -538,7 +563,8 @@ static int match_endpoint_audioformats(struct audioformat *fp, if (fp->channels == match->channels) score++; - snd_printdd("%s: (fmt @%p) score %d\n", __func__, fp, score); + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) score %d\n", __func__, fp, score); return score; } @@ -562,13 +588,15 @@ static int configure_sync_endpoint(struct snd_usb_substream *subs) subs->pcm_format, subs->channels, subs->period_bytes, + 0, 0, subs->cur_rate, subs->cur_audiofmt, NULL); /* Try to find the best matching audioformat. */ list_for_each_entry(fp, &sync_subs->fmt_list, list) { - int score = match_endpoint_audioformats(fp, subs->cur_audiofmt, + int score = match_endpoint_audioformats(subs, + fp, subs->cur_audiofmt, subs->cur_rate, subs->pcm_format); if (score > cur_score) { @@ -578,7 +606,8 @@ static int configure_sync_endpoint(struct snd_usb_substream *subs) } if (unlikely(sync_fp == NULL)) { - snd_printk(KERN_ERR "%s: no valid audioformat for sync ep %x found\n", + dev_err(&subs->dev->dev, + "%s: no valid audioformat for sync ep %x found\n", __func__, sync_subs->ep_num); return -EINVAL; } @@ -590,7 +619,8 @@ static int configure_sync_endpoint(struct snd_usb_substream *subs) if (sync_fp->channels != subs->channels) { sync_period_bytes = (subs->period_bytes / subs->channels) * sync_fp->channels; - snd_printdd("%s: adjusted sync ep period bytes (%d -> %d)\n", + dev_dbg(&subs->dev->dev, + "%s: adjusted sync ep period bytes (%d -> %d)\n", __func__, subs->period_bytes, sync_period_bytes); } @@ -598,6 +628,7 @@ static int configure_sync_endpoint(struct snd_usb_substream *subs) subs->pcm_format, sync_fp->channels, sync_period_bytes, + 0, 0, subs->cur_rate, sync_fp, NULL); @@ -620,6 +651,8 @@ static int configure_endpoint(struct snd_usb_substream *subs) subs->pcm_format, subs->channels, subs->period_bytes, + subs->period_frames, + subs->buffer_periods, subs->cur_rate, subs->cur_audiofmt, subs->sync_endpoint); @@ -656,12 +689,15 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, subs->pcm_format = params_format(hw_params); subs->period_bytes = params_period_bytes(hw_params); + subs->period_frames = params_period_size(hw_params); + subs->buffer_periods = params_periods(hw_params); subs->channels = params_channels(hw_params); subs->cur_rate = params_rate(hw_params); fmt = find_format(subs); if (!fmt) { - snd_printd(KERN_DEBUG "cannot set format: format = %#x, rate = %d, channels = %d\n", + dev_dbg(&subs->dev->dev, + "cannot set format: format = %#x, rate = %d, channels = %d\n", subs->pcm_format, subs->cur_rate, subs->channels); return -EINVAL; } @@ -697,7 +733,8 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) down_read(&subs->stream->chip->shutdown_rwsem); if (!subs->stream->chip->shutdown) { stop_endpoints(subs, true); - deactivate_endpoints(subs); + snd_usb_endpoint_deactivate(subs->sync_endpoint); + snd_usb_endpoint_deactivate(subs->data_endpoint); } up_read(&subs->stream->chip->shutdown_rwsem); return snd_pcm_lib_free_vmalloc_buffer(substream); @@ -717,7 +754,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) int ret; if (! subs->cur_audiofmt) { - snd_printk(KERN_ERR "usbaudio: no format is specified!\n"); + dev_err(&subs->dev->dev, "no format is specified!\n"); return -ENXIO; } @@ -1210,7 +1247,8 @@ static void retire_capture_urb(struct snd_usb_substream *subs, for (i = 0; i < urb->number_of_packets; i++) { cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset + subs->pkt_offset_adj; if (urb->iso_frame_desc[i].status && printk_ratelimit()) { - snd_printdd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status); + dev_dbg(&subs->dev->dev, "frame %d active: %d\n", + i, urb->iso_frame_desc[i].status); // continue; } bytes = urb->iso_frame_desc[i].actual_length; @@ -1220,7 +1258,8 @@ static void retire_capture_urb(struct snd_usb_substream *subs, if (bytes % (runtime->sample_bits >> 3) != 0) { int oldbytes = bytes; bytes = frames * stride; - snd_printdd(KERN_ERR "Corrected urb data len. %d->%d\n", + dev_warn(&subs->dev->dev, + "Corrected urb data len. %d->%d\n", oldbytes, bytes); } /* update the current pointer */ @@ -1330,6 +1369,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, frames = 0; urb->number_of_packets = 0; spin_lock_irqsave(&subs->lock, flags); + subs->frame_limit += ep->max_urb_frames; for (i = 0; i < ctx->packets; i++) { if (ctx->packet_size[i]) counts = ctx->packet_size[i]; @@ -1344,6 +1384,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, subs->transfer_done += counts; if (subs->transfer_done >= runtime->period_size) { subs->transfer_done -= runtime->period_size; + subs->frame_limit = 0; period_elapsed = 1; if (subs->fmt_type == UAC_FORMAT_TYPE_II) { if (subs->transfer_done > 0) { @@ -1366,8 +1407,10 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, break; } } - if (period_elapsed && - !snd_usb_endpoint_implicit_feedback_sink(subs->data_endpoint)) /* finish at the period boundary */ + /* finish at the period boundary or after enough frames */ + if ((period_elapsed || + subs->transfer_done >= subs->frame_limit) && + !snd_usb_endpoint_implicit_feedback_sink(ep)) break; } bytes = frames * ep->stride; @@ -1459,7 +1502,8 @@ static void retire_playback_urb(struct snd_usb_substream *subs, * on two reads of a counter updated every ms. */ if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2) - snd_printk(KERN_DEBUG "delay: estimated %d, actual %d\n", + dev_dbg_ratelimited(&subs->dev->dev, + "delay: estimated %d, actual %d\n", est_delay, subs->last_delay); if (!subs->running) { |
