]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/isdn/gigaset/bas-gigaset.c
Merge tag 'v2.6.37' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / isdn / gigaset / bas-gigaset.c
index 707d9c94cf9e0ed475e108d9d3037402322c34cb..178942a2ee61fe50124e9e6ba84ab2d6470787ad 100644 (file)
@@ -109,6 +109,9 @@ struct bas_cardstate {
 
        struct urb              *urb_int_in;    /* URB for interrupt pipe */
        unsigned char           *int_in_buf;
+       struct work_struct      int_in_wq;      /* for usb_clear_halt() */
+       struct timer_list       timer_int_in;   /* int read retry delay */
+       int                     retry_int_in;
 
        spinlock_t              lock;           /* locks all following */
        int                     basstate;       /* bitmap (BS_*) */
@@ -169,7 +172,7 @@ static char *get_usb_rcmsg(int rc)
        case -EAGAIN:
                return "start frame too early or too much scheduled";
        case -EFBIG:
-               return "too many isochronous frames requested";
+               return "too many isoc frames requested";
        case -EPIPE:
                return "endpoint stalled";
        case -EMSGSIZE:
@@ -200,13 +203,13 @@ static char *get_usb_statmsg(int status)
        case -ENOENT:
                return "unlinked (sync)";
        case -EINPROGRESS:
-               return "pending";
+               return "URB still pending";
        case -EPROTO:
-               return "bit stuffing error, timeout, or unknown USB error";
+               return "bitstuff error, timeout, or unknown USB error";
        case -EILSEQ:
                return "CRC mismatch, timeout, or unknown USB error";
        case -ETIME:
-               return "timed out";
+               return "USB response timeout";
        case -EPIPE:
                return "endpoint stalled";
        case -ECOMM:
@@ -214,15 +217,15 @@ static char *get_usb_statmsg(int status)
        case -ENOSR:
                return "OUT buffer underrun";
        case -EOVERFLOW:
-               return "too much data";
+               return "endpoint babble";
        case -EREMOTEIO:
-               return "short packet detected";
+               return "short packet";
        case -ENODEV:
                return "device removed";
        case -EXDEV:
-               return "partial isochronous transfer";
+               return "partial isoc transfer";
        case -EINVAL:
-               return "invalid argument";
+               return "ISO madness";
        case -ECONNRESET:
                return "unlinked (async)";
        case -ESHUTDOWN:
@@ -350,7 +353,7 @@ static inline void error_hangup(struct bc_state *bcs)
  * reset Gigaset device because of an unrecoverable error
  * This function may be called from any context, and takes care of
  * scheduling the necessary actions for execution outside of interrupt context.
- * cs->lock must not be held.
+ * cs->hw.bas->lock must not be held.
  * argument:
  *     controller state structure
  */
@@ -358,7 +361,9 @@ static inline void error_reset(struct cardstate *cs)
 {
        /* reset interrupt pipe to recover (ignore errors) */
        update_basstate(cs->hw.bas, BS_RESETTING, 0);
-       req_submit(cs->bcs, HD_RESET_INTERRUPT_PIPE, 0, BAS_TIMEOUT);
+       if (req_submit(cs->bcs, HD_RESET_INTERRUPT_PIPE, 0, BAS_TIMEOUT))
+               /* submission failed, escalate to USB port reset */
+               usb_queue_reset_device(cs->hw.bas->interface);
 }
 
 /* check_pending
@@ -438,23 +443,27 @@ static void cmd_in_timeout(unsigned long data)
                return;
        }
 
-       if (ucs->retry_cmd_in++ < BAS_RETRY) {
-               dev_notice(cs->dev, "control read: timeout, retry %d\n",
-                          ucs->retry_cmd_in);
-               rc = atread_submit(cs, BAS_TIMEOUT);
-               if (rc >= 0 || rc == -ENODEV)
-                       /* resubmitted or disconnected */
-                       /* - bypass regular exit block */
-                       return;
-       } else {
+       if (ucs->retry_cmd_in++ >= BAS_RETRY) {
                dev_err(cs->dev,
                        "control read: timeout, giving up after %d tries\n",
                        ucs->retry_cmd_in);
+               kfree(ucs->rcvbuf);
+               ucs->rcvbuf = NULL;
+               ucs->rcvbuf_size = 0;
+               error_reset(cs);
+               return;
+       }
+
+       gig_dbg(DEBUG_USBREQ, "%s: timeout, retry %d",
+               __func__, ucs->retry_cmd_in);
+       rc = atread_submit(cs, BAS_TIMEOUT);
+       if (rc < 0) {
+               kfree(ucs->rcvbuf);
+               ucs->rcvbuf = NULL;
+               ucs->rcvbuf_size = 0;
+               if (rc != -ENODEV)
+                       error_reset(cs);
        }
-       kfree(ucs->rcvbuf);
-       ucs->rcvbuf = NULL;
-       ucs->rcvbuf_size = 0;
-       error_reset(cs);
 }
 
 /* read_ctrl_callback
@@ -470,18 +479,11 @@ static void read_ctrl_callback(struct urb *urb)
        struct cardstate *cs = inbuf->cs;
        struct bas_cardstate *ucs = cs->hw.bas;
        int status = urb->status;
-       int have_data = 0;
        unsigned numbytes;
        int rc;
 
        update_basstate(ucs, 0, BS_ATRDPEND);
        wake_up(&ucs->waitqueue);
-
-       if (!ucs->rcvbuf_size) {
-               dev_warn(cs->dev, "%s: no receive in progress\n", __func__);
-               return;
-       }
-
        del_timer(&ucs->timer_cmd_in);
 
        switch (status) {
@@ -495,19 +497,10 @@ static void read_ctrl_callback(struct urb *urb)
                                numbytes = ucs->rcvbuf_size;
                }
 
-               /* copy received bytes to inbuf */
-               have_data = gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes);
-
-               if (unlikely(numbytes < ucs->rcvbuf_size)) {
-                       /* incomplete - resubmit for remaining bytes */
-                       ucs->rcvbuf_size -= numbytes;
-                       ucs->retry_cmd_in = 0;
-                       rc = atread_submit(cs, BAS_TIMEOUT);
-                       if (rc >= 0 || rc == -ENODEV)
-                               /* resubmitted or disconnected */
-                               /* - bypass regular exit block */
-                               return;
-                       error_reset(cs);
+               /* copy received bytes to inbuf, notify event layer */
+               if (gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes)) {
+                       gig_dbg(DEBUG_INTR, "%s-->BH", __func__);
+                       gigaset_schedule_event(cs);
                }
                break;
 
