forked from ShuriZma/suyu
1
0
Fork 0

Merge pull request #2592 from FernandoS27/sync1

Implement GPU Synchronization Mechanisms & Correct NVFlinger
This commit is contained in:
bunnei 2019-07-26 14:26:44 -04:00 committed by GitHub
commit 52f54c728d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 730 additions and 227 deletions

View File

@ -111,6 +111,8 @@ add_library(core STATIC
frontend/scope_acquire_window_context.h
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
hardware_interrupt_manager.cpp
hardware_interrupt_manager.h
hle/ipc.h
hle/ipc_helpers.h
hle/kernel/address_arbiter.cpp
@ -372,6 +374,7 @@ add_library(core STATIC
hle/service/nvdrv/devices/nvmap.h
hle/service/nvdrv/interface.cpp
hle/service/nvdrv/interface.h
hle/service/nvdrv/nvdata.h
hle/service/nvdrv/nvdrv.cpp
hle/service/nvdrv/nvdrv.h
hle/service/nvdrv/nvmemp.cpp

View File

@ -19,6 +19,7 @@
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hardware_interrupt_manager.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@ -151,7 +152,7 @@ struct System::Impl {
if (!renderer->Init()) {
return ResultStatus::ErrorVideoCore;
}
interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system);
gpu_core = VideoCore::CreateGPU(system);
is_powered_on = true;
@ -298,6 +299,7 @@ struct System::Impl {
std::unique_ptr<VideoCore::RendererBase> renderer;
std::unique_ptr<Tegra::GPU> gpu_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager;
CpuCoreManager cpu_core_manager;
bool is_powered_on = false;
@ -444,6 +446,14 @@ const Tegra::GPU& System::GPU() const {
return *impl->gpu_core;
}
Core::Hardware::InterruptManager& System::InterruptManager() {
return *impl->interrupt_manager;
}
const Core::Hardware::InterruptManager& System::InterruptManager() const {
return *impl->interrupt_manager;
}
VideoCore::RendererBase& System::Renderer() {
return *impl->renderer;
}

View File

@ -70,6 +70,10 @@ namespace Core::Timing {
class CoreTiming;
}
namespace Core::Hardware {
class InterruptManager;
}
namespace Core {
class ARM_Interface;
@ -234,6 +238,12 @@ public:
/// Provides a constant reference to the core timing instance.
const Timing::CoreTiming& CoreTiming() const;
/// Provides a reference to the interrupt manager instance.
Core::Hardware::InterruptManager& InterruptManager();
/// Provides a constant reference to the interrupt manager instance.
const Core::Hardware::InterruptManager& InterruptManager() const;
/// Provides a reference to the kernel instance.
Kernel::KernelCore& Kernel();

View File

@ -0,0 +1,30 @@
// Copyright 2019 Yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hardware_interrupt_manager.h"
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/sm/sm.h"
namespace Core::Hardware {
InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
gpu_interrupt_event =
system.CoreTiming().RegisterEvent("GPUInterrupt", [this](u64 message, s64) {
auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
const u32 syncpt = static_cast<u32>(message >> 32);
const u32 value = static_cast<u32>(message);
nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
});
}
InterruptManager::~InterruptManager() = default;
void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
system.CoreTiming().ScheduleEvent(10, gpu_interrupt_event, msg);
}
} // namespace Core::Hardware

View File

@ -0,0 +1,31 @@
// Copyright 2019 Yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Core::Timing {
struct EventType;
}
namespace Core::Hardware {
class InterruptManager {
public:
explicit InterruptManager(Core::System& system);
~InterruptManager();
void GPUInterruptSyncpt(u32 syncpoint_id, u32 value);
private:
Core::System& system;
Core::Timing::EventType* gpu_interrupt_event{};
};
} // namespace Core::Hardware

View File

@ -8,6 +8,11 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/nvdata.h"
namespace Core {
class System;
}
namespace Service::Nvidia::Devices {
@ -15,7 +20,7 @@ namespace Service::Nvidia::Devices {
/// implement the ioctl interface.
class nvdevice {
public:
nvdevice() = default;
explicit nvdevice(Core::System& system) : system{system} {};
virtual ~nvdevice() = default;
union Ioctl {
u32_le raw;
@ -33,7 +38,11 @@ public:
* @param output A buffer where the output data will be written to.
* @returns The result code of the ioctl.
*/
virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0;
virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) = 0;
protected:
Core::System& system;
};
} // namespace Service::Nvidia::Devices

View File

@ -13,10 +13,12 @@
namespace Service::Nvidia::Devices {
nvdisp_disp0::nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0 ::~nvdisp_disp0() = default;
u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
@ -34,9 +36,8 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
addr, offset, width, height, stride, static_cast<PixelFormat>(format),
transform, crop_rect};
auto& instance = Core::System::GetInstance();
instance.GetPerfStats().EndGameFrame();
instance.GPU().SwapBuffers(framebuffer);
system.GetPerfStats().EndGameFrame();
system.GPU().SwapBuffers(framebuffer);
}
} // namespace Service::Nvidia::Devices

View File

@ -17,10 +17,11 @@ class nvmap;
class nvdisp_disp0 final : public nvdevice {
public:
explicit nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev);
explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvdisp_disp0() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,

View File

