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