/** @file
  The NvmExpressPei driver is used to manage non-volatile memory subsystem
  which follows NVM Express specification at PEI phase.

  Copyright (c) 2018 - 2019, Intel Corporation. All rights reserved.<BR>

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

**/

#pragma once

#include <PiPei.h>

#include <IndustryStandard/Nvme.h>
#include <IndustryStandard/Pci.h>

#include <Ppi/NvmExpressHostController.h>
#include <Ppi/BlockIo.h>
#include <Ppi/BlockIo2.h>
#include <Ppi/StorageSecurityCommand.h>
#include <Ppi/NvmExpressPassThru.h>
#include <Ppi/IoMmu.h>
#include <Ppi/EndOfPeiPhase.h>
#include <Ppi/PciDevice.h>

#include <Library/DebugLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/TimerLib.h>
#include <Library/DevicePathLib.h>

//
// Structure forward declarations
//
typedef struct _PEI_NVME_NAMESPACE_INFO           PEI_NVME_NAMESPACE_INFO;
typedef struct _PEI_NVME_CONTROLLER_PRIVATE_DATA  PEI_NVME_CONTROLLER_PRIVATE_DATA;

/**
  Macro that checks whether device is a NVMHCI Interface.

  @param  _p      Specified device.

  @retval TRUE    Device is a NVMHCI Interface.
  @retval FALSE   Device is not a NVMHCI Interface.

**/
#define IS_PCI_NVMHCI(_p)  IS_CLASS3 (_p, PCI_CLASS_MASS_STORAGE, PCI_CLASS_MASS_STORAGE_SOLID_STATE, PCI_IF_MASS_STORAGE_SOLID_STATE_ENTERPRISE_NVMHCI)

#include "NvmExpressPeiHci.h"
#include "NvmExpressPeiPassThru.h"
#include "NvmExpressPeiBlockIo.h"
#include "NvmExpressPeiStorageSecurity.h"

//
// NVME PEI driver implementation related definitions
//
#define NVME_MAX_QUEUES  2                                  // Number of I/O queues supported by the driver, 1 for AQ, 1 for CQ
#define NVME_ASQ_SIZE    1                                  // Number of admin submission queue entries, which is 0-based
#define NVME_ACQ_SIZE    1                                  // Number of admin completion queue entries, which is 0-based
#define NVME_CSQ_SIZE    63                                 // Number of I/O submission queue entries, which is 0-based
#define NVME_CCQ_SIZE    63                                 // Number of I/O completion queue entries, which is 0-based
#define NVME_PRP_SIZE    (8)                                // Pages of PRP list

#define NVME_MEM_MAX_PAGES                                           \
  (                                                                  \
  1 /* ASQ */ +                                                      \
  1 /* ACQ */ +                                                      \
  1 /* SQs */ +                                                      \
  1 /* CQs */ +                                                      \
  NVME_PRP_SIZE)                            /* PRPs */

#define NVME_ADMIN_QUEUE      0x00
#define NVME_IO_QUEUE         0x01
#define NVME_GENERIC_TIMEOUT  5000000                           // Generic PassThru command timeout value, in us unit
#define NVME_POLL_INTERVAL    100                               // Poll interval for PassThru command, in us unit

//
// Nvme namespace data structure.
//
struct _PEI_NVME_NAMESPACE_INFO {
  UINT32                              NamespaceId;
  UINT64                              NamespaceUuid;
  EFI_PEI_BLOCK_IO2_MEDIA             Media;

  PEI_NVME_CONTROLLER_PRIVATE_DATA    *Controller;
};

#define NVME_CONTROLLER_NSID  0

//
// Unique signature for private data structure.
//
#define NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE  SIGNATURE_32 ('N','V','P','C')

//
// Nvme controller private data structure.
//
struct _PEI_NVME_CONTROLLER_PRIVATE_DATA {
  UINT32                                 Signature;
  UINTN                                  MmioBase;
  EFI_NVM_EXPRESS_PASS_THRU_MODE         PassThruMode;
  UINTN                                  DevicePathLength;
  EFI_DEVICE_PATH_PROTOCOL               *DevicePath;

  EFI_PEI_RECOVERY_BLOCK_IO_PPI          BlkIoPpi;
  EFI_PEI_RECOVERY_BLOCK_IO2_PPI         BlkIo2Ppi;
  EDKII_PEI_STORAGE_SECURITY_CMD_PPI     StorageSecurityPpi;
  EDKII_PEI_NVM_EXPRESS_PASS_THRU_PPI    NvmePassThruPpi;
  EFI_PEI_PPI_DESCRIPTOR                 BlkIoPpiList;
  EFI_PEI_PPI_DESCRIPTOR                 BlkIo2PpiList;
  EFI_PEI_PPI_DESCRIPTOR                 StorageSecurityPpiList;
  EFI_PEI_PPI_DESCRIPTOR                 NvmePassThruPpiList;
  EFI_PEI_NOTIFY_DESCRIPTOR              EndOfPeiNotifyList;

