Re: requirements for suspend()

Oliver Neukum <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[email protected]>
Am Freitag, 4. Mai 2007 06:47 schrieb Greg KH:
> On Thu, May 03, 2007 at 11:05:54AM -0400, Alan Stern wrote:
> > On Thu, 3 May 2007, Oliver Neukum wrote:
> > 
> > > Am Donnerstag, 3. Mai 2007 16:03 schrieb Alan Stern:
> > > > On Thu, 3 May 2007, Oliver Neukum wrote:
> > > > 
> > > > > > > How about exporting usb_disable_interface() and usb_enable_interface()?
> > > > > > > It doesn't solve the problem for endpoint 0, but this case is rare.
> > > > > > 
> > > > > > We could; it would save drivers the trouble of keeping track of their own 
> > > > > > URBs.  Do you have a particular driver in mind for this?
> > > > > 
> > > > > Everything derived from newer versions of the skeleton driver.
> > > > 
> > > > Would we be better off modifying the skeleton driver to keep track of its 
> > > > own URBs?
> > > 
> > > I am unsure. The "fire-and-forget" technique is certainly simple for
> > > driver writers. Perhaps we should make it easier to keep track of URBs.
> > > A list_head in the URB?
> > 
> > People have discussed adding one in the past.  Greg might have some 
> > opinions.
> 
> My opinion is that we need to ensure that we can still do the
> "fire-and-forget" type driver :)

And this can be done. What do you think:

--- a/include/linux/usb.h	2007-05-04 10:50:18.000000000 +0200
+++ b/include/linux/usb.h	2007-05-04 14:10:49.000000000 +0200
@@ -958,11 +958,23 @@ struct usb_iso_packet_descriptor {
 
 struct urb;
 
+struct usb_anchor {
+	struct list_head urb_list;
+	spinlock_t lock;
+};
+
+static inline void init_usb_anchor(struct usb_anchor *anchor)
+{
+	INIT_LIST_HEAD(&anchor->urb_list);
+	spin_lock_init(&anchor->lock);
+}
+
 typedef void (*usb_complete_t)(struct urb *);
 
 /**
  * struct urb - USB Request Block
  * @urb_list: For use by current owner of the URB.
+ * @anchor: to anchor URBs to a common mooring
  * @pipe: Holds endpoint number, direction, type, and more.
  *	Create these values with the eight macros available;
  *	usb_{snd,rcv}TYPEpipe(dev,endpoint), where the TYPE is "ctrl"
@@ -1135,6 +1147,8 @@ struct urb
 	/* public: documented fields in the urb that can be used by drivers */
 	struct list_head urb_list;	/* list head for use by the urb's
 					 * current owner */
+	struct list_head anchor_list;	/* the URB may be anchored by the driver */
+	struct usb_anchor *anchor;
 	struct usb_device *dev; 	/* (in) pointer to associated device */
 	unsigned int pipe;		/* (in) pipe information */
 	int status;			/* (return) non-ISO status */
@@ -1270,6 +1284,8 @@ extern struct urb *usb_get_urb(struct ur
 extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
 extern int usb_unlink_urb(struct urb *urb);
 extern void usb_kill_urb(struct urb *urb);
+extern void usb_associate_urb_to_anchor(struct urb *u, struct usb_anchor *a);
+extern void usb_kill_associated_urbs(struct usb_anchor *old, struct usb_anchor *new);
 
 void *usb_buffer_alloc (struct usb_device *dev, size_t size,
 	gfp_t mem_flags, dma_addr_t *dma);
--- a/drivers/usb/core/hcd.c	2007-05-04 12:04:29.000000000 +0200
+++ b/drivers/usb/core/hcd.c	2007-05-04 12:16:28.000000000 +0200
@@ -1408,6 +1408,15 @@ void usb_hcd_giveback_urb (struct usb_hc
 	}
 
 	usbmon_urb_complete (&hcd->self, urb);
+	if (urb->anchor) {
+		struct usb_anchor *anchor = urb->anchor;
+
+		spin_lock(&anchor->lock);
+		list_del(&urb->anchor_list);
+		urb->anchor = NULL;
+		spin_unlock(&anchor->lock);
+	}
+
 	/* pass ownership to the completion handler */
 	urb->complete (urb);
 	atomic_dec (&urb->use_count);
--- a/drivers/usb/core/urb.c	2007-05-04 10:53:14.000000000 +0200
+++ b/drivers/usb/core/urb.c	2007-05-04 14:28:29.000000000 +0200
@@ -11,6 +11,14 @@
 static void urb_destroy(struct kref *kref)
 {
 	struct urb *urb = to_urb(kref);
+	unsigned long flags;
+
+	if (urb->anchor) {
+		spin_lock_irqsave(&urb->anchor->lock, flags);
+		list_del(&urb->anchor_list);
+		spin_unlock_irqrestore(&urb->anchor->lock, flags);
+	}
+	
 	kfree(urb);
 }
 
@@ -34,6 +42,7 @@ void usb_init_urb(struct urb *urb)
 		memset(urb, 0, sizeof(*urb));
 		kref_init(&urb->kref);
 		spin_lock_init(&urb->lock);
+		INIT_LIST_HEAD(&urb->anchor_list);
 	}
 }
 
