diff --git a/Documentation/components/drivers/special/pci/index.rst b/Documentation/components/drivers/special/pci/index.rst index 1c598a910ee..99160423b4e 100644 --- a/Documentation/components/drivers/special/pci/index.rst +++ b/Documentation/components/drivers/special/pci/index.rst @@ -106,3 +106,19 @@ An example command to run the driver on ``x86_64`` looks like this: -nographic -serial mon:stdio -object can-bus,id=canbus0-bus \ -object can-host-socketcan,if=can0,canbus=canbus0-bus,id=canbus0-socketcan \ -device ctucan_pci,canbus0=canbus0-bus,canbus1=canbus0-bus + +xHCI over PCI +------------- + +xHCI support was tested on x86_64 target, both on QEMU and real hardware. + +Known issues with this driver: + +- Currently only USB2.0 is supported, there is no USB3.0 support in NuttX yet. + +- USB HUB devices not supported yet + +To enable xHCI support on QEMU, we have to add ``-device qemu-xhci`` argument. +All supported USB devices in QEMU can be found in +`QEMU documentation `_, +but not all classes are supported in NuttX. diff --git a/drivers/pci/pci_drivers.c b/drivers/pci/pci_drivers.c index bc33144e493..5a0301c8dfd 100644 --- a/drivers/pci/pci_drivers.c +++ b/drivers/pci/pci_drivers.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "pci_drivers.h" @@ -182,6 +183,16 @@ int pci_register_drivers(void) } #endif + /* Initialization xHCI pci driver */ + +#ifdef CONFIG_USBHOST_XHCI_PCI + ret = pci_xhci_init(); + if (ret < 0) + { + pcierr("pci_xhci_init failed, ret=%d\n", ret); + } +#endif + ret = pci_dev_register(); if (ret < 0) { diff --git a/drivers/usbhost/CMakeLists.txt b/drivers/usbhost/CMakeLists.txt index b38f7d78585..daf3ddd30b8 100644 --- a/drivers/usbhost/CMakeLists.txt +++ b/drivers/usbhost/CMakeLists.txt @@ -75,6 +75,10 @@ if(CONFIG_USBHOST) list(APPEND SRCS usbhost_ft232r.c) endif() + if(CONFIG_USBHOST_XHCI_PCI) + list(APPEND SRCS usbhost_xhci_pci.c usbhost_xhci_trace.c) + endif() + # HCD debug/trace logic if(CONFIG_USBHOST_TRACE) diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index 28e64ffc85c..4f9dc590238 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -752,4 +752,23 @@ 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" + default n + depends on PCI && PCI_MSIX && USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK + select USBHOST_HAVE_ASYNCH + ---help--- + USB xHCI PCI host driver support. + +if USBHOST_XHCI_PCI + +config USBHOST_XHCI_MAX_DEVS + int "xHCI maximum supported devices" + default 8 + range 1 256 + ---help--- + How many USB devices will be supported by xHCI driver. + +endif # USBHOST_XHCI_PCI + endif # USBHOST diff --git a/drivers/usbhost/Make.defs b/drivers/usbhost/Make.defs index 38e34c2add4..11cf128e973 100644 --- a/drivers/usbhost/Make.defs +++ b/drivers/usbhost/Make.defs @@ -80,6 +80,10 @@ ifeq ($(CONFIG_USBHOST_BTHCI),y) CSRCS += usbhost_bthci.c endif +ifeq ($(CONFIG_USBHOST_XHCI_PCI),y) +CSRCS += usbhost_xhci_pci.c usbhost_xhci_trace.c +endif + # HCD debug/trace logic ifeq ($(CONFIG_USBHOST_TRACE),y) diff --git a/drivers/usbhost/usbhost_xhci.h b/drivers/usbhost/usbhost_xhci.h new file mode 100644 index 00000000000..3888eabacf6 --- /dev/null +++ b/drivers/usbhost/usbhost_xhci.h @@ -0,0 +1,635 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_xhci.h + * + * 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 __DRIVERS_USBHOST_USBHOST_XHCI_H +#define __DRIVERS_USBHOST_USBHOST_XHCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* xHCI constants */ + +#define XHCI_MAX_DEVS (255) /* Up to 255 USB devices or hubs */ + +#define XHCI_PAGE_SIZE (4096) /* 4K Bytes is minimum Page size supported */ +#define XHCI_BUF_ALIGN (64) /* Buffer alignment */ +#define XHCI_CTX_ALIGN (64) /* Context alignment */ +#define XHCI_TRB_ALIGN (16) /* TRB alignment */ +#define XHCI_TD_LEN_MAX (65536) /* Maximum Transfer Descriptor data length is 64K bytes */ +#define XHCI_MAX_ENDPOINTS (31) /* EP0 + 1-15 EP OUT 1-15 EP IN */ + +/* xHCI Extended Capability Codes */ + +#define XHCI_CAP_USBLEGSUP (1) /* USB Legacy Support */ +#define XHCI_CAP_SUPPROTO (2) /* Supported Protocol */ +#define XHCI_CAP_EXTPWRMAN (3) /* Extended Power Management */ +#define XHCI_CAP_IOVIRT (4) /* I/O Virtualization */ +#define XHCI_CAP_MSGINT (5) /* Message Interrupt */ +#define XHCI_CAP_LOCALMEM (6) /* Local Memory */ + /* IDs 7-9: Reserved */ +#define XHCI_CAP_USBDEBUG (10) /* USB Debug Capability */ + /* IDs 11-16: Reserved */ +#define XHCI_CAP_EXTMSGINT (17) /* Extended Message Interrupt */ + /* IDs 18-191: Reserved */ + /* IDs 192-255: Vendor Defined */ + +/* USB Legacy Support Capability */ + +#define XHCI_USBLEGSUP (0x0000) /* USB Legacy Support Capability Register */ +#define XHCI_USBLEGCTLSTS (0x0004) /* USB Legacy Support Control and Status Register */ + +/* XHCI capability ID's */ + + /* 0: Reserved */ +#define XHCI_ID_USBLEGSUP (1) /* USB Legacy Support */ +#define XHCI_ID_SUPPROTO (2) /* Supported Protocol */ +#define XHCI_ID_EXTPM (3) /* Extended Power Management */ +#define XHCI_ID_IOVIRT (4) /* I/O Virtualization */ +#define XHCI_ID_MSGIRQ (5) /* Message Interrupt */ +#define XHCI_ID_LOCALMEM (6) /* Local Memory */ + /* 7-9: Reserved */ +#define XHCI_ID_DEBUG (10) /* USB Debug Capability */ + /* 11-16: Reserved */ +#define XHCI_ID_EXTMSGIRQ (17) /* Extended Message Interrupt */ + /* 18-191: Reserved */ +#define XHCI_ID_VENDOR (192) /* Vendor Defined */ + +/* USB Legacy Support Capability */ + +#define XHCI_USBLEGSUP_ID(x) ((x) & 0xff) /* Get Capability ID */ +#define XHCI_USBLEGSUP_NEXT(x) (((x) >> 8) & 0xff) /* Get next Capability Pointer */ +#define XHCI_USBLEGSUP_BIOS_SEM (2) /* Offset in bytes to BIOS Owned Semaphore */ +#define XHCI_USBLEGSUP_OS_SEM (3) /* Offset in bytes to OS Owned Semaphore */ + +/* Host Controller Capability Registers + * Address: Base + offset + */ + +#define XHCI_CAPLENGTH (0x0000) /* Capability Register Length */ + /* 0x0001: Reserved */ +#define XHCI_HCIVERSION (0x0002) /* Interface Version Number */ +#define XHCI_HCSPARAMS1 (0x0004) /* Structural Parameters 1 */ +#define XHCI_HCSPARAMS2 (0x0008) /* Structural Parameters 2 */ +#define XHCI_HCSPARAMS3 (0x000c) /* Structural Parameters 3 */ +#define XHCI_HCCPARAMS1 (0x0010) /* Capability Parameters 1 */ +#define XHCI_DBOFF (0x0014) /* Doorbell Offset */ +#define XHCI_RTSOFF (0x0018) /* Runtime Register Space Offset */ +#define XHCI_HCCPARAMS2 (0x001c) /* Capability Parameters 2 */ + +/* Structural Parameters 1 */ + +#define XHCI_HCSPARAMS1_MAXSLOTS(x) ((x) & 0xff) /* Bits 0-7: Number of Device Slots (MaxSlots) */ +#define XHCI_HCSPARAMS1_MAXINTRS(x) (((x) >> 8) & 0x7ff) /* Bits 8-18: Number of Interrupters (MaxIntrs) */ + /* Bits 19-23: Reserved */ +#define XHCI_HCSPARAMS1_MAXPORTS(x) (((x) >> 24) & 0xff) /* Bits 24-31: Number of Ports (MaxPorts) */ + +/* Structural Parameters 2 */ + +#define XHCI_HCSPARAMS2_MAXSPBHI(x) (((x) >> 21) & 0x1f) /* Bits 21-25: Max Scratchpad Bufs Hi */ +#define XHCI_HCSPARAMS2_MAXSPBLO(x) (((x) >> 27) & 0x1f) /* Bits 27-31: Max Scratchpad Bufs Lo */ +#define XHCI_HCSPARAMS2_MAXSPB(x) (XHCI_HCSPARAMS2_MAXSPBLO(x) + \ + (XHCI_HCSPARAMS2_MAXSPBHI(x) << 5)) + +#define XHCI_HCSPARAMS2_ERST(x) (((x) >> 4) & 0x0f) /* Bits 4-7: Event Ring Segment Table Max */ + +/* TODO: Structural Parameters 3 */ + +/* Capability Parameters 1 */ + +#define XHCI_HCCPARAMS1_CSZ (1 << 2) /* Bit 2: Context Size */ +#define XHCI_HCCPARAMS1_XECP(x) ((x) >> 16) /* Bits 16-31: xHCI Extended Capabilities Pointer */ + +/* TODO: Capability Parameters 2 */ + +/* Host Controller Operational Registers + * Address: Operational Base + offset + */ + +#define XHCI_USBCMD (0x0000) /* USB Command (32b) */ +#define XHCI_USBSTS (0x0004) /* USB Status (32b) */ +#define XHCI_PAGESIZE (0x0008) /* Page Size (32b) */ + /* 0x0c - 0x13: Reserved */ +#define XHCI_DNCTRL (0x0014) /* Device Notification Control (32b) */ +#define XHCI_CRCR (0x0018) /* Command Ring Control (64b) */ + /* 0x20 - 0x2f: Reserved */ +#define XHCI_DCBAAP (0x0030) /* Device Context Base Address Array Pointer (64b) */ +#define XHCI_CONFIG (0x0038) /* Configure (32b) */ + /* 0x03c - 0x3ff: Reserved */ +#define XHCI_PORT(n) (0x400 + 0x10 * (n)) /* Port Register Set */ +#define XHCI_PORTSC(n) (XHCI_PORT(n) + 0x00) /* Port Status and Control (32b) */ +#define XHCI_PORTPMSC(n) (XHCI_PORT(n) + 0x04) /* Port Power Management Status and Control (32b) */ +#define XHCI_PORTLI(n) (XHCI_PORT(n) + 0x08) /* Port Link Info (32b) */ +#define XHCI_PORTHLPMC(n) (XHCI_PORT(n) + 0x0c) /* Port Hardware LPM Control (32b) */ + +/* Host Controller Runtime Registers (Address: Runtime Base + offset) */ + +#define XHCI_MFINDEX (0x0000) /* Microframe Index */ + /* 0x0004 - 0x001f: Reserved */ +#define XHCI_IRQ(n) (0x0020 + 32 * (n)) /* Interrupter Register Set */ +#define XHCI_IMAN(n) (XHCI_IRQ(n) + 0x00) /* Interrupter Management (32b) */ +#define XHCI_IMOD(n) (XHCI_IRQ(n) + 0x04) /* Interrupter Moderation (32b) */ +#define XHCI_ERSTSZ(n) (XHCI_IRQ(n) + 0x08) /* Event Ring Segment Table Size (32b) */ + /* 0x0c: Reserved */ +#define XHCI_ERSTBA(n) (XHCI_IRQ(n) + 0x10) /* Event Ring Segment Table Base Address (64b) */ +#define XHCI_ERDP(n) (XHCI_IRQ(n) + 0x18) /* Event Ring Dequeue Pointer (64b) */ + +/* Doorbel Registers */ + +#define XHCI_DOORBEL(n) (0x0000 + 4 * (n)) +#define XHCI_DOORBEL_TARGET_SHIFT (0) /* Bits 0-7: DB Target */ +#define XHCI_DOORBEL_TARGET_MASK (0xff << XHCI_DOORBEL_TARGET_SHIFT) +#define XHCI_DOORBEL_TARGET(x) (((x) << XHCI_DOORBEL_TARGET_SHIFT) & XHCI_DOORBEL_TARGET_MASK) + /* Bits 8-15: Reserved */ +#define XHCI_DOORBEL_TASK_SHIFT (16) /* Bits 16-31: DB Task ID */ +#define XHCI_DOORBEL_TASK_MASK (0xffff << XHCI_DOORBEL_TASK_SHIFT) +#define XHCI_DOORBEL_TASK(x) (((x) << XHCI_DOORBEL_TASK_SHIFT) & XHCI_DOORBEL_TASK_MASK) + +/* USB Command */ + +#define XHCI_USBCMD_RS (1 << 0) /* Bit 0: Run/Stop */ +#define XHCI_USBCMD_HCRST (1 << 1) /* Bit 1: Host Controller Reset */ +#define XHCI_USBCMD_INTE (1 << 2) /* Bit 2: Interrupter Enable */ +#define XHCI_USBCMD_HSEE (1 << 3) /* Bit 3: Host System Error Enable */ + /* Bits 4-6: Reserved */ +#define XHCI_USBCMD_LHCRST (1 << 7) /* Bit 7: Light Host Controller Reset */ +#define XHCI_USBCMD_CSS (1 << 8) /* Bit 8: Controller Save State */ +#define XHCI_USBCMD_CRS (1 << 9) /* Bit 9: Controller Restore State */ +#define XHCI_USBCMD_EWE (1 << 10) /* Bit 10: Enable Wrap Event */ +#define XHCI_USBCMD_EU3S (1 << 11) /* Bit 11: Enable U3 MFINDEX Stop */ + /* Bit 12: Reserved */ +#define XHCI_USBCMD_CME (1 << 13) /* Bit 13: CEM Enable */ +#define XHCI_USBCMD_ETE (1 << 14) /* Bit 14: Extended TBC Enable */ +#define XHCI_USBCMD_TSCEN (1 << 15) /* Bit 15: Extended TBC TRB Status Enable */ +#define XHCI_USBCMD_VTIOE (1 << 16) /* Bit 16: VTIO Enable */ + /* Bits 17-31: Reserved */ + +/* USB Status */ + +#define XHCI_USBSTS_HCH (1 << 0) /* Bit 0: Host Controller Halted */ + /* Bit 1: Reserved */ +#define XHCI_USBSTS_HSE (1 << 2) /* Bit 2: Host System Error */ +#define XHCI_USBSTS_EINT (1 << 3) /* Bit 3: Event Interrupt */ +#define XHCI_USBSTS_PCD (1 << 4) /* Bit 4: Port Change Detect */ + /* Bits 5-7: Reserved */ +#define XHCI_USBSTS_SSS (1 << 8) /* Bit 8: Save State Status */ +#define XHCI_USBSTS_RSS (1 << 9) /* Bit 9: Restore State Status */ +#define XHCI_USBSTS_SRE (1 << 10) /* Bit 10: Save/Restore Error */ +#define XHCI_USBSTS_CNR (1 << 11) /* Bit 11: Controller Not Ready */ +#define XHCI_USBSTS_HCE (1 << 12) /* Bit 12: Host Controller Error */ + /* Bits 13-31: Reserved */ + +/* Page Size */ + +#define XHCI_PAGESIZE_MASK (0xffff) /* Bits 0-15: Page Size */ + +/* Device Notification Control */ + +#define XHCI_DNCTRL_MASK (0xffff) /* Bits 0-15: Notification Enable */ +#define XHCI_DNCTRL_N(n) ((1 << (n)) & XHCI_DNCTRL_MASK) + +/* Command Ring Control */ + +#define XHCI_CRCR_RCS (1 << 0) /* Bit 0: Ring Cycle State */ +#define XHCI_CRCR_CS (1 << 1) /* Bit 1: Command Stop */ +#define XHCI_CRCR_CA (1 << 2) /* Bit 2: Command Abort */ +#define XHCI_CRCR_CRR (1 << 3) /* Bit 3: Command Ring Running */ + /* Bits 4-5: Reserved */ + +#define XHCI_CRCR_CRP_SHIFT (6) /* Bits 6-63: Command Ring Pointer */ +#define XHCI_CRCR_CRP_MASK (0xffffffffffffffc0) + +/* Configure */ + +#define XHCI_CONFIG_MDSE_SHIFT (0) /* Bits 0-7: Max Device Slots Enabled (MaxSlotsEn) */ +#define XHCI_CONFIG_MDSE_MASK (0xff << XHCI_CONFIG_MDSE_SHIFT) +#define XHCI_CONFIG_U3E (1 << 8) /* Bit 8: U3 Entry Enable */ +#define XHCI_CONFIG_CIE (1 << 9) /* Bit 9: Configuration Information Enable */ + /* Bits 10-31: Reserved */ + +/* Port Status and Control */ + +#define XHCI_PORTSC_CCS (1 << 0) /* Bit 0: Current Connect Status */ +#define XHCI_PORTSC_PED (1 << 1) /* Bit 1: Port Enabled/Disabled */ + /* Bit 2: Reserved */ +#define XHCI_PORTSC_OCA (1 << 3) /* Bit 3: Over-current Active */ +#define XHCI_PORTSC_PR (1 << 4) /* Bit 4: Port Reset */ +#define XHCI_PORTSC_PLS_SHIFT (5) /* Bits 5-8: Port Link State */ +#define XHCI_PORTSC_PLS_MASK (0xf << XHCI_PORTSC_PLS_SHIFT) +#define XHCI_PORTSC_PLS(x) (((x) & XHCI_PORTSC_PLS_MASK) >> XHCI_PORTSC_PLS_SHIFT) +#define XHCI_PORTSC_PP (1 << 9) /* Bit 9: Port Power */ +#define XHCI_PORTSC_PS_SHIFT (10) /* Bits 10-13: Port Speed */ +#define XHCI_PORTSC_PS_MASK (0xf << XHCI_PORTSC_PS_SHIFT) +#define XHCI_PORTSC_PS(x) (((x) & XHCI_PORTSC_PS_MASK) >> XHCI_PORTSC_PS_SHIFT) +# define XHCI_PORTSC_PS_FULL (1) /* Full-speed */ +# define XHCI_PORTSC_PS_LOW (2) /* Low-speed */ +# define XHCI_PORTSC_PS_HIGH (3) /* High-speed */ +# define XHCI_PORTSC_PS_SUPPER11 (4) /* SuperSpeedPlus Gen1 x1 */ +# define XHCI_PORTSC_PS_SUPPER21 (5) /* SuperSpeedPlus Gen1 x2 */ +# define XHCI_PORTSC_PS_SUPPER12 (6) /* SuperSpeedPlus Gen1 x2 */ +# define XHCI_PORTSC_PS_SUPPER22 (7) /* SuperSpeedPlus Gen2 x2 */ +#define XHCI_PORTSC_PIC_SHIFT (14) /* Bits 14-15: Port Indicator Control */ +#define XHCI_PORTSC_LWS (1 << 16) /* Bit 16: Port Link State Write Strobe */ +#define XHCI_PORTSC_CSC (1 << 17) /* Bit 17: Connect Status Change */ +#define XHCI_PORTSC_PEC (1 << 18) /* Bit 18: Port Enabled/Disabled Change */ +#define XHCI_PORTSC_WRC (1 << 19) /* Bit 19: Warm Port Reset Change */ +#define XHCI_PORTSC_OCC (1 << 20) /* Bit 20: Over-current Change */ +#define XHCI_PORTSC_PRC (1 << 21) /* Bit 21: Port Reset Change */ +#define XHCI_PORTSC_PLC (1 << 22) /* Bit 22: Port Link State Change */ +#define XHCI_PORTSC_CEC (1 << 23) /* Bit 23: Port Config Error Change */ +#define XHCI_PORTSC_CAS (1 << 24) /* Bit 24: Cold Attach Status */ +#define XHCI_PORTSC_WCE (1 << 25) /* Bit 25: Wake on Connect Enable */ +#define XHCI_PORTSC_WDE (1 << 26) /* Bit 26: Wake on Disconnect Enable */ +#define XHCI_PORTSC_WOE (1 << 27) /* Bit 27: Wake on Over-current Enable */ + /* Bits 28-29: Reserved */ +#define XHCI_PORTSC_DR (1 << 30) /* Bit 30: Device Removable */ +#define XHCI_PORTSC_WPR (1 << 31) /* Bit 31: Warm Port Reset */ + +/* Port Power Management Status and Control (USB3) */ + +#define XHCI_PORTPMSC_U1TO_SHIFT (0) /* Bits 0-7: U1 Timeout */ +#define XHCI_PORTPMSC_U2TO_SHIFT (8) /* Bits 8-15: U2 Timeout */ +#define XHCI_PORTPMSC_FLA (1 << 16) /* Bit 16: Force Link PM Accept */ + /* Bits 17-31: Reserved */ + +/* Port Power Management Status and Control (USB2) */ + +#define XHCI_PORTPMSC_L1S_SHIFT (0) /* Bits 0-2: L1 Status */ +#define XHCI_PORTPMSC_RWE (1 << 3) /* Bit 3: Remote Wake Enable */ +#define XHCI_PORTPMSC_BESL_SHIFT (4) /* Bits 4-7: Best Effort Service Latency */ +#define XHCI_PORTPMSC_L1DS_SHIFT (8) /* Bits 8-15: L1 Device Slot */ +#define XHCI_PORTPMSC_HLE (1 << 16) /* Bit 16: Hardware LPM Enable */ + /* Bits 17-27: Reserved */ +#define XHCI_PORTPMSC_PTC_SHIFT (28) /* Bits 28-31: Port Test Control */ + +/* Port Link Info (USB3, reserved for USB2) */ + +#define XHCI_PORTLI_LER_SHIFT (0) /* Bits 0-15: Link Error Count */ +#define XHCI_PORTLI_RLC_SHIFT (16) /* Bits 16-19: Rx Lane Count */ +#define XHCI_PORTLI_TCL (20) /* Bits 20-23: Tx Lane Count */ + /* Bits 24-31: Reserved */ + +/* Interrupter Management */ + +#define XHCI_IMAN_IP (1 << 0) /* Bit 0: Interrupt Pending */ +#define XHCI_IMAN_IE (1 << 1) /* Bit 1: Interrupt Enable */ + +/* Interrupter Moderation */ + +#define XHCI_IMOD_IMODI_SHIFT (0) /* Bits 0-15: Interrupt Moderation Interval */ +#define XHCI_IMOD_IMODC_SHIFT (16) /* Bits 16-31: Interrupt Moderation Counter */ + +/* Event Ring Segment Table Size */ + +#define XHCI_ERSTS_MASK (0xffff) /* Bit 0-15: Event Ring Segment Table Size */ + /* Bits 16-31: Reserved */ + +/* Event Ring Segment Table Base Address */ + +#define XHCI_ERSTBA_PTR_SHIFT (6) /* Bit 6-63: Event Ring Segment Table Base Address Register */ +#define XHCI_ERSTBA_PTR_MASK (0xffffffffffffffc0) + +/* Event Ring Dequeue Pointer */ + +#define XHCI_ERDP_DESI_SHIFT (0) /* Bits 0-2: Dequeue ERST Segment Index */ +#define XHCI_ERDP_DESI_MASK (0x7 << XHCI_ERDP_DESI_SHIFT) +#define XHCI_ERDP_EHB (1 << 3) /* Bit 3: Event Handler Busy */ +#define XHCI_ERDP_PTR_SHIFT (4) /* Bit 4-64: Event Ring Dequeue Pointer */ +#define XHCI_ERDP_PTR_MASK (0xfffffffffffffff << XHCI_ERDP_PTR_SHIFT) + +/* TRB definitions **********************************************************/ + +/* TRB common fields */ + +#define XHCI_TRB_D2_C (1 << 0) /* Bit 0: Cycle bit */ +#define XHCI_TRB_D2_TC (1 << 1) /* Bit 1: Toggle Cycle */ +#define XHCI_TRB_D2_CH (1 << 4) /* Bit 4: Chain bit */ +#define XHCI_TRB_D2_TYPE_SHIFT (10) /* Bits 10-15: TRB Type */ +#define XHCI_TRB_D2_TYPE_MASK (0x3f << XHCI_TRB_D2_TYPE_SHIFT) +#define XHCI_TRB_D2_TYPE_GET(x) (((x) & XHCI_TRB_D2_TYPE_MASK) >> XHCI_TRB_D2_TYPE_SHIFT) +#define XHCI_TRB_D2_TYPE_SET(x) (((x) << XHCI_TRB_D2_TYPE_SHIFT) & XHCI_TRB_D2_TYPE_MASK) + +/* TRB common fields for events */ + +#define XHCI_TRB_D1_CC_SHIFT (24) /* Bits 24-41: Completion Code */ +#define XHCI_TRB_D1_CC_MASK (0xff << XHCI_TRB_D1_CC_SHIFT) +#define XHCI_TRB_D1_CC_GET(x) (((x) & XHCI_TRB_D1_CC_MASK) >> XHCI_TRB_D1_CC_SHIFT) +#define XHCI_TRB_D1_CC_SET(x) (((x) << XHCI_TRB_D1_CC_SHIFT) & XHCI_TRB_D1_CC_MASK) +# define XHCI_TRB_CC_INVAL (0) /* Invalid */ +# define XHCI_TRB_CC_SUCCESS (1) /* Success */ +# define XHCI_TRB_CC_DATA_BUF (2) /* Data Buffer Error */ +# define XHCI_TRB_CC_BABBLE (3) /* Babble Detected Error */ +# define XHCI_TRB_CC_USBT (4) /* USB Transaction Error */ +# define XHCI_TRB_CC_TRB (5) /* TRB Error */ +# define XHCI_TRB_CC_STALL (6) /* Stall Error */ +# define XHCI_TRB_CC_RES (7) /* Resource Error */ +# define XHCI_TRB_CC_BW (8) /* Bandwidth Error */ +# define XHCI_TRB_CC_NOSLOT (9) /* No Slots Available Error */ +# define XHCI_TRB_CC_INVALSTR (10) /* Invalid Stream Type Error */ +# define XHCI_TRB_CC_SLOTNE (11) /* Slot Not Enabled Error */ +# define XHCI_TRB_CC_EPNE (12) /* Endpoint Not Enabled Error */ +# define XHCI_TRB_CC_SHORT_PKT (13) /* Short Packet */ +# define XHCI_TRB_CC_RUR (14) /* Ring Underrun */ +# define XHCI_TRB_CC_ROV (15) /* Ring Overrun */ +# define XHCI_TRB_CC_VFFULL (16) /* VF Event Ring Full Error */ +# define XHCI_TRB_CC_PARAM (17) /* Parameter Error */ +# define XHCI_TRB_CC_BWOR (18) /* Bandwidth Overrun Error */ +# define XHCI_TRB_CC_CTXST (19) /* Context State Error */ +# define XHCI_TRB_CC_NOPING (20) /* No Ping Response Error */ +# define XHCI_TRB_CC_ERFULL (21) /* Event Ring Full Error */ +# define XHCI_TRB_CC_INCOMP (22) /* Incompatible Device Error */ +# define XHCI_TRB_CC_MISSRV (23) /* Missed Service Error */ +# define XHCI_TRB_CC_CRSTOPPED (24) /* Command Ring Stopped */ +# define XHCI_TRB_CC_CMDABORT (25) /* Command Aborted */ +# define XHCI_TRB_CC_STOPPED (26) /* Stopped */ +# define XHCI_TRB_CC_STOPPED_LEN (27) /* Stopped - Length Invalid */ +# define XHCI_TRB_CC_STOPPED_SHORT (28) /* Stopped - Short Packet */ +# define XHCI_TRB_CC_MELTLE (29) /* Max Exit Latency Too Large Error */ + +/* Reference: + * - 6.4 Transfer Request Block (TRB) section + */ + +/* TRB common fields for commands and events */ + +#define XHCI_TRB_D2_SLOTID_SHIFT (24) /* Bits 24-31: Slot ID */ +#define XHCI_TRB_D2_SLOTID_MASK (0xff << XHCI_TRB_D2_SLOTID_SHIFT) +#define XHCI_TRB_D2_SLOTID_GET(x) (((x) & XHCI_TRB_D2_SLOTID_MASK) >> XHCI_TRB_D2_SLOTID_SHIFT) +#define XHCI_TRB_D2_SLOTID_SET(x) (((x) << XHCI_TRB_D2_SLOTID_SHIFT) & XHCI_TRB_D2_SLOTID_MASK) + +#define XHCI_TRB_D2_EP_SHIFT (16) /* Bits 16-20: Endpoint ID */ +#define XHCI_TRB_D2_EP_MASK (0x1f << XHCI_TRB_D2_EP_SHIFT) +#define XHCI_TRB_D2_EP_GET(x) (((x) & XHCI_TRB_D2_EP_MASK) >> XHCI_TRB_D2_EP_SHIFT) +#define XHCI_TRB_D2_EP_SET(x) (((x) << XHCI_TRB_D2_EP_SHIFT) & XHCI_TRB_D2_EP_MASK) + +/* Normal TRB */ + +#define XHCI_TRB_D1_TXLEN_SHIFT (0) /* Bits 9-16: TRB Transfer Length */ +#define XHCI_TRB_D1_TXLEN_MASK (0x1ffff << XHCI_TRB_D1_TXLEN_SHIFT) +#define XHCI_TRB_D1_TXLEN_SET(x) (((x) << XHCI_TRB_D1_TXLEN_SHIFT) & XHCI_TRB_D1_TXLEN_MASK) +#define XHCI_TRB_D1_TXLEN_GET(x) (((x) & XHCI_TRB_D1_TXLEN_MASK) >> XHCI_TRB_D1_TXLEN_SHIFT) +#define XHCI_TRB_D1_TDSIZE_SHIFT (17) /* Bits 17-21: TD Size */ +#define XHCI_TRB_D1_TDSIZE_MASK (0x1f << XHCI_TRB_D1_TDSIZE_SHIFT) +#define XHCI_TRB_D1_TDSIZE_SET(x) (((x) << XHCI_TRB_D1_TDSIZE_SHIFT) & XHCI_TRB_D1_TDSIZE_MASK) +#define XHCI_TRB_D1_IRQ_SHIFT (22) /* Bits 22-31: Interrupter Target */ +#define XHCI_TRB_D1_IRQ_MASK (0x3ff << XHCI_TRB_D1_IRQ_SHIFT) +#define XHCI_TRB_D1_IRQ_SET(x) (((x) << XHCI_TRB_D1_IRQ_SHIFT) & XHCI_TRB_D1_IRQ_MASK) + +#define XHCI_TRB_D2_ISP (1 << 2) /* Bit 2: Interrupt-on Short Packet */ +#define XHCI_TRB_D2_NS (1 << 3) /* Bit 3: No Snoop */ +#define XHCI_TRB_D2_CH (1 << 4) /* Bit 4: Chain bit */ +#define XHCI_TRB_D2_IOC (1 << 5) /* Bit 5: Interrupt On Completion */ +#define XHCI_TRB_D2_IDT (1 << 6) /* Bit 6: Immediate Data */ + /* Bits 7-8: Reserved */ +#define XHCI_TRB_D2_BEI (1 << 9) /* Bit 9: Block Event Interrupt */ + +/* Setup Stage TRB */ + +#define XHCI_TRB_D2_TRT_SHIFT (16) /* Bits 16-17: Transfer Type */ +#define XHCI_TRB_D2_TRT_MASK (0x3 << XHCI_TRB_D2_TRT_SHIFT) +#define XHCI_TRB_D2_TRT_SET(x) (((x) << XHCI_TRB_D2_TRT_SHIFT) & XHCI_TRB_D2_TRT_MASK) +# define XHCI_TRB_D2_TRT_NODATA (0) /* 0: No Data Stage */ + /* 1: reserved */ +# define XHCI_TRB_D2_TRT_OUTDATA (2) /* 2: OUT Data Stage */ +# define XHCI_TRB_D2_TRT_INDATA (3) /* 3: IN Data Stage */ + +/* Data Stage TRB */ + +#define XHCI_TRB_D2_DIR (1 << 16) /* Bit 16: Direction */ + +/* Isoch TRB */ + +#define XHCI_TRB_D2_SIA (1 << 31) /* Bit 31: Start Isoch ASAP */ + +/* Address Device Command TRB */ + +#define XHCI_TRB_D2_BSR (1 << 9) /* Bit 9: Block Set Address Request */ + +/* Configure Endpoint Command TRB */ + +#define XHCI_TRB_D2_DC (1 << 9) /* Bit 9: Deconfigure */ + +/* Stop Endpoint Command TRB */ + +#define XHCI_TRB_D2_SP (1 << 23) /* Bit 23: Suspend */ + +/* Command Ring and Transfer Ring Allowed */ + +#define XHCI_TRB_TYPE_RESERVED (0x00) /* Reserved */ +#define XHCI_TRB_TYPE_NORMAL (0x01) /* Normal */ +#define XHCI_TRB_TYPE_SETUP_STAGE (0x02) /* Setup Stage */ +#define XHCI_TRB_TYPE_DATA_STAGE (0x03) /* Data Stage */ +#define XHCI_TRB_TYPE_STAT_STAGE (0x04) /* Status Stage */ +#define XHCI_TRB_TYPE_ISOCH (0x05) /* Isoch */ +#define XHCI_TRB_TYPE_LINK (0x06) /* Link */ +#define XHCI_TRB_TYPE_EV_DATA (0x07) /* Event Data */ +#define XHCI_TRB_TYPE_NOOP (0x08) /* No-Op */ +#define XHCI_TRB_TYPE_EN_SLOT (0x09) /* Enable Slot Command */ +#define XHCI_TRB_TYPE_DIS_SLOT (0x0a) /* Disable Slot Command */ +#define XHCI_TRB_TYPE_ADDR_DEV (0x0b) /* Address Device Command */ +#define XHCI_TRB_TYPE_CFG_EP (0x0c) /* Configure Endpoint Command */ +#define XHCI_TRB_TYPE_EVAL_CTX (0x0d) /* Evaluate Context Command */ +#define XHCI_TRB_TYPE_RST_EP (0x0e) /* Reset Endpoint Command */ +#define XHCI_TRB_TYPE_STOP_EP (0x0f) /* Stop Endpoint Command */ +#define XHCI_TRB_TYPE_SET_TR_DEQ (0x10) /* Set TR Dequeue Pointer Command */ +#define XHCI_TRB_TYPE_RST_DEV (0x11) /* Reset Device Command */ +#define XHCI_TRB_TYPE_FORCE_EV (0x12) /* Force Event Command */ +#define XHCI_TRB_TYPE_NEG_BW (0x13) /* Negotiate Bandwidth Command */ +#define XHCI_TRB_TYPE_SET_LAT_TOL (0x14) /* Set Latency Tolerance Value Command */ +#define XHCI_TRB_TYPE_GET_PORT_BW (0x15) /* Get Port Bandwidth Command */ +#define XHCI_TRB_TYPE_FORCE_HDR (0x16) /* Force Header Command */ +#define XHCI_TRB_TYPE_NOOP_CMD (0x17) /* No Op Command */ +#define XHCI_TRB_TYPE_GET_EXTPROP (0x18) /* Get Extended Property Command */ +#define XHCI_TRB_TYPE_SET_EXTPROP (0x19) /* Set Extended Property Command */ + +/* Event Ring Allowed */ + +#define XHCI_TRB_EVT_TRANSFER (0x20) /* Transfer Event */ +#define XHCI_TRB_EVT_CMD_COMP (0x21) /* Command Completion Event */ +#define XHCI_TRB_EVT_PSTAT_CHANGE (0x22) /* Port Status Change Event */ +#define XHCI_TRB_EVT_BW_REQUEST (0x23) /* Bandwidth Request Event */ +#define XHCI_TRB_EVT_DOORBELL (0x24) /* Doorbell Event */ +#define XHCI_TRB_EVT_HOST_CTRL (0x25) /* Host Controller Event */ +#define XHCI_TRB_EVT_DEV_NOTIFY (0x26) /* Device Notification Event */ +#define XHCI_TRB_EVT_MFINDEX_WRP (0x27) /* MFINDEX Wrap Event */ + +/* Slot Context */ + +#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_SPEED_SHIFT (20) /* Bits 20:23: Speed */ +#define XHCI_ST_CTX0_SPEED_MASK (0xf << XHCI_ST_CTX0_SPEED_SHIFT) +#define XHCI_ST_CTX0_MTT (1 << 25) /* Bit 25: Multi-TT */ + /* Bit 24: Reserved */ +#define XHCI_ST_CTX0_HUB (1 << 26) /* Bit 26: Hub */ +#define XHCI_ST_CTX0_CTXENT_SHIFT (27) /* Bit 27-31: Context Entries */ +#define XHCI_ST_CTX0_CTXENT_MASK (0x1f << XHCI_ST_CTX0_CTXENT_SHIFT) +#define XHCI_ST_CTX0_CTXENT_SET(x) (((x) << XHCI_ST_CTX0_CTXENT_SHIFT) & XHCI_ST_CTX0_CTXENT_MASK) +#define XHCI_ST_CTX1_RHPN_SHIFT (16) /* Bit 16-23: Root Hub Port Number */ +#define XHCI_ST_CTX1_RHPN_MASK (0xff << XHCI_ST_CTX1_RHPN_SHIFT) +#define XHCI_ST_CTX1_RHPN_SET(x) (((x) << XHCI_ST_CTX1_RHPN_SHIFT) & XHCI_ST_CTX1_RHPN_MASK) +#define XHCI_ST_CTX1_PORTS_SHIFT (24) /* Bit 24-31: Number of Ports */ +#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) + +#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) +#define XHCI_ST_CTX3_ADDR_GET(x) (((x) & XHCI_ST_CTX3_ADDR_MASK) >> XHCI_ST_CTX3_ADDR_SHIFT ) + +/* Input Control Context */ + +#define XHCI_IN_CTX0_D(x) (1 << (x)) /* Drop Context flags */ +#define XHCI_IN_CTX1_A(x) (1 << (x)) /* Add Context flags */ +# define XHCI_SLOT_FLAG (0) +# define XHCI_EP0_FLAG (1) +# define XHCI_EP_FLAG(ep) (ep) /* IMPORTANT: to be used with Device Context Index (DCI) */ + +/* Endpoint Context */ + +#define XHCI_EP_CTX0_EPSTATE_SHIFT (0) /* Bits 0-2: Endpoint State */ +#define XHCI_EP_CTX0_EPSTATE_MASK (7 << XHCI_EP_CTX0_EPSTATE_SHIFT) +#define XHCI_EP_CTX0_EPSTATE_GET(x) ((x) & XHCI_EP_CTX0_EPSTATE_MASK) >> XHCI_EP_CTX0_EPSTATE_SHIFT) + /* Bits 3-7: Reserved */ +#define XHCI_EP_CTX0_MULT_SHIFT (8) /* Bits 8-9: Mult */ +#define XHCI_EP_CTX0_MULT_MASK (3 << XHCI_EP_CTX0_MULT_SHIFT) +#define XHCI_EP_CTX0_MULT(x) (((x) << XHCI_EP_CTX0_MULT_SHIFT) & XHCI_EP_CTX0_MULT_MASK) + +#define XHCI_EP_CTX0_MAXPSTR_SHIFT (10) /* Bits 10-14: Max Primary Streams (MaxPStreams) */ +#define XHCI_EP_CTX0_MAXPSTR_MASK (0x1f << XHCI_EP_CTX0_MAXPSTR_SHIFT) +#define XHCI_EP_CTX0_MAXPSTR(x) (((x) << XHCI_EP_CTX0_MAXPSTR_SHIFT) & XHCI_EP_CTX0_MAXPSTR_MASK) + +#define XHCI_EP_CTX0_LSA (1 << 15) /* Bit 15: Linear Stream Array */ +#define XHCI_EP_CTX0_INTERVAL_SHIFT (16) /* Bits 16-23: Interval */ +#define XHCI_EP_CTX0_INTERVAL_MASK (0xff << XHCI_EP_CTX0_INTERVAL_SHIFT) +#define XHCI_EP_CTX0_INTERVAL(x) (((x) << XHCI_EP_CTX0_INTERVAL_SHIFT) & XHCI_EP_CTX0_INTERVAL_MASK) +#define XHCI_EP_CTX0_MESITHI_SHIFT (24) /* Bits 24-31: Max Endpoint Service Time Interval Payload High */ + +#define XHCI_EP_CTX1_CERR_SHIFT (1) /* Bits 1-2: Error Count */ +#define XHCI_EP_CTX1_CERR_MASK (3 << XHCI_EP_CTX1_CERR_SHIFT) +#define XHCI_EP_CTX1_CERR(x) (((x) << XHCI_EP_CTX1_CERR_SHIFT) & XHCI_EP_CTX1_CERR_MASK) + +#define XHCI_EP_CTX1_EPTYPE_SHIFT (3) /* Bits 3-5: Endpoint Type */ +#define XHCI_EP_CTX1_EPTYPE_MASK (7 << XHCI_EP_CTX1_EPTYPE_SHIFT) +#define XHCI_EP_CTX1_EPTYPE(x) (((x) << XHCI_EP_CTX1_EPTYPE_SHIFT) & XHCI_EP_CTX1_EPTYPE_MASK) +# define XHCI_EPTYPE_INVAL (0) /* Invalid */ +# define XHCI_EPTYPE_ISO_OUT (1) /* Isoch Out */ +# define XHCI_EPTYPE_BULK_OUT (2) /* Bulk Out */ +# define XHCI_EPTYPE_INTR_OUT (3) /* Interrupt Out */ +# define XHCI_EPTYPE_CTRL (4) /* Control Bidirectional */ +# define XHCI_EPTYPE_ISO_IN (5) /* Isoch In */ +# define XHCI_EPTYPE_BULK_IN (6) /* Bulk In */ +# define XHCI_EPTYPE_INTR_IN (7) /* Interrupt In */ +#define XHCI_EP_CTX1_HID (1 << 7) /* Bit 7: Host Initiate Disable */ +#define XHCI_EP_CTX1_MAXBRST_SHIFT (8) /* Bits 8-15: Max Burst Size */ +#define XHCI_EP_CTX1_MAXBRST_MASK (0xff << XHCI_EP_CTX1_MAXBRST_SHIFT) +#define XHCI_EP_CTX1_MAXBRST(x) (((x) << XHCI_EP_CTX1_MAXBRST_SHIFT) & XHCI_EP_CTX1_MAXBRST_MASK) +#define XHCI_EP_CTX1_MAXPKT_SHIFT (16) /* Bits 16-31: Max Packet Size */ +#define XHCI_EP_CTX1_MAXPKT_MASK (0xffff << XHCI_EP_CTX1_MAXPKT_SHIFT) +#define XHCI_EP_CTX1_MAXPKT(x) (((x) << XHCI_EP_CTX1_MAXPKT_SHIFT) & XHCI_EP_CTX1_MAXPKT_MASK) + +#define XHCI_EP_CTX2_DCS (1 << 0) /* Bit 1: Dequeue Cycle State */ + /* Bits 1-3: Reserved */ +#define XHCI_EP_CTX2_TRDP_SHIFT (4) /* Bits 4-63: TR Dequeue Pointer */ + +#define XHCI_EP_CTX3_AVGTRB_SHIFT (0) /* Bits 0-15: Average TRB Length */ +#define XHCI_EP_CTX3_MESITLO_SHIFT (16) /* Bits 16-31: Max Endpoint Service Time Interval Payload Low */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Common representation for TRB */ + +begin_packed_struct struct xhci_trb_s +{ + uint64_t d0; /* Parameter */ + uint32_t d1; /* Status */ + uint32_t d2; /* Control */ +} end_packed_struct; + +/* Event ring register */ + +begin_packed_struct struct xhci_event_ring_s +{ + uint64_t base; + uint32_t size; + uint32_t res; +} end_packed_struct; + +/* Input Control Context. + * + * Reference: + * - 6.2.5.1: Input Control Context + */ + +begin_packed_struct struct xhci_input_ctx_s +{ + uint32_t ctx[8]; +} end_packed_struct; + +/* Slot Context */ + +begin_packed_struct struct xhci_slot_ctx_s +{ + uint32_t ctx[8]; +} end_packed_struct; + +/* EP Context */ + +begin_packed_struct struct xhci_ep_ctx_s +{ + uint32_t ctx0; + uint32_t ctx1; + uint64_t ctx2; /* Need 64 here for pointers! */ + uint32_t ctx3; + uint32_t ctx4; + uint32_t ctx5; + uint32_t ctx6; +} end_packed_struct; + +/* Device Context. */ + +begin_packed_struct struct xhci_dev_ctx_s +{ + struct xhci_slot_ctx_s slot; /* Slot Context */ + struct xhci_ep_ctx_s ep[XHCI_MAX_ENDPOINTS]; /* EP Context */ +} end_packed_struct; + +/* Device Input Context. + * + * Reference: + * - Figure 6-5: Input Context + */ + +begin_packed_struct struct xhci_input_dev_ctx_s +{ + struct xhci_input_ctx_s input; /* Input Control Context */ + struct xhci_slot_ctx_s slot; /* Slot Context */ + struct xhci_ep_ctx_s ep[XHCI_MAX_ENDPOINTS]; /* EP Context */ +} end_packed_struct; + +#endif /* #define __DRIVERS_USBHOST_USBHOST_XHCI_H */ diff --git a/drivers/usbhost/usbhost_xhci_pci.c b/drivers/usbhost/usbhost_xhci_pci.c new file mode 100644 index 00000000000..4dd9aae9857 --- /dev/null +++ b/drivers/usbhost/usbhost_xhci_pci.c @@ -0,0 +1,4852 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_xhci_pci.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "usbhost_xhci.h" +#include "usbhost_xhci_trace.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Pre-requisites */ + +#if CONFIG_USBHOST_XHCI_MAX_DEVS > XHCI_MAX_DEVS +# error Invalid value for CONFIG_USBHOST_XHCI_MAX_DEVS +#endif + +/* USB HUB support is not yet implemented */ + +#ifdef CONFIG_USBHOST_HUB +# error XHCI USB HUB support is not yet implemented +#endif + +/* Some constants for this implementation */ + +#define XHCI_MAX_ERST (1) +#define XHCI_CMD_MAX (16) +#define XHCI_EVENT_MAX (232) +#define XHCI_TD_MAX (8) +#define XHCI_BUFSIZE (512) + +/* Port numbers macros */ + +#define HPNDX(hp) ((hp)->port) +#define HPORT(hp) (HPNDX(hp) + 1) +#define RHPNDX(rh) ((rh)->hport.hport.port) +#define RHPORT(rh) (RHPNDX(rh) + 1) + +/* Other helper macros */ + +#define XHCI_XCONN_FROM_CONN(c) ((FAR struct usbhost_conn_xhci_s *)c) +#define XHCI_PRIV_FROM_CONN(c) (XHCI_XCONN_FROM_CONN(c)->priv) +#define XHCI_RHPORT_FROM_DRVR(d) ((FAR struct xhci_rhport_s *)d) +#define XHCI_PRIV_FROM_RHPORT(r) (r->priv) +#define XHCI_PRIV_FROM_DRVR(d) (XHCI_PRIV_FROM_RHPORT(XHCI_RHPORT_FROM_DRVR(d))) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* USB device state + * + * Reference: + * - 4.5.3: Slot States + */ + +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 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); +static int pci_xhci_probe(FAR struct pci_device_s *dev); +static void pci_xhci_remove(FAR struct pci_device_s *dev); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* PCI device table */ + +static const struct pci_device_id_s g_pci_xhci_id_table[] = +{ + /* QEMU xHCI */ + + { + PCI_DEVICE(0x1b36, 0x000d), + .driver_data = 0 + }, + + /* Intel Alder Lake USB 3.2 xHCI controller */ + + { + PCI_DEVICE(0x8086, 0x51ed), + .driver_data = 0 + }, + + /* Intel Alder Lake-S USB 3.2 Gen 2x2 xHCI controller */ + + { + PCI_DEVICE(0x8086, 0x7ae0), + .driver_data = 0 + }, + { } +}; + +/* PCI driver */ + +static struct pci_driver_s g_pci_xhci_drv = +{ + .id_table = g_pci_xhci_id_table, + .probe = pci_xhci_probe, + .remove = pci_xhci_remove, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: xhci_capa_getreg + * + * Description: + * Get register (USB Legacy Support Capability) + * + ****************************************************************************/ + +static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, + unsigned int offset) +{ + uintptr_t addr = priv->capa_base + offset; + return *((FAR volatile uint32_t *)addr); +} + +/**************************************************************************** + * Name: xhci_capa_getreg_1b + * + * Description: + * Get 1B register (USB Legacy Support Capability) + * + ****************************************************************************/ + +static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, + unsigned int offset) +{ + uintptr_t addr = priv->capa_base + offset; + return *((FAR volatile uint8_t *)addr); +} + +/**************************************************************************** + * Name: xhci_capa_putreg_1b + * + * Description: + * Put 1B register (USB Legacy Support Capability) + * + ****************************************************************************/ + +static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, + unsigned int offset, + uint8_t value) +{ + uintptr_t addr = priv->capa_base + offset; + *((FAR volatile uint8_t *)addr) = value; +} + +/**************************************************************************** + * Name: xhci_oper_getreg + * + * 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; + return *((FAR volatile uint32_t *)addr); +} + +/**************************************************************************** + * 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; + *((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; + *((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; + return *((FAR volatile uint32_t *)addr); +} + +/**************************************************************************** + * 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; + *((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; + *((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; + *((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) +{ + pciinfo("\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; + + pciinfo("Dump xHCI registers: %s\n", msg); + + 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); + + 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); + + for (i = 0; i < priv->no_ports; i++) + { + 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)); + } + + /* Only one interrupter used */ + + 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 + +/**************************************************************************** + * 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; + + pciinfo("Start controller\n"); + + /* Reset controller before writing any Operational or Runtime registers */ + + ret = xhci_ctrl_reset(priv); + if (ret < 0) + { + usbhost_trace1(XHCI_TRACE1_RESET_FAILED, 0); + return ret; + } + + /* TODO: clear interrupts and disable all device notifications */ + + /* Max Device Slots Enabled */ + + xhci_oper_putreg(priv, XHCI_CONFIG, priv->no_slots); + + /* Slot 0 in Device Context is reserved for Scratchpad Buffer Array */ + + priv->pg_ctx[0] = htole64(up_addrenv_va_to_pa(priv->pg_sb)); + + /* Device Context Base Address Array Pointer */ + + xhci_oper_putreg_8b(priv, XHCI_DCBAAP, up_addrenv_va_to_pa(priv->pg_ctx)); + + /* Event Ring Segment Table Size */ + + xhci_runt_putreg(priv, XHCI_ERSTSZ(0), priv->no_erst); + + /* Initialize command ring state and event ring state */ + + ret = xhci_ring_init(&priv->cmd, XHCI_CMD_MAX); + if (ret < 0) + { + pcierr("cmd ring init failed\n"); + return ret; + } + + ret = xhci_ring_init(&priv->evnt, XHCI_EVENT_MAX); + if (ret < 0) + { + pcierr("event ring init failed\n"); + return ret; + } + + /* Configure Event Ring */ + + evnt = (struct xhci_event_ring_s *)priv->pg_erst; + evnt->base = htole64(up_addrenv_va_to_pa(priv->evnt.ring)); + evnt->size = XHCI_EVENT_MAX; + evnt->res = 0; + + /* Flush all memory before write to ERDP so xhci sees correct data */ + + up_flush_dcache_all(); + + xhci_runt_putreg_8b(priv, XHCI_ERDP(0), + up_addrenv_va_to_pa(priv->evnt.ring)); + + /* Write ERSTBA with ERST(0).BaseAddress. + * + * This must be done after ERST[0] initialization and after write to + * ERSTSZ. When the ERSTBA register is written, the Event Ring State + * Machine is set to the Start state. + * + * For details look at "4.9.4 Event Ring Management" + */ + + xhci_runt_putreg_8b(priv, XHCI_ERSTBA(0), + up_addrenv_va_to_pa(priv->pg_erst)); + + /* Last item in the command ring points to the beginning of the ring */ + + priv->cmd.ring[XHCI_CMD_MAX - 1].d0 = htole64( + up_addrenv_va_to_pa(priv->cmd.ring)); + + /* Configure the Command Ring */ + + xhci_oper_putreg_8b(priv, XHCI_CRCR, + up_addrenv_va_to_pa(priv->cmd.ring) | XHCI_CRCR_RCS); + + /* Enable interrupts */ + + regval = xhci_runt_getreg(priv, XHCI_IMAN(0)); + regval |= XHCI_IMAN_IE; + xhci_runt_putreg(priv, XHCI_IMAN(0), regval); + + /* Flush all memory once again */ + + up_flush_dcache_all(); + + /* Turn the host controller ON, enable interrupts and system errors */ + + xhci_oper_putreg(priv, XHCI_USBCMD, + XHCI_USBCMD_RS | + XHCI_USBCMD_INTE | + XHCI_USBCMD_HSEE); + + /* Wait for controller started */ + + ret = -EAGAIN; + for (i = 0; i < 10; i++) + { + up_mdelay(100); + + if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH)) + { + ret = OK; + break; + } + } + + /* Check for timeout */ + + if (ret != OK) + { + pcierr("Can't start controller!"); + return ret; + } + + /* Poll all pending events */ + + xhci_events_poll(priv); + + return OK; +} + +/**************************************************************************** + * Name: xhci_ctrl_halt + * + * Description: + * Halt controller. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv) +{ + int ret = -EAGAIN; + int i; + + /* Halt controller */ + + xhci_oper_putreg(priv, XHCI_USBCMD, 0); + + /* Wait for controller halted */ + + for (i = 0; i < 10; i++) + { + up_mdelay(100); + + if (xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH) + { + ret = OK; + break; + } + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_ctrl_reset + * + * Description: + * Reset controller. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv) +{ + int ret = -EAGAIN; + int i; + + /* Halt controller */ + + xhci_oper_putreg(priv, XHCI_USBCMD, XHCI_USBCMD_HCRST); + + /* Wait for controller halted */ + + for (i = 0; i < 10; i++) + { + up_mdelay(100); + + if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_CNR)) + { + ret = OK; + break; + } + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_probe_ports + * + * Description: + * Initial ports probe. + * + ****************************************************************************/ + +static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv) +{ + uint32_t portsc; + int i; + + for (i = 0; i < priv->no_ports; i++) + { + portsc = xhci_oper_getreg(priv, XHCI_PORTSC(i)); + priv->rhport[i].connected = ((portsc & XHCI_PORTSC_CCS) != 0); + + /* Clear status change */ + + xhci_oper_putreg(priv, XHCI_PORTSC(i), portsc); + } +} + +/**************************************************************************** + * Name: xhci_port_enable + * + * Description: + * Set port to the Enable state. + * + ****************************************************************************/ + +static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, + FAR struct usbhost_hubport_s *hport) +{ + uint32_t retries; + uint32_t regval; + uint8_t speed; + int rhpndx; + + DEBUGASSERT(hport != NULL); + rhpndx = hport->port; + + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + + /* A USB3 protocol port attempts to automatically advance to the + * Enabled state for port as part of the attach process. + */ + + if (!(regval & XHCI_PORTSC_PED)) + { + /* Reset the port */ + + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + regval |= XHCI_PORTSC_PR; + xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), regval); + + /* REVISIT: we get Port Status Change Event here */ + + /* Wait for Enabled state for port */ + + retries = 10; + while (!(xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)) + & XHCI_PORTSC_PED) && retries > 0) + { + retries--; + up_mdelay(100); + } + + if (retries == 0) + { + return -ETIMEDOUT; + } + } + + /* Get port status */ + + regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + + /* Get port speed */ + + speed = XHCI_PORTSC_PS(regval); + switch (speed) + { + case XHCI_PORTSC_PS_FULL: + { + hport->speed = USB_SPEED_FULL; + break; + } + + case XHCI_PORTSC_PS_LOW: + { + hport->speed = USB_SPEED_LOW; + break; + } + + case XHCI_PORTSC_PS_HIGH: + { + hport->speed = USB_SPEED_HIGH; + break; + } + + case XHCI_PORTSC_PS_SUPPER11: + { + hport->speed = USB_SPEED_SUPER; + break; + } + + case XHCI_PORTSC_PS_SUPPER21: + case XHCI_PORTSC_PS_SUPPER12: + case XHCI_PORTSC_PS_SUPPER22: + { + hport->speed = USB_SPEED_SUPER_PLUS; + break; + } + + default: + { + pcierr("speed = 0x%x\n", speed); + hport->speed = USB_SPEED_UNKNOWN; + return -EINVAL; + } + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_dcbaa_set + * + * Description: + * Set entry in the Device Context Base Address Array, which should point + * to the Output Device Context data structure. + * + ****************************************************************************/ + +static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, + uintptr_t ctx) +{ + /* NOTE: context must be physical address! */ + + priv->pg_ctx[index] = htole64(ctx); + + /* Flush context */ + + up_flush_dcache((uintptr_t)priv->pg_ctx, + (uintptr_t)(priv->pg_ctx + priv->no_slots + 1)); +} + +/**************************************************************************** + * Name: xhci_ep_configure + * + * Description: + * Configure endpoint context. + * + * Reference: + * - 4.8.2 Endpoint Context Initialization + * - 6.2.3 Endpoint Context + * + ****************************************************************************/ + +static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_ep_ctx_s *ctx, + uint8_t type, uint16_t maxpkt, + uint8_t maxburst, uint64_t tr_dp, + uint8_t mult, uint8_t interval) +{ + uint32_t ctx0 = 0; + uint32_t ctx1 = 0; + uint64_t ctx2 = 0; + + /* Set type */ + + ctx1 |= XHCI_EP_CTX1_EPTYPE(type); + + /* Set max packet size */ + + ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpkt); + + /* Set max burst size */ + + ctx1 |= XHCI_EP_CTX1_MAXBRST(maxburst); + + /* Set TR Dequeue Pointer. + * NOTE: must be physical address aligned to 16-byte. + */ + + DEBUGASSERT(tr_dp != 0 && tr_dp % 16 == 0); + ctx2 = tr_dp; + + /* Set DCS. + * Should be set to 1 only if no stream (USB3.0 specific). + */ + + ctx2 |= XHCI_EP_CTX2_DCS; + + /* Set interval */ + + ctx0 |= XHCI_EP_CTX0_INTERVAL(interval); + + /* Set max primary streams. + * Set to zero for now (USB3.0 specific) + */ + + ctx0 |= XHCI_EP_CTX0_MAXPSTR(0); + + /* Set mult */ + + ctx0 |= XHCI_EP_CTX0_MULT(mult); + + /* Set error count to 3 if this is not ISOCH endpoint */ + + if (type != XHCI_EPTYPE_ISO_OUT && type != XHCI_EPTYPE_ISO_IN) + { + ctx1 |= XHCI_EP_CTX1_CERR(3); + } + + /* Write context */ + + ctx->ctx0 = htole32(ctx0); + ctx->ctx1 = htole32(ctx1); + ctx->ctx2 = htole64(ctx2); + + /* Flush context */ + + up_flush_dcache((uintptr_t)ctx, + (uintptr_t)ctx + sizeof(struct xhci_ep_ctx_s)); +} + +/**************************************************************************** + * Name: xhci_address_set + * + * Description: + * Set address request. + * + * If setaddr is true, then xHC issue a SET_ADDRESS request. + * + ****************************************************************************/ + +static int xhci_address_set(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_rhport_s *rhport, bool setaddr) +{ + FAR struct xhci_dev_s *dev; + uint64_t ctx; + + dev = rhport->dev; + ctx = up_addrenv_va_to_pa(dev->input); + + return xhci_cmd_setaddr(priv, rhport->slot, ctx, !setaddr); +} + +/**************************************************************************** + * Name: xhci_slot_init + * + * Description: + * Initialize Device Slot data. + * + * Assumption: + * 1. All slot resources already allocated. + * 2. Port is in Enabled state. + * + * Reference: + * - 4.3.3. Device Slot Initialization + * + ****************************************************************************/ + +static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev) +{ + uint32_t regval; + uint16_t maxpkt; + uintptr_t drdp; + + /* Step 1. The Input Context data structure already allocated. + * Initialize all fields to 0. + */ + + memset(dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); + + /* Step 2. Initialize the Input Control Context by setting the A0 and + * A1 flags to 1 (Slot flag and EP0 flag). + */ + + regval = XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | + XHCI_IN_CTX1_A(XHCI_EP0_FLAG); + xhci_context_ctrl(priv, dev, 0, regval); + + /* Step 3. Initialize the Input Slot Context */ + + regval = XHCI_ST_CTX0_CTXENT_SET(1); + +#ifdef CONFIG_USBHOST_HUB + /* TODO: + * 1. Activate the transaction translator if required + * 2. Configure hub bit in slot context if hub + * 3. configure route string + */ + +# warning missing logic +#endif + + dev->input->slot.ctx[0] = htole32(regval); + + /* Configure Root Hub Port Number (starts from 1) */ + + regval = XHCI_ST_CTX1_RHPN_SET(RHPNDX(dev->rhport) + 1); + + /* TODO: configure number of ports */ + + regval |= XHCI_ST_CTX1_PORTS_SET(0); + dev->input->slot.ctx[1] = htole32(regval); + + /* Step 4. the Transfer Ring for the Default Control Endpoint is already + * allocated. + */ + + drdp = up_addrenv_va_to_pa(dev->rhport->ep0.td.ring); + + /* Step 5. Initialize the Input default control Endpoint 0 Context */ + + DEBUGASSERT(dev->rhport != NULL); + if (dev->rhport->hport.hport.speed == USB_SPEED_HIGH) + { + /* For high-speed, we must use 64 bytes */ + + maxpkt = 64; + } + else + { + /* Eight will work for both low- and full-speed */ + + maxpkt = 8; + } + + DEBUGASSERT(drdp != 0); + xhci_ep_configure(priv, + &dev->input->ep[0], + XHCI_EPTYPE_CTRL, maxpkt, + 0, drdp, + 0, 0); + + /* Step 6. The output Device Context data structure already allocated. + * Initialize all fields to 0. + */ + + memset(dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); + + /* Flush Device input context */ + + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + + sizeof(struct xhci_input_dev_ctx_s)); + + /* Step 7. Load the appropriate (Device Slot ID) entry in the Device + * Context Base Address Array with a pointer to the Output Device + * Context data structure + */ + + xhci_dcbaa_set(priv, dev->slot, up_addrenv_va_to_pa(dev->ctx)); + + return OK; +} + +/**************************************************************************** + * Name: xhci_device_init + * + * Description: + * Initialize Device. + * + * Assumption: + * 1. All device resources already allocated. + * 2. Port is in Enabled state. + * + * Reference: + * - 4.3: USB Device Initialization + * + ****************************************************************************/ + +static int xhci_device_init(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_rhport_s *rhport) +{ + FAR struct xhci_dev_s *dev; + uint8_t slot; + int ret; + + /* We enter this function after steps 1-3 from "USB Device Initialization" + * are done. + */ + + /* Step 4: Get Device Slot. + * + * Reference: + * - 4.3.2: Device Slot Assignment + */ + + ret = xhci_cmd_sloten(priv, &slot); + if (ret < 0 || slot > priv->no_slots) + { + /* Something goes wrong ! */ + + usbhost_vtrace1(XHCI_TRACE1_SLOTEN_FAILED, ret); + return ret; + } + + /* Slot ID is an index to the identify Device data */ + + rhport->dev = &priv->devs[slot - 1]; + rhport->slot = slot; + dev = rhport->dev; + + /* Slot has been allocated to software and is now in Enabled state */ + + dev->state = XHCI_SLOT_ENABLED; + + /* Step 5: Initialize the data structures associated with the slot. + * All data structured are already allocated. + */ + + ret = xhci_ring_init(&rhport->ep0.td, XHCI_TD_MAX); + if (ret < 0) + { + pcierr("ep0 ring init failed\n"); + return ret; + } + + rhport->ep0.slot = slot; + dev->rhport = rhport; + dev->slot = slot; + dev->epinfo[0] = &rhport->ep0; + + ret = xhci_slot_init(priv, dev); + if (ret < 0) + { + return ret; + } + + /* Step 6: Assign and address to the device and enable its Default + * Control Endpoint. + * + * NOTE: we don't send SET_ADDRESS request here. + * This is done in xhci_ctrlin() and controlled by NuttX USB Host + * stack. + */ + + ret = xhci_address_set(priv, rhport, false); + if (ret < 0) + { + pcierr("failed to set address %d\n", ret); + return ret; + } + + /* Steps 7-12 don't belong here! */ + + return OK; +} + +/**************************************************************************** + * Name: xhci_device_deinit + * + * Description: + * Free Device. + * + * Reference: + * - 4.3: USB Device Initialization + * + ****************************************************************************/ + +static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_rhport_s *rhport) +{ + uint8_t slot = rhport->slot; + int ret; + + /* Disable Slot */ + + ret = xhci_cmd_slotdis(priv, slot); + if (ret < 0) + { + pcierr("xhci_cmd_slotdis failed %d\n", ret); + } + + /* Clear DCBAA entry for this slot */ + + xhci_dcbaa_set(priv, slot, 0); + + /* Clean up device data, but don't touch allocated memory! */ + + rhport->dev->state = XHCI_SLOT_DISABLED; + + memset(rhport->dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); + memset(rhport->dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); + + /* Remove reference to a device slot */ + + rhport->dev = NULL; + + return OK; +} + +/**************************************************************************** + * Name: xhci_epno_get + * + * Description: + * Get EP index for a given endpoint. + * + * Returns Device Context Index (DCI). + * + ****************************************************************************/ + +static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo) +{ + DEBUGASSERT(epinfo); + + if (epinfo->epno == 0) + { + return 1; + } + + if (epinfo->dirin) + { + return epinfo->epno * 2 + 1; + } + else + { + return epinfo->epno * 2; + } +} + +/**************************************************************************** + * Name: xhci_context_ctrl + * + * Description: + * Configure Input Control Context, which defines which Device Context + * data structures are affected by a command. + * + * Assumption: + * Input Context must be flushed by caller. + * + ****************************************************************************/ + +static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_dev_s *dev, + uint32_t drop, uint32_t add) +{ + int i; + + dev->input->input.ctx[0] = htole32(drop); + dev->input->input.ctx[1] = htole32(add); + + /* Update Context Entries in Slot */ + + for (i = 31; i != 1; i--) + { + if (add & (1 << i)) + { + break; + } + } + + dev->input->slot.ctx[0] &= ~XHCI_ST_CTX0_CTXENT_MASK; + dev->input->slot.ctx[0] |= XHCI_ST_CTX0_CTXENT_SET(i); +} + +/**************************************************************************** + * Name: xhci_command + * + * Description: + * Issue a xHCI command. + * + * NOTE: + * trb data in host specific byte order. This function converts it + * to a correct order + * + ****************************************************************************/ + +static int xhci_command(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *trb, uint16_t timeout_ms) +{ + int ret; + + /* Lock bus */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Add command to ring */ + + xhci_add_trb(priv, &priv->cmd, trb, 1); + + /* Ringing the Host Controller Doorbell */ + + xhci_door_putreg(priv, XHCI_DOORBEL(0), 0); + + /* Wait for Command Completion Event */ + + ret = nxsem_tickwait_uninterruptible(&priv->cmdsem, + MSEC2TICK(timeout_ms)); + if (ret < 0) + { + /* Check for missed interrupts */ + + xhci_events_poll(priv); + } + + /* Return command results */ + + trb->d0 = priv->cmdres.d0; + trb->d1 = priv->cmdres.d1; + trb->d2 = priv->cmdres.d2; + + if (XHCI_TRB_D1_CC_GET(trb->d1) != XHCI_TRB_CC_SUCCESS) + { + pcierr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1)); + ret = -EIO; + } + + /* Clean response */ + + priv->cmdres.d0 = 0; + priv->cmdres.d1 = 0; + priv->cmdres.d2 = 0; + + /* Unlock bus */ + + nxmutex_unlock(&priv->lock); + + return ret; +} + +/**************************************************************************** + * Name: xhci_cmd_sloten + * + * Description: + * Enable Slot Command. + * + ****************************************************************************/ + +static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, + FAR uint8_t *slot) +{ + struct xhci_trb_s trb; + int ret; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = 0; + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EN_SLOT); + + ret = xhci_command(priv, &trb, 1000); + if (ret < 0) + { + *slot = 0; + return ret; + } + + /* Return available slot */ + + *slot = XHCI_TRB_D2_SLOTID_GET(le32toh(trb.d2)); + + return OK; +} + +/**************************************************************************** + * Name: xhci_cmd_slotdis + * + * Description: + * Disable Slot Command. + * + ****************************************************************************/ + +static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = 0; + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DIS_SLOT) | + XHCI_TRB_D2_SLOTID_SET(slot); + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_setaddr + * + * Description: + * Address Device Command. + * + ****************************************************************************/ + +static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx, bool bsr) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = htole64(ctx); + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ADDR_DEV) | + XHCI_TRB_D2_SLOTID_SET(slot); + + if (bsr) + { + trb.d2 |= XHCI_TRB_D2_BSR; + } + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_cfgep + * + * Description: + * Configure EP Command. + * + ****************************************************************************/ + +static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx, bool deconfig) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = htole64(ctx); + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_CFG_EP) | + XHCI_TRB_D2_SLOTID_SET(slot); + + if (deconfig) + { + trb.d2 |= XHCI_TRB_D2_DC; + } + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_stopep + * + * Description: + * Stop endpoint + * + ****************************************************************************/ + +static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint8_t ep, bool suspend) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = 0; + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) | + XHCI_TRB_D2_SLOTID_SET(slot) | XHCI_TRB_D2_EP_SET(ep); + + if (suspend) + { + trb.d2 |= XHCI_TRB_D2_SP; + } + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_cmd_evalctx + * + * Description: + * Evaluate Context Command + * + ****************************************************************************/ + +static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, + uint64_t ctx) +{ + struct xhci_trb_s trb; + + /* Host specific byte order. Conversion done by xhci_command() */ + + trb.d0 = htole64(ctx); + trb.d1 = 0; + trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EVAL_CTX) | + XHCI_TRB_D2_SLOTID_SET(slot); + + return xhci_command(priv, &trb, 100); +} + +/**************************************************************************** + * Name: xhci_ep_doorbell + * + * Description: + * Ring doorbell associated with a given endpoint. + * + ****************************************************************************/ + +static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo) +{ + uint8_t target = xhci_epno_get(epinfo); + uint32_t regval = 0; + + /* Doorbel tareget is EP number */ + + regval |= XHCI_DOORBEL_TARGET(target); + + /* Streams not supported yet (USB3.0 specific) */ + + regval |= XHCI_DOORBEL_TASK(0); + + /* Ring doorbell */ + + xhci_door_putreg(priv, XHCI_DOORBEL(epinfo->slot), regval); +} + +/**************************************************************************** + * Name: xhci_ioc_setup + * + * Description: + * Set the request for the IOC event well BEFORE enabling the transfer (as + * soon as we are absolutely committed to the transfer). We do + * this to minimize race conditions. This logic would have to be expanded + * if we want to have more than one packet in flight at a time! + * + * Assumption: + * The caller holds the XHCI lock + * + ****************************************************************************/ + +static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + irqstate_t flags; + int ret = -ENODEV; + + DEBUGASSERT(rhport && epinfo && !epinfo->iocwait); +#ifdef CONFIG_USBHOST_ASYNCH + DEBUGASSERT(epinfo->callback == NULL); +#endif + + /* Is the device still connected? */ + + flags = spin_lock_irqsave(&priv->spinlock); + if (rhport->connected) + { + /* Then set iocwait to indicate that we expect to be informed when + * either (1) the device is disconnected, or (2) the transfer + * completed. + */ + + epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */ + epinfo->status = 0; /* No status yet */ + epinfo->xfrd = 0; /* Nothing transferred yet */ + epinfo->buflen = buflen; /* Buffer length */ + epinfo->result = -EBUSY; /* Transfer in progress */ +#ifdef CONFIG_USBHOST_ASYNCH + epinfo->callback = NULL; /* No asynchronous callback */ + epinfo->arg = NULL; +#endif + ret = OK; /* We are good to go */ + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + return ret; +} + +/**************************************************************************** + * Name: xhci_ioc_wait + * + * Description: + * Wait for the IOC event. + * + * Assumption: + * The caller does *NOT* hold the xHCI lock. That would cause a deadlock + * when the bottom-half, worker thread needs to take the semaphore. + * + ****************************************************************************/ + +static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo) +{ + int ret = OK; + + /* Wait for the IOC event. Loop to handle any false alarm semaphore + * counts. Return an error if the task is canceled. + */ + + while (epinfo->iocwait) + { + ret = nxsem_wait_uninterruptible(&epinfo->iocsem); + if (ret < 0) + { + break; + } + } + + return ret < 0 ? ret : epinfo->result; +} + +/**************************************************************************** + * Name: xhci_control_setup + * + * Description: + * Process a IN or OUT request control ep. + * This function will enqueue the request and wait for it to + * complete. Bulk data transfers differ in that req == NULL and there are + * not SETUP or STATUS phases. + * + * This is a blocking function; it will not return until the control + * transfer has completed. + * + * Assumption: + * The caller holds the xHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is return on + * any failure. + * + ****************************************************************************/ + +static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer, size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + struct xhci_trb_s trb[3]; + uint8_t trt; + int i = 0; + + /* Prepare Setup Stage TRB */ + + trb[i].d0 = *((FAR uint64_t *)req); + trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(8); + trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | + XHCI_TRB_D2_IDT; + + /* Reference: + * - Table 4-7: USB SETUP Data to Data Stage TRB and Status Stage + * TRB mapping + */ + + if (req->type & USB_REQ_DIR_IN) + { + trt = XHCI_TRB_D2_TRT_INDATA; + } + else if (req->type & USB_REQ_DIR_OUT) + { + trt = XHCI_TRB_D2_TRT_OUTDATA; + } + else + { + trt = XHCI_TRB_D2_TRT_NODATA; + } + + trb[i].d2 |= XHCI_TRB_D2_TRT_SET(trt); + + /* Next TRB */ + + i++; + + /* Prepare Data Stage TRB */ + + if (buffer) + { + trb[i].d0 = up_addrenv_va_to_pa(buffer); + trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(buflen); + trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); + + if (req->type & USB_REQ_DIR_IN) + { + trb[i].d2 |= XHCI_TRB_D2_DIR; + } + + /* Next TRB */ + + i++; + } + + /* Prepare Status Stage TRB */ + + trb[i].d0 = 0; + trb[i].d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(0); + trb[i].d2 = XHCI_TRB_D2_IOC | + XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STAT_STAGE); + + if (!(req->type & USB_REQ_DIR_IN)) + { + trb[i].d2 |= XHCI_TRB_D2_DIR; + } + + /* Next TRB */ + + i++; + + /* Add TRBs to ring */ + + xhci_add_trb(priv, &epinfo->td, trb, i); + + /* Trigger transfer */ + + xhci_ep_doorbell(priv, epinfo); + + return OK; +} + +/**************************************************************************** + * Name: xhci_normal_setup + * + * Description: + * Process a IN or OUT request on bulk or interrupt endpoint. + * This function will enqueue the request and wait for it to complete. + * + * This is a blocking function; it will not return until the control + * transfer has completed. + * + * Assumption: + * The caller holds the xHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + struct xhci_trb_s trb; + + /* Prepare TRB */ + + trb.d0 = up_addrenv_va_to_pa(buffer); + trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); + trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_NORMAL); + + /* Add TRBs to ring */ + + xhci_add_trb(priv, &epinfo->td, &trb, 1); + + /* Trigger transfer */ + + xhci_ep_doorbell(priv, epinfo); + + return OK; +} + +#ifndef CONFIG_USBHOST_ISOC_DISABLE +/**************************************************************************** + * Name: xhci_isoc_setup + * + * Description: + * Process a request on isoch endpoint. + * + * Assumption: + * The caller holds the xHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + FAR uint8_t *buffer, size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + struct xhci_trb_s trb; + + /* Prepare TRB */ + + trb.d0 = up_addrenv_va_to_pa(buffer); + trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); + trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ISOCH); + + /* Start Isoch ASAP */ + + trb.d2 |= XHCI_TRB_D2_SIA; + + /* Add TRBs to ring */ + + xhci_add_trb(priv, &epinfo->td, &trb, 1); + + /* Trigger transfer */ + + xhci_ep_doorbell(priv, epinfo); + + return OK; +} +#endif + +/**************************************************************************** + * Name: xhci_transfer_wait + * + * Description: + * Wait for an IN or OUT transfer to complete. + * + * Assumption: + * The caller holds the xHCI lock. The caller must be aware that the xHCI + * lock will released while waiting for the transfer to complete, but will + * be re-acquired when before returning. The state of xHCI resources could + * be very different upon return. + * + * Returned Value: + * On success, this function returns the number of bytes actually + * transferred. For control transfers, this size includes the size of the + * control request plus the size of the data (which could be short); for + * bulk transfers, this will be the number of data bytes transfers (which + * could be short). + * + ****************************************************************************/ + +static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_epinfo_s *epinfo) +{ + int ret; + + /* Wait for the IOC completion event */ + + ret = xhci_ioc_wait(epinfo); + + /* Did xhci_ioc_wait() or nxmutex_lock report an error? */ + + if (ret < 0) + { + usbhost_trace1(XHCI_TRACE1_TRANSFER_FAILED, -ret); + epinfo->iocwait = false; + return (ssize_t)ret; + } + + /* Transfer completed successfully. Return the number of bytes + * transferred. + */ + + return epinfo->xfrd; +} + +#ifdef CONFIG_USBHOST_ASYNCH +/**************************************************************************** + * Name: xhci_ioc_async_setup + * + * Description: + * Setup to receive an asynchronous notification when a transfer completes. + * + * Input Parameters: + * epinfo - The IN or OUT endpoint descriptor for the device endpoint on + * which the transfer will be performed. + * callback - The function to be called when the transfer completes + * arg - An arbitrary argument that will be provided with the callback. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the interrupt level + * + ****************************************************************************/ + +static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, + FAR struct xhci_epinfo_s *epinfo, + usbhost_asynch_t callback, + FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); + irqstate_t flags; + int ret = -ENODEV; + + DEBUGASSERT(rhport && epinfo && !epinfo->iocwait && + epinfo->callback == NULL); + + /* Is the device still connected? */ + + flags = spin_lock_irqsave(&priv->spinlock); + if (rhport->connected) + { + /* Then save callback information to be used when either (1) the + * device is disconnected, or (2) the transfer completes. + */ + + epinfo->iocwait = false; /* No synchronous wakeup */ + epinfo->status = 0; /* No status yet */ + epinfo->xfrd = 0; /* Nothing transferred yet */ + epinfo->result = -EBUSY; /* Transfer in progress */ + epinfo->callback = callback; /* Asynchronous callback */ + epinfo->arg = arg; /* Argument that accompanies the callback */ + ret = OK; /* We are good to go */ + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + return ret; +} + +/**************************************************************************** + * Name: xhci_asynch_completion + * + * Description: + * This function is called at the interrupt level when an asynchronous + * transfer completes. It performs the pending callback. + * + * Input Parameters: + * epinfo - The IN or OUT endpoint descriptor for the device endpoint on + * which the transfer was performed. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the interrupt level + * + ****************************************************************************/ + +static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo) +{ + usbhost_asynch_t callback; + ssize_t nbytes; + FAR void *arg; + int result; + + DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false && + epinfo->callback != NULL); + + /* Extract and reset the callback info */ + + callback = epinfo->callback; + arg = epinfo->arg; + result = epinfo->result; + nbytes = epinfo->xfrd; + + epinfo->callback = NULL; + epinfo->arg = NULL; + epinfo->result = OK; + epinfo->iocwait = false; + + /* Then perform the callback. Provide the number of bytes successfully + * transferred or the negated errno value in the event of a failure. + */ + + if (result < 0) + { + nbytes = (ssize_t)result; + } + + callback(arg, nbytes); +} +#endif + +/**************************************************************************** + * Name: xhci_portsc_work + * + * Description: + * Handle Port Change work. + * + * Assumptions: + * - Never called from an interrupt handler. + * - Never called directly from xhci_events_poll() otherwise it gets stuck + * on CLASS_DISCONNECTED() + * + ****************************************************************************/ + +static void xhci_portsc_work(FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = arg; + FAR struct usbhost_hubport_s *hport; + FAR struct xhci_rhport_s *rhport; + uint32_t portsc; + int rhpndx; + + /* REVISIT: should this logic be protected? We can't use spinlock + * here because we get stack in CLASS_DISCONNECTED(). + */ + + /* Handle root hub status change on each root port */ + + for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) + { + rhport = &priv->rhport[rhpndx]; + portsc = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); + + usbhost_vtrace2(XHCI_VTRACE2_PORTSC, rhpndx + 1, portsc); + + /* Handle port connection status change (CSC) events */ + + if ((portsc & XHCI_PORTSC_CSC) != 0) + { + usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CSC, portsc); + + /* Check current connect status */ + + if ((portsc & XHCI_PORTSC_CCS) != 0) + { + /* Connected ... Did we just become connected? */ + + if (!rhport->connected) + { + /* Yes.. connected. */ + + rhport->connected = true; + + usbhost_vtrace2(XHCI_VTRACE2_PORTSC_CONNECTED, + rhpndx + 1, priv->pscwait); + + /* Notify any waiters */ + + if (priv->pscwait) + { + nxsem_post(&priv->pscsem); + priv->pscwait = false; + } + } + else + { + usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CONNALREADY, portsc); + } + } + else + { + /* Disconnected... Did we just become disconnected? */ + + if (rhport->connected) + { + /* Yes.. disconnect the device */ + + usbhost_vtrace2(XHCI_VTRACE2_PORTSC_DISCONND, + rhpndx + 1, priv->pscwait); + + rhport->connected = false; + + /* Are we bound to a class instance? */ + + hport = &rhport->hport.hport; + if (hport->devclass) + { + /* Yes.. Disconnect the class. */ + + CLASS_DISCONNECTED(hport->devclass); + hport->devclass = NULL; + } + + /* Notify any waiters for the Root Hub Status change + * event. + */ + + if (priv->pscwait) + { + nxsem_post(&priv->pscsem); + priv->pscwait = false; + } + } + else + { + usbhost_vtrace1(XHCI_VTRACE1_PORTSC_DISCALREADY, portsc); + } + } + } + + /* Clear pending bit but don't touch PED ! */ + + portsc &= ~XHCI_PORTSC_PED; + xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), portsc); + } +} + +/**************************************************************************** + * Name: xhci_transfer_complete + * + * Description: + * Handle transfer complete event + * + ****************************************************************************/ + +static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *evt) +{ + FAR struct xhci_epinfo_s *epinfo; + uint32_t tl = XHCI_TRB_D1_TXLEN_GET(evt->d1); + uint8_t slot = XHCI_TRB_D2_SLOTID_GET(evt->d2); + uint8_t ep = XHCI_TRB_D2_EP_GET(evt->d2); + uint8_t ret = XHCI_TRB_D1_CC_GET(evt->d1); + irqstate_t flags; + + /* Get EP associated with this transfer */ + + epinfo = priv->devs[slot - 1].epinfo[ep - 1]; + DEBUGASSERT(epinfo != NULL); + + flags = spin_lock_irqsave(&priv->spinlock); + + /* Get transferred length */ + + if (epinfo->buflen > 0) + { + epinfo->xfrd = epinfo->buflen - tl; + } + + /* Check transfer status */ + + if (ret == XHCI_TRB_CC_SUCCESS) + { + /* Report success */ + + epinfo->status = 0; + epinfo->result = OK; + } + + else if (ret == XHCI_TRB_CC_STALL) + { + /* Report STALL condition */ + + epinfo->status = 0; + epinfo->result = -EPERM; + } + + else if (ret == XHCI_TRB_CC_SHORT_PKT) + { + /* Report success */ + + epinfo->status = 0; + epinfo->result = OK; + } + + else + { + /* Report error */ + + pcierr("transfer CC = %d\n", ret); + epinfo->status = ret; + epinfo->result = -EIO; + } + + /* Is there a thread waiting for this transfer to complete? */ + + if (epinfo->iocwait) + { + /* Yes... wake it up */ + + epinfo->iocwait = 0; + nxsem_post(&epinfo->iocsem); + } + +#ifdef CONFIG_USBHOST_ASYNCH + /* No.. Is there a pending asynchronous transfer? */ + + else if (epinfo->callback != NULL) + { + /* Yes.. perform the callback */ + + xhci_asynch_completion(epinfo); + } +#endif + + spin_unlock_irqrestore(&priv->spinlock, flags); +} + +/**************************************************************************** + * Name: xhci_envet_complete + * + * Description: + * Handle event complete event + * + ****************************************************************************/ + +static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, + FAR struct xhci_trb_s *evt) +{ + irqstate_t flags; + + /* REVISIT: we assume for now that only one command is pending */ + + /* Store command result */ + + flags = spin_lock_irqsave(&priv->spinlock); + priv->cmdres.d0 = evt->d0; + priv->cmdres.d1 = evt->d1; + priv->cmdres.d2 = evt->d2; + spin_unlock_irqrestore(&priv->spinlock, flags); + + /* Signal that command is done */ + + nxsem_post(&priv->cmdsem); +} + +/**************************************************************************** + * Name: xhci_events_poll + * + * Description: + * Poll all pending events + * + ****************************************************************************/ + +static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) +{ + FAR struct xhci_trb_s *evt; + uintptr_t addr; + uint8_t type; + uint32_t d2; + + /* Invalidate event ring */ + + up_invalidate_dcache((uintptr_t)priv->evnt.ring, + (uintptr_t)(priv->evnt.ring + XHCI_EVENT_MAX)); + + /* Handle all pending events */ + + while (1) + { + evt = &priv->evnt.ring[priv->evnt.i]; + + /* Update address */ + + addr = (uintptr_t)evt; + + d2 = le32toh(evt->d2); + if ((d2 & XHCI_TRB_D2_C) != priv->evnt.ccs) + { + break; + } + + type = XHCI_TRB_D2_TYPE_GET(d2); + + switch (type) + { + /* Transfer Event */ + + case XHCI_TRB_EVT_TRANSFER: + { + xhci_transfer_complete(priv, evt); + + break; + } + + /* Command Completion Event */ + + case XHCI_TRB_EVT_CMD_COMP: + { + xhci_event_complete(priv, evt); + + break; + } + + /* Port Status Change Event */ + + case XHCI_TRB_EVT_PSTAT_CHANGE: + { + /* We have to handle Port Status Change in a separate work + * queue, otherwise we'll get stuck when handling disconnect + * request. + */ + + if (work_available(&priv->pscwork)) + { + work_queue(LPWORK, &priv->pscwork, xhci_portsc_work, + (FAR void *)priv, 0); + } + + break; + } + + default: + { + pciinfo("ignored event %d\n", type); + break; + } + } + + /* Next event */ + + priv->evnt.i++; + + /* Handle ring wrap */ + + if (priv->evnt.i >= XHCI_EVENT_MAX) + { + xhci_ring_reset(&priv->evnt, true); + } + } + + /* Clear ERDP busy bit and update dequeue pointer */ + + addr = up_addrenv_va_to_pa((FAR void *)addr); + addr |= XHCI_ERDP_EHB; + xhci_runt_putreg_8b(priv, XHCI_ERDP(0), addr); + + return OK; +} + +/**************************************************************************** + * Name: xhci_interupt_work + * + * Description: + * Handle xHCI interrupts + * + ****************************************************************************/ + +static void xhci_interrupt_work(FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = arg; + uint32_t iman; + + xhci_events_poll(priv); + + /* Port Change Detect */ + + if (priv->pending & XHCI_USBSTS_PCD) + { + /* Handled as event in xhci_events_poll() */ + + pciinfo("Port Change Detect\n"); + } + + /* Host Controller Halted */ + + if (priv->pending & XHCI_USBSTS_HCH) + { + pciinfo("Host Controller Halted\n"); + } + + /* Host System Error */ + + if (priv->pending & XHCI_USBSTS_HSE) + { + pciinfo("Host System Error\n"); + } + + /* Host Controller Error */ + + if (priv->pending & XHCI_USBSTS_HCE) + { + pciinfo("Host Controller Error\n"); + } + + /* ACK interrupts */ + + xhci_oper_putreg(priv, XHCI_USBSTS, priv->pending); + + /* Clear interrupter pending bit */ + + iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); + if (iman & XHCI_IMAN_IP) + { + xhci_runt_putreg(priv, XHCI_IMAN(0), iman); + } + + /* Clear pending bits */ + + priv->pending = 0; +} + +/**************************************************************************** + * Name: xhci_interupt + * + * Description: + * Interrupt handler for xHCI + * + ****************************************************************************/ + +static int xhci_interrupt(int irq, FAR void *context, FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = arg; + + /* Get pending interrupts */ + + priv->pending = xhci_oper_getreg(priv, XHCI_USBSTS); + + /* Handle interrupts in worker */ + + if (work_available(&priv->work)) + { + work_queue(HPWORK, &priv->work, xhci_interrupt_work, arg, 0); + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_wait + * + * Description: + * Wait for a device to be connected or disconnected to/from a hub port. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from + * the call to the USB driver initialization logic. + * hport - The location to return the hub port descriptor that detected + * the connection related event. + * + * Returned Value: + * Zero (OK) is returned on success when a device is connected or + * disconnected. This function will not return until either (1) a device is + * connected or disconnect to/from any hub port or until (2) some failure + * occurs. On a failure, a negated errno value is returned indicating the + * nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_wait(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s **hport) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); + FAR struct xhci_rhport_s *rhport; + FAR struct usbhost_hubport_s *connport; + irqstate_t flags; + int rhpndx; + int ret; + + /* Loop until the connection state changes on one of the root hub ports or + * until an error occurs. + */ + + while (true) + { + flags = spin_lock_irqsave(&priv->spinlock); + + /* Check for a change in the connection state on any root hub port */ + + for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) + { + /* Has the connection state changed on the RH port? */ + + rhport = &priv->rhport[rhpndx]; + connport = &rhport->hport.hport; + if (rhport->connected != connport->connected) + { + /* Yes.. Return the RH port to inform the caller which + * port has the connection change. + */ + + connport->connected = rhport->connected; + *hport = connport; + spin_unlock_irqrestore(&priv->spinlock, flags); + + usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, + rhpndx + 1, rhport->connected); + return OK; + } + } + +#ifdef CONFIG_USBHOST_HUB + /* Is a device connected to an external hub? */ + + if (priv->hport) + { + /* Yes.. return the external hub port */ + + connport = priv->hport; + priv->hport = NULL; + + *hport = (FAR struct usbhost_hubport_s *)connport; + spin_unlock_irqrestore(&priv->spinlock, flags); + + usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, + HPORT(connport), connport->connected); + return OK; + } +#endif + + /* No changes on any port. Wait for a connection/disconnection event + * and check again + */ + + priv->pscwait = true; + + spin_unlock_irqrestore(&priv->spinlock, flags); + + ret = nxsem_wait_uninterruptible(&priv->pscsem); + if (ret < 0) + { + return ret; + } + } +} + +/**************************************************************************** + * Name: xhci_rh_enumerate/xhci_enumerate + * + * Description: + * Enumerate the connected device. As part of this enumeration process, + * the driver will (1) get the device's configuration descriptor, (2) + * extract the class ID info from the configuration descriptor, (3) call + * usbhost_findclass() to find the class that supports this device, (4) + * call the create() method on the struct usbhost_registry_s interface + * to get a class instance, and finally (5) call the connect() method + * of the struct usbhost_class_s interface. After that, the class is in + * charge of the sequence of operations. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from + * the call to the USB driver initialization logic. + * hport - The descriptor of the hub port that has the newly connected + * device. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); + FAR struct xhci_rhport_s *rhport; + int rhpndx; + int ret; + + DEBUGASSERT(hport != NULL); + rhpndx = hport->port; + + DEBUGASSERT(rhpndx >= 0 && rhpndx < priv->no_ports); + rhport = &priv->rhport[rhpndx]; + + /* Are we connected to a device? The caller should have called the wait() + * method first to be assured that a device is connected. + */ + + if (!rhport->connected) + { + /* No, return an error */ + + pcierr("not connected\n"); + return -ENODEV; + } + + /* Enable port */ + + ret = xhci_port_enable(priv, hport); + if (ret < 0) + { + pcierr("Failed to enable port %d\n", ret); + return ret; + } + + /* Initialize device data */ + + ret = xhci_device_init(priv, rhport); + if (ret < 0) + { + pcierr("Failed to initialize device %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_enumerate + * + * Description: + * See description above. + * + ****************************************************************************/ + +static int xhci_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport) +{ + int ret; + + /* If this is a connection on the root hub, then we need to go to + * little more effort to get the device speed. If it is a connection + * on an external hub, then we already have that information. + */ + + DEBUGASSERT(hport); +#ifdef CONFIG_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + ret = xhci_rh_enumerate(conn, hport); + if (ret < 0) + { + return ret; + } + } + + /* Then let the common usbhost_enumerate do the real enumeration. */ + + ret = usbhost_enumerate(hport, &hport->devclass); + if (ret < 0) + { + /* Failed to enumerate */ + + /* If this is a root hub port, then marking the hub port not connected + * will cause xhci_wait() to return and we will try the connection + * again. + */ + + hport->connected = false; + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_ep0configure + * + * Description: + * Configure endpoint 0. This method is normally used internally by the + * enumerate() method but is made available at the interface to support + * an external implementation of the enumeration logic. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * funcaddr - The USB address of the function containing the endpoint that + * EP0 controls. A funcaddr of zero will be received if no address is + * yet assigned to the device. + * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH + * maxpacketsize - The maximum number of bytes that can be sent to or + * received from the endpoint in a single data packet + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, + uint8_t speed, uint16_t maxpacketsize) +{ + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep0; + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + uint64_t ctx; + int ret; + + DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048); + + ret = nxmutex_lock(&priv->lock); + if (ret >= 0) + { + /* Update max packet size */ + + rhport->dev->input->ep[0].ctx1 &= ~XHCI_EP_CTX1_MAXPKT_MASK; + rhport->dev->input->ep[0].ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpacketsize); + + /* Add Slot Context and EP0 Context */ + + xhci_context_ctrl(priv, rhport->dev, 0, + XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | + XHCI_IN_CTX1_A(XHCI_EP0_FLAG)); + + /* Flush Device input context */ + + up_flush_dcache((uintptr_t)rhport->dev->input, + (uintptr_t)rhport->dev->input + + sizeof(struct xhci_input_dev_ctx_s)); + + /* Free mutex before command execution */ + + nxmutex_unlock(&priv->lock); + + ctx = up_addrenv_va_to_pa(rhport->dev->input); + ret = xhci_cmd_evalctx(priv, epinfo->slot, ctx); + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_epalloc + * + * Description: + * Allocate and configure one endpoint. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * epdesc - Describes the endpoint to be allocated. + * ep - A memory location provided by the caller in which to receive the + * allocated endpoint descriptor. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_epdesc_s *epdesc, + FAR usbhost_ep_t *ep) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct usbhost_hubport_s *hport; + FAR struct xhci_epinfo_s *epinfo; + FAR struct xhci_dev_s *dev; + uint32_t mask; + uint8_t eptype; + uint8_t idx; + int ret; + + /* Sanity check. NOTE that this method should only be called if a device + * is connected (because we need a valid low speed indication). + */ + + DEBUGASSERT(drvr != 0 && epdesc != NULL && epdesc->hport != NULL + && ep != NULL); + hport = epdesc->hport; + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(XHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype); +#else + pciinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n", + epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr, + epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); +#endif + + /* Allocate a endpoint information structure */ + + epinfo = kmm_zalloc(sizeof(struct xhci_epinfo_s)); + if (!epinfo) + { + return -ENOMEM; + } + + /* Initialize the endpoint container (which is really just another form of + * 'struct usbhost_epdesc_s', packed differently and with additional + * information. A cleaner design might just embed struct usbhost_epdesc_s + * inside of struct xhci_epinfo_s and just memcpy here. + */ + + epinfo->dirin = epdesc->in; + epinfo->epno = epdesc->addr; + +#ifndef CONFIG_USBHOST_INT_DISABLE + epinfo->interval = epdesc->interval; +#endif + epinfo->xfrtype = epdesc->xfrtype; + nxsem_init(&epinfo->iocsem, 0, 0); + + /* xhci_epno_get() returns Device Context Index (DCI) */ + + idx = xhci_epno_get(epinfo); + mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx)); + dev = rhport->dev; + dev->epinfo[idx - 1] = epinfo; + + /* TD rings already allocated but not connected yet. */ + + ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX); + if (ret < 0) + { + pcierr("ep ring init failed\n"); + return ret; + } + + /* Store slot ID for later */ + + epinfo->slot = rhport->slot; + +#ifdef CONFIG_USBHOST_HUB + if (hport->speed != USB_SPEED_HIGH) + { + /* A high speed hub exists between this device and the root hub + * otherwise we would not get here. + */ + + FAR struct usbhost_hubport_s *parent = hport->parent; + + for (; parent->speed != USB_SPEED_HIGH; parent = hport->parent) + { + hport = parent; + } + + if (parent->speed == USB_SPEED_HIGH) + { + epinfo->hubport = HPORT(hport); + epinfo->hubaddr = hport->parent->funcaddr; + } + else + { + return -EINVAL; + } + } +#endif + + /* Get EP type */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: + { + eptype = epinfo->dirin ? XHCI_EPTYPE_BULK_IN : + XHCI_EPTYPE_BULK_OUT; + break; + } + +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: +#endif + { + eptype = epinfo->dirin ? XHCI_EPTYPE_INTR_IN : + XHCI_EPTYPE_INTR_OUT; + break; + } + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: + { + eptype = epinfo->dirin ? XHCI_EPTYPE_ISO_IN : + XHCI_EPTYPE_ISO_OUT; + break; + } +#endif + + default: + { + return -ENOSYS; + } + } + + /* REVISIT: do we need disable EP here? */ + + /* Initialize EP context. + * Max Burst Size set for 0 for now (USB3.0 specific) + */ + + xhci_ep_configure(priv, &dev->input->ep[idx - 1], + eptype, epdesc->mxpacketsize, 0, + up_addrenv_va_to_pa(epinfo->td.ring), + 0, epinfo->interval); + + /* Evaluate the slot context */ + + xhci_context_ctrl(priv, dev, 0, mask | XHCI_IN_CTX1_A(XHCI_SLOT_FLAG)); + + up_flush_dcache((uintptr_t)dev->input, + (uintptr_t)dev->input + + sizeof(struct xhci_input_dev_ctx_s)); + + /* Configure EP */ + + ret = xhci_cmd_cfgep(priv, epinfo->slot, + up_addrenv_va_to_pa(dev->input), false); + if (ret < 0) + { + pcierr("failed to configure EP %d\n", ret); + return ret; + } + + /* Success.. return an opaque reference to the endpoint information + * structure instance + */ + + *ep = (usbhost_ep_t)epinfo; + return OK; +} + +/**************************************************************************** + * Name: xhci_epfree + * + * Description: + * Free an endpoint previously allocated by DRVR_EPALLOC. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The endpoint to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + + /* There should not be any pending, transfers */ + + DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); + + /* Free ring */ + + xhci_ring_deinit(&epinfo->td); + + /* Free the container */ + + kmm_free(epinfo); + return OK; +} + +/**************************************************************************** + * Name: xhci_alloc + * + * Description: + * Some hardware supports special memory in which request and descriptor + * data can be accessed more efficiently. This method provides a + * mechanism to allocate the request/descriptor memory. If the underlying + * hardware does not support such "special" memory, this functions may + * simply map to kmm_malloc(). + * + * This interface was optimized under a particular assumption. It was + * assumed that the driver maintains a pool of small, pre-allocated buffers + * for descriptor traffic. NOTE that size is not an input, but an output: + * The size of the pre-allocated buffer is returned. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of a memory location provided by the caller in + * which to return the allocated buffer memory address. + * maxlen - The address of a memory location provided by the caller in + * which to return the maximum size of the allocated buffer memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_alloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, FAR size_t *maxlen) +{ + int ret = -ENOMEM; + + DEBUGASSERT(drvr && buffer && maxlen); + + /* Allocated buffer must not cross page boundaries */ + + *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , XHCI_BUFSIZE); + if (*buffer) + { + *maxlen = XHCI_BUFSIZE; + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_free + * + * Description: + * Some hardware supports special memory in which request and descriptor + * data can be accessed more efficiently. This method provides a + * mechanism to free that request/descriptor memory. If the underlying + * hardware does not support such "special" memory, this functions may + * simply map to kmm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the transfer/descriptor buffer + * memory + */ + + kmm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: xhci_ioalloc + * + * Description: + * Some hardware supports special memory in which larger IO buffers can + * be accessed more efficiently. This method provides a mechanism to + * allocate the request/descriptor memory. If the underlying hardware + * does not support such "special" memory, this functions may simply map + * to kumm_malloc. + * + * This interface differs from DRVR_ALLOC in that the buffers are variable- + * sized. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of a memory location provided by the caller in + * which to return the allocated buffer memory address. + * buflen - The size of the buffer required. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, size_t buflen) +{ + int ret = -ENOMEM; + + DEBUGASSERT(drvr && buffer && buflen > 0); + + /* Large transfers are not supported now */ + + if (buflen > XHCI_PAGE_SIZE) + { + return -ENOMEM; + } + + /* Allocated buffer must not cross page boundaries */ + + *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , buflen); + if (*buffer) + { + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: xhci_iofree + * + * Description: + * Some hardware supports special memory in which IO data can be accessed + * more efficiently. This method provides a mechanism to free that IO + * buffer memory. If the underlying hardware does not support such + * "special" memory, this functions may simply map to kumm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_iofree(FAR struct usbhost_driver_s *drvr, + FAR uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the transfer/descriptor buffer + * memory + */ + + kmm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: xhci_ctrl_xfer + * + * Description: + * Process a IN or OUT request on the control endpoint. These methods + * will enqueue the request and wait for it to complete. Only one + * transfer may be queued; Neither these methods nor the transfer() method + * can be called again until the control transfer function returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep0 - The control endpoint to send/receive the control request. + * req - Describes the request to be sent. This request must lie in + * memory created by DRVR_ALLOC. + * buffer - A buffer used for sending the request and for returning any + * responses. This buffer must be large enough to hold the + * length value in the request description. buffer must have been + * allocated using DRVR_ALLOC. + * + * NOTE: On an IN transaction, req and buffer may refer to the xHCI + * allocated memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *ep0info = (FAR struct xhci_epinfo_s *)ep0; + uint16_t len; + ssize_t nbytes; + int ret; + + DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL); + + len = xhci_getle16(req->len); + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(XHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req); +#endif + + /* Special case for SET_ADDRESS request */ + + if (req->req == USB_REQ_SETADDRESS) + { + /* Reset EP0 ring to its initial state, so when xHCI update EP0 + * context, TD dequeue pointer would be valid. It may be already + * off, because the USB Host stack has already sent some messages + * on control EP. + */ + + xhci_ring_init(&rhport->dev->rhport->ep0.td, 0); + + /* Issue SET_ADDRESS request */ + + ret = xhci_address_set(priv, rhport, true); + if (ret == OK) + { + /* Store USB Device Address assigned by xHCI */ + + ep0info->devaddr = + XHCI_ST_CTX3_ADDR_GET(rhport->dev->ctx->slot.ctx[3]); + rhport->dev->input->slot.ctx[3] = rhport->dev->ctx->slot.ctx[3]; + } + + return OK; + } + + /* We must have exclusive access to the XHCI hardware and data + * structures. + */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + + ret = xhci_ioc_setup(rhport, ep0info, 0); + if (ret != OK) + { + goto errout_with_lock; + } + + /* Now initiate the transfer */ + + ret = xhci_control_setup(rhport, ep0info, req, buffer, len); + if (ret < 0) + { + pcierr("ERROR: xhci_control_setup failed: %d\n", ret); + goto errout_with_iocwait; + } + + nxmutex_unlock(&priv->lock); + + /* And wait for the transfer to complete */ + + nbytes = xhci_transfer_wait(priv, ep0info); + return nbytes >= 0 ? OK : (int)nbytes; + +errout_with_iocwait: + ep0info->iocwait = false; +errout_with_lock: + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: xhci_ctrlin + * + * Description: + * Process IN request on the control endpoint. For details, see + * description for xhci_ctrl_xfer(). + * + ****************************************************************************/ + +static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer) +{ + /* xhci_ctrl_xfer() can handle both directions */ + + return xhci_ctrl_xfer(drvr, ep0, req, buffer); +} + +/**************************************************************************** + * Name: xhci_ctrlout + * + * Description: + * Process OUT request on the control endpoint. For details, see + * description for xhci_ctrl_xfer(). + * + ****************************************************************************/ + +static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + FAR const struct usb_ctrlreq_s *req, + FAR const uint8_t *buffer) +{ + /* xhci_ctrl_xfer() can handle both directions. We just need to work around + * the differences in the function signatures. + */ + + return xhci_ctrl_xfer(drvr, ep0, req, (FAR uint8_t *)buffer); +} + +/**************************************************************************** + * Name: xhci_transfer + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request, blocking until the transfer completes. + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until this function returns. + * + * This is a blocking method; this functions will not return until the + * transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on + * which to perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or + * received (IN endpoint). buffer must have been allocated using + * DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * + * Returned Value: + * On success, a non-negative value is returned that indicates the number + * of bytes successfully transferred. On a failure, a negated errno value + * is returned that indicates the nature of the failure: + * + * EAGAIN - If devices NAKs the transfer (or NYET or other error where + * it may be appropriate to restart the entire transaction). + * EPERM - If the endpoint stalls + * EIO - On a TX or data toggle error + * EPIPE - Overrun errors + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, + usbhost_ep_t ep, FAR uint8_t *buffer, + size_t buflen) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + ssize_t nbytes; + int ret; + + DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); + + /* We must have exclusive access to the xHCI hardware and data + * structures. + */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return (ssize_t)ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + + ret = xhci_ioc_setup(rhport, epinfo, buflen); + if (ret != OK) + { + goto errout_with_lock; + } + + /* Initiate the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: +#endif + { + ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); + break; + } + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: + { + ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); + break; + } +#endif + + case USB_EP_ATTR_XFER_CONTROL: + default: + { + usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + break; + } + } + + /* Check for errors in the setup of the transfer */ + + if (ret < 0) + { + uerr("ERROR: Transfer setup failed: %d\n", ret); + goto errout_with_iocwait; + } + + nxmutex_unlock(&priv->lock); + + /* Then wait for the transfer to complete */ + + nbytes = xhci_transfer_wait(priv, epinfo); + return nbytes; + +errout_with_iocwait: + epinfo->iocwait = false; +errout_with_lock: + nxmutex_unlock(&priv->lock); + return (ssize_t)ret; +} + +/**************************************************************************** + * Name: xhci_asynch + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately. When the transfer + * completes, the callback will be invoked with the provided transfer. + * This method is useful for receiving interrupt transfers which may come + * infrequently. + * + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until the transfer completes. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from + * the call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on + * which to perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or + * received (IN endpoint). buffer must have been allocated + * using DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * callback - This function will be called when the transfer completes. + * arg - The arbitrary parameter that will be passed to the callback + * function when the transfer completes. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, + FAR uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, FAR void *arg) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + int ret; + + DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); + + /* We must have exclusive access to the xHCI hardware and data + * structures. + */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Set the request for the callback well BEFORE initiating the transfer. */ + + ret = xhci_ioc_async_setup(rhport, epinfo, callback, arg); + if (ret != OK) + { + goto errout_with_lock; + } + + /* Initiate the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: +#endif + { + ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); + break; + } + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: + { + ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); + break; + } +#endif + + case USB_EP_ATTR_XFER_CONTROL: + default: + { + usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + break; + } + } + + /* Check for errors in the setup of the transfer */ + + if (ret < 0) + { + goto errout_with_callback; + } + + /* The transfer is in progress */ + + nxmutex_unlock(&priv->lock); + return OK; + +errout_with_callback: + epinfo->callback = NULL; + epinfo->arg = NULL; +errout_with_lock: + nxmutex_unlock(&priv->lock); + return ret; +} +#endif /* CONFIG_USBHOST_ASYNCH */ + +/**************************************************************************** + * Name: xhci_cancel + * + * Description: + * Cancel a pending transfer on an endpoint. Cancelled synchronous or + * asynchronous transfer will complete normally with the error -ESHUTDOWN. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on which + * an asynchronous transfer should be transferred. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + ****************************************************************************/ + +static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); +#ifdef CONFIG_USBHOST_ASYNCH + usbhost_asynch_t callback; + FAR void *arg; +#endif + irqstate_t flags; + bool iocwait; + + DEBUGASSERT(epinfo); + + /* Sample and reset all transfer termination information. This will + * prevent any callbacks from occurring while we performing the + * cancellation. The transfer may still be in progress, however, so this + * does not eliminate other DMA-related race conditions. + */ + + flags = spin_lock_irqsave(&priv->spinlock); +#ifdef CONFIG_USBHOST_ASYNCH + callback = epinfo->callback; + arg = epinfo->arg; +#endif + iocwait = epinfo->iocwait; + +#ifdef CONFIG_USBHOST_ASYNCH + epinfo->callback = NULL; + epinfo->arg = NULL; +#endif + epinfo->iocwait = false; + spin_unlock_irqrestore(&priv->spinlock, flags); + + /* Bail if there is no transfer in progress for this endpoint */ + +#ifdef CONFIG_USBHOST_ASYNCH + if (callback == NULL && !iocwait) +#else + if (!iocwait) +#endif + { + return OK; + } + + /* Stop endpoint */ + + xhci_cmd_stopep(priv, epinfo->slot, xhci_epno_get(epinfo), false); + + /* REVISIT: what if we interrupted the execution of a TD? page 139 */ + + epinfo->result = -ESHUTDOWN; + + if (iocwait) + { + /* Yes... wake it up */ + + nxsem_post(&epinfo->iocsem); + } + +#ifdef CONFIG_USBHOST_ASYNCH + /* No.. Is there a pending asynchronous transfer? */ + + else + { + /* Yes.. perform the callback */ + + DEBUGASSERT(callback != NULL); + callback(arg, -ESHUTDOWN); + } +#endif + + return OK; +} + +/**************************************************************************** + * Name: xhci_connect + * + * Description: + * New connections may be detected by an attached hub. This method is the + * mechanism that is used by the hub class to introduce a new connection + * and port description to the system. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * hport - The descriptor of the hub port that detected the connection + * related event + * connected - True: device connected; false: device disconnected + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_HUB +static int xhci_connect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport, + bool connected) +{ +#error missing logic +} +#endif + +/**************************************************************************** + * Name: xhci_disconnect + * + * Description: + * Called by the class when an error occurs and device has been + * disconnected. The USB host driver should discard the handle to the + * class instance (it is stale) and not attempt any further interaction + * with the class driver instance (until a new instance is received from + * the create() method). The driver should not call the class + * disconnected() method. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * hport - The port from which the device is being disconnected. Might be + * a port on a hub. + * + * Returned Value: + * None + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport) +{ + FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); + FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; + + DEBUGASSERT(hport != NULL); + hport->devclass = NULL; + + /* Deinit device slot */ + + if (rhport->dev) + { + xhci_device_deinit(priv, rhport); + } +} + +/**************************************************************************** + * Name: xhci_hw_getparams + * + * Description: + * Get hardware description of a connected xHCI device. + * + ****************************************************************************/ + +static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv) +{ + uint32_t regval; + + /* Get data form Host Controller Capability 1 Parameters */ + + regval = xhci_capa_getreg(priv, XHCI_HCCPARAMS1); + if (regval & XHCI_HCCPARAMS1_CSZ) + { + pcierr("Only 32 byte Context data structures supported!\n"); + return -EIO; + } + + /* Get data from Structural Parameters 1 register */ + + regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS1); + priv->no_slots = XHCI_HCSPARAMS1_MAXSLOTS(regval); + priv->no_ports = XHCI_HCSPARAMS1_MAXPORTS(regval); + + /* Limit number of slots to number of devices */ + + if (priv->no_slots > CONFIG_USBHOST_XHCI_MAX_DEVS) + { + priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS; + } + + pciinfo("no slots = %d, no ports = %d\n", + priv->no_slots, priv->no_ports); + + /* Check if valid */ + + if (priv->no_slots == 0 || priv->no_ports == 0) + { + return -EINVAL; + } + + /* Get data from Structural Parameters 2 register */ + + regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS2); + priv->no_scratch = XHCI_HCSPARAMS2_MAXSPB(regval); + + pciinfo("no scratch = %d\n", priv->no_scratch); + + priv->no_erst = 1 << XHCI_HCSPARAMS2_ERST(regval); + + pciinfo("no_erst = %d\n", priv->no_erst); + + /* Limit event ring segment table to 1 */ + + if (priv->no_erst > XHCI_MAX_ERST) + { + priv->no_erst = XHCI_MAX_ERST; + } + + pciinfo("no erst = %d\n", priv->no_erst); + + return OK; +} + +/**************************************************************************** + * Name: xhci_irq_initialize + * + * Description: + * Initialize xHCI interrupts - require MSI-X support. + * + ****************************************************************************/ + +static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv) +{ + int ret; + + /* Allocate MSI */ + + ret = pci_alloc_irq(priv->pcidev, &priv->irq, 1); + if (ret != 1) + { + pcierr("Failed to allocate MSI %d\n", ret); + return ret; + } + + /* Attach IRQ */ + + irq_attach(priv->irq, xhci_interrupt, priv); + + /* Connect MSI-X */ + + ret = pci_connect_irq(priv->pcidev, &priv->irq, 1); + if (ret != OK) + { + pcierr("Failed to connect MSI %d\n", ret); + pci_release_irq(priv->pcidev, &priv->irq, 1); + + return -ENOTSUP; + } + + up_enable_irq(priv->irq); + + return OK; +} + +/**************************************************************************** + * Name: xhci_mem_alloc + * + * Description: + * Allocated memory for a new detected xHCI device. + * + ****************************************************************************/ + +static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) +{ + size_t tmp; + int i; + + /* Allocate Scratchpad Buffer Array */ + + tmp = priv->no_scratch * sizeof(uint64_t); + priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp); + if (!priv->pg_sb) + { + pcierr("pg_sb malloc failed\n"); + return -ENOMEM; + } + + memset(priv->pg_sb, 0, tmp); + + for (i = 0; i < priv->no_scratch; i++) + { + /* Alloc page for each entry in array */ + + priv->pg_sb[i] = up_addrenv_va_to_pa( + kmm_memalign(XHCI_PAGE_SIZE, XHCI_PAGE_SIZE)); + if (!priv->pg_sb[i]) + { + pcierr("pg_sb[i] malloc failed\n"); + return -ENOMEM; + } + + /* Reset page */ + + memset((FAR void *)(up_addrenv_pa_to_va(priv->pg_sb[i])), + 0, XHCI_PAGE_SIZE); + } + + /* Allocate Device Context Array which shall be: + * size = MaxSlotsEn + 1 entries + */ + + tmp = sizeof(uint64_t) * (priv->no_slots + 1); + priv->pg_ctx = kmm_memalign(XHCI_BUF_ALIGN, tmp); + if (!priv->pg_ctx) + { + pcierr("pg_ctx malloc failed\n"); + return -ENOMEM; + } + + /* Reset context */ + + memset(priv->pg_ctx, 0, tmp); + + /* Allocate Event Table */ + + tmp = sizeof(struct xhci_event_ring_s) * priv->no_erst; + priv->pg_erst = kmm_memalign(XHCI_BUF_ALIGN, tmp); + if (!priv->pg_erst) + { + pcierr("priv->pg_erst malloc failed\n"); + return -ENOMEM; + } + + memset(priv->pg_erst, 0, tmp); + + /* Allocate root hub ports */ + + priv->rhport = kmm_zalloc(priv->no_ports * sizeof(struct xhci_rhport_s)); + if (!priv->rhport) + { + pcierr("rhport zalloc failed!\n"); + return -ENOMEM; + } + + /* Allocate xHC devices array */ + + priv->devs = kmm_zalloc(priv->no_slots * sizeof(struct xhci_dev_s)); + if (!priv->devs) + { + pcierr("devs zalloc failed!\n"); + return -ENOMEM; + } + + /* Allocate xHC devices resources */ + + for (i = 0; i < priv->no_slots; i++) + { + /* Allocate Device Context */ + + priv->devs[i].ctx = kmm_zalloc(sizeof(struct xhci_dev_ctx_s)); + if (!priv->devs[i].ctx) + { + pcierr("dev ctx zalloc failed!\n"); + return -ENOMEM; + } + + /* Allocate Input Context. The Input Context shall be physically + * contiguous within a page + */ + + priv->devs[i].input = kmm_memalign((XHCI_PAGE_SIZE / 2), + sizeof(struct xhci_input_dev_ctx_s)); + if (!priv->devs[i].input) + { + pcierr("dev input zalloc failed!\n"); + return -ENOMEM; + } + + /* No endpoint for device yet */ + + tmp = sizeof(uintptr_t) * XHCI_MAX_ENDPOINTS; + memset(priv->devs[i].epinfo, 0, tmp); + } + + return OK; +} + +/**************************************************************************** + * Name: xhci_mem_free + * + * Description: + * Free allocated memory for a xHCI device. + * + ****************************************************************************/ + +static int xhci_mem_free(FAR struct usbhost_xhci_s *priv) +{ + int i; + + /* Free scratch buffers */ + + for (i = 0; i < priv->no_scratch; i++) + { + kmm_free((FAR void *)priv->pg_sb[i]); + } + + kmm_free(priv->pg_sb); + + /* Free devices */ + + for (i = 0; i < priv->no_slots; i++) + { + kmm_free(priv->devs[i].ctx); + kmm_free(priv->devs[i].input); + } + + kmm_free(priv->devs); + kmm_free(priv->pg_ctx); + kmm_free(priv->pg_erst); + kmm_free(priv->rhport); + + /* Free command ring and event ring */ + + xhci_ring_deinit(&priv->cmd); + xhci_ring_deinit(&priv->evnt); + + return OK; +} + +/**************************************************************************** + * Name: xhci_hw_initialize + * + * Description: + * One-time setup of the host controller hardware for normal operations. + * + * Input Parameters: + * priv -- USB host driver private data structure. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv) +{ + int ret; + + /* Synchronize with BIOS */ + + ret = xhci_bios_wait(priv); + if (ret < 0) + { + pcierr("Failed to get xhci controller!\n"); + goto errout; + } + + /* Get structural parameters */ + + ret = xhci_hw_getparams(priv); + if (ret < 0) + { + goto errout; + } + + /* Allocate all required memory */ + + ret = xhci_mem_alloc(priv); + if (ret < 0) + { + goto errout; + } + + /* Configure interrupts */ + + ret = xhci_irq_initialize(priv); + if (ret < 0) + { + goto errout; + } + + /* Halt controller */ + + ret = xhci_ctrl_halt(priv); + if (ret < 0) + { + goto errout; + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: xhci_sw_initialize + * + * Description: + * One-time setup of the host driver state structure. + * + * Input Parameters: + * priv -- USB host driver private data structure. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv) +{ + FAR struct xhci_rhport_s *rhport; + FAR struct usbhost_hubport_s *hport; + int i; + + /* Initialize sync objects */ + + nxmutex_init(&priv->lock); + nxsem_init(&priv->pscsem, 0, 0); + nxsem_init(&priv->cmdsem, 0, 0); + + /* Initialize function address generation logic + * REVISIT: xHCI hardware is responsible for device address, but NuttX USB + * Host stack require this to be initialized. + */ + + usbhost_devaddr_initialize(&priv->devgen); + + /* Initialize devices */ + + for (i = 0; i < priv->no_slots; i++) + { + /* Slot disabled by defaulte */ + + priv->devs[i].state = XHCI_SLOT_DISABLED; + } + + /* Initialize the root hub port structures */ + + for (i = 0; i < priv->no_ports; i++) + { + rhport = &priv->rhport[i]; + + /* No device slot yet */ + + rhport->dev = NULL; + + /* Connect xhci instance */ + + rhport->priv = priv; + + /* Initialize the device operations */ + + rhport->drvr.ep0configure = xhci_ep0configure; + rhport->drvr.epalloc = xhci_epalloc; + rhport->drvr.epfree = xhci_epfree; + rhport->drvr.alloc = xhci_alloc; + rhport->drvr.free = xhci_free; + rhport->drvr.ioalloc = xhci_ioalloc; + rhport->drvr.iofree = xhci_iofree; + rhport->drvr.ctrlin = xhci_ctrlin; + rhport->drvr.ctrlout = xhci_ctrlout; + rhport->drvr.transfer = xhci_transfer; +#ifdef CONFIG_USBHOST_ASYNCH + rhport->drvr.asynch = xhci_asynch; +#endif + rhport->drvr.cancel = xhci_cancel; +#ifdef CONFIG_USBHOST_HUB + rhport->drvr.connect = xhci_connect; +#endif + rhport->drvr.disconnect = xhci_disconnect; + rhport->hport.pdevgen = &priv->devgen; + + /* Initialize EP0 */ + + rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL; + rhport->ep0.epno = 0; + rhport->ep0.devaddr = 0; + nxsem_init(&rhport->ep0.iocsem, 0, 0); + + /* Initialize the public port representation */ + + hport = &rhport->hport.hport; + hport->drvr = &rhport->drvr; +#ifdef CONFIG_USBHOST_HUB + hport->parent = NULL; +#endif + hport->ep0 = &rhport->ep0; + hport->port = i; + hport->speed = USB_SPEED_FULL; + } + + return OK; +} + +/**************************************************************************** + * Name: pci_xhci_probe + * + * Description: + * Initialize PCI device. + * + ****************************************************************************/ + +static int pci_xhci_probe(FAR struct pci_device_s *dev) +{ + FAR struct usbhost_conn_xhci_s *conn = NULL; + FAR struct usbhost_xhci_s *priv = NULL; + int ret = -ENOMEM; + + /* Init PCI bus */ + + pci_set_master(dev); + pciinfo("Enabled bus mastering\n"); + pci_enable_device(dev); + pciinfo("Enabled memory resources\n"); + + /* Allocate connection structure */ + + conn = kmm_zalloc(sizeof(struct usbhost_conn_xhci_s)); + if (!conn) + { + pcierr("zalloc failed!\n"); + goto errout; + } + + /* Allocate the driver structure */ + + priv = kmm_zalloc(sizeof(struct usbhost_xhci_s)); + if (!priv) + { + pcierr("zalloc failed!\n"); + goto errout; + } + + /* Initialize connection data */ + + conn->conn.wait = xhci_wait; + conn->conn.enumerate = xhci_enumerate; + conn->priv = priv; + + /* Connect PCI handler */ + + priv->pcidev = dev; + dev->priv = conn; + + /* Get base address - BAR 0 */ + + priv->base = (uintptr_t)pci_map_bar(dev, 0); + if (!priv->base) + { + pcierr("Not found BAR 0!\n"); + ret = -EIO; + goto errout; + } + + /* Get register address */ + + priv->capa_base = priv->base; + priv->oper_base = priv->base + xhci_capa_getreg_1b(priv, XHCI_CAPLENGTH); + priv->runt_base = priv->base + xhci_capa_getreg(priv, XHCI_RTSOFF); + priv->door_base = priv->base + xhci_capa_getreg(priv, XHCI_DBOFF); + + usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0); + + /* Initialize HW */ + + ret = xhci_hw_initialize(priv); + if (ret < 0) + { + pcierr("failed to initialize HW!\n"); + goto errout; + } + + /* Initialize SW */ + + ret = xhci_sw_initialize(priv); + if (ret < 0) + { + pcierr("failed to initialize SW!\n"); + goto errout; + } + + /* Start controller */ + + ret = xhci_ctrl_start(priv); + if (ret < 0) + { + usbhost_trace1(XHCI_TRACE1_START_FAILED, 0); + goto errout; + } + +#ifdef CONFIG_DEBUG_USB_INFO + /* Dump xhci registers */ + + xhci_dump_mem(priv, "after init"); +#endif + + /* If there is a USB device in the slot at power up, then we will not + * get the status change interrupt to signal us that the device is + * connected. We need to set the initial connected state accordingly. + */ + + xhci_probe_ports(priv); + + usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0); + + /* Initialize waiter */ + + ret = usbhost_waiter_initialize(&conn->conn); + if (ret < 0) + { + pcierr("failed to initialize waiter!\n"); + goto errout; + } + + /* Store waiter PID */ + + conn->pid = ret; + + return OK; + +errout: + + /* Free allocated xhci buffers */ + + xhci_mem_free(priv); + + /* Free allocated data */ + + kmm_free(conn); + kmm_free(priv); + + return ret; +} + +/**************************************************************************** + * Name: pci_xhci_remove + * + * Description: + * Remove PCI device. + * + ****************************************************************************/ + +static void pci_xhci_remove(FAR struct pci_device_s *dev) +{ + FAR struct usbhost_conn_xhci_s *conn = dev->priv; + FAR struct usbhost_xhci_s *priv = conn->priv; + + /* Free xhci interrupts */ + + irq_detach(priv->irq); + pci_release_irq(dev, &priv->irq, 1); + + /* Disable PCI devicve */ + + 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); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pci_xhci_init + * + * Description: + * Initialize the USB host xHCI as PCI device. + * + * Input Parameters: + * None + * + * Returned Value: + * On success this function will return zero (OK); A negated errno value + * will be returned on failure. + * + ****************************************************************************/ + +int pci_xhci_init(void) +{ + return pci_register_driver(&g_pci_xhci_drv); +} diff --git a/drivers/usbhost/usbhost_xhci_trace.c b/drivers/usbhost/usbhost_xhci_trace.c new file mode 100644 index 00000000000..f3d2e262594 --- /dev/null +++ b/drivers/usbhost/usbhost_xhci_trace.c @@ -0,0 +1,140 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_xhci_trace.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 "usbhost_xhci_trace.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define TRENTRY(id,string) {string} + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct xhci_usbhost_trace_s +{ + const char *string; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct xhci_usbhost_trace_s g_trace1[TRACE1_NSTRINGS] = +{ + TRENTRY(XHCI_TRACE1_RESET_FAILED, + "XHCI ERROR: xhci_reset failed %d\n"), + TRENTRY(XHCI_TRACE1_START_FAILED, + "XHCI ERROR: XHCI Failed to start\n"), + TRENTRY(XHCI_TRACE1_SLOTEN_FAILED, + "XHCI ERROR: XHCI Slot Enable failed %d\n"), + TRENTRY(XHCI_TRACE1_TRANSFER_FAILED, + "XHCI ERROR: XHCI transfer failed %d\n"), + TRENTRY(XHCI_TRACE1_BADXFRTYPE, + "XHCI ERROR: XHCI bad transfer type %d\n"), + + /* Verbose */ + + TRENTRY(XHCI_VTRACE1_INITIALIZING, + "XHCI Initializing XHCI Stack\n"), + TRENTRY(XHCI_VTRACE1_INIITIALIZED, + "XHCI USB XHCI Initialized\n"), + TRENTRY(XHCI_VTRACE1_PORTSC_CSC, + "XHCI Connect Status Change: %06x\n"), + TRENTRY(XHCI_VTRACE1_PORTSC_CONNALREADY, + "XHCI Already connected: %06x\n"), + TRENTRY(XHCI_VTRACE1_PORTSC_DISCALREADY, + "XHCI Already disconnected: %06x\n"), +}; + +static const struct xhci_usbhost_trace_s g_trace2[TRACE2_NSTRINGS] = +{ + TRENTRY(XHCI_TRACE2_EPSTALLED, + "XHCI EP%d Stalled: TOKEN=%04x\n"), + + /* Verbose */ + + TRENTRY(XHCI_VTRACE2_PORTSC, + "XHCI PORTSC%d: %08x\n"), + TRENTRY(XHCI_VTRACE2_PORTSC_CONNECTED, + "XHCI RHPort%d connected, pscwait: %d\n"), + TRENTRY(XHCI_VTRACE2_PORTSC_DISCONND, + "XHCI RHport%d disconnected, pscwait: %d\n"), + TRENTRY(XHCI_VTRACE2_MONWAKEUP, + "XHCI Hub port%d connected: %d\n"), + TRENTRY(XHCI_VTRACE2_CTRLINOUT, + "HXCI CTRLIN/OUT: RHPort%d req: %02x\n"), +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_trformat1 and usbhost_trformat2 + * + * Description: + * This interface must be provided by platform specific logic that knows + * the HCDs encoding of USB trace data. + * + * Given an 9-bit index, return a format string suitable for use with, say, + * printf. The returned format is expected to handle two unsigned integer + * values. + * + ****************************************************************************/ + +const char *usbhost_trformat1(uint16_t id) +{ + int ndx = TRACE1_INDEX(id); + + if (ndx < TRACE1_NSTRINGS) + { + return g_trace1[ndx].string; + } + + return NULL; +} + +const char *usbhost_trformat2(uint16_t id) +{ + int ndx = TRACE2_INDEX(id); + + if (ndx < TRACE2_NSTRINGS) + { + return g_trace2[ndx].string; + } + + return NULL; +} diff --git a/drivers/usbhost/usbhost_xhci_trace.h b/drivers/usbhost/usbhost_xhci_trace.h new file mode 100644 index 00000000000..933587d7bcf --- /dev/null +++ b/drivers/usbhost/usbhost_xhci_trace.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_xhci_trace.h + * + * 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 __DRIVERS_USBHOST_USBHOST_XHCI_TRACE_H +#define __DRIVERS_USBHOST_USBHOST_XHCI_TRACE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum usbhost_trace1codes_e +{ + __TRACE1_BASEVALUE = 0, /* This will force the first value to be 1 */ + + XHCI_TRACE1_RESET_FAILED, /* XHCI ERROR: sam_reset failed */ + XHCI_TRACE1_START_FAILED, /* XHCI ERROR: XHCI Failed to start */ + XHCI_TRACE1_SLOTEN_FAILED, /* XHCI ERROR: XHCI Slot Enable failed */ + XHCI_TRACE1_TRANSFER_FAILED, /* XHCI ERROR: XHCI transfer failed */ + XHCI_TRACE1_BADXFRTYPE, /* XHCI ERROR: XHCI bad transfer type */ + + /* Verbose */ + + XHCI_VTRACE1_INITIALIZING, /* XHCI Initializing XHCI Stack */ + XHCI_VTRACE1_INIITIALIZED, /* XHCI USB XHCI Initialized */ + XHCI_VTRACE1_PORTSC_CSC, /* XHCI Connect Status Change */ + XHCI_VTRACE1_PORTSC_CONNALREADY, /* XHCI Already connected */ + XHCI_VTRACE1_PORTSC_DISCALREADY, /* XHCI Already disconnected */ + + __TRACE1_NSTRINGS, /* Separates the format 1 from the format 2 strings */ + + XHCI_TRACE2_EPSTALLED, /* XHCI EP Stalled */ + + /* Verbose */ + + XHCI_VTRACE2_PORTSC, /* XHCI PORTSC */ + XHCI_VTRACE2_PORTSC_CONNECTED, /* XHCI RHPort connected */ + XHCI_VTRACE2_PORTSC_DISCONND, /* XHCI RHport disconnected */ + XHCI_VTRACE2_MONWAKEUP, /* XHCI RHPort connected wakeup */ + XHCI_VTRACE2_CTRLINOUT, /* XHCI CTRLIN/OUT */ + __TRACE2_NSTRINGS /* Total number of enumeration values */ +}; + +#define TRACE1_FIRST ((int)__TRACE1_BASEVALUE + 1) +#define TRACE1_INDEX(id) ((int)(id) - TRACE1_FIRST) +#define TRACE1_NSTRINGS TRACE1_INDEX(__TRACE1_NSTRINGS) + +#define TRACE2_FIRST ((int)__TRACE1_NSTRINGS + 1) +#define TRACE2_INDEX(id) ((int)(id) - TRACE2_FIRST) +#define TRACE2_NSTRINGS TRACE2_INDEX(__TRACE2_NSTRINGS) + +#endif /* #define __DRIVERS_USBHOST_USBHOST_XHCI_TRACE_H */ diff --git a/include/nuttx/usb/usbhost.h b/include/nuttx/usb/usbhost.h index 47e585a175b..e60b45c93f2 100644 --- a/include/nuttx/usb/usbhost.h +++ b/include/nuttx/usb/usbhost.h @@ -918,7 +918,7 @@ struct usbhost_driver_s usbhost_asynch_t callback, FAR void *arg); #endif - /* Cancel any pending syncrhonous or asynchronous transfer on an + /* Cancel any pending synchronous or asynchronous transfer on an * endpoint */ diff --git a/include/nuttx/usb/xhci_pci.h b/include/nuttx/usb/xhci_pci.h new file mode 100644 index 00000000000..022785fd64e --- /dev/null +++ b/include/nuttx/usb/xhci_pci.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * include/nuttx/usb/xhci_pci.h + * + * 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_PCI_H +#define __INCLUDE_NUTTX_USB_XHCI_PCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +#ifdef CONFIG_USBHOST_XHCI_PCI +int pci_xhci_init(void); +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_USB_XHCI_PCI_H */