#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>
  */
 /* #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");
        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;
        struct usbmidi_in_port {
                snd_rawmidi_substream_t* substream;
        } ports[0x10];
-       int seen_f5;
+       u8 seen_f5;
+       u8 error_resubmit;
        int current_port;
 };
 
  */
 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 */
+       }
 }
 
 /*
                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)) {
        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);
 }
        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)
  */
 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,
        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)
        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));