/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Support for generating ACPI tables and passing them to Guests
 *
 * Copyright (C) 2021 Loongson Technology Corporation Limited
 */

#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/bitmap.h"
#include "hw/pci/pci.h"
#include "hw/core/cpu.h"
#include "target/loongarch/cpu.h"
#include "hw/acpi/acpi-defs.h"
#include "hw/acpi/acpi.h"
#include "hw/nvram/fw_cfg.h"
#include "hw/acpi/bios-linker-loader.h"
#include "migration/vmstate.h"
#include "hw/mem/memory-device.h"
#include "system/reset.h"

/* Supported chipsets: */
#include "hw/loongarch/virt.h"

#include "hw/acpi/utils.h"
#include "hw/acpi/pci.h"

#include "qom/qom-qobject.h"

#include "hw/acpi/generic_event_device.h"
#include "hw/pci-host/gpex.h"
#include "system/system.h"
#include "system/tpm.h"
#include "hw/platform-bus.h"
#include "hw/acpi/aml-build.h"
#include "hw/acpi/hmat.h"

#define ACPI_BUILD_ALIGN_SIZE             0x1000
#define ACPI_BUILD_TABLE_SIZE             0x20000

#ifdef DEBUG_ACPI_BUILD
#define ACPI_BUILD_DPRINTF(fmt, ...)        \
    do {printf("ACPI_BUILD: " fmt, ## __VA_ARGS__); } while (0)
#else
#define ACPI_BUILD_DPRINTF(fmt, ...)
#endif

static void virt_madt_cpu_entry(int uid,
                                const CPUArchIdList *apic_ids,
                                GArray *entry, bool force_enabled)
{
    uint32_t flags, apic_id = apic_ids->cpus[uid].arch_id;

    flags = apic_ids->cpus[uid].cpu || force_enabled ? 1 /* Enabled */ : 0;

    /* Rev 1.0b, Table 5-13 Processor Local APIC Structure */
    build_append_int_noprefix(entry, 0, 1);       /* Type */
    build_append_int_noprefix(entry, 8, 1);       /* Length */
    build_append_int_noprefix(entry, uid, 1);     /* ACPI Processor ID */
    build_append_int_noprefix(entry, apic_id, 1); /* APIC ID */
    build_append_int_noprefix(entry, flags, 4); /* Flags */
}

/* build FADT */
static void init_common_fadt_data(AcpiFadtData *data)
{
    AcpiFadtData fadt = {
        /* ACPI 5.0: 4.1 Hardware-Reduced ACPI */
        .rev = 5,
        .flags = ((1 << ACPI_FADT_F_HW_REDUCED_ACPI) |
                  (1 << ACPI_FADT_F_RESET_REG_SUP)),

        /* ACPI 5.0: 4.8.3.7 Sleep Control and Status Registers */
        .sleep_ctl = {
            .space_id = AML_AS_SYSTEM_MEMORY,
            .bit_width = 8,
            .address = VIRT_GED_REG_ADDR + ACPI_GED_REG_SLEEP_CTL,
        },
        .sleep_sts = {
            .space_id = AML_AS_SYSTEM_MEMORY,
            .bit_width = 8,
            .address = VIRT_GED_REG_ADDR + ACPI_GED_REG_SLEEP_STS,
        },

        /* ACPI 5.0: 4.8.3.6 Reset Register */
        .reset_reg = {
            .space_id = AML_AS_SYSTEM_MEMORY,
            .bit_width = 8,
            .address = VIRT_GED_REG_ADDR + ACPI_GED_REG_RESET,
        },
        .reset_val = ACPI_GED_RESET_VALUE,
    };
    *data = fadt;
}

static void acpi_align_size(GArray *blob, unsigned align)
{
    /*
     * Align size to multiple of given size. This reduces the chance
     * we need to change size in the future (breaking cross version migration).
     */
    g_array_set_size(blob, ROUND_UP(acpi_data_len(blob), align));
}

/* build FACS */
static void
build_facs(GArray *table_data)
{
    const char *sig = "FACS";
    const uint8_t reserved[40] = {};

    g_array_append_vals(table_data, sig, 4); /* Signature */
    build_append_int_noprefix(table_data, 64, 4); /* Length */
    build_append_int_noprefix(table_data, 0, 4); /* Hardware Signature */
    build_append_int_noprefix(table_data, 0, 4); /* Firmware Waking Vector */
    build_append_int_noprefix(table_data, 0, 4); /* Global Lock */
    build_append_int_noprefix(table_data, 0, 4); /* Flags */
    g_array_append_vals(table_data, reserved, 40); /* Reserved */
}

/* build MADT */
static void
build_madt(GArray *table_data, BIOSLinker *linker,
           LoongArchVirtMachineState *lvms)
{
    MachineState *ms = MACHINE(lvms);
    MachineClass *mc = MACHINE_GET_CLASS(ms);
    const CPUArchIdList *arch_ids = mc->possible_cpu_arch_ids(ms);
    int i, arch_id, flags;
    AcpiTable table = { .sig = "APIC", .rev = 1, .oem_id = lvms->oem_id,
                        .oem_table_id = lvms->oem_table_id };

    acpi_table_begin(&table, table_data);

    /* Local APIC Address */
    build_append_int_noprefix(table_data, 0, 4);
    build_append_int_noprefix(table_data, 1 /* PCAT_COMPAT */, 4); /* Flags */

    for (i = 0; i < arch_ids->len; i++) {
        /* Processor Core Interrupt Controller Structure */
        arch_id = arch_ids->cpus[i].arch_id;
        flags   = arch_ids->cpus[i].cpu ? 1 : 0;
        build_append_int_noprefix(table_data, 17, 1);    /* Type */
        build_append_int_noprefix(table_data, 15, 1);    /* Length */
        build_append_int_noprefix(table_data, 1, 1);     /* Version */
        build_append_int_noprefix(table_data, i, 4);     /* ACPI Processor ID */
        build_append_int_noprefix(table_data, arch_id, 4); /* Core ID */
        build_append_int_noprefix(table_data, flags, 4); /* Flags */
    }

    /* Extend I/O Interrupt Controller Structure */
    build_append_int_noprefix(table_data, 20, 1);        /* Type */
    build_append_int_noprefix(table_data, 13, 1);        /* Length */
    build_append_int_noprefix(table_data, 1, 1);         /* Version */
    build_append_int_noprefix(table_data, 3, 1);         /* Cascade */
    build_append_int_noprefix(table_data, 0, 1);         /* Node */
    build_append_int_noprefix(table_data, 0xffff, 8);    /* Node map */

    /* MSI Interrupt Controller Structure */
    build_append_int_noprefix(table_data, 21, 1);        /* Type */
    build_append_int_noprefix(table_data, 19, 1);        /* Length */
    build_append_int_noprefix(table_data, 1, 1);         /* Version */
    build_append_int_noprefix(table_data, VIRT_PCH_MSI_ADDR_LOW, 8);/* Address */
    build_append_int_noprefix(table_data, 0x40, 4);      /* Start */
    build_append_int_noprefix(table_data, 0xc0, 4);      /* Count */

    /* Bridge I/O Interrupt Controller Structure */
    build_append_int_noprefix(table_data, 22, 1);        /* Type */
    build_append_int_noprefix(table_data, 17, 1);        /* Length */
    build_append_int_noprefix(table_data, 1, 1);         /* Version */
    build_append_int_noprefix(table_data, VIRT_PCH_REG_BASE, 8);/* Address */
    build_append_int_noprefix(table_data, 0x1000, 2);    /* Size */
    build_append_int_noprefix(table_data, 0, 2);         /* Id */
    build_append_int_noprefix(table_data, 0x40, 2);      /* Base */

    acpi_table_end(linker, &table);
}

/* build SRAT */
static void
build_srat(GArray *table_data, BIOSLinker *linker, MachineState *machine)
{
    int i, arch_id, node_id;
    hwaddr len, base, gap;
    NodeInfo *numa_info;
    int nodes, nb_numa_nodes = machine->numa_state->num_nodes;
    LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine);
    MachineClass *mc = MACHINE_GET_CLASS(lvms);
    const CPUArchIdList *arch_ids = mc->possible_cpu_arch_ids(machine);
    AcpiTable table = { .sig = "SRAT", .rev = 1, .oem_id = lvms->oem_id,
                        .oem_table_id = lvms->oem_table_id };

    acpi_table_begin(&table, table_data);
    build_append_int_noprefix(table_data, 1, 4); /* Reserved */
    build_append_int_noprefix(table_data, 0, 8); /* Reserved */

    for (i = 0; i < arch_ids->len; ++i) {
        arch_id = arch_ids->cpus[i].arch_id;
        node_id = arch_ids->cpus[i].props.node_id;

        /* Processor Local APIC/SAPIC Affinity Structure */
        build_append_int_noprefix(table_data, 0, 1);  /* Type  */
        build_append_int_noprefix(table_data, 16, 1); /* Length */
        /* Proximity Domain [7:0] */
        build_append_int_noprefix(table_data, node_id, 1);
        build_append_int_noprefix(table_data, arch_id, 1); /* APIC ID */
        /* Flags, Table 5-36 */
        build_append_int_noprefix(table_data, 1, 4);
        build_append_int_noprefix(table_data, 0, 1); /* Local SAPIC EID */
        /* Proximity Domain [31:8] */
        build_append_int_noprefix(table_data, 0, 3);
        build_append_int_noprefix(table_data, 0, 4); /* Reserved */
    }

    base = VIRT_LOWMEM_BASE;
    gap = VIRT_LOWMEM_SIZE;
    numa_info = machine->numa_state->nodes;
    nodes = nb_numa_nodes;
    if (!nodes) {
        nodes = 1;
    }

    for (i = 0; i < nodes; i++) {
        if (nb_numa_nodes) {
            len = numa_info[i].node_mem;
        } else {
            len = machine->ram_size;
        }

        /*
         * memory for the node splited into two part
         *   lowram:  [base, +gap)
         *   highram: [VIRT_HIGHMEM_BASE, +(len - gap))
         */
        if (len >= gap) {
            build_srat_memory(table_data, base, gap, i, MEM_AFFINITY_ENABLED);
            len -= gap;
            base = VIRT_HIGHMEM_BASE;
            gap = machine->ram_size - VIRT_LOWMEM_SIZE;
        }

        if (len) {
            build_srat_memory(table_data, base, len, i, MEM_AFFINITY_ENABLED);
            base += len;
            gap  -= len;
        }
    }

    if (machine->device_memory) {
        build_srat_memory(table_data, machine->device_memory->base,
                          memory_region_size(&machine->device_memory->mr),
                          nodes - 1,
                          MEM_AFFINITY_HOTPLUGGABLE | MEM_AFFINITY_ENABLED);
    }

    acpi_table_end(linker, &table);
}

/*
 * Serial Port Console Redirection Table (SPCR)
 * https://learn.microsoft.com/en-us/windows-hardware/drivers/serports/serial-port-console-redirection-table
 */
static void
spcr_setup(GArray *table_data, BIOSLinker *linker, MachineState *machine)
{
    LoongArchVirtMachineState *lvms;
    AcpiSpcrData serial = {
        .interface_type = 0,       /* 16550 compatible */
        .base_addr.id = AML_AS_SYSTEM_MEMORY,
        .base_addr.width = 32,
        .base_addr.offset = 0,
        .base_addr.size = 1,
        .base_addr.addr = VIRT_UART_BASE,
        .interrupt_type = 0,       /* Interrupt not supported */
        .pc_interrupt = 0,
        .interrupt = VIRT_UART_IRQ,
        .baud_rate = 7,            /* 115200 */
        .parity = 0,
        .stop_bits = 1,
        .flow_control = 0,
        .terminal_type = 3,        /* ANSI */
        .language = 0,             /* Language */
        .pci_device_id = 0xffff,   /* not a PCI device*/
        .pci_vendor_id = 0xffff,   /* not a PCI device*/
        .pci_bus = 0,
        .pci_device = 0,
        .pci_function = 0,
        .pci_flags = 0,
        .pci_segment = 0,
    };

    lvms = LOONGARCH_VIRT_MACHINE(machine);
    /*
     * Passing NULL as the SPCR Table for Revision 2 doesn't support
     * NameSpaceString.
     */
    build_spcr(table_data, linker, &serial, 2, lvms->oem_id,
               lvms->oem_table_id, NULL);
}

typedef
struct AcpiBuildState {
    /* Copy of table in RAM (for patching). */
    MemoryRegion *table_mr;
    /* Is table patched? */
    uint8_t patched;
    void *rsdp;
    MemoryRegion *rsdp_mr;
    MemoryRegion *linker_mr;
} AcpiBuildState;

static void build_uart_device_aml(Aml *table, int index)
{
    Aml *dev;
    Aml *crs;
    Aml *pkg0, *pkg1, *pkg2;
    Aml *scope;
    uint32_t uart_irq;
    uint64_t base;

    uart_irq = VIRT_UART_IRQ + index;
    base = VIRT_UART_BASE + index * VIRT_UART_SIZE;
    scope = aml_scope("_SB");
    dev = aml_device("COM%d", index);
    aml_append(dev, aml_name_decl("_HID", aml_string("PNP0501")));
    aml_append(dev, aml_name_decl("_UID", aml_int(index)));
    aml_append(dev, aml_name_decl("_CCA", aml_int(1)));
    crs = aml_resource_template();
    aml_append(crs,
        aml_qword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
                         AML_NON_CACHEABLE, AML_READ_WRITE,
                         0, base, base + VIRT_UART_SIZE - 1,
                         0, VIRT_UART_SIZE));
    aml_append(crs, aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
                                  AML_SHARED, &uart_irq, 1));
    aml_append(dev, aml_name_decl("_CRS", crs));
    pkg0 = aml_package(0x2);
    aml_append(pkg0, aml_int(0x05F5E100));
    aml_append(pkg0, aml_string("clock-frenquency"));
    pkg1 = aml_package(0x1);
    aml_append(pkg1, pkg0);
    pkg2 = aml_package(0x2);
    aml_append(pkg2, aml_touuid("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"));
    aml_append(pkg2, pkg1);
    aml_append(dev, aml_name_decl("_DSD", pkg2));
    aml_append(scope, dev);
    aml_append(table, scope);
}

