/** @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.
//
STATIC CHAR16 mVariableName[] = L"PlatformConfig";


/**
  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;
}
