Add QemuCpuhpWriteCpuStatus() which will be used to update the QEMU CPU status register. On error, it hangs in a similar fashion as other helper functions. Cc: Laszlo Ersek <lersek@redhat.com> Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Ard Biesheuvel <ard.biesheuvel@arm.com> Cc: Igor Mammedov <imammedo@redhat.com> Cc: Boris Ostrovsky <boris.ostrovsky@oracle.com> Cc: Aaron Young <aaron.young@oracle.com> Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=3132 Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com> Message-Id: <20210312062656.2477515-4-ankur.a.arora@oracle.com>
		
			
				
	
	
		
			359 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /** @file
 | |
|   Simple wrapper functions and utility functions that access QEMU's modern CPU
 | |
|   hotplug register block.
 | |
| 
 | |
|   These functions manipulate some of the registers described in
 | |
|   "docs/specs/acpi_cpu_hotplug.txt" in the QEMU source. IO Ports are accessed
 | |
|   via EFI_MM_CPU_IO_PROTOCOL. If a protocol call fails, these functions don't
 | |
|   return.
 | |
| 
 | |
|   Copyright (c) 2020, Red Hat, Inc.
 | |
| 
 | |
|   SPDX-License-Identifier: BSD-2-Clause-Patent
 | |
| **/
 | |
| 
 | |
| #include <IndustryStandard/Q35MchIch9.h>     // ICH9_CPU_HOTPLUG_BASE
 | |
| #include <IndustryStandard/QemuCpuHotplug.h> // QEMU_CPUHP_R_CMD_DATA2
 | |
| #include <Library/BaseLib.h>                 // CpuDeadLoop()
 | |
| #include <Library/DebugLib.h>                // DEBUG()
 | |
| 
 | |
| #include "QemuCpuhp.h"
 | |
| 
 | |
| UINT32
 | |
| QemuCpuhpReadCommandData2 (
 | |
|   IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
 | |
|   )
 | |
| {
 | |
|   UINT32     CommandData2;
 | |
|   EFI_STATUS Status;
 | |
| 
 | |
|   CommandData2 = 0;
 | |
|   Status = MmCpuIo->Io.Read (
 | |
|                          MmCpuIo,
 | |
|                          MM_IO_UINT32,
 | |
|                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CMD_DATA2,
 | |
|                          1,
 | |
|                          &CommandData2
 | |
|                          );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
 | |
|     ASSERT (FALSE);
 | |
|     CpuDeadLoop ();
 | |
|   }
 | |
|   return CommandData2;
 | |
| }
 | |
| 
 | |
| UINT8
 | |
| QemuCpuhpReadCpuStatus (
 | |
|   IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
 | |
|   )
 | |
| {
 | |
|   UINT8      CpuStatus;
 | |
|   EFI_STATUS Status;
 | |
| 
 | |
|   CpuStatus = 0;
 | |
|   Status = MmCpuIo->Io.Read (
 | |
|                          MmCpuIo,
 | |
|                          MM_IO_UINT8,
 | |
|                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
 | |
|                          1,
 | |
|                          &CpuStatus
 | |
|                          );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
 | |
|     ASSERT (FALSE);
 | |
|     CpuDeadLoop ();
 | |
|   }
 | |
|   return CpuStatus;
 | |
| }
 | |
| 
 | |
| UINT32
 | |
| QemuCpuhpReadCommandData (
 | |
|   IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
 | |
|   )
 | |
| {
 | |
|   UINT32     CommandData;
 | |
|   EFI_STATUS Status;
 | |
| 
 | |
|   CommandData = 0;
 | |
|   Status = MmCpuIo->Io.Read (
 | |
|                          MmCpuIo,
 | |
|                          MM_IO_UINT32,
 | |
|                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA,
 | |
|                          1,
 | |
|                          &CommandData
 | |
|                          );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
 | |
|     ASSERT (FALSE);
 | |
|     CpuDeadLoop ();
 | |
|   }
 | |
|   return CommandData;
 | |
| }
 | |
| 
 | |
| VOID
 | |
| QemuCpuhpWriteCpuSelector (
 | |
|   IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
 | |
|   IN UINT32                       Selector
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS Status;
 | |
| 
 | |
|   Status = MmCpuIo->Io.Write (
 | |
|                          MmCpuIo,
 | |
|                          MM_IO_UINT32,
 | |
|                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
 | |
|                          1,
 | |
|                          &Selector
 | |
|                          );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
 | |
|     ASSERT (FALSE);
 | |
|     CpuDeadLoop ();
 | |
|   }
 | |
| }
 | |
| 
 | |
| VOID
 | |
| QemuCpuhpWriteCpuStatus (
 | |
|   IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
 | |
|   IN UINT8                        CpuStatus
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS Status;
 | |
| 
 | |
|   Status = MmCpuIo->Io.Write (
 | |
|                          MmCpuIo,
 | |
|                          MM_IO_UINT8,
 | |
|                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
 | |
|                          1,
 | |
|                          &CpuStatus
 | |
|                          );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
 | |
|     ASSERT (FALSE);
 | |
|     CpuDeadLoop ();
 | |
|   }
 | |
