| | |
| | |
| |
|
| | #include "common/assert.h" |
| | #include "common/logging/log.h" |
| | #include "core/core.h" |
| | #include "core/core_timing.h" |
| | #include "core/memory.h" |
| | #include "core/tools/freezer.h" |
| |
|
| | namespace Tools { |
| | namespace { |
| |
|
| | constexpr auto memory_freezer_ns = std::chrono::nanoseconds{1000000000 / 60}; |
| |
|
| | u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) { |
| | switch (width) { |
| | case 1: |
| | return memory.Read8(addr); |
| | case 2: |
| | return memory.Read16(addr); |
| | case 4: |
| | return memory.Read32(addr); |
| | case 8: |
| | return memory.Read64(addr); |
| | default: |
| | UNREACHABLE(); |
| | } |
| | } |
| |
|
| | void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 value) { |
| | switch (width) { |
| | case 1: |
| | memory.Write8(addr, static_cast<u8>(value)); |
| | break; |
| | case 2: |
| | memory.Write16(addr, static_cast<u16>(value)); |
| | break; |
| | case 4: |
| | memory.Write32(addr, static_cast<u32>(value)); |
| | break; |
| | case 8: |
| | memory.Write64(addr, value); |
| | break; |
| | default: |
| | UNREACHABLE(); |
| | } |
| | } |
| |
|
| | } |
| |
|
| | Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_) |
| | : core_timing{core_timing_}, memory{memory_} { |
| | event = Core::Timing::CreateEvent("MemoryFreezer::FrameCallback", |
| | [this](s64 time, std::chrono::nanoseconds ns_late) |
| | -> std::optional<std::chrono::nanoseconds> { |
| | FrameCallback(ns_late); |
| | return std::nullopt; |
| | }); |
| | core_timing.ScheduleEvent(memory_freezer_ns, event); |
| | } |
| |
|
| | Freezer::~Freezer() { |
| | core_timing.UnscheduleEvent(event); |
| | } |
| |
|
| | void Freezer::SetActive(bool is_active) { |
| | if (!active.exchange(is_active)) { |
| | FillEntryReads(); |
| | core_timing.ScheduleEvent(memory_freezer_ns, event); |
| | LOG_DEBUG(Common_Memory, "Memory freezer activated!"); |
| | } else { |
| | LOG_DEBUG(Common_Memory, "Memory freezer deactivated!"); |
| | } |
| | } |
| |
|
| | bool Freezer::IsActive() const { |
| | return active.load(std::memory_order_relaxed); |
| | } |
| |
|
| | void Freezer::Clear() { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | LOG_DEBUG(Common_Memory, "Clearing all frozen memory values."); |
| |
|
| | entries.clear(); |
| | } |
| |
|
| | u64 Freezer::Freeze(VAddr address, u32 width) { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | const auto current_value = MemoryReadWidth(memory, width, address); |
| | entries.push_back({address, width, current_value}); |
| |
|
| | LOG_DEBUG(Common_Memory, |
| | "Freezing memory for address={:016X}, width={:02X}, current_value={:016X}", address, |
| | width, current_value); |
| |
|
| | return current_value; |
| | } |
| |
|
| | void Freezer::Unfreeze(VAddr address) { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address); |
| |
|
| | std::erase_if(entries, [address](const Entry& entry) { return entry.address == address; }); |
| | } |
| |
|
| | bool Freezer::IsFrozen(VAddr address) const { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | return FindEntry(address) != entries.cend(); |
| | } |
| |
|
| | void Freezer::SetFrozenValue(VAddr address, u64 value) { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | const auto iter = FindEntry(address); |
| |
|
| | if (iter == entries.cend()) { |
| | LOG_ERROR(Common_Memory, |
| | "Tried to set freeze value for address={:016X} that is not frozen!", address); |
| | return; |
| | } |
| |
|
| | LOG_DEBUG(Common_Memory, |
| | "Manually overridden freeze value for address={:016X}, width={:02X} to value={:016X}", |
| | iter->address, iter->width, value); |
| | iter->value = value; |
| | } |
| |
|
| | std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | const auto iter = FindEntry(address); |
| |
|
| | if (iter == entries.cend()) { |
| | return std::nullopt; |
| | } |
| |
|
| | return *iter; |
| | } |
| |
|
| | std::vector<Freezer::Entry> Freezer::GetEntries() const { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | return entries; |
| | } |
| |
|
| | Freezer::Entries::iterator Freezer::FindEntry(VAddr address) { |
| | return std::find_if(entries.begin(), entries.end(), |
| | [address](const Entry& entry) { return entry.address == address; }); |
| | } |
| |
|
| | Freezer::Entries::const_iterator Freezer::FindEntry(VAddr address) const { |
| | return std::find_if(entries.begin(), entries.end(), |
| | [address](const Entry& entry) { return entry.address == address; }); |
| | } |
| |
|
| | void Freezer::FrameCallback(std::chrono::nanoseconds ns_late) { |
| | if (!IsActive()) { |
| | LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); |
| | return; |
| | } |
| |
|
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | for (const auto& entry : entries) { |
| | LOG_DEBUG(Common_Memory, |
| | "Enforcing memory freeze at address={:016X}, value={:016X}, width={:02X}", |
| | entry.address, entry.value, entry.width); |
| | MemoryWriteWidth(memory, entry.width, entry.address, entry.value); |
| | } |
| |
|
| | core_timing.ScheduleEvent(memory_freezer_ns - ns_late, event); |
| | } |
| |
|
| | void Freezer::FillEntryReads() { |
| | std::scoped_lock lock{entries_mutex}; |
| |
|
| | LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values."); |
| |
|
| | for (auto& entry : entries) { |
| | entry.value = MemoryReadWidth(memory, entry.width, entry.address); |
| | } |
| | } |
| |
|
| | } |
| |
|