alder_lake_bios/Intel/AlderLake/ClientOneSiliconPkg/IpBlock/Spi/RuntimeDxe/Spi.c

438 lines
15 KiB
C

/** @file
PCH SPI Runtime Driver implements the SPI Host Controller Compatibility Interface.
@copyright
INTEL CONFIDENTIAL
Copyright 2004 - 2020 Intel Corporation.
The source code contained or described herein and all documents related to the
source code ("Material") are owned by Intel Corporation or its suppliers or
licensors. Title to the Material remains with Intel Corporation or its suppliers
and licensors. The Material may contain trade secrets and proprietary and
confidential information of Intel Corporation and its suppliers and licensors,
and is protected by worldwide copyright and trade secret laws and treaty
provisions. No part of the Material may be used, copied, reproduced, modified,
published, uploaded, posted, transmitted, distributed, or disclosed in any way
without Intel's prior express written permission.
No license under any patent, copyright, trade secret or other intellectual
property right is granted to or conferred upon you by disclosure or delivery
of the Materials, either expressly, by implication, inducement, estoppel or
otherwise. Any license under such intellectual property rights must be
express and approved by Intel in writing.
Unless otherwise agreed by Intel in writing, you may not remove or alter
this notice or any other notice embedded in Materials by Intel or
Intel's suppliers or licensors in any way.
This file contains an 'Intel Peripheral Driver' and is 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.
@par Specification Reference:
**/
/**
@note: If the operating system contains a driver that accesses the SPI
controller, then it is not possible to guarantee that the OS SPI
driver does not attempt to access the SPI controller on a different
thread at the same time this Runtime DXE code attempts to access the
same controller. Therefore, if one uses this Runtime DXE
implementation on an OS that performs direct SPI access, one must
ensure the OS implements a semaphore that guarantees mutual
exclusion between direct SPI access and the UEFI runtime services.
For this reason and other security reasons, on general purpose
computing systems the SMM implementation of this SPI driver is
recommended over the Runtime DXE implementation.
**/
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiRuntimeLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseLib.h>
#include <Guid/EventGroup.h>
#include <Library/PciSegmentLib.h>
#include <Protocol/Spi.h>
#include <Library/SpiCommonLib.h>
#include <PchReservedResources.h>
#include <IndustryStandard/Pci30.h>
#include <Register/SpiRegs.h>
///
/// Global variables
///
GLOBAL_REMOVE_IF_UNREFERENCED SPI_INSTANCE *mSpiInstance;
///
/// PchSpiBar0PhysicalAddr keeps the reserved MMIO range assigned to SPI from PEI.
/// It won't be updated no matter the SPI MMIO is reallocated by BIOS PCI enum.
/// And it's used to override the SPI BAR0 register in runtime environment,
///
GLOBAL_REMOVE_IF_UNREFERENCED UINT32 mPchSpiBar0PhysicalAddr;
///
/// PchSpiBar0VirtualAddr keeps the virtual address of PchSpiBar0PhysicalAddr.
/// And it's used to provide the SPI BAR0 virtual address mapping to PchSpiBar0PhysicalAddr
/// in runtime environment. Bit address could be over 4G in 64bit OS.
///
GLOBAL_REMOVE_IF_UNREFERENCED UINTN mPchSpiBar0VirtualAddr;
//
// mPchSpiBar0SavedPhysicalAddr keeps the MMIO range assigned to SPI by PCI enumeration.
// This is used to restore the original value for SPI BAR0 after finishing
// commands to the SPI controller.
//
GLOBAL_REMOVE_IF_UNREFERENCED UINT32 mPchSpiBar0SavedPhysicalAddr;
//
// mPchSpiBar0RefCount stores the reference count for SPI BAR0.
//
GLOBAL_REMOVE_IF_UNREFERENCED UINT32 mPchSpiBar0RefCount;
//
// mPchSpiSavedPciCmdReg stores the PCI command register state at the start of the
// SPI transaction. This is used to restore the original PCI command register
// state after finishing commands to the SPI controller.
//
GLOBAL_REMOVE_IF_UNREFERENCED UINT8 mPchSpiSavedPciCmdReg;
//
// mSavedInterruptState stores the original processor interrupt state at the start of
// the SPI transaction. This is used to restore the original interrupt state after
// finishing commands to the SPI controller.
//
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN mSavedInterruptState;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN mRuntimeFlag;
//
// Function implementations
//
/**
Fixup internal data pointers so that the services can be called in virtual mode.
@param[in] Event The event registered.
@param[in] Context Event context. Not used in this event handler.
**/
VOID
EFIAPI
PchSpiVirtualAddressChangeEvent (
IN EFI_EVENT Event,
IN VOID *Context
)
{
mRuntimeFlag = TRUE;
gRT->ConvertPointer (0, (VOID *) &mPchSpiBar0VirtualAddr);
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashRead));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashWrite));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashErase));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashReadSfdp));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashReadJedecId));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashWriteStatus));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.FlashReadStatus));
gRT->ConvertPointer (0, (VOID *) &(mSpiInstance->SpiProtocol.ReadPchSoftStrap));
gRT->ConvertPointer (0, (VOID *) &mSpiInstance);
}
/**
<b>SPI Runtime DXE Module Entry Point</b>\n
- <b>Introduction</b>\n
The SPI Runtime DXE module provide a standard way for other modules to use the PCH SPI Interface in DXE/Runtime.
- @pre
- If SPI Runtime DXE driver is run before Status Code Runtime Protocol is installed
and there is the need to use Status code in the driver, it will be necessary
to add EFI_STATUS_CODE_RUNTIME_PROTOCOL_GUID to the dependency file.
- @result
The SPI Runtime DXE driver produces @link _PCH_SPI_PROTOCOL PCH_SPI_PROTOCOL @endlink
- <b>Integration Check List</b>\n
- This driver supports Descriptor Mode only.
- This driver supports Hardware Sequence only.
@param[in] ImageHandle Image handle of this driver.
@param[in] SystemTable Global system service table.
@retval EFI_SUCCESS Initialization complete.
@exception EFI_UNSUPPORTED The chipset is unsupported by this driver.
@retval EFI_OUT_OF_RESOURCES Do not have enough resources to initialize the driver.
@retval EFI_DEVICE_ERROR Device error, driver exits abnormally.
**/
EFI_STATUS
EFIAPI
InstallPchSpi (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINT64 BaseAddress;
UINT64 Length;
EFI_GCD_MEMORY_SPACE_DESCRIPTOR SpiBar0MemorySpaceDescriptor;
UINT64 Attributes;
EFI_EVENT AddressChangeEvent;
DEBUG ((DEBUG_INFO, "InstallPchSpi() Start\n"));
///
/// Allocate Runtime memory for the SPI protocol instance.
///
mSpiInstance = AllocateRuntimeZeroPool (sizeof (SPI_INSTANCE));
if (mSpiInstance == NULL) {
return EFI_OUT_OF_RESOURCES;
}
///
/// Initialize the SPI protocol instance
///
mPchSpiBar0PhysicalAddr = PCH_SPI_BASE_ADDRESS;
mPchSpiBar0VirtualAddr = mPchSpiBar0PhysicalAddr;
mPchSpiBar0SavedPhysicalAddr = 0;
mPchSpiBar0RefCount = 0;
mPchSpiSavedPciCmdReg = 0;
mSavedInterruptState = FALSE;
mRuntimeFlag = FALSE;
Status = SpiProtocolConstructor (mSpiInstance);
if (EFI_ERROR (Status)) {
return Status;
}
PciSegmentRegisterForRuntimeAccess (mSpiInstance->PchSpiBase);
///
/// Install the PCH_SPI_PROTOCOL interface
///
Status = gBS->InstallMultipleProtocolInterfaces (
&(mSpiInstance->Handle),
&gPchSpiProtocolGuid,
&(mSpiInstance->SpiProtocol),
NULL
);
if (EFI_ERROR (Status)) {
FreePool (mSpiInstance);
return EFI_DEVICE_ERROR;
}
///
/// Create Address Change event
///
///
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
PchSpiVirtualAddressChangeEvent,
NULL,
&gEfiEventVirtualAddressChangeGuid,
&AddressChangeEvent
);
ASSERT_EFI_ERROR (Status);
///
/// Set SPI space in GCD to be RUNTIME so that the range will be supported in
/// virtual address mode in EFI aware OS runtime.
/// It will assert if SPI Memory Space is not allocated
/// The caller is responsible for the existence and allocation of the SPi Memory Spaces
///
//
// SPI MMIO memory space
//
BaseAddress = (EFI_PHYSICAL_ADDRESS) mPchSpiBar0PhysicalAddr;
Length = 0x1000;
Status = gDS->GetMemorySpaceDescriptor (BaseAddress, &SpiBar0MemorySpaceDescriptor);
ASSERT_EFI_ERROR (Status);
Attributes = SpiBar0MemorySpaceDescriptor.Attributes | EFI_MEMORY_RUNTIME;
Status = gDS->SetMemorySpaceAttributes (
BaseAddress,
Length,
Attributes
);
ASSERT_EFI_ERROR (Status);
DEBUG ((DEBUG_INFO, "InstallPchSpi() End\n"));
return EFI_SUCCESS;
}
/**
Acquire PCH SPI MMIO address.
It is not expected for this BAR0 to change because the SPI device is usually
hidden from the OS. But if it is ever different from the preallocated address,
reassign it back. The SpiBar0 could be 64bit in virtual address.
@param[in] SpiInstance Pointer to SpiInstance to initialize
@retval PchSpiBar0 return SPI MMIO address
**/
UINTN
AcquireSpiBar0 (
IN SPI_INSTANCE *SpiInstance
)
{
UINT32 SpiBar0;
//
// Save original SPI physical MMIO address
//
SpiBar0 = PciSegmentRead32 (SpiInstance->PchSpiBase + R_SPI_CFG_BAR0) & ~(B_SPI_CFG_BAR0_MASK);
if (SpiBar0 != mPchSpiBar0PhysicalAddr) {
//
// Save interrupt state, PCI command register state,
// and SPI BAR0 value assigned by PCI enumeration
//
mSavedInterruptState = SaveAndDisableInterrupts ();
mPchSpiSavedPciCmdReg = PciSegmentRead8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET);
mPchSpiBar0SavedPhysicalAddr = SpiBar0;
//
// Temporary disable MSE, and override with SPI reserved MMIO address, then enable MSE.
//
PciSegmentAnd8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET, (UINT8) ~EFI_PCI_COMMAND_MEMORY_SPACE);
PciSegmentWrite32 (SpiInstance->PchSpiBase + R_SPI_CFG_BAR0, mPchSpiBar0PhysicalAddr);
PciSegmentOr8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE);
} else if (mPchSpiBar0RefCount == 0) {
mPchSpiBar0SavedPhysicalAddr = 0;
mPchSpiSavedPciCmdReg = 0;
mSavedInterruptState = FALSE;
}
mPchSpiBar0RefCount++;
if (mRuntimeFlag) {
return mPchSpiBar0VirtualAddr;
} else {
return mPchSpiBar0PhysicalAddr;
}
}
/**
Release PCH SPI MMIO address. If AcquireSpiBar0() previously overwrote the
value of BAR0, this function will restore the original value assigned by PCI
enumeration
@param[in] SpiInstance Pointer to SpiInstance to initialize
@retval None
**/
VOID
ReleaseSpiBar0 (
IN SPI_INSTANCE *SpiInstance
)
{
//
// Reference counting is used here because multiple nested calls to
// AcquireSpiBar0()/ReleaseSpiBar0() will cause SpiBar0 to be reprogrammed
// back to the original value before access to the SPI controller is done.
// Reference counting ensures that the BAR is not restored until after access
// is complete.
//
if (mPchSpiBar0RefCount <= 1) {
mPchSpiBar0RefCount = 0;
if (mPchSpiBar0SavedPhysicalAddr != 0) {
//
// Temporary disable MSE, restore the original SPI MMIO address, then
// restore PCI command register state
//
PciSegmentAnd8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET, (UINT8)~EFI_PCI_COMMAND_MEMORY_SPACE);
PciSegmentWrite32 (SpiInstance->PchSpiBase + R_SPI_CFG_BAR0, mPchSpiBar0SavedPhysicalAddr);
PciSegmentWrite8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET, mPchSpiSavedPciCmdReg);
//
// Clear saved state
//
mPchSpiBar0SavedPhysicalAddr = 0;
mPchSpiSavedPciCmdReg = 0;
//
// Restore interrupt state
//
SetInterruptState (mSavedInterruptState);
mSavedInterruptState = FALSE;
}
} else {
mPchSpiBar0RefCount--;
}
}
/**
This function is a hook for Spi to disable BIOS Write Protect
@retval EFI_SUCCESS The protocol instance was properly initialized
@retval EFI_ACCESS_DENIED The BIOS Region can only be updated in SMM phase
**/
EFI_STATUS
EFIAPI
DisableBiosWriteProtect (
VOID
)
{
UINT64 SpiBaseAddress;
SpiBaseAddress = mSpiInstance->PchSpiBase;
if ((PciSegmentRead8 (SpiBaseAddress + R_SPI_CFG_BC) & B_SPI_CFG_BC_EISS) != 0) {
return EFI_ACCESS_DENIED;
}
///
/// Enable the access to the BIOS space for both read and write cycles
///
PciSegmentOr8 (
SpiBaseAddress + R_SPI_CFG_BC,
(UINT8) (B_SPI_CFG_BC_WPD)
);
return EFI_SUCCESS;
}
/**
This function is a hook for Spi to enable BIOS Write Protect
**/
VOID
EFIAPI
EnableBiosWriteProtect (
VOID
)
{
UINT64 SpiBaseAddress;
SpiBaseAddress = mSpiInstance->PchSpiBase;
///
/// Disable the access to the BIOS space for write cycles
///
PciSegmentAnd8 (
SpiBaseAddress + R_SPI_CFG_BC,
(UINT8) (~B_SPI_CFG_BC_WPD)
);
}
/**
Check if it's granted to do flash write.
@retval TRUE It's secure to do flash write.
@retval FALSE It's not secure to do flash write.
**/
BOOLEAN
IsSpiFlashWriteGranted (
VOID
)
{
return TRUE;
}
/**
Check if a save and restore of the SPI controller state is necessary
@retval TRUE It's necessary to save and restore SPI controller state
@retval FALSE It's not necessary to save and restore SPI controller state
**/
BOOLEAN
IsSpiControllerSaveRestoreEnabled (
VOID
)
{
return mRuntimeFlag;
}