cen64/device/device.c
2021-01-10 17:07:21 -07:00

354 lines
9.4 KiB
C

//
// device.c: CEN64 device container.
//
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
// Copyright (C) 2015, Tyler J. Stachecki.
//
// This file is subject to the terms and conditions defined in
// 'LICENSE', which is part of this source code package.
//
#include "common.h"
#include "device/device.h"
#include "device/netapi.h"
#include "fpu/fpu.h"
#include "gl_window.h"
#include "os/common/rom_file.h"
#include "os/common/save_file.h"
#include "bus/controller.h"
#include "ai/controller.h"
#include "dd/controller.h"
#include "pi/controller.h"
#include "ri/controller.h"
#include "si/controller.h"
#include "rsp/cpu.h"
#include "thread.h"
#include "vi/controller.h"
#include "vr4300/interface.h"
#include <setjmp.h>
#include <limits.h>
cen64_cold int angrylion_rdp_init(struct cen64_device *device);
cen64_cold static int device_debug_spin(struct cen64_device *device);
cen64_cold static int device_multithread_spin(struct cen64_device *device);
cen64_flatten cen64_hot static int device_spin(struct cen64_device *device);
cen64_flatten cen64_hot static CEN64_THREAD_RETURN_TYPE run_rcp_thread(void *);
cen64_flatten cen64_hot static CEN64_THREAD_RETURN_TYPE run_vr4300_thread(void *);
// Creates and initializes a device.
struct cen64_device *device_create(struct cen64_device *device,
const struct rom_file *ddipl, const struct dd_variant *dd_variant,
const struct rom_file *ddrom,
const struct rom_file *pifrom, const struct rom_file *cart,
const struct save_file *eeprom, const struct save_file *sram,
const struct save_file *flashram, struct is_viewer *is,
const struct controller *controller,
bool no_audio, bool no_video, bool profiling) {
// Allocate memory for VR4300
if ((device->vr4300 = vr4300_alloc()) == NULL) {
debug("create_device: Failed to allocate memory for the VR4300.\n");
return NULL;
}
// Initialize the bus.
device->bus.ai = &device->ai;
device->bus.dd = &device->dd;
device->bus.pi = &device->pi;
device->bus.ri = &device->ri;
device->bus.si = &device->si;
device->bus.vi = &device->vi;
device->bus.rdp = &device->rdp;
device->bus.rsp = &device->rsp;
device->bus.vr4300 = device->vr4300;
// Initialize the bus.
if (bus_init(&device->bus, dd_variant != NULL)) {
debug("create_device: Failed to initialize the bus.\n");
return NULL;
}
// Initialize the AI.
if (ai_init(&device->ai, &device->bus, no_audio)) {
debug("create_device: Failed to initialize the AI.\n");
return NULL;
}
// Initialize the DD.
if (dd_init(&device->dd, &device->bus,
ddipl->ptr, ddrom->ptr, ddrom->size)) {
debug("create_device: Failed to initialize the DD.\n");
return NULL;
}
// Initialize the PI.
if (pi_init(&device->pi, &device->bus, cart->ptr, cart->size, sram, flashram, is)) {
debug("create_device: Failed to initialize the PI.\n");
return NULL;
}
// Initialize the RI.
if (ri_init(&device->ri, &device->bus)) {
debug("create_device: Failed to initialize the RI.\n");
return NULL;
}
// Initialize the SI.
if (si_init(&device->si, &device->bus, pifrom->ptr,
cart->ptr, dd_variant, eeprom->ptr, eeprom->size,
controller)) {
debug("create_device: Failed to initialize the SI.\n");
return NULL;
}
// Initialize the VI.
if (vi_init(&device->vi, &device->bus, no_video)) {
debug("create_device: Failed to initialize the VI.\n");
return NULL;
}
// Initialize the RDP.
if (rdp_init(&device->rdp, &device->bus)) {
debug("create_device: Failed to initialize the RDP.\n");
return NULL;
}
// Initialize the RSP.
if (rsp_init(&device->rsp, &device->bus)) {
debug("create_device: Failed to initialize the RSP.\n");
return NULL;
}
// Initialize the VR4300.
if (vr4300_init(device->vr4300, &device->bus, profiling)) {
debug("create_device: Failed to initialize the VR4300.\n");
return NULL;
}
angrylion_rdp_init(device);
return device;
}
// Cleans up memory allocated for the device.
void device_destroy(struct cen64_device *device, const char *cart_path) {
vr4300_free(device->vr4300);
rsp_destroy(&device->rsp);
// Save profiling data, if any
if (cart_path && has_profile_samples(device->vr4300)) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s.profile", cart_path);
path[PATH_MAX - 1] = '\0';
FILE *f = fopen(path, "w");
if (!f) {
printf("Can't open %s\n", path);
return;
}
uint32_t i;
for (i = 0; i < 8 * 1024 * 1024; i++) {
const uint64_t sample = get_profile_sample(device->vr4300, i);
const uint64_t l1d_sample = get_profile_sample(device->vr4300, i + (8 * 1024 * 1024));
if (sample < 10 && l1d_sample < 10)
continue;
fprintf(f, "%x %lu %lu\n", i + 0x80000000, sample, l1d_sample);
}
fclose(f);
}
}
// Called when we should (probably?) leave simulation.
// After calling this function, we return to device_runmode_*.
void device_exit(struct bus_controller *bus) {
longjmp(bus->unwind_data, 1);
}
// Create a device and proceed to the main loop.
void device_run(struct cen64_device *device) {
fpu_state_t saved_fpu_state;
// TODO: Preserve host registers pinned to the device.
saved_fpu_state = fpu_get_state();
vr4300_cp1_init(device->vr4300);
rsp_late_init(&device->rsp);
// Spin the device until we return (from setjmp).
if (unlikely(device->debug_sfd > 0))
device_debug_spin(device);
else if (device->multithread)
device_multithread_spin(device);
else
device_spin(device);
// TODO: Restore host registers that were pinned.
fpu_set_state(saved_fpu_state);
}
CEN64_THREAD_RETURN_TYPE run_rcp_thread(void *opaque) {
struct cen64_device *device = (struct cen64_device *) opaque;
if (setjmp(device->bus.unwind_data))
return CEN64_THREAD_RETURN_VAL;
while (likely(device->running)) {
unsigned i;
for (i = 0; i < 6250; i++) {
rsp_cycle(&device->rsp);
vi_cycle(&device->vi);
}
// Sync up with the VR4300 thread.
cen64_mutex_lock(&device->sync_mutex);
if (!device->other_thread_is_waiting) {
device->other_thread_is_waiting = true;
cen64_cv_wait(&device->sync_cv, &device->sync_mutex);
}
else {
device->other_thread_is_waiting = false;
cen64_mutex_unlock(&device->sync_mutex);
cen64_cv_signal(&device->sync_cv);
}
}
return CEN64_THREAD_RETURN_VAL;
}
CEN64_THREAD_RETURN_TYPE run_vr4300_thread(void *opaque) {
cen64_thread_setname(NULL, "vr4300");
struct cen64_device *device = (struct cen64_device *) opaque;
while (likely(device->running)) {
unsigned i, j;
for (i = 0; i < 6250 / 2; i++) {
for (j = 0; j < 2; j++) {
ai_cycle(&device->ai);
pi_cycle(&device->pi);
}
for (j = 0; j < 3; j++)
vr4300_cycle(device->vr4300);
}
// Sync up with the RCP thread.
cen64_mutex_lock(&device->sync_mutex);
if (!device->other_thread_is_waiting) {
device->other_thread_is_waiting = true;
cen64_cv_wait(&device->sync_cv, &device->sync_mutex);
}
else {
device->other_thread_is_waiting = false;
cen64_mutex_unlock(&device->sync_mutex);
cen64_cv_signal(&device->sync_cv);
}
}
return CEN64_THREAD_RETURN_VAL;
}
// Continually cycles the device until setjmp returns.
int device_multithread_spin(struct cen64_device *device) {
cen64_thread vr4300_thread;
device->other_thread_is_waiting = false;
if (cen64_mutex_create(&device->sync_mutex)) {
printf("Failed to create the synchronization mutex.\n");
return 1;
}
if (cen64_cv_create(&device->sync_cv)) {
printf("Failed to create the synchronization CV.\n");
cen64_mutex_destroy(&device->sync_mutex);
return 1;
}
if (cen64_thread_create(&vr4300_thread, run_vr4300_thread, device)) {
printf("Failed to create the VR4300 thread.\n");
cen64_cv_destroy(&device->sync_cv);
cen64_mutex_destroy(&device->sync_mutex);
return 1;
}
cen64_thread_setname(vr4300_thread, "vr4300");
run_rcp_thread(device);
cen64_thread_join(&vr4300_thread);
cen64_cv_destroy(&device->sync_cv);
cen64_mutex_destroy(&device->sync_mutex);
return 0;
}
// Continually cycles the device until setjmp returns.
int device_spin(struct cen64_device *device) {
if (setjmp(device->bus.unwind_data))
return 1;
while (likely(device->running)) {
unsigned i;
for (i = 0; i < 2; i++) {
vr4300_cycle(device->vr4300);
rsp_cycle(&device->rsp);
ai_cycle(&device->ai);
pi_cycle(&device->pi);
vi_cycle(&device->vi);
}
vr4300_cycle(device->vr4300);
}
return 0;
}
// Continually cycles the device until setjmp returns.
int device_debug_spin(struct cen64_device *device) {
struct vr4300_stats* vr4300_stats = vr4300_stats_alloc();
// Prepare stats, set a breakpoint @ VR4300 IPL vector.
netapi_debug_wait(device->debug_sfd, device);
if (setjmp(device->bus.unwind_data))
return 1;
while (likely(device->running)) {
unsigned i;
for (i = 0; i < 2; i++) {
vr4300_cycle(device->vr4300);
rsp_cycle(&device->rsp);
ai_cycle(&device->ai);
pi_cycle(&device->pi);
vi_cycle(&device->vi);
vr4300_cycle_extra(device->vr4300, vr4300_stats);
}
vr4300_cycle(device->vr4300);
vr4300_cycle_extra(device->vr4300, vr4300_stats);
}
vr4300_stats_free(vr4300_stats);
return 0;
}
cen64_cold void device_connect_debugger(struct cen64_device *device, void* break_handler_data, vr4300_debug_break_handler break_handler) {
vr4300_connect_debugger(device->vr4300, break_handler_data, break_handler);
}