aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 13:48:52 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 13:48:52 -0400
commit63f61e109f2d303b62f8b908dbacadc13b79408f (patch)
tree75d5c0dc984687f457471b7bb69e8324336bc618
parent2da3cd99259390e455c9e7600dfc61006d65a45b (diff)
service: Switch 2 nvsrv and syncpt placeholder sessions behind gsv
- gsv 1402 returns an nvsrv session (handle at word 3, result at word 4); nn::nvsrv::BootWithRemoteMemory keeps it. - gsv 202 returns a syncpt session; gsv 204 takes two transfer memories (0x1000 RW, 0x2000 RO) and must leave a header in the RO one (version 1, count, five region offsets) and return two event handles. - syncpt TIPC 1 returns a u32 < 0x400 (32 here). RequestParser doesn't skip TIPC handle words, so 204 reads raw data words.
-rw-r--r--src/core/hle/service/nx2_gsv.h150
1 files changed, 129 insertions, 21 deletions
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 1de266aae9..18ae9a6bc3 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -8,30 +8,104 @@
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_server_session.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_session.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/memory.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nx2_stub_interface.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service {
+// Creates a session served by `iface` and returns its client end. Unlike PushIpcInterface this
+// works from a domain too, for callers that want a raw session handle.
+template <class T>
+inline Kernel::KClientSession* CreateRawSession(Core::System& system, HLERequestContext& ctx,
+ std::shared_ptr<T> iface) {
+ auto& kernel = system.Kernel();
+ ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(
+ kernel, Kernel::LimitableResource::SessionCountMax, 1));
+
+ auto* session = Kernel::KSession::Create(kernel);
+ session->Initialize(kernel, nullptr, 0);
+ Kernel::KSession::Register(kernel, session);
+
+ auto manager =
+ std::make_shared<SessionRequestManager>(kernel, ctx.GetManager()->GetServerManager());
+ manager->SetSessionHandler(std::move(iface));
+ ctx.GetManager()->GetServerManager().RegisterSession(&session->GetServerSession(), manager);
+ return &session->GetClientSession();
+}
+
+// Replies with success and a copied handle to a new session served by `iface`.
+template <class T>
+inline void ReturnRawSession(Core::System& system, HLERequestContext& ctx, std::shared_ptr<T> iface) {
+ auto* client = CreateRawSession(system, ctx, std::move(iface));
+ IPC::ResponseBuilder rb{ctx, 2, 1, 0};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(ctx, *client);
+}
+
+// Placeholder for the Switch 2 NVIDIA driver server (nn::nvsrv). It has no handlers, so every
+// command (CMIF or TIPC) gets an empty success and is logged through the nx2: prefix.
+class INx2NvsrvSession final : public ServiceFramework<INx2NvsrvSession> {
+public:
+ explicit INx2NvsrvSession(Core::System& system_) : ServiceFramework{system_, "nx2:nvsrv"} {}
+};
+
+// Placeholder for the Switch 2 syncpoint server (nn::syncpt), handed out by gsv 202. TIPC 1
+// returns a u32 the SDK requires to be < 0x400 and then passes min(value, 63) to TIPC 21.
+// Everything else is auto-stubbed through the nx2: prefix.
+class INx2SyncptSession final : public ServiceFramework<INx2SyncptSession> {
+public:
+ explicit INx2SyncptSession(Core::System& system_) : ServiceFramework{system_, "nx2:syncpt"} {
+ RegisterHandlersTipc({
+ {1, &INx2SyncptSession::GetCount, "GetCount (guessed)"},
+ });
+ }
+
+private:
+ void GetCount(HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) nx2:syncpt TIPC 1, returning 32");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(32);
+ }
+};
+
// Placeholder for the Switch 2 graphics-system server that nn::gsv::BootWithRemoteMemory talks
-// to over TIPC (commands 4, 5 and 101 during boot). Nothing behind it is emulated: every
-// command succeeds with no output and is logged, so boot can show what comes next.
+// 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.
class INx2GraphicsSystemSession final : public ServiceFramework<INx2GraphicsSystemSession> {
public:
explicit INx2GraphicsSystemSession(Core::System& system_)
- : ServiceFramework{system_, "nx2:gsv"} {
+ : ServiceFramework{system_, "nx2:gsv"}, m_context{system_, "nx2:gsv"} {
RegisterHandlersTipc({
{4, &INx2GraphicsSystemSession::Stub, "Boot (Switch 2, guessed)"},
{5, &INx2GraphicsSystemSession::Stub, "Unknown5"},
{101, &INx2GraphicsSystemSession::Stub, "Unknown101"},
+ {202, &INx2GraphicsSystemSession::OpenSyncptSession, "OpenSyncptSession (guessed)"},
+ {204, &INx2GraphicsSystemSession::AttachSyncptMemory, "AttachSyncptMemory (guessed)"},
+ {1402, &INx2GraphicsSystemSession::OpenNvsrvSession, "OpenNvsrvSession (guessed)"},
});
}
private:
+ // Index of the first raw data word of a TIPC request: header (2 words), then an optional
+ // special header with pid and copy/move handle words.
+ static size_t TipcDataOffset(const u32* cmd_buf) {
+ if ((cmd_buf[1] & 0x80000000) == 0) {
+ return 2;
+ }
+ const u32 desc = cmd_buf[2];
+ return 3 + ((desc & 1) ? 2 : 0) + ((desc >> 1) & 0xF) + ((desc >> 5) & 0xF);
+ }
+
void Stub(HLERequestContext& ctx) {
const auto* cmd_buf = ctx.CommandBuffer();
LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC {} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x}",
@@ -39,27 +113,61 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
-};
-// Creates a session to `iface` and returns its client end as a copied handle. Unlike
-// PushIpcInterface this works from a domain too, for callers that want a raw session.
-template <class T>
-inline void ReturnRawSession(Core::System& system, HLERequestContext& ctx, std::shared_ptr<T> iface) {
- auto& kernel = system.Kernel();
- ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(
- kernel, Kernel::LimitableResource::SessionCountMax, 1));
+ void OpenSyncptSession(HLERequestContext& ctx) {
+ // Used by nn::syncpt::sdk::InitializeEx; handle at word 3, result at word 4.
+ LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 202, returning a nx2:syncpt session");
+ ReturnRawSession(system, ctx, std::make_shared<INx2SyncptSession>(system));
+ }
- auto* session = Kernel::KSession::Create(kernel);
- session->Initialize(kernel, nullptr, 0);
- Kernel::KSession::Register(kernel, session);
+ // nn::syncpt::sdk::InitializeEx sends two transfer memories (0x1000 bytes RW, then 0x2000
+ // bytes the client maps read-only) and expects the server to have written a header into the
+ // second one: u32 version (must be 1) at +0, then u32s at +4 and +8 (a count; the client
+ // requires ((count + 0x40) >> 6) - 1 < 0x40) and five offsets at +0xc..+0x1c (into the RO,
+ // RW, RO, RW and RO regions). Reply: two handles (words 3 and 4), then the result.
+ void AttachSyncptMemory(HLERequestContext& ctx) {
+ // RequestParser doesn't skip TIPC handle words, so read the raw data words.
+ const auto* cmd_buf = ctx.CommandBuffer();
+ const size_t data = TipcDataOffset(cmd_buf);
+ const u32 version_requested = cmd_buf[data];
+ const u32 max_syncpoints = cmd_buf[data + 1];
+ LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 204, version={} max_syncpoints={:#x}",
+ version_requested, max_syncpoints);
- auto manager = std::make_shared<SessionRequestManager>(kernel, ctx.GetManager()->GetServerManager());
- manager->SetSessionHandler(std::move(iface));
- ctx.GetManager()->GetServerManager().RegisterSession(&session->GetServerSession(), manager);
+ auto ro = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(1));
+ if (ro.IsNotNull() && ro->GetSize() >= 0x2000) {
+ auto& memory = ro->GetOwner()->GetMemory();
+ const u64 base = GetInteger(ro->GetSourceAddress());
+ memory.Write32(base + 0x00, 1); // version
+ memory.Write32(base + 0x04, 0); // base id (guess)
+ memory.Write32(base + 0x08, max_syncpoints); // count
+ memory.Write32(base + 0x0c, 0x40); // -> RO region
+ memory.Write32(base + 0x10, 0x000); // -> RW region
+ memory.Write32(base + 0x14, 0x800); // -> RO region
+ memory.Write32(base + 0x18, 0x800); // -> RW region
+ memory.Write32(base + 0x1c, 0x1000); // -> RO region
+ } else {
+ LOG_ERROR(Service, "nx2:gsv TIPC 204: no read-only transfer memory");
+ }
- IPC::ResponseBuilder rb{ctx, 2, 1, 0};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(ctx, session->GetClientSession());
-}
+ if (m_syncpt_events[0] == nullptr) {
+ m_syncpt_events[0] = m_context.CreateEvent("nx2:gsv:SyncptEvent0");
+ m_syncpt_events[1] = m_context.CreateEvent("nx2:gsv:SyncptEvent1");
+ }
+ IPC::ResponseBuilder rb{ctx, 2, 2, 0};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(ctx, m_syncpt_events[0]->GetReadableEvent(),
+ m_syncpt_events[1]->GetReadableEvent());
+ }
+
+ void OpenNvsrvSession(HLERequestContext& ctx) {
+ // The SDK reads the handle at word 3 and the result at word 4.
+ LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 1402, returning a nx2:nvsrv session");
+ ReturnRawSession(system, ctx, std::make_shared<INx2NvsrvSession>(system));
+ }
+
+ KernelHelpers::ServiceContext m_context;
+ std::array<Kernel::KEvent*, 2> m_syncpt_events{};
+};
} // namespace Service