metal: initial support

This commit is contained in:
Samuliak 2024-07-02 08:28:41 +02:00
parent 29d9ed7224
commit f0547d1a71
167 changed files with 28839 additions and 1271 deletions

View file

@ -12,34 +12,36 @@ static const char* arbitrationTypeToString(u32 type) {
}
}
Handle Kernel::makeArbiter() {
HandleType Kernel::makeArbiter() {
if (arbiterCount >= appResourceLimits.maxAddressArbiters) {
Helpers::panic("Overflowed the number of address arbiters");
}
arbiterCount++;
Handle ret = makeObject(KernelObjectType::AddressArbiter);
HandleType ret = makeObject(KernelObjectType::AddressArbiter);
objects[ret].data = new AddressArbiter();
return ret;
}
// Result CreateAddressArbiter(Handle* arbiter)
// Result CreateAddressArbiter(HandleType* arbiter)
void Kernel::createAddressArbiter() {
logSVC("CreateAddressArbiter\n");
regs[0] = Result::Success;
regs[1] = makeArbiter();
}
// Result ArbitrateAddress(Handle arbiter, u32 addr, ArbitrationType type, s32 value, s64 nanoseconds)
// Result ArbitrateAddress(HandleType arbiter, u32 addr, ArbitrationType type, s32 value, s64 nanoseconds)
void Kernel::arbitrateAddress() {
const Handle handle = regs[0];
const HandleType handle = regs[0];
const u32 address = regs[1];
const u32 type = regs[2];
const s32 value = s32(regs[3]);
const s64 ns = s64(u64(regs[4]) | (u64(regs[5]) << 32));
logSVC("ArbitrateAddress(Handle = %X, address = %08X, type = %s, value = %d, ns = %lld)\n", handle, address,
arbitrationTypeToString(type), value, ns);
logSVC(
"ArbitrateAddress(HandleType = %X, address = %08X, type = %s, value = %d, ns = %lld)\n", handle, address, arbitrationTypeToString(type),
value, ns
);
const auto arbiter = getObject(handle, KernelObjectType::AddressArbiter);
if (arbiter == nullptr) [[unlikely]] {
@ -61,7 +63,7 @@ void Kernel::arbitrateAddress() {
switch (static_cast<ArbitrationType>(type)) {
// Puts this thread to sleep if word < value until another thread arbitrates the address using SIGNAL
case ArbitrationType::WaitIfLess: {
s32 word = static_cast<s32>(mem.read32(address)); // Yes this is meant to be signed
s32 word = static_cast<s32>(mem.read32(address)); // Yes this is meant to be signed
if (word < value) {
sleepThreadOnArbiter(address);
}
@ -71,7 +73,7 @@ void Kernel::arbitrateAddress() {
// Puts this thread to sleep if word < value until another thread arbitrates the address using SIGNAL
// If the thread is put to sleep, the arbiter address is decremented
case ArbitrationType::DecrementAndWaitIfLess: {
s32 word = static_cast<s32>(mem.read32(address)); // Yes this is meant to be signed
s32 word = static_cast<s32>(mem.read32(address)); // Yes this is meant to be signed
if (word < value) {
mem.write32(address, word - 1);
sleepThreadOnArbiter(address);
@ -79,12 +81,9 @@ void Kernel::arbitrateAddress() {
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));
}
requireReschedule();
@ -92,8 +91,9 @@ void Kernel::arbitrateAddress() {
// 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
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) {
@ -106,4 +106,4 @@ void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) {
if (count == threadCount && threadCount > 0) break;
}
}
}
}

View file

@ -8,10 +8,7 @@
#include "kernel.hpp"
namespace DirectoryOps {
enum : u32 {
Read = 0x08010042,
Close = 0x08020000
};
enum : u32 { Read = 0x08010042, Close = 0x08020000 };
}
// Helper to convert std::string to an 8.3 filename to mimic how Directory::Read works
@ -28,7 +25,7 @@ Filename83 convertTo83(const std::string& path) {
// Convert a character to add it to the 8.3 name
// "Characters such as + are changed to the underscore _, and letters are put in uppercase"
// For now we put letters in uppercase until we find out what is supposed to be converted to _ and so on
auto convertCharacter = [](char c) { return (char) std::toupper(c); };
auto convertCharacter = [](char c) { return (char)std::toupper(c); };
// List of forbidden character for 8.3 filenames, from Citra
// TODO: Use constexpr when C++20 support is solid
@ -66,7 +63,7 @@ Filename83 convertTo83(const std::string& path) {
filenameTooBig = true;
break;
}
filename[validCharacterCount++] = convertCharacter(c); // Append character to filename
filename[validCharacterCount++] = convertCharacter(c); // Append character to filename
}
// Truncate name to 6 characters and denote that it is too big
@ -87,7 +84,7 @@ Filename83 convertTo83(const std::string& path) {
return {filename, extension};
}
void Kernel::handleDirectoryOperation(u32 messagePointer, Handle directory) {
void Kernel::handleDirectoryOperation(u32 messagePointer, HandleType directory) {
const u32 cmd = mem.read32(messagePointer);
switch (cmd) {
case DirectoryOps::Close: closeDirectory(messagePointer, directory); break;
@ -96,7 +93,7 @@ void Kernel::handleDirectoryOperation(u32 messagePointer, Handle directory) {
}
}
void Kernel::closeDirectory(u32 messagePointer, Handle directory) {
void Kernel::closeDirectory(u32 messagePointer, HandleType directory) {
logFileIO("Closed directory %X\n", directory);
const auto p = getObject(directory, KernelObjectType::Directory);
@ -109,11 +106,11 @@ void Kernel::closeDirectory(u32 messagePointer, Handle directory) {
mem.write32(messagePointer + 4, Result::Success);
}
void Kernel::readDirectory(u32 messagePointer, Handle directory) {
void Kernel::readDirectory(u32 messagePointer, HandleType directory) {
const u32 entryCount = mem.read32(messagePointer + 4);
const u32 outPointer = mem.read32(messagePointer + 12);
logFileIO("Directory::Read (handle = %X, entry count = %d, out pointer = %08X)\n", directory, entryCount, outPointer);
const auto p = getObject(directory, KernelObjectType::Directory);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called ReadDirectory on non-existent directory");
@ -136,9 +133,9 @@ void Kernel::readDirectory(u32 messagePointer, Handle directory) {
bool isDirectory = std::filesystem::is_directory(relative);
std::u16string nameU16 = relative.u16string();
bool isHidden = nameU16[0] == u'.'; // If the first character is a dot then this is a hidden file/folder
bool isHidden = nameU16[0] == u'.'; // If the first character is a dot then this is a hidden file/folder
const u32 entryPointer = outPointer + (count * 0x228); // 0x228 is the size of a single entry
const u32 entryPointer = outPointer + (count * 0x228); // 0x228 is the size of a single entry
u32 utfPointer = entryPointer;
u32 namePointer = entryPointer + 0x20C;
u32 extensionPointer = entryPointer + 0x216;
@ -152,7 +149,7 @@ void Kernel::readDirectory(u32 messagePointer, Handle directory) {
mem.write16(utfPointer, u16(c));
utfPointer += sizeof(u16);
}
mem.write16(utfPointer, 0); // Null terminate the UTF16 name
mem.write16(utfPointer, 0); // Null terminate the UTF16 name
// Write 8.3 filename-extension
for (auto c : shortFilename) {

View file

@ -1,38 +1,25 @@
#include "kernel.hpp"
namespace Commands {
enum : u32 {
Throw = 0x00010800
};
enum : u32 { Throw = 0x00010800 };
}
namespace FatalErrorType {
enum : u32 {
Generic = 0,
Corrupted = 1,
CardRemoved = 2,
Exception = 3,
ResultFailure = 4,
Logged = 5
};
enum : u32 { Generic = 0, Corrupted = 1, CardRemoved = 2, Exception = 3, ResultFailure = 4, Logged = 5 };
}
// Handle SendSyncRequest targetting the err:f port
// HandleType SendSyncRequest targetting the err:f port
void Kernel::handleErrorSyncRequest(u32 messagePointer) {
u32 cmd = mem.read32(messagePointer);
switch (cmd) {
case Commands::Throw:
throwError(messagePointer);
break;
case Commands::Throw: throwError(messagePointer); break;
default:
Helpers::panic("Unimplemented err:f command %08X\n", cmd);
break;
default: Helpers::panic("Unimplemented err:f command %08X\n", cmd); break;
}
}
void Kernel::throwError(u32 messagePointer) {
const auto type = mem.read8(messagePointer + 4); // Fatal error type
const auto type = mem.read8(messagePointer + 4); // Fatal error type
const u32 pc = mem.read32(messagePointer + 12);
const u32 pid = mem.read32(messagePointer + 16);
logError("Thrown fatal error @ %08X (pid = %X, type = %d)\n", pc, pid, type);
@ -44,4 +31,4 @@ void Kernel::throwError(u32 messagePointer) {
}
Helpers::panic("Thrown fatal error");
}
}

View file

@ -1,8 +1,9 @@
#include "kernel.hpp"
#include "cpu.hpp"
#include <bit>
#include <utility>
#include "cpu.hpp"
#include "kernel.hpp"
const char* Kernel::resetTypeToString(u32 type) {
switch (type) {
case 0: return "One shot";
@ -12,13 +13,13 @@ const char* Kernel::resetTypeToString(u32 type) {
}
}
Handle Kernel::makeEvent(ResetType resetType, Event::CallbackType callback) {
Handle ret = makeObject(KernelObjectType::Event);
HandleType Kernel::makeEvent(ResetType resetType, Event::CallbackType callback) {
HandleType ret = makeObject(KernelObjectType::Event);
objects[ret].data = new Event(resetType, callback);
return ret;
}
bool Kernel::signalEvent(Handle handle) {
bool Kernel::signalEvent(HandleType handle) {
KernelObject* object = getObject(handle, KernelObjectType::Event);
if (object == nullptr) [[unlikely]] {
Helpers::panic("Tried to signal non-existent event");
@ -52,13 +53,12 @@ bool Kernel::signalEvent(Handle handle) {
return true;
}
// Result CreateEvent(Handle* event, ResetType resetType)
// Result CreateEvent(HandleType* event, ResetType resetType)
void Kernel::svcCreateEvent() {
const u32 outPointer = regs[0];
const u32 resetType = regs[1];
if (resetType > 2)
Helpers::panic("Invalid reset type for event %d", resetType);
if (resetType > 2) Helpers::panic("Invalid reset type for event %d", resetType);
logSVC("CreateEvent(handle pointer = %08X, resetType = %s)\n", outPointer, resetTypeToString(resetType));
@ -66,9 +66,9 @@ void Kernel::svcCreateEvent() {
regs[1] = makeEvent(static_cast<ResetType>(resetType));
}
// Result ClearEvent(Handle event)
// Result ClearEvent(HandleType event)
void Kernel::svcClearEvent() {
const Handle handle = regs[0];
const HandleType handle = regs[0];
const auto event = getObject(handle, KernelObjectType::Event);
logSVC("ClearEvent(event handle = %X)\n", handle);
@ -82,9 +82,9 @@ void Kernel::svcClearEvent() {
regs[0] = Result::Success;
}
// Result SignalEvent(Handle event)
// Result SignalEvent(HandleType event)
void Kernel::svcSignalEvent() {
const Handle handle = regs[0];
const HandleType handle = regs[0];
logSVC("SignalEvent(event handle = %X)\n", handle);
KernelObject* object = getObject(handle, KernelObjectType::Event);
@ -98,9 +98,9 @@ void Kernel::svcSignalEvent() {
}
}
// Result WaitSynchronization1(Handle handle, s64 timeout_nanoseconds)
// Result WaitSynchronization1(HandleType handle, s64 timeout_nanoseconds)
void Kernel::waitSynchronization1() {
const Handle handle = regs[0];
const HandleType handle = regs[0];
const s64 ns = s64(u64(regs[2]) | (u64(regs[3]) << 32));
logSVC("WaitSynchronization1(handle = %X, ns = %lld)\n", handle, ns);
@ -117,7 +117,7 @@ void Kernel::waitSynchronization1() {
}
if (!shouldWaitOnObject(object)) {
acquireSyncObject(object, threads[currentThreadIndex]); // Acquire the object since it's ready
acquireSyncObject(object, threads[currentThreadIndex]); // Acquire the object since it's ready
regs[0] = Result::Success;
} else {
// Timeout is 0, don't bother waiting, instantly timeout
@ -126,7 +126,7 @@ void Kernel::waitSynchronization1() {
return;
}
regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout
regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout
auto& t = threads[currentThreadIndex];
t.waitList.resize(1);
@ -141,7 +141,7 @@ void Kernel::waitSynchronization1() {
}
}
// Result WaitSynchronizationN(s32* out, Handle* handles, s32 handlecount, bool waitAll, s64 timeout_nanoseconds)
// Result WaitSynchronizationN(s32* out, HandleType* handles, s32 handlecount, bool waitAll, s64 timeout_nanoseconds)
void Kernel::waitSynchronizationN() {
// TODO: Are these arguments even correct?
s32 ns1 = regs[0];
@ -149,13 +149,12 @@ void Kernel::waitSynchronizationN() {
s32 handleCount = regs[2];
bool waitAll = regs[3] != 0;
u32 ns2 = regs[4];
s32 outPointer = regs[5]; // "out" pointer - shows which object got bonked if we're waiting on multiple objects
s32 outPointer = regs[5]; // "out" pointer - shows which object got bonked if we're waiting on multiple objects
s64 ns = s64(ns1) | (s64(ns2) << 32);
logSVC("WaitSynchronizationN (handle pointer: %08X, count: %d, timeout = %lld)\n", handles, handleCount, ns);
if (handleCount <= 0)
Helpers::panic("WaitSyncN: Invalid handle count");
if (handleCount <= 0) Helpers::panic("WaitSyncN: Invalid handle count");
// Temporary hack: Until we implement service sessions properly, don't bother sleeping when WaitSyncN targets a service handle
// This is necessary because a lot of games use WaitSyncN with eg the CECD service
@ -165,11 +164,11 @@ void Kernel::waitSynchronizationN() {
return;
}
using WaitObject = std::pair<Handle, KernelObject*>;
using WaitObject = std::pair<HandleType, KernelObject*>;
std::vector<WaitObject> waitObjects(handleCount);
// We don't actually need to wait if waitAll == true unless one of the objects is not ready
bool allReady = true; // Default initialize to true, set to fault if one of the objects is not ready
bool allReady = true; // Default initialize to true, set to fault if one of the objects is not ready
// Tracks whether at least one object is ready, + the index of the first ready object
// This is used when waitAll == false, because if one object is already available then we can skip the sleeping
@ -177,8 +176,8 @@ void Kernel::waitSynchronizationN() {
s32 firstReadyObjectIndex = 0;
for (s32 i = 0; i < handleCount; i++) {
Handle handle = mem.read32(handles);
handles += sizeof(Handle);
HandleType handle = mem.read32(handles);
handles += sizeof(HandleType);
auto object = getObject(handle);
// Panic if one of the objects is not even an object
@ -190,13 +189,12 @@ void Kernel::waitSynchronizationN() {
// Panic if one of the objects is not a valid sync object
if (!isWaitable(object)) [[unlikely]] {
Helpers::panic("Tried to wait on a non waitable object in WaitSyncN. Type: %s, handle: %X\n",
object->getTypeName(), handle);
Helpers::panic("Tried to wait on a non waitable object in WaitSyncN. Type: %s, handle: %X\n", object->getTypeName(), handle);
}
if (shouldWaitOnObject(object)) {
allReady = false; // Derp, not all objects are ready :(
} else { /// At least one object is ready to be acquired ahead of time. If it's the first one, write it down
allReady = false; // Derp, not all objects are ready :(
} else { /// At least one object is ready to be acquired ahead of time. If it's the first one, write it down
if (!oneObjectReady) {
oneObjectReady = true;
firstReadyObjectIndex = i;
@ -213,12 +211,12 @@ void Kernel::waitSynchronizationN() {
// If there's ready objects, acquire the first one and return
if (oneObjectReady) {
regs[0] = Result::Success;
regs[1] = firstReadyObjectIndex; // Return index of the acquired object
acquireSyncObject(waitObjects[firstReadyObjectIndex].second, t); // Acquire object
regs[1] = firstReadyObjectIndex; // Return index of the acquired object
acquireSyncObject(waitObjects[firstReadyObjectIndex].second, t); // Acquire object
return;
}
regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout
regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout
// If the thread wakes up without timeout, this will be adjusted to the index of the handle that woke us up
regs[1] = 0xFFFFFFFF;
t.waitList.resize(handleCount);
@ -227,8 +225,8 @@ void Kernel::waitSynchronizationN() {
t.wakeupTick = getWakeupTick(ns);
for (s32 i = 0; i < handleCount; i++) {
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
}
requireReschedule();
@ -243,4 +241,4 @@ void Kernel::runEventCallback(Event::CallbackType callback) {
case Event::CallbackType::DSPSemaphore: serviceManager.getDSP().onSemaphoreEventSignal(); break;
default: Helpers::panic("Unimplemented special callback for kernel event!"); break;
}
}
}

View file

@ -14,8 +14,7 @@ namespace FileOps {
};
}
void Kernel::handleFileOperation(u32 messagePointer, Handle file) {
void Kernel::handleFileOperation(u32 messagePointer, HandleType file) {
const u32 cmd = mem.read32(messagePointer);
switch (cmd) {
case FileOps::Close: closeFile(messagePointer, file); break;
@ -30,7 +29,7 @@ void Kernel::handleFileOperation(u32 messagePointer, Handle file) {
}
}
void Kernel::closeFile(u32 messagePointer, Handle fileHandle) {
void Kernel::closeFile(u32 messagePointer, HandleType fileHandle) {
logFileIO("Closed file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
@ -48,7 +47,7 @@ void Kernel::closeFile(u32 messagePointer, Handle fileHandle) {
mem.write32(messagePointer + 4, Result::Success);
}
void Kernel::flushFile(u32 messagePointer, Handle fileHandle) {
void Kernel::flushFile(u32 messagePointer, HandleType fileHandle) {
logFileIO("Flushed file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
@ -65,13 +64,12 @@ void Kernel::flushFile(u32 messagePointer, Handle fileHandle) {
mem.write32(messagePointer + 4, Result::Success);
}
void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
void Kernel::readFile(u32 messagePointer, HandleType fileHandle) {
u64 offset = mem.read64(messagePointer + 4);
u32 size = mem.read32(messagePointer + 12);
u32 dataPointer = mem.read32(messagePointer + 20);
logFileIO("Trying to read %X bytes from file %X, starting from offset %llX into memory address %08X\n",
size, fileHandle, offset, dataPointer);
logFileIO("Trying to read %X bytes from file %X, starting from offset %llX into memory address %08X\n", size, fileHandle, offset, dataPointer);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
@ -85,7 +83,7 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
Helpers::panic("Tried to read closed file");
}
// Handle files with their own file descriptors by just fread'ing the data
// HandleType files with their own file descriptors by just fread'ing the data
if (file->fd) {
std::unique_ptr<u8[]> data(new u8[size]);
IOFile f(file->fd);
@ -94,8 +92,7 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
if (!success) {
Helpers::panic("Kernel::ReadFile with file descriptor failed");
}
else {
} else {
for (size_t i = 0; i < bytesRead; i++) {
mem.write8(u32(dataPointer + i), data[i]);
}
@ -107,7 +104,7 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
return;
}
// Handle files without their own FD, such as SelfNCCH files
// HandleType files without their own FD, such as SelfNCCH files
auto archive = file->archive;
std::optional<u32> bytesRead = archive->readFile(file, offset, size, dataPointer);
if (!bytesRead.has_value()) {
@ -118,14 +115,13 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
}
}
void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
void Kernel::writeFile(u32 messagePointer, HandleType fileHandle) {
u64 offset = mem.read64(messagePointer + 4);
u32 size = mem.read32(messagePointer + 12);
u32 writeOption = mem.read32(messagePointer + 16);
u32 dataPointer = mem.read32(messagePointer + 24);
logFileIO("Trying to write %X bytes to file %X, starting from file offset %llX and memory address %08X\n",
size, fileHandle, offset, dataPointer);
logFileIO("Trying to write %X bytes to file %X, starting from file offset %llX and memory address %08X\n", size, fileHandle, offset, dataPointer);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
@ -137,8 +133,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
Helpers::panic("Tried to write closed file");
}
if (!file->fd)
Helpers::panic("[Kernel::File::WriteFile] Tried to write to file without a valid file descriptor");
if (!file->fd) Helpers::panic("[Kernel::File::WriteFile] Tried to write to file without a valid file descriptor");
std::unique_ptr<u8[]> data(new u8[size]);
for (size_t i = 0; i < size; i++) {
@ -162,7 +157,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
}
}
void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) {
void Kernel::setFileSize(u32 messagePointer, HandleType fileHandle) {
logFileIO("Setting size of file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
@ -191,7 +186,7 @@ void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) {
}
}
void Kernel::getFileSize(u32 messagePointer, Handle fileHandle) {
void Kernel::getFileSize(u32 messagePointer, HandleType fileHandle) {
logFileIO("Getting size of file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
@ -220,7 +215,7 @@ void Kernel::getFileSize(u32 messagePointer, Handle fileHandle) {
}
}
void Kernel::openLinkFile(u32 messagePointer, Handle fileHandle) {
void Kernel::openLinkFile(u32 messagePointer, HandleType fileHandle) {
logFileIO("Open link file (clone) of file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
@ -247,7 +242,7 @@ void Kernel::openLinkFile(u32 messagePointer, Handle fileHandle) {
mem.write32(messagePointer + 12, handle);
}
void Kernel::setFilePriority(u32 messagePointer, Handle fileHandle) {
void Kernel::setFilePriority(u32 messagePointer, HandleType fileHandle) {
const u32 priority = mem.read32(messagePointer + 4);
logFileIO("Setting priority of file %X to %d\n", fileHandle, priority);

View file

@ -1,11 +1,13 @@
#include <cassert>
#include "kernel.hpp"
#include "kernel_types.hpp"
#include <cassert>
#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) {
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
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);
@ -17,7 +19,7 @@ Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
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
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;
@ -79,12 +81,12 @@ 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
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);
HandleType Kernel::makeProcess(u32 id) {
const HandleType processHandle = makeObject(KernelObjectType::Process);
const HandleType resourceLimitHandle = makeObject(KernelObjectType::ResourceLimit);
// Allocate data
objects[processHandle].data = new Process(id);
@ -98,7 +100,7 @@ Handle Kernel::makeProcess(u32 id) {
// 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) {
KernelObject* Kernel::getProcessFromPID(HandleType handle) {
if (handle == KernelHandles::CurrentProcess) [[likely]] {
return getObject(currentProcess, KernelObjectType::Process);
} else {
@ -142,7 +144,7 @@ void Kernel::reset() {
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
t.threadsWaitingForTermination = 0; // No threads are waiting for this thread to terminate cause it's dead
}
for (auto& object : objects) {
@ -159,7 +161,7 @@ void Kernel::reset() {
// 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
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,
@ -169,19 +171,17 @@ void Kernel::reset() {
setupIdleThread();
// Create some of the OS ports
srvHandle = makePort("srv:"); // Service manager port
errorPortHandle = makePort("err:f"); // Error display port
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;
}
u32 Kernel::getTLSPointer() { return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; }
// Result CloseHandle(Handle handle)
// Result CloseHandle(HandleType handle)
void Kernel::svcCloseHandle() {
logSVC("CloseHandle(handle = %d) (Unimplemented)\n", regs[0]);
const Handle handle = regs[0];
const HandleType handle = regs[0];
KernelObject* object = getObject(handle);
if (object != nullptr) {
@ -242,7 +242,7 @@ void Kernel::getProcessID() {
regs[1] = process->getData<Process>()->id;
}
// Result GetProcessInfo(s64* out, Handle process, ProcessInfoType type)
// Result GetProcessInfo(s64* out, HandleType process, ProcessInfoType type)
void Kernel::getProcessInfo() {
const auto pid = regs[1];
const auto type = regs[2];
@ -269,26 +269,25 @@ void Kernel::getProcessInfo() {
regs[2] = 0;
break;
case 20: // Returns 0x20000000 - <linear memory base vaddr for process>
case 20: // Returns 0x20000000 - <linear memory base vaddr for process>
regs[1] = PhysicalAddrs::FCRAM - mem.getLinearHeapVaddr();
regs[2] = 0;
break;
default:
Helpers::panic("GetProcessInfo: unimplemented type %d", type);
default: Helpers::panic("GetProcessInfo: unimplemented type %d", type);
}
regs[0] = Result::Success;
}
// Result DuplicateHandle(Handle* out, Handle original)
// Result DuplicateHandle(HandleType* out, HandleType original)
void Kernel::duplicateHandle() {
Handle original = regs[1];
HandleType original = regs[1];
logSVC("DuplicateHandle(handle = %X)\n", original);
if (original == KernelHandles::CurrentThread) {
regs[0] = Result::Success;
Handle ret = makeObject(KernelObjectType::Thread);
HandleType ret = makeObject(KernelObjectType::Thread);
objects[ret].data = &threads[currentThreadIndex];
regs[1] = ret;
@ -379,7 +378,7 @@ void Kernel::getSystemInfo() {
regs[2] = 0;
break;
default:
default:
Helpers::warn("GetSystemInfo: Unknown PandaInformation subtype %x\n", subtype);
regs[0] = Result::FailurePlaceholder;
break;

View file

@ -17,37 +17,35 @@ namespace Operation {
namespace MemoryPermissions {
enum : u32 {
None = 0, // ---
Read = 1, // R--
Write = 2, // -W-
ReadWrite = 3, // RW-
Execute = 4, // --X
ReadExecute = 5, // R-X
WriteExecute = 6, // -WX
ReadWriteExecute = 7, // RWX
None = 0, // ---
Read = 1, // R--
Write = 2, // -W-
ReadWrite = 3, // RW-
Execute = 4, // --X
ReadExecute = 5, // R-X
WriteExecute = 6, // -WX
ReadWriteExecute = 7, // RWX
DontCare = 0x10000000
};
}
// Returns whether "value" is aligned to a page boundary (Ie a boundary of 4096 bytes)
static constexpr bool isAligned(u32 value) {
return (value & 0xFFF) == 0;
}
static constexpr bool isAligned(u32 value) { return (value & 0xFFF) == 0; }
// Result ControlMemory(u32* outaddr, u32 addr0, u32 addr1, u32 size,
// MemoryOperation operation, MemoryPermission permissions)
// This has a weird ABI documented here https://www.3dbrew.org/wiki/Kernel_ABI
// TODO: Does this need to write to outaddr?
void Kernel::controlMemory() {
u32 operation = regs[0]; // The base address is written here
u32 operation = regs[0]; // The base address is written here
u32 addr0 = regs[1];
u32 addr1 = regs[2];
u32 size = regs[3];
u32 perms = regs[4];
if (perms == MemoryPermissions::DontCare) {
perms = MemoryPermissions::ReadWrite; // We make "don't care" equivalent to read-write
perms = MemoryPermissions::ReadWrite; // We make "don't care" equivalent to read-write
Helpers::panic("Unimplemented allocation permission: DONTCARE");
}
@ -57,33 +55,33 @@ void Kernel::controlMemory() {
bool x = perms & 0b100;
bool linear = operation & Operation::Linear;
if (x)
Helpers::panic("ControlMemory: attempted to allocate executable memory");
if (x) Helpers::panic("ControlMemory: attempted to allocate executable memory");
if (!isAligned(addr0) || !isAligned(addr1) || !isAligned(size)) {
Helpers::panic("ControlMemory: Unaligned parameters\nAddr0: %08X\nAddr1: %08X\nSize: %08X", addr0, addr1, size);
}
logSVC("ControlMemory(addr0 = %08X, addr1 = %08X, size = %08X, operation = %X (%c%c%c)%s\n",
addr0, addr1, size, operation, r ? 'r' : '-', w ? 'w' : '-', x ? 'x' : '-', linear ? ", linear" : ""
logSVC(
"ControlMemory(addr0 = %08X, addr1 = %08X, size = %08X, operation = %X (%c%c%c)%s\n", addr0, addr1, size, operation, r ? 'r' : '-',
w ? 'w' : '-', x ? 'x' : '-', linear ? ", linear" : ""
);
switch (operation & 0xFF) {
case Operation::Commit: {
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear, r, w, x, true);
if (!address.has_value())
Helpers::panic("ControlMemory: Failed to allocate memory");
if (!address.has_value()) Helpers::panic("ControlMemory: Failed to allocate memory");
regs[1] = address.value();
break;
}
case Operation::Map:
mem.mirrorMapping(addr0, addr1, size);
break;
case Operation::Map: mem.mirrorMapping(addr0, addr1, size); break;
case Operation::Protect:
Helpers::warn("Ignoring mprotect! Hope nothing goes wrong but if the game accesses invalid memory or crashes then we prolly need to implement this\n");
Helpers::warn(
"Ignoring mprotect! Hope nothing goes wrong but if the game accesses invalid memory or crashes then we prolly need to implement "
"this\n"
);
break;
default: Helpers::warn("ControlMemory: unknown operation %X\n", operation); break;
@ -106,12 +104,12 @@ void Kernel::queryMemory() {
regs[2] = info.size;
regs[3] = info.perms;
regs[4] = info.state;
regs[5] = 0; // page flags
regs[5] = 0; // page flags
}
// Result MapMemoryBlock(Handle memblock, u32 addr, MemoryPermission myPermissions, MemoryPermission otherPermission)
// Result MapMemoryBlock(HandleType memblock, u32 addr, MemoryPermission myPermissions, MemoryPermission otherPermission)
void Kernel::mapMemoryBlock() {
const Handle block = regs[0];
const HandleType block = regs[0];
u32 addr = regs[1];
const u32 myPerms = regs[2];
const u32 otherPerms = regs[3];
@ -123,21 +121,15 @@ void Kernel::mapMemoryBlock() {
if (KernelHandles::isSharedMemHandle(block)) {
if (block == KernelHandles::FontSharedMemHandle && addr == 0) addr = 0x18000000;
u8* ptr = mem.mapSharedMemory(block, addr, myPerms, otherPerms); // Map shared memory block
u8* ptr = mem.mapSharedMemory(block, addr, myPerms, otherPerms); // Map shared memory block
// Pass pointer to shared memory to the appropriate service
switch (block) {
case KernelHandles::HIDSharedMemHandle:
serviceManager.setHIDSharedMem(ptr);
break;
case KernelHandles::HIDSharedMemHandle: serviceManager.setHIDSharedMem(ptr); break;
case KernelHandles::GSPSharedMemHandle:
serviceManager.setGSPSharedMem(ptr);
break;
case KernelHandles::GSPSharedMemHandle: serviceManager.setGSPSharedMem(ptr); break;
case KernelHandles::FontSharedMemHandle:
mem.copySharedFont(ptr);
break;
case KernelHandles::FontSharedMemHandle: mem.copySharedFont(ptr); break;
case KernelHandles::CSNDSharedMemHandle:
serviceManager.setCSNDSharedMem(ptr);
@ -154,8 +146,8 @@ void Kernel::mapMemoryBlock() {
regs[0] = Result::Success;
}
Handle Kernel::makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission) {
Handle ret = makeObject(KernelObjectType::MemoryBlock);
HandleType Kernel::makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission) {
HandleType ret = makeObject(KernelObjectType::MemoryBlock);
objects[ret].data = new MemoryBlock(addr, size, myPermission, otherPermission);
return ret;
@ -165,7 +157,7 @@ void Kernel::createMemoryBlock() {
const u32 addr = regs[1];
const u32 size = regs[2];
u32 myPermission = regs[3];
u32 otherPermission = mem.read32(regs[13] + 4); // This is placed on the stack rather than r4
u32 otherPermission = mem.read32(regs[13] + 4); // This is placed on the stack rather than r4
logSVC("CreateMemoryBlock (addr = %08X, size = %08X, myPermission = %d, otherPermission = %d)\n", addr, size, myPermission, otherPermission);
// Returns whether a permission is valid
@ -175,10 +167,9 @@ void Kernel::createMemoryBlock() {
case MemoryPermissions::Read:
case MemoryPermissions::Write:
case MemoryPermissions::ReadWrite:
case MemoryPermissions::DontCare:
return true;
case MemoryPermissions::DontCare: return true;
default: // Permissions with the executable flag enabled or invalid permissions are not allowed
default: // Permissions with the executable flag enabled or invalid permissions are not allowed
return false;
}
};
@ -197,8 +188,7 @@ void Kernel::createMemoryBlock() {
// TODO: The address needs to be in a specific range otherwise it throws an invalid address error
if (addr == 0)
Helpers::panic("CreateMemoryBlock: Tried to use addr = 0");
if (addr == 0) Helpers::panic("CreateMemoryBlock: Tried to use addr = 0");
// Implement "Don't care" permission as RW
if (myPermission == MemoryPermissions::DontCare) myPermission = MemoryPermissions::ReadWrite;
@ -209,7 +199,7 @@ void Kernel::createMemoryBlock() {
}
void Kernel::unmapMemoryBlock() {
Handle block = regs[0];
HandleType block = regs[0];
u32 addr = regs[1];
logSVC("Unmap memory block (block handle = %X, addr = %08X)\n", block, addr);

View file

@ -1,29 +1,30 @@
#include "kernel.hpp"
#include <cstring>
Handle Kernel::makePort(const char* name) {
Handle ret = makeObject(KernelObjectType::Port);
portHandles.push_back(ret); // Push the port handle to our cache of port handles
#include "kernel.hpp"
HandleType Kernel::makePort(const char* name) {
HandleType ret = makeObject(KernelObjectType::Port);
portHandles.push_back(ret); // Push the port handle to our cache of port handles
objects[ret].data = new Port(name);
return ret;
}
Handle Kernel::makeSession(Handle portHandle) {
HandleType Kernel::makeSession(HandleType portHandle) {
const auto port = getObject(portHandle, KernelObjectType::Port);
if (port == nullptr) [[unlikely]] {
Helpers::panic("Trying to make session for non-existent port");
}
// Allocate data for session
const Handle ret = makeObject(KernelObjectType::Session);
const HandleType ret = makeObject(KernelObjectType::Session);
objects[ret].data = new Session(portHandle);
return ret;
}
// Get the handle of a port based on its name
// If there's no such port, return nullopt
std::optional<Handle> Kernel::getPortHandle(const char* name) {
std::optional<HandleType> Kernel::getPortHandle(const char* name) {
for (auto handle : portHandles) {
const auto data = objects[handle].getData<Port>();
if (std::strncmp(name, data->name, Port::maxNameLen) == 0) {
@ -34,7 +35,7 @@ std::optional<Handle> Kernel::getPortHandle(const char* name) {
return std::nullopt;
}
// Result ConnectToPort(Handle* out, const char* portName)
// Result ConnectToPort(HandleType* out, const char* portName)
void Kernel::connectToPort() {
const u32 handlePointer = regs[0];
// Read up to max + 1 characters to see if the name is too long
@ -48,14 +49,14 @@ void Kernel::connectToPort() {
}
// Try getting a handle to the port
std::optional<Handle> optionalHandle = getPortHandle(port.c_str());
std::optional<HandleType> optionalHandle = getPortHandle(port.c_str());
if (!optionalHandle.has_value()) [[unlikely]] {
Helpers::panic("ConnectToPort: Port doesn't exist\n");
regs[0] = Result::Kernel::NotFound;
return;
}
Handle portHandle = optionalHandle.value();
HandleType portHandle = optionalHandle.value();
const auto portData = objects[portHandle].getData<Port>();
if (!portData->isPublic) {
@ -63,17 +64,17 @@ void Kernel::connectToPort() {
}
// TODO: Actually create session
Handle sessionHandle = makeSession(portHandle);
HandleType sessionHandle = makeSession(portHandle);
regs[0] = Result::Success;
regs[1] = sessionHandle;
}
// Result SendSyncRequest(Handle session)
// Result SendSyncRequest(HandleType session)
// Send an IPC message to a port (typically "srv:") or a service
void Kernel::sendSyncRequest() {
const auto handle = regs[0];
u32 messagePointer = getTLSPointer() + 0x80; // The message is stored starting at TLS+0x80
u32 messagePointer = getTLSPointer() + 0x80; // The message is stored starting at TLS+0x80
logSVC("SendSyncRequest(session handle = %X)\n", handle);
// Service calls via SendSyncRequest and file access needs to put the caller to sleep for a given amount of time
@ -93,7 +94,7 @@ void Kernel::sendSyncRequest() {
// Check if our sync request is targetting a file instead of a service
bool isFileOperation = getObject(handle, KernelObjectType::File) != nullptr;
if (isFileOperation) {
regs[0] = Result::Success; // r0 goes first here too
regs[0] = Result::Success; // r0 goes first here too
handleFileOperation(messagePointer, handle);
return;
}
@ -101,7 +102,7 @@ void Kernel::sendSyncRequest() {
// Check if our sync request is targetting a directory instead of a service
bool isDirectoryOperation = getObject(handle, KernelObjectType::Directory) != nullptr;
if (isDirectoryOperation) {
regs[0] = Result::Success; // r0 goes first here too
regs[0] = Result::Success; // r0 goes first here too
handleDirectoryOperation(messagePointer, handle);
return;
}
@ -115,12 +116,12 @@ void Kernel::sendSyncRequest() {
}
const auto sessionData = static_cast<Session*>(session->data);
const Handle portHandle = sessionData->portHandle;
const HandleType portHandle = sessionData->portHandle;
if (portHandle == srvHandle) { // Special-case SendSyncRequest targetting the "srv: port"
if (portHandle == srvHandle) { // Special-case SendSyncRequest targetting the "srv: port"
regs[0] = Result::Success;
serviceManager.handleSyncRequest(messagePointer);
} else if (portHandle == errorPortHandle) { // Special-case "err:f" for juicy logs too
} else if (portHandle == errorPortHandle) { // Special-case "err:f" for juicy logs too
regs[0] = Result::Success;
handleErrorSyncRequest(messagePointer);
} else {

View file

@ -1,7 +1,8 @@
#include "resource_limits.hpp"
#include "kernel.hpp"
// Result GetResourceLimit(Handle* resourceLimit, Handle process)
// Result GetResourceLimit(HandleType* resourceLimit, HandleType process)
// out: r0 -> result, r1 -> handle
void Kernel::getResourceLimit() {
const auto handlePointer = regs[0];
@ -20,10 +21,10 @@ void Kernel::getResourceLimit() {
regs[1] = processData->limits.handle;
}
// Result GetResourceLimitLimitValues(s64* values, Handle resourceLimit, LimitableResource* names, s32 nameCount)
// Result GetResourceLimitLimitValues(s64* values, HandleType resourceLimit, LimitableResource* names, s32 nameCount)
void Kernel::getResourceLimitLimitValues() {
u32 values = regs[0]; // Pointer to values (The resource limits get output here)
const Handle resourceLimit = regs[1];
u32 values = regs[0]; // Pointer to values (The resource limits get output here)
const HandleType resourceLimit = regs[1];
u32 names = regs[2]; // Pointer to resources that we should return
u32 count = regs[3]; // Number of resources
@ -49,10 +50,10 @@ void Kernel::getResourceLimitLimitValues() {
regs[0] = Result::Success;
}
// Result GetResourceLimitCurrentValues(s64* values, Handle resourceLimit, LimitableResource* names, s32 nameCount)
// Result GetResourceLimitCurrentValues(s64* values, HandleType resourceLimit, LimitableResource* names, s32 nameCount)
void Kernel::getResourceLimitCurrentValues() {
u32 values = regs[0]; // Pointer to values (The resource limits get output here)
const Handle resourceLimit = regs[1];
u32 values = regs[0]; // Pointer to values (The resource limits get output here)
const HandleType resourceLimit = regs[1];
u32 names = regs[2]; // Pointer to resources that we should return
u32 count = regs[3]; // Number of resources
logSVC("GetResourceLimitCurrentValues(values = %08X, handle = %X, names = %08X, count = %d)\n", values, resourceLimit, names, count);

View file

@ -33,7 +33,7 @@ void Kernel::switchThread(int newThreadIndex) {
std::memcpy(cpu.fprs().data(), newThread.fprs.data(), cpu.fprs().size_bytes()); // Load 32 FPRs
cpu.setCPSR(newThread.cpsr); // Load CPSR
cpu.setFPSCR(newThread.fpscr); // Load FPSCR
cpu.setTLSBase(newThread.tlsBase); // Load CP15 thread-local-storage pointer register
cpu.setTLSBase(newThread.tlsBase); // Load CP15 thread-local-storage pointer register
currentThreadIndex = newThreadIndex;
}
@ -42,21 +42,19 @@ void Kernel::switchThread(int newThreadIndex) {
// The threads with higher priority (aka the ones with a lower priority value) should come first in the vector
void Kernel::sortThreads() {
std::vector<int>& v = threadIndices;
std::sort(v.begin(), v.end(), [&](int a, int b) {
return threads[a].priority < threads[b].priority;
});
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;
} 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) {
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
return cpu.getTicks() >= t.wakeupTick;
}
// Handle timeouts and stuff here
// HandleType timeouts and stuff here
return false;
}
@ -100,8 +98,8 @@ void Kernel::rescheduleThreads() {
// Case 1: A thread can run
if (newThreadIndex.has_value()) {
switchThread(newThreadIndex.value());
}
}
// Case 2: No other thread can run, straight to the idle thread
else {
switchThread(idleThreadIndex);
@ -109,30 +107,30 @@ void Kernel::rescheduleThreads() {
}
// Internal OS function to spawn a thread
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status) {
int index; // Index of the created thread in the threads array
HandleType Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status) {
int index; // Index of the created thread in the threads array
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
index = threadCount++;
} else if (aliveThreadCount < appResourceLimits.maxThreads) { // If we have created many threads but at least one is dead & reusable
} else if (aliveThreadCount < appResourceLimits.maxThreads) { // If we have created many threads but at least one is dead & reusable
for (int i = 0; i < threads.size(); i++) {
if (threads[i].status == ThreadStatus::Dead) {
index = i;
break;
}
}
} else { // There is no thread we can use, we're screwed
} else { // There is no thread we can use, we're screwed
Helpers::panic("Overflowed thread count!!");
}
aliveThreadCount++;
threadIndices.push_back(index);
Thread& t = threads[index]; // Reference to thread data
Handle ret = makeObject(KernelObjectType::Thread);
Thread& t = threads[index]; // Reference to thread data
HandleType ret = makeObject(KernelObjectType::Thread);
objects[ret].data = &t;
const bool isThumb = (entrypoint & 1) != 0; // Whether the thread starts in thumb mode or not
const bool isThumb = (entrypoint & 1) != 0; // Whether the thread starts in thumb mode or not
// Set up initial thread context
t.gprs.fill(0);
@ -150,7 +148,7 @@ Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, Processor
t.status = status;
t.handle = ret;
t.waitingAddress = 0;
t.threadsWaitingForTermination = 0; // Thread just spawned, no other threads waiting for it to terminate
t.threadsWaitingForTermination = 0; // Thread just spawned, no other threads waiting for it to terminate
t.cpsr = CPSR::UserMode | (isThumb ? CPSR::Thumb : 0);
t.fpscr = FPSCR::ThreadDefault;
@ -161,8 +159,8 @@ Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, Processor
return ret;
}
Handle Kernel::makeMutex(bool locked) {
Handle ret = makeObject(KernelObjectType::Mutex);
HandleType Kernel::makeMutex(bool locked) {
HandleType ret = makeObject(KernelObjectType::Mutex);
objects[ret].data = new Mutex(locked, ret);
// If the mutex is initially locked, store the index of the thread that owns it and set lock count to 1
@ -181,15 +179,15 @@ Handle Kernel::makeMutex(bool locked) {
void Kernel::releaseMutex(Mutex* moo) {
// TODO: Assert lockCount > 0 before release, maybe. The SVC should be safe at least.
moo->lockCount--; // Decrement lock count
moo->lockCount--; // Decrement lock count
// If the lock count reached 0 then the thread no longer owns the mootex and it can be given to a new one
if (moo->lockCount == 0) {
moo->locked = false;
if (moo->waitlist != 0) {
int index = wakeupOneThread(moo->waitlist, moo->handle); // Wake up one thread and get its index
moo->waitlist ^= (1ull << index); // Remove thread from waitlist
int index = wakeupOneThread(moo->waitlist, moo->handle); // Wake up one thread and get its index
moo->waitlist ^= (1ull << index); // Remove thread from waitlist
// Have new thread acquire mutex
moo->locked = true;
@ -201,8 +199,8 @@ void Kernel::releaseMutex(Mutex* moo) {
}
}
Handle Kernel::makeSemaphore(u32 initialCount, u32 maximumCount) {
Handle ret = makeObject(KernelObjectType::Semaphore);
HandleType Kernel::makeSemaphore(u32 initialCount, u32 maximumCount) {
HandleType ret = makeObject(KernelObjectType::Semaphore);
objects[ret].data = new Semaphore(initialCount, maximumCount);
return ret;
@ -221,7 +219,7 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
switch (object->type) {
case KernelObjectType::Event: {
Event* e = object->getData<Event>();
if (e->resetType == ResetType::OneShot) { // One-shot events automatically get cleared after waking up a thread
if (e->resetType == ResetType::OneShot) { // One-shot events automatically get cleared after waking up a thread
e->fired = false;
}
break;
@ -245,15 +243,14 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
case KernelObjectType::Semaphore: {
Semaphore* s = object->getData<Semaphore>();
if (s->availableCount <= 0) [[unlikely]] // This should be unreachable but let's check anyways
if (s->availableCount <= 0) [[unlikely]] // This should be unreachable but let's check anyways
Helpers::panic("Tried to acquire unacquirable semaphore");
s->availableCount -= 1;
break;
}
case KernelObjectType::Thread:
break;
case KernelObjectType::Thread: break;
case KernelObjectType::Timer: {
Timer* timer = object->getData<Timer>();
@ -269,36 +266,36 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
// Wake up one of the threads in the waitlist (the one with highest prio) and return its index
// Must not be called with an empty waitlist
int Kernel::wakeupOneThread(u64 waitlist, Handle handle) {
int Kernel::wakeupOneThread(u64 waitlist, HandleType handle) {
if (waitlist == 0) [[unlikely]]
Helpers::panic("[Internal error] It shouldn't be possible to call wakeupOneThread when there's 0 threads waiting!");
// Find the waiting thread with the highest priority.
// We do this by first picking the first thread in the waitlist, then checking each other thread and comparing priority
int threadIndex = std::countr_zero(waitlist); // Index of first thread
int maxPriority = threads[threadIndex].priority; // Set initial max prio to the prio of the first thread
waitlist ^= (1ull << threadIndex); // Remove thread from the waitlist
int threadIndex = std::countr_zero(waitlist); // Index of first thread
int maxPriority = threads[threadIndex].priority; // Set initial max prio to the prio of the first thread
waitlist ^= (1ull << threadIndex); // Remove thread from the waitlist
while (waitlist != 0) {
int newThread = std::countr_zero(waitlist); // Get new thread and evaluate whether it has a higher priority
if (threads[newThread].priority < maxPriority) { // Low priority value means high priority
int newThread = std::countr_zero(waitlist); // Get new thread and evaluate whether it has a higher priority
if (threads[newThread].priority < maxPriority) { // Low priority value means high priority
threadIndex = newThread;
maxPriority = threads[newThread].priority;
}
waitlist ^= (1ull << threadIndex); // Remove thread from waitlist
waitlist ^= (1ull << threadIndex); // Remove thread from waitlist
}
Thread& t = threads[threadIndex];
switch (t.status) {
case ThreadStatus::WaitSync1:
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
break;
case ThreadStatus::WaitSyncAny:
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
// Get the index of the event in the object's waitlist, write it to r1
for (size_t i = 0; i < t.waitList.size(); i++) {
@ -309,44 +306,40 @@ int Kernel::wakeupOneThread(u64 waitlist, Handle handle) {
}
break;
case ThreadStatus::WaitSyncAll:
Helpers::panic("WakeupOneThread: Thread on WaitSyncAll");
break;
case ThreadStatus::WaitSyncAll: Helpers::panic("WakeupOneThread: Thread on WaitSyncAll"); break;
}
return threadIndex;
}
// Wake up every single thread in the waitlist using a bit scanning algorithm
void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) {
void Kernel::wakeupAllThreads(u64 waitlist, HandleType handle) {
while (waitlist != 0) {
const uint index = std::countr_zero(waitlist); // Get one of the set bits to see which thread is waiting
waitlist ^= (1ull << index); // Remove thread from waitlist by toggling its bit
const uint index = std::countr_zero(waitlist); // Get one of the set bits to see which thread is waiting
waitlist ^= (1ull << index); // Remove thread from waitlist by toggling its bit
// Get the thread we'll be signalling
Thread& t = threads[index];
switch (t.status) {
case ThreadStatus::WaitSync1:
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
break;
case ThreadStatus::WaitSync1:
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
break;
case ThreadStatus::WaitSyncAny:
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
case ThreadStatus::WaitSyncAny:
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // The thread did not timeout, so write success to r0
// Get the index of the event in the object's waitlist, write it to r1
for (size_t i = 0; i < t.waitList.size(); i++) {
if (t.waitList[i] == handle) {
t.gprs[1] = u32(i);
break;
// Get the index of the event in the object's waitlist, write it to r1
for (size_t i = 0; i < t.waitList.size(); i++) {
if (t.waitList[i] == handle) {
t.gprs[1] = u32(i);
break;
}
}
}
break;
break;
case ThreadStatus::WaitSyncAll:
Helpers::panic("WakeupAllThreads: Thread on WaitSyncAll");
break;
case ThreadStatus::WaitSyncAll: Helpers::panic("WakeupAllThreads: Thread on WaitSyncAll"); break;
}
}
}
@ -404,12 +397,11 @@ void Kernel::sleepThread(s64 ns) {
void Kernel::createThread() {
u32 priority = regs[0];
u32 entrypoint = regs[1];
u32 arg = regs[2]; // An argument value stored in r0 of the new thread
u32 initialSP = regs[3] & ~7; // SP is force-aligned to 8 bytes
u32 arg = regs[2]; // An argument value stored in r0 of the new thread
u32 initialSP = regs[3] & ~7; // SP is force-aligned to 8 bytes
s32 id = static_cast<s32>(regs[4]);
logSVC("CreateThread(entry = %08X, stacktop = %08X, arg = %X, priority = %X, processor ID = %d)\n", entrypoint,
initialSP, arg, priority, id);
logSVC("CreateThread(entry = %08X, stacktop = %08X, arg = %X, priority = %X, processor ID = %d)\n", entrypoint, initialSP, arg, priority, id);
if (priority > 0x3F) [[unlikely]] {
Helpers::panic("Created thread with bad priority value %X", priority);
@ -429,14 +421,14 @@ void Kernel::createThread() {
// void SleepThread(s64 nanoseconds)
void Kernel::svcSleepThread() {
const s64 ns = s64(u64(regs[0]) | (u64(regs[1]) << 32));
//logSVC("SleepThread(ns = %lld)\n", ns);
// logSVC("SleepThread(ns = %lld)\n", ns);
regs[0] = Result::Success;
sleepThread(ns);
}
void Kernel::getThreadID() {
Handle handle = regs[1];
HandleType handle = regs[1];
logSVC("GetThreadID(handle = %X)\n", handle);
if (handle == KernelHandles::CurrentThread) {
@ -456,7 +448,7 @@ void Kernel::getThreadID() {
}
void Kernel::getThreadPriority() {
const Handle handle = regs[1];
const HandleType handle = regs[1];
logSVC("GetThreadPriority (handle = %X)\n", handle);
if (handle == KernelHandles::CurrentThread) {
@ -474,7 +466,7 @@ void Kernel::getThreadPriority() {
}
void Kernel::getThreadIdealProcessor() {
const Handle handle = regs[1]; // Thread handle
const HandleType handle = regs[1]; // Thread handle
logSVC("GetThreadIdealProcessor (handle = %X)\n", handle);
// TODO: Not documented what this is or what it does. Citra doesn't implement it at all. Return AppCore as the ideal processor for now
@ -490,7 +482,7 @@ void Kernel::getThreadContext() {
}
void Kernel::setThreadPriority() {
const Handle handle = regs[0];
const HandleType handle = regs[0];
const u32 priority = regs[1];
logSVC("SetThreadPriority (handle = %X, priority = %X)\n", handle, priority);
@ -524,9 +516,7 @@ void Kernel::getCurrentProcessorNumber() {
// Until we properly implement per-core schedulers, return whatever processor ID passed to svcCreateThread
switch (id) {
// TODO: This is picked from exheader
case ProcessorID::Default:
ret = static_cast<s32>(ProcessorID::AppCore);
break;
case ProcessorID::Default: ret = static_cast<s32>(ProcessorID::AppCore); break;
case ProcessorID::AllCPUs:
ret = static_cast<s32>(ProcessorID::AppCore);
@ -565,8 +555,7 @@ void Kernel::exitThread() {
// Remove the index of this thread from the thread indices vector
for (int i = 0; i < threadIndices.size(); i++) {
if (threadIndices[i] == currentThreadIndex)
threadIndices.erase(threadIndices.begin() + i);
if (threadIndices[i] == currentThreadIndex) threadIndices.erase(threadIndices.begin() + i);
}
Thread& t = threads[currentThreadIndex];
@ -576,9 +565,9 @@ void Kernel::exitThread() {
// Check if any threads are sleeping, waiting for this thread to terminate, and wake them up
// This is how thread joining is implemented in the kernel - you wait on a thread, like any other wait object.
if (t.threadsWaitingForTermination != 0) {
// TODO: Handle cloned handles? Not sure how those interact with wait object signalling
// TODO: HandleType cloned handles? Not sure how those interact with wait object signalling
wakeupAllThreads(t.threadsWaitingForTermination, t.handle);
t.threadsWaitingForTermination = 0; // No other threads waiting
t.threadsWaitingForTermination = 0; // No other threads waiting
}
requireReschedule();
@ -593,7 +582,7 @@ void Kernel::svcCreateMutex() {
}
void Kernel::svcReleaseMutex() {
const Handle handle = regs[0];
const HandleType handle = regs[0];
logSVC("ReleaseMutex (handle = %x)\n", handle);
const auto object = getObject(handle, KernelObjectType::Mutex);
@ -619,18 +608,16 @@ void Kernel::svcCreateSemaphore() {
s32 maxCount = static_cast<s32>(regs[2]);
logSVC("CreateSemaphore (initial count = %d, max count = %d)\n", initialCount, maxCount);
if (initialCount > maxCount)
Helpers::panic("CreateSemaphore: Initial count higher than max count");
if (initialCount > maxCount) Helpers::panic("CreateSemaphore: Initial count higher than max count");
if (initialCount < 0 || maxCount < 0)
Helpers::panic("CreateSemaphore: Negative count value");
if (initialCount < 0 || maxCount < 0) Helpers::panic("CreateSemaphore: Negative count value");
regs[0] = Result::Success;
regs[1] = makeSemaphore(initialCount, maxCount);
}
void Kernel::svcReleaseSemaphore() {
const Handle handle = regs[1];
const HandleType handle = regs[1];
const s32 releaseCount = static_cast<s32>(regs[2]);
logSVC("ReleaseSemaphore (handle = %X, release count = %d)\n", handle, releaseCount);
@ -641,12 +628,10 @@ void Kernel::svcReleaseSemaphore() {
return;
}
if (releaseCount < 0)
Helpers::panic("ReleaseSemaphore: Negative count");
if (releaseCount < 0) Helpers::panic("ReleaseSemaphore: Negative count");
Semaphore* s = object->getData<Semaphore>();
if (s->maximumCount - s->availableCount < releaseCount)
Helpers::panic("ReleaseSemaphore: Release count too high");
if (s->maximumCount - s->availableCount < releaseCount) Helpers::panic("ReleaseSemaphore: Release count too high");
// Write success and old available count to r0 and r1 respectively
regs[0] = Result::Success;
@ -656,10 +641,10 @@ void Kernel::svcReleaseSemaphore() {
// Wake up threads one by one until the available count hits 0 or we run out of threads to wake up
while (s->availableCount > 0 && s->waitlist != 0) {
int index = wakeupOneThread(s->waitlist, handle); // Wake up highest priority thread
s->waitlist ^= (1ull << index); // Remove thread from waitlist
int index = wakeupOneThread(s->waitlist, handle); // Wake up highest priority thread
s->waitlist ^= (1ull << index); // Remove thread from waitlist
s->availableCount--; // Decrement available count
s->availableCount--; // Decrement available count
}
}
@ -675,25 +660,23 @@ bool Kernel::isWaitable(const KernelObject* object) {
// Returns whether we should wait on a sync object or not
bool Kernel::shouldWaitOnObject(KernelObject* object) {
switch (object->type) {
case KernelObjectType::Event: // We should wait on an event only if it has not been signalled
case KernelObjectType::Event: // We should wait on an event only if it has not been signalled
return !object->getData<Event>()->fired;
case KernelObjectType::Mutex: {
Mutex* moo = object->getData<Mutex>(); // mooooooooooo
return moo->locked && moo->ownerThread != currentThreadIndex; // If the current thread owns the moo then no reason to wait
Mutex* moo = object->getData<Mutex>(); // mooooooooooo
return moo->locked && moo->ownerThread != currentThreadIndex; // If the current thread owns the moo then no reason to wait
}
case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait
case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait
return object->getData<Thread>()->status != ThreadStatus::Dead;
case KernelObjectType::Timer: // We should wait on a timer only if it has not been signalled
case KernelObjectType::Timer: // We should wait on a timer only if it has not been signalled
return !object->getData<Timer>()->fired;
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
return object->getData<Semaphore>()->availableCount <= 0;
default:
Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName());
return true;
default: Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName()); return true;
}
}

View file

@ -4,8 +4,8 @@
#include "kernel.hpp"
#include "scheduler.hpp"
Handle Kernel::makeTimer(ResetType type) {
Handle ret = makeObject(KernelObjectType::Timer);
HandleType Kernel::makeTimer(ResetType type) {
HandleType ret = makeObject(KernelObjectType::Timer);
objects[ret].data = new Timer(type);
if (type == ResetType::Pulse) {
@ -52,11 +52,9 @@ void Kernel::pollTimers() {
}
}
void Kernel::cancelTimer(Timer* timer) {
timer->running = false;
}
void Kernel::cancelTimer(Timer* timer) { timer->running = false; }
void Kernel::signalTimer(Handle timerHandle, Timer* timer) {
void Kernel::signalTimer(HandleType timerHandle, Timer* timer) {
timer->fired = true;
requireReschedule();
@ -94,7 +92,7 @@ void Kernel::svcCreateTimer() {
}
void Kernel::svcSetTimer() {
Handle handle = regs[0];
HandleType handle = regs[0];
// TODO: Is this actually s64 or u64? 3DBrew says s64, but u64 makes more sense
const s64 initial = s64(u64(regs[2]) | (u64(regs[3]) << 32));
const s64 interval = s64(u64(regs[1]) | (u64(regs[4]) << 32));
@ -112,7 +110,7 @@ void Kernel::svcSetTimer() {
timer->interval = interval;
timer->running = true;
timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(initial);
Scheduler& scheduler = cpu.getScheduler();
// Signal an event to poll timers as soon as possible
scheduler.removeEvent(Scheduler::EventType::UpdateTimers);
@ -127,7 +125,7 @@ void Kernel::svcSetTimer() {
}
void Kernel::svcClearTimer() {
Handle handle = regs[0];
HandleType handle = regs[0];
logSVC("ClearTimer (handle = %X)\n", handle);
KernelObject* object = getObject(handle, KernelObjectType::Timer);
@ -141,7 +139,7 @@ void Kernel::svcClearTimer() {
}
void Kernel::svcCancelTimer() {
Handle handle = regs[0];
HandleType handle = regs[0];
logSVC("CancelTimer (handle = %X)\n", handle);
KernelObject* object = getObject(handle, KernelObjectType::Timer);
@ -152,4 +150,4 @@ void Kernel::svcCancelTimer() {
cancelTimer(object->getData<Timer>());
regs[0] = Result::Success;
}
}
}