cregit-Linux how code gets into the kernel

Release 4.11 drivers/usb/musb/musb_gadget_ep0.c

Directory: drivers/usb/musb
/*
 * MUSB OTG peripheral driver ep0 handling
 *
 * Copyright 2005 Mentor Graphics Corporation
 * Copyright (C) 2005-2006 by Texas Instruments
 * Copyright (C) 2006-2007 Nokia Corporation
 * Copyright (C) 2008-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/spinlock.h>
#include <linux/device.h>
#include <linux/interrupt.h>

#include "musb_core.h"

/* ep0 is always musb->endpoints[0].ep_in */

#define	next_ep0_request(musb)	next_in_request(&(musb)->endpoints[0])

/*
 * locking note:  we use only the controller lock, for simpler correctness.
 * It's always held with IRQs blocked.
 *
 * It protects the ep0 request queue as well as ep0_state, not just the
 * controller and indexed registers.  And that lock stays held unless it
 * needs to be dropped to allow reentering this driver ... like upcalls to
 * the gadget driver, or adjusting endpoint halt status.
 */


static char *decode_ep0stage(u8 stage) { switch (stage) { case MUSB_EP0_STAGE_IDLE: return "idle"; case MUSB_EP0_STAGE_SETUP: return "setup"; case MUSB_EP0_STAGE_TX: return "in"; case MUSB_EP0_STAGE_RX: return "out"; case MUSB_EP0_STAGE_ACKWAIT: return "wait"; case MUSB_EP0_STAGE_STATUSIN: return "in/status"; case MUSB_EP0_STAGE_STATUSOUT: return "out/status"; default: return "?"; } }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi5588.71%150.00%
Sergei Shtylyov711.29%150.00%
Total62100.00%2100.00%

