123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
- *
- * ep0.c - Endpoint 0 handling
- *
- * Copyright 2017 IBM Corporation
- */
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/delay.h>
- #include <linux/ioport.h>
- #include <linux/slab.h>
- #include <linux/errno.h>
- #include <linux/list.h>
- #include <linux/interrupt.h>
- #include <linux/proc_fs.h>
- #include <linux/prefetch.h>
- #include <linux/clk.h>
- #include <linux/usb/gadget.h>
- #include <linux/of.h>
- #include <linux/of_gpio.h>
- #include <linux/regmap.h>
- #include <linux/dma-mapping.h>
- #include "vhub.h"
- int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len)
- {
- struct usb_request *req = &ep->ep0.req.req;
- int rc;
- if (WARN_ON(ep->d_idx != 0))
- return std_req_stall;
- if (WARN_ON(!ep->ep0.dir_in))
- return std_req_stall;
- if (WARN_ON(len > AST_VHUB_EP0_MAX_PACKET))
- return std_req_stall;
- if (WARN_ON(req->status == -EINPROGRESS))
- return std_req_stall;
- req->buf = ptr;
- req->length = len;
- req->complete = NULL;
- req->zero = true;
- /*
- * Call internal queue directly after dropping the lock. This is
- * safe to do as the reply is always the last thing done when
- * processing a SETUP packet, usually as a tail call
- */
- spin_unlock(&ep->vhub->lock);
- if (ep->ep.ops->queue(&ep->ep, req, GFP_ATOMIC))
- rc = std_req_stall;
- else
- rc = std_req_data;
- spin_lock(&ep->vhub->lock);
- return rc;
- }
- int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...)
- {
- u8 *buffer = ep->buf;
- unsigned int i;
- va_list args;
- va_start(args, len);
- /* Copy data directly into EP buffer */
- for (i = 0; i < len; i++)
- buffer[i] = va_arg(args, int);
- va_end(args);
- /* req->buf NULL means data is already there */
- return ast_vhub_reply(ep, NULL, len);
- }
- void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep)
- {
- struct usb_ctrlrequest crq;
- enum std_req_rc std_req_rc;
- int rc = -ENODEV;
- if (WARN_ON(ep->d_idx != 0))
- return;
- /*
- * Grab the setup packet from the chip and byteswap
- * interesting fields
- */
- memcpy_fromio(&crq, ep->ep0.setup, sizeof(crq));
- EPDBG(ep, "SETUP packet %02x/%02x/%04x/%04x/%04x [%s] st=%d\n",
- crq.bRequestType, crq.bRequest,
- le16_to_cpu(crq.wValue),
- le16_to_cpu(crq.wIndex),
- le16_to_cpu(crq.wLength),
- (crq.bRequestType & USB_DIR_IN) ? "in" : "out",
- ep->ep0.state);
- /*
- * Check our state, cancel pending requests if needed
- *
- * Note: Under some circumstances, we can get a new setup
- * packet while waiting for the stall ack, just accept it.
- *
- * In any case, a SETUP packet in wrong state should have
- * reset the HW state machine, so let's just log, nuke
- * requests, move on.
- */
- if (ep->ep0.state != ep0_state_token &&
- ep->ep0.state != ep0_state_stall) {
- EPDBG(ep, "wrong state\n");
- ast_vhub_nuke(ep, -EIO);
- }
- /* Calculate next state for EP0 */
- ep->ep0.state = ep0_state_data;
- ep->ep0.dir_in = !!(crq.bRequestType & USB_DIR_IN);
- /* If this is the vHub, we handle requests differently */
- std_req_rc = std_req_driver;
- if (ep->dev == NULL) {
- if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
- std_req_rc = ast_vhub_std_hub_request(ep, &crq);
- else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS)
- std_req_rc = ast_vhub_class_hub_request(ep, &crq);
- else
- std_req_rc = std_req_stall;
- } else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
- std_req_rc = ast_vhub_std_dev_request(ep, &crq);
- /* Act upon result */
- switch(std_req_rc) {
- case std_req_complete:
- goto complete;
- case std_req_stall:
- goto stall;
- case std_req_driver:
- break;
- case std_req_data:
- return;
- }
- /* Pass request up to the gadget driver */
- if (WARN_ON(!ep->dev))
- goto stall;
- if (ep->dev->driver) {
- EPDBG(ep, "forwarding to gadget...\n");
- spin_unlock(&ep->vhub->lock);
- rc = ep->dev->driver->setup(&ep->dev->gadget, &crq);
- spin_lock(&ep->vhub->lock);
- EPDBG(ep, "driver returned %d\n", rc);
- } else {
- EPDBG(ep, "no gadget for request !\n");
- }
- if (rc >= 0)
- return;
- stall:
- EPDBG(ep, "stalling\n");
- writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat);
- ep->ep0.state = ep0_state_stall;
- ep->ep0.dir_in = false;
- return;
- complete:
- EPVDBG(ep, "sending [in] status with no data\n");
- writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
- ep->ep0.state = ep0_state_status;
- ep->ep0.dir_in = false;
- }
- static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep,
- struct ast_vhub_req *req)
- {
- unsigned int chunk;
- u32 reg;
- /* If this is a 0-length request, it's the gadget trying to
- * send a status on our behalf. We take it from here.
- */
- if (req->req.length == 0)
- req->last_desc = 1;
- /* Are we done ? Complete request, otherwise wait for next interrupt */
- if (req->last_desc >= 0) {
- EPVDBG(ep, "complete send %d/%d\n",
- req->req.actual, req->req.length);
- ep->ep0.state = ep0_state_status;
- writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat);
- ast_vhub_done(ep, req, 0);
- return;
- }
- /*
- * Next chunk cropped to max packet size. Also check if this
- * is the last packet
- */
- chunk = req->req.length - req->req.actual;
- if (chunk > ep->ep.maxpacket)
- chunk = ep->ep.maxpacket;
- else if ((chunk < ep->ep.maxpacket) || !req->req.zero)
- req->last_desc = 1;
- EPVDBG(ep, "send chunk=%d last=%d, req->act=%d mp=%d\n",
- chunk, req->last_desc, req->req.actual, ep->ep.maxpacket);
- /*
- * Copy data if any (internal requests already have data
- * in the EP buffer)
- */
- if (chunk && req->req.buf)
- memcpy(ep->buf, req->req.buf + req->req.actual, chunk);
- vhub_dma_workaround(ep->buf);
- /* Remember chunk size and trigger send */
- reg = VHUB_EP0_SET_TX_LEN(chunk);
- writel(reg, ep->ep0.ctlstat);
- writel(reg | VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
- req->req.actual += chunk;
- }
- static void ast_vhub_ep0_rx_prime(struct ast_vhub_ep *ep)
- {
- EPVDBG(ep, "rx prime\n");
- /* Prime endpoint for receiving data */
- writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat);
- }
- static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
- unsigned int len)
- {
- unsigned int remain;
- int rc = 0;
- /* We are receiving... grab request */
- remain = req->req.length - req->req.actual;
- EPVDBG(ep, "receive got=%d remain=%d\n", len, remain);
- /* Are we getting more than asked ? */
- if (len > remain) {
- EPDBG(ep, "receiving too much (ovf: %d) !\n",
- len - remain);
- len = remain;
- rc = -EOVERFLOW;
- }
- /* Hardware return wrong data len */
- if (len < ep->ep.maxpacket && len != remain) {
- EPDBG(ep, "using expected data len instead\n");
- len = remain;
- }
- if (len && req->req.buf)
- memcpy(req->req.buf + req->req.actual, ep->buf, len);
- req->req.actual += len;
- /* Done ? */
- if (len < ep->ep.maxpacket || len == remain) {
- ep->ep0.state = ep0_state_status;
- writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
- ast_vhub_done(ep, req, rc);
- } else
- ast_vhub_ep0_rx_prime(ep);
- }
- void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack)
- {
- struct ast_vhub_req *req;
- struct ast_vhub *vhub = ep->vhub;
- struct device *dev = &vhub->pdev->dev;
- bool stall = false;
- u32 stat;
- /* Read EP0 status */
- stat = readl(ep->ep0.ctlstat);
- /* Grab current request if any */
- req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue);
- EPVDBG(ep, "ACK status=%08x,state=%d is_in=%d in_ack=%d req=%p\n",
- stat, ep->ep0.state, ep->ep0.dir_in, in_ack, req);
- switch(ep->ep0.state) {
- case ep0_state_token:
- /* There should be no request queued in that state... */
- if (req) {
- dev_warn(dev, "request present while in TOKEN state\n");
- ast_vhub_nuke(ep, -EINVAL);
- }
- dev_warn(dev, "ack while in TOKEN state\n");
- stall = true;
- break;
- case ep0_state_data:
- /* Check the state bits corresponding to our direction */
- if ((ep->ep0.dir_in && (stat & VHUB_EP0_TX_BUFF_RDY)) ||
- (!ep->ep0.dir_in && (stat & VHUB_EP0_RX_BUFF_RDY)) ||
- (ep->ep0.dir_in != in_ack)) {
- /* In that case, ignore interrupt */
- dev_warn(dev, "irq state mismatch");
- break;
- }
- /*
- * We are in data phase and there's no request, something is
- * wrong, stall
- */
- if (!req) {
- dev_warn(dev, "data phase, no request\n");
- stall = true;
- break;
- }
- /* We have a request, handle data transfers */
- if (ep->ep0.dir_in)
- ast_vhub_ep0_do_send(ep, req);
- else
- ast_vhub_ep0_do_receive(ep, req, VHUB_EP0_RX_LEN(stat));
- return;
- case ep0_state_status:
- /* Nuke stale requests */
- if (req) {
- dev_warn(dev, "request present while in STATUS state\n");
- ast_vhub_nuke(ep, -EINVAL);
- }
- /*
- * If the status phase completes with the wrong ack, stall
- * the endpoint just in case, to abort whatever the host
- * was doing.
- */
- if (ep->ep0.dir_in == in_ack) {
- dev_warn(dev, "status direction mismatch\n");
- stall = true;
- }
- break;
- case ep0_state_stall:
- /*
- * There shouldn't be any request left, but nuke just in case
- * otherwise the stale request will block subsequent ones
- */
- ast_vhub_nuke(ep, -EIO);
- break;
- }
- /* Reset to token state or stall */
- if (stall) {
- writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat);
- ep->ep0.state = ep0_state_stall;
- } else
- ep->ep0.state = ep0_state_token;
- }
- static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req,
- gfp_t gfp_flags)
- {
- struct ast_vhub_req *req = to_ast_req(u_req);
- struct ast_vhub_ep *ep = to_ast_ep(u_ep);
- struct ast_vhub *vhub = ep->vhub;
- struct device *dev = &vhub->pdev->dev;
- unsigned long flags;
- /* Paranoid cheks */
- if (!u_req || (!u_req->complete && !req->internal)) {
- dev_warn(dev, "Bogus EP0 request ! u_req=%p\n", u_req);
- if (u_req) {
- dev_warn(dev, "complete=%p internal=%d\n",
- u_req->complete, req->internal);
- }
- return -EINVAL;
- }
- /* Not endpoint 0 ? */
- if (WARN_ON(ep->d_idx != 0))
- return -EINVAL;
- /* Disabled device */
- if (ep->dev && !ep->dev->enabled)
- return -ESHUTDOWN;
- /* Data, no buffer and not internal ? */
- if (u_req->length && !u_req->buf && !req->internal) {
- dev_warn(dev, "Request with no buffer !\n");
- return -EINVAL;
- }
- EPVDBG(ep, "enqueue req @%p\n", req);
- EPVDBG(ep, " l=%d zero=%d noshort=%d is_in=%d\n",
- u_req->length, u_req->zero,
- u_req->short_not_ok, ep->ep0.dir_in);
- /* Initialize request progress fields */
- u_req->status = -EINPROGRESS;
- u_req->actual = 0;
- req->last_desc = -1;
- req->active = false;
- spin_lock_irqsave(&vhub->lock, flags);
- /* EP0 can only support a single request at a time */
- if (!list_empty(&ep->queue) ||
- ep->ep0.state == ep0_state_token ||
- ep->ep0.state == ep0_state_stall) {
- dev_warn(dev, "EP0: Request in wrong state\n");
- EPVDBG(ep, "EP0: list_empty=%d state=%d\n",
- list_empty(&ep->queue), ep->ep0.state);
- spin_unlock_irqrestore(&vhub->lock, flags);
- return -EBUSY;
- }
- /* Add request to list and kick processing if empty */
- list_add_tail(&req->queue, &ep->queue);
- if (ep->ep0.dir_in) {
- /* IN request, send data */
- ast_vhub_ep0_do_send(ep, req);
- } else if (u_req->length == 0) {
- /* 0-len request, send completion as rx */
- EPVDBG(ep, "0-length rx completion\n");
- ep->ep0.state = ep0_state_status;
- writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
- ast_vhub_done(ep, req, 0);
- } else {
- /* OUT request, start receiver */
- ast_vhub_ep0_rx_prime(ep);
- }
- spin_unlock_irqrestore(&vhub->lock, flags);
- return 0;
- }
- static int ast_vhub_ep0_dequeue(struct usb_ep* u_ep, struct usb_request *u_req)
- {
- struct ast_vhub_ep *ep = to_ast_ep(u_ep);
- struct ast_vhub *vhub = ep->vhub;
- struct ast_vhub_req *req;
- unsigned long flags;
- int rc = -EINVAL;
- spin_lock_irqsave(&vhub->lock, flags);
- /* Only one request can be in the queue */
- req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue);
- /* Is it ours ? */
- if (req && u_req == &req->req) {
- EPVDBG(ep, "dequeue req @%p\n", req);
- /*
- * We don't have to deal with "active" as all
- * DMAs go to the EP buffers, not the request.
- */
- ast_vhub_done(ep, req, -ECONNRESET);
- /* We do stall the EP to clean things up in HW */
- writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat);
- ep->ep0.state = ep0_state_status;
- ep->ep0.dir_in = false;
- rc = 0;
- }
- spin_unlock_irqrestore(&vhub->lock, flags);
- return rc;
- }
- static const struct usb_ep_ops ast_vhub_ep0_ops = {
- .queue = ast_vhub_ep0_queue,
- .dequeue = ast_vhub_ep0_dequeue,
- .alloc_request = ast_vhub_alloc_request,
- .free_request = ast_vhub_free_request,
- };
- void ast_vhub_reset_ep0(struct ast_vhub_dev *dev)
- {
- struct ast_vhub_ep *ep = &dev->ep0;
- ast_vhub_nuke(ep, -EIO);
- ep->ep0.state = ep0_state_token;
- }
- void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep,
- struct ast_vhub_dev *dev)
- {
- memset(ep, 0, sizeof(*ep));
- INIT_LIST_HEAD(&ep->ep.ep_list);
- INIT_LIST_HEAD(&ep->queue);
- ep->ep.ops = &ast_vhub_ep0_ops;
- ep->ep.name = "ep0";
- ep->ep.caps.type_control = true;
- usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EP0_MAX_PACKET);
- ep->d_idx = 0;
- ep->dev = dev;
- ep->vhub = vhub;
- ep->ep0.state = ep0_state_token;
- INIT_LIST_HEAD(&ep->ep0.req.queue);
- ep->ep0.req.internal = true;
- /* Small difference between vHub and devices */
- if (dev) {
- ep->ep0.ctlstat = dev->regs + AST_VHUB_DEV_EP0_CTRL;
- ep->ep0.setup = vhub->regs +
- AST_VHUB_SETUP0 + 8 * (dev->index + 1);
- ep->buf = vhub->ep0_bufs +
- AST_VHUB_EP0_MAX_PACKET * (dev->index + 1);
- ep->buf_dma = vhub->ep0_bufs_dma +
- AST_VHUB_EP0_MAX_PACKET * (dev->index + 1);
- } else {
- ep->ep0.ctlstat = vhub->regs + AST_VHUB_EP0_CTRL;
- ep->ep0.setup = vhub->regs + AST_VHUB_SETUP0;
- ep->buf = vhub->ep0_bufs;
- ep->buf_dma = vhub->ep0_bufs_dma;
- }
- }
|