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 index ebc4e2e7..c749d7b1 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp @@ -1,5 +1,6 @@ // 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 @@ -7,75 +8,102 @@ #include "nvhost_ctrl.h" namespace skyline::service::nvdrv::device { - NvHostEvent::NvHostEvent(const DeviceState &state) : event(std::make_shared(state, false)) {} + 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); - void NvHostEvent::Signal() { auto oldState{state}; state = State::Signalling; - // This is to ensure that the HOS event isn't signalled when the nvhost event is cancelled + // We should only signal the KEvent if the event is actively being waited on if (oldState == State::Waiting) event->Signal(); state = State::Signalled; } - void NvHostEvent::Cancel(const std::shared_ptr &gpuState) { + void SyncpointEvent::Cancel(const std::shared_ptr &gpuState) { + std::lock_guard lock(mutex); + gpuState->syncpoints.at(fence.id).DeregisterWaiter(waiterId); Signal(); event->ResetSignal(); } - void NvHostEvent::Wait(const std::shared_ptr &gpuState, const Fence &fence) { - waiterId = gpuState->syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); }); + void SyncpointEvent::Wait(const std::shared_ptr &gpuState, const Fence &fence) { + std::lock_guard lock(mutex); - // If waiter ID is zero then the fence has already been hit and was signalled in the call to RegisterWaiter - if (waiterId) { - this->fence = fence; - state = State::Waiting; - } + this->fence = fence; + state = State::Waiting; + waiterId = gpuState->syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); }); } + NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state) {} - u32 NvHostCtrl::FindFreeEvent(u32 syncpointId) { - u32 eventIndex{constant::NvHostEventCount}; //!< Holds the index of the last populated event in the event array - u32 freeIndex{constant::NvHostEventCount}; //!< Holds the index of the first unused event id + 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 (events[i]) { - const auto &event{*events[i]}; + if (syncpointEvents[i]) { + auto event{syncpointEvents[i]}; - if (event.state == NvHostEvent::State::Cancelled || event.state == NvHostEvent::State::Available || event.state == NvHostEvent::State::Signalled) { - eventIndex = 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 eventIndex; + if (event->fence.id == syncpointId) + return eventSlot; } - } else if (freeIndex == constant::NvHostEventCount) { - freeIndex = i; + } else if (freeSlot == constant::NvHostEventCount) { + freeSlot = i; } } // Use an unused event if possible - if (freeIndex < constant::NvHostEventCount) { - events.at(freeIndex) = static_cast>(NvHostEvent(state)); - return freeIndex; + if (freeSlot < constant::NvHostEventCount) { + syncpointEvents[eventSlot] = std::make_shared(state); + return freeSlot; } // Recycle an existing event if all else fails - if (eventIndex < constant::NvHostEventCount) - return eventIndex; + if (eventSlot < constant::NvHostEventCount) + return eventSlot; throw exception("Failed to find a free nvhost event!"); } - NvStatus NvHostCtrl::EventWaitImpl(span buffer, bool async) { + NvStatus NvHostCtrl::SyncpointEventWaitImpl(span buffer, bool async) { struct Data { - Fence fence; // In - u32 timeout; // In - EventValue value; // InOut + Fence fence; // In + u32 timeout; // In + SyncpointEventValue value; // InOut } &data = buffer.as(); if (data.fence.id >= constant::MaxHwSyncpointCount) @@ -100,22 +128,29 @@ namespace skyline::service::nvdrv::device { return NvStatus::Success; } - u32 userEventId{}; + u32 eventSlot{}; if (async) { - if (data.value.val >= constant::NvHostEventCount || !events.at(data.value.val)) + if (data.value.val >= constant::NvHostEventCount) return NvStatus::BadValue; - userEventId = data.value.val; + eventSlot = data.value.val; } else { data.fence.value = 0; - userEventId = FindFreeEvent(data.fence.id); + eventSlot = FindFreeSyncpointEvent(data.fence.id); } - auto &event{*events.at(userEventId)}; - if (event.state == NvHostEvent::State::Cancelled || event.state == NvHostEvent::State::Available || event.state == NvHostEvent::State::Signalled) { - state.logger->Debug("Waiting on nvhost event: {} with fence: {}", userEventId, data.fence.id); - event.Wait(state.gpu, data.fence); + 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.gpu, data.fence); data.value.val = 0; @@ -126,7 +161,7 @@ namespace skyline::service::nvdrv::device { data.value.nonAsync = true; } - data.value.val |= userEventId; + data.value.val |= eventSlot; return NvStatus::Timeout; } else { @@ -138,53 +173,66 @@ namespace skyline::service::nvdrv::device { return NvStatus::BadValue; } - NvStatus NvHostCtrl::EventSignal(IoctlType type, span buffer, span inlineBuffer) { - auto userEventId{buffer.as()}; - state.logger->Debug("Signalling nvhost event: {}", userEventId); + NvStatus NvHostCtrl::SyncpointClearEventWait(IoctlType type, span buffer, span inlineBuffer) { + auto eventSlot{buffer.as()}; - if (userEventId >= constant::NvHostEventCount || !events.at(userEventId)) + if (eventSlot >= constant::NvHostEventCount) return NvStatus::BadValue; - auto &event{*events.at(userEventId)}; + std::lock_guard lock(syncpointEventMutex); - if (event.state == NvHostEvent::State::Waiting) { - event.state = NvHostEvent::State::Cancelling; - state.logger->Debug("Cancelling waiting nvhost event: {}", userEventId); - event.Cancel(state.gpu); + 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.gpu); } - event.state = NvHostEvent::State::Cancelled; + event->state = SyncpointEvent::State::Cancelled; auto driver{nvdrv::driver.lock()}; auto &hostSyncpoint{driver->hostSyncpoint}; - hostSyncpoint.UpdateMin(event.fence.id); + hostSyncpoint.UpdateMin(event->fence.id); return NvStatus::Success; } - NvStatus NvHostCtrl::EventWait(IoctlType type, span buffer, span inlineBuffer) { - return EventWaitImpl(buffer, false); + NvStatus NvHostCtrl::SyncpointEventWait(IoctlType type, span buffer, span inlineBuffer) { + return SyncpointEventWaitImpl(buffer, false); } - NvStatus NvHostCtrl::EventWaitAsync(IoctlType type, span buffer, span inlineBuffer) { - return EventWaitImpl(buffer, true); + NvStatus NvHostCtrl::SyncpointEventWaitAsync(IoctlType type, span buffer, span inlineBuffer) { + return SyncpointEventWaitImpl(buffer, true); } - NvStatus NvHostCtrl::EventRegister(IoctlType type, span buffer, span inlineBuffer) { - auto userEventId{buffer.as()}; - state.logger->Debug("Registering nvhost event: {}", userEventId); + NvStatus NvHostCtrl::SyncpointRegisterEvent(IoctlType type, span buffer, span inlineBuffer) { + auto eventSlot{buffer.as()}; + state.logger->Debug("Registering syncpoint event: {}", eventSlot); - auto &event{events.at(userEventId)}; + 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 = NvHostEvent(state); + + event = std::make_shared(state); return NvStatus::Success; } std::shared_ptr NvHostCtrl::QueryEvent(u32 eventId) { - EventValue eventValue{.val = eventId}; - const auto &event{events.at(eventValue.nonAsync ? eventValue.eventSlotNonAsync : eventValue.eventSlotAsync)}; + 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; 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 index 3fd7c0a5..667efb9e 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h @@ -13,9 +13,9 @@ namespace skyline { namespace service::nvdrv::device { /** - * @brief 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 + * @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 NvHostEvent { + class SyncpointEvent { private: u64 waiterId{}; @@ -31,61 +31,40 @@ namespace skyline { Cancelled = 5, }; - NvHostEvent(const DeviceState &state); + SyncpointEvent(const DeviceState &state); + std::recursive_mutex mutex; //!< Protects access to the entire event State state{State::Available}; - Fence fence{}; //!< The fence that is attached to this event + Fence fence{}; //!< The fence that is associated with this syncpoint event std::shared_ptr event{}; //!< Returned by 'QueryEvent' /** - * @brief Stops any wait requests on an event and immediately signals it + * @brief Removes any wait requests on a syncpoint event and resets its state */ void Cancel(const std::shared_ptr &gpuState); /** - * @brief Asynchronously waits on an event using the given fence + * @brief Asynchronously waits on a syncpoint event using the given fence */ void Wait(const std::shared_ptr &gpuState, const Fence &fence); }; /** - * @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization + * @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: - /** - * @brief Metadata about an event, it's used by QueryEvent and EventWait - */ - union EventValue { - 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(EventValue) == sizeof(u32)); - - std::array, constant::NvHostEventCount> events{}; + std::mutex syncpointEventMutex; + std::array, constant::NvHostEventCount> syncpointEvents{}; /** - * @brief Finds a free event for the given syncpoint id - * @return The index of the event in the event map + * @brief Finds a free syncpoint event for the given id + * @return The index of the syncpoint event in the map */ - u32 FindFreeEvent(u32 syncpointId); + u32 FindFreeSyncpointEvent(u32 syncpointId); - NvStatus EventWaitImpl(span buffer, bool async); + NvStatus SyncpointEventWaitImpl(span buffer, bool async); public: NvHostCtrl(const DeviceState &state); @@ -97,37 +76,37 @@ namespace skyline { NvStatus GetConfig(IoctlType type, span buffer, span inlineBuffer); /** - * @brief Signals an NvHost event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_SIGNAL + * @brief Clears a syncpoint event + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_CLEAR_EVENT_WAIT */ - NvStatus EventSignal(IoctlType type, span buffer, span inlineBuffer); + NvStatus SyncpointClearEventWait(IoctlType type, span buffer, span inlineBuffer); /** - * @brief Synchronously waits on an NvHost event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_WAIT + * @brief Synchronously waits on a syncpoint event + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT */ - NvStatus EventWait(IoctlType type, span buffer, span inlineBuffer); + NvStatus SyncpointEventWait(IoctlType type, span buffer, span inlineBuffer); /** - * @brief Asynchronously waits on an NvHost event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_WAIT_ASYNC + * @brief Asynchronously waits on a syncpoint event + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT_ASYNC */ - NvStatus EventWaitAsync(IoctlType type, span buffer, span inlineBuffer); + NvStatus SyncpointEventWaitAsync(IoctlType type, span buffer, span inlineBuffer); /** - * @brief Registers an NvHost event - * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_REGISTER + * @brief Registers a syncpoint event + * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_REGISTER_EVENT */ - NvStatus EventRegister(IoctlType type, span buffer, span inlineBuffer); + NvStatus SyncpointRegisterEvent(IoctlType type, span buffer, span inlineBuffer); std::shared_ptr QueryEvent(u32 eventId); NVDEVICE_DECL( NVFUNC(0x001B, NvHostCtrl, GetConfig), - NVFUNC(0x001C, NvHostCtrl, EventSignal), - NVFUNC(0x001D, NvHostCtrl, EventWait), - NVFUNC(0x001E, NvHostCtrl, EventWaitAsync), - NVFUNC(0x001F, NvHostCtrl, EventRegister) + NVFUNC(0x001C, NvHostCtrl, SyncpointClearEventWait), + NVFUNC(0x001D, NvHostCtrl, SyncpointEventWait), + NVFUNC(0x001E, NvHostCtrl, SyncpointEventWaitAsync), + NVFUNC(0x001F, NvHostCtrl, SyncpointRegisterEvent) ) }; }