/** @file ACPI Debug feature driver implementation. Copyright (c) 2019, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include #define ACPI_DEBUG_STR "INTEL ACPI DEBUG" // // ASL NAME structure // #pragma pack(1) typedef struct { UINT8 NameOp; // Byte [0]=0x08:NameOp. UINT32 NameString; // Byte [4:1]=Name of object. UINT8 DWordPrefix; // Byte [5]=0x0C:DWord Prefix. UINT32 Value; // 0 ; Value of named object. } NAME_LAYOUT; #pragma pack() #pragma pack(1) typedef struct { UINT8 Signature[16]; // "INTEL ACPI DEBUG" UINT32 BufferSize; // Total size of Acpi Debug buffer including header structure UINT32 Head; // Current buffer pointer for SMM to print out UINT32 Tail; // Current buffer pointer for ASL to input UINT8 SmiTrigger; // Value to trigger the SMI via B2 port UINT8 Wrap; // If current Tail < Head UINT8 SmmVersion; // If SMM version UINT8 Truncate; // If the input from ASL > MAX_BUFFER_SIZE } ACPI_DEBUG_HEAD; #pragma pack() #define AD_SIZE sizeof (ACPI_DEBUG_HEAD) // This is 0x20 #define MAX_BUFFER_SIZE 32 UINT32 mBufferEnd = 0; ACPI_DEBUG_HEAD *mAcpiDebug = NULL; EFI_SMM_SYSTEM_TABLE2 *mSmst = NULL; /** Patch and load ACPI table. @param[in] AcpiDebugAddress Address of Acpi debug memory buffer. @param[in] BufferIndex Index that starts after the Acpi Debug head. @param[in] BufferEnd End of Acpi debug memory buffer. **/ VOID PatchAndLoadAcpiTable ( IN ACPI_DEBUG_HEAD *AcpiDebugAddress, IN UINT32 BufferIndex, IN UINT32 BufferEnd ) { EFI_STATUS Status; EFI_ACPI_TABLE_PROTOCOL *AcpiTable; UINTN Size; EFI_ACPI_DESCRIPTION_HEADER *TableHeader; UINTN TableKey; UINT8 *CurrPtr; UINT32 *Signature; NAME_LAYOUT *NamePtr; UINT8 UpdateCounter; Status = GetSectionFromFv ( &gEfiCallerIdGuid, EFI_SECTION_RAW, 0, (VOID **) &TableHeader, &Size ); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return; } // // This is Acpi Debug SSDT. Acpi Debug should be enabled if we reach here so load the table. // ASSERT (((EFI_ACPI_DESCRIPTION_HEADER *) TableHeader)->OemTableId == SIGNATURE_64 ('A', 'D', 'e', 'b', 'T', 'a', 'b', 'l')); // // Patch some pointers for the ASL code before loading the SSDT. // // // Count pointer updates, so we can stop after all three pointers are patched. // UpdateCounter = 1; for (CurrPtr = (UINT8 *) TableHeader; CurrPtr <= ((UINT8 *) TableHeader + TableHeader->Length) && UpdateCounter < 4; CurrPtr++) { Signature = (UINT32 *) (CurrPtr + 1); // // patch DPTR (address of Acpi debug memory buffer) // if ((*CurrPtr == AML_NAME_OP) && *Signature == SIGNATURE_32 ('D', 'P', 'T', 'R')) { NamePtr = (NAME_LAYOUT *) CurrPtr; NamePtr->Value = (UINT32) (UINTN) AcpiDebugAddress; UpdateCounter++; } // // patch EPTR (end of Acpi debug memory buffer) // if ((*CurrPtr == AML_NAME_OP) && *Signature == SIGNATURE_32 ('E', 'P', 'T', 'R')) { NamePtr = (NAME_LAYOUT *) CurrPtr; NamePtr->Value = BufferEnd; UpdateCounter++; } // // patch CPTR (used as an index that starts after the Acpi Debug head) // if ((*CurrPtr == AML_NAME_OP) && *Signature == SIGNATURE_32 ('C', 'P', 'T', 'R')) { NamePtr = (NAME_LAYOUT *) CurrPtr; NamePtr->Value = BufferIndex; UpdateCounter++; } } // // Add the table // Status = gBS->LocateProtocol (&gEfiAcpiTableProtocolGuid, NULL, (VOID **)&AcpiTable); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { goto Done; } TableKey = 0; Status = AcpiTable->InstallAcpiTable ( AcpiTable, TableHeader, Size, &TableKey ); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { goto Done; } Done: gBS->FreePool (TableHeader); return ; } /** Allocate Acpi Debug memory. @param[out] BufferSize Pointer to Acpi debug memory buffer size. @return Address of Acpi debug memory buffer. **/ EFI_PHYSICAL_ADDRESS AllocateAcpiDebugMemory ( OUT UINT32 *BufferSize ) { EFI_STATUS Status; EFI_PHYSICAL_ADDRESS AcpiDebugAddress; UINTN PagesNum; AcpiDebugAddress = 0; *BufferSize = 0; // // Reserve memory to store Acpi Debug data. // AcpiDebugAddress = 0xFFFFFFFF; PagesNum = EFI_SIZE_TO_PAGES (PcdGet32 (PcdAcpiDebugBufferSize)); Status = gBS->AllocatePages ( AllocateMaxAddress, EfiReservedMemoryType, PagesNum, &AcpiDebugAddress ); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return 0; } DEBUG ((DEBUG_INFO, "AcpiDebugAddress - 0x%08x\n", AcpiDebugAddress)); Status = PcdSet32S (PcdAcpiDebugAddress, (UINT32) AcpiDebugAddress); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { gBS->FreePages (AcpiDebugAddress, PagesNum); return 0; } *BufferSize = PcdGet32 (PcdAcpiDebugBufferSize); return AcpiDebugAddress; } /** Acpi Debug EndOfDxe notification. @param[in] Event Event whose notification function is being invoked. @param[in] Context Pointer to the notification function's context. **/ VOID EFIAPI AcpiDebugEndOfDxeNotification ( IN EFI_EVENT Event, IN VOID *Context ) { UINT32 BufferSize; UINT32 BufferIndex; mAcpiDebug = (ACPI_DEBUG_HEAD *) (UINTN) AllocateAcpiDebugMemory (&BufferSize); if (mAcpiDebug != NULL) { // // Init ACPI DEBUG buffer to lower case 'x'. // SetMem ((VOID *) mAcpiDebug, BufferSize, 0x78); // // Clear header of AD_SIZE bytes. // ZeroMem ((VOID *) mAcpiDebug, AD_SIZE); // // Write a signature to the first line of the buffer, "INTEL ACPI DEBUG". // CopyMem ((VOID *) mAcpiDebug, ACPI_DEBUG_STR, sizeof (ACPI_DEBUG_STR) - 1); BufferIndex = (UINT32) (UINTN) mAcpiDebug; mBufferEnd = BufferIndex + BufferSize; // // Leave the Index after the Acpi Debug head. // BufferIndex += AD_SIZE; // // Patch and Load the SSDT ACPI Tables. // PatchAndLoadAcpiTable (mAcpiDebug, BufferIndex, mBufferEnd); mAcpiDebug->Head = BufferIndex; mAcpiDebug->Tail = BufferIndex; mAcpiDebug->BufferSize = BufferSize; } // // Close event, so it will not be invoked again. // gBS->CloseEvent (Event); return ; } /** Initialize ACPI Debug. @param[in] ImageHandle The firmware allocated handle for the EFI image. @param[in] SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The driver initializes correctly. **/ EFI_STATUS EFIAPI InitializeAcpiDebugDxe ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_EVENT EndOfDxeEvent; if (!PcdGetBool (PcdAcpiDebugFeatureActive)) { return EFI_SUCCESS; } // // Register EndOfDxe notification // that point could ensure the Acpi Debug related PCDs initialized. // Status = gBS->CreateEventEx ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, AcpiDebugEndOfDxeNotification, NULL, &gEfiEndOfDxeEventGroupGuid, &EndOfDxeEvent ); ASSERT_EFI_ERROR (Status); return Status; } /** Software SMI callback for ACPI Debug which is called from ACPI method. @param[in] DispatchHandle The unique handle assigned to this handler by SmiHandlerRegister(). @param[in] Context Points to an optional handler context which was specified when the handler was registered. @param[in, out] CommBuffer A pointer to a collection of data in memory that will be conveyed from a non-SMM environment into an SMM environment. @param[in, out] CommBufferSize The size of the CommBuffer. @retval EFI_SUCCESS The interrupt was handled successfully. **/ EFI_STATUS EFIAPI AcpiDebugSmmCallback ( IN EFI_HANDLE DispatchHandle, IN CONST VOID *Context, IN OUT VOID *CommBuffer, IN OUT UINTN *CommBufferSize ) { UINT8 Buffer[MAX_BUFFER_SIZE]; // // Validate the fields in mAcpiDebug to ensure there is no harm to SMI handler. // mAcpiDebug is below 4GB and the start address of whole buffer. // if ((mAcpiDebug->BufferSize != (mBufferEnd - (UINT32) (UINTN) mAcpiDebug)) || (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + AD_SIZE)) || (mAcpiDebug->Head > mBufferEnd) || (mAcpiDebug->Tail < (UINT32) ((UINTN) mAcpiDebug + AD_SIZE)) || (mAcpiDebug->Tail > mBufferEnd)) { // // If some fields in mAcpiDebug are invaid, return directly. // return EFI_SUCCESS; } if (!(BOOLEAN)mAcpiDebug->Wrap && ((mAcpiDebug->Head >= (UINT32) ((UINTN) mAcpiDebug + AD_SIZE)) && (mAcpiDebug->Head < mAcpiDebug->Tail))){ // // If curent ----- buffer + 020 // ... // ... Head // ... Data for SMM print // ... Tail // ... Vacant for ASL input // ----- buffer end // // skip NULL block // while ((*(CHAR8 *) (UINTN) mAcpiDebug->Head == '\0') && (mAcpiDebug->Head < mAcpiDebug->Tail)) { mAcpiDebug->Head ++; } if (mAcpiDebug->Head < mAcpiDebug->Tail) { ZeroMem (Buffer, MAX_BUFFER_SIZE); AsciiStrnCpyS ((CHAR8 *) Buffer, MAX_BUFFER_SIZE, (CHAR8 *) (UINTN) mAcpiDebug->Head, MAX_BUFFER_SIZE - 1); DEBUG ((DEBUG_INFO | DEBUG_ERROR, "%a%a\n", Buffer, (BOOLEAN) mAcpiDebug->Truncate ? "..." : "")); mAcpiDebug->Head += MAX_BUFFER_SIZE; if (mAcpiDebug->Head >= (mAcpiDebug->Tail)) { // // When head == tail, we do nothing in handler. // mAcpiDebug->Head = mAcpiDebug->Tail; } } } else if ((BOOLEAN) mAcpiDebug->Wrap && ((mAcpiDebug->Head > mAcpiDebug->Tail) && (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize)))){ // // If curent ----- buffer + 020 // ... Tail // ... Vacant for ASL input // ... Head // ... Data for SMM print // ----- buffer end // while ((*(CHAR8 *) (UINTN) mAcpiDebug->Head == '\0') && (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize))) { mAcpiDebug->Head ++; } if (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize)){ ZeroMem (Buffer, MAX_BUFFER_SIZE); AsciiStrnCpyS ((CHAR8 *) Buffer, MAX_BUFFER_SIZE, (CHAR8 *) (UINTN) mAcpiDebug->Head, MAX_BUFFER_SIZE - 1); DEBUG ((DEBUG_INFO | DEBUG_ERROR, "%a%a\n", Buffer, (BOOLEAN) mAcpiDebug->Truncate ? "..." : "")); mAcpiDebug->Head += MAX_BUFFER_SIZE; if (mAcpiDebug->Head >= (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize)) { // // We met end of buffer. // mAcpiDebug->Wrap = 0; mAcpiDebug->Head = (UINT32) ((UINTN) mAcpiDebug + AD_SIZE); } } } return EFI_SUCCESS; } /** Acpi Debug SmmEndOfDxe notification. @param[in] Protocol Points to the protocol's unique identifier. @param[in] Interface Points to the interface instance. @param[in] Handle The handle on which the interface was installed. @retval EFI_SUCCESS Notification runs successfully. **/ EFI_STATUS EFIAPI AcpiDebugSmmEndOfDxeNotification ( IN CONST EFI_GUID *Protocol, IN VOID *Interface, IN EFI_HANDLE Handle ) { EFI_STATUS Status; EFI_SMM_SW_DISPATCH2_PROTOCOL *SwDispatch; EFI_SMM_SW_REGISTER_CONTEXT SwContext; EFI_HANDLE SwHandle; AcpiDebugEndOfDxeNotification (NULL, NULL); if (mAcpiDebug != NULL) { // // Get the Sw dispatch protocol and register SMI callback function. // SwDispatch = NULL; Status = mSmst->SmmLocateProtocol (&gEfiSmmSwDispatch2ProtocolGuid, NULL, (VOID **) &SwDispatch); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return Status; } SwContext.SwSmiInputValue = (UINTN) -1; Status = SwDispatch->Register (SwDispatch, AcpiDebugSmmCallback, &SwContext, &SwHandle); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return Status; } mAcpiDebug->SmiTrigger = (UINT8) SwContext.SwSmiInputValue; mAcpiDebug->SmmVersion = 1; } return EFI_SUCCESS; } /** Initialize ACPI Debug. @param[in] ImageHandle The firmware allocated handle for the EFI image. @param[in] SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The driver initializes correctly. **/ EFI_STATUS EFIAPI InitializeAcpiDebugSmm ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; VOID *Registration; EFI_SMM_BASE2_PROTOCOL *SmmBase2; BOOLEAN InSmm; if (!PcdGetBool (PcdAcpiDebugFeatureActive)) { return EFI_SUCCESS; } Status = gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **) &SmmBase2); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return Status; } Status = SmmBase2->InSmm (SmmBase2, &InSmm); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return Status; } ASSERT (InSmm); if (!InSmm) { return EFI_UNSUPPORTED; } Status = SmmBase2->GetSmstLocation (SmmBase2, &mSmst); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return Status; } // // Register SmmEndOfDxe notification // that point could ensure the Acpi Debug related PCDs initialized. // Registration = NULL; Status = mSmst->SmmRegisterProtocolNotify ( &gEfiSmmEndOfDxeProtocolGuid, AcpiDebugSmmEndOfDxeNotification, &Registration ); ASSERT_EFI_ERROR (Status); return Status; }