Commit a65dd997 authored by Clemens Ladisch's avatar Clemens Ladisch Committed by Takashi Iwai

sound: usb-audio: add MIDI drain callback

When draining, instead of waiting for fifty milliseconds, just wait for
the currently active URBs to complete.  This cuts the usual waiting time
down to one USB frame, or zero in the common case when there is no URB.
Signed-off-by: default avatarClemens Ladisch <clemens@ladisch.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent ed4affa5
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/usb.h> #include <linux/usb.h>
#include <linux/wait.h>
#include <sound/core.h> #include <sound/core.h>
#include <sound/rawmidi.h> #include <sound/rawmidi.h>
#include <sound/asequencer.h> #include <sound/asequencer.h>
...@@ -124,9 +125,10 @@ struct snd_usb_midi_out_endpoint { ...@@ -124,9 +125,10 @@ struct snd_usb_midi_out_endpoint {
struct snd_usb_midi_out_endpoint *ep; struct snd_usb_midi_out_endpoint *ep;
} urbs[OUTPUT_URBS]; } urbs[OUTPUT_URBS];
unsigned int active_urbs; unsigned int active_urbs;
unsigned int drain_urbs;
int max_transfer; /* size of urb buffer */ int max_transfer; /* size of urb buffer */
struct tasklet_struct tasklet; struct tasklet_struct tasklet;
unsigned int next_urb;
spinlock_t buffer_lock; spinlock_t buffer_lock;
struct usbmidi_out_port { struct usbmidi_out_port {
...@@ -145,6 +147,8 @@ struct snd_usb_midi_out_endpoint { ...@@ -145,6 +147,8 @@ struct snd_usb_midi_out_endpoint {
uint8_t data[2]; uint8_t data[2];
} ports[0x10]; } ports[0x10];
int current_port; int current_port;
wait_queue_head_t drain_wait;
}; };
struct snd_usb_midi_in_endpoint { struct snd_usb_midi_in_endpoint {
...@@ -259,9 +263,15 @@ static void snd_usbmidi_out_urb_complete(struct urb* urb) ...@@ -259,9 +263,15 @@ static void snd_usbmidi_out_urb_complete(struct urb* urb)
{ {
struct out_urb_context *context = urb->context; struct out_urb_context *context = urb->context;
struct snd_usb_midi_out_endpoint* ep = context->ep; struct snd_usb_midi_out_endpoint* ep = context->ep;
unsigned int urb_index;
spin_lock(&ep->buffer_lock); spin_lock(&ep->buffer_lock);
ep->active_urbs &= ~(1 << (context - ep->urbs)); urb_index = context - ep->urbs;
ep->active_urbs &= ~(1 << urb_index);
if (unlikely(ep->drain_urbs)) {
ep->drain_urbs &= ~(1 << urb_index);
wake_up(&ep->drain_wait);
}
spin_unlock(&ep->buffer_lock); spin_unlock(&ep->buffer_lock);
if (urb->status < 0) { if (urb->status < 0) {
int err = snd_usbmidi_urb_error(urb->status); int err = snd_usbmidi_urb_error(urb->status);
...@@ -291,28 +301,28 @@ static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep) ...@@ -291,28 +301,28 @@ static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep)
return; return;
} }
urb_index = ep->next_urb;
for (;;) { for (;;) {
urb = NULL; if (!(ep->active_urbs & (1 << urb_index))) {
for (urb_index = 0; urb_index < OUTPUT_URBS; ++urb_index) urb = ep->urbs[urb_index].urb;
if (!(ep->active_urbs & (1 << urb_index))) { urb->transfer_buffer_length = 0;
urb = ep->urbs[urb_index].urb; ep->umidi->usb_protocol_ops->output(ep, urb);
if (urb->transfer_buffer_length == 0)
break; break;
}
if (!urb)
break;
urb->transfer_buffer_length = 0;
ep->umidi->usb_protocol_ops->output(ep, urb);
if (urb->transfer_buffer_length == 0)
break;
dump_urb("sending", urb->transfer_buffer, dump_urb("sending", urb->transfer_buffer,
urb->transfer_buffer_length); urb->transfer_buffer_length);
urb->dev = ep->umidi->chip->dev; urb->dev = ep->umidi->chip->dev;
if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0) if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0)
break;
ep->active_urbs |= 1 << urb_index;
}
if (++urb_index >= OUTPUT_URBS)
urb_index = 0;
if (urb_index == ep->next_urb)
break; break;
ep->active_urbs |= 1 << urb_index;
} }
ep->next_urb = urb_index;
spin_unlock_irqrestore(&ep->buffer_lock, flags); spin_unlock_irqrestore(&ep->buffer_lock, flags);
} }
...@@ -913,6 +923,35 @@ static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, ...@@ -913,6 +923,35 @@ static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream,
} }
} }
static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream)
{
struct usbmidi_out_port* port = substream->runtime->private_data;
struct snd_usb_midi_out_endpoint *ep = port->ep;
unsigned int drain_urbs;
DEFINE_WAIT(wait);
long timeout = msecs_to_jiffies(50);
/*
* The substream buffer is empty, but some data might still be in the
* currently active URBs, so we have to wait for those to complete.
*/
spin_lock_irq(&ep->buffer_lock);
drain_urbs = ep->active_urbs;
if (drain_urbs) {
ep->drain_urbs |= drain_urbs;
do {
prepare_to_wait(&ep->drain_wait, &wait,
TASK_UNINTERRUPTIBLE);
spin_unlock_irq(&ep->buffer_lock);
timeout = schedule_timeout(timeout);
spin_lock_irq(&ep->buffer_lock);
drain_urbs &= ep->drain_urbs;
} while (drain_urbs && timeout);
finish_wait(&ep->drain_wait, &wait);
}
spin_unlock_irq(&ep->buffer_lock);
}
static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream) static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream)
{ {
return 0; return 0;
...@@ -937,6 +976,7 @@ static struct snd_rawmidi_ops snd_usbmidi_output_ops = { ...@@ -937,6 +976,7 @@ static struct snd_rawmidi_ops snd_usbmidi_output_ops = {
.open = snd_usbmidi_output_open, .open = snd_usbmidi_output_open,
.close = snd_usbmidi_output_close, .close = snd_usbmidi_output_close,
.trigger = snd_usbmidi_output_trigger, .trigger = snd_usbmidi_output_trigger,
.drain = snd_usbmidi_output_drain,
}; };
static struct snd_rawmidi_ops snd_usbmidi_input_ops = { static struct snd_rawmidi_ops snd_usbmidi_input_ops = {
...@@ -1103,6 +1143,7 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi* umidi, ...@@ -1103,6 +1143,7 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi* umidi,
spin_lock_init(&ep->buffer_lock); spin_lock_init(&ep->buffer_lock);
tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep); tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
init_waitqueue_head(&ep->drain_wait);
for (i = 0; i < 0x10; ++i) for (i = 0; i < 0x10; ++i)
if (ep_info->out_cables & (1 << i)) { if (ep_info->out_cables & (1 << i)) {
......
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