@@ -516,37 +509,32 @@ static void read_ctrl_callback(struct urb *urb)
        case -EINPROGRESS:              /* pending */
        case -ENODEV:                   /* device removed */
        case -ESHUTDOWN:                /* device shut down */
-               /* no action necessary */
+               /* no further action necessary */
                gig_dbg(DEBUG_USBREQ, "%s: %s",
                        __func__, get_usb_statmsg(status));
                break;
 
-       default:                        /* severe trouble */
-               dev_warn(cs->dev, "control read: %s\n",
-                        get_usb_statmsg(status));
+       default:                        /* other errors: retry */
                if (ucs->retry_cmd_in++ < BAS_RETRY) {
-                       dev_notice(cs->dev, "control read: retry %d\n",
-                                  ucs->retry_cmd_in);
+                       gig_dbg(DEBUG_USBREQ, "%s: %s, retry %d", __func__,
+                               get_usb_statmsg(status), ucs->retry_cmd_in);
                        rc = atread_submit(cs, BAS_TIMEOUT);
-                       if (rc >= 0 || rc == -ENODEV)
-                               /* resubmitted or disconnected */
-                               /* - bypass regular exit block */
+                       if (rc >= 0)
+                               /* successfully resubmitted, skip freeing */
                                return;
-               } else {
-                       dev_err(cs->dev,
-                               "control read: giving up after %d tries\n",
-                               ucs->retry_cmd_in);
+                       if (rc == -ENODEV)
+                               /* disconnect, no further action necessary */
+                               break;
                }
+               dev_err(cs->dev, "control read: %s, giving up after %d tries\n",
+                       get_usb_statmsg(status), ucs->retry_cmd_in);
                error_reset(cs);
        }
 
+       /* read finished, free buffer */
        kfree(ucs->rcvbuf);
        ucs->rcvbuf = NULL;
        ucs->rcvbuf_size = 0;
