walnux/drivers/usbhost/usbhost_xhci_pci.c
p-szafonimateusz fab4f68b6f drivers/usbhost: add xHCI support
add xHCI PCI driver (usbhost).

Signed-off-by: p-szafonimateusz <p-szafonimateusz@xiaomi.com>
2025-05-15 21:58:57 +08:00

4852 lines
135 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <sys/endian.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/wqueue.h>
#include <nuttx/addrenv.h>
#include <nuttx/spinlock.h>
#include <nuttx/pci/pci.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbhost.h>
#include <nuttx/usb/usbhost_devaddr.h>
#include <nuttx/usb/usbhost_trace.h>
#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);
}