#pragma once #include #include #include #include #include #include #include "config.hpp" #include "helpers.hpp" #include "kernel_types.hpp" #include "logger.hpp" #include "memory.hpp" #include "resource_limits.hpp" #include "services/service_manager.hpp" class CPU; struct Scheduler; class Kernel { using Handle = HorizonHandle; std::span regs; CPU& cpu; Memory& mem; // The handle number for the next kernel object to be created u32 handleCounter; // A list of our OS threads, the max number of which depends on the resource limit (hardcoded 32 per process on retail it seems). // We have an extra thread for when no thread is capable of running. This thread is called the "idle thread" in our code // This thread is set up in setupIdleThread and just yields in a loop to see if any other thread has woken up std::array threads; static constexpr int idleThreadIndex = appResourceLimits.maxThreads; // Our waitlist system uses a bitfield of 64 bits to show which threads are waiting on an object. // That means we can have a maximum of 63 threads + 1 idle thread. This assert should never trigger because the max thread # is 32 // But we have it here for safety purposes static_assert(appResourceLimits.maxThreads <= 63, "The waitlist system is built on the premise that <= 63 threads max can be active"); std::vector objects; std::vector portHandles; std::vector mutexHandles; std::vector timerHandles; // Thread indices, sorted by priority std::vector threadIndices; Handle currentProcess; Handle mainThread; int currentThreadIndex; Handle srvHandle; // Handle for the special service manager port "srv:" Handle errorPortHandle; // Handle for the err:f port used for displaying errors u32 arbiterCount; u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32) u32 aliveThreadCount; // How many of these threads are actually alive? ServiceManager serviceManager; // Top 8 bits are the major version, bottom 8 are the minor version u16 kernelVersion = 0; // Shows whether a reschedule will be need bool needReschedule = false; Handle makeArbiter(); Handle makeProcess(u32 id); Handle makePort(const char* name); Handle makeSession(Handle port); Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant); Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission); public: // Needs to be public to be accessible to the APT/HID services Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None); // Needs to be public to be accessible to the APT/DSP services Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the service manager port Handle makeSemaphore(u32 initialCount, u32 maximumCount); Handle makeTimer(ResetType resetType); void pollTimers(); // Signals an event, returns true on success or false if the event does not exist bool signalEvent(Handle e); // Run the callback for "special" events that have callbacks void runEventCallback(Event::CallbackType callback); void clearEvent(Handle e) { KernelObject* object = getObject(e, KernelObjectType::Event); if (object != nullptr) { object->getData()->fired = false; } } private: void signalArbiter(u32 waitingAddress, s32 threadCount); void sleepThread(s64 ns); void sleepThreadOnArbiter(u32 waitingAddress); void switchThread(int newThreadIndex); void sortThreads(); std::optional getNextThread(); void rescheduleThreads(); bool canThreadRun(const Thread& t); bool shouldWaitOnObject(KernelObject* object); void releaseMutex(Mutex* moo); void cancelTimer(Timer* timer); void signalTimer(Handle timerHandle, Timer* timer); u64 getWakeupTick(s64 ns); // Wake up the thread with the highest priority out of all threads in the waitlist // Returns the index of the woken up thread // Do not call this function with an empty waitlist!!! int wakeupOneThread(u64 waitlist, Handle handle); void wakeupAllThreads(u64 waitlist, Handle handle); std::optional getPortHandle(const char* name); void deleteObjectData(KernelObject& object); KernelObject* getProcessFromPID(Handle handle); s32 getCurrentResourceValue(const KernelObject* limit, u32 resourceName); u32 getMaxForResource(const KernelObject* limit, u32 resourceName); u32 getTLSPointer(); void setupIdleThread(); void acquireSyncObject(KernelObject* object, const Thread& thread); bool isWaitable(const KernelObject* object); // Functions for the err:f port void handleErrorSyncRequest(u32 messagePointer); void throwError(u32 messagePointer); std::string getProcessName(u32 pid); const char* resetTypeToString(u32 type); MAKE_LOG_FUNCTION(log, kernelLogger) MAKE_LOG_FUNCTION(logSVC, svcLogger) MAKE_LOG_FUNCTION(logThread, threadLogger) MAKE_LOG_FUNCTION(logError, errorLogger) MAKE_LOG_FUNCTION(logFileIO, fileIOLogger) MAKE_LOG_FUNCTION_USER(logDebugString, debugStringLogger) // SVC implementations void arbitrateAddress(); void createAddressArbiter(); void createMemoryBlock(); void createThread(); void controlMemory(); void duplicateHandle(); void exitThread(); void mapMemoryBlock(); void unmapMemoryBlock(); void queryMemory(); void getCurrentProcessorNumber(); void getProcessID(); void getProcessInfo(); void getResourceLimit(); void getResourceLimitLimitValues(); void getResourceLimitCurrentValues(); void getSystemInfo(); void getSystemTick(); void getThreadContext(); void getThreadID(); void getThreadIdealProcessor(); void getThreadPriority(); void sendSyncRequest(); void setThreadPriority(); void svcCancelTimer(); void svcClearEvent(); void svcClearTimer(); void svcCloseHandle(); void svcCreateEvent(); void svcCreateMutex(); void svcCreateSemaphore(); void svcCreateTimer(); void svcReleaseMutex(); void svcReleaseSemaphore(); void svcSignalEvent(); void svcSetTimer(); void svcSleepThread(); void connectToPort(); void outputDebugString(); void waitSynchronization1(); void waitSynchronizationN(); // File operations void handleFileOperation(u32 messagePointer, Handle file); void closeFile(u32 messagePointer, Handle file); void flushFile(u32 messagePointer, Handle file); void readFile(u32 messagePointer, Handle file); void writeFile(u32 messagePointer, Handle file); void getFileSize(u32 messagePointer, Handle file); void openLinkFile(u32 messagePointer, Handle file); void setFileSize(u32 messagePointer, Handle file); void setFilePriority(u32 messagePointer, Handle file); // Directory operations void handleDirectoryOperation(u32 messagePointer, Handle directory); void closeDirectory(u32 messagePointer, Handle directory); void readDirectory(u32 messagePointer, Handle directory); public: Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config); void initializeFS() { return serviceManager.initializeFS(); } void setVersion(u8 major, u8 minor); void serviceSVC(u32 svc); void reset(); void requireReschedule() { needReschedule = true; } void evalReschedule() { if (needReschedule) { needReschedule = false; rescheduleThreads(); } } Handle makeObject(KernelObjectType type) { if (handleCounter > KernelHandles::Max) [[unlikely]] { Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing"); } objects.push_back(KernelObject(handleCounter, type)); log("Created %s object with handle %d\n", kernelObjectTypeToString(type), handleCounter); return handleCounter++; } std::vector& getObjects() { return objects; } // Get pointer to the object with the specified handle KernelObject* getObject(Handle handle) { // Accessing an object that has not been created if (handle >= objects.size()) [[unlikely]] { return nullptr; } return &objects[handle]; } // Get pointer to the object with the specified handle and type KernelObject* getObject(Handle handle, KernelObjectType type) { if (handle >= objects.size() || objects[handle].type != type) [[unlikely]] { return nullptr; } return &objects[handle]; } ServiceManager& getServiceManager() { return serviceManager; } Scheduler& getScheduler(); void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } void clearInstructionCache(); };