@ -22,10 +22,12 @@ enum {
};
}
nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@ -65,7 +67,7 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>&
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
auto& gpu = Core::System::GetInstance().GPU();
auto& gpu = system.GPU();
const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
if (params.flags & 1) {
params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1);
@ -85,7 +87,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
auto& gpu = Core::System::GetInstance().GPU();
auto& gpu = system.GPU();
for (const auto& entry : entries) {
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
entry.offset, entry.nvmap_handle, entry.pages);
@ -136,7 +138,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
// case to prevent unexpected behavior.
ASSERT(object->id == params.nvmap_handle);
auto& gpu = Core::System::GetInstance().GPU();
auto& gpu = system.GPU();
if (params.flags & 1) {
params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size);
@ -173,8 +175,7 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
return 0;
}
params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset,
itr->second.size);
params.offset = system.GPU().MemoryManager().UnmapBuffer(params.offset, itr->second.size);
buffer_mappings.erase(itr->second.offset);
std::memcpy(output.data(), &params, output.size());

View File

@ -17,10 +17,11 @@ class nvmap;
class nvhost_as_gpu final : public nvdevice {
public:
explicit nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev);
explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_as_gpu() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {

View File

@ -7,14 +7,20 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "video_core/gpu.h"
namespace Service::Nvidia::Devices {
nvhost_ctrl::nvhost_ctrl() = default;
nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface)
: nvdevice(system), events_interface{events_interface} {}
nvhost_ctrl::~nvhost_ctrl() = default;
u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@ -22,11 +28,15 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<
case IoctlCommand::IocGetConfigCommand:
return NvOsGetConfigU32(input, output);
case IoctlCommand::IocCtrlEventWaitCommand:
return IocCtrlEventWait(input, output, false);
return IocCtrlEventWait(input, output, false, ctrl);
case IoctlCommand::IocCtrlEventWaitAsyncCommand:
return IocCtrlEventWait(input, output, true);
return IocCtrlEventWait(input, output, true, ctrl);
case IoctlCommand::IocCtrlEventRegisterCommand:
return IocCtrlEventRegister(input, output);
case IoctlCommand::IocCtrlEventUnregisterCommand:
return IocCtrlEventUnregister(input, output);
case IoctlCommand::IocCtrlEventSignalCommand:
return IocCtrlEventSignal(input, output);
}
UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
@ -41,23 +51,137 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>&
}
u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
bool is_async) {
bool is_async, IoctlCtrl& ctrl) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
LOG_WARNING(Service_NVDRV,
"(STUBBED) called, syncpt_id={}, threshold={}, timeout={}, is_async={}",
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
params.syncpt_id, params.threshold, params.timeout, is_async);
// TODO(Subv): Implement actual syncpt waiting.
params.value = 0;
if (params.syncpt_id >= MaxSyncPoints) {
return NvResult::BadParameter;
}
auto& gpu = system.GPU();
// This is mostly to take into account unimplemented features. As synced
// gpu is always synced.
if (!gpu.IsAsync()) {
return NvResult::Success;
}
auto lock = gpu.LockSync();
const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id);
const s32 diff = current_syncpoint_value - params.threshold;
if (diff >= 0) {
params.value = current_syncpoint_value;
std::memcpy(output.data(), &params, sizeof(params));
return 0;
return NvResult::Success;
}
const u32 target_value = current_syncpoint_value - diff;
if (!is_async) {
params.value = 0;
}
if (params.timeout == 0) {
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Timeout;
}
u32 event_id;
if (is_async) {
event_id = params.value & 0x00FF;
if (event_id >= MaxNvEvents) {
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::BadParameter;
}
} else {
if (ctrl.fresh_call) {
const auto result = events_interface.GetFreeEvent();
if (result) {
event_id = *result;
} else {
LOG_CRITICAL(Service_NVDRV, "No Free Events available!");
event_id = params.value & 0x00FF;
}
} else {
event_id = ctrl.event_id;
}
}
EventState status = events_interface.status[event_id];
if (event_id < MaxNvEvents || status == EventState::Free || status == EventState::Registered) {
events_interface.SetEventStatus(event_id, EventState::Waiting);
events_interface.assigned_syncpt[event_id] = params.syncpt_id;
events_interface.assigned_value[event_id] = target_value;
if (is_async) {
params.value = params.syncpt_id << 4;
} else {
params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
}
params.value |= event_id;
events_interface.events[event_id].writable->Clear();
gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
if (!is_async && ctrl.fresh_call) {
ctrl.must_delay = true;
ctrl.timeout = params.timeout;
ctrl.event_id = event_id;
return NvResult::Timeout;
}
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Timeout;
}
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::BadParameter;
}
u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
// TODO(bunnei): Implement this.
return 0;
IocCtrlEventRegisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
if (events_interface.registered[event_id]) {
return NvResult::BadParameter;
}
events_interface.RegisterEvent(event_id);
return NvResult::Success;
}
u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventUnregisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
if (!events_interface.registered[event_id]) {
return NvResult::BadParameter;
}
events_interface.UnregisterEvent(event_id);
return NvResult::Success;
}
u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventSignalParams params{};
std::memcpy(&params, input.data(), sizeof(params));
// TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization
// It is believed from RE to cancel the GPU Event. However, better research is required
u32 event_id = params.user_event_id & 0x00FF;
LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
if (events_interface.status[event_id] == EventState::Waiting) {
auto& gpu = system.GPU();
if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id],
events_interface.assigned_value[event_id])) {
events_interface.LiberateEvent(event_id);
events_interface.events[event_id].writable->Signal();
}
}
return NvResult::Success;
}
} // namespace Service::Nvidia::Devices

