diff options
author | Martin Pieuchot <mpi@cvs.openbsd.org> | 2014-03-08 14:34:13 +0000 |
---|---|---|
committer | Martin Pieuchot <mpi@cvs.openbsd.org> | 2014-03-08 14:34:13 +0000 |
commit | eaa89fd716ef08f5a0ce1b718a9a650eb0a254ae (patch) | |
tree | ab7fd95e674af5b2d6c34e697a9332877b755ddf /sys | |
parent | 192e3a27076d32a4c5c9c4b99eff2766075879dd (diff) |
Dumb xhci(4) implementation.
This driver does not handle isochronous endpoint (yet) and has no logical
TD representation. Each transfer is linked to the raw TRB of its related
endpoint.
Most of the transfer error completion codes are not handled, even with all
the cheese provided by miod@ I couldn't find a proper way to reset an
endpoint asynchronously when a device babbles. Or maybe it was the wine?
Anyway this will come soon.
In general the endpoint configuration and reset code is really crude and
requires some love, but our stack should be fixed to properly open only
once the default pipe of every new USB device first.
This means this driver wont work as it is, our stack needs other changes
first.
Suspend/resume works but ports are not suspended for the moment.
But even with these problems, interrupt devices: ukbd(4), ums(4) and
sensors like ugold(4) work properly and USB 3.0 umass(4) devices give
me a reasonnable read/write speed.
Timeouts to cancel USB transfers are not enabled *on purpose*, to be able
to track down potential timing issues.
I'm committing now so that others can help fixing my bugs (8
All this work has been done on an ExpressCard with a NEC xHCI 0.96, other
implementations/versions might trigger more bugs :)
Diffstat (limited to 'sys')
-rw-r--r-- | sys/conf/files | 6 | ||||
-rw-r--r-- | sys/dev/pci/files.pci | 6 | ||||
-rw-r--r-- | sys/dev/pci/xhci_pci.c | 211 | ||||
-rw-r--r-- | sys/dev/usb/xhci.c | 2199 | ||||
-rw-r--r-- | sys/dev/usb/xhcireg.h | 439 | ||||
-rw-r--r-- | sys/dev/usb/xhcivar.h | 142 |
6 files changed, 3001 insertions, 2 deletions
diff --git a/sys/conf/files b/sys/conf/files index 97bb5273d6f..15c1b856778 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.567 2014/03/05 23:10:41 kettenis Exp $ +# $OpenBSD: files,v 1.568 2014/03/08 14:34:12 mpi Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -464,6 +464,10 @@ file dev/usb/ohci.c ohci needs-flag device ehci: usbus file dev/usb/ehci.c ehci needs-flag +# XHCI USB controller +device xhci: usbus +file dev/usb/xhci.c xhci needs-flag + # SDHC SD/MMC controller device sdhc: sdmmcbus file dev/sdmmc/sdhc.c sdhc needs-flag diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index f39c172fa53..d1d3fcb6dd7 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,4 +1,4 @@ -# $OpenBSD: files.pci,v 1.304 2014/03/05 23:10:41 kettenis Exp $ +# $OpenBSD: files.pci,v 1.305 2014/03/08 14:34:12 mpi Exp $ # $NetBSD: files.pci,v 1.20 1996/09/24 17:47:15 christos Exp $ # # Config file and device description for machine-independent PCI code. @@ -518,6 +518,10 @@ file dev/pci/ohci_pci.c ohci attach ehci at pci with ehci_pci file dev/pci/ehci_pci.c ehci +# XHCI USB controller +attach xhci at pci with xhci_pci +file dev/pci/xhci_pci.c xhci + # YENTA PCI-CardBus bridge #device cbb: cbbus, pcmciabus device cbb: pcmciaslot diff --git a/sys/dev/pci/xhci_pci.c b/sys/dev/pci/xhci_pci.c new file mode 100644 index 00000000000..75013f9c3e9 --- /dev/null +++ b/sys/dev/pci/xhci_pci.c @@ -0,0 +1,211 @@ +/* $OpenBSD: xhci_pci.c,v 1.1 2014/03/08 14:34:12 mpi Exp $ */ + +/* + * Copyright (c) 2001, 2002 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * 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. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/rwlock.h> +#include <sys/device.h> +#include <sys/timeout.h> +#include <sys/queue.h> + +#include <machine/bus.h> + +#include <dev/pci/pcidevs.h> +#include <dev/pci/pcivar.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/xhcireg.h> +#include <dev/usb/xhcivar.h> + +#ifdef XHCI_DEBUG +#define DPRINTF(x) if (xhcidebug) printf x +extern int xhcidebug; +#else +#define DPRINTF(x) +#endif + +struct xhci_pci_softc { + struct xhci_softc sc; + pci_chipset_tag_t sc_pc; + pcitag_t sc_tag; + void *sc_ih; /* interrupt vectoring */ +}; + +int xhci_pci_match(struct device *, void *, void *); +void xhci_pci_attach(struct device *, struct device *, void *); +int xhci_pci_detach(struct device *, int); +void xhci_pci_takecontroller(struct xhci_pci_softc *, int); + +struct cfattach xhci_pci_ca = { + sizeof(struct xhci_pci_softc), xhci_pci_match, xhci_pci_attach, + xhci_pci_detach, xhci_activate +}; + +int +xhci_pci_match(struct device *parent, void *match, void *aux) +{ + struct pci_attach_args *pa = (struct pci_attach_args *) aux; + + if (PCI_CLASS(pa->pa_class) == PCI_CLASS_SERIALBUS && + PCI_SUBCLASS(pa->pa_class) == PCI_SUBCLASS_SERIALBUS_USB && + PCI_INTERFACE(pa->pa_class) == PCI_INTERFACE_XHCI) + return (1); + + return (0); +} + +void +xhci_pci_attach(struct device *parent, struct device *self, void *aux) +{ + struct xhci_pci_softc *psc = (struct xhci_pci_softc *)self; + struct pci_attach_args *pa = (struct pci_attach_args *)aux; + const char *intrstr; + const char *vendor; + pci_intr_handle_t ih; + pcireg_t reg; + int error; + + reg = pci_mapreg_type(pa->pa_pc, pa->pa_tag, PCI_CBMEM); + if (pci_mapreg_map(pa, PCI_CBMEM, reg, 0, &psc->sc.iot, &psc->sc.ioh, + NULL, &psc->sc.sc_size, 0)) { + printf(": can't map mem space\n"); + return; + } + + psc->sc_pc = pa->pa_pc; + psc->sc_tag = pa->pa_tag; + psc->sc.sc_bus.dmatag = pa->pa_dmat; + + /* Map and establish the interrupt. */ + if (pci_intr_map(pa, &ih) != 0) { + printf(": couldn't map interrupt\n"); + goto unmap_ret; + } + intrstr = pci_intr_string(pa->pa_pc, ih); + + psc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_USB, + xhci_intr, psc, psc->sc.sc_bus.bdev.dv_xname); + if (psc->sc_ih == NULL) { + printf(": couldn't establish interrupt"); + if (intrstr != NULL) + printf(" at %s", intrstr); + printf("\n"); + goto unmap_ret; + } + printf(": %s\n", intrstr); + + /* Figure out vendor for root hub descriptor. */ + vendor = pci_findvendor(pa->pa_id); + psc->sc.sc_id_vendor = PCI_VENDOR(pa->pa_id); + if (vendor) + strlcpy(psc->sc.sc_vendor, vendor, sizeof(psc->sc.sc_vendor)); + else + snprintf(psc->sc.sc_vendor, sizeof(psc->sc.sc_vendor), + "vendor 0x%04x", PCI_VENDOR(pa->pa_id)); + + xhci_pci_takecontroller(psc, 0); + + if (xhci_init(&psc->sc)) { + printf("%s: init failed, error=%d\n", + psc->sc.sc_bus.bdev.dv_xname, error); + goto disestablish_ret; + } + + /* Attach usb device. */ + psc->sc.sc_child = config_found(self, &psc->sc.sc_bus, usbctlprint); + + return; + +disestablish_ret: + pci_intr_disestablish(psc->sc_pc, psc->sc_ih); +unmap_ret: + bus_space_unmap(psc->sc.iot, psc->sc.ioh, psc->sc.sc_size); +} + +int +xhci_pci_detach(struct device *self, int flags) +{ + struct xhci_pci_softc *psc = (struct xhci_pci_softc *)self; + int rv; + + rv = xhci_detach(&psc->sc, flags); + if (rv) + return (rv); + if (psc->sc_ih != NULL) { + pci_intr_disestablish(psc->sc_pc, psc->sc_ih); + psc->sc_ih = NULL; + } + if (psc->sc.sc_size) { + bus_space_unmap(psc->sc.iot, psc->sc.ioh, psc->sc.sc_size); + psc->sc.sc_size = 0; + } + return (0); +} + +void +xhci_pci_takecontroller(struct xhci_pci_softc *psc, int silent) +{ + uint32_t cparams, xecp, eec; + uint8_t bios_sem; + int i; + + cparams = XREAD4(&psc->sc, XHCI_HCCPARAMS); + eec = -1; + + /* Synchronise with the BIOS if it owns the controller. */ + for (xecp = XHCI_HCC_XECP(cparams) << 2; xecp != 0; + xecp = XHCI_XECP_NEXT(eec) << 2) { + eec = XREAD4(&psc->sc, xecp); + if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY) + continue; + bios_sem = XREAD1(&psc->sc, xecp + XHCI_XECP_BIOS_SEM); + if (bios_sem) { + XWRITE1(&psc->sc, xecp + XHCI_XECP_OS_SEM, 1); + DPRINTF(("%s: waiting for BIOS to give up control\n", + psc->sc.sc_bus.bdev.dv_xname)); + for (i = 0; i < 5000; i++) { + bios_sem = XREAD1(&psc->sc, xecp + + XHCI_XECP_BIOS_SEM); + if (bios_sem == 0) + break; + DELAY(1000); + } + if (silent == 0 && bios_sem) + printf("%s: timed out waiting for BIOS\n", + psc->sc.sc_bus.bdev.dv_xname); + } + } +} diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c new file mode 100644 index 00000000000..c94d4a54775 --- /dev/null +++ b/sys/dev/usb/xhci.c @@ -0,0 +1,2199 @@ +/* $OpenBSD: xhci.c,v 1.1 2014/03/08 14:34:11 mpi Exp $ */ + +/* + * Copyright (c) 2014 Martin Pieuchot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/device.h> +#include <sys/timeout.h> + +#include <machine/bus.h> +#include <machine/endian.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/xhcireg.h> +#include <dev/usb/xhcivar.h> + +struct cfdriver xhci_cd = { + NULL, "xhci", DV_DULL +}; + +#ifdef XHCI_DEBUG +#define DPRINTF(x) do { if (xhcidebug) printf x; } while(0) +#define DPRINTFN(n,x) do { if (xhcidebug>(n)) printf x; } while (0) +int xhcidebug = 3; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define DEVNAME(sc) ((sc)->sc_bus.bdev.dv_xname) + +#define TRBOFF(ring, trb) ((void *)(trb) - (void *)((ring).trbs)) +#define TRBADDR(ring, trb) DMAADDR(&(ring).dma, TRBOFF(ring, trb)) + +struct xhci_pipe { + struct usbd_pipe pipe; + + uint8_t dci; + uint8_t slot; /* Device slot ID */ + struct xhci_ring ring; + + /* + * XXX used to pass the xfer pointer back to the + * interrupt routine, better way? + */ + struct usbd_xfer *pending_xfers[XHCI_MAX_TRANSFERS]; + int halted; + size_t free_trbs; +}; + +int xhci_reset(struct xhci_softc *); +int xhci_intr1(struct xhci_softc *); +void xhci_waitintr(struct xhci_softc *, struct usbd_xfer *); +void xhci_event_dequeue(struct xhci_softc *); +void xhci_event_xfer(struct xhci_softc *, uint64_t, uint32_t, uint32_t); +void xhci_event_port_change(struct xhci_softc *, uint64_t, uint32_t); +int xhci_pipe_init(struct xhci_softc *, struct usbd_pipe *, uint32_t); +int xhci_scratchpad_alloc(struct xhci_softc *, int); +void xhci_scratchpad_free(struct xhci_softc *); +int xhci_softdev_alloc(struct xhci_softc *, uint8_t); +void xhci_softdev_free(struct xhci_softc *, uint8_t); +int xhci_ring_alloc(struct xhci_softc *, struct xhci_ring *, size_t); +void xhci_ring_free(struct xhci_softc *, struct xhci_ring *); +void xhci_ring_reset(struct xhci_softc *, struct xhci_ring *); +struct xhci_trb *xhci_ring_dequeue(struct xhci_softc *, struct xhci_ring *, + int); + +struct xhci_trb *xhci_xfer_get_trb(struct xhci_softc *, struct usbd_xfer*, + uint8_t *, int); +void xhci_xfer_done(struct usbd_xfer *xfer); +/* xHCI command helpers. */ +int xhci_command_submit(struct xhci_softc *, struct xhci_trb *, int); +int xhci_command_abort(struct xhci_softc *); + +int xhci_cmd_reset_endpoint(struct xhci_softc *, uint8_t, uint8_t); +int xhci_cmd_set_tr_deq(struct xhci_softc *, uint8_t, uint8_t, uint64_t); +int xhci_cmd_configure_endpoint(struct xhci_softc *, uint8_t, uint64_t); +int xhci_cmd_stop_endpoint(struct xhci_softc *, uint8_t, uint8_t); +int xhci_cmd_slot_control(struct xhci_softc *, uint8_t *, int); +int xhci_cmd_address_device(struct xhci_softc *,uint8_t, uint64_t, int); +int xhci_cmd_evaluate_ctx(struct xhci_softc *, uint8_t, uint64_t); +#ifdef XHCI_DEBUG +int xhci_cmd_noop(struct xhci_softc *); +#endif + +/* XXX should be part of the Bus interface. */ +void xhci_abort_xfer(struct usbd_xfer *, usbd_status); +void xhci_pipe_close(struct usbd_pipe *); +void xhci_noop(struct usbd_xfer *); + +/* XXX these are common to all HC drivers and should be merged. */ +void xhci_timeout(void *); +void xhci_timeout_task(void *); + +/* USBD Bus Interface. */ +usbd_status xhci_pipe_open(struct usbd_pipe *); +void xhci_softintr(void *); +void xhci_poll(struct usbd_bus *); +struct usbd_xfer *xhci_allocx(struct usbd_bus *); +void xhci_freex(struct usbd_bus *, struct usbd_xfer *); + +usbd_status xhci_root_ctrl_transfer(struct usbd_xfer *); +usbd_status xhci_root_ctrl_start(struct usbd_xfer *); + +usbd_status xhci_root_intr_transfer(struct usbd_xfer *); +usbd_status xhci_root_intr_start(struct usbd_xfer *); +void xhci_root_intr_abort(struct usbd_xfer *); +void xhci_root_intr_done(struct usbd_xfer *); + +usbd_status xhci_device_ctrl_transfer(struct usbd_xfer *); +usbd_status xhci_device_ctrl_start(struct usbd_xfer *); +void xhci_device_ctrl_abort(struct usbd_xfer *); + +usbd_status xhci_device_generic_transfer(struct usbd_xfer *); +usbd_status xhci_device_generic_start(struct usbd_xfer *); +void xhci_device_generic_abort(struct usbd_xfer *); +void xhci_device_generic_done(struct usbd_xfer *); + +#define XHCI_INTR_ENDPT 1 + +struct usbd_bus_methods xhci_bus_methods = { + .open_pipe = xhci_pipe_open, + .soft_intr = xhci_softintr, + .do_poll = xhci_poll, + .allocx = xhci_allocx, + .freex = xhci_freex, +}; + +struct usbd_pipe_methods xhci_root_ctrl_methods = { + .transfer = xhci_root_ctrl_transfer, + .start = xhci_root_ctrl_start, + .abort = xhci_noop, + .close = xhci_pipe_close, + .done = xhci_noop, +}; + +struct usbd_pipe_methods xhci_root_intr_methods = { + .transfer = xhci_root_intr_transfer, + .start = xhci_root_intr_start, + .abort = xhci_root_intr_abort, + .close = xhci_pipe_close, + .done = xhci_root_intr_done, +}; + +struct usbd_pipe_methods xhci_device_ctrl_methods = { + .transfer = xhci_device_ctrl_transfer, + .start = xhci_device_ctrl_start, + .abort = xhci_device_ctrl_abort, + .close = xhci_pipe_close, + .done = xhci_noop, +}; + +#if notyet +struct usbd_pipe_methods xhci_device_isoc_methods = { +}; +#endif + +struct usbd_pipe_methods xhci_device_bulk_methods = { + .transfer = xhci_device_generic_transfer, + .start = xhci_device_generic_start, + .abort = xhci_device_generic_abort, + .close = xhci_pipe_close, + .done = xhci_device_generic_done, +}; + +struct usbd_pipe_methods xhci_device_generic_methods = { + .transfer = xhci_device_generic_transfer, + .start = xhci_device_generic_start, + .abort = xhci_device_generic_abort, + .close = xhci_pipe_close, + .done = xhci_device_generic_done, +}; + +#if XHCI_DEBUG +static void +xhci_dump_trb(struct xhci_trb *trb) +{ + printf("trb=%p (0x%016llx 0x%08x 0x%08x)\n", trb, + (long long)trb->trb_paddr, trb->trb_status, trb->trb_flags); +} +#endif + +int +xhci_init(struct xhci_softc *sc) +{ + uint64_t paddr; + uint32_t hcr; + int npage, error; + +#ifdef XHCI_DEBUG + uint16_t vers; + + vers = XREAD2(sc, XHCI_HCIVERSION); + printf("%s: xHCI version %x.%x\n", DEVNAME(sc), vers >> 8, vers & 0xff); +#endif + sc->sc_bus.usbrev = USBREV_3_0; + sc->sc_bus.methods = &xhci_bus_methods; + sc->sc_bus.pipe_size = sizeof(struct xhci_pipe); + + sc->sc_oper_off = XREAD1(sc, XHCI_CAPLENGTH); + sc->sc_door_off = XREAD4(sc, XHCI_DBOFF); + sc->sc_runt_off = XREAD4(sc, XHCI_RTSOFF); + +#ifdef XHCI_DEBUG + printf("%s: CAPLENGTH=0x%x\n", DEVNAME(sc), sc->sc_oper_off); + printf("%s: DOORBELL=0x%x\n", DEVNAME(sc), sc->sc_door_off); + printf("%s: RUNTIME=0x%x\n", DEVNAME(sc), sc->sc_runt_off); +#endif + + error = xhci_reset(sc); + if (error) + return (error); + + hcr = XREAD4(sc, XHCI_HCCPARAMS); + sc->sc_ctxsize = XHCI_HCC_CSZ(hcr) ? 64 : 32; + DPRINTF(("%s: %d bytes context\n", DEVNAME(sc), sc->sc_ctxsize)); + +#ifdef XHCI_DEBUG + hcr = XOREAD4(sc, XHCI_PAGESIZE); + printf("%s: supported page size 0x%08x\n", DEVNAME(sc), hcr); +#endif + /* Use 4K for the moment since it's easier. */ + sc->sc_pagesize = 4096; + + /* Get port and device slot numbers. */ + hcr = XREAD4(sc, XHCI_HCSPARAMS1); + sc->sc_noport = XHCI_HCS1_N_PORTS(hcr); + sc->sc_noslot = XHCI_HCS1_DEVSLOT_MAX(hcr); + DPRINTF(("%s: %d ports and %d slots\n", DEVNAME(sc), sc->sc_noport, + sc->sc_noslot)); + + /* Make sure to program a number of device slots we can handle. */ + if (sc->sc_noslot > USB_MAX_DEVICES) + sc->sc_noslot = USB_MAX_DEVICES; + hcr = XOREAD4(sc, XHCI_CONFIG) & ~XHCI_CONFIG_SLOTS_MASK; + XOWRITE4(sc, XHCI_CONFIG, hcr | sc->sc_noslot); + + /* + * Section 6.1 - Device Context Base Address Array + * shall be aligned to a 64 byte boundary. + */ + sc->sc_dcbaa.size = (sc->sc_noslot + 1) * sizeof(uint64_t); + error = usb_allocmem(&sc->sc_bus, sc->sc_dcbaa.size, 64, + &sc->sc_dcbaa.dma); + if (error) + return (ENOMEM); + sc->sc_dcbaa.segs = KERNADDR(&sc->sc_dcbaa.dma, 0); + memset(sc->sc_dcbaa.segs, 0, sc->sc_dcbaa.size); + usb_syncmem(&sc->sc_dcbaa.dma, 0, sc->sc_dcbaa.size, + BUS_DMASYNC_PREWRITE); + + /* Setup command ring. */ + error = xhci_ring_alloc(sc, &sc->sc_cmd_ring, XHCI_MAX_COMMANDS); + if (error) { + printf("%s: could not allocate command ring.\n", DEVNAME(sc)); + usb_freemem(&sc->sc_bus, &sc->sc_dcbaa.dma); + return (error); + } + + /* Setup one event ring and its segment table (ERST). */ + error = xhci_ring_alloc(sc, &sc->sc_evt_ring, XHCI_MAX_EVENTS); + if (error) { + printf("%s: could not allocate event ring.\n", DEVNAME(sc)); + xhci_ring_free(sc, &sc->sc_cmd_ring); + usb_freemem(&sc->sc_bus, &sc->sc_dcbaa.dma); + return (error); + } + + /* Allocate the required entry for the segment table. */ + sc->sc_erst.size = 1 * sizeof(struct xhci_erseg); + error = usb_allocmem(&sc->sc_bus, sc->sc_erst.size, 64, + &sc->sc_erst.dma); + if (error) { + printf("%s: could not allocate segment table.\n", DEVNAME(sc)); + xhci_ring_free(sc, &sc->sc_evt_ring); + xhci_ring_free(sc, &sc->sc_cmd_ring); + usb_freemem(&sc->sc_bus, &sc->sc_dcbaa.dma); + return (ENOMEM); + } + sc->sc_erst.segs = KERNADDR(&sc->sc_erst.dma, 0); + + /* Set our ring address and size in its corresponding segment. */ + sc->sc_erst.segs[0].er_addr = htole64(DMAADDR(&sc->sc_evt_ring.dma, 0)); + sc->sc_erst.segs[0].er_size = htole32(XHCI_MAX_EVENTS); + sc->sc_erst.segs[0].er_rsvd = 0; + usb_syncmem(&sc->sc_erst.dma, 0, sc->sc_erst.size, + BUS_DMASYNC_PREWRITE); + + /* Get the number of scratch pages and configure them if necessary. */ + hcr = XREAD4(sc, XHCI_HCSPARAMS2); + npage = XHCI_HCS2_SPB_MAX(hcr); + DPRINTF(("%s: %d scratch pages\n", DEVNAME(sc), npage)); + + if (npage > 0 && xhci_scratchpad_alloc(sc, npage)) { + printf("%s: could not allocate scratchpad.\n", DEVNAME(sc)); + usb_freemem(&sc->sc_bus, &sc->sc_erst.dma); + xhci_ring_free(sc, &sc->sc_evt_ring); + xhci_ring_free(sc, &sc->sc_cmd_ring); + usb_freemem(&sc->sc_bus, &sc->sc_dcbaa.dma); + return (ENOMEM); + } + + /* Set the device context base array address. */ + paddr = (uint64_t)DMAADDR(&sc->sc_dcbaa.dma, 0); + XOWRITE4(sc, XHCI_DCBAAP_LO, (uint32_t)paddr); + XOWRITE4(sc, XHCI_DCBAAP_HI, (uint32_t)(paddr >> 32)); + + DPRINTF(("%s: DCBAAP=%08lx%08lx\n", DEVNAME(sc), + XOREAD4(sc, XHCI_DCBAAP_HI), XOREAD4(sc, XHCI_DCBAAP_LO))); + + /* Set the command ring address. */ + paddr = (uint64_t)DMAADDR(&sc->sc_cmd_ring.dma, 0); + XOWRITE4(sc, XHCI_CRCR_LO, ((uint32_t)paddr) | XHCI_CRCR_LO_RCS); + XOWRITE4(sc, XHCI_CRCR_HI, (uint32_t)(paddr >> 32)); + + DPRINTF(("%s: CRCR=%08lx%08lx (%016llx)\n", DEVNAME(sc), + XOREAD4(sc, XHCI_CRCR_HI), XOREAD4(sc, XHCI_CRCR_LO), paddr)); + + /* Set the ERST count number to 1, since we use only one event ring. */ + XRWRITE4(sc, XHCI_ERSTSZ(0), XHCI_ERSTS_SET(1)); + + /* Set the segment table address. */ + paddr = (uint64_t)DMAADDR(&sc->sc_erst.dma, 0); + XRWRITE4(sc, XHCI_ERSTBA_LO(0), (uint32_t)paddr); + XRWRITE4(sc, XHCI_ERSTBA_HI(0), (uint32_t)(paddr >> 32)); + + DPRINTF(("%s: ERSTBA=%08lx%08lx\n", DEVNAME(sc), + XRREAD4(sc, XHCI_ERSTBA_HI(0)), XRREAD4(sc, XHCI_ERSTBA_LO(0)))); + + /* Set the ring dequeue address. */ + paddr = (uint64_t)DMAADDR(&sc->sc_evt_ring.dma, 0); + XRWRITE4(sc, XHCI_ERDP_LO(0), (uint32_t)paddr); + XRWRITE4(sc, XHCI_ERDP_HI(0), (uint32_t)(paddr >> 32)); + + DPRINTF(("%s: ERDP=%08lx%08lx\n", DEVNAME(sc), + XRREAD4(sc, XHCI_ERDP_HI(0)), XRREAD4(sc, XHCI_ERDP_LO(0)))); + + /* Enable interrupts. */ + hcr = XRREAD4(sc, XHCI_IMAN(0)); + XRWRITE4(sc, XHCI_IMAN(0), hcr | XHCI_IMAN_INTR_ENA); + + /* Set default interrupt moderation. */ + XRWRITE4(sc, XHCI_IMOD(0), XHCI_IMOD_DEFAULT); + + /* Allow event interrupt and start the controller. */ + XOWRITE4(sc, XHCI_USBCMD, XHCI_CMD_INTE|XHCI_CMD_RS); + + DPRINTF(("%s: USBCMD=%08lx\n", DEVNAME(sc), XOREAD4(sc, XHCI_USBCMD))); + DPRINTF(("%s: IMAN=%08lx\n", DEVNAME(sc), XRREAD4(sc, XHCI_IMAN(0)))); + + return (0); +} + +int +xhci_detach(struct xhci_softc *sc, int flags) +{ + int rv; + + if (sc->sc_child != NULL) + rv = config_detach(sc->sc_child, flags); + + if (rv != 0) { + printf("%s: error while detaching %d\n", DEVNAME(sc), rv); + return (rv); + } + + /* Since the hardware might already be gone, ignore the errors. */ + xhci_command_abort(sc); + + xhci_reset(sc); + + /* Disable interrupts. */ + XRWRITE4(sc, XHCI_IMOD(0), 0); + XRWRITE4(sc, XHCI_IMAN(0), 0); + + /* Clear the event ring address. */ + XRWRITE4(sc, XHCI_ERDP_LO(0), 0); + XRWRITE4(sc, XHCI_ERDP_HI(0), 0); + + XRWRITE4(sc, XHCI_ERSTBA_LO(0), 0); + XRWRITE4(sc, XHCI_ERSTBA_HI(0), 0); + + XRWRITE4(sc, XHCI_ERSTSZ(0), 0); + + /* Clear the command ring address. */ + XOWRITE4(sc, XHCI_CRCR_LO, 0); + XOWRITE4(sc, XHCI_CRCR_HI, 0); + + XOWRITE4(sc, XHCI_DCBAAP_LO, 0); + XOWRITE4(sc, XHCI_DCBAAP_HI, 0); + + if (sc->sc_spad.npage > 0) + xhci_scratchpad_free(sc); + + usb_freemem(&sc->sc_bus, &sc->sc_erst.dma); + xhci_ring_free(sc, &sc->sc_evt_ring); + xhci_ring_free(sc, &sc->sc_cmd_ring); + usb_freemem(&sc->sc_bus, &sc->sc_dcbaa.dma); + + return (0); +} + +int +xhci_activate(struct device *self, int act) +{ + struct xhci_softc *sc = (struct xhci_softc *)self; + int rv = 0; + + switch (act) { + case DVACT_DEACTIVATE: + if (sc->sc_child != NULL) + rv = config_deactivate(sc->sc_child); + sc->sc_bus.dying = 1; + break; + case DVACT_POWERDOWN: + rv = config_activate_children(self, act); + xhci_reset(sc); + break; + default: + rv = config_activate_children(self, act); + break; + } + + return (rv); +} + +int +xhci_reset(struct xhci_softc *sc) +{ + uint32_t hcr; + int i; + + XOWRITE4(sc, XHCI_USBCMD, 0); /* Halt controller */ + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = XOREAD4(sc, XHCI_USBSTS) & XHCI_STS_HCH; + if (hcr) + break; + } + + if (!hcr) + printf("%s: halt timeout\n", DEVNAME(sc)); + + XOWRITE4(sc, XHCI_USBCMD, XHCI_CMD_HCRST); + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = XOREAD4(sc, XHCI_USBCMD) & XHCI_STS_CNR; + if (!hcr) + break; + } + + if (hcr) { + printf("%s: reset timeout\n", DEVNAME(sc)); + return (EIO); + } + + return (0); +} + + +int +xhci_intr(void *v) +{ + struct xhci_softc *sc = v; + + if (sc == NULL || sc->sc_bus.dying) + return (0); + + /* If we get an interrupt while polling, then just ignore it. */ + if (sc->sc_bus.use_polling) { + DPRINTFN(16, ("xhci_intr: ignored interrupt while polling\n")); + return (0); + } + + return (xhci_intr1(sc)); +} + +int +xhci_intr1(struct xhci_softc *sc) +{ + uint32_t intrs; + + intrs = XOREAD4(sc, XHCI_USBSTS); + if (intrs == 0xffffffff) { + sc->sc_bus.dying = 1; + return (0); + } + + if ((intrs & XHCI_STS_EINT) == 0) + return (0); + + sc->sc_bus.intr_context++; + sc->sc_bus.no_intrs++; + + if (intrs & XHCI_STS_HSE) { + printf("%s: host system error\n", DEVNAME(sc)); + sc->sc_bus.dying = 1; + sc->sc_bus.intr_context--; + return (1); + } + + XOWRITE4(sc, XHCI_USBSTS, intrs); /* Acknowledge */ + if (intrs & XHCI_STS_EINT) + usb_schedsoftintr(&sc->sc_bus); + + /* Acknowledge PCI interrupt */ + intrs = XRREAD4(sc, XHCI_IMAN(0)); + XRWRITE4(sc, XHCI_IMAN(0), intrs | XHCI_IMAN_INTR_PEND); + + sc->sc_bus.intr_context--; + + return (1); +} + +void +xhci_poll(struct usbd_bus *bus) +{ + struct xhci_softc *sc = (struct xhci_softc *)bus; + + if (XOREAD4(sc, XHCI_USBSTS)) + xhci_intr1(sc); +} + +void +xhci_waitintr(struct xhci_softc *sc, struct usbd_xfer *xfer) +{ + DPRINTF(("%s: stub\n", __func__)); +} + +void +xhci_softintr(void *v) +{ + struct xhci_softc *sc = v; + + if (sc->sc_bus.dying) + return; + + sc->sc_bus.intr_context++; + xhci_event_dequeue(sc); + sc->sc_bus.intr_context--; +} + +void +xhci_event_dequeue(struct xhci_softc *sc) +{ + struct xhci_trb *trb; + uint64_t paddr; + uint32_t status, flags; + + while ((trb = xhci_ring_dequeue(sc, &sc->sc_evt_ring, 1)) != NULL) { + paddr = letoh64(trb->trb_paddr); + status = letoh32(trb->trb_status); + flags = letoh32(trb->trb_flags); + + switch (flags & XHCI_TRB_TYPE_MASK) { + case XHCI_EVT_XFER: + xhci_event_xfer(sc, paddr, status, flags); + break; + case XHCI_EVT_CMD_COMPLETE: + if (paddr == TRBADDR(sc->sc_cmd_ring, sc->sc_cmd_trb)) { + memcpy(&sc->sc_result_trb, trb, sizeof(*trb)); + wakeup(&sc->sc_cmd_trb); + } + break; + case XHCI_EVT_PORT_CHANGE: + xhci_event_port_change(sc, paddr, status); + break; + default: +#if XHCI_DEBUG + printf("event (%d): ", XHCI_TRB_TYPE(flags)); + xhci_dump_trb(trb); +#endif + break; + } + + } + + paddr = (uint64_t)DMAADDR(&sc->sc_evt_ring.dma, + sizeof(struct xhci_trb) * sc->sc_evt_ring.index); + XRWRITE4(sc, XHCI_ERDP_LO(0), ((uint32_t)paddr) | XHCI_ERDP_LO_BUSY); + XRWRITE4(sc, XHCI_ERDP_HI(0), (uint32_t)(paddr >> 32)); +} + +void +xhci_event_xfer(struct xhci_softc *sc, uint64_t paddr, uint32_t status, + uint32_t flags) +{ + struct xhci_pipe *xp; + struct usbd_xfer *xfer; + uint8_t dci, slot, code, remain; + int trb_idx; + + slot = XHCI_TRB_GET_SLOT(flags); + dci = XHCI_TRB_GET_EP(flags); + if (slot > sc->sc_noslot) + return; /* XXX */ + + xp = sc->sc_sdevs[slot].pipes[dci - 1]; + + code = XHCI_TRB_GET_CODE(status); + remain = XHCI_TRB_REMAIN(status); + + trb_idx = (paddr - DMAADDR(&xp->ring.dma, 0)) / sizeof(struct xhci_trb); + if (trb_idx < 0 || trb_idx >= xp->ring.ntrb) { + printf("%s: wrong trb index (%d) max is %d\n", DEVNAME(sc), + trb_idx, xp->ring.ntrb - 1); + return; + } + + xfer = xp->pending_xfers[trb_idx]; + if (xfer == NULL) { +#if 1 + DPRINTF(("%s: dev %d dci=%d paddr=0x%016llx idx=%d remain=%u" + " code=%u\n", DEVNAME(sc), slot, dci, (long long)paddr, + trb_idx, remain, code)); +#endif + printf("%s: NULL xfer pointer\n", DEVNAME(sc)); + return; + } + + xhci_xfer_done(xfer); + + switch (code) { + case XHCI_CODE_SUCCESS: + case XHCI_CODE_SHORT_XFER: + xfer->actlen = xfer->length - remain; + xfer->status = USBD_NORMAL_COMPLETION; + break; +#if 0 + case XHCI_CODE_STALL: + xfer->status = USBD_STALLED; + xp->halted = 1; + break; + case XHCI_CODE_BABBLE: + xfer->status = USBD_IOERROR; + xp->halted = 1; + /* FALLTHROUGH */ +#endif + default: +#if 1 + DPRINTF(("%s: dev %d dci=%d paddr=0x%016llx idx=%d remain=%u" + " code=%u\n", DEVNAME(sc), slot, dci, (long long)paddr, + trb_idx, remain, code)); +#endif + DPRINTF(("%s: unhandled code %d\n", DEVNAME(sc), code)); + xfer->status = USBD_IOERROR; + xp->halted = 1; + } + + usb_transfer_complete(xfer); +} + +void +xhci_event_port_change(struct xhci_softc *sc, uint64_t paddr, uint32_t status) +{ + struct usbd_xfer *xfer = sc->sc_intrxfer; + uint32_t port = XHCI_TRB_PORTID(paddr); + uint8_t *p; + + if (XHCI_TRB_GET_CODE(status) != XHCI_CODE_SUCCESS) { + DPRINTF(("failed port status event\n"));/* XXX can it happen? */ + return; + } + + if (xfer == NULL) + return; + + p = KERNADDR(&xfer->dmabuf, 0); + memset(p, 0, xfer->length); + + p[port/8] |= 1 << (port%8); + DPRINTF(("%s: port=%d change=0x%02x\n", DEVNAME(sc), port, *p)); + + xfer->actlen = xfer->length; + xfer->status = USBD_NORMAL_COMPLETION; + + usb_transfer_complete(xfer); +} + +void +xhci_xfer_done(struct usbd_xfer *xfer) +{ + struct xhci_pipe *xp = (struct xhci_pipe *)xfer->pipe; + struct xhci_xfer *xx = (struct xhci_xfer *)xfer; + int ntrb, i; + +#ifdef XHCI_DEBUG + if (xp->pending_xfers[xx->index] == NULL) { + printf("%s: xfer=%p already done (index=%d)\n", __func__, + xfer, xx->index); + return; + } +#endif + + for (ntrb = 0, i = xx->index; ntrb < xx->ntrb; ntrb++, i--) { + xp->pending_xfers[i] = NULL; + if (i == 0) + i = (xp->ring.ntrb - 1); + } + xp->free_trbs += xx->ntrb; + xx->index = -1; + xx->ntrb = 0; +} + +static inline uint8_t +xhci_ed2dci(usb_endpoint_descriptor_t *ed) +{ + uint8_t dir; + + if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_CONTROL) + return (UE_GET_ADDR(ed->bEndpointAddress) * 2 + 1); + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + dir = 1; + else + dir = 0; + + return (UE_GET_ADDR(ed->bEndpointAddress) * 2 + dir); +} + +usbd_status +xhci_pipe_open(struct usbd_pipe *pipe) +{ + struct xhci_softc *sc = (struct xhci_softc *)pipe->device->bus; + struct xhci_pipe *xp = (struct xhci_pipe *)pipe; + usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; + uint8_t slot = 0, xfertype = UE_GET_XFERTYPE(ed->bmAttributes); + struct usbd_device *hub; + uint32_t rhport = 0; + int error; + + KASSERT(xp->slot == 0); + +#if XHCI_DEBUG + struct usbd_device *dev = pipe->device; + printf("%s: pipe=%p addr=%d depth=%d port=%d speed=%d\n", __func__, + pipe, dev->address, dev->depth, dev->powersrc->portno, dev->speed); +#endif + + if (sc->sc_bus.dying) + return (USBD_IOERROR); + + /* Root Hub */ + if (pipe->device->depth == 0) { + switch (ed->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &xhci_root_ctrl_methods; + break; + case UE_DIR_IN | XHCI_INTR_ENDPT: + pipe->methods = &xhci_root_intr_methods; + break; + default: + pipe->methods = NULL; + DPRINTF(("%s: bad bEndpointAddress 0x%02x\n", __func__, + ed->bEndpointAddress)); + return (USBD_INVAL); + } + return (USBD_NORMAL_COMPLETION); + } + +#if 0 + /* Issue a noop to check if the command ring is correctly configured. */ + xhci_cmd_noop(sc); +#endif + + switch (xfertype) { + case UE_CONTROL: + pipe->methods = &xhci_device_ctrl_methods; + + /* Get a slot and init the device's contexts. */ + error = xhci_cmd_slot_control(sc, &slot, 1); + if (error || slot == 0 || slot > sc->sc_noslot) + return (USBD_INVAL); + + if (xhci_softdev_alloc(sc, slot)) + return (USBD_NOMEM); + + /* Get root hub port */ + for (hub = pipe->device; hub->myhub->depth; hub = hub->myhub) + ; + rhport = hub->powersrc->portno; + break; + case UE_ISOCHRONOUS: +#if notyet + pipe->methods = &xhci_device_isoc_methods; + break; +#else + DPRINTF(("%s: isochronous xfer not supported \n", __func__)); + return (USBD_INVAL); +#endif + case UE_BULK: + pipe->methods = &xhci_device_bulk_methods; + break; + case UE_INTERRUPT: + pipe->methods = &xhci_device_generic_methods; + break; + default: + DPRINTF(("%s: bad xfer type %d\n", __func__, xfertype)); + return (USBD_INVAL); + } + + /* XXX Section nb? */ + xp->dci = xhci_ed2dci(ed); + + if (slot != 0) + xp->slot = slot; + else + xp->slot = ((struct xhci_pipe *)pipe->device->default_pipe)->slot; + + if (xhci_pipe_init(sc, pipe, rhport)) + return (USBD_IOERROR); + + return (USBD_NORMAL_COMPLETION); +} + +static inline uint32_t +xhci_endpoint_txinfo(struct xhci_softc *sc, usb_endpoint_descriptor_t *ed) +{ + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + return (XHCI_EPCTX_AVG_TRB_LEN(8)); + case UE_BULK: + return (0); + case UE_INTERRUPT: + case UE_ISOCHRONOUS: + default: + break; + } + + DPRINTF(("%s: partial stub\n", __func__)); + + return (XHCI_EPCTX_MAX_ESIT_PAYLOAD(0) | XHCI_EPCTX_AVG_TRB_LEN(0)); +} + +int +xhci_pipe_init(struct xhci_softc *sc, struct usbd_pipe *pipe, uint32_t port) +{ + struct xhci_pipe *xp = (struct xhci_pipe *)pipe; + struct xhci_soft_dev *sdev = &sc->sc_sdevs[xp->slot]; + usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; + uint8_t xfertype = UE_GET_XFERTYPE(ed->bmAttributes); + uint8_t ival, speed, cerr = 0; + uint32_t mps; + int error; + + DPRINTF(("%s: dev %d dci %u (epAddr=0x%x)\n", DEVNAME(sc), xp->slot, + xp->dci, pipe->endpoint->edesc->bEndpointAddress)); + + if (xhci_ring_alloc(sc, &xp->ring, XHCI_MAX_TRANSFERS)) + return (ENOMEM); + + xp->free_trbs = xp->ring.ntrb; + sdev->pipes[xp->dci - 1] = xp; + + switch (pipe->device->speed) { + case USB_SPEED_LOW: + ival= 3; + speed = XHCI_SPEED_LOW; + mps = USB_MAX_IPACKET; + break; + case USB_SPEED_FULL: + ival = 3; + speed = XHCI_SPEED_FULL; + mps = 64; + break; + case USB_SPEED_HIGH: + ival = min(3, ed->bInterval); + speed = XHCI_SPEED_HIGH; + mps = 64; + break; + case USB_SPEED_SUPER: + ival = min(3, ed->bInterval); + speed = XHCI_SPEED_SUPER; + mps = 512; + break; + default: + return (EINVAL); + } + + /* XXX Until we fix wMaxPacketSize for ctrl ep depending on the speed */ + mps = max(mps, UGETW(ed->wMaxPacketSize)); + + if (pipe->interval != USBD_DEFAULT_INTERVAL) + ival = min(ival, pipe->interval); + + DPRINTF(("%s: speed %d mps %d rhport %d\n", DEVNAME(sc), speed, mps, + port)); + + /* Setup the endpoint context */ + if (xfertype != UE_ISOCHRONOUS) + cerr = 3; + + if (xfertype == UE_CONTROL || xfertype == UE_BULK) + ival = 0; + + if ((ed->bEndpointAddress & UE_DIR_IN) || (xfertype == UE_CONTROL)) + xfertype |= 0x4; + + sdev->ep_ctx[xp->dci-1]->info_lo = htole32(XHCI_EPCTX_SET_IVAL(ival)); + sdev->ep_ctx[xp->dci-1]->info_hi = htole32( + XHCI_EPCTX_SET_MPS(mps) | XHCI_EPCTX_SET_EPTYPE(xfertype) | + XHCI_EPCTX_SET_CERR(cerr) | XHCI_EPCTX_SET_MAXB(0) + ); + sdev->ep_ctx[xp->dci-1]->txinfo = htole32(xhci_endpoint_txinfo(sc, ed)); + sdev->ep_ctx[xp->dci-1]->deqp = htole64( + DMAADDR(&xp->ring.dma, 0) | XHCI_EPCTX_DCS + ); + + /* Unmask the new endoint */ + sdev->input_ctx->drop_flags = 0; + sdev->input_ctx->add_flags = htole32(XHCI_INCTX_MASK_DCI(xp->dci)); + + /* Setup the slot context */ + sdev->slot_ctx->info_lo = htole32(XHCI_SCTX_SET_DCI(xp->dci)); + sdev->slot_ctx->info_hi = 0; + sdev->slot_ctx->tt = 0; + sdev->slot_ctx->state = 0; + + if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_CONTROL) { + sdev->slot_ctx->info_lo |= htole32(XHCI_SCTX_SET_SPEED(speed)); + sdev->slot_ctx->info_hi |= htole32(XHCI_SCTX_SET_RHPORT(port)); + } + + usb_syncmem(&sdev->ictx_dma, 0, sc->sc_pagesize, BUS_DMASYNC_PREWRITE); + + if (xp->dci == 1) { + error = xhci_cmd_address_device(sc, xp->slot, + DMAADDR(&sdev->ictx_dma, 0), 1 /* XXX see below */); + if (error) + return (error); + /* + * XXX Set the address. This is ugly and is not + * adapted to our stack. But the whole idea of + * reopening the default pipes should be revisited + * anyway... + */ +#if 1 + struct usbd_device *dev = pipe->device; + struct xhci_sctx *slot; + uint8_t addr; + + usb_syncmem(&sdev->octx_dma, 0, sc->sc_pagesize, + BUS_DMASYNC_POSTREAD); + + /* Get output slot context. */ + slot = KERNADDR(&sdev->octx_dma, 0); + addr = XHCI_SCTX_DEV_ADDR(letoh32(slot->state)); + if (addr == 0) + return (EINVAL); + + DPRINTF(("%s: dev %d new addr %d (old %d)\n", DEVNAME(sc), + xp->slot, addr, dev->address)); + + dev->bus->devices[dev->address] = 0; + dev->bus->devices[addr] = dev; + dev->address = addr; +#endif + } else { + error = xhci_cmd_configure_endpoint(sc, xp->slot, + DMAADDR(&sdev->ictx_dma, 0)); + if (error) { + xhci_ring_free(sc, &xp->ring); + return (EIO); + } + } + + usb_syncmem(&sdev->octx_dma, 0, sc->sc_pagesize, BUS_DMASYNC_POSTREAD); + + return (0); +} + +void +xhci_pipe_close(struct usbd_pipe *pipe) +{ + struct xhci_softc *sc = (struct xhci_softc *)pipe->device->bus; + struct xhci_pipe *xp = (struct xhci_pipe *)pipe; + struct xhci_soft_dev *sdev = &sc->sc_sdevs[xp->slot]; + + /* Root Hub */ + if (pipe->device->depth == 0) + return; + + /* XXX this is wrong */ + if (!xp->halted || xhci_cmd_stop_endpoint(sc, xp->slot, xp->dci)) { + DPRINTF(("%s: unable to stop pipe=%p dci=%d\n", DEVNAME(sc), + pipe, xp->dci)); + return; + } + + /* Mask the endpoint */ + sdev->input_ctx->drop_flags |= htole32(XHCI_INCTX_MASK_DCI(xp->dci)); + sdev->input_ctx->add_flags &= ~htole32(XHCI_INCTX_MASK_DCI(xp->dci)); + + /* Clear the endpoint context */ + memset(&sdev->ep_ctx[xp->dci-1], 0, sizeof(struct xhci_epctx)); + + usb_syncmem(&sdev->ictx_dma, 0, sc->sc_pagesize, BUS_DMASYNC_PREWRITE); + + xhci_ring_free(sc, &xp->ring); +} + +struct usbd_xfer * +xhci_allocx(struct usbd_bus *bus) +{ + struct xhci_softc *sc = (struct xhci_softc *)bus; + struct usbd_xfer *xfer; + + xfer = SIMPLEQ_FIRST(&sc->sc_free_xfers); + if (xfer != NULL) { + SIMPLEQ_REMOVE_HEAD(&sc->sc_free_xfers, next); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_FREE) + printf("%s: xfer=%p not free, 0x%08x\n", __func__, + xfer, xfer->busy_free); +#endif + } else + xfer = malloc(sizeof(struct xhci_xfer), M_USB, M_NOWAIT); + + if (xfer != NULL) { + memset(xfer, 0, sizeof(struct xhci_xfer)); +#ifdef DIAGNOSTIC + xfer->busy_free = XFER_BUSY; +#endif + } + return (xfer); +} + +void +xhci_freex(struct usbd_bus *bus, struct usbd_xfer *xfer) +{ + struct xhci_softc *sc = (struct xhci_softc *)bus; + +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("xhci_freex: xfer=%p not busy, 0x%08x\n", xfer, + xfer->busy_free); + return; + } + xfer->busy_free = XFER_FREE; +#endif + + SIMPLEQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); +} + +int +xhci_scratchpad_alloc(struct xhci_softc *sc, int npage) +{ + uint64_t *pte; + int error, i; + + /* Allocate the required entry for the table. */ + error = usb_allocmem(&sc->sc_bus, npage * sizeof(uint64_t), 64, + &sc->sc_spad.table_dma); + if (error) + return (ENOMEM); + pte = KERNADDR(&sc->sc_spad.table_dma, 0); + + /* Alloccate space for the pages. */ + error = usb_allocmem(&sc->sc_bus, npage * sc->sc_pagesize, + sc->sc_pagesize, &sc->sc_spad.pages_dma); + if (error) { + usb_freemem(&sc->sc_bus, &sc->sc_spad.table_dma); + return (ENOMEM); + } + memset(KERNADDR(&sc->sc_spad.pages_dma, 0), 0, npage * sc->sc_pagesize); + usb_syncmem(&sc->sc_spad.pages_dma, 0, npage * sc->sc_pagesize, + BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); + + for (i = 0; i < npage; i++) { + pte[i] = htole64( + DMAADDR(&sc->sc_spad.pages_dma, i * sc->sc_pagesize) + ); + } + usb_syncmem(&sc->sc_spad.table_dma, 0, npage * sizeof(uint64_t), + BUS_DMASYNC_PREWRITE); + + /* Entry 0 points to the table of scratchpad pointers. */ + sc->sc_dcbaa.segs[0] = htole64(DMAADDR(&sc->sc_spad.table_dma, 0)); + usb_syncmem(&sc->sc_dcbaa.dma, 0, sizeof(uint64_t), + BUS_DMASYNC_PREWRITE); + + sc->sc_spad.npage = npage; + + return (0); +} + +void +xhci_scratchpad_free(struct xhci_softc *sc) +{ + sc->sc_dcbaa.segs[0] = 0; + usb_syncmem(&sc->sc_dcbaa.dma, 0, sizeof(uint64_t), + BUS_DMASYNC_PREWRITE); + + usb_freemem(&sc->sc_bus, &sc->sc_spad.pages_dma); + usb_freemem(&sc->sc_bus, &sc->sc_spad.table_dma); +} + + +int +xhci_ring_alloc(struct xhci_softc *sc, struct xhci_ring *ring, size_t ntrb) +{ + size_t size; + + size = ntrb * sizeof(struct xhci_trb); + + if (usb_allocmem(&sc->sc_bus, size, 16, &ring->dma) != 0) + return (ENOMEM); + + ring->trbs = KERNADDR(&ring->dma, 0); + ring->ntrb = ntrb; + + memset(ring->trbs, 0, size); + + ring->index = 0; + ring->toggle = XHCI_TRB_CYCLE; + + /* + * Since all our rings use only one segment, at least for + * the moment, link their tail to their head. + */ + if (ring != &sc->sc_evt_ring) { + ring->trbs[ntrb - 1].trb_paddr = htole64(DMAADDR(&ring->dma, 0)); + ring->trbs[ntrb - 1].trb_flags = htole32( + XHCI_TRB_TYPE_LINK | XHCI_TRB_LINKSEG + ); + } + usb_syncmem(&ring->dma, 0, size, BUS_DMASYNC_PREWRITE); + + return (0); +} + +void +xhci_ring_free(struct xhci_softc *sc, struct xhci_ring *ring) +{ + usb_freemem(&sc->sc_bus, &ring->dma); +} + +void +xhci_ring_reset(struct xhci_softc *sc, struct xhci_ring *ring) +{ + size_t size; + + /* Don't touch the link TRB, hence the -1. */ + size = (ring->ntrb - 1) * sizeof(struct xhci_trb); + memset(ring->trbs, 0, size); + + ring->index = 0; + ring->toggle = XHCI_TRB_CYCLE; + + usb_syncmem(&ring->dma, 0, size, BUS_DMASYNC_PREWRITE); +} + +struct xhci_trb* +xhci_ring_dequeue(struct xhci_softc *sc, struct xhci_ring *ring, int cons) +{ + struct xhci_trb *trb; + uint32_t idx = ring->index; + + KASSERT(idx < ring->ntrb); + + usb_syncmem(&ring->dma, idx * sizeof(struct xhci_trb), + sizeof(struct xhci_trb), BUS_DMASYNC_POSTREAD); + + trb = &ring->trbs[idx]; + + /* Make sure this TRB can be consumed. */ + if (cons && ring->toggle != (letoh32(trb->trb_flags) & XHCI_TRB_CYCLE)) + return (NULL); + idx++; + + if (idx < (ring->ntrb - 1)) { + ring->index = idx; + } else { + if (ring->toggle) + ring->trbs[idx].trb_flags |= htole32(XHCI_TRB_CYCLE); + else + ring->trbs[idx].trb_flags &= ~htole32(XHCI_TRB_CYCLE); + + usb_syncmem(&ring->dma, sizeof(struct xhci_trb) * idx, + sizeof(struct xhci_trb), BUS_DMASYNC_PREWRITE); + + ring->index = 0; + ring->toggle ^= XHCI_TRB_CYCLE; + } + + return (trb); +} + +struct xhci_trb * +xhci_xfer_get_trb(struct xhci_softc *sc, struct usbd_xfer* xfer, + uint8_t *togglep, int last) +{ + struct xhci_pipe *xp = (struct xhci_pipe *)xfer->pipe; + struct xhci_xfer *xx = (struct xhci_xfer *)xfer; + + KASSERT(xp->free_trbs >= 1); + + /* Associate this TRB to our xfer. */ + xp->pending_xfers[xp->ring.index] = xfer; + xp->free_trbs--; + + xx->index = (last) ? xp->ring.index : -1; + xx->ntrb += 1; + + *togglep = xp->ring.toggle; + return (xhci_ring_dequeue(sc, &xp->ring, 0)); +} + +int +xhci_command_submit(struct xhci_softc *sc, struct xhci_trb *trb0, int timeout) +{ + struct xhci_trb *trb; + int error = 0; + + KASSERT(sc->sc_cmd_trb == NULL); + + trb0->trb_flags |= htole32(sc->sc_cmd_ring.toggle); + + trb = xhci_ring_dequeue(sc, &sc->sc_cmd_ring, 0); + memcpy(trb, trb0, sizeof(struct xhci_trb)); + usb_syncmem(&sc->sc_cmd_ring.dma, TRBOFF(sc->sc_cmd_ring, trb), + sizeof(struct xhci_trb), BUS_DMASYNC_PREWRITE); + + sc->sc_cmd_trb = trb; + + XDWRITE4(sc, XHCI_DOORBELL(0), 0); + error = tsleep(&sc->sc_cmd_trb, PZERO, "xhcicmd", + (timeout*hz+999)/ 1000 + 1); + if (error) { +#if XHCI_DEBUG + printf("cmd = %d " ,XHCI_TRB_TYPE(letoh32(trb->trb_flags))); + xhci_dump_trb(trb); +#endif +#ifdef DIAGNOSTIC + printf("%s: tsleep() = %d\n", __func__, error); +#endif + goto timedout; + } + + memcpy(trb0, &sc->sc_result_trb, sizeof(struct xhci_trb)); + + if (XHCI_TRB_GET_CODE(letoh32(trb0->trb_status)) != XHCI_CODE_SUCCESS) { + printf("%s: event error code=%d\n", DEVNAME(sc), + XHCI_TRB_GET_CODE(letoh32(trb0->trb_status))); + error = EIO; + } + +timedout: +#if XHCI_DEBUG + if (error) { + printf("result = %d ", XHCI_TRB_TYPE(letoh32(trb0->trb_flags))); + xhci_dump_trb(trb0); + } +#endif + sc->sc_cmd_trb = NULL; + return (error); +} + +int +xhci_command_abort(struct xhci_softc *sc) +{ + uint32_t reg; + int i; + + reg = XOREAD4(sc, XHCI_CRCR_LO); + if ((reg & XHCI_CRCR_LO_CRR) == 0) + return (0); + + XOWRITE4(sc, XHCI_CRCR_LO, reg | XHCI_CRCR_LO_CA); + XOWRITE4(sc, XHCI_CRCR_HI, 0); + + for (i = 0; i < 250; i++) { + usb_delay_ms(&sc->sc_bus, 1); + reg = XOREAD4(sc, XHCI_CRCR_LO) & XHCI_CRCR_LO_CRR; + if (!reg) + break; + } + + if (reg) { + printf("%s: command ring abort timeout\n", DEVNAME(sc)); + return (1); + } + + return (0); +} + +int +xhci_cmd_configure_endpoint(struct xhci_softc *sc, uint8_t slot, uint64_t addr) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = htole64(addr); + trb.trb_status = 0; + trb.trb_flags = htole32( + XHCI_TRB_SET_SLOT(slot) | XHCI_CMD_CONFIG_EP + ); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} + +int +xhci_cmd_stop_endpoint(struct xhci_softc *sc, uint8_t slot, uint8_t dci) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = 0; + trb.trb_status = 0; + trb.trb_flags = htole32( + XHCI_TRB_SET_SLOT(slot) | XHCI_TRB_SET_EP(dci) | XHCI_CMD_STOP_EP + ); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} + +int +xhci_cmd_reset_endpoint(struct xhci_softc *sc, uint8_t slot, uint8_t dci) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = 0; + trb.trb_status = 0; + trb.trb_flags = htole32( + XHCI_TRB_SET_SLOT(slot) | XHCI_TRB_SET_EP(dci) | XHCI_CMD_RESET_EP + ); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} + +int +xhci_cmd_set_tr_deq(struct xhci_softc *sc, uint8_t slot, uint8_t dci, + uint64_t addr) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = htole64(addr); + trb.trb_status = 0; + trb.trb_flags = htole32( + XHCI_TRB_SET_SLOT(slot) | XHCI_TRB_SET_EP(dci) | XHCI_CMD_SET_TR_DEQ + ); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} + +int +xhci_cmd_slot_control(struct xhci_softc *sc, uint8_t *slotp, int enable) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = 0; + trb.trb_status = 0; + trb.trb_flags = htole32( + enable ? XHCI_CMD_ENABLE_SLOT : XHCI_CMD_DISABLE_SLOT + ); + + if (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)) + return (EIO); + + *slotp = XHCI_TRB_GET_SLOT(letoh32(trb.trb_flags)); + + return (0); +} + +int +xhci_cmd_address_device(struct xhci_softc *sc, uint8_t slot, uint64_t addr, + int set_address) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = htole64(addr); + trb.trb_status = 0; + trb.trb_flags = htole32( + XHCI_TRB_SET_SLOT(slot) | (set_address ? 0 : XHCI_TRB_BSR) | + XHCI_CMD_ADDRESS_DEVICE + ); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} + +int +xhci_cmd_evaluate_ctx(struct xhci_softc *sc, uint8_t slot, uint64_t addr) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = htole64(addr); + trb.trb_status = 0; + trb.trb_flags = htole32( + XHCI_TRB_SET_SLOT(slot) | XHCI_CMD_EVAL_CTX + ); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} + +#ifdef XHCI_DEBUG +int +xhci_cmd_noop(struct xhci_softc *sc) +{ + struct xhci_trb trb; + + DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + + trb.trb_paddr = 0; + trb.trb_status = 0; + trb.trb_flags = htole32(XHCI_CMD_NOOP); + + return (xhci_command_submit(sc, &trb, XHCI_COMMAND_TIMEOUT)); +} +#endif + +int +xhci_softdev_alloc(struct xhci_softc *sc, uint8_t slot) +{ + struct xhci_soft_dev *sdev = &sc->sc_sdevs[slot]; + int i, error; + + /* + * Setup input context. Even with 64 byte context size, it + * fits into the smallest supported page size, so use that. + */ + error = usb_allocmem(&sc->sc_bus, sc->sc_pagesize, sc->sc_pagesize, + &sdev->ictx_dma); + if (error) + return (ENOMEM); + memset(KERNADDR(&sdev->ictx_dma, 0), 0, sc->sc_pagesize); + + sdev->input_ctx = KERNADDR(&sdev->ictx_dma, 0); + sdev->slot_ctx = KERNADDR(&sdev->ictx_dma, sc->sc_ctxsize); + for (i = 0; i < 31; i++) + sdev->ep_ctx[i] = + KERNADDR(&sdev->ictx_dma, (i + 2) * sc->sc_ctxsize); + + DPRINTF(("%s: dev %d, input=%p slot=%p ep0=%p\n", DEVNAME(sc), + slot, sdev->input_ctx, sdev->slot_ctx, sdev->ep_ctx[0])); + + /* Setup output context */ + error = usb_allocmem(&sc->sc_bus, sc->sc_pagesize, sc->sc_pagesize, + &sdev->octx_dma); + if (error) { + usb_freemem(&sc->sc_bus, &sdev->ictx_dma); + return (ENOMEM); + } + memset(KERNADDR(&sdev->octx_dma, 0), 0, sc->sc_pagesize); + + memset(&sdev->pipes, 0, sizeof(sdev->pipes)); + + DPRINTF(("%s: dev %d, setting DCBAA to 0x%016llx\n", DEVNAME(sc), + slot, (long long)DMAADDR(&sdev->octx_dma, 0))); + + sc->sc_dcbaa.segs[slot] = htole64(DMAADDR(&sdev->octx_dma, 0)); + usb_syncmem(&sc->sc_dcbaa.dma, slot * sizeof(uint64_t), + sizeof(uint64_t), BUS_DMASYNC_PREWRITE); + + return (0); +} + +void +xhci_softdev_free(struct xhci_softc *sc, uint8_t slot) +{ + struct xhci_soft_dev *sdev = &sc->sc_sdevs[slot]; + int i; + + sc->sc_dcbaa.segs[slot] = 0; + usb_syncmem(&sc->sc_dcbaa.dma, slot * sizeof(uint64_t), + sizeof(uint64_t), BUS_DMASYNC_PREWRITE); + + for (i = 0; i < 31; i++) { + if (sdev->pipes[i] != NULL) + xhci_ring_free(sc, &sdev->pipes[i]->ring); + } + + usb_freemem(&sc->sc_bus, &sdev->octx_dma); + usb_freemem(&sc->sc_bus, &sdev->ictx_dma); + + memset(sdev, 0, sizeof(struct xhci_soft_dev)); +} + +/* Root hub descriptors. */ +const usb_device_descriptor_t xhci_devd = { + USB_DEVICE_DESCRIPTOR_SIZE, + UDESC_DEVICE, /* type */ + {0x00, 0x03}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_HSHUBSTT, /* protocol */ + 9, /* max packet */ + {0},{0},{0x00,0x01}, /* device id */ + 1,2,0, /* string indexes */ + 1 /* # of configurations */ +}; + +const usb_config_descriptor_t xhci_confd = { + USB_CONFIG_DESCRIPTOR_SIZE, + UDESC_CONFIG, + {USB_CONFIG_DESCRIPTOR_SIZE + + USB_INTERFACE_DESCRIPTOR_SIZE + + USB_ENDPOINT_DESCRIPTOR_SIZE}, + 1, + 1, + 0, + UC_SELF_POWERED, + 0 /* max power */ +}; + +const usb_interface_descriptor_t xhci_ifcd = { + USB_INTERFACE_DESCRIPTOR_SIZE, + UDESC_INTERFACE, + 0, + 0, + 1, + UICLASS_HUB, + UISUBCLASS_HUB, + UIPROTO_HSHUBSTT, /* XXX */ + 0 +}; + +const usb_endpoint_descriptor_t xhci_endpd = { + USB_ENDPOINT_DESCRIPTOR_SIZE, + UDESC_ENDPOINT, + UE_DIR_IN | XHCI_INTR_ENDPT, + UE_INTERRUPT, + {2, 0}, /* max 15 ports */ + 255 +}; + +const usb_endpoint_ss_comp_descriptor_t xhci_endpcd = { + USB_ENDPOINT_SS_COMP_DESCRIPTOR_SIZE, + UDESC_ENDPOINT_SS_COMP, + 0, + 0, + {0, 0} /* XXX */ +}; + +const usb_hub_descriptor_t xhci_hubd = { + USB_HUB_DESCRIPTOR_SIZE, + UDESC_SS_HUB, + 0, + {0,0}, + 0, + 0, + {0}, +}; + +void +xhci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) +{ + int s; + + DPRINTF(("%s: partial stub\n", __func__)); + + xhci_xfer_done(xfer); + + xfer->status = status; + timeout_del(&xfer->timeout_handle); + usb_rem_task(xfer->device, &xfer->abort_task); + + s = splusb(); + usb_transfer_complete(xfer); + splx(s); +} + +void +xhci_timeout(void *addr) +{ + struct usbd_xfer *xfer = addr; + struct xhci_softc *sc = (struct xhci_softc *)xfer->device->bus; + + if (sc->sc_bus.dying) { + xhci_timeout_task(addr); + return; + } + + usb_init_task(&xfer->abort_task, xhci_timeout_task, addr, + USB_TASK_TYPE_ABORT); + usb_add_task(xfer->device, &xfer->abort_task); +} + +void +xhci_timeout_task(void *addr) +{ + struct usbd_xfer *xfer = addr; + + DPRINTF(("%s: xfer=%p\n", __func__, xfer)); + + xhci_abort_xfer(xfer, USBD_TIMEOUT); +} + +usbd_status +xhci_root_ctrl_transfer(struct usbd_xfer *xfer) +{ + usbd_status err; + + err = usb_insert_transfer(xfer); + if (err) + return (err); + + return (xhci_root_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +xhci_root_ctrl_start(struct usbd_xfer *xfer) +{ + struct xhci_softc *sc = (struct xhci_softc *)xfer->device->bus; + usb_port_status_t ps; + usb_device_request_t *req; + void *buf = NULL; + usb_hub_descriptor_t hubd; + usbd_status err; + int s, len, value, index; + int l, totlen = 0; + int port, i; + uint32_t v; + + KASSERT(xfer->rqflags & URQ_REQUEST); + + if (sc->sc_bus.dying) + return (USBD_IOERROR); + + req = &xfer->request; + + DPRINTFN(4,("%s: type=0x%02x request=%02x\n", __func__, + req->bmRequestType, req->bRequest)); + + len = UGETW(req->wLength); + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + if (len != 0) + buf = KERNADDR(&xfer->dmabuf, 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) { + *(uint8_t *)buf = sc->sc_conf; + totlen = 1; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + DPRINTFN(8,("xhci_root_ctrl_start: wValue=0x%04x\n", value)); + switch(value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); + USETW(xhci_devd.idVendor, sc->sc_id_vendor); + memcpy(buf, &xhci_devd, l); + break; + /* + * We can't really operate at another speed, but the spec says + * we need this descriptor. + */ + case UDESC_OTHER_SPEED_CONFIGURATION: + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); + memcpy(buf, &xhci_confd, l); + ((usb_config_descriptor_t *)buf)->bDescriptorType = + value >> 8; + buf = (char *)buf + l; + len -= l; + l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &xhci_ifcd, l); + buf = (char *)buf + l; + len -= l; + l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &xhci_endpd, l); + break; + case UDESC_STRING: + if (len == 0) + break; + *(u_int8_t *)buf = 0; + totlen = 1; + switch (value & 0xff) { + case 0: /* Language table */ + totlen = usbd_str(buf, len, "\001"); + break; + case 1: /* Vendor */ + totlen = usbd_str(buf, len, sc->sc_vendor); + break; + case 2: /* Product */ + totlen = usbd_str(buf, len, "XHCI root hub"); + break; + } + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + if (len > 0) { + *(uint8_t *)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): + if (value >= USB_MAX_DEVICES) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if (value != 0 && value != 1) { + err = USBD_IOERROR; + goto ret; + } + 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 ret; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(8, ("xhci_root_ctrl_start: UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", index, value)); + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + port = XHCI_PORTSC(index); + v = XOREAD4(sc, port) & ~XHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + XOWRITE4(sc, port, v | XHCI_PS_PED); + break; + case UHF_PORT_SUSPEND: + /* TODO */ + break; + case UHF_PORT_POWER: + XOWRITE4(sc, port, v & ~XHCI_PS_PP); + break; + case UHF_PORT_INDICATOR: + XOWRITE4(sc, port, v & ~XHCI_PS_SET_PIC(3)); + break; + case UHF_C_PORT_CONNECTION: + XOWRITE4(sc, port, v | XHCI_PS_CSC); + break; + case UHF_C_PORT_ENABLE: + XOWRITE4(sc, port, v | XHCI_PS_PEC); + break; + case UHF_C_PORT_SUSPEND: + XOWRITE4(sc, port, v | XHCI_PS_PLC); + break; + case UHF_C_PORT_OVER_CURRENT: + XOWRITE4(sc, port, v | XHCI_PS_OCC); + break; + case UHF_C_PORT_RESET: + XOWRITE4(sc, port, v | XHCI_PS_PRC); + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if (len == 0) + break; + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + v = XREAD4(sc, XHCI_HCCPARAMS); + hubd = xhci_hubd; + hubd.bNbrPorts = sc->sc_noport; + USETW(hubd.wHubCharacteristics, + (XHCI_HCC_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_GANGED) | + (XHCI_HCC_PIND(v) ? UHD_PORT_IND : 0)); + hubd.bPwrOn2PwrGood = 10; /* xHCI section 5.4.9 */ + for (i = 1; i <= sc->sc_noport; i++) { + v = XOREAD4(sc, XHCI_PORTSC(i)); + if (v & XHCI_PS_DR) + hubd.DeviceRemovable[i / 8] |= 1U << (i % 8); + } + hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE + i; + l = min(len, hubd.bDescLength); + totlen = l; + memcpy(buf, &hubd, l); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + if (len != 16) { + err = USBD_IOERROR; + goto ret; + } + memset(buf, 0, len); + totlen = len; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(8,("xhci_root_ctrl_start: get port status i=%d\n", + index)); + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + v = XOREAD4(sc, XHCI_PORTSC(index)); + DPRINTFN(8,("xhci_root_ctrl_start: port status=0x%04x\n", v)); + switch (XHCI_PS_SPEED(v)) { + case XHCI_SPEED_FULL: + i = UPS_FULL_SPEED; + break; + case XHCI_SPEED_LOW: + i = UPS_LOW_SPEED; + break; + case XHCI_SPEED_HIGH: + i = UPS_HIGH_SPEED; + break; + case XHCI_SPEED_SUPER: + default: + i = UPS_SUPER_SPEED; + break; + } + if (v & XHCI_PS_CCS) i |= UPS_CURRENT_CONNECT_STATUS; + if (v & XHCI_PS_PED) i |= UPS_PORT_ENABLED; + if (v & XHCI_PS_OCA) i |= UPS_OVERCURRENT_INDICATOR; + if (v & XHCI_PS_PR) i |= UPS_RESET; + if (v & XHCI_PS_PP) i |= UPS_PORT_POWER; + USETW(ps.wPortStatus, i); + i = 0; + if (v & XHCI_PS_CSC) i |= UPS_C_CONNECT_STATUS; + if (v & XHCI_PS_PEC) i |= UPS_C_PORT_ENABLED; + if (v & XHCI_PS_OCC) i |= UPS_C_OVERCURRENT_INDICATOR; + if (v & XHCI_PS_PRC) i |= UPS_C_PORT_RESET; + USETW(ps.wPortChange, i); + l = min(len, sizeof ps); + memcpy(buf, &ps, l); + totlen = l; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + + i = index >> 8; + index &= 0x00ff; + + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + port = XHCI_PORTSC(index); + v = XOREAD4(sc, port) & ~XHCI_PS_CLEAR; + + switch (value) { + case UHF_PORT_ENABLE: + XOWRITE4(sc, port, v | XHCI_PS_PED); + break; + case UHF_PORT_SUSPEND: + DPRINTFN(6, ("suspend port %u (LPM=%u)\n", index, i)); + if (XHCI_PS_SPEED(v) == XHCI_SPEED_SUPER) { + err = USBD_IOERROR; + goto ret; + } + XOWRITE4(sc, port, v | + XHCI_PS_SET_PLS(i ? 2 /* LPM */ : 3) | XHCI_PS_LWS); + break; + case UHF_PORT_RESET: + DPRINTFN(6, ("reset port %d\n", index)); + XOWRITE4(sc, port, v | XHCI_PS_PR); + break; + case UHF_PORT_POWER: + DPRINTFN(3, ("set port power %d\n", index)); + XOWRITE4(sc, port, v | XHCI_PS_PP); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(3, ("set port indicator %d\n", index)); + + v &= ~XHCI_PS_SET_PIC(3); + v |= XHCI_PS_SET_PIC(1); + + XOWRITE4(sc, port, v); + break; + case UHF_C_PORT_RESET: + XOWRITE4(sc, port, v | XHCI_PS_PRC); + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): + case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): + case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): + case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): + break; + default: + err = USBD_IOERROR; + goto ret; + } + xfer->actlen = totlen; + err = USBD_NORMAL_COMPLETION; +ret: + xfer->status = err; + s = splusb(); + usb_transfer_complete(xfer); + splx(s); + return (USBD_IN_PROGRESS); +} + + +void +xhci_noop(struct usbd_xfer *xfer) +{ +} + + +usbd_status +xhci_root_intr_transfer(struct usbd_xfer *xfer) +{ + usbd_status err; + + err = usb_insert_transfer(xfer); + if (err) + return (err); + + return (xhci_root_intr_start(SIMPLEQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +xhci_root_intr_start(struct usbd_xfer *xfer) +{ + struct xhci_softc *sc = (struct xhci_softc *)xfer->device->bus; + + if (sc->sc_bus.dying) + return (USBD_IOERROR); + + sc->sc_intrxfer = xfer; + + return (USBD_IN_PROGRESS); +} + +void +xhci_root_intr_abort(struct usbd_xfer *xfer) +{ + int s; + + xfer->status = USBD_CANCELLED; + + s = splusb(); + usb_transfer_complete(xfer); + splx(s); +} + +void +xhci_root_intr_done(struct usbd_xfer *xfer) +{ + struct xhci_softc *sc = (struct xhci_softc *)xfer->device->bus; + + KASSERT(sc->sc_intrxfer == xfer); + + if (!xfer->pipe->repeat) + sc->sc_intrxfer = NULL; +} + +usbd_status +xhci_device_ctrl_transfer(struct usbd_xfer *xfer) +{ + usbd_status err; + + err = usb_insert_transfer(xfer); + if (err) + return (err); + + return (xhci_device_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +xhci_device_ctrl_start(struct usbd_xfer *xfer) +{ + struct xhci_softc *sc = (struct xhci_softc *)xfer->device->bus; + struct xhci_pipe *xp = (struct xhci_pipe *)xfer->pipe; + struct xhci_trb *trb0, *trb; + uint32_t len = UGETW(xfer->request.wLength); + uint8_t toggle0, toggle; + + KASSERT(xfer->rqflags & URQ_REQUEST); + + if (sc->sc_bus.dying) + return (USBD_IOERROR); + + if (xp->free_trbs < 3) + return (USBD_NOMEM); + + /* We'll do the setup TRB once we're finished with the other stages. */ + trb0 = xhci_xfer_get_trb(sc, xfer, &toggle0, 0); + + /* Data TRB */ + if (len != 0) { + trb = xhci_xfer_get_trb(sc, xfer, &toggle, 0); + trb->trb_paddr = htole64(DMAADDR(&xfer->dmabuf, 0)); + trb->trb_status = htole32( + XHCI_TRB_INTR(0) | XHCI_TRB_TDREM(1) | XHCI_TRB_LEN(len) + ); + trb->trb_flags = htole32(XHCI_TRB_TYPE_DATA | toggle); + + if (usbd_xfer_isread(xfer)) + trb->trb_flags |= htole32(XHCI_TRB_DIR_IN|XHCI_TRB_ISP); + + } + + /* Status TRB */ + trb = xhci_xfer_get_trb(sc, xfer, &toggle, 1); + trb->trb_paddr = 0; + trb->trb_status = htole32(XHCI_TRB_INTR(0)); + trb->trb_flags = htole32(XHCI_TRB_TYPE_STATUS | XHCI_TRB_IOC | toggle); + + if (len == 0 || !usbd_xfer_isread(xfer)) + trb->trb_flags |= htole32(XHCI_TRB_DIR_IN); + + /* Setup TRB */ + trb0->trb_paddr = (uint64_t)*((uint64_t *)&xfer->request); + trb0->trb_status = htole32(XHCI_TRB_INTR(0) | XHCI_TRB_LEN(8)); + trb0->trb_flags = htole32(XHCI_TRB_TYPE_SETUP | XHCI_TRB_IDT); + + if (len != 0) { + if (usbd_xfer_isread(xfer)) + trb0->trb_flags |= htole32(XHCI_TRB_TRT_IN); + else + trb0->trb_flags |= htole32(XHCI_TRB_TRT_OUT); + } + + trb0->trb_flags |= htole32(toggle0); + + usb_syncmem(&xp->ring.dma, 0, xp->ring.ntrb * sizeof(struct xhci_trb), + BUS_DMASYNC_PREWRITE); /* XXX too big hammer? */ + XDWRITE4(sc, XHCI_DOORBELL(xp->slot), xp->dci); + + xfer->status = USBD_IN_PROGRESS; + + if (sc->sc_bus.use_polling) + xhci_waitintr(sc, xfer); +#if notyet + else if (xfer->timeout) { + timeout_del(&xfer->timeout_handle); + timeout_set(&xfer->timeout_handle, xhci_timeout, xfer); + timeout_add_msec(&xfer->timeout_handle, xfer->timeout); + } +#endif + + return (USBD_IN_PROGRESS); +} + +void +xhci_device_ctrl_abort(struct usbd_xfer *xfer) +{ + xhci_abort_xfer(xfer, USBD_CANCELLED); +} + +usbd_status +xhci_device_generic_transfer(struct usbd_xfer *xfer) +{ + usbd_status err; + + err = usb_insert_transfer(xfer); + if (err) + return (err); + + return (xhci_device_generic_start(SIMPLEQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +xhci_device_generic_start(struct usbd_xfer *xfer) +{ + struct xhci_softc *sc = (struct xhci_softc *)xfer->device->bus; + struct xhci_pipe *xp = (struct xhci_pipe *)xfer->pipe; + struct xhci_trb *trb; + uint8_t toggle; + + KASSERT(!(xfer->rqflags & URQ_REQUEST)); + + if (sc->sc_bus.dying) + return (USBD_IOERROR); + + if (xp->free_trbs < 1) + return (USBD_NOMEM); + + trb = xhci_xfer_get_trb(sc, xfer, &toggle, 1); + trb->trb_paddr = htole64(DMAADDR(&xfer->dmabuf, 0)); + trb->trb_status = htole32( + XHCI_TRB_INTR(0) | XHCI_TRB_TDREM(1) | XHCI_TRB_LEN(xfer->length) + ); + trb->trb_flags = htole32( + XHCI_TRB_TYPE_NORMAL | XHCI_TRB_ISP | XHCI_TRB_IOC | toggle + ); + + usb_syncmem(&xp->ring.dma, ((void *)trb - (void *)xp->ring.trbs), + sizeof(struct xhci_trb), BUS_DMASYNC_PREWRITE); + XDWRITE4(sc, XHCI_DOORBELL(xp->slot), xp->dci); + + xfer->status = USBD_IN_PROGRESS; + + if (sc->sc_bus.use_polling) + xhci_waitintr(sc, xfer); +#if notyet + else if (xfer->timeout) { + timeout_del(&xfer->timeout_handle); + timeout_set(&xfer->timeout_handle, xhci_timeout, xfer); + timeout_add_msec(&xfer->timeout_handle, xfer->timeout); + } +#endif + + return (USBD_IN_PROGRESS); +} + +void +xhci_device_generic_done(struct usbd_xfer *xfer) +{ + usb_syncmem(&xfer->dmabuf, 0, xfer->length, usbd_xfer_isread(xfer) ? + BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); + + /* Only happens with interrupt transfers. */ + if (xfer->pipe->repeat) + xfer->status = xhci_device_generic_start(xfer); +} + +void +xhci_device_generic_abort(struct usbd_xfer *xfer) +{ + KASSERT(!xfer->pipe->repeat || xfer->pipe->intrxfer == xfer); + + xhci_abort_xfer(xfer, USBD_CANCELLED); +} diff --git a/sys/dev/usb/xhcireg.h b/sys/dev/usb/xhcireg.h new file mode 100644 index 00000000000..e8ac28cee51 --- /dev/null +++ b/sys/dev/usb/xhcireg.h @@ -0,0 +1,439 @@ +/* $OpenBSD: xhcireg.h,v 1.1 2014/03/08 14:34:11 mpi Exp $ */ + +/*- + * Copyright (c) 2014 Martin Pieuchot. All rights reserved. + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#ifndef _XHCIREG_H_ +#define _XHCIREG_H_ + +/* Default command execution time (implementation defined). */ +#define XHCI_COMMAND_TIMEOUT 500 /* ms */ + +/* XHCI PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base MEM */ + +#define PCI_INTERFACE_XHCI 0x30 + +#define PCI_USBREV 0x60 /* RO USB protocol revision */ +#define PCI_USBREV_MASK 0xff +#define PCI_USBREV_3_0 0x30 /* USB 3.0 */ + +#define PCI_XHCI_FLADJ 0x61 /* RW frame length adjust */ + +#define PCI_XHCI_INTEL_XUSB2PR 0xd0 /* Intel USB2 Port Routing */ +#define PCI_XHCI_INTEL_USB3_PSSEN 0xd8 /* Intel USB3 Port SuperSpeed Enable */ + +/* XHCI capability registers */ +#define XHCI_CAPLENGTH 0x00 /* RO Capability reg. length field */ +#define XHCI_RESERVED 0x01 /* Reserved */ +#define XHCI_HCIVERSION 0x02 /* RO Interface version number */ +#define XHCI_HCIVERSION_0_9 0x0090 /* xHCI version 0.9 */ +#define XHCI_HCIVERSION_1_0 0x0100 /* xHCI version 1.0 */ + +#define XHCI_HCSPARAMS1 0x04 /* RO structual parameters 1 */ +#define XHCI_HCS1_DEVSLOT_MAX(x)((x) & 0xff) +#define XHCI_HCS1_IRQ_MAX(x) (((x) >> 8) & 0x3ff) +#define XHCI_HCS1_N_PORTS(x) (((x) >> 24) & 0xff) + +#define XHCI_HCSPARAMS2 0x08 /* RO structual parameters 2 */ +#define XHCI_HCS2_IST(x) ((x) & 0xF) +#define XHCI_HCS2_ERST_MAX(x) (((x) >> 4) & 0xf) +#define XHCI_HCS2_SPR(x) (((x) >> 24) & 0x1) +#define XHCI_HCS2_SPB_MAX(x) (((x) >> 27) & 0x7f) + +#define XHCI_HCSPARAMS3 0x0c /* RO structual parameters 3 */ +#define XHCI_HCS3_U1_DEL(x) ((x) & 0xff) +#define XHCI_HCS3_U2_DEL(x) (((x) >> 16) & 0xffff) + +#define XHCI_HCCPARAMS 0x10 /* RO capability parameters */ +#define XHCI_HCC_AC64(x) (((x) >> 0) & 0x1) /* 64-bit capable */ +#define XHCI_HCC_BNC(x) (((x) >> 1) & 0x1) /* BW negotiation */ +#define XHCI_HCC_CSZ(x) (((x) >> 2) & 0x1) /* Context size */ +#define XHCI_HCC_PPC(x) (((x) >> 3) & 0x1) /* Port power control */ +#define XHCI_HCC_PIND(x) (((x) >> 4) & 0x1) /* Port indicators */ +#define XHCI_HCC_LHRC(x) (((x) >> 5) & 0x1) /* Light HC reset */ +#define XHCI_HCC_LTC(x) (((x) >> 6) & 0x1) /* Latency tolerance msg */ +#define XHCI_HCC_NSS(x) (((x) >> 7) & 0x1) /* No secondary sid */ +#define XHCI_HCC_PAE(x) (((x) >> 8) & 0x1) /* Pase All Event Data */ +#define XHCI_HCC_SPC(x) (((x) >> 9) & 0x1) /* Short packet */ +#define XHCI_HCC_SEC(x) (((x) >> 10) & 0xf) /* Stopped EDTLA */ +#define XHCI_HCC_CFC(x) (((x) >> 11) & 0xf) /* Configuous Frame ID */ +#define XHCI_HCC_MAX_PSA_SZ(x) (((x) >> 12) & 0xf) /* Max pri. stream arr. */ +#define XHCI_HCC_XECP(x) (((x) >> 16) & 0xffff) /* Ext. capabilities */ + +#define XHCI_DBOFF 0x14 /* RO doorbell offset */ +#define XHCI_RTSOFF 0x18 /* RO runtime register space offset */ + +/* + * XHCI operational registers. + * Offset given by XHCI_CAPLENGTH register. + */ +#define XHCI_USBCMD 0x00 /* XHCI command */ +#define XHCI_CMD_RS 0x00000001 /* RW Run/Stop */ +#define XHCI_CMD_HCRST 0x00000002 /* RW Host Controller Reset */ +#define XHCI_CMD_INTE 0x00000004 /* RW Interrupter Enable */ +#define XHCI_CMD_HSEE 0x00000008 /* RW Host System Error Enable */ +#define XHCI_CMD_LHCRST 0x00000080 /* RO/RW Light HC Reset */ +#define XHCI_CMD_CSS 0x00000100 /* RW Controller Save State */ +#define XHCI_CMD_CRS 0x00000200 /* RW Controller Restore State */ +#define XHCI_CMD_EWE 0x00000400 /* RW Enable Wrap Event */ +#define XHCI_CMD_EU3S 0x00000800 /* RW Enable U3 MFINDEX Stop */ + +#define XHCI_USBSTS 0x04 /* XHCI status */ +#define XHCI_STS_HCH 0x00000001 /* RO - Host Controller Halted */ +#define XHCI_STS_HSE 0x00000004 /* RW - Host System Error */ +#define XHCI_STS_EINT 0x00000008 /* RW - Event Interrupt */ +#define XHCI_STS_PCD 0x00000010 /* RW - Port Change Detect */ +#define XHCI_STS_SSS 0x00000100 /* RO - Save State Status */ +#define XHCI_STS_RSS 0x00000200 /* RO - Restore State Status */ +#define XHCI_STS_SRE 0x00000400 /* RW - Save/Restore Error */ +#define XHCI_STS_CNR 0x00000800 /* RO - Controller Not Ready */ +#define XHCI_STS_HCE 0x00001000 /* RO - Host Controller Error */ + +#define XHCI_PAGESIZE 0x08 /* XHCI page size mask */ +#define XHCI_PAGESIZE_4K 0x00000001 /* 4K Page Size */ +#define XHCI_PAGESIZE_8K 0x00000002 /* 8K Page Size */ +#define XHCI_PAGESIZE_16K 0x00000004 /* 16K Page Size */ +#define XHCI_PAGESIZE_32K 0x00000008 /* 32K Page Size */ +#define XHCI_PAGESIZE_64K 0x00000010 /* 64K Page Size */ + +#define XHCI_DNCTRL 0x14 /* XHCI device notification control */ +#define XHCI_DNCTRL_MASK(n) (1 << (n)) + +#define XHCI_CRCR_LO 0x18 /* XHCI command ring control */ +#define XHCI_CRCR_LO_RCS 0x00000001 /* RW - consumer cycle state */ +#define XHCI_CRCR_LO_CS 0x00000002 /* RW - command stop */ +#define XHCI_CRCR_LO_CA 0x00000004 /* RW - command abort */ +#define XHCI_CRCR_LO_CRR 0x00000008 /* RW - command ring running */ +#define XHCI_CRCR_LO_MASK 0x0000000F + +#define XHCI_CRCR_HI 0x1C /* XHCI command ring control */ +#define XHCI_DCBAAP_LO 0x30 /* XHCI dev context BA pointer */ +#define XHCI_DCBAAP_HI 0x34 /* XHCI dev context BA pointer */ +#define XHCI_CONFIG 0x38 +#define XHCI_CONFIG_SLOTS_MASK 0x000000ff /* RW - nb of device slots enabled */ + +/* + * XHCI port status registers. + */ +#define XHCI_PORTSC(n) (0x3f0 + (0x10 * (n))) /* XHCI port status */ +#define XHCI_PS_CCS 0x00000001 /* RO - current connect status */ +#define XHCI_PS_PED 0x00000002 /* RW - port enabled / disabled */ +#define XHCI_PS_OCA 0x00000008 /* RO - over current active */ +#define XHCI_PS_PR 0x00000010 /* RW - port reset */ +#define XHCI_PS_GET_PLS(x) (((x) >> 5) & 0xf) /* RW - port link state */ +#define XHCI_PS_SET_PLS(x) (((x) & 0xf) << 5) /* RW - port link state */ +#define XHCI_PS_PP 0x00000200 /* RW - port power */ +#define XHCI_PS_SPEED(x) (((x) >> 10) & 0xf) /* RO - port speed */ +#define XHCI_PS_GET_PIC(x) (((x) >> 14) & 0x3) /* RW - port indicator */ +#define XHCI_PS_SET_PIC(x) (((x) & 0x3) << 14) /* RW - port indicator */ +#define XHCI_PS_LWS 0x00010000 /* RW - link state write strobe */ +#define XHCI_PS_CSC 0x00020000 /* RW - connect status change */ +#define XHCI_PS_PEC 0x00040000 /* RW - port enable/disable change */ +#define XHCI_PS_WRC 0x00080000 /* RW - warm port reset change */ +#define XHCI_PS_OCC 0x00100000 /* RW - over-current change */ +#define XHCI_PS_PRC 0x00200000 /* RW - port reset change */ +#define XHCI_PS_PLC 0x00400000 /* RW - port link state change */ +#define XHCI_PS_CEC 0x00800000 /* RW - config error change */ +#define XHCI_PS_CAS 0x01000000 /* RO - cold attach status */ +#define XHCI_PS_WCE 0x02000000 /* RW - wake on connect enable */ +#define XHCI_PS_WDE 0x04000000 /* RW - wake on disconnect enable */ +#define XHCI_PS_WOE 0x08000000 /* RW - wake on over-current enable*/ +#define XHCI_PS_DR 0x40000000 /* RO - device removable */ +#define XHCI_PS_WPR 0x80000000U /* RW - warm port reset */ +#define XHCI_PS_CLEAR 0x80ff01ffu /* command bits */ + +#define XHCI_PORTPMSC(n) (0x3f4 + (0x10 * (n))) /* XHCI status & ctrl */ +#define XHCI_PM3_U1TO(x) (((x) & 0xff) << 0) /* RW - U1 timeout */ +#define XHCI_PM3_U2TO(x) (((x) & 0xff) << 8) /* RW - U2 timeout */ +#define XHCI_PM3_FLA 0x00010000 /* RW - Force Link PM Accept */ +#define XHCI_PM2_L1S(x) (((x) >> 0) & 0x7) /* RO - L1 status */ +#define XHCI_PM2_RWE 0x00000008 /* RW - remote wakup enable */ +#define XHCI_PM2_HIRD(x) (((x) & 0xf) << 4) /* RW - resume duration */ +#define XHCI_PM2_L1SLOT(x) (((x) & 0xff) << 8) /* RW - L1 device slot */ +#define XHCI_PM2_HLE 0x00010000 /* RW - hardware LPM enable */ +#define XHCI_PORTLI(n) (0x3f8 + (0x10 * (n))) /* XHCI port link info */ +#define XHCI_PORTRSV(n) (0x3fC + (0x10 * (n))) /* XHCI port reserved */ + +/* + * XHCI runtime registers. + * Offset given by XHCI_CAPLENGTH + XHCI_RTSOFF registers. + */ +#define XHCI_MFINDEX 0x0000 /* RO - microframe index */ +#define XHCI_GET_MFINDEX(x) ((x) & 0x3fff) +#define XHCI_IMAN(n) (0x0020 + (0x20 * (n))) /* intr.management */ +#define XHCI_IMAN_INTR_PEND 0x00000001 /* RW - interrupt pending */ +#define XHCI_IMAN_INTR_ENA 0x00000002 /* RW - interrupt enable */ + +/* XHCI interrupt moderation */ +#define XHCI_IMOD(n) (0x0024 + (0x20 * (n))) +#define XHCI_IMOD_IVAL_GET(x) (((x) >> 0) & 0xffff) /* 250ns unit */ +#define XHCI_IMOD_IVAL_SET(x) (((x) & 0xffff) << 0) /* 250ns unit */ +#define XHCI_IMOD_ICNT_GET(x) (((x) >> 16) & 0xffff) /* 250ns unit */ +#define XHCI_IMOD_ICNT_SET(x) (((x) & 0xffff) << 16) /* 250ns unit */ +#define XHCI_IMOD_DEFAULT 0x000003E8U /* 8000 IRQ/second */ + +/* XHCI event ring segment table size */ +#define XHCI_ERSTSZ(n) (0x0028 + (0x20 * (n))) +#define XHCI_ERSTS_SET(x) ((x) & 0xffff) + +/* XHCI event ring segment table BA */ +#define XHCI_ERSTBA_LO(n) (0x0030 + (0x20 * (n))) +#define XHCI_ERSTBA_HI(n) (0x0034 + (0x20 * (n))) + +/* XHCI event ring dequeue pointer */ +#define XHCI_ERDP_LO(n) (0x0038 + (0x20 * (n))) +#define XHCI_ERDP_LO_BUSY 0x00000008 /* RW - event handler busy */ +#define XHCI_ERDP_HI(n) (0x003c + (0x20 * (n))) + +/* + * XHCI doorbell registers. + * Offset given by XHCI_CAPLENGTH + XHCI_DBOFF registers. + */ +#define XHCI_DOORBELL(n) (0x0000 + (4 * (n))) +#define XHCI_DB_GET_SID(x) (((x) >> 16) & 0xffff) /* RW - stream ID */ +#define XHCI_DB_SET_SID(x) (((x) & 0xffff) << 16) /* RW - stream ID */ + +/* XHCI legacy support */ +#define XHCI_XECP_ID(x) ((x) & 0xff) +#define XHCI_XECP_NEXT(x) (((x) >> 8) & 0xff) +#define XHCI_XECP_BIOS_SEM 0x0002 +#define XHCI_XECP_OS_SEM 0x0003 + +/* XHCI capability ID's */ +#define XHCI_ID_USB_LEGACY 0x0001 +#define XHCI_ID_PROTOCOLS 0x0002 +#define XHCI_ID_POWER_MGMT 0x0003 +#define XHCI_ID_VIRTUALIZATION 0x0004 +#define XHCI_ID_MSG_IRQ 0x0005 +#define XHCI_ID_USB_LOCAL_MEM 0x0006 + + +struct xhci_erseg { + uint64_t er_addr; + uint32_t er_size; + uint32_t er_rsvd; +} __packed; + + +struct xhci_sctx { + uint32_t info_lo; +#define XHCI_SCTX_ROUTE(x) ((x) & 0xfffff) +#define XHCI_SCTX_SET_SPEED(x) (((x) & 0xf) << 20) +#define XHCI_SCTX_GET_SPEED(x) (((x) >> 20) & 0xf) +#define XHCI_SCTX_SET_MTT(x) (((x) & 0x1) << 25) +#define XHCI_SCTX_GET_MTT(x) (((x) >> 25) & 0x1) +#define XHCI_SCTX_SET_HUB(x) (((x) & 0x1) << 26) +#define XHCI_SCTX_GET_HUB(x) (((x) >> 26) & 0x1) +#define XHCI_SCTX_SET_DCI(x) (((x) & 0x1f) << 27) +#define XHCI_SCTX_GET_DCI(x) (((x) >> 27) & 0x1f) + + uint32_t info_hi; +#define XHCI_SCTX_SET_MAX_EL(x) ((x) & 0xffff) +#define XHCI_SCTX_GET_MAX_EL(x) ((x) & 0xffff) +#define XHCI_SCTX_SET_RHPORT(x) (((x) & 0xff) << 16) +#define XHCI_SCTX_GET_RHPORT(x) (((x) >> 16) & 0xff) +#define XHCI_SCTX_SET_NPORTS(x) (((x) & 0xff) << 24) +#define XHCI_SCTX_GET_NPORTS(x) (((x) >> 24) & 0xff) + + uint32_t tt; +#define XHCI_SCTX_TT_HUB_SID(x) ((x) & 0xff) +#define XHCI_SCTX_SET_TT_PORT_NUM(x) (((x) & 0xff) << 8) +#define XHCI_SCTX_GET_TT_PORT_NUM(x) (((x) >> 8) & 0xff) +#define XHCI_SCTX_SET_TT_THINK_TIME(x) (((x) & 0x3) << 16) +#define XHCI_SCTX_GET_TT_THINK_TIME(x) (((x) >> 16) & 0x3) +#define XHCI_SCTX_SET_IRQ_TARGET(x) (((x) & 0x3ff) << 22) +#define XHCI_SCTX_GET_IRQ_TARGET(x) (((x) >> 22) & 0x3ff) + + uint32_t state; +#define XHCI_SCTX_DEV_ADDR(x) ((x) & 0xff) +#define XHCI_SCTX_SLOT_STATE(x) (((x) >> 27) & 0x1f) + + uint32_t rsvd[4]; +} __packed; + +struct xhci_epctx { + uint32_t info_lo; +#define XHCI_EPCTX_STATE(x) ((x) & 0x7) +#define XHCI_EP_DISABLED 0x0 +#define XHCI_EP_RUNNING 0x1 +#define XHCI_EP_HALTED 0x2 +#define XHCI_EP_STOPPED 0x3 +#define XHCI_EP_ERROR 0x4 +#define XHCI_EPCTX_SET_MULT(x) (((x) & 0x3) << 8) +#define XHCI_EPCTX_GET_MULT(x) (((x) >> 8) & 0x3) +#define XHCI_EPCTX_SET_MAXP_STREAMS(x) (((x) & 0x1F) << 10) +#define XHCI_EPCTX_GET_MAXP_STREAMS(x) (((x) >> 10) & 0x1F) +#define XHCI_EPCTX_SET_LSA(x) (((x) & 0x1) << 15) +#define XHCI_EPCTX_GET_LSA(x) (((x) >> 15) & 0x1) +#define XHCI_EPCTX_SET_IVAL(x) (((x) & 0xff) << 16) +#define XHCI_EPCTX_GET_IVAL(x) (((x) >> 16) & 0xFF) + + uint32_t info_hi; +#define XHCI_EPCTX_SET_CERR(x) (((x) & 0x3) << 1) +#define XHCI_EPCTX_SET_EPTYPE(x) (((x) & 0x7) << 3) +#define XHCI_EPCTX_GET_EPTYPE(x) (((x) >> 3) & 0x7) +#define XHCI_EPCTX_SET_HID(x) (((x) & 0x1) << 7) +#define XHCI_EPCTX_GET_HID(x) (((x) >> 7) & 0x1) +#define XHCI_EPCTX_SET_MAXB(x) (((x) & 0xff) << 8) +#define XHCI_EPCTX_GET_MAXB(x) (((x) >> 8) & 0xff) +#define XHCI_EPCTX_SET_MPS(x) (((x) & 0xffff) << 16) +#define XHCI_EPCTX_GET_MPS(x) (((x) >> 16) & 0xffff) +#define XHCI_SPEED_FULL 1 +#define XHCI_SPEED_LOW 2 +#define XHCI_SPEED_HIGH 3 +#define XHCI_SPEED_SUPER 4 + + uint64_t deqp; +#define XHCI_EPCTX_DCS 0x1 + + uint32_t txinfo; +#define XHCI_EPCTX_AVG_TRB_LEN(x) ((x) & 0xffff) +#define XHCI_EPCTX_MAX_ESIT_PAYLOAD(x) (((x) & 0xffff) << 16) + + uint32_t rsvd[3]; +} __packed; + + +struct xhci_inctx { + uint32_t drop_flags; + uint32_t add_flags; +#define XHCI_INCTX_MASK_DCI(n) (0x1 << (n)) + + uint32_t rsvd[6]; +} __packed; + + +struct xhci_trb { + uint64_t trb_paddr; +#define XHCI_TRB_PORTID(x) (((x) & (0xff << 24)) >> 24) /* Port ID */ + + uint32_t trb_status; +#define XHCI_TRB_GET_CODE(x) (((x) >> 24) & 0xff) +#define XHCI_TRB_TDREM(x) (((x) & 0x1f) << 17) /* TD remaining len. */ +#define XHCI_TRB_REMAIN(x) ((x) & 0xffffff) /* Remaining length */ +#define XHCI_TRB_LEN(x) ((x) & 0x1ffff) /* Transfer length */ +#define XHCI_TRB_INTR(x) (((x) & 0x3ff) << 22) /* MSI-X intr. target */ + + uint32_t trb_flags; +#define XHCI_TRB_CYCLE (1 << 0) /* Enqueue point of xfer ring */ +#define XHCI_TRB_ENT (1 << 1) /* Evaluate next TRB */ +#define XHCI_TRB_LINKSEG XHCI_TRB_ENT /* Link to next segment */ +#define XHCI_TRB_ISP (1 << 2) /* Interrupt on short packet */ +#define XHCI_TRB_NOSNOOP (1 << 3) /* PCIe no snoop */ +#define XHCI_TRB_CHAIN (1 << 4) /* Chained with next TRB */ +#define XHCI_TRB_IOC (1 << 5) /* Interrupt On Completion */ +#define XHCI_TRB_IDT (1 << 6) /* Immediate DaTa */ +#define XHCI_TRB_BSR (1 << 9) +#define XHCI_TRB_DIR_IN (1 << 16) +#define XHCI_TRB_TRT_OUT (2 << 16) +#define XHCI_TRB_TRT_IN (3 << 16) +#define XHCI_TRB_GET_EP(x) (((x) >> 16) & 0x1f) +#define XHCI_TRB_SET_EP(x) (((x) & 0x1f) << 16) +#define XHCI_TRB_GET_SLOT(x) (((x) >> 24) & 0xff) +#define XHCI_TRB_SET_SLOT(x) (((x) & 0xff) << 24) +} __packed; + +#define XHCI_TRB_TYPE_MASK 0xfc00 +#define XHCI_TRB_TYPE(x) (((x) & XHCI_TRB_TYPE_MASK) >> 10) + +/* Transfer Ring Types */ +#define XHCI_TRB_TYPE_NORMAL (1 << 10) +#define XHCI_TRB_TYPE_SETUP (2 << 10) /* Setup stage (ctrl only) */ +#define XHCI_TRB_TYPE_DATA (3 << 10) /* Data stage (ctrl only) */ +#define XHCI_TRB_TYPE_STATUS (4 << 10) /* Status stage (ctrl only) */ +#define XHCI_TRB_TYPE_ISOCH (5 << 10) +#define XHCI_TRB_TYPE_LINK (6 << 10) /* Link next seg. (all+cmd) */ +#define XHCI_TRB_TYPE_EVENT (7 << 10) /* Generate event (all) */ +#define XHCI_TRB_TYPE_NOOP (8 << 10) /* No-Op (all) */ + +/* Command ring Types */ +#define XHCI_CMD_ENABLE_SLOT (9 << 10) +#define XHCI_CMD_DISABLE_SLOT (10 << 10) +#define XHCI_CMD_ADDRESS_DEVICE (11 << 10) +#define XHCI_CMD_CONFIG_EP (12 << 10) +#define XHCI_CMD_EVAL_CTX (13 << 10) +#define XHCI_CMD_RESET_EP (14 << 10) +#define XHCI_CMD_STOP_EP (15 << 10) +#define XHCI_CMD_SET_TR_DEQ (16 << 10) +#define XHCI_CMD_RESET_DEV (17 << 10) +#define XHCI_CMD_FEVENT (18 << 10) +#define XHCI_CMD_NEG_BW (19 << 10) /* Negociate bandwith */ +#define XHCI_CMD_SET_LT (20 << 10) /* Set latency tolerance */ +#define XHCI_CMD_GET_BW (21 << 10) /* Get port bandwith */ +#define XHCI_CMD_FHEADER (22 << 10) +#define XHCI_CMD_NOOP (23 << 10) /* To test the command ring */ + +/* Event ring Types */ +#define XHCI_EVT_XFER (32 << 10) /* Transfer event */ +#define XHCI_EVT_CMD_COMPLETE (33 << 10) +#define XHCI_EVT_PORT_CHANGE (34 << 10) /* Port status change */ +#define XHCI_EVT_BW_REQUEST (35 << 10) +#define XHCI_EVT_DOORBELL (36 << 10) +#define XHCI_EVT_HOST_CTRL (37 << 10) +#define XHCI_EVT_DEVICE_NOTIFY (38 << 10) +#define XHCI_EVT_MFINDEX_WRAP (39 << 10) + +/* TRB Completion codes */ +#define XHCI_CODE_INVALID 0 /* Producer didn't update the code. */ +#define XHCI_CODE_SUCCESS 1 /* Badaboum, plaf, plouf, yeepee! */ +#define XHCI_CODE_DATA_BUF 2 /* Overrun or underrun */ +#define XHCI_CODE_BABBLE 3 /* Device is "babbling" */ +#define XHCI_CODE_TXERR 4 /* USB Transaction error */ +#define XHCI_CODE_TRB 5 /* Invalid TRB */ +#define XHCI_CODE_STALL 6 /* Stall condition */ +#define XHCI_CODE_RESOURCE 7 /* No resource available for the cmd */ +#define XHCI_CODE_BANDWIDTH 8 /* Not enough bandwidth for the cmd */ +#define XHCI_CODE_NO_SLOTS 9 /* MaxSlots limit reached */ +#define XHCI_CODE_STREAM_TYPE 10 /* Stream Context Type value detected */ +#define XHCI_CODE_SLOT_NOT_ON 11 /* Related device slot is disabled */ +#define XHCI_CODE_ENDP_NOT_ON 12 /* Related enpoint is disabled */ +#define XHCI_CODE_SHORT_XFER 13 /* Short packet */ +#define XHCI_CODE_RING_UNDERRUN 14 /* Empty ring when transmitting isoc */ +#define XHCI_CODE_RING_OVERRUN 15 /* Empty ring when receiving isoc */ +#define XHCI_CODE_VF_RING_FULL 16 /* VF's event ring is full */ +#define XHCI_CODE_PARAMETER 17 /* Context parameter is invalid */ +#define XHCI_CODE_BW_OVERRUN 18 /* TD exceeds the bandwidth */ +#define XHCI_CODE_CONTEXT_STATE 19 /* Transition from illegal ctx state */ +#define XHCI_CODE_NO_PING_RESP 20 /* Unable to complete periodic xfer */ +#define XHCI_CODE_EV_RING_FULL 21 /* Unable to post an evt to the ring */ +#define XHCI_CODE_INCOMPAT_DEV 22 /* Device cannot be accessed */ +#define XHCI_CODE_MISSED_SRV 23 /* Unable to service isoc EP in ESIT */ +#define XHCI_CODE_CMD_RING_STOP 24 /* Command Stop (CS) requested */ +#define XHCI_CODE_CMD_ABORTED 25 /* Command Abort (CA) operation */ +#define XHCI_CODE_XFER_STOPPED 26 /* xfer terminated by a stop endpoint */ +#define XHCI_CODE_XFER_INVLEN 27 /* TRB transfer length invalid */ +#define XHCI_CODE_XFER_SHORTPKT 28 /* Stopped before reaching end of TD */ +#define XHCI_CODE_MELAT 29 /* Max Exit Latency too large */ +#define XHCI_CODE_RESERVED 30 +#define XHCI_CODE_ISOC_OVERRUN 31 /* IN data buffer < Max ESIT Payload */ +#define XHCI_CODE_EVENT_LOST 32 /* Internal overrun - impl. specific */ +#define XHCI_CODE_UNDEFINED 33 /* Fatal error - impl. specific */ +#define XHCI_CODE_INVALID_SID 34 /* Invalid stream ID received */ +#define XHCI_CODE_SEC_BW 35 /* Cannot alloc secondary BW Domain */ +#define XHCI_CODE_SPLIT_XACT 36 /* USB2 split transaction */ + +#endif /* _XHCIREG_H_ */ diff --git a/sys/dev/usb/xhcivar.h b/sys/dev/usb/xhcivar.h new file mode 100644 index 00000000000..edcdfb85bdf --- /dev/null +++ b/sys/dev/usb/xhcivar.h @@ -0,0 +1,142 @@ +/* $OpenBSD: xhcivar.h,v 1.1 2014/03/08 14:34:11 mpi Exp $ */ + +/* + * Copyright (c) 2014 Martin Pieuchot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _XHCIVAR_H_ +#define _XHCIVAR_H_ + +#define XHCI_MAX_COMMANDS (16 * 1) +#define XHCI_MAX_EVENTS (16 * 13) +#define XHCI_MAX_TRANSFERS (16 * 16) + + +struct xhci_xfer { + struct usbd_xfer xfer; + int index; /* Index of the first TRB */ + size_t ntrb; /* Number of associated TRBs */ +}; + +struct xhci_ring { + struct xhci_trb *trbs; + size_t ntrb; + struct usb_dma dma; + + uint32_t index; + uint32_t toggle; /* Producer/Consumer bit */ +}; + +struct xhci_soft_dev { + struct xhci_inctx *input_ctx; /* Input context */ + struct xhci_sctx *slot_ctx; + struct xhci_epctx *ep_ctx[31]; + struct usb_dma ictx_dma; + + struct usb_dma octx_dma; /* Output context */ + + struct xhci_pipe *pipes[31]; +}; + +/* Device context segment table. */ +struct xhci_devctx { + uint64_t *segs; /* at most USB_MAX_DEVICES+1 */ + size_t size; + struct usb_dma dma; +}; + +/* Event ring segment table. */ +struct xhci_erst { + struct xhci_erseg *segs; /* One segment per event ring */ + size_t size; + struct usb_dma dma; +}; + +struct xhci_scratchpad { + struct usb_dma table_dma; + struct usb_dma pages_dma; + int npage; + +}; + +struct xhci_softc { + struct usbd_bus sc_bus; + + bus_space_tag_t iot; + bus_space_handle_t ioh; + bus_size_t sc_size; + + bus_size_t sc_oper_off; /* Operational Register space */ + bus_size_t sc_runt_off; /* Runtime */ + bus_size_t sc_door_off; /* Doorbell */ + + uint32_t sc_pagesize; /* xHCI page size, minimum 4k */ + uint32_t sc_ctxsize; /* 32/64 byte context structs */ + + int sc_noport; /* Maximum number of ports */ + + u_int8_t sc_addr; /* Device address */ + u_int8_t sc_conf; /* Device configuration */ + struct usbd_xfer *sc_intrxfer; /* Root HUB interrupt xfer */ + + struct xhci_devctx sc_dcbaa; /* Device context base addr. */ + struct xhci_ring sc_cmd_ring; /* Command ring */ + + struct xhci_erst sc_erst; /* Event ring segment table */ + struct xhci_ring sc_evt_ring; /* Event ring */ + + struct xhci_scratchpad sc_spad; /* Optional scratchpad */ + + int sc_noslot; /* Maximum number of slots */ + struct xhci_soft_dev sc_sdevs[USB_MAX_DEVICES]; + + struct xhci_trb *sc_cmd_trb; + struct xhci_trb sc_result_trb; + + SIMPLEQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ + + char sc_vendor[16]; /* Vendor string for root hub */ + int sc_id_vendor; /* Vendor ID for root hub */ + struct device *sc_child; /* /dev/usb# device */ +}; + +int xhci_init(struct xhci_softc *); +int xhci_intr(void *); +int xhci_detach(struct xhci_softc *, int); +int xhci_activate(struct device *, int); + +#define XREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (a)) +#define XREAD2(sc, a) bus_space_read_2((sc)->iot, (sc)->ioh, (a)) +#define XREAD4(sc, a) bus_space_read_4((sc)->iot, (sc)->ioh, (a)) +#define XWRITE1(sc, a, x) bus_space_write_1((sc)->iot, (sc)->ioh, (a), (x)) +#define XWRITE2(sc, a, x) bus_space_write_2((sc)->iot, (sc)->ioh, (a), (x)) +#define XWRITE4(sc, a, x) bus_space_write_4((sc)->iot, (sc)->ioh, (a), (x)) + +#define XOREAD4(sc, a) \ + bus_space_read_4((sc)->iot, (sc)->ioh, (sc)->sc_oper_off + (a)) +#define XOWRITE4(sc, a, x) \ + bus_space_write_4((sc)->iot, (sc)->ioh, (sc)->sc_oper_off + (a), (x)) + +#define XRREAD4(sc, a) \ + bus_space_read_4((sc)->iot, (sc)->ioh, (sc)->sc_runt_off + (a)) +#define XRWRITE4(sc, a, x) \ + bus_space_write_4((sc)->iot, (sc)->ioh, (sc)->sc_runt_off + (a), (x)) + +#define XDREAD4(sc, a) \ + bus_space_read_4((sc)->iot, (sc)->ioh, (sc)->sc_door_off + (a)) +#define XDWRITE4(sc, a, x) \ + bus_space_write_4((sc)->iot, (sc)->ioh, (sc)->sc_door_off + (a), (x)) + +#endif /* _XHCIVAR_H_ */ |