root/sys/arch/arm64/stand/efiboot/efiacpi.c
/*      $OpenBSD: efiacpi.c,v 1.19 2025/02/10 20:40:26 kettenis Exp $   */

/*
 * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>

#include <efi.h>
#include <efiapi.h>

#include "fdt.h"
#include "libsa.h"

#define efi_guidcmp(_a, _b)     memcmp((_a), (_b), sizeof(EFI_GUID))

#define fdt_node_add_string_property(n, p, s) \
    fdt_node_add_property((n), (p), (s), strlen((s)) + 1)
#define fdt_node_set_string_property(n, p, s) \
    fdt_node_set_property((n), (p), (s), strlen((s)) + 1)

extern EFI_SYSTEM_TABLE         *ST;

/* ACPI tables */

struct acpi_rsdp1 {
        uint8_t         signature[8];
#define RSDP_SIG        "RSD PTR "
#define rsdp_signature  rsdp1.signature
        uint8_t         checksum;       /* make sum == 0 */
#define rsdp_checksum   rsdp1.checksum
        uint8_t         oemid[6];
#define rsdp_oemid      rsdp1.oemid
        uint8_t         revision;       /* 0 for 1, 2 for 2 */
#define rsdp_revision   rsdp1.revision
        uint32_t        rsdt;           /* physical */
#define rsdp_rsdt       rsdp1.rsdt
} __packed;

struct acpi_rsdp {
        struct acpi_rsdp1 rsdp1;
        /*
         * The following values are only valid
         * when rsdp_revision == 2
         */
        uint32_t        rsdp_length;            /* length of rsdp */
        uint64_t        rsdp_xsdt;              /* physical */
        uint8_t         rsdp_extchecksum;       /* entire table */
        uint8_t         rsdp_reserved[3];       /* must be zero */
} __packed;

struct acpi_table_header {
        uint8_t         signature[4];
#define hdr_signature           hdr.signature
        uint32_t        length;
#define hdr_length              hdr.length
        uint8_t         revision;
#define hdr_revision            hdr.revision
        uint8_t         checksum;
#define hdr_checksum            hdr.checksum
        uint8_t         oemid[6];
#define hdr_oemid               hdr.oemid
        uint8_t         oemtableid[8];
#define hdr_oemtableid          hdr.oemtableid
        uint32_t        oemrevision;
#define hdr_oemrevision         hdr.oemrevision
        uint8_t         aslcompilerid[4];
#define hdr_aslcompilerid       hdr.aslcompilerid
        uint32_t        aslcompilerrevision;
#define hdr_aslcompilerrevision hdr.aslcompilerrevision
} __packed;

struct acpi_xsdt {
        struct acpi_table_header        hdr;
#define XSDT_SIG        "XSDT"
        uint64_t                        table_offsets[1];
} __packed;

struct acpi_gas {
        uint8_t         address_space_id;
#define GAS_SYSTEM_MEMORY       0
#define GAS_SYSTEM_IOSPACE      1
#define GAS_PCI_CFG_SPACE       2
#define GAS_EMBEDDED            3
#define GAS_SMBUS               4
#define GAS_FUNCTIONAL_FIXED    127
        uint8_t         register_bit_width;
        uint8_t         register_bit_offset;
        uint8_t         access_size;
#define GAS_ACCESS_UNDEFINED    0
#define GAS_ACCESS_BYTE         1
#define GAS_ACCESS_WORD         2
#define GAS_ACCESS_DWORD        3
#define GAS_ACCESS_QWORD        4
        uint64_t        address;
} __packed;

