/**@file
  Negotiate SMI features with QEMU, and configure UefiCpuPkg/PiSmmCpuDxeSmm
  accordingly.

  Copyright (C) 2016-2017, Red Hat, Inc.
  Copyright (c) Microsoft Corporation

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

#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/MemEncryptSevLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/QemuFwCfgLib.h>

#include "SmiFeatures.h"

//
// The following bit value stands for "broadcast SMI" in the
// "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files.
//
#define ICH9_LPC_SMI_F_BROADCAST  BIT0
//
// The following bit value stands for "enable CPU hotplug, and inject an SMI
// with control value ICH9_APM_CNT_CPU_HOTPLUG upon hotplug", in the
// "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files.
//
#define ICH9_LPC_SMI_F_CPU_HOTPLUG  BIT1
//
// The following bit value stands for "enable CPU hot-unplug, and inject an SMI
// with control value ICH9_APM_CNT_CPU_HOTPLUG upon hot-unplug", in the
// "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files.
//
#define ICH9_LPC_SMI_F_CPU_HOT_UNPLUG  BIT2

//
// These carry the selector keys of the "etc/smi/requested-features" and
// "etc/smi/features-ok" fw_cfg files from NegotiateSmiFeatures().
//
STATIC FIRMWARE_CONFIG_ITEM  mRequestedFeaturesItem;
STATIC FIRMWARE_CONFIG_ITEM  mFeaturesOkItem;

//
// Holds the negotiated SMI features from NegotiateSmiFeatures().
//
STATIC UINT64  mSmiFeatures;

/**
  Negotiate SMI features with QEMU.

  @retval FALSE  It is not an error if SMI feature negotiation is not supported
                 by QEMU. It just means the data cannot be used.

  @retval TRUE   SMI feature negotiation is supported, and it has completed
                 successfully as well (failure to negotiate is a fatal error
                 and the function never returns in that case).
**/
BOOLEAN
NegotiateSmiFeatures (
  VOID
  )
{
  FIRMWARE_CONFIG_ITEM  SupportedFeaturesItem;
  UINTN                 SupportedFeaturesSize;
  UINTN                 RequestedFeaturesSize;
  UINTN                 FeaturesOkSize;
  UINT64                RequestedFeaturesMask;

  //
  // Look up the fw_cfg files used for feature negotiation. The selector keys
  // of "etc/smi/requested-features" and "etc/smi/features-ok" are saved
  // statically. If the files are missing, then QEMU doesn't support SMI
  // feature negotiation.
  //
  if (RETURN_ERROR (
        QemuFwCfgFindFile (
          "etc/smi/supported-features",
          &SupportedFeaturesItem,
          &SupportedFeaturesSize
          )
        ) ||
      RETURN_ERROR (
        QemuFwCfgFindFile (
          "etc/smi/requested-features",
          &mRequestedFeaturesItem,
          &RequestedFeaturesSize
          )
        ) ||
      RETURN_ERROR (
        QemuFwCfgFindFile (
          "etc/smi/features-ok",
          &mFeaturesOkItem,
          &FeaturesOkSize
          )
        ))
  {
    DEBUG ((
      DEBUG_INFO,
      "%a: SMI feature negotiation unavailable\n",
      __func__
      ));
    return FALSE;
  }

  //
  // If the files are present but their sizes disagree with us, that's a fatal
  // error (we can't trust the behavior of SMIs either way).
  //
  if ((SupportedFeaturesSize != sizeof mSmiFeatures) ||
      (RequestedFeaturesSize != sizeof mSmiFeatures) ||
      (FeaturesOkSize != sizeof (UINT8)))
  {
    DEBUG ((
      DEBUG_ERROR,
      "%a: size mismatch in feature negotiation\n",
      __func__
      ));
    goto FatalError;
  }

  //
  // Get the features supported by the host.
  //
  QemuFwCfgSelectItem (SupportedFeaturesItem);
  QemuFwCfgReadBytes (sizeof mSmiFeatures, &mSmiFeatures);

  //
  // We want broadcast SMI, SMI on CPU hotplug, SMI on CPU hot-unplug
  // and nothing else.
  //
  RequestedFeaturesMask = ICH9_LPC_SMI_F_BROADCAST;
  if (!MemEncryptSevIsEnabled ()) {
    //
    // For now, we only support hotplug with SEV disabled.
    //
    RequestedFeaturesMask |= ICH9_LPC_SMI_F_CPU_HOTPLUG;
    RequestedFeaturesMask |= ICH9_LPC_SMI_F_CPU_HOT_UNPLUG;
  }

  mSmiFeatures &= RequestedFeaturesMask;
  QemuFwCfgSelectItem (mRequestedFeaturesItem);
  QemuFwCfgWriteBytes (sizeof mSmiFeatures, &mSmiFeatures);

  //
  // Invoke feature validation in QEMU. If the selection is accepted, the
  // features will be locked down. If the selection is rejected, feature
  // negotiation remains open; however we don't know what to do in that case,
  // so that's a fatal error.
  //
  QemuFwCfgSelectItem (mFeaturesOkItem);
  if (QemuFwCfgRead8 () != 1) {
    DEBUG ((
      DEBUG_ERROR,
      "%a: negotiation failed for feature bitmap 0x%Lx\n",
      __func__,
      mSmiFeatures
      ));
    goto FatalError;
  }

  if ((mSmiFeatures & ICH9_LPC_SMI_F_BROADCAST) == 0) {
    //
    // If we can't get broadcast SMIs from QEMU, that's acceptable too,
    // although not optimal.
    //
    DEBUG ((DEBUG_INFO, "%a: SMI broadcast unavailable\n", __func__));
  } else {
    //
    // Configure the traditional AP sync / SMI delivery mode for
    // PiSmmCpuDxeSmm. Effectively, restore the UefiCpuPkg defaults, from which
    // the original QEMU behavior (i.e., unicast SMI) used to differ.
    //
    // MU_CHANGE Starts: Removed Dynamic PCD change to support Standalone MM
    if ((PcdGet64 (PcdCpuSmmApSyncTimeout) != 1000000) ||
        (PcdGet8 (PcdCpuSmmSyncMode) != 0x00))
    {
      DEBUG ((
        DEBUG_ERROR,
        "%a: PiSmmCpuDxeSmm PCD configuration failed\n",
        __func__
        ));
      goto FatalError;
    }

    // MU_CHANGE Ends

    DEBUG ((DEBUG_INFO, "%a: using SMI broadcast\n", __func__));
  }

  if ((mSmiFeatures & ICH9_LPC_SMI_F_CPU_HOTPLUG) == 0) {
    DEBUG ((DEBUG_INFO, "%a: CPU hotplug not negotiated\n", __func__));
  } else {
    DEBUG ((
      DEBUG_INFO,
      "%a: CPU hotplug with SMI negotiated\n",
      __func__
      ));
  }

  if ((mSmiFeatures & ICH9_LPC_SMI_F_CPU_HOT_UNPLUG) == 0) {
    DEBUG ((DEBUG_INFO, "%a: CPU hot-unplug not negotiated\n", __func__));
  } else {
    DEBUG ((
      DEBUG_INFO,
      "%a: CPU hot-unplug with SMI negotiated\n",
      __func__
      ));
  }

  //
  // Negotiation successful (although we may not have gotten the optimal
  // feature set).
  //
  return TRUE;

FatalError:
  ASSERT (FALSE);
  CpuDeadLoop ();
  //
  // Keep the compiler happy.
  //
  return FALSE;
}
