From f4a889a14c20d8b2b71d4154c0ba7d31acb5f6a0 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:52:40 -0400 Subject: kernel: optional SVC trace (EDEN_NX2_SVC_TRACE=1) --- src/core/hle/kernel/svc.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/core/hle/kernel/svc.cpp') diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 3542d5a3b6..d2e45fabb6 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -6,6 +6,7 @@ // This file is automatically generated using svc_generator.py. // DO NOT MODIFY IT MANUALLY +#include #include #include "core/arm/arm_interface.h" @@ -2505,11 +2506,21 @@ void Call(Core::System& system, u32 imm) { LOG_TRACE(Kernel_SVC, "{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x} [6]={:#x}", imm, GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3), GetArg64(args, 4), GetArg64(args, 5), GetArg64(args, 6)); + // Set EDEN_NX2_SVC_TRACE=1 to log every SVC with its arguments and results. + static const bool svc_trace = std::getenv("EDEN_NX2_SVC_TRACE") != nullptr; + if (svc_trace) { + LOG_INFO(Kernel_SVC, "svc {:#04x} in [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x}", imm, + GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); + } if (process.Is64Bit()) { Call64(system, imm, args); } else { Call32(system, imm, args); } + if (svc_trace) { + LOG_INFO(Kernel_SVC, "svc {:#04x} out [0]={:#x} [1]={:#x} [2]={:#x}", imm, + GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2)); + } //kernel.ExitSVCProfile(); kernel.CurrentPhysicalCore().LoadSvcArguments(process, args); } -- cgit v1.2.3 From 559dfe7bb24d95fbbc32fd571c7f53e13e42342c Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:17:39 -0400 Subject: Switch 2 GPU memory: svc 0x80, gpu sub-session jobs, VM range, SM 0x808 - svc 0x80 (address, size, mask 0x20, attr 0x20) marks memory GPU accessible for nn::gpu::sdk::CreateGpuAccessibleMemory; accept it. - gpu 22's sub-session is served by the gpu object; sub 101 registers memory, sub 211 submits a job whose id event is signalled at once. - gpu 203 reports [0x8000000, 0xC000000000) as the in-process VirtualMachine range that serves AllocSpace (NVN reserves fixed VA at 0x8000000 up to 512 GiB). - Report SM 0x808: NVN2's engine class table is only returned for it. With this NVN device init allocates its GPU memory and opens /dev/nvhost-tsg-gpu. --- src/core/hle/kernel/svc.cpp | 9 +++++- .../hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 4 +-- src/core/hle/service/nx2_gsv.h | 37 ++++++++++++++++++---- 3 files changed, 40 insertions(+), 10 deletions(-) (limited to 'src/core/hle/kernel/svc.cpp') diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index d2e45fabb6..0d4b2937c2 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -2512,7 +2512,14 @@ void Call(Core::System& system, u32 imm) { LOG_INFO(Kernel_SVC, "svc {:#04x} in [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x}", imm, GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); } - if (process.Is64Bit()) { + if (imm == 0x80) { + // Switch 2 only. nn::gpu::sdk::CreateGpuAccessibleMemory and nn::gs native memory call it + // as (address, size, mask = 0x20, attr = 0x20), like svcSetMemoryAttribute with a new + // "GPU accessible" attribute. Emulated guest memory is always GPU-visible: accept it. + LOG_WARNING(Kernel_SVC, "(STUBBED) Switch 2 svc 0x80 addr={:#x} size={:#x} mask={:#x} attr={:#x}", + GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); + args[0] = 0; + } else if (process.Is64Bit()) { Call64(system, imm, args); } else { Call32(system, imm, args); 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 07776d3434..db9ae85cf0 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -142,8 +142,8 @@ void ApplyNx2Characteristics(GC& gc) { gc.arch = 0x170; // Ampere gc.impl = 0xE; // chip id 0x17E as listed in the Switch 2 NvRm chip table gc.num_tpc_per_gpc = 0x6; - gc.sm_arch_sm_version = 0x807; // sm_87 (guess) - gc.sm_arch_spa_version = 0x807; + gc.sm_arch_sm_version = 0x808; // NVN2 only accepts SM 0x808 (its class table lookup) + gc.sm_arch_spa_version = 0x808; gc.threed_class = 0xC797; // AMPERE_B gc.compute_class = 0xC7C0; // AMPERE_COMPUTE_B gc.gpfifo_class = 0xC56F; // AMPERE_CHANNEL_GPFIFO_A diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index a7d9817dd2..e8f31d0556 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -112,6 +112,9 @@ public: {22, &INx2GpuSession::OpenSubSession, "OpenSubSession (guessed)"}, {33, &INx2GpuSession::GetCount33, "Unknown33 (u32 count)"}, {203, &INx2GpuSession::GetVaRange, "GetVaRange (guessed)"}, + // Sub-session commands (gpu 22 hands out a second session to this same object). + {101, &INx2GpuSession::SubRegisterMemory, "Sub101 RegisterMemory (guessed)"}, + {211, &INx2GpuSession::SubSubmitJob, "Sub211 SubmitJob (guessed)"}, }); } @@ -161,22 +164,42 @@ private: } // Used by nn::gpu::sdk::CreateGpuAddressSpace: three u64s after the result, a kind (< 5, 0 = - // none) and what looks like a GPU VA range. Report kind 1, [0x10000000, +64 GiB). + // none) and the GPU VA range of the in-process VirtualMachine that serves AllocSpace + // (nn::gpu::sdk::util::ExecuteVirtualMachineAllocSpace). NVN reserves fixed VA from 0x8000000 + // up to 512 GiB, so report the same range NvRm gives AllocAsEx: [0x8000000, 0xC000000000). void GetVaRange(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 203 [2]={:#x}", ctx.CommandBuffer()[2]); constexpr u64 kind = 1; - constexpr u64 start = 0x10000000ULL; - constexpr u64 size = 64ULL << 30; + constexpr u64 start = 0x8000000ULL; + constexpr u64 size = 0xC000000000ULL - start; TipcReplyWords(ctx, {static_cast(kind), static_cast(kind >> 32), static_cast(start), static_cast(start >> 32), static_cast(size), static_cast(size >> 32)}); } void OpenSubSession(HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 22, returning a nx2:gpu-sub session"); - ReturnRawSession(system, ctx, - std::make_shared( - system, INx2StubInterface::InternName("nx2:gpu-sub"))); + // Served by this object so sub-session jobs can signal the per-id events. + LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 22, returning a sub-session on nx2:gpu"); + ReturnRawSession(system, ctx, std::static_pointer_cast(shared_from_this())); + } + + // 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]); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + // Sub 211: job submission; word 2 is the job/event id the SDK then waits on. Nothing runs + // here, so complete it at once. + void SubSubmitJob(HLERequestContext& ctx) { + const u32 id = ctx.CommandBuffer()[2]; + LOG_WARNING(Service, "(STUBBED) nx2:gpu sub 211 id={}, signalling completion", id); + EventFor(id)->Signal(system.Kernel()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); } KernelHelpers::ServiceContext m_context; -- cgit v1.2.3 From 6c3a8b09811608cd13749decc4b144c9de0c5617 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:21:19 -0400 Subject: Switch 2: aud:u audio, REv revision tag, job-slot events, open-file limit - aud:u (nn::audio::CreateUserClientSessionByHipc) command 2 returns Eden's IAudioRendererManager; the renderer accepts Switch 2's 'REv?' update tag by comparing revision numbers. - nx2:gpu sub 211 signals the job slot in request word 17, not word 2; mapping jobs no longer stall once the SDK rotates slots. - eden-cli raises the CRT open-file limit (_setmaxstdio(8192)) like the Qt frontend, so loose RomFS folders don't run out of handles. - Per-mapping GPU logs moved to Debug. --- src/audio_core/renderer/behavior/info_updater.cpp | 6 ++- src/core/CMakeLists.txt | 1 + src/core/hle/kernel/svc.cpp | 2 +- src/core/hle/service/audio/audio.cpp | 3 ++ src/core/hle/service/audio/nx2_audio_user_client.h | 43 ++++++++++++++++++++++ .../hle/service/nvdrv/devices/nvhost_as_gpu.cpp | 2 +- src/core/hle/service/nx2_gsv.h | 15 +++++--- src/yuzu_cmd/yuzu.cpp | 4 ++ 8 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/core/hle/service/audio/nx2_audio_user_client.h (limited to 'src/core/hle/kernel/svc.cpp') diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index a88eae91ad..ca3eaece94 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -588,7 +588,11 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { return Service::Audio::ResultInvalidUpdateInfo; } - if (in_params->revision != behaviour_.GetUserRevision()) { + // Compare revision numbers: Switch 2 opens the renderer with 'REV?' but tags its updates + // 'REv?' (lowercase v) for the same revision. + if (GetRevisionNum(in_params->revision) != GetRevisionNum(behaviour_.GetUserRevision())) { + LOG_ERROR(Service_Audio, "BehaviorInfo revision {:08X} does not match the renderer's {:08X}", + in_params->revision, behaviour_.GetUserRevision()); return Service::Audio::ResultInvalidUpdateInfo; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6058ce82ce..56fa653738 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -527,6 +527,7 @@ add_library(core STATIC hle/service/audio/audio_out.h hle/service/audio/audio_renderer_manager.cpp hle/service/audio/audio_renderer_manager.h + hle/service/audio/nx2_audio_user_client.h hle/service/audio/audio_renderer.cpp hle/service/audio/audio_renderer.h hle/service/audio/audio.cpp diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 0d4b2937c2..13a6c74cb7 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -2516,7 +2516,7 @@ void Call(Core::System& system, u32 imm) { // Switch 2 only. nn::gpu::sdk::CreateGpuAccessibleMemory and nn::gs native memory call it // as (address, size, mask = 0x20, attr = 0x20), like svcSetMemoryAttribute with a new // "GPU accessible" attribute. Emulated guest memory is always GPU-visible: accept it. - LOG_WARNING(Kernel_SVC, "(STUBBED) Switch 2 svc 0x80 addr={:#x} size={:#x} mask={:#x} attr={:#x}", + LOG_DEBUG(Kernel_SVC, "(STUBBED) Switch 2 svc 0x80 addr={:#x} size={:#x} mask={:#x} attr={:#x}", GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); args[0] = 0; } else if (process.Is64Bit()) { diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index fde766a275..211e1cb6bc 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -6,6 +6,7 @@ #include "core/core.h" #include "core/hle/service/audio/audio.h" +#include "core/hle/service/audio/nx2_audio_user_client.h" #include "core/hle/service/audio/audio_controller.h" #include "core/hle/service/audio/audio_in_manager.h" #include "core/hle/service/audio/audio_out_manager.h" @@ -203,6 +204,8 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService("audrec:a", std::make_shared(system), 30); server_manager->RegisterNamedService("audrec:u", std::make_shared(system), 30); server_manager->RegisterNamedService("audren:u", std::make_shared(system), 30); + // Switch 2 audio entry point (nn::audio::CreateUserClientSessionByHipc). + server_manager->RegisterNamedService("aud:u", std::make_shared(system), 30); server_manager->RegisterNamedService("audren:a", std::make_shared(system), 30); server_manager->RegisterNamedService("hwopus", std::make_shared(system), 25); ServerManager::RunServer(std::move(server_manager)); diff --git a/src/core/hle/service/audio/nx2_audio_user_client.h b/src/core/hle/service/audio/nx2_audio_user_client.h new file mode 100644 index 0000000000..2d1a158c84 --- /dev/null +++ b/src/core/hle/service/audio/nx2_audio_user_client.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/audio/audio_in_manager.h" +#include "core/hle/service/audio/audio_out_manager.h" +#include "core/hle/service/audio/audio_renderer_manager.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/service.h" + +namespace Service::Audio { + +// Switch 2 "aud:u" (nn::audio::CreateUserClientSessionByHipc). The SDK reaches the Switch 1 +// audio interfaces through it: command 2 returns an object used exactly like +// IAudioRendererManager (vtable slot 1 = GetWorkBufferSize with a Switch 1 parameter block), so +// it is backed by Eden's audren:u implementation. The nx2: name keeps unknown commands +// auto-stubbed and logged. +class INx2AudioUserClient final : public ServiceFramework { +public: + explicit INx2AudioUserClient(Core::System& system_) + : ServiceFramework{system_, "nx2:aud:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {2, &INx2AudioUserClient::GetAudioRendererManager, "GetAudioRendererManager (guessed)"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void GetAudioRendererManager(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 arg = rp.Pop(); + LOG_WARNING(Service_Audio, "(STUBBED) Switch 2 aud:u 2 arg={}, returning IAudioRendererManager", + arg); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, system); + } +}; + +} // namespace Service::Audio 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 f309949c5f..9543d09d26 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -127,7 +127,7 @@ void nvhost_as_gpu::Nx2MapIdentity(u64 address, u64 size) { 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, + LOG_DEBUG(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x} mapped at the same GPU address", address, size); } void nvhost_as_gpu::OnClose(DeviceFD fd) {} diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 9d1a4cdc93..a86f85ea0e 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -289,7 +289,7 @@ private: const auto* b = ctx.CommandBuffer(); 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, + LOG_DEBUG(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); @@ -306,12 +306,15 @@ private: rb.Push(ResultSuccess); } - // Sub 211: job submission; word 2 is the job/event id the SDK then waits on. Nothing runs - // here, so complete it at once. + // Sub 211: job submission (nn::gpu::sdk job tracking). Word 2 is a constant id; word 17 is + // the job slot whose event (gpu TIPC 12) the SDK then waits on. Nothing runs here, so + // complete it at once. void SubSubmitJob(HLERequestContext& ctx) { - const u32 id = ctx.CommandBuffer()[2]; - LOG_WARNING(Service, "(STUBBED) nx2:gpu sub 211 id={}, signalling completion", id); - EventFor(id)->Signal(system.Kernel()); + const auto* cmd_buf = ctx.CommandBuffer(); + const u32 slot = cmd_buf[17]; + LOG_DEBUG(Service, "(STUBBED) nx2:gpu sub 211 [2]={} slot={}, signalling completion", + cmd_buf[2], slot); + EventFor(slot)->Signal(system.Kernel()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 088141a251..3aaba78fd1 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -173,6 +174,9 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { freopen("CONOUT$", "wb", stdout); freopen("CONOUT$", "wb", stderr); } + // Raise the CRT's open-file limit like the Qt frontend: loose RomFS folders keep thousands of + // files open through the VFS cache. + _setmaxstdio(8192); #endif Common::Log::Initialize(); -- cgit v1.2.3 From f32b430326af86bd956f3d49d1bf7b6a1b9c5e31 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:30:20 -0400 Subject: Switch 2: native memory/images, GPU-accessible attribute, TSG subcontexts, syncpoint shim - gsv 111/112/114/423/429/430/314/315/435 track native memory and images with real ids (meanings from changes.patch); 1510/435 use its block-linear layout. - svc 0x80 sets a real GpuAccessible (0x20) memory attribute (from changes.patch). - Characteristics report TSG subcontexts (flag 22, 64 per TSG at 0xb4); NvRm otherwise allows one subcontext and a second NVN queue fails its setup. - Syncpoint shim: flag 26, address-space ioctl 0xB, and GPU reads of the shim page return guest syncpoint values (scheme from changes.patch). - EDEN_NX2_IPC_TRACE also logs TIPC commands; gdbrsp.py watch/mem improvements. --- src/core/hle/kernel/k_memory_block.h | 4 +- src/core/hle/kernel/k_page_table_base.cpp | 3 +- src/core/hle/kernel/svc.cpp | 33 +++- src/core/hle/kernel/svc_types.h | 1 + .../hle/service/nvdrv/devices/nvhost_as_gpu.cpp | 26 +++ src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h | 9 ++ .../hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 29 +++- src/core/hle/service/nx2_gsv.h | 180 +++++++++++++++++++-- src/core/hle/service/service.cpp | 6 + src/video_core/memory_manager.cpp | 13 ++ src/video_core/memory_manager.h | 5 + tools/nx2/gdbrsp.py | 20 ++- 12 files changed, 301 insertions(+), 28 deletions(-) (limited to 'src/core/hle/kernel/svc.cpp') diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h index f6543bdb3a..2abca31d87 100644 --- a/src/core/hle/kernel/k_memory_block.h +++ b/src/core/hle/kernel/k_memory_block.h @@ -195,7 +195,9 @@ enum class KMemoryAttribute : u8 { Uncached = static_cast(Svc::MemoryAttribute::Uncached), PermissionLocked = static_cast(Svc::MemoryAttribute::PermissionLocked), - SetMask = Uncached | PermissionLocked, + GpuAccessible = static_cast(Svc::MemoryAttribute::GpuAccessible), + + SetMask = Uncached | PermissionLocked | GpuAccessible, }; DECLARE_ENUM_FLAG_OPERATORS(KMemoryAttribute); diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index 2a0a7d49d5..fd5d2c2dce 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -2102,7 +2102,8 @@ Result KPageTableBase::SetMemoryAttribute(KProcessAddress addr, size_t size, KMe constexpr KMemoryAttribute AttributeTestMask = ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared); const KMemoryState state_test_mask = - (True(mask & KMemoryAttribute::Uncached) ? KMemoryState::FlagCanChangeAttribute + (True(mask & (KMemoryAttribute::Uncached | KMemoryAttribute::GpuAccessible)) + ? KMemoryState::FlagCanChangeAttribute : KMemoryState::None) | (True(mask & KMemoryAttribute::PermissionLocked) ? KMemoryState::FlagCanPermissionLock : KMemoryState::None); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 13a6c74cb7..e458a5057d 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -2513,12 +2513,33 @@ void Call(Core::System& system, u32 imm) { GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); } if (imm == 0x80) { - // Switch 2 only. nn::gpu::sdk::CreateGpuAccessibleMemory and nn::gs native memory call it - // as (address, size, mask = 0x20, attr = 0x20), like svcSetMemoryAttribute with a new - // "GPU accessible" attribute. Emulated guest memory is always GPU-visible: accept it. - LOG_DEBUG(Kernel_SVC, "(STUBBED) Switch 2 svc 0x80 addr={:#x} size={:#x} mask={:#x} attr={:#x}", - GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); - args[0] = 0; + // Switch 2 only: svcSetGpuMemoryAttribute(address, size, mask, attr). nn::gpu::sdk and + // nn::gs native memory set the new "GPU accessible" attribute (0x20) before registering + // memory with the GPU and clear it on destruction; NVN can see it through QueryMemory. + // Same checks as svcSetMemoryAttribute, restricted to that bit (as in changes.patch). + const u64 address = GetArg64(args, 0); + const u64 size = GetArg64(args, 1); + const u32 mask = static_cast(GetArg64(args, 2)); + const u32 attr = static_cast(GetArg64(args, 3)); + constexpr u32 Supported = static_cast(Svc::MemoryAttribute::GpuAccessible); + auto& page_table = process.GetPageTable(); + Result result = ResultSuccess; + if (!Common::IsAligned(address, PageSize)) { + result = ResultInvalidAddress; + } else if (size == 0 || !Common::IsAligned(size, PageSize)) { + result = ResultInvalidSize; + } else if ((attr & ~mask) != 0 || ((mask | attr) & ~Supported) != 0) { + result = ResultInvalidCombination; + } else if (address + size <= address || !page_table.Contains(address, size)) { + result = ResultInvalidCurrentMemory; + } else { + result = page_table.SetMemoryAttribute(address, size, + static_cast(mask), + static_cast(attr)); + } + LOG_DEBUG(Kernel_SVC, "Switch 2 svc 0x80 addr={:#x} size={:#x} mask={:#x} attr={:#x} -> {:#x}", + address, size, mask, attr, result.raw); + args[0] = result.raw; } else if (process.Is64Bit()) { Call64(system, imm, args); } else { diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 4d6dbb91f1..0bf2aaaa2d 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -50,6 +50,7 @@ enum class MemoryAttribute : u32 { DeviceShared = (1 << 2), Uncached = (1 << 3), PermissionLocked = (1 << 4), + GpuAccessible = (1 << 5), // Switch 2 (svc 0x80) }; DECLARE_ENUM_FLAG_OPERATORS(MemoryAttribute); 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 6d9fd4bd36..e2e197f0dd 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -64,6 +64,11 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span i } return result; } + case 0xb: + if (g_nx2_driver) { + return WrapFixed(this, &nvhost_as_gpu::Nx2GetSyncpointShimInfo, input, output); + } + break; case 0x14: return WrapVariable(this, &nvhost_as_gpu::Remap, input, output); default: @@ -139,6 +144,27 @@ void nvhost_as_gpu::Nx2Map(u64 gpu_address, u64 cpu_address, u64 size) { } void nvhost_as_gpu::OnClose(DeviceFD fd) {} +// Switch 2 NvRm (with characteristics flag 26) asks for the read-only syncpoint shim: a GPU page +// whose u32 at id * stride reads as that syncpoint's value; NVN waits on syncpoints through it and +// asserts without one. Same scheme as changes.patch, placed on the last page of the VA range. +NvResult nvhost_as_gpu::Nx2GetSyncpointShimInfo(IoctlNx2SyncpointShimInfo& params) { + std::scoped_lock lock(mutex); + if (!vm.initialised || !gmmu) { + return NvResult::NotSupported; + } + const u64 address = vm.va_range_end - VM::YUZU_PAGESIZE; + if (!nx2_syncpoint_shim) { + nx2_syncpoint_shim = true; + gmmu->SetNativeSyncpointShim(address); + } + params.address = address; + params.stride = sizeof(u32); + params.count = Tegra::MemoryManager::NumShimSyncpoints; + LOG_INFO(Service_NVDRV, "Switch 2 syncpoint shim at {:#x} ({} syncpoints)", address, + params.count); + return NvResult::Success; +} + NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { LOG_DEBUG(Service_NVDRV, "called, flags={:#x} big_page_size={:#x} va_range_start={:#x} va_range_end={:#x} va_range_split={:#x}", params.flags, params.big_page_size, params.va_range_start, params.va_range_end, params.va_range_split); 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 e9bd9daf73..ca94dc2632 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -222,6 +222,15 @@ private: } vm; std::unique_ptr gmmu; + struct IoctlNx2SyncpointShimInfo { + u64_le address; + u32_le stride; + u32_le count; + }; + static_assert(sizeof(IoctlNx2SyncpointShimInfo) == 16); + NvResult Nx2GetSyncpointShimInfo(IoctlNx2SyncpointShimInfo& params); + bool nx2_syncpoint_shim{}; + // Switch 2: maps GPU-accessible process memory (CPU range) at a GPU address. void Nx2Map(u64 gpu_address, u64 cpu_address, u64 size); NvCore::SessionId nx2_session{}; 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 53c7f85517..df2421cd13 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -19,6 +19,10 @@ namespace Service::Nvidia::Devices { +namespace { +void ApplyNx2CharacteristicsTail(std::span block); +} // namespace + nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_) : nvdevice{system_}, events_interface{events_interface_} { error_notifier_event = events_interface.CreateEvent("CtrlGpuErrorNotifier"); @@ -97,9 +101,14 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span switch (command.group) { case 'G': switch (command.cmd) { - case 0x5: - return WrapFixedInlOut(this, &nvhost_ctrl_gpu::GetCharacteristics3, input, output, - inline_output); + case 0x5: { + const auto result = WrapFixedInlOut(this, &nvhost_ctrl_gpu::GetCharacteristics3, + input, output, inline_output); + if (nx2_caller) { + ApplyNx2CharacteristicsTail(inline_output); + } + return result; + } case 0x6: return WrapFixedInlOut(this, &nvhost_ctrl_gpu::GetTPCMasks3, input, output, inline_output); @@ -191,6 +200,8 @@ void ApplyNx2Characteristics(GC& gc) { gc.dma_copy_class = 0xC7B5; // AMPERE_DMA_COPY_B gc.chipname = 0x6631306167; // "ga10f" gc.flags |= 0x100; // NVGPU_GPU_FLAGS_SUPPORT_TSG: NvRm binds channels to a TSG + gc.flags |= 1ULL << 22; // NVGPU_GPU_FLAGS_SUPPORT_TSG_SUBCONTEXTS (see below) + gc.flags |= 1ULL << 26; // NVGPU_GPU_FLAGS_SUPPORT_SYNCPOINT_ADDRESS: syncpoint shim // The Switch 2 layout keeps nine s16 interface versions at 0x70..0x80 (like nvgpu's // *_ioctl_nr_last); its NvRm requires e.g. [0x78] >= 6 and enables features above 0xa, 0x1f @@ -202,6 +213,18 @@ void ApplyNx2Characteristics(GC& gc) { } } +// Fields of the Switch 2 block past the Switch 1 layout. NvRm sizes each TSG's subcontext (VEID) +// allocator from the u32 at 0xb4, but only when the TSG interface version (0x72) is above 0xa and +// flags bits 8 and 22 are set; otherwise every TSG gets one subcontext and a second queue's +// NvRmGpuTSGSubcontextCreate fails (0x11), leaving that NVN queue without command memory. +void ApplyNx2CharacteristicsTail(std::span block) { + constexpr size_t MaxSubcontextsOffset = 0xb4; + constexpr u32 MaxSubcontexts = 64; // Ampere VEIDs per TSG + if (block.size() >= MaxSubcontextsOffset + sizeof(MaxSubcontexts)) { + std::memcpy(block.data() + MaxSubcontextsOffset, &MaxSubcontexts, sizeof(MaxSubcontexts)); + } +} + } // namespace NvResult nvhost_ctrl_gpu::GetCharacteristics1(IoctlCharacteristics& params) { diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index ff1fe219e8..c409c39ff8 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -437,6 +438,60 @@ private: std::vector m_events; }; +// Native memory and images handed out by the Switch 2 graphics server (gsv 111/114/423/429/315), +// shared with the vi2 session so presents can find an image's memory. Command meanings follow +// the shared changes.patch (IGraphicsSupervisor), re-checked against title 4's SDK. +struct Nx2NativeObjects { + struct Memory { + u64 size{}; + u64 address{}; // CPU (== GPU) address once bound by 114/423 + }; + struct Image { + u32 memory{}; + std::array preference{}; + u64 size{}; + bool window_pool{}; + }; + std::mutex mutex; + std::map memories; + std::map images; + u32 next_memory{1}; + u32 next_image{1}; +}; + +// Block-linear layout of an InterModuleUserImagePreference (16 words): word 2 holds +// (width - 1) | (height - 1) << 16, word 4 the block height (log2 of GOB rows), word 5 the row +// pitch in 64-byte GOBs. Formula from changes.patch (native_image_layout.h); preferences outside +// it fall back to 4 bytes per pixel and 128-row blocks. +struct Nx2ImageLayout { + u32 width; + u32 height; + u64 pitch; + u32 block_height_log2; + u64 size; + u64 alignment; +}; + +inline Nx2ImageLayout Nx2CalculateImageLayout(const u32* preference) { + Nx2ImageLayout layout{}; + layout.width = (preference[2] & 0xFFFF) + 1; + layout.height = (preference[2] >> 16) + 1; + const u64 pitch_gobs = preference[5]; + const u32 block_height_log2 = preference[4]; + if (block_height_log2 <= 8 && pitch_gobs >= (u64{layout.width} * 4 + 63) / 64 && + pitch_gobs <= 4096) { + layout.pitch = pitch_gobs * 64; + layout.block_height_log2 = block_height_log2; + } else { + layout.pitch = Common::AlignUp(u64{layout.width} * 4, 64); + layout.block_height_log2 = 7; + } + const u64 block_rows = u64{1} << layout.block_height_log2; + layout.size = layout.pitch * Common::AlignUp(u64{layout.height}, block_rows); + layout.alignment = std::max(64 * block_rows, 0x1000); + return layout; +} + // Placeholder for the Switch 2 graphics-system server that nn::gsv::BootWithRemoteMemory talks // to over TIPC. Nothing behind it is emulated: commands succeed and are logged, so boot can show // what comes next. Commands 202 and 1402 hand out the syncpt and nvsrv sessions the SDK uses. @@ -452,6 +507,15 @@ public: {204, &INx2GraphicsSystemSession::AttachSyncptMemory, "AttachSyncptMemory (guessed)"}, {305, &INx2GraphicsSystemSession::OpenViSession, "OpenViSession (guessed)"}, {497, &INx2GraphicsSystemSession::OpenGpuSession, "OpenGpuSession (guessed)"}, + {111, &INx2GraphicsSystemSession::CreateNativeMemory, "CreateNativeMemory"}, + {112, &INx2GraphicsSystemSession::DestroyNativeMemory, "DestroyNativeMemory"}, + {114, &INx2GraphicsSystemSession::BindNativeMemory, "BindNativeMemory"}, + {314, &INx2GraphicsSystemSession::ConfigureWindowImagePool, "DestroyNativeWindowAliasImagePool"}, + {315, &INx2GraphicsSystemSession::ConfigureWindowImagePool, "CreateNativeWindowAliasImagePool"}, + {423, &INx2GraphicsSystemSession::BindNativeGpuMemory, "BindNativeGpuMemory"}, + {429, &INx2GraphicsSystemSession::CreateNativeImage, "CreateNativeImage"}, + {430, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyNativeImage"}, + {435, &INx2GraphicsSystemSession::CalculateNativeImageSize, "CalculateNativeImageSize"}, {1402, &INx2GraphicsSystemSession::OpenNvsrvSession, "OpenNvsrvSession (guessed)"}, {1510, &INx2GraphicsSystemSession::CalculateRequirements, "CalculateRequirements"}, }); @@ -607,21 +671,106 @@ private: // size, u64 alignment; the client raises the alignment to at least 4 KiB and rounds the size. void CalculateRequirements(HLERequestContext& ctx) { const auto* w = ctx.CommandBuffer(); - const u32 width = (w[4] & 0xFFFF) + 1; - const u32 height = (w[4] >> 16) + 1; - const u32 pixel_format = w[8] & 0xFF; - // Pixel format numbering is unknown; 4 bytes per pixel. Oversizing (8 for format 4) left - // the game's 1.5 GiB heap short of its 300 MiB sound heap. - const u64 bytes_per_pixel = 4; - const u64 pitch = Common::AlignUp(width * bytes_per_pixel, 64); - const u64 size = pitch * Common::AlignUp(height, 128); - constexpr u64 alignment = 0x10000; - LOG_WARNING(Service, - "(STUBBED) nx2:gsv TIPC 1510 kind={} {}x{} [6]={:#x} [7]={:#x} format={} " - "[9]={:#x} [18]={:#x} -> size={:#x}", - w[3], width, height, w[6], w[7], pixel_format, w[9], w[18], size); - TipcReplyWords(ctx, {static_cast(size), static_cast(size >> 32), - static_cast(alignment), 0}); + const auto layout = Nx2CalculateImageLayout(w + 2); + LOG_DEBUG(Service, "nx2:gsv 1510 kind={} {}x{} block={} pitch={:#x} format={} -> size={:#x} align={:#x}", + w[3], layout.width, layout.height, layout.block_height_log2, layout.pitch, w[8], + layout.size, layout.alignment); + TipcReplyWords(ctx, {static_cast(layout.size), static_cast(layout.size >> 32), + static_cast(layout.alignment), + static_cast(layout.alignment >> 32)}); + } + + // 435(preference[16], u32 count): result, u64 size of a GPU native image. + void CalculateNativeImageSize(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + const u64 size = Nx2CalculateImageLayout(w + 2).size * std::max(w[18], 1); + LOG_DEBUG(Service, "nx2:gsv 435 count={} -> size={:#x}", w[18], size); + TipcReplyWords(ctx, {static_cast(size), static_cast(size >> 32)}); + } + + static u64 Pair(const u32* w, size_t i) { + return u64{w[i]} | (u64{w[i + 1]} << 32); + } + + // 111(u8 mode at word 2, u64 size): result, u32 native memory id. + void CreateNativeMemory(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + const u64 size = Pair(w, 3); + std::scoped_lock lk{m_objects->mutex}; + const u32 id = m_objects->next_memory++; + m_objects->memories[id] = {size, 0}; + LOG_INFO(Service, "nx2:gsv 111 CreateNativeMemory mode={} size={:#x} -> id {}", w[2] & 0xFF, + size, id); + TipcReplyWords(ctx, {id}); + } + + void DestroyNativeMemory(HLERequestContext& ctx) { + const u32 id = ctx.CommandBuffer()[2]; + std::scoped_lock lk{m_objects->mutex}; + m_objects->memories.erase(id); + LOG_DEBUG(Service, "nx2:gsv 112 DestroyNativeMemory id {}", id); + TipcReplyWords(ctx, {}); + } + + // 114(u32 id, u64 address, u64 size, u64 flags, u64 offset): back native memory with process + // memory. + void BindNativeMemory(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + BindMemory(w[2], Pair(w, 3), Pair(w, 5), "114"); + TipcReplyWords(ctx, {}); + } + + // 423(u32 id, u64 address, u64 size, u32 parent GpuMemoryHandle, u64 offset, ...): back + // native memory with part of GPU-accessible memory. + void BindNativeGpuMemory(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + LOG_DEBUG(Service, "nx2:gsv 423 parent={} offset={:#x}", w[7], Pair(w, 8)); + BindMemory(w[2], Pair(w, 3), Pair(w, 5), "423"); + TipcReplyWords(ctx, {}); + } + + void BindMemory(u32 id, u64 address, u64 size, const char* command) { + std::scoped_lock lk{m_objects->mutex}; + auto& memory = m_objects->memories[id]; + memory.address = address; + if (memory.size == 0) { + memory.size = size; + } + LOG_INFO(Service, "nx2:gsv {} bind native memory {} at {:#x}+{:#x}", command, id, address, + size); + } + + // 429(preference[16], u32 count, u32 memory id): result, u32 native image id. + void CreateNativeImage(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + std::scoped_lock lk{m_objects->mutex}; + const u32 id = m_objects->next_image++; + auto& image = m_objects->images[id]; + image.memory = w[19]; + std::copy_n(w + 2, image.preference.size(), image.preference.begin()); + image.size = Nx2CalculateImageLayout(w + 2).size; + LOG_INFO(Service, "nx2:gsv 429 CreateNativeImage memory={} count={} size={:#x} -> id {}", + w[19], w[18], image.size, id); + TipcReplyWords(ctx, {id}); + } + + void DestroyNativeImage(HLERequestContext& ctx) { + const u32 id = ctx.CommandBuffer()[2]; + std::scoped_lock lk{m_objects->mutex}; + m_objects->images.erase(id); + TipcReplyWords(ctx, {}); + } + + // 315(u32 image, u32 count) / 314(u32 image): add/remove a window alias image pool. + void ConfigureWindowImagePool(HLERequestContext& ctx) { + const u32 id = ctx.CommandBuffer()[2]; + const bool create = ctx.GetCommand() == 315; + std::scoped_lock lk{m_objects->mutex}; + if (auto it = m_objects->images.find(id); it != m_objects->images.end()) { + it->second.window_pool = create; + } + LOG_INFO(Service, "nx2:gsv {} window image pool image {}", ctx.GetCommand(), id); + TipcReplyWords(ctx, {}); } void OpenViSession(HLERequestContext& ctx) { @@ -658,6 +807,7 @@ private: static constexpr std::chrono::nanoseconds SyncptPollInterval{std::chrono::microseconds{500}}; + std::shared_ptr m_objects{std::make_shared()}; KernelHelpers::ServiceContext m_context; std::array m_syncpt_events{}; Core::Memory::Memory* m_syncpt_memory{}; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 9da9b3d8b9..931de1c3ef 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -116,6 +116,12 @@ void ServiceFrameworkBase::InvokeRequestTipc(HLERequestContext& ctx) { return ReportUnimplementedFunction(ctx, info); LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer())); + static const bool ipc_trace = std::getenv("EDEN_NX2_IPC_TRACE") != nullptr; + if (ipc_trace) { + const auto* w = ctx.CommandBuffer(); + LOG_INFO(Service, "tipc {}::{} ({}) [2]={:#x} [3]={:#x} [4]={:#x}", service_name, + ctx.GetCommand(), info->name, w[2], w[3], w[4]); + } handler_invoker(this, info->handler_callback, ctx); } diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 8ffdcb6276..0290500e7c 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -56,6 +56,10 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, GPU : MemoryManager(system_, system_.Host1x().MemoryManager(), address_space_bits_, split_address_, big_page_bits_, page_bits_) {} +void MemoryManager::SetNativeSyncpointShim(GPUVAddr address) { + native_syncpoint_shim.store(address, std::memory_order_release); +} + MemoryManager::~MemoryManager() = default; MemoryManager::EntryType MemoryManager::GetEntry(size_t position, bool is_big_page) const { @@ -231,6 +235,15 @@ std::optional MemoryManager::GpuToCpuAddress(GPUVAddr addr, std::size_t s template T MemoryManager::Read(GPUVAddr addr) const { + if constexpr (sizeof(T) == sizeof(u32)) { + const auto shim = native_syncpoint_shim.load(std::memory_order_acquire); + if (shim && addr >= shim && addr - shim < NumShimSyncpoints * sizeof(u32) && + Common::IsAligned(addr - shim, sizeof(u32))) { + return static_cast(system.Host1x().GetSyncpointManager().GetGuestSyncpointValue( + static_cast((addr - shim) / sizeof(u32)))); + } + } + if (auto page_pointer{GetPointer(addr)}; page_pointer) { // NOTE: Avoid adding any extra logic to this fast-path block T value; diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 08978567d2..1aaf2c1086 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -43,6 +43,10 @@ public: u64 big_page_bits_ = 16, u64 page_bits_ = 12); ~MemoryManager(); + // Switch 2: GPU address of the read-only syncpoint shim (u32 per syncpoint); 0 = none. + void SetNativeSyncpointShim(GPUVAddr address); + static constexpr u32 NumShimSyncpoints = 192; // Tegra::Host1x::SyncpointManager + static constexpr bool HAS_FLUSH_INVALIDATION = true; inline size_t GetID() const noexcept { @@ -178,6 +182,7 @@ private: GPUVAddr gpu_addr, std::size_t size, boost::container::small_vector, std::size_t>, 32>& result) const; + std::atomic native_syncpoint_shim{}; Core::System& system; MaxwellDeviceMemoryManager& memory; diff --git a/tools/nx2/gdbrsp.py b/tools/nx2/gdbrsp.py index d7c8a16460..51968254fa 100644 --- a/tools/nx2/gdbrsp.py +++ b/tools/nx2/gdbrsp.py @@ -153,7 +153,8 @@ def main(): regs = rsp.regs() print(" ".join(f"{k}={v:#x}" for k, v in regs.items())) elif op == "mem": - a = regs[parts[1]] if parts[1] in regs else int(parts[1], 16) + base, _, off = parts[1].partition("+") + a = (regs[base] if base in regs else int(base, 16)) + (int(off, 16) if off else 0) data = rsp.mem(a, int(parts[2], 0)) for i in range(0, len(data), 16): words = [struct.unpack_from(" Date: Wed, 28 Oct 2026 16:44:24 -0400 Subject: Performance diagnostics and small wins for title 1 - eden-cli: SDL called SDL_AppIterate back to back, keeping a host core at 100% polling input; run the main callbacks at 240 Hz. - EDEN_NX2_IDLE_SPIN_US: optional spin before an idle core blocks (measured: no gain on title 1, so off by default). - EDEN_NX2_SVC_TRACE=:: trace SVCs (with thread ids) for a range of presents only. - MME DMA reads flush GPU-written memory first (never triggered on title 1, kept for correctness); render-enable override histogram instead of the first-400 log. Measured on title 1's title screen (RTX 4070 Laptop): GPU ~20% busy, the six emulated cores ~0.9 host cores in total, Eden's GPU thread 45-70% busy and largely waiting: on host fences for semaphore acquires (GPU accuracy High delays every release to host completion), on ASTC upload Finish(), and in a 1 ms poll sleep. gpu_accuracy=Low: ~12.5 -> ~16 fps. --- src/core/hle/kernel/physical_core.cpp | 19 ++++++++++++++++++- src/core/hle/kernel/physical_core.h | 3 ++- src/core/hle/kernel/svc.cpp | 22 +++++++++++++++++----- src/video_core/macro.cpp | 23 +++++++++++++++++++++-- src/yuzu_cmd/yuzu.cpp | 3 +++ 5 files changed, 61 insertions(+), 9 deletions(-) (limited to 'src/core/hle/kernel/svc.cpp') diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index c5236c64e3..c3ae7629aa 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -4,6 +4,9 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include #include "common/scope_exit.h" #include "common/settings.h" #include "core/core.h" @@ -222,8 +225,22 @@ void PhysicalCore::LogBacktrace(KernelCore& kernel) { } void PhysicalCore::Idle() { + // Guest job systems hand work between cores many times per frame; blocking at once adds the + // host's thread wake-up latency to every handoff. EDEN_NX2_IDLE_SPIN_US=: poll for that + // many microseconds before blocking. + static const auto spin = std::chrono::microseconds{[] { + const char* text = std::getenv("EDEN_NX2_IDLE_SPIN_US"); + return text ? std::strtoll(text, nullptr, 10) : 0LL; + }()}; + if (spin.count() > 0) { + const auto deadline = std::chrono::steady_clock::now() + spin; + while (!m_is_interrupted.load(std::memory_order_acquire) && + std::chrono::steady_clock::now() < deadline) { + std::this_thread::yield(); + } + } std::unique_lock lk{m_guard}; - m_on_interrupt.wait(lk, [this] { return m_is_interrupted; }); + m_on_interrupt.wait(lk, [this] { return m_is_interrupted.load(); }); } bool PhysicalCore::IsInterrupted() const { diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h index b92201204a..20371d6717 100644 --- a/src/core/hle/kernel/physical_core.h +++ b/src/core/hle/kernel/physical_core.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -71,7 +72,7 @@ private: std::condition_variable m_on_interrupt; Core::ArmInterface* m_arm_interface{}; KThread* m_current_thread{}; - bool m_is_interrupted{}; + std::atomic m_is_interrupted{}; bool m_is_single_core{}; }; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index e458a5057d..53d964ecbc 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -7,6 +7,7 @@ // DO NOT MODIFY IT MANUALLY #include +#include #include #include "core/arm/arm_interface.h" @@ -2506,10 +2507,20 @@ void Call(Core::System& system, u32 imm) { LOG_TRACE(Kernel_SVC, "{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x} [6]={:#x}", imm, GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3), GetArg64(args, 4), GetArg64(args, 5), GetArg64(args, 6)); - // Set EDEN_NX2_SVC_TRACE=1 to log every SVC with its arguments and results. - static const bool svc_trace = std::getenv("EDEN_NX2_SVC_TRACE") != nullptr; + // Set EDEN_NX2_SVC_TRACE=1 to log every SVC with its arguments and results, or + // EDEN_NX2_SVC_TRACE=: for presents first..last only (a frame-time profile). + static const std::pair trace_range = [] { + const char* text = std::getenv("EDEN_NX2_SVC_TRACE"); + if (text == nullptr) return std::pair{1, 0}; + const char* colon = std::strchr(text, ':'); + if (colon == nullptr) return std::pair{0, ~u64{}}; + return std::pair{std::strtoull(text, nullptr, 10), std::strtoull(colon + 1, nullptr, 10)}; + }(); + const u64 presents = Core::g_nx2_present_count.load(std::memory_order_relaxed); + const bool svc_trace = presents >= trace_range.first && presents <= trace_range.second; if (svc_trace) { - LOG_INFO(Kernel_SVC, "svc {:#04x} in [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x}", imm, + LOG_INFO(Kernel_SVC, "svc {:#04x} in t{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x}", imm, + GetCurrentThread(kernel).GetThreadId(), GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); } if (imm == 0x80) { @@ -2546,8 +2557,9 @@ void Call(Core::System& system, u32 imm) { Call32(system, imm, args); } if (svc_trace) { - LOG_INFO(Kernel_SVC, "svc {:#04x} out [0]={:#x} [1]={:#x} [2]={:#x}", imm, - GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2)); + LOG_INFO(Kernel_SVC, "svc {:#04x} out t{} [0]={:#x} [1]={:#x} [2]={:#x}", imm, + GetCurrentThread(kernel).GetThreadId(), GetArg64(args, 0), GetArg64(args, 1), + GetArg64(args, 2)); } //kernel.ExitSVCProfile(); kernel.CurrentPhysicalCore().LoadSvcArguments(process, args); diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp index 59396cb58b..d70cfefd2f 100644 --- a/src/video_core/macro.cpp +++ b/src/video_core/macro.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -1360,8 +1361,15 @@ void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u [&](u32 address, u32 value) { if (address >= 0xe00) return false; if (address == 0x556) { - static u32 enable_logs{}; - if (enable_logs++ < 400) LOG_INFO(HW_GPU, "Native MME start={:#x} render enable mode={}", method, value); + // SET_RENDER_ENABLE_OVERRIDE: 0 use the render-enable condition, 1 always, + // 2 never. Log a histogram every 20000 macro writes (pop-in diagnostics). + static std::array modes{}; + static u64 enable_writes{}; + ++modes[value & 3]; + if (++enable_writes % 20000 == 0) { + LOG_INFO(HW_GPU, "Native MME render enable modes: use {} always {} never {} other {}", + modes[0], modes[1], modes[2], modes[3]); + } } maxwell3d.CallMethod(system, address, value, true); // Turing MME memory reads (methods 0x550 SET_MME_MEM_ADDRESS_A/B, 0x558 data RAM @@ -1374,6 +1382,17 @@ void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u maxwell3d.GetRegisterValue(MemAddressB); auto& memory = maxwell3d.GetMemoryManager(); const u32 ram = maxwell3d.GetRegisterValue(DataRamAddress); + // The words may have just been written by the GPU (e.g. indirect draw arguments + // from a culling dispatch) and still be only in host buffers: flush them first, + // or the macro draws with an older frame's arguments (objects pop in and out). + const size_t read_bytes = size_t{std::min(value, 0x1000)} * 4; + if (memory.IsMemoryDirty(source, read_bytes, VideoCommon::CacheType::BufferCache)) { + memory.FlushRegion(source, read_bytes, VideoCommon::CacheType::BufferCache); + static u64 flushes{}; + if (++flushes % 2000 == 1) { + LOG_INFO(HW_GPU, "Native MME DMA read flushed GPU-written memory ({} times)", flushes); + } + } static u32 dma_logs{}; if (dma_logs < 400) { ++dma_logs; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index f55da5fd61..f44916a2e3 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -172,6 +172,9 @@ struct SdlState { }; extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { + // SDL calls SDL_AppIterate back to back by default, which kept a host core at 100% polling + // input; 240 Hz is plenty for input and the key script. + SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "240"); SdlState* state = new SdlState(); *appstate = state; -- cgit v1.2.3