struct acpi_fadt {
        struct acpi_table_header        hdr;
#define FADT_SIG        "FACP"
        uint32_t        firmware_ctl;   /* phys addr FACS */
        uint32_t        dsdt;           /* phys addr DSDT */
        uint8_t         int_model;      /* interrupt model (hdr_revision < 3) */
#define FADT_INT_DUAL_PIC       0
#define FADT_INT_MULTI_APIC     1
        uint8_t         pm_profile;     /* power mgmt profile */
#define FADT_PM_UNSPEC          0
#define FADT_PM_DESKTOP         1
#define FADT_PM_MOBILE          2
#define FADT_PM_WORKSTATION     3
#define FADT_PM_ENT_SERVER      4
#define FADT_PM_SOHO_SERVER     5
#define FADT_PM_APPLIANCE       6
#define FADT_PM_PERF_SERVER     7
        uint16_t        sci_int;        /* SCI interrupt */
        uint32_t        smi_cmd;        /* SMI command port */
        uint8_t         acpi_enable;    /* value to enable */
        uint8_t         acpi_disable;   /* value to disable */
        uint8_t         s4bios_req;     /* value for S4 */
        uint8_t         pstate_cnt;     /* value for performance (hdr_revision > 2) */
        uint32_t        pm1a_evt_blk;   /* power management 1a */
        uint32_t        pm1b_evt_blk;   /* power management 1b */
        uint32_t        pm1a_cnt_blk;   /* pm control 1a */
        uint32_t        pm1b_cnt_blk;   /* pm control 1b */
        uint32_t        pm2_cnt_blk;    /* pm control 2 */
        uint32_t        pm_tmr_blk;
        uint32_t        gpe0_blk;
        uint32_t        gpe1_blk;
        uint8_t         pm1_evt_len;
        uint8_t         pm1_cnt_len;
        uint8_t         pm2_cnt_len;
        uint8_t         pm_tmr_len;
        uint8_t         gpe0_blk_len;
        uint8_t         gpe1_blk_len;
        uint8_t         gpe1_base;
        uint8_t         cst_cnt;        /* (hdr_revision > 2) */
        uint16_t        p_lvl2_lat;
        uint16_t        p_lvl3_lat;
        uint16_t        flush_size;
        uint16_t        flush_stride;
        uint8_t         duty_offset;
        uint8_t         duty_width;
        uint8_t         day_alrm;
        uint8_t         mon_alrm;
        uint8_t         century;
        uint16_t        iapc_boot_arch; /* (hdr_revision > 2) */
#define FADT_LEGACY_DEVICES             0x0001  /* Legacy devices supported */
#define FADT_i8042                      0x0002  /* Keyboard controller present */
#define FADT_NO_VGA                     0x0004  /* Do not probe VGA */
        uint8_t         reserved1;
        uint32_t        flags;
#define FADT_WBINVD                     0x00000001
#define FADT_WBINVD_FLUSH               0x00000002
#define FADT_PROC_C1                    0x00000004
#define FADT_P_LVL2_UP                  0x00000008
#define FADT_PWR_BUTTON                 0x00000010
#define FADT_SLP_BUTTON                 0x00000020
#define FADT_FIX_RTC                    0x00000040
#define FADT_RTC_S4                     0x00000080
#define FADT_TMR_VAL_EXT                0x00000100
#define FADT_DCK_CAP                    0x00000200
#define FADT_RESET_REG_SUP              0x00000400
#define FADT_SEALED_CASE                0x00000800
#define FADT_HEADLESS                   0x00001000
#define FADT_CPU_SW_SLP                 0x00002000
#define FADT_PCI_EXP_WAK                0x00004000
#define FADT_USE_PLATFORM_CLOCK         0x00008000
#define FADT_S4_RTC_STS_VALID           0x00010000
#define FADT_REMOTE_POWER_ON_CAPABLE    0x00020000
#define FADT_FORCE_APIC_CLUSTER_MODEL   0x00040000
#define FADT_FORCE_APIC_PHYS_DEST_MODE  0x00080000
#define FADT_HW_REDUCED_ACPI            0x00100000
#define FADT_POWER_S0_IDLE_CAPABLE      0x00200000
        /*
         * Following values only exist when rev > 1
         * If the extended addresses exists, they
         * must be used in preference to the non-
         * extended values above
         */
        struct acpi_gas reset_reg;
        uint8_t         reset_value;
        uint16_t        arm_boot_arch;  /* (hdr_revision > 3) */
#define FADT_PSCI_COMPLIANT             0x0001  /* PSCI is implemented */
#define FADT_PSCI_USE_HVC               0x0002  /* HVC used as PSCI conduit */
        uint8_t         reserved2;
        uint64_t        x_firmware_ctl;
        uint64_t        x_dsdt;
        struct acpi_gas x_pm1a_evt_blk;
        struct acpi_gas x_pm1b_evt_blk;
        struct acpi_gas x_pm1a_cnt_blk;
        struct acpi_gas x_pm1b_cnt_blk;
        struct acpi_gas x_pm2_cnt_blk;
        struct acpi_gas x_pm_tmr_blk;
        struct acpi_gas x_gpe0_blk;
        struct acpi_gas x_gpe1_blk;
        struct acpi_gas sleep_control_reg;
        struct acpi_gas sleep_status_reg;
} __packed;