static void
build_la_ged_aml(Aml *dsdt, MachineState *machine)
{
    uint32_t event;
    LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine);
    CPUHotplugFeatures opts;

    build_ged_aml(dsdt, "\\_SB."GED_DEVICE,
                  HOTPLUG_HANDLER(lvms->acpi_ged),
                  VIRT_SCI_IRQ, AML_SYSTEM_MEMORY,
                  VIRT_GED_EVT_ADDR);
    event = object_property_get_uint(OBJECT(lvms->acpi_ged),
                                     "ged-event", &error_abort);
    if (event & ACPI_GED_MEM_HOTPLUG_EVT) {
        build_memory_hotplug_aml(dsdt, machine->ram_slots, "\\_SB", NULL,
                                 AML_SYSTEM_MEMORY,
                                 VIRT_GED_MEM_ADDR);
    }

    if (event & ACPI_GED_CPU_HOTPLUG_EVT) {
        opts.acpi_1_compatible = false;
        opts.has_legacy_cphp = false;
        opts.fw_unplugs_cpu = false;
        opts.smi_path = NULL;

        build_cpus_aml(dsdt, machine, opts, virt_madt_cpu_entry,
                       VIRT_GED_CPUHP_ADDR, "\\_SB",
                       AML_GED_EVT_CPU_SCAN_METHOD, AML_SYSTEM_MEMORY);
    }

    acpi_dsdt_add_power_button(dsdt);
}

