[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: Clemens Ladisch <clemens@ladisch.de>
diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c
index 5f19b49..93dedde 100644
--- a/sound/usb/usbmidi.c
+++ b/sound/usb/usbmidi.c
@@ -44,6 +44,7 @@
 #include <linux/string.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/timer.h>
 #include <linux/usb.h>
 #include <sound/core.h>
 #include <sound/minors.h>
@@ -56,6 +57,12 @@
  */
 /* #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_DESCRIPTION("USB Audio/MIDI helper module");
@@ -100,6 +107,7 @@
 	snd_rawmidi_t* rmidi;
 	struct usb_protocol_ops* usb_protocol_ops;
 	struct list_head list;
+	struct timer_list error_timer;
 
 	struct snd_usb_midi_endpoint {
 		snd_usb_midi_out_endpoint_t *out;
@@ -141,7 +149,8 @@
 	struct usbmidi_in_port {
 		snd_rawmidi_substream_t* substream;
 	} ports[0x10];
-	int seen_f5;
+	u8 seen_f5;
+	u8 error_resubmit;
 	int current_port;
 };
 
@@ -167,14 +176,22 @@
  */
 static int snd_usbmidi_urb_error(int status)
 {
-	if (status == -ENOENT)
-		return status; /* killed */
-	if (status == -EILSEQ ||
-	    status == -ECONNRESET ||
-	    status == -ETIMEDOUT)
-		return -ENODEV; /* device removed/shutdown */
-	snd_printk(KERN_ERR "urb status %d\n", status);
-	return 0; /* continue */
+	switch (status) {
+	/* manually unlinked, or device gone */
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+	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);
+		return 0; /* continue */
+	}
 }
 
 /*
@@ -218,8 +235,15 @@
 		ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
 						   urb->actual_length);
 	} 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;
+		}
 	}
 
 	if (usb_pipe_needs_resubmit(urb->pipe)) {
@@ -236,8 +260,13 @@
 	ep->urb_active = 0;
 	spin_unlock(&ep->buffer_lock);
 	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;
+		}
 	}
 	snd_usbmidi_do_output(ep);
 }
@@ -276,6 +305,24 @@
 	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 */
 static int send_bulk_static_data(snd_usb_midi_out_endpoint_t* ep,
 				 const void *data, int len)
@@ -844,8 +891,6 @@
  */
 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) {
 		usb_buffer_free(ep->umidi->chip->dev, ep->max_transfer,
 				ep->urb->transfer_buffer,
@@ -934,8 +979,11 @@
 	int i;
 
 	umidi = list_entry(p, snd_usb_midi_t, list);
+	del_timer_sync(&umidi->error_timer);
 	for (i = 0; i < MIDI_MAX_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) {
 			usb_kill_urb(ep->out->urb);
 			if (umidi->usb_protocol_ops->finish_out_endpoint)
@@ -1496,6 +1544,9 @@
 	umidi->iface = iface;
 	umidi->quirk = quirk;
 	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 */
 	memset(endpoints, 0, sizeof(endpoints));