diff options
Diffstat (limited to '')
40 files changed, 3196 insertions, 2688 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 112c61b80..f16072e6c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -298,7 +298,42 @@ add_library(core STATIC hle/kernel/svc.h hle/kernel/svc_common.h hle/kernel/svc_types.h - hle/kernel/svc_wrap.h + hle/kernel/svc/svc_activity.cpp + hle/kernel/svc/svc_address_arbiter.cpp + hle/kernel/svc/svc_address_translation.cpp + hle/kernel/svc/svc_cache.cpp + hle/kernel/svc/svc_code_memory.cpp + hle/kernel/svc/svc_condition_variable.cpp + hle/kernel/svc/svc_debug.cpp + hle/kernel/svc/svc_debug_string.cpp + hle/kernel/svc/svc_device_address_space.cpp + hle/kernel/svc/svc_event.cpp + hle/kernel/svc/svc_exception.cpp + hle/kernel/svc/svc_info.cpp + hle/kernel/svc/svc_interrupt_event.cpp + hle/kernel/svc/svc_io_pool.cpp + hle/kernel/svc/svc_ipc.cpp + hle/kernel/svc/svc_kernel_debug.cpp + hle/kernel/svc/svc_light_ipc.cpp + hle/kernel/svc/svc_lock.cpp + hle/kernel/svc/svc_memory.cpp + hle/kernel/svc/svc_physical_memory.cpp + hle/kernel/svc/svc_port.cpp + hle/kernel/svc/svc_power_management.cpp + hle/kernel/svc/svc_process.cpp + hle/kernel/svc/svc_process_memory.cpp + hle/kernel/svc/svc_processor.cpp + hle/kernel/svc/svc_query_memory.cpp + hle/kernel/svc/svc_register.cpp + hle/kernel/svc/svc_resource_limit.cpp + hle/kernel/svc/svc_secure_monitor_call.cpp + hle/kernel/svc/svc_session.cpp + hle/kernel/svc/svc_shared_memory.cpp + hle/kernel/svc/svc_synchronization.cpp + hle/kernel/svc/svc_thread.cpp + hle/kernel/svc/svc_thread_profiler.cpp + hle/kernel/svc/svc_tick.cpp + hle/kernel/svc/svc_transfer_memory.cpp hle/result.h hle/service/acc/acc.cpp hle/service/acc/acc.h diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 67fa5d71c..4cb6f40a0 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1,2697 +1,16 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <algorithm> -#include <cinttypes> -#include <iterator> -#include <mutex> -#include <vector> - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/common_funcs.h" -#include "common/fiber.h" -#include "common/logging/log.h" -#include "common/scope_exit.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/debugger/debugger.h" -#include "core/hle/kernel/k_client_port.h" -#include "core/hle/kernel/k_client_session.h" -#include "core/hle/kernel/k_code_memory.h" -#include "core/hle/kernel/k_event.h" -#include "core/hle/kernel/k_handle_table.h" -#include "core/hle/kernel/k_memory_block.h" -#include "core/hle/kernel/k_memory_layout.h" -#include "core/hle/kernel/k_page_table.h" -#include "core/hle/kernel/k_port.h" +#include "common/common_types.h" #include "core/hle/kernel/k_process.h" -#include "core/hle/kernel/k_readable_event.h" -#include "core/hle/kernel/k_resource_limit.h" -#include "core/hle/kernel/k_scheduler.h" -#include "core/hle/kernel/k_scoped_resource_reservation.h" -#include "core/hle/kernel/k_session.h" -#include "core/hle/kernel/k_shared_memory.h" -#include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/k_thread.h" -#include "core/hle/kernel/k_thread_queue.h" -#include "core/hle/kernel/k_transfer_memory.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/svc.h" -#include "core/hle/kernel/svc_results.h" -#include "core/hle/kernel/svc_types.h" #include "core/hle/kernel/svc_wrap.h" #include "core/hle/result.h" -#include "core/memory.h" -#include "core/reporter.h" namespace Kernel::Svc { namespace { -// Checks if address + size is greater than the given address -// This can return false if the size causes an overflow of a 64-bit type -// or if the given size is zero. -constexpr bool IsValidAddressRange(VAddr address, u64 size) { - return address + size > address; -} - -// Helper function that performs the common sanity checks for svcMapMemory -// and svcUnmapMemory. This is doable, as both functions perform their sanitizing -// in the same order. -Result MapUnmapMemorySanityChecks(const KPageTable& manager, VAddr dst_addr, VAddr src_addr, - u64 size) { - if (!Common::Is4KBAligned(dst_addr)) { - LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(src_addr)) { - LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); - return ResultInvalidSize; - } - - if (size == 0) { - LOG_ERROR(Kernel_SVC, "Size is 0"); - return ResultInvalidSize; - } - - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); - return ResultInvalidSize; - } - - if (!IsValidAddressRange(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}", - dst_addr, size); - return ResultInvalidCurrentMemory; - } - - if (!IsValidAddressRange(src_addr, size)) { - LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}", - src_addr, size); - return ResultInvalidCurrentMemory; - } - - if (!manager.IsInsideAddressSpace(src_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", - src_addr, size); - return ResultInvalidCurrentMemory; - } - - if (manager.IsOutsideStackRegion(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}", - dst_addr, size); - return ResultInvalidMemoryRegion; - } - - if (manager.IsInsideHeapRegion(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination does not fit within the heap region, addr=0x{:016X}, " - "size=0x{:016X}", - dst_addr, size); - return ResultInvalidMemoryRegion; - } - - if (manager.IsInsideAliasRegion(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination does not fit within the map region, addr=0x{:016X}, " - "size=0x{:016X}", - dst_addr, size); - return ResultInvalidMemoryRegion; - } - - return ResultSuccess; -} - -enum class ResourceLimitValueType { - CurrentValue, - LimitValue, - PeakValue, -}; - -} // Anonymous namespace - -/// Set the process heap to a given Size. It can both extend and shrink the heap. -static Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size) { - LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", size); - - // Validate size. - R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); - R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize); - - // Set the heap size. - R_TRY(system.Kernel().CurrentProcess()->PageTable().SetHeapSize(out_address, size)); - - return ResultSuccess; -} - -static Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size) { - VAddr temp_heap_addr{}; - const Result result{SetHeapSize(system, &temp_heap_addr, heap_size)}; - *heap_addr = static_cast<u32>(temp_heap_addr); - return result; -} - -constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) { - switch (perm) { - case MemoryPermission::None: - case MemoryPermission::Read: - case MemoryPermission::ReadWrite: - return true; - default: - return false; - } -} - -static Result SetMemoryPermission(Core::System& system, VAddr address, u64 size, - MemoryPermission perm) { - LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size, - perm); - - // Validate address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the permission. - R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Validate that the region is in range for the current process. - auto& page_table = system.Kernel().CurrentProcess()->PageTable(); - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Set the memory attribute. - return page_table.SetMemoryPermission(address, size, perm); -} - -static Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, - u32 attr) { - LOG_DEBUG(Kernel_SVC, - "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, - size, mask, attr); - - // Validate address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the attribute and mask. - constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::Uncached); - R_UNLESS((mask | attr) == mask, ResultInvalidCombination); - R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination); - - // Validate that the region is in range for the current process. - auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Set the memory attribute. - return page_table.SetMemoryAttribute(address, size, mask, attr); -} - -static Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, - u32 attr) { - return SetMemoryAttribute(system, address, size, mask, attr); -} - -/// Maps a memory range into a different range. -static Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, - src_addr, size); - - auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; - - if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; - result.IsError()) { - return result; - } - - return page_table.MapMemory(dst_addr, src_addr, size); -} - -static Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return MapMemory(system, dst_addr, src_addr, size); -} - -/// Unmaps a region that was previously mapped with svcMapMemory -static Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, - src_addr, size); - - auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; - - if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; - result.IsError()) { - return result; - } - - return page_table.UnmapMemory(dst_addr, src_addr, size); -} - -static Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return UnmapMemory(system, dst_addr, src_addr, size); -} - -template <typename T> -Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u64 name) { - auto& process = *system.CurrentProcess(); - auto& handle_table = process.GetHandleTable(); - - // Declare the session we're going to allocate. - T* session; - - // Reserve a new session from the process resource limit. - // FIXME: LimitableResource_SessionCountMax - KScopedResourceReservation session_reservation(&process, LimitableResource::SessionCountMax); - if (session_reservation.Succeeded()) { - session = T::Create(system.Kernel()); - } else { - return ResultLimitReached; - - // // We couldn't reserve a session. Check that we support dynamically expanding the - // // resource limit. - // R_UNLESS(process.GetResourceLimit() == - // &system.Kernel().GetSystemResourceLimit(), ResultLimitReached); - // R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), ResultLimitReached()); - - // // Try to allocate a session from unused slab memory. - // session = T::CreateFromUnusedSlabMemory(); - // R_UNLESS(session != nullptr, ResultLimitReached); - // ON_RESULT_FAILURE { session->Close(); }; - - // // If we're creating a KSession, we want to add two KSessionRequests to the heap, to - // // prevent request exhaustion. - // // NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's - // // no reason to not do this statically. - // if constexpr (std::same_as<T, KSession>) { - // for (size_t i = 0; i < 2; i++) { - // KSessionRequest* request = KSessionRequest::CreateFromUnusedSlabMemory(); - // R_UNLESS(request != nullptr, ResultLimitReached); - // request->Close(); - // } - // } - - // We successfully allocated a session, so add the object we allocated to the resource - // limit. - // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::SessionCountMax, 1); - } - - // Check that we successfully created a session. - R_UNLESS(session != nullptr, ResultOutOfResource); - - // Initialize the session. - session->Initialize(nullptr, fmt::format("{}", name)); - - // Commit the session reservation. - session_reservation.Commit(); - - // Ensure that we clean up the session (and its only references are handle table) on function - // end. - SCOPE_EXIT({ - session->GetClientSession().Close(); - session->GetServerSession().Close(); - }); - - // Register the session. - T::Register(system.Kernel(), session); - - // Add the server session to the handle table. - R_TRY(handle_table.Add(out_server, &session->GetServerSession())); - - // Add the client session to the handle table. - const auto result = handle_table.Add(out_client, &session->GetClientSession()); - - if (!R_SUCCEEDED(result)) { - // Ensure that we maintaing a clean handle state on exit. - handle_table.Remove(*out_server); - } - - return result; -} - -static Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, - u32 is_light, u64 name) { - if (is_light) { - // return CreateSession<KLightSession>(system, out_server, out_client, name); - return ResultUnknown; - } else { - return CreateSession<KSession>(system, out_server, out_client, name); - } -} - -/// Connect to an OS service given the port name, returns the handle to the port to out -static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) { - auto& memory = system.Memory(); - if (!memory.IsValidVirtualAddress(port_name_address)) { - LOG_ERROR(Kernel_SVC, - "Port Name Address is not a valid virtual address, port_name_address=0x{:016X}", - port_name_address); - return ResultNotFound; - } - - static constexpr std::size_t PortNameMaxLength = 11; - // Read 1 char beyond the max allowed port name to detect names that are too long. - const std::string port_name = memory.ReadCString(port_name_address, PortNameMaxLength + 1); - if (port_name.size() > PortNameMaxLength) { - LOG_ERROR(Kernel_SVC, "Port name is too long, expected {} but got {}", PortNameMaxLength, - port_name.size()); - return ResultOutOfRange; - } - - LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); - - // Get the current handle table. - auto& kernel = system.Kernel(); - auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - - // Find the client port. - auto port = kernel.CreateNamedServicePort(port_name); - if (!port) { - LOG_ERROR(Kernel_SVC, "tried to connect to unknown port: {}", port_name); - return ResultNotFound; - } - - // Reserve a handle for the port. - // NOTE: Nintendo really does write directly to the output handle here. - R_TRY(handle_table.Reserve(out)); - auto handle_guard = SCOPE_GUARD({ handle_table.Unreserve(*out); }); - - // Create a session. - KClientSession* session{}; - R_TRY(port->CreateSession(std::addressof(session))); - - kernel.RegisterNamedServiceHandler(port_name, &port->GetParent()->GetServerPort()); - - // Register the session in the table, close the extra reference. - handle_table.Register(*out, session); - session->Close(); - - // We succeeded. - handle_guard.Cancel(); - return ResultSuccess; -} - -static Result ConnectToNamedPort32(Core::System& system, Handle* out_handle, - u32 port_name_address) { - - return ConnectToNamedPort(system, out_handle, port_name_address); -} - -/// Makes a blocking IPC call to a service. -static Result SendSyncRequest(Core::System& system, Handle handle) { - auto& kernel = system.Kernel(); - - // Create the wait queue. - KThreadQueue wait_queue(kernel); - - // Get the client session from its handle. - KScopedAutoObject session = - kernel.CurrentProcess()->GetHandleTable().GetObject<KClientSession>(handle); - R_UNLESS(session.IsNotNull(), ResultInvalidHandle); - - LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); - - return session->SendSyncRequest(); -} - -static Result SendSyncRequest32(Core::System& system, Handle handle) { - return SendSyncRequest(system, handle); -} - -static Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles, - s32 num_handles, Handle reply_target, s64 timeout_ns) { - auto& kernel = system.Kernel(); - auto& handle_table = GetCurrentThread(kernel).GetOwnerProcess()->GetHandleTable(); - - // Convert handle list to object table. - std::vector<KSynchronizationObject*> objs(num_handles); - R_UNLESS( - handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, num_handles), - ResultInvalidHandle); - - // Ensure handles are closed when we're done. - SCOPE_EXIT({ - for (auto i = 0; i < num_handles; ++i) { - objs[i]->Close(); - } - }); - - // Reply to the target, if one is specified. - if (reply_target != InvalidHandle) { - KScopedAutoObject session = handle_table.GetObject<KServerSession>(reply_target); - R_UNLESS(session.IsNotNull(), ResultInvalidHandle); - - // If we fail to reply, we want to set the output index to -1. - // ON_RESULT_FAILURE { *out_index = -1; }; - - // Send the reply. - // R_TRY(session->SendReply()); - - Result rc = session->SendReply(); - if (!R_SUCCEEDED(rc)) { - *out_index = -1; - return rc; - } - } - - // Wait for a message. - while (true) { - // Wait for an object. - s32 index; - Result result = KSynchronizationObject::Wait(kernel, &index, objs.data(), - static_cast<s32>(objs.size()), timeout_ns); - if (result == ResultTimedOut) { - return result; - } - - // Receive the request. - if (R_SUCCEEDED(result)) { - KServerSession* session = objs[index]->DynamicCast<KServerSession*>(); - if (session != nullptr) { - result = session->ReceiveRequest(); - if (result == ResultNotFound) { - continue; - } - } - } - - *out_index = index; - return result; - } -} - -/// Get the ID for the specified thread. -static Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) { - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Get the thread's id. - *out_thread_id = thread->GetId(); - return ResultSuccess; -} - -static Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high, - Handle thread_handle) { - u64 out_thread_id{}; - const Result result{GetThreadId(system, &out_thread_id, thread_handle)}; - - *out_thread_id_low = static_cast<u32>(out_thread_id >> 32); - *out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max()); - - return result; -} - -/// Gets the ID of the specified process or a specified thread's owning process. -static Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle); - - // Get the object from the handle table. - KScopedAutoObject obj = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KAutoObject>( - static_cast<Handle>(handle)); - R_UNLESS(obj.IsNotNull(), ResultInvalidHandle); - - // Get the process from the object. - KProcess* process = nullptr; - if (KProcess* p = obj->DynamicCast<KProcess*>(); p != nullptr) { - // The object is a process, so we can use it directly. - process = p; - } else if (KThread* t = obj->DynamicCast<KThread*>(); t != nullptr) { - // The object is a thread, so we want to use its parent. - process = reinterpret_cast<KThread*>(obj.GetPointerUnsafe())->GetOwnerProcess(); - } else { - // TODO(bunnei): This should also handle debug objects before returning. - UNIMPLEMENTED_MSG("Debug objects not implemented"); - } - - // Make sure the target process exists. - R_UNLESS(process != nullptr, ResultInvalidHandle); - - // Get the process id. - *out_process_id = process->GetId(); - - return ResultSuccess; -} - -static Result GetProcessId32(Core::System& system, u32* out_process_id_low, - u32* out_process_id_high, Handle handle) { - u64 out_process_id{}; - const auto result = GetProcessId(system, &out_process_id, handle); - *out_process_id_low = static_cast<u32>(out_process_id); - *out_process_id_high = static_cast<u32>(out_process_id >> 32); - return result; -} - -/// Wait for the given handles to synchronize, timeout after the specified nanoseconds -static Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, - s32 num_handles, s64 nano_seconds) { - LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, num_handles={}, nano_seconds={}", - handles_address, num_handles, nano_seconds); - - // Ensure number of handles is valid. - R_UNLESS(0 <= num_handles && num_handles <= ArgumentHandleCountMax, ResultOutOfRange); - - auto& kernel = system.Kernel(); - std::vector<KSynchronizationObject*> objs(num_handles); - const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - Handle* handles = system.Memory().GetPointer<Handle>(handles_address); - - // Copy user handles. - if (num_handles > 0) { - // Convert the handles to objects. - R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, - num_handles), - ResultInvalidHandle); - for (const auto& obj : objs) { - kernel.RegisterInUseObject(obj); - } - } - - // Ensure handles are closed when we're done. - SCOPE_EXIT({ - for (s32 i = 0; i < num_handles; ++i) { - kernel.UnregisterInUseObject(objs[i]); - objs[i]->Close(); - } - }); - - return KSynchronizationObject::Wait(kernel, index, objs.data(), static_cast<s32>(objs.size()), - nano_seconds); -} - -static Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, - s32 num_handles, u32 timeout_high, s32* index) { - const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)}; - return WaitSynchronization(system, index, handles_address, num_handles, nano_seconds); -} - -/// Resumes a thread waiting on WaitSynchronization -static Result CancelSynchronization(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Cancel the thread's wait. - thread->WaitCancel(); - return ResultSuccess; -} - -static Result CancelSynchronization32(Core::System& system, Handle handle) { - return CancelSynchronization(system, handle); -} - -/// Attempts to locks a mutex -static Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag) { - LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}", - thread_handle, address, tag); - - // Validate the input address. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})", - address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(u32))) { - LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); - return ResultInvalidAddress; - } - - return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag); -} - -static Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag) { - return ArbitrateLock(system, thread_handle, address, tag); -} - -/// Unlock a mutex -static Result ArbitrateUnlock(Core::System& system, VAddr address) { - LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address); - - // Validate the input address. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, - "Attempting to arbitrate an unlock on a kernel address (address={:08X})", - address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(u32))) { - LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); - return ResultInvalidAddress; - } - - return system.Kernel().CurrentProcess()->SignalToAddress(address); -} - -static Result ArbitrateUnlock32(Core::System& system, u32 address) { - return ArbitrateUnlock(system, address); -} - -/// Break program execution -static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { - BreakReason break_reason = - static_cast<BreakReason>(reason & ~static_cast<u32>(BreakReason::NotificationOnlyFlag)); - bool notification_only = (reason & static_cast<u32>(BreakReason::NotificationOnlyFlag)) != 0; - - bool has_dumped_buffer{}; - std::vector<u8> debug_buffer; - - const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { - if (sz == 0 || addr == 0 || has_dumped_buffer) { - return; - } - - auto& memory = system.Memory(); - - // This typically is an error code so we're going to assume this is the case - if (sz == sizeof(u32)) { - LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", memory.Read32(addr)); - } else { - // We don't know what's in here so we'll hexdump it - debug_buffer.resize(sz); - memory.ReadBlock(addr, debug_buffer.data(), sz); - std::string hexdump; - for (std::size_t i = 0; i < debug_buffer.size(); i++) { - hexdump += fmt::format("{:02X} ", debug_buffer[i]); - if (i != 0 && i % 16 == 0) { - hexdump += '\n'; - } - } - LOG_CRITICAL(Debug_Emulated, "debug_buffer=\n{}", hexdump); - } - has_dumped_buffer = true; - }; - switch (break_reason) { - case BreakReason::Panic: - LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1, - info2); - handle_debug_buffer(info1, info2); - break; - case BreakReason::Assert: - LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}", - info1, info2); - handle_debug_buffer(info1, info2); - break; - case BreakReason::User: - LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2); - handle_debug_buffer(info1, info2); - break; - case BreakReason::PreLoadDll: - LOG_INFO(Debug_Emulated, - "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1, - info2); - break; - case BreakReason::PostLoadDll: - LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, - info2); - break; - case BreakReason::PreUnloadDll: - LOG_INFO(Debug_Emulated, - "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1, - info2); - break; - case BreakReason::PostUnloadDll: - LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}", - info1, info2); - break; - case BreakReason::CppException: - LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered."); - break; - default: - LOG_WARNING( - Debug_Emulated, - "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - handle_debug_buffer(info1, info2); - break; - } - - system.GetReporter().SaveSvcBreakReport(reason, notification_only, info1, info2, - has_dumped_buffer ? std::make_optional(debug_buffer) - : std::nullopt); - - if (!notification_only) { - LOG_CRITICAL( - Debug_Emulated, - "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - - handle_debug_buffer(info1, info2); - - auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); - const auto thread_processor_id = current_thread->GetActiveCore(); - system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); - } - - if (system.DebuggerEnabled()) { - auto* thread = system.Kernel().GetCurrentEmuThread(); - system.GetDebugger().NotifyThreadStopped(thread); - thread->RequestSuspend(Kernel::SuspendType::Debug); - } -} - -static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { - Break(system, reason, info1, info2); -} - -/// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString(Core::System& system, VAddr address, u64 len) { - if (len == 0) { - return; - } - - std::string str(len, '\0'); - system.Memory().ReadBlock(address, str.data(), str.size()); - LOG_DEBUG(Debug_Emulated, "{}", str); -} - -static void OutputDebugString32(Core::System& system, u32 address, u32 len) { - OutputDebugString(system, address, len); -} - -/// Gets system/memory information for the current process -static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle, - u64 info_sub_id) { - LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, - info_sub_id, handle); - - const auto info_id_type = static_cast<InfoType>(info_id); - - switch (info_id_type) { - case InfoType::CoreMask: - case InfoType::PriorityMask: - case InfoType::AliasRegionAddress: - case InfoType::AliasRegionSize: - case InfoType::HeapRegionAddress: - case InfoType::HeapRegionSize: - case InfoType::AslrRegionAddress: - case InfoType::AslrRegionSize: - case InfoType::StackRegionAddress: - case InfoType::StackRegionSize: - case InfoType::TotalMemorySize: - case InfoType::UsedMemorySize: - case InfoType::SystemResourceSizeTotal: - case InfoType::SystemResourceSizeUsed: - case InfoType::ProgramId: - case InfoType::UserExceptionContextAddress: - case InfoType::TotalNonSystemMemorySize: - case InfoType::UsedNonSystemMemorySize: - case InfoType::IsApplication: - case InfoType::FreeThreadCount: { - if (info_sub_id != 0) { - LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, - info_sub_id); - return ResultInvalidEnumValue; - } - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}", - info_id, info_sub_id, handle); - return ResultInvalidHandle; - } - - switch (info_id_type) { - case InfoType::CoreMask: - *result = process->GetCoreMask(); - return ResultSuccess; - - case InfoType::PriorityMask: - *result = process->GetPriorityMask(); - return ResultSuccess; - - case InfoType::AliasRegionAddress: - *result = process->PageTable().GetAliasRegionStart(); - return ResultSuccess; - - case InfoType::AliasRegionSize: - *result = process->PageTable().GetAliasRegionSize(); - return ResultSuccess; - - case InfoType::HeapRegionAddress: - *result = process->PageTable().GetHeapRegionStart(); - return ResultSuccess; - - case InfoType::HeapRegionSize: - *result = process->PageTable().GetHeapRegionSize(); - return ResultSuccess; - - case InfoType::AslrRegionAddress: - *result = process->PageTable().GetAliasCodeRegionStart(); - return ResultSuccess; - - case InfoType::AslrRegionSize: - *result = process->PageTable().GetAliasCodeRegionSize(); - return ResultSuccess; - - case InfoType::StackRegionAddress: - *result = process->PageTable().GetStackRegionStart(); - return ResultSuccess; - - case InfoType::StackRegionSize: - *result = process->PageTable().GetStackRegionSize(); - return ResultSuccess; - - case InfoType::TotalMemorySize: - *result = process->GetTotalPhysicalMemoryAvailable(); - return ResultSuccess; - - case InfoType::UsedMemorySize: - *result = process->GetTotalPhysicalMemoryUsed(); - return ResultSuccess; - - case InfoType::SystemResourceSizeTotal: - *result = process->GetSystemResourceSize(); - return ResultSuccess; - - case InfoType::SystemResourceSizeUsed: - LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); - *result = process->GetSystemResourceUsage(); - return ResultSuccess; - - case InfoType::ProgramId: - *result = process->GetProgramID(); - return ResultSuccess; - - case InfoType::UserExceptionContextAddress: - *result = process->GetProcessLocalRegionAddress(); - return ResultSuccess; - - case InfoType::TotalNonSystemMemorySize: - *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); - return ResultSuccess; - - case InfoType::UsedNonSystemMemorySize: - *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); - return ResultSuccess; - - case InfoType::FreeThreadCount: - *result = process->GetFreeThreadCount(); - return ResultSuccess; - - default: - break; - } - - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); - return ResultInvalidEnumValue; - } - - case InfoType::DebuggerAttached: - *result = 0; - return ResultSuccess; - - case InfoType::ResourceLimit: { - if (handle != 0) { - LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle); - return ResultInvalidHandle; - } - - if (info_sub_id != 0) { - LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, - info_sub_id); - return ResultInvalidCombination; - } - - KProcess* const current_process = system.Kernel().CurrentProcess(); - KHandleTable& handle_table = current_process->GetHandleTable(); - const auto resource_limit = current_process->GetResourceLimit(); - if (!resource_limit) { - *result = Svc::InvalidHandle; - // Yes, the kernel considers this a successful operation. - return ResultSuccess; - } - - Handle resource_handle{}; - R_TRY(handle_table.Add(&resource_handle, resource_limit)); - - *result = resource_handle; - return ResultSuccess; - } - - case InfoType::RandomEntropy: - if (handle != 0) { - LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", - handle); - return ResultInvalidHandle; - } - - if (info_sub_id >= KProcess::RANDOM_ENTROPY_SIZE) { - LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}", - KProcess::RANDOM_ENTROPY_SIZE, info_sub_id); - return ResultInvalidCombination; - } - - *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id); - return ResultSuccess; - - case InfoType::InitialProcessIdRange: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query privileged process id bounds, returned 0"); - *result = 0; - return ResultSuccess; - - case InfoType::ThreadTickCount: { - constexpr u64 num_cpus = 4; - if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { - LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, - info_sub_id); - return ResultInvalidCombination; - } - - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>( - static_cast<Handle>(handle)); - if (thread.IsNull()) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", - static_cast<Handle>(handle)); - return ResultInvalidHandle; - } - - const auto& core_timing = system.CoreTiming(); - const auto& scheduler = *system.Kernel().CurrentScheduler(); - const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); - const bool same_thread = current_thread == thread.GetPointerUnsafe(); - - const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime(); - u64 out_ticks = 0; - if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { - const u64 thread_ticks = current_thread->GetCpuTime(); - - out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); - } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { - out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks; - } - - *result = out_ticks; - return ResultSuccess; - } - case InfoType::IdleTickCount: { - // Verify the input handle is invalid. - R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); - - // Verify the requested core is valid. - const bool core_valid = - (info_sub_id == 0xFFFFFFFFFFFFFFFF) || - (info_sub_id == static_cast<u64>(system.Kernel().CurrentPhysicalCoreIndex())); - R_UNLESS(core_valid, ResultInvalidCombination); - - // Get the idle tick count. - *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); - return ResultSuccess; - } - case InfoType::MesosphereCurrentProcess: { - // Verify the input handle is invalid. - R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); - - // Verify the sub-type is valid. - R_UNLESS(info_sub_id == 0, ResultInvalidCombination); - - // Get the handle table. - KProcess* current_process = system.Kernel().CurrentProcess(); - KHandleTable& handle_table = current_process->GetHandleTable(); - - // Get a new handle for the current process. - Handle tmp; - R_TRY(handle_table.Add(&tmp, current_process)); - - // Set the output. - *result = tmp; - - // We succeeded. - return ResultSuccess; - } - default: - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); - return ResultInvalidEnumValue; - } -} - -static Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, - u32 info_id, u32 handle, u32 sub_id_high) { - const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)}; - u64 res_value{}; - - const Result result{GetInfo(system, &res_value, info_id, handle, sub_id)}; - *result_high = static_cast<u32>(res_value >> 32); - *result_low = static_cast<u32>(res_value & std::numeric_limits<u32>::max()); - - return result; -} - -/// Maps memory at a desired address -static Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { - LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); - - if (!Common::Is4KBAligned(addr)) { - LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); - return ResultInvalidSize; - } - - if (size == 0) { - LOG_ERROR(Kernel_SVC, "Size is zero"); - return ResultInvalidSize; - } - - if (!(addr < addr + size)) { - LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); - return ResultInvalidMemoryRegion; - } - - KProcess* const current_process{system.Kernel().CurrentProcess()}; - auto& page_table{current_process->PageTable()}; - - if (current_process->GetSystemResourceSize() == 0) { - LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); - return ResultInvalidState; - } - - if (!page_table.IsInsideAddressSpace(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - if (page_table.IsOutsideAliasRegion(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - return page_table.MapPhysicalMemory(addr, size); -} - -static Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return MapPhysicalMemory(system, addr, size); -} - -/// Unmaps memory previously mapped via MapPhysicalMemory -static Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { - LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); - - if (!Common::Is4KBAligned(addr)) { - LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); - return ResultInvalidSize; - } - - if (size == 0) { - LOG_ERROR(Kernel_SVC, "Size is zero"); - return ResultInvalidSize; - } - - if (!(addr < addr + size)) { - LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); - return ResultInvalidMemoryRegion; - } - - KProcess* const current_process{system.Kernel().CurrentProcess()}; - auto& page_table{current_process->PageTable()}; - - if (current_process->GetSystemResourceSize() == 0) { - LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); - return ResultInvalidState; - } - - if (!page_table.IsInsideAddressSpace(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - if (page_table.IsOutsideAliasRegion(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - return page_table.UnmapPhysicalMemory(addr, size); -} - -static Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return UnmapPhysicalMemory(system, addr, size); -} - -/// Sets the thread activity -static Result SetThreadActivity(Core::System& system, Handle thread_handle, - ThreadActivity thread_activity) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle, - thread_activity); - - // Validate the activity. - constexpr auto IsValidThreadActivity = [](ThreadActivity activity) { - return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused; - }; - R_UNLESS(IsValidThreadActivity(thread_activity), ResultInvalidEnumValue); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Check that the activity is being set on a non-current thread for the current process. - R_UNLESS(thread->GetOwnerProcess() == system.Kernel().CurrentProcess(), ResultInvalidHandle); - R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(system.Kernel()), ResultBusy); - - // Set the activity. - R_TRY(thread->SetActivity(thread_activity)); - - return ResultSuccess; -} - -static Result SetThreadActivity32(Core::System& system, Handle thread_handle, - Svc::ThreadActivity thread_activity) { - return SetThreadActivity(system, thread_handle, thread_activity); -} - -/// Gets the thread context -static Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) { - LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context, - thread_handle); - - auto& kernel = system.Kernel(); - - // Get the thread from its handle. - KScopedAutoObject thread = - kernel.CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Require the handle be to a non-current thread in the current process. - const auto* current_process = kernel.CurrentProcess(); - R_UNLESS(current_process == thread->GetOwnerProcess(), ResultInvalidId); - - // Verify that the thread isn't terminated. - R_UNLESS(thread->GetState() != ThreadState::Terminated, ResultTerminationRequested); - - /// Check that the thread is not the current one. - /// NOTE: Nintendo does not check this, and thus the following loop will deadlock. - R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(kernel), ResultInvalidId); - - // Try to get the thread context until the thread isn't current on any core. - while (true) { - KScopedSchedulerLock sl{kernel}; - - // TODO(bunnei): Enforce that thread is suspended for debug here. - - // If the thread's raw state isn't runnable, check if it's current on some core. - if (thread->GetRawState() != ThreadState::Runnable) { - bool current = false; - for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { - if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetSchedulerCurrentThread()) { - current = true; - break; - } - } - - // If the thread is current, retry until it isn't. - if (current) { - continue; - } - } - - // Get the thread context. - std::vector<u8> context; - R_TRY(thread->GetThreadContext3(context)); - - // Copy the thread context to user space. - system.Memory().WriteBlock(out_context, context.data(), context.size()); - - return ResultSuccess; - } - - return ResultSuccess; -} - -static Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) { - return GetThreadContext(system, out_context, thread_handle); -} - -/// Gets the priority for the specified thread -static Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) { - LOG_TRACE(Kernel_SVC, "called"); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Get the thread's priority. - *out_priority = thread->GetPriority(); - return ResultSuccess; -} - -static Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) { - return GetThreadPriority(system, out_priority, handle); -} - -/// Sets the priority for the specified thread -static Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority) { - // Get the current process. - KProcess& process = *system.Kernel().CurrentProcess(); - - // Validate the priority. - R_UNLESS(HighestThreadPriority <= priority && priority <= LowestThreadPriority, - ResultInvalidPriority); - R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); - - // Get the thread from its handle. - KScopedAutoObject thread = process.GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Set the thread priority. - thread->SetBasePriority(priority); - return ResultSuccess; -} - -static Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority) { - return SetThreadPriority(system, thread_handle, priority); -} - -/// Get which CPU core is executing the current thread -static u32 GetCurrentProcessorNumber(Core::System& system) { - LOG_TRACE(Kernel_SVC, "called"); - return static_cast<u32>(system.CurrentPhysicalCore().CoreIndex()); -} - -static u32 GetCurrentProcessorNumber32(Core::System& system) { - return GetCurrentProcessorNumber(system); -} - -namespace { - -constexpr bool IsValidSharedMemoryPermission(Svc::MemoryPermission perm) { - switch (perm) { - case Svc::MemoryPermission::Read: - case Svc::MemoryPermission::ReadWrite: - return true; - default: - return false; - } -} - -[[maybe_unused]] constexpr bool IsValidRemoteSharedMemoryPermission(Svc::MemoryPermission perm) { - return IsValidSharedMemoryPermission(perm) || perm == Svc::MemoryPermission::DontCare; -} - -constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) { - switch (perm) { - case Svc::MemoryPermission::None: - case Svc::MemoryPermission::Read: - case Svc::MemoryPermission::ReadWrite: - case Svc::MemoryPermission::ReadExecute: - return true; - default: - return false; - } -} - -constexpr bool IsValidMapCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::ReadWrite; -} - -constexpr bool IsValidMapToOwnerCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::Read || perm == Svc::MemoryPermission::ReadExecute; -} - -constexpr bool IsValidUnmapCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::None; -} - -constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::None; -} - -} // Anonymous namespace - -static Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size, - Svc::MemoryPermission map_perm) { - LOG_TRACE(Kernel_SVC, - "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", - shmem_handle, address, size, map_perm); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the permission. - R_UNLESS(IsValidSharedMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); - - // Get the current process. - auto& process = *system.Kernel().CurrentProcess(); - auto& page_table = process.PageTable(); - - // Get the shared memory. - KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); - R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); - - // Verify that the mapping is in range. - R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); - - // Add the shared memory to the process. - R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); - - // Ensure that we clean up the shared memory if we fail to map it. - auto guard = - SCOPE_GUARD({ process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); }); - - // Map the shared memory. - R_TRY(shmem->Map(process, address, size, map_perm)); - - // We succeeded. - guard.Cancel(); - return ResultSuccess; -} - -static Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size, - Svc::MemoryPermission map_perm) { - return MapSharedMemory(system, shmem_handle, address, size, map_perm); -} - -static Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, - u64 size) { - // Validate the address/size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Get the current process. - auto& process = *system.Kernel().CurrentProcess(); - auto& page_table = process.PageTable(); - - // Get the shared memory. - KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); - R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); - - // Verify that the mapping is in range. - R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); - - // Unmap the shared memory. - R_TRY(shmem->Unmap(process, address, size)); - - // Remove the shared memory from the process. - process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); - - return ResultSuccess; -} - -static Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, - u32 size) { - return UnmapSharedMemory(system, shmem_handle, address, size); -} - -static Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address, - u64 size, Svc::MemoryPermission perm) { - LOG_TRACE(Kernel_SVC, - "called, process_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", - process_handle, address, size, perm); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); - R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); - - // Validate the memory permission. - R_UNLESS(IsValidProcessMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Get the process from its handle. - KScopedAutoObject process = - system.CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); - R_UNLESS(process.IsNotNull(), ResultInvalidHandle); - - // Validate that the address is in range. - auto& page_table = process->PageTable(); - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Set the memory permission. - return page_table.SetProcessMemoryPermission(address, size, perm); -} - -static Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, - VAddr src_address, u64 size) { - LOG_TRACE(Kernel_SVC, - "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", - dst_address, process_handle, src_address, size); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); - R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); - - // Get the processes. - KProcess* dst_process = system.CurrentProcess(); - KScopedAutoObject src_process = - dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); - R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); - - // Get the page tables. - auto& dst_pt = dst_process->PageTable(); - auto& src_pt = src_process->PageTable(); - - // Validate that the mapping is in range. - R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); - R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), - ResultInvalidMemoryRegion); - - // Create a new page group. - KPageGroup pg{system.Kernel(), dst_pt.GetBlockInfoManager()}; - R_TRY(src_pt.MakeAndOpenPageGroup( - std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess, - KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None, - KMemoryAttribute::All, KMemoryAttribute::None)); - - // Map the group. - R_TRY(dst_pt.MapPageGroup(dst_address, pg, KMemoryState::SharedCode, - KMemoryPermission::UserReadWrite)); - - return ResultSuccess; -} - -static Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, - VAddr src_address, u64 size) { - LOG_TRACE(Kernel_SVC, - "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", - dst_address, process_handle, src_address, size); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); - R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); - - // Get the processes. - KProcess* dst_process = system.CurrentProcess(); - KScopedAutoObject src_process = - dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); - R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); - - // Get the page tables. - auto& dst_pt = dst_process->PageTable(); - auto& src_pt = src_process->PageTable(); - - // Validate that the mapping is in range. - R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); - R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), - ResultInvalidMemoryRegion); - - // Unmap the memory. - R_TRY(dst_pt.UnmapProcessMemory(dst_address, size, src_pt, src_address)); - - return ResultSuccess; -} - -static Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, size=0x{:X}", address, size); - - // Get kernel instance. - auto& kernel = system.Kernel(); - - // Validate address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Create the code memory. - - KCodeMemory* code_mem = KCodeMemory::Create(kernel); - R_UNLESS(code_mem != nullptr, ResultOutOfResource); - - // Verify that the region is in range. - R_UNLESS(system.CurrentProcess()->PageTable().Contains(address, size), - ResultInvalidCurrentMemory); - - // Initialize the code memory. - R_TRY(code_mem->Initialize(system.DeviceMemory(), address, size)); - - // Register the code memory. - KCodeMemory::Register(kernel, code_mem); - - // Add the code memory to the handle table. - R_TRY(system.CurrentProcess()->GetHandleTable().Add(out, code_mem)); - - code_mem->Close(); - - return ResultSuccess; -} - -static Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size) { - return CreateCodeMemory(system, out, address, size); -} - -static Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation, - VAddr address, size_t size, Svc::MemoryPermission perm) { - - LOG_TRACE(Kernel_SVC, - "called, code_memory_handle=0x{:X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, " - "permission=0x{:X}", - code_memory_handle, operation, address, size, perm); - - // Validate the address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Get the code memory from its handle. - KScopedAutoObject code_mem = - system.CurrentProcess()->GetHandleTable().GetObject<KCodeMemory>(code_memory_handle); - R_UNLESS(code_mem.IsNotNull(), ResultInvalidHandle); - - // NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. - // This enables homebrew usage of these SVCs for JIT. - - // Perform the operation. - switch (static_cast<CodeMemoryOperation>(operation)) { - case CodeMemoryOperation::Map: { - // Check that the region is in range. - R_UNLESS( - system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidMapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Map the memory. - R_TRY(code_mem->Map(address, size)); - } break; - case CodeMemoryOperation::Unmap: { - // Check that the region is in range. - R_UNLESS( - system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Unmap the memory. - R_TRY(code_mem->Unmap(address, size)); - } break; - case CodeMemoryOperation::MapToOwner: { - // Check that the region is in range. - R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, - KMemoryState::GeneratedCode), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Map the memory to its owner. - R_TRY(code_mem->MapToOwner(address, size, perm)); - } break; - case CodeMemoryOperation::UnmapFromOwner: { - // Check that the region is in range. - R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, - KMemoryState::GeneratedCode), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Unmap the memory from its owner. - R_TRY(code_mem->UnmapFromOwner(address, size)); - } break; - default: - return ResultInvalidEnumValue; - } - - return ResultSuccess; -} - -static Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation, - u64 address, u64 size, Svc::MemoryPermission perm) { - return ControlCodeMemory(system, code_memory_handle, operation, address, size, perm); -} - -static Result QueryProcessMemory(Core::System& system, VAddr memory_info_address, - VAddr page_info_address, Handle process_handle, VAddr address) { - LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address); - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", - process_handle); - return ResultInvalidHandle; - } - - auto& memory{system.Memory()}; - const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()}; - - memory.Write64(memory_info_address + 0x00, memory_info.base_address); - memory.Write64(memory_info_address + 0x08, memory_info.size); - memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff); - memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attribute)); - memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.permission)); - memory.Write32(memory_info_address + 0x1c, memory_info.ipc_count); - memory.Write32(memory_info_address + 0x20, memory_info.device_count); - memory.Write32(memory_info_address + 0x24, 0); - - // Page info appears to be currently unused by the kernel and is always set to zero. - memory.Write32(page_info_address, 0); - - return ResultSuccess; -} - -static Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, - VAddr query_address) { - LOG_TRACE(Kernel_SVC, - "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, " - "query_address=0x{:016X}", - memory_info_address, page_info_address, query_address); - - return QueryProcessMemory(system, memory_info_address, page_info_address, CurrentProcess, - query_address); -} - -static Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address, - u32 query_address) { - return QueryMemory(system, memory_info_address, page_info_address, query_address); -} - -static Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, - u64 src_address, u64 size) { - LOG_DEBUG(Kernel_SVC, - "called. process_handle=0x{:08X}, dst_address=0x{:016X}, " - "src_address=0x{:016X}, size=0x{:016X}", - process_handle, dst_address, src_address, size); - - if (!Common::Is4KBAligned(src_address)) { - LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", - src_address); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(dst_address)) { - LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", - dst_address); - return ResultInvalidAddress; - } - - if (size == 0 || !Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size); - return ResultInvalidSize; - } - - if (!IsValidAddressRange(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range overflows the address space (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidCurrentMemory; - } - - if (!IsValidAddressRange(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range overflows the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", - process_handle); - return ResultInvalidHandle; - } - - auto& page_table = process->PageTable(); - if (!page_table.IsInsideAddressSpace(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range is not within the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - if (!page_table.IsInsideASLRRegion(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidMemoryRegion; - } - - return page_table.MapCodeMemory(dst_address, src_address, size); -} - -static Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, - u64 src_address, u64 size) { - LOG_DEBUG(Kernel_SVC, - "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, " - "size=0x{:016X}", - process_handle, dst_address, src_address, size); - - if (!Common::Is4KBAligned(dst_address)) { - LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", - dst_address); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(src_address)) { - LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", - src_address); - return ResultInvalidAddress; - } - - if (size == 0 || !Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size); - return ResultInvalidSize; - } - - if (!IsValidAddressRange(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range overflows the address space (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidCurrentMemory; - } - - if (!IsValidAddressRange(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range overflows the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", - process_handle); - return ResultInvalidHandle; - } - - auto& page_table = process->PageTable(); - if (!page_table.IsInsideAddressSpace(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range is not within the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - if (!page_table.IsInsideASLRRegion(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidMemoryRegion; - } - - return page_table.UnmapCodeMemory(dst_address, src_address, size, - KPageTable::ICacheInvalidationStrategy::InvalidateAll); -} - -/// Exits the current process -static void ExitProcess(Core::System& system) { - auto* current_process = system.Kernel().CurrentProcess(); - - LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); - ASSERT_MSG(current_process->GetState() == KProcess::State::Running, - "Process has already exited"); - - system.Exit(); -} - -static void ExitProcess32(Core::System& system) { - ExitProcess(system); -} - -namespace { - -constexpr bool IsValidVirtualCoreId(int32_t core_id) { - return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES)); -} - -} // Anonymous namespace - -/// Creates a new thread -static Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, - VAddr stack_bottom, u32 priority, s32 core_id) { - LOG_DEBUG(Kernel_SVC, - "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, " - "priority=0x{:08X}, core_id=0x{:08X}", - entry_point, arg, stack_bottom, priority, core_id); - - // Adjust core id, if it's the default magic. - auto& kernel = system.Kernel(); - auto& process = *kernel.CurrentProcess(); - if (core_id == IdealCoreUseProcessValue) { - core_id = process.GetIdealCoreId(); - } - - // Validate arguments. - if (!IsValidVirtualCoreId(core_id)) { - LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id); - return ResultInvalidCoreId; - } - if (((1ULL << core_id) & process.GetCoreMask()) == 0) { - LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id); - return ResultInvalidCoreId; - } - - if (HighestThreadPriority > priority || priority > LowestThreadPriority) { - LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority); - return ResultInvalidPriority; - } - if (!process.CheckThreadPriority(priority)) { - LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority); - return ResultInvalidPriority; - } - - // Reserve a new thread from the process resource limit (waiting up to 100ms). - KScopedResourceReservation thread_reservation( - kernel.CurrentProcess(), LimitableResource::ThreadCountMax, 1, - system.CoreTiming().GetGlobalTimeNs().count() + 100000000); - if (!thread_reservation.Succeeded()) { - LOG_ERROR(Kernel_SVC, "Could not reserve a new thread"); - return ResultLimitReached; - } - - // Create the thread. - KThread* thread = KThread::Create(kernel); - if (!thread) { - LOG_ERROR(Kernel_SVC, "Unable to create new threads. Thread creation limit reached."); - return ResultOutOfResource; - } - SCOPE_EXIT({ thread->Close(); }); - - // Initialize the thread. - { - KScopedLightLock lk{process.GetStateLock()}; - R_TRY(KThread::InitializeUserThread(system, thread, entry_point, arg, stack_bottom, - priority, core_id, &process)); - } - - // Set the thread name for debugging purposes. - thread->SetName(fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *out_handle)); - - // Commit the thread reservation. - thread_reservation.Commit(); - - // Register the new thread. - KThread::Register(kernel, thread); - - // Add the thread to the handle table. - R_TRY(process.GetHandleTable().Add(out_handle, thread)); - - return ResultSuccess; -} - -static Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority, - u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) { - return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id); -} - -/// Starts the thread for the provided handle -static Result StartThread(Core::System& system, Handle thread_handle) { - LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Try to start the thread. - R_TRY(thread->Run()); - - // If we succeeded, persist a reference to the thread. - thread->Open(); - system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe()); - - return ResultSuccess; -} - -static Result StartThread32(Core::System& system, Handle thread_handle) { - return StartThread(system, thread_handle); -} - -/// Called when a thread exits -static void ExitThread(Core::System& system) { - LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); - - auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); - system.GlobalSchedulerContext().RemoveThread(current_thread); - current_thread->Exit(); - system.Kernel().UnregisterInUseObject(current_thread); -} - -static void ExitThread32(Core::System& system) { - ExitThread(system); -} - -/// Sleep the current thread -static void SleepThread(Core::System& system, s64 nanoseconds) { - auto& kernel = system.Kernel(); - const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); - - LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); - - // When the input tick is positive, sleep. - if (nanoseconds > 0) { - // Convert the timeout from nanoseconds to ticks. - // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... - - // Sleep. - // NOTE: Nintendo does not check the result of this sleep. - static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); - } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { - KScheduler::YieldWithoutCoreMigration(kernel); - } else if (yield_type == Svc::YieldType::WithCoreMigration) { - KScheduler::YieldWithCoreMigration(kernel); - } else if (yield_type == Svc::YieldType::ToAnyThread) { - KScheduler::YieldToAnyThread(kernel); - } else { - // Nintendo does nothing at all if an otherwise invalid value is passed. - ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); - } -} - -static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) { - const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32)); - SleepThread(system, nanoseconds); -} - -/// Wait process wide key atomic -static Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag, - s64 timeout_ns) { - LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address, - cv_key, tag, timeout_ns); - - // Validate input. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(s32))) { - LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address); - return ResultInvalidAddress; - } - - // Convert timeout from nanoseconds to ticks. - s64 timeout{}; - if (timeout_ns > 0) { - const s64 offset_tick(timeout_ns); - if (offset_tick > 0) { - timeout = offset_tick + 2; - if (timeout <= 0) { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = timeout_ns; - } - - // Wait on the condition variable. - return system.Kernel().CurrentProcess()->WaitConditionVariable( - address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout); -} - -static Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, - u32 timeout_ns_low, u32 timeout_ns_high) { - const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); - return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns); -} - -/// Signal process wide key -static void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count) { - LOG_TRACE(Kernel_SVC, "called, cv_key=0x{:X}, count=0x{:08X}", cv_key, count); - - // Signal the condition variable. - return system.Kernel().CurrentProcess()->SignalConditionVariable( - Common::AlignDown(cv_key, sizeof(u32)), count); -} - -static void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count) { - SignalProcessWideKey(system, cv_key, count); -} - -namespace { - -constexpr bool IsValidSignalType(Svc::SignalType type) { - switch (type) { - case Svc::SignalType::Signal: - case Svc::SignalType::SignalAndIncrementIfEqual: - case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: - return true; - default: - return false; - } -} - -constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) { - switch (type) { - case Svc::ArbitrationType::WaitIfLessThan: - case Svc::ArbitrationType::DecrementAndWaitIfLessThan: - case Svc::ArbitrationType::WaitIfEqual: - return true; - default: - return false; - } -} - -} // namespace - -// Wait for an address (via Address Arbiter) -static Result WaitForAddress(Core::System& system, VAddr address, Svc::ArbitrationType arb_type, - s32 value, s64 timeout_ns) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}", - address, arb_type, value, timeout_ns); - - // Validate input. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(s32))) { - LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address); - return ResultInvalidAddress; - } - if (!IsValidArbitrationType(arb_type)) { - LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type); - return ResultInvalidEnumValue; - } - - // Convert timeout from nanoseconds to ticks. - s64 timeout{}; - if (timeout_ns > 0) { - const s64 offset_tick(timeout_ns); - if (offset_tick > 0) { - timeout = offset_tick + 2; - if (timeout <= 0) { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = timeout_ns; - } - - return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout); -} - -static Result WaitForAddress32(Core::System& system, u32 address, Svc::ArbitrationType arb_type, - s32 value, u32 timeout_ns_low, u32 timeout_ns_high) { - const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); - return WaitForAddress(system, address, arb_type, value, timeout); -} - -// Signals to an address (via Address Arbiter) -static Result SignalToAddress(Core::System& system, VAddr address, Svc::SignalType signal_type, - s32 value, s32 count) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}", - address, signal_type, value, count); - - // Validate input. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(s32))) { - LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address); - return ResultInvalidAddress; - } - if (!IsValidSignalType(signal_type)) { - LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type); - return ResultInvalidEnumValue; - } - - return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value, - count); -} - -static void SynchronizePreemptionState(Core::System& system) { - auto& kernel = system.Kernel(); - - // Lock the scheduler. - KScopedSchedulerLock sl{kernel}; - - // If the current thread is pinned, unpin it. - KProcess* cur_process = system.Kernel().CurrentProcess(); - const auto core_id = GetCurrentCoreId(kernel); - - if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(kernel)) { - // Clear the current thread's interrupt flag. - GetCurrentThread(kernel).ClearInterruptFlag(); - - // Unpin the current thread. - cur_process->UnpinCurrentThread(core_id); - } -} - -static Result SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type, - s32 value, s32 count) { - return SignalToAddress(system, address, signal_type, value, count); -} - -static void KernelDebug([[maybe_unused]] Core::System& system, - [[maybe_unused]] u32 kernel_debug_type, [[maybe_unused]] u64 param1, - [[maybe_unused]] u64 param2, [[maybe_unused]] u64 param3) { - // Intentionally do nothing, as this does nothing in released kernel binaries. -} - -static void ChangeKernelTraceState([[maybe_unused]] Core::System& system, - [[maybe_unused]] u32 trace_state) { - // Intentionally do nothing, as this does nothing in released kernel binaries. -} - -/// This returns the total CPU ticks elapsed since the CPU was powered-on -static u64 GetSystemTick(Core::System& system) { - LOG_TRACE(Kernel_SVC, "called"); - - auto& core_timing = system.CoreTiming(); - - // Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick) - const u64 result{core_timing.GetClockTicks()}; - - if (!system.Kernel().IsMulticore()) { - core_timing.AddTicks(400U); - } - - return result; -} - -static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) { - const auto time = GetSystemTick(system); - *time_low = static_cast<u32>(time); - *time_high = static_cast<u32>(time >> 32); -} - -/// Close a handle -static Result CloseHandle(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); - - // Remove the handle. - R_UNLESS(system.Kernel().CurrentProcess()->GetHandleTable().Remove(handle), - ResultInvalidHandle); - - return ResultSuccess; -} - -static Result CloseHandle32(Core::System& system, Handle handle) { - return CloseHandle(system, handle); -} - -/// Clears the signaled state of an event or process. -static Result ResetSignal(Core::System& system, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); - - // Get the current handle table. - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - - // Try to reset as readable event. - { - KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(handle); - if (readable_event.IsNotNull()) { - return readable_event->Reset(); - } - } - - // Try to reset as process. - { - KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); - if (process.IsNotNull()) { - return process->Reset(); - } - } - - LOG_ERROR(Kernel_SVC, "invalid handle (0x{:08X})", handle); - - return ResultInvalidHandle; -} - -static Result ResetSignal32(Core::System& system, Handle handle) { - return ResetSignal(system, handle); -} - -namespace { - -constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) { - switch (perm) { - case MemoryPermission::None: - case MemoryPermission::Read: - case MemoryPermission::ReadWrite: - return true; - default: - return false; - } -} - -} // Anonymous namespace - -/// Creates a TransferMemory object -static Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size, - MemoryPermission map_perm) { - auto& kernel = system.Kernel(); - - // Validate the size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the permissions. - R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); - - // Get the current process and handle table. - auto& process = *kernel.CurrentProcess(); - auto& handle_table = process.GetHandleTable(); - - // Reserve a new transfer memory from the process resource limit. - KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(), - LimitableResource::TransferMemoryCountMax); - R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached); - - // Create the transfer memory. - KTransferMemory* trmem = KTransferMemory::Create(kernel); - R_UNLESS(trmem != nullptr, ResultOutOfResource); - - // Ensure the only reference is in the handle table when we're done. - SCOPE_EXIT({ trmem->Close(); }); - - // Ensure that the region is in range. - R_UNLESS(process.PageTable().Contains(address, size), ResultInvalidCurrentMemory); - - // Initialize the transfer memory. - R_TRY(trmem->Initialize(address, size, map_perm)); - - // Commit the reservation. - trmem_reservation.Commit(); - - // Register the transfer memory. - KTransferMemory::Register(kernel, trmem); - - // Add the transfer memory to the handle table. - R_TRY(handle_table.Add(out, trmem)); - - return ResultSuccess; -} - -static Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size, - MemoryPermission map_perm) { - return CreateTransferMemory(system, out, address, size, map_perm); -} - -static Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, - u64* out_affinity_mask) { - LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Get the core mask. - R_TRY(thread->GetCoreMask(out_core_id, out_affinity_mask)); - - return ResultSuccess; -} - -static Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, - u32* out_affinity_mask_low, u32* out_affinity_mask_high) { - u64 out_affinity_mask{}; - const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask); - *out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32); - *out_affinity_mask_low = static_cast<u32>(out_affinity_mask); - return result; -} - -static Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, - u64 affinity_mask) { - // Determine the core id/affinity mask. - if (core_id == IdealCoreUseProcessValue) { - core_id = system.Kernel().CurrentProcess()->GetIdealCoreId(); - affinity_mask = (1ULL << core_id); - } else { - // Validate the affinity mask. - const u64 process_core_mask = system.Kernel().CurrentProcess()->GetCoreMask(); - R_UNLESS((affinity_mask | process_core_mask) == process_core_mask, ResultInvalidCoreId); - R_UNLESS(affinity_mask != 0, ResultInvalidCombination); - - // Validate the core id. - if (IsValidVirtualCoreId(core_id)) { - R_UNLESS(((1ULL << core_id) & affinity_mask) != 0, ResultInvalidCombination); - } else { - R_UNLESS(core_id == IdealCoreNoUpdate || core_id == IdealCoreDontCare, - ResultInvalidCoreId); - } - } - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Set the core mask. - R_TRY(thread->SetCoreMask(core_id, affinity_mask)); - - return ResultSuccess; -} - -static Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, - u32 affinity_mask_low, u32 affinity_mask_high) { - const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32); - return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask); -} - -static Result SignalEvent(Core::System& system, Handle event_handle) { - LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); - - // Get the current handle table. - const KHandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - - // Get the event. - KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); - R_UNLESS(event.IsNotNull(), ResultInvalidHandle); - - return event->Signal(); -} - -static Result SignalEvent32(Core::System& system, Handle event_handle) { - return SignalEvent(system, event_handle); -} - -static Result ClearEvent(Core::System& system, Handle event_handle) { - LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); - - // Get the current handle table. - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - - // Try to clear the writable event. - { - KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); - if (event.IsNotNull()) { - return event->Clear(); - } - } - - // Try to clear the readable event. - { - KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(event_handle); - if (readable_event.IsNotNull()) { - return readable_event->Clear(); - } - } - - LOG_ERROR(Kernel_SVC, "Event handle does not exist, event_handle=0x{:08X}", event_handle); - - return ResultInvalidHandle; -} - -static Result ClearEvent32(Core::System& system, Handle event_handle) { - return ClearEvent(system, event_handle); -} - -static Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) { - LOG_DEBUG(Kernel_SVC, "called"); - - // Get the kernel reference and handle table. - auto& kernel = system.Kernel(); - auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - - // Reserve a new event from the process resource limit - KScopedResourceReservation event_reservation(kernel.CurrentProcess(), - LimitableResource::EventCountMax); - R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); - - // Create a new event. - KEvent* event = KEvent::Create(kernel); - R_UNLESS(event != nullptr, ResultOutOfResource); - - // Initialize the event. - event->Initialize(kernel.CurrentProcess()); - - // Commit the thread reservation. - event_reservation.Commit(); - - // Ensure that we clean up the event (and its only references are handle table) on function end. - SCOPE_EXIT({ - event->GetReadableEvent().Close(); - event->Close(); - }); - - // Register the event. - KEvent::Register(kernel, event); - - // Add the event to the handle table. - R_TRY(handle_table.Add(out_write, event)); - - // Ensure that we maintaing a clean handle state on exit. - auto handle_guard = SCOPE_GUARD({ handle_table.Remove(*out_write); }); - - // Add the readable event to the handle table. - R_TRY(handle_table.Add(out_read, std::addressof(event->GetReadableEvent()))); - - // We succeeded. - handle_guard.Cancel(); - return ResultSuccess; -} - -static Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) { - return CreateEvent(system, out_write, out_read); -} - -static Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", - process_handle); - return ResultInvalidHandle; - } - - const auto info_type = static_cast<ProcessInfoType>(type); - if (info_type != ProcessInfoType::ProcessState) { - LOG_ERROR(Kernel_SVC, "Expected info_type to be ProcessState but got {} instead", type); - return ResultInvalidEnumValue; - } - - *out = static_cast<u64>(process->GetState()); - return ResultSuccess; -} - -static Result CreateResourceLimit(Core::System& system, Handle* out_handle) { - LOG_DEBUG(Kernel_SVC, "called"); - - // Create a new resource limit. - auto& kernel = system.Kernel(); - KResourceLimit* resource_limit = KResourceLimit::Create(kernel); - R_UNLESS(resource_limit != nullptr, ResultOutOfResource); - - // Ensure we don't leak a reference to the limit. - SCOPE_EXIT({ resource_limit->Close(); }); - - // Initialize the resource limit. - resource_limit->Initialize(&system.CoreTiming()); - - // Register the limit. - KResourceLimit::Register(kernel, resource_limit); - - // Add the limit to the handle table. - R_TRY(kernel.CurrentProcess()->GetHandleTable().Add(out_handle, resource_limit)); - - return ResultSuccess; -} - -static Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value, - Handle resource_limit_handle, LimitableResource which) { - LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, - which); - - // Validate the resource. - R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); - - // Get the resource limit. - auto& kernel = system.Kernel(); - KScopedAutoObject resource_limit = - kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); - R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); - - // Get the limit value. - *out_limit_value = resource_limit->GetLimitValue(which); - - return ResultSuccess; -} - -static Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value, - Handle resource_limit_handle, LimitableResource which) { - LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, - which); - - // Validate the resource. - R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); - - // Get the resource limit. - auto& kernel = system.Kernel(); - KScopedAutoObject resource_limit = - kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); - R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); - - // Get the current value. - *out_current_value = resource_limit->GetCurrentValue(which); - - return ResultSuccess; -} - -static Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle, - LimitableResource which, u64 limit_value) { - LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}, limit_value={}", - resource_limit_handle, which, limit_value); - - // Validate the resource. - R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); - - // Get the resource limit. - auto& kernel = system.Kernel(); - KScopedAutoObject resource_limit = - kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); - R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); - - // Set the limit value. - R_TRY(resource_limit->SetLimitValue(which, limit_value)); - - return ResultSuccess; -} - -static Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids, - u32 out_process_ids_size) { - LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}", - out_process_ids, out_process_ids_size); - - // If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail. - if ((out_process_ids_size & 0xF0000000) != 0) { - LOG_ERROR(Kernel_SVC, - "Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}", - out_process_ids_size); - return ResultOutOfRange; - } - - const auto& kernel = system.Kernel(); - const auto total_copy_size = out_process_ids_size * sizeof(u64); - - if (out_process_ids_size > 0 && !kernel.CurrentProcess()->PageTable().IsInsideAddressSpace( - out_process_ids, total_copy_size)) { - LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", - out_process_ids, out_process_ids + total_copy_size); - return ResultInvalidCurrentMemory; - } - - auto& memory = system.Memory(); - const auto& process_list = kernel.GetProcessList(); - const auto num_processes = process_list.size(); - const auto copy_amount = std::min(std::size_t{out_process_ids_size}, num_processes); - - for (std::size_t i = 0; i < copy_amount; ++i) { - memory.Write64(out_process_ids, process_list[i]->GetProcessID()); - out_process_ids += sizeof(u64); - } - - *out_num_processes = static_cast<u32>(num_processes); - return ResultSuccess; -} - -static Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, - u32 out_thread_ids_size, Handle debug_handle) { - // TODO: Handle this case when debug events are supported. - UNIMPLEMENTED_IF(debug_handle != InvalidHandle); - - LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}", - out_thread_ids, out_thread_ids_size); - - // If the size is negative or larger than INT32_MAX / sizeof(u64) - if ((out_thread_ids_size & 0xF0000000) != 0) { - LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. size={}", - out_thread_ids_size); - return ResultOutOfRange; - } - - auto* const current_process = system.Kernel().CurrentProcess(); - const auto total_copy_size = out_thread_ids_size * sizeof(u64); - - if (out_thread_ids_size > 0 && - !current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) { - LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", - out_thread_ids, out_thread_ids + total_copy_size); - return ResultInvalidCurrentMemory; - } - - auto& memory = system.Memory(); - const auto& thread_list = current_process->GetThreadList(); - const auto num_threads = thread_list.size(); - const auto copy_amount = std::min(std::size_t{out_thread_ids_size}, num_threads); - - auto list_iter = thread_list.cbegin(); - for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) { - memory.Write64(out_thread_ids, (*list_iter)->GetThreadID()); - out_thread_ids += sizeof(u64); - } - - *out_num_threads = static_cast<u32>(num_threads); - return ResultSuccess; -} - -static Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, - u64 size) { - // Validate address/size. - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); - R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); - - // Get the process from its handle. - KScopedAutoObject process = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); - R_UNLESS(process.IsNotNull(), ResultInvalidHandle); - - // Verify the region is within range. - auto& page_table = process->PageTable(); - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Perform the operation. - R_RETURN(system.Memory().FlushDataCache(*process, address, size)); -} - -namespace { struct FunctionDef { using Func = void(Core::System&); @@ -2699,6 +18,7 @@ struct FunctionDef { Func* func; const char* name; }; + } // namespace static const FunctionDef SVC_Table_32[] = { diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index 13f061b83..b599f9a3d 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -4,6 +4,8 @@ #pragma once #include "common/common_types.h" +#include "core/hle/kernel/svc_types.h" +#include "core/hle/result.h" namespace Core { class System; @@ -13,4 +15,158 @@ namespace Kernel::Svc { void Call(Core::System& system, u32 immediate); +Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size); +Result SetMemoryPermission(Core::System& system, VAddr address, u64 size, MemoryPermission perm); +Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, u32 attr); +Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size); +Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size); +Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + VAddr query_address); +void ExitProcess(Core::System& system); +Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, + VAddr stack_bottom, u32 priority, s32 core_id); +Result StartThread(Core::System& system, Handle thread_handle); +void ExitThread(Core::System& system); +void SleepThread(Core::System& system, s64 nanoseconds); +Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle); +Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority); +Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, + u64* out_affinity_mask); +Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, + u64 affinity_mask); +u32 GetCurrentProcessorNumber(Core::System& system); +Result SignalEvent(Core::System& system, Handle event_handle); +Result ClearEvent(Core::System& system, Handle event_handle); +Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size, + MemoryPermission map_perm); +Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size); +Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size, + MemoryPermission map_perm); +Result CloseHandle(Core::System& system, Handle handle); +Result ResetSignal(Core::System& system, Handle handle); +Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, s32 num_handles, + s64 nano_seconds); +Result CancelSynchronization(Core::System& system, Handle handle); +Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag); +Result ArbitrateUnlock(Core::System& system, VAddr address); +Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag, + s64 timeout_ns); +void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count); +u64 GetSystemTick(Core::System& system); +Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address); +Result SendSyncRequest(Core::System& system, Handle handle); +Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle); +Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle); +void Break(Core::System& system, u32 reason, u64 info1, u64 info2); +void OutputDebugString(Core::System& system, VAddr address, u64 len); +Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle, u64 info_sub_id); +Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size); +Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size); +Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value, + Handle resource_limit_handle, LimitableResource which); +Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value, + Handle resource_limit_handle, LimitableResource which); +Result SetThreadActivity(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity); +Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle); +Result WaitForAddress(Core::System& system, VAddr address, ArbitrationType arb_type, s32 value, + s64 timeout_ns); +Result SignalToAddress(Core::System& system, VAddr address, SignalType signal_type, s32 value, + s32 count); +void SynchronizePreemptionState(Core::System& system); +void KernelDebug(Core::System& system, u32 kernel_debug_type, u64 param1, u64 param2, u64 param3); +void ChangeKernelTraceState(Core::System& system, u32 trace_state); +Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u32 is_light, + u64 name); +Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles, s32 num_handles, + Handle reply_target, s64 timeout_ns); +Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read); +Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size); +Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation, + VAddr address, size_t size, MemoryPermission perm); +Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids, + u32 out_process_ids_size); +Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, + u32 out_thread_ids_size, Handle debug_handle); +Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address, + u64 size, MemoryPermission perm); +Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size); +Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size); +Result QueryProcessMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + Handle process_handle, VAddr address); +Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size); +Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size); +Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type); +Result CreateResourceLimit(Core::System& system, Handle* out_handle); +Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle, + LimitableResource which, u64 limit_value); + +// + +Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size); +Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, u32 attr); +Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size); +Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size); +Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address, + u32 query_address); +void ExitProcess32(Core::System& system); +Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority, u32 entry_point, + u32 arg, u32 stack_top, s32 processor_id); +Result StartThread32(Core::System& system, Handle thread_handle); +void ExitThread32(Core::System& system); +void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high); +Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle); +Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority); +Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, + u32* out_affinity_mask_low, u32* out_affinity_mask_high); +Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, + u32 affinity_mask_low, u32 affinity_mask_high); +u32 GetCurrentProcessorNumber32(Core::System& system); +Result SignalEvent32(Core::System& system, Handle event_handle); +Result ClearEvent32(Core::System& system, Handle event_handle); +Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size, + MemoryPermission map_perm); +Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size); +Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size, + MemoryPermission map_perm); +Result CloseHandle32(Core::System& system, Handle handle); +Result ResetSignal32(Core::System& system, Handle handle); +Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, + s32 num_handles, u32 timeout_high, s32* index); +Result CancelSynchronization32(Core::System& system, Handle handle); +Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag); +Result ArbitrateUnlock32(Core::System& system, u32 address); +Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, + u32 timeout_ns_low, u32 timeout_ns_high); +void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count); +void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high); +Result ConnectToNamedPort32(Core::System& system, Handle* out_handle, u32 port_name_address); +Result SendSyncRequest32(Core::System& system, Handle handle); +Result GetProcessId32(Core::System& system, u32* out_process_id_low, u32* out_process_id_high, + Handle handle); +Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high, + Handle thread_handle); +void Break32(Core::System& system, u32 reason, u32 info1, u32 info2); +void OutputDebugString32(Core::System& system, u32 address, u32 len); +Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, + u32 info_id, u32 handle, u32 sub_id_high); +Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size); +Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size); +Result SetThreadActivity32(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity); +Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle); +Result WaitForAddress32(Core::System& system, u32 address, ArbitrationType arb_type, s32 value, + u32 timeout_ns_low, u32 timeout_ns_high); +Result SignalToAddress32(Core::System& system, u32 address, SignalType signal_type, s32 value, + s32 count); +Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read); +Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size); +Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation, + u64 address, u64 size, MemoryPermission perm); +Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, u64 size); + } // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_activity.cpp b/src/core/hle/kernel/svc/svc_activity.cpp new file mode 100644 index 000000000..8774a5c98 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_activity.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel::Svc { + +/// Sets the thread activity +Result SetThreadActivity(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle, + thread_activity); + + // Validate the activity. + constexpr auto IsValidThreadActivity = [](ThreadActivity activity) { + return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused; + }; + R_UNLESS(IsValidThreadActivity(thread_activity), ResultInvalidEnumValue); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Check that the activity is being set on a non-current thread for the current process. + R_UNLESS(thread->GetOwnerProcess() == system.Kernel().CurrentProcess(), ResultInvalidHandle); + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(system.Kernel()), ResultBusy); + + // Set the activity. + R_TRY(thread->SetActivity(thread_activity)); + + return ResultSuccess; +} + +Result SetThreadActivity32(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity) { + return SetThreadActivity(system, thread_handle, thread_activity); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp new file mode 100644 index 000000000..842107726 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_types.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidSignalType(Svc::SignalType type) { + switch (type) { + case Svc::SignalType::Signal: + case Svc::SignalType::SignalAndIncrementIfEqual: + case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: + return true; + default: + return false; + } +} + +constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) { + switch (type) { + case Svc::ArbitrationType::WaitIfLessThan: + case Svc::ArbitrationType::DecrementAndWaitIfLessThan: + case Svc::ArbitrationType::WaitIfEqual: + return true; + default: + return false; + } +} + +} // namespace + +// Wait for an address (via Address Arbiter) +Result WaitForAddress(Core::System& system, VAddr address, ArbitrationType arb_type, s32 value, + s64 timeout_ns) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}", + address, arb_type, value, timeout_ns); + + // Validate input. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + if (!IsValidArbitrationType(arb_type)) { + LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type); + return ResultInvalidEnumValue; + } + + // Convert timeout from nanoseconds to ticks. + s64 timeout{}; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout); +} + +Result WaitForAddress32(Core::System& system, u32 address, ArbitrationType arb_type, s32 value, + u32 timeout_ns_low, u32 timeout_ns_high) { + const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); + return WaitForAddress(system, address, arb_type, value, timeout); +} + +// Signals to an address (via Address Arbiter) +Result SignalToAddress(Core::System& system, VAddr address, SignalType signal_type, s32 value, + s32 count) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}", + address, signal_type, value, count); + + // Validate input. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + if (!IsValidSignalType(signal_type)) { + LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type); + return ResultInvalidEnumValue; + } + + return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value, + count); +} + +Result SignalToAddress32(Core::System& system, u32 address, SignalType signal_type, s32 value, + s32 count) { + return SignalToAddress(system, address, signal_type, value, count); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_address_translation.cpp b/src/core/hle/kernel/svc/svc_address_translation.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_address_translation.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_cache.cpp b/src/core/hle/kernel/svc/svc_cache.cpp new file mode 100644 index 000000000..42167d35b --- /dev/null +++ b/src/core/hle/kernel/svc/svc_cache.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_types.h" + +namespace Kernel::Svc { + +Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, u64 size) { + // Validate address/size. + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); + R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); + + // Get the process from its handle. + KScopedAutoObject process = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), ResultInvalidHandle); + + // Verify the region is within range. + auto& page_table = process->PageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Perform the operation. + R_RETURN(system.Memory().FlushDataCache(*process, address, size)); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_code_memory.cpp b/src/core/hle/kernel/svc/svc_code_memory.cpp new file mode 100644 index 000000000..4cb21e101 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_code_memory.cpp @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_code_memory.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidMapCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::ReadWrite; +} + +constexpr bool IsValidMapToOwnerCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::Read || perm == MemoryPermission::ReadExecute; +} + +constexpr bool IsValidUnmapCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::None; +} + +constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::None; +} + +} // namespace + +Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, size=0x{:X}", address, size); + + // Get kernel instance. + auto& kernel = system.Kernel(); + + // Validate address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Create the code memory. + + KCodeMemory* code_mem = KCodeMemory::Create(kernel); + R_UNLESS(code_mem != nullptr, ResultOutOfResource); + + // Verify that the region is in range. + R_UNLESS(system.CurrentProcess()->PageTable().Contains(address, size), + ResultInvalidCurrentMemory); + + // Initialize the code memory. + R_TRY(code_mem->Initialize(system.DeviceMemory(), address, size)); + + // Register the code memory. + KCodeMemory::Register(kernel, code_mem); + + // Add the code memory to the handle table. + R_TRY(system.CurrentProcess()->GetHandleTable().Add(out, code_mem)); + + code_mem->Close(); + + return ResultSuccess; +} + +Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size) { + return CreateCodeMemory(system, out, address, size); +} + +Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation, + VAddr address, size_t size, MemoryPermission perm) { + + LOG_TRACE(Kernel_SVC, + "called, code_memory_handle=0x{:X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, " + "permission=0x{:X}", + code_memory_handle, operation, address, size, perm); + + // Validate the address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Get the code memory from its handle. + KScopedAutoObject code_mem = + system.CurrentProcess()->GetHandleTable().GetObject<KCodeMemory>(code_memory_handle); + R_UNLESS(code_mem.IsNotNull(), ResultInvalidHandle); + + // NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. + // This enables homebrew usage of these SVCs for JIT. + + // Perform the operation. + switch (static_cast<CodeMemoryOperation>(operation)) { + case CodeMemoryOperation::Map: { + // Check that the region is in range. + R_UNLESS( + system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidMapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Map the memory. + R_TRY(code_mem->Map(address, size)); + } break; + case CodeMemoryOperation::Unmap: { + // Check that the region is in range. + R_UNLESS( + system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Unmap the memory. + R_TRY(code_mem->Unmap(address, size)); + } break; + case CodeMemoryOperation::MapToOwner: { + // Check that the region is in range. + R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, + KMemoryState::GeneratedCode), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Map the memory to its owner. + R_TRY(code_mem->MapToOwner(address, size, perm)); + } break; + case CodeMemoryOperation::UnmapFromOwner: { + // Check that the region is in range. + R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, + KMemoryState::GeneratedCode), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Unmap the memory from its owner. + R_TRY(code_mem->UnmapFromOwner(address, size)); + } break; + default: + return ResultInvalidEnumValue; + } + + return ResultSuccess; +} + +Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation, + u64 address, u64 size, MemoryPermission perm) { + return ControlCodeMemory(system, code_memory_handle, operation, address, size, perm); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp new file mode 100644 index 000000000..d6cfc87c5 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel::Svc { + +/// Wait process wide key atomic +Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag, + s64 timeout_ns) { + LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address, + cv_key, tag, timeout_ns); + + // Validate input. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + + // Convert timeout from nanoseconds to ticks. + s64 timeout{}; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + // Wait on the condition variable. + return system.Kernel().CurrentProcess()->WaitConditionVariable( + address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout); +} + +Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, + u32 timeout_ns_low, u32 timeout_ns_high) { + const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); + return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns); +} + +/// Signal process wide key +void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count) { + LOG_TRACE(Kernel_SVC, "called, cv_key=0x{:X}, count=0x{:08X}", cv_key, count); + + // Signal the condition variable. + return system.Kernel().CurrentProcess()->SignalConditionVariable( + Common::AlignDown(cv_key, sizeof(u32)), count); +} + +void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count) { + SignalProcessWideKey(system, cv_key, count); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_debug.cpp b/src/core/hle/kernel/svc/svc_debug.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_debug.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp new file mode 100644 index 000000000..486e62cc4 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/svc.h" +#include "core/memory.h" + +namespace Kernel::Svc { + +/// Used to output a message on a debug hardware unit - does nothing on a retail unit +void OutputDebugString(Core::System& system, VAddr address, u64 len) { + if (len == 0) { + return; + } + + std::string str(len, '\0'); + system.Memory().ReadBlock(address, str.data(), str.size()); + LOG_DEBUG(Debug_Emulated, "{}", str); +} + +void OutputDebugString32(Core::System& system, u32 address, u32 len) { + OutputDebugString(system, address, len); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_device_address_space.cpp b/src/core/hle/kernel/svc/svc_device_address_space.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_device_address_space.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_event.cpp b/src/core/hle/kernel/svc/svc_event.cpp new file mode 100644 index 000000000..885f02f50 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_event.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +Result SignalEvent(Core::System& system, Handle event_handle) { + LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + + // Get the current handle table. + const KHandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + + // Get the event. + KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); + R_UNLESS(event.IsNotNull(), ResultInvalidHandle); + + return event->Signal(); +} + +Result SignalEvent32(Core::System& system, Handle event_handle) { + return SignalEvent(system, event_handle); +} + +Result ClearEvent(Core::System& system, Handle event_handle) { + LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + + // Get the current handle table. + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + + // Try to clear the writable event. + { + KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); + if (event.IsNotNull()) { + return event->Clear(); + } + } + + // Try to clear the readable event. + { + KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(event_handle); + if (readable_event.IsNotNull()) { + return readable_event->Clear(); + } + } + + LOG_ERROR(Kernel_SVC, "Event handle does not exist, event_handle=0x{:08X}", event_handle); + + return ResultInvalidHandle; +} + +Result ClearEvent32(Core::System& system, Handle event_handle) { + return ClearEvent(system, event_handle); +} + +Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) { + LOG_DEBUG(Kernel_SVC, "called"); + + // Get the kernel reference and handle table. + auto& kernel = system.Kernel(); + auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + + // Reserve a new event from the process resource limit + KScopedResourceReservation event_reservation(kernel.CurrentProcess(), + LimitableResource::EventCountMax); + R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); + + // Create a new event. + KEvent* event = KEvent::Create(kernel); + R_UNLESS(event != nullptr, ResultOutOfResource); + + // Initialize the event. + event->Initialize(kernel.CurrentProcess()); + + // Commit the thread reservation. + event_reservation.Commit(); + + // Ensure that we clean up the event (and its only references are handle table) on function end. + SCOPE_EXIT({ + event->GetReadableEvent().Close(); + event->Close(); + }); + + // Register the event. + KEvent::Register(kernel, event); + + // Add the event to the handle table. + R_TRY(handle_table.Add(out_write, event)); + + // Ensure that we maintaing a clean handle state on exit. + auto handle_guard = SCOPE_GUARD({ handle_table.Remove(*out_write); }); + + // Add the readable event to the handle table. + R_TRY(handle_table.Add(out_read, std::addressof(event->GetReadableEvent()))); + + // We succeeded. + handle_guard.Cancel(); + return ResultSuccess; +} + +Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) { + return CreateEvent(system, out_write, out_read); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp new file mode 100644 index 000000000..fb9f133c1 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_exception.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/debugger/debugger.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_types.h" +#include "core/memory.h" +#include "core/reporter.h" + +namespace Kernel::Svc { + +/// Break program execution +void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { + BreakReason break_reason = + static_cast<BreakReason>(reason & ~static_cast<u32>(BreakReason::NotificationOnlyFlag)); + bool notification_only = (reason & static_cast<u32>(BreakReason::NotificationOnlyFlag)) != 0; + + bool has_dumped_buffer{}; + std::vector<u8> debug_buffer; + + const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { + if (sz == 0 || addr == 0 || has_dumped_buffer) { + return; + } + + auto& memory = system.Memory(); + + // This typically is an error code so we're going to assume this is the case + if (sz == sizeof(u32)) { + LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", memory.Read32(addr)); + } else { + // We don't know what's in here so we'll hexdump it + debug_buffer.resize(sz); + memory.ReadBlock(addr, debug_buffer.data(), sz); + std::string hexdump; + for (std::size_t i = 0; i < debug_buffer.size(); i++) { + hexdump += fmt::format("{:02X} ", debug_buffer[i]); + if (i != 0 && i % 16 == 0) { + hexdump += '\n'; + } + } + LOG_CRITICAL(Debug_Emulated, "debug_buffer=\n{}", hexdump); + } + has_dumped_buffer = true; + }; + switch (break_reason) { + case BreakReason::Panic: + LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1, + info2); + handle_debug_buffer(info1, info2); + break; + case BreakReason::Assert: + LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}", + info1, info2); + handle_debug_buffer(info1, info2); + break; + case BreakReason::User: + LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2); + handle_debug_buffer(info1, info2); + break; + case BreakReason::PreLoadDll: + LOG_INFO(Debug_Emulated, + "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakReason::PostLoadDll: + LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakReason::PreUnloadDll: + LOG_INFO(Debug_Emulated, + "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakReason::PostUnloadDll: + LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}", + info1, info2); + break; + case BreakReason::CppException: + LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered."); + break; + default: + LOG_WARNING( + Debug_Emulated, + "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + handle_debug_buffer(info1, info2); + break; + } + + system.GetReporter().SaveSvcBreakReport(reason, notification_only, info1, info2, + has_dumped_buffer ? std::make_optional(debug_buffer) + : std::nullopt); + + if (!notification_only) { + LOG_CRITICAL( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + + handle_debug_buffer(info1, info2); + + auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); + const auto thread_processor_id = current_thread->GetActiveCore(); + system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); + } + + if (system.DebuggerEnabled()) { + auto* thread = system.Kernel().GetCurrentEmuThread(); + system.GetDebugger().NotifyThreadStopped(thread); + thread->RequestSuspend(Kernel::SuspendType::Debug); + } +} + +void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { + Break(system, reason, info1, info2); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp new file mode 100644 index 000000000..df5dd85a4 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_info.cpp @@ -0,0 +1,282 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Gets system/memory information for the current process +Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle, u64 info_sub_id) { + LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, + info_sub_id, handle); + + const auto info_id_type = static_cast<InfoType>(info_id); + + switch (info_id_type) { + case InfoType::CoreMask: + case InfoType::PriorityMask: + case InfoType::AliasRegionAddress: + case InfoType::AliasRegionSize: + case InfoType::HeapRegionAddress: + case InfoType::HeapRegionSize: + case InfoType::AslrRegionAddress: + case InfoType::AslrRegionSize: + case InfoType::StackRegionAddress: + case InfoType::StackRegionSize: + case InfoType::TotalMemorySize: + case InfoType::UsedMemorySize: + case InfoType::SystemResourceSizeTotal: + case InfoType::SystemResourceSizeUsed: + case InfoType::ProgramId: + case InfoType::UserExceptionContextAddress: + case InfoType::TotalNonSystemMemorySize: + case InfoType::UsedNonSystemMemorySize: + case InfoType::IsApplication: + case InfoType::FreeThreadCount: { + if (info_sub_id != 0) { + LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, + info_sub_id); + return ResultInvalidEnumValue; + } + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}", + info_id, info_sub_id, handle); + return ResultInvalidHandle; + } + + switch (info_id_type) { + case InfoType::CoreMask: + *result = process->GetCoreMask(); + return ResultSuccess; + + case InfoType::PriorityMask: + *result = process->GetPriorityMask(); + return ResultSuccess; + + case InfoType::AliasRegionAddress: + *result = process->PageTable().GetAliasRegionStart(); + return ResultSuccess; + + case InfoType::AliasRegionSize: + *result = process->PageTable().GetAliasRegionSize(); + return ResultSuccess; + + case InfoType::HeapRegionAddress: + *result = process->PageTable().GetHeapRegionStart(); + return ResultSuccess; + + case InfoType::HeapRegionSize: + *result = process->PageTable().GetHeapRegionSize(); + return ResultSuccess; + + case InfoType::AslrRegionAddress: + *result = process->PageTable().GetAliasCodeRegionStart(); + return ResultSuccess; + + case InfoType::AslrRegionSize: + *result = process->PageTable().GetAliasCodeRegionSize(); + return ResultSuccess; + + case InfoType::StackRegionAddress: + *result = process->PageTable().GetStackRegionStart(); + return ResultSuccess; + + case InfoType::StackRegionSize: + *result = process->PageTable().GetStackRegionSize(); + return ResultSuccess; + + case InfoType::TotalMemorySize: + *result = process->GetTotalPhysicalMemoryAvailable(); + return ResultSuccess; + + case InfoType::UsedMemorySize: + *result = process->GetTotalPhysicalMemoryUsed(); + return ResultSuccess; + + case InfoType::SystemResourceSizeTotal: + *result = process->GetSystemResourceSize(); + return ResultSuccess; + + case InfoType::SystemResourceSizeUsed: + LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); + *result = process->GetSystemResourceUsage(); + return ResultSuccess; + + case InfoType::ProgramId: + *result = process->GetProgramID(); + return ResultSuccess; + + case InfoType::UserExceptionContextAddress: + *result = process->GetProcessLocalRegionAddress(); + return ResultSuccess; + + case InfoType::TotalNonSystemMemorySize: + *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); + return ResultSuccess; + + case InfoType::UsedNonSystemMemorySize: + *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); + return ResultSuccess; + + case InfoType::FreeThreadCount: + *result = process->GetFreeThreadCount(); + return ResultSuccess; + + default: + break; + } + + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); + return ResultInvalidEnumValue; + } + + case InfoType::DebuggerAttached: + *result = 0; + return ResultSuccess; + + case InfoType::ResourceLimit: { + if (handle != 0) { + LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle); + return ResultInvalidHandle; + } + + if (info_sub_id != 0) { + LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, + info_sub_id); + return ResultInvalidCombination; + } + + KProcess* const current_process = system.Kernel().CurrentProcess(); + KHandleTable& handle_table = current_process->GetHandleTable(); + const auto resource_limit = current_process->GetResourceLimit(); + if (!resource_limit) { + *result = Svc::InvalidHandle; + // Yes, the kernel considers this a successful operation. + return ResultSuccess; + } + + Handle resource_handle{}; + R_TRY(handle_table.Add(&resource_handle, resource_limit)); + + *result = resource_handle; + return ResultSuccess; + } + + case InfoType::RandomEntropy: + if (handle != 0) { + LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", + handle); + return ResultInvalidHandle; + } + + if (info_sub_id >= KProcess::RANDOM_ENTROPY_SIZE) { + LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}", + KProcess::RANDOM_ENTROPY_SIZE, info_sub_id); + return ResultInvalidCombination; + } + + *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id); + return ResultSuccess; + + case InfoType::InitialProcessIdRange: + LOG_WARNING(Kernel_SVC, + "(STUBBED) Attempted to query privileged process id bounds, returned 0"); + *result = 0; + return ResultSuccess; + + case InfoType::ThreadTickCount: { + constexpr u64 num_cpus = 4; + if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { + LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, + info_sub_id); + return ResultInvalidCombination; + } + + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>( + static_cast<Handle>(handle)); + if (thread.IsNull()) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", + static_cast<Handle>(handle)); + return ResultInvalidHandle; + } + + const auto& core_timing = system.CoreTiming(); + const auto& scheduler = *system.Kernel().CurrentScheduler(); + const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); + const bool same_thread = current_thread == thread.GetPointerUnsafe(); + + const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime(); + u64 out_ticks = 0; + if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { + const u64 thread_ticks = current_thread->GetCpuTime(); + + out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); + } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { + out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks; + } + + *result = out_ticks; + return ResultSuccess; + } + case InfoType::IdleTickCount: { + // Verify the input handle is invalid. + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + + // Verify the requested core is valid. + const bool core_valid = + (info_sub_id == 0xFFFFFFFFFFFFFFFF) || + (info_sub_id == static_cast<u64>(system.Kernel().CurrentPhysicalCoreIndex())); + R_UNLESS(core_valid, ResultInvalidCombination); + + // Get the idle tick count. + *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); + return ResultSuccess; + } + case InfoType::MesosphereCurrentProcess: { + // Verify the input handle is invalid. + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + + // Verify the sub-type is valid. + R_UNLESS(info_sub_id == 0, ResultInvalidCombination); + + // Get the handle table. + KProcess* current_process = system.Kernel().CurrentProcess(); + KHandleTable& handle_table = current_process->GetHandleTable(); + + // Get a new handle for the current process. + Handle tmp; + R_TRY(handle_table.Add(&tmp, current_process)); + + // Set the output. + *result = tmp; + + // We succeeded. + return ResultSuccess; + } + default: + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); + return ResultInvalidEnumValue; + } +} + +Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, + u32 info_id, u32 handle, u32 sub_id_high) { + const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)}; + u64 res_value{}; + + const Result result{GetInfo(system, &res_value, info_id, handle, sub_id)}; + *result_high = static_cast<u32>(res_value >> 32); + *result_low = static_cast<u32>(res_value & std::numeric_limits<u32>::max()); + + return result; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_interrupt_event.cpp b/src/core/hle/kernel/svc/svc_interrupt_event.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_interrupt_event.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_io_pool.cpp b/src/core/hle/kernel/svc/svc_io_pool.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_io_pool.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp new file mode 100644 index 000000000..dbb68e89a --- /dev/null +++ b/src/core/hle/kernel/svc/svc_ipc.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Makes a blocking IPC call to a service. +Result SendSyncRequest(Core::System& system, Handle handle) { + auto& kernel = system.Kernel(); + + // Get the client session from its handle. + KScopedAutoObject session = + kernel.CurrentProcess()->GetHandleTable().GetObject<KClientSession>(handle); + R_UNLESS(session.IsNotNull(), ResultInvalidHandle); + + LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); + + return session->SendSyncRequest(); +} + +Result SendSyncRequest32(Core::System& system, Handle handle) { + return SendSyncRequest(system, handle); +} + +Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles, s32 num_handles, + Handle reply_target, s64 timeout_ns) { + auto& kernel = system.Kernel(); + auto& handle_table = GetCurrentThread(kernel).GetOwnerProcess()->GetHandleTable(); + + // Convert handle list to object table. + std::vector<KSynchronizationObject*> objs(num_handles); + R_UNLESS( + handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, num_handles), + ResultInvalidHandle); + + // Ensure handles are closed when we're done. + SCOPE_EXIT({ + for (auto i = 0; i < num_handles; ++i) { + objs[i]->Close(); + } + }); + + // Reply to the target, if one is specified. + if (reply_target != InvalidHandle) { + KScopedAutoObject session = handle_table.GetObject<KServerSession>(reply_target); + R_UNLESS(session.IsNotNull(), ResultInvalidHandle); + + // If we fail to reply, we want to set the output index to -1. + ON_RESULT_FAILURE { + *out_index = -1; + }; + + // Send the reply. + R_TRY(session->SendReply()); + } + + // Wait for a message. + while (true) { + // Wait for an object. + s32 index; + Result result = KSynchronizationObject::Wait(kernel, &index, objs.data(), + static_cast<s32>(objs.size()), timeout_ns); + if (result == ResultTimedOut) { + return result; + } + + // Receive the request. + if (R_SUCCEEDED(result)) { + KServerSession* session = objs[index]->DynamicCast<KServerSession*>(); + if (session != nullptr) { + result = session->ReceiveRequest(); + if (result == ResultNotFound) { + continue; + } + } + } + + *out_index = index; + return result; + } +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_kernel_debug.cpp b/src/core/hle/kernel/svc/svc_kernel_debug.cpp new file mode 100644 index 000000000..454255e7a --- /dev/null +++ b/src/core/hle/kernel/svc/svc_kernel_debug.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +void KernelDebug([[maybe_unused]] Core::System& system, [[maybe_unused]] u32 kernel_debug_type, + [[maybe_unused]] u64 param1, [[maybe_unused]] u64 param2, + [[maybe_unused]] u64 param3) { + // Intentionally do nothing, as this does nothing in released kernel binaries. +} + +void ChangeKernelTraceState([[maybe_unused]] Core::System& system, + [[maybe_unused]] u32 trace_state) { + // Intentionally do nothing, as this does nothing in released kernel binaries. +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_light_ipc.cpp b/src/core/hle/kernel/svc/svc_light_ipc.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_light_ipc.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp new file mode 100644 index 000000000..45f2a6553 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_lock.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Attempts to locks a mutex +Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag) { + LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}", + thread_handle, address, tag); + + // Validate the input address. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})", + address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(u32))) { + LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); + return ResultInvalidAddress; + } + + return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag); +} + +Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag) { + return ArbitrateLock(system, thread_handle, address, tag); +} + +/// Unlock a mutex +Result ArbitrateUnlock(Core::System& system, VAddr address) { + LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address); + + // Validate the input address. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, + "Attempting to arbitrate an unlock on a kernel address (address={:08X})", + address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(u32))) { + LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); + return ResultInvalidAddress; + } + + return system.Kernel().CurrentProcess()->SignalToAddress(address); +} + +Result ArbitrateUnlock32(Core::System& system, u32 address) { + return ArbitrateUnlock(system, address); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp new file mode 100644 index 000000000..f78b1239b --- /dev/null +++ b/src/core/hle/kernel/svc/svc_memory.cpp @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) { + switch (perm) { + case MemoryPermission::None: + case MemoryPermission::Read: + case MemoryPermission::ReadWrite: + return true; + default: + return false; + } +} + +// Checks if address + size is greater than the given address +// This can return false if the size causes an overflow of a 64-bit type +// or if the given size is zero. +constexpr bool IsValidAddressRange(VAddr address, u64 size) { + return address + size > address; +} + +// Helper function that performs the common sanity checks for svcMapMemory +// and svcUnmapMemory. This is doable, as both functions perform their sanitizing +// in the same order. +Result MapUnmapMemorySanityChecks(const KPageTable& manager, VAddr dst_addr, VAddr src_addr, + u64 size) { + if (!Common::Is4KBAligned(dst_addr)) { + LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(src_addr)) { + LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); + return ResultInvalidSize; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ResultInvalidSize; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); + return ResultInvalidSize; + } + + if (!IsValidAddressRange(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + dst_addr, size); + return ResultInvalidCurrentMemory; + } + + if (!IsValidAddressRange(src_addr, size)) { + LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + src_addr, size); + return ResultInvalidCurrentMemory; + } + + if (!manager.IsInsideAddressSpace(src_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", + src_addr, size); + return ResultInvalidCurrentMemory; + } + + if (manager.IsOutsideStackRegion(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}", + dst_addr, size); + return ResultInvalidMemoryRegion; + } + + if (manager.IsInsideHeapRegion(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination does not fit within the heap region, addr=0x{:016X}, " + "size=0x{:016X}", + dst_addr, size); + return ResultInvalidMemoryRegion; + } + + if (manager.IsInsideAliasRegion(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination does not fit within the map region, addr=0x{:016X}, " + "size=0x{:016X}", + dst_addr, size); + return ResultInvalidMemoryRegion; + } + + return ResultSuccess; +} + +} // namespace + +Result SetMemoryPermission(Core::System& system, VAddr address, u64 size, MemoryPermission perm) { + LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size, + perm); + + // Validate address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permission. + R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Validate that the region is in range for the current process. + auto& page_table = system.Kernel().CurrentProcess()->PageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Set the memory attribute. + return page_table.SetMemoryPermission(address, size, perm); +} + +Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, u32 attr) { + LOG_DEBUG(Kernel_SVC, + "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, + size, mask, attr); + + // Validate address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the attribute and mask. + constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::Uncached); + R_UNLESS((mask | attr) == mask, ResultInvalidCombination); + R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination); + + // Validate that the region is in range for the current process. + auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Set the memory attribute. + return page_table.SetMemoryAttribute(address, size, mask, attr); +} + +Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, u32 attr) { + return SetMemoryAttribute(system, address, size, mask, attr); +} + +/// Maps a memory range into a different range. +Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { + LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + src_addr, size); + + auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; + + if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; + result.IsError()) { + return result; + } + + return page_table.MapMemory(dst_addr, src_addr, size); +} + +Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { + return MapMemory(system, dst_addr, src_addr, size); +} + +/// Unmaps a region that was previously mapped with svcMapMemory +Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { + LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + src_addr, size); + + auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; + + if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; + result.IsError()) { + return result; + } + + return page_table.UnmapMemory(dst_addr, src_addr, size); +} + +Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { + return UnmapMemory(system, dst_addr, src_addr, size); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp new file mode 100644 index 000000000..0fc262203 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Set the process heap to a given Size. It can both extend and shrink the heap. +Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size) { + LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", size); + + // Validate size. + R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); + R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize); + + // Set the heap size. + R_TRY(system.Kernel().CurrentProcess()->PageTable().SetHeapSize(out_address, size)); + + return ResultSuccess; +} + +Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size) { + VAddr temp_heap_addr{}; + const Result result{SetHeapSize(system, &temp_heap_addr, heap_size)}; + *heap_addr = static_cast<u32>(temp_heap_addr); + return result; +} + +/// Maps memory at a desired address +Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { + LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); + return ResultInvalidSize; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is zero"); + return ResultInvalidSize; + } + + if (!(addr < addr + size)) { + LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); + return ResultInvalidMemoryRegion; + } + + KProcess* const current_process{system.Kernel().CurrentProcess()}; + auto& page_table{current_process->PageTable()}; + + if (current_process->GetSystemResourceSize() == 0) { + LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); + return ResultInvalidState; + } + + if (!page_table.IsInsideAddressSpace(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + if (page_table.IsOutsideAliasRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + return page_table.MapPhysicalMemory(addr, size); +} + +Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { + return MapPhysicalMemory(system, addr, size); +} + +/// Unmaps memory previously mapped via MapPhysicalMemory +Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { + LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); + return ResultInvalidSize; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is zero"); + return ResultInvalidSize; + } + + if (!(addr < addr + size)) { + LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); + return ResultInvalidMemoryRegion; + } + + KProcess* const current_process{system.Kernel().CurrentProcess()}; + auto& page_table{current_process->PageTable()}; + + if (current_process->GetSystemResourceSize() == 0) { + LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); + return ResultInvalidState; + } + + if (!page_table.IsInsideAddressSpace(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + if (page_table.IsOutsideAliasRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + return page_table.UnmapPhysicalMemory(addr, size); +} + +Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { + return UnmapPhysicalMemory(system, addr, size); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_port.cpp b/src/core/hle/kernel/svc/svc_port.cpp new file mode 100644 index 000000000..cdfe0dd16 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_port.cpp @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Connect to an OS service given the port name, returns the handle to the port to out +Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) { + auto& memory = system.Memory(); + if (!memory.IsValidVirtualAddress(port_name_address)) { + LOG_ERROR(Kernel_SVC, + "Port Name Address is not a valid virtual address, port_name_address=0x{:016X}", + port_name_address); + return ResultNotFound; + } + + static constexpr std::size_t PortNameMaxLength = 11; + // Read 1 char beyond the max allowed port name to detect names that are too long. + const std::string port_name = memory.ReadCString(port_name_address, PortNameMaxLength + 1); + if (port_name.size() > PortNameMaxLength) { + LOG_ERROR(Kernel_SVC, "Port name is too long, expected {} but got {}", PortNameMaxLength, + port_name.size()); + return ResultOutOfRange; + } + + LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); + + // Get the current handle table. + auto& kernel = system.Kernel(); + auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + + // Find the client port. + auto port = kernel.CreateNamedServicePort(port_name); + if (!port) { + LOG_ERROR(Kernel_SVC, "tried to connect to unknown port: {}", port_name); + return ResultNotFound; + } + + // Reserve a handle for the port. + // NOTE: Nintendo really does write directly to the output handle here. + R_TRY(handle_table.Reserve(out)); + auto handle_guard = SCOPE_GUARD({ handle_table.Unreserve(*out); }); + + // Create a session. + KClientSession* session{}; + R_TRY(port->CreateSession(std::addressof(session))); + + kernel.RegisterNamedServiceHandler(port_name, &port->GetParent()->GetServerPort()); + + // Register the session in the table, close the extra reference. + handle_table.Register(*out, session); + session->Close(); + + // We succeeded. + handle_guard.Cancel(); + return ResultSuccess; +} + +Result ConnectToNamedPort32(Core::System& system, Handle* out_handle, u32 port_name_address) { + + return ConnectToNamedPort(system, out_handle, port_name_address); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_power_management.cpp b/src/core/hle/kernel/svc/svc_power_management.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_power_management.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_process.cpp b/src/core/hle/kernel/svc/svc_process.cpp new file mode 100644 index 000000000..d6c8b4561 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_process.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Exits the current process +void ExitProcess(Core::System& system) { + auto* current_process = system.Kernel().CurrentProcess(); + + LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); + ASSERT_MSG(current_process->GetState() == KProcess::State::Running, + "Process has already exited"); + + system.Exit(); +} + +void ExitProcess32(Core::System& system) { + ExitProcess(system); +} + +/// Gets the ID of the specified process or a specified thread's owning process. +Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) { + LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle); + + // Get the object from the handle table. + KScopedAutoObject obj = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KAutoObject>( + static_cast<Handle>(handle)); + R_UNLESS(obj.IsNotNull(), ResultInvalidHandle); + + // Get the process from the object. + KProcess* process = nullptr; + if (KProcess* p = obj->DynamicCast<KProcess*>(); p != nullptr) { + // The object is a process, so we can use it directly. + process = p; + } else if (KThread* t = obj->DynamicCast<KThread*>(); t != nullptr) { + // The object is a thread, so we want to use its parent. + process = reinterpret_cast<KThread*>(obj.GetPointerUnsafe())->GetOwnerProcess(); + } else { + // TODO(bunnei): This should also handle debug objects before returning. + UNIMPLEMENTED_MSG("Debug objects not implemented"); + } + + // Make sure the target process exists. + R_UNLESS(process != nullptr, ResultInvalidHandle); + + // Get the process id. + *out_process_id = process->GetId(); + + return ResultSuccess; +} + +Result GetProcessId32(Core::System& system, u32* out_process_id_low, u32* out_process_id_high, + Handle handle) { + u64 out_process_id{}; + const auto result = GetProcessId(system, &out_process_id, handle); + *out_process_id_low = static_cast<u32>(out_process_id); + *out_process_id_high = static_cast<u32>(out_process_id >> 32); + return result; +} + +Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids, + u32 out_process_ids_size) { + LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}", + out_process_ids, out_process_ids_size); + + // If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail. + if ((out_process_ids_size & 0xF0000000) != 0) { + LOG_ERROR(Kernel_SVC, + "Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}", + out_process_ids_size); + return ResultOutOfRange; + } + + const auto& kernel = system.Kernel(); + const auto total_copy_size = out_process_ids_size * sizeof(u64); + + if (out_process_ids_size > 0 && !kernel.CurrentProcess()->PageTable().IsInsideAddressSpace( + out_process_ids, total_copy_size)) { + LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", + out_process_ids, out_process_ids + total_copy_size); + return ResultInvalidCurrentMemory; + } + + auto& memory = system.Memory(); + const auto& process_list = kernel.GetProcessList(); + const auto num_processes = process_list.size(); + const auto copy_amount = std::min(std::size_t{out_process_ids_size}, num_processes); + + for (std::size_t i = 0; i < copy_amount; ++i) { + memory.Write64(out_process_ids, process_list[i]->GetProcessID()); + out_process_ids += sizeof(u64); + } + + *out_num_processes = static_cast<u32>(num_processes); + return ResultSuccess; +} + +Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); + return ResultInvalidHandle; + } + + const auto info_type = static_cast<ProcessInfoType>(type); + if (info_type != ProcessInfoType::ProcessState) { + LOG_ERROR(Kernel_SVC, "Expected info_type to be ProcessState but got {} instead", type); + return ResultInvalidEnumValue; + } + + *out = static_cast<u64>(process->GetState()); + return ResultSuccess; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_process_memory.cpp b/src/core/hle/kernel/svc/svc_process_memory.cpp new file mode 100644 index 000000000..b6ac43af2 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_process_memory.cpp @@ -0,0 +1,274 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidAddressRange(VAddr address, u64 size) { + return address + size > address; +} + +constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) { + switch (perm) { + case Svc::MemoryPermission::None: + case Svc::MemoryPermission::Read: + case Svc::MemoryPermission::ReadWrite: + case Svc::MemoryPermission::ReadExecute: + return true; + default: + return false; + } +} + +} // namespace + +Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address, + u64 size, Svc::MemoryPermission perm) { + LOG_TRACE(Kernel_SVC, + "called, process_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + process_handle, address, size, perm); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); + R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); + + // Validate the memory permission. + R_UNLESS(IsValidProcessMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Get the process from its handle. + KScopedAutoObject process = + system.CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), ResultInvalidHandle); + + // Validate that the address is in range. + auto& page_table = process->PageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Set the memory permission. + return page_table.SetProcessMemoryPermission(address, size, perm); +} + +Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size) { + LOG_TRACE(Kernel_SVC, + "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", + dst_address, process_handle, src_address, size); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); + R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); + + // Get the processes. + KProcess* dst_process = system.CurrentProcess(); + KScopedAutoObject src_process = + dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); + + // Get the page tables. + auto& dst_pt = dst_process->PageTable(); + auto& src_pt = src_process->PageTable(); + + // Validate that the mapping is in range. + R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); + R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), + ResultInvalidMemoryRegion); + + // Create a new page group. + KPageGroup pg{system.Kernel(), dst_pt.GetBlockInfoManager()}; + R_TRY(src_pt.MakeAndOpenPageGroup( + std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess, + KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None, + KMemoryAttribute::All, KMemoryAttribute::None)); + + // Map the group. + R_TRY(dst_pt.MapPageGroup(dst_address, pg, KMemoryState::SharedCode, + KMemoryPermission::UserReadWrite)); + + return ResultSuccess; +} + +Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size) { + LOG_TRACE(Kernel_SVC, + "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", + dst_address, process_handle, src_address, size); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); + R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); + + // Get the processes. + KProcess* dst_process = system.CurrentProcess(); + KScopedAutoObject src_process = + dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); + + // Get the page tables. + auto& dst_pt = dst_process->PageTable(); + auto& src_pt = src_process->PageTable(); + + // Validate that the mapping is in range. + R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); + R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), + ResultInvalidMemoryRegion); + + // Unmap the memory. + R_TRY(dst_pt.UnmapProcessMemory(dst_address, size, src_pt, src_address)); + + return ResultSuccess; +} + +Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size) { + LOG_DEBUG(Kernel_SVC, + "called. process_handle=0x{:08X}, dst_address=0x{:016X}, " + "src_address=0x{:016X}, size=0x{:016X}", + process_handle, dst_address, src_address, size); + + if (!Common::Is4KBAligned(src_address)) { + LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", + src_address); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(dst_address)) { + LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", + dst_address); + return ResultInvalidAddress; + } + + if (size == 0 || !Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size); + return ResultInvalidSize; + } + + if (!IsValidAddressRange(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range overflows the address space (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidCurrentMemory; + } + + if (!IsValidAddressRange(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range overflows the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", + process_handle); + return ResultInvalidHandle; + } + + auto& page_table = process->PageTable(); + if (!page_table.IsInsideAddressSpace(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range is not within the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + if (!page_table.IsInsideASLRRegion(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidMemoryRegion; + } + + return page_table.MapCodeMemory(dst_address, src_address, size); +} + +Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size) { + LOG_DEBUG(Kernel_SVC, + "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, " + "size=0x{:016X}", + process_handle, dst_address, src_address, size); + + if (!Common::Is4KBAligned(dst_address)) { + LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", + dst_address); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(src_address)) { + LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", + src_address); + return ResultInvalidAddress; + } + + if (size == 0 || !Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size); + return ResultInvalidSize; + } + + if (!IsValidAddressRange(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range overflows the address space (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidCurrentMemory; + } + + if (!IsValidAddressRange(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range overflows the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", + process_handle); + return ResultInvalidHandle; + } + + auto& page_table = process->PageTable(); + if (!page_table.IsInsideAddressSpace(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range is not within the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + if (!page_table.IsInsideASLRRegion(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidMemoryRegion; + } + + return page_table.UnmapCodeMemory(dst_address, src_address, size, + KPageTable::ICacheInvalidationStrategy::InvalidateAll); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_processor.cpp b/src/core/hle/kernel/svc/svc_processor.cpp new file mode 100644 index 000000000..8561cf74f --- /dev/null +++ b/src/core/hle/kernel/svc/svc_processor.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/kernel/physical_core.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Get which CPU core is executing the current thread +u32 GetCurrentProcessorNumber(Core::System& system) { + LOG_TRACE(Kernel_SVC, "called"); + return static_cast<u32>(system.CurrentPhysicalCore().CoreIndex()); +} + +u32 GetCurrentProcessorNumber32(Core::System& system) { + return GetCurrentProcessorNumber(system); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_query_memory.cpp b/src/core/hle/kernel/svc/svc_query_memory.cpp new file mode 100644 index 000000000..aac3b2eca --- /dev/null +++ b/src/core/hle/kernel/svc/svc_query_memory.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + VAddr query_address) { + LOG_TRACE(Kernel_SVC, + "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, " + "query_address=0x{:016X}", + memory_info_address, page_info_address, query_address); + + return QueryProcessMemory(system, memory_info_address, page_info_address, CurrentProcess, + query_address); +} + +Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address, + u32 query_address) { + return QueryMemory(system, memory_info_address, page_info_address, query_address); +} + +Result QueryProcessMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + Handle process_handle, VAddr address) { + LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address); + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); + return ResultInvalidHandle; + } + + auto& memory{system.Memory()}; + const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()}; + + memory.Write64(memory_info_address + 0x00, memory_info.base_address); + memory.Write64(memory_info_address + 0x08, memory_info.size); + memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff); + memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attribute)); + memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.permission)); + memory.Write32(memory_info_address + 0x1c, memory_info.ipc_count); + memory.Write32(memory_info_address + 0x20, memory_info.device_count); + memory.Write32(memory_info_address + 0x24, 0); + + // Page info appears to be currently unused by the kernel and is always set to zero. + memory.Write32(page_info_address, 0); + + return ResultSuccess; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_register.cpp b/src/core/hle/kernel/svc/svc_register.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_register.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp new file mode 100644 index 000000000..679ba10fa --- /dev/null +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +Result CreateResourceLimit(Core::System& system, Handle* out_handle) { + LOG_DEBUG(Kernel_SVC, "called"); + + // Create a new resource limit. + auto& kernel = system.Kernel(); + KResourceLimit* resource_limit = KResourceLimit::Create(kernel); + R_UNLESS(resource_limit != nullptr, ResultOutOfResource); + + // Ensure we don't leak a reference to the limit. + SCOPE_EXIT({ resource_limit->Close(); }); + + // Initialize the resource limit. + resource_limit->Initialize(&system.CoreTiming()); + + // Register the limit. + KResourceLimit::Register(kernel, resource_limit); + + // Add the limit to the handle table. + R_TRY(kernel.CurrentProcess()->GetHandleTable().Add(out_handle, resource_limit)); + + return ResultSuccess; +} + +Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value, + Handle resource_limit_handle, LimitableResource which) { + LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, + which); + + // Validate the resource. + R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); + + // Get the resource limit. + auto& kernel = system.Kernel(); + KScopedAutoObject resource_limit = + kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); + + // Get the limit value. + *out_limit_value = resource_limit->GetLimitValue(which); + + return ResultSuccess; +} + +Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value, + Handle resource_limit_handle, LimitableResource which) { + LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, + which); + + // Validate the resource. + R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); + + // Get the resource limit. + auto& kernel = system.Kernel(); + KScopedAutoObject resource_limit = + kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); + + // Get the current value. + *out_current_value = resource_limit->GetCurrentValue(which); + + return ResultSuccess; +} + +Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle, + LimitableResource which, u64 limit_value) { + LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}, limit_value={}", + resource_limit_handle, which, limit_value); + + // Validate the resource. + R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); + + // Get the resource limit. + auto& kernel = system.Kernel(); + KScopedAutoObject resource_limit = + kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); + + // Set the limit value. + R_TRY(resource_limit->SetLimitValue(which, limit_value)); + + return ResultSuccess; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_secure_monitor_call.cpp b/src/core/hle/kernel/svc/svc_secure_monitor_call.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_secure_monitor_call.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_session.cpp b/src/core/hle/kernel/svc/svc_session.cpp new file mode 100644 index 000000000..dac8ce33c --- /dev/null +++ b/src/core/hle/kernel/svc/svc_session.cpp @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +template <typename T> +Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u64 name) { + auto& process = *system.CurrentProcess(); + auto& handle_table = process.GetHandleTable(); + + // Declare the session we're going to allocate. + T* session; + + // Reserve a new session from the process resource limit. + // FIXME: LimitableResource_SessionCountMax + KScopedResourceReservation session_reservation(&process, LimitableResource::SessionCountMax); + if (session_reservation.Succeeded()) { + session = T::Create(system.Kernel()); + } else { + return ResultLimitReached; + + // // We couldn't reserve a session. Check that we support dynamically expanding the + // // resource limit. + // R_UNLESS(process.GetResourceLimit() == + // &system.Kernel().GetSystemResourceLimit(), ResultLimitReached); + // R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), ResultLimitReached()); + + // // Try to allocate a session from unused slab memory. + // session = T::CreateFromUnusedSlabMemory(); + // R_UNLESS(session != nullptr, ResultLimitReached); + // ON_RESULT_FAILURE { session->Close(); }; + + // // If we're creating a KSession, we want to add two KSessionRequests to the heap, to + // // prevent request exhaustion. + // // NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's + // // no reason to not do this statically. + // if constexpr (std::same_as<T, KSession>) { + // for (size_t i = 0; i < 2; i++) { + // KSessionRequest* request = KSessionRequest::CreateFromUnusedSlabMemory(); + // R_UNLESS(request != nullptr, ResultLimitReached); + // request->Close(); + // } + // } + + // We successfully allocated a session, so add the object we allocated to the resource + // limit. + // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::SessionCountMax, 1); + } + + // Check that we successfully created a session. + R_UNLESS(session != nullptr, ResultOutOfResource); + + // Initialize the session. + session->Initialize(nullptr, fmt::format("{}", name)); + + // Commit the session reservation. + session_reservation.Commit(); + + // Ensure that we clean up the session (and its only references are handle table) on function + // end. + SCOPE_EXIT({ + session->GetClientSession().Close(); + session->GetServerSession().Close(); + }); + + // Register the session. + T::Register(system.Kernel(), session); + + // Add the server session to the handle table. + R_TRY(handle_table.Add(out_server, &session->GetServerSession())); + + // Add the client session to the handle table. + const auto result = handle_table.Add(out_client, &session->GetClientSession()); + + if (!R_SUCCEEDED(result)) { + // Ensure that we maintaing a clean handle state on exit. + handle_table.Remove(*out_server); + } + + return result; +} + +} // namespace + +Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u32 is_light, + u64 name) { + if (is_light) { + // return CreateSession<KLightSession>(system, out_server, out_client, name); + return ResultUnknown; + } else { + return CreateSession<KSession>(system, out_server, out_client, name); + } +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_shared_memory.cpp b/src/core/hle/kernel/svc/svc_shared_memory.cpp new file mode 100644 index 000000000..d465bcbe7 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_shared_memory.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidSharedMemoryPermission(MemoryPermission perm) { + switch (perm) { + case MemoryPermission::Read: + case MemoryPermission::ReadWrite: + return true; + default: + return false; + } +} + +[[maybe_unused]] constexpr bool IsValidRemoteSharedMemoryPermission(MemoryPermission perm) { + return IsValidSharedMemoryPermission(perm) || perm == MemoryPermission::DontCare; +} + +} // namespace + +Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size, + Svc::MemoryPermission map_perm) { + LOG_TRACE(Kernel_SVC, + "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + shmem_handle, address, size, map_perm); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permission. + R_UNLESS(IsValidSharedMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); + + // Get the current process. + auto& process = *system.Kernel().CurrentProcess(); + auto& page_table = process.PageTable(); + + // Get the shared memory. + KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); + R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); + + // Verify that the mapping is in range. + R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); + + // Add the shared memory to the process. + R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); + + // Ensure that we clean up the shared memory if we fail to map it. + auto guard = + SCOPE_GUARD({ process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); }); + + // Map the shared memory. + R_TRY(shmem->Map(process, address, size, map_perm)); + + // We succeeded. + guard.Cancel(); + return ResultSuccess; +} + +Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size, + Svc::MemoryPermission map_perm) { + return MapSharedMemory(system, shmem_handle, address, size, map_perm); +} + +Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size) { + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Get the current process. + auto& process = *system.Kernel().CurrentProcess(); + auto& page_table = process.PageTable(); + + // Get the shared memory. + KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); + R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); + + // Verify that the mapping is in range. + R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); + + // Unmap the shared memory. + R_TRY(shmem->Unmap(process, address, size)); + + // Remove the shared memory from the process. + process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); + + return ResultSuccess; +} + +Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size) { + return UnmapSharedMemory(system, shmem_handle, address, size); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp new file mode 100644 index 000000000..1bf6a612a --- /dev/null +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Close a handle +Result CloseHandle(Core::System& system, Handle handle) { + LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); + + // Remove the handle. + R_UNLESS(system.Kernel().CurrentProcess()->GetHandleTable().Remove(handle), + ResultInvalidHandle); + + return ResultSuccess; +} + +Result CloseHandle32(Core::System& system, Handle handle) { + return CloseHandle(system, handle); +} + +/// Clears the signaled state of an event or process. +Result ResetSignal(Core::System& system, Handle handle) { + LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); + + // Get the current handle table. + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + + // Try to reset as readable event. + { + KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(handle); + if (readable_event.IsNotNull()) { + return readable_event->Reset(); + } + } + + // Try to reset as process. + { + KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); + if (process.IsNotNull()) { + return process->Reset(); + } + } + + LOG_ERROR(Kernel_SVC, "invalid handle (0x{:08X})", handle); + + return ResultInvalidHandle; +} + +Result ResetSignal32(Core::System& system, Handle handle) { + return ResetSignal(system, handle); +} + +/// Wait for the given handles to synchronize, timeout after the specified nanoseconds +Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, s32 num_handles, + s64 nano_seconds) { + LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, num_handles={}, nano_seconds={}", + handles_address, num_handles, nano_seconds); + + // Ensure number of handles is valid. + R_UNLESS(0 <= num_handles && num_handles <= ArgumentHandleCountMax, ResultOutOfRange); + + auto& kernel = system.Kernel(); + std::vector<KSynchronizationObject*> objs(num_handles); + const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + Handle* handles = system.Memory().GetPointer<Handle>(handles_address); + + // Copy user handles. + if (num_handles > 0) { + // Convert the handles to objects. + R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, + num_handles), + ResultInvalidHandle); + for (const auto& obj : objs) { + kernel.RegisterInUseObject(obj); + } + } + + // Ensure handles are closed when we're done. + SCOPE_EXIT({ + for (s32 i = 0; i < num_handles; ++i) { + kernel.UnregisterInUseObject(objs[i]); + objs[i]->Close(); + } + }); + + return KSynchronizationObject::Wait(kernel, index, objs.data(), static_cast<s32>(objs.size()), + nano_seconds); +} + +Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, + s32 num_handles, u32 timeout_high, s32* index) { + const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)}; + return WaitSynchronization(system, index, handles_address, num_handles, nano_seconds); +} + +/// Resumes a thread waiting on WaitSynchronization +Result CancelSynchronization(Core::System& system, Handle handle) { + LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Cancel the thread's wait. + thread->WaitCancel(); + return ResultSuccess; +} + +Result CancelSynchronization32(Core::System& system, Handle handle) { + return CancelSynchronization(system, handle); +} + +void SynchronizePreemptionState(Core::System& system) { + auto& kernel = system.Kernel(); + + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // If the current thread is pinned, unpin it. + KProcess* cur_process = system.Kernel().CurrentProcess(); + const auto core_id = GetCurrentCoreId(kernel); + + if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(kernel)) { + // Clear the current thread's interrupt flag. + GetCurrentThread(kernel).ClearInterruptFlag(); + + // Unpin the current thread. + cur_process->UnpinCurrentThread(core_id); + } +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp new file mode 100644 index 000000000..dd9f8e8b1 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -0,0 +1,396 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc.h" + +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)); +} + +} // Anonymous namespace + +/// Creates a new thread +Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, + VAddr stack_bottom, u32 priority, s32 core_id) { + LOG_DEBUG(Kernel_SVC, + "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, " + "priority=0x{:08X}, core_id=0x{:08X}", + entry_point, arg, stack_bottom, priority, core_id); + + // Adjust core id, if it's the default magic. + auto& kernel = system.Kernel(); + auto& process = *kernel.CurrentProcess(); + if (core_id == IdealCoreUseProcessValue) { + core_id = process.GetIdealCoreId(); + } + + // Validate arguments. + if (!IsValidVirtualCoreId(core_id)) { + LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id); + return ResultInvalidCoreId; + } + if (((1ULL << core_id) & process.GetCoreMask()) == 0) { + LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id); + return ResultInvalidCoreId; + } + + if (HighestThreadPriority > priority || priority > LowestThreadPriority) { + LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority); + return ResultInvalidPriority; + } + if (!process.CheckThreadPriority(priority)) { + LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority); + return ResultInvalidPriority; + } + + // Reserve a new thread from the process resource limit (waiting up to 100ms). + KScopedResourceReservation thread_reservation( + kernel.CurrentProcess(), LimitableResource::ThreadCountMax, 1, + system.CoreTiming().GetGlobalTimeNs().count() + 100000000); + if (!thread_reservation.Succeeded()) { + LOG_ERROR(Kernel_SVC, "Could not reserve a new thread"); + return ResultLimitReached; + } + + // Create the thread. + KThread* thread = KThread::Create(kernel); + if (!thread) { + LOG_ERROR(Kernel_SVC, "Unable to create new threads. Thread creation limit reached."); + return ResultOutOfResource; + } + SCOPE_EXIT({ thread->Close(); }); + + // Initialize the thread. + { + KScopedLightLock lk{process.GetStateLock()}; + R_TRY(KThread::InitializeUserThread(system, thread, entry_point, arg, stack_bottom, + priority, core_id, &process)); + } + + // Set the thread name for debugging purposes. + thread->SetName(fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *out_handle)); + + // Commit the thread reservation. + thread_reservation.Commit(); + + // Register the new thread. + KThread::Register(kernel, thread); + + // Add the thread to the handle table. + R_TRY(process.GetHandleTable().Add(out_handle, thread)); + + return ResultSuccess; +} + +Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority, u32 entry_point, + u32 arg, u32 stack_top, s32 processor_id) { + return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id); +} + +/// Starts the thread for the provided handle +Result StartThread(Core::System& system, Handle thread_handle) { + LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Try to start the thread. + R_TRY(thread->Run()); + + // If we succeeded, persist a reference to the thread. + thread->Open(); + system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe()); + + return ResultSuccess; +} + +Result StartThread32(Core::System& system, Handle thread_handle) { + return StartThread(system, thread_handle); +} + +/// Called when a thread exits +void ExitThread(Core::System& system) { + LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); + + auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); + system.GlobalSchedulerContext().RemoveThread(current_thread); + current_thread->Exit(); + system.Kernel().UnregisterInUseObject(current_thread); +} + +void ExitThread32(Core::System& system) { + ExitThread(system); +} + +/// Sleep the current thread +void SleepThread(Core::System& system, s64 nanoseconds) { + auto& kernel = system.Kernel(); + const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); + + LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); + + // When the input tick is positive, sleep. + if (nanoseconds > 0) { + // Convert the timeout from nanoseconds to ticks. + // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + + // Sleep. + // NOTE: Nintendo does not check the result of this sleep. + static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); + } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { + KScheduler::YieldWithoutCoreMigration(kernel); + } else if (yield_type == Svc::YieldType::WithCoreMigration) { + KScheduler::YieldWithCoreMigration(kernel); + } else if (yield_type == Svc::YieldType::ToAnyThread) { + KScheduler::YieldToAnyThread(kernel); + } else { + // Nintendo does nothing at all if an otherwise invalid value is passed. + ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); + } +} + +void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) { + const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32)); + SleepThread(system, nanoseconds); +} + +/// Gets the thread context +Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) { + LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context, + thread_handle); + + auto& kernel = system.Kernel(); + + // Get the thread from its handle. + KScopedAutoObject thread = + kernel.CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Require the handle be to a non-current thread in the current process. + const auto* current_process = kernel.CurrentProcess(); + R_UNLESS(current_process == thread->GetOwnerProcess(), ResultInvalidId); + + // Verify that the thread isn't terminated. + R_UNLESS(thread->GetState() != ThreadState::Terminated, ResultTerminationRequested); + + /// Check that the thread is not the current one. + /// NOTE: Nintendo does not check this, and thus the following loop will deadlock. + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(kernel), ResultInvalidId); + + // Try to get the thread context until the thread isn't current on any core. + while (true) { + KScopedSchedulerLock sl{kernel}; + + // TODO(bunnei): Enforce that thread is suspended for debug here. + + // If the thread's raw state isn't runnable, check if it's current on some core. + if (thread->GetRawState() != ThreadState::Runnable) { + bool current = false; + for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { + if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetSchedulerCurrentThread()) { + current = true; + break; + } + } + + // If the thread is current, retry until it isn't. + if (current) { + continue; + } + } + + // Get the thread context. + std::vector<u8> context; + R_TRY(thread->GetThreadContext3(context)); + + // Copy the thread context to user space. + system.Memory().WriteBlock(out_context, context.data(), context.size()); + + return ResultSuccess; + } + + return ResultSuccess; +} + +Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) { + return GetThreadContext(system, out_context, thread_handle); +} + +/// Gets the priority for the specified thread +Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) { + LOG_TRACE(Kernel_SVC, "called"); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Get the thread's priority. + *out_priority = thread->GetPriority(); + return ResultSuccess; +} + +Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) { + return GetThreadPriority(system, out_priority, handle); +} + +/// Sets the priority for the specified thread +Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority) { + // Get the current process. + KProcess& process = *system.Kernel().CurrentProcess(); + + // Validate the priority. + R_UNLESS(HighestThreadPriority <= priority && priority <= LowestThreadPriority, + ResultInvalidPriority); + R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); + + // Get the thread from its handle. + KScopedAutoObject thread = process.GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Set the thread priority. + thread->SetBasePriority(priority); + return ResultSuccess; +} + +Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority) { + return SetThreadPriority(system, thread_handle, priority); +} + +Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, + u32 out_thread_ids_size, Handle debug_handle) { + // TODO: Handle this case when debug events are supported. + UNIMPLEMENTED_IF(debug_handle != InvalidHandle); + + LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}", + out_thread_ids, out_thread_ids_size); + + // If the size is negative or larger than INT32_MAX / sizeof(u64) + if ((out_thread_ids_size & 0xF0000000) != 0) { + LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. size={}", + out_thread_ids_size); + return ResultOutOfRange; + } + + auto* const current_process = system.Kernel().CurrentProcess(); + const auto total_copy_size = out_thread_ids_size * sizeof(u64); + + if (out_thread_ids_size > 0 && + !current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) { + LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", + out_thread_ids, out_thread_ids + total_copy_size); + return ResultInvalidCurrentMemory; + } + + auto& memory = system.Memory(); + const auto& thread_list = current_process->GetThreadList(); + const auto num_threads = thread_list.size(); + const auto copy_amount = std::min(std::size_t{out_thread_ids_size}, num_threads); + + auto list_iter = thread_list.cbegin(); + for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) { + memory.Write64(out_thread_ids, (*list_iter)->GetThreadID()); + out_thread_ids += sizeof(u64); + } + + *out_num_threads = static_cast<u32>(num_threads); + return ResultSuccess; +} + +Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, + u64* out_affinity_mask) { + LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Get the core mask. + R_TRY(thread->GetCoreMask(out_core_id, out_affinity_mask)); + + return ResultSuccess; +} + +Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, + u32* out_affinity_mask_low, u32* out_affinity_mask_high) { + u64 out_affinity_mask{}; + const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask); + *out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32); + *out_affinity_mask_low = static_cast<u32>(out_affinity_mask); + return result; +} + +Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, + u64 affinity_mask) { + // Determine the core id/affinity mask. + if (core_id == IdealCoreUseProcessValue) { + core_id = system.Kernel().CurrentProcess()->GetIdealCoreId(); + affinity_mask = (1ULL << core_id); + } else { + // Validate the affinity mask. + const u64 process_core_mask = system.Kernel().CurrentProcess()->GetCoreMask(); + R_UNLESS((affinity_mask | process_core_mask) == process_core_mask, ResultInvalidCoreId); + R_UNLESS(affinity_mask != 0, ResultInvalidCombination); + + // Validate the core id. + if (IsValidVirtualCoreId(core_id)) { + R_UNLESS(((1ULL << core_id) & affinity_mask) != 0, ResultInvalidCombination); + } else { + R_UNLESS(core_id == IdealCoreNoUpdate || core_id == IdealCoreDontCare, + ResultInvalidCoreId); + } + } + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Set the core mask. + R_TRY(thread->SetCoreMask(core_id, affinity_mask)); + + return ResultSuccess; +} + +Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, + u32 affinity_mask_low, u32 affinity_mask_high) { + const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32); + return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask); +} + +/// Get the ID for the specified thread. +Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) { + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Get the thread's id. + *out_thread_id = thread->GetId(); + return ResultSuccess; +} + +Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high, + Handle thread_handle) { + u64 out_thread_id{}; + const Result result{GetThreadId(system, &out_thread_id, thread_handle)}; + + *out_thread_id_low = static_cast<u32>(out_thread_id >> 32); + *out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max()); + + return result; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_thread_profiler.cpp b/src/core/hle/kernel/svc/svc_thread_profiler.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_thread_profiler.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_tick.cpp b/src/core/hle/kernel/svc/svc_tick.cpp new file mode 100644 index 000000000..e9b4fd5a6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_tick.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// This returns the total CPU ticks elapsed since the CPU was powered-on +u64 GetSystemTick(Core::System& system) { + LOG_TRACE(Kernel_SVC, "called"); + + auto& core_timing = system.CoreTiming(); + + // Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick) + const u64 result{core_timing.GetClockTicks()}; + + if (!system.Kernel().IsMulticore()) { + core_timing.AddTicks(400U); + } + + return result; +} + +void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) { + const auto time = GetSystemTick(system); + *time_low = static_cast<u32>(time); + *time_high = static_cast<u32>(time >> 32); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp new file mode 100644 index 000000000..b14ae24a1 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) { + switch (perm) { + case MemoryPermission::None: + case MemoryPermission::Read: + case MemoryPermission::ReadWrite: + return true; + default: + return false; + } +} + +} // Anonymous namespace + +/// Creates a TransferMemory object +Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size, + MemoryPermission map_perm) { + auto& kernel = system.Kernel(); + + // Validate the size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permissions. + R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); + + // Get the current process and handle table. + auto& process = *kernel.CurrentProcess(); + auto& handle_table = process.GetHandleTable(); + + // Reserve a new transfer memory from the process resource limit. + KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(), + LimitableResource::TransferMemoryCountMax); + R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached); + + // Create the transfer memory. + KTransferMemory* trmem = KTransferMemory::Create(kernel); + R_UNLESS(trmem != nullptr, ResultOutOfResource); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ trmem->Close(); }); + + // Ensure that the region is in range. + R_UNLESS(process.PageTable().Contains(address, size), ResultInvalidCurrentMemory); + + // Initialize the transfer memory. + R_TRY(trmem->Initialize(address, size, map_perm)); + + // Commit the reservation. + trmem_reservation.Commit(); + + // Register the transfer memory. + KTransferMemory::Register(kernel, trmem); + + // Add the transfer memory to the handle table. + R_TRY(handle_table.Add(out, trmem)); + + return ResultSuccess; +} + +Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size, + MemoryPermission map_perm) { + return CreateTransferMemory(system, out, address, size, map_perm); +} +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 1ea8c7fbc..052be40dd 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -172,11 +172,11 @@ void SvcWrap64(Core::System& system) { } // Used by GetResourceLimitLimitValue. -template <Result func(Core::System&, u64*, Handle, LimitableResource)> +template <Result func(Core::System&, u64*, Handle, Svc::LimitableResource)> void SvcWrap64(Core::System& system) { u64 param_1 = 0; const u32 retval = func(system, ¶m_1, static_cast<Handle>(Param(system, 1)), - static_cast<LimitableResource>(Param(system, 2))) + static_cast<Svc::LimitableResource>(Param(system, 2))) .raw; system.CurrentArmInterface().SetReg(1, param_1); @@ -189,10 +189,10 @@ void SvcWrap64(Core::System& system) { } // Used by SetResourceLimitLimitValue -template <Result func(Core::System&, Handle, LimitableResource, u64)> +template <Result func(Core::System&, Handle, Svc::LimitableResource, u64)> void SvcWrap64(Core::System& system) { FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)), - static_cast<LimitableResource>(Param(system, 1)), Param(system, 2)) + static_cast<Svc::LimitableResource>(Param(system, 1)), Param(system, 2)) .raw); } |