diff --git a/drivers/usbhost/CMakeLists.txt b/drivers/usbhost/CMakeLists.txt index cf7f8886fd04c..16917b441285a 100644 --- a/drivers/usbhost/CMakeLists.txt +++ b/drivers/usbhost/CMakeLists.txt @@ -83,8 +83,12 @@ if(CONFIG_USBHOST) list(APPEND SRCS usbhost_bthci.c) endif() + if(CONFIG_USBHOST_XHCI) + list(APPEND SRCS usbhost_xhci.c usbhost_xhci_trace.c) + endif() + if(CONFIG_USBHOST_XHCI_PCI) - list(APPEND SRCS usbhost_xhci_pci.c usbhost_xhci_trace.c) + list(APPEND SRCS usbhost_xhci_pci.c) endif() # HCD debug/trace logic diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index 4d98e382bb67d..f80b81329b9b3 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -778,15 +778,17 @@ config USBHOST_BTHCI ---help--- Select this option to build in support for USB Bluetooth HCI devices. -menuconfig USBHOST_XHCI_PCI - bool "USB xHCI PCI Host Driver Support" +config USBHOST_XHCI + bool default n - depends on PCI && PCI_MSIX && USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK + depends on USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK select USBHOST_HAVE_ASYNCH ---help--- - USB xHCI PCI host driver support. + The xHCI controller driver itself, which is the same wherever the + controller is fitted. Selected by whichever bus found one: PCI + below, or an SoC that wires one in. -if USBHOST_XHCI_PCI +if USBHOST_XHCI config USBHOST_XHCI_MAX_DEVS int "xHCI maximum supported devices" @@ -795,6 +797,30 @@ config USBHOST_XHCI_MAX_DEVS ---help--- How many USB devices will be supported by xHCI driver. -endif # USBHOST_XHCI_PCI +config USBHOST_XHCI_ENUM_RETRIES + int "xHCI enumeration attempts per port" + default 3 + range 1 255 + ---help--- + How many times to attempt enumeration of a newly connected device + before leaving the port alone until the device is unplugged. + + A device whose descriptors cannot be read, or that no class driver + claims, fails enumeration every time. Each failure marks the port + disconnected so the attempt repeats, so without a limit such a + device is retried for as long as it stays plugged in, logging and + taking a device slot on every pass. + + The count is per root hub port and is cleared by a new connection. + +endif # USBHOST_XHCI + +menuconfig USBHOST_XHCI_PCI + bool "USB xHCI PCI Host Driver Support" + default n + depends on PCI && PCI_MSIX && USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK + select USBHOST_XHCI + ---help--- + USB xHCI PCI host driver support. endif # USBHOST diff --git a/drivers/usbhost/Make.defs b/drivers/usbhost/Make.defs index 07e09f0bf490d..a75f951863bef 100644 --- a/drivers/usbhost/Make.defs +++ b/drivers/usbhost/Make.defs @@ -84,8 +84,12 @@ ifeq ($(CONFIG_USBHOST_BTHCI),y) CSRCS += usbhost_bthci.c endif +ifeq ($(CONFIG_USBHOST_XHCI),y) +CSRCS += usbhost_xhci.c usbhost_xhci_trace.c +endif + ifeq ($(CONFIG_USBHOST_XHCI_PCI),y) -CSRCS += usbhost_xhci_pci.c usbhost_xhci_trace.c +CSRCS += usbhost_xhci_pci.c endif # HCD debug/trace logic diff --git a/drivers/usbhost/usbhost_hub.c b/drivers/usbhost/usbhost_hub.c index 52032ee87dd74..a2ea159de000d 100644 --- a/drivers/usbhost/usbhost_hub.c +++ b/drivers/usbhost/usbhost_hub.c @@ -578,6 +578,17 @@ static inline int usbhost_hubdesc(FAR struct usbhost_class_s *hubclass) priv->pwrondelay = (2 * hubdesc->pwrondelay); priv->ctrlcurrent = hubdesc->ctrlcurrent; + /* Publish what describes this hub as a hub, rather than as a device, on + * the port it occupies. A host controller that has to be told about the + * hubs in a topology reads it from there when it sets up a device behind + * this one. This runs before any downstream port is activated, so it is + * in place before there is anything behind it to set up. + */ + + hport->nports = hubdesc->nports; + hport->ttt = (hubchar & USBHUB_CHAR_TTTT_MASK) >> + USBHUB_CHAR_TTTT_SHIFT; + uinfo("Hub Descriptor:\n"); uinfo(" bDescLength: %d\n", hubdesc->len); uinfo(" bDescriptorType: 0x%02x\n", hubdesc->type); diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c new file mode 100644 index 0000000000000..2c4a85aa9daa0 --- /dev/null +++ b/drivers/usbhost/usbhost_xhci.c @@ -0,0 +1,6041 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_xhci.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "usbhost_xhci.h" +#include "usbhost_xhci_trace.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Pre-requisites */ + +#if CONFIG_USBHOST_XHCI_MAX_DEVS > XHCI_MAX_DEVS +# error Invalid value for CONFIG_USBHOST_XHCI_MAX_DEVS +#endif + +/* Some constants for this implementation */ + +#define XHCI_MAX_ERST (1) +#define XHCI_CMD_MAX (16) +#define XHCI_EVENT_MAX (232) +#define XHCI_TD_MAX (8) + +/* How long to give the controller to stop, in milliseconds. The + * specification asks for it within 16; this is generous. + */ + +#define XHCI_HALT_TIMEOUT_MS (100) + +/* How long to give a port to come up after being reset, in milliseconds. + * USB 2.0 asks for the reset to be held 10ms and the port to be usable + * shortly after; this is generous. + */ + +#define XHCI_PORT_RESET_MS (500) + +/* How much memory a context occupies, which depends on the stride the + * controller asked for. One entry for the slot and one per endpoint, and + * the input context carries its control entry in front of both. + */ + +#define XHCI_DEVCTX_SIZE(priv) ((1 + XHCI_MAX_ENDPOINTS) * (priv)->ctxsize) +#define XHCI_INCTX_SIZE(priv) ((2 + XHCI_MAX_ENDPOINTS) * (priv)->ctxsize) +#define XHCI_BUFSIZE (512) + +/* Port numbers macros */ + +#define HPNDX(hp) ((hp)->port) +#define HPORT(hp) (HPNDX(hp) + 1) +#define RHPNDX(rh) ((rh)->hport.hport.port) +#define RHPORT(rh) (RHPNDX(rh) + 1) + +/* Other helper macros */ + +#define XHCI_XCONN_FROM_CONN(c) ((FAR struct usbhost_conn_xhci_s *)c) +#define XHCI_PRIV_FROM_CONN(c) (XHCI_XCONN_FROM_CONN(c)->priv) +#define XHCI_RHPORT_FROM_DRVR(d) ((FAR struct xhci_rhport_s *)d) +#define XHCI_PRIV_FROM_RHPORT(r) (r->priv) +#define XHCI_PRIV_FROM_DRVR(d) (XHCI_PRIV_FROM_RHPORT(XHCI_RHPORT_FROM_DRVR(d))) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* USB device state + * + * Reference: + * - 4.5.3: Slot States + */ + +enum xhci_slot_e +{ + XHCI_SLOT_DISABLED, + XHCI_SLOT_ENABLED, + XHCI_SLOT_DEFAULT, + XHCI_SLOT_ADDRESSED, + XHCI_SLOT_CONFIGURED, +}; + +/* Ring state */ + +struct xhci_ring_s +{ + FAR struct xhci_trb_s *ring; /* Ring */ + size_t i; /* Ring pointer */ + size_t len; /* Ring length */ + bool ccs; /* Consumer Cycle State */ +}; + +/* EP info */ + +struct xhci_epinfo_s +{ + uint8_t epno:7; /* Endpoint number */ + uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */ + uint8_t toggle:1; /* Next data toggle */ +#ifndef CONFIG_USBHOST_INT_DISABLE + uint8_t interval; /* Polling interval */ +#endif + uint8_t devaddr; /* Device address returned from xHCI */ + uint8_t status; /* Retained token status bits (for debug purposes) */ + bool iocwait; /* TRUE: Thread is waiting for transfer completion */ + uint8_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ + int result; /* The result of the transfer */ + size_t xfrd; /* On completion, will hold the number of bytes transferred */ + size_t buflen; /* Buffer length used for transfer */ + FAR uint8_t *buffer; /* The caller's buffer, for cache maintenance */ + FAR uint8_t *bounce; /* Aligned stand-in for it, or NULL */ + size_t dmalen; /* Length the cache is maintained over */ + size_t dmacopy; /* Length to copy back out of a stand-in */ + bool dmain; /* Direction this buffer was prepared for */ + sem_t iocsem; /* Semaphore used to wait for transfer completion */ + + /* One transfer at a time on an endpoint. The controller lock below is + * released while a transfer is in flight, so it cannot serve this: two + * threads would each set up a transfer on the same endpoint and the + * second would find iocwait already set. A device's default control + * endpoint is the one that meets this, since every interface driver on + * a composite device speaks through it. + */ + + mutex_t exclsem; /* Serialises transfers on this endpoint */ +#ifdef CONFIG_USBHOST_ASYNCH + usbhost_asynch_t callback; /* Transfer complete callback */ + FAR void *arg; /* Argument that accompanies the callback */ +#endif + struct xhci_ring_s td; /* TD ring for this endpoint */ + uint8_t slot; /* Slot where this EP resides */ +}; + +/* This structure retains the state of one root hub port */ + +struct xhci_rhport_s +{ + /* Common device fields. This must be the first thing defined in the + * structure so that it is possible to simply cast from struct usbhost_s + * to struct xhci_rhport_s. + */ + + struct usbhost_driver_s drvr; + + /* Root hub port status */ + + bool connected; /* Connected to device */ + uint8_t enumfail; /* Consecutive failed enumerations */ + int8_t slot; /* Slot ID associated with this port */ + struct xhci_epinfo_s ep0; /* EP0 endpoint info */ + struct usbhost_roothubport_s hport; /* This is the hub port description understood + * by class drivers + */ + FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */ + FAR struct xhci_dev_s *dev; /* Device reference */ +}; + +/* USB Devices xhci data */ + +struct xhci_dev_s +{ + uint8_t state; /* Slot stat */ + uint8_t slot; /* Slot ID associated with this device */ + FAR struct xhci_dev_ctx_s *ctx; /* Output Device Context. Managed by xHC */ + FAR struct xhci_input_dev_ctx_s *input; /* Input Device Context. Input to xHC */ + FAR struct xhci_rhport_s *rhport; /* Root Hub Port associated with this device */ + + /* The port this device is attached to. Several devices can share a root + * hub port once a hub is in between, so this, and not the port above, is + * what identifies a device to the class drivers. + */ + + FAR struct usbhost_hubport_s *hport; + + /* True once the controller has been told this device is a hub. It is not + * known when the slot is created: the hub descriptor is read later, and + * only then does anything know how many ports it has. + */ + + bool ishub; + + /* Reference to allocated endpoints */ + + FAR struct xhci_epinfo_s *epinfo[XHCI_MAX_ENDPOINTS]; +}; + +/* This structure contains the internal, private state of the xhci driver */ + +struct usbhost_xhci_s +{ +#ifdef CONFIG_USBHOST_HUB + /* Ports a hub has reported and the waiter has not collected. A hub + * reports each changed port without waiting for the last, so several can + * be outstanding, but never more than there are slots. + */ + + FAR struct usbhost_hubport_s *hports[CONFIG_USBHOST_XHCI_MAX_DEVS]; + uint8_t hhead; /* Next free entry */ + uint8_t htail; /* Next entry to collect */ +#endif + struct usbhost_devaddr_s devgen; /* Address generation data */ + bool pscwait; /* TRUE: Thread is waiting for port status change event */ + sem_t pscsem; /* Semaphore to wait for port status change events */ + mutex_t lock; /* Support mutually exclusive access */ + spinlock_t spinlock; + + /* xHCI parameters */ + + uint8_t no_ports; /* Number of USB Ports */ + uint8_t no_slots; /* Maximum number of Device Slots (one per USB device) */ + uint8_t no_scratch; /* Number of scratch buffers */ + uint8_t no_erst; /* Event Ring Segment Table size */ + + /* xHCI data */ + + FAR struct xhci_rhport_s *rhport; /* Root hub ports */ + FAR struct xhci_dev_s *devs; /* USB device xHC data. One entry per + * one supported USB device. + */ + + /* Allocated buffers for controller */ + + FAR uint64_t *pg_ctx; /* Device Context (no_slots + 1 elements). + * Slot 0 reserved for Scratchpad Buffer Array + */ + FAR uint64_t *pg_sb; /* Scratchpad Buffer Array (no_scratch elements) */ + FAR struct xhci_event_ring_s *pg_erst; /* Event Ring Segment Table */ + + /* Event ring handling */ + + struct xhci_ring_s evnt; /* Event ring handler */ + + /* Command ring handling */ + + sem_t cmdsem; /* Command done semaphore */ + struct xhci_trb_s cmdres; /* Command result */ + struct xhci_ring_s cmd; /* Command ring handler */ + + /* Bus hookup, supplied by whoever found this controller */ + + FAR const struct xhci_bus_ops_s *ops; /* Bus operations */ + FAR void *arg; /* Bus private data */ + FAR const char *name; /* What to call this controller */ + uint8_t ctxsize; /* Context stride, 32 or 64 bytes */ + uint32_t pending; /* IRQ pending status */ + struct work_s work; /* IRQ work */ + struct work_s pscwork; /* Port status change work */ + uint64_t base; /* xHCI base address */ + uint64_t capa_base; /* Capability base */ + uint64_t oper_base; /* Operational base */ + uint64_t runt_base; /* Runtime base */ + uint64_t door_base; /* Doorbell base */ +}; + +/* xHCI connection monitoring */ + +struct usbhost_conn_xhci_s +{ + struct usbhost_connection_s conn; /* Connection monitoring */ + FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */ + int pid; /* Waiter thread PID */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Helpers ******************************************************************/ + +static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset); + +static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, + unsigned int offset); +static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint8_t value); + +static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset); +static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint32_t value); + +static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint64_t value); + +static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset); +static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint32_t value); + +static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint64_t value); + +static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint32_t value); + +/* Byte stream access helper functions **************************************/ + +static inline uint16_t xhci_getle16(FAR const uint8_t *val); + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_USB_INFO +static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv, + FAR const char *msg, unsigned int offset); +static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv, + FAR const char *msg, unsigned int offset); +static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv, + FAR const char *msg, unsigned int offset); +static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, + FAR const char *msg); +#endif + +/* Ring management **********************************************************/ + +static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len); +static void xhci_ring_deinit(FAR struct xhci_ring_s *ring); +static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs); +static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_ring_s *ring, + FAR struct xhci_trb_s *trb, + int len); + +/* xHCI operations **********************************************************/ + +static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv); +static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv); +static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv); +static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv); + +/* Port management **********************************************************/ + +static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv); +static FAR const char *xhci_speed_str(uint32_t portsc); +static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, + FAR struct usbhost_hubport_s *hport); + +/* Slot management **********************************************************/ + +static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, + uintptr_t ctx); +static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_ep_ctx_s *ctx, + uint8_t type, uint16_t maxpkt, + uint8_t maxburst, uint64_t tr_dp, + uint8_t mult, uint8_t interval); +static int xhci_address_set(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev, bool setaddr); +static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev); +#ifdef CONFIG_USBHOST_HUB +static uint32_t xhci_route_string(FAR struct usbhost_hubport_s *hport); +static uint32_t xhci_slot_tt(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev); +#endif +static int xhci_device_init(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_rhport_s *rhport, + FAR struct usbhost_hubport_s *hport, + FAR struct xhci_epinfo_s *ep0info); +static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev); +static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo); +static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev, + uint32_t drop, uint32_t add); + +/* Command handling *********************************************************/ + +static int xhci_command(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *trb, uint16_t timeout_ms); +static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *slot); +static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot); +static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx, bool bsr); +static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx, bool deconfig); +static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint8_t ep, bool suspend); +static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx); + +/* Transfer handling ********************************************************/ + +static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo); +static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + size_t buflen); +static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo); +#ifdef CONFIG_USBHOST_ASYNCH +static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + size_t buflen, + usbhost_asynch_t callback, + FAR void *arg); +static void xhci_asynch_completion(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo); +#endif +static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer, size_t buflen); +static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen); +#ifndef CONFIG_USBHOST_ISOC_DISABLE +static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen); +#endif +static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo); +static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *buffer, size_t buflen); +static uint32_t xhci_speed_id(uint8_t speed); +static inline FAR struct xhci_slot_ctx_s * +xhci_in_slot(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_input_dev_ctx_s *input); +static inline FAR struct xhci_ep_ctx_s * +xhci_in_ep(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_input_dev_ctx_s *input, int epidx); +static inline FAR struct xhci_slot_ctx_s * +xhci_out_slot(FAR struct xhci_dev_ctx_s *ctx); +static FAR uint8_t *xhci_dma_prepare(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen, + bool dirin); +static void xhci_dma_finish(FAR struct xhci_epinfo_s *epinfo); + +/* Interrupt handling *******************************************************/ + +static void xhci_portsc_work(FAR void *arg); +static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *evt); +static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *evt); +static int xhci_events_poll(FAR struct usbhost_xhci_s *priv); +static void xhci_interrupt_work(FAR void *arg); +static int xhci_interrupt(int irq, FAR void *context, FAR void *arg); + +/* USB host controller operations *******************************************/ + +static int xhci_wait(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s **hport); +static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport); +static int xhci_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport); + +static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, + uint8_t speed, uint16_t maxpacketsize); +static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_epdesc_s *epdesc, + FAR usbhost_ep_t *ep); +static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); +static int xhci_alloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, FAR size_t *maxlen); +static int xhci_free(FAR struct usbhost_driver_s *drvr, + FAR uint8_t *buffer); +static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, size_t buflen); +static int xhci_iofree(FAR struct usbhost_driver_s *drvr, + FAR uint8_t *buffer); + +static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer); +static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer); +static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR const uint8_t *buffer); +static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, + FAR usbhost_ep_t ep, uint8_t *buffer, + size_t buflen); +#ifdef CONFIG_USBHOST_ASYNCH +static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, + FAR uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, void *arg); +#endif +static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); +#ifdef CONFIG_USBHOST_HUB +static int xhci_connect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport, + bool connected); +#endif + +static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport); + +/* Initialization ***********************************************************/ + +static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv); +static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv); +static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv); +static int xhci_mem_free(FAR struct usbhost_xhci_s *priv); +static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv); +static int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Every register accessor below forces the value through a register with + * an empty asm. Access width is part of the register interface: xHCI + * requires aligned accesses of the register's own size, and a controller + * may ignore anything narrower (QEMU's does). A volatile load does not + * pin the width; GCC 16 at -Os narrows "load 32, test bit 0" to a byte + * load. A value demanded in a register can only come from the full-width + * access. The same constraint on stores stops a load-modify-store being + * folded back into one instruction. + */ + +/**************************************************************************** + * Name: xhci_capa_getreg + * + * Description: + * Get register (USB Legacy Support Capability) + * + ****************************************************************************/ + +static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset) +{ + uintptr_t addr = priv->capa_base + offset; + uint32_t regval = *((FAR volatile uint32_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; +} + +/**************************************************************************** + * Name: xhci_capa_getreg_1b + * + * Description: + * Get 1B register (USB Legacy Support Capability) + * + ****************************************************************************/ + +static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, + unsigned int offset) +{ + uintptr_t addr = priv->capa_base + offset; + uint8_t regval = *((FAR volatile uint8_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; +} + +/**************************************************************************** + * Name: xhci_capa_putreg_1b + * + * Description: + * Put 1B register (USB Legacy Support Capability) + * + ****************************************************************************/ + +static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint8_t value) +{ + uintptr_t addr = priv->capa_base + offset; + + __asm__ __volatile__("" : "+r"(value)); + *((FAR volatile uint8_t *)addr) = value; +} + +/**************************************************************************** + * Name: xhci_oper_getreg + * + * Description: + * Get register (Host Controller Operational Registers) + * + ****************************************************************************/ + +static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset) +{ + uintptr_t addr = priv->oper_base + offset; + uint32_t regval = *((FAR volatile uint32_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; +} + +/**************************************************************************** + * Name: xhci_oper_putreg + * + * Description: + * Put register (Host Controller Operational Registers) + * + ****************************************************************************/ + +static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint32_t value) +{ + uintptr_t addr = priv->oper_base + offset; + + __asm__ __volatile__("" : "+r"(value)); + *((FAR volatile uint32_t *)addr) = value; +} + +/**************************************************************************** + * Name: xhci_oper_putreg_8b + * + * Description: + * Put register (Host Controller Operational Registers) + * + ****************************************************************************/ + +static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint64_t value) +{ + uintptr_t addr = priv->oper_base + offset; + + __asm__ __volatile__("" : "+r"(value)); + *((FAR volatile uint64_t *)addr) = value; +} + +/**************************************************************************** + * Name: xhci_runt_getreg + * + * Description: + * Get register (Host Controller Runtime Registers) + * + ****************************************************************************/ + +static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset) +{ + uintptr_t addr = priv->runt_base + offset; + uint32_t regval = *((FAR volatile uint32_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; +} + +/**************************************************************************** + * Name: xhci_runt_putreg + * + * Description: + * Put register (Host Controller Runtime Registers) + * + ****************************************************************************/ + +static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint32_t value) +{ + uintptr_t addr = priv->runt_base + offset; + + __asm__ __volatile__("" : "+r"(value)); + *((FAR volatile uint32_t *)addr) = value; +} + +/**************************************************************************** + * Name: xhci_runt_putreg_8b + * + * Description: + * Put register (Host Controller Runtime Registers) + * + ****************************************************************************/ + +static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint64_t value) +{ + uintptr_t addr = priv->runt_base + offset; + + __asm__ __volatile__("" : "+r"(value)); + *((FAR volatile uint64_t *)addr) = value; +} + +/**************************************************************************** + * Name: xhci_door_putreg + * + * Description: + * Put register (Doorbell Registers) + * + ****************************************************************************/ + +static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint32_t value) +{ + uintptr_t addr = priv->door_base + offset; + + __asm__ __volatile__("" : "+r"(value)); + *((FAR volatile uint32_t *)addr) = value; +} + +#ifdef CONFIG_DEBUG_USB_INFO +/**************************************************************************** + * Name: xhci_dump_capa_reg + ****************************************************************************/ + +static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv, + FAR const char *msg, unsigned int offset) +{ + uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_capa_getreg(priv, offset)); +} + +/**************************************************************************** + * Name: xhci_dump_oper_reg + ****************************************************************************/ + +static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv, + FAR const char *msg, unsigned int offset) +{ + uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_oper_getreg(priv, offset)); +} + +/**************************************************************************** + * Name: xhci_dump_runt_reg + ****************************************************************************/ + +static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv, + FAR const char *msg, unsigned int offset) +{ + uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_runt_getreg(priv, offset)); +} + +/**************************************************************************** + * Name: xhci_dump_mem + ****************************************************************************/ + +static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, + FAR const char *msg) +{ + int i; + + uinfo("Dump xHCI registers: %s\n", msg); + + uinfo("=== Host Controller Capability Registers ===\n"); + + /* CAPLENGTH and HCIVERSION share one word, and a register block reached + * over a bus that only answers aligned accesses cannot be read at the + * odd offset the second one has. Read the word once and take both from + * it. + */ + + uinfo("\tCAPLENGTH :\t\t0x%" PRIx32 "\n", + xhci_capa_getreg(priv, XHCI_CAPLENGTH) & 0xff); + uinfo("\tHCIVERSION :\t\t0x%" PRIx32 "\n", + xhci_capa_getreg(priv, XHCI_CAPLENGTH) >> 16); + xhci_dump_capa_reg(priv, "HCSPARAMS1 ", XHCI_HCSPARAMS1); + xhci_dump_capa_reg(priv, "HCSPARAMS2 ", XHCI_HCSPARAMS2); + xhci_dump_capa_reg(priv, "HCSPARAMS3 ", XHCI_HCSPARAMS3); + xhci_dump_capa_reg(priv, "HCCPARAMS1 ", XHCI_HCCPARAMS1); + xhci_dump_capa_reg(priv, "DBOFF ", XHCI_DBOFF); + xhci_dump_capa_reg(priv, "RTSOFF ", XHCI_RTSOFF); + xhci_dump_capa_reg(priv, "HCCPARAMS2 ", XHCI_HCCPARAMS2); + + uinfo("=== Host Controller Operational Registers ===\n"); + xhci_dump_oper_reg(priv, "USBCMD ", XHCI_USBCMD); + xhci_dump_oper_reg(priv, "USBSTS ", XHCI_USBSTS); + xhci_dump_oper_reg(priv, "PAGESIZE ", XHCI_PAGESIZE); + xhci_dump_oper_reg(priv, "DNCTRL ", XHCI_DNCTRL); + xhci_dump_oper_reg(priv, "CRCR ", XHCI_CRCR); + xhci_dump_oper_reg(priv, "DCBAAP ", XHCI_DCBAAP); + xhci_dump_oper_reg(priv, "CONFIG ", XHCI_CONFIG); + + for (i = 0; i < priv->no_ports; i++) + { + uinfo("port %d --------------------------------\n", i); + xhci_dump_oper_reg(priv, "PORTSC ", XHCI_PORTSC(i)); + xhci_dump_oper_reg(priv, "PORTPMSC ", XHCI_PORTPMSC(i)); + xhci_dump_oper_reg(priv, "PORTLI ", XHCI_PORTLI(i)); + } + + /* Only one interrupter used */ + + uinfo("=== Host Controller Runtime Registers ===\n"); + xhci_dump_runt_reg(priv, "MFINDEX ", XHCI_MFINDEX); + xhci_dump_runt_reg(priv, "IMAN(0) ", XHCI_IMAN(0)); + xhci_dump_runt_reg(priv, "IMOD(0) ", XHCI_IMOD(0)); + xhci_dump_runt_reg(priv, "ERSTSZ(0) ", XHCI_ERSTSZ(0)); + xhci_dump_runt_reg(priv, "ERSTBA(0) ", XHCI_ERSTBA(0)); + xhci_dump_runt_reg(priv, "ERDP(0) ", XHCI_ERDP(0)); +} +#endif + +/**************************************************************************** + * Name: xhci_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + ****************************************************************************/ + +static inline uint16_t xhci_getle16(FAR const uint8_t *val) +{ +#ifdef CONFIG_ENDIAN_BIG + return (uint16_t)val[0] << 8 | (uint16_t)val[1]; +#else + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +#endif +} + +/**************************************************************************** + * Name: xhci_ring_init + * + * Description: + * Initialize xHCI ring handler. + * + * If ring buffer is already initialized, this function reset ring + * to a initial state. + * + * Returned Value: + * OK on success. + * + ****************************************************************************/ + +static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len) +{ + FAR struct xhci_trb_s *trb; + + if (!ring->ring) + { + /* Allocate ring data */ + + ring->ring = kmm_memalign(XHCI_BUF_ALIGN, + sizeof(struct xhci_trb_s) * len); + if (!ring->ring) + { + return -ENOMEM; + } + + /* Store length */ + + ring->len = len; + } + + /* Reset data in ring. + * + * Clearing dirties every line, and the controller writes into this + * memory itself. Flush now, or a later writeback lands on top of an + * event somebody is waiting for. + */ + + memset(ring->ring, 0, ring->len * sizeof(struct xhci_trb_s)); + up_flush_dcache((uintptr_t)ring->ring, + (uintptr_t)(ring->ring + ring->len)); + + /* Fill Link TRB */ + + trb = &ring->ring[ring->len - 1]; + trb->d0 = htole64(up_addrenv_va_to_pa(&ring->ring[0])); + trb->d1 = 0; + trb->d2 = 0; + + up_flush_dcache((uintptr_t)trb, (uintptr_t)(trb + 1)); + + /* Reset state */ + + ring->i = 0; + ring->ccs = true; + + return OK; +} + +/**************************************************************************** + * Name: xhci_ring_deinit + * + * Description: + * Initialize xHCI ring handler. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void xhci_ring_deinit(FAR struct xhci_ring_s *ring) +{ + /* Free ring memory */ + + kmm_free(ring->ring); +} + +/**************************************************************************** + * Name: xhci_ring_reset + * + * Description: + * Reset xHCI ring handler. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs) +{ + /* Reset pointer */ + + ring->i = 0; + + /* Swap CCS if requestede */ + + if (swap_ccs) + { + ring->ccs = !ring->ccs; + } + else + { + ring->ccs = true; + } +} + +/**************************************************************************** + * Name: xhci_add_trb + * + * Description: + * Reset TRB to a ring. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_ring_s *ring, + FAR struct xhci_trb_s *trb, + int len) +{ + uint32_t d2; + int i; + + for (i = 0; i < len; i++) + { + d2 = trb[i].d2; + + if (ring->ccs) + { + d2 |= XHCI_TRB_D2_C; + } + else + { + d2 &= ~XHCI_TRB_D2_C; + } + + /* Make sure the cycle bit has the correct value */ + + DEBUGASSERT((ring->ring[ring->i].d2 & XHCI_TRB_D2_C) != ring->ccs); + + /* Write TRB */ + + ring->ring[ring->i].d0 = htole64(trb[i].d0); + ring->ring[ring->i].d1 = htole32(trb[i].d1); + ring->ring[ring->i].d2 = htole32(d2); + + /* Next TD */ + + ring->i++; + + /* Handle end of the command ring */ + + if (ring->i >= ring->len - 1) + { + /* Make sure the cycle bit has the correct value */ + + DEBUGASSERT((ring->ring[0].d2 & XHCI_TRB_D2_C) == ring->ccs); + + if (ring->ccs) + { + d2 = XHCI_TRB_D2_C | XHCI_TRB_D2_TC | + XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK); + } + else + { + d2 = XHCI_TRB_D2_TC | + XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK); + } + + /* Carry the chain forward across the join. + * + * A multi-TRB transfer can reach the end of the ring part way + * through, putting the link inside it. A link without the + * chain bit ends the transfer where it stands, and the TRB that + * asked for the completion interrupt is never reached. + */ + + if ((trb[i].d2 & XHCI_TRB_D2_CH) != 0) + { + d2 |= XHCI_TRB_D2_CH; + } + + /* Other parameters are already correct for this TRB */ + + ring->ring[ring->i].d2 = htole32(d2); + + /* Update CCS */ + + xhci_ring_reset(ring, true); + } + } + + /* Flush ring */ + + up_flush_dcache((uintptr_t)ring->ring, + (uintptr_t)(ring->ring + ring->len)); +} + +/**************************************************************************** + * Name: xhci_bios_wait + * + * Description: + * Wait for BIOS to give up the controller lock + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv) +{ + uint32_t cstart; + uint32_t ecp; + uint32_t eec; + uint8_t sem; + uint8_t timeout; + int ret = OK; + + /* Get Extended Capability Pointer */ + + cstart = XHCI_HCCPARAMS1_XECP(xhci_capa_getreg(priv, XHCI_HCCPARAMS1)); + + /* Find USBLEGSUP - if present, we have to acquire for BIOS semaphore */ + + eec = -1; + ecp = (cstart << 2); + + while (1) + { + if (ecp == 0 || XHCI_USBLEGSUP_NEXT(eec) == 0) + { + break; + } + + eec = xhci_capa_getreg(priv, ecp); + + if (XHCI_USBLEGSUP_ID(eec) == XHCI_ID_USBLEGSUP) + { + /* We have to wait for semaphore */ + + ret = -EAGAIN; + + /* Get BIOS semaphore */ + + sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM); + if (sem == 0) + { + ret = OK; + break; + } + + /* Get semaphore request */ + + xhci_capa_putreg_1b(priv, ecp + XHCI_USBLEGSUP_OS_SEM, 1); + + /* Wait for semaphore released from BIOS */ + + for (timeout = 0; timeout < 100; timeout++) + { + sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM); + if (sem == 0) + { + ret = OK; + break; + } + + up_mdelay(100); + } + } + + /* Next cap */ + + ecp += (XHCI_USBLEGSUP_NEXT(eec) << 2); + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_ctrl_start + * + * Description: + * Start controller. + * + * According to "4.2 Host Controller Initialization". + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) +{ + FAR struct xhci_event_ring_s *evnt; + uint32_t regval; + int ret; + int i; + + uinfo("Start controller\n"); + + /* Reset controller before writing any Operational or Runtime registers */ + + ret = xhci_ctrl_reset(priv); + if (ret < 0) + { + usbhost_trace1(XHCI_TRACE1_RESET_FAILED, 0); + return ret; + } + + /* TODO: clear interrupts and disable all device notifications */ + + /* Max Device Slots Enabled */ + + xhci_oper_putreg(priv, XHCI_CONFIG, priv->no_slots); + + /* Slot 0 of the Device Context array points at the Scratchpad Buffer + * Array, or is zero when the controller asked for none. + */ + + priv->pg_ctx[0] = priv->pg_sb ? + htole64(up_addrenv_va_to_pa(priv->pg_sb)) : 0; + + /* Device Context Base Address Array Pointer */ + + xhci_oper_putreg_8b(priv, XHCI_DCBAAP, up_addrenv_va_to_pa(priv->pg_ctx)); + + /* Event Ring Segment Table Size */ + + xhci_runt_putreg(priv, XHCI_ERSTSZ(0), priv->no_erst); + + /* Initialize command ring state and event ring state */ + + ret = xhci_ring_init(&priv->cmd, XHCI_CMD_MAX); + if (ret < 0) + { + uerr("cmd ring init failed\n"); + return ret; + } + + ret = xhci_ring_init(&priv->evnt, XHCI_EVENT_MAX); + if (ret < 0) + { + uerr("event ring init failed\n"); + return ret; + } + + /* Configure Event Ring */ + + evnt = (struct xhci_event_ring_s *)priv->pg_erst; + evnt->base = htole64(up_addrenv_va_to_pa(priv->evnt.ring)); + evnt->size = XHCI_EVENT_MAX; + evnt->res = 0; + + /* Push the structures the controller is about to be pointed at. + * + * Flush by address: up_flush_dcache_all() is a no-op on architectures + * whose cache can only be maintained by address. + */ + + up_flush_dcache((uintptr_t)priv->pg_erst, + (uintptr_t)priv->pg_erst + + sizeof(struct xhci_event_ring_s) * priv->no_erst); + up_flush_dcache((uintptr_t)priv->pg_ctx, + (uintptr_t)(priv->pg_ctx + priv->no_slots + 1)); + if (priv->pg_sb != NULL) + { + up_flush_dcache((uintptr_t)priv->pg_sb, + (uintptr_t)(priv->pg_sb + priv->no_scratch)); + } + + xhci_runt_putreg_8b(priv, XHCI_ERDP(0), + up_addrenv_va_to_pa(priv->evnt.ring)); + + /* Write ERSTBA with ERST(0).BaseAddress. + * + * This must be done after ERST[0] initialization and after write to + * ERSTSZ. When the ERSTBA register is written, the Event Ring State + * Machine is set to the Start state. + * + * For details look at "4.9.4 Event Ring Management" + */ + + xhci_runt_putreg_8b(priv, XHCI_ERSTBA(0), + up_addrenv_va_to_pa(priv->pg_erst)); + + /* Last item in the command ring points to the beginning of the ring */ + + priv->cmd.ring[XHCI_CMD_MAX - 1].d0 = htole64( + up_addrenv_va_to_pa(priv->cmd.ring)); + + /* Configure the Command Ring */ + + xhci_oper_putreg_8b(priv, XHCI_CRCR, + up_addrenv_va_to_pa(priv->cmd.ring) | XHCI_CRCR_RCS); + + /* Do not sit on completions; see XHCI_IMOD_INTERVAL */ + + xhci_runt_putreg(priv, XHCI_IMOD(0), XHCI_IMOD_DEFAULT); + + /* Enable interrupts */ + + regval = xhci_runt_getreg(priv, XHCI_IMAN(0)); + regval |= XHCI_IMAN_IE; + xhci_runt_putreg(priv, XHCI_IMAN(0), regval); + + /* And the command ring, whose last entry was just made to point back at + * its own beginning. + */ + + up_flush_dcache((uintptr_t)priv->cmd.ring, + (uintptr_t)(priv->cmd.ring + XHCI_CMD_MAX)); + + /* Turn the host controller ON, enable interrupts and system errors */ + + xhci_oper_putreg(priv, XHCI_USBCMD, + XHCI_USBCMD_RS | + XHCI_USBCMD_INTE | + XHCI_USBCMD_HSEE); + + /* Wait for controller started */ + + ret = -EAGAIN; + for (i = 0; i < 10; i++) + { + up_mdelay(100); + + if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH)) + { + ret = OK; + break; + } + } + + /* Check for timeout */ + + if (ret != OK) + { + uerr("Can't start controller!"); + return ret; + } + + /* Poll all pending events */ + + xhci_events_poll(priv); + + return OK; +} + +/**************************************************************************** + * Name: xhci_ctrl_halt + * + * Description: + * Halt controller. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv) +{ + uint32_t regval; + int i; + + /* A controller that was never started is already halted and says so. + * There is no transition to wait for, so check before waiting. + */ + + regval = xhci_oper_getreg(priv, XHCI_USBSTS); + if ((regval & XHCI_USBSTS_HCH) != 0) + { + return OK; + } + + /* Clear Run/Stop and leave the rest of the register alone. Writing the + * whole of it zero would clear the interrupt and host system error + * enables along with it. + */ + + regval = xhci_oper_getreg(priv, XHCI_USBCMD); + regval &= ~XHCI_USBCMD_RS; + xhci_oper_putreg(priv, XHCI_USBCMD, regval); + + for (i = 0; i < XHCI_HALT_TIMEOUT_MS; i++) + { + regval = xhci_oper_getreg(priv, XHCI_USBSTS); + if ((regval & XHCI_USBSTS_HCH) != 0) + { + return OK; + } + + up_udelay(1000); + } + + uerr("controller will not halt, USBSTS %08" PRIx32 "\n", regval); + return -EAGAIN; +} + +/**************************************************************************** + * Name: xhci_ctrl_reset + * + * Description: + * Reset controller. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv) +{ + int ret = -EAGAIN; + int i; + + /* Halt controller */ + + xhci_oper_putreg(priv, XHCI_USBCMD, XHCI_USBCMD_HCRST); + + /* Wait for controller halted */ + + for (i = 0; i < 10; i++) + { + up_mdelay(100); + + if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_CNR)) + { + ret = OK; + break; + } + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_probe_ports + * + * Description: + * Initial ports probe. + * + ****************************************************************************/ + +static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv) +{ + uint32_t portsc; + int i; + + for (i = 0; i < priv->no_ports; i++) + { + portsc = xhci_oper_getreg(priv, XHCI_PORTSC(i)); + priv->rhport[i].connected = ((portsc & XHCI_PORTSC_CCS) != 0); + + /* Clear status change, but not PED. Port Enabled/Disabled is + * write-one-to-clear, so writing back what was read disables any + * port that came up enabled, which is what a device attached at + * power up does. + */ + + portsc &= ~XHCI_PORTSC_PED; + xhci_oper_putreg(priv, XHCI_PORTSC(i), portsc); + } +} + +/**************************************************************************** + * Name: xhci_port_enable + * + * Description: + * Set port to the Enable state. + * + ****************************************************************************/ + +static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, + FAR struct usbhost_hubport_s *hport) +{ + uint32_t retries; + uint32_t regval; + uint8_t speed; + int rhpndx; + + DEBUGASSERT(hport != NULL); + rhpndx = hport->port; + + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + + /* A USB3 protocol port attempts to automatically advance to the + * Enabled state for port as part of the attach process. + */ + + if (!(regval & XHCI_PORTSC_PED)) + { + /* Reset the port, masking the write-one-to-clear bits out of the + * value first. See XHCI_PORTSC_RW1C. + */ + + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + regval &= ~XHCI_PORTSC_RW1C; + regval |= XHCI_PORTSC_PR; + xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), regval); + + /* REVISIT: we get Port Status Change Event here */ + + /* Wait for Enabled state for port */ + + for (retries = XHCI_PORT_RESET_MS; retries > 0; retries--) + { + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + if ((regval & XHCI_PORTSC_PED) != 0) + { + break; + } + + up_mdelay(1); + } + + /* Test the port, not the counter: a port that comes up on the last + * attempt leaves the loop with the count exhausted too. + */ + + if ((regval & XHCI_PORTSC_PED) == 0) + { + uerr("port %d will not enable, PORTSC %08" PRIx32 "\n", rhpndx, + regval); + return -ETIMEDOUT; + } + } + + /* Get port status */ + + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + + /* Get port speed */ + + speed = XHCI_PORTSC_PS(regval); + switch (speed) + { + case XHCI_PORTSC_PS_FULL: + { + hport->speed = USB_SPEED_FULL; + break; + } + + case XHCI_PORTSC_PS_LOW: + { + hport->speed = USB_SPEED_LOW; + break; + } + + case XHCI_PORTSC_PS_HIGH: + { + hport->speed = USB_SPEED_HIGH; + break; + } + + case XHCI_PORTSC_PS_SUPPER11: + { + hport->speed = USB_SPEED_SUPER; + break; + } + + case XHCI_PORTSC_PS_SUPPER21: + case XHCI_PORTSC_PS_SUPPER12: + case XHCI_PORTSC_PS_SUPPER22: + { + hport->speed = USB_SPEED_SUPER_PLUS; + break; + } + + default: + { + uerr("speed = 0x%x\n", speed); + hport->speed = USB_SPEED_UNKNOWN; + return -EINVAL; + } + } + + /* Say what turned up, now that the port can answer. + * + * The speed field only means anything once the port has been reset and + * enabled. A USB2 port reports the reset default, full speed, until + * then. + */ + + syslog(LOG_INFO, "%s: port %d: device attached at %s\n", + priv->name, rhpndx + 1, xhci_speed_str(regval)); + + return OK; +} + +/**************************************************************************** + * Name: xhci_dcbaa_set + * + * Description: + * Set entry in the Device Context Base Address Array, which should point + * to the Output Device Context data structure. + * + ****************************************************************************/ + +static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, + uintptr_t ctx) +{ + /* NOTE: context must be physical address! */ + + priv->pg_ctx[index] = htole64(ctx); + + /* Flush context */ + + up_flush_dcache((uintptr_t)priv->pg_ctx, + (uintptr_t)(priv->pg_ctx + priv->no_slots + 1)); +} + +/**************************************************************************** + * Name: xhci_ep_configure + * + * Description: + * Configure endpoint context. + * + * Reference: + * - 4.8.2 Endpoint Context Initialization + * - 6.2.3 Endpoint Context + * + ****************************************************************************/ + +static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_ep_ctx_s *ctx, + uint8_t type, uint16_t maxpkt, + uint8_t maxburst, uint64_t tr_dp, + uint8_t mult, uint8_t interval) +{ + uint32_t ctx0 = 0; + uint32_t ctx1 = 0; + uint64_t ctx2 = 0; + + /* Set type */ + + ctx1 |= XHCI_EP_CTX1_EPTYPE(type); + + /* Set max packet size */ + + ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpkt); + + /* Set max burst size */ + + ctx1 |= XHCI_EP_CTX1_MAXBRST(maxburst); + + /* Set TR Dequeue Pointer. + * NOTE: must be physical address aligned to 16-byte. + */ + + DEBUGASSERT(tr_dp != 0 && tr_dp % 16 == 0); + ctx2 = tr_dp; + + /* Set DCS. + * Should be set to 1 only if no stream (USB3.0 specific). + */ + + ctx2 |= XHCI_EP_CTX2_DCS; + + /* Set interval */ + + ctx0 |= XHCI_EP_CTX0_INTERVAL(interval); + + /* Set max primary streams. + * Set to zero for now (USB3.0 specific) + */ + + ctx0 |= XHCI_EP_CTX0_MAXPSTR(0); + + /* Set mult */ + + ctx0 |= XHCI_EP_CTX0_MULT(mult); + + /* Set error count to 3 if this is not ISOCH endpoint */ + + if (type != XHCI_EPTYPE_ISO_OUT && type != XHCI_EPTYPE_ISO_IN) + { + ctx1 |= XHCI_EP_CTX1_CERR(3); + } + + /* Write context */ + + ctx->ctx0 = htole32(ctx0); + ctx->ctx1 = htole32(ctx1); + ctx->ctx2 = htole64(ctx2); + + /* Flush context */ + + up_flush_dcache((uintptr_t)ctx, + (uintptr_t)ctx + sizeof(struct xhci_ep_ctx_s)); +} + +/**************************************************************************** + * Name: xhci_address_set + * + * Description: + * Set address request. + * + * If setaddr is true, then xHC issue a SET_ADDRESS request. + * + ****************************************************************************/ + +static int xhci_address_set(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev, bool setaddr) +{ + uint64_t ctx = up_addrenv_va_to_pa(dev->input); + + return xhci_cmd_setaddr(priv, dev->slot, ctx, !setaddr); +} + +/**************************************************************************** + * Name: xhci_slot_init + * + * Description: + * Initialize Device Slot data. + * + * Assumption: + * 1. All slot resources already allocated. + * 2. Port is in Enabled state. + * + * Reference: + * - 4.3.3. Device Slot Initialization + * + ****************************************************************************/ + +static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev) +{ + uint32_t regval; + uint16_t maxpkt; + uintptr_t drdp; + + /* Step 1. The Input Context data structure already allocated. + * Initialize all fields to 0. + */ + + memset(dev->input, 0, XHCI_INCTX_SIZE(priv)); + + /* Step 2. Initialize the Input Control Context by setting the A0 and + * A1 flags to 1 (Slot flag and EP0 flag). + */ + + regval = XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | + XHCI_IN_CTX1_A(XHCI_EP0_FLAG); + xhci_context_ctrl(priv, dev, 0, regval); + + /* Step 3. Initialize the Input Slot Context. + * + * The speed field has no valid zero. This is the only place the + * controller learns the device's speed, and one that checks refuses + * Address Device with a parameter error without it. + */ + + regval = XHCI_ST_CTX0_CTXENT_SET(1) | + XHCI_ST_CTX0_SPEED_SET(xhci_speed_id(dev->hport->speed)); + +#ifdef CONFIG_USBHOST_HUB + regval |= XHCI_ST_CTX0_RTSTR_SET(xhci_route_string(dev->hport)); + + /* TODO: + * 1. Activate the transaction translator if required + * 2. Configure hub bit in slot context if hub + */ + +# warning missing logic +#endif + + xhci_in_slot(priv, dev->input)->ctx[0] = htole32(regval); + + /* Configure Root Hub Port Number (starts from 1) */ + + regval = XHCI_ST_CTX1_RHPN_SET(RHPNDX(dev->rhport) + 1); + + /* TODO: configure number of ports */ + + regval |= XHCI_ST_CTX1_PORTS_SET(0); + xhci_in_slot(priv, dev->input)->ctx[1] = htole32(regval); + +#ifdef CONFIG_USBHOST_HUB + xhci_in_slot(priv, dev->input)->ctx[2] = htole32(xhci_slot_tt(priv, dev)); +#endif + + /* Step 4. the Transfer Ring for the Default Control Endpoint is already + * allocated. + */ + + DEBUGASSERT(dev->epinfo[0] != NULL); + drdp = up_addrenv_va_to_pa(dev->epinfo[0]->td.ring); + + /* Step 5. Initialize the Input default control Endpoint 0 Context */ + + DEBUGASSERT(dev->hport != NULL); + if (dev->hport->speed == USB_SPEED_HIGH) + { + /* For high-speed, we must use 64 bytes */ + + maxpkt = 64; + } + else + { + /* Eight will work for both low- and full-speed */ + + maxpkt = 8; + } + + DEBUGASSERT(drdp != 0); + xhci_ep_configure(priv, + xhci_in_ep(priv, dev->input, 0), + XHCI_EPTYPE_CTRL, maxpkt, + 0, drdp, + 0, 0); + + /* Step 6. The output Device Context data structure already allocated. + * Initialize all fields to 0. + */ + + memset(dev->ctx, 0, XHCI_DEVCTX_SIZE(priv)); + + /* Flush both contexts. + * + * The output context is the controller's to write, so clearing it must + * reach memory: the dirty zeros left in cache are written back later, on + * top of what the controller has put there. The slot state lives in + * that context, and losing it fails the next command against the slot. + */ + + up_flush_dcache((uintptr_t)dev->ctx, + (uintptr_t)dev->ctx + XHCI_DEVCTX_SIZE(priv)); + + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + + XHCI_INCTX_SIZE(priv)); + + /* Step 7. Load the appropriate (Device Slot ID) entry in the Device + * Context Base Address Array with a pointer to the Output Device + * Context data structure + */ + + xhci_dcbaa_set(priv, dev->slot, up_addrenv_va_to_pa(dev->ctx)); + + return OK; +} + +/**************************************************************************** + * Name: xhci_device_init + * + * Description: + * Initialize Device. + * + * Assumption: + * 1. All device resources already allocated. + * 2. Port is in Enabled state. + * + * Reference: + * - 4.3: USB Device Initialization + * + ****************************************************************************/ + +static int xhci_device_init(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_rhport_s *rhport, + FAR struct usbhost_hubport_s *hport, + FAR struct xhci_epinfo_s *ep0info) +{ + FAR struct xhci_dev_s *dev; + uint8_t slot; + int ret; + + /* We enter this function after steps 1-3 from "USB Device Initialization" + * are done. + */ + + /* Step 4: Get Device Slot. + * + * Reference: + * - 4.3.2: Device Slot Assignment + */ + + ret = xhci_cmd_sloten(priv, &slot); + if (ret < 0 || slot > priv->no_slots) + { + /* A slot the controller cannot address is no more usable than no + * slot at all, and the command itself succeeds in that case, so the + * caller needs an error either way. + */ + + if (ret >= 0) + { + ret = -EINVAL; + } + + usbhost_vtrace1(XHCI_TRACE1_SLOTEN_FAILED, ret); + return ret; + } + + /* Slot ID is an index to the identify Device data */ + + dev = &priv->devs[slot - 1]; + + /* A root hub port names the device on it, which a hub port must not + * disturb: the device its root port names is the hub itself. + */ + + if (hport == &rhport->hport.hport) + { + rhport->dev = dev; + rhport->slot = slot; + } + + /* Slot has been allocated to software and is now in Enabled state */ + + dev->state = XHCI_SLOT_ENABLED; + + /* Step 5: Initialize the data structures associated with the slot. + * All data structured are already allocated. + */ + + ret = xhci_ring_init(&ep0info->td, XHCI_TD_MAX); + if (ret < 0) + { + uerr("ep0 ring init failed\n"); + goto errout_with_slot; + } + + ep0info->slot = slot; + dev->rhport = rhport; + dev->hport = hport; + dev->slot = slot; + dev->epinfo[0] = ep0info; + + ret = xhci_slot_init(priv, dev); + if (ret < 0) + { + goto errout_with_slot; + } + + /* Step 6: Assign and address to the device and enable its Default + * Control Endpoint. + * + * NOTE: we don't send SET_ADDRESS request here. + * This is done in xhci_ctrlin() and controlled by NuttX USB Host + * stack. + */ + + ret = xhci_address_set(priv, dev, false); + if (ret < 0) + { + uerr("failed to set address %d\n", ret); + goto errout_with_slot; + } + + /* Steps 7-12 don't belong here! */ + + return OK; + +errout_with_slot: + + /* Nothing else gives the slot back, and the controller has a fixed + * number of them. + */ + + xhci_device_deinit(priv, dev); + return ret; +} + +/**************************************************************************** + * Name: xhci_device_deinit + * + * Description: + * Free Device. + * + * Reference: + * - 4.3: USB Device Initialization + * + ****************************************************************************/ + +static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev) +{ + uint8_t slot = dev->slot; + int ret; + + /* Disable Slot */ + + ret = xhci_cmd_slotdis(priv, slot); + if (ret < 0) + { + uerr("xhci_cmd_slotdis failed %d\n", ret); + } + + /* Clear DCBAA entry for this slot */ + + xhci_dcbaa_set(priv, slot, 0); + + /* Clean up device data, but don't touch allocated memory! */ + + dev->state = XHCI_SLOT_DISABLED; + + memset(dev->ctx, 0, XHCI_DEVCTX_SIZE(priv)); + memset(dev->input, 0, XHCI_INCTX_SIZE(priv)); + + /* And push both, so nothing is left to be written back later */ + + up_flush_dcache((uintptr_t)dev->ctx, + (uintptr_t)dev->ctx + XHCI_DEVCTX_SIZE(priv)); + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + XHCI_INCTX_SIZE(priv)); + + /* Remove reference to a device slot. Only the device sitting directly + * on the root port is the one that port points at; a device behind a hub + * must leave that pointing at the hub. + */ + + if (dev->rhport != NULL && dev->rhport->dev == dev) + { + dev->rhport->dev = NULL; + } + + dev->hport = NULL; + dev->rhport = NULL; + + return OK; +} + +/**************************************************************************** + * Name: xhci_epno_get + * + * Description: + * Get EP index for a given endpoint. + * + * Returns Device Context Index (DCI). + * + ****************************************************************************/ + +static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo) +{ + DEBUGASSERT(epinfo); + + if (epinfo->epno == 0) + { + return 1; + } + + if (epinfo->dirin) + { + return epinfo->epno * 2 + 1; + } + else + { + return epinfo->epno * 2; + } +} + +/**************************************************************************** + * Name: xhci_context_ctrl + * + * Description: + * Configure Input Control Context, which defines which Device Context + * data structures are affected by a command. + * + * Assumption: + * Input Context must be flushed by caller. + * + ****************************************************************************/ + +static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev, + uint32_t drop, uint32_t add) +{ + int i; + + dev->input->input.ctx[0] = htole32(drop); + dev->input->input.ctx[1] = htole32(add); + + /* Update Context Entries in Slot */ + + for (i = 31; i != 1; i--) + { + if (add & (1 << i)) + { + break; + } + } + + xhci_in_slot(priv, dev->input)->ctx[0] &= ~XHCI_ST_CTX0_CTXENT_MASK; + xhci_in_slot(priv, dev->input)->ctx[0] |= XHCI_ST_CTX0_CTXENT_SET(i); +} + +/**************************************************************************** + * Name: xhci_command + * + * Description: + * Issue a xHCI command. + * + * NOTE: + * trb data in host specific byte order. This function converts it + * to a correct order + * + ****************************************************************************/ + +static int xhci_command(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *trb, uint16_t timeout_ms) +{ + uint32_t cmdtype; + int ret; + + /* Lock bus */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Remember what this was before the result overwrites it */ + + cmdtype = XHCI_TRB_D2_TYPE_GET(trb->d2); + + /* Add command to ring */ + + xhci_add_trb(priv, &priv->cmd, trb, 1); + + /* Ringing the Host Controller Doorbell */ + + xhci_door_putreg(priv, XHCI_DOORBEL(0), 0); + + /* Wait for Command Completion Event */ + + ret = nxsem_tickwait_uninterruptible(&priv->cmdsem, + MSEC2TICK(timeout_ms)); + if (ret < 0) + { + /* Check for missed interrupts */ + + xhci_events_poll(priv); + } + + /* Return command results */ + + trb->d0 = priv->cmdres.d0; + trb->d1 = priv->cmdres.d1; + trb->d2 = priv->cmdres.d2; + + if (XHCI_TRB_D1_CC_GET(trb->d1) == XHCI_TRB_CC_SUCCESS) + { + /* The result is the completion event's, not whether we were woken + * for it. A completion found by the poll above still counts. + */ + + ret = OK; + } + else + { + uerr("command type %d failed, CC = %d\n", cmdtype, + XHCI_TRB_D1_CC_GET(trb->d1)); + ret = -EIO; + } + + /* Clean response */ + + priv->cmdres.d0 = 0; + priv->cmdres.d1 = 0; + priv->cmdres.d2 = 0; + + /* Unlock bus */ + + nxmutex_unlock(&priv->lock); + + return ret; +} + +/**************************************************************************** + * Name: xhci_cmd_sloten + * + * Description: + * Enable Slot Command. + * + ****************************************************************************/ + +static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *slot) +{ + struct xhci_trb_s trb; + int ret; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = 0; + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EN_SLOT); + + ret = xhci_command(priv, &trb, 1000); + if (ret < 0) + { + *slot = 0; + return ret; + } + + /* Return available slot */ + + *slot = XHCI_TRB_D2_SLOTID_GET(le32toh(trb.d2)); + + return OK; +} + +/**************************************************************************** + * Name: xhci_cmd_slotdis + * + * Description: + * Disable Slot Command. + * + ****************************************************************************/ + +static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = 0; + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DIS_SLOT) | + XHCI_TRB_D2_SLOTID_SET(slot); + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_setaddr + * + * Description: + * Address Device Command. + * + ****************************************************************************/ + +static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx, bool bsr) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = htole64(ctx); + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ADDR_DEV) | + XHCI_TRB_D2_SLOTID_SET(slot); + + if (bsr) + { + trb.d2 |= XHCI_TRB_D2_BSR; + } + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_cfgep + * + * Description: + * Configure EP Command. + * + ****************************************************************************/ + +static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx, bool deconfig) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = htole64(ctx); + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_CFG_EP) | + XHCI_TRB_D2_SLOTID_SET(slot); + + if (deconfig) + { + trb.d2 |= XHCI_TRB_D2_DC; + } + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_stopep + * + * Description: + * Stop endpoint + * + ****************************************************************************/ + +static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint8_t ep, bool suspend) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = 0; + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) | + XHCI_TRB_D2_SLOTID_SET(slot) | XHCI_TRB_D2_EP_SET(ep); + + if (suspend) + { + trb.d2 |= XHCI_TRB_D2_SP; + } + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_evalctx + * + * Description: + * Evaluate Context Command + * + ****************************************************************************/ + +static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = htole64(ctx); + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EVAL_CTX) | + XHCI_TRB_D2_SLOTID_SET(slot); + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_ep_doorbell + * + * Description: + * Ring doorbell associated with a given endpoint. + * + ****************************************************************************/ + +static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo) +{ + uint8_t target = xhci_epno_get(epinfo); + uint32_t regval = 0; + + /* Doorbel tareget is EP number */ + + regval |= XHCI_DOORBEL_TARGET(target); + + /* Streams not supported yet (USB3.0 specific) */ + + regval |= XHCI_DOORBEL_TASK(0); + + /* Ring doorbell */ + + xhci_door_putreg(priv, XHCI_DOORBEL(epinfo->slot), regval); +} + +/**************************************************************************** + * Name: xhci_ioc_setup + * + * Description: + * Set the request for the IOC event well BEFORE enabling the transfer (as + * soon as we are absolutely committed to the transfer). We do + * this to minimize race conditions. This logic would have to be expanded + * if we want to have more than one packet in flight at a time! + * + * Assumption: + * The caller holds the XHCI lock + * + ****************************************************************************/ + +static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + irqstate_t flags; + int ret = -ENODEV; + + DEBUGASSERT(rhport && epinfo && !epinfo->iocwait); +#ifdef CONFIG_USBHOST_ASYNCH + DEBUGASSERT(epinfo->callback == NULL); +#endif + + /* Is the device still connected? */ + + flags = spin_lock_irqsave(&priv->spinlock); + if (rhport->connected) + { + /* Then set iocwait to indicate that we expect to be informed when + * either (1) the device is disconnected, or (2) the transfer + * completed. + */ + + epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */ + epinfo->status = 0; /* No status yet */ + epinfo->xfrd = 0; /* Nothing transferred yet */ + epinfo->buflen = buflen; /* Buffer length */ + epinfo->result = -EBUSY; /* Transfer in progress */ +#ifdef CONFIG_USBHOST_ASYNCH + epinfo->callback = NULL; /* No asynchronous callback */ + epinfo->arg = NULL; +#endif + ret = OK; /* We are good to go */ + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + return ret; +} + +/**************************************************************************** + * Name: xhci_ioc_wait + * + * Description: + * Wait for the IOC event. + * + * Assumption: + * The caller does *NOT* hold the xHCI lock. That would cause a deadlock + * when the bottom-half, worker thread needs to take the semaphore. + * + ****************************************************************************/ + +static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo) +{ + int ret = OK; + + /* Wait for the IOC event. Loop to handle any false alarm semaphore + * counts. Return an error if the task is canceled. + */ + + while (epinfo->iocwait) + { + ret = nxsem_wait_uninterruptible(&epinfo->iocsem); + if (ret < 0) + { + break; + } + } + + return ret < 0 ? ret : epinfo->result; +} + +/**************************************************************************** + * Name: xhci_control_setup + * + * Description: + * Process a IN or OUT request control ep. + * This function will enqueue the request and wait for it to + * complete. Bulk data transfers differ in that req == NULL and there are + * not SETUP or STATUS phases. + * + * This is a blocking function; it will not return until the control + * transfer has completed. + * + * Assumption: + * The caller holds the xHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is return on + * any failure. + * + ****************************************************************************/ + +static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer, size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + struct xhci_trb_s trb[3]; + uint8_t trt; + int i = 0; + + /* Prepare Setup Stage TRB */ + + trb[i].d0 = *((FAR uint64_t *)req); + trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(8); + trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | + XHCI_TRB_D2_IDT; + + /* Reference: + * - Table 4-7: USB SETUP Data to Data Stage TRB and Status Stage + * TRB mapping + */ + + if (req->type & USB_REQ_DIR_IN) + { + trt = XHCI_TRB_D2_TRT_INDATA; + } + else if (req->type & USB_REQ_DIR_OUT) + { + trt = XHCI_TRB_D2_TRT_OUTDATA; + } + else + { + trt = XHCI_TRB_D2_TRT_NODATA; + } + + trb[i].d2 |= XHCI_TRB_D2_TRT_SET(trt); + + /* Next TRB */ + + i++; + + /* Prepare Data Stage TRB */ + + if (buffer) + { + buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen, + (req->type & USB_REQ_DIR_IN) != 0); + if (buffer == NULL) + { + return -ENOMEM; + } + + trb[i].d0 = up_addrenv_va_to_pa(buffer); + trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(buflen); + trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); + + if (req->type & USB_REQ_DIR_IN) + { + trb[i].d2 |= XHCI_TRB_D2_DIR; + } + + /* Next TRB */ + + i++; + } + + /* Prepare Status Stage TRB */ + + trb[i].d0 = 0; + trb[i].d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(0); + trb[i].d2 = XHCI_TRB_D2_IOC | + XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STAT_STAGE); + + if (!(req->type & USB_REQ_DIR_IN)) + { + trb[i].d2 |= XHCI_TRB_D2_DIR; + } + + /* Next TRB */ + + i++; + + /* Add TRBs to ring */ + + xhci_add_trb(priv, &epinfo->td, trb, i); + + /* Trigger transfer */ + + xhci_ep_doorbell(priv, epinfo); + + return OK; +} + +/**************************************************************************** + * Name: xhci_normal_setup + * + * Description: + * Process a IN or OUT request on bulk or interrupt endpoint. + * This function will enqueue the request and wait for it to complete. + * + * This is a blocking function; it will not return until the control + * transfer has completed. + * + * Assumption: + * The caller holds the xHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + struct xhci_trb_s trb; + + size_t left; + size_t chunk; + uintptr_t pa; + int n = 0; + + /* Make the buffer safe for the controller to reach */ + + buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen, + epinfo->dirin != 0); + if (buffer == NULL) + { + return -ENOMEM; + } + + /* One TRB describes one run of memory, and that run may not cross a 64K + * boundary. A longer transfer, or one starting near the wrong side of a + * boundary, becomes several TRBs chained into a single transfer, with + * the interrupt asked for only on the last so that one completion + * arrives for the whole of it. + */ + + pa = up_addrenv_va_to_pa(buffer); + left = buflen; + + while (left > 0) + { + chunk = XHCI_TD_LEN_MAX - (pa & (XHCI_TD_LEN_MAX - 1)); + if (chunk > left) + { + chunk = left; + } + + if (++n >= XHCI_TD_MAX) + { + uerr("transfer of %zu from pa %" PRIxPTR " needs more than %d " + "TRBs\n", buflen, pa, XHCI_TD_MAX); + return -EINVAL; + } + + trb.d0 = pa; + trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(chunk); + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_NORMAL); + + left -= chunk; + pa += chunk; + + /* Chain everything but the last, and interrupt only on the last */ + + if (left > 0) + { + trb.d2 |= XHCI_TRB_D2_CH; + } + else + { + trb.d2 |= XHCI_TRB_D2_IOC; + } + + xhci_add_trb(priv, &epinfo->td, &trb, 1); + } + + /* Trigger transfer */ + + xhci_ep_doorbell(priv, epinfo); + + return OK; +} + +#ifndef CONFIG_USBHOST_ISOC_DISABLE +/**************************************************************************** + * Name: xhci_isoc_setup + * + * Description: + * Process a request on isoch endpoint. + * + * Assumption: + * The caller holds the xHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + struct xhci_trb_s trb; + + /* Make the buffer safe for the controller to reach */ + + buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen, + epinfo->dirin != 0); + if (buffer == NULL) + { + return -ENOMEM; + } + + /* Prepare TRB */ + + trb.d0 = up_addrenv_va_to_pa(buffer); + trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); + trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ISOCH); + + /* Start Isoch ASAP */ + + trb.d2 |= XHCI_TRB_D2_SIA; + + /* Add TRBs to ring */ + + xhci_add_trb(priv, &epinfo->td, &trb, 1); + + /* Trigger transfer */ + + xhci_ep_doorbell(priv, epinfo); + + return OK; +} +#endif + +/**************************************************************************** + * Name: xhci_transfer_wait + * + * Description: + * Wait for an IN or OUT transfer to complete. + * + * Assumption: + * The caller holds the xHCI lock. The caller must be aware that the xHCI + * lock will released while waiting for the transfer to complete, but will + * be re-acquired when before returning. The state of xHCI resources could + * be very different upon return. + * + * Returned Value: + * On success, this function returns the number of bytes actually + * transferred. For control transfers, this size includes the size of the + * control request plus the size of the data (which could be short); for + * bulk transfers, this will be the number of data bytes transfers (which + * could be short). + * + ****************************************************************************/ + +static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo) +{ + int ret; + + /* Wait for the IOC completion event */ + + ret = xhci_ioc_wait(epinfo); + + /* Did xhci_ioc_wait() or nxmutex_lock report an error? */ + + if (ret < 0) + { + usbhost_trace1(XHCI_TRACE1_TRANSFER_FAILED, -ret); + epinfo->iocwait = false; + return (ssize_t)ret; + } + + /* Transfer completed successfully. Return the number of bytes + * transferred. + */ + + return epinfo->xfrd; +} + +#ifdef CONFIG_USBHOST_ASYNCH +/**************************************************************************** + * Name: xhci_ioc_async_setup + * + * Description: + * Setup to receive an asynchronous notification when a transfer completes. + * + * Input Parameters: + * epinfo - The IN or OUT endpoint descriptor for the device endpoint on + * which the transfer will be performed. + * buflen - The length of the transfer, from which the completion works + * out how much was transferred. + * callback - The function to be called when the transfer completes + * arg - An arbitrary argument that will be provided with the callback. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the interrupt level + * + ****************************************************************************/ + +static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + size_t buflen, + usbhost_asynch_t callback, + FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + irqstate_t flags; + int ret = -ENODEV; + + DEBUGASSERT(rhport && epinfo && !epinfo->iocwait && + epinfo->callback == NULL); + + /* Is the device still connected? */ + + flags = spin_lock_irqsave(&priv->spinlock); + if (rhport->connected) + { + /* Then save callback information to be used when either (1) the + * device is disconnected, or (2) the transfer completes. + */ + + epinfo->iocwait = false; /* No synchronous wakeup */ + epinfo->status = 0; /* No status yet */ + epinfo->xfrd = 0; /* Nothing transferred yet */ + epinfo->buflen = buflen; /* Buffer length */ + epinfo->result = -EBUSY; /* Transfer in progress */ + epinfo->callback = callback; /* Asynchronous callback */ + epinfo->arg = arg; /* Argument that accompanies the callback */ + ret = OK; /* We are good to go */ + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + return ret; +} + +/**************************************************************************** + * Name: xhci_asynch_completion + * + * Description: + * This function is called from the interrupt work queue when an + * asynchronous transfer completes. It performs the pending callback. + * + * Input Parameters: + * priv - xHCI private state + * epinfo - The IN or OUT endpoint descriptor for the device endpoint on + * which the transfer was performed. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the work queue, without the spinlock held + * + ****************************************************************************/ + +static void xhci_asynch_completion(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo) +{ + usbhost_asynch_t callback; + ssize_t nbytes; + FAR void *arg; + irqstate_t flags; + int result; + + DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false); + + /* Extract and reset the callback info, atomically against a concurrent + * cancellation. + */ + + flags = spin_lock_irqsave(&priv->spinlock); + + callback = epinfo->callback; + arg = epinfo->arg; + result = epinfo->result; + nbytes = epinfo->xfrd; + + epinfo->callback = NULL; + epinfo->arg = NULL; + epinfo->result = OK; + epinfo->iocwait = false; + + spin_unlock_irqrestore(&priv->spinlock, flags); + + /* A cancellation that got in first has already done the callback */ + + if (callback == NULL) + { + return; + } + + /* Bring back what the controller wrote before anyone reads it. The + * addresses are usable here: a transfer given to DRVR_ASYNCH must use + * memory from DRVR_ALLOC, and that is kernel memory, which this work + * queue thread can reach. + */ + + xhci_dma_finish(epinfo); + + /* Then perform the callback. Provide the number of bytes successfully + * transferred or the negated errno value in the event of a failure. + */ + + if (result < 0) + { + nbytes = (ssize_t)result; + } + + callback(arg, nbytes); +} +#endif + +/**************************************************************************** + * Name: xhci_speed_str + * + * Description: + * What a port negotiated, in words. PORTSC reports a speed ID, not a + * speed. + * + ****************************************************************************/ + +static FAR const char *xhci_speed_str(uint32_t portsc) +{ + switch (XHCI_PORTSC_PS(portsc)) + { + case XHCI_PORTSC_PS_FULL: + return "full speed, 12Mbps"; + case XHCI_PORTSC_PS_LOW: + return "low speed, 1.5Mbps"; + case XHCI_PORTSC_PS_HIGH: + return "high speed, 480Mbps"; + case XHCI_PORTSC_PS_SUPPER11: + return "SuperSpeed, 5Gbps"; + case XHCI_PORTSC_PS_SUPPER21: + case XHCI_PORTSC_PS_SUPPER12: + case XHCI_PORTSC_PS_SUPPER22: + return "SuperSpeed+, 10Gbps"; + default: + return "an unknown speed"; + } +} + +/**************************************************************************** + * Name: xhci_portsc_work + * + * Description: + * Handle Port Change work. + * + * Assumptions: + * - Never called from an interrupt handler. + * - Never called directly from xhci_events_poll() otherwise it gets stuck + * on CLASS_DISCONNECTED() + * + ****************************************************************************/ + +static void xhci_portsc_work(FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = arg; + FAR struct usbhost_hubport_s *hport; + FAR struct xhci_rhport_s *rhport; + uint32_t portsc; + int rhpndx; + + /* REVISIT: should this logic be protected? We can't use spinlock + * here because we get stack in CLASS_DISCONNECTED(). + */ + + /* Handle root hub status change on each root port */ + + for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) + { + rhport = &priv->rhport[rhpndx]; + portsc = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + + usbhost_vtrace2(XHCI_VTRACE2_PORTSC, rhpndx + 1, portsc); + + /* Handle port connection status change (CSC) events */ + + if ((portsc & XHCI_PORTSC_CSC) != 0) + { + usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CSC, portsc); + + /* Check current connect status */ + + if ((portsc & XHCI_PORTSC_CCS) != 0) + { + /* Connected ... Did we just become connected? */ + + if (!rhport->connected) + { + /* Yes.. connected. */ + + rhport->connected = true; + + /* A new device gets the full allowance of attempts, + * whatever the last one that sat here managed. + */ + + rhport->enumfail = 0; + + usbhost_vtrace2(XHCI_VTRACE2_PORTSC_CONNECTED, + rhpndx + 1, priv->pscwait); + + /* Notify any waiters */ + + if (priv->pscwait) + { + nxsem_post(&priv->pscsem); + priv->pscwait = false; + } + } + else + { + usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CONNALREADY, portsc); + } + } + else + { + /* Disconnected... Did we just become disconnected? */ + + if (rhport->connected) + { + /* Yes.. disconnect the device */ + + usbhost_vtrace2(XHCI_VTRACE2_PORTSC_DISCONND, + rhpndx + 1, priv->pscwait); + + syslog(LOG_INFO, "%s: port %d: device removed\n", + priv->name, rhpndx + 1); + + rhport->connected = false; + + /* Are we bound to a class instance? */ + + hport = &rhport->hport.hport; + if (hport->devclass) + { + /* Yes.. Disconnect the class. */ + + CLASS_DISCONNECTED(hport->devclass); + hport->devclass = NULL; + } + + /* Notify any waiters for the Root Hub Status change + * event. + */ + + if (priv->pscwait) + { + nxsem_post(&priv->pscsem); + priv->pscwait = false; + } + } + else + { + usbhost_vtrace1(XHCI_VTRACE1_PORTSC_DISCALREADY, portsc); + } + } + } + + /* Clear pending bit but don't touch PED ! */ + + portsc &= ~XHCI_PORTSC_PED; + xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), portsc); + } +} + +/**************************************************************************** + * Name: xhci_in_slot / xhci_in_ep / xhci_out_slot + * + * Description: + * Reach into a device context. + * + * A context is an array of equally sized entries, and how big they are is + * a property of the controller rather than of the specification: it + * reports either thirty-two or sixty-four bytes, and the wider form is + * the same fields with reserved space after them. So these are the same + * structures at a different stride, and only the arithmetic to find the + * n'th one has to know which. + * + * Output context: slot, then endpoints 1 upward. + * Input context: input control, then slot, then endpoints. + * + * The first entry of either is at offset zero, so only the ones after it + * need this. + * + ****************************************************************************/ + +static inline FAR struct xhci_slot_ctx_s * +xhci_in_slot(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_input_dev_ctx_s *input) +{ + return (FAR struct xhci_slot_ctx_s *)((uintptr_t)input + priv->ctxsize); +} + +static inline FAR struct xhci_ep_ctx_s * +xhci_in_ep(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_input_dev_ctx_s *input, int epidx) +{ + return (FAR struct xhci_ep_ctx_s *)((uintptr_t)input + + (epidx + 2) * priv->ctxsize); +} + +static inline FAR struct xhci_slot_ctx_s * +xhci_out_slot(FAR struct xhci_dev_ctx_s *ctx) +{ + return (FAR struct xhci_slot_ctx_s *)ctx; +} + +/**************************************************************************** + * Name: xhci_dev_from_ep + * + * Description: + * The device an endpoint belongs to. + * + * An endpoint records the slot it was opened on, and the slot indexes the + * device table, so this holds wherever the device sits. The root hub port + * does not: a class driver reaches the controller through the port it + * descends from, and a hub puts several devices behind one such port. + * + ****************************************************************************/ + +static inline FAR struct xhci_dev_s * +xhci_dev_from_ep(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo) +{ + DEBUGASSERT(epinfo->slot > 0 && epinfo->slot <= priv->no_slots); + return &priv->devs[epinfo->slot - 1]; +} + +/**************************************************************************** + * Name: xhci_dev_from_hport + * + * Description: + * The device attached to a hub port, or NULL if there is none. + * + * Used where there is no endpoint to ask yet, which is the case when the + * first one is being allocated. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_HUB +/**************************************************************************** + * Name: xhci_route_string + * + * Description: + * The route string for a device, which is how the controller finds it. + * + * Each hub between the root and the device contributes one nibble holding + * the number of the port the next thing down is plugged into, with the + * tier nearest the root in the lowest nibble. A device on a root hub port + * routes to zero, which is what the field means for "no hubs in between". + * + * Reference: + * - 8.9: Route String Field + * + ****************************************************************************/ + +static uint32_t xhci_route_string(FAR struct usbhost_hubport_s *hport) +{ + uint32_t route = 0; + int tier = 0; + + /* Walking up reaches the deepest tier first, and shifting what is already + * there left by a nibble each time leaves the tier nearest the root in the + * lowest one. USB allows five tiers of hubs and the field holds exactly + * that many, so a chain longer than the bus permits stops here rather than + * writing over the speed field above it. + */ + + while (hport->parent != NULL && tier < 5) + { + uint8_t portno = hport->port + 1; + + /* The nibble cannot express a port above fifteen. A hub that large + * is legal, so clamp rather than let the number wrap into the tier + * below it. + */ + + if (portno > 15) + { + portno = 15; + } + + route = (route << 4) | portno; + hport = hport->parent; + tier++; + } + + return route; +} +#endif + +static FAR struct xhci_dev_s * +xhci_dev_from_hport(FAR struct usbhost_xhci_s *priv, + FAR struct usbhost_hubport_s *hport) +{ + uint8_t i; + + for (i = 0; i < priv->no_slots; i++) + { + if (priv->devs[i].state != XHCI_SLOT_DISABLED && + priv->devs[i].hport == hport) + { + return &priv->devs[i]; + } + } + + return NULL; +} + +#ifdef CONFIG_USBHOST_HUB +/**************************************************************************** + * Name: xhci_slot_tt + * + * Description: + * Slot context dword 2, naming the transaction translator that carries a + * low or full speed device behind a high speed hub. Zero when no + * translator is involved, which is what the field means. + * + * Reference: + * - 6.2.2: Slot Context + * + ****************************************************************************/ + +static uint32_t xhci_slot_tt(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev) +{ + FAR struct usbhost_hubport_s *hport = dev->hport; + FAR struct xhci_dev_s *tthub; + + /* Only a low or full speed device is translated for. */ + + if (hport->speed == USB_SPEED_HIGH) + { + return 0; + } + + /* The translator lives in the nearest high speed ancestor, which need not + * be the hub the device is plugged into: a full speed hub below a high + * speed one is itself carried by the translator above it. + */ + + while (hport->parent != NULL && hport->parent->speed != USB_SPEED_HIGH) + { + hport = hport->parent; + } + + if (hport->parent == NULL) + { + /* Nothing high speed above, so the device is on a root hub port or + * the whole chain runs at its speed. Either way there is no + * translator to name. + */ + + return 0; + } + + tthub = xhci_dev_from_hport(priv, hport->parent); + if (tthub == NULL) + { + uerr("no device for the hub carrying port %d\n", hport->port); + return 0; + } + + /* Think time is the hub's, reported by the hub class driver from the hub + * descriptor. Both fields count in the same units, so the value carries + * across unchanged. + */ + + return XHCI_ST_CTX2_TTHSID_SET(tthub->slot) | + XHCI_ST_CTX2_TTPORT_SET(hport->port + 1) | + XHCI_ST_CTX2_TTT_SET(hport->parent->ttt); +} +#endif + +/**************************************************************************** + * Name: xhci_speed_id + * + * Description: + * Turn the speed the USB host stack uses into the one a slot context + * wants, which is a different numbering with no relation to it. + * + ****************************************************************************/ + +static uint32_t xhci_speed_id(uint8_t speed) +{ + switch (speed) + { + case USB_SPEED_LOW: + return XHCI_SPEED_LOW; + case USB_SPEED_FULL: + return XHCI_SPEED_FULL; + case USB_SPEED_HIGH: + return XHCI_SPEED_HIGH; + case USB_SPEED_SUPER: + return XHCI_SPEED_SUPER; + case USB_SPEED_SUPER_PLUS: + return XHCI_SPEED_SUPER_PLUS; + default: + + /* Nothing else can be described to a controller, and full speed + * is the safe answer. + */ + + uwarn("no speed ID for USB speed %d\n", speed); + return XHCI_SPEED_FULL; + } +} + +/**************************************************************************** + * Name: xhci_dmacapable + * + * Description: + * Whether the controller may be pointed at this buffer. + * + * The driver has no way to know this on its own. Whether an address can + * be turned into one the device will reach, and whether what lies behind + * it is contiguous, is a property of the system the controller was fitted + * into, so the answer comes from there. A platform that says nothing is + * taken to mean every address works, which is what a flat address space + * gives. + * + ****************************************************************************/ + +static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *buffer, size_t buflen) +{ + if (priv->ops->dmacapable == NULL) + { + return true; + } + + return priv->ops->dmacapable(priv->arg, buffer, buflen); +} + +/**************************************************************************** + * Name: xhci_dma_prepare + * + * Description: + * Make a caller's buffer safe for the controller to reach, and say which + * address to hand it. + * + * The controller writes memory behind the processor's back, so on a + * machine whose caches are not coherent every buffer it touches must be + * flushed before the controller reads and invalidated before the + * processor does. + * + * Both act a whole cache line at a time, which is unsafe for a buffer + * that does not own its lines: invalidating drops whatever shares the + * line, and a writeback lands on top of what the controller just put + * there. Class drivers pass their own structure members, a 31 byte + * command block or a 13 byte status, which share lines. + * + * Such a buffer gets an aligned stand-in and is copied at the ends. + * Anything large enough to matter comes from a filesystem or from + * xhci_ioalloc() and is already aligned. + * + * Returned Value: + * The address to give the controller, or NULL if a stand-in was needed + * and could not be allocated. + * + ****************************************************************************/ + +static FAR uint8_t *xhci_dma_prepare(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen, + bool dirin) +{ + size_t line = up_get_dcache_linesize(); + bool reachable = xhci_dmacapable(priv, buffer, buflen); + + epinfo->buffer = buffer; + epinfo->bounce = NULL; + epinfo->dmalen = buflen; + + /* How much to bring back afterwards. This cannot be taken from buflen + * at completion time: that field means the length of a data transfer and + * control transfers deliberately leave it zero, so a descriptor read + * would copy nothing back and the caller would see whatever its buffer + * held before. + */ + + epinfo->dmacopy = buflen; + epinfo->dmain = dirin; + + /* Nothing to arrange: no cache to maintain, and an address the + * controller can be pointed at as it stands. + */ + + if (line == 0 && reachable) + { + return buffer; + } + + if (!reachable || + ((uintptr_t)buffer & (line - 1)) != 0 || (buflen & (line - 1)) != 0) + { + /* A stand-in is needed; see xhci_dma_direct() for the same test */ + + /* The buffer shares a line with something else. Work in a stand-in + * that does not. + */ + + /* Maintain the whole stand-in, not just the part in use: cache + * operations work a line at a time and this chip rejects a partial + * range. + */ + + epinfo->dmalen = line ? ((buflen + line - 1) & ~(line - 1)) : buflen; + + epinfo->bounce = kmm_memalign(line ? line : sizeof(uintptr_t), + epinfo->dmalen); + if (epinfo->bounce == NULL) + { + return NULL; + } + + if (!dirin) + { + memcpy(epinfo->bounce, buffer, buflen); + } + + buffer = epinfo->bounce; + } + + /* Push what we are sending; drop what we are about to be sent, so that + * nothing the processor is still holding can be written back over it + * while the transfer is in flight. + */ + + if (dirin) + { + up_invalidate_dcache((uintptr_t)buffer, + (uintptr_t)buffer + epinfo->dmalen); + } + else + { + up_clean_dcache((uintptr_t)buffer, + (uintptr_t)buffer + epinfo->dmalen); + } + + return buffer; +} + +/**************************************************************************** + * Name: xhci_dma_finish + * + * Description: + * Read back what the controller wrote, and give up any stand-in buffer. + * + * This must run in the context of whoever asked for the transfer, not in + * the completion handler. The buffer being copied back into may belong + * to a user process, and its address means nothing in the work queue + * thread that handles the completion event, where the write would fault + * or corrupt another process. The caller is blocked until the transfer + * finishes anyway. + * + ****************************************************************************/ + +static void xhci_dma_finish(FAR struct xhci_epinfo_s *epinfo) +{ + FAR uint8_t *dma = epinfo->bounce ? epinfo->bounce : epinfo->buffer; + bool dirin = epinfo->dmain; + + if (dma == NULL) + { + return; + } + + if (dirin) + { + up_invalidate_dcache((uintptr_t)dma, + (uintptr_t)dma + epinfo->dmalen); + + if (epinfo->bounce != NULL && epinfo->buffer != NULL) + { + memcpy(epinfo->buffer, epinfo->bounce, epinfo->dmacopy); + } + } + + if (epinfo->bounce != NULL) + { + kmm_free(epinfo->bounce); + epinfo->bounce = NULL; + } + + epinfo->buffer = NULL; +} + +/**************************************************************************** + * Name: xhci_transfer_complete + * + * Description: + * Handle transfer complete event + * + ****************************************************************************/ + +static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *evt) +{ + FAR struct xhci_epinfo_s *epinfo; + uint32_t tl = XHCI_TRB_D1_TXLEN_GET(evt->d1); + uint8_t slot = XHCI_TRB_D2_SLOTID_GET(evt->d2); + uint8_t ep = XHCI_TRB_D2_EP_GET(evt->d2); + uint8_t ret = XHCI_TRB_D1_CC_GET(evt->d1); + irqstate_t flags; +#ifdef CONFIG_USBHOST_ASYNCH + bool asynch = false; +#endif + + /* Get EP associated with this transfer */ + + epinfo = priv->devs[slot - 1].epinfo[ep - 1]; + DEBUGASSERT(epinfo != NULL); + + flags = spin_lock_irqsave(&priv->spinlock); + + /* Get transferred length */ + + if (epinfo->buflen > 0) + { + epinfo->xfrd = epinfo->buflen - tl; + } + + /* Check transfer status */ + + if (ret == XHCI_TRB_CC_SUCCESS) + { + /* Report success */ + + epinfo->status = 0; + epinfo->result = OK; + } + + else if (ret == XHCI_TRB_CC_STALL) + { + /* Report STALL condition */ + + epinfo->status = 0; + epinfo->result = -EPERM; + } + + else if (ret == XHCI_TRB_CC_SHORT_PKT) + { + /* Report success */ + + epinfo->status = 0; + epinfo->result = OK; + } + + else + { + /* Report error */ + + uerr("transfer CC = %d\n", ret); + epinfo->status = ret; + epinfo->result = -EIO; + } + + /* Is there a thread waiting for this transfer to complete? */ + + if (epinfo->iocwait) + { + /* Yes... wake it up */ + + epinfo->iocwait = 0; + nxsem_post(&epinfo->iocsem); + } + +#ifdef CONFIG_USBHOST_ASYNCH + /* No.. Is there a pending asynchronous transfer instead? Decide while + * still holding the lock: the moment the waiter above is posted, the + * endpoint may be given a new transfer, and that one is not complete. + */ + + else + { + asynch = epinfo->callback != NULL; + } +#endif + + spin_unlock_irqrestore(&priv->spinlock, flags); + +#ifdef CONFIG_USBHOST_ASYNCH + /* The callback runs outside the spinlock: it is class driver code, and + * what it does (queue work, take its own locks) has no business running + * with interrupts masked. + */ + + if (asynch) + { + /* Perform the callback */ + + xhci_asynch_completion(priv, epinfo); + } +#endif +} + +/**************************************************************************** + * Name: xhci_envet_complete + * + * Description: + * Handle event complete event + * + ****************************************************************************/ + +static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *evt) +{ + irqstate_t flags; + + /* REVISIT: we assume for now that only one command is pending */ + + /* Store command result */ + + flags = spin_lock_irqsave(&priv->spinlock); + priv->cmdres.d0 = evt->d0; + priv->cmdres.d1 = evt->d1; + priv->cmdres.d2 = evt->d2; + spin_unlock_irqrestore(&priv->spinlock, flags); + + /* Signal that command is done */ + + nxsem_post(&priv->cmdsem); +} + +/**************************************************************************** + * Name: xhci_events_poll + * + * Description: + * Poll all pending events + * + ****************************************************************************/ + +static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) +{ + FAR struct xhci_trb_s *evt; + uintptr_t addr; + uint8_t type; + uint32_t d2; + int count = 0; + + /* Invalidate event ring */ + + up_invalidate_dcache((uintptr_t)priv->evnt.ring, + (uintptr_t)(priv->evnt.ring + XHCI_EVENT_MAX)); + + /* Handle all pending events */ + + while (1) + { + evt = &priv->evnt.ring[priv->evnt.i]; + + /* Update address */ + + addr = (uintptr_t)evt; + + d2 = le32toh(evt->d2); + if ((d2 & XHCI_TRB_D2_C) != priv->evnt.ccs) + { + break; + } + + type = XHCI_TRB_D2_TYPE_GET(d2); + + switch (type) + { + /* Transfer Event */ + + case XHCI_TRB_EVT_TRANSFER: + { + xhci_transfer_complete(priv, evt); + + break; + } + + /* Command Completion Event */ + + case XHCI_TRB_EVT_CMD_COMP: + { + xhci_event_complete(priv, evt); + + break; + } + + /* Port Status Change Event */ + + case XHCI_TRB_EVT_PSTAT_CHANGE: + { + /* We have to handle Port Status Change in a separate work + * queue, otherwise we'll get stuck when handling disconnect + * request. + */ + + if (work_available(&priv->pscwork)) + { + work_queue(LPWORK, &priv->pscwork, xhci_portsc_work, + (FAR void *)priv, 0); + } + + break; + } + + default: + { + uinfo("ignored event %d\n", type); + break; + } + } + + /* Next event */ + + count++; + priv->evnt.i++; + + /* Handle ring wrap */ + + if (priv->evnt.i >= XHCI_EVENT_MAX) + { + xhci_ring_reset(&priv->evnt, true); + } + } + + /* Clear ERDP busy bit and update dequeue pointer */ + + addr = up_addrenv_va_to_pa((FAR void *)addr); + addr |= XHCI_ERDP_EHB; + xhci_runt_putreg_8b(priv, XHCI_ERDP(0), addr); + + return count; +} + +/**************************************************************************** + * Name: xhci_interupt_work + * + * Description: + * Handle xHCI interrupts + * + ****************************************************************************/ + +static void xhci_interrupt_work(FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = arg; + uint32_t iman; + + /* Acknowledge before walking the ring, not after. An event arriving + * during the walk sets the pending bit again, and clearing after the + * walk discards it. Transfers have no timeout, so the one it belonged + * to would wait forever. + */ + + xhci_oper_putreg(priv, XHCI_USBSTS, priv->pending); + + iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); + if (iman & XHCI_IMAN_IP) + { + xhci_runt_putreg(priv, XHCI_IMAN(0), iman); + } + + xhci_events_poll(priv); + + /* Port Change Detect */ + + if (priv->pending & XHCI_USBSTS_PCD) + { + /* Handled as event in xhci_events_poll() */ + + uinfo("Port Change Detect\n"); + } + + /* Host Controller Halted */ + + if (priv->pending & XHCI_USBSTS_HCH) + { + uinfo("Host Controller Halted\n"); + } + + /* Host System Error */ + + if (priv->pending & XHCI_USBSTS_HSE) + { + uinfo("Host System Error\n"); + } + + /* Host Controller Error */ + + if (priv->pending & XHCI_USBSTS_HCE) + { + uinfo("Host Controller Error\n"); + } + + /* Clear pending bits */ + + priv->pending = 0; + + /* Let interrupts back in, which the handler masked on its way out, and + * clear the pending flag in the same write. + * + * A message signalled interrupt is sent on the flag's clear to set + * transition; a wire stays asserted while it is set. Events that + * arrived while this interrupter was masked have already set the flag, + * so enabling without clearing leaves a message with nothing to + * transition on, and transfers have no timeout. + * + * Clearing opens its own window: an event delivered between the ring + * going empty and this write is discarded. So drain again, and repeat + * if that drain found anything. A drain that finds nothing is the only + * state in which no event can have been lost. + */ + + do + { + iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); + xhci_runt_putreg(priv, XHCI_IMAN(0), + iman | XHCI_IMAN_IE | XHCI_IMAN_IP); + } + while (xhci_events_poll(priv) > 0); +} + +/**************************************************************************** + * Name: xhci_interupt + * + * Description: + * Interrupt handler for xHCI + * + ****************************************************************************/ + +static int xhci_interrupt(int irq, FAR void *context, FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = arg; + uint32_t iman; + + /* Get pending interrupts */ + + priv->pending = xhci_oper_getreg(priv, XHCI_USBSTS); + + /* Silence the interrupter before returning. + * + * Nothing here clears the condition that raised the interrupt; the work + * runs later on a work queue. On a level triggered line the source is + * still asserted on return, so the interrupt re-raises immediately and + * the worker never runs. The worker clears the status and unmasks. + */ + + iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); + xhci_runt_putreg(priv, XHCI_IMAN(0), iman & ~XHCI_IMAN_IE); + + /* Handle interrupts in worker */ + + if (work_available(&priv->work)) + { + work_queue(HPWORK, &priv->work, xhci_interrupt_work, arg, 0); + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_wait + * + * Description: + * Wait for a device to be connected or disconnected to/from a hub port. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from + * the call to the USB driver initialization logic. + * hport - The location to return the hub port descriptor that detected + * the connection related event. + * + * Returned Value: + * Zero (OK) is returned on success when a device is connected or + * disconnected. This function will not return until either (1) a device is + * connected or disconnect to/from any hub port or until (2) some failure + * occurs. On a failure, a negated errno value is returned indicating the + * nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_wait(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s **hport) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); + FAR struct xhci_rhport_s *rhport; + FAR struct usbhost_hubport_s *connport; + irqstate_t flags; + int rhpndx; + int ret; + + /* Loop until the connection state changes on one of the root hub ports or + * until an error occurs. + */ + + while (true) + { + flags = spin_lock_irqsave(&priv->spinlock); + + /* Check for a change in the connection state on any root hub port */ + + for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) + { + /* Has the connection state changed on the RH port? */ + + rhport = &priv->rhport[rhpndx]; + connport = &rhport->hport.hport; + if (rhport->connected != connport->connected) + { + /* Yes.. Return the RH port to inform the caller which + * port has the connection change. + */ + + connport->connected = rhport->connected; + *hport = connport; + spin_unlock_irqrestore(&priv->spinlock, flags); + + usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, + rhpndx + 1, rhport->connected); + return OK; + } + } + +#ifdef CONFIG_USBHOST_HUB + /* Is a device connected to an external hub? */ + + if (priv->hhead != priv->htail) + { + /* Yes.. return the external hub port */ + + connport = priv->hports[priv->htail]; + priv->htail = (priv->htail + 1) % CONFIG_USBHOST_XHCI_MAX_DEVS; + + *hport = (FAR struct usbhost_hubport_s *)connport; + spin_unlock_irqrestore(&priv->spinlock, flags); + + usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, + HPORT(connport), connport->connected); + return OK; + } +#endif + + /* No changes on any port. Wait for a connection/disconnection event + * and check again + */ + + priv->pscwait = true; + + spin_unlock_irqrestore(&priv->spinlock, flags); + + ret = nxsem_wait_uninterruptible(&priv->pscsem); + if (ret < 0) + { + return ret; + } + } +} + +/**************************************************************************** + * Name: xhci_rh_enumerate/xhci_enumerate + * + * Description: + * Enumerate the connected device. As part of this enumeration process, + * the driver will (1) get the device's configuration descriptor, (2) + * extract the class ID info from the configuration descriptor, (3) call + * usbhost_findclass() to find the class that supports this device, (4) + * call the create() method on the struct usbhost_registry_s interface + * to get a class instance, and finally (5) call the connect() method + * of the struct usbhost_class_s interface. After that, the class is in + * charge of the sequence of operations. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from + * the call to the USB driver initialization logic. + * hport - The descriptor of the hub port that has the newly connected + * device. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); + FAR struct xhci_rhport_s *rhport; + int rhpndx; + int ret; + + DEBUGASSERT(hport != NULL); + rhpndx = hport->port; + + DEBUGASSERT(rhpndx >= 0 && rhpndx < priv->no_ports); + rhport = &priv->rhport[rhpndx]; + + /* Are we connected to a device? The caller should have called the wait() + * method first to be assured that a device is connected. + */ + + if (!rhport->connected) + { + /* No, return an error */ + + uerr("not connected\n"); + return -ENODEV; + } + + /* Enable port */ + + ret = xhci_port_enable(priv, hport); + if (ret < 0) + { + uerr("Failed to enable port %d\n", ret); + return ret; + } + + /* Initialize device data */ + + ret = xhci_device_init(priv, rhport, &rhport->hport.hport, + &rhport->ep0); + if (ret < 0) + { + uerr("Failed to initialize device %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_enumerate + * + * Description: + * See description above. + * + ****************************************************************************/ + +static int xhci_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport) +{ + int ret; + + /* If this is a connection on the root hub, then we need to go to + * little more effort to get the device speed. If it is a connection + * on an external hub, then we already have that information. + */ + + DEBUGASSERT(hport); +#ifdef CONFIG_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + ret = xhci_rh_enumerate(conn, hport); + if (ret < 0) + { + return ret; + } + } + + /* Then let the common usbhost_enumerate do the real enumeration. */ + + ret = usbhost_enumerate(hport, &hport->devclass); + if (ret < 0) + { + /* Failed to enumerate */ + + /* The device is addressed by now, so it holds a slot, and the retry + * below asks for another. + */ + +#ifdef CONFIG_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); + FAR struct xhci_rhport_s *rhport = &priv->rhport[hport->port]; + + if (rhport->dev != NULL) + { + xhci_device_deinit(priv, rhport->dev); + } + + /* Clearing connected below is what makes xhci_wait() return, + * so it is also what repeats the attempt. Leave the port alone + * past the limit; a new connection clears the count. + */ + + if (++rhport->enumfail >= CONFIG_USBHOST_XHCI_ENUM_RETRIES) + { + syslog(LOG_ERR, "%s: port %d: giving up after %d attempts\n", + priv->name, hport->port + 1, rhport->enumfail); + return ret; + } + } + + /* If this is a root hub port, then marking the hub port not connected + * will cause xhci_wait() to return and we will try the connection + * again. + */ + + hport->connected = false; + } + else + { +#ifdef CONFIG_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); + + priv->rhport[hport->port].enumfail = 0; + } + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_ep0configure + * + * Description: + * Configure endpoint 0. This method is normally used internally by the + * enumerate() method but is made available at the interface to support + * an external implementation of the enumeration logic. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * funcaddr - The USB address of the function containing the endpoint that + * EP0 controls. A funcaddr of zero will be received if no address is + * yet assigned to the device. + * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH + * maxpacketsize - The maximum number of bytes that can be sent to or + * received from the endpoint in a single data packet + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, + uint8_t speed, uint16_t maxpacketsize) +{ + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep0; + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_dev_s *dev; + uint64_t ctx; + int ret; + + DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048); + + dev = xhci_dev_from_ep(priv, epinfo); + + ret = nxmutex_lock(&priv->lock); + if (ret >= 0) + { + /* Update max packet size */ + + FAR struct xhci_ep_ctx_s *ep0ctx = + xhci_in_ep(priv, dev->input, 0); + + ep0ctx->ctx1 &= ~XHCI_EP_CTX1_MAXPKT_MASK; + ep0ctx->ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpacketsize); + + /* Add Slot Context and EP0 Context */ + + xhci_context_ctrl(priv, dev, 0, + XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | + XHCI_IN_CTX1_A(XHCI_EP0_FLAG)); + + /* Flush Device input context */ + + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + + XHCI_INCTX_SIZE(priv)); + + /* Free mutex before command execution */ + + nxmutex_unlock(&priv->lock); + + ctx = up_addrenv_va_to_pa(dev->input); + + uinfo("slot %d funcaddr %d speed %d maxpacket %d\n", + epinfo->slot, funcaddr, speed, maxpacketsize); + + ret = xhci_cmd_evalctx(priv, epinfo->slot, ctx); + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_interval + * + * Description: + * Work out the Interval an endpoint context wants. + * + * The field is an exponent: the controller services the endpoint every + * 2^Interval microframes. An endpoint descriptor does not say it that + * way, and what it does say depends on how fast the device is, so the + * number cannot simply be copied across. + * + * A low or full speed interrupt endpoint counts in frames, so its period + * is bInterval milliseconds, or bInterval * 8 microframes, and the + * exponent is the position of the highest bit of that. Everything else + * that is periodic already states an exponent, one greater than the one + * wanted here. Control and bulk endpoints are not periodic and the field + * means nothing to them. + * + ****************************************************************************/ + +static uint8_t xhci_interval(uint8_t speed, uint8_t xfrtype, + uint8_t interval) +{ + unsigned int exp; + + if (xfrtype != USB_EP_ATTR_XFER_INT && xfrtype != USB_EP_ATTR_XFER_ISOC) + { + return 0; + } + + if ((speed == USB_SPEED_LOW || speed == USB_SPEED_FULL) && + xfrtype == USB_EP_ATTR_XFER_INT) + { + /* Frames. Round down to a power of two, and keep it inside what the + * specification allows for this kind of endpoint: 2^3 microframes is + * one frame, 2^10 is 128 of them. + */ + + if (interval == 0) + { + interval = 1; + } + + for (exp = 0; (1u << (exp + 1)) <= (unsigned int)interval * 8; exp++); + + if (exp < 3) + { + exp = 3; + } + else if (exp > 10) + { + exp = 10; + } + + return (uint8_t)exp; + } + + /* Already an exponent, counted from one */ + + return interval > 0 ? interval - 1 : 0; +} + +/**************************************************************************** + * Name: xhci_epalloc + * + * Description: + * Allocate and configure one endpoint. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * epdesc - Describes the endpoint to be allocated. + * ep - A memory location provided by the caller in which to receive the + * allocated endpoint descriptor. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_epdesc_s *epdesc, + FAR usbhost_ep_t *ep) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct usbhost_hubport_s *hport; + FAR struct xhci_epinfo_s *epinfo; + FAR struct xhci_dev_s *dev; + uint32_t mask; + uint8_t eptype; + uint8_t idx; + int ret; + + /* Sanity check. NOTE that this method should only be called if a device + * is connected (because we need a valid low speed indication). + */ + + DEBUGASSERT(drvr != 0 && epdesc != NULL && epdesc->hport != NULL + && ep != NULL); + hport = epdesc->hport; + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(XHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype); +#else + uinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n", + epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr, + epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); +#endif + + /* Allocate a endpoint information structure */ + + epinfo = kmm_zalloc(sizeof(struct xhci_epinfo_s)); + if (!epinfo) + { + return -ENOMEM; + } + + /* Initialize the endpoint container (which is really just another form of + * 'struct usbhost_epdesc_s', packed differently and with additional + * information. A cleaner design might just embed struct usbhost_epdesc_s + * inside of struct xhci_epinfo_s and just memcpy here. + */ + + epinfo->dirin = epdesc->in; + epinfo->epno = epdesc->addr; + +#ifndef CONFIG_USBHOST_INT_DISABLE + epinfo->interval = xhci_interval(hport->speed, epdesc->xfrtype, + epdesc->interval); +#endif + epinfo->xfrtype = epdesc->xfrtype; + nxsem_init(&epinfo->iocsem, 0, 0); + nxmutex_init(&epinfo->exclsem); + + /* xhci_epno_get() returns Device Context Index (DCI) */ + + idx = xhci_epno_get(epinfo); + mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx)); + dev = xhci_dev_from_hport(priv, hport); + +#ifdef CONFIG_USBHOST_HUB + /* A hub asks for the control endpoint of a port before it reports the + * connection, so there is no device to attach it to yet. Hand back an + * endpoint with no slot; xhci_connect() gives it one when it creates the + * device, which is the next thing the hub does. + */ + + if (dev == NULL && !ROOTHUB(hport) && + epdesc->xfrtype == USB_EP_ATTR_XFER_CONTROL) + { + ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX); + if (ret < 0) + { + uerr("ep0 ring init failed\n"); + nxmutex_destroy(&epinfo->exclsem); + nxsem_destroy(&epinfo->iocsem); + kmm_free(epinfo); + return ret; + } + + *ep = (usbhost_ep_t)epinfo; + return OK; + } +#endif + + /* There has to be a device to hang the endpoint off. A port whose + * enumeration failed is retried after its slot has been given back, so + * this can run for a port with nothing behind it. + */ + + if (dev == NULL) + { + uerr("no device on port %d\n", hport->port); + nxmutex_destroy(&epinfo->exclsem); + nxsem_destroy(&epinfo->iocsem); + kmm_free(epinfo); + return -ENODEV; + } + + dev->epinfo[idx - 1] = epinfo; + + /* TD rings already allocated but not connected yet. */ + + ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX); + if (ret < 0) + { + uerr("ep ring init failed\n"); + return ret; + } + + /* Store slot ID for later */ + + epinfo->slot = dev->slot; + + /* Get EP type */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: + { + eptype = epinfo->dirin ? XHCI_EPTYPE_BULK_IN : + XHCI_EPTYPE_BULK_OUT; + break; + } + +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: +#endif + { + eptype = epinfo->dirin ? XHCI_EPTYPE_INTR_IN : + XHCI_EPTYPE_INTR_OUT; + break; + } + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: + { + eptype = epinfo->dirin ? XHCI_EPTYPE_ISO_IN : + XHCI_EPTYPE_ISO_OUT; + break; + } +#endif + + default: + { + return -ENOSYS; + } + } + + /* REVISIT: do we need disable EP here? */ + + /* Initialize EP context. + * Max Burst Size set for 0 for now (USB3.0 specific) + */ + + xhci_ep_configure(priv, xhci_in_ep(priv, dev->input, idx - 1), + eptype, epdesc->mxpacketsize, 0, + up_addrenv_va_to_pa(epinfo->td.ring), + 0, epinfo->interval); + + /* Evaluate the slot context */ + + xhci_context_ctrl(priv, dev, 0, mask | XHCI_IN_CTX1_A(XHCI_SLOT_FLAG)); + + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + + XHCI_INCTX_SIZE(priv)); + + /* Configure EP */ + + ret = xhci_cmd_cfgep(priv, epinfo->slot, + up_addrenv_va_to_pa(dev->input), false); + if (ret < 0) + { + uerr("failed to configure EP %d\n", ret); + return ret; + } + + /* Success.. return an opaque reference to the endpoint information + * structure instance + */ + + *ep = (usbhost_ep_t)epinfo; + return OK; +} + +/**************************************************************************** + * Name: xhci_epfree + * + * Description: + * Free an endpoint previously allocated by DRVR_EPALLOC. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The endpoint to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + + /* There should not be any pending, transfers */ + + DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); + + /* Free ring */ + + xhci_ring_deinit(&epinfo->td); + + /* Free the container */ + + nxmutex_destroy(&epinfo->exclsem); + nxsem_destroy(&epinfo->iocsem); + kmm_free(epinfo); + return OK; +} + +/**************************************************************************** + * Name: xhci_alloc + * + * Description: + * Some hardware supports special memory in which request and descriptor + * data can be accessed more efficiently. This method provides a + * mechanism to allocate the request/descriptor memory. If the underlying + * hardware does not support such "special" memory, this functions may + * simply map to kmm_malloc(). + * + * This interface was optimized under a particular assumption. It was + * assumed that the driver maintains a pool of small, pre-allocated buffers + * for descriptor traffic. NOTE that size is not an input, but an output: + * The size of the pre-allocated buffer is returned. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of a memory location provided by the caller in + * which to return the allocated buffer memory address. + * maxlen - The address of a memory location provided by the caller in + * which to return the maximum size of the allocated buffer memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_alloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, FAR size_t *maxlen) +{ + int ret = -ENOMEM; + + DEBUGASSERT(drvr && buffer && maxlen); + + /* Allocated buffer must not cross page boundaries */ + + *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , XHCI_BUFSIZE); + if (*buffer) + { + *maxlen = XHCI_BUFSIZE; + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_free + * + * Description: + * Some hardware supports special memory in which request and descriptor + * data can be accessed more efficiently. This method provides a + * mechanism to free that request/descriptor memory. If the underlying + * hardware does not support such "special" memory, this functions may + * simply map to kmm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the transfer/descriptor buffer + * memory + */ + + kmm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: xhci_ioalloc + * + * Description: + * Some hardware supports special memory in which larger IO buffers can + * be accessed more efficiently. This method provides a mechanism to + * allocate the request/descriptor memory. If the underlying hardware + * does not support such "special" memory, this functions may simply map + * to kumm_malloc. + * + * This interface differs from DRVR_ALLOC in that the buffers are variable- + * sized. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of a memory location provided by the caller in + * which to return the allocated buffer memory address. + * buflen - The size of the buffer required. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, size_t buflen) +{ + size_t line; + int ret = -ENOMEM; + + DEBUGASSERT(drvr && buffer && buflen > 0); + + /* Large transfers are not supported now */ + + if (buflen > XHCI_PAGE_SIZE) + { + return -ENOMEM; + } + + /* Allocated buffer must not cross page boundaries. + * + * Round to whole cache lines as well as aligning the start, so that the + * buffer owns every line it touches and can be invalidated without + * disturbing whatever would otherwise share the last one. + */ + + line = up_get_dcache_linesize(); + if (line > 1) + { + buflen = (buflen + line - 1) & ~(line - 1); + } + + *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , buflen); + if (*buffer) + { + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_iofree + * + * Description: + * Some hardware supports special memory in which IO data can be accessed + * more efficiently. This method provides a mechanism to free that IO + * buffer memory. If the underlying hardware does not support such + * "special" memory, this functions may simply map to kumm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_iofree(FAR struct usbhost_driver_s *drvr, + FAR uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the transfer/descriptor buffer + * memory + */ + + kmm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: xhci_ctrl_xfer + * + * Description: + * Process a IN or OUT request on the control endpoint. These methods + * will enqueue the request and wait for it to complete. Only one + * transfer may be queued; Neither these methods nor the transfer() method + * can be called again until the control transfer function returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep0 - The control endpoint to send/receive the control request. + * req - Describes the request to be sent. This request must lie in + * memory created by DRVR_ALLOC. + * buffer - A buffer used for sending the request and for returning any + * responses. This buffer must be large enough to hold the + * length value in the request description. buffer must have been + * allocated using DRVR_ALLOC. + * + * NOTE: On an IN transaction, req and buffer may refer to the xHCI + * allocated memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *ep0info = (FAR struct xhci_epinfo_s *)ep0; + uint16_t len; + ssize_t nbytes; + int ret; + + DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL); + + /* One request at a time on this endpoint. Taken before the controller + * lock and held across the wait, so the ordering is always endpoint then + * controller and never the reverse. + */ + + ret = nxmutex_lock(&ep0info->exclsem); + if (ret < 0) + { + return ret; + } + + len = xhci_getle16(req->len); + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(XHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req); +#endif + + /* Special case for SET_ADDRESS request */ + + if (req->req == USB_REQ_SETADDRESS) + { + /* Reset EP0 ring to its initial state, so when xHCI update EP0 + * context, TD dequeue pointer would be valid. It may be already + * off, because the USB Host stack has already sent some messages + * on control EP. + */ + + xhci_ring_init(&ep0info->td, 0); + + /* Issue SET_ADDRESS request */ + + ret = xhci_address_set(priv, xhci_dev_from_ep(priv, ep0info), + true); + if (ret == OK) + { + FAR struct xhci_dev_s *dev = xhci_dev_from_ep(priv, ep0info); + + /* The controller chose this address and wrote it into the + * output context. Invalidate before reading, or the stale + * copy is used. + */ + + up_invalidate_dcache((uintptr_t)dev->ctx, + (uintptr_t)dev->ctx + + XHCI_DEVCTX_SIZE(priv)); + + /* Store USB Device Address assigned by xHCI */ + + ep0info->devaddr = + XHCI_ST_CTX3_ADDR_GET(xhci_out_slot(dev->ctx)->ctx[3]); + xhci_in_slot(priv, dev->input)->ctx[3] = + xhci_out_slot(dev->ctx)->ctx[3]; + } + + nxmutex_unlock(&ep0info->exclsem); + return OK; + } + + /* We must have exclusive access to the XHCI hardware and data + * structures. + */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + nxmutex_unlock(&ep0info->exclsem); + return ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + + ret = xhci_ioc_setup(rhport, ep0info, 0); + if (ret != OK) + { + goto errout_with_lock; + } + + /* Now initiate the transfer */ + + ret = xhci_control_setup(rhport, ep0info, req, buffer, len); + if (ret < 0) + { + uerr("ERROR: xhci_control_setup failed: %d\n", ret); + goto errout_with_iocwait; + } + + nxmutex_unlock(&priv->lock); + + /* And wait for the transfer to complete */ + + nbytes = xhci_transfer_wait(priv, ep0info); + + /* As for bulk: the copy back belongs in the caller's context */ + + xhci_dma_finish(ep0info); + + nxmutex_unlock(&ep0info->exclsem); + return nbytes >= 0 ? OK : (int)nbytes; + +errout_with_iocwait: + ep0info->iocwait = false; +errout_with_lock: + nxmutex_unlock(&priv->lock); + nxmutex_unlock(&ep0info->exclsem); + return ret; +} + +/**************************************************************************** + * Name: xhci_ctrlin + * + * Description: + * Process IN request on the control endpoint. For details, see + * description for xhci_ctrl_xfer(). + * + ****************************************************************************/ + +static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer) +{ + /* xhci_ctrl_xfer() can handle both directions */ + + return xhci_ctrl_xfer(drvr, ep0, req, buffer); +} + +/**************************************************************************** + * Name: xhci_ctrlout + * + * Description: + * Process OUT request on the control endpoint. For details, see + * description for xhci_ctrl_xfer(). + * + ****************************************************************************/ + +static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR const uint8_t *buffer) +{ + /* xhci_ctrl_xfer() can handle both directions. We just need to work around + * the differences in the function signatures. + */ + + return xhci_ctrl_xfer(drvr, ep0, req, (FAR uint8_t *)buffer); +} + +/**************************************************************************** + * Name: xhci_transfer + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request, blocking until the transfer completes. + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until this function returns. + * + * This is a blocking method; this functions will not return until the + * transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on + * which to perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or + * received (IN endpoint). buffer must have been allocated using + * DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * + * Returned Value: + * On success, a non-negative value is returned that indicates the number + * of bytes successfully transferred. On a failure, a negated errno value + * is returned that indicates the nature of the failure: + * + * EAGAIN - If devices NAKs the transfer (or NYET or other error where + * it may be appropriate to restart the entire transaction). + * EPERM - If the endpoint stalls + * EIO - On a TX or data toggle error + * EPIPE - Overrun errors + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep, FAR uint8_t *buffer, + size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + ssize_t nbytes; + int ret; + + DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); + + /* One transfer at a time on this endpoint, taken before the controller + * lock and held across the wait. See the note beside exclsem. + */ + + ret = nxmutex_lock(&epinfo->exclsem); + if (ret < 0) + { + return (ssize_t)ret; + } + + /* We must have exclusive access to the xHCI hardware and data + * structures. + */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + nxmutex_unlock(&epinfo->exclsem); + return (ssize_t)ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + + ret = xhci_ioc_setup(rhport, epinfo, buflen); + if (ret != OK) + { + goto errout_with_lock; + } + + /* Initiate the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: +#endif + { + ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); + break; + } + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: + { + ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); + break; + } +#endif + + case USB_EP_ATTR_XFER_CONTROL: + default: + { + usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + break; + } + } + + /* Check for errors in the setup of the transfer */ + + if (ret < 0) + { + uerr("ERROR: Transfer setup failed: %d\n", ret); + goto errout_with_iocwait; + } + + nxmutex_unlock(&priv->lock); + + /* Then wait for the transfer to complete */ + + nbytes = xhci_transfer_wait(priv, epinfo); + + /* And bring back what it produced, here rather than in the completion, + * because this is the context the caller's buffer belongs to. + */ + + xhci_dma_finish(epinfo); + + nxmutex_unlock(&epinfo->exclsem); + return nbytes; + +errout_with_iocwait: + epinfo->iocwait = false; +errout_with_lock: + nxmutex_unlock(&priv->lock); + nxmutex_unlock(&epinfo->exclsem); + return (ssize_t)ret; +} + +/**************************************************************************** + * Name: xhci_asynch + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately. When the transfer + * completes, the callback will be invoked with the provided transfer. + * This method is useful for receiving interrupt transfers which may come + * infrequently. + * + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until the transfer completes. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from + * the call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on + * which to perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or + * received (IN endpoint). buffer must have been allocated + * using DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * callback - This function will be called when the transfer completes. + * arg - The arbitrary parameter that will be passed to the callback + * function when the transfer completes. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, + FAR uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + int ret; + + DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); + + /* We must have exclusive access to the xHCI hardware and data + * structures. + */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Set the request for the callback well BEFORE initiating the transfer. */ + + ret = xhci_ioc_async_setup(rhport, epinfo, buflen, callback, arg); + if (ret != OK) + { + goto errout_with_lock; + } + + /* Initiate the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: +#endif + { + ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); + break; + } + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: + { + ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); + break; + } +#endif + + case USB_EP_ATTR_XFER_CONTROL: + default: + { + usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + break; + } + } + + /* Check for errors in the setup of the transfer */ + + if (ret < 0) + { + goto errout_with_callback; + } + + /* The transfer is in progress */ + + nxmutex_unlock(&priv->lock); + return OK; + +errout_with_callback: + epinfo->callback = NULL; + epinfo->arg = NULL; +errout_with_lock: + nxmutex_unlock(&priv->lock); + return ret; +} +#endif /* CONFIG_USBHOST_ASYNCH */ + +/**************************************************************************** + * Name: xhci_cancel + * + * Description: + * Cancel a pending transfer on an endpoint. Cancelled synchronous or + * asynchronous transfer will complete normally with the error -ESHUTDOWN. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on which + * an asynchronous transfer should be transferred. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + ****************************************************************************/ + +static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); +#ifdef CONFIG_USBHOST_ASYNCH + usbhost_asynch_t callback; + FAR void *arg; +#endif + irqstate_t flags; + bool iocwait; + + DEBUGASSERT(epinfo); + + /* Sample and reset all transfer termination information. This will + * prevent any callbacks from occurring while we performing the + * cancellation. The transfer may still be in progress, however, so this + * does not eliminate other DMA-related race conditions. + */ + + flags = spin_lock_irqsave(&priv->spinlock); +#ifdef CONFIG_USBHOST_ASYNCH + callback = epinfo->callback; + arg = epinfo->arg; +#endif + iocwait = epinfo->iocwait; + +#ifdef CONFIG_USBHOST_ASYNCH + epinfo->callback = NULL; + epinfo->arg = NULL; +#endif + epinfo->iocwait = false; + spin_unlock_irqrestore(&priv->spinlock, flags); + + /* Bail if there is no transfer in progress for this endpoint */ + +#ifdef CONFIG_USBHOST_ASYNCH + if (callback == NULL && !iocwait) +#else + if (!iocwait) +#endif + { + return OK; + } + + /* Stop endpoint */ + + xhci_cmd_stopep(priv, epinfo->slot, xhci_epno_get(epinfo), false); + + /* REVISIT: what if we interrupted the execution of a TD? page 139 */ + + epinfo->result = -ESHUTDOWN; + + if (iocwait) + { + /* Yes... wake it up */ + + nxsem_post(&epinfo->iocsem); + } + +#ifdef CONFIG_USBHOST_ASYNCH + /* No.. Is there a pending asynchronous transfer? */ + + else + { + /* Yes.. give back any stand-in buffer, then perform the callback. + * The endpoint has been stopped, so the controller is no longer + * writing into it. + */ + + DEBUGASSERT(callback != NULL); + xhci_dma_finish(epinfo); + callback(arg, -ESHUTDOWN); + } +#endif + + return OK; +} + +/**************************************************************************** + * Name: xhci_connect + * + * Description: + * New connections may be detected by an attached hub. This method is the + * mechanism that is used by the hub class to introduce a new connection + * and port description to the system. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * hport - The descriptor of the hub port that detected the connection + * related event + * connected - True: device connected; false: device disconnected + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_HUB +/**************************************************************************** + * Name: xhci_rhport_from_hport + * + * Description: + * The root hub port a device descends from, however many hubs are in the + * way. The slot context names it, because that is the port the traffic + * physically leaves by. + * + ****************************************************************************/ + +static FAR struct xhci_rhport_s * +xhci_rhport_from_hport(FAR struct usbhost_xhci_s *priv, + FAR struct usbhost_hubport_s *hport) +{ + while (hport->parent != NULL) + { + hport = hport->parent; + } + + return &priv->rhport[hport->port]; +} + +/**************************************************************************** + * Name: xhci_hub_update + * + * Description: + * Tell the controller that a device is a hub, so that it will route to + * what is behind it. + * + * The slot was created before anyone knew: a hub is addressed and + * configured like any other device, and only then does its class driver + * read the descriptor saying how many ports it has. So the slot context + * is corrected here, the first time something appears behind it. + * + ****************************************************************************/ + +static int xhci_hub_update(FAR struct usbhost_xhci_s *priv, + FAR struct usbhost_hubport_s *hubport) +{ + FAR struct xhci_slot_ctx_s *in; + FAR struct xhci_dev_s *dev; + uint64_t ctx; + int ret; + + dev = xhci_dev_from_hport(priv, hubport); + if (dev == NULL || dev->ishub || hubport->nports == 0) + { + /* Nothing to correct: no slot for it, already done, or the hub class + * driver has not reported the descriptor. + */ + + return OK; + } + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Only the slot context changes, and it must go in carrying everything + * the controller already holds, so start from the output context it has + * been maintaining. + */ + + up_invalidate_dcache((uintptr_t)dev->ctx, + (uintptr_t)dev->ctx + XHCI_DEVCTX_SIZE(priv)); + + xhci_context_ctrl(priv, dev, 0, XHCI_IN_CTX1_A(XHCI_SLOT_FLAG)); + + in = xhci_in_slot(priv, dev->input); + in->ctx[0] = xhci_out_slot(dev->ctx)->ctx[0] | htole32(XHCI_ST_CTX0_HUB); + in->ctx[1] = (xhci_out_slot(dev->ctx)->ctx[1] & + ~htole32(XHCI_ST_CTX1_PORTS_MASK)) | + htole32(XHCI_ST_CTX1_PORTS_SET(hubport->nports)); + in->ctx[2] = (xhci_out_slot(dev->ctx)->ctx[2] & + ~htole32(XHCI_ST_CTX2_TTT_MASK)) | + htole32(XHCI_ST_CTX2_TTT_SET(hubport->ttt)); + in->ctx[3] = xhci_out_slot(dev->ctx)->ctx[3]; + + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + XHCI_INCTX_SIZE(priv)); + + ctx = up_addrenv_va_to_pa(dev->input); + + nxmutex_unlock(&priv->lock); + + ret = xhci_cmd_cfgep(priv, dev->slot, ctx, false); + if (ret < 0) + { + uerr("failed to describe the hub on slot %d: %d\n", dev->slot, ret); + return ret; + } + + dev->ishub = true; + + syslog(LOG_INFO, "%s: port %d: hub with %d port%s\n", + priv->name, xhci_rhport_from_hport(priv, hubport)->hport.hport.port + + 1, hubport->nports, hubport->nports == 1 ? "" : "s"); + + return OK; +} + +static int xhci_connect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport, + bool connected) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_dev_s *dev; + irqstate_t flags; + int ret; + + DEBUGASSERT(priv != NULL && hport != NULL && !ROOTHUB(hport)); + + /* The hub owns this port, so there is no port register here to consult + * and no reset to drive: what the hub reports is the whole of what the + * controller can know about it. + */ + + hport->connected = connected; + + if (connected) + { + /* The controller has to know the port belongs to a hub before it will + * carry anything to it. + */ + + ret = xhci_hub_update(priv, hport->parent); + if (ret < 0) + { + return ret; + } + + /* Give the device a slot. The hub allocated its control endpoint + * before saying anything, so that endpoint is what the slot gets. + */ + + ret = xhci_device_init(priv, xhci_rhport_from_hport(priv, hport), + hport, (FAR struct xhci_epinfo_s *)hport->ep0); + if (ret < 0) + { + uerr("port %d: no slot for the device: %d\n", hport->port, ret); + return ret; + } + } + else + { + dev = xhci_dev_from_hport(priv, hport); + if (dev != NULL) + { + xhci_device_deinit(priv, dev); + } + } + + flags = spin_lock_irqsave(&priv->spinlock); + + /* Queue it for the waiter. Dropping one when the queue is full would + * lose a device silently, and the queue is as long as the controller has + * slots, so a full one means every slot is already spoken for. + */ + + if ((uint8_t)(priv->hhead + 1) % CONFIG_USBHOST_XHCI_MAX_DEVS != + priv->htail) + { + priv->hports[priv->hhead] = hport; + priv->hhead = (priv->hhead + 1) % CONFIG_USBHOST_XHCI_MAX_DEVS; + } + else + { + uerr("no room to report port %d\n", hport->port + 1); + } + + if (priv->pscwait) + { + priv->pscwait = false; + nxsem_post(&priv->pscsem); + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + return OK; +} +#endif + +/**************************************************************************** + * Name: xhci_disconnect + * + * Description: + * Called by the class when an error occurs and device has been + * disconnected. The USB host driver should discard the handle to the + * class instance (it is stale) and not attempt any further interaction + * with the class driver instance (until a new instance is received from + * the create() method). The driver should not call the class + * disconnected() method. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * hport - The port from which the device is being disconnected. Might be + * a port on a hub. + * + * Returned Value: + * None + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_dev_s *dev; + + DEBUGASSERT(hport != NULL); + hport->devclass = NULL; + + /* Deinit the device that was on this port. Taking it from the port and + * not from the root port matters once a hub is in the way, where the root + * port names the hub rather than the device going away. + */ + + dev = xhci_dev_from_hport(priv, hport); + if (dev != NULL) + { + xhci_device_deinit(priv, dev); + } +} + +/**************************************************************************** + * Name: xhci_hw_getparams + * + * Description: + * Get hardware description of a connected xHCI device. + * + ****************************************************************************/ + +static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv) +{ + uint32_t regval; + uint32_t erst; + + /* Get data form Host Controller Capability 1 Parameters */ + + /* Context entry stride, 32 or 64 bytes as the controller reports. The + * wider form is the same fields with padding. + */ + + regval = xhci_capa_getreg(priv, XHCI_HCCPARAMS1); + priv->ctxsize = (regval & XHCI_HCCPARAMS1_CSZ) ? 64 : 32; + + uinfo("context size = %d\n", priv->ctxsize); + + /* Get data from Structural Parameters 1 register */ + + regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS1); + priv->no_slots = XHCI_HCSPARAMS1_MAXSLOTS(regval); + priv->no_ports = XHCI_HCSPARAMS1_MAXPORTS(regval); + + /* Limit number of slots to number of devices */ + + if (priv->no_slots > CONFIG_USBHOST_XHCI_MAX_DEVS) + { + priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS; + } + + syslog(LOG_INFO, "%s: %d root port%s, %d device slot%s\n", + priv->name, priv->no_ports, priv->no_ports == 1 ? "" : "s", + priv->no_slots, priv->no_slots == 1 ? "" : "s"); + + /* Check if valid */ + + if (priv->no_slots == 0 || priv->no_ports == 0) + { + return -EINVAL; + } + + /* Get data from Structural Parameters 2 register */ + + regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS2); + priv->no_scratch = XHCI_HCSPARAMS2_MAXSPB(regval); + + uinfo("no scratch = %d\n", priv->no_scratch); + + /* How many event ring segments the controller will allow, which is a + * power of two and can reach 32768, so it is worked out at full width + * and only then narrowed to what this driver actually uses. Computed + * into the field directly it would wrap to zero on any controller + * offering more than 128 segments, and a table declared to hold no + * entries gives a controller with nowhere to report anything. + */ + + erst = 1ul << XHCI_HCSPARAMS2_ERST(regval); + + uinfo("erst max = %" PRIu32 "\n", erst); + + /* Limit event ring segment table to 1 */ + + priv->no_erst = (erst > XHCI_MAX_ERST) ? XHCI_MAX_ERST : erst; + + uinfo("no erst = %d\n", priv->no_erst); + + return OK; +} + +/**************************************************************************** + * Name: xhci_irq_initialize + * + * Description: + * Ask the bus this controller was found on for its interrupt. See + * struct xhci_bus_ops_s in include/nuttx/usb/xhci.h. + * + ****************************************************************************/ + +static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv) +{ + return priv->ops->irq_attach(priv->arg, xhci_interrupt, priv); +} + +/**************************************************************************** + * Name: xhci_mem_alloc + * + * Description: + * Allocated memory for a new detected xHCI device. + * + ****************************************************************************/ + +static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) +{ + size_t tmp; + int i; + + /* Allocate the Scratchpad Buffer Array, if the controller wants one. + * + * A controller may ask for no scratch space at all (QEMU's does). A + * zero byte allocation returns NULL, indistinguishable from out of + * memory, so test the count first. + */ + + if (priv->no_scratch > 0) + { + tmp = priv->no_scratch * sizeof(uint64_t); + priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp); + if (!priv->pg_sb) + { + uerr("pg_sb malloc failed\n"); + return -ENOMEM; + } + + memset(priv->pg_sb, 0, tmp); + } + + for (i = 0; i < priv->no_scratch; i++) + { + /* Alloc page for each entry in array */ + + priv->pg_sb[i] = up_addrenv_va_to_pa( + kmm_memalign(XHCI_PAGE_SIZE, XHCI_PAGE_SIZE)); + if (!priv->pg_sb[i]) + { + uerr("pg_sb[i] malloc failed\n"); + return -ENOMEM; + } + + /* Reset page */ + + memset((FAR void *)(up_addrenv_pa_to_va(priv->pg_sb[i])), + 0, XHCI_PAGE_SIZE); + } + + /* Allocate Device Context Array which shall be: + * size = MaxSlotsEn + 1 entries + */ + + tmp = sizeof(uint64_t) * (priv->no_slots + 1); + priv->pg_ctx = kmm_memalign(XHCI_BUF_ALIGN, tmp); + if (!priv->pg_ctx) + { + uerr("pg_ctx malloc failed\n"); + return -ENOMEM; + } + + /* Reset context */ + + memset(priv->pg_ctx, 0, tmp); + + /* Allocate Event Table */ + + tmp = sizeof(struct xhci_event_ring_s) * priv->no_erst; + priv->pg_erst = kmm_memalign(XHCI_BUF_ALIGN, tmp); + if (!priv->pg_erst) + { + uerr("priv->pg_erst malloc failed\n"); + return -ENOMEM; + } + + memset(priv->pg_erst, 0, tmp); + + /* Allocate root hub ports */ + + priv->rhport = kmm_zalloc(priv->no_ports * sizeof(struct xhci_rhport_s)); + if (!priv->rhport) + { + uerr("rhport zalloc failed!\n"); + return -ENOMEM; + } + + /* Allocate xHC devices array */ + + priv->devs = kmm_zalloc(priv->no_slots * sizeof(struct xhci_dev_s)); + if (!priv->devs) + { + uerr("devs zalloc failed!\n"); + return -ENOMEM; + } + + /* Allocate xHC devices resources */ + + for (i = 0; i < priv->no_slots; i++) + { + /* Allocate Device Context */ + + /* The base address array holds these, and every entry in it must be + * 64 byte aligned, so the allocation has to be too. + */ + + priv->devs[i].ctx = kmm_memalign(XHCI_CTX_ALIGN, + XHCI_DEVCTX_SIZE(priv)); + if (!priv->devs[i].ctx) + { + uerr("dev ctx zalloc failed!\n"); + return -ENOMEM; + } + + /* Allocate Input Context. The Input Context shall be physically + * contiguous within a page + */ + + priv->devs[i].input = kmm_memalign((XHCI_PAGE_SIZE / 2), + XHCI_INCTX_SIZE(priv)); + if (!priv->devs[i].input) + { + uerr("dev input zalloc failed!\n"); + return -ENOMEM; + } + + /* No endpoint for device yet */ + + tmp = sizeof(uintptr_t) * XHCI_MAX_ENDPOINTS; + memset(priv->devs[i].epinfo, 0, tmp); + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_mem_free + * + * Description: + * Free allocated memory for a xHCI device. + * + ****************************************************************************/ + +static int xhci_mem_free(FAR struct usbhost_xhci_s *priv) +{ + int i; + + /* Free scratch buffers */ + + for (i = 0; i < priv->no_scratch; i++) + { + kmm_free((FAR void *)priv->pg_sb[i]); + } + + kmm_free(priv->pg_sb); + + /* Free devices */ + + for (i = 0; i < priv->no_slots; i++) + { + kmm_free(priv->devs[i].ctx); + kmm_free(priv->devs[i].input); + } + + kmm_free(priv->devs); + kmm_free(priv->pg_ctx); + kmm_free(priv->pg_erst); + kmm_free(priv->rhport); + + /* Free command ring and event ring */ + + xhci_ring_deinit(&priv->cmd); + xhci_ring_deinit(&priv->evnt); + + return OK; +} + +/**************************************************************************** + * Name: xhci_hw_initialize + * + * Description: + * One-time setup of the host controller hardware for normal operations. + * + * Input Parameters: + * priv -- USB host driver private data structure. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv) +{ + int ret; + + /* Synchronize with BIOS */ + + ret = xhci_bios_wait(priv); + if (ret < 0) + { + uerr("Failed to get xhci controller!\n"); + goto errout; + } + + /* Get structural parameters */ + + ret = xhci_hw_getparams(priv); + if (ret < 0) + { + goto errout; + } + + /* Allocate all required memory */ + + ret = xhci_mem_alloc(priv); + if (ret < 0) + { + goto errout; + } + + /* Halt controller */ + + ret = xhci_ctrl_halt(priv); + if (ret < 0) + { + goto errout; + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: xhci_sw_initialize + * + * Description: + * One-time setup of the host driver state structure. + * + * Input Parameters: + * priv -- USB host driver private data structure. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv) +{ + FAR struct xhci_rhport_s *rhport; + FAR struct usbhost_hubport_s *hport; + int i; + + /* Initialize sync objects */ + + nxmutex_init(&priv->lock); + nxsem_init(&priv->pscsem, 0, 0); + nxsem_init(&priv->cmdsem, 0, 0); + + /* Initialize function address generation logic + * REVISIT: xHCI hardware is responsible for device address, but NuttX USB + * Host stack require this to be initialized. + */ + + usbhost_devaddr_initialize(&priv->devgen); + + /* Initialize devices */ + + for (i = 0; i < priv->no_slots; i++) + { + /* Slot disabled by defaulte */ + + priv->devs[i].state = XHCI_SLOT_DISABLED; + } + + /* Initialize the root hub port structures */ + + for (i = 0; i < priv->no_ports; i++) + { + rhport = &priv->rhport[i]; + + /* No device slot yet */ + + rhport->dev = NULL; + + /* Connect xhci instance */ + + rhport->priv = priv; + + /* Initialize the device operations */ + + rhport->drvr.ep0configure = xhci_ep0configure; + rhport->drvr.epalloc = xhci_epalloc; + rhport->drvr.epfree = xhci_epfree; + rhport->drvr.alloc = xhci_alloc; + rhport->drvr.free = xhci_free; + rhport->drvr.ioalloc = xhci_ioalloc; + rhport->drvr.iofree = xhci_iofree; + rhport->drvr.ctrlin = xhci_ctrlin; + rhport->drvr.ctrlout = xhci_ctrlout; + rhport->drvr.transfer = xhci_transfer; +#ifdef CONFIG_USBHOST_ASYNCH + rhport->drvr.asynch = xhci_asynch; +#endif + rhport->drvr.cancel = xhci_cancel; +#ifdef CONFIG_USBHOST_HUB + rhport->drvr.connect = xhci_connect; +#endif + rhport->drvr.disconnect = xhci_disconnect; + rhport->hport.pdevgen = &priv->devgen; + + /* Initialize EP0 */ + + rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL; + rhport->ep0.epno = 0; + rhport->ep0.devaddr = 0; + nxsem_init(&rhport->ep0.iocsem, 0, 0); + nxmutex_init(&rhport->ep0.exclsem); + + /* Initialize the public port representation */ + + hport = &rhport->hport.hport; + hport->drvr = &rhport->drvr; +#ifdef CONFIG_USBHOST_HUB + hport->parent = NULL; +#endif + hport->ep0 = &rhport->ep0; + hport->port = i; + hport->speed = USB_SPEED_FULL; + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: xhci_initialize + * + * Description: + * Bring up an xHCI controller and start watching its root hub ports. + * + * The caller has already found the controller and knows how to reach it; + * everything this function does from here on is described by the xHCI + * specification and is the same on any bus. + * + * Input Parameters: + * base - Where the controller's register block starts. The capability + * registers are at the beginning of it and say where the rest are. + * ops - How to reach the interrupt this controller raises + * arg - Opaque value passed back to ops + * + * Returned Value: + * A connection to hand to usbhost_waiter_initialize() or to drive + * directly; NULL on failure. + * + ****************************************************************************/ + +FAR struct usbhost_connection_s * +xhci_initialize(FAR const char *name, uintptr_t base, + FAR const struct xhci_bus_ops_s *ops, FAR void *arg) +{ + FAR struct usbhost_conn_xhci_s *conn = NULL; + FAR struct usbhost_xhci_s *priv = NULL; + uint32_t regval; + int ret; + + DEBUGASSERT(name != NULL && base != 0 && ops != NULL && + ops->irq_attach != NULL); + + conn = kmm_zalloc(sizeof(struct usbhost_conn_xhci_s)); + if (conn == NULL) + { + return NULL; + } + + priv = kmm_zalloc(sizeof(struct usbhost_xhci_s)); + if (priv == NULL) + { + kmm_free(conn); + return NULL; + } + + conn->conn.wait = xhci_wait; + conn->conn.enumerate = xhci_enumerate; + conn->priv = priv; + + priv->name = name; + priv->ops = ops; + priv->arg = arg; + priv->base = base; + + /* The capability registers are the only ones at a known offset. Every + * other block is where they say it is. + */ + + priv->capa_base = priv->base; + priv->oper_base = priv->base + xhci_capa_getreg_1b(priv, XHCI_CAPLENGTH); + priv->runt_base = priv->base + xhci_capa_getreg(priv, XHCI_RTSOFF); + priv->door_base = priv->base + xhci_capa_getreg(priv, XHCI_DBOFF); + + usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0); + + ret = xhci_hw_initialize(priv); + if (ret < 0) + { + uerr("failed to initialize HW: %d\n", ret); + goto errout; + } + + ret = xhci_sw_initialize(priv); + if (ret < 0) + { + uerr("failed to initialize SW: %d\n", ret); + goto errout; + } + + ret = xhci_ctrl_start(priv); + if (ret < 0) + { + usbhost_trace1(XHCI_TRACE1_START_FAILED, 0); + goto errout; + } + + /* Take the interrupt only now. + * + * The handler defers to a worker that walks the event ring, and the ring + * does not exist until the controller has been started. A controller + * left running by a boot loader can have an interrupt pending the moment + * the line is enabled, so attaching any earlier is a race with nothing + * to answer it. + */ + + ret = xhci_irq_initialize(priv); + if (ret < 0) + { + uerr("failed to attach interrupt: %d\n", ret); + goto errout; + } + + /* Acknowledge anything the controller raised before the handler was + * attached. A message is sent once, on the transition, so a bit set in + * that window would never produce another. Clear them, so the next + * event is a fresh assertion. + */ + + regval = xhci_oper_getreg(priv, XHCI_USBSTS); + xhci_oper_putreg(priv, XHCI_USBSTS, regval); + + regval = xhci_runt_getreg(priv, XHCI_IMAN(0)); + xhci_runt_putreg(priv, XHCI_IMAN(0), regval | XHCI_IMAN_IP); + +#ifdef CONFIG_DEBUG_USB_INFO + xhci_dump_mem(priv, "after init"); +#endif + + /* A device already in a port at power up produces no status change + * interrupt to tell us it is there, so look for one. + */ + + xhci_probe_ports(priv); + + ret = usbhost_waiter_initialize(&conn->conn); + if (ret < 0) + { + uerr("failed to initialize waiter: %d\n", ret); + goto errout; + } + + conn->pid = ret; + + return &conn->conn; + +errout: + xhci_mem_free(priv); + kmm_free(conn); + kmm_free(priv); + + return NULL; +} + +/**************************************************************************** + * Name: xhci_uninitialize + * + * Description: + * Stop watching a controller's ports and give back everything + * xhci_initialize() took. + * + ****************************************************************************/ + +void xhci_uninitialize(FAR struct usbhost_connection_s *conn) +{ + FAR struct usbhost_conn_xhci_s *xconn = XHCI_XCONN_FROM_CONN(conn); + FAR struct usbhost_xhci_s *priv; + + DEBUGASSERT(conn != NULL); + priv = xconn->priv; + + /* Stop the waiter before the memory it walks goes away */ + + kthread_delete(xconn->pid); + + priv->ops->irq_detach(priv->arg); + + xhci_mem_free(priv); + + kmm_free(priv); + kmm_free(xconn); +} diff --git a/drivers/usbhost/usbhost_xhci.h b/drivers/usbhost/usbhost_xhci.h index 3888eabacf6b6..23982c50a9dad 100644 --- a/drivers/usbhost/usbhost_xhci.h +++ b/drivers/usbhost/usbhost_xhci.h @@ -27,6 +27,10 @@ #include +#include + +#include + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -268,6 +272,19 @@ #define XHCI_PORTSC_DR (1 << 30) /* Bit 30: Device Removable */ #define XHCI_PORTSC_WPR (1 << 31) /* Bit 31: Warm Port Reset */ +/* The bits of PORTSC that are write-one-to-clear. + * + * A read-modify-write of this register acts on every one of these bits + * that happened to be set, disabling the port and discarding the changes + * it was reporting. Mask them out of the value first, unless clearing + * them is the intent. + */ + +#define XHCI_PORTSC_RW1C (XHCI_PORTSC_PED | XHCI_PORTSC_CSC | \ + XHCI_PORTSC_PEC | XHCI_PORTSC_WRC | \ + XHCI_PORTSC_OCC | XHCI_PORTSC_PRC | \ + XHCI_PORTSC_PLC | XHCI_PORTSC_CEC) + /* Port Power Management Status and Control (USB3) */ #define XHCI_PORTPMSC_U1TO_SHIFT (0) /* Bits 0-7: U1 Timeout */ @@ -302,6 +319,17 @@ #define XHCI_IMOD_IMODI_SHIFT (0) /* Bits 0-15: Interrupt Moderation Interval */ #define XHCI_IMOD_IMODC_SHIFT (16) /* Bits 16-31: Interrupt Moderation Counter */ +/* What to set the moderation interval to, in 250ns units. + * + * The reset default is 4000, a millisecond, which is far too long to wait + * to be told a transfer finished. Zero is too short: it puts no bound on + * how often a controller may interrupt, and a polled device such as a + * keyboard on an interrupt endpoint will then occupy a processor. 160 is + * 40us, which is what Linux uses. + */ + +#define XHCI_IMOD_DEFAULT (160) + /* Event Ring Segment Table Size */ #define XHCI_ERSTS_MASK (0xffff) /* Bit 0-15: Event Ring Segment Table Size */ @@ -480,8 +508,26 @@ #define XHCI_ST_CTX0_RTSTR_SHIFT (0) /* Bits 0:19: Route String */ #define XHCI_ST_CTX0_RTSTR_MASK (0xfffff << XHCI_ST_CTX0_RTSTR_SHIFT) +#define XHCI_ST_CTX0_RTSTR_SET(x) (((x) << XHCI_ST_CTX0_RTSTR_SHIFT) & \ + XHCI_ST_CTX0_RTSTR_MASK) #define XHCI_ST_CTX0_SPEED_SHIFT (20) /* Bits 20:23: Speed */ #define XHCI_ST_CTX0_SPEED_MASK (0xf << XHCI_ST_CTX0_SPEED_SHIFT) +#define XHCI_ST_CTX0_SPEED_SET(x) (((x) << XHCI_ST_CTX0_SPEED_SHIFT) & \ + XHCI_ST_CTX0_SPEED_MASK) + +/* Port Speed IDs, which xHCI numbers its own way rather than USB's. These + * are the values every controller reports in PORTSC and expects back in a + * slot context; a device is described to the controller with one of them + * and with nothing else, so zero is not a default but an invalid context. + * + * Reference: Table 7-13: Default USB Speed ID Mapping + */ + +#define XHCI_SPEED_FULL (1) +#define XHCI_SPEED_LOW (2) +#define XHCI_SPEED_HIGH (3) +#define XHCI_SPEED_SUPER (4) +#define XHCI_SPEED_SUPER_PLUS (5) #define XHCI_ST_CTX0_MTT (1 << 25) /* Bit 25: Multi-TT */ /* Bit 24: Reserved */ #define XHCI_ST_CTX0_HUB (1 << 26) /* Bit 26: Hub */ @@ -495,6 +541,25 @@ #define XHCI_ST_CTX1_PORTS_MASK (0xff << XHCI_ST_CTX1_PORTS_SHIFT) #define XHCI_ST_CTX1_PORTS_SET(x) (((x) << XHCI_ST_CTX1_PORTS_SHIFT) & XHCI_ST_CTX1_PORTS_MASK) +/* Slot Context dword 2 describes the transaction translator that carries a + * low or full speed device sitting behind a high speed hub. It names the + * nearest high speed ancestor, which is the hub whose TT does the work, and + * not the hub the device is plugged into if those differ. + */ + +#define XHCI_ST_CTX2_TTHSID_SHIFT (0) /* Bit 0-7: TT Hub Slot ID */ +#define XHCI_ST_CTX2_TTHSID_MASK (0xff << XHCI_ST_CTX2_TTHSID_SHIFT) +#define XHCI_ST_CTX2_TTHSID_SET(x) (((x) << XHCI_ST_CTX2_TTHSID_SHIFT) & \ + XHCI_ST_CTX2_TTHSID_MASK) +#define XHCI_ST_CTX2_TTPORT_SHIFT (8) /* Bit 8-15: TT Port Number */ +#define XHCI_ST_CTX2_TTPORT_MASK (0xff << XHCI_ST_CTX2_TTPORT_SHIFT) +#define XHCI_ST_CTX2_TTPORT_SET(x) (((x) << XHCI_ST_CTX2_TTPORT_SHIFT) & \ + XHCI_ST_CTX2_TTPORT_MASK) +#define XHCI_ST_CTX2_TTT_SHIFT (16) /* Bit 16-17: TT Think Time */ +#define XHCI_ST_CTX2_TTT_MASK (0x3 << XHCI_ST_CTX2_TTT_SHIFT) +#define XHCI_ST_CTX2_TTT_SET(x) (((x) << XHCI_ST_CTX2_TTT_SHIFT) & \ + XHCI_ST_CTX2_TTT_MASK) + #define XHCI_ST_CTX3_ADDR_SHIFT (0) /* Bit 0-7: USB Device Address */ #define XHCI_ST_CTX3_ADDR_MASK (0xff << XHCI_ST_CTX3_ADDR_SHIFT) #define XHCI_ST_CTX3_ADDR_SET(x) (((x) << XHCI_ST_CTX3_ADDR_SHIFT) & XHCI_ST_CTX3_ADDR_MASK) diff --git a/drivers/usbhost/usbhost_xhci_pci.c b/drivers/usbhost/usbhost_xhci_pci.c index 03b408168873d..b65b9c1449344 100644 --- a/drivers/usbhost/usbhost_xhci_pci.c +++ b/drivers/usbhost/usbhost_xhci_pci.c @@ -1,6 +1,8 @@ /**************************************************************************** * drivers/usbhost/usbhost_xhci_pci.c * + * SPDX-License-Identifier: Apache-2.0 + * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The @@ -18,6 +20,15 @@ * ****************************************************************************/ +/* Finding an xHCI controller on a PCI bus. + * + * The controller itself is described by its specification and driven by + * usbhost_xhci.c, which is the same code wherever the part is fitted. This + * file is only the part that is true of PCI and of nothing else: which + * device identifiers to answer to, how to switch a device on and find its + * register window, and how its interrupt is arranged. + */ + /**************************************************************************** * Included Files ****************************************************************************/ @@ -25,453 +36,39 @@ #include #include -#include +#include #include -#include - +#include +#include #include -#include -#include -#include -#include - #include - -#include #include -#include -#include #include "usbhost_xhci.h" -#include "usbhost_xhci_trace.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/* Pre-requisites */ - -#if CONFIG_USBHOST_XHCI_MAX_DEVS > XHCI_MAX_DEVS -# error Invalid value for CONFIG_USBHOST_XHCI_MAX_DEVS -#endif - -/* USB HUB support is not yet implemented */ - -#ifdef CONFIG_USBHOST_HUB -# error XHCI USB HUB support is not yet implemented -#endif - -/* Some constants for this implementation */ - -#define XHCI_MAX_ERST (1) -#define XHCI_CMD_MAX (16) -#define XHCI_EVENT_MAX (232) -#define XHCI_TD_MAX (8) -#define XHCI_BUFSIZE (512) - -/* Port numbers macros */ - -#define HPNDX(hp) ((hp)->port) -#define HPORT(hp) (HPNDX(hp) + 1) -#define RHPNDX(rh) ((rh)->hport.hport.port) -#define RHPORT(rh) (RHPNDX(rh) + 1) - -/* Other helper macros */ - -#define XHCI_XCONN_FROM_CONN(c) ((FAR struct usbhost_conn_xhci_s *)c) -#define XHCI_PRIV_FROM_CONN(c) (XHCI_XCONN_FROM_CONN(c)->priv) -#define XHCI_RHPORT_FROM_DRVR(d) ((FAR struct xhci_rhport_s *)d) -#define XHCI_PRIV_FROM_RHPORT(r) (r->priv) -#define XHCI_PRIV_FROM_DRVR(d) (XHCI_PRIV_FROM_RHPORT(XHCI_RHPORT_FROM_DRVR(d))) /**************************************************************************** * Private Types ****************************************************************************/ -/* USB device state - * - * Reference: - * - 4.5.3: Slot States +/* What this file needs to remember about a controller, which is only the + * things the PCI layer will ask for again when the device goes away. */ -enum xhci_slot_e -{ - XHCI_SLOT_DISABLED, - XHCI_SLOT_ENABLED, - XHCI_SLOT_DEFAULT, - XHCI_SLOT_ADDRESSED, - XHCI_SLOT_CONFIGURED, -}; - -/* Ring state */ - -struct xhci_ring_s -{ - FAR struct xhci_trb_s *ring; /* Ring */ - size_t i; /* Ring pointer */ - size_t len; /* Ring length */ - bool ccs; /* Consumer Cycle State */ -}; - -/* EP info */ - -struct xhci_epinfo_s -{ - uint8_t epno:7; /* Endpoint number */ - uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */ - uint8_t toggle:1; /* Next data toggle */ -#ifndef CONFIG_USBHOST_INT_DISABLE - uint8_t interval; /* Polling interval */ -#endif - uint8_t devaddr; /* Device address returned from xHCI */ - uint8_t status; /* Retained token status bits (for debug purposes) */ - bool iocwait; /* TRUE: Thread is waiting for transfer completion */ - uint8_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ - int result; /* The result of the transfer */ - size_t xfrd; /* On completion, will hold the number of bytes transferred */ - size_t buflen; /* Buffer length used for transfer */ - sem_t iocsem; /* Semaphore used to wait for transfer completion */ -#ifdef CONFIG_USBHOST_ASYNCH - usbhost_asynch_t callback; /* Transfer complete callback */ - FAR void *arg; /* Argument that accompanies the callback */ -#endif - struct xhci_ring_s td; /* TD ring for this endpoint */ - uint8_t slot; /* Slot where this EP resides */ - - /* These fields are used in the split-transaction protocol. */ - - uint8_t hubaddr; /* USB device address of the high-speed hub below - * which a full/low-speed device is attached. - */ - uint8_t hubport; /* The port on the above high-speed hub. */ -}; - -/* This structure retains the state of one root hub port */ - -struct xhci_rhport_s -{ - /* Common device fields. This must be the first thing defined in the - * structure so that it is possible to simply cast from struct usbhost_s - * to struct xhci_rhport_s. - */ - - struct usbhost_driver_s drvr; - - /* Root hub port status */ - - bool connected; /* Connected to device */ - int8_t slot; /* Slot ID associated with this port */ - struct xhci_epinfo_s ep0; /* EP0 endpoint info */ - struct usbhost_roothubport_s hport; /* This is the hub port description understood - * by class drivers - */ - FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */ - FAR struct xhci_dev_s *dev; /* Device reference */ -}; - -/* USB Devices xhci data */ - -struct xhci_dev_s -{ - uint8_t state; /* Slot stat */ - uint8_t slot; /* Slot ID associated with this device */ - FAR struct xhci_dev_ctx_s *ctx; /* Output Device Context. Managed by xHC */ - FAR struct xhci_input_dev_ctx_s *input; /* Input Device Context. Input to xHC */ - FAR struct xhci_rhport_s *rhport; /* Root Hub Port associated with this device */ - - /* Reference to allocated endpoints */ - - FAR struct xhci_epinfo_s *epinfo[XHCI_MAX_ENDPOINTS]; -}; - -/* This structure contains the internal, private state of the xhci driver */ - -struct usbhost_xhci_s -{ -#ifdef CONFIG_USBHOST_HUB - FAR struct usbhost_hubport_s *hport; /* Used to pass external hub port events */ -#endif - struct usbhost_devaddr_s devgen; /* Address generation data */ - bool pscwait; /* TRUE: Thread is waiting for port status change event */ - sem_t pscsem; /* Semaphore to wait for port status change events */ - mutex_t lock; /* Support mutually exclusive access */ - spinlock_t spinlock; - - /* xHCI parameters */ - - uint8_t no_ports; /* Number of USB Ports */ - uint8_t no_slots; /* Maximum number of Device Slots (one per USB device) */ - uint8_t no_scratch; /* Number of scratch buffers */ - uint8_t no_erst; /* Event Ring Segment Table size */ - - /* xHCI data */ - - FAR struct xhci_rhport_s *rhport; /* Root hub ports */ - FAR struct xhci_dev_s *devs; /* USB device xHC data. One entry per - * one supported USB device. - */ - - /* Allocated buffers for controller */ - - FAR uint64_t *pg_ctx; /* Device Context (no_slots + 1 elements). - * Slot 0 reserved for Scratchpad Buffer Array - */ - FAR uint64_t *pg_sb; /* Scratchpad Buffer Array (no_scratch elements) */ - FAR struct xhci_event_ring_s *pg_erst; /* Event Ring Segment Table */ - - /* Event ring handling */ - - struct xhci_ring_s evnt; /* Event ring handler */ - - /* Command ring handling */ - - sem_t cmdsem; /* Command done semaphore */ - struct xhci_trb_s cmdres; /* Command result */ - struct xhci_ring_s cmd; /* Command ring handler */ - - /* PCI data */ - - FAR struct pci_device_s *pcidev; /* PCI device reference */ - int irq; /* IRQ number used by the device */ - uint32_t pending; /* IRQ pending status */ - struct work_s work; /* IRQ work */ - struct work_s pscwork; /* Port status change work */ - uint64_t base; /* xHCI base address */ - uint64_t capa_base; /* Capability base */ - uint64_t oper_base; /* Operational base */ - uint64_t runt_base; /* Runtime base */ - uint64_t door_base; /* Doorbell base */ -}; - -/* xHCI connection monitoring */ - -struct usbhost_conn_xhci_s +struct pci_xhci_s { - struct usbhost_connection_s conn; /* Connection monitoring */ - FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */ - int pid; /* Waiter thread PID */ + FAR struct pci_device_s *dev; /* The device we were given */ + FAR struct usbhost_connection_s *conn; /* What the controller gave back */ + int irq; /* Allocated MSI-X vector */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ -/* Helpers ******************************************************************/ - -static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset); - -static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, - unsigned int offset); -static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint8_t value); - -static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset); -static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value); - -static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint64_t value); - -static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset); -static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value); - -static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint64_t value); - -static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value); - -/* Byte stream access helper functions **************************************/ - -static inline uint16_t xhci_getle16(FAR const uint8_t *val); - -/* Debug ********************************************************************/ - -#ifdef CONFIG_DEBUG_USB_INFO -static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv, - FAR const char *msg, unsigned int offset); -static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv, - FAR const char *msg, unsigned int offset); -static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv, - FAR const char *msg, unsigned int offset); -static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, - FAR const char *msg); -#endif - -/* Ring management **********************************************************/ - -static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len); -static void xhci_ring_deinit(FAR struct xhci_ring_s *ring); -static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs); -static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_ring_s *ring, - FAR struct xhci_trb_s *trb, - int len); - -/* xHCI operations **********************************************************/ - -static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv); -static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv); -static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv); -static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv); - -/* Port management **********************************************************/ - -static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv); -static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, - FAR struct usbhost_hubport_s *hport); - -/* Slot management **********************************************************/ - -static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, - uintptr_t ctx); -static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_ep_ctx_s *ctx, - uint8_t type, uint16_t maxpkt, - uint8_t maxburst, uint64_t tr_dp, - uint8_t mult, uint8_t interval); -static int xhci_address_set(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport, bool setaddr); -static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_dev_s *dev); -static int xhci_device_init(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport); -static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport); -static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo); -static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_dev_s *dev, - uint32_t drop, uint32_t add); - -/* Command handling *********************************************************/ - -static int xhci_command(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_trb_s *trb, uint16_t timeout_ms); -static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, - FAR uint8_t *slot); -static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot); -static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint64_t ctx, bool bsr); -static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint64_t ctx, bool deconfig); -static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint8_t ep, bool suspend); -static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint64_t ctx); - -/* Transfer handling ********************************************************/ - -static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_epinfo_s *epinfo); -static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - size_t buflen); -static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo); -#ifdef CONFIG_USBHOST_ASYNCH -static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - usbhost_asynch_t callback, - FAR void *arg); -static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo); -#endif -static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - FAR const struct usb_ctrlreq_s *req, - FAR uint8_t *buffer, size_t buflen); -static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - FAR uint8_t *buffer, size_t buflen); -#ifndef CONFIG_USBHOST_ISOC_DISABLE -static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - FAR uint8_t *buffer, size_t buflen); -#endif -static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_epinfo_s *epinfo); - -/* Interrupt handling *******************************************************/ - -static void xhci_portsc_work(FAR void *arg); -static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_trb_s *evt); -static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_trb_s *evt); -static int xhci_events_poll(FAR struct usbhost_xhci_s *priv); -static void xhci_interrupt_work(FAR void *arg); -static int xhci_interrupt(int irq, FAR void *context, FAR void *arg); - -/* USB host controller operations *******************************************/ - -static int xhci_wait(FAR struct usbhost_connection_s *conn, - FAR struct usbhost_hubport_s **hport); -static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, - FAR struct usbhost_hubport_s *hport); -static int xhci_enumerate(FAR struct usbhost_connection_s *conn, - FAR struct usbhost_hubport_s *hport); - -static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, - usbhost_ep_t ep0, uint8_t funcaddr, - uint8_t speed, uint16_t maxpacketsize); -static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, - FAR const struct usbhost_epdesc_s *epdesc, - FAR usbhost_ep_t *ep); -static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); -static int xhci_alloc(FAR struct usbhost_driver_s *drvr, - FAR uint8_t **buffer, FAR size_t *maxlen); -static int xhci_free(FAR struct usbhost_driver_s *drvr, - FAR uint8_t *buffer); -static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, - FAR uint8_t **buffer, size_t buflen); -static int xhci_iofree(FAR struct usbhost_driver_s *drvr, - FAR uint8_t *buffer); - -static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, - usbhost_ep_t ep0, - FAR const struct usb_ctrlreq_s *req, - FAR uint8_t *buffer); -static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, - FAR const struct usb_ctrlreq_s *req, - FAR uint8_t *buffer); -static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, - FAR const struct usb_ctrlreq_s *req, - FAR const uint8_t *buffer); -static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, - FAR usbhost_ep_t ep, uint8_t *buffer, - size_t buflen); -#ifdef CONFIG_USBHOST_ASYNCH -static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, - FAR uint8_t *buffer, size_t buflen, - usbhost_asynch_t callback, void *arg); -#endif -static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); -#ifdef CONFIG_USBHOST_HUB -static int xhci_connect(FAR struct usbhost_driver_s *drvr, - FAR struct usbhost_hubport_s *hport, - bool connected); -#endif - -static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, - FAR struct usbhost_hubport_s *hport); - -/* Initialization ***********************************************************/ - -static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv); -static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv); -static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv); -static int xhci_mem_free(FAR struct usbhost_xhci_s *priv); -static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv); -static int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv); +static int pci_xhci_irq_attach(FAR void *arg, xcpt_t handler, + FAR void *priv); +static void pci_xhci_irq_detach(FAR void *arg); static int pci_xhci_probe(FAR struct pci_device_s *dev); static void pci_xhci_remove(FAR struct pci_device_s *dev); @@ -479,6 +76,12 @@ static void pci_xhci_remove(FAR struct pci_device_s *dev); * Private Data ****************************************************************************/ +static const struct xhci_bus_ops_s g_pci_xhci_ops = +{ + .irq_attach = pci_xhci_irq_attach, + .irq_detach = pci_xhci_irq_detach, +}; + /* PCI device table */ static const struct pci_device_id_s g_pci_xhci_id_table[] = @@ -520,4311 +123,132 @@ static struct pci_driver_s g_pci_xhci_drv = ****************************************************************************/ /**************************************************************************** - * Name: xhci_capa_getreg - * - * Description: - * Get register (USB Legacy Support Capability) - * - ****************************************************************************/ - -static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset) -{ - uintptr_t addr = priv->capa_base + offset; - return *((FAR volatile uint32_t *)addr); -} - -/**************************************************************************** - * Name: xhci_capa_getreg_1b - * - * Description: - * Get 1B register (USB Legacy Support Capability) - * - ****************************************************************************/ - -static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, - unsigned int offset) -{ - uintptr_t addr = priv->capa_base + offset; - return *((FAR volatile uint8_t *)addr); -} - -/**************************************************************************** - * Name: xhci_capa_putreg_1b - * - * Description: - * Put 1B register (USB Legacy Support Capability) - * - ****************************************************************************/ - -static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint8_t value) -{ - uintptr_t addr = priv->capa_base + offset; - *((FAR volatile uint8_t *)addr) = value; -} - -/**************************************************************************** - * Name: xhci_oper_getreg + * Name: pci_xhci_irq_attach * * Description: - * Get register (Host Controller Operational Registers) + * Give the controller an interrupt. On PCI that means asking for a + * message rather than finding a wire, so the vector is allocated here and + * only then attached. * ****************************************************************************/ -static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset) +static int pci_xhci_irq_attach(FAR void *arg, xcpt_t handler, FAR void *priv) { - uintptr_t addr = priv->oper_base + offset; - return *((FAR volatile uint32_t *)addr); -} + FAR struct pci_xhci_s *pcix = arg; + int ret; -/**************************************************************************** - * Name: xhci_oper_putreg - * - * Description: - * Put register (Host Controller Operational Registers) - * - ****************************************************************************/ + ret = pci_alloc_irq(pcix->dev, &pcix->irq, 1); + if (ret != 1) + { + pcierr("Failed to allocate MSI %d\n", ret); + return ret; + } -static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value) -{ - uintptr_t addr = priv->oper_base + offset; - *((FAR volatile uint32_t *)addr) = value; -} + irq_attach(pcix->irq, handler, priv); -/**************************************************************************** - * Name: xhci_oper_putreg_8b - * - * Description: - * Put register (Host Controller Operational Registers) - * - ****************************************************************************/ + ret = pci_connect_irq(pcix->dev, &pcix->irq, 1); + if (ret != OK) + { + pcierr("Failed to connect MSI %d\n", ret); + pci_release_irq(pcix->dev, &pcix->irq, 1); -static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint64_t value) -{ - uintptr_t addr = priv->oper_base + offset; - *((FAR volatile uint64_t *)addr) = value; -} + return -ENOTSUP; + } -/**************************************************************************** - * Name: xhci_runt_getreg - * - * Description: - * Get register (Host Controller Runtime Registers) - * - ****************************************************************************/ + up_enable_irq(pcix->irq); -static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset) -{ - uintptr_t addr = priv->runt_base + offset; - return *((FAR volatile uint32_t *)addr); + return OK; } /**************************************************************************** - * Name: xhci_runt_putreg - * - * Description: - * Put register (Host Controller Runtime Registers) - * + * Name: pci_xhci_irq_detach ****************************************************************************/ -static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value) +static void pci_xhci_irq_detach(FAR void *arg) { - uintptr_t addr = priv->runt_base + offset; - *((FAR volatile uint32_t *)addr) = value; -} - -/**************************************************************************** - * Name: xhci_runt_putreg_8b - * - * Description: - * Put register (Host Controller Runtime Registers) - * - ****************************************************************************/ + FAR struct pci_xhci_s *pcix = arg; -static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint64_t value) -{ - uintptr_t addr = priv->runt_base + offset; - *((FAR volatile uint64_t *)addr) = value; + pci_release_irq(pcix->dev, &pcix->irq, 1); } /**************************************************************************** - * Name: xhci_door_putreg + * Name: pci_xhci_probe * * Description: - * Put register (Doorbell Registers) + * Switch on a PCI xHCI controller and hand it to the controller driver. * ****************************************************************************/ -static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value) -{ - uintptr_t addr = priv->door_base + offset; - *((FAR volatile uint32_t *)addr) = value; -} - -#ifdef CONFIG_DEBUG_USB_INFO -/**************************************************************************** - * Name: xhci_dump_capa_reg - ****************************************************************************/ - -static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv, - FAR const char *msg, unsigned int offset) -{ - pciinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_capa_getreg(priv, offset)); -} - -/**************************************************************************** - * Name: xhci_dump_oper_reg - ****************************************************************************/ - -static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv, - FAR const char *msg, unsigned int offset) -{ - pciinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_oper_getreg(priv, offset)); -} - -/**************************************************************************** - * Name: xhci_dump_runt_reg - ****************************************************************************/ - -static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv, - FAR const char *msg, unsigned int offset) +static int pci_xhci_probe(FAR struct pci_device_s *dev) { - pciinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_runt_getreg(priv, offset)); -} - -/**************************************************************************** - * Name: xhci_dump_mem - ****************************************************************************/ + FAR struct pci_xhci_s *pcix; + uintptr_t base; + int ret; -static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, - FAR const char *msg) -{ - int i; + pcix = kmm_zalloc(sizeof(struct pci_xhci_s)); + if (pcix == NULL) + { + return -ENOMEM; + } - pciinfo("Dump xHCI registers: %s\n", msg); + pcix->dev = dev; + dev->priv = pcix; - pciinfo("=== Host Controller Capability Registers ===\n"); - xhci_dump_capa_reg(priv, "CAPLENGTH ", XHCI_CAPLENGTH); - xhci_dump_capa_reg(priv, "HCIVERSION ", XHCI_HCIVERSION); - xhci_dump_capa_reg(priv, "HCSPARAMS1 ", XHCI_HCSPARAMS1); - xhci_dump_capa_reg(priv, "HCSPARAMS2 ", XHCI_HCSPARAMS2); - xhci_dump_capa_reg(priv, "HCSPARAMS3 ", XHCI_HCSPARAMS3); - xhci_dump_capa_reg(priv, "HCCPARAMS1 ", XHCI_HCCPARAMS1); - xhci_dump_capa_reg(priv, "DBOFF ", XHCI_DBOFF); - xhci_dump_capa_reg(priv, "RTSOFF ", XHCI_RTSOFF); - xhci_dump_capa_reg(priv, "HCCPARAMS2 ", XHCI_HCCPARAMS2); + /* The controller has to be able to reach memory on its own, and its + * registers have to be answerable, before either is used. + */ - pciinfo("=== Host Controller Operational Registers ===\n"); - xhci_dump_oper_reg(priv, "USBCMD ", XHCI_USBCMD); - xhci_dump_oper_reg(priv, "USBSTS ", XHCI_USBSTS); - xhci_dump_oper_reg(priv, "PAGESIZE ", XHCI_PAGESIZE); - xhci_dump_oper_reg(priv, "DNCTRL ", XHCI_DNCTRL); - xhci_dump_oper_reg(priv, "CRCR ", XHCI_CRCR); - xhci_dump_oper_reg(priv, "DCBAAP ", XHCI_DCBAAP); - xhci_dump_oper_reg(priv, "CONFIG ", XHCI_CONFIG); + pci_set_master(dev); + pciinfo("Enabled bus mastering\n"); + pci_enable_device(dev); + pciinfo("Enabled memory resources\n"); - for (i = 0; i < priv->no_ports; i++) + base = (uintptr_t)pci_map_bar(dev, 0); + if (base == 0) { - pciinfo("port %d --------------------------------\n", i); - xhci_dump_oper_reg(priv, "PORTSC ", XHCI_PORTSC(i)); - xhci_dump_oper_reg(priv, "PORTPMSC ", XHCI_PORTPMSC(i)); - xhci_dump_oper_reg(priv, "PORTLI ", XHCI_PORTLI(i)); + pcierr("Not found BAR 0!\n"); + ret = -EIO; + goto errout; } - /* Only one interrupter used */ + pcix->conn = xhci_initialize("usb", base, &g_pci_xhci_ops, pcix); + if (pcix->conn == NULL) + { + pcierr("xhci_initialize failed\n"); + ret = -EIO; + goto errout; + } - pciinfo("=== Host Controller Runtime Registers ===\n"); - xhci_dump_runt_reg(priv, "MFINDEX ", XHCI_MFINDEX); - xhci_dump_runt_reg(priv, "IMAN(0) ", XHCI_IMAN(0)); - xhci_dump_runt_reg(priv, "IMOD(0) ", XHCI_IMOD(0)); - xhci_dump_runt_reg(priv, "ERSTSZ(0) ", XHCI_ERSTSZ(0)); - xhci_dump_runt_reg(priv, "ERSTBA(0) ", XHCI_ERSTBA(0)); - xhci_dump_runt_reg(priv, "ERDP(0) ", XHCI_ERDP(0)); -} -#endif + return OK; -/**************************************************************************** - * Name: xhci_getle16 - * - * Description: - * Get a (possibly unaligned) 16-bit little endian value. - * - ****************************************************************************/ +errout: + pci_clear_master(dev); + pci_disable_device(dev); + kmm_free(pcix); -static inline uint16_t xhci_getle16(FAR const uint8_t *val) -{ -#ifdef CONFIG_ENDIAN_BIG - return (uint16_t)val[0] << 8 | (uint16_t)val[1]; -#else - return (uint16_t)val[1] << 8 | (uint16_t)val[0]; -#endif + return ret; } /**************************************************************************** - * Name: xhci_ring_init - * - * Description: - * Initialize xHCI ring handler. - * - * If ring buffer is already initialized, this function reset ring - * to a initial state. - * - * Returned Value: - * OK on success. - * + * Name: pci_xhci_remove ****************************************************************************/ -static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len) +static void pci_xhci_remove(FAR struct pci_device_s *dev) { - FAR struct xhci_trb_s *trb; + FAR struct pci_xhci_s *pcix = dev->priv; - if (!ring->ring) + if (pcix == NULL) { - /* Allocate ring data */ - - ring->ring = kmm_memalign(XHCI_BUF_ALIGN, - sizeof(struct xhci_trb_s) * len); - if (!ring->ring) - { - return -ENOMEM; - } - - /* Store length */ - - ring->len = len; + return; } - /* Reset data in ring */ - - memset(ring->ring, 0, ring->len * sizeof(struct xhci_trb_s)); - - /* Fill Link TRB */ - - trb = &ring->ring[ring->len - 1]; - trb->d0 = htole64(up_addrenv_va_to_pa(&ring->ring[0])); - trb->d1 = 0; - trb->d2 = 0; - - up_flush_dcache((uintptr_t)trb, (uintptr_t)(trb + 1)); - - /* Reset state */ - - ring->i = 0; - ring->ccs = true; - - return OK; -} - -/**************************************************************************** - * Name: xhci_ring_deinit - * - * Description: - * Initialize xHCI ring handler. - * - * Returned Value: - * None - * - ****************************************************************************/ - -static void xhci_ring_deinit(FAR struct xhci_ring_s *ring) -{ - /* Free ring memory */ - - kmm_free(ring->ring); -} - -/**************************************************************************** - * Name: xhci_ring_reset - * - * Description: - * Reset xHCI ring handler. - * - * Returned Value: - * None - * - ****************************************************************************/ - -static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs) -{ - /* Reset pointer */ - - ring->i = 0; - - /* Swap CCS if requestede */ - - if (swap_ccs) - { - ring->ccs = !ring->ccs; - } - else - { - ring->ccs = true; - } -} - -/**************************************************************************** - * Name: xhci_add_trb - * - * Description: - * Reset TRB to a ring. - * - * Returned Value: - * None - * - ****************************************************************************/ - -static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_ring_s *ring, - FAR struct xhci_trb_s *trb, - int len) -{ - uint32_t d2; - int i; - - for (i = 0; i < len; i++) - { - d2 = trb[i].d2; - - if (ring->ccs) - { - d2 |= XHCI_TRB_D2_C; - } - else - { - d2 &= ~XHCI_TRB_D2_C; - } - - /* Make sure the cycle bit has the correct value */ - - DEBUGASSERT((ring->ring[ring->i].d2 & XHCI_TRB_D2_C) != ring->ccs); - - /* Write TRB */ - - ring->ring[ring->i].d0 = htole64(trb[i].d0); - ring->ring[ring->i].d1 = htole32(trb[i].d1); - ring->ring[ring->i].d2 = htole32(d2); - - /* Next TD */ - - ring->i++; - - /* Handle end of the command ring */ - - if (ring->i >= ring->len - 1) - { - /* Make sure the cycle bit has the correct value */ - - DEBUGASSERT((ring->ring[0].d2 & XHCI_TRB_D2_C) == ring->ccs); - - if (ring->ccs) - { - d2 = XHCI_TRB_D2_C | XHCI_TRB_D2_TC | - XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK); - } - else - { - d2 = XHCI_TRB_D2_TC | - XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK); - } - - /* Other parameters are already correct for this TRB */ - - ring->ring[ring->i].d2 = htole32(d2); - - /* Update CCS */ - - xhci_ring_reset(ring, true); - } - } - - /* Flush ring */ - - up_flush_dcache((uintptr_t)ring->ring, - (uintptr_t)(ring->ring + ring->len)); -} - -/**************************************************************************** - * Name: xhci_bios_wait - * - * Description: - * Wait for BIOS to give up the controller lock - * - * Returned Value: - * Zero on success; a negated errno value on failure. - * - ****************************************************************************/ - -static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv) -{ - uint32_t cstart; - uint32_t ecp; - uint32_t eec; - uint8_t sem; - uint8_t timeout; - int ret = OK; - - /* Get Extended Capability Pointer */ - - cstart = XHCI_HCCPARAMS1_XECP(xhci_capa_getreg(priv, XHCI_HCCPARAMS1)); - - /* Find USBLEGSUP - if present, we have to acquire for BIOS semaphore */ - - eec = -1; - ecp = (cstart << 2); - - while (1) - { - if (ecp == 0 || XHCI_USBLEGSUP_NEXT(eec) == 0) - { - break; - } - - eec = xhci_capa_getreg(priv, ecp); - - if (XHCI_USBLEGSUP_ID(eec) == XHCI_ID_USBLEGSUP) - { - /* We have to wait for semaphore */ - - ret = -EAGAIN; - - /* Get BIOS semaphore */ - - sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM); - if (sem == 0) - { - ret = OK; - break; - } - - /* Get semaphore request */ - - xhci_capa_putreg_1b(priv, ecp + XHCI_USBLEGSUP_OS_SEM, 1); - - /* Wait for semaphore released from BIOS */ - - for (timeout = 0; timeout < 100; timeout++) - { - sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM); - if (sem == 0) - { - ret = OK; - break; - } - - up_mdelay(100); - } - } - - /* Next cap */ - - ecp += (XHCI_USBLEGSUP_NEXT(eec) << 2); - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_ctrl_start - * - * Description: - * Start controller. - * - * According to "4.2 Host Controller Initialization". - * - * Returned Value: - * Zero on success; a negated errno value on failure. - * - ****************************************************************************/ - -static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) -{ - FAR struct xhci_event_ring_s *evnt; - uint32_t regval; - int ret; - int i; - - pciinfo("Start controller\n"); - - /* Reset controller before writing any Operational or Runtime registers */ - - ret = xhci_ctrl_reset(priv); - if (ret < 0) - { - usbhost_trace1(XHCI_TRACE1_RESET_FAILED, 0); - return ret; - } - - /* TODO: clear interrupts and disable all device notifications */ - - /* Max Device Slots Enabled */ - - xhci_oper_putreg(priv, XHCI_CONFIG, priv->no_slots); - - /* Slot 0 in Device Context is reserved for Scratchpad Buffer Array */ - - priv->pg_ctx[0] = htole64(up_addrenv_va_to_pa(priv->pg_sb)); - - /* Device Context Base Address Array Pointer */ - - xhci_oper_putreg_8b(priv, XHCI_DCBAAP, up_addrenv_va_to_pa(priv->pg_ctx)); - - /* Event Ring Segment Table Size */ - - xhci_runt_putreg(priv, XHCI_ERSTSZ(0), priv->no_erst); - - /* Initialize command ring state and event ring state */ - - ret = xhci_ring_init(&priv->cmd, XHCI_CMD_MAX); - if (ret < 0) - { - pcierr("cmd ring init failed\n"); - return ret; - } - - ret = xhci_ring_init(&priv->evnt, XHCI_EVENT_MAX); - if (ret < 0) - { - pcierr("event ring init failed\n"); - return ret; - } - - /* Configure Event Ring */ - - evnt = (struct xhci_event_ring_s *)priv->pg_erst; - evnt->base = htole64(up_addrenv_va_to_pa(priv->evnt.ring)); - evnt->size = XHCI_EVENT_MAX; - evnt->res = 0; - - /* Flush all memory before write to ERDP so xhci sees correct data */ - - up_flush_dcache_all(); - - xhci_runt_putreg_8b(priv, XHCI_ERDP(0), - up_addrenv_va_to_pa(priv->evnt.ring)); - - /* Write ERSTBA with ERST(0).BaseAddress. - * - * This must be done after ERST[0] initialization and after write to - * ERSTSZ. When the ERSTBA register is written, the Event Ring State - * Machine is set to the Start state. - * - * For details look at "4.9.4 Event Ring Management" - */ - - xhci_runt_putreg_8b(priv, XHCI_ERSTBA(0), - up_addrenv_va_to_pa(priv->pg_erst)); - - /* Last item in the command ring points to the beginning of the ring */ - - priv->cmd.ring[XHCI_CMD_MAX - 1].d0 = htole64( - up_addrenv_va_to_pa(priv->cmd.ring)); - - /* Configure the Command Ring */ - - xhci_oper_putreg_8b(priv, XHCI_CRCR, - up_addrenv_va_to_pa(priv->cmd.ring) | XHCI_CRCR_RCS); - - /* Enable interrupts */ - - regval = xhci_runt_getreg(priv, XHCI_IMAN(0)); - regval |= XHCI_IMAN_IE; - xhci_runt_putreg(priv, XHCI_IMAN(0), regval); - - /* Flush all memory once again */ - - up_flush_dcache_all(); - - /* Turn the host controller ON, enable interrupts and system errors */ - - xhci_oper_putreg(priv, XHCI_USBCMD, - XHCI_USBCMD_RS | - XHCI_USBCMD_INTE | - XHCI_USBCMD_HSEE); - - /* Wait for controller started */ - - ret = -EAGAIN; - for (i = 0; i < 10; i++) - { - up_mdelay(100); - - if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH)) - { - ret = OK; - break; - } - } - - /* Check for timeout */ - - if (ret != OK) - { - pcierr("Can't start controller!"); - return ret; - } - - /* Poll all pending events */ - - xhci_events_poll(priv); - - return OK; -} - -/**************************************************************************** - * Name: xhci_ctrl_halt - * - * Description: - * Halt controller. - * - * Returned Value: - * Zero on success; a negated errno value on failure. - * - ****************************************************************************/ - -static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv) -{ - int ret = -EAGAIN; - int i; - - /* Halt controller */ - - xhci_oper_putreg(priv, XHCI_USBCMD, 0); - - /* Wait for controller halted */ - - for (i = 0; i < 10; i++) - { - up_mdelay(100); - - if (xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH) - { - ret = OK; - break; - } - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_ctrl_reset - * - * Description: - * Reset controller. - * - * Returned Value: - * Zero on success; a negated errno value on failure. - * - ****************************************************************************/ - -static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv) -{ - int ret = -EAGAIN; - int i; - - /* Halt controller */ - - xhci_oper_putreg(priv, XHCI_USBCMD, XHCI_USBCMD_HCRST); - - /* Wait for controller halted */ - - for (i = 0; i < 10; i++) - { - up_mdelay(100); - - if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_CNR)) - { - ret = OK; - break; - } - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_probe_ports - * - * Description: - * Initial ports probe. - * - ****************************************************************************/ - -static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv) -{ - uint32_t portsc; - int i; - - for (i = 0; i < priv->no_ports; i++) - { - portsc = xhci_oper_getreg(priv, XHCI_PORTSC(i)); - priv->rhport[i].connected = ((portsc & XHCI_PORTSC_CCS) != 0); - - /* Clear status change */ - - xhci_oper_putreg(priv, XHCI_PORTSC(i), portsc); - } -} - -/**************************************************************************** - * Name: xhci_port_enable - * - * Description: - * Set port to the Enable state. - * - ****************************************************************************/ - -static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, - FAR struct usbhost_hubport_s *hport) -{ - uint32_t retries; - uint32_t regval; - uint8_t speed; - int rhpndx; - - DEBUGASSERT(hport != NULL); - rhpndx = hport->port; - - regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); - - /* A USB3 protocol port attempts to automatically advance to the - * Enabled state for port as part of the attach process. - */ - - if (!(regval & XHCI_PORTSC_PED)) - { - /* Reset the port */ - - regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); - regval |= XHCI_PORTSC_PR; - xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), regval); - - /* REVISIT: we get Port Status Change Event here */ - - /* Wait for Enabled state for port */ - - retries = 10; - while (!(xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)) - & XHCI_PORTSC_PED) && retries > 0) - { - retries--; - up_mdelay(100); - } - - if (retries == 0) - { - return -ETIMEDOUT; - } - } - - /* Get port status */ - - regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); - - /* Get port speed */ - - speed = XHCI_PORTSC_PS(regval); - switch (speed) - { - case XHCI_PORTSC_PS_FULL: - { - hport->speed = USB_SPEED_FULL; - break; - } - - case XHCI_PORTSC_PS_LOW: - { - hport->speed = USB_SPEED_LOW; - break; - } - - case XHCI_PORTSC_PS_HIGH: - { - hport->speed = USB_SPEED_HIGH; - break; - } - - case XHCI_PORTSC_PS_SUPPER11: - { - hport->speed = USB_SPEED_SUPER; - break; - } - - case XHCI_PORTSC_PS_SUPPER21: - case XHCI_PORTSC_PS_SUPPER12: - case XHCI_PORTSC_PS_SUPPER22: - { - hport->speed = USB_SPEED_SUPER_PLUS; - break; - } - - default: - { - pcierr("speed = 0x%x\n", speed); - hport->speed = USB_SPEED_UNKNOWN; - return -EINVAL; - } - } - - return OK; -} - -/**************************************************************************** - * Name: xhci_dcbaa_set - * - * Description: - * Set entry in the Device Context Base Address Array, which should point - * to the Output Device Context data structure. - * - ****************************************************************************/ - -static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, - uintptr_t ctx) -{ - /* NOTE: context must be physical address! */ - - priv->pg_ctx[index] = htole64(ctx); - - /* Flush context */ - - up_flush_dcache((uintptr_t)priv->pg_ctx, - (uintptr_t)(priv->pg_ctx + priv->no_slots + 1)); -} - -/**************************************************************************** - * Name: xhci_ep_configure - * - * Description: - * Configure endpoint context. - * - * Reference: - * - 4.8.2 Endpoint Context Initialization - * - 6.2.3 Endpoint Context - * - ****************************************************************************/ - -static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_ep_ctx_s *ctx, - uint8_t type, uint16_t maxpkt, - uint8_t maxburst, uint64_t tr_dp, - uint8_t mult, uint8_t interval) -{ - uint32_t ctx0 = 0; - uint32_t ctx1 = 0; - uint64_t ctx2 = 0; - - /* Set type */ - - ctx1 |= XHCI_EP_CTX1_EPTYPE(type); - - /* Set max packet size */ - - ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpkt); - - /* Set max burst size */ - - ctx1 |= XHCI_EP_CTX1_MAXBRST(maxburst); - - /* Set TR Dequeue Pointer. - * NOTE: must be physical address aligned to 16-byte. - */ - - DEBUGASSERT(tr_dp != 0 && tr_dp % 16 == 0); - ctx2 = tr_dp; - - /* Set DCS. - * Should be set to 1 only if no stream (USB3.0 specific). - */ - - ctx2 |= XHCI_EP_CTX2_DCS; - - /* Set interval */ - - ctx0 |= XHCI_EP_CTX0_INTERVAL(interval); - - /* Set max primary streams. - * Set to zero for now (USB3.0 specific) - */ - - ctx0 |= XHCI_EP_CTX0_MAXPSTR(0); - - /* Set mult */ - - ctx0 |= XHCI_EP_CTX0_MULT(mult); - - /* Set error count to 3 if this is not ISOCH endpoint */ - - if (type != XHCI_EPTYPE_ISO_OUT && type != XHCI_EPTYPE_ISO_IN) - { - ctx1 |= XHCI_EP_CTX1_CERR(3); - } - - /* Write context */ - - ctx->ctx0 = htole32(ctx0); - ctx->ctx1 = htole32(ctx1); - ctx->ctx2 = htole64(ctx2); - - /* Flush context */ - - up_flush_dcache((uintptr_t)ctx, - (uintptr_t)ctx + sizeof(struct xhci_ep_ctx_s)); -} - -/**************************************************************************** - * Name: xhci_address_set - * - * Description: - * Set address request. - * - * If setaddr is true, then xHC issue a SET_ADDRESS request. - * - ****************************************************************************/ - -static int xhci_address_set(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport, bool setaddr) -{ - FAR struct xhci_dev_s *dev; - uint64_t ctx; - - dev = rhport->dev; - ctx = up_addrenv_va_to_pa(dev->input); - - return xhci_cmd_setaddr(priv, rhport->slot, ctx, !setaddr); -} - -/**************************************************************************** - * Name: xhci_slot_init - * - * Description: - * Initialize Device Slot data. - * - * Assumption: - * 1. All slot resources already allocated. - * 2. Port is in Enabled state. - * - * Reference: - * - 4.3.3. Device Slot Initialization - * - ****************************************************************************/ - -static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_dev_s *dev) -{ - uint32_t regval; - uint16_t maxpkt; - uintptr_t drdp; - - /* Step 1. The Input Context data structure already allocated. - * Initialize all fields to 0. - */ - - memset(dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); - - /* Step 2. Initialize the Input Control Context by setting the A0 and - * A1 flags to 1 (Slot flag and EP0 flag). - */ - - regval = XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | - XHCI_IN_CTX1_A(XHCI_EP0_FLAG); - xhci_context_ctrl(priv, dev, 0, regval); - - /* Step 3. Initialize the Input Slot Context */ - - regval = XHCI_ST_CTX0_CTXENT_SET(1); - -#ifdef CONFIG_USBHOST_HUB - /* TODO: - * 1. Activate the transaction translator if required - * 2. Configure hub bit in slot context if hub - * 3. configure route string - */ - -# warning missing logic -#endif - - dev->input->slot.ctx[0] = htole32(regval); - - /* Configure Root Hub Port Number (starts from 1) */ - - regval = XHCI_ST_CTX1_RHPN_SET(RHPNDX(dev->rhport) + 1); - - /* TODO: configure number of ports */ - - regval |= XHCI_ST_CTX1_PORTS_SET(0); - dev->input->slot.ctx[1] = htole32(regval); - - /* Step 4. the Transfer Ring for the Default Control Endpoint is already - * allocated. - */ - - drdp = up_addrenv_va_to_pa(dev->rhport->ep0.td.ring); - - /* Step 5. Initialize the Input default control Endpoint 0 Context */ - - DEBUGASSERT(dev->rhport != NULL); - if (dev->rhport->hport.hport.speed == USB_SPEED_HIGH) - { - /* For high-speed, we must use 64 bytes */ - - maxpkt = 64; - } - else - { - /* Eight will work for both low- and full-speed */ - - maxpkt = 8; - } - - DEBUGASSERT(drdp != 0); - xhci_ep_configure(priv, - &dev->input->ep[0], - XHCI_EPTYPE_CTRL, maxpkt, - 0, drdp, - 0, 0); - - /* Step 6. The output Device Context data structure already allocated. - * Initialize all fields to 0. - */ - - memset(dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); - - /* Flush Device input context */ - - up_flush_dcache((uintptr_t)dev->input, - (uintptr_t)dev->input + - sizeof(struct xhci_input_dev_ctx_s)); - - /* Step 7. Load the appropriate (Device Slot ID) entry in the Device - * Context Base Address Array with a pointer to the Output Device - * Context data structure - */ - - xhci_dcbaa_set(priv, dev->slot, up_addrenv_va_to_pa(dev->ctx)); - - return OK; -} - -/**************************************************************************** - * Name: xhci_device_init - * - * Description: - * Initialize Device. - * - * Assumption: - * 1. All device resources already allocated. - * 2. Port is in Enabled state. - * - * Reference: - * - 4.3: USB Device Initialization - * - ****************************************************************************/ - -static int xhci_device_init(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport) -{ - FAR struct xhci_dev_s *dev; - uint8_t slot; - int ret; - - /* We enter this function after steps 1-3 from "USB Device Initialization" - * are done. - */ - - /* Step 4: Get Device Slot. - * - * Reference: - * - 4.3.2: Device Slot Assignment - */ - - ret = xhci_cmd_sloten(priv, &slot); - if (ret < 0 || slot > priv->no_slots) - { - /* Something goes wrong ! */ - - usbhost_vtrace1(XHCI_TRACE1_SLOTEN_FAILED, ret); - return ret; - } - - /* Slot ID is an index to the identify Device data */ - - rhport->dev = &priv->devs[slot - 1]; - rhport->slot = slot; - dev = rhport->dev; - - /* Slot has been allocated to software and is now in Enabled state */ - - dev->state = XHCI_SLOT_ENABLED; - - /* Step 5: Initialize the data structures associated with the slot. - * All data structured are already allocated. - */ - - ret = xhci_ring_init(&rhport->ep0.td, XHCI_TD_MAX); - if (ret < 0) - { - pcierr("ep0 ring init failed\n"); - return ret; - } - - rhport->ep0.slot = slot; - dev->rhport = rhport; - dev->slot = slot; - dev->epinfo[0] = &rhport->ep0; - - ret = xhci_slot_init(priv, dev); - if (ret < 0) - { - return ret; - } - - /* Step 6: Assign and address to the device and enable its Default - * Control Endpoint. - * - * NOTE: we don't send SET_ADDRESS request here. - * This is done in xhci_ctrlin() and controlled by NuttX USB Host - * stack. - */ - - ret = xhci_address_set(priv, rhport, false); - if (ret < 0) - { - pcierr("failed to set address %d\n", ret); - return ret; - } - - /* Steps 7-12 don't belong here! */ - - return OK; -} - -/**************************************************************************** - * Name: xhci_device_deinit - * - * Description: - * Free Device. - * - * Reference: - * - 4.3: USB Device Initialization - * - ****************************************************************************/ - -static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport) -{ - uint8_t slot = rhport->slot; - int ret; - - /* Disable Slot */ - - ret = xhci_cmd_slotdis(priv, slot); - if (ret < 0) - { - pcierr("xhci_cmd_slotdis failed %d\n", ret); - } - - /* Clear DCBAA entry for this slot */ - - xhci_dcbaa_set(priv, slot, 0); - - /* Clean up device data, but don't touch allocated memory! */ - - rhport->dev->state = XHCI_SLOT_DISABLED; - - memset(rhport->dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); - memset(rhport->dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); - - /* Remove reference to a device slot */ - - rhport->dev = NULL; - - return OK; -} - -/**************************************************************************** - * Name: xhci_epno_get - * - * Description: - * Get EP index for a given endpoint. - * - * Returns Device Context Index (DCI). - * - ****************************************************************************/ - -static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo) -{ - DEBUGASSERT(epinfo); - - if (epinfo->epno == 0) - { - return 1; - } - - if (epinfo->dirin) - { - return epinfo->epno * 2 + 1; - } - else - { - return epinfo->epno * 2; - } -} - -/**************************************************************************** - * Name: xhci_context_ctrl - * - * Description: - * Configure Input Control Context, which defines which Device Context - * data structures are affected by a command. - * - * Assumption: - * Input Context must be flushed by caller. - * - ****************************************************************************/ - -static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_dev_s *dev, - uint32_t drop, uint32_t add) -{ - int i; - - dev->input->input.ctx[0] = htole32(drop); - dev->input->input.ctx[1] = htole32(add); - - /* Update Context Entries in Slot */ - - for (i = 31; i != 1; i--) - { - if (add & (1 << i)) - { - break; - } - } - - dev->input->slot.ctx[0] &= ~XHCI_ST_CTX0_CTXENT_MASK; - dev->input->slot.ctx[0] |= XHCI_ST_CTX0_CTXENT_SET(i); -} - -/**************************************************************************** - * Name: xhci_command - * - * Description: - * Issue a xHCI command. - * - * NOTE: - * trb data in host specific byte order. This function converts it - * to a correct order - * - ****************************************************************************/ - -static int xhci_command(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_trb_s *trb, uint16_t timeout_ms) -{ - int ret; - - /* Lock bus */ - - ret = nxmutex_lock(&priv->lock); - if (ret < 0) - { - return ret; - } - - /* Add command to ring */ - - xhci_add_trb(priv, &priv->cmd, trb, 1); - - /* Ringing the Host Controller Doorbell */ - - xhci_door_putreg(priv, XHCI_DOORBEL(0), 0); - - /* Wait for Command Completion Event */ - - ret = nxsem_tickwait_uninterruptible(&priv->cmdsem, - MSEC2TICK(timeout_ms)); - if (ret < 0) - { - /* Check for missed interrupts */ - - xhci_events_poll(priv); - } - - /* Return command results */ - - trb->d0 = priv->cmdres.d0; - trb->d1 = priv->cmdres.d1; - trb->d2 = priv->cmdres.d2; - - if (XHCI_TRB_D1_CC_GET(trb->d1) != XHCI_TRB_CC_SUCCESS) - { - pcierr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1)); - ret = -EIO; - } - - /* Clean response */ - - priv->cmdres.d0 = 0; - priv->cmdres.d1 = 0; - priv->cmdres.d2 = 0; - - /* Unlock bus */ - - nxmutex_unlock(&priv->lock); - - return ret; -} - -/**************************************************************************** - * Name: xhci_cmd_sloten - * - * Description: - * Enable Slot Command. - * - ****************************************************************************/ - -static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, - FAR uint8_t *slot) -{ - struct xhci_trb_s trb; - int ret; - - /* Host specific byte order. Conversion done by xhci_command() */ - - trb.d0 = 0; - trb.d1 = 0; - trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EN_SLOT); - - ret = xhci_command(priv, &trb, 1000); - if (ret < 0) - { - *slot = 0; - return ret; - } - - /* Return available slot */ - - *slot = XHCI_TRB_D2_SLOTID_GET(le32toh(trb.d2)); - - return OK; -} - -/**************************************************************************** - * Name: xhci_cmd_slotdis - * - * Description: - * Disable Slot Command. - * - ****************************************************************************/ - -static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot) -{ - struct xhci_trb_s trb; - - /* Host specific byte order. Conversion done by xhci_command() */ - - trb.d0 = 0; - trb.d1 = 0; - trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DIS_SLOT) | - XHCI_TRB_D2_SLOTID_SET(slot); - - return xhci_command(priv, &trb, 100); -} - -/**************************************************************************** - * Name: xhci_cmd_setaddr - * - * Description: - * Address Device Command. - * - ****************************************************************************/ - -static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint64_t ctx, bool bsr) -{ - struct xhci_trb_s trb; - - /* Host specific byte order. Conversion done by xhci_command() */ - - trb.d0 = htole64(ctx); - trb.d1 = 0; - trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ADDR_DEV) | - XHCI_TRB_D2_SLOTID_SET(slot); - - if (bsr) - { - trb.d2 |= XHCI_TRB_D2_BSR; - } - - return xhci_command(priv, &trb, 100); -} - -/**************************************************************************** - * Name: xhci_cmd_cfgep - * - * Description: - * Configure EP Command. - * - ****************************************************************************/ - -static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint64_t ctx, bool deconfig) -{ - struct xhci_trb_s trb; - - /* Host specific byte order. Conversion done by xhci_command() */ - - trb.d0 = htole64(ctx); - trb.d1 = 0; - trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_CFG_EP) | - XHCI_TRB_D2_SLOTID_SET(slot); - - if (deconfig) - { - trb.d2 |= XHCI_TRB_D2_DC; - } - - return xhci_command(priv, &trb, 100); -} - -/**************************************************************************** - * Name: xhci_cmd_stopep - * - * Description: - * Stop endpoint - * - ****************************************************************************/ - -static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint8_t ep, bool suspend) -{ - struct xhci_trb_s trb; - - /* Host specific byte order. Conversion done by xhci_command() */ - - trb.d0 = 0; - trb.d1 = 0; - trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) | - XHCI_TRB_D2_SLOTID_SET(slot) | XHCI_TRB_D2_EP_SET(ep); - - if (suspend) - { - trb.d2 |= XHCI_TRB_D2_SP; - } - - return xhci_command(priv, &trb, 100); -} - -/**************************************************************************** - * Name: xhci_cmd_evalctx - * - * Description: - * Evaluate Context Command - * - ****************************************************************************/ - -static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, - uint64_t ctx) -{ - struct xhci_trb_s trb; - - /* Host specific byte order. Conversion done by xhci_command() */ - - trb.d0 = htole64(ctx); - trb.d1 = 0; - trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EVAL_CTX) | - XHCI_TRB_D2_SLOTID_SET(slot); - - return xhci_command(priv, &trb, 100); -} - -/**************************************************************************** - * Name: xhci_ep_doorbell - * - * Description: - * Ring doorbell associated with a given endpoint. - * - ****************************************************************************/ - -static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_epinfo_s *epinfo) -{ - uint8_t target = xhci_epno_get(epinfo); - uint32_t regval = 0; - - /* Doorbel tareget is EP number */ - - regval |= XHCI_DOORBEL_TARGET(target); - - /* Streams not supported yet (USB3.0 specific) */ - - regval |= XHCI_DOORBEL_TASK(0); - - /* Ring doorbell */ - - xhci_door_putreg(priv, XHCI_DOORBEL(epinfo->slot), regval); -} - -/**************************************************************************** - * Name: xhci_ioc_setup - * - * Description: - * Set the request for the IOC event well BEFORE enabling the transfer (as - * soon as we are absolutely committed to the transfer). We do - * this to minimize race conditions. This logic would have to be expanded - * if we want to have more than one packet in flight at a time! - * - * Assumption: - * The caller holds the XHCI lock - * - ****************************************************************************/ - -static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - size_t buflen) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); - irqstate_t flags; - int ret = -ENODEV; - - DEBUGASSERT(rhport && epinfo && !epinfo->iocwait); -#ifdef CONFIG_USBHOST_ASYNCH - DEBUGASSERT(epinfo->callback == NULL); -#endif - - /* Is the device still connected? */ - - flags = spin_lock_irqsave(&priv->spinlock); - if (rhport->connected) - { - /* Then set iocwait to indicate that we expect to be informed when - * either (1) the device is disconnected, or (2) the transfer - * completed. - */ - - epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */ - epinfo->status = 0; /* No status yet */ - epinfo->xfrd = 0; /* Nothing transferred yet */ - epinfo->buflen = buflen; /* Buffer length */ - epinfo->result = -EBUSY; /* Transfer in progress */ -#ifdef CONFIG_USBHOST_ASYNCH - epinfo->callback = NULL; /* No asynchronous callback */ - epinfo->arg = NULL; -#endif - ret = OK; /* We are good to go */ - } - - spin_unlock_irqrestore(&priv->spinlock, flags); - return ret; -} - -/**************************************************************************** - * Name: xhci_ioc_wait - * - * Description: - * Wait for the IOC event. - * - * Assumption: - * The caller does *NOT* hold the xHCI lock. That would cause a deadlock - * when the bottom-half, worker thread needs to take the semaphore. - * - ****************************************************************************/ - -static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo) -{ - int ret = OK; - - /* Wait for the IOC event. Loop to handle any false alarm semaphore - * counts. Return an error if the task is canceled. - */ - - while (epinfo->iocwait) - { - ret = nxsem_wait_uninterruptible(&epinfo->iocsem); - if (ret < 0) - { - break; - } - } - - return ret < 0 ? ret : epinfo->result; -} - -/**************************************************************************** - * Name: xhci_control_setup - * - * Description: - * Process a IN or OUT request control ep. - * This function will enqueue the request and wait for it to - * complete. Bulk data transfers differ in that req == NULL and there are - * not SETUP or STATUS phases. - * - * This is a blocking function; it will not return until the control - * transfer has completed. - * - * Assumption: - * The caller holds the xHCI lock. - * - * Returned Value: - * Zero (OK) is returned on success; a negated errno value is return on - * any failure. - * - ****************************************************************************/ - -static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - FAR const struct usb_ctrlreq_s *req, - FAR uint8_t *buffer, size_t buflen) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); - struct xhci_trb_s trb[3]; - uint8_t trt; - int i = 0; - - /* Prepare Setup Stage TRB */ - - trb[i].d0 = *((FAR uint64_t *)req); - trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(8); - trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | - XHCI_TRB_D2_IDT; - - /* Reference: - * - Table 4-7: USB SETUP Data to Data Stage TRB and Status Stage - * TRB mapping - */ - - if (req->type & USB_REQ_DIR_IN) - { - trt = XHCI_TRB_D2_TRT_INDATA; - } - else if (req->type & USB_REQ_DIR_OUT) - { - trt = XHCI_TRB_D2_TRT_OUTDATA; - } - else - { - trt = XHCI_TRB_D2_TRT_NODATA; - } - - trb[i].d2 |= XHCI_TRB_D2_TRT_SET(trt); - - /* Next TRB */ - - i++; - - /* Prepare Data Stage TRB */ - - if (buffer) - { - trb[i].d0 = up_addrenv_va_to_pa(buffer); - trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(buflen); - trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); - - if (req->type & USB_REQ_DIR_IN) - { - trb[i].d2 |= XHCI_TRB_D2_DIR; - } - - /* Next TRB */ - - i++; - } - - /* Prepare Status Stage TRB */ - - trb[i].d0 = 0; - trb[i].d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(0); - trb[i].d2 = XHCI_TRB_D2_IOC | - XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STAT_STAGE); - - if (!(req->type & USB_REQ_DIR_IN)) - { - trb[i].d2 |= XHCI_TRB_D2_DIR; - } - - /* Next TRB */ - - i++; - - /* Add TRBs to ring */ - - xhci_add_trb(priv, &epinfo->td, trb, i); - - /* Trigger transfer */ - - xhci_ep_doorbell(priv, epinfo); - - return OK; -} - -/**************************************************************************** - * Name: xhci_normal_setup - * - * Description: - * Process a IN or OUT request on bulk or interrupt endpoint. - * This function will enqueue the request and wait for it to complete. - * - * This is a blocking function; it will not return until the control - * transfer has completed. - * - * Assumption: - * The caller holds the xHCI lock. - * - * Returned Value: - * Zero (OK) is returned on success; a negated errno value is returned on - * any failure. - * - ****************************************************************************/ - -static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - FAR uint8_t *buffer, size_t buflen) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); - struct xhci_trb_s trb; - - /* Prepare TRB */ - - trb.d0 = up_addrenv_va_to_pa(buffer); - trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); - trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_NORMAL); - - /* Add TRBs to ring */ - - xhci_add_trb(priv, &epinfo->td, &trb, 1); - - /* Trigger transfer */ - - xhci_ep_doorbell(priv, epinfo); - - return OK; -} - -#ifndef CONFIG_USBHOST_ISOC_DISABLE -/**************************************************************************** - * Name: xhci_isoc_setup - * - * Description: - * Process a request on isoch endpoint. - * - * Assumption: - * The caller holds the xHCI lock. - * - * Returned Value: - * Zero (OK) is returned on success; a negated errno value is returned on - * any failure. - * - ****************************************************************************/ - -static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - FAR uint8_t *buffer, size_t buflen) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); - struct xhci_trb_s trb; - - /* Prepare TRB */ - - trb.d0 = up_addrenv_va_to_pa(buffer); - trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); - trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ISOCH); - - /* Start Isoch ASAP */ - - trb.d2 |= XHCI_TRB_D2_SIA; - - /* Add TRBs to ring */ - - xhci_add_trb(priv, &epinfo->td, &trb, 1); - - /* Trigger transfer */ - - xhci_ep_doorbell(priv, epinfo); - - return OK; -} -#endif - -/**************************************************************************** - * Name: xhci_transfer_wait - * - * Description: - * Wait for an IN or OUT transfer to complete. - * - * Assumption: - * The caller holds the xHCI lock. The caller must be aware that the xHCI - * lock will released while waiting for the transfer to complete, but will - * be re-acquired when before returning. The state of xHCI resources could - * be very different upon return. - * - * Returned Value: - * On success, this function returns the number of bytes actually - * transferred. For control transfers, this size includes the size of the - * control request plus the size of the data (which could be short); for - * bulk transfers, this will be the number of data bytes transfers (which - * could be short). - * - ****************************************************************************/ - -static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_epinfo_s *epinfo) -{ - int ret; - - /* Wait for the IOC completion event */ - - ret = xhci_ioc_wait(epinfo); - - /* Did xhci_ioc_wait() or nxmutex_lock report an error? */ - - if (ret < 0) - { - usbhost_trace1(XHCI_TRACE1_TRANSFER_FAILED, -ret); - epinfo->iocwait = false; - return (ssize_t)ret; - } - - /* Transfer completed successfully. Return the number of bytes - * transferred. - */ - - return epinfo->xfrd; -} - -#ifdef CONFIG_USBHOST_ASYNCH -/**************************************************************************** - * Name: xhci_ioc_async_setup - * - * Description: - * Setup to receive an asynchronous notification when a transfer completes. - * - * Input Parameters: - * epinfo - The IN or OUT endpoint descriptor for the device endpoint on - * which the transfer will be performed. - * callback - The function to be called when the transfer completes - * arg - An arbitrary argument that will be provided with the callback. - * - * Returned Value: - * None - * - * Assumptions: - * - Called from the interrupt level - * - ****************************************************************************/ - -static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, - FAR struct xhci_epinfo_s *epinfo, - usbhost_asynch_t callback, - FAR void *arg) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); - irqstate_t flags; - int ret = -ENODEV; - - DEBUGASSERT(rhport && epinfo && !epinfo->iocwait && - epinfo->callback == NULL); - - /* Is the device still connected? */ - - flags = spin_lock_irqsave(&priv->spinlock); - if (rhport->connected) - { - /* Then save callback information to be used when either (1) the - * device is disconnected, or (2) the transfer completes. - */ - - epinfo->iocwait = false; /* No synchronous wakeup */ - epinfo->status = 0; /* No status yet */ - epinfo->xfrd = 0; /* Nothing transferred yet */ - epinfo->result = -EBUSY; /* Transfer in progress */ - epinfo->callback = callback; /* Asynchronous callback */ - epinfo->arg = arg; /* Argument that accompanies the callback */ - ret = OK; /* We are good to go */ - } - - spin_unlock_irqrestore(&priv->spinlock, flags); - return ret; -} - -/**************************************************************************** - * Name: xhci_asynch_completion - * - * Description: - * This function is called at the interrupt level when an asynchronous - * transfer completes. It performs the pending callback. - * - * Input Parameters: - * epinfo - The IN or OUT endpoint descriptor for the device endpoint on - * which the transfer was performed. - * - * Returned Value: - * None - * - * Assumptions: - * - Called from the interrupt level - * - ****************************************************************************/ - -static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo) -{ - usbhost_asynch_t callback; - ssize_t nbytes; - FAR void *arg; - int result; - - DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false && - epinfo->callback != NULL); - - /* Extract and reset the callback info */ - - callback = epinfo->callback; - arg = epinfo->arg; - result = epinfo->result; - nbytes = epinfo->xfrd; - - epinfo->callback = NULL; - epinfo->arg = NULL; - epinfo->result = OK; - epinfo->iocwait = false; - - /* Then perform the callback. Provide the number of bytes successfully - * transferred or the negated errno value in the event of a failure. - */ - - if (result < 0) - { - nbytes = (ssize_t)result; - } - - callback(arg, nbytes); -} -#endif - -/**************************************************************************** - * Name: xhci_portsc_work - * - * Description: - * Handle Port Change work. - * - * Assumptions: - * - Never called from an interrupt handler. - * - Never called directly from xhci_events_poll() otherwise it gets stuck - * on CLASS_DISCONNECTED() - * - ****************************************************************************/ - -static void xhci_portsc_work(FAR void *arg) -{ - FAR struct usbhost_xhci_s *priv = arg; - FAR struct usbhost_hubport_s *hport; - FAR struct xhci_rhport_s *rhport; - uint32_t portsc; - int rhpndx; - - /* REVISIT: should this logic be protected? We can't use spinlock - * here because we get stack in CLASS_DISCONNECTED(). - */ - - /* Handle root hub status change on each root port */ - - for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) - { - rhport = &priv->rhport[rhpndx]; - portsc = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); - - usbhost_vtrace2(XHCI_VTRACE2_PORTSC, rhpndx + 1, portsc); - - /* Handle port connection status change (CSC) events */ - - if ((portsc & XHCI_PORTSC_CSC) != 0) - { - usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CSC, portsc); - - /* Check current connect status */ - - if ((portsc & XHCI_PORTSC_CCS) != 0) - { - /* Connected ... Did we just become connected? */ - - if (!rhport->connected) - { - /* Yes.. connected. */ - - rhport->connected = true; - - usbhost_vtrace2(XHCI_VTRACE2_PORTSC_CONNECTED, - rhpndx + 1, priv->pscwait); - - /* Notify any waiters */ - - if (priv->pscwait) - { - nxsem_post(&priv->pscsem); - priv->pscwait = false; - } - } - else - { - usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CONNALREADY, portsc); - } - } - else - { - /* Disconnected... Did we just become disconnected? */ - - if (rhport->connected) - { - /* Yes.. disconnect the device */ - - usbhost_vtrace2(XHCI_VTRACE2_PORTSC_DISCONND, - rhpndx + 1, priv->pscwait); - - rhport->connected = false; - - /* Are we bound to a class instance? */ - - hport = &rhport->hport.hport; - if (hport->devclass) - { - /* Yes.. Disconnect the class. */ - - CLASS_DISCONNECTED(hport->devclass); - hport->devclass = NULL; - } - - /* Notify any waiters for the Root Hub Status change - * event. - */ - - if (priv->pscwait) - { - nxsem_post(&priv->pscsem); - priv->pscwait = false; - } - } - else - { - usbhost_vtrace1(XHCI_VTRACE1_PORTSC_DISCALREADY, portsc); - } - } - } - - /* Clear pending bit but don't touch PED ! */ - - portsc &= ~XHCI_PORTSC_PED; - xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), portsc); - } -} - -/**************************************************************************** - * Name: xhci_transfer_complete - * - * Description: - * Handle transfer complete event - * - ****************************************************************************/ - -static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_trb_s *evt) -{ - FAR struct xhci_epinfo_s *epinfo; - uint32_t tl = XHCI_TRB_D1_TXLEN_GET(evt->d1); - uint8_t slot = XHCI_TRB_D2_SLOTID_GET(evt->d2); - uint8_t ep = XHCI_TRB_D2_EP_GET(evt->d2); - uint8_t ret = XHCI_TRB_D1_CC_GET(evt->d1); - irqstate_t flags; - - /* Get EP associated with this transfer */ - - epinfo = priv->devs[slot - 1].epinfo[ep - 1]; - DEBUGASSERT(epinfo != NULL); - - flags = spin_lock_irqsave(&priv->spinlock); - - /* Get transferred length */ - - if (epinfo->buflen > 0) - { - epinfo->xfrd = epinfo->buflen - tl; - } - - /* Check transfer status */ - - if (ret == XHCI_TRB_CC_SUCCESS) - { - /* Report success */ - - epinfo->status = 0; - epinfo->result = OK; - } - - else if (ret == XHCI_TRB_CC_STALL) - { - /* Report STALL condition */ - - epinfo->status = 0; - epinfo->result = -EPERM; - } - - else if (ret == XHCI_TRB_CC_SHORT_PKT) - { - /* Report success */ - - epinfo->status = 0; - epinfo->result = OK; - } - - else - { - /* Report error */ - - pcierr("transfer CC = %d\n", ret); - epinfo->status = ret; - epinfo->result = -EIO; - } - - /* Is there a thread waiting for this transfer to complete? */ - - if (epinfo->iocwait) - { - /* Yes... wake it up */ - - epinfo->iocwait = 0; - nxsem_post(&epinfo->iocsem); - } - -#ifdef CONFIG_USBHOST_ASYNCH - /* No.. Is there a pending asynchronous transfer? */ - - else if (epinfo->callback != NULL) - { - /* Yes.. perform the callback */ - - xhci_asynch_completion(epinfo); - } -#endif - - spin_unlock_irqrestore(&priv->spinlock, flags); -} - -/**************************************************************************** - * Name: xhci_envet_complete - * - * Description: - * Handle event complete event - * - ****************************************************************************/ - -static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_trb_s *evt) -{ - irqstate_t flags; - - /* REVISIT: we assume for now that only one command is pending */ - - /* Store command result */ - - flags = spin_lock_irqsave(&priv->spinlock); - priv->cmdres.d0 = evt->d0; - priv->cmdres.d1 = evt->d1; - priv->cmdres.d2 = evt->d2; - spin_unlock_irqrestore(&priv->spinlock, flags); - - /* Signal that command is done */ - - nxsem_post(&priv->cmdsem); -} - -/**************************************************************************** - * Name: xhci_events_poll - * - * Description: - * Poll all pending events - * - ****************************************************************************/ - -static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) -{ - FAR struct xhci_trb_s *evt; - uintptr_t addr; - uint8_t type; - uint32_t d2; - - /* Invalidate event ring */ - - up_invalidate_dcache((uintptr_t)priv->evnt.ring, - (uintptr_t)(priv->evnt.ring + XHCI_EVENT_MAX)); - - /* Handle all pending events */ - - while (1) - { - evt = &priv->evnt.ring[priv->evnt.i]; - - /* Update address */ - - addr = (uintptr_t)evt; - - d2 = le32toh(evt->d2); - if ((d2 & XHCI_TRB_D2_C) != priv->evnt.ccs) - { - break; - } - - type = XHCI_TRB_D2_TYPE_GET(d2); - - switch (type) - { - /* Transfer Event */ - - case XHCI_TRB_EVT_TRANSFER: - { - xhci_transfer_complete(priv, evt); - - break; - } - - /* Command Completion Event */ - - case XHCI_TRB_EVT_CMD_COMP: - { - xhci_event_complete(priv, evt); - - break; - } - - /* Port Status Change Event */ - - case XHCI_TRB_EVT_PSTAT_CHANGE: - { - /* We have to handle Port Status Change in a separate work - * queue, otherwise we'll get stuck when handling disconnect - * request. - */ - - if (work_available(&priv->pscwork)) - { - work_queue(LPWORK, &priv->pscwork, xhci_portsc_work, - (FAR void *)priv, 0); - } - - break; - } - - default: - { - pciinfo("ignored event %d\n", type); - break; - } - } - - /* Next event */ - - priv->evnt.i++; - - /* Handle ring wrap */ - - if (priv->evnt.i >= XHCI_EVENT_MAX) - { - xhci_ring_reset(&priv->evnt, true); - } - } - - /* Clear ERDP busy bit and update dequeue pointer */ - - addr = up_addrenv_va_to_pa((FAR void *)addr); - addr |= XHCI_ERDP_EHB; - xhci_runt_putreg_8b(priv, XHCI_ERDP(0), addr); - - return OK; -} - -/**************************************************************************** - * Name: xhci_interupt_work - * - * Description: - * Handle xHCI interrupts - * - ****************************************************************************/ - -static void xhci_interrupt_work(FAR void *arg) -{ - FAR struct usbhost_xhci_s *priv = arg; - uint32_t iman; - - xhci_events_poll(priv); - - /* Port Change Detect */ - - if (priv->pending & XHCI_USBSTS_PCD) - { - /* Handled as event in xhci_events_poll() */ - - pciinfo("Port Change Detect\n"); - } - - /* Host Controller Halted */ - - if (priv->pending & XHCI_USBSTS_HCH) - { - pciinfo("Host Controller Halted\n"); - } - - /* Host System Error */ - - if (priv->pending & XHCI_USBSTS_HSE) - { - pciinfo("Host System Error\n"); - } - - /* Host Controller Error */ - - if (priv->pending & XHCI_USBSTS_HCE) - { - pciinfo("Host Controller Error\n"); - } - - /* ACK interrupts */ - - xhci_oper_putreg(priv, XHCI_USBSTS, priv->pending); - - /* Clear interrupter pending bit */ - - iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); - if (iman & XHCI_IMAN_IP) - { - xhci_runt_putreg(priv, XHCI_IMAN(0), iman); - } - - /* Clear pending bits */ - - priv->pending = 0; -} - -/**************************************************************************** - * Name: xhci_interupt - * - * Description: - * Interrupt handler for xHCI - * - ****************************************************************************/ - -static int xhci_interrupt(int irq, FAR void *context, FAR void *arg) -{ - FAR struct usbhost_xhci_s *priv = arg; - - /* Get pending interrupts */ - - priv->pending = xhci_oper_getreg(priv, XHCI_USBSTS); - - /* Handle interrupts in worker */ - - if (work_available(&priv->work)) - { - work_queue(HPWORK, &priv->work, xhci_interrupt_work, arg, 0); - } - - return OK; -} - -/**************************************************************************** - * Name: xhci_wait - * - * Description: - * Wait for a device to be connected or disconnected to/from a hub port. - * - * Input Parameters: - * conn - The USB host connection instance obtained as a parameter from - * the call to the USB driver initialization logic. - * hport - The location to return the hub port descriptor that detected - * the connection related event. - * - * Returned Value: - * Zero (OK) is returned on success when a device is connected or - * disconnected. This function will not return until either (1) a device is - * connected or disconnect to/from any hub port or until (2) some failure - * occurs. On a failure, a negated errno value is returned indicating the - * nature of the failure - * - * Assumptions: - * - Called from a single thread so no mutual exclusion is required. - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_wait(FAR struct usbhost_connection_s *conn, - FAR struct usbhost_hubport_s **hport) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); - FAR struct xhci_rhport_s *rhport; - FAR struct usbhost_hubport_s *connport; - irqstate_t flags; - int rhpndx; - int ret; - - /* Loop until the connection state changes on one of the root hub ports or - * until an error occurs. - */ - - while (true) - { - flags = spin_lock_irqsave(&priv->spinlock); - - /* Check for a change in the connection state on any root hub port */ - - for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) - { - /* Has the connection state changed on the RH port? */ - - rhport = &priv->rhport[rhpndx]; - connport = &rhport->hport.hport; - if (rhport->connected != connport->connected) - { - /* Yes.. Return the RH port to inform the caller which - * port has the connection change. - */ - - connport->connected = rhport->connected; - *hport = connport; - spin_unlock_irqrestore(&priv->spinlock, flags); - - usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, - rhpndx + 1, rhport->connected); - return OK; - } - } - -#ifdef CONFIG_USBHOST_HUB - /* Is a device connected to an external hub? */ - - if (priv->hport) - { - /* Yes.. return the external hub port */ - - connport = priv->hport; - priv->hport = NULL; - - *hport = (FAR struct usbhost_hubport_s *)connport; - spin_unlock_irqrestore(&priv->spinlock, flags); - - usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, - HPORT(connport), connport->connected); - return OK; - } -#endif - - /* No changes on any port. Wait for a connection/disconnection event - * and check again - */ - - priv->pscwait = true; - - spin_unlock_irqrestore(&priv->spinlock, flags); - - ret = nxsem_wait_uninterruptible(&priv->pscsem); - if (ret < 0) - { - return ret; - } - } -} - -/**************************************************************************** - * Name: xhci_rh_enumerate/xhci_enumerate - * - * Description: - * Enumerate the connected device. As part of this enumeration process, - * the driver will (1) get the device's configuration descriptor, (2) - * extract the class ID info from the configuration descriptor, (3) call - * usbhost_findclass() to find the class that supports this device, (4) - * call the create() method on the struct usbhost_registry_s interface - * to get a class instance, and finally (5) call the connect() method - * of the struct usbhost_class_s interface. After that, the class is in - * charge of the sequence of operations. - * - * Input Parameters: - * conn - The USB host connection instance obtained as a parameter from - * the call to the USB driver initialization logic. - * hport - The descriptor of the hub port that has the newly connected - * device. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * This function will *not* be called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, - FAR struct usbhost_hubport_s *hport) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); - FAR struct xhci_rhport_s *rhport; - int rhpndx; - int ret; - - DEBUGASSERT(hport != NULL); - rhpndx = hport->port; - - DEBUGASSERT(rhpndx >= 0 && rhpndx < priv->no_ports); - rhport = &priv->rhport[rhpndx]; - - /* Are we connected to a device? The caller should have called the wait() - * method first to be assured that a device is connected. - */ - - if (!rhport->connected) - { - /* No, return an error */ - - pcierr("not connected\n"); - return -ENODEV; - } - - /* Enable port */ - - ret = xhci_port_enable(priv, hport); - if (ret < 0) - { - pcierr("Failed to enable port %d\n", ret); - return ret; - } - - /* Initialize device data */ - - ret = xhci_device_init(priv, rhport); - if (ret < 0) - { - pcierr("Failed to initialize device %d\n", ret); - return ret; - } - - return OK; -} - -/**************************************************************************** - * Name: xhci_enumerate - * - * Description: - * See description above. - * - ****************************************************************************/ - -static int xhci_enumerate(FAR struct usbhost_connection_s *conn, - FAR struct usbhost_hubport_s *hport) -{ - int ret; - - /* If this is a connection on the root hub, then we need to go to - * little more effort to get the device speed. If it is a connection - * on an external hub, then we already have that information. - */ - - DEBUGASSERT(hport); -#ifdef CONFIG_USBHOST_HUB - if (ROOTHUB(hport)) -#endif - { - ret = xhci_rh_enumerate(conn, hport); - if (ret < 0) - { - return ret; - } - } - - /* Then let the common usbhost_enumerate do the real enumeration. */ - - ret = usbhost_enumerate(hport, &hport->devclass); - if (ret < 0) - { - /* Failed to enumerate */ - - /* If this is a root hub port, then marking the hub port not connected - * will cause xhci_wait() to return and we will try the connection - * again. - */ - - hport->connected = false; - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_ep0configure - * - * Description: - * Configure endpoint 0. This method is normally used internally by the - * enumerate() method but is made available at the interface to support - * an external implementation of the enumeration logic. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * funcaddr - The USB address of the function containing the endpoint that - * EP0 controls. A funcaddr of zero will be received if no address is - * yet assigned to the device. - * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH - * maxpacketsize - The maximum number of bytes that can be sent to or - * received from the endpoint in a single data packet - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure. - * - * Assumptions: - * This function will *not* be called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, - usbhost_ep_t ep0, uint8_t funcaddr, - uint8_t speed, uint16_t maxpacketsize) -{ - FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; - FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep0; - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); - uint64_t ctx; - int ret; - - DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048); - - ret = nxmutex_lock(&priv->lock); - if (ret >= 0) - { - /* Update max packet size */ - - rhport->dev->input->ep[0].ctx1 &= ~XHCI_EP_CTX1_MAXPKT_MASK; - rhport->dev->input->ep[0].ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpacketsize); - - /* Add Slot Context and EP0 Context */ - - xhci_context_ctrl(priv, rhport->dev, 0, - XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | - XHCI_IN_CTX1_A(XHCI_EP0_FLAG)); - - /* Flush Device input context */ - - up_flush_dcache((uintptr_t)rhport->dev->input, - (uintptr_t)rhport->dev->input + - sizeof(struct xhci_input_dev_ctx_s)); - - /* Free mutex before command execution */ - - nxmutex_unlock(&priv->lock); - - ctx = up_addrenv_va_to_pa(rhport->dev->input); - ret = xhci_cmd_evalctx(priv, epinfo->slot, ctx); - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_epalloc - * - * Description: - * Allocate and configure one endpoint. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * epdesc - Describes the endpoint to be allocated. - * ep - A memory location provided by the caller in which to receive the - * allocated endpoint descriptor. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure. - * - * Assumptions: - * This function will *not* be called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, - FAR const struct usbhost_epdesc_s *epdesc, - FAR usbhost_ep_t *ep) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); - FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; - FAR struct usbhost_hubport_s *hport; - FAR struct xhci_epinfo_s *epinfo; - FAR struct xhci_dev_s *dev; - uint32_t mask; - uint8_t eptype; - uint8_t idx; - int ret; - - /* Sanity check. NOTE that this method should only be called if a device - * is connected (because we need a valid low speed indication). - */ - - DEBUGASSERT(drvr != 0 && epdesc != NULL && epdesc->hport != NULL - && ep != NULL); - hport = epdesc->hport; - - /* Terse output only if we are tracing */ - -#ifdef CONFIG_USBHOST_TRACE - usbhost_vtrace2(XHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype); -#else - pciinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n", - epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr, - epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); -#endif - - /* Allocate a endpoint information structure */ - - epinfo = kmm_zalloc(sizeof(struct xhci_epinfo_s)); - if (!epinfo) - { - return -ENOMEM; - } - - /* Initialize the endpoint container (which is really just another form of - * 'struct usbhost_epdesc_s', packed differently and with additional - * information. A cleaner design might just embed struct usbhost_epdesc_s - * inside of struct xhci_epinfo_s and just memcpy here. - */ - - epinfo->dirin = epdesc->in; - epinfo->epno = epdesc->addr; - -#ifndef CONFIG_USBHOST_INT_DISABLE - epinfo->interval = epdesc->interval; -#endif - epinfo->xfrtype = epdesc->xfrtype; - nxsem_init(&epinfo->iocsem, 0, 0); - - /* xhci_epno_get() returns Device Context Index (DCI) */ - - idx = xhci_epno_get(epinfo); - mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx)); - dev = rhport->dev; - dev->epinfo[idx - 1] = epinfo; - - /* TD rings already allocated but not connected yet. */ - - ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX); - if (ret < 0) - { - pcierr("ep ring init failed\n"); - return ret; - } - - /* Store slot ID for later */ - - epinfo->slot = rhport->slot; - -#ifdef CONFIG_USBHOST_HUB - if (hport->speed != USB_SPEED_HIGH) - { - /* A high speed hub exists between this device and the root hub - * otherwise we would not get here. - */ - - FAR struct usbhost_hubport_s *parent = hport->parent; - - for (; parent->speed != USB_SPEED_HIGH; parent = hport->parent) - { - hport = parent; - } - - if (parent->speed == USB_SPEED_HIGH) - { - epinfo->hubport = HPORT(hport); - epinfo->hubaddr = hport->parent->funcaddr; - } - else - { - return -EINVAL; - } - } -#endif - - /* Get EP type */ - - switch (epinfo->xfrtype) - { - case USB_EP_ATTR_XFER_BULK: - { - eptype = epinfo->dirin ? XHCI_EPTYPE_BULK_IN : - XHCI_EPTYPE_BULK_OUT; - break; - } - -#ifndef CONFIG_USBHOST_INT_DISABLE - case USB_EP_ATTR_XFER_INT: -#endif - { - eptype = epinfo->dirin ? XHCI_EPTYPE_INTR_IN : - XHCI_EPTYPE_INTR_OUT; - break; - } - -#ifndef CONFIG_USBHOST_ISOC_DISABLE - case USB_EP_ATTR_XFER_ISOC: - { - eptype = epinfo->dirin ? XHCI_EPTYPE_ISO_IN : - XHCI_EPTYPE_ISO_OUT; - break; - } -#endif - - default: - { - return -ENOSYS; - } - } - - /* REVISIT: do we need disable EP here? */ - - /* Initialize EP context. - * Max Burst Size set for 0 for now (USB3.0 specific) - */ - - xhci_ep_configure(priv, &dev->input->ep[idx - 1], - eptype, epdesc->mxpacketsize, 0, - up_addrenv_va_to_pa(epinfo->td.ring), - 0, epinfo->interval); - - /* Evaluate the slot context */ - - xhci_context_ctrl(priv, dev, 0, mask | XHCI_IN_CTX1_A(XHCI_SLOT_FLAG)); - - up_flush_dcache((uintptr_t)dev->input, - (uintptr_t)dev->input + - sizeof(struct xhci_input_dev_ctx_s)); - - /* Configure EP */ - - ret = xhci_cmd_cfgep(priv, epinfo->slot, - up_addrenv_va_to_pa(dev->input), false); - if (ret < 0) - { - pcierr("failed to configure EP %d\n", ret); - return ret; - } - - /* Success.. return an opaque reference to the endpoint information - * structure instance - */ - - *ep = (usbhost_ep_t)epinfo; - return OK; -} - -/**************************************************************************** - * Name: xhci_epfree - * - * Description: - * Free an endpoint previously allocated by DRVR_EPALLOC. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * ep - The endpoint to be freed. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * This function will *not* be called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) -{ - FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; - - /* There should not be any pending, transfers */ - - DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); - - /* Free ring */ - - xhci_ring_deinit(&epinfo->td); - - /* Free the container */ - - kmm_free(epinfo); - return OK; -} - -/**************************************************************************** - * Name: xhci_alloc - * - * Description: - * Some hardware supports special memory in which request and descriptor - * data can be accessed more efficiently. This method provides a - * mechanism to allocate the request/descriptor memory. If the underlying - * hardware does not support such "special" memory, this functions may - * simply map to kmm_malloc(). - * - * This interface was optimized under a particular assumption. It was - * assumed that the driver maintains a pool of small, pre-allocated buffers - * for descriptor traffic. NOTE that size is not an input, but an output: - * The size of the pre-allocated buffer is returned. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * buffer - The address of a memory location provided by the caller in - * which to return the allocated buffer memory address. - * maxlen - The address of a memory location provided by the caller in - * which to return the maximum size of the allocated buffer memory. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * - Called from a single thread so no mutual exclusion is required. - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_alloc(FAR struct usbhost_driver_s *drvr, - FAR uint8_t **buffer, FAR size_t *maxlen) -{ - int ret = -ENOMEM; - - DEBUGASSERT(drvr && buffer && maxlen); - - /* Allocated buffer must not cross page boundaries */ - - *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , XHCI_BUFSIZE); - if (*buffer) - { - *maxlen = XHCI_BUFSIZE; - ret = OK; - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_free - * - * Description: - * Some hardware supports special memory in which request and descriptor - * data can be accessed more efficiently. This method provides a - * mechanism to free that request/descriptor memory. If the underlying - * hardware does not support such "special" memory, this functions may - * simply map to kmm_free(). - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * buffer - The address of the allocated buffer memory to be freed. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) -{ - DEBUGASSERT(drvr && buffer); - - /* No special action is require to free the transfer/descriptor buffer - * memory - */ - - kmm_free(buffer); - return OK; -} - -/**************************************************************************** - * Name: xhci_ioalloc - * - * Description: - * Some hardware supports special memory in which larger IO buffers can - * be accessed more efficiently. This method provides a mechanism to - * allocate the request/descriptor memory. If the underlying hardware - * does not support such "special" memory, this functions may simply map - * to kumm_malloc. - * - * This interface differs from DRVR_ALLOC in that the buffers are variable- - * sized. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * buffer - The address of a memory location provided by the caller in - * which to return the allocated buffer memory address. - * buflen - The size of the buffer required. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * This function will *not* be called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, - FAR uint8_t **buffer, size_t buflen) -{ - int ret = -ENOMEM; - - DEBUGASSERT(drvr && buffer && buflen > 0); - - /* Large transfers are not supported now */ - - if (buflen > XHCI_PAGE_SIZE) - { - return -ENOMEM; - } - - /* Allocated buffer must not cross page boundaries */ - - *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , buflen); - if (*buffer) - { - ret = OK; - } - - return ret; -} - -/**************************************************************************** - * Name: xhci_iofree - * - * Description: - * Some hardware supports special memory in which IO data can be accessed - * more efficiently. This method provides a mechanism to free that IO - * buffer memory. If the underlying hardware does not support such - * "special" memory, this functions may simply map to kumm_free(). - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * buffer - The address of the allocated buffer memory to be freed. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * This function will *not* be called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_iofree(FAR struct usbhost_driver_s *drvr, - FAR uint8_t *buffer) -{ - DEBUGASSERT(drvr && buffer); - - /* No special action is require to free the transfer/descriptor buffer - * memory - */ - - kmm_free(buffer); - return OK; -} - -/**************************************************************************** - * Name: xhci_ctrl_xfer - * - * Description: - * Process a IN or OUT request on the control endpoint. These methods - * will enqueue the request and wait for it to complete. Only one - * transfer may be queued; Neither these methods nor the transfer() method - * can be called again until the control transfer function returns. - * - * These are blocking methods; these functions will not return until the - * control transfer has completed. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * ep0 - The control endpoint to send/receive the control request. - * req - Describes the request to be sent. This request must lie in - * memory created by DRVR_ALLOC. - * buffer - A buffer used for sending the request and for returning any - * responses. This buffer must be large enough to hold the - * length value in the request description. buffer must have been - * allocated using DRVR_ALLOC. - * - * NOTE: On an IN transaction, req and buffer may refer to the xHCI - * allocated memory. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * - Called from a single thread so no mutual exclusion is required. - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, - usbhost_ep_t ep0, - FAR const struct usb_ctrlreq_s *req, - FAR uint8_t *buffer) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); - FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; - FAR struct xhci_epinfo_s *ep0info = (FAR struct xhci_epinfo_s *)ep0; - uint16_t len; - ssize_t nbytes; - int ret; - - DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL); - - len = xhci_getle16(req->len); - - /* Terse output only if we are tracing */ - -#ifdef CONFIG_USBHOST_TRACE - usbhost_vtrace2(XHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req); -#endif - - /* Special case for SET_ADDRESS request */ - - if (req->req == USB_REQ_SETADDRESS) - { - /* Reset EP0 ring to its initial state, so when xHCI update EP0 - * context, TD dequeue pointer would be valid. It may be already - * off, because the USB Host stack has already sent some messages - * on control EP. - */ - - xhci_ring_init(&rhport->dev->rhport->ep0.td, 0); - - /* Issue SET_ADDRESS request */ - - ret = xhci_address_set(priv, rhport, true); - if (ret == OK) - { - /* Store USB Device Address assigned by xHCI */ - - ep0info->devaddr = - XHCI_ST_CTX3_ADDR_GET(rhport->dev->ctx->slot.ctx[3]); - rhport->dev->input->slot.ctx[3] = rhport->dev->ctx->slot.ctx[3]; - } - - return OK; - } - - /* We must have exclusive access to the XHCI hardware and data - * structures. - */ - - ret = nxmutex_lock(&priv->lock); - if (ret < 0) - { - return ret; - } - - /* Set the request for the IOC event well BEFORE initiating the transfer. */ - - ret = xhci_ioc_setup(rhport, ep0info, 0); - if (ret != OK) - { - goto errout_with_lock; - } - - /* Now initiate the transfer */ - - ret = xhci_control_setup(rhport, ep0info, req, buffer, len); - if (ret < 0) - { - pcierr("ERROR: xhci_control_setup failed: %d\n", ret); - goto errout_with_iocwait; - } - - nxmutex_unlock(&priv->lock); - - /* And wait for the transfer to complete */ - - nbytes = xhci_transfer_wait(priv, ep0info); - return nbytes >= 0 ? OK : (int)nbytes; - -errout_with_iocwait: - ep0info->iocwait = false; -errout_with_lock: - nxmutex_unlock(&priv->lock); - return ret; -} - -/**************************************************************************** - * Name: xhci_ctrlin - * - * Description: - * Process IN request on the control endpoint. For details, see - * description for xhci_ctrl_xfer(). - * - ****************************************************************************/ - -static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, - FAR const struct usb_ctrlreq_s *req, - FAR uint8_t *buffer) -{ - /* xhci_ctrl_xfer() can handle both directions */ - - return xhci_ctrl_xfer(drvr, ep0, req, buffer); -} - -/**************************************************************************** - * Name: xhci_ctrlout - * - * Description: - * Process OUT request on the control endpoint. For details, see - * description for xhci_ctrl_xfer(). - * - ****************************************************************************/ - -static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, - FAR const struct usb_ctrlreq_s *req, - FAR const uint8_t *buffer) -{ - /* xhci_ctrl_xfer() can handle both directions. We just need to work around - * the differences in the function signatures. - */ - - return xhci_ctrl_xfer(drvr, ep0, req, (FAR uint8_t *)buffer); -} - -/**************************************************************************** - * Name: xhci_transfer - * - * Description: - * Process a request to handle a transfer descriptor. This method will - * enqueue the transfer request, blocking until the transfer completes. - * Only one transfer may be queued; Neither this method nor the ctrlin or - * ctrlout methods can be called again until this function returns. - * - * This is a blocking method; this functions will not return until the - * transfer has completed. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * ep - The IN or OUT endpoint descriptor for the device endpoint on - * which to perform the transfer. - * buffer - A buffer containing the data to be sent (OUT endpoint) or - * received (IN endpoint). buffer must have been allocated using - * DRVR_ALLOC - * buflen - The length of the data to be sent or received. - * - * Returned Value: - * On success, a non-negative value is returned that indicates the number - * of bytes successfully transferred. On a failure, a negated errno value - * is returned that indicates the nature of the failure: - * - * EAGAIN - If devices NAKs the transfer (or NYET or other error where - * it may be appropriate to restart the entire transaction). - * EPERM - If the endpoint stalls - * EIO - On a TX or data toggle error - * EPIPE - Overrun errors - * - * Assumptions: - * - Called from a single thread so no mutual exclusion is required. - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, - usbhost_ep_t ep, FAR uint8_t *buffer, - size_t buflen) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); - FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; - FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; - ssize_t nbytes; - int ret; - - DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); - - /* We must have exclusive access to the xHCI hardware and data - * structures. - */ - - ret = nxmutex_lock(&priv->lock); - if (ret < 0) - { - return (ssize_t)ret; - } - - /* Set the request for the IOC event well BEFORE initiating the transfer. */ - - ret = xhci_ioc_setup(rhport, epinfo, buflen); - if (ret != OK) - { - goto errout_with_lock; - } - - /* Initiate the transfer */ - - switch (epinfo->xfrtype) - { - case USB_EP_ATTR_XFER_BULK: -#ifndef CONFIG_USBHOST_INT_DISABLE - case USB_EP_ATTR_XFER_INT: -#endif - { - ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); - break; - } - -#ifndef CONFIG_USBHOST_ISOC_DISABLE - case USB_EP_ATTR_XFER_ISOC: - { - ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); - break; - } -#endif - - case USB_EP_ATTR_XFER_CONTROL: - default: - { - usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); - ret = -ENOSYS; - break; - } - } - - /* Check for errors in the setup of the transfer */ - - if (ret < 0) - { - uerr("ERROR: Transfer setup failed: %d\n", ret); - goto errout_with_iocwait; - } - - nxmutex_unlock(&priv->lock); - - /* Then wait for the transfer to complete */ - - nbytes = xhci_transfer_wait(priv, epinfo); - return nbytes; - -errout_with_iocwait: - epinfo->iocwait = false; -errout_with_lock: - nxmutex_unlock(&priv->lock); - return (ssize_t)ret; -} - -/**************************************************************************** - * Name: xhci_asynch - * - * Description: - * Process a request to handle a transfer descriptor. This method will - * enqueue the transfer request and return immediately. When the transfer - * completes, the callback will be invoked with the provided transfer. - * This method is useful for receiving interrupt transfers which may come - * infrequently. - * - * Only one transfer may be queued; Neither this method nor the ctrlin or - * ctrlout methods can be called again until the transfer completes. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from - * the call to the class create() method. - * ep - The IN or OUT endpoint descriptor for the device endpoint on - * which to perform the transfer. - * buffer - A buffer containing the data to be sent (OUT endpoint) or - * received (IN endpoint). buffer must have been allocated - * using DRVR_ALLOC - * buflen - The length of the data to be sent or received. - * callback - This function will be called when the transfer completes. - * arg - The arbitrary parameter that will be passed to the callback - * function when the transfer completes. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure - * - * Assumptions: - * - Called from a single thread so no mutual exclusion is required. - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -#ifdef CONFIG_USBHOST_ASYNCH -static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, - FAR uint8_t *buffer, size_t buflen, - usbhost_asynch_t callback, FAR void *arg) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); - FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; - FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; - int ret; - - DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); - - /* We must have exclusive access to the xHCI hardware and data - * structures. - */ - - ret = nxmutex_lock(&priv->lock); - if (ret < 0) - { - return ret; - } - - /* Set the request for the callback well BEFORE initiating the transfer. */ - - ret = xhci_ioc_async_setup(rhport, epinfo, callback, arg); - if (ret != OK) - { - goto errout_with_lock; - } - - /* Initiate the transfer */ - - switch (epinfo->xfrtype) - { - case USB_EP_ATTR_XFER_BULK: -#ifndef CONFIG_USBHOST_INT_DISABLE - case USB_EP_ATTR_XFER_INT: -#endif - { - ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); - break; - } - -#ifndef CONFIG_USBHOST_ISOC_DISABLE - case USB_EP_ATTR_XFER_ISOC: - { - ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); - break; - } -#endif - - case USB_EP_ATTR_XFER_CONTROL: - default: - { - usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); - ret = -ENOSYS; - break; - } - } - - /* Check for errors in the setup of the transfer */ - - if (ret < 0) - { - goto errout_with_callback; - } - - /* The transfer is in progress */ - - nxmutex_unlock(&priv->lock); - return OK; - -errout_with_callback: - epinfo->callback = NULL; - epinfo->arg = NULL; -errout_with_lock: - nxmutex_unlock(&priv->lock); - return ret; -} -#endif /* CONFIG_USBHOST_ASYNCH */ - -/**************************************************************************** - * Name: xhci_cancel - * - * Description: - * Cancel a pending transfer on an endpoint. Cancelled synchronous or - * asynchronous transfer will complete normally with the error -ESHUTDOWN. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * ep - The IN or OUT endpoint descriptor for the device endpoint on which - * an asynchronous transfer should be transferred. - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure. - * - ****************************************************************************/ - -static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) -{ - FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); -#ifdef CONFIG_USBHOST_ASYNCH - usbhost_asynch_t callback; - FAR void *arg; -#endif - irqstate_t flags; - bool iocwait; - - DEBUGASSERT(epinfo); - - /* Sample and reset all transfer termination information. This will - * prevent any callbacks from occurring while we performing the - * cancellation. The transfer may still be in progress, however, so this - * does not eliminate other DMA-related race conditions. - */ - - flags = spin_lock_irqsave(&priv->spinlock); -#ifdef CONFIG_USBHOST_ASYNCH - callback = epinfo->callback; - arg = epinfo->arg; -#endif - iocwait = epinfo->iocwait; - -#ifdef CONFIG_USBHOST_ASYNCH - epinfo->callback = NULL; - epinfo->arg = NULL; -#endif - epinfo->iocwait = false; - spin_unlock_irqrestore(&priv->spinlock, flags); - - /* Bail if there is no transfer in progress for this endpoint */ - -#ifdef CONFIG_USBHOST_ASYNCH - if (callback == NULL && !iocwait) -#else - if (!iocwait) -#endif - { - return OK; - } - - /* Stop endpoint */ - - xhci_cmd_stopep(priv, epinfo->slot, xhci_epno_get(epinfo), false); - - /* REVISIT: what if we interrupted the execution of a TD? page 139 */ - - epinfo->result = -ESHUTDOWN; - - if (iocwait) - { - /* Yes... wake it up */ - - nxsem_post(&epinfo->iocsem); - } - -#ifdef CONFIG_USBHOST_ASYNCH - /* No.. Is there a pending asynchronous transfer? */ - - else - { - /* Yes.. perform the callback */ - - DEBUGASSERT(callback != NULL); - callback(arg, -ESHUTDOWN); - } -#endif - - return OK; -} - -/**************************************************************************** - * Name: xhci_connect - * - * Description: - * New connections may be detected by an attached hub. This method is the - * mechanism that is used by the hub class to introduce a new connection - * and port description to the system. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * hport - The descriptor of the hub port that detected the connection - * related event - * connected - True: device connected; false: device disconnected - * - * Returned Value: - * On success, zero (OK) is returned. On a failure, a negated errno value - * is returned indicating the nature of the failure. - * - ****************************************************************************/ - -#ifdef CONFIG_USBHOST_HUB -static int xhci_connect(FAR struct usbhost_driver_s *drvr, - FAR struct usbhost_hubport_s *hport, - bool connected) -{ -#error missing logic -} -#endif - -/**************************************************************************** - * Name: xhci_disconnect - * - * Description: - * Called by the class when an error occurs and device has been - * disconnected. The USB host driver should discard the handle to the - * class instance (it is stale) and not attempt any further interaction - * with the class driver instance (until a new instance is received from - * the create() method). The driver should not call the class - * disconnected() method. - * - * Input Parameters: - * drvr - The USB host driver instance obtained as a parameter from the - * call to the class create() method. - * hport - The port from which the device is being disconnected. Might be - * a port on a hub. - * - * Returned Value: - * None - * - * Assumptions: - * - Only a single class bound to a single device is supported. - * - Never called from an interrupt handler. - * - ****************************************************************************/ - -static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, - FAR struct usbhost_hubport_s *hport) -{ - FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); - FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; - - DEBUGASSERT(hport != NULL); - hport->devclass = NULL; - - /* Deinit device slot */ - - if (rhport->dev) - { - xhci_device_deinit(priv, rhport); - } -} - -/**************************************************************************** - * Name: xhci_hw_getparams - * - * Description: - * Get hardware description of a connected xHCI device. - * - ****************************************************************************/ - -static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv) -{ - uint32_t regval; - - /* Get data form Host Controller Capability 1 Parameters */ - - regval = xhci_capa_getreg(priv, XHCI_HCCPARAMS1); - if (regval & XHCI_HCCPARAMS1_CSZ) - { - pcierr("Only 32 byte Context data structures supported!\n"); - return -EIO; - } - - /* Get data from Structural Parameters 1 register */ - - regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS1); - priv->no_slots = XHCI_HCSPARAMS1_MAXSLOTS(regval); - priv->no_ports = XHCI_HCSPARAMS1_MAXPORTS(regval); - - /* Limit number of slots to number of devices */ - - if (priv->no_slots > CONFIG_USBHOST_XHCI_MAX_DEVS) - { - priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS; - } - - pciinfo("no slots = %d, no ports = %d\n", - priv->no_slots, priv->no_ports); - - /* Check if valid */ - - if (priv->no_slots == 0 || priv->no_ports == 0) - { - return -EINVAL; - } - - /* Get data from Structural Parameters 2 register */ - - regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS2); - priv->no_scratch = XHCI_HCSPARAMS2_MAXSPB(regval); - - pciinfo("no scratch = %d\n", priv->no_scratch); - - priv->no_erst = 1 << XHCI_HCSPARAMS2_ERST(regval); - - pciinfo("no_erst = %d\n", priv->no_erst); - - /* Limit event ring segment table to 1 */ - - if (priv->no_erst > XHCI_MAX_ERST) - { - priv->no_erst = XHCI_MAX_ERST; - } - - pciinfo("no erst = %d\n", priv->no_erst); - - return OK; -} - -/**************************************************************************** - * Name: xhci_irq_initialize - * - * Description: - * Initialize xHCI interrupts - require MSI-X support. - * - ****************************************************************************/ - -static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv) -{ - int ret; - - /* Allocate MSI */ - - ret = pci_alloc_irq(priv->pcidev, &priv->irq, 1); - if (ret != 1) - { - pcierr("Failed to allocate MSI %d\n", ret); - return ret; - } - - /* Attach IRQ */ - - irq_attach(priv->irq, xhci_interrupt, priv); - - /* Connect MSI-X */ - - ret = pci_connect_irq(priv->pcidev, &priv->irq, 1); - if (ret != OK) - { - pcierr("Failed to connect MSI %d\n", ret); - pci_release_irq(priv->pcidev, &priv->irq, 1); - - return -ENOTSUP; - } - - up_enable_irq(priv->irq); - - return OK; -} - -/**************************************************************************** - * Name: xhci_mem_alloc - * - * Description: - * Allocated memory for a new detected xHCI device. - * - ****************************************************************************/ - -static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) -{ - size_t tmp; - int i; - - /* Allocate Scratchpad Buffer Array */ - - tmp = priv->no_scratch * sizeof(uint64_t); - priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp); - if (!priv->pg_sb) - { - pcierr("pg_sb malloc failed\n"); - return -ENOMEM; - } - - memset(priv->pg_sb, 0, tmp); - - for (i = 0; i < priv->no_scratch; i++) - { - /* Alloc page for each entry in array */ - - priv->pg_sb[i] = up_addrenv_va_to_pa( - kmm_memalign(XHCI_PAGE_SIZE, XHCI_PAGE_SIZE)); - if (!priv->pg_sb[i]) - { - pcierr("pg_sb[i] malloc failed\n"); - return -ENOMEM; - } - - /* Reset page */ - - memset((FAR void *)(up_addrenv_pa_to_va(priv->pg_sb[i])), - 0, XHCI_PAGE_SIZE); - } - - /* Allocate Device Context Array which shall be: - * size = MaxSlotsEn + 1 entries - */ - - tmp = sizeof(uint64_t) * (priv->no_slots + 1); - priv->pg_ctx = kmm_memalign(XHCI_BUF_ALIGN, tmp); - if (!priv->pg_ctx) - { - pcierr("pg_ctx malloc failed\n"); - return -ENOMEM; - } - - /* Reset context */ - - memset(priv->pg_ctx, 0, tmp); - - /* Allocate Event Table */ - - tmp = sizeof(struct xhci_event_ring_s) * priv->no_erst; - priv->pg_erst = kmm_memalign(XHCI_BUF_ALIGN, tmp); - if (!priv->pg_erst) - { - pcierr("priv->pg_erst malloc failed\n"); - return -ENOMEM; - } - - memset(priv->pg_erst, 0, tmp); - - /* Allocate root hub ports */ - - priv->rhport = kmm_zalloc(priv->no_ports * sizeof(struct xhci_rhport_s)); - if (!priv->rhport) - { - pcierr("rhport zalloc failed!\n"); - return -ENOMEM; - } - - /* Allocate xHC devices array */ - - priv->devs = kmm_zalloc(priv->no_slots * sizeof(struct xhci_dev_s)); - if (!priv->devs) - { - pcierr("devs zalloc failed!\n"); - return -ENOMEM; - } - - /* Allocate xHC devices resources */ - - for (i = 0; i < priv->no_slots; i++) - { - /* Allocate Device Context */ - - priv->devs[i].ctx = kmm_zalloc(sizeof(struct xhci_dev_ctx_s)); - if (!priv->devs[i].ctx) - { - pcierr("dev ctx zalloc failed!\n"); - return -ENOMEM; - } - - /* Allocate Input Context. The Input Context shall be physically - * contiguous within a page - */ - - priv->devs[i].input = kmm_memalign((XHCI_PAGE_SIZE / 2), - sizeof(struct xhci_input_dev_ctx_s)); - if (!priv->devs[i].input) - { - pcierr("dev input zalloc failed!\n"); - return -ENOMEM; - } - - /* No endpoint for device yet */ - - tmp = sizeof(uintptr_t) * XHCI_MAX_ENDPOINTS; - memset(priv->devs[i].epinfo, 0, tmp); - } - - return OK; -} - -/**************************************************************************** - * Name: xhci_mem_free - * - * Description: - * Free allocated memory for a xHCI device. - * - ****************************************************************************/ - -static int xhci_mem_free(FAR struct usbhost_xhci_s *priv) -{ - int i; - - /* Free scratch buffers */ - - for (i = 0; i < priv->no_scratch; i++) - { - kmm_free((FAR void *)priv->pg_sb[i]); - } - - kmm_free(priv->pg_sb); - - /* Free devices */ - - for (i = 0; i < priv->no_slots; i++) - { - kmm_free(priv->devs[i].ctx); - kmm_free(priv->devs[i].input); - } - - kmm_free(priv->devs); - kmm_free(priv->pg_ctx); - kmm_free(priv->pg_erst); - kmm_free(priv->rhport); - - /* Free command ring and event ring */ - - xhci_ring_deinit(&priv->cmd); - xhci_ring_deinit(&priv->evnt); - - return OK; -} - -/**************************************************************************** - * Name: xhci_hw_initialize - * - * Description: - * One-time setup of the host controller hardware for normal operations. - * - * Input Parameters: - * priv -- USB host driver private data structure. - * - * Returned Value: - * Zero on success; a negated errno value on failure. - * - ****************************************************************************/ - -static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv) -{ - int ret; - - /* Synchronize with BIOS */ - - ret = xhci_bios_wait(priv); - if (ret < 0) - { - pcierr("Failed to get xhci controller!\n"); - goto errout; - } - - /* Get structural parameters */ - - ret = xhci_hw_getparams(priv); - if (ret < 0) - { - goto errout; - } - - /* Allocate all required memory */ - - ret = xhci_mem_alloc(priv); - if (ret < 0) - { - goto errout; - } - - /* Configure interrupts */ - - ret = xhci_irq_initialize(priv); - if (ret < 0) - { - goto errout; - } - - /* Halt controller */ - - ret = xhci_ctrl_halt(priv); - if (ret < 0) - { - goto errout; - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: xhci_sw_initialize - * - * Description: - * One-time setup of the host driver state structure. - * - * Input Parameters: - * priv -- USB host driver private data structure. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv) -{ - FAR struct xhci_rhport_s *rhport; - FAR struct usbhost_hubport_s *hport; - int i; - - /* Initialize sync objects */ - - nxmutex_init(&priv->lock); - nxsem_init(&priv->pscsem, 0, 0); - nxsem_init(&priv->cmdsem, 0, 0); - - /* Initialize function address generation logic - * REVISIT: xHCI hardware is responsible for device address, but NuttX USB - * Host stack require this to be initialized. - */ - - usbhost_devaddr_initialize(&priv->devgen); - - /* Initialize devices */ - - for (i = 0; i < priv->no_slots; i++) - { - /* Slot disabled by defaulte */ - - priv->devs[i].state = XHCI_SLOT_DISABLED; - } - - /* Initialize the root hub port structures */ - - for (i = 0; i < priv->no_ports; i++) - { - rhport = &priv->rhport[i]; - - /* No device slot yet */ - - rhport->dev = NULL; - - /* Connect xhci instance */ - - rhport->priv = priv; - - /* Initialize the device operations */ - - rhport->drvr.ep0configure = xhci_ep0configure; - rhport->drvr.epalloc = xhci_epalloc; - rhport->drvr.epfree = xhci_epfree; - rhport->drvr.alloc = xhci_alloc; - rhport->drvr.free = xhci_free; - rhport->drvr.ioalloc = xhci_ioalloc; - rhport->drvr.iofree = xhci_iofree; - rhport->drvr.ctrlin = xhci_ctrlin; - rhport->drvr.ctrlout = xhci_ctrlout; - rhport->drvr.transfer = xhci_transfer; -#ifdef CONFIG_USBHOST_ASYNCH - rhport->drvr.asynch = xhci_asynch; -#endif - rhport->drvr.cancel = xhci_cancel; -#ifdef CONFIG_USBHOST_HUB - rhport->drvr.connect = xhci_connect; -#endif - rhport->drvr.disconnect = xhci_disconnect; - rhport->hport.pdevgen = &priv->devgen; - - /* Initialize EP0 */ - - rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL; - rhport->ep0.epno = 0; - rhport->ep0.devaddr = 0; - nxsem_init(&rhport->ep0.iocsem, 0, 0); - - /* Initialize the public port representation */ - - hport = &rhport->hport.hport; - hport->drvr = &rhport->drvr; -#ifdef CONFIG_USBHOST_HUB - hport->parent = NULL; -#endif - hport->ep0 = &rhport->ep0; - hport->port = i; - hport->speed = USB_SPEED_FULL; - } - - return OK; -} - -/**************************************************************************** - * Name: pci_xhci_probe - * - * Description: - * Initialize PCI device. - * - ****************************************************************************/ - -static int pci_xhci_probe(FAR struct pci_device_s *dev) -{ - FAR struct usbhost_conn_xhci_s *conn = NULL; - FAR struct usbhost_xhci_s *priv = NULL; - int ret = -ENOMEM; - - /* Init PCI bus */ - - pci_set_master(dev); - pciinfo("Enabled bus mastering\n"); - pci_enable_device(dev); - pciinfo("Enabled memory resources\n"); - - /* Allocate connection structure */ - - conn = kmm_zalloc(sizeof(struct usbhost_conn_xhci_s)); - if (!conn) - { - pcierr("zalloc failed!\n"); - goto errout; - } - - /* Allocate the driver structure */ - - priv = kmm_zalloc(sizeof(struct usbhost_xhci_s)); - if (!priv) - { - pcierr("zalloc failed!\n"); - goto errout; - } - - /* Initialize connection data */ - - conn->conn.wait = xhci_wait; - conn->conn.enumerate = xhci_enumerate; - conn->priv = priv; - - /* Connect PCI handler */ - - priv->pcidev = dev; - dev->priv = conn; - - /* Get base address - BAR 0 */ - - priv->base = (uintptr_t)pci_map_bar(dev, 0); - if (!priv->base) - { - pcierr("Not found BAR 0!\n"); - ret = -EIO; - goto errout; - } - - /* Get register address */ - - priv->capa_base = priv->base; - priv->oper_base = priv->base + xhci_capa_getreg_1b(priv, XHCI_CAPLENGTH); - priv->runt_base = priv->base + xhci_capa_getreg(priv, XHCI_RTSOFF); - priv->door_base = priv->base + xhci_capa_getreg(priv, XHCI_DBOFF); - - usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0); - - /* Initialize HW */ - - ret = xhci_hw_initialize(priv); - if (ret < 0) - { - pcierr("failed to initialize HW!\n"); - goto errout; - } - - /* Initialize SW */ - - ret = xhci_sw_initialize(priv); - if (ret < 0) - { - pcierr("failed to initialize SW!\n"); - goto errout; - } - - /* Start controller */ - - ret = xhci_ctrl_start(priv); - if (ret < 0) - { - usbhost_trace1(XHCI_TRACE1_START_FAILED, 0); - goto errout; - } - -#ifdef CONFIG_DEBUG_USB_INFO - /* Dump xhci registers */ - - xhci_dump_mem(priv, "after init"); -#endif - - /* If there is a USB device in the slot at power up, then we will not - * get the status change interrupt to signal us that the device is - * connected. We need to set the initial connected state accordingly. - */ - - xhci_probe_ports(priv); - - usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0); - - /* Initialize waiter */ - - ret = usbhost_waiter_initialize(&conn->conn); - if (ret < 0) - { - pcierr("failed to initialize waiter!\n"); - goto errout; - } - - /* Store waiter PID */ - - conn->pid = ret; - - return OK; - -errout: - - /* Free allocated xhci buffers */ - - xhci_mem_free(priv); - - /* Free allocated data */ - - kmm_free(conn); - kmm_free(priv); - - return ret; -} - -/**************************************************************************** - * Name: pci_xhci_remove - * - * Description: - * Remove PCI device. - * - ****************************************************************************/ - -static void pci_xhci_remove(FAR struct pci_device_s *dev) -{ - FAR struct usbhost_conn_xhci_s *conn = dev->priv; - FAR struct usbhost_xhci_s *priv = conn->priv; - - /* Free xhci interrupts */ - - irq_detach(priv->irq); - pci_release_irq(dev, &priv->irq, 1); - - /* Disable PCI devicve */ + xhci_uninitialize(pcix->conn); pci_clear_master(dev); pci_disable_device(dev); - /* Delete waiter thread */ - - kthread_delete(conn->pid); - - /* Free xhci buffers */ - - xhci_mem_free(priv); - - /* Free driver data */ - - kmm_free(conn); - kmm_free(priv); + dev->priv = NULL; + kmm_free(pcix); } /**************************************************************************** diff --git a/include/nuttx/usb/usbhost.h b/include/nuttx/usb/usbhost.h index 69f7aacd8b8c4..2565cdcd2a8df 100644 --- a/include/nuttx/usb/usbhost.h +++ b/include/nuttx/usb/usbhost.h @@ -721,6 +721,21 @@ struct usbhost_hubport_s uint8_t port; /* Hub port index */ uint8_t funcaddr; /* Device function address */ uint8_t speed; /* Device speed */ +#ifdef CONFIG_USBHOST_HUB + /* Set by the hub class driver when the device attached here is itself a + * hub, describing that hub rather than this port. Both are zero + * otherwise, and zero ports is not a hub. + * + * A host controller that has to be told about the hubs in a topology, + * rather than only about the device at the end of it, reads these from + * the parent of the port it is working on. They are set before the hub + * activates any downstream port, so they are in place before anything + * behind that hub can be enumerated. + */ + + uint8_t nports; /* Downstream ports on the attached hub */ + uint8_t ttt; /* Its transaction translator think time */ +#endif }; /* The root hub port differs in that it includes a data set that is used to diff --git a/include/nuttx/usb/xhci.h b/include/nuttx/usb/xhci.h new file mode 100644 index 0000000000000..1010811244ba6 --- /dev/null +++ b/include/nuttx/usb/xhci.h @@ -0,0 +1,112 @@ +/**************************************************************************** + * include/nuttx/usb/xhci.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_USB_XHCI_H +#define __INCLUDE_NUTTX_USB_XHCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* How the controller was found *********************************************/ + +/* What a controller cannot work out about itself. + * + * xHCI is the same silicon on PCI or wired into an SoC, and everything the + * specification describes is reached through the register block. What + * differs is how the interrupt arrives: PCI needs a message the device is + * configured to send, a memory mapped bus has a wire the interrupt + * controller already knows. The bus answers that, and nothing else. + */ + +struct xhci_bus_ops_s +{ + /* Attach the handler and make interrupts start arriving. Enabling + * belongs here rather than in the controller driver because on some + * buses attaching and enabling are one operation. + */ + + CODE int (*irq_attach)(FAR void *arg, xcpt_t handler, FAR void *priv); + + /* Undo it, and release anything the bus allocated to make it work */ + + CODE void (*irq_detach)(FAR void *arg); + + /* Whether the controller may be pointed at a given buffer, which is a + * property of the platform. Leave NULL where every address a caller can + * produce is reachable, as a flat address space gives. + */ + + CODE bool (*dmacapable)(FAR void *arg, FAR uint8_t *buffer, size_t buflen); +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: xhci_initialize + * + * Description: + * Bring up an xHCI controller and start watching its root hub ports. + * + * Input Parameters: + * name - What to call this controller when reporting what is attached to + * it, since a system may have more than one and "port 1" alone + * does not say which + * base - Where the controller's register block starts + * ops - How to reach the interrupt this controller raises + * arg - Opaque value passed back to ops + * + * Returned Value: + * A connection to hand to usbhost_waiter_initialize(); NULL on failure. + * + ****************************************************************************/ + +FAR struct usbhost_connection_s * +xhci_initialize(FAR const char *name, uintptr_t base, + FAR const struct xhci_bus_ops_s *ops, FAR void *arg); + +/**************************************************************************** + * Name: xhci_uninitialize + * + * Description: + * Stop watching a controller's ports and give back everything + * xhci_initialize() took. + * + ****************************************************************************/ + +void xhci_uninitialize(FAR struct usbhost_connection_s *conn); + +#endif /* __INCLUDE_NUTTX_USB_XHCI_H */