  //
  // Pointer to identify controller data
  //
  NVME_ADMIN_CONTROLLER_DATA             *ControllerData;

  //
  // (4 + NVME_PRP_SIZE) x 4kB aligned buffers will be carved out of this buffer
  // 1st 4kB boundary is the start of the admin submission queue
  // 2nd 4kB boundary is the start of the admin completion queue
  // 3rd 4kB boundary is the start of I/O submission queue
  // 4th 4kB boundary is the start of I/O completion queue
  // 5th 4kB boundary is the start of PRP list buffers
  //
  VOID                       *Buffer;
  VOID                       *BufferMapping;

  //
  // Pointers to 4kB aligned submission & completion queues
  //
  NVME_SQ                    *SqBuffer[NVME_MAX_QUEUES];
  NVME_CQ                    *CqBuffer[NVME_MAX_QUEUES];

  //
  // Submission and completion queue indices
  //
  NVME_SQTDBL                SqTdbl[NVME_MAX_QUEUES];
  NVME_CQHDBL                CqHdbl[NVME_MAX_QUEUES];

  UINT8                      Pt[NVME_MAX_QUEUES];
  UINT16                     Cid[NVME_MAX_QUEUES];

  //
  // Nvme controller capabilities
  //
  NVME_CAP                   Cap;

  //
  // Namespaces information on the controller
  //
  UINT32                     ActiveNamespaceNum;
  PEI_NVME_NAMESPACE_INFO    *NamespaceInfo;
};

#define GET_NVME_PEIM_HC_PRIVATE_DATA_FROM_THIS_BLKIO(a)               \
  CR (a, PEI_NVME_CONTROLLER_PRIVATE_DATA, BlkIoPpi, NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE)
#define GET_NVME_PEIM_HC_PRIVATE_DATA_FROM_THIS_BLKIO2(a)              \
  CR (a, PEI_NVME_CONTROLLER_PRIVATE_DATA, BlkIo2Ppi, NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE)
#define GET_NVME_PEIM_HC_PRIVATE_DATA_FROM_THIS_STROAGE_SECURITY(a)    \
  CR (a, PEI_NVME_CONTROLLER_PRIVATE_DATA, StorageSecurityPpi, NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE)
#define GET_NVME_PEIM_HC_PRIVATE_DATA_FROM_THIS_NVME_PASSTHRU(a)       \
  CR (a, PEI_NVME_CONTROLLER_PRIVATE_DATA, NvmePassThruPpi, NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE)
#define GET_NVME_PEIM_HC_PRIVATE_DATA_FROM_THIS_NOTIFY(a)              \
  CR (a, PEI_NVME_CONTROLLER_PRIVATE_DATA, EndOfPeiNotifyList, NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE)

//
// Internal functions
//

/**
  Allocates pages that are suitable for an OperationBusMasterCommonBuffer or
  OperationBusMasterCommonBuffer64 mapping.

  @param  Pages                 The number of pages to allocate.
  @param  HostAddress           A pointer to store the base system memory address of the
                                allocated range.
  @param  DeviceAddress         The resulting map address for the bus master PCI controller to use to
                                access the hosts HostAddress.
  @param  Mapping               A resulting value to pass to Unmap().

  @retval EFI_SUCCESS           The requested memory pages were allocated.
  @retval EFI_UNSUPPORTED       Attributes is unsupported. The only legal attribute bits are
                                MEMORY_WRITE_COMBINE and MEMORY_CACHED.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES  The memory pages could not be allocated.

**/
EFI_STATUS
IoMmuAllocateBuffer (
  IN UINTN                  Pages,
  OUT VOID                  **HostAddress,
  OUT EFI_PHYSICAL_ADDRESS  *DeviceAddress,
  OUT VOID                  **Mapping
  );

/**
  Frees memory that was allocated with AllocateBuffer().

  @param  Pages                 The number of pages to free.
  @param  HostAddress           The base system memory address of the allocated range.
  @param  Mapping               The mapping value returned from Map().

  @retval EFI_SUCCESS           The requested memory pages were freed.
  @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and Pages
                                was not allocated with AllocateBuffer().

**/
EFI_STATUS
IoMmuFreeBuffer (
  IN UINTN  Pages,
  IN VOID   *HostAddress,
  IN VOID   *Mapping
  );

/**
  Provides the controller-specific addresses required to access system memory from a
  DMA bus master.

  @param  Operation             Indicates if the bus master is going to read or write to system memory.
  @param  HostAddress           The system memory address to map to the PCI controller.
  @param  NumberOfBytes         On input the number of bytes to map. On output the number of bytes
                                that were mapped.
  @param  DeviceAddress         The resulting map address for the bus master PCI controller to use to
                                access the hosts HostAddress.
  @param  Mapping               A resulting value to pass to Unmap().

  @retval EFI_SUCCESS           The range was mapped for the returned NumberOfBytes.
  @retval EFI_UNSUPPORTED       The HostAddress cannot be mapped as a common buffer.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES  The request could not be completed due to a lack of resources.
  @retval EFI_DEVICE_ERROR      The system hardware could not map the requested address.

**/
EFI_STATUS
IoMmuMap (
  IN  EDKII_IOMMU_OPERATION  Operation,
  IN VOID                    *HostAddress,
  IN  OUT UINTN              *NumberOfBytes,
  OUT EFI_PHYSICAL_ADDRESS   *DeviceAddress,
  OUT VOID                   **Mapping
  );

