| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the GNU General Public License version 2. Note that NO WARRANTY is provided. |
| * See "LICENSE_GPLv2.txt" for details. |
| * |
| * @TAG(DATA61_GPL) |
| */ |
| |
| /* Routines for generating guest ACPI tables. |
| Author: W.A. */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "vmm/debug.h" |
| #include "vmm/platform/acpi.h" |
| #include "vmm/platform/guest_memory.h" |
| #include "vmm/platform/guest_vspace.h" |
| #include "vmm/processor/apicdef.h" |
| |
| #include "platsupport/plat/acpi/acpi.h" |
| |
| #define APIC_FLAGS_ENABLED (1) |
| |
| uint8_t acpi_calc_checksum(const char* table, int length) |
| { |
| uint32_t sum = 0; |
| |
| for (int i = 0; i < length; i++) { |
| sum += *table++; |
| } |
| |
| return 0x100 - (sum & 0xFF); |
| } |
| |
| static void acpi_fill_table_head(acpi_header_t *head, const char *signature, uint8_t rev) { |
| const char *oem = "NICTA "; |
| const char *padd = " "; |
| memcpy(head->signature, signature, sizeof(head->signature)); |
| memcpy(head->oem_id, oem, sizeof(head->oem_id)); |
| memcpy(head->creator_id, oem, sizeof(head->creator_id)); |
| memcpy(head->oem_table_id, signature, sizeof(head->signature)); |
| memcpy(head->oem_table_id + sizeof(head->signature), padd, 4); |
| |
| head->revision = rev; |
| head->checksum = 0; |
| head->length = sizeof(*head); |
| head->oem_revision = rev; |
| head->creator_revision = 1; |
| } |
| |
| static int make_guest_acpi_tables_continued(uintptr_t paddr, void *vaddr, |
| size_t size, size_t offset, void *cookie) { |
| (void)offset; |
| memcpy((char *)vaddr, (char *)cookie, size); |
| return 0; |
| } |
| |
| // Give some ACPI tables to the guest |
| int make_guest_acpi_tables(vmm_t *vmm) { |
| DPRINTF(2, "Making ACPI tables\n"); |
| |
| int cpus = vmm->num_vcpus; |
| |
| int err; |
| |
| // XSDT and other tables |
| void *tables[MAX_ACPI_TABLES]; |
| size_t table_sizes[MAX_ACPI_TABLES]; |
| int num_tables = 1; |
| |
| // MADT |
| int madt_size = sizeof(acpi_madt_t) |
| /* + sizeof(acpi_madt_ioapic_t)*/ |
| + sizeof(acpi_madt_local_apic_t) * cpus; |
| acpi_madt_t *madt = malloc(madt_size); |
| acpi_fill_table_head(&madt->header, "APIC", 3); |
| madt->local_int_crt_address = APIC_DEFAULT_PHYS_BASE; |
| madt->flags = 1; |
| |
| char *madt_entry = (char *)madt + sizeof(acpi_madt_t); |
| |
| #if 0 |
| acpi_madt_ioapic_t ioapic = { // MADT IOAPIC entry |
| .header = { |
| .type = ACPI_APIC_IOAPIC, |
| .length = sizeof(acpi_madt_ioapic_t) |
| }, |
| .ioapic_id = 0, |
| .address = IOAPIC_DEFAULT_PHYS_BASE, |
| .gs_interrupt_base = 0 // TODO set this up? |
| }; |
| memcpy(madt_entry, &ioapic, sizeof(ioapic)); |
| madt_entry += sizeof(ioapic); |
| #endif |
| |
| for (int i = 0; i < cpus; i++) { // MADT APIC entries |
| acpi_madt_local_apic_t apic = { |
| .header = { |
| .type = ACPI_APIC_LOCAL, |
| .length = sizeof(acpi_madt_local_apic_t) |
| }, |
| .processor_id = i + 1, |
| .apic_id = i, |
| .flags = APIC_FLAGS_ENABLED |
| }; |
| memcpy(madt_entry, &apic, sizeof(apic)); |
| madt_entry += sizeof(apic); |
| } |
| |
| madt->header.length = madt_size; |
| madt->header.checksum = acpi_calc_checksum((char *)madt, madt_size); |
| |
| tables[num_tables] = madt; |
| table_sizes[num_tables] = madt_size; |
| num_tables++; |
| |
| // Could set up other tables here... |
| |
| // XSDT |
| size_t xsdt_size = sizeof(acpi_xsdt_t) + sizeof(uint64_t) * (num_tables - 1); |
| |
| // How much space will all the tables take up? |
| size_t acpi_size = xsdt_size; |
| for (int i = 1; i < num_tables; i++) { |
| acpi_size += table_sizes[i]; |
| } |
| |
| // Allocate some frames for this |
| uintptr_t xsdt_addr = 0xe1000; // TODO actually allocate frames, can be anywhere |
| |
| acpi_xsdt_t *xsdt = malloc(xsdt_size); |
| acpi_fill_table_head(&xsdt->header, "XSDT", 1); |
| |
| // Add previous tables to XSDT pointer list |
| uintptr_t table_paddr = xsdt_addr + xsdt_size; |
| uint64_t *entry = (uint64_t *)((char *)xsdt + sizeof(acpi_xsdt_t)); |
| for (int i = 1; i < num_tables; i++) { |
| *entry++ = (uint64_t)table_paddr; |
| table_paddr += table_sizes[i]; |
| } |
| |
| xsdt->header.length = xsdt_size; |
| xsdt->header.checksum = acpi_calc_checksum((char *)xsdt, xsdt_size); |
| |
| tables[0] = xsdt; |
| table_sizes[0] = xsdt_size; |
| |
| // Copy all the tables to guest |
| table_paddr = xsdt_addr; |
| for (int i = 0; i < num_tables; i++) { |
| DPRINTF(2, "ACPI table \"%.4s\", addr = %p, size = %zu bytes\n", |
| (char *)tables[i], (void*)table_paddr, table_sizes[i]); |
| err = vmm_guest_vspace_touch(&vmm->guest_mem.vspace, table_paddr, |
| table_sizes[i], make_guest_acpi_tables_continued, tables[i]); |
| if (err) { |
| return err; |
| } |
| table_paddr += table_sizes[i]; |
| } |
| |
| // RSDP |
| uintptr_t rsdp_addr = ACPI_START; |
| err = vmm_alloc_guest_device_at(vmm, ACPI_START, sizeof(acpi_rsdp_t)); |
| if (err) { |
| return err; |
| } |
| |
| acpi_rsdp_t rsdp = { |
| .signature = "RSD PTR ", |
| .oem_id = "NICTA ", |
| .revision = 2, /* ACPI v3*/ |
| .checksum = 0, |
| .rsdt_address = xsdt_addr, |
| /* rsdt_addrss will not be inspected as the xsdt is present. |
| This is not ACPI 1 compliant */ |
| .length = sizeof(acpi_rsdp_t), |
| .xsdt_address = xsdt_addr, |
| .extended_checksum = 0, |
| .reserved = {0} |
| }; |
| |
| rsdp.checksum = acpi_calc_checksum((char *)&rsdp, 20); |
| rsdp.extended_checksum = acpi_calc_checksum((char *)&rsdp, sizeof(rsdp)); |
| |
| DPRINTF(2, "ACPI RSDP addr = %p\n", (void*)rsdp_addr); |
| |
| return vmm_guest_vspace_touch(&vmm->guest_mem.vspace, rsdp_addr, sizeof(rsdp), |
| make_guest_acpi_tables_continued, &rsdp); |
| } |