/** @file Usb bus enumeration support. Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "UsbBus.h" /** Return the endpoint descriptor in this interface. @param UsbIf The interface to search in. @param EpAddr The address of the endpoint to return. @return The endpoint descriptor or NULL. **/ USB_ENDPOINT_DESC * UsbGetEndpointDesc ( IN USB_INTERFACE *UsbIf, IN UINT8 EpAddr ) { USB_ENDPOINT_DESC *EpDesc; UINT8 Index; UINT8 NumEndpoints; NumEndpoints = UsbIf->IfSetting->Desc.NumEndpoints; for (Index = 0; Index < NumEndpoints; Index++) { EpDesc = UsbIf->IfSetting->Endpoints[Index]; if (EpDesc->Desc.EndpointAddress == EpAddr) { return EpDesc; } } return NULL; } /** Free the resource used by USB interface. @param UsbIf The USB interface to free. @retval EFI_ACCESS_DENIED The interface is still occupied. @retval EFI_SUCCESS The interface is freed. **/ EFI_STATUS UsbFreeInterface ( IN USB_INTERFACE *UsbIf ) { EFI_STATUS Status; UsbCloseHostProtoByChild (UsbIf->Device->Bus, UsbIf->Handle); Status = gBS->UninstallMultipleProtocolInterfaces ( UsbIf->Handle, &gEfiDevicePathProtocolGuid, UsbIf->DevicePath, &gEfiUsbIoProtocolGuid, &UsbIf->UsbIo, NULL ); if (!EFI_ERROR (Status)) { if (UsbIf->DevicePath != NULL) { FreePool (UsbIf->DevicePath); } FreePool (UsbIf); } else { UsbOpenHostProtoByChild (UsbIf->Device->Bus, UsbIf->Handle); } return Status; } /** Create an interface for the descriptor IfDesc. Each device's configuration can have several interfaces. @param Device The device has the interface descriptor. @param IfDesc The interface descriptor. @return The created USB interface for the descriptor, or NULL. **/ USB_INTERFACE * UsbCreateInterface ( IN USB_DEVICE *Device, IN USB_INTERFACE_DESC *IfDesc ) { USB_DEVICE_PATH UsbNode; USB_INTERFACE *UsbIf; USB_INTERFACE *HubIf; EFI_STATUS Status; UsbIf = AllocateZeroPool (sizeof (USB_INTERFACE)); if (UsbIf == NULL) { return NULL; } UsbIf->Signature = USB_INTERFACE_SIGNATURE; UsbIf->Device = Device; UsbIf->IfDesc = IfDesc; ASSERT (IfDesc->ActiveIndex < USB_MAX_INTERFACE_SETTING); UsbIf->IfSetting = IfDesc->Settings[IfDesc->ActiveIndex]; CopyMem ( &(UsbIf->UsbIo), &mUsbIoProtocol, sizeof (EFI_USB_IO_PROTOCOL) ); // // Install protocols for USBIO and device path // UsbNode.Header.Type = MESSAGING_DEVICE_PATH; UsbNode.Header.SubType = MSG_USB_DP; UsbNode.ParentPortNumber = Device->ParentPort; UsbNode.InterfaceNumber = UsbIf->IfSetting->Desc.InterfaceNumber; SetDevicePathNodeLength (&UsbNode.Header, sizeof (UsbNode)); HubIf = Device->ParentIf; ASSERT (HubIf != NULL); UsbIf->DevicePath = AppendDevicePathNode (HubIf->DevicePath, &UsbNode.Header); if (UsbIf->DevicePath == NULL) { DEBUG ((DEBUG_ERROR, "UsbCreateInterface: failed to create device path\n")); Status = EFI_OUT_OF_RESOURCES; goto ON_ERROR; } Status = gBS->InstallMultipleProtocolInterfaces ( &UsbIf->Handle, &gEfiDevicePathProtocolGuid, UsbIf->DevicePath, &gEfiUsbIoProtocolGuid, &UsbIf->UsbIo, NULL ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbCreateInterface: failed to install UsbIo - %r\n", Status)); goto ON_ERROR; } // // Open USB Host Controller Protocol by Child // Status = UsbOpenHostProtoByChild (Device->Bus, UsbIf->Handle); if (EFI_ERROR (Status)) { gBS->UninstallMultipleProtocolInterfaces ( UsbIf->Handle, &gEfiDevicePathProtocolGuid, UsbIf->DevicePath, &gEfiUsbIoProtocolGuid, &UsbIf->UsbIo, NULL ); DEBUG ((DEBUG_ERROR, "UsbCreateInterface: failed to open host for child - %r\n", Status)); goto ON_ERROR; } return UsbIf; ON_ERROR: if (UsbIf->DevicePath != NULL) { FreePool (UsbIf->DevicePath); } FreePool (UsbIf); return NULL; } /** Free the resource used by this USB device. @param Device The USB device to free. **/ VOID UsbFreeDevice ( IN USB_DEVICE *Device ) { if (Device->DevDesc != NULL) { UsbFreeDevDesc (Device->DevDesc); } gBS->FreePool (Device); } /** Create a device which is on the parent's ParentPort port. @param ParentIf The parent HUB interface. @param ParentPort The port on the HUB this device is connected to. @return Created USB device, Or NULL. **/ USB_DEVICE * UsbCreateDevice ( IN USB_INTERFACE *ParentIf, IN UINT8 ParentPort ) { USB_DEVICE *Device; ASSERT (ParentIf != NULL); Device = AllocateZeroPool (sizeof (USB_DEVICE)); if (Device == NULL) { return NULL; } Device->Bus = ParentIf->Device->Bus; Device->MaxPacket0 = 8; Device->ParentAddr = ParentIf->Device->Address; Device->ParentIf = ParentIf; Device->ParentPort = ParentPort; Device->Tier = (UINT8)(ParentIf->Device->Tier + 1); return Device; } /** Connect the USB interface with its driver. EFI USB bus will create a USB interface for each separate interface descriptor. @param UsbIf The interface to connect driver to. @return EFI_SUCCESS Interface is managed by some driver. @return Others Failed to locate a driver for this interface. **/ EFI_STATUS UsbConnectDriver ( IN USB_INTERFACE *UsbIf ) { EFI_STATUS Status; EFI_TPL OldTpl; Status = EFI_SUCCESS; // // Hub is maintained by the USB bus driver. Otherwise try to // connect drivers with this interface // if (UsbIsHubInterface (UsbIf)) { DEBUG ((DEBUG_INFO, "UsbConnectDriver: found a hub device\n")); Status = mUsbHubApi.Init (UsbIf); } else { // // This function is called in both UsbIoControlTransfer and // the timer callback in hub enumeration. So, at least it is // called at TPL_CALLBACK. Some driver sitting on USB has // twisted TPL used. It should be no problem for us to connect // or disconnect at CALLBACK. // // // Only recursively wanted usb child device // if (UsbBusIsWantedUsbIO (UsbIf->Device->Bus, UsbIf)) { OldTpl = UsbGetCurrentTpl (); DEBUG ((DEBUG_INFO, "UsbConnectDriver: TPL before connect is %d, %p\n", (UINT32)OldTpl, UsbIf->Handle)); gBS->RestoreTPL (TPL_CALLBACK); Status = gBS->ConnectController (UsbIf->Handle, NULL, NULL, TRUE); UsbIf->IsManaged = (BOOLEAN) !EFI_ERROR (Status); DEBUG ((DEBUG_INFO, "UsbConnectDriver: TPL after connect is %d\n", (UINT32)UsbGetCurrentTpl ())); ASSERT (UsbGetCurrentTpl () == TPL_CALLBACK); gBS->RaiseTPL (OldTpl); } } return Status; } /** Select an alternate setting for the interface. Each interface can have several mutually exclusive settings. Only one setting is active. It will also reset its endpoints' toggle to zero. @param IfDesc The interface descriptor to set. @param Alternate The alternate setting number to locate. @retval EFI_NOT_FOUND There is no setting with this alternate index. @retval EFI_SUCCESS The interface is set to Alternate setting. **/ EFI_STATUS UsbSelectSetting ( IN USB_INTERFACE_DESC *IfDesc, IN UINT8 Alternate ) { USB_INTERFACE_SETTING *Setting; UINTN Index; // // Locate the active alternate setting // Setting = NULL; for (Index = 0; Index < IfDesc->NumOfSetting; Index++) { ASSERT (Index < USB_MAX_INTERFACE_SETTING); Setting = IfDesc->Settings[Index]; if (Setting->Desc.AlternateSetting == Alternate) { break; } } if (Index == IfDesc->NumOfSetting) { return EFI_NOT_FOUND; } IfDesc->ActiveIndex = Index; ASSERT (Setting != NULL); DEBUG (( DEBUG_INFO, "UsbSelectSetting: setting %d selected for interface %d\n", Alternate, Setting->Desc.InterfaceNumber )); // // Reset the endpoint toggle to zero // for (Index = 0; Index < Setting->Desc.NumEndpoints; Index++) { Setting->Endpoints[Index]->Toggle = 0; } return EFI_SUCCESS; } /** Select a new configuration for the device. Each device may support several configurations. @param Device The device to select configuration. @param ConfigValue The index of the configuration ( != 0). @retval EFI_NOT_FOUND There is no configuration with the index. @retval EFI_OUT_OF_RESOURCES Failed to allocate resource. @retval EFI_SUCCESS The configuration is selected. **/ EFI_STATUS UsbSelectConfig ( IN USB_DEVICE *Device, IN UINT8 ConfigValue ) { USB_DEVICE_DESC *DevDesc; USB_CONFIG_DESC *ConfigDesc; USB_INTERFACE_DESC *IfDesc; USB_INTERFACE *UsbIf; EFI_STATUS Status; UINT8 Index; // // Locate the active config, then set the device's pointer // DevDesc = Device->DevDesc; ConfigDesc = NULL; for (Index = 0; Index < DevDesc->Desc.NumConfigurations; Index++) { ConfigDesc = DevDesc->Configs[Index]; if (ConfigDesc->Desc.ConfigurationValue == ConfigValue) { break; } } if (Index == DevDesc->Desc.NumConfigurations) { return EFI_NOT_FOUND; } Device->ActiveConfig = ConfigDesc; DEBUG (( DEBUG_INFO, "UsbSelectConfig: config %d selected for device %d\n", ConfigValue, Device->Address )); // // Create interfaces for each USB interface descriptor. // for (Index = 0; Index < ConfigDesc->Desc.NumInterfaces; Index++) { // // First select the default interface setting, and reset // the endpoint toggles to zero for its endpoints. // IfDesc = ConfigDesc->Interfaces[Index]; UsbSelectSetting (IfDesc, IfDesc->Settings[0]->Desc.AlternateSetting); // // Create a USB_INTERFACE and install USB_IO and other protocols // UsbIf = UsbCreateInterface (Device, ConfigDesc->Interfaces[Index]); if (UsbIf == NULL) { Device->NumOfInterface = Index; return EFI_OUT_OF_RESOURCES; } ASSERT (Index < USB_MAX_INTERFACE); Device->Interfaces[Index] = UsbIf; // // Connect the device to drivers, if it failed, ignore // the error. Don't let the unsupported interfaces to block // the supported interfaces. // Status = UsbConnectDriver (UsbIf); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_WARN, "UsbSelectConfig: failed to connect driver - %r, ignored\n", Status )); } } Device->NumOfInterface = Index; return EFI_SUCCESS; } /** Disconnect the USB interface with its driver. @param UsbIf The interface to disconnect driver from. **/ EFI_STATUS UsbDisconnectDriver ( IN USB_INTERFACE *UsbIf ) { EFI_TPL OldTpl; EFI_STATUS Status; // // Release the hub if it's a hub controller, otherwise // disconnect the driver if it is managed by other drivers. // Status = EFI_SUCCESS; if (UsbIf->IsHub) { Status = UsbIf->HubApi->Release (UsbIf); } else if (UsbIf->IsManaged) { // // This function is called in both UsbIoControlTransfer and // the timer callback in hub enumeration. So, at least it is // called at TPL_CALLBACK. Some driver sitting on USB has // twisted TPL used. It should be no problem for us to connect // or disconnect at CALLBACK. // OldTpl = UsbGetCurrentTpl (); DEBUG ((DEBUG_INFO, "UsbDisconnectDriver: old TPL is %d, %p\n", (UINT32)OldTpl, UsbIf->Handle)); gBS->RestoreTPL (TPL_CALLBACK); Status = gBS->DisconnectController (UsbIf->Handle, NULL, NULL); if (!EFI_ERROR (Status)) { UsbIf->IsManaged = FALSE; } DEBUG ((DEBUG_INFO, "UsbDisconnectDriver: TPL after disconnect is %d, %d\n", (UINT32)UsbGetCurrentTpl (), Status)); ASSERT (UsbGetCurrentTpl () == TPL_CALLBACK); gBS->RaiseTPL (OldTpl); } return Status; } /** Remove the current device configuration. @param Device The USB device to remove configuration from. **/ EFI_STATUS UsbRemoveConfig ( IN USB_DEVICE *Device ) { USB_INTERFACE *UsbIf; UINTN Index; EFI_STATUS Status; EFI_STATUS ReturnStatus; // // Remove each interface of the device // ReturnStatus = EFI_SUCCESS; for (Index = 0; Index < Device->NumOfInterface; Index++) { ASSERT (Index < USB_MAX_INTERFACE); UsbIf = Device->Interfaces[Index]; if (UsbIf == NULL) { continue; } Status = UsbDisconnectDriver (UsbIf); if (!EFI_ERROR (Status)) { Status = UsbFreeInterface (UsbIf); if (EFI_ERROR (Status)) { UsbConnectDriver (UsbIf); } } if (!EFI_ERROR (Status)) { Device->Interfaces[Index] = NULL; } else { ReturnStatus = Status; } } Device->ActiveConfig = NULL; return ReturnStatus; } /** Remove the device and all its children from the bus. @param Device The device to remove. @retval EFI_SUCCESS The device is removed. **/ EFI_STATUS UsbRemoveDevice ( IN USB_DEVICE *Device ) { USB_BUS *Bus; USB_DEVICE *Child; EFI_STATUS Status; EFI_STATUS ReturnStatus; UINTN Index; Bus = Device->Bus; // // Remove all the devices on its downstream ports. Search from devices[1]. // Devices[0] is the root hub. // ReturnStatus = EFI_SUCCESS; for (Index = 1; Index < Bus->MaxDevices; Index++) { Child = Bus->Devices[Index]; if ((Child == NULL) || (Child->ParentAddr != Device->Address)) { continue; } Status = UsbRemoveDevice (Child); if (!EFI_ERROR (Status)) { Bus->Devices[Index] = NULL; } else { Bus->Devices[Index]->DisconnectFail = TRUE; ReturnStatus = Status; DEBUG ((DEBUG_INFO, "UsbRemoveDevice: failed to remove child %p at parent %p\n", Child, Device)); } } if (EFI_ERROR (ReturnStatus)) { return ReturnStatus; } Status = UsbRemoveConfig (Device); if (!EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "UsbRemoveDevice: device %d removed\n", Device->Address)); ASSERT (Device->Address < Bus->MaxDevices); Bus->Devices[Device->Address] = NULL; UsbFreeDevice (Device); } else { Bus->Devices[Device->Address]->DisconnectFail = TRUE; } return Status; } /** Find the child device on the hub's port. @param HubIf The hub interface. @param Port The port of the hub this child is connected to. @return The device on the hub's port, or NULL if there is none. **/ USB_DEVICE * UsbFindChild ( IN USB_INTERFACE *HubIf, IN UINT8 Port ) { USB_DEVICE *Device; USB_BUS *Bus; UINTN Index; Bus = HubIf->Device->Bus; // // Start checking from device 1, device 0 is the root hub // for (Index = 1; Index < Bus->MaxDevices; Index++) { Device = Bus->Devices[Index]; if ((Device != NULL) && (Device->ParentAddr == HubIf->Device->Address) && (Device->ParentPort == Port)) { return Device; } } return NULL; } /** Enumerate and configure the new device on the port of this HUB interface. @param HubIf The HUB that has the device connected. @param Port The port index of the hub (started with zero). @param ResetIsNeeded The boolean to control whether skip the reset of the port. @retval EFI_SUCCESS The device is enumerated (added or removed). @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for the device. @retval Others Failed to enumerate the device. **/ EFI_STATUS UsbEnumerateNewDev ( IN USB_INTERFACE *HubIf, IN UINT8 Port, IN BOOLEAN ResetIsNeeded ) { USB_BUS *Bus; USB_HUB_API *HubApi; USB_DEVICE *Child; USB_DEVICE *Parent; EFI_USB_PORT_STATUS PortState; UINTN Address; UINT8 Config; EFI_STATUS Status; Parent = HubIf->Device; Bus = Parent->Bus; HubApi = HubIf->HubApi; Address = Bus->MaxDevices; gBS->Stall (USB_WAIT_PORT_STABLE_STALL); // // Hub resets the device for at least 10 milliseconds. // Host learns device speed. If device is of low/full speed // and the hub is a EHCI root hub, ResetPort will release // the device to its companion UHCI and return an error. // if (ResetIsNeeded) { Status = HubApi->ResetPort (HubIf, Port); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to reset port %d - %r\n", Port, Status)); return Status; } DEBUG ((DEBUG_INFO, "UsbEnumerateNewDev: hub port %d is reset\n", Port)); } else { DEBUG ((DEBUG_INFO, "UsbEnumerateNewDev: hub port %d reset is skipped\n", Port)); } Child = UsbCreateDevice (HubIf, Port); if (Child == NULL) { return EFI_OUT_OF_RESOURCES; } // // OK, now identify the device speed. After reset, hub // fully knows the actual device speed. // Status = HubApi->GetPortStatus (HubIf, Port, &PortState); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to get speed of port %d\n", Port)); goto ON_ERROR; } if (!USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_CONNECTION)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: No device present at port %d\n", Port)); Status = EFI_NOT_FOUND; goto ON_ERROR; } else if (USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_SUPER_SPEED)) { Child->Speed = EFI_USB_SPEED_SUPER; Child->MaxPacket0 = 512; } else if (USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_HIGH_SPEED)) { Child->Speed = EFI_USB_SPEED_HIGH; Child->MaxPacket0 = 64; } else if (USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_LOW_SPEED)) { Child->Speed = EFI_USB_SPEED_LOW; Child->MaxPacket0 = 8; } else { Child->Speed = EFI_USB_SPEED_FULL; Child->MaxPacket0 = 8; } DEBUG ((DEBUG_INFO, "UsbEnumerateNewDev: device is of %d speed\n", Child->Speed)); if (((Child->Speed == EFI_USB_SPEED_LOW) || (Child->Speed == EFI_USB_SPEED_FULL)) && (Parent->Speed == EFI_USB_SPEED_HIGH)) { // // If the child is a low or full speed device, it is necessary to // set the transaction translator. Port TT is 1-based. // This is quite simple: // 1. if parent is of high speed, then parent is our translator // 2. otherwise use parent's translator. // Child->Translator.TranslatorHubAddress = Parent->Address; Child->Translator.TranslatorPortNumber = (UINT8)(Port + 1); } else { Child->Translator = Parent->Translator; } DEBUG (( DEBUG_INFO, "UsbEnumerateNewDev: device uses translator (%d, %d)\n", Child->Translator.TranslatorHubAddress, Child->Translator.TranslatorPortNumber )); // // After port is reset, hub establishes a signal path between // the device and host (DEFAULT state). Device's registers are // reset, use default address 0 (host enumerates one device at // a time) , and ready to respond to control transfer at EP 0. // // // Host assigns an address to the device. Device completes the // status stage with default address, then switches to new address. // ADDRESS state. Address zero is reserved for root hub. // ASSERT (Bus->MaxDevices <= 256); for (Address = 1; Address < Bus->MaxDevices; Address++) { if (Bus->Devices[Address] == NULL) { break; } } if (Address >= Bus->MaxDevices) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: address pool is full for port %d\n", Port)); Status = EFI_ACCESS_DENIED; goto ON_ERROR; } Status = UsbSetAddress (Child, (UINT8)Address); Child->Address = (UINT8)Address; Bus->Devices[Address] = Child; if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to set device address - %r\n", Status)); goto ON_ERROR; } gBS->Stall (USB_SET_DEVICE_ADDRESS_STALL); DEBUG ((DEBUG_INFO, "UsbEnumerateNewDev: device is now ADDRESSED at %d\n", Address)); // // Host sends a Get_Descriptor request to learn the max packet // size of default pipe (only part of the device's descriptor). // Status = UsbGetMaxPacketSize0 (Child); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to get max packet for EP 0 - %r\n", Status)); goto ON_ERROR; } DEBUG ((DEBUG_INFO, "UsbEnumerateNewDev: max packet size for EP 0 is %d\n", Child->MaxPacket0)); // // Host learns about the device's abilities by requesting device's // entire descriptions. // Status = UsbBuildDescTable (Child); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to build descriptor table - %r\n", Status)); goto ON_ERROR; } // // Select a default configuration: UEFI must set the configuration // before the driver can connect to the device. // Config = Child->DevDesc->Configs[0]->Desc.ConfigurationValue; Status = UsbSetConfig (Child, Config); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to set configure %d - %r\n", Config, Status)); goto ON_ERROR; } DEBUG ((DEBUG_INFO, "UsbEnumerateNewDev: device %d is now in CONFIGED state\n", Address)); // // Host assigns and loads a device driver. // Status = UsbSelectConfig (Child, Config); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumerateNewDev: failed to create interfaces - %r\n", Status)); goto ON_ERROR; } // // Report Status Code to indicate USB device has been detected by hotplug // REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_PROGRESS_CODE, (EFI_IO_BUS_USB | EFI_IOB_PC_HOTPLUG), Bus->DevicePath ); return EFI_SUCCESS; ON_ERROR: // // If reach here, it means the enumeration process on a given port is interrupted due to error. // The s/w resources, including the assigned address(Address) and the allocated usb device data // structure(Bus->Devices[Address]), will NOT be freed here. These resources will be freed when // the device is unplugged from the port or DriverBindingStop() is invoked. // // This way is used to co-work with the lower layer EDKII UHCI/EHCI/XHCI host controller driver. // It's mainly because to keep UEFI spec unchanged EDKII XHCI driver have to maintain a state machine // to keep track of the mapping between actual address and request address. If the request address // (Address) is freed here, the Address value will be used by next enumerated device. Then EDKII XHCI // host controller driver will have wrong information, which will cause further transaction error. // // EDKII UHCI/EHCI doesn't get impacted as it's make sense to reserve s/w resource till it gets unplugged. // return Status; } /** Process the events on the port. @param HubIf The HUB that has the device connected. @param Port The port index of the hub (started with zero). @retval EFI_SUCCESS The device is enumerated (added or removed). @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for the device. @retval Others Failed to enumerate the device. **/ EFI_STATUS UsbEnumeratePort ( IN USB_INTERFACE *HubIf, IN UINT8 Port ) { USB_HUB_API *HubApi; USB_DEVICE *Child; EFI_USB_PORT_STATUS PortState; EFI_STATUS Status; Child = NULL; HubApi = HubIf->HubApi; // // Host learns of the new device by polling the hub for port changes. // Status = HubApi->GetPortStatus (HubIf, Port, &PortState); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "UsbEnumeratePort: failed to get state of port %d\n", Port)); return Status; } // // Only handle connection/enable/overcurrent/reset change. // Usb super speed hub may report other changes, such as warm reset change. Ignore them. // if ((PortState.PortChangeStatus & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_OVERCURRENT | USB_PORT_STAT_C_RESET)) == 0) { return EFI_SUCCESS; } DEBUG (( DEBUG_INFO, "UsbEnumeratePort: port %d state - %02x, change - %02x on %p\n", Port, PortState.PortStatus, PortState.PortChangeStatus, HubIf )); // // This driver only process two kinds of events now: over current and // connect/disconnect. Other three events are: ENABLE, SUSPEND, RESET. // ENABLE/RESET is used to reset port. SUSPEND isn't supported. // if (USB_BIT_IS_SET (PortState.PortChangeStatus, USB_PORT_STAT_C_OVERCURRENT)) { if (USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_OVERCURRENT)) { // // Case1: // Both OverCurrent and OverCurrentChange set, means over current occurs, // which probably is caused by short circuit. It has to wait system hardware // to perform recovery. // DEBUG ((DEBUG_ERROR, "UsbEnumeratePort: Critical Over Current (port %d)\n", Port)); return EFI_DEVICE_ERROR; } // // Case2: // Only OverCurrentChange set, means system has been recoveried from // over current. As a result, all ports are nearly power-off, so // it's necessary to detach and enumerate all ports again. // DEBUG ((DEBUG_ERROR, "UsbEnumeratePort: 2.0 device Recovery Over Current (port %d)\n", Port)); } if (USB_BIT_IS_SET (PortState.PortChangeStatus, USB_PORT_STAT_C_ENABLE)) { // // Case3: // 1.1 roothub port reg doesn't reflect over-current state, while its counterpart // on 2.0 roothub does. When over-current has influence on 1.1 device, the port // would be disabled, so it's also necessary to detach and enumerate again. // DEBUG ((DEBUG_ERROR, "UsbEnumeratePort: 1.1 device Recovery Over Current (port %d)\n", Port)); } if (USB_BIT_IS_SET (PortState.PortChangeStatus, USB_PORT_STAT_C_CONNECTION)) { // // Case4: // Device connected or disconnected normally. // DEBUG ((DEBUG_INFO, "UsbEnumeratePort: Device Connect/Disconnect Normally (port %d)\n", Port)); } // // Following as the above cases, it's safety to remove and create again. // Child = UsbFindChild (HubIf, Port); if (Child != NULL) { DEBUG ((DEBUG_INFO, "UsbEnumeratePort: device at port %d removed from root hub %p\n", Port, HubIf)); UsbRemoveDevice (Child); } if (USB_BIT_IS_SET (PortState.PortStatus, USB_PORT_STAT_CONNECTION)) { // // Now, new device connected, enumerate and configure the device // DEBUG ((DEBUG_INFO, "UsbEnumeratePort: new device connected at port %d\n", Port)); if (USB_BIT_IS_SET (PortState.PortChangeStatus, USB_PORT_STAT_C_RESET)) { Status = UsbEnumerateNewDev (HubIf, Port, FALSE); } else { Status = UsbEnumerateNewDev (HubIf, Port, TRUE); } } else { DEBUG ((DEBUG_INFO, "UsbEnumeratePort: device disconnected event on port %d\n", Port)); } HubApi->ClearPortChange (HubIf, Port); return Status; } /** Enumerate all the changed hub ports. @param Event The event that is triggered. @param Context The context to the event. **/ VOID EFIAPI UsbHubEnumeration ( IN EFI_EVENT Event, IN VOID *Context ) { USB_INTERFACE *HubIf; UINT8 Byte; UINT8 Bit; UINT8 Index; USB_DEVICE *Child; ASSERT (Context != NULL); HubIf = (USB_INTERFACE *)Context; for (Index = 0; Index < HubIf->NumOfPort; Index++) { Child = UsbFindChild (HubIf, Index); if ((Child != NULL) && (Child->DisconnectFail == TRUE)) { DEBUG ((DEBUG_INFO, "UsbEnumeratePort: The device disconnect fails at port %d from hub %p, try again\n", Index, HubIf)); UsbRemoveDevice (Child); } } if (HubIf->ChangeMap == NULL) { return; } // // HUB starts its port index with 1. // Byte = 0; Bit = 1; for (Index = 0; Index < HubIf->NumOfPort; Index++) { if (USB_BIT_IS_SET (HubIf->ChangeMap[Byte], USB_BIT (Bit))) { UsbEnumeratePort (HubIf, Index); } USB_NEXT_BIT (Byte, Bit); } UsbHubAckHubStatus (HubIf->Device); gBS->FreePool (HubIf->ChangeMap); HubIf->ChangeMap = NULL; return; } /** Enumerate all the changed hub ports. @param Event The event that is triggered. @param Context The context to the event. **/ VOID EFIAPI UsbRootHubEnumeration ( IN EFI_EVENT Event, IN VOID *Context ) { USB_INTERFACE *RootHub; UINT8 Index; USB_DEVICE *Child; RootHub = (USB_INTERFACE *)Context; for (Index = 0; Index < RootHub->NumOfPort; Index++) { Child = UsbFindChild (RootHub, Index); if ((Child != NULL) && (Child->DisconnectFail == TRUE)) { DEBUG ((DEBUG_INFO, "UsbEnumeratePort: The device disconnect fails at port %d from root hub %p, try again\n", Index, RootHub)); UsbRemoveDevice (Child); } UsbEnumeratePort (RootHub, Index); } }