[PATCH 03/11] usb: make urb scatter-gather support more generic

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

 



The WHCI HCD will also support urbs with scatter-gather lists.  Add a
usb_bus field to indicated how many sg list elements are supported by
the HCD.

Save space in struct urb by adding a URB_HAS_SG flag and reusing
transfer_buffer to point to a struct sg_table.

Split usb_sg_init() into usb_sg_init_with_sg() and
usb_sg_init_without_sg().

Signed-off-by: David Vrabel <david.vrabel@xxxxxxx>
---
 drivers/usb/core/message.c   |  289 +++++++++++++++++++++++-------------------
 drivers/usb/host/xhci-ring.c |   15 ++-
 drivers/usb/storage/usb.c    |   10 ++
 include/linux/usb.h          |    9 +-
 4 files changed, 184 insertions(+), 139 deletions(-)

diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 9720e69..a39765c 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -332,6 +332,158 @@ static void sg_complete(struct urb *urb)
 	spin_unlock(&io->lock);
 }
 
+static int
+usb_sg_init_without_sg(struct usb_sg_request *io, struct usb_device *dev,
+		       unsigned pipe, unsigned period, struct scatterlist *sg,
+		       size_t length, gfp_t mem_flags)
+{
+	int i;
+	int urb_flags;
+	int dma;
+
+	/* not all host controllers use DMA (like the mainstream pci ones);
+	 * they can use PIO (sl811) or be software over another transport.
+	 */
+	dma = (dev->dev.dma_mask != NULL);
+	if (dma)
+		io->entries = usb_buffer_map_sg(dev, usb_pipein(pipe),
+						sg, io->nents);
+	else
+		io->entries = io->nents;
+
+	/* initialize all the urbs we'll use */
+	if (io->entries <= 0)
+		return io->entries;
+
+	io->urbs = kmalloc(io->entries * sizeof *io->urbs, mem_flags);
+	if (!io->urbs)
+		goto nomem;
+
+	urb_flags = URB_NO_INTERRUPT;
+	if (dma)
+		urb_flags |= URB_NO_TRANSFER_DMA_MAP;
+	if (usb_pipein(pipe))
+		urb_flags |= URB_SHORT_NOT_OK;
+
+	for_each_sg(sg, sg, io->entries, i) {
+		unsigned len;
+
+		io->urbs[i] = usb_alloc_urb(0, mem_flags);
+		if (!io->urbs[i]) {
+			io->entries = i;
+			goto nomem;
+		}
+
+		io->urbs[i]->dev = NULL;
+		io->urbs[i]->pipe = pipe;
+		io->urbs[i]->interval = period;
+		io->urbs[i]->transfer_flags = urb_flags;
+
+		io->urbs[i]->complete = sg_complete;
+		io->urbs[i]->context = io;
+
+		/*
+		 * Some systems need to revert to PIO when DMA is
+		 * temporarily unavailable.  For their sakes, both
+		 * transfer_buffer and transfer_dma are set when
+		 * possible.  However this can only work on systems
+		 * without:
+		 *
+		 *  - HIGHMEM, since DMA buffers located in high memory
+		 *    are not directly addressable by the CPU for PIO;
+		 *
+		 *  - IOMMU, since dma_map_sg() is allowed to use an
+		 *    IOMMU to make virtually discontiguous buffers be
+		 *    "dma-contiguous" so that PIO and DMA need diferent
+		 *    numbers of URBs.
+		 *
+		 * So when HIGHMEM or IOMMU are in use, transfer_buffer
+		 * is NULL to prevent stale pointers and to help spot
+		 * bugs.
+		 */
+		if (dma) {
+			io->urbs[i]->transfer_dma = sg_dma_address(sg);
+			len = sg_dma_len(sg);
+#if defined(CONFIG_HIGHMEM) || defined(CONFIG_GART_IOMMU)
+			io->urbs[i]->transfer_buffer = NULL;
+#else
+			io->urbs[i]->transfer_buffer = sg_virt(sg);
+#endif
+		} else {
+			/* hc may use _only_ transfer_buffer */
+			io->urbs[i]->transfer_buffer = sg_virt(sg);
+			len = sg->length;
+		}
+
+		if (length) {
+			len = min_t(unsigned, len, length);
+			length -= len;
+			if (length == 0)
+				io->entries = i + 1;
+		}
+		io->urbs[i]->transfer_buffer_length = len;
+	}
+	io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT;
+
+	return 0;
+
+nomem:
+	sg_clean(io);
+	return -ENOMEM;
+}
+
+static int
+usb_sg_init_with_sg(struct usb_sg_request *io, struct usb_device *dev,
+		    unsigned pipe, unsigned period, struct scatterlist *sg,
+		    size_t length, gfp_t mem_flags)
+{
+	struct sg_table *sgt;
+
+	if (length == 0) {
+		struct scatterlist *s;
+		int i;
+		for_each_sg(sg, s, io->nents, i) {
+			length += s->length;
+		}
+	}
+
+	/* initialize one urb with transfer_buffer pointing to a new sg_table */
+	io->urbs = kmalloc(sizeof(struct urb *) + sizeof(struct sg_table), mem_flags);
+	if (!io->urbs)
+		return -ENOMEM;
+
+	sgt = (struct sg_table *)&io->urbs[1];
+	sgt->sgl = sg;
+	sgt->orig_nents = io->nents;
+
+	/* not all host controllers use DMA (like the mainstream pci ones);
+	 * they can use PIO (sl811) or be software over another transport.
+	 */
+	if (dev->dev.dma_mask != NULL)
+		sgt->nents = usb_buffer_map_sg(dev, usb_pipein(pipe), sg, io->nents);
+	else
+		sgt->nents = sgt->orig_nents;
+	if (sgt->nents <= 0)
+		return sgt->nents;
+
+	io->urbs[0] = usb_alloc_urb(0, mem_flags);
+	if (!io->urbs[0])
+		return -ENOMEM;
+	io->entries = 1;
+
+	io->urbs[0]->dev = NULL;
+	io->urbs[0]->pipe = pipe;
+	io->urbs[0]->interval = period;
+	io->urbs[0]->transfer_flags = URB_HAS_SG;
+
+	io->urbs[0]->complete = sg_complete;
+	io->urbs[0]->context = io;
+
+	io->urbs[0]->transfer_buffer = sgt;
+	io->urbs[0]->transfer_buffer_length = length;
+
+	return 0;
+}
 
 /**
  * usb_sg_init - initializes scatterlist-based bulk/interrupt I/O request
@@ -362,10 +514,7 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev,
 		unsigned pipe, unsigned	period, struct scatterlist *sg,
 		int nents, size_t length, gfp_t mem_flags)
 {
-	int i;
-	int urb_flags;
-	int dma;
-	int use_sg;
+	int ret;
 
 	if (!io || !dev || !sg
 			|| usb_pipecontrol(pipe)
@@ -378,128 +527,15 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev,
 	io->pipe = pipe;
 	io->sg = sg;
 	io->nents = nents;
+	io->entries = 0;
 
-	/* not all host controllers use DMA (like the mainstream pci ones);
-	 * they can use PIO (sl811) or be software over another transport.
-	 */
-	dma = (dev->dev.dma_mask != NULL);
-	if (dma)
-		io->entries = usb_buffer_map_sg(dev, usb_pipein(pipe),
-						sg, nents);
+	if (dev->bus->sg_tablesize)
+		ret = usb_sg_init_with_sg(io, dev, pipe, period, sg, length, mem_flags);
 	else
