| /** @file | |
| This module collects performance data for SMM driver boot records and S3 Suspend Performance Record. | |
| This module registers report status code listener to collect performance data | |
| for SMM driver boot records and S3 Suspend Performance Record. | |
| Caution: This module requires additional review when modified. | |
| This driver will have external input - communicate buffer in SMM mode. | |
| This external input must be validated carefully to avoid security issue like | |
| buffer overflow, integer overflow. | |
| FpdtSmiHandler() will receive untrusted input and do basic validation. | |
| Copyright (c) 2011 - 2016, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include <PiSmm.h> | |
| #include <Protocol/SmmReportStatusCodeHandler.h> | |
| #include <Guid/FirmwarePerformance.h> | |
| #include <Library/SmmServicesTableLib.h> | |
| #include <Library/BaseLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/TimerLib.h> | |
| #include <Library/LockBoxLib.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/SynchronizationLib.h> | |
| #include <Library/SmmMemLib.h> | |
| #define EXTENSION_RECORD_SIZE 0x1000 | |
| EFI_SMM_RSC_HANDLER_PROTOCOL *mRscHandlerProtocol = NULL; | |
| UINT64 mSuspendStartTime = 0; | |
| BOOLEAN mS3SuspendLockBoxSaved = FALSE; | |
| UINT32 mBootRecordSize = 0; | |
| UINT32 mBootRecordMaxSize = 0; | |
| UINT8 *mBootRecordBuffer = NULL; | |
| SPIN_LOCK mSmmFpdtLock; | |
| BOOLEAN mSmramIsOutOfResource = FALSE; | |
| /** | |
| Report status code listener for SMM. This is used to record the performance | |
| data for S3 Suspend Start and S3 Suspend End in FPDT. | |
| @param[in] CodeType Indicates the type of status code being reported. | |
| @param[in] Value Describes the current status of a hardware or software entity. | |
| This included information about the class and subclass that is used to | |
| classify the entity as well as an operation. | |
| @param[in] Instance The enumeration of a hardware or software entity within | |
| the system. Valid instance numbers start with 1. | |
| @param[in] CallerId This optional parameter may be used to identify the caller. | |
| This parameter allows the status code driver to apply different rules to | |
| different callers. | |
| @param[in] Data This optional parameter may be used to pass additional data. | |
| @retval EFI_SUCCESS Status code is what we expected. | |
| @retval EFI_UNSUPPORTED Status code not supported. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| FpdtStatusCodeListenerSmm ( | |
| IN EFI_STATUS_CODE_TYPE CodeType, | |
| IN EFI_STATUS_CODE_VALUE Value, | |
| IN UINT32 Instance, | |
| IN EFI_GUID *CallerId, | |
| IN EFI_STATUS_CODE_DATA *Data | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT64 CurrentTime; | |
| EFI_ACPI_5_0_FPDT_S3_SUSPEND_RECORD S3SuspendRecord; | |
| UINT8 *NewRecordBuffer; | |
| // | |
| // Check whether status code is what we are interested in. | |
| // | |
| if ((CodeType & EFI_STATUS_CODE_TYPE_MASK) != EFI_PROGRESS_CODE) { | |
| return EFI_UNSUPPORTED; | |
| } | |
| // | |
| // Collect one or more Boot records in boot time | |
| // | |
| if (Data != NULL && CompareGuid (&Data->Type, &gEfiFirmwarePerformanceGuid)) { | |
| AcquireSpinLock (&mSmmFpdtLock); | |
| if (mBootRecordSize + Data->Size > mBootRecordMaxSize) { | |
| // | |
| // Try to allocate big SMRAM data to store Boot record. | |
| // | |
| if (mSmramIsOutOfResource) { | |
| ReleaseSpinLock (&mSmmFpdtLock); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| NewRecordBuffer = ReallocatePool (mBootRecordSize, mBootRecordSize + Data->Size + EXTENSION_RECORD_SIZE, mBootRecordBuffer); | |
| if (NewRecordBuffer == NULL) { | |
| ReleaseSpinLock (&mSmmFpdtLock); | |
| mSmramIsOutOfResource = TRUE; | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| mBootRecordBuffer = NewRecordBuffer; | |
| mBootRecordMaxSize = mBootRecordSize + Data->Size + EXTENSION_RECORD_SIZE; | |
| } | |
| // | |
| // Save boot record into the temp memory space. | |
| // | |
| CopyMem (mBootRecordBuffer + mBootRecordSize, Data + 1, Data->Size); | |
| mBootRecordSize += Data->Size; | |
| ReleaseSpinLock (&mSmmFpdtLock); | |
| return EFI_SUCCESS; | |
| } | |
| if ((Value != PcdGet32 (PcdProgressCodeS3SuspendStart)) && | |
| (Value != PcdGet32 (PcdProgressCodeS3SuspendEnd))) { | |
| return EFI_UNSUPPORTED; | |
| } | |
| // | |
| // Retrieve current time. | |
| // | |
| CurrentTime = GetTimeInNanoSecond (GetPerformanceCounter ()); | |
| if (Value == PcdGet32 (PcdProgressCodeS3SuspendStart)) { | |
| // | |
| // S3 Suspend started, record the performance data and return. | |
| // | |
| mSuspendStartTime = CurrentTime; | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // We are going to S3 sleep, record S3 Suspend End performance data. | |
| // | |
| S3SuspendRecord.SuspendStart = mSuspendStartTime; | |
| S3SuspendRecord.SuspendEnd = CurrentTime; | |
| // | |
| // Save S3 suspend performance data to lock box, it will be used by Firmware Performance PEIM. | |
| // | |
| if (!mS3SuspendLockBoxSaved) { | |
| Status = SaveLockBox ( | |
| &gEfiFirmwarePerformanceGuid, | |
| &S3SuspendRecord, | |
| sizeof (EFI_ACPI_5_0_FPDT_S3_SUSPEND_RECORD) | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| mS3SuspendLockBoxSaved = TRUE; | |
| } else { | |
| Status = UpdateLockBox ( | |
| &gEfiFirmwarePerformanceGuid, | |
| 0, | |
| &S3SuspendRecord, | |
| sizeof (EFI_ACPI_5_0_FPDT_S3_SUSPEND_RECORD) | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Communication service SMI Handler entry. | |
| This SMI handler provides services for report SMM boot records. | |
| Caution: This function may receive untrusted input. | |
| Communicate buffer and buffer size are external input, so this function will do basic validation. | |
| @param[in] DispatchHandle The unique handle assigned to this handler by SmiHandlerRegister(). | |
| @param[in] RegisterContext 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 and quiesced. No other handlers | |
| should still be called. | |
| @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The interrupt has been quiesced but other handlers should | |
| still be called. | |
| @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The interrupt is still pending and other handlers should still | |
| be called. | |
| @retval EFI_INTERRUPT_PENDING The interrupt could not be quiesced. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| FpdtSmiHandler ( | |
| IN EFI_HANDLE DispatchHandle, | |
| IN CONST VOID *RegisterContext, | |
| IN OUT VOID *CommBuffer, | |
| IN OUT UINTN *CommBufferSize | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| SMM_BOOT_RECORD_COMMUNICATE *SmmCommData; | |
| UINTN BootRecordOffset; | |
| UINTN BootRecordSize; | |
| VOID *BootRecordData; | |
| UINTN TempCommBufferSize; | |
| // | |
| // If input is invalid, stop processing this SMI | |
| // | |
| if (CommBuffer == NULL || CommBufferSize == NULL) { | |
| return EFI_SUCCESS; | |
| } | |
| TempCommBufferSize = *CommBufferSize; | |
| if(TempCommBufferSize < sizeof (SMM_BOOT_RECORD_COMMUNICATE)) { | |
| return EFI_SUCCESS; | |
| } | |
| if (!SmmIsBufferOutsideSmmValid ((UINTN)CommBuffer, TempCommBufferSize)) { | |
| DEBUG ((EFI_D_ERROR, "FpdtSmiHandler: SMM communication data buffer in SMRAM or overflow!\n")); | |
| return EFI_SUCCESS; | |
| } | |
| SmmCommData = (SMM_BOOT_RECORD_COMMUNICATE*)CommBuffer; | |
| Status = EFI_SUCCESS; | |
| switch (SmmCommData->Function) { | |
| case SMM_FPDT_FUNCTION_GET_BOOT_RECORD_SIZE : | |
| SmmCommData->BootRecordSize = mBootRecordSize; | |
| break; | |
| case SMM_FPDT_FUNCTION_GET_BOOT_RECORD_DATA : | |
| Status = EFI_UNSUPPORTED; | |
| break; | |
| case SMM_FPDT_FUNCTION_GET_BOOT_RECORD_DATA_BY_OFFSET : | |
| BootRecordOffset = SmmCommData->BootRecordOffset; | |
| BootRecordData = SmmCommData->BootRecordData; | |
| BootRecordSize = SmmCommData->BootRecordSize; | |
| if (BootRecordData == NULL || BootRecordOffset >= mBootRecordSize) { | |
| Status = EFI_INVALID_PARAMETER; | |
| break; | |
| } | |
| // | |
| // Sanity check | |
| // | |
| if (BootRecordSize > mBootRecordSize - BootRecordOffset) { | |
| BootRecordSize = mBootRecordSize - BootRecordOffset; | |
| } | |
| SmmCommData->BootRecordSize = BootRecordSize; | |
| if (!SmmIsBufferOutsideSmmValid ((UINTN)BootRecordData, BootRecordSize)) { | |
| DEBUG ((EFI_D_ERROR, "FpdtSmiHandler: SMM Data buffer in SMRAM or overflow!\n")); | |
| Status = EFI_ACCESS_DENIED; | |
| break; | |
| } | |
| CopyMem ( | |
| (UINT8*)BootRecordData, | |
| mBootRecordBuffer + BootRecordOffset, | |
| BootRecordSize | |
| ); | |
| break; | |
| default: | |
| Status = EFI_UNSUPPORTED; | |
| } | |
| SmmCommData->ReturnStatus = Status; | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| The module Entry Point of the Firmware Performance Data Table SMM driver. | |
| @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 entry point is executed successfully. | |
| @retval Other Some error occurs when executing this entry point. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| FirmwarePerformanceSmmEntryPoint ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_HANDLE Handle; | |
| // | |
| // Initialize spin lock | |
| // | |
| InitializeSpinLock (&mSmmFpdtLock); | |
| // | |
| // Get SMM Report Status Code Handler Protocol. | |
| // | |
| Status = gSmst->SmmLocateProtocol ( | |
| &gEfiSmmRscHandlerProtocolGuid, | |
| NULL, | |
| (VOID **) &mRscHandlerProtocol | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| // | |
| // Register report status code listener for BootRecords and S3 Suspend Start and End. | |
| // | |
| Status = mRscHandlerProtocol->Register (FpdtStatusCodeListenerSmm); | |
| ASSERT_EFI_ERROR (Status); | |
| // | |
| // Register SMI handler. | |
| // | |
| Handle = NULL; | |
| Status = gSmst->SmiHandlerRegister (FpdtSmiHandler, &gEfiFirmwarePerformanceGuid, &Handle); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } |