diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-27 19:30:20 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-27 19:30:20 -0400 |
| commit | f32b430326af86bd956f3d49d1bf7b6a1b9c5e31 (patch) | |
| tree | 4dc321c41055bd68f1e1bb6707330df1ee9599fa | |
| parent | a750204a15561e8e22d40c50df8e0827eb45fd8f (diff) | |
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.
| -rw-r--r-- | src/core/hle/kernel/k_memory_block.h | 4 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_page_table_base.cpp | 3 | ||||
| -rw-r--r-- | src/core/hle/kernel/svc.cpp | 33 | ||||
| -rw-r--r-- | src/core/hle/kernel/svc_types.h | 1 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp | 26 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h | 9 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 29 | ||||
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 180 | ||||
| -rw-r--r-- | src/core/hle/service/service.cpp | 6 | ||||
| -rw-r--r-- | src/video_core/memory_manager.cpp | 13 | ||||
| -rw-r--r-- | src/video_core/memory_manager.h | 5 | ||||
| -rw-r--r-- | tools/nx2/gdbrsp.py | 20 |
12 files changed, 301 insertions, 28 deletions
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<u8>(Svc::MemoryAttribute::Uncached), PermissionLocked = static_cast<u8>(Svc::MemoryAttribute::PermissionLocked), - SetMask = Uncached | PermissionLocked, + GpuAccessible = static_cast<u8>(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<u32>(GetArg64(args, 2)); + const u32 attr = static_cast<u32>(GetArg64(args, 3)); + constexpr u32 Supported = static_cast<u32>(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<KMemoryAttribute>(mask), + static_cast<KMemoryAttribute>(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<const u8> 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<Tegra::MemoryManager> 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<u8> 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<const u8> 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<u8> 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 <algorithm> #include <array> #include <initializer_list> #include <atomic> @@ -437,6 +438,60 @@ private: std::vector<Kernel::KEvent*> 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<u32, 16> preference{}; + u64 size{}; + bool window_pool{}; + }; + std::mutex mutex; + std::map<u32, Memory> memories; + std::map<u32, Image> 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<u64>(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<u32>(size), static_cast<u32>(size >> 32), - static_cast<u32>(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<u32>(layout.size), static_cast<u32>(layout.size >> 32), + static_cast<u32>(layout.alignment), + static_cast<u32>(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<u32>(w[18], 1); + LOG_DEBUG(Service, "nx2:gsv 435 count={} -> size={:#x}", w[18], size); + TipcReplyWords(ctx, {static_cast<u32>(size), static_cast<u32>(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<Nx2NativeObjects> m_objects{std::make_shared<Nx2NativeObjects>()}; KernelHelpers::ServiceContext m_context; std::array<Kernel::KEvent*, 2> 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<DAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr, std::size_t s template <typename T> 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<T>(system.Host1x().GetSyncpointManager().GetGuestSyncpointValue( + static_cast<u32>((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::pair<std::conditional_t<is_gpu_address, GPUVAddr, DAddr>, std::size_t>, 32>& result) const; + std::atomic<GPUVAddr> 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("<Q", data, i + j)[0] for j in range(0, min(16, len(data) - i) - 7, 8)] @@ -258,9 +259,24 @@ def main(): regs = r d = rsp.mem(addr, 8) val = struct.unpack("<Q", d)[0] if len(d) == 8 else None - print(f" w{i} {reply[:40]} pc={r.get('pc', 0):#x} lr={r.get('x30', 0) & 0xFFFFFFFFFFFF:#x} value={val if val is None else hex(val)}") + chain = [] + fp = r.get("x29", 0) + for _ in range(6): + fd = rsp.mem(fp, 16) + if len(fd) < 16: + break + nfp, ret = struct.unpack("<QQ", fd) + chain.append(ret & 0xFFFFFFFFFFFF) + if nfp <= fp: + break + fp = nfp + print(f" w{i} {reply[:40]} pc={r.get('pc', 0):#x} lr={r.get('x30', 0) & 0xFFFFFFFFFFFF:#x} value={val if val is None else hex(val)} chain=" + ",".join(f"{c:#x}" for c in chain)) if stopval is not None and val == stopval: break + # Step off the access with the watchpoint removed, then re-arm it. + rsp.cmd(f"z2,{addr:x},8") + rsp.cmd("s") + rsp.cmd(f"Z2,{addr:x},8") elif op == "sleep": import time time.sleep(float(parts[1])) |