static void build_pci_device_aml(Aml *scope, LoongArchVirtMachineState *lvms)
{
    struct GPEXConfig cfg = {
        .mmio64.base = VIRT_PCI_MEM_BASE,
        .mmio64.size = VIRT_PCI_MEM_SIZE,
        .pio.base    = VIRT_PCI_IO_BASE,
        .pio.size    = VIRT_PCI_IO_SIZE,
        .ecam.base   = VIRT_PCI_CFG_BASE,
        .ecam.size   = VIRT_PCI_CFG_SIZE,
        .irq         = VIRT_GSI_BASE + VIRT_DEVICE_IRQS,
        .bus         = lvms->pci_bus,
    };

    acpi_dsdt_add_gpex(scope, &cfg);
}

static void build_flash_aml(Aml *scope, LoongArchVirtMachineState *lvms)
{
    Aml *dev, *crs;
    MemoryRegion *flash_mem;

    hwaddr flash0_base;
    hwaddr flash0_size;

    hwaddr flash1_base;
    hwaddr flash1_size;

    flash_mem = pflash_cfi01_get_memory(lvms->flash[0]);
    flash0_base = flash_mem->addr;
    flash0_size = memory_region_size(flash_mem);

    flash_mem = pflash_cfi01_get_memory(lvms->flash[1]);
    flash1_base = flash_mem->addr;
    flash1_size = memory_region_size(flash_mem);

    dev = aml_device("FLS0");
    aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0015")));
    aml_append(dev, aml_name_decl("_UID", aml_int(0)));

    crs = aml_resource_template();
    aml_append(crs, aml_memory32_fixed(flash0_base, flash0_size,
                                       AML_READ_WRITE));
    aml_append(dev, aml_name_decl("_CRS", crs));
    aml_append(scope, dev);

    dev = aml_device("FLS1");
    aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0015")));
    aml_append(dev, aml_name_decl("_UID", aml_int(1)));

    crs = aml_resource_template();
    aml_append(crs, aml_memory32_fixed(flash1_base, flash1_size,
                                       AML_READ_WRITE));
    aml_append(dev, aml_name_decl("_CRS", crs));
    aml_append(scope, dev);
}

