From f3f0b6f664f1d2921d99b79ea81deea64a48c1de Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 23 Jul 2021 22:18:41 +0200 Subject: [PATCH 1/3] Add unaligned stores test https://bugs.dolphin-emu.org/issues/12565 --- cputest/CMakeLists.txt | 1 + cputest/unaligned_stores.cpp | 195 +++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 cputest/unaligned_stores.cpp diff --git a/cputest/CMakeLists.txt b/cputest/CMakeLists.txt index d12a3eb..2cf67b8 100644 --- a/cputest/CMakeLists.txt +++ b/cputest/CMakeLists.txt @@ -7,3 +7,4 @@ add_hwtest(MODULE cputest TEST reciprocal FILES reciprocal.cpp) add_hwtest(MODULE cputest TEST mtspr FILES mtspr.cpp) add_hwtest(MODULE cputest TEST srawix FILES srawix.cpp) add_hwtest(MODULE cputest TEST rlw FILES rlw.cpp) +add_hwtest(MODULE cputest TEST unaligned_stores FILES unaligned_stores.cpp) diff --git a/cputest/unaligned_stores.cpp b/cputest/unaligned_stores.cpp new file mode 100644 index 0000000..ffae0b8 --- /dev/null +++ b/cputest/unaligned_stores.cpp @@ -0,0 +1,195 @@ +#include +#include + +#include +#include +#include +#include + +#include "common/BitUtils.h" +#include "common/hwtests.h" + +// This test covers the behavior described in https://bugs.dolphin-emu.org/issues/12565 + +constexpr size_t PAGE_SIZE = 4096; + +static volatile u32* const s_pi_reg = reinterpret_cast(0xCC003000); + +static volatile bool s_pi_error_occurred = false; + +static u32 Read(volatile u8* ptr) +{ + return *reinterpret_cast(ptr); +} + +static void Write(volatile u8* ptr, u32 value, u32 size) +{ + switch (size) + { + case 4: + asm volatile("stw %0, 0(%1)" :: "r"(value), "r"(ptr)); + break; + case 2: + asm volatile("sth %0, 0(%1)" :: "r"(value), "r"(ptr)); + break; + case 1: + asm volatile("stb %0, 0(%1)" :: "r"(value), "r"(ptr)); + break; + } +} + +static void WriteWithSimulatedQuirks(uintptr_t alignment, volatile u8* ptr, u32 value, u32 size) +{ + const uintptr_t misalignment_32 = alignment & 3; + + if (misalignment_32 || size < 4) + { + const uintptr_t misalignment_64 = alignment & 7; + const uintptr_t count = misalignment_64 + size; + + value = Common::RotateRight(value, (misalignment_32 + size) * 8); + + for (uintptr_t i = 0; i < count; i += 8) + { + Write(ptr - misalignment_64 + i, value, sizeof(u32)); + Write(ptr - misalignment_64 + i + 4, value, sizeof(u32)); + } + } + else + { + Write(ptr, value, size); + } +} + +static void UnalignedStoresTest(volatile u8* ptr, u32 size, bool cached_memory) +{ + network_printf("Starting test using ptr 0x%" PRIxPTR ", size %u\n", + reinterpret_cast(ptr), size); + + volatile u8 reference_buffer[32]; + const u32 word = 0x12345678; + + for (size_t i = 0; i <= 28; ++i) + { + // Use 32-bit writes to avoid accidentally triggering the behavior we're trying to test + std::fill(reinterpret_cast(ptr), reinterpret_cast(ptr + 32), 0); + std::fill(reference_buffer, reference_buffer + 32, 0); + + s_pi_error_occurred = false; + + // The actual write that we are testing the behavior of + Write(ptr + i, word, size); + + if (cached_memory) + Write(reference_buffer + i, word, size); + else + WriteWithSimulatedQuirks(i, reference_buffer + i, word, size); + + DO_TEST(std::equal(ptr, ptr + 32, reference_buffer), + "{}-byte write to {} failed\n" + "ACTUAL: EXPECTED:\n" + "{:08X} {:08X} {:08X} {:08X}\n" + "{:08X} {:08X} {:08X} {:08X}\n" + "{:08X} {:08X} {:08X} {:08X}\n" + "{:08X} {:08X} {:08X} {:08X}\n", + size, fmt::ptr(ptr + i), + Read(ptr), Read(ptr + 4), Read(reference_buffer), Read(reference_buffer + 4), + Read(ptr + 8), Read(ptr + 12), Read(reference_buffer + 8), Read(reference_buffer + 12), + Read(ptr + 16), Read(ptr + 20), Read(reference_buffer + 16), Read(reference_buffer + 20), + Read(ptr + 24), Read(ptr + 28), Read(reference_buffer + 24), Read(reference_buffer + 28)); + + // TODO: Why does this trigger even for aligned 32-bit writes? Might be a bug in the test code + int pi_error_expected = !cached_memory /* && ((i & 3) || size < 4) */; + int pi_error_actual = s_pi_error_occurred; + + DO_TEST(pi_error_actual == pi_error_expected, + "{}-byte write to 0x{:08x} failed\n" + "ACTUAL: EXPECTED:\n" + "pi_error_occurred={} pi_error_occurred={}\n", + size, reinterpret_cast(ptr + i), + pi_error_actual, pi_error_expected); + } +} + +static void PIErrorHandler([[maybe_unused]] u32 nIrq, [[maybe_unused]] void* pCtx) +{ + // Clear the interrupt + s_pi_reg[0] = 0x01; + + s_pi_error_occurred = true; +} + +static void Initialize() +{ + network_init(); + + // Initialise the video system + VIDEO_Init(); + + // This function initialises the attached controllers + WPAD_Init(); + + // Obtain the preferred video mode from the system + // This will correspond to the settings in the Wii menu + GXRModeObj* rmode = VIDEO_GetPreferredMode(NULL); + + // Allocate memory for the display in the uncached region + void* xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); + + // Initialise the console, required for printf + console_init(xfb, 20, 20, rmode->fbWidth, rmode->xfbHeight, rmode->fbWidth * VI_DISPLAY_PIX_SZ); + + // Set up the video registers with the chosen mode + VIDEO_Configure(rmode); + + // Tell the video hardware where our display memory is + VIDEO_SetNextFramebuffer(xfb); + + // Make the display visible + VIDEO_SetBlack(FALSE); + + // Flush the video register changes to the hardware + VIDEO_Flush(); + + // Wait for Video setup to complete + VIDEO_WaitVSync(); + if (rmode->viTVMode & VI_NON_INTERLACE) + VIDEO_WaitVSync(); +} + +int main() +{ + Initialize(); + + network_printf("Setting up PI ERROR interrupt handler...\n"); + IRQ_Request(IRQ_PI_ERROR, PIErrorHandler, nullptr); + __UnmaskIrq(IM_PI_ERROR); + + // Get a pointer to a page boundary, with at least 32 bytes available on each side of the boundary + network_printf("Allocating memory...\n"); + u8* memory_allocation = new u8[PAGE_SIZE + 32 * 4]; + + volatile u8* cached_ptr = reinterpret_cast( + reinterpret_cast(memory_allocation - 32) & ~(PAGE_SIZE - 1)); + + UnalignedStoresTest(cached_ptr - 16, 1, true); + UnalignedStoresTest(cached_ptr - 16, 2, true); + UnalignedStoresTest(cached_ptr - 16, 4, true); + + network_printf("Invalidating cache...\n"); + asm volatile("dcbi %0, %1" :: "r"(cached_ptr), "r"(-32)); + asm volatile("dcbi %0, %1" :: "r"(cached_ptr), "r"(0)); + + volatile u8* uncached_ptr = reinterpret_cast(MEM_K0_TO_K1(cached_ptr)); + + UnalignedStoresTest(uncached_ptr - 16, 1, false); + UnalignedStoresTest(uncached_ptr - 16, 2, false); + UnalignedStoresTest(uncached_ptr - 16, 4, false); + + delete[] memory_allocation; + + network_printf("Shutting down...\n"); + network_shutdown(); + + return 0; +} From 8716547583eb264c5931fc52863e38014f6d7882 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 26 Jul 2021 11:38:06 +0200 Subject: [PATCH 2/3] Ensure that allocated memory is in MEM1 --- common/Align.h | 24 +++++++++++++++++++++++ cputest/unaligned_stores.cpp | 37 +++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 common/Align.h diff --git a/common/Align.h b/common/Align.h new file mode 100644 index 0000000..8e51b8a --- /dev/null +++ b/common/Align.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: CC0-1.0 + +#pragma once + +#include +#include + +namespace Common +{ +template +constexpr T AlignUp(T value, size_t size) +{ + static_assert(std::is_unsigned(), "T must be an unsigned value."); + return static_cast(value + (size - value % size) % size); +} + +template +constexpr T AlignDown(T value, size_t size) +{ + static_assert(std::is_unsigned(), "T must be an unsigned value."); + return static_cast(value - value % size); +} + +} // namespace Common diff --git a/cputest/unaligned_stores.cpp b/cputest/unaligned_stores.cpp index ffae0b8..25fdd23 100644 --- a/cputest/unaligned_stores.cpp +++ b/cputest/unaligned_stores.cpp @@ -6,12 +6,14 @@ #include #include +#include "common/Align.h" #include "common/BitUtils.h" #include "common/hwtests.h" // This test covers the behavior described in https://bugs.dolphin-emu.org/issues/12565 -constexpr size_t PAGE_SIZE = 4096; +constexpr uintptr_t PAGE_SIZE = 4096; +constexpr uintptr_t CACHE_LINE_SIZE = 32; static volatile u32* const s_pi_reg = reinterpret_cast(0xCC003000); @@ -157,6 +159,15 @@ static void Initialize() VIDEO_WaitVSync(); } +// Get a pointer to a 64-byte buffer with a page boundary in the middle +u8* GetMEM1Buffer() +{ + const uintptr_t mem1_lo = reinterpret_cast(SYS_GetArena1Lo()); + const uintptr_t page_boundary = Common::AlignUp(mem1_lo + CACHE_LINE_SIZE, PAGE_SIZE); + SYS_SetArena1Lo(reinterpret_cast(page_boundary + CACHE_LINE_SIZE)); + return reinterpret_cast(page_boundary - CACHE_LINE_SIZE); +} + int main() { Initialize(); @@ -165,28 +176,24 @@ int main() IRQ_Request(IRQ_PI_ERROR, PIErrorHandler, nullptr); __UnmaskIrq(IM_PI_ERROR); - // Get a pointer to a page boundary, with at least 32 bytes available on each side of the boundary network_printf("Allocating memory...\n"); - u8* memory_allocation = new u8[PAGE_SIZE + 32 * 4]; + u8* memory_allocation = GetMEM1Buffer(); - volatile u8* cached_ptr = reinterpret_cast( - reinterpret_cast(memory_allocation - 32) & ~(PAGE_SIZE - 1)); + volatile u8* cached_ptr = reinterpret_cast(memory_allocation + 16); - UnalignedStoresTest(cached_ptr - 16, 1, true); - UnalignedStoresTest(cached_ptr - 16, 2, true); - UnalignedStoresTest(cached_ptr - 16, 4, true); + UnalignedStoresTest(cached_ptr, 1, true); + UnalignedStoresTest(cached_ptr, 2, true); + UnalignedStoresTest(cached_ptr, 4, true); network_printf("Invalidating cache...\n"); - asm volatile("dcbi %0, %1" :: "r"(cached_ptr), "r"(-32)); - asm volatile("dcbi %0, %1" :: "r"(cached_ptr), "r"(0)); + asm volatile("dcbi %0, %1" :: "r"(memory_allocation), "r"(0)); + asm volatile("dcbi %0, %1" :: "r"(memory_allocation), "r"(32)); volatile u8* uncached_ptr = reinterpret_cast(MEM_K0_TO_K1(cached_ptr)); - UnalignedStoresTest(uncached_ptr - 16, 1, false); - UnalignedStoresTest(uncached_ptr - 16, 2, false); - UnalignedStoresTest(uncached_ptr - 16, 4, false); - - delete[] memory_allocation; + UnalignedStoresTest(uncached_ptr, 1, false); + UnalignedStoresTest(uncached_ptr, 2, false); + UnalignedStoresTest(uncached_ptr, 4, false); network_printf("Shutting down...\n"); network_shutdown(); From 0f483ae7d04e090ec5e801418877c791207b6757 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 26 Jul 2021 17:10:43 +0200 Subject: [PATCH 3/3] Test uncached sthbrx/stwbrx too --- cputest/unaligned_stores.cpp | 49 ++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/cputest/unaligned_stores.cpp b/cputest/unaligned_stores.cpp index 25fdd23..06f8d55 100644 --- a/cputest/unaligned_stores.cpp +++ b/cputest/unaligned_stores.cpp @@ -24,18 +24,24 @@ static u32 Read(volatile u8* ptr) return *reinterpret_cast(ptr); } -static void Write(volatile u8* ptr, u32 value, u32 size) +static void Write(volatile u8* ptr, u32 value, u32 size, bool swap) { switch (size) { case 4: - asm volatile("stw %0, 0(%1)" :: "r"(value), "r"(ptr)); + if (swap) + asm volatile("stwbrx %0, 0, %1" :: "r"(value), "r"(ptr)); + else + asm volatile("stwx %0, 0, %1" :: "r"(value), "r"(ptr)); break; case 2: - asm volatile("sth %0, 0(%1)" :: "r"(value), "r"(ptr)); + if (swap) + asm volatile("sthbrx %0, 0, %1" :: "r"(value), "r"(ptr)); + else + asm volatile("sthx %0, 0, %1" :: "r"(value), "r"(ptr)); break; case 1: - asm volatile("stb %0, 0(%1)" :: "r"(value), "r"(ptr)); + asm volatile("stbx %0, 0, %1" :: "r"(value), "r"(ptr)); break; } } @@ -53,23 +59,24 @@ static void WriteWithSimulatedQuirks(uintptr_t alignment, volatile u8* ptr, u32 for (uintptr_t i = 0; i < count; i += 8) { - Write(ptr - misalignment_64 + i, value, sizeof(u32)); - Write(ptr - misalignment_64 + i + 4, value, sizeof(u32)); + Write(ptr - misalignment_64 + i, value, sizeof(u32), false); + Write(ptr - misalignment_64 + i + 4, value, sizeof(u32), false); } } else { - Write(ptr, value, size); + Write(ptr, value, size, false); } } -static void UnalignedStoresTest(volatile u8* ptr, u32 size, bool cached_memory) +static void UnalignedStoresTest(volatile u8* ptr, u32 size, bool cached_memory, bool swap) { - network_printf("Starting test using ptr 0x%" PRIxPTR ", size %u\n", - reinterpret_cast(ptr), size); + network_printf("Starting test using ptr 0x%" PRIxPTR ", size %u, swap %u\n", + reinterpret_cast(ptr), size, swap); volatile u8 reference_buffer[32]; const u32 word = 0x12345678; + const u32 swapped_word = swap ? (size == 2 ? 0x12347856 : 0x78563412) : word; for (size_t i = 0; i <= 28; ++i) { @@ -80,12 +87,12 @@ static void UnalignedStoresTest(volatile u8* ptr, u32 size, bool cached_memory) s_pi_error_occurred = false; // The actual write that we are testing the behavior of - Write(ptr + i, word, size); + Write(ptr + i, word, size, swap); if (cached_memory) - Write(reference_buffer + i, word, size); + Write(reference_buffer + i, swapped_word, size, false); else - WriteWithSimulatedQuirks(i, reference_buffer + i, word, size); + WriteWithSimulatedQuirks(i, reference_buffer + i, swapped_word, size); DO_TEST(std::equal(ptr, ptr + 32, reference_buffer), "{}-byte write to {} failed\n" @@ -181,9 +188,11 @@ int main() volatile u8* cached_ptr = reinterpret_cast(memory_allocation + 16); - UnalignedStoresTest(cached_ptr, 1, true); - UnalignedStoresTest(cached_ptr, 2, true); - UnalignedStoresTest(cached_ptr, 4, true); + UnalignedStoresTest(cached_ptr, 1, true, false); + UnalignedStoresTest(cached_ptr, 2, true, false); + UnalignedStoresTest(cached_ptr, 4, true, false); + UnalignedStoresTest(cached_ptr, 2, true, true); + UnalignedStoresTest(cached_ptr, 4, true, true); network_printf("Invalidating cache...\n"); asm volatile("dcbi %0, %1" :: "r"(memory_allocation), "r"(0)); @@ -191,9 +200,11 @@ int main() volatile u8* uncached_ptr = reinterpret_cast(MEM_K0_TO_K1(cached_ptr)); - UnalignedStoresTest(uncached_ptr, 1, false); - UnalignedStoresTest(uncached_ptr, 2, false); - UnalignedStoresTest(uncached_ptr, 4, false); + UnalignedStoresTest(uncached_ptr, 1, false, false); + UnalignedStoresTest(uncached_ptr, 2, false, false); + UnalignedStoresTest(uncached_ptr, 4, false, false); + UnalignedStoresTest(uncached_ptr, 2, false, true); + UnalignedStoresTest(uncached_ptr, 4, false, true); network_printf("Shutting down...\n"); network_shutdown();