From 54cdcacd5ab1f50905e0a35693297f2572bb379a Mon Sep 17 00:00:00 2001
From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
Date: Mon, 3 Feb 2025 00:16:24 +0200
Subject: [PATCH 1/4] Add thread preemption when a thread wakes up from a
 timeout

---
 include/kernel/kernel.hpp   |  5 +++++
 include/scheduler.hpp       |  3 ++-
 src/core/kernel/events.cpp  |  2 ++
 src/core/kernel/kernel.cpp  |  8 ++++++--
 src/core/kernel/threads.cpp | 38 +++++++++++++++++++++++++++++++++++++
 src/emulator.cpp            |  1 +
 6 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp
index 3f20b5e1..e62053c8 100644
--- a/include/kernel/kernel.hpp
+++ b/include/kernel/kernel.hpp
@@ -58,6 +58,7 @@ class Kernel {
 	// Top 8 bits are the major version, bottom 8 are the minor version
 	u16 kernelVersion = 0;
 
+	u64 nextScheduledWakeupTick = std::numeric_limits<u64>::max();
 	// Shows whether a reschedule will be need
 	bool needReschedule = false;
 
@@ -214,6 +215,8 @@ public:
 		}
 	}
 
+	void addWakeupEvent(u64 tick);
+
 	Handle makeObject(KernelObjectType type) {
 		if (handleCounter > KernelHandles::Max) [[unlikely]] {
 			Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
@@ -253,5 +256,7 @@ public:
 	void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
 	void clearInstructionCache();
 	void clearInstructionCacheRange(u32 start, u32 size);
+	void pollThreadWakeups();
+
 	u32 getSharedFontVaddr();
 };
\ No newline at end of file
diff --git a/include/scheduler.hpp b/include/scheduler.hpp
index cfc4d5e8..f8eae2fc 100644
--- a/include/scheduler.hpp
+++ b/include/scheduler.hpp
@@ -11,7 +11,8 @@ struct Scheduler {
 		VBlank = 0,          // End of frame event
 		UpdateTimers = 1,    // Update kernel timer objects
 		RunDSP = 2,          // Make the emulated DSP run for one audio frame
-		SignalY2R = 3,       // Signal that a Y2R conversion has finished
+		ThreadWakeup = 3,    // A thread is going to wake up and we need to reschedule threads
+		SignalY2R = 4,       // Signal that a Y2R conversion has finished
 		Panic = 4,           // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
 		TotalNumberOfEvents  // How many event types do we have in total?
 	};
diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp
index 6d3dfbd7..c06204a9 100644
--- a/src/core/kernel/events.cpp
+++ b/src/core/kernel/events.cpp
@@ -137,6 +137,7 @@ void Kernel::waitSynchronization1() {
 		// Add the current thread to the object's wait list
 		object->getWaitlist() |= (1ull << currentThreadIndex);
 
+		addWakeupEvent(t.wakeupTick);
 		requireReschedule();
 	}
 }
@@ -231,6 +232,7 @@ void Kernel::waitSynchronizationN() {
 			waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
 		}
 
+		addWakeupEvent(t.wakeupTick);
 		requireReschedule();
 	} else {
 		Helpers::panic("WaitSynchronizationN with waitAll");
diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp
index e097fab6..4e46fbb0 100644
--- a/src/core/kernel/kernel.cpp
+++ b/src/core/kernel/kernel.cpp
@@ -1,7 +1,10 @@
-#include <cassert>
 #include "kernel.hpp"
-#include "kernel_types.hpp"
+
+#include <cassert>
+#include <limits>
+
 #include "cpu.hpp"
+#include "kernel_types.hpp"
 
 Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
 	: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) {
@@ -159,6 +162,7 @@ void Kernel::reset() {
 	threadIndices.clear();
 	serviceManager.reset();
 
+	nextScheduledWakeupTick = std::numeric_limits<u64>::max();
 	needReschedule = false;
 
 	// Allocate handle #0 to a dummy object and make a main process object
diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp
index 9eb7a197..1fbaea08 100644
--- a/src/core/kernel/threads.cpp
+++ b/src/core/kernel/threads.cpp
@@ -1,6 +1,8 @@
+#include <algorithm>
 #include <bit>
 #include <cassert>
 #include <cstring>
+#include <limits>
 
 #include "arm_defs.hpp"
 #include "kernel.hpp"
@@ -396,6 +398,7 @@ void Kernel::sleepThread(s64 ns) {
 		t.status = ThreadStatus::WaitSleep;
 		t.wakeupTick = getWakeupTick(ns);
 
+		addWakeupEvent(t.wakeupTick);
 		requireReschedule();
 	}
 }
@@ -697,3 +700,38 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
 			return true;
 	}
 }