#ifdef CONFIG_TPM
static void acpi_dsdt_add_tpm(Aml *scope, LoongArchVirtMachineState *vms)
{
    PlatformBusDevice *pbus = PLATFORM_BUS_DEVICE(vms->platform_bus_dev);
    hwaddr pbus_base = VIRT_PLATFORM_BUS_BASEADDRESS;
    SysBusDevice *sbdev = SYS_BUS_DEVICE(tpm_find());
    MemoryRegion *sbdev_mr;
    hwaddr tpm_base;

    if (!sbdev) {
        return;
    }

    tpm_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
    assert(tpm_base != -1);

    tpm_base += pbus_base;

    sbdev_mr = sysbus_mmio_get_region(sbdev, 0);

    Aml *dev = aml_device("TPM0");
    aml_append(dev, aml_name_decl("_HID", aml_string("MSFT0101")));
    aml_append(dev, aml_name_decl("_STR", aml_string("TPM 2.0 Device")));
    aml_append(dev, aml_name_decl("_UID", aml_int(0)));

    Aml *crs = aml_resource_template();
    aml_append(crs,
               aml_memory32_fixed(tpm_base,
                                  (uint32_t)memory_region_size(sbdev_mr),
                                  AML_READ_WRITE));
    aml_append(dev, aml_name_decl("_CRS", crs));
    aml_append(scope, dev);
}
#endif

