| /* 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/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" |
| |
| #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, LoongArchMachineState *lams) |
| { |
| MachineState *ms = MACHINE(lams); |
| int i; |
| AcpiTable table = { .sig = "APIC", .rev = 1, .oem_id = lams->oem_id, |
| .oem_table_id = lams->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 < ms->smp.cpus; i++) { |
| /* Processor Core Interrupt Controller Structure */ |
| 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 + 1, 4); /* ACPI Processor ID */ |
| build_append_int_noprefix(table_data, i, 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) |
| { |
| uint64_t i; |
| LoongArchMachineState *lams = LOONGARCH_MACHINE(machine); |
| MachineState *ms = MACHINE(lams); |
| AcpiTable table = { .sig = "SRAT", .rev = 1, .oem_id = lams->oem_id, |
| .oem_table_id = lams->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 < ms->smp.cpus; ++i) { |
| /* 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, 0, 1); |
| build_append_int_noprefix(table_data, i, 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 */ |
| } |
| |
| build_srat_memory(table_data, VIRT_LOWMEM_BASE, VIRT_LOWMEM_SIZE, |
| 0, MEM_AFFINITY_ENABLED); |
| |
| build_srat_memory(table_data, VIRT_HIGHMEM_BASE, machine->ram_size - VIRT_LOWMEM_SIZE, |
| 0, MEM_AFFINITY_ENABLED); |
| |
| if (ms->device_memory) { |
| build_srat_memory(table_data, ms->device_memory->base, |
| memory_region_size(&ms->device_memory->mr), |
| 0, 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; |
| LoongArchMachineState *lams = LOONGARCH_MACHINE(machine); |
| |
| build_ged_aml(dsdt, "\\_SB."GED_DEVICE, |
| HOTPLUG_HANDLER(lams->acpi_ged), |
| VIRT_SCI_IRQ, AML_SYSTEM_MEMORY, |
| VIRT_GED_EVT_ADDR); |
| event = object_property_get_uint(OBJECT(lams->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); |
| } |
| } |
| |
| static void build_pci_device_aml(Aml *scope, LoongArchMachineState *lams) |
| { |
| 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 = PCH_PIC_IRQ_OFFSET + VIRT_DEVICE_IRQS, |
| .bus = lams->pci_bus, |
| }; |
| |
| acpi_dsdt_add_gpex(scope, &cfg); |
| } |
| |
| static void build_flash_aml(Aml *scope, LoongArchMachineState *lams) |
| { |
| Aml *dev, *crs; |
| |
| hwaddr flash_base = VIRT_FLASH_BASE; |
| hwaddr flash_size = VIRT_FLASH_SIZE; |
| |
| 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(flash_base, flash_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, LoongArchMachineState *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; |
| LoongArchMachineState *lams = LOONGARCH_MACHINE(machine); |
| AcpiTable table = { .sig = "DSDT", .rev = 1, .oem_id = lams->oem_id, |
| .oem_table_id = lams->oem_table_id }; |
| |
| acpi_table_begin(&table, table_data); |
| dsdt = init_aml_allocator(); |
| build_uart_device_aml(dsdt); |
| build_pci_device_aml(dsdt, lams); |
| build_la_ged_aml(dsdt, machine); |
| build_flash_aml(dsdt, lams); |
| #ifdef CONFIG_TPM |
| acpi_dsdt_add_tpm(dsdt, lams); |
| #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) |
| { |
| LoongArchMachineState *lams = LOONGARCH_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, |
| lams->oem_id, lams->oem_table_id); |
| |
| acpi_add_table(table_offsets, tables_blob); |
| build_madt(tables_blob, tables->linker, lams); |
| |
| acpi_add_table(table_offsets, tables_blob); |
| build_srat(tables_blob, tables->linker, machine); |
| |
| 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, lams->oem_id, |
| lams->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, lams->oem_id, |
| lams->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, |
| lams->oem_id, lams->oem_table_id); |
| |
| /* RSDP is in FSEG memory, so allocate it separately */ |
| { |
| AcpiRsdpData rsdp_data = { |
| .revision = 0, |
| .oem_id = lams->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."); |
| } |
| |
| 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 = (VMStateField[]) { |
| VMSTATE_UINT8(patched, AcpiBuildState), |
| VMSTATE_END_OF_LIST() |
| }, |
| }; |
| |
| void loongarch_acpi_setup(LoongArchMachineState *lams) |
| { |
| AcpiBuildTables tables; |
| AcpiBuildState *build_state; |
| |
| if (!lams->fw_cfg) { |
| ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n"); |
| return; |
| } |
| |
| if (!loongarch_is_acpi_enabled(lams)) { |
| 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(lams)); |
| |
| /* 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); |
| } |