The Pedigree Project 0.1
AcpiEvents.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2
3#include "AcpiEvents.h"
4#include "pedigree/kernel/Atomic.h"
5#include "pedigree/kernel/LockGuard.h"
6#include "pedigree/kernel/Log.h"
7#include "pedigree/kernel/machine/IrqHandler.h"
8#include "pedigree/kernel/machine/IrqManager.h"
9#include "pedigree/kernel/machine/Machine.h"
10#include "pedigree/kernel/process/ConditionVariable.h"
11#include "pedigree/kernel/process/CpuAffinity.h"
12#include "pedigree/kernel/process/Mutex.h"
13#include "pedigree/kernel/utilities/RequestQueue.h"
14#include "pedigree/kernel/utilities/new"
15
16#include <uacpi/kernel_api.h>
17
18namespace {
19static_assert(sizeof(uacpi_work_handler) <= sizeof(uint64_t));
20static_assert(sizeof(uacpi_handle) <= sizeof(uint64_t));
21
22class AcpiWorkQueue : public RequestQueue {
23 public:
24 AcpiWorkQueue() : RequestQueue(MakeConstantString("uACPI events")) {}
25
26 ~AcpiWorkQueue() override {
27 destroy();
28 }
29
30 protected:
31 bool workerPlacement(ThreadPlacement& placement) const override {
32 // GPE AML may enter firmware through SMI paths that assume the boot CPU.
33 placement.allowed.set(0);
34 return true;
35 }
36
37 uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t, uint64_t, uint64_t, uint64_t,
38 uint64_t, uint64_t) override {
39 auto handler = reinterpret_cast<uacpi_work_handler>(static_cast<uintptr_t>(p1));
40 auto context = reinterpret_cast<uacpi_handle>(static_cast<uintptr_t>(p2));
41 handler(context);
42 return 0;
43 }
44};
45
46class AcpiIrqHandler;
47
48struct AcpiEventsState {
49 Mutex mutex;
50 ConditionVariable callbackDone;
51 AcpiIrqHandler* handlers = nullptr;
52 size_t activeCallbacks = 0;
53 Atomic<size_t> ready{0};
54 AcpiWorkQueue work;
55};
56
57AcpiEventsState g_Events;
58
59class AcpiIrqHandler : public IrqHandler {
60 public:
61 AcpiIrqHandler(uacpi_interrupt_handler callback, uacpi_handle context)
62 : callback(callback), context(context) {}
63
64 ~AcpiIrqHandler() override = default;
65
66 IrqDisposition irq(irq_id_t) override {
67 {
68 LockGuard<Mutex> guard(g_Events.mutex);
69 ++g_Events.activeCallbacks;
70 }
71
72 const uacpi_interrupt_ret result = callback(context);
73
74 {
75 LockGuard<Mutex> guard(g_Events.mutex);
76 --g_Events.activeCallbacks;
77 if (!g_Events.activeCallbacks) {
78 g_Events.callbackDone.broadcast();
79 }
80 }
81
82 return result & UACPI_INTERRUPT_HANDLED ? IrqDisposition::Handled : IrqDisposition::NotHandled;
83 }
84
85 uacpi_interrupt_handler callback;
86 uacpi_handle context;
87 irq_id_t id = 0;
88 AcpiIrqHandler* next = nullptr;
89 bool removing = false;
90};
91} // namespace
92
93bool initialiseAcpiEvents() {
94 if (g_Events.ready.value()) {
95 return true;
96 }
97
98 g_Events.work.initialise();
99 if (g_Events.work.getLifecycleState() != RequestQueue::LifecycleState::Accepting) {
100 return false;
101 }
102
103 g_Events.ready = 1;
104 return true;
105}
106
107extern "C" uacpi_status uacpi_kernel_install_interrupt_handler(uacpi_u32 irq,
108 uacpi_interrupt_handler callback,
109 uacpi_handle context,
110 uacpi_handle* out_irq_handle) {
111 if (!callback || !out_irq_handle) {
112 return UACPI_STATUS_INVALID_ARGUMENT;
113 }
114 *out_irq_handle = nullptr;
115
116 // The current IRQ manager exposes ISA lines, not arbitrary GSI routing.
117 if (irq > 15) {
118 ERROR("uACPI: unsupported interrupt GSI " << Dec << irq);
119 return UACPI_STATUS_UNIMPLEMENTED;
120 }
121
122 if (!g_Events.ready.value()) {
123 return UACPI_STATUS_INIT_LEVEL_MISMATCH;
124 }
125
126 IrqManager* manager = Machine::instance().getIrqManager();
127 if (!manager) {
128 return UACPI_STATUS_UNIMPLEMENTED;
129 }
130
131 auto* handler = new AcpiIrqHandler(callback, context);
132 if (!handler) {
133 return UACPI_STATUS_OUT_OF_MEMORY;
134 }
135
136 {
137 LockGuard<Mutex> guard(g_Events.mutex);
138 handler->next = g_Events.handlers;
139 g_Events.handlers = handler;
140 }
141
142 handler->id = manager->registerIsaIrqHandler(static_cast<uint8_t>(irq), handler,
143 IrqPolicy::levelThreaded());
144 if (!handler->id) {
145 LockGuard<Mutex> guard(g_Events.mutex);
146 AcpiIrqHandler** link = &g_Events.handlers;
147 while (*link != handler) {
148 link = &(*link)->next;
149 }
150 *link = handler->next;
151 delete handler;
152 return UACPI_STATUS_INTERNAL_ERROR;
153 }
154
155 *out_irq_handle = handler;
156 return UACPI_STATUS_OK;
157}
158
159extern "C" uacpi_status uacpi_kernel_uninstall_interrupt_handler(uacpi_interrupt_handler callback,
160 uacpi_handle irq_handle) {
161 AcpiIrqHandler* handler = nullptr;
162 {
163 LockGuard<Mutex> guard(g_Events.mutex);
164 for (AcpiIrqHandler* current = g_Events.handlers; current; current = current->next) {
165 if (current == irq_handle) {
166 if (current->callback != callback || current->removing || !current->id) {
167 return UACPI_STATUS_INVALID_ARGUMENT;
168 }
169 current->removing = true;
170 handler = current;
171 break;
172 }
173 }
174 }
175 if (!handler) {
176 return UACPI_STATUS_INVALID_ARGUMENT;
177 }
178
179 IrqManager* manager = Machine::instance().getIrqManager();
180 if (!manager || !manager->unregisterHandler(handler->id, handler)) {
181 LockGuard<Mutex> guard(g_Events.mutex);
182 handler->removing = false;
183 return UACPI_STATUS_INTERNAL_ERROR;
184 }
185
186 {
187 LockGuard<Mutex> guard(g_Events.mutex);
188 AcpiIrqHandler** link = &g_Events.handlers;
189 while (*link != handler) {
190 link = &(*link)->next;
191 }
192 *link = handler->next;
193 }
194 delete handler;
195 return UACPI_STATUS_OK;
196}
197
198extern "C" uacpi_status uacpi_kernel_schedule_work(uacpi_work_type type, uacpi_work_handler handler,
199 uacpi_handle context) {
200 if ((type != UACPI_WORK_GPE_EXECUTION && type != UACPI_WORK_NOTIFICATION) || !handler) {
201 return UACPI_STATUS_INVALID_ARGUMENT;
202 }
203 if (!g_Events.ready.value()) {
204 return UACPI_STATUS_INIT_LEVEL_MISMATCH;
205 }
206
207 // The same worker preserves the notification-before-GPE-restore order.
208 return g_Events.work.publishAsyncRequest(0, reinterpret_cast<uintptr_t>(handler),
209 reinterpret_cast<uintptr_t>(context))
210 ? UACPI_STATUS_OK
211 : UACPI_STATUS_INTERNAL_ERROR;
212}
213
214extern "C" uacpi_status uacpi_kernel_wait_for_work_completion() {
215 if (!g_Events.ready.value()) {
216 return UACPI_STATUS_INIT_LEVEL_MISMATCH;
217 }
218 if (!g_Events.work.canWaitForCompletion()) {
219 return UACPI_STATUS_INVALID_ARGUMENT;
220 }
221
222 {
223 LockGuard<Mutex> guard(g_Events.mutex);
224 while (g_Events.activeCallbacks) {
225 g_Events.callbackDone.waitForCompletion(g_Events.mutex);
226 }
227 }
228
229 return g_Events.work.drain() ? UACPI_STATUS_OK : UACPI_STATUS_INTERNAL_ERROR;
230}
231
232bool shutdownAcpiEvents() {
233 if (!g_Events.ready.value()) {
234 return true;
235 }
236
237 // Close interrupt producers before draining AML, which may enqueue further
238 // notifications of its own. No firmware callbacks may outlive the scheduler.
239 while (true) {
240 AcpiIrqHandler* handler;
241 {
242 LockGuard<Mutex> guard(g_Events.mutex);
243 handler = g_Events.handlers;
244 }
245 if (!handler) {
246 break;
247 }
248 if (uacpi_kernel_uninstall_interrupt_handler(handler->callback, handler) != UACPI_STATUS_OK) {
249 return false;
250 }
251 }
252 if (!g_Events.work.drain()) {
253 return false;
254 }
255 g_Events.ready = 0;
256 g_Events.work.destroy();
257 return true;
258}
virtual IrqDisposition irq(irq_id_t number)=0
virtual irq_id_t registerIsaIrqHandler(uint8_t irq, IrqHandler *handler, const IrqPolicy &policy)=0
virtual bool unregisterHandler(irq_id_t Id, IrqHandlerBase *handler)=0
Definition Mutex.h:56
virtual void destroy()
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)=0
@ Dec
Definition Log.h:126
IrqDisposition
Definition IrqHandler.h:31