/** @file
  File to contain all the hardware specific stuff for the Smm USB dispatch protocol.

   Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
   SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "PchSmmHelpers.h"
#include <Register/PmcRegs.h>
#include <Library/PchPciBdfLib.h>

typedef enum {
  PchUsbControllerLpc0 = 0,
  PchUsbControllerXhci,
  PchUsbControllerTypeMax
} PCH_USB_CONTROLLER_TYPE;

typedef struct {
  UINT8                   Function;
  UINT8                   Device;
  PCH_USB_CONTROLLER_TYPE UsbConType;
} PCH_USB_CONTROLLER;

GLOBAL_REMOVE_IF_UNREFERENCED CONST PCH_SMM_SOURCE_DESC mUsb1Legacy = {
  PCH_SMM_NO_FLAGS,
  {
    {
      {
        ACPI_ADDR_TYPE,
        {R_ACPI_IO_SMI_EN}
      },
      S_ACPI_IO_SMI_EN,
      N_ACPI_IO_SMI_EN_LEGACY_USB
    },
    NULL_BIT_DESC_INITIALIZER
  },
  {
    {
      {
        ACPI_ADDR_TYPE,
        {R_ACPI_IO_SMI_STS}
      },
      S_ACPI_IO_SMI_STS,
      N_ACPI_IO_SMI_STS_LEGACY_USB
    }
  },
  {
    {
      ACPI_ADDR_TYPE,
      {R_ACPI_IO_SMI_STS}
    },
    S_ACPI_IO_SMI_STS,
    N_ACPI_IO_SMI_STS_LEGACY_USB
  }
};

GLOBAL_REMOVE_IF_UNREFERENCED CONST PCH_SMM_SOURCE_DESC mUsb3Legacy = {
  PCH_SMM_NO_FLAGS,
  {
    {
      {
        ACPI_ADDR_TYPE,
        {R_ACPI_IO_SMI_EN}
      },
      S_ACPI_IO_SMI_EN,
      N_ACPI_IO_SMI_EN_LEGACY_USB3
    },
    NULL_BIT_DESC_INITIALIZER
  },
  {
    {
      {
        ACPI_ADDR_TYPE,
        {R_ACPI_IO_SMI_STS}
      },
      S_ACPI_IO_SMI_STS,
      N_ACPI_IO_SMI_STS_LEGACY_USB3
    }
  },
  {
    {
      ACPI_ADDR_TYPE,
      {R_ACPI_IO_SMI_STS}
    },
    S_ACPI_IO_SMI_STS,
    N_ACPI_IO_SMI_STS_LEGACY_USB3
  }
};

GLOBAL_REMOVE_IF_UNREFERENCED PCH_USB_CONTROLLER  mUsbControllersMap[] = {
  {
    0xFF, // to be updated in PchSmmUsbUpdateDescriptors
    0xFF, // to be updated in PchSmmUsbUpdateDescriptors
    PchUsbControllerLpc0
  },
  {
    0xFF, // to be updated in PchSmmUsbUpdateDescriptors
    0xFF, // to be updated in PchSmmUsbUpdateDescriptors
    PchUsbControllerXhci
  }
};

/**
  Performs update of SmmUsb descriptors with values that have to be evaluated during runtime.
**/
VOID
PchSmmUsbUpdateDescriptors (
  VOID
  )
{
  //
  // mUsbControllersMap
  //
  mUsbControllersMap[0].Function  = LpcFuncNumber ();
  mUsbControllersMap[0].Device    = LpcDevNumber ();
  mUsbControllersMap[1].Function  = PchXhciFuncNumber ();
  mUsbControllersMap[1].Device    = PchXhciDevNumber ();
}

