/** @file This is the PEIM that performs the S3 resume tasks. @copyright Copyright (c) 2008 - 2015 Intel Corporation. All rights reserved This software and associated documentation (if any) is furnished under a license and may only be used or copied in accordance with the terms of the license. Except as permitted by such license, no part of this software or documentation may be reproduced, stored in a retrieval system, or transmitted in any form or by any means without the express written consent of Intel Corporation. This file contains an 'Intel Peripheral Driver' and uniquely identified as "Intel Reference Module" and is licensed for Intel CPUs and chipsets under the terms of your license agreement with Intel or your vendor. This file may be modified by the user, subject to additional terms of the license agreement **/ #include "PchS3Peim.h" #include "S3SupportHob.h" // // GUID Definitions // EFI_GUID gS3SupportHobGuid = S3_SUPPORT_HOB_GUID; EFI_GUID gS3SupportSmramDataGuid = EFI_PCH_S3_SUPPORT_DATA_GUID; EFI_GUID gS3DataHobGuid = S3_DATA_HOB_GUID; static EFI_PEI_NOTIFY_DESCRIPTOR mSmmAccessCallbackList = { (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gPeiSmmAccessPpiGuid, CreateS3DataHob }; /** PCH S3 PEIM entry point. This Entry Point is entered for three separate reasons. 1. It is entered via dispatcher in standard POST in order to create a HOB for the DXE module to find it's EntryPoint. 2. It is entered via dispatcher in S3 Resume in order to find the Dispatch Script in SMRAM and copy it to a HOB in Boot Services memory. 3. It is entered in response to an invocation from the Boot Script Dispatch Opcode. In all three cases it is critical that this code is executed directly from Flash and not from a location in memory. @param[in] FfsHeader Header for FFS @param[in] PeiServices PEI Services table pointer @retval EFI_SUCCESS Successfully completed **/ EFI_STATUS EFIAPI InitializePchS3Peim ( IN EFI_FFS_FILE_HEADER *FfsHeader, IN EFI_PEI_SERVICES **PeiServices ) { EFI_STATUS Status; UINT32 ParameterSize; UINT32 TypeSize; UINT32 Size; EFI_PCH_S3_DISPATCH_ARRAY *DispatchArray; EFI_PCH_S3_DISPATCH_ITEM *DispatchItem; S3_SUPPORT_HOB *S3SupportHob; S3_DATA_HOB *GuidHob; PEI_SMM_ACCESS_PPI *SmmAccessPpi; DEBUG ((EFI_D_INFO, "InitializePchS3Peim() Start\n")); Status = EFI_SUCCESS; GuidHob = (S3_DATA_HOB *)GetFirstGuidHob (&gS3DataHobGuid); /// /// Search for the S3 Data Hob /// Not finding the Hob indicates that this is the initial pass through this code for any particular POST (S3, S4, S5 or other) /// if (GuidHob == NULL) { // // If we are entering the entry point for the first time on a specific boot (regardless of mode), // then we need to generate a HOB with the entry point information in order to pass the data to DXE // for entry into the Boot Script. // DEBUG ((EFI_D_INFO, "PCH S3 Data HOB didn't exist\n")); DEBUG ((EFI_D_INFO, "Attempt to create the PchS3Peim S3 Support Hob\n")); Status = (*PeiServices)->CreateHob ( PeiServices, EFI_HOB_TYPE_GUID_EXTENSION, sizeof (S3_SUPPORT_HOB), &S3SupportHob ); if (!EFI_ERROR (Status)) { S3SupportHob->Header.Name = gS3SupportHobGuid; S3SupportHob->PchS3PeimEntryPoint = (UINTN) _ModuleEntryPoint; DEBUG ((EFI_D_INFO, "PCH S3 Hob Created - %g\n", &gS3SupportHobGuid)); } DEBUG ((EFI_D_INFO, "Collect data from SMRAM for PCH S3 Data Script\n")); /// /// Creation of the S3 Data Hob is only needed on S3. However, the boot mode can't be trusted /// until after Memory Initialization has been completed because of RapidStart (FFS). /// As a result, we will check boot mode inside of the CreateS3DataHob function after the SmmAccessPpi /// is available. /// Status = PeiServicesLocatePpi ( &gPeiSmmAccessPpiGuid, 0, NULL, (VOID **)&SmmAccessPpi ); if (Status == EFI_SUCCESS) { /// /// Then this is our first call on S3 resume /// Find the PCH S3 Boot Script within SMRAM and create the HOB used for executing the Script /// Status = CreateS3DataHob (PeiServices, NULL, SmmAccessPpi); DEBUG ((EFI_D_INFO, "CreateS3DataHob, result=%r\n", Status)); } else { /// /// Register for notify if SMM_ACCESS isn't yet available. /// We can't have a module dependency on SMM_ACCESS because the module /// must be called on both Normal Boot and S3 resume. SMM_ACCESS /// PPI isn't published on Normal Boot, however. /// DEBUG ((EFI_D_INFO, "SMM Access protocol not yet available -> Register for notification later.\n")); // // Register notify to set default variable once variable service is ready. // Status = (**PeiServices).NotifyPpi (PeiServices, &mSmmAccessCallbackList); ASSERT_PEI_ERROR (PeiServices, Status); } return Status; } else { DEBUG ((EFI_D_INFO, "PchS3Peim S3 Data Hob located. Proceed with Dispatch.\n")); /// /// Setup the DispatchArray variable to point at the Hob /// DispatchArray = (EFI_PCH_S3_DISPATCH_ARRAY *)(VOID *)&GuidHob->S3DispatchDataArray; } DEBUG ((EFI_D_INFO, "Dispatch Array Located -> 0x%x\n", DispatchArray)); DEBUG ((EFI_D_INFO, "Dispatch Item Located (Current NextDispatchItem entry) -> 0x%x\n", DispatchArray->NextDispatchItem)); DispatchItem = (EFI_PCH_S3_DISPATCH_ITEM *)DispatchArray->NextDispatchItem; DEBUG ((EFI_D_INFO, "Dispatch Item Type -> 0x%x\n", DispatchItem->ItemType.Value)); /// /// Calculate the size required; /// ** Always round up to be 8 byte aligned as the script is initially created from 64-bit code in DXE /// TypeSize = QWORD_ALIGNED_SIZE (EFI_PCH_S3_DISPATCH_ITEM_TYPE); switch (DispatchItem->ItemType.Value) { case PchS3ItemTypeSendCodecCommand: ParameterSize = QWORD_ALIGNED_SIZE(EFI_PCH_S3_PARAMETER_SEND_CODEC_COMMAND); Status = PchS3SendCodecCommand ((EFI_PCH_S3_PARAMETER_SEND_CODEC_COMMAND *)&DispatchItem->Parameter); break; case PchS3ItemTypeInitPcieRootPortDownstream: ParameterSize = QWORD_ALIGNED_SIZE(EFI_PCH_S3_PARAMETER_INIT_PCIE_ROOT_PORT_DOWNSTREAM); Status = PchS3InitPcieRootPortDownstream ((EFI_PCH_S3_PARAMETER_INIT_PCIE_ROOT_PORT_DOWNSTREAM *)&DispatchItem->Parameter); if (Status == EFI_NOT_FOUND) { /// /// EFI_NOT_FOUND is not an error here /// Status = EFI_SUCCESS; } break; case PchS3ItemTypePcieSetPm: ParameterSize = QWORD_ALIGNED_SIZE(EFI_PCH_S3_PARAMETER_PCIE_SET_PM); Status = PchS3PcieSetPm ((EFI_PCH_S3_PARAMETER_PCIE_SET_PM *)&DispatchItem->Parameter); if (Status == EFI_NOT_FOUND) { /// /// EFI_NOT_FOUND is not an error here /// Status = EFI_SUCCESS; } break; case PchS3ItemTypeProgramIobp: ParameterSize = QWORD_ALIGNED_SIZE(EFI_PCH_S3_PARAMETER_PROG_IOBP); Status = PchS3ProgramIobp ((EFI_PCH_S3_PARAMETER_PROG_IOBP *)&DispatchItem->Parameter); break; default: ParameterSize = 0; DEBUG ((EFI_D_INFO, "Parameter not found\n")); ASSERT (FALSE); break; } /// /// Advance the Execution Position /// Size = ParameterSize + TypeSize; DispatchArray->NextDispatchItem += Size; if ((UINTN)DispatchArray->NextDispatchItem > (UINTN)((UINT8*)DispatchArray + DispatchArray->MaximumBufferSize)) { /// /// We are beyond end, wrap for the next S3 resume path /// DispatchArray->NextDispatchItem = (UINT8*)DispatchArray + QWORD_ALIGNED_SIZE(EFI_PCH_S3_DISPATCH_ARRAY); } DEBUG ((EFI_D_INFO, "InitializePchS3Peim() End\n")); return Status; } /** This routine is used to search SMRAM and get SmramCpuData point. @param[in] PeiServices - PEI services global pointer @param[in] SmmAccessPpi - SmmAccess PPI instance @retval SmramCpuData - The pointer of CPU information in SMRAM. **/ EFI_STATUS CreateS3DataHob ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *Interface ) { EFI_SMRAM_DESCRIPTOR *SmramRanges; UINTN SmramRangeCount; UINTN Size; EFI_STATUS Status; UINT32 Address; EFI_PCH_S3_DISPATCH_ARRAY *S3DispatchArray; PEI_SMM_ACCESS_PPI *SmmAccessPpi; BOOLEAN Found; UINTN Index; S3_DATA_HOB *GuidHob; EFI_BOOT_MODE BootMode; DEBUG ((EFI_D_INFO, "CreateS3DataHob() Start\n")); /// /// Now that we know we have a valid Boot Mode. /// Check it to see if we need to process the S3 Data Hob Request /// Status = (*PeiServices)->GetBootMode (PeiServices, &BootMode); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_INFO, "PchS3Peim S3 Boot Mode not available.\n")); return Status; } DEBUG ((EFI_D_INFO, "PchS3Peim S3 Boot Mode Check: %x\n", BootMode)); if (BootMode != BOOT_ON_S3_RESUME) { // // Only Create the S3 Data Hob on S3 resume // return Status; } SmmAccessPpi = (PEI_SMM_ACCESS_PPI *)Interface; Found = FALSE; S3DispatchArray = NULL; /// /// Open all SMM regions /// Index = 0; do { Status = SmmAccessPpi->Open (PeiServices, SmmAccessPpi, Index); Index++; } while (!EFI_ERROR (Status)); /// /// Get all SMRAM range /// Size = 0; Status = SmmAccessPpi->GetCapabilities (PeiServices, SmmAccessPpi, &Size, NULL); ASSERT (Status == EFI_BUFFER_TOO_SMALL); Status = PeiServicesAllocatePool ( Size, (VOID **)&SmramRanges ); ASSERT_EFI_ERROR (Status); Status = SmmAccessPpi->GetCapabilities (PeiServices, SmmAccessPpi, &Size, SmramRanges); ASSERT_EFI_ERROR (Status); Size /= sizeof (*SmramRanges); SmramRangeCount = Size; /// /// Assume TSEG is the last range of SMRAM in SmramRanges /// SmramRanges += SmramRangeCount - 1; /// /// Search SMRAM on page alignment for the SMM PCH S3 SMRAM Data signature /// for (Address = (UINT32)(SmramRanges->CpuStart + SmramRanges->PhysicalSize - EFI_PAGE_SIZE); Address >= (UINT32)SmramRanges->CpuStart; Address -= EFI_PAGE_SIZE ) { S3DispatchArray = (EFI_PCH_S3_DISPATCH_ARRAY *)(UINTN)Address; if (CompareGuid (&S3DispatchArray->PchS3CustomScriptGuid, &gS3SupportSmramDataGuid)) { /// /// Find it /// Found = TRUE; break; } } /// /// Close all SMM regions /// Index = 0; do { Status = SmmAccessPpi->Close (PeiServices, SmmAccessPpi, Index); Index++; } while (!EFI_ERROR (Status)); if (!Found) { DEBUG ((EFI_D_INFO, "S3 Dispatch Data Not Found!\n")); return EFI_NOT_FOUND; } /// /// Generate Hob from SMRAM Data /// Status = (*PeiServices)->CreateHob ( PeiServices, EFI_HOB_TYPE_GUID_EXTENSION, EFI_PAGE_SIZE, &GuidHob ); if (!EFI_ERROR (Status)) { DEBUG ((EFI_D_INFO, "PchS3Peim S3 Data Hob created: Address - 0x%x\n", (UINTN)GuidHob)); GuidHob->Header.Name = gS3DataHobGuid; CopyMem (&GuidHob->S3DispatchDataArray, S3DispatchArray, S3DispatchArray->MaximumBufferSize); /// /// Reset the NextDispatchItem to the beginning of the buffer for playback. /// S3DispatchArray = (EFI_PCH_S3_DISPATCH_ARRAY *)(UINTN)&GuidHob->S3DispatchDataArray; S3DispatchArray->NextDispatchItem = (UINT8 *)S3DispatchArray + QWORD_ALIGNED_SIZE(EFI_PCH_S3_DISPATCH_ARRAY); } DEBUG ((EFI_D_INFO, "CreateS3DataHob() End\n")); return EFI_SUCCESS; } #define AZALIA_MAX_LOOP_TIME 10 #define AZALIA_WAIT_PERIOD 100 /** Polling the Status bit @param[in] StatusReg The regsiter address to read the status @param[in] PollingBitMap The bit mapping for polling @param[in] PollingData The Data for polling @retval EFI_SUCCESS The function completed successfully @retval EFI_TIMEOUT Polling the bit map time out **/ static EFI_STATUS CodecStatusPolling ( IN UINT32 StatusReg, IN UINT16 PollingBitMap, IN UINT16 PollingData ) { UINT32 LoopTime; for (LoopTime = 0; LoopTime < AZALIA_MAX_LOOP_TIME; LoopTime++) { if ((MmioRead16 (StatusReg) & PollingBitMap) == PollingData) { break; } else { PchPmTimerStall (AZALIA_WAIT_PERIOD); } } if (LoopTime >= AZALIA_MAX_LOOP_TIME) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** Send Codec command on S3 resume @param[in] Parameter Parameters passed in from DXE @retval EFI_DEVICE_ERROR Device status error, operation failed @retval EFI_SUCCESS The function completed successfully **/ EFI_STATUS PchS3SendCodecCommand ( EFI_PCH_S3_PARAMETER_SEND_CODEC_COMMAND *Parameter ) { UINT32 HdaBar; UINT32 *CodecCommandData; EFI_STATUS Status; HdaBar = Parameter->HdaBar; CodecCommandData = &Parameter->CodecCmdData; DEBUG ((EFI_D_INFO, "Going to SendCodecCommand: %08x! \n", *CodecCommandData)); Status = CodecStatusPolling (HdaBar + R_HDA_IRS, (UINT16) B_HDA_IRS_ICB, (UINT16) 0); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "ICB bit is not zero before SendCodecCommand! \n")); return EFI_DEVICE_ERROR; } MmioWrite32 (HdaBar + R_HDA_IC, *CodecCommandData); MmioOr16 ((UINTN) (HdaBar + R_HDA_IRS), (UINT16) ((B_HDA_IRS_IRV | B_HDA_IRS_ICB))); Status = CodecStatusPolling (HdaBar + R_HDA_IRS, (UINT16) B_HDA_IRS_ICB, (UINT16) 0); if (EFI_ERROR (Status)) { MmioAnd16 ((UINTN) (HdaBar + R_HDA_IRS), (UINT16)~(B_HDA_IRS_ICB)); DEBUG ((EFI_D_ERROR, "SendCodecCommand: SendCodecCommand:%08x fail! \n")); return Status; } return EFI_SUCCESS; } /** Perform Init Root Port Downstream devices on S3 resume @param[in] Parameter Parameters passed in from DXE @retval EFI_STATUS **/ EFI_STATUS PchS3InitPcieRootPortDownstream ( EFI_PCH_S3_PARAMETER_INIT_PCIE_ROOT_PORT_DOWNSTREAM *Parameter ) { EFI_STATUS Status; Status = PchPcieInitRootPortDownstreamDevices ( Parameter->RootPortBus, Parameter->RootPortDevice, Parameter->RootPortFunc, Parameter->TempBusNumberMin, Parameter->TempBusNumberMax, NULL ); /// /// Not checking the error status here - downstream device not present does not /// mean an error of this root port. Our return status of EFI_SUCCESS means this /// port is enabled and outer function depends on this return status to do /// subsequent initializations. /// return Status; } /** Perform Root Port Downstream devices PCIE ASPM and LTR override on S3 resume @param[in] Parameter Parameters passed in from DXE @retval EFI_STATUS **/ EFI_STATUS PchS3PcieSetPm ( EFI_PCH_S3_PARAMETER_PCIE_SET_PM *Parameter ) { EFI_STATUS Status; PCH_PCIE_DEVICE_ASPM_OVERRIDE *DevAspmOverride; PCH_PCIE_DEVICE_LTR_OVERRIDE *DevLtrOverride; PCH_PCIE_PWR_OPT *S3PchPwrOptPcie; BOOLEAN L1SubstatesSupported; BOOLEAN LtrSupported; DevAspmOverride = (PCH_PCIE_DEVICE_ASPM_OVERRIDE *) (UINTN) Parameter->DevAspmOverrideAddr; DevLtrOverride = (PCH_PCIE_DEVICE_LTR_OVERRIDE *) (UINTN) Parameter->DevLtrOverrideAddr; S3PchPwrOptPcie = (PCH_PCIE_PWR_OPT *) (UINTN) Parameter->PchPwrOptPcie; Status = PcieSetPm ( Parameter->RootPortBus, Parameter->RootPortDevice, Parameter->RootPortFunc, Parameter->RootPortAspm, Parameter->NumOfDevAspmOverride, DevAspmOverride, Parameter->TempBusNumberMin, Parameter->TempBusNumberMax, Parameter->NumOfDevLtrOverride, DevLtrOverride, S3PchPwrOptPcie, &L1SubstatesSupported, Parameter->L1SubstatesConfig, Parameter->PolicyRevision, Parameter->FirstRPToSetPm, Parameter->L1SupportedInAllEnabledPorts, Parameter->ClkreqSupportedInAllEnabledPorts, &LtrSupported ); /// /// Not checking the error status here - downstream device not present does not /// mean an error of this root port. Our return status of EFI_SUCCESS means this /// port is enabled and outer function depends on this return status to do /// subsequent initializations. /// return Status; } /** Perform PCH IOBP programming on S3 resume @param[in] Parameter Parameters passed in from DXE @retval EFI_STATUS **/ EFI_STATUS PchS3ProgramIobp ( EFI_PCH_S3_PARAMETER_PROG_IOBP *Parameter ) { EFI_STATUS Status; Status = ProgramIobp ( Parameter->RootComplexBar, Parameter->Address, Parameter->AndMask, Parameter->OrMask ); return Status; }