#include <linux/device.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
 #include <linux/usb.h>
 #include <linux/usb/input.h>
 #include <linux/pm_wakeup.h>
 #include <media/rc-core.h>
 
-#define DRIVER_VERSION "1.92"
+#define DRIVER_VERSION "1.93"
 #define DRIVER_AUTHOR  "Jarod Wilson <jarod@redhat.com>"
 #define DRIVER_DESC    "Windows Media Center Ed. eHome Infrared Transceiver " \
                        "device driver"
        /* usb */
        struct usb_device *usbdev;
        struct urb *urb_in;
+       unsigned int pipe_in;
        struct usb_endpoint_descriptor *usb_ep_out;
 
        /* buffers and dma */
        u8 num_rxports;         /* number of receive sensors */
        u8 txports_cabled;      /* bitmask of transmitters with cable */
        u8 rxports_active;      /* bitmask of active receive sensors */
+
+       /*
+        * support for async error handler mceusb_deferred_kevent()
+        * where usb_clear_halt(), usb_reset_configuration(),
+        * usb_reset_device(), etc. must be done in process context
+        */
+       struct work_struct kevent;
+       unsigned long kevent_flags;
+#              define EVENT_TX_HALT    0
+#              define EVENT_RX_HALT    1
 };
 
 /* MCE Device Command Strings, generally a port and command pair */
 #endif
 }
 
+/*
+ * Schedule work that can't be done in interrupt handlers
+ * (mceusb_dev_recv() and mce_async_callback()) nor tasklets.
+ * Invokes mceusb_deferred_kevent() for recovering from
+ * error events specified by the kevent bit field.
+ */
+static void mceusb_defer_kevent(struct mceusb_dev *ir, int kevent)
+{
+       set_bit(kevent, &ir->kevent_flags);
+       if (!schedule_work(&ir->kevent))
+               dev_err(ir->dev, "kevent %d may have been dropped", kevent);
+       else
+               dev_dbg(ir->dev, "kevent %d scheduled", kevent);
+}
+
 static void mce_async_callback(struct urb *urb)
 {
        struct mceusb_dev *ir;
                return;
 
        case -EPIPE:
+               dev_err(ir->dev, "Error: urb status = %d (RX HALT)",
+                       urb->status);
+               mceusb_defer_kevent(ir, EVENT_RX_HALT);
+               return;
+
        default:
                dev_err(ir->dev, "Error: urb status = %d", urb->status);
                break;
        mce_async_out(ir, FLASH_LED, sizeof(FLASH_LED));
 }
 
+/*
+ * Workqueue function
+ * for resetting or recovering device after occurrence of error events
+ * specified in ir->kevent bit field.
+ * Function runs (via schedule_work()) in non-interrupt context, for
+ * calls here (such as usb_clear_halt()) requiring non-interrupt context.
+ */
+static void mceusb_deferred_kevent(struct work_struct *work)
+{
+       struct mceusb_dev *ir =
+               container_of(work, struct mceusb_dev, kevent);
+       int status;
+
+       if (test_bit(EVENT_RX_HALT, &ir->kevent_flags)) {
+               usb_unlink_urb(ir->urb_in);
+               status = usb_clear_halt(ir->usbdev, ir->pipe_in);
+               if (status < 0) {
+                       dev_err(ir->dev, "rx clear halt error %d",
+                               status);
+                       return;
+               }
+               clear_bit(EVENT_RX_HALT, &ir->kevent_flags);
+               status = usb_submit_urb(ir->urb_in, GFP_KERNEL);
+               if (status < 0) {
+                       dev_err(ir->dev, "rx unhalt submit urb error %d",
+                               status);
+                       return;
+               }
+       }
+}
+
 static struct rc_dev *mceusb_init_rc_dev(struct mceusb_dev *ir)
 {
        struct usb_device *udev = ir->usbdev;
        if (!ir)
                goto mem_alloc_fail;
 
+       ir->pipe_in = pipe;
        ir->buf_in = usb_alloc_coherent(dev, maxp, GFP_ATOMIC, &ir->dma_in);
        if (!ir->buf_in)
                goto buf_in_alloc_fail;
                snprintf(name + strlen(name), sizeof(name) - strlen(name),
                         " %s", buf);
 
+       /*
+        * Initialize async USB error handler before registering
+        * or activating any mceusb RX and TX functions
+        */
+       INIT_WORK(&ir->kevent, mceusb_deferred_kevent);
+
        ir->rc = mceusb_init_rc_dev(ir);
        if (!ir->rc)
                goto rc_dev_fail;
 
        /* Error-handling path */
 rc_dev_fail:
+       cancel_work_sync(&ir->kevent);
        usb_put_dev(ir->usbdev);
        usb_kill_urb(ir->urb_in);
        usb_free_urb(ir->urb_in);
                return;
 
        ir->usbdev = NULL;
+       cancel_work_sync(&ir->kevent);
        rc_unregister_device(ir->rc);
        usb_kill_urb(ir->urb_in);
        usb_free_urb(ir->urb_in);