@@ -100,6 +109,25 @@ struct urb * usb_get_urb(struct urb *urb
 		kref_get(&urb->kref);
 	return urb;
 }
+
+/**
+ * usb_associate_urb_to_anchor - anchors an URB while it is processed
+ * @u: pointer to the urb to anchor
+ * @a: pointer to the anchor
+ *
+ * This can be called to have access to URBs which are to be executed
+ * without bothering to track them
+ */
+
+void usb_associate_urb_to_anchor(struct urb *u, struct usb_anchor *a)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&a->lock, flags);
+	list_add_tail(&u->anchor_list, &a->urb_list);
+	u->anchor = a;
+	spin_unlock_irqrestore(&a->lock, flags);
+}
 		
 		
 /*-------------------------------------------------------------------*/
@@ -477,12 +505,45 @@ void usb_kill_urb(struct urb *urb)
 	--urb->reject;
 	spin_unlock_irq(&urb->lock);
 }
+/**
+ * usb_kill_associated_urbs - cancel transfer requests en masse
+ * @old: anchor the requests are bound to
+ * @new: anchor the request which were canceled shall be bound to, may be NULL
+ *
+ * this allows all outstanding URBs to be cancelled and bound to an anchor
+ * for later processing
+ */
+
+void usb_kill_associated_urbs(struct usb_anchor *old, struct usb_anchor *new)
+{
+	unsigned long flags;
+	struct urb *victim;
+
+	spin_lock_irqsave(&old->lock, flags);
+	while (!list_empty(&old->urb_list)) {
+		victim = list_entry(old->urb_list.prev, struct urb, anchor_list);
+		list_del(&victim->anchor_list);
+		/* we must make sure the URB isn't freed before we kill it*/
+		usb_get_urb(victim);
+		spin_unlock_irqrestore(&old->lock, flags);
+		usb_kill_urb(victim);
+		/* we may transfer only if we killed */
+		if (new && victim->status == -ENOENT)
+			list_add(&victim->anchor_list, &new->urb_list);
+		else
+			usb_put_urb(victim);
+		spin_lock_irqsave(&old->lock, flags);
+	}
+	spin_unlock_irqrestore(&old->lock, flags);
+}
 
 EXPORT_SYMBOL(usb_init_urb);
 EXPORT_SYMBOL(usb_alloc_urb);
 EXPORT_SYMBOL(usb_free_urb);
 EXPORT_SYMBOL(usb_get_urb);
+EXPORT_SYMBOL(usb_associate_urb_to_anchor);
 EXPORT_SYMBOL(usb_submit_urb);
 EXPORT_SYMBOL(usb_unlink_urb);
 EXPORT_SYMBOL(usb_kill_urb);
+EXPORT_SYMBOL(usb_kill_associated_urbs);
 