-       if (have_data) {
-               gig_dbg(DEBUG_INTR, "%s-->BH", __func__);
-               gigaset_schedule_event(cs);
-       }
 }
 
 /* atread_submit
@@ -605,14 +593,67 @@ static int atread_submit(struct cardstate *cs, int timeout)
 
        if (timeout > 0) {
                gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
-               ucs->timer_cmd_in.expires = jiffies + timeout * HZ / 10;
-               ucs->timer_cmd_in.data = (unsigned long) cs;
-               ucs->timer_cmd_in.function = cmd_in_timeout;
-               add_timer(&ucs->timer_cmd_in);
+               mod_timer(&ucs->timer_cmd_in, jiffies + timeout * HZ / 10);
        }
        return 0;
 }
 
+/* int_in_work
+ * workqueue routine to clear halt on interrupt in endpoint
+ */
+
+static void int_in_work(struct work_struct *work)
+{
+       struct bas_cardstate *ucs =
+               container_of(work, struct bas_cardstate, int_in_wq);
+       struct urb *urb = ucs->urb_int_in;
+       struct cardstate *cs = urb->context;
+       int rc;
+
+       /* clear halt condition */
+       rc = usb_clear_halt(ucs->udev, urb->pipe);
+       gig_dbg(DEBUG_USBREQ, "clear_halt: %s", get_usb_rcmsg(rc));
+       if (rc == 0)
+               /* success, resubmit interrupt read URB */
+               rc = usb_submit_urb(urb, GFP_ATOMIC);
+       if (rc != 0 && rc != -ENODEV) {
+               dev_err(cs->dev, "clear halt failed: %s\n", get_usb_rcmsg(rc));
+               rc = usb_lock_device_for_reset(ucs->udev, ucs->interface);
+               if (rc == 0) {
+                       rc = usb_reset_device(ucs->udev);
+                       usb_unlock_device(ucs->udev);
+               }
+       }
+       ucs->retry_int_in = 0;
+}
+
+/* int_in_resubmit
+ * timer routine for interrupt read delayed resubmit
+ * argument:
+ *     controller state structure
+ */
+static void int_in_resubmit(unsigned long data)
+{
+       struct cardstate *cs = (struct cardstate *) data;
+       struct bas_cardstate *ucs = cs->hw.bas;
+       int rc;
+
+       if (ucs->retry_int_in++ >= BAS_RETRY) {
+               dev_err(cs->dev, "interrupt read: giving up after %d tries\n",
+                       ucs->retry_int_in);
+               usb_queue_reset_device(ucs->interface);
+               return;
+       }
+
+       gig_dbg(DEBUG_USBREQ, "%s: retry %d", __func__, ucs->retry_int_in);
+       rc = usb_submit_urb(ucs->urb_int_in, GFP_ATOMIC);
+       if (rc != 0 && rc != -ENODEV) {
+               dev_err(cs->dev, "could not resubmit interrupt URB: %s\n",
+                       get_usb_rcmsg(rc));
+               usb_queue_reset_device(ucs->interface);
+       }
+}
+
 /* read_int_callback
  * USB completion handler for interrupt pipe input
  * called by the USB subsystem in interrupt context
@@ -633,19 +674,29 @@ static void read_int_callback(struct urb *urb)
 
        switch (status) {
        case 0:                 /* success */
+               ucs->retry_int_in = 0;
                break;
+       case -EPIPE:                    /* endpoint stalled */
+               schedule_work(&ucs->int_in_wq);
+               /* fall through */
        case -ENOENT:                   /* cancelled */
        case -ECONNRESET:               /* cancelled (async) */
        case -EINPROGRESS:              /* pending */
-               /* ignore silently */
+       case -ENODEV:                   /* device removed */
+       case -ESHUTDOWN:                /* device shut down */
+               /* no further action necessary */
                gig_dbg(DEBUG_USBREQ, "%s: %s",
                        __func__, get_usb_statmsg(status));
                return;
-       case -ENODEV:                   /* device removed */
-       case -ESHUTDOWN:                /* device shut down */
-               gig_dbg(DEBUG_USBREQ, "%s: device disconnected", __func__);
+       case -EPROTO:                   /* protocol error or unplug */
+       case -EILSEQ:
+       case -ETIME:
+               /* resubmit after delay */
+               gig_dbg(DEBUG_USBREQ, "%s: %s",
+                       __func__, get_usb_statmsg(status));
+               mod_timer(&ucs->timer_int_in, jiffies + HZ / 10);
                return;
-       default:                /* severe trouble */
+       default:                /* other errors: just resubmit */
                dev_warn(cs->dev, "interrupt read: %s\n",
                         get_usb_statmsg(status));
                goto resubmit;
