[rft]fix for usb_clear_halt() in completion handler

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

you cannot use usb_clear_halt() in a completion handler,
as it will sleep. The fix is unfortunately enormous. Please test.

	Regards
		Oliver

--

commit 4df7424df7e99b6ab16c8219e8d17c15cc81d168
Author: Oliver Neukum <oliver@xxxxxxxxxx>
Date:   Thu Oct 15 18:04:40 2009 +0200

    usb:usbserial:cypress_m8: no sleeping in interrupt
    
    cypress_m8 must not use usb_clear_halt() in interrupt
    the fix is to use an URB which requires large changes

diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c
index a591ebe..6aa799a 100644
--- a/drivers/usb/serial/cypress_m8.c
+++ b/drivers/usb/serial/cypress_m8.c
@@ -138,6 +138,8 @@ struct cypress_private {
 	int cmd_count;			   /* used for statistics */
 	int cmd_ctrl;			   /* always set this to 1 before issuing a command */
 	struct cypress_buf *buf;	   /* write buffer */
+	struct usb_ctrlrequest *dr;	   /* for clearing halts */
+	struct urb *clear_urb;		   /* for clearing halts */
 	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 */
@@ -189,6 +191,7 @@ static int  cypress_tiocmset(struct tty_struct *tty, struct file *file,
 static int  cypress_chars_in_buffer(struct tty_struct *tty);
 static void cypress_throttle(struct tty_struct *tty);
 static void cypress_unthrottle(struct tty_struct *tty);
+static void cypress_disconnect(struct usb_serial *serial);
 static void cypress_set_dead(struct usb_serial_port *port);
 static void cypress_read_int_callback(struct urb *urb);
 static void cypress_write_int_callback(struct urb *urb);
@@ -215,6 +218,7 @@ static struct usb_serial_driver cypress_earthmate_device = {
 	.num_ports =			1,
 	.attach =			cypress_earthmate_startup,
 	.release =			cypress_release,
+	.disconnect =			cypress_disconnect,
 	.open =				cypress_open,
 	.close =			cypress_close,
 	.dtr_rts =			cypress_dtr_rts,
@@ -242,6 +246,7 @@ static struct usb_serial_driver cypress_hidcom_device = {
 	.num_ports =			1,
 	.attach =			cypress_hidcom_startup,
 	.release =			cypress_release,
+	.disconnect =			cypress_disconnect,
 	.open =				cypress_open,
 	.close =			cypress_close,
 	.dtr_rts =			cypress_dtr_rts,
@@ -269,6 +274,7 @@ static struct usb_serial_driver cypress_ca42v2_device = {
 	.num_ports =			1,
 	.attach =			cypress_ca42v2_startup,
 	.release =			cypress_release,
+	.disconnect =			cypress_disconnect,
 	.open =				cypress_open,
 	.close =			cypress_close,
 	.dtr_rts =			cypress_dtr_rts,
@@ -503,12 +509,18 @@ static int generic_startup(struct usb_serial *serial)
 	priv->comm_is_ok = !0;
 	spin_lock_init(&priv->lock);
 	priv->buf = cypress_buf_alloc(CYPRESS_BUF_SIZE);
-	if (priv->buf == NULL) {
-		kfree(priv);
-		return -ENOMEM;
-	}
+	if (priv->buf == NULL)
+		goto error;
 	init_waitqueue_head(&priv->delta_msr_wait);
 
+	priv->dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+	if (!priv->dr)
+		goto error;
+
+	priv->clear_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!priv->clear_urb)
+		goto error;
+
 	usb_reset_configuration(serial->dev);
 
 	priv->cmd_ctrl = 0;
@@ -541,6 +553,12 @@ static int generic_startup(struct usb_serial *serial)
 	usb_set_serial_port_data(port, priv);
 
 	return 0;
+
+error:
+	kfree(priv->dr);
+	kfree(priv->buf);
+	kfree(priv);
+	return -ENOMEM;
 }
 
 
@@ -627,6 +645,8 @@ static void cypress_release(struct usb_serial *serial)
 
 	if (priv) {
 		cypress_buf_free(priv->buf);
+		kfree(priv->dr);
+		usb_free_urb(priv->clear_urb);
 		kfree(priv);
 	}
 }
@@ -715,8 +735,12 @@ static void cypress_close(struct usb_serial_port *port)
 	}
 	cypress_buf_clear(priv->buf);
 	dbg("%s - stopping urbs", __func__);
-	usb_kill_urb(port->interrupt_in_urb);
-	usb_kill_urb(port->interrupt_out_urb);
+	usb_poison_urb(priv->clear_urb);
+	usb_poison_urb(port->interrupt_in_urb);
+	usb_poison_urb(port->interrupt_out_urb);
+	usb_unpoison_urb(priv->clear_urb);
+	usb_unpoison_urb(port->interrupt_in_urb);
+	usb_unpoison_urb(port->interrupt_out_urb);
 
 
 	if (stats)
@@ -1192,6 +1216,19 @@ static void cypress_unthrottle(struct tty_struct *tty)
 	}
 }
 
