mirror of
https://gitee.com/Vancouver2017/luban-lite-t3e-pro.git
synced 2025-12-14 18:38:55 +00:00
2632 lines
68 KiB
C
2632 lines
68 KiB
C
/*
|
||
* Copyright : (C) 2022 Phytium Information Technology, Inc.
|
||
* All Rights Reserved.
|
||
*
|
||
* This program is OPEN SOURCE software: you can redistribute it and/or modify it
|
||
* under the terms of the Phytium Public License as published by the Phytium Technology Co.,Ltd,
|
||
* either version 1.0 of the License, or (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY;
|
||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
* See the Phytium Public License for more details.
|
||
*
|
||
*
|
||
* FilePath: xhci.c
|
||
* Date: 2022-07-19 09:26:25
|
||
* LastEditTime: 2022-07-19 09:26:25
|
||
* Description: This file is for xhci functions implmentation.
|
||
*
|
||
* Modify History:
|
||
* Ver Who Date Changes
|
||
* ----- ------ -------- --------------------------------------
|
||
* 1.0 zhugengyu 2022/9/19 init commit
|
||
* 2.0 zhugengyu 2023/3/29 support usb3.0 device attached at roothub
|
||
*/
|
||
|
||
#include "usbh_core.h"
|
||
#include "usbh_hub.h"
|
||
|
||
#include "xhci_reg.h"
|
||
#include "xhci.h"
|
||
|
||
extern struct usbh_hubport *usbh_get_roothub_port(unsigned int port);
|
||
|
||
/**
|
||
* Get USB transaction translator
|
||
*
|
||
* @v hport Hub port of USB device
|
||
* @ret port Transaction translator port, or NULL
|
||
*/
|
||
struct usbh_hubport *usbh_transaction_translator(struct usbh_hubport *hport)
|
||
{
|
||
struct usbh_hubport *parent;
|
||
|
||
if (hport->parent->is_roothub) {
|
||
return NULL;
|
||
}
|
||
|
||
/* Navigate up to root hub. If we find a low-speed or
|
||
* full-speed device with a higher-speed parent hub, then that
|
||
* device's port is the transaction translator.
|
||
*/
|
||
for (; (parent = hport->parent->parent); hport = parent) {
|
||
if ((hport->speed <= USB_SPEED_FULL) &&
|
||
(parent->speed > USB_SPEED_FULL)) {
|
||
return hport;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* Get USB route string
|
||
*
|
||
* @v hport Hub Port of USB device
|
||
* @ret route USB route string
|
||
*/
|
||
unsigned int usbh_route_string(struct usbh_hubport *hport)
|
||
{
|
||
struct usbh_hubport *parent;
|
||
unsigned int route;
|
||
|
||
/* Navigate up to root hub, constructing route string as we go */
|
||
for (route = 0; (parent = hport->parent->parent); hport = parent) {
|
||
route <<= 4;
|
||
route |= ((hport->dev_addr > 0xf) ?
|
||
0xf :
|
||
hport->dev_addr);
|
||
}
|
||
|
||
return route;
|
||
}
|
||
|
||
/**
|
||
* Get USB root hub port
|
||
*
|
||
* @v usb Hub port of USB device
|
||
* @ret port Root hub port
|
||
*/
|
||
struct usbh_hubport *usbh_root_hub_port(struct usbh_hubport *hport)
|
||
{
|
||
struct usbh_hubport *parent;
|
||
|
||
/* Navigate up to root hub */
|
||
while (parent = hport->parent->parent) {
|
||
hport = parent;
|
||
}
|
||
|
||
return hport;
|
||
}
|
||
|
||
/** @file
|
||
*
|
||
* USB eXtensible Host Controller Interface (xHCI) driver
|
||
*
|
||
*/
|
||
|
||
#define XHCI_DUMP 1
|
||
|
||
/** Define a XHCI speed in PSI
|
||
*
|
||
* @v mantissa Mantissa
|
||
* @v exponent Exponent (in engineering terms: 1=k, 2=M, 3=G)
|
||
* @ret speed USB speed
|
||
*/
|
||
#define XCHI_PSI( mantissa, exponent ) ( (exponent << 16) | (mantissa) )
|
||
|
||
/** USB device speeds */
|
||
enum {
|
||
/** Not connected */
|
||
XCHI_PSI_NONE = 0,
|
||
/** Low speed (1.5Mbps) */
|
||
XCHI_PSI_LOW = XCHI_PSI ( 1500, 1 ),
|
||
/** Full speed (12Mbps) */
|
||
XCHI_PSI_FULL = XCHI_PSI ( 12, 2 ),
|
||
/** High speed (480Mbps) */
|
||
XCHI_PSI_HIGH = XCHI_PSI ( 480, 2 ),
|
||
/** Super speed (5Gbps) */
|
||
XCHI_PSI_SUPER = XCHI_PSI ( 5, 3 ),
|
||
};
|
||
|
||
|
||
/**
|
||
* Calculate buffer alignment
|
||
*
|
||
* @v len Length
|
||
* @ret align Buffer alignment
|
||
*
|
||
* Determine alignment required for a buffer which must be aligned to
|
||
* at least XHCI_MIN_ALIGN and which must not cross a page boundary.
|
||
*/
|
||
static inline size_t xhci_align ( size_t len ) {
|
||
size_t align;
|
||
|
||
/* Align to own length (rounded up to a power of two) */
|
||
align = ( 1 << fls ( len - 1 ) );
|
||
|
||
/* Round up to XHCI_MIN_ALIGN if needed */
|
||
if ( align < XHCI_MIN_ALIGN )
|
||
align = XHCI_MIN_ALIGN;
|
||
|
||
return align;
|
||
}
|
||
|
||
/**
|
||
* Write potentially 64-bit register
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v value Value
|
||
* @v reg Register address
|
||
* @ret rc Return status code
|
||
*/
|
||
static inline int xhci_writeq ( struct xhci_host *xhci, uintptr_t value, void *reg ) {
|
||
|
||
/* If this is a 32-bit build, then this can never fail
|
||
* (allowing the compiler to optimise out the error path).
|
||
*/
|
||
if ( sizeof ( value ) <= sizeof ( uint32_t ) ) {
|
||
writel ( value, reg );
|
||
writel ( 0, ( reg + sizeof ( uint32_t ) ) );
|
||
return 0;
|
||
}
|
||
|
||
/* If the device does not support 64-bit addresses and this
|
||
* address is outside the 32-bit address space, then fail.
|
||
*/
|
||
if ( ( value & ~0xffffffffULL ) && ! xhci->addr64 ) {
|
||
USB_LOG_DBG("XHCI %s cannot access address %lx\n",
|
||
xhci->name, value );
|
||
return -ENOTSUP;
|
||
}
|
||
|
||
/* If this is a 64-bit build, then writeq() is available */
|
||
writeq ( value, reg );
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Initialise device
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v regs MMIO registers
|
||
*/
|
||
static void xhci_init ( struct xhci_host *xhci, void *regs ) {
|
||
uint32_t hcsparams1;
|
||
uint32_t hcsparams2;
|
||
uint32_t hccparams1;
|
||
uint32_t pagesize;
|
||
size_t caplength;
|
||
size_t rtsoff;
|
||
size_t dboff;
|
||
|
||
/* Locate capability, operational, runtime, and doorbell registers */
|
||
xhci->cap = regs;
|
||
caplength = readb ( xhci->cap + XHCI_CAP_CAPLENGTH );
|
||
rtsoff = readl ( xhci->cap + XHCI_CAP_RTSOFF );
|
||
dboff = readl ( xhci->cap + XHCI_CAP_DBOFF );
|
||
xhci->op = ( xhci->cap + caplength );
|
||
xhci->run = ( xhci->cap + rtsoff );
|
||
xhci->db = ( xhci->cap + dboff );
|
||
|
||
/* avoid access XHCI_REG_CAP_HCIVERSION = 0x2, unaligned memory */
|
||
xhci->version = ((readl ( xhci->cap + XHCI_CAP_CAPLENGTH ) >> 16) & 0xffff);
|
||
|
||
USB_LOG_DBG("XHCI %s version %x cap %08lx op %08lx run %08lx db %08lx\n",
|
||
xhci->name, xhci->version, ( xhci->cap ),
|
||
( xhci->op ), ( xhci->run ),
|
||
( xhci->db ) );
|
||
|
||
if (xhci->version < 0x96 || xhci->version > 0x120) {
|
||
USB_LOG_WRN("XHCI %s not support.\n", xhci->name);
|
||
}
|
||
|
||
/* Read structural parameters 1 */
|
||
hcsparams1 = readl ( xhci->cap + XHCI_CAP_HCSPARAMS1 );
|
||
xhci->slots = XHCI_HCSPARAMS1_SLOTS ( hcsparams1 );
|
||
xhci->intrs = XHCI_HCSPARAMS1_INTRS ( hcsparams1 );
|
||
xhci->ports = XHCI_HCSPARAMS1_PORTS ( hcsparams1 );
|
||
USB_LOG_DBG("XHCI %s has %d slots %d intrs %d ports\n",
|
||
xhci->name, xhci->slots, xhci->intrs, xhci->ports );
|
||
|
||
/* Read structural parameters 2 */
|
||
hcsparams2 = readl ( xhci->cap + XHCI_CAP_HCSPARAMS2 );
|
||
xhci->scratch.count = XHCI_HCSPARAMS2_SCRATCHPADS ( hcsparams2 );
|
||
USB_LOG_DBG("XHCI %s needs %d scratchpads\n",
|
||
xhci->name, xhci->scratch.count );
|
||
|
||
/* Read capability parameters 1 */
|
||
hccparams1 = readl ( xhci->cap + XHCI_CAP_HCCPARAMS1 );
|
||
xhci->addr64 = XHCI_HCCPARAMS1_ADDR64 ( hccparams1 );
|
||
xhci->csz_shift = XHCI_HCCPARAMS1_CSZ_SHIFT ( hccparams1 );
|
||
xhci->xecp = (XHCI_HCCPARAMS1_XECP ( hccparams1 ));
|
||
USB_LOG_DBG("XHCI %s context %d bit\n",
|
||
xhci->name, (xhci->addr64 ? 64 : 32) );
|
||
|
||
/* Read page size */
|
||
pagesize = readl ( xhci->op + XHCI_OP_PAGESIZE );
|
||
xhci->pagesize = XHCI_PAGESIZE ( pagesize );
|
||
USB_ASSERT ( xhci->pagesize != 0 );
|
||
USB_ASSERT ( ( ( xhci->pagesize ) & ( xhci->pagesize - 1 ) ) == 0 );
|
||
USB_LOG_DBG("XHCI %s page size %zd bytes\n",
|
||
xhci->name, xhci->pagesize );
|
||
}
|
||
|
||
/**
|
||
* Find extended capability
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v id Capability ID
|
||
* @v offset Offset to previous extended capability instance, or zero
|
||
* @ret offset Offset to extended capability, or zero if not found
|
||
*/
|
||
static unsigned int xhci_extended_capability ( struct xhci_host *xhci,
|
||
unsigned int id,
|
||
unsigned int offset ) {
|
||
uint32_t xecp;
|
||
unsigned int next;
|
||
|
||
/* Locate the extended capability */
|
||
while ( 1 ) {
|
||
|
||
/* Locate first or next capability as applicable */
|
||
if ( offset ) {
|
||
xecp = readl ( xhci->cap + offset );
|
||
next = XHCI_XECP_NEXT ( xecp );
|
||
} else {
|
||
next = xhci->xecp;
|
||
}
|
||
if ( ! next )
|
||
return 0;
|
||
offset += next;
|
||
|
||
/* Check if this is the requested capability */
|
||
xecp = readl ( xhci->cap + offset );
|
||
if ( XHCI_XECP_ID ( xecp ) == id )
|
||
return offset;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Initialise USB legacy support
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_legacy_init ( struct xhci_host *xhci ) {
|
||
unsigned int legacy;
|
||
uint8_t bios;
|
||
|
||
/* Locate USB legacy support capability (if present) */
|
||
legacy = xhci_extended_capability ( xhci, XHCI_XECP_ID_LEGACY, 0 );
|
||
if ( ! legacy ) {
|
||
/* Not an error; capability may not be present */
|
||
USB_LOG_DBG("XHCI %s has no USB legacy support capability\n",
|
||
xhci->name );
|
||
return;
|
||
}
|
||
|
||
/* Check if legacy USB support is enabled */
|
||
USB_LOG_DBG("XHCI %s bios offset 0x%x\n", xhci->name, (xhci->cap + legacy + XHCI_USBLEGSUP_BIOS));
|
||
/* bios = readb ( xhci->cap + legacy + XHCI_USBLEGSUP_BIOS ); cannot access offset 0x2, work around */
|
||
bios = readl ( xhci->cap + legacy );
|
||
bios = (bios >> 16) & 0xffff;
|
||
if ( ! ( bios & XHCI_USBLEGSUP_BIOS_OWNED ) ) {
|
||
/* Not an error; already owned by OS */
|
||
USB_LOG_DBG("XHCI %s USB legacy support already disabled\n",
|
||
xhci->name );
|
||
return;
|
||
}
|
||
|
||
/* Record presence of USB legacy support capability */
|
||
xhci->legacy = legacy;
|
||
}
|
||
|
||
/**
|
||
* Claim ownership from BIOS
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_legacy_claim ( struct xhci_host *xhci ) {
|
||
uint32_t ctlsts;
|
||
uint8_t bios;
|
||
unsigned int i;
|
||
|
||
/* Do nothing unless legacy support capability is present */
|
||
if ( ! xhci->legacy )
|
||
return;
|
||
|
||
/* Claim ownership */
|
||
writeb ( XHCI_USBLEGSUP_OS_OWNED,
|
||
xhci->cap + xhci->legacy + XHCI_USBLEGSUP_OS );
|
||
|
||
/* Wait for BIOS to release ownership */
|
||
for ( i = 0 ; i < XHCI_USBLEGSUP_MAX_WAIT_MS ; i++ ) {
|
||
|
||
/* Check if BIOS has released ownership */
|
||
bios = readb ( xhci->cap + xhci->legacy + XHCI_USBLEGSUP_BIOS );
|
||
if ( ! ( bios & XHCI_USBLEGSUP_BIOS_OWNED ) ) {
|
||
USB_LOG_DBG("XHCI %s claimed ownership from BIOS\n",
|
||
xhci->name );
|
||
ctlsts = readl ( xhci->cap + xhci->legacy +
|
||
XHCI_USBLEGSUP_CTLSTS );
|
||
if ( ctlsts ) {
|
||
USB_LOG_DBG("XHCI %s warning: BIOS retained "
|
||
"SMIs: %08x\n", xhci->name, ctlsts );
|
||
}
|
||
return;
|
||
}
|
||
|
||
/* Delay */
|
||
usb_osal_msleep ( 1 );
|
||
}
|
||
|
||
/* BIOS did not release ownership. Claim it forcibly by
|
||
* disabling all SMIs.
|
||
*/
|
||
USB_LOG_DBG("XHCI %s could not claim ownership from BIOS: forcibly "
|
||
"disabling SMIs\n", xhci->name );
|
||
writel ( 0, xhci->cap + xhci->legacy + XHCI_USBLEGSUP_CTLSTS );
|
||
}
|
||
|
||
/** Prevent the release of ownership back to BIOS */
|
||
static int xhci_legacy_prevent_release;
|
||
|
||
/**
|
||
* Release ownership back to BIOS
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_legacy_release ( struct xhci_host *xhci ) {
|
||
|
||
/* Do nothing unless legacy support capability is present */
|
||
if ( ! xhci->legacy )
|
||
return;
|
||
|
||
/* Do nothing if releasing ownership is prevented */
|
||
if ( xhci_legacy_prevent_release ) {
|
||
USB_LOG_DBG("XHCI %s not releasing ownership to BIOS\n",
|
||
xhci->name );
|
||
return;
|
||
}
|
||
|
||
/* Release ownership */
|
||
writeb ( 0, xhci->cap + xhci->legacy + XHCI_USBLEGSUP_OS );
|
||
USB_LOG_DBG("XHCI %s released ownership to BIOS\n", xhci->name );
|
||
}
|
||
|
||
/**
|
||
* Stop xHCI device
|
||
*
|
||
* @v xhci xHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_stop ( struct xhci_host *xhci ) {
|
||
uint32_t usbcmd;
|
||
uint32_t usbsts;
|
||
unsigned int i;
|
||
|
||
/* Clear run/stop bit */
|
||
usbcmd = readl ( xhci->op + XHCI_OP_USBCMD );
|
||
usbcmd &= ~XHCI_USBCMD_RUN;
|
||
writel ( usbcmd, xhci->op + XHCI_OP_USBCMD );
|
||
|
||
/* Wait for device to stop */
|
||
for ( i = 0 ; i < XHCI_STOP_MAX_WAIT_MS ; i++ ) {
|
||
|
||
/* Check if device is stopped */
|
||
usbsts = readl ( xhci->op + XHCI_OP_USBSTS );
|
||
if ( usbsts & XHCI_USBSTS_HCH )
|
||
return 0;
|
||
|
||
/* Delay */
|
||
usb_osal_msleep ( 1 );
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s timed out waiting for stop\n", xhci->name );
|
||
return -ETIMEDOUT;
|
||
}
|
||
|
||
/**
|
||
* Reset xHCI device
|
||
*
|
||
* @v xhci xHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_reset ( struct xhci_host *xhci ) {
|
||
uint32_t usbcmd;
|
||
unsigned int i;
|
||
int rc;
|
||
|
||
/* The xHCI specification states that resetting a running
|
||
* device may result in undefined behaviour, so try stopping
|
||
* it first.
|
||
*/
|
||
if ( ( rc = xhci_stop ( xhci ) ) != 0 ) {
|
||
/* Ignore errors and attempt to reset the device anyway */
|
||
}
|
||
|
||
/* Reset device */
|
||
writel ( XHCI_USBCMD_HCRST, xhci->op + XHCI_OP_USBCMD );
|
||
|
||
/* Wait for reset to complete */
|
||
for ( i = 0 ; i < XHCI_RESET_MAX_WAIT_MS ; i++ ) {
|
||
|
||
/* Check if reset is complete */
|
||
usbcmd = readl ( xhci->op + XHCI_OP_USBCMD );
|
||
if ( ! ( usbcmd & XHCI_USBCMD_HCRST ) )
|
||
return 0;
|
||
|
||
/* Delay */
|
||
usb_osal_msleep ( 1 );
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s timed out waiting for reset\n", xhci->name );
|
||
return -ETIMEDOUT;
|
||
}
|
||
|
||
|
||
/**
|
||
* Find supported protocol extended capability for a port
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v port Port number
|
||
* @ret supported Offset to extended capability, or zero if not found
|
||
*/
|
||
static unsigned int xhci_supported_protocol ( struct xhci_host *xhci,
|
||
unsigned int port ) {
|
||
unsigned int supported = 0;
|
||
unsigned int offset;
|
||
unsigned int count;
|
||
uint32_t ports;
|
||
|
||
/* Iterate over all supported protocol structures */
|
||
while ( ( supported = xhci_extended_capability ( xhci,
|
||
XHCI_XECP_ID_SUPPORTED,
|
||
supported ) ) ) {
|
||
|
||
/* Determine port range */
|
||
ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS );
|
||
offset = XHCI_SUPPORTED_PORTS_OFFSET ( ports );
|
||
count = XHCI_SUPPORTED_PORTS_COUNT ( ports );
|
||
|
||
/* Check if port lies within this range */
|
||
if ( ( port - offset ) < count )
|
||
return supported;
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s Port-%d has no supported protocol\n",
|
||
xhci->name, port );
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Transcribe port speed (for debugging)
|
||
*
|
||
* @v psi Protocol speed ID
|
||
* @ret speed Transcribed speed
|
||
*/
|
||
static inline const char * xhci_speed_name ( uint32_t psi ) {
|
||
static const char *exponents[4] = { "", "k", "M", "G" };
|
||
static char buf[ 10 /* "xxxxxXbps" + NUL */ ];
|
||
unsigned int mantissa;
|
||
unsigned int exponent;
|
||
|
||
/* Extract mantissa and exponent */
|
||
mantissa = XHCI_SUPPORTED_PSI_MANTISSA ( psi );
|
||
exponent = XHCI_SUPPORTED_PSI_EXPONENT ( psi );
|
||
|
||
/* Transcribe speed */
|
||
snprintf ( buf, sizeof ( buf ), "%d%sbps",
|
||
mantissa, exponents[exponent] );
|
||
return buf;
|
||
}
|
||
|
||
/**
|
||
* Find port protocol
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v port Port number
|
||
* @ret protocol USB protocol, or zero if not found
|
||
*/
|
||
static unsigned int xhci_port_protocol ( struct xhci_host *xhci,
|
||
unsigned int port ) {
|
||
unsigned int supported = xhci_supported_protocol ( xhci, port );
|
||
union {
|
||
uint32_t raw;
|
||
char text[5];
|
||
} name;
|
||
unsigned int protocol;
|
||
unsigned int type;
|
||
unsigned int psic;
|
||
unsigned int psiv;
|
||
unsigned int i;
|
||
uint32_t revision;
|
||
uint32_t ports;
|
||
uint32_t slot;
|
||
uint32_t psi;
|
||
|
||
/* Fail if there is no supported protocol */
|
||
if ( ! supported )
|
||
return 0;
|
||
|
||
/* Determine protocol version */
|
||
revision = readl ( xhci->cap + supported + XHCI_SUPPORTED_REVISION );
|
||
protocol = XHCI_SUPPORTED_REVISION_VER ( revision );
|
||
|
||
/* Describe port protocol */
|
||
#if XHCI_DUMP
|
||
{
|
||
name.raw = CPU_TO_LE32 ( readl ( xhci->cap + supported +
|
||
XHCI_SUPPORTED_NAME ) );
|
||
name.text[4] = '\0';
|
||
slot = readl ( xhci->cap + supported + XHCI_SUPPORTED_SLOT );
|
||
type = XHCI_SUPPORTED_SLOT_TYPE ( slot );
|
||
USB_LOG_DBG("XHCI %s-%d %sv%04x type %d \r\n",
|
||
xhci->name, port, name.text, protocol, type );
|
||
ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS );
|
||
psic = XHCI_SUPPORTED_PORTS_PSIC ( ports );
|
||
if ( psic ) {
|
||
USB_LOG_DBG(" speeds \r\n" );
|
||
for ( i = 0 ; i < psic ; i++ ) {
|
||
psi = readl ( xhci->cap + supported +
|
||
XHCI_SUPPORTED_PSI ( i ) );
|
||
psiv = XHCI_SUPPORTED_PSI_VALUE ( psi );
|
||
USB_LOG_DBG(" %d:%s \r\n", psiv, xhci_speed_name ( psi ) );
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
return protocol;
|
||
}
|
||
|
||
/**
|
||
* Probe XCHI device
|
||
*
|
||
* @v xhci XCHI device
|
||
* @v baseaddr XHCI device register base address
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_probe ( struct xhci_host *xhci, unsigned long baseaddr ) {
|
||
USB_ASSERT(xhci);
|
||
int error = 0;
|
||
struct usbh_hubport *port;
|
||
unsigned int i;
|
||
|
||
xhci->base = (void *)baseaddr;
|
||
xhci->name[0] = '0' + xhci->id; /* Assert there are less than 10 xhci host */
|
||
xhci->name[1] = '\0';
|
||
|
||
/* Initialise xHCI device */
|
||
xhci_init ( xhci, xhci->base );
|
||
|
||
/* Initialise USB legacy support and claim ownership */
|
||
xhci_legacy_init ( xhci );
|
||
xhci_legacy_claim ( xhci );
|
||
|
||
/* Reset device */
|
||
if ( ( error = xhci_reset ( xhci ) ) != 0 )
|
||
goto err_reset;
|
||
|
||
/* Set port protocols */
|
||
for ( i = 1 ; i <= xhci->ports ; i++ ) {
|
||
port = usbh_get_roothub_port ( i );
|
||
port->protocol = xhci_port_protocol ( xhci, i );
|
||
}
|
||
|
||
return error;
|
||
|
||
err_reset:
|
||
xhci_legacy_release ( xhci );
|
||
return -1;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Allocate device context base address array
|
||
*
|
||
* @v xhci xHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_dcbaa_alloc ( struct xhci_host *xhci ) {
|
||
size_t len;
|
||
uintptr_t dcbaap;
|
||
int rc;
|
||
|
||
/* Allocate and initialise structure. Must be at least
|
||
* 64-byte aligned and must not cross a page boundary, so
|
||
* align on its own size (rounded up to a power of two and
|
||
* with a minimum of 64 bytes).
|
||
*/
|
||
len = ( ( xhci->slots + 1 ) * sizeof ( xhci->dcbaa.context[0] ) );
|
||
xhci->dcbaa.context = usb_align(xhci_align ( len ), len);
|
||
if ( ! xhci->dcbaa.context ) {
|
||
USB_LOG_ERR("XHCI %s could not allocate DCBAA\n", xhci->name );
|
||
rc = -ENOMEM;
|
||
goto err_alloc;
|
||
}
|
||
memset ( xhci->dcbaa.context, 0, len );
|
||
|
||
/* Program DCBAA pointer */
|
||
dcbaap = (uintptr_t)( xhci->dcbaa.context );
|
||
if ( ( rc = xhci_writeq ( xhci, dcbaap,
|
||
xhci->op + XHCI_OP_DCBAAP ) ) != 0 )
|
||
goto err_writeq;
|
||
|
||
USB_LOG_DBG("XHCI %s DCBAA at [%08lx,%08lx)\n", xhci->name,
|
||
( xhci->dcbaa.context ),
|
||
( ( xhci->dcbaa.context ) + len ) );
|
||
return 0;
|
||
|
||
err_writeq:
|
||
usb_free ( xhci->dcbaa.context );
|
||
err_alloc:
|
||
return rc;
|
||
}
|
||
|
||
|
||
/**
|
||
* Allocate scratchpad buffers
|
||
*
|
||
* @v xhci xHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_scratchpad_alloc ( struct xhci_host *xhci ) {
|
||
struct xhci_scratchpad *scratch = &xhci->scratch;
|
||
size_t buffer_len;
|
||
size_t array_len;
|
||
uintptr_t addr;
|
||
unsigned int i;
|
||
int rc;
|
||
|
||
/* Do nothing if no scratchpad buffers are used */
|
||
if ( ! scratch->count ) {
|
||
USB_LOG_INFO("XHCI %s no need to allocate scratchpad buffers\n",
|
||
xhci->name );
|
||
return 0;
|
||
}
|
||
|
||
/* Allocate scratchpad buffers */
|
||
buffer_len = ( scratch->count * xhci->pagesize );
|
||
scratch->buffer = (uintptr_t)usb_align ( xhci->pagesize, buffer_len );
|
||
if ( ! scratch->buffer ) {
|
||
USB_LOG_ERR("XHCI %s could not allocate scratchpad buffers\n",
|
||
xhci->name );
|
||
rc = -ENOMEM;
|
||
goto err_alloc;
|
||
}
|
||
memset ( (void *)scratch->buffer, 0, buffer_len );
|
||
|
||
/* Allocate scratchpad array */
|
||
array_len = ( scratch->count * sizeof ( scratch->array[0] ) );
|
||
scratch->array = usb_align(xhci_align ( array_len ), array_len);
|
||
if ( ! scratch->array ) {
|
||
USB_LOG_ERR("XHCI %s could not allocate scratchpad buffer "
|
||
"array\n", xhci->name );
|
||
rc = -ENOMEM;
|
||
goto err_alloc_array;
|
||
}
|
||
|
||
/* Populate scratchpad array */
|
||
addr = ( scratch->buffer + 0 );
|
||
for ( i = 0 ; i < scratch->count ; i++ ) {
|
||
scratch->array[i] = CPU_TO_LE64 ( addr );
|
||
addr += xhci->pagesize;
|
||
}
|
||
|
||
/* Set scratchpad array pointer */
|
||
USB_ASSERT ( xhci->dcbaa.context != NULL );
|
||
xhci->dcbaa.context[0] = CPU_TO_LE64 ( ( scratch->array ) );
|
||
|
||
USB_LOG_DBG("XHCI %s scratchpad [%08lx,%08lx) array [%08lx,%08lx)\n",
|
||
xhci->name, ( scratch->buffer, 0 ),
|
||
( scratch->buffer, buffer_len ),
|
||
( scratch->array ),
|
||
( ( scratch->array ) + array_len ) );
|
||
return 0;
|
||
|
||
usb_free ( scratch->array );
|
||
err_alloc_array:
|
||
usb_free ( scratch->buffer );
|
||
err_alloc:
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Allocate command ring
|
||
*
|
||
* @v xhci xHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_command_alloc ( struct xhci_host *xhci ) {
|
||
uintptr_t crp;
|
||
int rc;
|
||
|
||
/* Allocate TRB ring */
|
||
xhci->cmds = usb_align(XHCI_RING_SIZE, sizeof(*xhci->cmds)); /* command ring */
|
||
if (! xhci->cmds)
|
||
goto err_ring_alloc;
|
||
|
||
memset(xhci->cmds, 0U, sizeof(*xhci->cmds));
|
||
|
||
xhci->cmds->lock = usb_osal_mutex_create();
|
||
USB_ASSERT(xhci->cmds->lock);
|
||
|
||
xhci->cmds->cs = 1; /* cycle state = 1 */
|
||
|
||
/* Program command ring control register */
|
||
crp = (uintptr_t)( xhci->cmds );
|
||
if ( ( rc = xhci_writeq ( xhci, ( crp | XHCI_CRCR_RCS ),
|
||
xhci->op + XHCI_OP_CRCR ) ) != 0 )
|
||
goto err_writeq;
|
||
|
||
USB_LOG_DBG("XHCI %s CRCR at [%08lx,%08lx)\n", xhci->name,
|
||
( xhci->cmds->ring ),
|
||
( ( xhci->cmds->ring ) + sizeof(xhci->cmds->ring) ) );
|
||
return 0;
|
||
|
||
err_writeq:
|
||
usb_free(xhci->cmds);;
|
||
err_ring_alloc:
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Allocate event ring
|
||
*
|
||
* @v xhci xHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_event_alloc ( struct xhci_host *xhci ) {
|
||
unsigned int count;
|
||
size_t len;
|
||
int rc;
|
||
|
||
/* Allocate event ring */
|
||
xhci->evts = usb_align(XHCI_RING_SIZE, sizeof(*xhci->evts)); /* event ring */
|
||
if ( ! xhci->evts ) {
|
||
rc = -ENOMEM;
|
||
goto err_alloc_trb;
|
||
}
|
||
|
||
memset(xhci->evts, 0U, sizeof(*xhci->evts));
|
||
|
||
/* Allocate event ring segment table */
|
||
xhci->eseg = usb_align(XHCI_ALIGMENT, sizeof(*xhci->eseg)); /* event segment */
|
||
if ( ! xhci->eseg ) {
|
||
rc = -ENOMEM;
|
||
goto err_alloc_segment;
|
||
}
|
||
memset(xhci->eseg, 0U, sizeof(*xhci->eseg));
|
||
xhci->eseg->base = CPU_TO_LE64 ( ( xhci->evts ) );
|
||
xhci->eseg->count = XHCI_RING_ITEMS; /* items of event ring TRB */
|
||
|
||
/* Program event ring registers */
|
||
writel ( 1, xhci->run + XHCI_RUN_ERSTSZ ( 0 ) ); /* bit[15:0] event ring segment table size */
|
||
if ( ( rc = xhci_writeq ( xhci, (uintptr_t)( xhci->evts ),
|
||
xhci->run + XHCI_RUN_ERDP ( 0 ) ) ) != 0 ) /* bit[63:4] event ring dequeue pointer */
|
||
goto err_writeq_erdp;
|
||
if ( ( rc = xhci_writeq ( xhci, (uintptr_t)( xhci->eseg ),
|
||
xhci->run + XHCI_RUN_ERSTBA ( 0 ) ) ) != 0 ) /* bit[63:6] event ring segment table base addr */
|
||
goto err_writeq_erstba;
|
||
|
||
xhci->evts->cs = 1; /* cycle state = 1 */
|
||
USB_LOG_DBG("XHCI %s event ring [%08lx,%08lx) table [%08lx,%08lx)\n",
|
||
xhci->name, ( xhci->evts->ring ),
|
||
( ( xhci->evts->ring ) + sizeof(xhci->evts->ring) ),
|
||
( xhci->eseg ),
|
||
( ( xhci->eseg ) +
|
||
sizeof ( xhci->eseg[0] ) ) );
|
||
return 0;
|
||
|
||
xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERSTBA ( 0 ) );
|
||
err_writeq_erstba:
|
||
xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERDP ( 0 ) );
|
||
err_writeq_erdp:
|
||
usb_free ( xhci->eseg );
|
||
err_alloc_segment:
|
||
usb_free ( xhci->evts );
|
||
err_alloc_trb:
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Start xHCI device
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_run ( struct xhci_host *xhci ) {
|
||
uint32_t config;
|
||
uint32_t usbcmd;
|
||
uint32_t runtime;
|
||
|
||
/* Configure number of device slots */
|
||
config = readl ( xhci->op + XHCI_OP_CONFIG );
|
||
config &= ~XHCI_CONFIG_MAX_SLOTS_EN_MASK;
|
||
config |= XHCI_CONFIG_MAX_SLOTS_EN ( xhci->slots );
|
||
writel ( config, xhci->op + XHCI_OP_CONFIG );
|
||
|
||
/* Enable port interrupt */
|
||
writel ( 500U, xhci->run + XHCI_RUN_IR_IMOD ( 0 ) );
|
||
runtime = readl(xhci->run + XHCI_RUN_IR_IMAN ( 0 ));
|
||
runtime |= XHCI_RUN_IR_IMAN_IE;
|
||
writel (runtime, xhci->run + XHCI_RUN_IR_IMAN ( 0 ));
|
||
|
||
/* Set run/stop bit and enable interrupt */
|
||
usbcmd = readl ( xhci->op + XHCI_OP_USBCMD );
|
||
usbcmd |= XHCI_USBCMD_RUN | XHCI_USBCMD_INTE;
|
||
writel ( usbcmd, xhci->op + XHCI_OP_USBCMD );
|
||
|
||
USB_LOG_DBG("XHCI %s start running\n", xhci->name );
|
||
}
|
||
|
||
/**
|
||
* Free event ring
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_event_free ( struct xhci_host *xhci ) {
|
||
|
||
/* Clear event ring registers */
|
||
writel ( 0, xhci->run + XHCI_RUN_ERSTSZ ( 0 ) );
|
||
xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERSTBA ( 0 ) );
|
||
xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERDP ( 0 ) );
|
||
|
||
/* Free event ring segment table */
|
||
usb_free ( xhci->eseg );
|
||
|
||
/* Free event ring */
|
||
usb_free ( xhci->evts );
|
||
}
|
||
|
||
/**
|
||
* Free command ring
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_command_free ( struct xhci_host *xhci ) {
|
||
|
||
/* Sanity check */
|
||
USB_ASSERT ( ( readl ( xhci->op + XHCI_OP_CRCR ) & XHCI_CRCR_CRR ) == 0 );
|
||
|
||
/* Clear command ring control register */
|
||
xhci_writeq ( xhci, 0, xhci->op + XHCI_OP_CRCR );
|
||
|
||
/* Free TRB ring */
|
||
usb_free ( xhci->cmds );
|
||
}
|
||
|
||
/**
|
||
* Free scratchpad buffers
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_scratchpad_free ( struct xhci_host *xhci ) {
|
||
struct xhci_scratchpad *scratch = &xhci->scratch;
|
||
size_t array_len;
|
||
size_t buffer_len;
|
||
|
||
/* Do nothing if no scratchpad buffers are used */
|
||
if ( ! scratch->count )
|
||
return;
|
||
|
||
/* Clear scratchpad array pointer */
|
||
USB_ASSERT ( xhci->dcbaa.context != NULL );
|
||
xhci->dcbaa.context[0] = 0;
|
||
|
||
/* Free scratchpad array */
|
||
array_len = ( scratch->count * sizeof ( scratch->array[0] ) );
|
||
usb_free ( scratch->array );
|
||
|
||
/* Free scratchpad buffers */
|
||
buffer_len = ( scratch->count * xhci->pagesize );
|
||
usb_free ( scratch->buffer );
|
||
}
|
||
|
||
/**
|
||
* Free device context base address array
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_dcbaa_free ( struct xhci_host *xhci ) {
|
||
size_t len;
|
||
unsigned int i;
|
||
|
||
/* Sanity check */
|
||
for ( i = 0 ; i <= xhci->slots ; i++ )
|
||
USB_ASSERT ( xhci->dcbaa.context[i] == 0 );
|
||
|
||
/* Clear DCBAA pointer */
|
||
xhci_writeq ( xhci, 0, xhci->op + XHCI_OP_DCBAAP );
|
||
|
||
/* Free DCBAA */
|
||
len = ( ( xhci->slots + 1 ) * sizeof ( xhci->dcbaa.context[0] ) );
|
||
usb_free ( xhci->dcbaa.context );
|
||
}
|
||
|
||
/**
|
||
* Open XHCI device
|
||
*
|
||
* @v xhci XHCI device
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_open ( struct xhci_host *xhci ) {
|
||
int rc;
|
||
|
||
/* Allocate device slot array */
|
||
xhci->slot = usb_malloc ( ( xhci->slots + 1 ) * sizeof ( xhci->slot[0] ) );
|
||
if ( ! xhci->slot ) {
|
||
rc = -ENOMEM;
|
||
goto err_slot_alloc;
|
||
}
|
||
|
||
/* Allocate device context base address array */
|
||
if ( ( rc = xhci_dcbaa_alloc ( xhci ) ) != 0 )
|
||
goto err_dcbaa_alloc;
|
||
|
||
/* Allocate scratchpad buffers */
|
||
if ( ( rc = xhci_scratchpad_alloc ( xhci ) ) != 0 )
|
||
goto err_scratchpad_alloc;
|
||
|
||
/* Allocate command ring */
|
||
if ( ( rc = xhci_command_alloc ( xhci ) ) != 0 )
|
||
goto err_command_alloc;
|
||
|
||
/* Allocate event ring */
|
||
if ( ( rc = xhci_event_alloc ( xhci ) ) != 0 )
|
||
goto err_event_alloc;
|
||
|
||
/* Start controller */
|
||
xhci_run ( xhci );
|
||
|
||
return 0;
|
||
|
||
xhci_stop ( xhci );
|
||
xhci_event_free ( xhci );
|
||
err_event_alloc:
|
||
xhci_command_free ( xhci );
|
||
err_command_alloc:
|
||
xhci_scratchpad_free ( xhci );
|
||
err_scratchpad_alloc:
|
||
xhci_dcbaa_free ( xhci );
|
||
err_dcbaa_alloc:
|
||
usb_free ( xhci->slot );
|
||
err_slot_alloc:
|
||
return rc;
|
||
|
||
}
|
||
|
||
/**
|
||
* Close XHCI device
|
||
*
|
||
* @v xhci XHCI Device
|
||
*/
|
||
void xhci_close ( struct xhci_host *xhci ) {
|
||
unsigned int i;
|
||
|
||
/* Sanity checks */
|
||
USB_ASSERT ( xhci->slot != NULL );
|
||
for ( i = 0 ; i <= xhci->slots ; i++ )
|
||
USB_ASSERT ( xhci->slot[i] == NULL );
|
||
|
||
xhci_stop ( xhci );
|
||
usb_free (xhci->evts);
|
||
usb_free (xhci->eseg);
|
||
usb_free (xhci->cmds);
|
||
xhci_scratchpad_free ( xhci );
|
||
xhci_dcbaa_free ( xhci );
|
||
usb_free ( xhci->slot );
|
||
}
|
||
|
||
/**
|
||
* Remove XHCI device
|
||
*
|
||
* @v xhci XHCI device
|
||
*/
|
||
void xhci_remove ( struct xhci_host *xhci ) {
|
||
xhci_reset ( xhci );
|
||
xhci_legacy_release ( xhci );
|
||
usb_free ( xhci );
|
||
|
||
/* If we are shutting down to boot an OS, then prevent the
|
||
* release of ownership back to BIOS.
|
||
*/
|
||
xhci_legacy_prevent_release = 0;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Enable port
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v port Port number
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_port_enable(struct xhci_host *xhci, uint32_t port) {
|
||
uint32_t portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port ) );
|
||
|
||
/* double check if connected */
|
||
if (!(portsc & XHCI_PORTSC_CCS)) {
|
||
USB_LOG_ERR("device connectiong lost !!! \r\n");
|
||
return -ENOENT;
|
||
}
|
||
|
||
switch ( portsc & XHCI_PORTSC_PLS_MASK )
|
||
{
|
||
case XHCI_PORTSC_PLS_U0:
|
||
/* A USB3 port - controller automatically performs reset */
|
||
break;
|
||
case XHCI_PORTSC_PLS_POLLING:
|
||
/* A USB2 port - perform device reset */
|
||
xhci_dump_port_status(port, portsc);
|
||
writel ((portsc | XHCI_PORTSC_PR), (xhci->op + XHCI_OP_PORTSC ( port ))); /* reset port */
|
||
break;
|
||
default:
|
||
USB_LOG_ERR("PLS: %d \r\n", (portsc & XHCI_PORTSC_PLS_MASK));
|
||
return -ENOENT;
|
||
}
|
||
|
||
/* Wait for device to complete reset and be enabled */
|
||
uint32_t end = 1000U, start = 0U;
|
||
for (;;) {
|
||
portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port ) );
|
||
if (!(portsc & XHCI_PORTSC_CCS)) {
|
||
/* USB 2.0 port would be disconnected after reset */
|
||
USB_LOG_INFO("USB 2.0 port disconnected during reset, need rescan \r\n");
|
||
return 0;
|
||
}
|
||
|
||
if (portsc & XHCI_PORTSC_PED) { /* check if port enabled */
|
||
/* Reset complete */
|
||
break;
|
||
}
|
||
|
||
if (++start > end) {
|
||
USB_LOG_ERR("Wait timeout, portsc=0x%x !!!\n", portsc);
|
||
return -ENXIO;
|
||
}
|
||
|
||
usb_osal_msleep(1);
|
||
}
|
||
|
||
xhci_dump_port_status(port, portsc);
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Convert USB Speed to PSI
|
||
*
|
||
* @v speed USB speed
|
||
* @ret psi USB speed in PSI
|
||
*/
|
||
static unsigned int xhci_speed_to_psi(int speed) {
|
||
unsigned int psi = USB_SPEED_UNKNOWN;
|
||
|
||
switch (speed) {
|
||
case USB_SPEED_LOW:
|
||
psi = XCHI_PSI_LOW;
|
||
break;
|
||
case USB_SPEED_FULL:
|
||
psi = XCHI_PSI_FULL;
|
||
break;
|
||
case USB_SPEED_HIGH:
|
||
psi = XCHI_PSI_HIGH;
|
||
break;
|
||
case USB_SPEED_SUPER:
|
||
psi = XCHI_PSI_SUPER;
|
||
break;
|
||
}
|
||
|
||
return psi;
|
||
}
|
||
|
||
/**
|
||
* Convert USB PSI to Speed
|
||
*
|
||
* @v psi USB speed in PSI
|
||
* @ret speed USB speed
|
||
*/
|
||
static int xhci_psi_to_speed(int psi) {
|
||
int speed = USB_SPEED_UNKNOWN;
|
||
|
||
switch (psi) {
|
||
case XCHI_PSI_LOW:
|
||
speed = USB_SPEED_LOW;
|
||
break;
|
||
case XCHI_PSI_FULL:
|
||
speed = USB_SPEED_FULL;
|
||
break;
|
||
case XCHI_PSI_HIGH:
|
||
speed = USB_SPEED_HIGH;
|
||
break;
|
||
case XCHI_PSI_SUPER:
|
||
speed = USB_SPEED_SUPER;
|
||
break;
|
||
}
|
||
|
||
return speed;
|
||
}
|
||
|
||
/**
|
||
* Find port speed
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v port Port number
|
||
* @v psiv Protocol speed ID value
|
||
* @ret speed Port speed, or negative error
|
||
*/
|
||
static int xhci_port_speed ( struct xhci_host *xhci, unsigned int port,
|
||
unsigned int psiv ) {
|
||
unsigned int supported = xhci_supported_protocol ( xhci, port );
|
||
unsigned int psic;
|
||
unsigned int mantissa;
|
||
unsigned int exponent;
|
||
unsigned int speed;
|
||
unsigned int i;
|
||
uint32_t ports;
|
||
uint32_t psi;
|
||
|
||
/* Fail if there is no supported protocol */
|
||
if ( ! supported )
|
||
return -ENOTSUP;
|
||
|
||
/* Get protocol speed ID count */
|
||
ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS );
|
||
psic = XHCI_SUPPORTED_PORTS_PSIC ( ports );
|
||
|
||
/* Use protocol speed ID table unless device is known to be faulty */
|
||
/* Iterate over PSI dwords looking for a match */
|
||
for ( i = 0 ; i < psic ; i++ ) {
|
||
psi = readl ( xhci->cap + supported +
|
||
XHCI_SUPPORTED_PSI ( i ) );
|
||
if ( psiv == XHCI_SUPPORTED_PSI_VALUE ( psi ) ) {
|
||
mantissa = XHCI_SUPPORTED_PSI_MANTISSA ( psi );
|
||
exponent = XHCI_SUPPORTED_PSI_EXPONENT ( psi );
|
||
return xhci_psi_to_speed(XCHI_PSI ( mantissa, exponent ));
|
||
}
|
||
}
|
||
|
||
/* Record device as faulty if no match is found */
|
||
if ( psic != 0 ) {
|
||
USB_LOG_ERR("XHCI %s-%d spurious PSI value %d: "
|
||
"assuming PSI table is invalid\n",
|
||
xhci->name, port, psiv );
|
||
}
|
||
|
||
/* Use the default mappings */
|
||
switch ( psiv ) {
|
||
case XHCI_SPEED_LOW : return USB_SPEED_LOW;
|
||
case XHCI_SPEED_FULL : return USB_SPEED_FULL;
|
||
case XHCI_SPEED_HIGH : return USB_SPEED_HIGH;
|
||
case XHCI_SPEED_SUPER : return USB_SPEED_SUPER;
|
||
default:
|
||
USB_LOG_ERR("XHCI %s-%d unrecognised PSI value %d\n",
|
||
xhci->name, port, psiv );
|
||
return -ENOTSUP;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Find protocol speed ID value
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v port Port number
|
||
* @v speed USB speed
|
||
* @ret psiv Protocol speed ID value, or negative error
|
||
*/
|
||
static int xhci_port_psiv ( struct xhci_host *xhci, unsigned int port,
|
||
unsigned int speed ) {
|
||
unsigned int supported = xhci_supported_protocol ( xhci, port );
|
||
unsigned int psic;
|
||
unsigned int mantissa;
|
||
unsigned int exponent;
|
||
unsigned int psiv;
|
||
unsigned int i;
|
||
uint32_t ports;
|
||
uint32_t psi;
|
||
|
||
/* Fail if there is no supported protocol */
|
||
if ( ! supported )
|
||
return -ENOTSUP;
|
||
|
||
/* Get protocol speed ID count */
|
||
ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS );
|
||
psic = XHCI_SUPPORTED_PORTS_PSIC ( ports );
|
||
|
||
/* Use the default mappings if applicable */
|
||
if ( psic == 0 ) {
|
||
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;
|
||
default:
|
||
USB_LOG_DBG("XHCI %s-%d non-standard speed %d\n",
|
||
xhci->name, port, speed );
|
||
return -ENOTSUP;
|
||
}
|
||
}
|
||
|
||
/* Iterate over PSI dwords looking for a match */
|
||
for ( i = 0 ; i < psic ; i++ ) {
|
||
psi = readl ( xhci->cap + supported + XHCI_SUPPORTED_PSI ( i ));
|
||
mantissa = XHCI_SUPPORTED_PSI_MANTISSA ( psi );
|
||
exponent = XHCI_SUPPORTED_PSI_EXPONENT ( psi );
|
||
if ( xhci_speed_to_psi(speed) == XCHI_PSI ( mantissa, exponent ) ) {
|
||
psiv = XHCI_SUPPORTED_PSI_VALUE ( psi );
|
||
return psiv;
|
||
}
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s-%d unrepresentable speed %#x\n",
|
||
xhci->name, port, speed );
|
||
return -ENOENT;
|
||
}
|
||
|
||
/**
|
||
* Update root hub port speed
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v port Port number
|
||
* @ret rc Return status code (speed)
|
||
*/
|
||
uint32_t xhci_root_speed ( struct xhci_host *xhci, uint8_t port ) {
|
||
uint32_t portsc;
|
||
unsigned int psiv;
|
||
int ccs;
|
||
int ped;
|
||
int csc;
|
||
int speed;
|
||
unsigned int protocol = xhci_port_protocol(xhci, port);
|
||
|
||
/* Read port status */
|
||
portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port ) );
|
||
USB_LOG_DBG("XHCI %s-%d status is 0x%08x, protocol 0x%x\n",
|
||
xhci->name, port, portsc, protocol );
|
||
ccs = ( portsc & XHCI_PORTSC_CCS );
|
||
ped = ( portsc & XHCI_PORTSC_PED );
|
||
csc = ( portsc & XHCI_PORTSC_CSC );
|
||
psiv = XHCI_PORTSC_PSIV ( portsc );
|
||
|
||
USB_LOG_DBG("XHCI %s port-%d ccs: %d, ped: %d, csc: %d, psiv: 0x%x\n",
|
||
xhci->name, port, !!ccs, !!ped, !!csc, psiv);
|
||
|
||
/* Port speed is not valid unless port is connected */
|
||
if ( ! ccs ) {
|
||
speed = USB_SPEED_UNKNOWN;
|
||
USB_LOG_ERR("XHCI %s port-%d speed unkown\n", xhci->name, port);
|
||
return speed;
|
||
}
|
||
|
||
/* For USB2 ports, the PSIV field is not valid until the port
|
||
* completes reset and becomes enabled.
|
||
*/
|
||
if ( ( protocol < USB_3_0 ) && ! ped ) {
|
||
speed = USB_SPEED_FULL;
|
||
return speed;
|
||
}
|
||
|
||
/* Get port speed and map to generic USB speed */
|
||
speed = xhci_port_speed ( xhci, port, psiv );
|
||
if ( speed < 0 ) {
|
||
return speed;
|
||
}
|
||
|
||
return speed;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
/**
|
||
* Add a TRB to the given ring
|
||
*
|
||
* @v ring XHCI TRB ring
|
||
* @v trb TRB content to be added
|
||
*/
|
||
static inline void xhci_trb_fill(struct xhci_ring *ring, union xhci_trb *trb) {
|
||
union xhci_trb *dst = &ring->ring[ring->nidx];
|
||
memcpy((void *)dst, (void *)trb, sizeof(*trb));
|
||
dst->template.control |= (ring->cs ? XHCI_TRB_C : 0);
|
||
xhci_dump_trbs(dst, 1);
|
||
}
|
||
|
||
/**
|
||
* Queue a TRB onto a ring, wrapping ring as needed
|
||
*
|
||
* @v ring XHCI TRB ring
|
||
* @v trb TRB content to be added
|
||
*/
|
||
static void xhci_trb_queue(struct xhci_ring *ring, union xhci_trb *trb) {
|
||
|
||
if (ring->nidx >= XHCI_RING_ITEMS - 1) {
|
||
/* if it is the last trb in the list, put a link trb in the end */
|
||
union xhci_trb link_trb;
|
||
link_trb.link.type = XHCI_TRB_LINK;
|
||
link_trb.link.flags = XHCI_TRB_TC;
|
||
link_trb.link.next = CPU_TO_LE64((ring->ring));
|
||
|
||
xhci_trb_fill(ring, &link_trb);
|
||
|
||
ring->nidx = 0; /* adjust dequeue index to 0 */
|
||
ring->cs ^= 1; /* toggle cycle interpretation of sw */
|
||
}
|
||
|
||
xhci_trb_fill(ring, trb);
|
||
ring->nidx++; /* ahead dequeue index */
|
||
}
|
||
|
||
/**
|
||
* Wait for a ring to empty (all TRBs processed by hardware)
|
||
*
|
||
* @v xhci XHCI Device
|
||
* @v ep Owner Endpoint of current TRB ring
|
||
* @ ring XHCI TRB ring
|
||
*/
|
||
int xhci_event_wait(struct xhci_host *xhci,
|
||
struct xhci_endpoint *ep,
|
||
struct xhci_ring *ring) {
|
||
int cc = XHCI_CMPLT_SUCCESS;
|
||
|
||
if (ep->timeout > 0)
|
||
{
|
||
if (usb_osal_sem_take(ep->waitsem, ep->timeout) < 0)
|
||
{
|
||
cc = XHCI_CMPLT_TIMEOUT;
|
||
}
|
||
else
|
||
{
|
||
cc = ring->evt.complete.code; /* bit[31:24] completion code */
|
||
}
|
||
}
|
||
|
||
return cc;
|
||
}
|
||
|
||
/**
|
||
* Ring doorbell register
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v slotid Slot id to ring
|
||
* @v value Value send to doorbell
|
||
*/
|
||
static inline void xhci_doorbell ( struct xhci_host *xhci, uint32_t slotid, uint32_t value ) {
|
||
|
||
DSB();
|
||
writel ( value, xhci->db + slotid * XHCI_REG_DB_SIZE ); /* bit[7:0] db target, is ep_id */
|
||
}
|
||
|
||
/**
|
||
* Abort command
|
||
*
|
||
* @v xhci xHCI device
|
||
*/
|
||
static void xhci_abort ( struct xhci_host *xhci ) {
|
||
uintptr_t crp;
|
||
|
||
/* Abort the command */
|
||
USB_LOG_WRN("XHCI %s aborting command\n", xhci->name );
|
||
xhci_writeq ( xhci, XHCI_CRCR_CA, xhci->op + XHCI_OP_CRCR );
|
||
|
||
/* Allow time for command to abort */
|
||
usb_osal_msleep ( XHCI_COMMAND_ABORT_DELAY_MS );
|
||
|
||
/* Sanity check */
|
||
USB_ASSERT ( ( readl ( xhci->op + XHCI_OP_CRCR ) & XHCI_CRCR_CRR ) == 0 );
|
||
|
||
/* Consume (and ignore) any final command status */
|
||
int cc = xhci_event_wait(xhci, xhci->cur_cmd_pipe, xhci->cmds);
|
||
if (XHCI_CMPLT_SUCCESS != cc) {
|
||
USB_LOG_ERR("XHCI %s abort command failed, cc %d\n", xhci->name, cc);
|
||
}
|
||
|
||
/* Reset the command ring control register */
|
||
memset(xhci->cmds->ring, 0U, XHCI_RING_ITEMS * sizeof(union xhci_trb));
|
||
xhci_writeq ( xhci, ( (uint64_t)(uintptr_t)xhci->cmds | xhci->cmds->cs ), xhci->op + XHCI_OP_CRCR );
|
||
}
|
||
|
||
/**
|
||
* Submit a command to the xhci controller ring
|
||
*
|
||
* @v xhci XHCI Device
|
||
* @v ep Owner Endpoint of current TRB ring
|
||
* @ trb Command TRB to be sent
|
||
*/
|
||
static int xhci_cmd_submit(struct xhci_host *xhci, struct xhci_endpoint *ep, union xhci_trb *trb) {
|
||
|
||
int rc = 0;
|
||
usb_osal_mutex_take(xhci->cmds->lock); /* handle command one by one */
|
||
|
||
ep->timeout = 5000;
|
||
ep->waiter = true;
|
||
xhci->cur_cmd_pipe = ep;
|
||
|
||
xhci_trb_queue(xhci->cmds, trb);
|
||
|
||
/* pass command trb to hardware */
|
||
DSB();
|
||
|
||
xhci_doorbell(xhci, 0, 0); /* 0 = db host controller, 0 = db targe hc command */
|
||
int cc = xhci_event_wait(xhci, ep, xhci->cmds);
|
||
if (XHCI_CMPLT_SUCCESS != cc) {
|
||
USB_LOG_ERR("XHCI %s cmd failed, cc %d\n", xhci->name, cc);
|
||
xhci_abort(xhci); /* Abort command */
|
||
rc = -ENOTSUP;
|
||
}
|
||
|
||
usb_osal_mutex_give(xhci->cmds->lock);
|
||
xhci->cur_cmd_pipe = NULL;
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Issue NOP and wait for completion
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v ep Command Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_nop ( struct xhci_host *xhci, struct xhci_endpoint *ep ) {
|
||
union xhci_trb trb;
|
||
struct xhci_trb_common *nop = &trb.common;
|
||
int rc;
|
||
|
||
/* Construct command */
|
||
memset ( nop, 0, sizeof ( *nop ) );
|
||
nop->flags = XHCI_TRB_IOC;
|
||
nop->type = XHCI_TRB_NOP_CMD;
|
||
|
||
/* Issue command and wait for completion */
|
||
if ( ( rc = xhci_cmd_submit(xhci, ep, &trb ) ) != 0 )
|
||
return rc;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Issue Enable slot and wait for completion
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v ep Command Endpoint
|
||
* @v type Type of Slot to be enabled
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_enable_slot(struct xhci_host *xhci, struct xhci_endpoint *ep, unsigned int type) {
|
||
union xhci_trb trb;
|
||
struct xhci_trb_enable_slot *enable = &trb.enable;
|
||
struct xhci_trb_complete *enabled;
|
||
unsigned int slot;
|
||
int rc;
|
||
|
||
/* Construct command */
|
||
memset ( enable, 0, sizeof ( *enable ) );
|
||
enable->slot = type;
|
||
enable->type = XHCI_TRB_ENABLE_SLOT;
|
||
|
||
/* Issue command and Wait for completion */
|
||
if ( ( rc = xhci_cmd_submit(xhci, ep, &trb) ) != 0 ) {
|
||
USB_LOG_ERR("XHCI %s could not enable new slot, type %d\n",
|
||
xhci->name, type );
|
||
return rc;
|
||
}
|
||
|
||
/* Extract slot number */
|
||
enabled = &(xhci->cmds->evt.complete);
|
||
slot = enabled->slot;
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d enabled\n", xhci->name, slot );
|
||
return slot;
|
||
}
|
||
|
||
/**
|
||
* Disable slot
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v ep Command Endpoint
|
||
* @v slot Device slot
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_disable_slot ( struct xhci_host *xhci, struct xhci_endpoint *ep,
|
||
unsigned int slot ) {
|
||
union xhci_trb trb;
|
||
struct xhci_trb_disable_slot *disable = &trb.disable;
|
||
int rc;
|
||
|
||
/* Construct command */
|
||
memset ( disable, 0, sizeof ( *disable ) );
|
||
disable->type = XHCI_TRB_DISABLE_SLOT;
|
||
disable->slot = slot;
|
||
|
||
/* Issue command and wait for completion */
|
||
if ( ( rc = xhci_cmd_submit ( xhci, ep, &trb ) ) != 0 ) {
|
||
USB_LOG_DBG("XHCI %s could not disable slot %d: %s\n",
|
||
xhci->name, slot, strerror ( rc ) );
|
||
return rc;
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d disabled\n", xhci->name, slot );
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Issue context-based command and wait for completion
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @v type TRB type
|
||
* @v populate Input context populater
|
||
* @ret rc Return status code
|
||
*/
|
||
static int xhci_context ( struct xhci_host *xhci, struct xhci_slot *slot,
|
||
struct xhci_endpoint *ep, unsigned int type,
|
||
void ( * populate ) ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *ep,
|
||
void *input ) ) {
|
||
union xhci_trb trb;
|
||
struct xhci_trb_context *context = &trb.context;
|
||
size_t len;
|
||
void *input;
|
||
int rc;
|
||
|
||
/* Allocate an input context */
|
||
len = xhci_input_context_offset ( xhci, XHCI_CTX_END );
|
||
input = usb_align(xhci_align ( len ), len);
|
||
if ( ! input ) {
|
||
rc = -ENOMEM;
|
||
goto err_alloc;
|
||
}
|
||
memset ( input, 0, len );
|
||
|
||
/* Populate input context */
|
||
populate ( xhci, slot, ep, input );
|
||
|
||
/* Construct command */
|
||
memset ( context, 0, sizeof ( *context ) );
|
||
context->type = type;
|
||
context->input = CPU_TO_LE64 ( ( input ) );
|
||
context->slot = slot->id;
|
||
|
||
/* Issue command and wait for completion */
|
||
if ( ( rc = xhci_cmd_submit ( xhci, ep, &trb ) ) != 0 ) {
|
||
xhci_dump_input_ctx(xhci, ep, input);
|
||
goto err_command;
|
||
}
|
||
|
||
err_command:
|
||
usb_free ( input );
|
||
err_alloc:
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Populate address device input context
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @v input Input context
|
||
*/
|
||
static void xhci_address_device_input ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint,
|
||
void *input ) {
|
||
struct xhci_control_context *control_ctx;
|
||
struct xhci_slot_context *slot_ctx;
|
||
struct xhci_endpoint_context *ep_ctx;
|
||
|
||
/* Sanity checks */
|
||
USB_ASSERT ( endpoint->ctx == XHCI_CTX_EP0 );
|
||
|
||
/* Populate control context, add slot context and ep context */
|
||
control_ctx = input;
|
||
control_ctx->add = CPU_TO_LE32 ( ( 1 << XHCI_CTX_SLOT ) |
|
||
( 1 << XHCI_CTX_EP0 ) );
|
||
|
||
/* Populate slot context */
|
||
slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT ));
|
||
slot_ctx->info = CPU_TO_LE32 ( XHCI_SLOT_INFO ( 1, 0, slot->psiv,
|
||
slot->route ) );
|
||
slot_ctx->port = slot->port;
|
||
slot_ctx->tt_id = slot->tt_id;
|
||
slot_ctx->tt_port = slot->tt_port;
|
||
|
||
/* Populate control endpoint context */
|
||
ep_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_EP0 ) );
|
||
ep_ctx->type = XHCI_EP_TYPE_CONTROL; /* bit[5:3] endpoint type */
|
||
ep_ctx->burst = endpoint->burst; /* bit[16:8] max burst size */
|
||
ep_ctx->mtu = CPU_TO_LE16 ( endpoint->mtu ); /* bit[31:16] max packet size */
|
||
|
||
/*
|
||
bit[63:4] tr dequeue pointer
|
||
bit[0] dequeue cycle state
|
||
*/
|
||
ep_ctx->dequeue = CPU_TO_LE64 ( (uint64_t)( &endpoint->reqs.ring[0] ) | XHCI_EP_DCS );
|
||
ep_ctx->trb_len = CPU_TO_LE16 ( XHCI_EP0_TRB_LEN ); /* bit[15:0] average trb length */
|
||
}
|
||
|
||
/**
|
||
* Address device
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v endpoint Endpoint
|
||
* @v slot Device slot
|
||
* @ret rc Return status code
|
||
*/
|
||
static inline int xhci_address_device ( struct xhci_host *xhci,
|
||
struct xhci_endpoint *ep,
|
||
struct xhci_slot *slot ) {
|
||
struct xhci_slot_context *slot_ctx;
|
||
int rc;
|
||
|
||
/* Assign device address */
|
||
if ( ( rc = xhci_context ( xhci, slot, slot->endpoint[XHCI_CTX_EP0],
|
||
XHCI_TRB_ADDRESS_DEVICE,
|
||
xhci_address_device_input ) ) != 0 )
|
||
|
||
USB_LOG_DBG("XHCI %s slot ctx 0x%x\n", xhci->name, slot->context);
|
||
|
||
/* Get assigned address for check */
|
||
slot_ctx = ( slot->context +
|
||
xhci_device_context_offset ( xhci, XHCI_CTX_SLOT ) );
|
||
USB_LOG_DBG("XHCI %s slot ctx 0x%x assigned address 0x%x\n",
|
||
xhci->name, slot_ctx, slot_ctx->address );
|
||
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Reset endpoint
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_reset_endpoint ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint ) {
|
||
union xhci_trb trb;
|
||
struct xhci_trb_reset_endpoint *reset = &trb.reset;
|
||
int rc;
|
||
|
||
/* Construct command */
|
||
memset ( reset, 0, sizeof ( *reset ) );
|
||
reset->slot = slot->id;
|
||
reset->endpoint = endpoint->ctx;
|
||
reset->type = XHCI_TRB_RESET_ENDPOINT;
|
||
|
||
/* Issue command and wait for completion */
|
||
if ( ( rc = xhci_cmd_submit ( xhci, endpoint, &trb ) ) != 0 ) {
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d could not reset endpoint "
|
||
"in state %d: %s\n", xhci->name, slot->id, endpoint->ctx,
|
||
endpoint->context->state, strerror ( rc ) );
|
||
return rc;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Stop endpoint
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
static inline int xhci_stop_endpoint ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint ) {
|
||
union xhci_trb trb;
|
||
struct xhci_trb_stop_endpoint *stop = &trb.stop;
|
||
int rc;
|
||
|
||
/* Construct command */
|
||
memset ( stop, 0, sizeof ( *stop ) );
|
||
stop->slot = slot->id;
|
||
stop->endpoint = endpoint->ctx;
|
||
stop->type = XHCI_TRB_STOP_ENDPOINT;
|
||
|
||
/* Issue command and wait for completion */
|
||
if ( ( rc = xhci_cmd_submit ( xhci, endpoint, &trb ) ) != 0 ) {
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d could not stop endpoint "
|
||
"in state %d: %s\n", xhci->name, slot->id, endpoint->ctx,
|
||
endpoint->context->state, strerror ( rc ) );
|
||
return rc;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Find port slot type
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v port Port number
|
||
* @ret type Slot type, or negative error
|
||
*/
|
||
static int xhci_port_slot_type ( struct xhci_host *xhci, unsigned int port ) {
|
||
unsigned int supported = xhci_supported_protocol ( xhci, port );
|
||
unsigned int type;
|
||
uint32_t slot;
|
||
|
||
/* Fail if there is no supported protocol */
|
||
if ( ! supported )
|
||
return -ENOTSUP;
|
||
|
||
/* Get slot type */
|
||
slot = readl ( xhci->cap + supported + XHCI_SUPPORTED_SLOT );
|
||
type = XHCI_SUPPORTED_SLOT_TYPE ( slot );
|
||
|
||
return type;
|
||
}
|
||
|
||
/**
|
||
* Open device
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v ep Endpoint
|
||
* @ret slot_id Return device slot id
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_device_open ( struct xhci_host *xhci, struct xhci_endpoint *ep, int *slot_id ) {
|
||
struct usbh_hubport *hport = ep->hport;
|
||
struct usbh_hubport *tt = usbh_transaction_translator(hport);
|
||
struct xhci_slot *slot;
|
||
struct xhci_slot *tt_slot;
|
||
int type;
|
||
int rc;
|
||
int id;
|
||
size_t len;
|
||
|
||
/* Determine applicable slot type */
|
||
type = xhci_port_slot_type ( xhci, hport->port );
|
||
if ( type < 0 ) {
|
||
rc = type;
|
||
USB_LOG_ERR("XHCI %s-%d has no slot type\n",
|
||
xhci->name, hport->port );
|
||
goto err_type;
|
||
}
|
||
|
||
/* Allocate a device slot number */
|
||
id = xhci_enable_slot ( xhci, ep, type );
|
||
if ( id < 0 ) {
|
||
rc = id;
|
||
goto err_enable_slot;
|
||
}
|
||
|
||
USB_ASSERT ( ( id > 0 ) && ( ( unsigned int ) id <= xhci->slots ) );
|
||
USB_ASSERT ( xhci->slot[id] == NULL );
|
||
|
||
/* Allocate and initialise structure */
|
||
slot = usb_malloc ( sizeof ( *slot ) );
|
||
if ( ! slot ) {
|
||
rc = -ENOMEM;
|
||
goto err_alloc;
|
||
}
|
||
slot->id = id;
|
||
xhci->slot[id] = slot;
|
||
slot->xhci = xhci;
|
||
if ( tt ) {
|
||
tt_slot = xhci->slot[tt->dev_addr];
|
||
slot->tt_id = tt_slot->id;
|
||
slot->tt_port = tt->port;
|
||
}
|
||
|
||
/* Allocate a device context */
|
||
len = xhci_device_context_offset ( xhci, XHCI_CTX_END );
|
||
slot->context = usb_align(xhci_align ( len ), len);
|
||
if ( ! slot->context ) {
|
||
rc = -ENOMEM;
|
||
goto err_alloc_context;
|
||
}
|
||
memset ( slot->context, 0, len );
|
||
|
||
/* Set device context base address */
|
||
USB_ASSERT ( xhci->dcbaa.context[id] == 0 );
|
||
xhci->dcbaa.context[id] = CPU_TO_LE64 ( ( slot->context ) );
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d device context [%08lx,%08lx)\n",
|
||
xhci->name, slot->id, ( slot->context ),
|
||
( ( slot->context ) + len ) );
|
||
*slot_id = id;
|
||
return 0;
|
||
|
||
xhci->dcbaa.context[id] = 0;
|
||
usb_free ( slot->context );
|
||
|
||
err_alloc_context:
|
||
xhci->slot[id] = NULL;
|
||
usb_free ( slot );
|
||
err_alloc:
|
||
xhci_disable_slot ( xhci, ep, id );
|
||
err_enable_slot:
|
||
err_type:
|
||
return rc;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Assign device address
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v slot Slot
|
||
* @v ep Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_device_address ( struct xhci_host *xhci, struct xhci_slot *slot, struct xhci_endpoint *ep ) {
|
||
USB_ASSERT((slot->xhci) && (ep->hport));
|
||
struct usbh_hubport *hport = ep->hport;
|
||
int psiv;
|
||
int rc;
|
||
|
||
/* Calculate route string */
|
||
slot->route = usbh_route_string (hport);
|
||
|
||
/* Calculate root hub port number */
|
||
struct usbh_hubport *root_port = usbh_root_hub_port (hport);
|
||
slot->port = root_port->port;
|
||
|
||
/* Calculate protocol speed ID */
|
||
psiv = xhci_port_psiv ( xhci, slot->port, hport->speed );
|
||
if ( psiv < 0 ) {
|
||
rc = psiv;
|
||
return rc;
|
||
}
|
||
slot->psiv = psiv;
|
||
|
||
/* Address device */
|
||
if ( ( rc = xhci_address_device ( xhci, ep, slot ) ) != 0 )
|
||
return rc;
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/**
|
||
* Close device
|
||
*
|
||
* @v slot Slot
|
||
*/
|
||
void xhci_device_close ( struct xhci_slot *slot ) {
|
||
struct xhci_host *xhci = slot->xhci;
|
||
size_t len = xhci_device_context_offset ( xhci, XHCI_CTX_END );
|
||
unsigned int id = slot->id;
|
||
int rc;
|
||
|
||
/* Disable slot */
|
||
if ( ( rc = xhci_disable_slot ( xhci, slot->endpoint[0], id ) ) != 0 ) {
|
||
/* Slot is still enabled. Leak the slot context,
|
||
* since the controller may still write to this
|
||
* memory, and leave the DCBAA entry intact.
|
||
*
|
||
* If the controller later reports that this same slot
|
||
* has been re-enabled, then some assertions will be
|
||
* triggered.
|
||
*/
|
||
USB_LOG_DBG("XHCI %s slot %d leaking context memory\n",
|
||
xhci->name, slot->id );
|
||
slot->context = NULL;
|
||
}
|
||
|
||
/* Free slot */
|
||
if ( slot->context ) {
|
||
usb_free ( slot->context );
|
||
xhci->dcbaa.context[id] = 0;
|
||
}
|
||
xhci->slot[id] = NULL;
|
||
usb_free ( slot );
|
||
}
|
||
|
||
|
||
/**
|
||
* Populate configure endpoint input context
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @v input Input context
|
||
*/
|
||
static void xhci_configure_endpoint_input ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint,
|
||
void *input ) {
|
||
struct xhci_control_context *control_ctx;
|
||
struct xhci_slot_context *slot_ctx;
|
||
struct xhci_endpoint_context *ep_ctx;
|
||
|
||
xhci_dump_endpoint(endpoint);
|
||
|
||
/* Populate control context */
|
||
control_ctx = input;
|
||
control_ctx->add = CPU_TO_LE32 (( 1 << XHCI_CTX_SLOT ) | ( 1 << endpoint->ctx ) );
|
||
control_ctx->drop = CPU_TO_LE32 (( 1 << XHCI_CTX_SLOT ) | ( 1 << endpoint->ctx ) );
|
||
|
||
/* Populate slot context */
|
||
slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT ));
|
||
slot_ctx->info = CPU_TO_LE32 ( XHCI_SLOT_INFO ( ( XHCI_CTX_END - 1 ),
|
||
( slot->ports ? 1 : 0 ),
|
||
slot->psiv, 0 ) );
|
||
slot_ctx->ports = slot->ports;
|
||
|
||
/* Populate endpoint context */
|
||
ep_ctx = ( input + xhci_input_context_offset ( xhci, endpoint->ctx ) );
|
||
ep_ctx->interval = endpoint->interval; /* bit[23:16] for interrupt ep, set interval to control interrupt period */
|
||
/*
|
||
Value Endpoint Type Direction
|
||
0 Not Valid N/A
|
||
1 Isoch Out
|
||
2 Bulk Out
|
||
3 Interrupt Out
|
||
4 Control Bidirectional
|
||
5 Isoch In
|
||
6 Bulk In
|
||
7 Interrupt In
|
||
*/
|
||
ep_ctx->type = endpoint->ctx_type;
|
||
ep_ctx->burst = endpoint->burst;
|
||
ep_ctx->mtu = CPU_TO_LE16 ( endpoint->mtu ); /* bit[31:16] max packet size */
|
||
ep_ctx->dequeue = CPU_TO_LE64 ( (uint64_t)( &(endpoint->reqs.ring[0]) ) | XHCI_EP_DCS );
|
||
|
||
/* TODO: endpoint attached on hub may need different setting here */
|
||
if (endpoint->ep_type == USB_ENDPOINT_TYPE_BULK) {
|
||
ep_ctx->trb_len = CPU_TO_LE16 ( 256U ); /* bit[15:0] average trb length */
|
||
} else if (endpoint->ep_type == USB_ENDPOINT_TYPE_INTERRUPT) {
|
||
ep_ctx->trb_len = CPU_TO_LE16 (16U);
|
||
ep_ctx->esit_low = CPU_TO_LE16 ( endpoint->mtu ); /* bit[31:16] max ESIT payload */
|
||
}
|
||
|
||
xhci_dump_input_ctx(xhci, endpoint, input);
|
||
}
|
||
|
||
/**
|
||
* Configure endpoint
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
static inline int xhci_configure_endpoint ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint ){
|
||
int rc;
|
||
|
||
/* Configure endpoint */
|
||
if ( ( rc = xhci_context ( xhci, slot, endpoint,
|
||
XHCI_TRB_CONFIGURE_ENDPOINT,
|
||
xhci_configure_endpoint_input ) ) != 0 ){
|
||
USB_LOG_ERR("XHCI %s slot %d ctx %d configure failed, error %d !!!\n",
|
||
xhci->name, slot->id, endpoint->ctx, rc );
|
||
return rc;
|
||
}
|
||
|
||
/* Sanity check */
|
||
if ( ( endpoint->context->state & XHCI_ENDPOINT_STATE_MASK )
|
||
!= XHCI_ENDPOINT_RUNNING ){
|
||
xhci_dump_slot_ctx(endpoint->slot->context);
|
||
xhci_dump_ep_ctx(endpoint->context);
|
||
USB_LOG_ERR("XHCI %s slot %d ctx %d configure failed !!!\n",
|
||
xhci->name, slot->id, endpoint->ctx );
|
||
return -1;
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d configured\n",
|
||
xhci->name, slot->id, endpoint->ctx );
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Populate deconfigure endpoint input context
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @v input Input context
|
||
*/
|
||
static void
|
||
xhci_deconfigure_endpoint_input ( struct xhci_host *xhci __unused,
|
||
struct xhci_slot *slot __unused,
|
||
struct xhci_endpoint *endpoint,
|
||
void *input ) {
|
||
struct xhci_control_context *control_ctx;
|
||
struct xhci_slot_context *slot_ctx;
|
||
|
||
/* Populate control context */
|
||
control_ctx = input;
|
||
control_ctx->add = CPU_TO_LE32 ( 1 << XHCI_CTX_SLOT );
|
||
control_ctx->drop = CPU_TO_LE32 ( 1 << endpoint->ctx );
|
||
|
||
/* Populate slot context */
|
||
slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT ));
|
||
slot_ctx->info = CPU_TO_LE32 ( XHCI_SLOT_INFO ( ( XHCI_CTX_END - 1 ),
|
||
0, 0, 0 ) );
|
||
}
|
||
|
||
/**
|
||
* Deconfigure endpoint
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
static inline int xhci_deconfigure_endpoint ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint ) {
|
||
int rc;
|
||
|
||
/* Deconfigure endpoint */
|
||
if ( ( rc = xhci_context ( xhci, slot, endpoint,
|
||
XHCI_TRB_CONFIGURE_ENDPOINT,
|
||
xhci_deconfigure_endpoint_input ) ) != 0 )
|
||
return rc;
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d deconfigured\n",
|
||
xhci->name, slot->id, endpoint->ctx );
|
||
return 0;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Open control endpoint
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v slot Slot
|
||
* @v ep Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_ctrl_endpoint_open ( struct xhci_host *xhci, struct xhci_slot *slot, struct xhci_endpoint *ep ) {
|
||
unsigned int ctx;
|
||
|
||
/* Calculate context index */
|
||
ctx = XHCI_CTX ( ep->address );
|
||
USB_ASSERT ( slot->endpoint[ctx] == NULL );
|
||
|
||
if (USB_ENDPOINT_TYPE_CONTROL != ep->ep_type) {
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* initialise structure */
|
||
slot->endpoint[ctx] = ep;
|
||
ep->xhci = xhci;
|
||
ep->slot = slot;
|
||
ep->ctx = ctx;
|
||
ep->ctx_type = XHCI_EP_TYPE_CONTROL;
|
||
ep->context = ( ( ( void * ) slot->context ) +
|
||
xhci_device_context_offset ( xhci, ctx ) );
|
||
ep->reqs.cs = 1; /* cycle state = 1 */
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d endpoint 0x%x ep type %d xhci ep type 0x%x\n",
|
||
xhci->name, slot->id, ep->address, ep->ep_type,
|
||
(ep->ctx_type >> 3) );
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d ring [%08lx,%08lx)\n",
|
||
xhci->name, slot->id, ctx, ( ep->reqs.ring ),
|
||
( ( ep->reqs.ring ) + sizeof(ep->reqs.ring) ) );
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Open work endpoint
|
||
*
|
||
* @v xhci XHCI device
|
||
* @v slot Slot
|
||
* @v ep USB endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_work_endpoint_open ( struct xhci_host *xhci, struct xhci_slot *slot, struct xhci_endpoint *ep ) {
|
||
unsigned int ctx;
|
||
unsigned int interval;
|
||
unsigned int ctx_type;
|
||
int rc;
|
||
|
||
/* Calculate context index */
|
||
ctx = XHCI_CTX ( ep->address );
|
||
USB_ASSERT ( slot->endpoint[ctx] == NULL );
|
||
|
||
if (USB_ENDPOINT_TYPE_CONTROL == ep->ep_type) {
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* Calculate endpoint type */
|
||
/*
|
||
Value Endpoint Type Direction, bit[5:3]
|
||
0 Not Valid N/A
|
||
1 Isoch Out
|
||
2 Bulk Out
|
||
3 Interrupt Out
|
||
4 Control Bidirectional
|
||
5 Isoch In
|
||
6 Bulk In
|
||
7 Interrupt In
|
||
*/
|
||
ctx_type = XHCI_EP_TYPE ( ep->ep_type );
|
||
if ( ep->address & USB_EP_DIR_IN )
|
||
ctx_type |= XHCI_EP_TYPE_IN;
|
||
|
||
/* initialise structure */
|
||
slot->endpoint[ctx] = ep;
|
||
ep->xhci = xhci;
|
||
ep->slot = slot;
|
||
ep->ctx = ctx;
|
||
|
||
/* Calculate interval */
|
||
if ( ctx_type & XHCI_EP_TYPE_PERIODIC ) {
|
||
ep->interval = ( fls ( ep->interval ) - 1 );
|
||
}
|
||
|
||
ep->ctx_type = ctx_type;
|
||
ep->context = ( ( ( void * ) slot->context ) +
|
||
xhci_device_context_offset ( xhci, ctx ) );
|
||
ep->reqs.cs = 1; /* cycle state = 1 */
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d endpoint 0x%x ep type %d xhci ep type 0x%x\n",
|
||
xhci->name, slot->id, ep->address, ep->ep_type,
|
||
(ep->ctx_type >> 3) );
|
||
|
||
/* Configure endpoint */
|
||
if (( rc = xhci_configure_endpoint ( xhci, slot, ep ) ) != 0) {
|
||
goto err_configure_endpoint;
|
||
}
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d ring [%08lx,%08lx)\n",
|
||
xhci->name, slot->id, ctx, ( ep->reqs.ring ),
|
||
( ( ep->reqs.ring ) + sizeof(ep->reqs.ring) ) );
|
||
|
||
return 0;
|
||
err_configure_endpoint:
|
||
(void)xhci_deconfigure_endpoint ( xhci, slot, ep );
|
||
slot->endpoint[ctx] = NULL;
|
||
return rc;
|
||
}
|
||
|
||
/**
|
||
* Close endpoint
|
||
*
|
||
* @v ep USB endpoint
|
||
*/
|
||
void xhci_endpoint_close ( struct xhci_endpoint *ep ) {
|
||
struct xhci_slot *slot = ep->slot;
|
||
struct xhci_host *xhci = slot->xhci;
|
||
unsigned int ctx = ep->ctx;
|
||
|
||
/* Deconfigure endpoint, if applicable */
|
||
if ( ctx != XHCI_CTX_EP0 )
|
||
(void)xhci_deconfigure_endpoint ( xhci, slot, ep );
|
||
|
||
slot->endpoint[ctx] = NULL;
|
||
usb_free(ep);
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Enqueue message transfer
|
||
*
|
||
* @v ep USB endpoint
|
||
* @v packet Setup packet buffer
|
||
* @v data_buff Data buffer
|
||
* @v datalen Data length
|
||
* @ret rc Return status code
|
||
*/
|
||
void xhci_endpoint_message ( struct xhci_endpoint *ep,
|
||
struct usb_setup_packet *packet,
|
||
void *data_buff,
|
||
int datalen ) {
|
||
struct xhci_host *xhci = ep->xhci;
|
||
struct xhci_slot *slot = ep->slot;
|
||
union xhci_trb trb;
|
||
struct xhci_trb_setup *setup;
|
||
struct xhci_trb_data *data;
|
||
struct xhci_trb_status *status;
|
||
unsigned int input;
|
||
|
||
/* Construct setup stage TRB */
|
||
setup = &(trb.setup);
|
||
memset ( setup, 0, sizeof ( *setup ) );
|
||
|
||
memcpy ( &setup->packet, packet, sizeof ( setup->packet ) );
|
||
setup->len = CPU_TO_LE32 ( sizeof ( *packet ) ); /* bit[16:0] trb transfer length, always 8 */
|
||
setup->flags = XHCI_TRB_IDT; /* bit[6] Immediate Data (IDT), parameters take effect */
|
||
setup->type = XHCI_TRB_SETUP; /* bit[15:10] trb type */
|
||
input = ( packet->bmRequestType & CPU_TO_LE16 ( USB_REQUEST_DIR_IN ) );
|
||
if (datalen > 0) {
|
||
/* bit[17:16] Transfer type, 2 = OUT Data, 3 = IN Data */
|
||
setup->direction = ( input ? XHCI_SETUP_IN : XHCI_SETUP_OUT );
|
||
}
|
||
|
||
xhci_trb_queue(&(ep->reqs), &trb);
|
||
|
||
/* Construct data stage TRB, if applicable */
|
||
if (datalen > 0) {
|
||
data = &(trb.data);
|
||
memset ( data, 0, sizeof ( *data ) );
|
||
|
||
data->data = CPU_TO_LE64 ( data_buff );
|
||
data->len = CPU_TO_LE32 ( datalen );
|
||
data->type = XHCI_TRB_DATA; /* bit[15:10] trb type */
|
||
data->direction = ( input ? XHCI_DATA_IN : XHCI_DATA_OUT ); /* bit[16] Direction, 0 = OUT, 1 = IN */
|
||
|
||
xhci_trb_queue(&(ep->reqs), &trb);
|
||
}
|
||
|
||
status = &(trb.status);
|
||
memset ( status, 0, sizeof ( *status ) );
|
||
status->flags = XHCI_TRB_IOC;
|
||
status->type = XHCI_TRB_STATUS;
|
||
status->direction =
|
||
( ( datalen && input ) ? XHCI_STATUS_OUT : XHCI_STATUS_IN );
|
||
|
||
xhci_trb_queue(&(ep->reqs), &trb);
|
||
|
||
/* pass command trb to hardware */
|
||
DSB();
|
||
|
||
USB_LOG_DBG("ring doorbell slot-%d ep-%d \r\n", slot->id, ep->ctx);
|
||
xhci_doorbell(xhci, slot->id, ep->ctx); /* 0 = db host controller, 0 = db targe hc command */
|
||
|
||
return;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Enqueue stream transfer
|
||
*
|
||
* @v ep USB endpoint
|
||
* @v data_buff Data buffer
|
||
* @v datalen Data length
|
||
* @ret rc Return status code
|
||
*/
|
||
void xhci_endpoint_stream ( struct xhci_endpoint *ep,
|
||
void *data_buff,
|
||
int datalen ) {
|
||
struct xhci_host *xhci = ep->xhci;
|
||
struct xhci_slot *slot = ep->slot;
|
||
union xhci_trb trbs;
|
||
union xhci_trb *trb = &trbs;
|
||
struct xhci_trb_normal *normal;
|
||
int trb_len;
|
||
|
||
/* Calculate TRB length */
|
||
trb_len = XHCI_MTU;
|
||
if ( trb_len > datalen ) {
|
||
trb_len = datalen;
|
||
} else {
|
||
USB_LOG_ERR("transfer length %d exceed MTU %d \r\n", datalen, trb_len);
|
||
goto err_enqueue;
|
||
}
|
||
|
||
/* Construct normal TRBs */
|
||
normal = &trb->normal;
|
||
memset ( normal, 0, sizeof ( *normal ) );
|
||
normal->data = CPU_TO_LE64 ( (uintptr_t)data_buff );
|
||
normal->len = CPU_TO_LE32 ( trb_len );
|
||
normal->type = XHCI_TRB_NORMAL;
|
||
normal->flags = XHCI_TRB_IOC;
|
||
|
||
xhci_trb_queue(&(ep->reqs), trb);
|
||
|
||
/* pass command trb to hardware */
|
||
DSB();
|
||
|
||
xhci_doorbell(xhci, slot->id, ep->ctx);
|
||
|
||
err_enqueue:
|
||
return;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Populate evaluate context input context
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @v input Input context
|
||
*/
|
||
static void xhci_evaluate_context_input ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot __unused,
|
||
struct xhci_endpoint *endpoint,
|
||
void *input ) {
|
||
struct xhci_control_context *control_ctx;
|
||
struct xhci_slot_context *slot_ctx;
|
||
struct xhci_endpoint_context *ep_ctx;
|
||
|
||
/* Populate control context */
|
||
control_ctx = input;
|
||
control_ctx->add = CPU_TO_LE32 ( ( 1 << XHCI_CTX_SLOT ) /*|
|
||
( 1 << endpoint->ctx )*/ );
|
||
control_ctx->drop = CPU_TO_LE32 ( ( 1 << XHCI_CTX_SLOT ) /* |
|
||
( 1 << endpoint->ctx )*/ );
|
||
|
||
/* Populate slot context */
|
||
slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT ));
|
||
slot_ctx->info = CPU_TO_LE32 ( XHCI_SLOT_INFO ( ( XHCI_CTX_END - 1 ),
|
||
0, 0, 0 ) );
|
||
|
||
/* Populate endpoint context */
|
||
ep_ctx = ( input + xhci_input_context_offset ( xhci, endpoint->ctx ) );
|
||
ep_ctx->mtu = CPU_TO_LE16 ( endpoint->mtu );
|
||
}
|
||
|
||
/**
|
||
* Evaluate context
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v slot Device slot
|
||
* @v endpoint Endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
static inline int xhci_evaluate_context ( struct xhci_host *xhci,
|
||
struct xhci_slot *slot,
|
||
struct xhci_endpoint *endpoint ) {
|
||
int rc;
|
||
|
||
/* Configure endpoint */
|
||
if ( ( rc = xhci_context ( xhci, slot, endpoint,
|
||
XHCI_TRB_EVALUATE_CONTEXT,
|
||
xhci_evaluate_context_input ) ) != 0 )
|
||
return rc;
|
||
|
||
USB_LOG_DBG("XHCI %s slot %d ctx %d (re-)evaluated\n",
|
||
xhci->name, slot->id, endpoint->ctx );
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Update MTU
|
||
*
|
||
* @v ep USB endpoint
|
||
* @ret rc Return status code
|
||
*/
|
||
int xhci_endpoint_mtu ( struct xhci_endpoint *ep ) {
|
||
struct xhci_endpoint *endpoint = ( ep );
|
||
struct xhci_slot *slot = endpoint->slot;
|
||
struct xhci_host *xhci = slot->xhci;
|
||
int rc;
|
||
|
||
/* Evalulate context */
|
||
if ( ( rc = xhci_evaluate_context ( xhci, slot, endpoint ) ) != 0 )
|
||
return rc;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*********************************************************************/
|
||
|
||
/**
|
||
* Handle port status event
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v trb Port status event
|
||
*/
|
||
static void xhci_port_status ( struct xhci_host *xhci,
|
||
struct xhci_trb_port_status *trb ) {
|
||
uint32_t portsc;
|
||
|
||
/* Sanity check */
|
||
USB_ASSERT ( ( trb->port > 0 ) && ( trb->port <= xhci->ports ) );
|
||
|
||
/* Record disconnections, changes flag will be cleared later */
|
||
portsc = readl ( xhci->op + XHCI_OP_PORTSC ( trb->port ) );
|
||
xhci_dump_port_status(trb->port, portsc);
|
||
|
||
if (portsc & XHCI_PORTSC_CSC) {
|
||
/* Report port status change */
|
||
usbh_roothub_thread_wakeup ( trb->port );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle transfer event
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v trb Transfer event TRB
|
||
*/
|
||
static void xhci_transfer ( struct xhci_host *xhci,
|
||
struct xhci_trb_transfer *trb ) {
|
||
struct xhci_slot *slot;
|
||
struct xhci_endpoint *endpoint;
|
||
union xhci_trb *trans_trb = (void *)(uintptr_t)(trb->transfer);
|
||
struct xhci_ring *trans_ring = XHCI_RING(trans_trb); /* to align addr is ring base */
|
||
union xhci_trb *pending = &trans_ring->evt; /* preserve event trb pending to handle */
|
||
uint32_t eidx = trans_trb - trans_ring->ring + 1; /* calculate current evt trb index */
|
||
int rc;
|
||
|
||
/* Identify slot */
|
||
if ( ( trb->slot > xhci->slots ) ||
|
||
( ( slot = xhci->slot[trb->slot] ) == NULL ) ) {
|
||
USB_LOG_DBG("XHCI %s transfer event invalid slot %d:\n",
|
||
xhci->name, trb->slot );
|
||
return;
|
||
}
|
||
|
||
/* Identify endpoint */
|
||
if ( ( trb->endpoint >= XHCI_CTX_END ) ||
|
||
( ( endpoint = slot->endpoint[trb->endpoint] ) == NULL ) ) {
|
||
USB_LOG_DBG("XHCI %s slot %d transfer event invalid epid "
|
||
"%d:\n", xhci->name, slot->id, trb->endpoint );
|
||
return;
|
||
}
|
||
|
||
/* Record completion */
|
||
memcpy(pending, trb, sizeof(*trb)); /* copy current trb to cmd/transfer ring */
|
||
trans_ring->eidx = eidx;
|
||
|
||
/* Check for errors */
|
||
if ( ! ( ( trb->code == XHCI_CMPLT_SUCCESS ) ||
|
||
( trb->code == XHCI_CMPLT_SHORT ) ) ) {
|
||
USB_LOG_ERR("XHCI %s slot %d ctx %d failed (code %d)\n",
|
||
xhci->name, slot->id, endpoint->ctx, trb->code);
|
||
|
||
/* Sanity check */
|
||
USB_ASSERT ( ( endpoint->context->state & XHCI_ENDPOINT_STATE_MASK )
|
||
!= XHCI_ENDPOINT_RUNNING );
|
||
|
||
xhci_dump_ep_ctx(endpoint->context);
|
||
return;
|
||
}
|
||
|
||
if (endpoint->waiter) {
|
||
endpoint->waiter = false;
|
||
usb_osal_sem_give(endpoint->waitsem);
|
||
}
|
||
|
||
if (endpoint->urb) {
|
||
struct usbh_urb *cur_urb = endpoint->urb;
|
||
cur_urb->errorcode = trb->code;
|
||
/* bit [23:0] TRB Transfer length, residual number of bytes not transferred
|
||
for OUT, is the value of (len of trb) - (data bytes transmitted), '0' means successful
|
||
for IN, is the value of (len of trb) - (data bytes received),
|
||
if cc is Short Packet, value is the diff between expected trans size and actual recv bytes
|
||
if cc is other error, value is the diff between expected trans size and actual recv bytes */
|
||
cur_urb->actual_length += cur_urb->transfer_buffer_length - trb->residual; /* bit [23:0] */
|
||
|
||
if (cur_urb->complete) {
|
||
if (cur_urb->errorcode < 0) {
|
||
cur_urb->complete(cur_urb->arg, cur_urb->errorcode);
|
||
} else {
|
||
cur_urb->complete(cur_urb->arg, cur_urb->actual_length);
|
||
}
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
/**
|
||
* Handle command completion event
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v trb Command completion event
|
||
*/
|
||
static void xhci_complete ( struct xhci_host *xhci,
|
||
struct xhci_trb_complete *trb ) {
|
||
int rc;
|
||
union xhci_trb *cmd_trb = (void *)(uintptr_t)(trb->command);
|
||
struct xhci_ring *cmd_ring = XHCI_RING(cmd_trb); /* to align addr is ring base */
|
||
union xhci_trb *pending = &cmd_ring->evt; /* preserve event trb pending to handle */
|
||
uint32_t eidx = cmd_trb - cmd_ring->ring + 1; /* calculate current evt trb index */
|
||
struct xhci_endpoint *work_pipe = xhci->cur_cmd_pipe;
|
||
|
||
/* Ignore "command ring stopped" notifications */
|
||
if ( trb->code == XHCI_CMPLT_CMD_STOPPED ) {
|
||
USB_LOG_DBG("XHCI %s command ring stopped\n", xhci->name );
|
||
return;
|
||
}
|
||
|
||
/* Record completion */
|
||
USB_LOG_DBG("command-0x%x completed !!! \r\n", pending);
|
||
memcpy(pending, trb, sizeof(*trb)); /* copy current trb to cmd/transfer ring */
|
||
cmd_ring->eidx = eidx;
|
||
|
||
USB_ASSERT(work_pipe);
|
||
if (work_pipe->waiter)
|
||
{
|
||
work_pipe->waiter = false;
|
||
usb_osal_sem_give(work_pipe->waitsem);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle host controller event
|
||
*
|
||
* @v xhci xHCI device
|
||
* @v trb Host controller event
|
||
*/
|
||
static void xhci_host_controller ( struct xhci_host *xhci,
|
||
struct xhci_trb_host_controller *trb ) {
|
||
int rc;
|
||
|
||
/* Construct error */
|
||
rc = -( trb->code );
|
||
USB_LOG_ERR("XHCI %s host controller event (code %d)\n",
|
||
xhci->name, trb->code );
|
||
}
|
||
|
||
/**
|
||
* Process event ring in interrupt
|
||
*
|
||
* @v xhci xHCI device
|
||
* @r workpip current work endpoint
|
||
*/
|
||
struct xhci_endpoint *xhci_event_process(struct xhci_host *xhci) {
|
||
struct xhci_endpoint *work_pipe = NULL;
|
||
struct xhci_ring *evts = xhci->evts;
|
||
unsigned int evt_type;
|
||
unsigned int evt_cc;
|
||
|
||
/* check and ack event */
|
||
for (;;) {
|
||
/* Stop if we reach an empty TRB */
|
||
DSB();
|
||
|
||
uint32_t nidx = evts->nidx; /* index of dequeue trb */
|
||
uint32_t cs = evts->cs; /* cycle state toggle by xHc */
|
||
union xhci_trb *trb = evts->ring + nidx; /* current trb */
|
||
uint32_t control = trb->common.flags; /* trb control field */
|
||
|
||
if ((control & XHCI_TRB_C) != (cs ? 1 : 0)) { /* if cycle state not toggle, no events need to handle */
|
||
break;
|
||
}
|
||
|
||
/* Handle TRB */
|
||
evt_type = ( trb->common.type & XHCI_TRB_TYPE_MASK );
|
||
switch ( evt_type ) {
|
||
|
||
case XHCI_TRB_TRANSFER :
|
||
evt_cc = trb->transfer.code;
|
||
xhci_transfer ( xhci, &trb->transfer );
|
||
break;
|
||
|
||
case XHCI_TRB_COMPLETE :
|
||
evt_cc = trb->complete.code;
|
||
xhci_complete ( xhci, &trb->complete );
|
||
break;
|
||
|
||
case XHCI_TRB_PORT_STATUS:
|
||
evt_cc = trb->port.code;
|
||
xhci_port_status ( xhci, &trb->port );
|
||
break;
|
||
|
||
case XHCI_TRB_HOST_CONTROLLER:
|
||
evt_cc = trb->host.code;
|
||
xhci_host_controller ( xhci, &trb->host );
|
||
break;
|
||
|
||
default:
|
||
USB_LOG_DBG("XHCI %s unrecognised event type %d, cc %d\n:",
|
||
xhci->name, ( evt_type ) );
|
||
break;
|
||
}
|
||
|
||
/* move ring index, notify xhci */
|
||
nidx++; /* head to next trb */
|
||
if (nidx == XHCI_RING_ITEMS)
|
||
{
|
||
nidx = 0; /* roll-back if reach end of list */
|
||
cs = cs ? 0 : 1;
|
||
evts->cs = cs; /* sw toggle cycle state */
|
||
}
|
||
evts->nidx = nidx;
|
||
|
||
/* Update dequeue pointer if applicable */
|
||
uint64_t erdp = (uint64_t)(unsigned long)(evts->ring + nidx);
|
||
xhci_writeq ( xhci, (uintptr_t)( erdp ) | XHCI_RUN_ERDP_EHB,
|
||
xhci->run + XHCI_RUN_ERDP ( 0 ) );
|
||
}
|
||
|
||
return work_pipe;
|
||
} |