This commit is contained in:
wheremyfoodat 2025-08-01 10:31:06 -05:00 committed by GitHub
commit 815fc5e90b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 96 additions and 20 deletions

View file

@ -59,6 +59,7 @@ class Kernel {
// Top 8 bits are the major version, bottom 8 are the minor version // Top 8 bits are the major version, bottom 8 are the minor version
u16 kernelVersion = 0; u16 kernelVersion = 0;
u64 nextScheduledWakeupTick = std::numeric_limits<u64>::max();
// Shows whether a reschedule will be need // Shows whether a reschedule will be need
bool needReschedule = false; bool needReschedule = false;
@ -95,6 +96,8 @@ class Kernel {
void signalArbiter(u32 waitingAddress, s32 threadCount); void signalArbiter(u32 waitingAddress, s32 threadCount);
void sleepThread(s64 ns); void sleepThread(s64 ns);
void sleepThreadOnArbiter(u32 waitingAddress); void sleepThreadOnArbiter(u32 waitingAddress);
void sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs);
void switchThread(int newThreadIndex); void switchThread(int newThreadIndex);
void sortThreads(); void sortThreads();
std::optional<int> getNextThread(); std::optional<int> getNextThread();
@ -215,6 +218,8 @@ class Kernel {
} }
} }
void addWakeupEvent(u64 tick);
Handle makeObject(KernelObjectType type) { Handle makeObject(KernelObjectType type) {
if (handleCounter > KernelHandles::Max) [[unlikely]] { if (handleCounter > KernelHandles::Max) [[unlikely]] {
Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing"); Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
@ -252,6 +257,8 @@ class Kernel {
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void clearInstructionCache(); void clearInstructionCache();
void clearInstructionCacheRange(u32 start, u32 size); void clearInstructionCacheRange(u32 start, u32 size);
void pollThreadWakeups();
u32 getSharedFontVaddr(); u32 getSharedFontVaddr();
// For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display // For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display

View file

@ -98,16 +98,17 @@ struct Session {
}; };
enum class ThreadStatus { enum class ThreadStatus {
Running, // Currently running Running, // Currently running
Ready, // Ready to run Ready, // Ready to run
WaitArbiter, // Waiting on an address arbiter WaitArbiter, // Waiting on an address arbiter
WaitSleep, // Waiting due to a SleepThread SVC WaitArbiterTimeout, // Waiting on an address arbiter with timeout
WaitSync1, // Waiting for the single object in the wait list to be ready WaitSleep, // Waiting due to a SleepThread SVC
WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready WaitSync1, // Waiting for the single object in the wait list to be ready
WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready
WaitIPC, // Waiting for the reply from an IPC request WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready
Dormant, // Created but not yet made ready WaitIPC, // Waiting for the reply from an IPC request
Dead // Run to completion, or forcefully terminated Dormant, // Created but not yet made ready
Dead // Run to completion, or forcefully terminated
}; };
struct Thread { struct Thread {

View file

@ -9,11 +9,12 @@
struct Scheduler { struct Scheduler {
enum class EventType { enum class EventType {
VBlank = 0, // End of frame event VBlank = 0, // End of frame event
UpdateTimers = 1, // Update kernel timer objects ThreadWakeup = 1, // A thread might wake up from eg sleeping or timing outs
RunDSP = 2, // Make the emulated DSP run for one audio frame RunDSP = 2, // Make the emulated DSP run for one audio frame
SignalY2R = 3, // Signal that a Y2R conversion has finished UpdateTimers = 3, // Update kernel timer objects
UpdateIR = 4, // Update an IR device (For now, just the CirclePad Pro/N3DS controls) SignalY2R = 4, // Signal that a Y2R conversion has finished
Panic = 5, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) UpdateIR = 5, // Update an IR device (For now, just the CirclePad Pro/N3DS controls)
Panic = 6, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
TotalNumberOfEvents // How many event types do we have in total? TotalNumberOfEvents // How many event types do we have in total?
}; };
static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents); static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents);

View file

@ -80,6 +80,14 @@ void Kernel::arbitrateAddress() {
break; break;
} }
case ArbitrationType::WaitIfLessTimeout: {
s32 word = static_cast<s32>(mem.read32(address)); // Yes this is meant to be signed
if (word < value) {
sleepThreadOnArbiterWithTimeout(address, ns);
}
break;
}
case ArbitrationType::Signal: signalArbiter(address, value); break; case ArbitrationType::Signal: signalArbiter(address, value); break;
default: Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type)); default: Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type));
} }
@ -96,8 +104,9 @@ void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) {
// Wake threads with the highest priority threads being woken up first // Wake threads with the highest priority threads being woken up first
for (auto index : threadIndices) { for (auto index : threadIndices) {
Thread& t = threads[index]; Thread& t = threads[index];
if (t.status == ThreadStatus::WaitArbiter && t.waitingAddress == waitingAddress) { if ((t.status == ThreadStatus::WaitArbiter || t.status == ThreadStatus::WaitArbiterTimeout) && t.waitingAddress == waitingAddress) {
t.status = ThreadStatus::Ready; t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // Return that the arbiter was actually signalled and that we didn't timeout
count += 1; count += 1;
// Check if we've reached the max number of. If count < 0 then all threads are released. // Check if we've reached the max number of. If count < 0 then all threads are released.

View file

@ -138,6 +138,7 @@ void Kernel::waitSynchronization1() {
// Add the current thread to the object's wait list // Add the current thread to the object's wait list
object->getWaitlist() |= (1ull << currentThreadIndex); object->getWaitlist() |= (1ull << currentThreadIndex);
addWakeupEvent(t.wakeupTick);
requireReschedule(); requireReschedule();
} }
} }
@ -232,6 +233,7 @@ void Kernel::waitSynchronizationN() {
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
} }
addWakeupEvent(t.wakeupTick);
requireReschedule(); requireReschedule();
} else { } else {
Helpers::panic("WaitSynchronizationN with waitAll"); Helpers::panic("WaitSynchronizationN with waitAll");

View file

@ -1,6 +1,7 @@
#include "kernel.hpp" #include "kernel.hpp"
#include <cassert> #include <cassert>
#include <limits>
#include "cpu.hpp" #include "cpu.hpp"
#include "kernel_types.hpp" #include "kernel_types.hpp"
@ -161,6 +162,7 @@ void Kernel::reset() {
threadIndices.clear(); threadIndices.clear();
serviceManager.reset(); serviceManager.reset();
nextScheduledWakeupTick = std::numeric_limits<u64>::max();
needReschedule = false; needReschedule = false;
// Allocate handle #0 to a dummy object and make a main process object // Allocate handle #0 to a dummy object and make a main process object
@ -180,9 +182,7 @@ void Kernel::reset() {
} }
// Get pointer to thread-local storage // Get pointer to thread-local storage
u32 Kernel::getTLSPointer() { u32 Kernel::getTLSPointer() { return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; }
return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize;
}
// Result CloseHandle(Handle handle) // Result CloseHandle(Handle handle)
void Kernel::svcCloseHandle() { void Kernel::svcCloseHandle() {

View file

@ -2,6 +2,7 @@
#include <bit> #include <bit>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
#include <limits>
#include "arm_defs.hpp" #include "arm_defs.hpp"
#include "kernel.hpp" #include "kernel.hpp"
@ -50,7 +51,7 @@ bool Kernel::canThreadRun(const Thread& t) {
if (t.status == ThreadStatus::Ready) { if (t.status == ThreadStatus::Ready) {
return true; return true;
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny ||
t.status == ThreadStatus::WaitSyncAll) { t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) {
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All} // TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
return cpu.getTicks() >= t.wakeupTick; return cpu.getTicks() >= t.wakeupTick;
} }
@ -215,6 +216,23 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) {
requireReschedule(); requireReschedule();
} }
void Kernel::sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs) {
regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout
// Timeout is 0, don't bother waiting, instantly timeout
if (timeoutNs == 0) {
return;
}
Thread& t = threads[currentThreadIndex];
t.status = ThreadStatus::WaitArbiterTimeout;
t.waitingAddress = waitingAddress;
t.wakeupTick = getWakeupTick(timeoutNs);
addWakeupEvent(t.wakeupTick);
requireReschedule();
}
// Acquires an object that is **ready to be acquired** without waiting on it // Acquires an object that is **ready to be acquired** without waiting on it
void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) { void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
switch (object->type) { switch (object->type) {
@ -390,6 +408,7 @@ void Kernel::sleepThread(s64 ns) {
t.status = ThreadStatus::WaitSleep; t.status = ThreadStatus::WaitSleep;
t.wakeupTick = getWakeupTick(ns); t.wakeupTick = getWakeupTick(ns);
addWakeupEvent(t.wakeupTick);
requireReschedule(); requireReschedule();
} }
} }
@ -688,6 +707,42 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
} }
} }
void Kernel::pollThreadWakeups() {
rescheduleThreads();
bool haveSleepingThread = false;
u64 nextWakeupTick = std::numeric_limits<u64>::max();
for (auto index : threadIndices) {
const Thread& t = threads[index];
if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny ||
t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) {
nextWakeupTick = std::min<u64>(nextWakeupTick, t.wakeupTick);
haveSleepingThread = true;
}
}
auto& scheduler = cpu.getScheduler();
if (haveSleepingThread && nextWakeupTick > scheduler.currentTimestamp) {
nextScheduledWakeupTick = nextWakeupTick;
scheduler.addEvent(Scheduler::EventType::ThreadWakeup, nextWakeupTick);
} else {
nextScheduledWakeupTick = std::numeric_limits<u64>::max();
}
}
void Kernel::addWakeupEvent(u64 tick) {
// We only need to queue the event if the tick of the wakeup is coming sooner than our next scheduled wakeup.
if (nextScheduledWakeupTick > tick) {
nextScheduledWakeupTick = tick;
auto& scheduler = cpu.getScheduler();
scheduler.removeEvent(Scheduler::EventType::ThreadWakeup);
scheduler.addEvent(Scheduler::EventType::ThreadWakeup, tick);
}
}
std::vector<Thread> Kernel::getMainProcessThreads() { std::vector<Thread> Kernel::getMainProcessThreads() {
// Sort the thread indices so that they appear nicer in the debugger // Sort the thread indices so that they appear nicer in the debugger
auto indices = threadIndices; auto indices = threadIndices;

View file

@ -174,6 +174,7 @@ void Emulator::pollScheduler() {
break; break;
} }
case Scheduler::EventType::ThreadWakeup: kernel.pollThreadWakeups(); break;
case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break; case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
case Scheduler::EventType::RunDSP: { case Scheduler::EventType::RunDSP: {
dsp->runAudioFrame(time); dsp->runAudioFrame(time);