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:
ManDude
2021-05-01 05:32:19 +01:00
committed by GitHub
parent fab73cab75
commit 8cc63ff35c
12 changed files with 374 additions and 21 deletions
+2 -9
View File
@@ -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
View File
@@ -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.
+8
View File
@@ -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;
}
+8
View File
@@ -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
View File
@@ -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
+29
View File
@@ -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
+77
View File
@@ -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
+125
View File
@@ -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
+37
View File
@@ -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
+7 -7
View File
@@ -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
View File
@@ -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))
+14
View File
@@ -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