- X86 cpuid feature list dump was using the wrong array and walking off the end of one. - GICv2 code had a left shift by up to 31 of an integer. Needs to be unsigned. - PLIC same as GIC code. - fdtwalker code should be using a bytewise accessor based helper function for reading large integers out of an unaliged FDT. - PCI BIOS32 search code could do a 32bit unaligned read of a string, switch to using memcmp.
645 lines
20 KiB
C
645 lines
20 KiB
C
/*
|
|
* 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 <assert.h>
|
|
#include <lk/bits.h>
|
|
#include <lk/err.h>
|
|
#include <sys/types.h>
|
|
#include <lk/debug.h>
|
|
#include <dev/interrupt/arm_gic.h>
|
|
#include <lk/reg.h>
|
|
#include <kernel/thread.h>
|
|
#include <kernel/debug.h>
|
|
#include <lk/init.h>
|
|
#include <platform/interrupts.h>
|
|
#include <arch/ops.h>
|
|
#include <platform/gic.h>
|
|
#include <lk/trace.h>
|
|
#if WITH_LIB_SM
|
|
#include <lib/sm.h>
|
|
#include <lib/sm/sm_err.h>
|
|
#endif
|
|
|
|
#define LOCAL_TRACE 0
|
|
|
|
#if ARCH_ARM
|
|
#include <arch/arm.h>
|
|
#define iframe arm_iframe
|
|
#define IFRAME_PC(frame) ((frame)->pc)
|
|
#endif
|
|
#if ARCH_ARM64
|
|
#include <arch/arm64.h>
|
|
#define iframe arm64_iframe_short
|
|
#define IFRAME_PC(frame) ((frame)->elr)
|
|
#endif
|
|
|
|
static status_t arm_gic_set_secure_locked(u_int irq, bool secure);
|
|
|
|
static spin_lock_t gicd_lock;
|
|
#if WITH_LIB_SM
|
|
#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_IRQ_FIQ
|
|
#else
|
|
#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_INTERRUPTS
|
|
#endif
|
|
#define GIC_MAX_PER_CPU_INT 32
|
|
|
|
#if WITH_LIB_SM
|
|
static bool arm_gic_non_secure_interrupts_frozen;
|
|
|
|
static bool arm_gic_interrupt_change_allowed(uint irq) {
|
|
if (!arm_gic_non_secure_interrupts_frozen)
|
|
return true;
|
|
|
|
TRACEF("change to interrupt %u ignored after booting ns\n", irq);
|
|
return false;
|
|
}
|
|
|
|
static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd);
|
|
#else
|
|
static bool arm_gic_interrupt_change_allowed(uint irq) {
|
|
return true;
|
|
}
|
|
|
|
static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd) {
|
|
}
|
|
#endif
|
|
|
|
|
|
struct int_handler_struct {
|
|
int_handler handler;
|
|
void *arg;
|
|
};
|
|
|
|
static struct int_handler_struct int_handler_table_per_cpu[GIC_MAX_PER_CPU_INT][SMP_MAX_CPUS];
|
|
static struct int_handler_struct int_handler_table_shared[MAX_INT-GIC_MAX_PER_CPU_INT];
|
|
|
|
static struct int_handler_struct *get_int_handler(unsigned int vector, uint cpu) {
|
|
if (vector < GIC_MAX_PER_CPU_INT) {
|
|
return &int_handler_table_per_cpu[vector][cpu];
|
|
} else {
|
|
return &int_handler_table_shared[vector - GIC_MAX_PER_CPU_INT];
|
|
}
|
|
}
|
|
|
|
void register_int_handler(unsigned int vector, int_handler handler, void *arg) {
|
|
struct int_handler_struct *h;
|
|
uint cpu = arch_curr_cpu_num();
|
|
|
|
spin_lock_saved_state_t state;
|
|
|
|
if (vector >= MAX_INT)
|
|
panic("register_int_handler: vector out of range %d\n", vector);
|
|
|
|
spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
|
|
|
|
if (arm_gic_interrupt_change_allowed(vector)) {
|
|
h = get_int_handler(vector, cpu);
|
|
h->handler = handler;
|
|
h->arg = arg;
|
|
}
|
|
|
|
spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
|
|
}
|
|
|
|
void register_int_handler_msi(unsigned int vector, int_handler handler, void *arg, bool edge) {
|
|
// only can deal with edge triggered at the moment
|
|
DEBUG_ASSERT(edge);
|
|
|
|
register_int_handler(vector, handler, arg);
|
|
}
|
|
|
|
/* main cpu regs */
|
|
#define GICC_CTLR (GICC_OFFSET + 0x0000)
|
|
#define GICC_PMR (GICC_OFFSET + 0x0004)
|
|
#define GICC_BPR (GICC_OFFSET + 0x0008)
|
|
#define GICC_IAR (GICC_OFFSET + 0x000c)
|
|
#define GICC_EOIR (GICC_OFFSET + 0x0010)
|
|
#define GICC_RPR (GICC_OFFSET + 0x0014)
|
|
#define GICC_HPPIR (GICC_OFFSET + 0x0018)
|
|
#define GICC_APBR (GICC_OFFSET + 0x001c)
|
|
#define GICC_AIAR (GICC_OFFSET + 0x0020)
|
|
#define GICC_AEOIR (GICC_OFFSET + 0x0024)
|
|
#define GICC_AHPPIR (GICC_OFFSET + 0x0028)
|
|
#define GICC_APR(n) (GICC_OFFSET + 0x00d0 + (n) * 4)
|
|
#define GICC_NSAPR(n) (GICC_OFFSET + 0x00e0 + (n) * 4)
|
|
#define GICC_IIDR (GICC_OFFSET + 0x00fc)
|
|
#define GICC_DIR (GICC_OFFSET + 0x1000)
|
|
|
|
/* distribution regs */
|
|
#define GICD_CTLR (GICD_OFFSET + 0x000)
|
|
#define GICD_TYPER (GICD_OFFSET + 0x004)
|
|
#define GICD_IIDR (GICD_OFFSET + 0x008)
|
|
#define GICD_IGROUPR(n) (GICD_OFFSET + 0x080 + (n) * 4)
|
|
#define GICD_ISENABLER(n) (GICD_OFFSET + 0x100 + (n) * 4)
|
|
#define GICD_ICENABLER(n) (GICD_OFFSET + 0x180 + (n) * 4)
|
|
#define GICD_ISPENDR(n) (GICD_OFFSET + 0x200 + (n) * 4)
|
|
#define GICD_ICPENDR(n) (GICD_OFFSET + 0x280 + (n) * 4)
|
|
#define GICD_ISACTIVER(n) (GICD_OFFSET + 0x300 + (n) * 4)
|
|
#define GICD_ICACTIVER(n) (GICD_OFFSET + 0x380 + (n) * 4)
|
|
#define GICD_IPRIORITYR(n) (GICD_OFFSET + 0x400 + (n) * 4)
|
|
#define GICD_ITARGETSR(n) (GICD_OFFSET + 0x800 + (n) * 4)
|
|
#define GICD_ICFGR(n) (GICD_OFFSET + 0xc00 + (n) * 4)
|
|
#define GICD_NSACR(n) (GICD_OFFSET + 0xe00 + (n) * 4)
|
|
#define GICD_SGIR (GICD_OFFSET + 0xf00)
|
|
#define GICD_CPENDSGIR(n) (GICD_OFFSET + 0xf10 + (n) * 4)
|
|
#define GICD_SPENDSGIR(n) (GICD_OFFSET + 0xf20 + (n) * 4)
|
|
|
|
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
|
|
#define GIC_REG_COUNT(bit_per_reg) DIV_ROUND_UP(MAX_INT, (bit_per_reg))
|
|
#define DEFINE_GIC_SHADOW_REG(name, bit_per_reg, init_val, init_from) \
|
|
uint32_t (name)[GIC_REG_COUNT(bit_per_reg)] = { \
|
|
[((init_from) / (bit_per_reg)) ... \
|
|
(GIC_REG_COUNT(bit_per_reg) - 1)] = (init_val) \
|
|
}
|
|
|
|
#if WITH_LIB_SM
|
|
static DEFINE_GIC_SHADOW_REG(gicd_igroupr, 32, ~0U, 0);
|
|
#endif
|
|
static DEFINE_GIC_SHADOW_REG(gicd_itargetsr, 4, 0x01010101, 32);
|
|
|
|
// accessor routines for GIC registers that go through the mmio interface
|
|
static inline uint32_t gicreg_read32(uint32_t gic, uint32_t register_offset) {
|
|
return mmio_read32((volatile uint32_t *)(GICBASE(gic) + register_offset));
|
|
}
|
|
|
|
static inline void gicreg_write32(uint32_t gic, uint32_t register_offset, uint32_t value) {
|
|
mmio_write32((volatile uint32_t *)(GICBASE(gic) + register_offset), value);
|
|
}
|
|
|
|
static void gic_set_enable(uint vector, bool enable) {
|
|
uint reg = vector / 32;
|
|
uint32_t mask = 1ULL << (vector % 32);
|
|
|
|
if (enable) {
|
|
gicreg_write32(0, GICD_ISENABLER(reg), mask);
|
|
} else {
|
|
gicreg_write32(0, GICD_ICENABLER(reg), mask);
|
|
}
|
|
}
|
|
|
|
static void arm_gic_init_percpu(uint level) {
|
|
#if WITH_LIB_SM
|
|
gicreg_write32(0, GICC_CTLR, 0xb); // enable GIC0 and select fiq mode for secure
|
|
gicreg_write32(0, GICD_IGROUPR(0), ~0U); /* GICD_IGROUPR0 is banked */
|
|
#else
|
|
gicreg_write32(0, GICC_CTLR, 1); // enable GIC0
|
|
#endif
|
|
gicreg_write32(0, GICC_PMR, 0xFF); // unmask interrupts at all priority levels
|
|
}
|
|
|
|
LK_INIT_HOOK_FLAGS(arm_gic_init_percpu,
|
|
arm_gic_init_percpu,
|
|
LK_INIT_LEVEL_PLATFORM_EARLY, LK_INIT_FLAG_SECONDARY_CPUS);
|
|
|
|
static void arm_gic_suspend_cpu(uint level) {
|
|
suspend_resume_fiq(false, false);
|
|
}
|
|
|
|
LK_INIT_HOOK_FLAGS(arm_gic_suspend_cpu, arm_gic_suspend_cpu,
|
|
LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_SUSPEND);
|
|
|
|
static void arm_gic_resume_cpu(uint level) {
|
|
spin_lock_saved_state_t state;
|
|
bool resume_gicd = false;
|
|
|
|
spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
|
|
if (!(gicreg_read32(0, GICD_CTLR) & 1)) {
|
|
dprintf(SPEW, "%s: distibutor is off, calling arm_gic_init instead\n", __func__);
|
|
arm_gic_init();
|
|
resume_gicd = true;
|
|
} else {
|
|
arm_gic_init_percpu(0);
|
|
}
|
|
spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
|
|
suspend_resume_fiq(true, resume_gicd);
|
|
}
|
|
|
|
LK_INIT_HOOK_FLAGS(arm_gic_resume_cpu, arm_gic_resume_cpu,
|
|
LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_RESUME);
|
|
|
|
static uint arm_gic_max_cpu(void) {
|
|
return (gicreg_read32(0, GICD_TYPER) >> 5) & 0x7;
|
|
}
|
|
|
|
static 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_INT) || (vector < GIC_BASE_SPI)) {
|
|
return ERR_INVALID_ARGS;
|
|
}
|
|
|
|
if (pol != IRQ_POLARITY_ACTIVE_HIGH) {
|
|
// TODO: polarity should actually be configure through a GPIO controller
|
|
return 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_read32(0, GICD_ICFGR(reg_ndx));
|
|
if (tm == IRQ_TRIGGER_MODE_EDGE) {
|
|
reg_val |= (1U << bit_shift);
|
|
} else {
|
|
reg_val &= ~(1U << bit_shift);
|
|
}
|
|
gicreg_write32(0, GICD_ICFGR(reg_ndx), reg_val);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void arm_gic_init(void) {
|
|
// Are we a GICv2?
|
|
// NOTE: probably crashes on a V3
|
|
uint32_t iidr = gicreg_read32(0, GICC_IIDR);
|
|
if (BITS_SHIFT(iidr, 19, 16) != 0x2) {
|
|
dprintf(CRITICAL, "GIC: not a GICv2, IIDR 0x%x\n", iidr);
|
|
return;
|
|
}
|
|
dprintf(INFO, "GIC: version %lu\n", BITS_SHIFT(iidr, 19, 16));
|
|
|
|
// Read how many cpus and interrupts we support
|
|
uint32_t type = gicreg_read32(0, GICD_TYPER);
|
|
uint32_t cpu_count = (type >> 5) & 0x7;
|
|
uint32_t it_lines = (type & 0x1f) + 1;
|
|
if (it_lines > 6) {
|
|
it_lines = 6;
|
|
}
|
|
int max_int = (int)it_lines * 32;
|
|
if (max_int > MAX_INT) {
|
|
max_int = MAX_INT;
|
|
}
|
|
dprintf(INFO, "GICv2: GICD_TYPER 0x%x, cpu_count %u, max_int %u\n", type, cpu_count + 1, max_int);
|
|
|
|
for (int i = 0; i < max_int; i+= 32) {
|
|
gicreg_write32(0, GICD_ICENABLER(i / 32), ~0);
|
|
gicreg_write32(0, GICD_ICPENDR(i / 32), ~0);
|
|
}
|
|
|
|
if (arm_gic_max_cpu() > 0) {
|
|
/* Set external interrupts to target cpu 0 */
|
|
for (int i = 32; i < max_int; i += 4) {
|
|
gicreg_write32(0, GICD_ITARGETSR(i / 4), gicd_itargetsr[i / 4]);
|
|
}
|
|
}
|
|
|
|
// Initialize all the SPIs to edge triggered
|
|
for (int i = 32; i < max_int; i++) {
|
|
gic_configure_interrupt(i, IRQ_TRIGGER_MODE_EDGE, IRQ_POLARITY_ACTIVE_HIGH);
|
|
}
|
|
|
|
|
|
gicreg_write32(0, GICD_CTLR, 1); // enable GIC0
|
|
#if WITH_LIB_SM
|
|
gicreg_write32(0, GICD_CTLR, 3); // enable GIC0 ns interrupts
|
|
/*
|
|
* Iterate through all IRQs and set them to non-secure
|
|
* mode. This will allow the non-secure side to handle
|
|
* all the interrupts we don't explicitly claim.
|
|
*/
|
|
for (int i = 32; i < max_int; i += 32) {
|
|
u_int reg = i / 32;
|
|
gicreg_write32(0, GICD_IGROUPR(reg), gicd_igroupr[reg]);
|
|
}
|
|
#endif
|
|
arm_gic_init_percpu(0);
|
|
}
|
|
|
|
static status_t arm_gic_set_secure_locked(u_int irq, bool secure) {
|
|
#if WITH_LIB_SM
|
|
int reg = irq / 32;
|
|
uint32_t mask = 1ULL << (irq % 32);
|
|
|
|
if (irq >= MAX_INT)
|
|
return ERR_INVALID_ARGS;
|
|
|
|
if (secure)
|
|
gicreg_write32(0, GICD_IGROUPR(reg), (gicd_igroupr[reg] &= ~mask));
|
|
else
|
|
gicreg_write32(0, GICD_IGROUPR(reg), (gicd_igroupr[reg] |= mask));
|
|
LTRACEF("irq %d, secure %d, GICD_IGROUP%d = %x\n",
|
|
irq, secure, reg, gicreg_read32(0, GICD_IGROUPR(reg)));
|
|
#endif
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t arm_gic_set_target_locked(u_int irq, u_int cpu_mask, u_int enable_mask) {
|
|
u_int reg = irq / 4;
|
|
u_int shift = 8 * (irq % 4);
|
|
u_int old_val;
|
|
u_int new_val;
|
|
|
|
cpu_mask = (cpu_mask & 0xff) << shift;
|
|
enable_mask = (enable_mask << shift) & cpu_mask;
|
|
|
|
old_val = gicreg_read32(0, GICD_ITARGETSR(reg));
|
|
new_val = (gicd_itargetsr[reg] & ~cpu_mask) | enable_mask;
|
|
gicreg_write32(0, GICD_ITARGETSR(reg), (gicd_itargetsr[reg] = new_val));
|
|
LTRACEF("irq %i, GICD_ITARGETSR%d %x => %x (got %x)\n",
|
|
irq, reg, old_val, new_val, gicreg_read32(0, GICD_ITARGETSR(reg)));
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static uint8_t arm_gic_get_priority(u_int irq) {
|
|
u_int reg = irq / 4;
|
|
u_int shift = 8 * (irq % 4);
|
|
return (gicreg_read32(0, GICD_IPRIORITYR(reg)) >> shift) & 0xff;
|
|
}
|
|
|
|
static status_t arm_gic_set_priority_locked(u_int irq, uint8_t priority) {
|
|
u_int reg = irq / 4;
|
|
u_int shift = 8 * (irq % 4);
|
|
u_int mask = 0xff << shift;
|
|
uint32_t regval;
|
|
|
|
regval = gicreg_read32(0, GICD_IPRIORITYR(reg));
|
|
LTRACEF("irq %i, old GICD_IPRIORITYR%d = %x\n", irq, reg, regval);
|
|
regval = (regval & ~mask) | ((uint32_t)priority << shift);
|
|
gicreg_write32(0, GICD_IPRIORITYR(reg), regval);
|
|
LTRACEF("irq %i, new GICD_IPRIORITYR%d = %x, req %x\n",
|
|
irq, reg, gicreg_read32(0, GICD_IPRIORITYR(reg)), regval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
status_t arm_gic_sgi(u_int irq, u_int flags, u_int cpu_mask) {
|
|
u_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 ERR_INVALID_ARGS;
|
|
|
|
LTRACEF("GICD_SGIR: %x\n", val);
|
|
|
|
gicreg_write32(0, GICD_SGIR, val);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t mask_interrupt(unsigned int vector) {
|
|
if (vector >= MAX_INT)
|
|
return ERR_INVALID_ARGS;
|
|
|
|
if (arm_gic_interrupt_change_allowed(vector))
|
|
gic_set_enable(vector, false);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t unmask_interrupt(unsigned int vector) {
|
|
if (vector >= MAX_INT)
|
|
return ERR_INVALID_ARGS;
|
|
|
|
if (arm_gic_interrupt_change_allowed(vector))
|
|
gic_set_enable(vector, true);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static
|
|
enum handler_return __platform_irq(struct iframe *frame) {
|
|
// get the current vector
|
|
uint32_t iar = gicreg_read32(0, GICC_IAR);
|
|
unsigned int vector = iar & 0x3ff;
|
|
|
|
if (vector >= 0x3fe) {
|
|
// spurious
|
|
return INT_NO_RESCHEDULE;
|
|
}
|
|
|
|
THREAD_STATS_INC(interrupts);
|
|
KEVLOG_IRQ_ENTER(vector);
|
|
|
|
uint cpu = arch_curr_cpu_num();
|
|
|
|
LTRACEF_LEVEL(2, "iar 0x%x cpu %u currthread %p vector %d pc 0x%lx\n", iar, cpu,
|
|
get_current_thread(), vector, (uintptr_t)IFRAME_PC(frame));
|
|
|
|
// deliver the interrupt
|
|
enum handler_return ret;
|
|
|
|
ret = INT_NO_RESCHEDULE;
|
|
struct int_handler_struct *handler = get_int_handler(vector, cpu);
|
|
if (handler->handler)
|
|
ret = handler->handler(handler->arg);
|
|
|
|
gicreg_write32(0, GICC_EOIR, iar);
|
|
|
|
LTRACEF_LEVEL(2, "cpu %u exit %d\n", cpu, ret);
|
|
|
|
KEVLOG_IRQ_EXIT(vector);
|
|
|
|
return ret;
|
|
}
|
|
|
|
enum handler_return platform_irq(struct iframe *frame);
|
|
enum handler_return platform_irq(struct iframe *frame) {
|
|
#if WITH_LIB_SM
|
|
uint32_t ahppir = gicreg_read32(0, GICC_AHPPIR);
|
|
uint32_t pending_irq = ahppir & 0x3ff;
|
|
struct int_handler_struct *h;
|
|
uint cpu = arch_curr_cpu_num();
|
|
|
|
LTRACEF("ahppir %d\n", ahppir);
|
|
if (pending_irq < MAX_INT && get_int_handler(pending_irq, cpu)->handler) {
|
|
enum handler_return ret = 0;
|
|
uint32_t irq;
|
|
uint8_t old_priority;
|
|
spin_lock_saved_state_t state;
|
|
|
|
spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
|
|
|
|
/* Temporarily raise the priority of the interrupt we want to
|
|
* handle so another interrupt does not take its place before
|
|
* we can acknowledge it.
|
|
*/
|
|
old_priority = arm_gic_get_priority(pending_irq);
|
|
arm_gic_set_priority_locked(pending_irq, 0);
|
|
DSB;
|
|
irq = gicreg_read32(0, GICC_AIAR) & 0x3ff;
|
|
arm_gic_set_priority_locked(pending_irq, old_priority);
|
|
|
|
spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
|
|
|
|
LTRACEF("irq %d\n", irq);
|
|
if (irq < MAX_INT && (h = get_int_handler(pending_irq, cpu))->handler)
|
|
ret = h->handler(h->arg);
|
|
else
|
|
TRACEF("unexpected irq %d != %d may get lost\n", irq, pending_irq);
|
|
gicreg_write32(0, GICC_AEOIR, irq);
|
|
return ret;
|
|
}
|
|
return sm_handle_irq();
|
|
#else
|
|
return __platform_irq(frame);
|
|
#endif
|
|
}
|
|
|
|
void platform_fiq(struct iframe *frame);
|
|
void platform_fiq(struct iframe *frame) {
|
|
#if WITH_LIB_SM
|
|
sm_handle_fiq();
|
|
#else
|
|
PANIC_UNIMPLEMENTED;
|
|
#endif
|
|
}
|
|
|
|
#if WITH_LIB_SM
|
|
static status_t arm_gic_get_next_irq_locked(u_int min_irq, bool per_cpu) {
|
|
u_int irq;
|
|
u_int max_irq = per_cpu ? GIC_MAX_PER_CPU_INT : MAX_INT;
|
|
uint cpu = arch_curr_cpu_num();
|
|
|
|
if (!per_cpu && min_irq < GIC_MAX_PER_CPU_INT)
|
|
min_irq = GIC_MAX_PER_CPU_INT;
|
|
|
|
for (irq = min_irq; irq < max_irq; irq++)
|
|
if (get_int_handler(irq, cpu)->handler)
|
|
return irq;
|
|
|
|
return SM_ERR_END_OF_INPUT;
|
|
}
|
|
|
|
long smc_intc_get_next_irq(smc32_args_t *args) {
|
|
status_t ret;
|
|
spin_lock_saved_state_t state;
|
|
|
|
spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
|
|
|
|
arm_gic_non_secure_interrupts_frozen = true;
|
|
ret = arm_gic_get_next_irq_locked(args->params[0], args->params[1]);
|
|
LTRACEF("min_irq %d, per_cpu %d, ret %d\n",
|
|
args->params[0], args->params[1], ret);
|
|
|
|
spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u_long enabled_fiq_mask[BITMAP_NUM_WORDS(MAX_INT)];
|
|
|
|
static void bitmap_update_locked(u_long *bitmap, u_int bit, bool set) {
|
|
u_long mask = 1UL << BITMAP_BIT_IN_WORD(bit);
|
|
|
|
bitmap += BITMAP_WORD(bit);
|
|
if (set)
|
|
*bitmap |= mask;
|
|
else
|
|
*bitmap &= ~mask;
|
|
}
|
|
|
|
long smc_intc_request_fiq(smc32_args_t *args) {
|
|
u_int fiq = args->params[0];
|
|
bool enable = args->params[1];
|
|
spin_lock_saved_state_t state;
|
|
|
|
dprintf(SPEW, "%s: fiq %d, enable %d\n", __func__, fiq, enable);
|
|
spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
|
|
|
|
arm_gic_set_secure_locked(fiq, true);
|
|
arm_gic_set_target_locked(fiq, ~0, ~0);
|
|
arm_gic_set_priority_locked(fiq, 0);
|
|
|
|
gic_set_enable(fiq, enable);
|
|
bitmap_update_locked(enabled_fiq_mask, fiq, enable);
|
|
|
|
dprintf(SPEW, "%s: fiq %d, enable %d done\n", __func__, fiq, enable);
|
|
|
|
spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static u_int current_fiq[8] = { 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff };
|
|
|
|
static bool update_fiq_targets(u_int cpu, bool enable, u_int triggered_fiq, bool resume_gicd) {
|
|
u_int i, j;
|
|
u_long mask;
|
|
u_int fiq;
|
|
bool smp = arm_gic_max_cpu() > 0;
|
|
bool ret = false;
|
|
|
|
spin_lock(&gicd_lock); /* IRQs and FIQs are already masked */
|
|
for (i = 0; i < BITMAP_NUM_WORDS(MAX_INT); i++) {
|
|
mask = enabled_fiq_mask[i];
|
|
while (mask) {
|
|
j = _ffz(~mask);
|
|
mask &= ~(1UL << j);
|
|
fiq = i * BITMAP_BITS_PER_WORD + j;
|
|
if (fiq == triggered_fiq)
|
|
ret = true;
|
|
LTRACEF("cpu %d, irq %i, enable %d\n", cpu, fiq, enable);
|
|
if (smp)
|
|
arm_gic_set_target_locked(fiq, 1U << cpu, enable ? ~0 : 0);
|
|
if (!smp || resume_gicd)
|
|
gic_set_enable(fiq, enable);
|
|
}
|
|
}
|
|
spin_unlock(&gicd_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd) {
|
|
u_int cpu = arch_curr_cpu_num();
|
|
|
|
ASSERT(cpu < 8);
|
|
|
|
update_fiq_targets(cpu, resume_gicc, ~0, resume_gicd);
|
|
}
|
|
|
|
status_t sm_intc_fiq_enter(void) {
|
|
u_int cpu = arch_curr_cpu_num();
|
|
u_int irq = gicreg_read32(0, GICC_IAR) & 0x3ff;
|
|
bool fiq_enabled;
|
|
|
|
ASSERT(cpu < 8);
|
|
|
|
LTRACEF("cpu %d, irq %i\n", cpu, irq);
|
|
|
|
if (irq >= 1020) {
|
|
LTRACEF("spurious fiq: cpu %d, old %d, new %d\n", cpu, current_fiq[cpu], irq);
|
|
return ERR_NO_MSG;
|
|
}
|
|
|
|
fiq_enabled = update_fiq_targets(cpu, false, irq, false);
|
|
gicreg_write32(0, GICC_EOIR, irq);
|
|
|
|
if (current_fiq[cpu] != 0x3ff) {
|
|
dprintf(INFO, "more than one fiq active: cpu %d, old %d, new %d\n", cpu, current_fiq[cpu], irq);
|
|
return ERR_ALREADY_STARTED;
|
|
}
|
|
|
|
if (!fiq_enabled) {
|
|
dprintf(INFO, "got disabled fiq: cpu %d, new %d\n", cpu, irq);
|
|
return ERR_NOT_READY;
|
|
}
|
|
|
|
current_fiq[cpu] = irq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sm_intc_fiq_exit(void) {
|
|
u_int cpu = arch_curr_cpu_num();
|
|
|
|
ASSERT(cpu < 8);
|
|
|
|
LTRACEF("cpu %d, irq %i\n", cpu, current_fiq[cpu]);
|
|
if (current_fiq[cpu] == 0x3ff) {
|
|
dprintf(INFO, "%s: no fiq active, cpu %d\n", __func__, cpu);
|
|
return;
|
|
}
|
|
update_fiq_targets(cpu, true, current_fiq[cpu], false);
|
|
current_fiq[cpu] = 0x3ff;
|
|
}
|
|
#endif
|