diff --git a/tests/A32/test_coprocessor.cpp b/tests/A32/test_coprocessor.cpp new file mode 100644 index 00000000..75017b0b --- /dev/null +++ b/tests/A32/test_coprocessor.cpp @@ -0,0 +1,228 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2022 MerryMage + * SPDX-License-Identifier: 0BSD + */ + +#include + +#include + +#include "./testenv.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/interface/A32/a32.h" +#include "dynarmic/interface/A32/coprocessor.h" + +using namespace Dynarmic; + +struct CP15State { + u32 cp15_thread_uprw = 0; + u32 cp15_thread_uro = 0; + u32 cp15_flush_prefetch_buffer = 0; ///< dummy value + u32 cp15_data_sync_barrier = 0; ///< dummy value + u32 cp15_data_memory_barrier = 0; ///< dummy value +}; + +class TestCP15 final : public Dynarmic::A32::Coprocessor { +public: + using CoprocReg = Dynarmic::A32::CoprocReg; + + explicit TestCP15(CP15State&); + ~TestCP15() override; + + std::optional CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override; + CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override; + CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override; + CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override; + CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override; + std::optional CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, std::optional option) override; + std::optional CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, std::optional option) override; + +private: + CP15State& state; +}; + +using Callback = Dynarmic::A32::Coprocessor::Callback; +using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord; +using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords; + +TestCP15::TestCP15(CP15State& state) + : state(state) {} + +TestCP15::~TestCP15() = default; + +std::optional TestCP15::CompileInternalOperation([[maybe_unused]] bool two, [[maybe_unused]] unsigned opc1, [[maybe_unused]] CoprocReg CRd, [[maybe_unused]] CoprocReg CRn, [[maybe_unused]] CoprocReg CRm, [[maybe_unused]] unsigned opc2) { + return std::nullopt; +} + +CallbackOrAccessOneWord TestCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) { + if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) { + return Callback{ + [](void* user_arg, std::uint32_t, std::uint32_t) -> std::uint64_t { + CP15State& state = *reinterpret_cast(user_arg); + state.cp15_flush_prefetch_buffer = 1; + return 0; + }, + reinterpret_cast(&state), + }; + } + + if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) { + switch (opc2) { + case 4: + return Callback{ + [](void* user_arg, std::uint32_t, std::uint32_t) -> std::uint64_t { + CP15State& state = *reinterpret_cast(user_arg); + state.cp15_data_sync_barrier = 1; + return 0; + }, + reinterpret_cast(&state), + }; + case 5: + return Callback{ + [](void* user_arg, std::uint32_t, std::uint32_t) -> std::uint64_t { + CP15State& state = *reinterpret_cast(user_arg); + state.cp15_data_memory_barrier = 1; + return 0; + }, + reinterpret_cast(&state), + }; + default: + return std::monostate{}; + } + } + + if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) { + return &state.cp15_thread_uprw; + } + + return std::monostate{}; +} + +CallbackOrAccessTwoWords TestCP15::CompileSendTwoWords([[maybe_unused]] bool two, [[maybe_unused]] unsigned opc, [[maybe_unused]] CoprocReg CRm) { + return std::monostate{}; +} + +CallbackOrAccessOneWord TestCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) { + // TODO(merry): Privileged CP15 registers + + if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) { + switch (opc2) { + case 2: + return &state.cp15_thread_uprw; + case 3: + return &state.cp15_thread_uro; + default: + return std::monostate{}; + } + } + + return std::monostate{}; +} + +CallbackOrAccessTwoWords TestCP15::CompileGetTwoWords([[maybe_unused]] bool two, [[maybe_unused]] unsigned opc, [[maybe_unused]] CoprocReg CRm) { + return std::monostate{}; +} + +std::optional TestCP15::CompileLoadWords([[maybe_unused]] bool two, [[maybe_unused]] bool long_transfer, [[maybe_unused]] CoprocReg CRd, [[maybe_unused]] std::optional option) { + return std::nullopt; +} + +std::optional TestCP15::CompileStoreWords([[maybe_unused]] bool two, [[maybe_unused]] bool long_transfer, [[maybe_unused]] CoprocReg CRd, [[maybe_unused]] std::optional option) { + return std::nullopt; +} + +static A32::UserConfig GetUserConfig(ArmTestEnv* testenv, CP15State& cp15_state) { + A32::UserConfig user_config; + user_config.optimizations &= ~OptimizationFlag::FastDispatch; + user_config.callbacks = testenv; + user_config.coprocessors[15] = std::make_unique(cp15_state); + return user_config; +} + +TEST_CASE("arm: Test coprocessor (Read TPIDRURO)", "[arm][A32]") { + ArmTestEnv test_env; + CP15State cp15_state; + A32::Jit jit{GetUserConfig(&test_env, cp15_state)}; + + cp15_state.cp15_thread_uro = 0xf00d; + cp15_state.cp15_thread_uprw = 0xcafe; + jit.Regs()[0] = 0xaaaa; + + test_env.code_mem = { + 0xee1d1f70, // mrc p15, 0, r1, c13, c0, 3 (Read TPIDRURO into R1) + 0xeafffffe, // b +#0 + }; + + jit.SetCpsr(0x000001d0); // User-mode + + test_env.ticks_left = 2; + jit.Run(); + + REQUIRE(jit.Regs()[1] == 0xf00d); +} + +TEST_CASE("arm: Test coprocessor (Read TPIDRURW)", "[arm][A32]") { + ArmTestEnv test_env; + CP15State cp15_state; + A32::Jit jit{GetUserConfig(&test_env, cp15_state)}; + + cp15_state.cp15_thread_uro = 0xf00d; + cp15_state.cp15_thread_uprw = 0xcafe; + jit.Regs()[0] = 0xaaaa; + + test_env.code_mem = { + 0xee1d1f50, // mrc p15, 0, r1, c13, c0, 2 (Read TPIDRURW into R1) + 0xeafffffe, // b +#0 + }; + + jit.SetCpsr(0x000001d0); // User-mode + + test_env.ticks_left = 2; + jit.Run(); + + REQUIRE(jit.Regs()[1] == 0xcafe); +} + +TEST_CASE("arm: Test coprocessor (Write TPIDRURW)", "[arm][A32]") { + ArmTestEnv test_env; + CP15State cp15_state; + A32::Jit jit{GetUserConfig(&test_env, cp15_state)}; + + cp15_state.cp15_thread_uro = 0xf00d; + cp15_state.cp15_thread_uprw = 0xcafe; + jit.Regs()[0] = 0xaaaa; + + test_env.code_mem = { + 0xee0d0f50, // mcr p15, 0, r0, c13, c0, 2 (Write R0 into TPIDRURW) + 0xeafffffe, // b +#0 + }; + + jit.SetCpsr(0x000001d0); // User-mode + + test_env.ticks_left = 2; + jit.Run(); + + REQUIRE(cp15_state.cp15_thread_uprw == 0xaaaa); +} + +TEST_CASE("arm: Test coprocessor (DMB)", "[arm][A32]") { + ArmTestEnv test_env; + CP15State cp15_state; + A32::Jit jit{GetUserConfig(&test_env, cp15_state)}; + + cp15_state.cp15_thread_uro = 0xf00d; + cp15_state.cp15_thread_uprw = 0xcafe; + jit.Regs()[0] = 0xaaaa; + + test_env.code_mem = { + 0xee070fba, // mcr p15, 0, r0, c7, c10, 5 (Data Memory Barrier) + 0xeafffffe, // b +#0 + }; + + jit.SetCpsr(0x000001d0); // User-mode + + test_env.ticks_left = 2; + jit.Run(); + + REQUIRE(cp15_state.cp15_data_memory_barrier == 1); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8ed3bcc0..6587e606 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,7 @@ if ("A32" IN_LIST DYNARMIC_FRONTENDS) target_sources(dynarmic_tests PRIVATE A32/test_arm_disassembler.cpp A32/test_arm_instructions.cpp + A32/test_coprocessor.cpp A32/test_thumb_instructions.cpp A32/testenv.h decoder_tests.cpp