View File

@ -8,15 +8,17 @@
#include <vector>
#include "common/common_types.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
nvhost_ctrl();
explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface);
~nvhost_ctrl() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
@ -132,9 +134,16 @@ private:
u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async,
IoctlCtrl& ctrl);
u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output);
EventInterface& events_interface;
};
} // namespace Service::Nvidia::Devices

View File

@ -12,10 +12,11 @@
namespace Service::Nvidia::Devices {
nvhost_ctrl_gpu::nvhost_ctrl_gpu() = default;
nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@ -185,7 +186,7 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& o
IoctlGetGpuTime params{};
std::memcpy(&params, input.data(), input.size());
const auto ns = Core::Timing::CyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks());
const auto ns = Core::Timing::CyclesToNs(system.CoreTiming().GetTicks());
params.gpu_time = static_cast<u64_le>(ns.count());
std::memcpy(output.data(), &params, output.size());
return 0;

View File

@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_ctrl_gpu final : public nvdevice {
public:
nvhost_ctrl_gpu();
explicit nvhost_ctrl_gpu(Core::System& system);
~nvhost_ctrl_gpu() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {

View File

@ -13,10 +13,12 @@
namespace Service::Nvidia::Devices {
nvhost_gpu::nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_gpu::~nvhost_gpu() = default;
u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@ -119,8 +121,10 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
params.unk3);
params.fence_out.id = 0;
params.fence_out.value = 0;
auto& gpu = system.GPU();
params.fence_out.id = assigned_syncpoints;
params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints);
assigned_syncpoints++;
std::memcpy(output.data(), &params, output.size());
return 0;
}
@ -143,7 +147,7 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
params.address, params.num_entries, params.flags);
params.address, params.num_entries, params.flags.raw);
ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) +
params.num_entries * sizeof(Tegra::CommandListHeader),
@ -153,10 +157,18 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)],
params.num_entries * sizeof(Tegra::CommandListHeader));
Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries));
UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
auto& gpu = system.GPU();
u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
if (params.flags.increment.Value()) {
params.fence_out.value += current_syncpoint_value;
} else {
params.fence_out.value = current_syncpoint_value;
}
gpu.PushGPUEntries(std::move(entries));
params.fence_out.id = 0;
params.fence_out.value = 0;
std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
return 0;
}
@ -168,16 +180,24 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
params.address, params.num_entries, params.flags);
params.address, params.num_entries, params.flags.raw);
Tegra::CommandList entries(params.num_entries);
Memory::ReadBlock(params.address, entries.data(),
params.num_entries * sizeof(Tegra::CommandListHeader));
Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries));
UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
auto& gpu = system.GPU();
u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
if (params.flags.increment.Value()) {
params.fence_out.value += current_syncpoint_value;
} else {
params.fence_out.value = current_syncpoint_value;
}
gpu.PushGPUEntries(std::move(entries));
params.fence_out.id = 0;
params.fence_out.value = 0;
std::memcpy(output.data(), &params, output.size());
return 0;
}

View File

@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdata.h"
namespace Service::Nvidia::Devices {
@ -20,10 +21,11 @@ constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b);
class nvhost_gpu final : public nvdevice {
public:
explicit nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev);
explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_gpu() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
@ -113,11 +115,7 @@ private:
static_assert(sizeof(IoctlGetErrorNotification) == 16,
"IoctlGetErrorNotification is incorrect size");
struct IoctlFence {
u32_le id;
u32_le value;
};
static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size");
static_assert(sizeof(Fence) == 8, "Fence is incorrect size");
struct IoctlAllocGpfifoEx {
u32_le num_entries;
@ -135,7 +133,7 @@ private:
u32_le num_entries; // in
u32_le flags; // in
u32_le unk0; // in (1 works)
IoctlFence fence_out; // out
Fence fence_out; // out
u32_le unk1; // in
u32_le unk2; // in
u32_le unk3; // in
@ -153,10 +151,16 @@ private:
struct IoctlSubmitGpfifo {
u64_le address; // pointer to gpfifo entry structs
u32_le num_entries; // number of fence objects being submitted
u32_le flags;
IoctlFence fence_out; // returned new fence object for others to wait on
union {
u32_le raw;
BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
BitField<1, 1, u32_le> add_increment; // append an increment to the list
BitField<2, 1, u32_le> new_hw_format; // Mostly ignored
BitField<8, 1, u32_le> increment; // increment the returned fence
} flags;
Fence fence_out; // returned new fence object for others to wait on
};
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence),
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence),
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
@ -184,6 +188,7 @@ private:
u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
u32 assigned_syncpoints{};
};
} // namespace Service::Nvidia::Devices

View File

@ -10,10 +10,11 @@
namespace Service::Nvidia::Devices {
nvhost_nvdec::nvhost_nvdec() = default;
nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {}
nvhost_nvdec::~nvhost_nvdec() = default;
u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());

View File

