clean up old files
This commit is contained in:
		| @@ -1,59 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <cstring> | ||||
|  | ||||
| #include "common/cache_management.h" | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| #if defined(ARCHITECTURE_x86_64) | ||||
|  | ||||
| // Most cache operations are no-ops on x86 | ||||
|  | ||||
| void DataCacheLineCleanByVAToPoU(void* start, size_t size) {} | ||||
| void DataCacheLineCleanAndInvalidateByVAToPoC(void* start, size_t size) {} | ||||
| void DataCacheLineCleanByVAToPoC(void* start, size_t size) {} | ||||
| void DataCacheZeroByVA(void* start, size_t size) { | ||||
|     std::memset(start, 0, size); | ||||
| } | ||||
|  | ||||
| #elif defined(ARCHITECTURE_arm64) | ||||
|  | ||||
| // BS/DminLine is log2(cache size in words), we want size in bytes | ||||
| #define EXTRACT_DMINLINE(ctr_el0) (1 << ((((ctr_el0) >> 16) & 0xf) + 2)) | ||||
| #define EXTRACT_BS(dczid_el0) (1 << (((dczid_el0)&0xf) + 2)) | ||||
|  | ||||
| #define DEFINE_DC_OP(op_name, function_name)                                                       \ | ||||
|     void function_name(void* start, size_t size) {                                                 \ | ||||
|         size_t ctr_el0;                                                                            \ | ||||
|         asm volatile("mrs %[ctr_el0], ctr_el0\n\t" : [ctr_el0] "=r"(ctr_el0));                     \ | ||||
|         size_t cacheline_size = EXTRACT_DMINLINE(ctr_el0);                                         \ | ||||
|         uintptr_t va_start = reinterpret_cast<uintptr_t>(start);                                   \ | ||||
|         uintptr_t va_end = va_start + size;                                                        \ | ||||
|         for (uintptr_t va = va_start; va < va_end; va += cacheline_size) {                         \ | ||||
|             asm volatile("dc " #op_name ", %[va]\n\t" : : [va] "r"(va) : "memory");                \ | ||||
|         }                                                                                          \ | ||||
|     } | ||||
|  | ||||
| #define DEFINE_DC_OP_DCZID(op_name, function_name)                                                 \ | ||||
|     void function_name(void* start, size_t size) {                                                 \ | ||||
|         size_t dczid_el0;                                                                          \ | ||||
|         asm volatile("mrs %[dczid_el0], dczid_el0\n\t" : [dczid_el0] "=r"(dczid_el0));             \ | ||||
|         size_t cacheline_size = EXTRACT_BS(dczid_el0);                                             \ | ||||
|         uintptr_t va_start = reinterpret_cast<uintptr_t>(start);                                   \ | ||||
|         uintptr_t va_end = va_start + size;                                                        \ | ||||
|         for (uintptr_t va = va_start; va < va_end; va += cacheline_size) {                         \ | ||||
|             asm volatile("dc " #op_name ", %[va]\n\t" : : [va] "r"(va) : "memory");                \ | ||||
|         }                                                                                          \ | ||||
|     } | ||||
|  | ||||
| DEFINE_DC_OP(cvau, DataCacheLineCleanByVAToPoU); | ||||
| DEFINE_DC_OP(civac, DataCacheLineCleanAndInvalidateByVAToPoC); | ||||
| DEFINE_DC_OP(cvac, DataCacheLineCleanByVAToPoC); | ||||
| DEFINE_DC_OP_DCZID(zva, DataCacheZeroByVA); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| } // namespace Common | ||||
| @@ -1,27 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <cstddef> | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| // Data cache instructions enabled at EL0 by SCTLR_EL1.UCI. | ||||
| // VA = virtual address | ||||
| // PoC = point of coherency | ||||
| // PoU = point of unification | ||||
|  | ||||
| // dc cvau | ||||
| void DataCacheLineCleanByVAToPoU(void* start, size_t size); | ||||
|  | ||||
| // dc civac | ||||
| void DataCacheLineCleanAndInvalidateByVAToPoC(void* start, size_t size); | ||||
|  | ||||
| // dc cvac | ||||
| void DataCacheLineCleanByVAToPoC(void* start, size_t size); | ||||
|  | ||||
| // dc zva | ||||
| void DataCacheZeroByVA(void* start, size_t size); | ||||
|  | ||||
| } // namespace Common | ||||
| @@ -1,12 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
|  | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| @@ -1,58 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <chrono> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hardware_properties.h" | ||||
|  | ||||
| namespace Core::Timing { | ||||
|  | ||||
| namespace detail { | ||||
| constexpr u64 CNTFREQ_ADJUSTED = Hardware::CNTFREQ / 1000; | ||||
| constexpr u64 BASE_CLOCK_RATE_ADJUSTED = Hardware::BASE_CLOCK_RATE / 1000; | ||||
| } // namespace detail | ||||
|  | ||||
| [[nodiscard]] constexpr s64 msToCycles(std::chrono::milliseconds ms) { | ||||
|     return ms.count() * detail::BASE_CLOCK_RATE_ADJUSTED; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr s64 usToCycles(std::chrono::microseconds us) { | ||||
|     return us.count() * detail::BASE_CLOCK_RATE_ADJUSTED / 1000; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr s64 nsToCycles(std::chrono::nanoseconds ns) { | ||||
|     return ns.count() * detail::BASE_CLOCK_RATE_ADJUSTED / 1000000; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr u64 msToClockCycles(std::chrono::milliseconds ms) { | ||||
|     return static_cast<u64>(ms.count()) * detail::CNTFREQ_ADJUSTED; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr u64 usToClockCycles(std::chrono::microseconds us) { | ||||
|     return us.count() * detail::CNTFREQ_ADJUSTED / 1000; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr u64 nsToClockCycles(std::chrono::nanoseconds ns) { | ||||
|     return ns.count() * detail::CNTFREQ_ADJUSTED / 1000000; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr u64 CpuCyclesToClockCycles(u64 ticks) { | ||||
|     return ticks * detail::CNTFREQ_ADJUSTED / detail::BASE_CLOCK_RATE_ADJUSTED; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr std::chrono::milliseconds CyclesToMs(s64 cycles) { | ||||
|     return std::chrono::milliseconds(cycles / detail::BASE_CLOCK_RATE_ADJUSTED); | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr std::chrono::nanoseconds CyclesToNs(s64 cycles) { | ||||
|     return std::chrono::nanoseconds(cycles * 1000000 / detail::BASE_CLOCK_RATE_ADJUSTED); | ||||
| } | ||||
|  | ||||
| [[nodiscard]] constexpr std::chrono::microseconds CyclesToUs(s64 cycles) { | ||||
|     return std::chrono::microseconds(cycles * 1000 / detail::BASE_CLOCK_RATE_ADJUSTED); | ||||
| } | ||||
|  | ||||
| } // namespace Core::Timing | ||||
| @@ -1,505 +0,0 @@ | ||||
| // 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/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/server_manager.h" | ||||
|  | ||||
| namespace IPC { | ||||
|  | ||||
| constexpr Result ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301}; | ||||
|  | ||||
| class RequestHelperBase { | ||||
| protected: | ||||
|     Kernel::HLERequestContext* context = nullptr; | ||||
|     u32* cmdbuf; | ||||
|     u32 index = 0; | ||||
|  | ||||
| public: | ||||
|     explicit RequestHelperBase(u32* command_buffer) : cmdbuf(command_buffer) {} | ||||
|  | ||||
|     explicit RequestHelperBase(Kernel::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(Kernel::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, iface->GetServiceName()); | ||||
|  | ||||
|             auto next_manager = std::make_shared<Kernel::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(Kernel::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 | ||||
| @@ -1,531 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <sstream> | ||||
|  | ||||
| #include <boost/range/algorithm_ext/erase.hpp> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/scratch_buffer.h" | ||||
| #include "core/hle/ipc_helpers.h" | ||||
| #include "core/hle/kernel/hle_ipc.h" | ||||
| #include "core/hle/kernel/k_auto_object.h" | ||||
| #include "core/hle/kernel/k_handle_table.h" | ||||
| #include "core/hle/kernel/k_process.h" | ||||
| #include "core/hle/kernel/k_server_port.h" | ||||
| #include "core/hle/kernel/k_server_session.h" | ||||
| #include "core/hle/kernel/k_thread.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* service_name_) | ||||
|     : kernel{kernel_} {} | ||||
|  | ||||
| SessionRequestHandler::~SessionRequestHandler() = default; | ||||
|  | ||||
| SessionRequestManager::SessionRequestManager(KernelCore& kernel_, | ||||
|                                              Service::ServerManager& server_manager_) | ||||
|     : kernel{kernel_}, server_manager{server_manager_} {} | ||||
|  | ||||
| SessionRequestManager::~SessionRequestManager() = default; | ||||
|  | ||||
| bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& context) const { | ||||
|     if (IsDomain() && context.HasDomainMessageHeader()) { | ||||
|         const auto& message_header = context.GetDomainMessageHeader(); | ||||
|         const auto object_id = message_header.object_id; | ||||
|  | ||||
|         if (object_id > DomainHandlerCount()) { | ||||
|             LOG_CRITICAL(IPC, "object_id {} is too big!", object_id); | ||||
|             return false; | ||||
|         } | ||||
|         return !DomainHandler(object_id - 1).expired(); | ||||
|     } else { | ||||
|         return session_handler != nullptr; | ||||
|     } | ||||
| } | ||||
|  | ||||
| Result SessionRequestManager::CompleteSyncRequest(KServerSession* server_session, | ||||
|                                                   HLERequestContext& context) { | ||||
|     Result result = ResultSuccess; | ||||
|  | ||||
|     // If the session has been converted to a domain, handle the domain request | ||||
|     if (this->HasSessionRequestHandler(context)) { | ||||
|         if (IsDomain() && context.HasDomainMessageHeader()) { | ||||
|             result = HandleDomainSyncRequest(server_session, context); | ||||
|             // If there is no domain header, the regular session handler is used | ||||
|         } else if (this->HasSessionHandler()) { | ||||
|             // If this manager has an associated HLE handler, forward the request to it. | ||||
|             result = this->SessionHandler().HandleSyncRequest(*server_session, context); | ||||
|         } | ||||
|     } else { | ||||
|         ASSERT_MSG(false, "Session handler is invalid, stubbing response!"); | ||||
|         IPC::ResponseBuilder rb(context, 2); | ||||
|         rb.Push(ResultSuccess); | ||||
|     } | ||||
|  | ||||
|     if (convert_to_domain) { | ||||
|         ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance."); | ||||
|         this->ConvertToDomain(); | ||||
|         convert_to_domain = false; | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| Result SessionRequestManager::HandleDomainSyncRequest(KServerSession* server_session, | ||||
|                                                       HLERequestContext& context) { | ||||
|     if (!context.HasDomainMessageHeader()) { | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|  | ||||
|     // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs | ||||
|     ASSERT(context.GetManager().get() == this); | ||||
|  | ||||
|     // If there is a DomainMessageHeader, then this is CommandType "Request" | ||||
|     const auto& domain_message_header = context.GetDomainMessageHeader(); | ||||
|     const u32 object_id{domain_message_header.object_id}; | ||||
|     switch (domain_message_header.command) { | ||||
|     case IPC::DomainMessageHeader::CommandType::SendMessage: | ||||
|         if (object_id > this->DomainHandlerCount()) { | ||||
|             LOG_CRITICAL(IPC, | ||||
|                          "object_id {} is too big! This probably means a recent service call " | ||||
|                          "needed to return a new interface!", | ||||
|                          object_id); | ||||
|             ASSERT(false); | ||||
|             return ResultSuccess; // Ignore error if asserts are off | ||||
|         } | ||||
|         if (auto strong_ptr = this->DomainHandler(object_id - 1).lock()) { | ||||
|             return strong_ptr->HandleSyncRequest(*server_session, context); | ||||
|         } else { | ||||
|             ASSERT(false); | ||||
|             return ResultSuccess; | ||||
|         } | ||||
|  | ||||
|     case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { | ||||
|         LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); | ||||
|  | ||||
|         this->CloseDomainHandler(object_id - 1); | ||||
|  | ||||
|         IPC::ResponseBuilder rb{context, 2}; | ||||
|         rb.Push(ResultSuccess); | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|     } | ||||
|  | ||||
|     LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value()); | ||||
|     ASSERT(false); | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, | ||||
|                                      KServerSession* server_session_, KThread* thread_) | ||||
|     : server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} { | ||||
|     cmd_buf[0] = 0; | ||||
| } | ||||
|  | ||||
| HLERequestContext::~HLERequestContext() = default; | ||||
|  | ||||
| void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32_le* src_cmdbuf, | ||||
|                                            bool incoming) { | ||||
|     IPC::RequestParser rp(src_cmdbuf); | ||||
|     command_header = rp.PopRaw<IPC::CommandHeader>(); | ||||
|  | ||||
|     if (command_header->IsCloseCommand()) { | ||||
|         // Close does not populate the rest of the IPC header | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // If handle descriptor is present, add size of it | ||||
|     if (command_header->enable_handle_descriptor) { | ||||
|         handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>(); | ||||
|         if (handle_descriptor_header->send_current_pid) { | ||||
|             pid = rp.Pop<u64>(); | ||||
|         } | ||||
|         if (incoming) { | ||||
|             // Populate the object lists with the data in the IPC request. | ||||
|             incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy); | ||||
|             incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move); | ||||
|  | ||||
|             for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { | ||||
|                 incoming_copy_handles.push_back(rp.Pop<Handle>()); | ||||
|             } | ||||
|             for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) { | ||||
|                 incoming_move_handles.push_back(rp.Pop<Handle>()); | ||||
|             } | ||||
|         } else { | ||||
|             // For responses we just ignore the handles, they're empty and will be populated when | ||||
|             // translating the response. | ||||
|             rp.Skip(handle_descriptor_header->num_handles_to_copy, false); | ||||
|             rp.Skip(handle_descriptor_header->num_handles_to_move, false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buffer_x_desciptors.reserve(command_header->num_buf_x_descriptors); | ||||
|     buffer_a_desciptors.reserve(command_header->num_buf_a_descriptors); | ||||
|     buffer_b_desciptors.reserve(command_header->num_buf_b_descriptors); | ||||
|     buffer_w_desciptors.reserve(command_header->num_buf_w_descriptors); | ||||
|  | ||||
|     for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) { | ||||
|         buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>()); | ||||
|     } | ||||
|     for (u32 i = 0; i < command_header->num_buf_a_descriptors; ++i) { | ||||
|         buffer_a_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); | ||||
|     } | ||||
|     for (u32 i = 0; i < command_header->num_buf_b_descriptors; ++i) { | ||||
|         buffer_b_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); | ||||
|     } | ||||
|     for (u32 i = 0; i < command_header->num_buf_w_descriptors; ++i) { | ||||
|         buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); | ||||
|     } | ||||
|  | ||||
|     const auto buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size; | ||||
|  | ||||
|     if (!command_header->IsTipc()) { | ||||
|         // Padding to align to 16 bytes | ||||
|         rp.AlignWithPadding(); | ||||
|  | ||||
|         if (GetManager()->IsDomain() && | ||||
|             ((command_header->type == IPC::CommandType::Request || | ||||
|               command_header->type == IPC::CommandType::RequestWithContext) || | ||||
|              !incoming)) { | ||||
|             // If this is an incoming message, only CommandType "Request" has a domain header | ||||
|             // All outgoing domain messages have the domain header, if only incoming has it | ||||
|             if (incoming || domain_message_header) { | ||||
|                 domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>(); | ||||
|             } else { | ||||
|                 if (GetManager()->IsDomain()) { | ||||
|                     LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>(); | ||||
|  | ||||
|         data_payload_offset = rp.GetCurrentOffset(); | ||||
|  | ||||
|         if (domain_message_header && | ||||
|             domain_message_header->command == | ||||
|                 IPC::DomainMessageHeader::CommandType::CloseVirtualHandle) { | ||||
|             // CloseVirtualHandle command does not have SFC* or any data | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (incoming) { | ||||
|             ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I')); | ||||
|         } else { | ||||
|             ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O')); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     rp.SetCurrentOffset(buffer_c_offset); | ||||
|  | ||||
|     // For Inline buffers, the response data is written directly to buffer_c_offset | ||||
|     // and in this case we don't have any BufferDescriptorC on the request. | ||||
|     if (command_header->buf_c_descriptor_flags > | ||||
|         IPC::CommandHeader::BufferDescriptorCFlag::InlineDescriptor) { | ||||
|         if (command_header->buf_c_descriptor_flags == | ||||
|             IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) { | ||||
|             buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); | ||||
|         } else { | ||||
|             u32 num_buf_c_descriptors = | ||||
|                 static_cast<u32>(command_header->buf_c_descriptor_flags.Value()) - 2; | ||||
|  | ||||
|             // This is used to detect possible underflows, in case something is broken | ||||
|             // with the two ifs above and the flags value is == 0 || == 1. | ||||
|             ASSERT(num_buf_c_descriptors < 14); | ||||
|  | ||||
|             for (u32 i = 0; i < num_buf_c_descriptors; ++i) { | ||||
|                 buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     rp.SetCurrentOffset(data_payload_offset); | ||||
|  | ||||
|     command = rp.Pop<u32_le>(); | ||||
|     rp.Skip(1, false); // The command is actually an u64, but we don't use the high part. | ||||
| } | ||||
|  | ||||
| Result HLERequestContext::PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table, | ||||
|                                                             u32_le* src_cmdbuf) { | ||||
|     ParseCommandBuffer(handle_table, src_cmdbuf, true); | ||||
|  | ||||
|     if (command_header->IsCloseCommand()) { | ||||
|         // Close does not populate the rest of the IPC header | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|  | ||||
|     std::copy_n(src_cmdbuf, IPC::COMMAND_BUFFER_LENGTH, cmd_buf.begin()); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_thread) { | ||||
|     auto current_offset = handles_offset; | ||||
|     auto& owner_process = *requesting_thread.GetOwnerProcess(); | ||||
|     auto& handle_table = owner_process.GetHandleTable(); | ||||
|  | ||||
|     for (auto& object : outgoing_copy_objects) { | ||||
|         Handle handle{}; | ||||
|         if (object) { | ||||
|             R_TRY(handle_table.Add(&handle, object)); | ||||
|         } | ||||
|         cmd_buf[current_offset++] = handle; | ||||
|     } | ||||
|     for (auto& object : outgoing_move_objects) { | ||||
|         Handle handle{}; | ||||
|         if (object) { | ||||
|             R_TRY(handle_table.Add(&handle, object)); | ||||
|  | ||||
|             // Close our reference to the object, as it is being moved to the caller. | ||||
|             object->Close(); | ||||
|         } | ||||
|         cmd_buf[current_offset++] = handle; | ||||
|     } | ||||
|  | ||||
|     // Write the domain objects to the command buffer, these go after the raw untranslated data. | ||||
|     // TODO(Subv): This completely ignores C buffers. | ||||
|  | ||||
|     if (GetManager()->IsDomain()) { | ||||
|         current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size()); | ||||
|         for (auto& object : outgoing_domain_objects) { | ||||
|             GetManager()->AppendDomainHandler(std::move(object)); | ||||
|             cmd_buf[current_offset++] = static_cast<u32_le>(GetManager()->DomainHandlerCount()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Copy the translated command buffer back into the thread's command buffer area. | ||||
|     memory.WriteBlock(owner_process, requesting_thread.GetTLSAddress(), cmd_buf.data(), | ||||
|                       write_size * sizeof(u32)); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) const { | ||||
|     const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && | ||||
|                            BufferDescriptorA()[buffer_index].Size()}; | ||||
|     if (is_buffer_a) { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorA().size() > buffer_index, { return {}; }, | ||||
|             "BufferDescriptorA invalid buffer_index {}", buffer_index); | ||||
|         std::vector<u8> buffer(BufferDescriptorA()[buffer_index].Size()); | ||||
|         memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size()); | ||||
|         return buffer; | ||||
|     } else { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorX().size() > buffer_index, { return {}; }, | ||||
|             "BufferDescriptorX invalid buffer_index {}", buffer_index); | ||||
|         std::vector<u8> buffer(BufferDescriptorX()[buffer_index].Size()); | ||||
|         memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size()); | ||||
|         return buffer; | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { | ||||
|     static thread_local std::array<Common::ScratchBuffer<u8>, 2> read_buffer_a; | ||||
|     static thread_local std::array<Common::ScratchBuffer<u8>, 2> read_buffer_x; | ||||
|  | ||||
|     const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && | ||||
|                            BufferDescriptorA()[buffer_index].Size()}; | ||||
|     if (is_buffer_a) { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorA().size() > buffer_index, { return {}; }, | ||||
|             "BufferDescriptorA invalid buffer_index {}", buffer_index); | ||||
|         auto& read_buffer = read_buffer_a[buffer_index]; | ||||
|         read_buffer.resize_destructive(BufferDescriptorA()[buffer_index].Size()); | ||||
|         memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), read_buffer.data(), | ||||
|                          read_buffer.size()); | ||||
|         return read_buffer; | ||||
|     } else { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorX().size() > buffer_index, { return {}; }, | ||||
|             "BufferDescriptorX invalid buffer_index {}", buffer_index); | ||||
|         auto& read_buffer = read_buffer_x[buffer_index]; | ||||
|         read_buffer.resize_destructive(BufferDescriptorX()[buffer_index].Size()); | ||||
|         memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), read_buffer.data(), | ||||
|                          read_buffer.size()); | ||||
|         return read_buffer; | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, | ||||
|                                            std::size_t buffer_index) const { | ||||
|     if (size == 0) { | ||||
|         LOG_WARNING(Core, "skip empty buffer write"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     const bool is_buffer_b{BufferDescriptorB().size() > buffer_index && | ||||
|                            BufferDescriptorB()[buffer_index].Size()}; | ||||
|     const std::size_t buffer_size{GetWriteBufferSize(buffer_index)}; | ||||
|     if (size > buffer_size) { | ||||
|         LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, | ||||
|                      buffer_size); | ||||
|         size = buffer_size; // TODO(bunnei): This needs to be HW tested | ||||
|     } | ||||
|  | ||||
|     if (is_buffer_b) { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorB().size() > buffer_index && | ||||
|                 BufferDescriptorB()[buffer_index].Size() >= size, | ||||
|             { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); | ||||
|         WriteBufferB(buffer, size, buffer_index); | ||||
|     } else { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorC().size() > buffer_index && | ||||
|                 BufferDescriptorC()[buffer_index].Size() >= size, | ||||
|             { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); | ||||
|         WriteBufferC(buffer, size, buffer_index); | ||||
|     } | ||||
|  | ||||
|     return size; | ||||
| } | ||||
|  | ||||
| std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size, | ||||
|                                             std::size_t buffer_index) const { | ||||
|     if (buffer_index >= BufferDescriptorB().size() || size == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     const auto buffer_size{BufferDescriptorB()[buffer_index].Size()}; | ||||
|     if (size > buffer_size) { | ||||
|         LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, | ||||
|                      buffer_size); | ||||
|         size = buffer_size; // TODO(bunnei): This needs to be HW tested | ||||
|     } | ||||
|  | ||||
|     memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); | ||||
|     return size; | ||||
| } | ||||
|  | ||||
| std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size, | ||||
|                                             std::size_t buffer_index) const { | ||||
|     if (buffer_index >= BufferDescriptorC().size() || size == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     const auto buffer_size{BufferDescriptorC()[buffer_index].Size()}; | ||||
|     if (size > buffer_size) { | ||||
|         LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, | ||||
|                      buffer_size); | ||||
|         size = buffer_size; // TODO(bunnei): This needs to be HW tested | ||||
|     } | ||||
|  | ||||
|     memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); | ||||
|     return size; | ||||
| } | ||||
|  | ||||
| std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const { | ||||
|     const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && | ||||
|                            BufferDescriptorA()[buffer_index].Size()}; | ||||
|     if (is_buffer_a) { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorA().size() > buffer_index, { return 0; }, | ||||
|             "BufferDescriptorA invalid buffer_index {}", buffer_index); | ||||
|         return BufferDescriptorA()[buffer_index].Size(); | ||||
|     } else { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorX().size() > buffer_index, { return 0; }, | ||||
|             "BufferDescriptorX invalid buffer_index {}", buffer_index); | ||||
|         return BufferDescriptorX()[buffer_index].Size(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) const { | ||||
|     const bool is_buffer_b{BufferDescriptorB().size() > buffer_index && | ||||
|                            BufferDescriptorB()[buffer_index].Size()}; | ||||
|     if (is_buffer_b) { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorB().size() > buffer_index, { return 0; }, | ||||
|             "BufferDescriptorB invalid buffer_index {}", buffer_index); | ||||
|         return BufferDescriptorB()[buffer_index].Size(); | ||||
|     } else { | ||||
|         ASSERT_OR_EXECUTE_MSG( | ||||
|             BufferDescriptorC().size() > buffer_index, { return 0; }, | ||||
|             "BufferDescriptorC invalid buffer_index {}", buffer_index); | ||||
|         return BufferDescriptorC()[buffer_index].Size(); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| bool HLERequestContext::CanReadBuffer(std::size_t buffer_index) const { | ||||
|     const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && | ||||
|                            BufferDescriptorA()[buffer_index].Size()}; | ||||
|  | ||||
|     if (is_buffer_a) { | ||||
|         return BufferDescriptorA().size() > buffer_index; | ||||
|     } else { | ||||
|         return BufferDescriptorX().size() > buffer_index; | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const { | ||||
|     const bool is_buffer_b{BufferDescriptorB().size() > buffer_index && | ||||
|                            BufferDescriptorB()[buffer_index].Size()}; | ||||
|  | ||||
|     if (is_buffer_b) { | ||||
|         return BufferDescriptorB().size() > buffer_index; | ||||
|     } else { | ||||
|         return BufferDescriptorC().size() > buffer_index; | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::string HLERequestContext::Description() const { | ||||
|     if (!command_header) { | ||||
|         return "No command header available"; | ||||
|     } | ||||
|     std::ostringstream s; | ||||
|     s << "IPC::CommandHeader: Type:" << static_cast<u32>(command_header->type.Value()); | ||||
|     s << ", X(Pointer):" << command_header->num_buf_x_descriptors; | ||||
|     if (command_header->num_buf_x_descriptors) { | ||||
|         s << '['; | ||||
|         for (u64 i = 0; i < command_header->num_buf_x_descriptors; ++i) { | ||||
|             s << "0x" << std::hex << BufferDescriptorX()[i].Size(); | ||||
|             if (i < command_header->num_buf_x_descriptors - 1) | ||||
|                 s << ", "; | ||||
|         } | ||||
|         s << ']'; | ||||
|     } | ||||
|     s << ", A(Send):" << command_header->num_buf_a_descriptors; | ||||
|     if (command_header->num_buf_a_descriptors) { | ||||
|         s << '['; | ||||
|         for (u64 i = 0; i < command_header->num_buf_a_descriptors; ++i) { | ||||
|             s << "0x" << std::hex << BufferDescriptorA()[i].Size(); | ||||
|             if (i < command_header->num_buf_a_descriptors - 1) | ||||
|                 s << ", "; | ||||
|         } | ||||
|         s << ']'; | ||||
|     } | ||||
|     s << ", B(Receive):" << command_header->num_buf_b_descriptors; | ||||
|     if (command_header->num_buf_b_descriptors) { | ||||
|         s << '['; | ||||
|         for (u64 i = 0; i < command_header->num_buf_b_descriptors; ++i) { | ||||
|             s << "0x" << std::hex << BufferDescriptorB()[i].Size(); | ||||
|             if (i < command_header->num_buf_b_descriptors - 1) | ||||
|                 s << ", "; | ||||
|         } | ||||
|         s << ']'; | ||||
|     } | ||||
|     s << ", C(ReceiveList):" << BufferDescriptorC().size(); | ||||
|     if (!BufferDescriptorC().empty()) { | ||||
|         s << '['; | ||||
|         for (u64 i = 0; i < BufferDescriptorC().size(); ++i) { | ||||
|             s << "0x" << std::hex << BufferDescriptorC()[i].Size(); | ||||
|             if (i < BufferDescriptorC().size() - 1) | ||||
|                 s << ", "; | ||||
|         } | ||||
|         s << ']'; | ||||
|     } | ||||
|     s << ", data_size:" << command_header->data_size.Value(); | ||||
|  | ||||
|     return s.str(); | ||||
| } | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,421 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <span> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/concepts.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/hle/ipc.h" | ||||
| #include "core/hle/kernel/svc_common.h" | ||||
|  | ||||
| union Result; | ||||
|  | ||||
| namespace Core::Memory { | ||||
| class Memory; | ||||
| } | ||||
|  | ||||
| namespace IPC { | ||||
| class ResponseBuilder; | ||||
| } | ||||
|  | ||||
| namespace Service { | ||||
| class ServiceFrameworkBase; | ||||
| class ServerManager; | ||||
| } // namespace Service | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| class Domain; | ||||
| class HLERequestContext; | ||||
| class KAutoObject; | ||||
| class KernelCore; | ||||
| class KEvent; | ||||
| class KHandleTable; | ||||
| class KServerPort; | ||||
| class KProcess; | ||||
| class KServerSession; | ||||
| class KThread; | ||||
| class KReadableEvent; | ||||
| class KSession; | ||||
| class SessionRequestManager; | ||||
|  | ||||
| /** | ||||
|  * Interface implemented by HLE Session handlers. | ||||
|  * This can be provided to a ServerSession in order to hook into several relevant events | ||||
|  * (such as a new connection or a SyncRequest) so they can be implemented in the emulator. | ||||
|  */ | ||||
| class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> { | ||||
| public: | ||||
|     SessionRequestHandler(KernelCore& kernel_, const char* service_name_); | ||||
|     virtual ~SessionRequestHandler(); | ||||
|  | ||||
|     /** | ||||
|      * Handles a sync request from the emulated application. | ||||
|      * @param server_session The ServerSession that was triggered for this sync request, | ||||
|      * it should be used to differentiate which client (As in ClientSession) we're answering to. | ||||
|      * TODO(Subv): Use a wrapper structure to hold all the information relevant to | ||||
|      * this request (ServerSession, Originator thread, Translated command buffer, etc). | ||||
|      * @returns Result the result code of the translate operation. | ||||
|      */ | ||||
|     virtual Result HandleSyncRequest(Kernel::KServerSession& session, | ||||
|                                      Kernel::HLERequestContext& context) = 0; | ||||
|  | ||||
| protected: | ||||
|     KernelCore& kernel; | ||||
| }; | ||||
|  | ||||
| using SessionRequestHandlerWeakPtr = std::weak_ptr<SessionRequestHandler>; | ||||
| using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; | ||||
|  | ||||
| /** | ||||
|  * Manages the underlying HLE requests for a session, and whether (or not) the session should be | ||||
|  * treated as a domain. This is managed separately from server sessions, as this state is shared | ||||
|  * when objects are cloned. | ||||
|  */ | ||||
| class SessionRequestManager final { | ||||
| public: | ||||
|     explicit SessionRequestManager(KernelCore& kernel, Service::ServerManager& server_manager); | ||||
|     ~SessionRequestManager(); | ||||
|  | ||||
|     bool IsDomain() const { | ||||
|         return is_domain; | ||||
|     } | ||||
|  | ||||
|     void ConvertToDomain() { | ||||
|         domain_handlers = {session_handler}; | ||||
|         is_domain = true; | ||||
|     } | ||||
|  | ||||
|     void ConvertToDomainOnRequestEnd() { | ||||
|         convert_to_domain = true; | ||||
|     } | ||||
|  | ||||
|     std::size_t DomainHandlerCount() const { | ||||
|         return domain_handlers.size(); | ||||
|     } | ||||
|  | ||||
|     bool HasSessionHandler() const { | ||||
|         return session_handler != nullptr; | ||||
|     } | ||||
|  | ||||
|     SessionRequestHandler& SessionHandler() { | ||||
|         return *session_handler; | ||||
|     } | ||||
|  | ||||
|     const SessionRequestHandler& SessionHandler() const { | ||||
|         return *session_handler; | ||||
|     } | ||||
|  | ||||
|     void CloseDomainHandler(std::size_t index) { | ||||
|         if (index < DomainHandlerCount()) { | ||||
|             domain_handlers[index] = nullptr; | ||||
|         } else { | ||||
|             ASSERT_MSG(false, "Unexpected handler index {}", index); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     SessionRequestHandlerWeakPtr DomainHandler(std::size_t index) const { | ||||
|         ASSERT_MSG(index < DomainHandlerCount(), "Unexpected handler index {}", index); | ||||
|         return domain_handlers.at(index); | ||||
|     } | ||||
|  | ||||
|     void AppendDomainHandler(SessionRequestHandlerPtr&& handler) { | ||||
|         domain_handlers.emplace_back(std::move(handler)); | ||||
|     } | ||||
|  | ||||
|     void SetSessionHandler(SessionRequestHandlerPtr&& handler) { | ||||
|         session_handler = std::move(handler); | ||||
|     } | ||||
|  | ||||
|     bool HasSessionRequestHandler(const HLERequestContext& context) const; | ||||
|  | ||||
|     Result HandleDomainSyncRequest(KServerSession* server_session, HLERequestContext& context); | ||||
|     Result CompleteSyncRequest(KServerSession* server_session, HLERequestContext& context); | ||||
|  | ||||
|     Service::ServerManager& GetServerManager() { | ||||
|         return server_manager; | ||||
|     } | ||||
|  | ||||
|     // TODO: remove this when sm: is implemented with the proper IUserInterface | ||||
|     // abstraction, creating a new C++ handler object for each session: | ||||
|  | ||||
|     bool GetIsInitializedForSm() const { | ||||
|         return is_initialized_for_sm; | ||||
|     } | ||||
|  | ||||
|     void SetIsInitializedForSm() { | ||||
|         is_initialized_for_sm = true; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     bool convert_to_domain{}; | ||||
|     bool is_domain{}; | ||||
|     bool is_initialized_for_sm{}; | ||||
|     SessionRequestHandlerPtr session_handler; | ||||
|     std::vector<SessionRequestHandlerPtr> domain_handlers; | ||||
|  | ||||
| private: | ||||
|     KernelCore& kernel; | ||||
|     Service::ServerManager& server_manager; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Class containing information about an in-flight IPC request being handled by an HLE service | ||||
|  * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and | ||||
|  * when possible use the APIs in this class to service the request. | ||||
|  * | ||||
|  * HLE handle protocol | ||||
|  * =================== | ||||
|  * | ||||
|  * To avoid needing HLE services to keep a separate handle table, or having to directly modify the | ||||
|  * requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel | ||||
|  * will decode the incoming handles into object pointers and insert a id in the buffer where the | ||||
|  * handle would normally be. The service then calls GetIncomingHandle() with that id to get the | ||||
|  * pointer to the object. Similarly, instead of inserting a handle into the command buffer, the | ||||
|  * service calls AddOutgoingHandle() and stores the returned id where the handle would normally go. | ||||
|  * | ||||
|  * The end result is similar to just giving services their own real handle tables, but since these | ||||
|  * ids are local to a specific context, it avoids requiring services to manage handles for objects | ||||
|  * across multiple calls and ensuring that unneeded handles are cleaned up. | ||||
|  */ | ||||
| class HLERequestContext { | ||||
| public: | ||||
|     explicit HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory, | ||||
|                                KServerSession* session, KThread* thread); | ||||
|     ~HLERequestContext(); | ||||
|  | ||||
|     /// Returns a pointer to the IPC command buffer for this request. | ||||
|     [[nodiscard]] u32* CommandBuffer() { | ||||
|         return cmd_buf.data(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the session through which this request was made. This can be used as a map key to | ||||
|      * access per-client data on services. | ||||
|      */ | ||||
|     [[nodiscard]] Kernel::KServerSession* Session() { | ||||
|         return server_session; | ||||
|     } | ||||
|  | ||||
|     /// Populates this context with data from the requesting process/thread. | ||||
|     Result PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table, u32_le* src_cmdbuf); | ||||
|  | ||||
|     /// Writes data from this context back to the requesting process/thread. | ||||
|     Result WriteToOutgoingCommandBuffer(KThread& requesting_thread); | ||||
|  | ||||
|     [[nodiscard]] u32_le GetHipcCommand() const { | ||||
|         return command; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] u32_le GetTipcCommand() const { | ||||
|         return static_cast<u32_le>(command_header->type.Value()) - | ||||
|                static_cast<u32_le>(IPC::CommandType::TIPC_CommandRegion); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] u32_le GetCommand() const { | ||||
|         return command_header->IsTipc() ? GetTipcCommand() : GetHipcCommand(); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] bool IsTipc() const { | ||||
|         return command_header->IsTipc(); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] IPC::CommandType GetCommandType() const { | ||||
|         return command_header->type; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] u64 GetPID() const { | ||||
|         return pid; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] u32 GetDataPayloadOffset() const { | ||||
|         return data_payload_offset; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] const std::vector<IPC::BufferDescriptorX>& BufferDescriptorX() const { | ||||
|         return buffer_x_desciptors; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorA() const { | ||||
|         return buffer_a_desciptors; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorB() const { | ||||
|         return buffer_b_desciptors; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const { | ||||
|         return buffer_c_desciptors; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] const IPC::DomainMessageHeader& GetDomainMessageHeader() const { | ||||
|         return domain_message_header.value(); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] bool HasDomainMessageHeader() const { | ||||
|         return domain_message_header.has_value(); | ||||
|     } | ||||
|  | ||||
|     /// Helper function to get a span of a buffer using the appropriate buffer descriptor | ||||
|     [[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to read a copy of a buffer using the appropriate buffer descriptor | ||||
|     [[nodiscard]] std::vector<u8> ReadBufferCopy(std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to write a buffer using the appropriate buffer descriptor | ||||
|     std::size_t WriteBuffer(const void* buffer, std::size_t size, | ||||
|                             std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to write buffer B | ||||
|     std::size_t WriteBufferB(const void* buffer, std::size_t size, | ||||
|                              std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to write buffer C | ||||
|     std::size_t WriteBufferC(const void* buffer, std::size_t size, | ||||
|                              std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /* Helper function to write a buffer using the appropriate buffer descriptor | ||||
|      * | ||||
|      * @tparam T an arbitrary container that satisfies the | ||||
|      *         ContiguousContainer concept in the C++ standard library or a trivially copyable type. | ||||
|      * | ||||
|      * @param data         The container/data to write into a buffer. | ||||
|      * @param buffer_index The buffer in particular to write to. | ||||
|      */ | ||||
|     template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>> | ||||
|     std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const { | ||||
|         if constexpr (Common::IsContiguousContainer<T>) { | ||||
|             using ContiguousType = typename T::value_type; | ||||
|             static_assert(std::is_trivially_copyable_v<ContiguousType>, | ||||
|                           "Container to WriteBuffer must contain trivially copyable objects"); | ||||
|             return WriteBuffer(std::data(data), std::size(data) * sizeof(ContiguousType), | ||||
|                                buffer_index); | ||||
|         } else { | ||||
|             static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); | ||||
|             return WriteBuffer(&data, sizeof(T), buffer_index); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Helper function to get the size of the input buffer | ||||
|     [[nodiscard]] std::size_t GetReadBufferSize(std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to get the size of the output buffer | ||||
|     [[nodiscard]] std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to derive the number of elements able to be contained in the read buffer | ||||
|     template <typename T> | ||||
|     [[nodiscard]] std::size_t GetReadBufferNumElements(std::size_t buffer_index = 0) const { | ||||
|         return GetReadBufferSize(buffer_index) / sizeof(T); | ||||
|     } | ||||
|  | ||||
|     /// Helper function to derive the number of elements able to be contained in the write buffer | ||||
|     template <typename T> | ||||
|     [[nodiscard]] std::size_t GetWriteBufferNumElements(std::size_t buffer_index = 0) const { | ||||
|         return GetWriteBufferSize(buffer_index) / sizeof(T); | ||||
|     } | ||||
|  | ||||
|     /// Helper function to test whether the input buffer at buffer_index can be read | ||||
|     [[nodiscard]] bool CanReadBuffer(std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     /// Helper function to test whether the output buffer at buffer_index can be written | ||||
|     [[nodiscard]] bool CanWriteBuffer(std::size_t buffer_index = 0) const; | ||||
|  | ||||
|     [[nodiscard]] Handle GetCopyHandle(std::size_t index) const { | ||||
|         return incoming_copy_handles.at(index); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] Handle GetMoveHandle(std::size_t index) const { | ||||
|         return incoming_move_handles.at(index); | ||||
|     } | ||||
|  | ||||
|     void AddMoveObject(KAutoObject* object) { | ||||
|         outgoing_move_objects.emplace_back(object); | ||||
|     } | ||||
|  | ||||
|     void AddCopyObject(KAutoObject* object) { | ||||
|         outgoing_copy_objects.emplace_back(object); | ||||
|     } | ||||
|  | ||||
|     void AddDomainObject(SessionRequestHandlerPtr object) { | ||||
|         outgoing_domain_objects.emplace_back(std::move(object)); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     std::shared_ptr<T> GetDomainHandler(std::size_t index) const { | ||||
|         return std::static_pointer_cast<T>(GetManager()->DomainHandler(index).lock()); | ||||
|     } | ||||
|  | ||||
|     void SetSessionRequestManager(std::weak_ptr<SessionRequestManager> manager_) { | ||||
|         manager = manager_; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] std::string Description() const; | ||||
|  | ||||
|     [[nodiscard]] KThread& GetThread() { | ||||
|         return *thread; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] std::shared_ptr<SessionRequestManager> GetManager() const { | ||||
|         return manager.lock(); | ||||
|     } | ||||
|  | ||||
|     bool GetIsDeferred() const { | ||||
|         return is_deferred; | ||||
|     } | ||||
|  | ||||
|     void SetIsDeferred(bool is_deferred_ = true) { | ||||
|         is_deferred = is_deferred_; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     friend class IPC::ResponseBuilder; | ||||
|  | ||||
|     void ParseCommandBuffer(const KHandleTable& handle_table, u32_le* src_cmdbuf, bool incoming); | ||||
|  | ||||
|     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; | ||||
|     Kernel::KServerSession* server_session{}; | ||||
|     KThread* thread; | ||||
|  | ||||
|     std::vector<Handle> incoming_move_handles; | ||||
|     std::vector<Handle> incoming_copy_handles; | ||||
|  | ||||
|     std::vector<KAutoObject*> outgoing_move_objects; | ||||
|     std::vector<KAutoObject*> outgoing_copy_objects; | ||||
|     std::vector<SessionRequestHandlerPtr> outgoing_domain_objects; | ||||
|  | ||||
|     std::optional<IPC::CommandHeader> command_header; | ||||
|     std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header; | ||||
|     std::optional<IPC::DataPayloadHeader> data_payload_header; | ||||
|     std::optional<IPC::DomainMessageHeader> domain_message_header; | ||||
|     std::vector<IPC::BufferDescriptorX> buffer_x_desciptors; | ||||
|     std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors; | ||||
|     std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors; | ||||
|     std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors; | ||||
|     std::vector<IPC::BufferDescriptorC> buffer_c_desciptors; | ||||
|  | ||||
|     u32_le command{}; | ||||
|     u64 pid{}; | ||||
|     u32 write_size{}; | ||||
|     u32 data_payload_offset{}; | ||||
|     u32 handles_offset{}; | ||||
|     u32 domain_offset{}; | ||||
|  | ||||
|     std::weak_ptr<SessionRequestManager> manager{}; | ||||
|     bool is_deferred{false}; | ||||
|  | ||||
|     KernelCore& kernel; | ||||
|     Core::Memory::Memory& memory; | ||||
| }; | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,238 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <boost/intrusive/list.hpp> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "core/hle/kernel/slab_helpers.h" | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| class KernelCore; | ||||
|  | ||||
| class KLinkedListNode : public boost::intrusive::list_base_hook<>, | ||||
|                         public KSlabAllocated<KLinkedListNode> { | ||||
|  | ||||
| public: | ||||
|     explicit KLinkedListNode(KernelCore&) {} | ||||
|     KLinkedListNode() = default; | ||||
|  | ||||
|     void Initialize(void* it) { | ||||
|         m_item = it; | ||||
|     } | ||||
|  | ||||
|     void* GetItem() const { | ||||
|         return m_item; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void* m_item = nullptr; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class KLinkedList : private boost::intrusive::list<KLinkedListNode> { | ||||
| private: | ||||
|     using BaseList = boost::intrusive::list<KLinkedListNode>; | ||||
|  | ||||
| public: | ||||
|     template <bool Const> | ||||
|     class Iterator; | ||||
|  | ||||
|     using value_type = T; | ||||
|     using size_type = size_t; | ||||
|     using difference_type = ptrdiff_t; | ||||
|     using pointer = value_type*; | ||||
|     using const_pointer = const value_type*; | ||||
|     using reference = value_type&; | ||||
|     using const_reference = const value_type&; | ||||
|     using iterator = Iterator<false>; | ||||
|     using const_iterator = Iterator<true>; | ||||
|     using reverse_iterator = std::reverse_iterator<iterator>; | ||||
|     using const_reverse_iterator = std::reverse_iterator<const_iterator>; | ||||
|  | ||||
|     template <bool Const> | ||||
|     class Iterator { | ||||
|     private: | ||||
|         using BaseIterator = BaseList::iterator; | ||||
|         friend class KLinkedList; | ||||
|  | ||||
|     public: | ||||
|         using iterator_category = std::bidirectional_iterator_tag; | ||||
|         using value_type = typename KLinkedList::value_type; | ||||
|         using difference_type = typename KLinkedList::difference_type; | ||||
|         using pointer = std::conditional_t<Const, KLinkedList::const_pointer, KLinkedList::pointer>; | ||||
|         using reference = | ||||
|             std::conditional_t<Const, KLinkedList::const_reference, KLinkedList::reference>; | ||||
|  | ||||
|     public: | ||||
|         explicit Iterator(BaseIterator it) : m_base_it(it) {} | ||||
|  | ||||
|         pointer GetItem() const { | ||||
|             return static_cast<pointer>(m_base_it->GetItem()); | ||||
|         } | ||||
|  | ||||
|         bool operator==(const Iterator& rhs) const { | ||||
|             return m_base_it == rhs.m_base_it; | ||||
|         } | ||||
|  | ||||
|         bool operator!=(const Iterator& rhs) const { | ||||
|             return !(*this == rhs); | ||||
|         } | ||||
|  | ||||
|         pointer operator->() const { | ||||
|             return this->GetItem(); | ||||
|         } | ||||
|  | ||||
|         reference operator*() const { | ||||
|             return *this->GetItem(); | ||||
|         } | ||||
|  | ||||
|         Iterator& operator++() { | ||||
|             ++m_base_it; | ||||
|             return *this; | ||||
|         } | ||||
|  | ||||
|         Iterator& operator--() { | ||||
|             --m_base_it; | ||||
|             return *this; | ||||
|         } | ||||
|  | ||||
|         Iterator operator++(int) { | ||||
|             const Iterator it{*this}; | ||||
|             ++(*this); | ||||
|             return it; | ||||
|         } | ||||
|  | ||||
|         Iterator operator--(int) { | ||||
|             const Iterator it{*this}; | ||||
|             --(*this); | ||||
|             return it; | ||||
|         } | ||||
|  | ||||
|         operator Iterator<true>() const { | ||||
|             return Iterator<true>(m_base_it); | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         BaseIterator m_base_it; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     constexpr KLinkedList(KernelCore& kernel_) : BaseList(), kernel{kernel_} {} | ||||
|  | ||||
|     ~KLinkedList() { | ||||
|         // Erase all elements. | ||||
|         for (auto it = begin(); it != end(); it = erase(it)) { | ||||
|         } | ||||
|  | ||||
|         // Ensure we succeeded. | ||||
|         ASSERT(this->empty()); | ||||
|     } | ||||
|  | ||||
|     // Iterator accessors. | ||||
|     iterator begin() { | ||||
|         return iterator(BaseList::begin()); | ||||
|     } | ||||
|  | ||||
|     const_iterator begin() const { | ||||
|         return const_iterator(BaseList::begin()); | ||||
|     } | ||||
|  | ||||
|     iterator end() { | ||||
|         return iterator(BaseList::end()); | ||||
|     } | ||||
|  | ||||
|     const_iterator end() const { | ||||
|         return const_iterator(BaseList::end()); | ||||
|     } | ||||
|  | ||||
|     const_iterator cbegin() const { | ||||
|         return this->begin(); | ||||
|     } | ||||
|  | ||||
|     const_iterator cend() const { | ||||
|         return this->end(); | ||||
|     } | ||||
|  | ||||
|     reverse_iterator rbegin() { | ||||
|         return reverse_iterator(this->end()); | ||||
|     } | ||||
|  | ||||
|     const_reverse_iterator rbegin() const { | ||||
|         return const_reverse_iterator(this->end()); | ||||
|     } | ||||
|  | ||||
|     reverse_iterator rend() { | ||||
|         return reverse_iterator(this->begin()); | ||||
|     } | ||||
|  | ||||
|     const_reverse_iterator rend() const { | ||||
|         return const_reverse_iterator(this->begin()); | ||||
|     } | ||||
|  | ||||
|     const_reverse_iterator crbegin() const { | ||||
|         return this->rbegin(); | ||||
|     } | ||||
|  | ||||
|     const_reverse_iterator crend() const { | ||||
|         return this->rend(); | ||||
|     } | ||||
|  | ||||
|     // Content management. | ||||
|     using BaseList::empty; | ||||
|     using BaseList::size; | ||||
|  | ||||
|     reference back() { | ||||
|         return *(--this->end()); | ||||
|     } | ||||
|  | ||||
|     const_reference back() const { | ||||
|         return *(--this->end()); | ||||
|     } | ||||
|  | ||||
|     reference front() { | ||||
|         return *this->begin(); | ||||
|     } | ||||
|  | ||||
|     const_reference front() const { | ||||
|         return *this->begin(); | ||||
|     } | ||||
|  | ||||
|     iterator insert(const_iterator pos, reference ref) { | ||||
|         KLinkedListNode* new_node = KLinkedListNode::Allocate(kernel); | ||||
|         ASSERT(new_node != nullptr); | ||||
|         new_node->Initialize(std::addressof(ref)); | ||||
|         return iterator(BaseList::insert(pos.m_base_it, *new_node)); | ||||
|     } | ||||
|  | ||||
|     void push_back(reference ref) { | ||||
|         this->insert(this->end(), ref); | ||||
|     } | ||||
|  | ||||
|     void push_front(reference ref) { | ||||
|         this->insert(this->begin(), ref); | ||||
|     } | ||||
|  | ||||
|     void pop_back() { | ||||
|         this->erase(--this->end()); | ||||
|     } | ||||
|  | ||||
|     void pop_front() { | ||||
|         this->erase(this->begin()); | ||||
|     } | ||||
|  | ||||
|     iterator erase(const iterator pos) { | ||||
|         KLinkedListNode* freed_node = std::addressof(*pos.m_base_it); | ||||
|         iterator ret = iterator(BaseList::erase(pos.m_base_it)); | ||||
|         KLinkedListNode::Free(kernel, freed_node); | ||||
|  | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     KernelCore& kernel; | ||||
| }; | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,201 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "common/alignment.h" | ||||
| #include "common/literals.h" | ||||
| #include "core/hle/kernel/k_memory_layout.h" | ||||
| #include "core/hle/kernel/k_memory_manager.h" | ||||
| #include "core/hle/kernel/k_system_control.h" | ||||
| #include "core/hle/kernel/k_trace.h" | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| using namespace Common::Literals; | ||||
|  | ||||
| constexpr size_t CarveoutAlignment = 0x20000; | ||||
| constexpr size_t CarveoutSizeMax = (512_MiB) - CarveoutAlignment; | ||||
|  | ||||
| bool SetupPowerManagementControllerMemoryRegion(KMemoryLayout& memory_layout) { | ||||
|     // Above firmware 2.0.0, the PMC is not mappable. | ||||
|     return memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|                0x7000E000, 0x400, KMemoryRegionType_None | KMemoryRegionAttr_NoUserMap) && | ||||
|            memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|                0x7000E400, 0xC00, | ||||
|                KMemoryRegionType_PowerManagementController | KMemoryRegionAttr_NoUserMap); | ||||
| } | ||||
|  | ||||
| void InsertPoolPartitionRegionIntoBothTrees(KMemoryLayout& memory_layout, size_t start, size_t size, | ||||
|                                             KMemoryRegionType phys_type, | ||||
|                                             KMemoryRegionType virt_type, u32& cur_attr) { | ||||
|     const u32 attr = cur_attr++; | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert(start, size, | ||||
|                                                               static_cast<u32>(phys_type), attr)); | ||||
|     const KMemoryRegion* phys = memory_layout.GetPhysicalMemoryRegionTree().FindByTypeAndAttribute( | ||||
|         static_cast<u32>(phys_type), attr); | ||||
|     ASSERT(phys != nullptr); | ||||
|     ASSERT(phys->GetEndAddress() != 0); | ||||
|     ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(phys->GetPairAddress(), size, | ||||
|                                                              static_cast<u32>(virt_type), attr)); | ||||
| } | ||||
|  | ||||
| } // namespace | ||||
|  | ||||
| namespace Init { | ||||
|  | ||||
| void SetupDevicePhysicalMemoryRegions(KMemoryLayout& memory_layout) { | ||||
|     ASSERT(SetupPowerManagementControllerMemoryRegion(memory_layout)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x70019000, 0x1000, KMemoryRegionType_MemoryController | KMemoryRegionAttr_NoUserMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x7001C000, 0x1000, KMemoryRegionType_MemoryController0 | KMemoryRegionAttr_NoUserMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x7001D000, 0x1000, KMemoryRegionType_MemoryController1 | KMemoryRegionAttr_NoUserMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x50040000, 0x1000, KMemoryRegionType_None | KMemoryRegionAttr_NoUserMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x50041000, 0x1000, | ||||
|         KMemoryRegionType_InterruptDistributor | KMemoryRegionAttr_ShouldKernelMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x50042000, 0x1000, | ||||
|         KMemoryRegionType_InterruptCpuInterface | KMemoryRegionAttr_ShouldKernelMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x50043000, 0x1D000, KMemoryRegionType_None | KMemoryRegionAttr_NoUserMap)); | ||||
|  | ||||
|     // Map IRAM unconditionally, to support debug-logging-to-iram build config. | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x40000000, 0x40000, KMemoryRegionType_LegacyLpsIram | KMemoryRegionAttr_ShouldKernelMap)); | ||||
|  | ||||
|     // Above firmware 2.0.0, prevent mapping the bpmp exception vectors or the ipatch region. | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x6000F000, 0x1000, KMemoryRegionType_None | KMemoryRegionAttr_NoUserMap)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         0x6001DC00, 0x400, KMemoryRegionType_None | KMemoryRegionAttr_NoUserMap)); | ||||
| } | ||||
|  | ||||
| void SetupDramPhysicalMemoryRegions(KMemoryLayout& memory_layout) { | ||||
|     const size_t intended_memory_size = KSystemControl::Init::GetIntendedMemorySize(); | ||||
|     const PAddr physical_memory_base_address = | ||||
|         KSystemControl::Init::GetKernelPhysicalBaseAddress(DramPhysicalAddress); | ||||
|  | ||||
|     // Insert blocks into the tree. | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         physical_memory_base_address, intended_memory_size, KMemoryRegionType_Dram)); | ||||
|     ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|         physical_memory_base_address, ReservedEarlyDramSize, KMemoryRegionType_DramReservedEarly)); | ||||
|  | ||||
|     // Insert the KTrace block at the end of Dram, if KTrace is enabled. | ||||
|     static_assert(!IsKTraceEnabled || KTraceBufferSize > 0); | ||||
|     if constexpr (IsKTraceEnabled) { | ||||
|         const PAddr ktrace_buffer_phys_addr = | ||||
|             physical_memory_base_address + intended_memory_size - KTraceBufferSize; | ||||
|         ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( | ||||
|             ktrace_buffer_phys_addr, KTraceBufferSize, KMemoryRegionType_KernelTraceBuffer)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SetupPoolPartitionMemoryRegions(KMemoryLayout& memory_layout) { | ||||
|     // Start by identifying the extents of the DRAM memory region. | ||||
|     const auto dram_extents = memory_layout.GetMainMemoryPhysicalExtents(); | ||||
|     ASSERT(dram_extents.GetEndAddress() != 0); | ||||
|  | ||||
|     // Determine the end of the pool region. | ||||
|     const u64 pool_end = dram_extents.GetEndAddress() - KTraceBufferSize; | ||||
|  | ||||
|     // Find the start of the kernel DRAM region. | ||||
|     const KMemoryRegion* kernel_dram_region = | ||||
|         memory_layout.GetPhysicalMemoryRegionTree().FindFirstDerived( | ||||
|             KMemoryRegionType_DramKernelBase); | ||||
|     ASSERT(kernel_dram_region != nullptr); | ||||
|  | ||||
|     const u64 kernel_dram_start = kernel_dram_region->GetAddress(); | ||||
|     ASSERT(Common::IsAligned(kernel_dram_start, CarveoutAlignment)); | ||||
|  | ||||
|     // Find the start of the pool partitions region. | ||||
|     const KMemoryRegion* pool_partitions_region = | ||||
|         memory_layout.GetPhysicalMemoryRegionTree().FindByTypeAndAttribute( | ||||
|             KMemoryRegionType_DramPoolPartition, 0); | ||||
|     ASSERT(pool_partitions_region != nullptr); | ||||
|     const u64 pool_partitions_start = pool_partitions_region->GetAddress(); | ||||
|  | ||||
|     // Setup the pool partition layouts. | ||||
|     // On 5.0.0+, setup modern 4-pool-partition layout. | ||||
|  | ||||
|     // Get Application and Applet pool sizes. | ||||
|     const size_t application_pool_size = KSystemControl::Init::GetApplicationPoolSize(); | ||||
|     const size_t applet_pool_size = KSystemControl::Init::GetAppletPoolSize(); | ||||
|     const size_t unsafe_system_pool_min_size = | ||||
|         KSystemControl::Init::GetMinimumNonSecureSystemPoolSize(); | ||||
|  | ||||
|     // Decide on starting addresses for our pools. | ||||
|     const u64 application_pool_start = pool_end - application_pool_size; | ||||
|     const u64 applet_pool_start = application_pool_start - applet_pool_size; | ||||
|     const u64 unsafe_system_pool_start = std::min( | ||||
|         kernel_dram_start + CarveoutSizeMax, | ||||
|         Common::AlignDown(applet_pool_start - unsafe_system_pool_min_size, CarveoutAlignment)); | ||||
|     const size_t unsafe_system_pool_size = applet_pool_start - unsafe_system_pool_start; | ||||
|  | ||||
|     // We want to arrange application pool depending on where the middle of dram is. | ||||
|     const u64 dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2; | ||||
|     u32 cur_pool_attr = 0; | ||||
|     size_t total_overhead_size = 0; | ||||
|     if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) { | ||||
|         InsertPoolPartitionRegionIntoBothTrees( | ||||
|             memory_layout, application_pool_start, application_pool_size, | ||||
|             KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, | ||||
|             cur_pool_attr); | ||||
|         total_overhead_size += | ||||
|             KMemoryManager::CalculateManagementOverheadSize(application_pool_size); | ||||
|     } else { | ||||
|         const size_t first_application_pool_size = dram_midpoint - application_pool_start; | ||||
|         const size_t second_application_pool_size = | ||||
|             application_pool_start + application_pool_size - dram_midpoint; | ||||
|         InsertPoolPartitionRegionIntoBothTrees( | ||||
|             memory_layout, application_pool_start, first_application_pool_size, | ||||
|             KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, | ||||
|             cur_pool_attr); | ||||
|         InsertPoolPartitionRegionIntoBothTrees( | ||||
|             memory_layout, dram_midpoint, second_application_pool_size, | ||||
|             KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, | ||||
|             cur_pool_attr); | ||||
|         total_overhead_size += | ||||
|             KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size); | ||||
|         total_overhead_size += | ||||
|             KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size); | ||||
|     } | ||||
|  | ||||
|     // Insert the applet pool. | ||||
|     InsertPoolPartitionRegionIntoBothTrees(memory_layout, applet_pool_start, applet_pool_size, | ||||
|                                            KMemoryRegionType_DramAppletPool, | ||||
|                                            KMemoryRegionType_VirtualDramAppletPool, cur_pool_attr); | ||||
|     total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(applet_pool_size); | ||||
|  | ||||
|     // Insert the nonsecure system pool. | ||||
|     InsertPoolPartitionRegionIntoBothTrees( | ||||
|         memory_layout, unsafe_system_pool_start, unsafe_system_pool_size, | ||||
|         KMemoryRegionType_DramSystemNonSecurePool, KMemoryRegionType_VirtualDramSystemNonSecurePool, | ||||
|         cur_pool_attr); | ||||
|     total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(unsafe_system_pool_size); | ||||
|  | ||||
|     // Insert the pool management region. | ||||
|     total_overhead_size += KMemoryManager::CalculateManagementOverheadSize( | ||||
|         (unsafe_system_pool_start - pool_partitions_start) - total_overhead_size); | ||||
|     const u64 pool_management_start = unsafe_system_pool_start - total_overhead_size; | ||||
|     const size_t pool_management_size = total_overhead_size; | ||||
|     u32 pool_management_attr = 0; | ||||
|     InsertPoolPartitionRegionIntoBothTrees( | ||||
|         memory_layout, pool_management_start, pool_management_size, | ||||
|         KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, | ||||
|         pool_management_attr); | ||||
|  | ||||
|     // Insert the system pool. | ||||
|     const u64 system_pool_size = pool_management_start - pool_partitions_start; | ||||
|     InsertPoolPartitionRegionIntoBothTrees(memory_layout, pool_partitions_start, system_pool_size, | ||||
|                                            KMemoryRegionType_DramSystemPool, | ||||
|                                            KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr); | ||||
| } | ||||
|  | ||||
| } // namespace Init | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,206 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <functional> | ||||
| #include <map> | ||||
| #include <mutex> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/polyfill_thread.h" | ||||
| #include "common/scope_exit.h" | ||||
| #include "common/thread.h" | ||||
| #include "core/hle/ipc_helpers.h" | ||||
| #include "core/hle/kernel/hle_ipc.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/kernel/k_scoped_resource_reservation.h" | ||||
| #include "core/hle/kernel/k_session.h" | ||||
| #include "core/hle/kernel/k_thread.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/service_thread.h" | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| class ServiceThread::Impl final { | ||||
| public: | ||||
|     explicit Impl(KernelCore& kernel, const std::string& service_name); | ||||
|     ~Impl(); | ||||
|  | ||||
|     void WaitAndProcessImpl(); | ||||
|     void SessionClosed(KServerSession* server_session, | ||||
|                        std::shared_ptr<SessionRequestManager> manager); | ||||
|     void LoopProcess(); | ||||
|  | ||||
|     void RegisterServerSession(KServerSession* session, | ||||
|                                std::shared_ptr<SessionRequestManager> manager); | ||||
|  | ||||
| private: | ||||
|     KernelCore& kernel; | ||||
|     const std::string m_service_name; | ||||
|  | ||||
|     std::jthread m_host_thread{}; | ||||
|     std::mutex m_session_mutex{}; | ||||
|     std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions{}; | ||||
|     KEvent* m_wakeup_event{}; | ||||
|     KThread* m_thread{}; | ||||
|     std::atomic<bool> m_shutdown_requested{}; | ||||
| }; | ||||
|  | ||||
| void ServiceThread::Impl::WaitAndProcessImpl() { | ||||
|     // Create local list of waitable sessions. | ||||
|     std::vector<KSynchronizationObject*> objs; | ||||
|     std::vector<std::shared_ptr<SessionRequestManager>> managers; | ||||
|  | ||||
|     { | ||||
|         // Lock to get the set. | ||||
|         std::scoped_lock lk{m_session_mutex}; | ||||
|  | ||||
|         // Reserve the needed quantity. | ||||
|         objs.reserve(m_sessions.size() + 1); | ||||
|         managers.reserve(m_sessions.size()); | ||||
|  | ||||
|         // Copy to our local list. | ||||
|         for (const auto& [session, manager] : m_sessions) { | ||||
|             objs.push_back(session); | ||||
|             managers.push_back(manager); | ||||
|         } | ||||
|  | ||||
|         // Insert the wakeup event at the end. | ||||
|         objs.push_back(&m_wakeup_event->GetReadableEvent()); | ||||
|     } | ||||
|  | ||||
|     // Wait on the list of sessions. | ||||
|     s32 index{-1}; | ||||
|     Result rc = KSynchronizationObject::Wait(kernel, &index, objs.data(), | ||||
|                                              static_cast<s32>(objs.size()), -1); | ||||
|     ASSERT(!rc.IsFailure()); | ||||
|  | ||||
|     // If this was the wakeup event, clear it and finish. | ||||
|     if (index >= static_cast<s64>(objs.size() - 1)) { | ||||
|         m_wakeup_event->Clear(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // This event is from a server session. | ||||
|     auto* server_session = static_cast<KServerSession*>(objs[index]); | ||||
|     auto& manager = managers[index]; | ||||
|  | ||||
|     // Fetch the HLE request context. | ||||
|     std::shared_ptr<HLERequestContext> context; | ||||
|     rc = server_session->ReceiveRequest(&context, manager); | ||||
|  | ||||
|     // If the session was closed, handle that. | ||||
|     if (rc == ResultSessionClosed) { | ||||
|         SessionClosed(server_session, manager); | ||||
|  | ||||
|         // Finish. | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // TODO: handle other cases | ||||
|     ASSERT(rc == ResultSuccess); | ||||
|  | ||||
|     // Perform the request. | ||||
|     Result service_rc = manager->CompleteSyncRequest(server_session, *context); | ||||
|  | ||||
|     // Reply to the client. | ||||
|     rc = server_session->SendReplyHLE(); | ||||
|  | ||||
|     if (rc == ResultSessionClosed || service_rc == IPC::ERR_REMOTE_PROCESS_DEAD) { | ||||
|         SessionClosed(server_session, manager); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // TODO: handle other cases | ||||
|     ASSERT(rc == ResultSuccess); | ||||
|     ASSERT(service_rc == ResultSuccess); | ||||
| } | ||||
|  | ||||
| void ServiceThread::Impl::SessionClosed(KServerSession* server_session, | ||||
|                                         std::shared_ptr<SessionRequestManager> manager) { | ||||
|     { | ||||
|         // Lock to get the set. | ||||
|         std::scoped_lock lk{m_session_mutex}; | ||||
|  | ||||
|         // Erase the session. | ||||
|         ASSERT(m_sessions.erase(server_session) == 1); | ||||
|     } | ||||
|  | ||||
|     // Close our reference to the server session. | ||||
|     server_session->Close(); | ||||
| } | ||||
|  | ||||
| void ServiceThread::Impl::LoopProcess() { | ||||
|     Common::SetCurrentThreadName(m_service_name.c_str()); | ||||
|  | ||||
|     kernel.RegisterHostThread(m_thread); | ||||
|  | ||||
|     while (!m_shutdown_requested.load()) { | ||||
|         WaitAndProcessImpl(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ServiceThread::Impl::RegisterServerSession(KServerSession* server_session, | ||||
|                                                 std::shared_ptr<SessionRequestManager> manager) { | ||||
|     // Open the server session. | ||||
|     server_session->Open(); | ||||
|  | ||||
|     { | ||||
|         // Lock to get the set. | ||||
|         std::scoped_lock lk{m_session_mutex}; | ||||
|  | ||||
|         // Insert the session and manager. | ||||
|         m_sessions[server_session] = manager; | ||||
|     } | ||||
|  | ||||
|     // Signal the wakeup event. | ||||
|     m_wakeup_event->Signal(); | ||||
| } | ||||
|  | ||||
| ServiceThread::Impl::~Impl() { | ||||
|     // Shut down the processing thread. | ||||
|     m_shutdown_requested.store(true); | ||||
|     m_wakeup_event->Signal(); | ||||
|     m_host_thread.join(); | ||||
|  | ||||
|     // Close all remaining sessions. | ||||
|     for (const auto& [server_session, manager] : m_sessions) { | ||||
|         server_session->Close(); | ||||
|     } | ||||
|  | ||||
|     // Destroy remaining managers. | ||||
|     m_sessions.clear(); | ||||
|  | ||||
|     // Close event. | ||||
|     m_wakeup_event->GetReadableEvent().Close(); | ||||
|     m_wakeup_event->Close(); | ||||
|  | ||||
|     // Close thread. | ||||
|     m_thread->Close(); | ||||
| } | ||||
|  | ||||
| ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name) | ||||
|     : kernel{kernel_}, m_service_name{service_name} { | ||||
|     // Initialize event. | ||||
|     m_wakeup_event = KEvent::Create(kernel); | ||||
|     m_wakeup_event->Initialize(nullptr); | ||||
|  | ||||
|     // Initialize thread. | ||||
|     m_thread = KThread::Create(kernel); | ||||
|     ASSERT(KThread::InitializeDummyThread(m_thread, nullptr).IsSuccess()); | ||||
|  | ||||
|     // Start thread. | ||||
|     m_host_thread = std::jthread([this] { LoopProcess(); }); | ||||
| } | ||||
|  | ||||
| ServiceThread::ServiceThread(KernelCore& kernel, const std::string& name) | ||||
|     : impl{std::make_unique<Impl>(kernel, name)} {} | ||||
|  | ||||
| ServiceThread::~ServiceThread() = default; | ||||
|  | ||||
| void ServiceThread::RegisterServerSession(KServerSession* session, | ||||
|                                           std::shared_ptr<SessionRequestManager> manager) { | ||||
|     impl->RegisterServerSession(session, manager); | ||||
| } | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,29 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <string> | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| class HLERequestContext; | ||||
| class KernelCore; | ||||
| class KSession; | ||||
| class SessionRequestManager; | ||||
|  | ||||
| class ServiceThread final { | ||||
| public: | ||||
|     explicit ServiceThread(KernelCore& kernel, const std::string& name); | ||||
|     ~ServiceThread(); | ||||
|  | ||||
|     void RegisterServerSession(KServerSession* session, | ||||
|                                std::shared_ptr<SessionRequestManager> manager); | ||||
|  | ||||
| private: | ||||
|     class Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
| }; | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,733 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/arm/arm_interface.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/svc_types.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| static inline u64 Param(const Core::System& system, int n) { | ||||
|     return system.CurrentArmInterface().GetReg(n); | ||||
| } | ||||
|  | ||||
| static inline u32 Param32(const Core::System& system, int n) { | ||||
|     return static_cast<u32>(system.CurrentArmInterface().GetReg(n)); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * HLE a function return from the current ARM userland process | ||||
|  * @param system System context | ||||
|  * @param result Result to return | ||||
|  */ | ||||
| static inline void FuncReturn(Core::System& system, u64 result) { | ||||
|     system.CurrentArmInterface().SetReg(0, result); | ||||
| } | ||||
|  | ||||
| static inline void FuncReturn32(Core::System& system, u32 result) { | ||||
|     system.CurrentArmInterface().SetReg(0, (u64)result); | ||||
| } | ||||
|  | ||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
| // Function wrappers that return type Result | ||||
|  | ||||
| template <Result func(Core::System&, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0)).raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn( | ||||
|         system, | ||||
|         func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw); | ||||
| } | ||||
|  | ||||
| // Used by SetThreadActivity | ||||
| template <Result func(Core::System&, Handle, Svc::ThreadActivity)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), | ||||
|                             static_cast<Svc::ThreadActivity>(Param(system, 1))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32, u64, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), | ||||
|                             Param(system, 2), Param(system, 3)) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| // Used by MapProcessMemory and UnmapProcessMemory | ||||
| template <Result func(Core::System&, u64, u32, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)), | ||||
|                             Param(system, 2), Param(system, 3)) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| // Used by ControlCodeMemory | ||||
| template <Result func(Core::System&, Handle, u32, VAddr, size_t, Svc::MemoryPermission)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)), | ||||
|                             static_cast<u32>(Param(system, 1)), Param(system, 2), Param(system, 3), | ||||
|                             static_cast<Svc::MemoryPermission>(Param(system, 4))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32*)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param = 0; | ||||
|     const u32 retval = func(system, ¶m).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32*, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, static_cast<u32>(Param(system, 1))).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32*, u32*)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     u32 param_2 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, ¶m_2).raw; | ||||
|  | ||||
|     auto& arm_interface = system.CurrentArmInterface(); | ||||
|     arm_interface.SetReg(1, param_1); | ||||
|     arm_interface.SetReg(2, param_2); | ||||
|  | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32*, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1)).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32*, u64, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = | ||||
|         func(system, ¶m_1, Param(system, 1), static_cast<u32>(Param(system, 2))).raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64*, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u64 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, static_cast<u32>(Param(system, 1))).raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1))).raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64*, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u64 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1)).raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64*, u32, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u64 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, static_cast<u32>(Param(system, 1)), | ||||
|                             static_cast<u32>(Param(system, 2))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetResourceLimitLimitValue. | ||||
| template <Result func(Core::System&, u64*, Handle, 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))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1)).raw); | ||||
| } | ||||
|  | ||||
| // Used by SetResourceLimitLimitValue | ||||
| template <Result func(Core::System&, Handle, 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)) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| // Used by SetThreadCoreMask | ||||
| template <Result func(Core::System&, Handle, s32, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), | ||||
|                             static_cast<s32>(Param(system, 1)), Param(system, 2)) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| // Used by GetThreadCoreMask | ||||
| template <Result func(Core::System&, Handle, s32*, u64*)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     s32 param_1 = 0; | ||||
|     u64 param_2 = 0; | ||||
|     const Result retval = func(system, static_cast<u32>(Param(system, 2)), ¶m_1, ¶m_2); | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     FuncReturn(system, retval.raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u64, u32, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1), | ||||
|                             static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u64, u32, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1), | ||||
|                             static_cast<u32>(Param(system, 2)), Param(system, 3)) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32, u64, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), | ||||
|                             static_cast<u32>(Param(system, 2))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1), Param(system, 2)).raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u64, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn( | ||||
|         system, | ||||
|         func(system, Param(system, 0), Param(system, 1), static_cast<u32>(Param(system, 2))).raw); | ||||
| } | ||||
|  | ||||
| // Used by SetMemoryPermission | ||||
| template <Result func(Core::System&, u64, u64, Svc::MemoryPermission)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1), | ||||
|                             static_cast<Svc::MemoryPermission>(Param(system, 2))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| // Used by MapSharedMemory | ||||
| template <Result func(Core::System&, Handle, u64, u64, Svc::MemoryPermission)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)), Param(system, 1), | ||||
|                             Param(system, 2), static_cast<Svc::MemoryPermission>(Param(system, 3))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn( | ||||
|         system, | ||||
|         func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2)).raw); | ||||
| } | ||||
|  | ||||
| // Used by WaitSynchronization | ||||
| template <Result func(Core::System&, s32*, u64, s32, s64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     s32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1), static_cast<s32>(Param(system, 2)), | ||||
|                             static_cast<s64>(Param(system, 3))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u64, u64, u32, s64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1), | ||||
|                             static_cast<u32>(Param(system, 2)), static_cast<s64>(Param(system, 3))) | ||||
|                            .raw); | ||||
| } | ||||
|  | ||||
| // Used by GetInfo | ||||
| template <Result func(Core::System&, u64*, u64, Handle, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u64 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1), | ||||
|                             static_cast<Handle>(Param(system, 2)), Param(system, 3)) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, u32*, u64, u64, u64, u32, s32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1), Param(system, 2), Param(system, 3), | ||||
|                             static_cast<u32>(Param(system, 4)), static_cast<s32>(Param(system, 5))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by CreateTransferMemory | ||||
| template <Result func(Core::System&, Handle*, u64, u64, Svc::MemoryPermission)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1), Param(system, 2), | ||||
|                             static_cast<Svc::MemoryPermission>(Param(system, 3))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by CreateCodeMemory | ||||
| template <Result func(Core::System&, Handle*, VAddr, size_t)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1), Param(system, 2)).raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| template <Result func(Core::System&, Handle*, u64, u32, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param(system, 1), static_cast<u32>(Param(system, 2)), | ||||
|                             static_cast<u32>(Param(system, 3))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by CreateSession | ||||
| template <Result func(Core::System&, Handle*, Handle*, u32, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     Handle param_1 = 0; | ||||
|     Handle param_2 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, ¶m_2, static_cast<u32>(Param(system, 2)), | ||||
|                             static_cast<u32>(Param(system, 3))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by ReplyAndReceive | ||||
| template <Result func(Core::System&, s32*, Handle*, s32, Handle, s64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     s32 param_1 = 0; | ||||
|     s32 num_handles = static_cast<s32>(Param(system, 2)); | ||||
|  | ||||
|     std::vector<Handle> handles(num_handles); | ||||
|     system.Memory().ReadBlock(Param(system, 1), handles.data(), num_handles * sizeof(Handle)); | ||||
|  | ||||
|     const u32 retval = func(system, ¶m_1, handles.data(), num_handles, | ||||
|                             static_cast<s32>(Param(system, 3)), static_cast<s64>(Param(system, 4))) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by WaitForAddress | ||||
| template <Result func(Core::System&, u64, Svc::ArbitrationType, s32, s64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, | ||||
|                func(system, Param(system, 0), static_cast<Svc::ArbitrationType>(Param(system, 1)), | ||||
|                     static_cast<s32>(Param(system, 2)), static_cast<s64>(Param(system, 3))) | ||||
|                    .raw); | ||||
| } | ||||
|  | ||||
| // Used by SignalToAddress | ||||
| template <Result func(Core::System&, u64, Svc::SignalType, s32, s32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, | ||||
|                func(system, Param(system, 0), static_cast<Svc::SignalType>(Param(system, 1)), | ||||
|                     static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3))) | ||||
|                    .raw); | ||||
| } | ||||
|  | ||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
| // Function wrappers that return type u32 | ||||
|  | ||||
| template <u32 func(Core::System&)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system)); | ||||
| } | ||||
|  | ||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
| // Function wrappers that return type u64 | ||||
|  | ||||
| template <u64 func(Core::System&)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     FuncReturn(system, func(system)); | ||||
| } | ||||
|  | ||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
| /// Function wrappers that return type void | ||||
|  | ||||
| template <void func(Core::System&)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, u32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, static_cast<u32>(Param(system, 0))); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, u32, u64, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2), | ||||
|          Param(system, 3)); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, s64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, static_cast<s64>(Param(system, 0))); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, u64, s32)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, Param(system, 0), static_cast<s32>(Param(system, 1))); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, Param(system, 0), Param(system, 1)); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, u64, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, Param(system, 0), Param(system, 1), Param(system, 2)); | ||||
| } | ||||
|  | ||||
| template <void func(Core::System&, u32, u64, u64)> | ||||
| void SvcWrap64(Core::System& system) { | ||||
|     func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2)); | ||||
| } | ||||
|  | ||||
| // Used by QueryMemory32, ArbitrateLock32 | ||||
| template <Result func(Core::System&, u32, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     FuncReturn32(system, | ||||
|                  func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2)).raw); | ||||
| } | ||||
|  | ||||
| // Used by Break32 | ||||
| template <void func(Core::System&, u32, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2)); | ||||
| } | ||||
|  | ||||
| // Used by ExitProcess32, ExitThread32 | ||||
| template <void func(Core::System&)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     func(system); | ||||
| } | ||||
|  | ||||
| // Used by GetCurrentProcessorNumber32 | ||||
| template <u32 func(Core::System&)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     FuncReturn32(system, func(system)); | ||||
| } | ||||
|  | ||||
| // Used by SleepThread32 | ||||
| template <void func(Core::System&, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     func(system, Param32(system, 0), Param32(system, 1)); | ||||
| } | ||||
|  | ||||
| // Used by CreateThread32 | ||||
| template <Result func(Core::System&, Handle*, u32, u32, u32, u32, s32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     Handle param_1 = 0; | ||||
|  | ||||
|     const u32 retval = func(system, ¶m_1, Param32(system, 0), Param32(system, 1), | ||||
|                             Param32(system, 2), Param32(system, 3), Param32(system, 4)) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetInfo32 | ||||
| template <Result func(Core::System&, u32*, u32*, u32, u32, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     u32 param_2 = 0; | ||||
|  | ||||
|     const u32 retval = func(system, ¶m_1, ¶m_2, Param32(system, 0), Param32(system, 1), | ||||
|                             Param32(system, 2), Param32(system, 3)) | ||||
|                            .raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetThreadPriority32, ConnectToNamedPort32 | ||||
| template <Result func(Core::System&, u32*, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     const u32 retval = func(system, ¶m_1, Param32(system, 1)).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetThreadId32 | ||||
| template <Result func(Core::System&, u32*, u32*, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     u32 param_2 = 0; | ||||
|  | ||||
|     const u32 retval = func(system, ¶m_1, ¶m_2, Param32(system, 1)).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetSystemTick32 | ||||
| template <void func(Core::System&, u32*, u32*)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     u32 param_2 = 0; | ||||
|  | ||||
|     func(system, ¶m_1, ¶m_2); | ||||
|     system.CurrentArmInterface().SetReg(0, param_1); | ||||
|     system.CurrentArmInterface().SetReg(1, param_2); | ||||
| } | ||||
|  | ||||
| // Used by CreateEvent32 | ||||
| template <Result func(Core::System&, Handle*, Handle*)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     Handle param_1 = 0; | ||||
|     Handle param_2 = 0; | ||||
|  | ||||
|     const u32 retval = func(system, ¶m_1, ¶m_2).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetThreadId32 | ||||
| template <Result func(Core::System&, Handle, u32*, u32*, u32*)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     u32 param_1 = 0; | ||||
|     u32 param_2 = 0; | ||||
|     u32 param_3 = 0; | ||||
|  | ||||
|     const u32 retval = func(system, Param32(system, 2), ¶m_1, ¶m_2, ¶m_3).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     system.CurrentArmInterface().SetReg(3, param_3); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by GetThreadCoreMask32 | ||||
| template <Result func(Core::System&, Handle, s32*, u32*, u32*)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     s32 param_1 = 0; | ||||
|     u32 param_2 = 0; | ||||
|     u32 param_3 = 0; | ||||
|  | ||||
|     const u32 retval = func(system, Param32(system, 2), ¶m_1, ¶m_2, ¶m_3).raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     system.CurrentArmInterface().SetReg(2, param_2); | ||||
|     system.CurrentArmInterface().SetReg(3, param_3); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by SignalProcessWideKey32 | ||||
| template <void func(Core::System&, u32, s32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     func(system, static_cast<u32>(Param(system, 0)), static_cast<s32>(Param(system, 1))); | ||||
| } | ||||
|  | ||||
| // Used by SetThreadActivity32 | ||||
| template <Result func(Core::System&, Handle, Svc::ThreadActivity)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = func(system, static_cast<Handle>(Param(system, 0)), | ||||
|                             static_cast<Svc::ThreadActivity>(Param(system, 1))) | ||||
|                            .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by SetThreadPriority32 | ||||
| template <Result func(Core::System&, Handle, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = | ||||
|         func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by SetMemoryAttribute32 | ||||
| template <Result func(Core::System&, Handle, u32, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = | ||||
|         func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1)), | ||||
|              static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3))) | ||||
|             .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by MapSharedMemory32 | ||||
| template <Result func(Core::System&, Handle, u32, u32, Svc::MemoryPermission)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = func(system, static_cast<Handle>(Param(system, 0)), | ||||
|                             static_cast<u32>(Param(system, 1)), static_cast<u32>(Param(system, 2)), | ||||
|                             static_cast<Svc::MemoryPermission>(Param(system, 3))) | ||||
|                            .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by SetThreadCoreMask32 | ||||
| template <Result func(Core::System&, Handle, s32, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = | ||||
|         func(system, static_cast<Handle>(Param(system, 0)), static_cast<s32>(Param(system, 1)), | ||||
|              static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3))) | ||||
|             .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by WaitProcessWideKeyAtomic32 | ||||
| template <Result func(Core::System&, u32, u32, Handle, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = | ||||
|         func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)), | ||||
|              static_cast<Handle>(Param(system, 2)), static_cast<u32>(Param(system, 3)), | ||||
|              static_cast<u32>(Param(system, 4))) | ||||
|             .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by WaitForAddress32 | ||||
| template <Result func(Core::System&, u32, Svc::ArbitrationType, s32, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = func(system, static_cast<u32>(Param(system, 0)), | ||||
|                             static_cast<Svc::ArbitrationType>(Param(system, 1)), | ||||
|                             static_cast<s32>(Param(system, 2)), static_cast<u32>(Param(system, 3)), | ||||
|                             static_cast<u32>(Param(system, 4))) | ||||
|                            .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by SignalToAddress32 | ||||
| template <Result func(Core::System&, u32, Svc::SignalType, s32, s32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = func(system, static_cast<u32>(Param(system, 0)), | ||||
|                             static_cast<Svc::SignalType>(Param(system, 1)), | ||||
|                             static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3))) | ||||
|                            .raw; | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by SendSyncRequest32, ArbitrateUnlock32 | ||||
| template <Result func(Core::System&, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw); | ||||
| } | ||||
|  | ||||
| // Used by CreateTransferMemory32 | ||||
| template <Result func(Core::System&, Handle*, u32, u32, Svc::MemoryPermission)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     Handle handle = 0; | ||||
|     const u32 retval = func(system, &handle, Param32(system, 1), Param32(system, 2), | ||||
|                             static_cast<Svc::MemoryPermission>(Param32(system, 3))) | ||||
|                            .raw; | ||||
|     system.CurrentArmInterface().SetReg(1, handle); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by WaitSynchronization32 | ||||
| template <Result func(Core::System&, u32, u32, s32, u32, s32*)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     s32 param_1 = 0; | ||||
|     const u32 retval = func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2), | ||||
|                             Param32(system, 3), ¶m_1) | ||||
|                            .raw; | ||||
|     system.CurrentArmInterface().SetReg(1, param_1); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by CreateCodeMemory32 | ||||
| template <Result func(Core::System&, Handle*, u32, u32)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     Handle handle = 0; | ||||
|  | ||||
|     const u32 retval = func(system, &handle, Param32(system, 1), Param32(system, 2)).raw; | ||||
|  | ||||
|     system.CurrentArmInterface().SetReg(1, handle); | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by ControlCodeMemory32 | ||||
| template <Result func(Core::System&, Handle, u32, u64, u64, Svc::MemoryPermission)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u32 retval = | ||||
|         func(system, Param32(system, 0), Param32(system, 1), Param(system, 2), Param(system, 4), | ||||
|              static_cast<Svc::MemoryPermission>(Param32(system, 6))) | ||||
|             .raw; | ||||
|  | ||||
|     FuncReturn(system, retval); | ||||
| } | ||||
|  | ||||
| // Used by Invalidate/Store/FlushProcessDataCache32 | ||||
| template <Result func(Core::System&, Handle, u64, u64)> | ||||
| void SvcWrap32(Core::System& system) { | ||||
|     const u64 address = (Param(system, 3) << 32) | Param(system, 2); | ||||
|     const u64 size = (Param(system, 4) << 32) | Param(system, 1); | ||||
|     FuncReturn32(system, func(system, Param32(system, 0), address, size).raw); | ||||
| } | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,44 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/k_scheduler.h" | ||||
| #include "core/hle/kernel/k_thread.h" | ||||
| #include "core/hle/kernel/time_manager.h" | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| TimeManager::TimeManager(Core::System& system_) : system{system_} { | ||||
|     time_manager_event_type = Core::Timing::CreateEvent( | ||||
|         "Kernel::TimeManagerCallback", | ||||
|         [this](std::uintptr_t thread_handle, s64 time, | ||||
|                std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> { | ||||
|             KThread* thread = reinterpret_cast<KThread*>(thread_handle); | ||||
|             { | ||||
|                 KScopedSchedulerLock sl(system.Kernel()); | ||||
|                 thread->OnTimer(); | ||||
|             } | ||||
|             return std::nullopt; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|     if (nanoseconds > 0) { | ||||
|         ASSERT(thread); | ||||
|         ASSERT(thread->GetState() != ThreadState::Runnable); | ||||
|         system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds}, | ||||
|                                           time_manager_event_type, | ||||
|                                           reinterpret_cast<uintptr_t>(thread)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void TimeManager::UnscheduleTimeEvent(KThread* thread) { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|     system.CoreTiming().UnscheduleEvent(time_manager_event_type, | ||||
|                                         reinterpret_cast<uintptr_t>(thread)); | ||||
| } | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,41 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace Core::Timing { | ||||
| struct EventType; | ||||
| } // namespace Core::Timing | ||||
|  | ||||
| namespace Kernel { | ||||
|  | ||||
| class KThread; | ||||
|  | ||||
| /** | ||||
|  * The `TimeManager` takes care of scheduling time events on threads and executes their TimeUp | ||||
|  * method when the event is triggered. | ||||
|  */ | ||||
| class TimeManager { | ||||
| public: | ||||
|     explicit TimeManager(Core::System& system); | ||||
|  | ||||
|     /// Schedule a time event on `timetask` thread that will expire in 'nanoseconds' | ||||
|     void ScheduleTimeEvent(KThread* time_task, s64 nanoseconds); | ||||
|  | ||||
|     /// Unschedule an existing time event | ||||
|     void UnscheduleTimeEvent(KThread* thread); | ||||
|  | ||||
| private: | ||||
|     Core::System& system; | ||||
|     std::shared_ptr<Core::Timing::EventType> time_manager_event_type; | ||||
|     std::mutex mutex; | ||||
| }; | ||||
|  | ||||
| } // namespace Kernel | ||||
| @@ -1,22 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/am/tcap.h" | ||||
|  | ||||
| namespace Service::AM { | ||||
|  | ||||
| TCAP::TCAP(Core::System& system_) : ServiceFramework{system_, "tcap"} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "GetContinuousHighSkinTemperatureEvent"}, | ||||
|         {1, nullptr, "SetOperationMode"}, | ||||
|         {2, nullptr, "LoadAndApplySettings"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| TCAP::~TCAP() = default; | ||||
|  | ||||
| } // namespace Service::AM | ||||
| @@ -1,20 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::AM { | ||||
|  | ||||
| class TCAP final : public ServiceFramework<TCAP> { | ||||
| public: | ||||
|     explicit TCAP(Core::System& system_); | ||||
|     ~TCAP() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::AM | ||||
| @@ -1,21 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/audio/auddbg.h" | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| AudDbg::AudDbg(Core::System& system_, const char* name) : ServiceFramework{system_, name} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "RequestSuspendForDebug"}, | ||||
|         {1, nullptr, "RequestResumeForDebug"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| AudDbg::~AudDbg() = default; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,20 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| class AudDbg final : public ServiceFramework<AudDbg> { | ||||
| public: | ||||
|     explicit AudDbg(Core::System& system_, const char* name); | ||||
|     ~AudDbg() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,23 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/audio/audin_a.h" | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| AudInA::AudInA(Core::System& system_) : ServiceFramework{system_, "audin:a"} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "RequestSuspend"}, | ||||
|         {1, nullptr, "RequestResume"}, | ||||
|         {2, nullptr, "GetProcessMasterVolume"}, | ||||
|         {3, nullptr, "SetProcessMasterVolume"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| AudInA::~AudInA() = default; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,20 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| class AudInA final : public ServiceFramework<AudInA> { | ||||
| public: | ||||
|     explicit AudInA(Core::System& system_); | ||||
|     ~AudInA() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,25 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/audio/audout_a.h" | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| AudOutA::AudOutA(Core::System& system_) : ServiceFramework{system_, "audout:a"} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "RequestSuspend"}, | ||||
|         {1, nullptr, "RequestResume"}, | ||||
|         {2, nullptr, "GetProcessMasterVolume"}, | ||||
|         {3, nullptr, "SetProcessMasterVolume"}, | ||||
|         {4, nullptr, "GetProcessRecordVolume"}, | ||||
|         {5, nullptr, "SetProcessRecordVolume"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| AudOutA::~AudOutA() = default; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,20 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| class AudOutA final : public ServiceFramework<AudOutA> { | ||||
| public: | ||||
|     explicit AudOutA(Core::System& system_); | ||||
|     ~AudOutA() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,27 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/audio/audren_a.h" | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| AudRenA::AudRenA(Core::System& system_) : ServiceFramework{system_, "audren:a"} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "RequestSuspend"}, | ||||
|         {1, nullptr, "RequestResume"}, | ||||
|         {2, nullptr, "GetProcessMasterVolume"}, | ||||
|         {3, nullptr, "SetProcessMasterVolume"}, | ||||
|         {4, nullptr, "RegisterAppletResourceUserId"}, | ||||
|         {5, nullptr, "UnregisterAppletResourceUserId"}, | ||||
|         {6, nullptr, "GetProcessRecordVolume"}, | ||||
|         {7, nullptr, "SetProcessRecordVolume"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| AudRenA::~AudRenA() = default; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,20 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| class AudRenA final : public ServiceFramework<AudRenA> { | ||||
| public: | ||||
|     explicit AudRenA(Core::System& system_); | ||||
|     ~AudRenA() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,29 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/audio/codecctl.h" | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| CodecCtl::CodecCtl(Core::System& system_) : ServiceFramework{system_, "codecctl"} { | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "Initialize"}, | ||||
|         {1, nullptr, "Finalize"}, | ||||
|         {2, nullptr, "Sleep"}, | ||||
|         {3, nullptr, "Wake"}, | ||||
|         {4, nullptr, "SetVolume"}, | ||||
|         {5, nullptr, "GetVolumeMax"}, | ||||
|         {6, nullptr, "GetVolumeMin"}, | ||||
|         {7, nullptr, "SetActiveTarget"}, | ||||
|         {8, nullptr, "GetActiveTarget"}, | ||||
|         {9, nullptr, "BindHeadphoneMicJackInterrupt"}, | ||||
|         {10, nullptr, "IsHeadphoneMicJackInserted"}, | ||||
|         {11, nullptr, "ClearHeadphoneMicJackInterrupt"}, | ||||
|         {12, nullptr, "IsRequested"}, | ||||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| CodecCtl::~CodecCtl() = default; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,20 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::Audio { | ||||
|  | ||||
| class CodecCtl final : public ServiceFramework<CodecCtl> { | ||||
| public: | ||||
|     explicit CodecCtl(Core::System& system_); | ||||
|     ~CodecCtl() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
| @@ -1,11 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/result.h" | ||||
|  | ||||
| namespace Service::Friend { | ||||
|  | ||||
| constexpr Result ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15}; | ||||
| } | ||||
| @@ -1,400 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hid/hid_types.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/service/ipc_helpers.h" | ||||
| #include "core/hle/service/nfc/mifare_user.h" | ||||
| #include "core/hle/service/nfc/nfc_device.h" | ||||
| #include "core/hle/service/nfc/nfc_result.h" | ||||
|  | ||||
| namespace Service::NFC { | ||||
|  | ||||
| MFIUser::MFIUser(Core::System& system_) | ||||
|     : ServiceFramework{system_, "NFC::MFIUser"}, service_context{system_, service_name} { | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, &MFIUser::Initialize, "Initialize"}, | ||||
|         {1, &MFIUser::Finalize, "Finalize"}, | ||||
|         {2, &MFIUser::ListDevices, "ListDevices"}, | ||||
|         {3, &MFIUser::StartDetection, "StartDetection"}, | ||||
|         {4, &MFIUser::StopDetection, "StopDetection"}, | ||||
|         {5, &MFIUser::Read, "Read"}, | ||||
|         {6, &MFIUser::Write, "Write"}, | ||||
|         {7, &MFIUser::GetTagInfo, "GetTagInfo"}, | ||||
|         {8, &MFIUser::GetActivateEventHandle, "GetActivateEventHandle"}, | ||||
|         {9, &MFIUser::GetDeactivateEventHandle, "GetDeactivateEventHandle"}, | ||||
|         {10, &MFIUser::GetState, "GetState"}, | ||||
|         {11, &MFIUser::GetDeviceState, "GetDeviceState"}, | ||||
|         {12, &MFIUser::GetNpadId, "GetNpadId"}, | ||||
|         {13, &MFIUser::GetAvailabilityChangeEventHandle, "GetAvailabilityChangeEventHandle"}, | ||||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
|  | ||||
|     availability_change_event = service_context.CreateEvent("MFIUser:AvailabilityChangeEvent"); | ||||
|  | ||||
|     for (u32 device_index = 0; device_index < 10; device_index++) { | ||||
|         devices[device_index] = | ||||
|             std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system, | ||||
|                                         service_context, availability_change_event); | ||||
|     } | ||||
| } | ||||
|  | ||||
| MFIUser ::~MFIUser() { | ||||
|     availability_change_event->Close(); | ||||
| } | ||||
|  | ||||
| void MFIUser::Initialize(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
|  | ||||
|     state = State::Initialized; | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         device->Initialize(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 0}; | ||||
|     rb.Push(ResultSuccess); | ||||
| } | ||||
|  | ||||
| void MFIUser::Finalize(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
|  | ||||
|     state = State::NonInitialized; | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         device->Finalize(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(ResultSuccess); | ||||
| } | ||||
|  | ||||
| void MFIUser::ListDevices(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!ctx.CanWriteBuffer()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareInvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (ctx.GetWriteBufferSize() == 0) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareInvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::vector<u64> nfp_devices; | ||||
|     const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); | ||||
|  | ||||
|     for (const auto& device : devices) { | ||||
|         if (nfp_devices.size() >= max_allowed_devices) { | ||||
|             continue; | ||||
|         } | ||||
|         if (device->GetCurrentState() != NFP::DeviceState::Unavailable) { | ||||
|             nfp_devices.push_back(device->GetHandle()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (nfp_devices.empty()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ctx.WriteBuffer(nfp_devices); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.Push(static_cast<s32>(nfp_devices.size())); | ||||
| } | ||||
|  | ||||
| void MFIUser::StartDetection(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->StartDetection(NFP::TagProtocol::All); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void MFIUser::StopDetection(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->StopDetection(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void MFIUser::Read(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto buffer{ctx.ReadBuffer()}; | ||||
|     const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareReadBlockParameter>()}; | ||||
|     std::vector<NFP::MifareReadBlockParameter> read_commands(number_of_commands); | ||||
|  | ||||
|     memcpy(read_commands.data(), buffer.data(), | ||||
|            number_of_commands * sizeof(NFP::MifareReadBlockParameter)); | ||||
|  | ||||
|     LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}", | ||||
|              device_handle, number_of_commands); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Result result = ResultSuccess; | ||||
|     std::vector<NFP::MifareReadBlockData> out_data(number_of_commands); | ||||
|     for (std::size_t i = 0; i < number_of_commands; i++) { | ||||
|         result = device.value()->MifareRead(read_commands[i], out_data[i]); | ||||
|         if (result.IsError()) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ctx.WriteBuffer(out_data); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void MFIUser::Write(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto buffer{ctx.ReadBuffer()}; | ||||
|     const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareWriteBlockParameter>()}; | ||||
|     std::vector<NFP::MifareWriteBlockParameter> write_commands(number_of_commands); | ||||
|  | ||||
|     memcpy(write_commands.data(), buffer.data(), | ||||
|            number_of_commands * sizeof(NFP::MifareWriteBlockParameter)); | ||||
|  | ||||
|     LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, write_commands_size={}", | ||||
|              device_handle, number_of_commands); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Result result = ResultSuccess; | ||||
|     std::vector<NFP::MifareReadBlockData> out_data(number_of_commands); | ||||
|     for (std::size_t i = 0; i < number_of_commands; i++) { | ||||
|         result = device.value()->MifareWrite(write_commands[i]); | ||||
|         if (result.IsError()) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (result.IsSuccess()) { | ||||
|         result = device.value()->Flush(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetTagInfo(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     NFP::TagInfo tag_info{}; | ||||
|     const auto result = device.value()->GetTagInfo(tag_info, true); | ||||
|     ctx.WriteBuffer(tag_info); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetActivateEventHandle(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(device.value()->GetActivateEvent()); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetDeactivateEventHandle(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(device.value()->GetDeactivateEvent()); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetState(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(state); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetDeviceState(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(device.value()->GetCurrentState()); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetNpadId(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareDeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(device.value()->GetNpadId()); | ||||
| } | ||||
|  | ||||
| void MFIUser::GetAvailabilityChangeEventHandle(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(MifareNfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(availability_change_event->GetReadableEvent()); | ||||
| } | ||||
|  | ||||
| std::optional<std::shared_ptr<NfcDevice>> MFIUser::GetNfcDevice(u64 handle) { | ||||
|     for (auto& device : devices) { | ||||
|         if (device->GetHandle() == handle) { | ||||
|             return device; | ||||
|         } | ||||
|     } | ||||
|     return std::nullopt; | ||||
| } | ||||
|  | ||||
| } // namespace Service::NFC | ||||
| @@ -1,52 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
|  | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Service::NFC { | ||||
| class NfcDevice; | ||||
|  | ||||
| class MFIUser final : public ServiceFramework<MFIUser> { | ||||
| public: | ||||
|     explicit MFIUser(Core::System& system_); | ||||
|     ~MFIUser(); | ||||
|  | ||||
| private: | ||||
|     enum class State : u32 { | ||||
|         NonInitialized, | ||||
|         Initialized, | ||||
|     }; | ||||
|  | ||||
|     void Initialize(HLERequestContext& ctx); | ||||
|     void Finalize(HLERequestContext& ctx); | ||||
|     void ListDevices(HLERequestContext& ctx); | ||||
|     void StartDetection(HLERequestContext& ctx); | ||||
|     void StopDetection(HLERequestContext& ctx); | ||||
|     void Read(HLERequestContext& ctx); | ||||
|     void Write(HLERequestContext& ctx); | ||||
|     void GetTagInfo(HLERequestContext& ctx); | ||||
|     void GetActivateEventHandle(HLERequestContext& ctx); | ||||
|     void GetDeactivateEventHandle(HLERequestContext& ctx); | ||||
|     void GetState(HLERequestContext& ctx); | ||||
|     void GetDeviceState(HLERequestContext& ctx); | ||||
|     void GetNpadId(HLERequestContext& ctx); | ||||
|     void GetAvailabilityChangeEventHandle(HLERequestContext& ctx); | ||||
|  | ||||
|     std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle); | ||||
|  | ||||
|     KernelHelpers::ServiceContext service_context; | ||||
|  | ||||
|     std::array<std::shared_ptr<NfcDevice>, 10> devices{}; | ||||
|  | ||||
|     State state{State::NonInitialized}; | ||||
|     Kernel::KEvent* availability_change_event; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NFC | ||||
| @@ -1,288 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "common/input.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hid/emulated_controller.h" | ||||
| #include "core/hid/hid_core.h" | ||||
| #include "core/hid/hid_types.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/service/ipc_helpers.h" | ||||
| #include "core/hle/service/nfc/nfc_device.h" | ||||
| #include "core/hle/service/nfc/nfc_result.h" | ||||
| #include "core/hle/service/nfc/nfc_user.h" | ||||
|  | ||||
| namespace Service::NFC { | ||||
| NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, | ||||
|                      KernelHelpers::ServiceContext& service_context_, | ||||
|                      Kernel::KEvent* availability_change_event_) | ||||
|     : npad_id{npad_id_}, system{system_}, service_context{service_context_}, | ||||
|       availability_change_event{availability_change_event_} { | ||||
|     activate_event = service_context.CreateEvent("IUser:NFCActivateEvent"); | ||||
|     deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent"); | ||||
|     npad_device = system.HIDCore().GetEmulatedController(npad_id); | ||||
|  | ||||
|     Core::HID::ControllerUpdateCallback engine_callback{ | ||||
|         .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); }, | ||||
|         .is_npad_service = false, | ||||
|     }; | ||||
|     is_controller_set = true; | ||||
|     callback_key = npad_device->SetCallback(engine_callback); | ||||
| } | ||||
|  | ||||
| NfcDevice::~NfcDevice() { | ||||
|     activate_event->Close(); | ||||
|     deactivate_event->Close(); | ||||
|     if (!is_controller_set) { | ||||
|         return; | ||||
|     } | ||||
|     npad_device->DeleteCallback(callback_key); | ||||
|     is_controller_set = false; | ||||
| }; | ||||
|  | ||||
| void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { | ||||
|     if (!is_initalized) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (type == Core::HID::ControllerTriggerType::Connected) { | ||||
|         Initialize(); | ||||
|         availability_change_event->Signal(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (type == Core::HID::ControllerTriggerType::Disconnected) { | ||||
|         device_state = NFP::DeviceState::Unavailable; | ||||
|         availability_change_event->Signal(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (type != Core::HID::ControllerTriggerType::Nfc) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!npad_device->IsConnected()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto nfc_status = npad_device->GetNfc(); | ||||
|     switch (nfc_status.state) { | ||||
|     case Common::Input::NfcState::NewAmiibo: | ||||
|         LoadNfcTag(nfc_status.data); | ||||
|         break; | ||||
|     case Common::Input::NfcState::AmiiboRemoved: | ||||
|         if (device_state != NFP::DeviceState::SearchingForTag) { | ||||
|             CloseNfcTag(); | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool NfcDevice::LoadNfcTag(std::span<const u8> data) { | ||||
|     if (device_state != NFP::DeviceState::SearchingForTag) { | ||||
|         LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (data.size() < sizeof(NFP::EncryptedNTAG215File)) { | ||||
|         LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size()); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     tag_data.resize(data.size()); | ||||
|     memcpy(tag_data.data(), data.data(), data.size()); | ||||
|     memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); | ||||
|  | ||||
|     device_state = NFP::DeviceState::TagFound; | ||||
|     deactivate_event->GetReadableEvent().Clear(); | ||||
|     activate_event->Signal(); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void NfcDevice::CloseNfcTag() { | ||||
|     LOG_INFO(Service_NFC, "Remove nfc tag"); | ||||
|  | ||||
|     device_state = NFP::DeviceState::TagRemoved; | ||||
|     encrypted_tag_data = {}; | ||||
|     activate_event->GetReadableEvent().Clear(); | ||||
|     deactivate_event->Signal(); | ||||
| } | ||||
|  | ||||
| Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const { | ||||
|     return activate_event->GetReadableEvent(); | ||||
| } | ||||
|  | ||||
| Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const { | ||||
|     return deactivate_event->GetReadableEvent(); | ||||
| } | ||||
|  | ||||
| void NfcDevice::Initialize() { | ||||
|     device_state = | ||||
|         npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable; | ||||
|     encrypted_tag_data = {}; | ||||
|     is_initalized = true; | ||||
| } | ||||
|  | ||||
| void NfcDevice::Finalize() { | ||||
|     if (device_state == NFP::DeviceState::SearchingForTag || | ||||
|         device_state == NFP::DeviceState::TagRemoved) { | ||||
|         StopDetection(); | ||||
|     } | ||||
|     device_state = NFP::DeviceState::Unavailable; | ||||
|     is_initalized = false; | ||||
| } | ||||
|  | ||||
| Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) { | ||||
|     if (device_state != NFP::DeviceState::Initialized && | ||||
|         device_state != NFP::DeviceState::TagRemoved) { | ||||
|         LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||||
|         return WrongDeviceState; | ||||
|     } | ||||
|  | ||||
|     if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, | ||||
|                                     Common::Input::PollingMode::NFC) != | ||||
|         Common::Input::DriverResult::Success) { | ||||
|         LOG_ERROR(Service_NFC, "Nfc not supported"); | ||||
|         return NfcDisabled; | ||||
|     } | ||||
|  | ||||
|     device_state = NFP::DeviceState::SearchingForTag; | ||||
|     allowed_protocols = allowed_protocol; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result NfcDevice::StopDetection() { | ||||
|     npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, | ||||
|                                 Common::Input::PollingMode::Active); | ||||
|  | ||||
|     if (device_state == NFP::DeviceState::Initialized) { | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|  | ||||
|     if (device_state == NFP::DeviceState::TagFound || | ||||
|         device_state == NFP::DeviceState::TagMounted) { | ||||
|         CloseNfcTag(); | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|     if (device_state == NFP::DeviceState::SearchingForTag || | ||||
|         device_state == NFP::DeviceState::TagRemoved) { | ||||
|         device_state = NFP::DeviceState::Initialized; | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|  | ||||
|     LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||||
|     return WrongDeviceState; | ||||
| } | ||||
|  | ||||
| Result NfcDevice::Flush() { | ||||
|     if (device_state != NFP::DeviceState::TagFound && | ||||
|         device_state != NFP::DeviceState::TagMounted) { | ||||
|         LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||||
|         if (device_state == NFP::DeviceState::TagRemoved) { | ||||
|             return TagRemoved; | ||||
|         } | ||||
|         return WrongDeviceState; | ||||
|     } | ||||
|  | ||||
|     if (!npad_device->WriteNfc(tag_data)) { | ||||
|         LOG_ERROR(Service_NFP, "Error writing to file"); | ||||
|         return MifareReadError; | ||||
|     } | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { | ||||
|     if (device_state != NFP::DeviceState::TagFound && | ||||
|         device_state != NFP::DeviceState::TagMounted) { | ||||
|         LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||||
|         if (device_state == NFP::DeviceState::TagRemoved) { | ||||
|             return TagRemoved; | ||||
|         } | ||||
|         return WrongDeviceState; | ||||
|     } | ||||
|  | ||||
|     if (is_mifare) { | ||||
|         tag_info = { | ||||
|             .uuid = encrypted_tag_data.uuid.uid, | ||||
|             .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), | ||||
|             .protocol = NFP::TagProtocol::TypeA, | ||||
|             .tag_type = NFP::TagType::Type4, | ||||
|         }; | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|  | ||||
|     // Protocol and tag type may change here | ||||
|     tag_info = { | ||||
|         .uuid = encrypted_tag_data.uuid.uid, | ||||
|         .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), | ||||
|         .protocol = NFP::TagProtocol::TypeA, | ||||
|         .tag_type = NFP::TagType::Type2, | ||||
|     }; | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result NfcDevice::MifareRead(const NFP::MifareReadBlockParameter& parameter, | ||||
|                              NFP::MifareReadBlockData& read_block_data) { | ||||
|     const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock); | ||||
|     read_block_data.sector_number = parameter.sector_number; | ||||
|  | ||||
|     if (device_state != NFP::DeviceState::TagFound && | ||||
|         device_state != NFP::DeviceState::TagMounted) { | ||||
|         LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||||
|         if (device_state == NFP::DeviceState::TagRemoved) { | ||||
|             return TagRemoved; | ||||
|         } | ||||
|         return WrongDeviceState; | ||||
|     } | ||||
|  | ||||
|     if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) { | ||||
|         return MifareReadError; | ||||
|     } | ||||
|  | ||||
|     // TODO: Use parameter.sector_key to read encrypted data | ||||
|     memcpy(read_block_data.data.data(), tag_data.data() + sector_index, sizeof(NFP::DataBlock)); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result NfcDevice::MifareWrite(const NFP::MifareWriteBlockParameter& parameter) { | ||||
|     const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock); | ||||
|  | ||||
|     if (device_state != NFP::DeviceState::TagFound && | ||||
|         device_state != NFP::DeviceState::TagMounted) { | ||||
|         LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||||
|         if (device_state == NFP::DeviceState::TagRemoved) { | ||||
|             return TagRemoved; | ||||
|         } | ||||
|         return WrongDeviceState; | ||||
|     } | ||||
|  | ||||
|     if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) { | ||||
|         return MifareReadError; | ||||
|     } | ||||
|  | ||||
|     // TODO: Use parameter.sector_key to encrypt the data | ||||
|     memcpy(tag_data.data() + sector_index, parameter.data.data(), sizeof(NFP::DataBlock)); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| u64 NfcDevice::GetHandle() const { | ||||
|     // Generate a handle based of the npad id | ||||
|     return static_cast<u64>(npad_id); | ||||
| } | ||||
|  | ||||
| NFP::DeviceState NfcDevice::GetCurrentState() const { | ||||
|     return device_state; | ||||
| } | ||||
|  | ||||
| Core::HID::NpadIdType NfcDevice::GetNpadId() const { | ||||
|     return npad_id; | ||||
| } | ||||
|  | ||||
| } // namespace Service::NFC | ||||
| @@ -1,78 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/nfp/nfp_types.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace Core::HID { | ||||
| class EmulatedController; | ||||
| enum class ControllerTriggerType; | ||||
| enum class NpadIdType : u32; | ||||
| } // namespace Core::HID | ||||
|  | ||||
| namespace Service::NFC { | ||||
| class NfcDevice { | ||||
| public: | ||||
|     NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, | ||||
|               KernelHelpers::ServiceContext& service_context_, | ||||
|               Kernel::KEvent* availability_change_event_); | ||||
|     ~NfcDevice(); | ||||
|  | ||||
|     void Initialize(); | ||||
|     void Finalize(); | ||||
|  | ||||
|     Result StartDetection(NFP::TagProtocol allowed_protocol); | ||||
|     Result StopDetection(); | ||||
|     Result Flush(); | ||||
|  | ||||
|     Result GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const; | ||||
|  | ||||
|     Result MifareRead(const NFP::MifareReadBlockParameter& parameter, | ||||
|                       NFP::MifareReadBlockData& read_block_data); | ||||
|  | ||||
|     Result MifareWrite(const NFP::MifareWriteBlockParameter& parameter); | ||||
|  | ||||
|     u64 GetHandle() const; | ||||
|     NFP::DeviceState GetCurrentState() const; | ||||
|     Core::HID::NpadIdType GetNpadId() const; | ||||
|  | ||||
|     Kernel::KReadableEvent& GetActivateEvent() const; | ||||
|     Kernel::KReadableEvent& GetDeactivateEvent() const; | ||||
|  | ||||
| private: | ||||
|     void NpadUpdate(Core::HID::ControllerTriggerType type); | ||||
|     bool LoadNfcTag(std::span<const u8> data); | ||||
|     void CloseNfcTag(); | ||||
|  | ||||
|     bool is_controller_set{}; | ||||
|     int callback_key; | ||||
|     const Core::HID::NpadIdType npad_id; | ||||
|     Core::System& system; | ||||
|     Core::HID::EmulatedController* npad_device = nullptr; | ||||
|     KernelHelpers::ServiceContext& service_context; | ||||
|     Kernel::KEvent* activate_event = nullptr; | ||||
|     Kernel::KEvent* deactivate_event = nullptr; | ||||
|     Kernel::KEvent* availability_change_event = nullptr; | ||||
|  | ||||
|     bool is_initalized{}; | ||||
|     NFP::TagProtocol allowed_protocols{}; | ||||
|     NFP::DeviceState device_state{NFP::DeviceState::Unavailable}; | ||||
|  | ||||
|     NFP::EncryptedNTAG215File encrypted_tag_data{}; | ||||
|     std::vector<u8> tag_data{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NFC | ||||
| @@ -1,365 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hid/hid_types.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/service/ipc_helpers.h" | ||||
| #include "core/hle/service/nfc/nfc_device.h" | ||||
| #include "core/hle/service/nfc/nfc_result.h" | ||||
| #include "core/hle/service/nfc/nfc_user.h" | ||||
| #include "core/hle/service/time/clock_types.h" | ||||
|  | ||||
| namespace Service::NFC { | ||||
|  | ||||
| IUser::IUser(Core::System& system_) | ||||
|     : ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} { | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, &IUser::Initialize, "InitializeOld"}, | ||||
|         {1, &IUser::Finalize, "FinalizeOld"}, | ||||
|         {2, &IUser::GetState, "GetStateOld"}, | ||||
|         {3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"}, | ||||
|         {400, &IUser::Initialize, "Initialize"}, | ||||
|         {401, &IUser::Finalize, "Finalize"}, | ||||
|         {402, &IUser::GetState, "GetState"}, | ||||
|         {403, &IUser::IsNfcEnabled, "IsNfcEnabled"}, | ||||
|         {404, &IUser::ListDevices, "ListDevices"}, | ||||
|         {405, &IUser::GetDeviceState, "GetDeviceState"}, | ||||
|         {406, &IUser::GetNpadId, "GetNpadId"}, | ||||
|         {407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, | ||||
|         {408, &IUser::StartDetection, "StartDetection"}, | ||||
|         {409, &IUser::StopDetection, "StopDetection"}, | ||||
|         {410, &IUser::GetTagInfo, "GetTagInfo"}, | ||||
|         {411, &IUser::AttachActivateEvent, "AttachActivateEvent"}, | ||||
|         {412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"}, | ||||
|         {1000, nullptr, "ReadMifare"}, | ||||
|         {1001, nullptr, "WriteMifare"}, | ||||
|         {1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"}, | ||||
|         {1301, nullptr, "KeepPassThroughSession"}, | ||||
|         {1302, nullptr, "ReleasePassThroughSession"}, | ||||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
|  | ||||
|     availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent"); | ||||
|  | ||||
|     for (u32 device_index = 0; device_index < 10; device_index++) { | ||||
|         devices[device_index] = | ||||
|             std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system, | ||||
|                                         service_context, availability_change_event); | ||||
|     } | ||||
| } | ||||
|  | ||||
| IUser ::~IUser() { | ||||
|     availability_change_event->Close(); | ||||
| } | ||||
|  | ||||
| void IUser::Initialize(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
|  | ||||
|     state = State::Initialized; | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         device->Initialize(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 0}; | ||||
|     rb.Push(ResultSuccess); | ||||
| } | ||||
|  | ||||
| void IUser::Finalize(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
|  | ||||
|     state = State::NonInitialized; | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         device->Finalize(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(ResultSuccess); | ||||
| } | ||||
|  | ||||
| void IUser::GetState(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(state); | ||||
| } | ||||
|  | ||||
| void IUser::IsNfcEnabled(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.Push(state != State::NonInitialized); | ||||
| } | ||||
|  | ||||
| void IUser::ListDevices(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!ctx.CanWriteBuffer()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (ctx.GetWriteBufferSize() == 0) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::vector<u64> nfp_devices; | ||||
|     const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         if (nfp_devices.size() >= max_allowed_devices) { | ||||
|             continue; | ||||
|         } | ||||
|         if (device->GetCurrentState() != NFP::DeviceState::Unavailable) { | ||||
|             nfp_devices.push_back(device->GetHandle()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (nfp_devices.empty()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ctx.WriteBuffer(nfp_devices); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.Push(static_cast<s32>(nfp_devices.size())); | ||||
| } | ||||
|  | ||||
| void IUser::GetDeviceState(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(device.value()->GetCurrentState()); | ||||
| } | ||||
|  | ||||
| void IUser::GetNpadId(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(device.value()->GetNpadId()); | ||||
| } | ||||
|  | ||||
| void IUser::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(availability_change_event->GetReadableEvent()); | ||||
| } | ||||
|  | ||||
| void IUser::StartDetection(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto nfp_protocol{rp.PopEnum<NFP::TagProtocol>()}; | ||||
|     LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->StartDetection(nfp_protocol); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::StopDetection(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->StopDetection(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::GetTagInfo(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     NFP::TagInfo tag_info{}; | ||||
|     const auto result = device.value()->GetTagInfo(tag_info, false); | ||||
|     ctx.WriteBuffer(tag_info); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::AttachActivateEvent(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(device.value()->GetActivateEvent()); | ||||
| } | ||||
|  | ||||
| void IUser::AttachDeactivateEvent(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(device.value()->GetDeactivateEvent()); | ||||
| } | ||||
|  | ||||
| void IUser::SendCommandByPassThrough(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()}; | ||||
|     const auto command_data{ctx.ReadBuffer()}; | ||||
|  | ||||
|     LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}", | ||||
|              device_handle, timeout.ToSeconds(), command_data.size()); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfcDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::vector<u8> out_data(1); | ||||
|     // TODO: Request data from nfc device | ||||
|     ctx.WriteBuffer(out_data); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.Push(static_cast<u32>(out_data.size())); | ||||
| } | ||||
|  | ||||
| std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) { | ||||
|     for (auto& device : devices) { | ||||
|         if (device->GetHandle() == handle) { | ||||
|             return device; | ||||
|         } | ||||
|     } | ||||
|     return std::nullopt; | ||||
| } | ||||
|  | ||||
| } // namespace Service::NFC | ||||
| @@ -1,52 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
|  | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Service::NFC { | ||||
| class NfcDevice; | ||||
|  | ||||
| class IUser final : public ServiceFramework<IUser> { | ||||
| public: | ||||
|     explicit IUser(Core::System& system_); | ||||
|     ~IUser(); | ||||
|  | ||||
| private: | ||||
|     enum class State : u32 { | ||||
|         NonInitialized, | ||||
|         Initialized, | ||||
|     }; | ||||
|  | ||||
|     void Initialize(HLERequestContext& ctx); | ||||
|     void Finalize(HLERequestContext& ctx); | ||||
|     void GetState(HLERequestContext& ctx); | ||||
|     void IsNfcEnabled(HLERequestContext& ctx); | ||||
|     void ListDevices(HLERequestContext& ctx); | ||||
|     void GetDeviceState(HLERequestContext& ctx); | ||||
|     void GetNpadId(HLERequestContext& ctx); | ||||
|     void AttachAvailabilityChangeEvent(HLERequestContext& ctx); | ||||
|     void StartDetection(HLERequestContext& ctx); | ||||
|     void StopDetection(HLERequestContext& ctx); | ||||
|     void GetTagInfo(HLERequestContext& ctx); | ||||
|     void AttachActivateEvent(HLERequestContext& ctx); | ||||
|     void AttachDeactivateEvent(HLERequestContext& ctx); | ||||
|     void SendCommandByPassThrough(HLERequestContext& ctx); | ||||
|  | ||||
|     std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle); | ||||
|  | ||||
|     KernelHelpers::ServiceContext service_context; | ||||
|  | ||||
|     std::array<std::shared_ptr<NfcDevice>, 10> devices{}; | ||||
|  | ||||
|     State state{State::NonInitialized}; | ||||
|     Kernel::KEvent* availability_change_event; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NFC | ||||
| @@ -1,405 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| // SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| #include <array> | ||||
| #include <mbedtls/aes.h> | ||||
| #include <mbedtls/hmac_drbg.h> | ||||
|  | ||||
| #include "common/fs/file.h" | ||||
| #include "common/fs/fs.h" | ||||
| #include "common/fs/path_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/nfp/amiibo_crypto.h" | ||||
|  | ||||
| namespace Service::NFP::AmiiboCrypto { | ||||
|  | ||||
| bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { | ||||
|     const auto& amiibo_data = ntag_file.user_memory; | ||||
|     LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock); | ||||
|     LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container); | ||||
|     LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter)); | ||||
|  | ||||
|     LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); | ||||
|     LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); | ||||
|     LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); | ||||
|     LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", | ||||
|               static_cast<u16>(amiibo_data.model_info.model_number)); | ||||
|     LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series); | ||||
|     LOG_DEBUG(Service_NFP, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type); | ||||
|  | ||||
|     LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock); | ||||
|     LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0); | ||||
|     LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1); | ||||
|  | ||||
|     // Validate UUID | ||||
|     constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` | ||||
|     if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) != | ||||
|         ntag_file.uuid.uid[3]) { | ||||
|         return false; | ||||
|     } | ||||
|     if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^ | ||||
|          ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Check against all know constants on an amiibo binary | ||||
|     if (ntag_file.static_lock != 0xE00F) { | ||||
|         return false; | ||||
|     } | ||||
|     if (ntag_file.compability_container != 0xEEFF10F1U) { | ||||
|         return false; | ||||
|     } | ||||
|     if (amiibo_data.constant_value != 0xA5) { | ||||
|         return false; | ||||
|     } | ||||
|     if (amiibo_data.model_info.tag_type != PackedTagType::Type2) { | ||||
|         return false; | ||||
|     } | ||||
|     if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) { | ||||
|         return false; | ||||
|     } | ||||
|     if (ntag_file.CFG0 != 0x04000000U) { | ||||
|         return false; | ||||
|     } | ||||
|     if (ntag_file.CFG1 != 0x5F) { | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool IsAmiiboValid(const NTAG215File& ntag_file) { | ||||
|     return IsAmiiboValid(EncodedDataToNfcData(ntag_file)); | ||||
| } | ||||
|  | ||||
| NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | ||||
|     NTAG215File encoded_data{}; | ||||
|  | ||||
|     encoded_data.uid = nfc_data.uuid.uid; | ||||
|     encoded_data.nintendo_id = nfc_data.uuid.nintendo_id; | ||||
|     encoded_data.static_lock = nfc_data.static_lock; | ||||
|     encoded_data.compability_container = nfc_data.compability_container; | ||||
|     encoded_data.hmac_data = nfc_data.user_memory.hmac_data; | ||||
|     encoded_data.constant_value = nfc_data.user_memory.constant_value; | ||||
|     encoded_data.write_counter = nfc_data.user_memory.write_counter; | ||||
|     encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version; | ||||
|     encoded_data.settings = nfc_data.user_memory.settings; | ||||
|     encoded_data.owner_mii = nfc_data.user_memory.owner_mii; | ||||
|     encoded_data.application_id = nfc_data.user_memory.application_id; | ||||
|     encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter; | ||||
|     encoded_data.application_area_id = nfc_data.user_memory.application_area_id; | ||||
|     encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte; | ||||
|     encoded_data.unknown = nfc_data.user_memory.unknown; | ||||
|     encoded_data.mii_extension = nfc_data.user_memory.mii_extension; | ||||
|     encoded_data.unknown2 = nfc_data.user_memory.unknown2; | ||||
|     encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; | ||||
|     encoded_data.application_area = nfc_data.user_memory.application_area; | ||||
|     encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; | ||||
|     encoded_data.lock_bytes = nfc_data.uuid.lock_bytes; | ||||
|     encoded_data.model_info = nfc_data.user_memory.model_info; | ||||
|     encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; | ||||
|     encoded_data.dynamic_lock = nfc_data.dynamic_lock; | ||||
|     encoded_data.CFG0 = nfc_data.CFG0; | ||||
|     encoded_data.CFG1 = nfc_data.CFG1; | ||||
|     encoded_data.password = nfc_data.password; | ||||
|  | ||||
|     return encoded_data; | ||||
| } | ||||
|  | ||||
| EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | ||||
|     EncryptedNTAG215File nfc_data{}; | ||||
|  | ||||
|     nfc_data.uuid.uid = encoded_data.uid; | ||||
|     nfc_data.uuid.nintendo_id = encoded_data.nintendo_id; | ||||
|     nfc_data.uuid.lock_bytes = encoded_data.lock_bytes; | ||||
|     nfc_data.static_lock = encoded_data.static_lock; | ||||
|     nfc_data.compability_container = encoded_data.compability_container; | ||||
|     nfc_data.user_memory.hmac_data = encoded_data.hmac_data; | ||||
|     nfc_data.user_memory.constant_value = encoded_data.constant_value; | ||||
|     nfc_data.user_memory.write_counter = encoded_data.write_counter; | ||||
|     nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version; | ||||
|     nfc_data.user_memory.settings = encoded_data.settings; | ||||
|     nfc_data.user_memory.owner_mii = encoded_data.owner_mii; | ||||
|     nfc_data.user_memory.application_id = encoded_data.application_id; | ||||
|     nfc_data.user_memory.application_write_counter = encoded_data.application_write_counter; | ||||
|     nfc_data.user_memory.application_area_id = encoded_data.application_area_id; | ||||
|     nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte; | ||||
|     nfc_data.user_memory.unknown = encoded_data.unknown; | ||||
|     nfc_data.user_memory.mii_extension = encoded_data.mii_extension; | ||||
|     nfc_data.user_memory.unknown2 = encoded_data.unknown2; | ||||
|     nfc_data.user_memory.register_info_crc = encoded_data.register_info_crc; | ||||
|     nfc_data.user_memory.application_area = encoded_data.application_area; | ||||
|     nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag; | ||||
|     nfc_data.user_memory.model_info = encoded_data.model_info; | ||||
|     nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt; | ||||
|     nfc_data.dynamic_lock = encoded_data.dynamic_lock; | ||||
|     nfc_data.CFG0 = encoded_data.CFG0; | ||||
|     nfc_data.CFG1 = encoded_data.CFG1; | ||||
|     nfc_data.password = encoded_data.password; | ||||
|  | ||||
|     return nfc_data; | ||||
| } | ||||
|  | ||||
| u32 GetTagPassword(const TagUuid& uuid) { | ||||
|     // Verify that the generated password is correct | ||||
|     u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]); | ||||
|     password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8; | ||||
|     password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16; | ||||
|     password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24; | ||||
|     return password; | ||||
| } | ||||
|  | ||||
| HashSeed GetSeed(const NTAG215File& data) { | ||||
|     HashSeed seed{ | ||||
|         .magic = data.write_counter, | ||||
|         .padding = {}, | ||||
|         .uid_1 = data.uid, | ||||
|         .nintendo_id_1 = data.nintendo_id, | ||||
|         .uid_2 = data.uid, | ||||
|         .nintendo_id_2 = data.nintendo_id, | ||||
|         .keygen_salt = data.keygen_salt, | ||||
|     }; | ||||
|  | ||||
|     return seed; | ||||
| } | ||||
|  | ||||
| std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) { | ||||
|     const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length; | ||||
|     const std::size_t string_size = key.type_string.size(); | ||||
|     std::vector<u8> output(string_size + seedPart1Len); | ||||
|  | ||||
|     // Copy whole type string | ||||
|     memccpy(output.data(), key.type_string.data(), '\0', string_size); | ||||
|  | ||||
|     // Append (16 - magic_length) from the input seed | ||||
|     memcpy(output.data() + string_size, &seed, seedPart1Len); | ||||
|  | ||||
|     // Append all bytes from magicBytes | ||||
|     output.insert(output.end(), key.magic_bytes.begin(), | ||||
|                   key.magic_bytes.begin() + key.magic_length); | ||||
|  | ||||
|     output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); | ||||
|     output.emplace_back(seed.nintendo_id_1); | ||||
|     output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); | ||||
|     output.emplace_back(seed.nintendo_id_2); | ||||
|  | ||||
|     for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { | ||||
|         output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); | ||||
|     } | ||||
|  | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, | ||||
|                 const std::vector<u8>& seed) { | ||||
|     // Initialize context | ||||
|     ctx.used = false; | ||||
|     ctx.counter = 0; | ||||
|     ctx.buffer_size = sizeof(ctx.counter) + seed.size(); | ||||
|     memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size()); | ||||
|  | ||||
|     // Initialize HMAC context | ||||
|     mbedtls_md_init(&hmac_ctx); | ||||
|     mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); | ||||
|     mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size()); | ||||
| } | ||||
|  | ||||
| void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) { | ||||
|     // If used at least once, reinitialize the HMAC | ||||
|     if (ctx.used) { | ||||
|         mbedtls_md_hmac_reset(&hmac_ctx); | ||||
|     } | ||||
|  | ||||
|     ctx.used = true; | ||||
|  | ||||
|     // Store counter in big endian, and increment it | ||||
|     ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8); | ||||
|     ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0); | ||||
|     ctx.counter++; | ||||
|  | ||||
|     // Do HMAC magic | ||||
|     mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()), | ||||
|                            ctx.buffer_size); | ||||
|     mbedtls_md_hmac_finish(&hmac_ctx, output.data()); | ||||
| } | ||||
|  | ||||
| DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { | ||||
|     const auto seed = GetSeed(data); | ||||
|  | ||||
|     // Generate internal seed | ||||
|     const std::vector<u8> internal_key = GenerateInternalKey(key, seed); | ||||
|  | ||||
|     // Initialize context | ||||
|     CryptoCtx ctx{}; | ||||
|     mbedtls_md_context_t hmac_ctx; | ||||
|     CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key); | ||||
|  | ||||
|     // Generate derived keys | ||||
|     DerivedKeys derived_keys{}; | ||||
|     std::array<DrgbOutput, 2> temp{}; | ||||
|     CryptoStep(ctx, hmac_ctx, temp[0]); | ||||
|     CryptoStep(ctx, hmac_ctx, temp[1]); | ||||
|     memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys)); | ||||
|  | ||||
|     // Cleanup context | ||||
|     mbedtls_md_free(&hmac_ctx); | ||||
|  | ||||
|     return derived_keys; | ||||
| } | ||||
|  | ||||
| void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) { | ||||
|     mbedtls_aes_context aes; | ||||
|     std::size_t nc_off = 0; | ||||
|     std::array<u8, sizeof(keys.aes_iv)> nonce_counter{}; | ||||
|     std::array<u8, sizeof(keys.aes_iv)> stream_block{}; | ||||
|  | ||||
|     const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8); | ||||
|     mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size); | ||||
|     memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv)); | ||||
|  | ||||
|     constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START; | ||||
|     mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(), | ||||
|                           stream_block.data(), | ||||
|                           reinterpret_cast<const unsigned char*>(&in_data.settings), | ||||
|                           reinterpret_cast<unsigned char*>(&out_data.settings)); | ||||
|  | ||||
|     // Copy the rest of the data directly | ||||
|     out_data.uid = in_data.uid; | ||||
|     out_data.nintendo_id = in_data.nintendo_id; | ||||
|     out_data.lock_bytes = in_data.lock_bytes; | ||||
|     out_data.static_lock = in_data.static_lock; | ||||
|     out_data.compability_container = in_data.compability_container; | ||||
|  | ||||
|     out_data.constant_value = in_data.constant_value; | ||||
|     out_data.write_counter = in_data.write_counter; | ||||
|  | ||||
|     out_data.model_info = in_data.model_info; | ||||
|     out_data.keygen_salt = in_data.keygen_salt; | ||||
|     out_data.dynamic_lock = in_data.dynamic_lock; | ||||
|     out_data.CFG0 = in_data.CFG0; | ||||
|     out_data.CFG1 = in_data.CFG1; | ||||
|     out_data.password = in_data.password; | ||||
| } | ||||
|  | ||||
| bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { | ||||
|     const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); | ||||
|  | ||||
|     const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin", | ||||
|                                        Common::FS::FileAccessMode::Read, | ||||
|                                        Common::FS::FileType::BinaryFile}; | ||||
|  | ||||
|     if (!keys_file.IsOpen()) { | ||||
|         LOG_ERROR(Service_NFP, "Failed to open key file"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (keys_file.Read(unfixed_info) != 1) { | ||||
|         LOG_ERROR(Service_NFP, "Failed to read unfixed_info"); | ||||
|         return false; | ||||
|     } | ||||
|     if (keys_file.Read(locked_secret) != 1) { | ||||
|         LOG_ERROR(Service_NFP, "Failed to read locked-secret"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool IsKeyAvailable() { | ||||
|     const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); | ||||
|     return Common::FS::Exists(yuzu_keys_dir / "key_retail.bin"); | ||||
| } | ||||
|  | ||||
| bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { | ||||
|     InternalKey locked_secret{}; | ||||
|     InternalKey unfixed_info{}; | ||||
|  | ||||
|     if (!LoadKeys(locked_secret, unfixed_info)) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Generate keys | ||||
|     NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data); | ||||
|     const auto data_keys = GenerateKey(unfixed_info, encoded_data); | ||||
|     const auto tag_keys = GenerateKey(locked_secret, encoded_data); | ||||
|  | ||||
|     // Decrypt | ||||
|     Cipher(data_keys, encoded_data, tag_data); | ||||
|  | ||||
|     // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC! | ||||
|     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||||
|     mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), | ||||
|                     sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid), | ||||
|                     input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag)); | ||||
|  | ||||
|     // Regenerate data HMAC | ||||
|     constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; | ||||
|     mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(), | ||||
|                     sizeof(HmacKey), | ||||
|                     reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2, | ||||
|                     reinterpret_cast<unsigned char*>(&tag_data.hmac_data)); | ||||
|  | ||||
|     if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) { | ||||
|         LOG_ERROR(Service_NFP, "hmac_data doesn't match"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) { | ||||
|         LOG_ERROR(Service_NFP, "hmac_tag doesn't match"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) { | ||||
|     InternalKey locked_secret{}; | ||||
|     InternalKey unfixed_info{}; | ||||
|  | ||||
|     if (!LoadKeys(locked_secret, unfixed_info)) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Generate keys | ||||
|     const auto data_keys = GenerateKey(unfixed_info, tag_data); | ||||
|     const auto tag_keys = GenerateKey(locked_secret, tag_data); | ||||
|  | ||||
|     NTAG215File encoded_tag_data{}; | ||||
|  | ||||
|     // Generate tag HMAC | ||||
|     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||||
|     constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START; | ||||
|     mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), | ||||
|                     sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid), | ||||
|                     input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag)); | ||||
|  | ||||
|     // Init mbedtls HMAC context | ||||
|     mbedtls_md_context_t ctx; | ||||
|     mbedtls_md_init(&ctx); | ||||
|     mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); | ||||
|  | ||||
|     // Generate data HMAC | ||||
|     mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter), | ||||
|                            input_length2); // Data | ||||
|     mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), | ||||
|                            sizeof(HashData)); // Tag HMAC | ||||
|     mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid), | ||||
|                            input_length); | ||||
|     mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data)); | ||||
|  | ||||
|     // HMAC cleanup | ||||
|     mbedtls_md_free(&ctx); | ||||
|  | ||||
|     // Encrypt | ||||
|     Cipher(data_keys, tag_data, encoded_tag_data); | ||||
|  | ||||
|     // Convert back to hardware | ||||
|     encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace Service::NFP::AmiiboCrypto | ||||
| @@ -1,106 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
|  | ||||
| #include "core/hle/service/nfp/nfp_types.h" | ||||
|  | ||||
| struct mbedtls_md_context_t; | ||||
|  | ||||
| namespace Service::NFP::AmiiboCrypto { | ||||
| // Byte locations in Service::NFP::NTAG215File | ||||
| constexpr std::size_t HMAC_DATA_START = 0x8; | ||||
| constexpr std::size_t SETTINGS_START = 0x2c; | ||||
| constexpr std::size_t WRITE_COUNTER_START = 0x29; | ||||
| constexpr std::size_t HMAC_TAG_START = 0x1B4; | ||||
| constexpr std::size_t UUID_START = 0x1D4; | ||||
| constexpr std::size_t DYNAMIC_LOCK_START = 0x208; | ||||
|  | ||||
| using HmacKey = std::array<u8, 0x10>; | ||||
| using DrgbOutput = std::array<u8, 0x20>; | ||||
|  | ||||
| struct HashSeed { | ||||
|     u16_be magic; | ||||
|     std::array<u8, 0xE> padding; | ||||
|     UniqueSerialNumber uid_1; | ||||
|     u8 nintendo_id_1; | ||||
|     UniqueSerialNumber uid_2; | ||||
|     u8 nintendo_id_2; | ||||
|     std::array<u8, 0x20> keygen_salt; | ||||
| }; | ||||
| static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | ||||
|  | ||||
| struct InternalKey { | ||||
|     HmacKey hmac_key; | ||||
|     std::array<char, 0xE> type_string; | ||||
|     u8 reserved; | ||||
|     u8 magic_length; | ||||
|     std::array<u8, 0x10> magic_bytes; | ||||
|     std::array<u8, 0x20> xor_pad; | ||||
| }; | ||||
| static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable."); | ||||
|  | ||||
| struct CryptoCtx { | ||||
|     std::array<char, 480> buffer; | ||||
|     bool used; | ||||
|     std::size_t buffer_size; | ||||
|     s16 counter; | ||||
| }; | ||||
|  | ||||
| struct DerivedKeys { | ||||
|     std::array<u8, 0x10> aes_key; | ||||
|     std::array<u8, 0x10> aes_iv; | ||||
|     std::array<u8, 0x10> hmac_key; | ||||
| }; | ||||
| static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size"); | ||||
|  | ||||
| /// Validates that the amiibo file is not corrupted | ||||
| bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file); | ||||
|  | ||||
| /// Validates that the amiibo file is not corrupted | ||||
| bool IsAmiiboValid(const NTAG215File& ntag_file); | ||||
|  | ||||
| /// Converts from encrypted file format to encoded file format | ||||
| NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); | ||||
|  | ||||
| /// Converts from encoded file format to encrypted file format | ||||
| EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | ||||
|  | ||||
| /// Returns password needed to allow write access to protected memory | ||||
| u32 GetTagPassword(const TagUuid& uuid); | ||||
|  | ||||
| // Generates Seed needed for key derivation | ||||
| HashSeed GetSeed(const NTAG215File& data); | ||||
|  | ||||
| // Middle step on the generation of derived keys | ||||
| std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed); | ||||
|  | ||||
| // Initializes mbedtls context | ||||
| void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, | ||||
|                 const std::vector<u8>& seed); | ||||
|  | ||||
| // Feeds data to mbedtls context to generate the derived key | ||||
| void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output); | ||||
|  | ||||
| // Generates the derived key from amiibo data | ||||
| DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data); | ||||
|  | ||||
| // Encodes or decodes amiibo data | ||||
| void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data); | ||||
|  | ||||
| /// Loads both amiibo keys from key_retail.bin | ||||
| bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); | ||||
|  | ||||
| /// Returns true if key_retail.bin exist | ||||
| bool IsKeyAvailable(); | ||||
|  | ||||
| /// Decodes encrypted amiibo data returns true if output is valid | ||||
| bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); | ||||
|  | ||||
| /// Encodes plain amiibo data returns true if output is valid | ||||
| bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data); | ||||
|  | ||||
| } // namespace Service::NFP::AmiiboCrypto | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,120 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/nfp/nfp_types.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace Core::HID { | ||||
| class EmulatedController; | ||||
| enum class ControllerTriggerType; | ||||
| enum class NpadIdType : u32; | ||||
| } // namespace Core::HID | ||||
|  | ||||
| namespace Service::NFP { | ||||
| class NfpDevice { | ||||
| public: | ||||
|     NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, | ||||
|               KernelHelpers::ServiceContext& service_context_, | ||||
|               Kernel::KEvent* availability_change_event_); | ||||
|     ~NfpDevice(); | ||||
|  | ||||
|     void Initialize(); | ||||
|     void Finalize(); | ||||
|  | ||||
|     Result StartDetection(TagProtocol allowed_protocol); | ||||
|     Result StopDetection(); | ||||
|     Result Mount(MountTarget mount_target); | ||||
|     Result Unmount(); | ||||
|  | ||||
|     Result Flush(); | ||||
|     Result FlushDebug(); | ||||
|     Result FlushWithBreak(BreakType break_type); | ||||
|  | ||||
|     Result GetTagInfo(TagInfo& tag_info) const; | ||||
|     Result GetCommonInfo(CommonInfo& common_info) const; | ||||
|     Result GetModelInfo(ModelInfo& model_info) const; | ||||
|     Result GetRegisterInfo(RegisterInfo& register_info) const; | ||||
|     Result GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const; | ||||
|     Result GetAdminInfo(AdminInfo& admin_info) const; | ||||
|  | ||||
|     Result DeleteRegisterInfo(); | ||||
|     Result SetRegisterInfoPrivate(const AmiiboName& amiibo_name); | ||||
|     Result RestoreAmiibo(); | ||||
|     Result Format(); | ||||
|  | ||||
|     Result OpenApplicationArea(u32 access_id); | ||||
|     Result GetApplicationAreaId(u32& application_area_id) const; | ||||
|     Result GetApplicationArea(std::vector<u8>& data) const; | ||||
|     Result SetApplicationArea(std::span<const u8> data); | ||||
|     Result CreateApplicationArea(u32 access_id, std::span<const u8> data); | ||||
|     Result RecreateApplicationArea(u32 access_id, std::span<const u8> data); | ||||
|     Result DeleteApplicationArea(); | ||||
|     Result ExistApplicationArea(bool& has_application_area); | ||||
|  | ||||
|     Result GetAll(NfpData& data) const; | ||||
|     Result SetAll(const NfpData& data); | ||||
|     Result BreakTag(BreakType break_type); | ||||
|     Result ReadBackupData(); | ||||
|     Result WriteBackupData(); | ||||
|     Result WriteNtf(); | ||||
|  | ||||
|     u64 GetHandle() const; | ||||
|     u32 GetApplicationAreaSize() const; | ||||
|     DeviceState GetCurrentState() const; | ||||
|     Core::HID::NpadIdType GetNpadId() const; | ||||
|  | ||||
|     Kernel::KReadableEvent& GetActivateEvent() const; | ||||
|     Kernel::KReadableEvent& GetDeactivateEvent() const; | ||||
|  | ||||
| private: | ||||
|     void NpadUpdate(Core::HID::ControllerTriggerType type); | ||||
|     bool LoadAmiibo(std::span<const u8> data); | ||||
|     void CloseAmiibo(); | ||||
|  | ||||
|     AmiiboName GetAmiiboName(const AmiiboSettings& settings) const; | ||||
|     void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name); | ||||
|     AmiiboDate GetAmiiboDate(s64 posix_time) const; | ||||
|     u64 RemoveVersionByte(u64 application_id) const; | ||||
|     void UpdateSettingsCrc(); | ||||
|     void UpdateRegisterInfoCrc(); | ||||
|  | ||||
|     bool is_controller_set{}; | ||||
|     int callback_key; | ||||
|     const Core::HID::NpadIdType npad_id; | ||||
|     Core::System& system; | ||||
|     Core::HID::EmulatedController* npad_device = nullptr; | ||||
|     KernelHelpers::ServiceContext& service_context; | ||||
|     Kernel::KEvent* activate_event = nullptr; | ||||
|     Kernel::KEvent* deactivate_event = nullptr; | ||||
|     Kernel::KEvent* availability_change_event = nullptr; | ||||
|  | ||||
|     bool is_initalized{}; | ||||
|     bool is_data_moddified{}; | ||||
|     bool is_app_area_open{}; | ||||
|     bool is_plain_amiibo{}; | ||||
|     TagProtocol allowed_protocols{}; | ||||
|     s64 current_posix_time{}; | ||||
|     MountTarget mount_target{MountTarget::None}; | ||||
|     DeviceState device_state{DeviceState::Unavailable}; | ||||
|  | ||||
|     NTAG215File tag_data{}; | ||||
|     EncryptedNTAG215File encrypted_tag_data{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NFP | ||||
| @@ -1,672 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hid/hid_types.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/service/ipc_helpers.h" | ||||
| #include "core/hle/service/nfp/nfp_device.h" | ||||
| #include "core/hle/service/nfp/nfp_result.h" | ||||
| #include "core/hle/service/nfp/nfp_user.h" | ||||
|  | ||||
| namespace Service::NFP { | ||||
|  | ||||
| IUser::IUser(Core::System& system_) | ||||
|     : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} { | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, &IUser::Initialize, "Initialize"}, | ||||
|         {1, &IUser::Finalize, "Finalize"}, | ||||
|         {2, &IUser::ListDevices, "ListDevices"}, | ||||
|         {3, &IUser::StartDetection, "StartDetection"}, | ||||
|         {4, &IUser::StopDetection, "StopDetection"}, | ||||
|         {5, &IUser::Mount, "Mount"}, | ||||
|         {6, &IUser::Unmount, "Unmount"}, | ||||
|         {7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, | ||||
|         {8, &IUser::GetApplicationArea, "GetApplicationArea"}, | ||||
|         {9, &IUser::SetApplicationArea, "SetApplicationArea"}, | ||||
|         {10, &IUser::Flush, "Flush"}, | ||||
|         {11, &IUser::Restore, "Restore"}, | ||||
|         {12, &IUser::CreateApplicationArea, "CreateApplicationArea"}, | ||||
|         {13, &IUser::GetTagInfo, "GetTagInfo"}, | ||||
|         {14, &IUser::GetRegisterInfo, "GetRegisterInfo"}, | ||||
|         {15, &IUser::GetCommonInfo, "GetCommonInfo"}, | ||||
|         {16, &IUser::GetModelInfo, "GetModelInfo"}, | ||||
|         {17, &IUser::AttachActivateEvent, "AttachActivateEvent"}, | ||||
|         {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"}, | ||||
|         {19, &IUser::GetState, "GetState"}, | ||||
|         {20, &IUser::GetDeviceState, "GetDeviceState"}, | ||||
|         {21, &IUser::GetNpadId, "GetNpadId"}, | ||||
|         {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, | ||||
|         {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, | ||||
|         {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"}, | ||||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
|  | ||||
|     availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent"); | ||||
|  | ||||
|     for (u32 device_index = 0; device_index < 10; device_index++) { | ||||
|         devices[device_index] = | ||||
|             std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system, | ||||
|                                         service_context, availability_change_event); | ||||
|     } | ||||
| } | ||||
|  | ||||
| IUser ::~IUser() { | ||||
|     availability_change_event->Close(); | ||||
| } | ||||
|  | ||||
| void IUser::Initialize(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFP, "called"); | ||||
|  | ||||
|     state = State::Initialized; | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         device->Initialize(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(ResultSuccess); | ||||
| } | ||||
|  | ||||
| void IUser::Finalize(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFP, "called"); | ||||
|  | ||||
|     state = State::NonInitialized; | ||||
|  | ||||
|     for (auto& device : devices) { | ||||
|         device->Finalize(); | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(ResultSuccess); | ||||
| } | ||||
|  | ||||
| void IUser::ListDevices(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFP, "called"); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!ctx.CanWriteBuffer()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (ctx.GetWriteBufferSize() == 0) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::vector<u64> nfp_devices; | ||||
|     const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); | ||||
|  | ||||
|     for (const auto& device : devices) { | ||||
|         if (nfp_devices.size() >= max_allowed_devices) { | ||||
|             continue; | ||||
|         } | ||||
|         if (device->GetCurrentState() != DeviceState::Unavailable) { | ||||
|             nfp_devices.push_back(device->GetHandle()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (nfp_devices.empty()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ctx.WriteBuffer(nfp_devices); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.Push(static_cast<s32>(nfp_devices.size())); | ||||
| } | ||||
|  | ||||
| void IUser::StartDetection(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto nfp_protocol{rp.PopEnum<TagProtocol>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->StartDetection(nfp_protocol); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::StopDetection(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->StopDetection(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::Mount(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto model_type{rp.PopEnum<ModelType>()}; | ||||
|     const auto mount_target{rp.PopEnum<MountTarget>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, | ||||
|              model_type, mount_target); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->Mount(mount_target); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::Unmount(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->Unmount(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::OpenApplicationArea(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto access_id{rp.Pop<u32>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->OpenApplicationArea(access_id); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::GetApplicationArea(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto data_size = ctx.GetWriteBufferSize(); | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!ctx.CanWriteBuffer()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::vector<u8> data(data_size); | ||||
|     const auto result = device.value()->GetApplicationArea(data); | ||||
|     ctx.WriteBuffer(data); | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(result); | ||||
|     rb.Push(static_cast<u32>(data_size)); | ||||
| } | ||||
|  | ||||
| void IUser::SetApplicationArea(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto data{ctx.ReadBuffer()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size()); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!ctx.CanReadBuffer()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->SetApplicationArea(data); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::Flush(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->Flush(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::Restore(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->RestoreAmiibo(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::CreateApplicationArea(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto access_id{rp.Pop<u32>()}; | ||||
|     const auto data{ctx.ReadBuffer()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle, | ||||
|              access_id, data.size()); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!ctx.CanReadBuffer()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(InvalidArgument); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->CreateApplicationArea(access_id, data); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::GetTagInfo(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     TagInfo tag_info{}; | ||||
|     const auto result = device.value()->GetTagInfo(tag_info); | ||||
|     ctx.WriteBuffer(tag_info); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::GetRegisterInfo(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     RegisterInfo register_info{}; | ||||
|     const auto result = device.value()->GetRegisterInfo(register_info); | ||||
|     ctx.WriteBuffer(register_info); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::GetCommonInfo(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CommonInfo common_info{}; | ||||
|     const auto result = device.value()->GetCommonInfo(common_info); | ||||
|     ctx.WriteBuffer(common_info); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::GetModelInfo(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ModelInfo model_info{}; | ||||
|     const auto result = device.value()->GetModelInfo(model_info); | ||||
|     ctx.WriteBuffer(model_info); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| void IUser::AttachActivateEvent(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(device.value()->GetActivateEvent()); | ||||
| } | ||||
|  | ||||
| void IUser::AttachDeactivateEvent(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(device.value()->GetDeactivateEvent()); | ||||
| } | ||||
|  | ||||
| void IUser::GetState(HLERequestContext& ctx) { | ||||
|     LOG_DEBUG(Service_NFP, "called"); | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(state); | ||||
| } | ||||
|  | ||||
| void IUser::GetDeviceState(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(device.value()->GetCurrentState()); | ||||
| } | ||||
|  | ||||
| void IUser::GetNpadId(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushEnum(device.value()->GetNpadId()); | ||||
| } | ||||
|  | ||||
| void IUser::GetApplicationAreaSize(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 3}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.Push(device.value()->GetApplicationAreaSize()); | ||||
| } | ||||
|  | ||||
| void IUser::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { | ||||
|     LOG_INFO(Service_NFP, "called"); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|     rb.Push(ResultSuccess); | ||||
|     rb.PushCopyObjects(availability_change_event->GetReadableEvent()); | ||||
| } | ||||
|  | ||||
| void IUser::RecreateApplicationArea(HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     const auto device_handle{rp.Pop<u64>()}; | ||||
|     const auto access_id{rp.Pop<u32>()}; | ||||
|     const auto data{ctx.ReadBuffer()}; | ||||
|     LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle, | ||||
|              access_id, data.size()); | ||||
|  | ||||
|     if (state == State::NonInitialized) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(NfcDisabled); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto device = GetNfpDevice(device_handle); | ||||
|  | ||||
|     if (!device.has_value()) { | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(DeviceNotFound); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = device.value()->RecreateApplicationArea(access_id, data); | ||||
|     IPC::ResponseBuilder rb{ctx, 2}; | ||||
|     rb.Push(result); | ||||
| } | ||||
|  | ||||
| std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) { | ||||
|     for (auto& device : devices) { | ||||
|         if (device->GetHandle() == handle) { | ||||
|             return device; | ||||
|         } | ||||
|     } | ||||
|     return std::nullopt; | ||||
| } | ||||
|  | ||||
| } // namespace Service::NFP | ||||
| @@ -1,63 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
|  | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Service::NFP { | ||||
| class NfpDevice; | ||||
|  | ||||
| class IUser final : public ServiceFramework<IUser> { | ||||
| public: | ||||
|     explicit IUser(Core::System& system_); | ||||
|     ~IUser(); | ||||
|  | ||||
| private: | ||||
|     enum class State : u32 { | ||||
|         NonInitialized, | ||||
|         Initialized, | ||||
|     }; | ||||
|  | ||||
|     void Initialize(HLERequestContext& ctx); | ||||
|     void Finalize(HLERequestContext& ctx); | ||||
|     void ListDevices(HLERequestContext& ctx); | ||||
|     void StartDetection(HLERequestContext& ctx); | ||||
|     void StopDetection(HLERequestContext& ctx); | ||||
|     void Mount(HLERequestContext& ctx); | ||||
|     void Unmount(HLERequestContext& ctx); | ||||
|     void OpenApplicationArea(HLERequestContext& ctx); | ||||
|     void GetApplicationArea(HLERequestContext& ctx); | ||||
|     void SetApplicationArea(HLERequestContext& ctx); | ||||
|     void Flush(HLERequestContext& ctx); | ||||
|     void Restore(HLERequestContext& ctx); | ||||
|     void CreateApplicationArea(HLERequestContext& ctx); | ||||
|     void GetTagInfo(HLERequestContext& ctx); | ||||
|     void GetRegisterInfo(HLERequestContext& ctx); | ||||
|     void GetCommonInfo(HLERequestContext& ctx); | ||||
|     void GetModelInfo(HLERequestContext& ctx); | ||||
|     void AttachActivateEvent(HLERequestContext& ctx); | ||||
|     void AttachDeactivateEvent(HLERequestContext& ctx); | ||||
|     void GetState(HLERequestContext& ctx); | ||||
|     void GetDeviceState(HLERequestContext& ctx); | ||||
|     void GetNpadId(HLERequestContext& ctx); | ||||
|     void GetApplicationAreaSize(HLERequestContext& ctx); | ||||
|     void AttachAvailabilityChangeEvent(HLERequestContext& ctx); | ||||
|     void RecreateApplicationArea(HLERequestContext& ctx); | ||||
|  | ||||
|     std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle); | ||||
|  | ||||
|     KernelHelpers::ServiceContext service_context; | ||||
|  | ||||
|     std::array<std::shared_ptr<NfpDevice>, 10> devices{}; | ||||
|  | ||||
|     State state{State::NonInitialized}; | ||||
|     Kernel::KEvent* availability_change_event; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NFP | ||||
| @@ -1,43 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/binder/IBinder.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Kernel { | ||||
| class HLERequestContext; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| enum class TransactionId { | ||||
|     RequestBuffer = 1, | ||||
|     SetBufferCount = 2, | ||||
|     DequeueBuffer = 3, | ||||
|     DetachBuffer = 4, | ||||
|     DetachNextBuffer = 5, | ||||
|     AttachBuffer = 6, | ||||
|     QueueBuffer = 7, | ||||
|     CancelBuffer = 8, | ||||
|     Query = 9, | ||||
|     Connect = 10, | ||||
|     Disconnect = 11, | ||||
|     AllocateBuffers = 13, | ||||
|     SetPreallocatedBuffer = 14, | ||||
|     GetBufferHistory = 17, | ||||
| }; | ||||
|  | ||||
| class IBinder { | ||||
| public: | ||||
|     virtual ~IBinder() = default; | ||||
|     virtual void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, | ||||
|                           u32 flags) = 0; | ||||
|     virtual Kernel::KReadableEvent& GetNativeHandle() = 0; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,46 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItem.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "common/math_util.h" | ||||
| #include "core/hle/service/nvflinger/ui/fence.h" | ||||
| #include "core/hle/service/nvflinger/window.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class GraphicBuffer; | ||||
|  | ||||
| class BufferItem final { | ||||
| public: | ||||
|     constexpr BufferItem() = default; | ||||
|  | ||||
|     std::shared_ptr<GraphicBuffer> graphic_buffer; | ||||
|     Fence fence; | ||||
|     Common::Rectangle<s32> crop; | ||||
|     NativeWindowTransform transform{}; | ||||
|     u32 scaling_mode{}; | ||||
|     s64 timestamp{}; | ||||
|     bool is_auto_timestamp{}; | ||||
|     u64 frame_number{}; | ||||
|  | ||||
|     // The default value for buf, used to indicate this doesn't correspond to a slot. | ||||
|     static constexpr s32 INVALID_BUFFER_SLOT = -1; | ||||
|     union { | ||||
|         s32 slot{INVALID_BUFFER_SLOT}; | ||||
|         s32 buf; | ||||
|     }; | ||||
|  | ||||
|     bool is_droppable{}; | ||||
|     bool acquire_called{}; | ||||
|     bool transform_to_display_inverse{}; | ||||
|     s32 swap_interval{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,59 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferItemConsumer.cpp | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/nvflinger/buffer_item.h" | ||||
| #include "core/hle/service/nvflinger/buffer_item_consumer.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_consumer.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| BufferItemConsumer::BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer_) | ||||
|     : ConsumerBase{std::move(consumer_)} {} | ||||
|  | ||||
| Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when, | ||||
|                                          bool wait_for_fence) { | ||||
|     if (!item) { | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock lock{mutex}; | ||||
|  | ||||
|     if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) { | ||||
|         if (status != Status::NoBufferAvailable) { | ||||
|             LOG_ERROR(Service_NVFlinger, "Failed to acquire buffer: {}", status); | ||||
|         } | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     if (wait_for_fence) { | ||||
|         UNIMPLEMENTED(); | ||||
|     } | ||||
|  | ||||
|     item->graphic_buffer = slots[item->slot].graphic_buffer; | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferItemConsumer::ReleaseBuffer(const BufferItem& item, const Fence& release_fence) { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|  | ||||
|     if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence); | ||||
|         status != Status::NoError) { | ||||
|         LOG_ERROR(Service_NVFlinger, "Failed to add fence: {}", status); | ||||
|     } | ||||
|  | ||||
|     if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer); | ||||
|         status != Status::NoError) { | ||||
|         LOG_WARNING(Service_NVFlinger, "Failed to release buffer: {}", status); | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,28 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItemConsumer.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/consumer_base.h" | ||||
| #include "core/hle/service/nvflinger/status.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class BufferItem; | ||||
|  | ||||
| class BufferItemConsumer final : public ConsumerBase { | ||||
| public: | ||||
|     explicit BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer); | ||||
|     Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when, | ||||
|                          bool wait_for_fence = true); | ||||
|     Status ReleaseBuffer(const BufferItem& item, const Fence& release_fence); | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,213 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/nvdrv/core/nvmap.h" | ||||
| #include "core/hle/service/nvflinger/buffer_item.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_consumer.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_core.h" | ||||
| #include "core/hle/service/nvflinger/producer_listener.h" | ||||
| #include "core/hle/service/nvflinger/ui/graphic_buffer.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_, | ||||
|                                          Service::Nvidia::NvCore::NvMap& nvmap_) | ||||
|     : core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {} | ||||
|  | ||||
| BufferQueueConsumer::~BufferQueueConsumer() = default; | ||||
|  | ||||
| Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, | ||||
|                                           std::chrono::nanoseconds expected_present) { | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     // Check that the consumer doesn't currently have the maximum number of buffers acquired. | ||||
|     const s32 num_acquired_buffers{ | ||||
|         static_cast<s32>(std::count_if(slots.begin(), slots.end(), [](const auto& slot) { | ||||
|             return slot.buffer_state == BufferState::Acquired; | ||||
|         }))}; | ||||
|  | ||||
|     if (num_acquired_buffers >= core->max_acquired_buffer_count + 1) { | ||||
|         LOG_ERROR(Service_NVFlinger, "max acquired buffer count reached: {} (max {})", | ||||
|                   num_acquired_buffers, core->max_acquired_buffer_count); | ||||
|         return Status::InvalidOperation; | ||||
|     } | ||||
|  | ||||
|     // Check if the queue is empty. | ||||
|     if (core->queue.empty()) { | ||||
|         return Status::NoBufferAvailable; | ||||
|     } | ||||
|  | ||||
|     auto front(core->queue.begin()); | ||||
|  | ||||
|     // If expected_present is specified, we may not want to return a buffer yet. | ||||
|     if (expected_present.count() != 0) { | ||||
|         constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second | ||||
|  | ||||
|         // The expected_present argument indicates when the buffer is expected to be presented | ||||
|         // on-screen. | ||||
|         while (core->queue.size() > 1 && !core->queue[0].is_auto_timestamp) { | ||||
|             const auto& buffer_item{core->queue[1]}; | ||||
|  | ||||
|             // If entry[1] is timely, drop entry[0] (and repeat). | ||||
|             const auto desired_present = buffer_item.timestamp; | ||||
|             if (desired_present < expected_present.count() - MAX_REASONABLE_NSEC || | ||||
|                 desired_present > expected_present.count()) { | ||||
|                 // This buffer is set to display in the near future, or desired_present is garbage. | ||||
|                 LOG_DEBUG(Service_NVFlinger, "nodrop desire={} expect={}", desired_present, | ||||
|                           expected_present.count()); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             LOG_DEBUG(Service_NVFlinger, "drop desire={} expect={} size={}", desired_present, | ||||
|                       expected_present.count(), core->queue.size()); | ||||
|  | ||||
|             if (core->StillTracking(*front)) { | ||||
|                 // Front buffer is still in mSlots, so mark the slot as free | ||||
|                 slots[front->slot].buffer_state = BufferState::Free; | ||||
|             } | ||||
|  | ||||
|             core->queue.erase(front); | ||||
|             front = core->queue.begin(); | ||||
|         } | ||||
|  | ||||
|         // See if the front buffer is ready to be acquired. | ||||
|         const auto desired_present = front->timestamp; | ||||
|         if (desired_present > expected_present.count() && | ||||
|             desired_present < expected_present.count() + MAX_REASONABLE_NSEC) { | ||||
|             LOG_DEBUG(Service_NVFlinger, "defer desire={} expect={}", desired_present, | ||||
|                       expected_present.count()); | ||||
|             return Status::PresentLater; | ||||
|         } | ||||
|  | ||||
|         LOG_DEBUG(Service_NVFlinger, "accept desire={} expect={}", desired_present, | ||||
|                   expected_present.count()); | ||||
|     } | ||||
|  | ||||
|     const auto slot = front->slot; | ||||
|     *out_buffer = *front; | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "acquiring slot={}", slot); | ||||
|  | ||||
|     // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to | ||||
|     // avoid unnecessarily remapping this buffer on the consumer side. | ||||
|     if (out_buffer->acquire_called) { | ||||
|         out_buffer->graphic_buffer = nullptr; | ||||
|     } | ||||
|  | ||||
|     core->queue.erase(front); | ||||
|  | ||||
|     // We might have freed a slot while dropping old buffers, or the producer  may be blocked | ||||
|     // waiting for the number of buffers in the queue to decrease. | ||||
|     core->SignalDequeueCondition(); | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence) { | ||||
|     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot {} out of range", slot); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<IProducerListener> listener; | ||||
|     { | ||||
|         std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|         // If the frame number has changed because the buffer has been reallocated, we can ignore | ||||
|         // this ReleaseBuffer for the old buffer. | ||||
|         if (frame_number != slots[slot].frame_number) { | ||||
|             return Status::StaleBufferSlot; | ||||
|         } | ||||
|  | ||||
|         // Make sure this buffer hasn't been queued while acquired by the consumer. | ||||
|         auto current(core->queue.begin()); | ||||
|         while (current != core->queue.end()) { | ||||
|             if (current->slot == slot) { | ||||
|                 LOG_ERROR(Service_NVFlinger, "buffer slot {} pending release is currently queued", | ||||
|                           slot); | ||||
|                 return Status::BadValue; | ||||
|             } | ||||
|             ++current; | ||||
|         } | ||||
|  | ||||
|         slots[slot].buffer_state = BufferState::Free; | ||||
|  | ||||
|         nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true); | ||||
|  | ||||
|         listener = core->connected_producer_listener; | ||||
|  | ||||
|         LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot); | ||||
|  | ||||
|         core->SignalDequeueCondition(); | ||||
|     } | ||||
|  | ||||
|     // Call back without lock held | ||||
|     if (listener != nullptr) { | ||||
|         listener->OnBufferReleased(); | ||||
|     } | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueConsumer::Connect(std::shared_ptr<IConsumerListener> consumer_listener, | ||||
|                                     bool controlled_by_app) { | ||||
|     if (consumer_listener == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "consumer_listener may not be nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "controlled_by_app={}", controlled_by_app); | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     core->consumer_listener = std::move(consumer_listener); | ||||
|     core->consumer_controlled_by_app = controlled_by_app; | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) { | ||||
|     if (out_slot_mask == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "out_slot_mask may not be nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     u64 mask = 0; | ||||
|     for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { | ||||
|         if (!slots[s].acquire_called) { | ||||
|             mask |= (1ULL << s); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Remove from the mask queued buffers for which acquire has been called, since the consumer | ||||
|     // will not receive their buffer addresses and so must retain their cached information | ||||
|     auto current(core->queue.begin()); | ||||
|     while (current != core->queue.end()) { | ||||
|         if (current->acquire_called) { | ||||
|             mask &= ~(1ULL << current->slot); | ||||
|         } | ||||
|         ++current; | ||||
|     } | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "returning mask {}", mask); | ||||
|     *out_slot_mask = mask; | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,43 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueConsumer.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_defs.h" | ||||
| #include "core/hle/service/nvflinger/status.h" | ||||
|  | ||||
| namespace Service::Nvidia::NvCore { | ||||
| class NvMap; | ||||
| } // namespace Service::Nvidia::NvCore | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class BufferItem; | ||||
| class BufferQueueCore; | ||||
| class IConsumerListener; | ||||
|  | ||||
| class BufferQueueConsumer final { | ||||
| public: | ||||
|     explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_, | ||||
|                                  Service::Nvidia::NvCore::NvMap& nvmap_); | ||||
|     ~BufferQueueConsumer(); | ||||
|  | ||||
|     Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present); | ||||
|     Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence); | ||||
|     Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app); | ||||
|     Status GetReleasedBuffers(u64* out_slot_mask); | ||||
|  | ||||
| private: | ||||
|     std::shared_ptr<BufferQueueCore> core; | ||||
|     BufferQueueDefs::SlotsType& slots; | ||||
|     Service::Nvidia::NvCore::NvMap& nvmap; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,115 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp | ||||
|  | ||||
| #include "common/assert.h" | ||||
|  | ||||
| #include "core/hle/service/nvflinger/buffer_queue_core.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| BufferQueueCore::BufferQueueCore() = default; | ||||
|  | ||||
| BufferQueueCore::~BufferQueueCore() = default; | ||||
|  | ||||
| void BufferQueueCore::NotifyShutdown() { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|  | ||||
|     is_shutting_down = true; | ||||
|  | ||||
|     SignalDequeueCondition(); | ||||
| } | ||||
|  | ||||
| void BufferQueueCore::SignalDequeueCondition() { | ||||
|     dequeue_possible.store(true); | ||||
|     dequeue_condition.notify_all(); | ||||
| } | ||||
|  | ||||
| bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) { | ||||
|     if (is_shutting_down) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); }); | ||||
|     dequeue_possible.store(false); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const { | ||||
|     // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer. | ||||
|     if (!use_async_buffer) { | ||||
|         return max_acquired_buffer_count; | ||||
|     } | ||||
|  | ||||
|     if (dequeue_buffer_cannot_block || async) { | ||||
|         return max_acquired_buffer_count + 1; | ||||
|     } | ||||
|  | ||||
|     return max_acquired_buffer_count; | ||||
| } | ||||
|  | ||||
| s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const { | ||||
|     return GetMinUndequeuedBufferCountLocked(async) + 1; | ||||
| } | ||||
|  | ||||
| s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { | ||||
|     const auto min_buffer_count = GetMinMaxBufferCountLocked(async); | ||||
|     auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count); | ||||
|  | ||||
|     if (override_max_buffer_count != 0) { | ||||
|         ASSERT(override_max_buffer_count >= min_buffer_count); | ||||
|         max_buffer_count = override_max_buffer_count; | ||||
|     } | ||||
|  | ||||
|     // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed | ||||
|     // need to have their slots preserved. | ||||
|     for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { | ||||
|         const auto state = slots[slot].buffer_state; | ||||
|         if (state == BufferState::Queued || state == BufferState::Dequeued) { | ||||
|             max_buffer_count = slot + 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return max_buffer_count; | ||||
| } | ||||
|  | ||||
| s32 BufferQueueCore::GetPreallocatedBufferCountLocked() const { | ||||
|     return static_cast<s32>(std::count_if(slots.begin(), slots.end(), | ||||
|                                           [](const auto& slot) { return slot.is_preallocated; })); | ||||
| } | ||||
|  | ||||
| void BufferQueueCore::FreeBufferLocked(s32 slot) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot {}", slot); | ||||
|  | ||||
|     slots[slot].graphic_buffer.reset(); | ||||
|  | ||||
|     slots[slot].buffer_state = BufferState::Free; | ||||
|     slots[slot].frame_number = UINT32_MAX; | ||||
|     slots[slot].acquire_called = false; | ||||
|     slots[slot].fence = Fence::NoFence(); | ||||
| } | ||||
|  | ||||
| void BufferQueueCore::FreeAllBuffersLocked() { | ||||
|     buffer_has_been_queued = false; | ||||
|  | ||||
|     for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { | ||||
|         FreeBufferLocked(slot); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool BufferQueueCore::StillTracking(const BufferItem& item) const { | ||||
|     const BufferSlot& slot = slots[item.slot]; | ||||
|  | ||||
|     return (slot.graphic_buffer != nullptr) && (item.graphic_buffer == slot.graphic_buffer); | ||||
| } | ||||
|  | ||||
| void BufferQueueCore::WaitWhileAllocatingLocked() const { | ||||
|     while (is_allocating) { | ||||
|         is_allocating_condition.wait(mutex); | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,80 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <condition_variable> | ||||
| #include <list> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <set> | ||||
| #include <vector> | ||||
|  | ||||
| #include "core/hle/service/nvflinger/buffer_item.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_defs.h" | ||||
| #include "core/hle/service/nvflinger/pixel_format.h" | ||||
| #include "core/hle/service/nvflinger/status.h" | ||||
| #include "core/hle/service/nvflinger/window.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class IConsumerListener; | ||||
| class IProducerListener; | ||||
|  | ||||
| class BufferQueueCore final { | ||||
|     friend class BufferQueueProducer; | ||||
|     friend class BufferQueueConsumer; | ||||
|  | ||||
| public: | ||||
|     static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT; | ||||
|  | ||||
|     BufferQueueCore(); | ||||
|     ~BufferQueueCore(); | ||||
|  | ||||
|     void NotifyShutdown(); | ||||
|  | ||||
| private: | ||||
|     void SignalDequeueCondition(); | ||||
|     bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk); | ||||
|  | ||||
|     s32 GetMinUndequeuedBufferCountLocked(bool async) const; | ||||
|     s32 GetMinMaxBufferCountLocked(bool async) const; | ||||
|     s32 GetMaxBufferCountLocked(bool async) const; | ||||
|     s32 GetPreallocatedBufferCountLocked() const; | ||||
|     void FreeBufferLocked(s32 slot); | ||||
|     void FreeAllBuffersLocked(); | ||||
|     bool StillTracking(const BufferItem& item) const; | ||||
|     void WaitWhileAllocatingLocked() const; | ||||
|  | ||||
| private: | ||||
|     mutable std::mutex mutex; | ||||
|     bool is_abandoned{}; | ||||
|     bool consumer_controlled_by_app{}; | ||||
|     std::shared_ptr<IConsumerListener> consumer_listener; | ||||
|     u32 consumer_usage_bit{}; | ||||
|     NativeWindowApi connected_api{NativeWindowApi::NoConnectedApi}; | ||||
|     std::shared_ptr<IProducerListener> connected_producer_listener; | ||||
|     BufferQueueDefs::SlotsType slots{}; | ||||
|     std::vector<BufferItem> queue; | ||||
|     s32 override_max_buffer_count{}; | ||||
|     std::condition_variable dequeue_condition; | ||||
|     std::atomic<bool> dequeue_possible{}; | ||||
|     const bool use_async_buffer{}; // This is always disabled on HOS | ||||
|     bool dequeue_buffer_cannot_block{}; | ||||
|     PixelFormat default_buffer_format{PixelFormat::Rgba8888}; | ||||
|     u32 default_width{1}; | ||||
|     u32 default_height{1}; | ||||
|     s32 default_max_buffer_count{2}; | ||||
|     const s32 max_acquired_buffer_count{}; // This is always zero on HOS | ||||
|     bool buffer_has_been_queued{}; | ||||
|     u64 frame_counter{}; | ||||
|     u32 transform_hint{}; | ||||
|     bool is_allocating{}; | ||||
|     mutable std::condition_variable_any is_allocating_condition; | ||||
|     bool is_shutting_down{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,21 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueDefs.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/buffer_slot.h" | ||||
|  | ||||
| namespace Service::android::BufferQueueDefs { | ||||
|  | ||||
| // BufferQueue will keep track of at most this value of buffers. | ||||
| constexpr s32 NUM_BUFFER_SLOTS = 64; | ||||
|  | ||||
| using SlotsType = std::array<BufferSlot, NUM_BUFFER_SLOTS>; | ||||
|  | ||||
| } // namespace Service::android::BufferQueueDefs | ||||
| @@ -1,933 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/hle_ipc.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/kernel/k_readable_event.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/nvdrv/core/nvmap.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_core.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_producer.h" | ||||
| #include "core/hle/service/nvflinger/consumer_listener.h" | ||||
| #include "core/hle/service/nvflinger/parcel.h" | ||||
| #include "core/hle/service/nvflinger/ui/graphic_buffer.h" | ||||
| #include "core/hle/service/nvflinger/window.h" | ||||
| #include "core/hle/service/vi/vi.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, | ||||
|                                          std::shared_ptr<BufferQueueCore> buffer_queue_core_, | ||||
|                                          Service::Nvidia::NvCore::NvMap& nvmap_) | ||||
|     : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots), | ||||
|       nvmap(nvmap_) { | ||||
|     buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); | ||||
| } | ||||
|  | ||||
| BufferQueueProducer::~BufferQueueProducer() { | ||||
|     service_context.CloseEvent(buffer_wait_event); | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot {}", slot); | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot, | ||||
|                   BufferQueueDefs::NUM_BUFFER_SLOTS); | ||||
|         return Status::BadValue; | ||||
|     } else if (slots[slot].buffer_state != BufferState::Dequeued) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot, | ||||
|                   slots[slot].buffer_state); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     slots[slot].request_buffer_called = true; | ||||
|     *buf = slots[slot].graphic_buffer; | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::SetBufferCount(s32 buffer_count) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "count = {}", buffer_count); | ||||
|  | ||||
|     std::shared_ptr<IConsumerListener> listener; | ||||
|     { | ||||
|         std::scoped_lock lock{core->mutex}; | ||||
|         core->WaitWhileAllocatingLocked(); | ||||
|  | ||||
|         if (core->is_abandoned) { | ||||
|             LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|             return Status::NoInit; | ||||
|         } | ||||
|  | ||||
|         if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|             LOG_ERROR(Service_NVFlinger, "buffer_count {} too large (max {})", buffer_count, | ||||
|                       BufferQueueDefs::NUM_BUFFER_SLOTS); | ||||
|             return Status::BadValue; | ||||
|         } | ||||
|  | ||||
|         // There must be no dequeued buffers when changing the buffer count. | ||||
|         for (s32 s{}; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { | ||||
|             if (slots[s].buffer_state == BufferState::Dequeued) { | ||||
|                 LOG_ERROR(Service_NVFlinger, "buffer owned by producer"); | ||||
|                 return Status::BadValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (buffer_count == 0) { | ||||
|             core->override_max_buffer_count = 0; | ||||
|             core->SignalDequeueCondition(); | ||||
|             return Status::NoError; | ||||
|         } | ||||
|  | ||||
|         const s32 min_buffer_slots = core->GetMinMaxBufferCountLocked(false); | ||||
|         if (buffer_count < min_buffer_slots) { | ||||
|             LOG_ERROR(Service_NVFlinger, "requested buffer count {} is less than minimum {}", | ||||
|                       buffer_count, min_buffer_slots); | ||||
|             return Status::BadValue; | ||||
|         } | ||||
|  | ||||
|         // Here we are guaranteed that the producer doesn't have any dequeued buffers and will | ||||
|         // release all of its buffer references. | ||||
|         if (core->GetPreallocatedBufferCountLocked() <= 0) { | ||||
|             core->FreeAllBuffersLocked(); | ||||
|         } | ||||
|  | ||||
|         core->override_max_buffer_count = buffer_count; | ||||
|         core->SignalDequeueCondition(); | ||||
|         buffer_wait_event->Signal(); | ||||
|         listener = core->consumer_listener; | ||||
|     } | ||||
|  | ||||
|     // Call back without lock held | ||||
|     if (listener != nullptr) { | ||||
|         listener->OnBuffersReleased(); | ||||
|     } | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags, | ||||
|                                                       std::unique_lock<std::mutex>& lk) const { | ||||
|     bool try_again = true; | ||||
|  | ||||
|     while (try_again) { | ||||
|         if (core->is_abandoned) { | ||||
|             LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|             return Status::NoInit; | ||||
|         } | ||||
|  | ||||
|         const s32 max_buffer_count = core->GetMaxBufferCountLocked(async); | ||||
|         if (async && core->override_max_buffer_count) { | ||||
|             if (core->override_max_buffer_count < max_buffer_count) { | ||||
|                 LOG_ERROR(Service_NVFlinger, "async mode is invalid with buffer count override"); | ||||
|                 return Status::BadValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Free up any buffers that are in slots beyond the max buffer count | ||||
|         for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { | ||||
|             ASSERT(slots[s].buffer_state == BufferState::Free); | ||||
|             if (slots[s].graphic_buffer != nullptr) { | ||||
|                 core->FreeBufferLocked(s); | ||||
|                 *return_flags |= Status::ReleaseAllBuffers; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Look for a free buffer to give to the client | ||||
|         *found = BufferQueueCore::INVALID_BUFFER_SLOT; | ||||
|         s32 dequeued_count{}; | ||||
|         s32 acquired_count{}; | ||||
|         for (s32 s{}; s < max_buffer_count; ++s) { | ||||
|             switch (slots[s].buffer_state) { | ||||
|             case BufferState::Dequeued: | ||||
|                 ++dequeued_count; | ||||
|                 break; | ||||
|             case BufferState::Acquired: | ||||
|                 ++acquired_count; | ||||
|                 break; | ||||
|             case BufferState::Free: | ||||
|                 // We return the oldest of the free buffers to avoid stalling the producer if | ||||
|                 // possible, since the consumer may still have pending reads of in-flight buffers | ||||
|                 if (*found == BufferQueueCore::INVALID_BUFFER_SLOT || | ||||
|                     slots[s].frame_number < slots[*found].frame_number) { | ||||
|                     *found = s; | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Producers are not allowed to dequeue more than one buffer if they did not set a buffer | ||||
|         // count | ||||
|         if (!core->override_max_buffer_count && dequeued_count) { | ||||
|             LOG_ERROR(Service_NVFlinger, | ||||
|                       "can't dequeue multiple buffers without setting the buffer count"); | ||||
|             return Status::InvalidOperation; | ||||
|         } | ||||
|  | ||||
|         // See whether a buffer has been queued since the last SetBufferCount so we know whether to | ||||
|         // perform the min undequeued buffers check below | ||||
|         if (core->buffer_has_been_queued) { | ||||
|             // Make sure the producer is not trying to dequeue more buffers than allowed | ||||
|             const s32 new_undequeued_count = max_buffer_count - (dequeued_count + 1); | ||||
|             const s32 min_undequeued_count = core->GetMinUndequeuedBufferCountLocked(async); | ||||
|             if (new_undequeued_count < min_undequeued_count) { | ||||
|                 LOG_ERROR(Service_NVFlinger, | ||||
|                           "min undequeued buffer count({}) exceeded (dequeued={} undequeued={})", | ||||
|                           min_undequeued_count, dequeued_count, new_undequeued_count); | ||||
|                 return Status::InvalidOperation; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // If we disconnect and reconnect quickly, we can be in a state where our slots are empty | ||||
|         // but we have many buffers in the queue. This can cause us to run out of memory if we | ||||
|         // outrun the consumer. Wait here if it looks like we have too many buffers queued up. | ||||
|         const bool too_many_buffers = core->queue.size() > static_cast<size_t>(max_buffer_count); | ||||
|         if (too_many_buffers) { | ||||
|             LOG_ERROR(Service_NVFlinger, "queue size is {}, waiting", core->queue.size()); | ||||
|         } | ||||
|  | ||||
|         // If no buffer is found, or if the queue has too many buffers outstanding, wait for a | ||||
|         // buffer to be acquired or released, or for the max buffer count to change. | ||||
|         try_again = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || too_many_buffers; | ||||
|         if (try_again) { | ||||
|             // Return an error if we're in non-blocking mode (producer and consumer are controlled | ||||
|             // by the application). | ||||
|             if (core->dequeue_buffer_cannot_block && | ||||
|                 (acquired_count <= core->max_acquired_buffer_count)) { | ||||
|                 return Status::WouldBlock; | ||||
|             } | ||||
|  | ||||
|             if (!core->WaitForDequeueCondition(lk)) { | ||||
|                 // We are no longer running | ||||
|                 return Status::NoError; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool async, u32 width, | ||||
|                                           u32 height, PixelFormat format, u32 usage) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "async={} w={} h={} format={}, usage={}", async ? "true" : "false", | ||||
|               width, height, format, usage); | ||||
|  | ||||
|     if ((width != 0 && height == 0) || (width == 0 && height != 0)) { | ||||
|         LOG_ERROR(Service_NVFlinger, "invalid size: w={} h={}", width, height); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     Status return_flags = Status::NoError; | ||||
|     bool attached_by_consumer = false; | ||||
|     { | ||||
|         std::unique_lock lock{core->mutex}; | ||||
|         core->WaitWhileAllocatingLocked(); | ||||
|  | ||||
|         if (format == PixelFormat::NoFormat) { | ||||
|             format = core->default_buffer_format; | ||||
|         } | ||||
|  | ||||
|         // Enable the usage bits the consumer requested | ||||
|         usage |= core->consumer_usage_bit; | ||||
|  | ||||
|         s32 found{}; | ||||
|         Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags, lock); | ||||
|         if (status != Status::NoError) { | ||||
|             return status; | ||||
|         } | ||||
|  | ||||
|         // This should not happen | ||||
|         if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { | ||||
|             LOG_ERROR(Service_NVFlinger, "no available buffer slots"); | ||||
|             return Status::Busy; | ||||
|         } | ||||
|  | ||||
|         *out_slot = found; | ||||
|  | ||||
|         attached_by_consumer = slots[found].attached_by_consumer; | ||||
|  | ||||
|         const bool use_default_size = !width && !height; | ||||
|         if (use_default_size) { | ||||
|             width = core->default_width; | ||||
|             height = core->default_height; | ||||
|         } | ||||
|  | ||||
|         slots[found].buffer_state = BufferState::Dequeued; | ||||
|  | ||||
|         const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer); | ||||
|         if ((buffer == nullptr) || (buffer->Width() != width) || (buffer->Height() != height) || | ||||
|             (buffer->Format() != format) || ((buffer->Usage() & usage) != usage)) { | ||||
|             slots[found].acquire_called = false; | ||||
|             slots[found].graphic_buffer = nullptr; | ||||
|             slots[found].request_buffer_called = false; | ||||
|             slots[found].fence = Fence::NoFence(); | ||||
|  | ||||
|             return_flags |= Status::BufferNeedsReallocation; | ||||
|         } | ||||
|  | ||||
|         *out_fence = slots[found].fence; | ||||
|         slots[found].fence = Fence::NoFence(); | ||||
|     } | ||||
|  | ||||
|     if ((return_flags & Status::BufferNeedsReallocation) != Status::None) { | ||||
|         LOG_DEBUG(Service_NVFlinger, "allocating a new buffer for slot {}", *out_slot); | ||||
|  | ||||
|         auto graphic_buffer = std::make_shared<GraphicBuffer>(width, height, format, usage); | ||||
|         if (graphic_buffer == nullptr) { | ||||
|             LOG_ERROR(Service_NVFlinger, "creating GraphicBuffer failed"); | ||||
|             return Status::NoMemory; | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|             if (core->is_abandoned) { | ||||
|                 LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|                 return Status::NoInit; | ||||
|             } | ||||
|  | ||||
|             slots[*out_slot].frame_number = UINT32_MAX; | ||||
|             slots[*out_slot].graphic_buffer = graphic_buffer; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (attached_by_consumer) { | ||||
|         return_flags |= Status::BufferNeedsReallocation; | ||||
|     } | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "returning slot={} frame={}, flags={}", *out_slot, | ||||
|               slots[*out_slot].frame_number, return_flags); | ||||
|  | ||||
|     return return_flags; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::DetachBuffer(s32 slot) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot {}", slot); | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot {} out of range [0, {})", slot, | ||||
|                   BufferQueueDefs::NUM_BUFFER_SLOTS); | ||||
|         return Status::BadValue; | ||||
|     } else if (slots[slot].buffer_state != BufferState::Dequeued) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot, | ||||
|                   slots[slot].buffer_state); | ||||
|         return Status::BadValue; | ||||
|     } else if (!slots[slot].request_buffer_called) { | ||||
|         LOG_ERROR(Service_NVFlinger, "buffer in slot {} has not been requested", slot); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     core->FreeBufferLocked(slot); | ||||
|     core->SignalDequeueCondition(); | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, | ||||
|                                              Fence* out_fence) { | ||||
|     if (out_buffer == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "out_buffer must not be nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } else if (out_fence == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "out_fence must not be nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|     core->WaitWhileAllocatingLocked(); | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     // Find the oldest valid slot | ||||
|     int found = BufferQueueCore::INVALID_BUFFER_SLOT; | ||||
|     for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { | ||||
|         if (slots[s].buffer_state == BufferState::Free && slots[s].graphic_buffer != nullptr) { | ||||
|             if (found == BufferQueueCore::INVALID_BUFFER_SLOT || | ||||
|                 slots[s].frame_number < slots[found].frame_number) { | ||||
|                 found = s; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { | ||||
|         return Status::NoMemory; | ||||
|     } | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "Detached slot {}", found); | ||||
|  | ||||
|     *out_buffer = slots[found].graphic_buffer; | ||||
|     *out_fence = slots[found].fence; | ||||
|  | ||||
|     core->FreeBufferLocked(found); | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::AttachBuffer(s32* out_slot, | ||||
|                                          const std::shared_ptr<GraphicBuffer>& buffer) { | ||||
|     if (out_slot == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "out_slot must not be nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } else if (buffer == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "Cannot attach nullptr buffer"); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::unique_lock lock{core->mutex}; | ||||
|     core->WaitWhileAllocatingLocked(); | ||||
|  | ||||
|     Status return_flags = Status::NoError; | ||||
|     s32 found{}; | ||||
|  | ||||
|     const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags, lock); | ||||
|     if (status != Status::NoError) { | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     // This should not happen | ||||
|     if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { | ||||
|         LOG_ERROR(Service_NVFlinger, "No available buffer slots"); | ||||
|         return Status::Busy; | ||||
|     } | ||||
|  | ||||
|     *out_slot = found; | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "Returning slot {} flags={}", *out_slot, return_flags); | ||||
|  | ||||
|     slots[*out_slot].graphic_buffer = buffer; | ||||
|     slots[*out_slot].buffer_state = BufferState::Dequeued; | ||||
|     slots[*out_slot].fence = Fence::NoFence(); | ||||
|     slots[*out_slot].request_buffer_called = true; | ||||
|  | ||||
|     return return_flags; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, | ||||
|                                         QueueBufferOutput* output) { | ||||
|     s64 timestamp{}; | ||||
|     bool is_auto_timestamp{}; | ||||
|     Common::Rectangle<s32> crop; | ||||
|     NativeWindowScalingMode scaling_mode{}; | ||||
|     NativeWindowTransform transform; | ||||
|     u32 sticky_transform_{}; | ||||
|     bool async{}; | ||||
|     s32 swap_interval{}; | ||||
|     Fence fence{}; | ||||
|  | ||||
|     input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform, | ||||
|                   &sticky_transform_, &async, &swap_interval, &fence); | ||||
|  | ||||
|     switch (scaling_mode) { | ||||
|     case NativeWindowScalingMode::Freeze: | ||||
|     case NativeWindowScalingMode::ScaleToWindow: | ||||
|     case NativeWindowScalingMode::ScaleCrop: | ||||
|     case NativeWindowScalingMode::NoScaleCrop: | ||||
|         break; | ||||
|     default: | ||||
|         LOG_ERROR(Service_NVFlinger, "unknown scaling mode {}", scaling_mode); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<IConsumerListener> frame_available_listener; | ||||
|     std::shared_ptr<IConsumerListener> frame_replaced_listener; | ||||
|     s32 callback_ticket{}; | ||||
|     BufferItem item; | ||||
|  | ||||
|     { | ||||
|         std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|         if (core->is_abandoned) { | ||||
|             LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|             return Status::NoInit; | ||||
|         } | ||||
|  | ||||
|         const s32 max_buffer_count = core->GetMaxBufferCountLocked(async); | ||||
|         if (async && core->override_max_buffer_count) { | ||||
|             if (core->override_max_buffer_count < max_buffer_count) { | ||||
|                 LOG_ERROR(Service_NVFlinger, "async mode is invalid with " | ||||
|                                              "buffer count override"); | ||||
|                 return Status::BadValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (slot < 0 || slot >= max_buffer_count) { | ||||
|             LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot, | ||||
|                       max_buffer_count); | ||||
|             return Status::BadValue; | ||||
|         } else if (slots[slot].buffer_state != BufferState::Dequeued) { | ||||
|             LOG_ERROR(Service_NVFlinger, | ||||
|                       "slot {} is not owned by the producer " | ||||
|                       "(state = {})", | ||||
|                       slot, slots[slot].buffer_state); | ||||
|             return Status::BadValue; | ||||
|         } else if (!slots[slot].request_buffer_called) { | ||||
|             LOG_ERROR(Service_NVFlinger, | ||||
|                       "slot {} was queued without requesting " | ||||
|                       "a buffer", | ||||
|                       slot); | ||||
|             return Status::BadValue; | ||||
|         } | ||||
|  | ||||
|         LOG_DEBUG(Service_NVFlinger, | ||||
|                   "slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot, | ||||
|                   core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(), | ||||
|                   crop.Bottom(), transform, scaling_mode); | ||||
|  | ||||
|         const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer); | ||||
|         Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height()); | ||||
|         Common::Rectangle<s32> cropped_rect; | ||||
|         [[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect); | ||||
|  | ||||
|         if (cropped_rect != crop) { | ||||
|             LOG_ERROR(Service_NVFlinger, "crop rect is not contained within the buffer in slot {}", | ||||
|                       slot); | ||||
|             return Status::BadValue; | ||||
|         } | ||||
|  | ||||
|         slots[slot].fence = fence; | ||||
|         slots[slot].buffer_state = BufferState::Queued; | ||||
|         ++core->frame_counter; | ||||
|         slots[slot].frame_number = core->frame_counter; | ||||
|  | ||||
|         item.acquire_called = slots[slot].acquire_called; | ||||
|         item.graphic_buffer = slots[slot].graphic_buffer; | ||||
|         item.crop = crop; | ||||
|         item.transform = transform & ~NativeWindowTransform::InverseDisplay; | ||||
|         item.transform_to_display_inverse = | ||||
|             (transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None; | ||||
|         item.scaling_mode = static_cast<u32>(scaling_mode); | ||||
|         item.timestamp = timestamp; | ||||
|         item.is_auto_timestamp = is_auto_timestamp; | ||||
|         item.frame_number = core->frame_counter; | ||||
|         item.slot = slot; | ||||
|         item.fence = fence; | ||||
|         item.is_droppable = core->dequeue_buffer_cannot_block || async; | ||||
|         item.swap_interval = swap_interval; | ||||
|  | ||||
|         nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true); | ||||
|  | ||||
|         sticky_transform = sticky_transform_; | ||||
|  | ||||
|         if (core->queue.empty()) { | ||||
|             // When the queue is empty, we can simply queue this buffer | ||||
|             core->queue.push_back(item); | ||||
|             frame_available_listener = core->consumer_listener; | ||||
|         } else { | ||||
|             // When the queue is not empty, we need to look at the front buffer | ||||
|             // state to see if we need to replace it | ||||
|             auto front(core->queue.begin()); | ||||
|  | ||||
|             if (front->is_droppable) { | ||||
|                 // If the front queued buffer is still being tracked, we first | ||||
|                 // mark it as freed | ||||
|                 if (core->StillTracking(*front)) { | ||||
|                     slots[front->slot].buffer_state = BufferState::Free; | ||||
|                     // Reset the frame number of the freed buffer so that it is the first in line to | ||||
|                     // be dequeued again | ||||
|                     slots[front->slot].frame_number = 0; | ||||
|                 } | ||||
|                 // Overwrite the droppable buffer with the incoming one | ||||
|                 *front = item; | ||||
|                 frame_replaced_listener = core->consumer_listener; | ||||
|             } else { | ||||
|                 core->queue.push_back(item); | ||||
|                 frame_available_listener = core->consumer_listener; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         core->buffer_has_been_queued = true; | ||||
|         core->SignalDequeueCondition(); | ||||
|         output->Inflate(core->default_width, core->default_height, core->transform_hint, | ||||
|                         static_cast<u32>(core->queue.size())); | ||||
|  | ||||
|         // Take a ticket for the callback functions | ||||
|         callback_ticket = next_callback_ticket++; | ||||
|     } | ||||
|  | ||||
|     // Don't send the GraphicBuffer through the callback, and don't send the slot number, since the | ||||
|     // consumer shouldn't need it | ||||
|     item.graphic_buffer.reset(); | ||||
|     item.slot = BufferItem::INVALID_BUFFER_SLOT; | ||||
|  | ||||
|     // Call back without the main BufferQueue lock held, but with the callback lock held so we can | ||||
|     // ensure that callbacks occur in order | ||||
|     { | ||||
|         std::scoped_lock lock{callback_mutex}; | ||||
|         while (callback_ticket != current_callback_ticket) { | ||||
|             callback_condition.wait(callback_mutex); | ||||
|         } | ||||
|  | ||||
|         if (frame_available_listener != nullptr) { | ||||
|             frame_available_listener->OnFrameAvailable(item); | ||||
|         } else if (frame_replaced_listener != nullptr) { | ||||
|             frame_replaced_listener->OnFrameReplaced(item); | ||||
|         } | ||||
|  | ||||
|         ++current_callback_ticket; | ||||
|         callback_condition.notify_all(); | ||||
|     } | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot {}", slot); | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot, | ||||
|                   BufferQueueDefs::NUM_BUFFER_SLOTS); | ||||
|         return; | ||||
|     } else if (slots[slot].buffer_state != BufferState::Dequeued) { | ||||
|         LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot, | ||||
|                   slots[slot].buffer_state); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     slots[slot].buffer_state = BufferState::Free; | ||||
|     slots[slot].frame_number = 0; | ||||
|     slots[slot].fence = fence; | ||||
|  | ||||
|     core->SignalDequeueCondition(); | ||||
|     buffer_wait_event->Signal(); | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) { | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     if (out_value == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "outValue was nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     u32 value{}; | ||||
|     switch (what) { | ||||
|     case NativeWindow::Width: | ||||
|         value = core->default_width; | ||||
|         break; | ||||
|     case NativeWindow::Height: | ||||
|         value = core->default_height; | ||||
|         break; | ||||
|     case NativeWindow::Format: | ||||
|         value = static_cast<u32>(core->default_buffer_format); | ||||
|         break; | ||||
|     case NativeWindow::MinUndequeedBuffers: | ||||
|         value = core->GetMinUndequeuedBufferCountLocked(false); | ||||
|         break; | ||||
|     case NativeWindow::StickyTransform: | ||||
|         value = sticky_transform; | ||||
|         break; | ||||
|     case NativeWindow::ConsumerRunningBehind: | ||||
|         value = (core->queue.size() > 1); | ||||
|         break; | ||||
|     case NativeWindow::ConsumerUsageBits: | ||||
|         value = core->consumer_usage_bit; | ||||
|         break; | ||||
|     default: | ||||
|         ASSERT(false); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "what = {}, value = {}", what, value); | ||||
|  | ||||
|     *out_value = static_cast<s32>(value); | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::Connect(const std::shared_ptr<IProducerListener>& listener, | ||||
|                                     NativeWindowApi api, bool producer_controlled_by_app, | ||||
|                                     QueueBufferOutput* output) { | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "api = {} producer_controlled_by_app = {}", api, | ||||
|               producer_controlled_by_app); | ||||
|  | ||||
|     if (core->is_abandoned) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     if (core->consumer_listener == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "BufferQueue has no consumer"); | ||||
|         return Status::NoInit; | ||||
|     } | ||||
|  | ||||
|     if (output == nullptr) { | ||||
|         LOG_ERROR(Service_NVFlinger, "output was nullptr"); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     if (core->connected_api != NativeWindowApi::NoConnectedApi) { | ||||
|         LOG_ERROR(Service_NVFlinger, "already connected (cur = {} req = {})", core->connected_api, | ||||
|                   api); | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     Status status = Status::NoError; | ||||
|     switch (api) { | ||||
|     case NativeWindowApi::Egl: | ||||
|     case NativeWindowApi::Cpu: | ||||
|     case NativeWindowApi::Media: | ||||
|     case NativeWindowApi::Camera: | ||||
|         core->connected_api = api; | ||||
|         output->Inflate(core->default_width, core->default_height, core->transform_hint, | ||||
|                         static_cast<u32>(core->queue.size())); | ||||
|         core->connected_producer_listener = listener; | ||||
|         break; | ||||
|     default: | ||||
|         LOG_ERROR(Service_NVFlinger, "unknown api = {}", api); | ||||
|         status = Status::BadValue; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     core->buffer_has_been_queued = false; | ||||
|     core->dequeue_buffer_cannot_block = | ||||
|         core->consumer_controlled_by_app && producer_controlled_by_app; | ||||
|  | ||||
|     return status; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::Disconnect(NativeWindowApi api) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "api = {}", api); | ||||
|  | ||||
|     Status status = Status::NoError; | ||||
|     std::shared_ptr<IConsumerListener> listener; | ||||
|  | ||||
|     { | ||||
|         std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|         core->WaitWhileAllocatingLocked(); | ||||
|  | ||||
|         if (core->is_abandoned) { | ||||
|             // Disconnecting after the surface has been abandoned is a no-op. | ||||
|             return Status::NoError; | ||||
|         } | ||||
|  | ||||
|         // HACK: We are not Android. Remove handle for items in queue, and clear queue. | ||||
|         // Allows synchronous destruction of nvmap handles. | ||||
|         for (auto& item : core->queue) { | ||||
|             nvmap.FreeHandle(item.graphic_buffer->BufferId(), true); | ||||
|         } | ||||
|         core->queue.clear(); | ||||
|  | ||||
|         switch (api) { | ||||
|         case NativeWindowApi::Egl: | ||||
|         case NativeWindowApi::Cpu: | ||||
|         case NativeWindowApi::Media: | ||||
|         case NativeWindowApi::Camera: | ||||
|             if (core->connected_api == api) { | ||||
|                 core->FreeAllBuffersLocked(); | ||||
|                 core->connected_producer_listener = nullptr; | ||||
|                 core->connected_api = NativeWindowApi::NoConnectedApi; | ||||
|                 core->SignalDequeueCondition(); | ||||
|                 buffer_wait_event->Signal(); | ||||
|                 listener = core->consumer_listener; | ||||
|             } else { | ||||
|                 LOG_ERROR(Service_NVFlinger, "still connected to another api (cur = {} req = {})", | ||||
|                           core->connected_api, api); | ||||
|                 status = Status::BadValue; | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Service_NVFlinger, "unknown api = {}", api); | ||||
|             status = Status::BadValue; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Call back without lock held | ||||
|     if (listener != nullptr) { | ||||
|         listener->OnBuffersReleased(); | ||||
|     } | ||||
|  | ||||
|     return status; | ||||
| } | ||||
|  | ||||
| Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot, | ||||
|                                                   const std::shared_ptr<GraphicBuffer>& buffer) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot {}", slot); | ||||
|  | ||||
|     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|         return Status::BadValue; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock lock{core->mutex}; | ||||
|  | ||||
|     slots[slot] = {}; | ||||
|     slots[slot].graphic_buffer = buffer; | ||||
|     slots[slot].frame_number = 0; | ||||
|  | ||||
|     // Most games preallocate a buffer and pass a valid buffer here. However, it is possible for | ||||
|     // this to be called with an empty buffer, Naruto Ultimate Ninja Storm is a game that does this. | ||||
|     if (buffer) { | ||||
|         slots[slot].is_preallocated = true; | ||||
|  | ||||
|         core->override_max_buffer_count = core->GetPreallocatedBufferCountLocked(); | ||||
|         core->default_width = buffer->Width(); | ||||
|         core->default_height = buffer->Height(); | ||||
|         core->default_buffer_format = buffer->Format(); | ||||
|     } | ||||
|  | ||||
|     core->SignalDequeueCondition(); | ||||
|     buffer_wait_event->Signal(); | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) { | ||||
|     Status status{Status::NoError}; | ||||
|     InputParcel parcel_in{ctx.ReadBuffer()}; | ||||
|     OutputParcel parcel_out{}; | ||||
|  | ||||
|     switch (code) { | ||||
|     case TransactionId::Connect: { | ||||
|         const auto enable_listener = parcel_in.Read<bool>(); | ||||
|         const auto api = parcel_in.Read<NativeWindowApi>(); | ||||
|         const auto producer_controlled_by_app = parcel_in.Read<bool>(); | ||||
|  | ||||
|         UNIMPLEMENTED_IF_MSG(enable_listener, "Listener is unimplemented!"); | ||||
|  | ||||
|         std::shared_ptr<IProducerListener> listener; | ||||
|         QueueBufferOutput output{}; | ||||
|  | ||||
|         status = Connect(listener, api, producer_controlled_by_app, &output); | ||||
|  | ||||
|         parcel_out.Write(output); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::SetPreallocatedBuffer: { | ||||
|         const auto slot = parcel_in.Read<s32>(); | ||||
|         const auto buffer = parcel_in.ReadObject<GraphicBuffer>(); | ||||
|  | ||||
|         status = SetPreallocatedBuffer(slot, buffer); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::DequeueBuffer: { | ||||
|         const auto is_async = parcel_in.Read<bool>(); | ||||
|         const auto width = parcel_in.Read<u32>(); | ||||
|         const auto height = parcel_in.Read<u32>(); | ||||
|         const auto pixel_format = parcel_in.Read<PixelFormat>(); | ||||
|         const auto usage = parcel_in.Read<u32>(); | ||||
|  | ||||
|         s32 slot{}; | ||||
|         Fence fence{}; | ||||
|  | ||||
|         status = DequeueBuffer(&slot, &fence, is_async, width, height, pixel_format, usage); | ||||
|  | ||||
|         parcel_out.Write(slot); | ||||
|         parcel_out.WriteObject(&fence); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::RequestBuffer: { | ||||
|         const auto slot = parcel_in.Read<s32>(); | ||||
|  | ||||
|         std::shared_ptr<GraphicBuffer> buf; | ||||
|  | ||||
|         status = RequestBuffer(slot, &buf); | ||||
|  | ||||
|         parcel_out.WriteObject(buf); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::QueueBuffer: { | ||||
|         const auto slot = parcel_in.Read<s32>(); | ||||
|  | ||||
|         QueueBufferInput input{parcel_in}; | ||||
|         QueueBufferOutput output; | ||||
|  | ||||
|         status = QueueBuffer(slot, input, &output); | ||||
|  | ||||
|         parcel_out.Write(output); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::Query: { | ||||
|         const auto what = parcel_in.Read<NativeWindow>(); | ||||
|  | ||||
|         s32 value{}; | ||||
|  | ||||
|         status = Query(what, &value); | ||||
|  | ||||
|         parcel_out.Write(value); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::CancelBuffer: { | ||||
|         const auto slot = parcel_in.Read<s32>(); | ||||
|         const auto fence = parcel_in.ReadFlattened<Fence>(); | ||||
|  | ||||
|         CancelBuffer(slot, fence); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::Disconnect: { | ||||
|         const auto api = parcel_in.Read<NativeWindowApi>(); | ||||
|  | ||||
|         status = Disconnect(api); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::DetachBuffer: { | ||||
|         const auto slot = parcel_in.Read<s32>(); | ||||
|  | ||||
|         status = DetachBuffer(slot); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::SetBufferCount: { | ||||
|         const auto buffer_count = parcel_in.Read<s32>(); | ||||
|  | ||||
|         status = SetBufferCount(buffer_count); | ||||
|         break; | ||||
|     } | ||||
|     case TransactionId::GetBufferHistory: | ||||
|         LOG_WARNING(Service_NVFlinger, "(STUBBED) called, transaction=GetBufferHistory"); | ||||
|         break; | ||||
|     default: | ||||
|         ASSERT_MSG(false, "Unimplemented TransactionId {}", code); | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     parcel_out.Write(status); | ||||
|  | ||||
|     ctx.WriteBuffer(parcel_out.Serialize()); | ||||
| } | ||||
|  | ||||
| Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() { | ||||
|     return buffer_wait_event->GetReadableEvent(); | ||||
| } | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,90 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <condition_variable> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "core/hle/service/nvdrv/nvdata.h" | ||||
| #include "core/hle/service/nvflinger/binder.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_defs.h" | ||||
| #include "core/hle/service/nvflinger/buffer_slot.h" | ||||
| #include "core/hle/service/nvflinger/graphic_buffer_producer.h" | ||||
| #include "core/hle/service/nvflinger/pixel_format.h" | ||||
| #include "core/hle/service/nvflinger/status.h" | ||||
| #include "core/hle/service/nvflinger/window.h" | ||||
|  | ||||
| namespace Kernel { | ||||
| class KernelCore; | ||||
| class KEvent; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace Service::KernelHelpers { | ||||
| class ServiceContext; | ||||
| } // namespace Service::KernelHelpers | ||||
|  | ||||
| namespace Service::Nvidia::NvCore { | ||||
| class NvMap; | ||||
| } // namespace Service::Nvidia::NvCore | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class BufferQueueCore; | ||||
| class IProducerListener; | ||||
|  | ||||
| class BufferQueueProducer final : public IBinder { | ||||
| public: | ||||
|     explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, | ||||
|                                  std::shared_ptr<BufferQueueCore> buffer_queue_core_, | ||||
|                                  Service::Nvidia::NvCore::NvMap& nvmap_); | ||||
|     ~BufferQueueProducer(); | ||||
|  | ||||
|     void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override; | ||||
|  | ||||
|     Kernel::KReadableEvent& GetNativeHandle() override; | ||||
|  | ||||
| public: | ||||
|     Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf); | ||||
|     Status SetBufferCount(s32 buffer_count); | ||||
|     Status DequeueBuffer(s32* out_slot, android::Fence* out_fence, bool async, u32 width, | ||||
|                          u32 height, PixelFormat format, u32 usage); | ||||
|     Status DetachBuffer(s32 slot); | ||||
|     Status DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, Fence* out_fence); | ||||
|     Status AttachBuffer(s32* outSlot, const std::shared_ptr<GraphicBuffer>& buffer); | ||||
|     Status QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output); | ||||
|     void CancelBuffer(s32 slot, const Fence& fence); | ||||
|     Status Query(NativeWindow what, s32* out_value); | ||||
|     Status Connect(const std::shared_ptr<IProducerListener>& listener, NativeWindowApi api, | ||||
|                    bool producer_controlled_by_app, QueueBufferOutput* output); | ||||
|  | ||||
|     Status Disconnect(NativeWindowApi api); | ||||
|     Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<GraphicBuffer>& buffer); | ||||
|  | ||||
| private: | ||||
|     BufferQueueProducer(const BufferQueueProducer&) = delete; | ||||
|  | ||||
|     Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags, | ||||
|                                      std::unique_lock<std::mutex>& lk) const; | ||||
|  | ||||
|     Kernel::KEvent* buffer_wait_event{}; | ||||
|     Service::KernelHelpers::ServiceContext& service_context; | ||||
|  | ||||
|     std::shared_ptr<BufferQueueCore> core; | ||||
|     BufferQueueDefs::SlotsType& slots; | ||||
|     u32 sticky_transform{}; | ||||
|     std::mutex callback_mutex; | ||||
|     s32 next_callback_ticket{}; | ||||
|     s32 current_callback_ticket{}; | ||||
|     std::condition_variable_any callback_condition; | ||||
|  | ||||
|     Service::Nvidia::NvCore::NvMap& nvmap; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,38 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/ui/fence.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class GraphicBuffer; | ||||
|  | ||||
| enum class BufferState : u32 { | ||||
|     Free = 0, | ||||
|     Dequeued = 1, | ||||
|     Queued = 2, | ||||
|     Acquired = 3, | ||||
| }; | ||||
|  | ||||
| struct BufferSlot final { | ||||
|     constexpr BufferSlot() = default; | ||||
|  | ||||
|     std::shared_ptr<GraphicBuffer> graphic_buffer; | ||||
|     BufferState buffer_state{BufferState::Free}; | ||||
|     bool request_buffer_called{}; | ||||
|     u64 frame_number{}; | ||||
|     Fence fence; | ||||
|     bool acquire_called{}; | ||||
|     bool attached_by_consumer{}; | ||||
|     bool is_preallocated{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,25 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| enum class BufferTransformFlags : u32 { | ||||
|     /// No transform flags are set | ||||
|     Unset = 0x00, | ||||
|     /// Flip source image horizontally (around the vertical axis) | ||||
|     FlipH = 0x01, | ||||
|     /// Flip source image vertically (around the horizontal axis) | ||||
|     FlipV = 0x02, | ||||
|     /// Rotate source image 90 degrees clockwise | ||||
|     Rotate90 = 0x04, | ||||
|     /// Rotate source image 180 degrees | ||||
|     Rotate180 = 0x03, | ||||
|     /// Rotate source image 270 degrees clockwise | ||||
|     Rotate270 = 0x07, | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,133 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/ConsumerBase.cpp | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/nvflinger/buffer_item.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_consumer.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_core.h" | ||||
| #include "core/hle/service/nvflinger/consumer_base.h" | ||||
| #include "core/hle/service/nvflinger/ui/graphic_buffer.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_) | ||||
|     : consumer{std::move(consumer_)} {} | ||||
|  | ||||
| ConsumerBase::~ConsumerBase() { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|  | ||||
|     ASSERT_MSG(is_abandoned, "consumer is not abandoned!"); | ||||
| } | ||||
|  | ||||
| void ConsumerBase::Connect(bool controlled_by_app) { | ||||
|     consumer->Connect(shared_from_this(), controlled_by_app); | ||||
| } | ||||
|  | ||||
| void ConsumerBase::FreeBufferLocked(s32 slot_index) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot_index={}", slot_index); | ||||
|  | ||||
|     slots[slot_index].graphic_buffer = nullptr; | ||||
|     slots[slot_index].fence = Fence::NoFence(); | ||||
|     slots[slot_index].frame_number = 0; | ||||
| } | ||||
|  | ||||
| void ConsumerBase::OnFrameAvailable(const BufferItem& item) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "called"); | ||||
| } | ||||
|  | ||||
| void ConsumerBase::OnFrameReplaced(const BufferItem& item) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "called"); | ||||
| } | ||||
|  | ||||
| void ConsumerBase::OnBuffersReleased() { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "called"); | ||||
|  | ||||
|     if (is_abandoned) { | ||||
|         // Nothing to do if we're already abandoned. | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     u64 mask = 0; | ||||
|     consumer->GetReleasedBuffers(&mask); | ||||
|     for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) { | ||||
|         if (mask & (1ULL << i)) { | ||||
|             FreeBufferLocked(i); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ConsumerBase::OnSidebandStreamChanged() {} | ||||
|  | ||||
| Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when) { | ||||
|     Status err = consumer->AcquireBuffer(item, present_when); | ||||
|     if (err != Status::NoError) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     if (item->graphic_buffer != nullptr) { | ||||
|         slots[item->slot].graphic_buffer = item->graphic_buffer; | ||||
|     } | ||||
|  | ||||
|     slots[item->slot].frame_number = item->frame_number; | ||||
|     slots[item->slot].fence = item->fence; | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot={}", item->slot); | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status ConsumerBase::AddReleaseFenceLocked(s32 slot, | ||||
|                                            const std::shared_ptr<GraphicBuffer>& graphic_buffer, | ||||
|                                            const Fence& fence) { | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot={}", slot); | ||||
|  | ||||
|     // If consumer no longer tracks this graphic_buffer, we can safely | ||||
|     // drop this fence, as it will never be received by the producer. | ||||
|  | ||||
|     if (!StillTracking(slot, graphic_buffer)) { | ||||
|         return Status::NoError; | ||||
|     } | ||||
|  | ||||
|     slots[slot].fence = fence; | ||||
|  | ||||
|     return Status::NoError; | ||||
| } | ||||
|  | ||||
| Status ConsumerBase::ReleaseBufferLocked(s32 slot, | ||||
|                                          const std::shared_ptr<GraphicBuffer>& graphic_buffer) { | ||||
|     // If consumer no longer tracks this graphic_buffer (we received a new | ||||
|     // buffer on the same slot), the buffer producer is definitely no longer | ||||
|     // tracking it. | ||||
|  | ||||
|     if (!StillTracking(slot, graphic_buffer)) { | ||||
|         return Status::NoError; | ||||
|     } | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "slot={}", slot); | ||||
|     Status err = consumer->ReleaseBuffer(slot, slots[slot].frame_number, slots[slot].fence); | ||||
|     if (err == Status::StaleBufferSlot) { | ||||
|         FreeBufferLocked(slot); | ||||
|     } | ||||
|  | ||||
|     slots[slot].fence = Fence::NoFence(); | ||||
|  | ||||
|     return err; | ||||
| } | ||||
|  | ||||
| bool ConsumerBase::StillTracking(s32 slot, | ||||
|                                  const std::shared_ptr<GraphicBuffer>& graphic_buffer) const { | ||||
|     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return (slots[slot].graphic_buffer != nullptr && | ||||
|             slots[slot].graphic_buffer->Handle() == graphic_buffer->Handle()); | ||||
| } | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,60 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/ConsumerBase.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_defs.h" | ||||
| #include "core/hle/service/nvflinger/consumer_listener.h" | ||||
| #include "core/hle/service/nvflinger/status.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class BufferItem; | ||||
| class BufferQueueConsumer; | ||||
|  | ||||
| class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> { | ||||
| public: | ||||
|     void Connect(bool controlled_by_app); | ||||
|  | ||||
| protected: | ||||
|     explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_); | ||||
|     ~ConsumerBase() override; | ||||
|  | ||||
|     void OnFrameAvailable(const BufferItem& item) override; | ||||
|     void OnFrameReplaced(const BufferItem& item) override; | ||||
|     void OnBuffersReleased() override; | ||||
|     void OnSidebandStreamChanged() override; | ||||
|  | ||||
|     void FreeBufferLocked(s32 slot_index); | ||||
|     Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when); | ||||
|     Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer); | ||||
|     bool StillTracking(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer) const; | ||||
|     Status AddReleaseFenceLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer, | ||||
|                                  const Fence& fence); | ||||
|  | ||||
|     struct Slot final { | ||||
|         std::shared_ptr<GraphicBuffer> graphic_buffer; | ||||
|         Fence fence; | ||||
|         u64 frame_number{}; | ||||
|     }; | ||||
|  | ||||
| protected: | ||||
|     std::array<Slot, BufferQueueDefs::NUM_BUFFER_SLOTS> slots; | ||||
|  | ||||
|     bool is_abandoned{}; | ||||
|  | ||||
|     std::unique_ptr<BufferQueueConsumer> consumer; | ||||
|  | ||||
|     mutable std::mutex mutex; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,26 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IConsumerListener.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class BufferItem; | ||||
|  | ||||
| /// ConsumerListener is the interface through which the BufferQueue notifies the consumer of events | ||||
| /// that the consumer may wish to react to. | ||||
| class IConsumerListener { | ||||
| public: | ||||
|     IConsumerListener() = default; | ||||
|     virtual ~IConsumerListener() = default; | ||||
|  | ||||
|     virtual void OnFrameAvailable(const BufferItem& item) = 0; | ||||
|     virtual void OnFrameReplaced(const BufferItem& item) = 0; | ||||
|     virtual void OnBuffersReleased() = 0; | ||||
|     virtual void OnSidebandStreamChanged() = 0; | ||||
| }; | ||||
|  | ||||
| }; // namespace Service::android | ||||
| @@ -1,18 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp | ||||
|  | ||||
| #include "core/hle/service/nvflinger/graphic_buffer_producer.h" | ||||
| #include "core/hle/service/nvflinger/parcel.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| QueueBufferInput::QueueBufferInput(InputParcel& parcel) { | ||||
|     parcel.ReadFlattened(*this); | ||||
| } | ||||
|  | ||||
| QueueBufferOutput::QueueBufferOutput() = default; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,76 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/math_util.h" | ||||
| #include "core/hle/service/nvflinger/ui/fence.h" | ||||
| #include "core/hle/service/nvflinger/window.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class InputParcel; | ||||
|  | ||||
| #pragma pack(push, 1) | ||||
| struct QueueBufferInput final { | ||||
|     explicit QueueBufferInput(InputParcel& parcel); | ||||
|  | ||||
|     void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, | ||||
|                  NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, | ||||
|                  u32* sticky_transform_, bool* async_, s32* swap_interval_, Fence* fence_) const { | ||||
|         *timestamp_ = timestamp; | ||||
|         *is_auto_timestamp_ = static_cast<bool>(is_auto_timestamp); | ||||
|         *crop_ = crop; | ||||
|         *scaling_mode_ = scaling_mode; | ||||
|         *transform_ = transform; | ||||
|         *sticky_transform_ = sticky_transform; | ||||
|         *async_ = static_cast<bool>(async); | ||||
|         *swap_interval_ = swap_interval; | ||||
|         *fence_ = fence; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     s64 timestamp{}; | ||||
|     s32 is_auto_timestamp{}; | ||||
|     Common::Rectangle<s32> crop{}; | ||||
|     NativeWindowScalingMode scaling_mode{}; | ||||
|     NativeWindowTransform transform{}; | ||||
|     u32 sticky_transform{}; | ||||
|     s32 async{}; | ||||
|     s32 swap_interval{}; | ||||
|     Fence fence{}; | ||||
| }; | ||||
| #pragma pack(pop) | ||||
| static_assert(sizeof(QueueBufferInput) == 84, "QueueBufferInput has wrong size"); | ||||
|  | ||||
| struct QueueBufferOutput final { | ||||
|     QueueBufferOutput(); | ||||
|  | ||||
|     void Deflate(u32* width_, u32* height_, u32* transform_hint_, u32* num_pending_buffers_) const { | ||||
|         *width_ = width; | ||||
|         *height_ = height; | ||||
|         *transform_hint_ = transform_hint; | ||||
|         *num_pending_buffers_ = num_pending_buffers; | ||||
|     } | ||||
|  | ||||
|     void Inflate(u32 width_, u32 height_, u32 transform_hint_, u32 num_pending_buffers_) { | ||||
|         width = width_; | ||||
|         height = height_; | ||||
|         transform_hint = transform_hint_; | ||||
|         num_pending_buffers = num_pending_buffers_; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     u32 width{}; | ||||
|     u32 height{}; | ||||
|     u32 transform_hint{}; | ||||
|     u32 num_pending_buffers{}; | ||||
| }; | ||||
| static_assert(sizeof(QueueBufferOutput) == 16, "QueueBufferOutput has wrong size"); | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,36 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #include <mutex> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/hos_binder_driver_server.h" | ||||
|  | ||||
| namespace Service::NVFlinger { | ||||
|  | ||||
| HosBinderDriverServer::HosBinderDriverServer(Core::System& system_) | ||||
|     : service_context(system_, "HosBinderDriverServer") {} | ||||
|  | ||||
| HosBinderDriverServer::~HosBinderDriverServer() {} | ||||
|  | ||||
| u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) { | ||||
|     std::scoped_lock lk{lock}; | ||||
|  | ||||
|     last_id++; | ||||
|  | ||||
|     producers[last_id] = std::move(binder); | ||||
|  | ||||
|     return last_id; | ||||
| } | ||||
|  | ||||
| android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) { | ||||
|     std::scoped_lock lk{lock}; | ||||
|  | ||||
|     if (auto search = producers.find(id); search != producers.end()) { | ||||
|         return search->second.get(); | ||||
|     } | ||||
|  | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| } // namespace Service::NVFlinger | ||||
| @@ -1,37 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <unordered_map> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
| #include "core/hle/service/nvflinger/binder.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::NVFlinger { | ||||
|  | ||||
| class HosBinderDriverServer final { | ||||
| public: | ||||
|     explicit HosBinderDriverServer(Core::System& system_); | ||||
|     ~HosBinderDriverServer(); | ||||
|  | ||||
|     u64 RegisterProducer(std::unique_ptr<android::IBinder>&& binder); | ||||
|  | ||||
|     android::IBinder* TryGetProducer(u64 id); | ||||
|  | ||||
| private: | ||||
|     KernelHelpers::ServiceContext service_context; | ||||
|  | ||||
|     std::unordered_map<u64, std::unique_ptr<android::IBinder>> producers; | ||||
|     std::mutex lock; | ||||
|     u64 last_id{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NVFlinger | ||||
| @@ -1,335 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <optional> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/scope_exit.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/thread.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/k_readable_event.h" | ||||
| #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" | ||||
| #include "core/hle/service/nvdrv/nvdrv.h" | ||||
| #include "core/hle/service/nvflinger/buffer_item_consumer.h" | ||||
| #include "core/hle/service/nvflinger/buffer_queue_core.h" | ||||
| #include "core/hle/service/nvflinger/hos_binder_driver_server.h" | ||||
| #include "core/hle/service/nvflinger/nvflinger.h" | ||||
| #include "core/hle/service/nvflinger/ui/graphic_buffer.h" | ||||
| #include "core/hle/service/vi/display/vi_display.h" | ||||
| #include "core/hle/service/vi/layer/vi_layer.h" | ||||
| #include "core/hle/service/vi/vi_results.h" | ||||
| #include "video_core/gpu.h" | ||||
| #include "video_core/host1x/host1x.h" | ||||
| #include "video_core/host1x/syncpoint_manager.h" | ||||
|  | ||||
| namespace Service::NVFlinger { | ||||
|  | ||||
| constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60}; | ||||
|  | ||||
| void NVFlinger::SplitVSync(std::stop_token stop_token) { | ||||
|     system.RegisterHostThread(); | ||||
|     std::string name = "VSyncThread"; | ||||
|     MicroProfileOnThreadCreate(name.c_str()); | ||||
|  | ||||
|     // Cleanup | ||||
|     SCOPE_EXIT({ MicroProfileOnThreadExit(); }); | ||||
|  | ||||
|     Common::SetCurrentThreadName(name.c_str()); | ||||
|     Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | ||||
|  | ||||
|     while (!stop_token.stop_requested()) { | ||||
|         vsync_signal.wait(false); | ||||
|         vsync_signal.store(false); | ||||
|  | ||||
|         guard->lock(); | ||||
|  | ||||
|         Compose(); | ||||
|  | ||||
|         guard->unlock(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_) | ||||
|     : system(system_), service_context(system_, "nvflinger"), | ||||
|       hos_binder_driver_server(hos_binder_driver_server_) { | ||||
|     displays.emplace_back(0, "Default", hos_binder_driver_server, service_context, system); | ||||
|     displays.emplace_back(1, "External", hos_binder_driver_server, service_context, system); | ||||
|     displays.emplace_back(2, "Edid", hos_binder_driver_server, service_context, system); | ||||
|     displays.emplace_back(3, "Internal", hos_binder_driver_server, service_context, system); | ||||
|     displays.emplace_back(4, "Null", hos_binder_driver_server, service_context, system); | ||||
|     guard = std::make_shared<std::mutex>(); | ||||
|  | ||||
|     // Schedule the screen composition events | ||||
|     multi_composition_event = Core::Timing::CreateEvent( | ||||
|         "ScreenComposition", | ||||
|         [this](std::uintptr_t, s64 time, | ||||
|                std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | ||||
|             vsync_signal.store(true); | ||||
|             vsync_signal.notify_all(); | ||||
|             return std::chrono::nanoseconds(GetNextTicks()); | ||||
|         }); | ||||
|  | ||||
|     single_composition_event = Core::Timing::CreateEvent( | ||||
|         "ScreenComposition", | ||||
|         [this](std::uintptr_t, s64 time, | ||||
|                std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | ||||
|             const auto lock_guard = Lock(); | ||||
|             Compose(); | ||||
|  | ||||
|             return std::chrono::nanoseconds(GetNextTicks()); | ||||
|         }); | ||||
|  | ||||
|     if (system.IsMulticore()) { | ||||
|         system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); | ||||
|         vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); | ||||
|     } else { | ||||
|         system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); | ||||
|     } | ||||
| } | ||||
|  | ||||
| NVFlinger::~NVFlinger() { | ||||
|     if (system.IsMulticore()) { | ||||
|         system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); | ||||
|         vsync_thread.request_stop(); | ||||
|         vsync_signal.store(true); | ||||
|         vsync_signal.notify_all(); | ||||
|     } else { | ||||
|         system.CoreTiming().UnscheduleEvent(single_composition_event, {}); | ||||
|     } | ||||
|  | ||||
|     ShutdownLayers(); | ||||
|  | ||||
|     if (nvdrv) { | ||||
|         nvdrv->Close(disp_fd); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void NVFlinger::ShutdownLayers() { | ||||
|     for (auto& display : displays) { | ||||
|         for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) { | ||||
|             display.GetLayer(layer).Core().NotifyShutdown(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) { | ||||
|     nvdrv = std::move(instance); | ||||
|     disp_fd = nvdrv->Open("/dev/nvdisp_disp0"); | ||||
| } | ||||
|  | ||||
| std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) { | ||||
|     const auto lock_guard = Lock(); | ||||
|  | ||||
|     LOG_DEBUG(Service_NVFlinger, "Opening \"{}\" display", name); | ||||
|  | ||||
|     const auto itr = | ||||
|         std::find_if(displays.begin(), displays.end(), | ||||
|                      [&](const VI::Display& display) { return display.GetName() == name; }); | ||||
|  | ||||
|     if (itr == displays.end()) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|  | ||||
|     return itr->GetID(); | ||||
| } | ||||
|  | ||||
| bool NVFlinger::CloseDisplay(u64 display_id) { | ||||
|     const auto lock_guard = Lock(); | ||||
|     auto* const display = FindDisplay(display_id); | ||||
|  | ||||
|     if (display == nullptr) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     display->Reset(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { | ||||
|     const auto lock_guard = Lock(); | ||||
|     auto* const display = FindDisplay(display_id); | ||||
|  | ||||
|     if (display == nullptr) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|  | ||||
|     const u64 layer_id = next_layer_id++; | ||||
|     CreateLayerAtId(*display, layer_id); | ||||
|     return layer_id; | ||||
| } | ||||
|  | ||||
| void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { | ||||
|     const auto buffer_id = next_buffer_queue_id++; | ||||
|     display.CreateLayer(layer_id, buffer_id, nvdrv->container); | ||||
| } | ||||
|  | ||||
| void NVFlinger::CloseLayer(u64 layer_id) { | ||||
|     const auto lock_guard = Lock(); | ||||
|  | ||||
|     for (auto& display : displays) { | ||||
|         display.CloseLayer(layer_id); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) { | ||||
|     const auto lock_guard = Lock(); | ||||
|     const auto* const layer = FindOrCreateLayer(display_id, layer_id); | ||||
|  | ||||
|     if (layer == nullptr) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|  | ||||
|     return layer->GetBinderId(); | ||||
| } | ||||
|  | ||||
| ResultVal<Kernel::KReadableEvent*> NVFlinger::FindVsyncEvent(u64 display_id) { | ||||
|     const auto lock_guard = Lock(); | ||||
|     auto* const display = FindDisplay(display_id); | ||||
|  | ||||
|     if (display == nullptr) { | ||||
|         return VI::ResultNotFound; | ||||
|     } | ||||
|  | ||||
|     return display->GetVSyncEvent(); | ||||
| } | ||||
|  | ||||
| VI::Display* NVFlinger::FindDisplay(u64 display_id) { | ||||
|     const auto itr = | ||||
|         std::find_if(displays.begin(), displays.end(), | ||||
|                      [&](const VI::Display& display) { return display.GetID() == display_id; }); | ||||
|  | ||||
|     if (itr == displays.end()) { | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     return &*itr; | ||||
| } | ||||
|  | ||||
| const VI::Display* NVFlinger::FindDisplay(u64 display_id) const { | ||||
|     const auto itr = | ||||
|         std::find_if(displays.begin(), displays.end(), | ||||
|                      [&](const VI::Display& display) { return display.GetID() == display_id; }); | ||||
|  | ||||
|     if (itr == displays.end()) { | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     return &*itr; | ||||
| } | ||||
|  | ||||
| VI::Layer* NVFlinger::FindLayer(u64 display_id, u64 layer_id) { | ||||
|     auto* const display = FindDisplay(display_id); | ||||
|  | ||||
|     if (display == nullptr) { | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     return display->FindLayer(layer_id); | ||||
| } | ||||
|  | ||||
| const VI::Layer* NVFlinger::FindLayer(u64 display_id, u64 layer_id) const { | ||||
|     const auto* const display = FindDisplay(display_id); | ||||
|  | ||||
|     if (display == nullptr) { | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     return display->FindLayer(layer_id); | ||||
| } | ||||
|  | ||||
| VI::Layer* NVFlinger::FindOrCreateLayer(u64 display_id, u64 layer_id) { | ||||
|     auto* const display = FindDisplay(display_id); | ||||
|  | ||||
|     if (display == nullptr) { | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     auto* layer = display->FindLayer(layer_id); | ||||
|  | ||||
|     if (layer == nullptr) { | ||||
|         LOG_DEBUG(Service_NVFlinger, "Layer at id {} not found. Trying to create it.", layer_id); | ||||
|         CreateLayerAtId(*display, layer_id); | ||||
|         return display->FindLayer(layer_id); | ||||
|     } | ||||
|  | ||||
|     return layer; | ||||
| } | ||||
|  | ||||
| void NVFlinger::Compose() { | ||||
|     for (auto& display : displays) { | ||||
|         // Trigger vsync for this display at the end of drawing | ||||
|         SCOPE_EXIT({ display.SignalVSyncEvent(); }); | ||||
|  | ||||
|         // Don't do anything for displays without layers. | ||||
|         if (!display.HasLayers()) | ||||
|             continue; | ||||
|  | ||||
|         // TODO(Subv): Support more than 1 layer. | ||||
|         VI::Layer& layer = display.GetLayer(0); | ||||
|  | ||||
|         android::BufferItem buffer{}; | ||||
|         const auto status = layer.GetConsumer().AcquireBuffer(&buffer, {}, false); | ||||
|  | ||||
|         if (status != android::Status::NoError) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const auto& igbp_buffer = *buffer.graphic_buffer; | ||||
|  | ||||
|         if (!system.IsPoweredOn()) { | ||||
|             return; // We are likely shutting down | ||||
|         } | ||||
|  | ||||
|         // Now send the buffer to the GPU for drawing. | ||||
|         // TODO(Subv): Support more than just disp0. The display device selection is probably based | ||||
|         // on which display we're drawing (Default, Internal, External, etc) | ||||
|         auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd); | ||||
|         ASSERT(nvdisp); | ||||
|  | ||||
|         guard->unlock(); | ||||
|         Common::Rectangle<int> crop_rect{ | ||||
|             static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()), | ||||
|             static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())}; | ||||
|  | ||||
|         nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(), | ||||
|                      igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(), | ||||
|                      static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect, | ||||
|                      buffer.fence.fences, buffer.fence.num_fences); | ||||
|  | ||||
|         MicroProfileFlip(); | ||||
|         guard->lock(); | ||||
|  | ||||
|         swap_interval = buffer.swap_interval; | ||||
|  | ||||
|         layer.GetConsumer().ReleaseBuffer(buffer, android::Fence::NoFence()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| s64 NVFlinger::GetNextTicks() const { | ||||
|     const auto& settings = Settings::values; | ||||
|     auto speed_scale = 1.f; | ||||
|     if (settings.use_multi_core.GetValue()) { | ||||
|         if (settings.use_speed_limit.GetValue()) { | ||||
|             // Scales the speed based on speed_limit setting on MC. SC is handled by | ||||
|             // SpeedLimiter::DoSpeedLimiting. | ||||
|             speed_scale = 100.f / settings.speed_limit.GetValue(); | ||||
|         } else { | ||||
|             // Run at unlocked framerate. | ||||
|             speed_scale = 0.01f; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // As an extension, treat nonpositive swap interval as framerate multiplier. | ||||
|     const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval) | ||||
|                                                  : 60.f / static_cast<f32>(swap_interval); | ||||
|  | ||||
|     return static_cast<s64>(speed_scale * (1000000000.f / effective_fps)); | ||||
| } | ||||
|  | ||||
| } // namespace Service::NVFlinger | ||||
| @@ -1,155 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <list> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "common/polyfill_thread.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/kernel_helpers.h" | ||||
|  | ||||
| namespace Common { | ||||
| class Event; | ||||
| } // namespace Common | ||||
|  | ||||
| namespace Core::Timing { | ||||
| class CoreTiming; | ||||
| struct EventType; | ||||
| } // namespace Core::Timing | ||||
|  | ||||
| namespace Kernel { | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace Service::Nvidia { | ||||
| class Module; | ||||
| } // namespace Service::Nvidia | ||||
|  | ||||
| namespace Service::VI { | ||||
| class Display; | ||||
| class Layer; | ||||
| } // namespace Service::VI | ||||
|  | ||||
| namespace Service::android { | ||||
| class BufferQueueCore; | ||||
| class BufferQueueProducer; | ||||
| } // namespace Service::android | ||||
|  | ||||
| namespace Service::NVFlinger { | ||||
|  | ||||
| class NVFlinger final { | ||||
| public: | ||||
|     explicit NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); | ||||
|     ~NVFlinger(); | ||||
|  | ||||
|     void ShutdownLayers(); | ||||
|  | ||||
|     /// Sets the NVDrv module instance to use to send buffers to the GPU. | ||||
|     void SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance); | ||||
|  | ||||
|     /// Opens the specified display and returns the ID. | ||||
|     /// | ||||
|     /// If an invalid display name is provided, then an empty optional is returned. | ||||
|     [[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name); | ||||
|  | ||||
|     /// Closes the specified display by its ID. | ||||
|     /// | ||||
|     /// Returns false if an invalid display ID is provided. | ||||
|     [[nodiscard]] bool CloseDisplay(u64 display_id); | ||||
|  | ||||
|     /// Creates a layer on the specified display and returns the layer ID. | ||||
|     /// | ||||
|     /// If an invalid display ID is specified, then an empty optional is returned. | ||||
|     [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id); | ||||
|  | ||||
|     /// Closes a layer on all displays for the given layer ID. | ||||
|     void CloseLayer(u64 layer_id); | ||||
|  | ||||
|     /// Finds the buffer queue ID of the specified layer in the specified display. | ||||
|     /// | ||||
|     /// If an invalid display ID or layer ID is provided, then an empty optional is returned. | ||||
|     [[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id); | ||||
|  | ||||
|     /// Gets the vsync event for the specified display. | ||||
|     /// | ||||
|     /// If an invalid display ID is provided, then VI::ResultNotFound is returned. | ||||
|     /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned. | ||||
|     [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id); | ||||
|  | ||||
|     /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when | ||||
|     /// finished. | ||||
|     void Compose(); | ||||
|  | ||||
|     [[nodiscard]] s64 GetNextTicks() const; | ||||
|  | ||||
| private: | ||||
|     struct Layer { | ||||
|         std::unique_ptr<android::BufferQueueCore> core; | ||||
|         std::unique_ptr<android::BufferQueueProducer> producer; | ||||
|     }; | ||||
|  | ||||
| private: | ||||
|     [[nodiscard]] std::unique_lock<std::mutex> Lock() const { | ||||
|         return std::unique_lock{*guard}; | ||||
|     } | ||||
|  | ||||
|     /// Finds the display identified by the specified ID. | ||||
|     [[nodiscard]] VI::Display* FindDisplay(u64 display_id); | ||||
|  | ||||
|     /// Finds the display identified by the specified ID. | ||||
|     [[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const; | ||||
|  | ||||
|     /// Finds the layer identified by the specified ID in the desired display. | ||||
|     [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id); | ||||
|  | ||||
|     /// Finds the layer identified by the specified ID in the desired display. | ||||
|     [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const; | ||||
|  | ||||
|     /// Finds the layer identified by the specified ID in the desired display, | ||||
|     /// or creates the layer if it is not found. | ||||
|     /// To be used when the system expects the specified ID to already exist. | ||||
|     [[nodiscard]] VI::Layer* FindOrCreateLayer(u64 display_id, u64 layer_id); | ||||
|  | ||||
|     /// Creates a layer with the specified layer ID in the desired display. | ||||
|     void CreateLayerAtId(VI::Display& display, u64 layer_id); | ||||
|  | ||||
|     void SplitVSync(std::stop_token stop_token); | ||||
|  | ||||
|     std::shared_ptr<Nvidia::Module> nvdrv; | ||||
|     s32 disp_fd; | ||||
|  | ||||
|     std::list<VI::Display> displays; | ||||
|  | ||||
|     /// Id to use for the next layer that is created, this counter is shared among all displays. | ||||
|     u64 next_layer_id = 1; | ||||
|     /// Id to use for the next buffer queue that is created, this counter is shared among all | ||||
|     /// layers. | ||||
|     u32 next_buffer_queue_id = 1; | ||||
|  | ||||
|     s32 swap_interval = 1; | ||||
|  | ||||
|     /// Event that handles screen composition. | ||||
|     std::shared_ptr<Core::Timing::EventType> multi_composition_event; | ||||
|     std::shared_ptr<Core::Timing::EventType> single_composition_event; | ||||
|  | ||||
|     std::shared_ptr<std::mutex> guard; | ||||
|  | ||||
|     Core::System& system; | ||||
|  | ||||
|     std::atomic<bool> vsync_signal; | ||||
|  | ||||
|     std::jthread vsync_thread; | ||||
|  | ||||
|     KernelHelpers::ServiceContext service_context; | ||||
|  | ||||
|     HosBinderDriverServer& hos_binder_driver_server; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::NVFlinger | ||||
| @@ -1,177 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/alignment.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| struct ParcelHeader { | ||||
|     u32 data_size; | ||||
|     u32 data_offset; | ||||
|     u32 objects_size; | ||||
|     u32 objects_offset; | ||||
| }; | ||||
| static_assert(sizeof(ParcelHeader) == 16, "ParcelHeader has wrong size"); | ||||
|  | ||||
| class InputParcel final { | ||||
| public: | ||||
|     explicit InputParcel(std::span<const u8> in_data) : read_buffer(std::move(in_data)) { | ||||
|         DeserializeHeader(); | ||||
|         [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     void Read(T& val) { | ||||
|         static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); | ||||
|         ASSERT(read_index + sizeof(T) <= read_buffer.size()); | ||||
|  | ||||
|         std::memcpy(&val, read_buffer.data() + read_index, sizeof(T)); | ||||
|         read_index += sizeof(T); | ||||
|         read_index = Common::AlignUp(read_index, 4); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     T Read() { | ||||
|         T val; | ||||
|         Read(val); | ||||
|         return val; | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     void ReadFlattened(T& val) { | ||||
|         const auto flattened_size = Read<s64>(); | ||||
|         ASSERT(sizeof(T) == flattened_size); | ||||
|         Read(val); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     T ReadFlattened() { | ||||
|         T val; | ||||
|         ReadFlattened(val); | ||||
|         return val; | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     T ReadUnaligned() { | ||||
|         static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); | ||||
|         ASSERT(read_index + sizeof(T) <= read_buffer.size()); | ||||
|  | ||||
|         T val; | ||||
|         std::memcpy(&val, read_buffer.data() + read_index, sizeof(T)); | ||||
|         read_index += sizeof(T); | ||||
|         return val; | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     const std::shared_ptr<T> ReadObject() { | ||||
|         static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); | ||||
|  | ||||
|         const auto is_valid{Read<bool>()}; | ||||
|  | ||||
|         if (is_valid) { | ||||
|             auto result = std::make_shared<T>(); | ||||
|             ReadFlattened(*result); | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     std::u16string ReadInterfaceToken() { | ||||
|         [[maybe_unused]] const u32 unknown = Read<u32>(); | ||||
|         const u32 length = Read<u32>(); | ||||
|  | ||||
|         std::u16string token; | ||||
|         token.reserve(length + 1); | ||||
|  | ||||
|         for (u32 ch = 0; ch < length + 1; ++ch) { | ||||
|             token.push_back(ReadUnaligned<u16>()); | ||||
|         } | ||||
|  | ||||
|         read_index = Common::AlignUp(read_index, 4); | ||||
|  | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     void DeserializeHeader() { | ||||
|         ASSERT(read_buffer.size() > sizeof(ParcelHeader)); | ||||
|  | ||||
|         ParcelHeader header{}; | ||||
|         std::memcpy(&header, read_buffer.data(), sizeof(ParcelHeader)); | ||||
|  | ||||
|         read_index = header.data_offset; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::span<const u8> read_buffer; | ||||
|     std::size_t read_index = 0; | ||||
| }; | ||||
|  | ||||
| class OutputParcel final { | ||||
| public: | ||||
|     static constexpr std::size_t DefaultBufferSize = 0x40; | ||||
|  | ||||
|     OutputParcel() : buffer(DefaultBufferSize) {} | ||||
|  | ||||
|     template <typename T> | ||||
|     explicit OutputParcel(const T& out_data) : buffer(DefaultBufferSize) { | ||||
|         Write(out_data); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     void Write(const T& val) { | ||||
|         static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); | ||||
|  | ||||
|         if (buffer.size() < write_index + sizeof(T)) { | ||||
|             buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize); | ||||
|         } | ||||
|  | ||||
|         std::memcpy(buffer.data() + write_index, &val, sizeof(T)); | ||||
|         write_index += sizeof(T); | ||||
|         write_index = Common::AlignUp(write_index, 4); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     void WriteObject(const T* ptr) { | ||||
|         static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); | ||||
|  | ||||
|         if (!ptr) { | ||||
|             Write<u32>(0); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Write<u32>(1); | ||||
|         Write<s64>(sizeof(T)); | ||||
|         Write(*ptr); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     void WriteObject(const std::shared_ptr<T> ptr) { | ||||
|         WriteObject(ptr.get()); | ||||
|     } | ||||
|  | ||||
|     std::vector<u8> Serialize() const { | ||||
|         ParcelHeader header{}; | ||||
|         header.data_size = static_cast<u32>(write_index - sizeof(ParcelHeader)); | ||||
|         header.data_offset = sizeof(ParcelHeader); | ||||
|         header.objects_size = 4; | ||||
|         header.objects_offset = static_cast<u32>(sizeof(ParcelHeader) + header.data_size); | ||||
|         std::memcpy(buffer.data(), &header, sizeof(ParcelHeader)); | ||||
|  | ||||
|         return buffer; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     mutable std::vector<u8> buffer; | ||||
|     std::size_t write_index = sizeof(ParcelHeader); | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,21 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| enum class PixelFormat : u32 { | ||||
|     NoFormat = 0, | ||||
|     Rgba8888 = 1, | ||||
|     Rgbx8888 = 2, | ||||
|     Rgb888 = 3, | ||||
|     Rgb565 = 4, | ||||
|     Bgra8888 = 5, | ||||
|     Rgba5551 = 6, | ||||
|     Rgba4444 = 7, | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,17 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IProducerListener.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class IProducerListener { | ||||
| public: | ||||
|     virtual ~IProducerListener() = default; | ||||
|     virtual void OnBufferReleased() = 0; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,28 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| enum class Status : s32 { | ||||
|     None = 0, | ||||
|     NoError = 0, | ||||
|     StaleBufferSlot = 1, | ||||
|     NoBufferAvailable = 2, | ||||
|     PresentLater = 3, | ||||
|     WouldBlock = -11, | ||||
|     NoMemory = -12, | ||||
|     Busy = -16, | ||||
|     NoInit = -19, | ||||
|     BadValue = -22, | ||||
|     InvalidOperation = -37, | ||||
|     BufferNeedsReallocation = 1, | ||||
|     ReleaseAllBuffers = 2, | ||||
| }; | ||||
| DECLARE_ENUM_FLAG_OPERATORS(Status); | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,32 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Fence.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvdrv/nvdata.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class Fence { | ||||
| public: | ||||
|     constexpr Fence() = default; | ||||
|  | ||||
|     static constexpr Fence NoFence() { | ||||
|         Fence fence; | ||||
|         fence.fences[0].id = -1; | ||||
|         return fence; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     u32 num_fences{}; | ||||
|     std::array<Service::Nvidia::NvFence, 4> fences{}; | ||||
| }; | ||||
| static_assert(sizeof(Fence) == 36, "Fence has wrong size"); | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,100 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-FileCopyrightText: Copyright 2007 The Android Open Source Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
| // Parts of this implementation were based on: | ||||
| // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/GraphicBuffer.h | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nvflinger/pixel_format.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| class GraphicBuffer final { | ||||
| public: | ||||
|     constexpr GraphicBuffer() = default; | ||||
|  | ||||
|     constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) | ||||
|         : width{static_cast<s32>(width_)}, height{static_cast<s32>(height_)}, format{format_}, | ||||
|           usage{static_cast<s32>(usage_)} {} | ||||
|  | ||||
|     constexpr u32 Width() const { | ||||
|         return static_cast<u32>(width); | ||||
|     } | ||||
|  | ||||
|     constexpr u32 Height() const { | ||||
|         return static_cast<u32>(height); | ||||
|     } | ||||
|  | ||||
|     constexpr u32 Stride() const { | ||||
|         return static_cast<u32>(stride); | ||||
|     } | ||||
|  | ||||
|     constexpr u32 Usage() const { | ||||
|         return static_cast<u32>(usage); | ||||
|     } | ||||
|  | ||||
|     constexpr PixelFormat Format() const { | ||||
|         return format; | ||||
|     } | ||||
|  | ||||
|     constexpr u32 BufferId() const { | ||||
|         return buffer_id; | ||||
|     } | ||||
|  | ||||
|     constexpr PixelFormat ExternalFormat() const { | ||||
|         return external_format; | ||||
|     } | ||||
|  | ||||
|     constexpr u32 Handle() const { | ||||
|         return handle; | ||||
|     } | ||||
|  | ||||
|     constexpr u32 Offset() const { | ||||
|         return offset; | ||||
|     } | ||||
|  | ||||
|     constexpr bool NeedsReallocation(u32 width_, u32 height_, PixelFormat format_, | ||||
|                                      u32 usage_) const { | ||||
|         if (static_cast<s32>(width_) != width) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (static_cast<s32>(height_) != height) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (format_ != format) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if ((static_cast<u32>(usage) & usage_) != usage_) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     u32 magic{}; | ||||
|     s32 width{}; | ||||
|     s32 height{}; | ||||
|     s32 stride{}; | ||||
|     PixelFormat format{}; | ||||
|     s32 usage{}; | ||||
|     INSERT_PADDING_WORDS(1); | ||||
|     u32 index{}; | ||||
|     INSERT_PADDING_WORDS(3); | ||||
|     u32 buffer_id{}; | ||||
|     INSERT_PADDING_WORDS(6); | ||||
|     PixelFormat external_format{}; | ||||
|     INSERT_PADDING_WORDS(10); | ||||
|     u32 handle{}; | ||||
|     u32 offset{}; | ||||
|     INSERT_PADDING_WORDS(60); | ||||
| }; | ||||
| static_assert(sizeof(GraphicBuffer) == 0x16C, "GraphicBuffer has wrong size"); | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,53 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Service::android { | ||||
|  | ||||
| /// Attributes queryable with Query | ||||
| enum class NativeWindow : s32 { | ||||
|     Width = 0, | ||||
|     Height = 1, | ||||
|     Format = 2, | ||||
|     MinUndequeedBuffers = 3, | ||||
|     QueuesToWindowComposer = 4, | ||||
|     ConcreteType = 5, | ||||
|     DefaultWidth = 6, | ||||
|     DefaultHeight = 7, | ||||
|     TransformHint = 8, | ||||
|     ConsumerRunningBehind = 9, | ||||
|     ConsumerUsageBits = 10, | ||||
|     StickyTransform = 11, | ||||
|     DefaultDataSpace = 12, | ||||
|     BufferAge = 13, | ||||
| }; | ||||
|  | ||||
| /// Parameter for Connect/Disconnect | ||||
| enum class NativeWindowApi : s32 { | ||||
|     NoConnectedApi = 0, | ||||
|     Egl = 1, | ||||
|     Cpu = 2, | ||||
|     Media = 3, | ||||
|     Camera = 4, | ||||
| }; | ||||
|  | ||||
| /// Scaling mode parameter for QueueBuffer | ||||
| enum class NativeWindowScalingMode : s32 { | ||||
|     Freeze = 0, | ||||
|     ScaleToWindow = 1, | ||||
|     ScaleCrop = 2, | ||||
|     NoScaleCrop = 3, | ||||
| }; | ||||
|  | ||||
| /// Transform parameter for QueueBuffer | ||||
| enum class NativeWindowTransform : u32 { | ||||
|     None = 0x0, | ||||
|     InverseDisplay = 0x08, | ||||
| }; | ||||
| DECLARE_ENUM_FLAG_OPERATORS(NativeWindowTransform); | ||||
|  | ||||
| } // namespace Service::android | ||||
| @@ -1,42 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "core/hle/service/sockets/ethc.h" | ||||
|  | ||||
| namespace Service::Sockets { | ||||
|  | ||||
| ETHC_C::ETHC_C(Core::System& system_) : ServiceFramework{system_, "ethc:c"} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "Initialize"}, | ||||
|         {1, nullptr, "Cancel"}, | ||||
|         {2, nullptr, "GetResult"}, | ||||
|         {3, nullptr, "GetMediaList"}, | ||||
|         {4, nullptr, "SetMediaType"}, | ||||
|         {5, nullptr, "GetMediaType"}, | ||||
|         {6, nullptr, "Unknown6"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| ETHC_C::~ETHC_C() = default; | ||||
|  | ||||
| ETHC_I::ETHC_I(Core::System& system_) : ServiceFramework{system_, "ethc:i"} { | ||||
|     // clang-format off | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0, nullptr, "GetReadableHandle"}, | ||||
|         {1, nullptr, "Cancel"}, | ||||
|         {2, nullptr, "GetResult"}, | ||||
|         {3, nullptr, "GetInterfaceList"}, | ||||
|         {4, nullptr, "GetInterfaceCount"}, | ||||
|     }; | ||||
|     // clang-format on | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
| } | ||||
|  | ||||
| ETHC_I::~ETHC_I() = default; | ||||
|  | ||||
| } // namespace Service::Sockets | ||||
| @@ -1,26 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::Sockets { | ||||
|  | ||||
| class ETHC_C final : public ServiceFramework<ETHC_C> { | ||||
| public: | ||||
|     explicit ETHC_C(Core::System& system_); | ||||
|     ~ETHC_C() override; | ||||
| }; | ||||
|  | ||||
| class ETHC_I final : public ServiceFramework<ETHC_I> { | ||||
| public: | ||||
|     explicit ETHC_I(Core::System& system_); | ||||
|     ~ETHC_I() override; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Sockets | ||||
| @@ -1,186 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <memory> | ||||
|  | ||||
| #include "core/hle/service/service.h" | ||||
| #include "core/hle/service/sm/sm.h" | ||||
| #include "core/hle/service/wlan/wlan.h" | ||||
|  | ||||
| namespace Service::WLAN { | ||||
|  | ||||
| class WLANInfra final : public ServiceFramework<WLANInfra> { | ||||
| public: | ||||
|     explicit WLANInfra(Core::System& system_) : ServiceFramework{system_, "wlan:inf"} { | ||||
|         // clang-format off | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, nullptr, "OpenMode"}, | ||||
|             {1, nullptr, "CloseMode"}, | ||||
|             {2, nullptr, "GetMacAddress"}, | ||||
|             {3, nullptr, "StartScan"}, | ||||
|             {4, nullptr, "StopScan"}, | ||||
|             {5, nullptr, "Connect"}, | ||||
|             {6, nullptr, "CancelConnect"}, | ||||
|             {7, nullptr, "Disconnect"}, | ||||
|             {8, nullptr, "GetConnectionEvent"}, | ||||
|             {9, nullptr, "GetConnectionStatus"}, | ||||
|             {10, nullptr, "GetState"}, | ||||
|             {11, nullptr, "GetScanResult"}, | ||||
|             {12, nullptr, "GetRssi"}, | ||||
|             {13, nullptr, "ChangeRxAntenna"}, | ||||
|             {14, nullptr, "GetFwVersion"}, | ||||
|             {15, nullptr, "RequestSleep"}, | ||||
|             {16, nullptr, "RequestWakeUp"}, | ||||
|             {17, nullptr, "RequestIfUpDown"}, | ||||
|             {18, nullptr, "Unknown18"}, | ||||
|             {19, nullptr, "Unknown19"}, | ||||
|             {20, nullptr, "Unknown20"}, | ||||
|             {21, nullptr, "Unknown21"}, | ||||
|             {22, nullptr, "Unknown22"}, | ||||
|             {23, nullptr, "Unknown23"}, | ||||
|             {24, nullptr, "Unknown24"}, | ||||
|             {25, nullptr, "Unknown25"}, | ||||
|             {26, nullptr, "Unknown26"}, | ||||
|             {27, nullptr, "Unknown27"}, | ||||
|             {28, nullptr, "Unknown28"}, | ||||
|             {29, nullptr, "Unknown29"}, | ||||
|             {30, nullptr, "Unknown30"}, | ||||
|             {31, nullptr, "Unknown31"}, | ||||
|             {32, nullptr, "Unknown32"}, | ||||
|             {33, nullptr, "Unknown33"}, | ||||
|             {34, nullptr, "Unknown34"}, | ||||
|             {35, nullptr, "Unknown35"}, | ||||
|             {36, nullptr, "Unknown36"}, | ||||
|             {37, nullptr, "Unknown37"}, | ||||
|             {38, nullptr, "Unknown38"}, | ||||
|         }; | ||||
|         // clang-format on | ||||
|  | ||||
|         RegisterHandlers(functions); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class WLANLocal final : public ServiceFramework<WLANLocal> { | ||||
| public: | ||||
|     explicit WLANLocal(Core::System& system_) : ServiceFramework{system_, "wlan:lcl"} { | ||||
|         // clang-format off | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, nullptr, "Unknown0"}, | ||||
|             {1, nullptr, "Unknown1"}, | ||||
|             {2, nullptr, "Unknown2"}, | ||||
|             {3, nullptr, "Unknown3"}, | ||||
|             {4, nullptr, "Unknown4"}, | ||||
|             {5, nullptr, "Unknown5"}, | ||||
|             {6, nullptr, "GetMacAddress"}, | ||||
|             {7, nullptr, "CreateBss"}, | ||||
|             {8, nullptr, "DestroyBss"}, | ||||
|             {9, nullptr, "StartScan"}, | ||||
|             {10, nullptr, "StopScan"}, | ||||
|             {11, nullptr, "Connect"}, | ||||
|             {12, nullptr, "CancelConnect"}, | ||||
|             {13, nullptr, "Join"}, | ||||
|             {14, nullptr, "CancelJoin"}, | ||||
|             {15, nullptr, "Disconnect"}, | ||||
|             {16, nullptr, "SetBeaconLostCount"}, | ||||
|             {17, nullptr, "Unknown17"}, | ||||
|             {18, nullptr, "Unknown18"}, | ||||
|             {19, nullptr, "Unknown19"}, | ||||
|             {20, nullptr, "GetBssIndicationEvent"}, | ||||
|             {21, nullptr, "GetBssIndicationInfo"}, | ||||
|             {22, nullptr, "GetState"}, | ||||
|             {23, nullptr, "GetAllowedChannels"}, | ||||
|             {24, nullptr, "AddIe"}, | ||||
|             {25, nullptr, "DeleteIe"}, | ||||
|             {26, nullptr, "Unknown26"}, | ||||
|             {27, nullptr, "Unknown27"}, | ||||
|             {28, nullptr, "CreateRxEntry"}, | ||||
|             {29, nullptr, "DeleteRxEntry"}, | ||||
|             {30, nullptr, "Unknown30"}, | ||||
|             {31, nullptr, "Unknown31"}, | ||||
|             {32, nullptr, "AddMatchingDataToRxEntry"}, | ||||
|             {33, nullptr, "RemoveMatchingDataFromRxEntry"}, | ||||
|             {34, nullptr, "GetScanResult"}, | ||||
|             {35, nullptr, "Unknown35"}, | ||||
|             {36, nullptr, "SetActionFrameWithBeacon"}, | ||||
|             {37, nullptr, "CancelActionFrameWithBeacon"}, | ||||
|             {38, nullptr, "CreateRxEntryForActionFrame"}, | ||||
|             {39, nullptr, "DeleteRxEntryForActionFrame"}, | ||||
|             {40, nullptr, "Unknown40"}, | ||||
|             {41, nullptr, "Unknown41"}, | ||||
|             {42, nullptr, "CancelGetActionFrame"}, | ||||
|             {43, nullptr, "GetRssi"}, | ||||
|             {44, nullptr, "Unknown44"}, | ||||
|             {45, nullptr, "Unknown45"}, | ||||
|             {46, nullptr, "Unknown46"}, | ||||
|             {47, nullptr, "Unknown47"}, | ||||
|             {48, nullptr, "Unknown48"}, | ||||
|             {49, nullptr, "Unknown49"}, | ||||
|             {50, nullptr, "Unknown50"}, | ||||
|             {51, nullptr, "Unknown51"}, | ||||
|         }; | ||||
|         // clang-format on | ||||
|  | ||||
|         RegisterHandlers(functions); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class WLANLocalGetFrame final : public ServiceFramework<WLANLocalGetFrame> { | ||||
| public: | ||||
|     explicit WLANLocalGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:lg"} { | ||||
|         // clang-format off | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, nullptr, "Unknown"}, | ||||
|         }; | ||||
|         // clang-format on | ||||
|  | ||||
|         RegisterHandlers(functions); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class WLANSocketGetFrame final : public ServiceFramework<WLANSocketGetFrame> { | ||||
| public: | ||||
|     explicit WLANSocketGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:sg"} { | ||||
|         // clang-format off | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, nullptr, "Unknown"}, | ||||
|         }; | ||||
|         // clang-format on | ||||
|  | ||||
|         RegisterHandlers(functions); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class WLANSocketManager final : public ServiceFramework<WLANSocketManager> { | ||||
| public: | ||||
|     explicit WLANSocketManager(Core::System& system_) : ServiceFramework{system_, "wlan:soc"} { | ||||
|         // clang-format off | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, nullptr, "Unknown0"}, | ||||
|             {1, nullptr, "Unknown1"}, | ||||
|             {2, nullptr, "Unknown2"}, | ||||
|             {3, nullptr, "Unknown3"}, | ||||
|             {4, nullptr, "Unknown4"}, | ||||
|             {5, nullptr, "Unknown5"}, | ||||
|             {6, nullptr, "GetMacAddress"}, | ||||
|             {7, nullptr, "SwitchTsfTimerFunction"}, | ||||
|             {8, nullptr, "Unknown8"}, | ||||
|             {9, nullptr, "Unknown9"}, | ||||
|             {10, nullptr, "Unknown10"}, | ||||
|             {11, nullptr, "Unknown11"}, | ||||
|             {12, nullptr, "Unknown12"}, | ||||
|         }; | ||||
|         // clang-format on | ||||
|  | ||||
|         RegisterHandlers(functions); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { | ||||
|     std::make_shared<WLANInfra>(system)->InstallAsService(sm); | ||||
|     std::make_shared<WLANLocal>(system)->InstallAsService(sm); | ||||
|     std::make_shared<WLANLocalGetFrame>(system)->InstallAsService(sm); | ||||
|     std::make_shared<WLANSocketGetFrame>(system)->InstallAsService(sm); | ||||
|     std::make_shared<WLANSocketManager>(system)->InstallAsService(sm); | ||||
| } | ||||
|  | ||||
| } // namespace Service::WLAN | ||||
| @@ -1,18 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Service::SM { | ||||
| class ServiceManager; | ||||
| } | ||||
|  | ||||
| namespace Service::WLAN { | ||||
|  | ||||
| void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); | ||||
|  | ||||
| } // namespace Service::WLAN | ||||
| @@ -1,8 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2016 Citra Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #define CATCH_CONFIG_MAIN | ||||
| #include <catch2/catch.hpp> | ||||
|  | ||||
| // Catch provides the main function since we've given it the | ||||
| // CATCH_CONFIG_MAIN preprocessor directive. | ||||
| @@ -1,549 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <stdexcept> | ||||
| #include <unordered_map> | ||||
|  | ||||
| #include <catch2/catch_test_macros.hpp> | ||||
|  | ||||
| #include "common/alignment.h" | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/buffer_cache/buffer_base.h" | ||||
|  | ||||
| namespace { | ||||
| using VideoCommon::BufferBase; | ||||
| using Range = std::pair<u64, u64>; | ||||
|  | ||||
| constexpr u64 PAGE = 4096; | ||||
| constexpr u64 WORD = 4096 * 64; | ||||
|  | ||||
| constexpr VAddr c = 0x1328914000; | ||||
|  | ||||
| class RasterizerInterface { | ||||
| public: | ||||
|     void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { | ||||
|         const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS}; | ||||
|         const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >> | ||||
|                            Core::Memory::YUZU_PAGEBITS}; | ||||
|         for (u64 page = page_start; page < page_end; ++page) { | ||||
|             int& value = page_table[page]; | ||||
|             value += delta; | ||||
|             if (value < 0) { | ||||
|                 throw std::logic_error{"negative page"}; | ||||
|             } | ||||
|             if (value == 0) { | ||||
|                 page_table.erase(page); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] int Count(VAddr addr) const noexcept { | ||||
|         const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS); | ||||
|         return it == page_table.end() ? 0 : it->second; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] unsigned Count() const noexcept { | ||||
|         unsigned count = 0; | ||||
|         for (const auto& [index, value] : page_table) { | ||||
|             count += value; | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::unordered_map<u64, int> page_table; | ||||
| }; | ||||
| } // Anonymous namespace | ||||
|  | ||||
| TEST_CASE("BufferBase: Small buffer", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == WORD / PAGE); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{0, 0}); | ||||
|  | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE, 1); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{PAGE * 1, PAGE * 2}); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Large buffer", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 32); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 32); | ||||
|     buffer.MarkRegionAsCpuModified(c + 4096, WORD * 4); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{PAGE, WORD + PAGE * 2}); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{PAGE * 2, PAGE * 8}); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 4 + PAGE}); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{WORD * 4, WORD * 4 + PAGE}); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) == | ||||
|             Range{WORD * 3 + PAGE * 63, WORD * 4}); | ||||
|  | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) == | ||||
|             Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 9}); | ||||
|  | ||||
|     buffer.UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) == | ||||
|             Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 7}); | ||||
|  | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 32}); | ||||
|  | ||||
|     buffer.UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE); | ||||
|     buffer.UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE); | ||||
|  | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 32); | ||||
|     REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{0, 0}); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Rasterizer counting", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, PAGE * 2); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, PAGE); | ||||
|     REQUIRE(rasterizer.Count() == 1); | ||||
|     buffer.MarkRegionAsCpuModified(c, PAGE * 2); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, PAGE); | ||||
|     buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE); | ||||
|     REQUIRE(rasterizer.Count() == 2); | ||||
|     buffer.MarkRegionAsCpuModified(c, PAGE * 2); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Basic range", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.MarkRegionAsCpuModified(c, PAGE); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == 0U); | ||||
|         REQUIRE(size == PAGE); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 1U); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Border upload", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 2); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 2); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); | ||||
|     buffer.ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD - PAGE); | ||||
|         REQUIRE(size == PAGE * 2); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Border upload range", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 2); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 2); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); | ||||
|     buffer.ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD - PAGE); | ||||
|         REQUIRE(size == PAGE * 2); | ||||
|     }); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); | ||||
|     buffer.ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD - PAGE); | ||||
|         REQUIRE(size == PAGE); | ||||
|     }); | ||||
|     buffer.ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD); | ||||
|         REQUIRE(size == PAGE); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Border upload partial range", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 2); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 2); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); | ||||
|     buffer.ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD - PAGE); | ||||
|         REQUIRE(size == PAGE * 2); | ||||
|     }); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); | ||||
|     buffer.ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD - PAGE); | ||||
|         REQUIRE(size == PAGE); | ||||
|     }); | ||||
|     buffer.ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD); | ||||
|         REQUIRE(size == PAGE); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Partial word uploads", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, 0x9d000); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == 0U); | ||||
|         REQUIRE(size == WORD); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 1); | ||||
|     buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD); | ||||
|         REQUIRE(size == WORD); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 2); | ||||
|     buffer.ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD * 2); | ||||
|         REQUIRE(size == PAGE * 0x1d); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 3); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Partial page upload", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     int num = 0; | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 2, PAGE); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 9, PAGE); | ||||
|     buffer.ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 2); | ||||
|         REQUIRE(size == PAGE); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 1); | ||||
|     buffer.ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 9); | ||||
|         REQUIRE(size == PAGE); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 2); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Partial page upload with multiple words on the right") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 8); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 8); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 13); | ||||
|         REQUIRE(size == WORD * 7 - PAGE * 3); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 1); | ||||
|     buffer.ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD * 7 + PAGE * 10); | ||||
|         REQUIRE(size == PAGE * 3); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 2); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Partial page upload with multiple words on the left", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 8); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 8); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 16); | ||||
|         REQUIRE(size == WORD * 7 - PAGE * 3); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 1); | ||||
|     buffer.ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 13); | ||||
|         REQUIRE(size == PAGE * 3); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 2); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Partial page upload with multiple words in the middle", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 8); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 8); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 16); | ||||
|         REQUIRE(size == WORD); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 1); | ||||
|     buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == PAGE * 13); | ||||
|         REQUIRE(size == PAGE * 3); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 2); | ||||
|     buffer.ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD + PAGE * 16); | ||||
|         REQUIRE(size == PAGE * 73); | ||||
|         ++num; | ||||
|     }); | ||||
|     REQUIRE(num == 3); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Empty right bits", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 2048); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 2048); | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); | ||||
|     buffer.ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) { | ||||
|         REQUIRE(offset == WORD - PAGE); | ||||
|         REQUIRE(size == PAGE * 2); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Out of bound ranges 1", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.MarkRegionAsCpuModified(c, PAGE); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; }); | ||||
|     buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; }); | ||||
|     buffer.ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; }); | ||||
|     REQUIRE(num == 0); | ||||
|     buffer.ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; }); | ||||
|     REQUIRE(num == 1); | ||||
|     buffer.MarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Out of bound ranges 2", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, 0x22000); | ||||
|     REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x22000, PAGE)); | ||||
|     REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x28000, PAGE)); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
|     REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100)); | ||||
|     REQUIRE(rasterizer.Count() == 1); | ||||
|     REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c - 0x1000, PAGE * 2)); | ||||
|     buffer.UnmarkRegionAsCpuModified(c - 0x3000, PAGE * 2); | ||||
|     buffer.UnmarkRegionAsCpuModified(c - 0x2000, PAGE * 2); | ||||
|     REQUIRE(rasterizer.Count() == 2); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Out of bound ranges 3", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, 0x310720); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, 0x310720); | ||||
|     REQUIRE(rasterizer.Count(c) == 1); | ||||
|     REQUIRE(rasterizer.Count(c + PAGE) == 1); | ||||
|     REQUIRE(rasterizer.Count(c + WORD) == 1); | ||||
|     REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Sparse regions 1", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 1, PAGE); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4); | ||||
|     buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable { | ||||
|         static constexpr std::array<u64, 2> offsets{PAGE, PAGE * 3}; | ||||
|         static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4}; | ||||
|         REQUIRE(offset == offsets.at(i)); | ||||
|         REQUIRE(size == sizes.at(i)); | ||||
|         ++i; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Sparse regions 2", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, 0x22000); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, 0x22000); | ||||
|     REQUIRE(rasterizer.Count() == 0x22); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE); | ||||
|     buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable { | ||||
|         static constexpr std::array<u64, 2> offsets{PAGE * 0x1B, PAGE * 0x21}; | ||||
|         static constexpr std::array<u64, 2> sizes{PAGE, PAGE}; | ||||
|         REQUIRE(offset == offsets.at(i)); | ||||
|         REQUIRE(size == sizes.at(i)); | ||||
|         ++i; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Single page modified range", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, PAGE); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c, PAGE)); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, PAGE); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c, PAGE)); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Two page modified range", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, PAGE * 2); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c, PAGE * 2)); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, PAGE); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c, PAGE)); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Multi word modified ranges", "[video_core]") { | ||||
|     for (int offset = 0; offset < 4; ++offset) { | ||||
|         const VAddr address = c + WORD * offset; | ||||
|         RasterizerInterface rasterizer; | ||||
|         BufferBase buffer(rasterizer, address, WORD * 4); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address, PAGE)); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 48, PAGE)); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 56, PAGE)); | ||||
|  | ||||
|         buffer.UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE, WORD)); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE)); | ||||
|         REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE)); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 33, PAGE)); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE * 2)); | ||||
|         REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2)); | ||||
|  | ||||
|         buffer.UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE); | ||||
|         REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Single page in large buffer", "[video_core]") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 16); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 16); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c, WORD * 16)); | ||||
|  | ||||
|     buffer.MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c, WORD * 16)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 10, WORD * 2)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 11, WORD * 2)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12, WORD * 2)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2)); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Out of bounds region query") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 16); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c - PAGE, PAGE)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c - PAGE * 2, PAGE)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + WORD * 16 - PAGE, WORD * 64)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, WORD * 64)); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Wrap word regions") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD * 2); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD * 2); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c, WORD * 2)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 62, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 64, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 2)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 8)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 60, PAGE * 8)); | ||||
|  | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16)); | ||||
|     buffer.MarkRegionAsCpuModified(c + PAGE * 127, PAGE); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, PAGE)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 126, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 126, PAGE * 2)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 128, WORD * 16)); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Unaligned page region query") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.MarkRegionAsCpuModified(c + 4000, 1000); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1000)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1)); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Cached write") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.CachedCpuWrite(c + PAGE, PAGE); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     buffer.FlushCachedWrites(); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     buffer.MarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Multiple cached write") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.CachedCpuWrite(c + PAGE, PAGE); | ||||
|     buffer.CachedCpuWrite(c + PAGE * 3, PAGE); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 3, PAGE)); | ||||
|     buffer.FlushCachedWrites(); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 3, PAGE)); | ||||
|     buffer.MarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Cached write unmarked") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.CachedCpuWrite(c + PAGE, PAGE); | ||||
|     buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     buffer.FlushCachedWrites(); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     buffer.MarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Cached write iterated") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     buffer.CachedCpuWrite(c + PAGE, PAGE); | ||||
|     int num = 0; | ||||
|     buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); | ||||
|     REQUIRE(num == 0); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     buffer.FlushCachedWrites(); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     buffer.MarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
|  | ||||
| TEST_CASE("BufferBase: Cached write downloads") { | ||||
|     RasterizerInterface rasterizer; | ||||
|     BufferBase buffer(rasterizer, c, WORD); | ||||
|     buffer.UnmarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 64); | ||||
|     buffer.CachedCpuWrite(c + PAGE, PAGE); | ||||
|     REQUIRE(rasterizer.Count() == 63); | ||||
|     buffer.MarkRegionAsGpuModified(c + PAGE, PAGE); | ||||
|     int num = 0; | ||||
|     buffer.ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; }); | ||||
|     buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); | ||||
|     REQUIRE(num == 0); | ||||
|     REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE)); | ||||
|     buffer.FlushCachedWrites(); | ||||
|     REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); | ||||
|     REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE)); | ||||
|     buffer.MarkRegionAsCpuModified(c, WORD); | ||||
|     REQUIRE(rasterizer.Count() == 0); | ||||
| } | ||||
| @@ -1,13 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #version 450 | ||||
|  | ||||
| layout(binding = 0) uniform sampler2D tex; | ||||
|  | ||||
| layout(location = 0) in vec2 texcoord; | ||||
| layout(location = 0) out vec4 color; | ||||
|  | ||||
| void main() { | ||||
|     color = textureLod(tex, texcoord, 0); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user