The Pedigree Project 0.1
mach_virt/Acpi.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "Acpi.h"
9
10namespace {
11constexpr uint64_t DirectMapBase = 0xffff000000000000ULL;
12constexpr uint64_t DirectMapLimit = 64ULL << 30;
13constexpr uint32_t MaxTableSize = 1024 * 1024;
14constexpr uint32_t XsdtSignature = 0x54445358;
15constexpr uint32_t MadtSignature = 0x43495041;
16constexpr uint32_t GtdtSignature = 0x54445447;
17constexpr uint32_t SpcrSignature = 0x52435053;
18constexpr uint32_t McfgSignature = 0x4746434d;
19constexpr uint32_t IortSignature = 0x54524f49;
20constexpr uint32_t FadtSignature = 0x50434146;
21constexpr uint32_t HeaderSize = 36;
22
23const BootstrapStruct_t::MemoryMapEntry* g_MemoryMap = nullptr;
24size_t g_MemoryMapCount = 0;
25
26uint16_t read16(const uint8_t* p) {
27 return uint16_t(p[0]) | (uint16_t(p[1]) << 8);
28}
29
30uint32_t read32(const uint8_t* p) {
31 return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24);
32}
33
34uint64_t read64(const uint8_t* p) {
35 return uint64_t(read32(p)) | (uint64_t(read32(p + 4)) << 32);
36}
37
38bool checksum(const uint8_t* bytes, size_t size) {
39 uint8_t sum = 0;
40 for (size_t i = 0; i < size; ++i) {
41 sum += bytes[i];
42 }
43 return sum == 0;
44}
45
46const uint8_t* physical(uint64_t address, size_t bytes) {
47 if (!address || !bytes || address >= DirectMapLimit || bytes > DirectMapLimit - address) {
48 return nullptr;
49 }
50 const uint64_t end = address + bytes;
51 for (uint64_t cursor = address; cursor < end;) {
52 uint64_t next = cursor;
53 for (size_t i = 0; i < g_MemoryMapCount; ++i) {
54 const auto& region = g_MemoryMap[i];
55 if ((region.type != 3 && region.type != 4) || region.address > cursor || !region.length ||
56 region.address > UINT64_MAX - region.length || cursor >= region.address + region.length) {
57 continue;
58 }
59 const uint64_t regionEnd = region.address + region.length;
60 if (regionEnd > next) {
61 next = regionEnd < end ? regionEnd : end;
62 }
63 }
64 if (next == cursor) {
65 return nullptr;
66 }
67 cursor = next;
68 }
69 return reinterpret_cast<const uint8_t*>(DirectMapBase + address);
70}
71
72const uint8_t* tableAt(uint64_t address) {
73 const uint8_t* table = physical(address, HeaderSize);
74 if (!table) {
75 return nullptr;
76 }
77 const uint32_t length = read32(table + 4);
78 if (length < HeaderSize || length > MaxTableSize || !physical(address, length) ||
79 !checksum(table, length)) {
80 return nullptr;
81 }
82 return table;
83}
84
85bool package(const uint8_t* bytes, size_t size, size_t& cursor, size_t& end) {
86 if (cursor >= size) {
87 return false;
88 }
89 const uint8_t lead = bytes[cursor];
90 const size_t follow = lead >> 6;
91 if (cursor + follow >= size) {
92 return false;
93 }
94 uint32_t length = follow ? lead & 0x0f : lead & 0x3f;
95 for (size_t i = 0; i < follow; ++i) {
96 length |= uint32_t(bytes[cursor + 1 + i]) << (4 + 8 * i);
97 }
98 if (length < follow + 1 || length > size - cursor) {
99 return false;
100 }
101 end = cursor + length;
102 cursor += follow + 1;
103 return true;
104}
105
106bool integer(const uint8_t* bytes, size_t end, size_t& cursor, uint64_t& result) {
107 if (cursor >= end) {
108 return false;
109 }
110 const uint8_t op = bytes[cursor++];
111 if (op <= 1) {
112 result = op;
113 return true;
114 }
115 const size_t width = op == 0x0a ? 1 : op == 0x0b ? 2 : op == 0x0c ? 4 : op == 0x0e ? 8 : 0;
116 if (!width || width > end - cursor) {
117 return false;
118 }
119 result = 0;
120 for (size_t i = 0; i < width; ++i) {
121 result |= uint64_t(bytes[cursor++]) << (i * 8);
122 }
123 return true;
124}
125
126bool name(const uint8_t* p, const char* wanted) {
127 for (size_t i = 0; i < 4; ++i) {
128 if (p[i] != static_cast<uint8_t>(wanted[i])) {
129 return false;
130 }
131 }
132 return true;
133}
134
135// Only static AML objects are consumed. Methods and computed resources require
136// an interpreter and are deliberately left unsupported.
137bool device(const uint8_t* aml, size_t size, const char* wanted, size_t& start, size_t& end) {
138 for (size_t i = 0; i + 8 < size; ++i) {
139 if (aml[i] != 0x5b || aml[i + 1] != 0x82) {
140 continue;
141 }
142 size_t body = i + 2;
143 size_t limit = 0;
144 if (package(aml, size, body, limit) && body + 4 <= limit && name(aml + body, wanted)) {
145 start = body + 4;
146 end = limit;
147 return true;
148 }
149 }
150 return false;
151}
152
153bool namedObject(const uint8_t* aml, size_t start, size_t end, const char* wanted, uint8_t op,
154 size_t& body, size_t& limit) {
155 for (size_t i = start; i + 7 < end; ++i) {
156 if (aml[i] != 0x08 || !name(aml + i + 1, wanted) || aml[i + 5] != op) {
157 continue;
158 }
159 body = i + 6;
160 return package(aml, end, body, limit);
161 }
162 return false;
163}
164
165bool resources(const uint8_t* aml, size_t start, size_t end, const uint8_t*& data, size_t& size) {
166 size_t body = 0, limit = 0;
167 if (!namedObject(aml, start, end, "_CRS", 0x11, body, limit)) {
168 return false;
169 }
170 uint64_t declared = 0;
171 if (!integer(aml, limit, body, declared) || declared > limit - body) {
172 return false;
173 }
174 data = aml + body;
175 size = static_cast<size_t>(declared);
176 return true;
177}
178
179bool resource(const uint8_t* data, size_t size, size_t& cursor, uint8_t& tag,
180 const uint8_t*& payload, size_t& length) {
181 if (cursor >= size) {
182 return false;
183 }
184 const uint8_t lead = data[cursor++];
185 if (lead & 0x80) {
186 if (size - cursor < 2) {
187 return false;
188 }
189 tag = lead;
190 length = read16(data + cursor);
191 cursor += 2;
192 } else {
193 tag = lead & 0xf8;
194 length = lead & 7;
195 }
196 if (length > size - cursor) {
197 return false;
198 }
199 payload = data + cursor;
200 cursor += length;
201 return true;
202}
203
204bool addPciWindow(VirtAcpiInfo& out, uint8_t tag, const uint8_t* payload, size_t length) {
205 const size_t width = tag == 0x88 ? 2 : tag == 0x87 ? 4 : tag == 0x8a ? 8 : 0;
206 if (!width || length < 3 + width * 5 || out.pciWindowCount == 8) {
207 return false;
208 }
209 const uint8_t type = payload[0];
210 if (type > 1) {
211 return false;
212 }
213 const uint8_t* fields = payload + 3;
214 uint64_t minimum = 0, maximum = 0, translation = 0, bytes = 0;
215 for (size_t i = 0; i < width; ++i) {
216 minimum |= uint64_t(fields[width + i]) << (i * 8);
217 maximum |= uint64_t(fields[2 * width + i]) << (i * 8);
218 translation |= uint64_t(fields[3 * width + i]) << (i * 8);
219 bytes |= uint64_t(fields[4 * width + i]) << (i * 8);
220 }
221 if (!bytes || maximum < minimum || bytes - 1 > maximum - minimum ||
222 minimum > UINT64_MAX - translation || minimum + translation > UINT64_MAX - bytes) {
223 return false;
224 }
225 out.pciWindows[out.pciWindowCount++] = {type == 1 ? 0x01000000U
226 : tag == 0x8a ? 0x03000000U
227 : 0x02000000U,
228 minimum, minimum + translation, bytes};
229 return true;
230}
231
232uint32_t linkIrq(const uint8_t* aml, size_t size, const char* link) {
233 size_t start = 0, end = 0;
234 if (!device(aml, size, link, start, end)) {
235 return 0;
236 }
237 const uint8_t* data = nullptr;
238 size_t length = 0;
239 if (!resources(aml, start, end, data, length)) {
240 return 0;
241 }
242 size_t cursor = 0;
243 uint8_t tag = 0;
244 const uint8_t* payload = nullptr;
245 size_t bytes = 0;
246 while (resource(data, length, cursor, tag, payload, bytes)) {
247 if (tag == 0x89 && bytes >= 6 && (payload[0] == 1 || payload[0] == 9) && payload[1] == 1) {
248 const uint32_t irq = read32(payload + 2);
249 return irq >= 32 && irq < 256 ? irq : 0;
250 }
251 }
252 return 0;
253}
254
255bool pciResources(const uint8_t* aml, size_t size, VirtAcpiInfo& out) {
256 size_t start = 0, end = 0;
257 if (!device(aml, size, "PCI0", start, end)) {
258 return false;
259 }
260 const uint8_t* data = nullptr;
261 size_t length = 0;
262 if (!resources(aml, start, end, data, length)) {
263 return false;
264 }
265 size_t cursor = 0;
266 uint8_t tag = 0;
267 const uint8_t* payload = nullptr;
268 size_t bytes = 0;
269 while (resource(data, length, cursor, tag, payload, bytes)) {
270 addPciWindow(out, tag, payload, bytes);
271 }
272 if (!out.pciWindowCount) {
273 return false;
274 }
275
276 size_t body = 0, limit = 0;
277 if (!namedObject(aml, start, end, "_PRT", 0x13, body, limit)) {
278 return false;
279 }
280 uint64_t entries = 0;
281 if (!integer(aml, limit, body, entries) || entries > 1024) {
282 return false;
283 }
284 for (uint64_t i = 0; i < entries; ++i) {
285 if (body >= limit || aml[body++] != 0x12) {
286 return false;
287 }
288 size_t itemEnd = 0;
289 if (!package(aml, limit, body, itemEnd) || body >= itemEnd || aml[body++] != 4) {
290 return false;
291 }
292 uint64_t address = 0, pin = 0, sourceIndex = 0;
293 if (!integer(aml, itemEnd, body, address) || !integer(aml, itemEnd, body, pin) ||
294 itemEnd - body < 4) {
295 return false;
296 }
297 const char* link = reinterpret_cast<const char*>(aml + body);
298 body += 4;
299 if (!integer(aml, itemEnd, body, sourceIndex) || sourceIndex || (address & 0xffff) != 0xffff ||
300 pin >= 4) {
301 return false;
302 }
303 const uint32_t slot = (address >> 16) & 0xffff;
304 if (slot < 32) {
305 out.pciIrq[slot][pin] = linkIrq(aml, size, link);
306 }
307 body = itemEnd;
308 }
309 for (size_t slot = 0; slot < 32; ++slot) {
310 for (size_t pin = 0; pin < 4; ++pin) {
311 if (out.pciIrq[slot][pin]) {
312 return true;
313 }
314 }
315 }
316 return false;
317}
318
319void rtcResource(const uint8_t* aml, size_t size, VirtAcpiInfo& out) {
320 size_t start = 0, end = 0;
321 if (!device(aml, size, "RTC0", start, end)) {
322 return;
323 }
324 const uint8_t* data = nullptr;
325 size_t length = 0;
326 if (!resources(aml, start, end, data, length)) {
327 return;
328 }
329 size_t cursor = 0;
330 uint8_t tag = 0;
331 const uint8_t* payload = nullptr;
332 size_t bytes = 0;
333 while (resource(data, length, cursor, tag, payload, bytes)) {
334 if (tag == 0x86 && bytes >= 9 && read32(payload + 5) >= 4) {
335 out.rtc = read32(payload + 1);
336 return;
337 }
338 }
339}
340
341void parseMadt(const uint8_t* table, VirtAcpiInfo& out) {
342 const size_t length = read32(table + 4);
343 if (length < 44) {
344 return;
345 }
346 size_t cursor = 44;
347 while (cursor + 2 <= length) {
348 const uint8_t type = table[cursor];
349 const uint8_t bytes = table[cursor + 1];
350 if (bytes < 2 || bytes > length - cursor) {
351 return;
352 }
353 const uint8_t* entry = table + cursor;
354 if (type == 12 && bytes >= 24) {
355 out.gicDistributor = read64(entry + 8);
356 out.gicVersion = entry[20];
357 } else if (type == 13 && bytes >= 24 &&
358 out.msiController.type == VirtMsiController::Type::None) {
359 out.msiController = {
360 VirtMsiController::Type::GicV2m, read64(entry + 8), 0x1000,
361 static_cast<uint16_t>((read32(entry + 16) & 1) ? read16(entry + 22) : 0),
362 static_cast<uint16_t>((read32(entry + 16) & 1) ? read16(entry + 20) : 0)};
363 } else if (type == 14 && bytes >= 16) {
364 out.gicRedistributor = read64(entry + 4);
365 } else if (type == 15 && bytes >= 20 &&
366 out.msiController.type == VirtMsiController::Type::None) {
367 out.msiController = {VirtMsiController::Type::GicV3Its, read64(entry + 8), 0x20000, 0, 0};
368 out.msiItsId = read32(entry + 4);
369 } else if (type == 11 && bytes >= 40 && (read32(entry + 12) & 1)) {
370 out.gicCpu = read64(entry + 32);
371 }
372 cursor += bytes;
373 }
374}
375
376void parseGtdt(const uint8_t* table, VirtAcpiInfo& out) {
377 if (read32(table + 4) >= 68) {
378 out.physicalTimerIrq = read32(table + 56);
379 out.virtualTimerIrq = read32(table + 64);
380 }
381}
382
383void parseSpcr(const uint8_t* table, VirtAcpiInfo& out) {
384 if (read32(table + 4) >= 58 && table[36] == 3 && table[40] == 0) {
385 out.uart = read64(table + 44);
386 out.uartIrq = read32(table + 54);
387 }
388}
389
390void parseMcfg(const uint8_t* table, VirtAcpiInfo& out) {
391 const size_t length = read32(table + 4);
392 if (length < 60 || (length - 44) % 16) {
393 return;
394 }
395 for (size_t cursor = 44; cursor + 16 <= length; cursor += 16) {
396 if (read16(table + cursor + 8) != 0) {
397 continue;
398 }
399 const uint8_t first = table[cursor + 10];
400 const uint8_t last = table[cursor + 11];
401 const uint64_t base = read64(table + cursor);
402 if (last >= first && !(base & 0xfffff)) {
403 out.pciHost = {base, uint64_t(last - first + 1) << 20, first, last};
404 return;
405 }
406 }
407}
408
409bool pciMsiIdentity(const uint8_t* table, const VirtAcpiInfo& info) {
410 if (!table || info.msiController.type != VirtMsiController::Type::GicV3Its ||
411 !info.pciHost.size) {
412 return false;
413 }
414 const size_t length = read32(table + 4);
415 if (length < 48) {
416 return false;
417 }
418 const uint32_t count = read32(table + 36);
419 size_t offset = read32(table + 40);
420 if (count > 1024 || offset < 48 || offset >= length) {
421 return false;
422 }
423 const uint32_t firstId = info.pciHost.firstBus << 8;
424 const uint32_t lastId = (info.pciHost.lastBus << 8) | 0xff;
425 for (uint32_t index = 0; index < count; ++index) {
426 if (offset > length - 16) {
427 return false;
428 }
429 const uint8_t* node = table + offset;
430 const size_t size = read16(node + 1);
431 if (size < 16 || size > length - offset) {
432 return false;
433 }
434 if (node[0] == 2 && size >= 36 && !read32(node + 28)) {
435 const uint32_t mappings = read32(node + 8);
436 const size_t mappingOffset = read32(node + 12);
437 if (mappingOffset >= 36 && mappingOffset <= size && mappings <= (size - mappingOffset) / 20) {
438 for (uint32_t i = 0; i < mappings; ++i) {
439 const uint8_t* map = node + mappingOffset + i * 20;
440 const uint32_t input = read32(map);
441 const uint32_t output = read32(map + 8);
442 const uint32_t target = read32(map + 12);
443 if (read32(map + 16) || input > firstId || output != input ||
444 uint64_t(input) + read32(map + 4) < lastId || target > length - 24) {
445 continue;
446 }
447 const uint8_t* its = table + target;
448 if (its[0] == 0 && read16(its + 1) >= 24 && read16(its + 1) <= length - target &&
449 read32(its + 16) && read32(its + 20) == info.msiItsId) {
450 return true;
451 }
452 }
453 }
454 }
455 offset += size;
456 }
457 return false;
458}
459
460uint64_t parseFadt(const uint8_t* table, VirtAcpiInfo& out) {
461 const size_t length = read32(table + 4);
462 if (length >= 132) {
463 const uint16_t flags = read16(table + 129);
464 out.psciAvailable = flags & 1;
465 out.psciHvc = flags & 2;
466 }
467 if (length >= 148 && read64(table + 140)) {
468 return read64(table + 140);
469 }
470 return length >= 44 ? read32(table + 40) : 0;
471}
472} // namespace
473
474bool virtParseAcpi(uint64_t rsdpPhysical, const BootstrapStruct_t::MemoryMapEntry* memoryMap,
475 size_t memoryMapCount, VirtAcpiInfo& info) {
476 g_MemoryMap = memoryMap;
477 g_MemoryMapCount = memoryMapCount;
478 const uint8_t* rsdp = physical(rsdpPhysical, 36);
479 if (!rsdp || read64(rsdp) != 0x2052545020445352ULL || !checksum(rsdp, 20) || rsdp[15] < 2 ||
480 read32(rsdp + 20) < 36 || read32(rsdp + 20) > 4096 ||
481 !physical(rsdpPhysical, read32(rsdp + 20)) || !checksum(rsdp, read32(rsdp + 20))) {
482 return false;
483 }
484 const uint8_t* xsdt = tableAt(read64(rsdp + 24));
485 if (!xsdt || read32(xsdt) != XsdtSignature || (read32(xsdt + 4) - HeaderSize) % 8) {
486 return false;
487 }
488 info = {};
489 uint64_t dsdtPhysical = 0;
490 const uint8_t* iort = nullptr;
491 const size_t count = (read32(xsdt + 4) - HeaderSize) / 8;
492 for (size_t i = 0; i < count; ++i) {
493 const uint8_t* table = tableAt(read64(xsdt + HeaderSize + i * 8));
494 if (!table) {
495 continue;
496 }
497 switch (read32(table)) {
498 case MadtSignature:
499 parseMadt(table, info);
500 break;
501 case GtdtSignature:
502 parseGtdt(table, info);
503 break;
504 case SpcrSignature:
505 parseSpcr(table, info);
506 break;
507 case McfgSignature:
508 parseMcfg(table, info);
509 break;
510 case IortSignature:
511 iort = table;
512 break;
513 case FadtSignature:
514 dsdtPhysical = parseFadt(table, info);
515 break;
516 default:
517 break;
518 }
519 }
520 if (info.pciHost.size) {
521 const uint8_t* dsdt = tableAt(dsdtPhysical);
522 if (!dsdt || read32(dsdt) != 0x54445344 ||
523 !pciResources(dsdt + HeaderSize, read32(dsdt + 4) - HeaderSize, info)) {
524 info.pciHost = {};
525 info.pciWindowCount = 0;
526 } else {
527 rtcResource(dsdt + HeaderSize, read32(dsdt + 4) - HeaderSize, info);
528 }
529 }
530 info.pciMsiIdentity = pciMsiIdentity(iort, info);
531 return info.uart && info.gicDistributor &&
532 ((info.gicVersion == 2 && info.gicCpu) ||
533 (info.gicVersion == 3 && info.gicRedistributor)) &&
534 info.physicalTimerIrq && info.virtualTimerIrq;
535}