struct acpi_gtdt {
        struct acpi_table_header        hdr;
#define GTDT_SIG        "GTDT"
        uint64_t        cnt_ctrl_base;
        uint32_t        reserved;
        uint32_t        sec_el1_interrupt;
        uint32_t        sec_el1_flags;
#define ACPI_GTDT_TIMER_TRIGGER_EDGE    0x1
#define ACPI_GTDT_TIMER_POLARITY_LOW    0x2
#define ACPI_GTDT_TIMER_ALWAYS_ON       0x4
        uint32_t        nonsec_el1_interrupt;
        uint32_t        nonsec_el1_flags;
        uint32_t        virt_interrupt;
        uint32_t        virt_flags;
        uint32_t        nonsec_el2_interrupt;
        uint32_t        nonsec_el2_flags;
        uint64_t        cnt_read_base;
        uint32_t        platform_timer_count;
        uint32_t        platform_timer_offset;
} __packed;

struct acpi_madt {
        struct acpi_table_header        hdr;
#define MADT_SIG        "APIC"
        uint32_t        local_apic_address;
        uint32_t        flags;
#define ACPI_APIC_PCAT_COMPAT   0x00000001
} __packed;

struct acpi_madt_gicc {
        uint8_t         apic_type;
#define ACPI_MADT_GICC          11
        uint8_t         length;
        uint16_t        reserved1;
        uint32_t        gic_id;
        uint32_t        acpi_proc_uid;
        uint32_t        flags;
#define ACPI_PROC_ENABLE        0x00000001
        uint32_t        parking_protocol_version;
        uint32_t        performance_interrupt;
        uint64_t        parked_address;
        uint64_t        base_address;
        uint64_t        gicv_base_address;
        uint64_t        gich_base_address;
        uint32_t        maintenance_interrupt;
        uint64_t        gicr_base_address;
        uint64_t        mpidr;
        uint8_t         efficiency_class;
        uint8_t         reserved2[3];
} __packed;

struct acpi_madt_gicd {
        uint8_t         apic_type;
#define ACPI_MADT_GICD          12
        uint8_t         length;
        uint16_t        reserved1;
        uint32_t        gic_id;
        uint64_t        base_address;
        uint32_t        interrupt_base;
        uint8_t         version;
        uint8_t         reserved2[3];
} __packed;

struct acpi_madt_gic_msi {
        uint8_t         apic_type;
#define ACPI_MADT_GIC_MSI       13
        uint8_t         length;
        uint16_t        reserved1;
        uint32_t        msi_frame_id;
        uint64_t        base_address;
        uint32_t        flags;
#define ACPI_MADT_GIC_MSI_SPI_SELECT    0x00000001
        uint16_t        spi_count;
        uint16_t        spi_base;
} __packed;

