audk/MdeModulePkg/Bus/Pci/XhciDxe/Xhci.c

2126 lines
64 KiB
C

/** @file
The XHCI controller driver.
Copyright (c) 2011 - 2012, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include "Xhci.h"
//
// Two arrays used to translate the XHCI port state (change)
// to the UEFI protocol's port state (change).
//
USB_PORT_STATE_MAP mUsbPortStateMap[] = {
{XHC_PORTSC_CCS, USB_PORT_STAT_CONNECTION},
{XHC_PORTSC_PED, USB_PORT_STAT_ENABLE},
{XHC_PORTSC_OCA, USB_PORT_STAT_OVERCURRENT},
{XHC_PORTSC_RESET, USB_PORT_STAT_RESET}
};
USB_PORT_STATE_MAP mUsbPortChangeMap[] = {
{XHC_PORTSC_CSC, USB_PORT_STAT_C_CONNECTION},
{XHC_PORTSC_PEC, USB_PORT_STAT_C_ENABLE},
{XHC_PORTSC_OCC, USB_PORT_STAT_C_OVERCURRENT},
{XHC_PORTSC_PRC, USB_PORT_STAT_C_RESET}
};
USB_PORT_STATE_MAP mUsbHubPortStateMap[] = {
{XHC_HUB_PORTSC_CCS, USB_PORT_STAT_CONNECTION},
{XHC_HUB_PORTSC_PED, USB_PORT_STAT_ENABLE},
{XHC_HUB_PORTSC_OCA, USB_PORT_STAT_OVERCURRENT},
{XHC_HUB_PORTSC_RESET, USB_PORT_STAT_RESET}
};
USB_PORT_STATE_MAP mUsbHubPortChangeMap[] = {
{XHC_HUB_PORTSC_CSC, USB_PORT_STAT_C_CONNECTION},
{XHC_HUB_PORTSC_PEC, USB_PORT_STAT_C_ENABLE},
{XHC_HUB_PORTSC_OCC, USB_PORT_STAT_C_OVERCURRENT},
{XHC_HUB_PORTSC_PRC, USB_PORT_STAT_C_RESET}
};
EFI_DRIVER_BINDING_PROTOCOL gXhciDriverBinding = {
XhcDriverBindingSupported,
XhcDriverBindingStart,
XhcDriverBindingStop,
0x30,
NULL,
NULL
};
//
// Template for Xhci's Usb2 Host Controller Protocol Instance.
//
EFI_USB2_HC_PROTOCOL gXhciUsb2HcTemplate = {
XhcGetCapability,
XhcReset,
XhcGetState,
XhcSetState,
XhcControlTransfer,
XhcBulkTransfer,
XhcAsyncInterruptTransfer,
XhcSyncInterruptTransfer,
XhcIsochronousTransfer,
XhcAsyncIsochronousTransfer,
XhcGetRootHubPortStatus,
XhcSetRootHubPortFeature,
XhcClearRootHubPortFeature,
0x3,
0x0
};
/**
Retrieves the capability of root hub ports.
@param This The EFI_USB2_HC_PROTOCOL instance.
@param MaxSpeed Max speed supported by the controller.
@param PortNumber Number of the root hub ports.
@param Is64BitCapable Whether the controller supports 64-bit memory
addressing.
@retval EFI_SUCCESS Host controller capability were retrieved successfully.
@retval EFI_INVALID_PARAMETER Either of the three capability pointer is NULL.
**/
EFI_STATUS
EFIAPI
XhcGetCapability (
IN EFI_USB2_HC_PROTOCOL *This,
OUT UINT8 *MaxSpeed,
OUT UINT8 *PortNumber,
OUT UINT8 *Is64BitCapable
)
{
USB_XHCI_INSTANCE *Xhc;
EFI_TPL OldTpl;
if ((MaxSpeed == NULL) || (PortNumber == NULL) || (Is64BitCapable == NULL)) {
return EFI_INVALID_PARAMETER;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
*MaxSpeed = EFI_USB_SPEED_SUPER;
*PortNumber = (UINT8) (Xhc->HcSParams1.Data.MaxPorts);
*Is64BitCapable = (UINT8) (Xhc->HcCParams.Data.Ac64);
DEBUG ((EFI_D_INFO, "XhcGetCapability: %d ports, 64 bit %d\n", *PortNumber, *Is64BitCapable));
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
/**
Provides software reset for the USB host controller.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param Attributes A bit mask of the reset operation to perform.
@retval EFI_SUCCESS The reset operation succeeded.
@retval EFI_INVALID_PARAMETER Attributes is not valid.
@retval EFI_UNSUPPOURTED The type of reset specified by Attributes is
not currently supported by the host controller.
@retval EFI_DEVICE_ERROR Host controller isn't halted to reset.
**/
EFI_STATUS
EFIAPI
XhcReset (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT16 Attributes
)
{
USB_XHCI_INSTANCE *Xhc;
EFI_STATUS Status;
EFI_TPL OldTpl;
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
switch (Attributes) {
case EFI_USB_HC_RESET_GLOBAL:
//
// Flow through, same behavior as Host Controller Reset
//
case EFI_USB_HC_RESET_HOST_CONTROLLER:
//
// Host Controller must be Halt when Reset it
//
if (!XhcIsHalt (Xhc)) {
Status = XhcHaltHC (Xhc, XHC_GENERIC_TIMEOUT);
if (EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
goto ON_EXIT;
}
}
Status = XhcResetHC (Xhc, XHC_RESET_TIMEOUT);
ASSERT (!(XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_CNR)));
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// Clean up the asynchronous transfers, currently only
// interrupt supports asynchronous operation.
//
XhciDelAllAsyncIntTransfers (Xhc);
XhcFreeSched (Xhc);
XhcInitSched (Xhc);
break;
case EFI_USB_HC_RESET_GLOBAL_WITH_DEBUG:
case EFI_USB_HC_RESET_HOST_WITH_DEBUG:
Status = EFI_UNSUPPORTED;
break;
default:
Status = EFI_INVALID_PARAMETER;
}
ON_EXIT:
DEBUG ((EFI_D_INFO, "XhcReset: status %r\n", Status));
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Retrieve the current state of the USB host controller.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param State Variable to return the current host controller
state.
@retval EFI_SUCCESS Host controller state was returned in State.
@retval EFI_INVALID_PARAMETER State is NULL.
@retval EFI_DEVICE_ERROR An error was encountered while attempting to
retrieve the host controller's current state.
**/
EFI_STATUS
EFIAPI
XhcGetState (
IN EFI_USB2_HC_PROTOCOL *This,
OUT EFI_USB_HC_STATE *State
)
{
USB_XHCI_INSTANCE *Xhc;
EFI_TPL OldTpl;
if (State == NULL) {
return EFI_INVALID_PARAMETER;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
if (XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HALT)) {
*State = EfiUsbHcStateHalt;
} else {
*State = EfiUsbHcStateOperational;
}
DEBUG ((EFI_D_INFO, "XhcGetState: current state %d\n", *State));
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
/**
Sets the USB host controller to a specific state.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param State The state of the host controller that will be set.
@retval EFI_SUCCESS The USB host controller was successfully placed
in the state specified by State.
@retval EFI_INVALID_PARAMETER State is invalid.
@retval EFI_DEVICE_ERROR Failed to set the state due to device error.
**/
EFI_STATUS
EFIAPI
XhcSetState (
IN EFI_USB2_HC_PROTOCOL *This,
IN EFI_USB_HC_STATE State
)
{
USB_XHCI_INSTANCE *Xhc;
EFI_STATUS Status;
EFI_USB_HC_STATE CurState;
EFI_TPL OldTpl;
Status = XhcGetState (This, &CurState);
if (EFI_ERROR (Status)) {
return EFI_DEVICE_ERROR;
}
if (CurState == State) {
return EFI_SUCCESS;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
switch (State) {
case EfiUsbHcStateHalt:
Status = XhcHaltHC (Xhc, XHC_GENERIC_TIMEOUT);
break;
case EfiUsbHcStateOperational:
if (XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HSE)) {
Status = EFI_DEVICE_ERROR;
break;
}
//
// Software must not write a one to this field unless the host controller
// is in the Halted state. Doing so will yield undefined results.
// refers to Spec[XHCI1.0-2.3.1]
//
if (!XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HALT)) {
Status = EFI_DEVICE_ERROR;
break;
}
Status = XhcRunHC (Xhc, XHC_GENERIC_TIMEOUT);
break;
case EfiUsbHcStateSuspend:
Status = EFI_UNSUPPORTED;
break;
default:
Status = EFI_INVALID_PARAMETER;
}
DEBUG ((EFI_D_INFO, "XhcSetState: status %r\n", Status));
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Retrieves the current status of a USB root hub port.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param PortNumber The root hub port to retrieve the state from.
This value is zero-based.
@param PortStatus Variable to receive the port state.
@retval EFI_SUCCESS The status of the USB root hub port specified.
by PortNumber was returned in PortStatus.
@retval EFI_INVALID_PARAMETER PortNumber is invalid.
@retval EFI_DEVICE_ERROR Can't read register.
**/
EFI_STATUS
EFIAPI
XhcGetRootHubPortStatus (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 PortNumber,
OUT EFI_USB_PORT_STATUS *PortStatus
)
{
USB_XHCI_INSTANCE *Xhc;
UINT32 Offset;
UINT32 State;
UINT32 TotalPort;
UINTN Index;
UINTN MapSize;
EFI_STATUS Status;
USB_DEV_ROUTE ParentRouteChart;
EFI_TPL OldTpl;
if (PortStatus == NULL) {
return EFI_INVALID_PARAMETER;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
Status = EFI_SUCCESS;
TotalPort = Xhc->HcSParams1.Data.MaxPorts;
if (PortNumber >= TotalPort) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
Offset = (UINT32) (XHC_PORTSC_OFFSET + (0x10 * PortNumber));
PortStatus->PortStatus = 0;
PortStatus->PortChangeStatus = 0;
State = XhcReadOpReg (Xhc, Offset);
//
// According to XHCI 1.0 spec, bit 10~13 of the root port status register identifies the speed of the attached device.
//
switch ((State & XHC_PORTSC_PS) >> 10) {
case 2:
PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED;
break;
case 3:
PortStatus->PortStatus |= USB_PORT_STAT_HIGH_SPEED;
break;
case 4:
PortStatus->PortStatus |= USB_PORT_STAT_SUPER_SPEED;
break;
default:
break;
}
//
// Convert the XHCI port/port change state to UEFI status
//
MapSize = sizeof (mUsbPortStateMap) / sizeof (USB_PORT_STATE_MAP);
for (Index = 0; Index < MapSize; Index++) {
if (XHC_BIT_IS_SET (State, mUsbPortStateMap[Index].HwState)) {
PortStatus->PortStatus = (UINT16) (PortStatus->PortStatus | mUsbPortStateMap[Index].UefiState);
}
}
//
// Bit5~8 reflects its current link state.
//
if ((State & XHC_PORTSC_PLS) >> 5 == 3) {
PortStatus->PortStatus |= USB_PORT_STAT_SUSPEND;
}
MapSize = sizeof (mUsbPortChangeMap) / sizeof (USB_PORT_STATE_MAP);
for (Index = 0; Index < MapSize; Index++) {
if (XHC_BIT_IS_SET (State, mUsbPortChangeMap[Index].HwState)) {
PortStatus->PortChangeStatus = (UINT16) (PortStatus->PortChangeStatus | mUsbPortChangeMap[Index].UefiState);
}
}
//
// Poll the root port status register to enable/disable corresponding device slot if there is a device attached/detached.
// For those devices behind hub, we get its attach/detach event by hooking Get_Port_Status request at control transfer for those hub.
//
ParentRouteChart.Dword = 0;
XhcPollPortStatusChange (Xhc, ParentRouteChart, PortNumber, PortStatus);
ON_EXIT:
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Sets a feature for the specified root hub port.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param PortNumber Root hub port to set.
@param PortFeature Feature to set.
@retval EFI_SUCCESS The feature specified by PortFeature was set.
@retval EFI_INVALID_PARAMETER PortNumber is invalid or PortFeature is invalid.
@retval EFI_DEVICE_ERROR Can't read register.
**/
EFI_STATUS
EFIAPI
XhcSetRootHubPortFeature (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 PortNumber,
IN EFI_USB_PORT_FEATURE PortFeature
)
{
USB_XHCI_INSTANCE *Xhc;
UINT32 Offset;
UINT32 State;
UINT32 TotalPort;
UINT8 SlotId;
USB_DEV_ROUTE RouteChart;
EFI_STATUS Status;
EFI_TPL OldTpl;
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
Status = EFI_SUCCESS;
TotalPort = (Xhc->HcSParams1.Data.MaxPorts);
if (PortNumber >= TotalPort) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
Offset = (UINT32) (XHC_PORTSC_OFFSET + (0x10 * PortNumber));
State = XhcReadOpReg (Xhc, Offset);
//
// Mask off the port status change bits, these bits are
// write clean bit
//
State &= ~ (BIT1 | BIT17 | BIT18 | BIT19 | BIT20 | BIT21 | BIT22 | BIT23);
switch (PortFeature) {
case EfiUsbPortEnable:
//
// Ports may only be enabled by the xHC. Software cannot enable a port by writing a '1' to this flag.
// A port may be disabled by software writing a '1' to this flag.
//
Status = EFI_SUCCESS;
break;
case EfiUsbPortSuspend:
State |= XHC_PORTSC_LWS;
XhcWriteOpReg (Xhc, Offset, State);
State &= ~XHC_PORTSC_PLS;
State |= (3 << 5) ;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortReset:
DEBUG ((EFI_D_INFO, "XhcUsbPortReset!\n"));
//
// Make sure Host Controller not halt before reset it
//
if (XhcIsHalt (Xhc)) {
Status = XhcRunHC (Xhc, XHC_GENERIC_TIMEOUT);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_INFO, "XhcSetRootHubPortFeature :failed to start HC - %r\n", Status));
break;
}
}
RouteChart.Route.RouteString = 0;
RouteChart.Route.RootPortNum = PortNumber + 1;
RouteChart.Route.TierNum = 1;
//
// If the port reset operation happens after the usb super speed device is enabled,
// The subsequent configuration, such as getting device descriptor, will fail.
// So here a workaround is introduced to skip the reset operation if the device is enabled.
//
SlotId = XhcRouteStringToSlotId (Xhc, RouteChart);
if (SlotId == 0) {
//
// 4.3.1 Resetting a Root Hub Port
// 1) Write the PORTSC register with the Port Reset (PR) bit set to '1'.
//
State |= XHC_PORTSC_RESET;
XhcWriteOpReg (Xhc, Offset, State);
XhcWaitOpRegBit(Xhc, Offset, XHC_PORTSC_PRC, TRUE, XHC_GENERIC_TIMEOUT);
}
break;
case EfiUsbPortPower:
//
// Not supported, ignore the operation
//
Status = EFI_SUCCESS;
break;
case EfiUsbPortOwner:
//
// XHCI root hub port don't has the owner bit, ignore the operation
//
Status = EFI_SUCCESS;
break;
default:
Status = EFI_INVALID_PARAMETER;
}
ON_EXIT:
DEBUG ((EFI_D_INFO, "XhcSetRootHubPortFeature: status %r\n", Status));
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Clears a feature for the specified root hub port.
@param This A pointer to the EFI_USB2_HC_PROTOCOL instance.
@param PortNumber Specifies the root hub port whose feature is
requested to be cleared.
@param PortFeature Indicates the feature selector associated with the
feature clear request.
@retval EFI_SUCCESS The feature specified by PortFeature was cleared
for the USB root hub port specified by PortNumber.
@retval EFI_INVALID_PARAMETER PortNumber is invalid or PortFeature is invalid.
@retval EFI_DEVICE_ERROR Can't read register.
**/
EFI_STATUS
EFIAPI
XhcClearRootHubPortFeature (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 PortNumber,
IN EFI_USB_PORT_FEATURE PortFeature
)
{
USB_XHCI_INSTANCE *Xhc;
UINT32 Offset;
UINT32 State;
UINT32 TotalPort;
EFI_STATUS Status;
EFI_TPL OldTpl;
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
Status = EFI_SUCCESS;
TotalPort = (Xhc->HcSParams1.Data.MaxPorts);
if (PortNumber >= TotalPort) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
Offset = XHC_PORTSC_OFFSET + (0x10 * PortNumber);
//
// Mask off the port status change bits, these bits are
// write clean bit
//
State = XhcReadOpReg (Xhc, Offset);
State &= ~ (BIT1 | BIT17 | BIT18 | BIT19 | BIT20 | BIT21 | BIT22 | BIT23);
switch (PortFeature) {
case EfiUsbPortEnable:
//
// Ports may only be enabled by the xHC. Software cannot enable a port by writing a '1' to this flag.
// A port may be disabled by software writing a '1' to this flag.
//
State |= XHC_PORTSC_PED;
State &= ~XHC_PORTSC_RESET;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortSuspend:
State |= XHC_PORTSC_LWS;
XhcWriteOpReg (Xhc, Offset, State);
State &= ~XHC_PORTSC_PLS;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortReset:
//
// PORTSC_RESET BIT(4) bit is RW1S attribute, which means Write-1-to-set status:
// Register bits indicate status when read, a clear bit may be set by
// writing a '1'. Writing a '0' to RW1S bits has no effect.
//
break;
case EfiUsbPortOwner:
//
// XHCI root hub port don't has the owner bit, ignore the operation
//
break;
case EfiUsbPortConnectChange:
//
// Clear connect status change
//
State |= XHC_PORTSC_CSC;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortEnableChange:
//
// Clear enable status change
//
State |= XHC_PORTSC_PEC;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortOverCurrentChange:
//
// Clear PortOverCurrent change
//
State |= XHC_PORTSC_OCC;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortResetChange:
//
// Clear Port Reset change
//
State |= XHC_PORTSC_PRC;
XhcWriteOpReg (Xhc, Offset, State);
break;
case EfiUsbPortPower:
case EfiUsbPortSuspendChange:
//
// Not supported or not related operation
//
break;
default:
Status = EFI_INVALID_PARAMETER;
break;
}
ON_EXIT:
DEBUG ((EFI_D_INFO, "XhcClearRootHubPortFeature: status %r\n", Status));
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Submits control transfer to a target USB device.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param DeviceAddress The target device address.
@param DeviceSpeed Target device speed.
@param MaximumPacketLength Maximum packet size the default control transfer
endpoint is capable of sending or receiving.
@param Request USB device request to send.
@param TransferDirection Specifies the data direction for the data stage
@param Data Data buffer to be transmitted or received from USB
device.
@param DataLength The size (in bytes) of the data buffer.
@param Timeout Indicates the maximum timeout, in millisecond.
@param Translator Transaction translator to be used by this device.
@param TransferResult Return the result of this control transfer.
@retval EFI_SUCCESS Transfer was completed successfully.
@retval EFI_OUT_OF_RESOURCES The transfer failed due to lack of resources.
@retval EFI_INVALID_PARAMETER Some parameters are invalid.
@retval EFI_TIMEOUT Transfer failed due to timeout.
@retval EFI_DEVICE_ERROR Transfer failed due to host controller or device error.
**/
EFI_STATUS
EFIAPI
XhcControlTransfer (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 DeviceAddress,
IN UINT8 DeviceSpeed,
IN UINTN MaximumPacketLength,
IN EFI_USB_DEVICE_REQUEST *Request,
IN EFI_USB_DATA_DIRECTION TransferDirection,
IN OUT VOID *Data,
IN OUT UINTN *DataLength,
IN UINTN Timeout,
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
OUT UINT32 *TransferResult
)
{
USB_XHCI_INSTANCE *Xhc;
URB *Urb;
UINT8 Endpoint;
UINT8 Index;
UINT8 DescriptorType;
UINT8 SlotId;
UINT8 TTT;
UINT8 MTT;
UINT32 MaxPacket0;
EFI_USB_HUB_DESCRIPTOR *HubDesc;
EFI_TPL OldTpl;
EFI_STATUS Status;
EFI_STATUS RecoveryStatus;
UINTN MapSize;
EFI_USB_PORT_STATUS PortStatus;
UINT32 State;
//
// Validate parameters
//
if ((Request == NULL) || (TransferResult == NULL)) {
return EFI_INVALID_PARAMETER;
}
if ((TransferDirection != EfiUsbDataIn) &&
(TransferDirection != EfiUsbDataOut) &&
(TransferDirection != EfiUsbNoData)) {
return EFI_INVALID_PARAMETER;
}
if ((TransferDirection == EfiUsbNoData) &&
((Data != NULL) || (*DataLength != 0))) {
return EFI_INVALID_PARAMETER;
}
if ((TransferDirection != EfiUsbNoData) &&
((Data == NULL) || (*DataLength == 0))) {
return EFI_INVALID_PARAMETER;
}
if ((MaximumPacketLength != 8) && (MaximumPacketLength != 16) &&
(MaximumPacketLength != 32) && (MaximumPacketLength != 64) &&
(MaximumPacketLength != 512)
) {
return EFI_INVALID_PARAMETER;
}
if ((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8)) {
return EFI_INVALID_PARAMETER;
}
if ((DeviceSpeed == EFI_USB_SPEED_SUPER) && (MaximumPacketLength != 512)) {
return EFI_INVALID_PARAMETER;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
Status = EFI_DEVICE_ERROR;
*TransferResult = EFI_USB_ERR_SYSTEM;
if (XhcIsHalt (Xhc) || XhcIsSysError (Xhc)) {
DEBUG ((EFI_D_ERROR, "XhcControlTransfer: HC halted at entrance\n"));
goto ON_EXIT;
}
//
// Check if the device is still enabled before every transaction.
//
SlotId = XhcBusDevAddrToSlotId (Xhc, DeviceAddress);
if (SlotId == 0) {
goto ON_EXIT;
}
//
// Hook the Set_Address request from UsbBus.
// According to XHCI 1.0 spec, the Set_Address request is replaced by XHCI's Address_Device cmd.
//
if ((Request->Request == USB_REQ_SET_ADDRESS) &&
(Request->RequestType == USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_STANDARD, USB_TARGET_DEVICE))) {
//
// Reset the BusDevAddr field of all disabled entries in UsbDevContext array firstly.
// This way is used to clean the history to avoid using wrong device address by XhcAsyncInterruptTransfer().
//
for (Index = 0; Index < 255; Index++) {
if (!Xhc->UsbDevContext[Index + 1].Enabled &&
(Xhc->UsbDevContext[Index + 1].SlotId == 0) &&
(Xhc->UsbDevContext[Index + 1].BusDevAddr == (UINT8)Request->Value)) {
Xhc->UsbDevContext[Index + 1].BusDevAddr = 0;
}
}
//
// The actual device address has been assigned by XHCI during initializing the device slot.
// So we just need establish the mapping relationship between the device address requested from UsbBus
// and the actual device address assigned by XHCI. The the following invocations through EFI_USB2_HC_PROTOCOL interface
// can find out the actual device address by it.
//
Xhc->UsbDevContext[SlotId].BusDevAddr = (UINT8)Request->Value;
Status = EFI_SUCCESS;
goto ON_EXIT;
}
//
// If the port reset operation happens after the usb super speed device is enabled,
// The subsequent configuration, such as getting device descriptor, will fail.
// So here a workaround is introduced to skip the reset operation if the device is enabled.
//
if ((Request->Request == USB_REQ_SET_FEATURE) &&
(Request->RequestType == USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_CLASS, USB_TARGET_OTHER)) &&
(Request->Value == EfiUsbPortReset)) {
if (DeviceSpeed == EFI_USB_SPEED_SUPER) {
Status = EFI_SUCCESS;
goto ON_EXIT;
}
}
//
// Create a new URB, insert it into the asynchronous
// schedule list, then poll the execution status.
// Note that we encode the direction in address although default control
// endpoint is bidirectional. XhcCreateUrb expects this
// combination of Ep addr and its direction.
//
Endpoint = (UINT8) (0 | ((TransferDirection == EfiUsbDataIn) ? 0x80 : 0));
Urb = XhcCreateUrb (
Xhc,
DeviceAddress,
Endpoint,
DeviceSpeed,
MaximumPacketLength,
XHC_CTRL_TRANSFER,
Request,
Data,
*DataLength,
NULL,
NULL
);
if (Urb == NULL) {
DEBUG ((EFI_D_ERROR, "XhcControlTransfer: failed to create URB"));
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
Status = XhcExecTransfer (Xhc, FALSE, Urb, Timeout);
//
// Get the status from URB. The result is updated in XhcCheckUrbResult
// which is called by XhcExecTransfer
//
*TransferResult = Urb->Result;
*DataLength = Urb->Completed;
if (*TransferResult == EFI_USB_NOERROR) {
Status = EFI_SUCCESS;
} else if (*TransferResult == EFI_USB_ERR_STALL) {
RecoveryStatus = XhcRecoverHaltedEndpoint(Xhc, Urb);
ASSERT_EFI_ERROR (RecoveryStatus);
Status = EFI_DEVICE_ERROR;
goto FREE_URB;
} else {
goto FREE_URB;
}
//
// Hook Get_Descriptor request from UsbBus as we need evaluate context and configure endpoint.
// Hook Get_Status request form UsbBus as we need trace device attach/detach event happened at hub.
// Hook Set_Config request from UsbBus as we need configure device endpoint.
//
if ((Request->Request == USB_REQ_GET_DESCRIPTOR) &&
((Request->RequestType == USB_REQUEST_TYPE (EfiUsbDataIn, USB_REQ_TYPE_STANDARD, USB_TARGET_DEVICE)) ||
((Request->RequestType == USB_REQUEST_TYPE (EfiUsbDataIn, USB_REQ_TYPE_CLASS, USB_TARGET_DEVICE))))) {
DescriptorType = (UINT8)(Request->Value >> 8);
if ((DescriptorType == USB_DESC_TYPE_DEVICE) && (*DataLength == sizeof (EFI_USB_DEVICE_DESCRIPTOR))) {
ASSERT (Data != NULL);
//
// Store a copy of device scriptor as hub device need this info to configure endpoint.
//
CopyMem (&Xhc->UsbDevContext[SlotId].DevDesc, Data, *DataLength);
if (Xhc->UsbDevContext[SlotId].DevDesc.BcdUSB == 0x0300) {
//
// If it's a usb3.0 device, then its max packet size is a 2^n.
//
MaxPacket0 = 1 << Xhc->UsbDevContext[SlotId].DevDesc.MaxPacketSize0;
} else {
MaxPacket0 = Xhc->UsbDevContext[SlotId].DevDesc.MaxPacketSize0;
}
Xhc->UsbDevContext[SlotId].ConfDesc = AllocateZeroPool (Xhc->UsbDevContext[SlotId].DevDesc.NumConfigurations * sizeof (EFI_USB_CONFIG_DESCRIPTOR *));
if (Xhc->HcCParams.Data.Csz == 0) {
Status = XhcEvaluateContext (Xhc, SlotId, MaxPacket0);
} else {
Status = XhcEvaluateContext64 (Xhc, SlotId, MaxPacket0);
}
ASSERT_EFI_ERROR (Status);
} else if (DescriptorType == USB_DESC_TYPE_CONFIG) {
ASSERT (Data != NULL);
if (*DataLength == ((UINT16 *)Data)[1]) {
//
// Get configuration value from request, Store the configuration descriptor for Configure_Endpoint cmd.
//
Index = (UINT8)Request->Value;
ASSERT (Index < Xhc->UsbDevContext[SlotId].DevDesc.NumConfigurations);
Xhc->UsbDevContext[SlotId].ConfDesc[Index] = AllocateZeroPool(*DataLength);
CopyMem (Xhc->UsbDevContext[SlotId].ConfDesc[Index], Data, *DataLength);
}
} else if (((DescriptorType == USB_DESC_TYPE_HUB) ||
(DescriptorType == USB_DESC_TYPE_HUB_SUPER_SPEED)) && (*DataLength > 2)) {
ASSERT (Data != NULL);
HubDesc = (EFI_USB_HUB_DESCRIPTOR *)Data;
ASSERT (HubDesc->NumPorts <= 15);
//
// The bit 5,6 of HubCharacter field of Hub Descriptor is TTT.
//
TTT = (UINT8)((HubDesc->HubCharacter & (BIT5 | BIT6)) >> 5);
if (Xhc->UsbDevContext[SlotId].DevDesc.DeviceProtocol == 2) {
//
// Don't support multi-TT feature for super speed hub now.
//
MTT = 0;
DEBUG ((EFI_D_ERROR, "XHCI: Don't support multi-TT feature for Hub now. (force to disable MTT)\n"));
} else {
MTT = 0;
}
if (Xhc->HcCParams.Data.Csz == 0) {
Status = XhcConfigHubContext (Xhc, SlotId, HubDesc->NumPorts, TTT, MTT);
} else {
Status = XhcConfigHubContext64 (Xhc, SlotId, HubDesc->NumPorts, TTT, MTT);
}
ASSERT_EFI_ERROR (Status);
}
} else if ((Request->Request == USB_REQ_SET_CONFIG) &&
(Request->RequestType == USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_STANDARD, USB_TARGET_DEVICE))) {
//
// Hook Set_Config request from UsbBus as we need configure device endpoint.
//
for (Index = 0; Index < Xhc->UsbDevContext[SlotId].DevDesc.NumConfigurations; Index++) {
if (Xhc->UsbDevContext[SlotId].ConfDesc[Index]->ConfigurationValue == (UINT8)Request->Value) {
if (Xhc->HcCParams.Data.Csz == 0) {
Status = XhcSetConfigCmd (Xhc, SlotId, DeviceSpeed, Xhc->UsbDevContext[SlotId].ConfDesc[Index]);
} else {
Status = XhcSetConfigCmd64 (Xhc, SlotId, DeviceSpeed, Xhc->UsbDevContext[SlotId].ConfDesc[Index]);
}
ASSERT_EFI_ERROR (Status);
break;
}
}
} else if ((Request->Request == USB_REQ_GET_STATUS) &&
(Request->RequestType == USB_REQUEST_TYPE (EfiUsbDataIn, USB_REQ_TYPE_CLASS, USB_TARGET_OTHER))) {
ASSERT (Data != NULL);
//
// Hook Get_Status request from UsbBus to keep track of the port status change.
//
State = *(UINT32 *)Data;
PortStatus.PortStatus = 0;
PortStatus.PortChangeStatus = 0;
if (DeviceSpeed == EFI_USB_SPEED_SUPER) {
//
// For super speed hub, its bit10~12 presents the attached device speed.
//
if ((State & XHC_PORTSC_PS) >> 10 == 0) {
PortStatus.PortStatus |= USB_PORT_STAT_SUPER_SPEED;
}
} else if (DeviceSpeed == EFI_USB_SPEED_HIGH) {
//
// For high speed hub, its bit9~10 presents the attached device speed.
//
if (XHC_BIT_IS_SET (State, BIT9)) {
PortStatus.PortStatus |= USB_PORT_STAT_LOW_SPEED;
} else if (XHC_BIT_IS_SET (State, BIT10)) {
PortStatus.PortStatus |= USB_PORT_STAT_HIGH_SPEED;
}
} else {
ASSERT (0);
}
//
// Convert the XHCI port/port change state to UEFI status
//
MapSize = sizeof (mUsbHubPortStateMap) / sizeof (USB_PORT_STATE_MAP);
for (Index = 0; Index < MapSize; Index++) {
if (XHC_BIT_IS_SET (State, mUsbHubPortStateMap[Index].HwState)) {
PortStatus.PortStatus = (UINT16) (PortStatus.PortStatus | mUsbHubPortStateMap[Index].UefiState);
}
}
MapSize = sizeof (mUsbHubPortChangeMap) / sizeof (USB_PORT_STATE_MAP);
for (Index = 0; Index < MapSize; Index++) {
if (XHC_BIT_IS_SET (State, mUsbHubPortChangeMap[Index].HwState)) {
PortStatus.PortChangeStatus = (UINT16) (PortStatus.PortChangeStatus | mUsbHubPortChangeMap[Index].UefiState);
}
}
XhcPollPortStatusChange (Xhc, Xhc->UsbDevContext[SlotId].RouteString, (UINT8)Request->Index, &PortStatus);
*(UINT32 *)Data = *(UINT32*)&PortStatus;
}
FREE_URB:
FreePool (Urb);
ON_EXIT:
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcControlTransfer: error - %r, transfer - %x\n", Status, *TransferResult));
}
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Submits bulk transfer to a bulk endpoint of a USB device.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param DeviceAddress Target device address.
@param EndPointAddress Endpoint number and its direction in bit 7.
@param DeviceSpeed Device speed, Low speed device doesn't support bulk
transfer.
@param MaximumPacketLength Maximum packet size the endpoint is capable of
sending or receiving.
@param DataBuffersNumber Number of data buffers prepared for the transfer.
@param Data Array of pointers to the buffers of data to transmit
from or receive into.
@param DataLength The lenght of the data buffer.
@param DataToggle On input, the initial data toggle for the transfer;
On output, it is updated to to next data toggle to
use of the subsequent bulk transfer.
@param Timeout Indicates the maximum time, in millisecond, which
the transfer is allowed to complete.
@param Translator A pointr to the transaction translator data.
@param TransferResult A pointer to the detailed result information of the
bulk transfer.
@retval EFI_SUCCESS The transfer was completed successfully.
@retval EFI_OUT_OF_RESOURCES The transfer failed due to lack of resource.
@retval EFI_INVALID_PARAMETER Some parameters are invalid.
@retval EFI_TIMEOUT The transfer failed due to timeout.
@retval EFI_DEVICE_ERROR The transfer failed due to host controller error.
**/
EFI_STATUS
EFIAPI
XhcBulkTransfer (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 DeviceAddress,
IN UINT8 EndPointAddress,
IN UINT8 DeviceSpeed,
IN UINTN MaximumPacketLength,
IN UINT8 DataBuffersNumber,
IN OUT VOID *Data[EFI_USB_MAX_BULK_BUFFER_NUM],
IN OUT UINTN *DataLength,
IN OUT UINT8 *DataToggle,
IN UINTN Timeout,
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
OUT UINT32 *TransferResult
)
{
USB_XHCI_INSTANCE *Xhc;
URB *Urb;
UINT8 SlotId;
EFI_STATUS Status;
EFI_STATUS RecoveryStatus;
EFI_TPL OldTpl;
//
// Validate the parameters
//
if ((DataLength == NULL) || (*DataLength == 0) ||
(Data == NULL) || (Data[0] == NULL) || (TransferResult == NULL)) {
return EFI_INVALID_PARAMETER;
}
if ((*DataToggle != 0) && (*DataToggle != 1)) {
return EFI_INVALID_PARAMETER;
}
if ((DeviceSpeed == EFI_USB_SPEED_LOW) ||
((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) ||
((EFI_USB_SPEED_HIGH == DeviceSpeed) && (MaximumPacketLength > 512)) ||
((EFI_USB_SPEED_SUPER == DeviceSpeed) && (MaximumPacketLength > 1024))) {
return EFI_INVALID_PARAMETER;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
*TransferResult = EFI_USB_ERR_SYSTEM;
Status = EFI_DEVICE_ERROR;
if (XhcIsHalt (Xhc) || XhcIsSysError (Xhc)) {
DEBUG ((EFI_D_ERROR, "XhcBulkTransfer: HC is halted\n"));
goto ON_EXIT;
}
//
// Check if the device is still enabled before every transaction.
//
SlotId = XhcBusDevAddrToSlotId (Xhc, DeviceAddress);
if (SlotId == 0) {
goto ON_EXIT;
}
//
// Create a new URB, insert it into the asynchronous
// schedule list, then poll the execution status.
//
Urb = XhcCreateUrb (
Xhc,
DeviceAddress,
EndPointAddress,
DeviceSpeed,
MaximumPacketLength,
XHC_BULK_TRANSFER,
NULL,
Data[0],
*DataLength,
NULL,
NULL
);
if (Urb == NULL) {
DEBUG ((EFI_D_ERROR, "XhcBulkTransfer: failed to create URB\n"));
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
Status = XhcExecTransfer (Xhc, FALSE, Urb, Timeout);
*TransferResult = Urb->Result;
*DataLength = Urb->Completed;
if (*TransferResult == EFI_USB_NOERROR) {
Status = EFI_SUCCESS;
} else if (*TransferResult == EFI_USB_ERR_STALL) {
RecoveryStatus = XhcRecoverHaltedEndpoint(Xhc, Urb);
ASSERT_EFI_ERROR (RecoveryStatus);
Status = EFI_DEVICE_ERROR;
}
FreePool (Urb);
ON_EXIT:
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcBulkTransfer: error - %r, transfer - %x\n", Status, *TransferResult));
}
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Submits an asynchronous interrupt transfer to an
interrupt endpoint of a USB device.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param DeviceAddress Target device address.
@param EndPointAddress Endpoint number and its direction encoded in bit 7
@param DeviceSpeed Indicates device speed.
@param MaximumPacketLength Maximum packet size the target endpoint is capable
@param IsNewTransfer If TRUE, to submit an new asynchronous interrupt
transfer If FALSE, to remove the specified
asynchronous interrupt.
@param DataToggle On input, the initial data toggle to use; on output,
it is updated to indicate the next data toggle.
@param PollingInterval The he interval, in milliseconds, that the transfer
is polled.
@param DataLength The length of data to receive at the rate specified
by PollingInterval.
@param Translator Transaction translator to use.
@param CallBackFunction Function to call at the rate specified by
PollingInterval.
@param Context Context to CallBackFunction.
@retval EFI_SUCCESS The request has been successfully submitted or canceled.
@retval EFI_INVALID_PARAMETER Some parameters are invalid.
@retval EFI_OUT_OF_RESOURCES The request failed due to a lack of resources.
@retval EFI_DEVICE_ERROR The transfer failed due to host controller error.
**/
EFI_STATUS
EFIAPI
XhcAsyncInterruptTransfer (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 DeviceAddress,
IN UINT8 EndPointAddress,
IN UINT8 DeviceSpeed,
IN UINTN MaximumPacketLength,
IN BOOLEAN IsNewTransfer,
IN OUT UINT8 *DataToggle,
IN UINTN PollingInterval,
IN UINTN DataLength,
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
IN EFI_ASYNC_USB_TRANSFER_CALLBACK CallBackFunction,
IN VOID *Context OPTIONAL
)
{
USB_XHCI_INSTANCE *Xhc;
URB *Urb;
EFI_STATUS Status;
UINT8 SlotId;
UINT8 Index;
UINT8 *Data;
EFI_TPL OldTpl;
//
// Validate parameters
//
if (!XHCI_IS_DATAIN (EndPointAddress)) {
return EFI_INVALID_PARAMETER;
}
if (IsNewTransfer) {
if (DataLength == 0) {
return EFI_INVALID_PARAMETER;
}
if ((*DataToggle != 1) && (*DataToggle != 0)) {
return EFI_INVALID_PARAMETER;
}
if ((PollingInterval > 255) || (PollingInterval < 1)) {
return EFI_INVALID_PARAMETER;
}
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
//
// Delete Async interrupt transfer request.
//
if (!IsNewTransfer) {
//
// The delete request may happen after device is detached.
//
for (Index = 0; Index < 255; Index++) {
if (Xhc->UsbDevContext[Index + 1].BusDevAddr == DeviceAddress) {
break;
}
}
if (Index == 255) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
Status = XhciDelAsyncIntTransfer (Xhc, DeviceAddress, EndPointAddress);
DEBUG ((EFI_D_INFO, "XhcAsyncInterruptTransfer: remove old transfer for addr %d, Status = %r\n", DeviceAddress, Status));
goto ON_EXIT;
}
Status = EFI_SUCCESS;
if (XhcIsHalt (Xhc) || XhcIsSysError (Xhc)) {
DEBUG ((EFI_D_ERROR, "XhcAsyncInterruptTransfer: HC is halt\n"));
Status = EFI_DEVICE_ERROR;
goto ON_EXIT;
}
//
// Check if the device is still enabled before every transaction.
//
SlotId = XhcBusDevAddrToSlotId (Xhc, DeviceAddress);
if (SlotId == 0) {
goto ON_EXIT;
}
Data = AllocateZeroPool (DataLength);
if (Data == NULL) {
DEBUG ((EFI_D_ERROR, "XhcAsyncInterruptTransfer: failed to allocate buffer\n"));
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
Urb = XhcCreateUrb (
Xhc,
DeviceAddress,
EndPointAddress,
DeviceSpeed,
MaximumPacketLength,
XHC_INT_TRANSFER_ASYNC,
NULL,
Data,
DataLength,
CallBackFunction,
Context
);
if (Urb == NULL) {
DEBUG ((EFI_D_ERROR, "XhcAsyncInterruptTransfer: failed to create URB\n"));
FreePool (Data);
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
InsertHeadList (&Xhc->AsyncIntTransfers, &Urb->UrbList);
//
// Ring the doorbell
//
Status = RingIntTransferDoorBell (Xhc, Urb);
ON_EXIT:
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Submits synchronous interrupt transfer to an interrupt endpoint
of a USB device.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param DeviceAddress Target device address.
@param EndPointAddress Endpoint number and its direction encoded in bit 7
@param DeviceSpeed Indicates device speed.
@param MaximumPacketLength Maximum packet size the target endpoint is capable
of sending or receiving.
@param Data Buffer of data that will be transmitted to USB
device or received from USB device.
@param DataLength On input, the size, in bytes, of the data buffer; On
output, the number of bytes transferred.
@param DataToggle On input, the initial data toggle to use; on output,
it is updated to indicate the next data toggle.
@param Timeout Maximum time, in second, to complete.
@param Translator Transaction translator to use.
@param TransferResult Variable to receive the transfer result.
@return EFI_SUCCESS The transfer was completed successfully.
@return EFI_OUT_OF_RESOURCES The transfer failed due to lack of resource.
@return EFI_INVALID_PARAMETER Some parameters are invalid.
@return EFI_TIMEOUT The transfer failed due to timeout.
@return EFI_DEVICE_ERROR The failed due to host controller or device error
**/
EFI_STATUS
EFIAPI
XhcSyncInterruptTransfer (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 DeviceAddress,
IN UINT8 EndPointAddress,
IN UINT8 DeviceSpeed,
IN UINTN MaximumPacketLength,
IN OUT VOID *Data,
IN OUT UINTN *DataLength,
IN OUT UINT8 *DataToggle,
IN UINTN Timeout,
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
OUT UINT32 *TransferResult
)
{
USB_XHCI_INSTANCE *Xhc;
URB *Urb;
UINT8 SlotId;
EFI_STATUS Status;
EFI_STATUS RecoveryStatus;
EFI_TPL OldTpl;
//
// Validates parameters
//
if ((DataLength == NULL) || (*DataLength == 0) ||
(Data == NULL) || (TransferResult == NULL)) {
return EFI_INVALID_PARAMETER;
}
if (!XHCI_IS_DATAIN (EndPointAddress)) {
return EFI_INVALID_PARAMETER;
}
if ((*DataToggle != 1) && (*DataToggle != 0)) {
return EFI_INVALID_PARAMETER;
}
if (((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8)) ||
((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) ||
((DeviceSpeed == EFI_USB_SPEED_HIGH) && (MaximumPacketLength > 3072))) {
return EFI_INVALID_PARAMETER;
}
OldTpl = gBS->RaiseTPL (XHC_TPL);
Xhc = XHC_FROM_THIS (This);
*TransferResult = EFI_USB_ERR_SYSTEM;
Status = EFI_DEVICE_ERROR;
if (XhcIsHalt (Xhc) || XhcIsSysError (Xhc)) {
DEBUG ((EFI_D_ERROR, "EhcSyncInterruptTransfer: HC is halt\n"));
goto ON_EXIT;
}
//
// Check if the device is still enabled before every transaction.
//
SlotId = XhcBusDevAddrToSlotId (Xhc, DeviceAddress);
if (SlotId == 0) {
goto ON_EXIT;
}
Urb = XhcCreateUrb (
Xhc,
DeviceAddress,
EndPointAddress,
DeviceSpeed,
MaximumPacketLength,
XHC_INT_TRANSFER_SYNC,
NULL,
Data,
*DataLength,
NULL,
NULL
);
if (Urb == NULL) {
DEBUG ((EFI_D_ERROR, "XhcSyncInterruptTransfer: failed to create URB\n"));
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
Status = XhcExecTransfer (Xhc, FALSE, Urb, Timeout);
*TransferResult = Urb->Result;
*DataLength = Urb->Completed;
if (*TransferResult == EFI_USB_NOERROR) {
Status = EFI_SUCCESS;
} else if (*TransferResult == EFI_USB_ERR_STALL) {
RecoveryStatus = XhcRecoverHaltedEndpoint(Xhc, Urb);
ASSERT_EFI_ERROR (RecoveryStatus);
Status = EFI_DEVICE_ERROR;
}
FreePool (Urb);
ON_EXIT:
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcSyncInterruptTransfer: error - %r, transfer - %x\n", Status, *TransferResult));
}
gBS->RestoreTPL (OldTpl);
return Status;
}
/**
Submits isochronous transfer to a target USB device.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param DeviceAddress Target device address.
@param EndPointAddress End point address with its direction.
@param DeviceSpeed Device speed, Low speed device doesn't support this
type.
@param MaximumPacketLength Maximum packet size that the endpoint is capable of
sending or receiving.
@param DataBuffersNumber Number of data buffers prepared for the transfer.
@param Data Array of pointers to the buffers of data that will
be transmitted to USB device or received from USB
device.
@param DataLength The size, in bytes, of the data buffer.
@param Translator Transaction translator to use.
@param TransferResult Variable to receive the transfer result.
@return EFI_UNSUPPORTED Isochronous transfer is unsupported.
**/
EFI_STATUS
EFIAPI
XhcIsochronousTransfer (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 DeviceAddress,
IN UINT8 EndPointAddress,
IN UINT8 DeviceSpeed,
IN UINTN MaximumPacketLength,
IN UINT8 DataBuffersNumber,
IN OUT VOID *Data[EFI_USB_MAX_ISO_BUFFER_NUM],
IN UINTN DataLength,
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
OUT UINT32 *TransferResult
)
{
return EFI_UNSUPPORTED;
}
/**
Submits Async isochronous transfer to a target USB device.
@param This This EFI_USB2_HC_PROTOCOL instance.
@param DeviceAddress Target device address.
@param EndPointAddress End point address with its direction.
@param DeviceSpeed Device speed, Low speed device doesn't support this
type.
@param MaximumPacketLength Maximum packet size that the endpoint is capable of
sending or receiving.
@param DataBuffersNumber Number of data buffers prepared for the transfer.
@param Data Array of pointers to the buffers of data that will
be transmitted to USB device or received from USB
device.
@param DataLength The size, in bytes, of the data buffer.
@param Translator Transaction translator to use.
@param IsochronousCallBack Function to be called when the transfer complete.
@param Context Context passed to the call back function as
parameter.
@return EFI_UNSUPPORTED Isochronous transfer isn't supported.
**/
EFI_STATUS
EFIAPI
XhcAsyncIsochronousTransfer (
IN EFI_USB2_HC_PROTOCOL *This,
IN UINT8 DeviceAddress,
IN UINT8 EndPointAddress,
IN UINT8 DeviceSpeed,
IN UINTN MaximumPacketLength,
IN UINT8 DataBuffersNumber,
IN OUT VOID *Data[EFI_USB_MAX_ISO_BUFFER_NUM],
IN UINTN DataLength,
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
IN EFI_ASYNC_USB_TRANSFER_CALLBACK IsochronousCallBack,
IN VOID *Context
)
{
return EFI_UNSUPPORTED;
}
/**
Entry point for EFI drivers.
@param ImageHandle EFI_HANDLE.
@param SystemTable EFI_SYSTEM_TABLE.
@retval EFI_SUCCESS Success.
@retval Others Fail.
**/
EFI_STATUS
EFIAPI
XhcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gXhciDriverBinding,
ImageHandle,
&gXhciComponentName,
&gXhciComponentName2
);
}
/**
Test to see if this driver supports ControllerHandle. Any
ControllerHandle that has Usb2HcProtocol installed will
be supported.
@param This Protocol instance pointer.
@param Controller Handle of device to test.
@param RemainingDevicePath Not used.
@return EFI_SUCCESS This driver supports this device.
@return EFI_UNSUPPORTED This driver does not support this device.
**/
EFI_STATUS
EFIAPI
XhcDriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
USB_CLASSC UsbClassCReg;
//
// Test whether there is PCI IO Protocol attached on the controller handle.
//
Status = gBS->OpenProtocol (
Controller,
&gEfiPciIoProtocolGuid,
(VOID **) &PciIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
Status = PciIo->Pci.Read (
PciIo,
EfiPciIoWidthUint8,
PCI_CLASSCODE_OFFSET,
sizeof (USB_CLASSC) / sizeof (UINT8),
&UsbClassCReg
);
if (EFI_ERROR (Status)) {
Status = EFI_UNSUPPORTED;
goto ON_EXIT;
}
//
// Test whether the controller belongs to Xhci type
//
if ((UsbClassCReg.BaseCode != PCI_CLASS_SERIAL) ||
(UsbClassCReg.SubClassCode != PCI_CLASS_SERIAL_USB) ||
(UsbClassCReg.ProgInterface != PCI_IF_XHCI)) {
Status = EFI_UNSUPPORTED;
}
ON_EXIT:
gBS->CloseProtocol (
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
return Status;
}
/**
Create and initialize a USB_XHCI_INSTANCE structure.
@param PciIo The PciIo on this device.
@param OriginalPciAttributes Original PCI attributes.
@return The allocated and initialized USB_XHCI_INSTANCE structure if created,
otherwise NULL.
**/
USB_XHCI_INSTANCE*
XhcCreateUsbHc (
IN EFI_PCI_IO_PROTOCOL *PciIo,
IN UINT64 OriginalPciAttributes
)
{
USB_XHCI_INSTANCE *Xhc;
EFI_STATUS Status;
UINT32 PageSize;
UINT16 ExtCapReg;
Xhc = AllocateZeroPool (sizeof (USB_XHCI_INSTANCE));
if (Xhc == NULL) {
return NULL;
}
//
// Initialize private data structure
//
Xhc->Signature = XHCI_INSTANCE_SIG;
Xhc->PciIo = PciIo;
Xhc->OriginalPciAttributes = OriginalPciAttributes;
CopyMem (&Xhc->Usb2Hc, &gXhciUsb2HcTemplate, sizeof (EFI_USB2_HC_PROTOCOL));
InitializeListHead (&Xhc->AsyncIntTransfers);
//
// Be caution that the Offset passed to XhcReadCapReg() should be Dword align
//
Xhc->CapLength = XhcReadCapReg8 (Xhc, XHC_CAPLENGTH_OFFSET);
Xhc->HcSParams1.Dword = XhcReadCapReg (Xhc, XHC_HCSPARAMS1_OFFSET);
Xhc->HcSParams2.Dword = XhcReadCapReg (Xhc, XHC_HCSPARAMS2_OFFSET);
Xhc->HcCParams.Dword = XhcReadCapReg (Xhc, XHC_HCCPARAMS_OFFSET);
Xhc->DBOff = XhcReadCapReg (Xhc, XHC_DBOFF_OFFSET);
Xhc->RTSOff = XhcReadCapReg (Xhc, XHC_RTSOFF_OFFSET);
//
// This PageSize field defines the page size supported by the xHC implementation.
// This xHC supports a page size of 2^(n+12) if bit n is Set. For example,
// if bit 0 is Set, the xHC supports 4k byte page sizes.
//
PageSize = XhcReadOpReg(Xhc, XHC_PAGESIZE_OFFSET) & XHC_PAGESIZE_MASK;
Xhc->PageSize = 1 << (HighBitSet32(PageSize) + 12);
ExtCapReg = (UINT16) (Xhc->HcCParams.Data.ExtCapReg);
Xhc->ExtCapRegBase = ExtCapReg << 2;
Xhc->UsbLegSupOffset = XhcGetLegSupCapAddr (Xhc);
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: Capability length 0x%x\n", Xhc->CapLength));
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: HcSParams1 0x%x\n", Xhc->HcSParams1));
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: HcSParams2 0x%x\n", Xhc->HcSParams2));
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: HcCParams 0x%x\n", Xhc->HcCParams));
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: DBOff 0x%x\n", Xhc->DBOff));
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: RTSOff 0x%x\n", Xhc->RTSOff));
DEBUG ((EFI_D_INFO, "XhcCreateUsb3Hc: UsbLegSupOffset 0x%x\n", Xhc->UsbLegSupOffset));
//
// Create AsyncRequest Polling Timer
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
XhcMonitorAsyncRequests,
Xhc,
&Xhc->PollTimer
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
return Xhc;
ON_ERROR:
FreePool (Xhc);
return NULL;
}
/**
One notified function to stop the Host Controller when gBS->ExitBootServices() called.
@param Event Pointer to this event
@param Context Event hanlder private data
**/
VOID
EFIAPI
XhcExitBootService (
EFI_EVENT Event,
VOID *Context
)
{
USB_XHCI_INSTANCE *Xhc;
EFI_PCI_IO_PROTOCOL *PciIo;
Xhc = (USB_XHCI_INSTANCE*) Context;
PciIo = Xhc->PciIo;
//
// Stop AsyncRequest Polling timer then stop the XHCI driver
// and uninstall the XHCI protocl.
//
gBS->SetTimer (Xhc->PollTimer, TimerCancel, 0);
XhcHaltHC (Xhc, XHC_GENERIC_TIMEOUT);
if (Xhc->PollTimer != NULL) {
gBS->CloseEvent (Xhc->PollTimer);
}
//
// Restore original PCI attributes
//
PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationSet,
Xhc->OriginalPciAttributes,
NULL
);
XhcClearBiosOwnership (Xhc);
}
/**
Starting the Usb XHCI Driver.
@param This Protocol instance pointer.
@param Controller Handle of device to test.
@param RemainingDevicePath Not used.
@return EFI_SUCCESS supports this device.
@return EFI_UNSUPPORTED do not support this device.
@return EFI_DEVICE_ERROR cannot be started due to device Error.
@return EFI_OUT_OF_RESOURCES cannot allocate resources.
**/
EFI_STATUS
EFIAPI
XhcDriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT64 Supports;
UINT64 OriginalPciAttributes;
BOOLEAN PciAttributesSaved;
USB_XHCI_INSTANCE *Xhc;
//
// Open the PciIo Protocol, then enable the USB host controller
//
Status = gBS->OpenProtocol (
Controller,
&gEfiPciIoProtocolGuid,
(VOID **) &PciIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
PciAttributesSaved = FALSE;
//
// Save original PCI attributes
//
Status = PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationGet,
0,
&OriginalPciAttributes
);
if (EFI_ERROR (Status)) {
goto CLOSE_PCIIO;
}
PciAttributesSaved = TRUE;
Status = PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationSupported,
0,
&Supports
);
if (!EFI_ERROR (Status)) {
Supports &= EFI_PCI_DEVICE_ENABLE;
Status = PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationEnable,
Supports,
NULL
);
}
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcDriverBindingStart: failed to enable controller\n"));
goto CLOSE_PCIIO;
}
//
// Create then install USB2_HC_PROTOCOL
//
Xhc = XhcCreateUsbHc (PciIo, OriginalPciAttributes);
if (Xhc == NULL) {
DEBUG ((EFI_D_ERROR, "XhcDriverBindingStart: failed to create USB2_HC\n"));
return EFI_OUT_OF_RESOURCES;
}
XhcSetBiosOwnership (Xhc);
XhcResetHC (Xhc, XHC_RESET_TIMEOUT);
ASSERT (XhcIsHalt (Xhc));
//
// After Chip Hardware Reset wait until the Controller Not Ready (CNR) flag
// in the USBSTS is '0' before writing any xHC Operational or Runtime registers.
//
ASSERT (!(XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_CNR)));
//
// Initialize the schedule
//
XhcInitSched (Xhc);
//
// Start the Host Controller
//
XhcRunHC(Xhc, XHC_GENERIC_TIMEOUT);
//
// Start the asynchronous interrupt monitor
//
Status = gBS->SetTimer (Xhc->PollTimer, TimerPeriodic, XHC_ASYNC_TIMER_INTERVAL);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcDriverBindingStart: failed to start async interrupt monitor\n"));
XhcHaltHC (Xhc, XHC_GENERIC_TIMEOUT);
goto FREE_POOL;
}
//
// Create event to stop the HC when exit boot service.
//
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
XhcExitBootService,
Xhc,
&gEfiEventExitBootServicesGuid,
&Xhc->ExitBootServiceEvent
);
if (EFI_ERROR (Status)) {
goto FREE_POOL;
}
//
// Install the component name protocol, don't fail the start
// because of something for display.
//
AddUnicodeString2 (
"eng",
gXhciComponentName.SupportedLanguages,
&Xhc->ControllerNameTable,
L"eXtensible Host Controller (USB 3.0)",
TRUE
);
AddUnicodeString2 (
"en",
gXhciComponentName2.SupportedLanguages,
&Xhc->ControllerNameTable,
L"eXtensible Host Controller (USB 3.0)",
FALSE
);
Status = gBS->InstallProtocolInterface (
&Controller,
&gEfiUsb2HcProtocolGuid,
EFI_NATIVE_INTERFACE,
&Xhc->Usb2Hc
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcDriverBindingStart: failed to install USB2_HC Protocol\n"));
goto FREE_POOL;
}
DEBUG ((EFI_D_INFO, "XhcDriverBindingStart: XHCI started for controller @ %x\n", Controller));
return EFI_SUCCESS;
FREE_POOL:
gBS->CloseEvent (Xhc->PollTimer);
XhcFreeSched (Xhc);
FreePool (Xhc);
CLOSE_PCIIO:
if (PciAttributesSaved) {
//
// Restore original PCI attributes
//
PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationSet,
OriginalPciAttributes,
NULL
);
}
gBS->CloseProtocol (
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
return Status;
}
/**
Stop this driver on ControllerHandle. Support stoping any child handles
created by this driver.
@param This Protocol instance pointer.
@param Controller Handle of device to stop driver on.
@param NumberOfChildren Number of Children in the ChildHandleBuffer.
@param ChildHandleBuffer List of handles for the children we need to stop.
@return EFI_SUCCESS Success.
@return EFI_DEVICE_ERROR Fail.
**/
EFI_STATUS
EFIAPI
XhcDriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
EFI_USB2_HC_PROTOCOL *Usb2Hc;
EFI_PCI_IO_PROTOCOL *PciIo;
USB_XHCI_INSTANCE *Xhc;
UINT8 Index;
//
// Test whether the Controller handler passed in is a valid
// Usb controller handle that should be supported, if not,
// return the error status directly
//
Status = gBS->OpenProtocol (
Controller,
&gEfiUsb2HcProtocolGuid,
(VOID **) &Usb2Hc,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return Status;
}
Xhc = XHC_FROM_THIS (Usb2Hc);
PciIo = Xhc->PciIo;
//
// Stop AsyncRequest Polling timer then stop the XHCI driver
// and uninstall the XHCI protocl.
//
gBS->SetTimer (Xhc->PollTimer, TimerCancel, 0);
//
// Disable the device slots occupied by these devices on its downstream ports.
// Entry 0 is reserved.
//
for (Index = 0; Index < 255; Index++) {
if (!Xhc->UsbDevContext[Index + 1].Enabled ||
(Xhc->UsbDevContext[Index + 1].SlotId == 0)) {
continue;
}
if (Xhc->HcCParams.Data.Csz == 0) {
XhcDisableSlotCmd (Xhc, Xhc->UsbDevContext[Index + 1].SlotId);
} else {
XhcDisableSlotCmd64 (Xhc, Xhc->UsbDevContext[Index + 1].SlotId);
}
}
XhcHaltHC (Xhc, XHC_GENERIC_TIMEOUT);
XhcClearBiosOwnership (Xhc);
Status = gBS->UninstallProtocolInterface (
Controller,
&gEfiUsb2HcProtocolGuid,
Usb2Hc
);
if (EFI_ERROR (Status)) {
return Status;
}
if (Xhc->PollTimer != NULL) {
gBS->CloseEvent (Xhc->PollTimer);
}
if (Xhc->ExitBootServiceEvent != NULL) {
gBS->CloseEvent (Xhc->ExitBootServiceEvent);
}
XhciDelAllAsyncIntTransfers (Xhc);
XhcFreeSched (Xhc);
if (Xhc->ControllerNameTable) {
FreeUnicodeStringTable (Xhc->ControllerNameTable);
}
//
// Restore original PCI attributes
//
PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationSet,
Xhc->OriginalPciAttributes,
NULL
);
gBS->CloseProtocol (
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
FreePool (Xhc);
return EFI_SUCCESS;
}