/** @file
  The realization of EFI_RAM_DISK_PROTOCOL.

  Copyright (c) 2016 - 2019, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
  Copyright (c) Microsoft Corporation.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "RamDiskImpl.h"

RAM_DISK_PRIVATE_DATA mRamDiskPrivateDataTemplate = {
  RAM_DISK_PRIVATE_DATA_SIGNATURE,
  NULL
};

MEDIA_RAM_DISK_DEVICE_PATH  mRamDiskDeviceNodeTemplate = {
  {
    MEDIA_DEVICE_PATH,
    MEDIA_RAM_DISK_DP,
    {
      (UINT8) (sizeof (MEDIA_RAM_DISK_DEVICE_PATH)),
      (UINT8) ((sizeof (MEDIA_RAM_DISK_DEVICE_PATH)) >> 8)
    }
  }
};

BOOLEAN  mRamDiskSsdtTableKeyValid = FALSE;
UINTN    mRamDiskSsdtTableKey;


/**
  Initialize the RAM disk device node.

  @param[in]      PrivateData     Points to RAM disk private data.
  @param[in, out] RamDiskDevNode  Points to the RAM disk device node.

**/
VOID
RamDiskInitDeviceNode (
  IN     RAM_DISK_PRIVATE_DATA         *PrivateData,
  IN OUT MEDIA_RAM_DISK_DEVICE_PATH    *RamDiskDevNode
  )
{
  WriteUnaligned64 (
    (UINT64 *) &(RamDiskDevNode->StartingAddr[0]),
    (UINT64) PrivateData->StartingAddr
    );
  WriteUnaligned64 (
    (UINT64 *) &(RamDiskDevNode->EndingAddr[0]),
    (UINT64) PrivateData->StartingAddr + PrivateData->Size - 1
    );
  CopyGuid (&RamDiskDevNode->TypeGuid, &PrivateData->TypeGuid);
  RamDiskDevNode->Instance = PrivateData->InstanceNumber;
}


/**
  Initialize and publish NVDIMM root device SSDT in ACPI table.

  @retval EFI_SUCCESS        The NVDIMM root device SSDT is published.
  @retval Others             The NVDIMM root device SSDT is not published.

**/
EFI_STATUS
RamDiskPublishSsdt (
  VOID
  )
{
  EFI_STATUS                     Status;
  EFI_ACPI_DESCRIPTION_HEADER    *Table;
  UINTN                          SectionInstance;
  UINTN                          TableSize;

  Status          = EFI_SUCCESS;
  SectionInstance = 0;

  //
  // Scan all the EFI raw section instances in FV to find the NVDIMM root
  // device SSDT.
  //
  while (TRUE) {
    Status = GetSectionFromFv (
               &gEfiCallerIdGuid,
               EFI_SECTION_RAW,
               SectionInstance,
               (VOID **) &Table,
               &TableSize
               );
    if (EFI_ERROR (Status)) {
      break;
    }

    if (Table->OemTableId == SIGNATURE_64 ('R', 'a', 'm', 'D', 'i', 's', 'k', ' ')) {
      Status = mAcpiTableProtocol->InstallAcpiTable (
                                     mAcpiTableProtocol,
                                     Table,
                                     TableSize,
                                     &mRamDiskSsdtTableKey
                                     );
      ASSERT_EFI_ERROR (Status);

      if (!EFI_ERROR (Status)) {
        mRamDiskSsdtTableKeyValid = TRUE;
      }

      FreePool (Table);
      return Status;
    } else {
      FreePool (Table);
      SectionInstance++;
    }
  }

  return Status;
}