@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_nvdec final : public nvdevice {
public:
nvhost_nvdec();
explicit nvhost_nvdec(Core::System& system);
~nvhost_nvdec() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {

View File

@ -10,10 +10,11 @@
namespace Service::Nvidia::Devices {
nvhost_nvjpg::nvhost_nvjpg() = default;
nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
nvhost_nvjpg::~nvhost_nvjpg() = default;
u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());

View File

@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_nvjpg final : public nvdevice {
public:
nvhost_nvjpg();
explicit nvhost_nvjpg(Core::System& system);
~nvhost_nvjpg() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {

View File

@ -10,10 +10,11 @@
namespace Service::Nvidia::Devices {
nvhost_vic::nvhost_vic() = default;
nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {}
nvhost_vic::~nvhost_vic() = default;
u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());

View File

@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_vic final : public nvdevice {
public:
nvhost_vic();
explicit nvhost_vic(Core::System& system);
~nvhost_vic() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {

View File

@ -18,7 +18,7 @@ enum {
};
}
nvmap::nvmap() = default;
nvmap::nvmap(Core::System& system) : nvdevice(system) {}
nvmap::~nvmap() = default;
VAddr nvmap::GetObjectAddress(u32 handle) const {
@ -28,7 +28,8 @@ VAddr nvmap::GetObjectAddress(u32 handle) const {
return object->addr;
}
u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::Create:
return IocCreate(input, output);

View File

@ -16,13 +16,14 @@ namespace Service::Nvidia::Devices {
class nvmap final : public nvdevice {
public:
nvmap();
explicit nvmap(Core::System& system);
~nvmap() override;
/// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) override;
/// Represents an nvmap object.
struct Object {

View File

@ -8,12 +8,18 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia {
void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
nvdrv->SignalSyncpt(syncpoint_id, value);
}
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
@ -36,12 +42,32 @@ void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
std::vector<u8> output(ctx.GetWriteBufferSize());
IoctlCtrl ctrl{};
u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output, ctrl);
if (ctrl.must_delay) {
ctrl.fresh_call = false;
ctx.SleepClientThread(
"NVServices::DelayedResponse", ctrl.timeout,
[=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
IoctlCtrl ctrl2{ctrl};
std::vector<u8> output2 = output;
u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output2, ctrl2);
ctx.WriteBuffer(output2);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output));
rb.Push(result);
},
nvdrv->GetEventWriteable(ctrl.event_id));
} else {
ctx.WriteBuffer(output);
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(result);
}
void NVDRV::Close(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
@ -66,13 +92,19 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
u32 event_id = rp.Pop<u32>();
// TODO(Blinkhawk): Figure the meaning of the flag at bit 16
u32 event_id = rp.Pop<u32>() & 0x000000FF;
LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
IPC::ResponseBuilder rb{ctx, 3, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(query_event.readable);
if (event_id < MaxNvEvents) {
rb.PushCopyObjects(nvdrv->GetEvent(event_id));
rb.Push<u32>(NvResult::Success);
} else {
rb.Push<u32>(0);
rb.Push<u32>(NvResult::BadParameter);
}
}
void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) {
@ -127,10 +159,6 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
{13, &NVDRV::FinishInitialize, "FinishInitialize"},
};
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"NVDRV::query_event");
}
NVDRV::~NVDRV() = default;

View File

@ -19,6 +19,8 @@ public:
NVDRV(std::shared_ptr<Module> nvdrv, const char* name);
~NVDRV() override;
void SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value);
private:
void Open(Kernel::HLERequestContext& ctx);
void Ioctl(Kernel::HLERequestContext& ctx);
@ -33,8 +35,6 @@ private:
std::shared_ptr<Module> nvdrv;
u64 pid{};
Kernel::EventPair query_event;
};
} // namespace Service::Nvidia

View File

@ -0,0 +1,48 @@
#pragma once
#include <array>
#include "common/common_types.h"
namespace Service::Nvidia {
constexpr u32 MaxSyncPoints = 192;
constexpr u32 MaxNvEvents = 64;
struct Fence {
s32 id;
u32 value;
};
static_assert(sizeof(Fence) == 8, "Fence has wrong size");
struct MultiFence {
u32 num_fences;
std::array<Fence, 4> fences;
};
enum NvResult : u32 {
Success = 0,
BadParameter = 4,
Timeout = 5,
ResourceError = 15,
};
enum class EventState {
Free = 0,
Registered = 1,
Waiting = 2,
Busy = 3,
};
struct IoctlCtrl {
// First call done to the servioce for services that call itself again after a call.
bool fresh_call{true};
// Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep
bool must_delay{};
// Timeout for the delay
s64 timeout{};
// NV Event Id
s32 event_id{-1};
};
} // namespace Service::Nvidia

View File