+
+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) {
+			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);
+	}
+}
\ No newline at end of file
diff --git a/src/emulator.cpp b/src/emulator.cpp
index 11970d91..ff91aa9e 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -177,6 +177,7 @@ void Emulator::pollScheduler() {
 				break;
 			}
 
+			case Scheduler::EventType::ThreadWakeup: kernel.pollThreadWakeups(); break;
 			case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
 			case Scheduler::EventType::RunDSP: {
 				dsp->runAudioFrame(time);

From 456585a9e7ef9cb7612d010adb19de28bdfd493a Mon Sep 17 00:00:00 2001
From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
Date: Tue, 11 Feb 2025 19:02:14 +0200
Subject: [PATCH 2/4] Add arbiter sleeping with timeout

Update threads.cpp
---
 include/kernel/kernel.hpp           |  2 ++
 include/kernel/kernel_types.hpp     | 21 +++++++++---------
 src/core/kernel/address_arbiter.cpp |  8 +++++++
 src/core/kernel/threads.cpp         | 33 ++++++++++++++++++++++++++---
 4 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp
index e62053c8..a9facddf 100644
--- a/include/kernel/kernel.hpp
+++ b/include/kernel/kernel.hpp
@@ -95,6 +95,8 @@ public:
 	void signalArbiter(u32 waitingAddress, s32 threadCount);
 	void sleepThread(s64 ns);
 	void sleepThreadOnArbiter(u32 waitingAddress);
+	void sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs);
+
 	void switchThread(int newThreadIndex);
 	void sortThreads();
 	std::optional<int> getNextThread();
diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp
index a3a60c34..a5b27498 100644
--- a/include/kernel/kernel_types.hpp
+++ b/include/kernel/kernel_types.hpp
@@ -98,16 +98,17 @@ struct Session {
 };
 
 enum class ThreadStatus {
-	Running,      // Currently running
-	Ready,        // Ready to run
-	WaitArbiter,  // Waiting on an address arbiter
-	WaitSleep,    // Waiting due to a SleepThread SVC
-	WaitSync1,    // Waiting for the single object in the wait list to be ready
-	WaitSyncAny,  // Wait for one object of the many that might be in the wait list to be ready
-	WaitSyncAll,  // Waiting for ALL sync objects in its wait list to be ready
-	WaitIPC,      // Waiting for the reply from an IPC request
-	Dormant,      // Created but not yet made ready
-	Dead          // Run to completion, or forcefully terminated
+	Running,             // Currently running
+	Ready,               // Ready to run
+	WaitArbiter,         // Waiting on an address arbiter
+	WaitArbiterTimeout,  // Waiting on an address arbiter with timeout
+	WaitSleep,           // Waiting due to a SleepThread SVC
+	WaitSync1,           // Waiting for the single object in the wait list to be ready
+	WaitSyncAny,         // Wait for one object of the many that might be in the wait list to be ready
+	WaitSyncAll,         // Waiting for ALL sync objects in its wait list to be ready
+	WaitIPC,             // Waiting for the reply from an IPC request
+	Dormant,             // Created but not yet made ready
+	Dead                 // Run to completion, or forcefully terminated
 };
 
 struct Thread {
diff --git a/src/core/kernel/address_arbiter.cpp b/src/core/kernel/address_arbiter.cpp
index d15c81b8..276f6e68 100644
--- a/src/core/kernel/address_arbiter.cpp
+++ b/src/core/kernel/address_arbiter.cpp
@@ -79,6 +79,14 @@ void Kernel::arbitrateAddress() {
 			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;
diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp
index 1fbaea08..e905a22f 100644
--- a/src/core/kernel/threads.cpp
+++ b/src/core/kernel/threads.cpp
@@ -52,8 +52,7 @@ void Kernel::sortThreads() {
 bool Kernel::canThreadRun(const Thread& t) {
 	if (t.status == ThreadStatus::Ready) {
 		return true;
-	} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
-		|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
+	} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) {
 		// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
 		return cpu.getTicks() >= t.wakeupTick;
 	}
@@ -218,6 +217,23 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) {
 	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
 void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
 	switch (object->type) {
@@ -311,6 +327,11 @@ int Kernel::wakeupOneThread(u64 waitlist, Handle handle) {
 			}
 			break;
 
+		case ThreadStatus::WaitArbiterTimeout:
+			t.status = ThreadStatus::Ready;
+			t.gprs[0] = Result::Success;  // The thread did not timeout, so write success to r0
+			break;
+
 		case ThreadStatus::WaitSyncAll:
 			Helpers::panic("WakeupOneThread: Thread on WaitSyncAll");
 			break;
@@ -345,6 +366,11 @@ void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) {
 				}
 			}
 			break;
+			
+		case ThreadStatus::WaitArbiterTimeout:
+			t.status = ThreadStatus::Ready;
+			t.gprs[0] = Result::Success;  // The thread did not timeout, so write success to r0
+			break;
 
 		case ThreadStatus::WaitSyncAll:
 			Helpers::panic("WakeupAllThreads: Thread on WaitSyncAll");
@@ -709,7 +735,8 @@ void Kernel::pollThreadWakeups() {
 	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) {
+		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;
 		}

From 59dd4433cfe89f055e909c66bc9ba8f779fdbf10 Mon Sep 17 00:00:00 2001
From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
Date: Tue, 11 Feb 2025 20:53:43 +0200
Subject: [PATCH 3/4] Scheduler: Fix event numbers

---
 include/scheduler.hpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/scheduler.hpp b/include/scheduler.hpp
index f8eae2fc..b81b8807 100644
--- a/include/scheduler.hpp
+++ b/include/scheduler.hpp
@@ -13,7 +13,7 @@ struct Scheduler {
 		RunDSP = 2,          // Make the emulated DSP run for one audio frame
 		ThreadWakeup = 3,    // A thread is going to wake up and we need to reschedule threads
 		SignalY2R = 4,       // Signal that a Y2R conversion has finished
-		Panic = 4,           // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
+		Panic = 5,           // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
 		TotalNumberOfEvents  // How many event types do we have in total?
 	};
 	static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents);