@@ -723,6 +774,13 @@ static void read_int_callback(struct urb *urb)
                        break;
                }
                spin_lock_irqsave(&cs->lock, flags);
+               if (ucs->basstate & BS_ATRDPEND) {
+                       spin_unlock_irqrestore(&cs->lock, flags);
+                       dev_warn(cs->dev,
+       "HD_RECEIVEATDATA_ACK(%d) during HD_READ_ATMESSAGE(%d) ignored\n",
+                                l, ucs->rcvbuf_size);
+                       break;
+               }
                if (ucs->rcvbuf_size) {
                        /* throw away previous buffer - we have no queue */
                        dev_err(cs->dev,
@@ -735,7 +793,6 @@ static void read_int_callback(struct urb *urb)
                if (ucs->rcvbuf == NULL) {
                        spin_unlock_irqrestore(&cs->lock, flags);
                        dev_err(cs->dev, "out of memory receiving AT data\n");
-                       error_reset(cs);
                        break;
                }
                ucs->rcvbuf_size = l;
@@ -745,13 +802,10 @@ static void read_int_callback(struct urb *urb)
                        kfree(ucs->rcvbuf);
                        ucs->rcvbuf = NULL;
                        ucs->rcvbuf_size = 0;
-                       if (rc != -ENODEV) {
-                               spin_unlock_irqrestore(&cs->lock, flags);
-                               error_reset(cs);
-                               break;
-                       }
                }
                spin_unlock_irqrestore(&cs->lock, flags);
+               if (rc < 0 && rc != -ENODEV)
+                       error_reset(cs);
                break;
 
        case HD_RESET_INTERRUPT_PIPE_ACK:
@@ -818,6 +872,7 @@ static void read_iso_callback(struct urb *urb)
                tasklet_hi_schedule(&ubc->rcvd_tasklet);
        } else {
                /* tasklet still busy, drop data and resubmit URB */
+               gig_dbg(DEBUG_ISO, "%s: overrun", __func__);
                ubc->loststatus = status;
                for (i = 0; i < BAS_NUMFRAMES; i++) {
                        ubc->isoinlost += urb->iso_frame_desc[i].actual_length;
@@ -833,13 +888,11 @@ static void read_iso_callback(struct urb *urb)
                        urb->dev = bcs->cs->hw.bas->udev;
                        urb->transfer_flags = URB_ISO_ASAP;
                        urb->number_of_packets = BAS_NUMFRAMES;
-                       gig_dbg(DEBUG_ISO, "%s: isoc read overrun/resubmit",
-                               __func__);
                        rc = usb_submit_urb(urb, GFP_ATOMIC);
                        if (unlikely(rc != 0 && rc != -ENODEV)) {
                                dev_err(bcs->cs->dev,
-                                       "could not resubmit isochronous read "
-                                       "URB: %s\n", get_usb_rcmsg(rc));
+                                      "could not resubmit isoc read URB: %s\n",
+                                       get_usb_rcmsg(rc));
                                dump_urb(DEBUG_ISO, "isoc read", urb);
                                error_hangup(bcs);
                        }
@@ -1081,7 +1134,7 @@ static int submit_iso_write_urb(struct isow_urbctx_t *ucx)
                        gig_dbg(DEBUG_ISO, "%s: disconnected", __func__);
                else
                        dev_err(ucx->bcs->cs->dev,
-                               "could not submit isochronous write URB: %s\n",
+                               "could not submit isoc write URB: %s\n",
                                get_usb_rcmsg(rc));
                return rc;
        }
@@ -1126,7 +1179,7 @@ static void write_iso_tasklet(unsigned long data)
                ubc->isooutovfl = NULL;
                spin_unlock_irqrestore(&ubc->isooutlock, flags);
                if (ovfl) {
-                       dev_err(cs->dev, "isochronous write buffer underrun\n");
+                       dev_err(cs->dev, "isoc write underrun\n");
                        error_hangup(bcs);
                        break;
                }
@@ -1151,7 +1204,7 @@ static void write_iso_tasklet(unsigned long data)
                                if (next) {
                                        /* couldn't put it back */
                                        dev_err(cs->dev,
-                                             "losing isochronous write URB\n");
+                                               "losing isoc write URB\n");
                                        error_hangup(bcs);
                                }
                        }
