[RFC 4/4] USB: Support for USB 3.0 streams.

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

 



Bulk endpoint streams were added in the USB 3.0 specification.  Streams
allow a device driver to overload a bulk endpoint so that multiple
transfers can be queued at once.

The device then decides which transfer it wants to work on first, and can
queue part of a transfer before it switches to a new stream.  All this
switching is invisible to the device driver, which just gets a completion
for the URB.  Drivers that use streams must be able to handle URBs
completing in a different order than they were submitted to the endpoint.

This requires adding new API to set up xHCI data structures to support
multiple queues ("stream rings") per endpoint.  It also requires adding a
new field to struct urb to identify which stream ring the URB's buffer
should be queued to.

There are currently no drivers that use USB 3.0 streams.  However, the new
mass storage device class, USB Attached SCSI Protocol (UASP), will use
streams to queue multiple SCSI commands to one bulk endpoint.

Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx>
---
 Documentation/usb/bulk-streams.txt |   84 ++++++++++++++++++++++++++++++++
 drivers/usb/core/driver.c          |   92 ++++++++++++++++++++++++++++++++++++
 drivers/usb/core/hcd.h             |   10 ++++
 drivers/usb/host/xhci-pci.c        |    2 +
 include/linux/usb.h                |   10 ++++
 include/linux/usb/ch9.h            |    2 +
 6 files changed, 200 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/usb/bulk-streams.txt

