diff --git a/thermosphere/Makefile b/thermosphere/Makefile index 6b5836500..f1c15903d 100644 --- a/thermosphere/Makefile +++ b/thermosphere/Makefile @@ -145,7 +145,7 @@ export QEMU := qemu-system-aarch64 #export QEMU := ~/qemu/aarch64-softmmu/qemu-system-aarch64 QEMUFLAGS := -nographic -machine virt,secure=on,virtualization=on,gic-version=2 -cpu cortex-a57 -smp 4 -m 1024\ - -bios bl1.bin -d unimp,int -semihosting-config enable,target=native -serial mon:stdio + -bios bl1.bin -d unimp -semihosting-config enable,target=native -serial mon:stdio # NOTE: copy bl1.bin, bl2.bin, bl31.bin from your own build of Arm Trusted Firmware! diff --git a/thermosphere/src/core_ctx.h b/thermosphere/src/core_ctx.h index 2f73bc939..d37fd83a1 100644 --- a/thermosphere/src/core_ctx.h +++ b/thermosphere/src/core_ctx.h @@ -23,8 +23,9 @@ typedef struct CoreCtx { u8 *crashStack; // @0x10 u64 scratch; // @0x18 u32 coreId; // @0x20 - bool isBootCore; // @0x24 - bool warmboot; // @0x25 + u8 gicInterfaceId; // @0x24 + bool isBootCore; // @0x25 + bool warmboot; // @0x26 } CoreCtx; extern CoreCtx g_coreCtxs[4]; diff --git a/thermosphere/src/exception_vectors.s b/thermosphere/src/exception_vectors.s index c09ac4b96..4480606f2 100644 --- a/thermosphere/src/exception_vectors.s +++ b/thermosphere/src/exception_vectors.s @@ -216,13 +216,20 @@ vector_entry synch_spx mov x0, sp mrs x1, esr_el2 - bl handleSameElSyncException b . check_vector_size synch_spx vector_entry irq_spx - bl unknown_exception + save_all_regs + + mov x0, sp + mov w1, wzr + mov w2, wzr + bl handleIrqException + + b _restore_all_regs + check_vector_size irq_spx vector_entry fiq_spx @@ -239,14 +246,20 @@ vector_entry synch_a64 mov x0, sp mrs x1, esr_el2 - bl handleLowerElSyncException b _restore_all_regs check_vector_size synch_a64 vector_entry irq_a64 - bl unknown_exception + save_all_regs_reload_x18 + + mov x0, sp + mov w1, #1 + mov w2, wzr + bl handleIrqException + + b _restore_all_regs check_vector_size irq_a64 vector_entry fiq_a64 @@ -264,7 +277,14 @@ vector_entry synch_a32 check_vector_size synch_a32 vector_entry irq_a32 - bl unknown_exception + save_all_regs_reload_x18 + + mov x0, sp + mov w1, #1 + mov w2, #1 + bl handleIrqException + + b _restore_all_regs check_vector_size irq_a32 vector_entry fiq_a32 diff --git a/thermosphere/src/gicv2.h b/thermosphere/src/gicv2.h index ac6358a86..3bb45471a 100644 --- a/thermosphere/src/gicv2.h +++ b/thermosphere/src/gicv2.h @@ -18,6 +18,10 @@ #include "types.h" +#define GIC_IRQID_MAX 1020 +#define GIC_IRQID_SPURIOUS_GRPNEEDACK (GIC_IRQID_MAX + 2) +#define GIC_IRQID_SPURIOUS (GIC_IRQID_MAX + 3) + typedef struct ArmGicV2Distributor { u32 ctlr; u32 typer; diff --git a/thermosphere/src/irq.c b/thermosphere/src/irq.c new file mode 100644 index 000000000..8e936f1b1 --- /dev/null +++ b/thermosphere/src/irq.c @@ -0,0 +1,163 @@ +/* + * 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 . + */ + +#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 + } +} diff --git a/thermosphere/src/irq.h b/thermosphere/src/irq.h new file mode 100644 index 000000000..0f21ebb7b --- /dev/null +++ b/thermosphere/src/irq.h @@ -0,0 +1,56 @@ +/* + * 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 . + */ + +#pragma once + +#include "gicv2.h" +#include "spinlock.h" +#include "exceptions.h" +#include "utils.h" + +typedef struct IrqManager { + RecursiveSpinlock lock; + ArmGicV2 gic; + u8 numPriorityLevels; + u8 numCpuInterfaces; + u8 numSharedInterrupts; + // Note: we don't store interrupt handlers since we will handle some SGI + uart interrupt(s)... +} IrqManager; + +extern IrqManager g_irqManager; + +void initIrq(void); +void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32); + +static inline void generateSgiForAllOthers(u32 id) +{ + g_irqManager.gic.gicd->sgir = (1 << 24) | (id & 0xF); +} + +static inline void generateSgiForSelf(u32 id) +{ + g_irqManager.gic.gicd->sgir = (2 << 24) | (id & 0xF); +} + +static inline void generateSgiForList(u32 id, u32 list) +{ + g_irqManager.gic.gicd->sgir = (0 << 24) | (list << 16) | (id & 0xF); +} + +static inline void generateSgiForAll(u32 id) +{ + generateSgiForList(id, MASK(g_irqManager.numCpuInterfaces)); +} diff --git a/thermosphere/src/main.c b/thermosphere/src/main.c index 342f78a90..c1b9b3895 100644 --- a/thermosphere/src/main.c +++ b/thermosphere/src/main.c @@ -12,6 +12,8 @@ #include "breakpoints.h" #include "watchpoints.h" +#include "irq.h" + extern const u8 __start__[]; static void loadKernelViaSemihosting(void) @@ -41,6 +43,7 @@ void main(ExceptionStackFrame *frame) { enableTraps(); enableBreakpointsAndWatchpoints(); + initIrq(); if (currentCoreCtx->isBootCore) { uartInit(115200); @@ -74,4 +77,8 @@ void main(ExceptionStackFrame *frame) // Test singleStepSetNextState(frame, SingleStepState_ActivePending); + + // Test + unmaskIrq(); + generateSgiForAll(0); } diff --git a/thermosphere/src/platform/qemu/interrupt_config.h b/thermosphere/src/platform/qemu/interrupt_config.h index 84b626c8d..f1b5de3aa 100644 --- a/thermosphere/src/platform/qemu/interrupt_config.h +++ b/thermosphere/src/platform/qemu/interrupt_config.h @@ -21,7 +21,9 @@ // For both guest and host #define MAX_NUM_REGISTERED_INTERRUPTS 512 -static inline void initGicv2Pointers(ArmGicV2 *gic) +#define GIC_IRQID_MAINTENANCE 25 + +static inline void initGicV2Pointers(ArmGicV2 *gic) { gic->gicd = (volatile ArmGicV2Distributor *)0x08000000ull; gic->gicc = (volatile ArmGicV2Controller *)0x08010000ull; diff --git a/thermosphere/src/platform/tegra/interrupt_config.h b/thermosphere/src/platform/tegra/interrupt_config.h index 0470f1a27..8fcc7c310 100644 --- a/thermosphere/src/platform/tegra/interrupt_config.h +++ b/thermosphere/src/platform/tegra/interrupt_config.h @@ -21,7 +21,9 @@ // For both guest and host #define MAX_NUM_REGISTERED_INTERRUPTS 512 -static inline void initGicv2Pointers(ArmGicV2 *gic) +#define GIC_IRQID_MAINTENANCE 25 + +static inline void initGicV2Pointers(ArmGicV2 *gic) { gic->gicd = (volatile ArmGicV2Distributor *)0x50041000ull; gic->gicc = (volatile ArmGicV2Controller *)0x50042000ull; diff --git a/thermosphere/src/spinlock.h b/thermosphere/src/spinlock.h index 4fff76cc4..8fea94718 100644 --- a/thermosphere/src/spinlock.h +++ b/thermosphere/src/spinlock.h @@ -59,7 +59,7 @@ static inline u64 spinlockLockMaskIrq(Spinlock *lock) return ret; } -static inline void spinlockLockRestoreIrq(Spinlock *lock, u64 flags) +static inline void spinlockUnlockRestoreIrq(Spinlock *lock, u64 flags) { spinlockUnlock(lock); restoreInterruptFlags(flags); @@ -91,7 +91,7 @@ static inline u64 recursiveSpinlockLockMaskIrq(RecursiveSpinlock *lock) return ret; } -static inline void recursiveSpinlockLockRestoreIrq(RecursiveSpinlock *lock, u64 flags) +static inline void recursiveSpinlockUnlockRestoreIrq(RecursiveSpinlock *lock, u64 flags) { recursiveSpinlockUnlock(lock); restoreInterruptFlags(flags);