struct acpi_madt_gicr {
        uint8_t         apic_type;
#define ACPI_MADT_GICR          14
        uint8_t         length;
        uint16_t        reserved1;
        uint64_t        discovery_base_address;
        uint32_t        discovery_length;
} __packed;

struct acpi_madt_gic_its {
        uint8_t         apic_type;
#define ACPI_MADT_GIC_ITS       15
        uint8_t         length;
        uint16_t        reserved1;
        uint32_t        gic_its_id;
        uint64_t        base_address;
        uint32_t        reserved2;
} __packed;

union acpi_madt_entry {
        struct acpi_madt_gicc           madt_gicc;
        struct acpi_madt_gicd           madt_gicd;
        struct acpi_madt_gic_msi        madt_gic_msi;
        struct acpi_madt_gicr           madt_gicr;
        struct acpi_madt_gic_its        madt_gic_its;
} __packed;

struct acpi_spcr {
        struct acpi_table_header        hdr;
#define SPCR_SIG        "SPCR"
        uint8_t         interface_type;
#define SPCR_16550      0
#define SPCR_16450      1
#define SPCR_ARM_PL011  3
#define SPCR_ARM_SBSA   14
        uint8_t         reserved1[3];
        struct acpi_gas base_address;
        uint8_t         interrupt_type;
        uint8_t         irq;
        uint32_t        gsiv;
        uint8_t         baud_rate;
        uint8_t         parity;
        uint8_t         stop_bits;
        uint8_t         flow_control;
        uint8_t         terminal_type;
        uint8_t         reserved2;
        uint16_t        pci_device_id;
        uint16_t        pci_vendor_id;
        uint8_t         pci_bus;
        uint8_t         pci_device;
        uint8_t         pci_function;
        uint32_t        pci_flags;
        uint8_t         pci_segment;
        uint32_t        reserved3;
} __packed;

struct acpi_dbg2 {
        struct acpi_table_header        hdr;
#define DBG2_SIG        "DBG2"
        uint32_t        info_offset;
        uint32_t        info_count;
} __packed;

struct acpi_dbg2_info {
        uint8_t         revision;
        uint16_t        length;
        uint8_t         num_address;
        uint16_t        name_length;
        uint16_t        name_offset;
        uint16_t        oem_data_length;
        uint16_t        oem_data_offset;
        uint16_t        port_type;
#define DBG2_SERIAL     0x8000
        uint16_t        port_subtype;
#define DBG2_16550      0x0000
#define DBG2_16450      0x0001
#define DBG2_ARM_PL011  0x0003
#define DBG2_ARM_SBSA   0x000e
        uint16_t        reserved;
        uint16_t        base_address_offset;
        uint16_t        address_size_offset;
} __packed;

/* We'll never see ACPI 1.0 tables on ARM. */
static EFI_GUID acpi_guid = ACPI_20_TABLE_GUID;

static int psci = 0;

void
efi_acpi_fadt(struct acpi_table_header *hdr)
{
        struct acpi_fadt *fadt = (struct acpi_fadt *)hdr;
        void *node;

        /*
         * The PSCI flags were introduced in ACPI 5.1.  The relevant
         * field is set to zero for ACPU 5.0.
         */
        if (fadt->hdr_revision < 5)
                return;

        psci = fadt->arm_boot_arch & FADT_PSCI_COMPLIANT;

        node = fdt_find_node("/psci");
        if (fadt->arm_boot_arch & FADT_PSCI_COMPLIANT)
                fdt_node_set_string_property(node, "status", "okay");
        if (fadt->arm_boot_arch & FADT_PSCI_USE_HVC)
                fdt_node_set_string_property(node, "method", "hvc");
}