/**
  Publish the RAM disk NVDIMM Firmware Interface Table (NFIT) to the ACPI
  table.

  @param[in] PrivateData          Points to RAM disk private data.

  @retval EFI_SUCCESS             The RAM disk NFIT has been published.
  @retval others                  The RAM disk NFIT has not been published.

**/
EFI_STATUS
RamDiskPublishNfit (
  IN RAM_DISK_PRIVATE_DATA        *PrivateData
  )
{
  EFI_STATUS                                    Status;
  EFI_MEMORY_DESCRIPTOR                         *MemoryMap;
  EFI_MEMORY_DESCRIPTOR                         *MemoryMapEntry;
  EFI_MEMORY_DESCRIPTOR                         *MemoryMapEnd;
  UINTN                                         TableIndex;
  VOID                                          *TableHeader;
  EFI_ACPI_TABLE_VERSION                        TableVersion;
  UINTN                                         TableKey;
  EFI_ACPI_DESCRIPTION_HEADER                   *NfitHeader;
  EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE
                                                *SpaRange;
  VOID                                          *Nfit;
  UINT32                                        NfitLen;
  UINTN                                         MemoryMapSize;
  UINTN                                         MapKey;
  UINTN                                         DescriptorSize;
  UINT32                                        DescriptorVersion;
  UINT64                                        CurrentData;
  UINT8                                         Checksum;
  BOOLEAN                                       MemoryFound;

  //
  // Get the EFI memory map.
  //
  MemoryMapSize = 0;
  MemoryMap     = NULL;
  MemoryFound   = FALSE;

  Status = gBS->GetMemoryMap (
                  &MemoryMapSize,
                  MemoryMap,
                  &MapKey,
                  &DescriptorSize,
                  &DescriptorVersion
                  );
  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
  do {
    MemoryMap = (EFI_MEMORY_DESCRIPTOR *) AllocatePool (MemoryMapSize);
    ASSERT (MemoryMap != NULL);
    Status = gBS->GetMemoryMap (
                    &MemoryMapSize,
                    MemoryMap,
                    &MapKey,
                    &DescriptorSize,
                    &DescriptorVersion
                    );
    if (EFI_ERROR (Status)) {
      FreePool (MemoryMap);
    }
  } while (Status == EFI_BUFFER_TOO_SMALL);
  ASSERT_EFI_ERROR (Status);

  MemoryMapEntry = MemoryMap;
  MemoryMapEnd   = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + MemoryMapSize);
  while ((UINTN) MemoryMapEntry < (UINTN) MemoryMapEnd) {
    if ((MemoryMapEntry->Type == EfiReservedMemoryType) &&
        (MemoryMapEntry->PhysicalStart <= PrivateData->StartingAddr) &&
        (MemoryMapEntry->PhysicalStart +
         MultU64x32 (MemoryMapEntry->NumberOfPages, EFI_PAGE_SIZE)
         >= PrivateData->StartingAddr + PrivateData->Size)) {
      MemoryFound = TRUE;
      DEBUG ((
        EFI_D_INFO,
        "RamDiskPublishNfit: RAM disk with reserved memory type, will publish to NFIT.\n"
        ));
      break;
    }
    MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
  }
  FreePool (MemoryMap);

  if (!MemoryFound) {
    return EFI_NOT_FOUND;
  }

  //
  // Determine whether there is a NFIT already in the ACPI table.
  //
  Status      = EFI_SUCCESS;
  TableIndex  = 0;
  TableKey    = 0;
  TableHeader = NULL;

  while (!EFI_ERROR (Status)) {
    Status = mAcpiSdtProtocol->GetAcpiTable (
                                 TableIndex,
                                 (EFI_ACPI_SDT_HEADER **)&TableHeader,
                                 &TableVersion,
                                 &TableKey
                                 );
    if (!EFI_ERROR (Status)) {
      TableIndex++;

      if (((EFI_ACPI_SDT_HEADER *)TableHeader)->Signature ==
          EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE_STRUCTURE_SIGNATURE) {
        break;
      }
    }
  }

  if (!EFI_ERROR (Status)) {
    //
    // A NFIT is already in the ACPI table.
    //
    DEBUG ((
      EFI_D_INFO,
      "RamDiskPublishNfit: A NFIT is already exist in the ACPI Table.\n"
      ));

    NfitHeader = (EFI_ACPI_DESCRIPTION_HEADER *)TableHeader;
    NfitLen    = NfitHeader->Length + sizeof (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE);
    Nfit       = AllocateZeroPool (NfitLen);
    if (Nfit == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    CopyMem (Nfit, TableHeader, NfitHeader->Length);

    //
    // Update the NFIT head pointer.
    //
    NfitHeader = (EFI_ACPI_DESCRIPTION_HEADER *)Nfit;

    //
    // Uninstall the origin NFIT from the ACPI table.
    //
    Status = mAcpiTableProtocol->UninstallAcpiTable (
                                   mAcpiTableProtocol,
                                   TableKey
                                   );
    ASSERT_EFI_ERROR (Status);

    if (EFI_ERROR (Status)) {
      FreePool (Nfit);
      return Status;
    }

    //
    // Append the System Physical Address (SPA) Range Structure at the end
    // of the origin NFIT.
    //
    SpaRange   = (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE *)
                 ((UINT8 *)Nfit + NfitHeader->Length);

    //
    // Update the length field of the NFIT
    //
    NfitHeader->Length   = NfitLen;

    //
    // The checksum will be updated after the new contents are appended.
    //
    NfitHeader->Checksum = 0;
  } else {
    //
    // Assumption is made that if no NFIT is in the ACPI table, there is no
    // NVDIMM root device in the \SB scope.
    // Therefore, a NVDIMM root device will be reported via Secondary System
    // Description Table (SSDT).
    //
    Status = RamDiskPublishSsdt ();
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // No NFIT is in the ACPI table, we will create one here.
    //
    DEBUG ((
      EFI_D_INFO,
      "RamDiskPublishNfit: No NFIT is in the ACPI Table, will create one.\n"
      ));

    NfitLen = sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE) +
              sizeof (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE);
    Nfit    = AllocateZeroPool (NfitLen);
    if (Nfit == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    SpaRange = (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE *)
               ((UINT8 *)Nfit + sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE));

    NfitHeader                  = (EFI_ACPI_DESCRIPTION_HEADER *)Nfit;
    NfitHeader->Signature       = EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE_STRUCTURE_SIGNATURE;
    NfitHeader->Length          = NfitLen;
    NfitHeader->Revision        = EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE_REVISION;
    NfitHeader->Checksum        = 0;
    NfitHeader->OemRevision     = PcdGet32 (PcdAcpiDefaultOemRevision);
    NfitHeader->CreatorId       = PcdGet32 (PcdAcpiDefaultCreatorId);
    NfitHeader->CreatorRevision = PcdGet32 (PcdAcpiDefaultCreatorRevision);
    CurrentData                 = PcdGet64 (PcdAcpiDefaultOemTableId);
    CopyMem (NfitHeader->OemId, PcdGetPtr (PcdAcpiDefaultOemId), sizeof (NfitHeader->OemId));
    CopyMem (&NfitHeader->OemTableId, &CurrentData, sizeof (UINT64));
  }

  //
  // Fill in the content of the SPA Range Structure.
  //
  SpaRange->Type   = EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE_TYPE;
  SpaRange->Length = sizeof (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE);
  SpaRange->SystemPhysicalAddressRangeBase   = PrivateData->StartingAddr;
  SpaRange->SystemPhysicalAddressRangeLength = PrivateData->Size;
  CopyGuid (&SpaRange->AddressRangeTypeGUID, &PrivateData->TypeGuid);

  Checksum             = CalculateCheckSum8((UINT8 *)Nfit, NfitHeader->Length);
  NfitHeader->Checksum = Checksum;

  //
  // Publish the NFIT to the ACPI table.
  // Note, since the NFIT might be modified by other driver, therefore, we
  // do not track the returning TableKey from the InstallAcpiTable().
  //
  Status = mAcpiTableProtocol->InstallAcpiTable (
                                 mAcpiTableProtocol,
                                 Nfit,
                                 NfitHeader->Length,
                                 &TableKey
                                 );
  ASSERT_EFI_ERROR (Status);

  FreePool (Nfit);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  PrivateData->InNfit = TRUE;

  return EFI_SUCCESS;
}


