summaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
authorClemens Ladisch <clemens@ladisch.de>2009-07-13 13:48:36 +0200
committerTakashi Iwai <tiwai@suse.de>2009-07-15 11:56:41 +0200
commita65dd997b3cf057f6524466cf8dfb8382c132bd5 (patch)
treef5463308ba24a10266ae828ed869500c6f282fd1 /sound
parented4affa53229701be7be4f7f84ba84164135d7e8 (diff)
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: Clemens Ladisch <clemens@ladisch.de> Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound')
-rw-r--r--sound/usb/usbmidi.c79
1 files changed, 60 insertions, 19 deletions
diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c
index 32d70cc046e..0eff19ceb7e 100644
--- a/sound/usb/usbmidi.c
+++ b/sound/usb/usbmidi.c
@@ -45,6 +45,7 @@
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/usb.h>
+#include <linux/wait.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/asequencer.h>
@@ -124,9 +125,10 @@ struct snd_usb_midi_out_endpoint {
struct snd_usb_midi_out_endpoint *ep;
} urbs[OUTPUT_URBS];
unsigned int active_urbs;
+ unsigned int drain_urbs;
int max_transfer; /* size of urb buffer */
struct tasklet_struct tasklet;
-
+ unsigned int next_urb;
spinlock_t buffer_lock;
struct usbmidi_out_port {
@@ -145,6 +147,8 @@ struct snd_usb_midi_out_endpoint {
uint8_t data[2];
} ports[0x10];
int current_port;
+
+ wait_queue_head_t drain_wait;
};
struct snd_usb_midi_in_endpoint {
@@ -259,9 +263,15 @@ static void snd_usbmidi_out_urb_complete(struct urb* urb)
{
struct out_urb_context *context = urb->context;
struct snd_usb_midi_out_endpoint* ep = context->ep;
+ unsigned int urb_index;
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);
if (urb->status < 0) {
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)
return;
}
+ urb_index = ep->next_urb;
for (;;) {
- urb = NULL;
- for (urb_index = 0; urb_index < OUTPUT_URBS; ++urb_index)
- if (!(ep->active_urbs & (1 << urb_index))) {
- urb = ep->urbs[urb_index].urb;
+ if (!(ep->active_urbs & (1 << urb_index))) {
+ urb = ep->urbs[urb_index].urb;
+ urb->transfer_buffer_length = 0;
+ ep->umidi->usb_protocol_ops->output(ep, urb);
+ if (urb->transfer_buffer_length == 0)
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,
- urb->transfer_buffer_length);
- urb->dev = ep->umidi->chip->dev;
- if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0)
+ dump_urb("sending", urb->transfer_buffer,
+ urb->transfer_buffer_length);
+ urb->dev = ep->umidi->chip->dev;
+ 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;
- ep->active_urbs |= 1 << urb_index;
}
+ ep->next_urb = urb_index;
spin_unlock_irqrestore(&ep->buffer_lock, flags);
}
@@ -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)
{
return 0;
@@ -937,6 +976,7 @@ static struct snd_rawmidi_ops snd_usbmidi_output_ops = {
.open = snd_usbmidi_output_open,
.close = snd_usbmidi_output_close,
.trigger = snd_usbmidi_output_trigger,
+ .drain = snd_usbmidi_output_drain,
};
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,
spin_lock_init(&ep->buffer_lock);
tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
+ init_waitqueue_head(&ep->drain_wait);
for (i = 0; i < 0x10; ++i)
if (ep_info->out_cables & (1 << i)) {