/**
  Completes the Map() operation and releases any corresponding resources.

  @param  Mapping               The mapping value returned from Map().

  @retval EFI_SUCCESS           The range was unmapped.
  @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map().
  @retval EFI_DEVICE_ERROR      The data was not committed to the target system memory.
**/
EFI_STATUS
IoMmuUnmap (
  IN VOID  *Mapping
  );

/**
  One notified function to cleanup the allocated resources at the end of PEI.

  @param[in] PeiServices         Pointer to PEI Services Table.
  @param[in] NotifyDescriptor    Pointer to the descriptor for the Notification
                                 event that caused this function to execute.
  @param[in] Ppi                 Pointer to the PPI data associated with this function.

  @retval     EFI_SUCCESS  The function completes successfully

**/
EFI_STATUS
EFIAPI
NvmePeimEndOfPei (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  );

/**
  Get the size of the current device path instance.

  @param[in]  DevicePath             A pointer to the EFI_DEVICE_PATH_PROTOCOL
                                     structure.
  @param[out] InstanceSize           The size of the current device path instance.
  @param[out] EntireDevicePathEnd    Indicate whether the instance is the last
                                     one in the device path strucure.

  @retval EFI_SUCCESS    The size of the current device path instance is fetched.
  @retval Others         Fails to get the size of the current device path instance.

**/
EFI_STATUS
GetDevicePathInstanceSize (
  IN  EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  OUT UINTN                     *InstanceSize,
  OUT BOOLEAN                   *EntireDevicePathEnd
  );

/**
  Check the validity of the device path of a NVM Express host controller.

  @param[in] DevicePath          A pointer to the EFI_DEVICE_PATH_PROTOCOL
                                 structure.
  @param[in] DevicePathLength    The length of the device path.

  @retval EFI_SUCCESS              The device path is valid.
  @retval EFI_INVALID_PARAMETER    The device path is invalid.

**/
EFI_STATUS
NvmeIsHcDevicePathValid (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN UINTN                     DevicePathLength
  );

/**
  Build the device path for an Nvm Express device with given namespace identifier
  and namespace extended unique identifier.

  @param[in]  Private              A pointer to the PEI_NVME_CONTROLLER_PRIVATE_DATA
                                   data structure.
  @param[in]  NamespaceId          The given namespace identifier.
  @param[in]  NamespaceUuid        The given namespace extended unique identifier.
  @param[out] DevicePathLength     The length of the device path in bytes specified
                                   by DevicePath.
  @param[out] DevicePath           The device path of Nvm Express device.

  @retval EFI_SUCCESS              The operation succeeds.
  @retval EFI_INVALID_PARAMETER    The parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES     The operation fails due to lack of resources.

**/
EFI_STATUS
NvmeBuildDevicePath (
  IN  PEI_NVME_CONTROLLER_PRIVATE_DATA  *Private,
  IN  UINT32                            NamespaceId,
  IN  UINT64                            NamespaceUuid,
  OUT UINTN                             *DevicePathLength,
  OUT EFI_DEVICE_PATH_PROTOCOL          **DevicePath
  );

/**
  Determine if a specific NVM Express controller can be skipped for S3 phase.

  @param[in]  HcDevicePath          Device path of the controller.
  @param[in]  HcDevicePathLength    Length of the device path specified by
                                    HcDevicePath.

  @retval    The number of ports that need to be enumerated.

**/
BOOLEAN
NvmeS3SkipThisController (
  IN  EFI_DEVICE_PATH_PROTOCOL  *HcDevicePath,
  IN  UINTN                     HcDevicePathLength
  );

/**
  Callback for EDKII_PCI_DEVICE_PPI installation.

  @param[in] PeiServices         Pointer to PEI Services Table.
  @param[in] NotifyDescriptor    Pointer to the descriptor for the Notification
                                 event that caused this function to execute.
  @param[in] Ppi                 Pointer to the PPI data associated with this function.

  @retval EFI_SUCCESS            The function completes successfully
  @retval Others                 Cannot initialize Nvme controller from given PCI_DEVICE_PPI

**/
EFI_STATUS
EFIAPI
NvmePciDevicePpiInstallationCallback (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  );

/**
  Callback for EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI installation.

  @param[in] PeiServices         Pointer to PEI Services Table.
  @param[in] NotifyDescriptor    Pointer to the descriptor for the Notification
                                 event that caused this function to execute.
  @param[in] Ppi                 Pointer to the PPI data associated with this function.

  @retval EFI_SUCCESS            The function completes successfully
  @retval Others                 Cannot initialize Nvme controller from given EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI

**/
EFI_STATUS
EFIAPI
NvmeHostControllerPpiInstallationCallback (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  );