@@ -1178,10 +1231,10 @@ static void write_iso_tasklet(unsigned long data)
                                if (ifd->status ||
                                    ifd->actual_length != ifd->length) {
                                        dev_warn(cs->dev,
-                                            "isochronous write: frame %d: %s, "
-                                            "only %d of %d bytes sent\n",
-                                            i, get_usb_statmsg(ifd->status),
-                                            ifd->actual_length, ifd->length);
+                                           "isoc write: frame %d[%d/%d]: %s\n",
+                                                i, ifd->actual_length,
+                                                ifd->length,
+                                                get_usb_statmsg(ifd->status));
                                        offset = (ifd->offset +
                                                  ifd->actual_length)
                                                 % BAS_OUTBUFSIZE;
@@ -1190,11 +1243,11 @@ static void write_iso_tasklet(unsigned long data)
                        }
                        break;
                case -EPIPE:                    /* stall - probably underrun */
-                       dev_err(cs->dev, "isochronous write stalled\n");
+                       dev_err(cs->dev, "isoc write: stalled\n");
                        error_hangup(bcs);
                        break;
-               default:                        /* severe trouble */
-                       dev_warn(cs->dev, "isochronous write: %s\n",
+               default:                        /* other errors */
+                       dev_warn(cs->dev, "isoc write: %s\n",
                                 get_usb_statmsg(status));
                }
 
@@ -1250,6 +1303,7 @@ static void read_iso_tasklet(unsigned long data)
        struct cardstate *cs = bcs->cs;
        struct urb *urb;
        int status;
+       struct usb_iso_packet_descriptor *ifd;
        char *rcvbuf;
        unsigned long flags;
        int totleft, numbytes, offset, frame, rc;
@@ -1267,8 +1321,7 @@ static void read_iso_tasklet(unsigned long data)
                ubc->isoindone = NULL;
                if (unlikely(ubc->loststatus != -EINPROGRESS)) {
                        dev_warn(cs->dev,
-                                "isochronous read overrun, "
-                                "dropped URB with status: %s, %d bytes lost\n",
+               "isoc read overrun, URB dropped (status: %s, %d bytes)\n",
                                 get_usb_statmsg(ubc->loststatus),
                                 ubc->isoinlost);
                        ubc->loststatus = -EINPROGRESS;
@@ -1298,11 +1351,11 @@ static void read_iso_tasklet(unsigned long data)
                                __func__, get_usb_statmsg(status));
                        continue;               /* -> skip */
                case -EPIPE:
-                       dev_err(cs->dev, "isochronous read stalled\n");
+                       dev_err(cs->dev, "isoc read: stalled\n");
                        error_hangup(bcs);
                        continue;               /* -> skip */
-               default:                        /* severe trouble */
-                       dev_warn(cs->dev, "isochronous read: %s\n",
+               default:                        /* other error */
+                       dev_warn(cs->dev, "isoc read: %s\n",
                                 get_usb_statmsg(status));
                        goto error;
                }
@@ -1310,40 +1363,52 @@ static void read_iso_tasklet(unsigned long data)
                rcvbuf = urb->transfer_buffer;
                totleft = urb->actual_length;
                for (frame = 0; totleft > 0 && frame < BAS_NUMFRAMES; frame++) {
-                       numbytes = urb->iso_frame_desc[frame].actual_length;
-                       if (unlikely(urb->iso_frame_desc[frame].status))
+                       ifd = &urb->iso_frame_desc[frame];
+                       numbytes = ifd->actual_length;
+                       switch (ifd->status) {
+                       case 0:                 /* success */
+                               break;
+                       case -EPROTO:           /* protocol error or unplug */
+                       case -EILSEQ:
+                       case -ETIME:
+                               /* probably just disconnected, ignore */
+                               gig_dbg(DEBUG_ISO,
+                                       "isoc read: frame %d[%d]: %s\n",
+                                       frame, numbytes,
+                                       get_usb_statmsg(ifd->status));
+                               break;
+                       default:                /* other error */
+                               /* report, assume transferred bytes are ok */
                                dev_warn(cs->dev,
-                                        "isochronous read: frame %d[%d]: %s\n",
+                                        "isoc read: frame %d[%d]: %s\n",
                                         frame, numbytes,
-                                        get_usb_statmsg(
-                                           urb->iso_frame_desc[frame].status));
+                                        get_usb_statmsg(ifd->status));
+                       }
                        if (unlikely(numbytes > BAS_MAXFRAME))
                                dev_warn(cs->dev,
-                                        "isochronous read: frame %d: "
-                                        "numbytes (%d) > BAS_MAXFRAME\n",
-                                        frame, numbytes);
+                                        "isoc read: frame %d[%d]: %s\n",
+                                        frame, numbytes,
+                                        "exceeds max frame size");
                        if (unlikely(numbytes > totleft)) {
                                dev_warn(cs->dev,
-                                        "isochronous read: frame %d: "
-                                        "numbytes (%d) > totleft (%d)\n",
-                                        frame, numbytes, totleft);
+                                        "isoc read: frame %d[%d]: %s\n",
+                                        frame, numbytes,
+                                        "exceeds total transfer length");
                                numbytes = totleft;
                        }
