/** @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 // ICH9_CPU_HOTPLUG_BASE #include // QEMU_CPUHP_R_CMD_DATA2 #include // CpuDeadLoop() #include // 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; }