// SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #pragma once #include #include #include #include "KThread.h" #include "KTransferMemory.h" #include "KSession.h" #include "KEvent.h" namespace skyline { namespace constant { constexpr u16 TlsSlotSize{0x200}; //!< The size of a single TLS slot constexpr u8 TlsSlots{PAGE_SIZE / TlsSlotSize}; //!< The amount of TLS slots in a single page constexpr KHandle BaseHandleIndex{0xD000}; //!< The index of the base handle constexpr u32 MtxOwnerMask{0xBFFFFFFF}; //!< The mask of values which contain the owner of a mutex } namespace kernel::type { /** * @brief KProcess manages process-global state such as memory, kernel handles allocated to the process and synchronization primitives */ class KProcess : public KSyncObject { public: // We have intermittent public/private members to ensure proper construction/destruction order MemoryManager memory; private: struct WaitStatus { std::atomic_bool flag{false}; i8 priority; KHandle handle; u32 *mutex{}; WaitStatus(i8 priority, KHandle handle); WaitStatus(i8 priority, KHandle handle, u32 *mutex); }; std::unordered_map>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it std::unordered_map>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it std::mutex mutexLock; std::mutex conditionalLock; size_t threadIndex{}; //!< The ID assigned to the next created thread /** * @brief The status of a single TLS page (A page is 4096 bytes on ARMv8) * Each TLS page has 8 slots, each 0x200 (512) bytes in size * The first slot of the first page is reserved for user-mode exception handling * @url https://switchbrew.org/wiki/Thread_Local_Storage */ struct TlsPage { u8 index{}; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved std::shared_ptr memory; TlsPage(const std::shared_ptr &memory); u8 *ReserveSlot(); u8 *Get(u8 index); bool Full(); }; public: std::shared_ptr mainThreadStack; std::shared_ptr heap; std::vector> tlsPages; std::shared_ptr mainThread; vfs::NPDM npdm; private: std::shared_mutex handleMutex; std::vector> handles; public: KProcess(const DeviceState &state); /** * @note This requires VMM regions to be initialized, it will map heap at an arbitrary location otherwise */ void InitializeHeap(); /** * @return A 0x200 TLS slot allocated inside the TLS/IO region */ u8 *AllocateTlsSlot(); /** * @note The default values are for the main thread and will use values from the NPDM */ std::shared_ptr CreateThread(void *entry, u64 argument = 0, void *stackTop = nullptr, i8 priority = -1, i8 idealCore = -1); /** * @brief The output for functions that return created kernel objects * @tparam objectClass The class of the kernel object */ template struct HandleOut { std::shared_ptr item; //!< A shared pointer to the object KHandle handle; //!< The handle of the object in the process }; /** * @brief Creates a new handle to a KObject and adds it to the process handle_table * @tparam objectClass The class of the kernel object to create * @param args The arguments for the kernel object except handle, pid and state */ template HandleOut NewHandle(objectArgs... args) { std::unique_lock lock(handleMutex); std::shared_ptr item; if constexpr (std::is_same()) item = std::make_shared(state, constant::BaseHandleIndex + handles.size(), args...); else item = std::make_shared(state, args...); handles.push_back(std::static_pointer_cast(item)); return {item, static_cast((constant::BaseHandleIndex + handles.size()) - 1)}; } /** * @brief Inserts an item into the process handle table * @return The handle of the corresponding item in the handle table */ template KHandle InsertItem(std::shared_ptr &item) { std::unique_lock lock(handleMutex); handles.push_back(std::static_pointer_cast(item)); return static_cast((constant::BaseHandleIndex + handles.size()) - 1); } template std::shared_ptr GetHandle(KHandle handle) { std::shared_lock lock(handleMutex); KType objectType; if constexpr(std::is_same()) { constexpr KHandle threadSelf{0xFFFF8000}; // The handle used by threads to refer to themselves if (handle == threadSelf) return state.thread; objectType = KType::KThread; } else if constexpr(std::is_same()) objectType = KType::KProcess; else if constexpr(std::is_same()) objectType = KType::KSharedMemory; else if constexpr(std::is_same()) objectType = KType::KTransferMemory; else if constexpr(std::is_same()) objectType = KType::KPrivateMemory; else if constexpr(std::is_same()) objectType = KType::KSession; else if constexpr(std::is_same()) objectType = KType::KEvent; else throw exception("KProcess::GetHandle couldn't determine object type"); try { auto &item{handles.at(handle - constant::BaseHandleIndex)}; if (item != nullptr && item->objectType == objectType) return std::static_pointer_cast(item); else if (item == nullptr) throw exception("GetHandle was called with a deleted handle: 0x{:X}", handle); else throw exception("Tried to get kernel object (0x{:X}) with different type: {} when object is {}", handle, objectType, item->objectType); } catch (std::out_of_range) { throw std::out_of_range(fmt::format("GetHandle was called with an invalid handle: 0x{:X}", handle)); } } template<> std::shared_ptr GetHandle(KHandle handle) { return handles.at(handle - constant::BaseHandleIndex); } /** * @brief Retrieves a kernel memory object that owns the specified address * @param address The address to look for * @return A shared pointer to the corresponding KMemory object */ std::optional> GetMemoryObject(u8 *ptr); /** * @brief Closes a handle in the handle table */ inline void CloseHandle(KHandle handle) { handles.at(handle - constant::BaseHandleIndex) = nullptr; } /** * @brief Locks the Mutex at the specified address * @param owner The handle of the current mutex owner * @return If the mutex was successfully locked */ bool MutexLock(u32 *mutex, KHandle owner); /** * @brief Unlocks the Mutex at the specified address * @return If the mutex was successfully unlocked */ bool MutexUnlock(u32 *mutex); /** * @param timeout The amount of time to wait for the conditional variable * @return If the conditional variable was successfully waited for or timed out */ bool ConditionalVariableWait(void *conditional, u32 *mutex, u64 timeout); /** * @brief Signals a number of conditional variable waiters * @param amount The amount of waiters to signal */ void ConditionalVariableSignal(void *conditional, u64 amount); /** * @brief Resets the object to an unsignalled state */ inline void ResetSignal() { signalled = false; } }; } }