/* build DSDT */
static void
build_dsdt(GArray *table_data, BIOSLinker *linker, MachineState *machine)
{
    int i;
    Aml *dsdt, *scope, *pkg;
    LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine);
    AcpiTable table = { .sig = "DSDT", .rev = 1, .oem_id = lvms->oem_id,
                        .oem_table_id = lvms->oem_table_id };

    acpi_table_begin(&table, table_data);
    dsdt = init_aml_allocator();
    for (i = 0; i < VIRT_UART_COUNT; i++) {
        build_uart_device_aml(dsdt, i);
    }
    build_pci_device_aml(dsdt, lvms);
    build_la_ged_aml(dsdt, machine);
    build_flash_aml(dsdt, lvms);
#ifdef CONFIG_TPM
    acpi_dsdt_add_tpm(dsdt, lvms);
#endif
    /* System State Package */
    scope = aml_scope("\\");
    pkg = aml_package(4);
    aml_append(pkg, aml_int(ACPI_GED_SLP_TYP_S5));
    aml_append(pkg, aml_int(0)); /* ignored */
    aml_append(pkg, aml_int(0)); /* reserved */
    aml_append(pkg, aml_int(0)); /* reserved */
    aml_append(scope, aml_name_decl("_S5", pkg));
    aml_append(dsdt, scope);
    /* Copy AML table into ACPI tables blob and patch header there */
    g_array_append_vals(table_data, dsdt->buf->data, dsdt->buf->len);
    acpi_table_end(linker, &table);
    free_aml_allocator();
}