void
efi_acpi_gtdt(struct acpi_table_header *hdr)
{
        struct acpi_gtdt *gtdt = (struct acpi_gtdt *)hdr;
        const uint32_t map[] = { 0x4, 0x1, 0x8, 0x2 };
        const uint32_t mask = ACPI_GTDT_TIMER_TRIGGER_EDGE |
            ACPI_GTDT_TIMER_POLARITY_LOW;
        uint32_t interrupts[12];
        void *node;

        /* All interrupts are supposed to be PPIs. */
        interrupts[0] = htobe32(1);
        interrupts[1] = htobe32(gtdt->sec_el1_interrupt - 16);
        interrupts[2] = htobe32(map[gtdt->sec_el1_flags & mask]);
        interrupts[3] = htobe32(1);
        interrupts[4] = htobe32(gtdt->nonsec_el1_interrupt - 16);
        interrupts[5] = htobe32(map[gtdt->nonsec_el1_flags & mask]);
        interrupts[6] = htobe32(1);
        interrupts[7] = htobe32(gtdt->virt_interrupt - 16);
        interrupts[8] = htobe32(map[gtdt->virt_flags & mask]);
        interrupts[9] = htobe32(1);
        interrupts[10] = htobe32(gtdt->nonsec_el2_interrupt - 16);
        interrupts[11] = htobe32(map[gtdt->nonsec_el2_flags & mask]);

        node = fdt_find_node("/timer");
        fdt_node_set_property(node, "interrupts",
            interrupts, sizeof(interrupts));
        fdt_node_set_string_property(node, "status", "okay");
}

static int gic_version;
static uint64_t gicc_base;
static uint64_t gicd_base;
static uint64_t gicr_base;
static uint64_t gicr_size;
static uint64_t gicr_stride;

void
efi_acpi_madt_gicc(struct acpi_madt_gicc *gicc)
{
        uint64_t mpidr = gicc->mpidr;
        void *node, *child;
        uint64_t reg;
        char name[32];

        /* Skip disabled CPUs. */
        if ((gicc->flags & ACPI_PROC_ENABLE) == 0)
                return;

        /*
         * MPIDR field was introduced in ACPI 5.1.  Fall back on the
         * ACPI Processor UID on ACPI 5.0.
         */
        mpidr = (gicc->length >= 76) ? gicc->mpidr : gicc->acpi_proc_uid;

        snprintf(name, sizeof(name), "cpu@%llx", mpidr);
        reg = htobe64(mpidr);

        /* Create "cpu" node. */
        node = fdt_find_node("/cpus");
        fdt_node_add_node(node, name, &child);
        fdt_node_add_string_property(child, "device_type", "cpu");
        fdt_node_add_string_property(child, "compatible", "arm,armv8");
        fdt_node_add_property(child, "reg", &reg, sizeof(reg));
        if (gicc->parking_protocol_version == 0 || psci)
                fdt_node_add_string_property(child, "enable-method", "psci");

        /* Stash GIC information. */
        gicc_base = gicc->base_address;

        /*
         * The redistributor base address may be specified per-CPU.
         * In that case we will need to reconstruct the base, size and
         * stride to use for the redistributor registers.
         */
        if (gicc->gicr_base_address > 0) {
                if (gicr_base > 0) {
                        uint32_t size;

                        if (gicc->gicr_base_address < gicr_base)
                                size = gicr_base - gicc->gicr_base_address;
                        else
                                size = gicc->gicr_base_address - gicr_base;
                        if (gicr_stride == 0 || size < gicr_stride)
                                gicr_stride = size;
                        if (gicr_size == 0 || size > gicr_size)
                                gicr_size = size;
                        gicr_base = MIN(gicr_base, gicc->gicr_base_address);
                } else {
                        gicr_base = gicc->gicr_base_address;
                        gicr_size = 0x20000;
                }
        }
}

void
efi_acpi_madt_gicd(struct acpi_madt_gicd *gicd)
{
        /* Stash GIC information. */
        gic_version = gicd->version;
        gicd_base = gicd->base_address;
}

