/** @file\r
OVMF ACPI QEMU support\r
\r
- Copyright (c) 2008 - 2012, Intel Corporation. All rights reserved.<BR>\r
- This program and the accompanying materials\r
- are licensed and made available under the terms and conditions of the BSD License\r
- which accompanies this distribution. The full text of the license may be found at\r
- http://opensource.org/licenses/bsd-license.php\r
+ Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.<BR>\r
\r
- THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
- WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+ Copyright (C) 2012-2014, Red Hat, Inc.\r
\r
-**/ \r
+ SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
\r
#include "AcpiPlatform.h"\r
#include <Library/BaseMemoryLib.h>\r
#include <Library/MemoryAllocationLib.h>\r
#include <Library/QemuFwCfgLib.h>\r
-\r
+#include <Library/DxeServicesTableLib.h>\r
+#include <Library/PcdLib.h>\r
+#include <Library/OrderedCollectionLib.h>\r
+#include <IndustryStandard/Acpi.h>\r
\r
BOOLEAN\r
QemuDetected (\r
}\r
\r
\r
+STATIC\r
+UINTN\r
+CountBits16 (\r
+ UINT16 Mask\r
+ )\r
+{\r
+ //\r
+ // For all N >= 1, N bits are enough to represent the number of bits set\r
+ // among N bits. It's true for N == 1. When adding a new bit (N := N+1),\r
+ // the maximum number of possibly set bits increases by one, while the\r
+ // representable maximum doubles.\r
+ //\r
+ Mask = ((Mask & 0xAAAA) >> 1) + (Mask & 0x5555);\r
+ Mask = ((Mask & 0xCCCC) >> 2) + (Mask & 0x3333);\r
+ Mask = ((Mask & 0xF0F0) >> 4) + (Mask & 0x0F0F);\r
+ Mask = ((Mask & 0xFF00) >> 8) + (Mask & 0x00FF);\r
+\r
+ return Mask;\r
+}\r
+\r
+\r
STATIC\r
EFI_STATUS\r
EFIAPI\r
OUT UINTN *TableKey\r
)\r
{\r
- EFI_STATUS Status;\r
- UINTN Count;\r
- UINTN Loop;\r
- EFI_ACPI_DESCRIPTION_HEADER *Hdr;\r
- UINTN NewBufferSize;\r
- EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC_STRUCTURE *LocalApic;\r
+ UINTN CpuCount;\r
+ UINTN PciLinkIsoCount;\r
+ UINTN NewBufferSize;\r
+ EFI_ACPI_1_0_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER *Madt;\r
+ EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC_STRUCTURE *LocalApic;\r
+ EFI_ACPI_1_0_IO_APIC_STRUCTURE *IoApic;\r
+ EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE_STRUCTURE *Iso;\r
+ EFI_ACPI_1_0_LOCAL_APIC_NMI_STRUCTURE *LocalApicNmi;\r
+ VOID *Ptr;\r
+ UINTN Loop;\r
+ EFI_STATUS Status;\r
+\r
+ ASSERT (AcpiTableBufferSize >= sizeof (EFI_ACPI_DESCRIPTION_HEADER));\r
\r
QemuFwCfgSelectItem (QemuFwCfgItemSmpCpuCount);\r
- Count = (UINTN) QemuFwCfgRead16 ();\r
- ASSERT (Count >= 1);\r
-\r
- if (Count == 1) {\r
- //\r
- // The pre-built MADT table covers the single CPU case\r
- //\r
- return InstallAcpiTable (\r
- AcpiProtocol,\r
- AcpiTableBuffer,\r
- AcpiTableBufferSize,\r
- TableKey\r
- );\r
+ CpuCount = QemuFwCfgRead16 ();\r
+ ASSERT (CpuCount >= 1);\r
+\r
+ //\r
+ // Set Level-tiggered, Active High for these identity mapped IRQs. The bitset\r
+ // corresponds to the union of all possible interrupt assignments for the LNKA,\r
+ // LNKB, LNKC, LNKD PCI interrupt lines. See the DSDT.\r
+ //\r
+ PciLinkIsoCount = CountBits16 (PcdGet16 (Pcd8259LegacyModeEdgeLevel));\r
+\r
+ NewBufferSize = 1 * sizeof (*Madt) +\r
+ CpuCount * sizeof (*LocalApic) +\r
+ 1 * sizeof (*IoApic) +\r
+ (1 + PciLinkIsoCount) * sizeof (*Iso) +\r
+ 1 * sizeof (*LocalApicNmi);\r
+\r
+ Madt = AllocatePool (NewBufferSize);\r
+ if (Madt == NULL) {\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+\r
+ CopyMem (&(Madt->Header), AcpiTableBuffer, sizeof (EFI_ACPI_DESCRIPTION_HEADER));\r
+ Madt->Header.Length = (UINT32) NewBufferSize;\r
+ Madt->LocalApicAddress = PcdGet32 (PcdCpuLocalApicBaseAddress);\r
+ Madt->Flags = EFI_ACPI_1_0_PCAT_COMPAT;\r
+ Ptr = Madt + 1;\r
+\r
+ LocalApic = Ptr;\r
+ for (Loop = 0; Loop < CpuCount; ++Loop) {\r
+ LocalApic->Type = EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC;\r
+ LocalApic->Length = sizeof (*LocalApic);\r
+ LocalApic->AcpiProcessorId = (UINT8) Loop;\r
+ LocalApic->ApicId = (UINT8) Loop;\r
+ LocalApic->Flags = 1; // enabled\r
+ ++LocalApic;\r
+ }\r
+ Ptr = LocalApic;\r
+\r
+ IoApic = Ptr;\r
+ IoApic->Type = EFI_ACPI_1_0_IO_APIC;\r
+ IoApic->Length = sizeof (*IoApic);\r
+ IoApic->IoApicId = (UINT8) CpuCount;\r
+ IoApic->Reserved = EFI_ACPI_RESERVED_BYTE;\r
+ IoApic->IoApicAddress = 0xFEC00000;\r
+ IoApic->SystemVectorBase = 0x00000000;\r
+ Ptr = IoApic + 1;\r
+\r
+ //\r
+ // IRQ0 (8254 Timer) => IRQ2 (PIC) Interrupt Source Override Structure\r
+ //\r
+ Iso = Ptr;\r
+ Iso->Type = EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE;\r
+ Iso->Length = sizeof (*Iso);\r
+ Iso->Bus = 0x00; // ISA\r
+ Iso->Source = 0x00; // IRQ0\r
+ Iso->GlobalSystemInterruptVector = 0x00000002;\r
+ Iso->Flags = 0x0000; // Conforms to specs of the bus\r
+ ++Iso;\r
+\r
+ //\r
+ // Set Level-tiggered, Active High for all possible PCI link targets.\r
+ //\r
+ for (Loop = 0; Loop < 16; ++Loop) {\r
+ if ((PcdGet16 (Pcd8259LegacyModeEdgeLevel) & (1 << Loop)) == 0) {\r
+ continue;\r
+ }\r
+ Iso->Type = EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE;\r
+ Iso->Length = sizeof (*Iso);\r
+ Iso->Bus = 0x00; // ISA\r
+ Iso->Source = (UINT8) Loop;\r
+ Iso->GlobalSystemInterruptVector = (UINT32) Loop;\r
+ Iso->Flags = 0x000D; // Level-tiggered, Active High\r
+ ++Iso;\r
}\r
+ ASSERT (\r
+ (UINTN) (Iso - (EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE_STRUCTURE *)Ptr) ==\r
+ 1 + PciLinkIsoCount\r
+ );\r
+ Ptr = Iso;\r
\r
+ LocalApicNmi = Ptr;\r
+ LocalApicNmi->Type = EFI_ACPI_1_0_LOCAL_APIC_NMI;\r
+ LocalApicNmi->Length = sizeof (*LocalApicNmi);\r
+ LocalApicNmi->AcpiProcessorId = 0xFF; // applies to all processors\r
//\r
- // We need to add additional Local APIC entries to the MADT\r
+ // polarity and trigger mode of the APIC I/O input signals conform to the\r
+ // specifications of the bus\r
//\r
- NewBufferSize = AcpiTableBufferSize + ((Count - 1) * sizeof (*LocalApic));\r
- Hdr = (EFI_ACPI_DESCRIPTION_HEADER*) AllocatePool (NewBufferSize);\r
- ASSERT (Hdr != NULL);\r
+ LocalApicNmi->Flags = 0x0000;\r
+ //\r
+ // Local APIC interrupt input LINTn to which NMI is connected.\r
+ //\r
+ LocalApicNmi->LocalApicInti = 0x01;\r
+ Ptr = LocalApicNmi + 1;\r
+\r
+ ASSERT ((UINTN) ((UINT8 *)Ptr - (UINT8 *)Madt) == NewBufferSize);\r
+ Status = InstallAcpiTable (AcpiProtocol, Madt, NewBufferSize, TableKey);\r
+\r
+ FreePool (Madt);\r
+\r
+ return Status;\r
+}\r
+\r
+\r
+#pragma pack(1)\r
+\r
+typedef struct {\r
+ UINT64 Base;\r
+ UINT64 End;\r
+ UINT64 Length;\r
+} PCI_WINDOW;\r
+\r
+typedef struct {\r
+ PCI_WINDOW PciWindow32;\r
+ PCI_WINDOW PciWindow64;\r
+} FIRMWARE_DATA;\r
+\r
+typedef struct {\r
+ UINT8 BytePrefix;\r
+ UINT8 ByteValue;\r
+} AML_BYTE;\r
+\r
+typedef struct {\r
+ UINT8 NameOp;\r
+ UINT8 RootChar;\r
+ UINT8 NameChar[4];\r
+ UINT8 PackageOp;\r
+ UINT8 PkgLength;\r
+ UINT8 NumElements;\r
+ AML_BYTE Pm1aCntSlpTyp;\r
+ AML_BYTE Pm1bCntSlpTyp;\r
+ AML_BYTE Reserved[2];\r
+} SYSTEM_STATE_PACKAGE;\r
+\r
+#pragma pack()\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+PopulateFwData(\r
+ OUT FIRMWARE_DATA *FwData\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ UINTN NumDesc;\r
+ EFI_GCD_MEMORY_SPACE_DESCRIPTOR *AllDesc;\r
+\r
+ Status = gDS->GetMemorySpaceMap (&NumDesc, &AllDesc);\r
+ if (Status == EFI_SUCCESS) {\r
+ UINT64 NonMmio32MaxExclTop;\r
+ UINT64 Mmio32MinBase;\r
+ UINT64 Mmio32MaxExclTop;\r
+ UINTN CurDesc;\r
+\r
+ Status = EFI_UNSUPPORTED;\r
+\r
+ NonMmio32MaxExclTop = 0;\r
+ Mmio32MinBase = BASE_4GB;\r
+ Mmio32MaxExclTop = 0;\r
+\r
+ for (CurDesc = 0; CurDesc < NumDesc; ++CurDesc) {\r
+ CONST EFI_GCD_MEMORY_SPACE_DESCRIPTOR *Desc;\r
+ UINT64 ExclTop;\r
+\r
+ Desc = &AllDesc[CurDesc];\r
+ ExclTop = Desc->BaseAddress + Desc->Length;\r
+\r
+ if (ExclTop <= (UINT64) PcdGet32 (PcdOvmfFdBaseAddress)) {\r
+ switch (Desc->GcdMemoryType) {\r
+ case EfiGcdMemoryTypeNonExistent:\r
+ break;\r
\r
- CopyMem (Hdr, AcpiTableBuffer, AcpiTableBufferSize);\r
+ case EfiGcdMemoryTypeReserved:\r
+ case EfiGcdMemoryTypeSystemMemory:\r
+ if (NonMmio32MaxExclTop < ExclTop) {\r
+ NonMmio32MaxExclTop = ExclTop;\r
+ }\r
+ break;\r
\r
- LocalApic = (EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC_STRUCTURE*)\r
- (((UINT8*) Hdr) + AcpiTableBufferSize);\r
+ case EfiGcdMemoryTypeMemoryMappedIo:\r
+ if (Mmio32MinBase > Desc->BaseAddress) {\r
+ Mmio32MinBase = Desc->BaseAddress;\r
+ }\r
+ if (Mmio32MaxExclTop < ExclTop) {\r
+ Mmio32MaxExclTop = ExclTop;\r
+ }\r
+ break;\r
+\r
+ default:\r
+ ASSERT(0);\r
+ }\r
+ }\r
+ }\r
+\r
+ if (Mmio32MinBase < NonMmio32MaxExclTop) {\r
+ Mmio32MinBase = NonMmio32MaxExclTop;\r
+ }\r
+\r
+ if (Mmio32MinBase < Mmio32MaxExclTop) {\r
+ FwData->PciWindow32.Base = Mmio32MinBase;\r
+ FwData->PciWindow32.End = Mmio32MaxExclTop - 1;\r
+ FwData->PciWindow32.Length = Mmio32MaxExclTop - Mmio32MinBase;\r
+\r
+ FwData->PciWindow64.Base = 0;\r
+ FwData->PciWindow64.End = 0;\r
+ FwData->PciWindow64.Length = 0;\r
+\r
+ Status = EFI_SUCCESS;\r
+ }\r
+\r
+ FreePool (AllDesc);\r
+ }\r
+\r
+ DEBUG ((\r
+ DEBUG_INFO,\r
+ "ACPI PciWindow32: Base=0x%08lx End=0x%08lx Length=0x%08lx\n",\r
+ FwData->PciWindow32.Base,\r
+ FwData->PciWindow32.End,\r
+ FwData->PciWindow32.Length\r
+ ));\r
+ DEBUG ((\r
+ DEBUG_INFO,\r
+ "ACPI PciWindow64: Base=0x%08lx End=0x%08lx Length=0x%08lx\n",\r
+ FwData->PciWindow64.Base,\r
+ FwData->PciWindow64.End,\r
+ FwData->PciWindow64.Length\r
+ ));\r
+\r
+ return Status;\r
+}\r
+\r
+\r
+STATIC\r
+VOID\r
+EFIAPI\r
+GetSuspendStates (\r
+ UINTN *SuspendToRamSize,\r
+ SYSTEM_STATE_PACKAGE *SuspendToRam,\r
+ UINTN *SuspendToDiskSize,\r
+ SYSTEM_STATE_PACKAGE *SuspendToDisk\r
+ )\r
+{\r
+ STATIC CONST SYSTEM_STATE_PACKAGE Template = {\r
+ 0x08, // NameOp\r
+ '\\', // RootChar\r
+ { '_', 'S', 'x', '_' }, // NameChar[4]\r
+ 0x12, // PackageOp\r
+ 0x0A, // PkgLength\r
+ 0x04, // NumElements\r
+ { 0x0A, 0x00 }, // Pm1aCntSlpTyp\r
+ { 0x0A, 0x00 }, // Pm1bCntSlpTyp -- we don't support it\r
+ { // Reserved[2]\r
+ { 0x0A, 0x00 },\r
+ { 0x0A, 0x00 }\r
+ }\r
+ };\r
+ RETURN_STATUS Status;\r
+ FIRMWARE_CONFIG_ITEM FwCfgItem;\r
+ UINTN FwCfgSize;\r
+ UINT8 SystemStates[6];\r
+\r
+ //\r
+ // configure defaults\r
+ //\r
+ *SuspendToRamSize = sizeof Template;\r
+ CopyMem (SuspendToRam, &Template, sizeof Template);\r
+ SuspendToRam->NameChar[2] = '3'; // S3\r
+ SuspendToRam->Pm1aCntSlpTyp.ByteValue = 1; // PIIX4: STR\r
+\r
+ *SuspendToDiskSize = sizeof Template;\r
+ CopyMem (SuspendToDisk, &Template, sizeof Template);\r
+ SuspendToDisk->NameChar[2] = '4'; // S4\r
+ SuspendToDisk->Pm1aCntSlpTyp.ByteValue = 2; // PIIX4: POSCL\r
\r
//\r
- // Add Local APIC entries for the APs to the MADT\r
+ // check for overrides\r
//\r
- for (Loop = 1; Loop < Count; Loop++) {\r
- LocalApic->Type = EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC;\r
- LocalApic->Length = sizeof (*LocalApic);\r
- LocalApic->AcpiProcessorId = Loop;\r
- LocalApic->ApicId = Loop;\r
- LocalApic->Flags = 1;\r
- LocalApic++;\r
+ Status = QemuFwCfgFindFile ("etc/system-states", &FwCfgItem, &FwCfgSize);\r
+ if (Status != RETURN_SUCCESS || FwCfgSize != sizeof SystemStates) {\r
+ DEBUG ((DEBUG_INFO, "ACPI using S3/S4 defaults\n"));\r
+ return;\r
+ }\r
+ QemuFwCfgSelectItem (FwCfgItem);\r
+ QemuFwCfgReadBytes (sizeof SystemStates, SystemStates);\r
+\r
+ //\r
+ // Each byte corresponds to a system state. In each byte, the MSB tells us\r
+ // whether the given state is enabled. If so, the three LSBs specify the\r
+ // value to be written to the PM control register's SUS_TYP bits.\r
+ //\r
+ if (SystemStates[3] & BIT7) {\r
+ SuspendToRam->Pm1aCntSlpTyp.ByteValue =\r
+ SystemStates[3] & (BIT2 | BIT1 | BIT0);\r
+ DEBUG ((DEBUG_INFO, "ACPI S3 value: %d\n",\r
+ SuspendToRam->Pm1aCntSlpTyp.ByteValue));\r
+ } else {\r
+ *SuspendToRamSize = 0;\r
+ DEBUG ((DEBUG_INFO, "ACPI S3 disabled\n"));\r
+ }\r
+\r
+ if (SystemStates[4] & BIT7) {\r
+ SuspendToDisk->Pm1aCntSlpTyp.ByteValue =\r
+ SystemStates[4] & (BIT2 | BIT1 | BIT0);\r
+ DEBUG ((DEBUG_INFO, "ACPI S4 value: %d\n",\r
+ SuspendToDisk->Pm1aCntSlpTyp.ByteValue));\r
+ } else {\r
+ *SuspendToDiskSize = 0;\r
+ DEBUG ((DEBUG_INFO, "ACPI S4 disabled\n"));\r
}\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+QemuInstallAcpiSsdtTable (\r
+ IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol,\r
+ IN VOID *AcpiTableBuffer,\r
+ IN UINTN AcpiTableBufferSize,\r
+ OUT UINTN *TableKey\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ FIRMWARE_DATA *FwData;\r
+\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+\r
+ FwData = AllocateReservedPool (sizeof (*FwData));\r
+ if (FwData != NULL) {\r
+ UINTN SuspendToRamSize;\r
+ SYSTEM_STATE_PACKAGE SuspendToRam;\r
+ UINTN SuspendToDiskSize;\r
+ SYSTEM_STATE_PACKAGE SuspendToDisk;\r
+ UINTN SsdtSize;\r
+ UINT8 *Ssdt;\r
\r
- Hdr->Length = NewBufferSize;\r
+ GetSuspendStates (&SuspendToRamSize, &SuspendToRam,\r
+ &SuspendToDiskSize, &SuspendToDisk);\r
+ SsdtSize = AcpiTableBufferSize + 17 + SuspendToRamSize + SuspendToDiskSize;\r
+ Ssdt = AllocatePool (SsdtSize);\r
\r
- Status = InstallAcpiTable (AcpiProtocol, Hdr, NewBufferSize, TableKey);\r
+ if (Ssdt != NULL) {\r
+ Status = PopulateFwData (FwData);\r
\r
- FreePool (Hdr);\r
+ if (Status == EFI_SUCCESS) {\r
+ UINT8 *SsdtPtr;\r
+\r
+ SsdtPtr = Ssdt;\r
+\r
+ CopyMem (SsdtPtr, AcpiTableBuffer, AcpiTableBufferSize);\r
+ SsdtPtr += AcpiTableBufferSize;\r
+\r
+ //\r
+ // build "OperationRegion(FWDT, SystemMemory, 0x12345678, 0x87654321)"\r
+ //\r
+ *(SsdtPtr++) = 0x5B; // ExtOpPrefix\r
+ *(SsdtPtr++) = 0x80; // OpRegionOp\r
+ *(SsdtPtr++) = 'F';\r
+ *(SsdtPtr++) = 'W';\r
+ *(SsdtPtr++) = 'D';\r
+ *(SsdtPtr++) = 'T';\r
+ *(SsdtPtr++) = 0x00; // SystemMemory\r
+ *(SsdtPtr++) = 0x0C; // DWordPrefix\r
+\r
+ //\r
+ // no virtual addressing yet, take the four least significant bytes\r
+ //\r
+ CopyMem(SsdtPtr, &FwData, 4);\r
+ SsdtPtr += 4;\r
+\r
+ *(SsdtPtr++) = 0x0C; // DWordPrefix\r
+\r
+ *(UINT32*) SsdtPtr = sizeof (*FwData);\r
+ SsdtPtr += 4;\r
+\r
+ //\r
+ // add suspend system states\r
+ //\r
+ CopyMem (SsdtPtr, &SuspendToRam, SuspendToRamSize);\r
+ SsdtPtr += SuspendToRamSize;\r
+ CopyMem (SsdtPtr, &SuspendToDisk, SuspendToDiskSize);\r
+ SsdtPtr += SuspendToDiskSize;\r
+\r
+ ASSERT((UINTN) (SsdtPtr - Ssdt) == SsdtSize);\r
+ ((EFI_ACPI_DESCRIPTION_HEADER *) Ssdt)->Length = (UINT32) SsdtSize;\r
+ Status = InstallAcpiTable (AcpiProtocol, Ssdt, SsdtSize, TableKey);\r
+ }\r
+\r
+ FreePool(Ssdt);\r
+ }\r
+\r
+ if (Status != EFI_SUCCESS) {\r
+ FreePool(FwData);\r
+ }\r
+ }\r
\r
return Status;\r
}\r
case EFI_ACPI_1_0_APIC_SIGNATURE:\r
TableInstallFunction = QemuInstallAcpiMadtTable;\r
break;\r
+ case EFI_ACPI_1_0_SECONDARY_SYSTEM_DESCRIPTION_TABLE_SIGNATURE:\r
+ TableInstallFunction = QemuInstallAcpiSsdtTable;\r
+ break;\r
default:\r
TableInstallFunction = InstallAcpiTable;\r
}\r
TableKey\r
);\r
}\r
-\r