static void acpi_build(AcpiBuildTables *tables, MachineState *machine)
{
    LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine);
    GArray *table_offsets;
    AcpiFadtData fadt_data;
    unsigned facs, xsdt, dsdt;
    uint8_t *u;
    GArray *tables_blob = tables->table_data;

    init_common_fadt_data(&fadt_data);

    table_offsets = g_array_new(false, true, sizeof(uint32_t));
    ACPI_BUILD_DPRINTF("init ACPI tables\n");

    bios_linker_loader_alloc(tables->linker,
                             ACPI_BUILD_TABLE_FILE, tables_blob,
                             64, false);

    /*
     * FACS is pointed to by FADT.
     * We place it first since it's the only table that has alignment
     * requirements.
     */
    facs = tables_blob->len;
    build_facs(tables_blob);

    /* DSDT is pointed to by FADT */
    dsdt = tables_blob->len;
    build_dsdt(tables_blob, tables->linker, machine);

    /* ACPI tables pointed to by RSDT */
    acpi_add_table(table_offsets, tables_blob);
    fadt_data.facs_tbl_offset = &facs;
    fadt_data.dsdt_tbl_offset = &dsdt;
    fadt_data.xdsdt_tbl_offset = &dsdt;
    build_fadt(tables_blob, tables->linker, &fadt_data,
               lvms->oem_id, lvms->oem_table_id);

    acpi_add_table(table_offsets, tables_blob);
    build_madt(tables_blob, tables->linker, lvms);

    acpi_add_table(table_offsets, tables_blob);
    build_pptt(tables_blob, tables->linker, machine,
               lvms->oem_id, lvms->oem_table_id);

    acpi_add_table(table_offsets, tables_blob);
    build_srat(tables_blob, tables->linker, machine);
    acpi_add_table(table_offsets, tables_blob);

    if (machine->acpi_spcr_enabled)
        spcr_setup(tables_blob, tables->linker, machine);

    if (machine->numa_state->num_nodes) {
        if (machine->numa_state->have_numa_distance) {
            acpi_add_table(table_offsets, tables_blob);
            build_slit(tables_blob, tables->linker, machine, lvms->oem_id,
                       lvms->oem_table_id);
        }
        if (machine->numa_state->hmat_enabled) {
            acpi_add_table(table_offsets, tables_blob);
            build_hmat(tables_blob, tables->linker, machine->numa_state,
                       lvms->oem_id, lvms->oem_table_id);
        }
    }

    acpi_add_table(table_offsets, tables_blob);
    {
        AcpiMcfgInfo mcfg = {
           .base = VIRT_PCI_CFG_BASE,
           .size = VIRT_PCI_CFG_SIZE,
        };
        build_mcfg(tables_blob, tables->linker, &mcfg, lvms->oem_id,
                   lvms->oem_table_id);
    }

#ifdef CONFIG_TPM
    /* TPM info */
    if (tpm_get_version(tpm_find()) == TPM_VERSION_2_0) {
        acpi_add_table(table_offsets, tables_blob);
        build_tpm2(tables_blob, tables->linker,
                   tables->tcpalog, lvms->oem_id,
                   lvms->oem_table_id);
    }
