mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-23 23:08:42 +07:00
3905118912
Now that the SPDX tag is in all USB files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Cc: Felipe Balbi <felipe.balbi@linux.intel.com> Cc: Johan Hovold <johan@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
832 lines
20 KiB
C
832 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Wireless Host Controller (WHC) qset management.
|
|
*
|
|
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uwb/umc.h>
|
|
#include <linux/usb.h>
|
|
|
|
#include "../../wusbcore/wusbhc.h"
|
|
|
|
#include "whcd.h"
|
|
|
|
struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
|
|
{
|
|
struct whc_qset *qset;
|
|
dma_addr_t dma;
|
|
|
|
qset = dma_pool_zalloc(whc->qset_pool, mem_flags, &dma);
|
|
if (qset == NULL)
|
|
return NULL;
|
|
|
|
qset->qset_dma = dma;
|
|
qset->whc = whc;
|
|
|
|
INIT_LIST_HEAD(&qset->list_node);
|
|
INIT_LIST_HEAD(&qset->stds);
|
|
|
|
return qset;
|
|
}
|
|
|
|
/**
|
|
* qset_fill_qh - fill the static endpoint state in a qset's QHead
|
|
* @qset: the qset whose QH needs initializing with static endpoint
|
|
* state
|
|
* @urb: an urb for a transfer to this endpoint
|
|
*/
|
|
static void qset_fill_qh(struct whc *whc, struct whc_qset *qset, struct urb *urb)
|
|
{
|
|
struct usb_device *usb_dev = urb->dev;
|
|
struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
|
|
struct usb_wireless_ep_comp_descriptor *epcd;
|
|
bool is_out;
|
|
uint8_t phy_rate;
|
|
|
|
is_out = usb_pipeout(urb->pipe);
|
|
|
|
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;
|
|
} else {
|
|
qset->max_seq = 2;
|
|
qset->max_burst = 1;
|
|
}
|
|
|
|
/*
|
|
* Initial PHY rate is 53.3 Mbit/s for control endpoints or
|
|
* the maximum supported by the device for other endpoints
|
|
* (unless limited by the user).
|
|
*/
|
|
if (usb_pipecontrol(urb->pipe))
|
|
phy_rate = UWB_PHY_RATE_53;
|
|
else {
|
|
uint16_t phy_rates;
|
|
|
|
phy_rates = le16_to_cpu(wusb_dev->wusb_cap_descr->wPHYRates);
|
|
phy_rate = fls(phy_rates) - 1;
|
|
if (phy_rate > whc->wusbhc.phy_rate)
|
|
phy_rate = whc->wusbhc.phy_rate;
|
|
}
|
|
|
|
qset->qh.info1 = cpu_to_le32(
|
|
QH_INFO1_EP(usb_pipeendpoint(urb->pipe))
|
|
| (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(qset->max_packet)
|
|
);
|
|
qset->qh.info2 = cpu_to_le32(
|
|
QH_INFO2_BURST(qset->max_burst)
|
|
| QH_INFO2_DBP(0)
|
|
| QH_INFO2_MAX_COUNT(3)
|
|
| QH_INFO2_MAX_RETRY(3)
|
|
| QH_INFO2_MAX_SEQ(qset->max_seq - 1)
|
|
);
|
|
/* FIXME: where can we obtain these Tx parameters from? Why
|
|
* doesn't the chip know what Tx power to use? It knows the Rx
|
|
* strength and can presumably guess the Tx power required
|
|
* from that? */
|
|
qset->qh.info3 = cpu_to_le32(
|
|
QH_INFO3_TX_RATE(phy_rate)
|
|
| QH_INFO3_TX_PWR(0) /* 0 == max power */
|
|
);
|
|
|
|
qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
|
|
}
|
|
|
|
/**
|
|
* qset_clear - clear fields in a qset so it may be reinserted into a
|
|
* schedule.
|
|
*
|
|
* The sequence number and current window are not cleared (see
|
|
* qset_reset()).
|
|
*/
|
|
void qset_clear(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
qset->td_start = qset->td_end = qset->ntds = 0;
|
|
|
|
qset->qh.link = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
|
|
qset->qh.status = qset->qh.status & QH_STATUS_SEQ_MASK;
|
|
qset->qh.err_count = 0;
|
|
qset->qh.scratch[0] = 0;
|
|
qset->qh.scratch[1] = 0;
|
|
qset->qh.scratch[2] = 0;
|
|
|
|
memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay));
|
|
|
|
init_completion(&qset->remove_complete);
|
|
}
|
|
|
|
/**
|
|
* qset_reset - reset endpoint state in a qset.
|
|
*
|
|
* Clears the sequence number and current window. This qset must not
|
|
* be in the ASL or PZL.
|
|
*/
|
|
void qset_reset(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
qset->reset = 0;
|
|
|
|
qset->qh.status &= ~QH_STATUS_SEQ_MASK;
|
|
qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
|
|
}
|
|
|
|
/**
|
|
* get_qset - get the qset for an async endpoint
|
|
*
|
|
* A new qset is created if one does not already exist.
|
|
*/
|
|
struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
|
|
gfp_t mem_flags)
|
|
{
|
|
struct whc_qset *qset;
|
|
|
|
qset = urb->ep->hcpriv;
|
|
if (qset == NULL) {
|
|
qset = qset_alloc(whc, mem_flags);
|
|
if (qset == NULL)
|
|
return NULL;
|
|
|
|
qset->ep = urb->ep;
|
|
urb->ep->hcpriv = qset;
|
|
qset_fill_qh(whc, qset, urb);
|
|
}
|
|
return qset;
|
|
}
|
|
|
|
void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
qset->remove = 0;
|
|
list_del_init(&qset->list_node);
|
|
complete(&qset->remove_complete);
|
|
}
|
|
|
|
/**
|
|
* qset_add_qtds - add qTDs for an URB to a qset
|
|
*
|
|
* Returns true if the list (ASL/PZL) must be updated because (for a
|
|
* WHCI 0.95 controller) an activated qTD was pointed to be iCur.
|
|
*/
|
|
enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
struct whc_std *std;
|
|
enum whc_update update = 0;
|
|
|
|
list_for_each_entry(std, &qset->stds, list_node) {
|
|
struct whc_qtd *qtd;
|
|
uint32_t status;
|
|
|
|
if (qset->ntds >= WHCI_QSET_TD_MAX
|
|
|| (qset->pause_after_urb && std->urb != qset->pause_after_urb))
|
|
break;
|
|
|
|
if (std->qtd)
|
|
continue; /* already has a qTD */
|
|
|
|
qtd = std->qtd = &qset->qtd[qset->td_end];
|
|
|
|
/* Fill in setup bytes for control transfers. */
|
|
if (usb_pipecontrol(std->urb->pipe))
|
|
memcpy(qtd->setup, std->urb->setup_packet, 8);
|
|
|
|
status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len);
|
|
|
|
if (whc_std_last(std) && usb_pipeout(std->urb->pipe))
|
|
status |= QTD_STS_LAST_PKT;
|
|
|
|
/*
|
|
* For an IN transfer the iAlt field should be set so
|
|
* the h/w will automatically advance to the next
|
|
* transfer. However, if there are 8 or more TDs
|
|
* remaining in this transfer then iAlt cannot be set
|
|
* as it could point to somewhere in this transfer.
|
|
*/
|
|
if (std->ntds_remaining < WHCI_QSET_TD_MAX) {
|
|
int ialt;
|
|
ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX;
|
|
status |= QTD_STS_IALT(ialt);
|
|
} else if (usb_pipein(std->urb->pipe))
|
|
qset->pause_after_urb = std->urb;
|
|
|
|
if (std->num_pointers)
|
|
qtd->options = cpu_to_le32(QTD_OPT_IOC);
|
|
else
|
|
qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL);
|
|
qtd->page_list_ptr = cpu_to_le64(std->dma_addr);
|
|
|
|
qtd->status = cpu_to_le32(status);
|
|
|
|
if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end)
|
|
update = WHC_UPDATE_UPDATED;
|
|
|
|
if (++qset->td_end >= WHCI_QSET_TD_MAX)
|
|
qset->td_end = 0;
|
|
qset->ntds++;
|
|
}
|
|
|
|
return update;
|
|
}
|
|
|
|
/**
|
|
* qset_remove_qtd - remove the first qTD from a qset.
|
|
*
|
|
* The qTD might be still active (if it's part of a IN URB that
|
|
* resulted in a short read) so ensure it's deactivated.
|
|
*/
|
|
static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
qset->qtd[qset->td_start].status = 0;
|
|
|
|
if (++qset->td_start >= WHCI_QSET_TD_MAX)
|
|
qset->td_start = 0;
|
|
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
|
|
* @std: the sTD to remove and free.
|
|
*/
|
|
void qset_free_std(struct whc *whc, struct whc_std *std)
|
|
{
|
|
list_del(&std->list_node);
|
|
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 (!dma_mapping_error(whc->wusbhc.dev, 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);
|
|
}
|
|
|
|
/**
|
|
* qset_remove_qtds - remove an URB's qTDs (and sTDs).
|
|
*/
|
|
static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset,
|
|
struct urb *urb)
|
|
{
|
|
struct whc_std *std, *t;
|
|
|
|
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
|
|
if (std->urb != urb)
|
|
break;
|
|
if (std->qtd != NULL)
|
|
qset_remove_qtd(whc, qset);
|
|
qset_free_std(whc, std);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* qset_free_stds - free any remaining sTDs for an URB.
|
|
*/
|
|
static void qset_free_stds(struct whc_qset *qset, struct urb *urb)
|
|
{
|
|
struct whc_std *std, *t;
|
|
|
|
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
|
|
if (std->urb == urb)
|
|
qset_free_std(qset->whc, std);
|
|
}
|
|
}
|
|
|
|
static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags)
|
|
{
|
|
dma_addr_t dma_addr = std->dma_addr;
|
|
dma_addr_t sp, ep;
|
|
size_t pl_len;
|
|
int p;
|
|
|
|
/* 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);
|
|
std->pl_virt = kmalloc(pl_len, mem_flags);
|
|
if (std->pl_virt == NULL)
|
|
return -ENOMEM;
|
|
std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(whc->wusbhc.dev, std->dma_addr)) {
|
|
kfree(std->pl_virt);
|
|
return -EFAULT;
|
|
}
|
|
|
|
for (p = 0; p < std->num_pointers; p++) {
|
|
std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
|
|
dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* urb_dequeue_work - executes asl/pzl update and gives back the urb to the system.
|
|
*/
|
|
static void urb_dequeue_work(struct work_struct *work)
|
|
{
|
|
struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work);
|
|
struct whc_qset *qset = wurb->qset;
|
|
struct whc *whc = qset->whc;
|
|
unsigned long flags;
|
|
|
|
if (wurb->is_async)
|
|
asl_update(whc, WUSBCMD_ASYNC_UPDATED
|
|
| WUSBCMD_ASYNC_SYNCED_DB
|
|
| WUSBCMD_ASYNC_QSET_RM);
|
|
else
|
|
pzl_update(whc, WUSBCMD_PERIODIC_UPDATED
|
|
| WUSBCMD_PERIODIC_SYNCED_DB
|
|
| WUSBCMD_PERIODIC_QSET_RM);
|
|
|
|
spin_lock_irqsave(&whc->lock, flags);
|
|
qset_remove_urb(whc, qset, wurb->urb, wurb->status);
|
|
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 *new_pl_virt;
|
|
dma_addr_t prev_end = 0;
|
|
size_t pl_len;
|
|
int p = 0;
|
|
|
|
remaining = urb->transfer_buffer_length;
|
|
|
|
for_each_sg(urb->sg, sg, urb->num_mapped_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_t(size_t, sg_dma_len(sg), 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
|
|
* was not a whole number of packets 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 (std && std->len % qset->max_packet != 0)
|
|
return -EINVAL;
|
|
std = qset_new_std(whc, qset, urb, mem_flags);
|
|
if (std == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
ntds++;
|
|
p = 0;
|
|
}
|
|
|
|
dma_len = dma_remaining;
|
|
|
|
/*
|
|
* If the remainder of this element doesn't
|
|
* fit in a single qTD, limit the qTD to a
|
|
* whole number of packets. This allows the
|
|
* remainder to go into the next qTD.
|
|
*/
|
|
if (std->len + dma_len > QTD_MAX_XFER_SIZE) {
|
|
dma_len = (QTD_MAX_XFER_SIZE / qset->max_packet)
|
|
* qset->max_packet - std->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;
|
|
|
|
pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
|
|
|
|
new_pl_virt = krealloc(std->pl_virt, pl_len, mem_flags);
|
|
if (new_pl_virt == NULL) {
|
|
kfree(std->pl_virt);
|
|
std->pl_virt = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
std->pl_virt = new_pl_virt;
|
|
|
|
for (;p < std->num_pointers; p++) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt,
|
|
pl_len, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(whc->wusbhc.dev, std->dma_addr))
|
|
return -EFAULT;
|
|
std->ntds_remaining = ntds--;
|
|
}
|
|
}
|
|
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, urb->num_mapped_sgs, i) {
|
|
size_t len;
|
|
size_t sg_remaining;
|
|
void *orig;
|
|
|
|
if (remaining == 0) {
|
|
break;
|
|
}
|
|
|
|
sg_remaining = min_t(size_t, remaining, sg->length);
|
|
orig = sg_virt(sg);
|
|
|
|
while (sg_remaining) {
|
|
if (!std || std->len == max_std_len) {
|
|
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);
|
|
|
|
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 (dma_mapping_error(&whc->umc->dev, std->dma_addr))
|
|
return -EFAULT;
|
|
|
|
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.
|
|
*
|
|
* The URB is chopped into sTDs, one for each qTD that will required.
|
|
* At least one qTD (and sTD) is required even if the transfer has no
|
|
* data (e.g., for some control transfers).
|
|
*/
|
|
int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
|
|
gfp_t mem_flags)
|
|
{
|
|
struct whc_urb *wurb;
|
|
int remaining = urb->transfer_buffer_length;
|
|
u64 transfer_dma = urb->transfer_dma;
|
|
int ntds_remaining;
|
|
int ret;
|
|
|
|
wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
|
|
if (wurb == NULL)
|
|
goto err_no_mem;
|
|
urb->hcpriv = wurb;
|
|
wurb->qset = qset;
|
|
wurb->urb = urb;
|
|
INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
|
|
|
|
if (urb->num_sgs) {
|
|
ret = qset_add_urb_sg(whc, qset, urb, mem_flags);
|
|
if (ret == -EINVAL) {
|
|
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_len = remaining;
|
|
if (std_len > QTD_MAX_XFER_SIZE)
|
|
std_len = QTD_MAX_XFER_SIZE;
|
|
|
|
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;
|
|
|
|
if (qset_fill_page_list(whc, std, mem_flags) < 0)
|
|
goto err_no_mem;
|
|
|
|
ntds_remaining--;
|
|
remaining -= std_len;
|
|
transfer_dma += std_len;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_no_mem:
|
|
qset_free_stds(qset, urb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* qset_remove_urb - remove an URB from the urb queue.
|
|
*
|
|
* The URB is returned to the USB subsystem.
|
|
*/
|
|
void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
|
|
struct urb *urb, int status)
|
|
{
|
|
struct wusbhc *wusbhc = &whc->wusbhc;
|
|
struct whc_urb *wurb = urb->hcpriv;
|
|
|
|
usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb);
|
|
/* Drop the lock as urb->complete() may enqueue another urb. */
|
|
spin_unlock(&whc->lock);
|
|
wusbhc_giveback_urb(wusbhc, urb, status);
|
|
spin_lock(&whc->lock);
|
|
|
|
kfree(wurb);
|
|
}
|
|
|
|
/**
|
|
* get_urb_status_from_qtd - get the completed urb status from qTD status
|
|
* @urb: completed urb
|
|
* @status: qTD status
|
|
*/
|
|
static int get_urb_status_from_qtd(struct urb *urb, u32 status)
|
|
{
|
|
if (status & QTD_STS_HALTED) {
|
|
if (status & QTD_STS_DBE)
|
|
return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM;
|
|
else if (status & QTD_STS_BABBLE)
|
|
return -EOVERFLOW;
|
|
else if (status & QTD_STS_RCE)
|
|
return -ETIME;
|
|
return -EPIPE;
|
|
}
|
|
if (usb_pipein(urb->pipe)
|
|
&& (urb->transfer_flags & URB_SHORT_NOT_OK)
|
|
&& urb->actual_length < urb->transfer_buffer_length)
|
|
return -EREMOTEIO;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* process_inactive_qtd - process an inactive (but not halted) qTD.
|
|
*
|
|
* Update the urb with the transfer bytes from the qTD, if the urb is
|
|
* completely transferred or (in the case of an IN only) the LPF is
|
|
* set, then the transfer is complete and the urb should be returned
|
|
* to the system.
|
|
*/
|
|
void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
|
|
struct whc_qtd *qtd)
|
|
{
|
|
struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
|
|
struct urb *urb = std->urb;
|
|
uint32_t status;
|
|
bool complete;
|
|
|
|
status = le32_to_cpu(qtd->status);
|
|
|
|
urb->actual_length += std->len - QTD_STS_TO_LEN(status);
|
|
|
|
if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT))
|
|
complete = true;
|
|
else
|
|
complete = whc_std_last(std);
|
|
|
|
qset_remove_qtd(whc, qset);
|
|
qset_free_std(whc, std);
|
|
|
|
/*
|
|
* Transfers for this URB are complete? Then return it to the
|
|
* USB subsystem.
|
|
*/
|
|
if (complete) {
|
|
qset_remove_qtds(whc, qset, urb);
|
|
qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status));
|
|
|
|
/*
|
|
* If iAlt isn't valid then the hardware didn't
|
|
* advance iCur. Adjust the start and end pointers to
|
|
* match iCur.
|
|
*/
|
|
if (!(status & QTD_STS_IALT_VALID))
|
|
qset->td_start = qset->td_end
|
|
= QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status));
|
|
qset->pause_after_urb = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* process_halted_qtd - process a qset with a halted qtd
|
|
*
|
|
* Remove all the qTDs for the failed URB and return the failed URB to
|
|
* the USB subsystem. Then remove all other qTDs so the qset can be
|
|
* removed.
|
|
*
|
|
* FIXME: this is the point where rate adaptation can be done. If a
|
|
* transfer failed because it exceeded the maximum number of retries
|
|
* then it could be reactivated with a slower rate without having to
|
|
* remove the qset.
|
|
*/
|
|
void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
|
|
struct whc_qtd *qtd)
|
|
{
|
|
struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
|
|
struct urb *urb = std->urb;
|
|
int urb_status;
|
|
|
|
urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status));
|
|
|
|
qset_remove_qtds(whc, qset, urb);
|
|
qset_remove_urb(whc, qset, urb, urb_status);
|
|
|
|
list_for_each_entry(std, &qset->stds, list_node) {
|
|
if (qset->ntds == 0)
|
|
break;
|
|
qset_remove_qtd(whc, qset);
|
|
std->qtd = NULL;
|
|
}
|
|
|
|
qset->remove = 1;
|
|
}
|
|
|
|
void qset_free(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
dma_pool_free(whc->qset_pool, qset, qset->qset_dma);
|
|
}
|
|
|
|
/**
|
|
* qset_delete - wait for a qset to be unused, then free it.
|
|
*/
|
|
void qset_delete(struct whc *whc, struct whc_qset *qset)
|
|
{
|
|
wait_for_completion(&qset->remove_complete);
|
|
qset_free(whc, qset);
|
|
}
|