// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/scope_exit.h" #include "common/scratch_buffer.h" #include "core/core.h" #include "core/hle/kernel/k_client_session.h" #include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_session.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_results.h" namespace Kernel::Svc { namespace { Result SendSyncRequestImpl(KernelCore& kernel, uintptr_t message, size_t buffer_size, Handle session_handle) { // Get the client session. KScopedAutoObject session = GetCurrentProcess(kernel).GetHandleTable().GetObject(session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // Get the parent, and persist a reference to it until we're done. KScopedAutoObject parent = session->GetParent(); ASSERT(parent.IsNotNull()); // Send the request. R_RETURN(session->SendSyncRequest(message, buffer_size)); } Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t message, size_t buffer_size, KPhysicalAddress message_paddr, KSynchronizationObject** objs, int32_t num_objects, Handle reply_target, int64_t timeout_ns) { // Reply to the target, if one is specified. if (reply_target != InvalidHandle) { KScopedAutoObject session = GetCurrentProcess(kernel).GetHandleTable().GetObject(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(message, buffer_size, message_paddr)); } // Receive a message. { // Convert the timeout from nanoseconds to ticks. // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... s64 timeout; if (timeout_ns > 0) { const s64 offset_tick(timeout_ns); if (offset_tick > 0) { timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; if (timeout <= 0) { timeout = std::numeric_limits::max(); } } else { timeout = std::numeric_limits::max(); } } else { timeout = timeout_ns; } // Wait for a message. while (true) { // Wait for an object. s32 index; Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs, num_objects, timeout); if (ResultTimedOut == result) { R_THROW(result); } // Receive the request. if (R_SUCCEEDED(result)) { KServerSession* session = objs[index]->DynamicCast(); if (session != nullptr) { result = session->ReceiveRequest(message, buffer_size, message_paddr); if (ResultNotFound == result) { continue; } } } *out_index = index; R_RETURN(result); } } } Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t message, size_t buffer_size, KPhysicalAddress message_paddr, KProcessAddress user_handles, int32_t num_handles, Handle reply_target, int64_t timeout_ns) { // Ensure number of handles is valid. R_UNLESS(0 <= num_handles && num_handles <= Svc::ArgumentHandleCountMax, ResultOutOfRange); // Get the synchronization context. auto& process = GetCurrentProcess(kernel); auto& thread = GetCurrentThread(kernel); auto& handle_table = process.GetHandleTable(); KSynchronizationObject** objs = thread.GetSynchronizationObjectBuffer().data(); Handle* handles = thread.GetHandleBuffer().data(); // Copy user handles. if (num_handles > 0) { // Ensure that we can try to get the handles. R_UNLESS(process.GetPageTable().Contains(user_handles, num_handles * sizeof(Handle)), ResultInvalidPointer); // Get the handles R_UNLESS( GetCurrentMemory(kernel).ReadBlock(user_handles, handles, sizeof(Handle) * num_handles), ResultInvalidPointer); // Convert the handles to objects. R_UNLESS( handle_table.GetMultipleObjects(objs, 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(); } }); R_RETURN(ReplyAndReceiveImpl(kernel, out_index, message, buffer_size, message_paddr, objs, num_handles, reply_target, timeout_ns)); } } // namespace /// Makes a blocking IPC call to a service. Result SendSyncRequest(Core::System& system, Handle session_handle) { R_RETURN(SendSyncRequestImpl(system.Kernel(), 0, 0, session_handle)); } Result SendSyncRequestWithUserBuffer(Core::System& system, uint64_t message, uint64_t buffer_size, Handle session_handle) { auto& kernel = system.Kernel(); // Validate that the message buffer is page aligned and does not overflow. R_UNLESS(Common::IsAligned(message, PageSize), ResultInvalidAddress); R_UNLESS(buffer_size > 0, ResultInvalidSize); R_UNLESS(Common::IsAligned(buffer_size, PageSize), ResultInvalidSize); R_UNLESS(message < message + buffer_size, ResultInvalidCurrentMemory); // Get the process page table. auto& page_table = GetCurrentProcess(kernel).GetPageTable(); // Lock the message buffer. R_TRY(page_table.LockForIpcUserBuffer(nullptr, message, buffer_size)); { // If we fail to send the message, unlock the message buffer. ON_RESULT_FAILURE { page_table.UnlockForIpcUserBuffer(message, buffer_size); }; // Send the request. ASSERT(message != 0); R_TRY(SendSyncRequestImpl(kernel, message, buffer_size, session_handle)); } // We successfully processed, so try to unlock the message buffer. R_RETURN(page_table.UnlockForIpcUserBuffer(message, buffer_size)); } Result SendAsyncRequestWithUserBuffer(Core::System& system, Handle* out_event_handle, uint64_t message, uint64_t buffer_size, Handle session_handle) { // Get the process and handle table. auto& process = GetCurrentProcess(system.Kernel()); auto& handle_table = process.GetHandleTable(); // Reserve a new event from the process resource limit. KScopedResourceReservation event_reservation(std::addressof(process), Svc::LimitableResource::EventCountMax); R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); // Get the client session. KScopedAutoObject session = process.GetHandleTable().GetObject(session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // Get the parent, and persist a reference to it until we're done. KScopedAutoObject parent = session->GetParent(); ASSERT(parent.IsNotNull()); // Create a new event. KEvent* event = KEvent::Create(system.Kernel()); R_UNLESS(event != nullptr, ResultOutOfResource); // Initialize the event. event->Initialize(std::addressof(process)); // Commit our reservation. event_reservation.Commit(); // At end of scope, kill the standing references to the sub events. SCOPE_EXIT({ event->GetReadableEvent().Close(); event->Close(); }); // Register the event. KEvent::Register(system.Kernel(), event); // Add the readable event to the handle table. R_TRY(handle_table.Add(out_event_handle, std::addressof(event->GetReadableEvent()))); // Ensure that if we fail to send the request, we close the readable handle. ON_RESULT_FAILURE { handle_table.Remove(*out_event_handle); }; // Send the async request. R_RETURN(session->SendAsyncRequest(event, message, buffer_size)); } Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles, s32 num_handles, Handle reply_target, s64 timeout_ns) { R_RETURN(ReplyAndReceiveImpl(system.Kernel(), out_index, 0, 0, 0, handles, num_handles, reply_target, timeout_ns)); } Result ReplyAndReceiveWithUserBuffer(Core::System& system, int32_t* out_index, uint64_t message, uint64_t buffer_size, uint64_t handles, int32_t num_handles, Handle reply_target, int64_t timeout_ns) { // Validate that the message buffer is page aligned and does not overflow. R_UNLESS(Common::IsAligned(message, PageSize), ResultInvalidAddress); R_UNLESS(buffer_size > 0, ResultInvalidSize); R_UNLESS(Common::IsAligned(buffer_size, PageSize), ResultInvalidSize); R_UNLESS(message < message + buffer_size, ResultInvalidCurrentMemory); // Get the process page table. auto& page_table = GetCurrentProcess(system.Kernel()).GetPageTable(); // Lock the message buffer, getting its physical address. KPhysicalAddress message_paddr; R_TRY(page_table.LockForIpcUserBuffer(std::addressof(message_paddr), message, buffer_size)); { // If we fail to send the message, unlock the message buffer. ON_RESULT_FAILURE { page_table.UnlockForIpcUserBuffer(message, buffer_size); }; // Reply/Receive the request. ASSERT(message != 0); R_TRY(ReplyAndReceiveImpl(system.Kernel(), out_index, message, buffer_size, message_paddr, handles, num_handles, reply_target, timeout_ns)); } // We successfully processed, so try to unlock the message buffer. R_RETURN(page_table.UnlockForIpcUserBuffer(message, buffer_size)); } Result SendSyncRequest64(Core::System& system, Handle session_handle) { R_RETURN(SendSyncRequest(system, session_handle)); } Result SendSyncRequestWithUserBuffer64(Core::System& system, uint64_t message_buffer, uint64_t message_buffer_size, Handle session_handle) { R_RETURN( SendSyncRequestWithUserBuffer(system, message_buffer, message_buffer_size, session_handle)); } Result SendAsyncRequestWithUserBuffer64(Core::System& system, Handle* out_event_handle, uint64_t message_buffer, uint64_t message_buffer_size, Handle session_handle) { R_RETURN(SendAsyncRequestWithUserBuffer(system, out_event_handle, message_buffer, message_buffer_size, session_handle)); } Result ReplyAndReceive64(Core::System& system, int32_t* out_index, uint64_t handles, int32_t num_handles, Handle reply_target, int64_t timeout_ns) { R_RETURN(ReplyAndReceive(system, out_index, handles, num_handles, reply_target, timeout_ns)); } Result ReplyAndReceiveWithUserBuffer64(Core::System& system, int32_t* out_index, uint64_t message_buffer, uint64_t message_buffer_size, uint64_t handles, int32_t num_handles, Handle reply_target, int64_t timeout_ns) { R_RETURN(ReplyAndReceiveWithUserBuffer(system, out_index, message_buffer, message_buffer_size, handles, num_handles, reply_target, timeout_ns)); } Result SendSyncRequest64From32(Core::System& system, Handle session_handle) { R_RETURN(SendSyncRequest(system, session_handle)); } Result SendSyncRequestWithUserBuffer64From32(Core::System& system, uint32_t message_buffer, uint32_t message_buffer_size, Handle session_handle) { R_RETURN( SendSyncRequestWithUserBuffer(system, message_buffer, message_buffer_size, session_handle)); } Result SendAsyncRequestWithUserBuffer64From32(Core::System& system, Handle* out_event_handle, uint32_t message_buffer, uint32_t message_buffer_size, Handle session_handle) { R_RETURN(SendAsyncRequestWithUserBuffer(system, out_event_handle, message_buffer, message_buffer_size, session_handle)); } Result ReplyAndReceive64From32(Core::System& system, int32_t* out_index, uint32_t handles, int32_t num_handles, Handle reply_target, int64_t timeout_ns) { R_RETURN(ReplyAndReceive(system, out_index, handles, num_handles, reply_target, timeout_ns)); } Result ReplyAndReceiveWithUserBuffer64From32(Core::System& system, int32_t* out_index, uint32_t message_buffer, uint32_t message_buffer_size, uint32_t handles, int32_t num_handles, Handle reply_target, int64_t timeout_ns) { R_RETURN(ReplyAndReceiveWithUserBuffer(system, out_index, message_buffer, message_buffer_size, handles, num_handles, reply_target, timeout_ns)); } } // namespace Kernel::Svc