#endif
    /* Add tables supplied by user (if any) */
    for (u = acpi_table_first(); u; u = acpi_table_next(u)) {
        unsigned len = acpi_table_len(u);

        acpi_add_table(table_offsets, tables_blob);
        g_array_append_vals(tables_blob, u, len);
    }

    /* RSDT is pointed to by RSDP */
    xsdt = tables_blob->len;
    build_xsdt(tables_blob, tables->linker, table_offsets,
               lvms->oem_id, lvms->oem_table_id);

    /* RSDP is in FSEG memory, so allocate it separately */
    {
        AcpiRsdpData rsdp_data = {
            .revision = 2,
            .oem_id = lvms->oem_id,
            .xsdt_tbl_offset = &xsdt,
            .rsdt_tbl_offset = NULL,
        };
        build_rsdp(tables->rsdp, tables->linker, &rsdp_data);
    }

    /*
     * The align size is 128, warn if 64k is not enough therefore
     * the align size could be resized.
     */
    if (tables_blob->len > ACPI_BUILD_TABLE_SIZE / 2) {
        warn_report("ACPI table size %u exceeds %d bytes,"
                    " migration may not work",
                    tables_blob->len, ACPI_BUILD_TABLE_SIZE / 2);
        error_printf("Try removing CPUs, NUMA nodes, memory slots"
                     " or PCI bridges.\n");
    }

    acpi_align_size(tables->linker->cmd_blob, ACPI_BUILD_ALIGN_SIZE);

    /* Cleanup memory that's no longer used. */
    g_array_free(table_offsets, true);
}

static void acpi_ram_update(MemoryRegion *mr, GArray *data)
{
    uint32_t size = acpi_data_len(data);

    /*
     * Make sure RAM size is correct - in case it got changed
     * e.g. by migration
     */
    memory_region_ram_resize(mr, size, &error_abort);

    memcpy(memory_region_get_ram_ptr(mr), data->data, size);
    memory_region_set_dirty(mr, 0, size);
}

static void acpi_build_update(void *build_opaque)
{
    AcpiBuildState *build_state = build_opaque;
    AcpiBuildTables tables;

    /* No state to update or already patched? Nothing to do. */
    if (!build_state || build_state->patched) {
        return;
    }
    build_state->patched = 1;

    acpi_build_tables_init(&tables);

    acpi_build(&tables, MACHINE(qdev_get_machine()));

    acpi_ram_update(build_state->table_mr, tables.table_data);
    acpi_ram_update(build_state->rsdp_mr, tables.rsdp);
    acpi_ram_update(build_state->linker_mr, tables.linker->cmd_blob);

    acpi_build_tables_cleanup(&tables, true);
}

static void acpi_build_reset(void *build_opaque)
{
    AcpiBuildState *build_state = build_opaque;
    build_state->patched = 0;
}

static const VMStateDescription vmstate_acpi_build = {
    .name = "acpi_build",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (const VMStateField[]) {
        VMSTATE_UINT8(patched, AcpiBuildState),
        VMSTATE_END_OF_LIST()
    },
};

static bool virt_is_acpi_enabled(LoongArchVirtMachineState *lvms)
{
    if (lvms->acpi == ON_OFF_AUTO_OFF) {
        return false;
    }
    return true;
}

void virt_acpi_setup(LoongArchVirtMachineState *lvms)
{
    AcpiBuildTables tables;
    AcpiBuildState *build_state;

    if (!lvms->fw_cfg) {
        ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n");
        return;
    }

    if (!virt_is_acpi_enabled(lvms)) {
        ACPI_BUILD_DPRINTF("ACPI disabled. Bailing out.\n");
        return;
    }

    build_state = g_malloc0(sizeof *build_state);

    acpi_build_tables_init(&tables);
    acpi_build(&tables, MACHINE(lvms));

    /* Now expose it all to Guest */
    build_state->table_mr = acpi_add_rom_blob(acpi_build_update,
                                              build_state, tables.table_data,
                                              ACPI_BUILD_TABLE_FILE);
    assert(build_state->table_mr != NULL);

    build_state->linker_mr =
        acpi_add_rom_blob(acpi_build_update, build_state,
                          tables.linker->cmd_blob, ACPI_BUILD_LOADER_FILE);

    build_state->rsdp_mr = acpi_add_rom_blob(acpi_build_update,
                                             build_state, tables.rsdp,
                                             ACPI_BUILD_RSDP_FILE);

    fw_cfg_add_file(lvms->fw_cfg, ACPI_BUILD_TPMLOG_FILE, tables.tcpalog->data,
                    acpi_data_len(tables.tcpalog));

    qemu_register_reset(acpi_build_reset, build_state);
    acpi_build_reset(build_state);
    vmstate_register(NULL, 0, &vmstate_acpi_build, build_state);

    /*
     * Cleanup tables but don't free the memory: we track it
     * in build_state.
     */
    acpi_build_tables_cleanup(&tables, false);
}
