From 68d5a48df18f65dac0531beb9c224236b40a074c Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sun, 9 Aug 2020 14:36:47 +0100 Subject: [PATCH] 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. --- app/src/main/cpp/skyline/gpu/syncpoint.cpp | 62 ++++++ app/src/main/cpp/skyline/gpu/syncpoint.h | 56 +++++ .../skyline/services/nvdrv/INvDrvServices.cpp | 16 +- .../skyline/services/nvdrv/devices/nvdevice.h | 7 + .../services/nvdrv/devices/nvhost_channel.cpp | 46 ++-- .../services/nvdrv/devices/nvhost_channel.h | 11 +- .../services/nvdrv/devices/nvhost_ctrl.cpp | 201 +++++++++++++++++- .../services/nvdrv/devices/nvhost_ctrl.h | 119 ++++++++++- .../nvdrv/devices/nvhost_syncpoint.cpp | 32 ++- .../services/nvdrv/devices/nvhost_syncpoint.h | 122 ++++++----- .../skyline/services/nvdrv/devices/nvmap.cpp | 4 +- .../nvdrv/{devices/nvfence.h => fence.h} | 12 +- 12 files changed, 582 insertions(+), 106 deletions(-) create mode 100644 app/src/main/cpp/skyline/gpu/syncpoint.cpp create mode 100644 app/src/main/cpp/skyline/gpu/syncpoint.h rename app/src/main/cpp/skyline/services/nvdrv/{devices/nvfence.h => fence.h} (60%) diff --git a/app/src/main/cpp/skyline/gpu/syncpoint.cpp b/app/src/main/cpp/skyline/gpu/syncpoint.cpp new file mode 100644 index 00000000..af892510 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/syncpoint.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include "syncpoint.h" + +namespace skyline::gpu { + u64 Syncpoint::RegisterWaiter(u32 threshold, const std::function &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; }); + } +}; + diff --git a/app/src/main/cpp/skyline/gpu/syncpoint.h b/app/src/main/cpp/skyline/gpu/syncpoint.h new file mode 100644 index 00000000..cb5beaa5 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/syncpoint.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include + +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 callback; + }; + + Mutex waiterLock{}; //!< Locks insertions and deletions of waiters + std::map waiterMap{}; + u64 nextWaiterId{1}; + + public: + std::atomic 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 &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); + }; + } +} diff --git a/app/src/main/cpp/skyline/services/nvdrv/INvDrvServices.cpp b/app/src/main/cpp/skyline/services/nvdrv/INvDrvServices.cpp index 2ffe6d1f..96180353 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/INvDrvServices.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/INvDrvServices.cpp @@ -129,11 +129,19 @@ namespace skyline::service::nvdrv { void INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto fd = request.Pop(); auto eventId = request.Pop(); - auto event = std::make_shared(state); - auto handle = state.process->InsertItem(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(event); + + state.logger->Debug("QueryEvent: FD: {}, Event ID: {}, Handle: {}", fd, eventId, handle); + response.copyHandles.push_back(handle); + + response.Push(device::NvStatus::Success); + } else { + response.Push(device::NvStatus::BadValue); + } } void INvDrvServices::SetAruidByPID(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvdevice.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvdevice.h index e8c38083..819a402a 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvdevice.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvdevice.h @@ -5,6 +5,7 @@ #include #include +#include #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> 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 QueryEvent(u32 eventId) { + return nullptr; + } }; } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp index 1f701cdb..20cadc82 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp @@ -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(state)), smExceptionBreakpointPauseReportEvent(std::make_shared(state)), errorNotifierEvent(std::make_shared(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(buffer.input.at(0).address); + Fence fence; + } args = state.process->GetReference(buffer.output.at(0).address); auto &hostSyncpoint = state.os->serviceManager.GetService(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(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(buffer.input.at(0).address); + + channelFence.UpdateValue(state.os->serviceManager.GetService(Service::nvdrv_INvDrvServices)->hostSyncpoint); args.fence = channelFence; } void NvHostChannel::SetUserData(IoctlData &buffer) {} + + std::shared_ptr NvHostChannel::QueryEvent(u32 eventId) { + switch (eventId) { + case 1: + return smExceptionBreakpointIntReportEvent; + case 2: + return smExceptionBreakpointPauseReportEvent; + case 3: + return errorNotifierEvent; + default: + return nullptr; + } + } + } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.h index 1b873b7d..baffac09 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.h @@ -3,7 +3,7 @@ #pragma once -#include "nvfence.h" +#include #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 smExceptionBreakpointIntReportEvent; + std::shared_ptr smExceptionBreakpointPauseReportEvent; + std::shared_ptr 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 QueryEvent(u32 eventId); }; } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp index 3a3481c9..12b624c9 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.cpp @@ -1,12 +1,211 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include +#include +#include #include "nvhost_ctrl.h" namespace skyline::service::nvdrv::device { + NvHostEvent::NvHostEvent(const DeviceState &state) : event(std::make_shared(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 &gpuState) { + gpuState->syncpoints.at(fence.id).DeregisterWaiter(waiterId); + Signal(); + event->ResetSignal(); + } + + void NvHostEvent::Wait(const std::shared_ptr &gpuState, const Fence &fence) { + waiterId = gpuState->syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); }); + + // 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(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(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(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 NvHostCtrl::QueryEvent(u32 eventId) { + auto eventValue = reinterpret_cast(&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>(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(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(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; + } + } } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h index 22b77b49..0804b696 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_ctrl.h @@ -3,19 +3,118 @@ #pragma once +#include #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 event{}; //!< Returned by 'QueryEvent' + + /** + * @brief Stops any wait requests on an event and immediately signals it + */ + void Cancel(const std::shared_ptr &gpuState); + + /** + * @brief Asynchronously waits on an event using the given fence + */ + void Wait(const std::shared_ptr &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, 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 QueryEvent(u32 eventId); + }; + } } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp index a5be5c3e..40884884 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include #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(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; + } } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h index 43426056..6ba3a7d9 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_syncpoint.h @@ -3,66 +3,72 @@ #pragma once -#include #include +#include +#include -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 counterMin; - std::atomic counterMax; - bool clientManaged; - bool assigned; - }; - - const DeviceState &state; - std::array 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 counterMin; + std::atomic counterMax; + bool clientManaged; + bool reserved; }; - } + + const DeviceState &state; + std::array 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); + }; } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp index a04400fd..c8723722 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvmap.cpp @@ -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(buffer.input[0].address); const auto &object = handleTable.at(data.handle); diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvfence.h b/app/src/main/cpp/skyline/services/nvdrv/fence.h similarity index 60% rename from app/src/main/cpp/skyline/services/nvdrv/devices/nvfence.h rename to app/src/main/cpp/skyline/services/nvdrv/fence.h index e421ac14..d436a552 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvfence.h +++ b/app/src/main/cpp/skyline/services/nvdrv/fence.h @@ -4,22 +4,22 @@ #pragma once #include -#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); }