--- a/drivers/usb/usb-skeleton.c	2007-05-04 13:07:30.000000000 +0200
+++ b/drivers/usb/usb-skeleton.c	2007-05-04 14:11:50.000000000 +0200
@@ -54,10 +54,13 @@ struct usb_skel {
 	struct usb_device	*udev;			/* the usb device for this device */
 	struct usb_interface	*interface;		/* the interface for this device */
 	struct semaphore	limit_sem;		/* limiting the number of writes in progress */
+	struct usb_anchor	submitted;		/* in case we need to retract our submissions */
+	struct usb_anchor	redo;			/* to be redone in case of suspend or reset */
 	unsigned char           *bulk_in_buffer;	/* the buffer to receive data */
 	size_t			bulk_in_size;		/* the size of the receive buffer */
 	__u8			bulk_in_endpointAddr;	/* the address of the bulk in endpoint */
 	__u8			bulk_out_endpointAddr;	/* the address of the bulk out endpoint */
+	unsigned char		revoking;		/* this is set while submissions are revoked */
 	struct kref		kref;
 	struct mutex		io_mutex;		/* synchronize I/O with disconnect */
 };
@@ -185,11 +188,17 @@ static void skel_write_bulk_callback(str
 	      urb->status == -ESHUTDOWN)) {
 		err("%s - nonzero write bulk status received: %d",
 		    __FUNCTION__, urb->status);
+		/* the device is to be suspend,
+		   this urb must be redone,
+		   the buffer cannot be freed */
+		if (urb->status == -ENOENT && dev->revoking)
+			goto spare_buffer;
 	}
 
 	/* free up our allocated buffer */
 	usb_buffer_free(urb->dev, urb->transfer_buffer_length,
 			urb->transfer_buffer, urb->transfer_dma);
+spare_buffer:
 	up(&dev->limit_sem);
 }
 
@@ -244,6 +253,7 @@ static ssize_t skel_write(struct file *f
 			  usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
 			  buf, writesize, skel_write_bulk_callback, dev);
 	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	usb_associate_urb_to_anchor(urb, &dev->submitted);
 
 	/* send the data out the bulk port */
 	retval = usb_submit_urb(urb, GFP_KERNEL);
@@ -306,6 +316,8 @@ static int skel_probe(struct usb_interfa
 	kref_init(&dev->kref);
 	sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
 	mutex_init(&dev->io_mutex);
+	init_usb_anchor(&dev->submitted);
+	init_usb_anchor(&dev->redo);
 
 	dev->udev = usb_get_dev(interface_to_usbdev(interface));
 	dev->interface = interface;
@@ -391,10 +403,57 @@ static void skel_disconnect(struct usb_i
 	info("USB Skeleton #%d now disconnected", minor);
 }
 
+static int skel_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct usb_skel *dev= usb_get_intfdata(intf);
+
+	mutex_lock(&dev->io_mutex);
+	dev->revoking = 1;
+	usb_kill_associated_urbs(&dev->submitted, &dev->redo);
+	mutex_unlock(&dev->io_mutex);
+
+	return 0;
+}
+
+static int skel_resume (struct usb_interface *intf)
+{
+	struct usb_skel *dev= usb_get_intfdata(intf);
+	struct urb *lazarus, *n;
+	int r;
+
+	mutex_lock(&dev->io_mutex);
+	dev->revoking = 0;
+	list_for_each_entry_safe(lazarus, n, &dev->redo.urb_list, anchor_list) {
+		list_del(&lazarus->anchor_list);
+		usb_associate_urb_to_anchor(lazarus, &dev->submitted);
+		r = usb_submit_urb(lazarus, GFP_NOIO);
+		if (r < 0) {
+			/* if URB submission fails, it, its buffer and those
+			   after it and their buffers must be freed */
+			err("Could not redo URBs during resume");
+			usb_buffer_free(lazarus->dev, lazarus->transfer_buffer_length,
+			lazarus->transfer_buffer, lazarus->transfer_dma);
+			usb_free_urb(lazarus);
+			list_for_each_entry_safe(lazarus, n, &dev->redo.urb_list, anchor_list) {
+				usb_buffer_free(lazarus->dev, lazarus->transfer_buffer_length,
+				lazarus->transfer_buffer, lazarus->transfer_dma);
+				usb_free_urb(lazarus);	
+			}
+			mutex_unlock(&dev->io_mutex);
+			return r;
+		}
+		usb_free_urb(lazarus);
+	}
+	mutex_unlock(&dev->io_mutex);
+	return 0;
+}
+
 static struct usb_driver skel_driver = {
 	.name =		"skeleton",
 	.probe =	skel_probe,
 	.disconnect =	skel_disconnect,
+	.suspend =	skel_suspend,
+	.resume =	skel_resume,
 	.id_table =	skel_table,
 	.supports_autosuspend = 1,
 };

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.