/**
  Find the handle that best matches the input Device Path and return the USB controller type

  @param[in] DevicePath           Pointer to the device Path table
  @param[out] Controller          Returned with the USB controller type of the input device path

  @retval EFI_SUCCESS             Find the handle that best matches the input Device Path
  @exception EFI_UNSUPPORTED      Invalid device Path table or can't find any match USB device path
                                  PCH_USB_CONTROLLER_TYPE The USB controller type of the input
                                  device path
**/
EFI_STATUS
DevicePathToSupportedController (
  IN  EFI_DEVICE_PATH_PROTOCOL   *DevicePath,
  OUT PCH_USB_CONTROLLER_TYPE    *Controller
  )
{
  EFI_STATUS                Status;
  EFI_HANDLE                DeviceHandle;
  ACPI_HID_DEVICE_PATH      *AcpiNode;
  PCI_DEVICE_PATH           *PciNode;
  EFI_DEVICE_PATH_PROTOCOL  *RemaingDevicePath;
  UINT8                     UsbIndex;
  ///
  /// Find the handle that best matches the Device Path. If it is only a
  /// partial match the remaining part of the device path is returned in
  /// RemainingDevicePath.
  ///
  RemaingDevicePath = DevicePath;
  Status = gBS->LocateDevicePath (
                  &gEfiPciRootBridgeIoProtocolGuid,
                  &DevicePath,
                  &DeviceHandle
                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  DevicePath = RemaingDevicePath;

  ///
  /// Get first node: Acpi Node
  ///
  AcpiNode = (ACPI_HID_DEVICE_PATH *) RemaingDevicePath;

  if (AcpiNode->Header.Type != ACPI_DEVICE_PATH ||
      AcpiNode->Header.SubType != ACPI_DP ||
      DevicePathNodeLength (&AcpiNode->Header) != sizeof (ACPI_HID_DEVICE_PATH) ||
      AcpiNode->HID != EISA_PNP_ID (0x0A03) ||
      AcpiNode->UID != 0
      ) {
    return EFI_UNSUPPORTED;
  } else {
    ///
    /// Get the next node: Pci Node
    ///
    RemaingDevicePath = NextDevicePathNode (RemaingDevicePath);
    PciNode           = (PCI_DEVICE_PATH *) RemaingDevicePath;
    if (PciNode->Header.Type != HARDWARE_DEVICE_PATH ||
        PciNode->Header.SubType != HW_PCI_DP ||
        DevicePathNodeLength (&PciNode->Header) != sizeof (PCI_DEVICE_PATH)
        ) {
      return EFI_UNSUPPORTED;
    }

    for (UsbIndex = 0; UsbIndex < sizeof (mUsbControllersMap) / sizeof (PCH_USB_CONTROLLER); UsbIndex++) {
      if ((PciNode->Device == mUsbControllersMap[UsbIndex].Device) &&
          (PciNode->Function == mUsbControllersMap[UsbIndex].Function)) {
        *Controller = mUsbControllersMap[UsbIndex].UsbConType;
        return EFI_SUCCESS;
      }
    }

    return EFI_UNSUPPORTED;
  }
}

/**
  Maps a USB context to a source description.

  @param[in] Context              The context we need to map.  Type must be USB.
  @param[in] SrcDesc              The source description that corresponds to the given context.

**/
VOID
MapUsbToSrcDesc (
  IN  PCH_SMM_CONTEXT         *Context,
  OUT PCH_SMM_SOURCE_DESC     *SrcDesc
  )
{
  PCH_USB_CONTROLLER_TYPE Controller;
  EFI_STATUS              Status;

  Status = DevicePathToSupportedController (Context->Usb.Device, &Controller);
  ///
  /// Either the device path passed in by the child is incorrect or
  /// the ones stored here internally are incorrect.
  ///
  ASSERT_EFI_ERROR (Status);

  switch (Context->Usb.Type) {
    case UsbLegacy:
      switch (Controller) {
        case PchUsbControllerLpc0:
          CopyMem ((VOID *) SrcDesc, (VOID *) (&mUsb1Legacy), sizeof (PCH_SMM_SOURCE_DESC));
          break;

        case PchUsbControllerXhci:
          CopyMem ((VOID *) SrcDesc, (VOID *) (&mUsb3Legacy), sizeof (PCH_SMM_SOURCE_DESC));
          break;

        default:
          ASSERT (FALSE);
          break;
      }
      break;

    case UsbWake:
      ASSERT (FALSE);
      break;

    default:
      ASSERT (FALSE);
      break;
  }
}
