blob: c15d4fb3ef74db1bbc2d453af6beb3eedb5c17b9 [file] [log] [blame]
// Copyright 2023 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 "acpi.h"
#include <lib/stdcompat/span.h>
#include <lib/zbi-format/cpu.h>
#include <lib/zbi-format/driver-config.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <algorithm>
#include <numeric>
#include <efi/types.h>
#include <phys/efi/main.h>
namespace gigaboot {
namespace {
struct AcpiRsdpRevisionDescription {
uint8_t revision;
AcpiTableSignature signature;
size_t entry_size;
const SdtHeader* (*accessor)(const AcpiRsdp&);
};
// Describes the invariants for different revisions of the SDT table.
// Note: newer revisions MUST add entries at the beginning of the array
// so that when searching forward we find the NEWEST description
// that is not greater than the RSDP we are using.
//
// Note: in C++20, it is possible to validate this invariant in a static assert.
// Older language versions don't have sufficient support for constexpr to allow
// the check statically.
constexpr AcpiRsdpRevisionDescription kRevisionTable[] = {
{
.revision = 1,
.signature = kXsdtSignature,
.entry_size = sizeof(AcpiRsdp::xsdt_address),
.accessor =
[](const auto& entry) {
return reinterpret_cast<const SdtHeader*>(entry.xsdt_address);
},
},
{
.revision = 0,
.signature = kRsdtSignature,
.entry_size = sizeof(AcpiRsdp::rsdt_address),
.accessor =
[](const auto& entry) {
return reinterpret_cast<const SdtHeader*>(
static_cast<efi_physical_addr>(entry.rsdt_address));
},
},
};
template <typename PtrNum>
const SdtHeader* TraverseEntries(const SdtHeader* table, AcpiTableSignature sig) {
static_assert(std::numeric_limits<PtrNum>::is_integer);
// The array of entry pointers is not guaranteed to be correctly aligned,
// so we memcpy the entries into a local array.
// 32 is an educated guess about the maximum number of headers in the array.
size_t num_entries = (table->length - sizeof(*table)) / sizeof(PtrNum);
std::array<PtrNum, 32> aligned_ptrs;
cpp20::span<PtrNum> aligned_ptr_span(aligned_ptrs.begin(),
std::min(num_entries, aligned_ptrs.size()));
memcpy(aligned_ptr_span.data(), reinterpret_cast<const uint8_t*>(table + 1),
aligned_ptr_span.size_bytes());
for (const auto addr : aligned_ptr_span) {
const auto* entry = reinterpret_cast<const SdtHeader*>(addr);
if (entry && entry->signature == sig) {
return entry;
}
}
return nullptr;
}
} // namespace
std::optional<zbi_dcfg_arm_psci_driver_t> AcpiFadt::GetPsciDriver() const {
if (!(arm_boot_arch & kPsciCompliant)) {
return std::nullopt;
}
return zbi_dcfg_arm_psci_driver_t{
.use_hvc = static_cast<uint8_t>(arm_boot_arch & kPsciUseHvc),
};
}
zbi_dcfg_arm_generic_timer_driver_t AcpiGtdt::GetTimer() const {
return {
.irq_phys = nonsecure_el1_timer_gsiv,
.irq_virt = virtual_el1_timer_gsiv,
};
}
const SdtHeader* AcpiRsdp::LoadTableWithSignature(AcpiTableSignature sig) const {
// Note the requirement for listing newer revisions first in the definition of kRevisionTable
const auto lookup = std::find_if(std::cbegin(kRevisionTable), std::cend(kRevisionTable),
[&](const auto& entry) { return revision >= entry.revision; });
if (lookup == std::cend(kRevisionTable)) {
printf("%s: invalid RSDP revision: %u\n", __func__, revision);
return nullptr;
}
const SdtHeader* sdt_table = lookup->accessor(*this);
if (sdt_table == nullptr) {
printf("%s: null entry for SDT table\n", __func__);
return nullptr;
}
if (sdt_table->signature != lookup->signature) {
printf("%s: bad signature for SDT table with revision %u: %.*s\n", __func__, revision,
static_cast<int>(sdt_table->signature.size()), sdt_table->signature.data());
return nullptr;
}
if (lookup->entry_size == sizeof(uint64_t)) {
return TraverseEntries<uint64_t>(sdt_table, sig);
}
if (lookup->entry_size == sizeof(uint32_t)) {
return TraverseEntries<uint32_t>(sdt_table, sig);
}
ZX_ASSERT(false);
return nullptr;
}
uint32_t AcpiSpcr::GetKdrv() const {
// The SPCR table does not contain the granular subtype of the register
// interface we need in revision 1, so return early in this case.
if (hdr.revision < 2) {
return 0;
}
// The SPCR types are documented in Table 3 on:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/bringup/acpi-debug-port-table
// We currently only rely on PL011 devices to be initialized here.
switch (interface_type) {
case 0x0003:
return ZBI_KERNEL_DRIVER_PL011_UART;
default:
printf("%s: unsupported serial interface type: 0x%x\n", __func__, interface_type);
return 0;
}
}
cpp20::span<zbi_topology_node_t> AcpiMadt::GetTopology(
cpp20::span<zbi_topology_node_t> nodes) const {
auto last_node = nodes.begin();
for (const auto& controller : controllers()) {
const auto* gicc = GetInterruptController<AcpiMadtGicInterface>(controller);
if (gicc == nullptr) {
continue;
}
if (last_node == nodes.end()) {
return nodes;
}
size_t num_cpus = std::distance(nodes.begin(), last_node);
*last_node = zbi_topology_node_t{
.entity =
{
.discriminant = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.processor =
{
.architecture_info =
{
.discriminant = ZBI_TOPOLOGY_ARCHITECTURE_INFO_ARM64,
.arm64 =
{
.cluster_1_id =
static_cast<uint8_t>((gicc->mpidr >> 8) & 0xFF),
.cluster_2_id =
static_cast<uint8_t>((gicc->mpidr >> 16) & 0xFF),
.cluster_3_id =
static_cast<uint8_t>((gicc->mpidr >> 32) & 0xFF),
.cpu_id = static_cast<uint8_t>(gicc->mpidr & 0xFF),
.gic_id = static_cast<uint8_t>(gicc->cpu_interface_number),
},
},
.flags = static_cast<zbi_topology_processor_flags_t>(
(num_cpus) ? ZBI_TOPOLOGY_PROCESSOR_FLAGS_PRIMARY : 0),
.logical_ids = {static_cast<uint16_t>(num_cpus)},
.logical_id_count = 1,
},
},
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
};
++last_node;
}
return cpp20::span<zbi_topology_node_t>(nodes.begin(), last_node);
}
std::optional<AcpiMadt::GicDescriptor> AcpiMadt::GetGicDriver() const {
const AcpiMadtGicInterface* gicc = nullptr;
const AcpiMadtGicDistributor* gicd = nullptr;
const AcpiGicMsi* gic_msi = nullptr;
const AcpiMadtGicRedistributor* gicr = nullptr;
for (const auto& controller : controllers()) {
switch (controller.type) {
case AcpiMadtGicInterface::kType:
gicc = GetInterruptController<AcpiMadtGicInterface>(controller);
break;
case AcpiMadtGicDistributor::kType:
gicd = GetInterruptController<AcpiMadtGicDistributor>(controller);
break;
case AcpiMadtGicRedistributor::kType:
gicr = GetInterruptController<AcpiMadtGicRedistributor>(controller);
break;
case AcpiGicMsi::kType:
gic_msi = GetInterruptController<AcpiGicMsi>(controller);
break;
default:
printf("%s: unexpected interrupt controller type: 0x%x\n", __func__, controller.type);
break;
}
}
if (gicd == nullptr) {
printf("%s: no valid gicd found\n", __func__);
return std::nullopt;
}
uint64_t mmio_phys_addr = gicd->physical_base_address;
switch (gicd->gic_version) {
case 0x02:
if (gicc == nullptr) {
printf("%s: No gicc\n", __func__);
return std::nullopt;
}
mmio_phys_addr = std::min(gicc->physical_base_address, mmio_phys_addr);
return AcpiMadt::GicDescriptor{
.driver =
zbi_dcfg_arm_gic_v2_driver_t{
.mmio_phys = mmio_phys_addr,
.msi_frame_phys = gic_msi->physical_base_address,
.gicd_offset = gicd->physical_base_address - mmio_phys_addr,
.gicc_offset = gicc->physical_base_address - mmio_phys_addr,
.ipi_base = 0,
.optional = true,
.use_msi = gic_msi != nullptr,
},
.zbi_type = ZBI_KERNEL_DRIVER_ARM_GIC_V2,
};
case 0x03:
if (gicr == nullptr) {
printf("%s: No gicr\n", __func__);
return std::nullopt;
}
mmio_phys_addr = std::min(gicr->discovery_range_base_address, mmio_phys_addr);
return AcpiMadt::GicDescriptor{
.driver =
zbi_dcfg_arm_gic_v3_driver_t{
.mmio_phys = mmio_phys_addr,
.gicd_offset = gicd->physical_base_address - mmio_phys_addr,
.gicr_offset = gicr->discovery_range_base_address - mmio_phys_addr,
.gicr_stride = kGicv3rDefaultStride,
.ipi_base = 0,
.optional = true,
},
.zbi_type = ZBI_KERNEL_DRIVER_ARM_GIC_V3,
};
default:
printf("%s: unexpected gic version: %u\n", __func__, gicd->gic_version);
return std::nullopt;
}
}
zbi_dcfg_simple_t AcpiSpcr::DeriveUartDriver() const {
return {
.mmio_phys = base_address.address,
// IRQ is only valid if the lowest order bit of interrupt type is set.
// Any other bit set to 1 in the interrupt type indicates that we should
// use the Global System Interrupt (GSIV).
.irq = (0x1 & interrupt_type) ? irq : gsiv,
};
}
const AcpiRsdp* FindAcpiRsdp() {
const AcpiRsdp* rsdp = nullptr;
cpp20::span<const efi_configuration_table> entries(gEfiSystemTable->ConfigurationTable,
gEfiSystemTable->NumberOfTableEntries);
for (const efi_configuration_table& entry : entries) {
// Check if this entry is an ACPI RSD PTR.
const auto* vendor_table = reinterpret_cast<const std::array<uint8_t, 8>*>(entry.VendorTable);
if ((entry.VendorGuid == kAcpiTableGuid || entry.VendorGuid == kAcpi20TableGuid) &&
// Verify the signature of the ACPI RSD PTR.
vendor_table && *vendor_table == kAcpiRsdpSignature) {
rsdp = reinterpret_cast<const AcpiRsdp*>(entry.VendorTable);
break;
}
}
if (rsdp == nullptr) {
printf("RSDP was not found\n");
return nullptr;
}
// Verify the checksum of this table. Both V1 and V2 RSDPs should pass the
// V1 checksum, which only covers the first 20 bytes of the table.
// The checksum adds all the bytes and passes if the result is 0.
cpp20::span<const uint8_t> acpi_bytes(reinterpret_cast<const uint8_t*>(rsdp), kAcpiRsdpV1Size);
if (uint8_t res = std::reduce(acpi_bytes.begin(), acpi_bytes.end(), 0ull) & 0xFF; res) {
printf("RSDP V1 checksum failed\n");
return nullptr;
}
// V2 RSDPs should additionally pass a checksum of the entire table.
if (rsdp->revision > 0) {
acpi_bytes = {acpi_bytes.begin(), rsdp->length};
if (uint8_t res = static_cast<uint8_t>(std::reduce(acpi_bytes.begin(), acpi_bytes.end(), 0));
res) {
printf("RSDP V2 checksum failed\n");
return nullptr;
}
}
return rsdp;
}
} // namespace gigaboot