/** @file This driver is responsible for the registration of child drivers and the abstraction of the QNC SMI sources. Copyright (c) 2013-2017 Intel Corporation. SPDX-License-Identifier: BSD-2-Clause-Patent **/ // // Include common header file for this module. // #include "CommonHeader.h" #include "QNCSmm.h" #include "QNCSmmHelpers.h" // // ///////////////////////////////////////////////////////////////////////////// // MODULE / GLOBAL DATA // // Module variables used by the both the main dispatcher and the source dispatchers // Declared in QNCSmmSources.h // UINT32 mPciData; UINT32 mPciAddress; PRIVATE_DATA mPrivateData = { // for the structure { NULL }, // CallbackDataBase linked list head NULL, // Handler returned whan calling SmiHandlerRegister NULL, // EFI handle returned when calling InstallMultipleProtocolInterfaces { // protocol arrays // elements within the array // { PROTOCOL_SIGNATURE, SxType, &gEfiSmmSxDispatch2ProtocolGuid, { { (QNC_SMM_GENERIC_REGISTER) QNCSmmCoreRegister, (QNC_SMM_GENERIC_UNREGISTER) QNCSmmCoreUnRegister } } }, { PROTOCOL_SIGNATURE, SwType, &gEfiSmmSwDispatch2ProtocolGuid, { { (QNC_SMM_GENERIC_REGISTER) QNCSmmCoreRegister, (QNC_SMM_GENERIC_UNREGISTER) QNCSmmCoreUnRegister, (UINTN) MAXIMUM_SWI_VALUE } } }, { PROTOCOL_SIGNATURE, GpiType, &gEfiSmmGpiDispatch2ProtocolGuid, { { (QNC_SMM_GENERIC_REGISTER) QNCSmmCoreRegister, (QNC_SMM_GENERIC_UNREGISTER) QNCSmmCoreUnRegister, (UINTN) 1 } } }, { PROTOCOL_SIGNATURE, QNCnType, &gEfiSmmIchnDispatch2ProtocolGuid, { { (QNC_SMM_GENERIC_REGISTER) QNCSmmCoreRegister, (QNC_SMM_GENERIC_UNREGISTER) QNCSmmCoreUnRegister } } }, { PROTOCOL_SIGNATURE, PowerButtonType, &gEfiSmmPowerButtonDispatch2ProtocolGuid, { { (QNC_SMM_GENERIC_REGISTER) QNCSmmCoreRegister, (QNC_SMM_GENERIC_UNREGISTER) QNCSmmCoreUnRegister } } }, { PROTOCOL_SIGNATURE, PeriodicTimerType, &gEfiSmmPeriodicTimerDispatch2ProtocolGuid, { { (QNC_SMM_GENERIC_REGISTER) QNCSmmCoreRegister, (QNC_SMM_GENERIC_UNREGISTER) QNCSmmCoreUnRegister, (UINTN) QNCSmmPeriodicTimerDispatchGetNextShorterInterval } } }, } }; CONTEXT_FUNCTIONS mContextFunctions[NUM_PROTOCOLS] = { { SxGetContext, SxCmpContext, NULL }, { SwGetContext, SwCmpContext, SwGetBuffer }, { NULL, NULL, NULL }, { NULL, NULL, NULL }, { NULL, NULL, NULL }, { PeriodicTimerGetContext, PeriodicTimerCmpContext, PeriodicTimerGetBuffer, }, }; // // ///////////////////////////////////////////////////////////////////////////// // PROTOTYPES // // Functions use only in this file // EFI_STATUS QNCSmmCoreDispatcher ( IN EFI_HANDLE DispatchHandle, IN CONST VOID *Context, OPTIONAL IN OUT VOID *CommBuffer, OPTIONAL IN OUT UINTN *CommBufferSize OPTIONAL ); UINTN DevicePathSize ( IN EFI_DEVICE_PATH_PROTOCOL *DevicePath ); // // ///////////////////////////////////////////////////////////////////////////// // FUNCTIONS // // Driver entry point // EFI_STATUS EFIAPI InitializeQNCSmmDispatcher ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) /*++ Routine Description: Initializes the QNC SMM Dispatcher Arguments: ImageHandle - Pointer to the loaded image protocol for this driver SystemTable - Pointer to the EFI System Table Returns: Status - EFI_SUCCESS --*/ { EFI_STATUS Status; QNCSmmPublishDispatchProtocols (); // // Register a callback function to handle subsequent SMIs. This callback // will be called by SmmCoreDispatcher. // Status = gSmst->SmiHandlerRegister (QNCSmmCoreDispatcher, NULL, &mPrivateData.SmiHandle); ASSERT_EFI_ERROR (Status); // // Initialize Callback DataBase // InitializeListHead (&mPrivateData.CallbackDataBase); // // Enable SMIs on the QNC now that we have a callback // QNCSmmInitHardware (); return EFI_SUCCESS; } EFI_STATUS SaveState ( VOID ) /*++ Routine Description: Save Index registers to avoid corrupting the foreground environment Arguments: None Returns: Status - EFI_SUCCESS --*/ { mPciAddress = IoRead32 (EFI_PCI_ADDRESS_PORT); return EFI_SUCCESS; } EFI_STATUS RestoreState ( VOID ) /*++ Routine Description: Restore Index registers to avoid corrupting the foreground environment Arguments: None Returns: Status - EFI_SUCCESS --*/ { IoWrite32 (EFI_PCI_ADDRESS_PORT, mPciAddress); return EFI_SUCCESS; } EFI_STATUS SmiInputValueDuplicateCheck ( UINTN FedSwSmiInputValue ) /*++ Routine Description: Check the Fed SwSmiInputValue to see if there is a duplicated one in the database Arguments: None Returns: Status - EFI_SUCCESS, EFI_INVALID_PARAMETER --*/ // GC_TODO: FedSwSmiInputValue - add argument and description to function comment { DATABASE_RECORD *RecordInDb; LIST_ENTRY *LinkInDb; LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase); while (!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) { RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb); if (RecordInDb->ProtocolType == SwType) { if (RecordInDb->ChildContext.Sw.SwSmiInputValue == FedSwSmiInputValue) { return EFI_INVALID_PARAMETER; } } LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, &RecordInDb->Link); } return EFI_SUCCESS; } EFI_STATUS QNCSmmCoreRegister ( IN QNC_SMM_GENERIC_PROTOCOL *This, IN EFI_SMM_HANDLER_ENTRY_POINT2 DispatchFunction, IN QNC_SMM_CONTEXT *RegisterContext, OUT EFI_HANDLE *DispatchHandle ) /*++ Routine Description: Arguments: Returns: --*/ // GC_TODO: This - add argument and description to function comment // GC_TODO: DispatchFunction - add argument and description to function comment // GC_TODO: RegisterContext - add argument and description to function comment // GC_TODO: DispatchHandle - add argument and description to function comment // GC_TODO: EFI_OUT_OF_RESOURCES - add return value to function comment // GC_TODO: EFI_INVALID_PARAMETER - add return value to function comment // GC_TODO: EFI_SUCCESS - add return value to function comment // GC_TODO: EFI_INVALID_PARAMETER - add return value to function comment { EFI_STATUS Status; DATABASE_RECORD *Record; QNC_SMM_QUALIFIED_PROTOCOL *Qualified; INTN Index; // // Check for invalid parameter // if (This == NULL || RegisterContext == NULL || DispatchHandle == NULL) { return EFI_INVALID_PARAMETER; } // // Create database record and add to database // Record = (DATABASE_RECORD *) AllocateZeroPool (sizeof (DATABASE_RECORD)); if (Record == NULL) { return EFI_OUT_OF_RESOURCES; } // // Gather information about the registration request // Record->Callback = DispatchFunction; Record->CallbackContext = RegisterContext; CopyMem (&Record->ChildContext, RegisterContext, sizeof (QNC_SMM_CONTEXT)); Qualified = QUALIFIED_PROTOCOL_FROM_GENERIC (This); Record->ProtocolType = Qualified->Type; CopyMem (&Record->ContextFunctions, &mContextFunctions[Qualified->Type], sizeof (Record->ContextFunctions)); // // Perform linked list housekeeping // Record->Signature = DATABASE_RECORD_SIGNATURE; switch (Qualified->Type) { // // By the end of this switch statement, we'll know the // source description the child is registering for // case SxType: // // Check the validity of Context Type and Phase // if ((Record->ChildContext.Sx.Type < SxS0) || (Record->ChildContext.Sx.Type >= EfiMaximumSleepType) || (Record->ChildContext.Sx.Phase < SxEntry) || (Record->ChildContext.Sx.Phase >= EfiMaximumPhase) ) { goto Error; } InsertTailList (&mPrivateData.CallbackDataBase, &Record->Link); CopyMem (&Record->SrcDesc, &SX_SOURCE_DESC, sizeof (Record->SrcDesc)); // // use default clear source function // break; case SwType: if (RegisterContext->Sw.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 = SmiInputValueDuplicateCheck (Index); if (!EFI_ERROR (Status)) { RegisterContext->Sw.SwSmiInputValue = Index; break; } } if (RegisterContext->Sw.SwSmiInputValue == (UINTN)-1) { Status = gSmst->SmmFreePool (Record); return EFI_OUT_OF_RESOURCES; } // // Update ChildContext again as SwSmiInputValue has been changed // CopyMem (&Record->ChildContext, RegisterContext, sizeof (QNC_SMM_CONTEXT)); } // // Check the validity of Context Value // if (Record->ChildContext.Sw.SwSmiInputValue > MAXIMUM_SWI_VALUE) { goto Error; } if (EFI_ERROR (SmiInputValueDuplicateCheck (Record->ChildContext.Sw.SwSmiInputValue))) { goto Error; } InsertTailList (&mPrivateData.CallbackDataBase, &Record->Link); CopyMem (&Record->SrcDesc, &SW_SOURCE_DESC, sizeof (Record->SrcDesc)); Record->BufferSize = sizeof (EFI_SMM_SW_REGISTER_CONTEXT); // // use default clear source function // break; case GpiType: InsertTailList (&mPrivateData.CallbackDataBase, &Record->Link); CopyMem (&Record->SrcDesc, &GPI_SOURCE_DESC, sizeof (Record->SrcDesc)); // // use default clear source function // break; case QNCnType: // // Check the validity of Context Type // if ((Record->ChildContext.QNCn.Type < IchnMch) || (Record->ChildContext.QNCn.Type >= NUM_ICHN_TYPES)) { goto Error; } InsertTailList (&mPrivateData.CallbackDataBase, &Record->Link); CopyMem (&Record->SrcDesc, &QNCN_SOURCE_DESCS[Record->ChildContext.QNCn.Type], sizeof (Record->SrcDesc)); Record->ClearSource = QNCSmmQNCnClearSource; break; case PeriodicTimerType: Status = MapPeriodicTimerToSrcDesc (RegisterContext, &(Record->SrcDesc)); if (EFI_ERROR (Status)) { goto Error; } InsertTailList (&mPrivateData.CallbackDataBase, &Record->Link); Record->BufferSize = sizeof (EFI_SMM_PERIODIC_TIMER_CONTEXT); Record->ClearSource = QNCSmmPeriodicTimerClearSource; break; default: goto Error; break; }; if (Record->ClearSource == NULL) { // // Clear the SMI associated w/ the source using the default function // QNCSmmClearSource (&Record->SrcDesc); } else { // // This source requires special handling to clear // Record->ClearSource (&Record->SrcDesc); } QNCSmmEnableSource (&Record->SrcDesc); // // Child's handle will be the address linked list link in the record // *DispatchHandle = (EFI_HANDLE) (&Record->Link); return EFI_SUCCESS; Error: FreePool (Record); // // DEBUG((EFI_D_ERROR,"Free pool status %d\n", Status )); // return EFI_INVALID_PARAMETER; } EFI_STATUS QNCSmmCoreUnRegister ( IN QNC_SMM_GENERIC_PROTOCOL *This, IN EFI_HANDLE DispatchHandle ) /*++ Routine Description: Arguments: Returns: --*/ // GC_TODO: This - add argument and description to function comment // GC_TODO: DispatchHandle - add argument and description to function comment // GC_TODO: EFI_INVALID_PARAMETER - add return value to function comment // GC_TODO: EFI_INVALID_PARAMETER - add return value to function comment // GC_TODO: EFI_SUCCESS - add return value to function comment { BOOLEAN SafeToDisable; DATABASE_RECORD *RecordToDelete; DATABASE_RECORD *RecordInDb; LIST_ENTRY *LinkInDb; if (DispatchHandle == NULL) { return EFI_INVALID_PARAMETER; } if (BASE_CR (DispatchHandle, DATABASE_RECORD, Link)->Signature != DATABASE_RECORD_SIGNATURE) { return EFI_INVALID_PARAMETER; } RecordToDelete = DATABASE_RECORD_FROM_LINK (DispatchHandle); RemoveEntryList (&RecordToDelete->Link); RecordToDelete->Signature = 0; // // See if we can disable the source, reserved for future use since this might // not be the only criteria to disable // SafeToDisable = TRUE; LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase); while(!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) { RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb); if (CompareEnables (&RecordToDelete->SrcDesc, &RecordInDb->SrcDesc)) { SafeToDisable = FALSE; break; } LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, &RecordInDb->Link); } if (SafeToDisable) { QNCSmmDisableSource( &RecordToDelete->SrcDesc ); } FreePool (RecordToDelete); return EFI_SUCCESS; } /** This function is the main entry point for an SMM handler dispatch or communicate-based callback. @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 QNCSmmCoreDispatcher ( IN EFI_HANDLE DispatchHandle, IN CONST VOID *RegisterContext, IN OUT VOID *CommBuffer, IN OUT UINTN *CommBufferSize ) { // // Used to prevent infinite loops // UINTN EscapeCount; BOOLEAN ContextsMatch; BOOLEAN ResetListSearch; BOOLEAN EosSet; BOOLEAN SxChildWasDispatched; BOOLEAN ChildWasDispatched; DATABASE_RECORD *RecordInDb; DATABASE_RECORD ActiveRecordInDb; LIST_ENTRY *LinkInDb; DATABASE_RECORD *RecordToExhaust; LIST_ENTRY *LinkToExhaust; QNC_SMM_CONTEXT Context; VOID *CommunicationBuffer; UINTN BufferSize; EFI_STATUS Status; UINT32 NewValue; QNC_SMM_SOURCE_DESC ActiveSource = NULL_SOURCE_DESC_INITIALIZER; EscapeCount = 100; ContextsMatch = FALSE; ResetListSearch = FALSE; EosSet = FALSE; SxChildWasDispatched = FALSE; Status = EFI_WARN_INTERRUPT_SOURCE_PENDING; ChildWasDispatched = FALSE; // // Mark all child handlers as not processed // LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase); while (!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) { RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb); RecordInDb->Processed = FALSE; LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, LinkInDb); } // // Preserve Index registers // SaveState (); if (!IsListEmpty (&mPrivateData.CallbackDataBase)) { // // We have children registered w/ us -- continue // while ((!EosSet) && (EscapeCount > 0)) { EscapeCount--; // // Reset this flag in order to be able to process multiple SMI Sources in one loop. // ResetListSearch = FALSE; LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase); while ((!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) && (ResetListSearch == FALSE)) { RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb); // // Make a copy of the record that contains an active SMI source, // because un-register maybe invoked in callback function and // RecordInDb maybe released // CopyMem (&ActiveRecordInDb, RecordInDb, sizeof (ActiveRecordInDb)); // // look for the first active source // if (!SourceIsActive (&RecordInDb->SrcDesc)) { // // Didn't find the source yet, keep looking // LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, &RecordInDb->Link); } else { // // We found a source. If this is a sleep type, we have to go to // appropriate sleep state anyway.No matter there is sleep child or not // if (RecordInDb->ProtocolType == SxType) { SxChildWasDispatched = TRUE; } // // "cache" the source description and don't query I/O anymore // CopyMem (&ActiveSource, &RecordInDb->SrcDesc, sizeof (ActiveSource)); LinkToExhaust = LinkInDb; // // exhaust the rest of the queue looking for the same source // while (!IsNull (&mPrivateData.CallbackDataBase, LinkToExhaust)) { RecordToExhaust = DATABASE_RECORD_FROM_LINK (LinkToExhaust); LinkToExhaust = GetNextNode (&mPrivateData.CallbackDataBase, LinkToExhaust); if (RecordToExhaust->Processed) { // // Record has already been processed. Continue with next child handler. // continue; } if (CompareSources (&RecordToExhaust->SrcDesc, &ActiveSource)) { // // These source descriptions are equal, so this callback should be // dispatched. // if (RecordToExhaust->ContextFunctions.GetContext != NULL) { // // This child requires that we get a calling context from // hardware and compare that context to the one supplied // by the child. // ASSERT (RecordToExhaust->ContextFunctions.CmpContext != NULL); // // Make sure contexts match before dispatching event to child // RecordToExhaust->ContextFunctions.GetContext (RecordToExhaust, &Context); ContextsMatch = RecordToExhaust->ContextFunctions.CmpContext (&Context, &RecordToExhaust->ChildContext); } else { // // This child doesn't require any more calling context beyond what // it supplied in registration. Simply pass back what it gave us. // ASSERT (RecordToExhaust->Callback != NULL); ContextsMatch = TRUE; } // // Mark this child handler so it will not be processed again // RecordToExhaust->Processed = TRUE; if (ContextsMatch) { if (RecordToExhaust->BufferSize != 0) { ASSERT (RecordToExhaust->ContextFunctions.GetBuffer != NULL); RecordToExhaust->ContextFunctions.GetBuffer (RecordToExhaust); CommunicationBuffer = &RecordToExhaust->CommBuffer; BufferSize = RecordToExhaust->BufferSize; } else { CommunicationBuffer = NULL; BufferSize = 0; } ASSERT (RecordToExhaust->Callback != NULL); RecordToExhaust->Callback ( (EFI_HANDLE) & RecordToExhaust->Link, RecordToExhaust->CallbackContext, CommunicationBuffer, &BufferSize ); ChildWasDispatched = TRUE; if (RecordToExhaust->ProtocolType == SxType) { SxChildWasDispatched = TRUE; } } // // Can not use RecordInDb after this point because Callback may have unregistered RecordInDb // Restart processing of SMI handlers from the begining of the linked list because the // state of the linked listed may have been modified due to unregister actions in the Callback. // LinkToExhaust = GetFirstNode (&mPrivateData.CallbackDataBase); } } if (ActiveRecordInDb.ClearSource == NULL) { // // Clear the SMI associated w/ the source using the default function // QNCSmmClearSource (&ActiveSource); } else { // // This source requires special handling to clear // ActiveRecordInDb.ClearSource (&ActiveSource); } if (ChildWasDispatched) { // // The interrupt was handled and quiesced // Status = EFI_SUCCESS; } else { // // The interrupt was not handled but quiesced // Status = EFI_WARN_INTERRUPT_SOURCE_QUIESCED; } // // Queue is empty, reset the search // ResetListSearch = TRUE; } } EosSet = QNCSmmSetAndCheckEos (); } } // // If you arrive here, there are two possible reasons: // (1) you've got problems with clearing the SMI status bits in the // ACPI table. If you don't properly clear the SMI bits, then you won't be able to set the // EOS bit. If this happens too many times, the loop exits. // (2) there was a SMM communicate for callback messages that was received prior // to this driver. // If there is an asynchronous SMI that occurs while processing the Callback, let // all of the drivers (including this one) have an opportunity to scan for the SMI // and handle it. // If not, we don't want to exit and have the foreground app. clear EOS without letting // these other sources get serviced. // ASSERT (EscapeCount > 0); // // Restore Index registers // RestoreState (); if (SxChildWasDispatched) { // // A child of the SmmSxDispatch protocol was dispatched during this call; // put the system to sleep. // QNCSmmSxGoToSleep (); } // // Ensure that SMI signal pin indicator is clear at the end of SMM handling. // NewValue = QNCPortRead (QUARK_NC_HOST_BRIDGE_SB_PORT_ID, QUARK_NC_HOST_BRIDGE_HLEGACY_REG); NewValue &= ~(HLEGACY_SMI_PIN_VALUE); QNCPortWrite (QUARK_NC_HOST_BRIDGE_SB_PORT_ID, QUARK_NC_HOST_BRIDGE_HLEGACY_REG, NewValue); return Status; }