1 /*
2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 #include "events.h"
19 
20 #include <kernel/event.h>
21 #include <kernel/mutex.h>
22 #include <kernel/thread.h>
23 #include <lk/err.h>
24 #include <lk/list.h>
25 #include <lk/trace.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <uefi/types.h>
30 
31 #define LOCAL_TRACE 0
32 
33 namespace {
34 
35 lk_time_t backoff_wait_time = 1;
36 
37 Mutex event_list_mutex;
38 
39 struct list_node pending_events = LIST_INITIAL_VALUE(pending_events);
40 
invoke_callback(EfiEventImpl * ev)41 bool invoke_callback(EfiEventImpl *ev) {
42   DEBUG_ASSERT(ev->ready());
43   DEBUG_ASSERT(ev->creator_thread == get_current_thread());
44   if (!ev->callback_called && ev->notify_fn != nullptr) {
45     // TODO use atomic compare_exchange to set callback_called
46     // to true, so that we don't call the callback multiple times.
47     ev->callback_called = true;
48     LTRACEF("Triggering event %p callback %p ctx %p on thread %s\n", ev,
49             ev->notify_fn, ev->notify_ctx, get_current_thread()->name);
50     backoff_wait_time = 0;
51     ev->notify_fn(ev, ev->notify_ctx);
52     return true;
53   }
54   return false;
55 }
56 
delete_if_in_list(EfiEventImpl * event,AutoLock * al)57 void delete_if_in_list(EfiEventImpl *event, AutoLock *al) {
58   if (event->node.prev != nullptr && event->node.next != nullptr) {
59     list_delete(&event->node);
60   }
61 }
62 
process_pending_events()63 void process_pending_events() {
64   AutoLock al{&event_list_mutex};
65   EfiEventImpl *ev = nullptr;
66   EfiEventImpl *tmp_ev = nullptr;
67 
68   struct list_node completed_events = LIST_INITIAL_VALUE(completed_events);
69   list_for_every_entry_safe(&pending_events, ev, tmp_ev, EfiEventImpl, node) {
70     if (ev->ready()) {
71       delete_if_in_list(ev, &al);
72       list_add_tail(&completed_events, &ev->node);
73     }
74   }
75   al.release();
76   list_for_every_entry(&completed_events, ev, EfiEventImpl, node) {
77     invoke_callback(ev);
78   }
79 }
80 
81 }  // namespace
82 
wait_for_event(size_t num_events,EfiEvent * event,size_t * index)83 EfiStatus wait_for_event(size_t num_events, EfiEvent *event, size_t *index) {
84   LTRACEF("waiting for %zu events\n", num_events);
85   for (size_t i = 0; i < num_events; i++) {
86     EfiEventImpl *ev = reinterpret_cast<EfiEventImpl *>(event[i]);
87     if (ev->ready()) {
88       *index = i;
89       return SUCCESS;
90     }
91   }
92   while (true) {
93     // LK currently does not support waiting for multiple events.
94     // So we just have to wait for each event in a poll fashion.
95     for (size_t i = 0; i < num_events; i++) {
96       EfiEventImpl *ev = reinterpret_cast<EfiEventImpl *>(event[i]);
97       if (ev->ready()) {
98         *index = i;
99         return SUCCESS;
100       }
101       auto status = event_wait_timeout(&ev->ev, 200);
102       if (status == ERR_TIMED_OUT) {
103         continue;
104       }
105       return SUCCESS;
106     }
107   }
108   return NOT_READY;
109 }
110 
signal_event(EfiEvent event)111 EfiStatus signal_event(EfiEvent event) {
112   LTRACEF("%s(type=0x%x, ready=%d)\n", __FUNCTION__, event->type,
113           event->ready());
114   // This function can be called from interrupt context. In interrupt context,
115   // we can't switch thread context, so any blocking APIs such as malloc/free
116   // mutexes, etc. are not allowed.
117   if (event->ready()) {
118     printf("Event %p already signaled\n", event);
119     return SUCCESS;
120   }
121   event_signal(&event->ev, !arch_ints_disabled());
122   if ((event->type & NOTIFY_SIGNAL) && event->notify_fn != nullptr) {
123     // If this event is signaled on a different thread,  defer
124     // calling callbacks until the next check_event call. As UEFI apps
125     // are single threaded, we don't want to call event callbacks from another
126     // thread
127     if (event->creator_thread != get_current_thread() || arch_ints_disabled()) {
128       LTRACEF(
129           "Event %p of type 0x%x is signaled from thread %s, defer notify_fn "
130           "because event is created on another thread %s or interrupt is "
131           "disabled\n",
132           event, event->type, get_current_thread()->name,
133           event->creator_thread->name);
134       return SUCCESS;
135     }
136     // this is only possible in non-interrupt context, as this requires mutexes.
137     AutoLock al{&event_list_mutex};
138     delete_if_in_list(event, &al);
139     al.release();
140     invoke_callback(event);
141   }
142   return SUCCESS;
143 }
144 
create_event(EfiEventType type,EfiTpl notify_tpl,EfiEventNotify notify_fn,void * notify_ctx,EfiEvent * event)145 EfiStatus create_event(EfiEventType type, EfiTpl notify_tpl,
146                        EfiEventNotify notify_fn, void *notify_ctx,
147                        EfiEvent *event) {
148   process_pending_events();
149   if ((type & TIMER) != 0) {
150     printf("Creating timer event is not supported yet\n");
151     return UNSUPPORTED;
152   }
153   if ((type & SIGNAL_EXIT_BOOT_SERVICES) == SIGNAL_EXIT_BOOT_SERVICES ||
154       (type & SIGNAL_VIRTUAL_ADDRESS_CHANGE) == SIGNAL_VIRTUAL_ADDRESS_CHANGE) {
155     printf(
156         "Creating SIGNAL_EXIT_BOOT_SERVICES or SIGNAL_VIRTUAL_ADDRESS_CHANGE "
157         "event is not supported yet 0x%x\n",
158         type);
159     return UNSUPPORTED;
160   }
161   if ((type & NOTIFY_WAIT)) {
162     printf("Creating NOTIFY_WAIT event is not supported yet\n");
163     return UNSUPPORTED;
164   }
165   auto ev = reinterpret_cast<EfiEventImpl *>(malloc(sizeof(EfiEventImpl)));
166   memset(ev, 0, sizeof(EfiEventImpl));
167   ev->type = type;
168   event_init(&ev->ev, false, 0);
169   ev->notify_ctx = notify_ctx;
170   ev->notify_fn = notify_fn;
171   ev->creator_thread = get_current_thread();
172   AutoLock al{&event_list_mutex};
173   list_add_tail(&pending_events, &ev->node);
174 
175   LTRACEF("Created event 0x%x callback %p %p on thread %s\n", type, notify_fn,
176           notify_ctx, get_current_thread()->name);
177   *event = ev;
178   return SUCCESS;
179 }
180 
check_event(EfiEvent event)181 EfiStatus check_event(EfiEvent event) {
182   // Events can get signaled from interrupt context, in which we don't
183   // call the callback. check_event is definitely going to be called
184   // from UEFI app thread, this is a great time to handle any events
185   // that are signaled, but not yet had their callbacks called.
186   process_pending_events();
187   if (event == nullptr) {
188     // Some UEFI applications repeadtely call check_event(NULL) as a way to poll
189     // for completed events. To avoid busy waiting, implement an exponential
190     // backoff strategy if check_event is called repeatdely AND no events in the
191     // system are completed.
192 
193     if (backoff_wait_time == 0) {
194       thread_yield();
195       backoff_wait_time++;
196     } else {
197       thread_sleep(backoff_wait_time);
198       if (backoff_wait_time < 500) {
199         backoff_wait_time <<= 1;
200       }
201     }
202     return INVALID_PARAMETER;
203   }
204   if (event->ready()) {
205     AutoLock al{&event_list_mutex};
206     delete_if_in_list(event, &al);
207     al.release();
208     invoke_callback(event);
209     return SUCCESS;
210   }
211   return NOT_READY;
212 }
213 
close_event(EfiEvent event)214 EfiStatus close_event(EfiEvent event) {
215   AutoLock al{&event_list_mutex};
216   delete_if_in_list(event, &al);
217   al.release();
218   event_destroy(&event->ev);
219   free(event);
220   return SUCCESS;
221 }
222 
set_timer(EfiEvent event,EfiTimerDelay type,uint64_t trigger_time)223 EfiStatus set_timer(EfiEvent event, EfiTimerDelay type, uint64_t trigger_time) {
224   printf("%s is unsupported\n", __FUNCTION__);
225   return UNSUPPORTED;
226 }
227