/**
  Unpublish the RAM disk NVDIMM Firmware Interface Table (NFIT) from the
  ACPI table.

  @param[in] PrivateData          Points to RAM disk private data.

  @retval EFI_SUCCESS             The RAM disk NFIT has been unpublished.
  @retval others                  The RAM disk NFIT has not been unpublished.

**/
EFI_STATUS
RamDiskUnpublishNfit (
  IN RAM_DISK_PRIVATE_DATA        *PrivateData
  )
{
  EFI_STATUS                                    Status;
  UINTN                                         TableIndex;
  VOID                                          *TableHeader;
  EFI_ACPI_TABLE_VERSION                        TableVersion;
  UINTN                                         TableKey;
  EFI_ACPI_DESCRIPTION_HEADER                   *NewNfitHeader;
  EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE
                                                *SpaRange;
  VOID                                          *NewNfit;
  VOID                                          *NewNfitPtr;
  EFI_ACPI_6_1_NFIT_STRUCTURE_HEADER            *NfitStructHeader;
  UINT32                                        NewNfitLen;
  UINT32                                        RemainLen;
  UINT8                                         Checksum;

  //
  // Find the NFIT in the ACPI table.
  //
  Status      = EFI_SUCCESS;
  TableIndex  = 0;
  TableKey    = 0;
  TableHeader = NULL;

  while (!EFI_ERROR (Status)) {
    Status = mAcpiSdtProtocol->GetAcpiTable (
                                 TableIndex,
                                 (EFI_ACPI_SDT_HEADER **)&TableHeader,
                                 &TableVersion,
                                 &TableKey
                                 );
    if (!EFI_ERROR (Status)) {
      TableIndex++;

      if (((EFI_ACPI_SDT_HEADER *)TableHeader)->Signature ==
          EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE_STRUCTURE_SIGNATURE) {
        break;
      }
    }
  }

  if (EFI_ERROR (Status)) {
    //
    // No NFIT is found in the ACPI table.
    //
    return EFI_NOT_FOUND;
  }

  NewNfitLen    = ((EFI_ACPI_DESCRIPTION_HEADER *)TableHeader)->Length -
                  sizeof (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE);

  //
  // After removing this RAM disk from the NFIT, if no other structure is in
  // the NFIT, we just remove the NFIT and the SSDT which is used to report
  // the NVDIMM root device.
  //
  if (NewNfitLen == sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE)) {
    //
    // Remove the NFIT.
    //
    Status = mAcpiTableProtocol->UninstallAcpiTable (
                                   mAcpiTableProtocol,
                                   TableKey
                                   );
    ASSERT_EFI_ERROR (Status);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Remove the SSDT which is used by RamDiskDxe driver to report the NVDIMM
    // root device.
    // We do not care the return status since this SSDT might already be
    // uninstalled by other drivers to update the information of the NVDIMM
    // root device.
    //
    if (mRamDiskSsdtTableKeyValid) {
      mRamDiskSsdtTableKeyValid = FALSE;

      mAcpiTableProtocol->UninstallAcpiTable (
                            mAcpiTableProtocol,
                            mRamDiskSsdtTableKey
                            );
    }

    return EFI_SUCCESS;
  }

  NewNfit = AllocateZeroPool (NewNfitLen);
  if (NewNfit == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Get a copy of the old NFIT header content.
  //
  CopyMem (NewNfit, TableHeader, sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE));
  NewNfitHeader           = (EFI_ACPI_DESCRIPTION_HEADER *)NewNfit;
  NewNfitHeader->Length   = NewNfitLen;
  NewNfitHeader->Checksum = 0;

  //
  // Copy the content of required NFIT structures.
  //
  NewNfitPtr       = (UINT8 *)NewNfit + sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE);
  RemainLen        = NewNfitLen - sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE);
  NfitStructHeader = (EFI_ACPI_6_1_NFIT_STRUCTURE_HEADER *)
                     ((UINT8 *)TableHeader + sizeof (EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE));
  while (RemainLen > 0) {
    if ((NfitStructHeader->Type == EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE_TYPE) &&
        (NfitStructHeader->Length == sizeof (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE))) {
      SpaRange = (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE *)NfitStructHeader;

      if ((SpaRange->SystemPhysicalAddressRangeBase == PrivateData->StartingAddr) &&
          (SpaRange->SystemPhysicalAddressRangeLength == PrivateData->Size) &&
          (CompareGuid (&SpaRange->AddressRangeTypeGUID, &PrivateData->TypeGuid))) {
        //
        // Skip the SPA Range Structure for the RAM disk to be unpublished
        // from NFIT.
        //
        NfitStructHeader = (EFI_ACPI_6_1_NFIT_STRUCTURE_HEADER *)
                           ((UINT8 *)NfitStructHeader + NfitStructHeader->Length);
        continue;
      }
    }

    //
    // Copy the content of origin NFIT.
    //
    CopyMem (NewNfitPtr, NfitStructHeader, NfitStructHeader->Length);
    NewNfitPtr = (UINT8 *)NewNfitPtr + NfitStructHeader->Length;

    //
    // Move to the header of next NFIT structure.
    //
    RemainLen       -= NfitStructHeader->Length;
    NfitStructHeader = (EFI_ACPI_6_1_NFIT_STRUCTURE_HEADER *)
                       ((UINT8 *)NfitStructHeader + NfitStructHeader->Length);
  }

  Checksum                = CalculateCheckSum8((UINT8 *)NewNfit, NewNfitHeader->Length);
  NewNfitHeader->Checksum = Checksum;

  Status = mAcpiTableProtocol->UninstallAcpiTable (
                                 mAcpiTableProtocol,
                                 TableKey
                                 );
  ASSERT_EFI_ERROR (Status);

  if (EFI_ERROR (Status)) {
    FreePool (NewNfit);
    return Status;
  }

  //
  // Publish the NFIT to the ACPI table.
  // Note, since the NFIT might be modified by other driver, therefore, we
  // do not track the returning TableKey from the InstallAcpiTable().
  //
  Status = mAcpiTableProtocol->InstallAcpiTable (
                                 mAcpiTableProtocol,
                                 NewNfit,
                                 NewNfitLen,
                                 &TableKey
                                 );
  ASSERT_EFI_ERROR (Status);

  FreePool (NewNfit);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}


