blob: cc66c328046a9bf7b79165367d650c3cfd204fb6 [file] [log] [blame]
// Copyright 2018 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 <assert.h>
#include <arch/arm64/hypervisor/el2_state.h>
#include <arch/arm64/hypervisor/gic/gicv2.h>
#include <dev/interrupt/arm_gic_hw_interface.h>
#include <dev/interrupt/arm_gicv2_regs.h>
#include <vm/pmm.h>
static constexpr uint32_t kNumLrs = 64;
// Representation of GICH registers. For details please refer to ARM Generic Interrupt
// Controller Architecture Specification Version 2, 5.3 GIC virtual interface control
// registers.
typedef struct Gich {
uint32_t hcr;
uint32_t vtr;
uint32_t vmcr;
uint32_t reserved0;
uint32_t misr;
uint32_t reserved1[3];
uint32_t eisr0;
uint32_t eisr1;
uint32_t reserved2[2];
uint32_t elrsr0;
uint32_t elrsr1;
uint32_t reserved3[46];
uint32_t apr;
uint32_t reserved4[3];
uint32_t lr[kNumLrs];
} __attribute__((__packed__)) Gich;
static_assert(__offsetof(Gich, hcr) == 0x00, "");
static_assert(__offsetof(Gich, vtr) == 0x04, "");
static_assert(__offsetof(Gich, vmcr) == 0x08, "");
static_assert(__offsetof(Gich, misr) == 0x10, "");
static_assert(__offsetof(Gich, eisr0) == 0x20, "");
static_assert(__offsetof(Gich, eisr1) == 0x24, "");
static_assert(__offsetof(Gich, elrsr0) == 0x30, "");
static_assert(__offsetof(Gich, elrsr1) == 0x34, "");
static_assert(__offsetof(Gich, apr) == 0xf0, "");
static_assert(__offsetof(Gich, lr) == 0x100, "");
static volatile Gich* gich = NULL;
static zx_status_t gicv2_get_gicv(paddr_t* gicv_paddr) {
// Check for presence of GICv2 virtualisation extensions.
if (GICV_OFFSET == 0) {
return ZX_ERR_NOT_SUPPORTED;
}
*gicv_paddr = vaddr_to_paddr(reinterpret_cast<void*>(GICV_ADDRESS));
return ZX_OK;
}
static void giv2_read_gich_state(IchState* state) {
DEBUG_ASSERT(state->num_aprs == 1);
DEBUG_ASSERT(state->num_lrs <= kNumLrs);
gich->hcr = 0;
state->vmcr = gich->vmcr;
state->misr = gich->misr;
state->elrsr = gich->elrsr0 | (static_cast<uint64_t>(gich->elrsr1) << 32);
state->apr[0][0] = gich->apr;
for (uint8_t i = 0; i < state->num_lrs; i++) {
state->lr[i] = gich->lr[i];
}
}
static void giv2_write_gich_state(IchState* state, uint32_t hcr) {
DEBUG_ASSERT(state->num_aprs == 1);
DEBUG_ASSERT(state->num_lrs <= kNumLrs);
gich->hcr = hcr;
gich->vmcr = state->vmcr;
gich->apr = static_cast<uint32_t>(state->apr[0][0]);
for (uint8_t i = 0; i < state->num_lrs; i++) {
uint32_t lr = static_cast<uint32_t>(state->lr[i]);
if (lr & GICH_LR_HARDWARE) {
// We are adding a physical interrupt to a list register, therefore
// we mark the physical interrupt as active on the physical
// distributor so that the guest can deactivate it directly.
uint32_t vector = GICH_LR_VIRTUAL_ID(lr);
uint32_t reg = vector / 32;
uint32_t mask = 1u << (vector % 32);
GICREG(0, GICD_ISACTIVER(reg)) = mask;
}
gich->lr[i] = lr;
}
}
static uint32_t gicv2_default_gich_vmcr() {
return GICH_VMCR_VPMR | GICH_VMCR_VENG0;
}
static uint64_t gicv2_get_lr_from_vector(bool hw, uint8_t prio, uint32_t vector) {
uint64_t lr = GICH_LR_PENDING | GICH_LR_PRIORITY(prio) | GICH_LR_VIRTUAL_ID(vector);
if (hw) {
lr |= GICH_LR_HARDWARE | GICH_LR_PHYSICAL_ID(vector);
}
return lr;
}
static uint32_t gicv2_get_vector_from_lr(uint64_t lr) {
return lr & GICH_LR_VIRTUAL_ID(UINT64_MAX);
}
static uint8_t gicv2_get_num_pres() {
return static_cast<uint8_t>(GICH_VTR_PRES(gich->vtr));
}
static uint8_t gicv2_get_num_lrs() {
return static_cast<uint8_t>(GICH_VTR_LRS(gich->vtr));
}
static const struct arm_gic_hw_interface_ops gic_hw_register_ops = {
.get_gicv = gicv2_get_gicv,
.read_gich_state = giv2_read_gich_state,
.write_gich_state = giv2_write_gich_state,
.default_gich_vmcr = gicv2_default_gich_vmcr,
.get_lr_from_vector = gicv2_get_lr_from_vector,
.get_vector_from_lr = gicv2_get_vector_from_lr,
.get_num_pres = gicv2_get_num_pres,
.get_num_lrs = gicv2_get_num_lrs,
};
void gicv2_hw_interface_register() {
// Populate GICH
gich = reinterpret_cast<volatile Gich*>(GICH_ADDRESS);
arm_gic_hw_interface_register(&gic_hw_register_ops);
}