void
efi_acpi_madt_gic_msi(struct acpi_madt_gic_msi *msi)
{
        static uint32_t phandle = 2;
        void *node, *child;
        uint64_t reg[2];
        char name[32];

        snprintf(name, sizeof(name), "v2m@%llx", msi->base_address);
        reg[0] = htobe64(msi->base_address);
        reg[1] = htobe64(0x1000);

        /* Create "v2m" node. */
        node = fdt_find_node("/interrupt-controller");
        fdt_node_add_node(node, name, &child);
        fdt_node_add_string_property(child, "compatible", "arm,gic-v2m-frame");
        fdt_node_add_property(child, "msi-controller", NULL, 0);
        fdt_node_add_property(child, "reg", reg, sizeof(reg));
        if (msi->flags & ACPI_MADT_GIC_MSI_SPI_SELECT) {
                uint32_t spi_base = htobe32(msi->spi_base);
                uint32_t spi_count = htobe32(msi->spi_count);

                fdt_node_add_property(child, "arm,msi-base-spi",
                    &spi_base, sizeof(spi_base));
                fdt_node_add_property(child, "arm,msi-num-spis",
                    &spi_count, sizeof(spi_count));
        }
        fdt_node_add_property(child, "phandle", &phandle, sizeof(phandle));
        phandle++;
}

void
efi_acpi_madt_gicr(struct acpi_madt_gicr *gicr)
{
        /* Stash GIC information. */
        gicr_base = gicr->discovery_base_address;
        gicr_size = gicr->discovery_length;
}

void
efi_acpi_madt_gic_its(struct acpi_madt_gic_its *its)
{
        static uint32_t phandle = 2;
        void *node, *child;
        uint64_t reg[2];
        uint32_t its_id;
        char name[32];

        snprintf(name, sizeof(name), "gic-its@%llx", its->base_address);
        reg[0] = htobe64(its->base_address);
        reg[1] = htobe64(0x20000);
        its_id = htobe32(its->gic_its_id);

        /* Create "gic-its" node. */
        node = fdt_find_node("/interrupt-controller");
        fdt_node_add_node(node, name, &child);
        fdt_node_add_string_property(child, "compatible", "arm,gic-v3-its");
        fdt_node_add_property(child, "msi-controller", NULL, 0);
        fdt_node_add_property(child, "reg", reg, sizeof(reg));
        fdt_node_add_property(child, "phandle", &phandle, sizeof(phandle));
        fdt_node_add_property(child, "openbsd,gic-its-id", &its_id,
            sizeof(its_id));
        phandle++;
}

void
efi_acpi_madt(struct acpi_table_header *hdr)
{
        struct acpi_madt *madt = (struct acpi_madt *)hdr;
        char *compat;
        uint64_t reg[4];
        char *addr;
        void *node;

        /* GIC support was introduced in ACPI 5.0. */
        if (madt->hdr_revision < 3)
                return;

        addr = (char *)(madt + 1);
        while (addr < (char *)madt + madt->hdr.length) {
                union acpi_madt_entry *entry = (union acpi_madt_entry *)addr;
                uint8_t length = entry->madt_gicc.length;

                if (length < 2)
                        return;

                if (addr + length > (char *)madt + madt->hdr_length)
                        return;

                switch (entry->madt_gicc.apic_type) {
                case ACPI_MADT_GICC:
                        efi_acpi_madt_gicc(&entry->madt_gicc);
                        break;
                case ACPI_MADT_GICD:
                        efi_acpi_madt_gicd(&entry->madt_gicd);
                        break;
                case ACPI_MADT_GIC_MSI:
                        efi_acpi_madt_gic_msi(&entry->madt_gic_msi);
                        break;
                case ACPI_MADT_GICR:
                        efi_acpi_madt_gicr(&entry->madt_gicr);
                        break;
                case ACPI_MADT_GIC_ITS:
                        efi_acpi_madt_gic_its(&entry->madt_gic_its);
                        break;
                }

                addr += length;
        }

        /*
         * Now that we've collected all the necessary information, fix
         * up the "interrupt-controller" node.
         */

        switch (gic_version) {
        case 0:
                /* ACPI 5.0 doesn't provide a version; assume GICv2 */
        case 2:
                /* GICv2 */
                compat = "arm,gic-400";
                reg[0] = htobe64(gicd_base);
                reg[1] = htobe64(0x1000);
                reg[2] = htobe64(gicc_base);
                reg[3] = htobe64(0x100);
                break;
        case 3:
        case 4:
                /* GICv3 and GICv4 */
                compat = "arm,gic-v3";
                reg[0] = htobe64(gicd_base);
                reg[1] = htobe64(0x10000);
                reg[2] = htobe64(gicr_base);
                reg[3] = htobe64(gicr_size + gicr_stride);
                break;
        default:
                return;
        }

        /* Update "interrupt-controller" node. */
        node = fdt_find_node("/interrupt-controller");
        fdt_node_set_string_property(node, "compatible", compat);
        fdt_node_set_property(node, "reg", reg, sizeof(reg));
        if (gicr_stride > 0) {
                uint64_t stride = htobe64(gicr_stride);
                fdt_node_add_property(node, "redistributor-stride",
                    &stride, sizeof(stride));
        }
        fdt_node_set_string_property(node, "status", "okay");
}

