|
@@ -57,8 +57,9 @@ static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
|
|
|
|
|
|
is_out = usb_pipeout(urb->pipe);
|
|
|
|
|
|
- epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
|
|
|
+ qset->max_packet = le16_to_cpu(urb->ep->desc.wMaxPacketSize);
|
|
|
|
|
|
+ epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
|
|
|
if (epcd) {
|
|
|
qset->max_seq = epcd->bMaxSequence;
|
|
|
qset->max_burst = epcd->bMaxBurst;
|
|
@@ -72,7 +73,7 @@ static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
|
|
|
| (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN)
|
|
|
| usb_pipe_to_qh_type(urb->pipe)
|
|
|
| QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum))
|
|
|
- | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out))
|
|
|
+ | QH_INFO1_MAX_PKT_LEN(qset->max_packet)
|
|
|
);
|
|
|
qset->qh.info2 = cpu_to_le32(
|
|
|
QH_INFO2_BURST(qset->max_burst)
|
|
@@ -241,6 +242,36 @@ static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
|
|
|
qset->ntds--;
|
|
|
}
|
|
|
|
|
|
+static void qset_copy_bounce_to_sg(struct whc *whc, struct whc_std *std)
|
|
|
+{
|
|
|
+ struct scatterlist *sg;
|
|
|
+ void *bounce;
|
|
|
+ size_t remaining, offset;
|
|
|
+
|
|
|
+ bounce = std->bounce_buf;
|
|
|
+ remaining = std->len;
|
|
|
+
|
|
|
+ sg = std->bounce_sg;
|
|
|
+ offset = std->bounce_offset;
|
|
|
+
|
|
|
+ while (remaining) {
|
|
|
+ size_t len;
|
|
|
+
|
|
|
+ len = min(sg->length - offset, remaining);
|
|
|
+ memcpy(sg_virt(sg) + offset, bounce, len);
|
|
|
+
|
|
|
+ bounce += len;
|
|
|
+ remaining -= len;
|
|
|
+
|
|
|
+ offset += len;
|
|
|
+ if (offset >= sg->length) {
|
|
|
+ sg = sg_next(sg);
|
|
|
+ offset = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* qset_free_std - remove an sTD and free it.
|
|
|
* @whc: the WHCI host controller
|
|
@@ -249,13 +280,29 @@ static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
|
|
|
void qset_free_std(struct whc *whc, struct whc_std *std)
|
|
|
{
|
|
|
list_del(&std->list_node);
|
|
|
- if (std->num_pointers) {
|
|
|
- dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
|
|
|
- std->num_pointers * sizeof(struct whc_page_list_entry),
|
|
|
- DMA_TO_DEVICE);
|
|
|
+ if (std->bounce_buf) {
|
|
|
+ bool is_out = usb_pipeout(std->urb->pipe);
|
|
|
+ dma_addr_t dma_addr;
|
|
|
+
|
|
|
+ if (std->num_pointers)
|
|
|
+ dma_addr = le64_to_cpu(std->pl_virt[0].buf_ptr);
|
|
|
+ else
|
|
|
+ dma_addr = std->dma_addr;
|
|
|
+
|
|
|
+ dma_unmap_single(whc->wusbhc.dev, dma_addr,
|
|
|
+ std->len, is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
|
|
+ if (!is_out)
|
|
|
+ qset_copy_bounce_to_sg(whc, std);
|
|
|
+ kfree(std->bounce_buf);
|
|
|
+ }
|
|
|
+ if (std->pl_virt) {
|
|
|
+ if (std->dma_addr)
|
|
|
+ dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
|
|
|
+ std->num_pointers * sizeof(struct whc_page_list_entry),
|
|
|
+ DMA_TO_DEVICE);
|
|
|
kfree(std->pl_virt);
|
|
|
+ std->pl_virt = NULL;
|
|
|
}
|
|
|
-
|
|
|
kfree(std);
|
|
|
}
|
|
|
|
|
@@ -293,12 +340,17 @@ static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_f
|
|
|
{
|
|
|
dma_addr_t dma_addr = std->dma_addr;
|
|
|
dma_addr_t sp, ep;
|
|
|
- size_t std_len = std->len;
|
|
|
size_t pl_len;
|
|
|
int p;
|
|
|
|
|
|
- sp = ALIGN(dma_addr, WHCI_PAGE_SIZE);
|
|
|
- ep = dma_addr + std_len;
|
|
|
+ /* Short buffers don't need a page list. */
|
|
|
+ if (std->len <= WHCI_PAGE_SIZE) {
|
|
|
+ std->num_pointers = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ sp = dma_addr & ~(WHCI_PAGE_SIZE-1);
|
|
|
+ ep = dma_addr + std->len;
|
|
|
std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
|
|
|
|
|
|
pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
|
|
@@ -309,7 +361,7 @@ static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_f
|
|
|
|
|
|
for (p = 0; p < std->num_pointers; p++) {
|
|
|
std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
|
|
|
- dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE);
|
|
|
+ dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
@@ -339,6 +391,238 @@ static void urb_dequeue_work(struct work_struct *work)
|
|
|
spin_unlock_irqrestore(&whc->lock, flags);
|
|
|
}
|
|
|
|
|
|
+static struct whc_std *qset_new_std(struct whc *whc, struct whc_qset *qset,
|
|
|
+ struct urb *urb, gfp_t mem_flags)
|
|
|
+{
|
|
|
+ struct whc_std *std;
|
|
|
+
|
|
|
+ std = kzalloc(sizeof(struct whc_std), mem_flags);
|
|
|
+ if (std == NULL)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ std->urb = urb;
|
|
|
+ std->qtd = NULL;
|
|
|
+
|
|
|
+ INIT_LIST_HEAD(&std->list_node);
|
|
|
+ list_add_tail(&std->list_node, &qset->stds);
|
|
|
+
|
|
|
+ return std;
|
|
|
+}
|
|
|
+
|
|
|
+static int qset_add_urb_sg(struct whc *whc, struct whc_qset *qset, struct urb *urb,
|
|
|
+ gfp_t mem_flags)
|
|
|
+{
|
|
|
+ size_t remaining;
|
|
|
+ struct scatterlist *sg;
|
|
|
+ int i;
|
|
|
+ int ntds = 0;
|
|
|
+ struct whc_std *std = NULL;
|
|
|
+ struct whc_page_list_entry *entry;
|
|
|
+ dma_addr_t prev_end = 0;
|
|
|
+ size_t pl_len;
|
|
|
+ int p = 0;
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "adding urb w/ sg of length %d\n", urb->transfer_buffer_length);
|
|
|
+
|
|
|
+ remaining = urb->transfer_buffer_length;
|
|
|
+
|
|
|
+ for_each_sg(urb->sg->sg, sg, urb->num_sgs, i) {
|
|
|
+ dma_addr_t dma_addr;
|
|
|
+ size_t dma_remaining;
|
|
|
+ dma_addr_t sp, ep;
|
|
|
+ int num_pointers;
|
|
|
+
|
|
|
+ if (remaining == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ dma_addr = sg_dma_address(sg);
|
|
|
+ dma_remaining = min(sg_dma_len(sg), remaining);
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "adding sg[%d] %08x %d\n", i, (unsigned)dma_addr,
|
|
|
+ dma_remaining);
|
|
|
+
|
|
|
+ while (dma_remaining) {
|
|
|
+ size_t dma_len;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We can use the previous std (if it exists) provided that:
|
|
|
+ * - the previous one ended on a page boundary.
|
|
|
+ * - the current one begins on a page boundary.
|
|
|
+ * - the previous one isn't full.
|
|
|
+ *
|
|
|
+ * If a new std is needed but the previous one
|
|
|
+ * did not end on a wMaxPacketSize boundary
|
|
|
+ * then this sg list cannot be mapped onto
|
|
|
+ * multiple qTDs. Return an error and let the
|
|
|
+ * caller sort it out.
|
|
|
+ */
|
|
|
+ if (!std
|
|
|
+ || (prev_end & (WHCI_PAGE_SIZE-1))
|
|
|
+ || (dma_addr & (WHCI_PAGE_SIZE-1))
|
|
|
+ || std->len + WHCI_PAGE_SIZE > QTD_MAX_XFER_SIZE) {
|
|
|
+ if (prev_end % qset->max_packet != 0)
|
|
|
+ return -EINVAL;
|
|
|
+ dev_dbg(&whc->umc->dev, "need new std\n");
|
|
|
+ std = qset_new_std(whc, qset, urb, mem_flags);
|
|
|
+ if (std == NULL) {
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+ ntds++;
|
|
|
+ p = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ dma_len = dma_remaining;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If the remainder in this element doesn't
|
|
|
+ * fit in a single qTD, end the qTD on a
|
|
|
+ * wMaxPacketSize boundary.
|
|
|
+ */
|
|
|
+ if (std->len + dma_len > QTD_MAX_XFER_SIZE) {
|
|
|
+ dma_len = QTD_MAX_XFER_SIZE - std->len;
|
|
|
+ ep = ((dma_addr + dma_len) / qset->max_packet) * qset->max_packet;
|
|
|
+ dma_len = ep - dma_addr;
|
|
|
+ }
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "adding %d\n", dma_len);
|
|
|
+
|
|
|
+ std->len += dma_len;
|
|
|
+ std->ntds_remaining = -1; /* filled in later */
|
|
|
+
|
|
|
+ sp = dma_addr & ~(WHCI_PAGE_SIZE-1);
|
|
|
+ ep = dma_addr + dma_len;
|
|
|
+ num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
|
|
|
+ std->num_pointers += num_pointers;
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "need %d more (%d total) page pointers\n",
|
|
|
+ num_pointers, std->num_pointers);
|
|
|
+
|
|
|
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
|
|
|
+
|
|
|
+ std->pl_virt = krealloc(std->pl_virt, pl_len, mem_flags);
|
|
|
+ if (std->pl_virt == NULL) {
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (;p < std->num_pointers; p++, entry++) {
|
|
|
+ dev_dbg(&whc->umc->dev, "e[%d] %08x\n", p, dma_addr);
|
|
|
+ std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
|
|
|
+ dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ prev_end = dma_addr = ep;
|
|
|
+ dma_remaining -= dma_len;
|
|
|
+ remaining -= dma_len;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "used %d tds\n", ntds);
|
|
|
+
|
|
|
+ /* Now the number of stds is know, go back and fill in
|
|
|
+ std->ntds_remaining. */
|
|
|
+ list_for_each_entry(std, &qset->stds, list_node) {
|
|
|
+ if (std->ntds_remaining == -1) {
|
|
|
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
|
|
|
+ std->ntds_remaining = ntds--;
|
|
|
+ std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt,
|
|
|
+ pl_len, DMA_TO_DEVICE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * qset_add_urb_sg_linearize - add an urb with sg list, copying the data
|
|
|
+ *
|
|
|
+ * If the URB contains an sg list whose elements cannot be directly
|
|
|
+ * mapped to qTDs then the data must be transferred via bounce
|
|
|
+ * buffers.
|
|
|
+ */
|
|
|
+static int qset_add_urb_sg_linearize(struct whc *whc, struct whc_qset *qset,
|
|
|
+ struct urb *urb, gfp_t mem_flags)
|
|
|
+{
|
|
|
+ bool is_out = usb_pipeout(urb->pipe);
|
|
|
+ size_t max_std_len;
|
|
|
+ size_t remaining;
|
|
|
+ int ntds = 0;
|
|
|
+ struct whc_std *std = NULL;
|
|
|
+ void *bounce = NULL;
|
|
|
+ struct scatterlist *sg;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* limit maximum bounce buffer to 16 * 3.5 KiB ~= 28 k */
|
|
|
+ max_std_len = qset->max_burst * qset->max_packet;
|
|
|
+
|
|
|
+ remaining = urb->transfer_buffer_length;
|
|
|
+
|
|
|
+ for_each_sg(urb->sg->sg, sg, urb->sg->nents, i) {
|
|
|
+ size_t len;
|
|
|
+ size_t sg_remaining;
|
|
|
+ void *orig;
|
|
|
+
|
|
|
+ if (remaining == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ sg_remaining = min(remaining, sg->length);
|
|
|
+ orig = sg_virt(sg);
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "adding sg[%d] %d\n", i, sg_remaining);
|
|
|
+
|
|
|
+ while (sg_remaining) {
|
|
|
+ if (!std || std->len == max_std_len) {
|
|
|
+ dev_dbg(&whc->umc->dev, "need new std\n");
|
|
|
+ std = qset_new_std(whc, qset, urb, mem_flags);
|
|
|
+ if (std == NULL)
|
|
|
+ return -ENOMEM;
|
|
|
+ std->bounce_buf = kmalloc(max_std_len, mem_flags);
|
|
|
+ if (std->bounce_buf == NULL)
|
|
|
+ return -ENOMEM;
|
|
|
+ std->bounce_sg = sg;
|
|
|
+ std->bounce_offset = orig - sg_virt(sg);
|
|
|
+ bounce = std->bounce_buf;
|
|
|
+ ntds++;
|
|
|
+ }
|
|
|
+
|
|
|
+ len = min(sg_remaining, max_std_len - std->len);
|
|
|
+
|
|
|
+ dev_dbg(&whc->umc->dev, "added %d from sg[%d] @ offset %d\n",
|
|
|
+ len, i, orig - sg_virt(sg));
|
|
|
+
|
|
|
+ if (is_out)
|
|
|
+ memcpy(bounce, orig, len);
|
|
|
+
|
|
|
+ std->len += len;
|
|
|
+ std->ntds_remaining = -1; /* filled in later */
|
|
|
+
|
|
|
+ bounce += len;
|
|
|
+ orig += len;
|
|
|
+ sg_remaining -= len;
|
|
|
+ remaining -= len;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * For each of the new sTDs, map the bounce buffers, create
|
|
|
+ * page lists (if necessary), and fill in std->ntds_remaining.
|
|
|
+ */
|
|
|
+ list_for_each_entry(std, &qset->stds, list_node) {
|
|
|
+ if (std->ntds_remaining != -1)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ std->dma_addr = dma_map_single(&whc->umc->dev, std->bounce_buf, std->len,
|
|
|
+ is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
|
|
+
|
|
|
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ std->ntds_remaining = ntds--;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* qset_add_urb - add an urb to the qset's queue.
|
|
|
*
|
|
@@ -353,10 +637,7 @@ int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
|
|
|
int remaining = urb->transfer_buffer_length;
|
|
|
u64 transfer_dma = urb->transfer_dma;
|
|
|
int ntds_remaining;
|
|
|
-
|
|
|
- ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
|
|
|
- if (ntds_remaining == 0)
|
|
|
- ntds_remaining = 1;
|
|
|
+ int ret;
|
|
|
|
|
|
wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
|
|
|
if (wurb == NULL)
|
|
@@ -366,32 +647,41 @@ int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
|
|
|
wurb->urb = urb;
|
|
|
INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
|
|
|
|
|
|
+ if (urb->sg) {
|
|
|
+ ret = qset_add_urb_sg(whc, qset, urb, mem_flags);
|
|
|
+ if (ret == -EINVAL) {
|
|
|
+ dev_dbg(&whc->umc->dev, "linearizing %d octet urb\n",
|
|
|
+ urb->transfer_buffer_length);
|
|
|
+ qset_free_stds(qset, urb);
|
|
|
+ ret = qset_add_urb_sg_linearize(whc, qset, urb, mem_flags);
|
|
|
+ }
|
|
|
+ if (ret < 0)
|
|
|
+ goto err_no_mem;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
|
|
|
+ if (ntds_remaining == 0)
|
|
|
+ ntds_remaining = 1;
|
|
|
+
|
|
|
while (ntds_remaining) {
|
|
|
struct whc_std *std;
|
|
|
size_t std_len;
|
|
|
|
|
|
- std = kmalloc(sizeof(struct whc_std), mem_flags);
|
|
|
- if (std == NULL)
|
|
|
- goto err_no_mem;
|
|
|
-
|
|
|
std_len = remaining;
|
|
|
if (std_len > QTD_MAX_XFER_SIZE)
|
|
|
std_len = QTD_MAX_XFER_SIZE;
|
|
|
|
|
|
- std->urb = urb;
|
|
|
+ std = qset_new_std(whc, qset, urb, mem_flags);
|
|
|
+ if (std == NULL)
|
|
|
+ goto err_no_mem;
|
|
|
+
|
|
|
std->dma_addr = transfer_dma;
|
|
|
std->len = std_len;
|
|
|
std->ntds_remaining = ntds_remaining;
|
|
|
- std->qtd = NULL;
|
|
|
|
|
|
- INIT_LIST_HEAD(&std->list_node);
|
|
|
- list_add_tail(&std->list_node, &qset->stds);
|
|
|
-
|
|
|
- if (std_len > WHCI_PAGE_SIZE) {
|
|
|
- if (qset_fill_page_list(whc, std, mem_flags) < 0)
|
|
|
- goto err_no_mem;
|
|
|
- } else
|
|
|
- std->num_pointers = 0;
|
|
|
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
|
|
|
+ goto err_no_mem;
|
|
|
|
|
|
ntds_remaining--;
|
|
|
remaining -= std_len;
|