usb: wire adapter: add scatter gather support

This patch adds support for scatter gather DMA to the wire adapter and
updates the HWA to advertise support for SG transfers.  This allows the
block layer to submit transfer requests to the HWA HC without first
breaking them up into PAGE_SIZE requests.

Signed-off-by: Thomas Pugliese <thomas.pugliese@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c
index c0df599..4af750e 100644
--- a/drivers/usb/host/hwa-hc.c
+++ b/drivers/usb/host/hwa-hc.c
@@ -774,6 +774,7 @@
 		goto error_alloc;
 	}
 	usb_hcd->wireless = 1;
+	usb_hcd->self.sg_tablesize = ~0;
 	wusbhc = usb_hcd_to_wusbhc(usb_hcd);
 	hwahc = container_of(wusbhc, struct hwahc, wusbhc);
 	hwahc_init(hwahc);
diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c
index 6ef94bc..16968c8 100644
--- a/drivers/usb/wusbcore/wa-xfer.c
+++ b/drivers/usb/wusbcore/wa-xfer.c
@@ -85,6 +85,7 @@
 #include <linux/hash.h>
 #include <linux/ratelimit.h>
 #include <linux/export.h>
+#include <linux/scatterlist.h>
 
 #include "wa-hc.h"
 #include "wusbhc.h"
@@ -442,8 +443,7 @@
 		goto error;
 	}
 	xfer->seg_size = (xfer->seg_size / maxpktsize) * maxpktsize;
-	xfer->segs = (urb->transfer_buffer_length + xfer->seg_size - 1)
-		/ xfer->seg_size;
+	xfer->segs = DIV_ROUND_UP(urb->transfer_buffer_length, xfer->seg_size);
 	if (xfer->segs >= WA_SEGS_MAX) {
 		dev_err(dev, "BUG? ops, number of segments %d bigger than %d\n",
 			(int)(urb->transfer_buffer_length / xfer->seg_size),
@@ -627,6 +627,86 @@
 	}
 }
 
+/* allocate an SG list to store bytes_to_transfer bytes and copy the
+ * subset of the in_sg that matches the buffer subset
+ * we are about to transfer. */
+static struct scatterlist *wa_xfer_create_subset_sg(struct scatterlist *in_sg,
+	const unsigned int bytes_transferred,
+	const unsigned int bytes_to_transfer, unsigned int *out_num_sgs)
+{
+	struct scatterlist *out_sg;
+	unsigned int bytes_processed = 0, offset_into_current_page_data = 0,
+		nents;
+	struct scatterlist *current_xfer_sg = in_sg;
+	struct scatterlist *current_seg_sg, *last_seg_sg;
+
+	/* skip previously transferred pages. */
+	while ((current_xfer_sg) &&
+			(bytes_processed < bytes_transferred)) {
+		bytes_processed += current_xfer_sg->length;
+
+		/* advance the sg if current segment starts on or past the
+			next page. */
+		if (bytes_processed <= bytes_transferred)
+			current_xfer_sg = sg_next(current_xfer_sg);
+	}
+
+	/* the data for the current segment starts in current_xfer_sg.
+		calculate the offset. */
+	if (bytes_processed > bytes_transferred) {
+		offset_into_current_page_data = current_xfer_sg->length -
+			(bytes_processed - bytes_transferred);
+	}
+
+	/* calculate the number of pages needed by this segment. */
+	nents = DIV_ROUND_UP((bytes_to_transfer +
+		offset_into_current_page_data +
+		current_xfer_sg->offset),
+		PAGE_SIZE);
+
+	out_sg = kmalloc((sizeof(struct scatterlist) * nents), GFP_ATOMIC);
+	if (out_sg) {
+		sg_init_table(out_sg, nents);
+
+		/* copy the portion of the incoming SG that correlates to the
+		 * data to be transferred by this segment to the segment SG. */
+		last_seg_sg = current_seg_sg = out_sg;
+		bytes_processed = 0;
+
+		/* reset nents and calculate the actual number of sg entries
+			needed. */
+		nents = 0;
+		while ((bytes_processed < bytes_to_transfer) &&
+				current_seg_sg && current_xfer_sg) {
+			unsigned int page_len = min((current_xfer_sg->length -
+				offset_into_current_page_data),
+				(bytes_to_transfer - bytes_processed));
+
+			sg_set_page(current_seg_sg, sg_page(current_xfer_sg),
+				page_len,
+				current_xfer_sg->offset +
+				offset_into_current_page_data);
+
+			bytes_processed += page_len;
+
+			last_seg_sg = current_seg_sg;
+			current_seg_sg = sg_next(current_seg_sg);
+			current_xfer_sg = sg_next(current_xfer_sg);
+
+			/* only the first page may require additional offset. */
+			offset_into_current_page_data = 0;
+			nents++;
+		}
+
+		/* update num_sgs and terminate the list since we may have
+		 *  concatenated pages. */
+		sg_mark_end(last_seg_sg);
+		*out_num_sgs = nents;
+	}
+
+	return out_sg;
+}
+
 /*
  * Allocate the segs array and initialize each of them
  *
@@ -663,9 +743,9 @@
 						  dto_epd->bEndpointAddress),
 				  &seg->xfer_hdr, xfer_hdr_size,
 				  wa_seg_cb, seg);
-		buf_itr_size = buf_size > xfer->seg_size ?
-			xfer->seg_size : buf_size;
+		buf_itr_size = min(buf_size, xfer->seg_size);
 		if (xfer->is_inbound == 0 && buf_size > 0) {
+			/* outbound data. */
 			seg->dto_urb = usb_alloc_urb(0, GFP_ATOMIC);
 			if (seg->dto_urb == NULL)
 				goto error_dto_alloc;