| }
 | |
| 
 | |
| VOID
 | |
| QemuCpuhpWriteCommand (
 | |
|   IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
 | |
|   IN UINT8                        Command
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS Status;
 | |
| 
 | |
|   Status = MmCpuIo->Io.Write (
 | |
|                          MmCpuIo,
 | |
|                          MM_IO_UINT8,
 | |
|                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
 | |
|                          1,
 | |
|                          &Command
 | |
|                          );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
 | |
|     ASSERT (FALSE);
 | |
|     CpuDeadLoop ();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Collect the APIC IDs of
 | |
|   - the CPUs that have been hot-plugged,
 | |
|   - the CPUs that are about to be hot-unplugged.
 | |
| 
 | |
|   This function only scans for events -- it does not modify them -- in the
 | |
|   hotplug registers.
 | |
| 
 | |
|   On error, the contents of the output parameters are undefined.
 | |
| 
 | |
|   @param[in] MmCpuIo             The EFI_MM_CPU_IO_PROTOCOL instance for
 | |
|                                  accessing IO Ports.
 | |
| 
 | |
|   @param[in] PossibleCpuCount    The number of possible CPUs in the system. Must
 | |
|                                  be positive.
 | |
| 
 | |
|   @param[in] ApicIdCount         The number of elements each one of the
 | |
|                                  PluggedApicIds and ToUnplugApicIds arrays can
 | |
|                                  accommodate. Must be positive.
 | |
| 
 | |
|   @param[out] PluggedApicIds     The APIC IDs of the CPUs that have been
 | |
|                                  hot-plugged.
 | |
| 
 | |
|   @param[out] PluggedCount       The number of filled-in APIC IDs in
 | |
|                                  PluggedApicIds.
 | |
| 
 | |
|   @param[out] ToUnplugApicIds    The APIC IDs of the CPUs that are about to be
 | |
|                                  hot-unplugged.
 | |
| 
 | |
|   @param[out] ToUnplugSelectors  The QEMU Selectors of the CPUs that are about
 | |
|                                  to be hot-unplugged.
 | |
| 
 | |
|   @param[out] ToUnplugCount      The number of filled-in APIC IDs in
 | |
|                                  ToUnplugApicIds.
 | |
| 
 | |
|   @retval EFI_INVALID_PARAMETER  PossibleCpuCount is zero, or ApicIdCount is
 | |
|                                  zero.
 | |
| 
 | |
|   @retval EFI_PROTOCOL_ERROR     Invalid bitmap detected in the
 | |
|                                  QEMU_CPUHP_R_CPU_STAT register.
 | |
| 
 | |
|   @retval EFI_BUFFER_TOO_SMALL   There was an attempt to place more than
 | |
|                                  ApicIdCount APIC IDs into one of the
 | |
|                                  PluggedApicIds and ToUnplugApicIds arrays.
 | |
| 
 | |
|   @retval EFI_SUCCESS            Output parameters have been set successfully.
 | |
| **/
 | |
| EFI_STATUS
 | |
| QemuCpuhpCollectApicIds (
 | |
|   IN  CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
 | |
|   IN  UINT32                       PossibleCpuCount,
 | |
|   IN  UINT32                       ApicIdCount,
 | |
|   OUT APIC_ID                      *PluggedApicIds,
 | |
|   OUT UINT32                       *PluggedCount,
 | |
|   OUT APIC_ID                      *ToUnplugApicIds,
 | |
|   OUT UINT32                       *ToUnplugSelectors,
 | |
|   OUT UINT32                       *ToUnplugCount
 | |
|   )
 | |
| {
 | |
|   UINT32 CurrentSelector;
 | |
| 
 | |
|   if (PossibleCpuCount == 0 || ApicIdCount == 0) {
 | |
|     return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   *PluggedCount = 0;
 | |
|   *ToUnplugCount = 0;
 | |
| 
 | |
|   CurrentSelector = 0;
 | |
|   do {
 | |
|     UINT32  PendingSelector;
 | |
|     UINT8   CpuStatus;
 | |
|     APIC_ID *ExtendIds;
 | |
|     UINT32  *ExtendSels;
 | |
|     UINT32  *ExtendCount;
 | |
|     APIC_ID NewApicId;
 | |
| 
 | |
|     //
 | |
|     // Write CurrentSelector (which is valid) to the CPU selector register.
 | |
|     // Consequences:
 | |
|     //
 | |
|     // - Other register accesses will be permitted.
 | |
|     //
 | |
|     // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
 | |
|     //   with pending events at CurrentSelector (inclusive).
 | |
|     //
 | |
|     QemuCpuhpWriteCpuSelector (MmCpuIo, CurrentSelector);
 | |
|     //
 | |
|     // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
 | |
|     // (independently of each other):
 | |
|     //
 | |
|     // - If there is a CPU with pending events, starting at CurrentSelector
 | |
|     //   (inclusive), the CPU selector will be updated to that CPU. Note that
 | |
|     //   the scanning in QEMU may wrap around, because we must never clear the
 | |
|     //   event bits.
 | |
|     //
 | |
|     // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
 | |
|     //   CPU selector value.
 | |
|     //
 | |
|     QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
 | |
|     PendingSelector = QemuCpuhpReadCommandData (MmCpuIo);
 | |
|     if (PendingSelector < CurrentSelector) {
 | |
|       DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u PendingSelector=%u: "
 | |
|         "wrap-around\n", __FUNCTION__, CurrentSelector, PendingSelector));
 | |
|       break;
 | |
|     }
 | |
|     CurrentSelector = PendingSelector;
 | |
| 
 | |
|     //
 | |
|     // Check the known status / event bits for the currently selected CPU.
 | |
|     //
 | |
|     CpuStatus = QemuCpuhpReadCpuStatus (MmCpuIo);
 | |
|     if ((CpuStatus & QEMU_CPUHP_STAT_INSERT) != 0) {
 | |
|       //
 | |
|       // The "insert" event guarantees the "enabled" status; plus it excludes
 | |
|       // the "fw_remove" event.
 | |
|       //
 | |
|       if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0 ||
 | |
|           (CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0) {
 | |
|         DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "
 | |
|           "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,
 | |
|           CpuStatus));
 | |
|         return EFI_PROTOCOL_ERROR;
 | |
|       }
 | |
| 
 | |
|       DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: insert\n", __FUNCTION__,
 | |
|         CurrentSelector));
 | |