@ -4,7 +4,10 @@
#include <utility>
#include <fmt/format.h>
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
@ -22,8 +25,9 @@
namespace Service::Nvidia {
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger) {
auto module_ = std::make_shared<Module>();
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system) {
auto module_ = std::make_shared<Module>(system);
std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager);
std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager);
std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager);
@ -32,17 +36,25 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
nvflinger.SetNVDrvInstance(module_);
}
Module::Module() {
auto nvmap_dev = std::make_shared<Devices::nvmap>();
devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(nvmap_dev);
devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(nvmap_dev);
devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>();
Module::Module(Core::System& system) {
auto& kernel = system.Kernel();
for (u32 i = 0; i < MaxNvEvents; i++) {
std::string event_label = fmt::format("NVDRV::NvEvent_{}", i);
events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Automatic, event_label);
events_interface.status[i] = EventState::Free;
events_interface.registered[i] = false;
}
auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev);
devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
devices["/dev/nvmap"] = nvmap_dev;
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev);
devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>();
devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>();
devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>();
devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>();
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(system, events_interface);
devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system);
devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system);
}
Module::~Module() = default;
@ -59,12 +71,13 @@ u32 Module::Open(const std::string& device_name) {
return fd;
}
u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output) {
u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl) {
auto itr = open_files.find(fd);
ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
auto& device = itr->second;
return device->ioctl({command}, input, output);
return device->ioctl({command}, input, output, ctrl);
}
ResultCode Module::Close(u32 fd) {
@ -77,4 +90,22 @@ ResultCode Module::Close(u32 fd) {
return RESULT_SUCCESS;
}
void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
for (u32 i = 0; i < MaxNvEvents; i++) {
if (events_interface.assigned_syncpt[i] == syncpoint_id &&
events_interface.assigned_value[i] == value) {
events_interface.LiberateEvent(i);
events_interface.events[i].writable->Signal();
}
}
}
Kernel::SharedPtr<Kernel::ReadableEvent> Module::GetEvent(const u32 event_id) const {
return events_interface.events[event_id].readable;
}
Kernel::SharedPtr<Kernel::WritableEvent> Module::GetEventWriteable(const u32 event_id) const {
return events_interface.events[event_id].writable;
}
} // namespace Service::Nvidia

View File

@ -8,8 +8,14 @@
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::NVFlinger {
class NVFlinger;
}
@ -20,16 +26,72 @@ namespace Devices {
class nvdevice;
}
struct IoctlFence {
u32 id;
u32 value;
struct EventInterface {
// Mask representing currently busy events
u64 events_mask{};
// Each kernel event associated to an NV event
std::array<Kernel::EventPair, MaxNvEvents> events;
// The status of the current NVEvent
std::array<EventState, MaxNvEvents> status{};
// Tells if an NVEvent is registered or not
std::array<bool, MaxNvEvents> registered{};
// When an NVEvent is waiting on GPU interrupt, this is the sync_point
// associated with it.
std::array<u32, MaxNvEvents> assigned_syncpt{};
// This is the value of the GPU interrupt for which the NVEvent is waiting
// for.
std::array<u32, MaxNvEvents> assigned_value{};
// Constant to denote an unasigned syncpoint.
static constexpr u32 unassigned_syncpt = 0xFFFFFFFF;
std::optional<u32> GetFreeEvent() const {
u64 mask = events_mask;
for (u32 i = 0; i < MaxNvEvents; i++) {
const bool is_free = (mask & 0x1) == 0;
if (is_free) {
if (status[i] == EventState::Registered || status[i] == EventState::Free) {
return {i};
}
}
mask = mask >> 1;
}
return {};
}
void SetEventStatus(const u32 event_id, EventState new_status) {
EventState old_status = status[event_id];
if (old_status == new_status) {
return;
}
status[event_id] = new_status;
if (new_status == EventState::Registered) {
registered[event_id] = true;
}
if (new_status == EventState::Waiting || new_status == EventState::Busy) {
events_mask |= (1ULL << event_id);
}
}
void RegisterEvent(const u32 event_id) {
registered[event_id] = true;
if (status[event_id] == EventState::Free) {
status[event_id] = EventState::Registered;
}
}
void UnregisterEvent(const u32 event_id) {
registered[event_id] = false;
if (status[event_id] == EventState::Registered) {
status[event_id] = EventState::Free;
}
}
void LiberateEvent(const u32 event_id) {
status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free;
events_mask &= ~(1ULL << event_id);
assigned_syncpt[event_id] = unassigned_syncpt;
assigned_value[event_id] = 0;
}
};
static_assert(sizeof(IoctlFence) == 8, "IoctlFence has wrong size");
class Module final {
public:
Module();
Module(Core::System& system);
~Module();
/// Returns a pointer to one of the available devices, identified by its name.
@ -44,10 +106,17 @@ public:
/// Opens a device node and returns a file descriptor to it.
u32 Open(const std::string& device_name);
/// Sends an ioctl command to the specified file descriptor.
u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output);
u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output,
IoctlCtrl& ctrl);
/// Closes a device file descriptor and returns operation success.
ResultCode Close(u32 fd);
void SignalSyncpt(const u32 syncpoint_id, const u32 value);
Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent(u32 event_id) const;
Kernel::SharedPtr<Kernel::WritableEvent> GetEventWriteable(u32 event_id) const;
private:
/// Id to use for the next open file descriptor.
u32 next_fd = 1;
@ -57,9 +126,12 @@ private:
/// Mapping of device node names to their implementation.
std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
EventInterface events_interface;
};
/// Registers all NVDRV services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger);
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system);
} // namespace Service::Nvidia

View File