/**
  Register a RAM disk with specified address, size and type.

  @param[in]  RamDiskBase    The base address of registered RAM disk.
  @param[in]  RamDiskSize    The size of registered RAM disk.
  @param[in]  RamDiskType    The type of registered RAM disk. The GUID can be
                             any of the values defined in section 9.3.6.9, or a
                             vendor defined GUID.
  @param[in]  ParentDevicePath
                             Pointer to the parent device path. If there is no
                             parent device path then ParentDevicePath is NULL.
  @param[out] DevicePath     On return, points to a pointer to the device path
                             of the RAM disk device.
                             If ParentDevicePath is not NULL, the returned
                             DevicePath is created by appending a RAM disk node
                             to the parent device path. If ParentDevicePath is
                             NULL, the returned DevicePath is a RAM disk device
                             path without appending. This function is
                             responsible for allocating the buffer DevicePath
                             with the boot service AllocatePool().

  @retval EFI_SUCCESS             The RAM disk is registered successfully.
  @retval EFI_INVALID_PARAMETER   DevicePath or RamDiskType is NULL.
                                  RamDiskSize is 0.
  @retval EFI_ALREADY_STARTED     A Device Path Protocol instance to be created
                                  is already present in the handle database.
  @retval EFI_OUT_OF_RESOURCES    The RAM disk register operation fails due to
                                  resource limitation.

**/
EFI_STATUS
EFIAPI
RamDiskRegister (
  IN UINT64                       RamDiskBase,
  IN UINT64                       RamDiskSize,
  IN EFI_GUID                     *RamDiskType,
  IN EFI_DEVICE_PATH              *ParentDevicePath     OPTIONAL,
  OUT EFI_DEVICE_PATH_PROTOCOL    **DevicePath
  )
{
  EFI_STATUS                      Status;
  RAM_DISK_PRIVATE_DATA           *PrivateData;
  RAM_DISK_PRIVATE_DATA           *RegisteredPrivateData;
  MEDIA_RAM_DISK_DEVICE_PATH      *RamDiskDevNode;
  UINTN                           DevicePathSize;
  LIST_ENTRY                      *Entry;

  if ((0 == RamDiskSize) || (NULL == RamDiskType) || (NULL == DevicePath)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Add check to prevent data read across the memory boundary
  //
  if ((RamDiskSize > MAX_UINTN) ||
      (RamDiskBase > MAX_UINTN - RamDiskSize + 1)) {
    return EFI_INVALID_PARAMETER;
  }

  RamDiskDevNode = NULL;

  //
  // Create a new RAM disk instance and initialize its private data
  //
  PrivateData = AllocateCopyPool (
                  sizeof (RAM_DISK_PRIVATE_DATA),
                  &mRamDiskPrivateDataTemplate
                  );
  if (NULL == PrivateData) {
    return EFI_OUT_OF_RESOURCES;
  }

  PrivateData->StartingAddr = RamDiskBase;
  PrivateData->Size         = RamDiskSize;
  CopyGuid (&PrivateData->TypeGuid, RamDiskType);
  InitializeListHead (&PrivateData->ThisInstance);

  //
  // Generate device path information for the registered RAM disk
  //
  RamDiskDevNode = AllocateCopyPool (
                     sizeof (MEDIA_RAM_DISK_DEVICE_PATH),
                     &mRamDiskDeviceNodeTemplate
                     );
  if (NULL == RamDiskDevNode) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorExit;
  }

  RamDiskInitDeviceNode (PrivateData, RamDiskDevNode);

  *DevicePath = AppendDevicePathNode (
                  ParentDevicePath,
                  (EFI_DEVICE_PATH_PROTOCOL *) RamDiskDevNode
                  );
  if (NULL == *DevicePath) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorExit;
  }

  PrivateData->DevicePath = *DevicePath;

  //
  // Check whether the created device path is already present in the handle
  // database
  //
  if (!IsListEmpty(&RegisteredRamDisks)) {
    DevicePathSize = GetDevicePathSize (PrivateData->DevicePath);

    BASE_LIST_FOR_EACH (Entry, &RegisteredRamDisks) {
      RegisteredPrivateData = RAM_DISK_PRIVATE_FROM_THIS (Entry);
      if (DevicePathSize == GetDevicePathSize (RegisteredPrivateData->DevicePath)) {
        //
        // Compare device path
        //
        if ((CompareMem (
               PrivateData->DevicePath,
               RegisteredPrivateData->DevicePath,
               DevicePathSize)) == 0) {
          *DevicePath = NULL;
          Status      = EFI_ALREADY_STARTED;
          goto ErrorExit;
        }
      }
    }
  }

  //
  // Fill Block IO protocol informations for the RAM disk
  //
  RamDiskInitBlockIo (PrivateData);

  //
  // Install EFI_DEVICE_PATH_PROTOCOL & EFI_BLOCK_IO(2)_PROTOCOL on a new
  // handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &PrivateData->Handle,
                  &gEfiBlockIoProtocolGuid,
                  &PrivateData->BlockIo,
                  &gEfiBlockIo2ProtocolGuid,
                  &PrivateData->BlockIo2,
                  &gEfiDevicePathProtocolGuid,
                  PrivateData->DevicePath,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  //
  // Insert the newly created one to the registered RAM disk list
  //
  InsertTailList (&RegisteredRamDisks, &PrivateData->ThisInstance);

  gBS->ConnectController (PrivateData->Handle, NULL, NULL, TRUE);

  FreePool (RamDiskDevNode);

  if ((mAcpiTableProtocol != NULL) && (mAcpiSdtProtocol != NULL)) {
    RamDiskPublishNfit (PrivateData);
  }

  return EFI_SUCCESS;

