diff options
Diffstat (limited to 'drivers/arm/gic/gic_v3.c')
-rw-r--r-- | drivers/arm/gic/gic_v3.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/drivers/arm/gic/gic_v3.c b/drivers/arm/gic/gic_v3.c new file mode 100644 index 000000000..76b08639c --- /dev/null +++ b/drivers/arm/gic/gic_v3.c @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2018, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <arch.h> +#include <arch_helpers.h> +#include <arm_gic.h> +#include <assert.h> +#include <debug.h> +#include <gic_common.h> +#include <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; + +#ifndef AARCH32 +#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(unsigned int 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(unsigned int base, unsigned int val) +{ + mmio_write_32(base + GICR_ISENABLER0, val); +} + +static void gicr_write_icenabler0(unsigned int base, unsigned int val) +{ + mmio_write_32(base + GICR_ICENABLER0, val); +} + +static void gicr_write_icpendr0(unsigned int 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(unsigned int 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(unsigned int base) +{ + return mmio_read_32(base + GICR_ISPENDR0); +} + +/****************************************************************************** + * GIC Re-distributor interface accessors for individual interrupt + * manipulation + *****************************************************************************/ +static unsigned int gicr_get_isenabler0(unsigned int 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(unsigned int 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(unsigned int 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(unsigned int 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); +#ifndef AARCH32 + 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 = +#ifndef AARCH32 + ((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); +#ifndef AARCH32 + 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 */ +#ifndef AARCH32 + 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; +} |