@@ -89,4 +89,4 @@ struct Scheduler {
 
 		return (arm11Clock * s64(ns)) / 1000000000;
 	}
-};
\ No newline at end of file
+};

From ee24158d32636973204881ff653c258ee0427006 Mon Sep 17 00:00:00 2001
From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
Date: Wed, 12 Feb 2025 02:46:08 +0200
Subject: [PATCH 4/4] More address arbiter fixes

---
 src/core/kernel/address_arbiter.cpp |  3 ++-
 src/core/kernel/threads.cpp         | 10 ----------
 2 files changed, 2 insertions(+), 11 deletions(-)

diff --git a/src/core/kernel/address_arbiter.cpp b/src/core/kernel/address_arbiter.cpp
index 276f6e68..ecb0a302 100644
--- a/src/core/kernel/address_arbiter.cpp
+++ b/src/core/kernel/address_arbiter.cpp
@@ -106,8 +106,9 @@ void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) {
 	// Wake threads with the highest priority threads being woken up first
 	for (auto index : threadIndices) {
 		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.gprs[0] = Result::Success;  // Return that the arbiter was actually signalled and that we didn't timeout
 			count += 1;
 
 			// Check if we've reached the max number of. If count < 0 then all threads are released.
diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp
index e905a22f..108f6129 100644
--- a/src/core/kernel/threads.cpp
+++ b/src/core/kernel/threads.cpp
@@ -327,11 +327,6 @@ int Kernel::wakeupOneThread(u64 waitlist, Handle handle) {
 			}
 			break;
 
-		case ThreadStatus::WaitArbiterTimeout:
-			t.status = ThreadStatus::Ready;
-			t.gprs[0] = Result::Success;  // The thread did not timeout, so write success to r0
-			break;
-
 		case ThreadStatus::WaitSyncAll:
 			Helpers::panic("WakeupOneThread: Thread on WaitSyncAll");
 			break;
@@ -366,11 +361,6 @@ void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) {
 				}
 			}
 			break;
-			
-		case ThreadStatus::WaitArbiterTimeout:
-			t.status = ThreadStatus::Ready;
-			t.gprs[0] = Result::Success;  // The thread did not timeout, so write success to r0
-			break;
 
 		case ThreadStatus::WaitSyncAll:
 			Helpers::panic("WakeupAllThreads: Thread on WaitSyncAll");