/**
 * @file UsbDescriptors.c
 * @brief Functions to read USB Interface and Capabilities descriptors
 *
 * Copyright (c) 2018-2019, DisplayLink (UK) Ltd. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-2-Clause-Patent
 *
**/

#include "UsbDisplayLink.h"
#include "UsbDescriptors.h"

/**
 *
 * @param UsbIo
 * @param descriptorType
 * @param index
 * @param Buffer
 * @param Length
 * @param UsbStatus
 * @return
 */
STATIC EFI_STATUS
ReadDescriptor (
    IN EFI_USB_IO_PROTOCOL *UsbIo,
    UINT8 DescriptorType,
    UINT8 Index,
    VOID* Buffer,
    UINT16 Length,
    UINT32* UsbStatus)
{
  EFI_STATUS Status;

  UINT8 Header[2];
  ZeroMem (Header, sizeof (Header));

  EFI_USB_DEVICE_REQUEST Request;
  ZeroMem (&Request, sizeof (Request));
  Request.RequestType = USB_ENDPOINT_DIR_IN | USB_REQ_TYPE_STANDARD | USB_TARGET_DEVICE;
  Request.Request = USB_REQ_GET_DESCRIPTOR;
  Request.Index = 0;
  Request.Value = DescriptorType << 8 | Index;
  Request.Length = sizeof (Header);

  // Read the descriptor header to see how many bytes it contains
  Status = UsbIo->UsbControlTransfer (
      UsbIo,
      &Request,
      EfiUsbDataIn,
      DISPLAYLINK_USB_CTRL_TIMEOUT,
      Header,
      sizeof (Header),
      UsbStatus);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed to read length of descriptor type x%x, index %u (code %r, USB status x%x)\n",
          DescriptorType, Index, Status, *UsbStatus));
    return Status;
  }
  CONST UINT16 TotalLength = Header[0];

  // Now we know the size of it, we can read the entire descriptor
  Request.Length = TotalLength;

  Status = UsbIo->UsbControlTransfer (
      UsbIo,
      &Request,
      EfiUsbDataIn,
      DISPLAYLINK_USB_CTRL_TIMEOUT,
      Buffer,
      TotalLength,
      UsbStatus);

  return Status;
}

/**
  Perform a USB control transfer to read the DisplayLink vendor descriptor.

  @param UsbIo   Pointer to the instance of the USBIO protocol
  @param Buffer  Pointer to the buffer where descriptor should be written
  @param Length  Length of buffer (and the maximum amount of descriptor data that shall be read)

  @retval EFI_SUCCESS  The descriptor has been copied into Buffer
  @retval Other        The descriptor could not be read
**/
EFI_STATUS
ReadCapabilitiesDescriptor (
    IN EFI_USB_IO_PROTOCOL *UsbIo,
    OUT VOID* Buffer,
    IN UINT16 Length)
{
  UINT32 UsbStatus;
  EFI_STATUS Status;

  Status = ReadDescriptor (
      UsbIo,
      DESCRIPTOR_TYPE_DIRECTFB_CAPABILITY,
      0,
      Buffer,
      Length,
      &UsbStatus);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Could not read capabilities descriptor from DL device (code %r, USB status x%x). Unrecognised firmware version?\n", Status, UsbStatus));
  }

  return Status;
}


/**
 An alternative to the UBSIO protocol function EFI_USB_IO_GET_INTERFACE_DESCRIPTOR.
 This version allows you to specify an index.
 * @param UsbIo                 Pointer to the instance of the USBIO protocol
 * @param interfaceDescriptor   Where the descriptor should be written
 * @param index                 The index of the descriptor required (the standard USBIO function doesn't let you do this)
 * @return
 */
EFI_STATUS
UsbDisplayLinkGetInterfaceDescriptor (
    IN EFI_USB_IO_PROTOCOL *UsbIo,
    OUT EFI_USB_INTERFACE_DESCRIPTOR* InterfaceDescriptor,
    UINT8 Index
    )
{
  UINT32 UsbStatus;
  EFI_STATUS Status;

  Status = ReadDescriptor (
      UsbIo,
      USB_DESC_TYPE_INTERFACE,
      Index,
      InterfaceDescriptor,
      sizeof (EFI_USB_INTERFACE_DESCRIPTOR),
      &UsbStatus);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "USB control transfer failed while reading interface descriptor (code %r, USB status x%x)\n", Status, UsbStatus));
  }

  return Status;
}

