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