Skip to content

Commit

Permalink
UefiHidDxe: Change HID descriptor read algorithm to accomodate a wide…
Browse files Browse the repository at this point in the history
…r range of devices.
  • Loading branch information
joschock committed Oct 19, 2023
1 parent 2cc586d commit 2655bfd
Showing 1 changed file with 98 additions and 64 deletions.
162 changes: 98 additions & 64 deletions HidPkg/UsbHidDxe/UsbHidDxe.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@
#define SUBCLASS_BOOT 1
#define PROTOCOL_KEYBOARD 1

//
// A common header for usb standard descriptor.
// Each stand descriptor has a length and type.
//
#pragma pack(1)
typedef struct {
UINT8 Len;
UINT8 Type;
} USB_DESC_HEAD;
#pragma pack()

#define USB_HID_DEV_SIGNATURE SIGNATURE_32('U','H','I','D')

typedef struct {
Expand Down Expand Up @@ -583,10 +594,10 @@ IsUsbHid (
}

/**
Retrieves the full HID descriptor for the given device.
Retrieves the full HID descriptor for the given interface.
@param UsbIo UsbIo interface used to read the HID descriptor
@param Interface Interface identifier.
@param InterfaceDescriptor InterfaceDescriptor to retrieve HID descriptor for.
@param HidDescriptor Receives a pointer to a freshly allocated buffer containing the HID descriptor. Caller
is required to free.
Expand All @@ -595,84 +606,95 @@ IsUsbHid (
**/
EFI_STATUS
UsbGetFullHidDescriptor (
IN EFI_USB_IO_PROTOCOL *UsbIo,
IN UINT8 Interface,
OUT EFI_USB_HID_DESCRIPTOR **HidDescriptor
IN EFI_USB_IO_PROTOCOL *UsbIo,
IN EFI_USB_INTERFACE_DESCRIPTOR *InterfaceDescriptor,
OUT EFI_USB_HID_DESCRIPTOR **HidDescriptor
)
{
UINT32 Status;
EFI_STATUS Result;
EFI_USB_DEVICE_REQUEST Request;
EFI_USB_HID_DESCRIPTOR *TempDescriptor;

// HID descriptor is variable length.
// First read enough of the descriptor to determine the full length.
TempDescriptor = AllocatePool (sizeof (EFI_USB_HID_DESCRIPTOR));
if (TempDescriptor == NULL) {
return EFI_OUT_OF_RESOURCES;
EFI_STATUS Status;
EFI_USB_CONFIG_DESCRIPTOR ConfigDesc;
VOID *DescriptorBuffer;
UINT32 TransferResult;
USB_DESC_HEAD *DescriptorHeader;
UINT16 DescriptorCursor;
EFI_USB_HID_DESCRIPTOR *DiscoveredHidDescriptor;

if ((UsbIo == NULL) || (InterfaceDescriptor == NULL) || (HidDescriptor == NULL)) {
return EFI_INVALID_PARAMETER;
}

Request.RequestType = USB_HID_GET_DESCRIPTOR_REQ_TYPE;
Request.Request = USB_REQ_GET_DESCRIPTOR;
Request.Value = (UINT16)(USB_DESC_TYPE_HID << 8);
Request.Index = Interface;
Request.Length = (UINT16)sizeof (EFI_USB_HID_DESCRIPTOR);
// Note: while the USB Device Class Definition for HID spec 1.11 specifies support for retrieving the HID descriptor
// using a class-specifc Get_Descritpor request, not all devices support this. So read the entire configuration
// descriptor instead, and parse it for the HID descriptor.

Result = UsbIo->UsbControlTransfer (
UsbIo,
&Request,
EfiUsbDataIn,
PcdGet32 (PcdUsbTransferTimeoutValue),
TempDescriptor,
sizeof (EFI_USB_HID_DESCRIPTOR),
&Status
);
if (EFI_ERROR (Result)) {
DEBUG ((DEBUG_WARN, "[%a] unexpected failure reading HID descriptor: %r, Status: 0x%x\n", __FUNCTION__, Result, Status));
goto ErrorExit;
Status = UsbIo->UsbGetConfigDescriptor (UsbIo, &ConfigDesc);
if (EFI_ERROR (Status)) {
return Status;
}

// If the whole descriptor was read (i.e. there is only one HID class descriptor), then just return.
if (TempDescriptor->Length == sizeof (EFI_USB_HID_DESCRIPTOR)) {
*HidDescriptor = TempDescriptor;
return EFI_SUCCESS;
DescriptorBuffer = AllocateZeroPool (ConfigDesc.TotalLength);
if (DescriptorBuffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}

// If the descriptor is somehow _less_ in size than expected, bail out with an error.
if (TempDescriptor->Length < sizeof (EFI_USB_HID_DESCRIPTOR)) {
DEBUG ((DEBUG_ERROR, "[%a]Invalid sizeof (EFI_USB_HID_DESCRIPTOR): %d\n", __FUNCTION__, sizeof (EFI_USB_HID_DESCRIPTOR)));
Result = EFI_DEVICE_ERROR;
goto ErrorExit;
// Issuing Get_Descriptor(Configuration) request with total length returns the Configuration descriptor, all Interface
// descriptors, all Endpoint descriptors, and the HID descriptor for each interface.
Status = UsbGetDescriptor (
UsbIo,
(UINT16)((USB_DESC_TYPE_CONFIG << 8) | (ConfigDesc.ConfigurationValue - 1)),
0,
ConfigDesc.TotalLength,
DescriptorBuffer,
&TransferResult
);
if (EFI_ERROR (Status)) {
FreePool (DescriptorBuffer);
return Status;
}

// If there is more than one HID class descriptor, allocate and re-read the whole descriptor.
TempDescriptor = ReallocatePool (sizeof (EFI_USB_HID_DESCRIPTOR), TempDescriptor->Length, TempDescriptor);
if (TempDescriptor == NULL) {
return EFI_OUT_OF_RESOURCES;
// Locate the HID descriptor within the overall configuration descriptor buffer. The HID desciptor will immediately
// follow the interface descriptor. Refer to USB HID 1.11 section 7.1.
DiscoveredHidDescriptor = NULL;
DescriptorCursor = 0;
DescriptorHeader = (USB_DESC_HEAD *)DescriptorBuffer;
while (DescriptorCursor < ConfigDesc.TotalLength) {
if (DescriptorHeader->Type == USB_DESC_TYPE_INTERFACE) {
if ((((USB_INTERFACE_DESCRIPTOR *)DescriptorHeader)->InterfaceNumber == InterfaceDescriptor->InterfaceNumber) &&
(((USB_INTERFACE_DESCRIPTOR *)DescriptorHeader)->AlternateSetting == InterfaceDescriptor->AlternateSetting))
{
// The HID descriptor must immediately follow the interface descriptor.
DescriptorHeader = (USB_DESC_HEAD *)((UINT8 *)DescriptorBuffer + DescriptorCursor + DescriptorHeader->Len);
if (DescriptorHeader->Type == USB_DESC_TYPE_HID) {
// found the HID descriptor.
DiscoveredHidDescriptor = (EFI_USB_HID_DESCRIPTOR *)DescriptorHeader;
}

// If the HID descriptor exists, it must be at this point in the structure. So whether it was found or not,
// further searching is not required and the loop can be broken here.
break;
}
}

// move to next descriptor
DescriptorCursor += DescriptorHeader->Len;
DescriptorHeader = (USB_DESC_HEAD *)((UINT8 *)DescriptorBuffer + DescriptorCursor);
}

Request.Length = (UINT16)TempDescriptor->Length;
Result = UsbIo->UsbControlTransfer (
UsbIo,
&Request,
EfiUsbDataIn,
PcdGet32 (PcdUsbTransferTimeoutValue),
*HidDescriptor,
TempDescriptor->Length,
&Status
);

if (!EFI_ERROR (Result)) {
*HidDescriptor = TempDescriptor;
return EFI_SUCCESS;
// if HID descriptor not found, free and return.
if (DiscoveredHidDescriptor == NULL) {
FreePool (DescriptorBuffer);
return EFI_UNSUPPORTED;
}

ErrorExit:
if (EFI_ERROR (Result) && (TempDescriptor != NULL)) {
FreePool (TempDescriptor);
// if found, copy it and return.
*HidDescriptor = AllocateCopyPool (DiscoveredHidDescriptor->Length, DiscoveredHidDescriptor);
FreePool (DescriptorBuffer);

if (*HidDescriptor == NULL) {
return EFI_OUT_OF_RESOURCES;
}

return Result;
return EFI_SUCCESS;
}

/**
Expand All @@ -692,6 +714,8 @@ ReadDescriptors (
UINT8 Index;
EFI_USB_ENDPOINT_DESCRIPTOR EndpointDescriptor;

DEBUG ((DEBUG_INFO, "[%a:%d] getting descriptors.\n", __FUNCTION__, __LINE__));

ZeroMem (&UsbHidDevice->IntInEndpointDescriptor, sizeof (UsbHidDevice->IntInEndpointDescriptor));

Status = UsbHidDevice->UsbIo->UsbGetInterfaceDescriptor (UsbHidDevice->UsbIo, &UsbHidDevice->InterfaceDescriptor);
Expand All @@ -700,6 +724,16 @@ ReadDescriptors (
goto ErrorExit;
}

DEBUG ((
DEBUG_INFO,
"[%a:%d] interface class: 0x%x, subclass: 0x%x, protocol: 0x%x.\n",
__FUNCTION__,
__LINE__,
UsbHidDevice->InterfaceDescriptor.InterfaceClass,
UsbHidDevice->InterfaceDescriptor.InterfaceSubClass,
UsbHidDevice->InterfaceDescriptor.InterfaceProtocol
));

for (Index = 0; Index < UsbHidDevice->InterfaceDescriptor.NumEndpoints; Index++) {
Status = UsbHidDevice->UsbIo->UsbGetEndpointDescriptor (UsbHidDevice->UsbIo, Index, &EndpointDescriptor);
if (EFI_ERROR (Status)) {
Expand All @@ -721,7 +755,7 @@ ReadDescriptors (
goto ErrorExit;
}

Status = UsbGetFullHidDescriptor (UsbHidDevice->UsbIo, UsbHidDevice->InterfaceDescriptor.InterfaceNumber, &UsbHidDevice->HidDescriptor);
Status = UsbGetFullHidDescriptor (UsbHidDevice->UsbIo, &UsbHidDevice->InterfaceDescriptor, &UsbHidDevice->HidDescriptor);
if (EFI_ERROR (Status)) {
goto ErrorExit;
}
Expand Down

0 comments on commit 2655bfd

Please sign in to comment.