| 
 | |
|       ExtendIds   = PluggedApicIds;
 | |
|       ExtendSels  = NULL;
 | |
|       ExtendCount = PluggedCount;
 | |
|     } else if ((CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0) {
 | |
|       //
 | |
|       // "fw_remove" event guarantees "enabled".
 | |
|       //
 | |
|       if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0) {
 | |
|         DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "
 | |
|           "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,
 | |
|           CpuStatus));
 | |
|         return EFI_PROTOCOL_ERROR;
 | |
|       }
 | |
| 
 | |
|       DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: fw_remove\n",
 | |
|         __FUNCTION__, CurrentSelector));
 | |
| 
 | |
|       ExtendIds   = ToUnplugApicIds;
 | |
|       ExtendSels  = ToUnplugSelectors;
 | |
|       ExtendCount = ToUnplugCount;
 | |
|     } else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
 | |
|       //
 | |
|       // Let the OSPM deal with the "remove" event.
 | |
|       //
 | |
|       DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: remove (ignored)\n",
 | |
|         __FUNCTION__, CurrentSelector));
 | |
| 
 | |
|       ExtendIds   = NULL;
 | |
|       ExtendSels  = NULL;
 | |
|       ExtendCount = NULL;
 | |
|     } else {
 | |
|       DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: no event\n",
 | |
|         __FUNCTION__, CurrentSelector));
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     ASSERT ((ExtendIds == NULL) == (ExtendCount == NULL));
 | |
|     ASSERT ((ExtendSels == NULL) || (ExtendIds != NULL));
 | |
| 
 | |
|     if (ExtendIds != NULL) {
 | |
|       //
 | |
|       // Save the APIC ID of the CPU with the pending event, to the
 | |
|       // corresponding APIC ID array.
 | |
|       // For unplug events, also save the CurrentSelector.
 | |
|       //
 | |
|       if (*ExtendCount == ApicIdCount) {
 | |
|         DEBUG ((DEBUG_ERROR, "%a: APIC ID array too small\n", __FUNCTION__));
 | |
|         return EFI_BUFFER_TOO_SMALL;
 | |
|       }
 | |
|       QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_ARCH_ID);
 | |
|       NewApicId = QemuCpuhpReadCommandData (MmCpuIo);
 | |
|       DEBUG ((DEBUG_VERBOSE, "%a: ApicId=" FMT_APIC_ID "\n", __FUNCTION__,
 | |
|         NewApicId));
 | |
|       if (ExtendSels != NULL) {
 | |
|         ExtendSels[(*ExtendCount)] = CurrentSelector;
 | |
|       }
 | |
|       ExtendIds[(*ExtendCount)++] = NewApicId;
 | |
|     }
 | |
|     //
 | |
|     // We've processed the CPU with (known) pending events, but we must never
 | |
|     // clear events. Therefore we need to advance past this CPU manually;
 | |
|     // otherwise, QEMU_CPUHP_CMD_GET_PENDING would stick to the currently
 | |
|     // selected CPU.
 | |
|     //
 | |
|     CurrentSelector++;
 | |
|   } while (CurrentSelector < PossibleCpuCount);
 | |
| 
 | |
|   DEBUG ((DEBUG_VERBOSE, "%a: PluggedCount=%u ToUnplugCount=%u\n",
 | |
|     __FUNCTION__, *PluggedCount, *ToUnplugCount));
 | |
|   return EFI_SUCCESS;
 | |
| }
 |