aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 19:30:20 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 19:30:20 -0400
commitf32b430326af86bd956f3d49d1bf7b6a1b9c5e31 (patch)
tree4dc321c41055bd68f1e1bb6707330df1ee9599fa
parenta750204a15561e8e22d40c50df8e0827eb45fd8f (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.h4
-rw-r--r--src/core/hle/kernel/k_page_table_base.cpp3
-rw-r--r--src/core/hle/kernel/svc.cpp33
-rw-r--r--src/core/hle/kernel/svc_types.h1
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp26
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h9
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp29
-rw-r--r--src/core/hle/service/nx2_gsv.h180
-rw-r--r--src/core/hle/service/service.cpp6
-rw-r--r--src/video_core/memory_manager.cpp13
-rw-r--r--src/video_core/memory_manager.h5
-rw-r--r--tools/nx2/gdbrsp.py20
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]))