From 6c44367b5c7ace262e8e6ca9a0fd36f6b110e746 Mon Sep 17 00:00:00 2001 From: Yaya48 Date: Fri, 16 Oct 2026 02:49:39 +0200 Subject: [PATCH] Starlet: re-sample the interrupt lines on HW_ARMFIQMASK writes too Review of d486779f50 (StarOS decision 44): - A write to HW_ARMFIQMASK re-samples the attached core's IRQ and FIQ lines, as writes to HW_ARMIRQFLAG and HW_ARMIRQMASK do. - Tests: FIQ masking and unmasking of a latched cause (wide and byte-assembled writes, then an acknowledgement); two pending IRQ sources, where acknowledging one keeps the line asserted and acknowledging the last lowers it. The attached core cannot be used after it is destroyed: Starlet::Shutdown resets m_core then m_memory with nothing in between, ~Starlet destroys m_core (declared after m_memory) first, Init attaches only after the last path that can fail, StarletMemory::Reset keeps the core, which is still alive, and the probe's core is a local declared after its memory. Co-Authored-By: Claude Opus 5.5 --- .../Core/Core/IOS/Starlet/StarletMemory.cpp | 6 +- Source/Core/Core/IOS/Starlet/StarletMemory.h | 10 +-- .../Core/IOS/Starlet/ARMCoreTest.cpp | 65 +++++++++++++++++++ 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/IOS/Starlet/StarletMemory.cpp b/Source/Core/Core/IOS/Starlet/StarletMemory.cpp index bc18a84f0f..5ac67fe09c 100644 --- a/Source/Core/Core/IOS/Starlet/StarletMemory.cpp +++ b/Source/Core/Core/IOS/Starlet/StarletMemory.cpp @@ -335,6 +335,7 @@ constexpr u64 TIMER_CLOCK_DIVISOR = 128; constexpr u32 HW_ALARM = HW_BASE + 0x14; constexpr u32 HW_ARMIRQFLAG = HW_BASE + 0x38; constexpr u32 HW_ARMIRQMASK = HW_BASE + 0x3c; +constexpr u32 HW_ARMFIQMASK = HW_BASE + 0x40; constexpr u32 HW_GPIO_ENABLE = HW_BASE + 0xdc; constexpr u32 HW_GPIO_OUT = HW_BASE + 0xe0; constexpr u32 HW_GPIO_DIR = HW_BASE + 0xe4; @@ -1113,8 +1114,11 @@ void StarletMemory::AttachInterruptLines(ARMCore* core) void StarletMemory::UpdateInterruptLines(u32 address) { - if (m_interrupt_core == nullptr || (address != HW_ARMIRQFLAG && address != HW_ARMIRQMASK)) + if (m_interrupt_core == nullptr || + (address != HW_ARMIRQFLAG && address != HW_ARMIRQMASK && address != HW_ARMFIQMASK)) + { return; + } const WiiIPC& ipc = m_system.GetWiiIPC(); m_interrupt_core->SetIRQLine(ipc.IsStarletIRQAsserted()); m_interrupt_core->SetFIQLine(ipc.IsStarletFIQAsserted()); diff --git a/Source/Core/Core/IOS/Starlet/StarletMemory.h b/Source/Core/Core/IOS/Starlet/StarletMemory.h index bba6369e1a..039c194971 100644 --- a/Source/Core/Core/IOS/Starlet/StarletMemory.h +++ b/Source/Core/Core/IOS/Starlet/StarletMemory.h @@ -83,11 +83,11 @@ public: // range to its bus slow path, with the instruction address. `core` gives that address. void SetWatchCore(const ARMCore* core); // The core whose IRQ and FIQ lines follow Hollywood's ARM interrupt registers. A write to - // HW_ARMIRQFLAG or HW_ARMIRQMASK re-samples both lines at once, as the level-sensitive - // Hollywood output follows them on hardware, instead of at the next scheduler slice: an - // acknowledged or masked interrupt is never taken again in between. Raising a cause still - // reaches the line at the next slice. The owner of both attaches the core once it exists and - // destroys it before the memory. + // HW_ARMIRQFLAG, HW_ARMIRQMASK or HW_ARMFIQMASK re-samples both lines at once, as the + // level-sensitive Hollywood output follows them on hardware, instead of at the next scheduler + // slice: an acknowledged or masked interrupt is never taken again in between. Raising a cause + // still reaches the line at the next slice. The owner of both attaches the core once it exists + // and destroys it before the memory. void AttachInterruptLines(ARMCore* core); void Reset(); void DoState(PointerWrap& p); diff --git a/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp b/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp index d83adf071f..32b3127409 100644 --- a/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp +++ b/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp @@ -615,6 +615,71 @@ TEST(StarletInterrupts, InterruptRegisterWritesResampleTheAttachedCoreLines) EXPECT_FALSE(core.IsFIQLineAsserted()); } +TEST(StarletInterrupts, FIQMaskWritesResampleTheAttachedCoreLine) +{ + constexpr u32 hardware_base = 0x0d800000; + constexpr u32 arm_irq_flag = hardware_base + 0x38; + constexpr u32 arm_fiq_mask = hardware_base + 0x40; + Core::DeclareAsCPUThread(); + auto& system = Core::System::GetInstance(); + auto& ipc = system.GetWiiIPC(); + ipc.Reset(); + StarletMemory memory(system); + memory.Reset(); + ARMCore core(memory); + memory.AttachInterruptLines(&core); + + // A latched cause routed to no FIQ, sampled at a scheduler slice's start. + ipc.SetStarletInterrupt(INT_CAUSE_TIMER, true); + core.SetFIQLine(ipc.IsStarletFIQAsserted()); + ASSERT_FALSE(core.IsFIQLineAsserted()); + + // Unmasking it as an FIQ raises the line at once; masking it lowers the line at once. + memory.Write32(arm_fiq_mask, INT_CAUSE_TIMER); + EXPECT_TRUE(core.IsFIQLineAsserted()); + EXPECT_FALSE(core.IsIRQLineAsserted()); + memory.Write32(arm_fiq_mask, 0); + EXPECT_FALSE(core.IsFIQLineAsserted()); + + // The byte-assembled write path re-samples the same way. + for (u32 i = 0; i < 4; ++i) + memory.Write8(arm_fiq_mask + i, static_cast(INT_CAUSE_TIMER >> (24 - 8 * i))); + EXPECT_TRUE(core.IsFIQLineAsserted()); + memory.Write32(arm_irq_flag, INT_CAUSE_TIMER); + EXPECT_FALSE(core.IsFIQLineAsserted()); +} + +TEST(StarletInterrupts, AcknowledgingOneOfTwoPendingSourcesKeepsIRQAsserted) +{ + constexpr u32 hardware_base = 0x0d800000; + constexpr u32 arm_irq_flag = hardware_base + 0x38; + constexpr u32 arm_irq_mask = hardware_base + 0x3c; + Core::DeclareAsCPUThread(); + auto& system = Core::System::GetInstance(); + auto& ipc = system.GetWiiIPC(); + ipc.Reset(); + StarletMemory memory(system); + memory.Reset(); + ARMCore core(memory); + memory.AttachInterruptLines(&core); + + memory.Write32(arm_irq_mask, INT_CAUSE_TIMER | INT_CAUSE_NAND); + ipc.SetStarletInterrupt(INT_CAUSE_TIMER, true); + ipc.SetStarletInterrupt(INT_CAUSE_NAND, true); + core.SetIRQLine(ipc.IsStarletIRQAsserted()); + ASSERT_TRUE(core.IsIRQLineAsserted()); + + // One source acknowledged: the other still holds the line. + memory.Write32(arm_irq_flag, INT_CAUSE_TIMER); + EXPECT_TRUE(core.IsIRQLineAsserted()); + EXPECT_EQ(memory.Read32(arm_irq_flag) & (INT_CAUSE_TIMER | INT_CAUSE_NAND), INT_CAUSE_NAND); + + // The last one acknowledged: the line drops. + memory.Write32(arm_irq_flag, INT_CAUSE_NAND); + EXPECT_FALSE(core.IsIRQLineAsserted()); + EXPECT_EQ(memory.Read32(arm_irq_flag) & (INT_CAUSE_TIMER | INT_CAUSE_NAND), 0u); +} + TEST(StarletInterrupts, DriveAndResetUseDistinctHollywoodLines) { // Hollywood IRQ numbers, not the Broadway Processor Interface's DVD line.