#pragma once #include "interpreter.h" #include "os_hypervisor.hpp" #include "os_types.hpp" #include "framework/bit_field_new.hpp" #include "framework/console.hpp" #include "framework/settings.hpp" //#include #include #include #include #include #include #include #include #include #include #include #include #include #include "video_core/src/interrupt_listener.hpp" class LogManager; class PicaContext; namespace Interpreter { struct CPUContext; struct Setup; class GDBStub; } namespace Profiler { class Profiler; class Activity; } namespace HLE { namespace OS { // Abstract object with some unique ID. class Object : public std::enable_shared_from_this { public: Object() = default; Object(const Object&) = delete; virtual ~Object() = default; std::string name{}; std::string GetName() const { if (!name.empty()) return name; return typeid(*this).name(); } }; // TODO: Add a reference counting base class? class Thread; // Called "KSynchronizationObject" on 3dbrew. class ObserverSubject : public Object { friend class OS; public: // List of observers. May include the same Thread multiple times. std::list> observers; // TODO: This should be made purely virtual once all ObserverSubject // instances have this function implemented virtual bool TryAcquireImpl(std::shared_ptr) { throw std::runtime_error("This should never be called!"); } public: virtual ~ObserverSubject() = default; /** * Tries to acquire the maintained resource in the given thread. * If successful, thread will be unregistered from observers. */ bool TryAcquire(std::shared_ptr thread); /** * Registers the given thread for notifications. This will make sure that * threads waiting for this subject to become ready will be woken up when * it does. * The thread will be unregistered once the event has been acquired using * TryAcquire. */ void Register(std::shared_ptr thread); /** * Manually unregister the given thread from notifications. */ void Unregister(std::shared_ptr thread); }; /** * A handle table is a collection of handles owned by a particular process. * Handles are process-unique, but handles of different threads may refer to * the same object. The objects are "owned" in the sense that deleting all * handles that refer to a particular object will trigger destruction of the * object. */ class HandleTable { public: // TODO: Un-publicize. We currently need to do this to be able to find mutexes held by an exited thread :/ friend class ConsoleModule; std::unordered_map> table; Handle cur_handle = { 0 }; // Prints an internal logging message about a type mismatch. void ErrorNotFound(Handle, const char* requested_type); // Prints an internal logging message about a type mismatch. void ErrorWrongType(std::shared_ptr object, const char* requested_type); public: template using Entry = std::pair>; /** * Inserts the given Object into the handle table and returns a Handle to the object. * @return Immutable pair of Handle and pointer the object. */ template Entry CreateHandle(std::shared_ptr object, const DebugHandle::DebugInfo& debug_info) { static_assert(std::is_base_of::value, "object must be a shared_ptr to a derivative class of Object!"); ++cur_handle.value; // TODO: Make sure this never becomes one of the reserved handles DebugHandle debug_handle{cur_handle.value, debug_info}; table[debug_handle] = object; return { debug_handle, object }; } /** * Inserts the given Object into the handle table with the specified Handle. * Use this to set up reserved handles (e.g. 0xFFFF8001 for the handle of the current process) */ template void CreateEntry(const DebugHandle& handle, std::shared_ptr object) { static_assert(std::is_base_of::value, "object must be a shared_ptr to a derivative class of Object!"); if (table.count(handle)) throw std::runtime_error("Handle already in table!"); table[handle] = object; } /** * Returns a shared pointer to the kernel Object referenced by handle if * it is in the table and if the object can be downcast to Type. * @param fail_expected If true, we won't log an error when an object of different type is found with the given handle * @return shared_ptr of the given Type. nullptr if the handle can't be used to obtain a pointer to Type. */ template std::shared_ptr FindObject(Handle handle, bool fail_expected = false) { static_assert(std::is_base_of::value, "object must be a shared_ptr to a derivative class of Object!"); if (table.count(handle) == 0) { if (!fail_expected) { ErrorNotFound(handle, typeid(Type).name()); } return nullptr; } auto object_ptr = std::dynamic_pointer_cast(table[handle]); if (!fail_expected && !object_ptr) ErrorWrongType(table[handle], typeid(Type).name()); return object_ptr; } DebugHandle FindHandle(void* object) { for (auto& entry : table) { if (entry.second.get() == object) { return entry.first; } } return Handle{HANDLE_INVALID}; } template Handle FindHandle(std::shared_ptr object) { static_assert(std::is_base_of::value, "object must be a shared_ptr to a derivative class of Object!"); return FindHandle(object.get()); } /** * Sets the currently running thread (to be exposed as handle 0xFFFF8000) * TODO: What does this return when multiple threads of the same process run on different CPUs? */ void SetCurrentThread(std::shared_ptr thread) { if (!thread) { table.erase(Handle{0xFFFF8000}); } else { table[Handle{0xFFFF8000}] = std::static_pointer_cast(thread); } } /** * Closes the given handle. Does not release any locks still hold on ObserverSubjects. * @todo Implement error case? */ void CloseHandle(Handle handle); }; class Process; class OS; class Session; /// Returned as part of an SVCFuture to signalize that a Thread's wake_index should be returned upon next dispatch struct PromisedWakeIndex {}; /// Returned as part of an SVCFuture to signalize that a Thread's pending_result should be returned upon next dispatch struct PromisedResult {}; namespace detail { template struct SVCFutureResultType { using type = T; }; template<> struct SVCFutureResultType { using type = uint32_t; }; template<> struct SVCFutureResultType { using type = uint32_t; }; } // namespace detail // TODO: Move elsewhere template struct SVCFuture { using DataType = std::tuple; using ResultType = std::tuple::type...>; SVCFuture() = default; SVCFuture(DataType&& data) : data(data) {}; SVCFuture& operator =(const SVCFuture&) = default; // TODO: This shouldn't be provided outside of the two functions that really need it std::optional data; }; /** * Utility type for returning the equivalent of "void" from a system call * implementation */ using SVCEmptyFuture = SVCFuture; using SVCCallbackType = std::function)>; /** * Mechanism that switches control flow between emulated OS threads and the emulated OS scheduler. * * Usually this is implemented using coroutines, but for Tracy-based profiling there exists a * thread-based implementation. */ class ThreadControl { public: virtual ~ThreadControl() = default; /** * Move active control flow from this thread to the scheduler. * Must only be called from the running thread */ virtual void YieldToScheduler() = 0; /** * Move active control flow from the scheduler to the thread. * Must only be called from the scheduler */ virtual void ResumeFromScheduler() = 0; /** * Tear down any resources that may not be active when removing this thread * from the scheduler */ virtual void PrepareForExit() {} }; /// A single thread of execution (child classes may either be actual emulation threads or high-level emulated ones). class Thread : public ObserverSubject { uint32_t id; public: // TODO: Figure out how this actually affects behavior on the 3DS! uint32_t priority; bool pending_svc = false; static const uint32_t PriorityDefault = 0x30; /* * When status is either WaitingForTimeout or WaitingForArbitration, this * number denotes the system time (in nanoseconds) at which to wake up. */ int64_t timeout_at; // Address that this thread is currently watching via an AddressArbiter VAddr arbitration_address; // Arbitration handle (stored for debugging) Handle arbitration_handle; Process& parent_process; Profiler::Activity& activity; /// Result to return from a pending system call uint32_t promised_result; /** * Function to be called when a thread is interrupted due to a system call. * All variables captured in this function are guaranteed to be valid until * the thread execution is resumed or the thread exits. */ SVCCallbackType callback_for_svc; // TODO: Make this private! /* struct Context { uint32_t regs[16]; uint32_t cpsr; } context;*/ enum class Status { Ready, // running or ready to run Sleeping, // waiting until SignalResourceReady is called (TODO: SignalResourceReady is unused now!!!) WaitingForTimeout, // waiting for a fixed amount of time WaitingForArbitration, // waiting for another thread to signalize a particular address using ArbitrateAddress Stopped, // thread exited and is waiting for its destruction } status = Status::Ready; /// List of resources that are being waited on as part of SVCWaitSynchronizationN. std::list> wait_list; /// If true, will not wake up the thread before wait_list is empty. bool wait_for_all; /** * Index of the particular ObserverSubject in wait_list which has been * acquired most recently. Note that the value of this variable is only * intended to be read by emulated applications when wait_for_all is false. */ uint32_t wake_index; /** * weak_ptr to the most recently acquired ObserverSubject. This is only * valid when wait_for_all is false. */ std::weak_ptr woken_object; std::shared_ptr incoming_session; // Notify thread of a resource being ready for being TryAcquire'ed // TODO: This function is unused now! void SignalResourceReady(); std::unique_ptr control; Thread(Process& owner, uint32_t id, uint32_t priority); virtual ~Thread() = default; // Guaranteed to be valid, since every thread must have a process. Process& GetParentProcess() { return parent_process; } std::shared_ptr GetPointer(); HandleTable& GetProcessHandleTable(); uint32_t GetId() const; uint32_t GetPriority() const; OS& GetOS(); // Save the current CPU context into "context" virtual void SaveContext() = 0; // Restore the saved CPU context virtual void RestoreContext() = 0; /** * Run the thread. Execution may yield back to the OS and consecutively other threads through the OS's internal scheduling coroutine. * @note In order for execution to yield back to the OS, some system call must be invoked. */ virtual void Run() = 0; /** * Read a word from the TLS at the given byte offset (must be a multiple of 4) * @todo Obsolete. Replace usage of this with ReadMemory */ virtual uint32_t ReadTLS(uint32_t byte_offset) = 0; /** * Write a word to the TLS at the given byte offset (must be a multiple of 4) * @todo Obsolete. Replace usage of this with WriteMemory */ virtual void WriteTLS(uint32_t byte_offset, uint32_t value) = 0; /** * Write a byte to a location in this thread's parent process virtual memory */ void WriteMemory(VAddr addr, uint8_t value); /** * Write a word to a location in this thread's parent process virtual memory */ void WriteMemory32(VAddr addr, uint32_t value); /** * Read a byte from a location in this thread's parent process virtual memory */ uint8_t ReadMemory(VAddr addr); /** * Read a word from a location in this thread's parent process virtual memory */ uint32_t ReadMemory32(VAddr addr); /** * Get the value of the given CPU register in this thread's context. * @param reg_index Index of the CPU register. r0-r15 are mapped to the first 16 values; CPSR is value 16. */ virtual uint32_t GetCPURegisterValue(unsigned reg_index) { return 0; } /** * Set the value of the given CPU register in this thread's context. * @param reg_index Index of the CPU register. r0-r15 are mapped to the first 16 values; CPSR is value 16. * @param value New value for the specified register */ virtual void SetCPURegisterValue(unsigned reg_index, uint32_t value) { // Do nothing by default } /** * Add a breakpoint at the given address. The Thread implementation must * ensure that whenever execution reaches the address, execution is paused * and the GDB stub is notified about the event. */ virtual void AddBreakpoint(VAddr addr) { // Do nothing by default } /** * Removes any breakpoints from the given address. */ virtual void RemoveBreakpoint(VAddr addr) { // Do nothing by default } /** * Add a watchpoint at the given address. The Thread implementation must * ensure that whenever execution reaches the address, execution is paused * and the GDB stub is notified about the event. */ virtual void AddReadWatchpoint(VAddr addr) { // Do nothing by default } /** * Removes any read watchpoints from the given address. */ virtual void RemoveReadWatchpoint(VAddr addr) { // Do nothing by default } /** * Add a watchpoint at the given address. The Thread implementation must * ensure that whenever execution reaches the address, execution is paused * and the GDB stub is notified about the event. */ virtual void AddWriteWatchpoint(VAddr addr) { // Do nothing by default } /** * Removes any read watchpoints from the given address. */ virtual void RemoveWriteWatchpoint(VAddr addr) { // Do nothing by default } std::shared_ptr GetLogger(); /** * Notifies this Thread about the resource being acquired for it. The * resource will be removed from wait_list and the thread will be * woken up if appropriate. * @pre Thread state must be waiting for resources * @pre The given resource must have been in wait_list */ void OnResourceAcquired(ObserverSubject& resource); /** * Signals a software interrupt to the HardwareScheduler and yields the * Thread coroutine. The thread will not be dispatched back in until the * OS has processed the system call. In particular, this means the current * thread may be unscheduled and other threads may be dispatched before * control returns to this thread. */ void YieldForSVC(uint32_t svc); uint32_t GetPromise(PromisedResult) const { return promised_result; } uint32_t GetPromise(PromisedWakeIndex) const { return wake_index; } virtual bool TryAcquireImpl(std::shared_ptr) override; }; class TLSManager { friend struct TLSSlot; void Release(VAddr addr) { slot_occupied[(addr - first_tls_addr) / tls_size] = false; } static constexpr uint32_t page_size = 0x1000; static constexpr uint32_t tls_size = 0x200; static constexpr uint32_t slots_per_page = page_size / tls_size; static constexpr VAddr first_tls_addr = 0x1ff82000; VAddr next_tls_addr = first_tls_addr; std::vector slot_occupied; bool NextSlotNeedsNewPage() const { return (0 == (slot_occupied.size() % slots_per_page)); } public: struct TLSSlot GetFreePage(Process& process); }; struct TLSSlot { VAddr addr; TLSManager& manager; TLSSlot(VAddr addr, TLSManager& manager) : addr(addr), manager(manager) { } TLSSlot(TLSSlot&& slot) : manager(slot.manager) { addr = std::exchange(slot.addr, 0); } ~TLSSlot() { if (addr) { manager.Release(addr); } } }; // A thread performing CPU emulation. This is as close as to a "true" emulated userland thread as it gets. class EmuThread : public Thread { public: // TODO: Required by SVCRaw for now. Should have a DumpRegister interface... std::unique_ptr context; public: // TODO: Un-public-ize this! TLSSlot tls; public: EmuThread(Process& owner, std::unique_ptr, uint32_t id, uint32_t priority, VAddr entry, VAddr stack_top, TLSSlot tls, uint32_t r0, uint32_t fpscr); virtual ~EmuThread() = default; void SaveContext() override; void RestoreContext() override; virtual void Run() override; virtual uint32_t ReadTLS(uint32_t offset) override; virtual void WriteTLS(uint32_t offset, uint32_t value) override; uint32_t GetCPURegisterValue(unsigned reg_index) override; void SetCPURegisterValue(unsigned reg_index, uint32_t value) override; void AddBreakpoint(VAddr addr) override; void RemoveBreakpoint(VAddr addr) override; void AddReadWatchpoint(VAddr addr) override; void RemoveReadWatchpoint(VAddr addr) override; void AddWriteWatchpoint(VAddr addr) override; void RemoveWriteWatchpoint(VAddr addr) override; }; class FakeProcess; // A high-level emulated thread that omits any CPU emulation and instead executes a given C++ function // Implementations must provide the Thread::Run() member function. class FakeThread : public Thread { // Fake thread local storage - initialized to zero to make sure all static buffer parameters are reset, etc. // NOTE: This should actually be 0x200 bytes large. std::array tls{}; public: FakeThread(FakeProcess& parent); virtual ~FakeThread() = default; void SaveContext() override { // No context to save // TODO: Actually, we might want to restore MMU configuration (e.g. to access IO registers)! } void RestoreContext() override { // No context to restore } uint32_t ReadTLS(uint32_t offset) override { return tls[offset / 4]; } void WriteTLS(uint32_t offset, uint32_t value) override { tls[offset / 4] = value; } // Overloaded from Thread for convenience to retrieve a FakeProcess directly rather than a Process FakeProcess& GetParentProcess(); /** * Yield back to the OS thread dispatcher and invoke the given system call * implementation in its context. This function returns once the OS has * dispatched this thread back in. */ template auto CallSVC(SVCFuture (OS::*svc_func)(Args2...), Args&&... args); }; /** * Wraps the given function in a FakeThread, enabling convenient implementation * of threads using C++ functions. */ class WrappedFakeThread final : public FakeThread { std::function func; void Run() override { try { func(*this); } catch (HLE::OS::FakeThread*) { // TODO: Cleanup interface // Do nothing } } public: WrappedFakeThread(FakeProcess& parent, std::function func) : FakeThread(parent), func(std::move(func)) { } }; /// See FakeDebugProcess. class FakeDebugThread : public Thread { public: FakeDebugThread(Process& owner); void SaveContext() override { // No context to save } void RestoreContext() override { // No context to restore } uint32_t ReadTLS(uint32_t offset) override { // We don't maintain any TLS, so just return 0 return 0; } void WriteTLS(uint32_t offset, uint32_t value) override { // We don't maintain any TLS, so just do nothing } void Run() override { // Do nothing } /** * Block until the given ProcessorControl requests execution to continue */ void WaitForContinue(Interpreter::ProcessorController& controller); /** * Reports that a new process was just created, and signals this to the * given controller as a breakpoint. In return, the debugger will request * execution to be paused and eventually to continue. This function blocks * until the latter has happened. * @param process Process that just launched * @pre process must have its main thread created when calling this function. */ void ProcessLaunched(Process& process, Interpreter::ProcessorController& controller); }; class MemoryManager; class ResourceLimit; /** * Owner of a physical memory allocation: Either a Process, a SharedMemoryBlock, or a CodeSet */ class MemoryBlockOwner { public: virtual ~MemoryBlockOwner() = default; }; enum class MemoryPermissions : uint32_t { None = 0, Read = 1, Write = 2, ReadWrite = 3, Exec = 4, ReadExec = 5, }; /// A process (for a very broad definition of "process"). class Process : public ObserverSubject, public MemoryBlockOwner { struct VirtualMemoryBlock { uint32_t phys_start; uint32_t size; MemoryPermissions permissions; }; virtual bool TryAcquireImpl(std::shared_ptr) { for (auto& thread : threads) { if (thread->status != Thread::Status::Stopped) { return false; } } // Report that the process is terminated, since all of its threads stopped return (status != Status::Created); } public: // TODO: Make this private again! // Map from starting address to the virtual memory block std::map virtual_memory; private: OS& os; public: MemoryManager& memory_allocator; public: VAddr linear_base_addr = 0x14000000; private: // Process ID ProcessId pid; ThreadId next_tid; protected: public: // TODO: Un-public-ize this! Profiler::Activity& activity; /** * Every Process needs a CPU interpreter Setup, even if the Process is * high-level emulated: In particular, FakeProcesses might want to share * memory with low-level emulated ones. */ Interpreter::Setup& interpreter_setup; protected: ThreadId MakeNewThreadId(); /** * Called from MapVirtualMemory when a mapping took place. * @param phys_addr Base physical address to map data from * @param size Number of bytes to map. * @param vaddr Virtual address at which to map the given physical memory * @todo We might not actually need this! */ virtual void OnVirtualMemoryMapped(PAddr phys_addr, uint32_t size, VAddr vaddr) = 0; /** * Called from MapVirtualMemory when an unmapping took place. * @param size Number of bytes to unmap * @param vaddr Virtual address to start unmapping at */ virtual void OnVirtualMemoryUnmapped(VAddr vaddr, uint32_t size) = 0; public: // TODO: Consider making this private! std::list> threads; public: HandleTable handle_table; std::shared_ptr limit; enum class Status { Created, // Just created (doesn't have any threads yet) Running, // Process running with at least one thread (i.e. SVCRun has been called) Stopping, // Stopping or stopped (if all threads are Stopped) } status = Status::Created; Process(OS& os, Profiler::Activity&, Interpreter::Setup& setup, ProcessId pid, MemoryManager& memory_allocator); virtual ~Process() = default; OS& GetOS() { return os; } ProcessId GetId() const { return pid; } std::shared_ptr GetThreadFromId(ThreadId thread_id) const; /** * Set up a mapping from virtual memory to physical memory. * @param phys_addr Base physical address to map data from * @param size Number of bytes to map. * @param vaddr Virtual address at which to map the given physical memory * @return true on success, false otherwise * @todo This may currently only be called on active processes (since it may modify the underlying's CPUContext MMU configuration) */ bool MapVirtualMemory(PAddr phys_addr, uint32_t size, VAddr vaddr, MemoryPermissions); /** * Unmaps the given virtual memory range from this process. * @return true if the given range was successfully unmapped; false if parts of the range weren't unmapped */ bool UnmapVirtualMemory(VAddr vaddr, uint32_t size); /** * Translate the given virtual address to a physical one. */ std::optional ResolveVirtualAddr(VAddr addr); /** * Finds the first unmapped virtual address range of the given size in bytes within the given bounds. * @param size Minimum number of bytes in the given range * @param vaddr_start Minimal virtual address at which to look for available locations * @param vaddr_end Virtual address at which to stop looking for available locations * @return On success, returns the base virtual address for the found range * @todo This should actually be a Process matter! */ std::optional FindAvailableVirtualMemory(uint32_t size, VAddr vaddr_start, VAddr vaddr_end); MemoryManager& GetPhysicalMemoryManager(); /** * Write a byte to a location in this process's virtual memory * @todo Move to Process! */ virtual void WriteMemory(VAddr addr, uint8_t value) = 0; virtual void WriteMemory32(VAddr addr, uint32_t value); /** * Read a byte from a location in this process's virtual memory */ virtual uint8_t ReadMemory(VAddr addr) = 0; virtual uint32_t ReadMemory32(VAddr addr); /** * Read a byte from a location in physical memory. Only intended to be used * by the PXI process */ uint8_t ReadPhysicalMemory(PAddr addr); uint32_t ReadPhysicalMemory32(PAddr addr); void WritePhysicalMemory(PAddr addr, uint8_t value); void WritePhysicalMemory32(PAddr addr, uint32_t value); std::shared_ptr GetLogger(); }; class CodeSet; class ResourceLimit; // A low-level emulated process that is constituted of one or more EmuThreads class EmuProcess : public Process { public: // TODO: Un-publish std::unique_ptr processor; private: TLSManager tls_manager; void OnVirtualMemoryMapped(PAddr phys_addr, uint32_t size, VAddr vaddr) override; void OnVirtualMemoryUnmapped(VAddr vaddr, uint32_t size) override; public: // The given CPUContext is used to initialize the virtual address mapping for this process EmuProcess(OS& os, Interpreter::Setup& setup, uint32_t pid, std::shared_ptr codeset, MemoryManager& memory_allocator); ~EmuProcess(); /** * Creates a new EmuThread in this process (allocating memory for thread local storage along the way). * The caller is responsible for registering the thread to the OS scheduler. * @param entry_point Entry point of the thread. */ std::shared_ptr SpawnThread(uint32_t priority, VAddr entry_point, VAddr stack_top, uint32_t r0, uint32_t fpscr); std::shared_ptr codeset; void WriteMemory(VAddr addr, uint8_t value) override; uint8_t ReadMemory(VAddr addr) override; }; // A high-level emulated process that is constituted of one or more FakeThreads class FakeProcess : public Process { void OnVirtualMemoryMapped(PAddr phys_addr, uint32_t size, VAddr vaddr) override; void OnVirtualMemoryUnmapped(VAddr vaddr, uint32_t size) override; struct StaticBuffer { std::vector data; }; /** * Fake static buffers (maps from the fake start address to the data). * Contained buffers are guaranteed to be non-overlapping. * TODO: This can be turned into a generic buffer container */ std::map static_buffers; // Returns true if the given address points to process-internal storage. // Returns false if the given address points to emulated memory. bool IsInternalAddress(VAddr addr) const; public: FakeProcess(OS& os, Interpreter::Setup& setup, uint32_t pid, std::string_view name); virtual ~FakeProcess() = default; /** * Attaches the given thread to the internal thread list and registers it to the OS scheduler. */ void AttachThread(std::shared_ptr thread); /** * Allocate a static buffer for use in IPC requests. * @return A fake address used to identify the static buffer data. * @todo Actually, we can make this act as a generic "allocate buffer data" function */ uint32_t AllocateStaticBuffer(uint32_t size); /** * Allocate a static buffer for use in IPC requests * @param address Address previously obtained by AllocateStaticBuffer, which is used to identify the buffer data. */ void FreeStaticBuffer(uint32_t address); /** * Allocate an internal buffer that is accessible from kernel functions * @return a pair of an address where the buffer pretends to be mapped and a pointer to the buffer data */ std::pair AllocateBuffer(uint32_t size); void FreeBuffer(VAddr addr); void WriteMemory(VAddr addr, uint8_t value) override; void WriteMemory32(VAddr addr, uint32_t value) override; uint8_t ReadMemory(VAddr addr) override; }; /** * A fake process similar in spirit to FakeProcess, but even more reduced to the bare minimum. * FakeDebugProcess is not intended to actively participate in the OS operation; instead, it's * mere purpose is to act as a dummy process that host debuggers can attach to such that they * can listen for newly created EmuProcesses (which will report a SIGTRAP to debuggers). * Other than that, FakeDebugProcess implements the memory reading interface to return always * the same value (representing a "branch to self" ARM instruction), such that it appears to the * debugger as if we were stuck in an infinite loop (since the alternative of returning garbage * data would probably make the debugger go crazy). */ class FakeDebugProcess : public Process { void OnVirtualMemoryMapped(PAddr, uint32_t, VAddr) override { // Do nothing } void OnVirtualMemoryUnmapped(VAddr, uint32_t) override { // Do nothing } public: FakeDebugProcess(OS& os, Interpreter::Setup& setup, ProcessId pid); void WriteMemory(VAddr, uint8_t) override { // Do nothing } uint8_t ReadMemory(VAddr addr) override { // Always return the ARM instruction 0xeafffffe, which is a branch to // the current offset. To the debugger it will hence look as if we were // stuck in an infinite loop (which is better than returning garbage // instruction data). switch (addr % 4) { case 0: return 0xfe; case 1: return 0xff; case 2: return 0xff; case 3: return 0xea; default: return 0; // Silence compiler warning } } std::shared_ptr thread; }; // Tags for WrappedFakeProcess struct TagMapGPUMMIO {}; struct TagMapHIDMMIO {}; struct TagMapVRAM {}; struct TagMapDSPMemory {}; /** * Wraps a FakeProcess around the C++ code providing for the process main * thread logic. This logic is intended to be implemented in the State * constructor. Effectively, a State object is created that represents the main * process thread while it furthermore carries the entire process state (which * may be used to spawn additional threads). This allows for high-level * emulating stateful, multi-threaded Processes in a very natural manner. * @note The State object creation is delayed until the FakeProcess's main * thread is scheduled in, which allows the use of system calls in the * State constructor, hence making for cleaner resource management. * @todo Consider whether this should just be promoted to be our Thread class: * Both EmuThread and FakeThread could possibly be implemented using * an appropriate lambda expression. */ class WrappedFakeProcess : public FakeProcess { std::function functor; WrappedFakeProcess(OS& os, Interpreter::Setup& setup, uint32_t pid, std::string_view name, std::function functor) : FakeProcess(os, setup, pid, name), functor(std::move(functor)) { } public: void SpawnMainThread() { // TODO: Should we call SVCExitThread after State{...} is done? // If we do, we should assert that no more than one thread is left running! auto thread = std::make_shared(*this, std::move(functor)); AttachThread(thread); } static std::shared_ptr Create(OS& os, Interpreter::Setup& setup, uint32_t pid, const std::string& name, std::function functor) { auto process = std::shared_ptr(new WrappedFakeProcess(os, setup, pid, name, std::move(functor))); // TODO: Attach debug information! process->handle_table.CreateEntry(Handle{0xFFFF8001}, process); return process; } template static std::shared_ptr CreateWithContext(OS& os, Interpreter::Setup& setup, uint32_t pid, const std::string& name, Args&&... args) { auto process = Create(os, setup, pid, name, [args...](FakeThread& thread) { Context{thread, args...}; }); // Map IO memory for HLE processes if (std::is_base_of::value) { process->MapVirtualMemory(Memory::IO_GPU::start, Memory::IO_GPU::size, 0x1EF00000, MemoryPermissions::ReadWrite); } // TODO: Add TagMapHashMMIO tag // if (std::is_base_of::value) { process->MapVirtualMemory(Memory::IO_HASH::start, Memory::IO_HASH::size, 0x1EC01000, MemoryPermissions::ReadWrite); process->MapVirtualMemory(Memory::IO_HASH2::start, Memory::IO_HASH2::size, 0x1EE01000, MemoryPermissions::ReadWrite); // } if (std::is_base_of::value) { process->MapVirtualMemory(Memory::IO_HID::start, Memory::IO_HID::size, 0x1EC46000, MemoryPermissions::ReadWrite); } if (std::is_base_of::value) { process->MapVirtualMemory(Memory::VRAM::start, Memory::VRAM::size, 0x1F000000, MemoryPermissions::ReadWrite); } // TODO: Migrate DSP to new service framework and apply this tag /*if (std::is_base_of::value)*/ { process->MapVirtualMemory(Memory::DSP::start, Memory::DSP::size, 0x1FF00000, MemoryPermissions::ReadWrite); } return process; } }; template std::shared_ptr CreateFakeProcessViaContext(OS& os, Interpreter::Setup& setup, uint32_t pid, const std::string& name, Args&&... args) { return WrappedFakeProcess::CreateWithContext(os, setup, pid, name, std::forward(args)...); } struct CodeSetInfo { BOOST_HANA_DEFINE_STRUCT(CodeSetInfo, (std::array, app_name), (std::array, unknown), (VAddr, text_start), (uint32_t, text_pages), (VAddr, ro_start), (uint32_t, ro_pages), (VAddr, data_start), (uint32_t, data_pages), // bss exclusive // These fields seem to be the same as text_pages/ro_pages/data/pages (uint32_t, text_size), // in 0x1000-pages (uint32_t, ro_size), // in 0x1000-pages (uint32_t, data_size), // in 0x1000-pages (bss inclusive) (std::array, unknown2) ); }; // TODO: Most of the contents of this struct need to be verified! struct DMAConfig { // There are 8 channels the application may chose from (values 0 through 7) // 0xff means "any channel" uint8_t channel; // Value size (used for endian-swapping): 0=uint8, 2=uint16, 4=uint32, 8=uint64 uint8_t value_size; struct { uint8_t storage; using Fields = v2::BitField::Fields; /// Load target configuration following this structure auto LoadTargetConfig() const { return Fields::MakeOn<0, 1>(this); } /// Load source configuration following this structure and the target configuration auto LoadSourceConfig() const { return Fields::MakeOn<1, 1>(this); } auto BlockUntilCompletion() const { return Fields::MakeOn<2, 1>(this); } /// Load target configuration, but force its peripheral_id to be 0xff auto LoadTargetAltConfig() const { return Fields::MakeOn<6, 1>(this); } /// Load source configuration, but force its peripheral_id to be 0xff auto LoadSourceAltConfig() const { return Fields::MakeOn<7, 1>(this); } } flags; uint8_t unknown2; struct SubConfig { uint8_t unknown; // Seems to indicate the value size? TODO: how is this compatible with the member value_size above? uint8_t type; uint16_t unknown2; uint16_t transfer_size; uint16_t unknown3; uint16_t stride; static SubConfig Default() { return { 0xff, 0xf, 0x80, 0x0, 0x80, 0x0 }; } }; // NOTE: Possible 3dbrew erratum: Which SubConfig actually comes first? SubConfig dest; SubConfig source; }; class CodeSet : public Object, public MemoryBlockOwner { public: char app_name[9]; // 8 characters + null-terminator // NOTE: System version 10.x added pseudo address layout randomization, // so the mapped physical memory provided by the caller may not be // contiguous. We hence have to keep a list of mappings. // The virtual address space is still contiguous, so a single start address is sufficient. struct Mapping { PAddr phys_start; uint32_t num_pages; }; std::vector text_phys; VAddr text_vaddr; std::vector ro_phys; VAddr ro_vaddr; // data excluding bss std::vector data_phys; VAddr data_vaddr; PAddr bss_paddr; uint32_t bss_size; // in pages CodeSet(const CodeSetInfo& info); // Cleans up the allocated data virtual ~CodeSet() override; }; class Port; class ServerPort : public ObserverSubject { friend class OS; friend class ClientPort; // Number of sessions that can be created in addition to the ones that are already open uint32_t available_sessions; public: ServerPort(std::shared_ptr port, uint32_t max_sessions) : port(port), available_sessions(max_sessions) { } virtual ~ServerPort() = default; bool TryAcquireImpl(std::shared_ptr thread) override; void OfferSession(std::shared_ptr session); std::shared_ptr port; // Queue of incoming sessions std::queue> session_queue; }; class ClientPort : public ObserverSubject { public: ClientPort(std::shared_ptr port) : port(port) { } virtual ~ClientPort() = default; std::shared_ptr port; bool TryAcquireImpl(std::shared_ptr thread) override; }; class ServerSession : public ObserverSubject { public: // TODO: Un-public-ize uint32_t ipc_commands_pending = 0; public: ServerSession(std::shared_ptr session, std::shared_ptr port) : session(session), port(port) { } virtual ~ServerSession() = default; bool TryAcquireImpl(std::shared_ptr thread) override; void CommandReady(); std::shared_ptr session; // May be nullptr for sessions that aren't bound to any port std::shared_ptr port; }; class ClientSession : public ObserverSubject { public: ClientSession(std::shared_ptr session) : session(session) { } virtual ~ClientSession() = default; // Returns bool if the session has been signalled for the thread pushed as the argument // (thread = SVCSendSyncRequest sender) bool TryAcquireImpl(std::shared_ptr thread) override; // Releases the first of the remaining waiting threads void SetReady(std::shared_ptr waiting_thread); std::shared_ptr session; // location of the thread that last sent an IPC request using this session (for the server to read IPC requests from its TLS) std::list> threads; }; class Port : public Object { public: virtual ~Port() = default; // NOTE: Port is owned by its children, since one child is enough for a Port to exist, but if both children are released, the parent Port dies to. std::weak_ptr server; std::weak_ptr client; }; class Session : public Object { public: virtual ~Session() = default; // NOTE: Session is owned by its children, since one child is enough for a Session to exist, but if both children are released, the parent Session dies to. std::weak_ptr client; std::weak_ptr server; }; class Mutex : public ObserverSubject { uint32_t lock_count = 0; // Thread currently holding a lock in this mutex std::weak_ptr owner; public: Mutex(bool locked, std::shared_ptr initial_owner) : lock_count(locked ? 1 : 0), owner(initial_owner) {}; virtual ~Mutex() = default; bool TryAcquireImpl(std::shared_ptr thread) override; void Release(); bool IsOwner(std::shared_ptr thread) const; bool IsReady() const { return (lock_count == 0); } }; class Semaphore : public ObserverSubject { public: int32_t available_count; private: int32_t max_available; public: /// @pre 0 <= count <= max_count Semaphore(int32_t count, int32_t max_count) : available_count(count), max_available(max_count) { } bool TryAcquireImpl(std::shared_ptr thread) override; /** * Release this semaphore "times" times. This will allow "times" additional * threads to acquire the thread. "times" needs to be larger than zero. The * implied final count may not exceed the maximal count. * @result RESULT_OK on success */ uint32_t Release(int32_t times); }; enum class ResetType : uint32_t { OneShot = 0, // Be acquirable once, then reset Sticky = 1, // Be acquirable until explicitly reset Pulse = 2, // Timer-only: Be acquirable once, then reset and restart the timer. }; class Event : public ObserverSubject { // TODO: Make this class thread-safe! public: // TODO: Remove this ugly hack bool signalled = false; ResetType type; public: Event(ResetType type) : type(type) { } virtual ~Event() = default; bool TryAcquireImpl(std::shared_ptr thread) override; void SignalEvent(); void ResetEvent(); }; class DMAObject : public ObserverSubject { public: DMAObject() = default; virtual ~DMAObject() = default; bool TryAcquireImpl(std::shared_ptr) override { // Our current DMA SVC implementation has DMAs complete instantly, // so we always allow successful acquisition of this object return true; } }; constexpr uint64_t cpu_clockrate = 268111856; // Clock rate as per constants used in 3DS kernel using secs_per_tick = std::ratio<1,cpu_clockrate>; using ticks = std::chrono::duration; // This is determined by LCD hardware configuration: cpu_clockrate / 24 / ((HTotal+1) * (VTotal+1)) using vblanks_per_sec = std::chrono::duration>; // approximately 59.83 Hz class Timer : public ObserverSubject { // TODO: Make this class thread-safe! public: // TODO: remove this bool active = false; ResetType type; // timeout when current_time >= timeout_time_ns uint64_t timeout_time_ns = 0; uint64_t period_ns = 0; public: Timer(ResetType type) : type(type) { } virtual ~Timer() = default; bool TryAcquireImpl(std::shared_ptr thread) override; void Run(uint64_t timeout_time, uint64_t period); void Reset(); bool Expired(uint64_t current_time) const; }; /** * These are used e.g. to implement light events (of which there may be * arbitrarily many, but multiple light events need no more than a single * kernel handle). */ class AddressArbiter : public Object { public: AddressArbiter() = default; virtual ~AddressArbiter() = default; }; struct MemoryBlock { uint32_t size_bytes; std::weak_ptr owner; }; class MemoryManager { // physical starting address of region uint32_t region_start_paddr; uint32_t region_size_bytes; public: // TODO: Needs an interface to check for ownership // Maps describing the free/taken memory blocks (mapped by their starting physical address) std::map free; std::map taken; public: MemoryManager(PAddr start_address, uint32_t size); /** * Allocate a memory block of the given size. * @return Starting address of the allocated buffer. boost::none on failure. * @todo Provide means to deallocate the block again! */ std::optional AllocateBlock(std::shared_ptr, uint32_t size_bytes); void DeallocateBlock(std::shared_ptr, PAddr start_addr, uint32_t size_bytes); void TransferOwnership(std::shared_ptr old_owner, std::shared_ptr new_owner, PAddr start_addr, uint32_t size_bytes); void DeallocateBlock(PAddr start_addr, uint32_t size_bytes); uint32_t UsedMemory() const; uint32_t TotalSize() const; PAddr RegionStart() const { return region_start_paddr; } PAddr RegionEnd() const { return region_start_paddr + region_size_bytes; } }; class SharedMemoryBlock : public Object, public MemoryBlockOwner { public: // Address to data stored in emulated memory uint32_t phys_address; uint32_t size; bool owns_memory; SharedMemoryBlock(uint32_t physical_address, uint32_t size, bool owns_memory) : phys_address(physical_address), size(size), owns_memory(owns_memory) { } // TODO: On destruction, this must deallocate any pending memory! virtual ~SharedMemoryBlock() = default; }; // A service is an anonymous port that is queryable through the port srv, which needs service processes to provide the GetPrivateName method. class Service : Port { public: virtual const char* GetPrivateName() = 0; }; class OS final : public InterruptListener { friend class ConsoleModule; // Thread list for the scheduler (threads are owned by processes). std::list> threads; /* // GDB stub for Core 1 std::unique_ptr gdbstub; */ // Thread for running the GDB stub std::unique_ptr gdbthread; // TODO: Currently, we never release any of the references hold here before shutdown! // TODO: Consider storing ClientPorts here instead. std::map> ports; Hypervisor hypervisor; public: // TODO: Ugh. PAddr configuration_memory = 0; PAddr shared_memory_page = 0; PAddr firm_launch_parameters = 0; std::array>, 0x76> bound_interrupts; // Id of the next process that is going to be created ProcessId next_pid; // Number of CPU ticks since power-on ticks system_tick = ticks::zero(); std::list> active_timers; // Process handle table - all processes are owned exclusively by this table. std::unordered_map> process_handles; struct FakeProcessInfo { std::shared_ptr (*create)(OS&, const std::string&); std::weak_ptr fake_process {}; }; std::unordered_map hle_titles; // TODO: Get rid of the parameter DebugHandle::DebugInfo MakeHandleDebugInfo(DebugHandle::Source source = DebugHandle::Original) const; DebugHandle::DebugInfo MakeHandleDebugInfoFromIPC(uint32_t ipc_command) const; public: // TODO: privatize this again! std::list> ready_queue; // Queue of threads that are ready to run // std::list> priority_queue; // Queue of threads that are ready to run and should be prioritized over those in ready_queue (e.g. because they had been waiting on an event that was just signalled) std::vector> waiting_queue; // List of threads waiting on a timeout std::shared_ptr internal_memory_owner; // APP, SYSTEM, and BASE // For each application, all data (code + stack(?) + heap) is allocated // in the region indicated by the memory type in the application's exheader // kernel flags. The only known exception to this is Thread Local Storage, // which is always allocated in the BASE region. std::array memory_regions; [[deprecated]] MemoryManager& memory_app() { return memory_regions[0]; } [[deprecated]] MemoryManager& memory_system() { return memory_regions[1]; } [[deprecated]] MemoryManager& memory_base() { return memory_regions[2]; } MemoryManager& FindMemoryRegionContaining(uint32_t paddr, uint32_t size); OS(Profiler::Profiler&, Settings::Settings&, Interpreter::Setup&, LogManager&, PicaContext&, EmuDisplay::EmuDisplay&); ~OS(); Profiler::Profiler& profiler; Profiler::Activity& activity; PicaContext& pica_context; EmuDisplay::EmuDisplay& display; public: Settings::Settings& settings; Interpreter::Setup& setup; ProcessId MakeNewProcessId(); /// Appends the given process to the global process list void RegisterProcess(std::shared_ptr process); // Process for debuggers to attach to initially std::shared_ptr debug_process; static const int32_t MAX_SESSIONS = 0x1000; // Arbitrary choice // NOTE: Required for SVCRaw (for some reason) and GDBStub // TODO: Create an interface around this instead. Thread* active_thread; LogManager& log_manager; std::shared_ptr logger; private: std::atomic stop_requested { false }; public: using Result = uint32_t; template using ResultAnd = std::tuple; enum class BreakReason : uint32_t { Panic = 0, Assert = 1, User = 2 }; enum class ArbitrationType : uint32_t { Signal = 0, Acquire = 1, DecrementAndAcquire = 2, AcquireWithTimeout = 3, DecrementAndAcquireWithTimeout = 4, }; enum class MemoryRegion { App = 1, Sys = 2, Base = 3 }; struct StartupInfo { int32_t priority; uint32_t stack_size; int32_t argc; VAddr argv_addr; VAddr envp_addr; }; struct KernelCapability { uint32_t storage; // The number of leading 1s in this field distinguishes each type of capability auto identifier() const { return BitField::v3::MakeFieldOn<20, 12>(this); } auto kernel_version() const { struct { uint32_t storage; auto minor() const { return BitField::v3::MakeFieldOn<0, 8>(this); } auto major() const { return BitField::v3::MakeFieldOn<8, 8>(this); } } ret { storage }; return ret; } // TODO: Possible bugfix here... this used to be a struct of two BitFields, hence being uint64_t in total size??? auto map_io_page() const { struct { uint32_t storage; auto page_index() const { return BitField::v3::MakeFieldOn<0, 20>(this); } auto read_only() const { return BitField::v3::MakeFlagOn<20>(this); } } ret { storage }; return ret; } }; // System calls begin here /** * TODOTEST: This has a PID==1 check on actual hardware, see https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&curid=95&diff=18930&oldid=18655 * TODO: Change permission parameter to MemoryPermissions type */ SVCFuture SVCControlMemory(Thread& source, uint32_t addr0, uint32_t addr1, uint32_t size, uint32_t operation, uint32_t permissions); SVCFuture SVCControlProcessMemory(Thread& source, Process& process, uint32_t addr0, uint32_t addr1, uint32_t size, uint32_t operation, MemoryPermissions); SVCFuture> SVCCreateThread(Thread& source, uint32_t entry, uint32_t arg, uint32_t stack_top, uint32_t priority, uint32_t processor_id); void ExitThread(Thread& thread); SVCEmptyFuture SVCExitThread(Thread& source); SVCEmptyFuture SVCSleepThread(Thread& source, int64_t duration); // Allocate stack memory, set up the process main thread, and append the thread to the scheduler queue SVCFuture SVCRun(Thread& source, Handle process, const StartupInfo& startup); SVCFuture> SVCCreateMutex(Thread& source, bool lock); SVCFuture SVCReleaseMutex(Thread& source, Handle mutex); /// @pre 0 <= initial_count <= max_count SVCFuture> SVCCreateSemaphore(Thread& source, int32_t initial_count, int32_t max_count); SVCFuture SVCReleaseSemaphore(Thread& source, Semaphore& sema, int32_t release_count); SVCFuture> SVCCreateEvent(Thread& source, ResetType type); SVCFuture SVCSignalEvent(Thread& source, Handle event); SVCFuture SVCClearEvent(Thread& source, Handle event); SVCFuture> SVCCreateTimer(Thread& source, ResetType type); /** * Functional properties: * - Timer starts waking up threads after the given initial time duration (in nanoseconds) has passed * - ONESHOT timers will wake exactly one thread * - STICKY timers will wake any number of threads until they are reset * - PULSE timers will wake exactly one thread, and restart themselves to fire "interval" nanoseconds after the wakeup * - In particular, this means that threads in general get woken later than "initial + interval * n", because the time of firing the timer will be after waking the thread. */ SVCFuture SVCSetTimer(Thread& source, Timer& timer, int64_t initial, int64_t period); // TODO: This should transfer ownership of the shared pages to the memory block! SVCFuture> SVCCreateMemoryBlock(Thread& source, VAddr addr, uint32_t size, uint32_t owner_perms, uint32_t other_perms); /** * @todo Figure out what closing the block handle achieves: Does it unmap the memory? */ SVCFuture SVCMapMemoryBlock(Thread& source, Handle block_handle, VAddr addr, MemoryPermissions caller_perms, MemoryPermissions other_perms); /** * @todo Why does this take both a block and an address, when either would suffice? */ SVCFuture SVCUnmapMemoryBlock(Thread& source, SharedMemoryBlock& block, VAddr addr); SVCFuture> SVCCreateAddressArbiter(Thread& source); /** * Functional properties: * - Acquiring type: If the word at the given address is smaller than the * given value, the thread is put to sleep until woken * up by a call to this system call. * - Signalling type: Wakes up "value"" threads waiting due to a call to * this system call with the same address. * (A negative "value" resumes all threads waiting on * this arbiter.) * @todo value should actually be of type int32_t, because value=-1 means to signalize all waiting threads */ SVCFuture SVCArbitrateAddress(Thread& source, Handle arbiter_handle, uint32_t address, ArbitrationType type, uint32_t value, int64_t timeout); /** * Things to test: * - Does closing a mutex unlock it? */ SVCFuture SVCCloseHandle(Thread& source, Handle object); Result CloseHandle(Thread& source, Handle object); /** * Things to test: * - timeout = -1 implies indefinite sleep until resource is ready * - timeout = 0 implies instant wakeup if resource is not ready * - What about timeout < -2? */ SVCFuture SVCWaitSynchronization(Thread& source, Handle handle, int64_t timeout); /** * Things to test: * - What happens if one of the given objects is being destroyed while waiting on it? * - timeout = -1 implies indefinite sleep until resources are ready * - timeout = 0 implies instant wakeup if resources are not ready * - What about timeout < -2? */ SVCFuture SVCWaitSynchronizationN(Thread& source, Handle* handles, uint32_t handle_count, bool wait_for_all, int64_t timeout); // Create a new handle referencing the same object as the given handle. SVCFuture SVCDuplicateHandle(Thread& source, Handle handle); // Gets the time passed since turning on the system (measured in CPU ticks) SVCFuture SVCGetSystemTick(Thread& source); /** * Functional properties: * - Returns success instantly as long as the port exists (the server need not have called ReplyAndReceive yet) * - Returns 0xd88007fa if no port with the given name exists */ SVCFuture> SVCConnectToPort(Thread& source, const std::string& name); /** * Functional properties: * - Behavior when two threads use this system call on the same handle: * - The two requests get processed independently * - It's unclear whether causal order is preserved when processing the two requests * - SendSyncRequest can be called before the server has accepted the session in the first place (and SendSyncRequest will then block) * - Blocks until a response arrives. */ SVCFuture SVCSendSyncRequest(Thread& source, Handle session); /** * Things to test: * - May a process open itself? * - Will circular dependencies between two processes that have opened each other be resolved when both of the processes have exited? */ SVCFuture> SVCOpenProcess(Thread& source, ProcessId proc_id); SVCFuture SVCGetProcessId(Thread& source, Process& process); SVCFuture SVCGetThreadId(Thread& source, Thread& thread); [[noreturn]] SVCEmptyFuture SVCBreak(Thread& source, BreakReason reason); /** * Functional properties: * - A client port is only created when \p name is the empty string * - Multiple ports with the same name may be created * - No more than 7 ports may be created across the entire system * - There seems to be no way to unregister a port (TODO: Verify) */ SVCFuture,HandleTable::Entry> SVCCreatePort(Thread& source, const std::string& name, int32_t max_sessions); /** * Functional properties: * - Returns before the server calls SVCReplyAndReceive */ SVCFuture> SVCCreateSessionToPort(Thread& source, Handle client_port); SVCFuture,HandleTable::Entry> SVCCreateSession(Thread& source); SVCFuture> SVCAcceptSession(Thread& source, ServerPort& server_port); // returns server session /** * Functional properties: * - When TLS@0x80 is 0xffff0000 and reply_target==0, the reply is omitted * - handle_count==0 logically omits the "receive" part * - handles==nullptr => ?? * - this function returns when a ServerPort handle that it's waiting on is being connected to. Does it also return when a session handle is closed? */ SVCFuture SVCReplyAndReceive(Thread& source, Handle* handle_values, uint32_t handle_count, Handle reply_target); // returns index into @p handles SVCFuture SVCBindInterrupt(Thread& source, uint32_t interrupt_index, std::shared_ptr signal_event, int32_t priority, uint32_t is_manual_clear); /** * @param start Range start adress (must be mapped in the source process) */ SVCFuture SVCInvalidateProcessDataCache(Thread& source, Handle process, uint32_t start, uint32_t num_bytes); /** * @param start Range start adress (must be mapped in the source process) */ SVCFuture SVCFlushProcessDataCache(Thread& source, Handle process, uint32_t start, uint32_t num_bytes); SVCFuture> SVCStartInterprocessDMA(Thread& source, Process& src_process, Process& dst_process, const VAddr src_address, const VAddr dst_address, uint32_t size, DMAConfig& dma_config); /** * Functional properties: * - Unmaps the given memory from the virtual address space of the calling process (TODO: Verify) */ SVCFuture> SVCCreateCodeSet(Thread& source, const CodeSetInfo& info, VAddr text_data_addr, VAddr ro_data_addr, VAddr data_data_addr); /** * Functional properties: * - Returns 0xe0a01bf5 when the code set contains a ro and rw address that are equal */ SVCFuture> SVCCreateProcess(Thread& source, Handle codeset_handle, KernelCapability* kernel_caps, uint32_t num_kernel_caps); /** * */ SVCFuture> SVCCreateResourceLimit(Thread& source); /** * */ SVCFuture SVCSetResourceLimitValues(Thread& source, ResourceLimit& resource_limit, const std::vector>& limits); // Fake SVCs begin here - these don't map to native SVCs but provide useful functionality /** * Invokes the system call handler for the given svc_id and decodes * arguments appropriately from the given CPUContext. * @return Function to call in the Thread context to encode the return * values back into the CPUContext */ SVCCallbackType SVCRaw(Thread& source, unsigned svc_id, Interpreter::ExecutionContext&); SVCEmptyFuture SVCDoNothing(Thread& source); // TODO: We don't really need this one, it was just for testing! SVCFuture> SVCCreateDummyObject(FakeThread& source, const std::string& name); /** * Add the given Thread to the internal thread list recognized by the scheduler. * Intended to be used for FakeThreads, only. Native threads are added automatically when calling SVCCreateThread. * TODO: This doesn't seem to be used anymore. */ SVCEmptyFuture SVCAddThread(std::shared_ptr thread); static std::pair, std::unique_ptr> Create(Settings::Settings& settings, Interpreter::Setup& setup, LogManager& log_manager, Profiler::Profiler&, PicaContext&, EmuDisplay::EmuDisplay&); /** * Initialized the OS, spawning all service processes along the way. */ void Initialize(); /** * Creates a FakeProcess with no running threads. */ std::shared_ptr MakeFakeProcess(Interpreter::Setup& setup, const std::string& name); /** * Returns true if the given module should be high-level emulated, i.e. replaced with a FakeProcess */ bool ShouldHLEProcess(std::string_view module_name) const; /** * Registers a thread to the OS scheduler */ void RegisterToScheduler(std::shared_ptr thread); /** * Runs the OS dispatcher. * This function won't return unless the emulator encounters an exception * or RequestStop is called. * * @note This must be called after Initialize() * @note You have to add any user process explicitly before calling this if it should be ran, too. */ void Run(std::shared_ptr setup); /** * Requests OS execution to stop. * * Resources will be cleaned up gracefully as far as C++ goes, but * no guarantees are made with regards to emulated entities. Effectively, * the state of emulated entities is considered undefined behavior upon * calling this function. * * @note Thread-safe */ void RequestStop(); /** * Runs the OS dispatcher. Does not return until all threads have exited. * @todo This currently seems to overlap with Run in terms of functionality, but ultimately we want to move the whole OS scheduler/dispatcher to this function. */ // void StartScheduler(boost::coroutines::symmetric_coroutine::yield_type& yield); void EnterExecutionLoop(); void ElapseTime(std::chrono::nanoseconds time); void Reschedule(std::shared_ptr thread); void RescheduleImmediately(std::shared_ptr thread); void TriggerThreadDestruction(std::shared_ptr); /** * @pre The given thread must be the one currently running. */ void SwitchToSchedulerFromThread(Thread& thread); std::vector> GetThreadList() const; // TODO: This should eventually become SVCOpenProcess std::shared_ptr GetProcessFromId(ProcessId proc_id) /*const*/; /** * Translate an IPC message (i.e. a request or a reply) from the source * thread to the destination thread. */ void TranslateIPCMessage(Thread& source, Thread& dest, bool is_reply); uint64_t GetTimeInNanoSeconds() const; /** * Signalize all events that are bound to the given interrupt */ void NotifyInterrupt(uint32_t index) override; void OnResourceReady(ObserverSubject& resource); }; const OS::Result RESULT_OK = 0; template auto FakeThread::CallSVC(SVCFuture (OS::*svc_func)(Args2...), Args&&... args) { if constexpr (std::is_same_v) { if (svc_func == &OS::SVCBreak) { // Throw immediately rather than switching to the OS coroutine and throwing from there. // This makes error location much easier, since a debugger cannot print a relevant backtrace elsewhere. throw std::runtime_error("Called SVCBreak"); } } SVCFuture future; callback_for_svc = [&future, svc_func, &args...](std::shared_ptr thread) { // Care must be taken when writing the result here. If we processed SVCExitThread (or similar), the outer context won't be valid anymore. Hence the result is stored externally in result_storage_for_svc auto new_future = (thread->GetOS().*svc_func)(*std::static_pointer_cast(thread), args...); if (thread->status != Thread::Status::Stopped) { future = std::move(new_future); } }; // Bounce between the OS dispatcher and the OS thread to execute callbacks do { // Switch to OS scheduler to process the callback // (NOTE: This may cause other threads to be dispatched if a reschedule // happens within the callback) GetOS().SwitchToSchedulerFromThread(*this); // TODO: Rename! if (status == Thread::Status::Stopped) { // TODO: Come up with a cleaner interface callback_for_svc = nullptr; throw this; } // Repeat iff the callback added another callback } while (callback_for_svc); auto TransformTuple = [this](auto&&... args) { auto Lookup = [this](auto arg) { if constexpr (std::is_same_v) { return wake_index; } else if constexpr (std::is_same_v) { return promised_result; } else { return arg; } }; return std::make_tuple(Lookup(args)...); }; return std::apply(TransformTuple, *future.data); } inline std::string GetThreadObjectName(Thread& thread) { return thread.GetName(); } /// Utility structure to wrap a Process in a printable object struct ProcessPrinter { Process& process; }; /// Utility structure to wrap a Thread in a printable object struct ThreadPrinter { Thread& thread; }; /// Utility structure to wrap an Object reference in a printable object struct ObjectRefPrinter { Object& object; }; /// Utility structure to wrap an std::shared_ptr in a printable object struct ObjectPrinter { const std::shared_ptr& object_ptr; }; /// Utility structure to wrap a kernel handle in a printable object struct HandlePrinter { Thread& thread; Handle handle; }; std::ostream& operator<<(std::ostream& os, const ProcessPrinter& printer); std::ostream& operator<<(std::ostream& os, const ThreadPrinter& printer); std::ostream& operator<<(std::ostream& os, const ObjectRefPrinter& printer); std::ostream& operator<<(std::ostream& os, const ObjectPrinter& printer); std::ostream& operator<<(std::ostream& os, const HandlePrinter& printer); } // namespace OS } // namespace HLE // Now that we're done with the definitions, include some definitions that are required to instantiate the above structs (e.g. due to unique_ptr being used on incomplete types) #include "gdb_stub.h"