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:
Billy Laws 2020-08-09 14:36:47 +01:00 committed by ◱ PixelyIon
parent ed3ff862f6
commit 68d5a48df1
12 changed files with 582 additions and 106 deletions

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

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

View File

@ -129,11 +129,19 @@ namespace skyline::service::nvdrv {
void INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd = request.Pop<u32>(); auto fd = request.Pop<u32>();
auto eventId = request.Pop<u32>(); auto eventId = request.Pop<u32>();
auto event = std::make_shared<type::KEvent>(state); auto device = fdMap.at(fd);
auto handle = state.process->InsertItem<type::KEvent>(event); auto event = device->QueryEvent(eventId);
state.logger->Debug("QueryEvent: FD: {}, Event ID: {}, Handle: {}", fd, eventId, handle); if (event != nullptr) {
response.copyHandles.push_back(handle); 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) { void INvDrvServices::SetAruidByPID(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {

View File

@ -5,6 +5,7 @@
#include <common.h> #include <common.h>
#include <kernel/ipc.h> #include <kernel/ipc.h>
#include <kernel/types/KEvent.h>
#define NFUNC(function) std::bind(&function, this, std::placeholders::_1) #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) {} 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 * @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 * @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()); throw exception("{} (Device: {})", e.what(), getName());
} }
} }
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
return nullptr;
}
}; };
} }

View File

@ -9,10 +9,10 @@
#include "nvhost_channel.h" #include "nvhost_channel.h"
namespace skyline::service::nvdrv::device { 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)}, {0x4801, NFUNC(NvHostChannel::SetNvmapFd)},
{0x4803, NFUNC(NvHostChannel::SetSubmitTimeout)}, {0x4803, NFUNC(NvHostChannel::SetSubmitTimeout)},
{0x4808, NFUNC(NvHostChannel::SubmitGpFifo)}, {0x4808, NFUNC(NvHostChannel::SubmitGpfifo)},
{0x4809, NFUNC(NvHostChannel::AllocObjCtx)}, {0x4809, NFUNC(NvHostChannel::AllocObjCtx)},
{0x480B, NFUNC(NvHostChannel::ZcullBind)}, {0x480B, NFUNC(NvHostChannel::ZcullBind)},
{0x480C, NFUNC(NvHostChannel::SetErrorNotifier)}, {0x480C, NFUNC(NvHostChannel::SetErrorNotifier)},
@ -30,12 +30,12 @@ namespace skyline::service::nvdrv::device {
void NvHostChannel::SetSubmitTimeout(IoctlData &buffer) {} void NvHostChannel::SetSubmitTimeout(IoctlData &buffer) {}
void NvHostChannel::SubmitGpFifo(IoctlData &buffer) { void NvHostChannel::SubmitGpfifo(IoctlData &buffer) {
struct Data { struct Data {
u64 address; u64 address;
u32 numEntries; u32 numEntries;
union { union {
struct { struct __attribute__((__packed__)) {
bool fenceWait : 1; bool fenceWait : 1;
bool fenceIncrement : 1; bool fenceIncrement : 1;
bool hwFormat : 1; bool hwFormat : 1;
@ -46,8 +46,8 @@ namespace skyline::service::nvdrv::device {
}; };
u32 raw; u32 raw;
} flags; } flags;
NvFence fence; Fence fence;
} args = state.process->GetReference<Data>(buffer.input.at(0).address); } args = state.process->GetReference<Data>(buffer.output.at(0).address);
auto &hostSyncpoint = state.os->serviceManager.GetService<nvdrv::INvDrvServices>(Service::nvdrv_INvDrvServices)->hostSyncpoint; auto &hostSyncpoint = state.os->serviceManager.GetService<nvdrv::INvDrvServices>(Service::nvdrv_INvDrvServices)->hostSyncpoint;
@ -57,21 +57,19 @@ namespace skyline::service::nvdrv::device {
return; return;
} }
if (hostSyncpoint.HasSyncpointExpired(args.fence.id, args.fence.value)) { if (hostSyncpoint.HasSyncpointExpired(args.fence.id, args.fence.value))
state.logger->Warn("GPU Syncpoints are not currently supported!"); 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)); 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; args.fence.id = channelFence.id;
if (args.flags.fenceIncrement) { u32 increment = (args.flags.fenceIncrement ? 2 : 0) + (args.flags.incrementWithValue ? args.fence.value : 0);
state.logger->Warn("GPU Syncpoints are not currently supported!"); 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; args.flags.raw = 0;
} }
@ -103,11 +101,27 @@ namespace skyline::service::nvdrv::device {
u32 numEntries; u32 numEntries;
u32 numJobs; u32 numJobs;
u32 flags; u32 flags;
NvFence fence; Fence fence;
u32 reserved[3]; u32 reserved[3];
} args = state.process->GetReference<Data>(buffer.input.at(0).address); } 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; args.fence = channelFence;
} }
void NvHostChannel::SetUserData(IoctlData &buffer) {} 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;
}
}
} }

View File

