diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-27 13:48:52 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-27 13:48:52 -0400 |
| commit | 63f61e109f2d303b62f8b908dbacadc13b79408f (patch) | |
| tree | 75d5c0dc984687f457471b7bb69e8324336bc618 | |
| parent | 2da3cd99259390e455c9e7600dfc61006d65a45b (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.h | 150 |
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 |