ErrorExit:
  if (RamDiskDevNode != NULL) {
    FreePool (RamDiskDevNode);
  }

  if (PrivateData != NULL) {
    if (PrivateData->DevicePath) {
      FreePool (PrivateData->DevicePath);
    }

    FreePool (PrivateData);
  }

  return Status;
}


/**
  Unregister a RAM disk specified by DevicePath.

  @param[in] DevicePath      A pointer to the device path that describes a RAM
                             Disk device.

  @retval EFI_SUCCESS             The RAM disk is unregistered successfully.
  @retval EFI_INVALID_PARAMETER   DevicePath is NULL.
  @retval EFI_UNSUPPORTED         The device specified by DevicePath is not a
                                  valid ramdisk device path and not supported
                                  by the driver.
  @retval EFI_NOT_FOUND           The RAM disk pointed by DevicePath doesn't
                                  exist.

**/
EFI_STATUS
EFIAPI
RamDiskUnregister (
  IN  EFI_DEVICE_PATH_PROTOCOL    *DevicePath
  )
{
  LIST_ENTRY                      *Entry;
  LIST_ENTRY                      *NextEntry;
  BOOLEAN                         Found;
  UINT64                          StartingAddr;
  UINT64                          EndingAddr;
  EFI_DEVICE_PATH_PROTOCOL        *Header;
  MEDIA_RAM_DISK_DEVICE_PATH      *RamDiskDevNode;
  RAM_DISK_PRIVATE_DATA           *PrivateData;

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

  //
  // Locate the RAM disk device node.
  //
  RamDiskDevNode = NULL;
  Header         = DevicePath;
  do {
    //
    // Test if the current device node is a RAM disk.
    //
    if ((MEDIA_DEVICE_PATH == Header->Type) &&
      (MEDIA_RAM_DISK_DP == Header->SubType)) {
      RamDiskDevNode = (MEDIA_RAM_DISK_DEVICE_PATH *) Header;

      break;
    }

    Header = NextDevicePathNode (Header);
  } while ((Header->Type != END_DEVICE_PATH_TYPE));

  if (NULL == RamDiskDevNode) {
    return EFI_UNSUPPORTED;
  }

  Found          = FALSE;
  StartingAddr   = ReadUnaligned64 ((UINT64 *) &(RamDiskDevNode->StartingAddr[0]));
  EndingAddr     = ReadUnaligned64 ((UINT64 *) &(RamDiskDevNode->EndingAddr[0]));

  if (!IsListEmpty(&RegisteredRamDisks)) {
    BASE_LIST_FOR_EACH_SAFE (Entry, NextEntry, &RegisteredRamDisks) {
      PrivateData = RAM_DISK_PRIVATE_FROM_THIS (Entry);

      //
      // Unregister the RAM disk given by its starting address, ending address
      // and type guid.
      //
      if ((StartingAddr == PrivateData->StartingAddr) &&
          (EndingAddr == PrivateData->StartingAddr + PrivateData->Size - 1) &&
          (CompareGuid (&RamDiskDevNode->TypeGuid, &PrivateData->TypeGuid))) {
        //
        // Remove the content for this RAM disk in NFIT.
        //
        if (PrivateData->InNfit) {
          RamDiskUnpublishNfit (PrivateData);
        }

        //
        // Uninstall the EFI_DEVICE_PATH_PROTOCOL & EFI_BLOCK_IO(2)_PROTOCOL
        //
        gBS->UninstallMultipleProtocolInterfaces (
               PrivateData->Handle,
               &gEfiBlockIoProtocolGuid,
               &PrivateData->BlockIo,
               &gEfiBlockIo2ProtocolGuid,
               &PrivateData->BlockIo2,
               &gEfiDevicePathProtocolGuid,
               (EFI_DEVICE_PATH_PROTOCOL *) PrivateData->DevicePath,
               NULL
               );

        RemoveEntryList (&PrivateData->ThisInstance);

        if (RamDiskCreateHii == PrivateData->CreateMethod) {
          //
          // If a RAM disk is created within HII, then the RamDiskDxe driver
          // driver is responsible for freeing the allocated memory for the
          // RAM disk.
          //
          FreePool ((VOID *)(UINTN) PrivateData->StartingAddr);
        }

        FreePool (PrivateData->DevicePath);
        FreePool (PrivateData);
        Found = TRUE;

        break;
      }
    }
  }

  if (TRUE == Found) {
    return EFI_SUCCESS;
  } else {
    return EFI_NOT_FOUND;
  }
}
