mirror of
https://github.com/apache/nuttx.git
synced 2026-10-04 04:38:10 +00:00
struct usbhost_roothubport_s carries the number of the controller its port belongs to, so a port can be named on a system with more than one. Nothing set it. Take the number from whoever brings the controller up rather than counting registrations, which would agree with the name the driver reports only while controllers are registered in the order they are named. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond <justin@dynam.ac>
6035 lines
176 KiB
C
6035 lines
176 KiB
C
/****************************************************************************
|
|
* drivers/usbhost/usbhost_xhci.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <assert.h>
|
|
#include <nuttx/debug.h>
|
|
#include <errno.h>
|
|
#include <syslog.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/endian.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/kthread.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/addrenv.h>
|
|
#include <nuttx/cache.h>
|
|
#include <nuttx/spinlock.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
|
|
|
|
/* Some constants for this implementation */
|
|
|
|
#define XHCI_MAX_ERST (1)
|
|
#define XHCI_CMD_MAX (16)
|
|
#define XHCI_EVENT_MAX (232)
|
|
#define XHCI_TD_MAX (8)
|
|
|
|
/* How long to give the controller to stop, in milliseconds. The
|
|
* specification asks for it within 16; this is generous.
|
|
*/
|
|
|
|
#define XHCI_HALT_TIMEOUT_MS (100)
|
|
|
|
/* How long to give a port to come up after being reset, in milliseconds.
|
|
* USB 2.0 asks for the reset to be held 10ms and the port to be usable
|
|
* shortly after; this is generous.
|
|
*/
|
|
|
|
#define XHCI_PORT_RESET_MS (500)
|
|
|
|
/* How much memory a context occupies, which depends on the stride the
|
|
* controller asked for. One entry for the slot and one per endpoint, and
|
|
* the input context carries its control entry in front of both.
|
|
*/
|
|
|
|
#define XHCI_DEVCTX_SIZE(priv) ((1 + XHCI_MAX_ENDPOINTS) * (priv)->ctxsize)
|
|
#define XHCI_INCTX_SIZE(priv) ((2 + XHCI_MAX_ENDPOINTS) * (priv)->ctxsize)
|
|
#define XHCI_BUFSIZE (512)
|
|
|
|
/* Port numbers macros */
|
|
|
|
#define HPNDX(hp) ((hp)->port)
|
|
#define HPORT(hp) (HPNDX(hp) + 1)
|
|
#define RHPNDX(rh) ((rh)->hport.hport.port)
|
|
#define RHPORT(rh) (RHPNDX(rh) + 1)
|
|
|
|
/* Other helper macros */
|
|
|
|
#define XHCI_XCONN_FROM_CONN(c) ((FAR struct usbhost_conn_xhci_s *)c)
|
|
#define XHCI_PRIV_FROM_CONN(c) (XHCI_XCONN_FROM_CONN(c)->priv)
|
|
#define XHCI_RHPORT_FROM_DRVR(d) ((FAR struct xhci_rhport_s *)d)
|
|
#define XHCI_PRIV_FROM_RHPORT(r) (r->priv)
|
|
#define XHCI_PRIV_FROM_DRVR(d) (XHCI_PRIV_FROM_RHPORT(XHCI_RHPORT_FROM_DRVR(d)))
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* USB device state
|
|
*
|
|
* Reference:
|
|
* - 4.5.3: Slot States
|
|
*/
|
|
|
|
enum xhci_slot_e
|
|
{
|
|
XHCI_SLOT_DISABLED,
|
|
XHCI_SLOT_ENABLED,
|
|
XHCI_SLOT_DEFAULT,
|
|
XHCI_SLOT_ADDRESSED,
|
|
XHCI_SLOT_CONFIGURED,
|
|
};
|
|
|
|
/* Ring state */
|
|
|
|
struct xhci_ring_s
|
|
{
|
|
FAR struct xhci_trb_s *ring; /* Ring */
|
|
size_t i; /* Ring pointer */
|
|
size_t len; /* Ring length */
|
|
bool ccs; /* Consumer Cycle State */
|
|
};
|
|
|
|
/* EP info */
|
|
|
|
struct xhci_epinfo_s
|
|
{
|
|
uint8_t epno:7; /* Endpoint number */
|
|
uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */
|
|
uint8_t toggle:1; /* Next data toggle */
|
|
#ifndef CONFIG_USBHOST_INT_DISABLE
|
|
uint8_t interval; /* Polling interval */
|
|
#endif
|
|
uint8_t devaddr; /* Device address returned from xHCI */
|
|
uint8_t status; /* Retained token status bits (for debug purposes) */
|
|
bool iocwait; /* TRUE: Thread is waiting for transfer completion */
|
|
uint8_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */
|
|
int result; /* The result of the transfer */
|
|
size_t xfrd; /* On completion, will hold the number of bytes transferred */
|
|
size_t buflen; /* Buffer length used for transfer */
|
|
FAR uint8_t *buffer; /* The caller's buffer, for cache maintenance */
|
|
FAR uint8_t *bounce; /* Aligned stand-in for it, or NULL */
|
|
size_t dmalen; /* Length the cache is maintained over */
|
|
size_t dmacopy; /* Length to copy back out of a stand-in */
|
|
bool dmain; /* Direction this buffer was prepared for */
|
|
sem_t iocsem; /* Semaphore used to wait for transfer completion */
|
|
|
|
/* One transfer at a time on an endpoint. The controller lock below is
|
|
* released while a transfer is in flight, so it cannot serve this: two
|
|
* threads would each set up a transfer on the same endpoint and the
|
|
* second would find iocwait already set. A device's default control
|
|
* endpoint is the one that meets this, since every interface driver on
|
|
* a composite device speaks through it.
|
|
*/
|
|
|
|
mutex_t lock; /* Serialises transfers on this endpoint */
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
usbhost_asynch_t callback; /* Transfer complete callback */
|
|
FAR void *arg; /* Argument that accompanies the callback */
|
|
#endif
|
|
struct xhci_ring_s td; /* TD ring for this endpoint */
|
|
uint8_t slot; /* Slot where this EP resides */
|
|
};
|
|
|
|
/* This structure retains the state of one root hub port */
|
|
|
|
struct xhci_rhport_s
|
|
{
|
|
/* Common device fields. This must be the first thing defined in the
|
|
* structure so that it is possible to simply cast from struct usbhost_s
|
|
* to struct xhci_rhport_s.
|
|
*/
|
|
|
|
struct usbhost_driver_s drvr;
|
|
|
|
/* Root hub port status */
|
|
|
|
bool connected; /* Connected to device */
|
|
uint8_t enumfail; /* Consecutive failed enumerations */
|
|
int8_t slot; /* Slot ID associated with this port */
|
|
struct xhci_epinfo_s ep0; /* EP0 endpoint info */
|
|
struct usbhost_roothubport_s hport; /* This is the hub port description understood
|
|
* by class drivers
|
|
*/
|
|
FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */
|
|
FAR struct xhci_dev_s *dev; /* Device reference */
|
|
};
|
|
|
|
/* USB Devices xhci data */
|
|
|
|
struct xhci_dev_s
|
|
{
|
|
uint8_t state; /* Slot stat */
|
|
uint8_t slot; /* Slot ID associated with this device */
|
|
FAR struct xhci_dev_ctx_s *ctx; /* Output Device Context. Managed by xHC */
|
|
FAR struct xhci_input_dev_ctx_s *input; /* Input Device Context. Input to xHC */
|
|
FAR struct xhci_rhport_s *rhport; /* Root Hub Port associated with this device */
|
|
|
|
/* The port this device is attached to. Several devices can share a root
|
|
* hub port once a hub is in between, so this, and not the port above, is
|
|
* what identifies a device to the class drivers.
|
|
*/
|
|
|
|
FAR struct usbhost_hubport_s *hport;
|
|
|
|
/* True once the controller has been told this device is a hub. It is not
|
|
* known when the slot is created: the hub descriptor is read later, and
|
|
* only then does anything know how many ports it has.
|
|
*/
|
|
|
|
bool ishub;
|
|
|
|
/* Reference to allocated endpoints */
|
|
|
|
FAR struct xhci_epinfo_s *epinfo[XHCI_MAX_ENDPOINTS];
|
|
};
|
|
|
|
/* This structure contains the internal, private state of the xhci driver */
|
|
|
|
struct usbhost_xhci_s
|
|
{
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/* Ports a hub has reported and the waiter has not collected. A hub
|
|
* reports each changed port without waiting for the last, so several can
|
|
* be outstanding, but never more than there are slots.
|
|
*/
|
|
|
|
FAR struct usbhost_hubport_s *hports[CONFIG_USBHOST_XHCI_MAX_DEVS];
|
|
uint8_t hhead; /* Next free entry */
|
|
uint8_t htail; /* Next entry to collect */
|
|
#endif
|
|
struct usbhost_devaddr_s devgen; /* Address generation data */
|
|
bool pscwait; /* TRUE: Thread is waiting for port status change event */
|
|
sem_t pscsem; /* Semaphore to wait for port status change events */
|
|
mutex_t lock; /* Support mutually exclusive access */
|
|
spinlock_t spinlock;
|
|
|
|
/* xHCI parameters */
|
|
|
|
uint8_t no_ports; /* Number of USB Ports */
|
|
uint8_t bus; /* Which controller this is */
|
|
uint8_t no_slots; /* Maximum number of Device Slots (one per USB device) */
|
|
uint8_t no_scratch; /* Number of scratch buffers */
|
|
uint8_t no_erst; /* Event Ring Segment Table size */
|
|
|
|
/* xHCI data */
|
|
|
|
FAR struct xhci_rhport_s *rhport; /* Root hub ports */
|
|
FAR struct xhci_dev_s *devs; /* USB device xHC data. One entry per
|
|
* one supported USB device.
|
|
*/
|
|
|
|
/* Allocated buffers for controller */
|
|
|
|
FAR uint64_t *pg_ctx; /* Device Context (no_slots + 1 elements).
|
|
* Slot 0 reserved for Scratchpad Buffer Array
|
|
*/
|
|
FAR uint64_t *pg_sb; /* Scratchpad Buffer Array (no_scratch elements) */
|
|
FAR struct xhci_event_ring_s *pg_erst; /* Event Ring Segment Table */
|
|
|
|
/* Event ring handling */
|
|
|
|
struct xhci_ring_s evnt; /* Event ring handler */
|
|
|
|
/* Command ring handling */
|
|
|
|
sem_t cmdsem; /* Command done semaphore */
|
|
struct xhci_trb_s cmdres; /* Command result */
|
|
struct xhci_ring_s cmd; /* Command ring handler */
|
|
|
|
/* Bus hookup, supplied by whoever found this controller */
|
|
|
|
FAR const struct xhci_bus_ops_s *ops; /* Bus operations */
|
|
FAR void *arg; /* Bus private data */
|
|
FAR const char *name; /* What to call this controller */
|
|
uint8_t ctxsize; /* Context stride, 32 or 64 bytes */
|
|
uint32_t pending; /* IRQ pending status */
|
|
struct work_s work; /* IRQ work */
|
|
struct work_s pscwork; /* Port status change work */
|
|
uint64_t base; /* xHCI base address */
|
|
uint64_t capa_base; /* Capability base */
|
|
uint64_t oper_base; /* Operational base */
|
|
uint64_t runt_base; /* Runtime base */
|
|
uint64_t door_base; /* Doorbell base */
|
|
};
|
|
|
|
/* xHCI connection monitoring */
|
|
|
|
struct usbhost_conn_xhci_s
|
|
{
|
|
struct usbhost_connection_s conn; /* Connection monitoring */
|
|
FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */
|
|
int pid; /* Waiter thread PID */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Helpers ******************************************************************/
|
|
|
|
static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset);
|
|
|
|
static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset);
|
|
static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint8_t value);
|
|
|
|
static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset);
|
|
static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint32_t value);
|
|
|
|
static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint64_t value);
|
|
|
|
static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset);
|
|
static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint32_t value);
|
|
|
|
static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint64_t value);
|
|
|
|
static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint32_t value);
|
|
|
|
/* Byte stream access helper functions **************************************/
|
|
|
|
static inline uint16_t xhci_getle16(FAR const uint8_t *val);
|
|
|
|
/* Debug ********************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_USB_INFO
|
|
static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg, unsigned int offset);
|
|
static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg, unsigned int offset);
|
|
static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg, unsigned int offset);
|
|
static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg);
|
|
#endif
|
|
|
|
/* Ring management **********************************************************/
|
|
|
|
static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len);
|
|
static void xhci_ring_deinit(FAR struct xhci_ring_s *ring);
|
|
static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs);
|
|
static void xhci_add_trb(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_ring_s *ring,
|
|
FAR struct xhci_trb_s *trb,
|
|
int len);
|
|
|
|
/* xHCI operations **********************************************************/
|
|
|
|
static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv);
|
|
|
|
/* Port management **********************************************************/
|
|
|
|
static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv);
|
|
static FAR const char *xhci_speed_str(uint32_t portsc);
|
|
static int xhci_port_enable(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
/* Slot management **********************************************************/
|
|
|
|
static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index,
|
|
uintptr_t ctx);
|
|
static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_ep_ctx_s *ctx,
|
|
uint8_t type, uint16_t maxpkt,
|
|
uint8_t maxburst, uint64_t tr_dp,
|
|
uint8_t mult, uint8_t interval);
|
|
static int xhci_address_set(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev, bool setaddr);
|
|
static int xhci_slot_init(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev);
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
static uint32_t xhci_route_string(FAR struct usbhost_hubport_s *hport);
|
|
static uint32_t xhci_slot_tt(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev);
|
|
#endif
|
|
static int xhci_device_init(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_rhport_s *rhport,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
FAR struct xhci_epinfo_s *ep0info);
|
|
static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev);
|
|
static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo);
|
|
static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev,
|
|
uint32_t drop, uint32_t add);
|
|
|
|
/* Command handling *********************************************************/
|
|
|
|
static int xhci_command(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_trb_s *trb, uint16_t timeout_ms);
|
|
static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv,
|
|
FAR uint8_t *slot);
|
|
static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot);
|
|
static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint64_t ctx, bool bsr);
|
|
static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint64_t ctx, bool deconfig);
|
|
static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint8_t ep, bool suspend);
|
|
static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint64_t ctx);
|
|
|
|
/* Transfer handling ********************************************************/
|
|
|
|
static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo);
|
|
static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
size_t buflen);
|
|
static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
size_t buflen,
|
|
usbhost_asynch_t callback,
|
|
FAR void *arg);
|
|
static void xhci_asynch_completion(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo);
|
|
#endif
|
|
static int xhci_control_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
#ifndef CONFIG_USBHOST_ISOC_DISABLE
|
|
static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
#endif
|
|
static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo);
|
|
static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
static uint32_t xhci_speed_id(uint8_t speed);
|
|
static inline FAR struct xhci_slot_ctx_s *
|
|
xhci_in_slot(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_input_dev_ctx_s *input);
|
|
static inline FAR struct xhci_ep_ctx_s *
|
|
xhci_in_ep(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_input_dev_ctx_s *input, int epidx);
|
|
static inline FAR struct xhci_slot_ctx_s *
|
|
xhci_out_slot(FAR struct xhci_dev_ctx_s *ctx);
|
|
static FAR uint8_t *xhci_dma_prepare(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
bool dirin);
|
|
static void xhci_dma_finish(FAR struct xhci_epinfo_s *epinfo);
|
|
|
|
/* Interrupt handling *******************************************************/
|
|
|
|
static void xhci_portsc_work(FAR void *arg);
|
|
static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_trb_s *evt);
|
|
static void xhci_event_complete(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_trb_s *evt);
|
|
static int xhci_events_poll(FAR struct usbhost_xhci_s *priv);
|
|
static void xhci_interrupt_work(FAR void *arg);
|
|
static int xhci_interrupt(int irq, FAR void *context, FAR void *arg);
|
|
|
|
/* USB host controller operations *******************************************/
|
|
|
|
static int xhci_wait(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s **hport);
|
|
static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
static int xhci_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0, uint8_t funcaddr,
|
|
uint8_t speed, uint16_t maxpacketsize);
|
|
static int xhci_epalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep);
|
|
static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep);
|
|
static int xhci_alloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, FAR size_t *maxlen);
|
|
static int xhci_free(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t *buffer);
|
|
static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, size_t buflen);
|
|
static int xhci_iofree(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t *buffer);
|
|
|
|
static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer);
|
|
static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer);
|
|
static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR const uint8_t *buffer);
|
|
static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr,
|
|
FAR usbhost_ep_t ep, uint8_t *buffer,
|
|
size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, void *arg);
|
|
#endif
|
|
static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep);
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
static int xhci_connect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
bool connected);
|
|
#endif
|
|
|
|
static void xhci_disconnect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
/* Initialization ***********************************************************/
|
|
|
|
static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_mem_free(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv);
|
|
static int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/* xHCI requires aligned accesses of each register's own size, and
|
|
* narrower ones may be ignored. volatile does not pin the access width,
|
|
* so every accessor below launders the value through a register with an
|
|
* empty asm, on loads and stores both, to force the full-width access.
|
|
*/
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_capa_getreg
|
|
*
|
|
* Description:
|
|
* Get register (USB Legacy Support Capability)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset)
|
|
{
|
|
uintptr_t addr = priv->capa_base + offset;
|
|
uint32_t regval = *((FAR volatile uint32_t *)addr);
|
|
|
|
__asm__ __volatile__("" : "+r"(regval));
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_capa_getreg_1b
|
|
*
|
|
* Description:
|
|
* Get 1B register (USB Legacy Support Capability)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset)
|
|
{
|
|
uintptr_t addr = priv->capa_base + offset;
|
|
uint8_t regval = *((FAR volatile uint8_t *)addr);
|
|
|
|
__asm__ __volatile__("" : "+r"(regval));
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_capa_putreg_1b
|
|
*
|
|
* Description:
|
|
* Put 1B register (USB Legacy Support Capability)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint8_t value)
|
|
{
|
|
uintptr_t addr = priv->capa_base + offset;
|
|
|
|
__asm__ __volatile__("" : "+r"(value));
|
|
*((FAR volatile uint8_t *)addr) = value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_oper_getreg
|
|
*
|
|
* Description:
|
|
* Get register (Host Controller Operational Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset)
|
|
{
|
|
uintptr_t addr = priv->oper_base + offset;
|
|
uint32_t regval = *((FAR volatile uint32_t *)addr);
|
|
|
|
__asm__ __volatile__("" : "+r"(regval));
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_oper_putreg
|
|
*
|
|
* Description:
|
|
* Put register (Host Controller Operational Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint32_t value)
|
|
{
|
|
uintptr_t addr = priv->oper_base + offset;
|
|
|
|
__asm__ __volatile__("" : "+r"(value));
|
|
*((FAR volatile uint32_t *)addr) = value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_oper_putreg_8b
|
|
*
|
|
* Description:
|
|
* Put register (Host Controller Operational Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint64_t value)
|
|
{
|
|
uintptr_t addr = priv->oper_base + offset;
|
|
|
|
__asm__ __volatile__("" : "+r"(value));
|
|
*((FAR volatile uint64_t *)addr) = value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_runt_getreg
|
|
*
|
|
* Description:
|
|
* Get register (Host Controller Runtime Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset)
|
|
{
|
|
uintptr_t addr = priv->runt_base + offset;
|
|
uint32_t regval = *((FAR volatile uint32_t *)addr);
|
|
|
|
__asm__ __volatile__("" : "+r"(regval));
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_runt_putreg
|
|
*
|
|
* Description:
|
|
* Put register (Host Controller Runtime Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint32_t value)
|
|
{
|
|
uintptr_t addr = priv->runt_base + offset;
|
|
|
|
__asm__ __volatile__("" : "+r"(value));
|
|
*((FAR volatile uint32_t *)addr) = value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_runt_putreg_8b
|
|
*
|
|
* Description:
|
|
* Put register (Host Controller Runtime Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint64_t value)
|
|
{
|
|
uintptr_t addr = priv->runt_base + offset;
|
|
|
|
__asm__ __volatile__("" : "+r"(value));
|
|
*((FAR volatile uint64_t *)addr) = value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_door_putreg
|
|
*
|
|
* Description:
|
|
* Put register (Doorbell Registers)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv,
|
|
unsigned int offset,
|
|
uint32_t value)
|
|
{
|
|
uintptr_t addr = priv->door_base + offset;
|
|
|
|
__asm__ __volatile__("" : "+r"(value));
|
|
*((FAR volatile uint32_t *)addr) = value;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_USB_INFO
|
|
/****************************************************************************
|
|
* Name: xhci_dump_capa_reg
|
|
****************************************************************************/
|
|
|
|
static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg, unsigned int offset)
|
|
{
|
|
uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_capa_getreg(priv, offset));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dump_oper_reg
|
|
****************************************************************************/
|
|
|
|
static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg, unsigned int offset)
|
|
{
|
|
uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_oper_getreg(priv, offset));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dump_runt_reg
|
|
****************************************************************************/
|
|
|
|
static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg, unsigned int offset)
|
|
{
|
|
uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_runt_getreg(priv, offset));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dump_mem
|
|
****************************************************************************/
|
|
|
|
static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv,
|
|
FAR const char *msg)
|
|
{
|
|
int i;
|
|
|
|
uinfo("Dump xHCI registers: %s\n", msg);
|
|
|
|
uinfo("=== Host Controller Capability Registers ===\n");
|
|
|
|
/* CAPLENGTH and HCIVERSION share one word, and a register block reached
|
|
* over a bus that only answers aligned accesses cannot be read at the
|
|
* odd offset the second one has. Read the word once and take both from
|
|
* it.
|
|
*/
|
|
|
|
uinfo("\tCAPLENGTH :\t\t0x%" PRIx32 "\n",
|
|
xhci_capa_getreg(priv, XHCI_CAPLENGTH) & 0xff);
|
|
uinfo("\tHCIVERSION :\t\t0x%" PRIx32 "\n",
|
|
xhci_capa_getreg(priv, XHCI_CAPLENGTH) >> 16);
|
|
xhci_dump_capa_reg(priv, "HCSPARAMS1 ", XHCI_HCSPARAMS1);
|
|
xhci_dump_capa_reg(priv, "HCSPARAMS2 ", XHCI_HCSPARAMS2);
|
|
xhci_dump_capa_reg(priv, "HCSPARAMS3 ", XHCI_HCSPARAMS3);
|
|
xhci_dump_capa_reg(priv, "HCCPARAMS1 ", XHCI_HCCPARAMS1);
|
|
xhci_dump_capa_reg(priv, "DBOFF ", XHCI_DBOFF);
|
|
xhci_dump_capa_reg(priv, "RTSOFF ", XHCI_RTSOFF);
|
|
xhci_dump_capa_reg(priv, "HCCPARAMS2 ", XHCI_HCCPARAMS2);
|
|
|
|
uinfo("=== Host Controller Operational Registers ===\n");
|
|
xhci_dump_oper_reg(priv, "USBCMD ", XHCI_USBCMD);
|
|
xhci_dump_oper_reg(priv, "USBSTS ", XHCI_USBSTS);
|
|
xhci_dump_oper_reg(priv, "PAGESIZE ", XHCI_PAGESIZE);
|
|
xhci_dump_oper_reg(priv, "DNCTRL ", XHCI_DNCTRL);
|
|
xhci_dump_oper_reg(priv, "CRCR ", XHCI_CRCR);
|
|
xhci_dump_oper_reg(priv, "DCBAAP ", XHCI_DCBAAP);
|
|
xhci_dump_oper_reg(priv, "CONFIG ", XHCI_CONFIG);
|
|
|
|
for (i = 0; i < priv->no_ports; i++)
|
|
{
|
|
uinfo("port %d --------------------------------\n", i);
|
|
xhci_dump_oper_reg(priv, "PORTSC ", XHCI_PORTSC(i));
|
|
xhci_dump_oper_reg(priv, "PORTPMSC ", XHCI_PORTPMSC(i));
|
|
xhci_dump_oper_reg(priv, "PORTLI ", XHCI_PORTLI(i));
|
|
}
|
|
|
|
/* Only one interrupter used */
|
|
|
|
uinfo("=== Host Controller Runtime Registers ===\n");
|
|
xhci_dump_runt_reg(priv, "MFINDEX ", XHCI_MFINDEX);
|
|
xhci_dump_runt_reg(priv, "IMAN(0) ", XHCI_IMAN(0));
|
|
xhci_dump_runt_reg(priv, "IMOD(0) ", XHCI_IMOD(0));
|
|
xhci_dump_runt_reg(priv, "ERSTSZ(0) ", XHCI_ERSTSZ(0));
|
|
xhci_dump_runt_reg(priv, "ERSTBA(0) ", XHCI_ERSTBA(0));
|
|
xhci_dump_runt_reg(priv, "ERDP(0) ", XHCI_ERDP(0));
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_getle16
|
|
*
|
|
* Description:
|
|
* Get a (possibly unaligned) 16-bit little endian value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint16_t xhci_getle16(FAR const uint8_t *val)
|
|
{
|
|
#ifdef CONFIG_ENDIAN_BIG
|
|
return (uint16_t)val[0] << 8 | (uint16_t)val[1];
|
|
#else
|
|
return (uint16_t)val[1] << 8 | (uint16_t)val[0];
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ring_init
|
|
*
|
|
* Description:
|
|
* Initialize xHCI ring handler.
|
|
*
|
|
* If ring buffer is already initialized, this function reset ring
|
|
* to a initial state.
|
|
*
|
|
* Returned Value:
|
|
* OK on success.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len)
|
|
{
|
|
FAR struct xhci_trb_s *trb;
|
|
|
|
if (!ring->ring)
|
|
{
|
|
/* Allocate ring data */
|
|
|
|
ring->ring = kmm_memalign(XHCI_BUF_ALIGN,
|
|
sizeof(struct xhci_trb_s) * len);
|
|
if (!ring->ring)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Store length */
|
|
|
|
ring->len = len;
|
|
}
|
|
|
|
/* Reset data in ring.
|
|
*
|
|
* Clearing dirties every line, and the controller writes into this
|
|
* memory itself. Flush now, or a later writeback lands on top of an
|
|
* event somebody is waiting for.
|
|
*/
|
|
|
|
memset(ring->ring, 0, ring->len * sizeof(struct xhci_trb_s));
|
|
up_flush_dcache((uintptr_t)ring->ring,
|
|
(uintptr_t)(ring->ring + ring->len));
|
|
|
|
/* Fill Link TRB */
|
|
|
|
trb = &ring->ring[ring->len - 1];
|
|
trb->d0 = htole64(up_addrenv_va_to_pa(&ring->ring[0]));
|
|
trb->d1 = 0;
|
|
trb->d2 = 0;
|
|
|
|
up_flush_dcache((uintptr_t)trb, (uintptr_t)(trb + 1));
|
|
|
|
/* Reset state */
|
|
|
|
ring->i = 0;
|
|
ring->ccs = true;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ring_deinit
|
|
*
|
|
* Description:
|
|
* Initialize xHCI ring handler.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_ring_deinit(FAR struct xhci_ring_s *ring)
|
|
{
|
|
/* Free ring memory */
|
|
|
|
kmm_free(ring->ring);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ring_reset
|
|
*
|
|
* Description:
|
|
* Reset xHCI ring handler.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs)
|
|
{
|
|
/* Reset pointer */
|
|
|
|
ring->i = 0;
|
|
|
|
/* Swap CCS if requestede */
|
|
|
|
if (swap_ccs)
|
|
{
|
|
ring->ccs = !ring->ccs;
|
|
}
|
|
else
|
|
{
|
|
ring->ccs = true;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_add_trb
|
|
*
|
|
* Description:
|
|
* Reset TRB to a ring.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_add_trb(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_ring_s *ring,
|
|
FAR struct xhci_trb_s *trb,
|
|
int len)
|
|
{
|
|
uint32_t d2;
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
d2 = trb[i].d2;
|
|
|
|
if (ring->ccs)
|
|
{
|
|
d2 |= XHCI_TRB_D2_C;
|
|
}
|
|
else
|
|
{
|
|
d2 &= ~XHCI_TRB_D2_C;
|
|
}
|
|
|
|
/* Make sure the cycle bit has the correct value */
|
|
|
|
DEBUGASSERT((ring->ring[ring->i].d2 & XHCI_TRB_D2_C) != ring->ccs);
|
|
|
|
/* Write TRB */
|
|
|
|
ring->ring[ring->i].d0 = htole64(trb[i].d0);
|
|
ring->ring[ring->i].d1 = htole32(trb[i].d1);
|
|
ring->ring[ring->i].d2 = htole32(d2);
|
|
|
|
/* Next TD */
|
|
|
|
ring->i++;
|
|
|
|
/* Handle end of the command ring */
|
|
|
|
if (ring->i >= ring->len - 1)
|
|
{
|
|
/* Make sure the cycle bit has the correct value */
|
|
|
|
DEBUGASSERT((ring->ring[0].d2 & XHCI_TRB_D2_C) == ring->ccs);
|
|
|
|
if (ring->ccs)
|
|
{
|
|
d2 = XHCI_TRB_D2_C | XHCI_TRB_D2_TC |
|
|
XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK);
|
|
}
|
|
else
|
|
{
|
|
d2 = XHCI_TRB_D2_TC |
|
|
XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK);
|
|
}
|
|
|
|
/* Carry the chain forward across the join.
|
|
*
|
|
* A multi-TRB transfer can reach the end of the ring part way
|
|
* through, putting the link inside it. A link without the
|
|
* chain bit ends the transfer where it stands, and the TRB that
|
|
* asked for the completion interrupt is never reached.
|
|
*/
|
|
|
|
if ((trb[i].d2 & XHCI_TRB_D2_CH) != 0)
|
|
{
|
|
d2 |= XHCI_TRB_D2_CH;
|
|
}
|
|
|
|
/* Other parameters are already correct for this TRB */
|
|
|
|
ring->ring[ring->i].d2 = htole32(d2);
|
|
|
|
/* Update CCS */
|
|
|
|
xhci_ring_reset(ring, true);
|
|
}
|
|
}
|
|
|
|
/* Flush ring */
|
|
|
|
up_flush_dcache((uintptr_t)ring->ring,
|
|
(uintptr_t)(ring->ring + ring->len));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_bios_wait
|
|
*
|
|
* Description:
|
|
* Wait for BIOS to give up the controller lock
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
uint32_t cstart;
|
|
uint32_t ecp;
|
|
uint32_t eec;
|
|
uint8_t sem;
|
|
uint8_t timeout;
|
|
int ret = OK;
|
|
|
|
/* Get Extended Capability Pointer */
|
|
|
|
cstart = XHCI_HCCPARAMS1_XECP(xhci_capa_getreg(priv, XHCI_HCCPARAMS1));
|
|
|
|
/* Find USBLEGSUP - if present, we have to acquire for BIOS semaphore */
|
|
|
|
eec = -1;
|
|
ecp = (cstart << 2);
|
|
|
|
while (1)
|
|
{
|
|
if (ecp == 0 || XHCI_USBLEGSUP_NEXT(eec) == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
eec = xhci_capa_getreg(priv, ecp);
|
|
|
|
if (XHCI_USBLEGSUP_ID(eec) == XHCI_ID_USBLEGSUP)
|
|
{
|
|
/* We have to wait for semaphore */
|
|
|
|
ret = -EAGAIN;
|
|
|
|
/* Get BIOS semaphore */
|
|
|
|
sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM);
|
|
if (sem == 0)
|
|
{
|
|
ret = OK;
|
|
break;
|
|
}
|
|
|
|
/* Get semaphore request */
|
|
|
|
xhci_capa_putreg_1b(priv, ecp + XHCI_USBLEGSUP_OS_SEM, 1);
|
|
|
|
/* Wait for semaphore released from BIOS */
|
|
|
|
for (timeout = 0; timeout < 100; timeout++)
|
|
{
|
|
sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM);
|
|
if (sem == 0)
|
|
{
|
|
ret = OK;
|
|
break;
|
|
}
|
|
|
|
up_mdelay(100);
|
|
}
|
|
}
|
|
|
|
/* Next cap */
|
|
|
|
ecp += (XHCI_USBLEGSUP_NEXT(eec) << 2);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ctrl_start
|
|
*
|
|
* Description:
|
|
* Start controller.
|
|
*
|
|
* According to "4.2 Host Controller Initialization".
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
FAR struct xhci_event_ring_s *evnt;
|
|
uint32_t regval;
|
|
int ret;
|
|
int i;
|
|
|
|
uinfo("Start controller\n");
|
|
|
|
/* Reset controller before writing any Operational or Runtime registers */
|
|
|
|
ret = xhci_ctrl_reset(priv);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(XHCI_TRACE1_RESET_FAILED, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* TODO: clear interrupts and disable all device notifications */
|
|
|
|
/* Max Device Slots Enabled */
|
|
|
|
xhci_oper_putreg(priv, XHCI_CONFIG, priv->no_slots);
|
|
|
|
/* Slot 0 of the Device Context array points at the Scratchpad Buffer
|
|
* Array, or is zero when the controller asked for none.
|
|
*/
|
|
|
|
priv->pg_ctx[0] = priv->pg_sb ?
|
|
htole64(up_addrenv_va_to_pa(priv->pg_sb)) : 0;
|
|
|
|
/* Device Context Base Address Array Pointer */
|
|
|
|
xhci_oper_putreg_8b(priv, XHCI_DCBAAP, up_addrenv_va_to_pa(priv->pg_ctx));
|
|
|
|
/* Event Ring Segment Table Size */
|
|
|
|
xhci_runt_putreg(priv, XHCI_ERSTSZ(0), priv->no_erst);
|
|
|
|
/* Initialize command ring state and event ring state */
|
|
|
|
ret = xhci_ring_init(&priv->cmd, XHCI_CMD_MAX);
|
|
if (ret < 0)
|
|
{
|
|
uerr("cmd ring init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = xhci_ring_init(&priv->evnt, XHCI_EVENT_MAX);
|
|
if (ret < 0)
|
|
{
|
|
uerr("event ring init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Configure Event Ring */
|
|
|
|
evnt = (struct xhci_event_ring_s *)priv->pg_erst;
|
|
evnt->base = htole64(up_addrenv_va_to_pa(priv->evnt.ring));
|
|
evnt->size = XHCI_EVENT_MAX;
|
|
evnt->res = 0;
|
|
|
|
/* Push the structures the controller is about to be pointed at.
|
|
*
|
|
* Flush by address: up_flush_dcache_all() is a no-op on architectures
|
|
* whose cache can only be maintained by address.
|
|
*/
|
|
|
|
up_flush_dcache((uintptr_t)priv->pg_erst,
|
|
(uintptr_t)priv->pg_erst +
|
|
sizeof(struct xhci_event_ring_s) * priv->no_erst);
|
|
up_flush_dcache((uintptr_t)priv->pg_ctx,
|
|
(uintptr_t)(priv->pg_ctx + priv->no_slots + 1));
|
|
if (priv->pg_sb != NULL)
|
|
{
|
|
up_flush_dcache((uintptr_t)priv->pg_sb,
|
|
(uintptr_t)(priv->pg_sb + priv->no_scratch));
|
|
}
|
|
|
|
xhci_runt_putreg_8b(priv, XHCI_ERDP(0),
|
|
up_addrenv_va_to_pa(priv->evnt.ring));
|
|
|
|
/* Write ERSTBA with ERST(0).BaseAddress.
|
|
*
|
|
* This must be done after ERST[0] initialization and after write to
|
|
* ERSTSZ. When the ERSTBA register is written, the Event Ring State
|
|
* Machine is set to the Start state.
|
|
*
|
|
* For details look at "4.9.4 Event Ring Management"
|
|
*/
|
|
|
|
xhci_runt_putreg_8b(priv, XHCI_ERSTBA(0),
|
|
up_addrenv_va_to_pa(priv->pg_erst));
|
|
|
|
/* Last item in the command ring points to the beginning of the ring */
|
|
|
|
priv->cmd.ring[XHCI_CMD_MAX - 1].d0 = htole64(
|
|
up_addrenv_va_to_pa(priv->cmd.ring));
|
|
|
|
/* Configure the Command Ring */
|
|
|
|
xhci_oper_putreg_8b(priv, XHCI_CRCR,
|
|
up_addrenv_va_to_pa(priv->cmd.ring) | XHCI_CRCR_RCS);
|
|
|
|
/* Do not sit on completions; see XHCI_IMOD_INTERVAL */
|
|
|
|
xhci_runt_putreg(priv, XHCI_IMOD(0), XHCI_IMOD_DEFAULT);
|
|
|
|
/* Enable interrupts */
|
|
|
|
regval = xhci_runt_getreg(priv, XHCI_IMAN(0));
|
|
regval |= XHCI_IMAN_IE;
|
|
xhci_runt_putreg(priv, XHCI_IMAN(0), regval);
|
|
|
|
/* And the command ring, whose last entry was just made to point back at
|
|
* its own beginning.
|
|
*/
|
|
|
|
up_flush_dcache((uintptr_t)priv->cmd.ring,
|
|
(uintptr_t)(priv->cmd.ring + XHCI_CMD_MAX));
|
|
|
|
/* Turn the host controller ON, enable interrupts and system errors */
|
|
|
|
xhci_oper_putreg(priv, XHCI_USBCMD,
|
|
XHCI_USBCMD_RS |
|
|
XHCI_USBCMD_INTE |
|
|
XHCI_USBCMD_HSEE);
|
|
|
|
/* Wait for controller started */
|
|
|
|
ret = -EAGAIN;
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
up_mdelay(100);
|
|
|
|
if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH))
|
|
{
|
|
ret = OK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check for timeout */
|
|
|
|
if (ret != OK)
|
|
{
|
|
uerr("Can't start controller!");
|
|
return ret;
|
|
}
|
|
|
|
/* Poll all pending events */
|
|
|
|
xhci_events_poll(priv);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ctrl_halt
|
|
*
|
|
* Description:
|
|
* Halt controller.
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
int i;
|
|
|
|
/* A controller that was never started is already halted and says so.
|
|
* There is no transition to wait for, so check before waiting.
|
|
*/
|
|
|
|
regval = xhci_oper_getreg(priv, XHCI_USBSTS);
|
|
if ((regval & XHCI_USBSTS_HCH) != 0)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/* Clear Run/Stop and leave the rest of the register alone. Writing the
|
|
* whole of it zero would clear the interrupt and host system error
|
|
* enables along with it.
|
|
*/
|
|
|
|
regval = xhci_oper_getreg(priv, XHCI_USBCMD);
|
|
regval &= ~XHCI_USBCMD_RS;
|
|
xhci_oper_putreg(priv, XHCI_USBCMD, regval);
|
|
|
|
for (i = 0; i < XHCI_HALT_TIMEOUT_MS; i++)
|
|
{
|
|
regval = xhci_oper_getreg(priv, XHCI_USBSTS);
|
|
if ((regval & XHCI_USBSTS_HCH) != 0)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
up_udelay(1000);
|
|
}
|
|
|
|
uerr("controller will not halt, USBSTS %08" PRIx32 "\n", regval);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ctrl_reset
|
|
*
|
|
* Description:
|
|
* Reset controller.
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
int ret = -EAGAIN;
|
|
int i;
|
|
|
|
/* Halt controller */
|
|
|
|
xhci_oper_putreg(priv, XHCI_USBCMD, XHCI_USBCMD_HCRST);
|
|
|
|
/* Wait for controller halted */
|
|
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
up_mdelay(100);
|
|
|
|
if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_CNR))
|
|
{
|
|
ret = OK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_probe_ports
|
|
*
|
|
* Description:
|
|
* Initial ports probe.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
uint32_t portsc;
|
|
int i;
|
|
|
|
for (i = 0; i < priv->no_ports; i++)
|
|
{
|
|
portsc = xhci_oper_getreg(priv, XHCI_PORTSC(i));
|
|
priv->rhport[i].connected = ((portsc & XHCI_PORTSC_CCS) != 0);
|
|
|
|
/* Clear status change, but not PED. Port Enabled/Disabled is
|
|
* write-one-to-clear, so writing back what was read disables any
|
|
* port that came up enabled, which is what a device attached at
|
|
* power up does.
|
|
*/
|
|
|
|
portsc &= ~XHCI_PORTSC_PED;
|
|
xhci_oper_putreg(priv, XHCI_PORTSC(i), portsc);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_port_enable
|
|
*
|
|
* Description:
|
|
* Set port to the Enable state.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_port_enable(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
uint32_t retries;
|
|
uint32_t regval;
|
|
uint8_t speed;
|
|
int rhpndx;
|
|
|
|
DEBUGASSERT(hport != NULL);
|
|
rhpndx = hport->port;
|
|
|
|
regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx));
|
|
|
|
/* A USB3 protocol port attempts to automatically advance to the
|
|
* Enabled state for port as part of the attach process.
|
|
*/
|
|
|
|
if (!(regval & XHCI_PORTSC_PED))
|
|
{
|
|
/* Reset the port, masking the write-one-to-clear bits out of the
|
|
* value first. See XHCI_PORTSC_RW1C.
|
|
*/
|
|
|
|
regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx));
|
|
regval &= ~XHCI_PORTSC_RW1C;
|
|
regval |= XHCI_PORTSC_PR;
|
|
xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), regval);
|
|
|
|
/* REVISIT: we get Port Status Change Event here */
|
|
|
|
/* Wait for Enabled state for port */
|
|
|
|
for (retries = XHCI_PORT_RESET_MS; retries > 0; retries--)
|
|
{
|
|
regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx));
|
|
if ((regval & XHCI_PORTSC_PED) != 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
up_mdelay(1);
|
|
}
|
|
|
|
/* Test the port, not the counter: a port that comes up on the last
|
|
* attempt leaves the loop with the count exhausted too.
|
|
*/
|
|
|
|
if ((regval & XHCI_PORTSC_PED) == 0)
|
|
{
|
|
uerr("port %d will not enable, PORTSC %08" PRIx32 "\n", rhpndx,
|
|
regval);
|
|
return -ETIMEDOUT;
|
|
}
|
|
}
|
|
|
|
/* Get port status */
|
|
|
|
regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx));
|
|
|
|
/* Get port speed */
|
|
|
|
speed = XHCI_PORTSC_PS(regval);
|
|
switch (speed)
|
|
{
|
|
case XHCI_PORTSC_PS_FULL:
|
|
{
|
|
hport->speed = USB_SPEED_FULL;
|
|
break;
|
|
}
|
|
|
|
case XHCI_PORTSC_PS_LOW:
|
|
{
|
|
hport->speed = USB_SPEED_LOW;
|
|
break;
|
|
}
|
|
|
|
case XHCI_PORTSC_PS_HIGH:
|
|
{
|
|
hport->speed = USB_SPEED_HIGH;
|
|
break;
|
|
}
|
|
|
|
case XHCI_PORTSC_PS_SUPPER11:
|
|
{
|
|
hport->speed = USB_SPEED_SUPER;
|
|
break;
|
|
}
|
|
|
|
case XHCI_PORTSC_PS_SUPPER21:
|
|
case XHCI_PORTSC_PS_SUPPER12:
|
|
case XHCI_PORTSC_PS_SUPPER22:
|
|
{
|
|
hport->speed = USB_SPEED_SUPER_PLUS;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
uerr("speed = 0x%x\n", speed);
|
|
hport->speed = USB_SPEED_UNKNOWN;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Say what turned up, now that the port can answer.
|
|
*
|
|
* The speed field only means anything once the port has been reset and
|
|
* enabled. A USB2 port reports the reset default, full speed, until
|
|
* then.
|
|
*/
|
|
|
|
syslog(LOG_INFO, "%s: port %d: device attached at %s\n",
|
|
priv->name, rhpndx + 1, xhci_speed_str(regval));
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dcbaa_set
|
|
*
|
|
* Description:
|
|
* Set entry in the Device Context Base Address Array, which should point
|
|
* to the Output Device Context data structure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index,
|
|
uintptr_t ctx)
|
|
{
|
|
/* NOTE: context must be physical address! */
|
|
|
|
priv->pg_ctx[index] = htole64(ctx);
|
|
|
|
/* Flush context */
|
|
|
|
up_flush_dcache((uintptr_t)priv->pg_ctx,
|
|
(uintptr_t)(priv->pg_ctx + priv->no_slots + 1));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ep_configure
|
|
*
|
|
* Description:
|
|
* Configure endpoint context.
|
|
*
|
|
* Reference:
|
|
* - 4.8.2 Endpoint Context Initialization
|
|
* - 6.2.3 Endpoint Context
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_ep_ctx_s *ctx,
|
|
uint8_t type, uint16_t maxpkt,
|
|
uint8_t maxburst, uint64_t tr_dp,
|
|
uint8_t mult, uint8_t interval)
|
|
{
|
|
uint32_t ctx0 = 0;
|
|
uint32_t ctx1 = 0;
|
|
uint64_t ctx2 = 0;
|
|
|
|
/* Set type */
|
|
|
|
ctx1 |= XHCI_EP_CTX1_EPTYPE(type);
|
|
|
|
/* Set max packet size */
|
|
|
|
ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpkt);
|
|
|
|
/* Set max burst size */
|
|
|
|
ctx1 |= XHCI_EP_CTX1_MAXBRST(maxburst);
|
|
|
|
/* Set TR Dequeue Pointer.
|
|
* NOTE: must be physical address aligned to 16-byte.
|
|
*/
|
|
|
|
DEBUGASSERT(tr_dp != 0 && tr_dp % 16 == 0);
|
|
ctx2 = tr_dp;
|
|
|
|
/* Set DCS.
|
|
* Should be set to 1 only if no stream (USB3.0 specific).
|
|
*/
|
|
|
|
ctx2 |= XHCI_EP_CTX2_DCS;
|
|
|
|
/* Set interval */
|
|
|
|
ctx0 |= XHCI_EP_CTX0_INTERVAL(interval);
|
|
|
|
/* Set max primary streams.
|
|
* Set to zero for now (USB3.0 specific)
|
|
*/
|
|
|
|
ctx0 |= XHCI_EP_CTX0_MAXPSTR(0);
|
|
|
|
/* Set mult */
|
|
|
|
ctx0 |= XHCI_EP_CTX0_MULT(mult);
|
|
|
|
/* Set error count to 3 if this is not ISOCH endpoint */
|
|
|
|
if (type != XHCI_EPTYPE_ISO_OUT && type != XHCI_EPTYPE_ISO_IN)
|
|
{
|
|
ctx1 |= XHCI_EP_CTX1_CERR(3);
|
|
}
|
|
|
|
/* Write context */
|
|
|
|
ctx->ctx0 = htole32(ctx0);
|
|
ctx->ctx1 = htole32(ctx1);
|
|
ctx->ctx2 = htole64(ctx2);
|
|
|
|
/* Flush context */
|
|
|
|
up_flush_dcache((uintptr_t)ctx,
|
|
(uintptr_t)ctx + sizeof(struct xhci_ep_ctx_s));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_address_set
|
|
*
|
|
* Description:
|
|
* Set address request.
|
|
*
|
|
* If setaddr is true, then xHC issue a SET_ADDRESS request.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_address_set(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev, bool setaddr)
|
|
{
|
|
uint64_t ctx = up_addrenv_va_to_pa(dev->input);
|
|
|
|
return xhci_cmd_setaddr(priv, dev->slot, ctx, !setaddr);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_slot_init
|
|
*
|
|
* Description:
|
|
* Initialize Device Slot data.
|
|
*
|
|
* Assumption:
|
|
* 1. All slot resources already allocated.
|
|
* 2. Port is in Enabled state.
|
|
*
|
|
* Reference:
|
|
* - 4.3.3. Device Slot Initialization
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_slot_init(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev)
|
|
{
|
|
uint32_t regval;
|
|
uint16_t maxpkt;
|
|
uintptr_t drdp;
|
|
|
|
/* Step 1. The Input Context data structure already allocated.
|
|
* Initialize all fields to 0.
|
|
*/
|
|
|
|
memset(dev->input, 0, XHCI_INCTX_SIZE(priv));
|
|
|
|
/* Step 2. Initialize the Input Control Context by setting the A0 and
|
|
* A1 flags to 1 (Slot flag and EP0 flag).
|
|
*/
|
|
|
|
regval = XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) |
|
|
XHCI_IN_CTX1_A(XHCI_EP0_FLAG);
|
|
xhci_context_ctrl(priv, dev, 0, regval);
|
|
|
|
/* Step 3. Initialize the Input Slot Context.
|
|
*
|
|
* The speed field has no valid zero. This is the only place the
|
|
* controller learns the device's speed, and one that checks refuses
|
|
* Address Device with a parameter error without it.
|
|
*/
|
|
|
|
regval = XHCI_ST_CTX0_CTXENT_SET(1) |
|
|
XHCI_ST_CTX0_SPEED_SET(xhci_speed_id(dev->hport->speed));
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
regval |= XHCI_ST_CTX0_RTSTR_SET(xhci_route_string(dev->hport));
|
|
#endif
|
|
|
|
xhci_in_slot(priv, dev->input)->ctx[0] = htole32(regval);
|
|
|
|
/* Configure Root Hub Port Number (starts from 1) */
|
|
|
|
regval = XHCI_ST_CTX1_RHPN_SET(RHPNDX(dev->rhport) + 1);
|
|
|
|
/* TODO: configure number of ports */
|
|
|
|
regval |= XHCI_ST_CTX1_PORTS_SET(0);
|
|
xhci_in_slot(priv, dev->input)->ctx[1] = htole32(regval);
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
xhci_in_slot(priv, dev->input)->ctx[2] = htole32(xhci_slot_tt(priv, dev));
|
|
#endif
|
|
|
|
/* Step 4. the Transfer Ring for the Default Control Endpoint is already
|
|
* allocated.
|
|
*/
|
|
|
|
DEBUGASSERT(dev->epinfo[0] != NULL);
|
|
drdp = up_addrenv_va_to_pa(dev->epinfo[0]->td.ring);
|
|
|
|
/* Step 5. Initialize the Input default control Endpoint 0 Context */
|
|
|
|
DEBUGASSERT(dev->hport != NULL);
|
|
if (dev->hport->speed == USB_SPEED_HIGH)
|
|
{
|
|
/* For high-speed, we must use 64 bytes */
|
|
|
|
maxpkt = 64;
|
|
}
|
|
else
|
|
{
|
|
/* Eight will work for both low- and full-speed */
|
|
|
|
maxpkt = 8;
|
|
}
|
|
|
|
DEBUGASSERT(drdp != 0);
|
|
xhci_ep_configure(priv,
|
|
xhci_in_ep(priv, dev->input, 0),
|
|
XHCI_EPTYPE_CTRL, maxpkt,
|
|
0, drdp,
|
|
0, 0);
|
|
|
|
/* Step 6. The output Device Context data structure already allocated.
|
|
* Initialize all fields to 0.
|
|
*/
|
|
|
|
memset(dev->ctx, 0, XHCI_DEVCTX_SIZE(priv));
|
|
|
|
/* Flush both contexts.
|
|
*
|
|
* The output context is the controller's to write, so clearing it must
|
|
* reach memory: the dirty zeros left in cache are written back later, on
|
|
* top of what the controller has put there. The slot state lives in
|
|
* that context, and losing it fails the next command against the slot.
|
|
*/
|
|
|
|
up_flush_dcache((uintptr_t)dev->ctx,
|
|
(uintptr_t)dev->ctx + XHCI_DEVCTX_SIZE(priv));
|
|
|
|
up_flush_dcache((uintptr_t)dev->input,
|
|
(uintptr_t)dev->input +
|
|
XHCI_INCTX_SIZE(priv));
|
|
|
|
/* Step 7. Load the appropriate (Device Slot ID) entry in the Device
|
|
* Context Base Address Array with a pointer to the Output Device
|
|
* Context data structure
|
|
*/
|
|
|
|
xhci_dcbaa_set(priv, dev->slot, up_addrenv_va_to_pa(dev->ctx));
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_device_init
|
|
*
|
|
* Description:
|
|
* Initialize Device.
|
|
*
|
|
* Assumption:
|
|
* 1. All device resources already allocated.
|
|
* 2. Port is in Enabled state.
|
|
*
|
|
* Reference:
|
|
* - 4.3: USB Device Initialization
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_device_init(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_rhport_s *rhport,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
FAR struct xhci_epinfo_s *ep0info)
|
|
{
|
|
FAR struct xhci_dev_s *dev;
|
|
uint8_t slot;
|
|
int ret;
|
|
|
|
/* We enter this function after steps 1-3 from "USB Device Initialization"
|
|
* are done.
|
|
*/
|
|
|
|
/* Step 4: Get Device Slot.
|
|
*
|
|
* Reference:
|
|
* - 4.3.2: Device Slot Assignment
|
|
*/
|
|
|
|
ret = xhci_cmd_sloten(priv, &slot);
|
|
if (ret < 0 || slot > priv->no_slots)
|
|
{
|
|
/* A slot the controller cannot address is no more usable than no
|
|
* slot at all, and the command itself succeeds in that case, so the
|
|
* caller needs an error either way.
|
|
*/
|
|
|
|
if (ret >= 0)
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
usbhost_vtrace1(XHCI_TRACE1_SLOTEN_FAILED, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Slot ID is an index to the identify Device data */
|
|
|
|
dev = &priv->devs[slot - 1];
|
|
|
|
/* A root hub port names the device on it, which a hub port must not
|
|
* disturb: the device its root port names is the hub itself.
|
|
*/
|
|
|
|
if (hport == &rhport->hport.hport)
|
|
{
|
|
rhport->dev = dev;
|
|
rhport->slot = slot;
|
|
}
|
|
|
|
/* Slot has been allocated to software and is now in Enabled state */
|
|
|
|
dev->state = XHCI_SLOT_ENABLED;
|
|
|
|
/* Step 5: Initialize the data structures associated with the slot.
|
|
* All data structured are already allocated.
|
|
*/
|
|
|
|
ret = xhci_ring_init(&ep0info->td, XHCI_TD_MAX);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ep0 ring init failed\n");
|
|
goto errout_with_slot;
|
|
}
|
|
|
|
ep0info->slot = slot;
|
|
dev->rhport = rhport;
|
|
dev->hport = hport;
|
|
dev->slot = slot;
|
|
dev->epinfo[0] = ep0info;
|
|
|
|
ret = xhci_slot_init(priv, dev);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_slot;
|
|
}
|
|
|
|
/* Step 6: Assign and address to the device and enable its Default
|
|
* Control Endpoint.
|
|
*
|
|
* NOTE: we don't send SET_ADDRESS request here.
|
|
* This is done in xhci_ctrlin() and controlled by NuttX USB Host
|
|
* stack.
|
|
*/
|
|
|
|
ret = xhci_address_set(priv, dev, false);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to set address %d\n", ret);
|
|
goto errout_with_slot;
|
|
}
|
|
|
|
/* Steps 7-12 don't belong here! */
|
|
|
|
return OK;
|
|
|
|
errout_with_slot:
|
|
|
|
/* Nothing else gives the slot back, and the controller has a fixed
|
|
* number of them.
|
|
*/
|
|
|
|
xhci_device_deinit(priv, dev);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_device_deinit
|
|
*
|
|
* Description:
|
|
* Free Device.
|
|
*
|
|
* Reference:
|
|
* - 4.3: USB Device Initialization
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev)
|
|
{
|
|
uint8_t slot = dev->slot;
|
|
int ret;
|
|
|
|
/* Disable Slot */
|
|
|
|
ret = xhci_cmd_slotdis(priv, slot);
|
|
if (ret < 0)
|
|
{
|
|
uerr("xhci_cmd_slotdis failed %d\n", ret);
|
|
}
|
|
|
|
/* Clear DCBAA entry for this slot */
|
|
|
|
xhci_dcbaa_set(priv, slot, 0);
|
|
|
|
/* Clean up device data, but don't touch allocated memory! */
|
|
|
|
dev->state = XHCI_SLOT_DISABLED;
|
|
|
|
memset(dev->ctx, 0, XHCI_DEVCTX_SIZE(priv));
|
|
memset(dev->input, 0, XHCI_INCTX_SIZE(priv));
|
|
|
|
/* And push both, so nothing is left to be written back later */
|
|
|
|
up_flush_dcache((uintptr_t)dev->ctx,
|
|
(uintptr_t)dev->ctx + XHCI_DEVCTX_SIZE(priv));
|
|
up_flush_dcache((uintptr_t)dev->input,
|
|
(uintptr_t)dev->input + XHCI_INCTX_SIZE(priv));
|
|
|
|
/* Remove reference to a device slot. Only the device sitting directly
|
|
* on the root port is the one that port points at; a device behind a hub
|
|
* must leave that pointing at the hub.
|
|
*/
|
|
|
|
if (dev->rhport != NULL && dev->rhport->dev == dev)
|
|
{
|
|
dev->rhport->dev = NULL;
|
|
}
|
|
|
|
dev->hport = NULL;
|
|
dev->rhport = NULL;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_epno_get
|
|
*
|
|
* Description:
|
|
* Get EP index for a given endpoint.
|
|
*
|
|
* Returns Device Context Index (DCI).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
DEBUGASSERT(epinfo);
|
|
|
|
if (epinfo->epno == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (epinfo->dirin)
|
|
{
|
|
return epinfo->epno * 2 + 1;
|
|
}
|
|
else
|
|
{
|
|
return epinfo->epno * 2;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_context_ctrl
|
|
*
|
|
* Description:
|
|
* Configure Input Control Context, which defines which Device Context
|
|
* data structures are affected by a command.
|
|
*
|
|
* Assumption:
|
|
* Input Context must be flushed by caller.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev,
|
|
uint32_t drop, uint32_t add)
|
|
{
|
|
int i;
|
|
|
|
dev->input->input.ctx[0] = htole32(drop);
|
|
dev->input->input.ctx[1] = htole32(add);
|
|
|
|
/* Update Context Entries in Slot */
|
|
|
|
for (i = 31; i != 1; i--)
|
|
{
|
|
if (add & (1 << i))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
xhci_in_slot(priv, dev->input)->ctx[0] &= ~XHCI_ST_CTX0_CTXENT_MASK;
|
|
xhci_in_slot(priv, dev->input)->ctx[0] |= XHCI_ST_CTX0_CTXENT_SET(i);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_command
|
|
*
|
|
* Description:
|
|
* Issue a xHCI command.
|
|
*
|
|
* NOTE:
|
|
* trb data in host specific byte order. This function converts it
|
|
* to a correct order
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_command(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_trb_s *trb, uint16_t timeout_ms)
|
|
{
|
|
uint32_t cmdtype;
|
|
int ret;
|
|
|
|
/* Lock bus */
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Remember what this was before the result overwrites it */
|
|
|
|
cmdtype = XHCI_TRB_D2_TYPE_GET(trb->d2);
|
|
|
|
/* Add command to ring */
|
|
|
|
xhci_add_trb(priv, &priv->cmd, trb, 1);
|
|
|
|
/* Ringing the Host Controller Doorbell */
|
|
|
|
xhci_door_putreg(priv, XHCI_DOORBEL(0), 0);
|
|
|
|
/* Wait for Command Completion Event */
|
|
|
|
ret = nxsem_tickwait_uninterruptible(&priv->cmdsem,
|
|
MSEC2TICK(timeout_ms));
|
|
if (ret < 0)
|
|
{
|
|
/* Check for missed interrupts */
|
|
|
|
xhci_events_poll(priv);
|
|
}
|
|
|
|
/* Return command results */
|
|
|
|
trb->d0 = priv->cmdres.d0;
|
|
trb->d1 = priv->cmdres.d1;
|
|
trb->d2 = priv->cmdres.d2;
|
|
|
|
if (XHCI_TRB_D1_CC_GET(trb->d1) == XHCI_TRB_CC_SUCCESS)
|
|
{
|
|
/* The completion event decides the result, whether it arrived by
|
|
* interrupt or was found by the poll above.
|
|
*/
|
|
|
|
ret = OK;
|
|
}
|
|
else
|
|
{
|
|
uerr("command type %d failed, CC = %d\n", cmdtype,
|
|
XHCI_TRB_D1_CC_GET(trb->d1));
|
|
ret = -EIO;
|
|
}
|
|
|
|
/* Clean response */
|
|
|
|
priv->cmdres.d0 = 0;
|
|
priv->cmdres.d1 = 0;
|
|
priv->cmdres.d2 = 0;
|
|
|
|
/* Unlock bus */
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cmd_sloten
|
|
*
|
|
* Description:
|
|
* Enable Slot Command.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv,
|
|
FAR uint8_t *slot)
|
|
{
|
|
struct xhci_trb_s trb;
|
|
int ret;
|
|
|
|
/* Host specific byte order. Conversion done by xhci_command() */
|
|
|
|
trb.d0 = 0;
|
|
trb.d1 = 0;
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EN_SLOT);
|
|
|
|
ret = xhci_command(priv, &trb, 1000);
|
|
if (ret < 0)
|
|
{
|
|
*slot = 0;
|
|
return ret;
|
|
}
|
|
|
|
/* Return available slot */
|
|
|
|
*slot = XHCI_TRB_D2_SLOTID_GET(le32toh(trb.d2));
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cmd_slotdis
|
|
*
|
|
* Description:
|
|
* Disable Slot Command.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot)
|
|
{
|
|
struct xhci_trb_s trb;
|
|
|
|
/* Host specific byte order. Conversion done by xhci_command() */
|
|
|
|
trb.d0 = 0;
|
|
trb.d1 = 0;
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DIS_SLOT) |
|
|
XHCI_TRB_D2_SLOTID_SET(slot);
|
|
|
|
return xhci_command(priv, &trb, 100);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cmd_setaddr
|
|
*
|
|
* Description:
|
|
* Address Device Command.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint64_t ctx, bool bsr)
|
|
{
|
|
struct xhci_trb_s trb;
|
|
|
|
/* Host specific byte order. Conversion done by xhci_command() */
|
|
|
|
trb.d0 = htole64(ctx);
|
|
trb.d1 = 0;
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ADDR_DEV) |
|
|
XHCI_TRB_D2_SLOTID_SET(slot);
|
|
|
|
if (bsr)
|
|
{
|
|
trb.d2 |= XHCI_TRB_D2_BSR;
|
|
}
|
|
|
|
return xhci_command(priv, &trb, 100);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cmd_cfgep
|
|
*
|
|
* Description:
|
|
* Configure EP Command.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint64_t ctx, bool deconfig)
|
|
{
|
|
struct xhci_trb_s trb;
|
|
|
|
/* Host specific byte order. Conversion done by xhci_command() */
|
|
|
|
trb.d0 = htole64(ctx);
|
|
trb.d1 = 0;
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_CFG_EP) |
|
|
XHCI_TRB_D2_SLOTID_SET(slot);
|
|
|
|
if (deconfig)
|
|
{
|
|
trb.d2 |= XHCI_TRB_D2_DC;
|
|
}
|
|
|
|
return xhci_command(priv, &trb, 100);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cmd_stopep
|
|
*
|
|
* Description:
|
|
* Stop endpoint
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint8_t ep, bool suspend)
|
|
{
|
|
struct xhci_trb_s trb;
|
|
|
|
/* Host specific byte order. Conversion done by xhci_command() */
|
|
|
|
trb.d0 = 0;
|
|
trb.d1 = 0;
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) |
|
|
XHCI_TRB_D2_SLOTID_SET(slot) | XHCI_TRB_D2_EP_SET(ep);
|
|
|
|
if (suspend)
|
|
{
|
|
trb.d2 |= XHCI_TRB_D2_SP;
|
|
}
|
|
|
|
return xhci_command(priv, &trb, 100);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cmd_evalctx
|
|
*
|
|
* Description:
|
|
* Evaluate Context Command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot,
|
|
uint64_t ctx)
|
|
{
|
|
struct xhci_trb_s trb;
|
|
|
|
/* Host specific byte order. Conversion done by xhci_command() */
|
|
|
|
trb.d0 = htole64(ctx);
|
|
trb.d1 = 0;
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EVAL_CTX) |
|
|
XHCI_TRB_D2_SLOTID_SET(slot);
|
|
|
|
return xhci_command(priv, &trb, 100);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ep_doorbell
|
|
*
|
|
* Description:
|
|
* Ring doorbell associated with a given endpoint.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
uint8_t target = xhci_epno_get(epinfo);
|
|
uint32_t regval = 0;
|
|
|
|
/* Doorbel tareget is EP number */
|
|
|
|
regval |= XHCI_DOORBEL_TARGET(target);
|
|
|
|
/* Streams not supported yet (USB3.0 specific) */
|
|
|
|
regval |= XHCI_DOORBEL_TASK(0);
|
|
|
|
/* Ring doorbell */
|
|
|
|
xhci_door_putreg(priv, XHCI_DOORBEL(epinfo->slot), regval);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ioc_setup
|
|
*
|
|
* Description:
|
|
* Set the request for the IOC event well BEFORE enabling the transfer (as
|
|
* soon as we are absolutely committed to the transfer). We do
|
|
* this to minimize race conditions. This logic would have to be expanded
|
|
* if we want to have more than one packet in flight at a time!
|
|
*
|
|
* Assumption:
|
|
* The caller holds the XHCI lock
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
size_t buflen)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport);
|
|
irqstate_t flags;
|
|
int ret = -ENODEV;
|
|
|
|
DEBUGASSERT(rhport && epinfo && !epinfo->iocwait);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
DEBUGASSERT(epinfo->callback == NULL);
|
|
#endif
|
|
|
|
/* Is the device still connected? */
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
if (rhport->connected)
|
|
{
|
|
/* Then set iocwait to indicate that we expect to be informed when
|
|
* either (1) the device is disconnected, or (2) the transfer
|
|
* completed.
|
|
*/
|
|
|
|
epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */
|
|
epinfo->status = 0; /* No status yet */
|
|
epinfo->xfrd = 0; /* Nothing transferred yet */
|
|
epinfo->buflen = buflen; /* Buffer length */
|
|
epinfo->result = -EBUSY; /* Transfer in progress */
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
epinfo->callback = NULL; /* No asynchronous callback */
|
|
epinfo->arg = NULL;
|
|
#endif
|
|
ret = OK; /* We are good to go */
|
|
}
|
|
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ioc_wait
|
|
*
|
|
* Description:
|
|
* Wait for the IOC event.
|
|
*
|
|
* Assumption:
|
|
* The caller does *NOT* hold the xHCI lock. That would cause a deadlock
|
|
* when the bottom-half, worker thread needs to take the semaphore.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
int ret = OK;
|
|
|
|
/* Wait for the IOC event. Loop to handle any false alarm semaphore
|
|
* counts. Return an error if the task is canceled.
|
|
*/
|
|
|
|
while (epinfo->iocwait)
|
|
{
|
|
ret = nxsem_wait_uninterruptible(&epinfo->iocsem);
|
|
if (ret < 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret < 0 ? ret : epinfo->result;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_control_setup
|
|
*
|
|
* Description:
|
|
* Process a IN or OUT request control ep.
|
|
* This function will enqueue the request and wait for it to
|
|
* complete. Bulk data transfers differ in that req == NULL and there are
|
|
* not SETUP or STATUS phases.
|
|
*
|
|
* This is a blocking function; it will not return until the control
|
|
* transfer has completed.
|
|
*
|
|
* Assumption:
|
|
* The caller holds the xHCI lock.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is return on
|
|
* any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_control_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport);
|
|
struct xhci_trb_s trb[3];
|
|
uint8_t trt;
|
|
int i = 0;
|
|
|
|
/* Prepare Setup Stage TRB */
|
|
|
|
trb[i].d0 = *((FAR uint64_t *)req);
|
|
trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(8);
|
|
trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) |
|
|
XHCI_TRB_D2_IDT;
|
|
|
|
/* Reference:
|
|
* - Table 4-7: USB SETUP Data to Data Stage TRB and Status Stage
|
|
* TRB mapping
|
|
*/
|
|
|
|
if (req->type & USB_REQ_DIR_IN)
|
|
{
|
|
trt = XHCI_TRB_D2_TRT_INDATA;
|
|
}
|
|
else if (req->type & USB_REQ_DIR_OUT)
|
|
{
|
|
trt = XHCI_TRB_D2_TRT_OUTDATA;
|
|
}
|
|
else
|
|
{
|
|
trt = XHCI_TRB_D2_TRT_NODATA;
|
|
}
|
|
|
|
trb[i].d2 |= XHCI_TRB_D2_TRT_SET(trt);
|
|
|
|
/* Next TRB */
|
|
|
|
i++;
|
|
|
|
/* Prepare Data Stage TRB */
|
|
|
|
if (buffer)
|
|
{
|
|
buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen,
|
|
(req->type & USB_REQ_DIR_IN) != 0);
|
|
if (buffer == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
trb[i].d0 = up_addrenv_va_to_pa(buffer);
|
|
trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(buflen);
|
|
trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE);
|
|
|
|
if (req->type & USB_REQ_DIR_IN)
|
|
{
|
|
trb[i].d2 |= XHCI_TRB_D2_DIR;
|
|
}
|
|
|
|
/* Next TRB */
|
|
|
|
i++;
|
|
}
|
|
|
|
/* Prepare Status Stage TRB */
|
|
|
|
trb[i].d0 = 0;
|
|
trb[i].d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(0);
|
|
trb[i].d2 = XHCI_TRB_D2_IOC |
|
|
XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STAT_STAGE);
|
|
|
|
if (!(req->type & USB_REQ_DIR_IN))
|
|
{
|
|
trb[i].d2 |= XHCI_TRB_D2_DIR;
|
|
}
|
|
|
|
/* Next TRB */
|
|
|
|
i++;
|
|
|
|
/* Add TRBs to ring */
|
|
|
|
xhci_add_trb(priv, &epinfo->td, trb, i);
|
|
|
|
/* Trigger transfer */
|
|
|
|
xhci_ep_doorbell(priv, epinfo);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_normal_setup
|
|
*
|
|
* Description:
|
|
* Process a IN or OUT request on bulk or interrupt endpoint.
|
|
* This function will enqueue the request and wait for it to complete.
|
|
*
|
|
* This is a blocking function; it will not return until the control
|
|
* transfer has completed.
|
|
*
|
|
* Assumption:
|
|
* The caller holds the xHCI lock.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is returned on
|
|
* any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport);
|
|
struct xhci_trb_s trb;
|
|
|
|
size_t left;
|
|
size_t chunk;
|
|
uintptr_t pa;
|
|
int n = 0;
|
|
|
|
/* Make the buffer safe for the controller to reach */
|
|
|
|
buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen,
|
|
epinfo->dirin != 0);
|
|
if (buffer == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* One TRB describes one run of memory, and that run may not cross a 64K
|
|
* boundary. A longer transfer, or one starting near the wrong side of a
|
|
* boundary, becomes several TRBs chained into a single transfer, with
|
|
* the interrupt asked for only on the last so that one completion
|
|
* arrives for the whole of it.
|
|
*/
|
|
|
|
pa = up_addrenv_va_to_pa(buffer);
|
|
left = buflen;
|
|
|
|
while (left > 0)
|
|
{
|
|
chunk = XHCI_TD_LEN_MAX - (pa & (XHCI_TD_LEN_MAX - 1));
|
|
if (chunk > left)
|
|
{
|
|
chunk = left;
|
|
}
|
|
|
|
if (++n >= XHCI_TD_MAX)
|
|
{
|
|
uerr("transfer of %zu from pa %" PRIxPTR " needs more than %d "
|
|
"TRBs\n", buflen, pa, XHCI_TD_MAX);
|
|
return -EINVAL;
|
|
}
|
|
|
|
trb.d0 = pa;
|
|
trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(chunk);
|
|
trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_NORMAL);
|
|
|
|
left -= chunk;
|
|
pa += chunk;
|
|
|
|
/* Chain everything but the last, and interrupt only on the last */
|
|
|
|
if (left > 0)
|
|
{
|
|
trb.d2 |= XHCI_TRB_D2_CH;
|
|
}
|
|
else
|
|
{
|
|
trb.d2 |= XHCI_TRB_D2_IOC;
|
|
}
|
|
|
|
xhci_add_trb(priv, &epinfo->td, &trb, 1);
|
|
}
|
|
|
|
/* Trigger transfer */
|
|
|
|
xhci_ep_doorbell(priv, epinfo);
|
|
|
|
return OK;
|
|
}
|
|
|
|
#ifndef CONFIG_USBHOST_ISOC_DISABLE
|
|
/****************************************************************************
|
|
* Name: xhci_isoc_setup
|
|
*
|
|
* Description:
|
|
* Process a request on isoch endpoint.
|
|
*
|
|
* Assumption:
|
|
* The caller holds the xHCI lock.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is returned on
|
|
* any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport);
|
|
struct xhci_trb_s trb;
|
|
|
|
/* Make the buffer safe for the controller to reach */
|
|
|
|
buffer = xhci_dma_prepare(priv, epinfo, buffer, buflen,
|
|
epinfo->dirin != 0);
|
|
if (buffer == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Prepare TRB */
|
|
|
|
trb.d0 = up_addrenv_va_to_pa(buffer);
|
|
trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen);
|
|
trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ISOCH);
|
|
|
|
/* Start Isoch ASAP */
|
|
|
|
trb.d2 |= XHCI_TRB_D2_SIA;
|
|
|
|
/* Add TRBs to ring */
|
|
|
|
xhci_add_trb(priv, &epinfo->td, &trb, 1);
|
|
|
|
/* Trigger transfer */
|
|
|
|
xhci_ep_doorbell(priv, epinfo);
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_transfer_wait
|
|
*
|
|
* Description:
|
|
* Wait for an IN or OUT transfer to complete.
|
|
*
|
|
* Assumption:
|
|
* The caller holds the xHCI lock. The caller must be aware that the xHCI
|
|
* lock will released while waiting for the transfer to complete, but will
|
|
* be re-acquired when before returning. The state of xHCI resources could
|
|
* be very different upon return.
|
|
*
|
|
* Returned Value:
|
|
* On success, this function returns the number of bytes actually
|
|
* transferred. For control transfers, this size includes the size of the
|
|
* control request plus the size of the data (which could be short); for
|
|
* bulk transfers, this will be the number of data bytes transfers (which
|
|
* could be short).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
int ret;
|
|
|
|
/* Wait for the IOC completion event */
|
|
|
|
ret = xhci_ioc_wait(epinfo);
|
|
|
|
/* Did xhci_ioc_wait() or nxmutex_lock report an error? */
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(XHCI_TRACE1_TRANSFER_FAILED, -ret);
|
|
epinfo->iocwait = false;
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Transfer completed successfully. Return the number of bytes
|
|
* transferred.
|
|
*/
|
|
|
|
return epinfo->xfrd;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/****************************************************************************
|
|
* Name: xhci_ioc_async_setup
|
|
*
|
|
* Description:
|
|
* Setup to receive an asynchronous notification when a transfer completes.
|
|
*
|
|
* Input Parameters:
|
|
* epinfo - The IN or OUT endpoint descriptor for the device endpoint on
|
|
* which the transfer will be performed.
|
|
* buflen - The length of the transfer, from which the completion works
|
|
* out how much was transferred.
|
|
* callback - The function to be called when the transfer completes
|
|
* arg - An arbitrary argument that will be provided with the callback.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* - Called from the interrupt level
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
size_t buflen,
|
|
usbhost_asynch_t callback,
|
|
FAR void *arg)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport);
|
|
irqstate_t flags;
|
|
int ret = -ENODEV;
|
|
|
|
DEBUGASSERT(rhport && epinfo && !epinfo->iocwait &&
|
|
epinfo->callback == NULL);
|
|
|
|
/* Is the device still connected? */
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
if (rhport->connected)
|
|
{
|
|
/* Then save callback information to be used when either (1) the
|
|
* device is disconnected, or (2) the transfer completes.
|
|
*/
|
|
|
|
epinfo->iocwait = false; /* No synchronous wakeup */
|
|
epinfo->status = 0; /* No status yet */
|
|
epinfo->xfrd = 0; /* Nothing transferred yet */
|
|
epinfo->buflen = buflen; /* Buffer length */
|
|
epinfo->result = -EBUSY; /* Transfer in progress */
|
|
epinfo->callback = callback; /* Asynchronous callback */
|
|
epinfo->arg = arg; /* Argument that accompanies the callback */
|
|
ret = OK; /* We are good to go */
|
|
}
|
|
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_asynch_completion
|
|
*
|
|
* Description:
|
|
* This function is called from the interrupt work queue when an
|
|
* asynchronous transfer completes. It performs the pending callback.
|
|
*
|
|
* Input Parameters:
|
|
* priv - xHCI private state
|
|
* epinfo - The IN or OUT endpoint descriptor for the device endpoint on
|
|
* which the transfer was performed.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* - Called from the work queue, without the spinlock held
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_asynch_completion(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
usbhost_asynch_t callback;
|
|
ssize_t nbytes;
|
|
FAR void *arg;
|
|
irqstate_t flags;
|
|
int result;
|
|
|
|
DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false);
|
|
|
|
/* Extract and reset the callback info, atomically against a concurrent
|
|
* cancellation.
|
|
*/
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
|
|
callback = epinfo->callback;
|
|
arg = epinfo->arg;
|
|
result = epinfo->result;
|
|
nbytes = epinfo->xfrd;
|
|
|
|
epinfo->callback = NULL;
|
|
epinfo->arg = NULL;
|
|
epinfo->result = OK;
|
|
epinfo->iocwait = false;
|
|
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
/* A cancellation that got in first has already done the callback */
|
|
|
|
if (callback == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Bring back what the controller wrote before anyone reads it. The
|
|
* addresses are usable here: a transfer given to DRVR_ASYNCH must use
|
|
* memory from DRVR_ALLOC, and that is kernel memory, which this work
|
|
* queue thread can reach.
|
|
*/
|
|
|
|
xhci_dma_finish(epinfo);
|
|
|
|
/* Then perform the callback. Provide the number of bytes successfully
|
|
* transferred or the negated errno value in the event of a failure.
|
|
*/
|
|
|
|
if (result < 0)
|
|
{
|
|
nbytes = (ssize_t)result;
|
|
}
|
|
|
|
callback(arg, nbytes);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_speed_str
|
|
*
|
|
* Description:
|
|
* What a port negotiated, in words. PORTSC reports a speed ID, not a
|
|
* speed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR const char *xhci_speed_str(uint32_t portsc)
|
|
{
|
|
switch (XHCI_PORTSC_PS(portsc))
|
|
{
|
|
case XHCI_PORTSC_PS_FULL:
|
|
return "full speed, 12Mbps";
|
|
case XHCI_PORTSC_PS_LOW:
|
|
return "low speed, 1.5Mbps";
|
|
case XHCI_PORTSC_PS_HIGH:
|
|
return "high speed, 480Mbps";
|
|
case XHCI_PORTSC_PS_SUPPER11:
|
|
return "SuperSpeed, 5Gbps";
|
|
case XHCI_PORTSC_PS_SUPPER21:
|
|
case XHCI_PORTSC_PS_SUPPER12:
|
|
case XHCI_PORTSC_PS_SUPPER22:
|
|
return "SuperSpeed+, 10Gbps";
|
|
default:
|
|
return "an unknown speed";
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_portsc_work
|
|
*
|
|
* Description:
|
|
* Handle Port Change work.
|
|
*
|
|
* Assumptions:
|
|
* - Never called from an interrupt handler.
|
|
* - Never called directly from xhci_events_poll() otherwise it gets stuck
|
|
* on CLASS_DISCONNECTED()
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_portsc_work(FAR void *arg)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = arg;
|
|
FAR struct usbhost_hubport_s *hport;
|
|
FAR struct xhci_rhport_s *rhport;
|
|
uint32_t portsc;
|
|
int rhpndx;
|
|
|
|
/* REVISIT: should this logic be protected? We can't use spinlock
|
|
* here because we get stack in CLASS_DISCONNECTED().
|
|
*/
|
|
|
|
/* Handle root hub status change on each root port */
|
|
|
|
for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++)
|
|
{
|
|
rhport = &priv->rhport[rhpndx];
|
|
portsc = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx));
|
|
|
|
usbhost_vtrace2(XHCI_VTRACE2_PORTSC, rhpndx + 1, portsc);
|
|
|
|
/* Handle port connection status change (CSC) events */
|
|
|
|
if ((portsc & XHCI_PORTSC_CSC) != 0)
|
|
{
|
|
usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CSC, portsc);
|
|
|
|
/* Check current connect status */
|
|
|
|
if ((portsc & XHCI_PORTSC_CCS) != 0)
|
|
{
|
|
/* Connected ... Did we just become connected? */
|
|
|
|
if (!rhport->connected)
|
|
{
|
|
/* Yes.. connected. */
|
|
|
|
rhport->connected = true;
|
|
|
|
/* A new device gets the full allowance of attempts,
|
|
* whatever the last one that sat here managed.
|
|
*/
|
|
|
|
rhport->enumfail = 0;
|
|
|
|
usbhost_vtrace2(XHCI_VTRACE2_PORTSC_CONNECTED,
|
|
rhpndx + 1, priv->pscwait);
|
|
|
|
/* Notify any waiters */
|
|
|
|
if (priv->pscwait)
|
|
{
|
|
nxsem_post(&priv->pscsem);
|
|
priv->pscwait = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CONNALREADY, portsc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Disconnected... Did we just become disconnected? */
|
|
|
|
if (rhport->connected)
|
|
{
|
|
/* Yes.. disconnect the device */
|
|
|
|
usbhost_vtrace2(XHCI_VTRACE2_PORTSC_DISCONND,
|
|
rhpndx + 1, priv->pscwait);
|
|
|
|
syslog(LOG_INFO, "%s: port %d: device removed\n",
|
|
priv->name, rhpndx + 1);
|
|
|
|
rhport->connected = false;
|
|
|
|
/* Are we bound to a class instance? */
|
|
|
|
hport = &rhport->hport.hport;
|
|
if (hport->devclass)
|
|
{
|
|
/* Yes.. Disconnect the class. */
|
|
|
|
CLASS_DISCONNECTED(hport->devclass);
|
|
hport->devclass = NULL;
|
|
}
|
|
|
|
/* Notify any waiters for the Root Hub Status change
|
|
* event.
|
|
*/
|
|
|
|
if (priv->pscwait)
|
|
{
|
|
nxsem_post(&priv->pscsem);
|
|
priv->pscwait = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
usbhost_vtrace1(XHCI_VTRACE1_PORTSC_DISCALREADY, portsc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear pending bit but don't touch PED ! */
|
|
|
|
portsc &= ~XHCI_PORTSC_PED;
|
|
xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), portsc);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_in_slot / xhci_in_ep / xhci_out_slot
|
|
*
|
|
* Description:
|
|
* Reach into a device context.
|
|
*
|
|
* A context is an array of equally sized entries, and how big they are is
|
|
* a property of the controller rather than of the specification: it
|
|
* reports either thirty-two or sixty-four bytes, and the wider form is
|
|
* the same fields with reserved space after them. So these are the same
|
|
* structures at a different stride, and only the arithmetic to find the
|
|
* n'th one has to know which.
|
|
*
|
|
* Output context: slot, then endpoints 1 upward.
|
|
* Input context: input control, then slot, then endpoints.
|
|
*
|
|
* The first entry of either is at offset zero, so only the ones after it
|
|
* need this.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline FAR struct xhci_slot_ctx_s *
|
|
xhci_in_slot(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_input_dev_ctx_s *input)
|
|
{
|
|
return (FAR struct xhci_slot_ctx_s *)((uintptr_t)input + priv->ctxsize);
|
|
}
|
|
|
|
static inline FAR struct xhci_ep_ctx_s *
|
|
xhci_in_ep(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_input_dev_ctx_s *input, int epidx)
|
|
{
|
|
return (FAR struct xhci_ep_ctx_s *)((uintptr_t)input +
|
|
(epidx + 2) * priv->ctxsize);
|
|
}
|
|
|
|
static inline FAR struct xhci_slot_ctx_s *
|
|
xhci_out_slot(FAR struct xhci_dev_ctx_s *ctx)
|
|
{
|
|
return (FAR struct xhci_slot_ctx_s *)ctx;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dev_from_ep
|
|
*
|
|
* Description:
|
|
* The device an endpoint belongs to.
|
|
*
|
|
* An endpoint records the slot it was opened on, and the slot indexes the
|
|
* device table, so this holds wherever the device sits. The root hub port
|
|
* does not: a class driver reaches the controller through the port it
|
|
* descends from, and a hub puts several devices behind one such port.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline FAR struct xhci_dev_s *
|
|
xhci_dev_from_ep(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
DEBUGASSERT(epinfo->slot > 0 && epinfo->slot <= priv->no_slots);
|
|
return &priv->devs[epinfo->slot - 1];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dev_from_hport
|
|
*
|
|
* Description:
|
|
* The device attached to a hub port, or NULL if there is none.
|
|
*
|
|
* Used where there is no endpoint to ask yet, which is the case when the
|
|
* first one is being allocated.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/****************************************************************************
|
|
* Name: xhci_route_string
|
|
*
|
|
* Description:
|
|
* The route string for a device, which is how the controller finds it.
|
|
*
|
|
* Each hub between the root and the device contributes one nibble holding
|
|
* the number of the port the next thing down is plugged into, with the
|
|
* tier nearest the root in the lowest nibble. A device on a root hub port
|
|
* routes to zero, which is what the field means for "no hubs in between".
|
|
*
|
|
* Reference:
|
|
* - 8.9: Route String Field
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t xhci_route_string(FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
uint32_t route = 0;
|
|
int tier = 0;
|
|
|
|
/* Walking up reaches the deepest tier first, and shifting what is already
|
|
* there left by a nibble each time leaves the tier nearest the root in the
|
|
* lowest one. USB allows five tiers of hubs and the field holds exactly
|
|
* that many, so a chain longer than the bus permits stops here rather than
|
|
* writing over the speed field above it.
|
|
*/
|
|
|
|
while (hport->parent != NULL && tier < 5)
|
|
{
|
|
uint8_t portno = hport->port + 1;
|
|
|
|
/* The nibble cannot express a port above fifteen. A hub that large
|
|
* is legal, so clamp rather than let the number wrap into the tier
|
|
* below it.
|
|
*/
|
|
|
|
if (portno > 15)
|
|
{
|
|
portno = 15;
|
|
}
|
|
|
|
route = (route << 4) | portno;
|
|
hport = hport->parent;
|
|
tier++;
|
|
}
|
|
|
|
return route;
|
|
}
|
|
#endif
|
|
|
|
static FAR struct xhci_dev_s *
|
|
xhci_dev_from_hport(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < priv->no_slots; i++)
|
|
{
|
|
if (priv->devs[i].state != XHCI_SLOT_DISABLED &&
|
|
priv->devs[i].hport == hport)
|
|
{
|
|
return &priv->devs[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/****************************************************************************
|
|
* Name: xhci_slot_tt
|
|
*
|
|
* Description:
|
|
* Slot context dword 2, naming the transaction translator that carries a
|
|
* low or full speed device behind a high speed hub. Zero when no
|
|
* translator is involved, which is what the field means.
|
|
*
|
|
* Reference:
|
|
* - 6.2.2: Slot Context
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t xhci_slot_tt(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_dev_s *dev)
|
|
{
|
|
FAR struct usbhost_hubport_s *hport = dev->hport;
|
|
FAR struct xhci_dev_s *tthub;
|
|
|
|
/* Only a low or full speed device is translated for. */
|
|
|
|
if (hport->speed == USB_SPEED_HIGH)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* The translator lives in the nearest high speed ancestor, which need not
|
|
* be the hub the device is plugged into: a full speed hub below a high
|
|
* speed one is itself carried by the translator above it.
|
|
*/
|
|
|
|
while (hport->parent != NULL && hport->parent->speed != USB_SPEED_HIGH)
|
|
{
|
|
hport = hport->parent;
|
|
}
|
|
|
|
if (hport->parent == NULL)
|
|
{
|
|
/* Nothing high speed above, so the device is on a root hub port or
|
|
* the whole chain runs at its speed. Either way there is no
|
|
* translator to name.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
tthub = xhci_dev_from_hport(priv, hport->parent);
|
|
if (tthub == NULL)
|
|
{
|
|
uerr("no device for the hub carrying port %d\n", hport->port);
|
|
return 0;
|
|
}
|
|
|
|
/* Think time is the hub's, reported by the hub class driver from the hub
|
|
* descriptor. Both fields count in the same units, so the value carries
|
|
* across unchanged.
|
|
*/
|
|
|
|
return XHCI_ST_CTX2_TTHSID_SET(tthub->slot) |
|
|
XHCI_ST_CTX2_TTPORT_SET(hport->port + 1) |
|
|
XHCI_ST_CTX2_TTT_SET(hport->parent->ttt);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_speed_id
|
|
*
|
|
* Description:
|
|
* Turn the speed the USB host stack uses into the one a slot context
|
|
* wants, which is a different numbering with no relation to it.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t xhci_speed_id(uint8_t speed)
|
|
{
|
|
switch (speed)
|
|
{
|
|
case USB_SPEED_LOW:
|
|
return XHCI_SPEED_LOW;
|
|
case USB_SPEED_FULL:
|
|
return XHCI_SPEED_FULL;
|
|
case USB_SPEED_HIGH:
|
|
return XHCI_SPEED_HIGH;
|
|
case USB_SPEED_SUPER:
|
|
return XHCI_SPEED_SUPER;
|
|
case USB_SPEED_SUPER_PLUS:
|
|
return XHCI_SPEED_SUPER_PLUS;
|
|
default:
|
|
|
|
/* Nothing else can be described to a controller, and full speed
|
|
* is the safe answer.
|
|
*/
|
|
|
|
uwarn("no speed ID for USB speed %d\n", speed);
|
|
return XHCI_SPEED_FULL;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dmacapable
|
|
*
|
|
* Description:
|
|
* Whether the controller may be pointed at this buffer.
|
|
*
|
|
* The driver has no way to know this on its own. Whether an address can
|
|
* be turned into one the device will reach, and whether what lies behind
|
|
* it is contiguous, is a property of the system the controller was fitted
|
|
* into, so the answer comes from there. A platform that says nothing is
|
|
* taken to mean every address works, which is what a flat address space
|
|
* gives.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool xhci_dmacapable(FAR struct usbhost_xhci_s *priv,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
if (priv->ops->dmacapable == NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return priv->ops->dmacapable(priv->arg, buffer, buflen);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dma_prepare
|
|
*
|
|
* Description:
|
|
* Make a caller's buffer safe for the controller to reach, and say which
|
|
* address to hand it.
|
|
*
|
|
* The controller writes memory behind the processor's back, so on a
|
|
* machine whose caches are not coherent every buffer it touches must be
|
|
* flushed before the controller reads and invalidated before the
|
|
* processor does.
|
|
*
|
|
* Both act a whole cache line at a time, which is unsafe for a buffer
|
|
* that does not own its lines: invalidating drops whatever shares the
|
|
* line, and a writeback lands on top of what the controller just put
|
|
* there. Class drivers pass their own structure members, a 31 byte
|
|
* command block or a 13 byte status, which share lines.
|
|
*
|
|
* Such a buffer gets an aligned stand-in and is copied at the ends.
|
|
* Anything large enough to matter comes from a filesystem or from
|
|
* xhci_ioalloc() and is already aligned.
|
|
*
|
|
* Returned Value:
|
|
* The address to give the controller, or NULL if a stand-in was needed
|
|
* and could not be allocated.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR uint8_t *xhci_dma_prepare(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_epinfo_s *epinfo,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
bool dirin)
|
|
{
|
|
size_t line = up_get_dcache_linesize();
|
|
bool reachable = xhci_dmacapable(priv, buffer, buflen);
|
|
|
|
epinfo->buffer = buffer;
|
|
epinfo->bounce = NULL;
|
|
epinfo->dmalen = buflen;
|
|
|
|
/* How much to bring back afterwards. This cannot be taken from buflen
|
|
* at completion time: that field means the length of a data transfer and
|
|
* control transfers deliberately leave it zero, so a descriptor read
|
|
* would copy nothing back and the caller would see whatever its buffer
|
|
* held before.
|
|
*/
|
|
|
|
epinfo->dmacopy = buflen;
|
|
epinfo->dmain = dirin;
|
|
|
|
/* Nothing to arrange: no cache to maintain, and an address the
|
|
* controller can be pointed at as it stands.
|
|
*/
|
|
|
|
if (line == 0 && reachable)
|
|
{
|
|
return buffer;
|
|
}
|
|
|
|
if (!reachable ||
|
|
((uintptr_t)buffer & (line - 1)) != 0 || (buflen & (line - 1)) != 0)
|
|
{
|
|
/* A stand-in is needed; see xhci_dmacapable() for the same test */
|
|
|
|
/* The buffer shares a line with something else. Work in a stand-in
|
|
* that does not.
|
|
*/
|
|
|
|
/* Maintain the whole stand-in, not just the part in use: cache
|
|
* operations work a line at a time and this chip rejects a partial
|
|
* range.
|
|
*/
|
|
|
|
epinfo->dmalen = line ? ((buflen + line - 1) & ~(line - 1)) : buflen;
|
|
|
|
epinfo->bounce = kmm_memalign(line ? line : sizeof(uintptr_t),
|
|
epinfo->dmalen);
|
|
if (epinfo->bounce == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (!dirin)
|
|
{
|
|
memcpy(epinfo->bounce, buffer, buflen);
|
|
}
|
|
|
|
buffer = epinfo->bounce;
|
|
}
|
|
|
|
/* Push what we are sending; drop what we are about to be sent, so that
|
|
* nothing the processor is still holding can be written back over it
|
|
* while the transfer is in flight.
|
|
*/
|
|
|
|
if (dirin)
|
|
{
|
|
up_invalidate_dcache((uintptr_t)buffer,
|
|
(uintptr_t)buffer + epinfo->dmalen);
|
|
}
|
|
else
|
|
{
|
|
up_clean_dcache((uintptr_t)buffer,
|
|
(uintptr_t)buffer + epinfo->dmalen);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_dma_finish
|
|
*
|
|
* Description:
|
|
* Read back what the controller wrote, and give up any stand-in buffer.
|
|
*
|
|
* This must run in the context of whoever asked for the transfer, not in
|
|
* the completion handler. The buffer being copied back into may belong
|
|
* to a user process, and its address means nothing in the work queue
|
|
* thread that handles the completion event, where the write would fault
|
|
* or corrupt another process. The caller is blocked until the transfer
|
|
* finishes anyway.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_dma_finish(FAR struct xhci_epinfo_s *epinfo)
|
|
{
|
|
FAR uint8_t *dma = epinfo->bounce ? epinfo->bounce : epinfo->buffer;
|
|
bool dirin = epinfo->dmain;
|
|
|
|
if (dma == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dirin)
|
|
{
|
|
up_invalidate_dcache((uintptr_t)dma,
|
|
(uintptr_t)dma + epinfo->dmalen);
|
|
|
|
if (epinfo->bounce != NULL && epinfo->buffer != NULL)
|
|
{
|
|
memcpy(epinfo->buffer, epinfo->bounce, epinfo->dmacopy);
|
|
}
|
|
}
|
|
|
|
if (epinfo->bounce != NULL)
|
|
{
|
|
kmm_free(epinfo->bounce);
|
|
epinfo->bounce = NULL;
|
|
}
|
|
|
|
epinfo->buffer = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_transfer_complete
|
|
*
|
|
* Description:
|
|
* Handle transfer complete event
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_trb_s *evt)
|
|
{
|
|
FAR struct xhci_epinfo_s *epinfo;
|
|
uint32_t tl = XHCI_TRB_D1_TXLEN_GET(evt->d1);
|
|
uint8_t slot = XHCI_TRB_D2_SLOTID_GET(evt->d2);
|
|
uint8_t ep = XHCI_TRB_D2_EP_GET(evt->d2);
|
|
uint8_t ret = XHCI_TRB_D1_CC_GET(evt->d1);
|
|
irqstate_t flags;
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
bool asynch = false;
|
|
#endif
|
|
|
|
/* Get EP associated with this transfer */
|
|
|
|
epinfo = priv->devs[slot - 1].epinfo[ep - 1];
|
|
DEBUGASSERT(epinfo != NULL);
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
|
|
/* Get transferred length */
|
|
|
|
if (epinfo->buflen > 0)
|
|
{
|
|
epinfo->xfrd = epinfo->buflen - tl;
|
|
}
|
|
|
|
/* Check transfer status */
|
|
|
|
if (ret == XHCI_TRB_CC_SUCCESS)
|
|
{
|
|
/* Report success */
|
|
|
|
epinfo->status = 0;
|
|
epinfo->result = OK;
|
|
}
|
|
|
|
else if (ret == XHCI_TRB_CC_STALL)
|
|
{
|
|
/* Report STALL condition */
|
|
|
|
epinfo->status = 0;
|
|
epinfo->result = -EPERM;
|
|
}
|
|
|
|
else if (ret == XHCI_TRB_CC_SHORT_PKT)
|
|
{
|
|
/* Report success */
|
|
|
|
epinfo->status = 0;
|
|
epinfo->result = OK;
|
|
}
|
|
|
|
else
|
|
{
|
|
/* Report error */
|
|
|
|
uerr("transfer CC = %d\n", ret);
|
|
epinfo->status = ret;
|
|
epinfo->result = -EIO;
|
|
}
|
|
|
|
/* Is there a thread waiting for this transfer to complete? */
|
|
|
|
if (epinfo->iocwait)
|
|
{
|
|
/* Yes... wake it up */
|
|
|
|
epinfo->iocwait = 0;
|
|
nxsem_post(&epinfo->iocsem);
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* No.. Is there a pending asynchronous transfer instead? Decide while
|
|
* still holding the lock: the moment the waiter above is posted, the
|
|
* endpoint may be given a new transfer, and that one is not complete.
|
|
*/
|
|
|
|
else
|
|
{
|
|
asynch = epinfo->callback != NULL;
|
|
}
|
|
#endif
|
|
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* The callback runs outside the spinlock: it is class driver code, and
|
|
* what it does (queue work, take its own locks) has no business running
|
|
* with interrupts masked.
|
|
*/
|
|
|
|
if (asynch)
|
|
{
|
|
/* Perform the callback */
|
|
|
|
xhci_asynch_completion(priv, epinfo);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_envet_complete
|
|
*
|
|
* Description:
|
|
* Handle event complete event
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_event_complete(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct xhci_trb_s *evt)
|
|
{
|
|
irqstate_t flags;
|
|
|
|
/* REVISIT: we assume for now that only one command is pending */
|
|
|
|
/* Store command result */
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
priv->cmdres.d0 = evt->d0;
|
|
priv->cmdres.d1 = evt->d1;
|
|
priv->cmdres.d2 = evt->d2;
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
/* Signal that command is done */
|
|
|
|
nxsem_post(&priv->cmdsem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_events_poll
|
|
*
|
|
* Description:
|
|
* Poll all pending events
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_events_poll(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
FAR struct xhci_trb_s *evt;
|
|
uintptr_t addr;
|
|
uint8_t type;
|
|
uint32_t d2;
|
|
int count = 0;
|
|
|
|
/* Invalidate event ring */
|
|
|
|
up_invalidate_dcache((uintptr_t)priv->evnt.ring,
|
|
(uintptr_t)(priv->evnt.ring + XHCI_EVENT_MAX));
|
|
|
|
/* Handle all pending events */
|
|
|
|
while (1)
|
|
{
|
|
evt = &priv->evnt.ring[priv->evnt.i];
|
|
|
|
/* Update address */
|
|
|
|
addr = (uintptr_t)evt;
|
|
|
|
d2 = le32toh(evt->d2);
|
|
if ((d2 & XHCI_TRB_D2_C) != priv->evnt.ccs)
|
|
{
|
|
break;
|
|
}
|
|
|
|
type = XHCI_TRB_D2_TYPE_GET(d2);
|
|
|
|
switch (type)
|
|
{
|
|
/* Transfer Event */
|
|
|
|
case XHCI_TRB_EVT_TRANSFER:
|
|
{
|
|
xhci_transfer_complete(priv, evt);
|
|
|
|
break;
|
|
}
|
|
|
|
/* Command Completion Event */
|
|
|
|
case XHCI_TRB_EVT_CMD_COMP:
|
|
{
|
|
xhci_event_complete(priv, evt);
|
|
|
|
break;
|
|
}
|
|
|
|
/* Port Status Change Event */
|
|
|
|
case XHCI_TRB_EVT_PSTAT_CHANGE:
|
|
{
|
|
/* We have to handle Port Status Change in a separate work
|
|
* queue, otherwise we'll get stuck when handling disconnect
|
|
* request.
|
|
*/
|
|
|
|
if (work_available(&priv->pscwork))
|
|
{
|
|
work_queue(LPWORK, &priv->pscwork, xhci_portsc_work,
|
|
(FAR void *)priv, 0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
uinfo("ignored event %d\n", type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Next event */
|
|
|
|
count++;
|
|
priv->evnt.i++;
|
|
|
|
/* Handle ring wrap */
|
|
|
|
if (priv->evnt.i >= XHCI_EVENT_MAX)
|
|
{
|
|
xhci_ring_reset(&priv->evnt, true);
|
|
}
|
|
}
|
|
|
|
/* Clear ERDP busy bit and update dequeue pointer */
|
|
|
|
addr = up_addrenv_va_to_pa((FAR void *)addr);
|
|
addr |= XHCI_ERDP_EHB;
|
|
xhci_runt_putreg_8b(priv, XHCI_ERDP(0), addr);
|
|
|
|
return count;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_interupt_work
|
|
*
|
|
* Description:
|
|
* Handle xHCI interrupts
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_interrupt_work(FAR void *arg)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = arg;
|
|
uint32_t iman;
|
|
|
|
/* Acknowledge before walking the ring, not after. An event arriving
|
|
* during the walk sets the pending bit again, and clearing after the
|
|
* walk discards it. Transfers have no timeout, so the one it belonged
|
|
* to would wait forever.
|
|
*/
|
|
|
|
xhci_oper_putreg(priv, XHCI_USBSTS, priv->pending);
|
|
|
|
iman = xhci_runt_getreg(priv, XHCI_IMAN(0));
|
|
if (iman & XHCI_IMAN_IP)
|
|
{
|
|
xhci_runt_putreg(priv, XHCI_IMAN(0), iman);
|
|
}
|
|
|
|
xhci_events_poll(priv);
|
|
|
|
/* Port Change Detect */
|
|
|
|
if (priv->pending & XHCI_USBSTS_PCD)
|
|
{
|
|
/* Handled as event in xhci_events_poll() */
|
|
|
|
uinfo("Port Change Detect\n");
|
|
}
|
|
|
|
/* Host Controller Halted */
|
|
|
|
if (priv->pending & XHCI_USBSTS_HCH)
|
|
{
|
|
uinfo("Host Controller Halted\n");
|
|
}
|
|
|
|
/* Host System Error */
|
|
|
|
if (priv->pending & XHCI_USBSTS_HSE)
|
|
{
|
|
uinfo("Host System Error\n");
|
|
}
|
|
|
|
/* Host Controller Error */
|
|
|
|
if (priv->pending & XHCI_USBSTS_HCE)
|
|
{
|
|
uinfo("Host Controller Error\n");
|
|
}
|
|
|
|
/* Clear pending bits */
|
|
|
|
priv->pending = 0;
|
|
|
|
/* Let interrupts back in, which the handler masked on its way out, and
|
|
* clear the pending flag in the same write.
|
|
*
|
|
* A message signalled interrupt is sent on the flag's clear to set
|
|
* transition; a wire stays asserted while it is set. Events that
|
|
* arrived while this interrupter was masked have already set the flag,
|
|
* so enabling without clearing leaves a message with nothing to
|
|
* transition on, and transfers have no timeout.
|
|
*
|
|
* Clearing opens its own window: an event delivered between the ring
|
|
* going empty and this write is discarded. So drain again, and repeat
|
|
* if that drain found anything. A drain that finds nothing is the only
|
|
* state in which no event can have been lost.
|
|
*/
|
|
|
|
do
|
|
{
|
|
iman = xhci_runt_getreg(priv, XHCI_IMAN(0));
|
|
xhci_runt_putreg(priv, XHCI_IMAN(0),
|
|
iman | XHCI_IMAN_IE | XHCI_IMAN_IP);
|
|
}
|
|
while (xhci_events_poll(priv) > 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_interupt
|
|
*
|
|
* Description:
|
|
* Interrupt handler for xHCI
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = arg;
|
|
uint32_t iman;
|
|
|
|
/* Get pending interrupts */
|
|
|
|
priv->pending = xhci_oper_getreg(priv, XHCI_USBSTS);
|
|
|
|
/* Silence the interrupter before returning.
|
|
*
|
|
* Nothing here clears the condition that raised the interrupt; the work
|
|
* runs later on a work queue. On a level triggered line the source is
|
|
* still asserted on return, so the interrupt re-raises immediately and
|
|
* the worker never runs. The worker clears the status and unmasks.
|
|
*/
|
|
|
|
iman = xhci_runt_getreg(priv, XHCI_IMAN(0));
|
|
xhci_runt_putreg(priv, XHCI_IMAN(0), iman & ~XHCI_IMAN_IE);
|
|
|
|
/* Handle interrupts in worker */
|
|
|
|
if (work_available(&priv->work))
|
|
{
|
|
work_queue(HPWORK, &priv->work, xhci_interrupt_work, arg, 0);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_wait
|
|
*
|
|
* Description:
|
|
* Wait for a device to be connected or disconnected to/from a hub port.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The USB host connection instance obtained as a parameter from
|
|
* the call to the USB driver initialization logic.
|
|
* hport - The location to return the hub port descriptor that detected
|
|
* the connection related event.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success when a device is connected or
|
|
* disconnected. This function will not return until either (1) a device is
|
|
* connected or disconnect to/from any hub port or until (2) some failure
|
|
* occurs. On a failure, a negated errno value is returned indicating the
|
|
* nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_wait(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s **hport)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn);
|
|
FAR struct xhci_rhport_s *rhport;
|
|
FAR struct usbhost_hubport_s *connport;
|
|
irqstate_t flags;
|
|
int rhpndx;
|
|
int ret;
|
|
|
|
/* Loop until the connection state changes on one of the root hub ports or
|
|
* until an error occurs.
|
|
*/
|
|
|
|
while (true)
|
|
{
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
|
|
/* Check for a change in the connection state on any root hub port */
|
|
|
|
for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++)
|
|
{
|
|
/* Has the connection state changed on the RH port? */
|
|
|
|
rhport = &priv->rhport[rhpndx];
|
|
connport = &rhport->hport.hport;
|
|
if (rhport->connected != connport->connected)
|
|
{
|
|
/* Yes.. Return the RH port to inform the caller which
|
|
* port has the connection change.
|
|
*/
|
|
|
|
connport->connected = rhport->connected;
|
|
*hport = connport;
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP,
|
|
rhpndx + 1, rhport->connected);
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/* Is a device connected to an external hub? */
|
|
|
|
if (priv->hhead != priv->htail)
|
|
{
|
|
/* Yes.. return the external hub port */
|
|
|
|
connport = priv->hports[priv->htail];
|
|
priv->htail = (priv->htail + 1) % CONFIG_USBHOST_XHCI_MAX_DEVS;
|
|
|
|
*hport = (FAR struct usbhost_hubport_s *)connport;
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP,
|
|
HPORT(connport), connport->connected);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/* No changes on any port. Wait for a connection/disconnection event
|
|
* and check again
|
|
*/
|
|
|
|
priv->pscwait = true;
|
|
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
ret = nxsem_wait_uninterruptible(&priv->pscsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_rh_enumerate/xhci_enumerate
|
|
*
|
|
* Description:
|
|
* Enumerate the connected device. As part of this enumeration process,
|
|
* the driver will (1) get the device's configuration descriptor, (2)
|
|
* extract the class ID info from the configuration descriptor, (3) call
|
|
* usbhost_findclass() to find the class that supports this device, (4)
|
|
* call the create() method on the struct usbhost_registry_s interface
|
|
* to get a class instance, and finally (5) call the connect() method
|
|
* of the struct usbhost_class_s interface. After that, the class is in
|
|
* charge of the sequence of operations.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The USB host connection instance obtained as a parameter from
|
|
* the call to the USB driver initialization logic.
|
|
* hport - The descriptor of the hub port that has the newly connected
|
|
* device.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn);
|
|
FAR struct xhci_rhport_s *rhport;
|
|
int rhpndx;
|
|
int ret;
|
|
|
|
DEBUGASSERT(hport != NULL);
|
|
rhpndx = hport->port;
|
|
|
|
DEBUGASSERT(rhpndx >= 0 && rhpndx < priv->no_ports);
|
|
rhport = &priv->rhport[rhpndx];
|
|
|
|
/* Are we connected to a device? The caller should have called the wait()
|
|
* method first to be assured that a device is connected.
|
|
*/
|
|
|
|
if (!rhport->connected)
|
|
{
|
|
/* No, return an error */
|
|
|
|
uerr("not connected\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Enable port */
|
|
|
|
ret = xhci_port_enable(priv, hport);
|
|
if (ret < 0)
|
|
{
|
|
uerr("Failed to enable port %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Initialize device data */
|
|
|
|
ret = xhci_device_init(priv, rhport, &rhport->hport.hport,
|
|
&rhport->ep0);
|
|
if (ret < 0)
|
|
{
|
|
uerr("Failed to initialize device %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_enumerate
|
|
*
|
|
* Description:
|
|
* See description above.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
int ret;
|
|
|
|
/* If this is a connection on the root hub, then we need to go to
|
|
* little more effort to get the device speed. If it is a connection
|
|
* on an external hub, then we already have that information.
|
|
*/
|
|
|
|
DEBUGASSERT(hport);
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
if (ROOTHUB(hport))
|
|
#endif
|
|
{
|
|
ret = xhci_rh_enumerate(conn, hport);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Then let the common usbhost_enumerate do the real enumeration. */
|
|
|
|
ret = usbhost_enumerate(hport, &hport->devclass);
|
|
if (ret < 0)
|
|
{
|
|
/* Failed to enumerate */
|
|
|
|
/* The device is addressed by now, so it holds a slot, and the retry
|
|
* below asks for another.
|
|
*/
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
if (ROOTHUB(hport))
|
|
#endif
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn);
|
|
FAR struct xhci_rhport_s *rhport = &priv->rhport[hport->port];
|
|
|
|
if (rhport->dev != NULL)
|
|
{
|
|
xhci_device_deinit(priv, rhport->dev);
|
|
}
|
|
|
|
/* Clearing connected below is what makes xhci_wait() return,
|
|
* so it is also what repeats the attempt. Leave the port alone
|
|
* past the limit; a new connection clears the count.
|
|
*/
|
|
|
|
if (++rhport->enumfail >= CONFIG_USBHOST_XHCI_ENUM_RETRIES)
|
|
{
|
|
syslog(LOG_ERR, "%s: port %d: giving up after %d attempts\n",
|
|
priv->name, hport->port + 1, rhport->enumfail);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* If this is a root hub port, then marking the hub port not connected
|
|
* will cause xhci_wait() to return and we will try the connection
|
|
* again.
|
|
*/
|
|
|
|
hport->connected = false;
|
|
}
|
|
else
|
|
{
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
if (ROOTHUB(hport))
|
|
#endif
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn);
|
|
|
|
priv->rhport[hport->port].enumfail = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ep0configure
|
|
*
|
|
* Description:
|
|
* Configure endpoint 0. This method is normally used internally by the
|
|
* enumerate() method but is made available at the interface to support
|
|
* an external implementation of the enumeration logic.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* funcaddr - The USB address of the function containing the endpoint that
|
|
* EP0 controls. A funcaddr of zero will be received if no address is
|
|
* yet assigned to the device.
|
|
* speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH
|
|
* maxpacketsize - The maximum number of bytes that can be sent to or
|
|
* received from the endpoint in a single data packet
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure.
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0, uint8_t funcaddr,
|
|
uint8_t speed, uint16_t maxpacketsize)
|
|
{
|
|
FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep0;
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr);
|
|
FAR struct xhci_dev_s *dev;
|
|
uint64_t ctx;
|
|
int ret;
|
|
|
|
DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048);
|
|
|
|
dev = xhci_dev_from_ep(priv, epinfo);
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret >= 0)
|
|
{
|
|
/* Update max packet size */
|
|
|
|
FAR struct xhci_ep_ctx_s *ep0ctx =
|
|
xhci_in_ep(priv, dev->input, 0);
|
|
|
|
ep0ctx->ctx1 &= ~XHCI_EP_CTX1_MAXPKT_MASK;
|
|
ep0ctx->ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpacketsize);
|
|
|
|
/* Add Slot Context and EP0 Context */
|
|
|
|
xhci_context_ctrl(priv, dev, 0,
|
|
XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) |
|
|
XHCI_IN_CTX1_A(XHCI_EP0_FLAG));
|
|
|
|
/* Flush Device input context */
|
|
|
|
up_flush_dcache((uintptr_t)dev->input,
|
|
(uintptr_t)dev->input +
|
|
XHCI_INCTX_SIZE(priv));
|
|
|
|
/* Free mutex before command execution */
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
|
|
ctx = up_addrenv_va_to_pa(dev->input);
|
|
|
|
uinfo("slot %d funcaddr %d speed %d maxpacket %d\n",
|
|
epinfo->slot, funcaddr, speed, maxpacketsize);
|
|
|
|
ret = xhci_cmd_evalctx(priv, epinfo->slot, ctx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_interval
|
|
*
|
|
* Description:
|
|
* Work out the Interval an endpoint context wants.
|
|
*
|
|
* The field is an exponent: the controller services the endpoint every
|
|
* 2^Interval microframes. An endpoint descriptor does not say it that
|
|
* way, and what it does say depends on how fast the device is, so the
|
|
* number cannot simply be copied across.
|
|
*
|
|
* A low or full speed interrupt endpoint counts in frames, so its period
|
|
* is bInterval milliseconds, or bInterval * 8 microframes, and the
|
|
* exponent is the position of the highest bit of that. Everything else
|
|
* that is periodic already states an exponent, one greater than the one
|
|
* wanted here. Control and bulk endpoints are not periodic and the field
|
|
* means nothing to them.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint8_t xhci_interval(uint8_t speed, uint8_t xfrtype,
|
|
uint8_t interval)
|
|
{
|
|
unsigned int exp;
|
|
|
|
if (xfrtype != USB_EP_ATTR_XFER_INT && xfrtype != USB_EP_ATTR_XFER_ISOC)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ((speed == USB_SPEED_LOW || speed == USB_SPEED_FULL) &&
|
|
xfrtype == USB_EP_ATTR_XFER_INT)
|
|
{
|
|
/* Frames. Round down to a power of two, and keep it inside what the
|
|
* specification allows for this kind of endpoint: 2^3 microframes is
|
|
* one frame, 2^10 is 128 of them.
|
|
*/
|
|
|
|
if (interval == 0)
|
|
{
|
|
interval = 1;
|
|
}
|
|
|
|
for (exp = 0; (1u << (exp + 1)) <= interval * 8u; exp++);
|
|
|
|
if (exp < 3)
|
|
{
|
|
exp = 3;
|
|
}
|
|
else if (exp > 10)
|
|
{
|
|
exp = 10;
|
|
}
|
|
|
|
return exp;
|
|
}
|
|
|
|
/* Already an exponent, counted from one */
|
|
|
|
return interval > 0 ? interval - 1 : 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_epalloc
|
|
*
|
|
* Description:
|
|
* Allocate and configure one endpoint.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* epdesc - Describes the endpoint to be allocated.
|
|
* ep - A memory location provided by the caller in which to receive the
|
|
* allocated endpoint descriptor.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure.
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_epalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr);
|
|
FAR struct usbhost_hubport_s *hport;
|
|
FAR struct xhci_epinfo_s *epinfo;
|
|
FAR struct xhci_dev_s *dev;
|
|
uint32_t mask;
|
|
uint8_t eptype;
|
|
uint8_t idx;
|
|
int ret;
|
|
|
|
/* Sanity check. NOTE that this method should only be called if a device
|
|
* is connected (because we need a valid low speed indication).
|
|
*/
|
|
|
|
DEBUGASSERT(drvr != 0 && epdesc != NULL && epdesc->hport != NULL
|
|
&& ep != NULL);
|
|
hport = epdesc->hport;
|
|
|
|
/* Terse output only if we are tracing */
|
|
|
|
#ifdef CONFIG_USBHOST_TRACE
|
|
usbhost_vtrace2(XHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype);
|
|
#else
|
|
uinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n",
|
|
epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr,
|
|
epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize);
|
|
#endif
|
|
|
|
/* Allocate a endpoint information structure */
|
|
|
|
epinfo = kmm_zalloc(sizeof(struct xhci_epinfo_s));
|
|
if (!epinfo)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize the endpoint container (which is really just another form of
|
|
* 'struct usbhost_epdesc_s', packed differently and with additional
|
|
* information. A cleaner design might just embed struct usbhost_epdesc_s
|
|
* inside of struct xhci_epinfo_s and just memcpy here.
|
|
*/
|
|
|
|
epinfo->dirin = epdesc->in;
|
|
epinfo->epno = epdesc->addr;
|
|
|
|
#ifndef CONFIG_USBHOST_INT_DISABLE
|
|
epinfo->interval = xhci_interval(hport->speed, epdesc->xfrtype,
|
|
epdesc->interval);
|
|
#endif
|
|
epinfo->xfrtype = epdesc->xfrtype;
|
|
nxsem_init(&epinfo->iocsem, 0, 0);
|
|
nxmutex_init(&epinfo->lock);
|
|
|
|
/* xhci_epno_get() returns Device Context Index (DCI) */
|
|
|
|
idx = xhci_epno_get(epinfo);
|
|
mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx));
|
|
dev = xhci_dev_from_hport(priv, hport);
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/* A hub asks for the control endpoint of a port before it reports the
|
|
* connection, so there is no device to attach it to yet. Hand back an
|
|
* endpoint with no slot; xhci_connect() gives it one when it creates the
|
|
* device, which is the next thing the hub does.
|
|
*/
|
|
|
|
if (dev == NULL && !ROOTHUB(hport) &&
|
|
epdesc->xfrtype == USB_EP_ATTR_XFER_CONTROL)
|
|
{
|
|
ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ep0 ring init failed\n");
|
|
nxmutex_destroy(&epinfo->lock);
|
|
nxsem_destroy(&epinfo->iocsem);
|
|
kmm_free(epinfo);
|
|
return ret;
|
|
}
|
|
|
|
*ep = (usbhost_ep_t)epinfo;
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/* There has to be a device to hang the endpoint off. A port whose
|
|
* enumeration failed is retried after its slot has been given back, so
|
|
* this can run for a port with nothing behind it.
|
|
*/
|
|
|
|
if (dev == NULL)
|
|
{
|
|
uerr("no device on port %d\n", hport->port);
|
|
nxmutex_destroy(&epinfo->lock);
|
|
nxsem_destroy(&epinfo->iocsem);
|
|
kmm_free(epinfo);
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev->epinfo[idx - 1] = epinfo;
|
|
|
|
/* TD rings already allocated but not connected yet. */
|
|
|
|
ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ep ring init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Store slot ID for later */
|
|
|
|
epinfo->slot = dev->slot;
|
|
|
|
/* Get EP type */
|
|
|
|
switch (epinfo->xfrtype)
|
|
{
|
|
case USB_EP_ATTR_XFER_BULK:
|
|
{
|
|
eptype = epinfo->dirin ? XHCI_EPTYPE_BULK_IN :
|
|
XHCI_EPTYPE_BULK_OUT;
|
|
break;
|
|
}
|
|
|
|
#ifndef CONFIG_USBHOST_INT_DISABLE
|
|
case USB_EP_ATTR_XFER_INT:
|
|
#endif
|
|
{
|
|
eptype = epinfo->dirin ? XHCI_EPTYPE_INTR_IN :
|
|
XHCI_EPTYPE_INTR_OUT;
|
|
break;
|
|
}
|
|
|
|
#ifndef CONFIG_USBHOST_ISOC_DISABLE
|
|
case USB_EP_ATTR_XFER_ISOC:
|
|
{
|
|
eptype = epinfo->dirin ? XHCI_EPTYPE_ISO_IN :
|
|
XHCI_EPTYPE_ISO_OUT;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
}
|
|
|
|
/* REVISIT: do we need disable EP here? */
|
|
|
|
/* Initialize EP context.
|
|
* Max Burst Size set for 0 for now (USB3.0 specific)
|
|
*/
|
|
|
|
xhci_ep_configure(priv, xhci_in_ep(priv, dev->input, idx - 1),
|
|
eptype, epdesc->mxpacketsize, 0,
|
|
up_addrenv_va_to_pa(epinfo->td.ring),
|
|
0, epinfo->interval);
|
|
|
|
/* Evaluate the slot context */
|
|
|
|
xhci_context_ctrl(priv, dev, 0, mask | XHCI_IN_CTX1_A(XHCI_SLOT_FLAG));
|
|
|
|
up_flush_dcache((uintptr_t)dev->input,
|
|
(uintptr_t)dev->input +
|
|
XHCI_INCTX_SIZE(priv));
|
|
|
|
/* Configure EP */
|
|
|
|
ret = xhci_cmd_cfgep(priv, epinfo->slot,
|
|
up_addrenv_va_to_pa(dev->input), false);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to configure EP %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Success.. return an opaque reference to the endpoint information
|
|
* structure instance
|
|
*/
|
|
|
|
*ep = (usbhost_ep_t)epinfo;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_epfree
|
|
*
|
|
* Description:
|
|
* Free an endpoint previously allocated by DRVR_EPALLOC.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* ep - The endpoint to be freed.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
|
|
{
|
|
FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep;
|
|
|
|
/* There should not be any pending, transfers */
|
|
|
|
DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0);
|
|
|
|
/* Free ring */
|
|
|
|
xhci_ring_deinit(&epinfo->td);
|
|
|
|
/* Free the container */
|
|
|
|
nxmutex_destroy(&epinfo->lock);
|
|
nxsem_destroy(&epinfo->iocsem);
|
|
kmm_free(epinfo);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_alloc
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which request and descriptor
|
|
* data can be accessed more efficiently. This method provides a
|
|
* mechanism to allocate the request/descriptor memory. If the underlying
|
|
* hardware does not support such "special" memory, this functions may
|
|
* simply map to kmm_malloc().
|
|
*
|
|
* This interface was optimized under a particular assumption. It was
|
|
* assumed that the driver maintains a pool of small, pre-allocated buffers
|
|
* for descriptor traffic. NOTE that size is not an input, but an output:
|
|
* The size of the pre-allocated buffer is returned.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* buffer - The address of a memory location provided by the caller in
|
|
* which to return the allocated buffer memory address.
|
|
* maxlen - The address of a memory location provided by the caller in
|
|
* which to return the maximum size of the allocated buffer memory.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_alloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, FAR size_t *maxlen)
|
|
{
|
|
int ret = -ENOMEM;
|
|
|
|
DEBUGASSERT(drvr && buffer && maxlen);
|
|
|
|
/* Allocated buffer must not cross page boundaries */
|
|
|
|
*buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , XHCI_BUFSIZE);
|
|
if (*buffer)
|
|
{
|
|
*maxlen = XHCI_BUFSIZE;
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_free
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which request and descriptor
|
|
* data can be accessed more efficiently. This method provides a
|
|
* mechanism to free that request/descriptor memory. If the underlying
|
|
* hardware does not support such "special" memory, this functions may
|
|
* simply map to kmm_free().
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* buffer - The address of the allocated buffer memory to be freed.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer)
|
|
{
|
|
DEBUGASSERT(drvr && buffer);
|
|
|
|
/* No special action is require to free the transfer/descriptor buffer
|
|
* memory
|
|
*/
|
|
|
|
kmm_free(buffer);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ioalloc
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which larger IO buffers can
|
|
* be accessed more efficiently. This method provides a mechanism to
|
|
* allocate the request/descriptor memory. If the underlying hardware
|
|
* does not support such "special" memory, this functions may simply map
|
|
* to kumm_malloc.
|
|
*
|
|
* This interface differs from DRVR_ALLOC in that the buffers are variable-
|
|
* sized.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* buffer - The address of a memory location provided by the caller in
|
|
* which to return the allocated buffer memory address.
|
|
* buflen - The size of the buffer required.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, size_t buflen)
|
|
{
|
|
size_t line;
|
|
int ret = -ENOMEM;
|
|
|
|
DEBUGASSERT(drvr && buffer && buflen > 0);
|
|
|
|
/* Large transfers are not supported now */
|
|
|
|
if (buflen > XHCI_PAGE_SIZE)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Allocated buffer must not cross page boundaries.
|
|
*
|
|
* Round to whole cache lines as well as aligning the start, so that the
|
|
* buffer owns every line it touches and can be invalidated without
|
|
* disturbing whatever would otherwise share the last one.
|
|
*/
|
|
|
|
line = up_get_dcache_linesize();
|
|
if (line > 1)
|
|
{
|
|
buflen = (buflen + line - 1) & ~(line - 1);
|
|
}
|
|
|
|
*buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , buflen);
|
|
if (*buffer)
|
|
{
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_iofree
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which IO data can be accessed
|
|
* more efficiently. This method provides a mechanism to free that IO
|
|
* buffer memory. If the underlying hardware does not support such
|
|
* "special" memory, this functions may simply map to kumm_free().
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* buffer - The address of the allocated buffer memory to be freed.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_iofree(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t *buffer)
|
|
{
|
|
DEBUGASSERT(drvr && buffer);
|
|
|
|
/* No special action is require to free the transfer/descriptor buffer
|
|
* memory
|
|
*/
|
|
|
|
kmm_free(buffer);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_ctrl_xfer
|
|
*
|
|
* Description:
|
|
* Process a IN or OUT request on the control endpoint. These methods
|
|
* will enqueue the request and wait for it to complete. Only one
|
|
* transfer may be queued; Neither these methods nor the transfer() method
|
|
* can be called again until the control transfer function returns.
|
|
*
|
|
* These are blocking methods; these functions will not return until the
|
|
* control transfer has completed.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* ep0 - The control endpoint to send/receive the control request.
|
|
* req - Describes the request to be sent. This request must lie in
|
|
* memory created by DRVR_ALLOC.
|
|
* buffer - A buffer used for sending the request and for returning any
|
|
* responses. This buffer must be large enough to hold the
|
|
* length value in the request description. buffer must have been
|
|
* allocated using DRVR_ALLOC.
|
|
*
|
|
* NOTE: On an IN transaction, req and buffer may refer to the xHCI
|
|
* allocated memory.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr);
|
|
FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr;
|
|
FAR struct xhci_epinfo_s *ep0info = (FAR struct xhci_epinfo_s *)ep0;
|
|
uint16_t len;
|
|
ssize_t nbytes;
|
|
int ret;
|
|
|
|
DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL);
|
|
|
|
/* One request at a time on this endpoint. Taken before the controller
|
|
* lock and held across the wait, so the ordering is always endpoint then
|
|
* controller and never the reverse.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&ep0info->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
len = xhci_getle16(req->len);
|
|
|
|
/* Terse output only if we are tracing */
|
|
|
|
#ifdef CONFIG_USBHOST_TRACE
|
|
usbhost_vtrace2(XHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req);
|
|
#endif
|
|
|
|
/* Special case for SET_ADDRESS request */
|
|
|
|
if (req->req == USB_REQ_SETADDRESS)
|
|
{
|
|
/* Reset EP0 ring to its initial state, so when xHCI update EP0
|
|
* context, TD dequeue pointer would be valid. It may be already
|
|
* off, because the USB Host stack has already sent some messages
|
|
* on control EP.
|
|
*/
|
|
|
|
xhci_ring_init(&ep0info->td, 0);
|
|
|
|
/* Issue SET_ADDRESS request */
|
|
|
|
ret = xhci_address_set(priv, xhci_dev_from_ep(priv, ep0info),
|
|
true);
|
|
if (ret == OK)
|
|
{
|
|
FAR struct xhci_dev_s *dev = xhci_dev_from_ep(priv, ep0info);
|
|
|
|
/* The controller chose this address and wrote it into the
|
|
* output context. Invalidate before reading, or the stale
|
|
* copy is used.
|
|
*/
|
|
|
|
up_invalidate_dcache((uintptr_t)dev->ctx,
|
|
(uintptr_t)dev->ctx +
|
|
XHCI_DEVCTX_SIZE(priv));
|
|
|
|
/* Store USB Device Address assigned by xHCI */
|
|
|
|
ep0info->devaddr =
|
|
XHCI_ST_CTX3_ADDR_GET(xhci_out_slot(dev->ctx)->ctx[3]);
|
|
xhci_in_slot(priv, dev->input)->ctx[3] =
|
|
xhci_out_slot(dev->ctx)->ctx[3];
|
|
}
|
|
|
|
nxmutex_unlock(&ep0info->lock);
|
|
return OK;
|
|
}
|
|
|
|
/* We must have exclusive access to the XHCI hardware and data
|
|
* structures.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
nxmutex_unlock(&ep0info->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* Set the request for the IOC event well BEFORE initiating the transfer. */
|
|
|
|
ret = xhci_ioc_setup(rhport, ep0info, 0);
|
|
if (ret != OK)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Now initiate the transfer */
|
|
|
|
ret = xhci_control_setup(rhport, ep0info, req, buffer, len);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: xhci_control_setup failed: %d\n", ret);
|
|
goto errout_with_iocwait;
|
|
}
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
|
|
/* And wait for the transfer to complete */
|
|
|
|
nbytes = xhci_transfer_wait(priv, ep0info);
|
|
|
|
/* As for bulk: the copy back belongs in the caller's context */
|
|
|
|
xhci_dma_finish(ep0info);
|
|
|
|
nxmutex_unlock(&ep0info->lock);
|
|
return nbytes >= 0 ? OK : (int)nbytes;
|
|
|
|
errout_with_iocwait:
|
|
ep0info->iocwait = false;
|
|
errout_with_lock:
|
|
nxmutex_unlock(&priv->lock);
|
|
nxmutex_unlock(&ep0info->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);
|
|
|
|
/* One transfer at a time on this endpoint, taken before the controller
|
|
* lock and held across the wait. See the note on epinfo->lock.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&epinfo->lock);
|
|
if (ret < 0)
|
|
{
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* We must have exclusive access to the xHCI hardware and data
|
|
* structures.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
nxmutex_unlock(&epinfo->lock);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Set the request for the IOC event well BEFORE initiating the transfer. */
|
|
|
|
ret = xhci_ioc_setup(rhport, epinfo, buflen);
|
|
if (ret != OK)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Initiate the transfer */
|
|
|
|
switch (epinfo->xfrtype)
|
|
{
|
|
case USB_EP_ATTR_XFER_BULK:
|
|
#ifndef CONFIG_USBHOST_INT_DISABLE
|
|
case USB_EP_ATTR_XFER_INT:
|
|
#endif
|
|
{
|
|
ret = xhci_normal_setup(rhport, epinfo, buffer, buflen);
|
|
break;
|
|
}
|
|
|
|
#ifndef CONFIG_USBHOST_ISOC_DISABLE
|
|
case USB_EP_ATTR_XFER_ISOC:
|
|
{
|
|
ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case USB_EP_ATTR_XFER_CONTROL:
|
|
default:
|
|
{
|
|
usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype);
|
|
ret = -ENOSYS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check for errors in the setup of the transfer */
|
|
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Transfer setup failed: %d\n", ret);
|
|
goto errout_with_iocwait;
|
|
}
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
|
|
/* Then wait for the transfer to complete */
|
|
|
|
nbytes = xhci_transfer_wait(priv, epinfo);
|
|
|
|
/* And bring back what it produced, here rather than in the completion,
|
|
* because this is the context the caller's buffer belongs to.
|
|
*/
|
|
|
|
xhci_dma_finish(epinfo);
|
|
|
|
nxmutex_unlock(&epinfo->lock);
|
|
return nbytes;
|
|
|
|
errout_with_iocwait:
|
|
epinfo->iocwait = false;
|
|
errout_with_lock:
|
|
nxmutex_unlock(&priv->lock);
|
|
nxmutex_unlock(&epinfo->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, buflen, callback, arg);
|
|
if (ret != OK)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Initiate the transfer */
|
|
|
|
switch (epinfo->xfrtype)
|
|
{
|
|
case USB_EP_ATTR_XFER_BULK:
|
|
#ifndef CONFIG_USBHOST_INT_DISABLE
|
|
case USB_EP_ATTR_XFER_INT:
|
|
#endif
|
|
{
|
|
ret = xhci_normal_setup(rhport, epinfo, buffer, buflen);
|
|
break;
|
|
}
|
|
|
|
#ifndef CONFIG_USBHOST_ISOC_DISABLE
|
|
case USB_EP_ATTR_XFER_ISOC:
|
|
{
|
|
ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case USB_EP_ATTR_XFER_CONTROL:
|
|
default:
|
|
{
|
|
usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype);
|
|
ret = -ENOSYS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check for errors in the setup of the transfer */
|
|
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_callback;
|
|
}
|
|
|
|
/* The transfer is in progress */
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
return OK;
|
|
|
|
errout_with_callback:
|
|
epinfo->callback = NULL;
|
|
epinfo->arg = NULL;
|
|
errout_with_lock:
|
|
nxmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_USBHOST_ASYNCH */
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_cancel
|
|
*
|
|
* Description:
|
|
* Cancel a pending transfer on an endpoint. Cancelled synchronous or
|
|
* asynchronous transfer will complete normally with the error -ESHUTDOWN.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* ep - The IN or OUT endpoint descriptor for the device endpoint on which
|
|
* an asynchronous transfer should be transferred.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
|
|
{
|
|
FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep;
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
usbhost_asynch_t callback;
|
|
FAR void *arg;
|
|
#endif
|
|
irqstate_t flags;
|
|
bool iocwait;
|
|
|
|
DEBUGASSERT(epinfo);
|
|
|
|
/* Sample and reset all transfer termination information. This will
|
|
* prevent any callbacks from occurring while we performing the
|
|
* cancellation. The transfer may still be in progress, however, so this
|
|
* does not eliminate other DMA-related race conditions.
|
|
*/
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
callback = epinfo->callback;
|
|
arg = epinfo->arg;
|
|
#endif
|
|
iocwait = epinfo->iocwait;
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
epinfo->callback = NULL;
|
|
epinfo->arg = NULL;
|
|
#endif
|
|
epinfo->iocwait = false;
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
|
|
/* Bail if there is no transfer in progress for this endpoint */
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
if (callback == NULL && !iocwait)
|
|
#else
|
|
if (!iocwait)
|
|
#endif
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/* Stop endpoint */
|
|
|
|
xhci_cmd_stopep(priv, epinfo->slot, xhci_epno_get(epinfo), false);
|
|
|
|
/* REVISIT: what if we interrupted the execution of a TD? page 139 */
|
|
|
|
epinfo->result = -ESHUTDOWN;
|
|
|
|
if (iocwait)
|
|
{
|
|
/* Yes... wake it up */
|
|
|
|
nxsem_post(&epinfo->iocsem);
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* No.. Is there a pending asynchronous transfer? */
|
|
|
|
else
|
|
{
|
|
/* Yes.. give back any stand-in buffer, then perform the callback.
|
|
* The endpoint has been stopped, so the controller is no longer
|
|
* writing into it.
|
|
*/
|
|
|
|
DEBUGASSERT(callback != NULL);
|
|
xhci_dma_finish(epinfo);
|
|
callback(arg, -ESHUTDOWN);
|
|
}
|
|
#endif
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_connect
|
|
*
|
|
* Description:
|
|
* New connections may be detected by an attached hub. This method is the
|
|
* mechanism that is used by the hub class to introduce a new connection
|
|
* and port description to the system.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* hport - The descriptor of the hub port that detected the connection
|
|
* related event
|
|
* connected - True: device connected; false: device disconnected
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/****************************************************************************
|
|
* Name: xhci_rhport_from_hport
|
|
*
|
|
* Description:
|
|
* The root hub port a device descends from, however many hubs are in the
|
|
* way. The slot context names it, because that is the port the traffic
|
|
* physically leaves by.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR struct xhci_rhport_s *
|
|
xhci_rhport_from_hport(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
while (hport->parent != NULL)
|
|
{
|
|
hport = hport->parent;
|
|
}
|
|
|
|
return &priv->rhport[hport->port];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_hub_update
|
|
*
|
|
* Description:
|
|
* Tell the controller that a device is a hub, so that it will route to
|
|
* what is behind it.
|
|
*
|
|
* The slot was created before anyone knew: a hub is addressed and
|
|
* configured like any other device, and only then does its class driver
|
|
* read the descriptor saying how many ports it has. So the slot context
|
|
* is corrected here, the first time something appears behind it.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_hub_update(FAR struct usbhost_xhci_s *priv,
|
|
FAR struct usbhost_hubport_s *hubport)
|
|
{
|
|
FAR struct xhci_slot_ctx_s *in;
|
|
FAR struct xhci_dev_s *dev;
|
|
uint64_t ctx;
|
|
int ret;
|
|
|
|
dev = xhci_dev_from_hport(priv, hubport);
|
|
if (dev == NULL || dev->ishub || hubport->nports == 0)
|
|
{
|
|
/* Nothing to correct: no slot for it, already done, or the hub class
|
|
* driver has not reported the descriptor.
|
|
*/
|
|
|
|
return OK;
|
|
}
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Only the slot context changes, and it must go in carrying everything
|
|
* the controller already holds, so start from the output context it has
|
|
* been maintaining.
|
|
*/
|
|
|
|
up_invalidate_dcache((uintptr_t)dev->ctx,
|
|
(uintptr_t)dev->ctx + XHCI_DEVCTX_SIZE(priv));
|
|
|
|
xhci_context_ctrl(priv, dev, 0, XHCI_IN_CTX1_A(XHCI_SLOT_FLAG));
|
|
|
|
in = xhci_in_slot(priv, dev->input);
|
|
in->ctx[0] = xhci_out_slot(dev->ctx)->ctx[0] | htole32(XHCI_ST_CTX0_HUB);
|
|
in->ctx[1] = (xhci_out_slot(dev->ctx)->ctx[1] &
|
|
~htole32(XHCI_ST_CTX1_PORTS_MASK)) |
|
|
htole32(XHCI_ST_CTX1_PORTS_SET(hubport->nports));
|
|
in->ctx[2] = (xhci_out_slot(dev->ctx)->ctx[2] &
|
|
~htole32(XHCI_ST_CTX2_TTT_MASK)) |
|
|
htole32(XHCI_ST_CTX2_TTT_SET(hubport->ttt));
|
|
in->ctx[3] = xhci_out_slot(dev->ctx)->ctx[3];
|
|
|
|
up_flush_dcache((uintptr_t)dev->input,
|
|
(uintptr_t)dev->input + XHCI_INCTX_SIZE(priv));
|
|
|
|
ctx = up_addrenv_va_to_pa(dev->input);
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
|
|
ret = xhci_cmd_cfgep(priv, dev->slot, ctx, false);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to describe the hub on slot %d: %d\n", dev->slot, ret);
|
|
return ret;
|
|
}
|
|
|
|
dev->ishub = true;
|
|
|
|
uinfo("%s: port %d: hub with %d port%s\n",
|
|
priv->name, xhci_rhport_from_hport(priv, hubport)->hport.hport.port
|
|
+ 1, hubport->nports, hubport->nports == 1 ? "" : "s");
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int xhci_connect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
bool connected)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr);
|
|
FAR struct xhci_dev_s *dev;
|
|
irqstate_t flags;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL && hport != NULL && !ROOTHUB(hport));
|
|
|
|
/* The hub owns this port, so there is no port register here to consult
|
|
* and no reset to drive: what the hub reports is the whole of what the
|
|
* controller can know about it.
|
|
*/
|
|
|
|
hport->connected = connected;
|
|
|
|
if (connected)
|
|
{
|
|
/* The controller has to know the port belongs to a hub before it will
|
|
* carry anything to it.
|
|
*/
|
|
|
|
ret = xhci_hub_update(priv, hport->parent);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Give the device a slot. The hub allocated its control endpoint
|
|
* before saying anything, so that endpoint is what the slot gets.
|
|
*/
|
|
|
|
ret = xhci_device_init(priv, xhci_rhport_from_hport(priv, hport),
|
|
hport, (FAR struct xhci_epinfo_s *)hport->ep0);
|
|
if (ret < 0)
|
|
{
|
|
uerr("port %d: no slot for the device: %d\n", hport->port, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dev = xhci_dev_from_hport(priv, hport);
|
|
if (dev != NULL)
|
|
{
|
|
xhci_device_deinit(priv, dev);
|
|
}
|
|
}
|
|
|
|
flags = spin_lock_irqsave(&priv->spinlock);
|
|
|
|
/* Queue it for the waiter. Dropping one when the queue is full would
|
|
* lose a device silently, and the queue is as long as the controller has
|
|
* slots, so a full one means every slot is already spoken for.
|
|
*/
|
|
|
|
if ((uint8_t)(priv->hhead + 1) % CONFIG_USBHOST_XHCI_MAX_DEVS !=
|
|
priv->htail)
|
|
{
|
|
priv->hports[priv->hhead] = hport;
|
|
priv->hhead = (priv->hhead + 1) % CONFIG_USBHOST_XHCI_MAX_DEVS;
|
|
}
|
|
else
|
|
{
|
|
uerr("no room to report port %d\n", hport->port + 1);
|
|
}
|
|
|
|
if (priv->pscwait)
|
|
{
|
|
priv->pscwait = false;
|
|
nxsem_post(&priv->pscsem);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&priv->spinlock, flags);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_disconnect
|
|
*
|
|
* Description:
|
|
* Called by the class when an error occurs and device has been
|
|
* disconnected. The USB host driver should discard the handle to the
|
|
* class instance (it is stale) and not attempt any further interaction
|
|
* with the class driver instance (until a new instance is received from
|
|
* the create() method). The driver should not call the class
|
|
* disconnected() method.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the
|
|
* call to the class create() method.
|
|
* hport - The port from which the device is being disconnected. Might be
|
|
* a port on a hub.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* - Only a single class bound to a single device is supported.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xhci_disconnect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr);
|
|
FAR struct xhci_dev_s *dev;
|
|
|
|
DEBUGASSERT(hport != NULL);
|
|
hport->devclass = NULL;
|
|
|
|
/* Deinit the device that was on this port. Taking it from the port and
|
|
* not from the root port matters once a hub is in the way, where the root
|
|
* port names the hub rather than the device going away.
|
|
*/
|
|
|
|
dev = xhci_dev_from_hport(priv, hport);
|
|
if (dev != NULL)
|
|
{
|
|
xhci_device_deinit(priv, dev);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_hw_getparams
|
|
*
|
|
* Description:
|
|
* Get hardware description of a connected xHCI device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
uint32_t erst;
|
|
|
|
/* Get data form Host Controller Capability 1 Parameters */
|
|
|
|
/* Context entry stride, 32 or 64 bytes as the controller reports. The
|
|
* wider form is the same fields with padding.
|
|
*/
|
|
|
|
regval = xhci_capa_getreg(priv, XHCI_HCCPARAMS1);
|
|
priv->ctxsize = (regval & XHCI_HCCPARAMS1_CSZ) ? 64 : 32;
|
|
|
|
uinfo("context size = %d\n", priv->ctxsize);
|
|
|
|
/* Get data from Structural Parameters 1 register */
|
|
|
|
regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS1);
|
|
priv->no_slots = XHCI_HCSPARAMS1_MAXSLOTS(regval);
|
|
priv->no_ports = XHCI_HCSPARAMS1_MAXPORTS(regval);
|
|
|
|
/* Limit number of slots to number of devices */
|
|
|
|
if (priv->no_slots > CONFIG_USBHOST_XHCI_MAX_DEVS)
|
|
{
|
|
priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS;
|
|
}
|
|
|
|
uinfo("no slots = %d, no ports = %d\n",
|
|
priv->no_slots, priv->no_ports);
|
|
|
|
/* Check if valid */
|
|
|
|
if (priv->no_slots == 0 || priv->no_ports == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get data from Structural Parameters 2 register */
|
|
|
|
regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS2);
|
|
priv->no_scratch = XHCI_HCSPARAMS2_MAXSPB(regval);
|
|
|
|
uinfo("no scratch = %d\n", priv->no_scratch);
|
|
|
|
/* How many event ring segments the controller will allow, which is a
|
|
* power of two and can reach 32768, so it is worked out at full width
|
|
* and only then narrowed to what this driver actually uses. Computed
|
|
* into the field directly it would wrap to zero on any controller
|
|
* offering more than 128 segments, and a table declared to hold no
|
|
* entries gives a controller with nowhere to report anything.
|
|
*/
|
|
|
|
erst = 1ul << XHCI_HCSPARAMS2_ERST(regval);
|
|
|
|
uinfo("erst max = %" PRIu32 "\n", erst);
|
|
|
|
/* Limit event ring segment table to 1 */
|
|
|
|
priv->no_erst = (erst > XHCI_MAX_ERST) ? XHCI_MAX_ERST : erst;
|
|
|
|
uinfo("no erst = %d\n", priv->no_erst);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_irq_initialize
|
|
*
|
|
* Description:
|
|
* Ask the bus this controller was found on for its interrupt. See
|
|
* struct xhci_bus_ops_s in include/nuttx/usb/xhci.h.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
return priv->ops->irq_attach(priv->arg, xhci_interrupt, priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_mem_alloc
|
|
*
|
|
* Description:
|
|
* Allocated memory for a new detected xHCI device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
size_t tmp;
|
|
int i;
|
|
|
|
/* Allocate the Scratchpad Buffer Array, if one is wanted. no_scratch
|
|
* may be zero, and a zero byte allocation returns NULL.
|
|
*/
|
|
|
|
if (priv->no_scratch > 0)
|
|
{
|
|
tmp = priv->no_scratch * sizeof(uint64_t);
|
|
priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp);
|
|
if (!priv->pg_sb)
|
|
{
|
|
uerr("pg_sb malloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memset(priv->pg_sb, 0, tmp);
|
|
}
|
|
|
|
for (i = 0; i < priv->no_scratch; i++)
|
|
{
|
|
/* Alloc page for each entry in array */
|
|
|
|
priv->pg_sb[i] = up_addrenv_va_to_pa(
|
|
kmm_memalign(XHCI_PAGE_SIZE, XHCI_PAGE_SIZE));
|
|
if (!priv->pg_sb[i])
|
|
{
|
|
uerr("pg_sb[i] malloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Reset page */
|
|
|
|
memset((FAR void *)(up_addrenv_pa_to_va(priv->pg_sb[i])),
|
|
0, XHCI_PAGE_SIZE);
|
|
}
|
|
|
|
/* Allocate Device Context Array which shall be:
|
|
* size = MaxSlotsEn + 1 entries
|
|
*/
|
|
|
|
tmp = sizeof(uint64_t) * (priv->no_slots + 1);
|
|
priv->pg_ctx = kmm_memalign(XHCI_BUF_ALIGN, tmp);
|
|
if (!priv->pg_ctx)
|
|
{
|
|
uerr("pg_ctx malloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Reset context */
|
|
|
|
memset(priv->pg_ctx, 0, tmp);
|
|
|
|
/* Allocate Event Table */
|
|
|
|
tmp = sizeof(struct xhci_event_ring_s) * priv->no_erst;
|
|
priv->pg_erst = kmm_memalign(XHCI_BUF_ALIGN, tmp);
|
|
if (!priv->pg_erst)
|
|
{
|
|
uerr("priv->pg_erst malloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memset(priv->pg_erst, 0, tmp);
|
|
|
|
/* Allocate root hub ports */
|
|
|
|
priv->rhport = kmm_zalloc(priv->no_ports * sizeof(struct xhci_rhport_s));
|
|
if (!priv->rhport)
|
|
{
|
|
uerr("rhport zalloc failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Allocate xHC devices array */
|
|
|
|
priv->devs = kmm_zalloc(priv->no_slots * sizeof(struct xhci_dev_s));
|
|
if (!priv->devs)
|
|
{
|
|
uerr("devs zalloc failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Allocate xHC devices resources */
|
|
|
|
for (i = 0; i < priv->no_slots; i++)
|
|
{
|
|
/* Allocate Device Context */
|
|
|
|
/* The base address array holds these, and every entry in it must be
|
|
* 64 byte aligned, so the allocation has to be too.
|
|
*/
|
|
|
|
priv->devs[i].ctx = kmm_memalign(XHCI_CTX_ALIGN,
|
|
XHCI_DEVCTX_SIZE(priv));
|
|
if (!priv->devs[i].ctx)
|
|
{
|
|
uerr("dev ctx zalloc failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Allocate Input Context. The Input Context shall be physically
|
|
* contiguous within a page
|
|
*/
|
|
|
|
priv->devs[i].input = kmm_memalign((XHCI_PAGE_SIZE / 2),
|
|
XHCI_INCTX_SIZE(priv));
|
|
if (!priv->devs[i].input)
|
|
{
|
|
uerr("dev input zalloc failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* No endpoint for device yet */
|
|
|
|
tmp = sizeof(uintptr_t) * XHCI_MAX_ENDPOINTS;
|
|
memset(priv->devs[i].epinfo, 0, tmp);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_mem_free
|
|
*
|
|
* Description:
|
|
* Free allocated memory for a xHCI device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_mem_free(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
int i;
|
|
|
|
/* Free scratch buffers */
|
|
|
|
for (i = 0; i < priv->no_scratch; i++)
|
|
{
|
|
kmm_free((FAR void *)priv->pg_sb[i]);
|
|
}
|
|
|
|
kmm_free(priv->pg_sb);
|
|
|
|
/* Free devices */
|
|
|
|
for (i = 0; i < priv->no_slots; i++)
|
|
{
|
|
kmm_free(priv->devs[i].ctx);
|
|
kmm_free(priv->devs[i].input);
|
|
}
|
|
|
|
kmm_free(priv->devs);
|
|
kmm_free(priv->pg_ctx);
|
|
kmm_free(priv->pg_erst);
|
|
kmm_free(priv->rhport);
|
|
|
|
/* Free command ring and event ring */
|
|
|
|
xhci_ring_deinit(&priv->cmd);
|
|
xhci_ring_deinit(&priv->evnt);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_hw_initialize
|
|
*
|
|
* Description:
|
|
* One-time setup of the host controller hardware for normal operations.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* Synchronize with BIOS */
|
|
|
|
ret = xhci_bios_wait(priv);
|
|
if (ret < 0)
|
|
{
|
|
uerr("Failed to get xhci controller!\n");
|
|
goto errout;
|
|
}
|
|
|
|
/* Get structural parameters */
|
|
|
|
ret = xhci_hw_getparams(priv);
|
|
if (ret < 0)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
/* Allocate all required memory */
|
|
|
|
ret = xhci_mem_alloc(priv);
|
|
if (ret < 0)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
/* Halt controller */
|
|
|
|
ret = xhci_ctrl_halt(priv);
|
|
if (ret < 0)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
errout:
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_sw_initialize
|
|
*
|
|
* Description:
|
|
* One-time setup of the host driver state structure.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv)
|
|
{
|
|
FAR struct xhci_rhport_s *rhport;
|
|
FAR struct usbhost_hubport_s *hport;
|
|
int i;
|
|
|
|
/* Initialize sync objects */
|
|
|
|
nxmutex_init(&priv->lock);
|
|
nxsem_init(&priv->pscsem, 0, 0);
|
|
nxsem_init(&priv->cmdsem, 0, 0);
|
|
|
|
/* Initialize function address generation logic
|
|
* REVISIT: xHCI hardware is responsible for device address, but NuttX USB
|
|
* Host stack require this to be initialized.
|
|
*/
|
|
|
|
usbhost_devaddr_initialize(&priv->devgen);
|
|
|
|
/* Initialize devices */
|
|
|
|
for (i = 0; i < priv->no_slots; i++)
|
|
{
|
|
/* Slot disabled by defaulte */
|
|
|
|
priv->devs[i].state = XHCI_SLOT_DISABLED;
|
|
}
|
|
|
|
/* Initialize the root hub port structures */
|
|
|
|
for (i = 0; i < priv->no_ports; i++)
|
|
{
|
|
rhport = &priv->rhport[i];
|
|
|
|
/* No device slot yet */
|
|
|
|
rhport->dev = NULL;
|
|
|
|
/* Connect xhci instance */
|
|
|
|
rhport->priv = priv;
|
|
|
|
/* Initialize the device operations */
|
|
|
|
rhport->drvr.ep0configure = xhci_ep0configure;
|
|
rhport->drvr.epalloc = xhci_epalloc;
|
|
rhport->drvr.epfree = xhci_epfree;
|
|
rhport->drvr.alloc = xhci_alloc;
|
|
rhport->drvr.free = xhci_free;
|
|
rhport->drvr.ioalloc = xhci_ioalloc;
|
|
rhport->drvr.iofree = xhci_iofree;
|
|
rhport->drvr.ctrlin = xhci_ctrlin;
|
|
rhport->drvr.ctrlout = xhci_ctrlout;
|
|
rhport->drvr.transfer = xhci_transfer;
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
rhport->drvr.asynch = xhci_asynch;
|
|
#endif
|
|
rhport->drvr.cancel = xhci_cancel;
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
rhport->drvr.connect = xhci_connect;
|
|
#endif
|
|
rhport->drvr.disconnect = xhci_disconnect;
|
|
rhport->hport.pdevgen = &priv->devgen;
|
|
|
|
/* Initialize EP0 */
|
|
|
|
rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL;
|
|
rhport->ep0.epno = 0;
|
|
rhport->ep0.devaddr = 0;
|
|
nxsem_init(&rhport->ep0.iocsem, 0, 0);
|
|
rhport->hport.bus = priv->bus;
|
|
nxmutex_init(&rhport->ep0.lock);
|
|
|
|
/* Initialize the public port representation */
|
|
|
|
hport = &rhport->hport.hport;
|
|
hport->drvr = &rhport->drvr;
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
hport->parent = NULL;
|
|
#endif
|
|
hport->ep0 = &rhport->ep0;
|
|
hport->port = i;
|
|
hport->speed = USB_SPEED_FULL;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_initialize
|
|
*
|
|
* Description:
|
|
* Bring up an xHCI controller and start watching its root hub ports.
|
|
*
|
|
* The caller has already found the controller and knows how to reach it;
|
|
* everything this function does from here on is described by the xHCI
|
|
* specification and is the same on any bus.
|
|
*
|
|
* Input Parameters:
|
|
* base - Where the controller's register block starts. The capability
|
|
* registers are at the beginning of it and say where the rest are.
|
|
* ops - How to reach the interrupt this controller raises
|
|
* arg - Opaque value passed back to ops
|
|
*
|
|
* Returned Value:
|
|
* A connection to hand to usbhost_waiter_initialize() or to drive
|
|
* directly; NULL on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct usbhost_connection_s *
|
|
xhci_initialize(FAR const char *name, uint8_t bus, uintptr_t base,
|
|
FAR const struct xhci_bus_ops_s *ops, FAR void *arg)
|
|
{
|
|
FAR struct usbhost_conn_xhci_s *conn = NULL;
|
|
FAR struct usbhost_xhci_s *priv = NULL;
|
|
uint32_t regval;
|
|
int ret;
|
|
|
|
DEBUGASSERT(name != NULL && base != 0 && ops != NULL &&
|
|
ops->irq_attach != NULL);
|
|
|
|
conn = kmm_zalloc(sizeof(struct usbhost_conn_xhci_s));
|
|
if (conn == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
priv = kmm_zalloc(sizeof(struct usbhost_xhci_s));
|
|
if (priv == NULL)
|
|
{
|
|
kmm_free(conn);
|
|
return NULL;
|
|
}
|
|
|
|
conn->conn.wait = xhci_wait;
|
|
conn->conn.enumerate = xhci_enumerate;
|
|
conn->priv = priv;
|
|
|
|
priv->name = name;
|
|
|
|
/* The bus is what the controller calls itself, so that a port reported
|
|
* through the generic host stack and a message from this driver name the
|
|
* same thing. Numbering them here instead would agree only by accident.
|
|
*/
|
|
|
|
priv->bus = bus;
|
|
priv->ops = ops;
|
|
priv->arg = arg;
|
|
priv->base = base;
|
|
|
|
/* The capability registers are the only ones at a known offset. Every
|
|
* other block is where they say it is.
|
|
*/
|
|
|
|
priv->capa_base = priv->base;
|
|
priv->oper_base = priv->base + xhci_capa_getreg_1b(priv, XHCI_CAPLENGTH);
|
|
priv->runt_base = priv->base + xhci_capa_getreg(priv, XHCI_RTSOFF);
|
|
priv->door_base = priv->base + xhci_capa_getreg(priv, XHCI_DBOFF);
|
|
|
|
usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0);
|
|
|
|
ret = xhci_hw_initialize(priv);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to initialize HW: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
ret = xhci_sw_initialize(priv);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to initialize SW: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
ret = xhci_ctrl_start(priv);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(XHCI_TRACE1_START_FAILED, 0);
|
|
goto errout;
|
|
}
|
|
|
|
/* Take the interrupt only now.
|
|
*
|
|
* The handler defers to a worker that walks the event ring, and the ring
|
|
* does not exist until the controller has been started. A controller
|
|
* left running by a boot loader can have an interrupt pending the moment
|
|
* the line is enabled, so attaching any earlier is a race with nothing
|
|
* to answer it.
|
|
*/
|
|
|
|
ret = xhci_irq_initialize(priv);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to attach interrupt: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Acknowledge anything the controller raised before the handler was
|
|
* attached. A message is sent once, on the transition, so a bit set in
|
|
* that window would never produce another. Clear them, so the next
|
|
* event is a fresh assertion.
|
|
*/
|
|
|
|
regval = xhci_oper_getreg(priv, XHCI_USBSTS);
|
|
xhci_oper_putreg(priv, XHCI_USBSTS, regval);
|
|
|
|
regval = xhci_runt_getreg(priv, XHCI_IMAN(0));
|
|
xhci_runt_putreg(priv, XHCI_IMAN(0), regval | XHCI_IMAN_IP);
|
|
|
|
#ifdef CONFIG_DEBUG_USB_INFO
|
|
xhci_dump_mem(priv, "after init");
|
|
#endif
|
|
|
|
/* A device already in a port at power up produces no status change
|
|
* interrupt to tell us it is there, so look for one.
|
|
*/
|
|
|
|
xhci_probe_ports(priv);
|
|
|
|
ret = usbhost_waiter_initialize(&conn->conn);
|
|
if (ret < 0)
|
|
{
|
|
uerr("failed to initialize waiter: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
conn->pid = ret;
|
|
|
|
return &conn->conn;
|
|
|
|
errout:
|
|
xhci_mem_free(priv);
|
|
kmm_free(conn);
|
|
kmm_free(priv);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xhci_uninitialize
|
|
*
|
|
* Description:
|
|
* Stop watching a controller's ports and give back everything
|
|
* xhci_initialize() took.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xhci_uninitialize(FAR struct usbhost_connection_s *conn)
|
|
{
|
|
FAR struct usbhost_conn_xhci_s *xconn = XHCI_XCONN_FROM_CONN(conn);
|
|
FAR struct usbhost_xhci_s *priv;
|
|
|
|
DEBUGASSERT(conn != NULL);
|
|
priv = xconn->priv;
|
|
|
|
/* Stop the waiter before the memory it walks goes away */
|
|
|
|
kthread_delete(xconn->pid);
|
|
|
|
priv->ops->irq_detach(priv->arg);
|
|
|
|
xhci_mem_free(priv);
|
|
|
|
kmm_free(priv);
|
|
kmm_free(xconn);
|
|
}
|