blob: cd7fb7393ff5cee1a5a7689d3680b5a0679d28f9 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2012-2015 Travis Geiselbrecht
//
// 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 "arm_gicv2.h"
#include <assert.h>
#include <bits.h>
#include <debug.h>
#include <inttypes.h>
#include <lib/ktrace.h>
#include <lib/root_resource_filter.h>
#include <lib/system-topology.h>
#include <sys/types.h>
#include <trace.h>
#include <zircon/boot/driver-config.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <arch/arm64/hypervisor/gic/gicv2.h>
#include <arch/arm64/periphmap.h>
#include <arch/ops.h>
#include <arch/regs.h>
#include <dev/interrupt.h>
#include <dev/interrupt/arm_gic_common.h>
#include <dev/interrupt/arm_gicv2_init.h>
#include <dev/interrupt/arm_gicv2_regs.h>
#include <dev/interrupt/arm_gicv2m.h>
#include <dev/interrupt/arm_gicv2m_msi.h>
#include <kernel/cpu.h>
#include <kernel/stats.h>
#include <kernel/thread.h>
#include <ktl/iterator.h>
#include <lk/init.h>
#include <pdev/interrupt.h>
#include "arm_gicv2m_pcie.h"
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
#include <arch/arm64.h>
#define IFRAME_PC(frame) ((frame)->elr)
// Values read from the config.
vaddr_t arm_gicv2_gic_base = 0;
static uint64_t mmio_phys = 0;
static uint64_t msi_frame_phys = 0;
uint64_t arm_gicv2_gicd_offset = 0;
uint64_t arm_gicv2_gicc_offset = 0;
uint64_t arm_gicv2_gich_offset = 0;
uint64_t arm_gicv2_gicv_offset = 0;
static uint32_t ipi_base = 0;
static bool use_msi = false;
static uint max_irqs = 0;
static arm_gicv2::CpuMaskTranslator mask_translator;
static zx_status_t arm_gic_init();
static zx_status_t gic_configure_interrupt(unsigned int vector, enum interrupt_trigger_mode tm,
enum interrupt_polarity pol);
static uint32_t read_gicd_targetsr(int target_reg) { return GICREG(0, GICD_ITARGETSR(target_reg)); }
uint8_t gic_determine_local_mask(
fit::inline_function<uint32_t(int), sizeof(void*)> fetch_gicd_targetsr_reg) {
union {
char bytes[4];
uint32_t value;
} decoder;
// The first 7 GICD_ITARGETSR registers only return values that apply to the
// calling CPU. These are interrupt target registers so each byte is a cpu_mask
// of what cpus are targeted by their cpu_mask from GIC's perspective. The
// upside is that we can scan these target regsiters to determine what our
// cpu_mask is from the GIC's perspective.
for (int target_reg = 0; target_reg < 8; target_reg++) {
decoder.value = fetch_gicd_targetsr_reg(target_reg);
for (int i = 0; i < 4; i++) {
if (decoder.bytes[i] != 0) {
return decoder.bytes[i];
}
}
}
printf("GICv2: unable to determine local GIC mask!\n");
return 0;
}
static bool gic_is_valid_interrupt(uint vector, uint32_t flags) { return (vector < max_irqs); }
static uint32_t gic_get_base_vector() {
// ARM Generic Interrupt Controller v2 chapter 2.1
// INTIDs 0-15 are local CPU interrupts
return 16;
}
static uint32_t gic_get_max_vector() { return max_irqs; }
static void gic_set_enable(uint vector, bool enable) {
int reg = vector / 32;
uint32_t mask = (uint32_t)(1ULL << (vector % 32));
if (enable) {
GICREG(0, GICD_ISENABLER(reg)) = mask;
} else {
GICREG(0, GICD_ICENABLER(reg)) = mask;
}
}
static void gic_init_percpu_early() {
GICREG(0, GICC_CTLR) = 0x201; // EnableGrp1 and EOImodeNS
GICREG(0, GICC_PMR) = 0xff; // unmask interrupts at all priority levels
}
static unsigned int arm_gic_max_cpu() { return (GICREG(0, GICD_TYPER) >> 5) & 0x7; }
static zx_status_t arm_gic_init() {
uint i;
uint32_t typer = GICREG(0, GICD_TYPER);
uint32_t it_lines_number = BITS_SHIFT(typer, 4, 0);
max_irqs = (it_lines_number + 1) * 32;
printf("GICv2 detected: max interrupts %u, max_cpus %u, TYPER %#x\n", max_irqs, arm_gic_max_cpu(),
typer);
DEBUG_ASSERT(max_irqs <= MAX_INT);
for (i = 0; i < max_irqs; i += 32) {
GICREG(0, GICD_ICENABLER(i / 32)) = ~0;
GICREG(0, GICD_ICPENDR(i / 32)) = ~0;
}
if (arm_gic_max_cpu() > 0) {
// Set external interrupts to target cpu 0
for (i = 32; i < max_irqs; i += 4) {
GICREG(0, GICD_ITARGETSR(i / 4)) = 0x01010101;
}
}
// Initialize all the SPIs to edge triggered
for (i = GIC_BASE_SPI; i < max_irqs; i++) {
gic_configure_interrupt(i, IRQ_TRIGGER_MODE_EDGE, IRQ_POLARITY_ACTIVE_HIGH);
}
GICREG(0, GICD_CTLR) = 1; // enable GIC0
gic_init_percpu_early();
return ZX_OK;
}
static zx_status_t arm_gic_sgi(unsigned int irq, unsigned int flags, unsigned int cpu_mask) {
unsigned int val = ((flags & ARM_GIC_SGI_FLAG_TARGET_FILTER_MASK) << 24) |
((cpu_mask & 0xff) << 16) | ((flags & ARM_GIC_SGI_FLAG_NS) ? (1U << 15) : 0) |
(irq & 0xf);
if (irq >= 16) {
return ZX_ERR_INVALID_ARGS;
}
LTRACEF("GICD_SGIR: %x\n", val);
GICREG(0, GICD_SGIR) = val;
return ZX_OK;
}
static zx_status_t gic_mask_interrupt(unsigned int vector) {
LTRACEF("vector %u\n", vector);
if (vector >= max_irqs) {
return ZX_ERR_INVALID_ARGS;
}
gic_set_enable(vector, false);
return ZX_OK;
}
static zx_status_t gic_unmask_interrupt(unsigned int vector) {
LTRACEF("vector %u\n", vector);
if (vector >= max_irqs) {
return ZX_ERR_INVALID_ARGS;
}
gic_set_enable(vector, true);
return ZX_OK;
}
static zx_status_t gic_deactivate_interrupt(unsigned int vector) {
if (vector >= max_irqs) {
return ZX_ERR_INVALID_ARGS;
}
uint32_t reg = 1 << (vector % 32);
GICREG(0, GICD_ICACTIVER(vector / 32)) = reg;
return ZX_OK;
}
static zx_status_t gic_configure_interrupt(unsigned int vector, enum interrupt_trigger_mode tm,
enum interrupt_polarity pol) {
// Only configurable for SPI interrupts
if ((vector >= max_irqs) || (vector < GIC_BASE_SPI)) {
return ZX_ERR_INVALID_ARGS;
}
if (pol != IRQ_POLARITY_ACTIVE_HIGH) {
// TODO: polarity should actually be configure through a GPIO controller
return ZX_ERR_NOT_SUPPORTED;
}
// type is encoded with two bits, MSB of the two determine type
// 16 irqs encoded per ICFGR register
uint32_t reg_ndx = vector >> 4;
uint32_t bit_shift = ((vector & 0xf) << 1) + 1;
uint32_t reg_val = GICREG(0, GICD_ICFGR(reg_ndx));
if (tm == IRQ_TRIGGER_MODE_EDGE) {
reg_val |= (1 << bit_shift);
} else {
reg_val &= ~(1 << bit_shift);
}
GICREG(0, GICD_ICFGR(reg_ndx)) = reg_val;
const uint32_t clear_reg = vector / 32;
const uint32_t clear_mask = 1 << (vector % 32);
GICREG(0, GICD_ICPENDR(clear_reg)) = clear_mask;
return ZX_OK;
}
static zx_status_t gic_get_interrupt_config(unsigned int vector, enum interrupt_trigger_mode* tm,
enum interrupt_polarity* pol) {
if (vector >= max_irqs) {
return ZX_ERR_INVALID_ARGS;
}
if (tm) {
*tm = IRQ_TRIGGER_MODE_EDGE;
}
if (pol) {
*pol = IRQ_POLARITY_ACTIVE_HIGH;
}
return ZX_OK;
}
static unsigned int gic_remap_interrupt(unsigned int vector) { return vector; }
static void gic_handle_irq(iframe_t* frame) {
// get the current vector
uint32_t iar = GICREG(0, GICC_IAR);
unsigned int vector = iar & 0x3ff;
if (vector >= 0x3fe) {
// spurious
return;
}
// tracking external hardware irqs in this variable
if (vector >= 32)
CPU_STATS_INC(interrupts);
cpu_num_t cpu = arch_curr_cpu_num();
ktrace_tiny(TAG_IRQ_ENTER, (vector << 8) | cpu);
LTRACEF_LEVEL(2, "iar 0x%x cpu %u currthread %p vector %u pc %#" PRIxPTR "\n", iar, cpu,
Thread::Current::Get(), vector, (uintptr_t)IFRAME_PC(frame));
// deliver the interrupt
interrupt_eoi eoi;
if (!pdev_invoke_int_if_present(vector, &eoi)) {
eoi = IRQ_EOI_DEACTIVATE;
}
GICREG(0, GICC_EOIR) = iar;
if (eoi == IRQ_EOI_DEACTIVATE) {
GICREG(0, GICC_DIR) = iar;
}
LTRACEF_LEVEL(2, "cpu %u exit\n", cpu);
ktrace_tiny(TAG_IRQ_EXIT, (vector << 8) | cpu);
}
static void gic_send_ipi(cpu_mask_t logical_target, mp_ipi_t ipi) {
const cpu_mask_t target = mask_translator.LogicalMaskToGic(logical_target);
uint gic_ipi_num = ipi + ipi_base;
if (target != 0) {
LTRACEF("target 0x%x, gic_ipi %u\n", target, gic_ipi_num);
arm_gic_sgi(gic_ipi_num, ARM_GIC_SGI_FLAG_NS, target);
}
}
static interrupt_eoi arm_ipi_halt_handler(void*) {
LTRACEF("cpu %u\n", arch_curr_cpu_num());
arch_disable_ints();
while (true) {
__wfi();
}
return IRQ_EOI_DEACTIVATE;
}
static void gic_init_percpu() {
mp_set_curr_cpu_online(true);
unmask_interrupt(MP_IPI_GENERIC + ipi_base);
unmask_interrupt(MP_IPI_RESCHEDULE + ipi_base);
unmask_interrupt(MP_IPI_INTERRUPT + ipi_base);
unmask_interrupt(MP_IPI_HALT + ipi_base);
const cpu_num_t logical_num = arch_curr_cpu_num();
system_topology::Node* node = nullptr;
auto status =
system_topology::Graph::GetSystemTopology().ProcessorByLogicalId(logical_num, &node);
if (status == ZX_OK) {
DEBUG_ASSERT(node->entity_type == ZBI_TOPOLOGY_ENTITY_PROCESSOR &&
node->entity.processor.architecture == ZBI_TOPOLOGY_ARCH_ARM);
mask_translator.SetGicIdForLogicalId(logical_num,
node->entity.processor.architecture_info.arm.gic_id);
} else {
printf("arm_gicv2: unable to get logical processor %u in topology, status: %d\n", logical_num,
status);
}
// Lookup the gic_mask for this processor via GIC registers and compare it
// to what we were told by the bootloader, warn if there is a mismatch.
const cpu_mask_t gic_mask = gic_determine_local_mask(read_gicd_targetsr);
const cpu_mask_t assigned_gic_mask = mask_translator.GetGicMask(logical_num);
if (gic_mask != assigned_gic_mask) {
printf(
"arm_gicv2: WARNING assigned gic_id of %u does not match processor's gic_id of %u."
"Successful operation is unlikely!",
assigned_gic_mask, gic_mask);
}
LTRACEF("logical_cpu_mask: %u programmatic_gic_mask: %u assigned_gic_mask: %u\n",
cpu_num_to_mask(arch_curr_cpu_num()), gic_mask, assigned_gic_mask);
}
static void gic_shutdown() {
// Turn off all GIC0 interrupts at the distributor.
GICREG(0, GICD_CTLR) = 0;
}
// Returns true if any PPIs are enabled on the calling CPU.
static bool is_ppi_enabled() {
DEBUG_ASSERT(arch_ints_disabled());
// PPIs are 16-31.
uint32_t ppi_mask = 0xffff0000;
// GICD_ISENABLER0 is banked so it corresponds to *this* CPU's interface.
return (GICREG(0, GICD_ISENABLER(0)) & ppi_mask) != 0;
}
// Returns true if any SPIs are enabled on the calling CPU.
static bool is_spi_enabled() {
DEBUG_ASSERT(arch_ints_disabled());
// We're going to check four interrupts at a time. Build a repeated mask for the current CPU.
// Each byte in the mask is a CPU bit mask corresponding to CPU0..CPU7 (lsb..msb).
cpu_num_t cpu_num = arch_curr_cpu_num();
DEBUG_ASSERT(cpu_num < 8);
uint32_t mask = 0x01010101U << cpu_num;
for (unsigned int vector = GIC_BASE_SPI; vector < max_irqs; vector += 4) {
uint32_t reg = GICREG(0, GICD_ITARGETSR(vector / 4));
if (reg & mask) {
return true;
}
}
return false;
}
static void gic_shutdown_cpu() {
DEBUG_ASSERT(arch_ints_disabled());
// If we're running on a secondary CPU there's a good chance this CPU will be powered off shortly
// (PSCI_CPU_OFF). Sending an interrupt to a CPU that's been powered off may result in an
// "erronerous state" (see Power State Coordination Interface (PSCI) System Software on ARM
// specification, 5.5.2). So before we shutdown the GIC, make sure we've migrated/disabled any
// and all peripheral interrupts targeted at this CPU (PPIs and SPIs).
//
// Note, we don't perform these checks on the boot CPU because we don't call PSCI_CPU_OFF on the
// boot CPU, and we likely still have PPIs and SPIs targeting the boot CPU.
DEBUG_ASSERT(arch_curr_cpu_num() == BOOT_CPU_ID || !is_ppi_enabled());
DEBUG_ASSERT(arch_curr_cpu_num() == BOOT_CPU_ID || !is_spi_enabled());
// Turn off interrupts at the CPU interface.
GICREG(0, GICC_CTLR) = 0;
}
static const struct pdev_interrupt_ops gic_ops = {
.mask = gic_mask_interrupt,
.unmask = gic_unmask_interrupt,
.deactivate = gic_deactivate_interrupt,
.configure = gic_configure_interrupt,
.get_config = gic_get_interrupt_config,
.is_valid = gic_is_valid_interrupt,
.get_base_vector = gic_get_base_vector,
.get_max_vector = gic_get_max_vector,
.remap = gic_remap_interrupt,
.send_ipi = gic_send_ipi,
.init_percpu_early = gic_init_percpu_early,
.init_percpu = gic_init_percpu,
.handle_irq = gic_handle_irq,
.shutdown = gic_shutdown,
.shutdown_cpu = gic_shutdown_cpu,
.msi_is_supported = arm_gicv2m_msi_is_supported,
.msi_supports_masking = arm_gicv2m_msi_supports_masking,
.msi_mask_unmask = arm_gicv2m_msi_mask_unmask,
.msi_alloc_block = arm_gicv2m_msi_alloc_block,
.msi_free_block = arm_gicv2m_msi_free_block,
.msi_register_handler = arm_gicv2m_msi_register_handler,
};
void ArmGicInitEarly(const dcfg_arm_gicv2_driver_t& config) {
ASSERT(config.mmio_phys);
arm_gicv2_gic_base = periph_paddr_to_vaddr(config.mmio_phys);
ASSERT(arm_gicv2_gic_base);
mmio_phys = config.mmio_phys;
msi_frame_phys = config.msi_frame_phys;
arm_gicv2_gicd_offset = config.gicd_offset;
arm_gicv2_gicc_offset = config.gicc_offset;
arm_gicv2_gich_offset = config.gich_offset;
arm_gicv2_gicv_offset = config.gicv_offset;
ipi_base = config.ipi_base;
use_msi = config.use_msi;
if (arm_gic_init() != ZX_OK) {
if (config.optional) {
// failed to detect gic v2 but it's marked optional. continue
return;
}
printf("GICv2: failed to detect GICv2, interrupts will be broken\n");
return;
}
dprintf(SPEW, "GICv2 (ID %#x), IPI base %u, GICH offset %#lx, GICV offset %#lx\n",
GICREG(0, GICC_IIDR), ipi_base, arm_gicv2_gich_offset, arm_gicv2_gicv_offset);
// pass the list of physical and virtual addresses for the GICv2m register apertures
if (msi_frame_phys) {
// the following arrays must be static because arm_gicv2m_init stashes the pointer
static paddr_t GICV2M_REG_FRAMES[] = {0};
static vaddr_t GICV2M_REG_FRAMES_VIRT[] = {0};
GICV2M_REG_FRAMES[0] = msi_frame_phys;
GICV2M_REG_FRAMES_VIRT[0] = periph_paddr_to_vaddr(msi_frame_phys);
ASSERT(GICV2M_REG_FRAMES_VIRT[0]);
arm_gicv2m_init(GICV2M_REG_FRAMES, GICV2M_REG_FRAMES_VIRT, ktl::size(GICV2M_REG_FRAMES));
}
pdev_register_interrupts(&gic_ops);
zx_status_t status = gic_register_sgi_handler(MP_IPI_GENERIC + ipi_base, &mp_mbx_generic_irq);
DEBUG_ASSERT(status == ZX_OK);
status = gic_register_sgi_handler(MP_IPI_RESCHEDULE + ipi_base, &mp_mbx_reschedule_irq);
DEBUG_ASSERT(status == ZX_OK);
status = gic_register_sgi_handler(MP_IPI_INTERRUPT + ipi_base, &mp_mbx_interrupt_irq);
DEBUG_ASSERT(status == ZX_OK);
status = gic_register_sgi_handler(MP_IPI_HALT + ipi_base, &arm_ipi_halt_handler);
DEBUG_ASSERT(status == ZX_OK);
gicv2_hw_interface_register();
}
void ArmGicInitLate(const dcfg_arm_gicv2_driver_t& config) {
ASSERT(mmio_phys);
arm_gicv2_pcie_init(use_msi);
// Place the physical address of the GICv2 registers on the MMIO deny list.
// Users will not be able to create MMIO resources which permit mapping of the
// GIC registers, even if they have access to the root resource.
root_resource_filter_add_deny_region(mmio_phys + arm_gicv2_gicc_offset, GICC_REG_SIZE,
ZX_RSRC_KIND_MMIO);
root_resource_filter_add_deny_region(mmio_phys + arm_gicv2_gicd_offset, GICD_REG_SIZE,
ZX_RSRC_KIND_MMIO);
if (arm_gicv2_gich_offset) {
root_resource_filter_add_deny_region(mmio_phys + arm_gicv2_gich_offset, GICH_REG_SIZE,
ZX_RSRC_KIND_MMIO);
}
if (arm_gicv2_gicv_offset) {
root_resource_filter_add_deny_region(mmio_phys + arm_gicv2_gicv_offset, GICV_REG_SIZE,
ZX_RSRC_KIND_MMIO);
}
if (msi_frame_phys) {
root_resource_filter_add_deny_region(msi_frame_phys, GICV2M_FRAME_REG_SIZE, ZX_RSRC_KIND_MMIO);
}
}