mirror of
https://github.com/n64dev/cen64.git
synced 2024-06-20 21:17:58 -04:00
354 lines
9.4 KiB
C
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);
|
|
} |