mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-30 03:54:17 +01:00
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:
parent
a19bf973b1
commit
78356fa789
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
}
|
258
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp
Normal file
258
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp
Normal 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>
|
||||
}
|
142
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h
Normal file
142
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h
Normal 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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user