isdn/gigaset: unclog bas_gigaset AT response pipe
authorTilman Schmidt <tilman@imap.cc>
Thu, 30 Sep 2010 13:35:31 +0000 (13:35 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 1 Oct 2010 07:33:36 +0000 (00:33 -0700)
Recover from a lost HD_RECEIVEATDATA_ACK message by sending a
zero-length HD_READ_ATMESSAGE command when ev_layer sends "+++".

Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/isdn/gigaset/bas-gigaset.c

index 7520bc6..540f6d0 100644 (file)
@@ -1896,6 +1896,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);