diff --git a/Source/Core/Core/IOS/Starlet/ARMCore.h b/Source/Core/Core/IOS/Starlet/ARMCore.h index b4cdc1ca13..cd4b55f52f 100644 --- a/Source/Core/Core/IOS/Starlet/ARMCore.h +++ b/Source/Core/Core/IOS/Starlet/ARMCore.h @@ -159,6 +159,8 @@ public: } bool IsWaitingForInterrupt() const { return m_waiting_for_interrupt; } bool IsWaitingForMemoryPoll() const { return m_waiting_for_memory_poll; } + bool IsIRQLineAsserted() const { return m_irq_line; } + bool IsFIQLineAsserted() const { return m_fiq_line; } u32 GetMemoryPollAddress() const { return m_memory_poll_address; } bool IsWaitingForExternalEvent() const { diff --git a/Source/Core/Core/IOS/Starlet/Starlet.cpp b/Source/Core/Core/IOS/Starlet/Starlet.cpp index ad29974df0..b70dd27f80 100644 --- a/Source/Core/Core/IOS/Starlet/Starlet.cpp +++ b/Source/Core/Core/IOS/Starlet/Starlet.cpp @@ -144,6 +144,8 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error) // construct it only after the complete physical bus exists. m_core = std::make_unique(*m_memory, Config::Get(Config::MAIN_WII_STARLET_JIT_MEMORY_PROFILING)); + // m_core is declared after m_memory, so it is destroyed first. + m_memory->AttachInterruptLines(m_core.get()); #if defined(_M_X86_64) m_core->SetJitEnabled(Config::Get(Config::MAIN_WII_STARLET_JIT)); #else diff --git a/Source/Core/Core/IOS/Starlet/StarletMemory.cpp b/Source/Core/Core/IOS/Starlet/StarletMemory.cpp index a0843771ec..bc18a84f0f 100644 --- a/Source/Core/Core/IOS/Starlet/StarletMemory.cpp +++ b/Source/Core/Core/IOS/Starlet/StarletMemory.cpp @@ -333,6 +333,8 @@ constexpr u32 HW_AHBPROT = HW_BASE + 0x64; constexpr u32 HW_TIMER = HW_BASE + 0x10; 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_GPIO_ENABLE = HW_BASE + 0xdc; constexpr u32 HW_GPIO_OUT = HW_BASE + 0xe0; constexpr u32 HW_GPIO_DIR = HW_BASE + 0xe4; @@ -1104,6 +1106,20 @@ void StarletMemory::DoState(PointerWrap& p) } } +void StarletMemory::AttachInterruptLines(ARMCore* core) +{ + m_interrupt_core = core; +} + +void StarletMemory::UpdateInterruptLines(u32 address) +{ + if (m_interrupt_core == nullptr || (address != HW_ARMIRQFLAG && address != HW_ARMIRQMASK)) + return; + const WiiIPC& ipc = m_system.GetWiiIPC(); + m_interrupt_core->SetIRQLine(ipc.IsStarletIRQAsserted()); + m_interrupt_core->SetFIQLine(ipc.IsStarletFIQAsserted()); +} + u32 StarletMemory::GetTimer() const { // Hollywood's timer runs at Starlet / 128 (1.8984375 MHz at 243 MHz). @@ -4147,6 +4163,7 @@ void StarletMemory::Write8(u32 address, u8 value) word_address == HW_AHBPROT || word_address == HW_RESETS) { m_system.GetWiiIPC().WriteStarletRegister(word_address - HW_BASE, word); + UpdateInterruptLines(word_address); if (word_address == HW_BASE + 0x0c && (word & 0x09) != 0) { // The physical CPUs are concurrent. Give Broadway a scheduling boundary @@ -4229,6 +4246,7 @@ void StarletMemory::Write32(u32 address, u32 value) { WriteRegister(address, value); m_system.GetWiiIPC().WriteStarletRegister(address - HW_BASE, value); + UpdateInterruptLines(address); // Phase 11 H1: an IPC reply (ARMCTRL Y1) audits the watched ranges. if (!m_watch_ranges.empty() && address == HW_BASE + 0x0c && (value & 1) != 0) WatchAudit(); diff --git a/Source/Core/Core/IOS/Starlet/StarletMemory.h b/Source/Core/Core/IOS/Starlet/StarletMemory.h index fc814effa2..bba6369e1a 100644 --- a/Source/Core/Core/IOS/Starlet/StarletMemory.h +++ b/Source/Core/Core/IOS/Starlet/StarletMemory.h @@ -82,6 +82,13 @@ public: // that touches one of those physical ranges. The ARM JIT sends the accesses that hit a watched // 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. + void AttachInterruptLines(ARMCore* core); void Reset(); void DoState(PointerWrap& p); @@ -110,6 +117,9 @@ public: private: // Synthetic NAND fixtures exercise startup pairing without any console dumps or keys. friend class StarletPairingTest; + + void UpdateInterruptLines(u32 address); + ARMCore* m_interrupt_core = nullptr; friend class StarletDMATest; static constexpr u32 NAND_PAGE_DATA_SIZE = 0x800; diff --git a/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp b/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp index 903f3da7eb..d83adf071f 100644 --- a/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp +++ b/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp @@ -568,6 +568,53 @@ TEST(StarletRegisters, WideTimerAndInterruptAccessesMatchHardwareSemantics) EXPECT_EQ(memory.Read32(arm_irq_mask), 0x800619efu); } +TEST(StarletInterrupts, InterruptRegisterWritesResampleTheAttachedCoreLines) +{ + constexpr u32 hardware_base = 0x0d800000; + constexpr u32 arm_irq_flag = hardware_base + 0x38; + constexpr u32 arm_irq_mask = hardware_base + 0x3c; + 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, unmasked timer interrupt, sampled at a scheduler slice's start. + memory.Write32(arm_irq_mask, INT_CAUSE_TIMER); + ipc.SetStarletInterrupt(INT_CAUSE_TIMER, true); + core.SetIRQLine(ipc.IsStarletIRQAsserted()); + ASSERT_TRUE(core.IsIRQLineAsserted()); + + // Acknowledging it lowers the line at once, not at the next slice. + memory.Write32(arm_irq_flag, INT_CAUSE_TIMER); + EXPECT_FALSE(core.IsIRQLineAsserted()); + + // Raising a cause still waits for the slice; masking and unmasking a latched cause do not. + ipc.SetStarletInterrupt(INT_CAUSE_TIMER, true); + EXPECT_FALSE(core.IsIRQLineAsserted()); + memory.Write32(arm_irq_mask, 0); + EXPECT_FALSE(core.IsIRQLineAsserted()); + memory.Write32(arm_irq_mask, INT_CAUSE_TIMER); + EXPECT_TRUE(core.IsIRQLineAsserted()); + + // The byte-assembled write path acknowledges as the wide one does. + for (u32 i = 0; i < 4; ++i) + memory.Write8(arm_irq_flag + i, static_cast(INT_CAUSE_TIMER >> (24 - 8 * i))); + EXPECT_FALSE(core.IsIRQLineAsserted()); + + // An acknowledgement re-samples the FIQ line from the same flags. + memory.Write32(arm_fiq_mask, INT_CAUSE_TIMER); + ipc.SetStarletInterrupt(INT_CAUSE_TIMER, true); + core.SetFIQLine(ipc.IsStarletFIQAsserted()); + ASSERT_TRUE(core.IsFIQLineAsserted()); + memory.Write32(arm_irq_flag, INT_CAUSE_TIMER); + EXPECT_FALSE(core.IsFIQLineAsserted()); +} + TEST(StarletInterrupts, DriveAndResetUseDistinctHollywoodLines) { // Hollywood IRQ numbers, not the Broadway Processor Interface's DVD line. diff --git a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp index 989572a648..bf228fb6d4 100644 --- a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp +++ b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp @@ -82,6 +82,8 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, SBR1Mode sbr1, } IOS::LLE::ARMCore core(memory); + // Declared after memory, so destroyed first. + memory.AttachInterruptLines(&core); core.SetJitEnabled(jit); core.Reset(info.entry); core.SetBigEndian(true);