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

@ -1,23 +1,23 @@
#pragma once
#include "helpers.hpp"
using Handle = u32;
using HandleType = u32;
namespace KernelHandles {
enum : u32 {
Max = 0xFFFF7FFF, // Max handle the kernel can automagically allocate
Max = 0xFFFF7FFF, // Max handle the kernel can automagically allocate
// Hardcoded handles
CurrentThread = 0xFFFF8000, // Used by the original kernel
CurrentProcess = 0xFFFF8001, // Used by the original kernel
AC, // Something network related
ACT, // Handles NNID accounts
AM, // Application manager
APT, // App Title something service?
BOSS, // Streetpass stuff?
CAM, // Camera service
CECD, // More Streetpass stuff?
CFG_U, // CFG service (Console & region info)
CurrentThread = 0xFFFF8000, // Used by the original kernel
CurrentProcess = 0xFFFF8001, // Used by the original kernel
AC, // Something network related
ACT, // Handles NNID accounts
AM, // Application manager
APT, // App Title something service?
BOSS, // Streetpass stuff?
CAM, // Camera service
CECD, // More Streetpass stuff?
CFG_U, // CFG service (Console & region info)
CFG_I,
CFG_S, // Used by most system apps in lieu of cfg:u
CSND, // Plays audio directly from PCM samples
@ -50,10 +50,10 @@ namespace KernelHandles {
MinServiceHandle = AC,
MaxServiceHandle = Y2R,
GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory
GSPSharedMemHandle = MaxServiceHandle + 1, // HandleType for the GSP shared memory
FontSharedMemHandle,
CSNDSharedMemHandle,
APTCaptureSharedMemHandle, // Shared memory for display capture info,
APTCaptureSharedMemHandle, // Shared memory for display capture info,
HIDSharedMemHandle,
MinSharedMemHandle = GSPSharedMemHandle,
@ -61,17 +61,13 @@ namespace KernelHandles {
};
// Returns whether "handle" belongs to one of the OS services
static constexpr bool isServiceHandle(Handle handle) {
return handle >= MinServiceHandle && handle <= MaxServiceHandle;
}
static constexpr bool isServiceHandle(HandleType handle) { return handle >= MinServiceHandle && handle <= MaxServiceHandle; }
// Returns whether "handle" belongs to one of the OS services' shared memory areas
static constexpr bool isSharedMemHandle(Handle handle) {
return handle >= MinSharedMemHandle && handle <= MaxSharedMemHandle;
}
static constexpr bool isSharedMemHandle(HandleType handle) { return handle >= MinSharedMemHandle && handle <= MaxSharedMemHandle; }
// Returns the name of a handle as a string based on the given handle
static const char* getServiceName(Handle handle) {
static const char* getServiceName(HandleType handle) {
switch (handle) {
case AC: return "AC";
case ACT: return "ACT";
@ -110,4 +106,4 @@ namespace KernelHandles {
default: return "Unknown";
}
}
}
} // namespace KernelHandles

View file

@ -34,22 +34,22 @@ class Kernel {
static_assert(appResourceLimits.maxThreads <= 63, "The waitlist system is built on the premise that <= 63 threads max can be active");
std::vector<KernelObject> objects;
std::vector<Handle> portHandles;
std::vector<Handle> mutexHandles;
std::vector<Handle> timerHandles;
std::vector<HandleType> portHandles;
std::vector<HandleType> mutexHandles;
std::vector<HandleType> timerHandles;
// Thread indices, sorted by priority
std::vector<int> threadIndices;
Handle currentProcess;
Handle mainThread;
HandleType currentProcess;
HandleType 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
HandleType srvHandle; // HandleType for the special service manager port "srv:"
HandleType errorPortHandle; // HandleType 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?
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
@ -58,29 +58,29 @@ class Kernel {
// 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);
HandleType makeArbiter();
HandleType makeProcess(u32 id);
HandleType makePort(const char* name);
HandleType makeSession(HandleType port);
HandleType makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status = ThreadStatus::Dormant);
HandleType makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
public:
public:
// Needs to be public to be accessible to the APT/HID services
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
HandleType 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);
HandleType 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);
HandleType makeSemaphore(u32 initialCount, u32 maximumCount);
HandleType makeTimer(ResetType resetType);
void pollTimers();
// Signals an event, returns true on success or false if the event does not exist
bool signalEvent(Handle e);
bool signalEvent(HandleType e);
// Run the callback for "special" events that have callbacks
void runEventCallback(Event::CallbackType callback);
void clearEvent(Handle e) {
void clearEvent(HandleType e) {
KernelObject* object = getObject(e, KernelObjectType::Event);
if (object != nullptr) {
object->getData<Event>()->fired = false;
@ -99,19 +99,19 @@ public:
bool shouldWaitOnObject(KernelObject* object);
void releaseMutex(Mutex* moo);
void cancelTimer(Timer* timer);
void signalTimer(Handle timerHandle, Timer* timer);
void signalTimer(HandleType 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);
int wakeupOneThread(u64 waitlist, HandleType handle);
void wakeupAllThreads(u64 waitlist, HandleType handle);
std::optional<Handle> getPortHandle(const char* name);
std::optional<HandleType> getPortHandle(const char* name);
void deleteObjectData(KernelObject& object);
KernelObject* getProcessFromPID(Handle handle);
KernelObject* getProcessFromPID(HandleType handle);
s32 getCurrentResourceValue(const KernelObject* limit, u32 resourceName);
u32 getMaxForResource(const KernelObject* limit, u32 resourceName);
u32 getTLSPointer();
@ -178,22 +178,22 @@ public:
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);
void handleFileOperation(u32 messagePointer, HandleType file);
void closeFile(u32 messagePointer, HandleType file);
void flushFile(u32 messagePointer, HandleType file);
void readFile(u32 messagePointer, HandleType file);
void writeFile(u32 messagePointer, HandleType file);
void getFileSize(u32 messagePointer, HandleType file);
void openLinkFile(u32 messagePointer, HandleType file);
void setFileSize(u32 messagePointer, HandleType file);
void setFilePriority(u32 messagePointer, HandleType file);
// Directory operations
void handleDirectoryOperation(u32 messagePointer, Handle directory);
void closeDirectory(u32 messagePointer, Handle directory);
void readDirectory(u32 messagePointer, Handle directory);
void handleDirectoryOperation(u32 messagePointer, HandleType directory);
void closeDirectory(u32 messagePointer, HandleType directory);
void readDirectory(u32 messagePointer, HandleType directory);
public:
public:
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config);
void initializeFS() { return serviceManager.initializeFS(); }
void setVersion(u8 major, u8 minor);
@ -209,7 +209,7 @@ public:
}
}
Handle makeObject(KernelObjectType type) {
HandleType makeObject(KernelObjectType type) {
if (handleCounter > KernelHandles::Max) [[unlikely]] {
Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
}
@ -219,12 +219,10 @@ public:
return handleCounter++;
}
std::vector<KernelObject>& getObjects() {
return objects;
}
std::vector<KernelObject>& getObjects() { return objects; }
// Get pointer to the object with the specified handle
KernelObject* getObject(Handle handle) {
KernelObject* getObject(HandleType handle) {
// Accessing an object that has not been created
if (handle >= objects.size()) [[unlikely]] {
return nullptr;
@ -234,7 +232,7 @@ public:
}
// Get pointer to the object with the specified handle and type
KernelObject* getObject(Handle handle, KernelObjectType type) {
KernelObject* getObject(HandleType handle, KernelObjectType type) {
if (handle >= objects.size() || objects[handle].type != type) [[unlikely]] {
return nullptr;
}
@ -246,4 +244,4 @@ public:
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void clearInstructionCache();
};
};

View file

@ -1,98 +1,102 @@
#pragma once
#include <array>
#include <cstring>
#include "fs/archive_base.hpp"
#include "handles.hpp"
#include "helpers.hpp"
#include "result/result.hpp"
enum class KernelObjectType : u8 {
AddressArbiter, Archive, Directory, File, MemoryBlock, Process, ResourceLimit, Session, Dummy,
// Bundle waitable objects together in the enum to let the compiler optimize certain checks better
Event, Mutex, Port, Semaphore, Timer, Thread
AddressArbiter,
Archive,
Directory,
File,
MemoryBlock,
Process,
ResourceLimit,
Session,
Dummy,
// Bundle waitable objects together in the enum to let the compiler optimize certain checks better
Event,
Mutex,
Port,
Semaphore,
Timer,
Thread
};
enum class ResourceLimitCategory : int {
Application = 0,
SystemApplet = 1,
LibraryApplet = 2,
Misc = 3
};
enum class ResourceLimitCategory : int { Application = 0, SystemApplet = 1, LibraryApplet = 2, Misc = 3 };
// Reset types (for use with events and timers)
enum class ResetType {
OneShot = 0, // When the primitive is signaled, it will wake up exactly one thread and will clear itself automatically.
Sticky = 1, // When the primitive is signaled, it will wake up all threads and it won't clear itself automatically.
Pulse = 2, // Only meaningful for timers: same as ONESHOT but it will periodically signal the timer instead of just once.
OneShot = 0, // When the primitive is signaled, it will wake up exactly one thread and will clear itself automatically.
Sticky = 1, // When the primitive is signaled, it will wake up all threads and it won't clear itself automatically.
Pulse = 2, // Only meaningful for timers: same as ONESHOT but it will periodically signal the timer instead of just once.
};
enum class ArbitrationType {
Signal = 0,
WaitIfLess = 1,
DecrementAndWaitIfLess = 2,
WaitIfLessTimeout = 3,
DecrementAndWaitIfLessTimeout = 4
};
enum class ArbitrationType { Signal = 0, WaitIfLess = 1, DecrementAndWaitIfLess = 2, WaitIfLessTimeout = 3, DecrementAndWaitIfLessTimeout = 4 };
enum class ProcessorID : s32 {
AllCPUs = -1,
Default = -2,
AppCore = 0,
Syscore = 1,
New3DSExtra1 = 2,
New3DSExtra2 = 3
AllCPUs = -1,
Default = -2,
AppCore = 0,
Syscore = 1,
New3DSExtra1 = 2,
New3DSExtra2 = 3
};
struct AddressArbiter {};
struct ResourceLimits {
Handle handle;
HandleType handle;
s32 currentCommit = 0;
s32 currentCommit = 0;
};
struct Process {
// Resource limits for this process
ResourceLimits limits;
// Process ID
u32 id;
// Resource limits for this process
ResourceLimits limits;
// Process ID
u32 id;
Process(u32 id) : id(id) {}
Process(u32 id) : id(id) {}
};
struct Event {
// Some events (for now, only the DSP semaphore events) need to execute a callback when signalled
// This enum stores what kind of callback they should execute
enum class CallbackType : u32 {
None, DSPSemaphore,
};
// Some events (for now, only the DSP semaphore events) need to execute a callback when signalled
// This enum stores what kind of callback they should execute
enum class CallbackType : u32 {
None,
DSPSemaphore,
};
u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event
ResetType resetType = ResetType::OneShot;
CallbackType callback = CallbackType::None;
bool fired = false;
u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event
ResetType resetType = ResetType::OneShot;
CallbackType callback = CallbackType::None;
bool fired = false;
Event(ResetType resetType) : resetType(resetType), waitlist(0) {}
Event(ResetType resetType, CallbackType cb) : resetType(resetType), waitlist(0), callback(cb) {}
Event(ResetType resetType) : resetType(resetType), waitlist(0) {}
Event(ResetType resetType, CallbackType cb) : resetType(resetType), waitlist(0), callback(cb) {}
};
struct Port {
static constexpr u32 maxNameLen = 11;
static constexpr u32 maxNameLen = 11;
char name[maxNameLen + 1] = {};
bool isPublic = false; // Setting name=NULL creates a private port not accessible from svcConnectToPort.
char name[maxNameLen + 1] = {};
bool isPublic = false; // Setting name=NULL creates a private port not accessible from svcConnectToPort.
Port(const char* name) {
// If the name is empty (ie the first char is the null terminator) then the port is private
isPublic = name[0] != '\0';
std::strncpy(this->name, name, maxNameLen);
}
Port(const char* name) {
// If the name is empty (ie the first char is the null terminator) then the port is private
isPublic = name[0] != '\0';
std::strncpy(this->name, name, maxNameLen);
}
};
struct Session {
Handle portHandle; // The port this session is subscribed to
Session(Handle portHandle) : portHandle(portHandle) {}
HandleType portHandle; // The port this session is subscribed to
Session(HandleType portHandle) : portHandle(portHandle) {}
};
enum class ThreadStatus {
@ -115,14 +119,14 @@ struct Thread {
u32 arg;
ProcessorID processorID;
ThreadStatus status;
Handle handle; // OS handle for this thread
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
HandleType handle; // OS handle for this thread
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
// The waiting address for threads that are waiting on an AddressArbiter
u32 waitingAddress;
// For WaitSynchronization(N): A vector of objects this thread is waiting for
std::vector<Handle> waitList;
std::vector<HandleType> waitList;
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
bool waitAll;
// For WaitSynchronizationN: The "out" pointer
@ -141,88 +145,86 @@ struct Thread {
};
static const char* kernelObjectTypeToString(KernelObjectType t) {
switch (t) {
case KernelObjectType::AddressArbiter: return "address arbiter";
case KernelObjectType::Archive: return "archive";
case KernelObjectType::Directory: return "directory";
case KernelObjectType::Event: return "event";
case KernelObjectType::File: return "file";
case KernelObjectType::MemoryBlock: return "memory block";
case KernelObjectType::Port: return "port";
case KernelObjectType::Process: return "process";
case KernelObjectType::ResourceLimit: return "resource limit";
case KernelObjectType::Session: return "session";
case KernelObjectType::Mutex: return "mutex";
case KernelObjectType::Semaphore: return "semaphore";
case KernelObjectType::Thread: return "thread";
case KernelObjectType::Dummy: return "dummy";
default: return "unknown";
}
switch (t) {
case KernelObjectType::AddressArbiter: return "address arbiter";
case KernelObjectType::Archive: return "archive";
case KernelObjectType::Directory: return "directory";
case KernelObjectType::Event: return "event";
case KernelObjectType::File: return "file";
case KernelObjectType::MemoryBlock: return "memory block";
case KernelObjectType::Port: return "port";
case KernelObjectType::Process: return "process";
case KernelObjectType::ResourceLimit: return "resource limit";
case KernelObjectType::Session: return "session";
case KernelObjectType::Mutex: return "mutex";
case KernelObjectType::Semaphore: return "semaphore";
case KernelObjectType::Thread: return "thread";
case KernelObjectType::Dummy: return "dummy";
default: return "unknown";
}
}
struct Mutex {
u64 waitlist; // Refer to the getWaitlist function below for documentation
Handle ownerThread = 0; // Index of the thread that holds the mutex if it's locked
Handle handle; // Handle of the mutex itself
u32 lockCount; // Number of times this mutex has been locked by its daddy. 0 = not locked
bool locked;
u64 waitlist; // Refer to the getWaitlist function below for documentation
HandleType ownerThread = 0; // Index of the thread that holds the mutex if it's locked
HandleType handle; // HandleType of the mutex itself
u32 lockCount; // Number of times this mutex has been locked by its daddy. 0 = not locked
bool locked;
Mutex(bool lock, Handle handle) : locked(lock), waitlist(0), lockCount(lock ? 1 : 0), handle(handle) {}
Mutex(bool lock, HandleType handle) : locked(lock), waitlist(0), lockCount(lock ? 1 : 0), handle(handle) {}
};
struct Semaphore {
u64 waitlist; // Refer to the getWaitlist function below for documentation
s32 availableCount;
s32 maximumCount;
u64 waitlist; // Refer to the getWaitlist function below for documentation
s32 availableCount;
s32 maximumCount;
Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {}
Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {}
};
struct Timer {
u64 waitlist; // Refer to the getWaitlist function below for documentation
ResetType resetType = ResetType::OneShot;
u64 fireTick; // CPU tick the timer will be fired
u64 interval; // Number of ns until the timer fires for the second and future times
bool fired; // Has this timer been signalled?
bool running; // Is this timer running or stopped?
u64 fireTick; // CPU tick the timer will be fired
u64 interval; // Number of ns until the timer fires for the second and future times
bool fired; // Has this timer been signalled?
bool running; // Is this timer running or stopped?
Timer(ResetType type) : resetType(type), fireTick(0), interval(0), waitlist(0), fired(false), running(false) {}
};
struct MemoryBlock {
u32 addr = 0;
u32 size = 0;
u32 myPermission = 0;
u32 otherPermission = 0;
bool mapped = false;
u32 addr = 0;
u32 size = 0;
u32 myPermission = 0;
u32 otherPermission = 0;
bool mapped = false;
MemoryBlock(u32 addr, u32 size, u32 myPerm, u32 otherPerm) : addr(addr), size(size), myPermission(myPerm), otherPermission(otherPerm),
mapped(false) {}
MemoryBlock(u32 addr, u32 size, u32 myPerm, u32 otherPerm)
: addr(addr), size(size), myPermission(myPerm), otherPermission(otherPerm), mapped(false) {}
};
// Generic kernel object class
struct KernelObject {
Handle handle = 0; // A u32 the OS will use to identify objects
void* data = nullptr;
KernelObjectType type;
HandleType handle = 0; // A u32 the OS will use to identify objects
void* data = nullptr;
KernelObjectType type;
KernelObject(Handle handle, KernelObjectType type) : handle(handle), type(type) {}
KernelObject(HandleType handle, KernelObjectType type) : handle(handle), type(type) {}
// Our destructor does not free the data in order to avoid it being freed when our std::vector is expanded
// Thus, the kernel needs to delete it when appropriate
~KernelObject() {}
// Our destructor does not free the data in order to avoid it being freed when our std::vector is expanded
// Thus, the kernel needs to delete it when appropriate
~KernelObject() {}
template <typename T>
T* getData() {
return static_cast<T*>(data);
}
template <typename T>
T* getData() {
return static_cast<T*>(data);
}
const char* getTypeName() const {
return kernelObjectTypeToString(type);
}
const char* getTypeName() const { return kernelObjectTypeToString(type); }
// Retrieves a reference to the waitlist for a specified object
// Retrieves a reference to the waitlist for a specified object
// We return a reference because this function is only called in the kernel threading internals
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
@ -238,8 +240,7 @@ struct KernelObject {
case KernelObjectType::Timer: return getData<Timer>()->waitlist;
// This should be unreachable once we fully implement sync objects
default: [[unlikely]]
Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
default: [[unlikely]] Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
}
}
};
};