From 81b0f3dde0dc0e81a7d216b342719e186cd9002b Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Mon, 10 Oct 2022 16:41:08 +0300 Subject: [PATCH] [Kernel] Much better arbiter & thread scheduling impl --- include/kernel/kernel.hpp | 9 ++++ src/core/kernel/address_arbiter.cpp | 27 ++++++++-- src/core/kernel/events.cpp | 5 ++ src/core/kernel/kernel.cpp | 2 + src/core/kernel/ports.cpp | 1 - src/core/kernel/threads.cpp | 73 ++++++++++++++++++++++++--- src/core/services/service_manager.cpp | 2 +- src/main.cpp | 2 +- 8 files changed, 109 insertions(+), 12 deletions(-) diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 7e65b718..c718f9e7 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -23,6 +23,9 @@ class Kernel { std::vector objects; std::vector portHandles; + // Thread indices, sorted by priority + std::vector threadIndices; + Handle currentProcess; Handle mainThread; int currentThreadIndex; @@ -62,8 +65,14 @@ class Kernel { Handle makeSession(Handle port); Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg,ThreadStatus status = ThreadStatus::Dormant); + void signalArbiter(u32 waitingAddress, s32 threadCount); void sleepThreadOnArbiter(u32 waitingAddress); void switchThread(int newThreadIndex); + void sortThreads(); + std::optional getNextThread(); + void switchToNextThread(); + void rescheduleThreads(); + bool canThreadRun(const Thread& t); std::optional getPortHandle(const char* name); void deleteObjectData(KernelObject& object); diff --git a/src/core/kernel/address_arbiter.cpp b/src/core/kernel/address_arbiter.cpp index 3ac2099e..fc24fdb5 100644 --- a/src/core/kernel/address_arbiter.cpp +++ b/src/core/kernel/address_arbiter.cpp @@ -54,7 +54,7 @@ void Kernel::arbitrateAddress() { regs[0] = SVCResult::InvalidEnumValueAlt; return; } - // This needs to put the error code in r0 before we change threats + // This needs to put the error code in r0 before we change threads regs[0] = SVCResult::Success; switch (static_cast(type)) { @@ -68,11 +68,32 @@ void Kernel::arbitrateAddress() { } case ArbitrationType::Signal: - logSVC("Broken ArbitrateAddress (type == SIGNAL)\n"); - switchThread(0); + signalArbiter(address, value); break; default: Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type)); } +} + +// Signal up to "threadCount" threads waiting on the arbiter indicated by "waitingAddress" +void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) { + if (threadCount == 0) [[unlikely]] return; + s32 count = 0; // Number of threads we've woken up + + // 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) { + t.status = ThreadStatus::Ready; + count += 1; + + // Check if we've reached the max number of. If count < 0 then all threads are released. + if (count == threadCount && threadCount > 0) break; + } + } + + if (count != 0) { + rescheduleThreads(); + } } \ No newline at end of file diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index 4c2bb8ba..a0c0d299 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -72,4 +72,9 @@ void Kernel::waitSynchronizationN() { logSVC("WaitSynchronizationN (STUBBED)\n"); regs[0] = SVCResult::Success; + + if (currentThreadIndex == 1) { + printf("WaitSynchN OoT hack triggered\n"); + switchThread(0); + } } \ No newline at end of file diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index 3a28bf25..1471f368 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -7,6 +7,7 @@ Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu) : cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this) { objects.reserve(512); // Make room for a few objects to avoid further memory allocs later portHandles.reserve(32); + threadIndices.reserve(appResourceLimits.maxThreads); for (int i = 0; i < threads.size(); i++) { threads[i].index = i; @@ -98,6 +99,7 @@ void Kernel::reset() { } objects.clear(); portHandles.clear(); + threadIndices.clear(); serviceManager.reset(); // Allocate handle #0 to a dummy object and make a main process object diff --git a/src/core/kernel/ports.cpp b/src/core/kernel/ports.cpp index 9721361c..92aba381 100644 --- a/src/core/kernel/ports.cpp +++ b/src/core/kernel/ports.cpp @@ -74,7 +74,6 @@ void Kernel::connectToPort() { void Kernel::sendSyncRequest() { const auto handle = regs[0]; u32 messagePointer = getTLSPointer() + 0x80; // The message is stored starting at TLS+0x80 - logSVC("SendSyncRequest(session handle = %X)\n", handle); // The sync request is being sent at a service rather than whatever port, so have the service manager intercept it diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index f2621022..cf0a1cb1 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -8,13 +8,15 @@ // Switch to another thread // newThread: Index of the newThread in the thread array (NOT a handle). void Kernel::switchThread(int newThreadIndex) { - if (currentThreadIndex == newThreadIndex) { // Bail early if the new thread is actually the old thread + auto& oldThread = threads[currentThreadIndex]; + auto& newThread = threads[newThreadIndex]; + newThread.status = ThreadStatus::Running; + + // Bail early if the new thread is actually the old thread + if (currentThreadIndex == newThreadIndex) [[unlikely]] { return; } - auto& oldThread = threads[currentThreadIndex]; - const auto& newThread = threads[newThreadIndex]; - // Backup context std::memcpy(&oldThread.gprs[0], &cpu.regs()[0], 16 * sizeof(u32)); // Backup the 16 GPRs std::memcpy(&oldThread.fprs[0], &cpu.fprs()[0], 32 * sizeof(u32)); // Backup the 32 FPRs @@ -31,11 +33,69 @@ void Kernel::switchThread(int newThreadIndex) { currentThreadIndex = newThreadIndex; } +// Sort the threadIndices vector based on the priority of each thread +// The threads with higher priority (aka the ones with a lower priority value) should come first in the vector +void Kernel::sortThreads() { + std::vector& v = threadIndices; + std::sort(v.begin(), v.end(), [&](int a, int b) { + return threads[a].priority < threads[b].priority; + }); +} + +bool Kernel::canThreadRun(const Thread& t) { + if (t.status == ThreadStatus::Ready) { + return true; + } + + // Handle timeouts and stuff here + return false; +} + +// Get the index of the next thread to run by iterating through the thread list and finding the free thread with the highest priority +// Returns the thread index if a thread is found, or nullopt otherwise +std::optional Kernel::getNextThread() { + for (auto index : threadIndices) { + const Thread& t = threads[index]; + + // Thread is ready, return it + if (canThreadRun(t)) { + return index; + } + + // TODO: Check timeouts here + } + + // No thread was found + return std::nullopt; +} + +void Kernel::switchToNextThread() { + std::optional newThreadIndex = getNextThread(); + + if (!newThreadIndex.has_value()) { + Helpers::panic("Kernel tried to switch to the next thread but none found"); + } else { + switchThread(newThreadIndex.value()); + } +} + +// See if there;s a higher priority, ready thread and switch to that +void Kernel::rescheduleThreads() { + std::optional newThreadIndex = getNextThread(); + + if (newThreadIndex.has_value()) { + threads[currentThreadIndex].status = ThreadStatus::Ready; + switchThread(newThreadIndex.value()); + } +} + // Internal OS function to spawn a thread Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg, ThreadStatus status) { if (threadCount >= appResourceLimits.maxThreads) { Helpers::panic("Overflowed the number of threads"); } + threadIndices.push_back(threadCount); + Thread& t = threads[threadCount++]; // Reference to thread data Handle ret = makeObject(KernelObjectType::Thread); objects[ret].data = &t; @@ -64,6 +124,7 @@ Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u // Initial TLS base has already been set in Kernel::Kernel() // TODO: Does svcCreateThread zero-set the TLS of the new thread? + sortThreads(); return ret; } @@ -90,10 +151,10 @@ void Kernel::createThread() { void Kernel::sleepThreadOnArbiter(u32 waitingAddress) { Thread& t = threads[currentThreadIndex]; - t.status = ThreadStatus::WaitArbiter; t.waitingAddress = waitingAddress; - switchThread(1); // TODO: Properly change threads + + switchToNextThread(); } diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index f9f3ec80..d8dd08c0 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -103,7 +103,7 @@ void ServiceManager::enableNotification(u32 messagePointer) { void ServiceManager::receiveNotification(u32 messagePointer) { log("srv::ReceiveNotification() (STUBBED)\n"); - printf("r15 = %08X", regs[15]); + mem.write32(messagePointer + 4, Result::Success); // Result code mem.write32(messagePointer + 8, 0); // Notification ID } diff --git a/src/main.cpp b/src/main.cpp index d1ce592a..b2254df6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,7 @@ int main (int argc, char *argv[]) { emu.initGraphicsContext(); - auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "OoT.3ds"); + auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "SimplerTri.elf"); if (!emu.loadROM(romPath)) { // For some reason just .c_str() doesn't show the proper path Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());