/** @file | |
The Ehci controller driver. | |
EhciDxe driver is responsible for managing the behavior of EHCI controller. | |
It implements the interfaces of monitoring the status of all ports and transferring | |
Control, Bulk, Interrupt and Isochronous requests to Usb2.0 device. | |
Note that EhciDxe driver is enhanced to guarantee that the EHCI controller get attached | |
to the EHCI controller before a UHCI or OHCI driver attaches to the companion UHCI or | |
OHCI controller. This way avoids the control transfer on a shared port between EHCI | |
and companion host controller when UHCI or OHCI gets attached earlier than EHCI and a | |
USB 2.0 device inserts. | |
Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "Ehci.h" | |
// | |
// Two arrays used to translate the EHCI port state (change) | |
// to the UEFI protocol's port state (change). | |
// | |
USB_PORT_STATE_MAP mUsbPortStateMap[] = { | |
{ PORTSC_CONN, USB_PORT_STAT_CONNECTION }, | |
{ PORTSC_ENABLED, USB_PORT_STAT_ENABLE }, | |
{ PORTSC_SUSPEND, USB_PORT_STAT_SUSPEND }, | |
{ PORTSC_OVERCUR, USB_PORT_STAT_OVERCURRENT }, | |
{ PORTSC_RESET, USB_PORT_STAT_RESET }, | |
{ PORTSC_POWER, USB_PORT_STAT_POWER }, | |
{ PORTSC_OWNER, USB_PORT_STAT_OWNER } | |
}; | |
USB_PORT_STATE_MAP mUsbPortChangeMap[] = { | |
{ PORTSC_CONN_CHANGE, USB_PORT_STAT_C_CONNECTION }, | |
{ PORTSC_ENABLE_CHANGE, USB_PORT_STAT_C_ENABLE }, | |
{ PORTSC_OVERCUR_CHANGE, USB_PORT_STAT_C_OVERCURRENT } | |
}; | |
EFI_DRIVER_BINDING_PROTOCOL | |
gEhciDriverBinding = { | |
EhcDriverBindingSupported, | |
EhcDriverBindingStart, | |
EhcDriverBindingStop, | |
0x30, | |
NULL, | |
NULL | |
}; | |
/** | |
Retrieves the capability of root hub ports. | |
@param This This EFI_USB_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 | |
EhcGetCapability ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
OUT UINT8 *MaxSpeed, | |
OUT UINT8 *PortNumber, | |
OUT UINT8 *Is64BitCapable | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
if ((MaxSpeed == NULL) || (PortNumber == NULL) || (Is64BitCapable == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
*MaxSpeed = EFI_USB_SPEED_HIGH; | |
*PortNumber = (UINT8)(Ehc->HcStructParams & HCSP_NPORTS); | |
*Is64BitCapable = (UINT8)Ehc->Support64BitDma; | |
DEBUG ((DEBUG_INFO, "EhcGetCapability: %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 | |
EhcReset ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
IN UINT16 Attributes | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
EFI_STATUS Status; | |
Ehc = EHC_FROM_THIS (This); | |
if (Ehc->DevicePath != NULL) { | |
// | |
// Report Status Code to indicate reset happens | |
// | |
REPORT_STATUS_CODE_WITH_DEVICE_PATH ( | |
EFI_PROGRESS_CODE, | |
(EFI_IO_BUS_USB | EFI_IOB_PC_RESET), | |
Ehc->DevicePath | |
); | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
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 (EhcIsDebugPortInUse (Ehc, NULL)) { | |
Status = EFI_SUCCESS; | |
goto ON_EXIT; | |
} | |
if (!EhcIsHalt (Ehc)) { | |
Status = EhcHaltHC (Ehc, EHC_GENERIC_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_DEVICE_ERROR; | |
goto ON_EXIT; | |
} | |
} | |
// | |
// Clean up the asynchronous transfers, currently only | |
// interrupt supports asynchronous operation. | |
// | |
EhciDelAllAsyncIntTransfers (Ehc); | |
EhcAckAllInterrupt (Ehc); | |
EhcFreeSched (Ehc); | |
Status = EhcResetHC (Ehc, EHC_RESET_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
Status = EhcInitHC (Ehc); | |
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 ((DEBUG_INFO, "EhcReset: exit 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 | |
EhcGetState ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
OUT EFI_USB_HC_STATE *State | |
) | |
{ | |
EFI_TPL OldTpl; | |
USB2_HC_DEV *Ehc; | |
if (State == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
if (EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT)) { | |
*State = EfiUsbHcStateHalt; | |
} else { | |
*State = EfiUsbHcStateOperational; | |
} | |
gBS->RestoreTPL (OldTpl); | |
DEBUG ((DEBUG_INFO, "EhcGetState: current state %d\n", *State)); | |
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 | |
EhcSetState ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
IN EFI_USB_HC_STATE State | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
EFI_STATUS Status; | |
EFI_USB_HC_STATE CurState; | |
Status = EhcGetState (This, &CurState); | |
if (EFI_ERROR (Status)) { | |
return EFI_DEVICE_ERROR; | |
} | |
if (CurState == State) { | |
return EFI_SUCCESS; | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
switch (State) { | |
case EfiUsbHcStateHalt: | |
Status = EhcHaltHC (Ehc, EHC_GENERIC_TIMEOUT); | |
break; | |
case EfiUsbHcStateOperational: | |
if (EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_SYS_ERROR)) { | |
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[EHCI1.0-2.3.1] | |
// | |
if (!EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT)) { | |
Status = EFI_DEVICE_ERROR; | |
break; | |
} | |
Status = EhcRunHC (Ehc, EHC_GENERIC_TIMEOUT); | |
break; | |
case EfiUsbHcStateSuspend: | |
Status = EFI_UNSUPPORTED; | |
break; | |
default: | |
Status = EFI_INVALID_PARAMETER; | |
} | |
DEBUG ((DEBUG_INFO, "EhcSetState: exit 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 | |
EhcGetRootHubPortStatus ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
IN UINT8 PortNumber, | |
OUT EFI_USB_PORT_STATUS *PortStatus | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
UINT32 Offset; | |
UINT32 State; | |
UINT32 TotalPort; | |
UINTN Index; | |
UINTN MapSize; | |
EFI_STATUS Status; | |
if (PortStatus == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
Status = EFI_SUCCESS; | |
TotalPort = (Ehc->HcStructParams & HCSP_NPORTS); | |
if (PortNumber >= TotalPort) { | |
Status = EFI_INVALID_PARAMETER; | |
goto ON_EXIT; | |
} | |
Offset = (UINT32)(EHC_PORT_STAT_OFFSET + (4 * PortNumber)); | |
PortStatus->PortStatus = 0; | |
PortStatus->PortChangeStatus = 0; | |
if (EhcIsDebugPortInUse (Ehc, &PortNumber)) { | |
goto ON_EXIT; | |
} | |
State = EhcReadOpReg (Ehc, Offset); | |
// | |
// Identify device speed. If in K state, it is low speed. | |
// If the port is enabled after reset, the device is of | |
// high speed. The USB bus driver should retrieve the actual | |
// port speed after reset. | |
// | |
if (EHC_BIT_IS_SET (State, PORTSC_LINESTATE_K)) { | |
PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED; | |
} else if (EHC_BIT_IS_SET (State, PORTSC_ENABLED)) { | |
PortStatus->PortStatus |= USB_PORT_STAT_HIGH_SPEED; | |
} | |
// | |
// Convert the EHCI port/port change state to UEFI status | |
// | |
MapSize = sizeof (mUsbPortStateMap) / sizeof (USB_PORT_STATE_MAP); | |
for (Index = 0; Index < MapSize; Index++) { | |
if (EHC_BIT_IS_SET (State, mUsbPortStateMap[Index].HwState)) { | |
PortStatus->PortStatus = (UINT16)(PortStatus->PortStatus | mUsbPortStateMap[Index].UefiState); | |
} | |
} | |
MapSize = sizeof (mUsbPortChangeMap) / sizeof (USB_PORT_STATE_MAP); | |
for (Index = 0; Index < MapSize; Index++) { | |
if (EHC_BIT_IS_SET (State, mUsbPortChangeMap[Index].HwState)) { | |
PortStatus->PortChangeStatus = (UINT16)(PortStatus->PortChangeStatus | mUsbPortChangeMap[Index].UefiState); | |
} | |
} | |
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 | |
EhcSetRootHubPortFeature ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
IN UINT8 PortNumber, | |
IN EFI_USB_PORT_FEATURE PortFeature | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
UINT32 Offset; | |
UINT32 State; | |
UINT32 TotalPort; | |
EFI_STATUS Status; | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
Status = EFI_SUCCESS; | |
TotalPort = (Ehc->HcStructParams & HCSP_NPORTS); | |
if (PortNumber >= TotalPort) { | |
Status = EFI_INVALID_PARAMETER; | |
goto ON_EXIT; | |
} | |
Offset = (UINT32)(EHC_PORT_STAT_OFFSET + (4 * PortNumber)); | |
State = EhcReadOpReg (Ehc, Offset); | |
// | |
// Mask off the port status change bits, these bits are | |
// write clean bit | |
// | |
State &= ~PORTSC_CHANGE_MASK; | |
switch (PortFeature) { | |
case EfiUsbPortEnable: | |
// | |
// Sofeware can't set this bit, Port can only be enable by | |
// EHCI as a part of the reset and enable | |
// | |
State |= PORTSC_ENABLED; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortSuspend: | |
State |= PORTSC_SUSPEND; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortReset: | |
// | |
// Make sure Host Controller not halt before reset it | |
// | |
if (EhcIsHalt (Ehc)) { | |
Status = EhcRunHC (Ehc, EHC_GENERIC_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_INFO, "EhcSetRootHubPortFeature :failed to start HC - %r\n", Status)); | |
break; | |
} | |
} | |
// | |
// Set one to PortReset bit must also set zero to PortEnable bit | |
// | |
State |= PORTSC_RESET; | |
State &= ~PORTSC_ENABLED; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortPower: | |
// | |
// Set port power bit when PPC is 1 | |
// | |
if ((Ehc->HcCapParams & HCSP_PPC) == HCSP_PPC) { | |
State |= PORTSC_POWER; | |
EhcWriteOpReg (Ehc, Offset, State); | |
} | |
break; | |
case EfiUsbPortOwner: | |
State |= PORTSC_OWNER; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
default: | |
Status = EFI_INVALID_PARAMETER; | |
} | |
ON_EXIT: | |
DEBUG ((DEBUG_INFO, "EhcSetRootHubPortFeature: exit 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 | |
EhcClearRootHubPortFeature ( | |
IN EFI_USB2_HC_PROTOCOL *This, | |
IN UINT8 PortNumber, | |
IN EFI_USB_PORT_FEATURE PortFeature | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
UINT32 Offset; | |
UINT32 State; | |
UINT32 TotalPort; | |
EFI_STATUS Status; | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
Status = EFI_SUCCESS; | |
TotalPort = (Ehc->HcStructParams & HCSP_NPORTS); | |
if (PortNumber >= TotalPort) { | |
Status = EFI_INVALID_PARAMETER; | |
goto ON_EXIT; | |
} | |
Offset = EHC_PORT_STAT_OFFSET + (4 * PortNumber); | |
State = EhcReadOpReg (Ehc, Offset); | |
State &= ~PORTSC_CHANGE_MASK; | |
switch (PortFeature) { | |
case EfiUsbPortEnable: | |
// | |
// Clear PORT_ENABLE feature means disable port. | |
// | |
State &= ~PORTSC_ENABLED; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortSuspend: | |
// | |
// A write of zero to this bit is ignored by the host | |
// controller. The host controller will unconditionally | |
// set this bit to a zero when: | |
// 1. software sets the Forct Port Resume bit to a zero from a one. | |
// 2. software sets the Port Reset bit to a one frome a zero. | |
// | |
State &= ~PORSTSC_RESUME; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortReset: | |
// | |
// Clear PORT_RESET means clear the reset signal. | |
// | |
State &= ~PORTSC_RESET; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortOwner: | |
// | |
// Clear port owner means this port owned by EHC | |
// | |
State &= ~PORTSC_OWNER; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortConnectChange: | |
// | |
// Clear connect status change | |
// | |
State |= PORTSC_CONN_CHANGE; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortEnableChange: | |
// | |
// Clear enable status change | |
// | |
State |= PORTSC_ENABLE_CHANGE; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortOverCurrentChange: | |
// | |
// Clear PortOverCurrent change | |
// | |
State |= PORTSC_OVERCUR_CHANGE; | |
EhcWriteOpReg (Ehc, Offset, State); | |
break; | |
case EfiUsbPortPower: | |
// | |
// Clear port power bit when PPC is 1 | |
// | |
if ((Ehc->HcCapParams & HCSP_PPC) == HCSP_PPC) { | |
State &= ~PORTSC_POWER; | |
EhcWriteOpReg (Ehc, Offset, State); | |
} | |
break; | |
case EfiUsbPortSuspendChange: | |
case EfiUsbPortResetChange: | |
// | |
// Not supported or not related operation | |
// | |
break; | |
default: | |
Status = EFI_INVALID_PARAMETER; | |
break; | |
} | |
ON_EXIT: | |
DEBUG ((DEBUG_INFO, "EhcClearRootHubPortFeature: exit 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 | |
EhcControlTransfer ( | |
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 | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
URB *Urb; | |
EFI_TPL OldTpl; | |
UINT8 Endpoint; | |
EFI_STATUS Status; | |
// | |
// 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)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
Status = EFI_DEVICE_ERROR; | |
*TransferResult = EFI_USB_ERR_SYSTEM; | |
if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
DEBUG ((DEBUG_ERROR, "EhcControlTransfer: HC halted at entrance\n")); | |
EhcAckAllInterrupt (Ehc); | |
goto ON_EXIT; | |
} | |
EhcAckAllInterrupt (Ehc); | |
// | |
// Create a new URB, insert it into the asynchronous | |
// schedule list, then poll the execution status. | |
// | |
// | |
// Encode the direction in address, although default control | |
// endpoint is bidirectional. EhcCreateUrb expects this | |
// combination of Ep addr and its direction. | |
// | |
Endpoint = (UINT8)(0 | ((TransferDirection == EfiUsbDataIn) ? 0x80 : 0)); | |
Urb = EhcCreateUrb ( | |
Ehc, | |
DeviceAddress, | |
Endpoint, | |
DeviceSpeed, | |
0, | |
MaximumPacketLength, | |
Translator, | |
EHC_CTRL_TRANSFER, | |
Request, | |
Data, | |
*DataLength, | |
NULL, | |
NULL, | |
1 | |
); | |
if (Urb == NULL) { | |
DEBUG ((DEBUG_ERROR, "EhcControlTransfer: failed to create URB")); | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ON_EXIT; | |
} | |
EhcLinkQhToAsync (Ehc, Urb->Qh); | |
Status = EhcExecTransfer (Ehc, Urb, TimeOut); | |
EhcUnlinkQhFromAsync (Ehc, Urb->Qh); | |
// | |
// Get the status from URB. The result is updated in EhcCheckUrbResult | |
// which is called by EhcExecTransfer | |
// | |
*TransferResult = Urb->Result; | |
*DataLength = Urb->Completed; | |
if (*TransferResult == EFI_USB_NOERROR) { | |
Status = EFI_SUCCESS; | |
} | |
EhcAckAllInterrupt (Ehc); | |
EhcFreeUrb (Ehc, Urb); | |
ON_EXIT: | |
Ehc->PciIo->Flush (Ehc->PciIo); | |
gBS->RestoreTPL (OldTpl); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcControlTransfer: error - %r, transfer - %x\n", Status, *TransferResult)); | |
} | |
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 | |
EhcBulkTransfer ( | |
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 | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
URB *Urb; | |
EFI_TPL OldTpl; | |
EFI_STATUS Status; | |
UINTN DebugErrorLevel; | |
// | |
// 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))) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
*TransferResult = EFI_USB_ERR_SYSTEM; | |
Status = EFI_DEVICE_ERROR; | |
if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
DEBUG ((DEBUG_ERROR, "EhcBulkTransfer: HC is halted\n")); | |
EhcAckAllInterrupt (Ehc); | |
goto ON_EXIT; | |
} | |
EhcAckAllInterrupt (Ehc); | |
// | |
// Create a new URB, insert it into the asynchronous | |
// schedule list, then poll the execution status. | |
// | |
Urb = EhcCreateUrb ( | |
Ehc, | |
DeviceAddress, | |
EndPointAddress, | |
DeviceSpeed, | |
*DataToggle, | |
MaximumPacketLength, | |
Translator, | |
EHC_BULK_TRANSFER, | |
NULL, | |
Data[0], | |
*DataLength, | |
NULL, | |
NULL, | |
1 | |
); | |
if (Urb == NULL) { | |
DEBUG ((DEBUG_ERROR, "EhcBulkTransfer: failed to create URB\n")); | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ON_EXIT; | |
} | |
EhcLinkQhToAsync (Ehc, Urb->Qh); | |
Status = EhcExecTransfer (Ehc, Urb, TimeOut); | |
EhcUnlinkQhFromAsync (Ehc, Urb->Qh); | |
*TransferResult = Urb->Result; | |
*DataLength = Urb->Completed; | |
*DataToggle = Urb->DataToggle; | |
if (*TransferResult == EFI_USB_NOERROR) { | |
Status = EFI_SUCCESS; | |
} | |
EhcAckAllInterrupt (Ehc); | |
EhcFreeUrb (Ehc, Urb); | |
ON_EXIT: | |
Ehc->PciIo->Flush (Ehc->PciIo); | |
gBS->RestoreTPL (OldTpl); | |
if (EFI_ERROR (Status)) { | |
if (Status == EFI_TIMEOUT) { | |
DebugErrorLevel = DEBUG_VERBOSE; | |
} else { | |
DebugErrorLevel = DEBUG_ERROR; | |
} | |
DEBUG ((DebugErrorLevel, "EhcBulkTransfer: error - %r, transfer - %x\n", Status, *TransferResult)); | |
} | |
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 | |
EhcAsyncInterruptTransfer ( | |
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 | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
URB *Urb; | |
EFI_TPL OldTpl; | |
EFI_STATUS Status; | |
// | |
// Validate parameters | |
// | |
if (!EHCI_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 (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
// | |
// Delete Async interrupt transfer request. DataToggle will return | |
// the next data toggle to use. | |
// | |
if (!IsNewTransfer) { | |
Status = EhciDelAsyncIntTransfer (Ehc, DeviceAddress, EndPointAddress, DataToggle); | |
DEBUG ((DEBUG_INFO, "EhcAsyncInterruptTransfer: remove old transfer - %r\n", Status)); | |
goto ON_EXIT; | |
} | |
Status = EFI_SUCCESS; | |
if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
DEBUG ((DEBUG_ERROR, "EhcAsyncInterruptTransfer: HC is halt\n")); | |
EhcAckAllInterrupt (Ehc); | |
Status = EFI_DEVICE_ERROR; | |
goto ON_EXIT; | |
} | |
EhcAckAllInterrupt (Ehc); | |
Urb = EhciInsertAsyncIntTransfer ( | |
Ehc, | |
DeviceAddress, | |
EndPointAddress, | |
DeviceSpeed, | |
*DataToggle, | |
MaximumPacketLength, | |
Translator, | |
DataLength, | |
CallBackFunction, | |
Context, | |
PollingInterval | |
); | |
if (Urb == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ON_EXIT; | |
} | |
ON_EXIT: | |
Ehc->PciIo->Flush (Ehc->PciIo); | |
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 | |
EhcSyncInterruptTransfer ( | |
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 | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
URB *Urb; | |
EFI_STATUS Status; | |
// | |
// Validates parameters | |
// | |
if ((DataLength == NULL) || (*DataLength == 0) || | |
(Data == NULL) || (TransferResult == NULL)) | |
{ | |
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 (EHC_TPL); | |
Ehc = EHC_FROM_THIS (This); | |
*TransferResult = EFI_USB_ERR_SYSTEM; | |
Status = EFI_DEVICE_ERROR; | |
if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
DEBUG ((DEBUG_ERROR, "EhcSyncInterruptTransfer: HC is halt\n")); | |
EhcAckAllInterrupt (Ehc); | |
goto ON_EXIT; | |
} | |
EhcAckAllInterrupt (Ehc); | |
Urb = EhcCreateUrb ( | |
Ehc, | |
DeviceAddress, | |
EndPointAddress, | |
DeviceSpeed, | |
*DataToggle, | |
MaximumPacketLength, | |
Translator, | |
EHC_INT_TRANSFER_SYNC, | |
NULL, | |
Data, | |
*DataLength, | |
NULL, | |
NULL, | |
1 | |
); | |
if (Urb == NULL) { | |
DEBUG ((DEBUG_ERROR, "EhcSyncInterruptTransfer: failed to create URB\n")); | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ON_EXIT; | |
} | |
EhcLinkQhToPeriod (Ehc, Urb->Qh); | |
Status = EhcExecTransfer (Ehc, Urb, TimeOut); | |
EhcUnlinkQhFromPeriod (Ehc, Urb->Qh); | |
*TransferResult = Urb->Result; | |
*DataLength = Urb->Completed; | |
*DataToggle = Urb->DataToggle; | |
if (*TransferResult == EFI_USB_NOERROR) { | |
Status = EFI_SUCCESS; | |
} | |
EhcFreeUrb (Ehc, Urb); | |
ON_EXIT: | |
Ehc->PciIo->Flush (Ehc->PciIo); | |
gBS->RestoreTPL (OldTpl); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcSyncInterruptTransfer: error - %r, transfer - %x\n", Status, *TransferResult)); | |
} | |
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 | |
EhcIsochronousTransfer ( | |
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 | |
EhcAsyncIsochronousTransfer ( | |
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. | |
@return EFI_SUCCESS Success. | |
EFI_DEVICE_ERROR Fail. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
EhcDriverEntryPoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
return EfiLibInstallDriverBindingComponentName2 ( | |
ImageHandle, | |
SystemTable, | |
&gEhciDriverBinding, | |
ImageHandle, | |
&gEhciComponentName, | |
&gEhciComponentName2 | |
); | |
} | |
/** | |
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 | |
EhcDriverBindingSupported ( | |
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 Ehci type | |
// | |
if ( (UsbClassCReg.BaseCode != PCI_CLASS_SERIAL) || (UsbClassCReg.SubClassCode != PCI_CLASS_SERIAL_USB) | |
|| ((UsbClassCReg.ProgInterface != PCI_IF_EHCI) && (UsbClassCReg.ProgInterface != PCI_IF_UHCI) && (UsbClassCReg.ProgInterface != PCI_IF_OHCI))) | |
{ | |
Status = EFI_UNSUPPORTED; | |
} | |
ON_EXIT: | |
gBS->CloseProtocol ( | |
Controller, | |
&gEfiPciIoProtocolGuid, | |
This->DriverBindingHandle, | |
Controller | |
); | |
return Status; | |
} | |
/** | |
Get the usb debug port related information. | |
@param Ehc The EHCI device. | |
@retval RETURN_SUCCESS Get debug port number, bar and offset successfully. | |
@retval Others The usb host controller does not supported usb debug port capability. | |
**/ | |
EFI_STATUS | |
EhcGetUsbDebugPortInfo ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
UINT16 PciStatus; | |
UINT8 CapabilityPtr; | |
UINT8 CapabilityId; | |
UINT16 DebugPort; | |
EFI_STATUS Status; | |
ASSERT (Ehc->PciIo != NULL); | |
PciIo = Ehc->PciIo; | |
// | |
// Detect if the EHCI host controller support Capaility Pointer. | |
// | |
Status = PciIo->Pci.Read ( | |
PciIo, | |
EfiPciIoWidthUint8, | |
PCI_PRIMARY_STATUS_OFFSET, | |
sizeof (UINT16), | |
&PciStatus | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((PciStatus & EFI_PCI_STATUS_CAPABILITY) == 0) { | |
// | |
// The Pci Device Doesn't Support Capability Pointer. | |
// | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Get Pointer To Capability List | |
// | |
Status = PciIo->Pci.Read ( | |
PciIo, | |
EfiPciIoWidthUint8, | |
PCI_CAPBILITY_POINTER_OFFSET, | |
1, | |
&CapabilityPtr | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Find Capability ID 0xA, Which Is For Debug Port | |
// | |
while (CapabilityPtr != 0) { | |
Status = PciIo->Pci.Read ( | |
PciIo, | |
EfiPciIoWidthUint8, | |
CapabilityPtr, | |
1, | |
&CapabilityId | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (CapabilityId == EHC_DEBUG_PORT_CAP_ID) { | |
break; | |
} | |
Status = PciIo->Pci.Read ( | |
PciIo, | |
EfiPciIoWidthUint8, | |
CapabilityPtr + 1, | |
1, | |
&CapabilityPtr | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
// | |
// No Debug Port Capability Found | |
// | |
if (CapabilityPtr == 0) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Get The Base Address Of Debug Port Register In Debug Port Capability Register | |
// | |
Status = PciIo->Pci.Read ( | |
Ehc->PciIo, | |
EfiPciIoWidthUint8, | |
CapabilityPtr + 2, | |
sizeof (UINT16), | |
&DebugPort | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Ehc->DebugPortOffset = DebugPort & 0x1FFF; | |
Ehc->DebugPortBarNum = (UINT8)((DebugPort >> 13) - 1); | |
Ehc->DebugPortNum = (UINT8)((Ehc->HcStructParams & 0x00F00000) >> 20); | |
return EFI_SUCCESS; | |
} | |
/** | |
Create and initialize a USB2_HC_DEV. | |
@param PciIo The PciIo on this device. | |
@param DevicePath The device path of host controller. | |
@param OriginalPciAttributes Original PCI attributes. | |
@return The allocated and initialized USB2_HC_DEV structure if created, | |
otherwise NULL. | |
**/ | |
USB2_HC_DEV * | |
EhcCreateUsb2Hc ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, | |
IN UINT64 OriginalPciAttributes | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_STATUS Status; | |
Ehc = AllocateZeroPool (sizeof (USB2_HC_DEV)); | |
if (Ehc == NULL) { | |
return NULL; | |
} | |
// | |
// Init EFI_USB2_HC_PROTOCOL interface and private data structure | |
// | |
Ehc->Signature = USB2_HC_DEV_SIGNATURE; | |
Ehc->Usb2Hc.GetCapability = EhcGetCapability; | |
Ehc->Usb2Hc.Reset = EhcReset; | |
Ehc->Usb2Hc.GetState = EhcGetState; | |
Ehc->Usb2Hc.SetState = EhcSetState; | |
Ehc->Usb2Hc.ControlTransfer = EhcControlTransfer; | |
Ehc->Usb2Hc.BulkTransfer = EhcBulkTransfer; | |
Ehc->Usb2Hc.AsyncInterruptTransfer = EhcAsyncInterruptTransfer; | |
Ehc->Usb2Hc.SyncInterruptTransfer = EhcSyncInterruptTransfer; | |
Ehc->Usb2Hc.IsochronousTransfer = EhcIsochronousTransfer; | |
Ehc->Usb2Hc.AsyncIsochronousTransfer = EhcAsyncIsochronousTransfer; | |
Ehc->Usb2Hc.GetRootHubPortStatus = EhcGetRootHubPortStatus; | |
Ehc->Usb2Hc.SetRootHubPortFeature = EhcSetRootHubPortFeature; | |
Ehc->Usb2Hc.ClearRootHubPortFeature = EhcClearRootHubPortFeature; | |
Ehc->Usb2Hc.MajorRevision = 0x2; | |
Ehc->Usb2Hc.MinorRevision = 0x0; | |
Ehc->PciIo = PciIo; | |
Ehc->DevicePath = DevicePath; | |
Ehc->OriginalPciAttributes = OriginalPciAttributes; | |
InitializeListHead (&Ehc->AsyncIntTransfers); | |
Ehc->HcStructParams = EhcReadCapRegister (Ehc, EHC_HCSPARAMS_OFFSET); | |
Ehc->HcCapParams = EhcReadCapRegister (Ehc, EHC_HCCPARAMS_OFFSET); | |
Ehc->CapLen = EhcReadCapRegister (Ehc, EHC_CAPLENGTH_OFFSET) & 0x0FF; | |
DEBUG ((DEBUG_INFO, "EhcCreateUsb2Hc: capability length %d\n", Ehc->CapLen)); | |
// | |
// EHCI Controllers with a CapLen of 0 are ignored. | |
// | |
if (Ehc->CapLen == 0) { | |
gBS->FreePool (Ehc); | |
return NULL; | |
} | |
EhcGetUsbDebugPortInfo (Ehc); | |
// | |
// Create AsyncRequest Polling Timer | |
// | |
Status = gBS->CreateEvent ( | |
EVT_TIMER | EVT_NOTIFY_SIGNAL, | |
TPL_NOTIFY, | |
EhcMonitorAsyncRequests, | |
Ehc, | |
&Ehc->PollTimer | |
); | |
if (EFI_ERROR (Status)) { | |
gBS->FreePool (Ehc); | |
return NULL; | |
} | |
return Ehc; | |
} | |
/** | |
One notified function to stop the Host Controller when gBS->ExitBootServices() called. | |
@param Event Pointer to this event | |
@param Context Event handler private data | |
**/ | |
VOID | |
EFIAPI | |
EhcExitBootService ( | |
EFI_EVENT Event, | |
VOID *Context | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
Ehc = (USB2_HC_DEV *)Context; | |
// | |
// Reset the Host Controller | |
// | |
EhcResetHC (Ehc, EHC_RESET_TIMEOUT); | |
} | |
/** | |
Starting the Usb EHCI 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 | |
EhcDriverBindingStart ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
) | |
{ | |
EFI_STATUS Status; | |
USB2_HC_DEV *Ehc; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
EFI_PCI_IO_PROTOCOL *Instance; | |
UINT64 Supports; | |
UINT64 OriginalPciAttributes; | |
BOOLEAN PciAttributesSaved; | |
USB_CLASSC UsbClassCReg; | |
EFI_HANDLE *HandleBuffer; | |
UINTN NumberOfHandles; | |
UINTN Index; | |
UINTN CompanionSegmentNumber; | |
UINTN CompanionBusNumber; | |
UINTN CompanionDeviceNumber; | |
UINTN CompanionFunctionNumber; | |
UINTN EhciSegmentNumber; | |
UINTN EhciBusNumber; | |
UINTN EhciDeviceNumber; | |
UINTN EhciFunctionNumber; | |
EFI_DEVICE_PATH_PROTOCOL *HcDevicePath; | |
// | |
// 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; | |
} | |
// | |
// Open Device Path Protocol for on USB host controller | |
// | |
HcDevicePath = NULL; | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiDevicePathProtocolGuid, | |
(VOID **)&HcDevicePath, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
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 &= (UINT64)EFI_PCI_DEVICE_ENABLE; | |
Status = PciIo->Attributes ( | |
PciIo, | |
EfiPciIoAttributeOperationEnable, | |
Supports, | |
NULL | |
); | |
} | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcDriverBindingStart: failed to enable controller\n")); | |
goto CLOSE_PCIIO; | |
} | |
// | |
// Get the Pci device class code. | |
// | |
Status = PciIo->Pci.Read ( | |
PciIo, | |
EfiPciIoWidthUint8, | |
PCI_CLASSCODE_OFFSET, | |
sizeof (USB_CLASSC) / sizeof (UINT8), | |
&UsbClassCReg | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_UNSUPPORTED; | |
goto CLOSE_PCIIO; | |
} | |
// | |
// Determine if the device is UHCI or OHCI host controller or not. If yes, then find out the | |
// companion usb ehci host controller and force EHCI driver get attached to it before | |
// UHCI or OHCI driver attaches to UHCI or OHCI host controller. | |
// | |
if (((UsbClassCReg.ProgInterface == PCI_IF_UHCI) || (UsbClassCReg.ProgInterface == PCI_IF_OHCI)) && | |
(UsbClassCReg.BaseCode == PCI_CLASS_SERIAL) && | |
(UsbClassCReg.SubClassCode == PCI_CLASS_SERIAL_USB)) | |
{ | |
Status = PciIo->GetLocation ( | |
PciIo, | |
&CompanionSegmentNumber, | |
&CompanionBusNumber, | |
&CompanionDeviceNumber, | |
&CompanionFunctionNumber | |
); | |
if (EFI_ERROR (Status)) { | |
goto CLOSE_PCIIO; | |
} | |
Status = gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiPciIoProtocolGuid, | |
NULL, | |
&NumberOfHandles, | |
&HandleBuffer | |
); | |
if (EFI_ERROR (Status)) { | |
goto CLOSE_PCIIO; | |
} | |
for (Index = 0; Index < NumberOfHandles; Index++) { | |
// | |
// Get the device path on this handle | |
// | |
Status = gBS->HandleProtocol ( | |
HandleBuffer[Index], | |
&gEfiPciIoProtocolGuid, | |
(VOID **)&Instance | |
); | |
ASSERT_EFI_ERROR (Status); | |
Status = Instance->Pci.Read ( | |
Instance, | |
EfiPciIoWidthUint8, | |
PCI_CLASSCODE_OFFSET, | |
sizeof (USB_CLASSC) / sizeof (UINT8), | |
&UsbClassCReg | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_UNSUPPORTED; | |
goto CLOSE_PCIIO; | |
} | |
if ((UsbClassCReg.ProgInterface == PCI_IF_EHCI) && | |
(UsbClassCReg.BaseCode == PCI_CLASS_SERIAL) && | |
(UsbClassCReg.SubClassCode == PCI_CLASS_SERIAL_USB)) | |
{ | |
Status = Instance->GetLocation ( | |
Instance, | |
&EhciSegmentNumber, | |
&EhciBusNumber, | |
&EhciDeviceNumber, | |
&EhciFunctionNumber | |
); | |
if (EFI_ERROR (Status)) { | |
goto CLOSE_PCIIO; | |
} | |
// | |
// Currently, the judgment on the companion usb host controller is through the | |
// same bus number, which may vary on different platform. | |
// | |
if (EhciBusNumber == CompanionBusNumber) { | |
gBS->CloseProtocol ( | |
Controller, | |
&gEfiPciIoProtocolGuid, | |
This->DriverBindingHandle, | |
Controller | |
); | |
EhcDriverBindingStart (This, HandleBuffer[Index], NULL); | |
} | |
} | |
} | |
Status = EFI_NOT_FOUND; | |
goto CLOSE_PCIIO; | |
} | |
// | |
// Create then install USB2_HC_PROTOCOL | |
// | |
Ehc = EhcCreateUsb2Hc (PciIo, HcDevicePath, OriginalPciAttributes); | |
if (Ehc == NULL) { | |
DEBUG ((DEBUG_ERROR, "EhcDriverBindingStart: failed to create USB2_HC\n")); | |
Status = EFI_OUT_OF_RESOURCES; | |
goto CLOSE_PCIIO; | |
} | |
// | |
// Enable 64-bit DMA support in the PCI layer if this controller | |
// supports it. | |
// | |
if (EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT)) { | |
Status = PciIo->Attributes ( | |
PciIo, | |
EfiPciIoAttributeOperationEnable, | |
EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE, | |
NULL | |
); | |
if (!EFI_ERROR (Status)) { | |
Ehc->Support64BitDma = TRUE; | |
} else { | |
DEBUG (( | |
DEBUG_WARN, | |
"%a: failed to enable 64-bit DMA on 64-bit capable controller @ %p (%r)\n", | |
__func__, | |
Controller, | |
Status | |
)); | |
} | |
} | |
Status = gBS->InstallProtocolInterface ( | |
&Controller, | |
&gEfiUsb2HcProtocolGuid, | |
EFI_NATIVE_INTERFACE, | |
&Ehc->Usb2Hc | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcDriverBindingStart: failed to install USB2_HC Protocol\n")); | |
goto FREE_POOL; | |
} | |
// | |
// Robustnesss improvement such as for Duet platform | |
// Default is not required. | |
// | |
if (FeaturePcdGet (PcdTurnOffUsbLegacySupport)) { | |
EhcClearLegacySupport (Ehc); | |
} | |
if (!EhcIsDebugPortInUse (Ehc, NULL)) { | |
EhcResetHC (Ehc, EHC_RESET_TIMEOUT); | |
} | |
Status = EhcInitHC (Ehc); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcDriverBindingStart: failed to init host controller\n")); | |
goto UNINSTALL_USBHC; | |
} | |
// | |
// Start the asynchronous interrupt monitor | |
// | |
Status = gBS->SetTimer (Ehc->PollTimer, TimerPeriodic, EHC_ASYNC_POLL_INTERVAL); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcDriverBindingStart: failed to start async interrupt monitor\n")); | |
EhcHaltHC (Ehc, EHC_GENERIC_TIMEOUT); | |
goto UNINSTALL_USBHC; | |
} | |
// | |
// Create event to stop the HC when exit boot service. | |
// | |
Status = gBS->CreateEventEx ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_NOTIFY, | |
EhcExitBootService, | |
Ehc, | |
&gEfiEventExitBootServicesGuid, | |
&Ehc->ExitBootServiceEvent | |
); | |
if (EFI_ERROR (Status)) { | |
goto UNINSTALL_USBHC; | |
} | |
// | |
// Install the component name protocol, don't fail the start | |
// because of something for display. | |
// | |
AddUnicodeString2 ( | |
"eng", | |
gEhciComponentName.SupportedLanguages, | |
&Ehc->ControllerNameTable, | |
L"Enhanced Host Controller (USB 2.0)", | |
TRUE | |
); | |
AddUnicodeString2 ( | |
"en", | |
gEhciComponentName2.SupportedLanguages, | |
&Ehc->ControllerNameTable, | |
L"Enhanced Host Controller (USB 2.0)", | |
FALSE | |
); | |
DEBUG ((DEBUG_INFO, "EhcDriverBindingStart: EHCI started for controller @ %p\n", Controller)); | |
return EFI_SUCCESS; | |
UNINSTALL_USBHC: | |
gBS->UninstallProtocolInterface ( | |
Controller, | |
&gEfiUsb2HcProtocolGuid, | |
&Ehc->Usb2Hc | |
); | |
FREE_POOL: | |
EhcFreeSched (Ehc); | |
gBS->CloseEvent (Ehc->PollTimer); | |
gBS->FreePool (Ehc); | |
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 stopping 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 | |
EhcDriverBindingStop ( | |
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; | |
USB2_HC_DEV *Ehc; | |
// | |
// 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; | |
} | |
Ehc = EHC_FROM_THIS (Usb2Hc); | |
PciIo = Ehc->PciIo; | |
Status = gBS->UninstallProtocolInterface ( | |
Controller, | |
&gEfiUsb2HcProtocolGuid, | |
Usb2Hc | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Stop AsyncRequest Polling timer then stop the EHCI driver | |
// and uninstall the EHCI protocl. | |
// | |
gBS->SetTimer (Ehc->PollTimer, TimerCancel, EHC_ASYNC_POLL_INTERVAL); | |
EhcHaltHC (Ehc, EHC_GENERIC_TIMEOUT); | |
if (Ehc->PollTimer != NULL) { | |
gBS->CloseEvent (Ehc->PollTimer); | |
} | |
if (Ehc->ExitBootServiceEvent != NULL) { | |
gBS->CloseEvent (Ehc->ExitBootServiceEvent); | |
} | |
EhcFreeSched (Ehc); | |
if (Ehc->ControllerNameTable != NULL) { | |
FreeUnicodeStringTable (Ehc->ControllerNameTable); | |
} | |
// | |
// Disable routing of all ports to EHCI controller, so all ports are | |
// routed back to the UHCI or OHCI controller. | |
// | |
EhcClearOpRegBit (Ehc, EHC_CONFIG_FLAG_OFFSET, CONFIGFLAG_ROUTE_EHC); | |
// | |
// Restore original PCI attributes | |
// | |
PciIo->Attributes ( | |
PciIo, | |
EfiPciIoAttributeOperationSet, | |
Ehc->OriginalPciAttributes, | |
NULL | |
); | |
gBS->CloseProtocol ( | |
Controller, | |
&gEfiPciIoProtocolGuid, | |
This->DriverBindingHandle, | |
Controller | |
); | |
FreePool (Ehc); | |
return EFI_SUCCESS; | |
} |