// SPDX-License-Identifier: GPL-2.0+ /* * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget * * ep0.c - Endpoint 0 handling * * Copyright 2017 IBM Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 */ if (ep->ep0.state != ep0_state_token) { EPDBG(ep, "wrong state\n"); ast_vhub_nuke(ep, -EIO); /* * Accept the packet regardless, this seems to happen * when stalling a SETUP packet that has an OUT data * phase. */ ast_vhub_nuke(ep, 0); goto stall; } /* 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_status; 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; } 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)) { dev_warn(dev, "irq state mismatch"); stall = true; 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; } } /* Reset to token state */ ep->ep0.state = ep0_state_token; if (stall) writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); } 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 || ep->dev->suspended)) 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) { dev_warn(dev, "EP0: Request in wrong state\n"); 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_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; } }