| // Copyright 2016 The Fuchsia Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <assert.h> | 
 | #include <limits.h> | 
 | #include <stdint.h> | 
 | #include <stdio.h> | 
 |  | 
 | #include <acpica/acpi.h> | 
 |  | 
 | #include "acpi-private.h" | 
 |  | 
 | static inline void do_indent(unsigned int level) { | 
 |   while (level) { | 
 |     printf("  "); | 
 |     level--; | 
 |   } | 
 | } | 
 |  | 
 | #define INDENT_PRINTF(...) \ | 
 |   do {                     \ | 
 |     do_indent(level);      \ | 
 |     printf(__VA_ARGS__);   \ | 
 |   } while (0) | 
 |  | 
 | enum print_resource_request { | 
 |   CURRENT_RESOURCES, | 
 |   POSSIBLE_RESOURCES, | 
 | }; | 
 |  | 
 | static ACPI_STATUS acpi_print_resources(ACPI_HANDLE object, unsigned int level, | 
 |                                         enum print_resource_request type) { | 
 |   ACPI_BUFFER buffer = { | 
 |       .Length = ACPI_ALLOCATE_BUFFER, | 
 |       .Pointer = NULL, | 
 |   }; | 
 |   ACPI_STATUS status = AE_BAD_PARAMETER; | 
 |   if (type == POSSIBLE_RESOURCES) { | 
 |     status = AcpiGetPossibleResources(object, &buffer); | 
 |   } else if (type == CURRENT_RESOURCES) { | 
 |     status = AcpiGetCurrentResources(object, &buffer); | 
 |   } else { | 
 |     printf("Invalid resource type to print\n"); | 
 |     return AE_BAD_PARAMETER; | 
 |   } | 
 |  | 
 |   if (status != AE_OK) { | 
 |     if (buffer.Pointer) { | 
 |       AcpiOsFree(buffer.Pointer); | 
 |     } | 
 |     return status; | 
 |   } | 
 |   if (type == POSSIBLE_RESOURCES) { | 
 |     INDENT_PRINTF("PRS:\n"); | 
 |   } else if (type == CURRENT_RESOURCES) { | 
 |     INDENT_PRINTF("CRS:\n"); | 
 |   } | 
 |  | 
 |   uintptr_t entry_addr = (uintptr_t)buffer.Pointer; | 
 |   ACPI_RESOURCE* res = (ACPI_RESOURCE*)entry_addr; | 
 |   level += 1; | 
 |   while (res->Type != ACPI_RESOURCE_TYPE_END_TAG) { | 
 |     INDENT_PRINTF("Entry: "); | 
 |     level += 1; | 
 |     switch (res->Type) { | 
 |       case ACPI_RESOURCE_TYPE_IO: { | 
 |         printf("IO\n"); | 
 |         ACPI_RESOURCE_IO* io = &res->Data.Io; | 
 |         INDENT_PRINTF("io_decode: %d\n", io->IoDecode); | 
 |         INDENT_PRINTF("alignment: %d\n", io->Alignment); | 
 |         INDENT_PRINTF("addrlen: %d\n", io->AddressLength); | 
 |         INDENT_PRINTF("address min: %#04x\n", io->Minimum); | 
 |         INDENT_PRINTF("address max: %#04x\n", io->Maximum); | 
 |         break; | 
 |       } | 
 |       case ACPI_RESOURCE_TYPE_ADDRESS16: { | 
 |         printf("Address16\n"); | 
 |         ACPI_RESOURCE_ADDRESS16* a16 = &res->Data.Address16; | 
 |         INDENT_PRINTF("res_type: %d\n", a16->ResourceType); | 
 |         INDENT_PRINTF("produce_consume: %d\n", a16->ProducerConsumer); | 
 |         INDENT_PRINTF("decode: %d\n", a16->Decode); | 
 |         INDENT_PRINTF("min_addr_fixed: %d\n", a16->MinAddressFixed); | 
 |         INDENT_PRINTF("max_addr_fixed: %d\n", a16->MaxAddressFixed); | 
 |         INDENT_PRINTF("address granularity: %#04x\n", a16->Address.Granularity); | 
 |         INDENT_PRINTF("address min: %#04x\n", a16->Address.Minimum); | 
 |         INDENT_PRINTF("address max: %#04x\n", a16->Address.Maximum); | 
 |         INDENT_PRINTF("address xlat offset: %#04x\n", a16->Address.TranslationOffset); | 
 |         INDENT_PRINTF("address len: %#04x\n", a16->Address.AddressLength); | 
 |         // TODO: extract MTRR info from a16->Info | 
 |         break; | 
 |       } | 
 |       case ACPI_RESOURCE_TYPE_ADDRESS32: { | 
 |         printf("Address32\n"); | 
 |         ACPI_RESOURCE_ADDRESS32* a32 = &res->Data.Address32; | 
 |         INDENT_PRINTF("res_type: %d\n", a32->ResourceType); | 
 |         INDENT_PRINTF("produce_consume: %d\n", a32->ProducerConsumer); | 
 |         INDENT_PRINTF("decode: %d\n", a32->Decode); | 
 |         INDENT_PRINTF("min_addr_fixed: %d\n", a32->MinAddressFixed); | 
 |         INDENT_PRINTF("max_addr_fixed: %d\n", a32->MaxAddressFixed); | 
 |         INDENT_PRINTF("address granularity: %#08x\n", a32->Address.Granularity); | 
 |         INDENT_PRINTF("address min: %#08x\n", a32->Address.Minimum); | 
 |         INDENT_PRINTF("address max: %#08x\n", a32->Address.Maximum); | 
 |         INDENT_PRINTF("address xlat offset: %#08x\n", a32->Address.TranslationOffset); | 
 |         INDENT_PRINTF("address len: %#08x\n", a32->Address.AddressLength); | 
 |         // TODO: extract MTRR info from a32->Info | 
 |         break; | 
 |       } | 
 |       case ACPI_RESOURCE_TYPE_IRQ: { | 
 |         printf("IRQ\n"); | 
 |         ACPI_RESOURCE_IRQ* irq = &res->Data.Irq; | 
 |         INDENT_PRINTF("trigger: %s\n", irq->Triggering == ACPI_EDGE_SENSITIVE ? "edge" : "level"); | 
 |         const char* pol = "invalid"; | 
 |         switch (irq->Polarity) { | 
 |           case ACPI_ACTIVE_BOTH: | 
 |             pol = "both"; | 
 |             break; | 
 |           case ACPI_ACTIVE_LOW: | 
 |             pol = "low"; | 
 |             break; | 
 |           case ACPI_ACTIVE_HIGH: | 
 |             pol = "high"; | 
 |             break; | 
 |         } | 
 |         INDENT_PRINTF("polarity: %s\n", pol); | 
 |         INDENT_PRINTF("sharable: %d\n", irq->Shareable); | 
 |         INDENT_PRINTF("wake_cap: %d\n", irq->WakeCapable); | 
 |         for (unsigned int i = 0; i < irq->InterruptCount; ++i) { | 
 |           INDENT_PRINTF("irq #%d: %d\n", i, irq->Interrupts[i]); | 
 |         } | 
 |         break; | 
 |       } | 
 |       case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: { | 
 |         printf("Extended IRQ\n"); | 
 |         ACPI_RESOURCE_EXTENDED_IRQ* irq = &res->Data.ExtendedIrq; | 
 |         INDENT_PRINTF("produce_consume: %d\n", irq->ProducerConsumer); | 
 |         INDENT_PRINTF("trigger: %s\n", irq->Triggering == ACPI_EDGE_SENSITIVE ? "edge" : "level"); | 
 |         const char* pol = "invalid"; | 
 |         switch (irq->Polarity) { | 
 |           case ACPI_ACTIVE_BOTH: | 
 |             pol = "both"; | 
 |             break; | 
 |           case ACPI_ACTIVE_LOW: | 
 |             pol = "low"; | 
 |             break; | 
 |           case ACPI_ACTIVE_HIGH: | 
 |             pol = "high"; | 
 |             break; | 
 |         } | 
 |         INDENT_PRINTF("polarity: %s\n", pol); | 
 |         INDENT_PRINTF("sharable: %d\n", irq->Shareable); | 
 |         INDENT_PRINTF("wake_cap: %d\n", irq->WakeCapable); | 
 |         for (unsigned int i = 0; i < irq->InterruptCount; ++i) { | 
 |           INDENT_PRINTF("irq #%d: %d\n", i, irq->Interrupts[i]); | 
 |         } | 
 |         break; | 
 |       } | 
 |       default: | 
 |         printf("Unknown (type %u)\n", res->Type); | 
 |     } | 
 |     level -= 1; | 
 |  | 
 |     entry_addr += res->Length; | 
 |     res = (ACPI_RESOURCE*)entry_addr; | 
 |   } | 
 |   level -= 1; | 
 |  | 
 |   AcpiOsFree(buffer.Pointer); | 
 |   return AE_OK; | 
 | } | 
 |  | 
 | static ACPI_STATUS acpi_get_pcie_devices_crs(ACPI_HANDLE object, UINT32 nesting_level, | 
 |                                              void* context, void** ret) { | 
 |   printf("Found object %p\n", object); | 
 |   return acpi_print_resources(object, 1, CURRENT_RESOURCES); | 
 | } | 
 |  | 
 | static void acpi_debug_pcie_crs(void) { | 
 |   ACPI_STATUS status = AcpiGetDevices((char*)"PNP0A08", acpi_get_pcie_devices_crs, NULL, NULL); | 
 |   if (status != AE_OK) { | 
 |     printf("Could not find PCIe root complex\n"); | 
 |   } | 
 | } | 
 |  | 
 | static ACPI_STATUS acpi_print_prt(unsigned int level, ACPI_HANDLE object) { | 
 |   ACPI_STATUS status = AE_OK; | 
 |  | 
 |   ACPI_BUFFER buffer = { | 
 |       // Request that the ACPI subsystem allocate the buffer | 
 |       .Length = ACPI_ALLOCATE_BUFFER, | 
 |       .Pointer = NULL, | 
 |   }; | 
 |   status = AcpiGetIrqRoutingTable(object, &buffer); | 
 |   if (status != AE_OK) { | 
 |     if (buffer.Pointer) { | 
 |       AcpiOsFree(buffer.Pointer); | 
 |     } | 
 |     return status; | 
 |   } | 
 |   assert(buffer.Pointer); | 
 |  | 
 |   uintptr_t entry_addr = (uintptr_t)buffer.Pointer; | 
 |   ACPI_PCI_ROUTING_TABLE* entry; | 
 |   for (entry = (ACPI_PCI_ROUTING_TABLE*)entry_addr; entry->Length != 0; | 
 |        entry_addr += entry->Length, entry = (ACPI_PCI_ROUTING_TABLE*)entry_addr) { | 
 |     assert(entry_addr <= (uintptr_t)buffer.Pointer + buffer.Length); | 
 |  | 
 |     INDENT_PRINTF("Entry:\n"); | 
 |     level += 1; | 
 |     if (entry->Pin > 3) { | 
 |       INDENT_PRINTF("Pin: Invalid (%08x)\n", entry->Pin); | 
 |     } else { | 
 |       INDENT_PRINTF("Pin: INT%c\n", 'A' + entry->Pin); | 
 |     } | 
 |     INDENT_PRINTF("Address: %#016llx\n", entry->Address); | 
 |     level += 1; | 
 |     INDENT_PRINTF("Dev ID: %#04x\n", (uint16_t)(entry->Address >> 16)); | 
 |     level -= 1; | 
 |  | 
 |     if (entry->Source[0]) { | 
 |       // If the Source is not just a NULL byte, then it refers to a | 
 |       // PCI Interrupt Link Device | 
 |       INDENT_PRINTF("Source: %s\n", entry->Source); | 
 |       INDENT_PRINTF("Source Index: %u\n", entry->SourceIndex); | 
 |       ACPI_HANDLE ild; | 
 |       status = AcpiGetHandle(object, entry->Source, &ild); | 
 |       if (status != AE_OK) { | 
 |         INDENT_PRINTF("Could not lookup Interrupt Link Device\n"); | 
 |         continue; | 
 |       } | 
 |       status = acpi_print_resources(ild, 2, CURRENT_RESOURCES); | 
 |       if (status != AE_OK) { | 
 |         INDENT_PRINTF("Could not lookup ILD CRS\n"); | 
 |       } | 
 |       status = acpi_print_resources(ild, 2, POSSIBLE_RESOURCES); | 
 |       if (status != AE_OK) { | 
 |         INDENT_PRINTF("Could not lookup ILD PRS\n"); | 
 |       } | 
 |     } else { | 
 |       // Otherwise, it just refers to a global IRQ number that the pin | 
 |       // is connected to | 
 |       INDENT_PRINTF("GlobalIRQ: %u\n", entry->SourceIndex); | 
 |     } | 
 |     level -= 1; | 
 |   } | 
 |  | 
 |   AcpiOsFree(buffer.Pointer); | 
 |   return AE_OK; | 
 | } | 
 |  | 
 | static ACPI_STATUS acpi_get_pcie_devices_irq(ACPI_HANDLE object, UINT32 nesting_level, | 
 |                                              void* context, void** ret) { | 
 |   ACPI_STATUS status = acpi_print_prt(nesting_level, object); | 
 |   if (status != AE_OK) { | 
 |     printf("Failed to print PRT for root complex\n"); | 
 |     return status; | 
 |   } | 
 |  | 
 |   // Enumerate root ports | 
 |   ACPI_HANDLE child = NULL; | 
 |   while (1) { | 
 |     status = AcpiGetNextObject(ACPI_TYPE_DEVICE, object, child, &child); | 
 |     if (status == AE_NOT_FOUND) { | 
 |       break; | 
 |     } else if (status != AE_OK) { | 
 |       printf("Failed to get next child object of root complex\n"); | 
 |       return status; | 
 |     } | 
 |  | 
 |     ACPI_OBJECT object = {0}; | 
 |     ACPI_BUFFER buffer = { | 
 |         .Length = sizeof(object), | 
 |         .Pointer = &object, | 
 |     }; | 
 |     status = AcpiEvaluateObject(child, (char*)"_ADR", NULL, &buffer); | 
 |     if (status != AE_OK || buffer.Length < sizeof(object) || object.Type != ACPI_TYPE_INTEGER) { | 
 |       continue; | 
 |     } | 
 |     UINT64 data = object.Integer.Value; | 
 |     unsigned int level = nesting_level; | 
 |     INDENT_PRINTF("Device %#02x Function %#01x:\n", (uint8_t)(data >> 16), (uint8_t)(data & 0x7)); | 
 |     status = acpi_print_prt(nesting_level + 1, child); | 
 |     if (status != AE_OK) { | 
 |       continue; | 
 |     } | 
 |   } | 
 |  | 
 |   return AE_OK; | 
 | } | 
 |  | 
 | static void acpi_debug_pcie_irq_routing(void) { | 
 |   ACPI_STATUS status = AcpiGetDevices((char*)"PNP0A08", acpi_get_pcie_devices_irq, NULL, NULL); | 
 |   if (status != AE_OK) { | 
 |     printf("Could not enumerate PRTs\n"); | 
 |   } | 
 | } | 
 |  | 
 | static ACPI_STATUS acpi_debug_print_device_name(ACPI_HANDLE object, UINT32 nesting_level, | 
 |                                                 void* context, void** ret) { | 
 |   acpi::UniquePtr<ACPI_DEVICE_INFO> info; | 
 |   if (auto res = acpi::GetObjectInfo(object); res.is_error()) { | 
 |     return res.error_value(); | 
 |   } else { | 
 |     info = std::move(res.value()); | 
 |   } | 
 |  | 
 |   unsigned int level = nesting_level; | 
 |   INDENT_PRINTF("%4s\n", (char*)&info->Name); | 
 |  | 
 |   return AE_OK; | 
 | } | 
 |  | 
 | static void acpi_debug_walk_ns(void) { | 
 |   ACPI_STATUS status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, INT_MAX, | 
 |                                          acpi_debug_print_device_name, NULL, NULL, NULL); | 
 |   if (status != AE_OK) { | 
 |     printf("Failed to walk namespace\n"); | 
 |   } | 
 | } |