1 /* SPDX-License-Identifier: GPL-2.0-or-later */
3 * Support for generating ACPI tables and passing them to Guests
5 * Copyright (C) 2021 Loongson Technology Corporation Limited
8 #include "qemu/osdep.h"
9 #include "qapi/error.h"
10 #include "qemu/bitmap.h"
11 #include "hw/pci/pci.h"
12 #include "hw/core/cpu.h"
13 #include "target/loongarch/cpu.h"
14 #include "hw/acpi/acpi-defs.h"
15 #include "hw/acpi/acpi.h"
16 #include "hw/nvram/fw_cfg.h"
17 #include "hw/acpi/bios-linker-loader.h"
18 #include "migration/vmstate.h"
19 #include "hw/mem/memory-device.h"
20 #include "sysemu/reset.h"
22 /* Supported chipsets: */
23 #include "hw/pci-host/ls7a.h"
24 #include "hw/loongarch/virt.h"
25 #include "hw/acpi/aml-build.h"
27 #include "hw/acpi/utils.h"
28 #include "hw/acpi/pci.h"
30 #include "qom/qom-qobject.h"
32 #include "hw/acpi/generic_event_device.h"
33 #include "hw/pci-host/gpex.h"
34 #include "sysemu/tpm.h"
35 #include "hw/platform-bus.h"
36 #include "hw/acpi/aml-build.h"
38 #define ACPI_BUILD_ALIGN_SIZE 0x1000
39 #define ACPI_BUILD_TABLE_SIZE 0x20000
41 #ifdef DEBUG_ACPI_BUILD
42 #define ACPI_BUILD_DPRINTF(fmt, ...) \
43 do {printf("ACPI_BUILD: " fmt, ## __VA_ARGS__); } while (0)
45 #define ACPI_BUILD_DPRINTF(fmt, ...)
49 static void init_common_fadt_data(AcpiFadtData
*data
)
52 /* ACPI 5.0: 4.1 Hardware-Reduced ACPI */
54 .flags
= ((1 << ACPI_FADT_F_HW_REDUCED_ACPI
) |
55 (1 << ACPI_FADT_F_RESET_REG_SUP
)),
57 /* ACPI 5.0: 4.8.3.7 Sleep Control and Status Registers */
59 .space_id
= AML_AS_SYSTEM_MEMORY
,
61 .address
= VIRT_GED_REG_ADDR
+ ACPI_GED_REG_SLEEP_CTL
,
64 .space_id
= AML_AS_SYSTEM_MEMORY
,
66 .address
= VIRT_GED_REG_ADDR
+ ACPI_GED_REG_SLEEP_STS
,
69 /* ACPI 5.0: 4.8.3.6 Reset Register */
71 .space_id
= AML_AS_SYSTEM_MEMORY
,
73 .address
= VIRT_GED_REG_ADDR
+ ACPI_GED_REG_RESET
,
75 .reset_val
= ACPI_GED_RESET_VALUE
,
80 static void acpi_align_size(GArray
*blob
, unsigned align
)
83 * Align size to multiple of given size. This reduces the chance
84 * we need to change size in the future (breaking cross version migration).
86 g_array_set_size(blob
, ROUND_UP(acpi_data_len(blob
), align
));
91 build_facs(GArray
*table_data
)
93 const char *sig
= "FACS";
94 const uint8_t reserved
[40] = {};
96 g_array_append_vals(table_data
, sig
, 4); /* Signature */
97 build_append_int_noprefix(table_data
, 64, 4); /* Length */
98 build_append_int_noprefix(table_data
, 0, 4); /* Hardware Signature */
99 build_append_int_noprefix(table_data
, 0, 4); /* Firmware Waking Vector */
100 build_append_int_noprefix(table_data
, 0, 4); /* Global Lock */
101 build_append_int_noprefix(table_data
, 0, 4); /* Flags */
102 g_array_append_vals(table_data
, reserved
, 40); /* Reserved */
107 build_madt(GArray
*table_data
, BIOSLinker
*linker
, LoongArchMachineState
*lams
)
109 MachineState
*ms
= MACHINE(lams
);
111 AcpiTable table
= { .sig
= "APIC", .rev
= 1, .oem_id
= lams
->oem_id
,
112 .oem_table_id
= lams
->oem_table_id
};
114 acpi_table_begin(&table
, table_data
);
116 /* Local APIC Address */
117 build_append_int_noprefix(table_data
, 0, 4);
118 build_append_int_noprefix(table_data
, 1 /* PCAT_COMPAT */, 4); /* Flags */
120 for (i
= 0; i
< ms
->smp
.cpus
; i
++) {
121 /* Processor Core Interrupt Controller Structure */
122 build_append_int_noprefix(table_data
, 17, 1); /* Type */
123 build_append_int_noprefix(table_data
, 15, 1); /* Length */
124 build_append_int_noprefix(table_data
, 1, 1); /* Version */
125 build_append_int_noprefix(table_data
, i
+ 1, 4); /* ACPI Processor ID */
126 build_append_int_noprefix(table_data
, i
, 4); /* Core ID */
127 build_append_int_noprefix(table_data
, 1, 4); /* Flags */
130 /* Extend I/O Interrupt Controller Structure */
131 build_append_int_noprefix(table_data
, 20, 1); /* Type */
132 build_append_int_noprefix(table_data
, 13, 1); /* Length */
133 build_append_int_noprefix(table_data
, 1, 1); /* Version */
134 build_append_int_noprefix(table_data
, 3, 1); /* Cascade */
135 build_append_int_noprefix(table_data
, 0, 1); /* Node */
136 build_append_int_noprefix(table_data
, 0xffff, 8); /* Node map */
138 /* MSI Interrupt Controller Structure */
139 build_append_int_noprefix(table_data
, 21, 1); /* Type */
140 build_append_int_noprefix(table_data
, 19, 1); /* Length */
141 build_append_int_noprefix(table_data
, 1, 1); /* Version */
142 build_append_int_noprefix(table_data
, VIRT_PCH_MSI_ADDR_LOW
, 8);/* Address */
143 build_append_int_noprefix(table_data
, 0x40, 4); /* Start */
144 build_append_int_noprefix(table_data
, 0xc0, 4); /* Count */
146 /* Bridge I/O Interrupt Controller Structure */
147 build_append_int_noprefix(table_data
, 22, 1); /* Type */
148 build_append_int_noprefix(table_data
, 17, 1); /* Length */
149 build_append_int_noprefix(table_data
, 1, 1); /* Version */
150 build_append_int_noprefix(table_data
, VIRT_PCH_REG_BASE
, 8);/* Address */
151 build_append_int_noprefix(table_data
, 0x1000, 2); /* Size */
152 build_append_int_noprefix(table_data
, 0, 2); /* Id */
153 build_append_int_noprefix(table_data
, 0x40, 2); /* Base */
155 acpi_table_end(linker
, &table
);
160 build_srat(GArray
*table_data
, BIOSLinker
*linker
, MachineState
*machine
)
163 LoongArchMachineState
*lams
= LOONGARCH_MACHINE(machine
);
164 MachineState
*ms
= MACHINE(lams
);
165 AcpiTable table
= { .sig
= "SRAT", .rev
= 1, .oem_id
= lams
->oem_id
,
166 .oem_table_id
= lams
->oem_table_id
};
168 acpi_table_begin(&table
, table_data
);
169 build_append_int_noprefix(table_data
, 1, 4); /* Reserved */
170 build_append_int_noprefix(table_data
, 0, 8); /* Reserved */
172 for (i
= 0; i
< ms
->smp
.cpus
; ++i
) {
173 /* Processor Local APIC/SAPIC Affinity Structure */
174 build_append_int_noprefix(table_data
, 0, 1); /* Type */
175 build_append_int_noprefix(table_data
, 16, 1); /* Length */
176 /* Proximity Domain [7:0] */
177 build_append_int_noprefix(table_data
, 0, 1);
178 build_append_int_noprefix(table_data
, i
, 1); /* APIC ID */
179 /* Flags, Table 5-36 */
180 build_append_int_noprefix(table_data
, 1, 4);
181 build_append_int_noprefix(table_data
, 0, 1); /* Local SAPIC EID */
182 /* Proximity Domain [31:8] */
183 build_append_int_noprefix(table_data
, 0, 3);
184 build_append_int_noprefix(table_data
, 0, 4); /* Reserved */
187 build_srat_memory(table_data
, VIRT_LOWMEM_BASE
, VIRT_LOWMEM_SIZE
,
188 0, MEM_AFFINITY_ENABLED
);
190 build_srat_memory(table_data
, VIRT_HIGHMEM_BASE
, machine
->ram_size
- VIRT_LOWMEM_SIZE
,
191 0, MEM_AFFINITY_ENABLED
);
193 if (ms
->device_memory
) {
194 build_srat_memory(table_data
, ms
->device_memory
->base
,
195 memory_region_size(&ms
->device_memory
->mr
),
196 0, MEM_AFFINITY_HOTPLUGGABLE
| MEM_AFFINITY_ENABLED
);
199 acpi_table_end(linker
, &table
);
203 struct AcpiBuildState
{
204 /* Copy of table in RAM (for patching). */
205 MemoryRegion
*table_mr
;
206 /* Is table patched? */
209 MemoryRegion
*rsdp_mr
;
210 MemoryRegion
*linker_mr
;
213 static void build_uart_device_aml(Aml
*table
)
217 Aml
*pkg0
, *pkg1
, *pkg2
;
218 uint32_t uart_irq
= VIRT_UART_IRQ
;
220 Aml
*scope
= aml_scope("_SB");
221 dev
= aml_device("COMA");
222 aml_append(dev
, aml_name_decl("_HID", aml_string("PNP0501")));
223 aml_append(dev
, aml_name_decl("_UID", aml_int(0)));
224 aml_append(dev
, aml_name_decl("_CCA", aml_int(1)));
225 crs
= aml_resource_template();
227 aml_qword_memory(AML_POS_DECODE
, AML_MIN_FIXED
, AML_MAX_FIXED
,
228 AML_NON_CACHEABLE
, AML_READ_WRITE
,
229 0, VIRT_UART_BASE
, VIRT_UART_BASE
+ VIRT_UART_SIZE
- 1,
231 aml_append(crs
, aml_interrupt(AML_CONSUMER
, AML_LEVEL
, AML_ACTIVE_HIGH
,
232 AML_SHARED
, &uart_irq
, 1));
233 aml_append(dev
, aml_name_decl("_CRS", crs
));
234 pkg0
= aml_package(0x2);
235 aml_append(pkg0
, aml_int(0x05F5E100));
236 aml_append(pkg0
, aml_string("clock-frenquency"));
237 pkg1
= aml_package(0x1);
238 aml_append(pkg1
, pkg0
);
239 pkg2
= aml_package(0x2);
240 aml_append(pkg2
, aml_touuid("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"));
241 aml_append(pkg2
, pkg1
);
242 aml_append(dev
, aml_name_decl("_DSD", pkg2
));
243 aml_append(scope
, dev
);
244 aml_append(table
, scope
);
248 build_la_ged_aml(Aml
*dsdt
, MachineState
*machine
)
251 LoongArchMachineState
*lams
= LOONGARCH_MACHINE(machine
);
253 build_ged_aml(dsdt
, "\\_SB."GED_DEVICE
,
254 HOTPLUG_HANDLER(lams
->acpi_ged
),
255 VIRT_SCI_IRQ
, AML_SYSTEM_MEMORY
,
257 event
= object_property_get_uint(OBJECT(lams
->acpi_ged
),
258 "ged-event", &error_abort
);
259 if (event
& ACPI_GED_MEM_HOTPLUG_EVT
) {
260 build_memory_hotplug_aml(dsdt
, machine
->ram_slots
, "\\_SB", NULL
,
266 static void build_pci_device_aml(Aml
*scope
, LoongArchMachineState
*lams
)
268 struct GPEXConfig cfg
= {
269 .mmio64
.base
= VIRT_PCI_MEM_BASE
,
270 .mmio64
.size
= VIRT_PCI_MEM_SIZE
,
271 .pio
.base
= VIRT_PCI_IO_BASE
,
272 .pio
.size
= VIRT_PCI_IO_SIZE
,
273 .ecam
.base
= VIRT_PCI_CFG_BASE
,
274 .ecam
.size
= VIRT_PCI_CFG_SIZE
,
275 .irq
= PCH_PIC_IRQ_OFFSET
+ VIRT_DEVICE_IRQS
,
276 .bus
= lams
->pci_bus
,
279 acpi_dsdt_add_gpex(scope
, &cfg
);
283 static void acpi_dsdt_add_tpm(Aml
*scope
, LoongArchMachineState
*vms
)
285 PlatformBusDevice
*pbus
= PLATFORM_BUS_DEVICE(vms
->platform_bus_dev
);
286 hwaddr pbus_base
= VIRT_PLATFORM_BUS_BASEADDRESS
;
287 SysBusDevice
*sbdev
= SYS_BUS_DEVICE(tpm_find());
288 MemoryRegion
*sbdev_mr
;
295 tpm_base
= platform_bus_get_mmio_addr(pbus
, sbdev
, 0);
296 assert(tpm_base
!= -1);
298 tpm_base
+= pbus_base
;
300 sbdev_mr
= sysbus_mmio_get_region(sbdev
, 0);
302 Aml
*dev
= aml_device("TPM0");
303 aml_append(dev
, aml_name_decl("_HID", aml_string("MSFT0101")));
304 aml_append(dev
, aml_name_decl("_STR", aml_string("TPM 2.0 Device")));
305 aml_append(dev
, aml_name_decl("_UID", aml_int(0)));
307 Aml
*crs
= aml_resource_template();
309 aml_memory32_fixed(tpm_base
,
310 (uint32_t)memory_region_size(sbdev_mr
),
312 aml_append(dev
, aml_name_decl("_CRS", crs
));
313 aml_append(scope
, dev
);
319 build_dsdt(GArray
*table_data
, BIOSLinker
*linker
, MachineState
*machine
)
321 Aml
*dsdt
, *scope
, *pkg
;
322 LoongArchMachineState
*lams
= LOONGARCH_MACHINE(machine
);
323 AcpiTable table
= { .sig
= "DSDT", .rev
= 1, .oem_id
= lams
->oem_id
,
324 .oem_table_id
= lams
->oem_table_id
};
326 acpi_table_begin(&table
, table_data
);
327 dsdt
= init_aml_allocator();
328 build_uart_device_aml(dsdt
);
329 build_pci_device_aml(dsdt
, lams
);
330 build_la_ged_aml(dsdt
, machine
);
332 acpi_dsdt_add_tpm(dsdt
, lams
);
334 /* System State Package */
335 scope
= aml_scope("\\");
336 pkg
= aml_package(4);
337 aml_append(pkg
, aml_int(ACPI_GED_SLP_TYP_S5
));
338 aml_append(pkg
, aml_int(0)); /* ignored */
339 aml_append(pkg
, aml_int(0)); /* reserved */
340 aml_append(pkg
, aml_int(0)); /* reserved */
341 aml_append(scope
, aml_name_decl("_S5", pkg
));
342 aml_append(dsdt
, scope
);
343 /* Copy AML table into ACPI tables blob and patch header there */
344 g_array_append_vals(table_data
, dsdt
->buf
->data
, dsdt
->buf
->len
);
345 acpi_table_end(linker
, &table
);
346 free_aml_allocator();
349 static void acpi_build(AcpiBuildTables
*tables
, MachineState
*machine
)
351 LoongArchMachineState
*lams
= LOONGARCH_MACHINE(machine
);
352 GArray
*table_offsets
;
353 AcpiFadtData fadt_data
;
354 unsigned facs
, rsdt
, dsdt
;
356 GArray
*tables_blob
= tables
->table_data
;
358 init_common_fadt_data(&fadt_data
);
360 table_offsets
= g_array_new(false, true, sizeof(uint32_t));
361 ACPI_BUILD_DPRINTF("init ACPI tables\n");
363 bios_linker_loader_alloc(tables
->linker
,
364 ACPI_BUILD_TABLE_FILE
, tables_blob
,
368 * FACS is pointed to by FADT.
369 * We place it first since it's the only table that has alignment
372 facs
= tables_blob
->len
;
373 build_facs(tables_blob
);
375 /* DSDT is pointed to by FADT */
376 dsdt
= tables_blob
->len
;
377 build_dsdt(tables_blob
, tables
->linker
, machine
);
379 /* ACPI tables pointed to by RSDT */
380 acpi_add_table(table_offsets
, tables_blob
);
381 fadt_data
.facs_tbl_offset
= &facs
;
382 fadt_data
.dsdt_tbl_offset
= &dsdt
;
383 fadt_data
.xdsdt_tbl_offset
= &dsdt
;
384 build_fadt(tables_blob
, tables
->linker
, &fadt_data
,
385 lams
->oem_id
, lams
->oem_table_id
);
387 acpi_add_table(table_offsets
, tables_blob
);
388 build_madt(tables_blob
, tables
->linker
, lams
);
390 acpi_add_table(table_offsets
, tables_blob
);
391 build_srat(tables_blob
, tables
->linker
, machine
);
393 acpi_add_table(table_offsets
, tables_blob
);
395 AcpiMcfgInfo mcfg
= {
396 .base
= cpu_to_le64(VIRT_PCI_CFG_BASE
),
397 .size
= cpu_to_le64(VIRT_PCI_CFG_SIZE
),
399 build_mcfg(tables_blob
, tables
->linker
, &mcfg
, lams
->oem_id
,
405 if (tpm_get_version(tpm_find()) == TPM_VERSION_2_0
) {
406 acpi_add_table(table_offsets
, tables_blob
);
407 build_tpm2(tables_blob
, tables
->linker
,
408 tables
->tcpalog
, lams
->oem_id
,
412 /* Add tables supplied by user (if any) */
413 for (u
= acpi_table_first(); u
; u
= acpi_table_next(u
)) {
414 unsigned len
= acpi_table_len(u
);
416 acpi_add_table(table_offsets
, tables_blob
);
417 g_array_append_vals(tables_blob
, u
, len
);
420 /* RSDT is pointed to by RSDP */
421 rsdt
= tables_blob
->len
;
422 build_rsdt(tables_blob
, tables
->linker
, table_offsets
,
423 lams
->oem_id
, lams
->oem_table_id
);
425 /* RSDP is in FSEG memory, so allocate it separately */
427 AcpiRsdpData rsdp_data
= {
429 .oem_id
= lams
->oem_id
,
430 .xsdt_tbl_offset
= NULL
,
431 .rsdt_tbl_offset
= &rsdt
,
433 build_rsdp(tables
->rsdp
, tables
->linker
, &rsdp_data
);
437 * The align size is 128, warn if 64k is not enough therefore
438 * the align size could be resized.
440 if (tables_blob
->len
> ACPI_BUILD_TABLE_SIZE
/ 2) {
441 warn_report("ACPI table size %u exceeds %d bytes,"
442 " migration may not work",
443 tables_blob
->len
, ACPI_BUILD_TABLE_SIZE
/ 2);
444 error_printf("Try removing CPUs, NUMA nodes, memory slots"
448 acpi_align_size(tables
->linker
->cmd_blob
, ACPI_BUILD_ALIGN_SIZE
);
450 /* Cleanup memory that's no longer used. */
451 g_array_free(table_offsets
, true);
454 static void acpi_ram_update(MemoryRegion
*mr
, GArray
*data
)
456 uint32_t size
= acpi_data_len(data
);
459 * Make sure RAM size is correct - in case it got changed
462 memory_region_ram_resize(mr
, size
, &error_abort
);
464 memcpy(memory_region_get_ram_ptr(mr
), data
->data
, size
);
465 memory_region_set_dirty(mr
, 0, size
);
468 static void acpi_build_update(void *build_opaque
)
470 AcpiBuildState
*build_state
= build_opaque
;
471 AcpiBuildTables tables
;
473 /* No state to update or already patched? Nothing to do. */
474 if (!build_state
|| build_state
->patched
) {
477 build_state
->patched
= 1;
479 acpi_build_tables_init(&tables
);
481 acpi_build(&tables
, MACHINE(qdev_get_machine()));
483 acpi_ram_update(build_state
->table_mr
, tables
.table_data
);
484 acpi_ram_update(build_state
->rsdp_mr
, tables
.rsdp
);
485 acpi_ram_update(build_state
->linker_mr
, tables
.linker
->cmd_blob
);
487 acpi_build_tables_cleanup(&tables
, true);
490 static void acpi_build_reset(void *build_opaque
)
492 AcpiBuildState
*build_state
= build_opaque
;
493 build_state
->patched
= 0;
496 static const VMStateDescription vmstate_acpi_build
= {
497 .name
= "acpi_build",
499 .minimum_version_id
= 1,
500 .fields
= (VMStateField
[]) {
501 VMSTATE_UINT8(patched
, AcpiBuildState
),
502 VMSTATE_END_OF_LIST()
506 void loongarch_acpi_setup(LoongArchMachineState
*lams
)
508 AcpiBuildTables tables
;
509 AcpiBuildState
*build_state
;
512 ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n");
516 if (!loongarch_is_acpi_enabled(lams
)) {
517 ACPI_BUILD_DPRINTF("ACPI disabled. Bailing out.\n");
521 build_state
= g_malloc0(sizeof *build_state
);
523 acpi_build_tables_init(&tables
);
524 acpi_build(&tables
, MACHINE(lams
));
526 /* Now expose it all to Guest */
527 build_state
->table_mr
= acpi_add_rom_blob(acpi_build_update
,
528 build_state
, tables
.table_data
,
529 ACPI_BUILD_TABLE_FILE
);
530 assert(build_state
->table_mr
!= NULL
);
532 build_state
->linker_mr
=
533 acpi_add_rom_blob(acpi_build_update
, build_state
,
534 tables
.linker
->cmd_blob
, ACPI_BUILD_LOADER_FILE
);
536 build_state
->rsdp_mr
= acpi_add_rom_blob(acpi_build_update
,
537 build_state
, tables
.rsdp
,
538 ACPI_BUILD_RSDP_FILE
);
540 qemu_register_reset(acpi_build_reset
, build_state
);
541 acpi_build_reset(build_state
);
542 vmstate_register(NULL
, 0, &vmstate_acpi_build
, build_state
);
545 * Cleanup tables but don't free the memory: we track it
548 acpi_build_tables_cleanup(&tables
, false);