2022-02-12 09:53:26 +01:00
|
|
|
/**@file
|
|
|
|
|
|
|
|
Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.<BR>
|
|
|
|
Copyright (c) 2011, Andrei Warkentin <andreiw@motorola.com>
|
|
|
|
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
//
|
|
|
|
// The package level header files this module uses
|
|
|
|
//
|
|
|
|
#include <PiPei.h>
|
|
|
|
|
|
|
|
//
|
|
|
|
// The Library classes this module consumes
|
|
|
|
//
|
|
|
|
#include <Library/BaseMemoryLib.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
|
|
#include <Library/DebugLib.h>
|
|
|
|
#include <Library/HobLib.h>
|
2022-03-07 03:56:27 +01:00
|
|
|
#include <Library/IoLib.h>
|
|
|
|
#include <IndustryStandard/I440FxPiix4.h>
|
|
|
|
#include <IndustryStandard/Microvm.h>
|
|
|
|
#include <IndustryStandard/Pci22.h>
|
|
|
|
#include <IndustryStandard/Q35MchIch9.h>
|
|
|
|
#include <IndustryStandard/QemuCpuHotplug.h>
|
2022-06-29 04:46:22 +02:00
|
|
|
#include <Library/MemoryAllocationLib.h>
|
2022-03-07 03:56:27 +01:00
|
|
|
#include <Library/QemuFwCfgLib.h>
|
|
|
|
#include <Library/QemuFwCfgS3Lib.h>
|
|
|
|
#include <Library/QemuFwCfgSimpleParserLib.h>
|
|
|
|
#include <Library/PciLib.h>
|
2022-06-29 04:46:22 +02:00
|
|
|
#include <Guid/SystemNvDataGuid.h>
|
|
|
|
#include <Guid/VariableFormat.h>
|
2022-03-07 03:56:27 +01:00
|
|
|
#include <OvmfPlatforms.h>
|
|
|
|
|
2022-02-12 09:53:26 +01:00
|
|
|
#include <Library/PlatformInitLib.h>
|
|
|
|
|
OvmfPkg/PlatformInitLib: catch QEMU's CPU hotplug reg block regression
In QEMU v5.1.0, the CPU hotplug register block misbehaves: the negotiation
protocol is (effectively) broken such that it suggests that switching from
the legacy interface to the modern interface works, but in reality the
switch never happens. The symptom has been witnessed when using TCG
acceleration; KVM seems to mask the issue. The issue persists with the
following (latest) stable QEMU releases: v5.2.0, v6.2.0, v7.2.0. Currently
there is no stable release that addresses the problem.
The QEMU bug confuses the Present and Possible counting in function
PlatformMaxCpuCountInitialization(), in
"OvmfPkg/Library/PlatformInitLib/Platform.c". OVMF ends up with Present=0
Possible=1. This in turn further confuses MpInitLib in UefiCpuPkg (hence
firmware-time multiprocessing will be broken). Worse, CPU hot(un)plug with
SMI will be summarily broken in OvmfPkg/CpuHotplugSmm, which (considering
the privilege level of SMM) is not that great.
Detect the issue in PlatformCpuCountBugCheck(), and print an error message
and *hang* if the issue is present.
Users willing to take risks can override the hang with the experimental
QEMU command line option
-fw_cfg name=opt/org.tianocore/X-Cpuhp-Bugcheck-Override,string=yes
(The "-fw_cfg" QEMU option itself is not experimental; its above argument,
as far it concerns the firmware, is experimental.)
The problem was originally reported by Ard [0]. We analyzed it at [1] and
[2]. A QEMU patch was sent at [3]; now merged as commit dab30fbef389
("acpi: cpuhp: fix guest-visible maximum access size to the legacy reg
block", 2023-01-08), to be included in QEMU v8.0.0.
[0] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c2
[1] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c3
[2] IO port write width clamping differs between TCG and KVM
http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html
[3] acpi: cpuhp: fix guest-visible maximum access size to the legacy reg block
http://mid.mail-archive.com/20230104090138.214862-1-lersek@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00278.html
NOTE: PlatformInitLib is used in the following platform DSCs:
OvmfPkg/AmdSev/AmdSevX64.dsc
OvmfPkg/CloudHv/CloudHvX64.dsc
OvmfPkg/IntelTdx/IntelTdxX64.dsc
OvmfPkg/Microvm/MicrovmX64.dsc
OvmfPkg/OvmfPkgIa32.dsc
OvmfPkg/OvmfPkgIa32X64.dsc
OvmfPkg/OvmfPkgX64.dsc
but I can only test this change with the last three platforms, running on
QEMU.
Test results:
TCG QEMU OVMF override result
patched patched
--- ------- ------- -------- --------------------------------------
0 0 0 0 CPU counts OK (KVM masks the QEMU bug)
0 0 1 0 CPU counts OK (KVM masks the QEMU bug)
0 1 0 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
0 1 1 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
1 0 0 0 boot with broken CPU counts (original
QEMU bug)
1 0 1 0 broken CPU count caught (boot hangs)
1 0 1 1 broken CPU count caught, bug check
overridden, boot continues
1 1 0 0 CPU counts OK (QEMU fix)
1 1 1 0 CPU counts OK (QEMU fix)
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Brown <mcb30@ipxe.org>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Oliver Steffen <osteffen@redhat.com>
Cc: Sebastien Boeuf <sebastien.boeuf@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=4250
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20230119110131.91923-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Hugely-appreciated-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2023-01-19 12:01:31 +01:00
|
|
|
#define CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE \
|
|
|
|
"opt/org.tianocore/X-Cpuhp-Bugcheck-Override"
|
|
|
|
|
2022-02-12 09:53:26 +01:00
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryBase,
|
|
|
|
IN UINT64 MemorySize
|
|
|
|
)
|
|
|
|
{
|
|
|
|
BuildResourceDescriptorHob (
|
|
|
|
EFI_RESOURCE_MEMORY_MAPPED_IO,
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_PRESENT |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_TESTED,
|
|
|
|
MemoryBase,
|
|
|
|
MemorySize
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformAddReservedMemoryBaseSizeHob (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryBase,
|
|
|
|
IN UINT64 MemorySize,
|
|
|
|
IN BOOLEAN Cacheable
|
|
|
|
)
|
|
|
|
{
|
|
|
|
BuildResourceDescriptorHob (
|
|
|
|
EFI_RESOURCE_MEMORY_RESERVED,
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_PRESENT |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE |
|
|
|
|
(Cacheable ?
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_WRITE_COMBINEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_WRITE_THROUGH_CACHEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE :
|
|
|
|
0
|
|
|
|
) |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_TESTED,
|
|
|
|
MemoryBase,
|
|
|
|
MemorySize
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformAddIoMemoryRangeHob (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryBase,
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryLimit
|
|
|
|
)
|
|
|
|
{
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (MemoryBase, (UINT64)(MemoryLimit - MemoryBase));
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformAddMemoryBaseSizeHob (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryBase,
|
|
|
|
IN UINT64 MemorySize
|
|
|
|
)
|
|
|
|
{
|
|
|
|
BuildResourceDescriptorHob (
|
|
|
|
EFI_RESOURCE_SYSTEM_MEMORY,
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_PRESENT |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_WRITE_COMBINEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_WRITE_THROUGH_CACHEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_TESTED,
|
|
|
|
MemoryBase,
|
|
|
|
MemorySize
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformAddMemoryRangeHob (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryBase,
|
|
|
|
IN EFI_PHYSICAL_ADDRESS MemoryLimit
|
|
|
|
)
|
|
|
|
{
|
|
|
|
PlatformAddMemoryBaseSizeHob (MemoryBase, (UINT64)(MemoryLimit - MemoryBase));
|
|
|
|
}
|
2022-03-07 03:56:27 +01:00
|
|
|
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformMemMapInitialization (
|
|
|
|
IN OUT EFI_HOB_PLATFORM_INFO *PlatformInfoHob
|
|
|
|
)
|
|
|
|
{
|
|
|
|
UINT64 PciIoBase;
|
|
|
|
UINT64 PciIoSize;
|
|
|
|
UINT64 PciExBarBase;
|
|
|
|
UINT32 PciBase;
|
|
|
|
UINT32 PciSize;
|
|
|
|
|
|
|
|
PciIoBase = 0xC000;
|
|
|
|
PciIoSize = 0x4000;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Video memory + Legacy BIOS region
|
|
|
|
//
|
2022-02-16 06:42:55 +01:00
|
|
|
if (!TdIsEnabled ()) {
|
|
|
|
PlatformAddIoMemoryRangeHob (0x0A0000, BASE_1MB);
|
|
|
|
}
|
2022-03-07 03:56:27 +01:00
|
|
|
|
|
|
|
if (PlatformInfoHob->HostBridgeDevId == 0xffff /* microvm */) {
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (MICROVM_GED_MMIO_BASE, SIZE_4KB);
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (0xFEC00000, SIZE_4KB); /* ioapic #1 */
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (0xFEC10000, SIZE_4KB); /* ioapic #2 */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-28 09:34:00 +02:00
|
|
|
//
|
|
|
|
// address purpose size
|
|
|
|
// ------------ -------- -------------------------
|
|
|
|
// max(top, 2g) PCI MMIO 0xFC000000 - max(top, 2g) (pc)
|
2023-03-28 09:34:01 +02:00
|
|
|
// max(top, 2g) PCI MMIO 0xE0000000 - max(top, 2g) (q35)
|
|
|
|
// 0xE0000000 MMCONFIG 256 MB (q35)
|
2023-03-28 09:34:00 +02:00
|
|
|
// 0xFC000000 gap 44 MB
|
|
|
|
// 0xFEC00000 IO-APIC 4 KB
|
|
|
|
// 0xFEC01000 gap 1020 KB
|
|
|
|
// 0xFED00000 HPET 1 KB
|
|
|
|
// 0xFED00400 gap 111 KB
|
|
|
|
// 0xFED1C000 gap (PIIX4) / RCRB (ICH9) 16 KB
|
|
|
|
// 0xFED20000 gap 896 KB
|
|
|
|
// 0xFEE00000 LAPIC 1 MB
|
|
|
|
//
|
2023-01-17 13:16:26 +01:00
|
|
|
PlatformGetSystemMemorySizeBelow4gb (PlatformInfoHob);
|
2023-03-28 09:34:01 +02:00
|
|
|
PciBase = PlatformInfoHob->Uc32Base;
|
2022-03-07 03:56:27 +01:00
|
|
|
PciExBarBase = 0;
|
|
|
|
if (PlatformInfoHob->HostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
|
|
|
|
//
|
|
|
|
// The MMCONFIG area is expected to fall between the top of low RAM and
|
|
|
|
// the base of the 32-bit PCI host aperture.
|
|
|
|
//
|
2022-06-02 10:42:13 +02:00
|
|
|
PciExBarBase = PcdGet64 (PcdPciExpressBaseAddress);
|
2023-01-17 13:16:26 +01:00
|
|
|
ASSERT (PlatformInfoHob->LowMemory <= PciExBarBase);
|
2022-03-07 03:56:27 +01:00
|
|
|
ASSERT (PciExBarBase <= MAX_UINT32 - SIZE_256MB);
|
2023-03-28 09:34:01 +02:00
|
|
|
PciSize = (UINT32)(PciExBarBase - PciBase);
|
2022-03-07 03:56:27 +01:00
|
|
|
} else {
|
2023-01-17 13:16:26 +01:00
|
|
|
ASSERT (PlatformInfoHob->LowMemory <= PlatformInfoHob->Uc32Base);
|
2023-03-28 09:34:01 +02:00
|
|
|
PciSize = 0xFC000000 - PciBase;
|
2022-03-07 03:56:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (PciBase, PciSize);
|
|
|
|
|
|
|
|
PlatformInfoHob->PcdPciMmio32Base = PciBase;
|
|
|
|
PlatformInfoHob->PcdPciMmio32Size = PciSize;
|
|
|
|
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (0xFEC00000, SIZE_4KB);
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (0xFED00000, SIZE_1KB);
|
|
|
|
if (PlatformInfoHob->HostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (ICH9_ROOT_COMPLEX_BASE, SIZE_16KB);
|
|
|
|
//
|
|
|
|
// Note: there should be an
|
|
|
|
//
|
|
|
|
// PlatformAddIoMemoryBaseSizeHob (PciExBarBase, SIZE_256MB);
|
|
|
|
//
|
|
|
|
// call below, just like the one above for RCBA. However, Linux insists
|
|
|
|
// that the MMCONFIG area be marked in the E820 or UEFI memory map as
|
|
|
|
// "reserved memory" -- Linux does not content itself with a simple gap
|
|
|
|
// in the memory map wherever the MCFG ACPI table points to.
|
|
|
|
//
|
|
|
|
// This appears to be a safety measure. The PCI Firmware Specification
|
|
|
|
// (rev 3.1) says in 4.1.2. "MCFG Table Description": "The resources can
|
|
|
|
// *optionally* be returned in [...] EFIGetMemoryMap as reserved memory
|
|
|
|
// [...]". (Emphasis added here.)
|
|
|
|
//
|
|
|
|
// Normally we add memory resource descriptor HOBs in
|
|
|
|
// QemuInitializeRam(), and pre-allocate from those with memory
|
|
|
|
// allocation HOBs in InitializeRamRegions(). However, the MMCONFIG area
|
|
|
|
// is most definitely not RAM; so, as an exception, cover it with
|
|
|
|
// uncacheable reserved memory right here.
|
|
|
|
//
|
|
|
|
PlatformAddReservedMemoryBaseSizeHob (PciExBarBase, SIZE_256MB, FALSE);
|
|
|
|
BuildMemoryAllocationHob (
|
|
|
|
PciExBarBase,
|
|
|
|
SIZE_256MB,
|
|
|
|
EfiReservedMemoryType
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
PlatformAddIoMemoryBaseSizeHob (PcdGet32 (PcdCpuLocalApicBaseAddress), SIZE_1MB);
|
|
|
|
|
|
|
|
//
|
|
|
|
// On Q35, the IO Port space is available for PCI resource allocations from
|
|
|
|
// 0x6000 up.
|
|
|
|
//
|
|
|
|
if (PlatformInfoHob->HostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
|
|
|
|
PciIoBase = 0x6000;
|
|
|
|
PciIoSize = 0xA000;
|
|
|
|
ASSERT ((ICH9_PMBASE_VALUE & 0xF000) < PciIoBase);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Add PCI IO Port space available for PCI resource allocations.
|
|
|
|
//
|
|
|
|
BuildResourceDescriptorHob (
|
|
|
|
EFI_RESOURCE_IO,
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_PRESENT |
|
|
|
|
EFI_RESOURCE_ATTRIBUTE_INITIALIZED,
|
|
|
|
PciIoBase,
|
|
|
|
PciIoSize
|
|
|
|
);
|
|
|
|
|
|
|
|
PlatformInfoHob->PcdPciIoBase = PciIoBase;
|
|
|
|
PlatformInfoHob->PcdPciIoSize = PciIoSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch "opt/ovmf/PcdSetNxForStack" from QEMU
|
|
|
|
*
|
|
|
|
* @param Setting The pointer to the setting of "/opt/ovmf/PcdSetNxForStack".
|
|
|
|
* @return EFI_SUCCESS Successfully fetch the settings.
|
|
|
|
*/
|
|
|
|
EFI_STATUS
|
|
|
|
EFIAPI
|
|
|
|
PlatformNoexecDxeInitialization (
|
|
|
|
IN OUT EFI_HOB_PLATFORM_INFO *PlatformInfoHob
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return QemuFwCfgParseBool ("opt/ovmf/PcdSetNxForStack", &PlatformInfoHob->PcdSetNxForStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
PciExBarInitialization (
|
|
|
|
VOID
|
|
|
|
)
|
|
|
|
{
|
|
|
|
union {
|
|
|
|
UINT64 Uint64;
|
|
|
|
UINT32 Uint32[2];
|
|
|
|
} PciExBarBase;
|
|
|
|
|
|
|
|
//
|
|
|
|
// We only support the 256MB size for the MMCONFIG area:
|
|
|
|
// 256 buses * 32 devices * 8 functions * 4096 bytes config space.
|
|
|
|
//
|
|
|
|
// The masks used below enforce the Q35 requirements that the MMCONFIG area
|
|
|
|
// be (a) correctly aligned -- here at 256 MB --, (b) located under 64 GB.
|
|
|
|
//
|
|
|
|
// Note that (b) also ensures that the minimum address width we have
|
|
|
|
// determined in AddressWidthInitialization(), i.e., 36 bits, will suffice
|
|
|
|
// for DXE's page tables to cover the MMCONFIG area.
|
|
|
|
//
|
2022-06-02 10:42:13 +02:00
|
|
|
PciExBarBase.Uint64 = PcdGet64 (PcdPciExpressBaseAddress);
|
2022-03-07 03:56:27 +01:00
|
|
|
ASSERT ((PciExBarBase.Uint32[1] & MCH_PCIEXBAR_HIGHMASK) == 0);
|
|
|
|
ASSERT ((PciExBarBase.Uint32[0] & MCH_PCIEXBAR_LOWMASK) == 0);
|
|
|
|
|
|
|
|
//
|
|
|
|
// Clear the PCIEXBAREN bit first, before programming the high register.
|
|
|
|
//
|
|
|
|
PciWrite32 (DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_LOW), 0);
|
|
|
|
|
|
|
|
//
|
|
|
|
// Program the high register. Then program the low register, setting the
|
|
|
|
// MMCONFIG area size and enabling decoding at once.
|
|
|
|
//
|
|
|
|
PciWrite32 (DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_HIGH), PciExBarBase.Uint32[1]);
|
|
|
|
PciWrite32 (
|
|
|
|
DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_LOW),
|
|
|
|
PciExBarBase.Uint32[0] | MCH_PCIEXBAR_BUS_FF | MCH_PCIEXBAR_EN
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformMiscInitialization (
|
|
|
|
IN EFI_HOB_PLATFORM_INFO *PlatformInfoHob
|
|
|
|
)
|
|
|
|
{
|
|
|
|
UINTN PmCmd;
|
|
|
|
UINTN Pmba;
|
|
|
|
UINT32 PmbaAndVal;
|
|
|
|
UINT32 PmbaOrVal;
|
|
|
|
UINTN AcpiCtlReg;
|
|
|
|
UINT8 AcpiEnBit;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Disable A20 Mask
|
|
|
|
//
|
2022-05-10 14:50:46 +02:00
|
|
|
if (PlatformInfoHob->HostBridgeDevId != CLOUDHV_DEVICE_ID) {
|
|
|
|
IoOr8 (0x92, BIT1);
|
|
|
|
}
|
2022-03-07 03:56:27 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// Build the CPU HOB with guest RAM size dependent address width and 16-bits
|
|
|
|
// of IO space. (Side note: unlike other HOBs, the CPU HOB is needed during
|
|
|
|
// S3 resume as well, so we build it unconditionally.)
|
|
|
|
//
|
|
|
|
BuildCpuHob (PlatformInfoHob->PhysMemAddressWidth, 16);
|
|
|
|
|
|
|
|
//
|
|
|
|
// Determine platform type and save Host Bridge DID to PCD
|
|
|
|
//
|
|
|
|
switch (PlatformInfoHob->HostBridgeDevId) {
|
|
|
|
case INTEL_82441_DEVICE_ID:
|
|
|
|
PmCmd = POWER_MGMT_REGISTER_PIIX4 (PCI_COMMAND_OFFSET);
|
|
|
|
Pmba = POWER_MGMT_REGISTER_PIIX4 (PIIX4_PMBA);
|
|
|
|
PmbaAndVal = ~(UINT32)PIIX4_PMBA_MASK;
|
|
|
|
PmbaOrVal = PIIX4_PMBA_VALUE;
|
|
|
|
AcpiCtlReg = POWER_MGMT_REGISTER_PIIX4 (PIIX4_PMREGMISC);
|
|
|
|
AcpiEnBit = PIIX4_PMREGMISC_PMIOSE;
|
|
|
|
break;
|
|
|
|
case INTEL_Q35_MCH_DEVICE_ID:
|
|
|
|
PmCmd = POWER_MGMT_REGISTER_Q35 (PCI_COMMAND_OFFSET);
|
|
|
|
Pmba = POWER_MGMT_REGISTER_Q35 (ICH9_PMBASE);
|
|
|
|
PmbaAndVal = ~(UINT32)ICH9_PMBASE_MASK;
|
|
|
|
PmbaOrVal = ICH9_PMBASE_VALUE;
|
|
|
|
AcpiCtlReg = POWER_MGMT_REGISTER_Q35 (ICH9_ACPI_CNTL);
|
|
|
|
AcpiEnBit = ICH9_ACPI_CNTL_ACPI_EN;
|
|
|
|
break;
|
|
|
|
case CLOUDHV_DEVICE_ID:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_ERROR,
|
|
|
|
"%a: Unknown Host Bridge Device ID: 0x%04x\n",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2022-03-07 03:56:27 +01:00
|
|
|
PlatformInfoHob->HostBridgeDevId
|
|
|
|
));
|
|
|
|
ASSERT (FALSE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PlatformInfoHob->HostBridgeDevId == CLOUDHV_DEVICE_ID) {
|
2023-04-06 21:49:41 +02:00
|
|
|
DEBUG ((DEBUG_INFO, "%a: Cloud Hypervisor is done.\n", __func__));
|
2022-03-07 03:56:27 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// If the appropriate IOspace enable bit is set, assume the ACPI PMBA has
|
|
|
|
// been configured and skip the setup here. This matches the logic in
|
|
|
|
// AcpiTimerLibConstructor ().
|
|
|
|
//
|
|
|
|
if ((PciRead8 (AcpiCtlReg) & AcpiEnBit) == 0) {
|
|
|
|
//
|
|
|
|
// The PEI phase should be exited with fully accessibe ACPI PM IO space:
|
|
|
|
// 1. set PMBA
|
|
|
|
//
|
|
|
|
PciAndThenOr32 (Pmba, PmbaAndVal, PmbaOrVal);
|
|
|
|
|
|
|
|
//
|
|
|
|
// 2. set PCICMD/IOSE
|
|
|
|
//
|
|
|
|
PciOr8 (PmCmd, EFI_PCI_COMMAND_IO_SPACE);
|
|
|
|
|
|
|
|
//
|
|
|
|
// 3. set ACPI PM IO enable bit (PMREGMISC:PMIOSE or ACPI_CNTL:ACPI_EN)
|
|
|
|
//
|
|
|
|
PciOr8 (AcpiCtlReg, AcpiEnBit);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PlatformInfoHob->HostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
|
|
|
|
//
|
|
|
|
// Set Root Complex Register Block BAR
|
|
|
|
//
|
|
|
|
PciWrite32 (
|
|
|
|
POWER_MGMT_REGISTER_Q35 (ICH9_RCBA),
|
|
|
|
ICH9_ROOT_COMPLEX_BASE | ICH9_RCBA_EN
|
|
|
|
);
|
|
|
|
|
|
|
|
//
|
|
|
|
// Set PCI Express Register Range Base Address
|
|
|
|
//
|
|
|
|
PciExBarInitialization ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-19 12:01:30 +01:00
|
|
|
/**
|
|
|
|
Check for various QEMU bugs concerning CPU numbers.
|
|
|
|
|
|
|
|
Compensate for those bugs if various conditions are satisfied, by updating a
|
|
|
|
suitable subset of the input-output parameters. The function may not return
|
|
|
|
(it may hang deliberately), even in RELEASE builds, if the QEMU bug is
|
|
|
|
impossible to cover up.
|
|
|
|
|
|
|
|
@param[in,out] BootCpuCount On input, the boot CPU count reported by QEMU via
|
|
|
|
fw_cfg (QemuFwCfgItemSmpCpuCount). The caller is
|
|
|
|
responsible for ensuring (BootCpuCount > 0); that
|
|
|
|
is, if QEMU does not provide the boot CPU count
|
|
|
|
via fw_cfg *at all*, then this function must not
|
|
|
|
be called.
|
|
|
|
|
|
|
|
@param[in,out] Present On input, the number of present-at-boot CPUs, as
|
|
|
|
reported by QEMU through the modern CPU hotplug
|
|
|
|
register block.
|
|
|
|
|
|
|
|
@param[in,out] Possible On input, the number of possible CPUs, as
|
|
|
|
reported by QEMU through the modern CPU hotplug
|
|
|
|
register block.
|
|
|
|
**/
|
|
|
|
STATIC
|
|
|
|
VOID
|
|
|
|
PlatformCpuCountBugCheck (
|
|
|
|
IN OUT UINT16 *BootCpuCount,
|
|
|
|
IN OUT UINT32 *Present,
|
|
|
|
IN OUT UINT32 *Possible
|
|
|
|
)
|
|
|
|
{
|
|
|
|
ASSERT (*BootCpuCount > 0);
|
|
|
|
|
OvmfPkg/PlatformInitLib: catch QEMU's CPU hotplug reg block regression
In QEMU v5.1.0, the CPU hotplug register block misbehaves: the negotiation
protocol is (effectively) broken such that it suggests that switching from
the legacy interface to the modern interface works, but in reality the
switch never happens. The symptom has been witnessed when using TCG
acceleration; KVM seems to mask the issue. The issue persists with the
following (latest) stable QEMU releases: v5.2.0, v6.2.0, v7.2.0. Currently
there is no stable release that addresses the problem.
The QEMU bug confuses the Present and Possible counting in function
PlatformMaxCpuCountInitialization(), in
"OvmfPkg/Library/PlatformInitLib/Platform.c". OVMF ends up with Present=0
Possible=1. This in turn further confuses MpInitLib in UefiCpuPkg (hence
firmware-time multiprocessing will be broken). Worse, CPU hot(un)plug with
SMI will be summarily broken in OvmfPkg/CpuHotplugSmm, which (considering
the privilege level of SMM) is not that great.
Detect the issue in PlatformCpuCountBugCheck(), and print an error message
and *hang* if the issue is present.
Users willing to take risks can override the hang with the experimental
QEMU command line option
-fw_cfg name=opt/org.tianocore/X-Cpuhp-Bugcheck-Override,string=yes
(The "-fw_cfg" QEMU option itself is not experimental; its above argument,
as far it concerns the firmware, is experimental.)
The problem was originally reported by Ard [0]. We analyzed it at [1] and
[2]. A QEMU patch was sent at [3]; now merged as commit dab30fbef389
("acpi: cpuhp: fix guest-visible maximum access size to the legacy reg
block", 2023-01-08), to be included in QEMU v8.0.0.
[0] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c2
[1] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c3
[2] IO port write width clamping differs between TCG and KVM
http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html
[3] acpi: cpuhp: fix guest-visible maximum access size to the legacy reg block
http://mid.mail-archive.com/20230104090138.214862-1-lersek@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00278.html
NOTE: PlatformInitLib is used in the following platform DSCs:
OvmfPkg/AmdSev/AmdSevX64.dsc
OvmfPkg/CloudHv/CloudHvX64.dsc
OvmfPkg/IntelTdx/IntelTdxX64.dsc
OvmfPkg/Microvm/MicrovmX64.dsc
OvmfPkg/OvmfPkgIa32.dsc
OvmfPkg/OvmfPkgIa32X64.dsc
OvmfPkg/OvmfPkgX64.dsc
but I can only test this change with the last three platforms, running on
QEMU.
Test results:
TCG QEMU OVMF override result
patched patched
--- ------- ------- -------- --------------------------------------
0 0 0 0 CPU counts OK (KVM masks the QEMU bug)
0 0 1 0 CPU counts OK (KVM masks the QEMU bug)
0 1 0 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
0 1 1 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
1 0 0 0 boot with broken CPU counts (original
QEMU bug)
1 0 1 0 broken CPU count caught (boot hangs)
1 0 1 1 broken CPU count caught, bug check
overridden, boot continues
1 1 0 0 CPU counts OK (QEMU fix)
1 1 1 0 CPU counts OK (QEMU fix)
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Brown <mcb30@ipxe.org>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Oliver Steffen <osteffen@redhat.com>
Cc: Sebastien Boeuf <sebastien.boeuf@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=4250
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20230119110131.91923-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Hugely-appreciated-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2023-01-19 12:01:31 +01:00
|
|
|
//
|
|
|
|
// Sanity check: we need at least 1 present CPU (CPU#0 is always present).
|
|
|
|
//
|
|
|
|
// The legacy-to-modern switching of the CPU hotplug register block got broken
|
|
|
|
// (for TCG) in QEMU v5.1.0. Refer to "IO port write width clamping differs
|
|
|
|
// between TCG and KVM" at
|
|
|
|
// <http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com>
|
|
|
|
// or at
|
|
|
|
// <https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html>.
|
|
|
|
//
|
|
|
|
// QEMU received the fix in commit dab30fbef389 ("acpi: cpuhp: fix
|
|
|
|
// guest-visible maximum access size to the legacy reg block", 2023-01-08), to
|
|
|
|
// be included in QEMU v8.0.0.
|
|
|
|
//
|
|
|
|
// If we're affected by this QEMU bug, then we must not continue: it confuses
|
|
|
|
// the multiprocessing in UefiCpuPkg/Library/MpInitLib, and breaks CPU
|
|
|
|
// hot(un)plug with SMI in OvmfPkg/CpuHotplugSmm.
|
|
|
|
//
|
|
|
|
if (*Present == 0) {
|
|
|
|
UINTN Idx;
|
|
|
|
STATIC CONST CHAR8 *CONST Message[] = {
|
|
|
|
"Broken CPU hotplug register block found. Update QEMU to version 8+, or",
|
|
|
|
"to a stable release with commit dab30fbef389 backported. Refer to",
|
|
|
|
"<https://bugzilla.tianocore.org/show_bug.cgi?id=4250>.",
|
|
|
|
"Consequences of the QEMU bug may include, but are not limited to:",
|
|
|
|
"- all firmware logic, dependent on the CPU hotplug register block,",
|
|
|
|
" being confused, for example, multiprocessing-related logic;",
|
|
|
|
"- guest OS data loss, including filesystem corruption, due to crash or",
|
|
|
|
" hang during ACPI S3 resume;",
|
|
|
|
"- SMM privilege escalation, by a malicious guest OS or 3rd partty UEFI",
|
|
|
|
" agent, against the platform firmware.",
|
|
|
|
"These symptoms need not necessarily be limited to the QEMU user",
|
|
|
|
"attempting to hot(un)plug a CPU.",
|
|
|
|
"The firmware will now stop (hang) deliberately, in order to prevent the",
|
|
|
|
"above symptoms.",
|
|
|
|
"You can forcibly override the hang, *at your own risk*, with the",
|
|
|
|
"following *experimental* QEMU command line option:",
|
|
|
|
" -fw_cfg name=" CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE ",string=yes",
|
|
|
|
"Please only report such bugs that you can reproduce *without* the",
|
|
|
|
"override.",
|
|
|
|
};
|
|
|
|
RETURN_STATUS ParseStatus;
|
|
|
|
BOOLEAN Override;
|
|
|
|
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_ERROR,
|
|
|
|
"%a: Present=%u Possible=%u\n",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
OvmfPkg/PlatformInitLib: catch QEMU's CPU hotplug reg block regression
In QEMU v5.1.0, the CPU hotplug register block misbehaves: the negotiation
protocol is (effectively) broken such that it suggests that switching from
the legacy interface to the modern interface works, but in reality the
switch never happens. The symptom has been witnessed when using TCG
acceleration; KVM seems to mask the issue. The issue persists with the
following (latest) stable QEMU releases: v5.2.0, v6.2.0, v7.2.0. Currently
there is no stable release that addresses the problem.
The QEMU bug confuses the Present and Possible counting in function
PlatformMaxCpuCountInitialization(), in
"OvmfPkg/Library/PlatformInitLib/Platform.c". OVMF ends up with Present=0
Possible=1. This in turn further confuses MpInitLib in UefiCpuPkg (hence
firmware-time multiprocessing will be broken). Worse, CPU hot(un)plug with
SMI will be summarily broken in OvmfPkg/CpuHotplugSmm, which (considering
the privilege level of SMM) is not that great.
Detect the issue in PlatformCpuCountBugCheck(), and print an error message
and *hang* if the issue is present.
Users willing to take risks can override the hang with the experimental
QEMU command line option
-fw_cfg name=opt/org.tianocore/X-Cpuhp-Bugcheck-Override,string=yes
(The "-fw_cfg" QEMU option itself is not experimental; its above argument,
as far it concerns the firmware, is experimental.)
The problem was originally reported by Ard [0]. We analyzed it at [1] and
[2]. A QEMU patch was sent at [3]; now merged as commit dab30fbef389
("acpi: cpuhp: fix guest-visible maximum access size to the legacy reg
block", 2023-01-08), to be included in QEMU v8.0.0.
[0] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c2
[1] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c3
[2] IO port write width clamping differs between TCG and KVM
http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html
[3] acpi: cpuhp: fix guest-visible maximum access size to the legacy reg block
http://mid.mail-archive.com/20230104090138.214862-1-lersek@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00278.html
NOTE: PlatformInitLib is used in the following platform DSCs:
OvmfPkg/AmdSev/AmdSevX64.dsc
OvmfPkg/CloudHv/CloudHvX64.dsc
OvmfPkg/IntelTdx/IntelTdxX64.dsc
OvmfPkg/Microvm/MicrovmX64.dsc
OvmfPkg/OvmfPkgIa32.dsc
OvmfPkg/OvmfPkgIa32X64.dsc
OvmfPkg/OvmfPkgX64.dsc
but I can only test this change with the last three platforms, running on
QEMU.
Test results:
TCG QEMU OVMF override result
patched patched
--- ------- ------- -------- --------------------------------------
0 0 0 0 CPU counts OK (KVM masks the QEMU bug)
0 0 1 0 CPU counts OK (KVM masks the QEMU bug)
0 1 0 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
0 1 1 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
1 0 0 0 boot with broken CPU counts (original
QEMU bug)
1 0 1 0 broken CPU count caught (boot hangs)
1 0 1 1 broken CPU count caught, bug check
overridden, boot continues
1 1 0 0 CPU counts OK (QEMU fix)
1 1 1 0 CPU counts OK (QEMU fix)
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Brown <mcb30@ipxe.org>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Oliver Steffen <osteffen@redhat.com>
Cc: Sebastien Boeuf <sebastien.boeuf@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=4250
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20230119110131.91923-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Hugely-appreciated-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2023-01-19 12:01:31 +01:00
|
|
|
*Present,
|
|
|
|
*Possible
|
|
|
|
));
|
|
|
|
for (Idx = 0; Idx < ARRAY_SIZE (Message); ++Idx) {
|
2023-04-06 21:49:41 +02:00
|
|
|
DEBUG ((DEBUG_ERROR, "%a: %a\n", __func__, Message[Idx]));
|
OvmfPkg/PlatformInitLib: catch QEMU's CPU hotplug reg block regression
In QEMU v5.1.0, the CPU hotplug register block misbehaves: the negotiation
protocol is (effectively) broken such that it suggests that switching from
the legacy interface to the modern interface works, but in reality the
switch never happens. The symptom has been witnessed when using TCG
acceleration; KVM seems to mask the issue. The issue persists with the
following (latest) stable QEMU releases: v5.2.0, v6.2.0, v7.2.0. Currently
there is no stable release that addresses the problem.
The QEMU bug confuses the Present and Possible counting in function
PlatformMaxCpuCountInitialization(), in
"OvmfPkg/Library/PlatformInitLib/Platform.c". OVMF ends up with Present=0
Possible=1. This in turn further confuses MpInitLib in UefiCpuPkg (hence
firmware-time multiprocessing will be broken). Worse, CPU hot(un)plug with
SMI will be summarily broken in OvmfPkg/CpuHotplugSmm, which (considering
the privilege level of SMM) is not that great.
Detect the issue in PlatformCpuCountBugCheck(), and print an error message
and *hang* if the issue is present.
Users willing to take risks can override the hang with the experimental
QEMU command line option
-fw_cfg name=opt/org.tianocore/X-Cpuhp-Bugcheck-Override,string=yes
(The "-fw_cfg" QEMU option itself is not experimental; its above argument,
as far it concerns the firmware, is experimental.)
The problem was originally reported by Ard [0]. We analyzed it at [1] and
[2]. A QEMU patch was sent at [3]; now merged as commit dab30fbef389
("acpi: cpuhp: fix guest-visible maximum access size to the legacy reg
block", 2023-01-08), to be included in QEMU v8.0.0.
[0] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c2
[1] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c3
[2] IO port write width clamping differs between TCG and KVM
http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html
[3] acpi: cpuhp: fix guest-visible maximum access size to the legacy reg block
http://mid.mail-archive.com/20230104090138.214862-1-lersek@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00278.html
NOTE: PlatformInitLib is used in the following platform DSCs:
OvmfPkg/AmdSev/AmdSevX64.dsc
OvmfPkg/CloudHv/CloudHvX64.dsc
OvmfPkg/IntelTdx/IntelTdxX64.dsc
OvmfPkg/Microvm/MicrovmX64.dsc
OvmfPkg/OvmfPkgIa32.dsc
OvmfPkg/OvmfPkgIa32X64.dsc
OvmfPkg/OvmfPkgX64.dsc
but I can only test this change with the last three platforms, running on
QEMU.
Test results:
TCG QEMU OVMF override result
patched patched
--- ------- ------- -------- --------------------------------------
0 0 0 0 CPU counts OK (KVM masks the QEMU bug)
0 0 1 0 CPU counts OK (KVM masks the QEMU bug)
0 1 0 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
0 1 1 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
1 0 0 0 boot with broken CPU counts (original
QEMU bug)
1 0 1 0 broken CPU count caught (boot hangs)
1 0 1 1 broken CPU count caught, bug check
overridden, boot continues
1 1 0 0 CPU counts OK (QEMU fix)
1 1 1 0 CPU counts OK (QEMU fix)
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Brown <mcb30@ipxe.org>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Oliver Steffen <osteffen@redhat.com>
Cc: Sebastien Boeuf <sebastien.boeuf@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=4250
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20230119110131.91923-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Hugely-appreciated-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2023-01-19 12:01:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ParseStatus = QemuFwCfgParseBool (
|
|
|
|
CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE,
|
|
|
|
&Override
|
|
|
|
);
|
|
|
|
if (!RETURN_ERROR (ParseStatus) && Override) {
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_WARN,
|
|
|
|
"%a: \"%a\" active. You've been warned.\n",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
OvmfPkg/PlatformInitLib: catch QEMU's CPU hotplug reg block regression
In QEMU v5.1.0, the CPU hotplug register block misbehaves: the negotiation
protocol is (effectively) broken such that it suggests that switching from
the legacy interface to the modern interface works, but in reality the
switch never happens. The symptom has been witnessed when using TCG
acceleration; KVM seems to mask the issue. The issue persists with the
following (latest) stable QEMU releases: v5.2.0, v6.2.0, v7.2.0. Currently
there is no stable release that addresses the problem.
The QEMU bug confuses the Present and Possible counting in function
PlatformMaxCpuCountInitialization(), in
"OvmfPkg/Library/PlatformInitLib/Platform.c". OVMF ends up with Present=0
Possible=1. This in turn further confuses MpInitLib in UefiCpuPkg (hence
firmware-time multiprocessing will be broken). Worse, CPU hot(un)plug with
SMI will be summarily broken in OvmfPkg/CpuHotplugSmm, which (considering
the privilege level of SMM) is not that great.
Detect the issue in PlatformCpuCountBugCheck(), and print an error message
and *hang* if the issue is present.
Users willing to take risks can override the hang with the experimental
QEMU command line option
-fw_cfg name=opt/org.tianocore/X-Cpuhp-Bugcheck-Override,string=yes
(The "-fw_cfg" QEMU option itself is not experimental; its above argument,
as far it concerns the firmware, is experimental.)
The problem was originally reported by Ard [0]. We analyzed it at [1] and
[2]. A QEMU patch was sent at [3]; now merged as commit dab30fbef389
("acpi: cpuhp: fix guest-visible maximum access size to the legacy reg
block", 2023-01-08), to be included in QEMU v8.0.0.
[0] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c2
[1] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c3
[2] IO port write width clamping differs between TCG and KVM
http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html
[3] acpi: cpuhp: fix guest-visible maximum access size to the legacy reg block
http://mid.mail-archive.com/20230104090138.214862-1-lersek@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00278.html
NOTE: PlatformInitLib is used in the following platform DSCs:
OvmfPkg/AmdSev/AmdSevX64.dsc
OvmfPkg/CloudHv/CloudHvX64.dsc
OvmfPkg/IntelTdx/IntelTdxX64.dsc
OvmfPkg/Microvm/MicrovmX64.dsc
OvmfPkg/OvmfPkgIa32.dsc
OvmfPkg/OvmfPkgIa32X64.dsc
OvmfPkg/OvmfPkgX64.dsc
but I can only test this change with the last three platforms, running on
QEMU.
Test results:
TCG QEMU OVMF override result
patched patched
--- ------- ------- -------- --------------------------------------
0 0 0 0 CPU counts OK (KVM masks the QEMU bug)
0 0 1 0 CPU counts OK (KVM masks the QEMU bug)
0 1 0 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
0 1 1 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
1 0 0 0 boot with broken CPU counts (original
QEMU bug)
1 0 1 0 broken CPU count caught (boot hangs)
1 0 1 1 broken CPU count caught, bug check
overridden, boot continues
1 1 0 0 CPU counts OK (QEMU fix)
1 1 1 0 CPU counts OK (QEMU fix)
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Brown <mcb30@ipxe.org>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Oliver Steffen <osteffen@redhat.com>
Cc: Sebastien Boeuf <sebastien.boeuf@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=4250
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20230119110131.91923-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Hugely-appreciated-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2023-01-19 12:01:31 +01:00
|
|
|
CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE
|
|
|
|
));
|
|
|
|
//
|
|
|
|
// The bug is in QEMU v5.1.0+, where we're not affected by the QEMU v2.7
|
|
|
|
// reset bug, so BootCpuCount from fw_cfg is reliable. Assume a fully
|
|
|
|
// populated topology, like when the modern CPU hotplug interface is
|
|
|
|
// unavailable.
|
|
|
|
//
|
|
|
|
*Present = *BootCpuCount;
|
|
|
|
*Possible = *BootCpuCount;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT (FALSE);
|
|
|
|
CpuDeadLoop ();
|
|
|
|
}
|
|
|
|
|
2023-01-19 12:01:30 +01:00
|
|
|
//
|
|
|
|
// Sanity check: fw_cfg and the modern CPU hotplug interface should expose the
|
|
|
|
// same boot CPU count.
|
|
|
|
//
|
|
|
|
if (*BootCpuCount != *Present) {
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_WARN,
|
|
|
|
"%a: QEMU v2.7 reset bug: BootCpuCount=%d Present=%u\n",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2023-01-19 12:01:30 +01:00
|
|
|
*BootCpuCount,
|
|
|
|
*Present
|
|
|
|
));
|
|
|
|
//
|
|
|
|
// The handling of QemuFwCfgItemSmpCpuCount, across CPU hotplug plus
|
|
|
|
// platform reset (including S3), was corrected in QEMU commit e3cadac073a9
|
|
|
|
// ("pc: fix FW_CFG_NB_CPUS to account for -device added CPUs", 2016-11-16),
|
|
|
|
// part of release v2.8.0.
|
|
|
|
//
|
|
|
|
*BootCpuCount = (UINT16)*Present;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 03:56:27 +01:00
|
|
|
/**
|
|
|
|
Fetch the boot CPU count and the possible CPU count from QEMU, and expose
|
|
|
|
them to UefiCpuPkg modules.
|
|
|
|
**/
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
PlatformMaxCpuCountInitialization (
|
|
|
|
IN OUT EFI_HOB_PLATFORM_INFO *PlatformInfoHob
|
|
|
|
)
|
|
|
|
{
|
2022-05-10 14:50:44 +02:00
|
|
|
UINT16 BootCpuCount = 0;
|
2022-03-07 03:56:27 +01:00
|
|
|
UINT32 MaxCpuCount;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Try to fetch the boot CPU count.
|
|
|
|
//
|
2022-05-10 14:50:44 +02:00
|
|
|
if (QemuFwCfgIsAvailable ()) {
|
|
|
|
QemuFwCfgSelectItem (QemuFwCfgItemSmpCpuCount);
|
|
|
|
BootCpuCount = QemuFwCfgRead16 ();
|
|
|
|
}
|
|
|
|
|
2022-03-07 03:56:27 +01:00
|
|
|
if (BootCpuCount == 0) {
|
|
|
|
//
|
|
|
|
// QEMU doesn't report the boot CPU count. (BootCpuCount == 0) will let
|
|
|
|
// MpInitLib count APs up to (PcdCpuMaxLogicalProcessorNumber - 1), or
|
|
|
|
// until PcdCpuApInitTimeOutInMicroSeconds elapses (whichever is reached
|
|
|
|
// first).
|
|
|
|
//
|
2023-04-06 21:49:41 +02:00
|
|
|
DEBUG ((DEBUG_WARN, "%a: boot CPU count unavailable\n", __func__));
|
2022-03-07 03:56:27 +01:00
|
|
|
MaxCpuCount = PlatformInfoHob->DefaultMaxCpuNumber;
|
|
|
|
} else {
|
|
|
|
//
|
|
|
|
// We will expose BootCpuCount to MpInitLib. MpInitLib will count APs up to
|
|
|
|
// (BootCpuCount - 1) precisely, regardless of timeout.
|
|
|
|
//
|
|
|
|
// Now try to fetch the possible CPU count.
|
|
|
|
//
|
|
|
|
UINTN CpuHpBase;
|
|
|
|
UINT32 CmdData2;
|
|
|
|
|
|
|
|
CpuHpBase = ((PlatformInfoHob->HostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) ?
|
|
|
|
ICH9_CPU_HOTPLUG_BASE : PIIX4_CPU_HOTPLUG_BASE);
|
|
|
|
|
|
|
|
//
|
|
|
|
// If only legacy mode is available in the CPU hotplug register block, or
|
|
|
|
// the register block is completely missing, then the writes below are
|
|
|
|
// no-ops.
|
|
|
|
//
|
|
|
|
// 1. Switch the hotplug register block to modern mode.
|
|
|
|
//
|
|
|
|
IoWrite32 (CpuHpBase + QEMU_CPUHP_W_CPU_SEL, 0);
|
|
|
|
//
|
|
|
|
// 2. Select a valid CPU for deterministic reading of
|
|
|
|
// QEMU_CPUHP_R_CMD_DATA2.
|
|
|
|
//
|
|
|
|
// CPU#0 is always valid; it is the always present and non-removable
|
|
|
|
// BSP.
|
|
|
|
//
|
|
|
|
IoWrite32 (CpuHpBase + QEMU_CPUHP_W_CPU_SEL, 0);
|
|
|
|
//
|
|
|
|
// 3. Send a command after which QEMU_CPUHP_R_CMD_DATA2 is specified to
|
|
|
|
// read as zero, and which does not invalidate the selector. (The
|
|
|
|
// selector may change, but it must not become invalid.)
|
|
|
|
//
|
|
|
|
// Send QEMU_CPUHP_CMD_GET_PENDING, as it will prove useful later.
|
|
|
|
//
|
|
|
|
IoWrite8 (CpuHpBase + QEMU_CPUHP_W_CMD, QEMU_CPUHP_CMD_GET_PENDING);
|
|
|
|
//
|
|
|
|
// 4. Read QEMU_CPUHP_R_CMD_DATA2.
|
|
|
|
//
|
|
|
|
// If the register block is entirely missing, then this is an unassigned
|
|
|
|
// IO read, returning all-bits-one.
|
|
|
|
//
|
|
|
|
// If only legacy mode is available, then bit#0 stands for CPU#0 in the
|
|
|
|
// "CPU present bitmap". CPU#0 is always present.
|
|
|
|
//
|
|
|
|
// Otherwise, QEMU_CPUHP_R_CMD_DATA2 is either still reserved (returning
|
|
|
|
// all-bits-zero), or it is specified to read as zero after the above
|
|
|
|
// steps. Both cases confirm modern mode.
|
|
|
|
//
|
|
|
|
CmdData2 = IoRead32 (CpuHpBase + QEMU_CPUHP_R_CMD_DATA2);
|
2023-04-06 21:49:41 +02:00
|
|
|
DEBUG ((DEBUG_VERBOSE, "%a: CmdData2=0x%x\n", __func__, CmdData2));
|
2022-03-07 03:56:27 +01:00
|
|
|
if (CmdData2 != 0) {
|
|
|
|
//
|
|
|
|
// QEMU doesn't support the modern CPU hotplug interface. Assume that the
|
|
|
|
// possible CPU count equals the boot CPU count (precluding hotplug).
|
|
|
|
//
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_WARN,
|
|
|
|
"%a: modern CPU hotplug interface unavailable\n",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__
|
2022-03-07 03:56:27 +01:00
|
|
|
));
|
|
|
|
MaxCpuCount = BootCpuCount;
|
|
|
|
} else {
|
|
|
|
//
|
|
|
|
// Grab the possible CPU count from the modern CPU hotplug interface.
|
|
|
|
//
|
|
|
|
UINT32 Present, Possible, Selected;
|
|
|
|
|
|
|
|
Present = 0;
|
|
|
|
Possible = 0;
|
|
|
|
|
|
|
|
//
|
|
|
|
// We've sent QEMU_CPUHP_CMD_GET_PENDING last; this ensures
|
|
|
|
// QEMU_CPUHP_RW_CMD_DATA can now be read usefully. However,
|
|
|
|
// QEMU_CPUHP_CMD_GET_PENDING may have selected a CPU with actual pending
|
|
|
|
// hotplug events; therefore, select CPU#0 forcibly.
|
|
|
|
//
|
|
|
|
IoWrite32 (CpuHpBase + QEMU_CPUHP_W_CPU_SEL, Possible);
|
|
|
|
|
|
|
|
do {
|
|
|
|
UINT8 CpuStatus;
|
|
|
|
|
|
|
|
//
|
2023-01-19 12:01:30 +01:00
|
|
|
// Read the status of the currently selected CPU. This will help with
|
|
|
|
// various CPU count sanity checks.
|
2022-03-07 03:56:27 +01:00
|
|
|
//
|
|
|
|
CpuStatus = IoRead8 (CpuHpBase + QEMU_CPUHP_R_CPU_STAT);
|
|
|
|
if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) != 0) {
|
|
|
|
++Present;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Attempt to select the next CPU.
|
|
|
|
//
|
|
|
|
++Possible;
|
|
|
|
IoWrite32 (CpuHpBase + QEMU_CPUHP_W_CPU_SEL, Possible);
|
|
|
|
//
|
|
|
|
// If the selection is successful, then the following read will return
|
|
|
|
// the selector (which we know is positive at this point). Otherwise,
|
|
|
|
// the read will return 0.
|
|
|
|
//
|
|
|
|
Selected = IoRead32 (CpuHpBase + QEMU_CPUHP_RW_CMD_DATA);
|
|
|
|
ASSERT (Selected == Possible || Selected == 0);
|
|
|
|
} while (Selected > 0);
|
|
|
|
|
2023-01-19 12:01:30 +01:00
|
|
|
PlatformCpuCountBugCheck (&BootCpuCount, &Present, &Possible);
|
OvmfPkg/PlatformInitLib: catch QEMU's CPU hotplug reg block regression
In QEMU v5.1.0, the CPU hotplug register block misbehaves: the negotiation
protocol is (effectively) broken such that it suggests that switching from
the legacy interface to the modern interface works, but in reality the
switch never happens. The symptom has been witnessed when using TCG
acceleration; KVM seems to mask the issue. The issue persists with the
following (latest) stable QEMU releases: v5.2.0, v6.2.0, v7.2.0. Currently
there is no stable release that addresses the problem.
The QEMU bug confuses the Present and Possible counting in function
PlatformMaxCpuCountInitialization(), in
"OvmfPkg/Library/PlatformInitLib/Platform.c". OVMF ends up with Present=0
Possible=1. This in turn further confuses MpInitLib in UefiCpuPkg (hence
firmware-time multiprocessing will be broken). Worse, CPU hot(un)plug with
SMI will be summarily broken in OvmfPkg/CpuHotplugSmm, which (considering
the privilege level of SMM) is not that great.
Detect the issue in PlatformCpuCountBugCheck(), and print an error message
and *hang* if the issue is present.
Users willing to take risks can override the hang with the experimental
QEMU command line option
-fw_cfg name=opt/org.tianocore/X-Cpuhp-Bugcheck-Override,string=yes
(The "-fw_cfg" QEMU option itself is not experimental; its above argument,
as far it concerns the firmware, is experimental.)
The problem was originally reported by Ard [0]. We analyzed it at [1] and
[2]. A QEMU patch was sent at [3]; now merged as commit dab30fbef389
("acpi: cpuhp: fix guest-visible maximum access size to the legacy reg
block", 2023-01-08), to be included in QEMU v8.0.0.
[0] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c2
[1] https://bugzilla.tianocore.org/show_bug.cgi?id=4234#c3
[2] IO port write width clamping differs between TCG and KVM
http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html
[3] acpi: cpuhp: fix guest-visible maximum access size to the legacy reg block
http://mid.mail-archive.com/20230104090138.214862-1-lersek@redhat.com
https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00278.html
NOTE: PlatformInitLib is used in the following platform DSCs:
OvmfPkg/AmdSev/AmdSevX64.dsc
OvmfPkg/CloudHv/CloudHvX64.dsc
OvmfPkg/IntelTdx/IntelTdxX64.dsc
OvmfPkg/Microvm/MicrovmX64.dsc
OvmfPkg/OvmfPkgIa32.dsc
OvmfPkg/OvmfPkgIa32X64.dsc
OvmfPkg/OvmfPkgX64.dsc
but I can only test this change with the last three platforms, running on
QEMU.
Test results:
TCG QEMU OVMF override result
patched patched
--- ------- ------- -------- --------------------------------------
0 0 0 0 CPU counts OK (KVM masks the QEMU bug)
0 0 1 0 CPU counts OK (KVM masks the QEMU bug)
0 1 0 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
0 1 1 0 CPU counts OK (QEMU fix, but KVM masks
the QEMU bug anyway)
1 0 0 0 boot with broken CPU counts (original
QEMU bug)
1 0 1 0 broken CPU count caught (boot hangs)
1 0 1 1 broken CPU count caught, bug check
overridden, boot continues
1 1 0 0 CPU counts OK (QEMU fix)
1 1 1 0 CPU counts OK (QEMU fix)
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Brown <mcb30@ipxe.org>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Oliver Steffen <osteffen@redhat.com>
Cc: Sebastien Boeuf <sebastien.boeuf@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=4250
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20230119110131.91923-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Hugely-appreciated-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2023-01-19 12:01:31 +01:00
|
|
|
ASSERT (Present > 0);
|
|
|
|
ASSERT (Present <= Possible);
|
|
|
|
ASSERT (BootCpuCount == Present);
|
2022-03-07 03:56:27 +01:00
|
|
|
|
|
|
|
MaxCpuCount = Possible;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_INFO,
|
|
|
|
"%a: BootCpuCount=%d MaxCpuCount=%u\n",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2022-03-07 03:56:27 +01:00
|
|
|
BootCpuCount,
|
|
|
|
MaxCpuCount
|
|
|
|
));
|
|
|
|
ASSERT (BootCpuCount <= MaxCpuCount);
|
|
|
|
|
|
|
|
PlatformInfoHob->PcdCpuMaxLogicalProcessorNumber = MaxCpuCount;
|
|
|
|
PlatformInfoHob->PcdCpuBootLogicalProcessorNumber = BootCpuCount;
|
|
|
|
}
|
2022-06-29 04:46:22 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
Check padding data all bit should be 1.
|
|
|
|
|
|
|
|
@param[in] Buffer - A pointer to buffer header
|
|
|
|
@param[in] BufferSize - Buffer size
|
|
|
|
|
|
|
|
@retval TRUE - The padding data is valid.
|
|
|
|
@retval TRUE - The padding data is invalid.
|
|
|
|
|
|
|
|
**/
|
|
|
|
BOOLEAN
|
|
|
|
CheckPaddingData (
|
|
|
|
IN UINT8 *Buffer,
|
|
|
|
IN UINT32 BufferSize
|
|
|
|
)
|
|
|
|
{
|
|
|
|
UINT32 index;
|
|
|
|
|
|
|
|
for (index = 0; index < BufferSize; index++) {
|
|
|
|
if (Buffer[index] != 0xFF) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Check the integrity of NvVarStore.
|
|
|
|
|
|
|
|
@param[in] NvVarStoreBase - A pointer to NvVarStore header
|
|
|
|
@param[in] NvVarStoreSize - NvVarStore size
|
|
|
|
|
|
|
|
@retval TRUE - The NvVarStore is valid.
|
|
|
|
@retval FALSE - The NvVarStore is invalid.
|
|
|
|
|
|
|
|
**/
|
|
|
|
BOOLEAN
|
|
|
|
EFIAPI
|
|
|
|
PlatformValidateNvVarStore (
|
|
|
|
IN UINT8 *NvVarStoreBase,
|
|
|
|
IN UINT32 NvVarStoreSize
|
|
|
|
)
|
|
|
|
{
|
|
|
|
UINT16 Checksum;
|
|
|
|
UINTN VariableBase;
|
|
|
|
UINT32 VariableOffset;
|
|
|
|
UINT32 VariableOffsetBeforeAlign;
|
|
|
|
EFI_FIRMWARE_VOLUME_HEADER *NvVarStoreFvHeader;
|
|
|
|
VARIABLE_STORE_HEADER *NvVarStoreHeader;
|
|
|
|
AUTHENTICATED_VARIABLE_HEADER *VariableHeader;
|
|
|
|
|
|
|
|
static EFI_GUID FvHdrGUID = EFI_SYSTEM_NV_DATA_FV_GUID;
|
|
|
|
static EFI_GUID VarStoreHdrGUID = EFI_AUTHENTICATED_VARIABLE_GUID;
|
|
|
|
|
|
|
|
VariableOffset = 0;
|
|
|
|
|
|
|
|
if (NvVarStoreBase == NULL) {
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore pointer is NULL.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Verify the header zerovetor, filesystemguid,
|
|
|
|
// revision, signature, attributes, fvlength, checksum
|
|
|
|
// HeaderLength cannot be an odd number
|
|
|
|
//
|
|
|
|
NvVarStoreFvHeader = (EFI_FIRMWARE_VOLUME_HEADER *)NvVarStoreBase;
|
|
|
|
|
|
|
|
if ((!IsZeroBuffer (NvVarStoreFvHeader->ZeroVector, 16)) ||
|
|
|
|
(!CompareGuid (&FvHdrGUID, &NvVarStoreFvHeader->FileSystemGuid)) ||
|
|
|
|
(NvVarStoreFvHeader->Signature != EFI_FVH_SIGNATURE) ||
|
|
|
|
(NvVarStoreFvHeader->Attributes != 0x4feff) ||
|
2022-11-27 08:00:14 +01:00
|
|
|
((NvVarStoreFvHeader->HeaderLength & 0x01) != 0) ||
|
2022-06-29 04:46:22 +02:00
|
|
|
(NvVarStoreFvHeader->Revision != EFI_FVH_REVISION) ||
|
|
|
|
(NvVarStoreFvHeader->FvLength != NvVarStoreSize)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore FV headers were invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Verify the header checksum
|
|
|
|
//
|
|
|
|
Checksum = CalculateSum16 ((VOID *)NvVarStoreFvHeader, NvVarStoreFvHeader->HeaderLength);
|
|
|
|
|
|
|
|
if (Checksum != 0) {
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore FV checksum was invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Verify the header signature, size, format, state
|
|
|
|
//
|
|
|
|
NvVarStoreHeader = (VARIABLE_STORE_HEADER *)(NvVarStoreBase + NvVarStoreFvHeader->HeaderLength);
|
|
|
|
if ((!CompareGuid (&VarStoreHdrGUID, &NvVarStoreHeader->Signature)) ||
|
|
|
|
(NvVarStoreHeader->Format != VARIABLE_STORE_FORMATTED) ||
|
|
|
|
(NvVarStoreHeader->State != VARIABLE_STORE_HEALTHY) ||
|
|
|
|
(NvVarStoreHeader->Size > (NvVarStoreFvHeader->FvLength - NvVarStoreFvHeader->HeaderLength)) ||
|
|
|
|
(NvVarStoreHeader->Size < sizeof (VARIABLE_STORE_HEADER))
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore header signature/size/format/state were invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Verify the header startId, state
|
|
|
|
// Verify data to the end
|
|
|
|
//
|
|
|
|
VariableBase = (UINTN)NvVarStoreBase + NvVarStoreFvHeader->HeaderLength + sizeof (VARIABLE_STORE_HEADER);
|
|
|
|
while (VariableOffset < (NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER))) {
|
|
|
|
VariableHeader = (AUTHENTICATED_VARIABLE_HEADER *)(VariableBase + VariableOffset);
|
|
|
|
if (VariableHeader->StartId != VARIABLE_DATA) {
|
|
|
|
if (!CheckPaddingData ((UINT8 *)VariableHeader, NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER) - VariableOffset)) {
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore variable header StartId was invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
VariableOffset = NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER);
|
|
|
|
} else {
|
OvmfPkg/PlatformInitLib: Fix integrity checking failed of NvVarStore
In the commit 4f173db8b4 "OvmfPkg/PlatformInitLib: Add functions for
EmuVariableNvStore", it introduced a PlatformValidateNvVarStore() function
for checking the integrity of NvVarStore.
In some cases when the VariableHeader->StartId is VARIABLE_DATA, the
VariableHeader->State is not just one of the four primary states:
VAR_IN_DELETED_TRANSITION, VAR_DELETED, VAR_HEADER_VALID_ONLY, VAR_ADDED.
The state may combined two or three states, e.g.
0x3C = (VAR_IN_DELETED_TRANSITION & VAR_ADDED) & VAR_DELETED
or
0x3D = VAR_ADDED & VAR_DELETED
When the variable store has those variables, system booting/rebooting will
hangs in a ASSERT:
NvVarStore Variable header State was invalid.
ASSERT
/mnt/working/source_code-git/edk2/OvmfPkg/Library/PlatformInitLib/Platform.c(819):
((BOOLEAN)(0==1))
Adding more log to UpdateVariable() and PlatformValidateNvVarStore(), we
saw some variables which have 0x3C or 0x3D state in store.
e.g.
UpdateVariable(), VariableName=BootOrder
L1871, State=0000003F <-- VAR_ADDED
State &= VAR_DELETED=0000003D
FlushHobVariableToFlash(), VariableName=BootOrder
...
UpdateVariable(), VariableName=InitialAttemptOrder
L1977, State=0000003F
State &= VAR_IN_DELETED_TRANSITION=0000003E
L2376, State=0000003E
State &= VAR_DELETED=0000003C
FlushHobVariableToFlash(), VariableName=InitialAttemptOrder
...
UpdateVariable(), VariableName=ConIn
L1977, State=0000003F
State &= VAR_IN_DELETED_TRANSITION=0000003E
L2376, State=0000003E
State &= VAR_DELETED=0000003C
FlushHobVariableToFlash(), VariableName=ConIn
...
So, only allowing the four primary states is not enough. This patch changes
the falid states list (Follow Jiewen Yao's suggestion):
1. VAR_HEADER_VALID_ONLY (0x7F)
- Header added (*)
2. VAR_ADDED (0x3F)
- Header + data added
3. VAR_ADDED & VAR_IN_DELETED_TRANSITION (0x3E)
- marked as deleted, but still valid, before new data is added. (*)
4. VAR_ADDED & VAR_IN_DELETED_TRANSITION & VAR_DELETED (0x3C)
- deleted, after new data is added.
5. VAR_ADDED & VAR_DELETED (0x3D)
- deleted directly, without new data.
(*) means to support surprise shutdown.
And removed (VAR_IN_DELETED_TRANSITION) and (VAR_DELETED) because they are
invalid states.
v2:
Follow Jiewen Yao's suggestion to add the following valid states:
VAR_ADDED & VAR_DELETED (0x3D)
VAR_ADDED & VAR_IN_DELETED_TRANSITION (0x3E)
VAR_ADDED & VAR_IN_DELETED_TRANSITION & VAR_DELETED (0x3C)
and removed the following invalid states:
VAR_IN_DELETED_TRANSITION
VAR_DELETED
Signed-off-by: Chun-Yi Lee <jlee@suse.com>
Reviewed-by: Jiewen Yao <jiewen.yao@intel.com>
2022-12-15 15:27:23 +01:00
|
|
|
if (!((VariableHeader->State == VAR_HEADER_VALID_ONLY) ||
|
|
|
|
(VariableHeader->State == VAR_ADDED) ||
|
|
|
|
(VariableHeader->State == (VAR_ADDED & VAR_DELETED)) ||
|
|
|
|
(VariableHeader->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION)) ||
|
|
|
|
(VariableHeader->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION & VAR_DELETED))))
|
2022-06-29 04:46:22 +02:00
|
|
|
{
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore Variable header State was invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
VariableOffset += sizeof (AUTHENTICATED_VARIABLE_HEADER) + VariableHeader->NameSize + VariableHeader->DataSize;
|
|
|
|
// Verify VariableOffset should be less than or equal NvVarStoreHeader->Size - sizeof(VARIABLE_STORE_HEADER)
|
|
|
|
if (VariableOffset > (NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER))) {
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore Variable header VariableOffset was invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
VariableOffsetBeforeAlign = VariableOffset;
|
|
|
|
// 4 byte align
|
|
|
|
VariableOffset = (VariableOffset + 3) & (UINTN)(~3);
|
|
|
|
|
|
|
|
if (!CheckPaddingData ((UINT8 *)(VariableBase + VariableOffsetBeforeAlign), VariableOffset - VariableOffsetBeforeAlign)) {
|
|
|
|
DEBUG ((DEBUG_ERROR, "NvVarStore Variable header PaddingData was invalid.\n"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Allocate storage for NV variables early on so it will be
|
|
|
|
at a consistent address. Since VM memory is preserved
|
|
|
|
across reboots, this allows the NV variable storage to survive
|
|
|
|
a VM reboot.
|
|
|
|
|
|
|
|
*
|
|
|
|
* @retval VOID* The pointer to the storage for NV Variables
|
|
|
|
*/
|
|
|
|
VOID *
|
|
|
|
EFIAPI
|
|
|
|
PlatformReserveEmuVariableNvStore (
|
|
|
|
VOID
|
|
|
|
)
|
|
|
|
{
|
|
|
|
VOID *VariableStore;
|
|
|
|
UINT32 VarStoreSize;
|
|
|
|
|
|
|
|
VarStoreSize = 2 * PcdGet32 (PcdFlashNvStorageFtwSpareSize);
|
|
|
|
//
|
|
|
|
// Allocate storage for NV variables early on so it will be
|
|
|
|
// at a consistent address. Since VM memory is preserved
|
|
|
|
// across reboots, this allows the NV variable storage to survive
|
|
|
|
// a VM reboot.
|
|
|
|
//
|
|
|
|
VariableStore =
|
|
|
|
AllocateRuntimePages (
|
|
|
|
EFI_SIZE_TO_PAGES (VarStoreSize)
|
|
|
|
);
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_INFO,
|
|
|
|
"Reserved variable store memory: 0x%p; size: %dkb\n",
|
|
|
|
VariableStore,
|
|
|
|
VarStoreSize / 1024
|
|
|
|
));
|
|
|
|
|
|
|
|
return VariableStore;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
When OVMF is lauched with -bios parameter, UEFI variables will be
|
|
|
|
partially emulated, and non-volatile variables may lose their contents
|
|
|
|
after a reboot. This makes the secure boot feature not working.
|
|
|
|
|
|
|
|
This function is used to initialize the EmuVariableNvStore
|
|
|
|
with the conent in PcdOvmfFlashNvStorageVariableBase.
|
|
|
|
|
|
|
|
@param[in] EmuVariableNvStore - A pointer to EmuVariableNvStore
|
|
|
|
|
|
|
|
@retval EFI_SUCCESS - Successfully init the EmuVariableNvStore
|
|
|
|
@retval Others - As the error code indicates
|
|
|
|
*/
|
|
|
|
EFI_STATUS
|
|
|
|
EFIAPI
|
|
|
|
PlatformInitEmuVariableNvStore (
|
|
|
|
IN VOID *EmuVariableNvStore
|
|
|
|
)
|
|
|
|
{
|
|
|
|
UINT8 *Base;
|
|
|
|
UINT32 Size;
|
|
|
|
UINT32 EmuVariableNvStoreSize;
|
|
|
|
|
|
|
|
EmuVariableNvStoreSize = 2 * PcdGet32 (PcdFlashNvStorageFtwSpareSize);
|
|
|
|
if ((EmuVariableNvStore == NULL) || (EmuVariableNvStoreSize == 0)) {
|
|
|
|
DEBUG ((DEBUG_ERROR, "Invalid EmuVariableNvStore parameter.\n"));
|
|
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
}
|
|
|
|
|
|
|
|
Base = (UINT8 *)(UINTN)PcdGet32 (PcdOvmfFlashNvStorageVariableBase);
|
|
|
|
Size = (UINT32)PcdGet32 (PcdFlashNvStorageVariableSize);
|
|
|
|
ASSERT (Size < EmuVariableNvStoreSize);
|
|
|
|
|
|
|
|
if (!PlatformValidateNvVarStore (Base, PcdGet32 (PcdCfvRawDataSize))) {
|
|
|
|
ASSERT (FALSE);
|
|
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG ((DEBUG_INFO, "Init EmuVariableNvStore with the content in FlashNvStorage\n"));
|
|
|
|
|
|
|
|
CopyMem (EmuVariableNvStore, Base, Size);
|
|
|
|
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
}
|