@ -3,7 +3,7 @@
#pragma once #pragma once
#include "nvfence.h" #include <services/nvdrv/fence.h>
#include "nvdevice.h" #include "nvdevice.h"
namespace skyline::service::nvdrv::device { namespace skyline::service::nvdrv::device {
@ -18,8 +18,11 @@ namespace skyline::service::nvdrv::device {
High = 0x94 High = 0x94
}; };
NvFence channelFence{}; Fence channelFence{};
u32 timeslice{}; u32 timeslice{};
std::shared_ptr<type::KEvent> smExceptionBreakpointIntReportEvent;
std::shared_ptr<type::KEvent> smExceptionBreakpointPauseReportEvent;
std::shared_ptr<type::KEvent> errorNotifierEvent;
public: public:
NvHostChannel(const DeviceState &state, NvDeviceType type); 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) * @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) * @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) * @brief This sets the user specific data (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA)
*/ */
void SetUserData(IoctlData &buffer); void SetUserData(IoctlData &buffer);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId);
}; };
} }

View File

@ -1,12 +1,211 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // 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" #include "nvhost_ctrl.h"
namespace skyline::service::nvdrv::device { 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, { 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)}, {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;
}
}
} }

View File

@ -3,19 +3,118 @@
#pragma once #pragma once
#include <services/nvdrv/fence.h>
#include "nvdevice.h" #include "nvdevice.h"
namespace skyline::service::nvdrv::device { namespace skyline {
/** namespace constant {
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl) constexpr u32 NvHostEventCount = 64; //!< The maximum number of nvhost events
*/ }
class NvHostCtrl : public NvDevice {
public: namespace service::nvdrv::device {
NvHostCtrl(const DeviceState &state); 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);
};
}
} }

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include "nvhost_syncpoint.h" #include "nvhost_syncpoint.h"
namespace skyline::service::nvdrv { namespace skyline::service::nvdrv {
@ -14,13 +16,10 @@ namespace skyline::service::nvdrv {
} }
u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) { u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) {
if (id >= constant::MaxHwSyncpointCount) if (syncpoints.at(id).reserved)
throw exception("Requested syncpoint ID is too high");
if (syncpoints.at(id).assigned)
throw exception("Requested syncpoint is in use"); throw exception("Requested syncpoint is in use");
syncpoints.at(id).assigned = true; syncpoints.at(id).reserved = true;
syncpoints.at(id).clientManaged = clientManaged; syncpoints.at(id).clientManaged = clientManaged;
return id; return id;
@ -28,7 +27,7 @@ namespace skyline::service::nvdrv {
u32 NvHostSyncpoint::FindFreeSyncpoint() { u32 NvHostSyncpoint::FindFreeSyncpoint() {
for (u32 i = 0; i < constant::MaxHwSyncpointCount; i++) for (u32 i = 0; i < constant::MaxHwSyncpointCount; i++)
if (!syncpoints[i].assigned) if (!syncpoints[i].reserved)
return i; return i;
throw exception("Failed to find a free syncpoint!"); throw exception("Failed to find a free syncpoint!");
@ -42,6 +41,9 @@ namespace skyline::service::nvdrv {
bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) { bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) {
const SyncpointInfo &syncpoint = syncpoints.at(id); const SyncpointInfo &syncpoint = syncpoints.at(id);
if (!syncpoint.reserved)
throw exception("Cannot check the expiry status of an unreserved syncpoint!");
if (syncpoint.clientManaged) if (syncpoint.clientManaged)
return static_cast<i32>(syncpoint.counterMin - threshold) >= 0; return static_cast<i32>(syncpoint.counterMin - threshold) >= 0;
else else
@ -49,6 +51,24 @@ namespace skyline::service::nvdrv {
} }
u32 NvHostSyncpoint::IncrementSyncpointMaxExt(u32 id, u32 amount) { 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; 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;
}
} }

View File

@ -3,66 +3,72 @@
#pragma once #pragma once
#include <common.h>
#include <array> #include <array>
#include <common.h>
#include <gpu/syncpoint.h>
namespace skyline { namespace skyline::service::nvdrv {
namespace constant { /**
constexpr size_t MaxHwSyncpointCount = 192; * @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
namespace service::nvdrv { * @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 This holds information about a single syncpoint
* @brief NvHostSyncpoint handles allocating and accessing host1x syncpoints */
* @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html struct SyncpointInfo {
* @url https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c std::atomic<u32> counterMin;
*/ std::atomic<u32> counterMax;
class NvHostSyncpoint { bool clientManaged;
private: bool reserved;
/**
* @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);
}; };
}
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);
};
} }

View File

@ -78,9 +78,9 @@ namespace skyline::service::nvdrv::device {
struct Data { struct Data {
u32 handle; // In u32 handle; // In
u32 _pad0_; u32 _pad0_;
u32 address; // Out u64 address; // Out
u32 size; // Out u32 size; // Out
u64 flags; // Out u32 flags; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address); } data = state.process->GetObject<Data>(buffer.input[0].address);
const auto &object = handleTable.at(data.handle); const auto &object = handleTable.at(data.handle);

View File

@ -4,22 +4,22 @@
#pragma once #pragma once
#include <common.h> #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 * @brief This holds information about a fence
*/ */
struct NvFence { struct Fence {
u32 id{}; u32 id{};
u32 value{}; u32 value{};
/** /**
* @brief Synchronizes the fence's value with its underlying syncpoint * @brief Synchronizes the fence's value with its underlying syncpoint
*/ */
static inline void UpdateValue(const NvHostSyncpoint &hostSyncpoint) { inline void UpdateValue(NvHostSyncpoint &hostSyncpoint) {
//TODO: Implement this value = hostSyncpoint.UpdateMin(id);
} }
}; };
static_assert(sizeof(NvFence) == 0x8); static_assert(sizeof(Fence) == 0x8);
} }