diff options
author | Artem Leonenko <tikkeri@gmail.com> | 2010-12-14 23:45:50 -0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-12-16 13:32:37 -0800 |
commit | d9bb9c1820cb2a7aeb5e42a5470cf208002d9aa8 (patch) | |
tree | 758428f68ad6290ffc04c287f7bfd6cc2df48df8 /drivers/usb | |
parent | 6549e8b7f34b456d5689b98c2c0cf38c98414e47 (diff) |
USB: gadget: update ci13xxx to work with g_ether
There is one nasty scenario when CI13xxx driver fails:
a) two or more rx requests are queued (g_ether does that)
b) rx request completed, interrupt fires and ci13xxx dequeues rq
c) request complete() callback gets called and in turn it calls ep_queue()
c1) in ep_queue() request gets added to the TAIL of the rx queue list
d) ep gets primed with rq from (b)
e) interrupt fires
f) request gets popped from queue head for hw dequeue
G) requets from queue head wasn't enqueued
g1) isr_tr_complete_low() doesn't
enqueue more requests and it doesn't prime EP,
rx traffic stalls
Solution:
a) enque queued requests ASAP, i.e. before calling complete() callback.
b) don't HW enqueue and prime endpoint with recently added request and
use the oldest request in the queue.
Fixed issues:
a) ep_queue() may return an error code despite request was successfully
added to the queue (if _hardware_enqueue() fails)
b) Added requests are always processed in LIFO order, even if they are
added in complete() callback
c) Finally more than two and more queued requests are processed consistently,
even if they were added in complete() callback
The fix was successfully tested on MIPS based SoC with 4KEc CPU core and
CI13612 USB core. Board successfully boots with NFS root using g_ether
on ci13xxx udc.
Signed-off-by: Artem Leonenko <tikkeri@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/gadget/ci13xxx_udc.c | 20 |
1 files changed, 12 insertions, 8 deletions
diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c index 03505cbae87..f20e861deeb 100644 --- a/drivers/usb/gadget/ci13xxx_udc.c +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -1794,18 +1794,20 @@ __acquires(mEp->lock) dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); + if (!list_empty(&mEp->qh[mEp->dir].queue)) { + struct ci13xxx_req* mReqEnq; + + mReqEnq = list_entry(mEp->qh[mEp->dir].queue.next, + struct ci13xxx_req, queue); + _hardware_enqueue(mEp, mReqEnq); + } + if (!mReq->req.no_interrupt && mReq->req.complete != NULL) { spin_unlock(mEp->lock); mReq->req.complete(&mEp->ep, &mReq->req); spin_lock(mEp->lock); } - if (!list_empty(&mEp->qh[mEp->dir].queue)) { - mReq = list_entry(mEp->qh[mEp->dir].queue.next, - struct ci13xxx_req, queue); - _hardware_enqueue(mEp, mReq); - } - done: return retval; } @@ -2170,8 +2172,10 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, mReq->req.actual = 0; list_add_tail(&mReq->queue, &mEp->qh[mEp->dir].queue); - retval = _hardware_enqueue(mEp, mReq); - if (retval == -EALREADY || retval == -EBUSY) { + if (list_is_singular(&mEp->qh[mEp->dir].queue)) + retval = _hardware_enqueue(mEp, mReq); + + if (retval == -EALREADY) { dbg_event(_usb_addr(mEp), "QUEUE", retval); retval = 0; } |