diff --git a/app/src/main/cpp/skyline/services/nvdrv/core/nvmap.cpp b/app/src/main/cpp/skyline/services/nvdrv/core/nvmap.cpp new file mode 100644 index 00000000..894f1ce1 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/core/nvmap.cpp @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "nvmap.h" + +namespace skyline::service::nvdrv::core { + NvMap::Handle::Handle(u64 size, Id id) : size(size), alignedSize(size), origSize(size), id(id) {} + + PosixResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) { + std::scoped_lock lock(mutex); + + // Handles cannot be allocated twice + if (allocated) [[unlikely]] + return PosixResult::NotPermitted; + + flags = pFlags; + kind = pKind; + align = pAlign < PAGE_SIZE ? PAGE_SIZE : pAlign; + + // This flag is only applicable for handles with an address passed + if (pAddress) + flags.keepUncachedAfterFree = false; + else + throw exception("Mapping nvmap handles without a cpu side address is unimplemented!"); + + size = util::AlignUp(size, PAGE_SIZE); + alignedSize = util::AlignUp(size, align); + address = pAddress; + + // TODO: pin init + + allocated = true; + + return PosixResult::Success; + } + + PosixResult NvMap::Handle::Duplicate(bool internalSession) { + // Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS) + if (!allocated) [[unlikely]] + return PosixResult::InvalidArgument; + + std::scoped_lock lock(mutex); + + // If we internally use FromId the duplication tracking of handles won't work accurately due to us not implementing + // per-process handle refs. + if (internalSession) + internalDupes++; + else + dupes++; + + return PosixResult::Success; + } + + NvMap::NvMap(const DeviceState &state) : state(state) {} + + void NvMap::AddHandle(std::shared_ptr handle) { + std::scoped_lock lock(handlesLock); + + handles.emplace(handle->id, std::move(handle)); + } + + bool NvMap::TryRemoveHandle(const std::shared_ptr &h) { + // No dupes left, we can remove from handle map + if (h->dupes == 0 && h->internalDupes == 0) { + std::scoped_lock lock(handlesLock); + + auto it{handles.find(h->id)}; + if (it != handles.end()) + handles.erase(it); + + return true; + } else { + return false; + } + } + + PosixResultValue> NvMap::CreateHandle(u64 size) { + if (!size) [[unlikely]] + return PosixResult::InvalidArgument; + + u32 id{nextHandleId.fetch_add(HandleIdIncrement, std::memory_order_relaxed)}; + auto h{std::make_shared(size, id)}; + AddHandle(h); + + return h; + } + + std::shared_ptr NvMap::GetHandle(Handle::Id handle) { + std::scoped_lock lock(handlesLock); + + try { + return handles.at(handle); + } catch (std::out_of_range &e) { + return nullptr; + } + } + + std::optional NvMap::FreeHandle(Handle::Id handle, bool internalSession) { + std::weak_ptr hWeak{GetHandle(handle)}; + FreeInfo freeInfo; + + // We use a weak ptr here so we can tell when the handle has been freed and report that back to guest + if (auto h = hWeak.lock()) { + if (!h) [[unlikely]] + return std::nullopt; + + std::scoped_lock lock(h->mutex); + + if (internalSession) { + if (--h->internalDupes < 0) + state.logger->Warn("Internal duplicate count inbalance detected!"); + } else { + if (--h->dupes < 0) { + state.logger->Warn("User duplicate count inbalance detected!"); + } else if (h->dupes == 0) { + // TODO: unpin + } + } + + // Try to remove the shared ptr to the handle from the map, if nothing else is using the handle + // then it will now be freed when `h` goes out of scope + if (TryRemoveHandle(h)) + state.logger->Debug("Removed nvmap handle: {}", handle); + else + state.logger->Debug("Tried to free nvmap handle: {} but didn't as it still has duplicates", handle); + + freeInfo = { + .address = h->address, + .size = h->size, + .wasUncached = h->flags.mapUncached, + }; + } else { + return std::nullopt; + } + + // Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed + if (!hWeak.expired()) { + state.logger->Debug("nvmap handle: {} wasn't freed as it is still in use", handle); + freeInfo.address = 0; + } + + return freeInfo; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/nvdrv/core/nvmap.h b/app/src/main/cpp/skyline/services/nvdrv/core/nvmap.h new file mode 100644 index 00000000..3f3f51c8 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/core/nvmap.h @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include + +namespace skyline::service::nvdrv::core { + /** + * @brief The nvmap core class holds the global state for nvmap and provides methods to manage handles + */ + class NvMap { + public: + /** + * @brief A handle to a contiguous block of memory in an application's address space + */ + struct Handle { + std::mutex mutex; + + u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU + u64 size; //!< Page-aligned size of the memory the handle refers to + u64 alignedSize; //!< `align`-aligned size of the memory the handle refers to + u64 origSize; //!< Original unaligned size of the memory this handle refers to + + i32 dupes{1}; //!< How many guest references there are to this handle + i32 internalDupes{0}; //!< How many emulator-internal references there are to this handle + + using Id = u32; + Id id; //!< A globally unique identifier for this handle + + struct Flags { + bool mapUncached : 1; //!< If the handle should be mapped as uncached + bool _pad0_ : 1; + bool keepUncachedAfterFree : 1; //!< Only applicable when the handle was allocated with a fixed address + bool _pad1_ : 1; + bool _unk0_ : 1; //!< Passed to IOVMM for pins + u32 _pad2_ : 27; + } flags{}; + static_assert(sizeof(Flags) == sizeof(u32)); + + u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to, this can also be in the nvdrv tmem + bool isSharedMemMapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC call + + + u8 kind{}; //!< Used for memory compression + bool allocated{}; //!< If the handle has been allocated with `Alloc` + + Handle(u64 size, Id id); + + /** + * @brief Sets up the handle with the given memory config, can allocate memory from the tmem if a 0 address is passed + */ + [[nodiscard]] PosixResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress); + + /** + * @brief Increases the dupe counter of the handle for the given session + */ + [[nodiscard]] PosixResult Duplicate(bool internalSession); + + /** + * @brief Obtains a pointer to the handle's memory and marks the handle it as having been mapped + */ + u8 *GetPointer() { + if (!address) + throw exception("Cannot get a pointer to the memory of an unallocated handle!"); + + isSharedMemMapped = true; + return reinterpret_cast(address); + } + }; + + private: + const DeviceState &state; + + std::unordered_map> handles; //!< Main owning map of handles + std::mutex handlesLock; //!< Protects access to `handles` + + static constexpr u32 HandleIdIncrement{4}; //!< Each new handle ID is an increment of 4 from the previous + std::atomic nextHandleId{HandleIdIncrement}; + + void AddHandle(std::shared_ptr handle); + + /** + * @brief Removes a handle from the map taking its dupes into account + * @note h->mutex MUST be locked when calling this + * @return If the handle was removed from the map + */ + bool TryRemoveHandle(const std::shared_ptr &h); + + public: + NvMap(const DeviceState &state); + + /** + * @brief Encapsulates the result of a FreeHandle operation + */ + struct FreeInfo { + u64 address; //!< Address the handle referred to before deletion + u64 size; //!< Page-aligned handle size + bool wasUncached; //!< If the handle was allocated as uncached + }; + + /** + * @brief Creates an unallocated handle of the given size + */ + [[nodiscard]] PosixResultValue> CreateHandle(u64 size); + + std::shared_ptr GetHandle(Handle::Id handle); + + /** + * @brief Tries to free a handle and remove a single dupe + * If a handle has no dupes left and has no other users a FreeInfo struct will be returned describing the prior state of the handle. + */ + std::optional FreeHandle(Handle::Id handle, bool internalSession); + }; +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp index 98da976d..0689089f 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp @@ -1,172 +1,134 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include #include "nvmap.h" namespace skyline::service::nvdrv::device { - NvMap::NvMapObject::NvMapObject(u32 id, u32 size) : id(id), size(size) {} + NvMap::NvMap(const DeviceState &state, Core &core, const SessionContext &ctx) : NvDevice(state, core, ctx) {} - NvMap::NvMap(const DeviceState &state) : NvDevice(state) {} - - NvStatus NvMap::Create(IoctlType type, span buffer, span inlineBuffer) { - struct Data { - u32 size; // In - u32 handle; // Out - } &data = buffer.as(); - - std::unique_lock lock(mapMutex); - maps.push_back(std::make_shared(idIndex++, data.size)); - data.handle = maps.size(); - - state.logger->Debug("Size: 0x{:X} -> Handle: 0x{:X}", data.size, data.handle); - return NvStatus::Success; - } - - NvStatus NvMap::FromId(IoctlType type, span buffer, span inlineBuffer) { - struct Data { - u32 id; // In - u32 handle; // Out - } &data = buffer.as(); - - std::shared_lock lock(mapMutex); - for (auto it{maps.begin()}; it < maps.end(); it++) { - if ((*it)->id == data.id) { - data.handle = (it - maps.begin()) + 1; - state.logger->Debug("ID: 0x{:X} -> Handle: 0x{:X}", data.id, data.handle); - return NvStatus::Success; - } + PosixResult NvMap::Create(In size, Out handle) { + auto h{core.nvMap.CreateHandle(util::AlignUp(size, PAGE_SIZE))}; + if (h) { + (*h)->origSize = size; // Orig size is the unaligned size + handle = (*h)->id; } - state.logger->Warn("Handle not found for ID: 0x{:X}", data.id); - return NvStatus::BadValue; + return h; } - NvStatus NvMap::Alloc(IoctlType type, span buffer, span inlineBuffer) { - struct Data { - u32 handle; // In - u32 heapMask; // In - u32 flags; // In - u32 align; // In - u8 kind; // In - u8 _pad0_[7]; - u8 *ptr; // InOut - } &data = buffer.as(); + PosixResult NvMap::FromId(In id, Out handle) { + // Handles and IDs are always the same value in nvmap however IDs can be used globally given the right permissions. + // Since we don't plan on ever supporting multiprocess we can skip implementing handle refs and so this function + // just does simple validation and passes through the handle id. + if (!id) [[unlikely]] + return PosixResult::InvalidArgument; - try { - auto object{GetObject(data.handle)}; - object->heapMask = data.heapMask; - object->flags = data.flags; - object->align = data.align; - object->kind = data.kind; - object->ptr = data.ptr; - object->status = NvMapObject::Status::Allocated; + auto h{core.nvMap.GetHandle(id)}; + if (!h) [[unlikely]] + return PosixResult::InvalidArgument; - state.logger->Debug("Handle: 0x{:X}, HeapMask: 0x{:X}, Flags: {}, Align: 0x{:X}, Kind: {}, Pointer: 0x{:X}", data.handle, data.heapMask, data.flags, data.align, data.kind, data.ptr); - return NvStatus::Success; - } catch (const std::out_of_range &) { - state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle); - return NvStatus::BadParameter; + return h->Duplicate(ctx.internalSession); + } + + PosixResult NvMap::Alloc(In handle, In heapMask, In flags, InOut align, In kind, In address) { + if (!handle) [[unlikely]] + return PosixResult::InvalidArgument; + + if (!std::ispow2(align)) [[unlikely]] + return PosixResult::InvalidArgument; + + // Force page size alignment at a minimum + if (align < PAGE_SIZE) [[unlikely]] + align = PAGE_SIZE; + + auto h{core.nvMap.GetHandle(handle)}; + if (!h) [[unlikely]] + return PosixResult::InvalidArgument; + + return h->Alloc(flags, align, kind, address); + } + + PosixResult NvMap::Free(In handle, Out address, Out size, Out flags) { + if (!handle) [[unlikely]] + return PosixResult::Success; + + if (auto freeInfo{core.nvMap.FreeHandle(handle, ctx.internalSession)}) { + address = freeInfo->address; + size = static_cast(freeInfo->size); + flags = NvMapCore::Handle::Flags{ .mapUncached = freeInfo->wasUncached }; + } + + return PosixResult::Success; + } + + PosixResult NvMap::Param(In handle, In param, Out result) { + if (!handle) + return PosixResult::InvalidArgument; + + auto h{core.nvMap.GetHandle(handle)}; + if (!h) [[unlikely]] + return PosixResult::InvalidArgument; + + switch (param) { + case HandleParameterType::Size: + result = h->origSize; + return PosixResult::Success; + case HandleParameterType::Alignment: + result = h->align; + return PosixResult::Success; + case HandleParameterType::Base: + result = -static_cast(PosixResult::InvalidArgument); + return PosixResult::Success; + case HandleParameterType::Heap: + if (h->allocated) + result = 0x40000000; + else + result = 0; + + return PosixResult::Success; + case HandleParameterType::Kind: + result = h->kind; + return PosixResult::Success; + case HandleParameterType::IsSharedMemMapped: + result = h->isSharedMemMapped; + return PosixResult::Success; + default: + return PosixResult::InvalidArgument; } } - NvStatus NvMap::Free(IoctlType type, span buffer, span inlineBuffer) { - struct Data { - u32 handle; // In - u32 _pad0_; - u8 *ptr; // Out - u32 size; // Out - u32 flags; // Out - } &data = buffer.as(); + PosixResult NvMap::GetId(Out id, In handle) { + // See the comment in FromId for extra info on this function + if (!handle) [[unlikely]] + return PosixResult::InvalidArgument; - std::unique_lock lock(mapMutex); - try { - auto &object{maps.at(data.handle - 1)}; - if (object.use_count() > 1) { - data.ptr = object->ptr; - data.flags = 0x0; - } else { - data.ptr = nullptr; - data.flags = 0x1; // Not free yet - } + auto h{core.nvMap.GetHandle(handle)}; + if (!h) [[unlikely]] + return PosixResult::NotPermitted; // This will always return EPERM irrespective of if the handle exists or not - data.size = object->size; - object = nullptr; - - state.logger->Debug("Handle: 0x{:X} -> Pointer: 0x{:X}, Size: 0x{:X}, Flags: 0x{:X}", data.handle, data.ptr, data.size, data.flags); - return NvStatus::Success; - } catch (const std::out_of_range &) { - state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle); - return NvStatus::BadParameter; - } + id = h->id; + return PosixResult::Success; } - NvStatus NvMap::Param(IoctlType type, span buffer, span inlineBuffer) { - // https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102 - enum class Parameter : u32 { - Size = 1, - Alignment = 2, - Base = 3, - HeapMask = 4, - Kind = 5, - Compr = 6, - }; +#include "deserialisation/macro_def.h" + static constexpr u32 NvMapMagic{1}; - struct Data { - u32 handle; // In - Parameter parameter; // In - u32 result; // Out - } &data = buffer.as(); - - try { - auto object{GetObject(data.handle)}; - - switch (data.parameter) { - case Parameter::Size: - data.result = object->size; - break; - - case Parameter::Alignment: - data.result = object->align; - break; - - case Parameter::HeapMask: - data.result = object->heapMask; - break; - - case Parameter::Kind: - data.result = object->kind; - break; - - case Parameter::Compr: - data.result = 0; - break; - - default: - state.logger->Warn("Parameter not implemented: 0x{:X}", data.parameter); - return NvStatus::NotImplemented; - } - - state.logger->Debug("Handle: 0x{:X}, Parameter: {} -> Result: 0x{:X}", data.handle, data.parameter, data.result); - return NvStatus::Success; - } catch (const std::out_of_range &) { - state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle); - return NvStatus::BadParameter; - } - } - - NvStatus NvMap::GetId(IoctlType type, span buffer, span inlineBuffer) { - struct Data { - u32 id; // Out - u32 handle; // In - } &data = buffer.as(); - - try { - data.id = GetObject(data.handle)->id; - state.logger->Debug("Handle: 0x{:X} -> ID: 0x{:X}", data.handle, data.id); - return NvStatus::Success; - } catch (const std::out_of_range &) { - state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle); - return NvStatus::BadParameter; - } - } + IOCTL_HANDLER_FUNC(NvMap, ({ + IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x1), + Create, ARGS(In, Out)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x3), + FromId, ARGS(In, Out)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x20), MAGIC(NvMapMagic), FUNC(0x4), + Alloc, ARGS(In, In, In, InOut, In, Pad, In)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(NvMapMagic), FUNC(0x5), + Free, ARGS(In, Pad, Out, Out, Out)) + IOCTL_CASE_ARGS(INOUT, SIZE(0xC), MAGIC(NvMapMagic), FUNC(0x9), + Param, ARGS(In, In, Out)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0xE), + GetId, ARGS(Out, In)) + })) +#include "deserialisation/macro_undef.h" } + diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.h index a40fef9b..7846e317 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.h @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) #pragma once @@ -7,91 +7,60 @@ namespace skyline::service::nvdrv::device { /** - * @brief NvMap (/dev/nvmap) is used to map certain CPU memory as GPU memory (https://switchbrew.org/wiki/NV_services) + * @brief NvMap (/dev/nvmap) is used to keep track of buffers and map them onto the SMMU (https://switchbrew.org/wiki/NV_services) * @url https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h */ class NvMap : public NvDevice { public: - /** - * @brief NvMapObject is used to hold the state of held objects - */ - struct NvMapObject { - u32 id; - u32 size; - u8 *ptr{}; - u32 flags{}; //!< The flag of the memory (0 = Read Only, 1 = Read-Write) - u32 align{}; - u32 heapMask{}; //!< This is set during Alloc and returned during Param - u8 kind{}; //!< This is same as heapMask + using NvMapCore = core::NvMap; - enum class Status { - Created, //!< The object has been created but memory has not been allocated - Allocated //!< The object has been allocated - } status{Status::Created}; //!< This holds the status of the object - - NvMapObject(u32 id, u32 size); + enum class HandleParameterType : u32 { + Size = 1, + Alignment = 2, + Base = 3, + Heap = 4, + Kind = 5, + IsSharedMemMapped = 6 }; - std::shared_mutex mapMutex; //!< Synchronizes mutations and accesses of the mappings - std::vector> maps; - - u32 idIndex{1}; //!< This is used to keep track of the next ID to allocate - - NvMap(const DeviceState &state); - - std::shared_ptr GetObject(u32 handle) { - if (handle-- == 0) - throw std::out_of_range("0 is an invalid nvmap handle"); - std::shared_lock lock(mapMutex); - auto &object{maps.at(handle)}; - if (!object) - throw std::out_of_range("A freed nvmap handle was requested"); - return object; - } + NvMap(const DeviceState &state, Core &core, const SessionContext &ctx); /** - * @brief Creates an NvMapObject and returns an handle to it + * @brief Creates an nvmap handle for the given size * @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_CREATE */ - NvStatus Create(IoctlType type, span buffer, span inlineBuffer); + PosixResult Create(In size, Out handle); /** - * @brief Returns the handle of an NvMapObject from its ID + * @brief Creates a new ref to the handle of the given ID * @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID */ - NvStatus FromId(IoctlType type, span buffer, span inlineBuffer); + PosixResult FromId(In id, Out handle); /** - * @brief Allocates memory for an NvMapObject + * @brief Adds the given backing memory to the nvmap handle * @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_ALLOC */ - NvStatus Alloc(IoctlType type, span buffer, span inlineBuffer); + PosixResult Alloc(In handle, In heapMask, In flags, InOut align, In kind, In address); /** - * @brief Frees previously allocated memory + * @brief Attempts to free a handle and unpin it from SMMU memory * @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FREE */ - NvStatus Free(IoctlType type, span buffer, span inlineBuffer); + PosixResult Free(In handle, Out address, Out size, Out flags); /** - * @brief Returns a particular parameter from an NvMapObject + * @brief Returns info about a property of the nvmap handle * @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_PARAM */ - NvStatus Param(IoctlType type, span buffer, span inlineBuffer); + PosixResult Param(In handle, In param, Out result); /** - * @brief Returns the ID of an NvMapObject from its handle + * @brief Returns a global ID for the given nvmap handle * @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID */ - NvStatus GetId(IoctlType type, span buffer, span inlineBuffer); + PosixResult GetId(Out id, In handle); - NVDEVICE_DECL( - NVFUNC(0x0101, NvMap, Create), - NVFUNC(0x0103, NvMap, FromId), - NVFUNC(0x0104, NvMap, Alloc), - NVFUNC(0x0105, NvMap, Free), - NVFUNC(0x0109, NvMap, Param), - NVFUNC(0x010E, NvMap, GetId) - ) + PosixResult Ioctl(IoctlDescriptor cmd, span buffer) override; }; }