diff options
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 42 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h | 3 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp | 40 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nx2_stub_device.h | 14 | ||||
| -rw-r--r-- | tools/nx2/gdbrsp.py | 29 |
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": |
