/** @file The XHCI controller driver. Copyright (c) 2011 - 2022, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #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_CLEAR_PORT_MAP mUsbClearPortChangeMap[] = { { XHC_PORTSC_CSC, EfiUsbPortConnectChange }, { XHC_PORTSC_PEC, EfiUsbPortEnableChange }, { XHC_PORTSC_OCC, EfiUsbPortOverCurrentChange }, { XHC_PORTSC_PRC, EfiUsbPortResetChange } }; 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 } }; USB_CLEAR_PORT_MAP mUsbHubClearPortChangeMap[] = { { XHC_HUB_PORTSC_CSC, EfiUsbPortConnectChange }, { XHC_HUB_PORTSC_PEC, EfiUsbPortEnableChange }, { XHC_HUB_PORTSC_OCC, EfiUsbPortOverCurrentChange }, { XHC_HUB_PORTSC_PRC, EfiUsbPortResetChange }, { XHC_HUB_PORTSC_BHRC, Usb3PortBHPortResetChange } }; 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->Support64BitDma; DEBUG ((DEBUG_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; Xhc = XHC_FROM_THIS (This); if (Xhc->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), Xhc->DevicePath ); } OldTpl = gBS->RaiseTPL (XHC_TPL); switch (Attributes) { case EFI_USB_HC_RESET_GLOBAL: // // Flow through, same behavior as Host Controller Reset // case EFI_USB_HC_RESET_HOST_CONTROLLER: if ((Xhc->DebugCapSupOffset != 0xFFFFFFFF) && ((XhcReadExtCapReg (Xhc, Xhc->DebugCapSupOffset) & 0xFF) == XHC_CAP_USB_DEBUG) && ((XhcReadExtCapReg (Xhc, Xhc->DebugCapSupOffset + XHC_DC_DCCTRL) & BIT0) != 0)) { Status = EFI_SUCCESS; goto ON_EXIT; } // // 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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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; UINT8 PortSpeed; 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); PortSpeed = (State & XHC_PORTSC_PS) >> 10; // // According to XHCI 1.1 spec November 2017, // Section 7.2 xHCI Support Protocol Capability // if (PortSpeed > 0) { PortStatus->PortStatus = XhcCheckUsbPortSpeedUsedPsic (Xhc, PortSpeed, PortNumber); // If no match found in ext cap reg, fall back to PORTSC if (PortStatus->PortStatus == 0) { // // According to XHCI 1.1 spec November 2017, // bit 10~13 of the root port status register identifies the speed of the attached device. // switch (PortSpeed) { case 2: PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED; break; case 3: PortStatus->PortStatus |= USB_PORT_STAT_HIGH_SPEED; break; case 4: case 5: 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); } } MapSize = sizeof (mUsbClearPortChangeMap) / sizeof (USB_CLEAR_PORT_MAP); for (Index = 0; Index < MapSize; Index++) { if (XHC_BIT_IS_SET (State, mUsbClearPortChangeMap[Index].HwState)) { XhcClearRootHubPortFeature (This, PortNumber, (EFI_USB_PORT_FEATURE)mUsbClearPortChangeMap[Index].Selector); } } // // 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; Status = XhcPollPortStatusChange (Xhc, ParentRouteChart, PortNumber, PortStatus); // // Force resetting the port by clearing the USB_PORT_STAT_C_RESET bit in PortChangeStatus // when XhcPollPortStatusChange fails // if (EFI_ERROR (Status)) { PortStatus->PortChangeStatus &= ~(USB_PORT_STAT_C_RESET); Status = EFI_SUCCESS; } 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; 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 ((DEBUG_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 ((DEBUG_INFO, "XhcSetRootHubPortFeature :failed to start HC - %r\n", Status)); break; } } // // 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 ((DEBUG_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 ((DEBUG_INFO, "XhcClearRootHubPortFeature: status %r\n", Status)); gBS->RestoreTPL (OldTpl); return Status; } /** Submits a new transaction to a target USB device. @param Xhc The XHCI Instance. @param DeviceAddress The target device address. @param EndPointAddress Endpoint number and its direction encoded in bit 7 @param DeviceSpeed Target device speed. @param MaximumPacketLength Maximum packet size the default control transfer endpoint is capable of sending or receiving. @param Type The transaction type. @param Request USB device request to send. @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 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 XhcTransfer ( IN USB_XHCI_INSTANCE *Xhc, IN UINT8 DeviceAddress, IN UINT8 EndPointAddress, IN UINT8 DeviceSpeed, IN UINTN MaximumPacketLength, IN UINTN Type, IN EFI_USB_DEVICE_REQUEST *Request, IN OUT VOID *Data, IN OUT UINTN *DataLength, IN UINTN Timeout, OUT UINT32 *TransferResult ) { EFI_STATUS Status; EFI_STATUS RecoveryStatus; URB *Urb; ASSERT ((Type == XHC_CTRL_TRANSFER) || (Type == XHC_BULK_TRANSFER) || (Type == XHC_INT_TRANSFER_SYNC)); Urb = XhcCreateUrb ( Xhc, DeviceAddress, EndPointAddress, DeviceSpeed, MaximumPacketLength, Type, Request, Data, *DataLength, NULL, NULL ); if (Urb == NULL) { DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: failed to create URB!\n", Type)); return EFI_OUT_OF_RESOURCES; } Status = XhcExecTransfer (Xhc, FALSE, Urb, Timeout); if (Status == EFI_TIMEOUT) { // // The transfer timed out. Abort the transfer by dequeueing of the TD. // RecoveryStatus = XhcDequeueTrbFromEndpoint (Xhc, Urb); if (RecoveryStatus == EFI_ALREADY_STARTED) { // // The URB is finished just before stopping endpoint. // Change returning status from EFI_TIMEOUT to EFI_SUCCESS. // ASSERT (Urb->Result == EFI_USB_NOERROR); Status = EFI_SUCCESS; DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: pending URB is finished, Length = %d.\n", Type, Urb->Completed)); } else if (EFI_ERROR (RecoveryStatus)) { DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: XhcDequeueTrbFromEndpoint failed!\n", Type)); } } *TransferResult = Urb->Result; *DataLength = Urb->Completed; if ((*TransferResult == EFI_USB_ERR_STALL) || (*TransferResult == EFI_USB_ERR_BABBLE)) { ASSERT (Status == EFI_DEVICE_ERROR); RecoveryStatus = XhcRecoverHaltedEndpoint (Xhc, Urb); if (EFI_ERROR (RecoveryStatus)) { DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: XhcRecoverHaltedEndpoint failed!\n", Type)); } } Xhc->PciIo->Flush (Xhc->PciIo); XhcFreeUrb (Xhc, Urb); 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; 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; UINTN MapSize; EFI_USB_PORT_STATUS PortStatus; UINT32 State; EFI_USB_DEVICE_REQUEST ClearPortRequest; UINTN Len; // // 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; Len = 0; if (XhcIsHalt (Xhc) || XhcIsSysError (Xhc)) { DEBUG ((DEBUG_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; } } if (Xhc->UsbDevContext[SlotId].XhciDevAddr == 0) { Status = EFI_DEVICE_ERROR; goto ON_EXIT; } // // 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; } // // 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)); Status = XhcTransfer ( Xhc, DeviceAddress, Endpoint, DeviceSpeed, MaximumPacketLength, XHC_CTRL_TRANSFER, Request, Data, DataLength, Timeout, TransferResult ); if (EFI_ERROR (Status)) { goto ON_EXIT; } // // 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)) || ((DeviceSpeed == EFI_USB_SPEED_FULL) && (*DataLength == 8)))) { 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); } } 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); // // Default to use AlternateSetting 0 for all interfaces. // Xhc->UsbDevContext[SlotId].ActiveAlternateSetting = AllocateZeroPool (Xhc->UsbDevContext[SlotId].ConfDesc[Index]->NumInterfaces * sizeof (UINT8)); } } 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 ((DEBUG_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); } } } 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]); } break; } } } else if ((Request->Request == USB_REQ_SET_INTERFACE) && (Request->RequestType == USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_STANDARD, USB_TARGET_INTERFACE))) { // // Hook Set_Interface request from UsbBus as we need configure interface setting. // Request->Value indicates AlterlateSetting to set // Request->Index indicates Interface to set // if (Xhc->UsbDevContext[SlotId].ActiveAlternateSetting[(UINT8)Request->Index] != (UINT8)Request->Value) { if (Xhc->HcCParams.Data.Csz == 0) { Status = XhcSetInterface (Xhc, SlotId, DeviceSpeed, Xhc->UsbDevContext[SlotId].ConfDesc[Xhc->UsbDevContext[SlotId].ActiveConfiguration - 1], Request); } else { Status = XhcSetInterface64 (Xhc, SlotId, DeviceSpeed, Xhc->UsbDevContext[SlotId].ConfDesc[Xhc->UsbDevContext[SlotId].ActiveConfiguration - 1], Request); } } } 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 { // // For high or full/low 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; } } // // 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); } } MapSize = sizeof (mUsbHubClearPortChangeMap) / sizeof (USB_CLEAR_PORT_MAP); for (Index = 0; Index < MapSize; Index++) { if (XHC_BIT_IS_SET (State, mUsbHubClearPortChangeMap[Index].HwState)) { ZeroMem (&ClearPortRequest, sizeof (EFI_USB_DEVICE_REQUEST)); ClearPortRequest.RequestType = USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_CLASS, USB_TARGET_OTHER); ClearPortRequest.Request = (UINT8)USB_REQ_CLEAR_FEATURE; ClearPortRequest.Value = mUsbHubClearPortChangeMap[Index].Selector; ClearPortRequest.Index = Request->Index; ClearPortRequest.Length = 0; XhcControlTransfer ( This, DeviceAddress, DeviceSpeed, MaximumPacketLength, &ClearPortRequest, EfiUsbNoData, NULL, &Len, Timeout, Translator, TransferResult ); } } XhcPollPortStatusChange (Xhc, Xhc->UsbDevContext[SlotId].RouteString, (UINT8)Request->Index, &PortStatus); *(UINT32 *)Data = *(UINT32 *)&PortStatus; } ON_EXIT: if (EFI_ERROR (Status)) { DEBUG ((DEBUG_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; UINT8 SlotId; EFI_STATUS Status; EFI_TPL OldTpl; 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)) || ((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 ((DEBUG_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. // Status = XhcTransfer ( Xhc, DeviceAddress, EndPointAddress, DeviceSpeed, MaximumPacketLength, XHC_BULK_TRANSFER, NULL, Data[0], DataLength, Timeout, TransferResult ); ON_EXIT: if (EFI_ERROR (Status)) { if (Status == EFI_TIMEOUT) { DebugErrorLevel = DEBUG_VERBOSE; } else { DebugErrorLevel = DEBUG_ERROR; } DEBUG ((DebugErrorLevel, "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; 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 ((DEBUG_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 ((DEBUG_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; } Urb = XhciInsertAsyncIntTransfer ( Xhc, DeviceAddress, EndPointAddress, DeviceSpeed, MaximumPacketLength, DataLength, CallBackFunction, Context ); if (Urb == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ON_EXIT; } // // Ring the doorbell // Status = RingIntTransferDoorBell (Xhc, Urb); ON_EXIT: Xhc->PciIo->Flush (Xhc->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 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; UINT8 SlotId; EFI_STATUS Status; EFI_TPL OldTpl; // // 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 (XHC_TPL); Xhc = XHC_FROM_THIS (This); *TransferResult = EFI_USB_ERR_SYSTEM; Status = EFI_DEVICE_ERROR; if (XhcIsHalt (Xhc) || XhcIsSysError (Xhc)) { DEBUG ((DEBUG_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; } Status = XhcTransfer ( Xhc, DeviceAddress, EndPointAddress, DeviceSpeed, MaximumPacketLength, XHC_INT_TRANSFER_SYNC, NULL, Data, DataLength, Timeout, TransferResult ); ON_EXIT: if (EFI_ERROR (Status)) { DEBUG ((DEBUG_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 DevicePath The device path of host controller. @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 EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN UINT64 OriginalPciAttributes ) { USB_XHCI_INSTANCE *Xhc; EFI_STATUS Status; UINT32 PageSize; UINT16 ExtCapReg; UINT8 ReleaseNumber; Xhc = AllocateZeroPool (sizeof (USB_XHCI_INSTANCE)); if (Xhc == NULL) { return NULL; } // // Initialize private data structure // Xhc->Signature = XHCI_INSTANCE_SIG; Xhc->PciIo = PciIo; Xhc->DevicePath = DevicePath; Xhc->OriginalPciAttributes = OriginalPciAttributes; CopyMem (&Xhc->Usb2Hc, &gXhciUsb2HcTemplate, sizeof (EFI_USB2_HC_PROTOCOL)); Status = PciIo->Pci.Read ( PciIo, EfiPciIoWidthUint8, XHC_PCI_SBRN_OFFSET, 1, &ReleaseNumber ); if (!EFI_ERROR (Status)) { Xhc->Usb2Hc.MajorRevision = (ReleaseNumber & 0xF0) >> 4; Xhc->Usb2Hc.MinorRevision = (ReleaseNumber & 0x0F); } 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); if ((PageSize & (~XHC_PAGESIZE_MASK)) != 0) { DEBUG ((DEBUG_ERROR, "XhcCreateUsb3Hc: Reserved bits are not 0 for PageSize\n")); goto ON_ERROR; } PageSize &= XHC_PAGESIZE_MASK; Xhc->PageSize = 1 << (HighBitSet32 (PageSize) + 12); ExtCapReg = (UINT16)(Xhc->HcCParams.Data.ExtCapReg); Xhc->ExtCapRegBase = ExtCapReg << 2; Xhc->UsbLegSupOffset = XhcGetCapabilityAddr (Xhc, XHC_CAP_USB_LEGACY); Xhc->DebugCapSupOffset = XhcGetCapabilityAddr (Xhc, XHC_CAP_USB_DEBUG); Xhc->Usb2SupOffset = XhcGetSupportedProtocolCapabilityAddr (Xhc, XHC_SUPPORTED_PROTOCOL_DW0_MAJOR_REVISION_USB2); Xhc->Usb3SupOffset = XhcGetSupportedProtocolCapabilityAddr (Xhc, XHC_SUPPORTED_PROTOCOL_DW0_MAJOR_REVISION_USB3); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: Capability length 0x%x\n", Xhc->CapLength)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: HcSParams1 0x%x\n", Xhc->HcSParams1)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: HcSParams2 0x%x\n", Xhc->HcSParams2)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: HcCParams 0x%x\n", Xhc->HcCParams)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: DBOff 0x%x\n", Xhc->DBOff)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: RTSOff 0x%x\n", Xhc->RTSOff)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: UsbLegSupOffset 0x%x\n", Xhc->UsbLegSupOffset)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: DebugCapSupOffset 0x%x\n", Xhc->DebugCapSupOffset)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: Usb2SupOffset 0x%x\n", Xhc->Usb2SupOffset)); DEBUG ((DEBUG_INFO, "XhcCreateUsb3Hc: Usb3SupOffset 0x%x\n", Xhc->Usb3SupOffset)); // // Create AsyncRequest Polling Timer // Status = gBS->CreateEvent ( EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, 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 handler 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); } XhcClearBiosOwnership (Xhc); // // Restore original PCI attributes // PciIo->Attributes ( PciIo, EfiPciIoAttributeOperationSet, Xhc->OriginalPciAttributes, NULL ); } /** 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; 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, "XhcDriverBindingStart: failed to enable controller\n")); goto CLOSE_PCIIO; } // // Create then install USB2_HC_PROTOCOL // Xhc = XhcCreateUsbHc (PciIo, HcDevicePath, OriginalPciAttributes); if (Xhc == NULL) { DEBUG ((DEBUG_ERROR, "XhcDriverBindingStart: failed to create USB2_HC\n")); return EFI_OUT_OF_RESOURCES; } // // Enable 64-bit DMA support in the PCI layer if this controller // supports it. // if (Xhc->HcCParams.Data.Ac64 != 0) { Status = PciIo->Attributes ( PciIo, EfiPciIoAttributeOperationEnable, EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE, NULL ); if (!EFI_ERROR (Status)) { Xhc->Support64BitDma = TRUE; } else { DEBUG (( DEBUG_WARN, "%a: failed to enable 64-bit DMA on 64-bit capable controller @ %p (%r)\n", __FUNCTION__, Controller, Status )); } } 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 ((DEBUG_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 ((DEBUG_ERROR, "XhcDriverBindingStart: failed to install USB2_HC Protocol\n")); goto FREE_POOL; } DEBUG ((DEBUG_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 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 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; } Status = gBS->UninstallProtocolInterface ( Controller, &gEfiUsb2HcProtocolGuid, Usb2Hc ); 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); } } if (Xhc->PollTimer != NULL) { gBS->CloseEvent (Xhc->PollTimer); } if (Xhc->ExitBootServiceEvent != NULL) { gBS->CloseEvent (Xhc->ExitBootServiceEvent); } XhcHaltHC (Xhc, XHC_GENERIC_TIMEOUT); XhcClearBiosOwnership (Xhc); 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; }