During normal boot, CpuS3DataDxe allocates - an empty CPU_REGISTER_TABLE entry in the "ACPI_CPU_DATA.PreSmmInitRegisterTable" array, and - an empty CPU_REGISTER_TABLE entry in the "ACPI_CPU_DATA.RegisterTable" array, for every CPU whose APIC ID CpuS3DataDxe can learn. Currently EFI_MP_SERVICES_PROTOCOL is used for both determining the number of CPUs -- the protocol reports the present-at-boot CPU count --, and for retrieving the APIC IDs of those CPUs. Consequently, if a CPU is hot-plugged at OS runtime, then S3 resume breaks. That's because PiSmmCpuDxeSmm will not find the hot-added CPU's APIC ID associated with any CPU_REGISTER_TABLE object, in the SMRAM copies of either of the "RegisterTable" and "PreSmmInitRegisterTable" arrays. The failure to match the hot-added CPU's APIC ID trips the ASSERT() in SetRegister() [UefiCpuPkg/PiSmmCpuDxeSmm/CpuS3.c]. If "PcdQ35SmramAtDefaultSmbase" is TRUE, then: - prepare CPU_REGISTER_TABLE objects for all possible CPUs, not just the present-at-boot CPUs (PlatformPei stored the possible CPU count to "PcdCpuMaxLogicalProcessorNumber"); - use QEMU_CPUHP_CMD_GET_ARCH_ID for filling in the "InitialApicId" fields of the CPU_REGISTER_TABLE objects. This provides full APIC ID coverage for PiSmmCpuDxeSmm during S3 resume, accommodating CPUs hot-added at OS runtime. This patch is best reviewed with $ git show -b Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Igor Mammedov <imammedo@redhat.com> Cc: Jiewen Yao <jiewen.yao@intel.com> Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Michael Kinney <michael.d.kinney@intel.com> Cc: Philippe Mathieu-Daudé <philmd@redhat.com> Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1512 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Message-Id: <20200226221156.29589-17-lersek@redhat.com> Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Tested-by: Boris Ostrovsky <boris.ostrovsky@oracle.com>
		
			
				
	
	
		
			351 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			351 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/** @file
 | 
						|
ACPI CPU Data initialization module
 | 
						|
 | 
						|
This module initializes the ACPI_CPU_DATA structure and registers the address
 | 
						|
of this structure in the PcdCpuS3DataAddress PCD.  This is a generic/simple
 | 
						|
version of this module.  It does not provide a machine check handler or CPU
 | 
						|
register initialization tables for ACPI S3 resume.  It also only supports the
 | 
						|
number of CPUs reported by the MP Services Protocol, so this module does not
 | 
						|
support hot plug CPUs.  This module can be copied into a CPU specific package
 | 
						|
and customized if these additional features are required.
 | 
						|
 | 
						|
Copyright (c) 2013 - 2017, Intel Corporation. All rights reserved.<BR>
 | 
						|
Copyright (c) 2015 - 2020, Red Hat, Inc.
 | 
						|
 | 
						|
SPDX-License-Identifier: BSD-2-Clause-Patent
 | 
						|
 | 
						|
**/
 | 
						|
 | 
						|
#include <PiDxe.h>
 | 
						|
 | 
						|
#include <AcpiCpuData.h>
 | 
						|
 | 
						|
#include <Library/BaseLib.h>
 | 
						|
#include <Library/BaseMemoryLib.h>
 | 
						|
#include <Library/DebugLib.h>
 | 
						|
#include <Library/IoLib.h>
 | 
						|
#include <Library/MemoryAllocationLib.h>
 | 
						|
#include <Library/MtrrLib.h>
 | 
						|
#include <Library/UefiBootServicesTableLib.h>
 | 
						|
 | 
						|
#include <Protocol/MpService.h>
 | 
						|
#include <Guid/EventGroup.h>
 | 
						|
 | 
						|
#include <IndustryStandard/Q35MchIch9.h>
 | 
						|
#include <IndustryStandard/QemuCpuHotplug.h>
 | 
						|
 | 
						|
//
 | 
						|
// Data structure used to allocate ACPI_CPU_DATA and its supporting structures
 | 
						|
//
 | 
						|
