266 lines
8.4 KiB
C
Raw Normal View History

2019-04-23 17:38:22 -03:00
/*
* This file is part of GodMode9
* Copyright (C) 2017-2019 Wolfvak
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2020-07-19 11:59:52 -03:00
#include <common.h>
#include <arm.h>
#include "arm/gic.h"
/* Generic Interrupt Controller Registers */
#define REG_GIC(cpu, o, t) REG_ARM_PMR(0x200 + ((cpu) * 0x100) + (o), t)
#define REG_GIC_CONTROL(c) (*REG_GIC(c, 0x00, u32))
#define REG_GIC_PRIOMASK(c) (*REG_GIC(c, 0x04, u32))
2020-07-19 11:59:52 -03:00
#define REG_GIC_POI(c) (*REG_GIC(c, 0x08, u32))
#define REG_GIC_IRQACK(c) (*REG_GIC(c, 0x0C, u32))
#define REG_GIC_IRQEND(c) (*REG_GIC(c, 0x10, u32))
#define REG_GIC_LASTPRIO(c) (*REG_GIC(c, 0x14, u32))
#define REG_GIC_PENDING(c) (*REG_GIC(c, 0x18, u32))
#define GIC_THIS_CPU_ALIAS (-1)
#define GIC_IRQ_SPURIOUS (1023)
/* Interrupt Distributor Registers */
#define REG_DIC(off, type) REG_ARM_PMR(0x1000 + (off), type)
2020-07-19 11:59:52 -03:00
#define REG_DIC_CONTROL (*REG_DIC(0x00, u32))
#define REG_DIC_TYPE (*REG_DIC(0x04, u32))
#define REG_DIC_SETENABLE REG_DIC(0x100, u32) // 32 intcfg per reg
#define REG_DIC_CLRENABLE REG_DIC(0x180, u32)
#define REG_DIC_SETPENDING REG_DIC(0x200, u32)
#define REG_DIC_CLRPENDING REG_DIC(0x280, u32)
2020-07-19 11:59:52 -03:00
#define REG_DIC_PRIORITY REG_DIC(0x400, u8) // 1 intcfg per reg (in upper 4 bits)
#define REG_DIC_TARGETCPU REG_DIC(0x800, u8) // 1 intcfg per reg
#define REG_DIC_CFGREG REG_DIC(0xC00, u32) // 16 intcfg per reg
#define REG_DIC_SOFTINT (*REG_DIC(0xF00, u32))
// used only on reset routines
#define REG_DIC_PRIORITY32 REG_DIC(0x400, u32) // 4 intcfg per reg (in upper 4 bits)
#define REG_DIC_TARGETCPU32 REG_DIC(0x800, u32) // 4 intcfg per reg
#define GIC_PRIO_NEVER32 \
(GIC_PRIO_NEVER | (GIC_PRIO_NEVER << 8) | \
(GIC_PRIO_NEVER << 16) | (GIC_PRIO_NEVER << 24))
#define GIC_PRIO_HIGH32 \
(GIC_PRIO_HIGHEST | (GIC_PRIO_HIGHEST << 8) | \
(GIC_PRIO_HIGHEST << 16) | (GIC_PRIO_HIGHEST << 24))
/* CPU source ID is present in Interrupt Acknowledge register? */
#define IRQN_SRC_MASK (0x7 << 10)
/* Interrupt Handling */
#define LOCAL_IRQS (32)
#define DIC_MAX_IRQ (LOCAL_IRQS + MAX_IRQ)
2020-07-19 11:59:52 -03:00
#define COREMASK_VALID(x) (((x) > 0) && ((x) < BIT(MAX_CPU)))
#define IRQN_IS_VALID(n) ((n) < DIC_MAX_IRQ)
2020-07-19 11:59:52 -03:00
static gicIrqHandler gicIrqHandlers[DIC_MAX_IRQ];
2020-07-19 11:59:52 -03:00
static struct {
u8 tgt;
u8 prio;
} gicIrqConfig[DIC_MAX_IRQ];
2020-07-19 11:59:52 -03:00
// gets used whenever a NULL pointer is passed to gicEnableInterrupt
static void gicDummyHandler(u32 irqn) { (void)irqn; return; }
static const struct {
u8 low, high, mode;
} gicDefaultIrqCfg[] = {
{ .low = 0x00, .high = 0x1F, .mode = GIC_RISINGEDGE_NN },
{ .low = 0x20, .high = 0x23, .mode = GIC_LEVELHIGH_1N },
{ .low = 0x24, .high = 0x24, .mode = GIC_RISINGEDGE_1N },
{ .low = 0x25, .high = 0x27, .mode = GIC_LEVELHIGH_1N },
{ .low = 0x28, .high = 0x2D, .mode = GIC_RISINGEDGE_1N },
{ .low = 0x30, .high = 0x3B, .mode = GIC_LEVELHIGH_1N },
{ .low = 0x40, .high = 0x4E, .mode = GIC_RISINGEDGE_1N },
{ .low = 0x4F, .high = 0x4F, .mode = GIC_LEVELHIGH_1N },
{ .low = 0x50, .high = 0x57, .mode = GIC_RISINGEDGE_1N },
{ .low = 0x58, .high = 0x58, .mode = GIC_LEVELHIGH_1N },
{ .low = 0x59, .high = 0x75, .mode = GIC_RISINGEDGE_1N },
{ .low = 0x76, .high = 0x77, .mode = GIC_LEVELHIGH_1N },
{ .low = 0x78, .high = 0x78, .mode = GIC_RISINGEDGE_1N },
{ .low = 0x79, .high = 0x7d, .mode = GIC_LEVELHIGH_1N },
};
static u8 gicGetDefaultIrqCfg(u32 irqn) {
for (unsigned i = 0; i < countof(gicDefaultIrqCfg); i++) {
if ((irqn >= gicDefaultIrqCfg[i].low) && (irqn <= gicDefaultIrqCfg[i].high))
return gicDefaultIrqCfg[i].mode;
}
// TODO: would it be considerably faster to use bsearch?
return GIC_RISINGEDGE_1N;
}
2020-07-19 11:59:52 -03:00
void gicTopHandler(void)
{
2020-07-19 11:59:52 -03:00
while(1) {
u32 irqn;
2020-07-19 11:59:52 -03:00
/**
If more than one of these CPUs reads the Interrupt Acknowledge Register at the
same time, they can all acknowledge the same interrupt. The interrupt service
routine must ensure that only one of them tries to process the interrupt, with the
others returning after writing the ID to the End of Interrupt Register.
*/
irqn = REG_GIC_IRQACK(GIC_THIS_CPU_ALIAS);
2020-07-19 11:59:52 -03:00
if (irqn == GIC_IRQ_SPURIOUS) // no further processing is needed
break;
2020-07-19 11:59:52 -03:00
(gicIrqHandlers[irqn & ~IRQN_SRC_MASK])(irqn);
// if the id is < 16, the source CPU can be obtained from irqn
// if the handler isn't set, it'll try to branch to 0 and trigger a prefetch abort
REG_GIC_IRQEND(GIC_THIS_CPU_ALIAS) = irqn;
}
}
2020-07-19 11:59:52 -03:00
void gicGlobalReset(void)
{
2020-07-19 11:59:52 -03:00
u32 dic_type;
unsigned gicn, intn;
2020-07-19 11:59:52 -03:00
dic_type = REG_DIC_TYPE;
2020-07-19 11:59:52 -03:00
// number of local controllers
gicn = ((dic_type >> 5) & 3) + 1;
2020-07-19 11:59:52 -03:00
// number of interrupt lines (up to 224 external + 32 fixed internal per CPU)
intn = ((dic_type & 7) + 1) * 32;
// clamp it down to the amount of CPUs designed to handle
if (gicn > MAX_CPU)
gicn = MAX_CPU;
2020-07-19 11:59:52 -03:00
// clear the interrupt handler and config table
memset(gicIrqHandlers, 0, sizeof(gicIrqHandlers));
memset(gicIrqConfig, 0, sizeof(gicIrqConfig));
2020-07-19 11:59:52 -03:00
// disable all MP11 GICs
for (unsigned i = 0; i < gicn; i++)
REG_GIC_CONTROL(i) = 0;
2020-07-19 11:59:52 -03:00
// disable the main DIC
REG_DIC_CONTROL = 0;
2020-07-19 11:59:52 -03:00
// clear all external interrupts
for (unsigned i = 1; i < (intn / 32); i++) {
REG_DIC_CLRENABLE[i] = ~0;
REG_DIC_CLRPENDING[i] = ~0;
}
2020-07-19 11:59:52 -03:00
// reset all external priorities to highest by default
// clear target processor regs
for (unsigned i = 4; i < (intn / 4); i++) {
REG_DIC_PRIORITY32[i] = GIC_PRIO_HIGH32;
REG_DIC_TARGETCPU32[i] = 0;
}
2020-07-19 11:59:52 -03:00
// set all interrupts to active level triggered in N-N model
for (unsigned i = 16; i < (intn / 16); i++)
REG_DIC_CFGREG[i] = 0;
2020-07-19 11:59:52 -03:00
// re enable the main DIC
REG_DIC_CONTROL = 1;
2020-07-19 11:59:52 -03:00
for (unsigned i = 0; i < gicn; i++) {
// compare all priority bits
REG_GIC_POI(i) = 3;
2020-07-19 11:59:52 -03:00
// don't mask any interrupt with low priority
REG_GIC_PRIOMASK(i) = 0xF0;
2020-07-19 11:59:52 -03:00
// enable all the MP11 GICs
REG_GIC_CONTROL(i) = 1;
}
}
2020-07-19 11:59:52 -03:00
void gicLocalReset(void)
{
u32 irq_s;
2020-07-19 11:59:52 -03:00
// disable all local interrupts
REG_DIC_CLRENABLE[0] = ~0;
REG_DIC_CLRPENDING[0] = ~0;
2020-07-19 11:59:52 -03:00
for (unsigned i = 0; i < 4; i++) {
REG_DIC_PRIORITY32[i] = GIC_PRIO_HIGH32;
// local IRQs are always unmasked by default
2020-07-19 11:59:52 -03:00
// REG_DIC_TARGETCPU[i] = 0;
// not needed, always read as corresponding MP11 core
}
2020-07-19 11:59:52 -03:00
// ack until it gets a spurious IRQ
do {
irq_s = REG_GIC_PENDING(GIC_THIS_CPU_ALIAS);
REG_GIC_IRQEND(GIC_THIS_CPU_ALIAS) = irq_s;
} while(irq_s != GIC_IRQ_SPURIOUS);
}
static void gicSetIrqCfg(u32 irqn) {
2020-07-19 11:59:52 -03:00
u32 smt, cfg;
smt = irqn & 15;
cfg = REG_DIC_CFGREG[irqn / 16];
cfg &= ~(3 << smt);
cfg |= gicGetDefaultIrqCfg(irqn) << smt;
2020-07-19 11:59:52 -03:00
REG_DIC_CFGREG[irqn / 16] = cfg;
}
void gicSetInterruptConfig(u32 irqn, u32 coremask, u32 prio, gicIrqHandler handler)
{
2020-07-19 11:59:52 -03:00
if (handler == NULL) // maybe add runtime ptr checks here too?
handler = gicDummyHandler;
2020-07-19 11:59:52 -03:00
gicIrqConfig[irqn].tgt = coremask;
gicIrqConfig[irqn].prio = prio;
gicIrqHandlers[irqn] = handler;
}
2020-07-19 11:59:52 -03:00
void gicClearInterruptConfig(u32 irqn)
{
2020-07-19 11:59:52 -03:00
memset(&gicIrqConfig[irqn], 0, sizeof(gicIrqConfig[irqn]));
gicIrqHandlers[irqn] = NULL;
}
2020-07-19 11:59:52 -03:00
void gicEnableInterrupt(u32 irqn)
{
REG_DIC_PRIORITY[irqn] = gicIrqConfig[irqn].prio;
REG_DIC_TARGETCPU[irqn] = gicIrqConfig[irqn].tgt;
gicSetIrqCfg(irqn);
2020-07-19 11:59:52 -03:00
REG_DIC_CLRPENDING[irqn / 32] |= BIT(irqn & 0x1F);
REG_DIC_SETENABLE[irqn / 32] |= BIT(irqn & 0x1F);
}
void gicDisableInterrupt(u32 irqn)
{
REG_DIC_CLRENABLE[irqn / 32] |= BIT(irqn & 0x1F);
REG_DIC_CLRPENDING[irqn / 32] |= BIT(irqn & 0x1F);
}
2020-07-19 11:59:52 -03:00
void gicTriggerSoftInterrupt(u32 softirq)
{
2020-07-19 11:59:52 -03:00
REG_DIC_SOFTINT = softirq;
}