| /** @file | |
| Unit tests for UsbBusDxe descriptor parsing and root hub reset handling. | |
| Copyright (c) 2026, Intel Corporation. All rights reserved. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Library/GoogleTestLib.h> | |
| extern "C" { | |
| #include <Uefi.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include "../UsbBus.h" | |
| BOOLEAN | |
| UsbIsSSDevice ( | |
| IN EFI_USB_BOS_DESCRIPTOR *BosDesc, | |
| IN UINTN Len | |
| ); | |
| EFI_STATUS | |
| UsbGetDevBosDesc ( | |
| IN USB_DEVICE *UsbDev | |
| ); | |
| EFI_STATUS | |
| UsbRootHubResetPort ( | |
| IN USB_INTERFACE *RootIf, | |
| IN UINT8 Port | |
| ); | |
| } | |
| static EFI_BOOT_SERVICES mBootServices; | |
| static UINTN mFreePoolCount; | |
| static UINTN mControlTransferCount; | |
| static EFI_STATUS mControlTransferStatus; | |
| static EFI_USB_PORT_STATUS mRootHubPortStatus; | |
| static EFI_USB_BOS_DESCRIPTOR mBosDescriptor; | |
| extern "C" { | |
| EFI_BOOT_SERVICES *gBS = &mBootServices; | |
| /** | |
| Count and free a buffer through the test's mocked boot-services table. | |
| @param[in] Buffer Buffer to free. | |
| @retval EFI_SUCCESS The buffer was freed. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| TestFreePool ( | |
| IN VOID *Buffer | |
| ) | |
| { | |
| mFreePoolCount++; | |
| FreePool (Buffer); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Mock the boot-services stall function without delaying the test. | |
| @param[in] Microseconds Requested delay, ignored by this mock. | |
| @retval EFI_SUCCESS The mock completed successfully. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| TestStall ( | |
| IN UINTN Microseconds | |
| ) | |
| { | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Record a USB control-transfer call and return the status selected by the test. | |
| @param[in] UsbBus USB bus, unused by this mock. | |
| @param[in] DevAddr Device address, unused by this mock. | |
| @param[in] DevSpeed Device speed, unused by this mock. | |
| @param[in] MaxPacket Maximum packet size, unused by this mock. | |
| @param[in] Request Transfer request, unused by this mock. | |
| @param[in] Direction Transfer direction, unused by this mock. | |
| @param[in,out] Data Transfer buffer, unused by this mock. | |
| @param[in,out] DataLength Transfer length, unused by this mock. | |
| @param[in] TimeOut Transfer timeout, unused by this mock. | |
| @param[in] Translator Transaction translator, unused by this mock. | |
| @param[out] UsbResult USB transfer result, unused by this mock. | |
| @retval mControlTransferStatus Status configured by the test. | |
| **/ | |
| EFI_STATUS | |
| UsbHcControlTransfer ( | |
| IN USB_BUS *UsbBus, | |
| IN UINT8 DevAddr, | |
| IN UINT8 DevSpeed, | |
| IN UINTN MaxPacket, | |
| IN EFI_USB_DEVICE_REQUEST *Request, | |
| IN EFI_USB_DATA_DIRECTION Direction, | |
| IN OUT VOID *Data, | |
| IN OUT UINTN *DataLength, | |
| IN UINTN TimeOut, | |
| IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator, | |
| OUT UINT32 *UsbResult | |
| ) | |
| { | |
| mControlTransferCount++; | |
| if ((Request->Value >> 8) == USB_DESC_TYPE_BOS) { | |
| CopyMem (Data, &mBosDescriptor, MIN (*DataLength, sizeof (mBosDescriptor))); | |
| } | |
| return mControlTransferStatus; | |
| } | |
| /** | |
| Mock retrieval of USB host-controller capabilities. | |
| @param[in] UsbBus USB bus, unused by this mock. | |
| @param[out] MaxSpeed Maximum speed output, unused by this mock. | |
| @param[out] NumOfPort Port-count output, unused by this mock. | |
| @param[out] Is64BitCapable 64-bit capability output, unused by this mock. | |
| @retval EFI_UNSUPPORTED Capability retrieval is not used by these tests. | |
| **/ | |
| EFI_STATUS | |
| UsbHcGetCapability ( | |
| IN USB_BUS *UsbBus, | |
| OUT UINT8 *MaxSpeed, | |
| OUT UINT8 *NumOfPort, | |
| OUT UINT8 *Is64BitCapable | |
| ) | |
| { | |
| return EFI_UNSUPPORTED; | |
| } | |
| /** | |
| Mock setting a root-hub port feature. | |
| @param[in] UsbBus USB bus, unused by this mock. | |
| @param[in] PortIndex Root-hub port, unused by this mock. | |
| @param[in] Feature Feature to set, unused by this mock. | |
| @retval EFI_SUCCESS The mock completed successfully. | |
| **/ | |
| EFI_STATUS | |
| UsbHcSetRootHubPortFeature ( | |
| IN USB_BUS *UsbBus, | |
| IN UINT8 PortIndex, | |
| IN EFI_USB_PORT_FEATURE Feature | |
| ) | |
| { | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Mock clearing a root-hub port feature. | |
| @param[in] UsbBus USB bus, unused by this mock. | |
| @param[in] PortIndex Root-hub port, unused by this mock. | |
| @param[in] Feature Feature to clear, unused by this mock. | |
| @retval EFI_SUCCESS The mock completed successfully. | |
| **/ | |
| EFI_STATUS | |
| UsbHcClearRootHubPortFeature ( | |
| IN USB_BUS *UsbBus, | |
| IN UINT8 PortIndex, | |
| IN EFI_USB_PORT_FEATURE Feature | |
| ) | |
| { | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Return the root-hub port status configured by the test. | |
| @param[in] UsbBus USB bus, unused by this mock. | |
| @param[in] PortIndex Root-hub port, unused by this mock. | |
| @param[out] PortStatus Receives the configured port status. | |
| @retval EFI_SUCCESS The configured status was returned. | |
| **/ | |
| EFI_STATUS | |
| UsbHcGetRootHubPortStatus ( | |
| IN USB_BUS *UsbBus, | |
| IN UINT8 PortIndex, | |
| OUT EFI_USB_PORT_STATUS *PortStatus | |
| ) | |
| { | |
| *PortStatus = mRootHubPortStatus; | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Provide the no-op hub-enumeration callback needed to link UsbHub.c. | |
| @param[in] Event Event that triggered enumeration, unused by this stub. | |
| @param[in] Context Callback context, unused by this stub. | |
| **/ | |
| VOID | |
| EFIAPI | |
| UsbHubEnumeration ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| } | |
| /** | |
| Provide the no-op root-hub enumeration callback needed to link UsbHub.c. | |
| @param[in] Event Event that triggered enumeration, unused by this stub. | |
| @param[in] Context Callback context, unused by this stub. | |
| **/ | |
| VOID | |
| EFIAPI | |
| UsbRootHubEnumeration ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| } | |
| } | |
| class UsbBusDxeTest : public ::testing::Test { | |
| protected: | |
| /** | |
| Reset mocked boot services and per-test USB call state. | |
| **/ | |
| void | |
| SetUp ( | |
| ) override | |
| { | |
| ZeroMem (&mBootServices, sizeof (mBootServices)); | |
| mBootServices.FreePool = TestFreePool; | |
| mBootServices.Stall = TestStall; | |
| mFreePoolCount = 0; | |
| mControlTransferCount = 0; | |
| mControlTransferStatus = EFI_DEVICE_ERROR; | |
| ZeroMem (&mBosDescriptor, sizeof (mBosDescriptor)); | |
| mRootHubPortStatus.PortStatus = USB_PORT_STAT_ENABLE; | |
| mRootHubPortStatus.PortChangeStatus = 0; | |
| } | |
| }; | |
| /** | |
| Verify that a well-formed BOS containing a SuperSpeed capability is accepted. | |
| **/ | |
| TEST_F (UsbBusDxeTest, RecognizesValidSuperSpeedCapability) { | |
| UINT8 Buffer[sizeof (EFI_USB_BOS_DESCRIPTOR) + sizeof (EFI_USB_DEV_CAP_DESCRIPTOR)] = { 0 }; | |
| EFI_USB_BOS_DESCRIPTOR *Bos; | |
| EFI_USB_DEV_CAP_DESCRIPTOR *Capability; | |
| Bos = (EFI_USB_BOS_DESCRIPTOR *)Buffer; | |
| Bos->Length = sizeof (*Bos); | |
| Bos->DescriptorType = USB_DESC_TYPE_BOS; | |
| Bos->TotalLength = sizeof (Buffer); | |
| Bos->NumDeviceCaps = 1; | |
| Capability = (EFI_USB_DEV_CAP_DESCRIPTOR *)(Buffer + sizeof (*Bos)); | |
| Capability->Length = sizeof (*Capability); | |
| Capability->DescriptorType = USB_DESC_TYPE_DEV_CAP; | |
| Capability->DevCapabilityType = USB_DEV_CAP_SUPPERSPEED_USB; | |
| EXPECT_TRUE (UsbIsSSDevice (Bos, sizeof (Buffer))); | |
| } | |
| /** | |
| Verify that a three-byte PTM capability is accepted before a SuperSpeed capability. | |
| **/ | |
| TEST_F (UsbBusDxeTest, AcceptsThreeBytePtmCapability) { | |
| UINT8 Buffer[sizeof (EFI_USB_BOS_DESCRIPTOR) + 3 + sizeof (EFI_USB_DEV_CAP_DESCRIPTOR)] = { 0 }; | |
| EFI_USB_BOS_DESCRIPTOR *Bos; | |
| UINT8 *PtmCapability; | |
| EFI_USB_DEV_CAP_DESCRIPTOR *SsCapability; | |
| Bos = (EFI_USB_BOS_DESCRIPTOR *)Buffer; | |
| Bos->Length = sizeof (*Bos); | |
| Bos->DescriptorType = USB_DESC_TYPE_BOS; | |
| Bos->TotalLength = sizeof (Buffer); | |
| Bos->NumDeviceCaps = 2; | |
| PtmCapability = Buffer + sizeof (*Bos); | |
| PtmCapability[0] = OFFSET_OF (EFI_USB_DEV_CAP_DESCRIPTOR, CapData); | |
| PtmCapability[1] = USB_DESC_TYPE_DEV_CAP; | |
| PtmCapability[2] = 0x0B; | |
| SsCapability = (EFI_USB_DEV_CAP_DESCRIPTOR *)(PtmCapability + PtmCapability[0]); | |
| SsCapability->Length = sizeof (*SsCapability); | |
| SsCapability->DescriptorType = USB_DESC_TYPE_DEV_CAP; | |
| SsCapability->DevCapabilityType = USB_DEV_CAP_SUPPERSPEED_USB; | |
| EXPECT_TRUE (UsbIsSSDevice (Bos, sizeof (Buffer))); | |
| } | |
| /** | |
| Verify that a capability extending beyond the BOS total length is rejected. | |
| **/ | |
| TEST_F (UsbBusDxeTest, RejectsCapabilityBeyondBosTotalLength) { | |
| UINT8 Buffer[sizeof (EFI_USB_BOS_DESCRIPTOR) + sizeof (EFI_USB_DEV_CAP_DESCRIPTOR)] = { 0 }; | |
| EFI_USB_BOS_DESCRIPTOR *Bos; | |
| EFI_USB_DEV_CAP_DESCRIPTOR *Capability; | |
| Bos = (EFI_USB_BOS_DESCRIPTOR *)Buffer; | |
| Bos->Length = sizeof (*Bos); | |
| Bos->DescriptorType = USB_DESC_TYPE_BOS; | |
| Bos->TotalLength = sizeof (*Bos); | |
| Bos->NumDeviceCaps = 1; | |
| Capability = (EFI_USB_DEV_CAP_DESCRIPTOR *)(Buffer + sizeof (*Bos)); | |
| Capability->Length = sizeof (*Capability); | |
| Capability->DescriptorType = USB_DESC_TYPE_DEV_CAP; | |
| Capability->DevCapabilityType = USB_DEV_CAP_SUPPERSPEED_USB; | |
| EXPECT_FALSE (UsbIsSSDevice (Bos, sizeof (Buffer))); | |
| } | |
| /** | |
| Verify that a capability extending beyond the supplied BOS buffer is rejected. | |
| **/ | |
| TEST_F (UsbBusDxeTest, RejectsCapabilityLengthBeyondBosBuffer) { | |
| UINT8 Buffer[sizeof (EFI_USB_BOS_DESCRIPTOR) + sizeof (EFI_USB_DEV_CAP_DESCRIPTOR)] = { 0 }; | |
| EFI_USB_BOS_DESCRIPTOR *Bos; | |
| EFI_USB_DEV_CAP_DESCRIPTOR *Capability; | |
| Bos = (EFI_USB_BOS_DESCRIPTOR *)Buffer; | |
| Bos->Length = sizeof (*Bos); | |
| Bos->DescriptorType = USB_DESC_TYPE_BOS; | |
| Bos->TotalLength = sizeof (Buffer); | |
| Bos->NumDeviceCaps = 1; | |
| Capability = (EFI_USB_DEV_CAP_DESCRIPTOR *)(Buffer + sizeof (*Bos)); | |
| Capability->Length = sizeof (*Capability) + 1; | |
| Capability->DescriptorType = USB_DESC_TYPE_DEV_CAP; | |
| Capability->DevCapabilityType = USB_DEV_CAP_SUPPERSPEED_USB; | |
| EXPECT_FALSE (UsbIsSSDevice (Bos, sizeof (Buffer))); | |
| } | |
| /** | |
| Verify failed BOS reads free their buffer and short total lengths are rejected. | |
| **/ | |
| TEST_F (UsbBusDxeTest, HandlesBosReadFailureAndShortTotalLength) { | |
| USB_BUS Bus; | |
| USB_DEVICE UsbDevice; | |
| USB_DEVICE_DESC DeviceDesc; | |
| ZeroMem (&Bus, sizeof (Bus)); | |
| ZeroMem (&UsbDevice, sizeof (UsbDevice)); | |
| ZeroMem (&DeviceDesc, sizeof (DeviceDesc)); | |
| UsbDevice.Bus = &Bus; | |
| UsbDevice.DevDesc = &DeviceDesc; | |
| EXPECT_EQ (UsbGetDevBosDesc (&UsbDevice), EFI_DEVICE_ERROR); | |
| EXPECT_EQ (mControlTransferCount, (UINTN)1); | |
| EXPECT_EQ (mFreePoolCount, (UINTN)1); | |
| EXPECT_EQ (DeviceDesc.BosDesc, nullptr); | |
| mFreePoolCount = 0; | |
| mControlTransferCount = 0; | |
| mControlTransferStatus = EFI_SUCCESS; | |
| mBosDescriptor.Length = sizeof (mBosDescriptor); | |
| mBosDescriptor.DescriptorType = USB_DESC_TYPE_BOS; | |
| mBosDescriptor.TotalLength = sizeof (mBosDescriptor) - 1; | |
| EXPECT_EQ (UsbGetDevBosDesc (&UsbDevice), EFI_DEVICE_ERROR); | |
| EXPECT_EQ (mControlTransferCount, (UINTN)1); | |
| EXPECT_EQ (mFreePoolCount, (UINTN)1); | |
| EXPECT_EQ (DeviceDesc.BosDesc, nullptr); | |
| } | |
| /** | |
| Verify root-hub reset does not dereference a null device descriptor. | |
| **/ | |
| TEST_F (UsbBusDxeTest, AvoidsNullDereferenceDuringRootHubReset) { | |
| USB_BUS Bus; | |
| USB_DEVICE RootDevice; | |
| USB_DEVICE Child; | |
| USB_INTERFACE RootIf; | |
| EFI_USB2_HC_PROTOCOL Usb2Hc; | |
| CONST UINT8 Port = 1; | |
| ZeroMem (&Bus, sizeof (Bus)); | |
| ZeroMem (&RootDevice, sizeof (RootDevice)); | |
| ZeroMem (&Child, sizeof (Child)); | |
| ZeroMem (&RootIf, sizeof (RootIf)); | |
| ZeroMem (&Usb2Hc, sizeof (Usb2Hc)); | |
| Usb2Hc.MajorRevision = 0x3; | |
| Bus.MaxDevices = 2; | |
| Bus.Usb2Hc = &Usb2Hc; | |
| Bus.Devices[1] = &Child; | |
| RootDevice.Bus = &Bus; | |
| RootIf.Device = &RootDevice; | |
| Child.ParentIf = &RootIf; | |
| Child.ParentPort = Port; | |
| Child.Speed = EFI_USB_SPEED_HIGH; | |
| Child.IsSSDev = TRUE; | |
| Child.DevDesc = NULL; | |
| ASSERT_EQ (Child.DevDesc, nullptr); | |
| EXPECT_EQ (UsbRootHubResetPort (&RootIf, Port), EFI_SUCCESS); | |
| } | |
| /** | |
| Initialize GoogleTest and return the aggregate result of the test suite. | |
| @param[in,out] argc Number of command-line arguments. | |
| @param[in,out] argv Command-line arguments. | |
| @retval GoogleTest test-suite result. | |
| **/ | |
| int | |
| main ( | |
| int argc, | |
| char *argv[] | |
| ) | |
| { | |
| testing::InitGoogleTest (&argc, argv); | |
| return RUN_ALL_TESTS (); | |
| } |