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