aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 15:54:37 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 15:54:37 -0400
commit81cff29955cd1130ae83ec658adf5e6cd0264399 (patch)
treef0364fbed78d889dbe64b27e8c43a5c669b330ed /src
parent5cb4786f3e25136145fa71c17da7a65be35ff7dd (diff)
nvdrv: Switch 2 GetEngineInfo, TSG flag, Ampere object classes, channel ioctl passthrough
Once an nx2 NvRm identifies itself (0xD0-byte characteristics), nvhost-gpu and nvhost-ctrl-gpu answer unknown ioctls with a logged input echo instead of NotImplemented, ctrl-gpu reports GR + async-copy engines, and Ampere class numbers (and class 0) map onto Eden's Maxwell object slots. gdbrsp.py gains trace/flow/ breakwhen/fp helpers.
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp42
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h3
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp40
-rw-r--r--src/core/hle/service/nvdrv/devices/nx2_stub_device.h14
4 files changed, 99 insertions, 0 deletions
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 db9ae85cf0..b15828aedc 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <array>
#include <algorithm>
#include <cstring>
#include "common/assert.h"
@@ -12,6 +13,7 @@
#include "core/core_timing.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
+#include "core/hle/service/nvdrv/devices/nx2_stub_device.h"
#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia::Devices {
@@ -113,6 +115,9 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8>
default:
break;
}
+ if (nx2_caller && command.group == 'G' && command.cmd == 0x1F) {
+ return Nx2GetEngineInfo(input, output, inline_output);
+ }
if (nx2_caller) {
// Switch 2 NvRm uses ioctls Switch 1 never had; answer them with a zeroed success so
// device setup can continue, and log each one.
@@ -127,6 +132,41 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8>
return NvResult::NotImplemented;
}
+// nvgpu GET_ENGINE_INFO as the Switch 2 NvRm uses it: args {u32 buf_size, u32 reserved,
+// u64 buf_addr}, queried once for the size and once with an inline output buffer of 16-byte items
+// {u32 engine_id, u32 engine_instance, s32 runlist_id, u32 reserved}. NvRm picks the channel
+// runlist from this list. Report the graphics engine (runlist 0) and an async copy engine (1).
+NvResult nvhost_ctrl_gpu::Nx2GetEngineInfo(std::span<const u8> input, std::span<u8> output,
+ std::span<u8> inline_output) {
+ struct Item {
+ u32 engine_id;
+ u32 engine_instance;
+ s32 runlist_id;
+ u32 reserved;
+ };
+ static constexpr std::array<Item, 2> items{{
+ {0, 0, 0, 0}, // NVGPU_ENGINE_GR
+ {2, 0, 1, 0}, // NVGPU_ENGINE_ASYNC_COPY
+ }};
+ constexpr u32 needed = static_cast<u32>(items.size() * sizeof(Item));
+
+ u32 buf_size{};
+ if (input.size() >= sizeof(buf_size)) {
+ std::memcpy(&buf_size, input.data(), sizeof(buf_size));
+ }
+ LOG_WARNING(Service_NVDRV, "(STUBBED) Switch 2 GetEngineInfo buf_size={:#x} inline_out={:#x}",
+ buf_size, inline_output.size());
+ if (buf_size >= needed && inline_output.size() >= needed) {
+ std::memcpy(inline_output.data(), items.data(), needed);
+ }
+ std::fill(output.begin(), output.end(), u8{0});
+ std::copy_n(input.begin(), std::min(input.size(), output.size()), output.begin());
+ if (output.size() >= sizeof(needed)) {
+ std::memcpy(output.data(), &needed, sizeof(needed));
+ }
+ return NvResult::Success;
+}
+
void nvhost_ctrl_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {}
@@ -149,6 +189,7 @@ void ApplyNx2Characteristics(GC& gc) {
gc.gpfifo_class = 0xC56F; // AMPERE_CHANNEL_GPFIFO_A
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
// 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
@@ -248,6 +289,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics3(
params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
if (nx2) {
nx2_caller = true;
+ g_nx2_driver = true;
ApplyNx2Characteristics(params.gc);
params.gpu_characteristics_buf_size = Nx2CharacteristicsSize;
LOG_WARNING(Service_NVDRV, "Switch 2 caller, reporting Ampere chip 0x17E");
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 35b5a5f24c..507cdac71d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -208,6 +208,9 @@ private:
EventInterface& events_interface;
// Events
+ NvResult Nx2GetEngineInfo(std::span<const u8> input, std::span<u8> output,
+ std::span<u8> inline_output);
+
// Set once a caller asked for the Switch 2 characteristics layout.
bool nx2_caller{};
Kernel::KEvent* error_notifier_event;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 1e571be4c9..15458cd82d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -14,6 +14,7 @@
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/devices/nx2_stub_device.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/memory.h"
#include "video_core/control/channel_state.h"
@@ -101,6 +102,9 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
}
break;
}
+ if (g_nx2_driver) {
+ return Nx2UnknownIoctl("nvhost-gpu", command, input, output);
+ }
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented;
};
@@ -116,12 +120,19 @@ NvResult nvhost_gpu::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> inpu
}
break;
}
+ if (g_nx2_driver) {
+ return Nx2UnknownIoctl("nvhost-gpu", command, input, output);
+ }
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented;
}
NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input,
std::span<u8> output, std::span<u8> inline_output) {
+ if (g_nx2_driver) {
+ std::fill(inline_output.begin(), inline_output.end(), u8{0});
+ return Nx2UnknownIoctl("nvhost-gpu", command, input, output);
+ }
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented;
}
@@ -278,6 +289,35 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
params.flags = allowed_mask;
}
+ if (g_nx2_driver) {
+ // Switch 2 NvRm asks for Ampere classes, or class 0 (it reads the class numbers from a
+ // characteristics layout we don't know yet). Map them onto the Maxwell engine slots.
+ u32 mapped = params.class_num;
+ switch (params.class_num) {
+ case 0xC797: case 0xC697: case 0xC597: mapped = static_cast<u32>(CtxClasses::Ctx3D); break;
+ case 0xC7C0: case 0xC6C0: case 0xC5C0: mapped = static_cast<u32>(CtxClasses::CtxCompute); break;
+ case 0xC7B5: case 0xC6B5: case 0xC5B5: mapped = static_cast<u32>(CtxClasses::CtxDMA); break;
+ case 0xC56F: case 0xC76F: mapped = static_cast<u32>(CtxClasses::CtxChannelGPFIFO); break;
+ case 0: {
+ for (const auto candidate : {CtxClasses::Ctx3D, CtxClasses::CtxCompute, CtxClasses::CtxDMA,
+ CtxClasses::Ctx2D, CtxClasses::CtxKepler}) {
+ const auto index = GetObjectContextClassNumberIndex(candidate);
+ if (index >= 0 && !ctxObjs[index].has_value()) {
+ mapped = static_cast<u32>(candidate);
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (mapped != params.class_num) {
+ LOG_WARNING(Service_NVDRV, "Switch 2 object class {:#x} -> {:#x}", params.class_num, mapped);
+ params.class_num = mapped;
+ }
+ }
+
s32_le ctx_class_number_index =
GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num));
if (ctx_class_number_index < 0) {
diff --git a/src/core/hle/service/nvdrv/devices/nx2_stub_device.h b/src/core/hle/service/nvdrv/devices/nx2_stub_device.h
index 1dc2f8b7fa..4e8862a10a 100644
--- a/src/core/hle/service/nvdrv/devices/nx2_stub_device.h
+++ b/src/core/hle/service/nvdrv/devices/nx2_stub_device.h
@@ -4,6 +4,7 @@
#pragma once
#include <algorithm>
+#include <atomic>
#include <string>
#include "common/logging.h"
@@ -11,6 +12,19 @@
namespace Service::Nvidia::Devices {
+// Set once a Switch 2 NvRm has identified itself (0xD0-byte GetCharacteristics). Devices then
+// answer ioctls they don't know with a logged, input-echoing success instead of NotImplemented.
+inline std::atomic<bool> g_nx2_driver{false};
+
+inline NvResult Nx2UnknownIoctl(const char* device, Ioctl command, std::span<const u8> input,
+ std::span<u8> output) {
+ LOG_WARNING(Service_NVDRV, "(STUBBED) Switch 2 {} ioctl={:08X} group={:01X} command={:02X} in={:#x}",
+ device, command.raw, command.group, command.cmd, input.size());
+ std::fill(output.begin(), output.end(), u8{0});
+ std::copy_n(input.begin(), std::min(input.size(), output.size()), output.begin());
+ return NvResult::Success;
+}
+
// Device node that Switch 2 nvgpu drivers open but Switch 1 never had (e.g. /dev/nvhost-tsg-gpu).
// Every ioctl succeeds with a zeroed reply and is logged, so the driver's use of it shows up.
class nx2_stub_device final : public nvdevice {