From d159605caf00f05817442e01e788a9bc51a75367 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sat, 17 Jul 2021 17:16:32 +0100 Subject: [PATCH] Migrate syncpoint management over to the new device API The syncpoint manager has beeen given convinience functions for fences which remove the need to access the raw id/threshold most of the time and various accuracy fixes and cleanups to match HOS 12.0.0 have also been done. --- .../main/cpp/skyline/services/common/fence.h | 11 +- .../services/hosbinder/android_types.h | 2 +- .../syncpoint_manager.cpp} | 34 ++- .../syncpoint_manager.h} | 21 +- .../services/nvdrv/devices/nvhost/ctrl.cpp | 258 ++++++++++++++++++ .../services/nvdrv/devices/nvhost/ctrl.h | 142 ++++++++++ .../services/nvdrv/devices/nvhost_ctrl.cpp | 242 ---------------- .../services/nvdrv/devices/nvhost_ctrl.h | 113 -------- 8 files changed, 440 insertions(+), 383 deletions(-) rename app/src/main/cpp/skyline/services/nvdrv/{devices/nvhost_syncpoint.cpp => core/syncpoint_manager.cpp} (70%) rename app/src/main/cpp/skyline/services/nvdrv/{devices/nvhost_syncpoint.h => core/syncpoint_manager.h} (79%) create mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp create mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h delete mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp delete mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h diff --git a/app/src/main/cpp/skyline/services/common/fence.h b/app/src/main/cpp/skyline/services/common/fence.h index 8407811d..a3633415 100644 --- a/app/src/main/cpp/skyline/services/common/fence.h +++ b/app/src/main/cpp/skyline/services/common/fence.h @@ -3,22 +3,13 @@ #pragma once -#include - namespace skyline::service::nvdrv { /** * @brief A Fence is a synchronization primitive that describes a point in a Syncpoint to synchronize at */ struct Fence { u32 id{}; //!< The ID of the underlying syncpoint - u32 value{}; //!< The value of the syncpoint at which the fence is passed - - /** - * @brief Synchronizes the fence's value with its underlying syncpoint - */ - void UpdateValue(NvHostSyncpoint &hostSyncpoint) { - value = hostSyncpoint.UpdateMin(id); - } + u32 threshold{}; //!< The value of the syncpoint at which the fence is signalled }; static_assert(sizeof(Fence) == 0x8); } diff --git a/app/src/main/cpp/skyline/services/hosbinder/android_types.h b/app/src/main/cpp/skyline/services/hosbinder/android_types.h index 3416e46d..df3668a2 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/android_types.h +++ b/app/src/main/cpp/skyline/services/hosbinder/android_types.h @@ -64,7 +64,7 @@ namespace skyline::service::hosbinder { throw exception("Wait has larger fence count ({}) than storage size ({})", fenceCount, fences.size()); for (auto it{fences.begin()}, end{fences.begin() + fenceCount}; it < end; it++) if (it->id != InvalidFenceId) - host1x.syncpoints.at(it->id).Wait(it->value, std::chrono::steady_clock::duration::max()); + host1x.syncpoints.at(it->id).Wait(it->threshold, std::chrono::steady_clock::duration::max()); } }; diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp b/app/src/main/cpp/skyline/services/nvdrv/core/syncpoint_manager.cpp similarity index 70% rename from app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp rename to app/src/main/cpp/skyline/services/nvdrv/core/syncpoint_manager.cpp index fa019dca..00582dd4 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/core/syncpoint_manager.cpp @@ -1,12 +1,12 @@ -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: MIT OR MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) - +// Copyright © 2019-2020 Ryujinx Team and Contributors #include -#include "nvhost_syncpoint.h" +#include "syncpoint_manager.h" -namespace skyline::service::nvdrv { - NvHostSyncpoint::NvHostSyncpoint(const DeviceState &state) : state(state) { +namespace skyline::service::nvdrv::core { + SyncpointManager::SyncpointManager(const DeviceState &state) : state(state) { constexpr u32 VBlank0SyncpointId{26}; constexpr u32 VBlank1SyncpointId{27}; @@ -17,7 +17,7 @@ namespace skyline::service::nvdrv { ReserveSyncpoint(VBlank1SyncpointId, true); } - u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) { + u32 SyncpointManager::ReserveSyncpoint(u32 id, bool clientManaged) { if (syncpoints.at(id).reserved) throw exception("Requested syncpoint is in use"); @@ -27,7 +27,7 @@ namespace skyline::service::nvdrv { return id; } - u32 NvHostSyncpoint::FindFreeSyncpoint() { + u32 SyncpointManager::FindFreeSyncpoint() { for (u32 i{1}; i < syncpoints.size(); i++) if (!syncpoints[i].reserved) return i; @@ -35,12 +35,12 @@ namespace skyline::service::nvdrv { throw exception("Failed to find a free syncpoint!"); } - u32 NvHostSyncpoint::AllocateSyncpoint(bool clientManaged) { + u32 SyncpointManager::AllocateSyncpoint(bool clientManaged) { std::lock_guard lock(reservationLock); return ReserveSyncpoint(FindFreeSyncpoint(), clientManaged); } - bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) { + bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) { const SyncpointInfo &syncpoint{syncpoints.at(id)}; if (!syncpoint.reserved) @@ -53,25 +53,35 @@ namespace skyline::service::nvdrv { return (syncpoint.counterMax - threshold) >= (syncpoint.counterMin - threshold); } - u32 NvHostSyncpoint::IncrementSyncpointMaxExt(u32 id, u32 amount) { + u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) { if (!syncpoints.at(id).reserved) throw exception("Cannot increment an unreserved syncpoint!"); return syncpoints.at(id).counterMax += amount; } - u32 NvHostSyncpoint::ReadSyncpointMinValue(u32 id) { + u32 SyncpointManager::ReadSyncpointMinValue(u32 id) { if (!syncpoints.at(id).reserved) throw exception("Cannot read an unreserved syncpoint!"); return syncpoints.at(id).counterMin; } - u32 NvHostSyncpoint::UpdateMin(u32 id) { + u32 SyncpointManager::UpdateMin(u32 id) { if (!syncpoints.at(id).reserved) throw exception("Cannot update an unreserved syncpoint!"); syncpoints.at(id).counterMin = state.soc->host1x.syncpoints.at(id).Load(); return syncpoints.at(id).counterMin; } + + Fence SyncpointManager::GetSyncpointFence(u32 id) { + if (!syncpoints.at(id).reserved) + throw exception("Cannot access an unreserved syncpoint!"); + + return { + .id = id, + .threshold = syncpoints.at(id).counterMax + }; + } } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h b/app/src/main/cpp/skyline/services/nvdrv/core/syncpoint_manager.h similarity index 79% rename from app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h rename to app/src/main/cpp/skyline/services/nvdrv/core/syncpoint_manager.h index 55297674..2848b454 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h +++ b/app/src/main/cpp/skyline/services/nvdrv/core/syncpoint_manager.h @@ -1,18 +1,20 @@ -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: MIT OR MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +// Copyright © 2019-2020 Ryujinx Team and Contributors #pragma once #include +#include -namespace skyline::service::nvdrv { +namespace skyline::service::nvdrv::core { /** - * @brief NvHostSyncpoint handles allocating and accessing host1x syncpoints, these are cached versions of the HW syncpoints which are intermittently synced + * @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached versions of the HW syncpoints which are intermittently synced * @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them * @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html * @url https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c */ - class NvHostSyncpoint { + class SyncpointManager { private: struct SyncpointInfo { std::atomic counterMin; //!< The least value the syncpoint can be (The value it was when it was last synchronized with host1x) @@ -36,7 +38,7 @@ namespace skyline::service::nvdrv { u32 FindFreeSyncpoint(); public: - NvHostSyncpoint(const DeviceState &state); + SyncpointManager(const DeviceState &state); /** * @brief Finds a free syncpoint and reserves it @@ -49,6 +51,10 @@ namespace skyline::service::nvdrv { */ bool HasSyncpointExpired(u32 id, u32 threshold); + bool IsFenceSignalled(Fence fence) { + return HasSyncpointExpired(fence.id, fence.threshold); + } + /** * @brief Atomically increments the maximum value of a syncpoint by the given amount * @return The new max value of the syncpoint @@ -65,5 +71,10 @@ namespace skyline::service::nvdrv { * @return The new minimum value of the syncpoint */ u32 UpdateMin(u32 id); + + /** + * @return A fence that will be signalled once this syncpoint hits it's maximum value + */ + Fence GetSyncpointFence(u32 id); }; } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp new file mode 100644 index 00000000..81d85b19 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) +// Copyright © 2019-2020 Ryujinx Team and Contributors + +#include +#include +#include "ctrl.h" + +namespace skyline::service::nvdrv::device::nvhost { + Ctrl::SyncpointEvent::SyncpointEvent(const DeviceState &state) : event(std::make_shared(state, false)) {} + + void Ctrl::SyncpointEvent::Signal() { + // We should only signal the KEvent if the event is actively being waited on + if (state.exchange(State::Signalling) == State::Waiting) + event->Signal(); + + state = State::Signalled; + } + + void Ctrl::SyncpointEvent::Cancel(soc::host1x::Host1X &host1x) { + host1x.syncpoints.at(fence.id).DeregisterWaiter(waiterHandle); + waiterHandle = {}; + } + + void Ctrl::SyncpointEvent::RegisterWaiter(soc::host1x::Host1X &host1x, const Fence &pFence) { + fence = pFence; + state = State::Waiting; + waiterHandle = host1x.syncpoints.at(fence.id).RegisterWaiter(fence.threshold, [this] { Signal(); }); + } + + bool Ctrl::SyncpointEvent::IsInUse() { + return state == SyncpointEvent::State::Waiting || + state == SyncpointEvent::State::Cancelling || + state == SyncpointEvent::State::Signalling; + } + + Ctrl::Ctrl(const DeviceState &state, Core &core, const SessionContext &ctx) : NvDevice(state, core, ctx) {} + + u32 Ctrl::FindFreeSyncpointEvent(u32 syncpointId) { + u32 eventSlot{SyncpointEventCount}; //!< Holds the slot of the last populated event in the event array + u32 freeSlot{SyncpointEventCount}; //!< Holds the slot of the first unused event id + + for (u32 i{}; i < SyncpointEventCount; i++) { + if (syncpointEvents[i]) { + const auto &event{syncpointEvents[i]}; + + if (!event->IsInUse()) { + eventSlot = i; + + // This event is already attached to the requested syncpoint, so use it + if (event->fence.id == syncpointId) + return eventSlot; + } + } else if (freeSlot == SyncpointEventCount) { + freeSlot = i; + } + } + + // Use an unused event if possible + if (freeSlot < SyncpointEventCount) { + syncpointEvents[freeSlot] = std::make_unique(state); + return freeSlot; + } + + // Recycle an existing event if all else fails + if (eventSlot < SyncpointEventCount) + return eventSlot; + + throw exception("Failed to find a free nvhost event!"); + } + + PosixResult Ctrl::SyncpointWaitEventImpl(In fence, In timeout, InOut value, bool allocate) { + if (fence.id >= soc::host1x::SyncpointCount) + return PosixResult::InvalidArgument; + + // Check if the syncpoint has already expired using the last known values + if (core.syncpointManager.IsFenceSignalled(fence)) { + value.val = core.syncpointManager.ReadSyncpointMinValue(fence.id); + return PosixResult::Success; + } + + // Sync the syncpoint with the GPU then check again + auto minVal{core.syncpointManager.UpdateMin(fence.id)}; + if (core.syncpointManager.IsFenceSignalled(fence)) { + value.val = minVal; + return PosixResult::Success; + } + + // Don't try to register any waits if there is no timeout for them + if (!timeout) + return PosixResult::TryAgain; + + std::lock_guard lock(syncpointEventMutex); + + u32 slot = [&]() { + if (allocate) { + value.val = 0; + return FindFreeSyncpointEvent(fence.id); + } else { + return value.val; + } + }(); + + if (slot >= SyncpointEventCount) + return PosixResult::InvalidArgument; + + auto &event{syncpointEvents[slot]}; + if (!event) + return PosixResult::InvalidArgument; + + if (!event->IsInUse()) { + state.logger->Debug("Waiting on syncpoint event: {} with fence: ({}, {})", slot, fence.id, fence.threshold); + event->RegisterWaiter(state.soc->host1x, fence); + + value.val = 0; + + if (allocate) { + value.syncpointIdForAllocation = fence.id; + value.eventAllocated = true; + } else { + value.syncpointId = fence.id; + } + + // Slot will overwrite some of syncpointId here... it makes no sense for Nvidia to do this + value.val |= slot; + + return PosixResult::TryAgain; + } else { + return PosixResult::InvalidArgument; + } + } + + PosixResult Ctrl::SyncpointFreeEventLocked(In slot) { + if (slot >= SyncpointEventCount) + return PosixResult::InvalidArgument; + + auto &event{syncpointEvents[slot]}; + if (!event) + return PosixResult::Success; // If the event doesn't already exist then we don't need to do anything + + if (event->IsInUse()) // Avoid freeing events when they are still waiting etc. + return PosixResult::Busy; + + syncpointEvents[slot] = nullptr; + + return PosixResult::Success; + } + + PosixResult Ctrl::SyncpointClearEventWait(In value) { + u16 slot{value.slot}; + if (slot >= SyncpointEventCount) + return PosixResult::InvalidArgument; + + std::lock_guard lock(syncpointEventMutex); + + auto &event{syncpointEvents[slot]}; + if (!event) + return PosixResult::InvalidArgument; + + if (event->state.exchange(SyncpointEvent::State::Cancelling) == SyncpointEvent::State::Waiting) { + state.logger->Debug("Cancelling waiting syncpoint event: {}", slot); + event->Cancel(state.soc->host1x); + core.syncpointManager.UpdateMin(event->fence.id); + } + + event->state = SyncpointEvent::State::Cancelled; + event->event->ResetSignal(); + + return PosixResult::Success; + } + + PosixResult Ctrl::SyncpointWaitEvent(In fence, In timeout, InOut value) { + return SyncpointWaitEventImpl(fence, timeout, value, true); + } + + PosixResult Ctrl::SyncpointWaitEventSingle(In fence, In timeout, InOut value) { + return SyncpointWaitEventImpl(fence, timeout, value, false); + } + + PosixResult Ctrl::SyncpointAllocateEvent(In slot) { + state.logger->Debug("Registering syncpoint event: {}", slot); + + if (slot >= SyncpointEventCount) + return PosixResult::InvalidArgument; + + std::lock_guard lock(syncpointEventMutex); + + auto &event{syncpointEvents[slot]}; + if (event) // Recreate event if it already exists + if (auto err{SyncpointFreeEventLocked(slot)}; err != PosixResult::Success) + return err; + + event = std::make_unique(state); + + return PosixResult::Success; + } + + PosixResult Ctrl::SyncpointFreeEvent(In slot) { + std::lock_guard lock(syncpointEventMutex); + return SyncpointFreeEventLocked(slot); + } + + PosixResult Ctrl::SyncpointFreeEventBatch(In bitmask) { + auto err{PosixResult::Success}; + + // Avoid repeated locks/unlocks by just locking now + std::lock_guard lock(syncpointEventMutex); + + for (int i{}; i < 64; i++) { + if (bitmask & (1 << i)) + if (auto freeErr{SyncpointFreeEventLocked(i)}; freeErr != PosixResult::Success) + err = freeErr; + } + + return err; + } + + std::shared_ptr Ctrl::QueryEvent(u32 valueRaw) { + SyncpointEventValue value{.val = valueRaw}; + + // I have no idea why nvidia does this + u16 slot{value.eventAllocated ? static_cast(value.partialSlot) : value.slot}; + if (slot >= SyncpointEventCount) + return nullptr; + + u32 syncpointId{value.eventAllocated ? static_cast(value.syncpointIdForAllocation) : value.syncpointId}; + + std::lock_guard lock(syncpointEventMutex); + + auto &event{syncpointEvents[slot]}; + if (event && event->fence.id == syncpointId) + return event->event; + + return nullptr; + } + +#include + static constexpr u32 CtrlMagic{0}; + + IOCTL_HANDLER_FUNC(Ctrl, ({ + IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x1C), + SyncpointClearEventWait, ARGS(In)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(CtrlMagic), FUNC(0x1D), + SyncpointWaitEvent, ARGS(In, In, InOut)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(CtrlMagic), FUNC(0x1E), + SyncpointWaitEventSingle, ARGS(In, In, InOut)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x1F), + SyncpointAllocateEvent, ARGS(In)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x20), + SyncpointFreeEvent, ARGS(In)) + IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(CtrlMagic), FUNC(0x21), + SyncpointFreeEventBatch, ARGS(In)) + + IOCTL_CASE_RESULT(INOUT, SIZE(0x183), MAGIC(CtrlMagic), FUNC(0x1B), + PosixResult::InvalidArgument) // GetConfig isn't available in production + })) +#include +} diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h new file mode 100644 index 00000000..d41844c8 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h @@ -0,0 +1,142 @@ +// 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::device::nvhost { + /** + * @brief nvhost::Ctrl (/dev/nvhost-ctrl) provides IOCTLs for synchronization using syncpoints + * @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl + */ + class Ctrl : public NvDevice { + public: + /** + * @brief Metadata about a syncpoint event, used by QueryEvent and SyncpointEventWait + */ + union SyncpointEventValue { + u32 val; + + struct { + u8 partialSlot : 4; + u32 syncpointId : 28; + }; + + struct { + u16 slot; + u16 syncpointIdForAllocation : 12; + bool eventAllocated : 1; + u8 _pad12_ : 3; + }; + }; + static_assert(sizeof(SyncpointEventValue) == sizeof(u32)); + + private: + /** + * @brief Syncpoint Events are used to expose fences to the userspace, they can be waited on using an IOCTL + * or be converted into a native HOS KEvent object that can be waited on just like any other KEvent on the guest + */ + class SyncpointEvent { + private: + soc::host1x::Syncpoint::WaiterHandle waiterHandle{}; + + void Signal(); + + public: + enum class State { + Available = 0, + Waiting = 1, + Cancelling = 2, + Signalling = 3, + Signalled = 4, + Cancelled = 5, + }; + + SyncpointEvent(const DeviceState &state); + + std::atomic state{State::Available}; + Fence fence{}; //!< The fence that is associated with this syncpoint event + std::shared_ptr event{}; //!< Returned by 'QueryEvent' + + /** + * @brief Removes any wait requests on a syncpoint event and resets its state + * @note Accesses to this function for a specific event should be locked + */ + void Cancel(soc::host1x::Host1X &host1x); + + /** + * @brief Asynchronously waits on a syncpoint event using the given fence + * @note Accesses to this function for a specific event should be locked + */ + void RegisterWaiter(soc::host1x::Host1X &host1x, const Fence &fence); + + bool IsInUse(); + }; + + static constexpr u32 SyncpointEventCount{64}; //!< The maximum number of nvhost syncpoint events + + std::mutex syncpointEventMutex; + std::array, SyncpointEventCount> syncpointEvents{}; + + /** + * @brief Finds a free syncpoint event for the given syncpoint id + * @note syncpointEventMutex MUST be locked when calling this + * @return The free event slot + */ + u32 FindFreeSyncpointEvent(u32 syncpointId); + + PosixResult SyncpointWaitEventImpl(In fence, In timeout, InOut value, bool allocate); + + /** + * @brief Frees a single syncpoint event + * @note syncpointEventMutex MUST be locked when calling this + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_UNREGISTER_EVENT + */ + PosixResult SyncpointFreeEventLocked(In slot); + + public: + Ctrl(const DeviceState &state, Core &core, const SessionContext &ctx); + + /** + * @brief Clears a syncpoint event + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_CLEAR_EVENT_WAIT + */ + PosixResult SyncpointClearEventWait(In value); + + /** + * @brief Allocates a syncpoint event for the given syncpoint and registers as it waiting for the given fence + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_WAIT_EVENT + */ + PosixResult SyncpointWaitEvent(In fence, In timeout, InOut value); + + /** + * @brief Waits on a specific syncpoint event and registers as it waiting for the given fence + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_WAIT_EVENT_SINGLE + */ + PosixResult SyncpointWaitEventSingle(In fence, In timeout, InOut value); + + /** + * @brief Allocates a new syncpoint event at the given slot + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_ALLOC_EVENT + */ + PosixResult SyncpointAllocateEvent(In slot); + + /** + * @brief Frees a single syncpoint event + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_UNREGISTER_EVENT + */ + PosixResult SyncpointFreeEvent(In slot); + + /** + * @brief Frees a bitmask of a syncpoint events + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_FREE_EVENTS + */ + PosixResult SyncpointFreeEventBatch(In bitmask); + + std::shared_ptr QueryEvent(u32 slot) override; + + PosixResult Ioctl(IoctlDescriptor cmd, span buffer) override; + }; +} diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp deleted file mode 100644 index ac9834c5..00000000 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -// Copyright © 2019-2020 Ryujinx Team and Contributors - -#include -#include -#include -#include "nvhost_ctrl.h" - -namespace skyline::service::nvdrv::device { - SyncpointEvent::SyncpointEvent(const DeviceState &state) : event(std::make_shared(state, false)) {} - - /** - * @brief Metadata about a syncpoint event, used by QueryEvent and SyncpointEventWait - */ - union SyncpointEventValue { - u32 val; - - struct { - u8 _pad0_ : 4; - u32 syncpointIdAsync : 28; - }; - - struct { - union { - u8 eventSlotAsync; - u16 eventSlotNonAsync; - }; - u16 syncpointIdNonAsync : 12; - bool nonAsync : 1; - u8 _pad12_ : 3; - }; - }; - static_assert(sizeof(SyncpointEventValue) == sizeof(u32)); - - void SyncpointEvent::Signal() { - std::lock_guard lock(mutex); - - auto oldState{state}; - state = State::Signalling; - - // We should only signal the KEvent if the event is actively being waited on - if (oldState == State::Waiting) - event->Signal(); - - state = State::Signalled; - } - - void SyncpointEvent::Cancel(soc::host1x::Host1X &host1x) { - std::lock_guard lock(mutex); - - host1x.syncpoints.at(fence.id).DeregisterWaiter(waiterHandle); - waiterHandle = {}; - Signal(); - event->ResetSignal(); - } - - void SyncpointEvent::Wait(soc::host1x::Host1X &host1x, const Fence &pFence) { - std::lock_guard lock(mutex); - - fence = pFence; - state = State::Waiting; - waiterHandle = host1x.syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); }); - } - - NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state) {} - - u32 NvHostCtrl::FindFreeSyncpointEvent(u32 syncpointId) { - u32 eventSlot{constant::NvHostEventCount}; //!< Holds the slot of the last populated event in the event array - u32 freeSlot{constant::NvHostEventCount}; //!< Holds the slot of the first unused event id - std::lock_guard lock(syncpointEventMutex); - - for (u32 i{}; i < constant::NvHostEventCount; i++) { - if (syncpointEvents[i]) { - auto event{syncpointEvents[i]}; - - if (event->state == SyncpointEvent::State::Cancelled || event->state == SyncpointEvent::State::Available || event->state == SyncpointEvent::State::Signalled) { - eventSlot = i; - - // This event is already attached to the requested syncpoint, so use it - if (event->fence.id == syncpointId) - return eventSlot; - } - } else if (freeSlot == constant::NvHostEventCount) { - freeSlot = i; - } - } - - // Use an unused event if possible - if (freeSlot < constant::NvHostEventCount) { - syncpointEvents[eventSlot] = std::make_shared(state); - return freeSlot; - } - - // Recycle an existing event if all else fails - if (eventSlot < constant::NvHostEventCount) - return eventSlot; - - throw exception("Failed to find a free nvhost event!"); - } - - NvStatus NvHostCtrl::SyncpointEventWaitImpl(span buffer, bool async) { - struct Data { - Fence fence; // In - u32 timeout; // In - SyncpointEventValue value; // InOut - } &data = buffer.as(); - - if (data.fence.id >= soc::host1x::SyncpointCount) - return NvStatus::BadValue; - - if (data.timeout == 0) - return NvStatus::Timeout; - - auto driver{nvdrv::driver.lock()}; - auto &hostSyncpoint{driver->hostSyncpoint}; - - // Check if the syncpoint has already expired using the last known values - if (hostSyncpoint.HasSyncpointExpired(data.fence.id, data.fence.value)) { - data.value.val = hostSyncpoint.ReadSyncpointMinValue(data.fence.id); - return NvStatus::Success; - } - - // Sync the syncpoint with the GPU then check again - auto minVal{hostSyncpoint.UpdateMin(data.fence.id)}; - if (hostSyncpoint.HasSyncpointExpired(data.fence.id, data.fence.value)) { - data.value.val = minVal; - return NvStatus::Success; - } - - u32 eventSlot{}; - if (async) { - if (data.value.val >= constant::NvHostEventCount) - return NvStatus::BadValue; - - eventSlot = data.value.val; - } else { - data.fence.value = 0; - - eventSlot = FindFreeSyncpointEvent(data.fence.id); - } - - std::lock_guard lock(syncpointEventMutex); - - auto event{syncpointEvents[eventSlot]}; - if (!event) - return NvStatus::BadValue; - - std::lock_guard eventLock(event->mutex); - - if (event->state == SyncpointEvent::State::Cancelled || event->state == SyncpointEvent::State::Available || event->state == SyncpointEvent::State::Signalled) { - state.logger->Debug("Waiting on syncpoint event: {} with fence: ({}, {})", eventSlot, data.fence.id, data.fence.value); - event->Wait(state.soc->host1x, data.fence); - - data.value.val = 0; - - if (async) { - data.value.syncpointIdAsync = data.fence.id; - } else { - data.value.syncpointIdNonAsync = data.fence.id; - data.value.nonAsync = true; - } - - data.value.val |= eventSlot; - - return NvStatus::Timeout; - } else { - return NvStatus::BadValue; - } - } - - NvStatus NvHostCtrl::GetConfig(IoctlType type, span buffer, span inlineBuffer) { - return NvStatus::BadValue; - } - - NvStatus NvHostCtrl::SyncpointClearEventWait(IoctlType type, span buffer, span inlineBuffer) { - auto eventSlot{buffer.as()}; - - if (eventSlot >= constant::NvHostEventCount) - return NvStatus::BadValue; - - std::lock_guard lock(syncpointEventMutex); - - auto event{syncpointEvents[eventSlot]}; - if (!event) - return NvStatus::BadValue; - - std::lock_guard eventLock(event->mutex); - - if (event->state == SyncpointEvent::State::Waiting) { - event->state = SyncpointEvent::State::Cancelling; - state.logger->Debug("Cancelling waiting syncpoint event: {}", eventSlot); - event->Cancel(state.soc->host1x); - } - - event->state = SyncpointEvent::State::Cancelled; - - auto driver{nvdrv::driver.lock()}; - auto &hostSyncpoint{driver->hostSyncpoint}; - hostSyncpoint.UpdateMin(event->fence.id); - - return NvStatus::Success; - } - - NvStatus NvHostCtrl::SyncpointEventWait(IoctlType type, span buffer, span inlineBuffer) { - return SyncpointEventWaitImpl(buffer, false); - } - - NvStatus NvHostCtrl::SyncpointEventWaitAsync(IoctlType type, span buffer, span inlineBuffer) { - return SyncpointEventWaitImpl(buffer, true); - } - - NvStatus NvHostCtrl::SyncpointRegisterEvent(IoctlType type, span buffer, span inlineBuffer) { - auto eventSlot{buffer.as()}; - state.logger->Debug("Registering syncpoint event: {}", eventSlot); - - if (eventSlot >= constant::NvHostEventCount) - return NvStatus::BadValue; - - std::lock_guard lock(syncpointEventMutex); - - auto &event{syncpointEvents[eventSlot]}; - if (event) - throw exception("Recreating events is unimplemented"); - - event = std::make_shared(state); - - return NvStatus::Success; - } - - std::shared_ptr NvHostCtrl::QueryEvent(u32 eventId) { - SyncpointEventValue eventValue{.val = eventId}; - std::lock_guard lock(syncpointEventMutex); - - auto event{syncpointEvents.at(eventValue.nonAsync ? eventValue.eventSlotNonAsync : eventValue.eventSlotAsync)}; - - if (event && event->fence.id == (eventValue.nonAsync ? eventValue.syncpointIdNonAsync : eventValue.syncpointIdAsync)) - return event->event; - - return nullptr; - } -} diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h deleted file mode 100644 index 07562979..00000000 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) - -#pragma once - -#include -#include "nvdevice.h" - -namespace skyline { - namespace constant { - constexpr u32 NvHostEventCount{64}; //!< The maximum number of nvhost events - } - - namespace service::nvdrv::device { - /** - * @brief Syncpoint Events are used to expose fences to the userspace, they can be waited on using an IOCTL or be converted into a native HOS KEvent object that can be waited on just like any other KEvent on the guest - */ - class SyncpointEvent { - private: - soc::host1x::Syncpoint::WaiterHandle waiterHandle{}; - - void Signal(); - - public: - enum class State { - Available = 0, - Waiting = 1, - Cancelling = 2, - Signalling = 3, - Signalled = 4, - Cancelled = 5, - }; - - SyncpointEvent(const DeviceState &state); - - std::recursive_mutex mutex; //!< Protects access to the entire event - State state{State::Available}; - Fence fence{}; //!< The fence that is associated with this syncpoint event - std::shared_ptr event{}; //!< Returned by 'QueryEvent' - - /** - * @brief Removes any wait requests on a syncpoint event and resets its state - */ - void Cancel(soc::host1x::Host1X &host1x); - - /** - * @brief Asynchronously waits on a syncpoint event using the given fence - */ - void Wait(soc::host1x::Host1X &host1x, const Fence &fence); - }; - - /** - * @brief NvHostCtrl (/dev/nvhost-ctrl) is used for NvHost management and synchronisation - * @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl - */ - class NvHostCtrl : public NvDevice { - private: - std::mutex syncpointEventMutex; - std::array, constant::NvHostEventCount> syncpointEvents{}; - - /** - * @brief Finds a free syncpoint event for the given id - * @return The index of the syncpoint event in the map - */ - u32 FindFreeSyncpointEvent(u32 syncpointId); - - NvStatus SyncpointEventWaitImpl(span buffer, bool async); - - public: - NvHostCtrl(const DeviceState &state); - - /** - * @brief Gets the value of an nvdrv setting, it returns an error code on production switches - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_GET_CONFIG - */ - NvStatus GetConfig(IoctlType type, span buffer, span inlineBuffer); - - /** - * @brief Clears a syncpoint event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_CLEAR_EVENT_WAIT - */ - NvStatus SyncpointClearEventWait(IoctlType type, span buffer, span inlineBuffer); - - /** - * @brief Synchronously waits on a syncpoint event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT - */ - NvStatus SyncpointEventWait(IoctlType type, span buffer, span inlineBuffer); - - /** - * @brief Asynchronously waits on a syncpoint event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT_ASYNC - */ - NvStatus SyncpointEventWaitAsync(IoctlType type, span buffer, span inlineBuffer); - - /** - * @brief Registers a syncpoint event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_REGISTER_EVENT - */ - NvStatus SyncpointRegisterEvent(IoctlType type, span buffer, span inlineBuffer); - - std::shared_ptr QueryEvent(u32 eventId) override; - - NVDEVICE_DECL( - NVFUNC(0x001B, NvHostCtrl, GetConfig), - NVFUNC(0x001C, NvHostCtrl, SyncpointClearEventWait), - NVFUNC(0x001D, NvHostCtrl, SyncpointEventWait), - NVFUNC(0x001E, NvHostCtrl, SyncpointEventWaitAsync), - NVFUNC(0x001F, NvHostCtrl, SyncpointRegisterEvent) - ) - }; - } -}