/** @file UEFI Event support functions implemented in this file. Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.
(C) Copyright 2015 Hewlett Packard Enterprise Development LP
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "DxeMain.h" #include "Event.h" /// /// gEfiCurrentTpl - Current Task priority level /// EFI_TPL gEfiCurrentTpl = TPL_APPLICATION; /// /// gEventQueueLock - Protects the event queues /// EFI_LOCK gEventQueueLock = EFI_INITIALIZE_LOCK_VARIABLE (TPL_HIGH_LEVEL); /// /// gEventQueue - A list of event's to notify for each priority level /// LIST_ENTRY gEventQueue[TPL_HIGH_LEVEL + 1]; /// /// gEventPending - A bitmask of the EventQueues that are pending /// UINTN gEventPending = 0; /// /// gEventSignalQueue - A list of events to signal based on EventGroup type /// LIST_ENTRY gEventSignalQueue = INITIALIZE_LIST_HEAD_VARIABLE (gEventSignalQueue); /// /// Enumerate the valid types /// UINT32 mEventTable[] = { /// /// 0x80000200 Timer event with a notification function that is /// queue when the event is signaled with SignalEvent() /// EVT_TIMER | EVT_NOTIFY_SIGNAL, /// /// 0x80000000 Timer event without a notification function. It can be /// signaled with SignalEvent() and checked with CheckEvent() or WaitForEvent(). /// EVT_TIMER, /// /// 0x00000100 Generic event with a notification function that /// can be waited on with CheckEvent() or WaitForEvent() /// EVT_NOTIFY_WAIT, /// /// 0x00000200 Generic event with a notification function that /// is queue when the event is signaled with SignalEvent() /// EVT_NOTIFY_SIGNAL, /// /// 0x00000201 ExitBootServicesEvent. /// EVT_SIGNAL_EXIT_BOOT_SERVICES, /// /// 0x60000202 SetVirtualAddressMapEvent. /// EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, /// /// 0x00000000 Generic event without a notification function. /// It can be signaled with SignalEvent() and checked with CheckEvent() /// or WaitForEvent(). /// 0x00000000, /// /// 0x80000100 Timer event with a notification function that can be /// waited on with CheckEvent() or WaitForEvent() /// EVT_TIMER | EVT_NOTIFY_WAIT, }; /// /// gIdleLoopEvent - Event which is signalled when the core is idle /// EFI_EVENT gIdleLoopEvent = NULL; /** Enter critical section by acquiring the lock on gEventQueueLock. **/ VOID CoreAcquireEventLock ( VOID ) { CoreAcquireLock (&gEventQueueLock); } /** Exit critical section by releasing the lock on gEventQueueLock. **/ VOID CoreReleaseEventLock ( VOID ) { CoreReleaseLock (&gEventQueueLock); } /** Initializes "event" support. @retval EFI_SUCCESS Always return success **/ EFI_STATUS CoreInitializeEventServices ( VOID ) { UINTN Index; for (Index=0; Index <= TPL_HIGH_LEVEL; Index++) { InitializeListHead (&gEventQueue[Index]); } CoreInitializeTimer (); CoreCreateEventEx ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, EfiEventEmptyFunction, NULL, &gIdleLoopEventGuid, &gIdleLoopEvent ); return EFI_SUCCESS; } /** Dispatches all pending events. @param Priority The task priority level of event notifications to dispatch **/ VOID CoreDispatchEventNotifies ( IN EFI_TPL Priority ) { IEVENT *Event; LIST_ENTRY *Head; CoreAcquireEventLock (); ASSERT (gEventQueueLock.OwnerTpl == Priority); Head = &gEventQueue[Priority]; // // Dispatch all the pending notifications // while (!IsListEmpty (Head)) { Event = CR (Head->ForwardLink, IEVENT, NotifyLink, EVENT_SIGNATURE); RemoveEntryList (&Event->NotifyLink); Event->NotifyLink.ForwardLink = NULL; // // Only clear the SIGNAL status if it is a SIGNAL type event. // WAIT type events are only cleared in CheckEvent() // if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) { Event->SignalCount = 0; } CoreReleaseEventLock (); // // Notify this event // ASSERT (Event->NotifyFunction != NULL); Event->NotifyFunction (Event, Event->NotifyContext); // // Check for next pending event // CoreAcquireEventLock (); } gEventPending &= ~(UINTN)(1 << Priority); CoreReleaseEventLock (); } /** Queues the event's notification function to fire. @param Event The Event to notify **/ VOID CoreNotifyEvent ( IN IEVENT *Event ) { // // Event database must be locked // ASSERT_LOCKED (&gEventQueueLock); // // If the event is queued somewhere, remove it // if (Event->NotifyLink.ForwardLink != NULL) { RemoveEntryList (&Event->NotifyLink); Event->NotifyLink.ForwardLink = NULL; } // // Queue the event to the pending notification list // InsertTailList (&gEventQueue[Event->NotifyTpl], &Event->NotifyLink); gEventPending |= (UINTN)(1 << Event->NotifyTpl); } /** Signals all events in the EventGroup. @param EventGroup The list to signal **/ VOID CoreNotifySignalList ( IN EFI_GUID *EventGroup ) { LIST_ENTRY *Link; LIST_ENTRY *Head; IEVENT *Event; CoreAcquireEventLock (); Head = &gEventSignalQueue; for (Link = Head->ForwardLink; Link != Head; Link = Link->ForwardLink) { Event = CR (Link, IEVENT, SignalLink, EVENT_SIGNATURE); if (CompareGuid (&Event->EventGroup, EventGroup)) { CoreNotifyEvent (Event); } } CoreReleaseEventLock (); } /** Creates an event. @param Type The type of event to create and its mode and attributes @param NotifyTpl The task priority level of event notifications @param NotifyFunction Pointer to the events notification function @param NotifyContext Pointer to the notification functions context; corresponds to parameter "Context" in the notification function @param Event Pointer to the newly created event if the call succeeds; undefined otherwise @retval EFI_SUCCESS The event structure was created @retval EFI_INVALID_PARAMETER One of the parameters has an invalid value @retval EFI_OUT_OF_RESOURCES The event could not be allocated **/ EFI_STATUS EFIAPI CoreCreateEvent ( IN UINT32 Type, IN EFI_TPL NotifyTpl, IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL IN VOID *NotifyContext, OPTIONAL OUT EFI_EVENT *Event ) { return CoreCreateEventEx (Type, NotifyTpl, NotifyFunction, NotifyContext, NULL, Event); } /** Creates an event in a group. @param Type The type of event to create and its mode and attributes @param NotifyTpl The task priority level of event notifications @param NotifyFunction Pointer to the events notification function @param NotifyContext Pointer to the notification functions context; corresponds to parameter "Context" in the notification function @param EventGroup GUID for EventGroup if NULL act the same as gBS->CreateEvent(). @param Event Pointer to the newly created event if the call succeeds; undefined otherwise @retval EFI_SUCCESS The event structure was created @retval EFI_INVALID_PARAMETER One of the parameters has an invalid value @retval EFI_OUT_OF_RESOURCES The event could not be allocated **/ EFI_STATUS EFIAPI CoreCreateEventEx ( IN UINT32 Type, IN EFI_TPL NotifyTpl, IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL IN CONST VOID *NotifyContext, OPTIONAL IN CONST EFI_GUID *EventGroup, OPTIONAL OUT EFI_EVENT *Event ) { // // If it's a notify type of event, check for invalid NotifyTpl // if ((Type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) != 0) { if (NotifyTpl != TPL_APPLICATION && NotifyTpl != TPL_CALLBACK && NotifyTpl != TPL_NOTIFY) { return EFI_INVALID_PARAMETER; } } return CoreCreateEventInternal (Type, NotifyTpl, NotifyFunction, NotifyContext, EventGroup, Event); } /** Creates a general-purpose event structure @param Type The type of event to create and its mode and attributes @param NotifyTpl The task priority level of event notifications @param NotifyFunction Pointer to the events notification function @param NotifyContext Pointer to the notification functions context; corresponds to parameter "Context" in the notification function @param EventGroup GUID for EventGroup if NULL act the same as gBS->CreateEvent(). @param Event Pointer to the newly created event if the call succeeds; undefined otherwise @retval EFI_SUCCESS The event structure was created @retval EFI_INVALID_PARAMETER One of the parameters has an invalid value @retval EFI_OUT_OF_RESOURCES The event could not be allocated **/ EFI_STATUS EFIAPI CoreCreateEventInternal ( IN UINT32 Type, IN EFI_TPL NotifyTpl, IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL IN CONST VOID *NotifyContext, OPTIONAL IN CONST EFI_GUID *EventGroup, OPTIONAL OUT EFI_EVENT *Event ) { EFI_STATUS Status; IEVENT *IEvent; INTN Index; if (Event == NULL) { return EFI_INVALID_PARAMETER; } // // Check to make sure no reserved flags are set // Status = EFI_INVALID_PARAMETER; for (Index = 0; Index < (sizeof (mEventTable) / sizeof (UINT32)); Index++) { if (Type == mEventTable[Index]) { Status = EFI_SUCCESS; break; } } if(EFI_ERROR (Status)) { return EFI_INVALID_PARAMETER; } // // Convert Event type for pre-defined Event groups // if (EventGroup != NULL) { // // For event group, type EVT_SIGNAL_EXIT_BOOT_SERVICES and EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE // are not valid // if ((Type == EVT_SIGNAL_EXIT_BOOT_SERVICES) || (Type == EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE)) { return EFI_INVALID_PARAMETER; } if (CompareGuid (EventGroup, &gEfiEventExitBootServicesGuid)) { Type = EVT_SIGNAL_EXIT_BOOT_SERVICES; } else if (CompareGuid (EventGroup, &gEfiEventVirtualAddressChangeGuid)) { Type = EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE; } } else { // // Convert EFI 1.10 Events to their UEFI 2.0 CreateEventEx mapping // if (Type == EVT_SIGNAL_EXIT_BOOT_SERVICES) { EventGroup = &gEfiEventExitBootServicesGuid; } else if (Type == EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE) { EventGroup = &gEfiEventVirtualAddressChangeGuid; } } // // If it's a notify type of event, check its parameters // if ((Type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) != 0) { // // Check for an invalid NotifyFunction or NotifyTpl // if ((NotifyFunction == NULL) || (NotifyTpl <= TPL_APPLICATION) || (NotifyTpl >= TPL_HIGH_LEVEL)) { return EFI_INVALID_PARAMETER; } } else { // // No notification needed, zero ignored values // NotifyTpl = 0; NotifyFunction = NULL; NotifyContext = NULL; } // // Allocate and initialize a new event structure. // if ((Type & EVT_RUNTIME) != 0) { IEvent = AllocateRuntimeZeroPool (sizeof (IEVENT)); } else { IEvent = AllocateZeroPool (sizeof (IEVENT)); } if (IEvent == NULL) { return EFI_OUT_OF_RESOURCES; } IEvent->Signature = EVENT_SIGNATURE; IEvent->Type = Type; IEvent->NotifyTpl = NotifyTpl; IEvent->NotifyFunction = NotifyFunction; IEvent->NotifyContext = (VOID *)NotifyContext; if (EventGroup != NULL) { CopyGuid (&IEvent->EventGroup, EventGroup); IEvent->ExFlag |= EVT_EXFLAG_EVENT_GROUP; } *Event = IEvent; if ((Type & EVT_RUNTIME) != 0) { // // Keep a list of all RT events so we can tell the RT AP. // IEvent->RuntimeData.Type = Type; IEvent->RuntimeData.NotifyTpl = NotifyTpl; IEvent->RuntimeData.NotifyFunction = NotifyFunction; IEvent->RuntimeData.NotifyContext = (VOID *) NotifyContext; // // Work around the bug in the Platform Init specification (v1.7), reported // as Mantis#2017: "EFI_RUNTIME_EVENT_ENTRY.Event" should have type // EFI_EVENT, not (EFI_EVENT*). The PI spec documents the field correctly // as "The EFI_EVENT returned by CreateEvent()", but the type of the field // doesn't match the natural language description. Therefore we need an // explicit cast here. // IEvent->RuntimeData.Event = (EFI_EVENT *) IEvent; InsertTailList (&gRuntime->EventHead, &IEvent->RuntimeData.Link); } CoreAcquireEventLock (); if ((Type & EVT_NOTIFY_SIGNAL) != 0x00000000) { // // The Event's NotifyFunction must be queued whenever the event is signaled // InsertHeadList (&gEventSignalQueue, &IEvent->SignalLink); } CoreReleaseEventLock (); // // Done // return EFI_SUCCESS; } /** Signals the event. Queues the event to be notified if needed. @param UserEvent The event to signal . @retval EFI_INVALID_PARAMETER Parameters are not valid. @retval EFI_SUCCESS The event was signaled. **/ EFI_STATUS EFIAPI CoreSignalEvent ( IN EFI_EVENT UserEvent ) { IEVENT *Event; Event = UserEvent; if (Event == NULL) { return EFI_INVALID_PARAMETER; } if (Event->Signature != EVENT_SIGNATURE) { return EFI_INVALID_PARAMETER; } CoreAcquireEventLock (); // // If the event is not already signalled, do so // if (Event->SignalCount == 0x00000000) { Event->SignalCount++; // // If signalling type is a notify function, queue it // if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) { if ((Event->ExFlag & EVT_EXFLAG_EVENT_GROUP) != 0) { // // The CreateEventEx() style requires all members of the Event Group // to be signaled. // CoreReleaseEventLock (); CoreNotifySignalList (&Event->EventGroup); CoreAcquireEventLock (); } else { CoreNotifyEvent (Event); } } } CoreReleaseEventLock (); return EFI_SUCCESS; } /** Check the status of an event. @param UserEvent The event to check @retval EFI_SUCCESS The event is in the signaled state @retval EFI_NOT_READY The event is not in the signaled state @retval EFI_INVALID_PARAMETER Event is of type EVT_NOTIFY_SIGNAL **/ EFI_STATUS EFIAPI CoreCheckEvent ( IN EFI_EVENT UserEvent ) { IEVENT *Event; EFI_STATUS Status; Event = UserEvent; if (Event == NULL) { return EFI_INVALID_PARAMETER; } if (Event->Signature != EVENT_SIGNATURE) { return EFI_INVALID_PARAMETER; } if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) { return EFI_INVALID_PARAMETER; } Status = EFI_NOT_READY; if ((Event->SignalCount == 0) && ((Event->Type & EVT_NOTIFY_WAIT) != 0)) { // // Queue the wait notify function // CoreAcquireEventLock (); if (Event->SignalCount == 0) { CoreNotifyEvent (Event); } CoreReleaseEventLock (); } // // If the even looks signalled, get the lock and clear it // if (Event->SignalCount != 0) { CoreAcquireEventLock (); if (Event->SignalCount != 0) { Event->SignalCount = 0; Status = EFI_SUCCESS; } CoreReleaseEventLock (); } return Status; } /** Stops execution until an event is signaled. @param NumberOfEvents The number of events in the UserEvents array @param UserEvents An array of EFI_EVENT @param UserIndex Pointer to the index of the event which satisfied the wait condition @retval EFI_SUCCESS The event indicated by Index was signaled. @retval EFI_INVALID_PARAMETER The event indicated by Index has a notification function or Event was not a valid type @retval EFI_UNSUPPORTED The current TPL is not TPL_APPLICATION **/ EFI_STATUS EFIAPI CoreWaitForEvent ( IN UINTN NumberOfEvents, IN EFI_EVENT *UserEvents, OUT UINTN *UserIndex ) { EFI_STATUS Status; UINTN Index; // // Can only WaitForEvent at TPL_APPLICATION // if (gEfiCurrentTpl != TPL_APPLICATION) { return EFI_UNSUPPORTED; } if (NumberOfEvents == 0) { return EFI_INVALID_PARAMETER; } if (UserEvents == NULL) { return EFI_INVALID_PARAMETER; } for(;;) { for(Index = 0; Index < NumberOfEvents; Index++) { Status = CoreCheckEvent (UserEvents[Index]); // // provide index of event that caused problem // if (Status != EFI_NOT_READY) { if (UserIndex != NULL) { *UserIndex = Index; } return Status; } } // // Signal the Idle event // CoreSignalEvent (gIdleLoopEvent); } } /** Closes an event and frees the event structure. @param UserEvent Event to close @retval EFI_INVALID_PARAMETER Parameters are not valid. @retval EFI_SUCCESS The event has been closed **/ EFI_STATUS EFIAPI CoreCloseEvent ( IN EFI_EVENT UserEvent ) { EFI_STATUS Status; IEVENT *Event; Event = UserEvent; if (Event == NULL) { return EFI_INVALID_PARAMETER; } if (Event->Signature != EVENT_SIGNATURE) { return EFI_INVALID_PARAMETER; } // // If it's a timer event, make sure it's not pending // if ((Event->Type & EVT_TIMER) != 0) { CoreSetTimer (Event, TimerCancel, 0); } CoreAcquireEventLock (); // // If the event is queued somewhere, remove it // if (Event->RuntimeData.Link.ForwardLink != NULL) { RemoveEntryList (&Event->RuntimeData.Link); } if (Event->NotifyLink.ForwardLink != NULL) { RemoveEntryList (&Event->NotifyLink); } if (Event->SignalLink.ForwardLink != NULL) { RemoveEntryList (&Event->SignalLink); } CoreReleaseEventLock (); // // If the event is registered on a protocol notify, then remove it from the protocol database // if ((Event->ExFlag & EVT_EXFLAG_EVENT_PROTOCOL_NOTIFICATION) != 0) { CoreUnregisterProtocolNotify (Event); } // // To avoid the Event to be signalled wrongly after closed, // clear the Signature of Event before free pool. // Event->Signature = 0; Status = CoreFreePool (Event); ASSERT_EFI_ERROR (Status); return Status; }