-                       offset = urb->iso_frame_desc[frame].offset;
+                       offset = ifd->offset;
                        if (unlikely(offset + numbytes > BAS_INBUFSIZE)) {
                                dev_warn(cs->dev,
-                                        "isochronous read: frame %d: "
-                                        "offset (%d) + numbytes (%d) "
-                                        "> BAS_INBUFSIZE\n",
-                                        frame, offset, numbytes);
+                                        "isoc read: frame %d[%d]: %s\n",
+                                        frame, numbytes,
+                                        "exceeds end of buffer");
                                numbytes = BAS_INBUFSIZE - offset;
                        }
                        gigaset_isoc_receive(rcvbuf + offset, numbytes, bcs);
                        totleft -= numbytes;
                }
                if (unlikely(totleft > 0))
-                       dev_warn(cs->dev,
-                                "isochronous read: %d data bytes missing\n",
+                       dev_warn(cs->dev, "isoc read: %d data bytes missing\n",
                                 totleft);
 
 error:
@@ -1359,9 +1424,9 @@ error:
                rc = usb_submit_urb(urb, GFP_ATOMIC);
                if (unlikely(rc != 0 && rc != -ENODEV)) {
                        dev_err(cs->dev,
-                               "could not resubmit isochronous read URB: %s\n",
+                               "could not resubmit isoc read URB: %s\n",
                                get_usb_rcmsg(rc));
-                       dump_urb(DEBUG_ISO, "resubmit iso read", urb);
+                       dump_urb(DEBUG_ISO, "resubmit isoc read", urb);
                        error_hangup(bcs);
                }
        }
@@ -1373,12 +1438,12 @@ error:
 /* req_timeout
  * timeout routine for control output request
  * argument:
- *     B channel control structure
+ *     controller state structure
  */
 static void req_timeout(unsigned long data)
 {
-       struct bc_state *bcs = (struct bc_state *) data;
-       struct bas_cardstate *ucs = bcs->cs->hw.bas;
+       struct cardstate *cs = (struct cardstate *) data;
+       struct bas_cardstate *ucs = cs->hw.bas;
        int pending;
        unsigned long flags;
 
@@ -1395,38 +1460,44 @@ static void req_timeout(unsigned long data)
                break;
 
        case HD_OPEN_ATCHANNEL:
-               dev_err(bcs->cs->dev, "timeout opening AT channel\n");
-               error_reset(bcs->cs);
+               dev_err(cs->dev, "timeout opening AT channel\n");
+               error_reset(cs);
                break;
 
-       case HD_OPEN_B2CHANNEL:
        case HD_OPEN_B1CHANNEL:
-               dev_err(bcs->cs->dev, "timeout opening channel %d\n",
-                       bcs->channel + 1);
-               error_hangup(bcs);
+               dev_err(cs->dev, "timeout opening channel 1\n");
+               error_hangup(&cs->bcs[0]);
+               break;
+
+       case HD_OPEN_B2CHANNEL:
+               dev_err(cs->dev, "timeout opening channel 2\n");
+               error_hangup(&cs->bcs[1]);
                break;
 
        case HD_CLOSE_ATCHANNEL:
-               dev_err(bcs->cs->dev, "timeout closing AT channel\n");
-               error_reset(bcs->cs);
+               dev_err(cs->dev, "timeout closing AT channel\n");
+               error_reset(cs);
                break;
 
-       case HD_CLOSE_B2CHANNEL:
        case HD_CLOSE_B1CHANNEL:
-               dev_err(bcs->cs->dev, "timeout closing channel %d\n",
-                       bcs->channel + 1);
-               error_reset(bcs->cs);
+               dev_err(cs->dev, "timeout closing channel 1\n");
+               error_reset(cs);
+               break;
+
+       case HD_CLOSE_B2CHANNEL:
+               dev_err(cs->dev, "timeout closing channel 2\n");
+               error_reset(cs);
                break;
 
        case HD_RESET_INTERRUPT_PIPE:
                /* error recovery escalation */
-               dev_err(bcs->cs->dev,
+               dev_err(cs->dev,
                        "reset interrupt pipe timeout, attempting USB reset\n");
-               usb_queue_reset_device(bcs->cs->hw.bas->interface);
+               usb_queue_reset_device(ucs->interface);
                break;
 
        default:
-               dev_warn(bcs->cs->dev, "request 0x%02x timed out, clearing\n",
+               dev_warn(cs->dev, "request 0x%02x timed out, clearing\n",
                         pending);
        }
 
@@ -1557,10 +1628,7 @@ static int req_submit(struct bc_state *bcs, int req, int val, int timeout)
 
        if (timeout > 0) {
                gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
-               ucs->timer_ctrl.expires = jiffies + timeout * HZ / 10;
-               ucs->timer_ctrl.data = (unsigned long) bcs;
-               ucs->timer_ctrl.function = req_timeout;
-               add_timer(&ucs->timer_ctrl);
+               mod_timer(&ucs->timer_ctrl, jiffies + timeout * HZ / 10);
        }
 
        spin_unlock_irqrestore(&ucs->lock, flags);
@@ -1590,21 +1658,20 @@ static int gigaset_init_bchannel(struct bc_state *bcs)
 
        if (cs->hw.bas->basstate & BS_SUSPEND) {
                dev_notice(cs->dev,
-                          "not starting isochronous I/O, "
-                          "suspend in progress\n");
+                          "not starting isoc I/O, suspend in progress\n");
                spin_unlock_irqrestore(&cs->lock, flags);
                return -EHOSTUNREACH;
        }
 
        ret = starturbs(bcs);
        if (ret < 0) {
+               spin_unlock_irqrestore(&cs->lock, flags);
                dev_err(cs->dev,
-                       "could not start isochronous I/O for channel B%d: %s\n",
+                       "could not start isoc I/O for channel B%d: %s\n",
                        bcs->channel + 1,
                        ret == -EFAULT ? "null URB" : get_usb_rcmsg(ret));
                if (ret != -ENODEV)
                        error_hangup(bcs);
-               spin_unlock_irqrestore(&cs->lock, flags);
                return ret;
        }
 
@@ -1614,11 +1681,11 @@ static int gigaset_init_bchannel(struct bc_state *bcs)
                dev_err(cs->dev, "could not open channel B%d\n",
                        bcs->channel + 1);
                stopurbs(bcs->hw.bas);
-               if (ret != -ENODEV)
-                       error_hangup(bcs);
        }
 
        spin_unlock_irqrestore(&cs->lock, flags);
+       if (ret < 0 && ret != -ENODEV)
+               error_hangup(bcs);
        return ret;
 }
 
@@ -1826,10 +1893,7 @@ static int atwrite_submit(struct cardstate *cs, unsigned char *buf, int len)
        if (!(update_basstate(ucs, BS_ATTIMER, BS_ATREADY) & BS_ATTIMER)) {
                gig_dbg(DEBUG_OUTPUT, "setting ATREADY timeout of %d/10 secs",
                        ATRDY_TIMEOUT);
-               ucs->timer_atrdy.expires = jiffies + ATRDY_TIMEOUT * HZ / 10;
-               ucs->timer_atrdy.data = (unsigned long) cs;
-               ucs->timer_atrdy.function = atrdy_timeout;
-               add_timer(&ucs->timer_atrdy);
+               mod_timer(&ucs->timer_atrdy, jiffies + ATRDY_TIMEOUT * HZ / 10);
        }
        return 0;
 }