diff --git a/Documentation/usb/bulk-streams.txt b/Documentation/usb/bulk-streams.txt
new file mode 100644
index 0000000..76a80b4
--- /dev/null
+++ b/Documentation/usb/bulk-streams.txt
@@ -0,0 +1,84 @@
+Background
+==========
+
+Bulk endpoint streams were added in the USB 3.0 specification.  Streams allow a
+device driver to overload a bulk endpoint so that multiple transfers can be
+queued at once.
+
+Streams are defined in sections 4.4.6.4 and 8.12.1.4 of the Universal Serial Bus
+3.0 specification at http://www.usb.org/developers/docs/  The USB Attached SCSI
+Protocol, which uses streams to queue multiple SCSI commands, can be found on
+the T10 website (http://t10.org/).
+
+
+Device-side implications
+========================
+
+Once a buffer has been queued to a stream ring, the device is notified (through
+an out-of-band mechanism on another endpoint) that data is ready for that stream
+ID.  The device then tells the host which "stream" it wants to start.  The host
+can also initiate a transfer on a stream without the device asking, but the
+device can refuse that transfer.  Devices can switch between streams at any
+time.
+
+
+Driver implications
+===================
+
+int usb_alloc_streams(struct usb_driver *driver,
+		      struct usb_device *dev,
+		      unsigned int *epaddr,
+		      unsigned int num_eps,
+		      unsigned int num_streams,
+		      gfp_t mem_flags);
+
+Device drivers will call this API to request that the host controller driver
+allocate memory so the driver can use up to num_requested_streams stream IDs.
+They must pass an array of endpoint addresses that need to be setup with similar
+stream IDs.  This is to insure that a UASP driver will be able to use the same
+stream ID for the bulk IN and OUT endpoints used in a Bi-directional command
+sequence.
+
+The return value is an error condition (if one of the endpoints doesn't support
+streams, or the driver hasn't claimed the interface that the endpoints are a
+part of), or the number of streams the host controller allocated for this
+endpoint.  The xHCI host controller hardware declares how many stream IDs it can
+support, and each bulk endpoint on a SuperSpeed device will say how many stream
+IDs it can handle.  Therefore, drivers should be able to deal with being
+allocated less stream IDs than they requested.
+
+Do NOT call this function if you have URBs enqueued for any of the endpoints
+passed in as arguments.  Do not call this function to request less than two
+streams.
+
+Drivers will only be allowed to call this API once for the same endpoint.  This
+is a simplification for the xHCI host controller driver, and may change in the
+future.
+
+
+Picking new Stream IDs to use
+============================
+
+Stream ID 0 is reserved, and should not be used to communicate with devices.  If
+usb_alloc_streams() returns with a value of N, you may use streams 1 though N.
+To queue an URB for a specific stream, set the urb->stream_id value.  If the
+endpoint does not support streams, an error will be returned.
+
+Note that new API to choose the next stream ID will have to be added if the xHCI
+driver supports secondary stream IDs.
+
+
+Clean up
+========
+
+If a driver wishes to stop using streams to communicate with the device, it
+should call
+
+int usb_free_streams(struct usb_driver *driver,
+		     struct usb_device *dev,
+		     unsigned int *epaddr,
+		     unsigned int num_eps,
+		     gfp_t mem_flags);
+
+All stream IDs will be deallocated when the driver releases the interface, to
+ensure that drivers that don't support streams will be able to use the endpoint.
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 69e5773..e137545 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -399,6 +399,98 @@ void usb_driver_release_interface(struct usb_driver *driver,
 }
 EXPORT_SYMBOL_GPL(usb_driver_release_interface);
 
+static struct usb_host_endpoint **usb_make_endpoint_array(
+		struct usb_device *dev, unsigned int *epaddr,
+		unsigned int num_eps, gfp_t mem_flags)
+{
+	struct usb_host_endpoint **eps;
+	unsigned int epnum;
+	int i;
+
+	eps = kmalloc(sizeof(struct usb_host_endpoint *)*num_eps, mem_flags);
+	if (!eps)
+		return NULL;
+
+	for (i = 0; i < num_eps; i++) {
+		epnum = epaddr[i] & USB_ENDPOINT_NUMBER_MASK;
+		if (usb_endpoint_out(epaddr[i]))
+			eps[i] = dev->ep_out[epnum];
+		else
+			eps[i] = dev->ep_out[epnum];
+		if (!eps[i]) {
+			kfree(eps);
+			return NULL;
+		}
+	}
+	return eps;
+}
+
+/* Sets up a group of bulk endpoints to have num_streams stream IDs available.
+ * Drivers may queue multiple transfers to different stream IDs, which may
+ * complete in a different order than they were queued.
+ */
+int usb_alloc_streams(struct usb_driver *driver, struct usb_device *dev,
+		unsigned int *epaddr, unsigned int num_eps,
+		unsigned int num_streams, gfp_t mem_flags)
+{
+	struct usb_hcd		*hcd;
+	struct usb_host_endpoint **eps;
+	int i;
+
+	hcd = bus_to_hcd(dev->bus);
+	if (!hcd->driver->alloc_streams)
+		return -EINVAL;
+	if (dev->speed != USB_SPEED_SUPER)
+		return -EINVAL;
+	eps = usb_make_endpoint_array(dev, epaddr, num_eps, mem_flags);
+	if (!eps)
+		return -ENOMEM;
+
+	/* Streams only apply to bulk endpoints. */
+	for (i = 0; i < num_eps; i++) {
+		if (!usb_endpoint_xfer_bulk(&eps[i]->desc)) {
+			kfree(eps);
+			return -EINVAL;
+		}
+	}
+
+	i = hcd->driver->alloc_streams(hcd, dev, eps, num_eps,
+			num_streams, mem_flags);
+	kfree(eps);
+	return i;
+}
+EXPORT_SYMBOL_GPL(usb_alloc_streams);
+
+/* Reverts a group of bulk endpoints back to not using stream IDs.
+ * Can fail if we run out of memory, or are given bad arguments.
+ */
+int usb_free_streams(struct usb_driver *driver, struct usb_device *dev,
+		unsigned int *epaddr, unsigned int num_eps, gfp_t mem_flags)
+{
+	struct usb_hcd		*hcd;
+	struct usb_host_endpoint **eps;
+	int i;
+
+	hcd = bus_to_hcd(dev->bus);
+	if (!hcd->driver->free_streams)
+		return -EINVAL;
+	eps = usb_make_endpoint_array(dev, epaddr, num_eps, mem_flags);
+	if (!eps)
+		return -ENOMEM;
+
+	/* Streams only apply to bulk endpoints. */
+	for (i = 0; i < num_eps; i++) {
+		if (!usb_endpoint_xfer_bulk(&eps[i]->desc)) {
+			kfree(eps);
+			return -EINVAL;
+		}
+	}
+	i = hcd->driver->free_streams(hcd, dev, eps, num_eps, mem_flags);
+	kfree(eps);
+	return i;
+}
+EXPORT_SYMBOL_GPL(usb_free_streams);
+
 /* returns 0 if no match, 1 if match */
 int usb_match_device(struct usb_device *dev, const struct usb_device_id *id)
 {
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index ec5c67e..e602620 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -236,6 +236,16 @@ struct hc_driver {
 	int	(*alloc_dev)(struct usb_hcd *, struct usb_device *);
 		/* Called by usb_release_dev to free HC device structures */
 	void	(*free_dev)(struct usb_hcd *, struct usb_device *);
+	/* Change a group of bulk endpoints to support multiple stream IDs */
+	int	(*alloc_streams)(struct usb_hcd *hcd, struct usb_device *udev,
+		struct usb_host_endpoint **eps, unsigned int num_eps,
+		unsigned int num_streams, gfp_t mem_flags);
+	/* Reverts a group of bulk endpoints back to not using stream IDs.
+	 * Can fail if we run out of memory.
+	 */
+	int	(*free_streams)(struct usb_hcd *hcd, struct usb_device *udev,
+		struct usb_host_endpoint **eps, unsigned int num_eps,
+		gfp_t mem_flags);
 
 	/* Bandwidth computation functions */
 	/* Note that add_endpoint() can only be called once per endpoint before
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 6e77f32..9f6681f 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -124,6 +124,8 @@ static const struct hc_driver xhci_pci_hc_driver = {
 	.urb_dequeue =		xhci_urb_dequeue,
 	.alloc_dev =		xhci_alloc_dev,
 	.free_dev =		xhci_free_dev,
+	.alloc_streams =	xhci_alloc_streams,
+	.free_streams =		xhci_free_streams,
 	.add_endpoint =		xhci_add_endpoint,
 	.drop_endpoint =	xhci_drop_endpoint,
 	.endpoint_reset =	xhci_endpoint_reset,
diff --git a/include/linux/usb.h b/include/linux/usb.h
index b1e3c2f..bdc1964 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -594,6 +594,15 @@ static inline void usb_mark_last_busy(struct usb_device *udev)
 /* for drivers using iso endpoints */
 extern int usb_get_current_frame_number(struct usb_device *usb_dev);
 
+/* Sets up a group of bulk endpoints to support multiple stream IDs. */
+extern int usb_alloc_streams(struct usb_driver *driver, struct usb_device *dev,
+		unsigned int *epaddr, unsigned int num_eps,
+		unsigned int num_streams, gfp_t mem_flags);
+
+/* Reverts a group of bulk endpoints back to not using stream IDs. */
+extern int usb_free_streams(struct usb_driver *driver, struct usb_device *dev,
+		unsigned int *epaddr, unsigned int num_eps, gfp_t mem_flags);
+
 /* used these for multi-interface device registration */
 extern int usb_driver_claim_interface(struct usb_driver *driver,
 			struct usb_interface *iface, void *priv);
@@ -1199,6 +1208,7 @@ struct urb {
 	struct usb_device *dev; 	/* (in) pointer to associated device */
 	struct usb_host_endpoint *ep;	/* (internal) pointer to endpoint */
 	unsigned int pipe;		/* (in) pipe information */
+	unsigned int stream_id;		/* (in) stream ID */
 	int status;			/* (return) non-ISO status */
 	unsigned int transfer_flags;	/* (in) URB_SHORT_NOT_OK | ...*/
 	void *transfer_buffer;		/* (in) associated data buffer */
diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h
index 9322363..0977cef 100644
--- a/include/linux/usb/ch9.h
+++ b/include/linux/usb/ch9.h
@@ -548,6 +548,8 @@ struct usb_ss_ep_comp_descriptor {
 } __attribute__ ((packed));
 
 #define USB_DT_SS_EP_COMP_SIZE		6
+/* Bits 4:0 of bmAttributes if this is a bulk endpoint */
+#define USB_SS_MAX_STREAMS(p)		(1 << (p & 0x1f))
 
 /*-------------------------------------------------------------------------*/
 
-- 
1.6.0.4

--
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