1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <initializer_list>
#include <atomic>
#include <map>
#include <vector>
#include "common/alignment.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_client_session.h"
#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 "video_core/host1x/host1x.h"
#include "video_core/nx2.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/nvdrv/devices/nx2_stub_device.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/nx2_stub_interface.h"
#include "core/hle/service/sm/sm.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 to a TIPC request with success and raw u32 data words. ResponseBuilder::Push(Result)
// writes the CMIF two-word result, which shifts TIPC data one word past where the client reads
// it (and past the copied size); TIPC has a one-word result followed directly by the data.
inline void TipcReplyWords(HLERequestContext& ctx, std::initializer_list<u32> words) {
IPC::ResponseBuilder rb{ctx, static_cast<u32>(2 + words.size())};
rb.PushRaw<u32>(ResultSuccess.raw);
for (const u32 w : words) {
rb.Push<u32>(w);
}
}
// 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.
// - TIPC 22(u32 slot): an event handle for that slot (copy handle at word 3, result at word 4).
// NVN's NvOsEventPool asks for slots and multi-waits on them.
// - TIPC 201(slot, counter, u64 value, mode): signal the slot's event once the counter reaches
// value. The counter is a SyncCounterId, the bitwise NOT of the host1x syncpoint id; mode 0
// compares 32-bit wrapping, mode 1 64-bit. Eden's guest syncpoints back it.
// - TIPC 202(slot): cancel the slot's request.
class INx2SyncptSession final : public ServiceFramework<INx2SyncptSession> {
public:
explicit INx2SyncptSession(Core::System& system_)
: ServiceFramework{system_, "nx2:syncpt"}, m_context{system_, "nx2:syncpt"} {
RegisterHandlersTipc({
{1, &INx2SyncptSession::GetCount, "GetCount (guessed)"},
{22, &INx2SyncptSession::GetSlotEvent, "GetSlotEvent (guessed)"},
{201, &INx2SyncptSession::RequestSlotEvent, "RequestSlotEvent (guessed)"},
{202, &INx2SyncptSession::CancelSlotEvent, "CancelSlotEvent (guessed)"},
});
}
~INx2SyncptSession() override {
for (auto& [slot, request] : m_requests) {
Cancel(request);
}
for (auto& [slot, event] : m_events) {
m_context.CloseEvent(event);
}
}
private:
void GetCount(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) nx2:syncpt TIPC 1, returning 32");
TipcReplyWords(ctx, {32});
}
void GetSlotEvent(HLERequestContext& ctx) {
const u32 slot = ctx.CommandBuffer()[2];
LOG_WARNING(Service, "(STUBBED) nx2:syncpt TIPC 22 slot={:#x}, returning an event", slot);
auto*& event = m_events[slot];
if (event == nullptr) {
event = m_context.CreateEvent(fmt::format("nx2:syncpt:Slot{:x}", slot));
}
IPC::ResponseBuilder rb{ctx, 2, 1, 0};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, event->GetReadableEvent());
}
// Map nodes are stable, so a registered action can hold a reference to its request.
struct SlotRequest {
u32 syncpoint{};
std::atomic<bool> pending{};
Tegra::Host1x::SyncpointManager::ActionHandle handle{};
};
void RequestSlotEvent(HLERequestContext& ctx) {
const auto* cmd_buf = ctx.CommandBuffer();
const u32 slot = cmd_buf[2];
const u32 syncpoint = ~cmd_buf[3];
const u64 value = u64{cmd_buf[4]} | (u64{cmd_buf[5]} << 32);
const u32 mode = cmd_buf[6];
LOG_INFO(Service, "nx2:syncpt TIPC 201 slot={:#x} syncpoint={} value={:#x} mode={}", slot,
syncpoint, value, mode);
auto*& event = m_events[slot];
if (event == nullptr) {
event = m_context.CreateEvent(fmt::format("nx2:syncpt:Slot{:x}", slot));
}
if (syncpoint >= HostSyncpoints) {
LOG_WARNING(Service, "nx2:syncpt TIPC 201 syncpoint {} out of range, signalling",
syncpoint);
event->Signal(system.Kernel());
TipcReplyWords(ctx, {});
return;
}
auto& request = m_requests[slot];
Cancel(request);
request.syncpoint = syncpoint;
request.pending = true;
auto& manager = system.Host1x().GetSyncpointManager();
// The action runs on the GPU thread under the manager's lock, or inline if the value is
// already reached.
request.handle = manager.RegisterGuestAction(
syncpoint, static_cast<u32>(value), [this, &request, event] {
request.pending = false;
event->Signal(system.Kernel());
});
TipcReplyWords(ctx, {});
}
void CancelSlotEvent(HLERequestContext& ctx) {
const u32 slot = ctx.CommandBuffer()[2];
LOG_INFO(Service, "nx2:syncpt TIPC 202 slot={:#x}", slot);
if (auto it = m_requests.find(slot); it != m_requests.end()) {
Cancel(it->second);
}
TipcReplyWords(ctx, {});
}
void Cancel(SlotRequest& request) {
if (request.pending.exchange(false)) {
system.Host1x().GetSyncpointManager().DeregisterGuestAction(request.syncpoint,
request.handle);
}
}
static constexpr u32 HostSyncpoints = 192; // Tegra::Host1x::SyncpointManager
KernelHelpers::ServiceContext m_context;
std::map<u32, Kernel::KEvent*> m_events;
std::map<u32, SlotRequest> m_requests;
};
// Placeholder for the Switch 2 GPU session (nn::gpu::sdk), handed out by gsv 497.
// - TIPC 11: [first, last] id range (first <= min(last, 64)); ids outside it are masked out.
// - TIPC 12(id): event handle for that id (handle at word 3, result at word 4); the SDK attaches
// it as a SystemEvent.
// - TIPC 21(id): starts an operation; the SDK then waits on the id's event. Nothing runs here, so
// the event is signalled at once.
// - TIPC 22: another handle the SDK keeps like a session; a placeholder session is returned.
class INx2GpuSession final : public ServiceFramework<INx2GpuSession> {
public:
explicit INx2GpuSession(Core::System& system_)
: ServiceFramework{system_, "nx2:gpu"}, m_context{system_, "nx2:gpu"} {
RegisterHandlersTipc({
{11, &INx2GpuSession::GetIdRange, "GetIdRange (guessed)"},
{12, &INx2GpuSession::GetIdEvent, "GetIdEvent (guessed)"},
{21, &INx2GpuSession::Start, "Start (guessed)"},
{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)"},
});
}
~INx2GpuSession() override {
for (auto* event : m_events) {
if (event != nullptr) {
m_context.CloseEvent(event);
}
}
}
private:
static constexpr u32 NumIds = 32;
Kernel::KEvent* EventFor(u32 id) {
id %= NumIds;
if (m_events[id] == nullptr) {
m_events[id] = m_context.CreateEvent(fmt::format("nx2:gpu:Event{}", id));
}
return m_events[id];
}
void GetIdRange(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 11, returning [0, {}]", NumIds - 1);
TipcReplyWords(ctx, {0, NumIds - 1});
}
void GetIdEvent(HLERequestContext& ctx) {
const u32 id = ctx.CommandBuffer()[2];
LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 12 id={}", id);
IPC::ResponseBuilder rb{ctx, 2, 1, 0};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, EventFor(id)->GetReadableEvent());
}
void Start(HLERequestContext& ctx) {
const u32 id = ctx.CommandBuffer()[2];
LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 21 id={}, signalling completion", id);
EventFor(id)->Signal(system.Kernel());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void GetCount33(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) nx2:gpu TIPC 33 [2]={:#x}, returning 1", ctx.CommandBuffer()[2]);
TipcReplyWords(ctx, {1});
}
// Used by nn::gpu::sdk::CreateGpuAddressSpace: three u64s after the result, a kind (< 5, 0 =
// 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 = 0x8000000ULL;
constexpr u64 size = 0xC000000000ULL - start;
TipcReplyWords(ctx, {static_cast<u32>(kind), static_cast<u32>(kind >> 32),
static_cast<u32>(start), static_cast<u32>(start >> 32),
static_cast<u32>(size), static_cast<u32>(size >> 32)});
}
void OpenSubSession(HLERequestContext& ctx) {
// 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<INx2GpuSession>(shared_from_this()));
}
// Sub 101: (start u64, last u64, flags...) after CreateGpuAccessibleMemory's svc 0x80.
void SubRegisterMemory(HLERequestContext& ctx) {
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,
last, b[6], b[7]);
if (last > start) {
Nvidia::Devices::Nx2GpuMemoryRegistry::Get().Register(start, last - start + 1);
// The first two-page region holds the queue semaphores: GPU releases go to the second
// page, waits read the first (see video_core/nx2.h).
if (last - start + 1 == 0x2000 && Tegra::g_nx2_semaphore_source_page == 0) {
Tegra::g_nx2_semaphore_mirror_page = start;
Tegra::g_nx2_semaphore_source_page = start + 0x1000;
LOG_WARNING(Service, "nx2:gpu semaphore page {:#x} mirrored to {:#x}",
start + 0x1000, start);
}
}
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;
std::array<Kernel::KEvent*, NumIds> m_events{};
};
// Placeholder for the Switch 2 display session (nn::vi2::sdk), handed out by gsv 305. Reply
// shapes read from the SDK: TIPC 1 -> u32 < 0x400, TIPC 3 -> u32 < 8, TIPC 11/31(u32) and
// 32(handle, u32) -> one handle at word 3 then the result. Handles returned here are events (a
// guess); everything else is auto-stubbed through the nx2: prefix.
class INx2Vi2Session final : public ServiceFramework<INx2Vi2Session> {
public:
explicit INx2Vi2Session(Core::System& system_)
: ServiceFramework{system_, "nx2:vi2"}, m_context{system_, "nx2:vi2"} {
RegisterHandlersTipc({
{1, &INx2Vi2Session::ReturnOne, "Unknown1"},
{3, &INx2Vi2Session::ReturnOne, "GetDisplayCount (guessed)"},
{5, &INx2Vi2Session::GetTimestampRecorderCount, "GetTimestampRecorderCount (guessed)"},
{11, &INx2Vi2Session::ReturnEvent, "Unknown11"},
{31, &INx2Vi2Session::ReturnEvent, "Unknown31"},
{32, &INx2Vi2Session::ReturnEvent, "Unknown32"},
});
}
~INx2Vi2Session() override {
for (auto* event : m_events) {
m_context.CloseEvent(event);
}
}
private:
void ReturnOne(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) nx2:vi2 TIPC {}, returning 1", ctx.GetCommand());
TipcReplyWords(ctx, {1});
}
// Must be < 8; nn::vi::detail::LayerHistory creates 7 timestamp recorders per layer.
void GetTimestampRecorderCount(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) nx2:vi2 TIPC 5, returning 7");
TipcReplyWords(ctx, {7});
}
void ReturnEvent(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) nx2:vi2 TIPC {} [2]={:#x} [3]={:#x}, returning an event",
ctx.GetCommand(), ctx.CommandBuffer()[2], ctx.CommandBuffer()[3]);
auto* event = m_context.CreateEvent(fmt::format("nx2:vi2:Event{}", m_events.size()));
m_events.push_back(event);
IPC::ResponseBuilder rb{ctx, 2, 1, 0};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, event->GetReadableEvent());
}
KernelHelpers::ServiceContext m_context;
std::vector<Kernel::KEvent*> m_events;
};
// 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.
class INx2GraphicsSystemSession final : public ServiceFramework<INx2GraphicsSystemSession> {
public:
explicit INx2GraphicsSystemSession(Core::System& system_)
: 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)"},
{305, &INx2GraphicsSystemSession::OpenViSession, "OpenViSession (guessed)"},
{497, &INx2GraphicsSystemSession::OpenGpuSession, "OpenGpuSession (guessed)"},
{1402, &INx2GraphicsSystemSession::OpenNvsrvSession, "OpenNvsrvSession (guessed)"},
{1510, &INx2GraphicsSystemSession::CalculateRequirements, "CalculateRequirements"},
});
}
~INx2GraphicsSystemSession() override {
if (m_syncpt_poll) {
system.CoreTiming().UnscheduleEvent(m_syncpt_poll);
}
for (auto* event : m_syncpt_events) {
if (event != nullptr) {
m_context.CloseEvent(event);
}
}
}
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}",
ctx.GetCommand(), cmd_buf[2], cmd_buf[3], cmd_buf[4], cmd_buf[5]);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
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));
}
// 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, u32 base id at +4, u32 count at +8 (the client
// requires ((count + 0x40) >> 6) - 1 < 0x40), then offsets of: the u64 syncpoint values (RO,
// +0xc), the RW "armed" summary word (+0x10), the RO summary word (+0x14), the RW armed bits
// (+0x18) and the RO acknowledge bits (+0x1c).
// To wait, the client makes its RW bit differ from the RO bit, signals the first returned
// event (it attaches that handle as writable), and waits on the second until the server makes
// the bits equal again; it then rereads the value and rearms if the threshold isn't reached.
// Reply: the two handles (words 3 and 4), then the result.
static constexpr u32 SyncptValuesOffset = 0x40; // RO, u64 per syncpoint
static constexpr u32 SyncptRoSummaryOffset = 0x1040;
static constexpr u32 SyncptRoBitsOffset = 0x1100; // RO, up to 0x40 u64 words
static constexpr u32 SyncptRwSummaryOffset = 0x0;
static constexpr u32 SyncptRwBitsOffset = 0x100; // RW, up to 0x40 u64 words
static constexpr u32 HostSyncpoints = 192; // Tegra::Host1x::SyncpointManager
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 = std::min<u32>(cmd_buf[data + 1], (0x1000 - 0x40) / 8);
LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 204, version={} max_syncpoints={:#x}",
version_requested, max_syncpoints);
auto rw = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0));
auto ro = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(1));
if (rw.IsNotNull() && ro.IsNotNull() && ro->GetSize() >= 0x2000) {
m_syncpt_memory = &ro->GetOwner()->GetMemory();
m_syncpt_rw = GetInteger(rw->GetSourceAddress());
m_syncpt_ro = GetInteger(ro->GetSourceAddress());
m_syncpt_count = max_syncpoints;
auto& memory = *m_syncpt_memory;
memory.Write32(m_syncpt_ro + 0x00, 1); // version
memory.Write32(m_syncpt_ro + 0x04, 0); // base id (guess)
memory.Write32(m_syncpt_ro + 0x08, max_syncpoints);
memory.Write32(m_syncpt_ro + 0x0c, SyncptValuesOffset);
memory.Write32(m_syncpt_ro + 0x10, SyncptRwSummaryOffset);
memory.Write32(m_syncpt_ro + 0x14, SyncptRoSummaryOffset);
memory.Write32(m_syncpt_ro + 0x18, SyncptRwBitsOffset);
memory.Write32(m_syncpt_ro + 0x1c, SyncptRoBitsOffset);
UpdateSyncptValues();
if (!m_syncpt_poll) {
m_syncpt_poll = Core::Timing::CreateEvent(
"nx2:gsv:SyncptPoll",
[this](s64, std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
PollSyncpts();
return std::nullopt;
});
system.CoreTiming().ScheduleLoopingEvent(SyncptPollInterval, SyncptPollInterval,
m_syncpt_poll);
}
} else {
LOG_ERROR(Service, "nx2:gsv TIPC 204: missing syncpt transfer memory");
}
if (m_syncpt_events[0] == nullptr) {
m_syncpt_events[0] = m_context.CreateEvent("nx2:gsv:SyncptRequest");
m_syncpt_events[1] = m_context.CreateEvent("nx2:gsv:SyncptNotify");
}
IPC::ResponseBuilder rb{ctx, 2, 2, 0};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, *m_syncpt_events[0]); // signalled by the client
rb.PushCopyObjects(ctx, m_syncpt_events[1]->GetReadableEvent());
}
// Mirrors Eden's guest syncpoint values into the shared RO page. Returns true if any changed.
bool UpdateSyncptValues() {
auto& manager = system.Host1x().GetSyncpointManager();
bool changed = false;
for (u32 id = 0; id < std::min(m_syncpt_count, HostSyncpoints); ++id) {
const u64 value = manager.GetGuestSyncpointValue(id);
const u64 address = m_syncpt_ro + SyncptValuesOffset + id * 8;
if (m_syncpt_memory->Read64(address) != value) {
m_syncpt_memory->Write64(address, value);
changed = true;
}
}
return changed;
}
// Answers every armed wait: the value page is current, so the client rechecks its threshold
// and rearms if it still has to wait. Acknowledging without a value change costs the client a
// wakeup per poll, which keeps waits whose syncpoint we never see from hanging.
void PollSyncpts() {
if (m_syncpt_memory == nullptr) {
return;
}
UpdateSyncptValues();
bool acked = false;
const u32 words = (m_syncpt_count + 63) / 64;
for (u32 w = 0; w < words; ++w) {
const u64 rw_bits = m_syncpt_memory->Read64(m_syncpt_rw + SyncptRwBitsOffset + w * 8);
const u64 ro_address = m_syncpt_ro + SyncptRoBitsOffset + w * 8;
if (m_syncpt_memory->Read64(ro_address) != rw_bits) {
m_syncpt_memory->Write64(ro_address, rw_bits);
acked = true;
}
}
m_syncpt_memory->Write64(m_syncpt_ro + SyncptRoSummaryOffset,
m_syncpt_memory->Read64(m_syncpt_rw + SyncptRwSummaryOffset));
if (acked) {
m_syncpt_events[1]->Signal(system.Kernel());
}
}
// nn::gsv::shim::CalculateRequirements(u64* size, u64* alignment, preference, u32): the 0x40-byte
// InterModuleUserImagePreference (words 2..17) and a u32 (word 18). A block-linear preference
// (MakeInterModuleBlockUserImagePreference) holds kind 2 at word 3, (width - 1) | (height - 1)
// << 16 at word 4, two u32s at words 6-7 and the pixel format at word 8. Reply: result, u64
// 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});
}
void OpenViSession(HLERequestContext& ctx) {
// Used by nn::vi2::sdk::Initialize; takes one u64, returns a session (handle at word 3,
// result at word 4).
const auto* cmd_buf = ctx.CommandBuffer();
const size_t data = TipcDataOffset(cmd_buf);
LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 305 arg={:#x}, returning a nx2:vi2 session",
u64{cmd_buf[data]} | (u64{cmd_buf[data + 1]} << 32));
ReturnRawSession(system, ctx, std::make_shared<INx2Vi2Session>(system));
}
void OpenGpuSession(HLERequestContext& ctx) {
// Used by nn::gpu::sdk::BootWithRemoteMemory; takes one u64, returns a session (handle at
// word 3, result at word 4) that the GPU SDK keeps.
const auto* cmd_buf = ctx.CommandBuffer();
const size_t data = TipcDataOffset(cmd_buf);
LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 497 arg={:#x}, returning a nx2:gpu session",
u64{cmd_buf[data]} | (u64{cmd_buf[data + 1]} << 32));
ReturnRawSession(system, ctx, std::make_shared<INx2GpuSession>(system));
}
void OpenNvsrvSession(HLERequestContext& ctx) {
// The SDK reads the handle at word 3 and the result at word 4.
// nvsrv speaks the nvdrv INvDrvServices protocol (Open/Ioctl/Close/...,
// 13 = SetGraphicsFirmwareMemoryMarginEnabled), so serve it with Eden's nvdrv on the shared
// module. The nx2: name keeps unknown commands auto-stubbed and logged.
LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 1402, returning an nvdrv-backed nx2:nvsrv session");
auto module = system.ServiceManager().GetService<Nvidia::NVDRV>("nvdrv", true)->GetModule();
auto nvsrv = std::make_shared<Nvidia::NVDRV>(system, module, "nx2:nvsrv");
nvsrv->InitializeForProcess(ctx.GetThread().GetOwnerProcess());
ReturnRawSession(system, ctx, std::move(nvsrv));
}
static constexpr std::chrono::nanoseconds SyncptPollInterval{std::chrono::microseconds{500}};
KernelHelpers::ServiceContext m_context;
std::array<Kernel::KEvent*, 2> m_syncpt_events{};
Core::Memory::Memory* m_syncpt_memory{};
u64 m_syncpt_rw{};
u64 m_syncpt_ro{};
u32 m_syncpt_count{};
std::shared_ptr<Core::Timing::EventType> m_syncpt_poll;
};
} // namespace Service
|