/** @file

  Utility functions for serializing (persistently storing) and deserializing
  OVMF's platform configuration.

  Copyright (C) 2014, Red Hat, Inc.

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

**/

#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Guid/OvmfPlatformConfig.h>

#include "PlatformConfig.h"

//
// Name of the UEFI variable that we use for persistent storage.
//
CHAR16  mVariableName[] = L"PlatformConfig";
CHAR16  mHiiFormName[]  = L"MainFormState";

/**
  Serialize and persistently save platform configuration.

  @param[in] PlatformConfig  The platform configuration to serialize and save.

  @return  Status codes returned by gRT->SetVariable().
**/
EFI_STATUS
EFIAPI
PlatformConfigSave (
  IN PLATFORM_CONFIG  *PlatformConfig
  )
{
  EFI_STATUS  Status;

  //
  // We could implement any kind of translation here, as part of serialization.
  // For example, we could expose the platform configuration in separate
  // variables with human-readable contents, allowing other tools to access
  // them more easily. For now, just save a binary dump.
  //
  Status = gRT->SetVariable (
                  mVariableName,
                  &gOvmfPlatformConfigGuid,
                  EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS |
                  EFI_VARIABLE_RUNTIME_ACCESS,
                  sizeof *PlatformConfig,
                  PlatformConfig
                  );
  return Status;
}

/**
  Load and deserialize platform configuration.

  When the function fails, output parameters are indeterminate.

  @param[out] PlatformConfig    The platform configuration to receive the
                                loaded data.

  @param[out] OptionalElements  This bitmap describes the presence of optional
                                configuration elements that have been loaded.
                                PLATFORM_CONFIG_F_DOWNGRADE means that some
                                unknown elements, present in the wire format,
                                have been ignored.

  @retval  EFI_SUCCESS         Loading & deserialization successful.
  @return                      Error codes returned by GetVariable2().
**/
EFI_STATUS
EFIAPI
PlatformConfigLoad (
  OUT PLATFORM_CONFIG  *PlatformConfig,
  OUT UINT64           *OptionalElements
  )
{
  VOID        *Data;
  UINTN       DataSize;
  EFI_STATUS  Status;

  //
  // Any translation done in PlatformConfigSave() would have to be mirrored
  // here. For now, just load the binary dump.
  //
  // Versioning of the binary wire format is implemented based on size
  // (only incremental changes, ie. new fields), and on GUID.
  // (Incompatible changes require a GUID change.)
  //
  Status = GetVariable2 (
             mVariableName,
             &gOvmfPlatformConfigGuid,
             &Data,
             &DataSize
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *OptionalElements = 0;
  if (DataSize > sizeof *PlatformConfig) {
    //
    // Handle firmware downgrade -- keep only leading part.
    //
    CopyMem (PlatformConfig, Data, sizeof *PlatformConfig);
    *OptionalElements |= PLATFORM_CONFIG_F_DOWNGRADE;
  } else {
    CopyMem (PlatformConfig, Data, DataSize);

    //
    // Handle firmware upgrade -- zero out missing fields.
    //
    ZeroMem (
      (UINT8 *)PlatformConfig + DataSize,
      sizeof *PlatformConfig - DataSize
      );
  }

  //
  // Based on DataSize, report the optional features that we recognize.
  //
  if (DataSize >= (OFFSET_OF (PLATFORM_CONFIG, VerticalResolution) +
                   sizeof PlatformConfig->VerticalResolution))
  {
    *OptionalElements |= PLATFORM_CONFIG_F_GRAPHICS_RESOLUTION;
  }

  FreePool (Data);
  return EFI_SUCCESS;
}