@ -34,7 +34,8 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
buffer_wait_event.writable->Signal();
}
std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
u32 height) {
auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
// Only consider free buffers. Buffers become free once again after they've been Acquired
// and Released by the compositor, see the NVFlinger::Compose method.
@ -51,7 +52,7 @@ std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
}
itr->status = Buffer::Status::Dequeued;
return itr->slot;
return {{itr->slot, &itr->multi_fence}};
}
const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
@ -63,7 +64,8 @@ const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
}
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect) {
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
Service::Nvidia::MultiFence& multi_fence) {
auto itr = std::find_if(queue.begin(), queue.end(),
[&](const Buffer& buffer) { return buffer.slot == slot; });
ASSERT(itr != queue.end());
@ -71,12 +73,21 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
itr->status = Buffer::Status::Queued;
itr->transform = transform;
itr->crop_rect = crop_rect;
itr->swap_interval = swap_interval;
itr->multi_fence = multi_fence;
queue_sequence.push_back(slot);
}
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
return buffer.status == Buffer::Status::Queued;
auto itr = queue.end();
// Iterate to find a queued buffer matching the requested slot.
while (itr == queue.end() && !queue_sequence.empty()) {
u32 slot = queue_sequence.front();
itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
});
queue_sequence.pop_front();
}
if (itr == queue.end())
return {};
itr->status = Buffer::Status::Acquired;

View File

@ -4,6 +4,7 @@
#pragma once
#include <list>
#include <optional>
#include <vector>
@ -12,6 +13,7 @@
#include "common/swap.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
namespace Service::NVFlinger {
@ -68,13 +70,17 @@ public:
IGBPBuffer igbp_buffer;
BufferTransformFlags transform;
Common::Rectangle<int> crop_rect;
u32 swap_interval;
Service::Nvidia::MultiFence multi_fence;
};
void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
std::optional<u32> DequeueBuffer(u32 width, u32 height);
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> DequeueBuffer(u32 width,
u32 height);
const IGBPBuffer& RequestBuffer(u32 slot) const;
void QueueBuffer(u32 slot, BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect);
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
Service::Nvidia::MultiFence& multi_fence);
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot);
u32 Query(QueryType type);
@ -92,6 +98,7 @@ private:
u64 layer_id;
std::vector<Buffer> queue;
std::list<u32> queue_sequence;
Kernel::EventPair buffer_wait_event;
};

View File

@ -37,15 +37,14 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t
displays.emplace_back(4, "Null");
// Schedule the screen composition events
const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks;
composition_event = core_timing.RegisterEvent(
"ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {
composition_event = core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata,
s64 cycles_late) {
Compose();
this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);
const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks();
this->core_timing.ScheduleEvent(std::max<s64>(0LL, ticks - cycles_late), composition_event);
});
core_timing.ScheduleEvent(ticks, composition_event);
core_timing.ScheduleEvent(frame_ticks, composition_event);
}
NVFlinger::~NVFlinger() {
@ -206,8 +205,14 @@ void NVFlinger::Compose() {
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
buffer->get().transform, buffer->get().crop_rect);
swap_interval = buffer->get().swap_interval;
buffer_queue.ReleaseBuffer(buffer->get().slot);
}
}
s64 NVFlinger::GetNextTicks() const {
constexpr s64 max_hertz = 120LL;
return (Core::Timing::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz;
}
} // namespace Service::NVFlinger

View File

@ -74,6 +74,8 @@ public:
/// finished.
void Compose();
s64 GetNextTicks() const;
private:
/// Finds the display identified by the specified ID.
VI::Display* FindDisplay(u64 display_id);
@ -98,6 +100,8 @@ private:
/// layers.
u32 next_buffer_queue_id = 1;
u32 swap_interval = 1;
/// Event that handles screen composition.
Core::Timing::EventType* composition_event;

View File

@ -236,7 +236,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
NIM::InstallInterfaces(*sm);
NPNS::InstallInterfaces(*sm);
NS::InstallInterfaces(*sm);
Nvidia::InstallInterfaces(*sm, *nv_flinger);
Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
PCIe::InstallInterfaces(*sm);
PCTL::InstallInterfaces(*sm);
PCV::InstallInterfaces(*sm);

View File