/* handle a standard GET_STATUS request * Context: caller holds controller lock */
static int service_tx_status_request( struct musb *musb, const struct usb_ctrlrequest *ctrlrequest) { void __iomem *mbase = musb->mregs; int handled = 1; u8 result[2], epnum = 0; const u8 recip = ctrlrequest->bRequestType & USB_RECIP_MASK; result[1] = 0; switch (recip) { case USB_RECIP_DEVICE: result[0] = musb->g.is_selfpowered << USB_DEVICE_SELF_POWERED; result[0] |= musb->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; if (musb->g.is_otg) { result[0] |= musb->g.b_hnp_enable << USB_DEVICE_B_HNP_ENABLE; result[0] |= musb->g.a_alt_hnp_support << USB_DEVICE_A_ALT_HNP_SUPPORT; result[0] |= musb->g.a_hnp_support << USB_DEVICE_A_HNP_SUPPORT; } break; case USB_RECIP_INTERFACE: result[0] = 0; break; case USB_RECIP_ENDPOINT: { int is_in; struct musb_ep *ep; u16 tmp; void __iomem *regs; epnum = (u8) ctrlrequest->wIndex; if (!epnum) { result[0] = 0; break; } is_in = epnum & USB_DIR_IN; if (is_in) { epnum &= 0x0f; ep = &musb->endpoints[epnum].ep_in; } else { ep = &musb->endpoints[epnum].ep_out; } regs = musb->endpoints[epnum].regs; if (epnum >= MUSB_C_NUM_EPS || !ep->desc) { handled = -EINVAL; break; } musb_ep_select(mbase, epnum); if (is_in) tmp = musb_readw(regs, MUSB_TXCSR) & MUSB_TXCSR_P_SENDSTALL; else tmp = musb_readw(regs, MUSB_RXCSR) & MUSB_RXCSR_P_SENDSTALL; musb_ep_select(mbase, 0); result[0] = tmp ? 1 : 0; } break; default: /* class, vendor, etc ... delegate */ handled = 0; break; } /* fill up the fifo; caller updates csr0 */ if (handled > 0) { u16 len = le16_to_cpu(ctrlrequest->wLength); if (len > 2) len = 2; musb_write_fifo(&musb->endpoints[0], len, result); } return handled; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi37499.20%150.00%
Peter Chen30.80%150.00%
Total377100.00%2100.00%

/* * handle a control-IN request, the end0 buffer contains the current request * that is supposed to be a standard control request. Assumes the fifo to * be at least 2 bytes long. * * @return 0 if the request was NOT HANDLED, * < 0 when error * > 0 when the request is processed * * Context: caller holds controller lock */
static int service_in_request(struct musb *musb, const struct usb_ctrlrequest *ctrlrequest) { int handled = 0; /* not handled */ if ((ctrlrequest->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrlrequest->bRequest) { case USB_REQ_GET_STATUS: handled = service_tx_status_request(musb, ctrlrequest); break; /* case USB_REQ_SYNC_FRAME: */ default: break; } } return handled; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi64100.00%1100.00%
Total64100.00%1100.00%

/* * Context: caller holds controller lock */
static void musb_g_ep0_giveback(struct musb *musb, struct usb_request *req) { musb_g_giveback(&musb->endpoints[0].ep_in, req, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi33100.00%1100.00%
Total33100.00%1100.00%

/* * Tries to start B-device HNP negotiation if enabled via sysfs */
static inline void musb_try_b_hnp_enable(struct musb *musb) { void __iomem *mbase = musb->mregs; u8 devctl; musb_dbg(musb, "HNP: Setting HR"); devctl = musb_readb(mbase, MUSB_DEVCTL); musb_writeb(mbase, MUSB_DEVCTL, devctl | MUSB_DEVCTL_HR); }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi4996.08%266.67%
Bin Liu23.92%133.33%
Total51100.00%3100.00%

/* * Handle all control requests with no DATA stage, including standard * requests such as: * USB_REQ_SET_CONFIGURATION, USB_REQ_SET_INTERFACE, unrecognized * always delegated to the gadget driver * USB_REQ_SET_ADDRESS, USB_REQ_CLEAR_FEATURE, USB_REQ_SET_FEATURE * always handled here, except for class/vendor/... features * * Context: caller holds controller lock */
static int service_zero_data_request(struct musb *musb, struct usb_ctrlrequest *ctrlrequest) __releases(musb->lock) __acquires(musb->lock) { int handled = -EINVAL; void __iomem *mbase = musb->mregs; const u8 recip = ctrlrequest->bRequestType & USB_RECIP_MASK; /* the gadget driver handles everything except what we MUST handle */ if ((ctrlrequest->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrlrequest->bRequest) { case USB_REQ_SET_ADDRESS: /* change it after the status stage */ musb->set_address = true; musb->address = (u8) (ctrlrequest->wValue & 0x7f); handled = 1; break; case USB_REQ_CLEAR_FEATURE: switch (recip) { case USB_RECIP_DEVICE: if (ctrlrequest->wValue != USB_DEVICE_REMOTE_WAKEUP) break; musb->may_wakeup = 0; handled = 1; break; case USB_RECIP_INTERFACE: break; case USB_RECIP_ENDPOINT:{ const u8 epnum = ctrlrequest->wIndex & 0x0f; struct musb_ep *musb_ep; struct musb_hw_ep *ep; struct musb_request *request; void __iomem *regs; int is_in; u16 csr; if (epnum == 0 || epnum >= MUSB_C_NUM_EPS || ctrlrequest->wValue != USB_ENDPOINT_HALT) break; ep = musb->endpoints + epnum; regs = ep->regs; is_in = ctrlrequest->wIndex & USB_DIR_IN; if (is_in) musb_ep = &ep->ep_in; else musb_ep = &ep->ep_out; if (!musb_ep->desc) break; handled = 1; /* Ignore request if endpoint is wedged */ if (musb_ep->wedged) break; musb_ep_select(mbase, epnum); if (is_in) { csr = musb_readw(regs, MUSB_TXCSR); csr |= MUSB_TXCSR_CLRDATATOG | MUSB_TXCSR_P_WZC_BITS; csr &= ~(MUSB_TXCSR_P_SENDSTALL | MUSB_TXCSR_P_SENTSTALL | MUSB_TXCSR_TXPKTRDY); musb_writew(regs, MUSB_TXCSR, csr); } else { csr = musb_readw(regs, MUSB_RXCSR); csr |= MUSB_RXCSR_CLRDATATOG | MUSB_RXCSR_P_WZC_BITS; csr &= ~(MUSB_RXCSR_P_SENDSTALL | MUSB_RXCSR_P_SENTSTALL); musb_writew(regs, MUSB_RXCSR, csr); } /* Maybe start the first request in the queue */ request = next_request(musb_ep); if (!musb_ep->busy && request) { musb_dbg(musb, "restarting the request"); musb_ep_restart(musb, request); } /* select ep0 again */ musb_ep_select(mbase, 0); } break; default: /* class, vendor, etc ... delegate */ handled = 0; break; } break; case USB_REQ_SET_FEATURE: switch (recip) { case USB_RECIP_DEVICE: handled = 1; switch (ctrlrequest->wValue) { case USB_DEVICE_REMOTE_WAKEUP: musb->may_wakeup = 1; break; case USB_DEVICE_TEST_MODE: if (musb->g.speed != USB_SPEED_HIGH) goto stall; if (ctrlrequest->wIndex & 0xff) goto stall; switch (ctrlrequest->wIndex >> 8) { case 1: pr_debug("TEST_J\n"); /* TEST_J */ musb->test_mode_nr = MUSB_TEST_J; break; case 2: /* TEST_K */ pr_debug("TEST_K\n"); musb->test_mode_nr = MUSB_TEST_K; break; case 3: /* TEST_SE0_NAK */ pr_debug("TEST_SE0_NAK\n"); musb->test_mode_nr = MUSB_TEST_SE0_NAK; break; case 4: /* TEST_PACKET */ pr_debug("TEST_PACKET\n"); musb->test_mode_nr = MUSB_TEST_PACKET; break; case 0xc0: /* TEST_FORCE_HS */ pr_debug("TEST_FORCE_HS\n"); musb->test_mode_nr = MUSB_TEST_FORCE_HS; break; case 0xc1: /* TEST_FORCE_FS */ pr_debug("TEST_FORCE_FS\n"); musb->test_mode_nr = MUSB_TEST_FORCE_FS; break; case 0xc2: /* TEST_FIFO_ACCESS */ pr_debug("TEST_FIFO_ACCESS\n"); musb->test_mode_nr = MUSB_TEST_FIFO_ACCESS; break; case 0xc3: /* TEST_FORCE_HOST */ pr_debug("TEST_FORCE_HOST\n"); musb->test_mode_nr = MUSB_TEST_FORCE_HOST; break; default: goto stall; } /* enter test mode after irq */ if (handled > 0) musb->test_mode = true; break; case USB_DEVICE_B_HNP_ENABLE: if (!musb->g.is_otg) goto stall; musb->g.b_hnp_enable = 1; musb_try_b_hnp_enable(musb); break; case USB_DEVICE_A_HNP_SUPPORT: if (!musb->g.is_otg) goto stall; musb->g.a_hnp_support = 1; break; case USB_DEVICE_A_ALT_HNP_SUPPORT: if (!musb->g.is_otg) goto stall; musb->g.a_alt_hnp_support = 1; break; case USB_DEVICE_DEBUG_MODE: handled = 0; break; stall: default: handled = -EINVAL; break; } break; case USB_RECIP_INTERFACE: break; case USB_RECIP_ENDPOINT:{ const u8 epnum = ctrlrequest->wIndex & 0x0f; struct musb_ep *musb_ep; struct musb_hw_ep *ep; void __iomem *regs; int is_in; u16 csr; if (epnum == 0 || epnum >= MUSB_C_NUM_EPS || ctrlrequest->wValue != USB_ENDPOINT_HALT) break; ep = musb->endpoints + epnum; regs = ep->regs; is_in = ctrlrequest->wIndex & USB_DIR_IN; if (is_in) musb_ep = &ep->ep_in; else musb_ep = &ep->ep_out; if (!musb_ep->desc) break; musb_ep_select(mbase, epnum); if (is_in) { csr = musb_readw(regs, MUSB_TXCSR); if (csr & MUSB_TXCSR_FIFONOTEMPTY) csr |= MUSB_TXCSR_FLUSHFIFO; csr |= MUSB_TXCSR_P_SENDSTALL | MUSB_TXCSR_CLRDATATOG | MUSB_TXCSR_P_WZC_BITS; musb_writew(regs, MUSB_TXCSR, csr); } else { csr = musb_readw(regs, MUSB_RXCSR); csr |= MUSB_RXCSR_P_SENDSTALL | MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_CLRDATATOG | MUSB_RXCSR_P_WZC_BITS; musb_writew(regs, MUSB_RXCSR, csr); } /* select ep0 again */ musb_ep_select(mbase, 0); handled = 1; } break; default: /* class, vendor, etc ... delegate */ handled = 0; break; } break; default: /* delegate SET_CONFIGURATION, etc */ handled = 0; } } else handled = 0; return handled; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi75581.36%333.33%
Sergei Shtylyov16317.56%444.44%
stephane duverger80.86%111.11%
Bin Liu20.22%111.11%
Total928100.00%9100.00%

/* we have an ep0out data packet * Context: caller holds controller lock */
static void ep0_rxstate(struct musb *musb) { void __iomem *regs = musb->control_ep->regs; struct musb_request *request; struct usb_request *req; u16 count, csr; request = next_ep0_request(musb); req = &request->request; /* read packet and ack; or stall because of gadget driver bug: * should have provided the rx buffer before setup() returned. */ if (req) { void *buf = req->buf + req->actual; unsigned len = req->length - req->actual; /* read the buffer */ count = musb_readb(regs, MUSB_COUNT0); if (count > len) { req->status = -EOVERFLOW; count = len; } if (count > 0) { musb_read_fifo(&musb->endpoints[0], count, buf); req->actual += count; } csr = MUSB_CSR0_P_SVDRXPKTRDY; if (count < 64 || req->actual == req->length) { musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; csr |= MUSB_CSR0_P_DATAEND; } else req = NULL; } else csr = MUSB_CSR0_P_SVDRXPKTRDY | MUSB_CSR0_P_SENDSTALL; /* Completion handler may choose to stall, e.g. because the * message just received holds invalid data. */ if (req) { musb->ackpend = csr; musb_g_ep0_giveback(musb, req); if (!musb->ackpend) return; musb->ackpend = 0; } musb_ep_select(musb->mregs, 0); musb_writew(regs, MUSB_CSR0, csr); }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi20286.70%240.00%
Bryan Wu146.01%120.00%
Anand Gadiyar93.86%120.00%
Ravi Babu83.43%120.00%
Total233100.00%5100.00%

/* * transmitting to the host (IN), this code might be called from IRQ * and from kernel thread. * * Context: caller holds controller lock */
static void ep0_txstate(struct musb *musb) { void __iomem *regs = musb->control_ep->regs; struct musb_request *req = next_ep0_request(musb); struct usb_request *request; u16 csr = MUSB_CSR0_TXPKTRDY; u8 *fifo_src; u8 fifo_count; if (!req) { /* WARN_ON(1); */ musb_dbg(musb, "odd; csr0 %04x", musb_readw(regs, MUSB_CSR0)); return; } request = &req->request; /* load the data */ fifo_src = (u8 *) request->buf + request->actual; fifo_count = min((unsigned) MUSB_EP0_FIFOSIZE, request->length - request->actual); musb_write_fifo(&musb->endpoints[0], fifo_count, fifo_src); request->actual += fifo_count; /* update the flags */ if (fifo_count < MUSB_MAX_END0_PACKET || (request->actual == request->length && !request->zero)) { musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT; csr |= MUSB_CSR0_P_DATAEND; } else request = NULL; /* report completions as soon as the fifo's loaded; there's no * win in waiting till this last packet gets acked. (other than * very precise fault reporting, needed by USB TMC; possible with * this hardware, but not usable from portable gadget drivers.) */ if (request) { musb->ackpend = csr; musb_g_ep0_giveback(musb, request); if (!musb->ackpend) return; musb->ackpend = 0; } /* send it out, triggering a "txpktrdy cleared" irq */ musb_ep_select(musb->mregs, 0); musb_writew(regs, MUSB_CSR0, csr); }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi20792.00%350.00%
Anand Gadiyar94.00%116.67%
Daniel Glöckner73.11%116.67%
Bin Liu20.89%116.67%
Total225100.00%6100.00%

/* * Read a SETUP packet (struct usb_ctrlrequest) from the hardware. * Fields are left in USB byte-order. * * Context: caller holds controller lock. */
static void musb_read_setup(struct musb *musb, struct usb_ctrlrequest *req) { struct musb_request *r; void __iomem *regs = musb->control_ep->regs; musb_read_fifo(&musb->endpoints[0], sizeof *req, (u8 *)req); /* NOTE: earlier 2.6 versions changed setup packets to host * order, but now USB packets always stay in USB byte order. */ musb_dbg(musb, "SETUP req%02x.%02x v%04x i%04x l%d", req->bRequestType, req->bRequest, le16_to_cpu(req->wValue), le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); /* clean up any leftover transfers */ r = next_ep0_request(musb); if (r) musb_g_ep0_giveback(musb, &r->request); /* For zero-data requests we want to delay the STATUS stage to * avoid SETUPEND errors. If we read data (OUT), delay accepting * packets until there's a buffer to store them in. * * If we write data, the controller acts happier if we enable * the TX FIFO right away, and give the controller a moment * to switch modes... */ musb->set_address = false; musb->ackpend = MUSB_CSR0_P_SVDRXPKTRDY; if (req->wLength == 0) { if (req->bRequestType & USB_DIR_IN) musb->ackpend |= MUSB_CSR0_TXPKTRDY; musb->ep0_state = MUSB_EP0_STAGE_ACKWAIT; } else if (req->bRequestType & USB_DIR_IN) { musb->ep0_state = MUSB_EP0_STAGE_TX; musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SVDRXPKTRDY); while ((musb_readw(regs, MUSB_CSR0) & MUSB_CSR0_RXPKTRDY) != 0) cpu_relax(); musb->ackpend = 0; } else musb->ep0_state = MUSB_EP0_STAGE_RX; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi21099.06%375.00%
Bin Liu20.94%125.00%
Total212100.00%4100.00%


static int forward_to_driver(struct musb *musb, const struct usb_ctrlrequest *ctrlrequest) __releases(musb->lock) __acquires(musb->lock) { int retval; if (!musb->gadget_driver) return -EOPNOTSUPP; spin_unlock(&musb->lock); retval = musb->gadget_driver->setup(&musb->g, ctrlrequest); spin_lock(&musb->lock); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi78100.00%1100.00%
Total78100.00%1100.00%

/* * Handle peripheral ep0 interrupt * * Context: irq handler; we won't re-enter the driver that way. */
irqreturn_t musb_g_ep0_irq(struct musb *musb) { u16 csr; u16 len; void __iomem *mbase = musb->mregs; void __iomem *regs = musb->endpoints[0].regs; irqreturn_t retval = IRQ_NONE; musb_ep_select(mbase, 0); /* select ep0 */ csr = musb_readw(regs, MUSB_CSR0); len = musb_readb(regs, MUSB_COUNT0); musb_dbg(musb, "csr %04x, count %d, ep0stage %s", csr, len, decode_ep0stage(musb->ep0_state)); if (csr & MUSB_CSR0_P_DATAEND) { /* * If DATAEND is set we should not call the callback, * hence the status stage is not complete. */ return IRQ_HANDLED; } /* I sent a stall.. need to acknowledge it now.. */ if (csr & MUSB_CSR0_P_SENTSTALL) { musb_writew(regs, MUSB_CSR0, csr & ~MUSB_CSR0_P_SENTSTALL); retval = IRQ_HANDLED; musb->ep0_state = MUSB_EP0_STAGE_IDLE; csr = musb_readw(regs, MUSB_CSR0); } /* request ended "early" */ if (csr & MUSB_CSR0_P_SETUPEND) { musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SVDSETUPEND); retval = IRQ_HANDLED; /* Transition into the early status phase */ switch (musb->ep0_state) { case MUSB_EP0_STAGE_TX: musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT; break; case MUSB_EP0_STAGE_RX: musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; break; default: ERR("SetupEnd came in a wrong ep0stage %s\n", decode_ep0stage(musb->ep0_state)); } csr = musb_readw(regs, MUSB_CSR0); /* NOTE: request may need completion */ } /* docs from Mentor only describe tx, rx, and idle/setup states. * we need to handle nuances around status stages, and also the * case where status and setup stages come back-to-back ... */ switch (musb->ep0_state) { case MUSB_EP0_STAGE_TX: /* irq on clearing txpktrdy */ if ((csr & MUSB_CSR0_TXPKTRDY) == 0) { ep0_txstate(musb); retval = IRQ_HANDLED; } break; case MUSB_EP0_STAGE_RX: /* irq on set rxpktrdy */ if (csr & MUSB_CSR0_RXPKTRDY) { ep0_rxstate(musb); retval = IRQ_HANDLED; } break; case MUSB_EP0_STAGE_STATUSIN: /* end of sequence #2 (OUT/RX state) or #3 (no data) */ /* update address (if needed) only @ the end of the * status phase per usb spec, which also guarantees * we get 10 msec to receive this irq... until this * is done we won't see the next packet. */ if (musb->set_address) { musb->set_address = false; musb_writeb(mbase, MUSB_FADDR, musb->address); } /* enter test mode if needed (exit by reset) */ else if (musb->test_mode) { musb_dbg(musb, "entering TESTMODE"); if (MUSB_TEST_PACKET == musb->test_mode_nr) musb_load_testpacket(musb); musb_writeb(mbase, MUSB_TESTMODE, musb->test_mode_nr); } /* FALLTHROUGH */ case MUSB_EP0_STAGE_STATUSOUT: /* end of sequence #1: write to host (TX state) */ { struct musb_request *req; req = next_ep0_request(musb); if (req) musb_g_ep0_giveback(musb, &req->request); } /* * In case when several interrupts can get coalesced, * check to see if we've already received a SETUP packet... */ if (csr & MUSB_CSR0_RXPKTRDY) goto setup; retval = IRQ_HANDLED; musb->ep0_state = MUSB_EP0_STAGE_IDLE; break; case MUSB_EP0_STAGE_IDLE: /* * This state is typically (but not always) indiscernible * from the status states since the corresponding interrupts * tend to happen within too little period of time (with only * a zero-length packet in between) and so get coalesced... */ retval = IRQ_HANDLED; musb->ep0_state = MUSB_EP0_STAGE_SETUP; /* FALLTHROUGH */ case MUSB_EP0_STAGE_SETUP: setup: if (csr & MUSB_CSR0_RXPKTRDY) { struct usb_ctrlrequest setup; int handled = 0; if (len != 8) { ERR("SETUP packet len %d != 8 ?\n", len); break; } musb_read_setup(musb, &setup); retval = IRQ_HANDLED; /* sometimes the RESET won't be reported */ if (unlikely(musb->g.speed == USB_SPEED_UNKNOWN)) { u8 power; printk(KERN_NOTICE "%s: peripheral reset " "irq lost!\n", musb_driver_name); power = musb_readb(mbase, MUSB_POWER); musb->g.speed = (power & MUSB_POWER_HSMODE) ? USB_SPEED_HIGH : USB_SPEED_FULL; } switch (musb->ep0_state) { /* sequence #3 (no data stage), includes requests * we can't forward (notably SET_ADDRESS and the * device/endpoint feature set/clear operations) * plus SET_CONFIGURATION and others we must */ case MUSB_EP0_STAGE_ACKWAIT: handled = service_zero_data_request( musb, &setup); /* * We're expecting no data in any case, so * always set the DATAEND bit -- doing this * here helps avoid SetupEnd interrupt coming * in the idle stage when we're stalling... */ musb->ackpend |= MUSB_CSR0_P_DATAEND; /* status stage might be immediate */ if (handled > 0) musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; break; /* sequence #1 (IN to host), includes GET_STATUS * requests that we can't forward, GET_DESCRIPTOR * and others that we must */ case MUSB_EP0_STAGE_TX: handled = service_in_request(musb, &setup); if (handled > 0) { musb->ackpend = MUSB_CSR0_TXPKTRDY | MUSB_CSR0_P_DATAEND; musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT; } break; /* sequence #2 (OUT from host), always forward */ default: /* MUSB_EP0_STAGE_RX */ break; } musb_dbg(musb, "handled %d, csr %04x, ep0stage %s", handled, csr, decode_ep0stage(musb->ep0_state)); /* unless we need to delegate this to the gadget * driver, we know how to wrap this up: csr0 has * not yet been written. */ if (handled < 0) goto stall; else if (handled > 0) goto finish; handled = forward_to_driver(musb, &setup); if (handled < 0) { musb_ep_select(mbase, 0); stall: musb_dbg(musb, "stall (%d)", handled); musb->ackpend |= MUSB_CSR0_P_SENDSTALL; musb->ep0_state = MUSB_EP0_STAGE_IDLE; finish: musb_writew(regs, MUSB_CSR0, musb->ackpend); musb->ackpend = 0; } } break; case MUSB_EP0_STAGE_ACKWAIT: /* This should not happen. But happens with tusb6010 with * g_file_storage and high speed. Do nothing. */ retval = IRQ_HANDLED; break; default: /* "can't happen" */ WARN_ON(1); musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SENDSTALL); musb->ep0_state = MUSB_EP0_STAGE_IDLE; break; } return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi64587.28%342.86%
Sergei Shtylyov7410.01%228.57%
Hans Petter Selasky121.62%114.29%
Bin Liu81.08%114.29%
Total739100.00%7100.00%


static int musb_g_ep0_enable(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) { /* always enabled */ return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi22100.00%1100.00%
Total22100.00%1100.00%


static int musb_g_ep0_disable(struct usb_ep *e) { /* always enabled */ return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi16100.00%1100.00%
Total16100.00%1100.00%


static int musb_g_ep0_queue(struct usb_ep *e, struct usb_request *r, gfp_t gfp_flags) { struct musb_ep *ep; struct musb_request *req; struct musb *musb; int status; unsigned long lockflags; void __iomem *regs; if (!e || !r) return -EINVAL; ep = to_musb_ep(e); musb = ep->musb; regs = musb->control_ep->regs; req = to_musb_request(r); req->musb = musb; req->request.actual = 0; req->request.status = -EINPROGRESS; req->tx = ep->is_in; spin_lock_irqsave(&musb->lock, lockflags); if (!list_empty(&ep->req_list)) { status = -EBUSY; goto cleanup; } switch (musb->ep0_state) { case MUSB_EP0_STAGE_RX: /* control-OUT data */ case MUSB_EP0_STAGE_TX: /* control-IN data */ case MUSB_EP0_STAGE_ACKWAIT: /* zero-length data */ status = 0; break; default: musb_dbg(musb, "ep0 request queued in state %d", musb->ep0_state); status = -EINVAL; goto cleanup; } /* add request to the list */ list_add_tail(&req->list, &ep->req_list); musb_dbg(musb, "queue to %s (%s), length=%d", ep->name, ep->is_in ? "IN/TX" : "OUT/RX", req->request.length); musb_ep_select(musb->mregs, 0); /* sequence #1, IN ... start writing the data */ if (musb->ep0_state == MUSB_EP0_STAGE_TX) ep0_txstate(musb); /* sequence #3, no-data ... issue IN status */ else if (musb->ep0_state == MUSB_EP0_STAGE_ACKWAIT) { if (req->request.length) status = -EINVAL; else { musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; musb_writew(regs, MUSB_CSR0, musb->ackpend | MUSB_CSR0_P_DATAEND); musb->ackpend = 0; musb_g_ep0_giveback(ep->musb, r); } /* else for sequence #2 (OUT), caller provides a buffer * before the next packet arrives. deferred responses * (after SETUP is acked) are racey. */ } else if (musb->ackpend) { musb_writew(regs, MUSB_CSR0, musb->ackpend); musb->ackpend = 0; } cleanup: spin_unlock_irqrestore(&musb->lock, lockflags); return status; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi35598.89%266.67%
Bin Liu41.11%133.33%
Total359100.00%3100.00%


static int musb_g_ep0_dequeue(struct usb_ep *ep, struct usb_request *req) { /* we just won't support this */ return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi21100.00%1100.00%
Total21100.00%1100.00%


static int musb_g_ep0_halt(struct usb_ep *e, int value) { struct musb_ep *ep; struct musb *musb; void __iomem *base, *regs; unsigned long flags; int status; u16 csr; if (!e || !value) return -EINVAL; ep = to_musb_ep(e); musb = ep->musb; base = musb->mregs; regs = musb->control_ep->regs; status = 0; spin_lock_irqsave(&musb->lock, flags); if (!list_empty(&ep->req_list)) { status = -EBUSY; goto cleanup; } musb_ep_select(base, 0); csr = musb->ackpend; switch (musb->ep0_state) { /* Stalls are usually issued after parsing SETUP packet, either * directly in irq context from setup() or else later. */ case MUSB_EP0_STAGE_TX: /* control-IN data */ case MUSB_EP0_STAGE_ACKWAIT: /* STALL for zero-length data */ case MUSB_EP0_STAGE_RX: /* control-OUT data */ csr = musb_readw(regs, MUSB_CSR0); /* FALLTHROUGH */ /* It's also OK to issue stalls during callbacks when a non-empty * DATA stage buffer has been read (or even written). */ case MUSB_EP0_STAGE_STATUSIN: /* control-OUT status */ case MUSB_EP0_STAGE_STATUSOUT: /* control-IN status */ csr |= MUSB_CSR0_P_SENDSTALL; musb_writew(regs, MUSB_CSR0, csr); musb->ep0_state = MUSB_EP0_STAGE_IDLE; musb->ackpend = 0; break; default: musb_dbg(musb, "ep0 can't halt in state %d", musb->ep0_state); status = -EINVAL; } cleanup: spin_unlock_irqrestore(&musb->lock, flags); return status; }

Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi22498.68%250.00%
Bin Liu20.88%125.00%
Sergei Shtylyov10.44%125.00%
Total227100.00%4100.00%

const struct usb_ep_ops musb_g_ep0_ops = { .enable = musb_g_ep0_enable, .disable = musb_g_ep0_disable, .alloc_request = musb_alloc_request, .free_request = musb_free_request, .queue = musb_g_ep0_queue, .dequeue = musb_g_ep0_dequeue, .set_halt = musb_g_ep0_halt, };

Overall Contributors

PersonTokensPropCommitsCommitProp
Felipe Balbi339190.94%422.22%
Sergei Shtylyov2466.60%633.33%
Bin Liu220.59%15.56%
Anand Gadiyar180.48%15.56%
Bryan Wu140.38%15.56%
Hans Petter Selasky120.32%15.56%
Ravi Babu80.21%15.56%
stephane duverger80.21%15.56%
Daniel Glöckner70.19%15.56%
Peter Chen30.08%15.56%
Total3729100.00%18100.00%
Directory: drivers/usb/musb
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.