root/drivers/usb/gadget/udc/aspeed-vhub/ep0.c
// 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/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;
        }
}