Commit c8846970 authored by Clemens Ladisch's avatar Clemens Ladisch Committed by Jaroslav Kysela

[ALSA] usb-audio: throttle MIDI URB resubmits on USB errors

USB generic driver
When a USB error occurs that might indicate that the device has been
unplugged, don't resubmit the URB immediately to prevent flooding the
log with error messages before khubd has us disconnect()ed.
Signed-off-by: default avatarClemens Ladisch <clemens@ladisch.de>
parent d568121c
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include <linux/string.h> #include <linux/string.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/timer.h>
#include <linux/usb.h> #include <linux/usb.h>
#include <sound/core.h> #include <sound/core.h>
#include <sound/minors.h> #include <sound/minors.h>
...@@ -56,6 +57,12 @@ ...@@ -56,6 +57,12 @@
*/ */
/* #define DUMP_PACKETS */ /* #define DUMP_PACKETS */
/*
* how long to wait after some USB errors, so that khubd can disconnect() us
* without too many spurious errors
*/
#define ERROR_DELAY_JIFFIES (HZ / 10)
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_DESCRIPTION("USB Audio/MIDI helper module"); MODULE_DESCRIPTION("USB Audio/MIDI helper module");
...@@ -100,6 +107,7 @@ struct snd_usb_midi { ...@@ -100,6 +107,7 @@ struct snd_usb_midi {
snd_rawmidi_t* rmidi; snd_rawmidi_t* rmidi;
struct usb_protocol_ops* usb_protocol_ops; struct usb_protocol_ops* usb_protocol_ops;
struct list_head list; struct list_head list;
struct timer_list error_timer;
struct snd_usb_midi_endpoint { struct snd_usb_midi_endpoint {
snd_usb_midi_out_endpoint_t *out; snd_usb_midi_out_endpoint_t *out;
...@@ -141,7 +149,8 @@ struct snd_usb_midi_in_endpoint { ...@@ -141,7 +149,8 @@ struct snd_usb_midi_in_endpoint {
struct usbmidi_in_port { struct usbmidi_in_port {
snd_rawmidi_substream_t* substream; snd_rawmidi_substream_t* substream;
} ports[0x10]; } ports[0x10];
int seen_f5; u8 seen_f5;
u8 error_resubmit;
int current_port; int current_port;
}; };
...@@ -167,14 +176,22 @@ static int snd_usbmidi_submit_urb(struct urb* urb, int flags) ...@@ -167,14 +176,22 @@ static int snd_usbmidi_submit_urb(struct urb* urb, int flags)
*/ */
static int snd_usbmidi_urb_error(int status) static int snd_usbmidi_urb_error(int status)
{ {
if (status == -ENOENT) switch (status) {
return status; /* killed */ /* manually unlinked, or device gone */
if (status == -EILSEQ || case -ENOENT:
status == -ECONNRESET || case -ECONNRESET:
status == -ETIMEDOUT) case -ESHUTDOWN:
return -ENODEV; /* device removed/shutdown */ case -ENODEV:
return -ENODEV;
/* errors that might occur during unplugging */
case -EPROTO: /* EHCI */
case -ETIMEDOUT: /* OHCI */
case -EILSEQ: /* UHCI */
return -EIO;
default:
snd_printk(KERN_ERR "urb status %d\n", status); snd_printk(KERN_ERR "urb status %d\n", status);
return 0; /* continue */ return 0; /* continue */
}
} }
/* /*
...@@ -218,9 +235,16 @@ static void snd_usbmidi_in_urb_complete(struct urb* urb, struct pt_regs *regs) ...@@ -218,9 +235,16 @@ static void snd_usbmidi_in_urb_complete(struct urb* urb, struct pt_regs *regs)
ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer, ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
urb->actual_length); urb->actual_length);
} else { } else {
if (snd_usbmidi_urb_error(urb->status) < 0) int err = snd_usbmidi_urb_error(urb->status);
if (err < 0) {
if (err != -ENODEV) {
ep->error_resubmit = 1;
mod_timer(&ep->umidi->error_timer,
jiffies + ERROR_DELAY_JIFFIES);
}
return; return;
} }
}
if (usb_pipe_needs_resubmit(urb->pipe)) { if (usb_pipe_needs_resubmit(urb->pipe)) {
urb->dev = ep->umidi->chip->dev; urb->dev = ep->umidi->chip->dev;
...@@ -236,9 +260,14 @@ static void snd_usbmidi_out_urb_complete(struct urb* urb, struct pt_regs *regs) ...@@ -236,9 +260,14 @@ static void snd_usbmidi_out_urb_complete(struct urb* urb, struct pt_regs *regs)
ep->urb_active = 0; ep->urb_active = 0;
spin_unlock(&ep->buffer_lock); spin_unlock(&ep->buffer_lock);
if (urb->status < 0) { if (urb->status < 0) {
if (snd_usbmidi_urb_error(urb->status) < 0) int err = snd_usbmidi_urb_error(urb->status);
if (err < 0) {
if (err != -ENODEV)
mod_timer(&ep->umidi->error_timer,
jiffies + ERROR_DELAY_JIFFIES);
return; return;
} }
}
snd_usbmidi_do_output(ep); snd_usbmidi_do_output(ep);
} }
...@@ -276,6 +305,24 @@ static void snd_usbmidi_out_tasklet(unsigned long data) ...@@ -276,6 +305,24 @@ static void snd_usbmidi_out_tasklet(unsigned long data)
snd_usbmidi_do_output(ep); snd_usbmidi_do_output(ep);
} }
/* called after transfers had been interrupted due to some USB error */
static void snd_usbmidi_error_timer(unsigned long data)
{
snd_usb_midi_t *umidi = (snd_usb_midi_t *)data;
int i;
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
snd_usb_midi_in_endpoint_t *in = umidi->endpoints[i].in;
if (in && in->error_resubmit) {
in->error_resubmit = 0;
in->urb->dev = umidi->chip->dev;
snd_usbmidi_submit_urb(in->urb, GFP_ATOMIC);
}
if (umidi->endpoints[i].out)
snd_usbmidi_do_output(umidi->endpoints[i].out);
}
}
/* helper function to send static data that may not DMA-able */ /* helper function to send static data that may not DMA-able */
static int send_bulk_static_data(snd_usb_midi_out_endpoint_t* ep, static int send_bulk_static_data(snd_usb_midi_out_endpoint_t* ep,
const void *data, int len) const void *data, int len)
...@@ -844,8 +891,6 @@ static unsigned int snd_usbmidi_count_bits(unsigned int x) ...@@ -844,8 +891,6 @@ static unsigned int snd_usbmidi_count_bits(unsigned int x)
*/ */
static void snd_usbmidi_out_endpoint_delete(snd_usb_midi_out_endpoint_t* ep) static void snd_usbmidi_out_endpoint_delete(snd_usb_midi_out_endpoint_t* ep)
{ {
if (ep->tasklet.func)
tasklet_kill(&ep->tasklet);
if (ep->urb) { if (ep->urb) {
usb_buffer_free(ep->umidi->chip->dev, ep->max_transfer, usb_buffer_free(ep->umidi->chip->dev, ep->max_transfer,
ep->urb->transfer_buffer, ep->urb->transfer_buffer,
...@@ -934,8 +979,11 @@ void snd_usbmidi_disconnect(struct list_head* p) ...@@ -934,8 +979,11 @@ void snd_usbmidi_disconnect(struct list_head* p)
int i; int i;
umidi = list_entry(p, snd_usb_midi_t, list); umidi = list_entry(p, snd_usb_midi_t, list);
del_timer_sync(&umidi->error_timer);
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
snd_usb_midi_endpoint_t* ep = &umidi->endpoints[i]; snd_usb_midi_endpoint_t* ep = &umidi->endpoints[i];
if (ep->out)
tasklet_kill(&ep->out->tasklet);
if (ep->out && ep->out->urb) { if (ep->out && ep->out->urb) {
usb_kill_urb(ep->out->urb); usb_kill_urb(ep->out->urb);
if (umidi->usb_protocol_ops->finish_out_endpoint) if (umidi->usb_protocol_ops->finish_out_endpoint)
...@@ -1496,6 +1544,9 @@ int snd_usb_create_midi_interface(snd_usb_audio_t* chip, ...@@ -1496,6 +1544,9 @@ int snd_usb_create_midi_interface(snd_usb_audio_t* chip,
umidi->iface = iface; umidi->iface = iface;
umidi->quirk = quirk; umidi->quirk = quirk;
umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
init_timer(&umidi->error_timer);
umidi->error_timer.function = snd_usbmidi_error_timer;
umidi->error_timer.data = (unsigned long)umidi;
/* detect the endpoint(s) to use */ /* detect the endpoint(s) to use */
memset(endpoints, 0, sizeof(endpoints)); memset(endpoints, 0, sizeof(endpoints));
......
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