Compare commits
3
Commits
bef2c8cf02
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd2336f278 | ||
|
|
6c44367b5c | ||
|
|
d486779f50 |
No files matched your search
@@ -159,6 +159,8 @@ public:
|
|||||||
}
|
}
|
||||||
bool IsWaitingForInterrupt() const { return m_waiting_for_interrupt; }
|
bool IsWaitingForInterrupt() const { return m_waiting_for_interrupt; }
|
||||||
bool IsWaitingForMemoryPoll() const { return m_waiting_for_memory_poll; }
|
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; }
|
u32 GetMemoryPollAddress() const { return m_memory_poll_address; }
|
||||||
bool IsWaitingForExternalEvent() const
|
bool IsWaitingForExternalEvent() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
|
|||||||
// construct it only after the complete physical bus exists.
|
// construct it only after the complete physical bus exists.
|
||||||
m_core = std::make_unique<ARMCore>(*m_memory,
|
m_core = std::make_unique<ARMCore>(*m_memory,
|
||||||
Config::Get(Config::MAIN_WII_STARLET_JIT_MEMORY_PROFILING));
|
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)
|
#if defined(_M_X86_64)
|
||||||
m_core->SetJitEnabled(Config::Get(Config::MAIN_WII_STARLET_JIT));
|
m_core->SetJitEnabled(Config::Get(Config::MAIN_WII_STARLET_JIT));
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -333,6 +333,9 @@ constexpr u32 HW_AHBPROT = HW_BASE + 0x64;
|
|||||||
constexpr u32 HW_TIMER = HW_BASE + 0x10;
|
constexpr u32 HW_TIMER = HW_BASE + 0x10;
|
||||||
constexpr u64 TIMER_CLOCK_DIVISOR = 128;
|
constexpr u64 TIMER_CLOCK_DIVISOR = 128;
|
||||||
constexpr u32 HW_ALARM = HW_BASE + 0x14;
|
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_ENABLE = HW_BASE + 0xdc;
|
||||||
constexpr u32 HW_GPIO_OUT = HW_BASE + 0xe0;
|
constexpr u32 HW_GPIO_OUT = HW_BASE + 0xe0;
|
||||||
constexpr u32 HW_GPIO_DIR = HW_BASE + 0xe4;
|
constexpr u32 HW_GPIO_DIR = HW_BASE + 0xe4;
|
||||||
@@ -864,6 +867,7 @@ void StarletMemory::Reset()
|
|||||||
ResetOHCIController(controller);
|
ResetOHCIController(controller);
|
||||||
m_arm_cycles = 0;
|
m_arm_cycles = 0;
|
||||||
m_timer_offset = 0;
|
m_timer_offset = 0;
|
||||||
|
m_alarm_matched = false;
|
||||||
m_boot0_mapped = true;
|
m_boot0_mapped = true;
|
||||||
m_sram_split_mode = false;
|
m_sram_split_mode = false;
|
||||||
// Retail Hollywood production revision. Early firmware branches on this
|
// Retail Hollywood production revision. Early firmware branches on this
|
||||||
@@ -1068,6 +1072,7 @@ void StarletMemory::DoState(PointerWrap& p)
|
|||||||
p.Do(m_seeprom_write_all);
|
p.Do(m_seeprom_write_all);
|
||||||
p.Do(m_arm_cycles);
|
p.Do(m_arm_cycles);
|
||||||
p.Do(m_timer_offset);
|
p.Do(m_timer_offset);
|
||||||
|
p.Do(m_alarm_matched);
|
||||||
p.Do(m_initialized);
|
p.Do(m_initialized);
|
||||||
p.Do(m_boot0_mapped);
|
p.Do(m_boot0_mapped);
|
||||||
p.Do(m_sram_split_mode);
|
p.Do(m_sram_split_mode);
|
||||||
@@ -1104,6 +1109,36 @@ 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 && address != HW_ARMFIQMASK))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const WiiIPC& ipc = m_system.GetWiiIPC();
|
||||||
|
m_interrupt_core->SetIRQLine(ipc.IsStarletIRQAsserted());
|
||||||
|
m_interrupt_core->SetFIQLine(ipc.IsStarletFIQAsserted());
|
||||||
|
}
|
||||||
|
|
||||||
|
void StarletMemory::KeepMatchedAlarmAsserted(u32 address)
|
||||||
|
{
|
||||||
|
// Once the counter has reached HW_ALARM, acknowledging the timer through
|
||||||
|
// HW_ARMIRQFLAG does not clear its cause until HW_ALARM is written again.
|
||||||
|
// Observed on a console: StarOS's M3C gate H1 (2026-10-16) acknowledged the
|
||||||
|
// timer before rewriting HW_ALARM, tens of microseconds after the match, and
|
||||||
|
// read the cause still set. MINI's handler rewrites HW_ALARM first. One run
|
||||||
|
// does not tell a latch from a "counter >= alarm" comparison; the latch is
|
||||||
|
// the smaller model that explains it.
|
||||||
|
if (address == HW_ARMIRQFLAG && m_alarm_matched)
|
||||||
|
m_system.GetWiiIPC().SetStarletInterrupt(INT_CAUSE_TIMER, true);
|
||||||
|
}
|
||||||
|
|
||||||
u32 StarletMemory::GetTimer() const
|
u32 StarletMemory::GetTimer() const
|
||||||
{
|
{
|
||||||
// Hollywood's timer runs at Starlet / 128 (1.8984375 MHz at 243 MHz).
|
// Hollywood's timer runs at Starlet / 128 (1.8984375 MHz at 243 MHz).
|
||||||
@@ -1134,7 +1169,10 @@ void StarletMemory::AdvanceCycles(u64 cycles)
|
|||||||
const u32 alarm = ReadRegister(HW_ALARM);
|
const u32 alarm = ReadRegister(HW_ALARM);
|
||||||
const u32 ticks_until_alarm = alarm - old_timer;
|
const u32 ticks_until_alarm = alarm - old_timer;
|
||||||
if (ticks_until_alarm != 0 && ticks_until_alarm <= elapsed_timer_ticks)
|
if (ticks_until_alarm != 0 && ticks_until_alarm <= elapsed_timer_ticks)
|
||||||
|
{
|
||||||
|
m_alarm_matched = true;
|
||||||
m_system.GetWiiIPC().SetStarletInterrupt(INT_CAUSE_TIMER, true);
|
m_system.GetWiiIPC().SetStarletInterrupt(INT_CAUSE_TIMER, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_nand_cycles_until_completion != 0)
|
if (m_nand_cycles_until_completion != 0)
|
||||||
{
|
{
|
||||||
@@ -4099,9 +4137,11 @@ void StarletMemory::Write8(u32 address, u8 value)
|
|||||||
{
|
{
|
||||||
// HW_ALARM is a comparator, not an interrupt acknowledgement register.
|
// HW_ALARM is a comparator, not an interrupt acknowledgement register.
|
||||||
// Original Starlet software acknowledges the timer separately through
|
// Original Starlet software acknowledges the timer separately through
|
||||||
// HW_ARMIRQFLAG. Programming the comparator to the current counter value
|
// HW_ARMIRQFLAG. Programming the comparator re-arms it: a past match no
|
||||||
// creates an immediate match.
|
// longer holds the cause (KeepMatchedAlarmAsserted). Programming it to the
|
||||||
if (word == GetTimer())
|
// current counter value creates an immediate match.
|
||||||
|
m_alarm_matched = word == GetTimer();
|
||||||
|
if (m_alarm_matched)
|
||||||
m_system.GetWiiIPC().SetStarletInterrupt(INT_CAUSE_TIMER, true);
|
m_system.GetWiiIPC().SetStarletInterrupt(INT_CAUSE_TIMER, true);
|
||||||
}
|
}
|
||||||
else if (word_address == HW_SPARE0)
|
else if (word_address == HW_SPARE0)
|
||||||
@@ -4147,6 +4187,8 @@ void StarletMemory::Write8(u32 address, u8 value)
|
|||||||
word_address == HW_AHBPROT || word_address == HW_RESETS)
|
word_address == HW_AHBPROT || word_address == HW_RESETS)
|
||||||
{
|
{
|
||||||
m_system.GetWiiIPC().WriteStarletRegister(word_address - HW_BASE, word);
|
m_system.GetWiiIPC().WriteStarletRegister(word_address - HW_BASE, word);
|
||||||
|
KeepMatchedAlarmAsserted(word_address);
|
||||||
|
UpdateInterruptLines(word_address);
|
||||||
if (word_address == HW_BASE + 0x0c && (word & 0x09) != 0)
|
if (word_address == HW_BASE + 0x0c && (word & 0x09) != 0)
|
||||||
{
|
{
|
||||||
// The physical CPUs are concurrent. Give Broadway a scheduling boundary
|
// The physical CPUs are concurrent. Give Broadway a scheduling boundary
|
||||||
@@ -4229,6 +4271,8 @@ void StarletMemory::Write32(u32 address, u32 value)
|
|||||||
{
|
{
|
||||||
WriteRegister(address, value);
|
WriteRegister(address, value);
|
||||||
m_system.GetWiiIPC().WriteStarletRegister(address - HW_BASE, value);
|
m_system.GetWiiIPC().WriteStarletRegister(address - HW_BASE, value);
|
||||||
|
KeepMatchedAlarmAsserted(address);
|
||||||
|
UpdateInterruptLines(address);
|
||||||
// Phase 11 H1: an IPC reply (ARMCTRL Y1) audits the watched ranges.
|
// Phase 11 H1: an IPC reply (ARMCTRL Y1) audits the watched ranges.
|
||||||
if (!m_watch_ranges.empty() && address == HW_BASE + 0x0c && (value & 1) != 0)
|
if (!m_watch_ranges.empty() && address == HW_BASE + 0x0c && (value & 1) != 0)
|
||||||
WatchAudit();
|
WatchAudit();
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ public:
|
|||||||
// that touches one of those physical ranges. The ARM JIT sends the accesses that hit a watched
|
// 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.
|
// range to its bus slow path, with the instruction address. `core` gives that address.
|
||||||
void SetWatchCore(const ARMCore* core);
|
void SetWatchCore(const ARMCore* core);
|
||||||
|
// The core whose IRQ and FIQ lines follow Hollywood's ARM interrupt registers. A write to
|
||||||
|
// 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 Reset();
|
||||||
void DoState(PointerWrap& p);
|
void DoState(PointerWrap& p);
|
||||||
|
|
||||||
@@ -110,6 +117,10 @@ public:
|
|||||||
private:
|
private:
|
||||||
// Synthetic NAND fixtures exercise startup pairing without any console dumps or keys.
|
// Synthetic NAND fixtures exercise startup pairing without any console dumps or keys.
|
||||||
friend class StarletPairingTest;
|
friend class StarletPairingTest;
|
||||||
|
|
||||||
|
void UpdateInterruptLines(u32 address);
|
||||||
|
void KeepMatchedAlarmAsserted(u32 address);
|
||||||
|
ARMCore* m_interrupt_core = nullptr;
|
||||||
friend class StarletDMATest;
|
friend class StarletDMATest;
|
||||||
|
|
||||||
static constexpr u32 NAND_PAGE_DATA_SIZE = 0x800;
|
static constexpr u32 NAND_PAGE_DATA_SIZE = 0x800;
|
||||||
@@ -347,6 +358,8 @@ private:
|
|||||||
bool m_seeprom_write_all = false;
|
bool m_seeprom_write_all = false;
|
||||||
u64 m_arm_cycles = 0;
|
u64 m_arm_cycles = 0;
|
||||||
u32 m_timer_offset = 0;
|
u32 m_timer_offset = 0;
|
||||||
|
// The counter has reached HW_ALARM since it was last written.
|
||||||
|
bool m_alarm_matched = false;
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
// Phase 11 F1 write watch (REV_WATCH); empty when off.
|
// Phase 11 F1 write watch (REV_WATCH); empty when off.
|
||||||
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ struct CompressAndDumpStateArgs
|
|||||||
static Common::WorkQueueThreadSP<CompressAndDumpStateArgs> s_compress_and_dump_thread;
|
static Common::WorkQueueThreadSP<CompressAndDumpStateArgs> s_compress_and_dump_thread;
|
||||||
|
|
||||||
// Don't forget to increase this after doing changes on the savestate system
|
// Don't forget to increase this after doing changes on the savestate system
|
||||||
constexpr u32 STATE_VERSION = 196; // Correct Hollywood DI/reset pending interrupt bit mapping.
|
constexpr u32 STATE_VERSION = 197; // Hollywood's alarm stays matched until HW_ALARM is rewritten.
|
||||||
|
|
||||||
// Increase this if the StateExtendedHeader definition changes
|
// Increase this if the StateExtendedHeader definition changes
|
||||||
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
|
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
|
||||||
|
|||||||
@@ -568,6 +568,208 @@ TEST(StarletRegisters, WideTimerAndInterruptAccessesMatchHardwareSemantics)
|
|||||||
EXPECT_EQ(memory.Read32(arm_irq_mask), 0x800619efu);
|
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<u8>(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, 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<u8>(INT_CAUSE_TIMER >> (24 - 8 * i)));
|
||||||
|
EXPECT_TRUE(core.IsFIQLineAsserted());
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
EXPECT_FALSE(core.IsFIQLineAsserted());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StarletTimer, MatchedAlarmStaysAssertedUntilRewritten)
|
||||||
|
{
|
||||||
|
constexpr u32 hardware_base = 0x0d800000;
|
||||||
|
constexpr u32 timer = hardware_base + 0x10;
|
||||||
|
constexpr u32 alarm = hardware_base + 0x14;
|
||||||
|
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);
|
||||||
|
const auto timer_cause = [&ipc] { return ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER; };
|
||||||
|
|
||||||
|
memory.Write32(arm_irq_mask, INT_CAUSE_TIMER);
|
||||||
|
memory.Write32(alarm, 64);
|
||||||
|
memory.AdvanceCycles(64 * 128);
|
||||||
|
ASSERT_EQ(memory.Read32(timer), 64u);
|
||||||
|
ASSERT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
core.SetIRQLine(ipc.IsStarletIRQAsserted());
|
||||||
|
ASSERT_TRUE(core.IsIRQLineAsserted());
|
||||||
|
|
||||||
|
// Acknowledged before HW_ALARM is rewritten, after the counter moved on: the
|
||||||
|
// cause and the line stay asserted (the console's StarOS M3C gate H1).
|
||||||
|
memory.AdvanceCycles(32 * 128);
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
EXPECT_TRUE(core.IsIRQLineAsserted());
|
||||||
|
for (u32 i = 0; i < 4; ++i)
|
||||||
|
memory.Write8(arm_irq_flag + i, static_cast<u8>(INT_CAUSE_TIMER >> (24 - 8 * i)));
|
||||||
|
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
|
||||||
|
// A counter write does not end the match either.
|
||||||
|
memory.Write32(timer, 0);
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
|
||||||
|
// MINI's order: rewrite HW_ALARM ahead, then acknowledge. The rewrite alone
|
||||||
|
// acknowledges nothing; the acknowledgement then clears the cause and the line.
|
||||||
|
memory.Write32(alarm, memory.Read32(timer) + 64);
|
||||||
|
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
EXPECT_EQ(timer_cause(), 0u);
|
||||||
|
EXPECT_FALSE(core.IsIRQLineAsserted());
|
||||||
|
|
||||||
|
// The next match latches again.
|
||||||
|
memory.AdvanceCycles(64 * 128);
|
||||||
|
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StarletTimer, MatchedAlarmSurvivesStateRoundTrip)
|
||||||
|
{
|
||||||
|
constexpr u32 alarm = 0x0d800014;
|
||||||
|
constexpr u32 arm_irq_flag = 0x0d800038;
|
||||||
|
Core::DeclareAsCPUThread();
|
||||||
|
auto& system = Core::System::GetInstance();
|
||||||
|
auto& ipc = system.GetWiiIPC();
|
||||||
|
ipc.Reset();
|
||||||
|
StarletMemory memory(system);
|
||||||
|
memory.Reset();
|
||||||
|
memory.Write32(alarm, 8);
|
||||||
|
memory.AdvanceCycles(8 * 128);
|
||||||
|
ASSERT_EQ(ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||||
|
|
||||||
|
std::vector<u8> state_buffer(1024 * 1024);
|
||||||
|
u8* state_pointer = state_buffer.data();
|
||||||
|
PointerWrap writer(&state_pointer, state_buffer.size(), PointerWrap::Mode::Write);
|
||||||
|
memory.DoState(writer);
|
||||||
|
ASSERT_TRUE(writer.IsWriteMode());
|
||||||
|
const size_t state_size = state_pointer - state_buffer.data();
|
||||||
|
|
||||||
|
// A reset forgets the match: the acknowledgement clears the cause.
|
||||||
|
memory.Reset();
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
ASSERT_EQ(ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER, 0u);
|
||||||
|
|
||||||
|
// The restored state holds it again.
|
||||||
|
state_pointer = state_buffer.data();
|
||||||
|
PointerWrap reader(&state_pointer, state_size, PointerWrap::Mode::Read);
|
||||||
|
memory.DoState(reader);
|
||||||
|
ASSERT_TRUE(reader.IsReadMode());
|
||||||
|
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||||
|
EXPECT_EQ(ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
TEST(StarletInterrupts, DriveAndResetUseDistinctHollywoodLines)
|
||||||
{
|
{
|
||||||
// Hollywood IRQ numbers, not the Broadway Processor Interface's DVD line.
|
// Hollywood IRQ numbers, not the Broadway Processor Interface's DVD line.
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, SBR1Mode sbr1,
|
|||||||
}
|
}
|
||||||
|
|
||||||
IOS::LLE::ARMCore core(memory);
|
IOS::LLE::ARMCore core(memory);
|
||||||
|
// Declared after memory, so destroyed first.
|
||||||
|
memory.AttachInterruptLines(&core);
|
||||||
core.SetJitEnabled(jit);
|
core.SetJitEnabled(jit);
|
||||||
core.Reset(info.entry);
|
core.Reset(info.entry);
|
||||||
core.SetBigEndian(true);
|
core.SetBigEndian(true);
|
||||||
|
|||||||
Reference in new issue
Block a user