diff options
Diffstat (limited to 'src/core/hle/service')
102 files changed, 7054 insertions, 2034 deletions
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 420de3c54..4d7e5ecd3 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1337,7 +1337,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {200, nullptr, "GetLastApplicationExitReason"}, {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, - {1001, nullptr, "PrepareForJit"}, + {1001, &IApplicationFunctions::PrepareForJit, "PrepareForJit"}, }; // clang-format on @@ -1787,6 +1787,13 @@ void IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(Kernel::HLERe rb.PushCopyObjects(health_warning_disappeared_system_event->GetReadableEvent()); } +void IApplicationFunctions::PrepareForJit(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, Core::System& system) { auto message_queue = std::make_shared<AppletMessageQueue>(system); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index fdd937b82..11a3c0459 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -336,6 +336,7 @@ private: void TryPopFromFriendInvitationStorageChannel(Kernel::HLERequestContext& ctx); void GetNotificationStorageChannelEvent(Kernel::HLERequestContext& ctx); void GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx); + void PrepareForJit(Kernel::HLERequestContext& ctx); KernelHelpers::ServiceContext service_context; diff --git a/src/core/hle/service/am/applets/applet_mii.cpp b/src/core/hle/service/am/applets/applet_mii.cpp deleted file mode 100644 index facede97e..000000000 --- a/src/core/hle/service/am/applets/applet_mii.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/frontend/applets/mii.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applets/applet_mii.h" - -namespace Service::AM::Applets { - -Mii::Mii(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::MiiApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -Mii::~Mii() = default; - -void Mii::Initialize() { - is_complete = false; - - const auto storage = broker.PopNormalDataToApplet(); - ASSERT(storage != nullptr); - - const auto data = storage->GetData(); - ASSERT(data.size() == sizeof(MiiAppletInput)); - - std::memcpy(&input_data, data.data(), sizeof(MiiAppletInput)); -} - -bool Mii::TransactionComplete() const { - return is_complete; -} - -ResultCode Mii::GetStatus() const { - return ResultSuccess; -} - -void Mii::ExecuteInteractive() { - UNREACHABLE_MSG("Unexpected interactive applet data!"); -} - -void Mii::Execute() { - if (is_complete) { - return; - } - - const auto callback = [this](const Core::Frontend::MiiParameters& parameters) { - DisplayCompleted(parameters); - }; - - switch (input_data.applet_mode) { - case MiiAppletMode::ShowMiiEdit: { - Service::Mii::MiiManager manager; - Core::Frontend::MiiParameters params{ - .is_editable = false, - .mii_data = input_data.mii_char_info.mii_data, - }; - frontend.ShowMii(params, callback); - break; - } - case MiiAppletMode::EditMii: { - Service::Mii::MiiManager manager; - Core::Frontend::MiiParameters params{ - .is_editable = true, - .mii_data = input_data.mii_char_info.mii_data, - }; - frontend.ShowMii(params, callback); - break; - } - case MiiAppletMode::CreateMii: { - Service::Mii::MiiManager manager; - Core::Frontend::MiiParameters params{ - .is_editable = true, - .mii_data = manager.BuildDefault(0), - }; - frontend.ShowMii(params, callback); - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletMiiEdit mode={:02X}!", input_data.applet_mode); - } -} - -void Mii::DisplayCompleted(const Core::Frontend::MiiParameters& parameters) { - is_complete = true; - - std::vector<u8> reply(sizeof(AppletOutputForCharInfoEditing)); - output_data = { - .result = ResultSuccess, - .mii_data = parameters.mii_data, - }; - - std::memcpy(reply.data(), &output_data, sizeof(AppletOutputForCharInfoEditing)); - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(reply))); - broker.SignalStateChanged(); -} - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_mii.h b/src/core/hle/service/am/applets/applet_mii.h deleted file mode 100644 index 42326bfc2..000000000 --- a/src/core/hle/service/am/applets/applet_mii.h +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2022 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> - -#include "core/hle/result.h" -#include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/mii/mii_manager.h" - -namespace Core { -class System; -} - -namespace Service::AM::Applets { - -// This is nn::mii::AppletMode -enum class MiiAppletMode : u32 { - ShowMiiEdit = 0, - AppendMii = 1, - AppendMiiImage = 2, - UpdateMiiImage = 3, - CreateMii = 4, - EditMii = 5, -}; - -struct MiiCharInfo { - Service::Mii::MiiInfo mii_data{}; - INSERT_PADDING_BYTES(0x28); -}; -static_assert(sizeof(MiiCharInfo) == 0x80, "MiiCharInfo has incorrect size."); - -// This is nn::mii::AppletInput -struct MiiAppletInput { - s32 version{}; - MiiAppletMode applet_mode{}; - u32 special_mii_key_code{}; - union { - std::array<Common::UUID, 8> valid_uuid; - MiiCharInfo mii_char_info; - }; - Common::UUID used_uuid; - INSERT_PADDING_BYTES(0x64); -}; -static_assert(sizeof(MiiAppletInput) == 0x100, "MiiAppletInput has incorrect size."); - -// This is nn::mii::AppletOutput -struct MiiAppletOutput { - ResultCode result{ResultSuccess}; - s32 index{}; - INSERT_PADDING_BYTES(0x18); -}; -static_assert(sizeof(MiiAppletOutput) == 0x20, "MiiAppletOutput has incorrect size."); - -// This is nn::mii::AppletOutputForCharInfoEditing -struct AppletOutputForCharInfoEditing { - ResultCode result{ResultSuccess}; - Service::Mii::MiiInfo mii_data{}; - INSERT_PADDING_BYTES(0x24); -}; -static_assert(sizeof(AppletOutputForCharInfoEditing) == 0x80, - "AppletOutputForCharInfoEditing has incorrect size."); - -class Mii final : public Applet { -public: - explicit Mii(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::MiiApplet& frontend_); - ~Mii() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - void DisplayCompleted(const Core::Frontend::MiiParameters& parameters); - -private: - const Core::Frontend::MiiApplet& frontend; - MiiAppletInput input_data{}; - AppletOutputForCharInfoEditing output_data{}; - - bool is_complete = false; - Core::System& system; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp new file mode 100644 index 000000000..8fbde1be4 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp @@ -0,0 +1,139 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_mii_edit.h" +#include "core/hle/service/mii/mii_manager.h" + +namespace Service::AM::Applets { + +MiiEdit::MiiEdit(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::MiiEditApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +MiiEdit::~MiiEdit() = default; + +void MiiEdit::Initialize() { + // Note: MiiEdit is not initialized with common arguments. + // Instead, it is initialized by an AppletInput storage with size 0x100 bytes. + // Do NOT call Applet::Initialize() here. + + const auto storage = broker.PopNormalDataToApplet(); + ASSERT(storage != nullptr); + + const auto applet_input_data = storage->GetData(); + ASSERT(applet_input_data.size() >= sizeof(MiiEditAppletInputCommon)); + + std::memcpy(&applet_input_common, applet_input_data.data(), sizeof(MiiEditAppletInputCommon)); + + LOG_INFO(Service_AM, + "Initializing MiiEdit Applet with MiiEditAppletVersion={} and MiiEditAppletMode={}", + applet_input_common.version, applet_input_common.applet_mode); + + switch (applet_input_common.version) { + case MiiEditAppletVersion::Version3: + ASSERT(applet_input_data.size() == + sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV3)); + std::memcpy(&applet_input_v3, applet_input_data.data() + sizeof(MiiEditAppletInputCommon), + sizeof(MiiEditAppletInputV3)); + break; + case MiiEditAppletVersion::Version4: + ASSERT(applet_input_data.size() == + sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV4)); + std::memcpy(&applet_input_v4, applet_input_data.data() + sizeof(MiiEditAppletInputCommon), + sizeof(MiiEditAppletInputV4)); + break; + default: + UNIMPLEMENTED_MSG("Unknown MiiEditAppletVersion={} with size={}", + applet_input_common.version, applet_input_data.size()); + ASSERT(applet_input_data.size() >= + sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV4)); + std::memcpy(&applet_input_v4, applet_input_data.data() + sizeof(MiiEditAppletInputCommon), + sizeof(MiiEditAppletInputV4)); + break; + } +} + +bool MiiEdit::TransactionComplete() const { + return is_complete; +} + +ResultCode MiiEdit::GetStatus() const { + return ResultSuccess; +} + +void MiiEdit::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void MiiEdit::Execute() { + if (is_complete) { + return; + } + + // This is a default stub for each of the MiiEdit applet modes. + switch (applet_input_common.applet_mode) { + case MiiEditAppletMode::ShowMiiEdit: + case MiiEditAppletMode::AppendMii: + case MiiEditAppletMode::AppendMiiImage: + case MiiEditAppletMode::UpdateMiiImage: + MiiEditOutput(MiiEditResult::Success, 0); + break; + case MiiEditAppletMode::CreateMii: + case MiiEditAppletMode::EditMii: { + Service::Mii::MiiManager mii_manager; + + const MiiEditCharInfo char_info{ + .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii + ? applet_input_v4.char_info.mii_info + : mii_manager.BuildDefault(0)}, + }; + + MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); + break; + } + default: + UNIMPLEMENTED_MSG("Unknown MiiEditAppletMode={}", applet_input_common.applet_mode); + + MiiEditOutput(MiiEditResult::Success, 0); + break; + } +} + +void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) { + const MiiEditAppletOutput applet_output{ + .result{result}, + .index{index}, + }; + + std::vector<u8> out_data(sizeof(MiiEditAppletOutput)); + std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput)); + + is_complete = true; + + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data))); + broker.SignalStateChanged(); +} + +void MiiEdit::MiiEditOutputForCharInfoEditing(MiiEditResult result, + const MiiEditCharInfo& char_info) { + const MiiEditAppletOutputForCharInfoEditing applet_output{ + .result{result}, + .char_info{char_info}, + }; + + std::vector<u8> out_data(sizeof(MiiEditAppletOutputForCharInfoEditing)); + std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutputForCharInfoEditing)); + + is_complete = true; + + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h new file mode 100644 index 000000000..e9ca0e2af --- /dev/null +++ b/src/core/hle/service/am/applets/applet_mii_edit.h @@ -0,0 +1,45 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applet_mii_edit_types.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} // namespace Core + +namespace Service::AM::Applets { + +class MiiEdit final : public Applet { +public: + explicit MiiEdit(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::MiiEditApplet& frontend_); + ~MiiEdit() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void MiiEditOutput(MiiEditResult result, s32 index); + + void MiiEditOutputForCharInfoEditing(MiiEditResult result, const MiiEditCharInfo& char_info); + +private: + const Core::Frontend::MiiEditApplet& frontend; + Core::System& system; + + MiiEditAppletInputCommon applet_input_common{}; + MiiEditAppletInputV3 applet_input_v3{}; + MiiEditAppletInputV4 applet_input_v4{}; + + bool is_complete{false}; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h new file mode 100644 index 000000000..70dea0007 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h @@ -0,0 +1,83 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hle/service/mii/types.h" + +namespace Service::AM::Applets { + +enum class MiiEditAppletVersion : s32 { + Version3 = 0x3, // 1.0.0 - 10.1.1 + Version4 = 0x4, // 10.2.0+ +}; + +// This is nn::mii::AppletMode +enum class MiiEditAppletMode : u32 { + ShowMiiEdit = 0, + AppendMii = 1, + AppendMiiImage = 2, + UpdateMiiImage = 3, + CreateMii = 4, + EditMii = 5, +}; + +enum class MiiEditResult : u32 { + Success, + Cancel, +}; + +struct MiiEditCharInfo { + Service::Mii::MiiInfo mii_info{}; +}; +static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size."); + +struct MiiEditAppletInputCommon { + MiiEditAppletVersion version{}; + MiiEditAppletMode applet_mode{}; +}; +static_assert(sizeof(MiiEditAppletInputCommon) == 0x8, + "MiiEditAppletInputCommon has incorrect size."); + +struct MiiEditAppletInputV3 { + u32 special_mii_key_code{}; + std::array<Common::UUID, 8> valid_uuids{}; + Common::UUID used_uuid{}; + INSERT_PADDING_BYTES(0x64); +}; +static_assert(sizeof(MiiEditAppletInputV3) == 0x100 - sizeof(MiiEditAppletInputCommon), + "MiiEditAppletInputV3 has incorrect size."); + +struct MiiEditAppletInputV4 { + u32 special_mii_key_code{}; + MiiEditCharInfo char_info{}; + INSERT_PADDING_BYTES(0x28); + Common::UUID used_uuid{}; + INSERT_PADDING_BYTES(0x64); +}; +static_assert(sizeof(MiiEditAppletInputV4) == 0x100 - sizeof(MiiEditAppletInputCommon), + "MiiEditAppletInputV4 has incorrect size."); + +// This is nn::mii::AppletOutput +struct MiiEditAppletOutput { + MiiEditResult result{}; + s32 index{}; + INSERT_PADDING_BYTES(0x18); +}; +static_assert(sizeof(MiiEditAppletOutput) == 0x20, "MiiEditAppletOutput has incorrect size."); + +// This is nn::mii::AppletOutputForCharInfoEditing +struct MiiEditAppletOutputForCharInfoEditing { + MiiEditResult result{}; + MiiEditCharInfo char_info{}; + INSERT_PADDING_BYTES(0x24); +}; +static_assert(sizeof(MiiEditAppletOutputForCharInfoEditing) == 0x80, + "MiiEditAppletOutputForCharInfoEditing has incorrect size."); + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.cpp b/src/core/hle/service/am/applets/applet_software_keyboard.cpp index f38f53f69..ee669686c 100644 --- a/src/core/hle/service/am/applets/applet_software_keyboard.cpp +++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp @@ -226,7 +226,7 @@ void SoftwareKeyboard::InitializeForeground() { ASSERT(work_buffer_storage != nullptr); if (swkbd_config_common.initial_string_length == 0) { - InitializeFrontendKeyboard(); + InitializeFrontendNormalKeyboard(); return; } @@ -243,7 +243,7 @@ void SoftwareKeyboard::InitializeForeground() { LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text)); - InitializeFrontendKeyboard(); + InitializeFrontendNormalKeyboard(); } void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) { @@ -480,129 +480,173 @@ void SoftwareKeyboard::ChangeState(SwkbdState state) { ReplyDefault(); } -void SoftwareKeyboard::InitializeFrontendKeyboard() { - if (is_background) { - const auto& appear_arg = swkbd_calc_arg.appear_arg; - - std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - appear_arg.ok_text.data(), appear_arg.ok_text.size()); - - const u32 max_text_length = - appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH - ? appear_arg.max_text_length - : DEFAULT_MAX_TEXT_LENGTH; - - const u32 min_text_length = - appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; - - const s32 initial_cursor_position = - current_cursor_position > 0 ? current_cursor_position : 0; - - const auto text_draw_type = - max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; - - Core::Frontend::KeyboardInitializeParameters initialize_parameters{ - .ok_text{std::move(ok_text)}, - .header_text{}, - .sub_text{}, - .guide_text{}, - .initial_text{current_text}, - .max_text_length{max_text_length}, - .min_text_length{min_text_length}, - .initial_cursor_position{initial_cursor_position}, - .type{appear_arg.type}, - .password_mode{SwkbdPasswordMode::Disabled}, - .text_draw_type{text_draw_type}, - .key_disable_flags{appear_arg.key_disable_flags}, - .use_blur_background{false}, - .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, - .enable_return_button{appear_arg.enable_return_button}, - .disable_cancel_button{appear_arg.disable_cancel_button}, - }; - - frontend.InitializeKeyboard( - true, std::move(initialize_parameters), {}, - [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) { - SubmitTextInline(reply_type, submitted_text, cursor_position); - }); - } else { - std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size()); - - std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size()); - - std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size()); - - std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size()); - - const u32 max_text_length = - swkbd_config_common.max_text_length > 0 && - swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH - ? swkbd_config_common.max_text_length - : DEFAULT_MAX_TEXT_LENGTH; - - const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length - ? swkbd_config_common.min_text_length - : 0; - - const s32 initial_cursor_position = [this] { - switch (swkbd_config_common.initial_cursor_position) { - case SwkbdInitialCursorPosition::Start: - default: - return 0; - case SwkbdInitialCursorPosition::End: - return static_cast<s32>(initial_text.size()); - } - }(); - - const auto text_draw_type = [this, max_text_length] { - switch (swkbd_config_common.text_draw_type) { - case SwkbdTextDrawType::Line: - default: - return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; - case SwkbdTextDrawType::Box: - case SwkbdTextDrawType::DownloadCode: - return swkbd_config_common.text_draw_type; - } - }(); - - const auto enable_return_button = text_draw_type == SwkbdTextDrawType::Box - ? swkbd_config_common.enable_return_button - : false; - - const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227 - ? swkbd_config_new.disable_cancel_button - : false; - - Core::Frontend::KeyboardInitializeParameters initialize_parameters{ - .ok_text{std::move(ok_text)}, - .header_text{std::move(header_text)}, - .sub_text{std::move(sub_text)}, - .guide_text{std::move(guide_text)}, - .initial_text{initial_text}, - .max_text_length{max_text_length}, - .min_text_length{min_text_length}, - .initial_cursor_position{initial_cursor_position}, - .type{swkbd_config_common.type}, - .password_mode{swkbd_config_common.password_mode}, - .text_draw_type{text_draw_type}, - .key_disable_flags{swkbd_config_common.key_disable_flags}, - .use_blur_background{swkbd_config_common.use_blur_background}, - .enable_backspace_button{true}, - .enable_return_button{enable_return_button}, - .disable_cancel_button{disable_cancel_button}, - }; - - frontend.InitializeKeyboard( - false, std::move(initialize_parameters), - [this](SwkbdResult result, std::u16string submitted_text, bool confirmed) { - SubmitTextNormal(result, submitted_text, confirmed); - }, - {}); - } +void SoftwareKeyboard::InitializeFrontendNormalKeyboard() { + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size()); + + std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size()); + + std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size()); + + std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size()); + + const u32 max_text_length = + swkbd_config_common.max_text_length > 0 && + swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? swkbd_config_common.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length + ? swkbd_config_common.min_text_length + : 0; + + const s32 initial_cursor_position = [this] { + switch (swkbd_config_common.initial_cursor_position) { + case SwkbdInitialCursorPosition::Start: + default: + return 0; + case SwkbdInitialCursorPosition::End: + return static_cast<s32>(initial_text.size()); + } + }(); + + const auto text_draw_type = [this, max_text_length] { + switch (swkbd_config_common.text_draw_type) { + case SwkbdTextDrawType::Line: + default: + return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + case SwkbdTextDrawType::Box: + case SwkbdTextDrawType::DownloadCode: + return swkbd_config_common.text_draw_type; + } + }(); + + const auto enable_return_button = + text_draw_type == SwkbdTextDrawType::Box ? swkbd_config_common.enable_return_button : false; + + const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227 + ? swkbd_config_new.disable_cancel_button + : false; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{std::move(ok_text)}, + .header_text{std::move(header_text)}, + .sub_text{std::move(sub_text)}, + .guide_text{std::move(guide_text)}, + .initial_text{initial_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{swkbd_config_common.type}, + .password_mode{swkbd_config_common.password_mode}, + .text_draw_type{text_draw_type}, + .key_disable_flags{swkbd_config_common.key_disable_flags}, + .use_blur_background{swkbd_config_common.use_blur_background}, + .enable_backspace_button{true}, + .enable_return_button{enable_return_button}, + .disable_cancel_button{disable_cancel_button}, + }; + + frontend.InitializeKeyboard( + false, std::move(initialize_parameters), + [this](SwkbdResult result, std::u16string submitted_text, bool confirmed) { + SubmitTextNormal(result, submitted_text, confirmed); + }, + {}); +} + +void SoftwareKeyboard::InitializeFrontendInlineKeyboard( + Core::Frontend::KeyboardInitializeParameters initialize_parameters) { + frontend.InitializeKeyboard( + true, std::move(initialize_parameters), {}, + [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) { + SubmitTextInline(reply_type, submitted_text, cursor_position); + }); +} + +void SoftwareKeyboard::InitializeFrontendInlineKeyboardOld() { + const auto& appear_arg = swkbd_calc_arg_old.appear_arg; + + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + appear_arg.ok_text.data(), appear_arg.ok_text.size()); + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + const s32 initial_cursor_position = current_cursor_position > 0 ? current_cursor_position : 0; + + const auto text_draw_type = + max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{std::move(ok_text)}, + .header_text{}, + .sub_text{}, + .guide_text{}, + .initial_text{current_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{appear_arg.type}, + .password_mode{SwkbdPasswordMode::Disabled}, + .text_draw_type{text_draw_type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .use_blur_background{false}, + .enable_backspace_button{swkbd_calc_arg_old.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + InitializeFrontendInlineKeyboard(std::move(initialize_parameters)); +} + +void SoftwareKeyboard::InitializeFrontendInlineKeyboardNew() { + const auto& appear_arg = swkbd_calc_arg_new.appear_arg; + + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + appear_arg.ok_text.data(), appear_arg.ok_text.size()); + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + const s32 initial_cursor_position = current_cursor_position > 0 ? current_cursor_position : 0; + + const auto text_draw_type = + max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{std::move(ok_text)}, + .header_text{}, + .sub_text{}, + .guide_text{}, + .initial_text{current_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{appear_arg.type}, + .password_mode{SwkbdPasswordMode::Disabled}, + .text_draw_type{text_draw_type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .use_blur_background{false}, + .enable_backspace_button{swkbd_calc_arg_new.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + InitializeFrontendInlineKeyboard(std::move(initialize_parameters)); } void SoftwareKeyboard::ShowNormalKeyboard() { @@ -614,14 +658,21 @@ void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_resul frontend.ShowTextCheckDialog(text_check_result, std::move(text_check_message)); } -void SoftwareKeyboard::ShowInlineKeyboard() { +void SoftwareKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) { + frontend.ShowInlineKeyboard(std::move(appear_parameters)); + + ChangeState(SwkbdState::InitializedIsShown); +} + +void SoftwareKeyboard::ShowInlineKeyboardOld() { if (swkbd_state != SwkbdState::InitializedIsHidden) { return; } ChangeState(SwkbdState::InitializedIsAppearing); - const auto& appear_arg = swkbd_calc_arg.appear_arg; + const auto& appear_arg = swkbd_calc_arg_old.appear_arg; const u32 max_text_length = appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH @@ -634,21 +685,54 @@ void SoftwareKeyboard::ShowInlineKeyboard() { Core::Frontend::InlineAppearParameters appear_parameters{ .max_text_length{max_text_length}, .min_text_length{min_text_length}, - .key_top_scale_x{swkbd_calc_arg.key_top_scale_x}, - .key_top_scale_y{swkbd_calc_arg.key_top_scale_y}, - .key_top_translate_x{swkbd_calc_arg.key_top_translate_x}, - .key_top_translate_y{swkbd_calc_arg.key_top_translate_y}, + .key_top_scale_x{swkbd_calc_arg_old.key_top_scale_x}, + .key_top_scale_y{swkbd_calc_arg_old.key_top_scale_y}, + .key_top_translate_x{swkbd_calc_arg_old.key_top_translate_x}, + .key_top_translate_y{swkbd_calc_arg_old.key_top_translate_y}, .type{appear_arg.type}, .key_disable_flags{appear_arg.key_disable_flags}, - .key_top_as_floating{swkbd_calc_arg.key_top_as_floating}, - .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .key_top_as_floating{swkbd_calc_arg_old.key_top_as_floating}, + .enable_backspace_button{swkbd_calc_arg_old.enable_backspace_button}, .enable_return_button{appear_arg.enable_return_button}, .disable_cancel_button{appear_arg.disable_cancel_button}, }; - frontend.ShowInlineKeyboard(std::move(appear_parameters)); + ShowInlineKeyboard(std::move(appear_parameters)); +} - ChangeState(SwkbdState::InitializedIsShown); +void SoftwareKeyboard::ShowInlineKeyboardNew() { + if (swkbd_state != SwkbdState::InitializedIsHidden) { + return; + } + + ChangeState(SwkbdState::InitializedIsAppearing); + + const auto& appear_arg = swkbd_calc_arg_new.appear_arg; + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + Core::Frontend::InlineAppearParameters appear_parameters{ + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .key_top_scale_x{swkbd_calc_arg_new.key_top_scale_x}, + .key_top_scale_y{swkbd_calc_arg_new.key_top_scale_y}, + .key_top_translate_x{swkbd_calc_arg_new.key_top_translate_x}, + .key_top_translate_y{swkbd_calc_arg_new.key_top_translate_y}, + .type{appear_arg.type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .key_top_as_floating{swkbd_calc_arg_new.key_top_as_floating}, + .enable_backspace_button{swkbd_calc_arg_new.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + ShowInlineKeyboard(std::move(appear_parameters)); } void SoftwareKeyboard::HideInlineKeyboard() { @@ -693,6 +777,8 @@ void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) { void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector<u8>& request_data) { LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented."); + + ReplyReleasedUserWordInfo(); } void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_data) { @@ -702,53 +788,135 @@ void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_dat void SoftwareKeyboard::RequestCalc(const std::vector<u8>& request_data) { LOG_DEBUG(Service_AM, "Processing Request: Calc"); - ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArg)); + ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon)); - std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand), - sizeof(SwkbdCalcArg)); + std::memcpy(&swkbd_calc_arg_common, request_data.data() + sizeof(SwkbdRequestCommand), + sizeof(SwkbdCalcArgCommon)); + + switch (swkbd_calc_arg_common.calc_arg_size) { + case sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgOld): + ASSERT(request_data.size() == + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgOld)); + std::memcpy(&swkbd_calc_arg_old, + request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon), + sizeof(SwkbdCalcArgOld)); + RequestCalcOld(); + break; + case sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew): + ASSERT(request_data.size() == + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew)); + std::memcpy(&swkbd_calc_arg_new, + request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon), + sizeof(SwkbdCalcArgNew)); + RequestCalcNew(); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdCalcArg size={}", swkbd_calc_arg_common.calc_arg_size); + ASSERT(request_data.size() >= + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew)); + std::memcpy(&swkbd_calc_arg_new, + request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon), + sizeof(SwkbdCalcArgNew)); + RequestCalcNew(); + break; + } +} + +void SoftwareKeyboard::RequestCalcOld() { + if (swkbd_calc_arg_common.flags.set_input_text) { + current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_calc_arg_old.input_text.data(), swkbd_calc_arg_old.input_text.size()); + } + + if (swkbd_calc_arg_common.flags.set_cursor_position) { + current_cursor_position = swkbd_calc_arg_old.cursor_position; + } + + if (swkbd_calc_arg_common.flags.set_utf8_mode) { + inline_use_utf8 = swkbd_calc_arg_old.utf8_mode; + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg_common.flags.unset_customize_dic) { + ReplyUnsetCustomizeDic(); + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg_common.flags.unset_user_word_info) { + ReplyReleasedUserWordInfo(); + } + + if (swkbd_state == SwkbdState::NotInitialized && + swkbd_calc_arg_common.flags.set_initialize_arg) { + InitializeFrontendInlineKeyboardOld(); + + ChangeState(SwkbdState::InitializedIsHidden); + + ReplyFinishedInitialize(); + } + + if (!swkbd_calc_arg_common.flags.set_initialize_arg && + (swkbd_calc_arg_common.flags.set_input_text || + swkbd_calc_arg_common.flags.set_cursor_position)) { + InlineTextChanged(); + } + + if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg_common.flags.appear) { + ShowInlineKeyboardOld(); + return; + } + + if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg_common.flags.disappear) { + HideInlineKeyboard(); + return; + } +} - if (swkbd_calc_arg.flags.set_input_text) { +void SoftwareKeyboard::RequestCalcNew() { + if (swkbd_calc_arg_common.flags.set_input_text) { current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_calc_arg.input_text.data(), swkbd_calc_arg.input_text.size()); + swkbd_calc_arg_new.input_text.data(), swkbd_calc_arg_new.input_text.size()); } - if (swkbd_calc_arg.flags.set_cursor_position) { - current_cursor_position = swkbd_calc_arg.cursor_position; + if (swkbd_calc_arg_common.flags.set_cursor_position) { + current_cursor_position = swkbd_calc_arg_new.cursor_position; } - if (swkbd_calc_arg.flags.set_utf8_mode) { - inline_use_utf8 = swkbd_calc_arg.utf8_mode; + if (swkbd_calc_arg_common.flags.set_utf8_mode) { + inline_use_utf8 = swkbd_calc_arg_new.utf8_mode; } if (swkbd_state <= SwkbdState::InitializedIsHidden && - swkbd_calc_arg.flags.unset_customize_dic) { + swkbd_calc_arg_common.flags.unset_customize_dic) { ReplyUnsetCustomizeDic(); } if (swkbd_state <= SwkbdState::InitializedIsHidden && - swkbd_calc_arg.flags.unset_user_word_info) { + swkbd_calc_arg_common.flags.unset_user_word_info) { ReplyReleasedUserWordInfo(); } - if (swkbd_state == SwkbdState::NotInitialized && swkbd_calc_arg.flags.set_initialize_arg) { - InitializeFrontendKeyboard(); + if (swkbd_state == SwkbdState::NotInitialized && + swkbd_calc_arg_common.flags.set_initialize_arg) { + InitializeFrontendInlineKeyboardNew(); ChangeState(SwkbdState::InitializedIsHidden); ReplyFinishedInitialize(); } - if (!swkbd_calc_arg.flags.set_initialize_arg && - (swkbd_calc_arg.flags.set_input_text || swkbd_calc_arg.flags.set_cursor_position)) { + if (!swkbd_calc_arg_common.flags.set_initialize_arg && + (swkbd_calc_arg_common.flags.set_input_text || + swkbd_calc_arg_common.flags.set_cursor_position)) { InlineTextChanged(); } - if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg.flags.appear) { - ShowInlineKeyboard(); + if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg_common.flags.appear) { + ShowInlineKeyboardNew(); return; } - if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg.flags.disappear) { + if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg_common.flags.disappear) { HideInlineKeyboard(); return; } diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.h b/src/core/hle/service/am/applets/applet_software_keyboard.h index a0fddd965..7ee4c5eea 100644 --- a/src/core/hle/service/am/applets/applet_software_keyboard.h +++ b/src/core/hle/service/am/applets/applet_software_keyboard.h @@ -13,6 +13,11 @@ namespace Core { class System; } +namespace Core::Frontend { +struct KeyboardInitializeParameters; +struct InlineAppearParameters; +} // namespace Core::Frontend + namespace Service::AM::Applets { class SoftwareKeyboard final : public Applet { @@ -78,13 +83,22 @@ private: void ChangeState(SwkbdState state); /** - * Signals the frontend to initialize the software keyboard with common parameters. - * This initializes either the normal software keyboard or the inline software keyboard - * depending on the state of is_background. + * Signals the frontend to initialize the normal software keyboard with common parameters. * Note that this does not cause the keyboard to appear. - * Use the respective Show*Keyboard() functions to cause the respective keyboards to appear. + * Use the ShowNormalKeyboard() functions to cause the keyboard to appear. */ - void InitializeFrontendKeyboard(); + void InitializeFrontendNormalKeyboard(); + + /** + * Signals the frontend to initialize the inline software keyboard with common parameters. + * Note that this does not cause the keyboard to appear. + * Use the ShowInlineKeyboard() to cause the keyboard to appear. + */ + void InitializeFrontendInlineKeyboard( + Core::Frontend::KeyboardInitializeParameters initialize_parameters); + + void InitializeFrontendInlineKeyboardOld(); + void InitializeFrontendInlineKeyboardNew(); /// Signals the frontend to show the normal software keyboard. void ShowNormalKeyboard(); @@ -94,7 +108,10 @@ private: std::u16string text_check_message); /// Signals the frontend to show the inline software keyboard. - void ShowInlineKeyboard(); + void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters); + + void ShowInlineKeyboardOld(); + void ShowInlineKeyboardNew(); /// Signals the frontend to hide the inline software keyboard. void HideInlineKeyboard(); @@ -111,6 +128,8 @@ private: void RequestSetUserWordInfo(const std::vector<u8>& request_data); void RequestSetCustomizeDic(const std::vector<u8>& request_data); void RequestCalc(const std::vector<u8>& request_data); + void RequestCalcOld(); + void RequestCalcNew(); void RequestSetCustomizedDictionaries(const std::vector<u8>& request_data); void RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data); void RequestSetChangedStringV2Flag(const std::vector<u8>& request_data); @@ -149,7 +168,9 @@ private: SwkbdState swkbd_state{SwkbdState::NotInitialized}; SwkbdInitializeArg swkbd_initialize_arg; - SwkbdCalcArg swkbd_calc_arg; + SwkbdCalcArgCommon swkbd_calc_arg_common; + SwkbdCalcArgOld swkbd_calc_arg_old; + SwkbdCalcArgNew swkbd_calc_arg_new; bool use_changed_string_v2{false}; bool use_moved_cursor_v2{false}; bool inline_use_utf8{false}; diff --git a/src/core/hle/service/am/applets/applet_software_keyboard_types.h b/src/core/hle/service/am/applets/applet_software_keyboard_types.h index 21aa8e800..de1ca7aa5 100644 --- a/src/core/hle/service/am/applets/applet_software_keyboard_types.h +++ b/src/core/hle/service/am/applets/applet_software_keyboard_types.h @@ -10,6 +10,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "common/uuid.h" namespace Service::AM::Applets { @@ -216,7 +217,7 @@ struct SwkbdInitializeArg { }; static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size."); -struct SwkbdAppearArg { +struct SwkbdAppearArgOld { SwkbdType type{}; std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{}; char16_t left_optional_symbol_key{}; @@ -229,19 +230,76 @@ struct SwkbdAppearArg { bool enable_return_button{}; INSERT_PADDING_BYTES(3); u32 flags{}; - INSERT_PADDING_WORDS(6); + bool is_use_save_data{}; + INSERT_PADDING_BYTES(7); + Common::UUID user_id{}; }; -static_assert(sizeof(SwkbdAppearArg) == 0x48, "SwkbdAppearArg has incorrect size."); +static_assert(sizeof(SwkbdAppearArgOld) == 0x48, "SwkbdAppearArg has incorrect size."); -struct SwkbdCalcArg { +struct SwkbdAppearArgNew { + SwkbdType type{}; + std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + bool disable_cancel_button{}; + SwkbdKeyDisableFlags key_disable_flags{}; + u32 max_text_length{}; + u32 min_text_length{}; + bool enable_return_button{}; + INSERT_PADDING_BYTES(3); + u32 flags{}; + bool is_use_save_data{}; + INSERT_PADDING_BYTES(7); + Common::UUID user_id{}; + u64 start_sampling_number{}; + INSERT_PADDING_WORDS(8); +}; +static_assert(sizeof(SwkbdAppearArgNew) == 0x70, "SwkbdAppearArg has incorrect size."); + +struct SwkbdCalcArgCommon { u32 unknown{}; u16 calc_arg_size{}; INSERT_PADDING_BYTES(2); SwkbdCalcArgFlags flags{}; SwkbdInitializeArg initialize_arg{}; +}; +static_assert(sizeof(SwkbdCalcArgCommon) == 0x18, "SwkbdCalcArgCommon has incorrect size."); + +struct SwkbdCalcArgOld { + f32 volume{}; + s32 cursor_position{}; + SwkbdAppearArgOld appear_arg{}; + std::array<char16_t, 0x1FA> input_text{}; + bool utf8_mode{}; + INSERT_PADDING_BYTES(1); + bool enable_backspace_button{}; + INSERT_PADDING_BYTES(3); + bool key_top_as_floating{}; + bool footer_scalable{}; + bool alpha_enabled_in_input_mode{}; + u8 input_mode_fade_type{}; + bool disable_touch{}; + bool disable_hardware_keyboard{}; + INSERT_PADDING_BYTES(8); + f32 key_top_scale_x{}; + f32 key_top_scale_y{}; + f32 key_top_translate_x{}; + f32 key_top_translate_y{}; + f32 key_top_bg_alpha{}; + f32 footer_bg_alpha{}; + f32 balloon_scale{}; + INSERT_PADDING_WORDS(4); + u8 se_group{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdCalcArgOld) == 0x4A0 - sizeof(SwkbdCalcArgCommon), + "SwkbdCalcArgOld has incorrect size."); + +struct SwkbdCalcArgNew { + SwkbdAppearArgNew appear_arg{}; f32 volume{}; s32 cursor_position{}; - SwkbdAppearArg appear_arg{}; std::array<char16_t, 0x1FA> input_text{}; bool utf8_mode{}; INSERT_PADDING_BYTES(1); @@ -264,8 +322,10 @@ struct SwkbdCalcArg { INSERT_PADDING_WORDS(4); u8 se_group{}; INSERT_PADDING_BYTES(3); + INSERT_PADDING_WORDS(8); }; -static_assert(sizeof(SwkbdCalcArg) == 0x4A0, "SwkbdCalcArg has incorrect size."); +static_assert(sizeof(SwkbdCalcArgNew) == 0x4E8 - sizeof(SwkbdCalcArgCommon), + "SwkbdCalcArgNew has incorrect size."); struct SwkbdChangedStringArg { u32 text_length{}; diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index bb5cb61be..a4b3fb187 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -446,6 +446,14 @@ void WebBrowser::ExecuteLogin() { } void WebBrowser::ExecuteOffline() { + // TODO (Morph): This is a hack for WebSession foreground web applets such as those used by + // Super Mario 3D All-Stars. + // TODO (Morph): Implement WebSession. + if (applet_mode == LibraryAppletMode::AllForegroundInitiallyHidden) { + LOG_WARNING(Service_AM, "WebSession is not implemented"); + return; + } + const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document)); if (!Common::FS::Exists(main_url)) { diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 79e62679d..1f4c9786a 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -9,7 +9,7 @@ #include "core/frontend/applets/controller.h" #include "core/frontend/applets/error.h" #include "core/frontend/applets/general_frontend.h" -#include "core/frontend/applets/mii.h" +#include "core/frontend/applets/mii_edit.h" #include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/web_browser.h" @@ -20,7 +20,7 @@ #include "core/hle/service/am/applets/applet_controller.h" #include "core/hle/service/am/applets/applet_error.h" #include "core/hle/service/am/applets/applet_general_backend.h" -#include "core/hle/service/am/applets/applet_mii.h" +#include "core/hle/service/am/applets/applet_mii_edit.h" #include "core/hle/service/am/applets/applet_profile_select.h" #include "core/hle/service/am/applets/applet_software_keyboard.h" #include "core/hle/service/am/applets/applet_web_browser.h" @@ -173,12 +173,12 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, + MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet, - MiiApplet mii_applet, PhotoViewer photo_viewer_, - ProfileSelect profile_select_, + PhotoViewer photo_viewer_, ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_, WebBrowser web_browser_) : controller{std::move(controller_applet)}, error{std::move(error_applet)}, - parental_controls{std::move(parental_controls_applet)}, mii{std::move(mii_applet)}, + mii_edit{std::move(mii_edit_)}, parental_controls{std::move(parental_controls_applet)}, photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)}, software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {} @@ -205,12 +205,12 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { frontend.error = std::move(set.error); } - if (set.parental_controls != nullptr) { - frontend.parental_controls = std::move(set.parental_controls); + if (set.mii_edit != nullptr) { + frontend.mii_edit = std::move(set.mii_edit); } - if (set.mii != nullptr) { - frontend.mii = std::move(set.mii); + if (set.parental_controls != nullptr) { + frontend.parental_controls = std::move(set.parental_controls); } if (set.photo_viewer != nullptr) { @@ -245,15 +245,15 @@ void AppletManager::SetDefaultAppletsIfMissing() { frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>(); } + if (frontend.mii_edit == nullptr) { + frontend.mii_edit = std::make_unique<Core::Frontend::DefaultMiiEditApplet>(); + } + if (frontend.parental_controls == nullptr) { frontend.parental_controls = std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); } - if (frontend.mii == nullptr) { - frontend.mii = std::make_unique<Core::Frontend::DefaultMiiApplet>(); - } - if (frontend.photo_viewer == nullptr) { frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>(); } @@ -289,7 +289,7 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, LibraryAppletMode case AppletId::SoftwareKeyboard: return std::make_shared<SoftwareKeyboard>(system, mode, *frontend.software_keyboard); case AppletId::MiiEdit: - return std::make_shared<Mii>(system, mode, *frontend.mii); + return std::make_shared<MiiEdit>(system, mode, *frontend.mii_edit); case AppletId::Web: case AppletId::Shop: case AppletId::OfflineWeb: diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index 0c44aec79..50a7bdceb 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -20,8 +20,8 @@ namespace Core::Frontend { class ControllerApplet; class ECommerceApplet; class ErrorApplet; +class MiiEditApplet; class ParentalControlsApplet; -class MiiApplet; class PhotoViewerApplet; class ProfileSelectApplet; class SoftwareKeyboardApplet; @@ -179,8 +179,8 @@ protected: struct AppletFrontendSet { using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>; using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>; + using MiiEdit = std::unique_ptr<Core::Frontend::MiiEditApplet>; using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; - using MiiApplet = std::unique_ptr<Core::Frontend::MiiApplet>; using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>; using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>; using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>; @@ -188,7 +188,7 @@ struct AppletFrontendSet { AppletFrontendSet(); AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, - ParentalControlsApplet parental_controls_applet, MiiApplet mii_applet, + MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_, ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_, WebBrowser web_browser_); ~AppletFrontendSet(); @@ -201,8 +201,8 @@ struct AppletFrontendSet { ControllerApplet controller; ErrorApplet error; + MiiEdit mii_edit; ParentalControlsApplet parental_controls; - MiiApplet mii; PhotoViewer photo_viewer; ProfileSelect profile_select; SoftwareKeyboard software_keyboard; diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index affa7971c..a72956a28 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -41,9 +41,10 @@ public: explicit IAudioOut(Core::System& system_, AudoutParams audio_params_, AudioCore::AudioOut& audio_core_, std::string&& device_name_, std::string&& unique_name) - : ServiceFramework{system_, "IAudioOut"}, audio_core{audio_core_}, - device_name{std::move(device_name_)}, audio_params{audio_params_}, - main_memory{system.Memory()}, service_context{system_, "IAudioOut"} { + : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, + audio_core{audio_core_}, device_name{std::move(device_name_)}, + audio_params{audio_params_}, main_memory{system.Memory()}, service_context{system_, + "IAudioOut"} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index f45e5cecc..d4ffeb21d 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -24,7 +24,8 @@ public: explicit IAudioRenderer(Core::System& system_, const AudioCommon::AudioRendererParameter& audren_params, const std::size_t instance_number) - : ServiceFramework{system_, "IAudioRenderer"}, service_context{system_, "IAudioRenderer"} { + : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew}, + service_context{system_, "IAudioRenderer"} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 3703ca4c6..4208337db 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -174,7 +174,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, ASSERT_MSG(dest != nullptr, "Newly created file with success cannot be found."); ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(), - "Could not write all of the bytes but everything else has succeded."); + "Could not write all of the bytes but everything else has succeeded."); if (!src->GetContainingDirectory()->DeleteFile(Common::FS::GetFilename(src_path))) { // TODO(DarkLordZach): Find a better error code for this diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index b087e7bba..c07929ab8 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -58,7 +58,8 @@ enum class FileSystemType : u8 { class IStorage final : public ServiceFramework<IStorage> { public: explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_) - : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) { + : ServiceFramework{system_, "IStorage", ServiceThreadType::CreateNew}, + backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, @@ -116,7 +117,8 @@ private: class IFile final : public ServiceFramework<IFile> { public: explicit IFile(Core::System& system_, FileSys::VirtualFile backend_) - : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) { + : ServiceFramework{system_, "IFile", ServiceThreadType::CreateNew}, + backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, @@ -252,7 +254,8 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec class IDirectory final : public ServiceFramework<IDirectory> { public: explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_) - : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) { + : ServiceFramework{system_, "IDirectory", ServiceThreadType::CreateNew}, + backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IDirectory::Read, "Read"}, {1, &IDirectory::GetEntryCount, "GetEntryCount"}, @@ -308,8 +311,8 @@ private: class IFileSystem final : public ServiceFramework<IFileSystem> { public: explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_) - : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move( - size_)} { + : ServiceFramework{system_, "IFileSystem", ServiceThreadType::CreateNew}, + backend{std::move(backend_)}, size{std::move(size_)} { static const FunctionInfo functions[] = { {0, &IFileSystem::CreateFile, "CreateFile"}, {1, &IFileSystem::DeleteFile, "DeleteFile"}, diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index e5c951e06..68407e39a 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -17,6 +17,7 @@ #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/errors.h" #include "core/hle/service/kernel_helpers.h" namespace Service::HID { @@ -48,15 +49,17 @@ bool Controller_NPad::IsNpadIdValid(Core::HID::NpadIdType npad_id) { } bool Controller_NPad::IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle) { - return IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)) && - device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType && - device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; + const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)); + const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType; + const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; + return npad_id && npad_type && device_index; } bool Controller_NPad::IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle) { - return IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)) && - device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType && - device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; + const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)); + const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType; + const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; + return npad_id && npad_type && device_index; } Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_, @@ -262,11 +265,6 @@ void Controller_NPad::OnInit() { service_context.CreateEvent(fmt::format("npad:NpadStyleSetChanged_{}", i)); } - if (hid_core.GetSupportedStyleTag().raw == Core::HID::NpadStyleSet::None) { - // We want to support all controllers - hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); - } - supported_npad_id_types.resize(npad_id_list.size()); std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), npad_id_list.size() * sizeof(Core::HID::NpadIdType)); @@ -288,14 +286,6 @@ void Controller_NPad::OnInit() { WriteEmptyEntry(npad); } } - - // Connect controllers - for (auto& controller : controller_data) { - const auto& device = controller.device; - if (device->IsConnected()) { - AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); - } - } } void Controller_NPad::WriteEmptyEntry(NpadInternalState& npad) { @@ -320,6 +310,7 @@ void Controller_NPad::WriteEmptyEntry(NpadInternalState& npad) { } void Controller_NPad::OnRelease() { + is_controller_initialized = false; for (std::size_t i = 0; i < controller_data.size(); ++i) { auto& controller = controller_data[i]; service_context.CloseEvent(controller.styleset_changed_event); @@ -330,7 +321,7 @@ void Controller_NPad::OnRelease() { } void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { - std::lock_guard lock{mutex}; + std::scoped_lock lock{mutex}; auto& controller = GetControllerFromNpadIdType(npad_id); const auto controller_type = controller.device->GetNpadStyleIndex(); if (!controller.device->IsConnected()) { @@ -651,9 +642,27 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { hid_core.SetSupportedStyleTag(style_set); + + if (is_controller_initialized) { + return; + } + + // Once SetSupportedStyleSet is called controllers are fully initialized + is_controller_initialized = true; + + // Connect all active controllers + for (auto& controller : controller_data) { + const auto& device = controller.device; + if (device->IsConnected()) { + AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); + } + } } Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { + if (!is_controller_initialized) { + return {Core::HID::NpadStyleSet::None}; + } return hid_core.GetSupportedStyleTag(); } @@ -1001,87 +1010,271 @@ void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { WriteEmptyEntry(controller.shared_memory_entry); } -void Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, - GyroscopeZeroDriftMode drift_mode) { +ResultCode Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + GyroscopeZeroDriftMode drift_mode) { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - return; + return NpadInvalidHandle; + } + + auto& controller = GetControllerFromHandle(sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + controller.sixaxis_fullkey.gyroscope_zero_drift_mode = drift_mode; + break; + case Core::HID::NpadStyleIndex::Handheld: + controller.sixaxis_handheld.gyroscope_zero_drift_mode = drift_mode; + break; + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::GameCube: + case Core::HID::NpadStyleIndex::Pokeball: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + controller.sixaxis_dual_left.gyroscope_zero_drift_mode = drift_mode; + break; + } + controller.sixaxis_dual_right.gyroscope_zero_drift_mode = drift_mode; + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + controller.sixaxis_left.gyroscope_zero_drift_mode = drift_mode; + break; + case Core::HID::NpadStyleIndex::JoyconRight: + controller.sixaxis_right.gyroscope_zero_drift_mode = drift_mode; + break; + default: + LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); + return NpadInvalidHandle; + } + + return ResultSuccess; +} + +ResultCode Controller_NPad::GetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + GyroscopeZeroDriftMode& drift_mode) const { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + return NpadInvalidHandle; } + auto& controller = GetControllerFromHandle(sixaxis_handle); - controller.gyroscope_zero_drift_mode = drift_mode; + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + drift_mode = controller.sixaxis_fullkey.gyroscope_zero_drift_mode; + break; + case Core::HID::NpadStyleIndex::Handheld: + drift_mode = controller.sixaxis_handheld.gyroscope_zero_drift_mode; + break; + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::GameCube: + case Core::HID::NpadStyleIndex::Pokeball: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + drift_mode = controller.sixaxis_dual_left.gyroscope_zero_drift_mode; + break; + } + drift_mode = controller.sixaxis_dual_right.gyroscope_zero_drift_mode; + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + drift_mode = controller.sixaxis_left.gyroscope_zero_drift_mode; + break; + case Core::HID::NpadStyleIndex::JoyconRight: + drift_mode = controller.sixaxis_right.gyroscope_zero_drift_mode; + break; + default: + LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); + return NpadInvalidHandle; + } + + return ResultSuccess; } -Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMode( - Core::HID::SixAxisSensorHandle sixaxis_handle) const { +ResultCode Controller_NPad::IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool& is_at_rest) const { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - // Return the default value - return GyroscopeZeroDriftMode::Standard; + return NpadInvalidHandle; } const auto& controller = GetControllerFromHandle(sixaxis_handle); - return controller.gyroscope_zero_drift_mode; + is_at_rest = controller.sixaxis_at_rest; + return ResultSuccess; } -bool Controller_NPad::IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const { +ResultCode Controller_NPad::IsFirmwareUpdateAvailableForSixAxisSensor( + Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_firmware_available) const { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - // Return the default value - return true; + return NpadInvalidHandle; } - const auto& controller = GetControllerFromHandle(sixaxis_handle); - return controller.sixaxis_at_rest; + + // We don't support joycon firmware updates + is_firmware_available = false; + return ResultSuccess; } -void Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, - bool sixaxis_status) { +ResultCode Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool sixaxis_status) { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - return; + return NpadInvalidHandle; } auto& controller = GetControllerFromHandle(sixaxis_handle); controller.sixaxis_sensor_enabled = sixaxis_status; + return ResultSuccess; } -void Controller_NPad::SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, - bool sixaxis_fusion_status) { +ResultCode Controller_NPad::IsSixAxisSensorFusionEnabled( + Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_fusion_enabled) const { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - return; + return NpadInvalidHandle; } + auto& controller = GetControllerFromHandle(sixaxis_handle); - controller.sixaxis_fusion_enabled = sixaxis_fusion_status; -} + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + is_fusion_enabled = controller.sixaxis_fullkey.is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::Handheld: + is_fusion_enabled = controller.sixaxis_handheld.is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::GameCube: + case Core::HID::NpadStyleIndex::Pokeball: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + is_fusion_enabled = controller.sixaxis_dual_left.is_fusion_enabled; + break; + } + is_fusion_enabled = controller.sixaxis_dual_right.is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + is_fusion_enabled = controller.sixaxis_left.is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::JoyconRight: + is_fusion_enabled = controller.sixaxis_right.is_fusion_enabled; + break; + default: + LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); + return NpadInvalidHandle; + } -void Controller_NPad::SetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle, - Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) { + return ResultSuccess; +} +ResultCode Controller_NPad::SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool is_fusion_enabled) { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - return; + return NpadInvalidHandle; } + auto& controller = GetControllerFromHandle(sixaxis_handle); - controller.sixaxis_fusion = sixaxis_fusion_parameters; + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + controller.sixaxis_fullkey.is_fusion_enabled = is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::Handheld: + controller.sixaxis_handheld.is_fusion_enabled = is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::GameCube: + case Core::HID::NpadStyleIndex::Pokeball: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + controller.sixaxis_dual_left.is_fusion_enabled = is_fusion_enabled; + break; + } + controller.sixaxis_dual_right.is_fusion_enabled = is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + controller.sixaxis_left.is_fusion_enabled = is_fusion_enabled; + break; + case Core::HID::NpadStyleIndex::JoyconRight: + controller.sixaxis_right.is_fusion_enabled = is_fusion_enabled; + break; + default: + LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); + return NpadInvalidHandle; + } + + return ResultSuccess; } -Core::HID::SixAxisSensorFusionParameters Controller_NPad::GetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle) { +ResultCode Controller_NPad::SetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle, + Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - // Since these parameters are unknow just return zeros - return {}; + return NpadInvalidHandle; + } + const auto param1 = sixaxis_fusion_parameters.parameter1; + if (param1 < 0.0f || param1 > 1.0f) { + return InvalidSixAxisFusionRange; } + auto& controller = GetControllerFromHandle(sixaxis_handle); - return controller.sixaxis_fusion; + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + controller.sixaxis_fullkey.fusion = sixaxis_fusion_parameters; + break; + case Core::HID::NpadStyleIndex::Handheld: + controller.sixaxis_handheld.fusion = sixaxis_fusion_parameters; + break; + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::GameCube: + case Core::HID::NpadStyleIndex::Pokeball: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + controller.sixaxis_dual_left.fusion = sixaxis_fusion_parameters; + break; + } + controller.sixaxis_dual_right.fusion = sixaxis_fusion_parameters; + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + controller.sixaxis_left.fusion = sixaxis_fusion_parameters; + break; + case Core::HID::NpadStyleIndex::JoyconRight: + controller.sixaxis_right.fusion = sixaxis_fusion_parameters; + break; + default: + LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); + return NpadInvalidHandle; + } + + return ResultSuccess; } -void Controller_NPad::ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle) { +ResultCode Controller_NPad::GetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle, + Core::HID::SixAxisSensorFusionParameters& parameters) const { if (!IsDeviceHandleValid(sixaxis_handle)) { LOG_ERROR(Service_HID, "Invalid handle"); - return; + return NpadInvalidHandle; } - auto& controller = GetControllerFromHandle(sixaxis_handle); - // Since these parameters are unknow just fill with zeros - controller.sixaxis_fusion = {}; + + const auto& controller = GetControllerFromHandle(sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + parameters = controller.sixaxis_fullkey.fusion; + break; + case Core::HID::NpadStyleIndex::Handheld: + parameters = controller.sixaxis_handheld.fusion; + break; + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::GameCube: + case Core::HID::NpadStyleIndex::Pokeball: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + parameters = controller.sixaxis_dual_left.fusion; + break; + } + parameters = controller.sixaxis_dual_right.fusion; + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + parameters = controller.sixaxis_left.fusion; + break; + case Core::HID::NpadStyleIndex::JoyconRight: + parameters = controller.sixaxis_right.fusion; + break; + default: + LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); + return NpadInvalidHandle; + } + + return ResultSuccess; } void Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 3287cf435..f7313711f 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -28,7 +28,9 @@ class KReadableEvent; namespace Service::KernelHelpers { class ServiceContext; -} +} // namespace Service::KernelHelpers + +union ResultCode; namespace Service::HID { @@ -143,20 +145,26 @@ public: void DisconnectNpad(Core::HID::NpadIdType npad_id); - void SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, - GyroscopeZeroDriftMode drift_mode); - GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode( - Core::HID::SixAxisSensorHandle sixaxis_handle) const; - bool IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const; - void SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, bool sixaxis_status); - void SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, - bool sixaxis_fusion_status); - void SetSixAxisFusionParameters( + ResultCode SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + GyroscopeZeroDriftMode drift_mode); + ResultCode GetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + GyroscopeZeroDriftMode& drift_mode) const; + ResultCode IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool& is_at_rest) const; + ResultCode IsFirmwareUpdateAvailableForSixAxisSensor( + Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_firmware_available) const; + ResultCode SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool sixaxis_status); + ResultCode IsSixAxisSensorFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool& is_fusion_enabled) const; + ResultCode SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool is_fusion_enabled); + ResultCode SetSixAxisFusionParameters( Core::HID::SixAxisSensorHandle sixaxis_handle, Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters); - Core::HID::SixAxisSensorFusionParameters GetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle); - void ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle); + ResultCode GetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle, + Core::HID::SixAxisSensorFusionParameters& parameters) const; Core::HID::LedPattern GetLedPattern(Core::HID::NpadIdType npad_id); bool IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id) const; void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, @@ -191,16 +199,16 @@ private: // This is nn::hid::detail::NpadFullKeyColorState struct NpadFullKeyColorState { - ColorAttribute attribute; - Core::HID::NpadControllerColor fullkey; + ColorAttribute attribute{ColorAttribute::NoController}; + Core::HID::NpadControllerColor fullkey{}; }; static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size"); // This is nn::hid::detail::NpadJoyColorState struct NpadJoyColorState { - ColorAttribute attribute; - Core::HID::NpadControllerColor left; - Core::HID::NpadControllerColor right; + ColorAttribute attribute{ColorAttribute::NoController}; + Core::HID::NpadControllerColor left{}; + Core::HID::NpadControllerColor right{}; }; static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size"); @@ -226,11 +234,11 @@ private: // This is nn::hid::NpadPalmaState // This is nn::hid::NpadSystemExtState struct NPadGenericState { - s64_le sampling_number; - Core::HID::NpadButtonState npad_buttons; - Core::HID::AnalogStickState l_stick; - Core::HID::AnalogStickState r_stick; - NpadAttribute connection_status; + s64_le sampling_number{}; + Core::HID::NpadButtonState npad_buttons{}; + Core::HID::AnalogStickState l_stick{}; + Core::HID::AnalogStickState r_stick{}; + NpadAttribute connection_status{}; INSERT_PADDING_BYTES(4); // Reserved }; static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size"); @@ -253,7 +261,7 @@ private: Common::Vec3f gyro{}; Common::Vec3f rotation{}; std::array<Common::Vec3f, 3> orientation{}; - SixAxisSensorAttribute attribute; + SixAxisSensorAttribute attribute{}; INSERT_PADDING_BYTES(4); // Reserved }; static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size"); @@ -325,11 +333,11 @@ private: // This is nn::hid::detail::NfcXcdDeviceHandleStateImpl struct NfcXcdDeviceHandleStateImpl { - u64 handle; - bool is_available; - bool is_activated; + u64 handle{}; + bool is_available{}; + bool is_activated{}; INSERT_PADDING_BYTES(0x6); // Reserved - u64 sampling_number; + u64 sampling_number{}; }; static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18, "NfcXcdDeviceHandleStateImpl is an invalid size"); @@ -366,8 +374,8 @@ private: }; struct AppletFooterUi { - AppletFooterUiAttributes attributes; - AppletFooterUiType type; + AppletFooterUiAttributes attributes{}; + AppletFooterUiType type{AppletFooterUiType::None}; INSERT_PADDING_BYTES(0x5B); // Reserved }; static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); @@ -404,41 +412,41 @@ private: // This is nn::hid::detail::NpadInternalState struct NpadInternalState { - Core::HID::NpadStyleTag style_tag; - NpadJoyAssignmentMode assignment_mode; - NpadFullKeyColorState fullkey_color; - NpadJoyColorState joycon_color; - Lifo<NPadGenericState, hid_entry_count> fullkey_lifo; - Lifo<NPadGenericState, hid_entry_count> handheld_lifo; - Lifo<NPadGenericState, hid_entry_count> joy_dual_lifo; - Lifo<NPadGenericState, hid_entry_count> joy_left_lifo; - Lifo<NPadGenericState, hid_entry_count> joy_right_lifo; - Lifo<NPadGenericState, hid_entry_count> palma_lifo; - Lifo<NPadGenericState, hid_entry_count> system_ext_lifo; - Lifo<SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo; - Lifo<SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo; - Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo; - Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo; - Lifo<SixAxisSensorState, hid_entry_count> sixaxis_left_lifo; - Lifo<SixAxisSensorState, hid_entry_count> sixaxis_right_lifo; - DeviceType device_type; + Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; + NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual}; + NpadFullKeyColorState fullkey_color{}; + NpadJoyColorState joycon_color{}; + Lifo<NPadGenericState, hid_entry_count> fullkey_lifo{}; + Lifo<NPadGenericState, hid_entry_count> handheld_lifo{}; + Lifo<NPadGenericState, hid_entry_count> joy_dual_lifo{}; + Lifo<NPadGenericState, hid_entry_count> joy_left_lifo{}; + Lifo<NPadGenericState, hid_entry_count> joy_right_lifo{}; + Lifo<NPadGenericState, hid_entry_count> palma_lifo{}; + Lifo<NPadGenericState, hid_entry_count> system_ext_lifo{}; + Lifo<SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo{}; + Lifo<SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo{}; + Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo{}; + Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo{}; + Lifo<SixAxisSensorState, hid_entry_count> sixaxis_left_lifo{}; + Lifo<SixAxisSensorState, hid_entry_count> sixaxis_right_lifo{}; + DeviceType device_type{}; INSERT_PADDING_BYTES(0x4); // Reserved - NPadSystemProperties system_properties; - NpadSystemButtonProperties button_properties; - Core::HID::NpadBatteryLevel battery_level_dual; - Core::HID::NpadBatteryLevel battery_level_left; - Core::HID::NpadBatteryLevel battery_level_right; + NPadSystemProperties system_properties{}; + NpadSystemButtonProperties button_properties{}; + Core::HID::NpadBatteryLevel battery_level_dual{}; + Core::HID::NpadBatteryLevel battery_level_left{}; + Core::HID::NpadBatteryLevel battery_level_right{}; union { - Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo{}; - AppletFooterUi applet_footer; + AppletFooterUi applet_footer{}; + Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo; }; INSERT_PADDING_BYTES(0x20); // Unknown - Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo; - NpadLarkType lark_type_l_and_main; - NpadLarkType lark_type_r; - NpadLuciaType lucia_type; - NpadLagonType lagon_type; - NpadLagerType lager_type; + Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; + NpadLarkType lark_type_l_and_main{}; + NpadLarkType lark_type_r{}; + NpadLuciaType lucia_type{}; + NpadLagonType lagon_type{}; + NpadLagerType lager_type{}; // FW 13.x Investigate there is some sort of bitflag related to joycons INSERT_PADDING_BYTES(0x4); INSERT_PADDING_BYTES(0xc08); // Unknown @@ -451,6 +459,12 @@ private: std::chrono::steady_clock::time_point last_vibration_timepoint{}; }; + struct SixaxisParameters { + bool is_fusion_enabled{true}; + Core::HID::SixAxisSensorFusionParameters fusion{}; + GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; + }; + struct NpadControllerData { Core::HID::EmulatedController* device; Kernel::KEvent* styleset_changed_event{}; @@ -467,9 +481,12 @@ private: // Motion parameters bool sixaxis_at_rest{true}; bool sixaxis_sensor_enabled{true}; - bool sixaxis_fusion_enabled{false}; - Core::HID::SixAxisSensorFusionParameters sixaxis_fusion{}; - GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; + SixaxisParameters sixaxis_fullkey{}; + SixaxisParameters sixaxis_handheld{}; + SixaxisParameters sixaxis_dual_left{}; + SixaxisParameters sixaxis_dual_right{}; + SixaxisParameters sixaxis_left{}; + SixaxisParameters sixaxis_right{}; // Current pad state NPadGenericState npad_pad_state{}; @@ -481,6 +498,7 @@ private: SixAxisSensorState sixaxis_dual_right_state{}; SixAxisSensorState sixaxis_left_lifo_state{}; SixAxisSensorState sixaxis_right_lifo_state{}; + int callback_key; }; @@ -511,7 +529,8 @@ private: NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; NpadCommunicationMode communication_mode{NpadCommunicationMode::Default}; bool permit_vibration_session_enabled{false}; - bool analog_stick_use_center_clamp{}; + bool analog_stick_use_center_clamp{false}; bool is_in_lr_assignment_mode{false}; + bool is_controller_initialized{false}; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h index 3583642e7..7c5fb3e52 100644 --- a/src/core/hle/service/hid/errors.h +++ b/src/core/hle/service/hid/errors.h @@ -8,6 +8,8 @@ namespace Service::HID { -constexpr ResultCode ERR_NPAD_NOT_CONNECTED{ErrorModule::HID, 710}; +constexpr ResultCode NpadInvalidHandle{ErrorModule::HID, 100}; +constexpr ResultCode InvalidSixAxisFusionRange{ErrorModule::HID, 423}; +constexpr ResultCode NpadNotConnected{ErrorModule::HID, 710}; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index d9202ea6c..baf21df62 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -16,6 +16,7 @@ #include "core/hle/kernel/kernel.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid.h" +#include "core/hle/service/hid/hidbus.h" #include "core/hle/service/hid/irs.h" #include "core/hle/service/hid/xcd.h" #include "core/memory.h" @@ -247,7 +248,7 @@ Hid::Hid(Core::System& system_) {65, nullptr, "GetJoySixAxisSensorLifoHandle"}, {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, - {68, nullptr, "IsSixAxisSensorFusionEnabled"}, + {68, &Hid::IsSixAxisSensorFusionEnabled, "IsSixAxisSensorFusionEnabled"}, {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"}, {70, &Hid::SetSixAxisSensorFusionParameters, "SetSixAxisSensorFusionParameters"}, {71, &Hid::GetSixAxisSensorFusionParameters, "GetSixAxisSensorFusionParameters"}, @@ -527,8 +528,8 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSixAxisEnabled(parameters.sixaxis_handle, true); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetSixAxisEnabled(parameters.sixaxis_handle, true); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -536,7 +537,7 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { @@ -550,8 +551,8 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSixAxisEnabled(parameters.sixaxis_handle, false); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetSixAxisEnabled(parameters.sixaxis_handle, false); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -559,7 +560,33 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); +} + +void Hid::IsSixAxisSensorFusionEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + bool is_enabled{}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.IsSixAxisSensorFusionEnabled(parameters.sixaxis_handle, is_enabled); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(is_enabled); } void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { @@ -574,9 +601,9 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSixAxisFusionEnabled(parameters.sixaxis_handle, - parameters.enable_sixaxis_sensor_fusion); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetSixAxisFusionEnabled(parameters.sixaxis_handle, + parameters.enable_sixaxis_sensor_fusion); LOG_DEBUG(Service_HID, "called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, " @@ -586,7 +613,7 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { @@ -601,8 +628,9 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, parameter1={}, " @@ -612,7 +640,7 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { parameters.sixaxis_fusion.parameter2, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { @@ -626,9 +654,11 @@ void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - const auto sixaxis_fusion_parameters = - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetSixAxisFusionParameters(parameters.sixaxis_handle); + Core::HID::SixAxisSensorFusionParameters fusion_parameters{}; + const auto& controller = + GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.GetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -636,8 +666,8 @@ void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - rb.PushRaw(sixaxis_fusion_parameters); + rb.Push(result); + rb.PushRaw(fusion_parameters); } void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { @@ -651,8 +681,15 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .ResetSixAxisFusionParameters(parameters.sixaxis_handle); + // Since these parameters are unknow just use what HW outputs + const Core::HID::SixAxisSensorFusionParameters fusion_parameters{ + .parameter1 = 0.03f, + .parameter2 = 0.4f, + }; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result1 = + controller.SetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters); + const auto result2 = controller.SetSixAxisFusionEnabled(parameters.sixaxis_handle, true); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -660,6 +697,14 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; + if (result1.IsError()) { + rb.Push(result1); + return; + } + if (result2.IsError()) { + rb.Push(result2); + return; + } rb.Push(ResultSuccess); } @@ -669,8 +714,8 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, " @@ -679,7 +724,7 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { drift_mode, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { @@ -693,15 +738,18 @@ void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; + auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.GetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode); + LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetGyroscopeZeroDriftMode(parameters.sixaxis_handle)); + rb.Push(result); + rb.PushEnum(drift_mode); } void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { @@ -714,10 +762,10 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw<Parameters>()}; - const auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode); + const auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -725,7 +773,7 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { @@ -739,15 +787,18 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; + bool is_at_rest{}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.IsSixAxisSensorAtRest(parameters.sixaxis_handle, is_at_rest); + LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .IsSixAxisSensorAtRest(parameters.sixaxis_handle)); + rb.Push(result); + rb.Push(is_at_rest); } void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) { @@ -761,6 +812,11 @@ void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& c const auto parameters{rp.PopRaw<Parameters>()}; + bool is_firmware_available{}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.IsFirmwareUpdateAvailableForSixAxisSensor( + parameters.sixaxis_handle, is_firmware_available); + LOG_WARNING( Service_HID, "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -768,8 +824,8 @@ void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& c parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(false); + rb.Push(result); + rb.Push(is_firmware_available); } void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { @@ -878,6 +934,10 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown); + // Games expect this event to be signaled after calling this function + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SignalStyleSetChangedEvent(parameters.npad_id); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) @@ -1115,7 +1175,7 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } else { LOG_ERROR(Service_HID, "Npads are not connected!"); - rb.Push(ERR_NPAD_NOT_CONNECTED); + rb.Push(NpadNotConnected); } } @@ -2124,32 +2184,6 @@ public: } }; -class HidBus final : public ServiceFramework<HidBus> { -public: - explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} { - // clang-format off - static const FunctionInfo functions[] = { - {1, nullptr, "GetBusHandle"}, - {2, nullptr, "IsExternalDeviceConnected"}, - {3, nullptr, "Initialize"}, - {4, nullptr, "Finalize"}, - {5, nullptr, "EnableExternalDevice"}, - {6, nullptr, "GetExternalDeviceId"}, - {7, nullptr, "SendCommandAsync"}, - {8, nullptr, "GetSendCommandAsynceResult"}, - {9, nullptr, "SetEventForSendCommandAsycResult"}, - {10, nullptr, "GetSharedMemoryHandle"}, - {11, nullptr, "EnableJoyPollingReceiveMode"}, - {12, nullptr, "DisableJoyPollingReceiveMode"}, - {13, nullptr, "GetPollingData"}, - {14, nullptr, "SetStatusManagerType"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared<Hid>(system)->InstallAsService(service_manager); std::make_shared<HidBus>(system)->InstallAsService(service_manager); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index c281081a7..666cb6ff5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -103,6 +103,7 @@ private: void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); void StartSixAxisSensor(Kernel::HLERequestContext& ctx); void StopSixAxisSensor(Kernel::HLERequestContext& ctx); + void IsSixAxisSensorFusionEnabled(Kernel::HLERequestContext& ctx); void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx); void SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx); void GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp new file mode 100644 index 000000000..af7662a15 --- /dev/null +++ b/src/core/hle/service/hid/hidbus.cpp @@ -0,0 +1,531 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hid/hid_types.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/service/hid/hidbus.h" +#include "core/hle/service/hid/hidbus/ringcon.h" +#include "core/hle/service/hid/hidbus/starlink.h" +#include "core/hle/service/hid/hidbus/stubbed.h" +#include "core/hle/service/service.h" +#include "core/memory.h" + +namespace Service::HID { +// (15ms, 66Hz) +constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; + +HidBus::HidBus(Core::System& system_) + : ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} { + + // clang-format off + static const FunctionInfo functions[] = { + {1, &HidBus::GetBusHandle, "GetBusHandle"}, + {2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"}, + {3, &HidBus::Initialize, "Initialize"}, + {4, &HidBus::Finalize, "Finalize"}, + {5, &HidBus::EnableExternalDevice, "EnableExternalDevice"}, + {6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"}, + {7, &HidBus::SendCommandAsync, "SendCommandAsync"}, + {8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"}, + {9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"}, + {10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, + {11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"}, + {12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"}, + {13, nullptr, "GetPollingData"}, + {14, &HidBus::SetStatusManagerType, "SetStatusManagerType"}, + }; + // clang-format on + + RegisterHandlers(functions); + + // Register update callbacks + hidbus_update_event = Core::Timing::CreateEvent( + "Hidbus::UpdateCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); + UpdateHidbus(user_data, ns_late); + }); + + system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event); +} + +HidBus::~HidBus() { + system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0); +} + +void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + auto& core_timing = system.CoreTiming(); + + if (is_hidbus_enabled) { + for (std::size_t i = 0; i < devices.size(); ++i) { + if (!devices[i].is_device_initializated) { + continue; + } + auto& device = devices[i].device; + device->OnUpdate(); + auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index]; + cur_entry.is_polling_mode = device->IsPollingMode(); + cur_entry.polling_mode = device->GetPollingMode(); + cur_entry.is_enabled = device->IsEnabled(); + + u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer(); + std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status, + sizeof(HidbusStatusManagerEntry)); + } + } + + // If ns_late is higher than the update rate ignore the delay + if (ns_late > hidbus_update_ns) { + ns_late = {}; + } + + core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event); +} + +std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const { + for (std::size_t i = 0; i < devices.size(); ++i) { + const auto& device_handle = devices[i].handle; + if (handle.abstracted_pad_id == device_handle.abstracted_pad_id && + handle.internal_index == device_handle.internal_index && + handle.player_number == device_handle.player_number && + handle.bus_type == device_handle.bus_type && + handle.is_valid == device_handle.is_valid) { + return i; + } + } + return std::nullopt; +} + +void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::NpadIdType npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + BusType bus_type; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}", + parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id); + + bool is_handle_found = 0; + std::size_t handle_index = 0; + + for (std::size_t i = 0; i < devices.size(); i++) { + const auto& handle = devices[i].handle; + if (!handle.is_valid) { + continue; + } + if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id && + handle.bus_type == parameters.bus_type) { + is_handle_found = true; + handle_index = i; + break; + } + } + + // Handle not found. Create a new one + if (!is_handle_found) { + for (std::size_t i = 0; i < devices.size(); i++) { + if (devices[i].handle.is_valid) { + continue; + } + devices[i].handle = { + .abstracted_pad_id = static_cast<u8>(i), + .internal_index = static_cast<u8>(i), + .player_number = static_cast<u8>(parameters.npad_id), + .bus_type = parameters.bus_type, + .is_valid = true, + }; + handle_index = i; + break; + } + } + + struct OutData { + bool is_valid; + INSERT_PADDING_BYTES(7); + BusHandle handle; + }; + static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size."); + + const OutData out_data{ + .is_valid = true, + .handle = devices[handle_index].handle, + }; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(out_data); +} + +void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "Called, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + const bool is_attached = device->IsDeviceActivated(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_attached); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::Initialize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={} bus_type={} internal_index={} " + "player_number={} is_valid={}, applet_resource_user_id={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id); + + is_hidbus_enabled = true; + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto entry_index = devices[device_index.value()].handle.internal_index; + auto& cur_entry = hidbus_status.entries[entry_index]; + + if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) { + MakeDevice<RingController>(bus_handle_); + devices[device_index.value()].is_device_initializated = true; + devices[device_index.value()].device->ActivateDevice(); + cur_entry.is_in_focus = true; + cur_entry.is_connected = true; + cur_entry.is_connected_result = ResultSuccess; + cur_entry.is_enabled = false; + cur_entry.is_polling_mode = false; + } else { + MakeDevice<HidbusStubbed>(bus_handle_); + devices[device_index.value()].is_device_initializated = true; + cur_entry.is_in_focus = true; + cur_entry.is_connected = false; + cur_entry.is_connected_result = ResultSuccess; + cur_entry.is_enabled = false; + cur_entry.is_polling_mode = false; + } + + std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status, + sizeof(hidbus_status)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::Finalize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}, applet_resource_user_id={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto entry_index = devices[device_index.value()].handle.internal_index; + auto& cur_entry = hidbus_status.entries[entry_index]; + auto& device = devices[device_index.value()].device; + devices[device_index.value()].is_device_initializated = false; + device->DeactivateDevice(); + + cur_entry.is_in_focus = true; + cur_entry.is_connected = false; + cur_entry.is_connected_result = ResultSuccess; + cur_entry.is_enabled = false; + cur_entry.is_polling_mode = false; + std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status, + sizeof(hidbus_status)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + bool enable; + INSERT_PADDING_BYTES_NOINIT(7); + BusHandle bus_handle; + u64 inval; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_INFO(Service_HID, + "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", + parameters.enable, parameters.bus_handle.abstracted_pad_id, + parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, + parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, + parameters.applet_resource_user_id); + + const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->Enable(parameters.enable); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + u32 device_id = device->GetDeviceId(); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u32>(device_id); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto data = ctx.ReadBuffer(); + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_DEBUG(Service_HID, + "called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}", + data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type, + bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->SetCommand(data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +}; + +void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_DEBUG(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + const std::vector<u8> data = device->GetReply(); + const u64 data_size = ctx.WriteBuffer(data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push<u64>(data_size); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +}; + +void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(device->GetSendCommandAsycEvent()); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +}; + +void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem()); +} + +void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto t_mem_size{rp.Pop<u32>()}; + const auto t_mem_handle{ctx.GetCopyHandle(0)}; + const auto polling_mode_{rp.PopEnum<JoyPollingMode>()}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes"); + + auto t_mem = + system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); + + if (t_mem.IsNull()) { + LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size"); + + LOG_INFO(Service_HID, + "called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, " + "internal_index={}, player_number={}, is_valid={}", + t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type, + bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->SetPollingMode(polling_mode_); + device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress())); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->DisablePollingMode(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto manager_type{rp.PopEnum<StatusManagerType>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus.h b/src/core/hle/service/hid/hidbus.h new file mode 100644 index 000000000..b10d5156a --- /dev/null +++ b/src/core/hle/service/hid/hidbus.h @@ -0,0 +1,131 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> + +#include "core/hle/service/hid/hidbus/hidbus_base.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Core::Timing { +struct EventType; +} // namespace Core::Timing + +namespace Core { +class System; +} // namespace Core + +namespace Service::HID { + +class HidBus final : public ServiceFramework<HidBus> { +public: + explicit HidBus(Core::System& system_); + ~HidBus() override; + +private: + static const std::size_t max_number_of_handles = 0x13; + + enum class HidBusDeviceId : std::size_t { + RingController = 0x20, + FamicomRight = 0x21, + Starlink = 0x28, + }; + + // This is nn::hidbus::detail::StatusManagerType + enum class StatusManagerType : u32 { + None, + Type16, + Type32, + }; + + // This is nn::hidbus::BusType + enum class BusType : u8 { + LeftJoyRail, + RightJoyRail, + InternalBus, // Lark microphone + + MaxBusType, + }; + + // This is nn::hidbus::BusHandle + struct BusHandle { + u32 abstracted_pad_id; + u8 internal_index; + u8 player_number; + BusType bus_type; + bool is_valid; + }; + static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size"); + + // This is nn::hidbus::JoyPollingReceivedData + struct JoyPollingReceivedData { + std::array<u8, 0x30> data; + u64 out_size; + u64 sampling_number; + }; + static_assert(sizeof(JoyPollingReceivedData) == 0x40, + "JoyPollingReceivedData is an invalid size"); + + struct HidbusStatusManagerEntry { + u8 is_connected{}; + INSERT_PADDING_BYTES(0x3); + ResultCode is_connected_result{0}; + u8 is_enabled{}; + u8 is_in_focus{}; + u8 is_polling_mode{}; + u8 reserved{}; + JoyPollingMode polling_mode{}; + INSERT_PADDING_BYTES(0x70); // Unknown + }; + static_assert(sizeof(HidbusStatusManagerEntry) == 0x80, + "HidbusStatusManagerEntry is an invalid size"); + + struct HidbusStatusManager { + std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{}; + INSERT_PADDING_BYTES(0x680); // Unused + }; + static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size"); + + struct HidbusDevice { + bool is_device_initializated{}; + BusHandle handle{}; + std::unique_ptr<HidbusBase> device{nullptr}; + }; + + void GetBusHandle(Kernel::HLERequestContext& ctx); + void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx); + void Initialize(Kernel::HLERequestContext& ctx); + void Finalize(Kernel::HLERequestContext& ctx); + void EnableExternalDevice(Kernel::HLERequestContext& ctx); + void GetExternalDeviceId(Kernel::HLERequestContext& ctx); + void SendCommandAsync(Kernel::HLERequestContext& ctx); + void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx); + void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx); + void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); + void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx); + void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx); + void SetStatusManagerType(Kernel::HLERequestContext& ctx); + + void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); + std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const; + + template <typename T> + void MakeDevice(BusHandle handle) { + const auto device_index = GetDeviceIndexFromHandle(handle); + if (device_index) { + devices[device_index.value()].device = + std::make_unique<T>(system.HIDCore(), service_context); + } + } + + bool is_hidbus_enabled{false}; + HidbusStatusManager hidbus_status{}; + std::array<HidbusDevice, max_number_of_handles> devices{}; + std::shared_ptr<Core::Timing::EventType> hidbus_update_event; + KernelHelpers::ServiceContext service_context; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp new file mode 100644 index 000000000..09bff10e5 --- /dev/null +++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp @@ -0,0 +1,72 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/hid_core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/hid/hidbus/hidbus_base.h" +#include "core/hle/service/kernel_helpers.h" + +namespace Service::HID { + +HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_) + : service_context(service_context_) { + send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent"); +} +HidbusBase::~HidbusBase() = default; + +void HidbusBase::ActivateDevice() { + if (is_activated) { + return; + } + is_activated = true; + OnInit(); +} + +void HidbusBase::DeactivateDevice() { + if (is_activated) { + OnRelease(); + } + is_activated = false; +} + +bool HidbusBase::IsDeviceActivated() const { + return is_activated; +} + +void HidbusBase::Enable(bool enable) { + device_enabled = enable; +} + +bool HidbusBase::IsEnabled() const { + return device_enabled; +} + +bool HidbusBase::IsPollingMode() const { + return polling_mode_enabled; +} + +JoyPollingMode HidbusBase::GetPollingMode() const { + return polling_mode; +} + +void HidbusBase::SetPollingMode(JoyPollingMode mode) { + polling_mode = mode; + polling_mode_enabled = true; +} + +void HidbusBase::DisablePollingMode() { + polling_mode_enabled = false; +} + +void HidbusBase::SetTransferMemoryPointer(u8* t_mem) { + is_transfer_memory_set = true; + transfer_memory = t_mem; +} + +Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const { + return send_command_async_event->GetReadableEvent(); +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.h b/src/core/hle/service/hid/hidbus/hidbus_base.h new file mode 100644 index 000000000..13d073a3d --- /dev/null +++ b/src/core/hle/service/hid/hidbus/hidbus_base.h @@ -0,0 +1,179 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_types.h" +#include "core/hle/result.h" + +namespace Kernel { +class KEvent; +class KReadableEvent; +} // namespace Kernel + +namespace Service::KernelHelpers { +class ServiceContext; +} + +namespace Service::HID { + +// This is nn::hidbus::JoyPollingMode +enum class JoyPollingMode : u32 { + SixAxisSensorDisable, + SixAxisSensorEnable, + ButtonOnly, +}; + +struct DataAccessorHeader { + ResultCode result{ResultUnknown}; + INSERT_PADDING_WORDS(0x1); + std::array<u8, 0x18> unused{}; + u64 latest_entry{}; + u64 total_entries{}; +}; +static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size"); + +struct JoyDisableSixAxisPollingData { + std::array<u8, 0x26> data; + u8 out_size; + INSERT_PADDING_BYTES(0x1); + u64 sampling_number; +}; +static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30, + "JoyDisableSixAxisPollingData is an invalid size"); + +struct JoyEnableSixAxisPollingData { + std::array<u8, 0x8> data; + u8 out_size; + INSERT_PADDING_BYTES(0x7); + u64 sampling_number; +}; +static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18, + "JoyEnableSixAxisPollingData is an invalid size"); + +struct JoyButtonOnlyPollingData { + std::array<u8, 0x2c> data; + u8 out_size; + INSERT_PADDING_BYTES(0x3); + u64 sampling_number; +}; +static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38, + "JoyButtonOnlyPollingData is an invalid size"); + +struct JoyDisableSixAxisPollingEntry { + u64 sampling_number; + JoyDisableSixAxisPollingData polling_data; +}; +static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38, + "JoyDisableSixAxisPollingEntry is an invalid size"); + +struct JoyEnableSixAxisPollingEntry { + u64 sampling_number; + JoyEnableSixAxisPollingData polling_data; +}; +static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20, + "JoyEnableSixAxisPollingEntry is an invalid size"); + +struct JoyButtonOnlyPollingEntry { + u64 sampling_number; + JoyButtonOnlyPollingData polling_data; +}; +static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40, + "JoyButtonOnlyPollingEntry is an invalid size"); + +struct JoyDisableSixAxisDataAccessor { + DataAccessorHeader header{}; + std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{}; +}; +static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298, + "JoyDisableSixAxisDataAccessor is an invalid size"); + +struct JoyEnableSixAxisDataAccessor { + DataAccessorHeader header{}; + std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{}; +}; +static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190, + "JoyEnableSixAxisDataAccessor is an invalid size"); + +struct ButtonOnlyPollingDataAccessor { + DataAccessorHeader header; + std::array<JoyButtonOnlyPollingEntry, 0xb> entries; +}; +static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0, + "ButtonOnlyPollingDataAccessor is an invalid size"); + +class HidbusBase { +public: + explicit HidbusBase(KernelHelpers::ServiceContext& service_context_); + virtual ~HidbusBase(); + + void ActivateDevice(); + + void DeactivateDevice(); + + bool IsDeviceActivated() const; + + // Enables/disables the device + void Enable(bool enable); + + // returns true if device is enabled + bool IsEnabled() const; + + // returns true if polling mode is enabled + bool IsPollingMode() const; + + // returns polling mode + JoyPollingMode GetPollingMode() const; + + // Sets and enables JoyPollingMode + void SetPollingMode(JoyPollingMode mode); + + // Disables JoyPollingMode + void DisablePollingMode(); + + // Called on EnableJoyPollingReceiveMode + void SetTransferMemoryPointer(u8* t_mem); + + Kernel::KReadableEvent& GetSendCommandAsycEvent() const; + + virtual void OnInit() {} + + virtual void OnRelease() {} + + // Updates device transfer memory + virtual void OnUpdate() {} + + // Returns the device ID of the joycon + virtual u8 GetDeviceId() const { + return {}; + } + + // Assigns a command from data + virtual bool SetCommand(const std::vector<u8>& data) { + return {}; + } + + // Returns a reply from a command + virtual std::vector<u8> GetReply() const { + return {}; + } + +protected: + bool is_activated{}; + bool device_enabled{}; + bool polling_mode_enabled{}; + JoyPollingMode polling_mode = {}; + // TODO(German77): All data accessors need to be replaced with a ring lifo object + JoyDisableSixAxisDataAccessor disable_sixaxis_data{}; + JoyEnableSixAxisDataAccessor enable_sixaxis_data{}; + ButtonOnlyPollingDataAccessor button_only_data{}; + + u8* transfer_memory{nullptr}; + bool is_transfer_memory_set{}; + + Kernel::KEvent* send_command_async_event; + KernelHelpers::ServiceContext& service_context; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp new file mode 100644 index 000000000..5ec3cc83c --- /dev/null +++ b/src/core/hle/service/hid/hidbus/ringcon.cpp @@ -0,0 +1,286 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/emulated_devices.h" +#include "core/hid/hid_core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/hid/hidbus/ringcon.h" + +namespace Service::HID { + +RingController::RingController(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_) + : HidbusBase(service_context_) { + input = hid_core_.GetEmulatedDevices(); +} + +RingController::~RingController() = default; + +void RingController::OnInit() { + return; +} + +void RingController::OnRelease() { + return; +}; + +void RingController::OnUpdate() { + if (!is_activated) { + return; + } + + if (!device_enabled) { + return; + } + + if (!polling_mode_enabled || !is_transfer_memory_set) { + return; + } + + // TODO: Increment multitasking counters from motion and sensor data + + switch (polling_mode) { + case JoyPollingMode::SixAxisSensorEnable: { + enable_sixaxis_data.header.total_entries = 10; + enable_sixaxis_data.header.result = ResultSuccess; + const auto& last_entry = + enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; + + enable_sixaxis_data.header.latest_entry = + (enable_sixaxis_data.header.latest_entry + 1) % 10; + auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; + + curr_entry.sampling_number = last_entry.sampling_number + 1; + curr_entry.polling_data.sampling_number = curr_entry.sampling_number; + + const RingConData ringcon_value = GetSensorValue(); + curr_entry.polling_data.out_size = sizeof(ringcon_value); + std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value)); + + std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data)); + break; + } + default: + LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); + break; + } +} + +RingController::RingConData RingController::GetSensorValue() const { + RingConData ringcon_sensor_value{ + .status = DataValid::Valid, + .data = 0, + }; + + const f32 force_value = input->GetRingSensorForce().force * range; + ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value; + + return ringcon_sensor_value; +} + +u8 RingController::GetDeviceId() const { + return device_id; +} + +std::vector<u8> RingController::GetReply() const { + const RingConCommands current_command = command; + + switch (current_command) { + case RingConCommands::GetFirmwareVersion: + return GetFirmwareVersionReply(); + case RingConCommands::ReadId: + return GetReadIdReply(); + case RingConCommands::c20105: + return GetC020105Reply(); + case RingConCommands::ReadUnkCal: + return GetReadUnkCalReply(); + case RingConCommands::ReadFactoryCal: + return GetReadFactoryCalReply(); + case RingConCommands::ReadUserCal: + return GetReadUserCalReply(); + case RingConCommands::ReadRepCount: + return GetReadRepCountReply(); + case RingConCommands::ReadTotalPushCount: + return GetReadTotalPushCountReply(); + case RingConCommands::ResetRepCount: + return GetResetRepCountReply(); + case RingConCommands::SaveCalData: + return GetSaveDataReply(); + default: + return GetErrorReply(); + } +} + +bool RingController::SetCommand(const std::vector<u8>& data) { + if (data.size() < 4) { + LOG_ERROR(Service_HID, "Command size not supported {}", data.size()); + command = RingConCommands::Error; + return false; + } + + std::memcpy(&command, data.data(), sizeof(RingConCommands)); + + switch (command) { + case RingConCommands::GetFirmwareVersion: + case RingConCommands::ReadId: + case RingConCommands::c20105: + case RingConCommands::ReadUnkCal: + case RingConCommands::ReadFactoryCal: + case RingConCommands::ReadUserCal: + case RingConCommands::ReadRepCount: + case RingConCommands::ReadTotalPushCount: + ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); + send_command_async_event->GetWritableEvent().Signal(); + return true; + case RingConCommands::ResetRepCount: + ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); + total_rep_count = 0; + send_command_async_event->GetWritableEvent().Signal(); + return true; + case RingConCommands::SaveCalData: { + ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes"); + + SaveCalData save_info{}; + std::memcpy(&save_info, data.data(), sizeof(SaveCalData)); + user_calibration = save_info.calibration; + send_command_async_event->GetWritableEvent().Signal(); + return true; + } + default: + LOG_ERROR(Service_HID, "Command not implemented {}", command); + command = RingConCommands::Error; + // Signal a reply to avoid softlocking the game + send_command_async_event->GetWritableEvent().Signal(); + return false; + } +} + +std::vector<u8> RingController::GetFirmwareVersionReply() const { + const FirmwareVersionReply reply{ + .status = DataValid::Valid, + .firmware = version, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadIdReply() const { + // The values are hardcoded from a real joycon + const ReadIdReply reply{ + .status = DataValid::Valid, + .id_l_x0 = 8, + .id_l_x0_2 = 41, + .id_l_x4 = 22294, + .id_h_x0 = 19777, + .id_h_x0_2 = 13621, + .id_h_x4 = 8245, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetC020105Reply() const { + const Cmd020105Reply reply{ + .status = DataValid::Valid, + .data = 1, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadUnkCalReply() const { + const ReadUnkCalReply reply{ + .status = DataValid::Valid, + .data = 0, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadFactoryCalReply() const { + const ReadFactoryCalReply reply{ + .status = DataValid::Valid, + .calibration = factory_calibration, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadUserCalReply() const { + const ReadUserCalReply reply{ + .status = DataValid::Valid, + .calibration = user_calibration, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadRepCountReply() const { + const GetThreeByteReply reply{ + .status = DataValid::Valid, + .data = {total_rep_count, 0, 0}, + .crc = GetCrcValue({total_rep_count, 0, 0, 0}), + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadTotalPushCountReply() const { + const GetThreeByteReply reply{ + .status = DataValid::Valid, + .data = {total_push_count, 0, 0}, + .crc = GetCrcValue({total_push_count, 0, 0, 0}), + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetResetRepCountReply() const { + return GetReadRepCountReply(); +} + +std::vector<u8> RingController::GetSaveDataReply() const { + const StatusReply reply{ + .status = DataValid::Valid, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetErrorReply() const { + const ErrorReply reply{ + .status = DataValid::BadCRC, + }; + + return GetDataVector(reply); +} + +u8 RingController::GetCrcValue(const std::vector<u8>& data) const { + u8 crc = 0; + for (std::size_t index = 0; index < data.size(); index++) { + for (u8 i = 0x80; i > 0; i >>= 1) { + bool bit = (crc & 0x80) != 0; + if ((data[index] & i) != 0) { + bit = !bit; + } + crc <<= 1; + if (bit) { + crc ^= 0x8d; + } + } + } + return crc; +} + +template <typename T> +std::vector<u8> RingController::GetDataVector(const T& reply) const { + static_assert(std::is_trivially_copyable_v<T>); + std::vector<u8> data; + data.resize(sizeof(reply)); + std::memcpy(data.data(), &reply, sizeof(reply)); + return data; +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h new file mode 100644 index 000000000..2dbc6150e --- /dev/null +++ b/src/core/hle/service/hid/hidbus/ringcon.h @@ -0,0 +1,254 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_types.h" +#include "core/hle/service/hid/hidbus/hidbus_base.h" + +namespace Core::HID { +class EmulatedDevices; +} // namespace Core::HID + +namespace Service::HID { + +class RingController final : public HidbusBase { +public: + explicit RingController(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_); + ~RingController() override; + + void OnInit() override; + + void OnRelease() override; + + // Updates ringcon transfer memory + void OnUpdate() override; + + // Returns the device ID of the joycon + u8 GetDeviceId() const override; + + // Assigns a command from data + bool SetCommand(const std::vector<u8>& data) override; + + // Returns a reply from a command + std::vector<u8> GetReply() const override; + +private: + // These values are obtained from a real ring controller + static constexpr s16 idle_value = 2280; + static constexpr s16 idle_deadzone = 120; + static constexpr s16 range = 2500; + + // Most missing command names are leftovers from other firmware versions + enum class RingConCommands : u32 { + GetFirmwareVersion = 0x00020000, + ReadId = 0x00020100, + JoyPolling = 0x00020101, + Unknown1 = 0x00020104, + c20105 = 0x00020105, + Unknown2 = 0x00020204, + Unknown3 = 0x00020304, + Unknown4 = 0x00020404, + ReadUnkCal = 0x00020504, + ReadFactoryCal = 0x00020A04, + Unknown5 = 0x00021104, + Unknown6 = 0x00021204, + Unknown7 = 0x00021304, + ReadUserCal = 0x00021A04, + ReadRepCount = 0x00023104, + ReadTotalPushCount = 0x00023204, + ResetRepCount = 0x04013104, + Unknown8 = 0x04011104, + Unknown9 = 0x04011204, + Unknown10 = 0x04011304, + SaveCalData = 0x10011A04, + Error = 0xFFFFFFFF, + }; + + enum class DataValid : u32 { + Valid, + BadCRC, + Cal, + }; + + struct FirmwareVersion { + u8 sub; + u8 main; + }; + static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); + + struct FactoryCalibration { + s32_le os_max; + s32_le hk_max; + s32_le zero_min; + s32_le zero_max; + }; + static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size"); + + struct CalibrationValue { + s16 value; + u16 crc; + }; + static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size"); + + struct UserCalibration { + CalibrationValue os_max; + CalibrationValue hk_max; + CalibrationValue zero; + }; + static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size"); + + struct SaveCalData { + RingConCommands command; + UserCalibration calibration; + INSERT_PADDING_BYTES_NOINIT(4); + }; + static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size"); + static_assert(std::is_trivially_copyable_v<SaveCalData>, + "SaveCalData must be trivially copyable"); + + struct FirmwareVersionReply { + DataValid status; + FirmwareVersion firmware; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size"); + + struct Cmd020105Reply { + DataValid status; + u8 data; + INSERT_PADDING_BYTES(0x3); + }; + static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size"); + + struct StatusReply { + DataValid status; + }; + static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size"); + + struct GetThreeByteReply { + DataValid status; + std::array<u8, 3> data; + u8 crc; + }; + static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size"); + + struct ReadUnkCalReply { + DataValid status; + u16 data; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size"); + + struct ReadFactoryCalReply { + DataValid status; + FactoryCalibration calibration; + }; + static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size"); + + struct ReadUserCalReply { + DataValid status; + UserCalibration calibration; + INSERT_PADDING_BYTES(0x4); + }; + static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size"); + + struct ReadIdReply { + DataValid status; + u16 id_l_x0; + u16 id_l_x0_2; + u16 id_l_x4; + u16 id_h_x0; + u16 id_h_x0_2; + u16 id_h_x4; + }; + static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size"); + + struct ErrorReply { + DataValid status; + INSERT_PADDING_BYTES(0x3); + }; + static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size"); + + struct RingConData { + DataValid status; + s16_le data; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size"); + + // Returns RingConData struct with pressure sensor values + RingConData GetSensorValue() const; + + // Returns 8 byte reply with firmware version + std::vector<u8> GetFirmwareVersionReply() const; + + // Returns 16 byte reply with ID values + std::vector<u8> GetReadIdReply() const; + + // (STUBBED) Returns 8 byte reply + std::vector<u8> GetC020105Reply() const; + + // (STUBBED) Returns 8 byte empty reply + std::vector<u8> GetReadUnkCalReply() const; + + // Returns 20 byte reply with factory calibration values + std::vector<u8> GetReadFactoryCalReply() const; + + // Returns 20 byte reply with user calibration values + std::vector<u8> GetReadUserCalReply() const; + + // Returns 8 byte reply + std::vector<u8> GetReadRepCountReply() const; + + // Returns 8 byte reply + std::vector<u8> GetReadTotalPushCountReply() const; + + // Returns 8 byte reply + std::vector<u8> GetResetRepCountReply() const; + + // Returns 4 byte save data reply + std::vector<u8> GetSaveDataReply() const; + + // Returns 8 byte error reply + std::vector<u8> GetErrorReply() const; + + // Returns 8 bit redundancy check from provided data + u8 GetCrcValue(const std::vector<u8>& data) const; + + // Converts structs to an u8 vector equivalent + template <typename T> + std::vector<u8> GetDataVector(const T& reply) const; + + RingConCommands command{RingConCommands::Error}; + + // These counters are used in multitasking mode while the switch is sleeping + // Total steps taken + u8 total_rep_count = 0; + // Total times the ring was pushed + u8 total_push_count = 0; + + const u8 device_id = 0x20; + const FirmwareVersion version = { + .sub = 0x0, + .main = 0x2c, + }; + const FactoryCalibration factory_calibration = { + .os_max = idle_value + range + idle_deadzone, + .hk_max = idle_value - range - idle_deadzone, + .zero_min = idle_value - idle_deadzone, + .zero_max = idle_value + idle_deadzone, + }; + UserCalibration user_calibration = { + .os_max = {.value = range, .crc = 228}, + .hk_max = {.value = -range, .crc = 239}, + .zero = {.value = idle_value, .crc = 225}, + }; + + Core::HID::EmulatedDevices* input; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/starlink.cpp b/src/core/hle/service/hid/hidbus/starlink.cpp new file mode 100644 index 000000000..3175c48da --- /dev/null +++ b/src/core/hle/service/hid/hidbus/starlink.cpp @@ -0,0 +1,51 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/hidbus/starlink.h" + +namespace Service::HID { +constexpr u8 DEVICE_ID = 0x28; + +Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) + : HidbusBase(service_context_) {} +Starlink::~Starlink() = default; + +void Starlink::OnInit() { + return; +} + +void Starlink::OnRelease() { + return; +}; + +void Starlink::OnUpdate() { + if (!is_activated) { + return; + } + if (!device_enabled) { + return; + } + if (!polling_mode_enabled || !is_transfer_memory_set) { + return; + } + + LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); +} + +u8 Starlink::GetDeviceId() const { + return DEVICE_ID; +} + +std::vector<u8> Starlink::GetReply() const { + return {}; +} + +bool Starlink::SetCommand(const std::vector<u8>& data) { + LOG_ERROR(Service_HID, "Command not implemented"); + return false; +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/starlink.h b/src/core/hle/service/hid/hidbus/starlink.h new file mode 100644 index 000000000..79770b68e --- /dev/null +++ b/src/core/hle/service/hid/hidbus/starlink.h @@ -0,0 +1,39 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/hid/hidbus/hidbus_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::HID { + +class Starlink final : public HidbusBase { +public: + explicit Starlink(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_); + ~Starlink() override; + + void OnInit() override; + + void OnRelease() override; + + // Updates ringcon transfer memory + void OnUpdate() override; + + // Returns the device ID of the joycon + u8 GetDeviceId() const override; + + // Assigns a command from data + bool SetCommand(const std::vector<u8>& data) override; + + // Returns a reply from a command + std::vector<u8> GetReply() const override; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/stubbed.cpp b/src/core/hle/service/hid/hidbus/stubbed.cpp new file mode 100644 index 000000000..5474657be --- /dev/null +++ b/src/core/hle/service/hid/hidbus/stubbed.cpp @@ -0,0 +1,52 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/hidbus/stubbed.h" + +namespace Service::HID { +constexpr u8 DEVICE_ID = 0xFF; + +HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_) + : HidbusBase(service_context_) {} +HidbusStubbed::~HidbusStubbed() = default; + +void HidbusStubbed::OnInit() { + return; +} + +void HidbusStubbed::OnRelease() { + return; +}; + +void HidbusStubbed::OnUpdate() { + if (!is_activated) { + return; + } + if (!device_enabled) { + return; + } + if (!polling_mode_enabled || !is_transfer_memory_set) { + return; + } + + LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); +} + +u8 HidbusStubbed::GetDeviceId() const { + return DEVICE_ID; +} + +std::vector<u8> HidbusStubbed::GetReply() const { + return {}; +} + +bool HidbusStubbed::SetCommand(const std::vector<u8>& data) { + LOG_ERROR(Service_HID, "Command not implemented"); + return false; +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/stubbed.h b/src/core/hle/service/hid/hidbus/stubbed.h new file mode 100644 index 000000000..40acdfe8f --- /dev/null +++ b/src/core/hle/service/hid/hidbus/stubbed.h @@ -0,0 +1,39 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/hid/hidbus/hidbus_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::HID { + +class HidbusStubbed final : public HidbusBase { +public: + explicit HidbusStubbed(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_); + ~HidbusStubbed() override; + + void OnInit() override; + + void OnRelease() override; + + // Updates ringcon transfer memory + void OnUpdate() override; + + // Returns the device ID of the joycon + u8 GetDeviceId() const override; + + // Assigns a command from data + bool SetCommand(const std::vector<u8>& data) override; + + // Returns a reply from a command + std::vector<u8> GetReply() const override; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp new file mode 100644 index 000000000..0f9e33ef6 --- /dev/null +++ b/src/core/hle/service/jit/jit.cpp @@ -0,0 +1,332 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/arm/symbols.h" +#include "core/core.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_code_memory.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/result.h" +#include "core/hle/service/jit/jit.h" +#include "core/hle/service/jit/jit_context.h" +#include "core/hle/service/service.h" +#include "core/memory.h" + +namespace Service::JIT { + +struct CodeRange { + u64 offset; + u64 size; +}; + +class IJitEnvironment final : public ServiceFramework<IJitEnvironment> { +public: + explicit IJitEnvironment(Core::System& system_, CodeRange user_rx, CodeRange user_ro) + : ServiceFramework{system_, "IJitEnvironment", ServiceThreadType::CreateNew}, + context{system_.Memory()} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IJitEnvironment::GenerateCode, "GenerateCode"}, + {1, &IJitEnvironment::Control, "Control"}, + {1000, &IJitEnvironment::LoadPlugin, "LoadPlugin"}, + {1001, &IJitEnvironment::GetCodeAddress, "GetCodeAddress"}, + }; + // clang-format on + + RegisterHandlers(functions); + + // Identity map user code range into sysmodule context + configuration.user_ro_memory = user_ro; + configuration.user_rx_memory = user_rx; + configuration.sys_ro_memory = user_ro; + configuration.sys_rx_memory = user_rx; + } + + void GenerateCode(Kernel::HLERequestContext& ctx) { + struct Parameters { + u32 data_size; + u64 command; + CodeRange cr1; + CodeRange cr2; + Struct32 data; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters{rp.PopRaw<Parameters>()}; + std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; + std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); + + const VAddr return_ptr{context.AddHeap(0u)}; + const VAddr cr1_in_ptr{context.AddHeap(parameters.cr1)}; + const VAddr cr2_in_ptr{context.AddHeap(parameters.cr2)}; + const VAddr cr1_out_ptr{ + context.AddHeap(CodeRange{.offset = parameters.cr1.offset, .size = 0})}; + const VAddr cr2_out_ptr{ + context.AddHeap(CodeRange{.offset = parameters.cr2.offset, .size = 0})}; + const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; + const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; + const VAddr data_ptr{context.AddHeap(parameters.data)}; + const VAddr configuration_ptr{context.AddHeap(configuration)}; + + context.CallFunction(callbacks.GenerateCode, return_ptr, cr1_out_ptr, cr2_out_ptr, + configuration_ptr, parameters.command, input_ptr, input_buffer.size(), + cr1_in_ptr, cr2_in_ptr, data_ptr, parameters.data_size, output_ptr, + output_buffer.size()); + + const s32 return_value{context.GetHeap<s32>(return_ptr)}; + + if (return_value == 0) { + system.InvalidateCpuInstructionCacheRange(configuration.user_rx_memory.offset, + configuration.user_rx_memory.size); + + if (ctx.CanWriteBuffer()) { + context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); + ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); + } + const auto cr1_out{context.GetHeap<CodeRange>(cr1_out_ptr)}; + const auto cr2_out{context.GetHeap<CodeRange>(cr2_out_ptr)}; + + IPC::ResponseBuilder rb{ctx, 8}; + rb.Push(ResultSuccess); + rb.Push<u64>(return_value); + rb.PushRaw(cr1_out); + rb.PushRaw(cr2_out); + } else { + LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + } + }; + + void Control(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto command{rp.PopRaw<u64>()}; + const auto input_buffer{ctx.ReadBuffer()}; + std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); + + const VAddr return_ptr{context.AddHeap(0u)}; + const VAddr configuration_ptr{context.AddHeap(configuration)}; + const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; + const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; + const u64 wrapper_value{ + context.CallFunction(callbacks.Control, return_ptr, configuration_ptr, command, + input_ptr, input_buffer.size(), output_ptr, output_buffer.size())}; + const s32 return_value{context.GetHeap<s32>(return_ptr)}; + + if (wrapper_value == 0 && return_value == 0) { + if (ctx.CanWriteBuffer()) { + context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); + ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(return_value); + } else { + LOG_WARNING(Service_JIT, "plugin Control callback failed"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + } + } + + void LoadPlugin(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto tmem_size{rp.PopRaw<u64>()}; + if (tmem_size == 0) { + LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const auto tmem_handle{ctx.GetCopyHandle(0)}; + auto tmem{system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + tmem_handle)}; + if (tmem.IsNull()) { + LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + configuration.work_memory.offset = tmem->GetSourceAddress(); + configuration.work_memory.size = tmem_size; + + const auto nro_plugin{ctx.ReadBuffer(1)}; + auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)}; + const auto GetSymbol{[&](std::string name) { return symbols[name].first; }}; + + callbacks = + GuestCallbacks{.rtld_fini = GetSymbol("_fini"), + .rtld_init = GetSymbol("_init"), + .Control = GetSymbol("nnjitpluginControl"), + .ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols"), + .SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics"), + .Configure = GetSymbol("nnjitpluginConfigure"), + .GenerateCode = GetSymbol("nnjitpluginGenerateCode"), + .GetVersion = GetSymbol("nnjitpluginGetVersion"), + .Keeper = GetSymbol("nnjitpluginKeeper"), + .OnPrepared = GetSymbol("nnjitpluginOnPrepared")}; + + if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || + callbacks.OnPrepared == 0) { + LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + if (!context.LoadNRO(nro_plugin)) { + LOG_ERROR(Service_JIT, "failed to load plugin"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + context.MapProcessMemory(configuration.sys_ro_memory.offset, + configuration.sys_ro_memory.size); + context.MapProcessMemory(configuration.sys_rx_memory.offset, + configuration.sys_rx_memory.size); + context.MapProcessMemory(configuration.work_memory.offset, configuration.work_memory.size); + + if (callbacks.rtld_init != 0) { + context.CallFunction(callbacks.rtld_init); + } + + const auto version{context.CallFunction(callbacks.GetVersion)}; + if (version != 1) { + LOG_ERROR(Service_JIT, "unknown plugin version {}", version); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const auto resolve{context.GetHelper("_resolve")}; + if (callbacks.ResolveBasicSymbols != 0) { + context.CallFunction(callbacks.ResolveBasicSymbols, resolve); + } + const auto resolve_ptr{context.AddHeap(resolve)}; + if (callbacks.SetupDiagnostics != 0) { + context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr); + } + + context.CallFunction(callbacks.Configure, 0u); + const auto configuration_ptr{context.AddHeap(configuration)}; + context.CallFunction(callbacks.OnPrepared, configuration_ptr); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetCodeAddress(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.Push(configuration.user_rx_memory.offset); + rb.Push(configuration.user_ro_memory.offset); + } + +private: + using Struct32 = std::array<u8, 32>; + + struct GuestCallbacks { + VAddr rtld_fini; + VAddr rtld_init; + VAddr Control; + VAddr ResolveBasicSymbols; + VAddr SetupDiagnostics; + VAddr Configure; + VAddr GenerateCode; + VAddr GetVersion; + VAddr Keeper; + VAddr OnPrepared; + }; + + struct JITConfiguration { + CodeRange user_rx_memory; + CodeRange user_ro_memory; + CodeRange work_memory; + CodeRange sys_rx_memory; + CodeRange sys_ro_memory; + }; + + GuestCallbacks callbacks; + JITConfiguration configuration; + JITContext context; +}; + +class JITU final : public ServiceFramework<JITU> { +public: + explicit JITU(Core::System& system_) : ServiceFramework{system_, "jit:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &JITU::CreateJitEnvironment, "CreateJitEnvironment"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + + void CreateJitEnvironment(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_JIT, "called"); + + struct Parameters { + u64 rx_size; + u64 ro_size; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters{rp.PopRaw<Parameters>()}; + const auto executable_mem_handle{ctx.GetCopyHandle(1)}; + const auto readable_mem_handle{ctx.GetCopyHandle(2)}; + + if (parameters.rx_size == 0 || parameters.ro_size == 0) { + LOG_ERROR(Service_JIT, "attempted to init with empty code regions"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + // The copy handle at index 0 is the process handle, but handle tables are + // per-process, so there is no point reading it here until we are multiprocess + const auto& process{*system.CurrentProcess()}; + + auto executable_mem{ + process.GetHandleTable().GetObject<Kernel::KCodeMemory>(executable_mem_handle)}; + if (executable_mem.IsNull()) { + LOG_ERROR(Service_JIT, "executable_mem is null for handle=0x{:08X}", + executable_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + auto readable_mem{ + process.GetHandleTable().GetObject<Kernel::KCodeMemory>(readable_mem_handle)}; + if (readable_mem.IsNull()) { + LOG_ERROR(Service_JIT, "readable_mem is null for handle=0x{:08X}", readable_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const CodeRange user_rx{ + .offset = executable_mem->GetSourceAddress(), + .size = parameters.rx_size, + }; + + const CodeRange user_ro{ + .offset = readable_mem->GetSourceAddress(), + .size = parameters.ro_size, + }; + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IJitEnvironment>(system, user_rx, user_ro); + } +}; + +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<JITU>(system)->InstallAsService(sm); +} + +} // namespace Service::JIT diff --git a/src/core/hle/service/jit/jit.h b/src/core/hle/service/jit/jit.h new file mode 100644 index 000000000..8fbf504a1 --- /dev/null +++ b/src/core/hle/service/jit/jit.h @@ -0,0 +1,20 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::JIT { + +/// Registers all JIT services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); + +} // namespace Service::JIT diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp new file mode 100644 index 000000000..630368fb3 --- /dev/null +++ b/src/core/hle/service/jit/jit_context.cpp @@ -0,0 +1,424 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <map> +#include <span> +#include <boost/icl/interval_set.hpp> +#include <dynarmic/interface/A64/a64.h> +#include <dynarmic/interface/A64/config.h> + +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "common/div_ceil.h" +#include "common/logging/log.h" +#include "core/hle/service/jit/jit_context.h" +#include "core/memory.h" + +namespace Service::JIT { + +constexpr std::array<u8, 4> STOP_ARM64 = { + 0x01, 0x00, 0x00, 0xd4, // svc #0 +}; + +constexpr std::array<u8, 8> RESOLVE_ARM64 = { + 0x21, 0x00, 0x00, 0xd4, // svc #1 + 0xc0, 0x03, 0x5f, 0xd6, // ret +}; + +constexpr std::array<u8, 4> PANIC_ARM64 = { + 0x41, 0x00, 0x00, 0xd4, // svc #2 +}; + +constexpr std::array<u8, 60> MEMMOVE_ARM64 = { + 0x1f, 0x00, 0x01, 0xeb, // cmp x0, x1 + 0x83, 0x01, 0x00, 0x54, // b.lo #+34 + 0x42, 0x04, 0x00, 0xd1, // sub x2, x2, 1 + 0x22, 0x01, 0xf8, 0xb7, // tbnz x2, #63, #+36 + 0x23, 0x68, 0x62, 0x38, // ldrb w3, [x1, x2] + 0x03, 0x68, 0x22, 0x38, // strb w3, [x0, x2] + 0xfc, 0xff, 0xff, 0x17, // b #-16 + 0x24, 0x68, 0x63, 0x38, // ldrb w4, [x1, x3] + 0x04, 0x68, 0x23, 0x38, // strb w4, [x0, x3] + 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1 + 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2 + 0x8b, 0xff, 0xff, 0x54, // b.lt #-16 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0x03, 0x00, 0x80, 0xd2, // mov x3, 0 + 0xfc, 0xff, 0xff, 0x17, // b #-16 +}; + +constexpr std::array<u8, 28> MEMSET_ARM64 = { + 0x03, 0x00, 0x80, 0xd2, // mov x3, 0 + 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2 + 0x4b, 0x00, 0x00, 0x54, // b.lt #+8 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0x01, 0x68, 0x23, 0x38, // strb w1, [x0, x3] + 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1 + 0xfb, 0xff, 0xff, 0x17, // b #-20 +}; + +struct HelperFunction { + const char* name; + const std::span<const u8> data; +}; + +constexpr std::array<HelperFunction, 6> HELPER_FUNCTIONS{{ + {"_stop", STOP_ARM64}, + {"_resolve", RESOLVE_ARM64}, + {"_panic", PANIC_ARM64}, + {"memcpy", MEMMOVE_ARM64}, + {"memmove", MEMMOVE_ARM64}, + {"memset", MEMSET_ARM64}, +}}; + +struct Elf64_Dyn { + u64 d_tag; + u64 d_un; +}; + +struct Elf64_Rela { + u64 r_offset; + u64 r_info; + s64 r_addend; +}; + +static constexpr u32 Elf64_RelaType(const Elf64_Rela* rela) { + return static_cast<u32>(rela->r_info); +} + +constexpr int DT_RELA = 7; /* Address of Rela relocs */ +constexpr int DT_RELASZ = 8; /* Total size of Rela relocs */ +constexpr int R_AARCH64_RELATIVE = 1027; /* Adjust by program base. */ + +constexpr size_t STACK_ALIGN = 16; + +class JITContextImpl; + +using IntervalSet = boost::icl::interval_set<VAddr>::type; +using IntervalType = boost::icl::interval_set<VAddr>::interval_type; + +class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { +public: + explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector<u8>& local_memory_, + IntervalSet& mapped_ranges_, JITContextImpl& parent_) + : memory{memory_}, local_memory{local_memory_}, + mapped_ranges{mapped_ranges_}, parent{parent_} {} + + u8 MemoryRead8(u64 vaddr) override { + return ReadMemory<u8>(vaddr); + } + u16 MemoryRead16(u64 vaddr) override { + return ReadMemory<u16>(vaddr); + } + u32 MemoryRead32(u64 vaddr) override { + return ReadMemory<u32>(vaddr); + } + u64 MemoryRead64(u64 vaddr) override { + return ReadMemory<u64>(vaddr); + } + u128 MemoryRead128(u64 vaddr) override { + return ReadMemory<u128>(vaddr); + } + std::string MemoryReadCString(u64 vaddr) { + std::string result; + u8 next; + + while ((next = MemoryRead8(vaddr++)) != 0) { + result += next; + } + + return result; + } + + void MemoryWrite8(u64 vaddr, u8 value) override { + WriteMemory<u8>(vaddr, value); + } + void MemoryWrite16(u64 vaddr, u16 value) override { + WriteMemory<u16>(vaddr, value); + } + void MemoryWrite32(u64 vaddr, u32 value) override { + WriteMemory<u32>(vaddr, value); + } + void MemoryWrite64(u64 vaddr, u64 value) override { + WriteMemory<u64>(vaddr, value); + } + void MemoryWrite128(u64 vaddr, u128 value) override { + WriteMemory<u128>(vaddr, value); + } + + bool MemoryWriteExclusive8(u64 vaddr, u8 value, u8) override { + return WriteMemory<u8>(vaddr, value); + } + bool MemoryWriteExclusive16(u64 vaddr, u16 value, u16) override { + return WriteMemory<u16>(vaddr, value); + } + bool MemoryWriteExclusive32(u64 vaddr, u32 value, u32) override { + return WriteMemory<u32>(vaddr, value); + } + bool MemoryWriteExclusive64(u64 vaddr, u64 value, u64) override { + return WriteMemory<u64>(vaddr, value); + } + bool MemoryWriteExclusive128(u64 vaddr, u128 value, u128) override { + return WriteMemory<u128>(vaddr, value); + } + + void CallSVC(u32 swi) override; + void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override; + void InterpreterFallback(u64 pc, size_t num_instructions) override; + + void AddTicks(u64 ticks) override {} + u64 GetTicksRemaining() override { + return std::numeric_limits<u32>::max(); + } + u64 GetCNTPCT() override { + return 0; + } + + template <class T> + T ReadMemory(u64 vaddr) { + T ret{}; + if (boost::icl::contains(mapped_ranges, vaddr)) { + memory.ReadBlock(vaddr, &ret, sizeof(T)); + } else if (vaddr + sizeof(T) > local_memory.size()) { + LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr); + } else { + std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T)); + } + return ret; + } + + template <class T> + bool WriteMemory(u64 vaddr, const T value) { + if (boost::icl::contains(mapped_ranges, vaddr)) { + memory.WriteBlock(vaddr, &value, sizeof(T)); + } else if (vaddr + sizeof(T) > local_memory.size()) { + LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr); + } else { + std::memcpy(local_memory.data() + vaddr, &value, sizeof(T)); + } + return true; + } + +private: + Core::Memory::Memory& memory; + std::vector<u8>& local_memory; + IntervalSet& mapped_ranges; + JITContextImpl& parent; +}; + +class JITContextImpl { +public: + explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} { + callbacks = + std::make_unique<DynarmicCallbacks64>(memory, local_memory, mapped_ranges, *this); + user_config.callbacks = callbacks.get(); + jit = std::make_unique<Dynarmic::A64::Jit>(user_config); + } + + bool LoadNRO(std::span<const u8> data) { + local_memory.clear(); + local_memory.insert(local_memory.end(), data.begin(), data.end()); + + if (FixupRelocations()) { + InsertHelperFunctions(); + InsertStack(); + return true; + } else { + return false; + } + } + + bool FixupRelocations() { + const VAddr mod_offset{callbacks->MemoryRead32(4)}; + if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + return false; + } + + VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; + VAddr rela_dyn = 0; + size_t num_rela = 0; + while (true) { + const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; + dynamic_offset += sizeof(Elf64_Dyn); + + if (!dyn.d_tag) { + break; + } + if (dyn.d_tag == DT_RELA) { + rela_dyn = dyn.d_un; + } + if (dyn.d_tag == DT_RELASZ) { + num_rela = dyn.d_un / sizeof(Elf64_Rela); + } + } + + for (size_t i = 0; i < num_rela; i++) { + const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))}; + if (Elf64_RelaType(&rela) != R_AARCH64_RELATIVE) { + continue; + } + const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; + callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); + } + + return true; + } + + void InsertHelperFunctions() { + for (const auto& [name, contents] : HELPER_FUNCTIONS) { + helpers[name] = local_memory.size(); + local_memory.insert(local_memory.end(), contents.begin(), contents.end()); + } + } + + void InsertStack() { + const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - + local_memory.size()}; + local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); + top_of_stack = local_memory.size(); + heap_pointer = top_of_stack; + } + + void MapProcessMemory(VAddr dest_address, std::size_t size) { + mapped_ranges.add(IntervalType{dest_address, dest_address + size}); + } + + void PushArgument(const void* data, size_t size) { + const size_t num_words = Common::DivCeil(size, sizeof(u64)); + const size_t current_pos = argument_stack.size(); + argument_stack.insert(argument_stack.end(), num_words, 0); + std::memcpy(argument_stack.data() + current_pos, data, size); + } + + void SetupArguments() { + for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { + jit->SetRegister(i, argument_stack[i]); + } + if (argument_stack.size() > 8) { + const VAddr new_sp = Common::AlignDown( + top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); + for (size_t i = 8; i < argument_stack.size(); i++) { + callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]); + } + jit->SetSP(new_sp); + } + argument_stack.clear(); + heap_pointer = top_of_stack; + } + + u64 CallFunction(VAddr func) { + jit->SetRegister(30, helpers["_stop"]); + jit->SetSP(top_of_stack); + SetupArguments(); + + jit->SetPC(func); + jit->Run(); + return jit->GetRegister(0); + } + + VAddr GetHelper(const std::string& name) { + return helpers[name]; + } + + VAddr AddHeap(const void* data, size_t size) { + const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; + if (heap_pointer + num_bytes > local_memory.size()) { + local_memory.insert(local_memory.end(), + (heap_pointer + num_bytes) - local_memory.size(), 0); + } + const VAddr location{heap_pointer}; + std::memcpy(local_memory.data() + location, data, size); + heap_pointer += num_bytes; + return location; + } + + void GetHeap(VAddr location, void* data, size_t size) { + std::memcpy(data, local_memory.data() + location, size); + } + + std::unique_ptr<DynarmicCallbacks64> callbacks; + std::vector<u8> local_memory; + std::vector<u64> argument_stack; + IntervalSet mapped_ranges; + Dynarmic::A64::UserConfig user_config; + std::unique_ptr<Dynarmic::A64::Jit> jit; + std::map<std::string, VAddr, std::less<>> helpers; + Core::Memory::Memory& memory; + VAddr top_of_stack; + VAddr heap_pointer; +}; + +void DynarmicCallbacks64::CallSVC(u32 swi) { + switch (swi) { + case 0: + parent.jit->HaltExecution(); + break; + + case 1: { + // X0 contains a char* for a symbol to resolve + std::string name{MemoryReadCString(parent.jit->GetRegister(0))}; + const auto helper{parent.helpers[name]}; + + if (helper != 0) { + parent.jit->SetRegister(0, helper); + } else { + LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); + parent.jit->SetRegister(0, parent.helpers["_panic"]); + } + break; + } + + case 2: + default: + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + break; + } +} + +void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) { + LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc); + parent.jit->HaltExecution(); +} + +void DynarmicCallbacks64::InterpreterFallback(u64 pc, size_t num_instructions) { + LOG_CRITICAL(Service_JIT, "Unimplemented instruction PC @ {:08x}", pc); + parent.jit->HaltExecution(); +} + +JITContext::JITContext(Core::Memory::Memory& memory) + : impl{std::make_unique<JITContextImpl>(memory)} {} + +JITContext::~JITContext() {} + +bool JITContext::LoadNRO(std::span<const u8> data) { + return impl->LoadNRO(data); +} + +void JITContext::MapProcessMemory(VAddr dest_address, std::size_t size) { + impl->MapProcessMemory(dest_address, size); +} + +u64 JITContext::CallFunction(VAddr func) { + return impl->CallFunction(func); +} + +void JITContext::PushArgument(const void* data, size_t size) { + impl->PushArgument(data, size); +} + +VAddr JITContext::GetHelper(const std::string& name) { + return impl->GetHelper(name); +} + +VAddr JITContext::AddHeap(const void* data, size_t size) { + return impl->AddHeap(data, size); +} + +void JITContext::GetHeap(VAddr location, void* data, size_t size) { + impl->GetHeap(location, data, size); +} + +} // namespace Service::JIT diff --git a/src/core/hle/service/jit/jit_context.h b/src/core/hle/service/jit/jit_context.h new file mode 100644 index 000000000..d8bf76cff --- /dev/null +++ b/src/core/hle/service/jit/jit_context.h @@ -0,0 +1,65 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <span> +#include <string> + +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace Service::JIT { + +class JITContextImpl; + +class JITContext { +public: + explicit JITContext(Core::Memory::Memory& memory); + ~JITContext(); + + [[nodiscard]] bool LoadNRO(std::span<const u8> data); + void MapProcessMemory(VAddr dest_address, std::size_t size); + + template <typename T, typename... Ts> + u64 CallFunction(VAddr func, T argument, Ts... rest) { + static_assert(std::is_trivially_copyable_v<T>); + PushArgument(&argument, sizeof(argument)); + + if constexpr (sizeof...(rest) > 0) { + return CallFunction(func, rest...); + } else { + return CallFunction(func); + } + } + + u64 CallFunction(VAddr func); + VAddr GetHelper(const std::string& name); + + template <typename T> + VAddr AddHeap(T argument) { + return AddHeap(&argument, sizeof(argument)); + } + VAddr AddHeap(const void* data, size_t size); + + template <typename T> + T GetHeap(VAddr location) { + static_assert(std::is_trivially_copyable_v<T>); + T result; + GetHeap(location, &result, sizeof(result)); + return result; + } + void GetHeap(VAddr location, void* data, size_t size); + +private: + std::unique_ptr<JITContextImpl> impl; + + void PushArgument(const void* data, size_t size); +}; + +} // namespace Service::JIT diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 2477c5612..42f9cf811 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -160,7 +160,8 @@ public: class RelocatableObject final : public ServiceFramework<RelocatableObject> { public: - explicit RelocatableObject(Core::System& system_) : ServiceFramework{system_, "ldr:ro"} { + explicit RelocatableObject(Core::System& system_) + : ServiceFramework{system_, "ldr:ro", ServiceThreadType::CreateNew} { // clang-format off static const FunctionInfo functions[] = { {0, &RelocatableObject::LoadModule, "LoadModule"}, @@ -389,8 +390,12 @@ public: if (bss_size) { auto block_guard = detail::ScopeExit([&] { - page_table.UnmapCodeMemory(addr + nro_size, bss_addr, bss_size); - page_table.UnmapCodeMemory(addr, nro_addr, nro_size); + page_table.UnmapCodeMemory( + addr + nro_size, bss_addr, bss_size, + Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange); + page_table.UnmapCodeMemory( + addr, nro_addr, nro_size, + Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange); }); const ResultCode result{ @@ -570,17 +575,21 @@ public: auto& page_table{system.CurrentProcess()->PageTable()}; if (info.bss_size != 0) { - CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size + - info.ro_size + info.data_size, - info.bss_address, info.bss_size)); + CASCADE_CODE(page_table.UnmapCodeMemory( + info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address, + info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); } - CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size + info.ro_size, - info.src_addr + info.text_size + info.ro_size, - info.data_size)); - CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size, - info.src_addr + info.text_size, info.ro_size)); - CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address, info.src_addr, info.text_size)); + CASCADE_CODE(page_table.UnmapCodeMemory( + info.nro_address + info.text_size + info.ro_size, + info.src_addr + info.text_size + info.ro_size, info.data_size, + Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); + CASCADE_CODE(page_table.UnmapCodeMemory( + info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size, + Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); + CASCADE_CODE(page_table.UnmapCodeMemory( + info.nro_address, info.src_addr, info.text_size, + Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); return ResultSuccess; } diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 0a57c3cde..188231615 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -1,4 +1,4 @@ -// Copyright 2020 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -12,7 +12,6 @@ #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/raw_data.h" -#include "core/hle/service/mii/types.h" namespace Service::Mii { diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 6999d15b1..5d134c425 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -1,315 +1,16 @@ -// Copyright 2020 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <array> #include <vector> -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/uuid.h" + #include "core/hle/result.h" #include "core/hle/service/mii/types.h" namespace Service::Mii { -enum class Source : u32 { - Database = 0, - Default = 1, - Account = 2, - Friend = 3, -}; - -enum class SourceFlag : u32 { - None = 0, - Database = 1 << 0, - Default = 1 << 1, -}; -DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); - -struct MiiInfo { - Common::UUID uuid; - std::array<char16_t, 11> name; - u8 font_region; - u8 favorite_color; - u8 gender; - u8 height; - u8 build; - u8 type; - u8 region_move; - u8 faceline_type; - u8 faceline_color; - u8 faceline_wrinkle; - u8 faceline_make; - u8 hair_type; - u8 hair_color; - u8 hair_flip; - u8 eye_type; - u8 eye_color; - u8 eye_scale; - u8 eye_aspect; - u8 eye_rotate; - u8 eye_x; - u8 eye_y; - u8 eyebrow_type; - u8 eyebrow_color; - u8 eyebrow_scale; - u8 eyebrow_aspect; - u8 eyebrow_rotate; - u8 eyebrow_x; - u8 eyebrow_y; - u8 nose_type; - u8 nose_scale; - u8 nose_y; - u8 mouth_type; - u8 mouth_color; - u8 mouth_scale; - u8 mouth_aspect; - u8 mouth_y; - u8 beard_color; - u8 beard_type; - u8 mustache_type; - u8 mustache_scale; - u8 mustache_y; - u8 glasses_type; - u8 glasses_color; - u8 glasses_scale; - u8 glasses_y; - u8 mole_type; - u8 mole_scale; - u8 mole_x; - u8 mole_y; - u8 padding; - - std::u16string Name() const; -}; -static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); -static_assert(std::has_unique_object_representations_v<MiiInfo>, - "All bits of MiiInfo must contribute to its value."); - -#pragma pack(push, 4) - -struct MiiInfoElement { - MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} - - MiiInfo info{}; - Source source{}; -}; -static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); - -struct MiiStoreBitFields { - union { - u32 word_0{}; - - BitField<0, 8, u32> hair_type; - BitField<8, 7, u32> height; - BitField<15, 1, u32> mole_type; - BitField<16, 7, u32> build; - BitField<23, 1, HairFlip> hair_flip; - BitField<24, 7, u32> hair_color; - BitField<31, 1, u32> type; - }; - - union { - u32 word_1{}; - - BitField<0, 7, u32> eye_color; - BitField<7, 1, Gender> gender; - BitField<8, 7, u32> eyebrow_color; - BitField<16, 7, u32> mouth_color; - BitField<24, 7, u32> beard_color; - }; - - union { - u32 word_2{}; - - BitField<0, 7, u32> glasses_color; - BitField<8, 6, u32> eye_type; - BitField<14, 2, u32> region_move; - BitField<16, 6, u32> mouth_type; - BitField<22, 2, FontRegion> font_region; - BitField<24, 5, u32> eye_y; - BitField<29, 3, u32> glasses_scale; - }; - - union { - u32 word_3{}; - - BitField<0, 5, u32> eyebrow_type; - BitField<5, 3, MustacheType> mustache_type; - BitField<8, 5, u32> nose_type; - BitField<13, 3, BeardType> beard_type; - BitField<16, 5, u32> nose_y; - BitField<21, 3, u32> mouth_aspect; - BitField<24, 5, u32> mouth_y; - BitField<29, 3, u32> eyebrow_aspect; - }; - - union { - u32 word_4{}; - - BitField<0, 5, u32> mustache_y; - BitField<5, 3, u32> eye_rotate; - BitField<8, 5, u32> glasses_y; - BitField<13, 3, u32> eye_aspect; - BitField<16, 5, u32> mole_x; - BitField<21, 3, u32> eye_scale; - BitField<24, 5, u32> mole_y; - }; - - union { - u32 word_5{}; - - BitField<0, 5, u32> glasses_type; - BitField<8, 4, u32> favorite_color; - BitField<12, 4, u32> faceline_type; - BitField<16, 4, u32> faceline_color; - BitField<20, 4, u32> faceline_wrinkle; - BitField<24, 4, u32> faceline_makeup; - BitField<28, 4, u32> eye_x; - }; - - union { - u32 word_6{}; - - BitField<0, 4, u32> eyebrow_scale; - BitField<4, 4, u32> eyebrow_rotate; - BitField<8, 4, u32> eyebrow_x; - BitField<12, 4, u32> eyebrow_y; - BitField<16, 4, u32> nose_scale; - BitField<20, 4, u32> mouth_scale; - BitField<24, 4, u32> mustache_scale; - BitField<28, 4, u32> mole_scale; - }; -}; -static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); -static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, - "MiiStoreBitFields is not trivially copyable."); - -struct MiiStoreData { - using Name = std::array<char16_t, 10>; - - MiiStoreData(); - MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id); - - // This corresponds to the above structure MiiStoreBitFields. I did it like this because the - // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is - // not suitable for our uses. - struct { - std::array<u8, 0x1C> data{}; - static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); - - Name name{}; - Common::UUID uuid{}; - } data; - - u16 data_crc{}; - u16 device_crc{}; -}; -static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); - -struct MiiStoreDataElement { - MiiStoreData data{}; - Source source{}; -}; -static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); - -struct MiiDatabase { - u32 magic{}; // 'NFDB' - std::array<MiiStoreData, 0x64> miis{}; - INSERT_PADDING_BYTES(1); - u8 count{}; - u16 crc{}; -}; -static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); - -struct RandomMiiValues { - std::array<u8, 0xbc> values{}; -}; -static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); - -struct RandomMiiData4 { - Gender gender{}; - Age age{}; - Race race{}; - u32 values_count{}; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); - -struct RandomMiiData3 { - u32 arg_1; - u32 arg_2; - u32 values_count; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); - -struct RandomMiiData2 { - u32 arg_1; - u32 values_count; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); - -struct DefaultMii { - u32 face_type{}; - u32 face_color{}; - u32 face_wrinkle{}; - u32 face_makeup{}; - u32 hair_type{}; - u32 hair_color{}; - u32 hair_flip{}; - u32 eye_type{}; - u32 eye_color{}; - u32 eye_scale{}; - u32 eye_aspect{}; - u32 eye_rotate{}; - u32 eye_x{}; - u32 eye_y{}; - u32 eyebrow_type{}; - u32 eyebrow_color{}; - u32 eyebrow_scale{}; - u32 eyebrow_aspect{}; - u32 eyebrow_rotate{}; - u32 eyebrow_x{}; - u32 eyebrow_y{}; - u32 nose_type{}; - u32 nose_scale{}; - u32 nose_y{}; - u32 mouth_type{}; - u32 mouth_color{}; - u32 mouth_scale{}; - u32 mouth_aspect{}; - u32 mouth_y{}; - u32 mustache_type{}; - u32 beard_type{}; - u32 beard_color{}; - u32 mustache_scale{}; - u32 mustache_y{}; - u32 glasses_type{}; - u32 glasses_color{}; - u32 glasses_scale{}; - u32 glasses_y{}; - u32 mole_type{}; - u32 mole_scale{}; - u32 mole_x{}; - u32 mole_y{}; - u32 height{}; - u32 weight{}; - Gender gender{}; - u32 favorite_color{}; - u32 region{}; - FontRegion font_region{}; - u32 type{}; - INSERT_PADDING_WORDS(5); -}; -static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); - -#pragma pack(pop) - // The Mii manager is responsible for loading and storing the Miis to the database in NAND along // with providing an easy interface for HLE emulation of the mii service. class MiiManager { diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h index bd90c2162..2e39c0d4f 100644 --- a/src/core/hle/service/mii/raw_data.h +++ b/src/core/hle/service/mii/raw_data.h @@ -6,7 +6,7 @@ #include <array> -#include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/types.h" namespace Service::Mii::RawData { diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h index d65a1055e..5580b8c6a 100644 --- a/src/core/hle/service/mii/types.h +++ b/src/core/hle/service/mii/types.h @@ -1,11 +1,16 @@ -// Copyright 2020 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include <array> +#include <type_traits> + +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/uuid.h" namespace Service::Mii { @@ -25,7 +30,11 @@ enum class BeardType : u32 { Beard5, }; -enum class BeardAndMustacheFlag : u32 { Beard = 1, Mustache, All = Beard | Mustache }; +enum class BeardAndMustacheFlag : u32 { + Beard = 1, + Mustache, + All = Beard | Mustache, +}; DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); enum class FontRegion : u32 { @@ -64,4 +73,298 @@ enum class Race : u32 { All, }; +enum class Source : u32 { + Database = 0, + Default = 1, + Account = 2, + Friend = 3, +}; + +enum class SourceFlag : u32 { + None = 0, + Database = 1 << 0, + Default = 1 << 1, +}; +DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); + +struct MiiInfo { + Common::UUID uuid; + std::array<char16_t, 11> name; + u8 font_region; + u8 favorite_color; + u8 gender; + u8 height; + u8 build; + u8 type; + u8 region_move; + u8 faceline_type; + u8 faceline_color; + u8 faceline_wrinkle; + u8 faceline_make; + u8 hair_type; + u8 hair_color; + u8 hair_flip; + u8 eye_type; + u8 eye_color; + u8 eye_scale; + u8 eye_aspect; + u8 eye_rotate; + u8 eye_x; + u8 eye_y; + u8 eyebrow_type; + u8 eyebrow_color; + u8 eyebrow_scale; + u8 eyebrow_aspect; + u8 eyebrow_rotate; + u8 eyebrow_x; + u8 eyebrow_y; + u8 nose_type; + u8 nose_scale; + u8 nose_y; + u8 mouth_type; + u8 mouth_color; + u8 mouth_scale; + u8 mouth_aspect; + u8 mouth_y; + u8 beard_color; + u8 beard_type; + u8 mustache_type; + u8 mustache_scale; + u8 mustache_y; + u8 glasses_type; + u8 glasses_color; + u8 glasses_scale; + u8 glasses_y; + u8 mole_type; + u8 mole_scale; + u8 mole_x; + u8 mole_y; + u8 padding; +}; +static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); +static_assert(std::has_unique_object_representations_v<MiiInfo>, + "All bits of MiiInfo must contribute to its value."); + +#pragma pack(push, 4) + +struct MiiInfoElement { + MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} + + MiiInfo info{}; + Source source{}; +}; +static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); + +struct MiiStoreBitFields { + union { + u32 word_0{}; + + BitField<0, 8, u32> hair_type; + BitField<8, 7, u32> height; + BitField<15, 1, u32> mole_type; + BitField<16, 7, u32> build; + BitField<23, 1, HairFlip> hair_flip; + BitField<24, 7, u32> hair_color; + BitField<31, 1, u32> type; + }; + + union { + u32 word_1{}; + + BitField<0, 7, u32> eye_color; + BitField<7, 1, Gender> gender; + BitField<8, 7, u32> eyebrow_color; + BitField<16, 7, u32> mouth_color; + BitField<24, 7, u32> beard_color; + }; + + union { + u32 word_2{}; + + BitField<0, 7, u32> glasses_color; + BitField<8, 6, u32> eye_type; + BitField<14, 2, u32> region_move; + BitField<16, 6, u32> mouth_type; + BitField<22, 2, FontRegion> font_region; + BitField<24, 5, u32> eye_y; + BitField<29, 3, u32> glasses_scale; + }; + + union { + u32 word_3{}; + + BitField<0, 5, u32> eyebrow_type; + BitField<5, 3, MustacheType> mustache_type; + BitField<8, 5, u32> nose_type; + BitField<13, 3, BeardType> beard_type; + BitField<16, 5, u32> nose_y; + BitField<21, 3, u32> mouth_aspect; + BitField<24, 5, u32> mouth_y; + BitField<29, 3, u32> eyebrow_aspect; + }; + + union { + u32 word_4{}; + + BitField<0, 5, u32> mustache_y; + BitField<5, 3, u32> eye_rotate; + BitField<8, 5, u32> glasses_y; + BitField<13, 3, u32> eye_aspect; + BitField<16, 5, u32> mole_x; + BitField<21, 3, u32> eye_scale; + BitField<24, 5, u32> mole_y; + }; + + union { + u32 word_5{}; + + BitField<0, 5, u32> glasses_type; + BitField<8, 4, u32> favorite_color; + BitField<12, 4, u32> faceline_type; + BitField<16, 4, u32> faceline_color; + BitField<20, 4, u32> faceline_wrinkle; + BitField<24, 4, u32> faceline_makeup; + BitField<28, 4, u32> eye_x; + }; + + union { + u32 word_6{}; + + BitField<0, 4, u32> eyebrow_scale; + BitField<4, 4, u32> eyebrow_rotate; + BitField<8, 4, u32> eyebrow_x; + BitField<12, 4, u32> eyebrow_y; + BitField<16, 4, u32> nose_scale; + BitField<20, 4, u32> mouth_scale; + BitField<24, 4, u32> mustache_scale; + BitField<28, 4, u32> mole_scale; + }; +}; +static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); +static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, + "MiiStoreBitFields is not trivially copyable."); + +struct MiiStoreData { + using Name = std::array<char16_t, 10>; + + MiiStoreData(); + MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, + const Common::UUID& user_id); + + // This corresponds to the above structure MiiStoreBitFields. I did it like this because the + // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is + // not suitable for our uses. + struct { + std::array<u8, 0x1C> data{}; + static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); + + Name name{}; + Common::UUID uuid{}; + } data; + + u16 data_crc{}; + u16 device_crc{}; +}; +static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); + +struct MiiStoreDataElement { + MiiStoreData data{}; + Source source{}; +}; +static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); + +struct MiiDatabase { + u32 magic{}; // 'NFDB' + std::array<MiiStoreData, 0x64> miis{}; + INSERT_PADDING_BYTES(1); + u8 count{}; + u16 crc{}; +}; +static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); + +struct RandomMiiValues { + std::array<u8, 0xbc> values{}; +}; +static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); + +struct RandomMiiData4 { + Gender gender{}; + Age age{}; + Race race{}; + u32 values_count{}; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); + +struct RandomMiiData3 { + u32 arg_1; + u32 arg_2; + u32 values_count; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); + +struct RandomMiiData2 { + u32 arg_1; + u32 values_count; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); + +struct DefaultMii { + u32 face_type{}; + u32 face_color{}; + u32 face_wrinkle{}; + u32 face_makeup{}; + u32 hair_type{}; + u32 hair_color{}; + u32 hair_flip{}; + u32 eye_type{}; + u32 eye_color{}; + u32 eye_scale{}; + u32 eye_aspect{}; + u32 eye_rotate{}; + u32 eye_x{}; + u32 eye_y{}; + u32 eyebrow_type{}; + u32 eyebrow_color{}; + u32 eyebrow_scale{}; + u32 eyebrow_aspect{}; + u32 eyebrow_rotate{}; + u32 eyebrow_x{}; + u32 eyebrow_y{}; + u32 nose_type{}; + u32 nose_scale{}; + u32 nose_y{}; + u32 mouth_type{}; + u32 mouth_color{}; + u32 mouth_scale{}; + u32 mouth_aspect{}; + u32 mouth_y{}; + u32 mustache_type{}; + u32 beard_type{}; + u32 beard_color{}; + u32 mustache_scale{}; + u32 mustache_y{}; + u32 glasses_type{}; + u32 glasses_color{}; + u32 glasses_scale{}; + u32 glasses_y{}; + u32 mole_type{}; + u32 mole_scale{}; + u32 mole_x{}; + u32 mole_y{}; + u32 height{}; + u32 weight{}; + Gender gender{}; + u32 favorite_color{}; + u32 region{}; + FontRegion font_region{}; + u32 type{}; + INSERT_PADDING_WORDS(5); +}; +static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); + +#pragma pack(pop) + } // namespace Service::Mii diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 513107715..dab99b675 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -12,6 +12,7 @@ #include "core/hid/hid_types.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp_user.h" diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 022f13b29..ab652f635 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -9,7 +9,7 @@ #include "common/common_funcs.h" #include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/types.h" #include "core/hle/service/service.h" namespace Kernel { diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 68f1e9060..9fad45fe1 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -38,18 +38,16 @@ NvResult nvdisp_disp0::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& void nvdisp_disp0::OnOpen(DeviceFD fd) {} void nvdisp_disp0::OnClose(DeviceFD fd) {} -void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, - u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform, +void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, + u32 height, u32 stride, android::BufferTransformFlags transform, const Common::Rectangle<int>& crop_rect) { const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle); LOG_TRACE(Service, "Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}", addr, offset, width, height, stride, format); - const auto pixel_format = static_cast<Tegra::FramebufferConfig::PixelFormat>(format); - const auto transform_flags = static_cast<Tegra::FramebufferConfig::TransformFlags>(transform); - const Tegra::FramebufferConfig framebuffer{addr, offset, width, height, - stride, pixel_format, transform_flags, crop_rect}; + const Tegra::FramebufferConfig framebuffer{addr, offset, width, height, + stride, format, transform, crop_rect}; system.GetPerfStats().EndSystemFrame(); system.GPU().SwapBuffers(&framebuffer); diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index de01e1d5f..30b5da429 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -9,7 +9,8 @@ #include "common/common_types.h" #include "common/math_util.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -#include "core/hle/service/nvflinger/buffer_queue.h" +#include "core/hle/service/nvflinger/buffer_transform_flags.h" +#include "core/hle/service/nvflinger/pixel_format.h" namespace Service::Nvidia::Devices { @@ -31,8 +32,8 @@ public: void OnClose(DeviceFD fd) override; /// Performs a screen flip, drawing the buffer pointed to by the handle. - void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, - NVFlinger::BufferQueue::BufferTransformFlags transform, + void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height, + u32 stride, android::BufferTransformFlags transform, const Common::Rectangle<int>& crop_rect); private: diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index f9b82b504..44c54c665 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -134,7 +134,7 @@ NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector } EventState status = events_interface.status[event_id]; - const bool bad_parameter = status != EventState::Free && status != EventState::Registered; + const bool bad_parameter = status == EventState::Busy; if (bad_parameter) { std::memcpy(output.data(), ¶ms, sizeof(params)); return NvResult::BadParameter; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 0a043e386..dde5b1507 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -187,7 +187,7 @@ NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::ve return NvResult::Success; } -static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) { +static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) { return { Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1, Tegra::SubmissionMode::Increasing), @@ -198,7 +198,8 @@ static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) { }; } -static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, u32 add_increment) { +static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence, + u32 add_increment) { std::vector<Tegra::CommandHeader> result{ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1, Tegra::SubmissionMode::Increasing), @@ -213,7 +214,7 @@ static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, return result; } -static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(Fence fence, +static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence, u32 add_increment) { std::vector<Tegra::CommandHeader> result{ Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1, diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 3e4f3b6a7..b2e943e45 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -109,7 +109,7 @@ private: static_assert(sizeof(IoctlGetErrorNotification) == 16, "IoctlGetErrorNotification is incorrect size"); - static_assert(sizeof(Fence) == 8, "Fence is incorrect size"); + static_assert(sizeof(NvFence) == 8, "Fence is incorrect size"); struct IoctlAllocGpfifoEx { u32_le num_entries{}; @@ -127,7 +127,7 @@ private: u32_le num_entries{}; // in u32_le flags{}; // in u32_le unk0{}; // in (1 works) - Fence fence_out{}; // out + NvFence fence_out{}; // out u32_le unk1{}; // in u32_le unk2{}; // in u32_le unk3{}; // in @@ -153,13 +153,13 @@ private: BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt BitField<8, 1, u32_le> increment; // increment the returned fence } flags; - Fence fence_out{}; // returned new fence object for others to wait on + NvFence fence_out{}; // returned new fence object for others to wait on u32 AddIncrementValue() const { return flags.add_increment.Value() << 1; } }; - static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence), + static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence), "IoctlSubmitGpfifo is incorrect size"); struct IoctlGetWaitbase { @@ -194,7 +194,7 @@ private: std::shared_ptr<nvmap> nvmap_dev; SyncpointManager& syncpoint_manager; - Fence channel_fence; + NvFence channel_fence; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h index 5ab221fc1..3069c3c80 100644 --- a/src/core/hle/service/nvdrv/nvdata.h +++ b/src/core/hle/service/nvdrv/nvdata.h @@ -16,17 +16,11 @@ using DeviceFD = s32; constexpr DeviceFD INVALID_NVDRV_FD = -1; -struct Fence { +struct NvFence { s32 id; u32 value; }; - -static_assert(sizeof(Fence) == 8, "Fence has wrong size"); - -struct MultiFence { - u32 num_fences; - std::array<Fence, 4> fences; -}; +static_assert(sizeof(NvFence) == 8, "NvFence has wrong size"); enum class NvResult : u32 { Success = 0x0, diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index a5af5b785..11cf6c0d1 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -12,6 +12,7 @@ #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/syncpoint_manager.h" +#include "core/hle/service/nvflinger/ui/fence.h" #include "core/hle/service/service.h" namespace Core { @@ -37,7 +38,7 @@ class nvdevice; /// Represents an Nvidia event struct NvEvent { Kernel::KEvent* event{}; - Fence fence{}; + NvFence fence{}; }; struct EventInterface { diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp index c16babe14..8467b50e4 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp +++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp @@ -26,7 +26,7 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) { rb.Push<DeviceFD>(0); rb.PushEnum(NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + LOG_ERROR(Service_NVDRV, "NvServices is not initialized!"); return; } @@ -61,7 +61,7 @@ void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) { if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + LOG_ERROR(Service_NVDRV, "NvServices is not initialized!"); return; } @@ -87,7 +87,7 @@ void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + LOG_ERROR(Service_NVDRV, "NvServices is not initialized!"); return; } @@ -114,7 +114,7 @@ void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + LOG_ERROR(Service_NVDRV, "NvServices is not initialized!"); return; } @@ -139,7 +139,7 @@ void NVDRV::Close(Kernel::HLERequestContext& ctx) { if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + LOG_ERROR(Service_NVDRV, "NvServices is not initialized!"); return; } @@ -170,7 +170,7 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + LOG_ERROR(Service_NVDRV, "NvServices is not initialized!"); return; } @@ -230,7 +230,7 @@ void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { } NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name) - : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} { + : ServiceFramework{system_, name, ServiceThreadType::CreateNew}, nvdrv{std::move(nvdrv_)} { static const FunctionInfo functions[] = { {0, &NVDRV::Open, "Open"}, {1, &NVDRV::Ioctl1, "Ioctl"}, diff --git a/src/core/hle/service/nvflinger/binder.h b/src/core/hle/service/nvflinger/binder.h new file mode 100644 index 000000000..7d0d4d819 --- /dev/null +++ b/src/core/hle/service/nvflinger/binder.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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 void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, + u32 flags) = 0; + virtual Kernel::KReadableEvent& GetNativeHandle() = 0; +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/buffer_item.h b/src/core/hle/service/nvflinger/buffer_item.h new file mode 100644 index 000000000..64b82b851 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_item.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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 diff --git a/src/core/hle/service/nvflinger/buffer_item_consumer.cpp b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp new file mode 100644 index 000000000..d7ee5362b --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2012 The Android Open Source Project +// Parts of this implementation were base 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, 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 diff --git a/src/core/hle/service/nvflinger/buffer_item_consumer.h b/src/core/hle/service/nvflinger/buffer_item_consumer.h new file mode 100644 index 000000000..536db81e2 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_item_consumer.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2012 The Android Open Source Project +// Parts of this implementation were base 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, Fence& release_fence); +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp deleted file mode 100644 index 5fead6d1b..000000000 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/k_writable_event.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/nvflinger/buffer_queue.h" - -namespace Service::NVFlinger { - -BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_, - KernelHelpers::ServiceContext& service_context_) - : id(id_), layer_id(layer_id_), service_context{service_context_} { - buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); -} - -BufferQueue::~BufferQueue() { - service_context.CloseEvent(buffer_wait_event); -} - -void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { - ASSERT(slot < buffer_slots); - LOG_WARNING(Service, "Adding graphics buffer {}", slot); - - { - std::unique_lock lock{free_buffers_mutex}; - free_buffers.push_back(slot); - } - free_buffers_condition.notify_one(); - - buffers[slot] = { - .slot = slot, - .status = Buffer::Status::Free, - .igbp_buffer = igbp_buffer, - .transform = {}, - .crop_rect = {}, - .swap_interval = 0, - .multi_fence = {}, - }; - - buffer_wait_event->GetWritableEvent().Signal(); -} - -std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, - u32 height) { - // Wait for first request before trying to dequeue - { - std::unique_lock lock{free_buffers_mutex}; - free_buffers_condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; }); - } - - if (!is_connect) { - // Buffer was disconnected while the thread was blocked, this is most likely due to - // emulation being stopped - return std::nullopt; - } - - std::unique_lock lock{free_buffers_mutex}; - - auto f_itr = free_buffers.begin(); - auto slot = buffers.size(); - - while (f_itr != free_buffers.end()) { - const Buffer& buffer = buffers[*f_itr]; - if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width && - buffer.igbp_buffer.height == height) { - slot = *f_itr; - free_buffers.erase(f_itr); - break; - } - ++f_itr; - } - if (slot == buffers.size()) { - return std::nullopt; - } - buffers[slot].status = Buffer::Status::Dequeued; - return {{buffers[slot].slot, &buffers[slot].multi_fence}}; -} - -const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { - ASSERT(slot < buffers.size()); - ASSERT(buffers[slot].status == Buffer::Status::Dequeued); - ASSERT(buffers[slot].slot == slot); - - return buffers[slot].igbp_buffer; -} - -void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform, - const Common::Rectangle<int>& crop_rect, u32 swap_interval, - Service::Nvidia::MultiFence& multi_fence) { - ASSERT(slot < buffers.size()); - ASSERT(buffers[slot].status == Buffer::Status::Dequeued); - ASSERT(buffers[slot].slot == slot); - - buffers[slot].status = Buffer::Status::Queued; - buffers[slot].transform = transform; - buffers[slot].crop_rect = crop_rect; - buffers[slot].swap_interval = swap_interval; - buffers[slot].multi_fence = multi_fence; - std::unique_lock lock{queue_sequence_mutex}; - queue_sequence.push_back(slot); -} - -void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) { - ASSERT(slot < buffers.size()); - ASSERT(buffers[slot].status != Buffer::Status::Free); - ASSERT(buffers[slot].slot == slot); - - buffers[slot].status = Buffer::Status::Free; - buffers[slot].multi_fence = multi_fence; - buffers[slot].swap_interval = 0; - - { - std::unique_lock lock{free_buffers_mutex}; - free_buffers.push_back(slot); - } - free_buffers_condition.notify_one(); - - buffer_wait_event->GetWritableEvent().Signal(); -} - -std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() { - std::unique_lock lock{queue_sequence_mutex}; - std::size_t buffer_slot = buffers.size(); - // Iterate to find a queued buffer matching the requested slot. - while (buffer_slot == buffers.size() && !queue_sequence.empty()) { - const auto slot = static_cast<std::size_t>(queue_sequence.front()); - ASSERT(slot < buffers.size()); - if (buffers[slot].status == Buffer::Status::Queued) { - ASSERT(buffers[slot].slot == slot); - buffer_slot = slot; - } - queue_sequence.pop_front(); - } - if (buffer_slot == buffers.size()) { - return std::nullopt; - } - buffers[buffer_slot].status = Buffer::Status::Acquired; - return {{buffers[buffer_slot]}}; -} - -void BufferQueue::ReleaseBuffer(u32 slot) { - ASSERT(slot < buffers.size()); - ASSERT(buffers[slot].status == Buffer::Status::Acquired); - ASSERT(buffers[slot].slot == slot); - - buffers[slot].status = Buffer::Status::Free; - { - std::unique_lock lock{free_buffers_mutex}; - free_buffers.push_back(slot); - } - free_buffers_condition.notify_one(); - - buffer_wait_event->GetWritableEvent().Signal(); -} - -void BufferQueue::Connect() { - std::unique_lock lock{queue_sequence_mutex}; - queue_sequence.clear(); - is_connect = true; -} - -void BufferQueue::Disconnect() { - buffers.fill({}); - { - std::unique_lock lock{queue_sequence_mutex}; - queue_sequence.clear(); - } - buffer_wait_event->GetWritableEvent().Signal(); - is_connect = false; - free_buffers_condition.notify_one(); -} - -u32 BufferQueue::Query(QueryType type) { - LOG_WARNING(Service, "(STUBBED) called type={}", type); - - switch (type) { - case QueryType::NativeWindowFormat: - return static_cast<u32>(PixelFormat::RGBA8888); - case QueryType::NativeWindowWidth: - case QueryType::NativeWindowHeight: - break; - case QueryType::NativeWindowMinUndequeuedBuffers: - return 0; - case QueryType::NativeWindowConsumerUsageBits: - return 0; - } - UNIMPLEMENTED_MSG("Unimplemented query type={}", type); - return 0; -} - -Kernel::KWritableEvent& BufferQueue::GetWritableBufferWaitEvent() { - return buffer_wait_event->GetWritableEvent(); -} - -Kernel::KReadableEvent& BufferQueue::GetBufferWaitEvent() { - return buffer_wait_event->GetReadableEvent(); -} - -} // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h deleted file mode 100644 index f2a579133..000000000 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <condition_variable> -#include <list> -#include <mutex> -#include <optional> - -#include "common/common_funcs.h" -#include "common/math_util.h" -#include "common/swap.h" -#include "core/hle/kernel/k_event.h" -#include "core/hle/kernel/k_readable_event.h" -#include "core/hle/service/nvdrv/nvdata.h" - -namespace Kernel { -class KernelCore; -class KEvent; -class KReadableEvent; -class KWritableEvent; -} // namespace Kernel - -namespace Service::KernelHelpers { -class ServiceContext; -} // namespace Service::KernelHelpers - -namespace Service::NVFlinger { - -constexpr u32 buffer_slots = 0x40; -struct IGBPBuffer { - u32_le magic; - u32_le width; - u32_le height; - u32_le stride; - u32_le format; - u32_le usage; - INSERT_PADDING_WORDS(1); - u32_le index; - INSERT_PADDING_WORDS(3); - u32_le gpu_buffer_id; - INSERT_PADDING_WORDS(6); - u32_le external_format; - INSERT_PADDING_WORDS(10); - u32_le nvmap_handle; - u32_le offset; - INSERT_PADDING_WORDS(60); -}; - -static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size"); - -class BufferQueue final { -public: - enum class QueryType { - NativeWindowWidth = 0, - NativeWindowHeight = 1, - NativeWindowFormat = 2, - /// The minimum number of buffers that must remain un-dequeued after a buffer has been - /// queued - NativeWindowMinUndequeuedBuffers = 3, - /// The consumer gralloc usage bits currently set by the consumer - NativeWindowConsumerUsageBits = 10, - }; - - explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_, - KernelHelpers::ServiceContext& service_context_); - ~BufferQueue(); - - 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, - }; - - enum class PixelFormat : u32 { - RGBA8888 = 1, - RGBX8888 = 2, - RGB888 = 3, - RGB565 = 4, - BGRA8888 = 5, - RGBA5551 = 6, - RRGBA4444 = 7, - }; - - struct Buffer { - enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 }; - - u32 slot; - Status status = Status::Free; - IGBPBuffer igbp_buffer; - BufferTransformFlags transform; - Common::Rectangle<int> crop_rect; - u32 swap_interval; - Service::Nvidia::MultiFence multi_fence; - }; - - void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer); - std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> DequeueBuffer(u32 width, - u32 height); - const IGBPBuffer& RequestBuffer(u32 slot) const; - void QueueBuffer(u32 slot, BufferTransformFlags transform, - const Common::Rectangle<int>& crop_rect, u32 swap_interval, - Service::Nvidia::MultiFence& multi_fence); - void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence); - std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer(); - void ReleaseBuffer(u32 slot); - void Connect(); - void Disconnect(); - u32 Query(QueryType type); - - u32 GetId() const { - return id; - } - - bool IsConnected() const { - return is_connect; - } - - Kernel::KWritableEvent& GetWritableBufferWaitEvent(); - - Kernel::KReadableEvent& GetBufferWaitEvent(); - -private: - BufferQueue(const BufferQueue&) = delete; - - u32 id{}; - u64 layer_id{}; - std::atomic_bool is_connect{}; - - std::list<u32> free_buffers; - std::array<Buffer, buffer_slots> buffers; - std::list<u32> queue_sequence; - Kernel::KEvent* buffer_wait_event{}; - - std::mutex free_buffers_mutex; - std::condition_variable free_buffers_condition; - - std::mutex queue_sequence_mutex; - - KernelHelpers::ServiceContext& service_context; -}; - -} // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp new file mode 100644 index 000000000..3ab9a8c05 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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/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" + +namespace Service::android { + +BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_) + : core{std::move(core_)}, slots{core->slots} {} + +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 front buffer is still being tracked, update its slot state + if (core->StillTracking(*front)) { + slots[slot].acquire_called = true; + slots[slot].needs_cleanup_on_release = false; + slots[slot].buffer_state = BufferState::Acquired; + slots[slot].fence = Fence::NoFence(); + } + + // 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; + } + + if (slots[slot].buffer_state == BufferState::Acquired) { + slots[slot].fence = release_fence; + slots[slot].buffer_state = BufferState::Free; + + listener = core->connected_producer_listener; + + LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot); + } else if (slots[slot].needs_cleanup_on_release) { + LOG_DEBUG(Service_NVFlinger, "releasing a stale buffer slot {} (state = {})", slot, + slots[slot].buffer_state); + + slots[slot].needs_cleanup_on_release = false; + + return Status::StaleBufferSlot; + } else { + LOG_ERROR(Service_NVFlinger, "attempted to release buffer slot {} but its state was {}", + slot, slots[slot].buffer_state); + + return Status::BadValue; + } + + 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 = 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 diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.h b/src/core/hle/service/nvflinger/buffer_queue_consumer.h new file mode 100644 index 000000000..8a047fe06 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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::android { + +class BufferItem; +class BufferQueueCore; +class IConsumerListener; + +class BufferQueueConsumer final { +public: + explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_); + ~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; +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.cpp b/src/core/hle/service/nvflinger/buffer_queue_core.cpp new file mode 100644 index 000000000..ec5aabaeb --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_core.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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_condition.notify_all(); +} + +bool BufferQueueCore::WaitForDequeueCondition() { + if (is_shutting_down) { + return false; + } + + dequeue_condition.wait(mutex); + + 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(); + + if (slots[slot].buffer_state == BufferState::Acquired) { + slots[slot].needs_cleanup_on_release = true; + } + + 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 diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.h b/src/core/hle/service/nvflinger/buffer_queue_core.h new file mode 100644 index 000000000..e4e0937cb --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_core.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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(); + + 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{}; + mutable std::condition_variable_any dequeue_condition; + 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 diff --git a/src/core/hle/service/nvflinger/buffer_queue_defs.h b/src/core/hle/service/nvflinger/buffer_queue_defs.h new file mode 100644 index 000000000..387d3d36a --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_defs.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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 diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp new file mode 100644 index 000000000..6f604a88e --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp @@ -0,0 +1,923 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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/k_writable_event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nvdrv/nvdrv.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_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) { + 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->GetWritableEvent().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) 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()) { + // 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::scoped_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); + 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::scoped_lock lock{core->mutex}; + core->WaitWhileAllocatingLocked(); + + Status return_flags = Status::NoError; + s32 found{}; + + const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags); + 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; + + 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->GetWritableEvent().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: + UNREACHABLE(); + 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; + } + + 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->GetWritableEvent().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->GetWritableEvent().Signal(); + + return Status::NoError; +} + +void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) { + Status status{Status::NoError}; + Parcel parcel_in{ctx.ReadBuffer()}; + Parcel 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 diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.h b/src/core/hle/service/nvflinger/buffer_queue_producer.h new file mode 100644 index 000000000..c4ca68fd3 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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; +class KWritableEvent; +} // namespace Kernel + +namespace Service::KernelHelpers { +class ServiceContext; +} // namespace Service::KernelHelpers + +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_); + ~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) 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; +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/buffer_slot.h b/src/core/hle/service/nvflinger/buffer_slot.h new file mode 100644 index 000000000..e3ea58910 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_slot.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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 needs_cleanup_on_release{}; + bool attached_by_consumer{}; + bool is_preallocated{}; +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/buffer_transform_flags.h b/src/core/hle/service/nvflinger/buffer_transform_flags.h new file mode 100644 index 000000000..e8e6300e3 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_transform_flags.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#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 diff --git a/src/core/hle/service/nvflinger/consumer_base.cpp b/src/core/hle/service/nvflinger/consumer_base.cpp new file mode 100644 index 000000000..30fc21acc --- /dev/null +++ b/src/core/hle/service/nvflinger/consumer_base.cpp @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2010 The Android Open Source Project +// Parts of this implementation were base 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 diff --git a/src/core/hle/service/nvflinger/consumer_base.h b/src/core/hle/service/nvflinger/consumer_base.h new file mode 100644 index 000000000..736080e3a --- /dev/null +++ b/src/core/hle/service/nvflinger/consumer_base.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2010 The Android Open Source Project +// Parts of this implementation were base 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_); + virtual ~ConsumerBase(); + + virtual void OnFrameAvailable(const BufferItem& item) override; + virtual void OnFrameReplaced(const BufferItem& item) override; + virtual void OnBuffersReleased() override; + virtual 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 diff --git a/src/core/hle/service/nvflinger/consumer_listener.h b/src/core/hle/service/nvflinger/consumer_listener.h new file mode 100644 index 000000000..b6d1c3e9a --- /dev/null +++ b/src/core/hle/service/nvflinger/consumer_listener.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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 diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp new file mode 100644 index 000000000..04068827e --- /dev/null +++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2010 The Android Open Source Project +// Parts of this implementation were base 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(Parcel& parcel) { + parcel.ReadFlattened(*this); +} + +QueueBufferOutput::QueueBufferOutput() = default; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.h b/src/core/hle/service/nvflinger/graphic_buffer_producer.h new file mode 100644 index 000000000..98d27871c --- /dev/null +++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2010 The Android Open Source Project +// Parts of this implementation were base 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 Parcel; + +#pragma pack(push, 1) +struct QueueBufferInput final { + explicit QueueBufferInput(Parcel& 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 diff --git a/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp new file mode 100644 index 000000000..094ba2542 --- /dev/null +++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#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 diff --git a/src/core/hle/service/nvflinger/hos_binder_driver_server.h b/src/core/hle/service/nvflinger/hos_binder_driver_server.h new file mode 100644 index 000000000..cbca87fa0 --- /dev/null +++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#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 diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 01e69de30..6fb2cdff1 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -1,6 +1,5 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project #include <algorithm> #include <optional> @@ -16,8 +15,11 @@ #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_queue.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 "video_core/gpu.h" @@ -53,13 +55,14 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) { } } -NVFlinger::NVFlinger(Core::System& system_) - : system(system_), service_context(system_, "nvflinger") { - displays.emplace_back(0, "Default", service_context, system); - displays.emplace_back(1, "External", service_context, system); - displays.emplace_back(2, "Edid", service_context, system); - displays.emplace_back(3, "Internal", service_context, system); - displays.emplace_back(4, "Null", service_context, system); +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 @@ -83,12 +86,15 @@ NVFlinger::NVFlinger(Core::System& system_) } NVFlinger::~NVFlinger() { - for (auto& buffer_queue : buffer_queues) { - buffer_queue->Disconnect(); - } if (!system.IsMulticore()) { system.CoreTiming().UnscheduleEvent(composition_event, 0); } + + 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) { @@ -98,7 +104,7 @@ void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) { std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) { const auto lock_guard = Lock(); - LOG_DEBUG(Service, "Opening \"{}\" display", name); + LOG_DEBUG(Service_NVFlinger, "Opening \"{}\" display", name); const auto itr = std::find_if(displays.begin(), displays.end(), @@ -125,10 +131,8 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { } void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { - const u32 buffer_queue_id = next_buffer_queue_id++; - buffer_queues.emplace_back( - std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id, service_context)); - display.CreateLayer(layer_id, *buffer_queues.back()); + const auto buffer_id = next_buffer_queue_id++; + display.CreateLayer(layer_id, buffer_id); } void NVFlinger::CloseLayer(u64 layer_id) { @@ -147,7 +151,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) { return std::nullopt; } - return layer->GetBufferQueue().GetId(); + return layer->GetBinderId(); } Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) { @@ -161,18 +165,6 @@ Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) { return &display->GetVSyncEvent(); } -BufferQueue* NVFlinger::FindBufferQueue(u32 id) { - const auto lock_guard = Lock(); - const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), - [id](const auto& queue) { return queue->GetId() == id; }); - - if (itr == buffer_queues.end()) { - return nullptr; - } - - return itr->get(); -} - VI::Display* NVFlinger::FindDisplay(u64 display_id) { const auto itr = std::find_if(displays.begin(), displays.end(), @@ -227,7 +219,7 @@ VI::Layer* NVFlinger::FindOrCreateLayer(u64 display_id, u64 layer_id) { auto* layer = display->FindLayer(layer_id); if (layer == nullptr) { - LOG_DEBUG(Service, "Layer at id {} not found. Trying to create it.", layer_id); + LOG_DEBUG(Service_NVFlinger, "Layer at id {} not found. Trying to create it.", layer_id); CreateLayerAtId(*display, layer_id); return display->FindLayer(layer_id); } @@ -246,23 +238,22 @@ void NVFlinger::Compose() { // TODO(Subv): Support more than 1 layer. VI::Layer& layer = display.GetLayer(0); - auto& buffer_queue = layer.GetBufferQueue(); - // Search for a queued buffer and acquire it - auto buffer = buffer_queue.AcquireBuffer(); + android::BufferItem buffer{}; + const auto status = layer.GetConsumer().AcquireBuffer(&buffer, {}, false); - if (!buffer) { + if (status != android::Status::NoError) { continue; } - const auto& igbp_buffer = buffer->get().igbp_buffer; + const auto& igbp_buffer = *buffer.graphic_buffer; if (!system.IsPoweredOn()) { return; // We are likely shutting down } auto& gpu = system.GPU(); - const auto& multi_fence = buffer->get().multi_fence; + const auto& multi_fence = buffer.fence; guard->unlock(); for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) { const auto& fence = multi_fence.fences[fence_id]; @@ -278,12 +269,18 @@ void NVFlinger::Compose() { auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0"); ASSERT(nvdisp); - nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.external_format, - igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, - buffer->get().transform, buffer->get().crop_rect); + 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); + + swap_interval = buffer.swap_interval; - swap_interval = buffer->get().swap_interval; - buffer_queue.ReleaseBuffer(buffer->get().slot); + auto fence = android::Fence::NoFence(); + layer.GetConsumer().ReleaseBuffer(buffer, fence); } } diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 7935cf773..ed160f6f9 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -1,6 +1,5 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project #pragma once @@ -37,13 +36,16 @@ class Display; class Layer; } // namespace Service::VI -namespace Service::NVFlinger { +namespace Service::android { +class BufferQueueCore; +class BufferQueueProducer; +} // namespace Service::android -class BufferQueue; +namespace Service::NVFlinger { class NVFlinger final { public: - explicit NVFlinger(Core::System& system_); + explicit NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); ~NVFlinger(); /// Sets the NVDrv module instance to use to send buffers to the GPU. @@ -72,9 +74,6 @@ public: /// If an invalid display ID is provided, then nullptr is returned. [[nodiscard]] Kernel::KReadableEvent* FindVsyncEvent(u64 display_id); - /// Obtains a buffer queue identified by the ID. - [[nodiscard]] BufferQueue* FindBufferQueue(u32 id); - /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// finished. void Compose(); @@ -82,6 +81,12 @@ public: [[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}; } @@ -111,7 +116,6 @@ private: std::shared_ptr<Nvidia::Module> nvdrv; std::list<VI::Display> displays; - std::vector<std::unique_ptr<BufferQueue>> buffer_queues; /// Id to use for the next layer that is created, this counter is shared among all displays. u64 next_layer_id = 1; @@ -131,6 +135,8 @@ private: std::jthread vsync_thread; KernelHelpers::ServiceContext service_context; + + HosBinderDriverServer& hos_binder_driver_server; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/parcel.h b/src/core/hle/service/nvflinger/parcel.h new file mode 100644 index 000000000..aa36e6479 --- /dev/null +++ b/src/core/hle/service/nvflinger/parcel.h @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#pragma once + +#include <memory> +#include <vector> + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/common_types.h" + +namespace Service::android { + +class Parcel final { +public: + static constexpr std::size_t DefaultBufferSize = 0x40; + + Parcel() : buffer(DefaultBufferSize) {} + + template <typename T> + explicit Parcel(const T& out_data) : buffer(DefaultBufferSize) { + Write(out_data); + } + + explicit Parcel(std::vector<u8> in_data) : 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) <= buffer.size()); + + std::memcpy(&val, 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) <= buffer.size()); + + T val; + std::memcpy(&val, 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; + } + + 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()); + } + + void DeserializeHeader() { + ASSERT(buffer.size() > sizeof(Header)); + + Header header{}; + std::memcpy(&header, buffer.data(), sizeof(Header)); + + read_index = header.data_offset; + } + + std::vector<u8> Serialize() const { + ASSERT(read_index == 0); + + Header header{}; + header.data_size = static_cast<u32>(write_index - sizeof(Header)); + header.data_offset = sizeof(Header); + header.objects_size = 4; + header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size); + std::memcpy(buffer.data(), &header, sizeof(Header)); + + return buffer; + } + +private: + struct Header { + u32 data_size; + u32 data_offset; + u32 objects_size; + u32 objects_offset; + }; + static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size"); + + mutable std::vector<u8> buffer; + std::size_t read_index = 0; + std::size_t write_index = sizeof(Header); +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/pixel_format.h b/src/core/hle/service/nvflinger/pixel_format.h new file mode 100644 index 000000000..8a77d8bea --- /dev/null +++ b/src/core/hle/service/nvflinger/pixel_format.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#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 diff --git a/src/core/hle/service/nvflinger/producer_listener.h b/src/core/hle/service/nvflinger/producer_listener.h new file mode 100644 index 000000000..468e06431 --- /dev/null +++ b/src/core/hle/service/nvflinger/producer_listener.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base 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 void OnBufferReleased() = 0; +}; + +} // namespace Service::android diff --git a/src/core/hle/service/nvflinger/status.h b/src/core/hle/service/nvflinger/status.h new file mode 100644 index 000000000..a003eda89 --- /dev/null +++ b/src/core/hle/service/nvflinger/status.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#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 diff --git a/src/core/hle/service/nvflinger/ui/fence.h b/src/core/hle/service/nvflinger/ui/fence.h new file mode 100644 index 000000000..4a74e26a3 --- /dev/null +++ b/src/core/hle/service/nvflinger/ui/fence.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2012 The Android Open Source Project +// Parts of this implementation were base 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 diff --git a/src/core/hle/service/nvflinger/ui/graphic_buffer.h b/src/core/hle/service/nvflinger/ui/graphic_buffer.h new file mode 100644 index 000000000..7abbf78ba --- /dev/null +++ b/src/core/hle/service/nvflinger/ui/graphic_buffer.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2007 The Android Open Source Project +// Parts of this implementation were base 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 diff --git a/src/core/hle/service/nvflinger/window.h b/src/core/hle/service/nvflinger/window.h new file mode 100644 index 000000000..e26f8160e --- /dev/null +++ b/src/core/hle/service/nvflinger/window.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project + +#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 diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index eb1138313..0f59a03c5 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -32,6 +32,7 @@ #include "core/hle/service/glue/glue.h" #include "core/hle/service/grc/grc.h" #include "core/hle/service/hid/hid.h" +#include "core/hle/service/jit/jit.h" #include "core/hle/service/lbl/lbl.h" #include "core/hle/service/ldn/ldn.h" #include "core/hle/service/ldr/ldr.h" @@ -49,6 +50,7 @@ #include "core/hle/service/npns/npns.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvflinger/hos_binder_driver_server.h" #include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/olsc/olsc.h" #include "core/hle/service/pcie/pcie.h" @@ -90,8 +92,9 @@ namespace Service { } ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, - u32 max_sessions_, InvokerFn* handler_invoker_) - : SessionRequestHandler(system_.Kernel(), service_name_), system{system_}, + ServiceThreadType thread_type, u32 max_sessions_, + InvokerFn* handler_invoker_) + : SessionRequestHandler(system_.Kernel(), service_name_, thread_type), system{system_}, service_name{service_name_}, max_sessions{max_sessions_}, handler_invoker{handler_invoker_} {} ServiceFrameworkBase::~ServiceFrameworkBase() { @@ -230,7 +233,8 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& sessi /// Initialize Services Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) - : nv_flinger{std::make_unique<NVFlinger::NVFlinger>(system)} { + : hos_binder_driver_server{std::make_unique<NVFlinger::HosBinderDriverServer>(system)}, + nv_flinger{std::make_unique<NVFlinger::NVFlinger>(system, *hos_binder_driver_server)} { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. @@ -259,6 +263,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system Glue::InstallInterfaces(system); GRC::InstallInterfaces(*sm, system); HID::InstallInterfaces(*sm, system); + JIT::InstallInterfaces(*sm, system); LBL::InstallInterfaces(*sm, system); LDN::InstallInterfaces(*sm, system); LDR::InstallInterfaces(*sm, system); @@ -290,7 +295,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system SSL::InstallInterfaces(*sm, system); Time::InstallInterfaces(system); USB::InstallInterfaces(*sm, system); - VI::InstallInterfaces(*sm, system, *nv_flinger); + VI::InstallInterfaces(*sm, system, *nv_flinger, *hos_binder_driver_server); WLAN::InstallInterfaces(*sm, system); } diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index c9d6b879d..148265218 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -9,7 +9,6 @@ #include <string> #include <boost/container/flat_map.hpp> #include "common/common_types.h" -#include "common/spin_lock.h" #include "core/hle/kernel/hle_ipc.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -33,8 +32,9 @@ class FileSystemController; } namespace NVFlinger { +class HosBinderDriverServer; class NVFlinger; -} +} // namespace NVFlinger namespace SM { class ServiceManager; @@ -89,7 +89,7 @@ protected: using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. - [[nodiscard]] std::scoped_lock<Common::SpinLock> LockService() { + [[nodiscard]] std::scoped_lock<std::mutex> LockService() { return std::scoped_lock{lock_service}; } @@ -113,7 +113,8 @@ private: Kernel::HLERequestContext& ctx); explicit ServiceFrameworkBase(Core::System& system_, const char* service_name_, - u32 max_sessions_, InvokerFn* handler_invoker_); + ServiceThreadType thread_type, u32 max_sessions_, + InvokerFn* handler_invoker_); ~ServiceFrameworkBase() override; void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n); @@ -133,7 +134,7 @@ private: boost::container::flat_map<u32, FunctionInfoBase> handlers_tipc; /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. - Common::SpinLock lock_service; + std::mutex lock_service; }; /** @@ -175,14 +176,17 @@ protected: /** * Initializes the handler with no functions installed. * - * @param system_ The system context to construct this service under. + * @param system_ The system context to construct this service under. * @param service_name_ Name of the service. - * @param max_sessions_ Maximum number of sessions that can be - * connected to this service at the same time. + * @param thread_type Specifies the thread type for this service. If this is set to CreateNew, + * it creates a new thread for it, otherwise this uses the default thread. + * @param max_sessions_ Maximum number of sessions that can be connected to this service at the + * same time. */ explicit ServiceFramework(Core::System& system_, const char* service_name_, + ServiceThreadType thread_type = ServiceThreadType::Default, u32 max_sessions_ = ServerSessionCountMax) - : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {} + : ServiceFrameworkBase(system_, service_name_, thread_type, max_sessions_, Invoker) {} /// Registers handlers in the service. template <std::size_t N> @@ -236,6 +240,7 @@ public: ~Services(); private: + std::unique_ptr<NVFlinger::HosBinderDriverServer> hos_binder_driver_server; std::unique_ptr<NVFlinger::NVFlinger> nv_flinger; }; diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 695a1faa6..13f5e08ec 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -153,7 +153,7 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext& auto& port = port_result.Unwrap(); SCOPE_EXIT({ port->GetClientPort().Close(); }); - server_ports.emplace_back(&port->GetServerPort()); + kernel.RegisterServerObject(&port->GetServerPort()); // Create a new session. Kernel::KClientSession* session{}; @@ -206,7 +206,7 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) { } SM::SM(ServiceManager& service_manager_, Core::System& system_) - : ServiceFramework{system_, "sm:", 4}, + : ServiceFramework{system_, "sm:", ServiceThreadType::Default, 4}, service_manager{service_manager_}, kernel{system_.Kernel()} { RegisterHandlers({ {0, &SM::Initialize, "Initialize"}, @@ -224,10 +224,6 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_) }); } -SM::~SM() { - for (auto& server_port : server_ports) { - server_port->Close(); - } -} +SM::~SM() = default; } // namespace Service::SM diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 021eb51b4..f3ff7b27e 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -22,7 +22,6 @@ class KClientPort; class KClientSession; class KernelCore; class KPort; -class KServerPort; class SessionRequestHandler; } // namespace Kernel @@ -48,7 +47,6 @@ private: ServiceManager& service_manager; bool is_initialized{}; Kernel::KernelCore& kernel; - std::vector<Kernel::KServerPort*> server_ports; }; class ServiceManager { diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index fc93fb743..d25b050e2 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -689,6 +689,9 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con case OptName::REUSEADDR: ASSERT(value == 0 || value == 1); return Translate(socket->SetReuseAddr(value != 0)); + case OptName::KEEPALIVE: + ASSERT(value == 0 || value == 1); + return Translate(socket->SetKeepAlive(value != 0)); case OptName::BROADCAST: ASSERT(value == 0 || value == 1); return Translate(socket->SetBroadcast(value != 0)); @@ -837,7 +840,8 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co rb.PushEnum(bsd_errno); } -BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { +BSD::BSD(Core::System& system_, const char* name) + : ServiceFramework{system_, name, ServiceThreadType::CreateNew} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index fb6142c49..a193fb578 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -2,8 +2,28 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <string_view> +#include <utility> +#include <vector> + +#include "common/string_util.h" +#include "common/swap.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/sockets/sfdnsres.h" +#include "core/memory.h" + +#ifdef _WIN32 +#include <ws2tcpip.h> +#elif YUZU_UNIX +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <sys/socket.h> +#ifndef EAI_NODATA +#define EAI_NODATA EAI_NONAME +#endif +#endif namespace Service::Sockets { @@ -21,7 +41,7 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres" {9, nullptr, "CancelRequest"}, {10, nullptr, "GetHostByNameRequestWithOptions"}, {11, nullptr, "GetHostByAddrRequestWithOptions"}, - {12, nullptr, "GetAddrInfoRequestWithOptions"}, + {12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"}, {13, nullptr, "GetNameInfoRequestWithOptions"}, {14, nullptr, "ResolverSetOptionRequest"}, {15, nullptr, "ResolverGetOptionRequest"}, @@ -31,7 +51,142 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres" SFDNSRES::~SFDNSRES() = default; -void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { +enum class NetDbError : s32 { + Internal = -1, + Success = 0, + HostNotFound = 1, + TryAgain = 2, + NoRecovery = 3, + NoData = 4, +}; + +static NetDbError AddrInfoErrorToNetDbError(s32 result) { + // Best effort guess to map errors + switch (result) { + case 0: + return NetDbError::Success; + case EAI_AGAIN: + return NetDbError::TryAgain; + case EAI_NODATA: + return NetDbError::NoData; + default: + return NetDbError::HostNotFound; + } +} + +static std::vector<u8> SerializeAddrInfo(const addrinfo* addrinfo, s32 result_code, + std::string_view host) { + // Adapted from + // https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190 + std::vector<u8> data; + + auto* current = addrinfo; + while (current != nullptr) { + struct SerializedResponseHeader { + u32 magic; + s32 flags; + s32 family; + s32 socket_type; + s32 protocol; + u32 address_length; + }; + static_assert(sizeof(SerializedResponseHeader) == 0x18, + "Response header size must be 0x18 bytes"); + + constexpr auto header_size = sizeof(SerializedResponseHeader); + const auto addr_size = + current->ai_addr && current->ai_addrlen > 0 ? current->ai_addrlen : 4; + const auto canonname_size = current->ai_canonname ? strlen(current->ai_canonname) + 1 : 1; + + const auto last_size = data.size(); + data.resize(last_size + header_size + addr_size + canonname_size); + + // Header in network byte order + SerializedResponseHeader header{}; + + constexpr auto HEADER_MAGIC = 0xBEEFCAFE; + header.magic = htonl(HEADER_MAGIC); + header.family = htonl(current->ai_family); + header.flags = htonl(current->ai_flags); + header.socket_type = htonl(current->ai_socktype); + header.protocol = htonl(current->ai_protocol); + header.address_length = current->ai_addr ? htonl((u32)current->ai_addrlen) : 0; + + auto* header_ptr = data.data() + last_size; + std::memcpy(header_ptr, &header, header_size); + + if (header.address_length == 0) { + std::memset(header_ptr + header_size, 0, 4); + } else { + switch (current->ai_family) { + case AF_INET: { + struct SockAddrIn { + s16 sin_family; + u16 sin_port; + u32 sin_addr; + u8 sin_zero[8]; + }; + + SockAddrIn serialized_addr{}; + const auto addr = *reinterpret_cast<sockaddr_in*>(current->ai_addr); + serialized_addr.sin_port = htons(addr.sin_port); + serialized_addr.sin_family = htons(addr.sin_family); + serialized_addr.sin_addr = htonl(addr.sin_addr.s_addr); + std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn)); + + char addr_string_buf[64]{}; + inet_ntop(AF_INET, &addr.sin_addr, addr_string_buf, std::size(addr_string_buf)); + LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, addr_string_buf); + break; + } + case AF_INET6: { + struct SockAddrIn6 { + s16 sin6_family; + u16 sin6_port; + u32 sin6_flowinfo; + u8 sin6_addr[16]; + u32 sin6_scope_id; + }; + + SockAddrIn6 serialized_addr{}; + const auto addr = *reinterpret_cast<sockaddr_in6*>(current->ai_addr); + serialized_addr.sin6_family = htons(addr.sin6_family); + serialized_addr.sin6_port = htons(addr.sin6_port); + serialized_addr.sin6_flowinfo = htonl(addr.sin6_flowinfo); + serialized_addr.sin6_scope_id = htonl(addr.sin6_scope_id); + std::memcpy(serialized_addr.sin6_addr, &addr.sin6_addr, + sizeof(SockAddrIn6::sin6_addr)); + std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn6)); + + char addr_string_buf[64]{}; + inet_ntop(AF_INET6, &addr.sin6_addr, addr_string_buf, std::size(addr_string_buf)); + LOG_INFO(Service, "Resolved host '{}' to IPv6 address {}", host, addr_string_buf); + break; + } + default: + std::memcpy(header_ptr + header_size, current->ai_addr, addr_size); + break; + } + } + if (current->ai_canonname) { + std::memcpy(header_ptr + addr_size, current->ai_canonname, canonname_size); + } else { + *(header_ptr + header_size + addr_size) = 0; + } + + current = current->ai_next; + } + + // 4-byte sentinel value + data.push_back(0); + data.push_back(0); + data.push_back(0); + data.push_back(0); + + return data; +} + +static std::pair<u32, s32> GetAddrInfoRequestImpl(Kernel::HLERequestContext& ctx) { struct Parameters { u8 use_nsd_resolve; u32 unknown; @@ -42,11 +197,51 @@ void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { const auto parameters = rp.PopRaw<Parameters>(); LOG_WARNING(Service, - "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}", + "called with ignored parameters: use_nsd_resolve={}, unknown={}, process_id={}", parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); - IPC::ResponseBuilder rb{ctx, 2}; + const auto host_buffer = ctx.ReadBuffer(0); + const std::string host = Common::StringFromBuffer(host_buffer); + + const auto service_buffer = ctx.ReadBuffer(1); + const std::string service = Common::StringFromBuffer(service_buffer); + + addrinfo* addrinfo; + // Pass null for hints. Serialized hints are also passed in a buffer, but are ignored for now + s32 result_code = getaddrinfo(host.c_str(), service.c_str(), nullptr, &addrinfo); + + u32 data_size = 0; + if (result_code == 0 && addrinfo != nullptr) { + const std::vector<u8>& data = SerializeAddrInfo(addrinfo, result_code, host); + data_size = static_cast<u32>(data.size()); + freeaddrinfo(addrinfo); + + ctx.WriteBuffer(data, 0); + } + + return std::make_pair(data_size, result_code); +} + +void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { + auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(static_cast<s32>(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode + rb.Push(result_code); // errno + rb.Push(data_size); // serialized size +} + +void SFDNSRES::GetAddrInfoRequestWithOptions(Kernel::HLERequestContext& ctx) { + // Additional options are ignored + auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); + rb.Push(data_size); // serialized size + rb.Push(result_code); // errno + rb.Push(static_cast<s32>(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode + rb.Push(0); } -} // namespace Service::Sockets +} // namespace Service::Sockets
\ No newline at end of file diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index 5d3b4dc2d..f0c57377d 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -19,6 +19,7 @@ public: private: void GetAddrInfoRequest(Kernel::HLERequestContext& ctx); + void GetAddrInfoRequestWithOptions(Kernel::HLERequestContext& ctx); }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 02dbbae40..d69c86431 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -46,6 +46,7 @@ enum class Protocol : u32 { enum class OptName : u32 { REUSEADDR = 0x4, + KEEPALIVE = 0x8, BROADCAST = 0x20, LINGER = 0x80, SNDBUF = 0x1001, diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index b7705c02a..558022511 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -13,14 +13,34 @@ #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nvflinger/buffer_item_consumer.h" +#include "core/hle/service/nvflinger/buffer_queue_consumer.h" +#include "core/hle/service/nvflinger/buffer_queue_core.h" +#include "core/hle/service/nvflinger/buffer_queue_producer.h" +#include "core/hle/service/nvflinger/hos_binder_driver_server.h" #include "core/hle/service/vi/display/vi_display.h" #include "core/hle/service/vi/layer/vi_layer.h" namespace Service::VI { -Display::Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_, - Core::System& system_) - : display_id{id}, name{std::move(name_)}, service_context{service_context_} { +struct BufferQueue { + std::shared_ptr<android::BufferQueueCore> core; + std::unique_ptr<android::BufferQueueProducer> producer; + std::unique_ptr<android::BufferQueueConsumer> consumer; +}; + +static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context) { + auto buffer_queue_core = std::make_shared<android::BufferQueueCore>(); + return {buffer_queue_core, + std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core), + std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)}; +} + +Display::Display(u64 id, std::string name_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_, + KernelHelpers::ServiceContext& service_context_, Core::System& system_) + : display_id{id}, name{std::move(name_)}, hos_binder_driver_server{hos_binder_driver_server_}, + service_context{service_context_} { vsync_event = service_context.CreateEvent(fmt::format("Display VSync Event {}", id)); } @@ -44,21 +64,29 @@ void Display::SignalVSyncEvent() { vsync_event->GetWritableEvent().Signal(); } -void Display::CreateLayer(u64 layer_id, NVFlinger::BufferQueue& buffer_queue) { - // TODO(Subv): Support more than 1 layer. +void Display::CreateLayer(u64 layer_id, u32 binder_id) { ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment"); - layers.emplace_back(std::make_shared<Layer>(layer_id, buffer_queue)); + auto [core, producer, consumer] = CreateBufferQueue(service_context); + + auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer)); + buffer_item_consumer->Connect(false); + + layers.emplace_back(std::make_unique<Layer>(layer_id, binder_id, *core, *producer, + std::move(buffer_item_consumer))); + + hos_binder_driver_server.RegisterProducer(std::move(producer)); } void Display::CloseLayer(u64 layer_id) { - std::erase_if(layers, [layer_id](const auto& layer) { return layer->GetID() == layer_id; }); + std::erase_if(layers, + [layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; }); } Layer* Display::FindLayer(u64 layer_id) { const auto itr = - std::find_if(layers.begin(), layers.end(), [layer_id](const std::shared_ptr<Layer>& layer) { - return layer->GetID() == layer_id; + std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) { + return layer->GetLayerId() == layer_id; }); if (itr == layers.end()) { @@ -70,8 +98,8 @@ Layer* Display::FindLayer(u64 layer_id) { const Layer* Display::FindLayer(u64 layer_id) const { const auto itr = - std::find_if(layers.begin(), layers.end(), [layer_id](const std::shared_ptr<Layer>& layer) { - return layer->GetID() == layer_id; + std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) { + return layer->GetLayerId() == layer_id; }); if (itr == layers.end()) { diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 329f4ba86..e93d084ee 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -15,12 +15,17 @@ namespace Kernel { class KEvent; } -namespace Service::NVFlinger { -class BufferQueue; +namespace Service::android { +class BufferQueueProducer; } + namespace Service::KernelHelpers { class ServiceContext; -} // namespace Service::KernelHelpers +} + +namespace Service::NVFlinger { +class HosBinderDriverServer; +} namespace Service::VI { @@ -35,12 +40,13 @@ public: /// Constructs a display with a given unique ID and name. /// /// @param id The unique ID for this display. + /// @param hos_binder_driver_server_ NVFlinger HOSBinderDriver server instance. /// @param service_context_ The ServiceContext for the owning service. /// @param name_ The name for this display. /// @param system_ The global system instance. /// - Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_, - Core::System& system_); + Display(u64 id, std::string name_, NVFlinger::HosBinderDriverServer& hos_binder_driver_server_, + KernelHelpers::ServiceContext& service_context_, Core::System& system_); ~Display(); /// Gets the unique ID assigned to this display. @@ -64,6 +70,10 @@ public: /// Gets a layer for this display based off an index. const Layer& GetLayer(std::size_t index) const; + std::size_t GetNumLayers() const { + return layers.size(); + } + /// Gets the readable vsync event. Kernel::KReadableEvent& GetVSyncEvent(); @@ -72,10 +82,10 @@ public: /// Creates and adds a layer to this display with the given ID. /// - /// @param layer_id The ID to assign to the created layer. - /// @param buffer_queue The buffer queue for the layer instance to use. + /// @param layer_id The ID to assign to the created layer. + /// @param binder_id The ID assigned to the buffer queue. /// - void CreateLayer(u64 layer_id, NVFlinger::BufferQueue& buffer_queue); + void CreateLayer(u64 layer_id, u32 binder_id); /// Closes and removes a layer from this display with the given ID. /// @@ -104,9 +114,10 @@ public: private: u64 display_id; std::string name; + NVFlinger::HosBinderDriverServer& hos_binder_driver_server; KernelHelpers::ServiceContext& service_context; - std::vector<std::shared_ptr<Layer>> layers; + std::vector<std::unique_ptr<Layer>> layers; Kernel::KEvent* vsync_event{}; }; diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp index 9bc382587..93858e91f 100644 --- a/src/core/hle/service/vi/layer/vi_layer.cpp +++ b/src/core/hle/service/vi/layer/vi_layer.cpp @@ -6,7 +6,11 @@ namespace Service::VI { -Layer::Layer(u64 id, NVFlinger::BufferQueue& queue) : layer_id{id}, buffer_queue{queue} {} +Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_, + android::BufferQueueProducer& binder_, + std::shared_ptr<android::BufferItemConsumer>&& consumer_) + : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move( + consumer_)} {} Layer::~Layer() = default; diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h index ebdd85505..c28b14450 100644 --- a/src/core/hle/service/vi/layer/vi_layer.h +++ b/src/core/hle/service/vi/layer/vi_layer.h @@ -4,11 +4,15 @@ #pragma once +#include <memory> + #include "common/common_types.h" -namespace Service::NVFlinger { -class BufferQueue; -} +namespace Service::android { +class BufferItemConsumer; +class BufferQueueCore; +class BufferQueueProducer; +} // namespace Service::android namespace Service::VI { @@ -17,10 +21,13 @@ class Layer { public: /// Constructs a layer with a given ID and buffer queue. /// - /// @param id The ID to assign to this layer. - /// @param queue The buffer queue for this layer to use. + /// @param layer_id_ The ID to assign to this layer. + /// @param binder_id_ The binder ID to assign to this layer. + /// @param binder_ The buffer producer queue for this layer to use. /// - Layer(u64 id, NVFlinger::BufferQueue& queue); + Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_, + android::BufferQueueProducer& binder_, + std::shared_ptr<android::BufferItemConsumer>&& consumer_); ~Layer(); Layer(const Layer&) = delete; @@ -30,23 +37,47 @@ public: Layer& operator=(Layer&&) = delete; /// Gets the ID for this layer. - u64 GetID() const { + u64 GetLayerId() const { return layer_id; } + /// Gets the binder ID for this layer. + u32 GetBinderId() const { + return binder_id; + } + /// Gets a reference to the buffer queue this layer is using. - NVFlinger::BufferQueue& GetBufferQueue() { - return buffer_queue; + android::BufferQueueProducer& GetBufferQueue() { + return binder; } /// Gets a const reference to the buffer queue this layer is using. - const NVFlinger::BufferQueue& GetBufferQueue() const { - return buffer_queue; + const android::BufferQueueProducer& GetBufferQueue() const { + return binder; + } + + android::BufferItemConsumer& GetConsumer() { + return *consumer; + } + + const android::BufferItemConsumer& GetConsumer() const { + return *consumer; + } + + android::BufferQueueCore& Core() { + return core; + } + + const android::BufferQueueCore& Core() const { + return core; } private: - u64 layer_id; - NVFlinger::BufferQueue& buffer_queue; + const u64 layer_id; + const u32 binder_id; + android::BufferQueueCore& core; + android::BufferQueueProducer& binder; + std::shared_ptr<android::BufferItemConsumer> consumer; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 75ee3e5e4..a3436c8ea 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -22,8 +22,11 @@ #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/service/nvdrv/nvdata.h" -#include "core/hle/service/nvflinger/buffer_queue.h" +#include "core/hle/service/nvflinger/binder.h" +#include "core/hle/service/nvflinger/buffer_queue_producer.h" +#include "core/hle/service/nvflinger/hos_binder_driver_server.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/nvflinger/parcel.h" #include "core/hle/service/service.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" @@ -57,447 +60,25 @@ struct DisplayInfo { }; static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size"); -class Parcel { +class NativeWindow final { public: - // This default size was chosen arbitrarily. - static constexpr std::size_t DefaultBufferSize = 0x40; - Parcel() : buffer(DefaultBufferSize) {} - explicit Parcel(std::vector<u8> data) : buffer(std::move(data)) {} - virtual ~Parcel() = default; - - template <typename T> - T Read() { - static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); - ASSERT(read_index + sizeof(T) <= buffer.size()); - - T val; - std::memcpy(&val, buffer.data() + read_index, sizeof(T)); - read_index += sizeof(T); - read_index = Common::AlignUp(read_index, 4); - 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) <= buffer.size()); - - T val; - std::memcpy(&val, buffer.data() + read_index, sizeof(T)); - read_index += sizeof(T); - return val; - } - - std::vector<u8> ReadBlock(std::size_t length) { - ASSERT(read_index + length <= buffer.size()); - const u8* const begin = buffer.data() + read_index; - const u8* const end = begin + length; - std::vector<u8> data(begin, end); - read_index += length; - read_index = Common::AlignUp(read_index, 4); - return data; - } - - std::u16string ReadInterfaceToken() { - [[maybe_unused]] const u32 unknown = Read<u32_le>(); - const u32 length = Read<u32_le>(); - - std::u16string token{}; - - for (u32 ch = 0; ch < length + 1; ++ch) { - token.push_back(ReadUnaligned<u16_le>()); - } - - read_index = Common::AlignUp(read_index, 4); - - return token; - } - - 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& val) { - static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); - - const u32_le size = static_cast<u32>(sizeof(val)); - Write(size); - // TODO(Subv): Support file descriptors. - Write<u32_le>(0); // Fd count. - Write(val); - } - - void Deserialize() { - ASSERT(buffer.size() > sizeof(Header)); - - Header header{}; - std::memcpy(&header, buffer.data(), sizeof(Header)); - - read_index = header.data_offset; - DeserializeData(); - } - - std::vector<u8> Serialize() { - ASSERT(read_index == 0); - write_index = sizeof(Header); - - SerializeData(); - - Header header{}; - header.data_size = static_cast<u32_le>(write_index - sizeof(Header)); - header.data_offset = sizeof(Header); - header.objects_size = 4; - header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size); - std::memcpy(buffer.data(), &header, sizeof(Header)); - - return buffer; - } - -protected: - virtual void SerializeData() {} - - virtual void DeserializeData() {} - -private: - struct Header { - u32_le data_size; - u32_le data_offset; - u32_le objects_size; - u32_le objects_offset; - }; - static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size"); - - std::vector<u8> buffer; - std::size_t read_index = 0; - std::size_t write_index = 0; -}; - -class NativeWindow : public Parcel { -public: - explicit NativeWindow(u32 id) { - data.id = id; - } - ~NativeWindow() override = default; - -protected: - void SerializeData() override { - Write(data); - } - -private: - struct Data { - u32_le magic = 2; - u32_le process_id = 1; - u32_le id; - INSERT_PADDING_WORDS(3); - std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'}; - INSERT_PADDING_WORDS(2); - }; - static_assert(sizeof(Data) == 0x28, "ParcelData has wrong size"); - - Data data{}; -}; - -class IGBPConnectRequestParcel : public Parcel { -public: - explicit IGBPConnectRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - data = Read<Data>(); - } - - struct Data { - u32_le unk; - u32_le api; - u32_le producer_controlled_by_app; - }; - - Data data; -}; - -class IGBPConnectResponseParcel : public Parcel { -public: - explicit IGBPConnectResponseParcel(u32 width, u32 height) { - data.width = width; - data.height = height; - } - ~IGBPConnectResponseParcel() override = default; - -protected: - void SerializeData() override { - Write(data); - } - -private: - struct Data { - u32_le width; - u32_le height; - u32_le transform_hint; - u32_le num_pending_buffers; - u32_le status; - }; - static_assert(sizeof(Data) == 20, "ParcelData has wrong size"); - - Data data{}; -}; - -/// Represents a parcel containing one int '0' as its data -/// Used by DetachBuffer and Disconnect -class IGBPEmptyResponseParcel : public Parcel { -protected: - void SerializeData() override { - Write(data); - } - -private: - struct Data { - u32_le unk_0{}; - }; - - Data data{}; -}; - -class IGBPSetPreallocatedBufferRequestParcel : public Parcel { -public: - explicit IGBPSetPreallocatedBufferRequestParcel(std::vector<u8> buffer_) - : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - data = Read<Data>(); - if (data.contains_object != 0) { - buffer_container = Read<BufferContainer>(); - } - } - - struct Data { - u32_le slot; - u32_le contains_object; - }; - - struct BufferContainer { - u32_le graphic_buffer_length; - INSERT_PADDING_WORDS(1); - NVFlinger::IGBPBuffer buffer{}; - }; - - Data data{}; - BufferContainer buffer_container{}; -}; - -class IGBPSetPreallocatedBufferResponseParcel : public Parcel { -protected: - void SerializeData() override { - // TODO(Subv): Find out what this means - Write<u32>(0); - } -}; - -class IGBPCancelBufferRequestParcel : public Parcel { -public: - explicit IGBPCancelBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - data = Read<Data>(); - } - - struct Data { - u32_le slot; - Service::Nvidia::MultiFence multi_fence; - }; - - Data data; -}; - -class IGBPCancelBufferResponseParcel : public Parcel { -protected: - void SerializeData() override { - Write<u32>(0); // Success - } -}; - -class IGBPDequeueBufferRequestParcel : public Parcel { -public: - explicit IGBPDequeueBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - data = Read<Data>(); - } - - struct Data { - u32_le pixel_format; - u32_le width; - u32_le height; - u32_le get_frame_timestamps; - u32_le usage; - }; - - Data data; -}; - -class IGBPDequeueBufferResponseParcel : public Parcel { -public: - explicit IGBPDequeueBufferResponseParcel(u32 slot_, Nvidia::MultiFence& multi_fence_) - : slot(slot_), multi_fence(multi_fence_) {} - -protected: - void SerializeData() override { - Write(slot); - Write<u32_le>(1); - WriteObject(multi_fence); - Write<u32_le>(0); - } - - u32_le slot; - Service::Nvidia::MultiFence multi_fence; -}; - -class IGBPRequestBufferRequestParcel : public Parcel { -public: - explicit IGBPRequestBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - slot = Read<u32_le>(); - } - - u32_le slot; -}; - -class IGBPRequestBufferResponseParcel : public Parcel { -public: - explicit IGBPRequestBufferResponseParcel(NVFlinger::IGBPBuffer buffer_) : buffer(buffer_) {} - ~IGBPRequestBufferResponseParcel() override = default; - -protected: - void SerializeData() override { - // TODO(Subv): Figure out what this value means, writing non-zero here will make libnx - // try to read an IGBPBuffer object from the parcel. - Write<u32_le>(1); - WriteObject(buffer); - Write<u32_le>(0); - } - - NVFlinger::IGBPBuffer buffer; -}; - -class IGBPQueueBufferRequestParcel : public Parcel { -public: - explicit IGBPQueueBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - data = Read<Data>(); - } - - struct Data { - u32_le slot; - INSERT_PADDING_WORDS(3); - u32_le timestamp; - s32_le is_auto_timestamp; - s32_le crop_top; - s32_le crop_left; - s32_le crop_right; - s32_le crop_bottom; - s32_le scaling_mode; - NVFlinger::BufferQueue::BufferTransformFlags transform; - u32_le sticky_transform; - INSERT_PADDING_WORDS(1); - u32_le swap_interval; - Service::Nvidia::MultiFence multi_fence; - - Common::Rectangle<int> GetCropRect() const { - return {crop_left, crop_top, crop_right, crop_bottom}; - } - }; - static_assert(sizeof(Data) == 96, "ParcelData has wrong size"); - - Data data; -}; - -class IGBPQueueBufferResponseParcel : public Parcel { -public: - explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) { - data.width = width; - data.height = height; - } - ~IGBPQueueBufferResponseParcel() override = default; - -protected: - void SerializeData() override { - Write(data); - } - -private: - struct Data { - u32_le width; - u32_le height; - u32_le transform_hint; - u32_le num_pending_buffers; - u32_le status; - }; - static_assert(sizeof(Data) == 20, "ParcelData has wrong size"); - - Data data{}; -}; - -class IGBPQueryRequestParcel : public Parcel { -public: - explicit IGBPQueryRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) { - Deserialize(); - } - - void DeserializeData() override { - [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); - type = Read<u32_le>(); - } - - u32 type; -}; - -class IGBPQueryResponseParcel : public Parcel { -public: - explicit IGBPQueryResponseParcel(u32 value_) : value{value_} {} - ~IGBPQueryResponseParcel() override = default; - -protected: - void SerializeData() override { - Write(value); - } + constexpr explicit NativeWindow(u32 id_) : id{id_} {} private: - u32_le value; + const u32 magic = 2; + const u32 process_id = 1; + const u32 id; + INSERT_PADDING_WORDS(3); + std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'}; + INSERT_PADDING_WORDS(2); }; +static_assert(sizeof(NativeWindow) == 0x28, "NativeWindow has wrong size"); class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> { public: - explicit IHOSBinderDriver(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) - : ServiceFramework{system_, "IHOSBinderDriver"}, nv_flinger(nv_flinger_) { + explicit IHOSBinderDriver(Core::System& system_, NVFlinger::HosBinderDriverServer& server_) + : ServiceFramework{system_, "IHOSBinderDriver", ServiceThreadType::CreateNew}, + server(server_) { static const FunctionInfo functions[] = { {0, &IHOSBinderDriver::TransactParcel, "TransactParcel"}, {1, &IHOSBinderDriver::AdjustRefcount, "AdjustRefcount"}, @@ -508,147 +89,16 @@ public: } private: - 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 - }; - void TransactParcel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 id = rp.Pop<u32>(); - const auto transaction = static_cast<TransactionId>(rp.Pop<u32>()); + const auto transaction = static_cast<android::TransactionId>(rp.Pop<u32>()); const u32 flags = rp.Pop<u32>(); LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id, transaction, flags); - auto& buffer_queue = *nv_flinger.FindBufferQueue(id); - - switch (transaction) { - case TransactionId::Connect: { - IGBPConnectRequestParcel request{ctx.ReadBuffer()}; - IGBPConnectResponseParcel response{static_cast<u32>(DisplayResolution::UndockedWidth), - static_cast<u32>(DisplayResolution::UndockedHeight)}; - - buffer_queue.Connect(); - - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::SetPreallocatedBuffer: { - IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()}; - - buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer_container.buffer); - - IGBPSetPreallocatedBufferResponseParcel response{}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::DequeueBuffer: { - IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()}; - const u32 width{request.data.width}; - const u32 height{request.data.height}; - - do { - if (auto result = buffer_queue.DequeueBuffer(width, height); result) { - // Buffer is available - IGBPDequeueBufferResponseParcel response{result->first, *result->second}; - ctx.WriteBuffer(response.Serialize()); - break; - } - } while (buffer_queue.IsConnected()); - - break; - } - case TransactionId::RequestBuffer: { - IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()}; - - auto& buffer = buffer_queue.RequestBuffer(request.slot); - IGBPRequestBufferResponseParcel response{buffer}; - ctx.WriteBuffer(response.Serialize()); - - break; - } - case TransactionId::QueueBuffer: { - IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()}; - - buffer_queue.QueueBuffer(request.data.slot, request.data.transform, - request.data.GetCropRect(), request.data.swap_interval, - request.data.multi_fence); - - IGBPQueueBufferResponseParcel response{1280, 720}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::Query: { - IGBPQueryRequestParcel request{ctx.ReadBuffer()}; - - const u32 value = - buffer_queue.Query(static_cast<NVFlinger::BufferQueue::QueryType>(request.type)); - - IGBPQueryResponseParcel response{value}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::CancelBuffer: { - IGBPCancelBufferRequestParcel request{ctx.ReadBuffer()}; - - buffer_queue.CancelBuffer(request.data.slot, request.data.multi_fence); - - IGBPCancelBufferResponseParcel response{}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::Disconnect: { - LOG_WARNING(Service_VI, "(STUBBED) called, transaction=Disconnect"); - const auto buffer = ctx.ReadBuffer(); - - buffer_queue.Disconnect(); - - IGBPEmptyResponseParcel response{}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::DetachBuffer: { - const auto buffer = ctx.ReadBuffer(); - - IGBPEmptyResponseParcel response{}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::SetBufferCount: { - LOG_WARNING(Service_VI, "(STUBBED) called, transaction=SetBufferCount"); - [[maybe_unused]] const auto buffer = ctx.ReadBuffer(); - - IGBPEmptyResponseParcel response{}; - ctx.WriteBuffer(response.Serialize()); - break; - } - case TransactionId::GetBufferHistory: { - LOG_WARNING(Service_VI, "(STUBBED) called, transaction=GetBufferHistory"); - [[maybe_unused]] const auto buffer = ctx.ReadBuffer(); - - IGBPEmptyResponseParcel response{}; - ctx.WriteBuffer(response.Serialize()); - break; - } - default: - ASSERT_MSG(false, "Unimplemented"); - } + server.TryGetProducer(id)->Transact(ctx, transaction, flags); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -674,13 +124,13 @@ private: LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown); - // TODO(Subv): Find out what this actually is. IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(nv_flinger.FindBufferQueue(id)->GetBufferWaitEvent()); + rb.PushCopyObjects(server.TryGetProducer(id)->GetNativeHandle()); } - NVFlinger::NVFlinger& nv_flinger; +private: + NVFlinger::HosBinderDriverServer& server; }; class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { @@ -937,7 +387,40 @@ private: class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> { public: - explicit IApplicationDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); + IApplicationDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_) + : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_}, + hos_binder_driver_server{hos_binder_driver_server_} { + + static const FunctionInfo functions[] = { + {100, &IApplicationDisplayService::GetRelayService, "GetRelayService"}, + {101, &IApplicationDisplayService::GetSystemDisplayService, "GetSystemDisplayService"}, + {102, &IApplicationDisplayService::GetManagerDisplayService, + "GetManagerDisplayService"}, + {103, &IApplicationDisplayService::GetIndirectDisplayTransactionService, + "GetIndirectDisplayTransactionService"}, + {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"}, + {1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"}, + {1011, &IApplicationDisplayService::OpenDefaultDisplay, "OpenDefaultDisplay"}, + {1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"}, + {1101, &IApplicationDisplayService::SetDisplayEnabled, "SetDisplayEnabled"}, + {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"}, + {2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"}, + {2021, &IApplicationDisplayService::CloseLayer, "CloseLayer"}, + {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"}, + {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"}, + {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, + {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"}, + {2450, &IApplicationDisplayService::GetIndirectLayerImageMap, + "GetIndirectLayerImageMap"}, + {2451, nullptr, "GetIndirectLayerImageCropMap"}, + {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo, + "GetIndirectLayerImageRequiredMemoryInfo"}, + {5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"}, + {5203, nullptr, "GetDisplayVsyncEventForDebug"}, + }; + RegisterHandlers(functions); + } private: enum class ConvertedScaleMode : u64 { @@ -961,7 +444,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger); + rb.PushIpcInterface<IHOSBinderDriver>(system, hos_binder_driver_server); } void GetSystemDisplayService(Kernel::HLERequestContext& ctx) { @@ -985,7 +468,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger); + rb.PushIpcInterface<IHOSBinderDriver>(system, hos_binder_driver_server); } void OpenDisplay(Kernel::HLERequestContext& ctx) { @@ -1089,7 +572,7 @@ private: void ListDisplays(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); - DisplayInfo display_info; + const DisplayInfo display_info; ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); @@ -1124,8 +607,8 @@ private: return; } - NativeWindow native_window{*buffer_queue_id}; - const auto buffer_size = ctx.WriteBuffer(native_window.Serialize()); + const auto parcel = android::Parcel{NativeWindow{*buffer_queue_id}}; + const auto buffer_size = ctx.WriteBuffer(parcel.Serialize()); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); @@ -1170,8 +653,8 @@ private: return; } - NativeWindow native_window{*buffer_queue_id}; - const auto buffer_size = ctx.WriteBuffer(native_window.Serialize()); + const auto parcel = android::Parcel{NativeWindow{*buffer_queue_id}}; + const auto buffer_size = ctx.WriteBuffer(parcel.Serialize()); IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); @@ -1287,39 +770,9 @@ private: } NVFlinger::NVFlinger& nv_flinger; + NVFlinger::HosBinderDriverServer& hos_binder_driver_server; }; -IApplicationDisplayService::IApplicationDisplayService(Core::System& system_, - NVFlinger::NVFlinger& nv_flinger_) - : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_} { - static const FunctionInfo functions[] = { - {100, &IApplicationDisplayService::GetRelayService, "GetRelayService"}, - {101, &IApplicationDisplayService::GetSystemDisplayService, "GetSystemDisplayService"}, - {102, &IApplicationDisplayService::GetManagerDisplayService, "GetManagerDisplayService"}, - {103, &IApplicationDisplayService::GetIndirectDisplayTransactionService, - "GetIndirectDisplayTransactionService"}, - {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"}, - {1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"}, - {1011, &IApplicationDisplayService::OpenDefaultDisplay, "OpenDefaultDisplay"}, - {1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"}, - {1101, &IApplicationDisplayService::SetDisplayEnabled, "SetDisplayEnabled"}, - {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"}, - {2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"}, - {2021, &IApplicationDisplayService::CloseLayer, "CloseLayer"}, - {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"}, - {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"}, - {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, - {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"}, - {2450, &IApplicationDisplayService::GetIndirectLayerImageMap, "GetIndirectLayerImageMap"}, - {2451, nullptr, "GetIndirectLayerImageCropMap"}, - {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo, - "GetIndirectLayerImageRequiredMemoryInfo"}, - {5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"}, - {5203, nullptr, "GetDisplayVsyncEventForDebug"}, - }; - RegisterHandlers(functions); -} - static bool IsValidServiceAccess(Permission permission, Policy policy) { if (permission == Permission::User) { return policy == Policy::User; @@ -1333,7 +786,9 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) { } void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system, - NVFlinger::NVFlinger& nv_flinger, Permission permission) { + NVFlinger::NVFlinger& nv_flinger, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server, + Permission permission) { IPC::RequestParser rp{ctx}; const auto policy = rp.PopEnum<Policy>(); @@ -1346,14 +801,18 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger); + rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger, hos_binder_driver_server); } void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system, - NVFlinger::NVFlinger& nv_flinger) { - std::make_shared<VI_M>(system, nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_S>(system, nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_U>(system, nv_flinger)->InstallAsService(service_manager); + NVFlinger::NVFlinger& nv_flinger, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server) { + std::make_shared<VI_M>(system, nv_flinger, hos_binder_driver_server) + ->InstallAsService(service_manager); + std::make_shared<VI_S>(system, nv_flinger, hos_binder_driver_server) + ->InstallAsService(service_manager); + std::make_shared<VI_U>(system, nv_flinger, hos_binder_driver_server) + ->InstallAsService(service_manager); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index 2fd7f8e61..d68f2646b 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -15,8 +15,9 @@ class HLERequestContext; } namespace Service::NVFlinger { +class HosBinderDriverServer; class NVFlinger; -} +} // namespace Service::NVFlinger namespace Service::SM { class ServiceManager; @@ -47,11 +48,14 @@ enum class Policy { namespace detail { void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system, - NVFlinger::NVFlinger& nv_flinger, Permission permission); + NVFlinger::NVFlinger& nv_flinger, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server, + Permission permission); } // namespace detail /// Registers all VI services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system, - NVFlinger::NVFlinger& nv_flinger); + NVFlinger::NVFlinger& nv_flinger, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server); } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index 87db1c416..be0255f3d 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -8,8 +8,10 @@ namespace Service::VI { -VI_M::VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) - : ServiceFramework{system_, "vi:m"}, nv_flinger{nv_flinger_} { +VI_M::VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_) + : ServiceFramework{system_, "vi:m"}, nv_flinger{nv_flinger_}, hos_binder_driver_server{ + hos_binder_driver_server_} { static const FunctionInfo functions[] = { {2, &VI_M::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -22,7 +24,8 @@ VI_M::~VI_M() = default; void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); - detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::Manager); + detail::GetDisplayServiceImpl(ctx, system, nv_flinger, hos_binder_driver_server, + Permission::Manager); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index d79c41beb..efbd34e09 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -15,20 +15,23 @@ class HLERequestContext; } namespace Service::NVFlinger { +class HosBinderDriverServer; class NVFlinger; -} +} // namespace Service::NVFlinger namespace Service::VI { class VI_M final : public ServiceFramework<VI_M> { public: - explicit VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); + explicit VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_); ~VI_M() override; private: void GetDisplayService(Kernel::HLERequestContext& ctx); NVFlinger::NVFlinger& nv_flinger; + NVFlinger::HosBinderDriverServer& hos_binder_driver_server; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp index 5cd22f7df..7996a6811 100644 --- a/src/core/hle/service/vi/vi_s.cpp +++ b/src/core/hle/service/vi/vi_s.cpp @@ -8,8 +8,10 @@ namespace Service::VI { -VI_S::VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) - : ServiceFramework{system_, "vi:s"}, nv_flinger{nv_flinger_} { +VI_S::VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_) + : ServiceFramework{system_, "vi:s"}, nv_flinger{nv_flinger_}, hos_binder_driver_server{ + hos_binder_driver_server_} { static const FunctionInfo functions[] = { {1, &VI_S::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -22,7 +24,8 @@ VI_S::~VI_S() = default; void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); - detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::System); + detail::GetDisplayServiceImpl(ctx, system, nv_flinger, hos_binder_driver_server, + Permission::System); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h index 5f1f8f290..3812c5061 100644 --- a/src/core/hle/service/vi/vi_s.h +++ b/src/core/hle/service/vi/vi_s.h @@ -15,20 +15,23 @@ class HLERequestContext; } namespace Service::NVFlinger { +class HosBinderDriverServer; class NVFlinger; -} +} // namespace Service::NVFlinger namespace Service::VI { class VI_S final : public ServiceFramework<VI_S> { public: - explicit VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); + explicit VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_); ~VI_S() override; private: void GetDisplayService(Kernel::HLERequestContext& ctx); NVFlinger::NVFlinger& nv_flinger; + NVFlinger::HosBinderDriverServer& hos_binder_driver_server; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp index 0079d51f0..57c888313 100644 --- a/src/core/hle/service/vi/vi_u.cpp +++ b/src/core/hle/service/vi/vi_u.cpp @@ -8,8 +8,10 @@ namespace Service::VI { -VI_U::VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) - : ServiceFramework{system_, "vi:u"}, nv_flinger{nv_flinger_} { +VI_U::VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_) + : ServiceFramework{system_, "vi:u"}, nv_flinger{nv_flinger_}, hos_binder_driver_server{ + hos_binder_driver_server_} { static const FunctionInfo functions[] = { {0, &VI_U::GetDisplayService, "GetDisplayService"}, {1, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -22,7 +24,8 @@ VI_U::~VI_U() = default; void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); - detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::User); + detail::GetDisplayServiceImpl(ctx, system, nv_flinger, hos_binder_driver_server, + Permission::User); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h index 8e3885c73..b08e56576 100644 --- a/src/core/hle/service/vi/vi_u.h +++ b/src/core/hle/service/vi/vi_u.h @@ -15,20 +15,23 @@ class HLERequestContext; } namespace Service::NVFlinger { +class HosBinderDriverServer; class NVFlinger; -} +} // namespace Service::NVFlinger namespace Service::VI { class VI_U final : public ServiceFramework<VI_U> { public: - explicit VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); + explicit VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_, + NVFlinger::HosBinderDriverServer& hos_binder_driver_server_); ~VI_U() override; private: void GetDisplayService(Kernel::HLERequestContext& ctx); NVFlinger::NVFlinger& nv_flinger; + NVFlinger::HosBinderDriverServer& hos_binder_driver_server; }; } // namespace Service::VI |