From e3c942b2092761c1f812f1eff086f437b843341a Mon Sep 17 00:00:00 2001 From: MrPurple666 Date: Thu, 4 Dec 2025 07:25:21 +0100 Subject: [PATCH] [NCE] Fix cache invalidation and signal interrupt race condition (#3063) Inspired by PR #3047 This should theoretically fix 3 bugs in NCE: - **Bug 1**: `ClearInstructionCache()` now properly invalidates L1 instruction cache using IC IALLU instead of only using memory barriers - **Bug 2**: `InvalidateCacheRange()` implements proper range-based cache invalidation instead of always flushing entire L1 cache - **Bug 3**: `SignalInterrupt()` adds acquire fence to guarantee memory visibility of the `is_running` flag, preventing lost signals Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3063 Reviewed-by: CamilleLaVey Reviewed-by: Caio Oliveira Co-authored-by: MrPurple666 Co-committed-by: MrPurple666 --- src/core/arm/nce/arm_nce.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index f0b61f8042..dae3983a17 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -371,10 +371,12 @@ void ArmNce::SignalInterrupt(Kernel::KThread* thread) { // Add break loop condition. m_guest_ctx.esr_el1.fetch_or(static_cast(HaltReason::BreakLoop)); - // Lock the thread context. auto* params = &thread->GetNativeExecutionParameters(); LockThreadParameters(params); + // Ensure visibility of is_running after lock acquire + std::atomic_thread_fence(std::memory_order_acquire); + if (params->is_running) { // We should signal to the running thread. // The running thread will unlock the thread context. @@ -389,15 +391,28 @@ const std::size_t CACHE_PAGE_SIZE = 4096; void ArmNce::ClearInstructionCache() { #ifdef __aarch64__ - // Ensure all previous memory operations complete - asm volatile("dmb ish" ::: "memory"); - asm volatile("dsb ish" ::: "memory"); - asm volatile("isb" ::: "memory"); + // Use IC IALLU to actually invalidate L1 instruction cache + asm volatile("dsb ish\n" + "ic iallu\n" + "dsb ish\n" + "isb" ::: "memory"); #endif } void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) { - this->ClearInstructionCache(); +#ifdef ARCHITECTURE_arm64 + // Invalidate instruction cache for specific range instead of full flush + constexpr u64 cache_line_size = 64; + const u64 aligned_addr = addr & ~(cache_line_size - 1); + const u64 end_addr = (addr + size + cache_line_size - 1) & ~(cache_line_size - 1); + + asm volatile("dsb ish" ::: "memory"); + for (u64 i = aligned_addr; i < end_addr; i += cache_line_size) { + asm volatile("ic ivau, %0" :: "r"(i) : "memory"); + } + asm volatile("dsb ish\n" + "isb" ::: "memory"); +#endif } } // namespace Core