/** @file

  @copyright
  Copyright 1999 - 2021 Intel Corporation. <BR>

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

#include <Guid/SocketPciResourceData.h>
#include <Guid/SocketIioVariable.h>
#include <Protocol/IioUds.h>

#include "PciHostBridge.h"
#include "PciRootBridge.h"
#include "PciRebalance.h"


extern EFI_IIO_UDS_PROTOCOL *mIioUds;


/**
  Adjust resource assignments among sockets to fit the high
  MMIO resources (64-bit addresses, typically above 4GB) from
  the PCI(e) devices in the system

  @param[in,out] SocketResources - CPU_RESOURCE structure pointer that stores all resources need per socket
  @param[in]     ResourceType    - Type of resource that requires alignment
  @param[in]     ValidSockets    - Number of Valid Sockets, need it to calculate how resources need to be splitted

  @retval EFI_SUCCESS            -  Succeed.
  @retval EFI_OUT_OF_RESOURCES   -  Not enough resources to be adjusted within the socket.
 */
EFI_STATUS
AdjustSocketMmioH (
  IN OUT CPU_RESOURCE *SocketResources,
  IN     UINT8         ResourceType,
  IN     UINT8         ValidSockets
  )
{
  CONST UINT8 LastSocket = ValidSockets - 1;
  UINT8       Socket;
  UINT8       Stack;
  UINT8       LastStack;
  UINT64      Take;
  UINT32      UboxMmioSize;
  UINT64      UnAllocatedMmioh;
  UINT64      MaxMmioh;
  UINT64      ResourceSize;
  UINT64      TotalResourceSize;
  UINT64      TempMmioBase;
  UINT64      TempMmioLimit;

  Take = 0;
  UboxMmioSize = mIioUds->IioUdsPtr->PlatformData.UboxMmioSize;
  UnAllocatedMmioh = 0;
  //
  // Get first and last IO base address
  //
  TempMmioBase  = mIioUds->IioUdsPtr->PlatformData.PlatGlobalMmio64Base;
  TempMmioLimit = mIioUds->IioUdsPtr->PlatformData.PlatGlobalMmio64Limit;

  // Find all the extra space left
  for (Socket = 0; Socket < ValidSockets; Socket ++) {
    if ((SocketResources[Socket].MmiohResourceNeeds == 0) && (SocketResources[Socket].MmiohResourcesLeft != 0)) {

      Take += SocketResources[Socket].MmiohResourcesLeft + 1;
      SocketResources[Socket].MmiohResourcesLeft = 0;
    }
  }

  MaxMmioh = (UINT64) mIioUds->IioUdsPtr->PlatformData.MmiohGranularity.lo;
  MaxMmioh |= ((UINT64) mIioUds->IioUdsPtr->PlatformData.MmiohGranularity.hi) << 32;
  //
  // Maximum chunk accessible in the system based on the given granularity
  //
  if (UboxMmioSize == 0) {
    MaxMmioh = MaxMmioh * 32; //for 14nm
  } else {
    MaxMmioh = ((UINT64) 1 << mIioUds->IioUdsPtr->PlatformData.MaxAddressBits);
  }

  //
  // Find out how much MMIOH has not been allocated
  //
  if (MaxMmioh > (TempMmioLimit - TempMmioBase)) {
    UnAllocatedMmioh = MaxMmioh - (TempMmioLimit - TempMmioBase) - 1;
  } else {
    //
    // Extra MMIOH is not enough to close the gap for a successful adjustment.
    // Use all extra MMIOH in case if only a small amount is needed for adjustment or
    // the granularity was reduced due to MMIOH base.
    // (14nm only) or remove if map is rejected for this case to start over with correct values.
    // (10nm only) does not have this problem and doesn't need the code below because the limit is removed and we can use max address lines.
    //
    Take += MaxMmioh;
  }

  // Give space to sockets that needs more space favoring first come first served
  for (Socket = 0; Socket < ValidSockets; Socket ++) {
    if ((SocketResources[Socket].MmiohResourceNeeds != 0) && (Take >= SocketResources[Socket].MmiohResourceNeeds)) {
      //
      // The socket requesting additional resources can be granted by using the already
      // allocated range given to the whole system originally.
      //
      Take -= SocketResources[Socket].MmiohResourceNeeds;
      DEBUG ((DEBUG_ERROR, "SocketResources[%x].MmiohResourceNeeds = %llX\n",Socket, SocketResources[Socket].MmiohResourceNeeds));
      SocketResources[Socket].MmiohResourceNeeds = 0;
    } else if ((SocketResources[Socket].MmiohResourceNeeds != 0) &&
               (Take < SocketResources[Socket].MmiohResourceNeeds) &&
               ((UnAllocatedMmioh + Take) >= SocketResources[Socket].MmiohResourceNeeds)) {
      //
      // Apply unallocated Mmioh to the socket that requires more to satisfy its request
      // that's outside the allocated range given to the whole system originally.
      //
      SocketResources[Socket].MmiohResourceNeeds -= Take;
      UnAllocatedMmioh -= SocketResources[Socket].MmiohResourceNeeds;
      DEBUG ((DEBUG_INFO, "SocketResources[%x].MmiohResourceNeeds = %llX\n", Socket, SocketResources[Socket].MmiohResourceNeeds));
      DEBUG ((DEBUG_INFO, "Unallocated MMIOH left = %llX\n", UnAllocatedMmioh));
      SocketResources[Socket].MmiohResourceNeeds = 0;
    }
  }
  //
  // Give away leftover resources
  //
  LastStack = LastStackOfSocket (LastSocket);
  if (Take != 0) {
    if (SocketResources[LastSocket].StackRes[LastStack].MmiohLength != 0) {
      SocketResources[LastSocket].StackRes[LastStack].MmiohLength += Take;
    } else{
      SocketResources[LastSocket].StackRes[LastStack].MmiohLength += (Take - 1);
    }
  }
  //
  // Verify all resource requested can fit into the systems address range.
  //
  TotalResourceSize = 0;
  for (Socket = 0; Socket < ValidSockets; Socket++) {
    LastStackWithResources (&SocketResources[Socket], Socket, ResourceType, &LastStack, &ResourceSize);
    TotalResourceSize += ResourceSize;
  }
  DEBUG ((DEBUG_INFO, "MaxMmioh                 = %016llXh\n", MaxMmioh));
  DEBUG ((DEBUG_INFO, "Total Request MMIOH Range= %016llXh\n", TotalResourceSize));
  DEBUG ((DEBUG_INFO, "Total System MMIOH Range = %016llXh\n", (MaxMmioh - TempMmioBase)));
  if (TotalResourceSize > MaxMmioh) {
    //
    // Not enough system resources to support the request.
    // Remove all request to update NVRAM variable for this resource type.
    //
    for (Socket = 0; Socket < ValidSockets; Socket ++) {
      for (Stack = 0; Stack < MAX_IIO_STACK; Stack ++) {
        if (!(mIioUds->IioUdsPtr->PlatformData.CpuQpiInfo[Socket].stackPresentBitmap & (1 << Stack))) {
          continue;
        }
        SocketResources[Socket].StackRes[Stack].MmiohUpdate = 0;
      }
    }
    DEBUG ((DEBUG_ERROR, "[PCI] ERROR: Out of adjustable MMIOH resources. Can't adjust across sockets\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  DEBUG ((DEBUG_ERROR, "Assigning new socket MMIOH range...\n"));
  for (Socket = 0, TempMmioLimit = TempMmioBase - 1; Socket < ValidSockets; Socket++) {

    SocketResources[Socket].MmiohBase = TempMmioLimit + 1;
    //
    // Update the stacks base and limit values.
    //
    for (Stack = 0; Stack < MAX_IIO_STACK; Stack++) {

      if (!(mIioUds->IioUdsPtr->PlatformData.CpuQpiInfo[Socket].stackPresentBitmap & (1 << Stack))) {

       SocketResources[Socket].StackRes[Stack].MmiohBase = 0;
       SocketResources[Socket].StackRes[Stack].MmiohLimit = 0;

      } else {

        SocketResources[Socket].StackRes[Stack].MmiohBase = TempMmioLimit + 1;
        if (SocketResources[Socket].StackRes[Stack].MmiohLength != 0) {
          //
          // MmiohLength is actually length-1, so we should move TempMmioLimit by MmiohLength+1,
          // but only when it is >0, i.e. only for stack that has resources.
          //
          TempMmioLimit += SocketResources[Socket].StackRes[Stack].MmiohLength + 1;
          SocketResources[Socket].StackRes[Stack].MmiohLimit = TempMmioLimit;

        } else {

          SocketResources[Socket].StackRes[Stack].MmiohLimit = SocketResources[Socket].StackRes[Stack].MmiohBase;
        }
        SocketResources[Socket].StackRes[Stack].MmiohUpdate = 1;
      }
      DEBUG ((DEBUG_ERROR, "SocketResources[%x].StackRes[%x].MmiohBase  = %llX   newLength = %llX\n", Socket, Stack,
              SocketResources[Socket].StackRes[Stack].MmiohBase, SocketResources[Socket].StackRes[Stack].MmiohLength));
      DEBUG ((DEBUG_ERROR, "SocketResources[%x].StackRes[%x].MmiohLimit = %llX\n", Socket, Stack,
              SocketResources[Socket].StackRes[Stack].MmiohLimit));
      DEBUG ((DEBUG_ERROR, "SocketResources[%x].StackRes[%x].MmiohUpdate= %d\n", Socket, Stack,
              SocketResources[Socket].StackRes[Stack].MmiohUpdate));
    } // for (Stack...)

    SocketResources[Socket].MmiohLimit = TempMmioLimit;
  } // for (Socket...)
  return EFI_SUCCESS;
}

