Skip to content

Commit

Permalink
usb: chipidea: udc: add multiple td support to hardware_{en,de}queue
Browse files Browse the repository at this point in the history
This patch removes the restriction of having a limited amount of only
four active tds on one endpoint. We use the linked list implementation
to manage all tds which get added and removed by hardware_{en,de}queue.

The removal of this restriction adds the driver to run into a hardware
errata. It's possible that the hardware will still address an transfer
descriptor that already got cleaned up. To solve this the patch also
postpone the cleanup of processed tds by one.

Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Michael Grzeschik authored and Greg Kroah-Hartman committed Jun 17, 2013
1 parent cc9e6c4 commit 2e27041
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 83 deletions.
1 change: 1 addition & 0 deletions drivers/usb/chipidea/ci.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct ci13xxx_ep {
struct ci13xxx *ci;
spinlock_t *lock;
struct dma_pool *td_pool;
struct td_node *pending_td;
};

enum ci_role {
Expand Down
1 change: 0 additions & 1 deletion drivers/usb/chipidea/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
* TODO List
* - OTG
* - Interrupt Traffic
* - Handle requests which spawns into several TDs
* - GET_STATUS(device) - always reports 0
* - Gadget API (majority of optional features)
* - Suspend & Remote Wakeup
Expand Down
192 changes: 110 additions & 82 deletions drivers/usb/chipidea/udc.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,17 +369,11 @@ static int hw_usb_reset(struct ci13xxx *ci)
* UTIL block
*****************************************************************************/

static void setup_td_bits(struct td_node *tdnode, unsigned length)
{
memset(tdnode->ptr, 0, sizeof(*tdnode->ptr));
tdnode->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES));
tdnode->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES);
tdnode->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE);
}

static int add_td_to_list(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq,
unsigned length)
{
int i;
u32 temp;
struct td_node *lastnode, *node = kzalloc(sizeof(struct td_node),
GFP_ATOMIC);

Expand All @@ -393,7 +387,22 @@ static int add_td_to_list(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq,
return -ENOMEM;
}

setup_td_bits(node, length);
memset(node->ptr, 0, sizeof(struct ci13xxx_td));
node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES));
node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES);
node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE);

temp = (u32) (mReq->req.dma + mReq->req.actual);
if (length) {
node->ptr->page[0] = cpu_to_le32(temp);
for (i = 1; i < TD_PAGE_COUNT; i++) {
u32 page = temp + i * CI13XXX_PAGE_SIZE;
page &= ~TD_RESERVED_MASK;
node->ptr->page[i] = cpu_to_le32(page);
}
}

mReq->req.actual += length;