@ -21,6 +21,7 @@
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@ -328,32 +329,22 @@ public:
Data data;
};
struct BufferProducerFence {
u32 is_valid;
std::array<Nvidia::IoctlFence, 4> fences;
};
static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong size");
class IGBPDequeueBufferResponseParcel : public Parcel {
public:
explicit IGBPDequeueBufferResponseParcel(u32 slot) : slot(slot) {}
explicit IGBPDequeueBufferResponseParcel(u32 slot, Service::Nvidia::MultiFence& multi_fence)
: slot(slot), multi_fence(multi_fence) {}
~IGBPDequeueBufferResponseParcel() override = default;
protected:
void SerializeData() override {
// TODO(Subv): Find out how this Fence is used.
BufferProducerFence fence = {};
fence.is_valid = 1;
for (auto& fence_ : fence.fences)
fence_.id = -1;
Write(slot);
Write<u32_le>(1);
WriteObject(fence);
WriteObject(multi_fence);
Write<u32_le>(0);
}
u32_le slot;
Service::Nvidia::MultiFence multi_fence;
};
class IGBPRequestBufferRequestParcel : public Parcel {
@ -400,12 +391,6 @@ public:
data = Read<Data>();
}
struct Fence {
u32_le id;
u32_le value;
};
static_assert(sizeof(Fence) == 8, "Fence has wrong size");
struct Data {
u32_le slot;
INSERT_PADDING_WORDS(3);
@ -418,15 +403,15 @@ public:
s32_le scaling_mode;
NVFlinger::BufferQueue::BufferTransformFlags transform;
u32_le sticky_transform;
INSERT_PADDING_WORDS(2);
u32_le fence_is_valid;
std::array<Fence, 2> fences;
INSERT_PADDING_WORDS(1);
u32_le swap_interval;
Service::Nvidia::MultiFence multi_fence;
Common::Rectangle<int> GetCropRect() const {
return {crop_left, crop_top, crop_right, crop_bottom};
}
};
static_assert(sizeof(Data) == 80, "ParcelData has wrong size");
static_assert(sizeof(Data) == 96, "ParcelData has wrong size");
Data data;
};
@ -547,11 +532,11 @@ private:
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
const u32 width{request.data.width};
const u32 height{request.data.height};
std::optional<u32> slot = buffer_queue.DequeueBuffer(width, height);
auto result = buffer_queue.DequeueBuffer(width, height);
if (slot) {
if (result) {
// Buffer is available
IGBPDequeueBufferResponseParcel response{*slot};
IGBPDequeueBufferResponseParcel response{result->first, *result->second};
ctx.WriteBuffer(response.Serialize());
} else {
// Wait the current thread until a buffer becomes available
@ -561,10 +546,10 @@ private:
Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
auto& buffer_queue = nv_flinger->FindBufferQueue(id);
std::optional<u32> slot = buffer_queue.DequeueBuffer(width, height);
ASSERT_MSG(slot != std::nullopt, "Could not dequeue buffer.");
auto result = buffer_queue.DequeueBuffer(width, height);
ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer.");
IGBPDequeueBufferResponseParcel response{*slot};
IGBPDequeueBufferResponseParcel response{result->first, *result->second};
ctx.WriteBuffer(response.Serialize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@ -582,7 +567,8 @@ private:
IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()};
buffer_queue.QueueBuffer(request.data.slot, request.data.transform,
request.data.GetCropRect());
request.data.GetCropRect(), request.data.swap_interval,
request.data.multi_fence);
IGBPQueueBufferResponseParcel response{1280, 720};
ctx.WriteBuffer(response.Serialize());

View File

@ -525,8 +525,9 @@ void Maxwell3D::ProcessSyncPoint() {
const u32 sync_point = regs.sync_info.sync_point.Value();
const u32 increment = regs.sync_info.increment.Value();
const u32 cache_flush = regs.sync_info.unknown.Value();
LOG_DEBUG(HW_GPU, "Syncpoint set {}, increment: {}, unk: {}", sync_point, increment,
cache_flush);
if (increment) {
system.GPU().IncrementSyncPoint(sync_point);
}
}
void Maxwell3D::DrawArrays() {

View File

@ -29,7 +29,8 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
UNREACHABLE();
}
GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} {
GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async)
: system{system}, renderer{renderer}, is_async{is_async} {
auto& rasterizer{renderer.Rasterizer()};
memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer);
dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
@ -74,6 +75,51 @@ const DmaPusher& GPU::DmaPusher() const {
return *dma_pusher;
}
void GPU::IncrementSyncPoint(const u32 syncpoint_id) {
syncpoints[syncpoint_id]++;
std::lock_guard lock{sync_mutex};
if (!syncpt_interrupts[syncpoint_id].empty()) {
u32 value = syncpoints[syncpoint_id].load();
auto it = syncpt_interrupts[syncpoint_id].begin();
while (it != syncpt_interrupts[syncpoint_id].end()) {
if (value >= *it) {
TriggerCpuInterrupt(syncpoint_id, *it);
it = syncpt_interrupts[syncpoint_id].erase(it);
continue;
}
it++;
}
}
}
u32 GPU::GetSyncpointValue(const u32 syncpoint_id) const {
return syncpoints[syncpoint_id].load();
}
void GPU::RegisterSyncptInterrupt(const u32 syncpoint_id, const u32 value) {
auto& interrupt = syncpt_interrupts[syncpoint_id];
bool contains = std::any_of(interrupt.begin(), interrupt.end(),
[value](u32 in_value) { return in_value == value; });
if (contains) {
return;
}
syncpt_interrupts[syncpoint_id].emplace_back(value);
}
bool GPU::CancelSyncptInterrupt(const u32 syncpoint_id, const u32 value) {
std::lock_guard lock{sync_mutex};
auto& interrupt = syncpt_interrupts[syncpoint_id];
const auto iter =
std::find_if(interrupt.begin(), interrupt.end(),
[value](u32 interrupt_value) { return value == interrupt_value; });
if (iter == interrupt.end()) {
return false;
}
interrupt.erase(iter);
return true;
}
u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
ASSERT(format != RenderTargetFormat::NONE);

View File

@ -5,8 +5,12 @@
#pragma once
#include <array>
#include <atomic>
#include <list>
#include <memory>
#include <mutex>
#include "common/common_types.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "video_core/dma_pusher.h"
@ -127,7 +131,7 @@ class MemoryManager;
class GPU {
public:
explicit GPU(Core::System& system, VideoCore::RendererBase& renderer);
explicit GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async);
virtual ~GPU();
@ -170,6 +174,22 @@ public:
/// Returns a reference to the GPU DMA pusher.
Tegra::DmaPusher& DmaPusher();
void IncrementSyncPoint(u32 syncpoint_id);
u32 GetSyncpointValue(u32 syncpoint_id) const;
void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value);
bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
std::unique_lock<std::mutex> LockSync() {
return std::unique_lock{sync_mutex};
}
bool IsAsync() const {
return is_async;
}
/// Returns a const reference to the GPU DMA pusher.
const Tegra::DmaPusher& DmaPusher() const;
@ -239,6 +259,9 @@ public:
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
protected:
virtual void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const = 0;
private:
void ProcessBindMethod(const MethodCall& method_call);
void ProcessSemaphoreTriggerMethod();
@ -257,6 +280,7 @@ private:
protected:
std::unique_ptr<Tegra::DmaPusher> dma_pusher;
VideoCore::RendererBase& renderer;
Core::System& system;
private:
std::unique_ptr<Tegra::MemoryManager> memory_manager;
@ -273,6 +297,14 @@ private:
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
/// Inline memory engine
std::unique_ptr<Engines::KeplerMemory> kepler_memory;
std::array<std::atomic<u32>, Service::Nvidia::MaxSyncPoints> syncpoints{};
std::array<std::list<u32>, Service::Nvidia::MaxSyncPoints> syncpt_interrupts;
std::mutex sync_mutex;
const bool is_async;
};
#define ASSERT_REG_POSITION(field_name, position) \

