#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/endian.h>
#include <machine/bus.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usb_mem.h>
#include <dev/usb/dwc2/dwc2.h>
#include <dev/usb/dwc2/dwc2var.h>
#include <dev/usb/dwc2/dwc2_core.h>
#include <dev/usb/dwc2/dwc2_hcd.h>
#ifdef DWC2_COUNTERS
#define DWC2_EVCNT_ADD(a,b) ((void)((a).ev_count += (b)))
#else
#define DWC2_EVCNT_ADD(a,b) do { } while (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 *);
STATIC struct usbd_xfer *dwc2_allocx(struct usbd_bus *);
STATIC void dwc2_freex(struct usbd_bus *, struct usbd_xfer *);
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 *);
int dwc2_check_core_version(struct dwc2_hsotg *);
#define DWC2_INTR_ENDPT 1
STATIC const struct usbd_bus_methods dwc2_bus_methods = {
.open_pipe = dwc2_open,
.dev_setaddr = dwc2_setaddr,
.soft_intr = dwc2_softintr,
.do_poll = dwc2_poll,
.allocx = dwc2_allocx,
.freex = dwc2_freex,
};
STATIC const 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 const 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 const 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 const 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 const 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 const 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,
};
STATIC int
dwc2_setaddr(struct usbd_device *dev, int addr)
{
if (usbd_set_address(dev, addr))
return (1);
dev->address = addr;
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 | PR_ZERO);
if (dxfer != NULL) {
#ifdef DIAGNOSTIC
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_softc *sc = DWC2_BUS2SC(bus);
DPRINTFN(10, "\n");
#ifdef DIAGNOSTIC
if (xfer->busy_free != XFER_ONQU &&
xfer->status != USBD_NOT_STARTED) {
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);
pool_put(&sc->sc_xferpool, xfer);
}
STATIC void
dwc2_rhc(void *addr)
{
struct dwc2_softc *sc = addr;
struct usbd_xfer *xfer;
u_char *p;
DPRINTF("\n");
mtx_enter(&sc->sc_lock);
xfer = sc->sc_intrxfer;
if (xfer == NULL) {
mtx_leave(&sc->sc_lock);
return;
}
p = KERNADDR(&xfer->dmabuf, 0);
p[0] = 0x02;
xfer->actlen = xfer->length;
xfer->status = USBD_NORMAL_COMPLETION;
usb_transfer_complete(xfer);
mtx_leave(&sc->sc_lock);
}
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, *next;
TAILQ_HEAD(, dwc2_xfer) claimed = TAILQ_HEAD_INITIALIZER(claimed);
mtx_enter(&hsotg->lock);
TAILQ_FOREACH_SAFE(dxfer, &sc->sc_complete, xnext, next) {
KASSERT(dxfer->xfer.status == USBD_IN_PROGRESS);
KASSERT(dxfer->intr_status != USBD_CANCELLED);
KASSERT(dxfer->intr_status != USBD_TIMEOUT);
TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext);
TAILQ_INSERT_TAIL(&claimed, dxfer, xnext);
}
mtx_leave(&hsotg->lock);
while (!TAILQ_EMPTY(&claimed)) {
dxfer = TAILQ_FIRST(&claimed);
KASSERT(dxfer->xfer.status == USBD_IN_PROGRESS);
KASSERT(dxfer->intr_status != USBD_CANCELLED);
KASSERT(dxfer->intr_status != USBD_TIMEOUT);
TAILQ_REMOVE(&claimed, dxfer, xnext);
dxfer->xfer.status = dxfer->intr_status;
usb_transfer_complete(&dxfer->xfer);
}
}
STATIC void
dwc2_timeout(void *addr)
{
struct usbd_xfer *xfer = addr;
struct dwc2_softc *sc = DWC2_XFER2SC(xfer);
if (sc->sc_bus.dying) {
dwc2_timeout_task(addr);
return;
}
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;
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_bus.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, USB_DMA_COHERENT, &dpipe->req_dma);
if (err)
return USBD_NOMEM;
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;
return USBD_NORMAL_COMPLETION;
}
STATIC void
dwc2_poll(struct usbd_bus *bus)
{
struct dwc2_softc *sc = DWC2_BUS2SC(bus);
dwc2_interrupt(sc);
}
STATIC void
dwc2_close_pipe(struct usbd_pipe *pipe)
{
}
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;
int err;
splsoftassert(IPL_SOFTUSB);
DPRINTF("xfer %p pipe %p status 0x%08x\n", xfer, xfer->pipe,
xfer->status);
if (sc->sc_bus.dying || xfer->status == USBD_NOT_STARTED) {
xfer->status = status;
timeout_del(&xfer->timeout_handle);
usb_rem_task(xfer->device, &xfer->abort_task);
usb_transfer_complete(xfer);
return;
}
KASSERT(xfer->status != USBD_CANCELLED);
if (xfer->status != USBD_IN_PROGRESS) {
DPRINTF("%s: already done \n", __func__);
return;
}
timeout_del(&xfer->timeout_handle);
usb_rem_task(xfer->device, &xfer->abort_task);
xfer->status = USBD_CANCELLED;
KASSERTMSG((xfer->status == USBD_CANCELLED ||
xfer->status == USBD_TIMEOUT),
"bad abort status: %d", xfer->status);
mtx_enter(&hsotg->lock);
TAILQ_FOREACH(d, &sc->sc_complete, xnext) {
if (d == dxfer) {
TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext);
break;
}
}
err = dwc2_hcd_urb_dequeue(hsotg, dxfer->urb);
if (err) {
DPRINTF("dwc2_hcd_urb_dequeue failed\n");
}
mtx_leave(&hsotg->lock);
usb_transfer_complete(xfer);
}
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);
}
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),
.bNumInterfaces = 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},
.bInterval = 255,
},
};
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;
KASSERT(xfer->rqflags & URQ_REQUEST);
if (sc->sc_bus.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):
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);
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:
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)
{
}
STATIC void
dwc2_root_ctrl_close(struct usbd_pipe *pipe)
{
}
STATIC void
dwc2_root_ctrl_done(struct usbd_xfer *xfer)
{
}
STATIC usbd_status
dwc2_root_intr_transfer(struct usbd_xfer *xfer)
{
usbd_status err;
err = usb_insert_transfer(xfer);
if (err)
return err;
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);
if (sc->sc_bus.dying)
return USBD_IOERROR;
sc->sc_intrxfer = xfer;
return USBD_IN_PROGRESS;
}
STATIC void
dwc2_root_intr_abort(struct usbd_xfer *xfer)
{
struct dwc2_softc *sc = DWC2_XFER2SC(xfer);
int s;
sc->sc_intrxfer = NULL;
xfer->status = USBD_CANCELLED;
s = splusb();
usb_transfer_complete(xfer);
splx(s);
}
STATIC void
dwc2_root_intr_close(struct usbd_pipe *pipe)
{
}
STATIC void
dwc2_root_intr_done(struct usbd_xfer *xfer)
{
}
STATIC usbd_status
dwc2_device_ctrl_transfer(struct usbd_xfer *xfer)
{
usbd_status err;
err = usb_insert_transfer(xfer);
if (err)
return err;
return dwc2_device_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue));
}
STATIC usbd_status
dwc2_device_ctrl_start(struct usbd_xfer *xfer)
{
struct dwc2_softc *sc = DWC2_XFER2SC(xfer);
usbd_status err;
KASSERT(xfer->rqflags & URQ_REQUEST);
if (sc->sc_bus.dying)
return USBD_IOERROR;
err = dwc2_device_start(xfer);
if (err)
return err;
return USBD_IN_PROGRESS;
}
STATIC void
dwc2_device_ctrl_abort(struct usbd_xfer *xfer)
{
dwc2_abort_xfer(xfer, USBD_CANCELLED);
}
STATIC void
dwc2_device_ctrl_close(struct usbd_pipe *pipe)
{
struct dwc2_softc * const sc = DWC2_PIPE2SC(pipe);
struct dwc2_pipe * const dpipe = DWC2_PIPE2DPIPE(pipe);
dwc2_close_pipe(pipe);
usb_freemem(&sc->sc_bus, &dpipe->req_dma);
}
STATIC void
dwc2_device_ctrl_done(struct usbd_xfer *xfer)
{
KASSERT(xfer->rqflags & URQ_REQUEST);
}
STATIC usbd_status
dwc2_device_bulk_transfer(struct usbd_xfer *xfer)
{
usbd_status err;
err = usb_insert_transfer(xfer);
if (err)
return err;
return dwc2_device_bulk_start(SIMPLEQ_FIRST(&xfer->pipe->queue));
}
STATIC usbd_status
dwc2_device_bulk_start(struct usbd_xfer *xfer)
{
struct dwc2_softc *sc = DWC2_XFER2SC(xfer);
usbd_status err;
KASSERT(!(xfer->rqflags & URQ_REQUEST));
if (sc->sc_bus.dying)
return (USBD_IOERROR);
err = dwc2_device_start(xfer);
if (err)
return err;
return USBD_IN_PROGRESS;
}
STATIC void
dwc2_device_bulk_abort(struct usbd_xfer *xfer)
{
dwc2_abort_xfer(xfer, USBD_CANCELLED);
}
STATIC void
dwc2_device_bulk_close(struct usbd_pipe *pipe)
{
dwc2_close_pipe(pipe);
}
STATIC void
dwc2_device_bulk_done(struct usbd_xfer *xfer)
{
}
STATIC usbd_status
dwc2_device_intr_transfer(struct usbd_xfer *xfer)
{
usbd_status err;
err = usb_insert_transfer(xfer);
if (err)
return err;
return dwc2_device_intr_start(SIMPLEQ_FIRST(&xfer->pipe->queue));
}
STATIC usbd_status
dwc2_device_intr_start(struct usbd_xfer *xfer)
{
struct dwc2_softc *sc = DWC2_XFER2SC(xfer);
usbd_status err;
KASSERT(!(xfer->rqflags & URQ_REQUEST));
if (sc->sc_bus.dying)
return (USBD_IOERROR);
err = dwc2_device_start(xfer);
if (err)
return err;
return USBD_IN_PROGRESS;
}
STATIC void
dwc2_device_intr_abort(struct usbd_xfer *xfer)
{
KASSERT(!xfer->pipe->repeat || xfer->pipe->intrxfer == xfer);
dwc2_abort_xfer(xfer, USBD_CANCELLED);
}
STATIC void
dwc2_device_intr_close(struct usbd_pipe *pipe)
{
dwc2_close_pipe(pipe);
}
STATIC void
dwc2_device_intr_done(struct usbd_xfer *xfer)
{
if (xfer->pipe->repeat)
dwc2_device_start(xfer);
}
usbd_status
dwc2_device_isoc_transfer(struct usbd_xfer *xfer)
{
usbd_status err;
err = usb_insert_transfer(xfer);
if (err)
return err;
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;
if (sc->sc_bus.use_polling)
return (USBD_INVAL);
err = dwc2_device_start(xfer);
if (err)
return err;
return USBD_IN_PROGRESS;
}
void
dwc2_device_isoc_abort(struct usbd_xfer *xfer)
{
dwc2_abort_xfer(xfer, USBD_CANCELLED);
}
void
dwc2_device_isoc_close(struct usbd_pipe *pipe)
{
dwc2_close_pipe(pipe);
}
void
dwc2_device_isoc_done(struct usbd_xfer *xfer)
{
}
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);
uint32_t mps = UGETW(ed->wMaxPacketSize);
uint32_t len;
uint32_t flags = 0;
uint32_t off = 0;
int retval, err;
int alloc_bandwidth = 0;
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);
}
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,
UE_GET_SIZE(mps));
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 if (xfertype == UE_ISOCHRONOUS) {
DPRINTFN(3, "xfer=%p nframes=%d flags=%d addr=%d endpt=%d,"
" mps=%d dir %s\n", xfer, xfer->nframes, xfer->flags, addr,
epnum, UE_GET_SIZE(mps), dir == UT_READ ? "in" :"out");
#ifdef DIAGNOSTIC
len = 0;
for (size_t i = 0; i < xfer->nframes; i++)
len += xfer->frlengths[i];
if (len != xfer->length)
panic("len (%d) != xfer->length (%d)", len,
xfer->length);
#endif
len = xfer->length;
} 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, UE_GET_SIZE(mps), dir == UT_READ ? "in" :"out");
len = xfer->length;
}
dxfer->urb = dwc2_hcd_urb_alloc(sc->sc_hsotg, xfer->nframes, M_NOWAIT);
dwc2_urb = dxfer->urb;
if (!dwc2_urb)
return USBD_NOMEM;
memset(dwc2_urb, 0, sizeof(*dwc2_urb) +
sizeof(dwc2_urb->iso_descs[0]) * xfer->nframes);
dwc2_urb->priv = xfer;
dwc2_urb->packet_count = xfer->nframes;
dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, addr, epnum, xfertype, dir,
UE_GET_SIZE(mps), UE_GET_TRANS(mps) + 1);
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 {
if ((xfer->flags & USBD_FORCE_SHORT_XFER) && (len %
UE_GET_SIZE(mps)) == 0)
flags |= URB_SEND_ZERO_PACKET;
}
flags |= URB_GIVEBACK_ASAP;
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);
usb_syncmem(&xfer->dmabuf, 0, len,
dir == UE_DIR_IN ?
BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
}
dwc2_urb->length = len;
dwc2_urb->flags = flags;
dwc2_urb->status = -EINPROGRESS;
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) {
ival = 256;
} else {
ival = (1 << (ival - 1));
}
} else {
if (xfertype == UE_INTERRUPT && ival < 10)
ival = 10;
}
dwc2_urb->interval = ival;
}
xfer->actlen = 0;
KASSERTMSG(xfer->nframes == 0 || xfertype == UE_ISOCHRONOUS,
"nframes %d xfertype %d\n", xfer->nframes, xfertype);
off = 0;
for (size_t i = 0; i < xfer->nframes; ++i) {
DPRINTFN(3, "xfer=%p frame=%zu 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];
}
struct dwc2_qh *qh = dpipe->priv;
struct dwc2_qtd *qtd;
bool qh_allocated = false;
if (!qh) {
qh = dwc2_hcd_qh_create(hsotg, dwc2_urb, M_ZERO | M_NOWAIT);
if (!qh) {
retval = -ENOMEM;
goto fail;
}
dpipe->priv = qh;
qh_allocated = true;
}
qtd = pool_get(&sc->sc_qtdpool, PR_NOWAIT | PR_ZERO);
if (!qtd) {
retval = -ENOMEM;
goto fail1;
}
mtx_enter(&hsotg->lock);
retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, qh, qtd);
if (retval)
goto fail2;
if (xfer->timeout && !sc->sc_bus.use_polling) {
timeout_set(&xfer->timeout_handle, dwc2_timeout, xfer);
timeout_add_msec(&xfer->timeout_handle, xfer->timeout);
}
xfer->status = USBD_IN_PROGRESS;
if (alloc_bandwidth) {
dwc2_allocate_bus_bandwidth(hsotg,
dwc2_hcd_get_ep_bandwidth(hsotg, dpipe),
xfer);
}
mtx_leave(&hsotg->lock);
return USBD_IN_PROGRESS;
fail2:
dwc2_urb->priv = NULL;
mtx_leave(&hsotg->lock);
pool_put(&sc->sc_qtdpool, qtd);
fail1:
if (qh_allocated) {
dpipe->priv = NULL;
dwc2_hcd_qh_free(hsotg, qh);
}
fail:
switch (retval) {
case -EINVAL:
case -ENODEV:
err = USBD_INVAL;
break;
case -ENOMEM:
err = USBD_NOMEM;
break;
default:
err = USBD_IOERROR;
}
return err;
}
int dwc2_intr(void *p)
{
struct dwc2_softc *sc = p;
struct dwc2_hsotg *hsotg;
int ret = 0;
if (sc == NULL)
return 0;
hsotg = sc->sc_hsotg;
if (sc->sc_bus.dying)
goto done;
if (sc->sc_bus.use_polling) {
uint32_t intrs;
intrs = dwc2_read_core_intr(hsotg);
dwc2_writel(hsotg, intrs, GINTSTS);
} else {
ret = dwc2_interrupt(sc);
}
done:
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 retval, err = 0;
struct dwc2_hsotg *hsotg;
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;
mtx_init(&sc->sc_lock, IPL_SOFTUSB);
TAILQ_INIT(&sc->sc_complete);
sc->sc_rhc_si = softintr_establish(IPL_SOFTUSB, dwc2_rhc, sc);
pool_init(&sc->sc_xferpool, sizeof(struct dwc2_xfer), 0, IPL_VM, 0,
"dwc2xfer", NULL);
pool_init(&sc->sc_qhpool, sizeof(struct dwc2_qh), 0, IPL_VM, 0,
"dwc2qh", NULL);
pool_init(&sc->sc_qtdpool, sizeof(struct dwc2_qtd), 0, IPL_VM, 0,
"dwc2qtd", NULL);
sc->sc_hsotg = malloc(sizeof(struct dwc2_hsotg), M_USBHC,
M_ZERO | M_WAITOK);
sc->sc_hsotg->hsotg_sc = sc;
sc->sc_hsotg->dev = &sc->sc_bus.bdev;
sc->sc_hcdenabled = true;
hsotg = sc->sc_hsotg;
hsotg->dr_mode = USB_DR_MODE_HOST;
retval = dwc2_check_core_version(hsotg);
if (retval)
goto fail2;
retval = dwc2_core_reset(hsotg, false);
if (retval)
goto fail2;
retval = dwc2_get_hwparams(hsotg);
if (retval)
goto fail2;
dwc2_force_dr_mode(hsotg);
retval = dwc2_init_params(hsotg);
if (retval)
goto fail2;
#if 0
if (hsotg->dr_mode != USB_DR_MODE_HOST) {
retval = dwc2_gadget_init(hsotg);
if (retval)
goto fail2;
hsotg->gadget_enabled = 1;
}
#endif
if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
retval = dwc2_hcd_init(hsotg);
if (retval) {
if (hsotg->gadget_enabled)
dwc2_hsotg_remove(hsotg);
goto fail2;
}
hsotg->hcd_enabled = 1;
}
hsotg->hibernated = 0;
return 0;
fail2:
err = -retval;
free(sc->sc_hsotg, M_USBHC, sizeof(struct dwc2_hsotg));
softintr_disestablish(sc->sc_rhc_si);
return err;
}
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);
}
int dwc2_check_core_version(struct dwc2_hsotg *hsotg)
{
struct dwc2_hw_params *hw = &hsotg->hw_params;
hw->snpsid = dwc2_readl(hsotg, GSNPSID);
if ((hw->snpsid & GSNPSID_ID_MASK) != DWC2_OTG_ID &&
(hw->snpsid & GSNPSID_ID_MASK) != DWC2_FS_IOT_ID &&
(hw->snpsid & GSNPSID_ID_MASK) != DWC2_HS_IOT_ID) {
dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n",
hw->snpsid);
return -ENODEV;
}
dev_dbg(hsotg->dev, "Core Release: %1x.%1x%1x%1x (snpsid=%x)\n",
hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf,
hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid);
return 0;
}