static int serial = 0;

void
efi_acpi_serial(char *compat, struct acpi_gas *base_address,
    uint32_t address_size)
{
        uint64_t reg[2], reg_shift, reg_io_width;
        void *node;

        /* No idea how to support anything else on ARM. */
        if (base_address->address_space_id != GAS_SYSTEM_MEMORY)
                return;

        switch (base_address->access_size) {
        case GAS_ACCESS_BYTE:
                reg_io_width = 1;
                break;
        case GAS_ACCESS_WORD:
                reg_io_width = 2;
                break;
        case GAS_ACCESS_DWORD:
                reg_io_width = 4;
                break;
        case GAS_ACCESS_QWORD:
                reg_io_width = 8;
                break;
        default:
                return;
        }
        reg_io_width = htobe32(reg_io_width);

        reg_shift = 0;
        if (base_address->register_bit_width > 8)
                reg_shift = 1;
        if (base_address->register_bit_width > 16)
                reg_shift = 2;
        if (base_address->register_bit_width > 32)
                reg_shift = 3;
        reg_shift = htobe32(reg_shift);

        /* Update "serial" node. */
        node = fdt_find_node("/serial");
        fdt_node_set_string_property(node, "compatible", compat);
        if (strcmp(compat, "snps,dw-apb-uart") == 0) {
                fdt_node_add_property(node, "reg-shift",
                    &reg_shift, sizeof(reg_shift));
                fdt_node_add_property(node, "reg-io-width",
                    &reg_io_width, sizeof(reg_io_width));
        }
        reg[0] = htobe64(base_address->address);
        reg[1] = htobe64(address_size);
        fdt_node_set_property(node, "reg", reg, sizeof(reg));
}

void
efi_acpi_spcr(struct acpi_table_header *hdr)
{
        struct acpi_spcr *spcr = (struct acpi_spcr *)hdr;

        /* Minimal revision required by Server Base Boot Requirements is 2. */
        if (spcr->hdr_revision < 2)
                return;

        switch (spcr->interface_type) {
        case SPCR_16550:
        case SPCR_16450:
                efi_acpi_serial("snps,dw-apb-uart", &spcr->base_address, 0x100);
                break;
        case SPCR_ARM_PL011:
        case SPCR_ARM_SBSA:
                efi_acpi_serial("arm,pl011", &spcr->base_address, 0x1000);
                break;
        default:
                return;
        }
        serial = 1;
}

