isdn/gigaset: fix bas_gigaset interrupt read error handling

Rework the handling of USB errors in interrupt input reads
to clear halts correctly, delay URB resubmission after errors,
limit retries, and improve error recovery.

Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Tilman Schmidt 2010-09-30 13:35:42 +00:00 committed by David S. Miller
parent 4cb5e42f61
commit c9c0c30434
1 changed files with 93 additions and 13 deletions

View 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_*) */
@ -595,6 +598,62 @@ static int atread_submit(struct cardstate *cs, int timeout)
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
@ -615,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;
@ -705,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,
@ -717,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;
@ -727,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:
@ -2138,7 +2210,9 @@ static int gigaset_initcshw(struct cardstate *cs)
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;
}
@ -2286,6 +2360,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);
@ -2338,10 +2413,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);
@ -2404,12 +2481,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;
@ -2431,6 +2510,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);