diff options
| -rw-r--r-- | src/core/device_memory_manager.h | 2 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp | 47 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h | 4 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 2 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp | 39 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nx2_stub_device.h | 45 | ||||
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 235 | ||||
| -rw-r--r-- | src/video_core/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/video_core/engines/maxwell_3d.cpp | 7 | ||||
| -rw-r--r-- | src/video_core/engines/puller.cpp | 29 | ||||
| -rw-r--r-- | src/video_core/nx2.h | 14 |
11 files changed, 397 insertions, 28 deletions
diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index b4b3b46088..b80c02ef36 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -139,7 +139,7 @@ private: static constexpr size_t device_virtual_bits = Traits::device_virtual_bits; static constexpr size_t device_as_size = 1ULL << device_virtual_bits; static constexpr size_t physical_min_bits = 32; - static constexpr size_t physical_max_bits = 33; + static constexpr size_t physical_max_bits = 34; // covers the 10 and 12 GiB layouts static constexpr size_t page_bits = 12; static constexpr size_t page_size = 1ULL << page_bits; static constexpr size_t page_mask = page_size - 1ULL; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 0733b5afaf..f309949c5f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -14,13 +14,16 @@ #include "common/logging.h" #include "core/core.h" #include "core/hle/service/nvdrv/core/container.h" +#include "core/hle/service/nvdrv/core/heap_mapper.h" #include "core/hle/service/nvdrv/core/nvmap.h" #include "core/hle/service/nvdrv/devices/ioctl_serialization.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" +#include "core/hle/service/nvdrv/devices/nx2_stub_device.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "video_core/control/channel_state.h" #include "video_core/gpu.h" +#include "video_core/host1x/host1x.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" @@ -30,7 +33,9 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Con : nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{}, gmmu{} {} -nvhost_as_gpu::~nvhost_as_gpu() = default; +nvhost_as_gpu::~nvhost_as_gpu() { + Nx2GpuMemoryRegistry::Get().RemoveListener(this); +} NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output) { @@ -49,8 +54,14 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> i return WrapFixed(this, &nvhost_as_gpu::MapBufferEx, input, output); case 0x8: return WrapFixed(this, &nvhost_as_gpu::GetVARegions1, input, output); - case 0x9: - return WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output); + case 0x9: { + const auto result = WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output); + if (result == NvResult::Success && g_nx2_driver) { + Nx2GpuMemoryRegistry::Get().AddListener( + this, [this](u64 address, u64 size) { Nx2MapIdentity(address, size); }); + } + return result; + } case 0x14: return WrapVariable(this, &nvhost_as_gpu::Remap, input, output); default: @@ -90,7 +101,35 @@ NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> i return NvResult::NotImplemented; } -void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {} +void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) { + nx2_session = session_id; +} + +void nvhost_as_gpu::Nx2MapIdentity(u64 address, u64 size) { + std::scoped_lock lock(mutex); + auto* session = container.GetSession(nx2_session); + if (!gmmu || session == nullptr) { + LOG_ERROR(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x}: no address space or session", + address, size); + return; + } + DAddr device_address{}; + if (session->has_preallocated_area && session->mapper->IsInBounds(address, size)) { + device_address = session->mapper->Map(address, size); + } else { + auto& smmu = system.Host1x().MemoryManager(); + device_address = smmu.Allocate(Common::AlignUp(size, 0x20000)); + if (device_address == 0) { + LOG_CRITICAL(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x}: out of SMMU space", + address, size); + return; + } + smmu.Map(device_address, address, size, session->asid, true); + } + gmmu->Map(address, device_address, size, Tegra::PTEKind::PITCH, false); + LOG_INFO(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x} mapped at the same GPU address", address, + size); +} void nvhost_as_gpu::OnClose(DeviceFD fd) {} NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 9102252c96..419cf4cce7 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -221,6 +221,10 @@ private: bool absolute_big_page_limit{}; //!< Switch 2 layout: big-page limit is va_range_end } vm; std::unique_ptr<Tegra::MemoryManager> gmmu; + + // Switch 2: maps GPU-accessible process memory at GPU address == CPU address. + void Nx2MapIdentity(u64 address, u64 size); + NvCore::SessionId nx2_session{}; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index b15828aedc..53c7f85517 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -14,6 +14,7 @@ #include "core/hle/service/nvdrv/devices/ioctl_serialization.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" #include "core/hle/service/nvdrv/devices/nx2_stub_device.h" +#include "video_core/nx2.h" #include "core/hle/service/nvdrv/nvdrv.h" namespace Service::Nvidia::Devices { @@ -290,6 +291,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics3( if (nx2) { nx2_caller = true; g_nx2_driver = true; + Tegra::g_nx2_ampere = true; ApplyNx2Characteristics(params.gc); params.gpu_characteristics_buf_size = Nx2CharacteristicsSize; LOG_WARNING(Service_NVDRV, "Switch 2 caller, reporting Ampere chip 0x17E"); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 15458cd82d..b650d3cac8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -367,6 +367,27 @@ static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementC return result; } +// Switch 2 pushbuffers signal completion with semaphore releases, never host1x syncpoint methods, +// but the driver still has the kernel raise the syncpoint max by the submission's increment value +// and then waits for that value. Append the increments after its commands. +static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementCountCommandList( + NvFence fence, u32 count) { + boost::container::small_vector<Tegra::CommandHeader, 512> result{ + Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1, + Tegra::SubmissionMode::Increasing), + {}, + Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1, + Tegra::SubmissionMode::Increasing), + {}}; + for (u32 i = 0; i < count; ++i) { + result.push_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1, + Tegra::SubmissionMode::Increasing)); + result.push_back( + BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id)); + } + return result; +} + static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementWithWfiCommandList( NvFence fence) { boost::container::small_vector<Tegra::CommandHeader, 512> result{ @@ -393,6 +414,13 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL const auto bind_id = channel_state->bind_id; + if (g_nx2_driver) { + for (const auto& entry : entries.command_lists) { + LOG_DEBUG(Service_NVDRV, "nx2 GPFIFO entry raw={:#018x} addr={:#x} size={:#x}", entry.raw, + entry.addr.Value(), entry.size.Value()); + } + } + auto& flags = params.flags; if (flags.fence_wait.Value()) { @@ -412,6 +440,11 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment); gpu.PushGPUEntries(bind_id, std::move(entries)); + if (g_nx2_driver && !flags.fence_increment.Value() && increment > 0) { + gpu.PushGPUEntries(bind_id, + Tegra::CommandList{BuildIncrementCountCommandList(params.fence, increment)}); + } + if (flags.fence_increment.Value()) { if (flags.suppress_wfi.Value()) { gpu.PushGPUEntries(bind_id, @@ -422,6 +455,12 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL } } + if (g_nx2_driver) { + LOG_DEBUG(Service_NVDRV, + "nx2 SubmitGPFIFO entries={} flags={:#x} fence_out=({}, {}) guest_value={}", + params.num_entries, flags.raw, params.fence.id, params.fence.value, + system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(params.fence.id)); + } flags.raw = 0; return NvResult::Success; diff --git a/src/core/hle/service/nvdrv/devices/nx2_stub_device.h b/src/core/hle/service/nvdrv/devices/nx2_stub_device.h index 4e8862a10a..1e766a6d06 100644 --- a/src/core/hle/service/nvdrv/devices/nx2_stub_device.h +++ b/src/core/hle/service/nvdrv/devices/nx2_stub_device.h @@ -5,7 +5,12 @@ #include <algorithm> #include <atomic> +#include <functional> +#include <map> +#include <mutex> #include <string> +#include <utility> +#include <vector> #include "common/logging.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" @@ -25,6 +30,46 @@ inline NvResult Nx2UnknownIoctl(const char* device, Ioctl command, std::span<con return NvResult::Success; } +// GPU-accessible memory a Switch 2 process registered through nx2:gpu (sub-session 101). The GPU +// shares the process address space there: GPFIFO entries and pushbuffers use the CPU address as +// the GPU address. Every nvhost-as-gpu address space maps each range at that address, including +// ranges registered before the address space existed. +class Nx2GpuMemoryRegistry { +public: + using Listener = std::function<void(u64 address, u64 size)>; + + static Nx2GpuMemoryRegistry& Get() { + static Nx2GpuMemoryRegistry registry; + return registry; + } + + void Register(u64 address, u64 size) { + std::scoped_lock lk{mutex}; + ranges.emplace_back(address, size); + for (auto& [owner, listener] : listeners) { + listener(address, size); + } + } + + void AddListener(const void* owner, Listener listener) { + std::scoped_lock lk{mutex}; + for (const auto& [address, size] : ranges) { + listener(address, size); + } + listeners.insert_or_assign(owner, std::move(listener)); + } + + void RemoveListener(const void* owner) { + std::scoped_lock lk{mutex}; + listeners.erase(owner); + } + +private: + std::mutex mutex; + std::vector<std::pair<u64, u64>> ranges; + std::map<const void*, Listener> listeners; +}; + // Device node that Switch 2 nvgpu drivers open but Switch 1 never had (e.g. /dev/nvhost-tsg-gpu). // Every ioctl succeeds with a zeroed reply and is logged, so the driver's use of it shows up. class nx2_stub_device final : public nvdevice { diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index e8f31d0556..ef222befca 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -5,9 +5,12 @@ #include <array> #include <initializer_list> +#include <atomic> +#include <map> #include <vector> #include "core/core.h" +#include "core/core_timing.h" #include "core/hle/kernel/k_client_session.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_resource_limit.h" @@ -17,9 +20,11 @@ #include "core/hle/kernel/k_session.h" #include "core/hle/kernel/k_transfer_memory.h" #include "core/memory.h" +#include "video_core/host1x/host1x.h" #include "core/hle/service/hle_ipc.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nvdrv/devices/nx2_stub_device.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvdrv_interface.h" #include "core/hle/service/nx2_stub_interface.h" @@ -79,19 +84,113 @@ public: // Placeholder for the Switch 2 syncpoint server (nn::syncpt), handed out by gsv 202. TIPC 1 // returns a u32 the SDK requires to be < 0x400 and then passes min(value, 63) to TIPC 21. // Everything else is auto-stubbed through the nx2: prefix. +// - TIPC 22(u32 slot): an event handle for that slot (copy handle at word 3, result at word 4). +// NVN's NvOsEventPool asks for slots and multi-waits on them. +// - TIPC 201(slot, counter, u64 value, mode): signal the slot's event once the counter reaches +// value. The counter is a SyncCounterId, the bitwise NOT of the host1x syncpoint id; mode 0 +// compares 32-bit wrapping, mode 1 64-bit. Eden's guest syncpoints back it. +// - TIPC 202(slot): cancel the slot's request. class INx2SyncptSession final : public ServiceFramework<INx2SyncptSession> { public: - explicit INx2SyncptSession(Core::System& system_) : ServiceFramework{system_, "nx2:syncpt"} { + explicit INx2SyncptSession(Core::System& system_) + : ServiceFramework{system_, "nx2:syncpt"}, m_context{system_, "nx2:syncpt"} { RegisterHandlersTipc({ {1, &INx2SyncptSession::GetCount, "GetCount (guessed)"}, + {22, &INx2SyncptSession::GetSlotEvent, "GetSlotEvent (guessed)"}, + {201, &INx2SyncptSession::RequestSlotEvent, "RequestSlotEvent (guessed)"}, + {202, &INx2SyncptSession::CancelSlotEvent, "CancelSlotEvent (guessed)"}, }); } + ~INx2SyncptSession() override { + for (auto& [slot, request] : m_requests) { + Cancel(request); + } + for (auto& [slot, event] : m_events) { + m_context.CloseEvent(event); + } + } + private: void GetCount(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) nx2:syncpt TIPC 1, returning 32"); TipcReplyWords(ctx, {32}); } + + void GetSlotEvent(HLERequestContext& ctx) { + const u32 slot = ctx.CommandBuffer()[2]; + LOG_WARNING(Service, "(STUBBED) nx2:syncpt TIPC 22 slot={:#x}, returning an event", slot); + auto*& event = m_events[slot]; + if (event == nullptr) { + event = m_context.CreateEvent(fmt::format("nx2:syncpt:Slot{:x}", slot)); + } + IPC::ResponseBuilder rb{ctx, 2, 1, 0}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, event->GetReadableEvent()); + } + + // Map nodes are stable, so a registered action can hold a reference to its request. + struct SlotRequest { + u32 syncpoint{}; + std::atomic<bool> pending{}; + Tegra::Host1x::SyncpointManager::ActionHandle handle{}; + }; + + void RequestSlotEvent(HLERequestContext& ctx) { + const auto* cmd_buf = ctx.CommandBuffer(); + const u32 slot = cmd_buf[2]; + const u32 syncpoint = ~cmd_buf[3]; + const u64 value = u64{cmd_buf[4]} | (u64{cmd_buf[5]} << 32); + const u32 mode = cmd_buf[6]; + LOG_INFO(Service, "nx2:syncpt TIPC 201 slot={:#x} syncpoint={} value={:#x} mode={}", slot, + syncpoint, value, mode); + auto*& event = m_events[slot]; + if (event == nullptr) { + event = m_context.CreateEvent(fmt::format("nx2:syncpt:Slot{:x}", slot)); + } + if (syncpoint >= HostSyncpoints) { + LOG_WARNING(Service, "nx2:syncpt TIPC 201 syncpoint {} out of range, signalling", + syncpoint); + event->Signal(system.Kernel()); + TipcReplyWords(ctx, {}); + return; + } + auto& request = m_requests[slot]; + Cancel(request); + request.syncpoint = syncpoint; + request.pending = true; + auto& manager = system.Host1x().GetSyncpointManager(); + // The action runs on the GPU thread under the manager's lock, or inline if the value is + // already reached. + request.handle = manager.RegisterGuestAction( + syncpoint, static_cast<u32>(value), [this, &request, event] { + request.pending = false; + event->Signal(system.Kernel()); + }); + TipcReplyWords(ctx, {}); + } + + void CancelSlotEvent(HLERequestContext& ctx) { + const u32 slot = ctx.CommandBuffer()[2]; + LOG_INFO(Service, "nx2:syncpt TIPC 202 slot={:#x}", slot); + if (auto it = m_requests.find(slot); it != m_requests.end()) { + Cancel(it->second); + } + TipcReplyWords(ctx, {}); + } + + void Cancel(SlotRequest& request) { + if (request.pending.exchange(false)) { + system.Host1x().GetSyncpointManager().DeregisterGuestAction(request.syncpoint, + request.handle); + } + } + + static constexpr u32 HostSyncpoints = 192; // Tegra::Host1x::SyncpointManager + + KernelHelpers::ServiceContext m_context; + std::map<u32, Kernel::KEvent*> m_events; + std::map<u32, SlotRequest> m_requests; }; // Placeholder for the Switch 2 GPU session (nn::gpu::sdk), handed out by gsv 497. @@ -186,8 +285,13 @@ private: // Sub 101: (start u64, last u64, flags...) after CreateGpuAccessibleMemory's svc 0x80. void SubRegisterMemory(HLERequestContext& ctx) { const auto* b = ctx.CommandBuffer(); - LOG_WARNING(Service, "(STUBBED) nx2:gpu sub 101 start={:#x} last={:#x} [6]={:#x} [7]={:#x}", - u64{b[2]} | (u64{b[3]} << 32), u64{b[4]} | (u64{b[5]} << 32), b[6], b[7]); + const u64 start = u64{b[2]} | (u64{b[3]} << 32); + const u64 last = u64{b[4]} | (u64{b[5]} << 32); + LOG_WARNING(Service, "nx2:gpu sub 101 start={:#x} last={:#x} [6]={:#x} [7]={:#x}", start, + last, b[6], b[7]); + if (last > start) { + Nvidia::Devices::Nx2GpuMemoryRegistry::Get().Register(start, last - start + 1); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -275,6 +379,17 @@ public: }); } + ~INx2GraphicsSystemSession() override { + if (m_syncpt_poll) { + system.CoreTiming().UnscheduleEvent(m_syncpt_poll); + } + for (auto* event : m_syncpt_events) { + if (event != nullptr) { + m_context.CloseEvent(event); + } + } + } + private: // Index of the first raw data word of a TIPC request: header (2 words), then an optional // special header with pid and copy/move handle words. @@ -302,42 +417,109 @@ private: // nn::syncpt::sdk::InitializeEx sends two transfer memories (0x1000 bytes RW, then 0x2000 // bytes the client maps read-only) and expects the server to have written a header into the - // second one: u32 version (must be 1) at +0, then u32s at +4 and +8 (a count; the client - // requires ((count + 0x40) >> 6) - 1 < 0x40) and five offsets at +0xc..+0x1c (into the RO, - // RW, RO, RW and RO regions). Reply: two handles (words 3 and 4), then the result. + // second one: u32 version (must be 1) at +0, u32 base id at +4, u32 count at +8 (the client + // requires ((count + 0x40) >> 6) - 1 < 0x40), then offsets of: the u64 syncpoint values (RO, + // +0xc), the RW "armed" summary word (+0x10), the RO summary word (+0x14), the RW armed bits + // (+0x18) and the RO acknowledge bits (+0x1c). + // To wait, the client makes its RW bit differ from the RO bit, signals the first returned + // event (it attaches that handle as writable), and waits on the second until the server makes + // the bits equal again; it then rereads the value and rearms if the threshold isn't reached. + // Reply: the two handles (words 3 and 4), then the result. + static constexpr u32 SyncptValuesOffset = 0x40; // RO, u64 per syncpoint + static constexpr u32 SyncptRoSummaryOffset = 0x1040; + static constexpr u32 SyncptRoBitsOffset = 0x1100; // RO, up to 0x40 u64 words + static constexpr u32 SyncptRwSummaryOffset = 0x0; + static constexpr u32 SyncptRwBitsOffset = 0x100; // RW, up to 0x40 u64 words + static constexpr u32 HostSyncpoints = 192; // Tegra::Host1x::SyncpointManager + void AttachSyncptMemory(HLERequestContext& ctx) { // RequestParser doesn't skip TIPC handle words, so read the raw data words. const auto* cmd_buf = ctx.CommandBuffer(); const size_t data = TipcDataOffset(cmd_buf); const u32 version_requested = cmd_buf[data]; - const u32 max_syncpoints = cmd_buf[data + 1]; + const u32 max_syncpoints = std::min<u32>(cmd_buf[data + 1], (0x1000 - 0x40) / 8); LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 204, version={} max_syncpoints={:#x}", version_requested, max_syncpoints); + auto rw = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0)); auto ro = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(1)); - if (ro.IsNotNull() && ro->GetSize() >= 0x2000) { - auto& memory = ro->GetOwner()->GetMemory(); - const u64 base = GetInteger(ro->GetSourceAddress()); - memory.Write32(base + 0x00, 1); // version - memory.Write32(base + 0x04, 0); // base id (guess) - memory.Write32(base + 0x08, max_syncpoints); // count - memory.Write32(base + 0x0c, 0x40); // -> RO region - memory.Write32(base + 0x10, 0x000); // -> RW region - memory.Write32(base + 0x14, 0x800); // -> RO region - memory.Write32(base + 0x18, 0x800); // -> RW region - memory.Write32(base + 0x1c, 0x1000); // -> RO region + if (rw.IsNotNull() && ro.IsNotNull() && ro->GetSize() >= 0x2000) { + m_syncpt_memory = &ro->GetOwner()->GetMemory(); + m_syncpt_rw = GetInteger(rw->GetSourceAddress()); + m_syncpt_ro = GetInteger(ro->GetSourceAddress()); + m_syncpt_count = max_syncpoints; + auto& memory = *m_syncpt_memory; + memory.Write32(m_syncpt_ro + 0x00, 1); // version + memory.Write32(m_syncpt_ro + 0x04, 0); // base id (guess) + memory.Write32(m_syncpt_ro + 0x08, max_syncpoints); + memory.Write32(m_syncpt_ro + 0x0c, SyncptValuesOffset); + memory.Write32(m_syncpt_ro + 0x10, SyncptRwSummaryOffset); + memory.Write32(m_syncpt_ro + 0x14, SyncptRoSummaryOffset); + memory.Write32(m_syncpt_ro + 0x18, SyncptRwBitsOffset); + memory.Write32(m_syncpt_ro + 0x1c, SyncptRoBitsOffset); + UpdateSyncptValues(); + if (!m_syncpt_poll) { + m_syncpt_poll = Core::Timing::CreateEvent( + "nx2:gsv:SyncptPoll", + [this](s64, std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> { + PollSyncpts(); + return std::nullopt; + }); + system.CoreTiming().ScheduleLoopingEvent(SyncptPollInterval, SyncptPollInterval, + m_syncpt_poll); + } } else { - LOG_ERROR(Service, "nx2:gsv TIPC 204: no read-only transfer memory"); + LOG_ERROR(Service, "nx2:gsv TIPC 204: missing syncpt transfer memory"); } if (m_syncpt_events[0] == nullptr) { - m_syncpt_events[0] = m_context.CreateEvent("nx2:gsv:SyncptEvent0"); - m_syncpt_events[1] = m_context.CreateEvent("nx2:gsv:SyncptEvent1"); + m_syncpt_events[0] = m_context.CreateEvent("nx2:gsv:SyncptRequest"); + m_syncpt_events[1] = m_context.CreateEvent("nx2:gsv:SyncptNotify"); } IPC::ResponseBuilder rb{ctx, 2, 2, 0}; rb.Push(ResultSuccess); - rb.PushCopyObjects(ctx, m_syncpt_events[0]->GetReadableEvent(), - m_syncpt_events[1]->GetReadableEvent()); + rb.PushCopyObjects(ctx, *m_syncpt_events[0]); // signalled by the client + rb.PushCopyObjects(ctx, m_syncpt_events[1]->GetReadableEvent()); + } + + // Mirrors Eden's guest syncpoint values into the shared RO page. Returns true if any changed. + bool UpdateSyncptValues() { + auto& manager = system.Host1x().GetSyncpointManager(); + bool changed = false; + for (u32 id = 0; id < std::min(m_syncpt_count, HostSyncpoints); ++id) { + const u64 value = manager.GetGuestSyncpointValue(id); + const u64 address = m_syncpt_ro + SyncptValuesOffset + id * 8; + if (m_syncpt_memory->Read64(address) != value) { + m_syncpt_memory->Write64(address, value); + changed = true; + } + } + return changed; + } + + // Answers every armed wait: the value page is current, so the client rechecks its threshold + // and rearms if it still has to wait. Acknowledging without a value change costs the client a + // wakeup per poll, which keeps waits whose syncpoint we never see from hanging. + void PollSyncpts() { + if (m_syncpt_memory == nullptr) { + return; + } + UpdateSyncptValues(); + bool acked = false; + const u32 words = (m_syncpt_count + 63) / 64; + for (u32 w = 0; w < words; ++w) { + const u64 rw_bits = m_syncpt_memory->Read64(m_syncpt_rw + SyncptRwBitsOffset + w * 8); + const u64 ro_address = m_syncpt_ro + SyncptRoBitsOffset + w * 8; + if (m_syncpt_memory->Read64(ro_address) != rw_bits) { + m_syncpt_memory->Write64(ro_address, rw_bits); + acked = true; + } + } + m_syncpt_memory->Write64(m_syncpt_ro + SyncptRoSummaryOffset, + m_syncpt_memory->Read64(m_syncpt_rw + SyncptRwSummaryOffset)); + if (acked) { + m_syncpt_events[1]->Signal(system.Kernel()); + } } void OpenViSession(HLERequestContext& ctx) { @@ -372,8 +554,15 @@ private: ReturnRawSession(system, ctx, std::move(nvsrv)); } + static constexpr std::chrono::nanoseconds SyncptPollInterval{std::chrono::microseconds{500}}; + KernelHelpers::ServiceContext m_context; std::array<Kernel::KEvent*, 2> m_syncpt_events{}; + Core::Memory::Memory* m_syncpt_memory{}; + u64 m_syncpt_rw{}; + u64 m_syncpt_ro{}; + u32 m_syncpt_count{}; + std::shared_ptr<Core::Timing::EventType> m_syncpt_poll; }; } // namespace Service diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 64d14ab2a6..312a6a09db 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -99,6 +99,7 @@ add_library(video_core STATIC invalidation_accumulator.h memory_manager.cpp memory_manager.h + nx2.h present.h pte_kind.h query_cache/bank_base.h diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index e7da9d9138..1a43133296 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -12,6 +12,7 @@ #include "common/settings.h" #include "core/core.h" #include "core/core_timing.h" +#include "video_core/nx2.h" #include "video_core/dirty_flags.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_3d.h" @@ -380,6 +381,12 @@ void Maxwell3D::CallMacroMethod(Core::System& system, u32 method, const std::vec const u32 entry = ((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size()); + // Switch 2 uploads Turing+ (MME64) macros, which Eden's Fermi-style macro engine can't run. + if (g_nx2_ampere) { + LOG_DEBUG(HW_GPU, "Switch 2 macro {} with {} parameters skipped", entry, parameters.size()); + return; + } + // Execute the current macro. macro_engine.Execute(system, *this, macro_positions[entry], parameters); draw_manager.DrawDeferred(*this); diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 0b05d31718..c9c1153bbf 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include <cstdlib> + #include "common/assert.h" #include "common/logging.h" #include "common/settings.h" @@ -18,6 +20,7 @@ #include "video_core/engines/puller.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" +#include "video_core/nx2.h" #include "video_core/rasterizer_interface.h" namespace Tegra::Engines { @@ -35,6 +38,27 @@ void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_c if ((engine & ~Gf100BindClassMask) != 0 && (engine & ~Gf100BindValidMask) == 0) { engine &= Gf100BindClassMask; } + // Switch 2 binds Ampere classes; run them on the Maxwell engines (same method layout for + // most state). Class 0 unbinds the subchannel. + switch (engine & 0xFFFF) { + case 0xC797: // AMPERE_B + engine = static_cast<u32>(EngineID::MAXWELL_B); + break; + case 0xC7C0: // AMPERE_COMPUTE_B + engine = static_cast<u32>(EngineID::KEPLER_COMPUTE_B); + break; + case 0xC7B5: // AMPERE_DMA_COPY_B + engine = static_cast<u32>(EngineID::MAXWELL_DMA_COPY_A); + break; + case 0x0: + if (g_nx2_ampere) { + LOG_DEBUG(HW_GPU, "Switch 2 unbinds subchannel {}", method_call.subchannel); + return; + } + break; + default: + break; + } const auto engine_id = static_cast<EngineID>(engine); bound_engines[method_call.subchannel] = engine_id; @@ -139,6 +163,11 @@ void Puller::ProcessSemaphoreAcquire(DmaPusher& dma_pusher) { /// Calls a GPU puller method. void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { + static const bool nx2_trace = std::getenv("EDEN_NX2_PULLER_TRACE") != nullptr; + if (nx2_trace) { + LOG_INFO(HW_GPU, "nx2 puller sub={} method={:#x} arg={:#x}", method_call.subchannel, + method_call.method, method_call.argument); + } regs.reg_array[method_call.method] = method_call.argument; const auto method = static_cast<BufferMethods>(method_call.method); diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h new file mode 100644 index 0000000000..b8258ca243 --- /dev/null +++ b/src/video_core/nx2.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <atomic> + +namespace Tegra { + +// Set once a Switch 2 (Ampere, NVN2) driver is talking to the GPU. Its command streams use +// Ampere classes and the Turing+ macro ISA, which Eden's Maxwell engines don't implement. +inline std::atomic<bool> g_nx2_ampere{false}; + +} // namespace Tegra |
