Skip to content

Commit

Permalink
Misc fixes and implement osStopThread (partial), osEepromWrite, and o…
Browse files Browse the repository at this point in the history
…sEepromRead (#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
  • Loading branch information
Mr-Wiseguy authored Oct 27, 2024
1 parent 3e39c2e commit 9ee0e73
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 33 deletions.
6 changes: 4 additions & 2 deletions librecomp/include/librecomp/addresses.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
22 changes: 20 additions & 2 deletions librecomp/src/eep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions librecomp/src/pi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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];
}
}
Expand Down
23 changes: 19 additions & 4 deletions librecomp/src/recomp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
26 changes: 13 additions & 13 deletions ultramodern/src/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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;
}
32 changes: 22 additions & 10 deletions ultramodern/src/threads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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_);
Expand All @@ -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_) {
Expand Down

0 comments on commit 9ee0e73

Please sign in to comment.