@@ -679,9 +759,42 @@
 					xfer->urb->transfer_dma + buf_itr;
 				seg->dto_urb->transfer_flags |=
 					URB_NO_TRANSFER_DMA_MAP;
-			} else
-				seg->dto_urb->transfer_buffer =
-					xfer->urb->transfer_buffer + buf_itr;
+				seg->dto_urb->transfer_buffer = NULL;
+				seg->dto_urb->sg = NULL;
+				seg->dto_urb->num_sgs = 0;
+			} else {
+				/* do buffer or SG processing. */
+				seg->dto_urb->transfer_flags &=
+					~URB_NO_TRANSFER_DMA_MAP;
+				/* this should always be 0 before a resubmit. */
+				seg->dto_urb->num_mapped_sgs = 0;
+
+				if (xfer->urb->transfer_buffer) {
+					seg->dto_urb->transfer_buffer =
+						xfer->urb->transfer_buffer +
+						buf_itr;
+					seg->dto_urb->sg = NULL;
+					seg->dto_urb->num_sgs = 0;
+				} else {
+					/* allocate an SG list to store seg_size
+					    bytes and copy the subset of the
+					    xfer->urb->sg that matches the
+					    buffer subset we are about to read.
+					*/
+					seg->dto_urb->sg =
+						wa_xfer_create_subset_sg(
+						xfer->urb->sg,
+						buf_itr, buf_itr_size,
+						&(seg->dto_urb->num_sgs));
+
+					if (!(seg->dto_urb->sg)) {
+						seg->dto_urb->num_sgs	= 0;
+						goto error_sg_alloc;
+					}
+
+					seg->dto_urb->transfer_buffer = NULL;
+				}
+			}
 			seg->dto_urb->transfer_buffer_length = buf_itr_size;
 		}
 		seg->status = WA_SEG_READY;
@@ -690,6 +803,8 @@
 	}
 	return 0;
 
+error_sg_alloc:
+	kfree(seg->dto_urb);
 error_dto_alloc:
 	kfree(xfer->seg[cnt]);
 	cnt--;
@@ -1026,7 +1141,8 @@
 	unsigned long my_flags;
 	unsigned cant_sleep = irqs_disabled() | in_atomic();
 
