diff options
| author | Djihads <76681596+Djihads80@users.noreply.github.com> | 2026-10-29 02:25:36 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-10-29 02:25:36 +0100 |
| commit | 0bb5055b6541c74f47603a8dff0569a509cbaf18 (patch) | |
| tree | da827ff9c4f2f9a5309ee2804ee689f608c0ac2f /src/core | |
| parent | 1af7fe72927af9fc64aa4af117bc9b30043c4dc7 (diff) | |
| parent | 535b6769709eb22333af26269f1b16f37adce4db (diff) | |
Merge pull request #1 from Djihads80/yumekiru/ns2-progress-v2
Experimental NS2 support and accumulated GPU fixes
Diffstat (limited to 'src/core')
71 files changed, 4139 insertions, 68 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1b578f67ba..168037e5dc 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -460,6 +460,18 @@ add_library(core STATIC hle/service/am/service/application_proxy.h hle/service/am/service/application_proxy_service.cpp hle/service/am/service/application_proxy_service.h + hle/service/am/service/performance_functions.cpp + hle/service/am/service/performance_functions.h + hle/service/am/service/gpu_session.cpp + hle/service/am/service/gpu_session.h + hle/service/am/service/graphics_session.cpp + hle/service/am/service/graphics_session.h + hle/service/am/service/graphics_supervisor.cpp + hle/service/am/service/graphics_supervisor.h + hle/service/am/service/graphics_supervisor_protocol.h + hle/service/am/service/graphics_syncpoint_cache.h + hle/service/am/service/graphics_scaling_parameters_functions.cpp + hle/service/am/service/graphics_scaling_parameters_functions.h hle/service/am/service/audio_controller.cpp hle/service/am/service/audio_controller.h hle/service/am/service/common_state_getter.cpp @@ -855,6 +867,8 @@ add_library(core STATIC hle/service/nvdrv/devices/nvhost_ctrl_gpu.h hle/service/nvdrv/devices/nvhost_gpu.cpp hle/service/nvdrv/devices/nvhost_gpu.h + hle/service/nvdrv/devices/nvhost_tsg_gpu.cpp + hle/service/nvdrv/devices/nvhost_tsg_gpu.h hle/service/nvdrv/devices/nvhost_nvdec.cpp hle/service/nvdrv/devices/nvhost_nvdec.h hle/service/nvdrv/devices/nvhost_nvdec_common.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 894857a86d..b5190e40cf 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -194,6 +194,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s // Callbacks config.callbacks = std::addressof(*m_cb); + config.enable_pauth_returns = m_cb->m_process && m_cb->m_process->UsesPAuthReturns(); // Memory if (page_table) { @@ -444,6 +445,8 @@ void ArmDynarmic64::ClearInstructionCache() { } void ArmDynarmic64::InvalidateCacheRange(u64 addr, std::size_t size) { + // Refetch the source page too, including instructions restored by the debugger. + m_cb->last_code_addr = u64(-1); m_jit->InvalidateCacheRange(addr, size); } diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 97c2706abb..0087548978 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -21,6 +21,7 @@ #include "common/scope_exit.h" #include "common/settings.h" #include "core/device_memory.h" +#include "core/hle/kernel/board/nintendo/nx/k_system_control.h" #include "core/device_memory_manager.h" #include "core/memory.h" @@ -172,7 +173,7 @@ template <typename Traits> DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_) : physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())} , device_inter{nullptr} - , compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS)) + , compressed_device_addr(Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize() >> Memory::YUZU_PAGEBITS) , tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS) { impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>(); diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 63ca9d3fe3..b528523e67 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -20,6 +20,10 @@ ProgramMetadata::ProgramMetadata() = default; ProgramMetadata::~ProgramMetadata() = default; Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { + uses_extended_acid = false; + if (!file) { + return Loader::ResultStatus::ErrorBadNPDMHeader; + } const std::size_t total_size = file->GetSize(); if (total_size < sizeof(Header)) { return Loader::ResultStatus::ErrorBadNPDMHeader; @@ -29,17 +33,55 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { return Loader::ResultStatus::ErrorBadNPDMHeader; } - if (sizeof(AcidHeader) != file->ReadObject(&acid_header, npdm_header.acid_offset)) { + if (npdm_header.magic != std::array{'M', 'E', 'T', 'A'}) { + return Loader::ResultStatus::ErrorBadNPDMHeader; + } + + const auto contains = [](u64 offset, u64 size, u64 limit) { + return offset <= limit && size <= limit - offset; + }; + const u64 acid_offset = npdm_header.acid_offset; + const u64 acid_size = npdm_header.acid_size; + const u64 aci_offset = npdm_header.aci_offset; + const u64 aci_size = npdm_header.aci_size; + if (acid_offset < sizeof(Header) || !contains(acid_offset, acid_size, total_size)) { return Loader::ResultStatus::ErrorBadACIDHeader; } - if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) { + const auto read_acid = [&](u64 body_offset) { + return contains(body_offset, sizeof(AcidHeader), acid_size) && + file->ReadObject(&acid_header, acid_offset + body_offset) == sizeof(AcidHeader) && + acid_header.magic == std::array{'A', 'C', 'I', 'D'}; + }; + if (!read_acid(0x200)) { + if (!read_acid(0x300)) { + return Loader::ResultStatus::ErrorBadACIDHeader; + } + uses_extended_acid = true; + } + + if (aci_offset < sizeof(Header) || !contains(aci_offset, aci_size, total_size) || + aci_size < sizeof(AciHeader) || + sizeof(AciHeader) != file->ReadObject(&aci_header, aci_offset) || + aci_header.magic != std::array{'A', 'C', 'I', '0'}) { return Loader::ResultStatus::ErrorBadACIHeader; } + constexpr u64 fac_read_size = 4 + sizeof(FileAccessControl::permissions) + + sizeof(FileAccessControl::unknown); + if (acid_header.fac_size < fac_read_size || + !contains(acid_header.fac_offset, acid_header.fac_size, acid_size)) { + return Loader::ResultStatus::ErrorBadFileAccessControl; + } + constexpr u64 fah_read_size = 4 + sizeof(FileAccessHeader::permissions) + 4 * sizeof(u32); + if (aci_header.fah_size < fah_read_size || + !contains(aci_header.fah_offset, aci_header.fah_size, aci_size)) { + return Loader::ResultStatus::ErrorBadFileAccessHeader; + } + // Load acid_file_access per-component instead of the entire struct, since this struct does not // reflect the layout of the real data. - std::size_t current_offset = acid_header.fac_offset; + std::size_t current_offset = acid_offset + acid_header.fac_offset; if (sizeof(FileAccessControl::version) != file->ReadBytes(&acid_file_access.version, sizeof(FileAccessControl::version), current_offset)) { @@ -57,7 +99,7 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { } // Load aci_file_access per-component instead of the entire struct, same as acid_file_access - current_offset = aci_header.fah_offset; + current_offset = aci_offset + aci_header.fah_offset; if (sizeof(FileAccessHeader::version) != file->ReadBytes(&aci_file_access.version, sizeof(FileAccessHeader::version), current_offset)) { @@ -89,6 +131,10 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { return Loader::ResultStatus::ErrorBadFileAccessHeader; } + if (aci_header.kac_size % sizeof(u32) != 0 || + !contains(aci_header.kac_offset, aci_header.kac_size, aci_size)) { + return Loader::ResultStatus::ErrorBadKernelCapabilityDescriptors; + } aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32)); const u64 read_size = aci_header.kac_size; const u64 read_offset = npdm_header.aci_offset + aci_header.kac_offset; @@ -128,6 +174,7 @@ void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address u32 main_thread_stack_size, u64 title_id, u64 filesystem_permissions, u32 system_resource_size, KernelCapabilityDescriptors capabilities) { + uses_extended_acid = false; npdm_header.has_64_bit_instructions.Assign(is_64_bit); npdm_header.address_space_type.Assign(address_space); npdm_header.main_thread_priority = static_cast<u8>(main_thread_prio); diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 115e6d6cd1..eb4cf11894 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -77,6 +77,9 @@ public: u8 GetMainThreadCore() const; u32 GetMainThreadStackSize() const; u64 GetTitleID() const; + bool UsesExtendedAcid() const { + return uses_extended_acid; + } u64 GetFilesystemPermissions() const; u32 GetSystemResourceSize() const; PoolPartition GetPoolPartition() const; @@ -116,8 +119,6 @@ private: static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); struct AcidHeader { - std::array<u8, 0x100> signature; - std::array<u8, 0x100> nca_modulus; std::array<char, 4> magic; u32_le nca_size; std::array<u8, 0x4> reserved; @@ -139,7 +140,7 @@ private: INSERT_PADDING_BYTES(0x8); }; - static_assert(sizeof(AcidHeader) == 0x240, "ACID header structure size is wrong"); + static_assert(sizeof(AcidHeader) == 0x40, "ACID header body size is wrong"); struct AciHeader { std::array<char, 4> magic; @@ -177,6 +178,7 @@ private: }; Header npdm_header{}; + bool uses_extended_acid{}; AciHeader aci_header{}; AcidHeader acid_header{}; diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h index f6543bdb3a..2abca31d87 100644 --- a/src/core/hle/kernel/k_memory_block.h +++ b/src/core/hle/kernel/k_memory_block.h @@ -195,7 +195,9 @@ enum class KMemoryAttribute : u8 { Uncached = static_cast<u8>(Svc::MemoryAttribute::Uncached), PermissionLocked = static_cast<u8>(Svc::MemoryAttribute::PermissionLocked), - SetMask = Uncached | PermissionLocked, + GpuAccessible = static_cast<u8>(Svc::MemoryAttribute::GpuAccessible), + + SetMask = Uncached | PermissionLocked | GpuAccessible, }; DECLARE_ENUM_FLAG_OPERATORS(KMemoryAttribute); diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index 2a0a7d49d5..fd5d2c2dce 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -2102,7 +2102,8 @@ Result KPageTableBase::SetMemoryAttribute(KProcessAddress addr, size_t size, KMe constexpr KMemoryAttribute AttributeTestMask = ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared); const KMemoryState state_test_mask = - (True(mask & KMemoryAttribute::Uncached) ? KMemoryState::FlagCanChangeAttribute + (True(mask & (KMemoryAttribute::Uncached | KMemoryAttribute::GpuAccessible)) + ? KMemoryState::FlagCanChangeAttribute : KMemoryState::None) | (True(mask & KMemoryAttribute::PermissionLocked) ? KMemoryState::FlagCanPermissionLock : KMemoryState::None); diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 27d1b1bfc4..813d8e9402 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -1260,6 +1260,7 @@ Result KProcess::LoadFromMetadata(KernelCore& kernel, const FileSys::ProgramMeta // Assign remaining properties. m_ideal_core_id = metadata.GetMainThreadCore(); + m_uses_pauth_returns = metadata.UsesExtendedAcid(); // Set up emulation context. this->InitializeInterfaces(kernel); diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index b5693751c3..ca96591ed0 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -135,6 +135,7 @@ private: bool m_is_signaled : 1 = false; bool m_is_initialized : 1 = false; bool m_is_application : 1 = false; + bool m_uses_pauth_returns : 1 = false; bool m_is_default_application_system_resource : 1 = false; bool m_is_suspended : 1 = false; bool m_is_immortal : 1 = false; @@ -248,6 +249,10 @@ public: return m_is_application; } + bool UsesPAuthReturns() const { + return m_uses_pauth_returns; + } + bool IsDefaultApplicationSystemResource() const { return m_is_default_application_system_resource; } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 4f1d849209..c7beb9c3ce 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -120,13 +120,13 @@ struct KernelCore::Impl { } void TerminateAllProcesses() { - std::scoped_lock lk{process_list_lock}; - for (auto& process : process_list) { - process->Terminate(system.Kernel()); - process->Close(system.Kernel()); - process = nullptr; + auto& kernel = system.Kernel(); + // Applet cleanup can remove processes while termination waits for its threads. + auto processes = kernel.GetProcessList(); + for (auto& process : processes) { + process->Terminate(kernel); + kernel.RemoveProcess(process.GetPointerUnsafe()); } - process_list.clear(); } void Shutdown(KernelCore& kernel) { diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 3542d5a3b6..c91ae43dd8 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -2370,6 +2370,10 @@ static void Call32(Core::System& system, u32 imm, std::span<uint64_t, 8> args) { static void Call64(Core::System& system, u32 imm, std::span<uint64_t, 8> args) { switch (SvcId(imm)) { + case SvcId::SetGpuMemoryAttribute: + args[0] = SetGpuMemoryAttribute(system, args[0], args[1], + static_cast<u32>(args[2]), static_cast<u32>(args[3])).raw; + return; case SvcId::SetHeapSize: return SvcWrap_SetHeapSize64(system, args); case SvcId::SetMemoryPermission: return SvcWrap_SetMemoryPermission64(system, args); case SvcId::SetMemoryAttribute: return SvcWrap_SetMemoryAttribute64(system, args); diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index 356c1c109a..422b8b8719 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -22,6 +22,7 @@ namespace Kernel::Svc { Result SetHeapSize(Core::System& system, uint64_t* out_address, uint64_t size); Result SetMemoryPermission(Core::System& system, uint64_t address, uint64_t size, MemoryPermission perm); Result SetMemoryAttribute(Core::System& system, uint64_t address, uint64_t size, uint32_t mask, uint32_t attr); +Result SetGpuMemoryAttribute(Core::System& system, uint64_t address, uint64_t size, uint32_t mask, uint32_t attr); Result MapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size); Result UnmapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size); Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address); @@ -504,6 +505,7 @@ enum class SvcId : u32 { CreateResourceLimit = 0x7d, SetResourceLimitLimitValue = 0x7e, CallSecureMonitor = 0x7f, + SetGpuMemoryAttribute = 0x80, MapInsecureMemory = 0x90, UnmapInsecureMemory = 0x91, }; diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp index 863bb58fa8..aea7407542 100644 --- a/src/core/hle/kernel/svc/svc_info.cpp +++ b/src/core/hle/kernel/svc/svc_info.cpp @@ -14,11 +14,32 @@ #include "core/hle/kernel/svc_version.h" namespace Kernel::Svc { +namespace { +// Identified from the extended SDK's RemoteRegionManager initialization. +constexpr InfoType RemoteRegionAddress = static_cast<InfoType>(29); +constexpr InfoType RemoteRegionSize = static_cast<InfoType>(30); +constexpr InfoType RemoteMemoryUsed = static_cast<InfoType>(31); +constexpr InfoType RemoteMemoryTotal = static_cast<InfoType>(32); +constexpr InfoType MemoryMappingAlignment = static_cast<InfoType>(33); +} // namespace /// Gets system/memory information for the current process Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle handle, u64 info_sub_id) { LOG_TRACE(Kernel_SVC, "called info_id={:#x}, info_sub_id={:#x}, handle={:#08x}", info_id_type, info_sub_id, handle); u32 info_id = u32(info_id_type); + if (info_id_type == RemoteRegionAddress || info_id_type == RemoteRegionSize || + info_id_type == RemoteMemoryUsed || info_id_type == RemoteMemoryTotal || + info_id_type == MemoryMappingAlignment) { + R_UNLESS(info_sub_id == 0, ResultInvalidEnumValue); + const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(system.Kernel(), handle); + R_UNLESS(process.IsNotNull(), ResultInvalidHandle); + R_UNLESS(process->UsesPAuthReturns(), ResultInvalidEnumValue); + // Remote mappings are absent; ordinary mappings use the kernel page size. + *result = info_id_type == MemoryMappingAlignment ? PageSize : 0; + LOG_DEBUG(Kernel_SVC, "Extended memory info_id={:#x}, value={:#x}", info_id, *result); + R_SUCCEED(); + } switch (info_id_type) { case InfoType::CoreMask: case InfoType::PriorityMask: @@ -124,7 +145,8 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle default: break; } - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id); + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#x}, sub_id={:#x}, handle={:#x}", + info_id, info_sub_id, handle); R_THROW(ResultInvalidEnumValue); } case InfoType::DebuggerAttached: @@ -258,7 +280,8 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle R_SUCCEED(); } default: - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id); + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#x}, sub_id={:#x}, handle={:#x}", + info_id, info_sub_id, handle); R_THROW(ResultInvalidEnumValue); } } diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp index 875590d6f7..fadde6c093 100644 --- a/src/core/hle/kernel/svc/svc_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_ipc.cpp @@ -13,6 +13,7 @@ #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_results.h" @@ -142,7 +143,23 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes /// Makes a blocking IPC call to a service. Result SendSyncRequest(Core::System& system, Handle session_handle) { - R_RETURN(SendSyncRequestImpl(system.Kernel(), 0, 0, session_handle)); + auto& process = GetCurrentProcess(system.Kernel()); + if (!process.UsesPAuthReturns()) { + R_RETURN(SendSyncRequestImpl(system.Kernel(), 0, 0, session_handle)); + } + const auto tls = GetCurrentThread(system.Kernel()).GetTlsAddress(); + const u64 header = process.GetMemory().Read64(tls); + const bool trace_tipc = (header & 0xffff) >= 16; + if (trace_tipc) { + LOG_DEBUG(Kernel_SVC, "TIPC send handle={:#x}, header={:#x}, special={:#x}", + session_handle, header, process.GetMemory().Read64(tls + 8)); + } + const auto result = SendSyncRequestImpl(system.Kernel(), 0, 0, session_handle); + if (trace_tipc) { + LOG_DEBUG(Kernel_SVC, "TIPC receive result={:#x}, header={:#x}, data={:#x}", + result.raw, process.GetMemory().Read64(tls), process.GetMemory().Read64(tls + 8)); + } + R_RETURN(result); } Result SendSyncRequestWithUserBuffer(Core::System& system, uint64_t message, uint64_t buffer_size, diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp index 970912fe50..53c0d43456 100644 --- a/src/core/hle/kernel/svc/svc_memory.cpp +++ b/src/core/hle/kernel/svc/svc_memory.cpp @@ -126,6 +126,31 @@ Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); // Set the memory attribute. + const auto result = page_table.SetMemoryAttribute(address, size, + static_cast<KMemoryAttribute>(mask), + static_cast<KMemoryAttribute>(attr)); + if (GetCurrentProcess(system.Kernel()).UsesPAuthReturns()) { + LOG_DEBUG(Kernel_SVC, "native memory attribute result={:#x}", result.raw); + } + R_RETURN(result); +} + +// Captured native SDK callers set bit 0x20 before registering GPU-accessible +// memory and clear it during destruction. This tracks eligibility on real mapped +// memory; GPU mappings and submission remain separate operations. +Result SetGpuMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) { + auto& process = GetCurrentProcess(system.Kernel()); + R_UNLESS(process.UsesPAuthReturns(), ResultNotImplemented); + LOG_DEBUG(Kernel_SVC, "GPU memory address={:#x}, size={:#x}, mask={:#x}, attr={:#x}", + address, size, mask, attr); + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(size > 0 && Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(address < address + size, ResultInvalidCurrentMemory); + constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::GpuAccessible); + R_UNLESS((attr & ~mask) == 0 && ((mask | attr) & ~SupportedMask) == 0, + ResultInvalidCombination); + auto& page_table = process.GetPageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); R_RETURN(page_table.SetMemoryAttribute(address, size, static_cast<KMemoryAttribute>(mask), static_cast<KMemoryAttribute>(attr))); } diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index 2b019c52d1..2f72f60215 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -15,13 +15,24 @@ namespace Kernel::Svc { +namespace { +void LogInvalidNativeSyncHandle(Core::System& system, const char* operation, Handle handle) { + if (!GetCurrentProcess(system.Kernel()).UsesPAuthReturns()) { return; } + const auto& context = GetCurrentThread(system.Kernel()).GetContext(); + LOG_DEBUG(Kernel_SVC, "native {} invalid handle={:#x}, pc={:#x}, lr={:#x}, fp={:#x}", + operation, handle, context.pc, context.lr, context.fp); +} +} + /// Close a handle Result CloseHandle(Core::System& system, Handle handle) { LOG_TRACE(Kernel_SVC, "Closing handle {:#08x}", handle); // Remove the handle. - R_UNLESS(GetCurrentProcess(system.Kernel()).GetHandleTable().Remove(system.Kernel(), handle), - ResultInvalidHandle); + if (!GetCurrentProcess(system.Kernel()).GetHandleTable().Remove(system.Kernel(), handle)) { + LogInvalidNativeSyncHandle(system, "CloseHandle", handle); + R_THROW(ResultInvalidHandle); + } R_SUCCEED(); } @@ -49,6 +60,7 @@ Result ResetSignal(Core::System& system, Handle handle) { } } + LogInvalidNativeSyncHandle(system, "ResetSignal", handle); R_THROW(ResultInvalidHandle); } @@ -72,7 +84,10 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha R_UNLESS(GetCurrentMemory(system.Kernel()).ReadBlock(user_handles, handles.data(), sizeof(Handle) * num_handles), ResultInvalidPointer); // Convert the handles to objects. - R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(system.Kernel(), objs.data(), handles.data(), num_handles), ResultInvalidHandle); + if (!handle_table.GetMultipleObjects<KSynchronizationObject>(system.Kernel(), objs.data(), handles.data(), num_handles)) { + for (s32 i = 0; i < num_handles; ++i) { LogInvalidNativeSyncHandle(system, "WaitSynchronization set", handles[i]); } + R_THROW(ResultInvalidHandle); + } } // Ensure handles are closed when we're done. diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index dbca13dd26..b4eb0b5719 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -16,8 +16,11 @@ namespace Kernel::Svc { namespace { -constexpr bool IsValidVirtualCoreId(int32_t core_id) { - return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES)); +bool IsValidVirtualCoreId(const KProcess& process, int32_t core_id) { + // Extended processes use the kernel's existing virtual-core mapping. + const auto core_count = process.UsesPAuthReturns() ? Core::Hardware::NumVirtualCores + : Core::Hardware::NUM_CPU_CORES; + return 0 <= core_id && core_id < static_cast<int32_t>(core_count); } } // Anonymous namespace @@ -38,7 +41,7 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u } // Validate arguments. - R_UNLESS(IsValidVirtualCoreId(core_id), ResultInvalidCoreId); + R_UNLESS(IsValidVirtualCoreId(process, core_id), ResultInvalidCoreId); R_UNLESS(((1ull << core_id) & process.GetCoreMask()) != 0, ResultInvalidCoreId); R_UNLESS(HighestThreadPriority <= priority && priority <= LowestThreadPriority, @@ -262,7 +265,7 @@ Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id R_UNLESS(affinity_mask != 0, ResultInvalidCombination); // Validate the core id. - if (IsValidVirtualCoreId(core_id)) { + if (IsValidVirtualCoreId(GetCurrentProcess(system.Kernel()), core_id)) { R_UNLESS(((1ULL << core_id) & affinity_mask) != 0, ResultInvalidCombination); } else { R_UNLESS(core_id == IdealCoreNoUpdate || core_id == IdealCoreDontCare, diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index b0f4796da4..c7b68cc0df 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -50,6 +50,7 @@ enum class MemoryAttribute : u32 { DeviceShared = (1 << 2), Uncached = (1 << 3), PermissionLocked = (1 << 4), + GpuAccessible = (1 << 5), }; DECLARE_ENUM_FLAG_OPERATORS(MemoryAttribute); diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h index 571fa65219..69d2876363 100644 --- a/src/core/hle/service/am/applet.h +++ b/src/core/hle/service/am/applet.h @@ -84,6 +84,7 @@ struct Applet { // Application functions bool game_play_recording_supported{}; bool media_playback_state{}; + bool graphics_scale_changed_notification_enabled{}; GamePlayRecordingState game_play_recording_state{GamePlayRecordingState::Disabled}; bool jit_service_launched{}; bool application_crash_report_enabled{}; diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index 90d292c4a2..f86806dd3c 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -118,7 +118,8 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {220, nullptr, "Unknown220"}, // [20.0.0+] {300, nullptr, "CreateMovieWriter"}, // [19.0.0+] {310, nullptr, "Unknown310"}, // [20.0.0+] - {320, nullptr, "Unknown320"}, // [20.0.0+] + {320, D<&IApplicationFunctions::GetScreenShotAndContinuousRecordingInitiallyEnabled>, + "GetScreenShotAndContinuousRecordingInitiallyEnabled"}, {330, D<&IApplicationFunctions::Unknown330>, "Unknown330"}, // [20.0.0+] {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, @@ -544,6 +545,17 @@ Result IApplicationFunctions::GetUnknownEvent210( R_SUCCEED(); } +Result IApplicationFunctions::GetScreenShotAndContinuousRecordingInitiallyEnabled( + Out<bool> out_screenshot_enabled, Out<bool> out_recording_enabled) { + std::scoped_lock lk{m_applet->lock}; + *out_screenshot_enabled = m_applet->screenshot_permission != ScreenshotPermission::Disable; + *out_recording_enabled = m_applet->game_play_recording_supported && + m_applet->game_play_recording_state == GamePlayRecordingState::Enabled; + LOG_DEBUG(Service_AM, "called, screenshot={}, recording={}", *out_screenshot_enabled, + *out_recording_enabled); + R_SUCCEED(); +} + Result IApplicationFunctions::Unknown330(Out<u8> out) { LOG_DEBUG(Service_AM, "called"); *out = 0; diff --git a/src/core/hle/service/am/service/application_functions.h b/src/core/hle/service/am/service/application_functions.h index b97ed7e25e..172b712f14 100644 --- a/src/core/hle/service/am/service/application_functions.h +++ b/src/core/hle/service/am/service/application_functions.h @@ -82,6 +82,8 @@ private: Result GetHealthWarningDisappearedSystemEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result CreateApplicationAndRequestToStart(u64 application_id); Result GetUnknownEvent210(OutCopyHandle<Kernel::KReadableEvent> out_event); + Result GetScreenShotAndContinuousRecordingInitiallyEnabled( + Out<bool> out_screenshot_enabled, Out<bool> out_recording_enabled); Result Unknown330(Out<u8> out); Result PrepareForJit(); diff --git a/src/core/hle/service/am/service/application_proxy.cpp b/src/core/hle/service/am/service/application_proxy.cpp index df438e1e17..02502ae764 100644 --- a/src/core/hle/service/am/service/application_proxy.cpp +++ b/src/core/hle/service/am/service/application_proxy.cpp @@ -11,7 +11,9 @@ #include "core/hle/service/am/service/common_state_getter.h" #include "core/hle/service/am/service/debug_functions.h" #include "core/hle/service/am/service/display_controller.h" +#include "core/hle/service/am/service/graphics_scaling_parameters_functions.h" #include "core/hle/service/am/service/library_applet_creator.h" +#include "core/hle/service/am/service/performance_functions.h" #include "core/hle/service/am/service/process_winding_controller.h" #include "core/hle/service/am/service/self_controller.h" #include "core/hle/service/am/service/window_controller.h" @@ -33,6 +35,9 @@ IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr<Appl {10, D<&IApplicationProxy::GetProcessWindingController>, "GetProcessWindingController"}, {11, D<&IApplicationProxy::GetLibraryAppletCreator>, "GetLibraryAppletCreator"}, {20, D<&IApplicationProxy::GetApplicationFunctions>, "GetApplicationFunctions"}, + {40, D<&IApplicationProxy::GetPerformanceFunctions>, "GetPerformanceFunctions"}, + {60, D<&IApplicationProxy::GetGraphicsScalingParametersFunctions>, + "GetGraphicsScalingParametersFunctions"}, {1000, D<&IApplicationProxy::GetDebugFunctions>, "GetDebugFunctions"}, }; // clang-format on @@ -106,4 +111,18 @@ Result IApplicationProxy::GetApplicationFunctions( R_SUCCEED(); } +Result IApplicationProxy::GetPerformanceFunctions( + Out<SharedPointer<IPerformanceFunctions>> out_functions) { + LOG_DEBUG(Service_AM, "called"); + *out_functions = std::make_shared<IPerformanceFunctions>(system); + R_SUCCEED(); +} + +Result IApplicationProxy::GetGraphicsScalingParametersFunctions( + Out<SharedPointer<IGraphicsScalingParametersFunctions>> out_functions) { + LOG_DEBUG(Service_AM, "called"); + *out_functions = std::make_shared<IGraphicsScalingParametersFunctions>(system, m_applet); + R_SUCCEED(); +} + } // namespace Service::AM diff --git a/src/core/hle/service/am/service/application_proxy.h b/src/core/hle/service/am/service/application_proxy.h index b56f597605..aa1ab1938a 100644 --- a/src/core/hle/service/am/service/application_proxy.h +++ b/src/core/hle/service/am/service/application_proxy.h @@ -17,7 +17,9 @@ class IApplicationFunctions; class ICommonStateGetter; class IDebugFunctions; class IDisplayController; +class IGraphicsScalingParametersFunctions; class ILibraryAppletCreator; +class IPerformanceFunctions; class IProcessWindingController; class ISelfController; class IWindowController; @@ -43,6 +45,10 @@ private: Result GetApplicationFunctions( Out<SharedPointer<IApplicationFunctions>> out_application_functions); + Result GetPerformanceFunctions(Out<SharedPointer<IPerformanceFunctions>> out_functions); + Result GetGraphicsScalingParametersFunctions( + Out<SharedPointer<IGraphicsScalingParametersFunctions>> out_functions); + private: WindowSystem& m_window_system; Kernel::KProcess* const m_process; diff --git a/src/core/hle/service/am/service/application_proxy_service.cpp b/src/core/hle/service/am/service/application_proxy_service.cpp index 7727951d8c..da11cce94d 100644 --- a/src/core/hle/service/am/service/application_proxy_service.cpp +++ b/src/core/hle/service/am/service/application_proxy_service.cpp @@ -19,6 +19,8 @@ IApplicationProxyService::IApplicationProxyService(Core::System& system_, : ServiceFramework{system_, "appletOE"}, m_window_system{window_system} { static const FunctionInfo functions[] = { {0, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy"}, + {10, D<&IApplicationProxyService::OpenApplicationProxyWithFlags>, + "OpenApplicationProxyWithFlags"}, }; RegisterHandlers(functions); } @@ -40,6 +42,15 @@ Result IApplicationProxyService::OpenApplicationProxy( } } +Result IApplicationProxyService::OpenApplicationProxyWithFlags( + Out<SharedPointer<IApplicationProxy>> out_application_proxy, u32 flags, ClientProcessId pid, + InCopyHandle<Kernel::KProcess> process_handle) { + LOG_DEBUG(Service_AM, "called, flags={:#x}", flags); + // The extended SDK supplies an additional u32 before the process ID. + R_UNLESS(flags == 0 || flags == 2, ResultUnknown); + R_RETURN(OpenApplicationProxy(out_application_proxy, pid, process_handle)); +} + std::shared_ptr<Applet> IApplicationProxyService::GetAppletFromProcessId(ProcessId process_id) { return m_window_system.GetByAppletResourceUserId(process_id.pid); } diff --git a/src/core/hle/service/am/service/application_proxy_service.h b/src/core/hle/service/am/service/application_proxy_service.h index fce860a7c1..5812f6133a 100644 --- a/src/core/hle/service/am/service/application_proxy_service.h +++ b/src/core/hle/service/am/service/application_proxy_service.h @@ -26,6 +26,10 @@ private: Result OpenApplicationProxy(Out<SharedPointer<IApplicationProxy>> out_application_proxy, ClientProcessId pid, InCopyHandle<Kernel::KProcess> process_handle); + Result OpenApplicationProxyWithFlags( + Out<SharedPointer<IApplicationProxy>> out_application_proxy, u32 flags, ClientProcessId pid, + InCopyHandle<Kernel::KProcess> process_handle); + private: std::shared_ptr<Applet> GetAppletFromProcessId(ProcessId pid); diff --git a/src/core/hle/service/am/service/gpu_memory_registration.h b/src/core/hle/service/am/service/gpu_memory_registration.h new file mode 100644 index 0000000000..37ac7fd377 --- /dev/null +++ b/src/core/hle/service/am/service/gpu_memory_registration.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once +#include <optional> +#include <span> +#include "common/common_types.h" + +namespace Service::AM { +struct GpuMemoryRegistration { + u64 begin; + u64 end; + u8 flags; + u8 device; +}; + +constexpr std::optional<GpuMemoryRegistration> DecodeGpuMemoryRegistration(std::span<const u32> packet) { + if (packet.size() < 8 || packet[0] != 0x75 || packet[1] != 6) return std::nullopt; + // The SDK writes STRB at offsets 0x18 and 0x1c, leaving three padding bytes. + return GpuMemoryRegistration{u64{packet[2]} | (u64{packet[3]} << 32), + u64{packet[4]} | (u64{packet[5]} << 32), static_cast<u8>(packet[6]), static_cast<u8>(packet[7])}; +} +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/gpu_session.cpp b/src/core/hle/service/am/service/gpu_session.cpp new file mode 100644 index 0000000000..a83381d7bc --- /dev/null +++ b/src/core/hle/service/am/service/gpu_session.cpp @@ -0,0 +1,633 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <algorithm> +#include <cstring> +#include <mutex> +#include <vector> +#include "common/swap.h" +#include "common/alignment.h" +#include "core/hle/service/nvdrv/core/nvmap.h" +#include "core/core.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/service/am/service/graphics_session.h" +#include "core/hle/service/am/service/gpu_session.h" +#include "core/hle/service/am/service/gpu_memory_registration.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" +#include "core/hle/service/nvdrv/devices/native_gpu_kind.h" + +namespace Service::AM { +struct GpuDeviceRegistry { + GpuDeviceRegistry(Core::System& system, std::shared_ptr<Nvidia::Module> module_, Kernel::KProcess* process_) + : context{system, "GpuDevices"}, module{std::move(module_)}, process{process_} { + process->Open(context.kernel); + } + ~GpuDeviceRegistry() { + for (const auto& fd : address_spaces) { if (fd) { module->Close(*fd); } } + if (nv_session) { module->GetContainer().CloseSession(Nvidia::NvCore::SessionId{*nv_session}); } + process->Close(context.kernel); + for (auto* event : events) { context.CloseEvent(event); } + } + KernelHelpers::ServiceContext context; + std::array<Kernel::KEvent*, 64> events{}; + struct Device { u64 name; u64 debug_label; }; + std::mutex mutex; + std::shared_ptr<Nvidia::Module> module; + Kernel::KProcess* process; + std::array<std::optional<s32>, 8> address_spaces{}; + std::array<bool, 8> address_space_registered{}; + std::optional<size_t> nv_session; + std::array<std::optional<Device>, 256> devices{}; +}; +namespace { +class IGpuWorker final : public ServiceFramework<IGpuWorker> { +public: + IGpuWorker(Core::System& system, std::shared_ptr<Nvidia::Module> module, + Kernel::KProcess* process, std::shared_ptr<GpuDeviceRegistry> devices) + : ServiceFramework{system, "IGpuWorker"}, m_devices{std::move(devices)}, + m_module{std::move(module)}, m_process{process} { + m_process->Open(kernel); + static const FunctionInfo functions[] = { + {101, &IGpuWorker::CreateGpuAccessibleMemory, "CreateGpuAccessibleMemory"}, + {102, &IGpuWorker::DestroyGpuAccessibleMemory, "DestroyGpuAccessibleMemory"}, + {211, &IGpuWorker::NormalMapGpuMemory4K, "NormalMapGpuMemory4K"}, + {212, &IGpuWorker::NormalModifyGpuMemory4K, "NormalModifyGpuMemory4K"}, + {213, &IGpuWorker::NormalUnmapGpuMemory4K, "NormalUnmapGpuMemory4K"}, + {214, &IGpuWorker::CreateSparseRegion, "CreateSparseRegion"}, + {215, &IGpuWorker::DestroySparseRegion, "DestroySparseRegion"}, + }; + RegisterHandlersTipc(functions); + } + ~IGpuWorker() override { + for (const auto& mapping : m_mappings) { + if (auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(mapping.as_id)) { + as->UnmapNative4K(mapping.address, mapping.size); + } + } + for (const auto& region : m_sparse_regions) { + if (auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(region.as_id)) { + as->DestroyNativeSparseRegion(region.address, region.size); + } + } + auto& nvmap = m_module->GetContainer().GetNvMapFile(); + for (const auto id : m_memory) { + if (const auto info = nvmap.FreeHandle(id, false); info && info->can_unlock) { + const auto result = m_process->GetPageTable().UnlockForDeviceAddressSpace( + info->address, info->size); + if (result.IsError()) { + LOG_ERROR(Service_AM, "GPU memory cleanup failed: {:#x}", result.raw); + } + } + } + if (m_nv_session) { + m_module->GetContainer().CloseSession(Nvidia::NvCore::SessionId{*m_nv_session}); + } + m_process->Close(kernel); + } + Result HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) override { + if (!ctx.IsTipc() || handlers_tipc.contains(ctx.GetCommand())) { + return ServiceFrameworkBase::HandleSyncRequest(session, ctx); + } + const auto guard = LockService(); + InvokeRequestTipc(ctx); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultNotImplemented.raw); + return system.IsPoweredOn() ? ctx.WriteToOutgoingCommandBuffer() : ResultSuccess; + } +private: + struct Mapping { u32 as_id; u64 address; u64 size; u32 memory_id; }; + std::vector<Mapping> m_mappings; + std::vector<Mapping> m_sparse_regions; + static u64 ReadPair(const u32* packet, size_t index) { + return u64{packet[index]} | (u64{packet[index + 1]} << 32); + } + bool OwnsAddressSpace(u32 id) const { + for (size_t i = 0; i < m_devices->address_spaces.size(); ++i) { + if (m_devices->address_spaces[i] && + static_cast<u32>(*m_devices->address_spaces[i]) == id && + m_devices->address_space_registered[i]) { return true; } + } + return false; + } + void CreateSparseRegion(HLERequestContext& ctx) { + const std::scoped_lock device_lock{m_devices->mutex}; + const auto* p = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + const u64 begin = ReadPair(p, 3), end = ReadPair(p, 5); + const u32 notification = p[11]; + if (p[0] == 0xe6 && p[1] == 10 && notification < m_devices->events.size() && m_devices->events[notification]) { + result = Kernel::ResultNotFound; + const u32 shift = p[7] & 0xff, device = p[9] & 0xff; + if (OwnsAddressSpace(p[2]) && device < m_devices->devices.size() && m_devices->devices[device]) { + result = Kernel::ResultInvalidArgument; + if ((shift == 12 || shift == 16) && (p[8] & 0xff) <= 1 && (p[10] & 0xff) <= 1 && + end >= begin && end != ~u64{0} && Common::IsAligned(begin, 1ULL << shift) && + Common::IsAligned(end - begin + 1, 1ULL << shift)) { + const auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(p[2]); + result = as && as->CreateNativeSparseRegion(begin, end - begin + 1) == Nvidia::NvResult::Success + ? ResultSuccess : Kernel::ResultInvalidCurrentMemory; + if (result.IsSuccess()) { m_sparse_regions.push_back({p[2], begin, end - begin + 1, 0}); } + } + } + m_devices->events[notification]->Signal(kernel); + } + LOG_DEBUG(Service_AM, "GPU sparse region as={}, VA={:#x}..{:#x}, result={:#x}", p[2], begin, end, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void DestroySparseRegion(HLERequestContext& ctx) { + const std::scoped_lock device_lock{m_devices->mutex}; + const auto* p = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + const u64 begin = ReadPair(p, 3), end = ReadPair(p, 5); + const u32 notification = p[8]; + if (p[0] == 0xe7 && p[1] == 7 && (p[7] & 0xff) <= 1 && + notification < m_devices->events.size() && m_devices->events[notification]) { + const auto it = std::find_if(m_sparse_regions.begin(), m_sparse_regions.end(), [&](const auto& region) { + return region.as_id == p[2] && region.address == begin && end >= begin && region.size == end - begin + 1; + }); + result = Kernel::ResultNotFound; + if (it != m_sparse_regions.end()) { + const auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(p[2]); + if (as && as->DestroyNativeSparseRegion(begin, it->size) == Nvidia::NvResult::Success) { + std::erase_if(m_mappings, [&](const auto& mapping) { + return mapping.as_id == p[2] && mapping.address >= begin && mapping.address <= end; + }); + m_sparse_regions.erase(it); + result = ResultSuccess; + } + } + m_devices->events[notification]->Signal(kernel); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + Result MapMemory(const u32* packet, u8& selected_kind) { + R_UNLESS(OwnsAddressSpace(packet[2]), Kernel::ResultNotFound); + R_UNLESS(std::find(m_memory.begin(), m_memory.end(), packet[7]) != m_memory.end(), + Kernel::ResultNotFound); + R_UNLESS(packet[15] < m_devices->devices.size() && m_devices->devices[packet[15]], + Kernel::ResultNotFound); + R_UNLESS(packet[10] == 0 && packet[11] <= 1 && packet[14] == 0 && packet[16] <= 1, + Kernel::ResultNotImplemented); + const auto kind = Nvidia::Devices::SelectNativeMemoryKind( + static_cast<u8>(packet[12]), static_cast<u8>(packet[13])); + R_UNLESS(kind, Kernel::ResultNotImplemented); + const u64 begin = ReadPair(packet, 3), end = ReadPair(packet, 5); + R_UNLESS(end >= begin && end != ~u64{0}, Kernel::ResultInvalidSize); + R_UNLESS(m_mappings.size() < 1024, Kernel::ResultOutOfResource); + const auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(packet[2]); + R_UNLESS(as, Kernel::ResultNotFound); + const auto result = as->MapNative4K(packet[7], begin, ReadPair(packet, 8), + end - begin + 1, kind->backend); + R_UNLESS(result == Nvidia::NvResult::Success, Kernel::ResultInvalidCurrentMemory); + m_mappings.push_back({packet[2], begin, end - begin + 1, packet[7]}); + selected_kind = kind->selected; + R_SUCCEED(); + } + void NormalMapGpuMemory4K(HLERequestContext& ctx) { + std::array<u32, 18> words; + std::copy_n(ctx.CommandBuffer(), words.size(), words.begin()); + // These arguments are single-byte TIPC fields; SDK padding is not zeroed. + for (size_t i = 10; i <= 16; ++i) { words[i] &= 0xff; } + const auto* packet = words.data(); + const std::scoped_lock device_lock{m_devices->mutex}; + Result result = Kernel::ResultInvalidCombination; + u8 selected_kind{}; + if (packet[0] == 0xe3 && packet[1] == 16 && packet[17] < m_devices->events.size() && + m_devices->events[packet[17]]) { + result = MapMemory(packet, selected_kind); + m_devices->events[packet[17]]->Signal(kernel); + LOG_DEBUG(Service_AM, "GPU map4K as={}, memory={}, VA={:#x}..{:#x}, permission={}, cache={}, kinds={:#x}/{:#x}, mode={}, sync={}, notification={}, selected={:#x}, result={:#x}", + packet[2], packet[7], ReadPair(packet, 3), ReadPair(packet, 5), packet[10], + packet[11], packet[12], packet[13], packet[14], packet[16], packet[17], selected_kind, result.raw); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push<u32>(selected_kind); + } + void NormalModifyGpuMemory4K(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const std::scoped_lock device_lock{m_devices->mutex}; + Result result = Kernel::ResultInvalidCombination; + u8 selected{}; + const u64 begin = ReadPair(packet, 3), end = ReadPair(packet, 5); + const auto requested = static_cast<u8>(packet[7]); + const auto fallback = static_cast<u8>(packet[8]); + if (packet[0] == 0xe4 && packet[1] == 9 && (packet[9] & 0xff) <= 1 && + packet[10] < m_devices->events.size() && m_devices->events[packet[10]]) { + result = Kernel::ResultNotFound; + const auto kind = Nvidia::Devices::SelectNativeMemoryKind(requested, fallback); + const auto owner = std::find_if(m_mappings.begin(), m_mappings.end(), [&](const auto& mapping) { + return mapping.as_id == packet[2] && begin >= mapping.address && + begin - mapping.address < mapping.size && end >= begin && + end - mapping.address < mapping.size; + }); + if (kind && OwnsAddressSpace(packet[2]) && owner != m_mappings.end()) { + const auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(packet[2]); + if (as && as->ModifyNativeKind(begin, end - begin + 1, requested, fallback) == + Nvidia::NvResult::Success) { + selected = kind->selected; + result = ResultSuccess; + } + } + m_devices->events[packet[10]]->Signal(kernel); + } + LOG_DEBUG(Service_AM, "GPU modify4K as={}, VA={:#x}..{:#x}, kinds={:#x}/{:#x}, sync={}, notification={}, selected={}, result={:#x}", + packet[2], begin, end, requested, fallback, packet[9], packet[10], selected, result.raw); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push<u32>(selected); + } + void NormalUnmapGpuMemory4K(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const std::scoped_lock device_lock{m_devices->mutex}; + Result result = Kernel::ResultInvalidCombination; + u64 unmapped{}; + if (packet[0] == 0xe5 && packet[1] == 7 && (packet[7] & 0xff) <= 1 && + packet[8] < m_devices->events.size() && m_devices->events[packet[8]]) { + const u64 begin = ReadPair(packet, 3), end = ReadPair(packet, 5); + const auto it = std::find_if(m_mappings.begin(), m_mappings.end(), [&](const auto& mapping) { + return mapping.as_id == packet[2] && mapping.address == begin && + end >= begin && end - begin + 1 == mapping.size; + }); + result = Kernel::ResultNotFound; + if (it != m_mappings.end()) { + const auto as = m_module->GetDevice<Nvidia::Devices::nvhost_as_gpu>(it->as_id); + result = Kernel::ResultInvalidState; + if (as && as->UnmapNative4K(it->address, it->size) == Nvidia::NvResult::Success) { + unmapped = it->size; + m_mappings.erase(it); + result = ResultSuccess; + } + } + m_devices->events[packet[8]]->Signal(kernel); + LOG_DEBUG(Service_AM, "GPU unmap4K as={}, VA={:#x}..{:#x}, bytes={:#x}, result={:#x}", + packet[2], begin, end, unmapped, result.raw); + } + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result.raw); + rb.Push(unmapped); + } + Result RegisterMemory(u64 begin, u64 end, u32 flags, u32 device, u32& id) { + R_UNLESS(flags == 0, Kernel::ResultNotImplemented); + R_UNLESS(device < m_devices->devices.size(), Kernel::ResultNotFound); + const std::scoped_lock device_lock{m_devices->mutex}; + R_UNLESS(m_devices->devices[device], Kernel::ResultNotFound); + R_UNLESS(end >= begin && end != ~u64{0}, Kernel::ResultInvalidSize); + const u64 size = end - begin + 1; + R_UNLESS(Common::IsAligned(begin, Kernel::PageSize), Kernel::ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, Kernel::PageSize), Kernel::ResultInvalidSize); + R_UNLESS(m_memory.size() < 1024, Kernel::ResultOutOfResource); + auto& page_table = m_process->GetPageTable(); + R_UNLESS(page_table.Contains(begin, size), Kernel::ResultInvalidCurrentMemory); + for (u64 address = begin; address <= end;) { + Kernel::KMemoryInfo info{}; + Kernel::Svc::PageInfo page{}; + R_TRY(page_table.QueryInfo(&info, &page, address)); + R_UNLESS(True(info.m_attribute & Kernel::KMemoryAttribute::GpuAccessible), + Kernel::ResultInvalidCurrentMemory); + const u64 next = info.m_address + info.m_size; + R_UNLESS(next > address, Kernel::ResultInvalidCurrentMemory); + address = next; + } + if (!m_nv_session) { + m_nv_session = m_module->GetContainer().OpenSession(m_process).id; + } + auto& nvmap = m_module->GetContainer().GetNvMapFile(); + std::shared_ptr<Nvidia::NvCore::NvMap::Handle> handle; + R_UNLESS(nvmap.CreateHandle(size, handle) == Nvidia::NvResult::Success, + Kernel::ResultOutOfResource); + bool is_io{}; + const auto lock_result = page_table.LockForMapDeviceAddressSpace( + &is_io, begin, size, Kernel::KMemoryPermission::None, true, true); + if (lock_result.IsError()) { + nvmap.FreeHandle(handle->id, false); + R_RETURN(lock_result); + } + const auto alloc_result = handle->Alloc({}, Kernel::PageSize, 0, begin, + Nvidia::NvCore::SessionId{*m_nv_session}); + if (alloc_result != Nvidia::NvResult::Success) { + page_table.UnlockForDeviceAddressSpace(begin, size); + nvmap.FreeHandle(handle->id, false); + R_RETURN(Kernel::ResultOutOfResource); + } + id = handle->id; + m_memory.push_back(id); + R_SUCCEED(); + } + Result ReleaseMemory(u32 id, u32 notification) { + const std::scoped_lock device_lock{m_devices->mutex}; + R_UNLESS(notification < m_devices->events.size() && m_devices->events[notification], + Kernel::ResultInvalidCombination); + const auto it = std::find(m_memory.begin(), m_memory.end(), id); + R_UNLESS(it != m_memory.end(), Kernel::ResultNotFound); + auto& nvmap = m_module->GetContainer().GetNvMapFile(); + auto handle = nvmap.GetHandle(id); + R_UNLESS(handle, Kernel::ResultNotFound); + { + const std::scoped_lock handle_lock{handle->mutex}; + // No asynchronous GPU work is implemented yet. Never report release + // completion for memory still retained or pinned by another user. + R_UNLESS(handle->pins == 0 && handle->internal_dupes == 0 && handle->dupes == 1, + Kernel::ResultBusy); + } + // FreeHandle uses the final shared reference to decide when CPU memory + // may be unshared. Drop this temporary inspection reference first. + handle.reset(); + const auto info = nvmap.FreeHandle(id, false); + R_UNLESS(info && info->can_unlock, Kernel::ResultInvalidState); + m_memory.erase(it); + R_TRY(m_process->GetPageTable().UnlockForDeviceAddressSpace(info->address, info->size)); + m_devices->events[notification]->Signal(kernel); + R_SUCCEED(); + } + void DestroyGpuAccessibleMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const auto result = packet[0] == 0x76 && packet[1] == 2 + ? ReleaseMemory(packet[2], packet[3]) : Kernel::ResultInvalidCombination; + LOG_DEBUG(Service_AM, "GPU memory release id={}, notification={}, result={:#x}", + packet[2], packet[3], result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void CreateGpuAccessibleMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + u32 id{}; + Result result = Kernel::ResultInvalidCombination; + if (const auto request = DecodeGpuMemoryRegistration(std::span<const u32>{packet, 8})) { + result = RegisterMemory(request->begin, request->end, request->flags, request->device, id); + LOG_DEBUG(Service_AM, "GPU memory {:#x}..{:#x}, device={}, raw_device={:#x}, flags={}, id={}, result={:#x}", + request->begin, request->end, request->device, packet[7], request->flags, id, result.raw); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push(id); + } + std::shared_ptr<GpuDeviceRegistry> m_devices; + std::optional<size_t> m_nv_session; + std::vector<u32> m_memory; + std::shared_ptr<Nvidia::Module> m_module; + Kernel::KProcess* const m_process; +}; +} // namespace + +IGpuSession::IGpuSession(Core::System& system, std::shared_ptr<Nvidia::Module> module, + Kernel::KProcess* process, bool legacy_protocol) + : ServiceFramework{system, "IGpuSession"}, + m_process{process}, m_module{std::move(module)}, m_legacy_protocol{legacy_protocol} { + m_process->Open(kernel); + m_devices = std::make_shared<GpuDeviceRegistry>(system, m_module, m_process); + static const FunctionInfo functions[] = { + {11, &IGpuSession::GetNotificationRange, "GetNotificationRange"}, + {12, &IGpuSession::GetNotificationEvent, "GetNotificationEvent"}, + {21, &IGpuSession::RequestWorker, "RequestWorker"}, + {22, &IGpuSession::AcquireWorker, "AcquireWorker"}, + {33, &IGpuSession::CreateAddressSpace, "CreateAddressSpace"}, + {41, &IGpuSession::CreateGraphicsDevice, "CreateGraphicsDevice"}, + {42, &IGpuSession::DestroyGraphicsDevice, "DestroyGraphicsDevice"}, + {201, &IGpuSession::GetAddressSpacePartition, "GetLegacyGsvPartition"}, + {202, &IGpuSession::GetAddressSpacePartition, "GetGsvPartition"}, + {203, &IGpuSession::GetAddressSpacePartition, "GetUserPartition"}, + }; + RegisterHandlersTipc(functions); +} + +IGpuSession::~IGpuSession() { + if (m_worker) { m_worker->Close(kernel); } + m_process->Close(kernel); +} + +Result IGpuSession::HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) { + if (!ctx.IsTipc() || handlers_tipc.contains(ctx.GetCommand())) { + return ServiceFrameworkBase::HandleSyncRequest(session, ctx); + } + const auto guard = LockService(); + InvokeRequestTipc(ctx); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultNotImplemented.raw); + return system.IsPoweredOn() ? ctx.WriteToOutgoingCommandBuffer() : ResultSuccess; +} + +Result IGpuSession::RegisterAddressSpace(u32 id) { + const auto guard = LockService(); + const std::scoped_lock device_lock{m_devices->mutex}; + for (size_t i = 0; i < m_devices->address_spaces.size(); ++i) { + if (m_devices->address_spaces[i] && static_cast<u32>(*m_devices->address_spaces[i]) == id) { + m_devices->address_space_registered[i] = true; + R_SUCCEED(); + } + } + R_THROW(Kernel::ResultNotFound); +} + +void IGpuSession::GetAddressSpacePartition(HLERequestContext& ctx) { + const std::scoped_lock device_lock{m_devices->mutex}; + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + std::array<u64, 9> values{}; + const bool legacy_user = m_legacy_protocol && (ctx.GetCommand() == 201 || ctx.GetCommand() == 202); + const bool user = legacy_user || ctx.GetCommand() == 203; + if (!user) { + values[0] = ~u64{0}; // No supervisor-owned partition is reserved by this adapter. + } + if (packet[0] == 16 + ctx.GetCommand() && packet[1] == 1) { + const u32 id = packet[2]; + const bool exists = std::any_of(m_devices->address_spaces.begin(), m_devices->address_spaces.end(), + [id](const auto& fd) { return fd && static_cast<u32>(*fd) == id; }); + result = exists ? ResultSuccess : Kernel::ResultNotFound; + if (exists && user) { + std::array<u8, 64> input{}; + std::array<u8, 64> output{}; + const u32_le buffer_size{48}; + std::memcpy(input.data() + 8, &buffer_size, sizeof(buffer_size)); + Nvidia::Ioctl command{}; + command.raw = 0xc0404108; + if (m_module->Ioctl1(static_cast<s32>(id), command, input, output) == + Nvidia::NvResult::Success) { + std::array<Nvidia::Devices::nvhost_as_gpu::VaRegion, 2> regions; + std::memcpy(regions.data(), output.data() + 16, sizeof(regions)); + const u64 end = regions[1].offset + regions[1].pages * regions[1].page_size - 1; + if (legacy_user) { + const auto& region = regions[ctx.GetCommand() == 201 ? 0 : 1]; + values[0] = region.offset; + values[1] = region.offset + region.pages * region.page_size - 1; + } else { + values[0] = 1; + values[1] = regions[0].offset; + values[2] = end; + } + } else { + result = Kernel::ResultInvalidState; + } + } + LOG_DEBUG(Service_AM, "GPU partition command={}, id={}, begin={:#x}, end={:#x}, result={:#x}", + ctx.GetCommand(), id, user ? values[1] : values[0], + user ? values[2] : values[1], result.raw); + } + IPC::ResponseBuilder rb{ctx, user && !legacy_user ? 20U : 6U}; + rb.Push(result.raw); + for (size_t i = 0; i < (user && !legacy_user ? values.size() : size_t{2}); ++i) { + rb.Push(values[i]); + } +} + +void IGpuSession::CreateAddressSpace(HLERequestContext& ctx) { + const std::scoped_lock device_lock{m_devices->mutex}; + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + u32 id{}; + if (packet[0] == 0x31 && packet[1] == 1 && packet[2] == 0) { + result = Kernel::ResultOutOfResource; + for (auto& slot : m_devices->address_spaces) { + if (slot) { + continue; + } + if (!m_devices->nv_session) { + m_devices->nv_session = m_module->GetContainer().OpenSession(m_process).id; + } + const auto fd = m_module->Open("/dev/nvhost-as-gpu", + Nvidia::NvCore::SessionId{*m_devices->nv_session}); + if (fd == Nvidia::INVALID_NVDRV_FD) { + break; + } + std::array<u8, 40> input{}; + std::array<u8, 40> output{}; + const u32_le big_page_size{0x20000}; + std::memcpy(input.data() + 8, &big_page_size, sizeof(big_page_size)); + Nvidia::Ioctl command{}; + command.raw = 0x40284109; + const auto nv_result = m_module->Ioctl1(fd, command, input, output); + if (nv_result == Nvidia::NvResult::Success) { + slot = fd; + id = fd; + result = ResultSuccess; + } else { + m_module->Close(fd); + result = Kernel::ResultInvalidState; + } + break; + } + } + LOG_DEBUG(Service_AM, "GPU address space id={}, result={:#x}", id, result.raw); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push(id); +} + +void IGpuSession::CreateGraphicsDevice(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + u32 id{}; + if (packet[0] == 0x39 && packet[1] == 6) { + const u64 name = u64{packet[2]} | (u64{packet[3]} << 32); + const u64 flags = u64{packet[4]} | (u64{packet[5]} << 32); + const u64 debug_label = u64{packet[6]} | (u64{packet[7]} << 32); + result = flags == 0 ? Kernel::ResultOutOfResource : Kernel::ResultNotImplemented; + if (flags == 0) { + const std::scoped_lock device_lock{m_devices->mutex}; + for (u32 candidate = 1; candidate < m_devices->devices.size(); ++candidate) { + if (!m_devices->devices[candidate]) { + id = candidate; + m_devices->devices[id] = GpuDeviceRegistry::Device{name, debug_label}; + result = ResultSuccess; + break; + } + } + } + LOG_DEBUG(Service_AM, "graphics device name={:#x}, flags={:#x}, id={}, result={:#x}", + name, flags, id, result.raw); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push(id); +} + +void IGpuSession::DestroyGraphicsDevice(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x3a && packet[1] == 1) { + const u8 id = static_cast<u8>(packet[2]); + const std::scoped_lock device_lock{m_devices->mutex}; + result = m_devices->devices[id] ? ResultSuccess : Kernel::ResultNotFound; + m_devices->devices[id].reset(); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGpuSession::GetNotificationRange(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const Result result = packet[0] == 0x1b && packet[1] == 0 + ? ResultSuccess : Kernel::ResultInvalidCombination; + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result.raw); + rb.Push<u32>(0); + rb.Push<u32>(static_cast<u32>(m_devices->events.size() - 1)); + LOG_DEBUG(Service_AM, "GPU notification range 0..{}", m_devices->events.size() - 1); +} + +void IGpuSession::GetNotificationEvent(HLERequestContext& ctx) { + const std::scoped_lock device_lock{m_devices->mutex}; + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + Kernel::KReadableEvent* readable{}; + if (packet[0] == 0x1c && packet[1] == 1 && packet[2] < m_devices->events.size()) { + const u32 index = packet[2]; + if (!m_devices->events[index]) { m_devices->events[index] = m_devices->context.CreateEvent("GpuNotification"); } + if (m_devices->events[index]) { + readable = &m_devices->events[index]->GetReadableEvent(); + readable->Open(kernel); + result = ResultSuccess; + } else { result = Kernel::ResultOutOfResource; } + LOG_DEBUG(Service_AM, "GPU notification event index={}, result={:#x}", index, result.raw); + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(readable); +} + +void IGpuSession::RequestWorker(HLERequestContext& ctx) { + const std::scoped_lock device_lock{m_devices->mutex}; + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x25 && packet[1] == 1 && packet[2] < m_devices->events.size()) { + const u32 index = packet[2]; + if (!m_devices->events[index]) { + result = Kernel::ResultInvalidState; + } else { + result = ResultSuccess; + if (!m_worker) { + result = CreateGraphicsSession(system, ctx, + std::make_shared<IGpuWorker>(system, m_module, m_process, m_devices), &m_worker); + } + if (result.IsSuccess()) { m_devices->events[index]->Signal(kernel); } + } + LOG_DEBUG(Service_AM, "GPU worker request index={}, result={:#x}", index, result.raw); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGpuSession::AcquireWorker(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + Kernel::KClientSession* client{}; + if (packet[0] == 0x26 && packet[1] == 0) { + if (m_worker) { + client = m_worker; + client->Open(kernel); + result = ResultSuccess; + } else { result = Kernel::ResultInvalidState; } + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(client); + LOG_DEBUG(Service_AM, "GPU worker acquisition result={:#x}", result.raw); +} +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/gpu_session.h b/src/core/hle/service/am/service/gpu_session.h new file mode 100644 index 0000000000..e8f580bef1 --- /dev/null +++ b/src/core/hle/service/am/service/gpu_session.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> +#include <optional> +#include "core/hle/service/service.h" +#include "core/hle/service/kernel_helpers.h" + +namespace Kernel { +class KClientSession; +class KProcess; +class KEvent; +} +namespace Service::Nvidia { class Module; } +namespace Service::AM { + +struct GpuDeviceRegistry; + +class IGpuSession final : public ServiceFramework<IGpuSession> { +public: + IGpuSession(Core::System& system, std::shared_ptr<Nvidia::Module> module, + Kernel::KProcess* process, bool legacy_protocol = false); + ~IGpuSession() override; + Result RegisterAddressSpace(u32 id); + Result HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) override; + +private: + void GetAddressSpacePartition(HLERequestContext& ctx); + void CreateAddressSpace(HLERequestContext& ctx); + void CreateGraphicsDevice(HLERequestContext& ctx); + void DestroyGraphicsDevice(HLERequestContext& ctx); + void GetNotificationRange(HLERequestContext& ctx); + void GetNotificationEvent(HLERequestContext& ctx); + void RequestWorker(HLERequestContext& ctx); + void AcquireWorker(HLERequestContext& ctx); + + std::shared_ptr<GpuDeviceRegistry> m_devices; + Kernel::KClientSession* m_worker{}; + Kernel::KProcess* const m_process; + std::shared_ptr<Nvidia::Module> m_module; + const bool m_legacy_protocol; +}; +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_scaling_parameters_functions.cpp b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.cpp new file mode 100644 index 0000000000..931c81850d --- /dev/null +++ b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.cpp @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/am/applet.h" +#include "core/hle/service/am/service/graphics_scaling_parameters_functions.h" +#include "core/hle/service/cmif_serialization.h" + +namespace Service::AM { + +IGraphicsScalingParametersFunctions::IGraphicsScalingParametersFunctions( + Core::System& system, std::shared_ptr<Applet> applet) + : ServiceFramework{system, "IGraphicsScalingParametersFunctions"}, m_applet{std::move(applet)} { + static const FunctionInfo functions[] = { + {10, D<&IGraphicsScalingParametersFunctions::GetGraphicsScalingParameters>, + "GetGraphicsScalingParameters"}, + {1000, nullptr, "SetSupportingGraphicsScalingParametersForDebug"}, + {1010, nullptr, "SetGpuQuotaForDebug"}, + {1020, nullptr, "ClearGpuQuotaForDebug"}, + {1030, nullptr, "SetScreenRatioForDebug"}, + {1040, nullptr, "ClearScreenRatioForDebug"}, + {1050, D<&IGraphicsScalingParametersFunctions::SetGraphicsScaleChangedNotificationEnabled>, + "SetGraphicsScaleChangedNotificationEnabled"}, + }; + RegisterHandlers(functions); +} + +IGraphicsScalingParametersFunctions::~IGraphicsScalingParametersFunctions() = default; + +Result IGraphicsScalingParametersFunctions::GetGraphicsScalingParameters( + Out<GraphicsScalingParameters> out_parameters) { + // Prototype policy: lowest SDK scale (10 maps to public scale 0), unit screen ratio. + // The ratio field interpretation is inferred from the adjacent debug API. + *out_parameters = {10, 1, 1}; + LOG_DEBUG(Service_AM, "called, prototype scale=10, screen ratio=1/1"); + R_SUCCEED(); +} + +Result IGraphicsScalingParametersFunctions::SetGraphicsScaleChangedNotificationEnabled(bool enabled) { + LOG_DEBUG(Service_AM, "called, enabled={}", enabled); + std::scoped_lock lk{m_applet->lock}; + m_applet->graphics_scale_changed_notification_enabled = enabled; + R_SUCCEED(); +} + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h new file mode 100644 index 0000000000..7c92cb53f4 --- /dev/null +++ b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" + +namespace Service::AM { + +struct Applet; + +struct GraphicsScalingParameters { + u32 graphics_scale; + u32 screen_ratio_numerator; + u32 screen_ratio_denominator; +}; +static_assert(sizeof(GraphicsScalingParameters) == 12); + +class IGraphicsScalingParametersFunctions final + : public ServiceFramework<IGraphicsScalingParametersFunctions> { +public: + explicit IGraphicsScalingParametersFunctions(Core::System& system, + std::shared_ptr<Applet> applet); + ~IGraphicsScalingParametersFunctions() override; + +private: + Result GetGraphicsScalingParameters(Out<GraphicsScalingParameters> out_parameters); + Result SetGraphicsScaleChangedNotificationEnabled(bool enabled); + + const std::shared_ptr<Applet> m_applet; +}; + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_session.cpp b/src/core/hle/service/am/service/graphics_session.cpp new file mode 100644 index 0000000000..902e7f9706 --- /dev/null +++ b/src/core/hle/service/am/service/graphics_session.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/service/am/service/graphics_session.h" +#include "core/hle/service/server_manager.h" + +namespace Service::AM { +Result CreateGraphicsSession(Core::System& system, HLERequestContext& ctx, + std::shared_ptr<SessionRequestHandler> handler, + Kernel::KClientSession** out_client) { + auto& kernel = system.Kernel(); + Kernel::KScopedResourceReservation reservation( + kernel, Kernel::GetCurrentProcessPointer(kernel), Kernel::LimitableResource::SessionCountMax); + R_UNLESS(reservation.Succeeded(), Kernel::ResultLimitReached); + auto* session = Kernel::KSession::Create(kernel); + R_UNLESS(session != nullptr, Kernel::ResultOutOfResource); + session->Initialize(kernel, nullptr, 0); + reservation.Commit(); + Kernel::KSession::Register(kernel, session); + auto& server = ctx.GetManager()->GetServerManager(); + auto manager = std::make_shared<SessionRequestManager>(kernel, server); + manager->SetSessionHandler(std::move(handler)); + R_TRY(server.RegisterSession(&session->GetServerSession(), std::move(manager))); + *out_client = &session->GetClientSession(); + R_SUCCEED(); +} + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_session.h b/src/core/hle/service/am/service/graphics_session.h new file mode 100644 index 0000000000..698b5ee8ee --- /dev/null +++ b/src/core/hle/service/am/service/graphics_session.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <memory> +#include "core/hle/result.h" + +namespace Core { class System; } +namespace Kernel { class KClientSession; } +namespace Service { +class HLERequestContext; +class SessionRequestHandler; +} +namespace Service::AM { +Result CreateGraphicsSession(Core::System& system, HLERequestContext& ctx, + std::shared_ptr<SessionRequestHandler> handler, + Kernel::KClientSession** out_client); +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_supervisor.cpp b/src/core/hle/service/am/service/graphics_supervisor.cpp new file mode 100644 index 0000000000..7759f575e7 --- /dev/null +++ b/src/core/hle/service/am/service/graphics_supervisor.cpp @@ -0,0 +1,1612 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <algorithm> +#include <atomic> +#include <bit> +#include <array> +#include "common/alignment.h" +#include "core/hle/service/nvdrv/core/nvmap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/service/nvnflinger/buffer_queue_producer.h" +#include "core/hle/service/nvnflinger/ui/graphic_buffer.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/service/am/service/graphics_supervisor.h" +#include "core/hle/service/am/service/graphics_session.h" +#include "core/hle/service/am/service/gpu_session.h" +#include "core/hle/service/am/service/graphics_supervisor_protocol.h" +#include "core/hle/service/am/service/graphics_syncpoint_cache.h" +#include "core/hle/service/am/service/native_image_layout.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nvdrv/nvdrv_interface.h" +#include "core/hle/service/nvdrv/core/container.h" +#include "core/hle/service/nvdrv/core/syncpoint_manager.h" +#include "core/hle/service/server_manager.h" +#include "core/hle/service/sm/sm.h" +#include "core/hle/service/vi/application_display_service.h" +#include "core/hle/service/vi/container.h" +#include "core/hle/service/vi/manager_root_service.h" +#include "core/hle/service/vi/vi_types.h" + +namespace Service::AM { +struct NativeImageRegistry { + struct Image { + u32 id; + u32 memory_id; + std::array<u32, 16> preference; + u64 size; + bool window_pool{}; + }; + NativeImageRegistry(Kernel::KernelCore& kernel_, Kernel::KProcess* process_) + : kernel{kernel_}, process{process_} { + process->Open(kernel); + } + ~NativeImageRegistry() { + if (module) { + for (const auto& image : images) { + ReleaseMemory(image.memory_id); + } + } + if (memory_session) { + module->GetContainer().CloseSession(*memory_session); + } + process->Close(kernel); + } + void ReleaseMemory(u32 id) { + const auto info = module->GetContainer().GetNvMapFile().FreeHandle(id, true); + if (info && info->can_unlock && info->address != 0) { + process->GetPageTable().UnlockForDeviceAddressSpace(info->address, info->size); + } + } + Kernel::KernelCore& kernel; + Kernel::KProcess* process; + std::mutex mutex; + std::shared_ptr<Nvidia::Module> module; + std::optional<Nvidia::NvCore::SessionId> memory_session; + std::vector<Image> images; +}; +using NativeImage = NativeImageRegistry::Image; + + +namespace { + +// TIPC payloads follow a 32-bit result, so these handlers push result.raw. + +class ISyncpointSupervisor final : public ServiceFramework<ISyncpointSupervisor> { +public: + ISyncpointSupervisor(Core::System& system, std::shared_ptr<Nvidia::Module> module) + : ServiceFramework{system, "ISyncpointSupervisor"}, m_module{std::move(module)}, + m_context{system, "NativeSyncpoint"} { + static const FunctionInfo functions[] = { + {0, &ISyncpointSupervisor::ExchangeClientToken, "ExchangeClientToken"}, + {1, &ISyncpointSupervisor::GetMaximumEventCount, "GetMaximumEventCount"}, + {21, &ISyncpointSupervisor::SetEventCount, "SetEventCount"}, + {22, &ISyncpointSupervisor::GetEvent, "GetEvent"}, + {201, &ISyncpointSupervisor::ArmEvent, "ArmEvent"}, + {202, &ISyncpointSupervisor::CancelEvent, "CancelEvent"}, + {103, &ISyncpointSupervisor::QueryCounter, "QueryCounter"}, + }; + RegisterHandlersTipc(functions); + } + + ~ISyncpointSupervisor() override { + if (m_poll) system.CoreTiming().UnscheduleEvent(m_poll); + for (auto& event : m_events) m_context.CloseEvent(event.event); + } + + Result HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) override { + if (!ctx.IsTipc() || handlers_tipc.contains(ctx.GetCommand())) { + return ServiceFrameworkBase::HandleSyncRequest(session, ctx); + } + const auto guard = LockService(); + LOG_DEBUG(Service_AM, "syncpoint unsupported command={}, words={:08x}", ctx.GetCommand(), + fmt::join(std::span<const u32>(ctx.CommandBuffer() + 2, std::min<u32>(ctx.CommandBuffer()[1], 32)), ",")); + InvokeRequestTipc(ctx); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultNotImplemented.raw); + return system.IsPoweredOn() ? ctx.WriteToOutgoingCommandBuffer() : ResultSuccess; + } + +private: + static constexpr u32 MaximumEventCount = 63; + + void ExchangeClientToken(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const Result result = packet[0] == 0x10 && packet[1] == 2 + ? ResultSuccess : Kernel::ResultInvalidCombination; + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result.raw); + rb.Push(token); + LOG_DEBUG(Service_AM, "syncpoint token={:#x}, result={:#x}", token, result.raw); + } + void GetMaximumEventCount(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const Result result = packet[0] == 0x11 && packet[1] == 0 + ? ResultSuccess : Kernel::ResultInvalidCombination; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push(MaximumEventCount); + LOG_DEBUG(Service_AM, "syncpoint maximum events={}", MaximumEventCount); + } + + void SetEventCount(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x25 && packet[1] == 1 && packet[2] <= MaximumEventCount) { + m_event_count = packet[2]; + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + LOG_DEBUG(Service_AM, "syncpoint event count={}, result={:#x}", m_event_count, result.raw); + } + + void GetEvent(HLERequestContext& ctx) { + const auto* p = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + Kernel::KReadableEvent* readable{}; + if (p[0] == 0x26 && p[1] == 1 && p[2] < m_event_count) { + auto& event = m_events[p[2]]; + if (!event.event) event.event = m_context.CreateEvent("SyncpointCompletion"); + result = event.event ? ResultSuccess : Kernel::ResultOutOfResource; + if (event.event) { + readable = &event.event->GetReadableEvent(); + readable->Open(kernel); + } + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(readable); + } + void RefreshEvents() { + auto& manager = m_module->GetContainer().GetSyncpointManager(); + for (auto& event : m_events) { + if (!event.pending) continue; + const auto current = manager.ReadAllocatedSyncpoint(event.id); + if (current && event.last_observed != *current) { + event.last_observed = *current; + LOG_INFO(Service_AM, "Syncpoint wait progress: id={}, current={}, target={}", event.id, *current, event.threshold); + } + if (current && static_cast<s32>(*current - event.threshold) >= 0) { + event.pending = false; + event.event->Signal(kernel); + } + } + } + void ArmEvent(HLERequestContext& ctx) { + const auto* p = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + const u32 id = ~p[3]; + if (p[0] == 0xd9 && p[1] == 5 && p[2] < m_event_count && + p[5] == 0 && p[6] == 0 && + m_module->GetContainer().GetSyncpointManager().ReadAllocatedSyncpoint(id)) { + auto& event = m_events[p[2]]; + if (!event.event) event.event = m_context.CreateEvent("SyncpointCompletion"); + result = event.event ? ResultSuccess : Kernel::ResultOutOfResource; + if (event.event) { + event.event->Clear(kernel); + event.id = id; + event.threshold = p[4]; + event.last_observed = ~u32{0}; + LOG_INFO(Service_AM, "Arm syncpoint wait: slot={}, id={}, target={}", p[2], id, event.threshold); + event.pending = true; + RefreshEvents(); + if (!m_poll) { + m_poll = Core::Timing::CreateEvent("NativeSyncpointCompletion", + [this](s64, std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> { + const auto guard = LockService(); + RefreshEvents(); + return std::nullopt; + }); + system.CoreTiming().ScheduleLoopingEvent(std::chrono::milliseconds{1}, + std::chrono::milliseconds{1}, m_poll); + } + } + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + LOG_DEBUG(Service_AM, "syncpoint arm event={}, id={}, threshold={}, result={:#x}", + p[2], id, p[4], result.raw); + } + void CancelEvent(HLERequestContext& ctx) { + const auto* p = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (p[0] == 0xda && p[1] == 1 && p[2] < m_event_count) { + auto& event = m_events[p[2]]; + event.pending = false; + // The native SDK waits for this event after cancelling a timed wait + // before it releases the event slot. Cancellation acknowledges that + // the wait is retired; it does not advance the syncpoint counter. + if (event.event) event.event->Signal(kernel); + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + + void QueryCounter(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + u64 value{}; + if (packet[0] == 0x77 && packet[1] == 1) { + const u32 id = ~packet[2]; + auto current = m_module->GetContainer().GetSyncpointManager().ReadAllocatedSyncpoint(id); + result = current ? ResultSuccess : Kernel::ResultNotFound; + value = current.value_or(0); + LOG_DEBUG(Service_AM, "query syncpoint id={}, value={}, result={:#x}", id, value, result.raw); + } + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(result.raw); + rb.Push(value); + rb.Push<u32>(0); // Existing NVDRV counters use 32-bit modular comparisons. + } + + std::shared_ptr<Nvidia::Module> m_module; + u32 m_event_count{}; + struct CompletionEvent { + Kernel::KEvent* event{}; + u32 id{}; + u32 threshold{}; + u32 last_observed = ~u32{0}; + bool pending{}; + }; + KernelHelpers::ServiceContext m_context; + std::array<CompletionEvent, MaximumEventCount> m_events{}; + std::shared_ptr<Core::Timing::EventType> m_poll; +}; + +class IVi2Session final : public ServiceFramework<IVi2Session> { + struct ClientState { + u64 token{}; + u32 slots{}; + std::optional<u64> display; + std::optional<u64> layer; + u64 available{0x7fff}; + u32 retire_notification{}; + u64 output_encoding{0x14}; + u32 available_notification{}; + std::shared_ptr<android::BufferQueueProducer> producer; + std::array<std::optional<s32>, 15> queued{}; + std::optional<size_t> player_display; + }; + +public: + explicit IVi2Session(Core::System& system, u64 aruid, std::shared_ptr<NativeImageRegistry> images) : ServiceFramework{system, "IVi2Session"}, m_images{std::move(images)}, m_aruid{aruid}, m_context{system, "Vi2Session"} { + static const FunctionInfo functions[] = { + {1, &IVi2Session::GetSessionLimit, "GetNotificationLimit"}, + {11, &IVi2Session::GetNotificationEvent, "GetNotificationEvent"}, + {13, &IVi2Session::ReleaseNotificationEvent, "ReleaseNotificationEvent"}, + {21, &IVi2Session::GetNotifications, "GetNotifications"}, + {31, &IVi2Session::DuplicateVsyncEvent, "GetDisplayEvent"}, + {32, &IVi2Session::DuplicateDisplayEvent, "DuplicateDisplayEvent"}, + {3, &IVi2Session::GetSessionLimit, "GetSessionLimit"}, + {5, &IVi2Session::GetSessionLimit, "GetRequestGroupLimit"}, + {51, &IVi2Session::ConfigureRequestGroups, "ConfigureRequestGroups"}, + {101, &IVi2Session::OpenDisplay, "OpenDisplay"}, + {102, &IVi2Session::CloseDisplay, "CloseDisplay"}, + {111, &IVi2Session::OpenEndpoint, "OpenEndpoint"}, + {112, &IVi2Session::CloseEndpoint, "CloseEndpoint"}, + {201, &IVi2Session::RegisterClientState, "RegisterClientState"}, + {202, &IVi2Session::UnregisterClientState, "UnregisterClientState"}, + {212, &IVi2Session::GetClientAvailability, "GetClientAvailability"}, + {229, &IVi2Session::PresentClientBuffer, "PresentClientBuffer"}, + {228, &IVi2Session::CancelClientBuffer, "CancelClientBuffer"}, + {351, &IVi2Session::SetClientOutputEncoding, "SetClientOutputEncoding"}, + {261, &IVi2Session::SetDisplayCapability, "SetDisplayCapability261"}, + {265, &IVi2Session::SetDisplayCapability, "SetDisplayCapability265"}, + {267, &IVi2Session::SetDisplayCapability, "SetDisplayCapability267"}, + {271, &IVi2Session::SetDisplayCapability, "SetDisplayCapability271"}, + {251, &IVi2Session::BindClientDisplay, "BindClientDisplay"}, + {252, &IVi2Session::UnbindClientDisplay, "UnbindClientDisplay"}, + }; + RegisterHandlersTipc(functions); + } + + ~IVi2Session() override { + if (m_release_poll) system.CoreTiming().UnscheduleEvent(m_release_poll); + if (m_display_service) { + for (auto& state : m_client_states) { + if (state.layer) m_display_service->GetContainer()->DestroyManagedLayer(*state.layer); + } + } + if (m_default_display) { m_display_service->CloseDisplay(*m_default_display); } + for (auto* event : m_display_change_events) m_context.CloseEvent(event); + m_context.CloseEvent(m_event); + } + + Result HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) override { + if (!ctx.IsTipc() || handlers_tipc.contains(ctx.GetCommand())) { + return ServiceFrameworkBase::HandleSyncRequest(session, ctx); + } + const auto guard = LockService(); + LOG_DEBUG(Service_AM, "VI2 unsupported command={}, words={:08x}", ctx.GetCommand(), + fmt::join(std::span<const u32>(ctx.CommandBuffer() + 2, std::min<u32>(ctx.CommandBuffer()[1], 32)), ",")); + InvokeRequestTipc(ctx); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultNotImplemented.raw); + return system.IsPoweredOn() ? ctx.WriteToOutgoingCommandBuffer() : ResultSuccess; + } +private: + void SetDisplayCapability(HLERequestContext& ctx) { + const auto* p = ctx.CommandBuffer(); + const u64 name = u64{p[2]} | (u64{p[3]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (p[0] == ctx.GetCommand() + 16 && p[1] == 3 && p[4] <= 1) { + result = name == 0x00746c7561666564ULL && m_default_display + ? ResultSuccess : Kernel::ResultNotFound; + if (result.IsSuccess()) { + // Per-client display mode capabilities do not change the host video mode. + m_display_capabilities[(ctx.GetCommand() - 261) / 2] = p[4] != 0; + } + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void DuplicateVsyncEvent(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + Kernel::KReadableEvent* readable{}; + if (packet[0] == 0x2f && packet[1] == 1 && packet[2] < 5) { + result = m_display_service && m_default_display + ? ResultSuccess : Kernel::ResultInvalidState; + if (result.IsSuccess() && !m_vsync_event) { + result = m_display_service->GetDisplayVsyncEvent(&m_vsync_event, *m_default_display); + } + if (result.IsSuccess()) { + if (packet[2] == 0) { + readable = m_vsync_event; + } else { + // Fixed host display properties have no change to report until changed. + auto*& event = m_display_change_events[packet[2] - 1]; + if (!event) event = m_context.CreateEvent("DisplayPropertyChange"); + if (event) readable = &event->GetReadableEvent(); + else result = Kernel::ResultOutOfResource; + } + if (readable) readable->Open(kernel); + } + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(readable); + LOG_DEBUG(Service_AM, "VI2 vsync event result={:#x}", result.raw); + } + void DuplicateDisplayEvent(HLERequestContext& ctx) { + const auto* p = ctx.CommandBuffer(); + auto source = ctx.GetObjectFromHandle<Kernel::KReadableEvent>(ctx.GetCopyHandle(0)); + const Result result = p[0] == 0x30 && p[1] == 0x80000001 && p[4] <= 1 && source.IsNotNull() + ? ResultSuccess : Kernel::ResultInvalidCombination; + Kernel::KReadableEvent* readable{}; + if (result.IsSuccess()) { + readable = source.GetPointerUnsafe(); + readable->Open(kernel); + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(readable); + } + static std::optional<size_t> GetPlayerDisplayIndex(u64 name) { + if ((name & 0xffffffffffffULL) != 0x726579616c70ULL || (name >> 56) != 0) { + return std::nullopt; + } + const u32 player = (name >> 48) & 0xff; + if (player < '1' || player > '3') return std::nullopt; + return player - '1'; + } + Result OpenDefaultDisplay(u64 name, u32 flags) { + if (const auto player = GetPlayerDisplayIndex(name)) { + R_UNLESS(flags < m_notifications.size() * 32, Kernel::ResultInvalidCombination); + R_UNLESS(m_player_display_references[*player] < 1024, Kernel::ResultOutOfResource); + // GameShare's detached player displays retain their own lifetime; + // they do not target the host's default display compositor. + m_player_display_notifications[*player] = flags; + ++m_player_display_references[*player]; + R_SUCCEED(); + } + // The native display token is an eight-byte name; command 101 returns + // only a result. Retain the real VI display ID for later operations. + R_UNLESS(name == 0x00746c7561666564ULL, Kernel::ResultNotFound); + R_UNLESS(flags == 1, Kernel::ResultNotImplemented); + R_UNLESS(m_display_references < 1024, Kernel::ResultOutOfResource); + if (!m_display_service) { + const auto root = system.ServiceManager().GetService<VI::IManagerRootService>("vi:m", true); + R_UNLESS(root, Kernel::ResultInvalidState); + R_TRY(root->GetDisplayService(&m_display_service, VI::Policy::Compositor)); + } + if (!m_default_display) { + u64 id{}; + R_TRY(m_display_service->OpenDefaultDisplay(&id)); + m_default_display = id; + } + m_display_notification = flags; + ++m_display_references; + R_SUCCEED(); + } + void OpenDisplay(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 name = u64{packet[2]} | (u64{packet[3]} << 32); + const auto result = packet[0] == 0x75 && packet[1] == 3 + ? OpenDefaultDisplay(name, packet[4]) : Kernel::ResultInvalidCombination; + LOG_DEBUG(Service_AM, "VI2 open display name={:#x}, flags={:#x}, references={}, result={:#x}", + name, packet[4], m_display_references, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void CloseDisplay(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + const u64 name = u64{packet[2]} | (u64{packet[3]} << 32); + if (packet[0] == 0x76 && packet[1] == 2 && GetPlayerDisplayIndex(name)) { + const auto player = *GetPlayerDisplayIndex(name); + result = Kernel::ResultNotFound; + if (m_player_display_references[player]) { + if (--m_player_display_references[player] == 0) { + const auto notification = m_player_display_notifications[player]; + m_notifications[notification / 32] |= 1U << (notification % 32); + if (m_event) m_event->Signal(kernel); + } + result = ResultSuccess; + } + } else if (packet[0] == 0x76 && packet[1] == 2 && name == 0x00746c7561666564ULL) { + result = Kernel::ResultNotFound; + if (m_default_display && m_display_references) { + result = ResultSuccess; + if (m_display_references == 1) { + result = m_display_service->CloseDisplay(*m_default_display); + if (result.IsSuccess()) { m_default_display.reset(); } + } + if (result.IsSuccess()) { + --m_display_references; + if (!m_display_references) { + m_notifications[m_display_notification / 32] |= 1U << (m_display_notification % 32); + if (m_event) m_event->Signal(kernel); + LOG_DEBUG(Service_AM, "VI2 display retired notification={}", m_display_notification); + } + } + } + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void RegisterClientState(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + const u32 first = packet[4]; + const u32 second = packet[5]; + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0xd9 && packet[1] == 4 && token != 0 && + first < 32 && second < 32 && first != second) { + const u32 slots = (1U << first) | (1U << second); + const auto existing = std::ranges::find(m_client_states, token, &ClientState::token); + const auto free = std::ranges::find(m_client_states, u64{0}, &ClientState::token); + result = Kernel::ResultInvalidState; + if (existing == m_client_states.end() && !(m_client_slots & slots)) { + result = Kernel::ResultOutOfResource; + if (free != m_client_states.end()) { + // The SDK sends its object address as an opaque identity, not a + // service-accessible buffer. Keep the associated slots session-local. + *free = {token, slots}; + free->retire_notification = first; + free->available_notification = second; + m_client_slots |= slots; + result = ResultSuccess; + } + } + } + LOG_DEBUG(Service_AM, "VI2 register client state token={:#x}, slots={}/{}, result={:#x}", + token, first, second, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void UnregisterClientState(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0xda && packet[1] == 2 && token != 0) { + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (state != m_client_states.end()) { + if (m_default_endpoint == token) { m_default_endpoint.reset(); } + if (state->layer) m_display_service->GetContainer()->DestroyManagedLayer(*state->layer); + m_notifications[state->retire_notification / 32] |= 1U << (state->retire_notification % 32); + if (m_event) m_event->Signal(kernel); + LOG_DEBUG(Service_AM, "VI2 client retired token={:#x}, notification={}", token, state->retire_notification); + m_client_slots &= ~state->slots; + *state = {}; + result = ResultSuccess; + } + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void GetClientAvailability(HLERequestContext& ctx) { + RefreshReleasedBuffers(); + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + Result result = Kernel::ResultInvalidCombination; + u64 id{}; + if (packet[0] == 0xe4 && packet[1] == 2 && token) { + auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (state != m_client_states.end() && m_default_display) { + result = ResultSuccess; + if (state->player_display) { + id = 0; + } else if (!state->layer) { + result = m_display_service->GetContainer()->CreateManagedLayer(&id, *m_default_display, m_aruid); + if (result.IsSuccess()) state->layer = id; + } else { + id = *state->layer; + } + } + } + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + const u64 available = result.IsSuccess() ? state->available : 0; + LOG_DEBUG(Service_AM, "VI2 client availability token={:#x}, layer={}, mask={:#x}, result={:#x}", token, id, available, result.raw); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result.raw); + rb.Push(available); + } + void CancelClientBuffer(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + const u32 slot = GraphicsSupervisorProtocol::DecodeBufferSlot(packet[4]); + Result result = Kernel::ResultInvalidCombination; + u64 available{}; + if (packet[0] == 0xf4 && packet[1] == 19 && slot < 15) { + auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (token && state != m_client_states.end()) { + // Unlike present (229), cancel has no image or input GPU fence. + state->available |= u64{1} << slot; + available = state->available; + result = ResultSuccess; + } + } + LOG_DEBUG(Service_AM, "VI2 cancel buffer token={:#x}, slot={}, mask={:#x}, result={:#x}", token, slot, available, result.raw); + IPC::ResponseBuilder rb{ctx, 8}; + rb.Push(result.raw); + rb.Push(available); + for (u32 i = 0; i < 4; ++i) rb.Push<u32>(0); + } + void RefreshReleasedBuffers() { + for (auto& state : m_client_states) { + if (!state.producer || !m_images->module) continue; + bool changed{}; + for (size_t slot = 0; slot < state.queued.size(); ++slot) { + if (!state.queued[slot]) continue; + const auto fence = state.producer->GetReleasedBufferFence(*state.queued[slot]); + if (!fence) continue; + bool complete = true; + for (u32 i = 0; i < fence->num_fences; ++i) { + const auto& point = fence->fences[i]; + if (point.id != ~u32{0}) { + const auto counter = m_images->module->GetContainer().GetSyncpointManager().ReadAllocatedSyncpoint(point.id); + if (!counter || static_cast<s32>(*counter - point.value) < 0) { + complete = false; + } + } + } + if (!complete) continue; + state.queued[slot].reset(); + state.available |= u64{1} << slot; + LOG_DEBUG(Service_AM, "Native display buffer retired: slot={}, mask={:#x}", slot, state.available); + changed = true; + } + if (changed) { + m_notifications[state.available_notification / 32] |= 1U << (state.available_notification % 32); + if (m_event) m_event->Signal(kernel); + } + } + } + Result QueueNativeImage(ClientState& state, const u32* packet) { + const u32 slot = GraphicsSupervisorProtocol::DecodeBufferSlot(packet[4]); + R_UNLESS(!state.player_display, Kernel::ResultNotImplemented); + R_UNLESS(state.layer && state.display && slot < 15 && !state.queued[slot], Kernel::ResultInvalidState); + const std::scoped_lock images_lock{m_images->mutex}; + const auto image = std::ranges::find(m_images->images, packet[5], &NativeImage::id); + R_UNLESS(image != m_images->images.end() && image->window_pool && m_images->module, Kernel::ResultNotFound); + // The observed presentation is a four-byte color image with the + // backend's 16-GOB block height and no byte offset within its pool. + R_UNLESS(packet[6] == 0 && packet[7] == image->preference[2] && packet[8] == 3 && + image->preference[4] == 7 && + (state.output_encoding == 0x14 || state.output_encoding == 0x5), + Kernel::ResultNotImplemented); + const u32 fence_id = ~packet[22]; + R_UNLESS(packet[23] == 0 && packet[25] == 0 && + (fence_id == ~u32{0} || m_images->module->GetContainer().GetSyncpointManager().IsSyncpointAllocated(fence_id)), + Kernel::ResultInvalidCombination); + if (!state.producer) { + R_TRY(m_display_service->GetContainer()->GetLayerProducerHandle(&state.producer, *state.layer)); + android::QueueBufferOutput output{}; + R_UNLESS(state.producer->Connect({}, android::NativeWindowApi::Egl, false, &output) == android::Status::NoError, + Kernel::ResultInvalidState); + R_UNLESS(state.producer->SetBufferCount(16) == android::Status::NoError, Kernel::ResultInvalidState); + s32 binder{}; + R_TRY(m_display_service->GetContainer()->OpenLayer(&binder, *state.layer, m_aruid)); + R_TRY(m_display_service->GetContainer()->SetLayerVisibility(*state.layer, true)); + } + auto native = std::make_shared<android::NvGraphicBuffer>(); + native->width = static_cast<s32>((image->preference[2] & 0xffffU) + 1); + native->height = static_cast<s32>((image->preference[2] >> 16) + 1); + native->stride = static_cast<s32>(image->preference[5] * 16); + native->format = android::PixelFormat::Rgba8888; + native->external_format = native->format; + native->buffer_id = image->memory_id; + auto buffer = std::make_shared<android::GraphicBuffer>(m_images->module->GetContainer().GetNvMapFile(), native); + s32 queue_slot{}; + R_UNLESS(state.producer->AttachBuffer(&queue_slot, buffer) == android::Status::NoError, Kernel::ResultOutOfResource); + android::QueueBufferInput input{}; + android::QueueBufferOutput output{}; + input.crop = {0, 0, native->width, native->height}; + input.swap_interval = 1; + input.fence = android::Fence::NoFence(); + if (fence_id != ~u32{0}) { + input.fence.num_fences = 1; + input.fence.fences[0] = {static_cast<s32>(fence_id), packet[24]}; + } + if (state.producer->QueueBuffer(queue_slot, input, &output) != android::Status::NoError) { + state.producer->CancelBuffer(queue_slot, input.fence); + return Kernel::ResultInvalidState; + } + state.queued[slot] = queue_slot; + state.available &= ~(u64{1} << slot); + if (!m_release_poll) { + m_release_poll = Core::Timing::CreateEvent("NativeViBufferRelease", [this](s64, std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> { + const auto guard = LockService(); + RefreshReleasedBuffers(); + return std::nullopt; + }); + system.CoreTiming().ScheduleLoopingEvent(std::chrono::milliseconds{1}, std::chrono::milliseconds{1}, m_release_poll); + } + LOG_INFO(Service_AM, "Native image queued for display: image={}, slot={}, fence={}/{}, dimensions={}x{}", + image->id, slot, fence_id, packet[24], native->width, native->height); + R_SUCCEED(); + } + void PresentClientBuffer(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + RefreshReleasedBuffers(); + Result result = Kernel::ResultInvalidCombination; + u64 available{}; + if (packet[0] == 0xf5 && packet[1] == 24) { + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (token && state != m_client_states.end()) { + result = QueueNativeImage(*state, packet); + available = state->available; + } + } + LOG_DEBUG(Service_AM, "VI2 present token={:#x}, image={}, slot={}, mask={:#x}, result={:#x}", token, packet[5], packet[4], available, result.raw); + IPC::ResponseBuilder rb{ctx, 8}; + rb.Push(result.raw); + rb.Push(available); + for (u32 i = 0; i < 4; ++i) rb.Push<u32>(0); + } + void SetClientOutputEncoding(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + const u64 encoding = u64{packet[4]} | (u64{packet[5]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x16f && packet[1] == 4) { + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (token && state != m_client_states.end()) { + result = Kernel::ResultNotImplemented; + // Only the SDK's observed default output encoding is supported. + if (encoding == 0x14 || encoding == 0x5) { + state->output_encoding = encoding; + result = ResultSuccess; + } + } + } + LOG_DEBUG(Service_AM, "VI2 output encoding token={:#x}, encoding={:#x}, result={:#x}", token, encoding, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void BindClientDisplay(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + const u64 name = u64{packet[4]} | (u64{packet[5]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x10b && packet[1] == 4) { + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (token && state != m_client_states.end() && GetPlayerDisplayIndex(name)) { + const auto player = *GetPlayerDisplayIndex(name); + result = Kernel::ResultInvalidState; + if (m_player_display_references[player] && !state->display) { + result = ResultSuccess; + if (state->layer) { + result = m_display_service->GetContainer()->DestroyManagedLayer(*state->layer); + } + if (result.IsSuccess()) { + state->layer.reset(); + state->display = name; + state->player_display = player; + } + } + } else if (token && state != m_client_states.end() && name == 0x00746c7561666564ULL && + m_default_display && m_display_references) { + result = Kernel::ResultInvalidState; + if (!state->display) { + state->display = m_default_display; + result = ResultSuccess; + } + } + } + LOG_DEBUG(Service_AM, "VI2 bind client token={:#x}, display={:#x}, result={:#x}", + token, name, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void UnbindClientDisplay(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 token = u64{packet[2]} | (u64{packet[3]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x10c && packet[1] == 2) { + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (token && state != m_client_states.end() && state->display) { + state->display.reset(); + state->player_display.reset(); + result = ResultSuccess; + } + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void OpenEndpoint(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 name = u64{packet[2]} | (u64{packet[3]} << 32); + const u64 display_name = u64{packet[4]} | (u64{packet[5]} << 32); + const u64 token = u64{packet[6]} | (u64{packet[7]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x7f && packet[1] == 6) { + const auto state = std::ranges::find(m_client_states, token, &ClientState::token); + result = Kernel::ResultNotFound; + if (token && state != m_client_states.end() && state->display && + m_default_display == state->display && name == 0x00746c7561666564ULL && + display_name == name) { + result = Kernel::ResultInvalidState; + if (!m_default_endpoint) { + m_default_endpoint = token; + result = ResultSuccess; + } + } + } + LOG_DEBUG(Service_AM, "VI2 open endpoint name={:#x}, display={:#x}, client={:#x}, result={:#x}", + name, display_name, token, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void CloseEndpoint(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 name = u64{packet[2]} | (u64{packet[3]} << 32); + const u64 display_name = u64{packet[4]} | (u64{packet[5]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x80 && packet[1] == 4 && name == 0x00746c7561666564ULL && + display_name == name) { + result = Kernel::ResultNotFound; + if (m_default_endpoint) { + m_default_endpoint.reset(); + result = ResultSuccess; + } + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void ConfigureRequestGroups(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x43 && packet[1] == 1 && packet[2] < 8) { + m_request_group_limit = packet[2]; + m_request_groups_initialized = true; + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void GetSessionLimit(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const Result result = packet[0] == 16 + ctx.GetCommand() && packet[1] == 0 + ? ResultSuccess : Kernel::ResultInvalidCombination; + // The SDK converts this inclusive upper group index into 32 client slots. + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push<u32>(ctx.GetCommand() == 5 ? 7 : 0); + } + + void GetNotifications(HLERequestContext& ctx) { + Result result = Kernel::ResultInvalidSize; + if (ctx.GetWriteBufferSize() >= sizeof(m_notifications)) { + ctx.WriteBuffer(m_notifications); + LOG_DEBUG(Service_AM, "VI2 notifications={:08x}", fmt::join(m_notifications, ",")); + m_notifications.fill(0); + if (m_event) m_event->Clear(kernel); + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void ReleaseNotificationEvent(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const auto result = packet[0] == 0x1d && packet[1] == 1 && packet[2] == 0 + ? ResultSuccess : Kernel::ResultInvalidCombination; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + } + void GetNotificationEvent(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + Kernel::KReadableEvent* readable{}; + if (packet[0] == 0x1b && packet[1] == 1 && packet[2] == 0) { + if (!m_event) { + m_event = m_context.CreateEvent("Vi2Notification"); + } + result = m_event ? ResultSuccess : Kernel::ResultOutOfResource; + if (m_event) { + if (std::ranges::any_of(m_notifications, [](u32 value) { return value != 0; })) m_event->Signal(kernel); + readable = &m_event->GetReadableEvent(); + readable->Open(kernel); + } + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(readable); + } + + std::shared_ptr<NativeImageRegistry> m_images; + std::shared_ptr<Core::Timing::EventType> m_release_poll; + std::array<ClientState, 16> m_client_states{}; + u32 m_client_slots{}; + std::shared_ptr<VI::IApplicationDisplayService> m_display_service; + Kernel::KReadableEvent* m_vsync_event{}; + std::array<Kernel::KEvent*, 4> m_display_change_events{}; + std::optional<u64> m_default_display; + std::array<bool, 6> m_display_capabilities{}; + u32 m_display_references{}; + u32 m_display_notification{}; + std::array<u32, 3> m_player_display_references{}; + std::array<u32, 3> m_player_display_notifications{}; + bool m_request_groups_initialized{}; + u32 m_request_group_limit{}; + std::optional<u64> m_default_endpoint; + std::array<u32, 32> m_notifications{}; + const u64 m_aruid; + KernelHelpers::ServiceContext m_context; + Kernel::KEvent* m_event{}; +}; + +} // namespace + + +IGraphicsSupervisor::IGraphicsSupervisor(Core::System& system, Kernel::KProcess* process, u64 aruid) + : ServiceFramework{system, "IGraphicsSupervisor"}, m_context{system, "GraphicsSupervisor"}, + m_syncpoint_cache_event{m_context}, m_process{process}, m_aruid{aruid} { + m_process->Open(kernel); + m_image_registry = std::make_shared<NativeImageRegistry>(kernel, m_process); + static const FunctionInfo functions[] = { + {4, &IGraphicsSupervisor::Initialize, "Initialize"}, + {11, &IGraphicsSupervisor::BindProcess, "BindProcess"}, + {101, &IGraphicsSupervisor::CompleteBootstrap, "CompleteBootstrap"}, + {115, &IGraphicsSupervisor::ConfigureNativeMemory, "ConfigureNativeMemory"}, + {111, &IGraphicsSupervisor::CreateNativeMemory, "CreateNativeMemory"}, + {112, &IGraphicsSupervisor::DestroyNativeMemory, "DestroyNativeMemory"}, + {114, &IGraphicsSupervisor::BindNativeMemory, "BindNativeMemory"}, + {201, &IGraphicsSupervisor::InitializeSyncpointContext, "InitializeSyncpointContext"}, + {202, &IGraphicsSupervisor::GetSyncpointSession, "GetSyncpointSession"}, + {204, &IGraphicsSupervisor::BindSyncpointMemory, "BindSyncpointMemory"}, + {301, &IGraphicsSupervisor::InitializeDisplayContext, "InitializeDisplayContext"}, + {305, &IGraphicsSupervisor::OpenDisplaySession, "OpenDisplaySession"}, + {314, &IGraphicsSupervisor::ConfigureNativeWindowImagePool, "DestroyNativeWindowAliasImagePool"}, + {315, &IGraphicsSupervisor::ConfigureNativeWindowImagePool, "CreateNativeWindowAliasImagePool"}, + {401, &IGraphicsSupervisor::InitializeGpuContext, "InitializeGpuContext"}, + {402, &IGraphicsSupervisor::InitializeGpuContext, "ActivateGpuContext"}, + {423, &IGraphicsSupervisor::BindNativeGpuMemory, "BindNativeGpuMemory"}, + {429, &IGraphicsSupervisor::CreateNativeImage, "CreateNativeImage"}, + {430, &IGraphicsSupervisor::DestroyNativeImage, "DestroyNativeImage"}, + {434, &IGraphicsSupervisor::RegisterGpuAddressSpace, "RegisterGpuAddressSpace"}, + {497, &IGraphicsSupervisor::OpenGpuSession, "OpenGpuSession"}, + {1101, &IGraphicsSupervisor::InitializeFrameContext, "InitializeFrameContext"}, + {1102, &IGraphicsSupervisor::InitializeFrameContext, "ActivateFrameContext"}, + {1402, &IGraphicsSupervisor::GetNvidiaDriver, "GetNvidiaDriver"}, + {1501, &IGraphicsSupervisor::InitializeImageContext, "InitializeImageContext1501"}, + {435, &IGraphicsSupervisor::CalculateImageRequirements, "CalculateNativeImageSize"}, + {1510, &IGraphicsSupervisor::CalculateImageRequirements, "CalculateImageRequirements"}, + {1502, &IGraphicsSupervisor::InitializeImageContext, "InitializeImageContext1502"}, + }; + RegisterHandlersTipc(functions); +} + +IGraphicsSupervisor::~IGraphicsSupervisor() { + if (m_cache_poll) { + system.CoreTiming().UnscheduleEvent(m_cache_poll); + } + if (m_syncpoint_rw) { + m_syncpoint_rw->Close(kernel); + m_syncpoint_ro->Close(kernel); + } + if (m_nv_module) { + auto& nvmap = m_nv_module->GetContainer().GetNvMapFile(); + for (const auto id : m_native_memories) { + if (const auto info = nvmap.FreeHandle(id, false); info && info->can_unlock && info->address != 0) { + m_process->GetPageTable().UnlockForDeviceAddressSpace(info->address, info->size); + } + } + if (m_native_memory_session) { + m_nv_module->GetContainer().CloseSession(Nvidia::NvCore::SessionId{*m_native_memory_session}); + } + } + m_context.CloseEvent(m_syncpoint_request_event); + m_process->Close(kernel); +} + +Result IGraphicsSupervisor::HandleSyncRequest(Kernel::KServerSession& session, + HLERequestContext& ctx) { + if (!ctx.IsTipc() || handlers_tipc.contains(ctx.GetCommand())) { + return ServiceFrameworkBase::HandleSyncRequest(session, ctx); + } + const auto guard = LockService(); + InvokeRequestTipc(ctx); + // Unsupported TIPC must return an error instead of leaving request data as a reply. + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultNotImplemented.raw); + return system.IsPoweredOn() ? ctx.WriteToOutgoingCommandBuffer() : ResultSuccess; +} + +void IGraphicsSupervisor::Initialize(HLERequestContext& ctx) { + const auto request = GraphicsSupervisorProtocol::DecodeBootstrap( + std::span<const u32>{ctx.CommandBuffer(), IPC::COMMAND_BUFFER_LENGTH}); + Result result = Kernel::ResultInvalidCombination; + if (request) { + auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(request->process_handle); + const bool empty_remote_memory = std::ranges::all_of( + request->parameters, [](u64 value) { return value == 0; }); + if (process.IsNull() || process.GetPointerUnsafe() != m_process) { + result = Kernel::ResultInvalidHandle; + } else if (m_phase != Phase::Created) { + result = Kernel::ResultInvalidState; + } else if (request->applet_resource_user_id != m_aruid) { + result = Kernel::ResultInvalidArgument; + } else if ((request->flags != 0x8000000000000001ULL && request->flags != 0x8000000000000005ULL && request->flags != 0x8000000000000006ULL && request->flags != 0x8000000000000007ULL) || !empty_remote_memory) { + result = Kernel::ResultNotImplemented; + } else { + m_phase = Phase::Initialized; + result = ResultSuccess; + } + LOG_DEBUG(Service_AM, "graphics bootstrap aruid={:#x}, flags={:#x}, result={:#x}", + request->applet_resource_user_id, request->flags, result.raw); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::BindProcess(HLERequestContext& ctx) { + Result result = Kernel::ResultInvalidCombination; + const auto* packet = ctx.CommandBuffer(); + if (packet[0] == 0x1b && packet[1] == 0x80000000 && packet[2] == 2) { + auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(ctx.GetCopyHandle(0)); + if (process.IsNull() || process.GetPointerUnsafe() != m_process) { + result = Kernel::ResultInvalidHandle; + } else if (m_phase != Phase::Initialized) { + result = Kernel::ResultInvalidState; + } else { + m_phase = Phase::ProcessBound; + result = ResultSuccess; + } + } + LOG_DEBUG(Service_AM, "graphics process binding result={:#x}", result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::CompleteBootstrap(HLERequestContext& ctx) { + Result result = Kernel::ResultInvalidCombination; + const auto* packet = ctx.CommandBuffer(); + if (packet[0] == 0x75 && packet[1] == 0) { + if (m_phase != Phase::ProcessBound) { + result = Kernel::ResultInvalidState; + } else { + m_phase = Phase::Ready; + result = ResultSuccess; + } + } + LOG_DEBUG(Service_AM, "graphics bootstrap completion result={:#x}", result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::InitializeImageContext(HLERequestContext& ctx) { + Result result = Kernel::ResultInvalidCombination; + const auto* packet = ctx.CommandBuffer(); + if (packet[0] == 16 + ctx.GetCommand() && packet[1] == 0) { + if (m_phase != Phase::Ready) { + result = Kernel::ResultInvalidState; + } else if (ctx.GetCommand() == 1501) { + m_image_context_initialized = true; + result = ResultSuccess; + } else if (m_image_context_initialized) { + m_image_formats_initialized = true; + result = ResultSuccess; + } else { + result = Kernel::ResultInvalidState; + } + } + LOG_DEBUG(Service_AM, "empty image context command={}, result={:#x}", + ctx.GetCommand(), result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::OpenGpuSession(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + Kernel::KClientSession* client{}; + const u64 flags = u64{packet[2]} | (u64{packet[3]} << 32); + if (packet[0] == 0x201 && packet[1] == 2 && (flags == 3 || flags == 4)) { + if (m_phase != Phase::Ready || !m_nv_module) { + result = Kernel::ResultInvalidState; + } else { + auto handler = std::make_shared<IGpuSession>(system, m_nv_module, m_process, flags == 3); + result = CreateGraphicsSession(system, ctx, handler, &client); + if (result.IsSuccess()) { + if (flags == 3) { m_image_formats_initialized = true; } + std::erase_if(m_gpu_sessions, [](const auto& item) { return item.expired(); }); + m_gpu_sessions.emplace_back(handler); + } + } + } + LOG_DEBUG(Service_AM, "GPU control session flags={:#x}, result={:#x}", flags, result.raw); + // This SDK reads the move-handle slot before the result, including on failure. + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(client); +} + +void IGraphicsSupervisor::RegisterGpuAddressSpace(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x1c2 && packet[1] == 1) { + result = m_gpu_context_ready ? Kernel::ResultNotFound : Kernel::ResultInvalidState; + if (m_gpu_context_ready) { + for (const auto& item : m_gpu_sessions) { + if (auto handler = item.lock()) { + result = handler->RegisterAddressSpace(packet[2]); + if (result.IsSuccess()) { + break; + } + } + } + } + } + LOG_DEBUG(Service_AM, "register GPU address space id={}, result={:#x}", packet[2], result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::InitializeDisplayContext(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x13d && packet[1] == 0) { + result = m_phase == Phase::Ready ? ResultSuccess : Kernel::ResultInvalidState; + if (result.IsSuccess()) { + m_display_context_initialized = true; + } + } + LOG_DEBUG(Service_AM, "display context initialization result={:#x}", result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::OpenDisplaySession(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Kernel::KClientSession* client{}; + const u64 flags = u64{packet[2]} | (u64{packet[3]} << 32); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x141 && packet[1] == 2 && (flags == 0x300010000ULL || flags == 0x300020000ULL)) { + result = m_display_context_initialized ? ResultSuccess : Kernel::ResultInvalidState; + if (result.IsSuccess()) { + result = CreateGraphicsSession(system, ctx, std::make_shared<IVi2Session>(system, m_aruid, m_image_registry), + &client); + } + } + LOG_DEBUG(Service_AM, "VI2 session flags={:#x}, result={:#x}", flags, result.raw); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result.raw); + ctx.AddMoveObject(client); +} + +void IGraphicsSupervisor::InitializeGpuContext(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 16 + ctx.GetCommand() && packet[1] == 0) { + if (m_phase != Phase::Ready) { + result = Kernel::ResultInvalidState; + } else if (ctx.GetCommand() == 401) { + m_gpu_context_initialized = true; + result = ResultSuccess; + } else if (m_gpu_context_initialized) { + m_gpu_context_ready = true; + result = ResultSuccess; + } else { + result = Kernel::ResultInvalidState; + } + } + LOG_DEBUG(Service_AM, "GPU context command={}, result={:#x}", ctx.GetCommand(), result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::InitializeFrameContext(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 16 + ctx.GetCommand() && packet[1] == 0) { + if (m_phase != Phase::Ready) { + result = Kernel::ResultInvalidState; + } else if (ctx.GetCommand() == 1101) { + m_frame_context_initialized = true; + result = ResultSuccess; + } else if (m_frame_context_initialized) { + m_frame_context_ready = true; + result = ResultSuccess; + } else { + result = Kernel::ResultInvalidState; + } + } + LOG_DEBUG(Service_AM, "empty frame context command={}, result={:#x}", ctx.GetCommand(), result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::ConfigureNativeMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x83 && packet[1] == 2 && packet[3] == 0 && + (packet[2] == 0 || packet[2] == 2)) { + result = m_phase == Phase::Ready ? ResultSuccess : Kernel::ResultInvalidState; + } + // Clear the optional Gpuk-memory mode: Eden currently provides the NVDRV path. + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result.raw); + rb.Push<u64>(0); + LOG_DEBUG(Service_AM, "native memory uses NVDRV-compatible mode, result={:#x}", result.raw); +} + +void IGraphicsSupervisor::InitializeSyncpointContext(HLERequestContext& ctx) { + Result result = Kernel::ResultInvalidCombination; + const auto* packet = ctx.CommandBuffer(); + if (packet[0] == 0xd9 && packet[1] == 0) { + if (m_phase != Phase::Ready) { + result = Kernel::ResultInvalidState; + } else { + m_syncpoint_context_initialized = true; + result = ResultSuccess; + } + } + LOG_DEBUG(Service_AM, "syncpoint context initialization result={:#x}", result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::GetSyncpointSession(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidState; + Kernel::KClientSession* client{}; + if (packet[0] == 0xda && packet[1] == 0 && m_syncpoint_context_initialized && m_nv_module) { + result = CreateGraphicsSession(system, ctx, + std::make_shared<ISyncpointSupervisor>(system, m_nv_module), &client); + } + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + return; + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess.raw); + rb.PushMoveObjects(ctx, client); + LOG_DEBUG(Service_AM, "opened syncpoint-supervisor session"); +} + +void IGraphicsSupervisor::GetNvidiaDriver(HLERequestContext& ctx) { + const auto Fail = [&](Result result) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); + }; + const auto* packet = ctx.CommandBuffer(); + if (packet[0] != 0x58a || packet[1] != 0) { + Fail(Kernel::ResultInvalidCombination); + return; + } + if (m_phase != Phase::Ready) { + Fail(Kernel::ResultInvalidState); + return; + } + auto source = system.ServiceManager().GetService<Nvidia::NVDRV>("nvdrv:s", true); + if (!source) { + Fail(Kernel::ResultNotFound); + return; + } + m_nv_module = source->GetModule(); + Kernel::KClientSession* client{}; + const auto result = CreateGraphicsSession(system, ctx, + std::make_shared<Nvidia::NVDRV>(system, m_nv_module, "nvdrv:gsv", m_process, m_aruid), + &client); + if (result.IsError()) { + Fail(result); + return; + } + LOG_DEBUG(Service_AM, "opened process-bound NVDRV session for aruid={:#x}", m_aruid); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess.raw); + rb.PushMoveObjects(ctx, client); +} + +void IGraphicsSupervisor::BindSyncpointMemory(HLERequestContext& ctx) { + using namespace GraphicsSyncpointCache; + const auto Fail = [&](Result result) { + IPC::ResponseBuilder rb{ctx, 2, 0, 2}; + rb.Push(result.raw); + ctx.AddMoveObject(nullptr); + ctx.AddMoveObject(nullptr); + }; + const auto* packet = ctx.CommandBuffer(); + if (packet[0] != 0xdc || packet[1] != 0x80000006 || packet[2] != 4 || + packet[5] != 1 || packet[6] != 0x1ff || packet[7] != 0x1000 || packet[8] != 0 || + packet[9] != 0x2000 || packet[10] != 0) { + Fail(Kernel::ResultInvalidCombination); + return; + } + if (!m_syncpoint_context_initialized || !m_nv_module || m_syncpoint_rw) { + Fail(Kernel::ResultInvalidState); + return; + } + auto rw = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0)); + auto ro = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(1)); + if (rw.IsNull() || ro.IsNull() || rw->GetOwner() != m_process || ro->GetOwner() != m_process || + rw.GetPointerUnsafe() == ro.GetPointerUnsafe()) { + Fail(Kernel::ResultInvalidHandle); + return; + } + if (rw->GetSize() != 0x1000 || ro->GetSize() != 0x2000) { + Fail(Kernel::ResultInvalidSize); + return; + } + auto* request_event = m_context.CreateEvent("SyncpointCacheRequest"); + if (request_event == nullptr) { + Fail(Kernel::ResultOutOfResource); + return; + } + m_syncpoint_request_event = request_event; + m_syncpoint_rw = rw.GetPointerUnsafe(); + m_syncpoint_ro = ro.GetPointerUnsafe(); + m_syncpoint_rw->Open(kernel); + m_syncpoint_ro->Open(kernel); + auto& memory = m_process->GetMemory(); + const auto rw_base = rw->GetSourceAddress(); + const auto ro_base = ro->GetSourceAddress(); + const std::array<u8, 0x2000> zeros{}; + memory.WriteBlock(rw_base, zeros.data(), 0x1000); + memory.WriteBlock(ro_base, zeros.data(), 0x2000); + memory.WriteBlock(ro_base, Header.data(), sizeof(Header)); + for (u32 id = 0; id < CounterCount; ++id) { + memory.Write64(ro_base + ValuesOffset + id * sizeof(u64), Unallocated); + } + m_cache_poll = Core::Timing::CreateEvent("GraphicsSyncpointCache", + [this](s64, std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> { + const auto guard = LockService(); + this->RefreshSyncpointCache(); + return std::nullopt; + }); + system.CoreTiming().ScheduleLoopingEvent(std::chrono::milliseconds{1}, + std::chrono::milliseconds{1}, m_cache_poll); + request_event->Open(kernel); + auto* completed = m_syncpoint_cache_event.GetHandle(); + completed->Open(kernel); + IPC::ResponseBuilder rb{ctx, 2, 0, 2}; + rb.Push(ResultSuccess.raw); + ctx.AddMoveObject(request_event); + ctx.AddMoveObject(completed); + LOG_DEBUG(Service_AM, "bound syncpoint cache: {} real counters, rw={:#x}, ro={:#x}", + CounterCount, GetInteger(rw_base), GetInteger(ro_base)); +} + +void IGraphicsSupervisor::RefreshSyncpointCache() { + using namespace GraphicsSyncpointCache; + if (!m_syncpoint_rw || !system.IsPoweredOn()) { + return; + } + auto& memory = m_process->GetMemory(); + const auto rw = m_syncpoint_rw->GetSourceAddress(); + const auto ro = m_syncpoint_ro->GetSourceAddress(); + const u64 requests = memory.Read64(rw + GroupRequestOffset); + const u64 acknowledgements = memory.Read64(ro + GroupAckOffset); + u64 groups = (requests ^ acknowledgements) & ((1ULL << GroupCount) - 1); + if (groups == 0) { + return; + } + auto& syncpoints = m_nv_module->GetContainer().GetSyncpointManager(); + while (groups) { + const u32 group = static_cast<u32>(std::countr_zero(groups)); + groups &= groups - 1; + const u64 requested_slots = memory.Read64(rw + SlotRequestOffset + group * sizeof(u64)); + u64 slots = requested_slots ^ memory.Read64(ro + SlotAckOffset + group * sizeof(u64)); + while (slots) { + const u32 bit = static_cast<u32>(std::countr_zero(slots)); + slots &= slots - 1; + const u32 id = group * 64 + bit; + const auto address = ro + ValuesOffset + id * sizeof(u64); + const auto value = syncpoints.ReadAllocatedSyncpoint(id); + const u64 updated = m_counter_values[id].Observe(value); + memory.Write64(address, updated); + } + std::atomic_thread_fence(std::memory_order_release); + memory.Write64(ro + SlotAckOffset + group * sizeof(u64), requested_slots); + } + std::atomic_thread_fence(std::memory_order_release); + memory.Write64(ro + GroupAckOffset, requests); + m_syncpoint_request_event->Clear(kernel); + m_syncpoint_cache_event.Signal(kernel); +} + +void IGraphicsSupervisor::CalculateImageRequirements(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + std::optional<NativeImageRequirements> requirements; + const bool gpu_image = ctx.GetCommand() == 435; + if (packet[0] == (gpu_image ? 0x1c3U : 0x5f6U) && packet[1] == 17 && + packet[3] == (gpu_image ? 1U : 2U)) { + result = Kernel::ResultInvalidState; + if (m_phase == Phase::Ready && m_image_formats_initialized) { + requirements = CalculateNativeImageRequirements(std::span{packet + 2, 16}, packet[18]); + result = requirements ? ResultSuccess : Kernel::ResultNotImplemented; + } + } + LOG_DEBUG(Service_AM, "native image requirements dimensions={:#x}, block={}, pitch={}, size={:#x}, alignment={:#x}, result={:#x}", + packet[4], packet[6], packet[7], requirements ? requirements->size : 0, + requirements ? requirements->alignment : 0, result.raw); + IPC::ResponseBuilder rb{ctx, gpu_image ? 4U : 6U}; + rb.Push(result.raw); + rb.Push<u64>(requirements ? requirements->size : 0); + if (!gpu_image) { rb.Push<u64>(requirements ? requirements->alignment : 0); } +} +void IGraphicsSupervisor::CreateNativeMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const u64 size = u64{packet[3]} | (u64{packet[4]} << 32); + Result result = Kernel::ResultInvalidCombination; + u32 id{}; + if (packet[0] == 0x7f && packet[1] == 3 && (packet[2] & 0xff) == 3) { + result = Kernel::ResultInvalidState; + if (m_phase == Phase::Ready && m_image_formats_initialized && m_nv_module) { + result = Kernel::ResultInvalidSize; + if (size && size <= (1ULL << 32) && Common::IsAligned(size, Kernel::PageSize)) { + result = Kernel::ResultOutOfResource; + if (m_native_memories.size() < 1024) { + auto& nvmap = m_nv_module->GetContainer().GetNvMapFile(); + std::shared_ptr<Nvidia::NvCore::NvMap::Handle> handle; + if (nvmap.CreateHandle(size, handle) == Nvidia::NvResult::Success) { + id = handle->id; + m_native_memories.push_back(id); + result = ResultSuccess; + } + } + } + } + } + LOG_DEBUG(Service_AM, "native memory create size={:#x}, id={}, result={:#x}", size, id, result.raw); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push(id); +} + +Result IGraphicsSupervisor::BindNativeMemoryBacking(u32 id, u64 address, u64 size, u64 flags, u64 offset) { + R_UNLESS(m_nv_module && std::ranges::find(m_native_memories, id) != m_native_memories.end(), + Kernel::ResultNotFound); + R_UNLESS((flags == 0 || flags == 2) && offset == 0, Kernel::ResultNotImplemented); + auto handle = m_nv_module->GetContainer().GetNvMapFile().GetHandle(id); + R_UNLESS(handle && !handle->allocated, Kernel::ResultInvalidState); + R_UNLESS(size && size == handle->size && address != 0 && address + size > address, + Kernel::ResultInvalidSize); + R_UNLESS(Common::IsAligned(address, Kernel::PageSize), Kernel::ResultInvalidAddress); + auto& page_table = m_process->GetPageTable(); + R_UNLESS(page_table.Contains(address, size), Kernel::ResultInvalidCurrentMemory); + if (flags == 2) { + for (u64 current = address; current < address + size;) { + Kernel::KMemoryInfo info{}; + Kernel::Svc::PageInfo page{}; + R_TRY(page_table.QueryInfo(&info, &page, current)); + R_UNLESS(True(info.m_attribute & Kernel::KMemoryAttribute::GpuAccessible), + Kernel::ResultInvalidCurrentMemory); + const u64 next = info.m_address + info.m_size; + R_UNLESS(next > current, Kernel::ResultInvalidCurrentMemory); + current = next; + } + } + bool is_io{}; + R_TRY(page_table.LockForMapDeviceAddressSpace(&is_io, address, size, + Kernel::KMemoryPermission::None, true, true)); + if (is_io) { + page_table.UnlockForDeviceAddressSpace(address, size); + R_RETURN(Kernel::ResultNotImplemented); + } + if (!m_native_memory_session) { + m_native_memory_session = m_nv_module->GetContainer().OpenSession(m_process).id; + } + const auto result = handle->Alloc({}, Kernel::PageSize, 0, address, + Nvidia::NvCore::SessionId{*m_native_memory_session}); + if (result != Nvidia::NvResult::Success) { + page_table.UnlockForDeviceAddressSpace(address, size); + R_RETURN(Kernel::ResultOutOfResource); + } + R_SUCCEED(); +} + +void IGraphicsSupervisor::BindNativeMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const auto pair = [&](size_t i) { return u64{packet[i]} | (u64{packet[i + 1]} << 32); }; + const auto result = packet[0] == 0x82 && packet[1] == 9 + ? BindNativeMemoryBacking(packet[2], pair(3), pair(5), pair(7), pair(9)) + : Kernel::ResultInvalidCombination; + LOG_DEBUG(Service_AM, "native memory bind id={}, address={:#x}, size={:#x}, flags={:#x}, result={:#x}", + packet[2], pair(3), pair(5), pair(7), result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +Result IGraphicsSupervisor::ReleaseNativeMemory(u32 id) { + const auto it = std::ranges::find(m_native_memories, id); + R_UNLESS(m_nv_module && it != m_native_memories.end(), Kernel::ResultNotFound); + auto& nvmap = m_nv_module->GetContainer().GetNvMapFile(); + auto handle = nvmap.GetHandle(id); + R_UNLESS(handle, Kernel::ResultNotFound); + { + const std::scoped_lock lock{handle->mutex}; + R_UNLESS(handle->pins == 0 && handle->internal_dupes == 0 && handle->dupes == 1, + Kernel::ResultBusy); + } + const bool allocated = handle->allocated; + handle.reset(); + const auto info = nvmap.FreeHandle(id, false); + m_native_memories.erase(it); + if (allocated) { + R_UNLESS(info && info->can_unlock, Kernel::ResultInvalidState); + R_TRY(m_process->GetPageTable().UnlockForDeviceAddressSpace(info->address, info->size)); + } + R_SUCCEED(); +} + +void IGraphicsSupervisor::DestroyNativeMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const auto result = packet[0] == 0x80 && packet[1] == 1 + ? ReleaseNativeMemory(packet[2]) : Kernel::ResultInvalidCombination; + LOG_DEBUG(Service_AM, "native memory destroy id={}, result={:#x}", packet[2], result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::BindNativeGpuMemory(HLERequestContext& ctx) { + const auto* packet = ctx.CommandBuffer(); + const auto pair = [&](size_t i) { return u64{packet[i]} | (u64{packet[i + 1]} << 32); }; + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x1b7 && packet[1] == 12 && pair(10) == 0 && pair(12) == 0) { + result = Kernel::ResultNotFound; + if (m_nv_module) { + auto& container = m_nv_module->GetContainer(); + const auto parent = container.GetNvMapFile().GetHandle(packet[7]); + if (parent && parent->allocated) { + const auto* session = container.GetSession(parent->session_id); + const u64 offset = pair(8); + const u64 address = pair(3); + const u64 size = pair(5); + if (session && session->process == m_process && offset <= parent->size && + size <= parent->size - offset && parent->address + offset >= parent->address && + address == parent->address + offset) { + result = BindNativeMemoryBacking(packet[2], address, size, 2, 0); + } + } + } + } + LOG_DEBUG(Service_AM, "native GPU memory bind id={}, parent={}, address={:#x}, size={:#x}, offset={:#x}, result={:#x}", + packet[2], packet[7], pair(3), pair(5), pair(8), result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::CreateNativeImage(HLERequestContext& ctx) { + const std::scoped_lock images_lock{m_image_registry->mutex}; + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + u32 id{}; + if (packet[0] == 0x1bd && packet[1] == 18 && packet[3] == 1) { + result = Kernel::ResultInvalidState; + if (m_phase == Phase::Ready && m_gpu_context_ready && m_image_formats_initialized && m_nv_module) { + const auto requirements = CalculateNativeImageRequirements(std::span{packet + 2, 16}, packet[18]); + result = Kernel::ResultNotImplemented; + if (requirements) { + result = Kernel::ResultNotFound; + auto& nvmap = m_nv_module->GetContainer().GetNvMapFile(); + const auto memory = nvmap.GetHandle(packet[19]); + if (std::ranges::find(m_native_memories, packet[19]) != m_native_memories.end() && + memory && memory->allocated) { + result = Kernel::ResultInvalidSize; + if (memory->size >= requirements->size) { + result = Kernel::ResultOutOfResource; + if (m_image_registry->images.size() < 1024 && m_next_native_image_id != 0 && + memory->Duplicate(true) == Nvidia::NvResult::Success) { + NativeImage image{m_next_native_image_id++, packet[19], {}, requirements->size}; + std::copy_n(packet + 2, image.preference.size(), image.preference.begin()); + id = image.id; + if (!m_image_registry->module) { + m_image_registry->module = m_nv_module; + m_image_registry->memory_session = m_nv_module->GetContainer().OpenSession(m_process); + } + m_image_registry->images.push_back(image); + result = ResultSuccess; + } + } + } + } + } + } + LOG_DEBUG(Service_AM, "native image create memory={}, count={}, id={}, result={:#x}", + packet[19], packet[18], id, result.raw); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result.raw); + rb.Push(id); +} + +void IGraphicsSupervisor::DestroyNativeImage(HLERequestContext& ctx) { + const std::scoped_lock images_lock{m_image_registry->mutex}; + const auto* packet = ctx.CommandBuffer(); + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == 0x1be && packet[1] == 1) { + const auto image = std::ranges::find(m_image_registry->images, packet[2], &NativeImage::id); + result = Kernel::ResultNotFound; + if (m_nv_module && image != m_image_registry->images.end()) { + result = Kernel::ResultBusy; + if (!image->window_pool) { + m_image_registry->ReleaseMemory(image->memory_id); + m_image_registry->images.erase(image); + result = ResultSuccess; + } + } + } + LOG_DEBUG(Service_AM, "native image destroy id={}, result={:#x}", packet[2], result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +void IGraphicsSupervisor::ConfigureNativeWindowImagePool(HLERequestContext& ctx) { + const std::scoped_lock images_lock{m_image_registry->mutex}; + const auto* packet = ctx.CommandBuffer(); + const bool create = ctx.GetCommand() == 315; + Result result = Kernel::ResultInvalidCombination; + if (packet[0] == (create ? 0x14bU : 0x14aU) && packet[1] == (create ? 2U : 1U)) { + const auto image = std::ranges::find(m_image_registry->images, packet[2], &NativeImage::id); + result = Kernel::ResultNotFound; + if (image != m_image_registry->images.end()) { + result = Kernel::ResultNotImplemented; + if (!create || packet[3] == 1) { + result = Kernel::ResultInvalidState; + if (image->window_pool != create) { + image->window_pool = create; + result = ResultSuccess; + } + } + } + } + LOG_DEBUG(Service_AM, "native window image pool id={}, create={}, result={:#x}", + packet[2], create, result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.raw); +} + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_supervisor.h b/src/core/hle/service/am/service/graphics_supervisor.h new file mode 100644 index 0000000000..4163c2c150 --- /dev/null +++ b/src/core/hle/service/am/service/graphics_supervisor.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <vector> + +#include "core/hle/service/service.h" +#include "core/hle/service/am/service/graphics_syncpoint_cache.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/os/event.h" + +namespace Kernel { +class KProcess; +class KTransferMemory; +class KEvent; +} + +namespace Core::Timing { +struct EventType; +} +namespace Service::Nvidia { +class Module; +} + +namespace Service::AM { +struct NativeImageRegistry; + +class IGpuSession; + +class IGraphicsSupervisor final : public ServiceFramework<IGraphicsSupervisor> { +public: + explicit IGraphicsSupervisor(Core::System& system, Kernel::KProcess* process, u64 aruid); + ~IGraphicsSupervisor() override; + Result HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) override; + +private: + enum class Phase { Created, Initialized, ProcessBound, Ready }; + void Initialize(HLERequestContext& ctx); + void BindProcess(HLERequestContext& ctx); + void CompleteBootstrap(HLERequestContext& ctx); + void InitializeImageContext(HLERequestContext& ctx); + void GetNvidiaDriver(HLERequestContext& ctx); + void CalculateImageRequirements(HLERequestContext& ctx); + void CreateNativeMemory(HLERequestContext& ctx); + void CreateNativeImage(HLERequestContext& ctx); + void DestroyNativeImage(HLERequestContext& ctx); + void ConfigureNativeWindowImagePool(HLERequestContext& ctx); + void BindNativeMemory(HLERequestContext& ctx); + void BindNativeGpuMemory(HLERequestContext& ctx); + void DestroyNativeMemory(HLERequestContext& ctx); + Result BindNativeMemoryBacking(u32 id, u64 address, u64 size, u64 flags, u64 offset); + Result ReleaseNativeMemory(u32 id); + void ConfigureNativeMemory(HLERequestContext& ctx); + void InitializeFrameContext(HLERequestContext& ctx); + void RegisterGpuAddressSpace(HLERequestContext& ctx); + void OpenGpuSession(HLERequestContext& ctx); + void OpenDisplaySession(HLERequestContext& ctx); + void InitializeDisplayContext(HLERequestContext& ctx); + void InitializeGpuContext(HLERequestContext& ctx); + void InitializeSyncpointContext(HLERequestContext& ctx); + void GetSyncpointSession(HLERequestContext& ctx); + void BindSyncpointMemory(HLERequestContext& ctx); + void RefreshSyncpointCache(); + + std::vector<std::weak_ptr<IGpuSession>> m_gpu_sessions; + std::shared_ptr<Nvidia::Module> m_nv_module; + std::optional<size_t> m_native_memory_session; + std::vector<u32> m_native_memories; + std::shared_ptr<NativeImageRegistry> m_image_registry; + u32 m_next_native_image_id{1}; + std::shared_ptr<Core::Timing::EventType> m_cache_poll; + std::array<GraphicsSyncpointCache::CounterValue, GraphicsSyncpointCache::CounterCount> + m_counter_values{}; + Kernel::KTransferMemory* m_syncpoint_rw{}; + Kernel::KTransferMemory* m_syncpoint_ro{}; + Kernel::KEvent* m_syncpoint_request_event{}; + KernelHelpers::ServiceContext m_context; + Event m_syncpoint_cache_event; + bool m_syncpoint_context_initialized{}; + Kernel::KProcess* const m_process; + const u64 m_aruid; + Phase m_phase{Phase::Created}; + bool m_display_context_initialized{}; + bool m_gpu_context_initialized{}; + bool m_gpu_context_ready{}; + bool m_frame_context_initialized{}; + bool m_frame_context_ready{}; + bool m_image_context_initialized{}; + bool m_image_formats_initialized{}; +}; + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/graphics_supervisor_protocol.h b/src/core/hle/service/am/service/graphics_supervisor_protocol.h new file mode 100644 index 0000000000..7762ced531 --- /dev/null +++ b/src/core/hle/service/am/service/graphics_supervisor_protocol.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> +#include <optional> +#include <span> +#include "common/common_types.h" + +namespace Service::AM::GraphicsSupervisorProtocol { + +constexpr u8 DecodeBufferSlot(u32 packed) { + return static_cast<u8>(packed); +} + +struct BootstrapRequest { + u32 process_handle; + u64 applet_resource_user_id; + u64 flags; + std::array<u64, 4> parameters; +}; + +inline std::optional<BootstrapRequest> DecodeBootstrap(std::span<const u32> packet) { + // Captured remote-memory bootstrap: TIPC 4, one copied handle, 48 data bytes. + if (packet.size() < 16 || packet[0] != 0x14 || packet[1] != 0x8000000c || + packet[2] != 2) { + return std::nullopt; + } + const auto Read64 = [&](size_t index) { + return u64{packet[index]} | (u64{packet[index + 1]} << 32); + }; + return BootstrapRequest{packet[3], Read64(4), Read64(6), + {Read64(8), Read64(10), Read64(12), Read64(14)}}; +} + +} // namespace Service::AM::GraphicsSupervisorProtocol diff --git a/src/core/hle/service/am/service/graphics_syncpoint_cache.h b/src/core/hle/service/am/service/graphics_syncpoint_cache.h new file mode 100644 index 0000000000..23361b006c --- /dev/null +++ b/src/core/hle/service/am/service/graphics_syncpoint_cache.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> +#include <limits> +#include <optional> +#include "common/common_types.h" + +namespace Service::AM::GraphicsSyncpointCache { +constexpr u32 ValuesOffset = 0x40; +constexpr u32 GroupAckOffset = 0x640; +constexpr u32 SlotAckOffset = 0x648; +constexpr u32 GroupRequestOffset = 0; +constexpr u32 SlotRequestOffset = 8; +constexpr u32 CounterCount = 192; +constexpr u32 GroupCount = (CounterCount + 63) / 64; +constexpr u64 Unallocated = (std::numeric_limits<u64>::max)(); + +constexpr std::array<u32, 8> Header{ + 1, 0, CounterCount - 1, ValuesOffset, GroupRequestOffset, + GroupAckOffset, SlotRequestOffset, SlotAckOffset}; +static_assert(ValuesOffset + CounterCount * sizeof(u64) <= GroupAckOffset); +static_assert(SlotAckOffset + GroupCount * sizeof(u64) <= 0x2000); +static_assert(SlotRequestOffset + GroupCount * sizeof(u64) <= 0x1000); + +constexpr u64 ExtendCounter(u64 previous, u32 current) { + if (previous == Unallocated) { + return current; + } + u64 high = previous & 0xffffffff00000000ULL; + if (current < static_cast<u32>(previous)) { + high += 1ULL << 32; + } + return high | current; +} +struct CounterValue { + u64 previous{Unallocated}; + + u64 Observe(std::optional<u32> current) { + if (!current) { + return Unallocated; + } + previous = ExtendCounter(previous, *current); + return previous; + } +}; +} // namespace Service::AM::GraphicsSyncpointCache diff --git a/src/core/hle/service/am/service/native_image_layout.h b/src/core/hle/service/am/service/native_image_layout.h new file mode 100644 index 0000000000..eb45fc470d --- /dev/null +++ b/src/core/hle/service/am/service/native_image_layout.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <optional> +#include <span> +#include "common/common_types.h" + +namespace Service::AM { + +struct NativeImageRequirements { + u64 size; + u64 alignment; +}; + +// Experimental inter-module block image layout, restricted to the observed +// four-byte color format. Row pitch is in 64-byte GOBs; block height is in pixels. +inline std::optional<NativeImageRequirements> CalculateNativeImageRequirements( + std::span<const u32> preference, u32 count) { + if (preference.size() != 16 || count != 1 || preference[0] != 0 || + (preference[1] != 1 && preference[1] != 2) || preference[3] != 0 || preference[6] != 1 || + preference[7] != 1 || preference[4] < 3 || preference[4] > 8) { + return std::nullopt; + } + for (size_t i = 8; i < preference.size(); ++i) { + if (preference[i] != 0) { return std::nullopt; } + } + const u64 width = (preference[2] & 0xffffU) + 1; + const u64 height = (preference[2] >> 16) + 1; + const u64 pitch_gobs = preference[5]; + if (pitch_gobs < (width * 4 + 63) / 64 || pitch_gobs > 4096) { + return std::nullopt; + } + const u64 block_height = 1ULL << preference[4]; + const u64 aligned_height = (height + block_height - 1) & ~(block_height - 1); + return NativeImageRequirements{pitch_gobs * 64 * aligned_height, 64 * block_height}; +} + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/performance_functions.cpp b/src/core/hle/service/am/service/performance_functions.cpp new file mode 100644 index 0000000000..71a46cc5b5 --- /dev/null +++ b/src/core/hle/service/am/service/performance_functions.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/am/service/performance_functions.h" + +namespace Service::AM { + +IPerformanceFunctions::IPerformanceFunctions(Core::System& system) + : ServiceFramework{system, "IPerformanceFunctions"} { + static const FunctionInfo functions[] = { + {0, nullptr, "IsCpuOverclockEnabled"}, + {1, nullptr, "SetCpuOverclockEnabled"}, + }; + RegisterHandlers(functions); +} + +IPerformanceFunctions::~IPerformanceFunctions() = default; + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/performance_functions.h b/src/core/hle/service/am/service/performance_functions.h new file mode 100644 index 0000000000..12195d1217 --- /dev/null +++ b/src/core/hle/service/am/service/performance_functions.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::AM { + +class IPerformanceFunctions final : public ServiceFramework<IPerformanceFunctions> { +public: + explicit IPerformanceFunctions(Core::System& system); + ~IPerformanceFunctions() override; +}; + +} // namespace Service::AM diff --git a/src/core/hle/service/am/service/self_controller.cpp b/src/core/hle/service/am/service/self_controller.cpp index 3a654b9daa..45b9120093 100644 --- a/src/core/hle/service/am/service/self_controller.cpp +++ b/src/core/hle/service/am/service/self_controller.cpp @@ -5,10 +5,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/result.h" #include "core/hle/service/am/am_results.h" #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/service/self_controller.h" +#include "core/hle/service/am/service/graphics_supervisor.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/server_manager.h" #include "core/hle/service/caps/caps_su.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/nvnflinger/nvnflinger.h" @@ -73,6 +80,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet> {230, D<&ISelfController::Unknown230>, "Unknown230"}, {240, D<&ISelfController::Unknown240>, "Unknown240"}, {1000, nullptr, "GetDebugStorageChannel"}, + {2000, &ISelfController::GetGraphicsSupervisor, "GetGraphicsSupervisor"}, }; // clang-format on @@ -84,10 +92,47 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet> } ISelfController::~ISelfController() { + if (m_graphics_supervisor_session != nullptr) { + m_graphics_supervisor_session->Close(kernel); + } std::scoped_lock lk{m_applet->lock}; m_applet->display_layer_manager.Finalize(); } +void ISelfController::GetGraphicsSupervisor(HLERequestContext& ctx) { + if (m_graphics_supervisor_session != nullptr) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, m_graphics_supervisor_session); + return; + } + Kernel::KScopedResourceReservation reservation( + kernel, Kernel::GetCurrentProcessPointer(kernel), Kernel::LimitableResource::SessionCountMax); + if (!reservation.Succeeded()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultLimitReached); + return; + } + auto* session = Kernel::KSession::Create(kernel); + if (session == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultOutOfResource); + return; + } + session->Initialize(kernel, nullptr, 0); + reservation.Commit(); + Kernel::KSession::Register(kernel, session); + auto& server = ctx.GetManager()->GetServerManager(); + auto manager = std::make_shared<SessionRequestManager>(kernel, server); + manager->SetSessionHandler(std::make_shared<IGraphicsSupervisor>(system, m_process, m_applet->aruid.pid)); + server.RegisterSession(&session->GetServerSession(), std::move(manager)); + LOG_DEBUG(Service_AM, "created graphics-supervisor session"); + m_graphics_supervisor_session = &session->GetClientSession(); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, m_graphics_supervisor_session); +} + Result ISelfController::Exit() { LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/am/service/self_controller.h b/src/core/hle/service/am/service/self_controller.h index c9f480f575..52970ed4a8 100644 --- a/src/core/hle/service/am/service/self_controller.h +++ b/src/core/hle/service/am/service/self_controller.h @@ -12,6 +12,7 @@ namespace Kernel { class KReadableEvent; +class KClientSession; } namespace Service::Capture { @@ -30,6 +31,7 @@ public: ~ISelfController() override; private: + void GetGraphicsSupervisor(HLERequestContext& ctx); Result Exit(); Result LockExit(); Result UnlockExit(); @@ -70,6 +72,7 @@ private: Result Unknown230(u32 in_val, Out<u16> out_val); Result Unknown240(u32 in_val); + Kernel::KClientSession* m_graphics_supervisor_session{}; Kernel::KProcess* const m_process; const std::shared_ptr<Applet> m_applet; }; diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index fde766a275..63d578462f 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -5,6 +5,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/audio/audio.h" #include "core/hle/service/audio/audio_controller.h" #include "core/hle/service/audio/audio_in_manager.h" @@ -18,6 +20,57 @@ namespace Service::Audio { +class INativeAudioManager final : public ServiceFramework<INativeAudioManager> { +public: + INativeAudioManager(Core::System& system_, std::shared_ptr<IAudioInManager> input, + std::shared_ptr<IAudioOutManager> output, + std::shared_ptr<IAudioRendererManager> renderer) + : ServiceFramework{system_, "aud:u"}, m_input{std::move(input)}, + m_output{std::move(output)}, m_renderer{std::move(renderer)} { + static const FunctionInfo functions[] = { + {0, &INativeAudioManager::GetAudioInManager, "GetAudioInManager"}, + {1, &INativeAudioManager::GetAudioOutManager, "GetAudioOutManager"}, + {2, &INativeAudioManager::GetAudioRendererManager, "GetAudioRendererManager"}, + }; + RegisterHandlers(functions); + } + + Result HandleSyncRequest(Kernel::KServerSession& session, HLERequestContext& ctx) override { + if (!ctx.IsTipc()) { + return ServiceFrameworkBase::HandleSyncRequest(session, ctx); + } + const auto guard = LockService(); + LOG_WARNING(Service_Audio, "Native audio unsupported command={}, words={:08x}", ctx.GetCommand(), + fmt::join(std::span<const u32>(ctx.CommandBuffer() + 2, + std::min<u32>(ctx.CommandBuffer()[1], 32)), ",")); + InvokeRequestTipc(ctx); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultNotImplemented.raw); + return system.IsPoweredOn() ? ctx.WriteToOutgoingCommandBuffer() : ResultSuccess; + } + +private: + void GetAudioInManager(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, m_input); + } + void GetAudioOutManager(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, m_output); + } + void GetAudioRendererManager(HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "Native user client opens existing audio renderer manager"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, m_renderer); + } + std::shared_ptr<IAudioInManager> m_input; + std::shared_ptr<IAudioOutManager> m_output; + std::shared_ptr<IAudioRendererManager> m_renderer; +}; + class IAudioOutManagerForApplet final : public ServiceFramework<IAudioOutManagerForApplet> { public: explicit IAudioOutManagerForApplet(Core::System& system_) @@ -193,18 +246,23 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService("audrec:d", std::make_shared<IFinalOutputRecorderManagerForDebugger>(system), 30); server_manager->RegisterNamedService("audren:d", std::make_shared<IAudioRendererManagerForDebugger>(system), 30); - server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system), 30); + auto audio_input = std::make_shared<IAudioInManager>(system); + server_manager->RegisterNamedService("audin:u", audio_input, 30); server_manager->RegisterNamedService("audin:a", std::make_shared<IAudioInManagerForApplet>(system), 30); - server_manager->RegisterNamedService("audout:u", std::make_shared<IAudioOutManager>(system), 30); + auto audio_output = std::make_shared<IAudioOutManager>(system); + server_manager->RegisterNamedService("audout:u", audio_output, 30); server_manager->RegisterNamedService("audout:a", std::make_shared<IAudioOutManagerForApplet>(system), 30); server_manager->RegisterNamedService("auddev", std::make_shared<IAudioSnoopManager>(system), 30); // Depends on audout:u and audin:u on ctor! server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system), 30); server_manager->RegisterNamedService("audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system), 30); server_manager->RegisterNamedService("audrec:u", std::make_shared<IFinalOutputRecorderManager>(system), 30); - server_manager->RegisterNamedService("audren:u", std::make_shared<IAudioRendererManager>(system), 30); + auto audio_renderer = std::make_shared<IAudioRendererManager>(system); + server_manager->RegisterNamedService("audren:u", audio_renderer, 30); server_manager->RegisterNamedService("audren:a", std::make_shared<IAudioRendererManagerForApplet>(system), 30); server_manager->RegisterNamedService("hwopus", std::make_shared<IHardwareOpusDecoderManager>(system), 25); + server_manager->RegisterNamedService("aud:u", + std::make_shared<INativeAudioManager>(system, audio_input, audio_output, audio_renderer), 30); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 9689bce8dd..7ddb944abe 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -132,6 +132,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {210, C<&IHidServer::EndPermitVibrationSession>, "EndPermitVibrationSession"}, {211, C<&IHidServer::IsVibrationDeviceMounted>, "IsVibrationDeviceMounted"}, {212, C<&IHidServer::SendVibrationValueInBool>, "SendVibrationValueInBool"}, + {214, C<&IHidServer::SendNativeVibrationValues>, "SendNativeVibrationValues"}, {300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"}, {301, C<&IHidServer::StartConsoleSixAxisSensor>, "StartConsoleSixAxisSensor"}, {302, C<&IHidServer::StopConsoleSixAxisSensor>, "StopConsoleSixAxisSensor"}, @@ -237,6 +238,11 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {3013, nullptr, "SetDebugPadGenericPadMap"}, //21.0.0+ {3014, nullptr, "GetDebugPadKeyboardMap"}, //21.0.0+ {3015, nullptr, "SetDebugPadKeyboardMap"}, //21.0.0+ + {3110, C<&IHidServer::RegisterJcmsMemory>, "RegisterJcmsMemory"}, + {3136, C<&IHidServer::SetNmouseButtonConfigJoyLeftJcms>, "SetNmouseButtonConfigJoyLeftJcms"}, + {3137, C<&IHidServer::SetNmouseButtonConfigJoyRightJcms>, "SetNmouseButtonConfigJoyRightJcms"}, + {3138, C<&IHidServer::SetMergedNmouseSourceTypes>, "SetMergedNmouseSourceTypes"}, + {3139, C<&IHidServer::RegisterNativeInputMemory>, "RegisterNativeInputMemory"}, {3150, C<&IHidServer::SetMouseLibraryVersion>, "SetMouseLibraryVersion"}, //21.0.0+ // What? -- {12010, nullptr, "SetButtonConfigLeft"}, }; @@ -245,7 +251,11 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r RegisterHandlers(functions); } -IHidServer::~IHidServer() = default; +IHidServer::~IHidServer() { + for (const auto& registration : native_input_memories) { + registration.memory->Close(kernel); + } +} Result IHidServer::CreateAppletResource(OutInterface<IAppletResource> out_applet_resource, ClientAppletResourceUserId aruid) { @@ -984,6 +994,17 @@ Result IHidServer::SendVibrationValues( R_SUCCEED(); } +Result IHidServer::SendNativeVibrationValues( + u16 flags, AppletResourceUserId aruid, + InArray<Core::HID::VibrationDeviceHandle, BufferAttr_HipcPointer> vibration_handles, + InArray<Core::HID::VibrationValue, BufferAttr_HipcPointer> vibration_values) { + static u32 diagnostic_batches{}; + if (diagnostic_batches++ < 8) LOG_INFO(Service_HID, "Native vibration batch flags={:#x}, aruid={}, handles={}, values={}", + flags, aruid.pid, vibration_handles.size(), vibration_values.size()); + R_UNLESS(flags == 0 || flags == 2, IPC::ResultNotSupported); + R_RETURN(SendVibrationValues(ClientAppletResourceUserId{aruid.pid}, vibration_handles, vibration_values)); +} + Result IHidServer::SendVibrationGcErmCommand( Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid, Core::HID::VibrationGcErmCommand gc_erm_command) { @@ -1471,6 +1492,63 @@ Result IHidServer::SetTouchScreenResolution(u32 width, u32 height, R_SUCCEED(); } +Result IHidServer::RegisterNativeInputMemory( + u8 mode, u32 parameter0, u32 parameter1, u32 parameter2, + ClientAppletResourceUserId aruid, u64 size, + InCopyHandle<Kernel::KTransferMemory> memory) { + LOG_INFO(Service_HID, "Native input memory mode={}, parameters={:#x}/{:#x}/{:#x}, aruid={}, size={:#x}", + mode, parameter0, parameter1, parameter2, aruid.pid, size); + R_UNLESS(memory.Get() && memory->GetOwner()->UsesPAuthReturns() && + memory->GetOwner()->GetProcessId() == aruid.pid, Kernel::ResultInvalidHandle); + R_UNLESS(mode <= 1 && size && size == memory->GetSize() && size <= 0x1000000, + Kernel::ResultInvalidSize); + R_UNLESS(native_input_memories.size() < 64, Kernel::ResultOutOfResource); + const auto existing = std::ranges::find(native_input_memories, memory.Get(), &NativeInputMemory::memory); + R_UNLESS(existing == native_input_memories.end(), Kernel::ResultInvalidState); + memory->Open(kernel); + native_input_memories.push_back({memory.Get(), aruid.pid, size, {mode, parameter0, parameter1, parameter2}}); + // Retain the real guest-owned input buffer. No Joy-Con mouse samples are + // produced while those devices are disconnected. + R_SUCCEED(); +} + +Result IHidServer::RegisterJcmsMemory(ClientAppletResourceUserId aruid, u64 size, + InCopyHandle<Kernel::KTransferMemory> memory) { + LOG_INFO(Service_HID, "Native Joy-Con mouse memory: aruid={}, size={:#x}", aruid.pid, size); + return RegisterNativeInputMemory(1, 0, 0, 0, aruid, size, memory); +} + +Result IHidServer::SetMergedNmouseSourceTypes(u32 source_types, ClientAppletResourceUserId aruid) { + LOG_INFO(Service_HID, "Native merged mouse sources: types={:#x}, aruid={}", source_types, aruid.pid); + R_UNLESS((source_types & ~6U) == 0, Kernel::ResultInvalidCombination); + const auto memory = std::ranges::find_if(native_input_memories, [&](const auto& registration) { + return registration.aruid == aruid.pid && registration.parameters[0] == 0; + }); + R_UNLESS(memory != native_input_memories.end(), Kernel::ResultNotFound); + memory->merged_source_types = source_types; + R_SUCCEED(); +} + +Result IHidServer::SetNmouseButtonConfiguration(ClientAppletResourceUserId aruid, u64 configuration, + size_t side) { + LOG_INFO(Service_HID, "Native Joy-Con mouse buttons: side={}, config={:#x}, aruid={}", + side, configuration, aruid.pid); + const auto memory = std::ranges::find_if(native_input_memories, [&](const auto& registration) { + return registration.aruid == aruid.pid && registration.parameters[0] == 0; + }); + R_UNLESS(memory != native_input_memories.end(), Kernel::ResultNotFound); + memory->jcms_button_configurations[side] = configuration; + R_SUCCEED(); +} + +Result IHidServer::SetNmouseButtonConfigJoyLeftJcms(ClientAppletResourceUserId aruid, u64 configuration) { + return SetNmouseButtonConfiguration(aruid, configuration, 0); +} + +Result IHidServer::SetNmouseButtonConfigJoyRightJcms(ClientAppletResourceUserId aruid, u64 configuration) { + return SetNmouseButtonConfiguration(aruid, configuration, 1); +} + Result IHidServer::SetMouseLibraryVersion(ClientAppletResourceUserId aruid) { LOG_INFO(Service_HID, "(STUBBED) called, applet_resource_user_id={}", aruid.pid); diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 57a1a04b9c..45097f361d 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -6,6 +6,8 @@ #pragma once +#include <array> +#include <vector> #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" #include "hid_core/hid_types.h" @@ -175,6 +177,10 @@ private: ClientAppletResourceUserId aruid, InArray<Core::HID::VibrationDeviceHandle, BufferAttr_HipcPointer> vibration_handles, InArray<Core::HID::VibrationValue, BufferAttr_HipcPointer> vibration_values); + Result SendNativeVibrationValues( + u16 flags, AppletResourceUserId aruid, + InArray<Core::HID::VibrationDeviceHandle, BufferAttr_HipcPointer> vibration_handles, + InArray<Core::HID::VibrationValue, BufferAttr_HipcPointer> vibration_values); Result SendVibrationGcErmCommand(Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid, Core::HID::VibrationGcErmCommand gc_erm_command); @@ -265,6 +271,24 @@ private: Result SetTouchScreenResolution(u32 width, u32 height, ClientAppletResourceUserId aruid); Result SetMouseLibraryVersion(ClientAppletResourceUserId aruid); + Result RegisterNativeInputMemory(u8 mode, u32 parameter0, u32 parameter1, u32 parameter2, + ClientAppletResourceUserId aruid, u64 size, + InCopyHandle<Kernel::KTransferMemory> memory); + Result RegisterJcmsMemory(ClientAppletResourceUserId aruid, u64 size, + InCopyHandle<Kernel::KTransferMemory> memory); + Result SetMergedNmouseSourceTypes(u32 source_types, ClientAppletResourceUserId aruid); + Result SetNmouseButtonConfigJoyLeftJcms(ClientAppletResourceUserId aruid, u64 configuration); + Result SetNmouseButtonConfigJoyRightJcms(ClientAppletResourceUserId aruid, u64 configuration); + Result SetNmouseButtonConfiguration(ClientAppletResourceUserId aruid, u64 configuration, size_t side); + struct NativeInputMemory { + Kernel::KTransferMemory* memory; + u64 aruid; + u64 size; + std::array<u32, 4> parameters; + u32 merged_source_types{}; + std::array<u64, 2> jcms_button_configurations{}; + }; + std::vector<NativeInputMemory> native_input_memories; std::shared_ptr<ResourceManager> resource_manager; std::shared_ptr<HidFirmwareSettings> firmware_settings; }; diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp index e79108a1eb..2c79336d93 100644 --- a/src/core/hle/service/ngc/ngc.cpp +++ b/src/core/hle/service/ngc/ngc.cpp @@ -7,6 +7,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/cmif_types.h" @@ -182,6 +183,8 @@ public: explicit IUserShimScopedObject(Core::System& system_) : ServiceFramework(system_, "IUserShimScopedObject") { // clang-format off static const FunctionInfo functions[] = { + {1, D<&IUserShimScopedObject::GetRequiredMemorySize>, "GetRequiredMemorySize"}, + {300, D<&IUserShimScopedObject::GetGuestHidSharedMemory>, "GetGuestHidSharedMemory"}, {450, nullptr, "InitializeForSaveData"}, {451, nullptr, "FinalizeForSaveData"}, {452, D<&IUserShimScopedObject::OpenSaveData>, "OpenSaveData"}, @@ -195,6 +198,50 @@ public: RegisterHandlers(functions); } + ~IUserShimScopedObject() override { + if (m_guest_hid_memory) { + m_guest_hid_memory->Close(kernel); + } + } + + struct GuestHidMemoryConfig { + u64 size; + u64 offset; + }; + + Result GetRequiredMemorySize(Out<u32> out_size, u8 type, u8 mode, s32 guest_count) { + LOG_INFO(Service_NGC, "GameShare work memory query type={}, mode={}, guests={}", type, mode, guest_count); + R_UNLESS(type <= 1 && mode == 1 && guest_count >= 0 && guest_count <= 3, + IPC::ResultNotSupported); + // The disconnected implementation reserves one page for its session + // control state. Streaming/encoding work is not implemented. + *out_size = 0x1000; + R_SUCCEED(); + } + + Result GetGuestHidSharedMemory(Out<GuestHidMemoryConfig> out_config, + OutCopyHandle<Kernel::KSharedMemory> out_memory) { + // Three guest pad blocks, touch rings, and JCMS rings. Empty rings and + // zero style sets describe no connected GameShare guests. + constexpr u64 GuestHidMemorySize = 0x84000; + if (!m_guest_hid_memory) { + auto* memory = Kernel::KSharedMemory::Create(kernel); + const auto result = memory->Initialize(kernel, system.DeviceMemory(), nullptr, + Kernel::Svc::MemoryPermission::ReadWrite, Kernel::Svc::MemoryPermission::Read, + GuestHidMemorySize); + if (R_FAILED(result)) { + memory->Close(kernel); + R_RETURN(result); + } + Kernel::KSharedMemory::Register(kernel, memory); + m_guest_hid_memory = memory; + } + *out_config = GuestHidMemoryConfig{GuestHidMemorySize, 0}; + *out_memory = m_guest_hid_memory; + LOG_DEBUG(Service_NGC, "GameShare guest HID memory size={:#x}, no guests connected", GuestHidMemorySize); + R_SUCCEED(); + } + Result OpenSaveData(Account::Uid unk0, Out<SaveDataHandle> unk1) { LOG_WARNING(Service_NGC, "stubbed"); R_THROW(IPC::ResultNotSupported); @@ -210,6 +257,9 @@ public: // to implement R_SUCCEED(); } + +private: + Kernel::KSharedMemory* m_guest_hid_memory{}; }; class IUserService final : public ServiceFramework<IUserService> { diff --git a/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp index c4c4c25932..8b929a7ef3 100644 --- a/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp +++ b/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp @@ -63,6 +63,14 @@ void SyncpointManager::FreeSyncpoint(u32 id) { syncpoint.reserved = false; } +std::optional<u32> SyncpointManager::ReadAllocatedSyncpoint(u32 id) { + std::lock_guard lock(reservation_lock); + if (id >= SyncpointCount || !syncpoints[id].reserved) { + return std::nullopt; + } + return UpdateMin(id); +} + bool SyncpointManager::IsSyncpointAllocated(u32 id) const { return (id < SyncpointCount) && syncpoints[id].reserved; } diff --git a/src/core/hle/service/nvdrv/core/syncpoint_manager.h b/src/core/hle/service/nvdrv/core/syncpoint_manager.h index 7728ff5967..42e1d547b8 100644 --- a/src/core/hle/service/nvdrv/core/syncpoint_manager.h +++ b/src/core/hle/service/nvdrv/core/syncpoint_manager.h @@ -7,6 +7,7 @@ #include <array> #include <atomic> #include <mutex> +#include <optional> #include "common/common_types.h" #include "core/hle/service/nvdrv/nvdata.h" @@ -38,6 +39,8 @@ enum class ChannelType : u32 { */ class SyncpointManager final { public: + std::optional<u32> ReadAllocatedSyncpoint(u32 id); + explicit SyncpointManager(Tegra::Host1x::Host1x& host1x); ~SyncpointManager(); diff --git a/src/core/hle/service/nvdrv/devices/gpu_address_space_layout.h b/src/core/hle/service/nvdrv/devices/gpu_address_space_layout.h new file mode 100644 index 0000000000..a54ebb18d7 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/gpu_address_space_layout.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Service::Nvidia::Devices { + +struct GpuAddressSpaceLayout { + u64 start; + u64 split; + u64 end; +}; + +constexpr GpuAddressSpaceLayout GetGpuAddressSpaceLayout(bool extended) { + // Leave room for other 4 KiB allocations beyond the native bootstrap + // reservation of 0x7ff8000000 bytes. + // Both layouts fit within the GPU's reported 40-bit virtual address width. + return {0x8000000, extended ? 3ULL << 38 : 1ULL << 34, + extended ? 1ULL << 40 : 1ULL << 37}; +} + +constexpr u32 GetGpuAddressSpaceBigPageSize(bool extended, u32 requested) { + // The native SDK passes its preferred allocation size here. A preference for + // ordinary 4 KiB mappings still needs a separate large-page pool in NVDRV. + return extended && requested == 0x1000 ? 0x10000 : requested; +} + +constexpr u64 GpuPageAddress(u32 page, u32 page_bits) { + return static_cast<u64>(page) << page_bits; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/native_gpu_characteristics.h b/src/core/hle/service/nvdrv/devices/native_gpu_characteristics.h new file mode 100644 index 0000000000..44f45cab75 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/native_gpu_characteristics.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> +#include <cstring> +#include <span> +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::Nvidia::Devices { + +inline constexpr size_t NativeGpuCharacteristicsSize = 0xd0; + +inline std::array<u8, NativeGpuCharacteristicsSize> MakeNativeGpuCharacteristics( + std::span<const u8, 0xa0> legacy) { + std::array<u8, NativeGpuCharacteristicsSize> result{}; + // The GPU description and engine classes share the legacy prefix. The native + // SDK consumes signed ioctl interface levels where the legacy ABI has topology. + std::memcpy(result.data(), legacy.data(), 0x70); + // Select the SDK's native control ABI, whose normal mappings support + // 4 KiB CPU/GPU aliases. Engine execution remains a separate implementation. + const u32_le native_arch{0x170}; + const u32_le native_impl{0xe}; + std::memcpy(result.data(), &native_arch, sizeof(native_arch)); + std::memcpy(result.data() + 4, &native_impl, sizeof(native_impl)); + // Native NVN's built-in shader table accepts Ampere SM 8.x. Carrying the + // inherited Maxwell SM 5.3 value here rejects its initialization shaders. + const u32_le native_sm_version{0x807}; + std::memcpy(result.data() + 0x3c, &native_sm_version, sizeof(native_sm_version)); + std::memcpy(result.data() + 0x40, &native_sm_version, sizeof(native_sm_version)); + // Native NVN maps CPU buffers into its shared VA reservation. Those buffers + // are 4 KiB aligned, so large pages must remain optional for allocation. + const u32_le preferred_page_size{0x1000}; + std::memcpy(result.data() + 0x28, &preferred_page_size, sizeof(preferred_page_size)); + u32_le available_page_sizes; + std::memcpy(&available_page_sizes, legacy.data() + 0x34, sizeof(available_page_sizes)); + available_page_sizes = (static_cast<u32>(available_page_sizes) & 0x10000) | 0x1000; + std::memcpy(result.data() + 0x34, &available_page_sizes, sizeof(available_page_sizes)); + u64_le flags; + std::memcpy(&flags, legacy.data() + 0x50, sizeof(flags)); + // Native SDK class enumeration filters 2D/3D/compute/inline families + // with bits 40..43 before selecting context identities from its profile. + // The native SDK gates ModifyKind (AS ioctl 12) on capability bit 34. + flags = static_cast<u64>(flags) | (1ULL << 8) | (1ULL << 22) | (1ULL << 26) | (1ULL << 34) | (0xfULL << 40); + std::memcpy(result.data() + 0x50, &flags, sizeof(flags)); + constexpr std::array<s16, 9> levels{ + 0x1c, // GPU control: GetGpuTime + 11, // TSG: bind/unbind channel with native subcontext IDs + -1, // GPU debugger device is unavailable + 0x1d, // Channel: SetTimeslice + 0x14, // Address space: Remap + 0, 0, 0, 0, // Additional interfaces have not been implemented. + }; + for (size_t i = 0; i < levels.size(); ++i) { + const u16_le value{static_cast<u16>(levels[i])}; + std::memcpy(result.data() + 0x70 + i * sizeof(value), &value, sizeof(value)); + } + // The native SDK allocates shared queue subcontexts locally, then binds + // each channel with TSG ioctl 11. Each emulated channel retains its own state. + const u32_le max_subcontexts{64}; + std::memcpy(result.data() + 0xb4, &max_subcontexts, sizeof(max_subcontexts)); + return result; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/native_gpu_context.h b/src/core/hle/service/nvdrv/devices/native_gpu_context.h new file mode 100644 index 0000000000..7c7131f211 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/native_gpu_context.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <optional> +#include "common/common_types.h" + +namespace Service::Nvidia::Devices { + +// Native profile context identities. This classifies allocation metadata; +// engine method execution is handled separately by the GPU frontend. +inline std::optional<u32> GetNativeGpuContextSlot(u32 identity) { + switch (identity) { + case 0xc797: return 1; + case 0xc7c0: return 2; + case 0xc7b5: return 4; + case 0xc56f: return 5; + default: return std::nullopt; + } +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/native_gpu_kind.h b/src/core/hle/service/nvdrv/devices/native_gpu_kind.h new file mode 100644 index 0000000000..ecf3781162 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/native_gpu_kind.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <optional> +#include "common/common_types.h" +#include "video_core/pte_kind.h" + +namespace Service::Nvidia::Devices { + +inline std::optional<u8> TranslateNativeUncompressedKind(u8 kind, u8 fallback) { + // NVIDIA's Turing MMU uses 6 for GENERIC_MEMORY and 7 for INVALID: + // open-gpu-doc/manuals/turing/tu104/dev_mmu.ref.txt. Eden's older kind + // numbering uses 0xfe for generic memory. Equal kinds need no compression. + if (kind != fallback) { return std::nullopt; } + switch (kind) { + case 0: return static_cast<u8>(Tegra::PTEKind::PITCH); + case 1: return static_cast<u8>(Tegra::PTEKind::Z16); + case 3: return static_cast<u8>(Tegra::PTEKind::S8Z24); + case 6: return static_cast<u8>(Tegra::PTEKind::GENERIC_16BX2); + default: return std::nullopt; + } +} + +struct NativeMemoryKind { + u8 backend; + u8 selected; +}; + +inline std::optional<NativeMemoryKind> SelectNativeMemoryKind(u8 requested, u8 fallback) { + if (const auto kind = TranslateNativeUncompressedKind(requested, fallback)) { + return NativeMemoryKind{*kind, requested}; + } + // The native caller supplies an uncompressed alternative explicitly. Return + // that selected native kind so the SDK invalidates its compressed preference. + if (requested == 8 && fallback == 6) { + return NativeMemoryKind{static_cast<u8>(Tegra::PTEKind::GENERIC_16BX2), 6}; + } + // TU104 kind 0x0c is the compressible S8Z24 variant; kind 3 is + // the caller's uncompressed S8Z24 alternative (dev_mmu.ref.txt). + if (requested == 0xb && fallback == 1) { + return NativeMemoryKind{static_cast<u8>(Tegra::PTEKind::Z16), 1}; + } + if (requested == 0xc && fallback == 3) { + return NativeMemoryKind{static_cast<u8>(Tegra::PTEKind::S8Z24), 3}; + } + return std::nullopt; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index d23ec63bae..cbcee2a938 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -27,7 +27,9 @@ namespace Service::Nvidia::Devices { /// implement the ioctl interface. class nvdevice { public: - explicit nvdevice(Core::System& system_) : system{system_} {} + enum class Type { Other, GpuChannel, GpuTsg, GpuAddressSpace }; + explicit nvdevice(Core::System& system_, Type type_ = Type::Other) : system{system_}, type{type_} {} + Type GetType() const { return type; } virtual ~nvdevice() = default; /** @@ -80,6 +82,8 @@ public: protected: Core::System& system; +private: + const Type type; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index b8a8979315..0db83215ca 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -5,17 +5,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <boost/container/small_vector.hpp> +#include <cstdlib> +#include <fstream> +#include <filesystem> +#include "core/frontend/framebuffer_layout.h" +#include "video_core/renderer_base.h" #include "common/adpf.h" #include "common/assert.h" #include "common/logging.h" #include "core/core.h" +#include "core/hle/kernel/k_process.h" #include "core/core_timing.h" #include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/nvmap.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/perf_stats.h" #include "video_core/gpu.h" +#include "video_core/host1x/host1x.h" namespace Service::Nvidia::Devices { @@ -67,6 +74,9 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers output_fences.reserve(sorted_layers.size()); for (auto& layer : sorted_layers) { + const auto handle = nvmap.GetHandle(layer.buffer_handle); + const auto* session = handle ? container.GetSession(handle->session_id) : nullptr; + const bool native_tiling = session && session->process->UsesPAuthReturns(); output_layers.emplace_back(Tegra::FramebufferConfig{ .address = nvmap.GetHandleAddress(layer.buffer_handle), .offset = layer.offset, @@ -77,6 +87,7 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers .transform_flags = layer.transform, .crop_rect = layer.crop_rect, .blending = ConvertBlending(layer.blending), + .native_tiling = native_tiling, .layer_stack_mask = layer.layer_stack_mask, }); @@ -87,6 +98,56 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers } } + const char* capture = std::getenv("EDEN_NATIVE_FRAME_CAPTURE"); + const bool sequence = std::getenv("EDEN_NATIVE_FRAME_SEQUENCE") != nullptr; + const bool on_demand = std::getenv("EDEN_NATIVE_FRAME_ON_DEMAND") != nullptr; + const bool requested = on_demand && !output_layers.empty() && + !diagnostic_frame_pending->load() && std::filesystem::exists("native-frame.request"); + if (requested) std::filesystem::remove("native-frame.request"); + const bool sample_frame = requested || (capture && std::string_view{capture} == "1" && !output_layers.empty() && + diagnostic_frame_count < (sequence ? 36000U : 3840U) && ++diagnostic_frame_count != 0 && + (sequence ? (diagnostic_frame_count == 1 || diagnostic_frame_count % 120 == 0) + : !diagnostic_visible_frame->load())); + if (sample_frame && !diagnostic_frame_pending->exchange(true)) { + for (const auto& fence : output_fences) { + LOG_INFO(Service_NVDRV, "Native composite waits for syncpoint={}/{}, current={}", fence.id, fence.value, + system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(fence.id)); + } + LOG_INFO(Service_NVDRV, "Native composite submitted: layers={}, address={:#x}", output_layers.size(), output_layers.front().address); + const auto layout = Layout::DefaultFrameLayout(1920, 1080); + auto pixels = std::make_shared<std::vector<u8>>(layout.width * layout.height * 4); + const bool first = diagnostic_frame_count == 1; + auto pending = diagnostic_frame_pending; + auto visible = diagnostic_visible_frame; + system.GPU().Renderer().RequestScreenshot(pixels->data(), [pixels, layout, first, pending, visible, sequence, on_demand](bool invert_y) { + std::array<u8, 18> header{}; + header[2] = 2; + header[12] = static_cast<u8>(layout.width & 255); + header[13] = static_cast<u8>(layout.width >> 8); + header[14] = static_cast<u8>(layout.height & 255); + header[15] = static_cast<u8>(layout.height >> 8); + header[16] = 32; + header[17] = invert_y ? 8 : 0x28; + bool nonblack{}; + for (size_t i = 0; i < pixels->size(); i += 4) { + if ((*pixels)[i] || (*pixels)[i + 1] || (*pixels)[i + 2]) { + nonblack = true; + break; + } + } + const auto save = [&](const char* path) { + std::ofstream out(path, std::ios::binary); + out.write(reinterpret_cast<const char*>(header.data()), header.size()); + out.write(reinterpret_cast<const char*>(pixels->data()), pixels->size()); + return out.good(); + }; + if (first) save("native-first-frame.tga"); + const bool saved = (first || nonblack || sequence || on_demand) ? save("native-latest-frame.tga") : true; + if (nonblack && !visible->load() && save("native-visible-frame.tga")) visible->store(true); + LOG_INFO(Service_NVDRV, "Captured composited frame: {}x{}, saved={}, nonblack={}", layout.width, layout.height, saved, nonblack); + pending->store(false); + }, layout); + } system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences)); Common::ADPF::ReportFrameInterval(); system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs()); diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index ff0bfb2ec9..f795144ebe 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -6,6 +6,7 @@ #pragma once +#include <atomic> #include <memory> #include <vector> #include "common/common_types.h" @@ -45,6 +46,9 @@ public: Kernel::KEvent* QueryEvent(u32 event_id) override; private: + u32 diagnostic_frame_count{}; + std::shared_ptr<std::atomic_bool> diagnostic_frame_pending{std::make_shared<std::atomic_bool>(false)}; + std::shared_ptr<std::atomic_bool> diagnostic_visible_frame{std::make_shared<std::atomic_bool>(false)}; NvCore::Container& container; NvCore::NvMap& nvmap; }; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 3c9e42f602..947ea6088f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -6,6 +6,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include <cstring> +#include <limits> #include <utility> #include "common/alignment.h" @@ -13,10 +14,13 @@ #include "common/common_types.h" #include "common/logging.h" #include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/service/nvdrv/devices/gpu_address_space_layout.h" #include "core/hle/service/nvdrv/core/container.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/native_gpu_kind.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "video_core/control/channel_state.h" @@ -27,10 +31,12 @@ namespace Service::Nvidia::Devices { nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Container& core) - : nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{}, + : nvdevice{system_, Type::GpuAddressSpace}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{}, gmmu{} {} -nvhost_as_gpu::~nvhost_as_gpu() = default; +nvhost_as_gpu::~nvhost_as_gpu() { + if (m_owner_process) { m_owner_process->Close(system.Kernel()); } +} NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output) { @@ -51,6 +57,10 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> i return WrapFixed(this, &nvhost_as_gpu::GetVARegions1, input, output); case 0x9: return WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output); + case 0xb: + return WrapFixed(this, &nvhost_as_gpu::GetSyncpointShimInfo, input, output); + case 0xc: + return WrapFixed(this, &nvhost_as_gpu::ModifyKind, input, output); case 0x14: return WrapVariable(this, &nvhost_as_gpu::Remap, input, output); default: @@ -90,11 +100,139 @@ 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) { + m_fd = fd; + if (const auto* session = container.GetSession(session_id)) { + m_owner_process = session->process; + m_owner_process->Open(system.Kernel()); + m_extended_abi = m_owner_process->UsesPAuthReturns(); + } +} void nvhost_as_gpu::OnClose(DeviceFD fd) {} +NvResult nvhost_as_gpu::CreateNativeSparseRegion(u64 address, u64 size) { + if (!m_extended_abi || !size || !Common::IsAligned(address, VM::YUZU_PAGESIZE) || + !Common::IsAligned(size, VM::YUZU_PAGESIZE) || (size >> VM::PAGE_SIZE_BITS) > 0xffffffffULL) { + return NvResult::BadParameter; + } + { + const std::scoped_lock lock{mutex}; + if (!vm.initialised || address < vm.va_range_start || address >= vm.va_range_split || + size > vm.va_range_split - address) { return NvResult::BadParameter; } + const auto next = allocation_map.lower_bound(address); + if ((next != allocation_map.end() && next->first < address + size) || + (next != allocation_map.begin() && std::prev(next)->first + std::prev(next)->second.size > address)) { + return NvResult::BadParameter; + } + } + IoctlAllocSpace reservation{}; + reservation.pages = static_cast<u32>(size >> VM::PAGE_SIZE_BITS); + reservation.page_size = VM::YUZU_PAGESIZE; + reservation.flags = MappingFlags::Fixed | MappingFlags::Sparse; + reservation.offset = address; + return AllocateSpace(reservation); +} + +NvResult nvhost_as_gpu::DestroyNativeSparseRegion(u64 address, u64 size) { + { + const std::scoped_lock lock{mutex}; + const auto it = allocation_map.find(address); + if (!m_extended_abi || it == allocation_map.end() || !it->second.sparse || it->second.size != size) { + return NvResult::BadParameter; + } + } + IoctlFreeSpace release{}; + release.offset = address; + release.pages = static_cast<u32>(size >> VM::PAGE_SIZE_BITS); + release.page_size = VM::YUZU_PAGESIZE; + return FreeSpace(release); +} + +NvResult nvhost_as_gpu::MapNative4K(u32 handle_id, u64 address, u64 buffer_offset, u64 size, u8 kind) { + if (!m_extended_abi || !size || !Common::IsAligned(address, VM::YUZU_PAGESIZE) || + !Common::IsAligned(size, VM::YUZU_PAGESIZE) || + !Common::IsAligned(buffer_offset, VM::YUZU_PAGESIZE)) { + return NvResult::BadParameter; + } + const auto handle = nvmap.GetHandle(handle_id); + if (!handle || buffer_offset > handle->orig_size || size > handle->orig_size - buffer_offset) { + return NvResult::BadParameter; + } + bool sparse_reservation = false; + { + const std::scoped_lock lock{mutex}; + if (!vm.initialised || address < vm.va_range_start || address >= vm.va_range_split || + size > vm.va_range_split - address) { + return NvResult::BadParameter; + } + const auto upper = allocation_map.upper_bound(address); + if (upper != allocation_map.begin()) { + const auto owner = std::prev(upper); + sparse_reservation = owner->second.sparse && address >= owner->first && + address - owner->first < owner->second.size && size <= owner->second.size - (address - owner->first); + } + const auto next = allocation_map.lower_bound(address); + if (!sparse_reservation && ((next != allocation_map.end() && next->first < address + size) || + (next != allocation_map.begin() && + std::prev(next)->first + std::prev(next)->second.size > address))) { + return NvResult::BadParameter; + } + } + IoctlAllocSpace reservation{}; + reservation.pages = static_cast<u32>(size >> VM::PAGE_SIZE_BITS); + reservation.page_size = VM::YUZU_PAGESIZE; + reservation.flags = MappingFlags::Fixed; + reservation.offset = address; + const auto reserved = sparse_reservation ? NvResult::Success : AllocateSpace(reservation); + if (reserved != NvResult::Success) { return reserved; } + IoctlMapBufferEx mapping{}; + mapping.flags = MappingFlags::Fixed; + mapping.kind = kind; + mapping.handle = handle_id; + mapping.page_size = VM::YUZU_PAGESIZE; + mapping.buffer_offset = static_cast<s64>(buffer_offset); + mapping.mapping_size = size; + mapping.offset = static_cast<s64>(address); + const auto result = MapBufferEx(mapping); + if (result != NvResult::Success && !sparse_reservation) { + nvmap.UnpinHandle(handle_id); + IoctlFreeSpace release{}; + release.offset = address; + release.pages = reservation.pages; + release.page_size = VM::YUZU_PAGESIZE; + FreeSpace(release); + } + return result; +} + +NvResult nvhost_as_gpu::UnmapNative4K(u64 address, u64 size) { + if (!m_extended_abi || !size || !Common::IsAligned(address, VM::YUZU_PAGESIZE) || + !Common::IsAligned(size, VM::YUZU_PAGESIZE)) { + return NvResult::BadParameter; + } + bool sparse_mapping = false; + { + const std::scoped_lock lock{mutex}; + const auto it = mapping_map.find(address); + sparse_mapping = it != mapping_map.end() && it->second.sparse_alloc && it->second.size == size; + } + if (sparse_mapping) { + IoctlUnmapBuffer unmap{}; + unmap.offset = address; + return UnmapBuffer(unmap); + } + IoctlFreeSpace release{}; + release.offset = address; + release.pages = static_cast<u32>(size >> VM::PAGE_SIZE_BITS); + release.page_size = VM::YUZU_PAGESIZE; + return FreeSpace(release); +} + + NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { - LOG_DEBUG(Service_NVDRV, "called, big_page_size={:#x}", params.big_page_size); + LOG_DEBUG(Service_NVDRV, "called, flags={:#x}, as_fd={}, big_page_size={:#x}, reserved={:#x}, VA={:#x}/{:#x}/{:#x}", + params.flags, params.as_fd, params.big_page_size, params.reserved, + params.va_range_start, params.va_range_split, params.va_range_end); std::scoped_lock lock(mutex); @@ -103,21 +241,43 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { return NvResult::InvalidState; } - if (params.big_page_size) { - if (!std::has_single_bit(params.big_page_size)) { - LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: {:#x}!", params.big_page_size); + std::shared_ptr<nvhost_as_gpu> shared_parent; + if (m_extended_abi && params.as_fd != 0) { + if (params.as_fd == m_fd || params.flags != 5 || params.big_page_size != 0x1000 || + params.reserved != 0 || params.va_range_split != 0) { return NvResult::BadParameter; } + const auto device = module.GetDevice<nvdevice>(params.as_fd); + if (!device || device->GetType() != Type::GpuAddressSpace) { return NvResult::BadParameter; } + shared_parent = std::static_pointer_cast<nvhost_as_gpu>(device); + if (!shared_parent->m_extended_abi || !shared_parent->vm.initialised || + shared_parent->m_owner_process != m_owner_process || + params.va_range_start < shared_parent->vm.va_range_start || + params.va_range_start >= params.va_range_end || + params.va_range_end > shared_parent->vm.va_range_split) { return NvResult::BadParameter; } + } + + const u32 big_page_size = GetGpuAddressSpaceBigPageSize(m_extended_abi, params.big_page_size); + if (big_page_size) { + if (!std::has_single_bit(big_page_size)) { + LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: {:#x}!", big_page_size); return NvResult::BadValue; } - if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) { - LOG_ERROR(Service_NVDRV, "Unsupported big page size: {:#x}!", params.big_page_size); + if ((big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) { + LOG_ERROR(Service_NVDRV, "Unsupported big page size: {:#x}!", big_page_size); return NvResult::BadValue; } - vm.big_page_size = params.big_page_size; - vm.big_page_size_bits = static_cast<u32>(std::countr_zero(params.big_page_size)); + vm.big_page_size = big_page_size; + vm.big_page_size_bits = static_cast<u32>(std::countr_zero(big_page_size)); + + vm.va_range_start = big_page_size << VM::VA_START_SHIFT; + } - vm.va_range_start = params.big_page_size << VM::VA_START_SHIFT; + if (m_extended_abi && !params.va_range_start) { + const auto layout = GetGpuAddressSpaceLayout(true); + vm.va_range_start = layout.start; + vm.va_range_split = layout.split; + vm.va_range_end = layout.end; } // If this is unspecified then default values should be used @@ -127,6 +287,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { vm.va_range_end = params.va_range_end; } + if (shared_parent) { + // Native flags 5 expose the parent's small-page region as a separate + // reservation view. GPU mappings must resolve through the same page table. + vm.va_range_split = params.va_range_end; + } + const u64 max_big_page_bits = Common::Log2Ceil(vm.va_range_end); const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)}; @@ -135,11 +301,17 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)}; const auto end_big_pages{ - static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)}; + static_cast<u32>(vm.va_range_end >> vm.big_page_size_bits)}; vm.big_page_allocator.emplace(start_big_pages, end_big_pages); - gmmu = std::make_unique<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split, vm.big_page_size_bits, VM::PAGE_SIZE_BITS); - system.GPU().InitAddressSpace(*gmmu); + if (shared_parent) { + m_shared_parent = std::move(shared_parent); + gmmu = m_shared_parent->gmmu; + LOG_DEBUG(Service_NVDRV, "native AS view fd={} shares page table with fd={}", m_fd, params.as_fd); + } else { + gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split, vm.big_page_size_bits, VM::PAGE_SIZE_BITS); + system.GPU().InitAddressSpace(*gmmu); + } vm.initialised = true; return NvResult::Success; @@ -159,7 +331,7 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) { return NvResult::BadValue; } - if (params.page_size != vm.big_page_size && + if (!m_extended_abi && params.page_size != vm.big_page_size && ((params.flags & MappingFlags::Sparse) != MappingFlags::None)) { UNIMPLEMENTED_MSG("Sparse small pages are not implemented!"); return NvResult::NotImplemented; @@ -184,7 +356,7 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) { u64 size{static_cast<u64>(params.pages) * params.page_size}; if ((params.flags & MappingFlags::Sparse) != MappingFlags::None) { - gmmu->MapSparse(params.offset, size); + gmmu->MapSparse(params.offset, size, params.page_size != VM::YUZU_PAGESIZE); } allocation_map[params.offset] = { @@ -217,6 +389,7 @@ bool nvhost_as_gpu::FreeMappingLocked(u64 offset) noexcept { gmmu->Unmap(offset, mapping.size); } mapping_map.erase(offset); + map_buffer_offsets.erase(static_cast<s64>(offset)); return true; } return false; @@ -251,6 +424,77 @@ NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) { return NvResult::BadValue; } +NvResult nvhost_as_gpu::GetNativeSyncpointShim(u64& address, u32& stride, u32& count) { + if (!m_extended_abi || !vm.initialised) { return NvResult::NotSupported; } + address = vm.va_range_split - VM::YUZU_PAGESIZE; + if (!m_syncpoint_shim_reserved) { + IoctlAllocSpace allocation{}; + allocation.pages = 1; + allocation.page_size = VM::YUZU_PAGESIZE; + allocation.flags = MappingFlags::Fixed; + allocation.offset = address; + const auto result = AllocateSpace(allocation); + if (result != NvResult::Success) { return result; } + m_syncpoint_shim_reserved = true; + gmmu->SetNativeSyncpointShim(address); + } + stride = sizeof(u32); + count = 192; + return NvResult::Success; +} + +NvResult nvhost_as_gpu::GetSyncpointShimInfo(IoctlSyncpointShimInfo& params) { + u64 address{}; + u32 stride{}, count{}; + const auto result = GetNativeSyncpointShim(address, stride, count); + params.address = address; + params.stride = stride; + params.count = count; + LOG_DEBUG(Service_NVDRV, "native syncpoint shim address={:#x}, stride={}, count={}, result={}", + address, stride, count, static_cast<u32>(result)); + return result; +} + +NvResult nvhost_as_gpu::ModifyNativeKind(u64 address, u64 size, u8 requested, u8 fallback) { + IoctlModifyKind params{}; + params.compressed_kind = requested; + params.uncompressed_kind = fallback; + params.mapping_base = address; + params.size = size; + return ModifyKind(params); +} + +NvResult nvhost_as_gpu::ModifyKind(IoctlModifyKind& params) { + const u32 requested = params.compressed_kind; + const u32 fallback = params.uncompressed_kind; + const u64 base = params.mapping_base; + const u64 offset = params.buffer_offset; + const u64 size = params.size; + LOG_DEBUG(Service_NVDRV, "native modify kind base={:#x}, offset={:#x}, size={:#x}, kinds={:#x}/{:#x}", + base, offset, size, requested, fallback); + if (!m_extended_abi || params.reserved != 0 || fallback > 255 || + (requested > 255 && requested != 0xffff) || !size || + offset > std::numeric_limits<u64>::max() - base || + !Common::IsAligned(base + offset, VM::YUZU_PAGESIZE) || + !Common::IsAligned(size, VM::YUZU_PAGESIZE)) { + return NvResult::BadParameter; + } + const auto kind = SelectNativeMemoryKind(static_cast<u8>(requested == 0xffff ? fallback : requested), + static_cast<u8>(fallback)); + if (!kind) { return NvResult::NotSupported; } + const std::scoped_lock lock{mutex}; + const u64 address = base + offset; + auto it = mapping_map.upper_bound(address); + if (!vm.initialised || it == mapping_map.begin()) { return NvResult::BadParameter; } + --it; + const auto& mapping = it->second; + const u64 delta = address - it->first; + if (delta >= mapping.size || size > mapping.size - delta) { return NvResult::BadParameter; } + gmmu->Map(address, mapping.ptr + delta, size, static_cast<Tegra::PTEKind>(kind->backend), + mapping.big_page); + return NvResult::Success; +} + NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) { LOG_DEBUG(Service_NVDRV, "called, num_entries={:#x}", entries.size()); @@ -414,7 +658,13 @@ NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) { NvResult nvhost_as_gpu::BindChannel(IoctlBindChannel& params) { LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); - auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd); + const auto device = module.GetDevice<nvdevice>(params.fd); + if (!device || device->GetType() != nvdevice::Type::GpuChannel) { return NvResult::BadParameter; } + const auto gpu_channel_device = std::static_pointer_cast<nvhost_gpu>(device); + if (!vm.initialised || !gmmu || (gpu_channel_device->channel_state->initialized && + gpu_channel_device->channel_state->memory_manager != gmmu.get())) { return NvResult::InvalidState; } + gpu_channel_device->channel_state->memory_manager_owner = + std::shared_ptr<Tegra::MemoryManager>(module.GetDevice<nvhost_as_gpu>(m_fd), gmmu.get()); gpu_channel_device->channel_state->memory_manager = gmmu.get(); return NvResult::Success; } @@ -424,13 +674,13 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) { params.regions = std::array<VaRegion, 2>{ VaRegion{ - .offset = vm.small_page_allocator->GetVAStart() << VM::PAGE_SIZE_BITS, + .offset = GpuPageAddress(vm.small_page_allocator->GetVAStart(), VM::PAGE_SIZE_BITS), .page_size = VM::YUZU_PAGESIZE, ._pad0_{}, .pages = vm.small_page_allocator->GetVALimit() - vm.small_page_allocator->GetVAStart(), }, VaRegion{ - .offset = vm.big_page_allocator->GetVAStart() << vm.big_page_size_bits, + .offset = GpuPageAddress(vm.big_page_allocator->GetVAStart(), vm.big_page_size_bits), .page_size = vm.big_page_size, ._pad0_{}, .pages = vm.big_page_allocator->GetVALimit() - vm.big_page_allocator->GetVAStart(), diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index b13b1c9efa..8c7306e42a 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -64,6 +64,13 @@ public: Kernel::KEvent* QueryEvent(u32 event_id) override; + NvResult CreateNativeSparseRegion(u64 address, u64 size); + NvResult DestroyNativeSparseRegion(u64 address, u64 size); + NvResult MapNative4K(u32 handle, u64 address, u64 buffer_offset, u64 size, u8 kind); + NvResult UnmapNative4K(u64 address, u64 size); + NvResult GetNativeSyncpointShim(u64& address, u32& stride, u32& count); + NvResult ModifyNativeKind(u64 address, u64 size, u8 requested, u8 fallback); + struct VaRegion { u64 offset; u32 page_size; @@ -145,6 +152,25 @@ private: static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2, "IoctlGetVaRegions is incorrect size"); + struct IoctlSyncpointShimInfo { + u64_le address; + u32_le stride; + u32_le count; + }; + static_assert(sizeof(IoctlSyncpointShimInfo) == 16); + NvResult GetSyncpointShimInfo(IoctlSyncpointShimInfo& params); + + struct IoctlModifyKind { + u16_le compressed_kind; + u16_le uncompressed_kind; + u32_le reserved; + u64_le buffer_offset; + u64_le size; + u64_le mapping_base; + }; + static_assert(sizeof(IoctlModifyKind) == 32); + NvResult ModifyKind(IoctlModifyKind& params); + NvResult AllocAsEx(IoctlAllocAsEx& params); NvResult AllocateSpace(IoctlAllocSpace& params); NvResult Remap(std::span<IoctlRemapEntry> params); @@ -196,6 +222,12 @@ private: std::map<u64, Allocation> allocation_map; std::mutex mutex; //!< Locks all AS operations + bool m_extended_abi{}; + bool m_syncpoint_shim_reserved{}; + DeviceFD m_fd{-1}; + Kernel::KProcess* m_owner_process{}; + std::shared_ptr<nvhost_as_gpu> m_shared_parent; + struct VM { static constexpr u32 YUZU_PAGESIZE{0x1000}; static constexpr u32 PAGE_SIZE_BITS{static_cast<u32>(std::countr_zero<u32>(YUZU_PAGESIZE))}; @@ -219,7 +251,7 @@ private: bool initialised{}; } vm; - std::unique_ptr<Tegra::MemoryManager> gmmu; + std::shared_ptr<Tegra::MemoryManager> gmmu; }; } // namespace Service::Nvidia::Devices 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 14cdd8fda4..6684352b00 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 <algorithm> #include <cstring> #include "common/assert.h" #include "common/logging.h" @@ -11,6 +12,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/native_gpu_characteristics.h" #include "core/hle/service/nvdrv/nvdrv.h" namespace Service::Nvidia::Devices { @@ -94,6 +96,25 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> case 'G': switch (command.cmd) { case 0x5: + if (command.raw == 0xc0e04705) { + constexpr size_t packet_size = 0x10 + NativeGpuCharacteristicsSize; + if (input.size() < packet_size || output.size() < packet_size || + inline_output.size() < NativeGpuCharacteristicsSize) { + return NvResult::BadParameter; + } + IoctlCharacteristics legacy{}; + GetCharacteristics3(legacy, {}); + const auto native = MakeNativeGpuCharacteristics( + std::span<const u8, 0xa0>{reinterpret_cast<const u8*>(&legacy.gc), 0xa0}); + std::fill(output.begin(), output.end(), u8{}); + std::fill(inline_output.begin(), inline_output.end(), u8{}); + std::memcpy(output.data(), input.data(), 0x10); + const u64_le size{NativeGpuCharacteristicsSize}; + std::memcpy(output.data(), &size, sizeof(size)); + std::memcpy(output.data() + 0x10, native.data(), native.size()); + std::memcpy(inline_output.data(), native.data(), native.size()); + return NvResult::Success; + } return WrapFixedInlOut(this, &nvhost_ctrl_gpu::GetCharacteristics3, input, output, inline_output); case 0x6: diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 1e571be4c9..6b9678e440 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <cstdlib> +#include <fstream> #include <cstring> #include "common/assert.h" #include "common/logging.h" @@ -14,11 +16,13 @@ #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/native_gpu_context.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/memory.h" #include "video_core/control/channel_state.h" #include "video_core/engines/puller.h" #include "video_core/gpu.h" +#include "video_core/memory_manager.h" #include "video_core/host1x/host1x.h" namespace Service::Nvidia::Devices { @@ -33,7 +37,7 @@ Tegra::CommandHeader BuildFenceAction(Tegra::Engines::Puller::FenceOperation op, nvhost_gpu::nvhost_gpu(Core::System& system_, EventInterface& events_interface_, NvCore::Container& core_) - : nvdevice{system_}, events_interface{events_interface_}, core{core_}, + : nvdevice{system_, Type::GpuChannel}, events_interface{events_interface_}, core{core_}, syncpoint_manager{core_.GetSyncpointManager()}, nvmap{core.GetNvMapFile()}, channel_state{system.GPU().AllocateChannel()} { channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false); @@ -128,6 +132,7 @@ NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> inpu void nvhost_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) { sessions[fd] = session_id; + channel_state->native_abi = UsesNativeAbi(); } void nvhost_gpu::OnClose(DeviceFD fd) { @@ -280,6 +285,9 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { s32_le ctx_class_number_index = GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num)); + if (const auto native_slot = GetNativeGpuContextSlot(params.class_num)) { + if (UsesNativeAbi()) { ctx_class_number_index = *native_slot; } + } if (ctx_class_number_index < 0) { LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#x}", params.class_num); @@ -311,13 +319,13 @@ static boost::container::small_vector<Tegra::CommandHeader, 512> BuildWaitComman } static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementCommandList( - NvFence fence) { + NvFence fence, u32 increment_count = 2) { boost::container::small_vector<Tegra::CommandHeader, 512> result{ Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1, Tegra::SubmissionMode::Increasing), {}}; - for (u32 count = 0; count < 2; ++count) { + for (u32 count = 0; count < increment_count; ++count) { result.push_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1, Tegra::SubmissionMode::Increasing)); result.push_back( @@ -328,12 +336,12 @@ static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementC } static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementWithWfiCommandList( - NvFence fence) { + NvFence fence, u32 increment_count = 2) { boost::container::small_vector<Tegra::CommandHeader, 512> result{ Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1, Tegra::SubmissionMode::Increasing), {}}; - auto increment_list{BuildIncrementCommandList(fence)}; + auto increment_list{BuildIncrementCommandList(fence, increment_count)}; result.insert(result.end(), increment_list.begin(), increment_list.end()); return result; } @@ -351,6 +359,41 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL system.GPU().InitChannel(*channel_state, channel_state->program_id); } + if (UsesNativeAbi() && native_submit_logs++ < 8) { + LOG_DEBUG(Service_NVDRV, "native submit channel={}, initialized={}, entries={}, flags={:#x}, fence={}:{}", + channel_state->bind_id, channel_state->initialized, entries.command_lists.size(), + params.flags.raw, params.fence.id, params.fence.value); + for (size_t i = 0; i < std::min<size_t>(entries.command_lists.size(), 4); ++i) { + const auto& entry = entries.command_lists[i]; + std::array<u32, 16> words{}; + const size_t bytes = std::min<size_t>(entry.size * sizeof(u32), sizeof(words)); + const bool mapped = channel_state->memory_manager && bytes && + channel_state->memory_manager->IsFullyMappedRange(entry.addr, bytes); + if (mapped) { channel_state->memory_manager->ReadBlockUnsafe(entry.addr, words.data(), bytes); } + if (mapped && entry.size > 1000 && std::getenv("EDEN_NATIVE_FRAME_CAPTURE")) { + std::vector<u32> contents(static_cast<size_t>(entry.size)); + channel_state->memory_manager->ReadBlockUnsafe(entry.addr, contents.data(), contents.size() * sizeof(u32)); + std::ofstream dump("native-init-channel-" + std::to_string(channel_state->bind_id) + "-" + std::to_string(i) + ".bin", std::ios::binary); + dump.write(reinterpret_cast<const char*>(contents.data()), contents.size() * sizeof(u32)); + } + LOG_DEBUG(Service_NVDRV, "native pushbuffer entry={} raw={:#x}, address={:#x}, words={}, mapped={}, data={:08x}", + i, entry.raw, static_cast<u64>(entry.addr), static_cast<u64>(entry.size), + mapped, fmt::join(words, ",")); + } + } + + if (UsesNativeAbi() && std::getenv("EDEN_NATIVE_FRAME_CAPTURE") && native_submit_logs < 100) { + for (size_t i = 0; i < entries.command_lists.size(); ++i) { + const auto& entry = entries.command_lists[i]; + const auto bytes = static_cast<size_t>(entry.size) * sizeof(u32); + if (!bytes || bytes > 0x100000 || !channel_state->memory_manager->IsFullyMappedRange(entry.addr, bytes)) continue; + std::vector<u32> contents(static_cast<size_t>(entry.size)); + channel_state->memory_manager->ReadBlockUnsafe(entry.addr, contents.data(), bytes); + std::ofstream dump("native-submit-" + std::to_string(channel_state->bind_id) + "-" + std::to_string(native_submit_logs) + "-" + std::to_string(i) + ".bin", std::ios::binary); + dump.write(reinterpret_cast<const char*>(contents.data()), bytes); + } + } + const auto bind_id = channel_state->bind_id; auto& flags = params.flags; @@ -365,6 +408,8 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL } } + const u32 native_completion = UsesNativeAbi() && flags.increment_value.Value() && !flags.fence_increment.Value() ? params.fence.value : 0; + if (native_completion > 64) return NvResult::BadParameter; params.fence.id = channel_syncpoint; u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) + @@ -382,6 +427,14 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL } } + if (native_completion) { + // The native C56F stream reports its completion count without emitting + // host syncpoint methods. Order actual increments after that GPU work. + gpu.PushGPUEntries(bind_id, Tegra::CommandList{flags.suppress_wfi.Value() + ? BuildIncrementCommandList(params.fence, native_completion) + : BuildIncrementWithWfiCommandList(params.fence, native_completion)}); + LOG_DEBUG(Service_NVDRV, "Native ordered completion: syncpoint={}, increments={}, target={}", channel_syncpoint, native_completion, params.fence.value); + } flags.raw = 0; return NvResult::Success; @@ -463,10 +516,46 @@ Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) { return sm_exception_breakpoint_pause_report_event; case 3: return error_notifier_event; + case 7: + // The native SDK requests this event immediately before configuring + // CHANNEL_SET_ERROR_NOTIFIER, then wraps it in its channel error wait. + return UsesNativeAbi() ? error_notifier_event : nullptr; default: LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id); return nullptr; } } +NvResult nvhost_gpu::BindToTsg(DeviceFD group, Kernel::KProcess* process, std::shared_ptr<Tegra::NativeMacroContext> macros) { + const std::scoped_lock lock{tsg_mutex}; + if (tsg_group) { return NvResult::Busy; } + bool owned{}; + for (const auto& [fd, session_id] : sessions) { + const auto* session = core.GetSession(session_id); + owned |= session && session->process == process; + } + if (!owned) { return NvResult::AccessDenied; } + if (channel_state->native_abi) { + channel_state->native_macro_context = std::move(macros); + if (channel_state->payload) { + channel_state->payload->maxwell_3d.SetNativeMacroContext(channel_state->native_macro_context); + } + } + tsg_group = group; + return NvResult::Success; +} + +void nvhost_gpu::UnbindFromTsg(DeviceFD group) { + const std::scoped_lock lock{tsg_mutex}; + if (tsg_group == group) { tsg_group.reset(); } +} + +bool nvhost_gpu::UsesNativeAbi() const { + for (const auto& [fd, session_id] : sessions) { + const auto* session = core.GetSession(session_id); + if (session && session->process && session->process->UsesPAuthReturns()) { return true; } + } + return false; +} + } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 70a6b1bd68..8219e4afea 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -7,6 +7,8 @@ #pragma once #include <memory> +#include <mutex> +#include <optional> #include <vector> #include "common/bit_field.h" #include "common/common_funcs.h" @@ -20,7 +22,10 @@ namespace Core::Memory { class Memory; } +namespace Kernel { class KProcess; } + namespace Tegra { +struct NativeMacroContext; namespace Control { struct ChannelState; } @@ -58,9 +63,12 @@ public: void OnClose(DeviceFD fd) override; Kernel::KEvent* QueryEvent(u32 event_id) override; + NvResult BindToTsg(DeviceFD group, Kernel::KProcess* process, std::shared_ptr<Tegra::NativeMacroContext> macros); + void UnbindFromTsg(DeviceFD group); private: friend class nvhost_as_gpu; + bool UsesNativeAbi() const; enum class CtxClasses : u32_le { Ctx2D = 0x902D, Ctx3D = 0xB197, @@ -218,6 +226,9 @@ private: NvCore::NvMap& nvmap; std::shared_ptr<Tegra::Control::ChannelState> channel_state; ::Common::unordered_map<DeviceFD, NvCore::SessionId> sessions; + std::mutex tsg_mutex; + std::optional<DeviceFD> tsg_group; + u32 native_submit_logs{}; u32 channel_syncpoint; std::mutex channel_mutex; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_tsg_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_tsg_gpu.cpp new file mode 100644 index 0000000000..ae9d2a50f5 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_tsg_gpu.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <algorithm> +#include <array> +#include <cstring> +#include "common/logging.h" +#include "core/core.h" +#include "video_core/macro.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvdrv/devices/nvhost_gpu.h" +#include "core/hle/service/nvdrv/devices/nvhost_tsg_gpu.h" + +namespace Service::Nvidia::Devices { + +nvhost_tsg_gpu::nvhost_tsg_gpu(Core::System& system, Module& module_, NvCore::Container& core_) + : nvdevice{system, Type::GpuTsg}, native_macros{std::make_shared<Tegra::NativeMacroContext>()}, module{module_}, core{core_} { native_macros->engine.native_turing = true; native_macros->compute_engine.native_turing = true; } + +nvhost_tsg_gpu::~nvhost_tsg_gpu() { OnClose(0); } + +void nvhost_tsg_gpu::OnOpen(NvCore::SessionId id, DeviceFD fd) { + group_fd = fd; + if (const auto* session = core.GetSession(id)) { + process = session->process; + process->Open(system.Kernel()); + } +} + +void nvhost_tsg_gpu::OnClose(DeviceFD fd) { + for (const auto& [channel_fd, channel] : channels) { channel->UnbindFromTsg(group_fd); } + channels.clear(); + if (process) { + process->Close(system.Kernel()); + process = nullptr; + } +} + + +NvResult nvhost_tsg_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<u8> output) { + std::array<u32, 8> words{}; + if (!input.empty()) { std::memcpy(words.data(), input.data(), std::min(input.size(), sizeof(words))); } + LOG_DEBUG(Service_NVDRV, "native TSG ioctl={:#x}, input={}, output={}, words={:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x}", + command.raw, input.size(), output.size(), words[0], words[1], words[2], words[3], + words[4], words[5], words[6], words[7]); + if (command.raw != 0xc0045401 && command.raw != 0xc0045402 && command.raw != 0xc018540b) { return NvResult::NotImplemented; } + const size_t packet_size = command.raw == 0xc018540b ? 24 : 4; + if (!process || input.size() != packet_size || output.size() != packet_size) { + return NvResult::BadParameter; + } + if (packet_size == 24 && (words[1] >= 64 || words[2] || words[3] || words[4] || words[5])) { + return NvResult::BadParameter; + } + const DeviceFD channel_fd = static_cast<s32>(words[0]); + if (command.cmd == 1 || command.cmd == 11) { + if (channels.contains(channel_fd)) { return NvResult::Busy; } + if (channels.size() >= 64) { return NvResult::InsufficientMemory; } + const auto device = module.GetDevice<nvdevice>(channel_fd); + if (!device || device->GetType() != Type::GpuChannel) { return NvResult::BadParameter; } + const auto channel = std::static_pointer_cast<nvhost_gpu>(device); + const auto result = channel->BindToTsg(fd, process, native_macros); + if (result != NvResult::Success) { return result; } + channels.emplace(channel_fd, channel); + } else { + const auto it = channels.find(channel_fd); + if (it == channels.end()) { return NvResult::BadParameter; } + it->second->UnbindFromTsg(fd); + channels.erase(it); + } + std::copy(input.begin(), input.end(), output.begin()); + return NvResult::Success; +} + +NvResult nvhost_tsg_gpu::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::span<u8> output) { + return NvResult::NotImplemented; +} + +NvResult nvhost_tsg_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<u8> output, std::span<u8> inline_output) { + return NvResult::NotImplemented; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_tsg_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_tsg_gpu.h new file mode 100644 index 0000000000..d64c5df3e0 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_tsg_gpu.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <map> +#include <memory> +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Tegra { struct NativeMacroContext; } +namespace Kernel { class KProcess; } +namespace Service::Nvidia { class Module; } + +namespace Service::Nvidia::Devices { +class nvhost_gpu; + +class nvhost_tsg_gpu final : public nvdevice { +public: + nvhost_tsg_gpu(Core::System& system, Module& module, NvCore::Container& core); + ~nvhost_tsg_gpu() override; + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<u8> output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::span<u8> output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<u8> output, std::span<u8> inline_output) override; + void OnOpen(NvCore::SessionId session, DeviceFD fd) override; + void OnClose(DeviceFD fd) override; +private: + std::shared_ptr<Tegra::NativeMacroContext> native_macros; + Module& module; + NvCore::Container& core; + Kernel::KProcess* process{}; + DeviceFD group_fd{-1}; + std::map<DeviceFD, std::shared_ptr<nvhost_gpu>> channels; +}; + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index b252a2efa5..9873d07c24 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -18,6 +18,8 @@ #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" +#include "core/hle/service/nvdrv/devices/nvhost_tsg_gpu.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h" #include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" #include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h" @@ -133,6 +135,10 @@ Module::Module(Core::System& system) auto device = std::make_shared<Devices::nvhost_gpu>(system, events_interface, container); return open_files.emplace(fd, std::move(device)).first; }; + builders["/dev/nvhost-tsg-gpu"] = [this, &system](DeviceFD fd) { + auto device = std::make_shared<Devices::nvhost_tsg_gpu>(system, *this, container); + return open_files.emplace(fd, std::move(device)).first; + }; builders["/dev/nvhost-ctrl-gpu"] = [this, &system](DeviceFD fd) { auto device = std::make_shared<Devices::nvhost_ctrl_gpu>(system, events_interface); return open_files.emplace(fd, std::move(device)).first; @@ -163,7 +169,12 @@ Module::Module(Core::System& system) }; } -Module::~Module() {} +Module::~Module() { + // Devices (including TSG-retained channels) release events and core state. + // Destroy them while those module members are still alive. + for (const auto& [fd, device] : open_files) { device->OnClose(fd); } + open_files.clear(); +} NvResult Module::VerifyFD(DeviceFD fd) const { if (fd < 0) { @@ -180,6 +191,12 @@ NvResult Module::VerifyFD(DeviceFD fd) const { } DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_id) { + if (device_name == "/dev/nvhost-tsg-gpu") { + const auto* session = container.GetSession(session_id); + if (!session || !session->process || !session->process->UsesPAuthReturns()) { + return INVALID_NVDRV_FD; + } + } auto it = builders.find(device_name); if (it == builders.end()) { LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name); diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp index 5fae68adbb..3f81720a39 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp +++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp @@ -72,6 +72,10 @@ void NVDRV::Ioctl1(HLERequestContext& ctx) { const auto input_buffer = ctx.ReadBuffer(0); const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer); + if (nv_result != NvResult::Success && ctx.GetThread().GetOwnerProcess()->UsesPAuthReturns()) { + LOG_DEBUG(Service_NVDRV, "native ioctl failure fd={}, ioctl={:#x}, result={}, caller={:#x}", + fd, command.raw, static_cast<u32>(nv_result), ctx.GetThread().GetContext().lr); + } if (command.is_out != 0) { ctx.WriteBuffer(output_buffer); } @@ -99,6 +103,10 @@ void NVDRV::Ioctl2(HLERequestContext& ctx) { const auto nv_result = nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer); + if (nv_result != NvResult::Success && ctx.GetThread().GetOwnerProcess()->UsesPAuthReturns()) { + LOG_DEBUG(Service_NVDRV, "native ioctl failure fd={}, ioctl={:#x}, result={}, caller={:#x}", + fd, command.raw, static_cast<u32>(nv_result), ctx.GetThread().GetContext().lr); + } if (command.is_out != 0) { ctx.WriteBuffer(output_buffer); } @@ -126,6 +134,11 @@ void NVDRV::Ioctl3(HLERequestContext& ctx) { const auto nv_result = nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, inline_output_buffer); + if (command.raw == 0xc0e04705) { + LOG_DEBUG(Service_NVDRV, "native characteristics input={}, output={}, inline={}, result={}", + input_buffer.size(), output_buffer.size(), inline_output_buffer.size(), + static_cast<u32>(nv_result)); + } if (command.is_out != 0) { ctx.WriteBuffer(output_buffer, 0); ctx.WriteBuffer(inline_output_buffer, 1); @@ -185,6 +198,10 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto fd = rp.Pop<DeviceFD>(); const auto event_id = rp.Pop<u32>(); + if (ctx.GetThread().GetOwnerProcess()->UsesPAuthReturns()) { + LOG_DEBUG(Service_NVDRV, "native QueryEvent fd={}, id={}, caller={:#x}", fd, event_id, + ctx.GetThread().GetContext().lr); + } if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); @@ -265,6 +282,14 @@ NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* RegisterHandlers(functions); } +NVDRV::NVDRV(Core::System& system, std::shared_ptr<Module> module, const char* name, + Kernel::KProcess* process, u64 aruid) + : NVDRV{system, std::move(module), name} { + session_id = nvdrv->GetContainer().OpenSession(process); + pid = aruid; + is_initialized = true; +} + NVDRV::~NVDRV() { if (is_initialized) { auto& container = nvdrv->GetContainer(); diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.h b/src/core/hle/service/nvdrv/nvdrv_interface.h index f628fca7df..d6fe4c18f5 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.h +++ b/src/core/hle/service/nvdrv/nvdrv_interface.h @@ -17,6 +17,8 @@ namespace Service::Nvidia { class NVDRV final : public ServiceFramework<NVDRV> { public: explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name); + explicit NVDRV(Core::System& system, std::shared_ptr<Module> module, const char* name, + Kernel::KProcess* process, u64 aruid); ~NVDRV() override; std::shared_ptr<Module> GetModule() const { diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index c3eb2796e3..9de8abaeb6 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -393,6 +393,15 @@ Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out return Status::NoError; } +std::optional<Fence> BufferQueueProducer::GetReleasedBufferFence(s32 slot) { + const std::scoped_lock lock{core->mutex}; + if (slot < 0 || static_cast<size_t>(slot) >= slots.size() || + slots[slot].buffer_state != BufferState::Free) { + return std::nullopt; + } + return slots[slot].fence; +} + Status BufferQueueProducer::AttachBuffer(s32* out_slot, const std::shared_ptr<GraphicBuffer>& buffer) { if (out_slot == nullptr) { diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h index 697de0ac9b..bf815eb8d8 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h @@ -12,6 +12,7 @@ #include <condition_variable> #include <memory> #include <mutex> +#include <optional> #include "common/common_funcs.h" #include "common/cpu_features.h" @@ -57,6 +58,7 @@ public: Kernel::KReadableEvent* GetNativeHandle(u32 type_id) override; public: + std::optional<Fence> GetReleasedBufferFence(s32 slot); Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf); Status SetBufferCount(s32 buffer_count); Status DequeueBuffer(s32* out_slot, android::Fence* out_fence, bool async, u32 width, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index b4fe2b3ed5..a7e7e10199 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -4,12 +4,16 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <chrono> +#include <limits> #include <fmt/ranges.h> #include "common/assert.h" #include "common/logging.h" #include "common/settings.h" #include "core/core.h" #include "core/hle/ipc.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/service.h" @@ -67,6 +71,26 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx, fmt::format_to(std::back_inserter(buf), ", [{}]={:#x}", i, cmd_buf[i]); buf.push_back('}'); + if (ctx.GetThread().GetOwnerProcess()->UsesPAuthReturns()) { + const auto& context = ctx.GetThread().GetContext(); + LOG_DEBUG(Service, "Unsupported IPC caller pc={:#x}, lr={:#x}, sp={:#x}, fp={:#x}", + context.pc, context.lr, context.sp, context.fp); + u64 frame = context.fp; + for (u32 depth = 0; depth < 8 && frame != 0; ++depth) { + if (frame > (std::numeric_limits<u64>::max)() - 15 || + !ctx.GetMemory().IsValidVirtualAddress(frame) || + !ctx.GetMemory().IsValidVirtualAddress(frame + 15)) { + break; + } + const u64 next = ctx.GetMemory().Read64(frame); + LOG_DEBUG(Service, "Unsupported IPC frame {}: return={:#x}", depth, + ctx.GetMemory().Read64(frame + 8)); + if (next <= frame) { + break; + } + frame = next; + } + } system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name, service_name); UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); if (Settings::values.use_auto_stub) { @@ -82,7 +106,7 @@ void ServiceFrameworkBase::InvokeRequest(HLERequestContext& ctx) { if (info == nullptr || info->handler_callback == nullptr) return ReportUnimplementedFunction(ctx, info); - LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer())); + LOG_DEBUG(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer())); handler_invoker(this, info->handler_callback, ctx); } @@ -92,7 +116,7 @@ void ServiceFrameworkBase::InvokeRequestTipc(HLERequestContext& ctx) { if (info == nullptr || info->handler_callback == nullptr) return ReportUnimplementedFunction(ctx, info); - LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer())); + LOG_DEBUG(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer())); handler_invoker(this, info->handler_callback, ctx); } diff --git a/src/core/launch_params.cpp b/src/core/launch_params.cpp index a0483226ec..814a2cb553 100644 --- a/src/core/launch_params.cpp +++ b/src/core/launch_params.cpp @@ -94,7 +94,7 @@ LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wch break; case 'u': { // Launch game with a specific user - bool argument_ok = isdigit(optarg[0]); + bool argument_ok = isdigit(static_cast<unsigned char>(optarg[0])) != 0; p.selected_user = atoi(optarg); if (!argument_ok) { // try to look it up by username, only finds the first username that matches. diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 5e2ec7e0b9..1dce245f7f 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -13,6 +13,7 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/romfs_factory.h" +#include "core/file_sys/romfs.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" #include "core/hle/service/filesystem/filesystem.h" @@ -129,10 +130,14 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( : AppLoader(directory->GetFile("main")) , dir(std::move(directory)) , override_update(override_update_) + , standalone(false) {} FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { - if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { + const auto directory = dir_file->GetContainingDirectory(); + if (dir_file->GetName() == "main" && directory && + AppLoader_NSO::IdentifyType(dir_file) == FileType::NSO && + FileSys::IsDirectoryExeFS(directory)) { return FileType::DeconstructedRomDirectory; } @@ -182,11 +187,11 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } metadata.Print(); - // Enable NCE only for applications with 39-bit address space. + // Extended ACID programs currently require the Dynarmic compatibility path. const bool is_39bit = metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit; const bool is_application = metadata.GetPoolPartition() == FileSys::PoolPartition::Application; - Settings::SetNceEnabled(is_39bit); + Settings::SetNceEnabled(is_39bit && !metadata.UsesExtendedAcid()); const std::array static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", @@ -267,11 +272,39 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } is_loaded = true; + if (standalone) { + title_id = metadata.GetTitleID(); + system.GetFileSystemController().RegisterProcess( + process.GetProcessId(), {}, + std::make_shared<FileSys::RomFSFactory>(*this, system.GetContentProvider(), + system.GetFileSystemController())); + } return {ResultStatus::Success, LoadParameters{metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()}}; } ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile& out_dir) { + if (romfs == nullptr && standalone) { + const auto directory = file->GetContainingDirectory(); + for (const auto& entry : directory->GetFiles()) { + if (entry->GetExtension() == "romfs") { + romfs = entry; + break; + } + } + if (romfs == nullptr) { + auto loose = directory->GetSubdirectory("romfs"); + if (!loose && directory->GetName() == "exefs") { + const auto parent = directory->GetParentDirectory(); + if (parent) { + loose = parent->GetSubdirectory("romfs"); + } + } + if (loose) { + romfs = FileSys::CreateRomFS(std::move(loose)); + } + } + } if (romfs == nullptr) { return ResultStatus::ErrorNoRomFS; } diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 7283db0662..aba89935a3 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -62,6 +62,7 @@ private: std::string name; u64 title_id{}; bool override_update; + bool standalone{true}; Modules modules; }; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 92e2e2f602..d802d15e9f 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -125,6 +125,10 @@ bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) { } // namespace FileType IdentifyFile(FileSys::VirtualFile file) { + if (const auto romdir_type = + IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) { + return *romdir_type; + } if (const auto nsp_type = IdentifyFileLoader<AppLoader_NSP>(file)) { return *nsp_type; } else if (const auto xci_type = IdentifyFileLoader<AppLoader_XCI>(file)) { @@ -139,9 +143,6 @@ FileType IdentifyFile(FileSys::VirtualFile file) { return *kip_type; } else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) { return *nso_type; - } else if (const auto romdir_type = - IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) { - return *romdir_type; } else { return FileType::Unknown; } |
