diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json index bf381869f3..c4599d8d9f 100644 --- a/.vs/launch.vs.json +++ b/.vs/launch.vs.json @@ -26,21 +26,14 @@ "project" : "CMakeLists.txt", "projectTarget" : "gk.exe (bin\\gk.exe)", "name" : "Run Runtime (no kernel)", - "args" : [ "-fakeiso", "-debug", "-nokernel" ] + "args" : [ "-fakeiso", "-debug", "-nokernel", "-v", "-nodisplay" ] }, { "type" : "default", "project" : "CMakeLists.txt", "projectTarget" : "gk.exe (bin\\gk.exe)", "name" : "Run Runtime (with kernel)", - "args" : [ "-fakeiso", "-debug" ] - }, - { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "gk.exe (bin\\gk.exe)", - "name" : "Run Runtime (no kernel) (verbose)", - "args" : [ "-fakeiso", "-debug", "-nokernel", "-v" ] + "args" : [ "-fakeiso", "-debug", "-v", "-nodisplay" ] }, { "type" : "default", diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 10ae56bf6a..17e31fc7d3 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -72,7 +72,9 @@ set(RUNTIME_SOURCE overlord/ssound.cpp overlord/stream.cpp graphics/gfx.cpp - graphics/display.cpp) + graphics/display.cpp + system/vm/dmac.cpp + system/vm/vm.cpp) # we build the runtime as a static library. diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index ba708d8928..21317bb0d0 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -29,6 +29,8 @@ #include "game/sce/libpad.h" #include "common/symbols.h" #include "common/log/log.h" + +#include "game/system/vm/vm.h" using namespace ee; /*! @@ -369,6 +371,12 @@ int ShutdownMachine() { CloseListener(); ShutdownSound(); ShutdownGoalProto(); + + // OpenGOAL only - kill ps2 VM + if (VM::use) { + VM::vm_kill(); + } + Msg(6, "kernel: machine shutdown\n"); return 0; } diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index 513ecdfd07..d5e0c1dff9 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -23,6 +23,8 @@ #include "common/log/log.h" #include "common/util/Timer.h" +#include "game/system/vm/vm.h" + //! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a //! warning message if EnableMethodSet = 1 u32 FastLink; @@ -1963,6 +1965,12 @@ s32 InitHeapAndSymbol() { // set *boot-video-mode* intern_from_c("*boot-video-mode*")->value = 0; + // OpenGOAL only - init ps2 VM + if (VM::use) { + make_function_symbol_from_c("vm-ptr", (void*)VM::get_vm_ptr); + VM::vm_init(); + } + lg::info("Initialized GOAL heap in {:.2} ms", heap_init_timer.getMs()); // load the kernel! // todo, remove MasterUseKernel diff --git a/game/runtime.cpp b/game/runtime.cpp index 507ec3a9ae..264711b43a 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -51,6 +51,9 @@ #include "game/graphics/gfx.h" #include "game/graphics/display.h" +#include "game/system/vm/vm.h" +#include "game/system/vm/dmac.h" + #include "common/goal_constants.h" #include "common/cross_os_debug/xdbg.h" @@ -229,20 +232,61 @@ void iop_runner(SystemThreadInterface& iface) { } } // namespace +/*! + * SystemThread function for running NothingTM. + */ +void null_runner(SystemThreadInterface& iface) { + iface.initialization_complete(); + + return; +} + +/*! + * SystemThread function for running the PS2 DMA controller. + * This does not actually emulate the DMAC, it only fakes its existence enough that we can debug + * the DMA packets the original game sends. The port will replace all DMAC code. + */ +void dmac_runner(SystemThreadInterface& iface) { + VM::subscribe_component(); + + VM::dmac_init_globals(); + + iface.initialization_complete(); + + while (!iface.get_want_exit() && !VM::vm_want_exit()) { + for (int i = 0; i < 10; ++i) { + if (VM::dmac_ch[i]->chcr.str) { + lg::info("DMA detected on channel {}, clearing", i); + VM::dmac_ch[i]->chcr.str = 0; + } + } + // avoid running the DMAC on full blast (this does not sync to its clockrate) + std::this_thread::sleep_for(std::chrono::microseconds(50)); + } + + VM::unsubscribe_component(); + + return; +} + /*! * Main function to launch the runtime. - * Arguments are currently ignored. + * GOAL kernel arguments are currently ignored. */ u32 exec_runtime(int argc, char** argv) { g_argc = argc; g_argv = argv; g_main_thread_id = std::this_thread::get_id(); + // parse opengoal arguments bool enable_display = true; for (int i = 1; i < argc; i++) { - if (std::string("-nodisplay") == argv[i]) { + if (std::string("-nodisplay") == argv[i]) { // disable video display enable_display = false; - break; + } else if (std::string("-vm") == argv[i]) { // enable debug ps2 VM + VM::use = true; + } else if (std::string("-novm") == argv[i]) { // disable debug ps2 VM + VM::use = false; } } @@ -253,15 +297,22 @@ u32 exec_runtime(int argc, char** argv) { ee::LIBRARY_INIT_sceSif(); // step 2: system prep + VM::vm_prepare(); // our fake ps2 VM needs to be prepared SystemThreadManager tm; auto& deci_thread = tm.create_thread("DMP"); auto& iop_thread = tm.create_thread("IOP"); auto& ee_thread = tm.create_thread("EE"); + auto& vm_dmac_thread = tm.create_thread("VM-DMAC"); // step 3: start the EE! iop_thread.start(iop_runner); ee_thread.start(ee_runner); deci_thread.start(deci2_runner); + if (VM::use) { + vm_dmac_thread.start(dmac_runner); + } else { + vm_dmac_thread.start(null_runner); + } // step 4: wait for EE to signal a shutdown. meanwhile, run video loop on main thread. // TODO relegate this to its own function diff --git a/game/system/vm/dmac.cpp b/game/system/vm/dmac.cpp new file mode 100644 index 0000000000..0b3751e10d --- /dev/null +++ b/game/system/vm/dmac.cpp @@ -0,0 +1,29 @@ +/*! + * @file dmac.cpp + * DMAC implementation for the "PS2 virtual machine". + * Not meant to work as a full DMAC emulator, just enough to inspect DMA packets. + */ + +#include "dmac.h" +#include "vm.h" +#include "game/runtime.h" +#include "game/kernel/kmalloc.h" +#include "common/log/log.h" + +namespace VM { + +Ptr dmac; +Ptr dmac_ch[10]; + +void dmac_init_globals() { + dmac = kmalloc(kdebugheap, sizeof(DmaCommonRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmac") + .cast(); + + for (int i = 0; i < 10; ++i) { + dmac_ch[i] = + kmalloc(kdebugheap, sizeof(DmaChannelRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmach") + .cast(); + } +} + +} // namespace VM diff --git a/game/system/vm/dmac.h b/game/system/vm/dmac.h new file mode 100644 index 0000000000..3c6b0aee57 --- /dev/null +++ b/game/system/vm/dmac.h @@ -0,0 +1,77 @@ +#pragma once + +/*! + * @file dmac.h + * DMAC implementation for the "PS2 virtual machine". + * Not meant to work as a full DMAC emulator, just enough to inspect DMA packets. + */ + +#ifndef VM_DMAC_H +#define VM_DMAC_H + +#include "common/common_types.h" +#include "game/kernel/Ptr.h" + +namespace VM { + +/*! + * DMA channel CHCR register. The only one we care about right now. + */ +struct DmaChcr { + u32 dir : 1; + u32 _pad1 : 1; + u32 mod : 2; + u32 asp : 2; + u32 tte : 1; + u32 tie : 1; + u32 str : 1; + u32 _pad2 : 7; + u16 tag : 16; +}; + +/*! + * Layout of the DMA channel registers in EE memory. For simplicity's sake, all are included, + * however, each channel may not actually have some of these registers. + * They are 16-byte aligned. + */ +struct alignas(16) DmaChannelRegisters { + alignas(16) DmaChcr chcr; + alignas(16) u32 madr; + alignas(16) u32 qwc; + alignas(16) u32 tadr; + alignas(16) u32 asr0; + alignas(16) u32 asr1; + alignas(16) u128 _pad1; + alignas(16) u128 _pad2; + alignas(16) u32 sadr; +}; + +/*! + * Layout of the DMAC registers in EE memory. + * They are 16-byte aligned. + */ +struct alignas(16) DmaCommonRegisters { + alignas(16) u32 ctrl; + alignas(16) u32 stat; + alignas(16) u32 pcr; + alignas(16) u32 sqwc; + alignas(16) u32 rbsr; + alignas(16) u32 rbor; + alignas(16) u32 stadr; +}; + +// pointer to DMAC registers +extern Ptr dmac; +// array of pointers to DMAC channels (they are not stored contiguously) +extern Ptr dmac_ch[10]; + +// enum DmaChannel { VIF0, VIF1, GIF, fromIPU, toIPU, SIF0, SIF1, SIF2, fromSPR, toSPR }; + +static_assert(sizeof(DmaChannelRegisters) == 0x90, "DmaChannelRegisters wrong size"); +static_assert(alignof(DmaChannelRegisters) == 0x10, "DmaChannelRegisters unaligned"); + +void dmac_init_globals(); + +} // namespace VM + +#endif // VM_DMAC_H diff --git a/game/system/vm/vm.cpp b/game/system/vm/vm.cpp new file mode 100644 index 0000000000..964d9543f7 --- /dev/null +++ b/game/system/vm/vm.cpp @@ -0,0 +1,125 @@ +/*! + * @file vm.cpp + * Base "PS2 virtual machine" code. + * Simulates the existence of select PS2 components, for inspection & debugging. + * Not an emulator! + */ + +#include "vm.h" +#include "dmac.h" +#include "common/log/log.h" +#include "game/kernel/kscheme.h" +#include +#include + +namespace VM { + +bool use = true; // enable VM by default, since we're debugging right now + +namespace { +Status status; +std::condition_variable vm_init_cv; +std::condition_variable vm_dead_cv; +std::mutex init_mutex; +std::mutex dead_mutex; + +int components = 0; +} // namespace + +void wait_vm_init() { + std::unique_lock lk(init_mutex); + + vm_init_cv.wait(lk, [&] { return status == Status::Inited; }); +} + +void wait_vm_dead() { + std::unique_lock lk(dead_mutex); + + vm_dead_cv.wait(lk, [&] { return status == Status::Dead; }); +} + +bool vm_want_exit() { + return status == Status::Kill || status == Status::Dead; +} + +void vm_prepare() { + lg::debug("[VM] Preparing..."); + status = Status::Uninited; + lg::debug("[VM] Prepared"); +} + +void vm_init() { + if (status != Status::Uninited) { + lg::warn("[VM] unexpected status {}", status); + } + + lg::debug("[VM] Inited"); + status = Status::Inited; + vm_init_cv.notify_all(); +} + +void vm_kill() { + lg::debug("[VM] Killing"); + + status = Status::Kill; + + // stall caller until VM is done dying + wait_vm_dead(); +} + +void subscribe_component() { + ++components; + + // stall component until VM is ready + if (status == Status::Uninited) { + wait_vm_init(); + } +} + +void unsubscribe_component() { + --components; + + // the VM is "killed" when there's no more components running + if (status == Status::Kill && components == 0) { + status = Status::Dead; + vm_dead_cv.notify_all(); + } +} + +/*! + * Return the GOAL pointer to a specified PS2 VM component based on the EE address. + */ +u64 get_vm_ptr(u32 ptr) { + // currently, only DMAC and DMA channel banks are implemented. add more as necessary. + if (ptr == 0x10008000) { + return VM::dmac_ch[0].offset; + } else if (ptr == 0x10009000) { + return VM::dmac_ch[1].offset; + } else if (ptr == 0x1000a000) { + return VM::dmac_ch[2].offset; + } else if (ptr == 0x1000b000) { + return VM::dmac_ch[3].offset; + } else if (ptr == 0x1000b400) { + return VM::dmac_ch[4].offset; + } else if (ptr == 0x1000c000) { + return VM::dmac_ch[5].offset; + } else if (ptr == 0x1000c400) { + return VM::dmac_ch[6].offset; + } else if (ptr == 0x1000c800) { + return VM::dmac_ch[7].offset; + } else if (ptr == 0x1000d000) { + return VM::dmac_ch[8].offset; + } else if (ptr == 0x1000d400) { + return VM::dmac_ch[9].offset; + } else if (ptr == 0x1000e000) { + return VM::dmac.offset; + } else { + // return zero, using this result will segfault GOAL! + // we could die immediately, but it might be worth it to keep going just on the off chance more + // errors are reported, and not just only this one. + lg::error("unknown EE register for VM at #x{:08x}", ptr); + return 0; + } +} + +} // namespace VM diff --git a/game/system/vm/vm.h b/game/system/vm/vm.h new file mode 100644 index 0000000000..13c921ae9e --- /dev/null +++ b/game/system/vm/vm.h @@ -0,0 +1,37 @@ +#pragma once + +/*! + * @file vm.h + * Base "PS2 virtual machine" code. + * Simulates the existence of select PS2 components, for inspection & debugging. + * Not an emulator! + */ + +#ifndef VM_H +#define VM_H + +#include "common/common_types.h" + +namespace VM { + +extern bool use; + +enum class Status { Disabled, Uninited, Inited, Kill, Dead }; + +void wait_vm_init(); +void wait_vm_dead(); + +bool vm_want_exit(); + +void vm_prepare(); +void vm_init(); +void vm_kill(); + +void subscribe_component(); +void unsubscribe_component(); + +u64 get_vm_ptr(u32 ptr); + +} // namespace VM + +#endif // VM_H diff --git a/goal_src/engine/dma/dma-h.gc b/goal_src/engine/dma/dma-h.gc index 26a80f4611..c136e70096 100644 --- a/goal_src/engine/dma/dma-h.gc +++ b/goal_src/engine/dma/dma-h.gc @@ -19,7 +19,7 @@ ;; When INSTANT_DMA is enabled, these functions will return this value. (defglobalconstant INSTANT_DMA_COUNT 123) -;; DMA Channel Control Register. This starts the DMA and can be checked to see if it done. +;; DMA Channel Control Register. This starts the DMA and can be checked to see if it's done. ;; There is one CHCR per DMA channel. (deftype dma-chcr (uint32) ((dir uint8 :offset 0 :size 1) ;; 1 - from memory @@ -90,12 +90,12 @@ ;; These addresses are the location of DMA banks for each channel. ;; These do not exist in OpenGOAL. -(defconstant VIF0_DMA_BANK (the dma-bank-vif #x10008000)) -(defconstant VIF1_DMA_BANK (the dma-bank-vif #x10009000)) -(defconstant GIF_DMA_BANK (the dma-bank #x1000a000)) +(defconstant VIF0_DMA_BANK (the dma-bank-vif (get-vm-ptr #x10008000))) +(defconstant VIF1_DMA_BANK (the dma-bank-vif (get-vm-ptr #x10009000))) +(defconstant GIF_DMA_BANK (the dma-bank (get-vm-ptr #x1000a000))) ;; ipuFrom, ipTop, sif0, sif1, sif2 believed unused. -(defconstant SPR_FROM_BANK (the dma-bank-spr #x1000d000)) -(defconstant SPR_TO_BANK (the dma-bank-spr #x1000d400)) +(defconstant SPR_FROM_BANK (the dma-bank-spr (get-vm-ptr #x1000d000))) +(defconstant SPR_TO_BANK (the dma-bank-spr (get-vm-ptr #x1000d400))) (defconstant VU0_DATA_MEM_MAP (the (pointer uint32) #x11004000)) (defconstant VU1_DATA_MEM_MAP (the (pointer uint32) #x1100c000)) @@ -149,7 +149,7 @@ :flag-assert #x900001594 ) -(defconstant DMA_CONTROL_BANK (the dma-bank-control #x1000e000)) +(defconstant DMA_CONTROL_BANK (the dma-bank-control (get-vm-ptr #x1000e000))) ;; Seems to be unused. The vu-function type is used instead. (deftype vu-code-block (basic) diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index fb4afada27..3062faf7a6 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -232,4 +232,13 @@ :method-count-assert 9 :flag-assert #x90000000c :no-runtime-type ;; already constructed, don't do it again. - ) \ No newline at end of file + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; vm functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; These are functions used by the OpenGOAL PS2 VM components +;; These were made by us. + +(define-extern vm-ptr (function int pointer)) diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 1c0a58f741..59e15998d6 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -21,6 +21,20 @@ (defconstant MEM_USAGE_METHOD_ID 8) (defglobalconstant PC_PORT #t) +(defglobalconstant USE_VM #t) + +(defmacro get-vm-ptr (ptr) + "Turn an EE register address into a valid PS2 VM address" + + `(#cond + (USE_VM + (vm-ptr ,ptr) + ) + (#t + ,ptr + ) + ) + ) ;; distance from a symbol pointer to a (pointer string) ;; this relies on the memory layout of the symbol table