-	if (urb->transfer_buffer == NULL
+	if ((urb->transfer_buffer == NULL)
+	    && (urb->sg == NULL)
 	    && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
 	    && urb->transfer_buffer_length != 0) {
 		dev_err(dev, "BUG? urb %p: NULL xfer buffer & NODMA\n", urb);
@@ -1261,7 +1377,7 @@
 	seg = xfer->seg[seg_idx];
 	rpipe = xfer->ep->hcpriv;
 	usb_status = xfer_result->bTransferStatus;
-	dev_dbg(dev, "xfer %p#%u: bTransferStatus 0x%02x (seg %u)\n",
+	dev_dbg(dev, "xfer %p#%u: bTransferStatus 0x%02x (seg status %u)\n",
 		xfer, seg_idx, usb_status, seg->status);
 	if (seg->status == WA_SEG_ABORTED
 	    || seg->status == WA_SEG_ERROR)	/* already handled */
@@ -1276,8 +1392,8 @@
 	}
 	if (usb_status & 0x80) {
 		seg->result = wa_xfer_status_to_errno(usb_status);
-		dev_err(dev, "DTI: xfer %p#%u failed (0x%02x)\n",
-			xfer, seg->index, usb_status);
+		dev_err(dev, "DTI: xfer %p#:%08X:%u failed (0x%02x)\n",
+			xfer, xfer->id, seg->index, usb_status);
 		goto error_complete;
 	}
 	/* FIXME: we ignore warnings, tally them for stats */
@@ -1286,18 +1402,47 @@
 	if (xfer->is_inbound) {	/* IN data phase: read to buffer */
 		seg->status = WA_SEG_DTI_PENDING;
 		BUG_ON(wa->buf_in_urb->status == -EINPROGRESS);
+		/* this should always be 0 before a resubmit. */
+		wa->buf_in_urb->num_mapped_sgs	= 0;
+
 		if (xfer->is_dma) {
 			wa->buf_in_urb->transfer_dma =
 				xfer->urb->transfer_dma
-				+ seg_idx * xfer->seg_size;
+				+ (seg_idx * xfer->seg_size);
 			wa->buf_in_urb->transfer_flags
 				|= URB_NO_TRANSFER_DMA_MAP;
+			wa->buf_in_urb->transfer_buffer = NULL;
+			wa->buf_in_urb->sg = NULL;
+			wa->buf_in_urb->num_sgs = 0;
 		} else {
-			wa->buf_in_urb->transfer_buffer =
-				xfer->urb->transfer_buffer
-				+ seg_idx * xfer->seg_size;
+			/* do buffer or SG processing. */
 			wa->buf_in_urb->transfer_flags
 				&= ~URB_NO_TRANSFER_DMA_MAP;
+
+			if (xfer->urb->transfer_buffer) {
+				wa->buf_in_urb->transfer_buffer =
+					xfer->urb->transfer_buffer
+					+ (seg_idx * xfer->seg_size);
+				wa->buf_in_urb->sg = NULL;
+				wa->buf_in_urb->num_sgs = 0;
+			} else {
+				/* allocate an SG list to store seg_size bytes
+					and copy the subset of the xfer->urb->sg
+					that matches the buffer subset we are
+					about to read. */
+				wa->buf_in_urb->sg = wa_xfer_create_subset_sg(
+					xfer->urb->sg,
+					seg_idx * xfer->seg_size,
+					le32_to_cpu(
+						xfer_result->dwTransferLength),
+					&(wa->buf_in_urb->num_sgs));
+
+				if (!(wa->buf_in_urb->sg)) {
+					wa->buf_in_urb->num_sgs	= 0;
+					goto error_sg_alloc;
+				}
+				wa->buf_in_urb->transfer_buffer = NULL;
+			}
 		}
 		wa->buf_in_urb->transfer_buffer_length =
 			le32_to_cpu(xfer_result->dwTransferLength);
@@ -1330,6 +1475,8 @@
 		dev_err(dev, "xfer %p#%u: can't submit DTI data phase: %d\n",
 			xfer, seg_idx, result);
 	seg->result = result;
+	kfree(wa->buf_in_urb->sg);
+error_sg_alloc:
 error_complete:
 	seg->status = WA_SEG_ERROR;
 	xfer->segs_done++;
@@ -1381,6 +1528,10 @@
 	unsigned long flags;
 	u8 done = 0;
 
+	/* free the sg if it was used. */
+	kfree(urb->sg);
+	urb->sg = NULL;
+
 	switch (urb->status) {
 	case 0:
 		spin_lock_irqsave(&xfer->lock, flags);