Add locking to nvhost-ctrl syncpoint events and sync with switchbrew

NvHostEvents were renamed to SyncpointEvents which is a much clearer
name that more accurately describes them. Locking is needed as IOCTLs
can be called asynchronously and so event registration and signalling
can race.
This commit is contained in:
Billy Laws 2020-11-17 18:17:26 +00:00 committed by ◱ Mark
parent c282276b74
commit 78cdb1eeb4
2 changed files with 140 additions and 113 deletions

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2019-2020 Ryujinx Team and Contributors
#include <gpu.h> #include <gpu.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
@ -7,75 +8,102 @@
#include "nvhost_ctrl.h" #include "nvhost_ctrl.h"
namespace skyline::service::nvdrv::device { namespace skyline::service::nvdrv::device {
NvHostEvent::NvHostEvent(const DeviceState &state) : event(std::make_shared<type::KEvent>(state, false)) {} SyncpointEvent::SyncpointEvent(const DeviceState &state) : event(std::make_shared<type::KEvent>(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}; auto oldState{state};
state = State::Signalling; 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) if (oldState == State::Waiting)
event->Signal(); event->Signal();
state = State::Signalled; state = State::Signalled;
} }
void NvHostEvent::Cancel(const std::shared_ptr<gpu::GPU> &gpuState) { void SyncpointEvent::Cancel(const std::shared_ptr<gpu::GPU> &gpuState) {
std::lock_guard lock(mutex);
gpuState->syncpoints.at(fence.id).DeregisterWaiter(waiterId); gpuState->syncpoints.at(fence.id).DeregisterWaiter(waiterId);
Signal(); Signal();
event->ResetSignal(); event->ResetSignal();
} }
void NvHostEvent::Wait(const std::shared_ptr<gpu::GPU> &gpuState, const Fence &fence) { void SyncpointEvent::Wait(const std::shared_ptr<gpu::GPU> &gpuState, const Fence &fence) {
waiterId = gpuState->syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); }); 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 this->fence = fence;
if (waiterId) { state = State::Waiting;
this->fence = fence; waiterId = gpuState->syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); });
state = State::Waiting;
}
} }
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state) {} NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state) {}
u32 NvHostCtrl::FindFreeEvent(u32 syncpointId) { u32 NvHostCtrl::FindFreeSyncpointEvent(u32 syncpointId) {
u32 eventIndex{constant::NvHostEventCount}; //!< Holds the index of the last populated event in the event array u32 eventSlot{constant::NvHostEventCount}; //!< Holds the slot of the last populated event in the event array
u32 freeIndex{constant::NvHostEventCount}; //!< Holds the index of the first unused event id 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++) { for (u32 i{}; i < constant::NvHostEventCount; i++) {
if (events[i]) { if (syncpointEvents[i]) {
const auto &event{*events[i]}; auto event{syncpointEvents[i]};
if (event.state == NvHostEvent::State::Cancelled || event.state == NvHostEvent::State::Available || event.state == NvHostEvent::State::Signalled) { if (event->state == SyncpointEvent::State::Cancelled || event->state == SyncpointEvent::State::Available || event->state == SyncpointEvent::State::Signalled) {
eventIndex = i; eventSlot = i;
// This event is already attached to the requested syncpoint, so use it // This event is already attached to the requested syncpoint, so use it
if (event.fence.id == syncpointId) if (event->fence.id == syncpointId)
return eventIndex; return eventSlot;
} }
} else if (freeIndex == constant::NvHostEventCount) { } else if (freeSlot == constant::NvHostEventCount) {
freeIndex = i; freeSlot = i;
} }
} }
// Use an unused event if possible // Use an unused event if possible
if (freeIndex < constant::NvHostEventCount) { if (freeSlot < constant::NvHostEventCount) {
events.at(freeIndex) = static_cast<const std::optional<NvHostEvent>>(NvHostEvent(state)); syncpointEvents[eventSlot] = std::make_shared<SyncpointEvent>(state);
return freeIndex; return freeSlot;
} }
// Recycle an existing event if all else fails // Recycle an existing event if all else fails
if (eventIndex < constant::NvHostEventCount) if (eventSlot < constant::NvHostEventCount)
return eventIndex; return eventSlot;
throw exception("Failed to find a free nvhost event!"); throw exception("Failed to find a free nvhost event!");
} }
NvStatus NvHostCtrl::EventWaitImpl(span<u8> buffer, bool async) { NvStatus NvHostCtrl::SyncpointEventWaitImpl(span<u8> buffer, bool async) {
struct Data { struct Data {
Fence fence; // In Fence fence; // In
u32 timeout; // In u32 timeout; // In
EventValue value; // InOut SyncpointEventValue value; // InOut
} &data = buffer.as<Data>(); } &data = buffer.as<Data>();
if (data.fence.id >= constant::MaxHwSyncpointCount) if (data.fence.id >= constant::MaxHwSyncpointCount)
@ -100,22 +128,29 @@ namespace skyline::service::nvdrv::device {
return NvStatus::Success; return NvStatus::Success;
} }
u32 userEventId{}; u32 eventSlot{};
if (async) { if (async) {
if (data.value.val >= constant::NvHostEventCount || !events.at(data.value.val)) if (data.value.val >= constant::NvHostEventCount)
return NvStatus::BadValue; return NvStatus::BadValue;
userEventId = data.value.val; eventSlot = data.value.val;
} else { } else {
data.fence.value = 0; data.fence.value = 0;
userEventId = FindFreeEvent(data.fence.id); eventSlot = FindFreeSyncpointEvent(data.fence.id);
} }
auto &event{*events.at(userEventId)}; std::lock_guard lock(syncpointEventMutex);
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); auto event{syncpointEvents[eventSlot]};
event.Wait(state.gpu, data.fence); 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; data.value.val = 0;
@ -126,7 +161,7 @@ namespace skyline::service::nvdrv::device {
data.value.nonAsync = true; data.value.nonAsync = true;
} }
data.value.val |= userEventId; data.value.val |= eventSlot;
return NvStatus::Timeout; return NvStatus::Timeout;
} else { } else {
@ -138,53 +173,66 @@ namespace skyline::service::nvdrv::device {
return NvStatus::BadValue; return NvStatus::BadValue;
} }
NvStatus NvHostCtrl::EventSignal(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) { NvStatus NvHostCtrl::SyncpointClearEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
auto userEventId{buffer.as<u16>()}; auto eventSlot{buffer.as<u16>()};
state.logger->Debug("Signalling nvhost event: {}", userEventId);
if (userEventId >= constant::NvHostEventCount || !events.at(userEventId)) if (eventSlot >= constant::NvHostEventCount)
return NvStatus::BadValue; return NvStatus::BadValue;
auto &event{*events.at(userEventId)}; std::lock_guard lock(syncpointEventMutex);
if (event.state == NvHostEvent::State::Waiting) { auto event{syncpointEvents[eventSlot]};
event.state = NvHostEvent::State::Cancelling; if (!event)
state.logger->Debug("Cancelling waiting nvhost event: {}", userEventId); return NvStatus::BadValue;
event.Cancel(state.gpu);
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 driver{nvdrv::driver.lock()};
auto &hostSyncpoint{driver->hostSyncpoint}; auto &hostSyncpoint{driver->hostSyncpoint};
hostSyncpoint.UpdateMin(event.fence.id); hostSyncpoint.UpdateMin(event->fence.id);
return NvStatus::Success; return NvStatus::Success;
} }
NvStatus NvHostCtrl::EventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) { NvStatus NvHostCtrl::SyncpointEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return EventWaitImpl(buffer, false); return SyncpointEventWaitImpl(buffer, false);
} }
NvStatus NvHostCtrl::EventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) { NvStatus NvHostCtrl::SyncpointEventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return EventWaitImpl(buffer, true); return SyncpointEventWaitImpl(buffer, true);
} }
NvStatus NvHostCtrl::EventRegister(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) { NvStatus NvHostCtrl::SyncpointRegisterEvent(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
auto userEventId{buffer.as<u32>()}; auto eventSlot{buffer.as<u32>()};
state.logger->Debug("Registering nvhost event: {}", userEventId); 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) if (event)
throw exception("Recreating events is unimplemented"); throw exception("Recreating events is unimplemented");
event = NvHostEvent(state);
event = std::make_shared<SyncpointEvent>(state);
return NvStatus::Success; return NvStatus::Success;
} }
std::shared_ptr<type::KEvent> NvHostCtrl::QueryEvent(u32 eventId) { std::shared_ptr<type::KEvent> NvHostCtrl::QueryEvent(u32 eventId) {
EventValue eventValue{.val = eventId}; SyncpointEventValue eventValue{.val = eventId};
const auto &event{events.at(eventValue.nonAsync ? eventValue.eventSlotNonAsync : eventValue.eventSlotAsync)}; 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)) if (event && event->fence.id == (eventValue.nonAsync ? eventValue.syncpointIdNonAsync : eventValue.syncpointIdAsync))
return event->event; return event->event;

View File

@ -13,9 +13,9 @@ namespace skyline {
namespace service::nvdrv::device { 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: private:
u64 waiterId{}; u64 waiterId{};
@ -31,61 +31,40 @@ namespace skyline {
Cancelled = 5, Cancelled = 5,
}; };
NvHostEvent(const DeviceState &state); SyncpointEvent(const DeviceState &state);
std::recursive_mutex mutex; //!< Protects access to the entire event
State state{State::Available}; 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<type::KEvent> event{}; //!< Returned by 'QueryEvent' std::shared_ptr<type::KEvent> 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<gpu::GPU> &gpuState); void Cancel(const std::shared_ptr<gpu::GPU> &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<gpu::GPU> &gpuState, const Fence &fence); void Wait(const std::shared_ptr<gpu::GPU> &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 * @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl
*/ */
class NvHostCtrl : public NvDevice { class NvHostCtrl : public NvDevice {
private: private:
/** std::mutex syncpointEventMutex;
* @brief Metadata about an event, it's used by QueryEvent and EventWait std::array<std::shared_ptr<SyncpointEvent>, constant::NvHostEventCount> syncpointEvents{};
*/
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<std::optional<NvHostEvent>, constant::NvHostEventCount> events{};
/** /**
* @brief Finds a free event for the given syncpoint id * @brief Finds a free syncpoint event for the given id
* @return The index of the event in the event map * @return The index of the syncpoint event in the map
*/ */
u32 FindFreeEvent(u32 syncpointId); u32 FindFreeSyncpointEvent(u32 syncpointId);
NvStatus EventWaitImpl(span<u8> buffer, bool async); NvStatus SyncpointEventWaitImpl(span<u8> buffer, bool async);
public: public:
NvHostCtrl(const DeviceState &state); NvHostCtrl(const DeviceState &state);
@ -97,37 +76,37 @@ namespace skyline {
NvStatus GetConfig(IoctlType type, span<u8> buffer, span<u8> inlineBuffer); NvStatus GetConfig(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/** /**
* @brief Signals an NvHost event * @brief Clears a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_SIGNAL * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_CLEAR_EVENT_WAIT
*/ */
NvStatus EventSignal(IoctlType type, span<u8> buffer, span<u8> inlineBuffer); NvStatus SyncpointClearEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/** /**
* @brief Synchronously waits on an NvHost event * @brief Synchronously waits on a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_WAIT * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT
*/ */
NvStatus EventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer); NvStatus SyncpointEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/** /**
* @brief Asynchronously waits on an NvHost event * @brief Asynchronously waits on a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_WAIT_ASYNC * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT_ASYNC
*/ */
NvStatus EventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer); NvStatus SyncpointEventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/** /**
* @brief Registers an NvHost event * @brief Registers a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_REGISTER * @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_REGISTER_EVENT
*/ */
NvStatus EventRegister(IoctlType type, span<u8> buffer, span<u8> inlineBuffer); NvStatus SyncpointRegisterEvent(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId); std::shared_ptr<type::KEvent> QueryEvent(u32 eventId);
NVDEVICE_DECL( NVDEVICE_DECL(
NVFUNC(0x001B, NvHostCtrl, GetConfig), NVFUNC(0x001B, NvHostCtrl, GetConfig),
NVFUNC(0x001C, NvHostCtrl, EventSignal), NVFUNC(0x001C, NvHostCtrl, SyncpointClearEventWait),
NVFUNC(0x001D, NvHostCtrl, EventWait), NVFUNC(0x001D, NvHostCtrl, SyncpointEventWait),
NVFUNC(0x001E, NvHostCtrl, EventWaitAsync), NVFUNC(0x001E, NvHostCtrl, SyncpointEventWaitAsync),
NVFUNC(0x001F, NvHostCtrl, EventRegister) NVFUNC(0x001F, NvHostCtrl, SyncpointRegisterEvent)
) )
}; };
} }