if (!list_empty(&mReq->tds)) {
/* get the last entry */
Expand Down Expand Up @@ -427,9 +436,9 @@ static inline u8 _usb_addr(struct ci13xxx_ep *ep)
static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
{
struct ci13xxx *ci = mEp->ci;
unsigned i;
int ret = 0;
unsigned length = mReq->req.length;
unsigned rest = mReq->req.length;
int pages = TD_PAGE_COUNT;
struct td_node *firstnode, *lastnode;

/* don't queue twice */
Expand All @@ -442,21 +451,29 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
if (ret)
return ret;

firstnode = list_first_entry(&mReq->tds,
struct td_node, td);
/*
* The first buffer could be not page aligned.
* In that case we have to span into one extra td.
*/
if (mReq->req.dma % PAGE_SIZE)
pages--;

setup_td_bits(firstnode, length);
if (rest == 0)
add_td_to_list(mEp, mReq, 0);

firstnode->ptr->page[0] = cpu_to_le32(mReq->req.dma);
for (i = 1; i < TD_PAGE_COUNT; i++) {
u32 page = mReq->req.dma + i * CI13XXX_PAGE_SIZE;
page &= ~TD_RESERVED_MASK;
firstnode->ptr->page[i] = cpu_to_le32(page);
while (rest > 0) {
unsigned count = min(mReq->req.length - mReq->req.actual,
(unsigned)(pages * CI13XXX_PAGE_SIZE));
add_td_to_list(mEp, mReq, count);
rest -= count;
}

if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0))
if (mReq->req.zero && mReq->req.length
&& (mReq->req.length % mEp->ep.maxpacket == 0))
add_td_to_list(mEp, mReq, 0);

firstnode = list_first_entry(&mReq->tds, struct td_node, td);

lastnode = list_entry(mReq->tds.prev,
struct td_node, td);

Expand All @@ -465,6 +482,7 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
lastnode->ptr->token |= cpu_to_le32(TD_IOC);
wmb();

mReq->req.actual = 0;
if (!list_empty(&mEp->qh.queue)) {
struct ci13xxx_req *mReqPrev;
int n = hw_ep_bit(mEp->num, mEp->dir);
Expand Down Expand Up @@ -511,6 +529,19 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
return ret;
}

/*
* free_pending_td: remove a pending request for the endpoint
* @mEp: endpoint
*/
static void free_pending_td(struct ci13xxx_ep *mEp)
{
struct td_node *pending = mEp->pending_td;

dma_pool_free(mEp->td_pool, pending->ptr, pending->dma);
mEp->pending_td = NULL;
kfree(pending);
}

/**
* _hardware_dequeue: handles a request at hardware level
* @gadget: gadget
Expand All @@ -521,42 +552,62 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
{
u32 tmptoken;
struct td_node *node, *tmpnode, *firstnode;
struct td_node *node, *tmpnode;
unsigned remaining_length;
unsigned actual = mReq->req.length;

if (mReq->req.status != -EALREADY)
return -EINVAL;

firstnode = list_first_entry(&mReq->tds,
struct td_node, td);
mReq->req.status = 0;

list_for_each_entry_safe(node, tmpnode, &mReq->tds, td) {
tmptoken = le32_to_cpu(node->ptr->token);
if ((TD_STATUS_ACTIVE & tmptoken) != 0)
if ((TD_STATUS_ACTIVE & tmptoken) != 0) {
mReq->req.status = -EALREADY;
return -EBUSY;
if (node != firstnode) {
dma_pool_free(mEp->td_pool, node->ptr, node->dma);
list_del_init(&node->td);
node->ptr = NULL;
kfree(node);
}
}

mReq->req.status = 0;
remaining_length = (tmptoken & TD_TOTAL_BYTES);
remaining_length >>= __ffs(TD_TOTAL_BYTES);
actual -= remaining_length;

mReq->req.status = tmptoken & TD_STATUS;
if ((TD_STATUS_HALTED & mReq->req.status)) {
mReq->req.status = -EPIPE;
break;
} else if ((TD_STATUS_DT_ERR & mReq->req.status)) {
mReq->req.status = -EPROTO;
break;
} else if ((TD_STATUS_TR_ERR & mReq->req.status)) {
mReq->req.status = -EILSEQ;
break;
}

if (remaining_length) {
if (mEp->dir) {
mReq->req.status = -EPROTO;
break;
}
}
/*
* As the hardware could still address the freed td
* which will run the udc unusable, the cleanup of the
* td has to be delayed by one.
*/
if (mEp->pending_td)
free_pending_td(mEp);

mEp->pending_td = node;
list_del_init(&node->td);
}

usb_gadget_unmap_request(&mEp->ci->gadget, &mReq->req, mEp->dir);

mReq->req.status = tmptoken & TD_STATUS;
if ((TD_STATUS_HALTED & mReq->req.status) != 0)
mReq->req.status = -1;
else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0)
mReq->req.status = -1;
else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0)
mReq->req.status = -1;
mReq->req.actual += actual;

mReq->req.actual = tmptoken & TD_TOTAL_BYTES;
mReq->req.actual >>= __ffs(TD_TOTAL_BYTES);
mReq->req.actual = mReq->req.length - mReq->req.actual;
mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual;
if (mReq->req.status)
return mReq->req.status;