+static void cypress_disconnect(struct usb_serial *serial)
+{
+	struct usb_serial_port *port = serial->port[0];
+	struct cypress_private *priv = usb_get_serial_port_data(port);
+
+	usb_poison_urb(priv->clear_urb);
+	usb_poison_urb(port->interrupt_in_urb);
+	usb_poison_urb(port->interrupt_out_urb);
+	usb_unpoison_urb(priv->clear_urb);
+	usb_unpoison_urb(port->interrupt_in_urb);
+	usb_unpoison_urb(port->interrupt_out_urb);	
+}
+
 
 static void cypress_read_int_callback(struct urb *urb)
 {
@@ -1336,7 +1373,7 @@ continue_read:
 				cypress_read_int_callback, port,
 				priv->read_urb_interval);
 		result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
-		if (result) {
+		if (result && result != -EPERM) {
 			dev_err(&urb->dev->dev, "%s - failed resubmitting "
 					"read urb, error %d\n", __func__,
 					result);
@@ -1347,6 +1384,29 @@ continue_read:
 	return;
 } /* cypress_read_int_callback */
 
+static void cypress_clear_halt_callback(struct urb *urb)
+{
+	struct usb_serial_port *port = urb->context;
+	int status = urb->status;
+	int result;
+
+	if (status) {
+		dbg("%s - clearing halt with status: %d",
+						__func__, status);
+		cypress_set_dead(port);
+		return;
+	}
+
+	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 == 0 || result == -EPERM)
+		return;
+	dev_err(&urb->dev->dev,
+		"%s - failed resubmitting write urb, error %d\n",
+						__func__, result);
+	cypress_set_dead(port);
+}
 
 static void cypress_write_int_callback(struct urb *urb)
 {
@@ -1372,20 +1432,28 @@ static void cypress_write_int_callback(struct urb *urb)
 	case -EPIPE: /* no break needed; clear halt and resubmit */
 		if (!priv->comm_is_ok)
 			break;
-		usb_clear_halt(port->serial->dev, 0x02);
+
+		priv->dr->bRequestType = USB_RECIP_ENDPOINT;
+		priv->dr->bRequest = USB_REQ_CLEAR_FEATURE;
+		priv->dr->wValue = cpu_to_le16(USB_ENDPOINT_HALT);
+		priv->dr->wIndex = cpu_to_le16(0x02);
+		priv->dr->wLength = cpu_to_le16(0);
+
+		usb_fill_control_urb(priv->clear_urb,
+					urb->dev,
+					usb_sndctrlpipe(urb->dev, 0),
+					(unsigned char *)priv->dr,
+					NULL, 0,
+					cypress_clear_halt_callback,
+					NULL);
+		result = usb_submit_urb(priv->clear_urb, GFP_ATOMIC);
+
 		/* error in the urb, so we have to resubmit it */
 		dbg("%s - nonzero write bulk status received: %d",
 			__func__, 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)
-			return;
-		dev_err(&urb->dev->dev,
-			"%s - failed resubmitting write urb, error %d\n",
-							__func__, result);
-		cypress_set_dead(port);
-		break;
+		if (result && result != -EPERM)
+			cypress_set_dead(port);
+		return;
 	default:
 		dev_err(&urb->dev->dev,
 			 "%s - unexpected nonzero write status received: %d\n",

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux