blob: b4ddd839ffd1aeb95603e01b3df11ace9e8fbfc8 [file] [log] [blame]
/*
* Copyright 2019, 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 <vka/capops.h>
#include <vka/object.h>
#include <sel4vm/guest_vm.h>
#include <sel4vm/guest_ram.h>
#include <sel4vm/guest_memory.h>
#include <sel4vm/guest_memory_helpers.h>
#include <platsupport/plat/acpi/acpi.h>
#include <sel4vmmplatsupport/guest_memory_util.h>
#include <sel4vmmplatsupport/arch/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(vm_t *vm, uintptr_t paddr, void *vaddr,
size_t size, size_t offset, void *cookie)
{
(void)offset;
memcpy((char *)vaddr, (char *)cookie, size);
return 0;
}
struct bios_iterator_cookie {
cspacepath_t *bios_frames;
vm_t *vm;
};
static vm_frame_t bios_memory_iterator(uintptr_t addr, void *cookie)
{
int error;
cspacepath_t return_frame;
vm_frame_t frame_result = { seL4_CapNull, seL4_NoRights, 0, 0 };
struct bios_iterator_cookie *bios_cookie = (struct bios_iterator_cookie *)cookie;
cspacepath_t *bios_frames = bios_cookie->bios_frames;
vm_t *vm = bios_cookie->vm;
int page_size = seL4_PageBits;
uintptr_t frame_start = ROUND_DOWN(addr, BIT(page_size));
if (frame_start < LOWER_BIOS_START || frame_start > LOWER_BIOS_START + LOWER_BIOS_SIZE) {
ZF_LOGE("Error: Not BIOS region");
return frame_result;
}
int page_idx = (frame_start - LOWER_BIOS_START) / BIT(page_size);
error = vka_cspace_alloc_path(vm->vka, &return_frame);
if (error) {
ZF_LOGE("Failed to allocate cspace path for bios frame");
return frame_result;
}
error = vka_cnode_copy(&return_frame, &bios_frames[page_idx], seL4_AllRights);
if (error) {
ZF_LOGE("Failed to cnode_copy for device frame");
vka_cspace_free_path(vm->vka, return_frame);
return frame_result;
}
frame_result.cptr = return_frame.capPtr;
frame_result.rights = seL4_AllRights;
frame_result.vaddr = frame_start;
frame_result.size_bits = page_size;
return frame_result;
}
static void *alloc_bios_memory(vm_t *vm, cspacepath_t **bios_frames)
{
int err;
unsigned int num_pages = ROUND_UP(LOWER_BIOS_SIZE, BIT(seL4_PageBits)) >> seL4_PageBits;
seL4_CPtr caps[num_pages];
cspacepath_t *bios_frames_paths = (cspacepath_t *)calloc(1, num_pages * sizeof(cspacepath_t));
if (!bios_frames_paths) {
ZF_LOGE("Failed to allocate caps for bios memory");
return NULL;
}
uintptr_t current_addr = LOWER_BIOS_START;
for (unsigned int i = 0; i < num_pages; i++) {
vka_object_t frame;
err = vka_alloc_frame(vm->vka, seL4_PageBits, &frame);
if (err) {
ZF_LOGE("Failed to allocate frame for bios addr 0x%x", (unsigned int)current_addr);
return NULL;
}
vka_cspace_make_path(vm->vka, frame.cptr, &bios_frames_paths[i]);
caps[i] = bios_frames_paths[i].capPtr;
current_addr += BIT(seL4_PageBits);
}
void *bios_addr = vspace_map_pages(&vm->mem.vmm_vspace, caps, NULL, seL4_AllRights,
num_pages, seL4_PageBits, 0);
if (!bios_addr) {
ZF_LOGE("Failed to map new pages for bios memory");
return NULL;
}
memset(bios_addr, LOWER_BIOS_SIZE, 0);
*bios_frames = bios_frames_paths;
return bios_addr;
}
// Give some ACPI tables to the guest
int make_guest_acpi_tables(vm_t *vm)
{
ZF_LOGD("Making ACPI tables\n");
int cpus = vm->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 = calloc(1, 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];
}
cspacepath_t *bios_frames;
uintptr_t lower_bios_addr = (uintptr_t)alloc_bios_memory(vm, &bios_frames);
if (lower_bios_addr == 0) {
return -1;
}
uintptr_t xsdt_addr = lower_bios_addr + (XSDT_START - LOWER_BIOS_START);
acpi_xsdt_t *xsdt = calloc(1, 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++) {
ZF_LOGD("ACPI table \"%.4s\", addr = %p, size = %zu bytes\n",
(char *)tables[i], (void *)table_paddr, table_sizes[i]);
memcpy((void *)table_paddr, (char *)tables[i], table_sizes[i]);
table_paddr += table_sizes[i];
}
// RSDP
uintptr_t rsdp_addr = lower_bios_addr + (ACPI_START - LOWER_BIOS_START);
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));
ZF_LOGD("ACPI RSDP addr = %p\n", (void *)rsdp_addr);
memcpy((void *)rsdp_addr, (char *)&rsdp, sizeof(rsdp));
struct bios_iterator_cookie *bios_cookie = calloc(1, sizeof(struct bios_iterator_cookie));
if (!bios_cookie) {
ZF_LOGE("Failed to allocate bios iterator cookie");
return -1;
}
bios_cookie->vm = vm;
bios_cookie->bios_frames = bios_frames;
vm_memory_reservation_t *bios_reservation = vm_reserve_memory_at(vm, LOWER_BIOS_START, LOWER_BIOS_SIZE,
default_error_fault_callback, NULL);
if (!bios_reservation) {
ZF_LOGE("Failed to reserve LOWER BIOS memory");
return -1;
}
return vm_map_reservation(vm, bios_reservation, bios_memory_iterator, (void *)bios_cookie);
}