2008-09-17 23:34:28 +08:00
|
|
|
/*
|
|
|
|
* Wireless Host Controller (WHC) qset management.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License version
|
|
|
|
* 2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2008-09-17 23:34:28 +08:00
|
|
|
#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_alloc(whc->qset_pool, mem_flags, &dma);
|
|
|
|
if (qset == NULL)
|
|
|
|
return NULL;
|
|
|
|
memset(qset, 0, sizeof(struct whc_qset));
|
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
2009-10-12 23:45:18 +08:00
|
|
|
static void qset_fill_qh(struct whc *whc, struct whc_qset *qset, struct urb *urb)
|
2008-09-17 23:34:28 +08:00
|
|
|
{
|
|
|
|
struct usb_device *usb_dev = urb->dev;
|
2009-10-12 23:45:18 +08:00
|
|
|
struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
|
2008-09-17 23:34:28 +08:00
|
|
|
struct usb_wireless_ep_comp_descriptor *epcd;
|
|
|
|
bool is_out;
|
2009-10-12 23:45:18 +08:00
|
|
|
uint8_t phy_rate;
|
2008-09-17 23:34:28 +08:00
|
|
|
|
|
|
|
is_out = usb_pipeout(urb->pipe);
|
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
qset->max_packet = le16_to_cpu(urb->ep->desc.wMaxPacketSize);
|
2008-09-17 23:34:28 +08:00
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
|
2008-09-17 23:34:28 +08:00
|
|
|
if (epcd) {
|
|
|
|
qset->max_seq = epcd->bMaxSequence;
|
|
|
|
qset->max_burst = epcd->bMaxBurst;
|
|
|
|
} else {
|
|
|
|
qset->max_seq = 2;
|
|
|
|
qset->max_burst = 1;
|
|
|
|
}
|
|
|
|
|
2009-10-12 23:45:18 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2008-09-17 23:34:28 +08:00
|
|
|
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))
|
2009-08-24 22:02:27 +08:00
|
|
|
| QH_INFO1_MAX_PKT_LEN(qset->max_packet)
|
2008-09-17 23:34:28 +08:00
|
|
|
);
|
|
|
|
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(
|
2009-10-12 23:45:18 +08:00
|
|
|
QH_INFO3_TX_RATE(phy_rate)
|
2008-09-17 23:34:28 +08:00
|
|
|
| QH_INFO3_TX_PWR(0) /* 0 == max power */
|
|
|
|
);
|
2009-04-09 01:36:29 +08:00
|
|
|
|
|
|
|
qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
|
2008-09-17 23:34:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qset_clear - clear fields in a qset so it may be reinserted into a
|
2009-04-09 01:36:29 +08:00
|
|
|
* schedule.
|
|
|
|
*
|
|
|
|
* The sequence number and current window are not cleared (see
|
|
|
|
* qset_reset()).
|
2008-09-17 23:34:28 +08:00
|
|
|
*/
|
|
|
|
void qset_clear(struct whc *whc, struct whc_qset *qset)
|
|
|
|
{
|
|
|
|
qset->td_start = qset->td_end = qset->ntds = 0;
|
|
|
|
|
|
|
|
qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T);
|
2009-04-09 01:36:29 +08:00
|
|
|
qset->qh.status = qset->qh.status & QH_STATUS_SEQ_MASK;
|
2008-09-17 23:34:28 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-04-09 01:36:29 +08:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
2009-06-25 01:26:40 +08:00
|
|
|
qset->reset = 0;
|
2009-04-09 01:36:29 +08:00
|
|
|
|
|
|
|
qset->qh.status &= ~QH_STATUS_SEQ_MASK;
|
|
|
|
qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
|
|
|
|
}
|
|
|
|
|
2008-09-17 23:34:28 +08:00
|
|
|
/**
|
|
|
|
* 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;
|
2009-10-12 23:45:18 +08:00
|
|
|
qset_fill_qh(whc, qset, urb);
|
2008-09-17 23:34:28 +08:00
|
|
|
}
|
|
|
|
return qset;
|
|
|
|
}
|
|
|
|
|
|
|
|
void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
|
|
|
|
{
|
2009-06-25 01:26:40 +08:00
|
|
|
qset->remove = 0;
|
2008-09-17 23:34:28 +08:00
|
|
|
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--;
|
|
|
|
}
|
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2008-09-17 23:34:28 +08:00
|
|
|
/**
|
|
|
|
* 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);
|
2009-08-24 22:02:27 +08:00
|
|
|
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);
|
2008-09-17 23:34:28 +08:00
|
|
|
kfree(std->pl_virt);
|
2009-08-24 22:02:27 +08:00
|
|
|
std->pl_virt = NULL;
|
2008-09-17 23:34:28 +08:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
/* 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;
|
2008-09-17 23:34:28 +08:00
|
|
|
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);
|
|
|
|
|
|
|
|
for (p = 0; p < std->num_pointers; p++) {
|
|
|
|
std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
|
2009-08-24 22:02:27 +08:00
|
|
|
dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
|
2008-09-17 23:34:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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 == true)
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
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;
|
|
|
|
|
|
|
|
remaining = urb->transfer_buffer_length;
|
|
|
|
|
2010-05-02 02:20:01 +08:00
|
|
|
for_each_sg(urb->sg, sg, urb->num_sgs, i) {
|
2009-08-24 22:02:27 +08:00
|
|
|
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);
|
2009-10-14 21:23:55 +08:00
|
|
|
dma_remaining = min_t(size_t, sg_dma_len(sg), remaining);
|
2009-08-24 22:02:27 +08:00
|
|
|
|
|
|
|
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
|
2009-12-07 21:50:39 +08:00
|
|
|
* 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.
|
2009-08-24 22:02:27 +08:00
|
|
|
*/
|
|
|
|
if (!std
|
|
|
|
|| (prev_end & (WHCI_PAGE_SIZE-1))
|
|
|
|
|| (dma_addr & (WHCI_PAGE_SIZE-1))
|
|
|
|
|| std->len + WHCI_PAGE_SIZE > QTD_MAX_XFER_SIZE) {
|
2010-05-28 00:10:08 +08:00
|
|
|
if (std && std->len % qset->max_packet != 0)
|
2009-08-24 22:02:27 +08:00
|
|
|
return -EINVAL;
|
|
|
|
std = qset_new_std(whc, qset, urb, mem_flags);
|
|
|
|
if (std == NULL) {
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
ntds++;
|
|
|
|
p = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dma_len = dma_remaining;
|
|
|
|
|
|
|
|
/*
|
2009-12-07 21:50:39 +08:00
|
|
|
* 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.
|
2009-08-24 22:02:27 +08:00
|
|
|
*/
|
|
|
|
if (std->len + dma_len > QTD_MAX_XFER_SIZE) {
|
2009-12-07 21:50:39 +08:00
|
|
|
dma_len = (QTD_MAX_XFER_SIZE / qset->max_packet)
|
|
|
|
* qset->max_packet - std->len;
|
2009-08-24 22:02:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
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++) {
|
|
|
|
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->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;
|
|
|
|
|
2010-05-02 02:20:01 +08:00
|
|
|
for_each_sg(urb->sg, sg, urb->num_sgs, i) {
|
2009-08-24 22:02:27 +08:00
|
|
|
size_t len;
|
|
|
|
size_t sg_remaining;
|
|
|
|
void *orig;
|
|
|
|
|
|
|
|
if (remaining == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-10-14 21:23:55 +08:00
|
|
|
sg_remaining = min_t(size_t, remaining, sg->length);
|
2009-08-24 22:02:27 +08:00
|
|
|
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 (qset_fill_page_list(whc, std, mem_flags) < 0)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
std->ntds_remaining = ntds--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-09-17 23:34:28 +08:00
|
|
|
/**
|
|
|
|
* 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;
|
2009-08-24 22:02:27 +08:00
|
|
|
int ret;
|
2008-09-17 23:34:28 +08:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2010-04-03 01:27:28 +08:00
|
|
|
if (urb->num_sgs) {
|
2009-08-24 22:02:27 +08:00
|
|
|
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;
|
|
|
|
|
2008-09-17 23:34:28 +08:00
|
|
|
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;
|
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
std = qset_new_std(whc, qset, urb, mem_flags);
|
|
|
|
if (std == NULL)
|
|
|
|
goto err_no_mem;
|
|
|
|
|
2008-09-17 23:34:28 +08:00
|
|
|
std->dma_addr = transfer_dma;
|
|
|
|
std->len = std_len;
|
|
|
|
std->ntds_remaining = ntds_remaining;
|
|
|
|
|
2009-08-24 22:02:27 +08:00
|
|
|
if (qset_fill_page_list(whc, std, mem_flags) < 0)
|
|
|
|
goto err_no_mem;
|
2008-09-17 23:34:28 +08:00
|
|
|
|
|
|
|
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
|
2011-03-31 09:57:33 +08:00
|
|
|
* completely transferred or (in the case of an IN only) the LPF is
|
2008-09-17 23:34:28 +08:00
|
|
|
* 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);
|
|
|
|
}
|