From 319aadfcf9271f6664c8296e5a774802fec6e687 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 15:57:05 +0800 Subject: [PATCH 01/15] drivers/usbhost: Make the xHCI driver work on a conforming controller. Four faults that between them kept this driver from reaching a device on any controller that enforces the specification rather than tolerating the driver's assumptions. The width of a register access is part of the register interface. xHCI asks for aligned accesses of the register's own size, and a controller may answer anything narrower with nothing at all; QEMU's does. A volatile load does not pin the width: when only one bit of the value is used, GCC 16.1.0 at -Os narrows "load 32 bits, test bit zero" into a one byte testb, the read comes back zero, and a poll of USBSTS for the halted bit never sees it. The binary happened to work only because a diagnostic consumed all 32 bits of the polled value and pinned that one load full width. Proven through QEMU's gdbstub against an unmodified binary: with the diagnostics compiled out the halt poll times out while the controller's true state, read 32 bits at a time from outside, is USBSTS=0x9, halted throughout; a 4-byte read of the register returns 0x9 and a 1-byte read of the same address is refused. Every accessor now forces the value through a register with an empty asm, loads and stores both, so the access can only be the full-width one the source wrote. A controller is entitled to want no scratchpad buffers, and QEMU's reports zero. The driver worked that count into a size and asked the allocator for it, and a zero-byte allocation returns NULL, indistinguishable from being out of memory, so a controller asking for no scratch space was refused for lack of it before it was ever started. Skip the allocation when none is wanted and leave the first device context base address array entry zero, which is what it means. Halting waited for something that had already happened. The driver wrote the whole of USBCMD zero and waited a second for the halted bit, but a controller that was never started is already halted and says so, so there is no transition to wait for. Writing the whole register zero also cleared the interrupt and host system error enables along with Run/Stop. Look first, stop it only if it is running, and clear the one bit that was meant. Resetting a port disabled it. Eight bits of PORTSC are write-one-to-clear, so writing back what was just read acts on every one that happened to be set: setting Port Reset that way also cleared Port Enabled and discarded every change the port was reporting. Mask them out first, and name the set in the header for the next read-modify-write of this register. The wait afterwards judged itself by its own counter rather than by the port, so a port coming up on the last attempt was reported as a timeout, and only when the timing landed that way, which reads as intermittent rather than wrong. Decide on Port Enabled, and say what the register held when it does fail. Finally, a command whose completion the fallback poll did find, after a late or missed interrupt, still returned the timeout, so a command that had demonstrably succeeded was reported unanswered and its caller unwound work the controller had done. The result is what the completion event said. This does not make a missing interrupt harmless, but it stops the driver contradicting the evidence in front of it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.h | 13 +++ drivers/usbhost/usbhost_xhci_pci.c | 170 ++++++++++++++++++++++------- 2 files changed, 144 insertions(+), 39 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.h b/drivers/usbhost/usbhost_xhci.h index 3888eabacf6b6..980f9924b1afb 100644 --- a/drivers/usbhost/usbhost_xhci.h +++ b/drivers/usbhost/usbhost_xhci.h @@ -268,6 +268,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 */ diff --git a/drivers/usbhost/usbhost_xhci_pci.c b/drivers/usbhost/usbhost_xhci_pci.c index 03b408168873d..2f296dc937740 100644 --- a/drivers/usbhost/usbhost_xhci_pci.c +++ b/drivers/usbhost/usbhost_xhci_pci.c @@ -68,6 +68,19 @@ #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) #define XHCI_BUFSIZE (512) /* Port numbers macros */ @@ -519,6 +532,16 @@ static struct pci_driver_s g_pci_xhci_drv = * 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 * @@ -530,8 +553,11 @@ static struct pci_driver_s g_pci_xhci_drv = 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); + uintptr_t addr = priv->capa_base + offset; + uint32_t regval = *((FAR volatile uint32_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; } /**************************************************************************** @@ -545,8 +571,11 @@ static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, 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); + uintptr_t addr = priv->capa_base + offset; + uint8_t regval = *((FAR volatile uint8_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; } /**************************************************************************** @@ -562,6 +591,8 @@ static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, uint8_t value) { uintptr_t addr = priv->capa_base + offset; + + __asm__ __volatile__("" : "+r"(value)); *((FAR volatile uint8_t *)addr) = value; } @@ -576,8 +607,11 @@ static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset) { - uintptr_t addr = priv->oper_base + offset; - return *((FAR volatile uint32_t *)addr); + uintptr_t addr = priv->oper_base + offset; + uint32_t regval = *((FAR volatile uint32_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; } /**************************************************************************** @@ -593,6 +627,8 @@ static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, uint32_t value) { uintptr_t addr = priv->oper_base + offset; + + __asm__ __volatile__("" : "+r"(value)); *((FAR volatile uint32_t *)addr) = value; } @@ -609,6 +645,8 @@ static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, uint64_t value) { uintptr_t addr = priv->oper_base + offset; + + __asm__ __volatile__("" : "+r"(value)); *((FAR volatile uint64_t *)addr) = value; } @@ -623,8 +661,11 @@ static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, 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); + uintptr_t addr = priv->runt_base + offset; + uint32_t regval = *((FAR volatile uint32_t *)addr); + + __asm__ __volatile__("" : "+r"(regval)); + return regval; } /**************************************************************************** @@ -640,6 +681,8 @@ static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, uint32_t value) { uintptr_t addr = priv->runt_base + offset; + + __asm__ __volatile__("" : "+r"(value)); *((FAR volatile uint32_t *)addr) = value; } @@ -656,6 +699,8 @@ static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, uint64_t value) { uintptr_t addr = priv->runt_base + offset; + + __asm__ __volatile__("" : "+r"(value)); *((FAR volatile uint64_t *)addr) = value; } @@ -672,6 +717,8 @@ static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, uint32_t value) { uintptr_t addr = priv->door_base + offset; + + __asm__ __volatile__("" : "+r"(value)); *((FAR volatile uint32_t *)addr) = value; } @@ -1074,9 +1121,12 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) xhci_oper_putreg(priv, XHCI_CONFIG, priv->no_slots); - /* Slot 0 in Device Context is reserved for Scratchpad Buffer Array */ + /* 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] = htole64(up_addrenv_va_to_pa(priv->pg_sb)); + priv->pg_ctx[0] = priv->pg_sb ? + htole64(up_addrenv_va_to_pa(priv->pg_sb)) : 0; /* Device Context Base Address Array Pointer */ @@ -1197,27 +1247,41 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv) { - int ret = -EAGAIN; - int i; + uint32_t regval; + int i; - /* Halt controller */ + /* 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; + } - xhci_oper_putreg(priv, XHCI_USBCMD, 0); + /* 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. + */ - /* Wait for controller halted */ + regval = xhci_oper_getreg(priv, XHCI_USBCMD); + regval &= ~XHCI_USBCMD_RS; + xhci_oper_putreg(priv, XHCI_USBCMD, regval); - for (i = 0; i < 10; i++) + for (i = 0; i < XHCI_HALT_TIMEOUT_MS; i++) { - up_mdelay(100); - - if (xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH) + regval = xhci_oper_getreg(priv, XHCI_USBSTS); + if ((regval & XHCI_USBSTS_HCH) != 0) { - ret = OK; - break; + return OK; } + + up_udelay(1000); } - return ret; + pcierr("controller will not halt, USBSTS %08" PRIx32 "\n", regval); + return -EAGAIN; } /**************************************************************************** @@ -1307,9 +1371,12 @@ static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, if (!(regval & XHCI_PORTSC_PED)) { - /* Reset the port */ + /* 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_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + regval &= ~XHCI_PORTSC_RW1C; regval |= XHCI_PORTSC_PR; xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), regval); @@ -1317,16 +1384,25 @@ static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, /* Wait for Enabled state for port */ - retries = 10; - while (!(xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)) - & XHCI_PORTSC_PED) && retries > 0) + for (retries = XHCI_PORT_RESET_MS; retries > 0; retries--) { - retries--; - up_mdelay(100); + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + if ((regval & XHCI_PORTSC_PED) != 0) + { + break; + } + + up_mdelay(1); } - if (retries == 0) + /* 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) { + pcierr("port %d will not enable, PORTSC %08" PRIx32 "\n", rhpndx, + regval); return -ETIMEDOUT; } } @@ -1869,7 +1945,15 @@ static int xhci_command(FAR struct usbhost_xhci_s *priv, trb->d1 = priv->cmdres.d1; trb->d2 = priv->cmdres.d2; - if (XHCI_TRB_D1_CC_GET(trb->d1) != XHCI_TRB_CC_SUCCESS) + 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 { pcierr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1)); ret = -EIO; @@ -4335,17 +4419,25 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) size_t tmp; int i; - /* Allocate Scratchpad Buffer Array */ + /* 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. + */ - tmp = priv->no_scratch * sizeof(uint64_t); - priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp); - if (!priv->pg_sb) + if (priv->no_scratch > 0) { - pcierr("pg_sb malloc failed\n"); - return -ENOMEM; - } + 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); + memset(priv->pg_sb, 0, tmp); + } for (i = 0; i < priv->no_scratch; i++) { From 2c0e65d1200e92c85dcbfdcaac1a004e8b31a5f1 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Fri, 7 Aug 2026 18:11:20 +0800 Subject: [PATCH 02/15] drivers/usbhost: Separate the xHCI driver from the PCI bus. The controller driver and the PCI bus it happened to be found on were one file. Nothing in the driver is PCI-specific beyond finding the registers and the interrupt, so an SoC that wires an xHCI controller directly could not use any of it. Move the driver to usbhost_xhci.c and leave usbhost_xhci_pci.c as the PCI attachment: the ID table, the BAR mapping and the MSI-X vector. What passes between them is in include/nuttx/usb/xhci.h: a bus supplies the register base, a way to attach the interrupt, and a name to report the controller by, since a system may have more than one and "port 1" alone does not say which. The interrupt belongs to the bus entirely: the bus attaches it, the bus detaches it, and the controller driver never holds an interrupt number, so there is no number for the two sides to disagree about. USBHOST_XHCI is the driver and is not selectable on its own; USBHOST_XHCI_PCI selects it. Another bus adds its own symbol beside it. No functional change intended. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/CMakeLists.txt | 6 +- drivers/usbhost/Kconfig | 22 +- drivers/usbhost/Make.defs | 6 +- drivers/usbhost/usbhost_xhci.c | 4822 +++++++++++++++++++++++++++ drivers/usbhost/usbhost_xhci.h | 4 + drivers/usbhost/usbhost_xhci_pci.c | 4872 +--------------------------- include/nuttx/usb/xhci.h | 105 + 7 files changed, 5059 insertions(+), 4778 deletions(-) create mode 100644 drivers/usbhost/usbhost_xhci.c create mode 100644 include/nuttx/usb/xhci.h 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..a5059b9088c53 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,14 @@ config USBHOST_XHCI_MAX_DEVS ---help--- How many USB devices will be supported by xHCI driver. -endif # USBHOST_XHCI_PCI +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_xhci.c b/drivers/usbhost/usbhost_xhci.c new file mode 100644 index 0000000000000..a63a5c99df3de --- /dev/null +++ b/drivers/usbhost/usbhost_xhci.c @@ -0,0 +1,4822 @@ +/**************************************************************************** + * 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 "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) + +/* 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) +#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 */ + 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 */ + + /* 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 */ + 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 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); + +/**************************************************************************** + * 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"); + 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); + + 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 */ + + 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; + + 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; + + /* 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) + { + 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 */ + + 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; + } + } + + 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) + { + uerr("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) + { + uerr("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) + { + 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! */ + + 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) + { + /* 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("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 */ + + 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? */ + + 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: + { + uinfo("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() */ + + 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"); + } + + /* 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 */ + + 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); + 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 */ + + /* 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 + 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 = 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) + { + uerr("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) + { + 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 */ + + 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) + { + 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); + 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) + { + uerr("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; + } + + uinfo("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); + + uinfo("no scratch = %d\n", priv->no_scratch); + + priv->no_erst = 1 << XHCI_HCSPARAMS2_ERST(regval); + + uinfo("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; + } + + 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 */ + + priv->devs[i].ctx = kmm_zalloc(sizeof(struct xhci_dev_ctx_s)); + 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), + sizeof(struct xhci_input_dev_ctx_s)); + 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; + } + + /* 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; +} + +/**************************************************************************** + * 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; + 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; + } + +#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 980f9924b1afb..7b2a5d7465592 100644 --- a/drivers/usbhost/usbhost_xhci.h +++ b/drivers/usbhost/usbhost_xhci.h @@ -27,6 +27,10 @@ #include +#include + +#include + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ diff --git a/drivers/usbhost/usbhost_xhci_pci.c b/drivers/usbhost/usbhost_xhci_pci.c index 2f296dc937740..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,466 +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) - -/* 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) -#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); @@ -492,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[] = @@ -532,4391 +122,133 @@ static struct pci_driver_s g_pci_xhci_drv = * 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 + * Name: pci_xhci_irq_attach * * Description: - * Put 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 void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint64_t value) +static int pci_xhci_irq_attach(FAR void *arg, xcpt_t handler, FAR void *priv) { - uintptr_t addr = priv->oper_base + offset; - - __asm__ __volatile__("" : "+r"(value)); - *((FAR volatile uint64_t *)addr) = value; -} + FAR struct pci_xhci_s *pcix = arg; + int ret; -/**************************************************************************** - * Name: xhci_runt_getreg - * - * Description: - * Get register (Host Controller Runtime Registers) - * - ****************************************************************************/ + ret = pci_alloc_irq(pcix->dev, &pcix->irq, 1); + if (ret != 1) + { + pcierr("Failed to allocate MSI %d\n", ret); + return ret; + } -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); + irq_attach(pcix->irq, handler, priv); - __asm__ __volatile__("" : "+r"(regval)); - return regval; -} + 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); -/**************************************************************************** - * Name: xhci_runt_putreg - * - * Description: - * Put register (Host Controller Runtime Registers) - * - ****************************************************************************/ + return -ENOTSUP; + } -static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint32_t value) -{ - uintptr_t addr = priv->runt_base + offset; + up_enable_irq(pcix->irq); - __asm__ __volatile__("" : "+r"(value)); - *((FAR volatile uint32_t *)addr) = value; + return OK; } /**************************************************************************** - * Name: xhci_runt_putreg_8b - * - * Description: - * Put register (Host Controller Runtime Registers) - * + * Name: pci_xhci_irq_detach ****************************************************************************/ -static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, - unsigned int offset, - uint64_t value) +static void pci_xhci_irq_detach(FAR void *arg) { - uintptr_t addr = priv->runt_base + offset; + FAR struct pci_xhci_s *pcix = arg; - __asm__ __volatile__("" : "+r"(value)); - *((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; - - __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) -{ - 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 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) - { - 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) -{ - 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); - } - - pcierr("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 */ - - 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) - { - pcierr("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: - { - 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) - { - /* 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 - { - 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 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) - { - 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/xhci.h b/include/nuttx/usb/xhci.h new file mode 100644 index 0000000000000..2368a5a9efe23 --- /dev/null +++ b/include/nuttx/usb/xhci.h @@ -0,0 +1,105 @@ +/**************************************************************************** + * 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); +}; + +/**************************************************************************** + * 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 */ From 463868005fa27e1aeb40eaeb65f0e71d1df1ac85 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 01:08:25 +0800 Subject: [PATCH 03/15] drivers/usbhost: Make the xHCI interrupt path work on any trigger. This driver has only ever run behind QEMU's message signalled interrupt, and it holds assumptions that are safe only there, and some not even there. It never silences the source. The handler reads the status, queues the work that will answer it, and returns with everything still asserted. On a level triggered line the interrupt controller sees the condition still true and raises it again at once, forever, and the work that would have cleared it never runs. Mask the interrupter on the way out and let the worker unmask when it is done. It acknowledges after walking the event ring rather than before. An event arriving during the walk sets the pending bit again, and clearing the bit afterwards discards it. That event is the last this controller will raise until something else happens, so whatever was waiting on it waits forever: transfers have no timeout, only commands do, and a lost transfer event is therefore a hang rather than an error. Acknowledge first; a spurious second pass over an empty ring costs nothing. It attaches the interrupt before there is anything to answer it with. The handler defers to a worker that walks the event ring, and the ring is not allocated until the controller is started, several steps later. A controller that a boot loader left running has an interrupt pending the instant the line is enabled. Attach after the start instead. Attaching late then loses the first interrupt behind a message, because a message is sent once, on the pending flag's transition from clear to set, and a flag raised while nobody was attached has already spent it. A wire is still asserted when the handler finally arrives, so it costs a wire nothing. Clear the status and pending flags once the handler is in place, so the next event is a fresh transition. The same rule governs the worker's unmask. Events that arrived while the interrupter was masked have left the pending flag set, and enabling with it still set gives a message nothing to transition on. Clear it in the same write, then drain the ring again: clearing can discard an event that arrived a moment earlier, and repeating until a drain comes back empty is the only state in which none was lost. And while reading ports, do not disable them. xhci_probe_ports() writes PORTSC back to clear the change bits, including PED, which is write-one-to-clear. A port that came up enabled, which is what a device attached at power up produces once the controller settles, is switched off by the act of looking at it. The port status worker already gets this right and says so in a comment; this path did not. Once the interrupts arrive at all, they arrive too late. The interrupter moderation interval is how long a controller waits after an event before reporting it, in 250ns units, and it resets to 4000, a full millisecond. The driver never wrote the register. A transfer therefore cost a millisecond before its completion was even reported, and mass storage spends three transfers on each request, so a request waited three milliseconds no matter how little it asked for. Measured on a Synopsys DWC3 with a USB 2.0 drive, timestamping from the doorbell to the interrupt: 986-1021us before, 13-56us after. reading 1MiB before after 512 byte blocks 166 KB/s 775 KB/s 32 KiB blocks 10666 KB/s 18618 KB/s mounting a FAT32 volume: 92.7s before, 21.1s after Set it to 160, which is 40us, rather than to zero. Zero puts no bound on how often a controller may interrupt: measured with a keyboard on an interrupt endpoint, it took interrupts continuously and spent an entire processor doing it. It is the same value Linux uses, for the same reason. Found on two controllers: the level triggered half on a Synopsys DWC3 whose PLIC line re-fired forever, the message signalled half on QEMU, where enumeration stopped dead after the port reset with no error and no further interrupts. With both, the same driver serves both. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 109 +++++++++++++++++++++++++++------ drivers/usbhost/usbhost_xhci.h | 11 ++++ 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index a63a5c99df3de..9d30f8b83ef64 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -1145,6 +1145,10 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) 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)); @@ -1295,8 +1299,13 @@ static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv) portsc = xhci_oper_getreg(priv, XHCI_PORTSC(i)); priv->rhport[i].connected = ((portsc & XHCI_PORTSC_CCS) != 0); - /* Clear status change */ + /* 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); } } @@ -2803,6 +2812,7 @@ static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) uintptr_t addr; uint8_t type; uint32_t d2; + int count = 0; /* Invalidate event ring */ @@ -2874,6 +2884,7 @@ static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) /* Next event */ + count++; priv->evnt.i++; /* Handle ring wrap */ @@ -2890,7 +2901,7 @@ static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) addr |= XHCI_ERDP_EHB; xhci_runt_putreg_8b(priv, XHCI_ERDP(0), addr); - return OK; + return count; } /**************************************************************************** @@ -2906,6 +2917,20 @@ 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 */ @@ -2938,21 +2963,32 @@ static void xhci_interrupt_work(FAR void *arg) uinfo("Host Controller Error\n"); } - /* ACK interrupts */ + /* Clear pending bits */ - xhci_oper_putreg(priv, XHCI_USBSTS, priv->pending); + priv->pending = 0; - /* Clear interrupter pending bit */ + /* 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. + */ - iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); - if (iman & XHCI_IMAN_IP) + do { - xhci_runt_putreg(priv, XHCI_IMAN(0), iman); + iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); + xhci_runt_putreg(priv, XHCI_IMAN(0), + iman | XHCI_IMAN_IE | XHCI_IMAN_IP); } - - /* Clear pending bits */ - - priv->pending = 0; + while (xhci_events_poll(priv) > 0); } /**************************************************************************** @@ -2966,11 +3002,23 @@ static void xhci_interrupt_work(FAR void *arg) 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)) @@ -4552,14 +4600,6 @@ static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv) goto errout; } - /* Configure interrupts */ - - ret = xhci_irq_initialize(priv); - if (ret < 0) - { - goto errout; - } - /* Halt controller */ ret = xhci_ctrl_halt(priv); @@ -4704,6 +4744,7 @@ xhci_initialize(FAR const char *name, uintptr_t base, { 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 && @@ -4763,6 +4804,34 @@ xhci_initialize(FAR const char *name, uintptr_t base, 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 diff --git a/drivers/usbhost/usbhost_xhci.h b/drivers/usbhost/usbhost_xhci.h index 7b2a5d7465592..f8539bb8f0833 100644 --- a/drivers/usbhost/usbhost_xhci.h +++ b/drivers/usbhost/usbhost_xhci.h @@ -319,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 */ From 4ebb10bc97400df648bc4ac3453611f444f31c71 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 01:14:08 +0800 Subject: [PATCH 04/15] drivers/usbhost: Fix xHCI cache maintenance and transfer limits. The controller moves every byte itself; there is no programmed-I/O path in xHCI to fall back to. On a machine whose caches are not coherent with it, and on one whose addresses are not flat, that makes cache maintenance and address validation part of getting a transfer right rather than an optimisation. This driver was doing neither, beyond its own descriptors. Its rings were published with up_flush_dcache_all() before the controller was pointed at them. That reads as thorough and is the opposite: an architecture whose cache can only be maintained by address implements the whole-cache variants as a barrier and nothing more, so the event ring segment table, the device context base address array and the scratchpad pointers were never written out at all. The controller then reads whatever those addresses held before, which presents as every command timing out with no events ever arriving, a failure that looks like the interrupt is missing rather than like the ring is unreadable. Flush each structure by name instead. xhci_ring_init() had the same shape of bug from the other direction: it clears the whole ring and then flushes one descriptor, the link entry it writes afterwards, leaving the rest of the clearing in the cache. This is memory the controller writes into itself, so a dirty line written back later lands on top of whatever the controller has put there since. What gets destroyed is an event somebody is waiting for, and the ring then looks permanently empty while the controller believes it has reported everything. Flush the whole ring, which is what the clearing was for. Data buffers got no maintenance whatsoever: nothing pushed before an OUT, nothing dropped after an IN. On a coherent host this cannot be seen, which is presumably why it survived. And the buffers a class driver hands down are not all its own to maintain. Cache operations work a whole line at a time because that is all the hardware offers, so dropping a line that a buffer only partly covers also drops whatever else lives in it, and writing one back over memory the controller has just filled destroys the transfer. Mass storage passes a thirty-one byte command block and a thirteen byte status straight out of its instance structure, sharing lines with everything around them. So a buffer that does not own its lines is copied through one that does, and only the small transfers ever need it: anything large comes from a filesystem or from xhci_ioalloc(), already aligned. While here, make xhci_ioalloc() round its length up as well as aligning its start, so what it returns owns its last line too. Also drop the device output context before reading the address out of it. The controller chose that address and wrote it there; reading without invalidating returns whatever the processor had cached, and the driver then addresses the device by a number it was never assigned. Separately, two ways a transfer could be programmed that the controller will not honour. A buffer that cannot be reached. The driver turns a caller's address into a physical one and hands it to the controller, and on a system with an address environment that translation is only meaningful for some addresses. A userspace buffer under CONFIG_BUILD_KERNEL is neither mapped address-for-address nor physically contiguous, so what the controller gets is a number that names the wrong memory. The transfer then completes successfully, having read or written somewhere else entirely. That is the worst shape a fault can take, and it is indistinguishable from a cache problem when the data comes back wrong. Whether an address can be used this way is a property of the system the controller was fitted into, not of the controller, so it is asked rather than assumed: a platform may supply dmacapable, and one that does not is taken to mean every address works, which is what every existing user has. A buffer that is refused gives -EFAULT, which is a redirection rather than a failure: the FAT filesystem answers it by retrying through its own DMA-safe sector buffer, and the read succeeds by DMA either way. And transfers longer than one descriptor can describe. A Normal TRB carries one run of memory that may not cross a 64K boundary, while the block layer above hands down whole multi-sector reads whose length is bounded by nothing here. A single TRB was being programmed regardless, so a long enough read, or merely one starting near the wrong side of a boundary, produced a descriptor the controller is entitled to reject or to satisfy partly. Chain as many as the run needs instead, asking for the completion interrupt only on the last so that one event still arrives for the whole transfer. The cache half compiles to nothing on an architecture with no cache to maintain, and dmacapable is NULL on PCI, so the existing user is unaffected. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 312 +++++++++++++++++++++++++++++++-- include/nuttx/usb/xhci.h | 7 + 2 files changed, 306 insertions(+), 13 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 9d30f8b83ef64..c8675e7b8b146 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include @@ -142,6 +144,9 @@ struct xhci_epinfo_s 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 */ + bool dmain; /* Direction this buffer was prepared for */ sem_t iocsem; /* Semaphore used to wait for transfer completion */ #ifdef CONFIG_USBHOST_ASYNCH usbhost_asynch_t callback; /* Transfer complete callback */ @@ -411,6 +416,12 @@ static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, #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 FAR uint8_t *xhci_dma_prepare(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 *******************************************************/ @@ -812,9 +823,16 @@ static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len) ring->len = len; } - /* Reset data in ring */ + /* 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 */ @@ -1116,9 +1134,22 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) evnt->size = XHCI_EVENT_MAX; evnt->res = 0; - /* Flush all memory before write to ERDP so xhci sees correct data */ + /* 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_all(); + 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)); @@ -1155,9 +1186,12 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) regval |= XHCI_IMAN_IE; xhci_runt_putreg(priv, XHCI_IMAN(0), regval); - /* Flush all memory once again */ + /* And the command ring, whose last entry was just made to point back at + * its own beginning. + */ - up_flush_dcache_all(); + 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 */ @@ -2279,6 +2313,13 @@ static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, if (buffer) { + buffer = xhci_dma_prepare(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); @@ -2346,15 +2387,64 @@ static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); struct xhci_trb_s trb; - /* Prepare TRB */ + size_t left; + size_t chunk; + uintptr_t pa; + int n = 0; - 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); + /* Make the buffer safe for the controller to reach */ - /* Add TRBs to ring */ + buffer = xhci_dma_prepare(epinfo, buffer, buflen, epinfo->dirin != 0); + if (buffer == NULL) + { + return -ENOMEM; + } - xhci_add_trb(priv, &epinfo->td, &trb, 1); + /* 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 needs more TRBs than the ring holds\n", + buflen); + 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 */ @@ -2386,6 +2476,14 @@ static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, 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(epinfo, buffer, buflen, epinfo->dirin != 0); + if (buffer == NULL) + { + return -ENOMEM; + } + /* Prepare TRB */ trb.d0 = up_addrenv_va_to_pa(buffer); @@ -2679,6 +2777,152 @@ static void xhci_portsc_work(FAR void *arg) } } +/**************************************************************************** + * 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 xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen, + bool dirin) +{ + size_t line = up_get_dcache_linesize(); + + epinfo->buffer = buffer; + epinfo->bounce = NULL; + epinfo->dmain = dirin; + + /* No cache to maintain, so nothing to arrange */ + + if (line == 0) + { + return buffer; + } + + if (((uintptr_t)buffer & (line - 1)) != 0 || (buflen & (line - 1)) != 0) + { + /* The buffer shares a line with something else. Work in a stand-in + * that does not. + */ + + epinfo->bounce = kmm_memalign(line, (buflen + line - 1) & ~(line - 1)); + 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 + buflen); + } + else + { + up_clean_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); + } + + return buffer; +} + +/**************************************************************************** + * Name: xhci_dma_finish + * + * Description: + * Read back what the controller wrote, and give up any stand-in buffer. + * Called on completion, before whoever is waiting is woken. + * + ****************************************************************************/ + +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->buflen); + + if (epinfo->bounce != NULL && epinfo->buffer != NULL) + { + memcpy(epinfo->buffer, epinfo->bounce, epinfo->buflen); + } + } + + if (epinfo->bounce != NULL) + { + kmm_free(epinfo->bounce); + epinfo->bounce = NULL; + } + + epinfo->buffer = NULL; +} + /**************************************************************************** * Name: xhci_transfer_complete * @@ -2702,6 +2946,10 @@ static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, epinfo = priv->devs[slot - 1].epinfo[ep - 1]; DEBUGASSERT(epinfo != NULL); + /* Read back what the controller wrote before anyone looks at it */ + + xhci_dma_finish(epinfo); + flags = spin_lock_irqsave(&priv->spinlock); /* Get transferred length */ @@ -3677,7 +3925,8 @@ 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) { - int ret = -ENOMEM; + size_t line; + int ret = -ENOMEM; DEBUGASSERT(drvr && buffer && buflen > 0); @@ -3688,7 +3937,18 @@ static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, return -ENOMEM; } - /* Allocated buffer must not cross page boundaries */ + /* 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) @@ -3787,6 +4047,13 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, len = xhci_getle16(req->len); + /* Refuse a buffer the controller cannot reach, as for bulk transfers */ + + if (buffer != NULL && len > 0 && !xhci_dmacapable(priv, buffer, len)) + { + return -EFAULT; + } + /* Terse output only if we are tracing */ #ifdef CONFIG_USBHOST_TRACE @@ -3810,6 +4077,15 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, ret = xhci_address_set(priv, rhport, true); if (ret == OK) { + /* 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)rhport->dev->ctx, + (uintptr_t)rhport->dev->ctx + + sizeof(struct xhci_dev_ctx_s)); + /* Store USB Device Address assigned by xHCI */ ep0info->devaddr = @@ -3950,6 +4226,16 @@ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); + /* Refuse a buffer the controller cannot reach rather than pointing it at + * the wrong memory. A caller that has somewhere better to put the data + * will try again with it; the FAT filesystem does exactly that. + */ + + if (!xhci_dmacapable(priv, buffer, buflen)) + { + return -EFAULT; + } + /* We must have exclusive access to the xHCI hardware and data * structures. */ diff --git a/include/nuttx/usb/xhci.h b/include/nuttx/usb/xhci.h index 2368a5a9efe23..1010811244ba6 100644 --- a/include/nuttx/usb/xhci.h +++ b/include/nuttx/usb/xhci.h @@ -62,6 +62,13 @@ struct xhci_bus_ops_s /* 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); }; /**************************************************************************** From 933269f62c0d12d9ad7e905860f64eac00910a8a Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 01:19:52 +0800 Subject: [PATCH 05/15] drivers/usbhost: Describe devices to an xHCI controller correctly. Everything a controller is told about a device before it will accept it, found by bringing this driver up on a Synopsys DWC3 core, which checks what QEMU's controller does not. Contexts came in one size only. A controller says in HCCPARAMS1 whether its context structures are thirty-two or sixty-four bytes, and this driver refused the wider form outright with -EIO, so it worked on exactly the controllers reporting the narrower one. QEMU's is one; DWC3 cores are not, and the EIC7700X reports HCCPARAMS1 = 0x0220fe45 on both of its controllers, so this driver could not have driven either. The difference is smaller than the refusal suggests: a wide context is the same fields with reserved space after them, so nothing about the layout changes, only the distance from one entry to the next. Read the size at start up and let the three places that walk a context array use it. Contexts also have to be allocated aligned; every entry of the device context base address array points at one and must be 64 byte aligned, and the output context was coming from kmm_zalloc(), which promises nothing of the sort. The event ring segment table came out sized zero. How many segments a controller allows is a power of two reported as its exponent, and the exponent can be 15, so computing 1 << exponent into the uint8_t that holds it wraps to zero on any controller offering more than 128. A controller told its event ring table holds no entries has nowhere to report anything: every command times out, and the first thing to notice is a host controller error with no explanation. Work it out at full width and narrow it afterwards. The slot context never carried the device speed. It is the only place a controller is told how fast the device it is about to address runs, and the field has no meaningful zero, so the context described nothing and a controller that validates it answers Address Device with a parameter error rather than guessing. The speed was known: the endpoint context built next to it had the right maximum packet size all along. The numbering is xHCI's own and unrelated to the values the USB host stack uses, hence the mapping. The output device context was cleared and never flushed. That context is the controller's to write, which is exactly why clearing it has to reach memory: what stays behind is a dirty line of zeros that the processor writes back whenever it next needs the line, on top of whatever the controller has put there since. The slot state lives in that context, so what is destroyed is the record of the device having been addressed at all, and the next command against the slot is refused with a context state error. The symptom is enumeration reaching SET_ADDRESS and stopping. A buffer copied through an aligned stand-in was copied back using the wrong length. buflen means the length of a data transfer and control transfers deliberately leave it zero, so a descriptor read copied nothing back and the caller was handed whatever its buffer held before. That reads as a device returning nonsense, and is followed by the endpoint being configured with a garbage maximum packet size. Keep the requested length separately. Cache maintenance on such a buffer is also rounded to the whole of it rather than the part in use, since the operation works a line at a time and an architecture may reasonably refuse a range stopping part way through one. A buffer the controller cannot reach is now copied rather than refused. Answering -EFAULT works for a caller with somewhere better to put the data, which the FAT filesystem has, and fails outright for one without: reading a block device directly from a user program returned an error where the transfer could simply have gone through a stand-in. Two diagnostics while here. The register dump read HCIVERSION with a 32-bit access at offset two; it is a 16-bit register sharing a word with CAPLENGTH, so that is an unaligned read of a device register, harmless where the bus permits it and a fault where it does not. And a rejected command now says which command it was: the difference between a refused Address Device and a refused Evaluate Context is most of the diagnosis, and the completion code alone does not give it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 322 +++++++++++++++++++++++++-------- drivers/usbhost/usbhost_xhci.h | 16 ++ 2 files changed, 264 insertions(+), 74 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index c8675e7b8b146..6644c81775580 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,14 @@ */ #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 */ @@ -146,6 +155,8 @@ struct xhci_epinfo_s 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 */ #ifdef CONFIG_USBHOST_ASYNCH @@ -251,6 +262,7 @@ struct usbhost_xhci_s 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 */ @@ -418,7 +430,17 @@ 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 FAR uint8_t *xhci_dma_prepare(FAR struct xhci_epinfo_s *epinfo, +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); @@ -733,8 +755,17 @@ static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, uinfo("Dump xHCI registers: %s\n", msg); uinfo("=== Host Controller Capability Registers ===\n"); - xhci_dump_capa_reg(priv, "CAPLENGTH ", XHCI_CAPLENGTH); - xhci_dump_capa_reg(priv, "HCIVERSION ", XHCI_HCIVERSION); + + /* 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); @@ -1609,7 +1640,7 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, * Initialize all fields to 0. */ - memset(dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); + 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). @@ -1619,9 +1650,16 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, XHCI_IN_CTX1_A(XHCI_EP0_FLAG); xhci_context_ctrl(priv, dev, 0, regval); - /* Step 3. Initialize the Input Slot Context */ + /* 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); + regval = XHCI_ST_CTX0_CTXENT_SET(1) | + XHCI_ST_CTX0_SPEED_SET( + xhci_speed_id(dev->rhport->hport.hport.speed)); #ifdef CONFIG_USBHOST_HUB /* TODO: @@ -1633,7 +1671,7 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, # warning missing logic #endif - dev->input->slot.ctx[0] = htole32(regval); + xhci_in_slot(priv, dev->input)->ctx[0] = htole32(regval); /* Configure Root Hub Port Number (starts from 1) */ @@ -1642,7 +1680,7 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, /* TODO: configure number of ports */ regval |= XHCI_ST_CTX1_PORTS_SET(0); - dev->input->slot.ctx[1] = htole32(regval); + xhci_in_slot(priv, dev->input)->ctx[1] = htole32(regval); /* Step 4. the Transfer Ring for the Default Control Endpoint is already * allocated. @@ -1668,7 +1706,7 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, DEBUGASSERT(drdp != 0); xhci_ep_configure(priv, - &dev->input->ep[0], + xhci_in_ep(priv, dev->input, 0), XHCI_EPTYPE_CTRL, maxpkt, 0, drdp, 0, 0); @@ -1677,13 +1715,22 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, * Initialize all fields to 0. */ - memset(dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); + 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. + */ - /* Flush Device input context */ + 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 + - sizeof(struct xhci_input_dev_ctx_s)); + 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 @@ -1821,8 +1868,15 @@ static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, 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)); + memset(rhport->dev->ctx, 0, XHCI_DEVCTX_SIZE(priv)); + memset(rhport->dev->input, 0, XHCI_INCTX_SIZE(priv)); + + /* And push both, so nothing is left to be written back later */ + + up_flush_dcache((uintptr_t)rhport->dev->ctx, + (uintptr_t)rhport->dev->ctx + XHCI_DEVCTX_SIZE(priv)); + up_flush_dcache((uintptr_t)rhport->dev->input, + (uintptr_t)rhport->dev->input + XHCI_INCTX_SIZE(priv)); /* Remove reference to a device slot */ @@ -1891,8 +1945,8 @@ static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, } } - dev->input->slot.ctx[0] &= ~XHCI_ST_CTX0_CTXENT_MASK; - dev->input->slot.ctx[0] |= XHCI_ST_CTX0_CTXENT_SET(i); + 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); } /**************************************************************************** @@ -1910,7 +1964,8 @@ static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, static int xhci_command(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *trb, uint16_t timeout_ms) { - int ret; + uint32_t cmdtype; + int ret; /* Lock bus */ @@ -1920,6 +1975,10 @@ static int xhci_command(FAR struct usbhost_xhci_s *priv, 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); @@ -1955,7 +2014,8 @@ static int xhci_command(FAR struct usbhost_xhci_s *priv, } else { - uerr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1)); + uerr("command type %d failed, CC = %d\n", cmdtype, + XHCI_TRB_D1_CC_GET(trb->d1)); ret = -EIO; } @@ -2313,7 +2373,7 @@ static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, if (buffer) { - buffer = xhci_dma_prepare(epinfo, buffer, buflen, + buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen, (req->type & USB_REQ_DIR_IN) != 0); if (buffer == NULL) { @@ -2394,7 +2454,8 @@ static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, /* Make the buffer safe for the controller to reach */ - buffer = xhci_dma_prepare(epinfo, buffer, buflen, epinfo->dirin != 0); + buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen, + epinfo->dirin != 0); if (buffer == NULL) { return -ENOMEM; @@ -2420,8 +2481,8 @@ static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, if (++n >= XHCI_TD_MAX) { - uerr("transfer of %zu needs more TRBs than the ring holds\n", - buflen); + uerr("transfer of %zu from pa %" PRIxPTR " needs more than %d " + "TRBs\n", buflen, pa, XHCI_TD_MAX); return -EINVAL; } @@ -2478,7 +2539,8 @@ static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, /* Make the buffer safe for the controller to reach */ - buffer = xhci_dma_prepare(epinfo, buffer, buflen, epinfo->dirin != 0); + buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen, + epinfo->dirin != 0); if (buffer == NULL) { return -ENOMEM; @@ -2777,6 +2839,82 @@ static void xhci_portsc_work(FAR void *arg) } } +/**************************************************************************** + * 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_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 * @@ -2831,30 +2969,53 @@ static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv, * ****************************************************************************/ -static FAR uint8_t *xhci_dma_prepare(FAR struct xhci_epinfo_s *epinfo, +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(); + 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->buffer = buffer; - epinfo->bounce = NULL; - epinfo->dmain = dirin; + epinfo->dmacopy = buflen; + epinfo->dmain = dirin; - /* No cache to maintain, so nothing to arrange */ + /* Nothing to arrange: no cache to maintain, and an address the + * controller can be pointed at as it stands. + */ - if (line == 0) + if (line == 0 && reachable) { return buffer; } - if (((uintptr_t)buffer & (line - 1)) != 0 || (buflen & (line - 1)) != 0) + if (!reachable || + ((uintptr_t)buffer & (line - 1)) != 0 || (buflen & (line - 1)) != 0) { /* The buffer shares a line with something else. Work in a stand-in * that does not. */ - epinfo->bounce = kmm_memalign(line, (buflen + line - 1) & ~(line - 1)); + /* 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; @@ -2875,11 +3036,13 @@ static FAR uint8_t *xhci_dma_prepare(FAR struct xhci_epinfo_s *epinfo, if (dirin) { - up_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); + up_invalidate_dcache((uintptr_t)buffer, + (uintptr_t)buffer + epinfo->dmalen); } else { - up_clean_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); + up_clean_dcache((uintptr_t)buffer, + (uintptr_t)buffer + epinfo->dmalen); } return buffer; @@ -2906,11 +3069,12 @@ static void xhci_dma_finish(FAR struct xhci_epinfo_s *epinfo) if (dirin) { - up_invalidate_dcache((uintptr_t)dma, (uintptr_t)dma + epinfo->buflen); + up_invalidate_dcache((uintptr_t)dma, + (uintptr_t)dma + epinfo->dmalen); if (epinfo->bounce != NULL && epinfo->buffer != NULL) { - memcpy(epinfo->buffer, epinfo->bounce, epinfo->buflen); + memcpy(epinfo->buffer, epinfo->bounce, epinfo->dmacopy); } } @@ -3546,8 +3710,11 @@ static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, { /* 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); + FAR struct xhci_ep_ctx_s *ep0ctx = + xhci_in_ep(priv, rhport->dev->input, 0); + + ep0ctx->ctx1 &= ~XHCI_EP_CTX1_MAXPKT_MASK; + ep0ctx->ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpacketsize); /* Add Slot Context and EP0 Context */ @@ -3559,13 +3726,17 @@ static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, up_flush_dcache((uintptr_t)rhport->dev->input, (uintptr_t)rhport->dev->input + - sizeof(struct xhci_input_dev_ctx_s)); + XHCI_INCTX_SIZE(priv)); /* Free mutex before command execution */ nxmutex_unlock(&priv->lock); ctx = up_addrenv_va_to_pa(rhport->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); } @@ -3616,6 +3787,12 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, && ep != NULL); hport = epdesc->hport; + /* Only the tracing alternative below and the hub logic further down use + * this, and a configuration may have neither. + */ + + UNUSED(hport); + /* Terse output only if we are tracing */ #ifdef CONFIG_USBHOST_TRACE @@ -3736,7 +3913,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, * Max Burst Size set for 0 for now (USB3.0 specific) */ - xhci_ep_configure(priv, &dev->input->ep[idx - 1], + 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); @@ -3747,7 +3924,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, up_flush_dcache((uintptr_t)dev->input, (uintptr_t)dev->input + - sizeof(struct xhci_input_dev_ctx_s)); + XHCI_INCTX_SIZE(priv)); /* Configure EP */ @@ -4047,13 +4224,6 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, len = xhci_getle16(req->len); - /* Refuse a buffer the controller cannot reach, as for bulk transfers */ - - if (buffer != NULL && len > 0 && !xhci_dmacapable(priv, buffer, len)) - { - return -EFAULT; - } - /* Terse output only if we are tracing */ #ifdef CONFIG_USBHOST_TRACE @@ -4084,13 +4254,14 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, up_invalidate_dcache((uintptr_t)rhport->dev->ctx, (uintptr_t)rhport->dev->ctx + - sizeof(struct xhci_dev_ctx_s)); + XHCI_DEVCTX_SIZE(priv)); /* 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]; + XHCI_ST_CTX3_ADDR_GET(xhci_out_slot(rhport->dev->ctx)->ctx[3]); + xhci_in_slot(priv, rhport->dev->input)->ctx[3] = + xhci_out_slot(rhport->dev->ctx)->ctx[3]; } return OK; @@ -4226,16 +4397,6 @@ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); - /* Refuse a buffer the controller cannot reach rather than pointing it at - * the wrong memory. A caller that has somewhere better to put the data - * will try again with it; the FAT filesystem does exactly that. - */ - - if (!xhci_dmacapable(priv, buffer, buflen)) - { - return -EFAULT; - } - /* We must have exclusive access to the xHCI hardware and data * structures. */ @@ -4600,15 +4761,18 @@ static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, 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); - if (regval & XHCI_HCCPARAMS1_CSZ) - { - uerr("Only 32 byte Context data structures supported!\n"); - return -EIO; - } + priv->ctxsize = (regval & XHCI_HCCPARAMS1_CSZ) ? 64 : 32; + + uinfo("context size = %d\n", priv->ctxsize); /* Get data from Structural Parameters 1 register */ @@ -4640,16 +4804,21 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv) uinfo("no scratch = %d\n", priv->no_scratch); - priv->no_erst = 1 << XHCI_HCSPARAMS2_ERST(regval); + /* 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("no_erst = %d\n", priv->no_erst); + uinfo("erst max = %" PRIu32 "\n", erst); /* Limit event ring segment table to 1 */ - if (priv->no_erst > XHCI_MAX_ERST) - { - priv->no_erst = XHCI_MAX_ERST; - } + priv->no_erst = (erst > XHCI_MAX_ERST) ? XHCI_MAX_ERST : erst; uinfo("no erst = %d\n", priv->no_erst); @@ -4773,7 +4942,12 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) { /* Allocate Device Context */ - priv->devs[i].ctx = kmm_zalloc(sizeof(struct xhci_dev_ctx_s)); + /* 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"); @@ -4785,7 +4959,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) */ priv->devs[i].input = kmm_memalign((XHCI_PAGE_SIZE / 2), - sizeof(struct xhci_input_dev_ctx_s)); + XHCI_INCTX_SIZE(priv)); if (!priv->devs[i].input) { uerr("dev input zalloc failed!\n"); diff --git a/drivers/usbhost/usbhost_xhci.h b/drivers/usbhost/usbhost_xhci.h index f8539bb8f0833..06b901ccc1b04 100644 --- a/drivers/usbhost/usbhost_xhci.h +++ b/drivers/usbhost/usbhost_xhci.h @@ -510,6 +510,22 @@ #define XHCI_ST_CTX0_RTSTR_MASK (0xfffff << XHCI_ST_CTX0_RTSTR_SHIFT) #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 */ From 15dd7d2fcbd0f2f00aceea5bffca14ca3d92d4bf Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 11:17:54 +0800 Subject: [PATCH 06/15] drivers/usbhost: Make the xHCI data path work with a real device. Four things, all found by driving a USB drive and a keyboard on a DWC3 controller, and all invisible on a coherent host with a flat address space. A link TRB inside a transfer was not chained. A transfer described by more than one TRB can reach the end of the ring part way through, and the link that sends the controller back to the beginning is then inside the transfer rather than between two of them. Written without the chain bit, that link ends the transfer where it stands: the controller follows it, considers the work finished, and reports nothing, because the descriptor that asked for the completion interrupt is on the far side of the join and is never reached. Nothing waiting is woken, and transfers have no timeout, so the symptom is a read that never returns. It only appears once transfers need more than one descriptor. The effect is not subtle. Reading a megabyte from a USB drive: 512 byte blocks 166 KB/s 4 KiB blocks 1333 KB/s 32 KiB blocks 10666 KB/s 64 KiB blocks 15515 KB/s Before the fix the last two did not complete at all. The same board reads its SD card at 16000 KB/s and its eMMC at 42666 KB/s, so a USB 2.0 drive now sits where it ought to between them. A stand-in buffer was copied back in the wrong context. That work was being done in the completion handler, which runs on a work queue, while the buffer it copies into may belong to a user process whose addresses mean nothing there. Reading a block device directly from a user program faulted. The caller is blocked until the transfer finishes anyway, so the copy belongs there instead. An asynchronous transfer cannot use a stand-in at all, for the same reason: there is no caller to come back to, and the copy would have to happen in the completion. Refuse a buffer that would need one. The callers of that path are class drivers using kernel memory, which do not. And say what is attached. A controller now reports each device once, by name, as it comes up, and from the end of the port enable rather than at connect, because the speed field in PORTSC only means anything once the port has been reset: before that a USB2 port reports its reset default, which reads as full speed, and every device would be announced at 12Mbps regardless of what it negotiates a moment later. Verified with both at once: a high speed drive on one controller announces 480Mbps while a low speed keyboard on the other announces 1.5Mbps. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 127 +++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 6644c81775580..08af5a6d2a15b 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -358,6 +359,7 @@ 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); @@ -431,6 +433,10 @@ static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, 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); +#ifdef CONFIG_USBHOST_ASYNCH +static bool xhci_dma_direct(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *buffer, size_t buflen); +#endif 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); @@ -994,6 +1000,19 @@ static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, 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); @@ -1487,6 +1506,16 @@ static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, } } + /* 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; } @@ -2723,6 +2752,36 @@ static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo) } #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 * @@ -2802,6 +2861,9 @@ static void xhci_portsc_work(FAR void *arg) 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? */ @@ -2941,6 +3003,32 @@ static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv, return priv->ops->dmacapable(priv->arg, buffer, buflen); } +#ifdef CONFIG_USBHOST_ASYNCH +/**************************************************************************** + * Name: xhci_dma_direct + * + * Description: + * Whether the controller can be pointed straight at this buffer, with no + * stand-in needed: an address it can reach, owning whole cache lines. + * + ****************************************************************************/ + +static bool xhci_dma_direct(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *buffer, size_t buflen) +{ + size_t line = up_get_dcache_linesize(); + + if (!xhci_dmacapable(priv, buffer, buflen)) + { + return false; + } + + return line == 0 || + (((uintptr_t)buffer & (line - 1)) == 0 && + (buflen & (line - 1)) == 0); +} +#endif + /**************************************************************************** * Name: xhci_dma_prepare * @@ -3003,6 +3091,8 @@ static FAR uint8_t *xhci_dma_prepare(FAR struct usbhost_xhci_s *priv, 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. */ @@ -3053,7 +3143,13 @@ static FAR uint8_t *xhci_dma_prepare(FAR struct usbhost_xhci_s *priv, * * Description: * Read back what the controller wrote, and give up any stand-in buffer. - * Called on completion, before whoever is waiting is woken. + * + * 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. * ****************************************************************************/ @@ -3110,10 +3206,6 @@ static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, epinfo = priv->devs[slot - 1].epinfo[ep - 1]; DEBUGASSERT(epinfo != NULL); - /* Read back what the controller wrote before anyone looks at it */ - - xhci_dma_finish(epinfo); - flags = spin_lock_irqsave(&priv->spinlock); /* Get transferred length */ @@ -4299,6 +4391,11 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, /* 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); + return nbytes >= 0 ? OK : (int)nbytes; errout_with_iocwait: @@ -4458,6 +4555,13 @@ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, /* 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); + return nbytes; errout_with_iocwait: @@ -4515,6 +4619,19 @@ static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); + /* An asynchronous transfer has no caller to come back to, so a buffer + * needing a stand-in cannot be used: the copy back out of it would have + * to happen in the completion handler, which runs in a work queue thread + * where a caller's address means nothing. The callers of this are class + * drivers using kernel memory, which needs no stand-in. + */ + + if (!xhci_dma_direct(priv, buffer, buflen)) + { + uerr("ERROR: asynchronous transfer needs a directly usable buffer\n"); + return -EFAULT; + } + /* We must have exclusive access to the xHCI hardware and data * structures. */ From 44a68892592ae8daddbc2b075296733e207e811c Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 19:16:56 +0800 Subject: [PATCH 07/15] drivers/usbhost: Fix xHCI endpoint allocation for an interrupt endpoint. Two faults a low speed keyboard meets in succession, the second only reachable because of the first. The Interval field of an endpoint context 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 be copied across, which is what this did. A low or full speed interrupt endpoint counts in frames, so a keyboard asking to be polled every 10ms was programmed as 2^10 microframes, an interval it never agreed to and one the controller would not accept: the Configure Endpoint command went unanswered, endpoint allocation failed with -EIO, and the keyboard never enumerated. Convert instead. Low and full speed interrupt endpoints state a period in frames, so the exponent is the highest bit of that period in microframes, kept inside the range the specification allows. Everything else 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. A root hub port whose enumeration failed that way is then enumerated again, and the slot the failed attempt was using has been given back before that happens, so the port has no device context behind it any more. xhci_epalloc() took that pointer and wrote the new endpoint through it without looking. Storing through NULL costs the whole system, and it does it in answer to a device that merely failed to come up: the first attempt reports the error correctly and the retry then panics the kernel. Check for the device, and give back the endpoint that has no home rather than leaking it. With both, a low speed keyboard configures its interrupt endpoint and enumerates, where before it failed every time and took the system down on the retry. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 85 ++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 08af5a6d2a15b..a8b8eef87b199 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -3835,6 +3835,68 @@ static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, 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 * @@ -3913,16 +3975,31 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, epinfo->epno = epdesc->addr; #ifndef CONFIG_USBHOST_INT_DISABLE - epinfo->interval = epdesc->interval; + epinfo->interval = xhci_interval(hport->speed, epdesc->xfrtype, + 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; + idx = xhci_epno_get(epinfo); + mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx)); + dev = rhport->dev; + + /* 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 root hub port with nothing behind it. + */ + + if (dev == NULL) + { + uerr("no device on port %d\n", RHPNDX(rhport)); + nxsem_destroy(&epinfo->iocsem); + kmm_free(epinfo); + return -ENODEV; + } + dev->epinfo[idx - 1] = epinfo; /* TD rings already allocated but not connected yet. */ From fcf7033e550bcc26abfe5cf015526d1d1f26964d Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 5 Aug 2026 23:31:26 +0800 Subject: [PATCH 08/15] drivers/usbhost: Make xHCI asynchronous transfers deliver their data. An asynchronous transfer never completed. Submitting one refused any buffer that would need a cache line stand-in, and the test for that also refuses every buffer whose length is not a whole number of cache lines, which an interrupt transfer's essentially never is: a HID keyboard reads eight bytes at a time. Every submission came straight back with -EFAULT before a descriptor was written. The refusal is silent in a normal build, because the only report of it is error logging that is usually compiled out, and a class driver in its interrupt-driven mode resubmits from the completion callback, so the first refusal is also the last word. A keyboard enumerated, registered its devices, and never produced a byte. The refusal was there because the completion side had nowhere to bring the data back: the copy out of a stand-in is done by the blocked caller, and an asynchronous transfer has no blocked caller. But the work queue thread that handles the completion is a fine place for it. A buffer given to DRVR_ASYNCH must come from DRVR_ALLOC, which is kernel memory reachable from any thread, so the addresses mean the same thing there as in the submitting context. Let the submission use the same stand-in machinery as every other transfer, and finish the DMA in the completion, just before the callback: invalidate, copy back, give up the stand-in. A transfer that is cancelled instead gives its stand-in back on cancellation. The callback also moves outside the spinlock. It is class driver code: it queues work and takes locks of its own, and the completion must now also be free to return a stand-in to the heap, none of which has any business happening with interrupts masked. Whether a completion is synchronous or asynchronous is still decided under the lock, because the moment a synchronous waiter is posted the endpoint may be carrying a new transfer, and that one is not complete. The byte count went the same way. The completion callback is handed the number of bytes transferred, worked out from the residue in the transfer event and the length that was asked for, and only the synchronous setup recorded that length; the asynchronous one left whatever was there from before, which is nothing on an endpoint that has only ever carried asynchronous transfers. The HID keyboard class gets away with a zero, because it treats any non-negative count as a report worth parsing and reads the buffer regardless, but the count is part of what DRVR_ASYNCH promises. Record the length in the asynchronous setup exactly as the synchronous setup does. Verified on QEMU with a keyboard and a drive on the same bus: keys injected from the monitor arrive through /dev/kbda while the drive mounts and reads back its file. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 124 ++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index a8b8eef87b199..87c27c6d69f82 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -412,9 +412,11 @@ 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 xhci_epinfo_s *epinfo); +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, @@ -433,10 +435,6 @@ static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, 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); -#ifdef CONFIG_USBHOST_ASYNCH -static bool xhci_dma_direct(FAR struct usbhost_xhci_s *priv, - FAR uint8_t *buffer, size_t buflen); -#endif 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); @@ -2653,6 +2651,8 @@ static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, * 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. * @@ -2666,6 +2666,7 @@ static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, 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) { @@ -2688,6 +2689,7 @@ static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, 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 */ @@ -2702,10 +2704,11 @@ static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, * Name: xhci_asynch_completion * * Description: - * This function is called at the interrupt level when an asynchronous - * transfer completes. It performs the pending callback. + * 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. * @@ -2713,21 +2716,26 @@ static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, * None * * Assumptions: - * - Called from the interrupt level + * - Called from the work queue, without the spinlock held * ****************************************************************************/ -static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo) +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 && - epinfo->callback != NULL); + DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false); + + /* Extract and reset the callback info, atomically against a concurrent + * cancellation. + */ - /* Extract and reset the callback info */ + flags = spin_lock_irqsave(&priv->spinlock); callback = epinfo->callback; arg = epinfo->arg; @@ -2739,6 +2747,23 @@ static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo) 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. */ @@ -3003,32 +3028,6 @@ static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv, return priv->ops->dmacapable(priv->arg, buffer, buflen); } -#ifdef CONFIG_USBHOST_ASYNCH -/**************************************************************************** - * Name: xhci_dma_direct - * - * Description: - * Whether the controller can be pointed straight at this buffer, with no - * stand-in needed: an address it can reach, owning whole cache lines. - * - ****************************************************************************/ - -static bool xhci_dma_direct(FAR struct usbhost_xhci_s *priv, - FAR uint8_t *buffer, size_t buflen) -{ - size_t line = up_get_dcache_linesize(); - - if (!xhci_dmacapable(priv, buffer, buflen)) - { - return false; - } - - return line == 0 || - (((uintptr_t)buffer & (line - 1)) == 0 && - (buflen & (line - 1)) == 0); -} -#endif - /**************************************************************************** * Name: xhci_dma_prepare * @@ -3200,6 +3199,9 @@ static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, 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 */ @@ -3261,17 +3263,32 @@ static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, } #ifdef CONFIG_USBHOST_ASYNCH - /* No.. Is there a pending asynchronous transfer? */ + /* 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 if (epinfo->callback != NULL) + else { - /* Yes.. perform the callback */ - - xhci_asynch_completion(epinfo); + 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 } /**************************************************************************** @@ -4696,19 +4713,6 @@ static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); - /* An asynchronous transfer has no caller to come back to, so a buffer - * needing a stand-in cannot be used: the copy back out of it would have - * to happen in the completion handler, which runs in a work queue thread - * where a caller's address means nothing. The callers of this are class - * drivers using kernel memory, which needs no stand-in. - */ - - if (!xhci_dma_direct(priv, buffer, buflen)) - { - uerr("ERROR: asynchronous transfer needs a directly usable buffer\n"); - return -EFAULT; - } - /* We must have exclusive access to the xHCI hardware and data * structures. */ @@ -4721,7 +4725,7 @@ static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, /* Set the request for the callback well BEFORE initiating the transfer. */ - ret = xhci_ioc_async_setup(rhport, epinfo, callback, arg); + ret = xhci_ioc_async_setup(rhport, epinfo, buflen, callback, arg); if (ret != OK) { goto errout_with_lock; @@ -4861,9 +4865,13 @@ static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) else { - /* Yes.. perform the callback */ + /* 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 From d4d19a26b80ef63ee1c5ad05932b2dd34f113cf9 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 11:55:27 +0800 Subject: [PATCH 09/15] drivers/usbhost: Serialise xHCI transfers per endpoint. xhci_ctrl_xfer() and xhci_transfer() release the controller lock before calling xhci_transfer_wait(), so the lock does not cover the interval in which a transfer is outstanding. Two threads issuing requests on the same endpoint therefore both reach xhci_ioc_setup(), and the second one trips the DEBUGASSERT(!epinfo->iocwait) that guards it. Where the assertion is compiled out the second thread overwrites the first thread's completion state instead. A device's default control endpoint reaches this readily. Every interface driver on a composite device speaks through endpoint 0, so a two interface HID keyboard runs two poll threads that both issue GET_REPORT there. Every other host controller driver in the tree holds its controller lock across the wait. Doing that here would serialise the whole controller and give up the per endpoint rings that xHCI provides, so add a mutex to struct xhci_epinfo_s instead and hold it across the wait. It is taken before the controller lock on both paths, so the order is always endpoint then controller and never the reverse. xhci_epfree() also freed the endpoint container without destroying iocsem. Destroy both objects there. The fault predates the preceding commits and is reachable on any xHCI controller. It was found on an EIC7700X board, where a two interface USB keyboard tripped the assertion on every boot and does not with this change applied. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 87c27c6d69f82..a80d57f1e7c6a 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -160,6 +160,16 @@ struct xhci_epinfo_s 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 */ @@ -3997,6 +4007,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, #endif epinfo->xfrtype = epdesc->xfrtype; nxsem_init(&epinfo->iocsem, 0, 0); + nxmutex_init(&epinfo->exclsem); /* xhci_epno_get() returns Device Context Index (DCI) */ @@ -4012,6 +4023,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, if (dev == NULL) { uerr("no device on port %d\n", RHPNDX(rhport)); + nxmutex_destroy(&epinfo->exclsem); nxsem_destroy(&epinfo->iocsem); kmm_free(epinfo); return -ENODEV; @@ -4164,6 +4176,8 @@ static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) /* Free the container */ + nxmutex_destroy(&epinfo->exclsem); + nxsem_destroy(&epinfo->iocsem); kmm_free(epinfo); return OK; } @@ -4408,6 +4422,17 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, 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 */ @@ -4450,6 +4475,7 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, xhci_out_slot(rhport->dev->ctx)->ctx[3]; } + nxmutex_unlock(&ep0info->exclsem); return OK; } @@ -4460,6 +4486,7 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, ret = nxmutex_lock(&priv->lock); if (ret < 0) { + nxmutex_unlock(&ep0info->exclsem); return ret; } @@ -4490,12 +4517,14 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, 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; } @@ -4588,6 +4617,16 @@ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, 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. */ @@ -4595,6 +4634,7 @@ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, ret = nxmutex_lock(&priv->lock); if (ret < 0) { + nxmutex_unlock(&epinfo->exclsem); return (ssize_t)ret; } @@ -4656,12 +4696,14 @@ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, 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; } @@ -5358,6 +5400,7 @@ static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv) 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 */ From 1a42ff468c59862273d7fa3e5d2762d40f3aeaf6 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 12:37:50 +0800 Subject: [PATCH 10/15] drivers/usbhost: Release the xHCI slot when enumeration fails. A device slot is a finite controller resource: HCSPARAMS1 reports how many exist, and Enable Slot fails with No Slots Available once they are gone. Two paths took a slot and returned without giving it back. xhci_device_init() enables a slot before it initialises the transfer ring, the slot context and the device address. Each of those three can fail, and each returned directly. The same function also treated a slot number larger than the controller supports as success, because Enable Slot itself had succeeded, and returned a slot the driver cannot address. The larger leak is in xhci_enumerate(). The device is addressed by the time usbhost_enumerate() runs, so a failure there, a device whose descriptor cannot be read or one no class driver claims, leaves the slot held. That path then clears hport->connected so the port is retried, which asks for another slot, and the retry never stops on a device that cannot be enumerated. Release the slot on both paths with xhci_device_deinit(), which already issues Disable Slot, clears the DCBAA entry and resets the context. The endpoint ring is deliberately left allocated: xhci_ring_init() reuses an existing ring and only allocates when there is none. Tested on an EIC7700X board with a USB hub, which no class driver claims because this driver does not yet support hubs, so the port retries indefinitely. Before, the eighth attempt failed with Enable Slot completion code 9 (No Slots Available) and the controller enumerated nothing further, including on its other port. After, 1104 consecutive attempts produced no slot failure and a keyboard on the second port enumerated normally throughout. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 42 ++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index a80d57f1e7c6a..953419356ab66 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -1814,7 +1814,15 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, ret = xhci_cmd_sloten(priv, &slot); if (ret < 0 || slot > priv->no_slots) { - /* Something goes wrong ! */ + /* 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; @@ -1838,7 +1846,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, if (ret < 0) { uerr("ep0 ring init failed\n"); - return ret; + goto errout_with_slot; } rhport->ep0.slot = slot; @@ -1849,7 +1857,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, ret = xhci_slot_init(priv, dev); if (ret < 0) { - return ret; + goto errout_with_slot; } /* Step 6: Assign and address to the device and enable its Default @@ -1864,12 +1872,21 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, if (ret < 0) { uerr("failed to set address %d\n", ret); - return 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, rhport); + return ret; } /**************************************************************************** @@ -3774,6 +3791,23 @@ static int xhci_enumerate(FAR struct usbhost_connection_s *conn, { /* 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); + } + } + /* 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. From 7de0c8ac6e9930457b976f6b594ac9e455cad544 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 12:55:35 +0800 Subject: [PATCH 11/15] drivers/usbhost: Stop retrying an xHCI port that will not enumerate. xhci_enumerate() reports a failure by marking the hub port disconnected, which is what makes xhci_wait() return and the attempt repeat. The root port itself is still connected, so the two disagree again immediately and the attempt repeats for as long as the device stays plugged in. Nothing bounded that. A device that fails every time, one whose descriptors cannot be read or that no class driver claims, is retried forever. Measured on an EIC7700X board with a USB hub, which no class driver claims because this driver does not support hubs yet: 1055 attempts in 90 seconds, enough console traffic to stop the board being usable at all. Count consecutive failures per root port and stop at CONFIG_USBHOST_XHCI_ENUM_RETRIES, leaving the port as it is so xhci_wait() blocks until something physically changes. A new connection clears the count, as does a successful enumeration, so a device that needs a second attempt still gets one. The default of three rides out a slow device or a marginal reset without spinning. The same board with the same hub now makes three attempts, reports that it has given up, and falls silent; a keyboard on the other port enumerates throughout and the shell responds in 1.5 s. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/Kconfig | 16 ++++++++++++++++ drivers/usbhost/usbhost_xhci.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index a5059b9088c53..f80b81329b9b3 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -797,6 +797,22 @@ config USBHOST_XHCI_MAX_DEVS ---help--- How many USB devices will be supported by xHCI driver. +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 diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 953419356ab66..0a3fbaf9a8cad 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -199,6 +199,7 @@ struct xhci_rhport_s /* 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 @@ -2886,6 +2887,12 @@ static void xhci_portsc_work(FAR void *arg) 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); @@ -3806,6 +3813,18 @@ static int xhci_enumerate(FAR struct usbhost_connection_s *conn, { xhci_device_deinit(priv, rhport); } + + /* 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 @@ -3815,6 +3834,17 @@ static int xhci_enumerate(FAR struct usbhost_connection_s *conn, 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; } From 31fb7ac782bade278ad5d1e7d07dce7184990c3c Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 14:42:19 +0800 Subject: [PATCH 12/15] drivers/usbhost: Key an xHCI device by its port, not by the root port. A root hub port and a device were the same thing in this driver. The slot, the default control endpoint and the device context all lived in struct xhci_rhport_s, and anything needing a device reached it as rhport->dev. That holds only while every device is plugged straight into the controller. A hub puts several behind one root port, each with its own slot and context, so the port cannot go on being the identity. Two identities replace it, because there are two questions. An endpoint records the slot it was opened on, so xhci_dev_from_ep() answers "which device does this transfer belong to". A hub port belongs to one device wherever it sits, so xhci_dev_from_hport() answers "which device is on this port" for the case with no endpoint to ask yet: the first one, whose allocation is what needs the device in the first place. The functions converted here were using both keys at once. xhci_ep0configure() issued Evaluate Context for epinfo->slot while filling in the context belonging to rhport->dev, and xhci_ctrl_xfer() reached the endpoint ring as rhport->dev->rhport->ep0.td, a round trip through the port back to the endpoint the caller had supplied. xhci_slot_init() read the device's speed and control ring through the port as well, which is the one that would have failed quietly: the slot context speed field has no valid zero, and a low speed device behind a high speed hub does not have its hub's speed. No functional change for a directly attached device: its port's slot and its endpoint's slot are the same one, and its hub port is the root port's own. Tested on an EIC7700X board, where a two interface USB keyboard enumerates through the full Address Device path and both of its interfaces still work. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 112 ++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 0a3fbaf9a8cad..06b834a841d61 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -219,6 +219,13 @@ struct xhci_dev_s 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; + /* Reference to allocated endpoints */ FAR struct xhci_epinfo_s *epinfo[XHCI_MAX_ENDPOINTS]; @@ -1696,8 +1703,7 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, */ regval = XHCI_ST_CTX0_CTXENT_SET(1) | - XHCI_ST_CTX0_SPEED_SET( - xhci_speed_id(dev->rhport->hport.hport.speed)); + XHCI_ST_CTX0_SPEED_SET(xhci_speed_id(dev->hport->speed)); #ifdef CONFIG_USBHOST_HUB /* TODO: @@ -1724,12 +1730,13 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, * allocated. */ - drdp = up_addrenv_va_to_pa(dev->rhport->ep0.td.ring); + 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->rhport != NULL); - if (dev->rhport->hport.hport.speed == USB_SPEED_HIGH) + DEBUGASSERT(dev->hport != NULL); + if (dev->hport->speed == USB_SPEED_HIGH) { /* For high-speed, we must use 64 bytes */ @@ -1852,6 +1859,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, rhport->ep0.slot = slot; dev->rhport = rhport; + dev->hport = &rhport->hport.hport; dev->slot = slot; dev->epinfo[0] = &rhport->ep0; @@ -1935,6 +1943,7 @@ static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, /* Remove reference to a device slot */ + rhport->dev->hport = NULL; rhport->dev = NULL; return OK; @@ -3002,6 +3011,56 @@ 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. + * + ****************************************************************************/ + +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; +} + /**************************************************************************** * Name: xhci_speed_id * @@ -3880,42 +3939,44 @@ 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); + 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, rhport->dev->input, 0); + 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, rhport->dev, 0, + 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)rhport->dev->input, - (uintptr_t)rhport->dev->input + + 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(rhport->dev->input); + ctx = up_addrenv_va_to_pa(dev->input); uinfo("slot %d funcaddr %d speed %d maxpacket %d\n", epinfo->slot, funcaddr, speed, maxpacketsize); @@ -4015,7 +4076,6 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, 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; @@ -4032,12 +4092,6 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, && ep != NULL); hport = epdesc->hport; - /* Only the tracing alternative below and the hub logic further down use - * this, and a configuration may have neither. - */ - - UNUSED(hport); - /* Terse output only if we are tracing */ #ifdef CONFIG_USBHOST_TRACE @@ -4077,16 +4131,16 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, idx = xhci_epno_get(epinfo); mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx)); - dev = rhport->dev; + dev = xhci_dev_from_hport(priv, hport); /* 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 root hub port with nothing behind it. + * this can run for a port with nothing behind it. */ if (dev == NULL) { - uerr("no device on port %d\n", RHPNDX(rhport)); + uerr("no device on port %d\n", hport->port); nxmutex_destroy(&epinfo->exclsem); nxsem_destroy(&epinfo->iocsem); kmm_free(epinfo); @@ -4106,7 +4160,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, /* Store slot ID for later */ - epinfo->slot = rhport->slot; + epinfo->slot = dev->slot; #ifdef CONFIG_USBHOST_HUB if (hport->speed != USB_SPEED_HIGH) @@ -4515,28 +4569,30 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, * on control EP. */ - xhci_ring_init(&rhport->dev->rhport->ep0.td, 0); + xhci_ring_init(&ep0info->td, 0); /* Issue SET_ADDRESS request */ ret = xhci_address_set(priv, rhport, 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)rhport->dev->ctx, - (uintptr_t)rhport->dev->ctx + + 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(rhport->dev->ctx)->ctx[3]); - xhci_in_slot(priv, rhport->dev->input)->ctx[3] = - xhci_out_slot(rhport->dev->ctx)->ctx[3]; + 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); From bc285e9aa0df8a80833c8e8ce34fb07d470bbe15 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 13:39:53 +0800 Subject: [PATCH 13/15] usbhost: Report what a hub is on the port it occupies. Some host controllers have to be told about the hubs in a topology, not only about the device at the end of it. xHCI is one: a hub's slot context carries a hub flag, its downstream port count and the think time of its transaction translator, and the controller uses them to route to anything behind that hub. The hub class driver already reads all of this from the hub descriptor and keeps it privately. Publish the two values a controller can act on, on the hub's own hub port, beside the speed and function address that already describe the device attached there rather than the port itself. A driver setting up a device behind a hub finds them on that device's parent. They are written before the hub activates any downstream port, so they are in place before there is anything behind the hub to set up, and a port with no hub attached reports zero ports because the hub class clears each child before use. Nothing is required to read them, so a controller that does not need them is unaffected. Fields rather than a new driver method: a method would need a null check at the call site and would define an order in which it must be called, and neither can be got wrong here. Both are inside CONFIG_USBHOST_HUB, as struct usbhost_hubport_s's parent pointer already is, so a build without hub support is unchanged. Multi-TT is not included. It comes from the hub's interface protocol rather than the hub descriptor, and treating a multi-TT hub as single-TT costs bandwidth behind that hub but is correct. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_hub.c | 11 +++++++++++ include/nuttx/usb/usbhost.h | 15 +++++++++++++++ 2 files changed, 26 insertions(+) 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/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 From 6cd10aeaec0bff87389f76d9acb98b9d5d9364b6 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 14:42:38 +0800 Subject: [PATCH 14/15] drivers/usbhost: Describe a device behind a hub to the xHCI controller. A controller reaches a device by the path to it and, for a slow device, through the hub that translates for it. Neither was described, so a device behind a hub was addressed as though it were on the root port. The route string is that path: each hub between the device and the root contributes a nibble holding the port the next thing down is plugged into, tier nearest the root in the lowest nibble. Walking up from the device reaches the deepest tier first, so shifting what is already there left by a nibble each time leaves them in the order the field wants. The walk stops after five, which is both what the field holds and what USB allows, and a port above fifteen is clamped rather than carrying into the tier below it. Slot context dword 2 names the transaction translator that carries a low or full speed device behind a high speed hub. It reports the hub by slot, where EHCI reports it by USB address, and it names the nearest high speed ancestor rather than the immediate parent: a full speed hub below a high speed one is itself carried by the translator above it, so the device's own hub is not always the one doing the work. The think time comes from the hub descriptor by way of the hub class driver, and both count in the same units, so it carries across unchanged. What was there instead came from EHCI. xhci_epalloc() carried a copy of sam_ehci.c's block, writing epinfo->hubaddr and epinfo->hubport, which are how EHCI describes a split transaction in its queue head. This driver never read either field, so the work was thrown away, and the place xHCI wants it is the slot context rather than the endpoint. Both fields and the code setting them are gone. Multi-TT is not set. It comes from the hub's interface protocol rather than its descriptor, and driving a multi-TT hub as single-TT costs bandwidth behind that hub but is correct. No functional change: hubs cannot be enabled yet, and a device on a root port has neither hubs above it nor a translator, so both fields are zero as before. Tested on an EIC7700X board with a directly attached low speed keyboard, the case that would use a translator if a hub were in the way. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 162 ++++++++++++++++++++++++++------- drivers/usbhost/usbhost_xhci.h | 21 +++++ 2 files changed, 149 insertions(+), 34 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 06b834a841d61..3ecce2646c6d3 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -176,13 +176,6 @@ struct xhci_epinfo_s #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 */ @@ -394,6 +387,11 @@ 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); +#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); static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, @@ -1706,10 +1704,11 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, 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 - * 3. configure route string */ # warning missing logic @@ -1726,6 +1725,10 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, 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. */ @@ -3043,6 +3046,58 @@ xhci_dev_from_ep(FAR struct usbhost_xhci_s *priv, * ****************************************************************************/ +#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) @@ -3061,6 +3116,71 @@ xhci_dev_from_hport(FAR struct usbhost_xhci_s *priv, 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 * @@ -4162,32 +4282,6 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, epinfo->slot = dev->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) diff --git a/drivers/usbhost/usbhost_xhci.h b/drivers/usbhost/usbhost_xhci.h index 06b901ccc1b04..23982c50a9dad 100644 --- a/drivers/usbhost/usbhost_xhci.h +++ b/drivers/usbhost/usbhost_xhci.h @@ -508,6 +508,8 @@ #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) & \ @@ -539,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) From 5a140b620cd71f321b15b8876adb19be523e4471 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 14:42:59 +0800 Subject: [PATCH 15/15] drivers/usbhost: Support USB hubs on xHCI. The driver refused CONFIG_USBHOST_HUB outright. Everything needed to describe a device behind a hub is now in place, so implement the rest and let the configuration build. A device is created wherever it sits. xhci_device_init() took a root hub port and read the slot, the control endpoint and the device out of it, all of which belong to the device. It now takes the hub port and the control endpoint too, and records the device on the root port only when that is where it is: the device a root port names, once a hub is plugged in, is the hub. xhci_address_set() and xhci_device_deinit() likewise work on a device, and xhci_disconnect() finds the device by the port going away rather than assuming the root port's. The hub asks for a port's control endpoint before it reports the connection, so xhci_epalloc() has nothing to attach one to. It returns an endpoint with no slot in that case, and xhci_connect() gives it one when it creates the device, which is the hub's next action. A hub also has to be described to the controller as a hub before anything behind it can be reached, and nothing knows it is one when its slot is created: it is addressed and configured like any other device, and only then does its class driver read the descriptor saying how many ports it has. xhci_hub_update() corrects the slot context with a Configure Endpoint command the first time something appears behind it. A hub reports each port whose state changed one after another without waiting for any to be dealt with, so the connect method queues them. Holding one pointer, as it first did, meant the second report overwrote the first and the device on it was never enumerated, silently. No more can be outstanding than the controller has slots to put devices in. Report the geometry the hardware describes rather than leaving it to a debug build: the root port and slot counts from HCSPARAMS1, and the port count a hub gives in its descriptor. Tested on an EIC7700X board with a hub on one controller and a keyboard on the other. Behind the hub, a 59 GB mass storage device mounts and reads a file back correctly, a composite CDC device gives four ttyACM nodes, and a Realtek adapter with no driver in this tree is enumerated and reported as unclaimed. Both interfaces of the keyboard keep working throughout. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/usbhost_xhci.c | 341 +++++++++++++++++++++++++++------ 1 file changed, 286 insertions(+), 55 deletions(-) diff --git a/drivers/usbhost/usbhost_xhci.c b/drivers/usbhost/usbhost_xhci.c index 3ecce2646c6d3..2c4a85aa9daa0 100644 --- a/drivers/usbhost/usbhost_xhci.c +++ b/drivers/usbhost/usbhost_xhci.c @@ -58,12 +58,6 @@ # 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) @@ -219,6 +213,13 @@ struct xhci_dev_s 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]; @@ -229,7 +230,14 @@ struct xhci_dev_s struct usbhost_xhci_s { #ifdef CONFIG_USBHOST_HUB - FAR struct usbhost_hubport_s *hport; /* Used to pass external hub port events */ + /* 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 */ @@ -384,7 +392,7 @@ static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, 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); + 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 @@ -393,9 +401,11 @@ 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 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_rhport_s *rhport); + 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, @@ -1646,15 +1656,11 @@ static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, ****************************************************************************/ 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, bool setaddr) { - FAR struct xhci_dev_s *dev; - uint64_t ctx; - - dev = rhport->dev; - ctx = up_addrenv_va_to_pa(dev->input); + uint64_t ctx = up_addrenv_va_to_pa(dev->input); - return xhci_cmd_setaddr(priv, rhport->slot, ctx, !setaddr); + return xhci_cmd_setaddr(priv, dev->slot, ctx, !setaddr); } /**************************************************************************** @@ -1806,7 +1812,9 @@ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, ****************************************************************************/ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport) + 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; @@ -1841,9 +1849,17 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, /* Slot ID is an index to the identify Device data */ - rhport->dev = &priv->devs[slot - 1]; - rhport->slot = slot; - dev = rhport->dev; + 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 */ @@ -1853,18 +1869,18 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, * All data structured are already allocated. */ - ret = xhci_ring_init(&rhport->ep0.td, XHCI_TD_MAX); + ret = xhci_ring_init(&ep0info->td, XHCI_TD_MAX); if (ret < 0) { uerr("ep0 ring init failed\n"); goto errout_with_slot; } - rhport->ep0.slot = slot; - dev->rhport = rhport; - dev->hport = &rhport->hport.hport; - dev->slot = slot; - dev->epinfo[0] = &rhport->ep0; + 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) @@ -1880,7 +1896,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, * stack. */ - ret = xhci_address_set(priv, rhport, false); + ret = xhci_address_set(priv, dev, false); if (ret < 0) { uerr("failed to set address %d\n", ret); @@ -1897,7 +1913,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, * number of them. */ - xhci_device_deinit(priv, rhport); + xhci_device_deinit(priv, dev); return ret; } @@ -1913,9 +1929,9 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, ****************************************************************************/ static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, - FAR struct xhci_rhport_s *rhport) + FAR struct xhci_dev_s *dev) { - uint8_t slot = rhport->slot; + uint8_t slot = dev->slot; int ret; /* Disable Slot */ @@ -1932,22 +1948,30 @@ static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, /* Clean up device data, but don't touch allocated memory! */ - rhport->dev->state = XHCI_SLOT_DISABLED; + dev->state = XHCI_SLOT_DISABLED; - memset(rhport->dev->ctx, 0, XHCI_DEVCTX_SIZE(priv)); - memset(rhport->dev->input, 0, XHCI_INCTX_SIZE(priv)); + 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)rhport->dev->ctx, - (uintptr_t)rhport->dev->ctx + XHCI_DEVCTX_SIZE(priv)); - up_flush_dcache((uintptr_t)rhport->dev->input, - (uintptr_t)rhport->dev->input + XHCI_INCTX_SIZE(priv)); + 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 */ + /* 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. + */ - rhport->dev->hport = NULL; - rhport->dev = NULL; + if (dev->rhport != NULL && dev->rhport->dev == dev) + { + dev->rhport->dev = NULL; + } + + dev->hport = NULL; + dev->rhport = NULL; return OK; } @@ -3833,12 +3857,12 @@ static int xhci_wait(FAR struct usbhost_connection_s *conn, #ifdef CONFIG_USBHOST_HUB /* Is a device connected to an external hub? */ - if (priv->hport) + if (priv->hhead != priv->htail) { /* Yes.. return the external hub port */ - connport = priv->hport; - priv->hport = NULL; + 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); @@ -3930,7 +3954,8 @@ static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, /* Initialize device data */ - ret = xhci_device_init(priv, rhport); + ret = xhci_device_init(priv, rhport, &rhport->hport.hport, + &rhport->ep0); if (ret < 0) { uerr("Failed to initialize device %d\n", ret); @@ -3990,7 +4015,7 @@ static int xhci_enumerate(FAR struct usbhost_connection_s *conn, if (rhport->dev != NULL) { - xhci_device_deinit(priv, rhport); + xhci_device_deinit(priv, rhport->dev); } /* Clearing connected below is what makes xhci_wait() return, @@ -4253,6 +4278,31 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, 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. @@ -4667,7 +4717,8 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, /* Issue SET_ADDRESS request */ - ret = xhci_address_set(priv, rhport, true); + 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); @@ -5157,11 +5208,186 @@ static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) ****************************************************************************/ #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) { -#error missing logic + 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 @@ -5194,17 +5420,21 @@ static int xhci_connect(FAR struct usbhost_driver_s *drvr, 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; + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_dev_s *dev; DEBUGASSERT(hport != NULL); hport->devclass = NULL; - /* Deinit device slot */ + /* 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. + */ - if (rhport->dev) + dev = xhci_dev_from_hport(priv, hport); + if (dev != NULL) { - xhci_device_deinit(priv, rhport); + xhci_device_deinit(priv, dev); } } @@ -5245,8 +5475,9 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv) priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS; } - uinfo("no slots = %d, no ports = %d\n", - priv->no_slots, priv->no_ports); + 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 */