mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 08:31:48 +01:00
Implement syncpoints and nvhost events and fix an nvmap bug
These are used to allow the CPU to synchronise with the GPU as it reaches specific points in its command stream. Also fixes an nvmap bug where a struct was incorrect.
This commit is contained in:
parent
ed3ff862f6
commit
68d5a48df1
62
app/src/main/cpp/skyline/gpu/syncpoint.cpp
Normal file
62
app/src/main/cpp/skyline/gpu/syncpoint.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <common.h>
|
||||
#include "syncpoint.h"
|
||||
|
||||
namespace skyline::gpu {
|
||||
u64 Syncpoint::RegisterWaiter(u32 threshold, const std::function<void()> &callback) {
|
||||
if (value >= threshold) {
|
||||
callback();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard guard(waiterLock);
|
||||
waiterMap.insert({nextWaiterId, Waiter{threshold, callback}});
|
||||
|
||||
return nextWaiterId++;
|
||||
}
|
||||
|
||||
void Syncpoint::DeregisterWaiter(u64 id) {
|
||||
std::lock_guard guard(waiterLock);
|
||||
waiterMap.erase(id);
|
||||
}
|
||||
|
||||
u32 Syncpoint::Increment() {
|
||||
value++;
|
||||
|
||||
std::lock_guard guard(waiterLock);
|
||||
std::erase_if(waiterMap, [this](const auto &entry) {
|
||||
if (value >= entry.second.threshold) {
|
||||
entry.second.callback();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool Syncpoint::Wait(u32 threshold, std::chrono::steady_clock::duration timeout) {
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool flag{};
|
||||
|
||||
if (timeout == timeout.max())
|
||||
timeout = std::chrono::seconds(1);
|
||||
|
||||
if (!RegisterWaiter(threshold, [&cv, &mtx, &flag] {
|
||||
std::unique_lock lock(mtx);
|
||||
flag = true;
|
||||
lock.unlock();
|
||||
cv.notify_all();
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_lock lock(mtx);
|
||||
return cv.wait_for(lock, timeout, [&flag] { return flag; });
|
||||
}
|
||||
};
|
||||
|
56
app/src/main/cpp/skyline/gpu/syncpoint.h
Normal file
56
app/src/main/cpp/skyline/gpu/syncpoint.h
Normal file
@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr size_t MaxHwSyncpointCount = 192;
|
||||
}
|
||||
|
||||
namespace gpu {
|
||||
class Syncpoint {
|
||||
private:
|
||||
/**
|
||||
* @brief This holds information about a single waiter on a syncpoint
|
||||
*/
|
||||
struct Waiter {
|
||||
u32 threshold;
|
||||
std::function<void()> callback;
|
||||
};
|
||||
|
||||
Mutex waiterLock{}; //!< Locks insertions and deletions of waiters
|
||||
std::map<u64, Waiter> waiterMap{};
|
||||
u64 nextWaiterId{1};
|
||||
|
||||
public:
|
||||
std::atomic<u32> value{};
|
||||
|
||||
/**
|
||||
* @brief Registers a new waiter with a callback that will be called when the syncpoint reaches the target threshold
|
||||
* @note The callback will be called immediately if the syncpoint has already reached the given threshold
|
||||
* @return A persistent identifier that can be used to refer to the waiter, or 0 if the threshold has already been reached
|
||||
*/
|
||||
u64 RegisterWaiter(u32 threshold, const std::function<void()> &callback);
|
||||
|
||||
/**
|
||||
* @brief Removes a waiter given by 'id' from the pending waiter map
|
||||
*/
|
||||
void DeregisterWaiter(u64 id);
|
||||
|
||||
/**
|
||||
* @brief Increments the syncpoint by 1
|
||||
* @return The new value of the syncpoint
|
||||
*/
|
||||
u32 Increment();
|
||||
|
||||
/**
|
||||
* @brief Waits for the syncpoint to reach given threshold
|
||||
* @return false if the timeout was reached, otherwise true
|
||||
*/
|
||||
bool Wait(u32 threshold, std::chrono::steady_clock::duration timeout);
|
||||
};
|
||||
}
|
||||
}
|
@ -129,11 +129,19 @@ namespace skyline::service::nvdrv {
|
||||
void INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto fd = request.Pop<u32>();
|
||||
auto eventId = request.Pop<u32>();
|
||||
auto event = std::make_shared<type::KEvent>(state);
|
||||
auto handle = state.process->InsertItem<type::KEvent>(event);
|
||||
auto device = fdMap.at(fd);
|
||||
auto event = device->QueryEvent(eventId);
|
||||
|
||||
state.logger->Debug("QueryEvent: FD: {}, Event ID: {}, Handle: {}", fd, eventId, handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
if (event != nullptr) {
|
||||
auto handle = state.process->InsertItem<type::KEvent>(event);
|
||||
|
||||
state.logger->Debug("QueryEvent: FD: {}, Event ID: {}, Handle: {}", fd, eventId, handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
|
||||
response.Push<u32>(device::NvStatus::Success);
|
||||
} else {
|
||||
response.Push<u32>(device::NvStatus::BadValue);
|
||||
}
|
||||
}
|
||||
|
||||
void INvDrvServices::SetAruidByPID(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include <common.h>
|
||||
#include <kernel/ipc.h>
|
||||
#include <kernel/types/KEvent.h>
|
||||
|
||||
#define NFUNC(function) std::bind(&function, this, std::placeholders::_1)
|
||||
|
||||
@ -149,6 +150,8 @@ namespace skyline::service::nvdrv::device {
|
||||
*/
|
||||
NvDevice(const DeviceState &state, NvDeviceType deviceType, std::unordered_map<u32, std::function<void(IoctlData &)>> vTable) : state(state), deviceType(deviceType), vTable(vTable) {}
|
||||
|
||||
virtual ~NvDevice() = default;
|
||||
|
||||
/**
|
||||
* @brief This returns the name of the current service
|
||||
* @note It may not return the exact name the service was initialized with if there are multiple entries in ServiceString
|
||||
@ -182,5 +185,9 @@ namespace skyline::service::nvdrv::device {
|
||||
throw exception("{} (Device: {})", e.what(), getName());
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -9,10 +9,10 @@
|
||||
#include "nvhost_channel.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
NvHostChannel::NvHostChannel(const DeviceState &state, NvDeviceType type) : NvDevice(state, type, {
|
||||
NvHostChannel::NvHostChannel(const DeviceState &state, NvDeviceType type) : smExceptionBreakpointIntReportEvent(std::make_shared<type::KEvent>(state)), smExceptionBreakpointPauseReportEvent(std::make_shared<type::KEvent>(state)), errorNotifierEvent(std::make_shared<type::KEvent>(state)), NvDevice(state, type, {
|
||||
{0x4801, NFUNC(NvHostChannel::SetNvmapFd)},
|
||||
{0x4803, NFUNC(NvHostChannel::SetSubmitTimeout)},
|
||||
{0x4808, NFUNC(NvHostChannel::SubmitGpFifo)},
|
||||
{0x4808, NFUNC(NvHostChannel::SubmitGpfifo)},
|
||||
{0x4809, NFUNC(NvHostChannel::AllocObjCtx)},
|
||||
{0x480B, NFUNC(NvHostChannel::ZcullBind)},
|
||||
{0x480C, NFUNC(NvHostChannel::SetErrorNotifier)},
|
||||
@ -30,12 +30,12 @@ namespace skyline::service::nvdrv::device {
|
||||
|
||||
void NvHostChannel::SetSubmitTimeout(IoctlData &buffer) {}
|
||||
|
||||
void NvHostChannel::SubmitGpFifo(IoctlData &buffer) {
|
||||
void NvHostChannel::SubmitGpfifo(IoctlData &buffer) {
|
||||
struct Data {
|
||||
u64 address;
|
||||
u32 numEntries;
|
||||
union {
|
||||
struct {
|
||||
struct __attribute__((__packed__)) {
|
||||
bool fenceWait : 1;
|
||||
bool fenceIncrement : 1;
|
||||
bool hwFormat : 1;
|
||||
@ -46,8 +46,8 @@ namespace skyline::service::nvdrv::device {
|
||||
};
|
||||
u32 raw;
|
||||
} flags;
|
||||
NvFence fence;
|
||||
} args = state.process->GetReference<Data>(buffer.input.at(0).address);
|
||||
Fence fence;
|
||||
} args = state.process->GetReference<Data>(buffer.output.at(0).address);
|
||||
|
||||
auto &hostSyncpoint = state.os->serviceManager.GetService<nvdrv::INvDrvServices>(Service::nvdrv_INvDrvServices)->hostSyncpoint;
|
||||
|
||||
@ -57,21 +57,19 @@ namespace skyline::service::nvdrv::device {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hostSyncpoint.HasSyncpointExpired(args.fence.id, args.fence.value)) {
|
||||
state.logger->Warn("GPU Syncpoints are not currently supported!");
|
||||
}
|
||||
if (hostSyncpoint.HasSyncpointExpired(args.fence.id, args.fence.value))
|
||||
throw exception("Waiting on a fence through SubmitGpfifo is unimplemented");
|
||||
}
|
||||
|
||||
state.gpu->gpfifo.Push(std::span(state.process->GetPointer<gpu::gpfifo::GpEntry>(args.address), args.numEntries));
|
||||
|
||||
bool increment = args.flags.fenceIncrement || args.flags.incrementWithValue;
|
||||
u32 amount = increment ? (args.flags.fenceIncrement ? 2 : 0) + (args.flags.incrementWithValue ? args.fence.value : 0) : 0;
|
||||
args.fence.value = hostSyncpoint.IncrementSyncpointMaxExt(args.fence.id, amount);
|
||||
args.fence.id = channelFence.id;
|
||||
|
||||
if (args.flags.fenceIncrement) {
|
||||
state.logger->Warn("GPU Syncpoints are not currently supported!");
|
||||
}
|
||||
u32 increment = (args.flags.fenceIncrement ? 2 : 0) + (args.flags.incrementWithValue ? args.fence.value : 0);
|
||||
args.fence.value = hostSyncpoint.IncrementSyncpointMaxExt(args.fence.id, increment);
|
||||
|
||||
if (args.flags.fenceIncrement)
|
||||
throw exception("Incrementing a fence through SubmitGpfifo is unimplemented");
|
||||
|
||||
args.flags.raw = 0;
|
||||
}
|
||||
@ -103,11 +101,27 @@ namespace skyline::service::nvdrv::device {
|
||||
u32 numEntries;
|
||||
u32 numJobs;
|
||||
u32 flags;
|
||||
NvFence fence;
|
||||
Fence fence;
|
||||
u32 reserved[3];
|
||||
} args = state.process->GetReference<Data>(buffer.input.at(0).address);
|
||||
|
||||
channelFence.UpdateValue(state.os->serviceManager.GetService<nvdrv::INvDrvServices>(Service::nvdrv_INvDrvServices)->hostSyncpoint);
|
||||
args.fence = channelFence;
|
||||
}
|
||||
|
||||
void NvHostChannel::SetUserData(IoctlData &buffer) {}
|
||||
|
||||
std::shared_ptr<type::KEvent> NvHostChannel::QueryEvent(u32 eventId) {
|
||||
switch (eventId) {
|
||||
case 1:
|
||||
return smExceptionBreakpointIntReportEvent;
|
||||
case 2:
|
||||
return smExceptionBreakpointPauseReportEvent;
|
||||
case 3:
|
||||
return errorNotifierEvent;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nvfence.h"
|
||||
#include <services/nvdrv/fence.h>
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
@ -18,8 +18,11 @@ namespace skyline::service::nvdrv::device {
|
||||
High = 0x94
|
||||
};
|
||||
|
||||
NvFence channelFence{};
|
||||
Fence channelFence{};
|
||||
u32 timeslice{};
|
||||
std::shared_ptr<type::KEvent> smExceptionBreakpointIntReportEvent;
|
||||
std::shared_ptr<type::KEvent> smExceptionBreakpointPauseReportEvent;
|
||||
std::shared_ptr<type::KEvent> errorNotifierEvent;
|
||||
|
||||
public:
|
||||
NvHostChannel(const DeviceState &state, NvDeviceType type);
|
||||
@ -37,7 +40,7 @@ namespace skyline::service::nvdrv::device {
|
||||
/**
|
||||
* @brief This submits a command to the GPFIFO (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO)
|
||||
*/
|
||||
void SubmitGpFifo(IoctlData &buffer);
|
||||
void SubmitGpfifo(IoctlData &buffer);
|
||||
|
||||
/**
|
||||
* @brief This allocates a graphic context object (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_OBJ_CTX)
|
||||
@ -68,5 +71,7 @@ namespace skyline::service::nvdrv::device {
|
||||
* @brief This sets the user specific data (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA)
|
||||
*/
|
||||
void SetUserData(IoctlData &buffer);
|
||||
|
||||
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId);
|
||||
};
|
||||
}
|
||||
|
@ -1,12 +1,211 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <os.h>
|
||||
#include <gpu.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <services/nvdrv/INvDrvServices.h>
|
||||
#include "nvhost_ctrl.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
NvHostEvent::NvHostEvent(const DeviceState &state) : event(std::make_shared<type::KEvent>(state)) {}
|
||||
|
||||
void NvHostEvent::Signal() {
|
||||
auto oldState = state;
|
||||
state = State::Signaling;
|
||||
|
||||
// This is to ensure that the HOS event isn't signalled when the nvhost event is cancelled
|
||||
if (oldState == State::Waiting)
|
||||
event->Signal();
|
||||
|
||||
state = State::Signaled;
|
||||
}
|
||||
|
||||
void NvHostEvent::Cancel(const std::shared_ptr<gpu::GPU> &gpuState) {
|
||||
gpuState->syncpoints.at(fence.id).DeregisterWaiter(waiterId);
|
||||
Signal();
|
||||
event->ResetSignal();
|
||||
}
|
||||
|
||||
void NvHostEvent::Wait(const std::shared_ptr<gpu::GPU> &gpuState, const Fence &fence) {
|
||||
waiterId = gpuState->syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); });
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl, {
|
||||
{0x001B, NFUNC(NvHostCtrl::GetConfig)},
|
||||
{0x001C, NFUNC(NvHostCtrl::EventSignal)},
|
||||
{0x001D, NFUNC(NvHostCtrl::EventWait)},
|
||||
{0x001E, NFUNC(NvHostCtrl::EventWaitAsync)},
|
||||
{0x001F, NFUNC(NvHostCtrl::EventRegister)},
|
||||
}) {}
|
||||
|
||||
void NvHostCtrl::EventRegister(IoctlData &buffer) {}
|
||||
void NvHostCtrl::GetConfig(IoctlData &buffer) {
|
||||
buffer.status = NvStatus::BadValue;
|
||||
}
|
||||
|
||||
void NvHostCtrl::EventSignal(IoctlData &buffer) {
|
||||
struct Data {
|
||||
u16 _pad_;
|
||||
u16 userEventId;
|
||||
};
|
||||
auto userEventId = state.process->GetObject<Data>(buffer.input.at(0).address).userEventId;
|
||||
state.logger->Debug("Signalling nvhost event: {}", userEventId);
|
||||
|
||||
if (userEventId >= constant::NvHostEventCount || !events.at(userEventId)) {
|
||||
buffer.status = NvStatus::BadValue;
|
||||
return;
|
||||
}
|
||||
|
||||
auto &event = *events.at(userEventId);
|
||||
|
||||
if (event.state == NvHostEvent::State::Waiting) {
|
||||
event.state = NvHostEvent::State::Cancelling;
|
||||
state.logger->Debug("Cancelling waiting nvhost event: {}", userEventId);
|
||||
event.Cancel(state.gpu);
|
||||
}
|
||||
|
||||
event.state = NvHostEvent::State::Cancelled;
|
||||
|
||||
auto &hostSyncpoint = state.os->serviceManager.GetService<nvdrv::INvDrvServices>(Service::nvdrv_INvDrvServices)->hostSyncpoint;
|
||||
hostSyncpoint.UpdateMin(event.fence.id);
|
||||
}
|
||||
|
||||
void NvHostCtrl::EventWait(IoctlData &buffer) {
|
||||
EventWaitImpl(buffer, false);
|
||||
}
|
||||
|
||||
void NvHostCtrl::EventWaitAsync(IoctlData &buffer) {
|
||||
EventWaitImpl(buffer, true);
|
||||
}
|
||||
|
||||
void NvHostCtrl::EventRegister(IoctlData &buffer) {
|
||||
auto userEventId = state.process->GetObject<u32>(buffer.input.at(0).address);
|
||||
state.logger->Debug("Registering nvhost event: {}", userEventId);
|
||||
|
||||
if (events.at(userEventId))
|
||||
throw exception("Recreating events is unimplemented");
|
||||
|
||||
events.at(userEventId) = NvHostEvent(state);
|
||||
}
|
||||
|
||||
std::shared_ptr<type::KEvent> NvHostCtrl::QueryEvent(u32 eventId) {
|
||||
auto eventValue = reinterpret_cast<EventValue *>(&eventId);
|
||||
const auto &event = events.at(eventValue->nonAsync ? eventValue->eventSlotNonAsync : eventValue->eventSlotAsync);
|
||||
|
||||
if (event && event->fence.id == (eventValue->nonAsync ? eventValue->syncpointIdNonAsync : eventValue->syncpointIdAsync))
|
||||
return event->event;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
for (u32 i{}; i < constant::NvHostEventCount; i++) {
|
||||
if (events[i]) {
|
||||
const auto &event = *events[i];
|
||||
|
||||
if (event.state == NvHostEvent::State::Cancelled || event.state == NvHostEvent::State::Available || event.state == NvHostEvent::State::Signaled) {
|
||||
eventIndex = i;
|
||||
|
||||
// This event is already attached to the requested syncpoint, so use it
|
||||
if (event.fence.id == syncpointId)
|
||||
return eventIndex;
|
||||
}
|
||||
} else if (freeIndex == constant::NvHostEventCount) {
|
||||
freeIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Use an unused event if possible
|
||||
if (freeIndex < constant::NvHostEventCount) {
|
||||
events.at(freeIndex) = static_cast<const std::optional<NvHostEvent>>(NvHostEvent(state));
|
||||
return freeIndex;
|
||||
}
|
||||
|
||||
// Recycle an existing event if all else fails
|
||||
if (eventIndex < constant::NvHostEventCount)
|
||||
return eventIndex;
|
||||
|
||||
throw exception("Failed to find a free nvhost event!");
|
||||
}
|
||||
|
||||
void NvHostCtrl::EventWaitImpl(IoctlData &buffer, bool async) {
|
||||
struct Data {
|
||||
Fence fence;
|
||||
u32 timeout;
|
||||
EventValue value;
|
||||
} args = state.process->GetReference<Data>(buffer.output.at(0).address);
|
||||
|
||||
if (args.fence.id >= constant::MaxHwSyncpointCount) {
|
||||
buffer.status = NvStatus::BadValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.timeout == 0) {
|
||||
buffer.status = NvStatus::Timeout;
|
||||
return;
|
||||
}
|
||||
|
||||
auto &hostSyncpoint = state.os->serviceManager.GetService<nvdrv::INvDrvServices>(Service::nvdrv_INvDrvServices)->hostSyncpoint;
|
||||
|
||||
// Check if the syncpoint has already expired using the last known values
|
||||
if (hostSyncpoint.HasSyncpointExpired(args.fence.id, args.fence.value)) {
|
||||
args.value.val = hostSyncpoint.ReadSyncpointMinValue(args.fence.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync the syncpoint with the GPU then check again
|
||||
auto minVal = hostSyncpoint.UpdateMin(args.fence.id);
|
||||
if (hostSyncpoint.HasSyncpointExpired(args.fence.id, args.fence.value)) {
|
||||
args.value.val = minVal;
|
||||
return;
|
||||
}
|
||||
|
||||
u32 userEventId{};
|
||||
|
||||
if (async) {
|
||||
if (args.value.val >= constant::NvHostEventCount || !events.at(args.value.val)) {
|
||||
buffer.status = NvStatus::BadValue;
|
||||
return;
|
||||
}
|
||||
|
||||
userEventId = args.value.val;
|
||||
} else {
|
||||
args.fence.value = 0;
|
||||
|
||||
userEventId = FindFreeEvent(args.fence.id);
|
||||
}
|
||||
|
||||
auto event = &*events.at(userEventId);
|
||||
|
||||
if (event->state == NvHostEvent::State::Cancelled || event->state == NvHostEvent::State::Available || event->state == NvHostEvent::State::Signaled) {
|
||||
state.logger->Debug("Now waiting on nvhost event: {} with fence: {}", userEventId, args.fence.id);
|
||||
event->Wait(state.gpu, args.fence);
|
||||
|
||||
args.value.val = 0;
|
||||
|
||||
if (async) {
|
||||
args.value.syncpointIdAsync = args.fence.id;
|
||||
} else {
|
||||
args.value.syncpointIdNonAsync = args.fence.id;
|
||||
args.value.nonAsync = true;
|
||||
}
|
||||
|
||||
args.value.val |= userEventId;
|
||||
|
||||
buffer.status = NvStatus::Timeout;
|
||||
return;
|
||||
} else {
|
||||
buffer.status = NvStatus::BadValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,19 +3,118 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <services/nvdrv/fence.h>
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
/**
|
||||
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl)
|
||||
*/
|
||||
class NvHostCtrl : public NvDevice {
|
||||
public:
|
||||
NvHostCtrl(const DeviceState &state);
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr u32 NvHostEventCount = 64; //!< The maximum number of nvhost events
|
||||
}
|
||||
|
||||
namespace service::nvdrv::device {
|
||||
class NvHostEvent {
|
||||
private:
|
||||
u64 waiterId{};
|
||||
|
||||
void Signal();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief This enumerates the possible states of an event
|
||||
*/
|
||||
enum class State {
|
||||
Available = 0,
|
||||
Waiting = 1,
|
||||
Cancelling = 2,
|
||||
Signaling = 3,
|
||||
Signaled = 4,
|
||||
Cancelled = 5
|
||||
};
|
||||
|
||||
NvHostEvent(const DeviceState &state);
|
||||
|
||||
State state{State::Available};
|
||||
Fence fence{}; //!< The fence that is attached to this event
|
||||
std::shared_ptr<type::KEvent> event{}; //!< Returned by 'QueryEvent'
|
||||
|
||||
/**
|
||||
* @brief Stops any wait requests on an event and immediately signals it
|
||||
*/
|
||||
void Cancel(const std::shared_ptr<gpu::GPU> &gpuState);
|
||||
|
||||
/**
|
||||
* @brief Asynchronously waits on an event using the given fence
|
||||
*/
|
||||
void Wait(const std::shared_ptr<gpu::GPU> &gpuState, const Fence &fence);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This registers a GPU event (https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_REGISTER)
|
||||
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl)
|
||||
*/
|
||||
void EventRegister(IoctlData &buffer);
|
||||
};
|
||||
class NvHostCtrl : public NvDevice {
|
||||
private:
|
||||
/**
|
||||
* @brief This holds metadata about an event, it is used by QueryEvent and EventWait
|
||||
*/
|
||||
union EventValue {
|
||||
struct {
|
||||
u8 _pad0_ : 4;
|
||||
u32 syncpointIdAsync : 28;
|
||||
};
|
||||
|
||||
struct {
|
||||
union {
|
||||
u8 eventSlotAsync;
|
||||
u16 eventSlotNonAsync;
|
||||
};
|
||||
u16 syncpointIdNonAsync : 12;
|
||||
bool nonAsync : 1;
|
||||
u8 _pad12_ : 3;
|
||||
};
|
||||
|
||||
u32 val;
|
||||
};
|
||||
static_assert(sizeof(EventValue) == sizeof(u32));
|
||||
|
||||
std::array<std::optional<NvHostEvent>, constant::NvHostEventCount> events{};
|
||||
|
||||
/**
|
||||
* @brief Finds a free event for the given syncpoint id
|
||||
* @return The index of the event in the event map
|
||||
*/
|
||||
u32 FindFreeEvent(u32 syncpointId);
|
||||
|
||||
void EventWaitImpl(IoctlData &buffer, bool async);
|
||||
|
||||
public:
|
||||
NvHostCtrl(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This gets the value of an nvdrv setting, it returns an error code on production switches (https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_GET_CONFIG)
|
||||
*/
|
||||
void GetConfig(IoctlData &buffer);
|
||||
|
||||
/**
|
||||
* @brief This signals an NvHost event (https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_SIGNAL)
|
||||
*/
|
||||
void EventSignal(IoctlData &buffer);
|
||||
|
||||
/**
|
||||
* @brief This synchronously waits on an NvHost event (https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_WAIT)
|
||||
*/
|
||||
void EventWait(IoctlData &buffer);
|
||||
|
||||
/**
|
||||
* @brief This asynchronously waits on an NvHost event (https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_WAIT_ASYNC)
|
||||
*/
|
||||
void EventWaitAsync(IoctlData &buffer);
|
||||
|
||||
/**
|
||||
* @brief This registers an NvHost event (https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_EVENT_REGISTER)
|
||||
*/
|
||||
void EventRegister(IoctlData &buffer);
|
||||
|
||||
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
|
||||
#include <gpu.h>
|
||||
#include "nvhost_syncpoint.h"
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
@ -14,13 +16,10 @@ namespace skyline::service::nvdrv {
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) {
|
||||
if (id >= constant::MaxHwSyncpointCount)
|
||||
throw exception("Requested syncpoint ID is too high");
|
||||
|
||||
if (syncpoints.at(id).assigned)
|
||||
if (syncpoints.at(id).reserved)
|
||||
throw exception("Requested syncpoint is in use");
|
||||
|
||||
syncpoints.at(id).assigned = true;
|
||||
syncpoints.at(id).reserved = true;
|
||||
syncpoints.at(id).clientManaged = clientManaged;
|
||||
|
||||
return id;
|
||||
@ -28,7 +27,7 @@ namespace skyline::service::nvdrv {
|
||||
|
||||
u32 NvHostSyncpoint::FindFreeSyncpoint() {
|
||||
for (u32 i = 0; i < constant::MaxHwSyncpointCount; i++)
|
||||
if (!syncpoints[i].assigned)
|
||||
if (!syncpoints[i].reserved)
|
||||
return i;
|
||||
|
||||
throw exception("Failed to find a free syncpoint!");
|
||||
@ -42,6 +41,9 @@ namespace skyline::service::nvdrv {
|
||||
bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) {
|
||||
const SyncpointInfo &syncpoint = syncpoints.at(id);
|
||||
|
||||
if (!syncpoint.reserved)
|
||||
throw exception("Cannot check the expiry status of an unreserved syncpoint!");
|
||||
|
||||
if (syncpoint.clientManaged)
|
||||
return static_cast<i32>(syncpoint.counterMin - threshold) >= 0;
|
||||
else
|
||||
@ -49,6 +51,24 @@ namespace skyline::service::nvdrv {
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::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) {
|
||||
if (!syncpoints.at(id).reserved)
|
||||
throw exception("Cannot read an unreserved syncpoint!");
|
||||
|
||||
return syncpoints.at(id).counterMin;
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::UpdateMin(u32 id) {
|
||||
if (!syncpoints.at(id).reserved)
|
||||
throw exception("Cannot update an unreserved syncpoint!");
|
||||
|
||||
syncpoints.at(id).counterMin = state.gpu->syncpoints.at(id).value.load();
|
||||
return syncpoints.at(id).counterMin;
|
||||
}
|
||||
}
|
||||
|
@ -3,66 +3,72 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
#include <array>
|
||||
#include <common.h>
|
||||
#include <gpu/syncpoint.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr size_t MaxHwSyncpointCount = 192;
|
||||
}
|
||||
|
||||
namespace service::nvdrv {
|
||||
namespace skyline::service::nvdrv {
|
||||
/**
|
||||
* @todo Implement the GPU side of this
|
||||
* @brief NvHostSyncpoint handles allocating and accessing host1x syncpoints
|
||||
* @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 {
|
||||
private:
|
||||
/**
|
||||
* @todo Implement the GPU side of this
|
||||
* @brief NvHostSyncpoint handles allocating and accessing host1x syncpoints
|
||||
* @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 {
|
||||
private:
|
||||
/**
|
||||
* @brief This holds information about a single syncpoint
|
||||
*/
|
||||
struct SyncpointInfo {
|
||||
std::atomic<u32> counterMin;
|
||||
std::atomic<u32> counterMax;
|
||||
bool clientManaged;
|
||||
bool assigned;
|
||||
};
|
||||
|
||||
const DeviceState &state;
|
||||
std::array<SyncpointInfo, skyline::constant::MaxHwSyncpointCount> syncpoints{};
|
||||
Mutex reservationLock;
|
||||
|
||||
/**
|
||||
* @note reservationLock should be locked when calling this
|
||||
*/
|
||||
u32 ReserveSyncpoint(u32 id, bool clientManaged);
|
||||
|
||||
/**
|
||||
* @return The ID of the first free syncpoint
|
||||
*/
|
||||
u32 FindFreeSyncpoint();
|
||||
|
||||
public:
|
||||
NvHostSyncpoint(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Finds a free syncpoint and reserves it
|
||||
* @return The ID of the reserved syncpoint
|
||||
*/
|
||||
u32 AllocateSyncpoint(bool clientManaged);
|
||||
|
||||
/**
|
||||
* @url https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
|
||||
*/
|
||||
bool HasSyncpointExpired(u32 id, u32 threshold);
|
||||
|
||||
/**
|
||||
* @brief Atomically increments the maximum value of a syncpoint by the given amount
|
||||
* @return The new value of the syncpoint
|
||||
*/
|
||||
u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
|
||||
* @brief This holds information about a single syncpoint
|
||||
*/
|
||||
struct SyncpointInfo {
|
||||
std::atomic<u32> counterMin;
|
||||
std::atomic<u32> counterMax;
|
||||
bool clientManaged;
|
||||
bool reserved;
|
||||
};
|
||||
}
|
||||
|
||||
const DeviceState &state;
|
||||
std::array<SyncpointInfo, skyline::constant::MaxHwSyncpointCount> syncpoints{};
|
||||
Mutex reservationLock;
|
||||
|
||||
/**
|
||||
* @note reservationLock should be locked when calling this
|
||||
*/
|
||||
u32 ReserveSyncpoint(u32 id, bool clientManaged);
|
||||
|
||||
/**
|
||||
* @return The ID of the first free syncpoint
|
||||
*/
|
||||
u32 FindFreeSyncpoint();
|
||||
|
||||
public:
|
||||
NvHostSyncpoint(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Finds a free syncpoint and reserves it
|
||||
* @return The ID of the reserved syncpoint
|
||||
*/
|
||||
u32 AllocateSyncpoint(bool clientManaged);
|
||||
|
||||
/**
|
||||
* @url https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
|
||||
*/
|
||||
bool HasSyncpointExpired(u32 id, u32 threshold);
|
||||
|
||||
/**
|
||||
* @brief Atomically increments the maximum value of a syncpoint by the given amount
|
||||
* @return The new max value of the syncpoint
|
||||
*/
|
||||
u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
|
||||
|
||||
/**
|
||||
* @return The minimum value of the syncpoint
|
||||
*/
|
||||
u32 ReadSyncpointMinValue(u32 id);
|
||||
|
||||
/**
|
||||
* @brief Synchronises the minimum value of the syncpoint to with the GPU
|
||||
* @return The new minimum value of the syncpoint
|
||||
*/
|
||||
u32 UpdateMin(u32 id);
|
||||
};
|
||||
}
|
||||
|
@ -78,9 +78,9 @@ namespace skyline::service::nvdrv::device {
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
u32 _pad0_;
|
||||
u32 address; // Out
|
||||
u64 address; // Out
|
||||
u32 size; // Out
|
||||
u64 flags; // Out
|
||||
u32 flags; // Out
|
||||
} data = state.process->GetObject<Data>(buffer.input[0].address);
|
||||
|
||||
const auto &object = handleTable.at(data.handle);
|
||||
|
@ -4,22 +4,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
#include "nvhost_syncpoint.h"
|
||||
#include "devices/nvhost_syncpoint.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
namespace skyline::service::nvdrv {
|
||||
/**
|
||||
* @brief This holds information about a fence
|
||||
*/
|
||||
struct NvFence {
|
||||
struct Fence {
|
||||
u32 id{};
|
||||
u32 value{};
|
||||
|
||||
/**
|
||||
* @brief Synchronizes the fence's value with its underlying syncpoint
|
||||
*/
|
||||
static inline void UpdateValue(const NvHostSyncpoint &hostSyncpoint) {
|
||||
//TODO: Implement this
|
||||
inline void UpdateValue(NvHostSyncpoint &hostSyncpoint) {
|
||||
value = hostSyncpoint.UpdateMin(id);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(NvFence) == 0x8);
|
||||
static_assert(sizeof(Fence) == 0x8);
|
||||
}
|
Loading…
Reference in New Issue
Block a user