/** @file
|
File to contain all the hardware specific stuff for the Smm Sw dispatch protocol.
|
|
Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
**/
|
#include "PchSmmHelpers.h"
|
#include <Protocol/SmmCpu.h>
|
#include <Register/PchRegsLpc.h>
|
#include <Register/PmcRegs.h>
|
|
GLOBAL_REMOVE_IF_UNREFERENCED EFI_SMM_CPU_PROTOCOL *mSmmCpuProtocol;
|
|
STATIC LIST_ENTRY mSwSmiCallbackDataBase;
|
|
//
|
// "SWSMI" RECORD
|
// Linked list data structures
|
//
|
#define SW_SMI_RECORD_SIGNATURE SIGNATURE_32 ('S', 'W', 'S', 'M')
|
|
#define SW_SMI_RECORD_FROM_LINK(_record) CR (_record, SW_SMI_RECORD, Link, SW_SMI_RECORD_SIGNATURE)
|
|
typedef struct {
|
UINT32 Signature;
|
LIST_ENTRY Link;
|
EFI_SMM_SW_REGISTER_CONTEXT Context;
|
EFI_SMM_HANDLER_ENTRY_POINT2 Callback;
|
} SW_SMI_RECORD;
|
|
GLOBAL_REMOVE_IF_UNREFERENCED CONST PCH_SMM_SOURCE_DESC mSwSourceDesc = {
|
PCH_SMM_NO_FLAGS,
|
{
|
{
|
{
|
ACPI_ADDR_TYPE,
|
{R_ACPI_IO_SMI_EN}
|
},
|
S_ACPI_IO_SMI_EN,
|
N_ACPI_IO_SMI_EN_APMC
|
},
|
NULL_BIT_DESC_INITIALIZER
|
},
|
{
|
{
|
{
|
ACPI_ADDR_TYPE,
|
{R_ACPI_IO_SMI_STS}
|
},
|
S_ACPI_IO_SMI_STS,
|
N_ACPI_IO_SMI_STS_APM
|
}
|
},
|
{
|
{
|
ACPI_ADDR_TYPE,
|
{R_ACPI_IO_SMI_STS}
|
},
|
S_ACPI_IO_SMI_STS,
|
N_ACPI_IO_SMI_STS_APM
|
}
|
};
|
|
/**
|
Check the SwSmiInputValue to see if there is a duplicated one in the database
|
|
@param[in] SwSmiInputValue SwSmiInputValue
|
|
@retval EFI_SUCCESS There is no duplicated SwSmiInputValue
|
@retval EFI_INVALID_PARAMETER There is a duplicated SwSmiInputValue
|
**/
|
EFI_STATUS
|
SmiInputValueDuplicateCheck (
|
IN UINTN SwSmiInputValue
|
)
|
{
|
SW_SMI_RECORD *SwSmiRecord;
|
LIST_ENTRY *LinkInDb;
|
|
LinkInDb = GetFirstNode (&mSwSmiCallbackDataBase);
|
while (!IsNull (&mSwSmiCallbackDataBase, LinkInDb)) {
|
SwSmiRecord = SW_SMI_RECORD_FROM_LINK (LinkInDb);
|
if (SwSmiRecord->Context.SwSmiInputValue == SwSmiInputValue) {
|
return EFI_INVALID_PARAMETER;
|
}
|
LinkInDb = GetNextNode (&mSwSmiCallbackDataBase, &SwSmiRecord->Link);
|
}
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Register a child SMI source dispatch function for the specified software SMI.
|
|
This service registers a function (DispatchFunction) which will be called when the software
|
SMI source specified by RegisterContext->SwSmiCpuIndex is detected. On return,
|
DispatchHandle contains a unique handle which may be used later to unregister the function
|
using UnRegister().
|
|
@param[in] This Pointer to the EFI_SMM_SW_DISPATCH2_PROTOCOL instance.
|
@param[in] DispatchFunction Function to register for handler when the specified software
|
SMI is generated.
|
@param[in, out] RegisterContext Pointer to the dispatch function's context.
|
The caller fills this context in before calling
|
the register function to indicate to the register
|
function which Software SMI input value the
|
dispatch function should be invoked for.
|
@param[out] DispatchHandle Handle generated by the dispatcher to track the
|
function instance.
|
|
@retval EFI_SUCCESS The dispatch function has been successfully
|
registered and the SMI source has been enabled.
|
@retval EFI_DEVICE_ERROR The SW driver was unable to enable the SMI source.
|
@retval EFI_INVALID_PARAMETER RegisterContext is invalid. The SW SMI input value
|
is not within a valid range or is already in use.
|
@retval EFI_OUT_OF_RESOURCES There is not enough memory (system or SMM) to manage this
|
child.
|
@retval EFI_OUT_OF_RESOURCES A unique software SMI value could not be assigned
|
for this dispatch.
|
**/
|
EFI_STATUS
|
EFIAPI
|
PchSwSmiRegister (
|
IN EFI_SMM_SW_DISPATCH2_PROTOCOL *This,
|
IN EFI_SMM_HANDLER_ENTRY_POINT2 DispatchFunction,
|
IN EFI_SMM_SW_REGISTER_CONTEXT *DispatchContext,
|
OUT EFI_HANDLE *DispatchHandle
|
)
|
{
|
EFI_STATUS Status;
|
SW_SMI_RECORD *SwSmiRecord;
|
UINTN Index;
|
|
//
|
// Return access denied if the SmmReadyToLock event has been triggered
|
//
|
if (mReadyToLock == TRUE) {
|
DEBUG ((DEBUG_ERROR, "Register is not allowed if the SmmReadyToLock event has been triggered! \n"));
|
return EFI_ACCESS_DENIED;
|
}
|
|
//
|
// Find available SW SMI value if the input is -1
|
//
|
if (DispatchContext->SwSmiInputValue == (UINTN) -1) {
|
for (Index = 1; Index < MAXIMUM_SWI_VALUE; Index++) {
|
if (!EFI_ERROR (SmiInputValueDuplicateCheck (Index))) {
|
DispatchContext->SwSmiInputValue = Index;
|
break;
|
}
|
}
|
if (DispatchContext->SwSmiInputValue == (UINTN) -1) {
|
return EFI_OUT_OF_RESOURCES;
|
}
|
}
|
//
|
// Check if it's a valid SW SMI value.
|
// The value must not bigger than 0xFF.
|
// And the value must not be 0xFF sincie it's used for SmmControll protocol.
|
//
|
if (DispatchContext->SwSmiInputValue >= MAXIMUM_SWI_VALUE) {
|
return EFI_INVALID_PARAMETER;
|
}
|
|
if (EFI_ERROR (SmiInputValueDuplicateCheck (DispatchContext->SwSmiInputValue))) {
|
return EFI_INVALID_PARAMETER;
|
}
|
|
//
|
// Create database record and add to database
|
//
|
Status = gSmst->SmmAllocatePool (
|
EfiRuntimeServicesData,
|
sizeof (SW_SMI_RECORD),
|
(VOID **) &SwSmiRecord
|
);
|
if (EFI_ERROR (Status)) {
|
DEBUG ((DEBUG_ERROR, "Failed to allocate memory for SwSmiRecord! \n"));
|
return EFI_OUT_OF_RESOURCES;
|
}
|
//
|
// Gather information about the registration request
|
//
|
SwSmiRecord->Signature = SW_SMI_RECORD_SIGNATURE;
|
SwSmiRecord->Context.SwSmiInputValue = DispatchContext->SwSmiInputValue;
|
SwSmiRecord->Callback = DispatchFunction;
|
//
|
// Publish the S/W SMI numbers in Serial logs used for Debug build.
|
//
|
DEBUG ((DEBUG_INFO, "SW SMI NUM %x Sw Record at Address 0x%X\n", SwSmiRecord->Context.SwSmiInputValue, SwSmiRecord));
|
|
InsertTailList (&mSwSmiCallbackDataBase, &SwSmiRecord->Link);
|
|
//
|
// Child's handle will be the address linked list link in the record
|
//
|
*DispatchHandle = (EFI_HANDLE) (&SwSmiRecord->Link);
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Unregister a child SMI source dispatch function for the specified software SMI.
|
|
This service removes the handler associated with DispatchHandle so that it will no longer be
|
called in response to a software SMI.
|
|
@param[in] This Pointer to the EFI_SMM_SW_DISPATCH2_PROTOCOL instance.
|
@param[in] DispatchHandle Handle of dispatch function to deregister.
|
|
@retval EFI_SUCCESS The dispatch function has been successfully unregistered.
|
@retval EFI_INVALID_PARAMETER The DispatchHandle was not valid.
|
**/
|
EFI_STATUS
|
EFIAPI
|
PchSwSmiUnRegister (
|
IN CONST EFI_SMM_SW_DISPATCH2_PROTOCOL *This,
|
IN EFI_HANDLE DispatchHandle
|
)
|
{
|
EFI_STATUS Status;
|
SW_SMI_RECORD *RecordToDelete;
|
|
if (DispatchHandle == 0) {
|
return EFI_INVALID_PARAMETER;
|
}
|
|
//
|
// Return access denied if the SmmReadyToLock event has been triggered
|
//
|
if (mReadyToLock == TRUE) {
|
DEBUG ((DEBUG_ERROR, "UnRegister is not allowed if the SmmReadyToLock event has been triggered! \n"));
|
return EFI_ACCESS_DENIED;
|
}
|
|
RecordToDelete = SW_SMI_RECORD_FROM_LINK (DispatchHandle);
|
//
|
// Take the entry out of the linked list
|
//
|
if (RecordToDelete->Signature != SW_SMI_RECORD_SIGNATURE) {
|
return EFI_INVALID_PARAMETER;
|
}
|
|
RemoveEntryList (&RecordToDelete->Link);
|
ZeroMem (RecordToDelete, sizeof (SW_SMI_RECORD));
|
Status = gSmst->SmmFreePool (RecordToDelete);
|
ASSERT_EFI_ERROR (Status);
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Main entry point for an SMM handler dispatch or communicate-based callback.
|
|
@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 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
|
PchSwSmiDispatcher (
|
IN EFI_HANDLE DispatchHandle,
|
IN CONST VOID *Context,
|
IN OUT VOID *CommBuffer,
|
IN OUT UINTN *CommBufferSize
|
)
|
{
|
EFI_STATUS Status;
|
EFI_SMM_SAVE_STATE_IO_INFO SmiIoInfo;
|
UINTN CpuIndex;
|
SW_SMI_RECORD *SwSmiRecord;
|
LIST_ENTRY *LinkInDb;
|
EFI_SMM_SW_CONTEXT SwSmiCommBuffer;
|
UINTN SwSmiCommBufferSize;
|
|
SwSmiCommBufferSize = sizeof (EFI_SMM_SW_CONTEXT);
|
//
|
// The value in DataPort might not be accurate in multiple thread environment.
|
// There might be racing condition for R_PCH_IO_APM_STS port.
|
// Therefor, this is just for reference.
|
//
|
SwSmiCommBuffer.DataPort = IoRead8 (R_PCH_IO_APM_STS);
|
|
for (CpuIndex = 0; CpuIndex < gSmst->NumberOfCpus; CpuIndex++) {
|
Status = mSmmCpuProtocol->ReadSaveState (
|
mSmmCpuProtocol,
|
sizeof (EFI_SMM_SAVE_STATE_IO_INFO),
|
EFI_SMM_SAVE_STATE_REGISTER_IO,
|
CpuIndex,
|
&SmiIoInfo
|
);
|
//
|
// If this is not the SMI source, skip it.
|
//
|
if (EFI_ERROR (Status)) {
|
continue;
|
}
|
//
|
// If the IO address is not "BYTE" "WRITE" to "R_PCH_IO_APM_CNT (0xB2)", skip it.
|
//
|
if ((SmiIoInfo.IoPort != R_PCH_IO_APM_CNT) ||
|
(SmiIoInfo.IoType != EFI_SMM_SAVE_STATE_IO_TYPE_OUTPUT) ||
|
(SmiIoInfo.IoWidth != EFI_SMM_SAVE_STATE_IO_WIDTH_UINT8))
|
{
|
continue;
|
}
|
//
|
// If the IO data is used for SmmControl protocol, skip it.
|
//
|
if (SmiIoInfo.IoData == 0xFF) {
|
continue;
|
}
|
|
SwSmiCommBuffer.SwSmiCpuIndex = CpuIndex;
|
SwSmiCommBuffer.CommandPort = (UINT8) SmiIoInfo.IoData;
|
|
LinkInDb = GetFirstNode (&mSwSmiCallbackDataBase);
|
while (!IsNull (&mSwSmiCallbackDataBase, LinkInDb)) {
|
SwSmiRecord = SW_SMI_RECORD_FROM_LINK (LinkInDb);
|
if (SwSmiRecord->Context.SwSmiInputValue == SmiIoInfo.IoData) {
|
SwSmiRecord->Callback ((EFI_HANDLE) &SwSmiRecord->Link, &SwSmiRecord->Context, &SwSmiCommBuffer, &SwSmiCommBufferSize);
|
}
|
LinkInDb = GetNextNode (&mSwSmiCallbackDataBase, &SwSmiRecord->Link);
|
}
|
}
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Init required protocol for Pch Sw Dispatch protocol.
|
**/
|
VOID
|
PchSwDispatchInit (
|
VOID
|
)
|
{
|
EFI_STATUS Status;
|
EFI_HANDLE DispatchHandle;
|
DATABASE_RECORD Record;
|
|
//
|
// Locate PI SMM CPU protocol
|
//
|
Status = gSmst->SmmLocateProtocol (&gEfiSmmCpuProtocolGuid, NULL, (VOID **)&mSmmCpuProtocol);
|
ASSERT_EFI_ERROR (Status);
|
|
//
|
// Initialize SW SMI Callback DataBase
|
//
|
InitializeListHead (&mSwSmiCallbackDataBase);
|
|
//
|
// Insert SwSmi handler to PchSmmCore database
|
// There will always be one SwType record in PchSmmCore database
|
//
|
ZeroMem (&Record, sizeof (DATABASE_RECORD));
|
Record.Signature = DATABASE_RECORD_SIGNATURE;
|
Record.Callback = PchSwSmiDispatcher;
|
Record.ProtocolType = SwType;
|
|
CopyMem (&Record.SrcDesc, &mSwSourceDesc, sizeof (PCH_SMM_SOURCE_DESC));
|
|
DispatchHandle = NULL;
|
Status = SmmCoreInsertRecord (
|
&Record,
|
&DispatchHandle
|
);
|
ASSERT_EFI_ERROR (Status);
|
}
|