@@ -1914,6 +1978,28 @@ static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb)
         * The next command will reopen the AT channel automatically.
         */
        if (cb->len == 3 && !memcmp(cb->buf, "+++", 3)) {
+               /* If an HD_RECEIVEATDATA_ACK message remains unhandled
+                * because of an error, the base never sends another one.
+                * The response channel is thus effectively blocked.
+                * Closing and reopening the AT channel does *not* clear
+                * this condition.
+                * As a stopgap measure, submit a zero-length AT read
+                * before closing the AT channel. This has the undocumented
+                * effect of triggering a new HD_RECEIVEATDATA_ACK message
+                * from the base if necessary.
+                * The subsequent AT channel close then discards any pending
+                * messages.
+                */
+               spin_lock_irqsave(&cs->lock, flags);
+               if (!(cs->hw.bas->basstate & BS_ATRDPEND)) {
+                       kfree(cs->hw.bas->rcvbuf);
+                       cs->hw.bas->rcvbuf = NULL;
+                       cs->hw.bas->rcvbuf_size = 0;
+                       cs->hw.bas->retry_cmd_in = 0;
+                       atread_submit(cs, 0);
+               }
+               spin_unlock_irqrestore(&cs->lock, flags);
+
                rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, BAS_TIMEOUT);
                if (cb->wake_tasklet)
                        tasklet_schedule(cb->wake_tasklet);
@@ -2010,7 +2096,7 @@ static int gigaset_freebcshw(struct bc_state *bcs)
 
        /* kill URBs and tasklets before freeing - better safe than sorry */
        ubc->running = 0;
-       gig_dbg(DEBUG_INIT, "%s: killing iso URBs", __func__);
+       gig_dbg(DEBUG_INIT, "%s: killing isoc URBs", __func__);
        for (i = 0; i < BAS_OUTURBS; ++i) {
                usb_kill_urb(ubc->isoouturbs[i].urb);
                usb_free_urb(ubc->isoouturbs[i].urb);
@@ -2131,10 +2217,12 @@ static int gigaset_initcshw(struct cardstate *cs)
        ucs->pending = 0;
 
        ucs->basstate = 0;
-       init_timer(&ucs->timer_ctrl);
-       init_timer(&ucs->timer_atrdy);
-       init_timer(&ucs->timer_cmd_in);
+       setup_timer(&ucs->timer_ctrl, req_timeout, (unsigned long) cs);
+       setup_timer(&ucs->timer_atrdy, atrdy_timeout, (unsigned long) cs);
+       setup_timer(&ucs->timer_cmd_in, cmd_in_timeout, (unsigned long) cs);
+       setup_timer(&ucs->timer_int_in, int_in_resubmit, (unsigned long) cs);
        init_waitqueue_head(&ucs->waitqueue);
+       INIT_WORK(&ucs->int_in_wq, int_in_work);
 
        return 1;
 }
@@ -2282,6 +2370,7 @@ static int gigaset_probe(struct usb_interface *interface,
                        get_usb_rcmsg(rc));
                goto error;
        }
+       ucs->retry_int_in = 0;
 
        /* tell the device that the driver is ready */
        rc = req_submit(cs->bcs, HD_DEVICE_INIT_ACK, 0, 0);
@@ -2334,10 +2423,12 @@ static void gigaset_disconnect(struct usb_interface *interface)
        /* stop driver (common part) */
        gigaset_stop(cs);
 
-       /* stop timers and URBs, free ressources */
+       /* stop delayed work and URBs, free ressources */
        del_timer_sync(&ucs->timer_ctrl);
        del_timer_sync(&ucs->timer_atrdy);
        del_timer_sync(&ucs->timer_cmd_in);
+       del_timer_sync(&ucs->timer_int_in);
+       cancel_work_sync(&ucs->int_in_wq);
        freeurbs(cs);
        usb_set_intfdata(interface, NULL);
        kfree(ucs->rcvbuf);
@@ -2400,10 +2491,14 @@ static int gigaset_suspend(struct usb_interface *intf, pm_message_t message)
                /* in case of timeout, proceed anyway */
        }
 
-       /* kill all URBs and timers that might still be pending */
+       /* kill all URBs and delayed work that might still be pending */
        usb_kill_urb(ucs->urb_ctrl);
        usb_kill_urb(ucs->urb_int_in);
        del_timer_sync(&ucs->timer_ctrl);
+       del_timer_sync(&ucs->timer_atrdy);
+       del_timer_sync(&ucs->timer_cmd_in);
+       del_timer_sync(&ucs->timer_int_in);
+       cancel_work_sync(&ucs->int_in_wq);
 
        gig_dbg(DEBUG_SUSPEND, "suspend complete");
        return 0;
@@ -2425,6 +2520,7 @@ static int gigaset_resume(struct usb_interface *intf)
                        get_usb_rcmsg(rc));
                return rc;
        }
+       ucs->retry_int_in = 0;
 
        /* clear suspend flag to reallow activity */
        update_basstate(ucs, 0, BS_SUSPEND);