typedef struct {
 | 
						|
  ACPI_CPU_DATA             AcpiCpuData;
 | 
						|
  MTRR_SETTINGS             MtrrTable;
 | 
						|
  IA32_DESCRIPTOR           GdtrProfile;
 | 
						|
  IA32_DESCRIPTOR           IdtrProfile;
 | 
						|
} ACPI_CPU_DATA_EX;
 | 
						|
 | 
						|
/**
 | 
						|
  Allocate EfiACPIMemoryNVS memory.
 | 
						|
 | 
						|
  @param[in] Size   Size of memory to allocate.
 | 
						|
 | 
						|
  @return       Allocated address for output.
 | 
						|
 | 
						|
**/
 | 
						|
VOID *
 | 
						|
AllocateAcpiNvsMemory (
 | 
						|
  IN UINTN  Size
 | 
						|
  )
 | 
						|
{
 | 
						|
  EFI_PHYSICAL_ADDRESS  Address;
 | 
						|
  EFI_STATUS            Status;
 | 
						|
  VOID                  *Buffer;
 | 
						|
 | 
						|
  Status  = gBS->AllocatePages (
 | 
						|
                   AllocateAnyPages,
 | 
						|
                   EfiACPIMemoryNVS,
 | 
						|
                   EFI_SIZE_TO_PAGES (Size),
 | 
						|
                   &Address
 | 
						|
                   );
 | 
						|
  if (EFI_ERROR (Status)) {
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  Buffer = (VOID *)(UINTN)Address;
 | 
						|
  ZeroMem (Buffer, Size);
 | 
						|
 | 
						|
  return Buffer;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Allocate memory and clean it with zero.
 | 
						|
 | 
						|
  @param[in] Size   Size of memory to allocate.
 | 
						|
 | 
						|
  @return       Allocated address for output.
 | 
						|
 | 
						|
**/
 | 
						|
VOID *
 | 
						|
AllocateZeroPages (
 | 
						|
  IN UINTN  Size
 | 
						|
  )
 | 
						|
{
 | 
						|
  VOID                  *Buffer;
 | 
						|
 | 
						|
  Buffer = AllocatePages (EFI_SIZE_TO_PAGES (Size));
 | 
						|
  if (Buffer != NULL) {
 | 
						|
    ZeroMem (Buffer, Size);
 | 
						|
  }
 | 
						|
 | 
						|
  return Buffer;
 | 
						|
}
 | 
						|
/**
 | 
						|
  Callback function executed when the EndOfDxe event group is signaled.
 | 
						|
 | 
						|
  We delay allocating StartupVector and saving the MTRR settings until BDS signals EndOfDxe.
 | 
						|
 | 
						|
  @param[in]  Event    Event whose notification function is being invoked.
 | 
						|
  @param[out] Context  Pointer to the MTRR_SETTINGS buffer to fill in.
 | 
						|
**/
 | 
						|
VOID
 | 
						|
EFIAPI
 | 
						|
CpuS3DataOnEndOfDxe (
 | 
						|
  IN  EFI_EVENT  Event,
 | 
						|
  OUT VOID       *Context
 | 
						|
  )
 | 
						|
{
 | 
						|
  EFI_STATUS         Status;
 | 
						|
  ACPI_CPU_DATA_EX   *AcpiCpuDataEx;
 | 
						|
 | 
						|
  AcpiCpuDataEx = (ACPI_CPU_DATA_EX *) Context;
 | 
						|
  //
 | 
						|
  // Allocate a 4KB reserved page below 1MB
 | 
						|
  //
 | 
						|
  AcpiCpuDataEx->AcpiCpuData.StartupVector = BASE_1MB - 1;
 | 
						|
  Status = gBS->AllocatePages (
 | 
						|
                  AllocateMaxAddress,
 | 
						|
                  EfiReservedMemoryType,
 | 
						|
                  1,
 | 
						|
                  &AcpiCpuDataEx->AcpiCpuData.StartupVector
 | 
						|
                  );
 | 
						|
  ASSERT_EFI_ERROR (Status);
 | 
						|
 | 
						|
  DEBUG ((DEBUG_VERBOSE, "%a\n", __FUNCTION__));
 | 
						|
  MtrrGetAllMtrrs (&AcpiCpuDataEx->MtrrTable);
 | 
						|
 | 
						|
  //
 | 
						|
  // Close event, so it will not be invoked again.
 | 
						|
  //
 | 
						|
  gBS->CloseEvent (Event);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
   The entry function of the CpuS3Data driver.
 | 
						|
 | 
						|
   Allocate and initialize all fields of the ACPI_CPU_DATA structure except the
 | 
						|
   MTRR settings.  Register an event notification on gEfiEndOfDxeEventGroupGuid
 | 
						|
   to capture the ACPI_CPU_DATA MTRR settings.  The PcdCpuS3DataAddress is set
 | 
						|
   to the address that ACPI_CPU_DATA is allocated at.
 | 
						|
 | 
						|
   @param[in] ImageHandle  The firmware allocated handle for the EFI image.
 | 
						|
   @param[in] SystemTable  A pointer to the EFI System Table.
 | 
						|
 | 
						|
   @retval EFI_SUCCESS     The entry point is executed successfully.
 | 
						|
   @retval EFI_UNSUPPORTED Do not support ACPI S3.
 | 
						|
   @retval other           Some error occurs when executing this entry point.
 | 
						|
 | 
						|
**/
 | 
						|
EFI_STATUS
 | 
						|
EFIAPI
 | 
						|
CpuS3DataInitialize (
 | 
						|
  IN EFI_HANDLE        ImageHandle,
 | 
						|
  IN EFI_SYSTEM_TABLE  *SystemTable
 | 
						|
  )
 | 
						|
{
 | 
						|
  EFI_STATUS                 Status;
 | 
						|
  ACPI_CPU_DATA_EX           *AcpiCpuDataEx;
 | 
						|
  ACPI_CPU_DATA              *AcpiCpuData;
 | 
						|
  EFI_MP_SERVICES_PROTOCOL   *MpServices;
 | 
						|
  UINTN                      NumberOfCpus;
 | 
						|
  VOID                       *Stack;
 | 
						|
  UINTN                      TableSize;
 | 
						|
  CPU_REGISTER_TABLE         *RegisterTable;
 | 
						|
  UINTN                      Index;
 | 
						|
  EFI_PROCESSOR_INFORMATION  ProcessorInfoBuffer;
 | 
						|
  UINTN                      GdtSize;
 | 
						|
  UINTN                      IdtSize;
 | 
						|
  VOID                       *Gdt;
 | 
						|
  VOID                       *Idt;
 | 
						|
  EFI_EVENT                  Event;
 | 
						|
  ACPI_CPU_DATA              *OldAcpiCpuData;
 | 
						|
  BOOLEAN                    FetchPossibleApicIds;
 | 
						|
 | 
						|
  if (!PcdGetBool (PcdAcpiS3Enable)) {
 | 
						|
    return EFI_UNSUPPORTED;
 | 
						|
  }
 | 
						|
 | 
						|
  //
 | 
						|
  // Set PcdCpuS3DataAddress to the base address of the ACPI_CPU_DATA structure
 | 
						|
  //
 | 
						|
  OldAcpiCpuData = (ACPI_CPU_DATA *) (UINTN) PcdGet64 (PcdCpuS3DataAddress);
 | 
						|
 | 
						|
  AcpiCpuDataEx = AllocateZeroPages (sizeof (ACPI_CPU_DATA_EX));
 | 
						|
  ASSERT (AcpiCpuDataEx != NULL);
 | 
						|
  AcpiCpuData = &AcpiCpuDataEx->AcpiCpuData;
 | 
						|
 | 
						|
  //
 | 
						|
  // The "SMRAM at default SMBASE" feature guarantees that
 | 
						|
  // QEMU_CPUHP_CMD_GET_ARCH_ID too is available.
 | 
						|
  //
 | 
						|
  FetchPossibleApicIds = PcdGetBool (PcdQ35SmramAtDefaultSmbase);
 | 
						|
 | 
						|
  if (FetchPossibleApicIds) {
 | 
						|
    NumberOfCpus = PcdGet32 (PcdCpuMaxLogicalProcessorNumber);
 | 
						|
  } else {
 | 
						|
    UINTN NumberOfEnabledProcessors;
 | 
						|
 | 
						|
    //
 | 
						|
    // Get MP Services Protocol
 | 
						|
    //
 | 
						|
    Status = gBS->LocateProtocol (
 | 
						|
                    &gEfiMpServiceProtocolGuid,
 | 
						|
                    NULL,
 | 
						|
                    (VOID **)&MpServices
 | 
						|
                    );
 | 
						|
    ASSERT_EFI_ERROR (Status);
 | 
						|
 | 
						|
    //
 | 
						|
    // Get the number of CPUs
 | 
						|
    //
 | 
						|
    Status = MpServices->GetNumberOfProcessors (
 | 
						|
                           MpServices,
 | 
						|
                           &NumberOfCpus,
 | 
						|
                           &NumberOfEnabledProcessors
 | 
						|
                           );
 | 
						|
    ASSERT_EFI_ERROR (Status);
 | 
						|
  }
 | 
						|
  AcpiCpuData->NumberOfCpus = (UINT32)NumberOfCpus;
 | 
						|
 | 
						|
  //
 | 
						|
  // Initialize ACPI_CPU_DATA fields
 | 
						|
  //
 | 
						|
  AcpiCpuData->StackSize                 = PcdGet32 (PcdCpuApStackSize);
 | 
						|
  AcpiCpuData->ApMachineCheckHandlerBase = 0;
 | 
						|
  AcpiCpuData->ApMachineCheckHandlerSize = 0;
 | 
						|
  AcpiCpuData->GdtrProfile  = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->GdtrProfile;
 | 
						|
  AcpiCpuData->IdtrProfile  = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->IdtrProfile;
 | 
						|
  AcpiCpuData->MtrrTable    = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->MtrrTable;
 | 
						|
 | 
						|
  //
 | 
						|
  // Allocate stack space for all CPUs.
 | 
						|
  // Use ACPI NVS memory type because this data will be directly used by APs
 | 
						|
  // in S3 resume phase in long mode. Also during S3 resume, the stack buffer
 | 
						|
  // will only be used as scratch space. i.e. we won't read anything from it
 | 
						|
  // before we write to it, in PiSmmCpuDxeSmm.
 | 
						|
  //
 | 
						|
  Stack = AllocateAcpiNvsMemory (NumberOfCpus * AcpiCpuData->StackSize);
 | 
						|
  ASSERT (Stack != NULL);
 | 
						|
  AcpiCpuData->StackAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Stack;
 | 
						|
 | 
						|
  //
 | 
						|
  // Get the boot processor's GDT and IDT
 | 
						|
  //
 | 
						|
  AsmReadGdtr (&AcpiCpuDataEx->GdtrProfile);
 | 
						|
  AsmReadIdtr (&AcpiCpuDataEx->IdtrProfile);
 | 
						|
 | 
						|
  //
 | 
						|
  // Allocate GDT and IDT and copy current GDT and IDT contents
 | 
						|
  //
 | 
						|
  GdtSize = AcpiCpuDataEx->GdtrProfile.Limit + 1;
 | 
						|
  IdtSize = AcpiCpuDataEx->IdtrProfile.Limit + 1;
 | 
						|
  Gdt = AllocateZeroPages (GdtSize + IdtSize);
 | 
						|
  ASSERT (Gdt != NULL);
 | 
						|
  Idt = (VOID *)((UINTN)Gdt + GdtSize);
 | 
						|
  CopyMem (Gdt, (VOID *)AcpiCpuDataEx->GdtrProfile.Base, GdtSize);
 | 
						|
  CopyMem (Idt, (VOID *)AcpiCpuDataEx->IdtrProfile.Base, IdtSize);
 | 
						|
  AcpiCpuDataEx->GdtrProfile.Base = (UINTN)Gdt;
 | 
						|
  AcpiCpuDataEx->IdtrProfile.Base = (UINTN)Idt;
 | 
						|
 | 
						|
  if (OldAcpiCpuData != NULL) {
 | 
						|
    AcpiCpuData->RegisterTable           = OldAcpiCpuData->RegisterTable;
 | 
						|
    AcpiCpuData->PreSmmInitRegisterTable = OldAcpiCpuData->PreSmmInitRegisterTable;
 | 
						|
    AcpiCpuData->ApLocation              = OldAcpiCpuData->ApLocation;
 | 
						|
    CopyMem (&AcpiCpuData->CpuStatus, &OldAcpiCpuData->CpuStatus, sizeof (CPU_STATUS_INFORMATION));
 | 
						|
  } else {
 | 
						|
    //
 | 
						|
    // Allocate buffer for empty RegisterTable and PreSmmInitRegisterTable for all CPUs
 | 
						|
    //
 | 
						|
    TableSize = 2 * NumberOfCpus * sizeof (CPU_REGISTER_TABLE);
 | 
						|
    RegisterTable = (CPU_REGISTER_TABLE *)AllocateZeroPages (TableSize);
 | 
						|
    ASSERT (RegisterTable != NULL);
 | 
						|
 | 
						|
    if (FetchPossibleApicIds) {
 | 
						|
      //
 | 
						|
      // Write a valid selector so that other hotplug registers can be
 | 
						|
      // accessed.
 | 
						|
      //
 | 
						|
      IoWrite32 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL, 0);
 | 
						|
      //
 | 
						|
      // We'll be fetching the APIC IDs.
 | 
						|
      //
 | 
						|
      IoWrite8 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
 | 
						|
        QEMU_CPUHP_CMD_GET_ARCH_ID);
 | 
						|
    }
 | 
						|
    for (Index = 0; Index < NumberOfCpus; Index++) {
 | 
						|
      UINT32 InitialApicId;
 | 
						|
 | 
						|
      if (FetchPossibleApicIds) {
 | 
						|
        IoWrite32 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
 | 
						|
          (UINT32)Index);
 | 
						|
        InitialApicId = IoRead32 (
 | 
						|
                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA);
 | 
						|
      } else {
 | 
						|
        Status = MpServices->GetProcessorInfo (
 | 
						|
                             MpServices,
 | 
						|
                             Index,
 | 
						|
                             &ProcessorInfoBuffer
 | 
						|
                             );
 | 
						|
        ASSERT_EFI_ERROR (Status);
 | 
						|
        InitialApicId = (UINT32)ProcessorInfoBuffer.ProcessorId;
 | 
						|
      }
 | 
						|
 | 
						|
      DEBUG ((DEBUG_VERBOSE, "%a: Index=%05Lu ApicId=0x%08x\n", __FUNCTION__,
 | 
						|
        (UINT64)Index, InitialApicId));
 | 
						|
 | 
						|
      RegisterTable[Index].InitialApicId      = InitialApicId;
 | 
						|
      RegisterTable[Index].TableLength        = 0;
 | 
						|
      RegisterTable[Index].AllocatedSize      = 0;
 | 
						|
      RegisterTable[Index].RegisterTableEntry = 0;
 | 
						|
 | 
						|
      RegisterTable[NumberOfCpus + Index].InitialApicId      = InitialApicId;
 | 
						|
      RegisterTable[NumberOfCpus + Index].TableLength        = 0;
 | 
						|
      RegisterTable[NumberOfCpus + Index].AllocatedSize      = 0;
 | 
						|
      RegisterTable[NumberOfCpus + Index].RegisterTableEntry = 0;
 | 
						|
    }
 | 
						|
    AcpiCpuData->RegisterTable           = (EFI_PHYSICAL_ADDRESS)(UINTN)RegisterTable;
 | 
						|
    AcpiCpuData->PreSmmInitRegisterTable = (EFI_PHYSICAL_ADDRESS)(UINTN)(RegisterTable + NumberOfCpus);
 | 
						|
  }
 | 
						|
 | 
						|
  //
 | 
						|
  // Set PcdCpuS3DataAddress to the base address of the ACPI_CPU_DATA structure
 | 
						|
  //
 | 
						|
  Status = PcdSet64S (PcdCpuS3DataAddress, (UINT64)(UINTN)AcpiCpuData);
 | 
						|
  ASSERT_EFI_ERROR (Status);
 | 
						|
 | 
						|
  //
 | 
						|
  // Register EFI_END_OF_DXE_EVENT_GROUP_GUID event.
 | 
						|
  // The notification function allocates StartupVector and saves MTRRs for ACPI_CPU_DATA
 | 
						|
  //
 | 
						|
  Status = gBS->CreateEventEx (
 | 
						|
                  EVT_NOTIFY_SIGNAL,
 | 
						|
                  TPL_CALLBACK,
 | 
						|
                  CpuS3DataOnEndOfDxe,
 | 
						|
                  AcpiCpuData,
 | 
						|
                  &gEfiEndOfDxeEventGroupGuid,
 | 
						|
                  &Event
 | 
						|
                  );
 | 
						|
  ASSERT_EFI_ERROR (Status);
 | 
						|
 | 
						|
  return EFI_SUCCESS;
 | 
						|
}
 |