blob: 93c707a5d73198be349c469b97e2eb31d7fc3f31 [file] [log] [blame]
/*
* Copyright (c) 2018-2020, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <drivers/arm/arm_gic.h>
#include <drivers/arm/gic_common.h>
#include <drivers/arm/gic_v3.h>
#include <mmio.h>
#include <platform.h>
/* Global variables to store the GIC base addresses */
static uintptr_t gicr_base_addr;
static uintptr_t gicd_base_addr;
#ifdef __aarch64__
#define MPIDR_AFFLVL3_MASK ((unsigned long long)MPIDR_AFFLVL_MASK << MPIDR_AFF3_SHIFT)
#define gic_typer_affinity_from_mpidr(mpidr) \
(((mpidr) & (~MPIDR_AFFLVL3_MASK)) | (((mpidr) & MPIDR_AFFLVL3_MASK) >> 8))
#else
#define gic_typer_affinity_from_mpidr(mpidr) \
((mpidr) & ((MPIDR_AFFLVL_MASK << MPIDR_AFF2_SHIFT) | MPID_MASK))
#endif
/*
* Data structure to store the GIC per CPU context before entering
* system suspend. Only the GIC context of first 32 interrupts (SGIs and PPIs)
* will be saved. The GIC SPI context needs to be restored by the respective
* drivers.
*/
struct gicv3_pcpu_ctx {
/* Flag to indicate whether the CPU is suspended */
unsigned int is_suspended;
unsigned int icc_igrpen1;
unsigned int gicr_isenabler;
unsigned int gicr_ipriorityr[NUM_PCPU_INTR >> IPRIORITYR_SHIFT];
unsigned int gicr_icfgr;
};
/* Array to store the per-cpu GICv3 context when being suspended.*/
static struct gicv3_pcpu_ctx pcpu_ctx[PLATFORM_CORE_COUNT];
/* Array to store the per-cpu redistributor frame addresses */
static uintptr_t rdist_pcpu_base[PLATFORM_CORE_COUNT];
/*
* Array to store the mpidr corresponding to each initialized per-CPU
* redistributor interface.
*/
static unsigned long long mpidr_list[PLATFORM_CORE_COUNT] = {UINT64_MAX};
/******************************************************************************
* GIC Distributor interface accessors for writing entire registers
*****************************************************************************/
static void gicd_write_irouter(uintptr_t base,
unsigned int interrupt_id,
unsigned long long route)
{
assert(interrupt_id >= MIN_SPI_ID);
mmio_write_64(base + GICD_IROUTER + (interrupt_id << 3), route);
}
/******************************************************************************
* GIC Re-distributor interface accessors for writing entire registers
*****************************************************************************/
static void gicr_write_isenabler0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ISENABLER0, val);
}
static void gicr_write_icenabler0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICENABLER0, val);
}
static void gicr_write_icpendr0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICPENDR0, val);
}
static void gicr_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned n = id >> IPRIORITYR_SHIFT;
mmio_write_32(base + GICR_IPRIORITYR + (n << 2), val);
}
static void gicr_write_icfgr1(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICFGR1, val);
}
/******************************************************************************
* GIC Re-distributor interface accessors for reading entire registers
*****************************************************************************/
static unsigned long long gicr_read_typer(uintptr_t base)
{
return mmio_read_64(base + GICR_TYPER);
}
static unsigned int gicr_read_icfgr1(uintptr_t base)
{
return mmio_read_32(base + GICR_ICFGR1);
}
static unsigned int gicr_read_isenabler0(uintptr_t base)
{
return mmio_read_32(base + GICR_ISENABLER0);
}
static unsigned int gicr_read_ipriorityr(uintptr_t base, unsigned int id)
{
unsigned n = id >> IPRIORITYR_SHIFT;
return mmio_read_32(base + GICR_IPRIORITYR + (n << 2));
}
static unsigned int gicr_read_ispendr0(uintptr_t base)
{
return mmio_read_32(base + GICR_ISPENDR0);
}
/******************************************************************************
* GIC Re-distributor interface accessors for individual interrupt
* manipulation
*****************************************************************************/
static unsigned int gicr_get_isenabler0(uintptr_t base,
unsigned int interrupt_id)
{
unsigned bit_num = interrupt_id & ((1 << ISENABLER_SHIFT) - 1);
return gicr_read_isenabler0(base) & (1 << bit_num);
}
static void gicr_set_isenabler0(uintptr_t base, unsigned int interrupt_id)
{
unsigned bit_num = interrupt_id & ((1 << ISENABLER_SHIFT) - 1);
gicr_write_isenabler0(base, (1 << bit_num));
}
static void gicr_set_icenabler0(uintptr_t base, unsigned int interrupt_id)
{
unsigned bit_num = interrupt_id & ((1 << ISENABLER_SHIFT) - 1);
gicr_write_icenabler0(base, (1 << bit_num));
}
static void gicr_set_icpendr0(uintptr_t base, unsigned int interrupt_id)
{
unsigned bit_num = interrupt_id & ((1 << ICPENDR_SHIFT) - 1);
gicr_write_icpendr0(base, (1 << bit_num));
}
/******************************************************************************
* GICv3 public driver API
*****************************************************************************/
void gicv3_enable_cpuif(void)
{
/* Assert that system register access is enabled */
assert(IS_IN_EL2() ? (read_icc_sre_el2() & ICC_SRE_SRE_BIT) :
(read_icc_sre_el1() & ICC_SRE_SRE_BIT));
/* Enable Group1 non secure interrupts */
write_icc_igrpen1_el1(read_icc_igrpen1_el1() | IGRPEN1_EL1_ENABLE_BIT);
isb();
}
void gicv3_setup_cpuif(void)
{
/* Set the priority mask register to allow all interrupts to trickle in */
write_icc_pmr_el1(GIC_PRI_MASK);
isb();
gicv3_enable_cpuif();
}
void gicv3_disable_cpuif(void)
{
/* Disable Group1 non secure interrupts */
write_icc_igrpen1_el1(read_icc_igrpen1_el1() &
~IGRPEN1_EL1_ENABLE_BIT);
isb();
}
void gicv3_save_cpuif_context(void)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
/* Set the `is_suspended` flag as this core is being suspended. */
pcpu_ctx[core_pos].is_suspended = 1;
pcpu_ctx[core_pos].icc_igrpen1 = read_icc_igrpen1_el1();
}
void gicv3_restore_cpuif_context(void)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
/* Reset the `is_suspended` flag as this core has resumed from suspend. */
pcpu_ctx[core_pos].is_suspended = 0;
write_icc_pmr_el1(GIC_PRI_MASK);
write_icc_igrpen1_el1(pcpu_ctx[core_pos].icc_igrpen1);
isb();
}
void gicv3_save_sgi_ppi_context(void)
{
unsigned int i, core_pos;
unsigned int my_core_pos = platform_get_core_pos(read_mpidr_el1());
/* Save the context for all the suspended cores */
for (core_pos = 0; core_pos < PLATFORM_CORE_COUNT; core_pos++) {
/*
* Continue if the core pos is not the current core
* and has not suspended
*/
if ((core_pos != my_core_pos) &&
(!pcpu_ctx[core_pos].is_suspended))
continue;
assert(rdist_pcpu_base[core_pos]);
pcpu_ctx[core_pos].gicr_isenabler =
gicr_read_isenabler0(rdist_pcpu_base[core_pos]);
/* Read the ipriority registers, 4 at a time */
for (i = 0; i < (NUM_PCPU_INTR >> IPRIORITYR_SHIFT); i++)
pcpu_ctx[core_pos].gicr_ipriorityr[i] =
gicr_read_ipriorityr(rdist_pcpu_base[core_pos],
i << IPRIORITYR_SHIFT);
pcpu_ctx[core_pos].gicr_icfgr =
gicr_read_icfgr1(rdist_pcpu_base[core_pos]);
}
}
void gicv3_restore_sgi_ppi_context(void)
{
unsigned int i, core_pos;
unsigned int my_core_pos = platform_get_core_pos(read_mpidr_el1());
/* Restore the context for all the suspended cores */
for (core_pos = 0; core_pos < PLATFORM_CORE_COUNT; core_pos++) {
/*
* Continue if the core pos is not the current core
* and has not suspended
*/
if ((core_pos != my_core_pos) &&
(!pcpu_ctx[core_pos].is_suspended))
continue;
assert(rdist_pcpu_base[core_pos]);
/* Read the ipriority registers, 4 at a time */
for (i = 0; i < (NUM_PCPU_INTR >> IPRIORITYR_SHIFT); i++)
gicr_write_ipriorityr(rdist_pcpu_base[core_pos],
i << IPRIORITYR_SHIFT,
pcpu_ctx[core_pos].gicr_ipriorityr[i]);
gicr_write_icfgr1(rdist_pcpu_base[core_pos],
pcpu_ctx[core_pos].gicr_icfgr);
gicr_write_isenabler0(rdist_pcpu_base[core_pos],
pcpu_ctx[core_pos].gicr_isenabler);
}
}
unsigned int gicv3_get_ipriorityr(unsigned int interrupt_id)
{
unsigned int core_pos;
assert(gicd_base_addr);
assert(IS_VALID_INTR_ID(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
return mmio_read_8(rdist_pcpu_base[core_pos] + GICR_IPRIORITYR
+ interrupt_id);
} else {
return mmio_read_8(gicd_base_addr +
GICD_IPRIORITYR + interrupt_id);
}
}
void gicv3_set_ipriorityr(unsigned int interrupt_id,
unsigned int priority)
{
unsigned int core_pos;
assert(gicd_base_addr);
assert(IS_VALID_INTR_ID(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
mmio_write_8(rdist_pcpu_base[core_pos] + GICR_IPRIORITYR
+ interrupt_id, priority & GIC_PRI_MASK);
} else {
mmio_write_8(gicd_base_addr + GICD_IPRIORITYR + interrupt_id,
priority & GIC_PRI_MASK);
}
}
void gicv3_send_sgi(unsigned int sgi_id, unsigned int core_pos)
{
unsigned long long aff0, aff1, aff2;
unsigned long long sgir, target_list;
assert(IS_SGI(sgi_id));
assert(core_pos < PLATFORM_CORE_COUNT);
assert(mpidr_list[core_pos] != UINT64_MAX);
/* Extract the affinity information */
aff0 = MPIDR_AFF_ID(mpidr_list[core_pos], 0);
aff1 = MPIDR_AFF_ID(mpidr_list[core_pos], 1);
aff2 = MPIDR_AFF_ID(mpidr_list[core_pos], 2);
#ifdef __aarch64__
unsigned long long aff3;
aff3 = MPIDR_AFF_ID(mpidr_list[core_pos], 3);
#endif
/* Construct the SGI target list using Affinity 0 */
assert(aff0 < SGI_TARGET_MAX_AFF0);
target_list = 1 << aff0;
/* Construct the SGI target affinity */
sgir =
#ifdef __aarch64__
((aff3 & SGI1R_AFF_MASK) << SGI1R_AFF3_SHIFT) |
#endif
((aff2 & SGI1R_AFF_MASK) << SGI1R_AFF2_SHIFT) |
((aff1 & SGI1R_AFF_MASK) << SGI1R_AFF1_SHIFT) |
((target_list & SGI1R_TARGET_LIST_MASK)
<< SGI1R_TARGET_LIST_SHIFT);
/* Combine SGI target affinity with the SGI ID */
sgir |= ((sgi_id & SGI1R_INTID_MASK) << SGI1R_INTID_SHIFT);
#ifdef __aarch64__
write_icc_sgi1r(sgir);
#else
write64_icc_sgi1r(sgir);
#endif
isb();
}
void gicv3_set_intr_route(unsigned int interrupt_id,
unsigned int core_pos)
{
unsigned long long route_affinity;
assert(gicd_base_addr);
assert(core_pos < PLATFORM_CORE_COUNT);
assert(mpidr_list[core_pos] != UINT64_MAX);
/* Routing information can be set only for SPIs */
assert(IS_SPI(interrupt_id));
route_affinity = mpidr_list[core_pos];
gicd_write_irouter(gicd_base_addr, interrupt_id, route_affinity);
}
unsigned int gicv3_get_isenabler(unsigned int interrupt_id)
{
unsigned int core_pos;
assert(gicd_base_addr);
assert(IS_VALID_INTR_ID(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
return gicr_get_isenabler0(rdist_pcpu_base[core_pos], interrupt_id);
} else
return gicd_get_isenabler(gicd_base_addr, interrupt_id);
}
void gicv3_set_isenabler(unsigned int interrupt_id)
{
unsigned int core_pos;
assert(gicd_base_addr);
assert(IS_VALID_INTR_ID(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
gicr_set_isenabler0(rdist_pcpu_base[core_pos], interrupt_id);
} else
gicd_set_isenabler(gicd_base_addr, interrupt_id);
}
void gicv3_set_icenabler(unsigned int interrupt_id)
{
unsigned int core_pos;
assert(gicd_base_addr);
assert(IS_VALID_INTR_ID(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
gicr_set_icenabler0(rdist_pcpu_base[core_pos], interrupt_id);
} else
gicd_set_icenabler(gicd_base_addr, interrupt_id);
}
unsigned int gicv3_get_ispendr(unsigned int interrupt_id)
{
unsigned int ispendr;
unsigned int bit_pos, core_pos;
assert(gicd_base_addr);
assert(IS_VALID_INTR_ID(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
ispendr = gicr_read_ispendr0(rdist_pcpu_base[core_pos]);
} else
ispendr = gicd_read_ispendr(gicd_base_addr, interrupt_id);
bit_pos = interrupt_id % (1 << ISPENDR_SHIFT);
return !!(ispendr & (1 << bit_pos));
}
void gicv3_set_icpendr(unsigned int interrupt_id)
{
unsigned int core_pos;
assert(gicd_base_addr);
assert(IS_SPI(interrupt_id) || IS_PPI(interrupt_id));
if (interrupt_id < MIN_SPI_ID) {
core_pos = platform_get_core_pos(read_mpidr_el1());
assert(rdist_pcpu_base[core_pos]);
gicr_set_icpendr0(rdist_pcpu_base[core_pos], interrupt_id);
} else
gicd_set_icpendr(gicd_base_addr, interrupt_id);
}
void gicv3_probe_redistif_addr(void)
{
unsigned long long typer_val;
uintptr_t rdistif_base;
unsigned long long affinity;
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
assert(gicr_base_addr);
/*
* Return if the re-distributor base address is already populated
* for this core.
*/
if (rdist_pcpu_base[core_pos])
return;
/* Iterate over the GICR frames and find the matching frame*/
rdistif_base = gicr_base_addr;
affinity = gic_typer_affinity_from_mpidr(read_mpidr_el1() & MPIDR_AFFINITY_MASK);
do {
typer_val = gicr_read_typer(rdistif_base);
if (affinity == ((typer_val >> TYPER_AFF_VAL_SHIFT) & TYPER_AFF_VAL_MASK)) {
rdist_pcpu_base[core_pos] = rdistif_base;
mpidr_list[core_pos] = read_mpidr_el1() & MPIDR_AFFINITY_MASK;
return;
}
rdistif_base += (1 << GICR_PCPUBASE_SHIFT);
} while (!(typer_val & TYPER_LAST_BIT));
ERROR("Re-distributor address not found for core %d\n", core_pos);
panic();
}
void gicv3_setup_distif(void)
{
unsigned int gicd_ctlr;
assert(gicd_base_addr);
/* Check for system register support */
#ifdef __aarch64__
assert(read_id_aa64pfr0_el1() &
(ID_AA64PFR0_GIC_MASK << ID_AA64PFR0_GIC_SHIFT));
#else
assert(read_id_pfr1() & (ID_PFR1_GIC_MASK << ID_PFR1_GIC_SHIFT));
#endif
/* Assert that system register access is enabled */
assert(is_sre_enabled());
/* Enable the forwarding of interrupts to CPU interface */
gicd_ctlr = gicd_read_ctlr(gicd_base_addr);
/* Assert ARE_NS bit in GICD */
assert(gicd_ctlr & (GICD_CTLR_ARE_NS_MASK << GICD_CTLR_ARE_NS_SHIFT));
gicd_ctlr |= GICD_CTLR_ENABLE_GRP1A;
gicd_write_ctlr(gicd_base_addr, gicd_ctlr);
}
void gicv3_init(uintptr_t gicr_base, uintptr_t gicd_base)
{
assert(gicr_base);
assert(gicd_base);
gicr_base_addr = gicr_base;
gicd_base_addr = gicd_base;
}
unsigned int gicv3_get_gicd_typer(void)
{
return gicd_read_typer(gicd_base_addr);
}