/** @file

  Copyright (c) 2014, ARM Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

/*
 * Implementation of the FASTBOOT_TRANSPORT_PROTOCOL using the USB_DEVICE_PROTOCOL
 */

#include <Protocol/UsbDevice.h>
#include <Protocol/AndroidFastbootTransport.h>
#include <Protocol/SimpleTextOut.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiDriverEntryPoint.h>

STATIC USB_DEVICE_PROTOCOL *mUsbDevice;

// Configuration attributes:
// bit 7 reserved and must be 1, bit 6 means self-powered.
#define CONFIG_DESC_ATTRIBUTES      (BIT7 | BIT6)

#define MAX_PACKET_SIZE_BULK        512

STATIC USB_DEVICE_PROTOCOL  *mUsbDevice;
STATIC EFI_EVENT             mReceiveEvent = NULL;
STATIC LIST_ENTRY            mPacketList;

// List type for queued received packets
typedef struct _FASTBOOT_USB_PACKET_LIST {
  LIST_ENTRY  Link;
  VOID       *Buffer;
  UINTN       BufferSize;
} FASTBOOT_USB_PACKET_LIST;


/*
  No string descriptors - all string descriptor members are set to 0
*/

STATIC USB_DEVICE_DESCRIPTOR mDeviceDescriptor = {
  sizeof (USB_DEVICE_DESCRIPTOR),                  //Length
  USB_DESC_TYPE_DEVICE,                            //DescriptorType
  0x0200,                                          //BcdUSB
  0xFF,                                            //DeviceClass
  0,                                               //DeviceSubClass
  0,                                               //DeviceProtocol
  64,                                              //MaxPacketSize0
  FixedPcdGet32 (PcdAndroidFastbootUsbVendorId),   //IdVendor
  FixedPcdGet32 (PcdAndroidFastbootUsbProductId),  //IdProduct
  0,                                               //BcdDevice
  0,                                               //StrManufacturer
  0,                                               //StrProduct
  0,                                               //StrSerialNumber
  1                                                //NumConfigurations
};

/*
  We have one configuration, one interface, and two endpoints (one IN, one OUT)
*/

// Lazy (compile-time) way to concatenate descriptors to pass to the USB device
// protocol

#pragma pack(1)
typedef struct {
  USB_CONFIG_DESCRIPTOR     ConfigDescriptor;
  USB_INTERFACE_DESCRIPTOR  InterfaceDescriptor;
  USB_ENDPOINT_DESCRIPTOR   EndpointDescriptor1;
  USB_ENDPOINT_DESCRIPTOR   EndpointDescriptor2;
} GET_CONFIG_DESCRIPTOR_RESPONSE;
#pragma pack()

STATIC GET_CONFIG_DESCRIPTOR_RESPONSE mGetConfigDescriptorResponse = {
  { // USB_CONFIG_DESCRIPTOR
    sizeof (USB_CONFIG_DESCRIPTOR),                   //Length;
    USB_DESC_TYPE_CONFIG,                             //DescriptorType;
    sizeof (GET_CONFIG_DESCRIPTOR_RESPONSE),          //TotalLength;
    1,                                                //NumInterfaces;
    1,                                                //ConfigurationValue;
    0,                                                //Configuration;
    CONFIG_DESC_ATTRIBUTES,                           //Attributes;
    0                                                 //MaxPower;
  },
  { // USB_INTERFACE_DESCRIPTOR
    sizeof (USB_INTERFACE_DESCRIPTOR), //Length;
    USB_DESC_TYPE_INTERFACE, //DescriptorType;
    0,                                                //InterfaceNumber;
    0,                                                //AlternateSetting;
    2,                                                //NumEndpoints;
    0xFF,                                             //InterfaceClass;
    // Vendor specific interface subclass and protocol codes.
    // I found these values in the Fastboot code
    // (in match_fastboot_with_serial in fastboot.c).
    0x42,                                             //InterfaceSubClass;
    0x03,                                             //InterfaceProtocol;
    0                                                 //Interface;
  },
  { // USB_ENDPOINT_DESCRIPTOR (In Endpoint)
    sizeof (USB_ENDPOINT_DESCRIPTOR),                 //Length;
    USB_DESC_TYPE_ENDPOINT,                           //DescriptorType;
    1 | BIT7,                                         //EndpointAddress;
    0x2,                                              //Attributes;
    MAX_PACKET_SIZE_BULK,                             //MaxPacketSize;
    16                                                //Interval;
  },
  { // STATIC USB_ENDPOINT_DESCRIPTOR (Out Endpoint)
    sizeof (USB_ENDPOINT_DESCRIPTOR),                 //Length;
    USB_DESC_TYPE_ENDPOINT,                           //DescriptorType;
    1,                                                //EndpointAddress;
    0x2,                                              //Attributes;
    MAX_PACKET_SIZE_BULK,                             //MaxPacketSize;
    16                                                //Interval;
  }
};

STATIC
VOID
DataReceived (
  IN UINTN    Size,
  IN VOID    *Buffer
  )
{
  FASTBOOT_USB_PACKET_LIST *NewEntry;

  NewEntry = AllocatePool (sizeof (*NewEntry));
  ASSERT (NewEntry != NULL);

  NewEntry->Buffer = Buffer;
  NewEntry->BufferSize = Size;

  InsertTailList (&mPacketList, &NewEntry->Link);

  if (mReceiveEvent) {
    gBS->SignalEvent (mReceiveEvent);
  }
}

STATIC
VOID
DataSent (
  IN UINT8 EndpointIndex
  )
{
  // Don't care.
}

/*
  Set up the transport system for use by Fastboot.
  e.g. For USB this probably means making the device enumerable.
*/
EFI_STATUS
FastbootTransportUsbStart (
  EFI_EVENT ReceiveEvent
  )
{
  GET_CONFIG_DESCRIPTOR_RESPONSE  *Responses;

  mReceiveEvent = ReceiveEvent;

  mGetConfigDescriptorResponse.ConfigDescriptor.TotalLength = sizeof (GET_CONFIG_DESCRIPTOR_RESPONSE);
  Responses = &mGetConfigDescriptorResponse;

  InitializeListHead (&mPacketList);

  return mUsbDevice->Start (&mDeviceDescriptor, (VOID **) &Responses, DataReceived, DataSent);
}

/*
  Function to be called when all Fastboot transactions are finished, to
  de-initialise the transport system.
  e.g. A USB OTG system might want to get out of peripheral mode so it can be
       a USB host.
*/
EFI_STATUS
FastbootTransportUsbStop (
  VOID
  )
{
  // not yet implemented in USB
  return EFI_SUCCESS;
}

/*
  Send data. This function can be used both for command responses like "OKAY"
  and for the data phase (the protocol doesn't describe any situation when the
   latter might be necessary, but does allow it)
 */
EFI_STATUS
FastbootTransportUsbSend (
  IN        UINTN      BufferSize,
  IN  CONST VOID      *Buffer,
  IN        EFI_EVENT *FatalErrorEvent
  )
{
  // Current USB protocol is blocking, so ignore FatalErrorEvent
  return mUsbDevice->Send(1, BufferSize, Buffer);
}

/*
  When the event has been Signalled to say data is available from the host,
  this function is used to get data. In order to handle the case where several
  packets are received before ReceiveEvent's notify function is called, packets
  received are queued, and each call to this function returns the next packet in
  the queue. It should therefore be called in a loop, the exit condition being a
  return of EFI_NOT_READY.

  Parameters:
    Buffer      - The buffer in which to place data
    BufferSize  - The size of Buffer in bytes

  Return EFI_NOT_READY if there is no data available
*/
EFI_STATUS
FastbootTransportUsbReceive (
  OUT UINTN  *BufferSize,
  OUT VOID  **Buffer
  )
{
  FASTBOOT_USB_PACKET_LIST *Entry;

  if (IsListEmpty (&mPacketList)) {
    return EFI_NOT_READY;
  }

  Entry = (FASTBOOT_USB_PACKET_LIST *) GetFirstNode (&mPacketList);

  *BufferSize = Entry->BufferSize;
  *Buffer = Entry->Buffer;

  RemoveEntryList (&Entry->Link);
  FreePool (Entry);

  return EFI_SUCCESS;
}

STATIC FASTBOOT_TRANSPORT_PROTOCOL mTransportProtocol = {
  FastbootTransportUsbStart,
  FastbootTransportUsbStop,
  FastbootTransportUsbSend,
  FastbootTransportUsbReceive
};

EFI_STATUS
FastbootTransportUsbEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_STATUS Status;

  // Assume there's only one USB peripheral controller.
  Status = gBS->LocateProtocol (&gUsbDeviceProtocolGuid, NULL, (VOID **) &mUsbDevice);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->InstallProtocolInterface (
                  &ImageHandle,
                  &gAndroidFastbootTransportProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mTransportProtocol
                  );
  return Status;
}
