diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/hle/service/ipc_helpers.h | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/src/core/hle/service/ipc_helpers.h b/src/core/hle/service/ipc_helpers.h new file mode 100644 index 000000000..0e222362e --- /dev/null +++ b/src/core/hle/service/ipc_helpers.h @@ -0,0 +1,506 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <cstring> +#include <memory> +#include <type_traits> +#include <utility> +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/result.h" +#include "core/hle/service/hle_ipc.h" +#include "core/hle/service/server_manager.h" + +namespace IPC { + +constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301}; + +class RequestHelperBase { +protected: + Service::HLERequestContext* context = nullptr; + u32* cmdbuf; + u32 index = 0; + +public: + explicit RequestHelperBase(u32* command_buffer) : cmdbuf(command_buffer) {} + + explicit RequestHelperBase(Service::HLERequestContext& ctx) + : context(&ctx), cmdbuf(ctx.CommandBuffer()) {} + + void Skip(u32 size_in_words, bool set_to_null) { + if (set_to_null) { + memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); + } + index += size_in_words; + } + + /** + * Aligns the current position forward to a 16-byte boundary, padding with zeros. + */ + void AlignWithPadding() { + if (index & 3) { + Skip(static_cast<u32>(4 - (index & 3)), true); + } + } + + u32 GetCurrentOffset() const { + return index; + } + + void SetCurrentOffset(u32 offset) { + index = offset; + } +}; + +class ResponseBuilder : public RequestHelperBase { +public: + /// Flags used for customizing the behavior of ResponseBuilder + enum class Flags : u32 { + None = 0, + /// Uses move handles to move objects in the response, even when in a domain. This is + /// required when PushMoveObjects is used. + AlwaysMoveHandles = 1, + }; + + explicit ResponseBuilder(Service::HLERequestContext& ctx, u32 normal_params_size_, + u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0, + Flags flags = Flags::None) + : RequestHelperBase(ctx), normal_params_size(normal_params_size_), + num_handles_to_copy(num_handles_to_copy_), + num_objects_to_move(num_objects_to_move_), kernel{ctx.kernel} { + + memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); + + IPC::CommandHeader header{}; + + // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory + // padding. + u32 raw_data_size = ctx.write_size = + ctx.IsTipc() ? normal_params_size - 1 : normal_params_size; + u32 num_handles_to_move{}; + u32 num_domain_objects{}; + const bool always_move_handles{ + (static_cast<u32>(flags) & static_cast<u32>(Flags::AlwaysMoveHandles)) != 0}; + if (!ctx.GetManager()->IsDomain() || always_move_handles) { + num_handles_to_move = num_objects_to_move; + } else { + num_domain_objects = num_objects_to_move; + } + + if (ctx.GetManager()->IsDomain()) { + raw_data_size += + static_cast<u32>(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects); + ctx.write_size += num_domain_objects; + } + + if (ctx.IsTipc()) { + header.type.Assign(ctx.GetCommandType()); + } else { + raw_data_size += static_cast<u32>(sizeof(IPC::DataPayloadHeader) / sizeof(u32) + 4 + + normal_params_size); + } + + header.data_size.Assign(raw_data_size); + if (num_handles_to_copy || num_handles_to_move) { + header.enable_handle_descriptor.Assign(1); + } + PushRaw(header); + + if (header.enable_handle_descriptor) { + IPC::HandleDescriptorHeader handle_descriptor_header{}; + handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy_); + handle_descriptor_header.num_handles_to_move.Assign(num_handles_to_move); + PushRaw(handle_descriptor_header); + + ctx.handles_offset = index; + + Skip(num_handles_to_copy + num_handles_to_move, true); + } + + if (!ctx.IsTipc()) { + AlignWithPadding(); + + if (ctx.GetManager()->IsDomain() && ctx.HasDomainMessageHeader()) { + IPC::DomainMessageHeader domain_header{}; + domain_header.num_objects = num_domain_objects; + PushRaw(domain_header); + } + + IPC::DataPayloadHeader data_payload_header{}; + data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O'); + PushRaw(data_payload_header); + } + + data_payload_index = index; + + ctx.data_payload_offset = index; + ctx.write_size += index; + ctx.domain_offset = static_cast<u32>(index + raw_data_size / sizeof(u32)); + } + + template <class T> + void PushIpcInterface(std::shared_ptr<T> iface) { + auto manager{context->GetManager()}; + + if (manager->IsDomain()) { + context->AddDomainObject(std::move(iface)); + } else { + kernel.ApplicationProcess()->GetResourceLimit()->Reserve( + Kernel::LimitableResource::SessionCountMax, 1); + + auto* session = Kernel::KSession::Create(kernel); + session->Initialize(nullptr, 0); + Kernel::KSession::Register(kernel, session); + + auto next_manager = std::make_shared<Service::SessionRequestManager>( + kernel, manager->GetServerManager()); + next_manager->SetSessionHandler(iface); + manager->GetServerManager().RegisterSession(&session->GetServerSession(), next_manager); + + context->AddMoveObject(&session->GetClientSession()); + } + } + + template <class T, class... Args> + void PushIpcInterface(Args&&... args) { + PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...)); + } + + void PushImpl(s8 value); + void PushImpl(s16 value); + void PushImpl(s32 value); + void PushImpl(s64 value); + void PushImpl(u8 value); + void PushImpl(u16 value); + void PushImpl(u32 value); + void PushImpl(u64 value); + void PushImpl(float value); + void PushImpl(double value); + void PushImpl(bool value); + void PushImpl(Result value); + + template <typename T> + void Push(T value) { + return PushImpl(value); + } + + template <typename First, typename... Other> + void Push(const First& first_value, const Other&... other_values); + + /** + * Helper function for pushing strongly-typed enumeration values. + * + * @tparam Enum The enumeration type to be pushed + * + * @param value The value to push. + * + * @note The underlying size of the enumeration type is the size of the + * data that gets pushed. e.g. "enum class SomeEnum : u16" will + * push a u16-sized amount of data. + */ + template <typename Enum> + void PushEnum(Enum value) { + static_assert(std::is_enum_v<Enum>, "T must be an enum type within a PushEnum call."); + static_assert(!std::is_convertible_v<Enum, int>, + "enum type in PushEnum must be a strongly typed enum."); + Push(static_cast<std::underlying_type_t<Enum>>(value)); + } + + /** + * @brief Copies the content of the given trivially copyable class to the buffer as a normal + * param + * @note: The input class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + void PushRaw(const T& value); + + template <typename... O> + void PushMoveObjects(O*... pointers); + + template <typename... O> + void PushMoveObjects(O&... pointers); + + template <typename... O> + void PushCopyObjects(O*... pointers); + + template <typename... O> + void PushCopyObjects(O&... pointers); + +private: + u32 normal_params_size{}; + u32 num_handles_to_copy{}; + u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent + u32 data_payload_index{}; + Kernel::KernelCore& kernel; +}; + +/// Push /// + +inline void ResponseBuilder::PushImpl(s32 value) { + cmdbuf[index++] = value; +} + +inline void ResponseBuilder::PushImpl(u32 value) { + cmdbuf[index++] = value; +} + +template <typename T> +void ResponseBuilder::PushRaw(const T& value) { + static_assert(std::is_trivially_copyable_v<T>, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); + std::memcpy(cmdbuf + index, &value, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length +} + +inline void ResponseBuilder::PushImpl(Result value) { + // Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded. + Push(value.raw); + Push<u32>(0); +} + +inline void ResponseBuilder::PushImpl(s8 value) { + PushRaw(value); +} + +inline void ResponseBuilder::PushImpl(s16 value) { + PushRaw(value); +} + +inline void ResponseBuilder::PushImpl(s64 value) { + PushImpl(static_cast<u32>(value)); + PushImpl(static_cast<u32>(value >> 32)); +} + +inline void ResponseBuilder::PushImpl(u8 value) { + PushRaw(value); +} + +inline void ResponseBuilder::PushImpl(u16 value) { + PushRaw(value); +} + +inline void ResponseBuilder::PushImpl(u64 value) { + PushImpl(static_cast<u32>(value)); + PushImpl(static_cast<u32>(value >> 32)); +} + +inline void ResponseBuilder::PushImpl(float value) { + u32 integral; + std::memcpy(&integral, &value, sizeof(u32)); + PushImpl(integral); +} + +inline void ResponseBuilder::PushImpl(double value) { + u64 integral; + std::memcpy(&integral, &value, sizeof(u64)); + PushImpl(integral); +} + +inline void ResponseBuilder::PushImpl(bool value) { + PushImpl(static_cast<u8>(value)); +} + +template <typename First, typename... Other> +void ResponseBuilder::Push(const First& first_value, const Other&... other_values) { + Push(first_value); + Push(other_values...); +} + +template <typename... O> +inline void ResponseBuilder::PushCopyObjects(O*... pointers) { + auto objects = {pointers...}; + for (auto& object : objects) { + context->AddCopyObject(object); + } +} + +template <typename... O> +inline void ResponseBuilder::PushCopyObjects(O&... pointers) { + auto objects = {&pointers...}; + for (auto& object : objects) { + context->AddCopyObject(object); + } +} + +template <typename... O> +inline void ResponseBuilder::PushMoveObjects(O*... pointers) { + auto objects = {pointers...}; + for (auto& object : objects) { + context->AddMoveObject(object); + } +} + +template <typename... O> +inline void ResponseBuilder::PushMoveObjects(O&... pointers) { + auto objects = {&pointers...}; + for (auto& object : objects) { + context->AddMoveObject(object); + } +} + +class RequestParser : public RequestHelperBase { +public: + explicit RequestParser(u32* command_buffer) : RequestHelperBase(command_buffer) {} + + explicit RequestParser(Service::HLERequestContext& ctx) : RequestHelperBase(ctx) { + // TIPC does not have data payload offset + if (!ctx.IsTipc()) { + ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete"); + Skip(ctx.GetDataPayloadOffset(), false); + } + + // Skip the u64 command id, it's already stored in the context + static constexpr u32 CommandIdSize = 2; + Skip(CommandIdSize, false); + } + + template <typename T> + T Pop(); + + template <typename T> + void Pop(T& value); + + template <typename First, typename... Other> + void Pop(First& first_value, Other&... other_values); + + template <typename T> + T PopEnum() { + static_assert(std::is_enum_v<T>, "T must be an enum type within a PopEnum call."); + static_assert(!std::is_convertible_v<T, int>, + "enum type in PopEnum must be a strongly typed enum."); + return static_cast<T>(Pop<std::underlying_type_t<T>>()); + } + + /** + * @brief Reads the next normal parameters as a struct, by copying it + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + void PopRaw(T& value); + + /** + * @brief Reads the next normal parameters as a struct, by copying it into a new value + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + T PopRaw(); + + template <class T> + std::weak_ptr<T> PopIpcInterface() { + ASSERT(context->GetManager()->IsDomain()); + ASSERT(context->GetDomainMessageHeader().input_object_count > 0); + return context->GetDomainHandler<T>(Pop<u32>() - 1); + } +}; + +/// Pop /// + +template <> +inline u32 RequestParser::Pop() { + return cmdbuf[index++]; +} + +template <> +inline s32 RequestParser::Pop() { + return static_cast<s32>(Pop<u32>()); +} + +// Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif +template <typename T> +void RequestParser::PopRaw(T& value) { + static_assert(std::is_trivially_copyable_v<T>, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); + std::memcpy(&value, cmdbuf + index, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length +} +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif + +template <typename T> +T RequestParser::PopRaw() { + T value; + PopRaw(value); + return value; +} + +template <> +inline u8 RequestParser::Pop() { + return PopRaw<u8>(); +} + +template <> +inline u16 RequestParser::Pop() { + return PopRaw<u16>(); +} + +template <> +inline u64 RequestParser::Pop() { + const u64 lsw = Pop<u32>(); + const u64 msw = Pop<u32>(); + return msw << 32 | lsw; +} + +template <> +inline s8 RequestParser::Pop() { + return static_cast<s8>(Pop<u8>()); +} + +template <> +inline s16 RequestParser::Pop() { + return static_cast<s16>(Pop<u16>()); +} + +template <> +inline s64 RequestParser::Pop() { + return static_cast<s64>(Pop<u64>()); +} + +template <> +inline float RequestParser::Pop() { + const u32 value = Pop<u32>(); + float real; + std::memcpy(&real, &value, sizeof(real)); + return real; +} + +template <> +inline double RequestParser::Pop() { + const u64 value = Pop<u64>(); + double real; + std::memcpy(&real, &value, sizeof(real)); + return real; +} + +template <> +inline bool RequestParser::Pop() { + return Pop<u8>() != 0; +} + +template <> +inline Result RequestParser::Pop() { + return Result{Pop<u32>()}; +} + +template <typename T> +void RequestParser::Pop(T& value) { + value = Pop<T>(); +} + +template <typename First, typename... Other> +void RequestParser::Pop(First& first_value, Other&... other_values) { + first_value = Pop<First>(); + Pop(other_values...); +} + +} // namespace IPC |