Switch 2 GPU: syncpt server, identity GPU mappings, Ampere classes, submit fences
- gsv 204: real syncpoint shared page (values mirrored from host1x, armed waits acknowledged by a 500us poller); first handle is the writable request event. - syncpt 22/201/202: per-slot events signalled through host1x guest actions. - nx2:gpu sub 101 memory is identity-mapped (GPU VA == CPU VA) into every nvhost-as-gpu GMMU via Nx2GpuMemoryRegistry; SMMU physical range widened to 34 bits for the 10/12 GiB layouts. - Puller runs Ampere 3D/compute/copy classes on the Maxwell engines; class 0 unbinds. - Turing+ macros are skipped (Eden only has the Fermi macro ISA). - Submits with increment_value get Eden-side syncpoint increments, since NVN2 signals completion with semaphore releases only.
This commit is contained in:
parent
0d679f770e
commit
563e727332
11 changed files with 397 additions and 28 deletions
|
|
@ -139,7 +139,7 @@ private:
|
|||
static constexpr size_t device_virtual_bits = Traits::device_virtual_bits;
|
||||
static constexpr size_t device_as_size = 1ULL << device_virtual_bits;
|
||||
static constexpr size_t physical_min_bits = 32;
|
||||
static constexpr size_t physical_max_bits = 33;
|
||||
static constexpr size_t physical_max_bits = 34; // covers the 10 and 12 GiB layouts
|
||||
static constexpr size_t page_bits = 12;
|
||||
static constexpr size_t page_size = 1ULL << page_bits;
|
||||
static constexpr size_t page_mask = page_size - 1ULL;
|
||||
|
|
|
|||
|
|
@ -14,13 +14,16 @@
|
|||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/core/heap_mapper.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.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 "video_core/control/channel_state.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
|
|
@ -30,7 +33,9 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Con
|
|||
: nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{},
|
||||
gmmu{} {}
|
||||
|
||||
nvhost_as_gpu::~nvhost_as_gpu() = default;
|
||||
nvhost_as_gpu::~nvhost_as_gpu() {
|
||||
Nx2GpuMemoryRegistry::Get().RemoveListener(this);
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
|
||||
std::span<u8> output) {
|
||||
|
|
@ -49,8 +54,14 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> i
|
|||
return WrapFixed(this, &nvhost_as_gpu::MapBufferEx, input, output);
|
||||
case 0x8:
|
||||
return WrapFixed(this, &nvhost_as_gpu::GetVARegions1, input, output);
|
||||
case 0x9:
|
||||
return WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output);
|
||||
case 0x9: {
|
||||
const auto result = WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output);
|
||||
if (result == NvResult::Success && g_nx2_driver) {
|
||||
Nx2GpuMemoryRegistry::Get().AddListener(
|
||||
this, [this](u64 address, u64 size) { Nx2MapIdentity(address, size); });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case 0x14:
|
||||
return WrapVariable(this, &nvhost_as_gpu::Remap, input, output);
|
||||
default:
|
||||
|
|
@ -90,7 +101,35 @@ NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> i
|
|||
return NvResult::NotImplemented;
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
|
||||
void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
|
||||
nx2_session = session_id;
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::Nx2MapIdentity(u64 address, u64 size) {
|
||||
std::scoped_lock lock(mutex);
|
||||
auto* session = container.GetSession(nx2_session);
|
||||
if (!gmmu || session == nullptr) {
|
||||
LOG_ERROR(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x}: no address space or session",
|
||||
address, size);
|
||||
return;
|
||||
}
|
||||
DAddr device_address{};
|
||||
if (session->has_preallocated_area && session->mapper->IsInBounds(address, size)) {
|
||||
device_address = session->mapper->Map(address, size);
|
||||
} else {
|
||||
auto& smmu = system.Host1x().MemoryManager();
|
||||
device_address = smmu.Allocate(Common::AlignUp(size, 0x20000));
|
||||
if (device_address == 0) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x}: out of SMMU space",
|
||||
address, size);
|
||||
return;
|
||||
}
|
||||
smmu.Map(device_address, address, size, session->asid, true);
|
||||
}
|
||||
gmmu->Map(address, device_address, size, Tegra::PTEKind::PITCH, false);
|
||||
LOG_INFO(Service_NVDRV, "Switch 2 GPU memory {:#x}+{:#x} mapped at the same GPU address", address,
|
||||
size);
|
||||
}
|
||||
void nvhost_as_gpu::OnClose(DeviceFD fd) {}
|
||||
|
||||
NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
|
||||
|
|
|
|||
|
|
@ -221,6 +221,10 @@ private:
|
|||
bool absolute_big_page_limit{}; //!< Switch 2 layout: big-page limit is va_range_end
|
||||
} vm;
|
||||
std::unique_ptr<Tegra::MemoryManager> gmmu;
|
||||
|
||||
// Switch 2: maps GPU-accessible process memory at GPU address == CPU address.
|
||||
void Nx2MapIdentity(u64 address, u64 size);
|
||||
NvCore::SessionId nx2_session{};
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#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 "video_core/nx2.h"
|
||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||
|
||||
namespace Service::Nvidia::Devices {
|
||||
|
|
@ -290,6 +291,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics3(
|
|||
if (nx2) {
|
||||
nx2_caller = true;
|
||||
g_nx2_driver = true;
|
||||
Tegra::g_nx2_ampere = true;
|
||||
ApplyNx2Characteristics(params.gc);
|
||||
params.gpu_characteristics_buf_size = Nx2CharacteristicsSize;
|
||||
LOG_WARNING(Service_NVDRV, "Switch 2 caller, reporting Ampere chip 0x17E");
|
||||
|
|
|
|||
|
|
@ -367,6 +367,27 @@ static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementC
|
|||
return result;
|
||||
}
|
||||
|
||||
// Switch 2 pushbuffers signal completion with semaphore releases, never host1x syncpoint methods,
|
||||
// but the driver still has the kernel raise the syncpoint max by the submission's increment value
|
||||
// and then waits for that value. Append the increments after its commands.
|
||||
static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementCountCommandList(
|
||||
NvFence fence, u32 count) {
|
||||
boost::container::small_vector<Tegra::CommandHeader, 512> result{
|
||||
Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1,
|
||||
Tegra::SubmissionMode::Increasing),
|
||||
{},
|
||||
Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
|
||||
Tegra::SubmissionMode::Increasing),
|
||||
{}};
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
result.push_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
|
||||
Tegra::SubmissionMode::Increasing));
|
||||
result.push_back(
|
||||
BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementWithWfiCommandList(
|
||||
NvFence fence) {
|
||||
boost::container::small_vector<Tegra::CommandHeader, 512> result{
|
||||
|
|
@ -393,6 +414,13 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
|
|||
|
||||
const auto bind_id = channel_state->bind_id;
|
||||
|
||||
if (g_nx2_driver) {
|
||||
for (const auto& entry : entries.command_lists) {
|
||||
LOG_DEBUG(Service_NVDRV, "nx2 GPFIFO entry raw={:#018x} addr={:#x} size={:#x}", entry.raw,
|
||||
entry.addr.Value(), entry.size.Value());
|
||||
}
|
||||
}
|
||||
|
||||
auto& flags = params.flags;
|
||||
|
||||
if (flags.fence_wait.Value()) {
|
||||
|
|
@ -412,6 +440,11 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
|
|||
params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment);
|
||||
gpu.PushGPUEntries(bind_id, std::move(entries));
|
||||
|
||||
if (g_nx2_driver && !flags.fence_increment.Value() && increment > 0) {
|
||||
gpu.PushGPUEntries(bind_id,
|
||||
Tegra::CommandList{BuildIncrementCountCommandList(params.fence, increment)});
|
||||
}
|
||||
|
||||
if (flags.fence_increment.Value()) {
|
||||
if (flags.suppress_wfi.Value()) {
|
||||
gpu.PushGPUEntries(bind_id,
|
||||
|
|
@ -422,6 +455,12 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
|
|||
}
|
||||
}
|
||||
|
||||
if (g_nx2_driver) {
|
||||
LOG_DEBUG(Service_NVDRV,
|
||||
"nx2 SubmitGPFIFO entries={} flags={:#x} fence_out=({}, {}) guest_value={}",
|
||||
params.num_entries, flags.raw, params.fence.id, params.fence.value,
|
||||
system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(params.fence.id));
|
||||
}
|
||||
flags.raw = 0;
|
||||
|
||||
return NvResult::Success;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
|
|
@ -25,6 +30,46 @@ inline NvResult Nx2UnknownIoctl(const char* device, Ioctl command, std::span<con
|
|||
return NvResult::Success;
|
||||
}
|
||||
|
||||
// GPU-accessible memory a Switch 2 process registered through nx2:gpu (sub-session 101). The GPU
|
||||
// shares the process address space there: GPFIFO entries and pushbuffers use the CPU address as
|
||||
// the GPU address. Every nvhost-as-gpu address space maps each range at that address, including
|
||||
// ranges registered before the address space existed.
|
||||
class Nx2GpuMemoryRegistry {
|
||||
public:
|
||||
using Listener = std::function<void(u64 address, u64 size)>;
|
||||
|
||||
static Nx2GpuMemoryRegistry& Get() {
|
||||
static Nx2GpuMemoryRegistry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
void Register(u64 address, u64 size) {
|
||||
std::scoped_lock lk{mutex};
|
||||
ranges.emplace_back(address, size);
|
||||
for (auto& [owner, listener] : listeners) {
|
||||
listener(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
void AddListener(const void* owner, Listener listener) {
|
||||
std::scoped_lock lk{mutex};
|
||||
for (const auto& [address, size] : ranges) {
|
||||
listener(address, size);
|
||||
}
|
||||
listeners.insert_or_assign(owner, std::move(listener));
|
||||
}
|
||||
|
||||
void RemoveListener(const void* owner) {
|
||||
std::scoped_lock lk{mutex};
|
||||
listeners.erase(owner);
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::vector<std::pair<u64, u64>> ranges;
|
||||
std::map<const void*, Listener> listeners;
|
||||
};
|
||||
|
||||
// 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 {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@
|
|||
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#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"
|
||||
|
|
@ -17,9 +20,11 @@
|
|||
#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 "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"
|
||||
|
|
@ -79,19 +84,113 @@ public:
|
|||
// 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"} {
|
||||
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.
|
||||
|
|
@ -186,8 +285,13 @@ private:
|
|||
// Sub 101: (start u64, last u64, flags...) after CreateGpuAccessibleMemory's svc 0x80.
|
||||
void SubRegisterMemory(HLERequestContext& ctx) {
|
||||
const auto* b = ctx.CommandBuffer();
|
||||
LOG_WARNING(Service, "(STUBBED) nx2:gpu sub 101 start={:#x} last={:#x} [6]={:#x} [7]={:#x}",
|
||||
u64{b[2]} | (u64{b[3]} << 32), u64{b[4]} | (u64{b[5]} << 32), b[6], b[7]);
|
||||
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);
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
|
@ -275,6 +379,17 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
~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.
|
||||
|
|
@ -302,42 +417,109 @@ private:
|
|||
|
||||
// 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.
|
||||
// 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 = cmd_buf[data + 1];
|
||||
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 (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
|
||||
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: no read-only transfer memory");
|
||||
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:SyncptEvent0");
|
||||
m_syncpt_events[1] = m_context.CreateEvent("nx2:gsv:SyncptEvent1");
|
||||
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]->GetReadableEvent(),
|
||||
m_syncpt_events[1]->GetReadableEvent());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
void OpenViSession(HLERequestContext& ctx) {
|
||||
|
|
@ -372,8 +554,15 @@ private:
|
|||
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
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ add_library(video_core STATIC
|
|||
invalidation_accumulator.h
|
||||
memory_manager.cpp
|
||||
memory_manager.h
|
||||
nx2.h
|
||||
present.h
|
||||
pte_kind.h
|
||||
query_cache/bank_base.h
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "video_core/nx2.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
|
|
@ -380,6 +381,12 @@ void Maxwell3D::CallMacroMethod(Core::System& system, u32 method, const std::vec
|
|||
const u32 entry =
|
||||
((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size());
|
||||
|
||||
// Switch 2 uploads Turing+ (MME64) macros, which Eden's Fermi-style macro engine can't run.
|
||||
if (g_nx2_ampere) {
|
||||
LOG_DEBUG(HW_GPU, "Switch 2 macro {} with {} parameters skipped", entry, parameters.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the current macro.
|
||||
macro_engine.Execute(system, *this, macro_positions[entry], parameters);
|
||||
draw_manager.DrawDeferred(*this);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
|
|
@ -18,6 +20,7 @@
|
|||
#include "video_core/engines/puller.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/nx2.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
|
@ -35,6 +38,27 @@ void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_c
|
|||
if ((engine & ~Gf100BindClassMask) != 0 && (engine & ~Gf100BindValidMask) == 0) {
|
||||
engine &= Gf100BindClassMask;
|
||||
}
|
||||
// Switch 2 binds Ampere classes; run them on the Maxwell engines (same method layout for
|
||||
// most state). Class 0 unbinds the subchannel.
|
||||
switch (engine & 0xFFFF) {
|
||||
case 0xC797: // AMPERE_B
|
||||
engine = static_cast<u32>(EngineID::MAXWELL_B);
|
||||
break;
|
||||
case 0xC7C0: // AMPERE_COMPUTE_B
|
||||
engine = static_cast<u32>(EngineID::KEPLER_COMPUTE_B);
|
||||
break;
|
||||
case 0xC7B5: // AMPERE_DMA_COPY_B
|
||||
engine = static_cast<u32>(EngineID::MAXWELL_DMA_COPY_A);
|
||||
break;
|
||||
case 0x0:
|
||||
if (g_nx2_ampere) {
|
||||
LOG_DEBUG(HW_GPU, "Switch 2 unbinds subchannel {}", method_call.subchannel);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto engine_id = static_cast<EngineID>(engine);
|
||||
bound_engines[method_call.subchannel] = engine_id;
|
||||
|
|
@ -139,6 +163,11 @@ void Puller::ProcessSemaphoreAcquire(DmaPusher& dma_pusher) {
|
|||
|
||||
/// Calls a GPU puller method.
|
||||
void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
|
||||
static const bool nx2_trace = std::getenv("EDEN_NX2_PULLER_TRACE") != nullptr;
|
||||
if (nx2_trace) {
|
||||
LOG_INFO(HW_GPU, "nx2 puller sub={} method={:#x} arg={:#x}", method_call.subchannel,
|
||||
method_call.method, method_call.argument);
|
||||
}
|
||||
regs.reg_array[method_call.method] = method_call.argument;
|
||||
const auto method = static_cast<BufferMethods>(method_call.method);
|
||||
|
||||
|
|
|
|||
14
src/video_core/nx2.h
Normal file
14
src/video_core/nx2.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
// Set once a Switch 2 (Ampere, NVN2) driver is talking to the GPU. Its command streams use
|
||||
// Ampere classes and the Turing+ macro ISA, which Eden's Maxwell engines don't implement.
|
||||
inline std::atomic<bool> g_nx2_ampere{false};
|
||||
|
||||
} // namespace Tegra
|
||||
Loading…
Reference in a new issue