mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-04-26 20:11:52 +00:00
Misc fixes and implement osStopThread (partial), osEepromWrite, and osEepromRead (#67)
* Fix rdram allocation to only allow kseg0 accesses * Implement osEepromWrite and osEepromRead, fix implicit casting error in save_write * Partially implement osStopThread * Fix audio allowing games to queue 0 samples * Remove commented out line
This commit is contained in:
parent
3e39c2ec34
commit
9ee0e7369a
6 changed files with 80 additions and 33 deletions
|
|
@ -6,8 +6,10 @@
|
|||
#include "librecomp/recomp.h"
|
||||
|
||||
namespace recomp {
|
||||
// 2GB (Addressable upper half of rdram)
|
||||
constexpr size_t mem_size = 2U * 1024U * 1024U * 1024U;
|
||||
// 512GB (kseg0 size)
|
||||
constexpr size_t mem_size = 512U * 1024U * 1024U;
|
||||
// 2GB (Addressable upper half of the address space)
|
||||
constexpr size_t allocation_size = 2048U * 1024U * 1024U;
|
||||
// We need a place in rdram to hold the PI handles, so pick an address in extended rdram
|
||||
constexpr int32_t cart_handle = 0x80800000;
|
||||
constexpr int32_t drive_handle = (int32_t)(cart_handle + sizeof(OSPiHandle));
|
||||
|
|
|
|||
|
|
@ -16,7 +16,16 @@ extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
|||
}
|
||||
|
||||
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = eeprom_block_size;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
|
@ -33,7 +42,16 @@ extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
|||
}
|
||||
|
||||
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = eeprom_block_size;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ void save_write_ptr(const void* in, uint32_t offset, uint32_t count) {
|
|||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
for (gpr i = 0; i < count; i++) {
|
||||
save_context.save_buffer[offset + i] = MEM_B(i, rdram_address);
|
||||
}
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t cou
|
|||
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
for (gpr i = 0; i < count; i++) {
|
||||
MEM_B(i, rdram_address) = save_context.save_buffer[offset + i];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,15 +609,30 @@ void recomp::start(
|
|||
}
|
||||
|
||||
// Allocate rdram without comitting it. Use a platform-specific virtual allocation function
|
||||
// that initializes to zero.
|
||||
// that initializes to zero. Protect the region above the memory size to catch accesses to invalid addresses.
|
||||
uint8_t* rdram;
|
||||
bool alloc_failed;
|
||||
#ifdef _WIN32
|
||||
rdram = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, mem_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
||||
rdram = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, allocation_size, MEM_COMMIT | MEM_RESERVE, PAGE_NOACCESS));
|
||||
DWORD old_protect = 0;
|
||||
alloc_failed = (rdram == nullptr);
|
||||
if (!alloc_failed) {
|
||||
// VirtualProtect returns 0 on failure.
|
||||
alloc_failed = (VirtualProtect(rdram, mem_size, PAGE_READWRITE, &old_protect) == 0);
|
||||
if (alloc_failed) {
|
||||
VirtualFree(rdram, 0, MEM_RELEASE);
|
||||
}
|
||||
}
|
||||
#else
|
||||
rdram = (uint8_t*)mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
rdram = (uint8_t*)mmap(NULL, allocation_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
alloc_failed = rdram == reinterpret_cast<uint8_t*>(MAP_FAILED);
|
||||
if (!alloc_failed) {
|
||||
// mprotect returns -1 on failure.
|
||||
alloc_failed = (mprotect(rdram, mem_size, PROT_READ | PROT_WRITE) == -1);
|
||||
if (alloc_failed) {
|
||||
munmap(rdram, allocation_size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (alloc_failed) {
|
||||
|
|
@ -659,7 +674,7 @@ void recomp::start(
|
|||
free_failed = (VirtualFree(rdram, 0, MEM_RELEASE) == 0);
|
||||
#else
|
||||
// munmap returns -1 on failure.
|
||||
free_failed = (munmap(rdram, mem_size) == -1);
|
||||
free_failed = (munmap(rdram, allocation_size) == -1);
|
||||
#endif
|
||||
|
||||
if (free_failed) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ void ultramodern::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_
|
|||
uint32_t sample_count = byte_count / sizeof(int16_t);
|
||||
|
||||
// Queue the swapped audio data.
|
||||
if (audio_callbacks.queue_samples) {
|
||||
if (sample_count > 0 && audio_callbacks.queue_samples) {
|
||||
audio_callbacks.queue_samples(TO_PTR(int16_t, audio_data_), sample_count);
|
||||
}
|
||||
}
|
||||
|
|
@ -52,16 +52,16 @@ uint32_t ultramodern::get_remaining_audio_bytes() {
|
|||
else {
|
||||
buffered_byte_count = 100;
|
||||
}
|
||||
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
|
||||
// there are enough samples even if the audio thread experiences a small amount of lag. This prevents
|
||||
// audio popping on games that use the buffered audio byte count to determine how many samples
|
||||
// to generate.
|
||||
uint32_t samples_per_vi = (sample_rate / 60);
|
||||
if (buffered_byte_count > static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) {
|
||||
buffered_byte_count -= static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi);
|
||||
}
|
||||
else {
|
||||
buffered_byte_count = 0;
|
||||
}
|
||||
return buffered_byte_count;
|
||||
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
|
||||
// there are enough samples even if the audio thread experiences a small amount of lag. This prevents
|
||||
// audio popping on games that use the buffered audio byte count to determine how many samples
|
||||
// to generate.
|
||||
uint32_t samples_per_vi = (sample_rate / 60);
|
||||
if (buffered_byte_count > static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) {
|
||||
buffered_byte_count -= static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi);
|
||||
}
|
||||
else {
|
||||
buffered_byte_count = 0;
|
||||
}
|
||||
return buffered_byte_count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ void ultramodern::set_native_thread_priority(ThreadPriority pri) {}
|
|||
#endif
|
||||
|
||||
void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context) {
|
||||
TO_PTR(OSThread, ultramodern::this_thread())->context->running.wait();
|
||||
thread_context->running.wait();
|
||||
// If this thread's context was replaced by another thread or deleted, destroy it again from its own context.
|
||||
// This will trigger thread cleanup instead.
|
||||
if (TO_PTR(OSThread, ultramodern::this_thread())->context != thread_context) {
|
||||
|
|
@ -198,7 +198,10 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry
|
|||
debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
|
||||
|
||||
// Wait until the thread is marked as running.
|
||||
wait_for_resumed(PASS_RDRAM thread_context);
|
||||
try {
|
||||
wait_for_resumed(PASS_RDRAM thread_context);
|
||||
} catch (ultramodern::thread_terminated& terminated) {
|
||||
}
|
||||
|
||||
// Make sure the thread wasn't replaced or destroyed before it was started.
|
||||
if (self->context == thread_context) {
|
||||
|
|
@ -228,11 +231,6 @@ extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) {
|
|||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
debug_printf("[os] Start Thread %d\n", t->id);
|
||||
|
||||
// Wait until the thread is initialized to indicate that it's ready to be started.
|
||||
t->context->initialized.wait();
|
||||
|
||||
debug_printf("[os] Thread %d is ready to be started\n", t->id);
|
||||
|
||||
// If this is a game thread, insert the new thread into the running queue and then check the running queue.
|
||||
if (thread_self) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM t_);
|
||||
|
|
@ -259,12 +257,26 @@ extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_f
|
|||
|
||||
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
|
||||
// Pass the context as an argument to the thread function to ensure that it can't get cleared before the thread captures its value.
|
||||
t->context = new UltraThreadContext{};
|
||||
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context};
|
||||
UltraThreadContext* context = new UltraThreadContext{};
|
||||
t->context = context;
|
||||
context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context};
|
||||
|
||||
// Wait until the thread is initialized to indicate that it's ready to be started.
|
||||
context->initialized.wait();
|
||||
debug_printf("[os] Thread %d is ready to be started\n", t->id);
|
||||
}
|
||||
|
||||
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
assert(false);
|
||||
if (t_ == NULLPTR) {
|
||||
t_ = thread_self;
|
||||
}
|
||||
// Check if the thread is stopping itself (arg is null or thread_self).
|
||||
if (t_ == thread_self) {
|
||||
ultramodern::run_next_thread_and_wait(PASS_RDRAM1);
|
||||
}
|
||||
else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue