Skip to content

Commit

Permalink
cypress_m8: implement graceful failure handling
Browse files Browse the repository at this point in the history
When receiving a fatal error from the USB core, e.g. EILSEQ (which can
happen if the polling interval is too short), fail gracefully.
Previously the driver would fill the log with useless error messages
or (more alarmingly) silently spin forever trying to write updated
control information to the device.  This change implements a new flag
which if cleared indicates that the driver has failed.  The flag will
be set on initialization, cleared on fatal errors, and anything else
that touches the USB port in the driver will abort if the flag is
clear.  When the flag is cleared, a message will be logged indicating
that the driver has failed.

Signed-off-by: Mike Isely <isely@pobox.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Mike Isely authored and Greg Kroah-Hartman committed Sep 27, 2006
1 parent 48298e5 commit 78aef51
Showing 1 changed file with 75 additions and 15 deletions.
90 changes: 75 additions & 15 deletions drivers/usb/serial/cypress_m8.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ struct cypress_private {
int write_urb_in_use; /* write urb in use indicator */
int write_urb_interval; /* interval to use for write urb */
int read_urb_interval; /* interval to use for read urb */
int comm_is_ok; /* true if communication is (still) ok */
int termios_initialized;
__u8 line_control; /* holds dtr / rts value */
__u8 current_status; /* received from last read - info on dsr,cts,cd,ri,etc */
Expand Down Expand Up @@ -170,6 +171,7 @@ static int cypress_tiocmset (struct usb_serial_port *port, struct file *file,
static int cypress_chars_in_buffer (struct usb_serial_port *port);
static void cypress_throttle (struct usb_serial_port *port);
static void cypress_unthrottle (struct usb_serial_port *port);
static void cypress_set_dead (struct usb_serial_port *port);
static void cypress_read_int_callback (struct urb *urb, struct pt_regs *regs);
static void cypress_write_int_callback (struct urb *urb, struct pt_regs *regs);
/* baud helper functions */
Expand Down Expand Up @@ -290,6 +292,9 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m

priv = usb_get_serial_port_data(port);

if (!priv->comm_is_ok)
return -ENODEV;

switch(cypress_request_type) {
case CYPRESS_SET_CONFIG:

Expand Down Expand Up @@ -369,9 +374,10 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m

} while (retval != 8 && retval != -ENODEV);

if (retval != 8)
if (retval != 8) {
err("%s - failed sending serial line settings - %d", __FUNCTION__, retval);
else {
cypress_set_dead(port);
} else {
spin_lock_irqsave(&priv->lock, flags);
priv->baud_rate = new_baudrate;
priv->cbr_mask = baud_mask;
Expand All @@ -396,6 +402,7 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m

if (retval != 5) {
err("%s - failed to retrieve serial line settings - %d", __FUNCTION__, retval);
cypress_set_dead(port);
return retval;
} else {
spin_lock_irqsave(&priv->lock, flags);
Expand All @@ -417,6 +424,24 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m
} /* cypress_serial_control */


static void cypress_set_dead(struct usb_serial_port *port)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
unsigned long flags;

spin_lock_irqsave(&priv->lock, flags);
if (!priv->comm_is_ok) {
spin_unlock_irqrestore(&priv->lock, flags);
return;
}
priv->comm_is_ok = 0;
spin_unlock_irqrestore(&priv->lock, flags);

err("cypress_m8 suspending failing port %d - interval might be too short",
port->number);
}


/* given a baud mask, it will return integer baud on success */
static int mask_to_rate (unsigned mask)
{
Expand Down Expand Up @@ -478,6 +503,7 @@ static int generic_startup (struct usb_serial *serial)
if (!priv)
return -ENOMEM;

priv->comm_is_ok = !0;
spin_lock_init(&priv->lock);
priv->buf = cypress_buf_alloc(CYPRESS_BUF_SIZE);
if (priv->buf == NULL) {
Expand Down Expand Up @@ -595,6 +621,9 @@ static int cypress_open (struct usb_serial_port *port, struct file *filp)

dbg("%s - port %d", __FUNCTION__, port->number);

if (!priv->comm_is_ok)
return -EIO;

/* clear halts before open */
usb_clear_halt(serial->dev, 0x81);
usb_clear_halt(serial->dev, 0x02);
Expand Down Expand Up @@ -639,6 +668,7 @@ static int cypress_open (struct usb_serial_port *port, struct file *filp)

if (result){
dev_err(&port->dev, "%s - failed submitting read urb, error %d\n", __FUNCTION__, result);
cypress_set_dead(port);
}

return result;
Expand Down Expand Up @@ -743,6 +773,9 @@ static void cypress_send(struct usb_serial_port *port)
struct cypress_private *priv = usb_get_serial_port_data(port);
unsigned long flags;

if (!priv->comm_is_ok)
return;

dbg("%s - port %d", __FUNCTION__, port->number);
dbg("%s - interrupt out size is %d", __FUNCTION__, port->interrupt_out_size);

Expand Down Expand Up @@ -825,6 +858,7 @@ static void cypress_send(struct usb_serial_port *port)
dev_err(&port->dev, "%s - failed submitting write urb, error %d\n", __FUNCTION__,
result);
priv->write_urb_in_use = 0;
cypress_set_dead(port);
}

spin_lock_irqsave(&priv->lock, flags);
Expand Down Expand Up @@ -1225,13 +1259,18 @@ static void cypress_unthrottle (struct usb_serial_port *port)
priv->rx_flags = 0;
spin_unlock_irqrestore(&priv->lock, flags);

if (!priv->comm_is_ok)
return;

if (actually_throttled) {
port->interrupt_in_urb->dev = port->serial->dev;

result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
if (result)
if (result) {
dev_err(&port->dev, "%s - failed submitting read urb, "
"error %d\n", __FUNCTION__, result);
cypress_set_dead(port);
}
}
}

Expand All @@ -1251,9 +1290,22 @@ static void cypress_read_int_callback(struct urb *urb, struct pt_regs *regs)

dbg("%s - port %d", __FUNCTION__, port->number);

if (urb->status) {
dbg("%s - nonzero read status received: %d", __FUNCTION__,
urb->status);
switch (urb->status) {
case 0: /* success */
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/* precursor to disconnect so just go away */
return;
case -EPIPE:
usb_clear_halt(port->serial->dev,0x81);
break;
default:
/* something ugly is going on... */
dev_err(&urb->dev->dev,"%s - unexpected nonzero read status received: %d\n",
__FUNCTION__,urb->status);
cypress_set_dead(port);
return;
}

Expand Down Expand Up @@ -1354,18 +1406,20 @@ static void cypress_read_int_callback(struct urb *urb, struct pt_regs *regs)

/* Continue trying to always read... unless the port has closed. */

if (port->open_count > 0) {
if (port->open_count > 0 && priv->comm_is_ok) {
usb_fill_int_urb(port->interrupt_in_urb, port->serial->dev,
usb_rcvintpipe(port->serial->dev,
port->interrupt_in_endpointAddress),
port->interrupt_in_urb->transfer_buffer,
port->interrupt_in_urb->transfer_buffer_length,
cypress_read_int_callback, port, priv->read_urb_interval);
result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
if (result)
if (result) {
dev_err(&urb->dev->dev, "%s - failed resubmitting "
"read urb, error %d\n", __FUNCTION__,
result);
cypress_set_dead(port);
}
}

return;
Expand All @@ -1391,20 +1445,26 @@ static void cypress_write_int_callback(struct urb *urb, struct pt_regs *regs)
dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
priv->write_urb_in_use = 0;
return;
case -EPIPE: /* no break needed */
case -EPIPE: /* no break needed; clear halt and resubmit */
if (!priv->comm_is_ok)
break;
usb_clear_halt(port->serial->dev, 0x02);
default:
/* error in the urb, so we have to resubmit it */
dbg("%s - Overflow in write", __FUNCTION__);
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
port->interrupt_out_urb->transfer_buffer_length = 1;
port->interrupt_out_urb->dev = port->serial->dev;
result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC);
if (result)
dev_err(&urb->dev->dev, "%s - failed resubmitting write urb, error %d\n",
__FUNCTION__, result);
else
if (!result)
return;
dev_err(&urb->dev->dev, "%s - failed resubmitting write urb, error %d\n",
__FUNCTION__, result);
cypress_set_dead(port);
break;
default:
dev_err(&urb->dev->dev,"%s - unexpected nonzero write status received: %d\n",
__FUNCTION__,urb->status);
cypress_set_dead(port);
break;
}

priv->write_urb_in_use = 0;
Expand Down

0 comments on commit 78aef51

Please sign in to comment.