void
efi_acpi_dbg2(struct acpi_table_header *hdr)
{
        struct acpi_dbg2 *dbg2 = (struct acpi_dbg2 *)hdr;
        struct acpi_dbg2_info *info;
        struct acpi_gas *base_address;
        uint32_t address_size;

        info = (struct acpi_dbg2_info *)((char *)hdr + dbg2->info_offset);

        /* Only looking for serial ports. */
        if (info->port_type != DBG2_SERIAL)
                return;

        base_address = (struct acpi_gas *)
            ((char *)info + info->base_address_offset);
        address_size = *(uint32_t *)
            ((char *)info + info->address_size_offset);

        switch (info->port_subtype) {
        case DBG2_16550:
        case DBG2_16450:
                efi_acpi_serial("snps,dw-apb-uart", base_address, address_size);
                break;
        case DBG2_ARM_PL011:
        case DBG2_ARM_SBSA:
                efi_acpi_serial("arm,pl011", base_address, address_size);
                break;
        default:
                return;
        }
}

void *
efi_acpi(void)
{
        extern uint64_t dma_constraint[2];
        extern u_char dt_blob_start[];
        void *fdt = dt_blob_start;
        struct acpi_table_header *hdr;
        struct acpi_rsdp *rsdp = NULL;
        struct acpi_xsdt *xsdt;
        uint64_t reg[2];
        int i, ntables;
        size_t len;
        void *node;

        for (i = 0; i < ST->NumberOfTableEntries; i++) {
                if (efi_guidcmp(&acpi_guid,
                    &ST->ConfigurationTable[i].VendorGuid) == 0)
                        rsdp = ST->ConfigurationTable[i].VendorTable;
        }

        if (rsdp == NULL)
                return NULL;

        if (memcmp(rsdp->rsdp_signature, RSDP_SIG, 8) != 0 ||
            rsdp->rsdp_revision < 2)
                return NULL;

        xsdt = (struct acpi_xsdt *)rsdp->rsdp_xsdt;
        len = xsdt->hdr.length;
        ntables = (len - sizeof(struct acpi_table_header)) /
            sizeof(xsdt->table_offsets[0]);
        if (ntables == 0)
                return NULL;

        if (!fdt_init(fdt))
                return NULL;

        for (i = 0; i < ntables; i++) {
                hdr = (struct acpi_table_header *)xsdt->table_offsets[i];
                printf("%c%c%c%c ", hdr->signature[0], hdr->signature[1],
                    hdr->signature[2], hdr->signature[3]);
                if (memcmp(hdr->signature, FADT_SIG, 4) == 0)
                        efi_acpi_fadt(hdr);
                if (memcmp(hdr->signature, GTDT_SIG, 4) == 0)
                        efi_acpi_gtdt(hdr);
                if (memcmp(hdr->signature, MADT_SIG, 4) == 0)
                        efi_acpi_madt(hdr);
                if (memcmp(hdr->signature, SPCR_SIG, 4) == 0)
                        efi_acpi_spcr(hdr);
        }
        printf("\n");

        for (i = 0; i < ntables; i++) {
                hdr = (struct acpi_table_header *)xsdt->table_offsets[i];
                if (memcmp(hdr->signature, DBG2_SIG, 4) == 0 && !serial)
                        efi_acpi_dbg2(hdr);
        }

        reg[0] = htobe64((uint64_t)rsdp);
        reg[1] = htobe64(rsdp->rsdp_length);

        /* Update "acpi" node. */
        node = fdt_find_node("/acpi");
        fdt_node_set_property(node, "reg", reg, sizeof(reg));

        /* Use framebuffer if SPCR is absent or unusable. */
        if (!serial)
                cnset(ttydev("fb0"));

        /* Raspberry Pi 4 is "special". */
        if (memcmp(xsdt->hdr_oemid, "RPIFDN", 6) == 0 &&
            memcmp(xsdt->hdr_oemtableid, "RPI4", 4) == 0)
                dma_constraint[1] = htobe64(0x3bffffff);

        fdt_finalize();

        return fdt;
}