mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-15 08:26:56 +00:00
164 lines
4.8 KiB
C
164 lines
4.8 KiB
C
|
/*
|
||
|
* Copyright (c) 2019 Atmosphère-NX
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it
|
||
|
* under the terms and conditions of the GNU General Public License,
|
||
|
* version 2, as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope 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/>.
|
||
|
*/
|
||
|
|
||
|
#include "irq.h"
|
||
|
#include "platform/interrupt_config.h"
|
||
|
#include "core_ctx.h"
|
||
|
#include "debug_log.h"
|
||
|
|
||
|
IrqManager g_irqManager = {0};
|
||
|
|
||
|
static void initGic(void)
|
||
|
{
|
||
|
// Reinits the GICD and GICC (for non-secure mode, obviously)
|
||
|
if (currentCoreCtx->isBootCore && !currentCoreCtx->warmboot) {
|
||
|
initGicV2Pointers(&g_irqManager.gic);
|
||
|
|
||
|
// Disable interrupt handling & global interrupt distribution
|
||
|
g_irqManager.gic.gicd->ctlr = 0;
|
||
|
|
||
|
// Get some info
|
||
|
g_irqManager.numSharedInterrupts = 32 * (g_irqManager.gic.gicd->typer & 0x1F); // number of interrupt lines / 32
|
||
|
|
||
|
// unimplemented priority bits (lowest significant) are RAZ/WI
|
||
|
g_irqManager.gic.gicd->ipriorityr[0] = 0xFF;
|
||
|
g_irqManager.numPriorityLevels = (u8)BIT(__builtin_popcount(g_irqManager.gic.gicd->ipriorityr[0]));
|
||
|
|
||
|
// same thing for CPU interfaces targets (save for ITARGETSR registers corresponding to SGIs)
|
||
|
g_irqManager.gic.gicd->itargetsr[32] = 0xFF;
|
||
|
g_irqManager.numCpuInterfaces = (u8)__builtin_popcount(g_irqManager.gic.gicd->itargetsr[32]);
|
||
|
}
|
||
|
|
||
|
volatile ArmGicV2Controller *gicc = g_irqManager.gic.gicc;
|
||
|
volatile ArmGicV2Distributor *gicd = g_irqManager.gic.gicd;
|
||
|
|
||
|
// Only one core will reset the GIC state for the shared peripheral interrupts
|
||
|
|
||
|
u32 numInterrupts = 32;
|
||
|
if (currentCoreCtx->isBootCore) {
|
||
|
numInterrupts += g_irqManager.numSharedInterrupts;
|
||
|
}
|
||
|
|
||
|
// Filter all interrupts
|
||
|
gicc->pmr = 0;
|
||
|
|
||
|
// Disable interrupt preemption
|
||
|
gicc->bpr = 7;
|
||
|
|
||
|
// Note: the GICD I...n regs are banked for private interrupts
|
||
|
|
||
|
// Disable all interrupts, clear active status, clear pending status, also clear secure regs igroupr to be sure
|
||
|
for (u32 i = 0; i < numInterrupts / 32; i++) {
|
||
|
gicd->icenabler[i] = 0xFFFFFFFF;
|
||
|
gicd->icactiver[i] = 0xFFFFFFFF;
|
||
|
gicd->icpendr[i] = 0xFFFFFFFF;
|
||
|
gicd->igroupr[i] = 0x00000000;
|
||
|
}
|
||
|
|
||
|
// Set priorities to lowest
|
||
|
for (u32 i = 0; i < numInterrupts; i++) {
|
||
|
gicd->ipriorityr[i] = 0xFF;
|
||
|
}
|
||
|
|
||
|
// Reset icfgr, itargetsr for shared peripheral interrupts
|
||
|
for (u32 i = 32 / 16; i < numInterrupts / 16; i++) {
|
||
|
gicd->icfgr[i] = 0;
|
||
|
}
|
||
|
|
||
|
for (u32 i = 32; i < numInterrupts; i++) {
|
||
|
gicd->itargetsr[i] = 0;
|
||
|
}
|
||
|
|
||
|
// Now, reenable interrupts
|
||
|
|
||
|
// Enable the distributor
|
||
|
if (currentCoreCtx->isBootCore) {
|
||
|
gicd->ctlr = 1;
|
||
|
}
|
||
|
|
||
|
// Enable the CPU interface. Set EOIModeNS=1 (split prio drop & deactivate priority)
|
||
|
gicc->ctlr = BIT(9) | 1;
|
||
|
|
||
|
// Disable interrupt filtering
|
||
|
gicc->pmr = 0xFF;
|
||
|
|
||
|
currentCoreCtx->gicInterfaceId = gicd->itargetsr[0];
|
||
|
}
|
||
|
|
||
|
static void configureInterrupt(u16 id, u8 prio, bool isLevelSensitive)
|
||
|
{
|
||
|
volatile ArmGicV2Distributor *gicd = g_irqManager.gic.gicd;
|
||
|
gicd->icenabler[id / 32] |= BIT(id % 32);
|
||
|
|
||
|
if (id >= 32) {
|
||
|
gicd->icfgr[id / 16] &= ~3 << (2 * (id % 16));
|
||
|
gicd->icfgr[id / 16] |= (!isLevelSensitive ? 2 : 0) << (2 * (id % 16));
|
||
|
}
|
||
|
|
||
|
if (id >= 16) {
|
||
|
gicd->itargetsr[id] |= currentCoreCtx->gicInterfaceId;
|
||
|
}
|
||
|
|
||
|
gicd->icpendr[id / 32] |= BIT(id % 32);
|
||
|
gicd->ipriorityr[id] = prio;
|
||
|
gicd->isenabler[id / 32] |= BIT(id % 32);
|
||
|
}
|
||
|
|
||
|
void initIrq(void)
|
||
|
{
|
||
|
u64 flags = recursiveSpinlockLockMaskIrq(&g_irqManager.lock);
|
||
|
|
||
|
initGic();
|
||
|
|
||
|
// Configure the interrupts we use here
|
||
|
configureInterrupt(0, 0, false);
|
||
|
configureInterrupt(GIC_IRQID_MAINTENANCE, 0, true);
|
||
|
|
||
|
recursiveSpinlockUnlockRestoreIrq(&g_irqManager.lock, flags);
|
||
|
}
|
||
|
|
||
|
void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32)
|
||
|
{
|
||
|
(void)isLowerEl;
|
||
|
(void)isA32;
|
||
|
volatile ArmGicV2Controller *gicc = g_irqManager.gic.gicc;
|
||
|
|
||
|
// Acknowledge the interrupt. Interrupt goes from pending to active.
|
||
|
u32 iar = gicc->iar;
|
||
|
u32 irqId = iar & 0x3FF;
|
||
|
|
||
|
DEBUG("Received irq %x\n", irqId);
|
||
|
|
||
|
if (irqId == GIC_IRQID_SPURIOUS) {
|
||
|
// Spurious interrupt received
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool isGuestInterrupt = false;
|
||
|
|
||
|
// TODO: handle the interrupt if it's a host interrupt
|
||
|
|
||
|
// Priority drop
|
||
|
gicc->eoir = iar;
|
||
|
|
||
|
if (!isGuestInterrupt) {
|
||
|
// Deactivate the interrupt
|
||
|
gicc->dir = iar;
|
||
|
} else {
|
||
|
// TODO
|
||
|
}
|
||
|
}
|