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.