From: vanjeff Date: Wed, 11 Jul 2007 06:46:38 +0000 (+0000) Subject: Import EhciDxe and UhciDxe into MdeModulePkg. X-Git-Tag: edk2-stable201903~22764 X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=commitdiff_plain;h=913cb9dc645d6db47d8c2a0be0369083b8bed25d;hp=20b1aab6096eb922e0ce8acec44abfd440756e6f Import EhciDxe and UhciDxe into MdeModulePkg. git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@3191 6f19259b-4bc3-4df7-8a09-765794883524 --- diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/ComponentName.c b/MdeModulePkg/Bus/Pci/EhciDxe/ComponentName.c new file mode 100644 index 0000000000..5595217953 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/ComponentName.c @@ -0,0 +1,200 @@ +/** @file + +Copyright 2006 - 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + ComponentName.c + +Abstract: + + +**/ + +#include "Ehci.h" + +// +// EFI Component Name Functions +// +EFI_STATUS +EFIAPI +EhciComponentNameGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ); + +EFI_STATUS +EFIAPI +EhciComponentNameGetControllerName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle, OPTIONAL + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ); + +// +// EFI Component Name Protocol +// +EFI_COMPONENT_NAME_PROTOCOL gEhciComponentName = { + EhciComponentNameGetDriverName, + EhciComponentNameGetControllerName, + "eng" +}; + +static EFI_UNICODE_STRING_TABLE mEhciDriverNameTable[] = { + { "eng", L"Usb Ehci Driver" }, + { NULL , NULL } +}; + +EFI_STATUS +EFIAPI +EhciComponentNameGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ) +/*++ + + Routine Description: + Retrieves a Unicode string that is the user readable name of the EFI Driver. + + Arguments: + This - A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance. + Language - A pointer to a three character ISO 639-2 language identifier. + This is the language of the driver name that that the caller + is requesting, and it must match one of the languages specified + in SupportedLanguages. The number of languages supported by a + driver is up to the driver writer. + DriverName - A pointer to the Unicode string to return. This Unicode string + is the name of the driver specified by This in the language + specified by Language. + + Returns: + EFI_SUCCESS - The Unicode string for the Driver specified by This + and the language specified by Language was returned + in DriverName. + EFI_INVALID_PARAMETER - Language is NULL. + EFI_INVALID_PARAMETER - DriverName is NULL. + EFI_UNSUPPORTED - The driver specified by This does not support the + language specified by Language. + +--*/ +{ + return LookupUnicodeString ( + Language, + gEhciComponentName.SupportedLanguages, + mEhciDriverNameTable, + DriverName + ); +} + +EFI_STATUS +EFIAPI +EhciComponentNameGetControllerName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle, OPTIONAL + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +/*++ + + Routine Description: + Retrieves a Unicode string that is the user readable name of the controller + that is being managed by an EFI Driver. + + Arguments: + This - A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance. + ControllerHandle - The handle of a controller that the driver specified by + This is managing. This handle specifies the controller + whose name is to be returned. + ChildHandle - The handle of the child controller to retrieve the name + of. This is an optional parameter that may be NULL. It + will be NULL for device drivers. It will also be NULL + for a bus drivers that wish to retrieve the name of the + bus controller. It will not be NULL for a bus driver + that wishes to retrieve the name of a child controller. + Language - A pointer to a three character ISO 639-2 language + identifier. This is the language of the controller name + that that the caller is requesting, and it must match one + of the languages specified in SupportedLanguages. The + number of languages supported by a driver is up to the + driver writer. + ControllerName - A pointer to the Unicode string to return. This Unicode + string is the name of the controller specified by + ControllerHandle and ChildHandle in the language + specified by Language from the point of view of the + driver specified by This. + + Returns: + EFI_SUCCESS - The Unicode string for the user readable name in the + language specified by Language for the driver + specified by This was returned in DriverName. + EFI_INVALID_PARAMETER - ControllerHandle is not a valid EFI_HANDLE. + EFI_INVALID_PARAMETER - ChildHandle is not NULL and it is not a valid + EFI_HANDLE. + EFI_INVALID_PARAMETER - Language is NULL. + EFI_INVALID_PARAMETER - ControllerName is NULL. + EFI_UNSUPPORTED - The driver specified by This is not currently + managing the controller specified by + ControllerHandle and ChildHandle. + EFI_UNSUPPORTED - The driver specified by This does not support the + language specified by Language. + +--*/ +{ + EFI_STATUS Status; + USB2_HC_DEV *EhciDev; + EFI_USB2_HC_PROTOCOL *Usb2Hc; + + // + // This is a device driver, so ChildHandle must be NULL. + // + if (ChildHandle != NULL) { + return EFI_UNSUPPORTED; + } + // + // Make sure this driver is currently managing ControllerHandle + // + Status = EfiTestManagedDevice ( + ControllerHandle, + gEhciDriverBinding.DriverBindingHandle, + &gEfiPciIoProtocolGuid + ); + if (EFI_ERROR (Status)) { + return Status; + } + // + // Get the device context + // + Status = gBS->OpenProtocol ( + ControllerHandle, + &gEfiUsb2HcProtocolGuid, + (VOID **) &Usb2Hc, + gEhciDriverBinding.DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + if (EFI_ERROR (Status)) { + return Status; + } + + EhciDev = EHC_FROM_THIS (Usb2Hc); + + return LookupUnicodeString ( + Language, + gEhciComponentName.SupportedLanguages, + EhciDev->ControllerNameTable, + ControllerName + ); + +} diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.c b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.c new file mode 100644 index 0000000000..093388d45f --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.c @@ -0,0 +1,1708 @@ +/** @file + +Copyright (c) 2006 - 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + Ehci.c + +Abstract: + + +Revision History + +**/ + + +#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} +}; + + +/** + Retrieves the capablility 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. + + @return EFI_SUCCESS : host controller capability were retrieved successfully. + @return EFI_INVALID_PARAMETER : Either of the three capability pointer is NULL + +**/ +STATIC +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->HcCapParams & HCCP_64BIT); + + EHC_DEBUG (("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. + + @return EFI_SUCCESS : The reset operation succeeded. + @return EFI_INVALID_PARAMETER : Attributes is not valid. + @return EFI_UNSUPPOURTED : The type of reset specified by Attributes is + @return not currently supported by the host controller. + @return EFI_DEVICE_ERROR : Host controller isn't halted to reset. + +**/ +STATIC +EFI_STATUS +EFIAPI +EhcReset ( + IN EFI_USB2_HC_PROTOCOL *This, + IN UINT16 Attributes + ) +{ + USB2_HC_DEV *Ehc; + EFI_TPL OldTpl; + EFI_STATUS Status; + + OldTpl = gBS->RaiseTPL (EHC_TPL); + Ehc = EHC_FROM_THIS (This); + + switch (Attributes) { + case EFI_USB_HC_RESET_GLOBAL: + // + // Flow through, same behavior as Host Controller Reset + // + case EFI_USB_HC_RESET_HOST_CONTROLLER: + // + // Host Controller must be Halt when Reset it + // + if (!EhcIsHalt (Ehc)) { + Status = EhcHaltHC (Ehc, EHC_GENERIC_TIME); + + 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_STALL_1_SECOND); + + 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: + EHC_DEBUG (("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. + + @return EFI_SUCCESS : Host controller state was returned in State. + @return EFI_INVALID_PARAMETER : State is NULL. + @return EFI_DEVICE_ERROR : An error was encountered while attempting to + @return retrieve the host controller's current state. + +**/ +STATIC +EFI_STATUS +EFIAPI +EhcGetState ( + IN CONST 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); + + EHC_DEBUG (("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. + + @return EFI_SUCCESS : The USB host controller was successfully placed + @return in the state specified by State. + @return EFI_INVALID_PARAMETER : State is invalid. + @return EFI_DEVICE_ERROR : Failed to set the state due to device error. + +**/ +STATIC +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_TIME); + break; + + case EfiUsbHcStateOperational: + if (EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_SYS_ERROR)) { + Status = EFI_DEVICE_ERROR; + break; + } + + Status = EhcRunHC (Ehc, EHC_GENERIC_TIME); + break; + + case EfiUsbHcStateSuspend: + Status = EFI_UNSUPPORTED; + break; + + default: + Status = EFI_INVALID_PARAMETER; + } + + EHC_DEBUG (("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 + + @return EFI_SUCCESS : The status of the USB root hub port specified + @return by PortNumber was returned in PortStatus. + @return EFI_INVALID_PARAMETER : PortNumber is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +EhcGetRootHubPortStatus ( + IN CONST EFI_USB2_HC_PROTOCOL *This, + IN CONST 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; + + 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 |= 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 |= 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 + + @return EFI_SUCCESS : The feature specified by PortFeature was set + @return EFI_INVALID_PARAMETER : PortNumber is invalid or PortFeature is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +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_TIME); + + if (EFI_ERROR (Status)) { + EHC_DEBUG (("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: + // + // Not supported, ignore the operation + // + Status = EFI_SUCCESS; + break; + + case EfiUsbPortOwner: + State |= PORTSC_OWNER; + EhcWriteOpReg (Ehc, Offset, State); + break; + + default: + Status = EFI_INVALID_PARAMETER; + } + +ON_EXIT: + EHC_DEBUG (("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. + + @return EFI_SUCCESS : The feature specified by PortFeature was cleared + @return for the USB root hub port specified by PortNumber. + @return EFI_INVALID_PARAMETER : PortNumber is invalid or PortFeature is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +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: + case EfiUsbPortSuspendChange: + case EfiUsbPortResetChange: + // + // Not supported or not related operation + // + break; + + default: + Status = EFI_INVALID_PARAMETER; + break; + } + +ON_EXIT: + EHC_DEBUG (("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. + + @return EFI_SUCCESS : Transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : The transfer failed due to lack of resources. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Transfer failed due to timeout. + @return EFI_DEVICE_ERROR : Transfer failed due to host controller or device error. + +**/ +STATIC +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)) { + EHC_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 = 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) { + EHC_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)) { + EHC_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 Translator A pointr to the transaction translator data. + @param TimeOut Indicates the maximum time, in millisecond, which + the transfer is allowed to complete. + @param TransferResult A pointer to the detailed result information of the + bulk transfer. + + @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 transfer failed due to host controller error. + +**/ +STATIC +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; + + // + // 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)) { + EHC_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) { + EHC_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)) { + EHC_ERROR (("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. + + @return EFI_SUCCESS : The request has been successfully submitted or canceled. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_OUT_OF_RESOURCES : The request failed due to a lack of resources. + @return EFI_DEVICE_ERROR : The transfer failed due to host controller error. + +**/ +STATIC +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; + UINT8 *Data; + + // + // 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); + + EHC_DEBUG (("EhcAsyncInterruptTransfer: remove old transfer - %r\n", Status)); + goto ON_EXIT; + } + + Status = EFI_SUCCESS; + + if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { + EHC_ERROR (("EhcAsyncInterruptTransfer: HC is halt\n")); + EhcAckAllInterrupt (Ehc); + + Status = EFI_DEVICE_ERROR; + goto ON_EXIT; + } + + EhcAckAllInterrupt (Ehc); + + Data = AllocatePool (DataLength); + + if (Data == NULL) { + EHC_ERROR (("EhcAsyncInterruptTransfer: failed to allocate buffer\n")); + + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + Urb = EhcCreateUrb ( + Ehc, + DeviceAddress, + EndPointAddress, + DeviceSpeed, + *DataToggle, + MaximumPacketLength, + Translator, + EHC_INT_TRANSFER_ASYNC, + NULL, + Data, + DataLength, + CallBackFunction, + Context, + PollingInterval + ); + + if (Urb == NULL) { + EHC_ERROR (("EhcAsyncInterruptTransfer: failed to create URB\n")); + + gBS->FreePool (Data); + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + // + // New asynchronous transfer must inserted to the head. + // Check the comments in EhcMoniteAsyncRequests + // + EhcLinkQhToPeriod (Ehc, Urb->Qh); + InsertHeadList (&Ehc->AsyncIntTransfers, &Urb->UrbList); + +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 + +**/ +STATIC +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 (!EHCI_IS_DATAIN (EndPointAddress)) { + return EFI_INVALID_PARAMETER; + } + + if ((*DataToggle != 1) && (*DataToggle != 0)) { + return EFI_INVALID_PARAMETER; + } + + if (((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8)) || + ((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) || + ((DeviceSpeed == EFI_USB_SPEED_HIGH) && (MaximumPacketLength > 3072))) { + return EFI_INVALID_PARAMETER; + } + + OldTpl = gBS->RaiseTPL (EHC_TPL); + Ehc = EHC_FROM_THIS (This); + + *TransferResult = EFI_USB_ERR_SYSTEM; + Status = EFI_DEVICE_ERROR; + + if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { + EHC_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) { + EHC_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; + } + +ON_EXIT: + Ehc->PciIo->Flush (Ehc->PciIo); + gBS->RestoreTPL (OldTpl); + + if (EFI_ERROR (Status)) { + EHC_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. + +**/ +STATIC +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 + +**/ +STATIC +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; +} + +//@MT: EFI_DRIVER_ENTRY_POINT (EhcDriverEntryPoint) + +EFI_STATUS +EFIAPI +EhcDriverEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +/*++ + +Routine Description: + + Entry point for EFI drivers. + +Arguments: + + ImageHandle - EFI_HANDLE + SystemTable - EFI_SYSTEM_TABLE + +Returns: + + EFI_SUCCESS Success + EFI_DEVICE_ERROR Fail + +--*/ +{ + return EfiLibInstallAllDriverProtocols ( + ImageHandle, + SystemTable, + &gEhciDriverBinding, + ImageHandle, + &gEhciComponentName, + NULL, + NULL + ); +} + + +/** + Test to see if this driver supports ControllerHandle. Any + ControllerHandle that has Usb2HcProtocol installed will + be supported. + + @param This Protocol instance pointer. + @param Controlle 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, + &PciIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + + if (EFI_ERROR (Status)) { + return EFI_UNSUPPORTED; + } + + Status = PciIo->Pci.Read ( + PciIo, + EfiPciIoWidthUint8, + EHC_PCI_CLASSC, + 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.PI != EHC_PCI_CLASSC_PI)) { + + Status = EFI_UNSUPPORTED; + } + +ON_EXIT: + gBS->CloseProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + return Status; +} + + +/** + Create and initialize a USB2_HC_DEV + + @param PciIo The PciIo on this device + + @return The allocated and initialized USB2_HC_DEV structure + @return if created, otherwise NULL. + +**/ +STATIC +USB2_HC_DEV * +EhcCreateUsb2Hc ( + IN EFI_PCI_IO_PROTOCOL *PciIo + ) +{ + 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 = 0x1; + Ehc->Usb2Hc.MinorRevision = 0x1; + + Ehc->PciIo = PciIo; + + 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; + + EHC_DEBUG (("EhcCreateUsb2Hc: capability length %d\n", Ehc->CapLen)); + + // + // Create AsyncRequest Polling Timer + // + Status = gBS->CreateEvent ( + EVT_TIMER | EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + EhcMoniteAsyncRequests, + Ehc, + &Ehc->PollTimer + ); + + if (EFI_ERROR (Status)) { + gBS->FreePool (Ehc); + return NULL; + } + + return Ehc; +} + + +/** + 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; + + // + // Open the PciIo Protocol, then enable the USB host controller + // + Status = gBS->OpenProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + &PciIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcDriverBindingStart: failed to open PCI_IO\n")); + return EFI_DEVICE_ERROR; + } + + Status = PciIo->Attributes ( + PciIo, + EfiPciIoAttributeOperationEnable, + EFI_PCI_DEVICE_ENABLE, + NULL + ); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcDriverBindingStart: failed to enable controller\n")); + goto CLOSE_PCIIO; + } + + // + // Create then install USB2_HC_PROTOCOL + // + Ehc = EhcCreateUsb2Hc (PciIo); + + if (Ehc == NULL) { + EHC_ERROR (("EhcDriverBindingStart: failed to create USB2_HC\n")); + + Status = EFI_OUT_OF_RESOURCES; + goto CLOSE_PCIIO; + } + + Status = gBS->InstallProtocolInterface ( + &Controller, + &gEfiUsb2HcProtocolGuid, + EFI_NATIVE_INTERFACE, + &Ehc->Usb2Hc + ); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcDriverBindingStart: failed to install USB2_HC Protocol\n")); + goto FREE_POOL; + } + + // + // Robustnesss improvement such as for UoL + // + EhcClearLegacySupport (Ehc); + EhcResetHC (Ehc, EHC_STALL_1_SECOND); + + Status = EhcInitHC (Ehc); + + if (EFI_ERROR (Status)) { + EHC_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_TIME); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcDriverBindingStart: failed to start async interrupt monitor\n")); + + EhcHaltHC (Ehc, EHC_GENERIC_TIME); + goto UNINSTALL_USBHC; + } + + // + // Install the component name protocol, don't fail the start + // because of something for display. + // + AddUnicodeString ( + "eng", + gEhciComponentName.SupportedLanguages, + &Ehc->ControllerNameTable, + L"Enhanced Host Controller (USB 2.0)" + ); + + EHC_DEBUG (("EhcDriverBindingStart: EHCI started for controller @ %x\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: + gBS->CloseProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + return Status; +} + + +/** + Stop this driver on ControllerHandle. Support stoping any child handles + created by this driver. + + @param This Protocol instance pointer. + @param Controller Handle of device to stop driver on + @param NumberOfChildren Number of Children in the ChildHandleBuffer + @param ChildHandleBuffer List of handles for the children we need to stop. + + @return EFI_SUCCESS Success + @return EFI_DEVICE_ERROR Fail + +**/ +EFI_STATUS +EFIAPI +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, + &Usb2Hc, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + Ehc = EHC_FROM_THIS (Usb2Hc); + PciIo = Ehc->PciIo; + + // + // Stop AsyncRequest Polling timer then stop the EHCI driver + // and uninstall the EHCI protocl. + // + gBS->SetTimer (Ehc->PollTimer, TimerCancel, EHC_ASYNC_POLL_TIME); + EhcHaltHC (Ehc, EHC_GENERIC_TIME); + + Status = gBS->UninstallProtocolInterface ( + Controller, + &gEfiUsb2HcProtocolGuid, + Usb2Hc + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + if (Ehc->PollTimer != NULL) { + gBS->CloseEvent (Ehc->PollTimer); + } + + EhcFreeSched (Ehc); + + if (Ehc->ControllerNameTable) { + FreeUnicodeStringTable (Ehc->ControllerNameTable); + } + + // + // Disable the USB Host Controller + // + PciIo->Attributes ( + PciIo, + EfiPciIoAttributeOperationDisable, + EFI_PCI_DEVICE_ENABLE, + NULL + ); + + gBS->CloseProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + gBS->FreePool (Ehc); + return Status; +} + +EFI_DRIVER_BINDING_PROTOCOL +gEhciDriverBinding = { + EhcDriverBindingSupported, + EhcDriverBindingStart, + EhcDriverBindingStop, + 0x10, + NULL, + NULL +}; diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.h b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.h new file mode 100644 index 0000000000..57c0c00084 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.h @@ -0,0 +1,152 @@ +/** @file + +Copyright (c) 2006 - 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + Ehci.h + +Abstract: + + +Revision History + +**/ + +#ifndef _EFI_EHCI_H_ +#define _EFI_EHCI_H_ + +// +// The package level header files this module uses +// +#include +// +// The protocols, PPI and GUID defintions for this module +// +#include +#include +// +// The Library classes this module consumes +// +#include +#include +#include +#include +#include +#include +#include + + +#include + +typedef struct _USB2_HC_DEV USB2_HC_DEV; + +#include "UsbHcMem.h" +#include "EhciReg.h" +#include "EhciUrb.h" +#include "EhciSched.h" +#include "EhciDebug.h" + +enum { + USB2_HC_DEV_SIGNATURE = EFI_SIGNATURE_32 ('e', 'h', 'c', 'i'), + EHC_STALL_1_MICROSECOND = 1, + EHC_STALL_1_MILLISECOND = 1000 * EHC_STALL_1_MICROSECOND, + EHC_STALL_1_SECOND = 1000 * EHC_STALL_1_MILLISECOND, + + EHC_SET_PORT_RESET_TIME = 50 * EHC_STALL_1_MILLISECOND, + EHC_CLEAR_PORT_RESET_TIME = EHC_STALL_1_MILLISECOND, + EHC_GENERIC_TIME = 10 * EHC_STALL_1_MILLISECOND, + EHC_SYNC_POLL_TIME = 20 * EHC_STALL_1_MICROSECOND, + EHC_ASYNC_POLL_TIME = 50 * 10000UL, // The unit of time is 100us + + EHC_TPL = TPL_NOTIFY, +}; + +// +//Iterate through the doule linked list. NOT delete safe +// +#define EFI_LIST_FOR_EACH(Entry, ListHead) \ + for(Entry = (ListHead)->ForwardLink; Entry != (ListHead); Entry = Entry->ForwardLink) + +// +//Iterate through the doule linked list. This is delete-safe. +//Don't touch NextEntry +// +#define EFI_LIST_FOR_EACH_SAFE(Entry, NextEntry, ListHead) \ + for(Entry = (ListHead)->ForwardLink, NextEntry = Entry->ForwardLink;\ + Entry != (ListHead); Entry = NextEntry, NextEntry = Entry->ForwardLink) + +#define EFI_LIST_CONTAINER(Entry, Type, Field) _CR(Entry, Type, Field) + + +#define EHC_LOW_32BIT(Addr64) ((UINT32)(((UINTN)(Addr64)) & 0XFFFFFFFF)) +#define EHC_HIGH_32BIT(Addr64) ((UINT32)(RShiftU64((UINTN)(Addr64), 32) & 0XFFFFFFFF)) +#define EHC_BIT_IS_SET(Data, Bit) ((BOOLEAN)(((Data) & (Bit)) == (Bit))) + +#define EHC_REG_BIT_IS_SET(Ehc, Offset, Bit) \ + (EHC_BIT_IS_SET(EhcReadOpReg ((Ehc), (Offset)), (Bit))) + +#define EHC_FROM_THIS(a) CR(a, USB2_HC_DEV, Usb2Hc, USB2_HC_DEV_SIGNATURE) + +typedef struct _USB2_HC_DEV { + UINTN Signature; + EFI_USB2_HC_PROTOCOL Usb2Hc; + + EFI_PCI_IO_PROTOCOL *PciIo; + USBHC_MEM_POOL *MemPool; + + // + // Schedule data shared between asynchronous and periodic + // transfers: + // ShortReadStop, as its name indicates, is used to terminate + // the short read except the control transfer. EHCI follows + // the alternative next QTD point when a short read happens. + // For control transfer, even the short read happens, try the + // status stage. + // + EHC_QTD *ShortReadStop; + EFI_EVENT PollTimer; + + // + // Asynchronous(bulk and control) transfer schedule data: + // ReclaimHead is used as the head of the asynchronous transfer + // list. It acts as the reclamation header. + // + EHC_QH *ReclaimHead; + + // + // Peroidic (interrupt) transfer schedule data: + // + VOID *PeriodFrame; // Mapped as common buffer + VOID *PeriodFrameHost; + VOID *PeriodFrameMap; + + EHC_QH *PeriodOne; + LIST_ENTRY AsyncIntTransfers; + + // + // EHCI configuration data + // + UINT32 HcStructParams; // Cache of HC structure parameter, EHC_HCSPARAMS_OFFSET + UINT32 HcCapParams; // Cache of HC capability parameter, HCCPARAMS + UINT32 CapLen; // Capability length + UINT32 High32bitAddr; + + // + // Misc + // + EFI_UNICODE_STRING_TABLE *ControllerNameTable; +} USB2_HC_DEV; + + +extern EFI_DRIVER_BINDING_PROTOCOL gEhciDriverBinding; +extern EFI_COMPONENT_NAME_PROTOCOL gEhciComponentName; + +#endif diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.inf b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.inf new file mode 100644 index 0000000000..2be08326cf --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.inf @@ -0,0 +1,100 @@ +#/** @file +# Component name for module Ehci +# +# FIX ME! +# Copyright (c) 2006, Intel Corporation. All right reserved. +# +# All rights reserved. This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# +# +#**/ + +################################################################################ +# +# Defines Section - statements that will be processed to create a Makefile. +# +################################################################################ +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = Ehci + FILE_GUID = BDFE430E-8F2A-4db0-9991-6F856594777E + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + EDK_RELEASE_VERSION = 0x00020000 + EFI_SPECIFICATION_VERSION = 0x00020000 + + ENTRY_POINT = EhcDriverEntryPoint + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 IPF EBC +# + +################################################################################ +# +# Sources Section - list of files that are required for the build to succeed. +# +################################################################################ + +[Sources.common] + UsbHcMem.h + EhciUrb.c + EhciReg.h + UsbHcMem.c + EhciSched.c + EhciDebug.c + EhciReg.c + EhciDebug.h + ComponentName.c + EhciUrb.h + Ehci.h + EhciSched.h + Ehci.c + +################################################################################ +# +# Package Dependency Section - list of Package files that are required for +# this module. +# +################################################################################ + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + + +################################################################################ +# +# Library Class Section - list of Library Classes that are required for +# this module. +# +################################################################################ + +[LibraryClasses] + MemoryAllocationLib + BaseLib + UefiLib + UefiBootServicesTableLib + UefiDriverEntryPoint + BaseMemoryLib + DebugLib + + +################################################################################ +# +# Protocol C Name Section - list of Protocol and Protocol Notify C Names +# that this module uses or produces. +# +################################################################################ + +[Protocols] + gEfiPciIoProtocolGuid # PROTOCOL ALWAYS_CONSUMED + gEfiUsb2HcProtocolGuid # PROTOCOL ALWAYS_CONSUMED + diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.msa b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.msa new file mode 100644 index 0000000000..dec9936c8e --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/Ehci.msa @@ -0,0 +1,81 @@ + + + Ehci + DXE_DRIVER + BDFE430E-8F2A-4db0-9991-6F856594777E + 1.0 + Component name for module Ehci + FIX ME! + Copyright (c) 2006, Intel Corporation. All right reserved. + All rights reserved. This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + FRAMEWORK_BUILD_PACKAGING_SPECIFICATION 0x00000052 + + + IA32 X64 IPF EBC + false + Ehci + + + + DebugLib + + + BaseMemoryLib + + + UefiDriverEntryPoint + + + UefiBootServicesTableLib + + + UefiLib + + + BaseLib + + + MemoryAllocationLib + + + + Ehci.c + EhciSched.h + Ehci.h + EhciUrb.h + ComponentName.c + EhciDebug.h + EhciReg.c + EhciDebug.c + EhciSched.c + UsbHcMem.c + EhciReg.h + EhciUrb.c + UsbHcMem.h + + + + + + + + gEfiUsb2HcProtocolGuid + + + gEfiPciIoProtocolGuid + + + + EFI_SPECIFICATION_VERSION 0x00020000 + EDK_RELEASE_VERSION 0x00020000 + + EhcDriverEntryPoint + + + \ No newline at end of file diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciDebug.c b/MdeModulePkg/Bus/Pci/EhciDxe/EhciDebug.c new file mode 100644 index 0000000000..086cdbc9fc --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciDebug.c @@ -0,0 +1,345 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciDebug.c + +Abstract: + This file provides the information dump support for EHCI when in debug mode. +You can dynamically adjust the debug level by changing variable mEhcDebugLevel +and mEhcErrorLevel. + +Revision History + +**/ + + +#include "Ehci.h" + +#ifdef EFI_DEBUG +UINTN mEhcDebugMask = USB_DEBUG_FORCE_OUTPUT; + + +/** + EHCI's debug output function. It determines whether + to output by the mask and level + + @param Level The output level + @param Format The format parameters to the print + @param ... The variable length parameters after format + + @return None + +**/ +VOID +EhciDebugPrint ( + IN UINTN Level, + IN CHAR8 *Format, + ... + ) +{ + + VA_LIST Marker; + + VA_START (Marker, Format); + + if (Level & mEhcDebugMask) { + if (mEhcDebugMask & USB_DEBUG_FORCE_OUTPUT) { + DebugVPrint (DEBUG_ERROR, Format, Marker); + } else { + DebugVPrint (DEBUG_INFO, Format, Marker); + } + } + + VA_END (Marker); +} + + +/** + EHCI's debug output function. It determines whether + to output by the mask and level + + @param Format The format parameters to the print + @param ... The variable length parameters after format + + @return None + +**/ +VOID +EhcDebug ( + IN CHAR8 *Format, + ... + ) +{ + VA_LIST Marker; + + VA_START (Marker, Format); + DebugVPrint (DEBUG_INFO, Format, Marker); + VA_END (Marker); +} + + +/** + EHCI's error output function. It determines whether + to output by the mask and level + + @param Format The format parameters to the print + @param ... The variable length parameters after format + + @return None + +**/ +VOID +EhcError ( + IN CHAR8 *Format, + ... + ) +{ + + VA_LIST Marker; + + VA_START (Marker, Format); + DebugVPrint (DEBUG_ERROR, Format, Marker); + VA_END (Marker); +} + + +/** + Dump the status byte in QTD/QH to a more friendly + format + + @param State The state in the QTD/QH + @param Level The output level + + @return None + +**/ +STATIC +VOID +EhcDumpStatus ( + IN UINT32 State, + IN UINTN Level + ) +{ + if (EHC_BIT_IS_SET (State, QTD_STAT_DO_PING)) { + EhciDebugPrint (Level, " Do_Ping"); + } else { + EhciDebugPrint (Level, " Do_Out"); + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_DO_CS)) { + EhciDebugPrint (Level, " Do_CS"); + } else { + EhciDebugPrint (Level, " Do_SS"); + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR)) { + EhciDebugPrint (Level, " Transfer_Error"); + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) { + EhciDebugPrint (Level, " Babble_Error"); + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) { + EhciDebugPrint (Level, " Buffer_Error"); + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) { + EhciDebugPrint (Level, " Halted"); + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) { + EhciDebugPrint (Level, " Active"); + } + + EhciDebugPrint (Level, "\n"); +} + + +/** + Dump the fields of a QTD + + @param Qtd The QTD to dump + @param Msg The message to print before the dump + + @return None + +**/ +VOID +EhcDumpQtd ( + IN EHC_QTD *Qtd, + IN UINT8 *Msg + ) +{ + QTD_HW *QtdHw; + UINTN Index; + UINTN Level; + + Level = EHC_DEBUG_QTD; + + if (Msg != NULL) { + EhciDebugPrint (Level, Msg); + } + + EhciDebugPrint (Level, "Queue TD @ 0x%x, data length %d\n", Qtd, Qtd->DataLen); + + QtdHw = &Qtd->QtdHw; + + EhciDebugPrint (Level, "Next QTD : %x\n", QtdHw->NextQtd); + EhciDebugPrint (Level, "AltNext QTD : %x\n", QtdHw->AltNext); + EhciDebugPrint (Level, "Status : %x\n", QtdHw->Status); + EhcDumpStatus (QtdHw->Status, Level); + + if (QtdHw->Pid == QTD_PID_SETUP) { + EhciDebugPrint (Level, "PID : Setup\n"); + + } else if (QtdHw->Pid == QTD_PID_INPUT) { + EhciDebugPrint (Level, "PID : IN\n"); + + } else if (QtdHw->Pid == QTD_PID_OUTPUT) { + EhciDebugPrint (Level, "PID : OUT\n"); + + } + + EhciDebugPrint (Level, "Error Count : %d\n", QtdHw->ErrCnt); + EhciDebugPrint (Level, "Current Page : %d\n", QtdHw->CurPage); + EhciDebugPrint (Level, "IOC : %d\n", QtdHw->IOC); + EhciDebugPrint (Level, "Total Bytes : %d\n", QtdHw->TotalBytes); + EhciDebugPrint (Level, "Data Toggle : %d\n", QtdHw->DataToggle); + + for (Index = 0; Index < 5; Index++) { + EhciDebugPrint (Level, "Page[%d] : 0x%x\n", Index, QtdHw->Page[Index]); + } +} + + +/** + Dump the queue head + + @param Qh The queue head to dump + @param Msg The message to print before the dump + @param DumpBuf Whether to dump the memory buffer of the associated QTD + + @return None + +**/ +VOID +EhcDumpQh ( + IN EHC_QH *Qh, + IN UINT8 *Msg, + IN BOOLEAN DumpBuf + ) +{ + EHC_QTD *Qtd; + QH_HW *QhHw; + LIST_ENTRY *Entry; + UINTN Index; + UINTN Level; + + Level = EHC_DEBUG_QH; + + if (Msg != NULL) { + EhciDebugPrint (Level, Msg); + } + + EhciDebugPrint (Level, "Queue head @ 0x%x, interval %d, next qh %x\n", + Qh, Qh->Interval, Qh->NextQh); + + QhHw = &Qh->QhHw; + + EhciDebugPrint (Level, "Hoziontal link: %x\n", QhHw->HorizonLink); + EhciDebugPrint (Level, "Device address: %d\n", QhHw->DeviceAddr); + EhciDebugPrint (Level, "Inactive : %d\n", QhHw->Inactive); + EhciDebugPrint (Level, "EP number : %d\n", QhHw->EpNum); + EhciDebugPrint (Level, "EP speed : %d\n", QhHw->EpSpeed); + EhciDebugPrint (Level, "DT control : %d\n", QhHw->DtCtrl); + EhciDebugPrint (Level, "Reclaim head : %d\n", QhHw->ReclaimHead); + EhciDebugPrint (Level, "Max packet len: %d\n", QhHw->MaxPacketLen); + EhciDebugPrint (Level, "Ctrl EP : %d\n", QhHw->CtrlEp); + EhciDebugPrint (Level, "Nak reload : %d\n", QhHw->NakReload); + + EhciDebugPrint (Level, "SMask : %x\n", QhHw->SMask); + EhciDebugPrint (Level, "CMask : %x\n", QhHw->CMask); + EhciDebugPrint (Level, "Hub address : %d\n", QhHw->HubAddr); + EhciDebugPrint (Level, "Hub port : %d\n", QhHw->PortNum); + EhciDebugPrint (Level, "Multiplier : %d\n", QhHw->Multiplier); + + EhciDebugPrint (Level, "Cur QTD : %x\n", QhHw->CurQtd); + + EhciDebugPrint (Level, "Next QTD : %x\n", QhHw->NextQtd); + EhciDebugPrint (Level, "AltNext QTD : %x\n", QhHw->AltQtd); + EhciDebugPrint (Level, "Status : %x\n", QhHw->Status); + EhcDumpStatus (QhHw->Status, Level); + + if (QhHw->Pid == QTD_PID_SETUP) { + EhciDebugPrint (Level, "PID : Setup\n"); + + } else if (QhHw->Pid == QTD_PID_INPUT) { + EhciDebugPrint (Level, "PID : IN\n"); + + } else if (QhHw->Pid == QTD_PID_OUTPUT) { + EhciDebugPrint (Level, "PID : OUT\n"); + } + + EhciDebugPrint (Level, "Error Count : %d\n", QhHw->ErrCnt); + EhciDebugPrint (Level, "Current Page : %d\n", QhHw->CurPage); + EhciDebugPrint (Level, "IOC : %d\n", QhHw->IOC); + EhciDebugPrint (Level, "Total Bytes : %d\n", QhHw->TotalBytes); + EhciDebugPrint (Level, "Data Toggle : %d\n", QhHw->DataToggle); + + for (Index = 0; Index < 5; Index++) { + EhciDebugPrint (Level, "Page[%d] : 0x%x\n", Index, QhHw->Page[Index]); + } + + EhciDebugPrint (Level, "\n"); + + EFI_LIST_FOR_EACH (Entry, &Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList); + EhcDumpQtd (Qtd, NULL); + + if (DumpBuf && (Qtd->DataLen != 0)) { + EhcDumpBuf (Qtd->Data, Qtd->DataLen); + } + } +} + + +/** + Dump the buffer in the form of hex + + @param Buf The buffer to dump + @param Len The length of buffer + + @return None + +**/ +VOID +EhcDumpBuf ( + IN UINT8 *Buf, + IN UINTN Len + ) +{ + UINTN Index; + + for (Index = 0; Index < Len; Index++) { + if (Index % 16 == 0) { + EhciDebugPrint (EHC_DEBUG_BUF, "\n"); + } + + EhciDebugPrint (EHC_DEBUG_BUF, "%02x ", Buf[Index]); + } + + EhciDebugPrint (EHC_DEBUG_BUF, "\n"); +} + +#endif diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciDebug.h b/MdeModulePkg/Bus/Pci/EhciDxe/EhciDebug.h new file mode 100644 index 0000000000..be4bcd6556 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciDebug.h @@ -0,0 +1,159 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciDebug.h + +Abstract: + + This file contains the definination for host controller debug support routines + +Revision History + +**/ + +#ifndef _EFI_EHCI_DEBUG_H_ +#define _EFI_EHCI_DEBUG_H_ + + +enum { + USB_DEBUG_FORCE_OUTPUT = (UINTN)(1 << 0), + + EHC_DEBUG_QH = (UINTN)(1 << 8), + EHC_DEBUG_QTD = (UINTN)(1 << 9), + EHC_DEBUG_BUF = (UINTN)(1 << 10), +}; + + +/** + EHCI's debug output function. It determines whether + to output by the mask and level + + @param Level The output level + @param Format The format parameters to the print + @param ... The variable length parameters after format + + @return None + +**/ +VOID +EhciDebugPrint ( + IN UINTN Level, + IN CHAR8 *Format, + ... + ) +; + + +/** + EHCI's debug output function. It determines whether + to output by the mask and level + + @param Format The format parameters to the print + @param ... The variable length parameters after format + + @return None + +**/ +VOID +EhcDebug ( + IN CHAR8 *Format, + ... + ) +; + + + +/** + EHCI's error output function. It determines whether + to output by the mask and level + + @param Format The format parameters to the print + @param ... The variable length parameters after format + + @return None + +**/ +VOID +EhcError ( + IN CHAR8 *Format, + ... + ) +; + + + +/** + Dump the fields of a QTD + + @param Qtd The QTD to dump + @param Msg The message to print before the dump + + @return None + +**/ +VOID +EhcDumpQtd ( + IN EHC_QTD *Qtd, + IN UINT8 *Msg + ) +; + + + +/** + Dump the queue head + + @param Qh The queue head to dump + @param Msg The message to print before the dump + @param DumpBuf Whether to dump the memory buffer of the associated QTD + + @return None + +**/ +VOID +EhcDumpQh ( + IN EHC_QH *Qh, + IN UINT8 *Msg, + IN BOOLEAN DumpBuf + ) +; + + + +/** + Dump the buffer in the form of hex + + @param Buf The buffer to dump + @param Len The length of buffer + + @return None + +**/ +VOID +EhcDumpBuf ( + IN UINT8 *Buf, + IN UINTN Len + ) +; + +#ifdef EFI_DEBUG + #define EHC_DEBUG(arg) EhcDebug arg + #define EHC_ERROR(arg) EhcError arg + #define EHC_DUMP_QH(arg) EhcDumpQh arg +#else + #define EHC_DEBUG(arg) + #define EHC_ERROR(arg) + #define EHC_DUMP_QH(arg) +#endif + +#endif diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciReg.c b/MdeModulePkg/Bus/Pci/EhciDxe/EhciReg.c new file mode 100644 index 0000000000..aa16605556 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciReg.c @@ -0,0 +1,634 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciReg.c + +Abstract: + + The EHCI register operation routines. + + +Revision History + +**/ + + +#include "Ehci.h" + + +/** + Read EHCI capability register + + @param Ehc The Ehc device + @param Offset Capability register address + + @return The register content read + +**/ +UINT32 +EhcReadCapRegister ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset + ) +{ + UINT32 Data; + EFI_STATUS Status; + + Status = Ehc->PciIo->Mem.Read ( + Ehc->PciIo, + EfiPciIoWidthUint32, + EHC_BAR_INDEX, + (UINT64) Offset, + 1, + &Data + ); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcReadCapRegister: Pci Io read error - %r at %d\n", Status, Offset)); + Data = 0xFFFF; + } + + return Data; +} + + +/** + Read Ehc Operation register + + @param Ehc The EHCI device + @param Offset The operation register offset + + @return The register content read + +**/ +UINT32 +EhcReadOpReg ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset + ) +{ + UINT32 Data; + EFI_STATUS Status; + + ASSERT (Ehc->CapLen != 0); + + Status = Ehc->PciIo->Mem.Read ( + Ehc->PciIo, + EfiPciIoWidthUint32, + EHC_BAR_INDEX, + (UINT64) (Ehc->CapLen + Offset), + 1, + &Data + ); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcReadOpReg: Pci Io Read error - %r at %d\n", Status, Offset)); + Data = 0xFFFF; + } + + return Data; +} + + +/** + Write the data to the EHCI operation register + + @param Ehc The EHCI device + @param Offset EHCI operation register offset + @param Data The data to write + + @return None + +**/ +VOID +EhcWriteOpReg ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset, + IN UINT32 Data + ) +{ + EFI_STATUS Status; + + ASSERT (Ehc->CapLen != 0); + + Status = Ehc->PciIo->Mem.Write ( + Ehc->PciIo, + EfiPciIoWidthUint32, + EHC_BAR_INDEX, + (UINT64) (Ehc->CapLen + Offset), + 1, + &Data + ); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcWriteOpReg: Pci Io Write error: %r at %d\n", Status, Offset)); + } +} + + +/** + Set one bit of the operational register while keeping other bits + + @param Ehc The EHCI device + @param Offset The offset of the operational register + @param Bit The bit mask of the register to set + + @return None + +**/ +STATIC +VOID +EhcSetOpRegBit ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset, + IN UINT32 Bit + ) +{ + UINT32 Data; + + Data = EhcReadOpReg (Ehc, Offset); + Data |= Bit; + EhcWriteOpReg (Ehc, Offset, Data); +} + + +/** + Clear one bit of the operational register while keeping other bits + + @param Ehc The EHCI device + @param Offset The offset of the operational register + @param Bit The bit mask of the register to clear + + @return None + +**/ +STATIC +VOID +EhcClearOpRegBit ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset, + IN UINT32 Bit + ) +{ + UINT32 Data; + + Data = EhcReadOpReg (Ehc, Offset); + Data &= ~Bit; + EhcWriteOpReg (Ehc, Offset, Data); +} + + +/** + Wait the operation register's bit as specified by Bit + to become set (or clear) + + @param Ehc The EHCI device + @param Offset The offset of the operation register + @param Bit The bit of the register to wait for + @param WaitToSet Wait the bit to set or clear + @param Timeout The time to wait before abort (in millisecond) + + @retval EFI_SUCCESS The bit successfully changed by host controller + @retval EFI_TIMEOUT The time out occurred + +**/ +STATIC +EFI_STATUS +EhcWaitOpRegBit ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset, + IN UINT32 Bit, + IN BOOLEAN WaitToSet, + IN UINT32 Timeout + ) +{ + UINT32 Index; + + for (Index = 0; Index < Timeout / EHC_SYNC_POLL_TIME + 1; Index++) { + if (EHC_REG_BIT_IS_SET (Ehc, Offset, Bit) == WaitToSet) { + return EFI_SUCCESS; + } + + gBS->Stall (EHC_SYNC_POLL_TIME); + } + + return EFI_TIMEOUT; +} + + +/** + Add support for UEFI Over Legacy (UoL) feature, stop + the legacy USB SMI support + + @param Ehc The EHCI device. + + @return None + +**/ +VOID +EhcClearLegacySupport ( + IN USB2_HC_DEV *Ehc + ) +{ + UINT32 ExtendCap; + EFI_PCI_IO_PROTOCOL *PciIo; + UINT32 Value; + UINT32 TimeOut; + + EHC_DEBUG (("EhcClearLegacySupport: called to clear legacy support\n")); + + PciIo = Ehc->PciIo; + ExtendCap = (Ehc->HcCapParams >> 8) & 0xFF; + + PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); + PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap + 0x4, 1, &Value); + + PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); + Value |= (0x1 << 24); + PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); + + TimeOut = 40; + while (TimeOut--) { + gBS->Stall (500); + + PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); + + if ((Value & 0x01010000) == 0x01000000) { + break; + } + } + + PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); + PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap + 0x4, 1, &Value); +} + + + +/** + Set door bell and wait it to be ACKed by host controller. + This function is used to synchronize with the hardware. + + @param Ehc The EHCI device + @param Timeout The time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : Synchronized with the hardware + @return EFI_TIMEOUT : Time out happened while waiting door bell to set + +**/ +EFI_STATUS +EhcSetAndWaitDoorBell ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + UINT32 Data; + + EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_IAAD); + + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_IAA, TRUE, Timeout); + + // + // ACK the IAA bit in USBSTS register. Make sure other + // interrupt bits are not ACKed. These bits are WC (Write Clean). + // + Data = EhcReadOpReg (Ehc, EHC_USBSTS_OFFSET); + Data &= ~USBSTS_INTACK_MASK; + Data |= USBSTS_IAA; + + EhcWriteOpReg (Ehc, EHC_USBSTS_OFFSET, Data); + + return Status; +} + + +/** + Clear all the interrutp status bits, these bits + are Write-Clean + + @param Ehc The EHCI device + + @return None + +**/ +VOID +EhcAckAllInterrupt ( + IN USB2_HC_DEV *Ehc + ) +{ + EhcWriteOpReg (Ehc, EHC_USBSTS_OFFSET, USBSTS_INTACK_MASK); +} + + +/** + Enable the periodic schedule then wait EHC to + actually enable it. + + @param Ehc The EHCI device + @param Timeout The time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : The periodical schedule is enabled + @return EFI_TIMEOUT : Time out happened while enabling periodic schedule + +**/ +STATIC +EFI_STATUS +EhcEnablePeriodSchd ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_PERIOD); + + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_PERIOD_ENABLED, TRUE, Timeout); + return Status; +} + + + +/** + Disable periodic schedule + + @param Ehc The EHCI device + @param Timeout Time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : Periodic schedule is disabled. + @return EFI_DEVICE_ERROR : Fail to disable periodic schedule + +**/ +STATIC +EFI_STATUS +EhcDisablePeriodSchd ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + EhcClearOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_PERIOD); + + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_PERIOD_ENABLED, FALSE, Timeout); + return Status; +} + + + +/** + Enable asynchrounous schedule + + @param Ehc The EHCI device + @param Timeout Time to wait before abort + + @return EFI_SUCCESS : The EHCI asynchronous schedule is enabled + @return Others : Failed to enable the asynchronous scheudle + +**/ +STATIC +EFI_STATUS +EhcEnableAsyncSchd ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_ASYNC); + + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_ASYNC_ENABLED, TRUE, Timeout); + return Status; +} + + + +/** + Disable asynchrounous schedule + + @param Ehc The EHCI device + @param Timeout Time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : The asynchronous schedule is disabled + @return Others : Failed to disable the asynchronous schedule + +**/ +STATIC +EFI_STATUS +EhcDisableAsyncSchd ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + EhcClearOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_ASYNC); + + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_ASYNC_ENABLED, FALSE, Timeout); + return Status; +} + + + +/** + Whether Ehc is halted + + @param Ehc The EHCI device + + @return TRUE : The controller is halted + @return FALSE : It isn't halted + +**/ +BOOLEAN +EhcIsHalt ( + IN USB2_HC_DEV *Ehc + ) +{ + return EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT); +} + + +/** + Whether system error occurred + + @param Ehc The EHCI device + + @return TRUE : System error happened + @return FALSE : No system error + +**/ +BOOLEAN +EhcIsSysError ( + IN USB2_HC_DEV *Ehc + ) +{ + return EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_SYS_ERROR); +} + + +/** + Reset the host controller + + @param Ehc The EHCI device + @param Timeout Time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : The host controller is reset + @return Others : Failed to reset the host + +**/ +EFI_STATUS +EhcResetHC ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + // + // Host can only be reset when it is halt. If not so, halt it + // + if (!EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT)) { + Status = EhcHaltHC (Ehc, Timeout); + + if (EFI_ERROR (Status)) { + return Status; + } + } + + EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RESET); + Status = EhcWaitOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RESET, FALSE, Timeout); + return Status; +} + + +/** + Halt the host controller + + @param Ehc The EHCI device + @param Timeout Time to wait before abort + + @return EFI_SUCCESS : The EHCI is halt + @return EFI_TIMEOUT : Failed to halt the controller before Timeout + +**/ +EFI_STATUS +EhcHaltHC ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + EhcClearOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT, TRUE, Timeout); + return Status; +} + + +/** + Set the EHCI to run + + @param Ehc The EHCI device + @param Timeout Time to wait before abort + + @return EFI_SUCCESS : The EHCI is running + @return Others : Failed to set the EHCI to run + +**/ +EFI_STATUS +EhcRunHC ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + + EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); + Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT, FALSE, Timeout); + return Status; +} + + +/** + Initialize the HC hardware. + EHCI spec lists the five things to do to initialize the hardware + 1. Program CTRLDSSEGMENT + 2. Set USBINTR to enable interrupts + 3. Set periodic list base + 4. Set USBCMD, interrupt threshold, frame list size etc + 5. Write 1 to CONFIGFLAG to route all ports to EHCI + + @param Ehc The EHCI device + + @return EFI_SUCCESS : The EHCI has come out of halt state + @return EFI_TIMEOUT : Time out happened + +**/ +EFI_STATUS +EhcInitHC ( + IN USB2_HC_DEV *Ehc + ) +{ + EFI_STATUS Status; + + ASSERT (EhcIsHalt (Ehc)); + + // + // Allocate the periodic frame and associated memeory + // management facilities if not already done. + // + if (Ehc->PeriodFrame != NULL) { + EhcFreeSched (Ehc); + } + + Status = EhcInitSched (Ehc); + + if (EFI_ERROR (Status)) { + return Status; + } + // + // 1. Program the CTRLDSSEGMENT register with the high 32 bit addr + // + EhcWriteOpReg (Ehc, EHC_CTRLDSSEG_OFFSET, Ehc->High32bitAddr); + + // + // 2. Clear USBINTR to disable all the interrupt. UEFI works by polling + // + EhcWriteOpReg (Ehc, EHC_USBINTR_OFFSET, 0); + + // + // 3. Program periodic frame list, already done in EhcInitSched + // 4. Start the Host Controller + // + EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); + + // + // 5. Set all ports routing to EHC + // + EhcSetOpRegBit (Ehc, EHC_CONFIG_FLAG_OFFSET, CONFIGFLAG_ROUTE_EHC); + + Status = EhcEnablePeriodSchd (Ehc, EHC_GENERIC_TIME); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcInitHC: failed to enable period schedule\n")); + return Status; + } + + Status = EhcEnableAsyncSchd (Ehc, EHC_GENERIC_TIME); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcInitHC: failed to enable async schedule\n")); + return Status; + } + + return EFI_SUCCESS; +} diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciReg.h b/MdeModulePkg/Bus/Pci/EhciDxe/EhciReg.h new file mode 100644 index 0000000000..2ac27eb39c --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciReg.h @@ -0,0 +1,351 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciReg.h + +Abstract: + + This file contains the definination for host controller register operation routines + +Revision History + +**/ + +#ifndef _EFI_EHCI_REG_H_ +#define _EFI_EHCI_REG_H_ + + +enum { + // + // Capability register offset + // + EHC_CAPLENGTH_OFFSET = 0, // Capability register length offset + EHC_HCSPARAMS_OFFSET = 0x04, // Structural Parameters 04-07h + EHC_HCCPARAMS_OFFSET = 0x08, // Capability parameters offset + + // + // Capability register bit definition + // + HCSP_NPORTS = 0x0F, // Number of root hub port + HCCP_64BIT = 0x01, // 64-bit addressing capability + + // + // Operational register offset + // + EHC_USBCMD_OFFSET = 0x0, // USB command register offset + EHC_USBSTS_OFFSET = 0x04, // Statue register offset + EHC_USBINTR_OFFSET = 0x08, // USB interrutp offset + EHC_FRINDEX_OFFSET = 0x0C, // Frame index offset + EHC_CTRLDSSEG_OFFSET = 0x10, // Control data structure segment offset + EHC_FRAME_BASE_OFFSET = 0x14, // Frame list base address offset + EHC_ASYNC_HEAD_OFFSET = 0x18, // Next asynchronous list address offset + EHC_CONFIG_FLAG_OFFSET = 0x40, // Configure flag register offset + EHC_PORT_STAT_OFFSET = 0x44, // Port status/control offset + + EHC_FRAME_LEN = 1024, + + // + // Register bit definition + // + CONFIGFLAG_ROUTE_EHC = 0x01, // Route port to EHC + + USBCMD_RUN = 0x01, // Run/stop + USBCMD_RESET = 0x02, // Start the host controller reset + USBCMD_ENABLE_PERIOD = 0x10, // Enable periodic schedule + USBCMD_ENABLE_ASYNC = 0x20, // Enable asynchronous schedule + USBCMD_IAAD = 0x40, // Interrupt on async advance doorbell + + USBSTS_IAA = 0x20, // Interrupt on async advance + USBSTS_PERIOD_ENABLED = 0x4000, // Periodic schedule status + USBSTS_ASYNC_ENABLED = 0x8000, // Asynchronous schedule status + USBSTS_HALT = 0x1000, // Host controller halted + USBSTS_SYS_ERROR = 0x10, // Host system error + USBSTS_INTACK_MASK = 0x003F, // Mask for the interrupt ACK, the WC + // (write clean) bits in USBSTS register + + PORTSC_CONN = 0x01, // Current Connect Status + PORTSC_CONN_CHANGE = 0x02, // Connect Status Change + PORTSC_ENABLED = 0x04, // Port Enable / Disable + PORTSC_ENABLE_CHANGE = 0x08, // Port Enable / Disable Change + PORTSC_OVERCUR = 0x10, // Over current Active + PORTSC_OVERCUR_CHANGE = 0x20, // Over current Change + PORSTSC_RESUME = 0x40, // Force Port Resume + PORTSC_SUSPEND = 0x80, // Port Suspend State + PORTSC_RESET = 0x100, // Port Reset + PORTSC_LINESTATE_K = 0x400, // Line Status K-state + PORTSC_LINESTATE_J = 0x800, // Line Status J-state + PORTSC_POWER = 0x1000, // Port Power + PORTSC_OWNER = 0x2000, // Port Owner + PORTSC_CHANGE_MASK = 0x2A, // Mask of the port change bits, + // they are WC (write clean) + // + // PCI Configuration Registers + // + EHC_PCI_CLASSC = 0x09, + EHC_PCI_CLASSC_PI = 0x20, + EHC_BAR_INDEX = 0, /* how many bytes away from USB_BASE to 0x10 */ +}; + +#define EHC_LINK_TERMINATED(Link) (((Link) & 0x01) != 0) + +#define EHC_ADDR(High, QhHw32) \ + ((VOID *) (UINTN) (LShiftU64 ((High), 32) | ((QhHw32) & 0xFFFFFFF0))) + +#define EHCI_IS_DATAIN(EndpointAddr) EHC_BIT_IS_SET((EndpointAddr), 0x80) + +// +// Structure to map the hardware port states to the +// UEFI's port states. +// +typedef struct { + UINT16 HwState; + UINT16 UefiState; +} USB_PORT_STATE_MAP; + +// +// Ehci Data and Ctrl Structures +// +#pragma pack(1) +typedef struct { + UINT8 PI; + UINT8 SubClassCode; + UINT8 BaseCode; +} USB_CLASSC; +#pragma pack() + + +UINT32 +EhcReadCapRegister ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset + ) +/*++ + +Routine Description: + + Read EHCI capability register + +Arguments: + + Ehc - The Ehc device + Offset - Capability register address + +Returns: + + The register content read + +--*/ +; + + +/** + Read Ehc Operation register + + @param Ehc The EHCI device + @param Offset The operation register offset + + @return The register content read + +**/ +UINT32 +EhcReadOpReg ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset + ) +; + + +/** + Write the data to the EHCI operation register + + @param Ehc The EHCI device + @param Offset EHCI operation register offset + @param Data The data to write + + @return None + +**/ +VOID +EhcWriteOpReg ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Offset, + IN UINT32 Data + ) +; + + +/** + Add support for UEFI Over Legacy (UoL) feature, stop + the legacy USB SMI support + + @param Ehc The EHCI device. + + @return None + +**/ +VOID +EhcClearLegacySupport ( + IN USB2_HC_DEV *Ehc + ) +; + + + +/** + Set door bell and wait it to be ACKed by host controller. + This function is used to synchronize with the hardware. + + @param Ehc The EHCI device + @param Timeout The time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : Synchronized with the hardware + @return EFI_TIMEOUT : Time out happened while waiting door bell to set + +**/ +EFI_STATUS +EhcSetAndWaitDoorBell ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +; + + +/** + Clear all the interrutp status bits, these bits + are Write-Clean + + @param Ehc The EHCI device + + @return None + +**/ +VOID +EhcAckAllInterrupt ( + IN USB2_HC_DEV *Ehc + ) +; + + + +/** + Whether Ehc is halted + + @param Ehc The EHCI device + + @return TRUE : The controller is halted + @return FALSE : It isn't halted + +**/ +BOOLEAN +EhcIsHalt ( + IN USB2_HC_DEV *Ehc + ) +; + + +/** + Whether system error occurred + + @param Ehc The EHCI device + + @return TRUE : System error happened + @return FALSE : No system error + +**/ +BOOLEAN +EhcIsSysError ( + IN USB2_HC_DEV *Ehc + ) +; + + + +/** + Reset the host controller + + @param Ehc The EHCI device + @param Timeout Time to wait before abort (in millisecond, ms) + + @return EFI_SUCCESS : The host controller is reset + @return Others : Failed to reset the host + +**/ +EFI_STATUS +EhcResetHC ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +; + + + +/** + Halt the host controller + + @param Ehc The EHCI device + @param Timeout Time to wait before abort + + @return EFI_SUCCESS : The EHCI is halt + @return EFI_TIMEOUT : Failed to halt the controller before Timeout + +**/ +EFI_STATUS +EhcHaltHC ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +; + + + +/** + Set the EHCI to run + + @param Ehc The EHCI device + @param Timeout Time to wait before abort + + @return EFI_SUCCESS : The EHCI is running + @return Others : Failed to set the EHCI to run + +**/ +EFI_STATUS +EhcRunHC ( + IN USB2_HC_DEV *Ehc, + IN UINT32 Timeout + ) +; + + + +/** + Initialize the HC hardware. + EHCI spec lists the five things to do to initialize the hardware + 1. Program CTRLDSSEGMENT + 2. Set USBINTR to enable interrupts + 3. Set periodic list base + 4. Set USBCMD, interrupt threshold, frame list size etc + 5. Write 1 to CONFIGFLAG to route all ports to EHCI + + @param Ehc The EHCI device + + @return EFI_SUCCESS : The EHCI has come out of halt state + @return EFI_TIMEOUT : Time out happened + +**/ +EFI_STATUS +EhcInitHC ( + IN USB2_HC_DEV *Ehc + ) +; + +#endif diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.c b/MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.c new file mode 100644 index 0000000000..b13e4078b0 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.c @@ -0,0 +1,941 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciSched.c + +Abstract: + + EHCI transfer scheduling routines + +Revision History + +**/ + +#include "Ehci.h" + + +/** + Create helper QTD/QH for the EHCI device + + @param Ehc The EHCI device + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for helper QTD/QH + @retval EFI_SUCCESS Helper QH/QTD are created + +**/ +STATIC +EFI_STATUS +EhcCreateHelpQ ( + IN USB2_HC_DEV *Ehc + ) +{ + USB_ENDPOINT Ep; + EHC_QH *Qh; + QH_HW *QhHw; + EHC_QTD *Qtd; + + // + // Create an inactive Qtd to terminate the short packet read. + // + Qtd = EhcCreateQtd (Ehc, NULL, 0, QTD_PID_INPUT, 0, 64); + + if (Qtd == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Qtd->QtdHw.Status = QTD_STAT_HALTED; + Ehc->ShortReadStop = Qtd; + + // + // Create a QH to act as the EHC reclamation header. + // Set the header to loopback to itself. + // + Ep.DevAddr = 0; + Ep.EpAddr = 1; + Ep.Direction = EfiUsbDataIn; + Ep.DevSpeed = EFI_USB_SPEED_HIGH; + Ep.MaxPacket = 64; + Ep.HubAddr = 0; + Ep.HubPort = 0; + Ep.Toggle = 0; + Ep.Type = EHC_BULK_TRANSFER; + Ep.PollRate = 1; + + Qh = EhcCreateQh (Ehc, &Ep); + + if (Qh == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + QhHw = &Qh->QhHw; + QhHw->HorizonLink = QH_LINK (QhHw, EHC_TYPE_QH, FALSE); + QhHw->Status = QTD_STAT_HALTED; + QhHw->ReclaimHead = 1; + Ehc->ReclaimHead = Qh; + + // + // Create a dummy QH to act as the terminator for periodical schedule + // + Ep.EpAddr = 2; + Ep.Type = EHC_INT_TRANSFER_SYNC; + + Qh = EhcCreateQh (Ehc, &Ep); + + if (Qh == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Qh->QhHw.Status = QTD_STAT_HALTED; + Ehc->PeriodOne = Qh; + + return EFI_SUCCESS; +} + + + +/** + Initialize the schedule data structure such as frame list + + @param Ehc The EHCI device to init schedule data for + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data + @retval EFI_SUCCESS The schedule data is initialized + +**/ +EFI_STATUS +EhcInitSched ( + IN USB2_HC_DEV *Ehc + ) +{ + EFI_PCI_IO_PROTOCOL *PciIo; + VOID *Buf; + EFI_PHYSICAL_ADDRESS PhyAddr; + VOID *Map; + UINTN Pages; + UINTN Bytes; + UINTN Index; + UINT32 *Desc; + EFI_STATUS Status; + + // + // First initialize the periodical schedule data: + // 1. Allocate and map the memory for the frame list + // 2. Create the help QTD/QH + // 3. Initialize the frame entries + // 4. Set the frame list register + // + PciIo = Ehc->PciIo; + + Bytes = 4096; + Pages = EFI_SIZE_TO_PAGES (Bytes); + + Status = PciIo->AllocateBuffer ( + PciIo, + AllocateAnyPages, + EfiBootServicesData, + Pages, + &Buf, + 0 + ); + + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + + Status = PciIo->Map ( + PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + Buf, + &Bytes, + &PhyAddr, + &Map + ); + + if (EFI_ERROR (Status) || (Bytes != 4096)) { + PciIo->FreeBuffer (PciIo, Pages, Buf); + return EFI_OUT_OF_RESOURCES; + } + + Ehc->PeriodFrameHost = Buf; + Ehc->PeriodFrame = (VOID *) ((UINTN) PhyAddr); + Ehc->PeriodFrameMap = Map; + Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr); + + // + // Init memory pool management then create the helper + // QTD/QH. If failed, previously allocated resources + // will be freed by EhcFreeSched + // + Ehc->MemPool = UsbHcInitMemPool ( + PciIo, + EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT), + Ehc->High32bitAddr + ); + + if (Ehc->MemPool == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EhcCreateHelpQ (Ehc); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Initialize the frame list entries then set the registers + // + Desc = (UINT32 *) Ehc->PeriodFrame; + + for (Index = 0; Index < EHC_FRAME_LEN; Index++) { + Desc[Index] = QH_LINK (Ehc->PeriodOne, EHC_TYPE_QH, FALSE); + } + + EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (Ehc->PeriodFrame)); + + // + // Second initialize the asynchronous schedule: + // Only need to set the AsynListAddr register to + // the reclamation header + // + EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (Ehc->ReclaimHead)); + return EFI_SUCCESS; +} + + + +/** + Free the schedule data. It may be partially initialized. + + @param Ehc The EHCI device + + @return None + +**/ +VOID +EhcFreeSched ( + IN USB2_HC_DEV *Ehc + ) +{ + EFI_PCI_IO_PROTOCOL *PciIo; + + EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0); + EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0); + + if (Ehc->PeriodOne != NULL) { + UsbHcFreeMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (EHC_QH)); + Ehc->PeriodOne = NULL; + } + + if (Ehc->ReclaimHead != NULL) { + UsbHcFreeMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (EHC_QH)); + Ehc->ReclaimHead = NULL; + } + + if (Ehc->ShortReadStop != NULL) { + UsbHcFreeMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (EHC_QTD)); + Ehc->ShortReadStop = NULL; + } + + if (Ehc->MemPool != NULL) { + UsbHcFreeMemPool (Ehc->MemPool); + Ehc->MemPool = NULL; + } + + if (Ehc->PeriodFrame != NULL) { + PciIo = Ehc->PciIo; + ASSERT (PciIo != NULL); + + PciIo->Unmap (PciIo, Ehc->PeriodFrameMap); + + PciIo->FreeBuffer ( + PciIo, + EFI_SIZE_TO_PAGES (EFI_PAGE_SIZE), + Ehc->PeriodFrameHost + ); + + Ehc->PeriodFrame = NULL; + } +} + + + +/** + Link the queue head to the asynchronous schedule list. + UEFI only supports one CTRL/BULK transfer at a time + due to its interfaces. This simplifies the AsynList + management: A reclamation header is always linked to + the AsyncListAddr, the only active QH is appended to it. + + @param Ehc The EHCI device + @param Qh The queue head to link + + @return None + +**/ +VOID +EhcLinkQhToAsync ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +{ + EHC_QH *Head; + + // + // Append the queue head after the reclaim header, then + // fix the hardware visiable parts (EHCI R1.0 page 72). + // ReclaimHead is always linked to the EHCI's AsynListAddr. + // + Head = Ehc->ReclaimHead; + + Qh->NextQh = Head->NextQh; + Head->NextQh = Qh; + + Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);; + Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE); +} + + +/** + Unlink a queue head from the asynchronous schedule list. + Need to synchronize with hardware + + @param Ehc The EHCI device + @param Qh The queue head to unlink + + @return None + +**/ +VOID +EhcUnlinkQhFromAsync ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +{ + EHC_QH *Head; + EFI_STATUS Status; + + ASSERT (Ehc->ReclaimHead->NextQh == Qh); + + // + // Remove the QH from reclamation head, then update the hardware + // visiable part: Only need to loopback the ReclaimHead. The Qh + // is pointing to ReclaimHead (which is staill in the list). + // + Head = Ehc->ReclaimHead; + + Head->NextQh = Qh->NextQh; + Qh->NextQh = NULL; + + Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE); + + // + // Set and wait the door bell to synchronize with the hardware + // + Status = EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIME); + + if (EFI_ERROR (Status)) { + EHC_ERROR (("EhcUnlinkQhFromAsync: Failed to synchronize with doorbell\n")); + } +} + + +/** + Link a queue head for interrupt transfer to the periodic + schedule frame list. This code is very much the same as + that in UHCI. + + @param Ehc The EHCI device + @param Qh The queue head to link + + @return None + +**/ +VOID +EhcLinkQhToPeriod ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +{ + UINT32 *Frames; + UINTN Index; + EHC_QH *Prev; + EHC_QH *Next; + + Frames = Ehc->PeriodFrame; + + for (Index = 0; Index < EHC_FRAME_LEN; Index += Qh->Interval) { + // + // First QH can't be NULL because we always keep PeriodOne + // heads on the frame list + // + ASSERT (!EHC_LINK_TERMINATED (Frames[Index])); + Next = EHC_ADDR (Ehc->High32bitAddr, Frames[Index]); + Prev = NULL; + + // + // Now, insert the queue head (Qh) into this frame: + // 1. Find a queue head with the same poll interval, just insert + // Qh after this queue head, then we are done. + // + // 2. Find the position to insert the queue head into: + // Previous head's interval is bigger than Qh's + // Next head's interval is less than Qh's + // Then, insert the Qh between then + // + while (Next->Interval > Qh->Interval) { + Prev = Next; + Next = Next->NextQh; + } + + ASSERT (Next != NULL); + + // + // The entry may have been linked into the frame by early insertation. + // For example: if insert a Qh with Qh.Interval == 4, and there is a Qh + // with Qh.Interval == 8 on the frame. If so, we are done with this frame. + // It isn't necessary to compare all the QH with the same interval to + // Qh. This is because if there is other QH with the same interval, Qh + // should has been inserted after that at Frames[0] and at Frames[0] it is + // impossible for (Next == Qh) + // + if (Next == Qh) { + continue; + } + + if (Next->Interval == Qh->Interval) { + // + // If there is a QH with the same interval, it locates at + // Frames[0], and we can simply insert it after this QH. We + // are all done. + // + ASSERT ((Index == 0) && (Qh->NextQh == NULL)); + + Prev = Next; + Next = Next->NextQh; + + Qh->NextQh = Next; + Prev->NextQh = Qh; + + Qh->QhHw.HorizonLink = Prev->QhHw.HorizonLink; + Prev->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE); + break; + } + + // + // OK, find the right position, insert it in. If Qh's next + // link has already been set, it is in position. This is + // guarranted by 2^n polling interval. + // + if (Qh->NextQh == NULL) { + Qh->NextQh = Next; + Qh->QhHw.HorizonLink = QH_LINK (Next, EHC_TYPE_QH, FALSE); + } + + if (Prev == NULL) { + Frames[Index] = QH_LINK (Qh, EHC_TYPE_QH, FALSE); + } else { + Prev->NextQh = Qh; + Prev->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE); + } + } +} + + +/** + Unlink an interrupt queue head from the periodic + schedule frame list + + @param Ehc The EHCI device + @param Qh The queue head to unlink + + @return None + +**/ +VOID +EhcUnlinkQhFromPeriod ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +{ + UINT32 *Frames; + UINTN Index; + EHC_QH *Prev; + EHC_QH *This; + + Frames = Ehc->PeriodFrame; + + for (Index = 0; Index < EHC_FRAME_LEN; Index += Qh->Interval) { + // + // Frame link can't be NULL because we always keep PeroidOne + // on the frame list + // + ASSERT (!EHC_LINK_TERMINATED (Frames[Index])); + This = EHC_ADDR (Ehc->High32bitAddr, Frames[Index]); + Prev = NULL; + + // + // Walk through the frame's QH list to find the + // queue head to remove + // + while ((This != NULL) && (This != Qh)) { + Prev = This; + This = This->NextQh; + } + + // + // Qh may have already been unlinked from this frame + // by early action. See the comments in EhcLinkQhToPeriod. + // + if (This == NULL) { + continue; + } + + if (Prev == NULL) { + // + // Qh is the first entry in the frame + // + Frames[Index] = Qh->QhHw.HorizonLink; + } else { + Prev->NextQh = Qh->NextQh; + Prev->QhHw.HorizonLink = Qh->QhHw.HorizonLink; + } + } +} + + + +/** + Check the URB's execution result and update the URB's + result accordingly. + + @param Ehc The EHCI device + @param Urb The URB to check result + + @return Whether the result of URB transfer is finialized. + +**/ +STATIC +BOOLEAN +EhcCheckUrbResult ( + IN USB2_HC_DEV *Ehc, + IN URB *Urb + ) +{ + LIST_ENTRY *Entry; + EHC_QTD *Qtd; + QTD_HW *QtdHw; + UINT8 State; + BOOLEAN Finished; + + ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL)); + + Finished = TRUE; + Urb->Completed = 0; + + Urb->Result = EFI_USB_NOERROR; + + if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { + Urb->Result |= EFI_USB_ERR_SYSTEM; + goto ON_EXIT; + } + + EFI_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList); + QtdHw = &Qtd->QtdHw; + State = (UINT8) QtdHw->Status; + + if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) { + // + // EHCI will halt the queue head when met some error. + // If it is halted, the result of URB is finialized. + // + if ((State & QTD_STAT_ERR_MASK) == 0) { + Urb->Result |= EFI_USB_ERR_STALL; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) { + Urb->Result |= EFI_USB_ERR_BABBLE; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) { + Urb->Result |= EFI_USB_ERR_BUFFER; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR) && (QtdHw->ErrCnt == 0)) { + Urb->Result |= EFI_USB_ERR_TIMEOUT; + } + + Finished = TRUE; + goto ON_EXIT; + + } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) { + // + // The QTD is still active, no need to check furthur. + // + Urb->Result |= EFI_USB_ERR_NOTEXECUTE; + + Finished = FALSE; + goto ON_EXIT; + + } else { + // + // This QTD is finished OK or met short packet read. Update the + // transfer length if it isn't a setup. + // + if (QtdHw->Pid != QTD_PID_SETUP) { + Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes; + } + + if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) { + EHC_DUMP_QH ((Urb->Qh, "Short packet read", FALSE)); + + // + // Short packet read condition. If it isn't a setup transfer, + // no need to check furthur: the queue head will halt at the + // ShortReadStop. If it is a setup transfer, need to check the + // Status Stage of the setup transfer to get the finial result + // + if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) { + EHC_DEBUG (("EhcCheckUrbResult: Short packet read, break\n")); + + Finished = TRUE; + goto ON_EXIT; + } + + EHC_DEBUG (("EhcCheckUrbResult: Short packet read, continue\n")); + } + } + } + +ON_EXIT: + // + // Return the data toggle set by EHCI hardware, bulk and interrupt + // transfer will use this to initialize the next transaction. For + // Control transfer, it always start a new data toggle sequence for + // new transfer. + // + // NOTICE: don't move DT update before the loop, otherwise there is + // a race condition that DT is wrong. + // + Urb->DataToggle = (UINT8) Urb->Qh->QhHw.DataToggle; + + return Finished; +} + + +/** + Execute the transfer by polling the URB. This is a synchronous operation. + + @param Ehc The EHCI device + @param Urb The URB to execute + @param TimeOut The time to wait before abort, in millisecond. + + @return EFI_DEVICE_ERROR : The transfer failed due to transfer error + @return EFI_TIMEOUT : The transfer failed due to time out + @return EFI_SUCCESS : The transfer finished OK + +**/ +EFI_STATUS +EhcExecTransfer ( + IN USB2_HC_DEV *Ehc, + IN URB *Urb, + IN UINTN TimeOut + ) +{ + EFI_STATUS Status; + UINTN Index; + UINTN Loop; + BOOLEAN Finished; + + Status = EFI_SUCCESS; + Loop = (TimeOut * EHC_STALL_1_MILLISECOND / EHC_SYNC_POLL_TIME) + 1; + Finished = FALSE; + + for (Index = 0; Index < Loop; Index++) { + Finished = EhcCheckUrbResult (Ehc, Urb); + + if (Finished) { + break; + } + + gBS->Stall (EHC_SYNC_POLL_TIME); + } + + if (!Finished) { + EHC_ERROR (("EhcExecTransfer: transfer not finished in %dms\n", TimeOut)); + EHC_DUMP_QH ((Urb->Qh, NULL, FALSE)); + + Status = EFI_TIMEOUT; + + } else if (Urb->Result != EFI_USB_NOERROR) { + EHC_ERROR (("EhcExecTransfer: transfer failed with %x\n", Urb->Result)); + EHC_DUMP_QH ((Urb->Qh, NULL, FALSE)); + + Status = EFI_DEVICE_ERROR; + } + + return Status; +} + + +/** + Delete a single asynchronous interrupt transfer for + the device and endpoint + + @param Ehc The EHCI device + @param DevAddr The address of the target device + @param EpNum The endpoint of the target + @param DataToggle Return the next data toggle to use + + @retval EFI_SUCCESS An asynchronous transfer is removed + @retval EFI_NOT_FOUND No transfer for the device is found + +**/ +EFI_STATUS +EhciDelAsyncIntTransfer ( + IN USB2_HC_DEV *Ehc, + IN UINT8 DevAddr, + IN UINT8 EpNum, + OUT UINT8 *DataToggle + ) +{ + LIST_ENTRY *Entry; + LIST_ENTRY *Next; + URB *Urb; + EFI_USB_DATA_DIRECTION Direction; + + Direction = ((EpNum & 0x80) ? EfiUsbDataIn : EfiUsbDataOut); + EpNum &= 0x0F; + + EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) { + Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList); + + if ((Urb->Ep.DevAddr == DevAddr) && (Urb->Ep.EpAddr == EpNum) && + (Urb->Ep.Direction == Direction)) { + // + // Check the URB status to retrieve the next data toggle + // from the associated queue head. + // + EhcCheckUrbResult (Ehc, Urb); + *DataToggle = Urb->DataToggle; + + EhcUnlinkQhFromPeriod (Ehc, Urb->Qh); + RemoveEntryList (&Urb->UrbList); + + gBS->FreePool (Urb->Data); + EhcFreeUrb (Ehc, Urb); + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + + +/** + Remove all the asynchronous interrutp transfers + + @param Ehc The EHCI device + + @return None + +**/ +VOID +EhciDelAllAsyncIntTransfers ( + IN USB2_HC_DEV *Ehc + ) +{ + LIST_ENTRY *Entry; + LIST_ENTRY *Next; + URB *Urb; + + EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) { + Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList); + + EhcUnlinkQhFromPeriod (Ehc, Urb->Qh); + RemoveEntryList (&Urb->UrbList); + + gBS->FreePool (Urb->Data); + EhcFreeUrb (Ehc, Urb); + } +} + + + +/** + Update the queue head for next round of asynchronous transfer + + @param Urb The URB to update + + @return None + +**/ +STATIC +VOID +EhcUpdateAsyncRequest ( + IN URB *Urb + ) +{ + LIST_ENTRY *Entry; + EHC_QTD *FirstQtd; + QH_HW *QhHw; + EHC_QTD *Qtd; + QTD_HW *QtdHw; + UINTN Index; + + Qtd = NULL; + + if (Urb->Result == EFI_USB_NOERROR) { + FirstQtd = NULL; + + EFI_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList); + + if (FirstQtd == NULL) { + FirstQtd = Qtd; + } + + // + // Update the QTD for next round of transfer. Host control + // may change dt/Total Bytes to Transfer/C_Page/Cerr/Status/ + // Current Offset. These fields need to be updated. DT isn't + // used by interrupt transfer. It uses DT in queue head. + // Current Offset is in Page[0], only need to reset Page[0] + // to initial data buffer. + // + QtdHw = &Qtd->QtdHw; + QtdHw->Status = QTD_STAT_ACTIVE; + QtdHw->ErrCnt = QTD_MAX_ERR; + QtdHw->CurPage = 0; + QtdHw->TotalBytes = (UINT32) Qtd->DataLen; + QtdHw->Page[0] = EHC_LOW_32BIT (Qtd->Data); + } + + // + // Update QH for next round of transfer. Host control only + // touch the fields in transfer overlay area. Only need to + // zero out the overlay area and set NextQtd to the first + // QTD. DateToggle bit is left untouched. + // + QhHw = &Urb->Qh->QhHw; + QhHw->CurQtd = QTD_LINK (0, TRUE); + QhHw->AltQtd = 0; + + QhHw->Status = 0; + QhHw->Pid = 0; + QhHw->ErrCnt = 0; + QhHw->CurPage = 0; + QhHw->IOC = 0; + QhHw->TotalBytes = 0; + + for (Index = 0; Index < 5; Index++) { + QhHw->Page[Index] = 0; + QhHw->PageHigh[Index] = 0; + } + + QhHw->NextQtd = QTD_LINK (FirstQtd, FALSE); + } + + return ; +} + + +/** + Interrupt transfer periodic check handler + + @param Event Interrupt event + @param Context Pointer to USB2_HC_DEV + + @return None + +**/ +VOID +EhcMoniteAsyncRequests ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + USB2_HC_DEV *Ehc; + EFI_TPL OldTpl; + LIST_ENTRY *Entry; + LIST_ENTRY *Next; + BOOLEAN Finished; + UINT8 *ProcBuf; + URB *Urb; + + OldTpl = gBS->RaiseTPL (EHC_TPL); + Ehc = (USB2_HC_DEV *) Context; + + EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) { + Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList); + + // + // Check the result of URB execution. If it is still + // active, check the next one. + // + Finished = EhcCheckUrbResult (Ehc, Urb); + + if (!Finished) { + continue; + } + + // + // Allocate a buffer then copy the transferred data for user. + // If failed to allocate the buffer, update the URB for next + // round of transfer. Ignore the data of this round. + // + ProcBuf = NULL; + + if (Urb->Result == EFI_USB_NOERROR) { + ASSERT (Urb->Completed <= Urb->DataLen); + + ProcBuf = AllocatePool (Urb->Completed); + + if (ProcBuf == NULL) { + EhcUpdateAsyncRequest (Urb); + continue; + } + + CopyMem (ProcBuf, Urb->Data, Urb->Completed); + } + + EhcUpdateAsyncRequest (Urb); + + // + // Leave error recovery to its related device driver. A + // common case of the error recovery is to re-submit the + // interrupt transfer which is linked to the head of the + // list. This function scans from head to tail. So the + // re-submitted interrupt transfer's callback function + // will not be called again in this round. Don't touch this + // URB after the callback, it may have been removed by the + // callback. + // + if (Urb->Callback != NULL) { + // + // Restore the old TPL, USB bus maybe connect device in + // his callback. Some drivers may has a lower TPL restriction. + // + gBS->RestoreTPL (OldTpl); + (Urb->Callback) (ProcBuf, Urb->Completed, Urb->Context, Urb->Result); + OldTpl = gBS->RaiseTPL (EHC_TPL); + } + + if (ProcBuf != NULL) { + gBS->FreePool (ProcBuf); + } + } + + gBS->RestoreTPL (OldTpl); +} diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.h b/MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.h new file mode 100644 index 0000000000..d8f2661059 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.h @@ -0,0 +1,220 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciSched.h + +Abstract: + + This file contains the definination for host controller schedule routines + +Revision History + +**/ + +#ifndef _EFI_EHCI_SCHED_H_ +#define _EFI_EHCI_SCHED_H_ + +EFI_STATUS +EhcInitSched ( + IN USB2_HC_DEV *Ehc + ) +/*++ + +Routine Description: + + Initialize the schedule data structure such as frame list + +Arguments: + + Ehc - The EHCI device to init schedule data for + +Returns: + + EFI_OUT_OF_RESOURCES - Failed to allocate resource to init schedule data + EFI_SUCCESS - The schedule data is initialized + +--*/ +; + + + +/** + Free the schedule data. It may be partially initialized. + + @param Ehc The EHCI device + + @return None + +**/ +VOID +EhcFreeSched ( + IN USB2_HC_DEV *Ehc + ) +; + + + +/** + Link the queue head to the asynchronous schedule list. + UEFI only supports one CTRL/BULK transfer at a time + due to its interfaces. This simplifies the AsynList + management: A reclamation header is always linked to + the AsyncListAddr, the only active QH is appended to it. + + @param Ehc The EHCI device + @param Qh The queue head to link + + @return None + +**/ +VOID +EhcLinkQhToAsync ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +; + + +/** + Unlink a queue head from the asynchronous schedule list. + Need to synchronize with hardware + + @param Ehc The EHCI device + @param Qh The queue head to unlink + + @return None + +**/ +VOID +EhcUnlinkQhFromAsync ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +; + + +/** + Link a queue head for interrupt transfer to the periodic + schedule frame list. This code is very much the same as + that in UHCI. + + @param Ehc The EHCI device + @param Qh The queue head to link + + @return None + +**/ +VOID +EhcLinkQhToPeriod ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +; + + +/** + Unlink an interrupt queue head from the periodic + schedule frame list + + @param Ehc The EHCI device + @param Qh The queue head to unlink + + @return None + +**/ +VOID +EhcUnlinkQhFromPeriod ( + IN USB2_HC_DEV *Ehc, + IN EHC_QH *Qh + ) +; + + + +/** + Execute the transfer by polling the URB. This is a synchronous operation. + + @param Ehc The EHCI device + @param Urb The URB to execute + @param TimeOut The time to wait before abort, in millisecond. + + @return EFI_DEVICE_ERROR : The transfer failed due to transfer error + @return EFI_TIMEOUT : The transfer failed due to time out + @return EFI_SUCCESS : The transfer finished OK + +**/ +EFI_STATUS +EhcExecTransfer ( + IN USB2_HC_DEV *Ehc, + IN URB *Urb, + IN UINTN TimeOut + ) +; + + +/** + Delete a single asynchronous interrupt transfer for + the device and endpoint + + @param Ehc The EHCI device + @param DevAddr The address of the target device + @param EpNum The endpoint of the target + @param DataToggle Return the next data toggle to use + + @retval EFI_SUCCESS An asynchronous transfer is removed + @retval EFI_NOT_FOUND No transfer for the device is found + +**/ +EFI_STATUS +EhciDelAsyncIntTransfer ( + IN USB2_HC_DEV *Ehc, + IN UINT8 DevAddr, + IN UINT8 EpNum, + OUT UINT8 *DataToggle + ) +; + + +/** + Remove all the asynchronous interrutp transfers + + @param Ehc The EHCI device + + @return None + +**/ +VOID +EhciDelAllAsyncIntTransfers ( + IN USB2_HC_DEV *Ehc + ) +; + + + +/** + Interrupt transfer periodic check handler + + @param Event Interrupt event + @param Context Pointer to USB2_HC_DEV + + @return None + +**/ +VOID +EhcMoniteAsyncRequests ( + IN EFI_EVENT Event, + IN VOID *Context + ) +; + +#endif diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciUrb.c b/MdeModulePkg/Bus/Pci/EhciDxe/EhciUrb.c new file mode 100644 index 0000000000..7b8b7b58f2 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciUrb.c @@ -0,0 +1,669 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciUrb.c + +Abstract: + + This file contains URB request, each request is warpped in a + URB (Usb Request Block) + +Revision History + +**/ + +#include "Ehci.h" + + +/** + Create a single QTD to hold the data + + @param Ehc The EHCI device + @param Data Current data not associated with a QTD + @param DataLen The length of the data + @param PktId Packet ID to use in the QTD + @param Toggle Data toggle to use in the QTD + @param MaxPacket Maximu packet length of the endpoint + + @return Created QTD or NULL if failed to create one + +**/ +EHC_QTD * +EhcCreateQtd ( + IN USB2_HC_DEV *Ehc, + IN UINT8 *Data, + IN UINTN DataLen, + IN UINT8 PktId, + IN UINT8 Toggle, + IN UINTN MaxPacket + ) +{ + EHC_QTD *Qtd; + QTD_HW *QtdHw; + UINTN Index; + UINTN Len; + UINTN ThisBufLen; + + ASSERT (Ehc != NULL); + + Qtd = UsbHcAllocateMem (Ehc->MemPool, sizeof (EHC_QTD)); + + if (Qtd == NULL) { + return NULL; + } + + Qtd->Signature = EHC_QTD_SIG; + Qtd->Data = Data; + Qtd->DataLen = 0; + + InitializeListHead (&Qtd->QtdList); + + QtdHw = &Qtd->QtdHw; + QtdHw->NextQtd = QTD_LINK (NULL, TRUE); + QtdHw->AltNext = QTD_LINK (NULL, TRUE); + QtdHw->Status = QTD_STAT_ACTIVE; + QtdHw->Pid = PktId; + QtdHw->ErrCnt = QTD_MAX_ERR; + QtdHw->IOC = 0; + QtdHw->TotalBytes = 0; + QtdHw->DataToggle = Toggle; + + // + // Fill in the buffer points + // + if (Data != NULL) { + Len = 0; + + for (Index = 0; Index <= QTD_MAX_BUFFER; Index++) { + // + // Set the buffer point (Check page 41 EHCI Spec 1.0). No need to + // compute the offset and clear Reserved fields. This is already + // done in the data point. + // + QtdHw->Page[Index] = EHC_LOW_32BIT (Data); + QtdHw->PageHigh[Index] = EHC_HIGH_32BIT (Data); + + ThisBufLen = QTD_BUF_LEN - (EHC_LOW_32BIT (Data) & QTD_BUF_MASK); + + if (Len + ThisBufLen >= DataLen) { + Len = DataLen; + break; + } + + Len += ThisBufLen; + Data += ThisBufLen; + } + + // + // Need to fix the last pointer if the Qtd can't hold all the + // user's data to make sure that the length is in the unit of + // max packets. If it can hold all the data, there is no such + // need. + // + if (Len < DataLen) { + Len = Len - Len % MaxPacket; + } + + QtdHw->TotalBytes = (UINT32) Len; + Qtd->DataLen = Len; + } + + return Qtd; +} + + + +/** + Initialize the queue head for interrupt transfer, + that is, initialize the following three fields: + 1. SplitXState in the Status field + 2. Microframe S-mask + 3. Microframe C-mask + + @param Ep The queue head's related endpoint + @param QhHw The queue head to initialize + + @return None + +**/ +STATIC +VOID +EhcInitIntQh ( + IN USB_ENDPOINT *Ep, + IN QH_HW *QhHw + ) +{ + // + // Because UEFI interface can't utilitize an endpoint with + // poll rate faster than 1ms, only need to set one bit in + // the queue head. simple. But it may be changed later. If + // sub-1ms interrupt is supported, need to update the S-Mask + // here + // + if (Ep->DevSpeed == EFI_USB_SPEED_HIGH) { + QhHw->SMask = QH_MICROFRAME_0; + return ; + } + + // + // For low/full speed device, the transfer must go through + // the split transaction. Need to update three fields + // 1. SplitXState in the status + // 2. Microframe S-Mask + // 3. Microframe C-Mask + // UEFI USB doesn't exercise admission control. It simplely + // schedule the high speed transactions in microframe 0, and + // full/low speed transactions at microframe 1. This also + // avoid the use of FSTN. + // + QhHw->SMask = QH_MICROFRAME_1; + QhHw->CMask = QH_MICROFRAME_3 | QH_MICROFRAME_4 | QH_MICROFRAME_5; +} + + + +/** + Allocate and initialize a EHCI queue head + + @param Ehci The EHCI device + @param Ep The endpoint to create queue head for + + @return Created queue head or NULL if failed to create one + +**/ +EHC_QH * +EhcCreateQh ( + IN USB2_HC_DEV *Ehci, + IN USB_ENDPOINT *Ep + ) +{ + EHC_QH *Qh; + QH_HW *QhHw; + + Qh = UsbHcAllocateMem (Ehci->MemPool, sizeof (EHC_QH)); + + if (Qh == NULL) { + return NULL; + } + + Qh->Signature = EHC_QH_SIG; + Qh->NextQh = NULL; + Qh->Interval = Ep->PollRate; + + InitializeListHead (&Qh->Qtds); + + QhHw = &Qh->QhHw; + QhHw->HorizonLink = QH_LINK (NULL, 0, TRUE); + QhHw->DeviceAddr = Ep->DevAddr; + QhHw->Inactive = 0; + QhHw->EpNum = Ep->EpAddr; + QhHw->EpSpeed = Ep->DevSpeed; + QhHw->DtCtrl = 0; + QhHw->ReclaimHead = 0; + QhHw->MaxPacketLen = (UINT32) Ep->MaxPacket; + QhHw->CtrlEp = 0; + QhHw->NakReload = QH_NAK_RELOAD; + QhHw->HubAddr = Ep->HubAddr; + QhHw->PortNum = Ep->HubPort; + QhHw->Multiplier = 1; + QhHw->DataToggle = Ep->Toggle; + + if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) { + QhHw->Status |= QTD_STAT_DO_SS; + } + + switch (Ep->Type) { + case EHC_CTRL_TRANSFER: + // + // Special initialization for the control transfer: + // 1. Control transfer initialize data toggle from each QTD + // 2. Set the Control Endpoint Flag (C) for low/full speed endpoint. + // + QhHw->DtCtrl = 1; + + if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) { + QhHw->CtrlEp = 1; + } + break; + + case EHC_INT_TRANSFER_ASYNC: + case EHC_INT_TRANSFER_SYNC: + // + // Special initialization for the interrupt transfer + // to set the S-Mask and C-Mask + // + QhHw->NakReload = 0; + EhcInitIntQh (Ep, QhHw); + break; + + case EHC_BULK_TRANSFER: + if ((Ep->DevSpeed == EFI_USB_SPEED_HIGH) && (Ep->Direction == EfiUsbDataOut)) { + QhHw->Status |= QTD_STAT_DO_PING; + } + + break; + } + + return Qh; +} + + + +/** + Convert the poll interval from application to that + be used by EHCI interface data structure. Only need + to get the max 2^n that is less than interval. UEFI + can't support high speed endpoint with a interval less + than 8 microframe because interval is specified in + the unit of ms (millisecond) + + @param Interval The interval to convert + + @return The converted interval + +**/ +STATIC +UINTN +EhcConvertPollRate ( + IN UINTN Interval + ) +{ + UINTN BitCount; + + if (Interval == 0) { + return 1; + } + + // + // Find the index (1 based) of the highest non-zero bit + // + BitCount = 0; + + while (Interval != 0) { + Interval >>= 1; + BitCount++; + } + + return 1 << (BitCount - 1); +} + + + +/** + Free a list of QTDs + + @param Ehc The EHCI device + @param Qtds The list head of the QTD + + @return None + +**/ +STATIC +VOID +EhcFreeQtds ( + IN USB2_HC_DEV *Ehc, + IN LIST_ENTRY *Qtds + ) +{ + LIST_ENTRY *Entry; + LIST_ENTRY *Next; + EHC_QTD *Qtd; + + EFI_LIST_FOR_EACH_SAFE (Entry, Next, Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList); + + RemoveEntryList (&Qtd->QtdList); + UsbHcFreeMem (Ehc->MemPool, Qtd, sizeof (EHC_QTD)); + } +} + + +/** + Free an allocated URB. It is possible for it to be partially inited. + + @param Ehc The EHCI device + @param Urb The URB to free + + @return None + +**/ +VOID +EhcFreeUrb ( + IN USB2_HC_DEV *Ehc, + IN URB *Urb + ) +{ + EFI_PCI_IO_PROTOCOL *PciIo; + + PciIo = Ehc->PciIo; + + if (Urb->RequestPhy != NULL) { + PciIo->Unmap (PciIo, Urb->RequestMap); + } + + if (Urb->DataMap != NULL) { + PciIo->Unmap (PciIo, Urb->DataMap); + } + + if (Urb->Qh != NULL) { + // + // Ensure that this queue head has been unlinked from the + // schedule data structures. Free all the associated QTDs + // + EhcFreeQtds (Ehc, &Urb->Qh->Qtds); + UsbHcFreeMem (Ehc->MemPool, Urb->Qh, sizeof (EHC_QH)); + } + + gBS->FreePool (Urb); +} + + + +/** + Create a list of QTDs for the URB + + @param Ehc The EHCI device + @param Urb The URB to create QTDs for + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for QTD + @retval EFI_SUCCESS The QTDs are allocated for the URB + +**/ +STATIC +EFI_STATUS +EhcCreateQtds ( + IN USB2_HC_DEV *Ehc, + IN URB *Urb + ) +{ + USB_ENDPOINT *Ep; + EHC_QH *Qh; + EHC_QTD *Qtd; + EHC_QTD *StatusQtd; + EHC_QTD *NextQtd; + LIST_ENTRY *Entry; + UINT32 AlterNext; + UINT8 Toggle; + UINTN Len; + UINT8 Pid; + + ASSERT ((Urb != NULL) && (Urb->Qh != NULL)); + + // + // EHCI follows the alternet next QTD pointer if it meets + // a short read and the AlterNext pointer is valid. UEFI + // EHCI driver should terminate the transfer except the + // control transfer. + // + Toggle = 0; + Qh = Urb->Qh; + Ep = &Urb->Ep; + StatusQtd = NULL; + AlterNext = QTD_LINK (NULL, TRUE); + + if (Ep->Direction == EfiUsbDataIn) { + AlterNext = QTD_LINK (Ehc->ShortReadStop, FALSE); + } + + // + // Build the Setup and status packets for control transfer + // + if (Urb->Ep.Type == EHC_CTRL_TRANSFER) { + Len = sizeof (EFI_USB_DEVICE_REQUEST); + Qtd = EhcCreateQtd (Ehc, Urb->RequestPhy, Len, QTD_PID_SETUP, 0, Ep->MaxPacket); + + if (Qtd == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InsertTailList (&Qh->Qtds, &Qtd->QtdList); + + // + // Create the status packet now. Set the AlterNext to it. So, when + // EHCI meets a short control read, it can resume at the status stage. + // Use the opposite direction of the data stage, or IN if there is + // no data stage. + // + if (Ep->Direction == EfiUsbDataIn) { + Pid = QTD_PID_OUTPUT; + } else { + Pid = QTD_PID_INPUT; + } + + StatusQtd = EhcCreateQtd (Ehc, NULL, 0, Pid, 1, Ep->MaxPacket); + + if (StatusQtd == NULL) { + goto ON_ERROR; + } + + if (Ep->Direction == EfiUsbDataIn) { + AlterNext = QTD_LINK (StatusQtd, FALSE); + } + + Toggle = 1; + } + + // + // Build the data packets for all the transfers + // + if (Ep->Direction == EfiUsbDataIn) { + Pid = QTD_PID_INPUT; + } else { + Pid = QTD_PID_OUTPUT; + } + + Qtd = NULL; + Len = 0; + + while (Len < Urb->DataLen) { + Qtd = EhcCreateQtd ( + Ehc, + (UINT8 *) Urb->DataPhy + Len, + Urb->DataLen - Len, + Pid, + Toggle, + Ep->MaxPacket + ); + + if (Qtd == NULL) { + goto ON_ERROR; + } + + Qtd->QtdHw.AltNext = AlterNext; + InsertTailList (&Qh->Qtds, &Qtd->QtdList); + + // + // Switch the Toggle bit if odd number of packets are included in the QTD. + // + if (((Qtd->DataLen + Ep->MaxPacket - 1) / Ep->MaxPacket) % 2) { + Toggle = 1 - Toggle; + } + + Len += Qtd->DataLen; + } + + // + // Insert the status packet for control transfer + // + if (Ep->Type == EHC_CTRL_TRANSFER) { + InsertTailList (&Qh->Qtds, &StatusQtd->QtdList); + } + + // + // OK, all the QTDs needed are created. Now, fix the NextQtd point + // + EFI_LIST_FOR_EACH (Entry, &Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList); + + // + // break if it is the last entry on the list + // + if (Entry->ForwardLink == &Qh->Qtds) { + break; + } + + NextQtd = EFI_LIST_CONTAINER (Entry->ForwardLink, EHC_QTD, QtdList); + Qtd->QtdHw.NextQtd = QTD_LINK (NextQtd, FALSE); + } + + // + // Link the QTDs to the queue head + // + NextQtd = EFI_LIST_CONTAINER (Qh->Qtds.ForwardLink, EHC_QTD, QtdList); + Qh->QhHw.NextQtd = QTD_LINK (NextQtd, FALSE); + return EFI_SUCCESS; + +ON_ERROR: + EhcFreeQtds (Ehc, &Qh->Qtds); + return EFI_OUT_OF_RESOURCES; +} + + +/** + Create a new URB and its associated QTD + + @param Ehc The EHCI device + @param DevAddr The device address + @param EpAddr Endpoint addrress & its direction + @param DevSpeed The device speed + @param Toggle Initial data toggle to use + @param MaxPacket The max packet length of the endpoint + @param Hub The transaction translator to use + @param Type The transaction type + @param Request The standard USB request for control transfer + @param Data The user data to transfer + @param DataLen The length of data buffer + @param Callback The function to call when data is transferred + @param Context The context to the callback + @param Interval The interval for interrupt transfer + + @return Created URB or NULL + +**/ +URB * +EhcCreateUrb ( + IN USB2_HC_DEV *Ehc, + IN UINT8 DevAddr, + IN UINT8 EpAddr, + IN UINT8 DevSpeed, + IN UINT8 Toggle, + IN UINTN MaxPacket, + IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Hub, + IN UINTN Type, + IN EFI_USB_DEVICE_REQUEST *Request, + IN VOID *Data, + IN UINTN DataLen, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, + IN VOID *Context, + IN UINTN Interval + ) +{ + USB_ENDPOINT *Ep; + EFI_PHYSICAL_ADDRESS PhyAddr; + EFI_PCI_IO_PROTOCOL_OPERATION MapOp; + EFI_PCI_IO_PROTOCOL *PciIo; + EFI_STATUS Status; + UINTN Len; + URB *Urb; + VOID *Map; + + Urb = AllocateZeroPool (sizeof (URB)); + + if (Urb == NULL) { + return NULL; + } + + Urb->Signature = EHC_URB_SIG; + InitializeListHead (&Urb->UrbList); + + Ep = &Urb->Ep; + Ep->DevAddr = DevAddr; + Ep->EpAddr = EpAddr & 0x0F; + Ep->Direction = ((EpAddr & 0x80) ? EfiUsbDataIn : EfiUsbDataOut); + Ep->DevSpeed = DevSpeed; + Ep->MaxPacket = MaxPacket; + + Ep->HubAddr = 0; + Ep->HubPort = 0; + + if (DevSpeed != EFI_USB_SPEED_HIGH) { + ASSERT (Hub != NULL); + + Ep->HubAddr = Hub->TranslatorHubAddress; + Ep->HubPort = Hub->TranslatorPortNumber; + } + + Ep->Toggle = Toggle; + Ep->Type = Type; + Ep->PollRate = EhcConvertPollRate (Interval); + + Urb->Request = Request; + Urb->Data = Data; + Urb->DataLen = DataLen; + Urb->Callback = Callback; + Urb->Context = Context; + + PciIo = Ehc->PciIo; + Urb->Qh = EhcCreateQh (Ehc, &Urb->Ep); + + if (Urb->Qh == NULL) { + goto ON_ERROR; + } + + // + // Map the request and user data + // + if (Request != NULL) { + Len = sizeof (EFI_USB_DEVICE_REQUEST); + MapOp = EfiPciIoOperationBusMasterRead; + Status = PciIo->Map (PciIo, MapOp, Request, &Len, &PhyAddr, &Map); + + if (EFI_ERROR (Status) || (Len != sizeof (EFI_USB_DEVICE_REQUEST))) { + goto ON_ERROR; + } + + Urb->RequestPhy = (VOID *) ((UINTN) PhyAddr); + Urb->RequestMap = Map; + } + + if (Data != NULL) { + Len = DataLen; + + if (Ep->Direction == EfiUsbDataIn) { + MapOp = EfiPciIoOperationBusMasterWrite; + } else { + MapOp = EfiPciIoOperationBusMasterRead; + } + + Status = PciIo->Map (PciIo, MapOp, Data, &Len, &PhyAddr, &Map); + + if (EFI_ERROR (Status) || (Len != DataLen)) { + goto ON_ERROR; + } + + Urb->DataPhy = (VOID *) ((UINTN) PhyAddr); + Urb->DataMap = Map; + } + + Status = EhcCreateQtds (Ehc, Urb); + + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + return Urb; + +ON_ERROR: + EhcFreeUrb (Ehc, Urb); + return NULL; +} diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/EhciUrb.h b/MdeModulePkg/Bus/Pci/EhciDxe/EhciUrb.h new file mode 100644 index 0000000000..a2a5826de8 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/EhciUrb.h @@ -0,0 +1,350 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciUrb.h + +Abstract: + + This file contains URB request, each request is warpped in a + URB (Usb Request Block) + +Revision History + +**/ + +#ifndef _EFI_EHCI_URB_H_ +#define _EFI_EHCI_URB_H_ + + +typedef struct _EHC_QTD EHC_QTD; +typedef struct _EHC_QH EHC_QH; +typedef struct _URB URB; + +enum { + // + // Transfer types, used in URB to identify the transfer type + // + EHC_CTRL_TRANSFER = 0x01, + EHC_BULK_TRANSFER = 0x02, + EHC_INT_TRANSFER_SYNC = 0x04, + EHC_INT_TRANSFER_ASYNC = 0x08, + + EHC_QTD_SIG = EFI_SIGNATURE_32 ('U', 'S', 'B', 'T'), + EHC_QH_SIG = EFI_SIGNATURE_32 ('U', 'S', 'B', 'H'), + EHC_URB_SIG = EFI_SIGNATURE_32 ('U', 'S', 'B', 'R'), + + // + // Hardware related bit definitions + // + EHC_TYPE_ITD = 0x00, + EHC_TYPE_QH = 0x02, + EHC_TYPE_SITD = 0x04, + EHC_TYPE_FSTN = 0x06, + + QH_NAK_RELOAD = 3, + QH_HSHBW_MULTI = 1, + + QTD_MAX_ERR = 3, + QTD_PID_OUTPUT = 0x00, + QTD_PID_INPUT = 0x01, + QTD_PID_SETUP = 0x02, + + QTD_STAT_DO_OUT = 0, + QTD_STAT_DO_SS = 0, + QTD_STAT_DO_PING = 0x01, + QTD_STAT_DO_CS = 0x02, + QTD_STAT_TRANS_ERR = 0x08, + QTD_STAT_BABBLE_ERR = 0x10, + QTD_STAT_BUFF_ERR = 0x20, + QTD_STAT_HALTED = 0x40, + QTD_STAT_ACTIVE = 0x80, + QTD_STAT_ERR_MASK = QTD_STAT_TRANS_ERR | QTD_STAT_BABBLE_ERR | QTD_STAT_BUFF_ERR, + + QTD_MAX_BUFFER = 4, + QTD_BUF_LEN = 4096, + QTD_BUF_MASK = 0x0FFF, + + QH_MICROFRAME_0 = 0x01, + QH_MICROFRAME_1 = 0x02, + QH_MICROFRAME_2 = 0x04, + QH_MICROFRAME_3 = 0x08, + QH_MICROFRAME_4 = 0x10, + QH_MICROFRAME_5 = 0x20, + QH_MICROFRAME_6 = 0x40, + QH_MICROFRAME_7 = 0x80, + + USB_ERR_SHORT_PACKET = 0x200, +}; + +// +// Fill in the hardware link point: pass in a EHC_QH/QH_HW +// pointer to QH_LINK; A EHC_QTD/QTD_HW pointer to QTD_LINK +// +#define QH_LINK(Addr, Type, Term) \ + ((UINT32) ((EHC_LOW_32BIT (Addr) & 0xFFFFFFE0) | (Type) | ((Term) ? 1 : 0))) + +#define QTD_LINK(Addr, Term) QH_LINK((Addr), 0, (Term)) + +// +// The defination of EHCI hardware used data structure for +// little endian architecture. The QTD and QH structures +// are required to be 32 bytes aligned. Don't add members +// to the head of the associated software strucuture. +// +#pragma pack(1) +typedef struct { + UINT32 NextQtd; + UINT32 AltNext; + + UINT32 Status : 8; + UINT32 Pid : 2; + UINT32 ErrCnt : 2; + UINT32 CurPage : 3; + UINT32 IOC : 1; + UINT32 TotalBytes : 15; + UINT32 DataToggle : 1; + + UINT32 Page[5]; + UINT32 PageHigh[5]; +} QTD_HW; + +typedef struct { + UINT32 HorizonLink; + // + // Endpoint capabilities/Characteristics DWord 1 and DWord 2 + // + UINT32 DeviceAddr : 7; + UINT32 Inactive : 1; + UINT32 EpNum : 4; + UINT32 EpSpeed : 2; + UINT32 DtCtrl : 1; + UINT32 ReclaimHead : 1; + UINT32 MaxPacketLen : 11; + UINT32 CtrlEp : 1; + UINT32 NakReload : 4; + + UINT32 SMask : 8; + UINT32 CMask : 8; + UINT32 HubAddr : 7; + UINT32 PortNum : 7; + UINT32 Multiplier : 2; + + // + // Transaction execution overlay area + // + UINT32 CurQtd; + UINT32 NextQtd; + UINT32 AltQtd; + + UINT32 Status : 8; + UINT32 Pid : 2; + UINT32 ErrCnt : 2; + UINT32 CurPage : 3; + UINT32 IOC : 1; + UINT32 TotalBytes : 15; + UINT32 DataToggle : 1; + + UINT32 Page[5]; + UINT32 PageHigh[5]; +} QH_HW; +#pragma pack() + + +// +// Endpoint address and its capabilities +// +typedef struct _USB_ENDPOINT { + UINT8 DevAddr; + UINT8 EpAddr; // Endpoint address, no direction encoded in + EFI_USB_DATA_DIRECTION Direction; + UINT8 DevSpeed; + UINTN MaxPacket; + UINT8 HubAddr; + UINT8 HubPort; + UINT8 Toggle; // Data toggle, not used for control transfer + UINTN Type; + UINTN PollRate; // Polling interval used by EHCI +} USB_ENDPOINT; + +// +// Software QTD strcture, this is used to manage all the +// QTD generated from a URB. Don't add fields before QtdHw. +// +typedef struct _EHC_QTD { + QTD_HW QtdHw; + UINT32 Signature; + LIST_ENTRY QtdList; // The list of QTDs to one end point + UINT8 *Data; // Buffer of the original data + UINTN DataLen; // Original amount of data in this QTD +} EHC_QTD; + +// +// Software QH structure. All three different transaction types +// supported by UEFI USB, that is the control/bulk/interrupt +// transfers use the queue head and queue token strcuture. +// +// Interrupt QHs are linked to periodic frame list in the reversed +// 2^N tree. Each interrupt QH is linked to the list starting at +// frame 0. There is a dummy interrupt QH linked to each frame as +// a sentinental whose polling interval is 1. Synchronous interrupt +// transfer is linked after this dummy QH. +// +// For control/bulk transfer, only synchronous (in the sense of UEFI) +// transfer is supported. A dummy QH is linked to EHCI AsyncListAddr +// as the reclamation header. New transfer is inserted after this QH. +// +typedef struct _EHC_QH { + QH_HW QhHw; + UINT32 Signature; + EHC_QH *NextQh; // The queue head pointed to by horizontal link + LIST_ENTRY Qtds; // The list of QTDs to this queue head + UINTN Interval; +} EHC_QH; + +// +// URB (Usb Request Block) contains information for all kinds of +// usb requests. +// +typedef struct _URB { + UINT32 Signature; + LIST_ENTRY UrbList; + + // + // Transaction information + // + USB_ENDPOINT Ep; + EFI_USB_DEVICE_REQUEST *Request; // Control transfer only + VOID *RequestPhy; // Address of the mapped request + VOID *RequestMap; + VOID *Data; + UINTN DataLen; + VOID *DataPhy; // Address of the mapped user data + VOID *DataMap; + EFI_ASYNC_USB_TRANSFER_CALLBACK Callback; + VOID *Context; + + // + // Schedule data + // + EHC_QH *Qh; + + // + // Transaction result + // + UINT32 Result; + UINTN Completed; // completed data length + UINT8 DataToggle; +} URB; + + + +/** + Create a single QTD to hold the data + + @param Ehc The EHCI device + @param Data Current data not associated with a QTD + @param DataLen The length of the data + @param PktId Packet ID to use in the QTD + @param Toggle Data toggle to use in the QTD + @param MaxPacket Maximu packet length of the endpoint + + @return Created QTD or NULL if failed to create one + +**/ +EHC_QTD * +EhcCreateQtd ( + IN USB2_HC_DEV *Ehc, + IN UINT8 *Data, + IN UINTN DataLen, + IN UINT8 PktId, + IN UINT8 Toggle, + IN UINTN MaxPacket + ) +; + + + +/** + Allocate and initialize a EHCI queue head + + @param Ehci The EHCI device + @param Ep The endpoint to create queue head for + + @return Created queue head or NULL if failed to create one + +**/ +EHC_QH * +EhcCreateQh ( + IN USB2_HC_DEV *Ehci, + IN USB_ENDPOINT *Ep + ) +; + + +/** + Free an allocated URB. It is possible for it to be partially inited. + + @param Ehc The EHCI device + @param Urb The URB to free + + @return None + +**/ +VOID +EhcFreeUrb ( + IN USB2_HC_DEV *Ehc, + IN URB *Urb + ) +; + + +/** + Create a new URB and its associated QTD + + @param Ehc The EHCI device + @param DevAddr The device address + @param EpAddr Endpoint addrress & its direction + @param DevSpeed The device speed + @param Toggle Initial data toggle to use + @param MaxPacket The max packet length of the endpoint + @param Hub The transaction translator to use + @param Type The transaction type + @param Request The standard USB request for control transfer + @param Data The user data to transfer + @param DataLen The length of data buffer + @param Callback The function to call when data is transferred + @param Context The context to the callback + @param Interval The interval for interrupt transfer + + @return Created URB or NULL + +**/ +URB * +EhcCreateUrb ( + IN USB2_HC_DEV *Ehc, + IN UINT8 DevAddr, + IN UINT8 EpAddr, + IN UINT8 DevSpeed, + IN UINT8 Toggle, + IN UINTN MaxPacket, + IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Hub, + IN UINTN Type, + IN EFI_USB_DEVICE_REQUEST *Request, + IN VOID *Data, + IN UINTN DataLen, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, + IN VOID *Context, + IN UINTN Interval + ) +; +#endif diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/UsbHcMem.c b/MdeModulePkg/Bus/Pci/EhciDxe/UsbHcMem.c new file mode 100644 index 0000000000..b7e7d66025 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/UsbHcMem.c @@ -0,0 +1,549 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciMem.c + +Abstract: + + +Revision History + +**/ + + +#include "Ehci.h" + + +UINTN mUsbHcDebugLevel = DEBUG_INFO; + + +/** + Allocate a block of memory to be used by the buffer pool + + @param Pool The buffer pool to allocate memory for + @param Pages How many pages to allocate + + @return The allocated memory block or NULL if failed + +**/ +STATIC +USBHC_MEM_BLOCK * +UsbHcAllocMemBlock ( + IN USBHC_MEM_POOL *Pool, + IN UINTN Pages + ) +{ + USBHC_MEM_BLOCK *Block; + EFI_PCI_IO_PROTOCOL *PciIo; + VOID *BufHost; + VOID *Mapping; + EFI_PHYSICAL_ADDRESS MappedAddr; + UINTN Bytes; + EFI_STATUS Status; + + PciIo = Pool->PciIo; + + Block = AllocateZeroPool (sizeof (USBHC_MEM_BLOCK)); + if (Block == NULL) { + return NULL; + } + + // + // each bit in the bit array represents USBHC_MEM_UNIT + // bytes of memory in the memory block. + // + ASSERT (USBHC_MEM_UNIT * 8 <= EFI_PAGE_SIZE); + + Block->BufLen = EFI_PAGES_TO_SIZE (Pages); + Block->BitsLen = Block->BufLen / (USBHC_MEM_UNIT * 8); + Block->Bits = AllocateZeroPool (Block->BitsLen); + + if (Block->Bits == NULL) { + gBS->FreePool (Block); + return NULL; + } + + // + // Allocate the number of Pages of memory, then map it for + // bus master read and write. + // + Status = PciIo->AllocateBuffer ( + PciIo, + AllocateAnyPages, + EfiBootServicesData, + Pages, + &BufHost, + 0 + ); + + if (EFI_ERROR (Status)) { + goto FREE_BITARRAY; + } + + Bytes = EFI_PAGES_TO_SIZE (Pages); + Status = PciIo->Map ( + PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + BufHost, + &Bytes, + &MappedAddr, + &Mapping + ); + + if (EFI_ERROR (Status) || (Bytes != EFI_PAGES_TO_SIZE (Pages))) { + goto FREE_BUFFER; + } + + // + // Check whether the data structure used by the host controller + // should be restricted into the same 4G + // + if (Pool->Check4G && (Pool->Which4G != USB_HC_HIGH_32BIT (MappedAddr))) { + PciIo->Unmap (PciIo, Mapping); + goto FREE_BUFFER; + } + + Block->BufHost = BufHost; + Block->Buf = (UINT8 *) ((UINTN) MappedAddr); + Block->Mapping = Mapping; + + DEBUG ((mUsbHcDebugLevel, "UsbHcAllocMemBlock: block %x created with buffer %x\n", + Block, Block->Buf)); + + return Block; + +FREE_BUFFER: + PciIo->FreeBuffer (PciIo, Pages, BufHost); + +FREE_BITARRAY: + gBS->FreePool (Block->Bits); + gBS->FreePool (Block); + return NULL; +} + + +/** + Free the memory block from the memory pool + + @param Pool The memory pool to free the block from + @param Block The memory block to free + + @return VOID + +**/ +STATIC +VOID +UsbHcFreeMemBlock ( + IN USBHC_MEM_POOL *Pool, + IN USBHC_MEM_BLOCK *Block + ) +{ + EFI_PCI_IO_PROTOCOL *PciIo; + + ASSERT ((Pool != NULL) && (Block != NULL)); + + PciIo = Pool->PciIo; + + // + // Unmap the common buffer then free the structures + // + PciIo->Unmap (PciIo, Block->Mapping); + PciIo->FreeBuffer (PciIo, EFI_SIZE_TO_PAGES (Block->BufLen), Block->BufHost); + + gBS->FreePool (Block->Bits); + gBS->FreePool (Block); +} + + +/** + Alloc some memory from the block + + @param Block The memory block to allocate memory from + @param Mem The variable to store the memory allocated + @param Units Number of memory units to allocate + + @return EFI_SUCCESS : The needed memory is allocated + @return EFI_NOT_FOUND : Can't find the free memory + +**/ +STATIC +VOID * +UsbHcAllocMemFromBlock ( + IN USBHC_MEM_BLOCK *Block, + IN UINTN Units + ) +{ + UINTN Byte; + UINT8 Bit; + UINTN StartByte; + UINT8 StartBit; + UINTN Available; + UINTN Count; + + ASSERT ((Block != 0) && (Units != 0)); + + StartByte = 0; + StartBit = 0; + Available = 0; + + for (Byte = 0, Bit = 0; Byte < Block->BitsLen;) { + // + // If current bit is zero, the corresponding memory unit is + // available, otherwise we need to restart our searching. + // Available counts the consective number of zero bit. + // + if (!USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)) { + Available++; + + if (Available >= Units) { + break; + } + + NEXT_BIT (Byte, Bit); + + } else { + NEXT_BIT (Byte, Bit); + + Available = 0; + StartByte = Byte; + StartBit = Bit; + } + } + + if (Available < Units) { + return NULL; + } + + // + // Mark the memory as allocated + // + Byte = StartByte; + Bit = StartBit; + + for (Count = 0; Count < Units; Count++) { + ASSERT (!USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)); + + Block->Bits[Byte] |= USB_HC_BIT (Bit); + NEXT_BIT (Byte, Bit); + } + + return Block->Buf + (StartByte * 8 + StartBit) * USBHC_MEM_UNIT; +} + + +/** + Insert the memory block to the pool's list of the blocks + + @param Head The head of the memory pool's block list + @param Block The memory block to insert + + @return VOID + +**/ +STATIC +VOID +UsbHcInsertMemBlockToPool ( + IN USBHC_MEM_BLOCK *Head, + IN USBHC_MEM_BLOCK *Block + ) +{ + ASSERT ((Head != NULL) && (Block != NULL)); + Block->Next = Head->Next; + Head->Next = Block; +} + + +/** + Is the memory block empty? + + @param Block The memory block to check + + @return TRUE : The memory block is empty + @return FALSE : The memory block isn't empty + +**/ +STATIC +BOOLEAN +UsbHcIsMemBlockEmpty ( + IN USBHC_MEM_BLOCK *Block + ) +{ + UINTN Index; + + for (Index = 0; Index < Block->BitsLen; Index++) { + if (Block->Bits[Index] != 0) { + return FALSE; + } + } + + return TRUE; +} + + +/** + Unlink the memory block from the pool's list + + @param Head The block list head of the memory's pool + @param BlockToUnlink The memory block to unlink. + + @return VOID + +**/ +STATIC +VOID +UsbHcUnlinkMemBlock ( + IN USBHC_MEM_BLOCK *Head, + IN USBHC_MEM_BLOCK *BlockToUnlink + ) +{ + USBHC_MEM_BLOCK *Block; + + ASSERT ((Head != NULL) && (BlockToUnlink != NULL)); + + for (Block = Head; Block != NULL; Block = Block->Next) { + if (Block->Next == BlockToUnlink) { + Block->Next = BlockToUnlink->Next; + BlockToUnlink->Next = NULL; + break; + } + } +} + + +/** + Initialize the memory management pool for the host controller + + @param Pool The USB memory pool to initialize + @param PciIo The PciIo that can be used to access the host controller + @param Check4G Whether the host controller requires allocated memory + from one 4G address space. + @param Which4G The 4G memory area each memory allocated should be from + + @return EFI_SUCCESS : The memory pool is initialized + @return EFI_OUT_OF_RESOURCE : Fail to init the memory pool + +**/ +USBHC_MEM_POOL * +UsbHcInitMemPool ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN BOOLEAN Check4G, + IN UINT32 Which4G + ) +{ + USBHC_MEM_POOL *Pool; + + Pool = AllocatePool (sizeof (USBHC_MEM_POOL)); + + if (Pool == NULL) { + return Pool; + } + + Pool->PciIo = PciIo; + Pool->Check4G = Check4G; + Pool->Which4G = Which4G; + Pool->Head = UsbHcAllocMemBlock (Pool, USBHC_MEM_DEFAULT_PAGES); + + if (Pool->Head == NULL) { + gBS->FreePool (Pool); + Pool = NULL; + } + + return Pool; +} + + +/** + Release the memory management pool + + @param Pool The USB memory pool to free + + @return EFI_SUCCESS : The memory pool is freed + @return EFI_DEVICE_ERROR : Failed to free the memory pool + +**/ +EFI_STATUS +UsbHcFreeMemPool ( + IN USBHC_MEM_POOL *Pool + ) +{ + USBHC_MEM_BLOCK *Block; + + ASSERT (Pool->Head != NULL); + + // + // Unlink all the memory blocks from the pool, then free them. + // UsbHcUnlinkMemBlock can't be used to unlink and free the + // first block. + // + for (Block = Pool->Head->Next; Block != NULL; Block = Pool->Head->Next) { + UsbHcUnlinkMemBlock (Pool->Head, Block); + UsbHcFreeMemBlock (Pool, Block); + } + + UsbHcFreeMemBlock (Pool, Pool->Head); + gBS->FreePool (Pool); + return EFI_SUCCESS; +} + + +/** + Allocate some memory from the host controller's memory pool + which can be used to communicate with host controller. + + @param Pool The host controller's memory pool + @param Size Size of the memory to allocate + + @return The allocated memory or NULL + +**/ +VOID * +UsbHcAllocateMem ( + IN USBHC_MEM_POOL *Pool, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + USBHC_MEM_BLOCK *NewBlock; + VOID *Mem; + UINTN AllocSize; + UINTN Pages; + + Mem = NULL; + AllocSize = USBHC_MEM_ROUND (Size); + Head = Pool->Head; + ASSERT (Head != NULL); + + // + // First check whether current memory blocks can satisfy the allocation. + // + for (Block = Head; Block != NULL; Block = Block->Next) { + Mem = UsbHcAllocMemFromBlock (Block, AllocSize / USBHC_MEM_UNIT); + + if (Mem != NULL) { + ZeroMem (Mem, Size); + break; + } + } + + if (Mem != NULL) { + return Mem; + } + + // + // Create a new memory block if there is not enough memory + // in the pool. If the allocation size is larger than the + // default page number, just allocate a large enough memory + // block. Otherwise allocate default pages. + // + if (AllocSize > EFI_PAGES_TO_SIZE (USBHC_MEM_DEFAULT_PAGES)) { + Pages = EFI_SIZE_TO_PAGES (AllocSize) + 1; + } else { + Pages = USBHC_MEM_DEFAULT_PAGES; + } + + NewBlock = UsbHcAllocMemBlock (Pool, Pages); + + if (NewBlock == NULL) { + DEBUG ((mUsbHcDebugLevel, "UsbHcAllocateMem: failed to allocate block\n")); + return NULL; + } + + // + // Add the new memory block to the pool, then allocate memory from it + // + UsbHcInsertMemBlockToPool (Head, NewBlock); + Mem = UsbHcAllocMemFromBlock (NewBlock, AllocSize / USBHC_MEM_UNIT); + + if (Mem != NULL) { + ZeroMem (Mem, Size); + } + + return Mem; +} + + +/** + Free the allocated memory back to the memory pool + + @param Pool The memory pool of the host controller + @param Mem The memory to free + @param Size The size of the memory to free + + @return VOID + +**/ +VOID +UsbHcFreeMem ( + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + UINT8 *ToFree; + UINTN AllocSize; + UINTN Byte; + UINTN Bit; + UINTN Count; + + Head = Pool->Head; + AllocSize = USBHC_MEM_ROUND (Size); + ToFree = (UINT8 *) Mem; + + for (Block = Head; Block != NULL; Block = Block->Next) { + // + // scan the memory block list for the memory block that + // completely contains the memory to free. + // + if ((Block->Buf <= ToFree) && ((ToFree + AllocSize) <= (Block->Buf + Block->BufLen))) { + // + // compute the start byte and bit in the bit array + // + Byte = ((ToFree - Block->Buf) / USBHC_MEM_UNIT) / 8; + Bit = ((ToFree - Block->Buf) / USBHC_MEM_UNIT) % 8; + + // + // reset associated bits in bit arry + // + for (Count = 0; Count < (AllocSize / USBHC_MEM_UNIT); Count++) { + ASSERT (USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)); + + Block->Bits[Byte] ^= (UINT8) USB_HC_BIT (Bit); + NEXT_BIT (Byte, Bit); + } + + break; + } + } + + // + // If Block == NULL, it means that the current memory isn't + // in the host controller's pool. This is critical because + // the caller has passed in a wrong memory point + // + ASSERT (Block != NULL); + + // + // Release the current memory block if it is empty and not the head + // + if ((Block != Head) && UsbHcIsMemBlockEmpty (Block)) { + DEBUG ((mUsbHcDebugLevel, "UsbHcFreeMem: block %x is empty, recycle\n", Block)); + + UsbHcUnlinkMemBlock (Head, Block); + UsbHcFreeMemBlock (Pool, Block); + } + + return ; +} diff --git a/MdeModulePkg/Bus/Pci/EhciDxe/UsbHcMem.h b/MdeModulePkg/Bus/Pci/EhciDxe/UsbHcMem.h new file mode 100644 index 0000000000..10d3545feb --- /dev/null +++ b/MdeModulePkg/Bus/Pci/EhciDxe/UsbHcMem.h @@ -0,0 +1,168 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciMem.h + +Abstract: + + This file contains the definination for host controller memory management routines + +Revision History + +**/ + +#ifndef _EFI_EHCI_MEM_H_ +#define _EFI_EHCI_MEM_H_ + + +#include + +#define USB_HC_BIT(a) ((UINTN)(1 << (a))) + +#define USB_HC_BIT_IS_SET(Data, Bit) \ + ((BOOLEAN)(((Data) & USB_HC_BIT(Bit)) == USB_HC_BIT(Bit))) + +#define USB_HC_HIGH_32BIT(Addr64) \ + ((UINT32)(RShiftU64((UINTN)(Addr64), 32) & 0XFFFFFFFF)) + +typedef struct _USBHC_MEM_BLOCK USBHC_MEM_BLOCK; + +typedef struct _USBHC_MEM_BLOCK { + UINT8 *Bits; // Bit array to record which unit is allocated + UINTN BitsLen; + UINT8 *Buf; + UINT8 *BufHost; + UINTN BufLen; // Memory size in bytes + VOID *Mapping; + USBHC_MEM_BLOCK *Next; +} USBHC_MEM_BLOCK; + +// +// USBHC_MEM_POOL is used to manage the memory used by USB +// host controller. EHCI requires the control memory and transfer +// data to be on the same 4G memory. +// +typedef struct _USBHC_MEM_POOL { + EFI_PCI_IO_PROTOCOL *PciIo; + BOOLEAN Check4G; + UINT32 Which4G; + USBHC_MEM_BLOCK *Head; +} USBHC_MEM_POOL; + +enum { + USBHC_MEM_UNIT = 64, // Memory allocation unit, must be 2^n, n>4 + + USBHC_MEM_UNIT_MASK = USBHC_MEM_UNIT - 1, + USBHC_MEM_DEFAULT_PAGES = 16, +}; + +#define USBHC_MEM_ROUND(Len) (((Len) + USBHC_MEM_UNIT_MASK) & (~USBHC_MEM_UNIT_MASK)) + +// +// Advance the byte and bit to the next bit, adjust byte accordingly. +// +#define NEXT_BIT(Byte, Bit) \ + do { \ + (Bit)++; \ + if ((Bit) > 7) { \ + (Byte)++; \ + (Bit) = 0; \ + } \ + } while (0) + + + +USBHC_MEM_POOL * +UsbHcInitMemPool ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN BOOLEAN Check4G, + IN UINT32 Which4G + ) +/*++ + +Routine Description: + + Initialize the memory management pool for the host controller + +Arguments: + + Pool - The USB memory pool to initialize + PciIo - The PciIo that can be used to access the host controller + Check4G - Whether the host controller requires allocated memory + from one 4G address space. + Which4G - The 4G memory area each memory allocated should be from + +Returns: + + EFI_SUCCESS : The memory pool is initialized + EFI_OUT_OF_RESOURCE : Fail to init the memory pool + +--*/ +; + + + +/** + Release the memory management pool + + @param Pool The USB memory pool to free + + @return EFI_SUCCESS : The memory pool is freed + @return EFI_DEVICE_ERROR : Failed to free the memory pool + +**/ +EFI_STATUS +UsbHcFreeMemPool ( + IN USBHC_MEM_POOL *Pool + ) +; + + + +/** + Allocate some memory from the host controller's memory pool + which can be used to communicate with host controller. + + @param Pool The host controller's memory pool + @param Size Size of the memory to allocate + + @return The allocated memory or NULL + +**/ +VOID * +UsbHcAllocateMem ( + IN USBHC_MEM_POOL *Pool, + IN UINTN Size + ) +; + + + +/** + Free the allocated memory back to the memory pool + + @param Pool The memory pool of the host controller + @param Mem The memory to free + @param Size The size of the memory to free + + @return VOID + +**/ +VOID +UsbHcFreeMem ( + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +; +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/ComponentName.c b/MdeModulePkg/Bus/Pci/UhciDxe/ComponentName.c new file mode 100644 index 0000000000..d1cb1eb121 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/ComponentName.c @@ -0,0 +1,203 @@ +/** @file + +Copyright (c) 2004 - 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + ComponentName.c + +Abstract: + + +**/ + +#include "uhci.h" + +// +// EFI Component Name Functions +// +EFI_STATUS +EFIAPI +UhciComponentNameGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ); + +EFI_STATUS +EFIAPI +UhciComponentNameGetControllerName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle, OPTIONAL + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ); + +// +// EFI Component Name Protocol +// +EFI_COMPONENT_NAME_PROTOCOL gUhciComponentName = { + UhciComponentNameGetDriverName, + UhciComponentNameGetControllerName, + "eng" +}; + +static EFI_UNICODE_STRING_TABLE mUhciDriverNameTable[] = { + { "eng", L"Usb Uhci Driver" }, + { NULL, NULL } +}; + +EFI_STATUS +EFIAPI +UhciComponentNameGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ) +/*++ + + Routine Description: + Retrieves a Unicode string that is the user readable name of the EFI Driver. + + Arguments: + This - A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance. + Language - A pointer to a three character ISO 639-2 language identifier. + This is the language of the driver name that that the caller + is requesting, and it must match one of the languages specified + in SupportedLanguages. The number of languages supported by a + driver is up to the driver writer. + DriverName - A pointer to the Unicode string to return. This Unicode string + is the name of the driver specified by This in the language + specified by Language. + + Returns: + EFI_SUCCESS - The Unicode string for the Driver specified by This + and the language specified by Language was returned + in DriverName. + EFI_INVALID_PARAMETER - Language is NULL. + EFI_INVALID_PARAMETER - DriverName is NULL. + EFI_UNSUPPORTED - The driver specified by This does not support the + language specified by Language. + +--*/ +{ + return LookupUnicodeString ( + Language, + gUhciComponentName.SupportedLanguages, + mUhciDriverNameTable, + DriverName + ); +} + +EFI_STATUS +EFIAPI +UhciComponentNameGetControllerName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle, OPTIONAL + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +/*++ + + Routine Description: + Retrieves a Unicode string that is the user readable name of the controller + that is being managed by an EFI Driver. + + Arguments: + This - A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance. + ControllerHandle - The handle of a controller that the driver specified by + This is managing. This handle specifies the controller + whose name is to be returned. + ChildHandle - The handle of the child controller to retrieve the name + of. This is an optional parameter that may be NULL. It + will be NULL for device drivers. It will also be NULL + for a bus drivers that wish to retrieve the name of the + bus controller. It will not be NULL for a bus driver + that wishes to retrieve the name of a child controller. + Language - A pointer to a three character ISO 639-2 language + identifier. This is the language of the controller name + that that the caller is requesting, and it must match one + of the languages specified in SupportedLanguages. The + number of languages supported by a driver is up to the + driver writer. + ControllerName - A pointer to the Unicode string to return. This Unicode + string is the name of the controller specified by + ControllerHandle and ChildHandle in the language + specified by Language from the point of view of the + driver specified by This. + + Returns: + EFI_SUCCESS - The Unicode string for the user readable name in the + language specified by Language for the driver + specified by This was returned in DriverName. + EFI_INVALID_PARAMETER - ControllerHandle is not a valid EFI_HANDLE. + EFI_INVALID_PARAMETER - ChildHandle is not NULL and it is not a valid + EFI_HANDLE. + EFI_INVALID_PARAMETER - Language is NULL. + EFI_INVALID_PARAMETER - ControllerName is NULL. + EFI_UNSUPPORTED - The driver specified by This is not currently + managing the controller specified by + ControllerHandle and ChildHandle. + EFI_UNSUPPORTED - The driver specified by This does not support the + language specified by Language. + +--*/ +{ + EFI_STATUS Status; + USB_HC_DEV *UhciDev; + EFI_USB_HC_PROTOCOL *UsbHc; + + // + // This is a device driver, so ChildHandle must be NULL. + // + if (ChildHandle != NULL) { + return EFI_UNSUPPORTED; + } + + // + // Make sure this driver is currently managing ControllerHandle + // + Status = EfiTestManagedDevice ( + ControllerHandle, + gUhciDriverBinding.DriverBindingHandle, + &gEfiPciIoProtocolGuid + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Get the device context + // + Status = gBS->OpenProtocol ( + ControllerHandle, + &gEfiUsbHcProtocolGuid, + (VOID **) &UsbHc, + gUhciDriverBinding.DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + UhciDev = UHC_FROM_USB_HC_PROTO (UsbHc); + + return LookupUnicodeString ( + Language, + gUhciComponentName.SupportedLanguages, + UhciDev->CtrlNameTable, + ControllerName + ); + +} diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.c b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.c new file mode 100644 index 0000000000..1c3e37a7bc --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.c @@ -0,0 +1,2331 @@ +/** @file + +Copyright (c) 2004 - 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + Uhci.c + +Abstract: + + The UHCI driver model and HC protocol routines. + +Revision History + + +**/ + +#include "Uhci.h" + + +/** + Provides software reset for the USB host controller. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + Attributes: A bit mask of the reset operation to perform. + + @return EFI_SUCCESS : The reset operation succeeded. + @return EFI_INVALID_PARAMETER : Attributes is not valid. + @return EFI_DEVICE_ERROR : An error was encountered while attempting + @return to perform the reset operation. + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciReset ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT16 Attributes + ) +{ + USB_HC_DEV *Uhc; + EFI_TPL OldTpl; + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + Uhc = UHC_FROM_USB_HC_PROTO (This); + + switch (Attributes) { + case EFI_USB_HC_RESET_GLOBAL: + // + // Stop schedule and set the Global Reset bit in the command register + // + UhciStopHc (Uhc, STALL_1_SECOND); + UhciSetRegBit (Uhc->PciIo, USBCMD_OFFSET, USBCMD_GRESET); + + // + // Wait 50ms for root port to let reset complete + // See UHCI spec page122 Reset signaling + // + gBS->Stall (ROOT_PORT_REST_TIME); + + // + // Clear the Global Reset bit to zero. + // + UhciClearRegBit (Uhc->PciIo, USBCMD_OFFSET, USBCMD_GRESET); + + // + // UHCI spec page120 reset recovery time + // + gBS->Stall (PORT_RESET_RECOVERY_TIME); + break; + + case EFI_USB_HC_RESET_HOST_CONTROLLER: + // + // Stop schedule and set Host Controller Reset bit to 1 + // + UhciStopHc (Uhc, STALL_1_SECOND); + UhciSetRegBit (Uhc->PciIo, USBCMD_OFFSET, USBCMD_HCRESET); + + // + // this bit will be reset by Host Controller when reset is completed. + // wait 10ms to let reset complete + // + gBS->Stall (PORT_RESET_RECOVERY_TIME); + break; + + default: + goto ON_INVAILD_PARAMETER; + } + + // + // Delete all old transactions on the USB bus, then + // reinitialize the frame list + // + UhciFreeAllAsyncReq (Uhc); + UhciDestoryFrameList (Uhc); + UhciInitFrameList (Uhc); + + gBS->RestoreTPL (OldTpl); + + return EFI_SUCCESS; + +ON_INVAILD_PARAMETER: + + gBS->RestoreTPL (OldTpl); + + return EFI_INVALID_PARAMETER; +} + + +/** + Retrieves current state of the USB host controller. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + State : A pointer to the EFI_USB_HC_STATE data structure that + indicates current state of the USB host controller. + + @return EFI_SUCCESS : State was returned + @return EFI_INVALID_PARAMETER : State is NULL. + @return EFI_DEVICE_ERROR : An error was encountered + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciGetState ( + IN EFI_USB_HC_PROTOCOL *This, + OUT EFI_USB_HC_STATE *State + ) +{ + USB_HC_DEV *Uhc; + UINT16 UsbSts; + UINT16 UsbCmd; + + if (State == NULL) { + return EFI_INVALID_PARAMETER; + } + + Uhc = UHC_FROM_USB_HC_PROTO (This); + + UsbCmd = UhciReadReg (Uhc->PciIo, USBCMD_OFFSET); + UsbSts = UhciReadReg (Uhc->PciIo, USBSTS_OFFSET); + + if (UsbCmd & USBCMD_EGSM) { + *State = EfiUsbHcStateSuspend; + + } else if ((UsbSts & USBSTS_HCH) != 0) { + *State = EfiUsbHcStateHalt; + + } else { + *State = EfiUsbHcStateOperational; + } + + return EFI_SUCCESS; +} + + +/** + Sets the USB host controller to a specific state. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + State : Indicates the state of the host controller that will be set. + + @return EFI_SUCCESS : The USB host controller was successfully set + @return EFI_INVALID_PARAMETER : State is invalid. + @return EFI_DEVICE_ERROR : Failed to set the state specified + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciSetState ( + IN EFI_USB_HC_PROTOCOL *This, + IN EFI_USB_HC_STATE State + ) +{ + EFI_USB_HC_STATE CurState; + USB_HC_DEV *Uhc; + EFI_TPL OldTpl; + EFI_STATUS Status; + UINT16 UsbCmd; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + Status = UhciGetState (This, &CurState); + + if (EFI_ERROR (Status)) { + return EFI_DEVICE_ERROR; + } + + if (CurState == State) { + return EFI_SUCCESS; + } + + Status = EFI_SUCCESS; + OldTpl = gBS->RaiseTPL (UHCI_TPL); + + switch (State) { + case EfiUsbHcStateHalt: + Status = UhciStopHc (Uhc, STALL_1_SECOND); + break; + + case EfiUsbHcStateOperational: + UsbCmd = UhciReadReg (Uhc->PciIo, USBCMD_OFFSET); + + if (CurState == EfiUsbHcStateHalt) { + // + // Set Run/Stop bit to 1, also set the bandwidht reclamation + // point to 64 bytes + // + UsbCmd |= USBCMD_RS | USBCMD_MAXP; + UhciWriteReg (Uhc->PciIo, USBCMD_OFFSET, UsbCmd); + + } else if (CurState == EfiUsbHcStateSuspend) { + // + // If FGR(Force Global Resume) bit is 0, set it + // + if ((UsbCmd & USBCMD_FGR) == 0) { + UsbCmd |= USBCMD_FGR; + UhciWriteReg (Uhc->PciIo, USBCMD_OFFSET, UsbCmd); + } + + // + // wait 20ms to let resume complete (20ms is specified by UHCI spec) + // + gBS->Stall (FORCE_GLOBAL_RESUME_TIME); + + // + // Write FGR bit to 0 and EGSM(Enter Global Suspend Mode) bit to 0 + // + UsbCmd &= ~USBCMD_FGR; + UsbCmd &= ~USBCMD_EGSM; + UsbCmd |= USBCMD_RS; + UhciWriteReg (Uhc->PciIo, USBCMD_OFFSET, UsbCmd); + } + + break; + + case EfiUsbHcStateSuspend: + Status = UhciSetState (This, EfiUsbHcStateHalt); + + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto ON_EXIT; + } + + // + // Set Enter Global Suspend Mode bit to 1. + // + UsbCmd = UhciReadReg (Uhc->PciIo, USBCMD_OFFSET); + UsbCmd |= USBCMD_EGSM; + UhciWriteReg (Uhc->PciIo, USBCMD_OFFSET, UsbCmd); + break; + + default: + Status = EFI_INVALID_PARAMETER; + break; + } + +ON_EXIT: + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Retrieves the number of root hub ports. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + PortNumber : A pointer to the number of the root hub ports. + + @return EFI_SUCCESS : The port number was retrieved successfully. + @return EFI_INVALID_PARAMETER : PortNumber is NULL. + @return EFI_DEVICE_ERROR : An error was encountered + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciGetRootHubPortNumber ( + IN EFI_USB_HC_PROTOCOL *This, + OUT UINT8 *PortNumber + ) +{ + USB_HC_DEV *Uhc; + UINT32 Offset; + UINT16 PortSC; + UINT32 Index; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + + if (PortNumber == NULL) { + return EFI_INVALID_PARAMETER; + } + + *PortNumber = 0; + + for (Index = 0; Index < USB_MAX_ROOTHUB_PORT; Index++) { + Offset = USBPORTSC_OFFSET + Index * 2; + PortSC = UhciReadReg (Uhc->PciIo, Offset); + + // + // Port status's bit 7 is reserved and always returns 1 if + // the port number is valid. Intel's UHCI (in EHCI controller) + // returns 0 in this bit if port number is invalid. Also, if + // PciIo IoRead returns error, 0xFFFF is returned to caller. + // + if (((PortSC & 0x80) != 0) && (PortSC != 0xFFFF)) { + (*PortNumber)++; + } + } + + Uhc->RootPorts = *PortNumber; + + UHCI_DEBUG (("UhciGetRootHubPortNumber: %d ports\n", Uhc->RootPorts)); + return EFI_SUCCESS; +} + + +/** + Retrieves the current status of a USB root hub port. + + This : A pointer to the EFI_USB_HC_PROTOCOL. + PortNumber : Specifies the root hub port. This value is zero-based. + PortStatus : A pointer to the current port status bits and port status change bits. + + @return EFI_SUCCESS : The port status was returned in PortStatus. + @return EFI_INVALID_PARAMETER : PortNumber is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +EFI_STATUS +EFIAPI +UhciGetRootHubPortStatus ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 PortNumber, + OUT EFI_USB_PORT_STATUS *PortStatus + ) +{ + USB_HC_DEV *Uhc; + UINT32 Offset; + UINT16 PortSC; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + + if (PortStatus == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (PortNumber >= Uhc->RootPorts) { + return EFI_INVALID_PARAMETER; + } + + Offset = USBPORTSC_OFFSET + PortNumber * 2; + PortStatus->PortStatus = 0; + PortStatus->PortChangeStatus = 0; + + PortSC = UhciReadReg (Uhc->PciIo, Offset); + + if (PortSC & USBPORTSC_CCS) { + PortStatus->PortStatus |= USB_PORT_STAT_CONNECTION; + } + + if (PortSC & USBPORTSC_PED) { + PortStatus->PortStatus |= USB_PORT_STAT_ENABLE; + } + + if (PortSC & USBPORTSC_SUSP) { + UHCI_DEBUG (("UhciGetRootHubPortStatus: port %d is suspended\n", PortNumber)); + PortStatus->PortStatus |= USB_PORT_STAT_SUSPEND; + } + + if (PortSC & USBPORTSC_PR) { + PortStatus->PortStatus |= USB_PORT_STAT_RESET; + } + + if (PortSC & USBPORTSC_LSDA) { + PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED; + } + + // + // CHC will always return one in port owner bit + // + PortStatus->PortStatus |= USB_PORT_STAT_OWNER; + + if (PortSC & USBPORTSC_CSC) { + PortStatus->PortChangeStatus |= USB_PORT_STAT_C_CONNECTION; + } + + if (PortSC & USBPORTSC_PEDC) { + PortStatus->PortChangeStatus |= USB_PORT_STAT_C_ENABLE; + } + + return EFI_SUCCESS; +} + + +/** + Sets a feature for the specified root hub port. + + This : A pointer to the EFI_USB_HC_PROTOCOL. + PortNumber : Specifies the root hub port whose feature + is requested to be set. + PortFeature : Indicates the feature selector associated + with the feature set request. + + @return EFI_SUCCESS : The feature was set for the port. + @return EFI_INVALID_PARAMETER : PortNumber is invalid or PortFeature is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciSetRootHubPortFeature ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 PortNumber, + IN EFI_USB_PORT_FEATURE PortFeature + ) +{ + USB_HC_DEV *Uhc; + EFI_TPL OldTpl; + UINT32 Offset; + UINT16 PortSC; + UINT16 Command; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + + if (PortNumber >= Uhc->RootPorts) { + return EFI_INVALID_PARAMETER; + } + + Offset = USBPORTSC_OFFSET + PortNumber * 2; + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + PortSC = UhciReadReg (Uhc->PciIo, Offset); + + switch (PortFeature) { + case EfiUsbPortSuspend: + Command = UhciReadReg (Uhc->PciIo, USBCMD_OFFSET); + if (!(Command & USBCMD_EGSM)) { + // + // if global suspend is not active, can set port suspend + // + PortSC &= 0xfff5; + PortSC |= USBPORTSC_SUSP; + } + break; + + case EfiUsbPortReset: + PortSC &= 0xfff5; + PortSC |= USBPORTSC_PR; + break; + + case EfiUsbPortPower: + // + // No action + // + break; + + case EfiUsbPortEnable: + PortSC &= 0xfff5; + PortSC |= USBPORTSC_PED; + break; + + default: + gBS->RestoreTPL (OldTpl); + return EFI_INVALID_PARAMETER; + } + + UhciWriteReg (Uhc->PciIo, Offset, PortSC); + gBS->RestoreTPL (OldTpl); + + return EFI_SUCCESS; +} + + +/** + Clears a feature for the specified root hub port. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + PortNumber : Specifies the root hub port whose feature + is requested to be cleared. + PortFeature : Indicates the feature selector associated with the + feature clear request. + + @return EFI_SUCCESS : The feature was cleared for the port. + @return EFI_INVALID_PARAMETER : PortNumber is invalid or PortFeature is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciClearRootHubPortFeature ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 PortNumber, + IN EFI_USB_PORT_FEATURE PortFeature + ) +{ + USB_HC_DEV *Uhc; + EFI_TPL OldTpl; + UINT32 Offset; + UINT16 PortSC; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + + if (PortNumber >= Uhc->RootPorts) { + return EFI_INVALID_PARAMETER; + } + + Offset = USBPORTSC_OFFSET + PortNumber * 2; + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + PortSC = UhciReadReg (Uhc->PciIo, Offset); + + switch (PortFeature) { + case EfiUsbPortEnable: + PortSC &= 0xfff5; + PortSC &= ~USBPORTSC_PED; + break; + + case EfiUsbPortSuspend: + // + // Cause a resume on the specified port if in suspend mode. + // + PortSC &= 0xfff5; + PortSC &= ~USBPORTSC_SUSP; + break; + + case EfiUsbPortPower: + // + // No action + // + break; + + case EfiUsbPortReset: + PortSC &= 0xfff5; + PortSC &= ~USBPORTSC_PR; + break; + + case EfiUsbPortConnectChange: + PortSC &= 0xfff5; + PortSC |= USBPORTSC_CSC; + break; + + case EfiUsbPortEnableChange: + PortSC &= 0xfff5; + PortSC |= USBPORTSC_PEDC; + break; + + case EfiUsbPortSuspendChange: + // + // Root hub does not support this + // + break; + + case EfiUsbPortOverCurrentChange: + // + // Root hub does not support this + // + break; + + case EfiUsbPortResetChange: + // + // Root hub does not support this + // + break; + + default: + gBS->RestoreTPL (OldTpl); + return EFI_INVALID_PARAMETER; + } + + UhciWriteReg (Uhc->PciIo, Offset, PortSC); + gBS->RestoreTPL (OldTpl); + + return EFI_SUCCESS; +} + + +/** + Submits control transfer to a target USB device. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + DeviceAddress : Usb device address + IsSlowDevice : Whether the device is of slow speed or full speed + MaximumPacketLength : maximum packet size of the default control endpoint + Request : USB device request to send + TransferDirection : Specifies the data direction for the transfer. + Data : Data buffer to transmit from or receive into + DataLength : Number of bytes of the data + TimeOut : Maximum time, in microseconds + TransferResult : Return result in this + + @return EFI_SUCCESS : Transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : Failed due to a lack of resources. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Failed due to timeout. + @return EFI_DEVICE_ERROR : Failed due to host controller or device error. + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciControlTransfer ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 DeviceAddress, + IN BOOLEAN IsSlowDevice, + IN UINT8 MaximumPacketLength, + IN EFI_USB_DEVICE_REQUEST *Request, + IN EFI_USB_DATA_DIRECTION TransferDirection, + IN OUT VOID *Data, OPTIONAL + IN OUT UINTN *DataLength, OPTIONAL + IN UINTN TimeOut, + OUT UINT32 *TransferResult + ) +{ + USB_HC_DEV *Uhc; + UHCI_TD_SW *TDs; + EFI_TPL OldTpl; + EFI_STATUS Status; + UHCI_QH_RESULT QhResult; + UINT8 PktId; + UINT8 *RequestPhy; + VOID *RequestMap; + UINT8 *DataPhy; + VOID *DataMap; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + TDs = NULL; + DataPhy = NULL; + DataMap = NULL; + RequestPhy = NULL; + RequestMap = NULL; + + // + // Parameters Checking + // + if (Request == NULL || TransferResult == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (IsSlowDevice && (MaximumPacketLength != 8)) { + return EFI_INVALID_PARAMETER; + } + + if ((MaximumPacketLength != 8) && (MaximumPacketLength != 16) && + (MaximumPacketLength != 32) && (MaximumPacketLength != 64)) { + + return EFI_INVALID_PARAMETER; + } + + if ((TransferDirection != EfiUsbNoData) && (DataLength == NULL)) { + return EFI_INVALID_PARAMETER; + } + + *TransferResult = EFI_USB_ERR_SYSTEM; + Status = EFI_DEVICE_ERROR; + + // + // If errors exist that cause host controller halt, + // clear status then return EFI_DEVICE_ERROR. + // + UhciAckAllInterrupt (Uhc); + + if (!UhciIsHcWorking (Uhc->PciIo)) { + return EFI_DEVICE_ERROR; + } + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + + // + // Map the Request and data for bus master access, + // then create a list of TD for this transfer + // + Status = UhciMapUserRequest (Uhc, Request, &RequestPhy, &RequestMap); + + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + Status = UhciMapUserData (Uhc, TransferDirection, Data, DataLength, &PktId, &DataPhy, &DataMap); + + if (EFI_ERROR (Status)) { + Uhc->PciIo->Unmap (Uhc->PciIo, RequestMap); + goto ON_EXIT; + } + + TDs = UhciCreateCtrlTds ( + Uhc, + DeviceAddress, + PktId, + RequestPhy, + DataPhy, + *DataLength, + MaximumPacketLength, + IsSlowDevice + ); + + if (TDs == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto UNMAP_DATA; + } + + // + // According to the speed of the end point, link + // the TD to corrosponding queue head, then check + // the execution result + // + UhciLinkTdToQh (Uhc->CtrlQh, TDs); + Status = UhciExecuteTransfer (Uhc, Uhc->CtrlQh, TDs, TimeOut, IsSlowDevice, &QhResult); + UhciUnlinkTdFromQh (Uhc->CtrlQh, TDs); + + Uhc->PciIo->Flush (Uhc->PciIo); + + *TransferResult = QhResult.Result; + + if (DataLength != NULL) { + *DataLength = QhResult.Complete; + } + + UhciDestoryTds (Uhc, TDs); + +UNMAP_DATA: + Uhc->PciIo->Unmap (Uhc->PciIo, DataMap); + Uhc->PciIo->Unmap (Uhc->PciIo, RequestMap); + +ON_EXIT: + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Submits bulk transfer to a bulk endpoint of a USB device. + + This :A pointer to the EFI_USB_HC_PROTOCOL instance. + DeviceAddress : Usb device address + EndPointAddress : Endpoint number and endpoint direction + MaximumPacketLength : Maximum packet size of the target endpoint + Data : Data buffer to transmit from or receive into + DataLength : Length of the data buffer + DataToggle : On input, data toggle to use, on output, the next toggle + TimeOut : Indicates the maximum time + TransferResult : Variable to receive the transfer result + + @return EFI_SUCCESS : The bulk transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : Failed due to lack of resource. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Failed due to timeout. + @return EFI_DEVICE_ERROR : Failed due to host controller or device error. + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciBulkTransfer ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 DeviceAddress, + IN UINT8 EndPointAddress, + IN UINT8 MaximumPacketLength, + IN OUT VOID *Data, + IN OUT UINTN *DataLength, + IN OUT UINT8 *DataToggle, + IN UINTN TimeOut, + OUT UINT32 *TransferResult + ) +{ + EFI_USB_DATA_DIRECTION Direction; + EFI_TPL OldTpl; + USB_HC_DEV *Uhc; + UHCI_TD_SW *TDs; + UHCI_QH_SW *BulkQh; + UHCI_QH_RESULT QhResult; + EFI_STATUS Status; + UINT8 PktId; + UINT8 *DataPhy; + VOID *DataMap; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + DataPhy = NULL; + DataMap = NULL; + + if ((DataLength == NULL) || (Data == NULL) || (TransferResult == NULL)) { + return EFI_INVALID_PARAMETER; + } + + if (*DataLength == 0) { + return EFI_INVALID_PARAMETER; + } + + if ((*DataToggle != 1) && (*DataToggle != 0)) { + return EFI_INVALID_PARAMETER; + } + + if ((MaximumPacketLength != 8) && (MaximumPacketLength != 16) && + (MaximumPacketLength != 32) && (MaximumPacketLength != 64)) { + return EFI_INVALID_PARAMETER; + } + + *TransferResult = EFI_USB_ERR_SYSTEM; + Status = EFI_OUT_OF_RESOURCES; + + // + // If has errors that cause host controller halt, + // then return EFI_DEVICE_ERROR directly. + // + UhciAckAllInterrupt (Uhc); + + if (!UhciIsHcWorking (Uhc->PciIo)) { + return EFI_DEVICE_ERROR; + } + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + + // + // Map the source data buffer for bus master access, + // then create a list of TDs + // + if (EndPointAddress & 0x80) { + Direction = EfiUsbDataIn; + } else { + Direction = EfiUsbDataOut; + } + + Status = UhciMapUserData (Uhc, Direction, Data, DataLength, &PktId, &DataPhy, &DataMap); + + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + Status = EFI_OUT_OF_RESOURCES; + TDs = UhciCreateBulkOrIntTds ( + Uhc, + DeviceAddress, + EndPointAddress, + PktId, + DataPhy, + *DataLength, + DataToggle, + MaximumPacketLength, + FALSE + ); + + if (TDs == NULL) { + Uhc->PciIo->Unmap (Uhc->PciIo, DataMap); + goto ON_EXIT; + } + + + // + // Link the TDs to bulk queue head. According to the platfore + // defintion of UHCI_NO_BW_RECLAMATION, BulkQh is either configured + // to do full speed bandwidth reclamation or not. + // + BulkQh = Uhc->BulkQh; + + UhciLinkTdToQh (BulkQh, TDs); + Status = UhciExecuteTransfer (Uhc, BulkQh, TDs, TimeOut, FALSE, &QhResult); + UhciUnlinkTdFromQh (BulkQh, TDs); + + Uhc->PciIo->Flush (Uhc->PciIo); + + *TransferResult = QhResult.Result; + *DataToggle = QhResult.NextToggle; + *DataLength = QhResult.Complete; + + UhciDestoryTds (Uhc, TDs); + Uhc->PciIo->Unmap (Uhc->PciIo, DataMap); + +ON_EXIT: + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Submits an asynchronous interrupt transfer to an interrupt endpoint of a USB device. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : Endpoint number with direction + IsSlowDevice : Whether the target device is slow device or full-speed device. + MaximumPacketLength : Maximum packet size of the target endpoint + IsNewTransfer : If TRUE, submit a new async interrupt transfer, otherwise + cancel an existed one + DataToggle : On input, the data toggle to use; On output, next data toggle + PollingInterval : Interrupt poll rate in milliseconds + DataLength : Length of data to receive + CallBackFunction : Function to call periodically + Context : User context + + @return EFI_SUCCESS : Request is submitted or cancelled + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_OUT_OF_RESOURCES : Failed due to a lack of resources. + @return EFI_DEVICE_ERROR : Failed to due to device error + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciAsyncInterruptTransfer ( + IN EFI_USB_HC_PROTOCOL * This, + IN UINT8 DeviceAddress, + IN UINT8 EndPointAddress, + IN BOOLEAN IsSlowDevice, + IN UINT8 MaximumPacketLength, + IN BOOLEAN IsNewTransfer, + IN OUT UINT8 *DataToggle, + IN UINTN PollingInterval, OPTIONAL + IN UINTN DataLength, OPTIONAL + IN EFI_ASYNC_USB_TRANSFER_CALLBACK CallBackFunction, OPTIONAL + IN VOID *Context OPTIONAL + ) +{ + USB_HC_DEV *Uhc; + UHCI_QH_SW *Qh; + UHCI_TD_SW *IntTds; + EFI_TPL OldTpl; + EFI_STATUS Status; + UINT8 *DataPtr; + UINT8 *DataPhy; + VOID *DataMap; + UINT8 PktId; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + Qh = NULL; + IntTds = NULL; + DataPtr = NULL; + DataPhy = NULL; + DataMap = NULL; + + if ((EndPointAddress & 0x80) == 0) { + return EFI_INVALID_PARAMETER; + } + + // + // Delete Async interrupt transfer request + // + if (!IsNewTransfer) { + OldTpl = gBS->RaiseTPL (UHCI_TPL); + Status = UhciRemoveAsyncReq (Uhc, DeviceAddress, EndPointAddress, DataToggle); + + gBS->RestoreTPL (OldTpl); + return Status; + } + + if (PollingInterval < 1 || PollingInterval > 255) { + return EFI_INVALID_PARAMETER; + } + + if (DataLength == 0) { + return EFI_INVALID_PARAMETER; + } + + if ((*DataToggle != 1) && (*DataToggle != 0)) { + return EFI_INVALID_PARAMETER; + } + + // + // If has errors that cause host controller halt, + // then return EFI_DEVICE_ERROR directly. + // + UhciAckAllInterrupt (Uhc); + + if (!UhciIsHcWorking (Uhc->PciIo)) { + return EFI_DEVICE_ERROR; + } + + // + // Allocate and map source data buffer for bus master access. + // + DataPtr = AllocatePool (DataLength); + + if (DataPtr == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + + // + // Map the user data then create a queue head and + // list of TD for it. + // + Status = UhciMapUserData ( + Uhc, + EfiUsbDataIn, + DataPtr, + &DataLength, + &PktId, + &DataPhy, + &DataMap + ); + + if (EFI_ERROR (Status)) { + goto FREE_DATA; + } + + Qh = UhciCreateQh (Uhc, PollingInterval); + + if (Qh == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto UNMAP_DATA; + } + + IntTds = UhciCreateBulkOrIntTds ( + Uhc, + DeviceAddress, + EndPointAddress, + PktId, + DataPhy, + DataLength, + DataToggle, + MaximumPacketLength, + IsSlowDevice + ); + + if (IntTds == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto DESTORY_QH; + } + + UhciLinkTdToQh (Qh, IntTds); + + // + // Save QH-TD structures to async Interrupt transfer list, + // for monitor interrupt transfer execution routine use. + // + Status = UhciCreateAsyncReq ( + Uhc, + Qh, + IntTds, + DeviceAddress, + EndPointAddress, + DataLength, + PollingInterval, + DataMap, + DataPtr, + CallBackFunction, + Context, + IsSlowDevice + ); + + if (EFI_ERROR (Status)) { + goto DESTORY_QH; + } + + UhciLinkQhToFrameList (Uhc->FrameBase, Qh); + + gBS->RestoreTPL (OldTpl); + return EFI_SUCCESS; + +DESTORY_QH: + UsbHcFreeMem (Uhc->MemPool, Qh, sizeof (UHCI_QH_SW)); + +UNMAP_DATA: + Uhc->PciIo->Unmap (Uhc->PciIo, DataMap); + +FREE_DATA: + gBS->FreePool (DataPtr); + Uhc->PciIo->Flush (Uhc->PciIo); + + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Submits synchronous interrupt transfer to an interrupt endpoint of a USB device. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + DeviceAddress : Device address of the target USB device + EndPointAddress : Endpoint number and direction + IsSlowDevice : Whether the target device is of slow speed or full speed + MaximumPacketLength : Maximum packet size of target endpoint + Data : Data to transmit or receive + DataLength : On input, data length to transmit or buffer size. + On output, the number of bytes transferred. + DataToggle : On input, data toggle to use; On output, next data toggle + TimeOut : Maximum time, in microseconds, transfer is allowed to complete. + TransferResult : Variable to receive transfer result + + @return EFI_SUCCESS : Transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : Failed due to lack of resource. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Failed due to timeout. + @return EFI_DEVICE_ERROR : Failed due to host controller or device error + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciSyncInterruptTransfer ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 DeviceAddress, + IN UINT8 EndPointAddress, + IN BOOLEAN IsSlowDevice, + IN UINT8 MaximumPacketLength, + IN OUT VOID *Data, + IN OUT UINTN *DataLength, + IN OUT UINT8 *DataToggle, + IN UINTN TimeOut, + OUT UINT32 *TransferResult + ) +{ + EFI_STATUS Status; + USB_HC_DEV *Uhc; + UHCI_TD_SW *TDs; + UHCI_QH_RESULT QhResult; + EFI_TPL OldTpl; + UINT8 *DataPhy; + VOID *DataMap; + UINT8 PktId; + + Uhc = UHC_FROM_USB_HC_PROTO (This); + DataPhy = NULL; + DataMap = NULL; + TDs = NULL; + + if ((DataLength == NULL) || (Data == NULL) || (TransferResult == NULL)) { + return EFI_INVALID_PARAMETER; + } + + if ((EndPointAddress & 0x80) == 0) { + return EFI_INVALID_PARAMETER; + } + + if ((*DataToggle != 1) && (*DataToggle != 0)) { + return EFI_INVALID_PARAMETER; + } + + if ((*DataLength == 0) || (MaximumPacketLength > 64)) { + return EFI_INVALID_PARAMETER; + } + + if (IsSlowDevice && (MaximumPacketLength > 8)) { + return EFI_INVALID_PARAMETER; + } + + *TransferResult = EFI_USB_ERR_SYSTEM; + Status = EFI_DEVICE_ERROR; + + + UhciAckAllInterrupt (Uhc); + + if (!UhciIsHcWorking (Uhc->PciIo)) { + return Status; + } + + OldTpl = gBS->RaiseTPL (UHCI_TPL); + + // + // Map the source data buffer for bus master access. + // Create Tds list, then link it to the UHC's interrupt list + // + Status = UhciMapUserData ( + Uhc, + EfiUsbDataIn, + Data, + DataLength, + &PktId, + &DataPhy, + &DataMap + ); + + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + TDs = UhciCreateBulkOrIntTds ( + Uhc, + DeviceAddress, + EndPointAddress, + PktId, + DataPhy, + *DataLength, + DataToggle, + MaximumPacketLength, + IsSlowDevice + ); + + if (TDs == NULL) { + Uhc->PciIo->Unmap (Uhc->PciIo, DataMap); + + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + + UhciLinkTdToQh (Uhc->SyncIntQh, TDs); + + Status = UhciExecuteTransfer (Uhc, Uhc->SyncIntQh, TDs, TimeOut, IsSlowDevice, &QhResult); + + UhciUnlinkTdFromQh (Uhc->SyncIntQh, TDs); + Uhc->PciIo->Flush (Uhc->PciIo); + + *TransferResult = QhResult.Result; + *DataToggle = QhResult.NextToggle; + *DataLength = QhResult.Complete; + + UhciDestoryTds (Uhc, TDs); + Uhc->PciIo->Unmap (Uhc->PciIo, DataMap); + +ON_EXIT: + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Submits isochronous transfer to a target USB device. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : End point address withdirection + MaximumPacketLength : Maximum packet size of the endpoint + Data : Data to transmit or receive + DataLength : Bytes of the data + TransferResult : Variable to receive the result + + @return EFI_UNSUPPORTED + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciIsochronousTransfer ( + IN EFI_USB_HC_PROTOCOL *This, + IN UINT8 DeviceAddress, + IN UINT8 EndPointAddress, + IN UINT8 MaximumPacketLength, + IN OUT VOID *Data, + IN UINTN DataLength, + OUT UINT32 *TransferResult + ) +{ + return EFI_UNSUPPORTED; +} + + +/** + Submits Async isochronous transfer to a target USB device. + + This : A pointer to the EFI_USB_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : End point address withdirection + MaximumPacketLength : Maximum packet size of the endpoint + Data : Data to transmit or receive + IsochronousCallBack : Function to call when the transfer completes + Context : User context + + @return EFI_UNSUPPORTED + +**/ +STATIC +EFI_STATUS +EFIAPI +UhciAsyncIsochronousTransfer ( + IN EFI_USB_HC_PROTOCOL * This, + IN UINT8 DeviceAddress, + IN UINT8 EndPointAddress, + IN UINT8 MaximumPacketLength, + IN OUT VOID *Data, + IN UINTN DataLength, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK IsochronousCallBack, + IN VOID *Context OPTIONAL + ) +{ + return EFI_UNSUPPORTED; +} + + + +/** + Provides software reset for the USB host controller according to UEFI 2.0 spec. + + @param This A pointer to the EFI_USB2_HC_PROTOCOL instance. + @param Attributes A bit mask of the reset operation to perform. See + below for a list of the supported bit mask values. + + @return EFI_SUCCESS : The reset operation succeeded. + @return EFI_INVALID_PARAMETER : Attributes is not valid. + @return EFI_UNSUPPORTED : This type of reset is not currently supported + @return EFI_DEVICE_ERROR : Other errors + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2Reset ( + IN EFI_USB2_HC_PROTOCOL *This, + IN UINT16 Attributes + ) +{ + USB_HC_DEV *UhciDev; + + UhciDev = UHC_FROM_USB2_HC_PROTO (This); + + if ((Attributes == EFI_USB_HC_RESET_GLOBAL_WITH_DEBUG) || + (Attributes == EFI_USB_HC_RESET_HOST_WITH_DEBUG)) { + return EFI_UNSUPPORTED; + } + + return UhciReset (&UhciDev->UsbHc, Attributes); +} + + +/** + Retrieves current state of the USB host controller according to UEFI 2.0 spec. + + @param This A pointer to the EFI_USB_HC_PROTOCOL instance. + @param State Variable to receive current device state + + @return EFI_SUCCESS : The state is returned + @return EFI_INVALID_PARAMETER : State is not valid. + @return EFI_DEVICE_ERROR : Other errors2006 + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2GetState ( + IN CONST EFI_USB2_HC_PROTOCOL *This, + OUT EFI_USB_HC_STATE *State + ) +{ + USB_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + return UhciGetState (&Uhc->UsbHc, State); +} + + +/** + Sets the USB host controller to a specific state according to UEFI 2.0 spec. + + @param This A pointer to the EFI_USB_HC_PROTOCOL instance. + @param State Indicates the state of the host controller that will + be set. + + @return EFI_SUCCESS : Host controller was successfully placed in the state + @return EFI_INVALID_PARAMETER : State is invalid. + @return EFI_DEVICE_ERROR : Failed to set the state + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2SetState ( + IN EFI_USB2_HC_PROTOCOL *This, + IN EFI_USB_HC_STATE State + ) +{ + USB_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + return UhciSetState (&Uhc->UsbHc, State); +} + + +/** + Retrieves capabilities of USB host controller according to UEFI 2.0 spec. + + @param This A pointer to the EFI_USB2_HC_PROTOCOL instance + @param MaxSpeed A pointer to the max speed USB host controller + supports. + @param PortNumber A pointer to the number of root hub ports. + @param Is64BitCapable A pointer to an integer to show whether USB host + controller supports 64-bit memory addressing. + + @return EFI_SUCCESS : capabilities were retrieved successfully. + @return EFI_INVALID_PARAMETER : MaxSpeed or PortNumber or Is64BitCapable is NULL. + @return EFI_DEVICE_ERROR : An error was encountered + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2GetCapability ( + IN EFI_USB2_HC_PROTOCOL *This, + OUT UINT8 *MaxSpeed, + OUT UINT8 *PortNumber, + OUT UINT8 *Is64BitCapable + ) +{ + USB_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + + if ((NULL == MaxSpeed) || (NULL == PortNumber) || (NULL == Is64BitCapable)) { + return EFI_INVALID_PARAMETER; + } + + *MaxSpeed = EFI_USB_SPEED_FULL; + *Is64BitCapable = (UINT8) FALSE; + + return UhciGetRootHubPortNumber (&Uhc->UsbHc, PortNumber); +} + + +/** + Retrieves the current status of a USB root hub port according to UEFI 2.0 spec. + + @param This A pointer to the EFI_USB2_HC_PROTOCOL. + @param PortNumber The port to get status + @param PortStatus A pointer to the current port status bits and port + status change bits. + + @return EFI_SUCCESS : status of the USB root hub port was returned in PortStatus. + @return EFI_INVALID_PARAMETER : PortNumber is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2GetRootHubPortStatus ( + IN CONST EFI_USB2_HC_PROTOCOL *This, + IN CONST UINT8 PortNumber, + OUT EFI_USB_PORT_STATUS *PortStatus + ) +{ + USB_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + + return UhciGetRootHubPortStatus (&Uhc->UsbHc, PortNumber, PortStatus); +} + + +/** + Sets a feature for the specified root hub port according to UEFI 2.0 spec. + + @param This A pointer to the EFI_USB2_HC_PROTOCOL. + @param PortNumber Specifies the root hub port whose feature is + requested to be set. + @param PortFeature Indicates the feature selector associated with the + feature set request. + + @return EFI_SUCCESS : PortFeature was set for the root port + @return EFI_INVALID_PARAMETER : PortNumber is invalid or PortFeature is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2SetRootHubPortFeature ( + IN EFI_USB2_HC_PROTOCOL *This, + IN UINT8 PortNumber, + IN EFI_USB_PORT_FEATURE PortFeature + ) +{ + USB_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + + return UhciSetRootHubPortFeature (&Uhc->UsbHc, PortNumber, PortFeature); +} + + +/** + Clears a feature for the specified root hub port according to Uefi 2.0 spec. + + @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. + + @return EFI_SUCCESS : PortFeature was cleared for the USB root hub port + @return EFI_INVALID_PARAMETER : PortNumber is invalid or PortFeature is invalid. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2ClearRootHubPortFeature ( + IN EFI_USB2_HC_PROTOCOL *This, + IN UINT8 PortNumber, + IN EFI_USB_PORT_FEATURE PortFeature + ) +{ + USB_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + + return UhciClearRootHubPortFeature (&Uhc->UsbHc, PortNumber, PortFeature); +} + + +/** + Submits control transfer to a target USB device accroding to UEFI 2.0 spec.. + + This : A pointer to the EFI_USB2_HC_PROTOCOL instance. + DeviceAddress : Target device address + DeviceSpeed : Device speed + MaximumPacketLength : Maximum packet size of the target endpoint + Request : USB device request to send + TransferDirection : Data direction of the Data stage in control transfer + Data : Data to transmit/receive in data stage + DataLength : Length of the data + TimeOut : Maximum time, in microseconds, for transfer to complete. + TransferResult : Variable to receive the transfer result + + @return EFI_SUCCESS : The control transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : Failed due to lack of resource. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Failed due to timeout. + @return EFI_DEVICE_ERROR : Failed due to host controller or device error. + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2ControlTransfer ( + 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_HC_DEV *Uhc; + BOOLEAN IsSlow; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + IsSlow = (EFI_USB_SPEED_LOW == DeviceSpeed) ? TRUE : FALSE; + + return UhciControlTransfer ( + &Uhc->UsbHc, + DeviceAddress, + IsSlow, + (UINT8) MaximumPacketLength, + Request, + TransferDirection, + Data, + DataLength, + TimeOut, + TransferResult + ); +} + + +/** + Submits bulk transfer to a bulk endpoint of a USB device + + This : A pointer to the EFI_USB2_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : Endpoint number and direction + DeviceSpeed : Device speed + MaximumPacketLength : Maximum packet size of the target endpoint + DataBuffersNumber : Number of data buffers prepared for the transfer. + Data : Array of pointers to the buffers of data + DataLength : On input, size of the data buffer, On output, + actually transferred data size. + DataToggle : On input, data toggle to use; On output, next data toggle + Translator : A pointr to the transaction translator data. + TimeOut : Maximum time out, in microseconds + TransferResult : Variable to receive transfer result + + @return EFI_SUCCESS : The bulk transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : Failed due to lack of resource. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Failed due to timeout. + @return EFI_DEVICE_ERROR : Failed due to host controller or device error. + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2BulkTransfer ( + 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_HC_DEV *Uhc; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + + if (Data == NULL || DeviceSpeed == EFI_USB_SPEED_LOW) { + return EFI_INVALID_PARAMETER; + } + + // + // For full-speed bulk transfers only the data pointed by Data[0] shall be used + // + return UhciBulkTransfer ( + &Uhc->UsbHc, + DeviceAddress, + EndPointAddress, + (UINT8) MaximumPacketLength, + *Data, + DataLength, + DataToggle, + TimeOut, + TransferResult + ); +} + + +/** + Submits an asynchronous interrupt transfer to an + interrupt endpoint of a USB device according to UEFI 2.0 spec. + + This : A pointer to the EFI_USB2_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : Endpoint number and direction + DeviceSpeed : Device speed + MaximumPacketLength : Maximum packet size of the target endpoint + IsNewTransfer : If TRUE, submit a new transfer, if FALSE cancel old transfer + DataToggle : On input, data toggle to use; On output, next data toggle + PollingInterval : Interrupt poll rate in milliseconds + DataLength : On input, size of the data buffer, On output, + actually transferred data size. + Translator : A pointr to the transaction translator data. + CallBackFunction : Function to call periodically + Context : User context + + @return EFI_SUCCESS : Transfer was submitted + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_OUT_OF_RESOURCES : Failed due to a lack of resources. + @return EFI_DEVICE_ERROR : Can't read register + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2AsyncInterruptTransfer ( + 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 + ) +{ + USB_HC_DEV *Uhc; + BOOLEAN IsSlow; + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + IsSlow = (EFI_USB_SPEED_LOW == DeviceSpeed) ? TRUE : FALSE; + + return UhciAsyncInterruptTransfer ( + &Uhc->UsbHc, + DeviceAddress, + EndPointAddress, + IsSlow, + (UINT8) MaximumPacketLength, + IsNewTransfer, + DataToggle, + PollingInterval, + DataLength, + CallBackFunction, + Context + ); +} + + +/** + Submits synchronous interrupt transfer to an interrupt endpoint + of a USB device according to UEFI 2.0 spec. + + This : A pointer to the EFI_USB2_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : Endpoint number and direction + DeviceSpeed : Device speed + MaximumPacketLength : Maximum packet size of the target endpoint + DataBuffersNumber : Number of data buffers prepared for the transfer. + Data : Array of pointers to the buffers of data + DataLength : On input, size of the data buffer, On output, + actually transferred data size. + DataToggle : On input, data toggle to use; On output, next data toggle + TimeOut : Maximum time out, in microseconds + Translator : A pointr to the transaction translator data. + TransferResult : Variable to receive transfer result + + @return EFI_SUCCESS : The transfer was completed successfully. + @return EFI_OUT_OF_RESOURCES : Failed due to lack of resource. + @return EFI_INVALID_PARAMETER : Some parameters are invalid. + @return EFI_TIMEOUT : Failed due to timeout. + @return EFI_DEVICE_ERROR : Failed due to host controller or device error. + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2SyncInterruptTransfer ( + 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_HC_DEV *Uhc; + BOOLEAN IsSlow; + + if (DeviceSpeed == EFI_USB_SPEED_HIGH) { + return EFI_INVALID_PARAMETER; + } + + Uhc = UHC_FROM_USB2_HC_PROTO (This); + IsSlow = (EFI_USB_SPEED_LOW == DeviceSpeed) ? TRUE : FALSE; + + return UhciSyncInterruptTransfer ( + &Uhc->UsbHc, + DeviceAddress, + EndPointAddress, + IsSlow, + (UINT8) MaximumPacketLength, + Data, + DataLength, + DataToggle, + TimeOut, + TransferResult + ); +} + + +/** + Submits isochronous transfer to a target USB device according to UEFI 2.0 spec. + + This : A pointer to the EFI_USB2_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : Endpoint number and direction + DeviceSpeed : Device speed + MaximumPacketLength : Maximum packet size of the target endpoint + DataBuffersNumber : Number of data buffers prepared for the transfer. + Data : Array of pointers to the buffers of data + DataLength : On input, size of the data buffer, On output, + actually transferred data size. + Translator : A pointr to the transaction translator data. + TransferResult : Variable to receive transfer result + + @return EFI_UNSUPPORTED + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2IsochronousTransfer ( + 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 according to UEFI 2.0 spec. + + This : A pointer to the EFI_USB2_HC_PROTOCOL instance. + DeviceAddress : Target device address + EndPointAddress : Endpoint number and direction + DeviceSpeed : Device speed + MaximumPacketLength : Maximum packet size of the target endpoint + DataBuffersNumber : Number of data buffers prepared for the transfer. + Data : Array of pointers to the buffers of data + Translator : A pointr to the transaction translator data. + IsochronousCallBack : Function to call when the transfer complete + Context : Pass to the call back function as parameter + + @return EFI_UNSUPPORTED + +**/ +STATIC +EFI_STATUS +EFIAPI +Uhci2AsyncIsochronousTransfer ( + 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; +} + +//@MT: EFI_DRIVER_ENTRY_POINT (UhciDriverEntryPoint) + +EFI_STATUS +EFIAPI +UhciDriverEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +/*++ + + Routine Description: + + Entry point for EFI drivers. + + Arguments: + + ImageHandle - EFI_HANDLE + SystemTable - EFI_SYSTEM_TABLE + + Returns: + + EFI_SUCCESS : Driver is successfully loaded + Others : Failed + +--*/ +{ + return EfiLibInstallAllDriverProtocols ( + ImageHandle, + SystemTable, + &gUhciDriverBinding, + ImageHandle, + &gUhciComponentName, + NULL, + NULL + ); +} + + +/** + Test to see if this driver supports ControllerHandle. Any + ControllerHandle that has UsbHcProtocol 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 +UhciDriverBindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE Controller, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + EFI_STATUS OpenStatus; + EFI_STATUS Status; + EFI_PCI_IO_PROTOCOL *PciIo; + USB_CLASSC UsbClassCReg; + + // + // Test whether there is PCI IO Protocol attached on the controller handle. + // + OpenStatus = gBS->OpenProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + &PciIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + + if (EFI_ERROR (OpenStatus)) { + return OpenStatus; + } + + Status = PciIo->Pci.Read ( + PciIo, + EfiPciIoWidthUint8, + CLASSC_OFFSET, + sizeof (USB_CLASSC) / sizeof (UINT8), + &UsbClassCReg + ); + + if (EFI_ERROR (Status)) { + Status = EFI_UNSUPPORTED; + goto ON_EXIT; + } + + // + // Test whether the controller belongs to UHCI type + // + if ((UsbClassCReg.BaseCode != PCI_CLASS_SERIAL) || + (UsbClassCReg.SubClassCode != PCI_CLASS_SERIAL_USB) || + (UsbClassCReg.PI != PCI_CLASSC_PI_UHCI) + ) { + + Status = EFI_UNSUPPORTED; + } + +ON_EXIT: + gBS->CloseProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + return Status; + +} + + +/** + Allocate and initialize the empty UHCI device + + @param PciIo The PCIIO to use + + @return Allocated UHCI device + +**/ +STATIC +USB_HC_DEV * +UhciAllocateDev ( + IN EFI_PCI_IO_PROTOCOL *PciIo + ) +{ + USB_HC_DEV *Uhc; + EFI_STATUS Status; + + Uhc = AllocateZeroPool (sizeof (USB_HC_DEV)); + + if (Uhc == NULL) { + return NULL; + } + + // + // This driver supports both USB_HC_PROTOCOL and USB2_HC_PROTOCOL. + // USB_HC_PROTOCOL is for EFI 1.1 backward compability. + // + Uhc->Signature = USB_HC_DEV_SIGNATURE; + Uhc->UsbHc.Reset = UhciReset; + Uhc->UsbHc.GetState = UhciGetState; + Uhc->UsbHc.SetState = UhciSetState; + Uhc->UsbHc.ControlTransfer = UhciControlTransfer; + Uhc->UsbHc.BulkTransfer = UhciBulkTransfer; + Uhc->UsbHc.AsyncInterruptTransfer = UhciAsyncInterruptTransfer; + Uhc->UsbHc.SyncInterruptTransfer = UhciSyncInterruptTransfer; + Uhc->UsbHc.IsochronousTransfer = UhciIsochronousTransfer; + Uhc->UsbHc.AsyncIsochronousTransfer = UhciAsyncIsochronousTransfer; + Uhc->UsbHc.GetRootHubPortNumber = UhciGetRootHubPortNumber; + Uhc->UsbHc.GetRootHubPortStatus = UhciGetRootHubPortStatus; + Uhc->UsbHc.SetRootHubPortFeature = UhciSetRootHubPortFeature; + Uhc->UsbHc.ClearRootHubPortFeature = UhciClearRootHubPortFeature; + Uhc->UsbHc.MajorRevision = 0x1; + Uhc->UsbHc.MinorRevision = 0x1; + + Uhc->Usb2Hc.GetCapability = Uhci2GetCapability; + Uhc->Usb2Hc.Reset = Uhci2Reset; + Uhc->Usb2Hc.GetState = Uhci2GetState; + Uhc->Usb2Hc.SetState = Uhci2SetState; + Uhc->Usb2Hc.ControlTransfer = Uhci2ControlTransfer; + Uhc->Usb2Hc.BulkTransfer = Uhci2BulkTransfer; + Uhc->Usb2Hc.AsyncInterruptTransfer = Uhci2AsyncInterruptTransfer; + Uhc->Usb2Hc.SyncInterruptTransfer = Uhci2SyncInterruptTransfer; + Uhc->Usb2Hc.IsochronousTransfer = Uhci2IsochronousTransfer; + Uhc->Usb2Hc.AsyncIsochronousTransfer = Uhci2AsyncIsochronousTransfer; + Uhc->Usb2Hc.GetRootHubPortStatus = Uhci2GetRootHubPortStatus; + Uhc->Usb2Hc.SetRootHubPortFeature = Uhci2SetRootHubPortFeature; + Uhc->Usb2Hc.ClearRootHubPortFeature = Uhci2ClearRootHubPortFeature; + Uhc->Usb2Hc.MajorRevision = 0x1; + Uhc->Usb2Hc.MinorRevision = 0x1; + + Uhc->PciIo = PciIo; + Uhc->MemPool = UsbHcInitMemPool (PciIo, TRUE, 0); + + if (Uhc->MemPool == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_ERROR; + } + + InitializeListHead (&Uhc->AsyncIntList); + + Status = gBS->CreateEvent ( + EVT_TIMER | EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + UhciMonitorAsyncReqList, + Uhc, + &Uhc->AsyncIntMonitor + ); + + if (EFI_ERROR (Status)) { + UsbHcFreeMemPool (Uhc->MemPool); + goto ON_ERROR; + } + + return Uhc; + +ON_ERROR: + gBS->FreePool (Uhc); + return NULL; +} + + +/** + Free the UHCI device and release its associated resources + + @param Uhc The UHCI device to release + + @return None + +**/ +STATIC +VOID +UhciFreeDev ( + IN USB_HC_DEV *Uhc + ) +{ + if (Uhc->AsyncIntMonitor != NULL) { + gBS->CloseEvent (Uhc->AsyncIntMonitor); + } + + if (Uhc->MemPool != NULL) { + UsbHcFreeMemPool (Uhc->MemPool); + } + + if (Uhc->CtrlNameTable) { + FreeUnicodeStringTable (Uhc->CtrlNameTable); + } + + gBS->FreePool (Uhc); +} + + +/** + Uninstall all Uhci Interface + + @param Controller Controller handle + @param This Protocol instance pointer. + + @return VOID + +**/ +STATIC +VOID +UhciCleanDevUp ( + IN EFI_HANDLE Controller, + IN EFI_USB_HC_PROTOCOL *This + ) +{ + USB_HC_DEV *Uhc; + + // + // Uninstall the USB_HC and USB_HC2 protocol, then disable the controller + // + Uhc = UHC_FROM_USB_HC_PROTO (This); + UhciStopHc (Uhc, STALL_1_SECOND); + + gBS->UninstallProtocolInterface ( + Controller, + &gEfiUsbHcProtocolGuid, + &Uhc->UsbHc + ); + + gBS->UninstallProtocolInterface ( + Controller, + &gEfiUsb2HcProtocolGuid, + &Uhc->Usb2Hc + ); + + UhciFreeAllAsyncReq (Uhc); + UhciDestoryFrameList (Uhc); + + Uhc->PciIo->Attributes ( + Uhc->PciIo, + EfiPciIoAttributeOperationDisable, + EFI_PCI_DEVICE_ENABLE, + NULL + ); + + UhciFreeDev (Uhc); +} + + +/** + Starting the Usb UHCI Driver + + @param This Protocol instance pointer. + @param Controller Handle of device to test + @param RemainingDevicePath Not used + + @retval EFI_SUCCESS This driver supports this device. + @retval EFI_UNSUPPORTED This driver does not support this device. + @retval EFI_DEVICE_ERROR This driver cannot be started due to device Error + EFI_OUT_OF_RESOURCES- Failed due to resource + shortage + +**/ +EFI_STATUS +EFIAPI +UhciDriverBindingStart ( + 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_HC_DEV *Uhc; + + // + // Open PCIIO, then enable the EHC device and turn off emulation + // + Uhc = NULL; + Status = gBS->OpenProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + &PciIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + UhciTurnOffUsbEmulation (PciIo); + + Status = PciIo->Attributes ( + PciIo, + EfiPciIoAttributeOperationEnable, + EFI_PCI_DEVICE_ENABLE, + NULL + ); + + if (EFI_ERROR (Status)) { + goto CLOSE_PCIIO; + } + + Uhc = UhciAllocateDev (PciIo); + + if (Uhc == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto CLOSE_PCIIO; + } + + // + // Allocate and Init Host Controller's Frame List Entry + // + Status = UhciInitFrameList (Uhc); + + if (EFI_ERROR (Status)) { + Status = EFI_OUT_OF_RESOURCES; + goto FREE_UHC; + } + + Status = gBS->SetTimer ( + Uhc->AsyncIntMonitor, + TimerPeriodic, + INTERRUPT_POLLING_TIME + ); + + if (EFI_ERROR (Status)) { + goto FREE_UHC; + } + + // + // Install both USB_HC_PROTOCOL and USB2_HC_PROTOCOL + // + Status = gBS->InstallMultipleProtocolInterfaces ( + &Controller, + &gEfiUsbHcProtocolGuid, + &Uhc->UsbHc, + &gEfiUsb2HcProtocolGuid, + &Uhc->Usb2Hc, + NULL + ); + + if (EFI_ERROR (Status)) { + goto FREE_UHC; + } + + // + // Install the component name protocol + // + Uhc->CtrlNameTable = NULL; + + AddUnicodeString ( + "eng", + gUhciComponentName.SupportedLanguages, + &Uhc->CtrlNameTable, + L"Usb Universal Host Controller" + ); + + // + // Start the UHCI hardware, also set its reclamation point to 64 bytes + // + UhciWriteReg (Uhc->PciIo, USBCMD_OFFSET, USBCMD_RS | USBCMD_MAXP); + + return EFI_SUCCESS; + +FREE_UHC: + UhciFreeDev (Uhc); + +CLOSE_PCIIO: + gBS->CloseProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + return Status; +} + + +/** + Stop this driver on ControllerHandle. Support stoping any child handles + created by this driver. + + @param This Protocol instance pointer. + @param Controller Handle of device to stop driver on + @param NumberOfChildren Number of Children in the ChildHandleBuffer + @param ChildHandleBuffer List of handles for the children we need to stop. + + @return EFI_SUCCESS + @return others + +**/ +EFI_STATUS +EFIAPI +UhciDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE Controller, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + EFI_USB_HC_PROTOCOL *UsbHc; + EFI_USB2_HC_PROTOCOL *Usb2Hc; + EFI_STATUS Status; + + Status = gBS->OpenProtocol ( + Controller, + &gEfiUsbHcProtocolGuid, + &UsbHc, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + // + // Test whether the Controller handler passed in is a valid + // Usb controller handle that should be supported, if not, + // return the error status directly + // + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->OpenProtocol ( + Controller, + &gEfiUsb2HcProtocolGuid, + &Usb2Hc, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + + // + // Test whether the Controller handler passed in is a valid + // Usb controller handle that should be supported, if not, + // return the error status directly + // + if (EFI_ERROR (Status)) { + return Status; + } + + UhciCleanDevUp (Controller, UsbHc); + + gBS->CloseProtocol ( + Controller, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + return EFI_SUCCESS; +} + +EFI_DRIVER_BINDING_PROTOCOL gUhciDriverBinding = { + UhciDriverBindingSupported, + UhciDriverBindingStart, + UhciDriverBindingStop, + 0x20, + NULL, + NULL +}; diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.h b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.h new file mode 100644 index 0000000000..c37894d3a8 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.h @@ -0,0 +1,140 @@ +/** @file + +Copyright (c) 2004 - 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + Uhci.h + +Abstract: + + The definition for UHCI driver model and HC protocol routines. + +Revision History + + +**/ + +#ifndef _UHCI_H +#define _UHCI_H + +// +// The package level header files this module uses +// +#include +// +// The protocols, PPI and GUID defintions for this module +// +#include +#include +#include +// +// The Library classes this module consumes +// +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef struct _USB_HC_DEV USB_HC_DEV; + +#include "UsbHcMem.h" +#include "UhciQueue.h" +#include "UhciReg.h" +#include "UhciSched.h" +#include "UhciDebug.h" + +enum { + // + // Stall times + // + STALL_1_MS = 1000, + STALL_1_SECOND = 1000 *STALL_1_MS, + + UHC_SYN_POLL = 50, + FORCE_GLOBAL_RESUME_TIME = 20 *STALL_1_MS, + ROOT_PORT_REST_TIME = 50 *STALL_1_MS, + PORT_RESET_RECOVERY_TIME = 10 *STALL_1_MS, + INTERRUPT_POLLING_TIME = 50 * 10000UL, + + // + // UHC raises TPL to TPL_NOTIFY to serialize all its operations + // to protect shared data structures. + // + UHCI_TPL = TPL_NOTIFY, + + USB_HC_DEV_SIGNATURE = EFI_SIGNATURE_32 ('u', 'h', 'c', 'i'), +}; + +#pragma pack(1) +typedef struct { + UINT8 PI; + UINT8 SubClassCode; + UINT8 BaseCode; +} USB_CLASSC; +#pragma pack() + +#define UHC_FROM_USB_HC_PROTO(This) CR(This, USB_HC_DEV, UsbHc, USB_HC_DEV_SIGNATURE) +#define UHC_FROM_USB2_HC_PROTO(This) CR(This, USB_HC_DEV, Usb2Hc, USB_HC_DEV_SIGNATURE) + +// +// USB_HC_DEV support the UHCI hardware controller. It schedules +// the asynchronous interrupt transfer with the same method as +// EHCI: a reversed tree structure. For synchronous interrupt, +// control and bulk transfer, it uses three static queue head to +// schedule them. SyncIntQh is for interrupt transfer. LsCtrlQh is +// for LOW speed control transfer, and FsCtrlBulkQh is for FULL +// speed control or bulk transfer. This is because FULL speed contrl +// or bulk transfer can reclaim the unused bandwidth. Some USB +// device requires this bandwidth reclamation capability. +// +typedef struct _USB_HC_DEV { + UINT32 Signature; + EFI_USB_HC_PROTOCOL UsbHc; + EFI_USB2_HC_PROTOCOL Usb2Hc; + EFI_PCI_IO_PROTOCOL *PciIo; + + // + // Schedule data structures + // + UINT32 *FrameBase; + UHCI_QH_SW *SyncIntQh; + UHCI_QH_SW *CtrlQh; + UHCI_QH_SW *BulkQh; + + // + // Structures to maintain asynchronus interrupt transfers. + // When asynchronous interrutp transfer is unlinked from + // the frame list, the hardware may still hold a pointer + // to it. To synchronize with hardware, its resoureces are + // released in two steps using Recycle and RecycleWait. + // Check the asynchronous interrupt management routines. + // + LIST_ENTRY AsyncIntList; + EFI_EVENT AsyncIntMonitor; + UHCI_ASYNC_REQUEST *Recycle; + UHCI_ASYNC_REQUEST *RecycleWait; + + + UINTN RootPorts; + USBHC_MEM_POOL *MemPool; + EFI_UNICODE_STRING_TABLE *CtrlNameTable; + VOID *FrameMapping; +} USB_HC_DEV; + +extern EFI_DRIVER_BINDING_PROTOCOL gUhciDriverBinding; +extern EFI_COMPONENT_NAME_PROTOCOL gUhciComponentName; + +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.inf b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.inf new file mode 100644 index 0000000000..34d7b74fd0 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.inf @@ -0,0 +1,101 @@ +#/** @file +# Component name for module Uhci +# +# Copyright (c) 2006, Intel Corporation. All right reserved. +# +# All rights reserved. This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# +# +#**/ + +################################################################################ +# +# Defines Section - statements that will be processed to create a Makefile. +# +################################################################################ +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = Uhci + FILE_GUID = 2FB92EFA-2EE0-4bae-9EB6-7464125E1EF7 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + EDK_RELEASE_VERSION = 0x00020000 + EFI_SPECIFICATION_VERSION = 0x00020000 + + ENTRY_POINT = UhciDriverEntryPoint + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 IPF EBC +# + +################################################################################ +# +# Sources Section - list of files that are required for the build to succeed. +# +################################################################################ + +[Sources.common] + UhciSched.c + UhciDebug.c + UsbHcMem.h + UhciDebug.h + UhciQueue.c + UhciReg.c + UsbHcMem.c + UhciQueue.h + Uhci.c + Uhci.h + UhciReg.h + UhciSched.h + ComponentName.c + + +################################################################################ +# +# Package Dependency Section - list of Package files that are required for +# this module. +# +################################################################################ + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + + +################################################################################ +# +# Library Class Section - list of Library Classes that are required for +# this module. +# +################################################################################ + +[LibraryClasses] + MemoryAllocationLib + BaseLib + UefiLib + UefiBootServicesTableLib + UefiDriverEntryPoint + BaseMemoryLib + DebugLib + + +################################################################################ +# +# Protocol C Name Section - list of Protocol and Protocol Notify C Names +# that this module uses or produces. +# +################################################################################ + +[Protocols] + gEfiPciIoProtocolGuid # PROTOCOL ALWAYS_CONSUMED + gEfiUsbHcProtocolGuid # PROTOCOL ALWAYS_CONSUMED + gEfiUsb2HcProtocolGuid # PROTOCOL ALWAYS_CONSUMED + diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.msa b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.msa new file mode 100644 index 0000000000..75ebf35ba1 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/Uhci.msa @@ -0,0 +1,84 @@ + + + Uhci + DXE_DRIVER + 2FB92EFA-2EE0-4bae-9EB6-7464125E1EF7 + 1.0 + Component name for module Uhci + FIX ME! + Copyright (c) 2006, Intel Corporation. All right reserved. + All rights reserved. This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + FRAMEWORK_BUILD_PACKAGING_SPECIFICATION 0x00000052 + + + IA32 X64 IPF EBC + false + Uhci + + + + DebugLib + + + BaseMemoryLib + + + UefiDriverEntryPoint + + + UefiBootServicesTableLib + + + UefiLib + + + BaseLib + + + MemoryAllocationLib + + + + ComponentName.c + uhci.h + UhciSched.h + UhciReg.h + uhci.c + UhciQueue.h + UsbHcMem.c + UhciReg.c + UhciQueue.c + UhciDebug.h + UsbHcMem.h + UhciDebug.c + UhciSched.c + + + + + + + + gEfiUsb2HcProtocolGuid + + + gEfiUsbHcProtocolGuid + + + gEfiPciIoProtocolGuid + + + + EFI_SPECIFICATION_VERSION 0x00020000 + EDK_RELEASE_VERSION 0x00020000 + + UhciDriverEntryPoint + + + \ No newline at end of file diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciDebug.c b/MdeModulePkg/Bus/Pci/UhciDxe/UhciDebug.c new file mode 100644 index 0000000000..d47314ffbf --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciDebug.c @@ -0,0 +1,183 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciDebug.c + +Abstract: + + This file provides the information dump support for Uhci when in debug mode. + You can dynamically adjust the debug level by changing variable gEHCDebugLevel + and gEHCErrorLevel. + +Revision History + + +**/ + +#include "Uhci.h" +#include "UhciDebug.h" + +#ifdef EFI_DEBUG + +UINTN mUhciDebugMask = USB_DEBUG_FORCE_OUTPUT; + + +/** + Debug debug print interface for UHCI + + @param Format String to use for the print, followed by print arguments + + @return None + +**/ +VOID +UhciDebug ( + IN CHAR8 *Format, + ... + ) +{ + VA_LIST Marker; + + VA_START (Marker, Format); + DebugVPrint (DEBUG_INFO, Format, Marker); + VA_END (Marker); +} + + +/** + Debug error print interface for UHCI + + @param Format String to use for the print, followed by print arguments + + @return None + +**/ +VOID +UhciError ( + IN CHAR8 *Format, + ... + ) +{ + VA_LIST Marker; + + VA_START (Marker, Format); + DebugVPrint (DEBUG_ERROR, Format, Marker); + VA_END (Marker); +} + + + +/** + Debug print interface for UHCI + + @param Level Level to control debug print + @param Format String to use for the print, followed by print arguments + + @return None + +**/ +VOID +UhciDebugPrint ( + IN UINTN Level, + IN CHAR8 *Format, + ... + ) +{ + VA_LIST Marker; + + VA_START (Marker, Format); + + if (Level & mUhciDebugMask) { + if (mUhciDebugMask & USB_DEBUG_FORCE_OUTPUT) { + DebugVPrint (DEBUG_ERROR, Format, Marker); + } else { + DebugVPrint (DEBUG_INFO, Format, Marker); + } + } + + VA_END (Marker); +} + + +/** + Dump the content of QH structure + + @param QhSw Pointer to software QH structure + + @return None + +**/ +VOID +UhciDumpQh ( + IN UHCI_QH_SW *QhSw + ) +{ + UINTN Level; + + Level = UHCI_DEBUG_QH; + + UhciDebugPrint (Level, "&QhSw @ 0x%x\n", QhSw); + UhciDebugPrint (Level, "QhSw.NextQh - 0x%x\n", QhSw->NextQh); + UhciDebugPrint (Level, "QhSw.TDs - 0x%x\n", QhSw->TDs); + UhciDebugPrint (Level, "QhSw.QhHw:\n"); + UhciDebugPrint (Level, " Horizon Link - %x\n", QhSw->QhHw.HorizonLink); + UhciDebugPrint (Level, " Vertical Link - %x\n\n", QhSw->QhHw.VerticalLink); +} + + +/** + Dump the content of TD structure. + + @param TdSw Pointer to software TD structure + @param IsCur Whether dump the whole list, or only dump the current TD + + @return None + +**/ +VOID +UhciDumpTds ( + IN UHCI_TD_SW *TdSw + ) +{ + UHCI_TD_SW *CurTdSw; + UINTN Level; + + Level = UHCI_DEBUG_TD; + CurTdSw = TdSw; + + while (CurTdSw != NULL) { + UhciDebugPrint (Level, "TdSw @ 0x%x\n", CurTdSw); + UhciDebugPrint (Level, "TdSw.NextTd - 0x%x\n", CurTdSw->NextTd); + UhciDebugPrint (Level, "TdSw.DataLen - %d\n", CurTdSw->DataLen); + UhciDebugPrint (Level, "TdSw.Data - 0x%x\n", CurTdSw->Data); + UhciDebugPrint (Level, "TdHw:\n"); + UhciDebugPrint (Level, " NextLink - 0x%x\n", CurTdSw->TdHw.NextLink); + UhciDebugPrint (Level, " ActualLen - %d\n", CurTdSw->TdHw.ActualLen); + UhciDebugPrint (Level, " Status - 0x%x\n", CurTdSw->TdHw.Status); + UhciDebugPrint (Level, " IOC - %d\n", CurTdSw->TdHw.IntOnCpl); + UhciDebugPrint (Level, " IsIsoCh - %d\n", CurTdSw->TdHw.IsIsoch); + UhciDebugPrint (Level, " LowSpeed - %d\n", CurTdSw->TdHw.LowSpeed); + UhciDebugPrint (Level, " ErrorCount - %d\n", CurTdSw->TdHw.ErrorCount); + UhciDebugPrint (Level, " ShortPacket - %d\n", CurTdSw->TdHw.ShortPacket); + UhciDebugPrint (Level, " PidCode - 0x%x\n", CurTdSw->TdHw.PidCode); + UhciDebugPrint (Level, " DevAddr - %d\n", CurTdSw->TdHw.DeviceAddr); + UhciDebugPrint (Level, " EndPoint - %d\n", CurTdSw->TdHw.EndPoint); + UhciDebugPrint (Level, " DataToggle - %d\n", CurTdSw->TdHw.DataToggle); + UhciDebugPrint (Level, " MaxPacketLen - %d\n", CurTdSw->TdHw.MaxPacketLen); + UhciDebugPrint (Level, " DataBuffer - 0x%x\n\n",CurTdSw->TdHw.DataBuffer); + + CurTdSw = CurTdSw->NextTd; + } +} + +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciDebug.h b/MdeModulePkg/Bus/Pci/UhciDxe/UhciDebug.h new file mode 100644 index 0000000000..d71791a414 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciDebug.h @@ -0,0 +1,134 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciDebug.h + +Abstract: + + This file contains the definination for host controller debug support routines + +Revision History + + +**/ + +#ifndef _EFI_UHCI_DEBUG_H_ +#define _EFI_UHCI_DEBUG_H_ + +// +// DEBUG support +// +#define USB_DEBUG_FORCE_OUTPUT (UINTN) (1 << 0) +#define UHCI_DEBUG_QH (UINTN) (1 << 2) +#define UHCI_DEBUG_TD (UINTN) (1 << 3) + +VOID +UhciDebugPrint ( + IN UINTN Level, + IN CHAR8 *Format, + ... + ) +/*++ + +Routine Description: + + Debug print interface for UHCI + +Arguments: + + Level - Level to control debug print + Format - String to use for the print, followed by print arguments + +Returns: + + None + +--*/ +; + + +/** + Debug print interface for UHCI + + @param Format String to use for the print, followed by print arguments + + @return None + +**/ +VOID +UhciDebug ( + IN CHAR8 *Format, + ... + ) +; + + +/** + Debug error print interface for UHCI + + @param Format String to use for the print, followed by print arguments + + @return None + +**/ +VOID +UhciError ( + IN CHAR8 *Format, + ... + ) +; + + +/** + Dump the content of QH structure + + @param QhSw Pointer to software QH structure + + @return None + +**/ +VOID +UhciDumpQh ( + IN UHCI_QH_SW *QhSw + ) +; + + +/** + Dump the content of TD structure. + + @param TdSw Pointer to software TD structure + + @return None + +**/ +VOID +UhciDumpTds ( + IN UHCI_TD_SW *TdSw + ) +; + + +#ifdef EFI_DEBUG + #define UHCI_DEBUG(arg) UhciDebug arg + #define UHCI_ERROR(arg) UhciError arg + #define UHCI_DUMP_TDS(arg) UhciDumpTds arg + #define UHCI_DUMP_QH(arg) UhciDumpQh arg +#else + #define UHCI_DEBUG(arg) + #define UHCI_ERROR(arg) + #define UHCI_DUMP_TDS(arg) + #define UHCI_DUMP_QH(arg) +#endif + +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciQueue.c b/MdeModulePkg/Bus/Pci/UhciDxe/UhciQueue.c new file mode 100644 index 0000000000..843c9e38aa --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciQueue.c @@ -0,0 +1,703 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciQueue.c + +Abstract: + + The UHCI register operation routines. + +Revision History + + +**/ + +#include "Uhci.h" + + +/** + Map address of request structure buffer + + @param Uhc The UHCI device + @param Request The user request buffer + @param MappedAddr Mapped address of request + @param Map Identificaion of this mapping to return + + @return EFI_SUCCESS : Success + @return EFI_DEVICE_ERROR : Fail to map the user request + +**/ +EFI_STATUS +UhciMapUserRequest ( + IN USB_HC_DEV *Uhc, + IN OUT VOID *Request, + OUT UINT8 **MappedAddr, + OUT VOID **Map + ) +{ + EFI_STATUS Status; + UINTN Len; + EFI_PHYSICAL_ADDRESS PhyAddr; + + Len = sizeof (EFI_USB_DEVICE_REQUEST); + Status = Uhc->PciIo->Map ( + Uhc->PciIo, + EfiPciIoOperationBusMasterRead, + Request, + &Len, + &PhyAddr, + Map + ); + + if (!EFI_ERROR (Status)) { + *MappedAddr = (UINT8 *) (UINTN) PhyAddr; + } + + return Status; +} + + +/** + Map address of user data buffer + + @param Uhc The UHCI device + @param Direction direction of the data transfer + @param Data The user data buffer + @param Len Length of the user data + @param PktId Packet identificaion + @param MappedAddr mapped address to return + @param Map identificaion of this mapping to return + + @return EFI_SUCCESS : Success + @return EFI_DEVICE_ERROR : Fail to map the user data + +**/ +EFI_STATUS +UhciMapUserData ( + IN USB_HC_DEV *Uhc, + IN EFI_USB_DATA_DIRECTION Direction, + IN VOID *Data, + IN OUT UINTN *Len, + OUT UINT8 *PktId, + OUT UINT8 **MappedAddr, + OUT VOID **Map + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS PhyAddr; + + Status = EFI_SUCCESS; + + switch (Direction) { + case EfiUsbDataIn: + // + // BusMasterWrite means cpu read + // + *PktId = INPUT_PACKET_ID; + Status = Uhc->PciIo->Map ( + Uhc->PciIo, + EfiPciIoOperationBusMasterWrite, + Data, + Len, + &PhyAddr, + Map + ); + + if (EFI_ERROR (Status)) { + goto EXIT; + } + + *MappedAddr = (UINT8 *) (UINTN) PhyAddr; + break; + + case EfiUsbDataOut: + *PktId = OUTPUT_PACKET_ID; + Status = Uhc->PciIo->Map ( + Uhc->PciIo, + EfiPciIoOperationBusMasterRead, + Data, + Len, + &PhyAddr, + Map + ); + + if (EFI_ERROR (Status)) { + goto EXIT; + } + + *MappedAddr = (UINT8 *) (UINTN) PhyAddr; + break; + + case EfiUsbNoData: + if ((Len != NULL) && (*Len != 0)) { + Status = EFI_INVALID_PARAMETER; + goto EXIT; + } + + *PktId = OUTPUT_PACKET_ID; + *Len = 0; + *MappedAddr = NULL; + *Map = NULL; + break; + + default: + Status = EFI_INVALID_PARAMETER; + } + +EXIT: + return Status; +} + + + +/** + Link the TD To QH + + @param Qh The queue head for the TD to link to + @param Td The TD to link + + @return VOID + +**/ +VOID +UhciLinkTdToQh ( + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *Td + ) +{ + ASSERT ((Qh != NULL) && (Td != NULL)); + + Qh->QhHw.VerticalLink = QH_VLINK (Td, FALSE); + Qh->TDs = (VOID *) Td; +} + + +/** + Unlink TD from the QH + + @param Qh The queue head to unlink from + @param Td The TD to unlink + + @return VOID + +**/ +VOID +UhciUnlinkTdFromQh ( + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *Td + ) +{ + ASSERT ((Qh != NULL) && (Td != NULL)); + + Qh->QhHw.VerticalLink = QH_VLINK (NULL, TRUE); + Qh->TDs = NULL; +} + + +/** + Append a new TD To the previous TD + + @param PrevTd Previous UHCI_TD_SW to be linked to + @param ThisTd TD to link + + @return VOID + +**/ +STATIC +VOID +UhciAppendTd ( + IN UHCI_TD_SW *PrevTd, + IN UHCI_TD_SW *ThisTd + ) +{ + ASSERT ((PrevTd != NULL) && (ThisTd != NULL)); + + PrevTd->TdHw.NextLink = TD_LINK (ThisTd, TRUE, FALSE); + PrevTd->NextTd = (VOID *) ThisTd; +} + + +/** + Delete a list of TDs + + @param Uhc The UHCI device + @param FirstTd TD link list head + + @return VOID + +**/ +VOID +UhciDestoryTds ( + IN USB_HC_DEV *Uhc, + IN UHCI_TD_SW *FirstTd + ) +{ + UHCI_TD_SW *NextTd; + UHCI_TD_SW *ThisTd; + + NextTd = FirstTd; + + while (NextTd != NULL) { + ThisTd = NextTd; + NextTd = ThisTd->NextTd; + UsbHcFreeMem (Uhc->MemPool, ThisTd, sizeof (UHCI_TD_SW)); + } +} + + +/** + Create an initialize a new queue head + + @param Uhc The UHCI device + @param Interval The polling interval for the queue + + @return The newly created queue header + +**/ +UHCI_QH_SW * +UhciCreateQh ( + IN USB_HC_DEV *Uhc, + IN UINTN Interval + ) +{ + UHCI_QH_SW *Qh; + + Qh = UsbHcAllocateMem (Uhc->MemPool, sizeof (UHCI_QH_SW)); + + if (Qh == NULL) { + return NULL; + } + + Qh->QhHw.HorizonLink = QH_HLINK (NULL, TRUE); + Qh->QhHw.VerticalLink = QH_VLINK (NULL, TRUE); + Qh->Interval = Interval; + Qh->TDs = NULL; + Qh->NextQh = NULL; + + return Qh; +} + + +/** + Create and intialize a TD + + @param Uhc The UHCI device + + @return The newly allocated and initialized TD + +**/ +STATIC +UHCI_TD_SW * +UhciCreateTd ( + IN USB_HC_DEV *Uhc + ) +{ + UHCI_TD_SW *Td; + + Td = UsbHcAllocateMem (Uhc->MemPool, sizeof (UHCI_TD_SW)); + if (Td == NULL) { + return NULL; + } + + Td->TdHw.NextLink = TD_LINK (NULL, FALSE, TRUE); + Td->NextTd = NULL; + Td->Data = NULL; + Td->DataLen = 0; + + return Td; +} + + +/** + Create and initialize a TD for Setup Stage of a control transfer + + @param Uhc The UHCI device + @param DevAddr Device address + @param Request Device request + @param IsLow Full speed or low speed + + @return The created setup Td Pointer + +**/ +STATIC +UHCI_TD_SW * +UhciCreateSetupTd ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 *Request, + IN BOOLEAN IsLow + ) +{ + UHCI_TD_SW *Td; + + Td = UhciCreateTd (Uhc); + + if (Td == NULL) { + return NULL; + } + + Td->TdHw.NextLink = TD_LINK (NULL, TRUE, TRUE); + Td->TdHw.ShortPacket = FALSE; + Td->TdHw.IsIsoch = FALSE; + Td->TdHw.IntOnCpl = FALSE; + Td->TdHw.ErrorCount = 0x03; + Td->TdHw.Status |= USBTD_ACTIVE; + Td->TdHw.DataToggle = 0; + Td->TdHw.EndPoint = 0; + Td->TdHw.LowSpeed = IsLow ? 1 : 0; + Td->TdHw.DeviceAddr = DevAddr & 0x7F; + Td->TdHw.MaxPacketLen = (UINT32) (sizeof (EFI_USB_DEVICE_REQUEST) - 1); + Td->TdHw.PidCode = SETUP_PACKET_ID; + Td->TdHw.DataBuffer = (UINT32) (UINTN) Request; + + Td->Data = Request; + Td->DataLen = sizeof (EFI_USB_DEVICE_REQUEST); + + return Td; +} + + +/** + Create a TD for data + + @param Uhc The UHCI device + @param DevAddr Device address + @param Endpoint Endpoint number + @param DataPtr Data buffer + @param Len Data length + @param PktId Packet ID + @param Toggle Data toggle value + @param IsLow Full speed or low speed + + @return Data Td pointer if success, otherwise NUL + +**/ +STATIC +UHCI_TD_SW * +UhciCreateDataTd ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 Endpoint, + IN UINT8 *DataPtr, + IN UINTN Len, + IN UINT8 PktId, + IN UINT8 Toggle, + IN BOOLEAN IsLow + ) +{ + UHCI_TD_SW *Td; + + // + // Code as length - 1, and the max valid length is 0x500 + // + ASSERT (Len <= 0x500); + + Td = UhciCreateTd (Uhc); + + if (Td == NULL) { + return NULL; + } + + Td->TdHw.NextLink = TD_LINK (NULL, TRUE, TRUE); + Td->TdHw.ShortPacket = FALSE; + Td->TdHw.IsIsoch = FALSE; + Td->TdHw.IntOnCpl = FALSE; + Td->TdHw.ErrorCount = 0X03; + Td->TdHw.Status = USBTD_ACTIVE; + Td->TdHw.LowSpeed = IsLow ? 1 : 0; + Td->TdHw.DataToggle = Toggle & 0x01; + Td->TdHw.EndPoint = Endpoint & 0x0F; + Td->TdHw.DeviceAddr = DevAddr & 0x7F; + Td->TdHw.MaxPacketLen = (UINT32) (Len - 1); + Td->TdHw.PidCode = (UINT8) PktId; + Td->TdHw.DataBuffer = (UINT32) (UINTN) DataPtr; + + Td->Data = DataPtr; + Td->DataLen = (UINT16) Len; + + return Td; +} + + +/** + Create TD for the Status Stage of control transfer + + @param Uhc The UHCI device + @param DevAddr Device address + @param PktId Packet ID + @param IsLow Full speed or low speed + + @return Status Td Pointer + +**/ +STATIC +UHCI_TD_SW * +UhciCreateStatusTd ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 PktId, + IN BOOLEAN IsLow + ) +{ + UHCI_TD_SW *Td; + + Td = UhciCreateTd (Uhc); + + if (Td == NULL) { + return NULL; + } + + Td->TdHw.NextLink = TD_LINK (NULL, TRUE, TRUE); + Td->TdHw.ShortPacket = FALSE; + Td->TdHw.IsIsoch = FALSE; + Td->TdHw.IntOnCpl = FALSE; + Td->TdHw.ErrorCount = 0x03; + Td->TdHw.Status |= USBTD_ACTIVE; + Td->TdHw.MaxPacketLen = 0x7FF; //0x7FF: there is no data (refer to UHCI spec) + Td->TdHw.DataToggle = 1; + Td->TdHw.EndPoint = 0; + Td->TdHw.LowSpeed = IsLow ? 1 : 0; + Td->TdHw.DeviceAddr = DevAddr & 0x7F; + Td->TdHw.PidCode = (UINT8) PktId; + Td->TdHw.DataBuffer = (UINT32) (UINTN) NULL; + + Td->Data = NULL; + Td->DataLen = 0; + + return Td; +} + + +/** + Create Tds list for Control Transfer + + @param Uhc The UHCI device + @param DeviceAddr The device address + @param DataPktId Packet Identification of Data Tds + @param Request A pointer to request structure buffer to transfer + @param Data A pointer to user data buffer to transfer + @param DataLen Length of user data to transfer + @param MaxPacket Maximum packet size for control transfer + @param IsLow Full speed or low speed + + @return The Td list head for the control transfer + +**/ +UHCI_TD_SW * +UhciCreateCtrlTds ( + IN USB_HC_DEV *Uhc, + IN UINT8 DeviceAddr, + IN UINT8 DataPktId, + IN UINT8 *Request, + IN UINT8 *Data, + IN UINTN DataLen, + IN UINT8 MaxPacket, + IN BOOLEAN IsLow + ) +{ + UHCI_TD_SW *SetupTd; + UHCI_TD_SW *FirstDataTd; + UHCI_TD_SW *DataTd; + UHCI_TD_SW *PrevDataTd; + UHCI_TD_SW *StatusTd; + UINT8 DataToggle; + UINT8 StatusPktId; + UINTN ThisTdLen; + + + DataTd = NULL; + SetupTd = NULL; + FirstDataTd = NULL; + PrevDataTd = NULL; + StatusTd = NULL; + + // + // Create setup packets for the transfer + // + SetupTd = UhciCreateSetupTd (Uhc, DeviceAddr, Request, IsLow); + + if (SetupTd == NULL) { + return NULL; + } + + // + // Create data packets for the transfer + // + DataToggle = 1; + + while (DataLen > 0) { + // + // PktSize is the data load size in each Td. + // + ThisTdLen = (DataLen > MaxPacket ? MaxPacket : DataLen); + + DataTd = UhciCreateDataTd ( + Uhc, + DeviceAddr, + 0, + Data, + ThisTdLen, + DataPktId, + DataToggle, + IsLow + ); + + if (DataTd == NULL) { + goto FREE_TD; + } + + if (FirstDataTd == NULL) { + FirstDataTd = DataTd; + FirstDataTd->NextTd = NULL; + } else { + UhciAppendTd (PrevDataTd, DataTd); + } + + DataToggle ^= 1; + PrevDataTd = DataTd; + Data += ThisTdLen; + DataLen -= ThisTdLen; + } + + // + // Status packet is on the opposite direction to data packets + // + if (OUTPUT_PACKET_ID == DataPktId) { + StatusPktId = INPUT_PACKET_ID; + } else { + StatusPktId = OUTPUT_PACKET_ID; + } + + StatusTd = UhciCreateStatusTd (Uhc, DeviceAddr, StatusPktId, IsLow); + + if (StatusTd == NULL) { + goto FREE_TD; + } + + // + // Link setup Td -> data Tds -> status Td together + // + if (FirstDataTd != NULL) { + UhciAppendTd (SetupTd, FirstDataTd); + UhciAppendTd (PrevDataTd, StatusTd); + } else { + UhciAppendTd (SetupTd, StatusTd); + } + + return SetupTd; + +FREE_TD: + if (SetupTd != NULL) { + UhciDestoryTds (Uhc, SetupTd); + } + + if (FirstDataTd != NULL) { + UhciDestoryTds (Uhc, FirstDataTd); + } + + return NULL; +} + + +/** + Create Tds list for Bulk/Interrupt Transfer + + @param Uhc USB_HC_DEV + @param DevAddr Address of Device + @param EndPoint Endpoint Number + @param PktId Packet Identification of Data Tds + @param Data A pointer to user data buffer to transfer + @param DataLen Length of user data to transfer + @param DataToggle Data Toggle Pointer + @param MaxPacket Maximum packet size for Bulk/Interrupt transfer + @param IsLow Is Low Speed Device + + @return The Tds list head for the bulk transfer + +**/ +UHCI_TD_SW * +UhciCreateBulkOrIntTds ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 EndPoint, + IN UINT8 PktId, + IN UINT8 *Data, + IN UINTN DataLen, + IN OUT UINT8 *DataToggle, + IN UINT8 MaxPacket, + IN BOOLEAN IsLow + ) +{ + UHCI_TD_SW *DataTd; + UHCI_TD_SW *FirstDataTd; + UHCI_TD_SW *PrevDataTd; + UINTN ThisTdLen; + + DataTd = NULL; + FirstDataTd = NULL; + PrevDataTd = NULL; + + // + // Create data packets for the transfer + // + while (DataLen > 0) { + // + // PktSize is the data load size that each Td. + // + ThisTdLen = DataLen; + + if (DataLen > MaxPacket) { + ThisTdLen = MaxPacket; + } + + DataTd = UhciCreateDataTd ( + Uhc, + DevAddr, + EndPoint, + Data, + ThisTdLen, + PktId, + *DataToggle, + IsLow + ); + + if (DataTd == NULL) { + goto FREE_TD; + } + + if (PktId == INPUT_PACKET_ID) { + DataTd->TdHw.ShortPacket = TRUE; + } + + if (FirstDataTd == NULL) { + FirstDataTd = DataTd; + FirstDataTd->NextTd = NULL; + } else { + UhciAppendTd (PrevDataTd, DataTd); + } + + *DataToggle ^= 1; + PrevDataTd = DataTd; + Data += ThisTdLen; + DataLen -= ThisTdLen; + } + + return FirstDataTd; + +FREE_TD: + if (FirstDataTd != NULL) { + UhciDestoryTds (Uhc, FirstDataTd); + } + + return NULL; +} diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciQueue.h b/MdeModulePkg/Bus/Pci/UhciDxe/UhciQueue.h new file mode 100644 index 0000000000..8b03ec6959 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciQueue.h @@ -0,0 +1,283 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciQueue.h + +Abstract: + + The definition for UHCI register operation routines. + +Revision History + + +**/ + +#ifndef _EFI_UHCI_QUEUE_H_ +#define _EFI_UHCI_QUEUE_H_ + +// +// Macroes used to set various links in UHCI's driver. +// In this UHCI driver, QH's horizontal link always pointers to other QH, +// and its vertical link always pointers to TD. TD's next pointer always +// pointers to other sibling TD. Frame link always pointers to QH because +// ISO transfer isn't supported. +// +// We should use UINT32 to access these pointers to void race conditions +// with hardware. +// +#define QH_HLINK(Pointer, Terminate) \ + (((UINT32) ((UINTN) (Pointer)) & 0xFFFFFFF0) | 0x02 | ((Terminate) ? 0x01 : 0)) + +#define QH_VLINK(Pointer, Terminate) \ + (((UINT32) ((UINTN) (Pointer)) & 0xFFFFFFF0) | ((Terminate) ? 0x01 : 0)) + +#define TD_LINK(Pointer, VertFirst, Terminate) \ + (((UINT32) ((UINTN) (Pointer)) & 0xFFFFFFF0) | \ + ((VertFirst) ? 0x04 : 0) | ((Terminate) ? 0x01 : 0)) + +#define LINK_TERMINATED(Link) (((Link) & 0x01) != 0) + +#define UHCI_ADDR(QhOrTd) ((VOID *) (UINTN) ((QhOrTd) & 0xFFFFFFF0)) + +#pragma pack(1) +// +// Both links in QH has this internal structure: +// Next pointer: 28, Reserved: 2, NextIsQh: 1, Terminate: 1 +// This is the same as frame list entry. +// +typedef struct { + UINT32 HorizonLink; + UINT32 VerticalLink; +} UHCI_QH_HW; + +// +// Next link in TD has this internal structure: +// Next pointer: 28, Reserved: 1, Vertical First: 1, NextIsQh: 1, Terminate: 1 +// +typedef struct { + UINT32 NextLink; + UINT32 ActualLen : 11; + UINT32 Reserved1 : 5; + UINT32 Status : 8; + UINT32 IntOnCpl : 1; + UINT32 IsIsoch : 1; + UINT32 LowSpeed : 1; + UINT32 ErrorCount : 2; + UINT32 ShortPacket : 1; + UINT32 Reserved2 : 2; + UINT32 PidCode : 8; + UINT32 DeviceAddr : 7; + UINT32 EndPoint : 4; + UINT32 DataToggle : 1; + UINT32 Reserved3 : 1; + UINT32 MaxPacketLen: 11; + UINT32 DataBuffer; +} UHCI_TD_HW; +#pragma pack() + +typedef struct _UHCI_TD_SW UHCI_TD_SW; +typedef struct _UHCI_QH_SW UHCI_QH_SW; + +typedef struct _UHCI_QH_SW { + UHCI_QH_HW QhHw; + UHCI_QH_SW *NextQh; + UHCI_TD_SW *TDs; + UINTN Interval; +} UHCI_QH_SW; + +typedef struct _UHCI_TD_SW { + UHCI_TD_HW TdHw; + UHCI_TD_SW *NextTd; + UINT8 *Data; + UINT16 DataLen; +} UHCI_TD_SW; + + +/** + Link the TD To QH + + @param Qh The queue head for the TD to link to + @param Td The TD to link + + @return VOID + +**/ +VOID +UhciLinkTdToQh ( + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *Td + ) +; + + +/** + Unlink TD from the QH + + @param Qh The queue head to unlink from + @param Td The TD to unlink + + @return VOID + +**/ +VOID +UhciUnlinkTdFromQh ( + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *Td + ) +; + + +/** + Map address of request structure buffer + + @param Uhc The UHCI device + @param Request The user request buffer + @param MappedAddr Mapped address of request + @param Map Identificaion of this mapping to return + + @return EFI_SUCCESS : Success + @return EFI_DEVICE_ERROR : Fail to map the user request + +**/ +EFI_STATUS +UhciMapUserRequest ( + IN USB_HC_DEV *Uhc, + IN OUT VOID *Request, + OUT UINT8 **MappedAddr, + OUT VOID **Map + ) +; + + +/** + Map address of user data buffer + + @param Uhc The UHCI device + @param Direction direction of the data transfer + @param Data The user data buffer + @param Len Length of the user data + @param PktId Packet identificaion + @param MappedAddr mapped address to return + @param Map identificaion of this mapping to return + + @return EFI_SUCCESS : Success + @return EFI_DEVICE_ERROR : Fail to map the user data + +**/ +EFI_STATUS +UhciMapUserData ( + IN USB_HC_DEV *Uhc, + IN EFI_USB_DATA_DIRECTION Direction, + IN VOID *Data, + IN OUT UINTN *Len, + OUT UINT8 *PktId, + OUT UINT8 **MappedAddr, + OUT VOID **Map + ) +; + + +/** + Delete a list of TDs + + @param Uhc The UHCI device + @param FirstTd TD link list head + + @return VOID + +**/ +VOID +UhciDestoryTds ( + IN USB_HC_DEV *Uhc, + IN UHCI_TD_SW *FirstTd + ) +; + + +/** + Create an initialize a new queue head + + @param Uhc The UHCI device + @param Interval The polling interval for the queue + + @return The newly created queue header + +**/ +UHCI_QH_SW * +UhciCreateQh ( + IN USB_HC_DEV *Uhc, + IN UINTN Interval + ) +; + + +/** + Create Tds list for Control Transfer + + @param Uhc The UHCI device + @param DeviceAddr The device address + @param DataPktId Packet Identification of Data Tds + @param Request A pointer to request structure buffer to transfer + @param Data A pointer to user data buffer to transfer + @param DataLen Length of user data to transfer + @param MaxPacket Maximum packet size for control transfer + @param IsLow Full speed or low speed + + @return The Td list head for the control transfer + +**/ +UHCI_TD_SW * +UhciCreateCtrlTds ( + IN USB_HC_DEV *Uhc, + IN UINT8 DeviceAddr, + IN UINT8 DataPktId, + IN UINT8 *Request, + IN UINT8 *Data, + IN UINTN DataLen, + IN UINT8 MaxPacket, + IN BOOLEAN IsLow + ) +; + + +/** + Create Tds list for Bulk/Interrupt Transfer + + @param Uhc USB_HC_DEV + @param DevAddr Address of Device + @param EndPoint Endpoint Number + @param PktId Packet Identification of Data Tds + @param Data A pointer to user data buffer to transfer + @param DataLen Length of user data to transfer + @param DataToggle Data Toggle Pointer + @param MaxPacket Maximum packet size for Bulk/Interrupt transfer + @param IsLow Is Low Speed Device + + @return The Tds list head for the bulk transfer + +**/ +UHCI_TD_SW * +UhciCreateBulkOrIntTds ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 EndPoint, + IN UINT8 PktId, + IN UINT8 *Data, + IN UINTN DataLen, + IN OUT UINT8 *DataToggle, + IN UINT8 MaxPacket, + IN BOOLEAN IsLow + ) +; + +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciReg.c b/MdeModulePkg/Bus/Pci/UhciDxe/UhciReg.c new file mode 100644 index 0000000000..63326292f3 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciReg.c @@ -0,0 +1,303 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciReg.c + +Abstract: + + The UHCI register operation routines. + +Revision History + + +**/ + +#include "Uhci.h" + + +/** + Read a UHCI register + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Offset Register offset to USB_BAR_INDEX + + @return Content of register + +**/ +UINT16 +UhciReadReg ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset + ) +{ + UINT16 Data; + EFI_STATUS Status; + + Status = PciIo->Io.Read ( + PciIo, + EfiPciIoWidthUint16, + USB_BAR_INDEX, + Offset, + 1, + &Data + ); + + if (EFI_ERROR (Status)) { + UHCI_ERROR (("UhciReadReg: PciIo Io.Read error: %r at offset %d\n", Status, Offset)); + + Data = 0xFFFF; + } + + return Data; +} + + +/** + Write data to UHCI register + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Offset Register offset to USB_BAR_INDEX + @param Data Data to write + + @return VOID + +**/ +VOID +UhciWriteReg ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset, + IN UINT16 Data + ) +{ + EFI_STATUS Status; + + Status = PciIo->Io.Write ( + PciIo, + EfiPciIoWidthUint16, + USB_BAR_INDEX, + Offset, + 1, + &Data + ); + + if (EFI_ERROR (Status)) { + UHCI_ERROR (("UhciWriteReg: PciIo Io.Write error: %r at offset %d\n", Status, Offset)); + } +} + + +/** + Set a bit of the UHCI Register + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Offset Register offset to USB_BAR_INDEX + @param Bit The bit to set + + @return None + +**/ +VOID +UhciSetRegBit ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset, + IN UINT16 Bit + ) +{ + UINT16 Data; + + Data = UhciReadReg (PciIo, Offset); + Data |= Bit; + UhciWriteReg (PciIo, Offset, Data); +} + + +/** + Clear a bit of the UHCI Register + + @param PciIo The PCI_IO protocol to access the PCI + @param Offset Register offset to USB_BAR_INDEX + @param Bit The bit to clear + + @return None + +**/ +VOID +UhciClearRegBit ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset, + IN UINT16 Bit + ) +{ + UINT16 Data; + + Data = UhciReadReg (PciIo, Offset); + Data &= ~Bit; + UhciWriteReg (PciIo, Offset, Data); +} + + +/** + Clear all the interrutp status bits, these bits + are Write-Clean + + @param Uhc The UHCI device + + @return None + +**/ +VOID +UhciAckAllInterrupt ( + IN USB_HC_DEV *Uhc + ) +{ + UhciWriteReg (Uhc->PciIo, USBSTS_OFFSET, 0x3F); + + // + // If current HC is halted, re-enable it. Host Controller Process Error + // is a temporary error status. + // + if (!UhciIsHcWorking (Uhc->PciIo)) { + UHCI_ERROR (("UhciAckAllInterrupt: re-enable the UHCI from system error\n")); + Uhc->UsbHc.SetState (&Uhc->UsbHc, EfiUsbHcStateOperational); + } +} + + + +/** + Stop the host controller + + @param Uhc The UHCI device + @param Timeout Max time allowed + + @retval EFI_SUCCESS The host controller is stopped + @retval EFI_TIMEOUT Failed to stop the host controller + +**/ +EFI_STATUS +UhciStopHc ( + IN USB_HC_DEV *Uhc, + IN UINTN Timeout + ) +{ + UINT16 UsbSts; + UINTN Index; + + UhciClearRegBit (Uhc->PciIo, USBCMD_OFFSET, USBCMD_RS); + + // + // ensure the HC is in halt status after send the stop command + // Timeout is in us unit. + // + for (Index = 0; Index < (Timeout / 50) + 1; Index++) { + UsbSts = UhciReadReg (Uhc->PciIo, USBSTS_OFFSET); + + if ((UsbSts & USBSTS_HCH) == USBSTS_HCH) { + return EFI_SUCCESS; + } + + gBS->Stall (50); + } + + return EFI_TIMEOUT; +} + + +/** + Check whether the host controller operates well + + @param PciIo The PCI_IO protocol to use + + @retval TRUE Host controller is working + @retval FALSE Host controller is halted or system error + +**/ +BOOLEAN +UhciIsHcWorking ( + IN EFI_PCI_IO_PROTOCOL *PciIo + ) +{ + UINT16 UsbSts; + + UsbSts = UhciReadReg (PciIo, USBSTS_OFFSET); + + if (UsbSts & (USBSTS_HCPE | USBSTS_HSE | USBSTS_HCH)) { + UHCI_ERROR (("UhciIsHcWorking: current USB state is %x\n", UsbSts)); + return FALSE; + } + + return TRUE; +} + + +/** + Set the UHCI frame list base address. It can't use + UhciWriteReg which access memory in UINT16. + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Addr Address to set + + @return VOID + +**/ +VOID +UhciSetFrameListBaseAddr ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN VOID *Addr + ) +{ + EFI_STATUS Status; + UINT32 Data; + + Data = (UINT32) ((UINTN) Addr & 0xFFFFF000); + + Status = PciIo->Io.Write ( + PciIo, + EfiPciIoWidthUint32, + USB_BAR_INDEX, + (UINT64) USB_FRAME_BASE_OFFSET, + 1, + &Data + ); + + if (EFI_ERROR (Status)) { + UHCI_ERROR (("UhciSetFrameListBaseAddr: PciIo Io.Write error: %r\n", Status)); + } +} + + +/** + Disable USB Emulation + + @param PciIo The EFI_PCI_IO_PROTOCOL protocol to use + + @return VOID + +**/ +VOID +UhciTurnOffUsbEmulation ( + IN EFI_PCI_IO_PROTOCOL *PciIo + ) +{ + UINT16 Command; + + Command = 0; + + PciIo->Pci.Write ( + PciIo, + EfiPciIoWidthUint16, + USB_EMULATION_OFFSET, + 1, + &Command + ); +} diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciReg.h b/MdeModulePkg/Bus/Pci/UhciDxe/UhciReg.h new file mode 100644 index 0000000000..95a98dbab9 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciReg.h @@ -0,0 +1,264 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciReg.h + +Abstract: + + The definition for UHCI register operation routines. + +Revision History + + +**/ + +#ifndef _EFI_UHCI_REG_H_ +#define _EFI_UHCI_REG_H_ + +#define BIT(a) (1 << (a)) + +enum { + UHCI_FRAME_NUM = 1024, + + // + // Register offset and PCI related staff + // + CLASSC_OFFSET = 0x09, + USBBASE_OFFSET = 0x20, + USB_BAR_INDEX = 4, + PCI_CLASSC_PI_UHCI = 0x00, + + USBCMD_OFFSET = 0, + USBSTS_OFFSET = 2, + USBINTR_OFFSET = 4, + USBPORTSC_OFFSET = 0x10, + USB_FRAME_NO_OFFSET = 6, + USB_FRAME_BASE_OFFSET = 8, + USB_EMULATION_OFFSET = 0xC0, + + // + // Packet IDs + // + SETUP_PACKET_ID = 0x2D, + INPUT_PACKET_ID = 0x69, + OUTPUT_PACKET_ID = 0xE1, + ERROR_PACKET_ID = 0x55, + + // + // USB port status and control bit definition. + // + USBPORTSC_CCS = BIT(0), // Current Connect Status + USBPORTSC_CSC = BIT(1), // Connect Status Change + USBPORTSC_PED = BIT(2), // Port Enable / Disable + USBPORTSC_PEDC = BIT(3), // Port Enable / Disable Change + USBPORTSC_LSL = BIT(4), // Line Status Low BIT + USBPORTSC_LSH = BIT(5), // Line Status High BIT + USBPORTSC_RD = BIT(6), // Resume Detect + USBPORTSC_LSDA = BIT(8), // Low Speed Device Attached + USBPORTSC_PR = BIT(9), // Port Reset + USBPORTSC_SUSP = BIT(12), // Suspend + + USB_MAX_ROOTHUB_PORT = 0x0F, // Max number of root hub port + + // + // Command register bit definitions + // + USBCMD_RS = BIT(0), // Run/Stop + USBCMD_HCRESET = BIT(1), // Host reset + USBCMD_GRESET = BIT(2), // Global reset + USBCMD_EGSM = BIT(3), // Global Suspend Mode + USBCMD_FGR = BIT(4), // Force Global Resume + USBCMD_SWDBG = BIT(5), // SW Debug mode + USBCMD_CF = BIT(6), // Config Flag (sw only) + USBCMD_MAXP = BIT(7), // Max Packet (0 = 32, 1 = 64) + + // + // USB Status register bit definitions + // + USBSTS_USBINT = BIT(0), // Interrupt due to IOC + USBSTS_ERROR = BIT(1), // Interrupt due to error + USBSTS_RD = BIT(2), // Resume Detect + USBSTS_HSE = BIT(3), // Host System Error + USBSTS_HCPE = BIT(4), // Host Controller Process Error + USBSTS_HCH = BIT(5), // HC Halted + + USBTD_ACTIVE = BIT(7), // TD is still active + USBTD_STALLED = BIT(6), // TD is stalled + USBTD_BUFFERR = BIT(5), // Buffer underflow or overflow + USBTD_BABBLE = BIT(4), // Babble condition + USBTD_NAK = BIT(3), // NAK is received + USBTD_CRC = BIT(2), // CRC/Time out error + USBTD_BITSTUFF = BIT(1), // Bit stuff error +}; + + +/** + Read a UHCI register + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Offset Register offset to USB_BAR_INDEX + + @return Content of register + +**/ +UINT16 +UhciReadReg ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset + ) +; + + + +/** + Write data to UHCI register + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Offset Register offset to USB_BAR_INDEX + @param Data Data to write + + @return VOID + +**/ +VOID +UhciWriteReg ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset, + IN UINT16 Data + ) +; + + + +/** + Set a bit of the UHCI Register + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Offset Register offset to USB_BAR_INDEX + @param Bit The bit to set + + @return None + +**/ +VOID +UhciSetRegBit ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset, + IN UINT16 Bit + ) +; + + + +/** + Clear a bit of the UHCI Register + + @param PciIo The PCI_IO protocol to access the PCI + @param Offset Register offset to USB_BAR_INDEX + @param Bit The bit to clear + + @return None + +**/ +VOID +UhciClearRegBit ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT32 Offset, + IN UINT16 Bit + ) +; + + +/** + Clear all the interrutp status bits, these bits + are Write-Clean + + @param Uhc The UHCI device + + @return None + +**/ +VOID +UhciAckAllInterrupt ( + IN USB_HC_DEV *Uhc + ) +; + + +/** + Stop the host controller + + @param Uhc The UHCI device + @param Timeout Max time allowed + + @retval EFI_SUCCESS The host controller is stopped + @retval EFI_TIMEOUT Failed to stop the host controller + +**/ +EFI_STATUS +UhciStopHc ( + IN USB_HC_DEV *Uhc, + IN UINTN Timeout + ) +; + + + +/** + Check whether the host controller operates well + + @param PciIo The PCI_IO protocol to use + + @retval TRUE Host controller is working + @retval FALSE Host controller is halted or system error + +**/ +BOOLEAN +UhciIsHcWorking ( + IN EFI_PCI_IO_PROTOCOL *PciIo + ) +; + + +/** + Set the UHCI frame list base address. It can't use + UhciWriteReg which access memory in UINT16. + + @param PciIo The EFI_PCI_IO_PROTOCOL to use + @param Addr Address to set + + @return VOID + +**/ +VOID +UhciSetFrameListBaseAddr ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN VOID *Addr + ) +; + + +/** + Disable USB Emulation + + @param PciIo The EFI_PCI_IO_PROTOCOL protocol to use + + @return VOID + +**/ +VOID +UhciTurnOffUsbEmulation ( + IN EFI_PCI_IO_PROTOCOL *PciIo + ) +; +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c b/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c new file mode 100644 index 0000000000..222322a2b0 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c @@ -0,0 +1,1051 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciSched.c + +Abstract: + + The EHCI register operation routines. + +Revision History + + +**/ + +#include "Uhci.h" + + +/** + Create Frame List Structure + + @param Uhc UHCI device + + @retval EFI_OUT_OF_RESOURCES Can't allocate memory resources + @retval EFI_UNSUPPORTED Map memory fail + @retval EFI_SUCCESS Success + +**/ +EFI_STATUS +UhciInitFrameList ( + IN USB_HC_DEV *Uhc + ) +{ + EFI_PHYSICAL_ADDRESS MappedAddr; + EFI_STATUS Status; + VOID *Buffer; + VOID *Mapping; + UINTN Pages; + UINTN Bytes; + UINTN Index; + + // + // The Frame List is a common buffer that will be + // accessed by both the cpu and the usb bus master + // at the same time. The Frame List ocupies 4K bytes, + // and must be aligned on 4-Kbyte boundaries. + // + Bytes = 4096; + Pages = EFI_SIZE_TO_PAGES (Bytes); + + Status = Uhc->PciIo->AllocateBuffer ( + Uhc->PciIo, + AllocateAnyPages, + EfiBootServicesData, + Pages, + &Buffer, + 0 + ); + + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + + Status = Uhc->PciIo->Map ( + Uhc->PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + Buffer, + &Bytes, + &MappedAddr, + &Mapping + ); + + if (EFI_ERROR (Status) || (Bytes != 4096)) { + Status = EFI_UNSUPPORTED; + goto ON_ERROR; + } + + Uhc->FrameBase = (UINT32 *) (UINTN) MappedAddr; + Uhc->FrameMapping = Mapping; + + // + // Allocate the QH used by sync interrupt/control/bulk transfer. + // FS ctrl/bulk queue head is set to loopback so additional BW + // can be reclaimed. Notice, LS don't support bulk transfer and + // also doesn't support BW reclamation. + // + Uhc->SyncIntQh = UhciCreateQh (Uhc, 1); + Uhc->CtrlQh = UhciCreateQh (Uhc, 1); + Uhc->BulkQh = UhciCreateQh (Uhc, 1); + + if ((Uhc->SyncIntQh == NULL) || (Uhc->CtrlQh == NULL) || (Uhc->BulkQh == NULL)) { + Uhc->PciIo->Unmap (Uhc->PciIo, Mapping); + Status = EFI_OUT_OF_RESOURCES; + goto ON_ERROR; + } + + // + // +-------------+ + // | | + // Link the three together: SyncIntQh->CtrlQh->BulkQh <---------+ + // Each frame entry is linked to this sequence of QH. These QH + // will remain on the schedul, never got removed + // + Uhc->SyncIntQh->QhHw.HorizonLink = QH_HLINK (Uhc->CtrlQh, FALSE); + Uhc->SyncIntQh->NextQh = Uhc->CtrlQh; + + Uhc->CtrlQh->QhHw.HorizonLink = QH_HLINK (Uhc->BulkQh, FALSE); + Uhc->CtrlQh->NextQh = Uhc->BulkQh; + + // + // Some old platform such as Intel's Tiger 4 has a difficult time + // in supporting the full speed bandwidth reclamation in the previous + // mentioned form. Most new platforms don't suffer it. + // +#ifdef UHCI_NO_BW_RECLAMATION + Uhc->BulkQh->QhHw.HorizonLink = QH_HLINK (NULL, TRUE); +#else + Uhc->BulkQh->QhHw.HorizonLink = QH_HLINK (Uhc->BulkQh, FALSE); +#endif + + Uhc->BulkQh->NextQh = NULL; + + for (Index = 0; Index < UHCI_FRAME_NUM; Index++) { + Uhc->FrameBase[Index] = QH_HLINK (Uhc->SyncIntQh, FALSE); + } + + // + // Tell the Host Controller where the Frame List lies, + // by set the Frame List Base Address Register. + // + UhciSetFrameListBaseAddr (Uhc->PciIo, (VOID *) (Uhc->FrameBase)); + return EFI_SUCCESS; + +ON_ERROR: + if (Uhc->SyncIntQh != NULL) { + UsbHcFreeMem (Uhc->MemPool, Uhc->SyncIntQh, sizeof (UHCI_QH_SW)); + } + + if (Uhc->CtrlQh != NULL) { + UsbHcFreeMem (Uhc->MemPool, Uhc->CtrlQh, sizeof (UHCI_QH_SW)); + } + + if (Uhc->BulkQh != NULL) { + UsbHcFreeMem (Uhc->MemPool, Uhc->BulkQh, sizeof (UHCI_QH_SW)); + } + + Uhc->PciIo->FreeBuffer (Uhc->PciIo, Pages, Buffer); + return Status; +} + + +/** + Destory FrameList buffer + + @param Uhc The UHCI device + + @return VOID + +**/ +VOID +UhciDestoryFrameList ( + IN USB_HC_DEV *Uhc + ) +{ + // + // Unmap the common buffer for framelist entry, + // and free the common buffer. + // Uhci's frame list occupy 4k memory. + // + Uhc->PciIo->Unmap (Uhc->PciIo, Uhc->FrameMapping); + + Uhc->PciIo->FreeBuffer ( + Uhc->PciIo, + EFI_SIZE_TO_PAGES (4096), + (VOID *) Uhc->FrameBase + ); + + if (Uhc->SyncIntQh != NULL) { + UsbHcFreeMem (Uhc->MemPool, Uhc->SyncIntQh, sizeof (UHCI_QH_SW)); + } + + if (Uhc->CtrlQh != NULL) { + UsbHcFreeMem (Uhc->MemPool, Uhc->CtrlQh, sizeof (UHCI_QH_SW)); + } + + if (Uhc->BulkQh != NULL) { + UsbHcFreeMem (Uhc->MemPool, Uhc->BulkQh, sizeof (UHCI_QH_SW)); + } + + Uhc->FrameBase = NULL; + Uhc->SyncIntQh = NULL; + Uhc->CtrlQh = NULL; + Uhc->BulkQh = NULL; +} + + +/** + Convert the poll rate to the maxium 2^n that is smaller + than Interval + + @param Interval The poll rate to convert + + @return The converted poll rate + +**/ +UINTN +UhciConvertPollRate ( + IN UINTN Interval + ) +{ + UINTN BitCount; + + ASSERT (Interval != 0); + + // + // Find the index (1 based) of the highest non-zero bit + // + BitCount = 0; + + while (Interval != 0) { + Interval >>= 1; + BitCount++; + } + + return (UINTN)1 << (BitCount - 1); +} + + +/** + Link a queue head (for asynchronous interrupt transfer) to + the frame list. + + @param FrameBase The base of the frame list + @param Qh The queue head to link into + + @return None + +**/ +VOID +UhciLinkQhToFrameList ( + UINT32 *FrameBase, + UHCI_QH_SW *Qh + ) +{ + UINTN Index; + UHCI_QH_SW *Prev; + UHCI_QH_SW *Next; + + ASSERT ((FrameBase != NULL) && (Qh != NULL)); + + for (Index = 0; Index < UHCI_FRAME_NUM; Index += Qh->Interval) { + // + // First QH can't be NULL because we always keep static queue + // heads on the frame list + // + ASSERT (!LINK_TERMINATED (FrameBase[Index])); + Next = UHCI_ADDR (FrameBase[Index]); + Prev = NULL; + + // + // Now, insert the queue head (Qh) into this frame: + // 1. Find a queue head with the same poll interval, just insert + // Qh after this queue head, then we are done. + // + // 2. Find the position to insert the queue head into: + // Previous head's interval is bigger than Qh's + // Next head's interval is less than Qh's + // Then, insert the Qh between then + // + // This method is very much the same as that used by EHCI. + // Because each QH's interval is round down to 2^n, poll + // rate is correct. + // + while (Next->Interval > Qh->Interval) { + Prev = Next; + Next = Next->NextQh; + } + + ASSERT (Next != NULL); + + // + // The entry may have been linked into the frame by early insertation. + // For example: if insert a Qh with Qh.Interval == 4, and there is a Qh + // with Qh.Interval == 8 on the frame. If so, we are done with this frame. + // It isn't necessary to compare all the QH with the same interval to + // Qh. This is because if there is other QH with the same interval, Qh + // should has been inserted after that at FrameBase[0] and at FrameBase[0] it is + // impossible (Next == Qh) + // + if (Next == Qh) { + continue; + } + + if (Next->Interval == Qh->Interval) { + // + // If there is a QH with the same interval, it locates at + // FrameBase[0], and we can simply insert it after this QH. We + // are all done. + // + ASSERT ((Index == 0) && (Qh->NextQh == NULL)); + + Prev = Next; + Next = Next->NextQh; + + Qh->NextQh = Next; + Prev->NextQh = Qh; + + Qh->QhHw.HorizonLink = Prev->QhHw.HorizonLink; + Prev->QhHw.HorizonLink = QH_HLINK (Qh, FALSE); + break; + } + + // + // OK, find the right position, insert it in. If Qh's next + // link has already been set, it is in position. This is + // guarranted by 2^n polling interval. + // + if (Qh->NextQh == NULL) { + Qh->NextQh = Next; + Qh->QhHw.HorizonLink = QH_HLINK (Next, FALSE); + } + + if (Prev == NULL) { + FrameBase[Index] = QH_HLINK (Qh, FALSE); + } else { + Prev->NextQh = Qh; + Prev->QhHw.HorizonLink = QH_HLINK (Qh, FALSE); + } + } +} + + +/** + Unlink QH from the frame list is easier: find all + the precedence node, and pointer there next to QhSw's + next. + + @param FrameBase The base address of the frame list + @param Qh The queue head to unlink + + @return None + +**/ +VOID +UhciUnlinkQhFromFrameList ( + UINT32 *FrameBase, + UHCI_QH_SW *Qh + ) +{ + UINTN Index; + UHCI_QH_SW *Prev; + UHCI_QH_SW *This; + + ASSERT ((FrameBase != NULL) && (Qh != NULL)); + + for (Index = 0; Index < UHCI_FRAME_NUM; Index += Qh->Interval) { + // + // Frame link can't be NULL because we always keep static + // queue heads on the frame list + // + ASSERT (!LINK_TERMINATED (FrameBase[Index])); + This = UHCI_ADDR (FrameBase[Index]); + Prev = NULL; + + // + // Walk through the frame's QH list to find the + // queue head to remove + // + while ((This != NULL) && (This != Qh)) { + Prev = This; + This = This->NextQh; + } + + // + // Qh may have already been unlinked from this frame + // by early action. + // + if (This == NULL) { + continue; + } + + if (Prev == NULL) { + // + // Qh is the first entry in the frame + // + FrameBase[Index] = Qh->QhHw.HorizonLink; + } else { + Prev->NextQh = Qh->NextQh; + Prev->QhHw.HorizonLink = Qh->QhHw.HorizonLink; + } + } +} + + +/** + Check TDs Results + + @param Uhc This UHCI device + @param Td UHCI_TD_SW to check + @param IsLow Is Low Speed Device + @param QhResult Return the result of this TD list + + @return Whether the TD's result is finialized. + +**/ +STATIC +BOOLEAN +UhciCheckTdStatus ( + IN USB_HC_DEV *Uhc, + IN UHCI_TD_SW *Td, + IN BOOLEAN IsLow, + OUT UHCI_QH_RESULT *QhResult + ) +{ + UINTN Len; + UINT8 State; + UHCI_TD_HW *TdHw; + BOOLEAN Finished; + + Finished = TRUE; + + // + // Initialize the data toggle to that of the first + // TD. The next toggle to use is either: + // 1. first TD's toggle if no TD is executed OK + // 2. the next toggle of last executed-OK TD + // + QhResult->Result = EFI_USB_NOERROR; + QhResult->NextToggle = (UINT8)Td->TdHw.DataToggle; + QhResult->Complete = 0; + + while (Td != NULL) { + TdHw = &Td->TdHw; + State = (UINT8)TdHw->Status; + + // + // UHCI will set STALLED bit when it abort the execution + // of TD list. There are several reasons: + // 1. BABBLE error happened + // 2. Received a STALL response + // 3. Error count decreased to zero. + // + // It also set CRC/Timeout/NAK/Buffer Error/BitStuff Error + // bits when corresponding conditions happen. But these + // conditions are not deadly, that is a TD can successfully + // completes even these bits are set. But it is likely that + // upper layer won't distinguish these condtions. So, only + // set these bits when TD is actually halted. + // + if (State & USBTD_STALLED) { + if (State & USBTD_BABBLE) { + QhResult->Result |= EFI_USB_ERR_BABBLE; + + } else if (TdHw->ErrorCount != 0) { + QhResult->Result |= EFI_USB_ERR_STALL; + } + + if (State & USBTD_CRC) { + QhResult->Result |= EFI_USB_ERR_CRC; + } + + if (State & USBTD_BUFFERR) { + QhResult->Result |= EFI_USB_ERR_BUFFER; + } + + if (Td->TdHw.Status & USBTD_BITSTUFF) { + QhResult->Result |= EFI_USB_ERR_BITSTUFF; + } + + if (TdHw->ErrorCount == 0) { + QhResult->Result |= EFI_USB_ERR_TIMEOUT; + } + + Finished = TRUE; + goto ON_EXIT; + + } else if (State & USBTD_ACTIVE) { + // + // The TD is still active, no need to check further. + // + QhResult->Result |= EFI_USB_ERR_NOTEXECUTE; + + Finished = FALSE; + goto ON_EXIT; + + } else { + // + // Update the next data toggle, it is always the + // next to the last known-good TD's data toggle if + // any TD is executed OK + // + QhResult->NextToggle = 1 - (UINT8)TdHw->DataToggle; + + // + // This TD is finished OK or met short packet read. Update the + // transfer length if it isn't a SETUP. + // + Len = (TdHw->ActualLen + 1) & 0x7FF; + + if (TdHw->PidCode != SETUP_PACKET_ID) { + QhResult->Complete += Len; + } + + // + // Short packet condition for full speed input TD, also + // terminate the transfer + // + if (!IsLow && (TdHw->ShortPacket == 1) && (Len < Td->DataLen)) { + UHCI_DEBUG (("UhciCheckTdStatus: short packet read occured\n")); + + Finished = TRUE; + goto ON_EXIT; + } + } + + Td = Td->NextTd; + } + +ON_EXIT: + // + // Check whether HC is halted. Don't move this up. It must be + // called after data toggle is successfully updated. + // + if (!UhciIsHcWorking (Uhc->PciIo)) { + QhResult->Result |= EFI_USB_ERR_SYSTEM; + Finished = TRUE; + } + + if (Finished) { + Uhc->PciIo->Flush (Uhc->PciIo); + } + + UhciAckAllInterrupt (Uhc); + return Finished; +} + + + +/** + Check the result of the transfer + + @param Uhc The UHCI device + @param Td The first TDs of the transfer + @param TimeOut TimeOut value in milliseconds + @param IsLow Is Low Speed Device + @param QhResult The variable to return result + + @retval EFI_SUCCESS The transfer finished with success + @retval EFI_DEVICE_ERROR Transfer failed + +**/ +EFI_STATUS +UhciExecuteTransfer ( + IN USB_HC_DEV *Uhc, + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *Td, + IN UINTN TimeOut, + IN BOOLEAN IsLow, + OUT UHCI_QH_RESULT *QhResult + ) +{ + UINTN Index; + UINTN Delay; + BOOLEAN Finished; + EFI_STATUS Status; + + Finished = FALSE; + Status = EFI_SUCCESS; + Delay = (TimeOut * STALL_1_MS / UHC_SYN_POLL) + 1; + + for (Index = 0; Index < Delay; Index++) { + Finished = UhciCheckTdStatus (Uhc, Td, IsLow, QhResult); + + // + // Transfer is OK or some error occured (TD inactive) + // + if (Finished) { + break; + } + + gBS->Stall (UHC_SYN_POLL); + } + + if (!Finished) { + UHCI_ERROR (("UhciExecuteTransfer: execution not finished for %dms\n", TimeOut)); + UHCI_DUMP_QH ((Qh)); + UHCI_DUMP_TDS ((Td)); + + Status = EFI_TIMEOUT; + + } else if (QhResult->Result != EFI_USB_NOERROR) { + UHCI_ERROR (("UhciExecuteTransfer: execution failed with result %x\n", QhResult->Result)); + UHCI_DUMP_QH ((Qh)); + UHCI_DUMP_TDS ((Td)); + + Status = EFI_DEVICE_ERROR; + } + + return Status; +} + + +/** + Update Async Request, QH and TDs + + @param AsyncReq The UHCI asynchronous transfer to update + @param Result Transfer reslut + @param ErrTdPos Error TD Position + + @return VOID + +**/ +STATIC +VOID +UhciUpdateAsyncReq ( + IN UHCI_ASYNC_REQUEST *AsyncReq, + IN UINT32 Result, + IN UINT32 NextToggle + ) +{ + UHCI_QH_SW *Qh; + UHCI_TD_SW *FirstTd; + UHCI_TD_SW *Td; + + Qh = AsyncReq->QhSw; + FirstTd = AsyncReq->FirstTd; + + if (Result == EFI_USB_NOERROR) { + // + // The last transfer succeeds. Then we need to update + // the Qh and Td for next round of transfer. + // 1. Update the TD's data toggle + // 2. Activate all the TDs + // 3. Link the TD to the queue head again since during + // execution, queue head's TD pointer is changed by + // hardware. + // + for (Td = FirstTd; Td != NULL; Td = Td->NextTd) { + Td->TdHw.DataToggle = NextToggle; + NextToggle ^= 1; + Td->TdHw.Status |= USBTD_ACTIVE; + } + + UhciLinkTdToQh (Qh, FirstTd); + return ; + } +} + + +/** + Create Async Request node, and Link to List + + @param Uhc The UHCI device + @param Qh The queue head of the transfer + @param FirstTd First TD of the transfer + @param DevAddr Device Address + @param EndPoint EndPoint Address + @param DataLen Data length + @param Interval Polling Interval when inserted to frame list + @param Mapping Mapping value + @param Data Data buffer, unmapped + @param Callback Callback after interrupt transfeer + @param Context Callback Context passed as function parameter + @param IsLow Is Low Speed + + @retval EFI_SUCCESS An asynchronous transfer is created + @retval EFI_INVALID_PARAMETER Paremeter is error + @retval EFI_OUT_OF_RESOURCES Failed because of resource shortage. + +**/ +EFI_STATUS +UhciCreateAsyncReq ( + IN USB_HC_DEV *Uhc, + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *FirstTd, + IN UINT8 DevAddr, + IN UINT8 EndPoint, + IN UINTN DataLen, + IN UINTN Interval, + IN VOID *Mapping, + IN UINT8 *Data, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, + IN VOID *Context, + IN BOOLEAN IsLow + ) +{ + UHCI_ASYNC_REQUEST *AsyncReq; + + AsyncReq = AllocatePool (sizeof (UHCI_ASYNC_REQUEST)); + + if (AsyncReq == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Fill Request field. Data is allocated host memory, not mapped + // + AsyncReq->Signature = UHCI_ASYNC_INT_SIGNATURE; + AsyncReq->DevAddr = DevAddr; + AsyncReq->EndPoint = EndPoint; + AsyncReq->DataLen = DataLen; + AsyncReq->Interval = Interval; + AsyncReq->Mapping = Mapping; + AsyncReq->Data = Data; + AsyncReq->Callback = Callback; + AsyncReq->Context = Context; + AsyncReq->QhSw = Qh; + AsyncReq->FirstTd = FirstTd; + AsyncReq->IsLow = IsLow; + + // + // Insert the new interrupt transfer to the head of the list. + // The interrupt transfer's monitor function scans the whole + // list from head to tail. The new interrupt transfer MUST be + // added to the head of the list. + // + InsertHeadList (&(Uhc->AsyncIntList), &(AsyncReq->Link)); + + return EFI_SUCCESS; +} + + + +/** + Free an asynchronous request's resource such as memory + + @param Uhc The UHCI device + @param AsyncReq The asynchronous request to free + + @return None + +**/ +STATIC +VOID +UhciFreeAsyncReq ( + IN USB_HC_DEV *Uhc, + IN UHCI_ASYNC_REQUEST *AsyncReq + ) +{ + ASSERT ((Uhc != NULL) && (AsyncReq != NULL)); + + UhciDestoryTds (Uhc, AsyncReq->FirstTd); + UsbHcFreeMem (Uhc->MemPool, AsyncReq->QhSw, sizeof (UHCI_QH_SW)); + + if (AsyncReq->Mapping != NULL) { + Uhc->PciIo->Unmap (Uhc->PciIo, AsyncReq->Mapping); + } + + if (AsyncReq->Data != NULL) { + gBS->FreePool (AsyncReq->Data); + } + + gBS->FreePool (AsyncReq); +} + + +/** + Unlink an asynchronous request's from UHC's asynchronus list. + also remove the queue head from the frame list. If FreeNow, + release its resource also. Otherwise, add the request to the + UHC's recycle list to wait for a while before release the memory. + Until then, hardware won't hold point to the request. + + @param Uhc The UHCI device + @param AsyncReq The asynchronous request to free + @param FreeNow If TRUE, free the resource immediately, otherwise + add the request to recycle wait list. + + @return None + +**/ +STATIC +VOID +UhciUnlinkAsyncReq ( + IN USB_HC_DEV *Uhc, + IN UHCI_ASYNC_REQUEST *AsyncReq, + IN BOOLEAN FreeNow + ) +{ + ASSERT ((Uhc != NULL) && (AsyncReq != NULL)); + + RemoveEntryList (&(AsyncReq->Link)); + UhciUnlinkQhFromFrameList (Uhc->FrameBase, AsyncReq->QhSw); + + if (FreeNow) { + UhciFreeAsyncReq (Uhc, AsyncReq); + } else { + // + // To sychronize with hardware, mark the queue head as inactive + // then add AsyncReq to UHC's recycle list + // + AsyncReq->QhSw->QhHw.VerticalLink = QH_VLINK (NULL, TRUE); + AsyncReq->Recycle = Uhc->RecycleWait; + Uhc->RecycleWait = AsyncReq; + } +} + + +/** + Delete Async Interrupt QH and TDs + + @param Uhc The UHCI device + @param DevAddr Device Address + @param EndPoint EndPoint Address + @param Toggle The next data toggle to use + + @retval EFI_SUCCESS The request is deleted + @retval EFI_INVALID_PARAMETER Paremeter is error + @retval EFI_NOT_FOUND The asynchronous isn't found + +**/ +EFI_STATUS +UhciRemoveAsyncReq ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 EndPoint, + OUT UINT8 *Toggle + ) +{ + EFI_STATUS Status; + UHCI_ASYNC_REQUEST *AsyncReq; + UHCI_QH_RESULT QhResult; + LIST_ENTRY *Link; + BOOLEAN Found; + + Status = EFI_SUCCESS; + + // + // If no asynchronous interrupt transaction exists + // + if (IsListEmpty (&(Uhc->AsyncIntList))) { + return EFI_SUCCESS; + } + + // + // Find the asynchronous transfer to this device/endpoint pair + // + Found = FALSE; + Link = Uhc->AsyncIntList.ForwardLink; + + do { + AsyncReq = UHCI_ASYNC_INT_FROM_LINK (Link); + Link = Link->ForwardLink; + + if ((AsyncReq->DevAddr == DevAddr) && (AsyncReq->EndPoint == EndPoint)) { + Found = TRUE; + break; + } + + } while (Link != &(Uhc->AsyncIntList)); + + if (!Found) { + return EFI_NOT_FOUND; + } + + // + // Check the result of the async transfer then update it + // to get the next data toggle to use. + // + UhciCheckTdStatus (Uhc, AsyncReq->FirstTd, AsyncReq->IsLow, &QhResult); + *Toggle = QhResult.NextToggle; + + // + // Don't release the request now, keep it to synchronize with hardware. + // + UhciUnlinkAsyncReq (Uhc, AsyncReq, FALSE); + return Status; +} + + +/** + Recycle the asynchronouse request. When a queue head + is unlinked from frame list, host controller hardware + may still hold a cached pointer to it. To synchronize + with hardware, the request is released in two steps: + first it is linked to the UHC's RecycleWait list. At + the next time UhciMonitorAsyncReqList is fired, it is + moved to UHC's Recylelist. Then, at another timer + activation, all the requests on Recycle list is freed. + This guarrantes that each unlink queue head keeps + existing for at least 50ms, far enough for the hardware + to clear its cache. + + @param Uhc The UHCI device + + @return None + +**/ +STATIC +VOID +UhciRecycleAsyncReq ( + IN USB_HC_DEV *Uhc + ) +{ + UHCI_ASYNC_REQUEST *Req; + UHCI_ASYNC_REQUEST *Next; + + Req = Uhc->Recycle; + + while (Req != NULL) { + Next = Req->Recycle; + UhciFreeAsyncReq (Uhc, Req); + Req = Next; + } + + Uhc->Recycle = Uhc->RecycleWait; + Uhc->RecycleWait = NULL; +} + + + +/** + Release all the asynchronous transfers on the lsit. + + @param Uhc The UHCI device + + @return VOID + +**/ +VOID +UhciFreeAllAsyncReq ( + IN USB_HC_DEV *Uhc + ) +{ + LIST_ENTRY *Head; + UHCI_ASYNC_REQUEST *AsyncReq; + + // + // Call UhciRecycleAsyncReq twice. The requests on Recycle + // will be released at the first call; The requests on + // RecycleWait will be released at the second call. + // + UhciRecycleAsyncReq (Uhc); + UhciRecycleAsyncReq (Uhc); + + Head = &(Uhc->AsyncIntList); + + if (IsListEmpty (Head)) { + return; + } + + while (!IsListEmpty (Head)) { + AsyncReq = UHCI_ASYNC_INT_FROM_LINK (Head->ForwardLink); + UhciUnlinkAsyncReq (Uhc, AsyncReq, TRUE); + } +} + + +/** + Interrupt transfer periodic check handler + + @param Event The event of the time + @param Context Context of the event, pointer to USB_HC_DEV + + @return VOID + +**/ +VOID +UhciMonitorAsyncReqList ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + UHCI_ASYNC_REQUEST *AsyncReq; + LIST_ENTRY *Link; + USB_HC_DEV *Uhc; + VOID *Data; + BOOLEAN Finished; + UHCI_QH_RESULT QhResult; + + Uhc = (USB_HC_DEV *) Context; + + // + // Recycle the asynchronous requests expired, and promote + // requests waiting to be recycled the next time when this + // timer expires + // + UhciRecycleAsyncReq (Uhc); + + if (IsListEmpty (&(Uhc->AsyncIntList))) { + return ; + } + + // + // This loop must be delete safe + // + Link = Uhc->AsyncIntList.ForwardLink; + + do { + AsyncReq = UHCI_ASYNC_INT_FROM_LINK (Link); + Link = Link->ForwardLink; + + Finished = UhciCheckTdStatus (Uhc, AsyncReq->FirstTd, AsyncReq->IsLow, &QhResult); + + if (!Finished) { + continue; + } + + // + // Copy the data to temporary buffer if there are some + // data transferred. We may have zero-length packet + // + Data = NULL; + + if (QhResult.Complete != 0) { + Data = AllocatePool (QhResult.Complete); + + if (Data == NULL) { + return ; + } + + CopyMem (Data, AsyncReq->FirstTd->Data, QhResult.Complete); + } + + UhciUpdateAsyncReq (AsyncReq, QhResult.Result, QhResult.NextToggle); + + // + // Now, either transfer is SUCCESS or met errors since + // we have skipped to next transfer earlier if current + // transfer is still active. + // + if (QhResult.Result == EFI_USB_NOERROR) { + AsyncReq->Callback (Data, QhResult.Complete, AsyncReq->Context, QhResult.Result); + } else { + // + // Leave error recovery to its related device driver. + // A common case of the error recovery is to re-submit + // the interrupt transfer. When an interrupt transfer + // is re-submitted, its position in the linked list is + // changed. It is inserted to the head of the linked + // list, while this function scans the whole list from + // head to tail. Thus, the re-submitted interrupt transfer's + // callback function will not be called again in this round. + // + AsyncReq->Callback (NULL, 0, AsyncReq->Context, QhResult.Result); + } + + if (Data != NULL) { + gBS->FreePool (Data); + } + } while (Link != &(Uhc->AsyncIntList)); +} diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.h b/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.h new file mode 100644 index 0000000000..cba65c661b --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.h @@ -0,0 +1,305 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + UhciSched.h + +Abstract: + + The definition for EHCI register operation routines. + +Revision History + + +**/ + +#ifndef _EFI_UHCI_SCHED_H_ +#define _EFI_UHCI_SCHED_H_ + + +enum { + UHCI_ASYNC_INT_SIGNATURE = EFI_SIGNATURE_32 ('u', 'h', 'c', 'a'), + + // + // The failure mask for USB transfer return status. If any of + // these bit is set, the transfer failed. EFI_USB_ERR_NOEXECUTE + // and EFI_USB_ERR_NAK are not considered as error condition: + // the transfer is still going on. + // + USB_ERR_FAIL_MASK = EFI_USB_ERR_STALL | EFI_USB_ERR_BUFFER | + EFI_USB_ERR_BABBLE | EFI_USB_ERR_CRC | + EFI_USB_ERR_TIMEOUT | EFI_USB_ERR_BITSTUFF | + EFI_USB_ERR_SYSTEM, + +}; + +// +// Structure to return the result of UHCI QH execution. +// Result is the final result of the QH's QTD. NextToggle +// is the next data toggle to use. Complete is the actual +// length of data transferred. +// +typedef struct { + UINT32 Result; + UINT8 NextToggle; + UINTN Complete; +} UHCI_QH_RESULT; + +typedef struct _UHCI_ASYNC_REQUEST UHCI_ASYNC_REQUEST; + +// +// Structure used to manager the asynchronous interrupt transfers. +// +typedef struct _UHCI_ASYNC_REQUEST{ + UINTN Signature; + LIST_ENTRY Link; + UHCI_ASYNC_REQUEST *Recycle; + + // + // Endpoint attributes + // + UINT8 DevAddr; + UINT8 EndPoint; + BOOLEAN IsLow; + UINTN Interval; + + // + // Data and UHC structures + // + UHCI_QH_SW *QhSw; + UHCI_TD_SW *FirstTd; + UINT8 *Data; // Allocated host memory, not mapped memory + UINTN DataLen; + VOID *Mapping; + + // + // User callback and its context + // + EFI_ASYNC_USB_TRANSFER_CALLBACK Callback; + VOID *Context; +} UHCI_ASYNC_REQUEST; + +#define UHCI_ASYNC_INT_FROM_LINK(a) \ + CR (a, UHCI_ASYNC_REQUEST, Link, UHCI_ASYNC_INT_SIGNATURE) + +EFI_STATUS +UhciInitFrameList ( + IN USB_HC_DEV *Uhc + ) +/*++ + +Routine Description: + + Create Frame List Structure + +Arguments: + + Uhc - UHCI device + +Returns: + + EFI_OUT_OF_RESOURCES - Can't allocate memory resources + EFI_UNSUPPORTED - Map memory fail + EFI_SUCCESS - Success + +--*/ +; + + +/** + Destory FrameList buffer + + @param Uhc The UHCI device + + @return VOID + +**/ +VOID +UhciDestoryFrameList ( + IN USB_HC_DEV *Uhc + ) +; + + +/** + Convert the poll rate to the maxium 2^n that is smaller + than Interval + + @param Interval The poll rate to convert + + @return The converted poll rate + +**/ +UINTN +UhciConvertPollRate ( + IN UINTN Interval + ) +; + + +/** + Link a queue head (for asynchronous interrupt transfer) to + the frame list. + + @param FrameBase The base of the frame list + @param Qh The queue head to link into + + @return None + +**/ +VOID +UhciLinkQhToFrameList ( + UINT32 *FrameBase, + UHCI_QH_SW *Qh + ) +; + + +/** + Unlink QH from the frame list is easier: find all + the precedence node, and pointer there next to QhSw's + next. + + @param FrameBase The base address of the frame list + @param Qh The queue head to unlink + + @return None + +**/ +VOID +UhciUnlinkQhFromFrameList ( + UINT32 *FrameBase, + UHCI_QH_SW *Qh + ) +; + + +/** + Check the result of the transfer + + @param Uhc The UHCI device + @param Td The first TDs of the transfer + @param TimeOut TimeOut value in milliseconds + @param IsLow Is Low Speed Device + @param QhResult The variable to return result + + @retval EFI_SUCCESS The transfer finished with success + @retval EFI_DEVICE_ERROR Transfer failed + +**/ +EFI_STATUS +UhciExecuteTransfer ( + IN USB_HC_DEV *Uhc, + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *Td, + IN UINTN TimeOut, + IN BOOLEAN IsLow, + OUT UHCI_QH_RESULT *QhResult + ) +; + + +/** + Create Async Request node, and Link to List + + @param Uhc The UHCI device + @param Qh The queue head of the transfer + @param FirstTd First TD of the transfer + @param DevAddr Device Address + @param EndPoint EndPoint Address + @param Toggle Data Toggle + @param DataLen Data length + @param Interval Polling Interval when inserted to frame list + @param Mapping Mapping value + @param Data Data buffer, unmapped + @param Callback Callback after interrupt transfeer + @param Context Callback Context passed as function parameter + @param IsLow Is Low Speed + + @retval EFI_SUCCESS An asynchronous transfer is created + @retval EFI_INVALID_PARAMETER Paremeter is error + @retval EFI_OUT_OF_RESOURCES Failed because of resource shortage. + +**/ +EFI_STATUS +UhciCreateAsyncReq ( + IN USB_HC_DEV *Uhc, + IN UHCI_QH_SW *Qh, + IN UHCI_TD_SW *FirstTd, + IN UINT8 DevAddr, + IN UINT8 EndPoint, + IN UINTN DataLen, + IN UINTN Interval, + IN VOID *Mapping, + IN UINT8 *Data, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, + IN VOID *Context, + IN BOOLEAN IsLow + ) +; + + +/** + Delete Async Interrupt QH and TDs + + @param Uhc The UHCI device + @param DevAddr Device Address + @param EndPoint EndPoint Address + @param Toggle The next data toggle to use + + @retval EFI_SUCCESS The request is deleted + @retval EFI_INVALID_PARAMETER Paremeter is error + @retval EFI_NOT_FOUND The asynchronous isn't found + +**/ +EFI_STATUS +UhciRemoveAsyncReq ( + IN USB_HC_DEV *Uhc, + IN UINT8 DevAddr, + IN UINT8 EndPoint, + OUT UINT8 *Toggle + ) +; + + +/** + Release all the asynchronous transfers on the lsit. + + @param Uhc The UHCI device + + @return VOID + +**/ +VOID +UhciFreeAllAsyncReq ( + IN USB_HC_DEV *Uhc + ) +; + + +/** + Interrupt transfer periodic check handler + + @param Event The event of the time + @param Context Context of the event, pointer to USB_HC_DEV + + @return VOID + +**/ +VOID +UhciMonitorAsyncReqList ( + IN EFI_EVENT Event, + IN VOID *Context + ) +; + +#endif diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UsbHcMem.c b/MdeModulePkg/Bus/Pci/UhciDxe/UsbHcMem.c new file mode 100644 index 0000000000..32082b15bf --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UsbHcMem.c @@ -0,0 +1,548 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciMem.c + +Abstract: + + +Revision History + +**/ + +#include "Uhci.h" + + +UINTN mUsbHcDebugLevel = DEBUG_INFO; + + +/** + Allocate a block of memory to be used by the buffer pool + + @param Pool The buffer pool to allocate memory for + @param Pages How many pages to allocate + + @return The allocated memory block or NULL if failed + +**/ +STATIC +USBHC_MEM_BLOCK * +UsbHcAllocMemBlock ( + IN USBHC_MEM_POOL *Pool, + IN UINTN Pages + ) +{ + USBHC_MEM_BLOCK *Block; + EFI_PCI_IO_PROTOCOL *PciIo; + VOID *BufHost; + VOID *Mapping; + EFI_PHYSICAL_ADDRESS MappedAddr; + UINTN Bytes; + EFI_STATUS Status; + + PciIo = Pool->PciIo; + + Block = AllocateZeroPool (sizeof (USBHC_MEM_BLOCK)); + if (Block == NULL) { + return NULL; + } + + // + // each bit in the bit array represents USBHC_MEM_UNIT + // bytes of memory in the memory block. + // + ASSERT (USBHC_MEM_UNIT * 8 <= EFI_PAGE_SIZE); + + Block->BufLen = EFI_PAGES_TO_SIZE (Pages); + Block->BitsLen = Block->BufLen / (USBHC_MEM_UNIT * 8); + Block->Bits = AllocateZeroPool (Block->BitsLen); + + if (Block->Bits == NULL) { + gBS->FreePool (Block); + return NULL; + } + + // + // Allocate the number of Pages of memory, then map it for + // bus master read and write. + // + Status = PciIo->AllocateBuffer ( + PciIo, + AllocateAnyPages, + EfiBootServicesData, + Pages, + &BufHost, + 0 + ); + + if (EFI_ERROR (Status)) { + goto FREE_BITARRAY; + } + + Bytes = EFI_PAGES_TO_SIZE (Pages); + Status = PciIo->Map ( + PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + BufHost, + &Bytes, + &MappedAddr, + &Mapping + ); + + if (EFI_ERROR (Status) || (Bytes != EFI_PAGES_TO_SIZE (Pages))) { + goto FREE_BUFFER; + } + + // + // Check whether the data structure used by the host controller + // should be restricted into the same 4G + // + if (Pool->Check4G && (Pool->Which4G != USB_HC_HIGH_32BIT (MappedAddr))) { + PciIo->Unmap (PciIo, Mapping); + goto FREE_BUFFER; + } + + Block->BufHost = BufHost; + Block->Buf = (UINT8 *) ((UINTN) MappedAddr); + Block->Mapping = Mapping; + + DEBUG ((mUsbHcDebugLevel, "UsbHcAllocMemBlock: block %x created with buffer %x\n", + Block, Block->Buf)); + + return Block; + +FREE_BUFFER: + PciIo->FreeBuffer (PciIo, Pages, BufHost); + +FREE_BITARRAY: + gBS->FreePool (Block->Bits); + gBS->FreePool (Block); + return NULL; +} + + +/** + Free the memory block from the memory pool + + @param Pool The memory pool to free the block from + @param Block The memory block to free + + @return VOID + +**/ +STATIC +VOID +UsbHcFreeMemBlock ( + IN USBHC_MEM_POOL *Pool, + IN USBHC_MEM_BLOCK *Block + ) +{ + EFI_PCI_IO_PROTOCOL *PciIo; + + ASSERT ((Pool != NULL) && (Block != NULL)); + + PciIo = Pool->PciIo; + + // + // Unmap the common buffer then free the structures + // + PciIo->Unmap (PciIo, Block->Mapping); + PciIo->FreeBuffer (PciIo, EFI_SIZE_TO_PAGES (Block->BufLen), Block->BufHost); + + gBS->FreePool (Block->Bits); + gBS->FreePool (Block); +} + + +/** + Alloc some memory from the block + + @param Block The memory block to allocate memory from + @param Mem The variable to store the memory allocated + @param Units Number of memory units to allocate + + @return EFI_SUCCESS : The needed memory is allocated + @return EFI_NOT_FOUND : Can't find the free memory + +**/ +STATIC +VOID * +UsbHcAllocMemFromBlock ( + IN USBHC_MEM_BLOCK *Block, + IN UINTN Units + ) +{ + UINTN Byte; + UINT8 Bit; + UINTN StartByte; + UINT8 StartBit; + UINTN Available; + UINTN Count; + + ASSERT ((Block != 0) && (Units != 0)); + + StartByte = 0; + StartBit = 0; + Available = 0; + + for (Byte = 0, Bit = 0; Byte < Block->BitsLen;) { + // + // If current bit is zero, the corresponding memory unit is + // available, otherwise we need to restart our searching. + // Available counts the consective number of zero bit. + // + if (!USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)) { + Available++; + + if (Available >= Units) { + break; + } + + NEXT_BIT (Byte, Bit); + + } else { + NEXT_BIT (Byte, Bit); + + Available = 0; + StartByte = Byte; + StartBit = Bit; + } + } + + if (Available < Units) { + return NULL; + } + + // + // Mark the memory as allocated + // + Byte = StartByte; + Bit = StartBit; + + for (Count = 0; Count < Units; Count++) { + ASSERT (!USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)); + + Block->Bits[Byte] |= USB_HC_BIT (Bit); + NEXT_BIT (Byte, Bit); + } + + return Block->Buf + (StartByte * 8 + StartBit) * USBHC_MEM_UNIT; +} + + +/** + Insert the memory block to the pool's list of the blocks + + @param Head The head of the memory pool's block list + @param Block The memory block to insert + + @return VOID + +**/ +STATIC +VOID +UsbHcInsertMemBlockToPool ( + IN USBHC_MEM_BLOCK *Head, + IN USBHC_MEM_BLOCK *Block + ) +{ + ASSERT ((Head != NULL) && (Block != NULL)); + Block->Next = Head->Next; + Head->Next = Block; +} + + +/** + Is the memory block empty? + + @param Block The memory block to check + + @return TRUE : The memory block is empty + @return FALSE : The memory block isn't empty + +**/ +STATIC +BOOLEAN +UsbHcIsMemBlockEmpty ( + IN USBHC_MEM_BLOCK *Block + ) +{ + UINTN Index; + + for (Index = 0; Index < Block->BitsLen; Index++) { + if (Block->Bits[Index] != 0) { + return FALSE; + } + } + + return TRUE; +} + + +/** + Unlink the memory block from the pool's list + + @param Head The block list head of the memory's pool + @param BlockToUnlink The memory block to unlink. + + @return VOID + +**/ +STATIC +VOID +UsbHcUnlinkMemBlock ( + IN USBHC_MEM_BLOCK *Head, + IN USBHC_MEM_BLOCK *BlockToUnlink + ) +{ + USBHC_MEM_BLOCK *Block; + + ASSERT ((Head != NULL) && (BlockToUnlink != NULL)); + + for (Block = Head; Block != NULL; Block = Block->Next) { + if (Block->Next == BlockToUnlink) { + Block->Next = BlockToUnlink->Next; + BlockToUnlink->Next = NULL; + break; + } + } +} + + +/** + Initialize the memory management pool for the host controller + + @param Pool The USB memory pool to initialize + @param PciIo The PciIo that can be used to access the host controller + @param Check4G Whether the host controller requires allocated memory + from one 4G address space. + @param Which4G The 4G memory area each memory allocated should be from + + @return EFI_SUCCESS : The memory pool is initialized + @return EFI_OUT_OF_RESOURCE : Fail to init the memory pool + +**/ +USBHC_MEM_POOL * +UsbHcInitMemPool ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN BOOLEAN Check4G, + IN UINT32 Which4G + ) +{ + USBHC_MEM_POOL *Pool; + + Pool = AllocatePool (sizeof (USBHC_MEM_POOL)); + + if (Pool == NULL) { + return Pool; + } + + Pool->PciIo = PciIo; + Pool->Check4G = Check4G; + Pool->Which4G = Which4G; + Pool->Head = UsbHcAllocMemBlock (Pool, USBHC_MEM_DEFAULT_PAGES); + + if (Pool->Head == NULL) { + gBS->FreePool (Pool); + Pool = NULL; + } + + return Pool; +} + + +/** + Release the memory management pool + + @param Pool The USB memory pool to free + + @return EFI_SUCCESS : The memory pool is freed + @return EFI_DEVICE_ERROR : Failed to free the memory pool + +**/ +EFI_STATUS +UsbHcFreeMemPool ( + IN USBHC_MEM_POOL *Pool + ) +{ + USBHC_MEM_BLOCK *Block; + + ASSERT (Pool->Head != NULL); + + // + // Unlink all the memory blocks from the pool, then free them. + // UsbHcUnlinkMemBlock can't be used to unlink and free the + // first block. + // + for (Block = Pool->Head->Next; Block != NULL; Block = Pool->Head->Next) { + UsbHcUnlinkMemBlock (Pool->Head, Block); + UsbHcFreeMemBlock (Pool, Block); + } + + UsbHcFreeMemBlock (Pool, Pool->Head); + gBS->FreePool (Pool); + return EFI_SUCCESS; +} + + +/** + Allocate some memory from the host controller's memory pool + which can be used to communicate with host controller. + + @param Pool The host controller's memory pool + @param Size Size of the memory to allocate + + @return The allocated memory or NULL + +**/ +VOID * +UsbHcAllocateMem ( + IN USBHC_MEM_POOL *Pool, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + USBHC_MEM_BLOCK *NewBlock; + VOID *Mem; + UINTN AllocSize; + UINTN Pages; + + Mem = NULL; + AllocSize = USBHC_MEM_ROUND (Size); + Head = Pool->Head; + ASSERT (Head != NULL); + + // + // First check whether current memory blocks can satisfy the allocation. + // + for (Block = Head; Block != NULL; Block = Block->Next) { + Mem = UsbHcAllocMemFromBlock (Block, AllocSize / USBHC_MEM_UNIT); + + if (Mem != NULL) { + ZeroMem (Mem, Size); + break; + } + } + + if (Mem != NULL) { + return Mem; + } + + // + // Create a new memory block if there is not enough memory + // in the pool. If the allocation size is larger than the + // default page number, just allocate a large enough memory + // block. Otherwise allocate default pages. + // + if (AllocSize > EFI_PAGES_TO_SIZE (USBHC_MEM_DEFAULT_PAGES)) { + Pages = EFI_SIZE_TO_PAGES (AllocSize) + 1; + } else { + Pages = USBHC_MEM_DEFAULT_PAGES; + } + + NewBlock = UsbHcAllocMemBlock (Pool, Pages); + + if (NewBlock == NULL) { + DEBUG ((mUsbHcDebugLevel, "UsbHcAllocateMem: failed to allocate block\n")); + return NULL; + } + + // + // Add the new memory block to the pool, then allocate memory from it + // + UsbHcInsertMemBlockToPool (Head, NewBlock); + Mem = UsbHcAllocMemFromBlock (NewBlock, AllocSize / USBHC_MEM_UNIT); + + if (Mem != NULL) { + ZeroMem (Mem, Size); + } + + return Mem; +} + + +/** + Free the allocated memory back to the memory pool + + @param Pool The memory pool of the host controller + @param Mem The memory to free + @param Size The size of the memory to free + + @return VOID + +**/ +VOID +UsbHcFreeMem ( + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + UINT8 *ToFree; + UINTN AllocSize; + UINTN Byte; + UINTN Bit; + UINTN Count; + + Head = Pool->Head; + AllocSize = USBHC_MEM_ROUND (Size); + ToFree = (UINT8 *) Mem; + + for (Block = Head; Block != NULL; Block = Block->Next) { + // + // scan the memory block list for the memory block that + // completely contains the memory to free. + // + if ((Block->Buf <= ToFree) && ((ToFree + AllocSize) <= (Block->Buf + Block->BufLen))) { + // + // compute the start byte and bit in the bit array + // + Byte = ((ToFree - Block->Buf) / USBHC_MEM_UNIT) / 8; + Bit = ((ToFree - Block->Buf) / USBHC_MEM_UNIT) % 8; + + // + // reset associated bits in bit arry + // + for (Count = 0; Count < (AllocSize / USBHC_MEM_UNIT); Count++) { + ASSERT (USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)); + + Block->Bits[Byte] ^= (UINT8) USB_HC_BIT (Bit); + NEXT_BIT (Byte, Bit); + } + + break; + } + } + + // + // If Block == NULL, it means that the current memory isn't + // in the host controller's pool. This is critical because + // the caller has passed in a wrong memory point + // + ASSERT (Block != NULL); + + // + // Release the current memory block if it is empty and not the head + // + if ((Block != Head) && UsbHcIsMemBlockEmpty (Block)) { + DEBUG ((mUsbHcDebugLevel, "UsbHcFreeMem: block %x is empty, recycle\n", Block)); + + UsbHcUnlinkMemBlock (Head, Block); + UsbHcFreeMemBlock (Pool, Block); + } + + return ; +} diff --git a/MdeModulePkg/Bus/Pci/UhciDxe/UsbHcMem.h b/MdeModulePkg/Bus/Pci/UhciDxe/UsbHcMem.h new file mode 100644 index 0000000000..df9ab0ec38 --- /dev/null +++ b/MdeModulePkg/Bus/Pci/UhciDxe/UsbHcMem.h @@ -0,0 +1,167 @@ +/** @file + +Copyright (c) 2007, Intel Corporation +All rights reserved. This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +Module Name: + + EhciMem.h + +Abstract: + + This file contains the definination for host controller memory management routines + +Revision History + +**/ + +#ifndef _EFI_EHCI_MEM_H_ +#define _EFI_EHCI_MEM_H_ + +#include + +#define USB_HC_BIT(a) ((UINTN)(1 << (a))) + +#define USB_HC_BIT_IS_SET(Data, Bit) \ + ((BOOLEAN)(((Data) & USB_HC_BIT(Bit)) == USB_HC_BIT(Bit))) + +#define USB_HC_HIGH_32BIT(Addr64) \ + ((UINT32)(RShiftU64((UINTN)(Addr64), 32) & 0XFFFFFFFF)) + +typedef struct _USBHC_MEM_BLOCK USBHC_MEM_BLOCK; + +typedef struct _USBHC_MEM_BLOCK { + UINT8 *Bits; // Bit array to record which unit is allocated + UINTN BitsLen; + UINT8 *Buf; + UINT8 *BufHost; + UINTN BufLen; // Memory size in bytes + VOID *Mapping; + USBHC_MEM_BLOCK *Next; +} USBHC_MEM_BLOCK; + +// +// USBHC_MEM_POOL is used to manage the memory used by USB +// host controller. EHCI requires the control memory and transfer +// data to be on the same 4G memory. +// +typedef struct _USBHC_MEM_POOL { + EFI_PCI_IO_PROTOCOL *PciIo; + BOOLEAN Check4G; + UINT32 Which4G; + USBHC_MEM_BLOCK *Head; +} USBHC_MEM_POOL; + +enum { + USBHC_MEM_UNIT = 64, // Memory allocation unit, must be 2^n, n>4 + + USBHC_MEM_UNIT_MASK = USBHC_MEM_UNIT - 1, + USBHC_MEM_DEFAULT_PAGES = 16, +}; + +#define USBHC_MEM_ROUND(Len) (((Len) + USBHC_MEM_UNIT_MASK) & (~USBHC_MEM_UNIT_MASK)) + +// +// Advance the byte and bit to the next bit, adjust byte accordingly. +// +#define NEXT_BIT(Byte, Bit) \ + do { \ + (Bit)++; \ + if ((Bit) > 7) { \ + (Byte)++; \ + (Bit) = 0; \ + } \ + } while (0) + + + +USBHC_MEM_POOL * +UsbHcInitMemPool ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN BOOLEAN Check4G, + IN UINT32 Which4G + ) +/*++ + +Routine Description: + + Initialize the memory management pool for the host controller + +Arguments: + + Pool - The USB memory pool to initialize + PciIo - The PciIo that can be used to access the host controller + Check4G - Whether the host controller requires allocated memory + from one 4G address space. + Which4G - The 4G memory area each memory allocated should be from + +Returns: + + EFI_SUCCESS : The memory pool is initialized + EFI_OUT_OF_RESOURCE : Fail to init the memory pool + +--*/ +; + + + +/** + Release the memory management pool + + @param Pool The USB memory pool to free + + @return EFI_SUCCESS : The memory pool is freed + @return EFI_DEVICE_ERROR : Failed to free the memory pool + +**/ +EFI_STATUS +UsbHcFreeMemPool ( + IN USBHC_MEM_POOL *Pool + ) +; + + + +/** + Allocate some memory from the host controller's memory pool + which can be used to communicate with host controller. + + @param Pool The host controller's memory pool + @param Size Size of the memory to allocate + + @return The allocated memory or NULL + +**/ +VOID * +UsbHcAllocateMem ( + IN USBHC_MEM_POOL *Pool, + IN UINTN Size + ) +; + + + +/** + Free the allocated memory back to the memory pool + + @param Pool The memory pool of the host controller + @param Mem The memory to free + @param Size The size of the memory to free + + @return VOID + +**/ +VOID +UsbHcFreeMem ( + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +; +#endif