-		io->entries = nents;
-
-	/* initialize all the urbs we'll use */
-	if (io->entries <= 0)
-		return io->entries;
-
-	/* If we're running on an xHCI host controller, queue the whole scatter
-	 * gather list with one call to urb_enqueue().  This is only for bulk,
-	 * as that endpoint type does not care how the data gets broken up
-	 * across frames.
-	 */
-	if (usb_pipebulk(pipe) &&
-			bus_to_hcd(dev->bus)->driver->flags & HCD_USB3) {
-		io->urbs = kmalloc(sizeof *io->urbs, mem_flags);
-		use_sg = true;
-	} else {
-		io->urbs = kmalloc(io->entries * sizeof *io->urbs, mem_flags);
-		use_sg = false;
-	}
-	if (!io->urbs)
-		goto nomem;
-
-	urb_flags = URB_NO_INTERRUPT;
-	if (dma)
-		urb_flags |= URB_NO_TRANSFER_DMA_MAP;
-	if (usb_pipein(pipe))
-		urb_flags |= URB_SHORT_NOT_OK;
-
-	if (use_sg) {
-		io->urbs[0] = usb_alloc_urb(0, mem_flags);
-		if (!io->urbs[0]) {
-			io->entries = 0;
-			goto nomem;
-		}
-
-		io->urbs[0]->dev = NULL;
-		io->urbs[0]->pipe = pipe;
-		io->urbs[0]->interval = period;
-		io->urbs[0]->transfer_flags = urb_flags;
-
-		io->urbs[0]->complete = sg_complete;
-		io->urbs[0]->context = io;
-		/* A length of zero means transfer the whole sg list */
-		io->urbs[0]->transfer_buffer_length = length;
-		if (length == 0) {
-			for_each_sg(sg, sg, io->entries, i) {
-				io->urbs[0]->transfer_buffer_length +=
-					sg_dma_len(sg);
-			}
-		}
-		io->urbs[0]->sg = io;
-		io->urbs[0]->num_sgs = io->entries;
-		io->entries = 1;
-	} else {
-		for_each_sg(sg, sg, io->entries, i) {
-			unsigned len;
-
-			io->urbs[i] = usb_alloc_urb(0, mem_flags);
-			if (!io->urbs[i]) {
-				io->entries = i;
-				goto nomem;
-			}
-
-			io->urbs[i]->dev = NULL;
-			io->urbs[i]->pipe = pipe;
-			io->urbs[i]->interval = period;
-			io->urbs[i]->transfer_flags = urb_flags;
-
-			io->urbs[i]->complete = sg_complete;
-			io->urbs[i]->context = io;
-
-			/*
-			 * Some systems need to revert to PIO when DMA is
-			 * temporarily unavailable.  For their sakes, both
-			 * transfer_buffer and transfer_dma are set when
-			 * possible.  However this can only work on systems
-			 * without:
-			 *
-			 *  - HIGHMEM, since DMA buffers located in high memory
-			 *    are not directly addressable by the CPU for PIO;
-			 *
-			 *  - IOMMU, since dma_map_sg() is allowed to use an
-			 *    IOMMU to make virtually discontiguous buffers be
-			 *    "dma-contiguous" so that PIO and DMA need diferent
-			 *    numbers of URBs.
-			 *
-			 * So when HIGHMEM or IOMMU are in use, transfer_buffer
-			 * is NULL to prevent stale pointers and to help spot
-			 * bugs.
-			 */
-			if (dma) {
-				io->urbs[i]->transfer_dma = sg_dma_address(sg);
-				len = sg_dma_len(sg);
-#if defined(CONFIG_HIGHMEM) || defined(CONFIG_GART_IOMMU)
-				io->urbs[i]->transfer_buffer = NULL;
-#else
-				io->urbs[i]->transfer_buffer = sg_virt(sg);
-#endif
-			} else {
-				/* hc may use _only_ transfer_buffer */
-				io->urbs[i]->transfer_buffer = sg_virt(sg);
-				len = sg->length;
-			}
-
-			if (length) {
-				len = min_t(unsigned, len, length);
-				length -= len;
-				if (length == 0)
-					io->entries = i + 1;
-			}
-			io->urbs[i]->transfer_buffer_length = len;
-		}
-		io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT;
+		ret = usb_sg_init_without_sg(io, dev, pipe, period, sg, length, mem_flags);
+	if (ret < 0) {
+		sg_clean(io);
+		return ret;
 	}
 
 	/* transaction state */
@@ -507,11 +543,8 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev,
 	io->status = 0;
 	io->bytes = 0;
 	init_completion(&io->complete);
-	return 0;
 
-nomem:
-	sg_clean(io);
-	return -ENOMEM;
+	return 0;
 }
 EXPORT_SYMBOL_GPL(usb_sg_init);
 
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index aa88a06..bd5a96e 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1265,15 +1265,16 @@ static int prepare_transfer(struct xhci_hcd *xhci,
 static unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb)
 {
 	int num_sgs, num_trbs, running_total, temp, i;
+	struct sg_table *sg_table;
 	struct scatterlist *sg;
 
-	sg = NULL;
-	num_sgs = urb->num_sgs;
+	sg_table = urb->transfer_buffer;
+	num_sgs = sg_table->nents;
 	temp = urb->transfer_buffer_length;
 
 	xhci_dbg(xhci, "count sg list trbs: \n");
 	num_trbs = 0;
-	for_each_sg(urb->sg->sg, sg, num_sgs, i) {
+	for_each_sg(sg_table->sgl, sg, num_sgs, i) {
 		unsigned int previous_total_trbs = num_trbs;
 		unsigned int len = sg_dma_len(sg);
 
@@ -1341,6 +1342,7 @@ static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 	struct xhci_ring *ep_ring;
 	unsigned int num_trbs;
 	struct xhci_td *td;
+	struct sg_table *sg_table;
 	struct scatterlist *sg;
 	int num_sgs;
 	int trb_buff_len, this_sg_len, running_total;
@@ -1352,7 +1354,8 @@ static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 
 	ep_ring = xhci->devs[slot_id]->ep_rings[ep_index];
 	num_trbs = count_sg_trbs_needed(xhci, urb);
-	num_sgs = urb->num_sgs;
+	sg_table = urb->transfer_buffer;
+	num_sgs = sg_table->nents;
 
 	trb_buff_len = prepare_transfer(xhci, xhci->devs[slot_id],
 			ep_index, num_trbs, urb, &td, mem_flags);
@@ -1376,7 +1379,7 @@ static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 	 *    the amount of memory allocated for this scatter-gather list.
 	 * 3. TRBs buffers can't cross 64KB boundaries.
 	 */
-	sg = urb->sg->sg;
+	sg = sg_table->sgl;
 	addr = (u64) sg_dma_address(sg);
 	this_sg_len = sg_dma_len(sg);
 	trb_buff_len = TRB_MAX_BUFF_SIZE -
@@ -1480,7 +1483,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 	int running_total, trb_buff_len, ret;
 	u64 addr;
 
-	if (urb->sg)
+	if (urb->transfer_flags & URB_HAS_SG)
 		return queue_bulk_sg_tx(xhci, mem_flags, urb, slot_id, ep_index);
 
 	ep_ring = xhci->devs[slot_id]->ep_rings[ep_index];
diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
index 8060b85..964d8ee 100644
--- a/drivers/usb/storage/usb.c
+++ b/drivers/usb/storage/usb.c
@@ -840,6 +840,15 @@ static int usb_stor_scan_thread(void * __us)
 	complete_and_exit(&us->scanning_done, 0);
 }
 
+static unsigned int usb_stor_sg_tablesize(struct usb_interface *intf)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+
+	if (usb_dev->bus->sg_tablesize) {
+		return usb_dev->bus->sg_tablesize;
+	}
+	return SG_ALL;
+}
 
 /* First part of general USB mass-storage probing */
 int usb_stor_probe1(struct us_data **pus,
@@ -868,6 +877,7 @@ int usb_stor_probe1(struct us_data **pus,
 	 * Allow 16-byte CDBs and thus > 2TB
 	 */
 	host->max_cmd_len = 16;
+	host->sg_tablesize = usb_stor_sg_tablesize(intf);
 	*pus = us = host_to_us(host);
 	memset(us, 0, sizeof(struct us_data));
 	mutex_init(&(us->dev_mutex));
diff --git a/include/linux/usb.h b/include/linux/usb.h
index b1e3c2f..b96e7d8 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -331,6 +331,7 @@ struct usb_bus {
 	u8 otg_port;			/* 0, or number of OTG/HNP port */
 	unsigned is_b_host:1;		/* true during some HNP roleswitches */
 	unsigned b_hnp_enable:1;	/* OTG: did A-Host enable HNP? */
+	int sg_tablesize;               /* 0 or largest number of sg list entries */
 
 	int devnum_next;		/* Next open device number in
 					 * round-robin allocation */
@@ -977,6 +978,7 @@ extern int usb_disabled(void);
 					 * ignored */
 #define URB_NO_TRANSFER_DMA_MAP	0x0004	/* urb->transfer_dma valid on submit */
 #define URB_NO_SETUP_DMA_MAP	0x0008	/* urb->setup_dma valid on submit */
+#define URB_HAS_SG		0x0010	/* urb->transfer_buffer points to a struct sg_table */
 #define URB_NO_FSBR		0x0020	/* UHCI-specific */
 #define URB_ZERO_PACKET		0x0040	/* Finish bulk OUT with short packet */
 #define URB_NO_INTERRUPT	0x0080	/* HINT: no non-error interrupt
@@ -1041,13 +1043,12 @@ typedef void (*usb_complete_t)(struct urb *);
  *	is set).  This buffer must be suitable for DMA; allocate it with
  *	kmalloc() or equivalent.  For transfers to "in" endpoints, contents
  *	of this buffer will be modified.  This buffer is used for the data
- *	stage of control transfers.
+ *	stage of control transfers. If @transfer_flags & URB_HAS_SG then
+ *	this points to a struct sg_table instead.
  * @transfer_dma: When transfer_flags includes URB_NO_TRANSFER_DMA_MAP,
  *	the device driver is saying that it provided this DMA address,
  *	which the host controller driver should use in preference to the
  *	transfer_buffer.
- * @sg: scatter gather buffer list
- * @num_sgs: number of entries in the sg list
  * @transfer_buffer_length: How big is transfer_buffer.  The transfer may
  *	be broken up into chunks according to the current maximum packet
  *	size for the endpoint, which is a function of the configuration
@@ -1203,8 +1204,6 @@ struct urb {
 	unsigned int transfer_flags;	/* (in) URB_SHORT_NOT_OK | ...*/
 	void *transfer_buffer;		/* (in) associated data buffer */
 	dma_addr_t transfer_dma;	/* (in) dma addr for transfer_buffer */
-	struct usb_sg_request *sg;	/* (in) scatter gather buffer list */
-	int num_sgs;			/* (in) number of entries in the sg list */
 	u32 transfer_buffer_length;	/* (in) data buffer length */
 	u32 actual_length;		/* (return) actual transfer length */
 	unsigned char *setup_packet;	/* (in) setup packet (control only) */
-- 
1.6.3.3

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