/** @file | |
Unified interface for RootHub and Hub. | |
Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "UsbBus.h" | |
// | |
// Array that maps the change bit to feature value which is | |
// used to clear these change bit. USB HUB API will clear | |
// these change bit automatically. For non-root hub, these | |
// bits determine whether hub will report the port in changed | |
// bit maps. | |
// | |
USB_CHANGE_FEATURE_MAP mHubFeatureMap[] = { | |
{ USB_PORT_STAT_C_CONNECTION, EfiUsbPortConnectChange }, | |
{ USB_PORT_STAT_C_ENABLE, EfiUsbPortEnableChange }, | |
{ USB_PORT_STAT_C_SUSPEND, EfiUsbPortSuspendChange }, | |
{ USB_PORT_STAT_C_OVERCURRENT, EfiUsbPortOverCurrentChange }, | |
{ USB_PORT_STAT_C_RESET, EfiUsbPortResetChange } | |
}; | |
USB_CHANGE_FEATURE_MAP mRootHubFeatureMap[] = { | |
{ USB_PORT_STAT_C_CONNECTION, EfiUsbPortConnectChange }, | |
{ USB_PORT_STAT_C_ENABLE, EfiUsbPortEnableChange }, | |
{ USB_PORT_STAT_C_SUSPEND, EfiUsbPortSuspendChange }, | |
{ USB_PORT_STAT_C_OVERCURRENT, EfiUsbPortOverCurrentChange }, | |
{ USB_PORT_STAT_C_RESET, EfiUsbPortResetChange }, | |
}; | |
// | |
// USB hub class specific requests. Although USB hub | |
// is related to an interface, these requests are sent | |
// to the control endpoint of the device. | |
// | |
/** | |
USB hub control transfer to set the hub depth. | |
@param HubDev The device of the hub. | |
@param Depth The depth to set. | |
@retval EFI_SUCCESS Depth of the hub is set. | |
@retval Others Failed to set the depth. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlSetHubDepth ( | |
IN USB_DEVICE *HubDev, | |
IN UINT16 Depth | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbNoData, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_HUB, | |
USB_HUB_REQ_SET_DEPTH, | |
Depth, | |
0, | |
NULL, | |
0 | |
); | |
return Status; | |
} | |
/** | |
USB hub control transfer to clear the hub feature. | |
@param HubDev The device of the hub. | |
@param Feature The feature to clear. | |
@retval EFI_SUCCESS Feature of the hub is cleared. | |
@retval Others Failed to clear the feature. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlClearHubFeature ( | |
IN USB_DEVICE *HubDev, | |
IN UINT16 Feature | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbNoData, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_HUB, | |
USB_HUB_REQ_CLEAR_FEATURE, | |
Feature, | |
0, | |
NULL, | |
0 | |
); | |
return Status; | |
} | |
/** | |
Clear the feature of the device's port. | |
@param HubDev The hub device. | |
@param Port The port to clear feature. | |
@param Feature The feature to clear. | |
@retval EFI_SUCCESS The feature of the port is cleared. | |
@retval Others Failed to clear the feature. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlClearPortFeature ( | |
IN USB_DEVICE *HubDev, | |
IN UINT8 Port, | |
IN UINT16 Feature | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// In USB bus, all the port index starts from 0. But HUB | |
// indexes its port from 1. So, port number is added one. | |
// | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbNoData, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_PORT, | |
USB_HUB_REQ_CLEAR_FEATURE, | |
Feature, | |
(UINT16)(Port + 1), | |
NULL, | |
0 | |
); | |
return Status; | |
} | |
/** | |
Clear the transaction translate buffer if full/low | |
speed control/bulk transfer failed and the transfer | |
uses this hub as translator.Remember to clear the TT | |
buffer of transaction translator, not that of the | |
parent. | |
@param HubDev The hub device. | |
@param Port The port of the hub. | |
@param DevAddr Address of the failed transaction. | |
@param EpNum The endpoint number of the failed transaction. | |
@param EpType The type of failed transaction. | |
@retval EFI_SUCCESS The TT buffer is cleared. | |
@retval Others Failed to clear the TT buffer. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlClearTTBuffer ( | |
IN USB_DEVICE *HubDev, | |
IN UINT8 Port, | |
IN UINT16 DevAddr, | |
IN UINT16 EpNum, | |
IN UINT16 EpType | |
) | |
{ | |
EFI_STATUS Status; | |
UINT16 Value; | |
// | |
// Check USB2.0 spec page 424 for wValue's encoding | |
// | |
Value = (UINT16)((EpNum & 0x0F) | (DevAddr << 4) | | |
((EpType & 0x03) << 11) | ((EpNum & 0x80) << 15)); | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbNoData, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_PORT, | |
USB_HUB_REQ_CLEAR_TT, | |
Value, | |
(UINT16)(Port + 1), | |
NULL, | |
0 | |
); | |
return Status; | |
} | |
/** | |
Usb hub control transfer to get the (super speed) hub descriptor. | |
@param HubDev The hub device. | |
@param Buf The buffer to hold the descriptor. | |
@param Len The length to retrieve. | |
@retval EFI_SUCCESS The hub descriptor is retrieved. | |
@retval Others Failed to retrieve the hub descriptor. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlGetHubDesc ( | |
IN USB_DEVICE *HubDev, | |
OUT VOID *Buf, | |
IN UINTN Len | |
) | |
{ | |
EFI_STATUS Status; | |
UINT8 DescType; | |
DescType = (HubDev->Speed == EFI_USB_SPEED_SUPER) ? | |
USB_DESC_TYPE_HUB_SUPER_SPEED : | |
USB_DESC_TYPE_HUB; | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbDataIn, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_HUB, | |
USB_HUB_REQ_GET_DESC, | |
(UINT16)(DescType << 8), | |
0, | |
Buf, | |
Len | |
); | |
return Status; | |
} | |
/** | |
Usb hub control transfer to get the hub status. | |
@param HubDev The hub device. | |
@param State The variable to return the status. | |
@retval EFI_SUCCESS The hub status is returned in State. | |
@retval Others Failed to get the hub status. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlGetHubStatus ( | |
IN USB_DEVICE *HubDev, | |
OUT UINT32 *State | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbDataIn, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_HUB, | |
USB_HUB_REQ_GET_STATUS, | |
0, | |
0, | |
State, | |
4 | |
); | |
return Status; | |
} | |
/** | |
Usb hub control transfer to get the port status. | |
@param HubDev The hub device. | |
@param Port The port of the hub. | |
@param State Variable to return the hub port state. | |
@retval EFI_SUCCESS The port state is returned in State. | |
@retval Others Failed to retrieve the port state. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlGetPortStatus ( | |
IN USB_DEVICE *HubDev, | |
IN UINT8 Port, | |
OUT VOID *State | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// In USB bus, all the port index starts from 0. But HUB | |
// indexes its port from 1. So, port number is added one. | |
// No need to convert the hub bit to UEFI definition, they | |
// are the same | |
// | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbDataIn, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_PORT, | |
USB_HUB_REQ_GET_STATUS, | |
0, | |
(UINT16)(Port + 1), | |
State, | |
4 | |
); | |
return Status; | |
} | |
/** | |
Usb hub control transfer to set the port feature. | |
@param HubDev The Usb hub device. | |
@param Port The Usb port to set feature for. | |
@param Feature The feature to set. | |
@retval EFI_SUCCESS The feature is set for the port. | |
@retval Others Failed to set the feature. | |
**/ | |
EFI_STATUS | |
UsbHubCtrlSetPortFeature ( | |
IN USB_DEVICE *HubDev, | |
IN UINT8 Port, | |
IN UINT8 Feature | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// In USB bus, all the port index starts from 0. But HUB | |
// indexes its port from 1. So, port number is added one. | |
// | |
Status = UsbCtrlRequest ( | |
HubDev, | |
EfiUsbNoData, | |
USB_REQ_TYPE_CLASS, | |
USB_HUB_TARGET_PORT, | |
USB_HUB_REQ_SET_FEATURE, | |
Feature, | |
(UINT16)(Port + 1), | |
NULL, | |
0 | |
); | |
return Status; | |
} | |
/** | |
Read the whole usb hub descriptor. It is necessary | |
to do it in two steps because hub descriptor is of | |
variable length. | |
@param HubDev The hub device. | |
@param HubDesc The variable to return the descriptor. | |
@retval EFI_SUCCESS The hub descriptor is read. | |
@retval Others Failed to read the hub descriptor. | |
**/ | |
EFI_STATUS | |
UsbHubReadDesc ( | |
IN USB_DEVICE *HubDev, | |
OUT EFI_USB_HUB_DESCRIPTOR *HubDesc | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// First get the hub descriptor length | |
// | |
Status = UsbHubCtrlGetHubDesc (HubDev, HubDesc, 2); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Get the whole hub descriptor | |
// | |
return UsbHubCtrlGetHubDesc (HubDev, HubDesc, HubDesc->Length); | |
} | |
/** | |
Ack the hub change bits. If these bits are not ACKed, Hub will | |
always return changed bit map from its interrupt endpoint. | |
@param HubDev The hub device. | |
@retval EFI_SUCCESS The hub change status is ACKed. | |
@retval Others Failed to ACK the hub status. | |
**/ | |
EFI_STATUS | |
UsbHubAckHubStatus ( | |
IN USB_DEVICE *HubDev | |
) | |
{ | |
EFI_USB_PORT_STATUS HubState; | |
EFI_STATUS Status; | |
Status = UsbHubCtrlGetHubStatus (HubDev, (UINT32 *)&HubState); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (USB_BIT_IS_SET (HubState.PortChangeStatus, USB_HUB_STAT_C_LOCAL_POWER)) { | |
UsbHubCtrlClearHubFeature (HubDev, USB_HUB_C_HUB_LOCAL_POWER); | |
} | |
if (USB_BIT_IS_SET (HubState.PortChangeStatus, USB_HUB_STAT_C_OVER_CURRENT)) { | |
UsbHubCtrlClearHubFeature (HubDev, USB_HUB_C_HUB_OVER_CURRENT); | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Test whether the interface is a hub interface. | |
@param UsbIf The interface to test. | |
@retval TRUE The interface is a hub interface. | |
@retval FALSE The interface isn't a hub interface. | |
**/ | |
BOOLEAN | |
UsbIsHubInterface ( | |
IN USB_INTERFACE *UsbIf | |
) | |
{ | |
EFI_USB_INTERFACE_DESCRIPTOR *Setting; | |
// | |
// If the hub is a high-speed hub with multiple TT, | |
// the hub will has a default setting of single TT. | |
// | |
Setting = &UsbIf->IfSetting->Desc; | |
if ((Setting->InterfaceClass == USB_HUB_CLASS_CODE) && | |
(Setting->InterfaceSubClass == USB_HUB_SUBCLASS_CODE)) | |
{ | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
The callback function to the USB hub status change | |
interrupt endpoint. It is called periodically by | |
the underlying host controller. | |
@param Data The data read. | |
@param DataLength The length of the data read. | |
@param Context The context. | |
@param Result The result of the last interrupt transfer. | |
@retval EFI_SUCCESS The process is OK. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate resource. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
UsbOnHubInterrupt ( | |
IN VOID *Data, | |
IN UINTN DataLength, | |
IN VOID *Context, | |
IN UINT32 Result | |
) | |
{ | |
USB_INTERFACE *HubIf; | |
EFI_USB_IO_PROTOCOL *UsbIo; | |
EFI_USB_ENDPOINT_DESCRIPTOR *EpDesc; | |
EFI_STATUS Status; | |
HubIf = (USB_INTERFACE *)Context; | |
UsbIo = &(HubIf->UsbIo); | |
EpDesc = &(HubIf->HubEp->Desc); | |
if (Result != EFI_USB_NOERROR) { | |
// | |
// If endpoint is stalled, clear the stall. Use UsbIo to access | |
// the control transfer so internal status are maintained. | |
// | |
if (USB_BIT_IS_SET (Result, EFI_USB_ERR_STALL)) { | |
UsbIoClearFeature ( | |
UsbIo, | |
USB_TARGET_ENDPOINT, | |
USB_FEATURE_ENDPOINT_HALT, | |
EpDesc->EndpointAddress | |
); | |
} | |
// | |
// Delete and submit a new async interrupt | |
// | |
Status = UsbIo->UsbAsyncInterruptTransfer ( | |
UsbIo, | |
EpDesc->EndpointAddress, | |
FALSE, | |
0, | |
0, | |
NULL, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "UsbOnHubInterrupt: failed to remove async transfer - %r\n", Status)); | |
return Status; | |
} | |
Status = UsbIo->UsbAsyncInterruptTransfer ( | |
UsbIo, | |
EpDesc->EndpointAddress, | |
TRUE, | |
USB_HUB_POLL_INTERVAL, | |
HubIf->NumOfPort / 8 + 1, | |
UsbOnHubInterrupt, | |
HubIf | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "UsbOnHubInterrupt: failed to submit new async transfer - %r\n", Status)); | |
} | |
return Status; | |
} | |
if ((DataLength == 0) || (Data == NULL)) { | |
return EFI_SUCCESS; | |
} | |
// | |
// OK, actually something is changed, save the change map | |
// then signal the HUB to do enumeration. This is a good | |
// practise since UsbOnHubInterrupt is called in the context | |
// of host controller's AsyncInterrupt monitor. | |
// | |
HubIf->ChangeMap = AllocateZeroPool (DataLength); | |
if (HubIf->ChangeMap == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (HubIf->ChangeMap, Data, DataLength); | |
gBS->SignalEvent (HubIf->HubNotify); | |
return EFI_SUCCESS; | |
} | |
/** | |
Initialize the device for a non-root hub. | |
@param HubIf The USB hub interface. | |
@retval EFI_SUCCESS The hub is initialized. | |
@retval EFI_DEVICE_ERROR Failed to initialize the hub. | |
**/ | |
EFI_STATUS | |
UsbHubInit ( | |
IN USB_INTERFACE *HubIf | |
) | |
{ | |
UINT8 HubDescBuffer[256]; | |
EFI_USB_HUB_DESCRIPTOR *HubDesc; | |
USB_ENDPOINT_DESC *EpDesc; | |
USB_INTERFACE_SETTING *Setting; | |
EFI_USB_IO_PROTOCOL *UsbIo; | |
USB_DEVICE *HubDev; | |
EFI_STATUS Status; | |
UINT8 Index; | |
UINT8 NumEndpoints; | |
UINT16 Depth; | |
// | |
// Locate the interrupt endpoint for port change map | |
// | |
HubIf->IsHub = FALSE; | |
Setting = HubIf->IfSetting; | |
HubDev = HubIf->Device; | |
EpDesc = NULL; | |
NumEndpoints = Setting->Desc.NumEndpoints; | |
for (Index = 0; Index < NumEndpoints; Index++) { | |
ASSERT ((Setting->Endpoints != NULL) && (Setting->Endpoints[Index] != NULL)); | |
EpDesc = Setting->Endpoints[Index]; | |
if (USB_BIT_IS_SET (EpDesc->Desc.EndpointAddress, USB_ENDPOINT_DIR_IN) && | |
(USB_ENDPOINT_TYPE (&EpDesc->Desc) == USB_ENDPOINT_INTERRUPT)) | |
{ | |
break; | |
} | |
} | |
if (Index == NumEndpoints) { | |
DEBUG ((DEBUG_ERROR, "UsbHubInit: no interrupt endpoint found for hub %d\n", HubDev->Address)); | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// The length field of descriptor is UINT8 type, so the buffer | |
// with 256 bytes is enough to hold the descriptor data. | |
// | |
HubDesc = (EFI_USB_HUB_DESCRIPTOR *)HubDescBuffer; | |
Status = UsbHubReadDesc (HubDev, HubDesc); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "UsbHubInit: failed to read HUB descriptor - %r\n", Status)); | |
return Status; | |
} | |
HubIf->NumOfPort = HubDesc->NumPorts; | |
DEBUG ((DEBUG_INFO, "UsbHubInit: hub %d has %d ports\n", HubDev->Address, HubIf->NumOfPort)); | |
// | |
// OK, set IsHub to TRUE. Now usb bus can handle this device | |
// as a working HUB. If failed earlier, bus driver will not | |
// recognize it as a hub. Other parts of the bus should be able | |
// to work. | |
// | |
HubIf->IsHub = TRUE; | |
HubIf->HubApi = &mUsbHubApi; | |
HubIf->HubEp = EpDesc; | |
if (HubIf->Device->Speed == EFI_USB_SPEED_SUPER) { | |
Depth = (UINT16)(HubIf->Device->Tier - 1); | |
DEBUG ((DEBUG_INFO, "UsbHubInit: Set Hub Depth as 0x%x\n", Depth)); | |
UsbHubCtrlSetHubDepth (HubIf->Device, Depth); | |
for (Index = 0; Index < HubDesc->NumPorts; Index++) { | |
UsbHubCtrlSetPortFeature (HubIf->Device, Index, USB_HUB_PORT_REMOTE_WAKE_MASK); | |
} | |
} else { | |
// | |
// Feed power to all the hub ports. It should be ok | |
// for both gang/individual powered hubs. | |
// | |
for (Index = 0; Index < HubDesc->NumPorts; Index++) { | |
UsbHubCtrlSetPortFeature (HubIf->Device, Index, (EFI_USB_PORT_FEATURE)USB_HUB_PORT_POWER); | |
} | |
// | |
// Update for the usb hub has no power on delay requirement | |
// | |
if (HubDesc->PwrOn2PwrGood > 0) { | |
gBS->Stall (HubDesc->PwrOn2PwrGood * USB_SET_PORT_POWER_STALL); | |
} | |
UsbHubAckHubStatus (HubIf->Device); | |
} | |
// | |
// Create an event to enumerate the hub's port. On | |
// | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
UsbHubEnumeration, | |
HubIf, | |
&HubIf->HubNotify | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"UsbHubInit: failed to create signal for hub %d - %r\n", | |
HubDev->Address, | |
Status | |
)); | |
return Status; | |
} | |
// | |
// Create AsyncInterrupt to query hub port change endpoint | |
// periodically. If the hub ports are changed, hub will return | |
// changed port map from the interrupt endpoint. The port map | |
// must be able to hold (HubIf->NumOfPort + 1) bits (one bit for | |
// host change status). | |
// | |
UsbIo = &HubIf->UsbIo; | |
Status = UsbIo->UsbAsyncInterruptTransfer ( | |
UsbIo, | |
EpDesc->Desc.EndpointAddress, | |
TRUE, | |
USB_HUB_POLL_INTERVAL, | |
HubIf->NumOfPort / 8 + 1, | |
UsbOnHubInterrupt, | |
HubIf | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"UsbHubInit: failed to queue interrupt transfer for hub %d - %r\n", | |
HubDev->Address, | |
Status | |
)); | |
gBS->CloseEvent (HubIf->HubNotify); | |
HubIf->HubNotify = NULL; | |
return Status; | |
} | |
DEBUG ((DEBUG_INFO, "UsbHubInit: hub %d initialized\n", HubDev->Address)); | |
return Status; | |
} | |
/** | |
Get the port status. This function is required to | |
ACK the port change bits although it will return | |
the port changes in PortState. Bus enumeration code | |
doesn't need to ACK the port change bits. | |
@param HubIf The hub interface. | |
@param Port The port of the hub to get state. | |
@param PortState Variable to return the port state. | |
@retval EFI_SUCCESS The port status is successfully returned. | |
@retval Others Failed to return the status. | |
**/ | |
EFI_STATUS | |
UsbHubGetPortStatus ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port, | |
OUT EFI_USB_PORT_STATUS *PortState | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbHubCtrlGetPortStatus (HubIf->Device, Port, PortState); | |
return Status; | |
} | |
/** | |
Clear the port change status. | |
@param HubIf The hub interface. | |
@param Port The hub port. | |
**/ | |
VOID | |
UsbHubClearPortChange ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port | |
) | |
{ | |
EFI_USB_PORT_STATUS PortState; | |
USB_CHANGE_FEATURE_MAP *Map; | |
UINTN Index; | |
EFI_STATUS Status; | |
Status = UsbHubGetPortStatus (HubIf, Port, &PortState); | |
if (EFI_ERROR (Status)) { | |
return; | |
} | |
// | |
// OK, get the usb port status, now ACK the change bits. | |
// Don't return error when failed to clear the change bits. | |
// It may lead to extra port state report. USB bus should | |
// be able to handle this. | |
// | |
for (Index = 0; Index < ARRAY_SIZE (mHubFeatureMap); Index++) { | |
Map = &mHubFeatureMap[Index]; | |
if (USB_BIT_IS_SET (PortState.PortChangeStatus, Map->ChangedBit)) { | |
UsbHubCtrlClearPortFeature (HubIf->Device, Port, (UINT16)Map->Feature); | |
} | |
} | |
} | |
/** | |
Function to set the port feature for non-root hub. | |
@param HubIf The hub interface. | |
@param Port The port of the hub. | |
@param Feature The feature of the port to set. | |
@retval EFI_SUCCESS The hub port feature is set. | |
@retval Others Failed to set the port feature. | |
**/ | |
EFI_STATUS | |
UsbHubSetPortFeature ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port, | |
IN EFI_USB_PORT_FEATURE Feature | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbHubCtrlSetPortFeature (HubIf->Device, Port, (UINT8)Feature); | |
return Status; | |
} | |
/** | |
Interface function to clear the port feature for non-root hub. | |
@param HubIf The hub interface. | |
@param Port The port of the hub to clear feature for. | |
@param Feature The feature to clear. | |
@retval EFI_SUCCESS The port feature is cleared. | |
@retval Others Failed to clear the port feature. | |
**/ | |
EFI_STATUS | |
UsbHubClearPortFeature ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port, | |
IN EFI_USB_PORT_FEATURE Feature | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbHubCtrlClearPortFeature (HubIf->Device, Port, (UINT8)Feature); | |
return Status; | |
} | |
/** | |
Interface function to reset the port. | |
@param HubIf The hub interface. | |
@param Port The port to reset. | |
@retval EFI_SUCCESS The hub port is reset. | |
@retval EFI_TIMEOUT Failed to reset the port in time. | |
@retval Others Failed to reset the port. | |
**/ | |
EFI_STATUS | |
UsbHubResetPort ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port | |
) | |
{ | |
EFI_USB_PORT_STATUS PortState; | |
UINTN Index; | |
EFI_STATUS Status; | |
Status = UsbHubSetPortFeature (HubIf, Port, (EFI_USB_PORT_FEATURE)USB_HUB_PORT_RESET); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Drive the reset signal for worst 20ms. Check USB 2.0 Spec | |
// section 7.1.7.5 for timing requirements. | |
// | |
gBS->Stall (USB_SET_PORT_RESET_STALL); | |
// | |
// Check USB_PORT_STAT_C_RESET bit to see if the resetting state is done. | |
// | |
ZeroMem (&PortState, sizeof (EFI_USB_PORT_STATUS)); | |
for (Index = 0; Index < USB_WAIT_PORT_STS_CHANGE_LOOP; Index++) { | |
Status = UsbHubGetPortStatus (HubIf, Port, &PortState); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (!EFI_ERROR (Status) && | |
USB_BIT_IS_SET (PortState.PortChangeStatus, USB_PORT_STAT_C_RESET)) | |
{ | |
gBS->Stall (USB_SET_PORT_RECOVERY_STALL); | |
return EFI_SUCCESS; | |
} | |
gBS->Stall (USB_WAIT_PORT_STS_CHANGE_STALL); | |
} | |
return EFI_TIMEOUT; | |
} | |
/** | |
Release the hub's control of the interface. | |
@param HubIf The hub interface. | |
@retval EFI_SUCCESS The interface is release of hub control. | |
**/ | |
EFI_STATUS | |
UsbHubRelease ( | |
IN USB_INTERFACE *HubIf | |
) | |
{ | |
EFI_USB_IO_PROTOCOL *UsbIo; | |
EFI_STATUS Status; | |
UsbIo = &HubIf->UsbIo; | |
Status = UsbIo->UsbAsyncInterruptTransfer ( | |
UsbIo, | |
HubIf->HubEp->Desc.EndpointAddress, | |
FALSE, | |
USB_HUB_POLL_INTERVAL, | |
0, | |
NULL, | |
0 | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
gBS->CloseEvent (HubIf->HubNotify); | |
HubIf->IsHub = FALSE; | |
HubIf->HubApi = NULL; | |
HubIf->HubEp = NULL; | |
HubIf->HubNotify = NULL; | |
DEBUG ((DEBUG_INFO, "UsbHubRelease: hub device %d released\n", HubIf->Device->Address)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Initialize the interface for root hub. | |
@param HubIf The root hub interface. | |
@retval EFI_SUCCESS The interface is initialized for root hub. | |
@retval Others Failed to initialize the hub. | |
**/ | |
EFI_STATUS | |
UsbRootHubInit ( | |
IN USB_INTERFACE *HubIf | |
) | |
{ | |
EFI_STATUS Status; | |
UINT8 MaxSpeed; | |
UINT8 NumOfPort; | |
UINT8 Support64; | |
Status = UsbHcGetCapability (HubIf->Device->Bus, &MaxSpeed, &NumOfPort, &Support64); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
DEBUG (( | |
DEBUG_INFO, | |
"UsbRootHubInit: root hub %p - max speed %d, %d ports\n", | |
HubIf, | |
MaxSpeed, | |
NumOfPort | |
)); | |
HubIf->IsHub = TRUE; | |
HubIf->HubApi = &mUsbRootHubApi; | |
HubIf->HubEp = NULL; | |
HubIf->MaxSpeed = MaxSpeed; | |
HubIf->NumOfPort = NumOfPort; | |
HubIf->HubNotify = NULL; | |
// | |
// Create a timer to poll root hub ports periodically | |
// | |
Status = gBS->CreateEvent ( | |
EVT_TIMER | EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
UsbRootHubEnumeration, | |
HubIf, | |
&HubIf->HubNotify | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// It should signal the event immediately here, or device detection | |
// by bus enumeration might be delayed by the timer interval. | |
// | |
gBS->SignalEvent (HubIf->HubNotify); | |
Status = gBS->SetTimer ( | |
HubIf->HubNotify, | |
TimerPeriodic, | |
USB_ROOTHUB_POLL_INTERVAL | |
); | |
if (EFI_ERROR (Status)) { | |
gBS->CloseEvent (HubIf->HubNotify); | |
} | |
return Status; | |
} | |
/** | |
Get the port status. This function is required to | |
ACK the port change bits although it will return | |
the port changes in PortState. Bus enumeration code | |
doesn't need to ACK the port change bits. | |
@param HubIf The root hub interface. | |
@param Port The root hub port to get the state. | |
@param PortState Variable to return the port state. | |
@retval EFI_SUCCESS The port state is returned. | |
@retval Others Failed to retrieve the port state. | |
**/ | |
EFI_STATUS | |
UsbRootHubGetPortStatus ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port, | |
OUT EFI_USB_PORT_STATUS *PortState | |
) | |
{ | |
USB_BUS *Bus; | |
EFI_STATUS Status; | |
Bus = HubIf->Device->Bus; | |
Status = UsbHcGetRootHubPortStatus (Bus, Port, PortState); | |
return Status; | |
} | |
/** | |
Clear the port change status. | |
@param HubIf The root hub interface. | |
@param Port The root hub port. | |
**/ | |
VOID | |
UsbRootHubClearPortChange ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port | |
) | |
{ | |
EFI_USB_PORT_STATUS PortState; | |
USB_CHANGE_FEATURE_MAP *Map; | |
UINTN Index; | |
EFI_STATUS Status; | |
Status = UsbRootHubGetPortStatus (HubIf, Port, &PortState); | |
if (EFI_ERROR (Status)) { | |
return; | |
} | |
// | |
// OK, get the usb port status, now ACK the change bits. | |
// Don't return error when failed to clear the change bits. | |
// It may lead to extra port state report. USB bus should | |
// be able to handle this. | |
// | |
for (Index = 0; Index < ARRAY_SIZE (mRootHubFeatureMap); Index++) { | |
Map = &mRootHubFeatureMap[Index]; | |
if (USB_BIT_IS_SET (PortState.PortChangeStatus, Map->ChangedBit)) { | |
UsbHcClearRootHubPortFeature (HubIf->Device->Bus, Port, (EFI_USB_PORT_FEATURE)Map->Feature); | |
} | |
} | |
} | |
/** | |
Set the root hub port feature. | |
@param HubIf The Usb hub interface. | |
@param Port The hub port. | |
@param Feature The feature to set. | |
@retval EFI_SUCCESS The root hub port is set with the feature. | |
@retval Others Failed to set the feature. | |
**/ | |
EFI_STATUS | |
UsbRootHubSetPortFeature ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port, | |
IN EFI_USB_PORT_FEATURE Feature | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbHcSetRootHubPortFeature (HubIf->Device->Bus, Port, Feature); | |
return Status; | |
} | |
/** | |
Clear the root hub port feature. | |
@param HubIf The root hub interface. | |
@param Port The root hub port. | |
@param Feature The feature to clear. | |
@retval EFI_SUCCESS The root hub port is cleared of the feature. | |
@retval Others Failed to clear the feature. | |
**/ | |
EFI_STATUS | |
UsbRootHubClearPortFeature ( | |
IN USB_INTERFACE *HubIf, | |
IN UINT8 Port, | |
IN EFI_USB_PORT_FEATURE Feature | |
) | |
{ | |
EFI_STATUS Status; | |
Status = UsbHcClearRootHubPortFeature (HubIf->Device->Bus, Port, Feature); | |
return Status; | |
} | |
/** | |
Interface function to reset the root hub port. | |
@param RootIf The root hub interface. | |
@param Port The port to reset. | |
@retval EFI_SUCCESS The hub port is reset. | |
@retval EFI_TIMEOUT Failed to reset the port in time. | |
@retval EFI_NOT_FOUND The low/full speed device connected to high speed. | |
root hub is released to the companion UHCI. | |
@retval Others Failed to reset the port. | |
**/ | |
EFI_STATUS | |
UsbRootHubResetPort ( | |
IN USB_INTERFACE *RootIf, | |
IN UINT8 Port | |
) | |
{ | |
USB_BUS *Bus; | |
EFI_STATUS Status; | |
EFI_USB_PORT_STATUS PortState; | |
UINTN Index; | |
// | |
// Notice: although EHCI requires that ENABLED bit be cleared | |
// when reset the port, we don't need to care that here. It | |
// should be handled in the EHCI driver. | |
// | |
Bus = RootIf->Device->Bus; | |
Status = UsbHcSetRootHubPortFeature (Bus, Port, EfiUsbPortReset); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "UsbRootHubResetPort: failed to start reset on port %d\n", Port)); | |
return Status; | |
} | |
// | |
// Drive the reset signal for at least 50ms. Check USB 2.0 Spec | |
// section 7.1.7.5 for timing requirements. | |
// | |
gBS->Stall (USB_SET_ROOT_PORT_RESET_STALL); | |
Status = UsbHcClearRootHubPortFeature (Bus, Port, EfiUsbPortReset); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "UsbRootHubResetPort: failed to clear reset on port %d\n", Port)); | |
return Status; | |
} | |
gBS->Stall (USB_CLR_ROOT_PORT_RESET_STALL); | |
// | |
// USB host controller won't clear the RESET bit until | |
// reset is actually finished. | |
// | |
ZeroMem (&PortState, sizeof (EFI_USB_PORT_STATUS)); | |
for (Index = 0; Index < USB_WAIT_PORT_STS_CHANGE_LOOP; Index++) { | |
Status = UsbHcGetRootHubPortStatus (Bus, Port, &PortState); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (!USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_RESET)) { | |
break; | |
} | |
gBS->Stall (USB_WAIT_PORT_STS_CHANGE_STALL); | |
} | |
if (Index == USB_WAIT_PORT_STS_CHANGE_LOOP) { | |
DEBUG ((DEBUG_ERROR, "UsbRootHubResetPort: reset not finished in time on port %d\n", Port)); | |
return EFI_TIMEOUT; | |
} | |
if (!USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_ENABLE)) { | |
// | |
// OK, the port is reset. If root hub is of high speed and | |
// the device is of low/full speed, release the ownership to | |
// companion UHCI. If root hub is of full speed, it won't | |
// automatically enable the port, we need to enable it manually. | |
// | |
if (RootIf->MaxSpeed == EFI_USB_SPEED_HIGH) { | |
DEBUG ((DEBUG_ERROR, "UsbRootHubResetPort: release low/full speed device (%d) to UHCI\n", Port)); | |
UsbRootHubSetPortFeature (RootIf, Port, EfiUsbPortOwner); | |
return EFI_NOT_FOUND; | |
} else { | |
Status = UsbRootHubSetPortFeature (RootIf, Port, EfiUsbPortEnable); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "UsbRootHubResetPort: failed to enable port %d for UHCI\n", Port)); | |
return Status; | |
} | |
gBS->Stall (USB_SET_ROOT_PORT_ENABLE_STALL); | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Release the root hub's control of the interface. | |
@param HubIf The root hub interface. | |
@retval EFI_SUCCESS The root hub's control of the interface is | |
released. | |
**/ | |
EFI_STATUS | |
UsbRootHubRelease ( | |
IN USB_INTERFACE *HubIf | |
) | |
{ | |
DEBUG ((DEBUG_INFO, "UsbRootHubRelease: root hub released for hub %p\n", HubIf)); | |
gBS->SetTimer (HubIf->HubNotify, TimerCancel, USB_ROOTHUB_POLL_INTERVAL); | |
gBS->CloseEvent (HubIf->HubNotify); | |
return EFI_SUCCESS; | |
} | |
USB_HUB_API mUsbHubApi = { | |
UsbHubInit, | |
UsbHubGetPortStatus, | |
UsbHubClearPortChange, | |
UsbHubSetPortFeature, | |
UsbHubClearPortFeature, | |
UsbHubResetPort, | |
UsbHubRelease | |
}; | |
USB_HUB_API mUsbRootHubApi = { | |
UsbRootHubInit, | |
UsbRootHubGetPortStatus, | |
UsbRootHubClearPortChange, | |
UsbRootHubSetPortFeature, | |
UsbRootHubClearPortFeature, | |
UsbRootHubResetPort, | |
UsbRootHubRelease | |
}; |