return mReq->req.actual;
}
Expand All @@ -572,7 +623,7 @@ static int _ep_nuke(struct ci13xxx_ep *mEp)
__releases(mEp->lock)
__acquires(mEp->lock)
{
struct td_node *node, *tmpnode, *firstnode;
struct td_node *node, *tmpnode;
if (mEp == NULL)
return -EINVAL;

Expand All @@ -585,17 +636,11 @@ __acquires(mEp->lock)
list_entry(mEp->qh.queue.next,
struct ci13xxx_req, queue);

firstnode = list_first_entry(&mReq->tds,
struct td_node, td);

list_for_each_entry_safe(node, tmpnode, &mReq->tds, td) {
if (node != firstnode) {
dma_pool_free(mEp->td_pool, node->ptr,
node->dma);
list_del_init(&node->td);
node->ptr = NULL;
kfree(node);
}
dma_pool_free(mEp->td_pool, node->ptr, node->dma);
list_del_init(&node->td);
node->ptr = NULL;
kfree(node);
}

list_del_init(&mReq->queue);
Expand All @@ -607,6 +652,10 @@ __acquires(mEp->lock)
spin_lock(mEp->lock);
}
}

if (mEp->pending_td)
free_pending_td(mEp);

return 0;
}

Expand Down Expand Up @@ -742,11 +791,6 @@ static int _ep_queue(struct usb_ep *ep, struct usb_request *req,
return -EBUSY;
}

if (req->length > (TD_PAGE_COUNT - 1) * CI13XXX_PAGE_SIZE) {
dev_err(mEp->ci->dev, "request bigger than one td\n");
return -EMSGSIZE;
}

/* push request */
mReq->req.status = -EINPROGRESS;
mReq->req.actual = 0;
Expand Down Expand Up @@ -882,13 +926,9 @@ __acquires(mEp->lock)
struct ci13xxx_req *mReq, *mReqTemp;
struct ci13xxx_ep *mEpTemp = mEp;
int retval = 0;
struct td_node *firstnode;

list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue,
queue) {
firstnode = list_first_entry(&mReq->tds,
struct td_node, td);

retval = _hardware_dequeue(mEp, mReq);
if (retval < 0)
break;
Expand Down Expand Up @@ -1189,29 +1229,15 @@ static int ep_disable(struct usb_ep *ep)
*/
static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
{
struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep);
struct ci13xxx_req *mReq = NULL;
struct td_node *node;

if (ep == NULL)
return NULL;

mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags);
node = kzalloc(sizeof(struct td_node), gfp_flags);
if (mReq != NULL && node != NULL) {
if (mReq != NULL) {
INIT_LIST_HEAD(&mReq->queue);
INIT_LIST_HEAD(&mReq->tds);
INIT_LIST_HEAD(&node->td);

node->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags,
&node->dma);
if (node->ptr == NULL) {
kfree(node);
kfree(mReq);
mReq = NULL;
} else {
list_add_tail(&node->td, &mReq->tds);
}
}

return (mReq == NULL) ? NULL : &mReq->req;
Expand All @@ -1226,7 +1252,7 @@ static void ep_free_request(struct usb_ep *ep, struct usb_request *req)
{
struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep);
struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req);
struct td_node *firstnode;
struct td_node *node, *tmpnode;
unsigned long flags;

if (ep == NULL || req == NULL) {
Expand All @@ -1238,11 +1264,13 @@ static void ep_free_request(struct usb_ep *ep, struct usb_request *req)

spin_lock_irqsave(mEp->lock, flags);

firstnode = list_first_entry(&mReq->tds,
struct td_node, td);
list_for_each_entry_safe(node, tmpnode, &mReq->tds, td) {
dma_pool_free(mEp->td_pool, node->ptr, node->dma);
list_del_init(&node->td);
node->ptr = NULL;
kfree(node);
}

if (firstnode->ptr)
dma_pool_free(mEp->td_pool, firstnode->ptr, firstnode->dma);
kfree(mReq);

spin_unlock_irqrestore(mEp->lock, flags);
Expand Down

0 comments on commit 2e27041

Please sign in to comment.