#include #include "kernel.hpp" #include "kernel_types.hpp" #include "cpu.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) { objects.reserve(512); // Make room for a few objects to avoid further memory allocs later mutexHandles.reserve(8); portHandles.reserve(32); threadIndices.reserve(appResourceLimits.maxThreads); for (int i = 0; i < threads.size(); i++) { Thread& t = threads[i]; t.index = i; t.tlsBase = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize; t.status = ThreadStatus::Dead; t.waitList.clear(); t.waitList.reserve(10); // Reserve some space for the wait list to avoid further memory allocs later // The state below isn't necessary to initialize but we do it anyways out of caution t.outPointer = 0; t.waitAll = false; } setVersion(1, 69); } void Kernel::serviceSVC(u32 svc) { switch (svc) { case 0x01: controlMemory(); break; case 0x02: queryMemory(); break; case 0x08: createThread(); break; case 0x09: exitThread(); break; case 0x0A: svcSleepThread(); break; case 0x0B: getThreadPriority(); break; case 0x0C: setThreadPriority(); break; case 0x0F: getThreadIdealProcessor(); break; case 0x11: getCurrentProcessorNumber(); break; case 0x13: svcCreateMutex(); break; case 0x14: svcReleaseMutex(); break; case 0x15: svcCreateSemaphore(); break; case 0x16: svcReleaseSemaphore(); break; case 0x17: svcCreateEvent(); break; case 0x18: svcSignalEvent(); break; case 0x19: svcClearEvent(); break; case 0x1A: svcCreateTimer(); break; case 0x1B: svcSetTimer(); break; case 0x1C: svcCancelTimer(); break; case 0x1D: svcClearTimer(); break; case 0x1E: createMemoryBlock(); break; case 0x1F: mapMemoryBlock(); break; case 0x21: createAddressArbiter(); break; case 0x22: arbitrateAddress(); break; case 0x23: svcCloseHandle(); break; case 0x24: waitSynchronization1(); break; case 0x25: waitSynchronizationN(); break; case 0x27: duplicateHandle(); break; case 0x28: getSystemTick(); break; case 0x2A: getSystemInfo(); break; case 0x2B: getProcessInfo(); break; case 0x2D: connectToPort(); break; case 0x32: sendSyncRequest(); break; case 0x35: getProcessID(); break; case 0x37: getThreadID(); break; case 0x38: getResourceLimit(); break; case 0x39: getResourceLimitLimitValues(); break; case 0x3A: getResourceLimitCurrentValues(); break; case 0x3D: outputDebugString(); break; default: Helpers::panic("Unimplemented svc: %X @ %08X", svc, regs[15]); break; } evalReschedule(); } void Kernel::setVersion(u8 major, u8 minor) { u16 descriptor = (u16(major) << 8) | u16(minor); kernelVersion = descriptor; mem.kernelVersion = descriptor; // The memory objects needs a copy because you can read the kernel ver from config mem } Handle Kernel::makeProcess(u32 id) { const Handle processHandle = makeObject(KernelObjectType::Process); const Handle resourceLimitHandle = makeObject(KernelObjectType::ResourceLimit); // Allocate data objects[processHandle].data = new Process(id); const auto processData = objects[processHandle].getData(); // Link resource limit object with its parent process objects[resourceLimitHandle].data = &processData->limits; processData->limits.handle = resourceLimitHandle; return processHandle; } // Get a pointer to the process indicated by handle, taking into account that 0xFFFF8001 always refers to the current process // Returns nullptr if the handle does not correspond to a process KernelObject* Kernel::getProcessFromPID(Handle handle) { if (handle == KernelHandles::CurrentProcess) [[likely]] { return getObject(currentProcess, KernelObjectType::Process); } else { return getObject(handle, KernelObjectType::Process); } } void Kernel::deleteObjectData(KernelObject& object) { if (object.data == nullptr) { return; } // Resource limit and thread objects do not allocate heap data, so we don't delete anything switch (object.type) { case KernelObjectType::AddressArbiter: delete object.getData(); return; case KernelObjectType::Archive: delete object.getData(); return; case KernelObjectType::Directory: delete object.getData(); return; case KernelObjectType::Event: delete object.getData(); return; case KernelObjectType::File: delete object.getData(); return; case KernelObjectType::MemoryBlock: delete object.getData(); return; case KernelObjectType::Port: delete object.getData(); return; case KernelObjectType::Process: delete object.getData(); return; case KernelObjectType::ResourceLimit: return; case KernelObjectType::Session: delete object.getData(); return; case KernelObjectType::Mutex: delete object.getData(); return; case KernelObjectType::Semaphore: delete object.getData(); return; case KernelObjectType::Thread: return; case KernelObjectType::Dummy: return; default: [[unlikely]] Helpers::warn("unknown object type"); return; } } void Kernel::reset() { handleCounter = 0; arbiterCount = 0; threadCount = 0; aliveThreadCount = 0; for (auto& t : threads) { t.status = ThreadStatus::Dead; t.waitList.clear(); t.threadsWaitingForTermination = 0; // No threads are waiting for this thread to terminate cause it's dead } for (auto& object : objects) { deleteObjectData(object); } objects.clear(); mutexHandles.clear(); portHandles.clear(); threadIndices.clear(); serviceManager.reset(); needReschedule = false; // Allocate handle #0 to a dummy object and make a main process object makeObject(KernelObjectType::Dummy); currentProcess = makeProcess(1); // Use ID = 1 for main process // Make main thread object. We do not have to set the entrypoint and SP for it as the ROM loader does. // Main thread seems to have a priority of 0x30. TODO: This creates a dummy context for thread 0, // which is thankfully not used. Maybe we should prevent this mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, ProcessorID::Default, 0, ThreadStatus::Running); currentThreadIndex = 0; setupIdleThread(); // Create some of the OS ports srvHandle = makePort("srv:"); // Service manager port errorPortHandle = makePort("err:f"); // Error display port } // Get pointer to thread-local storage u32 Kernel::getTLSPointer() { return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; } // Result CloseHandle(Handle handle) void Kernel::svcCloseHandle() { logSVC("CloseHandle(handle = %d) (Unimplemented)\n", regs[0]); regs[0] = Result::Success; } // u64 GetSystemTick() void Kernel::getSystemTick() { logSVC("GetSystemTick()\n"); u64 ticks = cpu.getTicks(); regs[0] = u32(ticks); regs[1] = u32(ticks >> 32); } // Result OutputDebugString(const char* str, s32 size) // TODO: Does this actually write an error code in r0 and is the above signature correct? void Kernel::outputDebugString() { const u32 pointer = regs[0]; const u32 size = regs[1]; std::string message = mem.readString(pointer, size); logDebugString("[OutputDebugString] %s\n", message.c_str()); regs[0] = Result::Success; } void Kernel::getProcessID() { const auto pid = regs[1]; const auto process = getProcessFromPID(pid); logSVC("GetProcessID(process: %s)\n", getProcessName(pid).c_str()); if (process == nullptr) [[unlikely]] { regs[0] = Result::Kernel::InvalidHandle; return; } regs[0] = Result::Success; regs[1] = process->getData()->id; } // Result GetProcessInfo(s64* out, Handle process, ProcessInfoType type) void Kernel::getProcessInfo() { const auto pid = regs[1]; const auto type = regs[2]; const auto process = getProcessFromPID(pid); logSVC("GetProcessInfo(process: %s, type = %d)\n", getProcessName(pid).c_str(), type); if (process == nullptr) [[unlikely]] { regs[0] = Result::Kernel::InvalidHandle; return; } switch (type) { // According to 3DBrew: Amount of private (code, data, heap) memory used by the process + total supervisor-mode // stack size + page-rounded size of the external handle table case 2: regs[1] = mem.getUsedUserMem(); regs[2] = 0; break; case 20: // Returns 0x20000000 - regs[1] = PhysicalAddrs::FCRAM - mem.getLinearHeapVaddr(); regs[2] = 0; break; default: Helpers::panic("GetProcessInfo: unimplemented type %d", type); } regs[0] = Result::Success; } // Result DuplicateHandle(Handle* out, Handle original) void Kernel::duplicateHandle() { Handle original = regs[1]; logSVC("DuplicateHandle(handle = %X)\n", original); if (original == KernelHandles::CurrentThread) { regs[0] = Result::Success; Handle ret = makeObject(KernelObjectType::Thread); objects[ret].data = &threads[currentThreadIndex]; regs[1] = ret; } else { Helpers::panic("DuplicateHandle: unimplemented handle type"); } } namespace SystemInfoType { enum : u32 { MemoryInformation = 0, // Gets information related to Citra (We don't implement this, we just report this emulator is not Citra) CitraInformation = 0x20000, // Gets information related to this emulator PandaInformation = 0x20001, }; }; namespace CitraInfoType { enum : u32 { IsCitra = 0, BuildName = 10, // (ie: Nightly, Canary). BuildVersion = 11, // Build version. BuildDate1 = 20, // Build date first 7 characters. BuildDate2 = 21, // Build date next 7 characters. BuildDate3 = 22, // Build date next 7 characters. BuildDate4 = 23, // Build date last 7 characters. BuildBranch1 = 30, // Git branch first 7 characters. BuildBranch2 = 31, // Git branch last 7 characters. BuildDesc1 = 40, // Git description (commit) first 7 characters. BuildDesc2 = 41, // Git description (commit) last 7 characters. }; } namespace PandaInfoType { enum : u32 { IsPanda = 0, }; } void Kernel::getSystemInfo() { const u32 infoType = regs[1]; const u32 subtype = regs[2]; log("GetSystemInfo (type = %X, subtype = %X)\n", infoType, subtype); regs[0] = Result::Success; switch (infoType) { case SystemInfoType::MemoryInformation: { switch (subtype) { // Total used memory size in the APPLICATION memory region case 1: regs[1] = mem.getUsedUserMem(); regs[2] = 0; break; default: Helpers::panic("GetSystemInfo: Unknown MemoryInformation subtype %x\n", subtype); regs[0] = Result::FailurePlaceholder; break; } break; } case SystemInfoType::CitraInformation: { switch (subtype) { case CitraInfoType::IsCitra: // Report that we're not Citra regs[1] = 0; regs[2] = 0; break; default: Helpers::warn("GetSystemInfo: Unknown CitraInformation subtype %x\n", subtype); regs[0] = Result::FailurePlaceholder; break; } break; } case SystemInfoType::PandaInformation: { switch (subtype) { case PandaInfoType::IsPanda: // This is indeed us, set output to 1 regs[1] = 1; regs[2] = 0; break; default: Helpers::warn("GetSystemInfo: Unknown PandaInformation subtype %x\n", subtype); regs[0] = Result::FailurePlaceholder; break; } break; } default: Helpers::panic("GetSystemInfo: Unknown system info type: %x (subtype: %x)\n", infoType, subtype); break; } } std::string Kernel::getProcessName(u32 pid) { if (pid == KernelHandles::CurrentProcess) { return "current"; } else { Helpers::panic("Attempted to name non-current process"); } }