mirror of
https://github.com/open-goal/jak-project
synced 2026-07-09 23:01:56 -04:00
Add a debug PS2 VM to the runtime (#401)
* update VS launch target params * remove redundant VS launch option * Add a debug PS2 VM to the runtime, currently only for the DMAC * Formatting * remove broken assert * Avoid weird buffer overflow bug * Test on `VIF0_DMA_BANK`! * Add a docstring * patch pointers for the other dma channels * patch DMAC pointer * remove dead leftover code * Change default return value for `get_vm_ptr`
This commit is contained in:
+2
-9
@@ -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",
|
||||
|
||||
+3
-1
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+54
-3
@@ -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
|
||||
|
||||
@@ -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<DmaCommonRegisters> dmac;
|
||||
Ptr<DmaChannelRegisters> dmac_ch[10];
|
||||
|
||||
void dmac_init_globals() {
|
||||
dmac = kmalloc(kdebugheap, sizeof(DmaCommonRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmac")
|
||||
.cast<DmaCommonRegisters>();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
dmac_ch[i] =
|
||||
kmalloc(kdebugheap, sizeof(DmaChannelRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmach")
|
||||
.cast<DmaChannelRegisters>();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VM
|
||||
@@ -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<DmaCommonRegisters> dmac;
|
||||
// array of pointers to DMAC channels (they are not stored contiguously)
|
||||
extern Ptr<DmaChannelRegisters> 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
|
||||
@@ -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 <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
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<std::mutex> lk(init_mutex);
|
||||
|
||||
vm_init_cv.wait(lk, [&] { return status == Status::Inited; });
|
||||
}
|
||||
|
||||
void wait_vm_dead() {
|
||||
std::unique_lock<std::mutex> 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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
+10
-1
@@ -232,4 +232,13 @@
|
||||
:method-count-assert 9
|
||||
:flag-assert #x90000000c
|
||||
:no-runtime-type ;; already constructed, don't do it again.
|
||||
)
|
||||
)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; vm functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; These are functions used by the OpenGOAL PS2 VM components
|
||||
;; These were made by us.
|
||||
|
||||
(define-extern vm-ptr (function int pointer))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user