blob: e4a35a8621733730188a99707fb3c61560cf17ea [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <arch/arm64/mmu.h>
#include <arch/hypervisor.h>
#include <dev/interrupt.h>
#include <fbl/auto_lock.h>
#include <hypervisor/cpu.h>
#include <kernel/cpu.h>
#include <kernel/mutex.h>
#include <ktl/move.h>
#include <lk/init.h>
#include <vm/physmap.h>
#include <vm/pmm.h>
#include "el2_cpu_state_priv.h"
namespace {
DECLARE_SINGLETON_MUTEX(GuestMutex);
} // namespace
static size_t num_guests TA_GUARDED(GuestMutex::Get()) = 0;
static ktl::unique_ptr<El2CpuState> el2_cpu_state TA_GUARDED(GuestMutex::Get());
zx_status_t El2TranslationTable::Init() {
zx_status_t status = l0_page_.Alloc(0);
if (status != ZX_OK) {
return status;
}
status = l1_page_.Alloc(0);
if (status != ZX_OK) {
return status;
}
// L0: Point to a single L1 translation table.
pte_t* l0_pte = l0_page_.VirtualAddress<pte_t>();
*l0_pte = l1_page_.PhysicalAddress() | MMU_PTE_L012_DESCRIPTOR_TABLE;
// L1: Identity map the first 512GB of physical memory at.
pte_t* l1_pte = l1_page_.VirtualAddress<pte_t>();
for (size_t i = 0; i < PAGE_SIZE / sizeof(pte_t); i++) {
l1_pte[i] = i * (1u << 30) | MMU_PTE_ATTR_AF | MMU_PTE_ATTR_SH_INNER_SHAREABLE |
MMU_PTE_ATTR_AP_P_RW_U_RW | MMU_PTE_ATTR_NORMAL_MEMORY |
MMU_PTE_L012_DESCRIPTOR_BLOCK;
}
__dmb(ARM_MB_SY);
return ZX_OK;
}
zx_paddr_t El2TranslationTable::Base() const { return l0_page_.PhysicalAddress(); }
zx_status_t El2Stack::Alloc() { return page_.Alloc(0); }
zx_paddr_t El2Stack::Top() const { return page_.PhysicalAddress() + PAGE_SIZE; }
zx_status_t El2CpuState::OnTask(void* context, cpu_num_t cpu_num) {
auto cpu_state = static_cast<El2CpuState*>(context);
El2TranslationTable& table = cpu_state->table_;
El2Stack& stack = cpu_state->stacks_[cpu_num];
zx_status_t status = arm64_el2_on(table.Base(), stack.Top());
if (status != ZX_OK) {
dprintf(CRITICAL, "Failed to turn EL2 on for CPU %u\n", cpu_num);
return status;
}
unmask_interrupt(kMaintenanceVector);
unmask_interrupt(kTimerVector);
return ZX_OK;
}
static void el2_off_task(void* arg) {
mask_interrupt(kTimerVector);
mask_interrupt(kMaintenanceVector);
zx_status_t status = arm64_el2_off();
if (status != ZX_OK) {
dprintf(CRITICAL, "Failed to turn EL2 off for CPU %u\n", arch_curr_cpu_num());
}
}
// static
zx_status_t El2CpuState::Create(ktl::unique_ptr<El2CpuState>* out) {
fbl::AllocChecker ac;
ktl::unique_ptr<El2CpuState> cpu_state(new (&ac) El2CpuState);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = cpu_state->Init();
if (status != ZX_OK) {
return status;
}
// Initialise the EL2 translation table.
status = cpu_state->table_.Init();
if (status != ZX_OK) {
return status;
}
// Allocate EL2 stack for each CPU.
size_t num_cpus = arch_max_num_cpus();
El2Stack* stacks = new (&ac) El2Stack[num_cpus];
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
fbl::Array<El2Stack> el2_stacks(stacks, num_cpus);
for (auto& stack : el2_stacks) {
zx_status_t status = stack.Alloc();
if (status != ZX_OK) {
return status;
}
}
cpu_state->stacks_ = ktl::move(el2_stacks);
// Setup EL2 for all online CPUs.
cpu_state->cpu_mask_ = percpu_exec(OnTask, cpu_state.get());
if (cpu_state->cpu_mask_ != mp_get_online_mask()) {
return ZX_ERR_NOT_SUPPORTED;
}
*out = ktl::move(cpu_state);
return ZX_OK;
}
El2CpuState::~El2CpuState() { mp_sync_exec(MP_IPI_TARGET_MASK, cpu_mask_, el2_off_task, nullptr); }
zx_status_t alloc_vmid(uint8_t* vmid) {
Guard<Mutex> guard(GuestMutex::Get());
if (num_guests == 0) {
zx_status_t status = El2CpuState::Create(&el2_cpu_state);
if (status != ZX_OK) {
return status;
}
}
num_guests++;
return el2_cpu_state->AllocId(vmid);
}
zx_status_t free_vmid(uint8_t vmid) {
Guard<Mutex> guard(GuestMutex::Get());
zx_status_t status = el2_cpu_state->FreeId(vmid);
if (status != ZX_OK) {
return status;
}
num_guests--;
if (num_guests == 0) {
el2_cpu_state.reset();
}
return ZX_OK;
}
LK_INIT_HOOK(
hypervisor_el2_state,
[](unsigned int) {
// Work around fxbug.dev/78920 by initialising the Mutex during boot.
//
// TODO(fxbug.dev/78920): Remove this once singleton mutexes are thread-safe on first use.
GuestMutex::Get();
},
LK_INIT_LEVEL_ARCH)