linux-stable/drivers/usb/musb/musb_gadget.c

2146 lines
56 KiB
C
Raw Normal View History

/*
* MUSB OTG driver peripheral support
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
* Copyright (C) 2009 MontaVista Software, Inc. <source@mvista.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/module.h>
#include <linux/smp.h>
#include <linux/spinlock.h>
#include <linux/delay.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 08:04:11 +00:00
#include <linux/slab.h>
#include "musb_core.h"
/* ----------------------------------------------------------------------- */
#define is_buffer_mapped(req) (is_dma_capable() && \
(req->map_state != UN_MAPPED))
/* Maps the buffer to dma */
static inline void map_dma_buffer(struct musb_request *request,
struct musb *musb, struct musb_ep *musb_ep)
{
int compatible = true;
struct dma_controller *dma = musb->dma_controller;
request->map_state = UN_MAPPED;
if (!is_dma_capable() || !musb_ep->dma)
return;
/* Check if DMA engine can handle this request.
* DMA code must reject the USB request explicitly.
* Default behaviour is to map the request.
*/
if (dma->is_compatible)
compatible = dma->is_compatible(musb_ep->dma,
musb_ep->packet_sz, request->request.buf,
request->request.length);
if (!compatible)
return;
if (request->request.dma == DMA_ADDR_INVALID) {
dma_addr_t dma_addr;
int ret;
dma_addr = dma_map_single(
musb->controller,
request->request.buf,
request->request.length,
request->tx
? DMA_TO_DEVICE
: DMA_FROM_DEVICE);
ret = dma_mapping_error(musb->controller, dma_addr);
if (ret)
return;
request->request.dma = dma_addr;
request->map_state = MUSB_MAPPED;
} else {
dma_sync_single_for_device(musb->controller,
request->request.dma,
request->request.length,
request->tx
? DMA_TO_DEVICE
: DMA_FROM_DEVICE);
request->map_state = PRE_MAPPED;
}
}
/* Unmap the buffer from dma and maps it back to cpu */
static inline void unmap_dma_buffer(struct musb_request *request,
struct musb *musb)
{
struct musb_ep *musb_ep = request->ep;
if (!is_buffer_mapped(request) || !musb_ep->dma)
return;
if (request->request.dma == DMA_ADDR_INVALID) {
dev_vdbg(musb->controller,
"not unmapping a never mapped buffer\n");
return;
}
if (request->map_state == MUSB_MAPPED) {
dma_unmap_single(musb->controller,
request->request.dma,
request->request.length,
request->tx
? DMA_TO_DEVICE
: DMA_FROM_DEVICE);
request->request.dma = DMA_ADDR_INVALID;
} else { /* PRE_MAPPED */
dma_sync_single_for_cpu(musb->controller,
request->request.dma,
request->request.length,
request->tx
? DMA_TO_DEVICE
: DMA_FROM_DEVICE);
}
request->map_state = UN_MAPPED;
}
/*
* Immediately complete a request.
*
* @param request the request to complete
* @param status the status to complete the request with
* Context: controller locked, IRQs blocked.
*/
void musb_g_giveback(
struct musb_ep *ep,
struct usb_request *request,
int status)
__releases(ep->musb->lock)
__acquires(ep->musb->lock)
{
struct musb_request *req;
struct musb *musb;
int busy = ep->busy;
req = to_musb_request(request);
list_del(&req->list);
if (req->request.status == -EINPROGRESS)
req->request.status = status;
musb = req->musb;
ep->busy = 1;
spin_unlock(&musb->lock);
if (!dma_mapping_error(&musb->g.dev, request->dma))
unmap_dma_buffer(req, musb);
if (request->status == 0)
dev_dbg(musb->controller, "%s done request %p, %d/%d\n",
ep->end_point.name, request,
req->request.actual, req->request.length);
else
dev_dbg(musb->controller, "%s request %p, %d/%d fault %d\n",
ep->end_point.name, request,
req->request.actual, req->request.length,
request->status);
usb_gadget_giveback_request(&req->ep->end_point, &req->request);
spin_lock(&musb->lock);
ep->busy = busy;
}
/* ----------------------------------------------------------------------- */
/*
* Abort requests queued to an endpoint using the status. Synchronous.
* caller locked controller and blocked irqs, and selected this ep.
*/
static void nuke(struct musb_ep *ep, const int status)
{
struct musb *musb = ep->musb;
struct musb_request *req = NULL;
void __iomem *epio = ep->musb->endpoints[ep->current_epnum].regs;
ep->busy = 1;
if (is_dma_capable() && ep->dma) {
struct dma_controller *c = ep->musb->dma_controller;
int value;
if (ep->is_in) {
/*
* The programming guide says that we must not clear
* the DMAMODE bit before DMAENAB, so we only
* clear it in the second write...
*/
musb_writew(epio, MUSB_TXCSR,
MUSB_TXCSR_DMAMODE | MUSB_TXCSR_FLUSHFIFO);
musb_writew(epio, MUSB_TXCSR,
0 | MUSB_TXCSR_FLUSHFIFO);
} else {
musb_writew(epio, MUSB_RXCSR,
0 | MUSB_RXCSR_FLUSHFIFO);
musb_writew(epio, MUSB_RXCSR,
0 | MUSB_RXCSR_FLUSHFIFO);
}
value = c->channel_abort(ep->dma);
dev_dbg(musb->controller, "%s: abort DMA --> %d\n",
ep->name, value);
c->channel_release(ep->dma);
ep->dma = NULL;
}
while (!list_empty(&ep->req_list)) {
req = list_first_entry(&ep->req_list, struct musb_request, list);
musb_g_giveback(ep, &req->request, status);
}
}
/* ----------------------------------------------------------------------- */
/* Data transfers - pure PIO, pure DMA, or mixed mode */
/*
* This assumes the separate CPPI engine is responding to DMA requests
* from the usb core ... sequenced a bit differently from mentor dma.
*/
static inline int max_ep_writesize(struct musb *musb, struct musb_ep *ep)
{
if (can_bulk_split(musb, ep->type))
return ep->hw_ep->max_packet_sz_tx;
else
return ep->packet_sz;
}
/*
* An endpoint is transmitting data. This can be called either from
* the IRQ routine or from ep.queue() to kickstart a request on an
* endpoint.
*
* Context: controller locked, IRQs blocked, endpoint selected
*/
static void txstate(struct musb *musb, struct musb_request *req)
{
u8 epnum = req->epnum;
struct musb_ep *musb_ep;
void __iomem *epio = musb->endpoints[epnum].regs;
struct usb_request *request;
u16 fifo_count = 0, csr;
int use_dma = 0;
musb_ep = req->ep;
usb: musb: handle nuked ep dma interrupt User can trigger disabling of gadget at run time while the transfers are going on. Eg: 1: rmmod of musb driver while transfers are going on Eg: 2: On android doing: echo 0 > /sys/class/android_usb/android0/enable While a big file transfer is going on via PTP/MTP. In such a case, musb_gadget_disable() calls nuke() but the dma interrupt may still happen for an endpoint since hw would raise the interrupt in anycase. This can result in a NULL pointer access crash: [ 314.030426] PC is at txstate+0x74/0x20c [ 314.034759] LR is at musb_g_tx+0x140/0x204 [ 314.039489] pc : [<c03506f4>] lr : [<c0350bcc>] psr: 20000193 [ 314.039520] sp : c783bc68 ip : 00000002 fp : c783bc9c [ 314.052429] r10: 00000018 r9 : 00000000 r8 : 00000200 [ 314.058258] r7 : 00000000 r6 : fc0ab130 r5 : c781a410 r4 : c6caf640 [ 314.065643] r3 : 00000000 r2 : 00000000 r1 : 00000000 r0 : c781a000 [ 315.083251] Backtrace: [ 315.086242] [<c0350680>] (txstate+0x0/0x20c) from [<c0350bcc>] (musb_g_tx+0x140/0x204) [ 315.095123] [<c0350a8c>] (musb_g_tx+0x0/0x204) from [<c034eb00>] (musb_dma_completion+0x40/0x54) [ 315.104980] [<c034eac0>] (musb_dma_completion+0x0/0x54) from [<c0351e6c>] (dma_controller_irq+0x118/0x184) [ 315.115661] [<c0351d54>] (dma_controller_irq+0x0/0x184) from [<c00d86b8>] (handle_irq_event_percpu+0x54/0x188) So put protection in code to handle possiblity of getting an interrupt for an endpoint that might have been already nuked. Reported-by: Todd Poynor <toddpoynor@google.com> Signed-off-by: Vikram Pandita <vikram.pandita@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2012-05-18 20:48:04 +00:00
/* Check if EP is disabled */
if (!musb_ep->desc) {
dev_dbg(musb->controller, "ep:%s disabled - ignore request\n",
musb_ep->end_point.name);
return;
}
/* we shouldn't get here while DMA is active ... but we do ... */
if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) {
dev_dbg(musb->controller, "dma pending...\n");
return;
}
/* read TXCSR before */
csr = musb_readw(epio, MUSB_TXCSR);
request = &req->request;
fifo_count = min(max_ep_writesize(musb, musb_ep),
(int)(request->length - request->actual));
if (csr & MUSB_TXCSR_TXPKTRDY) {
dev_dbg(musb->controller, "%s old packet still ready , txcsr %03x\n",
musb_ep->end_point.name, csr);
return;
}
if (csr & MUSB_TXCSR_P_SENDSTALL) {
dev_dbg(musb->controller, "%s stalling, txcsr %03x\n",
musb_ep->end_point.name, csr);
return;
}
dev_dbg(musb->controller, "hw_ep%d, maxpacket %d, fifo count %d, txcsr %03x\n",
epnum, musb_ep->packet_sz, fifo_count,
csr);
#ifndef CONFIG_MUSB_PIO_ONLY
if (is_buffer_mapped(req)) {
struct dma_controller *c = musb->dma_controller;
size_t request_size;
/* setup DMA, then program endpoint CSR */
request_size = min_t(size_t, request->length - request->actual,
musb_ep->dma->max_len);
use_dma = (request->dma != DMA_ADDR_INVALID && request_size);
/* MUSB_TXCSR_P_ISO is still set correctly */
if (musb_dma_inventra(musb) || musb_dma_ux500(musb)) {
if (request_size < musb_ep->packet_sz)
musb_ep->dma->desired_mode = 0;
else
musb_ep->dma->desired_mode = 1;
use_dma = use_dma && c->channel_program(
musb_ep->dma, musb_ep->packet_sz,
musb_ep->dma->desired_mode,
request->dma + request->actual, request_size);
if (use_dma) {
if (musb_ep->dma->desired_mode == 0) {
/*
* We must not clear the DMAMODE bit
* before the DMAENAB bit -- and the
* latter doesn't always get cleared
* before we get here...
*/
csr &= ~(MUSB_TXCSR_AUTOSET
| MUSB_TXCSR_DMAENAB);
musb_writew(epio, MUSB_TXCSR, csr
| MUSB_TXCSR_P_WZC_BITS);
csr &= ~MUSB_TXCSR_DMAMODE;
csr |= (MUSB_TXCSR_DMAENAB |
MUSB_TXCSR_MODE);
/* against programming guide */
} else {
csr |= (MUSB_TXCSR_DMAENAB
| MUSB_TXCSR_DMAMODE
| MUSB_TXCSR_MODE);
/*
* Enable Autoset according to table
* below
* bulk_split hb_mult Autoset_Enable
* 0 0 Yes(Normal)
* 0 >0 No(High BW ISO)
* 1 0 Yes(HS bulk)
* 1 >0 Yes(FS bulk)
*/
if (!musb_ep->hb_mult ||
(musb_ep->hb_mult &&
can_bulk_split(musb,
musb_ep->type)))
csr |= MUSB_TXCSR_AUTOSET;
}
csr &= ~MUSB_TXCSR_P_UNDERRUN;
musb_writew(epio, MUSB_TXCSR, csr);
}
}
if (is_cppi_enabled(musb)) {
/* program endpoint CSR first, then setup DMA */
csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY);
csr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_DMAMODE |
MUSB_TXCSR_MODE;
musb_writew(epio, MUSB_TXCSR, (MUSB_TXCSR_P_WZC_BITS &
~MUSB_TXCSR_P_UNDERRUN) | csr);
/* ensure writebuffer is empty */
csr = musb_readw(epio, MUSB_TXCSR);
/*
* NOTE host side sets DMAENAB later than this; both are
* OK since the transfer dma glue (between CPPI and
* Mentor fifos) just tells CPPI it could start. Data
* only moves to the USB TX fifo when both fifos are
* ready.
*/
/*
* "mode" is irrelevant here; handle terminating ZLPs
* like PIO does, since the hardware RNDIS mode seems
* unreliable except for the
* last-packet-is-already-short case.
*/
use_dma = use_dma && c->channel_program(
musb_ep->dma, musb_ep->packet_sz,
0,
request->dma + request->actual,
request_size);
if (!use_dma) {
c->channel_release(musb_ep->dma);
musb_ep->dma = NULL;
csr &= ~MUSB_TXCSR_DMAENAB;
musb_writew(epio, MUSB_TXCSR, csr);
/* invariant: prequest->buf is non-null */
}
} else if (tusb_dma_omap(musb))
use_dma = use_dma && c->channel_program(
musb_ep->dma, musb_ep->packet_sz,
request->zero,
request->dma + request->actual,
request_size);
}
#endif
if (!use_dma) {
/*
* Unmap the dma buffer back to cpu if dma channel
* programming fails
*/
unmap_dma_buffer(req, musb);
musb_write_fifo(musb_ep->hw_ep, fifo_count,
(u8 *) (request->buf + request->actual));
request->actual += fifo_count;
csr |= MUSB_TXCSR_TXPKTRDY;
csr &= ~MUSB_TXCSR_P_UNDERRUN;
musb_writew(epio, MUSB_TXCSR, csr);
}
/* host may already have the data when this message shows... */
dev_dbg(musb->controller, "%s TX/IN %s len %d/%d, txcsr %04x, fifo %d/%d\n",
musb_ep->end_point.name, use_dma ? "dma" : "pio",
request->actual, request->length,
musb_readw(epio, MUSB_TXCSR),
fifo_count,
musb_readw(epio, MUSB_TXMAXP));
}
/*
* FIFO state update (e.g. data ready).
* Called from IRQ, with controller locked.
*/
void musb_g_tx(struct musb *musb, u8 epnum)
{
u16 csr;
struct musb_request *req;
struct usb_request *request;
u8 __iomem *mbase = musb->mregs;
struct musb_ep *musb_ep = &musb->endpoints[epnum].ep_in;
void __iomem *epio = musb->endpoints[epnum].regs;
struct dma_channel *dma;
musb_ep_select(mbase, epnum);
req = next_request(musb_ep);
request = &req->request;
csr = musb_readw(epio, MUSB_TXCSR);
dev_dbg(musb->controller, "<== %s, txcsr %04x\n", musb_ep->end_point.name, csr);
dma = is_dma_capable() ? musb_ep->dma : NULL;
/*
* REVISIT: for high bandwidth, MUSB_TXCSR_P_INCOMPTX
* probably rates reporting as a host error.
*/
if (csr & MUSB_TXCSR_P_SENTSTALL) {
csr |= MUSB_TXCSR_P_WZC_BITS;
csr &= ~MUSB_TXCSR_P_SENTSTALL;
musb_writew(epio, MUSB_TXCSR, csr);
return;
}
if (csr & MUSB_TXCSR_P_UNDERRUN) {
/* We NAKed, no big deal... little reason to care. */
csr |= MUSB_TXCSR_P_WZC_BITS;
csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY);
musb_writew(epio, MUSB_TXCSR, csr);
dev_vdbg(musb->controller, "underrun on ep%d, req %p\n",
epnum, request);
}
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
/*
* SHOULD NOT HAPPEN... has with CPPI though, after
* changing SENDSTALL (and other cases); harmless?
*/
dev_dbg(musb->controller, "%s dma still busy?\n", musb_ep->end_point.name);
return;
}
if (request) {
u8 is_dma = 0;
bool short_packet = false;
if (dma && (csr & MUSB_TXCSR_DMAENAB)) {
is_dma = 1;
csr |= MUSB_TXCSR_P_WZC_BITS;
csr &= ~(MUSB_TXCSR_DMAENAB | MUSB_TXCSR_P_UNDERRUN |
MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_AUTOSET);
musb_writew(epio, MUSB_TXCSR, csr);
/* Ensure writebuffer is empty. */
csr = musb_readw(epio, MUSB_TXCSR);
request->actual += musb_ep->dma->actual_len;
dev_dbg(musb->controller, "TXCSR%d %04x, DMA off, len %zu, req %p\n",
epnum, csr, musb_ep->dma->actual_len, request);
}
/*
* First, maybe a terminating short packet. Some DMA
* engines might handle this by themselves.
*/
if ((request->zero && request->length)
&& (request->length % musb_ep->packet_sz == 0)
&& (request->actual == request->length))
short_packet = true;
if ((musb_dma_inventra(musb) || musb_dma_ux500(musb)) &&
(is_dma && (!dma->desired_mode ||
(request->actual &
(musb_ep->packet_sz - 1)))))
short_packet = true;
if (short_packet) {
/*
* On DMA completion, FIFO may not be
* available yet...
*/
if (csr & MUSB_TXCSR_TXPKTRDY)
return;
dev_dbg(musb->controller, "sending zero pkt\n");
musb_writew(epio, MUSB_TXCSR, MUSB_TXCSR_MODE
| MUSB_TXCSR_TXPKTRDY);
request->zero = 0;
}
if (request->actual == request->length) {
musb_g_giveback(musb_ep, request, 0);
usb: musb: Reselect index reg in interrupt context musb INDEX register is getting modified/corrupted during temporary un-locking in a SMP system. Set this register with proper value after re-acquiring the lock Scenario: --------- CPU1 is handling a data transfer completion interrupt received for the CLASS1 EP CPU2 is handling a CLASS2 thread which is queuing data to musb for transfer Below is the error sequence: CPU1 | CPU2 -------------------------------------------------------------------- Data transfer completion inter- | rupt recieved. | | musb INDEX reg set to CLASS1 EP | | musb LOCK is acquired. | | | CLASS2 thread queues data. | | CLASS2 thread tries to acquire musb | LOCK but lock is already taken by | CLASS1, so CLASS2 thread is | spinning. | From Interrupt Context musb | giveback function is called | | The giveback function releases | CLASS2 thread now acquires LOCK LOCK | | ClASS1 Request's completion cal-| ClASS2 schedules the data transfer and lback is called | sets the MUSB INDEX to Class2 EP number | Interrupt handler for CLASS1 EP | tries to acquire LOCK and is | spinning | | Interrupt for Class1 EP acquires| Class2 completes the scheduling etc and the MUSB LOCK | releases the musb LOCK | Interrupt for Class1 EP schedul-| es the next data transfer | but musb INDEX register is still| set to CLASS2 EP | Since the MUSB INDEX register is set to a different endpoint, we read and modify the wrong registers. Hence data transfer will not happen properly. This results in unpredictable behavior So, the MUSB INDEX register is set to proper value again when interrupt re-acquires the lock Cc: stable@vger.kernel.org Signed-off-by: Supriya Karanth <supriya.karanth@stericsson.com> Signed-off-by: Praveena Nadahally <praveen.nadahally@stericsson.com> Reviewed-by: srinidhi kasagar <srinidhi.kasagar@stericsson.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2012-02-17 09:24:52 +00:00
/*
* In the giveback function the MUSB lock is
* released and acquired after sometime. During
* this time period the INDEX register could get
* changed by the gadget_queue function especially
* on SMP systems. Reselect the INDEX to be sure
* we are reading/modifying the right registers
*/
musb_ep_select(mbase, epnum);
req = musb_ep->desc ? next_request(musb_ep) : NULL;
if (!req) {
dev_dbg(musb->controller, "%s idle now\n",
musb_ep->end_point.name);
return;
}
}
txstate(musb, req);
}
}
/* ------------------------------------------------------------ */
/*
* Context: controller locked, IRQs blocked, endpoint selected
*/
static void rxstate(struct musb *musb, struct musb_request *req)
{
const u8 epnum = req->epnum;
struct usb_request *request = &req->request;
usb: musb: gadget: fix kernel panic if using out ep with FIFO_TXRX style For shared fifo hw endpoint(with FIFO_TXRX style), only ep_in field of musb_hw_ep is intialized in musb_g_init_endpoints, and ep_out is not initialized, but musb_g_rx and rxstate may access ep_out field of musb_hw_ep by the method below: musb_ep = &musb->endpoints[epnum].ep_out which can cause the kernel panic[1] below, this patch fixes the issue by getting 'musb_ep' from '&musb->endpoints[epnum].ep_in' for shared fifo endpoint. [1], kernel panic [root@OMAP3EVM /]# musb_interrupt 1583: ** IRQ peripheral usb0008 tx0000 rx4000 musb_stage0_irq 460: <== Power=f0, DevCtl=99, int_usb=0x8 musb_g_rx 772: <== (null), rxcsr 4007 ffffffe8 musb_g_rx 786: iso overrun on ffffffe8 Unable to handle kernel NULL pointer dereference at virtual address 00000008 pgd = c0004000 [00000008] *pgd=00000000 Internal error: Oops: 17 [#1] PREEMPT last sysfs file: /sys/devices/platform/musb_hdrc/usb1/usb_device/usbdev1.1/dev Modules linked in: g_zero CPU: 0 Tainted: G W (2.6.35-rc6-gkh-wl+ #92) PC is at musb_g_rx+0xfc/0x2ec LR is at vprintk+0x3f4/0x458 pc : [<c02c07a4>] lr : [<c006ccb0>] psr: 20000193 sp : c760bd78 ip : c03c9d70 fp : c760bdbc r10: 00000000 r9 : fa0ab1e0 r8 : 0000000e r7 : c7e80158 r6 : ffffffe8 r5 : 00000001 r4 : 00004003 r3 : 00010003 r2 : c760bcd8 r1 : c03cd030 r0 : 0000002e Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c5387d Table: 8778c019 DAC: 00000017 Process kmemleak (pid: 421, stack limit = 0xc760a2e8) Stack: (0xc760bd78 to 0xc760c000) bd60: ffffffe8 c04b1b58 bd80: ffffffe8 c7c01ac0 00000000 c7e80d24 c0084238 00000001 00000001 c7e80158 bda0: 0000000e 00000008 00000099 000000f0 c760be04 c760bdc0 c02bcd68 c02c06b4 bdc0: 00000099 00000008 00004000 c760bdd8 c03cc4f8 00000000 00000002 c7e80158 bde0: c7d2e300 60000193 c760a000 0000005c 00000000 00000000 c760be24 c760be08 be00: c02bcecc c02bc1ac c7d2e300 c7d2e300 0000005c c760a000 c760be54 c760be28 be20: c00ad698 c02bce6c 00000000 c7d2e300 c067c258 0000005c c067c294 00000001 be40: c760a000 00000000 c760be74 c760be58 c00af984 c00ad5fc 0000005c 00000000 be60: 00000000 00000002 c760be8c c760be78 c0039080 c00af8d0 ffffffff fa200000 be80: c760beec c760be90 c0039b6c c003900c 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff 00000000 c068bae8 bee0: c760bf24 c760bef0 c00ff7d0 c0064ec4 00000001 00000000 c00ff700 00000000 bf00: c0087f00 00000000 60000013 c0d76a70 c0e23795 00000001 c760bf4c c760bf28 bf20: c00ffdd8 c00ff70c c068bb08 c068bae8 60000013 c0100938 c068bb30 00000000 bf40: c760bf84 c760bf50 c010014c c00ffd84 00000001 00000000 c010000c 00012c00 bf60: c7c33f04 00012c00 c7c33f04 00000000 c0100938 00000000 c760bf9c c760bf88 bf80: c01009a8 c0100018 c760bfa8 c7c33f04 c760bff4 c760bfa0 c0088000 c0100944 bfa0: c760bf98 00000000 00000000 00000001 dead4ead ffffffff ffffffff c08ba2bc bfc0: 00000000 c049e7fa 00000000 c0087f70 c760bfd0 c760bfd0 c7c33f04 c0087f70 bfe0: c006f5e8 00000013 00000000 c760bff8 c006f5e8 c0087f7c 7f0004ff df2000ff Backtrace: [<c02c06a8>] (musb_g_rx+0x0/0x2ec) from [<c02bcd68>] (musb_interrupt+0xbc8/0xcc0) [<c02bc1a0>] (musb_interrupt+0x0/0xcc0) from [<c02bcecc>] (generic_interrupt+0x6c/0x84) [<c02bce60>] (generic_interrupt+0x0/0x84) from [<c00ad698>] (handle_IRQ_event+0xa8/0x1ec) r7:c760a000 r6:0000005c r5:c7d2e300 r4:c7d2e300 [<c00ad5f0>] (handle_IRQ_event+0x0/0x1ec) from [<c00af984>] (handle_level_irq+0xc0/0x13c) [<c00af8c4>] (handle_level_irq+0x0/0x13c) from [<c0039080>] (asm_do_IRQ+0x80/0xa0) r7:00000002 r6:00000000 r5:00000000 r4:0000005c [<c0039000>] (asm_do_IRQ+0x0/0xa0) from [<c0039b6c>] (__irq_svc+0x4c/0xb4) Exception stack(0xc760be90 to 0xc760bed8) be80: 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff r5:fa200000 r4:ffffffff [<c0064eb8>] (sub_preempt_count+0x0/0x100) from [<c00ff7d0>] (find_and_get_object+0xd0/0x110) r5:c068bae8 r4:00000000 [<c00ff700>] (find_and_get_object+0x0/0x110) from [<c00ffdd8>] (scan_block+0x60/0x104) r8:00000001 r7:c0e23795 r6:c0d76a70 r5:60000013 r4:00000000 [<c00ffd78>] (scan_block+0x0/0x104) from [<c010014c>] (kmemleak_scan+0x140/0x484) [<c010000c>] (kmemleak_scan+0x0/0x484) from [<c01009a8>] (kmemleak_scan_thread+0x70/0xcc) r8:00000000 r7:c0100938 r6:00000000 r5:c7c33f04 r4:00012c00 [<c0100938>] (kmemleak_scan_thread+0x0/0xcc) from [<c0088000>] (kthread+0x90/0x98) r5:c7c33f04 r4:c760bfa8 [<c0087f70>] (kthread+0x0/0x98) from [<c006f5e8>] (do_exit+0x0/0x684) r7:00000013 r6:c006f5e8 r5:c0087f70 r4:c7c33f04 Code: e3002312 e58d6000 e2833e16 eb0422d5 (e5963020) ---[ end trace f3d5e96f75c297b7 ]--- Signed-off-by: Ming Lei <tom.leiming@gmail.com> Reviewed-by: Sergei Shtylyov <sshtylyov@mvista.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: Anand Gadiyar <gadiyar@ti.com> Cc: Mike Frysinger <vapier@gentoo.org> Cc: Sergei Shtylyov <sshtylyov@ru.mvista.com> Cc: stable <stable@kernel.org> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-09-20 07:32:01 +00:00
struct musb_ep *musb_ep;
void __iomem *epio = musb->endpoints[epnum].regs;
unsigned len = 0;
u16 fifo_count;
u16 csr = musb_readw(epio, MUSB_RXCSR);
usb: musb: gadget: fix kernel panic if using out ep with FIFO_TXRX style For shared fifo hw endpoint(with FIFO_TXRX style), only ep_in field of musb_hw_ep is intialized in musb_g_init_endpoints, and ep_out is not initialized, but musb_g_rx and rxstate may access ep_out field of musb_hw_ep by the method below: musb_ep = &musb->endpoints[epnum].ep_out which can cause the kernel panic[1] below, this patch fixes the issue by getting 'musb_ep' from '&musb->endpoints[epnum].ep_in' for shared fifo endpoint. [1], kernel panic [root@OMAP3EVM /]# musb_interrupt 1583: ** IRQ peripheral usb0008 tx0000 rx4000 musb_stage0_irq 460: <== Power=f0, DevCtl=99, int_usb=0x8 musb_g_rx 772: <== (null), rxcsr 4007 ffffffe8 musb_g_rx 786: iso overrun on ffffffe8 Unable to handle kernel NULL pointer dereference at virtual address 00000008 pgd = c0004000 [00000008] *pgd=00000000 Internal error: Oops: 17 [#1] PREEMPT last sysfs file: /sys/devices/platform/musb_hdrc/usb1/usb_device/usbdev1.1/dev Modules linked in: g_zero CPU: 0 Tainted: G W (2.6.35-rc6-gkh-wl+ #92) PC is at musb_g_rx+0xfc/0x2ec LR is at vprintk+0x3f4/0x458 pc : [<c02c07a4>] lr : [<c006ccb0>] psr: 20000193 sp : c760bd78 ip : c03c9d70 fp : c760bdbc r10: 00000000 r9 : fa0ab1e0 r8 : 0000000e r7 : c7e80158 r6 : ffffffe8 r5 : 00000001 r4 : 00004003 r3 : 00010003 r2 : c760bcd8 r1 : c03cd030 r0 : 0000002e Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c5387d Table: 8778c019 DAC: 00000017 Process kmemleak (pid: 421, stack limit = 0xc760a2e8) Stack: (0xc760bd78 to 0xc760c000) bd60: ffffffe8 c04b1b58 bd80: ffffffe8 c7c01ac0 00000000 c7e80d24 c0084238 00000001 00000001 c7e80158 bda0: 0000000e 00000008 00000099 000000f0 c760be04 c760bdc0 c02bcd68 c02c06b4 bdc0: 00000099 00000008 00004000 c760bdd8 c03cc4f8 00000000 00000002 c7e80158 bde0: c7d2e300 60000193 c760a000 0000005c 00000000 00000000 c760be24 c760be08 be00: c02bcecc c02bc1ac c7d2e300 c7d2e300 0000005c c760a000 c760be54 c760be28 be20: c00ad698 c02bce6c 00000000 c7d2e300 c067c258 0000005c c067c294 00000001 be40: c760a000 00000000 c760be74 c760be58 c00af984 c00ad5fc 0000005c 00000000 be60: 00000000 00000002 c760be8c c760be78 c0039080 c00af8d0 ffffffff fa200000 be80: c760beec c760be90 c0039b6c c003900c 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff 00000000 c068bae8 bee0: c760bf24 c760bef0 c00ff7d0 c0064ec4 00000001 00000000 c00ff700 00000000 bf00: c0087f00 00000000 60000013 c0d76a70 c0e23795 00000001 c760bf4c c760bf28 bf20: c00ffdd8 c00ff70c c068bb08 c068bae8 60000013 c0100938 c068bb30 00000000 bf40: c760bf84 c760bf50 c010014c c00ffd84 00000001 00000000 c010000c 00012c00 bf60: c7c33f04 00012c00 c7c33f04 00000000 c0100938 00000000 c760bf9c c760bf88 bf80: c01009a8 c0100018 c760bfa8 c7c33f04 c760bff4 c760bfa0 c0088000 c0100944 bfa0: c760bf98 00000000 00000000 00000001 dead4ead ffffffff ffffffff c08ba2bc bfc0: 00000000 c049e7fa 00000000 c0087f70 c760bfd0 c760bfd0 c7c33f04 c0087f70 bfe0: c006f5e8 00000013 00000000 c760bff8 c006f5e8 c0087f7c 7f0004ff df2000ff Backtrace: [<c02c06a8>] (musb_g_rx+0x0/0x2ec) from [<c02bcd68>] (musb_interrupt+0xbc8/0xcc0) [<c02bc1a0>] (musb_interrupt+0x0/0xcc0) from [<c02bcecc>] (generic_interrupt+0x6c/0x84) [<c02bce60>] (generic_interrupt+0x0/0x84) from [<c00ad698>] (handle_IRQ_event+0xa8/0x1ec) r7:c760a000 r6:0000005c r5:c7d2e300 r4:c7d2e300 [<c00ad5f0>] (handle_IRQ_event+0x0/0x1ec) from [<c00af984>] (handle_level_irq+0xc0/0x13c) [<c00af8c4>] (handle_level_irq+0x0/0x13c) from [<c0039080>] (asm_do_IRQ+0x80/0xa0) r7:00000002 r6:00000000 r5:00000000 r4:0000005c [<c0039000>] (asm_do_IRQ+0x0/0xa0) from [<c0039b6c>] (__irq_svc+0x4c/0xb4) Exception stack(0xc760be90 to 0xc760bed8) be80: 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff r5:fa200000 r4:ffffffff [<c0064eb8>] (sub_preempt_count+0x0/0x100) from [<c00ff7d0>] (find_and_get_object+0xd0/0x110) r5:c068bae8 r4:00000000 [<c00ff700>] (find_and_get_object+0x0/0x110) from [<c00ffdd8>] (scan_block+0x60/0x104) r8:00000001 r7:c0e23795 r6:c0d76a70 r5:60000013 r4:00000000 [<c00ffd78>] (scan_block+0x0/0x104) from [<c010014c>] (kmemleak_scan+0x140/0x484) [<c010000c>] (kmemleak_scan+0x0/0x484) from [<c01009a8>] (kmemleak_scan_thread+0x70/0xcc) r8:00000000 r7:c0100938 r6:00000000 r5:c7c33f04 r4:00012c00 [<c0100938>] (kmemleak_scan_thread+0x0/0xcc) from [<c0088000>] (kthread+0x90/0x98) r5:c7c33f04 r4:c760bfa8 [<c0087f70>] (kthread+0x0/0x98) from [<c006f5e8>] (do_exit+0x0/0x684) r7:00000013 r6:c006f5e8 r5:c0087f70 r4:c7c33f04 Code: e3002312 e58d6000 e2833e16 eb0422d5 (e5963020) ---[ end trace f3d5e96f75c297b7 ]--- Signed-off-by: Ming Lei <tom.leiming@gmail.com> Reviewed-by: Sergei Shtylyov <sshtylyov@mvista.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: Anand Gadiyar <gadiyar@ti.com> Cc: Mike Frysinger <vapier@gentoo.org> Cc: Sergei Shtylyov <sshtylyov@ru.mvista.com> Cc: stable <stable@kernel.org> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-09-20 07:32:01 +00:00
struct musb_hw_ep *hw_ep = &musb->endpoints[epnum];
u8 use_mode_1;
usb: musb: gadget: fix kernel panic if using out ep with FIFO_TXRX style For shared fifo hw endpoint(with FIFO_TXRX style), only ep_in field of musb_hw_ep is intialized in musb_g_init_endpoints, and ep_out is not initialized, but musb_g_rx and rxstate may access ep_out field of musb_hw_ep by the method below: musb_ep = &musb->endpoints[epnum].ep_out which can cause the kernel panic[1] below, this patch fixes the issue by getting 'musb_ep' from '&musb->endpoints[epnum].ep_in' for shared fifo endpoint. [1], kernel panic [root@OMAP3EVM /]# musb_interrupt 1583: ** IRQ peripheral usb0008 tx0000 rx4000 musb_stage0_irq 460: <== Power=f0, DevCtl=99, int_usb=0x8 musb_g_rx 772: <== (null), rxcsr 4007 ffffffe8 musb_g_rx 786: iso overrun on ffffffe8 Unable to handle kernel NULL pointer dereference at virtual address 00000008 pgd = c0004000 [00000008] *pgd=00000000 Internal error: Oops: 17 [#1] PREEMPT last sysfs file: /sys/devices/platform/musb_hdrc/usb1/usb_device/usbdev1.1/dev Modules linked in: g_zero CPU: 0 Tainted: G W (2.6.35-rc6-gkh-wl+ #92) PC is at musb_g_rx+0xfc/0x2ec LR is at vprintk+0x3f4/0x458 pc : [<c02c07a4>] lr : [<c006ccb0>] psr: 20000193 sp : c760bd78 ip : c03c9d70 fp : c760bdbc r10: 00000000 r9 : fa0ab1e0 r8 : 0000000e r7 : c7e80158 r6 : ffffffe8 r5 : 00000001 r4 : 00004003 r3 : 00010003 r2 : c760bcd8 r1 : c03cd030 r0 : 0000002e Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c5387d Table: 8778c019 DAC: 00000017 Process kmemleak (pid: 421, stack limit = 0xc760a2e8) Stack: (0xc760bd78 to 0xc760c000) bd60: ffffffe8 c04b1b58 bd80: ffffffe8 c7c01ac0 00000000 c7e80d24 c0084238 00000001 00000001 c7e80158 bda0: 0000000e 00000008 00000099 000000f0 c760be04 c760bdc0 c02bcd68 c02c06b4 bdc0: 00000099 00000008 00004000 c760bdd8 c03cc4f8 00000000 00000002 c7e80158 bde0: c7d2e300 60000193 c760a000 0000005c 00000000 00000000 c760be24 c760be08 be00: c02bcecc c02bc1ac c7d2e300 c7d2e300 0000005c c760a000 c760be54 c760be28 be20: c00ad698 c02bce6c 00000000 c7d2e300 c067c258 0000005c c067c294 00000001 be40: c760a000 00000000 c760be74 c760be58 c00af984 c00ad5fc 0000005c 00000000 be60: 00000000 00000002 c760be8c c760be78 c0039080 c00af8d0 ffffffff fa200000 be80: c760beec c760be90 c0039b6c c003900c 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff 00000000 c068bae8 bee0: c760bf24 c760bef0 c00ff7d0 c0064ec4 00000001 00000000 c00ff700 00000000 bf00: c0087f00 00000000 60000013 c0d76a70 c0e23795 00000001 c760bf4c c760bf28 bf20: c00ffdd8 c00ff70c c068bb08 c068bae8 60000013 c0100938 c068bb30 00000000 bf40: c760bf84 c760bf50 c010014c c00ffd84 00000001 00000000 c010000c 00012c00 bf60: c7c33f04 00012c00 c7c33f04 00000000 c0100938 00000000 c760bf9c c760bf88 bf80: c01009a8 c0100018 c760bfa8 c7c33f04 c760bff4 c760bfa0 c0088000 c0100944 bfa0: c760bf98 00000000 00000000 00000001 dead4ead ffffffff ffffffff c08ba2bc bfc0: 00000000 c049e7fa 00000000 c0087f70 c760bfd0 c760bfd0 c7c33f04 c0087f70 bfe0: c006f5e8 00000013 00000000 c760bff8 c006f5e8 c0087f7c 7f0004ff df2000ff Backtrace: [<c02c06a8>] (musb_g_rx+0x0/0x2ec) from [<c02bcd68>] (musb_interrupt+0xbc8/0xcc0) [<c02bc1a0>] (musb_interrupt+0x0/0xcc0) from [<c02bcecc>] (generic_interrupt+0x6c/0x84) [<c02bce60>] (generic_interrupt+0x0/0x84) from [<c00ad698>] (handle_IRQ_event+0xa8/0x1ec) r7:c760a000 r6:0000005c r5:c7d2e300 r4:c7d2e300 [<c00ad5f0>] (handle_IRQ_event+0x0/0x1ec) from [<c00af984>] (handle_level_irq+0xc0/0x13c) [<c00af8c4>] (handle_level_irq+0x0/0x13c) from [<c0039080>] (asm_do_IRQ+0x80/0xa0) r7:00000002 r6:00000000 r5:00000000 r4:0000005c [<c0039000>] (asm_do_IRQ+0x0/0xa0) from [<c0039b6c>] (__irq_svc+0x4c/0xb4) Exception stack(0xc760be90 to 0xc760bed8) be80: 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff r5:fa200000 r4:ffffffff [<c0064eb8>] (sub_preempt_count+0x0/0x100) from [<c00ff7d0>] (find_and_get_object+0xd0/0x110) r5:c068bae8 r4:00000000 [<c00ff700>] (find_and_get_object+0x0/0x110) from [<c00ffdd8>] (scan_block+0x60/0x104) r8:00000001 r7:c0e23795 r6:c0d76a70 r5:60000013 r4:00000000 [<c00ffd78>] (scan_block+0x0/0x104) from [<c010014c>] (kmemleak_scan+0x140/0x484) [<c010000c>] (kmemleak_scan+0x0/0x484) from [<c01009a8>] (kmemleak_scan_thread+0x70/0xcc) r8:00000000 r7:c0100938 r6:00000000 r5:c7c33f04 r4:00012c00 [<c0100938>] (kmemleak_scan_thread+0x0/0xcc) from [<c0088000>] (kthread+0x90/0x98) r5:c7c33f04 r4:c760bfa8 [<c0087f70>] (kthread+0x0/0x98) from [<c006f5e8>] (do_exit+0x0/0x684) r7:00000013 r6:c006f5e8 r5:c0087f70 r4:c7c33f04 Code: e3002312 e58d6000 e2833e16 eb0422d5 (e5963020) ---[ end trace f3d5e96f75c297b7 ]--- Signed-off-by: Ming Lei <tom.leiming@gmail.com> Reviewed-by: Sergei Shtylyov <sshtylyov@mvista.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: Anand Gadiyar <gadiyar@ti.com> Cc: Mike Frysinger <vapier@gentoo.org> Cc: Sergei Shtylyov <sshtylyov@ru.mvista.com> Cc: stable <stable@kernel.org> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-09-20 07:32:01 +00:00
if (hw_ep->is_shared_fifo)
musb_ep = &hw_ep->ep_in;
else
musb_ep = &hw_ep->ep_out;
fifo_count = musb_ep->packet_sz;
usb: musb: handle nuked ep dma interrupt User can trigger disabling of gadget at run time while the transfers are going on. Eg: 1: rmmod of musb driver while transfers are going on Eg: 2: On android doing: echo 0 > /sys/class/android_usb/android0/enable While a big file transfer is going on via PTP/MTP. In such a case, musb_gadget_disable() calls nuke() but the dma interrupt may still happen for an endpoint since hw would raise the interrupt in anycase. This can result in a NULL pointer access crash: [ 314.030426] PC is at txstate+0x74/0x20c [ 314.034759] LR is at musb_g_tx+0x140/0x204 [ 314.039489] pc : [<c03506f4>] lr : [<c0350bcc>] psr: 20000193 [ 314.039520] sp : c783bc68 ip : 00000002 fp : c783bc9c [ 314.052429] r10: 00000018 r9 : 00000000 r8 : 00000200 [ 314.058258] r7 : 00000000 r6 : fc0ab130 r5 : c781a410 r4 : c6caf640 [ 314.065643] r3 : 00000000 r2 : 00000000 r1 : 00000000 r0 : c781a000 [ 315.083251] Backtrace: [ 315.086242] [<c0350680>] (txstate+0x0/0x20c) from [<c0350bcc>] (musb_g_tx+0x140/0x204) [ 315.095123] [<c0350a8c>] (musb_g_tx+0x0/0x204) from [<c034eb00>] (musb_dma_completion+0x40/0x54) [ 315.104980] [<c034eac0>] (musb_dma_completion+0x0/0x54) from [<c0351e6c>] (dma_controller_irq+0x118/0x184) [ 315.115661] [<c0351d54>] (dma_controller_irq+0x0/0x184) from [<c00d86b8>] (handle_irq_event_percpu+0x54/0x188) So put protection in code to handle possiblity of getting an interrupt for an endpoint that might have been already nuked. Reported-by: Todd Poynor <toddpoynor@google.com> Signed-off-by: Vikram Pandita <vikram.pandita@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2012-05-18 20:48:04 +00:00
/* Check if EP is disabled */
if (!musb_ep->desc) {
dev_dbg(musb->controller, "ep:%s disabled - ignore request\n",
musb_ep->end_point.name);
return;
}
/* We shouldn't get here while DMA is active, but we do... */
if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) {
dev_dbg(musb->controller, "DMA pending...\n");
return;
}
if (csr & MUSB_RXCSR_P_SENDSTALL) {
dev_dbg(musb->controller, "%s stalling, RXCSR %04x\n",
musb_ep->end_point.name, csr);
return;
}
if (is_cppi_enabled(musb) && is_buffer_mapped(req)) {
struct dma_controller *c = musb->dma_controller;
struct dma_channel *channel = musb_ep->dma;
/* NOTE: CPPI won't actually stop advancing the DMA
* queue after short packet transfers, so this is almost
* always going to run as IRQ-per-packet DMA so that
* faults will be handled correctly.
*/
if (c->channel_program(channel,
musb_ep->packet_sz,
!request->short_not_ok,
request->dma + request->actual,
request->length - request->actual)) {
/* make sure that if an rxpkt arrived after the irq,
* the cppi engine will be ready to take it as soon
* as DMA is enabled
*/
csr &= ~(MUSB_RXCSR_AUTOCLEAR
| MUSB_RXCSR_DMAMODE);
csr |= MUSB_RXCSR_DMAENAB | MUSB_RXCSR_P_WZC_BITS;
musb_writew(epio, MUSB_RXCSR, csr);
return;
}
}
if (csr & MUSB_RXCSR_RXPKTRDY) {
fifo_count = musb_readw(epio, MUSB_RXCOUNT);
/*
* Enable Mode 1 on RX transfers only when short_not_ok flag
* is set. Currently short_not_ok flag is set only from
* file_storage and f_mass_storage drivers
*/
if (request->short_not_ok && fifo_count == musb_ep->packet_sz)
use_mode_1 = 1;
else
use_mode_1 = 0;
if (request->actual < request->length) {
if (!is_buffer_mapped(req))
goto buffer_aint_mapped;
if (musb_dma_inventra(musb)) {
struct dma_controller *c;
struct dma_channel *channel;
int use_dma = 0;
unsigned int transfer_size;
c = musb->dma_controller;
channel = musb_ep->dma;
/* We use DMA Req mode 0 in rx_csr, and DMA controller operates in
* mode 0 only. So we do not get endpoint interrupts due to DMA
* completion. We only get interrupts from DMA controller.
*
* We could operate in DMA mode 1 if we knew the size of the tranfer
* in advance. For mass storage class, request->length = what the host
* sends, so that'd work. But for pretty much everything else,
* request->length is routinely more than what the host sends. For
* most these gadgets, end of is signified either by a short packet,
* or filling the last byte of the buffer. (Sending extra data in
* that last pckate should trigger an overflow fault.) But in mode 1,
* we don't get DMA completion interrupt for short packets.
*
* Theoretically, we could enable DMAReq irq (MUSB_RXCSR_DMAMODE = 1),
* to get endpoint interrupt on every DMA req, but that didn't seem
* to work reliably.
*
* REVISIT an updated g_file_storage can set req->short_not_ok, which
* then becomes usable as a runtime "use mode 1" hint...
*/
/* Experimental: Mode1 works with mass storage use cases */
if (use_mode_1) {
csr |= MUSB_RXCSR_AUTOCLEAR;
musb_writew(epio, MUSB_RXCSR, csr);
csr |= MUSB_RXCSR_DMAENAB;
musb_writew(epio, MUSB_RXCSR, csr);
/*
* this special sequence (enabling and then
* disabling MUSB_RXCSR_DMAMODE) is required
* to get DMAReq to activate
*/
musb_writew(epio, MUSB_RXCSR,
csr | MUSB_RXCSR_DMAMODE);
musb_writew(epio, MUSB_RXCSR, csr);
transfer_size = min_t(unsigned int,
request->length -
request->actual,
channel->max_len);
musb_ep->dma->desired_mode = 1;
} else {
if (!musb_ep->hb_mult &&
musb_ep->hw_ep->rx_double_buffered)
csr |= MUSB_RXCSR_AUTOCLEAR;
csr |= MUSB_RXCSR_DMAENAB;
musb_writew(epio, MUSB_RXCSR, csr);
transfer_size = min(request->length - request->actual,
(unsigned)fifo_count);
musb_ep->dma->desired_mode = 0;
}
use_dma = c->channel_program(
channel,
musb_ep->packet_sz,
channel->desired_mode,
request->dma
+ request->actual,
transfer_size);
if (use_dma)
return;
}
if ((musb_dma_ux500(musb)) &&
(request->actual < request->length)) {
struct dma_controller *c;
struct dma_channel *channel;
unsigned int transfer_size = 0;
c = musb->dma_controller;
channel = musb_ep->dma;
/* In case first packet is short */
if (fifo_count < musb_ep->packet_sz)
transfer_size = fifo_count;
else if (request->short_not_ok)
transfer_size = min_t(unsigned int,
request->length -
request->actual,
channel->max_len);
else
transfer_size = min_t(unsigned int,
request->length -
request->actual,
(unsigned)fifo_count);
csr &= ~MUSB_RXCSR_DMAMODE;
csr |= (MUSB_RXCSR_DMAENAB |
MUSB_RXCSR_AUTOCLEAR);
musb_writew(epio, MUSB_RXCSR, csr);
if (transfer_size <= musb_ep->packet_sz) {
musb_ep->dma->desired_mode = 0;
} else {
musb_ep->dma->desired_mode = 1;
/* Mode must be set after DMAENAB */
csr |= MUSB_RXCSR_DMAMODE;
musb_writew(epio, MUSB_RXCSR, csr);
}
if (c->channel_program(channel,
musb_ep->packet_sz,
channel->desired_mode,
request->dma
+ request->actual,
transfer_size))
return;
}
len = request->length - request->actual;
dev_dbg(musb->controller, "%s OUT/RX pio fifo %d/%d, maxpacket %d\n",
musb_ep->end_point.name,
fifo_count, len,
musb_ep->packet_sz);
fifo_count = min_t(unsigned, len, fifo_count);
if (tusb_dma_omap(musb)) {
struct dma_controller *c = musb->dma_controller;
struct dma_channel *channel = musb_ep->dma;
u32 dma_addr = request->dma + request->actual;
int ret;
ret = c->channel_program(channel,
musb_ep->packet_sz,
channel->desired_mode,
dma_addr,
fifo_count);
if (ret)
return;
}
/*
* Unmap the dma buffer back to cpu if dma channel
* programming fails. This buffer is mapped if the
* channel allocation is successful
*/
unmap_dma_buffer(req, musb);
/*
* Clear DMAENAB and AUTOCLEAR for the
* PIO mode transfer
*/
csr &= ~(MUSB_RXCSR_DMAENAB | MUSB_RXCSR_AUTOCLEAR);
musb_writew(epio, MUSB_RXCSR, csr);
buffer_aint_mapped:
musb_read_fifo(musb_ep->hw_ep, fifo_count, (u8 *)
(request->buf + request->actual));
request->actual += fifo_count;
/* REVISIT if we left anything in the fifo, flush
* it and report -EOVERFLOW
*/
/* ack the read! */
csr |= MUSB_RXCSR_P_WZC_BITS;
csr &= ~MUSB_RXCSR_RXPKTRDY;
musb_writew(epio, MUSB_RXCSR, csr);
}
}
/* reach the end or short packet detected */
if (request->actual == request->length ||
fifo_count < musb_ep->packet_sz)
musb_g_giveback(musb_ep, request, 0);
}
/*
* Data ready for a request; called from IRQ
*/
void musb_g_rx(struct musb *musb, u8 epnum)
{
u16 csr;
struct musb_request *req;
struct usb_request *request;
void __iomem *mbase = musb->mregs;
usb: musb: gadget: fix kernel panic if using out ep with FIFO_TXRX style For shared fifo hw endpoint(with FIFO_TXRX style), only ep_in field of musb_hw_ep is intialized in musb_g_init_endpoints, and ep_out is not initialized, but musb_g_rx and rxstate may access ep_out field of musb_hw_ep by the method below: musb_ep = &musb->endpoints[epnum].ep_out which can cause the kernel panic[1] below, this patch fixes the issue by getting 'musb_ep' from '&musb->endpoints[epnum].ep_in' for shared fifo endpoint. [1], kernel panic [root@OMAP3EVM /]# musb_interrupt 1583: ** IRQ peripheral usb0008 tx0000 rx4000 musb_stage0_irq 460: <== Power=f0, DevCtl=99, int_usb=0x8 musb_g_rx 772: <== (null), rxcsr 4007 ffffffe8 musb_g_rx 786: iso overrun on ffffffe8 Unable to handle kernel NULL pointer dereference at virtual address 00000008 pgd = c0004000 [00000008] *pgd=00000000 Internal error: Oops: 17 [#1] PREEMPT last sysfs file: /sys/devices/platform/musb_hdrc/usb1/usb_device/usbdev1.1/dev Modules linked in: g_zero CPU: 0 Tainted: G W (2.6.35-rc6-gkh-wl+ #92) PC is at musb_g_rx+0xfc/0x2ec LR is at vprintk+0x3f4/0x458 pc : [<c02c07a4>] lr : [<c006ccb0>] psr: 20000193 sp : c760bd78 ip : c03c9d70 fp : c760bdbc r10: 00000000 r9 : fa0ab1e0 r8 : 0000000e r7 : c7e80158 r6 : ffffffe8 r5 : 00000001 r4 : 00004003 r3 : 00010003 r2 : c760bcd8 r1 : c03cd030 r0 : 0000002e Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c5387d Table: 8778c019 DAC: 00000017 Process kmemleak (pid: 421, stack limit = 0xc760a2e8) Stack: (0xc760bd78 to 0xc760c000) bd60: ffffffe8 c04b1b58 bd80: ffffffe8 c7c01ac0 00000000 c7e80d24 c0084238 00000001 00000001 c7e80158 bda0: 0000000e 00000008 00000099 000000f0 c760be04 c760bdc0 c02bcd68 c02c06b4 bdc0: 00000099 00000008 00004000 c760bdd8 c03cc4f8 00000000 00000002 c7e80158 bde0: c7d2e300 60000193 c760a000 0000005c 00000000 00000000 c760be24 c760be08 be00: c02bcecc c02bc1ac c7d2e300 c7d2e300 0000005c c760a000 c760be54 c760be28 be20: c00ad698 c02bce6c 00000000 c7d2e300 c067c258 0000005c c067c294 00000001 be40: c760a000 00000000 c760be74 c760be58 c00af984 c00ad5fc 0000005c 00000000 be60: 00000000 00000002 c760be8c c760be78 c0039080 c00af8d0 ffffffff fa200000 be80: c760beec c760be90 c0039b6c c003900c 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff 00000000 c068bae8 bee0: c760bf24 c760bef0 c00ff7d0 c0064ec4 00000001 00000000 c00ff700 00000000 bf00: c0087f00 00000000 60000013 c0d76a70 c0e23795 00000001 c760bf4c c760bf28 bf20: c00ffdd8 c00ff70c c068bb08 c068bae8 60000013 c0100938 c068bb30 00000000 bf40: c760bf84 c760bf50 c010014c c00ffd84 00000001 00000000 c010000c 00012c00 bf60: c7c33f04 00012c00 c7c33f04 00000000 c0100938 00000000 c760bf9c c760bf88 bf80: c01009a8 c0100018 c760bfa8 c7c33f04 c760bff4 c760bfa0 c0088000 c0100944 bfa0: c760bf98 00000000 00000000 00000001 dead4ead ffffffff ffffffff c08ba2bc bfc0: 00000000 c049e7fa 00000000 c0087f70 c760bfd0 c760bfd0 c7c33f04 c0087f70 bfe0: c006f5e8 00000013 00000000 c760bff8 c006f5e8 c0087f7c 7f0004ff df2000ff Backtrace: [<c02c06a8>] (musb_g_rx+0x0/0x2ec) from [<c02bcd68>] (musb_interrupt+0xbc8/0xcc0) [<c02bc1a0>] (musb_interrupt+0x0/0xcc0) from [<c02bcecc>] (generic_interrupt+0x6c/0x84) [<c02bce60>] (generic_interrupt+0x0/0x84) from [<c00ad698>] (handle_IRQ_event+0xa8/0x1ec) r7:c760a000 r6:0000005c r5:c7d2e300 r4:c7d2e300 [<c00ad5f0>] (handle_IRQ_event+0x0/0x1ec) from [<c00af984>] (handle_level_irq+0xc0/0x13c) [<c00af8c4>] (handle_level_irq+0x0/0x13c) from [<c0039080>] (asm_do_IRQ+0x80/0xa0) r7:00000002 r6:00000000 r5:00000000 r4:0000005c [<c0039000>] (asm_do_IRQ+0x0/0xa0) from [<c0039b6c>] (__irq_svc+0x4c/0xb4) Exception stack(0xc760be90 to 0xc760bed8) be80: 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff r5:fa200000 r4:ffffffff [<c0064eb8>] (sub_preempt_count+0x0/0x100) from [<c00ff7d0>] (find_and_get_object+0xd0/0x110) r5:c068bae8 r4:00000000 [<c00ff700>] (find_and_get_object+0x0/0x110) from [<c00ffdd8>] (scan_block+0x60/0x104) r8:00000001 r7:c0e23795 r6:c0d76a70 r5:60000013 r4:00000000 [<c00ffd78>] (scan_block+0x0/0x104) from [<c010014c>] (kmemleak_scan+0x140/0x484) [<c010000c>] (kmemleak_scan+0x0/0x484) from [<c01009a8>] (kmemleak_scan_thread+0x70/0xcc) r8:00000000 r7:c0100938 r6:00000000 r5:c7c33f04 r4:00012c00 [<c0100938>] (kmemleak_scan_thread+0x0/0xcc) from [<c0088000>] (kthread+0x90/0x98) r5:c7c33f04 r4:c760bfa8 [<c0087f70>] (kthread+0x0/0x98) from [<c006f5e8>] (do_exit+0x0/0x684) r7:00000013 r6:c006f5e8 r5:c0087f70 r4:c7c33f04 Code: e3002312 e58d6000 e2833e16 eb0422d5 (e5963020) ---[ end trace f3d5e96f75c297b7 ]--- Signed-off-by: Ming Lei <tom.leiming@gmail.com> Reviewed-by: Sergei Shtylyov <sshtylyov@mvista.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: Anand Gadiyar <gadiyar@ti.com> Cc: Mike Frysinger <vapier@gentoo.org> Cc: Sergei Shtylyov <sshtylyov@ru.mvista.com> Cc: stable <stable@kernel.org> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-09-20 07:32:01 +00:00
struct musb_ep *musb_ep;
void __iomem *epio = musb->endpoints[epnum].regs;
struct dma_channel *dma;
usb: musb: gadget: fix kernel panic if using out ep with FIFO_TXRX style For shared fifo hw endpoint(with FIFO_TXRX style), only ep_in field of musb_hw_ep is intialized in musb_g_init_endpoints, and ep_out is not initialized, but musb_g_rx and rxstate may access ep_out field of musb_hw_ep by the method below: musb_ep = &musb->endpoints[epnum].ep_out which can cause the kernel panic[1] below, this patch fixes the issue by getting 'musb_ep' from '&musb->endpoints[epnum].ep_in' for shared fifo endpoint. [1], kernel panic [root@OMAP3EVM /]# musb_interrupt 1583: ** IRQ peripheral usb0008 tx0000 rx4000 musb_stage0_irq 460: <== Power=f0, DevCtl=99, int_usb=0x8 musb_g_rx 772: <== (null), rxcsr 4007 ffffffe8 musb_g_rx 786: iso overrun on ffffffe8 Unable to handle kernel NULL pointer dereference at virtual address 00000008 pgd = c0004000 [00000008] *pgd=00000000 Internal error: Oops: 17 [#1] PREEMPT last sysfs file: /sys/devices/platform/musb_hdrc/usb1/usb_device/usbdev1.1/dev Modules linked in: g_zero CPU: 0 Tainted: G W (2.6.35-rc6-gkh-wl+ #92) PC is at musb_g_rx+0xfc/0x2ec LR is at vprintk+0x3f4/0x458 pc : [<c02c07a4>] lr : [<c006ccb0>] psr: 20000193 sp : c760bd78 ip : c03c9d70 fp : c760bdbc r10: 00000000 r9 : fa0ab1e0 r8 : 0000000e r7 : c7e80158 r6 : ffffffe8 r5 : 00000001 r4 : 00004003 r3 : 00010003 r2 : c760bcd8 r1 : c03cd030 r0 : 0000002e Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c5387d Table: 8778c019 DAC: 00000017 Process kmemleak (pid: 421, stack limit = 0xc760a2e8) Stack: (0xc760bd78 to 0xc760c000) bd60: ffffffe8 c04b1b58 bd80: ffffffe8 c7c01ac0 00000000 c7e80d24 c0084238 00000001 00000001 c7e80158 bda0: 0000000e 00000008 00000099 000000f0 c760be04 c760bdc0 c02bcd68 c02c06b4 bdc0: 00000099 00000008 00004000 c760bdd8 c03cc4f8 00000000 00000002 c7e80158 bde0: c7d2e300 60000193 c760a000 0000005c 00000000 00000000 c760be24 c760be08 be00: c02bcecc c02bc1ac c7d2e300 c7d2e300 0000005c c760a000 c760be54 c760be28 be20: c00ad698 c02bce6c 00000000 c7d2e300 c067c258 0000005c c067c294 00000001 be40: c760a000 00000000 c760be74 c760be58 c00af984 c00ad5fc 0000005c 00000000 be60: 00000000 00000002 c760be8c c760be78 c0039080 c00af8d0 ffffffff fa200000 be80: c760beec c760be90 c0039b6c c003900c 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff 00000000 c068bae8 bee0: c760bf24 c760bef0 c00ff7d0 c0064ec4 00000001 00000000 c00ff700 00000000 bf00: c0087f00 00000000 60000013 c0d76a70 c0e23795 00000001 c760bf4c c760bf28 bf20: c00ffdd8 c00ff70c c068bb08 c068bae8 60000013 c0100938 c068bb30 00000000 bf40: c760bf84 c760bf50 c010014c c00ffd84 00000001 00000000 c010000c 00012c00 bf60: c7c33f04 00012c00 c7c33f04 00000000 c0100938 00000000 c760bf9c c760bf88 bf80: c01009a8 c0100018 c760bfa8 c7c33f04 c760bff4 c760bfa0 c0088000 c0100944 bfa0: c760bf98 00000000 00000000 00000001 dead4ead ffffffff ffffffff c08ba2bc bfc0: 00000000 c049e7fa 00000000 c0087f70 c760bfd0 c760bfd0 c7c33f04 c0087f70 bfe0: c006f5e8 00000013 00000000 c760bff8 c006f5e8 c0087f7c 7f0004ff df2000ff Backtrace: [<c02c06a8>] (musb_g_rx+0x0/0x2ec) from [<c02bcd68>] (musb_interrupt+0xbc8/0xcc0) [<c02bc1a0>] (musb_interrupt+0x0/0xcc0) from [<c02bcecc>] (generic_interrupt+0x6c/0x84) [<c02bce60>] (generic_interrupt+0x0/0x84) from [<c00ad698>] (handle_IRQ_event+0xa8/0x1ec) r7:c760a000 r6:0000005c r5:c7d2e300 r4:c7d2e300 [<c00ad5f0>] (handle_IRQ_event+0x0/0x1ec) from [<c00af984>] (handle_level_irq+0xc0/0x13c) [<c00af8c4>] (handle_level_irq+0x0/0x13c) from [<c0039080>] (asm_do_IRQ+0x80/0xa0) r7:00000002 r6:00000000 r5:00000000 r4:0000005c [<c0039000>] (asm_do_IRQ+0x0/0xa0) from [<c0039b6c>] (__irq_svc+0x4c/0xb4) Exception stack(0xc760be90 to 0xc760bed8) be80: 00000001 00000000 c7d1e240 00000000 bea0: 00000000 c068bae8 00000000 60000013 00000001 00000000 00000000 c760beec bec0: c0064ecc c760bed8 c00ff7d0 c003a0a8 60000013 ffffffff r5:fa200000 r4:ffffffff [<c0064eb8>] (sub_preempt_count+0x0/0x100) from [<c00ff7d0>] (find_and_get_object+0xd0/0x110) r5:c068bae8 r4:00000000 [<c00ff700>] (find_and_get_object+0x0/0x110) from [<c00ffdd8>] (scan_block+0x60/0x104) r8:00000001 r7:c0e23795 r6:c0d76a70 r5:60000013 r4:00000000 [<c00ffd78>] (scan_block+0x0/0x104) from [<c010014c>] (kmemleak_scan+0x140/0x484) [<c010000c>] (kmemleak_scan+0x0/0x484) from [<c01009a8>] (kmemleak_scan_thread+0x70/0xcc) r8:00000000 r7:c0100938 r6:00000000 r5:c7c33f04 r4:00012c00 [<c0100938>] (kmemleak_scan_thread+0x0/0xcc) from [<c0088000>] (kthread+0x90/0x98) r5:c7c33f04 r4:c760bfa8 [<c0087f70>] (kthread+0x0/0x98) from [<c006f5e8>] (do_exit+0x0/0x684) r7:00000013 r6:c006f5e8 r5:c0087f70 r4:c7c33f04 Code: e3002312 e58d6000 e2833e16 eb0422d5 (e5963020) ---[ end trace f3d5e96f75c297b7 ]--- Signed-off-by: Ming Lei <tom.leiming@gmail.com> Reviewed-by: Sergei Shtylyov <sshtylyov@mvista.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: Anand Gadiyar <gadiyar@ti.com> Cc: Mike Frysinger <vapier@gentoo.org> Cc: Sergei Shtylyov <sshtylyov@ru.mvista.com> Cc: stable <stable@kernel.org> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-09-20 07:32:01 +00:00
struct musb_hw_ep *hw_ep = &musb->endpoints[epnum];
if (hw_ep->is_shared_fifo)
musb_ep = &hw_ep->ep_in;
else
musb_ep = &hw_ep->ep_out;
musb_ep_select(mbase, epnum);
req = next_request(musb_ep);
if (!req)
return;
request = &req->request;
csr = musb_readw(epio, MUSB_RXCSR);
dma = is_dma_capable() ? musb_ep->dma : NULL;
dev_dbg(musb->controller, "<== %s, rxcsr %04x%s %p\n", musb_ep->end_point.name,
csr, dma ? " (dma)" : "", request);
if (csr & MUSB_RXCSR_P_SENTSTALL) {
csr |= MUSB_RXCSR_P_WZC_BITS;
csr &= ~MUSB_RXCSR_P_SENTSTALL;
musb_writew(epio, MUSB_RXCSR, csr);
return;
}
if (csr & MUSB_RXCSR_P_OVERRUN) {
/* csr |= MUSB_RXCSR_P_WZC_BITS; */
csr &= ~MUSB_RXCSR_P_OVERRUN;
musb_writew(epio, MUSB_RXCSR, csr);
dev_dbg(musb->controller, "%s iso overrun on %p\n", musb_ep->name, request);
if (request->status == -EINPROGRESS)
request->status = -EOVERFLOW;
}
if (csr & MUSB_RXCSR_INCOMPRX) {
/* REVISIT not necessarily an error */
dev_dbg(musb->controller, "%s, incomprx\n", musb_ep->end_point.name);
}
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
/* "should not happen"; likely RXPKTRDY pending for DMA */
dev_dbg(musb->controller, "%s busy, csr %04x\n",
musb_ep->end_point.name, csr);
return;
}
if (dma && (csr & MUSB_RXCSR_DMAENAB)) {
csr &= ~(MUSB_RXCSR_AUTOCLEAR
| MUSB_RXCSR_DMAENAB
| MUSB_RXCSR_DMAMODE);
musb_writew(epio, MUSB_RXCSR,
MUSB_RXCSR_P_WZC_BITS | csr);
request->actual += musb_ep->dma->actual_len;
dev_dbg(musb->controller, "RXCSR%d %04x, dma off, %04x, len %zu, req %p\n",
epnum, csr,
musb_readw(epio, MUSB_RXCSR),
musb_ep->dma->actual_len, request);
#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) || \
defined(CONFIG_USB_UX500_DMA)
/* Autoclear doesn't clear RxPktRdy for short packets */
if ((dma->desired_mode == 0 && !hw_ep->rx_double_buffered)
|| (dma->actual_len
& (musb_ep->packet_sz - 1))) {
/* ack the read! */
csr &= ~MUSB_RXCSR_RXPKTRDY;
musb_writew(epio, MUSB_RXCSR, csr);
}
/* incomplete, and not short? wait for next IN packet */
if ((request->actual < request->length)
&& (musb_ep->dma->actual_len
== musb_ep->packet_sz)) {
/* In double buffer case, continue to unload fifo if
* there is Rx packet in FIFO.
**/
csr = musb_readw(epio, MUSB_RXCSR);
if ((csr & MUSB_RXCSR_RXPKTRDY) &&
hw_ep->rx_double_buffered)
goto exit;
return;
}
#endif
musb_g_giveback(musb_ep, request, 0);
usb: musb: Reselect index reg in interrupt context musb INDEX register is getting modified/corrupted during temporary un-locking in a SMP system. Set this register with proper value after re-acquiring the lock Scenario: --------- CPU1 is handling a data transfer completion interrupt received for the CLASS1 EP CPU2 is handling a CLASS2 thread which is queuing data to musb for transfer Below is the error sequence: CPU1 | CPU2 -------------------------------------------------------------------- Data transfer completion inter- | rupt recieved. | | musb INDEX reg set to CLASS1 EP | | musb LOCK is acquired. | | | CLASS2 thread queues data. | | CLASS2 thread tries to acquire musb | LOCK but lock is already taken by | CLASS1, so CLASS2 thread is | spinning. | From Interrupt Context musb | giveback function is called | | The giveback function releases | CLASS2 thread now acquires LOCK LOCK | | ClASS1 Request's completion cal-| ClASS2 schedules the data transfer and lback is called | sets the MUSB INDEX to Class2 EP number | Interrupt handler for CLASS1 EP | tries to acquire LOCK and is | spinning | | Interrupt for Class1 EP acquires| Class2 completes the scheduling etc and the MUSB LOCK | releases the musb LOCK | Interrupt for Class1 EP schedul-| es the next data transfer | but musb INDEX register is still| set to CLASS2 EP | Since the MUSB INDEX register is set to a different endpoint, we read and modify the wrong registers. Hence data transfer will not happen properly. This results in unpredictable behavior So, the MUSB INDEX register is set to proper value again when interrupt re-acquires the lock Cc: stable@vger.kernel.org Signed-off-by: Supriya Karanth <supriya.karanth@stericsson.com> Signed-off-by: Praveena Nadahally <praveen.nadahally@stericsson.com> Reviewed-by: srinidhi kasagar <srinidhi.kasagar@stericsson.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2012-02-17 09:24:52 +00:00
/*
* In the giveback function the MUSB lock is
* released and acquired after sometime. During
* this time period the INDEX register could get
* changed by the gadget_queue function especially
* on SMP systems. Reselect the INDEX to be sure
* we are reading/modifying the right registers
*/
musb_ep_select(mbase, epnum);
req = next_request(musb_ep);
if (!req)
return;
}
#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) || \
defined(CONFIG_USB_UX500_DMA)
exit:
#endif
/* Analyze request */
rxstate(musb, req);
}
/* ------------------------------------------------------------ */
static int musb_gadget_enable(struct usb_ep *ep,
const struct usb_endpoint_descriptor *desc)
{
unsigned long flags;
struct musb_ep *musb_ep;
struct musb_hw_ep *hw_ep;
void __iomem *regs;
struct musb *musb;
void __iomem *mbase;
u8 epnum;
u16 csr;
unsigned tmp;
int status = -EINVAL;
if (!ep || !desc)
return -EINVAL;
musb_ep = to_musb_ep(ep);
hw_ep = musb_ep->hw_ep;
regs = hw_ep->regs;
musb = musb_ep->musb;
mbase = musb->mregs;
epnum = musb_ep->current_epnum;
spin_lock_irqsave(&musb->lock, flags);
if (musb_ep->desc) {
status = -EBUSY;
goto fail;
}
musb_ep->type = usb_endpoint_type(desc);
/* check direction and (later) maxpacket size against endpoint */
if (usb_endpoint_num(desc) != epnum)
goto fail;
/* REVISIT this rules out high bandwidth periodic transfers */
USB: use usb_endpoint_maxp() instead of le16_to_cpu() Now ${LINUX}/drivers/usb/* can use usb_endpoint_maxp(desc) to get maximum packet size instead of le16_to_cpu(desc->wMaxPacketSize). This patch fix it up Cc: Armin Fuerst <fuerst@in.tum.de> Cc: Pavel Machek <pavel@ucw.cz> Cc: Johannes Erdfelt <johannes@erdfelt.com> Cc: Vojtech Pavlik <vojtech@suse.cz> Cc: Oliver Neukum <oliver@neukum.name> Cc: David Kubicek <dave@awk.cz> Cc: Johan Hovold <jhovold@gmail.com> Cc: Brad Hards <bhards@bigpond.net.au> Acked-by: Felipe Balbi <balbi@ti.com> Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Cc: Thomas Dahlmann <dahlmann.thomas@arcor.de> Cc: David Brownell <david-b@pacbell.net> Cc: David Lopo <dlopo@chipidea.mips.com> Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Michal Nazarewicz <m.nazarewicz@samsung.com> Cc: Xie Xiaobo <X.Xie@freescale.com> Cc: Li Yang <leoli@freescale.com> Cc: Jiang Bo <tanya.jiang@freescale.com> Cc: Yuan-hsin Chen <yhchen@faraday-tech.com> Cc: Darius Augulis <augulis.darius@gmail.com> Cc: Xiaochen Shen <xiaochen.shen@intel.com> Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Cc: OKI SEMICONDUCTOR, <toshiharu-linux@dsn.okisemi.com> Cc: Robert Jarzmik <robert.jarzmik@free.fr> Cc: Ben Dooks <ben@simtec.co.uk> Cc: Thomas Abraham <thomas.ab@samsung.com> Cc: Herbert Pötzl <herbert@13thfloor.at> Cc: Arnaud Patard <arnaud.patard@rtp-net.org> Cc: Roman Weissgaerber <weissg@vienna.at> Acked-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Cc: Tony Olech <tony.olech@elandigitalsystems.com> Cc: Florian Floe Echtler <echtler@fs.tum.de> Cc: Christian Lucht <lucht@codemercs.com> Cc: Juergen Stuber <starblue@sourceforge.net> Cc: Georges Toth <g.toth@e-biz.lu> Cc: Bill Ryder <bryder@sgi.com> Cc: Kuba Ober <kuba@mareimbrium.org> Cc: Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2011-08-23 10:12:03 +00:00
tmp = usb_endpoint_maxp(desc);
if (tmp & ~0x07ff) {
int ok;
if (usb_endpoint_dir_in(desc))
ok = musb->hb_iso_tx;
else
ok = musb->hb_iso_rx;
if (!ok) {
dev_dbg(musb->controller, "no support for high bandwidth ISO\n");
goto fail;
}
musb_ep->hb_mult = (tmp >> 11) & 3;
} else {
musb_ep->hb_mult = 0;
}
musb_ep->packet_sz = tmp & 0x7ff;
tmp = musb_ep->packet_sz * (musb_ep->hb_mult + 1);
/* enable the interrupts for the endpoint, set the endpoint
* packet size (or fail), set the mode, clear the fifo
*/
musb_ep_select(mbase, epnum);
if (usb_endpoint_dir_in(desc)) {
if (hw_ep->is_shared_fifo)
musb_ep->is_in = 1;
if (!musb_ep->is_in)
goto fail;
if (tmp > hw_ep->max_packet_sz_tx) {
dev_dbg(musb->controller, "packet size beyond hardware FIFO size\n");
goto fail;
}
musb->intrtxe |= (1 << epnum);
musb_writew(mbase, MUSB_INTRTXE, musb->intrtxe);
/* REVISIT if can_bulk_split(), use by updating "tmp";
* likewise high bandwidth periodic tx
*/
/* Set TXMAXP with the FIFO size of the endpoint
* to disable double buffering mode.
*/
if (musb->double_buffer_not_ok) {
musb_writew(regs, MUSB_TXMAXP, hw_ep->max_packet_sz_tx);
} else {
if (can_bulk_split(musb, musb_ep->type))
musb_ep->hb_mult = (hw_ep->max_packet_sz_tx /
musb_ep->packet_sz) - 1;
musb_writew(regs, MUSB_TXMAXP, musb_ep->packet_sz
| (musb_ep->hb_mult << 11));
}
csr = MUSB_TXCSR_MODE | MUSB_TXCSR_CLRDATATOG;
if (musb_readw(regs, MUSB_TXCSR)
& MUSB_TXCSR_FIFONOTEMPTY)
csr |= MUSB_TXCSR_FLUSHFIFO;
if (musb_ep->type == USB_ENDPOINT_XFER_ISOC)
csr |= MUSB_TXCSR_P_ISO;
/* set twice in case of double buffering */
musb_writew(regs, MUSB_TXCSR, csr);
/* REVISIT may be inappropriate w/o FIFONOTEMPTY ... */
musb_writew(regs, MUSB_TXCSR, csr);
} else {
if (hw_ep->is_shared_fifo)
musb_ep->is_in = 0;
if (musb_ep->is_in)
goto fail;
if (tmp > hw_ep->max_packet_sz_rx) {
dev_dbg(musb->controller, "packet size beyond hardware FIFO size\n");
goto fail;
}
musb->intrrxe |= (1 << epnum);
musb_writew(mbase, MUSB_INTRRXE, musb->intrrxe);
/* REVISIT if can_bulk_combine() use by updating "tmp"
* likewise high bandwidth periodic rx
*/
/* Set RXMAXP with the FIFO size of the endpoint
* to disable double buffering mode.
*/
if (musb->double_buffer_not_ok)
musb_writew(regs, MUSB_RXMAXP, hw_ep->max_packet_sz_tx);
else
musb_writew(regs, MUSB_RXMAXP, musb_ep->packet_sz
| (musb_ep->hb_mult << 11));
/* force shared fifo to OUT-only mode */
if (hw_ep->is_shared_fifo) {
csr = musb_readw(regs, MUSB_TXCSR);
csr &= ~(MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY);
musb_writew(regs, MUSB_TXCSR, csr);
}
csr = MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_CLRDATATOG;
if (musb_ep->type == USB_ENDPOINT_XFER_ISOC)
csr |= MUSB_RXCSR_P_ISO;
else if (musb_ep->type == USB_ENDPOINT_XFER_INT)
csr |= MUSB_RXCSR_DISNYET;
/* set twice in case of double buffering */
musb_writew(regs, MUSB_RXCSR, csr);
musb_writew(regs, MUSB_RXCSR, csr);
}
/* NOTE: all the I/O code _should_ work fine without DMA, in case
* for some reason you run out of channels here.
*/
if (is_dma_capable() && musb->dma_controller) {
struct dma_controller *c = musb->dma_controller;
musb_ep->dma = c->channel_alloc(c, hw_ep,
(desc->bEndpointAddress & USB_DIR_IN));
} else
musb_ep->dma = NULL;
musb_ep->desc = desc;
musb_ep->busy = 0;
musb_ep->wedged = 0;
status = 0;
pr_debug("%s periph: enabled %s for %s %s, %smaxpacket %d\n",
musb_driver_name, musb_ep->end_point.name,
({ char *s; switch (musb_ep->type) {
case USB_ENDPOINT_XFER_BULK: s = "bulk"; break;
case USB_ENDPOINT_XFER_INT: s = "int"; break;
default: s = "iso"; break;
} s; }),
musb_ep->is_in ? "IN" : "OUT",
musb_ep->dma ? "dma, " : "",
musb_ep->packet_sz);
schedule_work(&musb->irq_work);
fail:
spin_unlock_irqrestore(&musb->lock, flags);
return status;
}
/*
* Disable an endpoint flushing all requests queued.
*/
static int musb_gadget_disable(struct usb_ep *ep)
{
unsigned long flags;
struct musb *musb;
u8 epnum;
struct musb_ep *musb_ep;
void __iomem *epio;
int status = 0;
musb_ep = to_musb_ep(ep);
musb = musb_ep->musb;
epnum = musb_ep->current_epnum;
epio = musb->endpoints[epnum].regs;
spin_lock_irqsave(&musb->lock, flags);
musb_ep_select(musb->mregs, epnum);
/* zero the endpoint sizes */
if (musb_ep->is_in) {
musb->intrtxe &= ~(1 << epnum);
musb_writew(musb->mregs, MUSB_INTRTXE, musb->intrtxe);
musb_writew(epio, MUSB_TXMAXP, 0);
} else {
musb->intrrxe &= ~(1 << epnum);
musb_writew(musb->mregs, MUSB_INTRRXE, musb->intrrxe);
musb_writew(epio, MUSB_RXMAXP, 0);
}
musb_ep->desc = NULL;
musb_ep->end_point.desc = NULL;
/* abort all pending DMA and requests */
nuke(musb_ep, -ESHUTDOWN);
schedule_work(&musb->irq_work);
spin_unlock_irqrestore(&(musb->lock), flags);
dev_dbg(musb->controller, "%s\n", musb_ep->end_point.name);
return status;
}
/*
* Allocate a request for an endpoint.
* Reused by ep0 code.
*/
struct usb_request *musb_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
{
struct musb_ep *musb_ep = to_musb_ep(ep);
struct musb *musb = musb_ep->musb;
struct musb_request *request = NULL;
request = kzalloc(sizeof *request, gfp_flags);
if (!request) {
dev_dbg(musb->controller, "not enough memory\n");
return NULL;
}
request->request.dma = DMA_ADDR_INVALID;
request->epnum = musb_ep->current_epnum;
request->ep = musb_ep;
return &request->request;
}
/*
* Free a request
* Reused by ep0 code.
*/
void musb_free_request(struct usb_ep *ep, struct usb_request *req)
{
kfree(to_musb_request(req));
}
static LIST_HEAD(buffers);
struct free_record {
struct list_head list;
struct device *dev;
unsigned bytes;
dma_addr_t dma;
};
/*
* Context: controller locked, IRQs blocked.
*/
void musb_ep_restart(struct musb *musb, struct musb_request *req)
{
dev_dbg(musb->controller, "<== %s request %p len %u on hw_ep%d\n",
req->tx ? "TX/IN" : "RX/OUT",
&req->request, req->request.length, req->epnum);
musb_ep_select(musb->mregs, req->epnum);
if (req->tx)
txstate(musb, req);
else
rxstate(musb, req);
}
static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req,
gfp_t gfp_flags)
{
struct musb_ep *musb_ep;
struct musb_request *request;
struct musb *musb;
int status = 0;
unsigned long lockflags;
if (!ep || !req)
return -EINVAL;
if (!req->buf)
return -ENODATA;
musb_ep = to_musb_ep(ep);
musb = musb_ep->musb;
request = to_musb_request(req);
request->musb = musb;
if (request->ep != musb_ep)
return -EINVAL;
dev_dbg(musb->controller, "<== to %s request=%p\n", ep->name, req);
/* request is mine now... */
request->request.actual = 0;
request->request.status = -EINPROGRESS;
request->epnum = musb_ep->current_epnum;
request->tx = musb_ep->is_in;
map_dma_buffer(request, musb, musb_ep);
spin_lock_irqsave(&musb->lock, lockflags);
/* don't queue if the ep is down */
if (!musb_ep->desc) {
dev_dbg(musb->controller, "req %p queued to %s while ep %s\n",
req, ep->name, "disabled");
status = -ESHUTDOWN;
unmap_dma_buffer(request, musb);
goto unlock;
}
/* add request to the list */
list_add_tail(&request->list, &musb_ep->req_list);
/* it this is the head of the queue, start i/o ... */
if (!musb_ep->busy && &request->list == musb_ep->req_list.next)
musb_ep_restart(musb, request);
unlock:
spin_unlock_irqrestore(&musb->lock, lockflags);
return status;
}
static int musb_gadget_dequeue(struct usb_ep *ep, struct usb_request *request)
{
struct musb_ep *musb_ep = to_musb_ep(ep);
struct musb_request *req = to_musb_request(request);
struct musb_request *r;
unsigned long flags;
int status = 0;
struct musb *musb = musb_ep->musb;
if (!ep || !request || to_musb_request(request)->ep != musb_ep)
return -EINVAL;
spin_lock_irqsave(&musb->lock, flags);
list_for_each_entry(r, &musb_ep->req_list, list) {
if (r == req)
break;
}
if (r != req) {
dev_dbg(musb->controller, "request %p not queued to %s\n", request, ep->name);
status = -EINVAL;
goto done;
}
/* if the hardware doesn't have the request, easy ... */
if (musb_ep->req_list.next != &req->list || musb_ep->busy)
musb_g_giveback(musb_ep, request, -ECONNRESET);
/* ... else abort the dma transfer ... */
else if (is_dma_capable() && musb_ep->dma) {
struct dma_controller *c = musb->dma_controller;
musb_ep_select(musb->mregs, musb_ep->current_epnum);
if (c->channel_abort)
status = c->channel_abort(musb_ep->dma);
else
status = -EBUSY;
if (status == 0)
musb_g_giveback(musb_ep, request, -ECONNRESET);
} else {
/* NOTE: by sticking to easily tested hardware/driver states,
* we leave counting of in-flight packets imprecise.
*/
musb_g_giveback(musb_ep, request, -ECONNRESET);
}
done:
spin_unlock_irqrestore(&musb->lock, flags);
return status;
}
/*
* Set or clear the halt bit of an endpoint. A halted enpoint won't tx/rx any
* data but will queue requests.
*
* exported to ep0 code
*/
static int musb_gadget_set_halt(struct usb_ep *ep, int value)
{
struct musb_ep *musb_ep = to_musb_ep(ep);
u8 epnum = musb_ep->current_epnum;
struct musb *musb = musb_ep->musb;
void __iomem *epio = musb->endpoints[epnum].regs;
void __iomem *mbase;
unsigned long flags;
u16 csr;
struct musb_request *request;
int status = 0;
if (!ep)
return -EINVAL;
mbase = musb->mregs;
spin_lock_irqsave(&musb->lock, flags);
if ((USB_ENDPOINT_XFER_ISOC == musb_ep->type)) {
status = -EINVAL;
goto done;
}
musb_ep_select(mbase, epnum);
request = next_request(musb_ep);
if (value) {
if (request) {
dev_dbg(musb->controller, "request in progress, cannot halt %s\n",
ep->name);
status = -EAGAIN;
goto done;
}
/* Cannot portably stall with non-empty FIFO */
if (musb_ep->is_in) {
csr = musb_readw(epio, MUSB_TXCSR);
if (csr & MUSB_TXCSR_FIFONOTEMPTY) {
dev_dbg(musb->controller, "FIFO busy, cannot halt %s\n", ep->name);
status = -EAGAIN;
goto done;
}
}
} else
musb_ep->wedged = 0;
/* set/clear the stall and toggle bits */
dev_dbg(musb->controller, "%s: %s stall\n", ep->name, value ? "set" : "clear");
if (musb_ep->is_in) {
csr = musb_readw(epio, MUSB_TXCSR);
csr |= MUSB_TXCSR_P_WZC_BITS
| MUSB_TXCSR_CLRDATATOG;
if (value)
csr |= MUSB_TXCSR_P_SENDSTALL;
else
csr &= ~(MUSB_TXCSR_P_SENDSTALL
| MUSB_TXCSR_P_SENTSTALL);
csr &= ~MUSB_TXCSR_TXPKTRDY;
musb_writew(epio, MUSB_TXCSR, csr);
} else {
csr = musb_readw(epio, MUSB_RXCSR);
csr |= MUSB_RXCSR_P_WZC_BITS
| MUSB_RXCSR_FLUSHFIFO
| MUSB_RXCSR_CLRDATATOG;
if (value)
csr |= MUSB_RXCSR_P_SENDSTALL;
else
csr &= ~(MUSB_RXCSR_P_SENDSTALL
| MUSB_RXCSR_P_SENTSTALL);
musb_writew(epio, MUSB_RXCSR, csr);
}
/* maybe start the first request in the queue */
if (!musb_ep->busy && !value && request) {
dev_dbg(musb->controller, "restarting the request\n");
musb_ep_restart(musb, request);
}
done:
spin_unlock_irqrestore(&musb->lock, flags);
return status;
}
/*
* Sets the halt feature with the clear requests ignored
*/
static int musb_gadget_set_wedge(struct usb_ep *ep)
{
struct musb_ep *musb_ep = to_musb_ep(ep);
if (!ep)
return -EINVAL;
musb_ep->wedged = 1;
return usb_ep_set_halt(ep);
}
static int musb_gadget_fifo_status(struct usb_ep *ep)
{
struct musb_ep *musb_ep = to_musb_ep(ep);
void __iomem *epio = musb_ep->hw_ep->regs;
int retval = -EINVAL;
if (musb_ep->desc && !musb_ep->is_in) {
struct musb *musb = musb_ep->musb;
int epnum = musb_ep->current_epnum;
void __iomem *mbase = musb->mregs;
unsigned long flags;
spin_lock_irqsave(&musb->lock, flags);
musb_ep_select(mbase, epnum);
/* FIXME return zero unless RXPKTRDY is set */
retval = musb_readw(epio, MUSB_RXCOUNT);
spin_unlock_irqrestore(&musb->lock, flags);
}
return retval;
}
static void musb_gadget_fifo_flush(struct usb_ep *ep)
{
struct musb_ep *musb_ep = to_musb_ep(ep);
struct musb *musb = musb_ep->musb;
u8 epnum = musb_ep->current_epnum;
void __iomem *epio = musb->endpoints[epnum].regs;
void __iomem *mbase;
unsigned long flags;
u16 csr;
mbase = musb->mregs;
spin_lock_irqsave(&musb->lock, flags);
musb_ep_select(mbase, (u8) epnum);
/* disable interrupts */
musb_writew(mbase, MUSB_INTRTXE, musb->intrtxe & ~(1 << epnum));
if (musb_ep->is_in) {
csr = musb_readw(epio, MUSB_TXCSR);
if (csr & MUSB_TXCSR_FIFONOTEMPTY) {
csr |= MUSB_TXCSR_FLUSHFIFO | MUSB_TXCSR_P_WZC_BITS;
/*
* Setting both TXPKTRDY and FLUSHFIFO makes controller
* to interrupt current FIFO loading, but not flushing
* the already loaded ones.
*/
csr &= ~MUSB_TXCSR_TXPKTRDY;
musb_writew(epio, MUSB_TXCSR, csr);
/* REVISIT may be inappropriate w/o FIFONOTEMPTY ... */
musb_writew(epio, MUSB_TXCSR, csr);
}
} else {
csr = musb_readw(epio, MUSB_RXCSR);
csr |= MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_P_WZC_BITS;
musb_writew(epio, MUSB_RXCSR, csr);
musb_writew(epio, MUSB_RXCSR, csr);
}
/* re-enable interrupt */
musb_writew(mbase, MUSB_INTRTXE, musb->intrtxe);
spin_unlock_irqrestore(&musb->lock, flags);
}
static const struct usb_ep_ops musb_ep_ops = {
.enable = musb_gadget_enable,
.disable = musb_gadget_disable,
.alloc_request = musb_alloc_request,
.free_request = musb_free_request,
.queue = musb_gadget_queue,
.dequeue = musb_gadget_dequeue,
.set_halt = musb_gadget_set_halt,
.set_wedge = musb_gadget_set_wedge,
.fifo_status = musb_gadget_fifo_status,
.fifo_flush = musb_gadget_fifo_flush
};
/* ----------------------------------------------------------------------- */
static int musb_gadget_get_frame(struct usb_gadget *gadget)
{
struct musb *musb = gadget_to_musb(gadget);
return (int)musb_readw(musb->mregs, MUSB_FRAME);
}
static int musb_gadget_wakeup(struct usb_gadget *gadget)
{
struct musb *musb = gadget_to_musb(gadget);
void __iomem *mregs = musb->mregs;
unsigned long flags;
int status = -EINVAL;
u8 power, devctl;
int retries;
spin_lock_irqsave(&musb->lock, flags);
switch (musb->xceiv->otg->state) {
case OTG_STATE_B_PERIPHERAL:
/* NOTE: OTG state machine doesn't include B_SUSPENDED;
* that's part of the standard usb 1.1 state machine, and
* doesn't affect OTG transitions.
*/
if (musb->may_wakeup && musb->is_suspended)
break;
goto done;
case OTG_STATE_B_IDLE:
/* Start SRP ... OTG not required. */
devctl = musb_readb(mregs, MUSB_DEVCTL);
dev_dbg(musb->controller, "Sending SRP: devctl: %02x\n", devctl);
devctl |= MUSB_DEVCTL_SESSION;
musb_writeb(mregs, MUSB_DEVCTL, devctl);
devctl = musb_readb(mregs, MUSB_DEVCTL);
retries = 100;
while (!(devctl & MUSB_DEVCTL_SESSION)) {
devctl = musb_readb(mregs, MUSB_DEVCTL);
if (retries-- < 1)
break;
}
retries = 10000;
while (devctl & MUSB_DEVCTL_SESSION) {
devctl = musb_readb(mregs, MUSB_DEVCTL);
if (retries-- < 1)
break;
}
spin_unlock_irqrestore(&musb->lock, flags);
otg_start_srp(musb->xceiv->otg);
spin_lock_irqsave(&musb->lock, flags);
/* Block idling for at least 1s */
musb_platform_try_idle(musb,
jiffies + msecs_to_jiffies(1 * HZ));
status = 0;
goto done;
default:
dev_dbg(musb->controller, "Unhandled wake: %s\n",
usb_otg_state_string(musb->xceiv->otg->state));
goto done;
}
status = 0;
power = musb_readb(mregs, MUSB_POWER);
power |= MUSB_POWER_RESUME;
musb_writeb(mregs, MUSB_POWER, power);
dev_dbg(musb->controller, "issue wakeup\n");
/* FIXME do this next chunk in a timer callback, no udelay */
mdelay(2);
power = musb_readb(mregs, MUSB_POWER);
power &= ~MUSB_POWER_RESUME;
musb_writeb(mregs, MUSB_POWER, power);
done:
spin_unlock_irqrestore(&musb->lock, flags);
return status;
}
static int
musb_gadget_set_self_powered(struct usb_gadget *gadget, int is_selfpowered)
{
gadget->is_selfpowered = !!is_selfpowered;
return 0;
}
static void musb_pullup(struct musb *musb, int is_on)
{
u8 power;
power = musb_readb(musb->mregs, MUSB_POWER);
if (is_on)
power |= MUSB_POWER_SOFTCONN;
else
power &= ~MUSB_POWER_SOFTCONN;
/* FIXME if on, HdrcStart; if off, HdrcStop */
dev_dbg(musb->controller, "gadget D+ pullup %s\n",
is_on ? "on" : "off");
musb_writeb(musb->mregs, MUSB_POWER, power);
}
#if 0
static int musb_gadget_vbus_session(struct usb_gadget *gadget, int is_active)
{
dev_dbg(musb->controller, "<= %s =>\n", __func__);
/*
* FIXME iff driver's softconnect flag is set (as it is during probe,
* though that can clear it), just musb_pullup().
*/
return -EINVAL;
}
#endif
static int musb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA)
{
struct musb *musb = gadget_to_musb(gadget);
if (!musb->xceiv->set_power)
return -EOPNOTSUPP;
return usb_phy_set_power(musb->xceiv, mA);
}
static int musb_gadget_pullup(struct usb_gadget *gadget, int is_on)
{
struct musb *musb = gadget_to_musb(gadget);
unsigned long flags;
is_on = !!is_on;
pm_runtime_get_sync(musb->controller);
/* NOTE: this assumes we are sensing vbus; we'd rather
* not pullup unless the B-session is active.
*/
spin_lock_irqsave(&musb->lock, flags);
if (is_on != musb->softconnect) {
musb->softconnect = is_on;
musb_pullup(musb, is_on);
}
spin_unlock_irqrestore(&musb->lock, flags);
pm_runtime_put(musb->controller);
return 0;
}
#ifdef CONFIG_BLACKFIN
static struct usb_ep *musb_match_ep(struct usb_gadget *g,
struct usb_endpoint_descriptor *desc,
struct usb_ss_ep_comp_descriptor *ep_comp)
{
struct usb_ep *ep = NULL;
switch (usb_endpoint_type(desc)) {
case USB_ENDPOINT_XFER_ISOC:
case USB_ENDPOINT_XFER_BULK:
if (usb_endpoint_dir_in(desc))
ep = gadget_find_ep_by_name(g, "ep5in");
else
ep = gadget_find_ep_by_name(g, "ep6out");
break;
case USB_ENDPOINT_XFER_INT:
if (usb_endpoint_dir_in(desc))
ep = gadget_find_ep_by_name(g, "ep1in");
else
ep = gadget_find_ep_by_name(g, "ep2out");
break;
default:
break;
}
if (ep && usb_gadget_ep_match_desc(g, ep, desc, ep_comp))
return ep;
return NULL;
}
#else
#define musb_match_ep NULL
#endif
static int musb_gadget_start(struct usb_gadget *g,
struct usb_gadget_driver *driver);
static int musb_gadget_stop(struct usb_gadget *g);
usb: gadget: convert all users to the new udc infrastructure peripheral drivers are using usb_add_gadget()/usb_del_gadget() to register/unregister to the udc-core. The udc-core will take the first available gadget driver and attach function driver which is calling usb_gadget_register_driver(). This is the same behaviour we have right now. Only dummy_hcd was tested, the others were compiled tested. Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Anton Tikhomirov <av.tikhomirov@samsung.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: Dan Carpenter <error27@gmail.com> Cc: Darius Augulis <augulis.darius@gmail.com> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Jingoo Han <jg1.han@samsung.com> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Cc: Li Yang <leoli@freescale.com> Cc: Michael Hennerich <michael.hennerich@analog.com> Acked-by: Mike Frysinger <vapier@gentoo.org> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Pavankumar Kondeti <pkondeti@codeaurora.org> Cc: Roy Huang <roy.huang@analog.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Toshiharu Okada <toshiharu-linux@dsn.okisemi.com> Cc: Xiaochen Shen <xiaochen.shen@intel.com> Cc: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Cc: Yuan-Hsin Chen <yhchen@faraday-tech.com> Cc: cxie4 <cxie4@marvell.com> Cc: linux-geode@lists.infradead.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2011-06-28 13:33:47 +00:00
static const struct usb_gadget_ops musb_gadget_operations = {
.get_frame = musb_gadget_get_frame,
.wakeup = musb_gadget_wakeup,
.set_selfpowered = musb_gadget_set_self_powered,
/* .vbus_session = musb_gadget_vbus_session, */
.vbus_draw = musb_gadget_vbus_draw,
.pullup = musb_gadget_pullup,
.udc_start = musb_gadget_start,
.udc_stop = musb_gadget_stop,
.match_ep = musb_match_ep,
};
/* ----------------------------------------------------------------------- */
/* Registration */
/* Only this registration code "knows" the rule (from USB standards)
* about there being only one external upstream port. It assumes
* all peripheral ports are external...
*/
static void
init_peripheral_ep(struct musb *musb, struct musb_ep *ep, u8 epnum, int is_in)
{
struct musb_hw_ep *hw_ep = musb->endpoints + epnum;
memset(ep, 0, sizeof *ep);
ep->current_epnum = epnum;
ep->musb = musb;
ep->hw_ep = hw_ep;
ep->is_in = is_in;
INIT_LIST_HEAD(&ep->req_list);
sprintf(ep->name, "ep%d%s", epnum,
(!epnum || hw_ep->is_shared_fifo) ? "" : (
is_in ? "in" : "out"));
ep->end_point.name = ep->name;
INIT_LIST_HEAD(&ep->end_point.ep_list);
if (!epnum) {
usb_ep_set_maxpacket_limit(&ep->end_point, 64);
ep->end_point.caps.type_control = true;
ep->end_point.ops = &musb_g_ep0_ops;
musb->g.ep0 = &ep->end_point;
} else {
if (is_in)
usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_tx);
else
usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_rx);
ep->end_point.caps.type_iso = true;
ep->end_point.caps.type_bulk = true;
ep->end_point.caps.type_int = true;
ep->end_point.ops = &musb_ep_ops;
list_add_tail(&ep->end_point.ep_list, &musb->g.ep_list);
}
if (!epnum || hw_ep->is_shared_fifo) {
ep->end_point.caps.dir_in = true;
ep->end_point.caps.dir_out = true;
} else if (is_in)
ep->end_point.caps.dir_in = true;
else
ep->end_point.caps.dir_out = true;
}
/*
* Initialize the endpoints exposed to peripheral drivers, with backlinks
* to the rest of the driver state.
*/
static inline void musb_g_init_endpoints(struct musb *musb)
{
u8 epnum;
struct musb_hw_ep *hw_ep;
unsigned count = 0;
/* initialize endpoint list just once */
INIT_LIST_HEAD(&(musb->g.ep_list));
for (epnum = 0, hw_ep = musb->endpoints;
epnum < musb->nr_endpoints;
epnum++, hw_ep++) {
if (hw_ep->is_shared_fifo /* || !epnum */) {
init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 0);
count++;
} else {
if (hw_ep->max_packet_sz_tx) {
init_peripheral_ep(musb, &hw_ep->ep_in,
epnum, 1);
count++;
}
if (hw_ep->max_packet_sz_rx) {
init_peripheral_ep(musb, &hw_ep->ep_out,
epnum, 0);
count++;
}
}
}
}
/* called once during driver setup to initialize and link into
* the driver model; memory is zeroed.
*/
int musb_gadget_setup(struct musb *musb)
{
int status;
/* REVISIT minor race: if (erroneously) setting up two
* musb peripherals at the same time, only the bus lock
* is probably held.
*/
musb->g.ops = &musb_gadget_operations;
musb->g.max_speed = USB_SPEED_HIGH;
musb->g.speed = USB_SPEED_UNKNOWN;
MUSB_DEV_MODE(musb);
musb->xceiv->otg->default_a = 0;
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
/* this "gadget" abstracts/virtualizes the controller */
musb->g.name = musb_driver_name;
#if IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE)
musb->g.is_otg = 1;
#elif IS_ENABLED(CONFIG_USB_MUSB_GADGET)
musb->g.is_otg = 0;
#endif
musb_g_init_endpoints(musb);
musb->is_active = 0;
musb_platform_try_idle(musb, 0);
usb: gadget: convert all users to the new udc infrastructure peripheral drivers are using usb_add_gadget()/usb_del_gadget() to register/unregister to the udc-core. The udc-core will take the first available gadget driver and attach function driver which is calling usb_gadget_register_driver(). This is the same behaviour we have right now. Only dummy_hcd was tested, the others were compiled tested. Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Anton Tikhomirov <av.tikhomirov@samsung.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: Dan Carpenter <error27@gmail.com> Cc: Darius Augulis <augulis.darius@gmail.com> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Jingoo Han <jg1.han@samsung.com> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Cc: Li Yang <leoli@freescale.com> Cc: Michael Hennerich <michael.hennerich@analog.com> Acked-by: Mike Frysinger <vapier@gentoo.org> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Pavankumar Kondeti <pkondeti@codeaurora.org> Cc: Roy Huang <roy.huang@analog.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Toshiharu Okada <toshiharu-linux@dsn.okisemi.com> Cc: Xiaochen Shen <xiaochen.shen@intel.com> Cc: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Cc: Yuan-Hsin Chen <yhchen@faraday-tech.com> Cc: cxie4 <cxie4@marvell.com> Cc: linux-geode@lists.infradead.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2011-06-28 13:33:47 +00:00
status = usb_add_gadget_udc(musb->controller, &musb->g);
if (status)
goto err;
return 0;
err:
usb: musb: gadget: fix error path In case one "forgot" to load the receiver i.e. doing |modprobe omap2430 |modprobe musb_hdrc he ends up with: |musb-hdrc: version 6.0, ?dma?, otg (peripheral+host) |HS USB OTG: no transceiver configured |musb-hdrc musb-hdrc: musb_init_controller failed with status -19 |(NULL device *): gadget not registered. |Unable to handle kernel NULL pointer dereference at virtual address 0000001c |Internal error: Oops: 17 [#1] SMP |[<c011383c>] (sysfs_find_dirent+0x4/0x60) from [<c01138c0>] (sysfs_get_dirent+0x28/0x78) |[<c01138c0>] (sysfs_get_dirent+0x28/0x78) from [<c0115b78>] (sysfs_unmerge_group+0x1c/0x90) |[<c0115b78>] (sysfs_unmerge_group+0x1c/0x90) from [<c0179ba4>] (dpm_sysfs_remove+0x14/0x3c) |[<c0179ba4>] (dpm_sysfs_remove+0x14/0x3c) from [<c01742f8>] (device_del+0x40/0x1b4) |[<c01742f8>] (device_del+0x40/0x1b4) from [<c0174478>] (device_unregister+0xc/0x18) |[<c0174478>] (device_unregister+0xc/0x18) from [<bf0489b4>] (musb_free+0x24/0x88 [musb_hdrc]) |[<bf0489b4>] (musb_free+0x24/0x88 [musb_hdrc]) from [<bf057d18>] (musb_probe+0xb50/0xe3c [musb_hdrc]) |[<bf057d18>] (musb_probe+0xb50/0xe3c [musb_hdrc]) from [<c01779c4>] (platform_drv_probe+0x1c/0x24) The problem is that musb_free() tries to figure out what was initializued and what wasn't and clean up only the initialized part. This works well for usb_del_gadget_udc() but device_unregister() can't deal with it. Therefore we rely on the fact the we always have a parent device and only then remove the device. I broke this in 0f91349 ("usb: gadget: convert all users to the new udc infrastructure") Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-08-10 09:01:57 +00:00
musb->g.dev.parent = NULL;
usb: gadget: convert all users to the new udc infrastructure peripheral drivers are using usb_add_gadget()/usb_del_gadget() to register/unregister to the udc-core. The udc-core will take the first available gadget driver and attach function driver which is calling usb_gadget_register_driver(). This is the same behaviour we have right now. Only dummy_hcd was tested, the others were compiled tested. Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Anton Tikhomirov <av.tikhomirov@samsung.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: Dan Carpenter <error27@gmail.com> Cc: Darius Augulis <augulis.darius@gmail.com> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Jingoo Han <jg1.han@samsung.com> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Cc: Li Yang <leoli@freescale.com> Cc: Michael Hennerich <michael.hennerich@analog.com> Acked-by: Mike Frysinger <vapier@gentoo.org> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Pavankumar Kondeti <pkondeti@codeaurora.org> Cc: Roy Huang <roy.huang@analog.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Toshiharu Okada <toshiharu-linux@dsn.okisemi.com> Cc: Xiaochen Shen <xiaochen.shen@intel.com> Cc: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Cc: Yuan-Hsin Chen <yhchen@faraday-tech.com> Cc: cxie4 <cxie4@marvell.com> Cc: linux-geode@lists.infradead.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2011-06-28 13:33:47 +00:00
device_unregister(&musb->g.dev);
return status;
}
void musb_gadget_cleanup(struct musb *musb)
{
if (musb->port_mode == MUSB_PORT_MODE_HOST)
return;
usb: gadget: convert all users to the new udc infrastructure peripheral drivers are using usb_add_gadget()/usb_del_gadget() to register/unregister to the udc-core. The udc-core will take the first available gadget driver and attach function driver which is calling usb_gadget_register_driver(). This is the same behaviour we have right now. Only dummy_hcd was tested, the others were compiled tested. Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Anton Tikhomirov <av.tikhomirov@samsung.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: Dan Carpenter <error27@gmail.com> Cc: Darius Augulis <augulis.darius@gmail.com> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Jingoo Han <jg1.han@samsung.com> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Cc: Li Yang <leoli@freescale.com> Cc: Michael Hennerich <michael.hennerich@analog.com> Acked-by: Mike Frysinger <vapier@gentoo.org> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Pavankumar Kondeti <pkondeti@codeaurora.org> Cc: Roy Huang <roy.huang@analog.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Toshiharu Okada <toshiharu-linux@dsn.okisemi.com> Cc: Xiaochen Shen <xiaochen.shen@intel.com> Cc: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Cc: Yuan-Hsin Chen <yhchen@faraday-tech.com> Cc: cxie4 <cxie4@marvell.com> Cc: linux-geode@lists.infradead.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2011-06-28 13:33:47 +00:00
usb_del_gadget_udc(&musb->g);
}
/*
* Register the gadget driver. Used by gadget drivers when
* registering themselves with the controller.
*
* -EINVAL something went wrong (not driver)
* -EBUSY another gadget is already using the controller
* -ENOMEM no memory to perform the operation
*
* @param driver the gadget driver
* @return <0 if error, 0 if everything is fine
*/
static int musb_gadget_start(struct usb_gadget *g,
struct usb_gadget_driver *driver)
{
struct musb *musb = gadget_to_musb(g);
struct usb_otg *otg = musb->xceiv->otg;
unsigned long flags;
int retval = 0;
if (driver->max_speed < USB_SPEED_HIGH) {
retval = -EINVAL;
goto err;
}
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
pm_runtime_get_sync(musb->controller);
musb->softconnect = 0;
musb->gadget_driver = driver;
spin_lock_irqsave(&musb->lock, flags);
musb->is_active = 1;
otg_set_peripheral(otg, &musb->g);
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
spin_unlock_irqrestore(&musb->lock, flags);
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
musb_start(musb);
/* REVISIT: funcall to other code, which also
* handles power budgeting ... this way also
* ensures HdrcStart is indirectly called.
*/
if (musb->xceiv->last_event == USB_EVENT_ID)
musb_platform_set_vbus(musb, 1);
if (musb->xceiv->last_event == USB_EVENT_NONE)
pm_runtime_put(musb->controller);
return 0;
err:
return retval;
}
/*
* Unregister the gadget driver. Used by gadget drivers when
* unregistering themselves from the controller.
*
* @param driver the gadget driver to unregister
*/
static int musb_gadget_stop(struct usb_gadget *g)
{
struct musb *musb = gadget_to_musb(g);
unsigned long flags;
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
if (musb->xceiv->last_event == USB_EVENT_NONE)
pm_runtime_get_sync(musb->controller);
/*
* REVISIT always use otg_set_peripheral() here too;
* this needs to shut down the OTG engine.
*/
spin_lock_irqsave(&musb->lock, flags);
musb_hnp_stop(musb);
(void) musb_gadget_vbus_draw(&musb->g, 0);
musb->xceiv->otg->state = OTG_STATE_UNDEFINED;
musb_stop(musb);
otg_set_peripheral(musb->xceiv->otg, NULL);
musb->is_active = 0;
musb->gadget_driver = NULL;
musb_platform_try_idle(musb, 0);
spin_unlock_irqrestore(&musb->lock, flags);
/*
* FIXME we need to be able to register another
* gadget driver here and have everything work;
* that currently misbehaves.
*/
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
pm_runtime_put(musb->controller);
return 0;
}
/* ----------------------------------------------------------------------- */
/* lifecycle operations called through plat_uds.c */
void musb_g_resume(struct musb *musb)
{
musb->is_suspended = 0;
switch (musb->xceiv->otg->state) {
case OTG_STATE_B_IDLE:
break;
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_PERIPHERAL:
musb->is_active = 1;
if (musb->gadget_driver && musb->gadget_driver->resume) {
spin_unlock(&musb->lock);
musb->gadget_driver->resume(&musb->g);
spin_lock(&musb->lock);
}
break;
default:
WARNING("unhandled RESUME transition (%s)\n",
usb_otg_state_string(musb->xceiv->otg->state));
}
}
/* called when SOF packets stop for 3+ msec */
void musb_g_suspend(struct musb *musb)
{
u8 devctl;
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
dev_dbg(musb->controller, "devctl %02x\n", devctl);
switch (musb->xceiv->otg->state) {
case OTG_STATE_B_IDLE:
if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS)
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
break;
case OTG_STATE_B_PERIPHERAL:
musb->is_suspended = 1;
if (musb->gadget_driver && musb->gadget_driver->suspend) {
spin_unlock(&musb->lock);
musb->gadget_driver->suspend(&musb->g);
spin_lock(&musb->lock);
}
break;
default:
/* REVISIT if B_HOST, clear DEVCTL.HOSTREQ;
* A_PERIPHERAL may need care too
*/
WARNING("unhandled SUSPEND transition (%s)\n",
usb_otg_state_string(musb->xceiv->otg->state));
}
}
/* Called during SRP */
void musb_g_wakeup(struct musb *musb)
{
musb_gadget_wakeup(&musb->g);
}
/* called when VBUS drops below session threshold, and in other cases */
void musb_g_disconnect(struct musb *musb)
{
void __iomem *mregs = musb->mregs;
u8 devctl = musb_readb(mregs, MUSB_DEVCTL);
dev_dbg(musb->controller, "devctl %02x\n", devctl);
/* clear HR */
musb_writeb(mregs, MUSB_DEVCTL, devctl & MUSB_DEVCTL_SESSION);
/* don't draw vbus until new b-default session */
(void) musb_gadget_vbus_draw(&musb->g, 0);
musb->g.speed = USB_SPEED_UNKNOWN;
if (musb->gadget_driver && musb->gadget_driver->disconnect) {
spin_unlock(&musb->lock);
musb->gadget_driver->disconnect(&musb->g);
spin_lock(&musb->lock);
}
switch (musb->xceiv->otg->state) {
default:
dev_dbg(musb->controller, "Unhandled disconnect %s, setting a_idle\n",
usb_otg_state_string(musb->xceiv->otg->state));
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
MUSB_HST_MODE(musb);
break;
case OTG_STATE_A_PERIPHERAL:
musb->xceiv->otg->state = OTG_STATE_A_WAIT_BCON;
MUSB_HST_MODE(musb);
break;
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_HOST:
case OTG_STATE_B_PERIPHERAL:
case OTG_STATE_B_IDLE:
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
break;
case OTG_STATE_B_SRP_INIT:
break;
}
musb->is_active = 0;
}
void musb_g_reset(struct musb *musb)
__releases(musb->lock)
__acquires(musb->lock)
{
void __iomem *mbase = musb->mregs;
u8 devctl = musb_readb(mbase, MUSB_DEVCTL);
u8 power;
dev_dbg(musb->controller, "<== %s driver '%s'\n",
(devctl & MUSB_DEVCTL_BDEVICE)
? "B-Device" : "A-Device",
musb->gadget_driver
? musb->gadget_driver->driver.name
: NULL
);
/* report reset, if we didn't already (flushing EP state) */
if (musb->gadget_driver && musb->g.speed != USB_SPEED_UNKNOWN) {
spin_unlock(&musb->lock);
usb_gadget_udc_reset(&musb->g, musb->gadget_driver);
spin_lock(&musb->lock);
}
/* clear HR */
else if (devctl & MUSB_DEVCTL_HR)
musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
/* what speed did we negotiate? */
power = musb_readb(mbase, MUSB_POWER);
musb->g.speed = (power & MUSB_POWER_HSMODE)
? USB_SPEED_HIGH : USB_SPEED_FULL;
/* start in USB_STATE_DEFAULT */
musb->is_active = 1;
musb->is_suspended = 0;
MUSB_DEV_MODE(musb);
musb->address = 0;
musb->ep0_state = MUSB_EP0_STAGE_SETUP;
musb->may_wakeup = 0;
musb->g.b_hnp_enable = 0;
musb->g.a_alt_hnp_support = 0;
musb->g.a_hnp_support = 0;
musb->g.quirk_zlp_not_supp = 1;
/* Normal reset, as B-Device;
* or else after HNP, as A-Device
*/
if (!musb->g.is_otg) {
/* USB device controllers that are not OTG compatible
* may not have DEVCTL register in silicon.
* In that case, do not rely on devctl for setting
* peripheral mode.
*/
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
musb->g.is_a_peripheral = 0;
} else if (devctl & MUSB_DEVCTL_BDEVICE) {
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
musb->g.is_a_peripheral = 0;
} else {
musb->xceiv->otg->state = OTG_STATE_A_PERIPHERAL;
musb->g.is_a_peripheral = 1;
}
/* start with default limits on VBUS power draw */
(void) musb_gadget_vbus_draw(&musb->g, 8);
}