1 // Copyright 2020 The Fuchsia Authors
2 // Copyright 2021 Travis Geiselbrecht
3 //
4 // Use of this source code is governed by a MIT-style
5 // license that can be found in the LICENSE file or at
6 // https://opensource.org/licenses/MIT
7
8 #include <lib/acpi_lite.h>
9
10 #include <inttypes.h>
11 #include <string.h>
12 #include <lk/compiler.h>
13 #include <lk/cpp.h>
14 #include <lk/debug.h>
15 #include <lk/err.h>
16 #include <lk/trace.h>
17 #include <kernel/vm.h>
18
19 // uses the vm to map in ACPI tables as they are found
20 static_assert(WITH_KERNEL_VM);
21
22 #define LOCAL_TRACE 0
23
24 // global state of the acpi lite library
25 struct acpi_lite_state {
26 const acpi_rsdp* rsdp;
27 paddr_t rsdp_pa;
28 const acpi_rsdt_xsdt* sdt;
29 paddr_t sdt_pa;
30 bool xsdt; // are the pointers in the SDT 64 or 32bit?
31
32 size_t num_tables; // number of top level tables
33 const void **tables; // array of pointers to detected tables
34 } acpi;
35
36 // map a region around a physical address
map_region(paddr_t pa,size_t len,const char * name)37 static void *map_region(paddr_t pa, size_t len, const char *name) {
38 const auto pa_page_aligned = ROUNDDOWN(pa, PAGE_SIZE);
39 const size_t align_offset = pa - pa_page_aligned;
40 size_t map_len = ROUNDUP(len + align_offset, PAGE_SIZE);
41
42 uint perms = ARCH_MMU_FLAG_PERM_RO;
43 if (arch_mmu_supports_nx_mappings()) {
44 perms |= ARCH_MMU_FLAG_PERM_NO_EXECUTE;
45 }
46
47 void *ptr;
48 status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), name, map_len,
49 &ptr, 0, pa_page_aligned, 0, perms);
50 if (err < 0) {
51 return nullptr;
52 }
53
54 return (void *)((uintptr_t)ptr + align_offset);
55 }
56
acpi_checksum(const void * _buf,size_t len)57 static uint8_t acpi_checksum(const void* _buf, size_t len) {
58 const uint8_t* buf = static_cast<const uint8_t*>(_buf);
59
60 uint8_t c = 0;
61 for (size_t i = 0; i < len; i++) {
62 c += buf[i];
63 }
64
65 return c;
66 }
67
validate_rsdp(const acpi_rsdp * rsdp,bool debug_output=true)68 static bool validate_rsdp(const acpi_rsdp* rsdp, bool debug_output = true) {
69 // check the signature
70 if (memcmp(ACPI_RSDP_SIG, rsdp->sig, 8)) {
71 // Generates a huge pile of info as it scans for RSDP
72 if (debug_output && LOCAL_TRACE) {
73 LTRACEF("acpi rsdp signature failed:\n");
74 hexdump8(rsdp->sig, 8);
75 }
76 return false;
77 }
78
79 // validate the v1 checksum on the first 20 bytes of the table
80 uint8_t c = acpi_checksum(rsdp, 20);
81 if (c) {
82 LTRACEF("v1 checksum failed\n");
83 return false;
84 }
85
86 // is it v2?
87 LTRACEF("rsdp version %u\n", rsdp->revision);
88 if (rsdp->revision >= 2) {
89 LTRACEF("rsdp length %u\n", rsdp->length);
90 if (rsdp->length < 36 || rsdp->length > 4096) {
91 // keep the table length within reason
92 return false;
93 }
94
95 c = acpi_checksum(rsdp, rsdp->length);
96 if (c) {
97 LTRACEF("full checksum failed\n");
98 return false;
99 }
100 }
101
102 // seems okay
103 return true;
104 }
105
106 // search the bios region on a PC for the Root System Description Pointer (RSDP)
find_rsdp_pc()107 static paddr_t find_rsdp_pc() {
108 LTRACE_ENTRY;
109
110 const paddr_t range_start = 0xe0000;
111 const paddr_t range_end = 0x100000;
112 const size_t len = range_end - range_start;
113
114 // map all of the scannable area, 0xe0000...1MB
115 const uint8_t *bios_ptr;
116 status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "acpi rsdp bios area", len,
117 (void **)&bios_ptr, 0, range_start, 0, ARCH_MMU_FLAG_PERM_RO);
118 if (err < 0) {
119 return 0;
120 }
121 LTRACEF("bios area mapping at %p\n", bios_ptr);
122
123 // free the region when we exit
124 auto ac = lk::make_auto_call([bios_ptr]() {
125 vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)bios_ptr);
126 });
127
128 // search for it in the BIOS EBDA area (0xe0000..0xfffff) on 16 byte boundaries
129 for (size_t i = 0; i < len; i += 16) {
130 const auto rsdp = reinterpret_cast<const acpi_rsdp*>(bios_ptr + i);
131
132 if (validate_rsdp(rsdp, false)) {
133 LTRACEF("found rsdp at vaddr %p, paddr %#lx\n", bios_ptr + i, range_start + i);
134 return range_start + i;
135 }
136 }
137
138 return 0;
139 }
140
validate_sdt(const acpi_rsdt_xsdt * sdt,size_t * num_tables,bool * xsdt)141 static bool validate_sdt(const acpi_rsdt_xsdt* sdt, size_t* num_tables, bool* xsdt) {
142 LTRACEF("pointer %p\n", sdt);
143
144 // bad pointer
145 if (!sdt) {
146 LTRACEF("failing due to null pointer\n");
147 return false;
148 }
149
150 // check the signature and see if it's a rsdt or xsdt
151 if (!memcmp(sdt->header.sig, "XSDT", 4)) {
152 LTRACEF("found XSDT\n");
153 *xsdt = true;
154 } else if (!memcmp(sdt->header.sig, "RSDT", 4)) {
155 LTRACEF("found RSDT\n");
156 *xsdt = false;
157 } else {
158 LTRACEF("did not find XSDT or RSDT\n");
159 return false;
160 }
161
162 // is the length sane?
163 if (sdt->header.length < 36 || sdt->header.length > 4096) {
164 LTRACEF("bad length %u\n", sdt->header.length);
165 return false;
166 }
167
168 // is it a revision we understand?
169 if (sdt->header.revision != 1) {
170 LTRACEF("revision we do not handle %u\n", sdt->header.revision);
171 return false;
172 }
173
174 // checksum the entire table
175 uint8_t c = acpi_checksum(sdt, sdt->header.length);
176 if (c) {
177 LTRACEF("failed checksum\n");
178 return false;
179 }
180
181 // compute the number of pointers to tables we have
182 *num_tables = (sdt->header.length - 36u) / (*xsdt ? 8u : 4u);
183
184 // looks okay
185 return true;
186 }
187
acpi_get_table_pa_at_index(size_t index)188 static paddr_t acpi_get_table_pa_at_index(size_t index) {
189 if (index >= acpi.num_tables) {
190 return 0;
191 }
192
193 paddr_t pa;
194 if (acpi.xsdt) {
195 pa = acpi.sdt->addr64[index];
196 } else {
197 pa = acpi.sdt->addr32[index];
198 }
199 LTRACEF("index %zu, pa %#lx\n", index, pa);
200
201 return pa;
202 }
203
acpi_get_table_at_index(size_t index)204 static const acpi_sdt_header* acpi_get_table_at_index(size_t index) {
205 if (index >= acpi.num_tables) {
206 return nullptr;
207 }
208
209 return static_cast<const acpi_sdt_header *>(acpi.tables[index]);
210 }
211
acpi_get_table_by_sig(const char * sig)212 const acpi_sdt_header* acpi_get_table_by_sig(const char* sig) {
213 // walk the list of tables
214 for (size_t i = 0; i < acpi.num_tables; i++) {
215 const auto header = acpi_get_table_at_index(i);
216 if (!header) {
217 continue;
218 }
219
220 if (!memcmp(sig, header->sig, 4)) {
221 // checksum should already have been validated when the table was loaded
222 return header;
223 }
224 }
225
226 return nullptr;
227 }
228
initialize_table(size_t i)229 static status_t initialize_table(size_t i) {
230 char name[64];
231 snprintf(name, sizeof(name), "acpi table %zu", i);
232
233 const size_t table_initial_len = PAGE_SIZE; // enough to read the header
234 auto pa = acpi_get_table_pa_at_index(i);
235
236 const acpi_sdt_header *header = (const acpi_sdt_header *)map_region(pa, table_initial_len, name);
237 if (!header) {
238 dprintf(INFO, "ACPI LITE: failed to map table %zu address %#" PRIxPTR "\n", i, pa);
239 return ERR_NOT_FOUND;
240 }
241
242 // cleanup the mapping that maps just the first page when we exit
243 auto cleanup_header_mapping = lk::make_auto_call([header]() {
244 vmm_free_region(vmm_get_kernel_aspace(), ROUNDDOWN((vaddr_t)header, PAGE_SIZE));
245 });
246
247 // check the header and determine the real size
248 if (header->length > 1024*1024) {
249 // probably bogus?
250 dprintf(INFO, "ACPI LITE: table %zu has length %u, too large\n", i, header->length);
251 return ERR_NOT_FOUND;
252 }
253 if (header->length < sizeof(*header)) {
254 return ERR_NOT_FOUND;
255 }
256
257 // try to map the real table
258 char sig[5] = {};
259 sig[0] = header->sig[0];
260 sig[1] = header->sig[1];
261 sig[2] = header->sig[2];
262 sig[3] = header->sig[3];
263 snprintf(name, sizeof(name), "acpi table %s", sig);
264 acpi.tables[i] = map_region(pa, header->length, name);
265 if (!acpi.tables[i]) {
266 dprintf(INFO, "ACPI LITE: failed to map table %zu address %#" PRIxPTR "\n", i, pa);
267 return ERR_NOT_FOUND;
268 }
269
270 LTRACEF("table %zu (%s) mapped at %p\n", i, sig, acpi.tables[i]);
271
272 // ODO compute checksum on table?
273 header = (const acpi_sdt_header *)acpi.tables[i];
274 uint8_t c = acpi_checksum(header, header->length);
275 if (c != 0) {
276 dprintf(INFO, "ACPI LITE: table %zu (%s) fails checksum\n", i, sig);
277 acpi.tables[i] = nullptr;
278 return ERR_NOT_FOUND;
279 }
280
281 return NO_ERROR;
282 }
283
acpi_lite_init(paddr_t rsdp_pa)284 status_t acpi_lite_init(paddr_t rsdp_pa) {
285 LTRACEF("passed in rsdp %#" PRIxPTR "\n", rsdp_pa);
286
287 // see if the rsdp pointer is valid
288 if (rsdp_pa == 0) {
289 // search around for it in a platform-specific way
290 #if PLATFORM_PC
291 rsdp_pa = find_rsdp_pc();
292 if (rsdp_pa == 0) {
293 dprintf(INFO, "ACPI LITE: couldn't find ACPI RSDP in BIOS area\n");
294 }
295 #endif
296
297 if (rsdp_pa == 0) {
298 return ERR_NOT_FOUND;
299 }
300 }
301
302 const size_t rsdp_area_len = 0x1000; // 4K should cover it. TODO: see if it's specced
303 const void * const rsdp_ptr = map_region(rsdp_pa, rsdp_area_len, "acpi rsdp area");
304 if (!rsdp_ptr) {
305 dprintf(INFO, "ACPI LITE: failed to map RSDP address %#" PRIxPTR " to virtual\n", rsdp_pa);
306 return ERR_NOT_FOUND;
307 }
308 LTRACEF("rsdp mapped at %p\n", rsdp_ptr);
309
310 // free the region if we abort
311 auto cleanup_rsdp_mapping = lk::make_auto_call([rsdp_ptr]() {
312 vmm_free_region(vmm_get_kernel_aspace(), ROUNDDOWN((vaddr_t)rsdp_ptr, PAGE_SIZE));
313 acpi.rsdp_pa = 0;
314 acpi.rsdp = nullptr;
315 });
316
317 // see if the RSDP is there
318 acpi.rsdp = static_cast<const acpi_rsdp*>(rsdp_ptr);
319 if (!validate_rsdp(acpi.rsdp)) {
320 dprintf(INFO, "ACPI LITE: RSDP structure does not check out\n");
321 return ERR_NOT_FOUND;
322 }
323 acpi.rsdp_pa = rsdp_pa;
324
325 dprintf(SPEW, "ACPI LITE: RSDP checks out, found at %#lx, revision %u\n",
326 acpi.rsdp_pa, acpi.rsdp->revision);
327
328 // find the pointer to either the RSDT or XSDT
329 acpi.sdt = nullptr;
330 if (acpi.rsdp->revision < 2) {
331 // v1 RSDP, pointing at a RSDT
332 LTRACEF("v1 RSDP, using 32 bit RSDT address %#x\n", acpi.rsdp->rsdt_address);
333 acpi.sdt_pa = acpi.rsdp->rsdt_address;
334 } else {
335 // v2+ RSDP, pointing at a XSDT
336 LTRACEF("v2+ RSDP, usingying 64 bit XSDT address %#" PRIx64 "\n", acpi.rsdp->xsdt_address);
337 acpi.sdt_pa = acpi.rsdp->xsdt_address;
338 }
339
340 // map the *sdt somewhere
341 const size_t sdt_area_len = 0x1000; // 4K should cover it. TODO: see if it's specced
342 const void * const sdt_ptr = map_region(acpi.sdt_pa, sdt_area_len, "acpi sdt area");
343 if (!sdt_ptr) {
344 dprintf(INFO, "ACPI LITE: failed to map SDT address %#" PRIxPTR " to virtual\n", acpi.sdt_pa);
345 return ERR_NOT_FOUND;
346 }
347 LTRACEF("sdt mapped at %p\n", sdt_ptr);
348
349 auto cleanup_sdt_mapping = lk::make_auto_call([sdt_ptr]() {
350 vmm_free_region(vmm_get_kernel_aspace(), ROUNDDOWN((vaddr_t)sdt_ptr, PAGE_SIZE));
351 acpi.sdt_pa = 0;
352 acpi.sdt = nullptr;
353 });
354
355 acpi.sdt = static_cast<const acpi_rsdt_xsdt *>(sdt_ptr);
356
357 if (!validate_sdt(acpi.sdt, &acpi.num_tables, &acpi.xsdt)) {
358 dprintf(INFO, "ACPI LITE: RSDT/XSDT structure does not check out\n");
359 return ERR_NOT_FOUND;
360 }
361
362 dprintf(SPEW, "ACPI LITE: RSDT/XSDT checks out, %zu tables\n", acpi.num_tables);
363
364 // map all of the tables in
365 acpi.tables = new const void *[acpi.num_tables];
366 for (size_t i = 0; i < acpi.num_tables; i++) {
367 status_t err = initialize_table(i);
368 if (err < 0) {
369 dprintf(INFO, "ACPI LITE: failed to initialize table %zu\n", i);
370 // for now, simply continue, the table entry should not be initialized
371 }
372 }
373
374 // we should be initialized at this point
375 cleanup_sdt_mapping.cancel();
376 cleanup_rsdp_mapping.cancel();
377
378 if (LOCAL_TRACE) {
379 acpi_lite_dump_tables(false);
380 }
381
382 return NO_ERROR;
383 }
384
acpi_lite_dump_tables(bool full_dump)385 void acpi_lite_dump_tables(bool full_dump) {
386 if (!acpi.sdt) {
387 return;
388 }
389
390 printf("root table:\n");
391 if (full_dump) {
392 hexdump(acpi.sdt, acpi.sdt->header.length);
393 }
394
395 // walk the table list
396 for (size_t i = 0; i < acpi.num_tables; i++) {
397 const auto header = acpi_get_table_at_index(i);
398 if (!header) {
399 continue;
400 }
401
402 printf("table %zu: '%c%c%c%c' len %u\n", i, header->sig[0], header->sig[1], header->sig[2],
403 header->sig[3], header->length);
404 if (full_dump) {
405 hexdump(header, header->length);
406 }
407 }
408 }
409
acpi_process_madt_entries_etc(const uint8_t search_type,const madt_entry_callback callback,void * const cookie)410 status_t acpi_process_madt_entries_etc(const uint8_t search_type, const madt_entry_callback callback, void * const cookie) {
411 const acpi_madt_table* madt =
412 reinterpret_cast<const acpi_madt_table*>(acpi_get_table_by_sig(ACPI_MADT_SIG));
413 if (!madt) {
414 return ERR_NOT_FOUND;
415 }
416
417 // bytewise array of the same table
418 const uint8_t* madt_array = reinterpret_cast<const uint8_t*>(madt);
419
420 LTRACEF("table at %p\n", madt_array);
421
422 // walk the table off the end of the header, looking for the requested type
423 size_t off = sizeof(*madt);
424 while (off < madt->header.length) {
425 uint8_t type = madt_array[off];
426 uint8_t length = madt_array[off + 1];
427
428 LTRACEF("type %u, length %u\n", type, length);
429 if (type == search_type) {
430 callback(static_cast<const void*>(&madt_array[off]), length, cookie);
431 }
432
433 off += length;
434 }
435
436 return NO_ERROR;
437 }
438
acpi_lite_dump_madt_table()439 void acpi_lite_dump_madt_table() {
440 auto local_apic_callback = [](const void *_entry, size_t entry_len, void *cookie) {
441 const auto *entry = reinterpret_cast<const struct acpi_madt_local_apic_entry *>(_entry);
442
443 printf("\tLOCAL APIC id %d, processor id %d, flags %#x\n",
444 entry->apic_id, entry->processor_id, entry->flags);
445 };
446
447 auto io_apic_callback = [](const void *_entry, size_t entry_len, void *cookie) {
448 const auto *entry = reinterpret_cast<const struct acpi_madt_io_apic_entry *>(_entry);
449
450 printf("\tIO APIC id %d, address %#x gsi base %u\n",
451 entry->io_apic_id, entry->io_apic_address, entry->global_system_interrupt_base);
452 };
453
454 auto int_source_override_callback = [](const void *_entry, size_t entry_len, void *cookie) {
455 const auto *entry = reinterpret_cast<const struct acpi_madt_int_source_override_entry *>(_entry);
456
457 printf("\tINT OVERRIDE bus %u, source %u, gsi %u, flags %#x\n",
458 entry->bus, entry->source, entry->global_sys_interrupt, entry->flags);
459 };
460 printf("MADT/APIC table:\n");
461 acpi_process_madt_entries_etc(ACPI_MADT_TYPE_LOCAL_APIC, local_apic_callback, nullptr);
462 acpi_process_madt_entries_etc(ACPI_MADT_TYPE_IO_APIC, io_apic_callback, nullptr);
463 acpi_process_madt_entries_etc(ACPI_MADT_TYPE_INT_SOURCE_OVERRIDE, int_source_override_callback, nullptr);
464 }
465
466 // vim: set ts=2 sw=2 expandtab:
467