aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--tools/nx2/gdbrsp.py29
5 files changed, 128 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 {
diff --git a/tools/nx2/gdbrsp.py b/tools/nx2/gdbrsp.py
index ba81c33c1a..9b3b2d8b37 100644
--- a/tools/nx2/gdbrsp.py
+++ b/tools/nx2/gdbrsp.py
@@ -100,6 +100,35 @@ def main():
print(f" t pc={r['pc']:#x} x0={r['x0']:#x} x8={r['x8']:#x}")
if stop_at is not None and r["pc"] == stop_at:
break
+ elif op == "breakwhen":
+ # breakwhen <addr> <reg> <value> [max]: stop at addr only when reg == value.
+ addr, reg, want = int(parts[1], 16), parts[2], int(parts[3], 16)
+ for _ in range(int(parts[4]) if len(parts) > 4 else 50):
+ rsp.cmd(f"Z0,{addr:x},4")
+ rsp.cmd("c")
+ regs = rsp.regs()
+ if regs.get("pc") != addr:
+ print(" breakwhen: stopped elsewhere at", hex(regs.get("pc", 0)))
+ break
+ if regs.get(reg) == want:
+ print(f" breakwhen: hit with {reg}={want:#x}")
+ break
+ rsp.cmd(f"z0,{addr:x},4")
+ rsp.cmd("s")
+ print(" " + " ".join(f"{k}={v:#x}" for k, v in regs.items() if k in ("pc", "x0", "x1", "x2", "x19", "x20", "x21", "x22", "x24", "x30")))
+ elif op == "flow":
+ # flow N stop_hex: single-step N instructions, print only non-sequential pcs (branches)
+ n, stop_at = int(parts[1]), int(parts[2], 16)
+ prev = rsp.regs().get("pc", 0)
+ for _ in range(n):
+ rsp.cmd("s")
+ r = rsp.regs()
+ pc = r.get("pc", 0)
+ if pc != prev + 4:
+ print(f" f {prev:#x} -> {pc:#x} x0={r.get('x0', 0):#x}")
+ prev = pc
+ if pc == stop_at:
+ break
elif op == "continue":
print("stop:", rsp.cmd("c"))
elif op == "x0":