| /* 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 "sysemu/reset.h" | 
 |  | 
 | /* Supported chipsets: */ | 
 | #include "hw/pci-host/ls7a.h" | 
 | #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 "sysemu/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 | 
 |  | 
 | /* 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; | 
 |     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; | 
 |  | 
 |         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, 1, 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, len, 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); | 
 | } | 
 |  | 
 | 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) | 
 | { | 
 |     Aml *dev; | 
 |     Aml *crs; | 
 |     Aml *pkg0, *pkg1, *pkg2; | 
 |     uint32_t uart_irq = VIRT_UART_IRQ; | 
 |  | 
 |     Aml *scope = aml_scope("_SB"); | 
 |     dev = aml_device("COMA"); | 
 |     aml_append(dev, aml_name_decl("_HID", aml_string("PNP0501"))); | 
 |     aml_append(dev, aml_name_decl("_UID", aml_int(0))); | 
 |     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, VIRT_UART_BASE, VIRT_UART_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); | 
 |  | 
 |     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); | 
 |     } | 
 |     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) | 
 | { | 
 |     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(); | 
 |     build_uart_device_aml(dsdt); | 
 |     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, rsdt, 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); | 
 |  | 
 |     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 = cpu_to_le64(VIRT_PCI_CFG_BASE), | 
 |            .size = cpu_to_le64(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 */ | 
 |     rsdt = tables_blob->len; | 
 |     build_rsdt(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 = 0, | 
 |             .oem_id = lvms->oem_id, | 
 |             .xsdt_tbl_offset = NULL, | 
 |             .rsdt_tbl_offset = &rsdt, | 
 |         }; | 
 |         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 loongarch_is_acpi_enabled(LoongArchVirtMachineState *lvms) | 
 | { | 
 |     if (lvms->acpi == ON_OFF_AUTO_OFF) { | 
 |         return false; | 
 |     } | 
 |     return true; | 
 | } | 
 |  | 
 | void loongarch_acpi_setup(LoongArchVirtMachineState *lvms) | 
 | { | 
 |     AcpiBuildTables tables; | 
 |     AcpiBuildState *build_state; | 
 |  | 
 |     if (!lvms->fw_cfg) { | 
 |         ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n"); | 
 |         return; | 
 |     } | 
 |  | 
 |     if (!loongarch_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); | 
 |  | 
 |     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); | 
 | } |