| /** @file | |
| SMM SwDispatch2 Protocol. | |
| Copyright (c) 2021, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "PchSmiDispatchSmm.h" | |
| typedef struct { | |
| UINT8 EosBitOffset; | |
| UINT8 ApmBitOffset; | |
| UINT32 SmiEosAddr; | |
| UINT32 SmiApmStsAddr; | |
| } SMM_PCH_REGISTER; | |
| SMM_PCH_REGISTER mSmiPchReg; | |
| EFI_SMM_CPU_PROTOCOL *mSmmCpuProtocol; | |
| LIST_ENTRY mSmmSwDispatch2Queue = INITIALIZE_LIST_HEAD_VARIABLE (mSmmSwDispatch2Queue); | |
| /** | |
| Find SmmSwDispatch2Context by SwSmiInputValue. | |
| @param[in] SwSmiInputValue The value to identify the SmmSwDispatch2 context | |
| @return Pointer to EFI_SMM_SW_DISPATCH2_CONTEXT context | |
| **/ | |
| EFI_SMM_SW_DISPATCH2_CONTEXT * | |
| FindContextBySwSmiInputValue ( | |
| IN UINTN SwSmiInputValue | |
| ) | |
| { | |
| LIST_ENTRY *Node; | |
| EFI_SMM_SW_DISPATCH2_CONTEXT *Dispatch2Context; | |
| Node = mSmmSwDispatch2Queue.ForwardLink; | |
| for ( ; Node != &mSmmSwDispatch2Queue; Node = Node->ForwardLink) { | |
| Dispatch2Context = BASE_CR (Node, EFI_SMM_SW_DISPATCH2_CONTEXT, Link); | |
| if (Dispatch2Context->SwSmiInputValue == SwSmiInputValue) { | |
| return Dispatch2Context; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /** | |
| Find SmmSwDispatch2Context by DispatchHandle. | |
| @param DispatchHandle The handle to identify the SmmSwDispatch2 context | |
| @return Pointer to EFI_SMM_SW_DISPATCH2_CONTEXT context | |
| **/ | |
| EFI_SMM_SW_DISPATCH2_CONTEXT * | |
| FindContextByDispatchHandle ( | |
| IN EFI_HANDLE DispatchHandle | |
| ) | |
| { | |
| LIST_ENTRY *Node; | |
| EFI_SMM_SW_DISPATCH2_CONTEXT *Dispatch2Context; | |
| Node = mSmmSwDispatch2Queue.ForwardLink; | |
| for ( ; Node != &mSmmSwDispatch2Queue; Node = Node->ForwardLink) { | |
| Dispatch2Context = BASE_CR (Node, EFI_SMM_SW_DISPATCH2_CONTEXT, Link); | |
| if (Dispatch2Context->DispatchHandle == DispatchHandle) { | |
| return Dispatch2Context; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /** | |
| Dispatch registered SMM handlers | |
| @param DispatchHandle The unique handle assigned to this handler by SmiHandlerRegister(). | |
| @param RegisterContext Points to an optional handler context which was specified when the handler was registered. | |
| @param CommBuffer A pointer to a collection of data in memory that will | |
| be conveyed from a non-SMM environment into an SMM environment. | |
| @param CommBufferSize The size of the CommBuffer. | |
| @return Status Code | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| SmmSwDispatcher ( | |
| IN EFI_HANDLE DispatchHandle, | |
| IN CONST VOID *RegisterContext, | |
| IN OUT VOID *CommBuffer, | |
| IN OUT UINTN *CommBufferSize | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_SMM_SW_CONTEXT SwContext; | |
| UINTN Index; | |
| EFI_SMM_SW_DISPATCH2_CONTEXT *Context; | |
| EFI_SMM_HANDLER_ENTRY_POINT2 DispatchFunction; | |
| EFI_SMM_SW_REGISTER_CONTEXT DispatchContext; | |
| UINTN Size; | |
| EFI_SMM_SAVE_STATE_IO_INFO IoInfo; | |
| // | |
| // Construct new context | |
| // | |
| SwContext.SwSmiCpuIndex = 0; | |
| SwContext.CommandPort = IoRead8 (SMM_CONTROL_PORT); | |
| SwContext.DataPort = IoRead8 (SMM_DATA_PORT); | |
| // | |
| // Try to find which CPU trigger SWSMI | |
| // | |
| for (Index = 0; Index < gSmst->NumberOfCpus; Index++) { | |
| Status = mSmmCpuProtocol->ReadSaveState ( | |
| mSmmCpuProtocol, | |
| sizeof (IoInfo), | |
| EFI_SMM_SAVE_STATE_REGISTER_IO, | |
| Index, | |
| &IoInfo | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| continue; | |
| } | |
| if (IoInfo.IoPort == SMM_CONTROL_PORT) { | |
| // | |
| // Great! Find it. | |
| // | |
| SwContext.SwSmiCpuIndex = Index; | |
| DEBUG ((DEBUG_VERBOSE, "CPU index = 0x%x/0x%x\n", Index, gSmst->NumberOfCpus)); | |
| break; | |
| } | |
| } | |
| if (SwContext.CommandPort == 0) { | |
| DEBUG ((DEBUG_VERBOSE, "NOT SW SMI\n")); | |
| Status = EFI_SUCCESS; | |
| goto End; | |
| } | |
| // | |
| // Search context | |
| // | |
| Context = FindContextBySwSmiInputValue (SwContext.CommandPort); | |
| if (Context == NULL) { | |
| DEBUG ((DEBUG_INFO, "No handler for SMI value 0x%x\n", SwContext.CommandPort)); | |
| Status = EFI_SUCCESS; | |
| goto End; | |
| } | |
| DEBUG ((DEBUG_VERBOSE, "Prepare to call handler for 0x%x\n", SwContext.CommandPort)); | |
| // | |
| // Dispatch | |
| // | |
| DispatchContext.SwSmiInputValue = SwContext.CommandPort; | |
| Size = sizeof (SwContext); | |
| DispatchFunction = (EFI_SMM_HANDLER_ENTRY_POINT2)Context->DispatchFunction; | |
| Status = DispatchFunction (DispatchHandle, &DispatchContext, &SwContext, &Size); | |
| End: | |
| // | |
| // Clear SMI APM status | |
| // | |
| IoOr32 (mSmiPchReg.SmiApmStsAddr, 1 << mSmiPchReg.ApmBitOffset); | |
| // | |
| // Set EOS bit | |
| // | |
| IoOr32 (mSmiPchReg.SmiEosAddr, 1 << mSmiPchReg.EosBitOffset); | |
| return Status; | |
| } | |
| /** | |
| Check the SwSmiInputValue is already used | |
| @param[in] SwSmiInputValue To identify the SmmSwDispatch2 context | |
| @retval EFI_SUCCESS SwSmiInputValue could be used. | |
| @retval EFI_INVALID_PARAMETER SwSmiInputValue is already be used. | |
| **/ | |
| EFI_STATUS | |
| SmiInputValueCheck ( | |
| IN UINTN SwSmiInputValue | |
| ) | |
| { | |
| LIST_ENTRY *Node; | |
| EFI_SMM_SW_DISPATCH2_CONTEXT *Dispatch2Context; | |
| Node = mSmmSwDispatch2Queue.ForwardLink; | |
| for ( ; Node != &mSmmSwDispatch2Queue; Node = Node->ForwardLink) { | |
| Dispatch2Context = BASE_CR (Node, EFI_SMM_SW_DISPATCH2_CONTEXT, Link); | |
| if (Dispatch2Context->SwSmiInputValue == SwSmiInputValue) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| } | |
| 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 RegContext->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] RegContext 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 valid range. | |
| @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 | |
| SmmSwDispatch2Register ( | |
| IN CONST EFI_SMM_SW_DISPATCH2_PROTOCOL *This, | |
| IN EFI_SMM_HANDLER_ENTRY_POINT2 DispatchFunction, | |
| IN OUT EFI_SMM_SW_REGISTER_CONTEXT *RegContext, | |
| OUT EFI_HANDLE *DispatchHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN Index; | |
| EFI_SMM_SW_DISPATCH2_CONTEXT *Context; | |
| if (RegContext->SwSmiInputValue == (UINTN)-1) { | |
| // | |
| // If SwSmiInputValue is set to (UINTN) -1 then a unique value | |
| // will be assigned and returned in the structure. | |
| // | |
| Status = EFI_NOT_FOUND; | |
| for (Index = 1; Index < MAXIMUM_SWI_VALUE; Index++) { | |
| Status = SmiInputValueCheck (Index); | |
| if (!EFI_ERROR (Status)) { | |
| RegContext->SwSmiInputValue = Index; | |
| break; | |
| } | |
| } | |
| if (RegContext->SwSmiInputValue == (UINTN)-1) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| } | |
| if ((RegContext->SwSmiInputValue > MAXIMUM_SWI_VALUE) || (RegContext->SwSmiInputValue == 0)) { | |
| DEBUG ((DEBUG_ERROR, "ERROR: SMI value range (1 ~ 0x%x)\n", MAXIMUM_SWI_VALUE)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Register | |
| // | |
| Status = gSmst->SmmAllocatePool (EfiRuntimeServicesData, sizeof (*Context), (VOID **)&Context); | |
| ASSERT_EFI_ERROR (Status); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| *DispatchHandle = (EFI_HANDLE)Context; | |
| Context->Signature = SMI_SW_HANDLER_SIGNATURE; | |
| Context->SwSmiInputValue = RegContext->SwSmiInputValue; | |
| Context->DispatchFunction = (UINTN)DispatchFunction; | |
| Context->DispatchHandle = *DispatchHandle; | |
| InsertTailList (&mSmmSwDispatch2Queue, &Context->Link); | |
| return Status; | |
| } | |
| /** | |
| 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 | |
| SmmSwDispatch2UnRegister ( | |
| IN CONST EFI_SMM_SW_DISPATCH2_PROTOCOL *This, | |
| IN EFI_HANDLE DispatchHandle | |
| ) | |
| { | |
| EFI_SMM_SW_DISPATCH2_CONTEXT *Context; | |
| // | |
| // Unregister | |
| // | |
| Context = FindContextByDispatchHandle (DispatchHandle); | |
| ASSERT (Context != NULL); | |
| if (Context != NULL) { | |
| RemoveEntryList (&Context->Link); | |
| gSmst->SmmFreePool (Context); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| EFI_SMM_SW_DISPATCH2_PROTOCOL gSmmSwDispatch2 = { | |
| SmmSwDispatch2Register, | |
| SmmSwDispatch2UnRegister, | |
| MAXIMUM_SWI_VALUE | |
| }; | |
| /** | |
| Get specified SMI register based on given register ID | |
| @param[in] SmmRegister SMI related register array from bootloader | |
| @param[in] Id The register ID to get. | |
| @retval NULL The register is not found or the format is not expected. | |
| @return smi register | |
| **/ | |
| PLD_GENERIC_REGISTER * | |
| GetSmmCtrlRegById ( | |
| IN PLD_SMM_REGISTERS *SmmRegister, | |
| IN UINT32 Id | |
| ) | |
| { | |
| UINT32 Index; | |
| PLD_GENERIC_REGISTER *PldReg; | |
| PldReg = NULL; | |
| for (Index = 0; Index < SmmRegister->Count; Index++) { | |
| if (SmmRegister->Registers[Index].Id == Id) { | |
| PldReg = &SmmRegister->Registers[Index]; | |
| break; | |
| } | |
| } | |
| if (PldReg == NULL) { | |
| DEBUG ((DEBUG_INFO, "Register %d not found.\n", Id)); | |
| return NULL; | |
| } | |
| // | |
| // Checking the register if it is expected. | |
| // | |
| if ((PldReg->Address.AccessSize != EFI_ACPI_3_0_DWORD) || | |
| (PldReg->Address.Address == 0) || | |
| (PldReg->Address.RegisterBitWidth != 1) || | |
| (PldReg->Address.AddressSpaceId != EFI_ACPI_3_0_SYSTEM_IO) || | |
| (PldReg->Value != 1)) | |
| { | |
| DEBUG ((DEBUG_INFO, "Unexpected SMM register.\n")); | |
| DEBUG ((DEBUG_INFO, "AddressSpaceId= 0x%x\n", PldReg->Address.AddressSpaceId)); | |
| DEBUG ((DEBUG_INFO, "RegBitWidth = 0x%x\n", PldReg->Address.RegisterBitWidth)); | |
| DEBUG ((DEBUG_INFO, "RegBitOffset = 0x%x\n", PldReg->Address.RegisterBitOffset)); | |
| DEBUG ((DEBUG_INFO, "AccessSize = 0x%x\n", PldReg->Address.AccessSize)); | |
| DEBUG ((DEBUG_INFO, "Address = 0x%lx\n", PldReg->Address.Address)); | |
| return NULL; | |
| } | |
| return PldReg; | |
| } | |
| /** | |
| Entry Point for this driver. | |
| @param[in] ImageHandle Image handle of this driver. | |
| @param[in] SystemTable A Pointer to the EFI System Table. | |
| @retval EFI_SUCCESS The entry point is executed successfully. | |
| @retval other Some error occurred when executing this entry point. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| PchSmiDispatchEntryPoint ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_HANDLE DispatchHandle; | |
| EFI_HOB_GUID_TYPE *GuidHob; | |
| PLD_SMM_REGISTERS *SmmRegister; | |
| PLD_GENERIC_REGISTER *SmiEosReg; | |
| PLD_GENERIC_REGISTER *SmiApmStsReg; | |
| GuidHob = GetFirstGuidHob (&gSmmRegisterInfoGuid); | |
| if (GuidHob == NULL) { | |
| return EFI_UNSUPPORTED; | |
| } | |
| SmmRegister = (PLD_SMM_REGISTERS *)GET_GUID_HOB_DATA (GuidHob); | |
| SmiEosReg = GetSmmCtrlRegById (SmmRegister, REGISTER_ID_SMI_EOS); | |
| if (SmiEosReg == NULL) { | |
| DEBUG ((DEBUG_ERROR, "SMI EOS reg not found.\n")); | |
| return EFI_NOT_FOUND; | |
| } | |
| mSmiPchReg.SmiEosAddr = (UINT32)SmiEosReg->Address.Address; | |
| mSmiPchReg.EosBitOffset = SmiEosReg->Address.RegisterBitOffset; | |
| SmiApmStsReg = GetSmmCtrlRegById (SmmRegister, REGISTER_ID_SMI_APM_STS); | |
| if (SmiApmStsReg == NULL) { | |
| DEBUG ((DEBUG_ERROR, "SMI APM status reg not found.\n")); | |
| return EFI_NOT_FOUND; | |
| } | |
| mSmiPchReg.SmiApmStsAddr = (UINT32)SmiApmStsReg->Address.Address; | |
| mSmiPchReg.ApmBitOffset = SmiApmStsReg->Address.RegisterBitOffset; | |
| // | |
| // Locate PI SMM CPU protocol | |
| // | |
| Status = gSmst->SmmLocateProtocol (&gEfiSmmCpuProtocolGuid, NULL, (VOID **)&mSmmCpuProtocol); | |
| ASSERT_EFI_ERROR (Status); | |
| // | |
| // Register a SMM handler to handle subsequent SW SMIs. | |
| // | |
| Status = gSmst->SmiHandlerRegister ((EFI_MM_HANDLER_ENTRY_POINT)SmmSwDispatcher, NULL, &DispatchHandle); | |
| ASSERT_EFI_ERROR (Status); | |
| // | |
| // Publish PI SMM SwDispatch2 Protocol | |
| // | |
| ImageHandle = NULL; | |
| Status = gSmst->SmmInstallProtocolInterface ( | |
| &ImageHandle, | |
| &gEfiSmmSwDispatch2ProtocolGuid, | |
| EFI_NATIVE_INTERFACE, | |
| &gSmmSwDispatch2 | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } |