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.
This commit is contained in:
Billy Laws 2021-07-17 17:16:32 +01:00
parent f6a7ccf7eb
commit d159605caf
8 changed files with 440 additions and 383 deletions

View File

@ -3,22 +3,13 @@
#pragma once
#include <services/nvdrv/devices/nvhost_syncpoint.h>
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);
}

View File

@ -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());
}
};

View File

@ -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 <soc.h>
#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
};
}
}

View File

@ -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 <soc/host1x.h>
#include <services/common/fence.h>
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<u32> 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);
};
}

View File

@ -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 <soc.h>
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
#include "ctrl.h"
namespace skyline::service::nvdrv::device::nvhost {
Ctrl::SyncpointEvent::SyncpointEvent(const DeviceState &state) : event(std::make_shared<type::KEvent>(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<SyncpointEvent>(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> fence, In<i32> timeout, InOut<SyncpointEventValue> 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<u32> 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<SyncpointEventValue> 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> fence, In<i32> timeout, InOut<SyncpointEventValue> value) {
return SyncpointWaitEventImpl(fence, timeout, value, true);
}
PosixResult Ctrl::SyncpointWaitEventSingle(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value) {
return SyncpointWaitEventImpl(fence, timeout, value, false);
}
PosixResult Ctrl::SyncpointAllocateEvent(In<u32> 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<SyncpointEvent>(state);
return PosixResult::Success;
}
PosixResult Ctrl::SyncpointFreeEvent(In<u32> slot) {
std::lock_guard lock(syncpointEventMutex);
return SyncpointFreeEventLocked(slot);
}
PosixResult Ctrl::SyncpointFreeEventBatch(In<u64> 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<type::KEvent> Ctrl::QueryEvent(u32 valueRaw) {
SyncpointEventValue value{.val = valueRaw};
// I have no idea why nvidia does this
u16 slot{value.eventAllocated ? static_cast<u16>(value.partialSlot) : value.slot};
if (slot >= SyncpointEventCount)
return nullptr;
u32 syncpointId{value.eventAllocated ? static_cast<u32>(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 <services/nvdrv/devices/deserialisation/macro_def.h>
static constexpr u32 CtrlMagic{0};
IOCTL_HANDLER_FUNC(Ctrl, ({
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x1C),
SyncpointClearEventWait, ARGS(In<SyncpointEventValue>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(CtrlMagic), FUNC(0x1D),
SyncpointWaitEvent, ARGS(In<Fence>, In<i32>, InOut<SyncpointEventValue>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(CtrlMagic), FUNC(0x1E),
SyncpointWaitEventSingle, ARGS(In<Fence>, In<i32>, InOut<SyncpointEventValue>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x1F),
SyncpointAllocateEvent, ARGS(In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x20),
SyncpointFreeEvent, ARGS(In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(CtrlMagic), FUNC(0x21),
SyncpointFreeEventBatch, ARGS(In<u64>))
IOCTL_CASE_RESULT(INOUT, SIZE(0x183), MAGIC(CtrlMagic), FUNC(0x1B),
PosixResult::InvalidArgument) // GetConfig isn't available in production
}))
#include <services/nvdrv/devices/deserialisation/macro_undef.h>
}

View File

@ -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 <services/nvdrv/devices/nvdevice.h>
#include <services/common/fence.h>
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{State::Available};
Fence fence{}; //!< The fence that is associated with this syncpoint event
std::shared_ptr<type::KEvent> 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<std::unique_ptr<SyncpointEvent>, 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> fence, In<i32> timeout, InOut<SyncpointEventValue> 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<u32> 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<SyncpointEventValue> 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> fence, In<i32> timeout, InOut<SyncpointEventValue> 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> fence, In<i32> timeout, InOut<SyncpointEventValue> 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<u32> slot);
/**
* @brief Frees a single syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_UNREGISTER_EVENT
*/
PosixResult SyncpointFreeEvent(In<u32> 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<u64> bitmask);
std::shared_ptr<type::KEvent> QueryEvent(u32 slot) override;
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
};
}

View File

@ -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 <soc.h>
#include <kernel/types/KProcess.h>
#include <services/nvdrv/driver.h>
#include "nvhost_ctrl.h"
namespace skyline::service::nvdrv::device {
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);
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<SyncpointEvent>(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<u8> buffer, bool async) {
struct Data {
Fence fence; // In
u32 timeout; // In
SyncpointEventValue value; // InOut
} &data = buffer.as<Data>();
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<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::BadValue;
}
NvStatus NvHostCtrl::SyncpointClearEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
auto eventSlot{buffer.as<u16>()};
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<u8> buffer, span<u8> inlineBuffer) {
return SyncpointEventWaitImpl(buffer, false);
}
NvStatus NvHostCtrl::SyncpointEventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return SyncpointEventWaitImpl(buffer, true);
}
NvStatus NvHostCtrl::SyncpointRegisterEvent(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
auto eventSlot{buffer.as<u32>()};
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<SyncpointEvent>(state);
return NvStatus::Success;
}
std::shared_ptr<type::KEvent> 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;
}
}

View File

@ -1,113 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/common/fence.h>
#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<type::KEvent> 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<std::shared_ptr<SyncpointEvent>, 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<u8> 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<u8> buffer, span<u8> 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<u8> buffer, span<u8> 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<u8> buffer, span<u8> 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<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Registers a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_REGISTER_EVENT
*/
NvStatus SyncpointRegisterEvent(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
std::shared_ptr<type::KEvent> 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)
)
};
}
}