/** @file
  The device path help function.

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

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

**/

#include "NvmExpressPei.h"

//
// Template for an Nvm Express Device Path node
//
NVME_NAMESPACE_DEVICE_PATH  mNvmeDevicePathNodeTemplate = {
  {        // Header
    MESSAGING_DEVICE_PATH,
    MSG_NVME_NAMESPACE_DP,
    {
      (UINT8)(sizeof (NVME_NAMESPACE_DEVICE_PATH)),
      (UINT8)((sizeof (NVME_NAMESPACE_DEVICE_PATH)) >> 8)
    }
  },
  0x0,     // NamespaceId
  0x0      // NamespaceUuid
};

//
// Template for an End of entire Device Path node
//
EFI_DEVICE_PATH_PROTOCOL  mNvmeEndDevicePathNodeTemplate = {
  END_DEVICE_PATH_TYPE,
  END_ENTIRE_DEVICE_PATH_SUBTYPE,
  {
    (UINT8)(sizeof (EFI_DEVICE_PATH_PROTOCOL)),
    (UINT8)((sizeof (EFI_DEVICE_PATH_PROTOCOL)) >> 8)
  }
};

/**
  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
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *Walker;

  if ((DevicePath == NULL) || (InstanceSize == NULL) || (EntireDevicePathEnd == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Find the end of the device path instance
  //
  Walker = DevicePath;
  while (Walker->Type != END_DEVICE_PATH_TYPE) {
    Walker = NextDevicePathNode (Walker);
  }

  //
  // Check if 'Walker' points to the end of an entire device path
  //
  if (Walker->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE) {
    *EntireDevicePathEnd = TRUE;
  } else if (Walker->SubType == END_INSTANCE_DEVICE_PATH_SUBTYPE) {
    *EntireDevicePathEnd = FALSE;
  } else {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Compute the size of the device path instance
  //
  *InstanceSize = ((UINTN)Walker - (UINTN)(DevicePath)) + sizeof (EFI_DEVICE_PATH_PROTOCOL);

  return EFI_SUCCESS;
}

/**
  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
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *Start;
  UINTN                     Size;

  if (DevicePath == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Validate the DevicePathLength is big enough to touch the first node.
  //
  if (DevicePathLength < sizeof (EFI_DEVICE_PATH_PROTOCOL)) {
    return EFI_INVALID_PARAMETER;
  }

  Start = DevicePath;
  while (!(DevicePath->Type == END_DEVICE_PATH_TYPE &&
           DevicePath->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE))
  {
    DevicePath = NextDevicePathNode (DevicePath);

    //
    // Prevent overflow and invalid zero in the 'Length' field of a device path
    // node.
    //
    if ((UINTN)DevicePath <= (UINTN)Start) {
      return EFI_INVALID_PARAMETER;
    }

    //
    // Prevent touching memory beyond given DevicePathLength.
    //
    if ((UINTN)DevicePath - (UINTN)Start >
        DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL))
    {
      return EFI_INVALID_PARAMETER;
    }
  }

  //
  // Check if the device path and its size match exactly with each other.
  //
  Size = ((UINTN)DevicePath - (UINTN)Start) + sizeof (EFI_DEVICE_PATH_PROTOCOL);
  if (Size != DevicePathLength) {
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}

/**
  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
  )
{
  EFI_DEVICE_PATH_PROTOCOL    *DevicePathWalker;
  NVME_NAMESPACE_DEVICE_PATH  *NvmeDeviceNode;

  if ((DevicePathLength == NULL) || (DevicePath == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  *DevicePathLength = Private->DevicePathLength + sizeof (NVME_NAMESPACE_DEVICE_PATH);
  *DevicePath       = AllocatePool (*DevicePathLength);
  if (*DevicePath == NULL) {
    *DevicePathLength = 0;
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Construct the host controller part device nodes
  //
  DevicePathWalker = *DevicePath;
  CopyMem (
    DevicePathWalker,
    Private->DevicePath,
    Private->DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL)
    );

  //
  // Construct the Nvm Express device node
  //
  DevicePathWalker = (EFI_DEVICE_PATH_PROTOCOL *)((UINT8 *)DevicePathWalker +
                                                  (Private->DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL)));
  CopyMem (
    DevicePathWalker,
    &mNvmeDevicePathNodeTemplate,
    sizeof (mNvmeDevicePathNodeTemplate)
    );
  NvmeDeviceNode                = (NVME_NAMESPACE_DEVICE_PATH *)DevicePathWalker;
  NvmeDeviceNode->NamespaceId   = NamespaceId;
  NvmeDeviceNode->NamespaceUuid = NamespaceUuid;

  //
  // Construct the end device node
  //
  DevicePathWalker = (EFI_DEVICE_PATH_PROTOCOL *)((UINT8 *)DevicePathWalker +
                                                  sizeof (NVME_NAMESPACE_DEVICE_PATH));
  CopyMem (
    DevicePathWalker,
    &mNvmeEndDevicePathNodeTemplate,
    sizeof (mNvmeEndDevicePathNodeTemplate)
    );

  return EFI_SUCCESS;
}
