/* $OpenBSD: dwc2.c,v 1.48 2019/03/14 04:28:10 visa Exp $ */ /* $NetBSD: dwc2.c,v 1.32 2014/09/02 23:26:20 macallan Exp $ */ /*- * Copyright (c) 2013 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Nick Hudson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION OR CONTRIBUTORS * 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. */ #if 0 #include "opt_usb.h" #endif #include #include #include #include #include #include #include #include #include #if 0 #include #endif #include #include #include #include #include #if 0 #include #endif #include #include #include #include #ifdef DWC2_COUNTERS #define DWC2_EVCNT_ADD(a,b) ((void)((a).ev_count += (b))) #else #define DWC2_EVCNT_ADD(a,b) do { } while (/*CONSTCOND*/0) #endif #define DWC2_EVCNT_INCR(a) DWC2_EVCNT_ADD((a), 1) #ifdef DWC2_DEBUG #define DPRINTFN(n,fmt,...) do { \ if (dwc2debug >= (n)) { \ printf("%s: " fmt, \ __FUNCTION__,## __VA_ARGS__); \ } \ } while (0) #define DPRINTF(...) DPRINTFN(1, __VA_ARGS__) int dwc2debug = 0; #else #define DPRINTF(...) do { } while (0) #define DPRINTFN(...) do { } while (0) #endif STATIC usbd_status dwc2_open(struct usbd_pipe *); STATIC int dwc2_setaddr(struct usbd_device *, int); STATIC void dwc2_poll(struct usbd_bus *); STATIC void dwc2_softintr(void *); #if 0 STATIC usbd_status dwc2_allocm(struct usbd_bus *, struct usb_dma *, uint32_t); STATIC void dwc2_freem(struct usbd_bus *, struct usb_dma *); #endif STATIC struct usbd_xfer *dwc2_allocx(struct usbd_bus *); STATIC void dwc2_freex(struct usbd_bus *, struct usbd_xfer *); #if 0 STATIC void dwc2_get_lock(struct usbd_bus *, struct mutex **); #endif STATIC usbd_status dwc2_root_ctrl_transfer(struct usbd_xfer *); STATIC usbd_status dwc2_root_ctrl_start(struct usbd_xfer *); STATIC void dwc2_root_ctrl_abort(struct usbd_xfer *); STATIC void dwc2_root_ctrl_close(struct usbd_pipe *); STATIC void dwc2_root_ctrl_done(struct usbd_xfer *); STATIC usbd_status dwc2_root_intr_transfer(struct usbd_xfer *); STATIC usbd_status dwc2_root_intr_start(struct usbd_xfer *); STATIC void dwc2_root_intr_abort(struct usbd_xfer *); STATIC void dwc2_root_intr_close(struct usbd_pipe *); STATIC void dwc2_root_intr_done(struct usbd_xfer *); STATIC usbd_status dwc2_device_ctrl_transfer(struct usbd_xfer *); STATIC usbd_status dwc2_device_ctrl_start(struct usbd_xfer *); STATIC void dwc2_device_ctrl_abort(struct usbd_xfer *); STATIC void dwc2_device_ctrl_close(struct usbd_pipe *); STATIC void dwc2_device_ctrl_done(struct usbd_xfer *); STATIC usbd_status dwc2_device_bulk_transfer(struct usbd_xfer *); STATIC usbd_status dwc2_device_bulk_start(struct usbd_xfer *); STATIC void dwc2_device_bulk_abort(struct usbd_xfer *); STATIC void dwc2_device_bulk_close(struct usbd_pipe *); STATIC void dwc2_device_bulk_done(struct usbd_xfer *); STATIC usbd_status dwc2_device_intr_transfer(struct usbd_xfer *); STATIC usbd_status dwc2_device_intr_start(struct usbd_xfer *); STATIC void dwc2_device_intr_abort(struct usbd_xfer *); STATIC void dwc2_device_intr_close(struct usbd_pipe *); STATIC void dwc2_device_intr_done(struct usbd_xfer *); STATIC usbd_status dwc2_device_isoc_transfer(struct usbd_xfer *); STATIC usbd_status dwc2_device_isoc_start(struct usbd_xfer *); STATIC void dwc2_device_isoc_abort(struct usbd_xfer *); STATIC void dwc2_device_isoc_close(struct usbd_pipe *); STATIC void dwc2_device_isoc_done(struct usbd_xfer *); STATIC usbd_status dwc2_device_start(struct usbd_xfer *); STATIC void dwc2_close_pipe(struct usbd_pipe *); STATIC void dwc2_abort_xfer(struct usbd_xfer *, usbd_status); STATIC void dwc2_device_clear_toggle(struct usbd_pipe *); STATIC void dwc2_noop(struct usbd_pipe *pipe); STATIC int dwc2_interrupt(struct dwc2_softc *); STATIC void dwc2_rhc(void *); STATIC void dwc2_timeout(void *); STATIC void dwc2_timeout_task(void *); STATIC_INLINE void dwc2_allocate_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, struct usbd_xfer *xfer) { } STATIC_INLINE void dwc2_free_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, struct usbd_xfer *xfer) { } #define DWC2_INTR_ENDPT 1 STATIC struct usbd_bus_methods dwc2_bus_methods = { .open_pipe = dwc2_open, .dev_setaddr = dwc2_setaddr, .soft_intr = dwc2_softintr, .do_poll = dwc2_poll, #if 0 .allocm = dwc2_allocm, .freem = dwc2_freem, #endif .allocx = dwc2_allocx, .freex = dwc2_freex, #if 0 .get_lock = dwc2_get_lock, #endif }; STATIC struct usbd_pipe_methods dwc2_root_ctrl_methods = { .transfer = dwc2_root_ctrl_transfer, .start = dwc2_root_ctrl_start, .abort = dwc2_root_ctrl_abort, .close = dwc2_root_ctrl_close, .cleartoggle = dwc2_noop, .done = dwc2_root_ctrl_done, }; STATIC struct usbd_pipe_methods dwc2_root_intr_methods = { .transfer = dwc2_root_intr_transfer, .start = dwc2_root_intr_start, .abort = dwc2_root_intr_abort, .close = dwc2_root_intr_close, .cleartoggle = dwc2_noop, .done = dwc2_root_intr_done, }; STATIC struct usbd_pipe_methods dwc2_device_ctrl_methods = { .transfer = dwc2_device_ctrl_transfer, .start = dwc2_device_ctrl_start, .abort = dwc2_device_ctrl_abort, .close = dwc2_device_ctrl_close, .cleartoggle = dwc2_noop, .done = dwc2_device_ctrl_done, }; STATIC struct usbd_pipe_methods dwc2_device_intr_methods = { .transfer = dwc2_device_intr_transfer, .start = dwc2_device_intr_start, .abort = dwc2_device_intr_abort, .close = dwc2_device_intr_close, .cleartoggle = dwc2_device_clear_toggle, .done = dwc2_device_intr_done, }; STATIC struct usbd_pipe_methods dwc2_device_bulk_methods = { .transfer = dwc2_device_bulk_transfer, .start = dwc2_device_bulk_start, .abort = dwc2_device_bulk_abort, .close = dwc2_device_bulk_close, .cleartoggle = dwc2_device_clear_toggle, .done = dwc2_device_bulk_done, }; STATIC struct usbd_pipe_methods dwc2_device_isoc_methods = { .transfer = dwc2_device_isoc_transfer, .start = dwc2_device_isoc_start, .abort = dwc2_device_isoc_abort, .close = dwc2_device_isoc_close, .cleartoggle = dwc2_noop, .done = dwc2_device_isoc_done, }; #if 0 STATIC usbd_status dwc2_allocm(struct usbd_bus *bus, struct usb_dma *dma, uint32_t size) { struct dwc2_softc *sc = DWC2_BUS2SC(bus); usbd_status status; DPRINTFN(10, "\n"); status = usb_allocmem(&sc->sc_bus, size, 0, dma); if (status == USBD_NOMEM) status = usb_reserve_allocm(&sc->sc_dma_reserve, dma, size); return status; } STATIC void dwc2_freem(struct usbd_bus *bus, struct usb_dma *dma) { struct dwc2_softc *sc = DWC2_BUS2SC(bus); DPRINTFN(10, "\n"); if (dma->block->flags & USB_DMA_RESERVE) { usb_reserve_freem(&sc->sc_dma_reserve, dma); return; } usb_freemem(&sc->sc_bus, dma); } #endif /* * Work around the half configured control (default) pipe when setting * the address of a device. */ STATIC int dwc2_setaddr(struct usbd_device *dev, int addr) { if (usbd_set_address(dev, addr)) return (1); dev->address = addr; /* * Re-establish the default pipe with the new address and the * new max packet size. */ dwc2_close_pipe(dev->default_pipe); if (dwc2_open(dev->default_pipe)) return (EINVAL); return (0); } struct usbd_xfer * dwc2_allocx(struct usbd_bus *bus) { struct dwc2_softc *sc = DWC2_BUS2SC(bus); struct dwc2_xfer *dxfer; DPRINTFN(10, "\n"); DWC2_EVCNT_INCR(sc->sc_ev_xferpoolget); dxfer = pool_get(&sc->sc_xferpool, PR_NOWAIT); if (dxfer != NULL) { memset(dxfer, 0, sizeof(*dxfer)); dxfer->urb = dwc2_hcd_urb_alloc(sc->sc_hsotg, DWC2_MAXISOCPACKETS, GFP_ATOMIC); if (dxfer->urb == NULL) { pool_put(&sc->sc_xferpool, dxfer); return NULL; } #ifdef DWC2_DEBUG dxfer->xfer.busy_free = XFER_ONQU; #endif } return (struct usbd_xfer *)dxfer; } void dwc2_freex(struct usbd_bus *bus, struct usbd_xfer *xfer) { struct dwc2_xfer *dxfer = DWC2_XFER2DXFER(xfer); struct dwc2_softc *sc = DWC2_BUS2SC(bus); DPRINTFN(10, "\n"); #ifdef DWC2_DEBUG if (xfer->busy_free != XFER_ONQU) { DPRINTF("xfer=%p not busy, 0x%08x\n", xfer, xfer->busy_free); } xfer->busy_free = XFER_FREE; #endif DWC2_EVCNT_INCR(sc->sc_ev_xferpoolput); dwc2_hcd_urb_free(sc->sc_hsotg, dxfer->urb, DWC2_MAXISOCPACKETS); pool_put(&sc->sc_xferpool, xfer); } #if 0 STATIC void dwc2_get_lock(struct usbd_bus *bus, struct mutex **lock) { struct dwc2_softc *sc = DWC2_BUS2SC(bus); *lock = &sc->sc_lock; } #endif STATIC void dwc2_rhc(void *addr) { struct dwc2_softc *sc = addr; struct usbd_xfer *xfer; u_char *p; DPRINTF("\n"); xfer = sc->sc_intrxfer; if (xfer == NULL) { /* Just ignore the change. */ return; } /* set port bit */ p = KERNADDR(&xfer->dmabuf, 0); p[0] = 0x02; /* we only have one port (1 << 1) */ xfer->actlen = xfer->length; xfer->status = USBD_NORMAL_COMPLETION; usb_transfer_complete(xfer); } STATIC void dwc2_softintr(void *v) { struct usbd_bus *bus = v; struct dwc2_softc *sc = DWC2_BUS2SC(bus); struct dwc2_hsotg *hsotg = sc->sc_hsotg; struct dwc2_xfer *dxfer; mtx_enter(&hsotg->lock); while ((dxfer = TAILQ_FIRST(&sc->sc_complete)) != NULL) { KASSERTMSG(!timeout_pending(&dxfer->xfer.timeout_handle), "xfer %p pipe %p\n", dxfer, dxfer->xfer.pipe); /* * dwc2_abort_xfer will remove this transfer from the * sc_complete queue */ /*XXXNH not tested */ if (dxfer->flags & DWC2_XFER_ABORTING) { wakeup(&dxfer->flags); continue; } TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext); mtx_leave(&hsotg->lock); usb_transfer_complete(&dxfer->xfer); mtx_enter(&hsotg->lock); } mtx_leave(&hsotg->lock); } STATIC void dwc2_timeout(void *addr) { struct usbd_xfer *xfer = addr; struct dwc2_softc *sc = DWC2_XFER2SC(xfer); DPRINTF("xfer=%p\n", xfer); if (sc->sc_dying) { dwc2_timeout_task(addr); return; } /* Execute the abort in a process context. */ usb_init_task(&xfer->abort_task, dwc2_timeout_task, addr, USB_TASK_TYPE_ABORT); usb_add_task(xfer->device, &xfer->abort_task); } STATIC void dwc2_timeout_task(void *addr) { struct usbd_xfer *xfer = addr; int s; DPRINTF("xfer=%p\n", xfer); s = splusb(); dwc2_abort_xfer(xfer, USBD_TIMEOUT); splx(s); } usbd_status dwc2_open(struct usbd_pipe *pipe) { struct usbd_device *dev = pipe->device; struct dwc2_softc *sc = DWC2_PIPE2SC(pipe); struct dwc2_pipe *dpipe = DWC2_PIPE2DPIPE(pipe); usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; uint8_t addr = dev->address; uint8_t xfertype = UE_GET_XFERTYPE(ed->bmAttributes); usbd_status err; DPRINTF("pipe %p addr %d xfertype %d dir %s\n", pipe, addr, xfertype, UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in" : "out"); if (sc->sc_dying) { return USBD_IOERROR; } if (addr == sc->sc_addr) { switch (ed->bEndpointAddress) { case USB_CONTROL_ENDPOINT: pipe->methods = &dwc2_root_ctrl_methods; break; case UE_DIR_IN | DWC2_INTR_ENDPT: pipe->methods = &dwc2_root_intr_methods; break; default: DPRINTF("bad bEndpointAddress 0x%02x\n", ed->bEndpointAddress); return USBD_INVAL; } DPRINTF("root hub pipe open\n"); return USBD_NORMAL_COMPLETION; } switch (xfertype) { case UE_CONTROL: pipe->methods = &dwc2_device_ctrl_methods; err = usb_allocmem(&sc->sc_bus, sizeof(usb_device_request_t), 0, &dpipe->req_dma); if (err) return err; break; case UE_INTERRUPT: pipe->methods = &dwc2_device_intr_methods; break; case UE_ISOCHRONOUS: pipe->methods = &dwc2_device_isoc_methods; break; case UE_BULK: pipe->methods = &dwc2_device_bulk_methods; break; default: DPRINTF("bad xfer type %d\n", xfertype); return USBD_INVAL; } dpipe->priv = NULL; /* QH */ return USBD_NORMAL_COMPLETION; } STATIC void dwc2_poll(struct usbd_bus *bus) { struct dwc2_softc *sc = DWC2_BUS2SC(bus); struct dwc2_hsotg *hsotg = sc->sc_hsotg; mtx_enter(&hsotg->lock); dwc2_interrupt(sc); mtx_leave(&hsotg->lock); } /* * Close a reqular pipe. * Assumes that there are no pending transactions. */ STATIC void dwc2_close_pipe(struct usbd_pipe *pipe) { /* nothing */ } /* * Abort a device request. */ STATIC void dwc2_abort_xfer(struct usbd_xfer *xfer, usbd_status status) { struct dwc2_xfer *dxfer = DWC2_XFER2DXFER(xfer); struct dwc2_softc *sc = DWC2_XFER2SC(xfer); struct dwc2_hsotg *hsotg = sc->sc_hsotg; struct dwc2_xfer *d, *tmp; bool wake; int err; splsoftassert(IPL_SOFTUSB); DPRINTF("xfer=%p\n", xfer); if (sc->sc_dying) { xfer->status = status; timeout_del(&xfer->timeout_handle); usb_rem_task(xfer->device, &xfer->abort_task); usb_transfer_complete(xfer); return; } /* * If an abort is already in progress then just wait for it to * complete and return. */ if (dxfer->flags & DWC2_XFER_ABORTING) { xfer->status = status; dxfer->flags |= DWC2_XFER_ABORTWAIT; while (dxfer->flags & DWC2_XFER_ABORTING) tsleep(&dxfer->flags, PZERO, "dwc2xfer", 0); return; } mtx_enter(&hsotg->lock); /* The transfer might have been completed already. */ if (xfer->status != USBD_IN_PROGRESS) { DPRINTF("xfer=%p already completed\n", xfer); mtx_leave(&hsotg->lock); return; } /* * Step 1: Make the stack ignore it and stop the timeout. */ dxfer->flags |= DWC2_XFER_ABORTING; xfer->status = status; /* make software ignore it */ timeout_del(&xfer->timeout_handle); usb_rem_task(xfer->device, &xfer->abort_task); /* XXXNH suboptimal */ TAILQ_FOREACH_SAFE(d, &sc->sc_complete, xnext, tmp) { if (d == dxfer) { TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext); } } err = dwc2_hcd_urb_dequeue(hsotg, dxfer->urb); if (err) { DPRINTF("dwc2_hcd_urb_dequeue failed\n"); } mtx_leave(&hsotg->lock); /* * Step 2: Execute callback. */ wake = dxfer->flags & DWC2_XFER_ABORTWAIT; dxfer->flags &= ~(DWC2_XFER_ABORTING | DWC2_XFER_ABORTWAIT); usb_transfer_complete(xfer); if (wake) { wakeup(&dxfer->flags); } } STATIC void dwc2_noop(struct usbd_pipe *pipe) { } STATIC void dwc2_device_clear_toggle(struct usbd_pipe *pipe) { DPRINTF("toggle %d -> 0", pipe->endpoint->savedtoggle); } /***********************************************************************/ /* * Data structures and routines to emulate the root hub. */ STATIC const usb_device_descriptor_t dwc2_devd = { .bLength = sizeof(usb_device_descriptor_t), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_HSHUBSTT, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; struct dwc2_config_desc { usb_config_descriptor_t confd; usb_interface_descriptor_t ifcd; usb_endpoint_descriptor_t endpd; } __packed; STATIC const struct dwc2_config_desc dwc2_confd = { .confd = { .bLength = USB_CONFIG_DESCRIPTOR_SIZE, .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(dwc2_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_BUS_POWERED | UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = USB_INTERFACE_DESCRIPTOR_SIZE, .bDescriptorType = UDESC_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = UIPROTO_HSHUBSTT, .iInterface = 0 }, .endpd = { .bLength = USB_ENDPOINT_DESCRIPTOR_SIZE, .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | DWC2_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize = {8, 0}, /* max packet */ .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } #if 0 /* appears to be unused */ STATIC const usb_hub_descriptor_t dwc2_hubd = { .bDescLength = USB_HUB_DESCRIPTOR_SIZE, .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #endif STATIC usbd_status dwc2_root_ctrl_transfer(struct usbd_xfer *xfer) { usbd_status err; err = usb_insert_transfer(xfer); if (err) return err; return dwc2_root_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); } STATIC usbd_status dwc2_root_ctrl_start(struct usbd_xfer *xfer) { struct dwc2_softc *sc = DWC2_XFER2SC(xfer); usb_device_request_t *req; uint8_t *buf; uint16_t len; int value, index, l, s, totlen; usbd_status err = USBD_IOERROR; if (sc->sc_dying) return USBD_IOERROR; req = &xfer->request; DPRINTFN(4, "type=0x%02x request=%02x\n", req->bmRequestType, req->bRequest); len = UGETW(req->wLength); value = UGETW(req->wValue); index = UGETW(req->wIndex); buf = len ? KERNADDR(&xfer->dmabuf, 0) : NULL; totlen = 0; #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): if (len > 0) { *buf = sc->sc_conf; totlen = 1; } break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): DPRINTFN(8, "wValue=0x%04x\n", value); if (len == 0) break; switch (value) { case C(0, UDESC_DEVICE): l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); // USETW(dwc2_devd.idVendor, sc->sc_id_vendor); memcpy(buf, &dwc2_devd, l); buf += l; len -= l; totlen += l; break; case C(0, UDESC_CONFIG): l = min(len, sizeof(dwc2_confd)); memcpy(buf, &dwc2_confd, l); buf += l; len -= l; totlen += l; break; #define sd ((usb_string_descriptor_t *)buf) case C(0, UDESC_STRING): totlen = usbd_str(sd, len, "\001"); break; case C(1, UDESC_STRING): totlen = usbd_str(sd, len, sc->sc_vendor); break; case C(2, UDESC_STRING): totlen = usbd_str(sd, len, "DWC2 root hub"); break; #undef sd default: goto fail; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): if (len > 0) { *buf = 0; totlen = 1; } break; case C(UR_GET_STATUS, UT_READ_DEVICE): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); totlen = 2; } break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus, 0); totlen = 2; } break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): DPRINTF("UR_SET_ADDRESS, UT_WRITE_DEVICE: addr %d\n", value); if (value >= USB_MAX_DEVICES) goto fail; sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if (value != 0 && value != 1) goto fail; sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USBD_IOERROR; goto fail; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; default: /* Hub requests - XXXNH len check? */ err = dwc2_hcd_hub_control(sc->sc_hsotg, C(req->bRequest, req->bmRequestType), value, index, buf, len); if (err) { err = USBD_IOERROR; goto fail; } totlen = len; } xfer->actlen = totlen; err = USBD_NORMAL_COMPLETION; fail: s = splusb(); xfer->status = err; usb_transfer_complete(xfer); splx(s); return err; } STATIC void dwc2_root_ctrl_abort(struct usbd_xfer *xfer) { DPRINTFN(10, "\n"); /* Nothing to do, all transfers are synchronous. */ } STATIC void dwc2_root_ctrl_close(struct usbd_pipe *pipe) { DPRINTFN(10, "\n"); /* Nothing to do. */ } STATIC void dwc2_root_ctrl_done(struct usbd_xfer *xfer) { DPRINTFN(10, "\n"); /* Nothing to do. */ } STATIC usbd_status dwc2_root_intr_transfer(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("\n"); /* Insert last in queue. */ err = usb_insert_transfer(xfer); if (err) return err; /* Pipe isn't running, start first */ return dwc2_root_intr_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); } STATIC usbd_status dwc2_root_intr_start(struct usbd_xfer *xfer) { struct dwc2_softc *sc = DWC2_XFER2SC(xfer); DPRINTF("\n"); if (sc->sc_dying) return USBD_IOERROR; KASSERT(sc->sc_intrxfer == NULL); sc->sc_intrxfer = xfer; return USBD_IN_PROGRESS; } /* Abort a root interrupt request. */ STATIC void dwc2_root_intr_abort(struct usbd_xfer *xfer) { struct dwc2_softc *sc = DWC2_XFER2SC(xfer); DPRINTF("xfer=%p\n", xfer); KASSERT(xfer->pipe->intrxfer == xfer); sc->sc_intrxfer = NULL; xfer->status = USBD_CANCELLED; usb_transfer_complete(xfer); } STATIC void dwc2_root_intr_close(struct usbd_pipe *pipe) { struct dwc2_softc *sc = DWC2_PIPE2SC(pipe); DPRINTF("\n"); sc->sc_intrxfer = NULL; } STATIC void dwc2_root_intr_done(struct usbd_xfer *xfer) { DPRINTF("\n"); } /***********************************************************************/ STATIC usbd_status dwc2_device_ctrl_transfer(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("\n"); /* Insert last in queue. */ err = usb_insert_transfer(xfer); if (err) return err; /* Pipe isn't running, start first */ return dwc2_device_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); } STATIC usbd_status dwc2_device_ctrl_start(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("\n"); xfer->status = USBD_IN_PROGRESS; err = dwc2_device_start(xfer); return err; } STATIC void dwc2_device_ctrl_abort(struct usbd_xfer *xfer) { DPRINTF("xfer=%p\n", xfer); dwc2_abort_xfer(xfer, USBD_CANCELLED); } STATIC void dwc2_device_ctrl_close(struct usbd_pipe *pipe) { DPRINTF("pipe=%p\n", pipe); dwc2_close_pipe(pipe); } STATIC void dwc2_device_ctrl_done(struct usbd_xfer *xfer) { DPRINTF("xfer=%p\n", xfer); } /***********************************************************************/ STATIC usbd_status dwc2_device_bulk_transfer(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("xfer=%p\n", xfer); /* Insert last in queue. */ err = usb_insert_transfer(xfer); if (err) return err; /* Pipe isn't running, start first */ return dwc2_device_bulk_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); } STATIC usbd_status dwc2_device_bulk_start(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("xfer=%p\n", xfer); xfer->status = USBD_IN_PROGRESS; err = dwc2_device_start(xfer); return err; } STATIC void dwc2_device_bulk_abort(struct usbd_xfer *xfer) { DPRINTF("xfer=%p\n", xfer); dwc2_abort_xfer(xfer, USBD_CANCELLED); } STATIC void dwc2_device_bulk_close(struct usbd_pipe *pipe) { DPRINTF("pipe=%p\n", pipe); dwc2_close_pipe(pipe); } STATIC void dwc2_device_bulk_done(struct usbd_xfer *xfer) { DPRINTF("xfer=%p\n", xfer); } /***********************************************************************/ STATIC usbd_status dwc2_device_intr_transfer(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("xfer=%p\n", xfer); /* Insert last in queue. */ err = usb_insert_transfer(xfer); if (err) return err; /* Pipe isn't running, start first */ return dwc2_device_intr_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); } STATIC usbd_status dwc2_device_intr_start(struct usbd_xfer *xfer) { usbd_status err; xfer->status = USBD_IN_PROGRESS; err = dwc2_device_start(xfer); return err; } /* Abort a device interrupt request. */ STATIC void dwc2_device_intr_abort(struct usbd_xfer *xfer) { KASSERT(xfer->pipe->intrxfer == xfer); DPRINTF("xfer=%p\n", xfer); dwc2_abort_xfer(xfer, USBD_CANCELLED); } STATIC void dwc2_device_intr_close(struct usbd_pipe *pipe) { DPRINTF("pipe=%p\n", pipe); dwc2_close_pipe(pipe); } STATIC void dwc2_device_intr_done(struct usbd_xfer *xfer) { DPRINTF("\n"); if (xfer->pipe->repeat) { xfer->status = USBD_IN_PROGRESS; dwc2_device_start(xfer); } } /***********************************************************************/ usbd_status dwc2_device_isoc_transfer(struct usbd_xfer *xfer) { usbd_status err; DPRINTF("xfer=%p\n", xfer); /* Insert last in queue. */ err = usb_insert_transfer(xfer); if (err) return err; /* Pipe isn't running, start first */ return dwc2_device_isoc_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); } usbd_status dwc2_device_isoc_start(struct usbd_xfer *xfer) { struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); struct dwc2_softc *sc = DWC2_DPIPE2SC(dpipe); usbd_status err; /* Why would you do that anyway? */ if (sc->sc_bus.use_polling) return (USBD_INVAL); xfer->status = USBD_IN_PROGRESS; err = dwc2_device_start(xfer); return err; } void dwc2_device_isoc_abort(struct usbd_xfer *xfer) { DPRINTF("xfer=%p\n", xfer); dwc2_abort_xfer(xfer, USBD_CANCELLED); } void dwc2_device_isoc_close(struct usbd_pipe *pipe) { DPRINTF("\n"); dwc2_close_pipe(pipe); } void dwc2_device_isoc_done(struct usbd_xfer *xfer) { DPRINTF("\n"); } usbd_status dwc2_device_start(struct usbd_xfer *xfer) { struct dwc2_xfer *dxfer = DWC2_XFER2DXFER(xfer); struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); struct dwc2_softc *sc = DWC2_XFER2SC(xfer); struct dwc2_hsotg *hsotg = sc->sc_hsotg; struct dwc2_hcd_urb *dwc2_urb; struct usbd_device *dev = xfer->pipe->device; usb_endpoint_descriptor_t *ed = xfer->pipe->endpoint->edesc; uint8_t addr = dev->address; uint8_t xfertype = UE_GET_XFERTYPE(ed->bmAttributes); uint8_t epnum = UE_GET_ADDR(ed->bEndpointAddress); uint8_t dir = UE_GET_DIR(ed->bEndpointAddress); uint16_t mps = UE_GET_SIZE(UGETW(ed->wMaxPacketSize)); uint32_t len; uint32_t flags = 0; uint32_t off = 0; int retval, err = USBD_IN_PROGRESS; int alloc_bandwidth = 0; int i; DPRINTFN(1, "xfer=%p pipe=%p\n", xfer, xfer->pipe); if (xfertype == UE_ISOCHRONOUS || xfertype == UE_INTERRUPT) { mtx_enter(&hsotg->lock); if (!dwc2_hcd_is_bandwidth_allocated(hsotg, xfer)) alloc_bandwidth = 1; mtx_leave(&hsotg->lock); } /* * For Control pipe the direction is from the request, all other * transfers have been set correctly at pipe open time. */ if (xfertype == UE_CONTROL) { usb_device_request_t *req = &xfer->request; DPRINTFN(3, "xfer=%p type=0x%02x request=0x%02x wValue=0x%04x " "wIndex=0x%04x len=%d addr=%d endpt=%d dir=%s speed=%d " "mps=%d\n", xfer, req->bmRequestType, req->bRequest, UGETW(req->wValue), UGETW(req->wIndex), UGETW(req->wLength), dev->address, epnum, dir == UT_READ ? "in" :"out", dev->speed, mps); /* Copy request packet to our DMA buffer */ memcpy(KERNADDR(&dpipe->req_dma, 0), req, sizeof(*req)); usb_syncmem(&dpipe->req_dma, 0, sizeof(*req), BUS_DMASYNC_PREWRITE); len = UGETW(req->wLength); if ((req->bmRequestType & UT_READ) == UT_READ) { dir = UE_DIR_IN; } else { dir = UE_DIR_OUT; } DPRINTFN(3, "req = %p dma = %llx len %d dir %s\n", KERNADDR(&dpipe->req_dma, 0), (long long)DMAADDR(&dpipe->req_dma, 0), len, dir == UE_DIR_IN ? "in" : "out"); } else { DPRINTFN(3, "xfer=%p len=%d flags=%d addr=%d endpt=%d," " mps=%d dir %s\n", xfer, xfer->length, xfer->flags, addr, epnum, mps, dir == UT_READ ? "in" :"out"); len = xfer->length; } dwc2_urb = dxfer->urb; if (!dwc2_urb) return USBD_NOMEM; memset(dwc2_urb, 0, sizeof(*dwc2_urb) + sizeof(dwc2_urb->iso_descs[0]) * DWC2_MAXISOCPACKETS); dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, addr, epnum, xfertype, dir, mps); if (xfertype == UE_CONTROL) { dwc2_urb->setup_usbdma = &dpipe->req_dma; dwc2_urb->setup_packet = KERNADDR(&dpipe->req_dma, 0); dwc2_urb->setup_dma = DMAADDR(&dpipe->req_dma, 0); } else { /* XXXNH - % mps required? */ if ((xfer->flags & USBD_FORCE_SHORT_XFER) && (len % mps) == 0) flags |= URB_SEND_ZERO_PACKET; } flags |= URB_GIVEBACK_ASAP; /* * control transfers with no data phase don't touch usbdma, but * everything else does. */ if (!(xfertype == UE_CONTROL && len == 0)) { dwc2_urb->usbdma = &xfer->dmabuf; dwc2_urb->buf = KERNADDR(dwc2_urb->usbdma, 0); dwc2_urb->dma = DMAADDR(dwc2_urb->usbdma, 0); } dwc2_urb->length = len; dwc2_urb->flags = flags; dwc2_urb->status = -EINPROGRESS; dwc2_urb->packet_count = xfer->nframes; if (xfertype == UE_INTERRUPT || xfertype == UE_ISOCHRONOUS) { uint16_t ival; if (xfertype == UE_INTERRUPT && dpipe->pipe.interval != USBD_DEFAULT_INTERVAL) { ival = dpipe->pipe.interval; } else { ival = ed->bInterval; } if (ival < 1) { retval = -ENODEV; goto fail; } if (dev->speed == USB_SPEED_HIGH || (dev->speed == USB_SPEED_FULL && xfertype == UE_ISOCHRONOUS)) { if (ival > 16) { /* * illegal with HS/FS, but there were * documentation bugs in the spec */ ival = 256; } else { ival = (1 << (ival - 1)); } } else { if (xfertype == UE_INTERRUPT && ival < 10) ival = 10; } dwc2_urb->interval = ival; } xfer->actlen = 0; KASSERT(xfertype != UE_ISOCHRONOUS || xfer->nframes < DWC2_MAXISOCPACKETS); KASSERTMSG(xfer->nframes == 0 || xfertype == UE_ISOCHRONOUS, "nframes %d xfertype %d\n", xfer->nframes, xfertype); for (off = i = 0; i < xfer->nframes; ++i) { DPRINTFN(3, "xfer=%p frame=%d offset=%d length=%d\n", xfer, i, off, xfer->frlengths[i]); dwc2_hcd_urb_set_iso_desc_params(dwc2_urb, i, off, xfer->frlengths[i]); off += xfer->frlengths[i]; } /* might need to check cpu_intr_p */ mtx_enter(&hsotg->lock); if (xfer->timeout && !sc->sc_bus.use_polling) { timeout_reset(&xfer->timeout_handle, mstohz(xfer->timeout), dwc2_timeout, xfer); } dwc2_urb->priv = xfer; retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, &dpipe->priv, 0); if (retval) goto fail; if (alloc_bandwidth) { dwc2_allocate_bus_bandwidth(hsotg, dwc2_hcd_get_ep_bandwidth(hsotg, dpipe), xfer); } fail: mtx_leave(&hsotg->lock); switch (retval) { case 0: break; case -ENODEV: err = USBD_INVAL; break; case -ENOMEM: err = USBD_NOMEM; break; default: err = USBD_IOERROR; } return err; } void dwc2_worker(struct task *wk, void *priv) { struct dwc2_softc *sc = priv; struct dwc2_hsotg *hsotg = sc->sc_hsotg; /* db_enter(); */ #if 0 struct usbd_xfer *xfer = dwork->xfer; struct dwc2_xfer *dxfer = DWC2_XFER2DXFER(xfer); dwc2_hcd_endpoint_disable(sc->dwc_dev.hcd, dpipe->priv, 250); dwc_free(NULL, dpipe->urb); #endif if (wk == &hsotg->wf_otg) { dwc2_conn_id_status_change(wk); } else if (wk == &hsotg->start_work.work) { dwc2_hcd_start_func(wk); } else if (wk == &hsotg->reset_work.work) { dwc2_hcd_reset_func(wk); } else { #if 0 KASSERT(dwork->xfer != NULL); KASSERT(dxfer->queued == true); if (!(dxfer->flags & DWC2_XFER_ABORTING)) { dwc2_start_standard_chain(xfer); } dxfer->queued = false; wakeup(&dxfer->flags); #endif } } int dwc2_intr(void *p) { struct dwc2_softc *sc = p; struct dwc2_hsotg *hsotg = sc->sc_hsotg; int ret = 0; mtx_enter(&hsotg->lock); if (sc->sc_dying) goto done; if (sc->sc_bus.use_polling) { uint32_t intrs; intrs = dwc2_read_core_intr(hsotg); DWC2_WRITE_4(hsotg, GINTSTS, intrs); } else { ret = dwc2_interrupt(sc); } done: mtx_leave(&hsotg->lock); return ret; } int dwc2_interrupt(struct dwc2_softc *sc) { int ret = 0; if (sc->sc_hcdenabled) { ret |= dwc2_handle_hcd_intr(sc->sc_hsotg); } ret |= dwc2_handle_common_intr(sc->sc_hsotg); return ret; } /***********************************************************************/ int dwc2_detach(struct dwc2_softc *sc, int flags) { int rv = 0; if (sc->sc_child != NULL) rv = config_detach(sc->sc_child, flags); return rv; } /***********************************************************************/ int dwc2_init(struct dwc2_softc *sc) { int err = 0; sc->sc_bus.usbrev = USBREV_2_0; sc->sc_bus.methods = &dwc2_bus_methods; sc->sc_bus.pipe_size = sizeof(struct dwc2_pipe); sc->sc_hcdenabled = false; TAILQ_INIT(&sc->sc_complete); sc->sc_rhc_si = softintr_establish(IPL_SOFTUSB, dwc2_rhc, sc); #if 0 usb_setup_reserve(&sc->sc_bus, &sc->sc_dma_reserve, sc->sc_bus.dmatag, USB_MEM_RESERVE); #endif pool_init(&sc->sc_xferpool, sizeof(struct dwc2_xfer), 0, IPL_USB, 0, "dwc2xfer", NULL); pool_init(&sc->sc_qhpool, sizeof(struct dwc2_qh), 0, IPL_USB, 0, "dwc2qh", NULL); pool_init(&sc->sc_qtdpool, sizeof(struct dwc2_qtd), 0, IPL_USB, 0, "dwc2qtd", NULL); sc->sc_hsotg = malloc(sizeof(struct dwc2_hsotg), M_DEVBUF, M_ZERO | M_WAITOK); sc->sc_hsotg->hsotg_sc = sc; sc->sc_hsotg->dev = &sc->sc_bus.bdev; sc->sc_hcdenabled = true; err = dwc2_hcd_init(sc->sc_hsotg, sc->sc_params); if (err) { err = -err; goto fail; } return 0; fail: free(sc->sc_hsotg, M_DEVBUF, sizeof(struct dwc2_hsotg)); softintr_disestablish(sc->sc_rhc_si); return err; } int dwc2_dma_config(struct dwc2_softc *sc, struct dwc2_core_dma_config *config) { sc->sc_dma_config = config; return dwc2_hcd_dma_config(sc->sc_hsotg, sc->sc_dma_config); } #if 0 /* * curmode is a mode indication bit 0 = device, 1 = host */ STATIC const char * const intnames[32] = { "curmode", "modemis", "otgint", "sof", "rxflvl", "nptxfemp", "ginnakeff", "goutnakeff", "ulpickint", "i2cint", "erlysusp", "usbsusp", "usbrst", "enumdone", "isooutdrop", "eopf", "restore_done", "epmis", "iepint", "oepint", "incompisoin", "incomplp", "fetsusp", "resetdet", "prtint", "hchint", "ptxfemp", "lpm", "conidstschng", "disconnint", "sessreqint", "wkupint" }; /***********************************************************************/ #endif void dw_timeout(void *arg) { struct delayed_work *dw = arg; task_set(&dw->work, dw->dw_fn, dw->dw_arg); task_add(dw->dw_wq, &dw->work); } void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, int *hub_addr, int *hub_port) { struct usbd_xfer *xfer = context; struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); struct usbd_device *dev = dpipe->pipe.device; if (dev->myhsport != NULL) { *hub_addr = dev->myhsport->parent->address; *hub_port = dev->myhsport->portno; } } int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context) { struct usbd_xfer *xfer = context; struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); struct usbd_device *dev = dpipe->pipe.device; return dev->speed; } /* * Sets the final status of an URB and returns it to the upper layer. Any * required cleanup of the URB is performed. * * Must be called with interrupt disabled and spinlock held */ void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, int status) { struct usbd_xfer *xfer; struct dwc2_xfer *dxfer; struct dwc2_softc *sc; usb_endpoint_descriptor_t *ed; uint8_t xfertype; if (!qtd) { dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__); return; } if (!qtd->urb) { dev_dbg(hsotg->dev, "## %s: qtd->urb is NULL ##\n", __func__); return; } xfer = qtd->urb->priv; if (!xfer) { dev_dbg(hsotg->dev, "## %s: urb->priv is NULL ##\n", __func__); return; } dxfer = DWC2_XFER2DXFER(xfer); sc = DWC2_XFER2SC(xfer); ed = xfer->pipe->endpoint->edesc; xfertype = UE_GET_XFERTYPE(ed->bmAttributes); xfer->actlen = dwc2_hcd_urb_get_actual_length(qtd->urb); DPRINTFN(3, "xfer=%p actlen=%d\n", xfer, xfer->actlen); if (xfertype == UE_ISOCHRONOUS && dbg_perio()) { int i; for (i = 0; i < xfer->nframes; i++) dev_vdbg(hsotg->dev, " ISO Desc %d status %d\n", i, qtd->urb->iso_descs[i].status); } if (xfertype == UE_ISOCHRONOUS) { int i; xfer->actlen = 0; for (i = 0; i < xfer->nframes; ++i) { xfer->frlengths[i] = dwc2_hcd_urb_get_iso_desc_actual_length( qtd->urb, i); xfer->actlen += xfer->frlengths[i]; } } if (!status) { if (!(xfer->flags & USBD_SHORT_XFER_OK) && xfer->actlen < xfer->length) status = -EIO; } switch (status) { case 0: xfer->status = USBD_NORMAL_COMPLETION; break; case -EPIPE: xfer->status = USBD_STALLED; break; case -ETIMEDOUT: xfer->status = USBD_TIMEOUT; break; case -EPROTO: xfer->status = USBD_INVAL; break; case -EIO: xfer->status = USBD_IOERROR; break; case -EOVERFLOW: xfer->status = USBD_IOERROR; break; default: printf("%s: unknown error status %d\n", __func__, status); } if (xfertype == UE_ISOCHRONOUS || xfertype == UE_INTERRUPT) { struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); dwc2_free_bus_bandwidth(hsotg, dwc2_hcd_get_ep_bandwidth(hsotg, dpipe), xfer); } qtd->urb = NULL; timeout_del(&xfer->timeout_handle); usb_rem_task(xfer->device, &xfer->abort_task); MUTEX_ASSERT_LOCKED(&hsotg->lock); TAILQ_INSERT_TAIL(&sc->sc_complete, dxfer, xnext); mtx_leave(&hsotg->lock); usb_schedsoftintr(&sc->sc_bus); mtx_enter(&hsotg->lock); } int _dwc2_hcd_start(struct dwc2_hsotg *hsotg) { dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); mtx_enter(&hsotg->lock); hsotg->op_state = OTG_STATE_A_HOST; dwc2_hcd_reinit(hsotg); /* Try to enable port. */ dwc2_handle_hcd_intr(hsotg); /*XXXNH*/ delay(50); mtx_leave(&hsotg->lock); return 0; } int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg) { return false; }