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