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>
This commit is contained in:
Thomas Pugliese 2013-06-11 10:39:31 -05:00 committed by Greg Kroah-Hartman
parent 5b92f46c96
commit 2b81c083c6
2 changed files with 167 additions and 15 deletions

View file

@ -774,6 +774,7 @@ static int hwahc_probe(struct usb_interface *usb_iface,
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);

View file

@ -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 @@ static ssize_t __wa_xfer_setup_sizes(struct wa_xfer *xfer,
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 @@ static void wa_seg_cb(struct urb *urb)
}
}
/* 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 @@ static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size)
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 @@ static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size)
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 @@ static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size)
}
return 0;
error_sg_alloc:
kfree(seg->dto_urb);
error_dto_alloc:
kfree(xfer->seg[cnt]);
cnt--;
@ -1026,7 +1141,8 @@ int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep,
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 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer)
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 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer)
}
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 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer)
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 @@ error_submit_buf_in:
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 @@ static void wa_buf_in_cb(struct urb *urb)
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);