View File

@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hardware_interrupt_manager.h"
#include "video_core/gpu_asynch.h"
#include "video_core/gpu_thread.h"
#include "video_core/renderer_base.h"
@ -9,7 +11,7 @@
namespace VideoCommon {
GPUAsynch::GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer)
: GPU(system, renderer), gpu_thread{system} {}
: GPU(system, renderer, true), gpu_thread{system} {}
GPUAsynch::~GPUAsynch() = default;
@ -38,4 +40,9 @@ void GPUAsynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size);
}
void GPUAsynch::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const {
auto& interrupt_manager = system.InterruptManager();
interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
}
} // namespace VideoCommon

View File

@ -27,6 +27,9 @@ public:
void InvalidateRegion(CacheAddr addr, u64 size) override;
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
protected:
void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override;
private:
GPUThread::ThreadManager gpu_thread;
};

View File

@ -8,7 +8,7 @@
namespace VideoCommon {
GPUSynch::GPUSynch(Core::System& system, VideoCore::RendererBase& renderer)
: GPU(system, renderer) {}
: GPU(system, renderer, false) {}
GPUSynch::~GPUSynch() = default;

View File

@ -25,6 +25,10 @@ public:
void FlushRegion(CacheAddr addr, u64 size) override;
void InvalidateRegion(CacheAddr addr, u64 size) override;
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
protected:
void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id,
[[maybe_unused]] u32 value) const override {}
};
} // namespace VideoCommon

View File

@ -21,7 +21,8 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
MicroProfileOnThreadCreate("GpuThread");
// Wait for first GPU command before acquiring the window context
state.WaitForCommands();
while (state.queue.Empty())
;
// If emulation was stopped during disk shader loading, abort before trying to acquire context
if (!state.is_running) {
@ -32,7 +33,6 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
CommandDataContainer next;
while (state.is_running) {
state.WaitForCommands();
while (!state.queue.Empty()) {
state.queue.Pop(next);
if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
@ -49,8 +49,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
} else {
UNREACHABLE();
}
state.signaled_fence = next.fence;
state.TrySynchronize();
state.signaled_fence.store(next.fence);
}
}
}
@ -89,12 +88,7 @@ void ThreadManager::FlushRegion(CacheAddr addr, u64 size) {
}
void ThreadManager::InvalidateRegion(CacheAddr addr, u64 size) {
if (state.queue.Empty()) {
// It's quicker to invalidate a single region on the CPU if the queue is already empty
system.Renderer().Rasterizer().InvalidateRegion(addr, size);
} else {
PushCommand(InvalidateRegionCommand(addr, size));
}
}
void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
@ -105,22 +99,13 @@ void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
u64 ThreadManager::PushCommand(CommandData&& command_data) {
const u64 fence{++state.last_fence};
state.queue.Push(CommandDataContainer(std::move(command_data), fence));
state.SignalCommands();
return fence;
}
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
void SynchState::WaitForSynchronization(u64 fence) {
if (signaled_fence >= fence) {
return;
}
// Wait for the GPU to be idle (all commands to be executed)
{
MICROPROFILE_SCOPE(GPU_wait);
std::unique_lock lock{synchronization_mutex};
synchronization_condition.wait(lock, [this, fence] { return signaled_fence >= fence; });
}
while (signaled_fence.load() < fence)
;
}
} // namespace VideoCommon::GPUThread

View File

@ -88,41 +88,9 @@ struct CommandDataContainer {
/// Struct used to synchronize the GPU thread
struct SynchState final {
std::atomic_bool is_running{true};
std::atomic_int queued_frame_count{};
std::mutex synchronization_mutex;
std::mutex commands_mutex;
std::condition_variable commands_condition;
std::condition_variable synchronization_condition;
/// Returns true if the gap in GPU commands is small enough that we can consider the CPU and GPU
/// synchronized. This is entirely empirical.
bool IsSynchronized() const {
constexpr std::size_t max_queue_gap{5};
return queue.Size() <= max_queue_gap;
}
void TrySynchronize() {
if (IsSynchronized()) {
std::lock_guard lock{synchronization_mutex};
synchronization_condition.notify_one();
}
}
void WaitForSynchronization(u64 fence);
void SignalCommands() {
if (queue.Empty()) {
return;
}
commands_condition.notify_one();
}
void WaitForCommands() {
std::unique_lock lock{commands_mutex};
commands_condition.wait(lock, [this] { return !queue.Empty(); });
}
using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
CommandQueue queue;
u64 last_fence{};