diff options
Diffstat (limited to 'src/core')
72 files changed, 2501 insertions, 1002 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 11d554bad..a614b176b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,4 +1,9 @@ +# SPDX-FileCopyrightText: 2018 yuzu Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + add_library(core STATIC + announce_multiplayer_session.cpp + announce_multiplayer_session.h arm/arm_interface.h arm/arm_interface.cpp arm/cpu_interrupt_handler.cpp @@ -158,6 +163,7 @@ add_library(core STATIC hid/input_converter.h hid/input_interpreter.cpp hid/input_interpreter.h + hid/irs_types.h hid/motion_input.cpp hid/motion_input.h hle/api_version.h @@ -477,6 +483,20 @@ add_library(core STATIC hle/service/hid/hidbus/starlink.h hle/service/hid/hidbus/stubbed.cpp hle/service/hid/hidbus/stubbed.h + hle/service/hid/irsensor/clustering_processor.cpp + hle/service/hid/irsensor/clustering_processor.h + hle/service/hid/irsensor/image_transfer_processor.cpp + hle/service/hid/irsensor/image_transfer_processor.h + hle/service/hid/irsensor/ir_led_processor.cpp + hle/service/hid/irsensor/ir_led_processor.h + hle/service/hid/irsensor/moment_processor.cpp + hle/service/hid/irsensor/moment_processor.h + hle/service/hid/irsensor/pointing_processor.cpp + hle/service/hid/irsensor/pointing_processor.h + hle/service/hid/irsensor/processor_base.cpp + hle/service/hid/irsensor/processor_base.h + hle/service/hid/irsensor/tera_plugin_processor.cpp + hle/service/hid/irsensor/tera_plugin_processor.h hle/service/jit/jit_context.cpp hle/service/jit/jit_context.h hle/service/jit/jit.cpp @@ -699,6 +719,11 @@ add_library(core STATIC hle/service/vi/vi_u.h hle/service/wlan/wlan.cpp hle/service/wlan/wlan.h + internal_network/network.cpp + internal_network/network.h + internal_network/network_interface.cpp + internal_network/network_interface.h + internal_network/sockets.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h loader/elf.cpp @@ -726,11 +751,6 @@ add_library(core STATIC memory/dmnt_cheat_vm.h memory.cpp memory.h - network/network.cpp - network/network.h - network/network_interface.cpp - network/network_interface.h - network/sockets.h perf_stats.cpp perf_stats.h reporter.cpp @@ -765,7 +785,7 @@ endif() create_target_directory_groups(core) -target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) +target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) if (MINGW) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) diff --git a/src/core/announce_multiplayer_session.cpp b/src/core/announce_multiplayer_session.cpp new file mode 100644 index 000000000..d73a488cf --- /dev/null +++ b/src/core/announce_multiplayer_session.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <chrono> +#include <future> +#include <vector> +#include "announce_multiplayer_session.h" +#include "common/announce_multiplayer_room.h" +#include "common/assert.h" +#include "common/settings.h" +#include "network/network.h" + +#ifdef ENABLE_WEB_SERVICE +#include "web_service/announce_room_json.h" +#endif + +namespace Core { + +// Time between room is announced to web_service +static constexpr std::chrono::seconds announce_time_interval(15); + +AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_) + : room_network{room_network_} { +#ifdef ENABLE_WEB_SERVICE + backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(), + Settings::values.yuzu_username.GetValue(), + Settings::values.yuzu_token.GetValue()); +#else + backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>(); +#endif +} + +WebService::WebResult AnnounceMultiplayerSession::Register() { + std::shared_ptr<Network::Room> room = room_network.GetRoom().lock(); + if (!room) { + return WebService::WebResult{WebService::WebResult::Code::LibError, + "Network is not initialized", ""}; + } + if (room->GetState() != Network::Room::State::Open) { + return WebService::WebResult{WebService::WebResult::Code::LibError, "Room is not open", ""}; + } + UpdateBackendData(room); + WebService::WebResult result = backend->Register(); + if (result.result_code != WebService::WebResult::Code::Success) { + return result; + } + LOG_INFO(WebService, "Room has been registered"); + room->SetVerifyUID(result.returned_data); + registered = true; + return WebService::WebResult{WebService::WebResult::Code::Success, "", ""}; +} + +void AnnounceMultiplayerSession::Start() { + if (announce_multiplayer_thread) { + Stop(); + } + shutdown_event.Reset(); + announce_multiplayer_thread = + std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this); +} + +void AnnounceMultiplayerSession::Stop() { + if (announce_multiplayer_thread) { + shutdown_event.Set(); + announce_multiplayer_thread->join(); + announce_multiplayer_thread.reset(); + backend->Delete(); + registered = false; + } +} + +AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback( + std::function<void(const WebService::WebResult&)> function) { + std::lock_guard lock(callback_mutex); + auto handle = std::make_shared<std::function<void(const WebService::WebResult&)>>(function); + error_callbacks.insert(handle); + return handle; +} + +void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) { + std::lock_guard lock(callback_mutex); + error_callbacks.erase(handle); +} + +AnnounceMultiplayerSession::~AnnounceMultiplayerSession() { + Stop(); +} + +void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) { + Network::RoomInformation room_information = room->GetRoomInformation(); + std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList(); + backend->SetRoomInformation(room_information.name, room_information.description, + room_information.port, room_information.member_slots, + Network::network_version, room->HasPassword(), + room_information.preferred_game); + backend->ClearPlayers(); + for (const auto& member : memberlist) { + backend->AddPlayer(member); + } +} + +void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { + // Invokes all current bound error callbacks. + const auto ErrorCallback = [this](WebService::WebResult result) { + std::lock_guard<std::mutex> lock(callback_mutex); + for (auto callback : error_callbacks) { + (*callback)(result); + } + }; + + if (!registered) { + WebService::WebResult result = Register(); + if (result.result_code != WebService::WebResult::Code::Success) { + ErrorCallback(result); + return; + } + } + + auto update_time = std::chrono::steady_clock::now(); + std::future<WebService::WebResult> future; + while (!shutdown_event.WaitUntil(update_time)) { + update_time += announce_time_interval; + std::shared_ptr<Network::Room> room = room_network.GetRoom().lock(); + if (!room) { + break; + } + if (room->GetState() != Network::Room::State::Open) { + break; + } + UpdateBackendData(room); + WebService::WebResult result = backend->Update(); + if (result.result_code != WebService::WebResult::Code::Success) { + ErrorCallback(result); + } + if (result.result_string == "404") { + registered = false; + // Needs to register the room again + WebService::WebResult register_result = Register(); + if (register_result.result_code != WebService::WebResult::Code::Success) { + ErrorCallback(register_result); + } + } + } +} + +AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() { + return backend->GetRoomList(); +} + +bool AnnounceMultiplayerSession::IsRunning() const { + return announce_multiplayer_thread != nullptr; +} + +void AnnounceMultiplayerSession::UpdateCredentials() { + ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running"); + +#ifdef ENABLE_WEB_SERVICE + backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(), + Settings::values.yuzu_username.GetValue(), + Settings::values.yuzu_token.GetValue()); +#endif +} + +} // namespace Core diff --git a/src/core/announce_multiplayer_session.h b/src/core/announce_multiplayer_session.h new file mode 100644 index 000000000..db790f7d2 --- /dev/null +++ b/src/core/announce_multiplayer_session.h @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <atomic> +#include <functional> +#include <memory> +#include <mutex> +#include <set> +#include <thread> +#include "common/announce_multiplayer_room.h" +#include "common/common_types.h" +#include "common/thread.h" + +namespace Network { +class Room; +class RoomNetwork; +} // namespace Network + +namespace Core { + +/** + * Instruments AnnounceMultiplayerRoom::Backend. + * Creates a thread that regularly updates the room information and submits them + * An async get of room information is also possible + */ +class AnnounceMultiplayerSession { +public: + using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>; + AnnounceMultiplayerSession(Network::RoomNetwork& room_network_); + ~AnnounceMultiplayerSession(); + + /** + * Allows to bind a function that will get called if the announce encounters an error + * @param function The function that gets called + * @return A handle that can be used the unbind the function + */ + CallbackHandle BindErrorCallback(std::function<void(const WebService::WebResult&)> function); + + /** + * Unbind a function from the error callbacks + * @param handle The handle for the function that should get unbind + */ + void UnbindErrorCallback(CallbackHandle handle); + + /** + * Registers a room to web services + * @return The result of the registration attempt. + */ + WebService::WebResult Register(); + + /** + * Starts the announce of a room to web services + */ + void Start(); + + /** + * Stops the announce to web services + */ + void Stop(); + + /** + * Returns a list of all room information the backend got + * @param func A function that gets executed when the async get finished, e.g. a signal + * @return a list of rooms received from the web service + */ + AnnounceMultiplayerRoom::RoomList GetRoomList(); + + /** + * Whether the announce session is still running + */ + bool IsRunning() const; + + /** + * Recreates the backend, updating the credentials. + * This can only be used when the announce session is not running. + */ + void UpdateCredentials(); + +private: + void UpdateBackendData(std::shared_ptr<Network::Room> room); + void AnnounceMultiplayerLoop(); + + Common::Event shutdown_event; + std::mutex callback_mutex; + std::set<CallbackHandle> error_callbacks; + std::unique_ptr<std::thread> announce_multiplayer_thread; + + /// Backend interface that logs fields + std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend; + + std::atomic_bool registered = false; ///< Whether the room has been registered + + Network::RoomNetwork& room_network; +}; + +} // namespace Core diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index e72b250be..953d96439 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -154,9 +154,10 @@ void ARM_Interface::Run() { break; } - // Handle syscalls and scheduling (this may change the current thread) + // Handle syscalls and scheduling (this may change the current thread/core) if (Has(hr, svc_call)) { Kernel::Svc::Call(system, GetSvcNumber()); + break; } if (Has(hr, break_loop) || !uses_wall_clock) { break; diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index c092db9ff..73f259525 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp index 6aae79c48..e9123c13d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <fmt/format.h> #include "common/logging/log.h" diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h index f271b2070..5b2a51636 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/core.cpp b/src/core/core.cpp index 0ede0d85c..ea32a4a8d 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <array> #include <atomic> @@ -43,14 +42,15 @@ #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/time/time_manager.h" +#include "core/internal_network/network.h" #include "core/loader/loader.h" #include "core/memory.h" #include "core/memory/cheat_engine.h" -#include "core/network/network.h" #include "core/perf_stats.h" #include "core/reporter.h" #include "core/telemetry_session.h" #include "core/tools/freezer.h" +#include "network/network.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -130,7 +130,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, struct System::Impl { explicit Impl(System& system) - : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, + : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{}, cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} SystemResultStatus Run() { @@ -315,6 +315,17 @@ struct System::Impl { GetAndResetPerfStats(); perf_stats->BeginSystemFrame(); + std::string name = "Unknown Game"; + if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) { + LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result); + } + if (auto room_member = room_network.GetRoomMember().lock()) { + Network::GameInfo game_info; + game_info.name = name; + game_info.id = program_id; + room_member->SendGameInfo(game_info); + } + status = SystemResultStatus::Success; return status; } @@ -362,6 +373,11 @@ struct System::Impl { memory.Reset(); applet_manager.ClearAll(); + if (auto room_member = room_network.GetRoomMember().lock()) { + Network::GameInfo game_info{}; + room_member->SendGameInfo(game_info); + } + LOG_DEBUG(Core, "Shutdown OK"); } @@ -434,6 +450,8 @@ struct System::Impl { std::unique_ptr<AudioCore::AudioCore> audio_core; Core::Memory::Memory memory; Core::HID::HIDCore hid_core; + Network::RoomNetwork room_network; + CpuManager cpu_manager; std::atomic_bool is_powered_on{}; bool exit_lock = false; @@ -879,6 +897,14 @@ const Core::Debugger& System::GetDebugger() const { return *impl->debugger; } +Network::RoomNetwork& System::GetRoomNetwork() { + return impl->room_network; +} + +const Network::RoomNetwork& System::GetRoomNetwork() const { + return impl->room_network; +} + void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) { impl->execute_program_callback = std::move(callback); } diff --git a/src/core/core.h b/src/core/core.h index a49d1214b..0ce3b1d60 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -97,6 +96,10 @@ namespace Core::HID { class HIDCore; } +namespace Network { +class RoomNetwork; +} + namespace Core { class ARM_Interface; @@ -379,6 +382,12 @@ public: [[nodiscard]] Core::Debugger& GetDebugger(); [[nodiscard]] const Core::Debugger& GetDebugger() const; + /// Gets a mutable reference to the Room Network. + [[nodiscard]] Network::RoomNetwork& GetRoomNetwork(); + + /// Gets an immutable reference to the Room Network. + [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; + void SetExitLock(bool locked); [[nodiscard]] bool GetExitLock() const; diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 37d3d83b9..9b1565ae1 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -8,6 +8,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/cpu_manager.h" +#include "core/hle/kernel/k_interrupt_manager.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" @@ -49,14 +50,6 @@ void CpuManager::GuestThreadFunction() { } } -void CpuManager::GuestRewindFunction() { - if (is_multicore) { - MultiCoreRunGuestLoop(); - } else { - SingleCoreRunGuestLoop(); - } -} - void CpuManager::IdleThreadFunction() { if (is_multicore) { MultiCoreRunIdleThread(); @@ -69,21 +62,21 @@ void CpuManager::ShutdownThreadFunction() { ShutdownThread(); } +void CpuManager::HandleInterrupt() { + auto& kernel = system.Kernel(); + auto core_index = kernel.CurrentPhysicalCoreIndex(); + + Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_index)); +} + /////////////////////////////////////////////////////////////////////////////// /// MultiCore /// /////////////////////////////////////////////////////////////////////////////// void CpuManager::MultiCoreRunGuestThread() { + // Similar to UserModeThreadStarter in HOS auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); - auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread(); - auto& host_context = thread->GetHostContext(); - host_context->SetRewindPoint([this] { GuestRewindFunction(); }); - MultiCoreRunGuestLoop(); -} - -void CpuManager::MultiCoreRunGuestLoop() { - auto& kernel = system.Kernel(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); @@ -91,18 +84,26 @@ void CpuManager::MultiCoreRunGuestLoop() { physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } - { - Kernel::KScopedDisableDispatch dd(kernel); - physical_core->ArmInterface().ClearExclusiveState(); - } + + HandleInterrupt(); } } void CpuManager::MultiCoreRunIdleThread() { + // Not accurate to HOS. Remove this entire method when singlecore is removed. + // See notes in KScheduler::ScheduleImpl for more information about why this + // is inaccurate. + auto& kernel = system.Kernel(); + kernel.CurrentScheduler()->OnThreadStart(); + while (true) { - Kernel::KScopedDisableDispatch dd(kernel); - kernel.CurrentPhysicalCore().Idle(); + auto& physical_core = kernel.CurrentPhysicalCore(); + if (!physical_core.IsInterrupted()) { + physical_core.Idle(); + } + + HandleInterrupt(); } } @@ -113,80 +114,73 @@ void CpuManager::MultiCoreRunIdleThread() { void CpuManager::SingleCoreRunGuestThread() { auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); - auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread(); - auto& host_context = thread->GetHostContext(); - host_context->SetRewindPoint([this] { GuestRewindFunction(); }); - SingleCoreRunGuestLoop(); -} -void CpuManager::SingleCoreRunGuestLoop() { - auto& kernel = system.Kernel(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); if (!physical_core->IsInterrupted()) { physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } + kernel.SetIsPhantomModeForSingleCore(true); system.CoreTiming().Advance(); kernel.SetIsPhantomModeForSingleCore(false); - physical_core->ArmInterface().ClearExclusiveState(); + PreemptSingleCore(); - auto& scheduler = kernel.Scheduler(current_core); - scheduler.RescheduleCurrentCore(); + HandleInterrupt(); } } void CpuManager::SingleCoreRunIdleThread() { auto& kernel = system.Kernel(); + kernel.CurrentScheduler()->OnThreadStart(); + while (true) { - auto& physical_core = kernel.CurrentPhysicalCore(); PreemptSingleCore(false); system.CoreTiming().AddTicks(1000U); idle_count++; - auto& scheduler = physical_core.Scheduler(); - scheduler.RescheduleCurrentCore(); + HandleInterrupt(); } } -void CpuManager::PreemptSingleCore(bool from_running_enviroment) { - { - auto& kernel = system.Kernel(); - auto& scheduler = kernel.Scheduler(current_core); - Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread(); - if (idle_count >= 4 || from_running_enviroment) { - if (!from_running_enviroment) { - system.CoreTiming().Idle(); - idle_count = 0; - } - kernel.SetIsPhantomModeForSingleCore(true); - system.CoreTiming().Advance(); - kernel.SetIsPhantomModeForSingleCore(false); - } - current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); - system.CoreTiming().ResetTicks(); - scheduler.Unload(scheduler.GetSchedulerCurrentThread()); - - auto& next_scheduler = kernel.Scheduler(current_core); - Common::Fiber::YieldTo(current_thread->GetHostContext(), *next_scheduler.ControlContext()); - } +void CpuManager::PreemptSingleCore(bool from_running_environment) { + auto& kernel = system.Kernel(); - // May have changed scheduler - { - auto& scheduler = system.Kernel().Scheduler(current_core); - scheduler.Reload(scheduler.GetSchedulerCurrentThread()); - if (!scheduler.IsIdle()) { + if (idle_count >= 4 || from_running_environment) { + if (!from_running_environment) { + system.CoreTiming().Idle(); idle_count = 0; } + kernel.SetIsPhantomModeForSingleCore(true); + system.CoreTiming().Advance(); + kernel.SetIsPhantomModeForSingleCore(false); + } + current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); + system.CoreTiming().ResetTicks(); + kernel.Scheduler(current_core).PreemptSingleCore(); + + // We've now been scheduled again, and we may have exchanged schedulers. + // Reload the scheduler in case it's different. + if (!kernel.Scheduler(current_core).IsIdle()) { + idle_count = 0; } } +void CpuManager::GuestActivate() { + // Similar to the HorizonKernelMain callback in HOS + auto& kernel = system.Kernel(); + auto* scheduler = kernel.CurrentScheduler(); + + scheduler->Activate(); + UNREACHABLE(); +} + void CpuManager::ShutdownThread() { auto& kernel = system.Kernel(); + auto* thread = kernel.GetCurrentEmuThread(); auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0; - auto* current_thread = kernel.GetCurrentEmuThread(); - Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context); + Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context); UNREACHABLE(); } @@ -218,9 +212,12 @@ void CpuManager::RunThread(std::size_t core) { system.GPU().ObtainContext(); } - auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread(); - Kernel::SetCurrentThread(system.Kernel(), current_thread); - Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext()); + auto& kernel = system.Kernel(); + auto& scheduler = *kernel.CurrentScheduler(); + auto* thread = scheduler.GetSchedulerCurrentThread(); + Kernel::SetCurrentThread(kernel, thread); + + Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext()); } } // namespace Core diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index 76dc58ee1..95ea3ef39 100644 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -50,7 +50,10 @@ public: void Initialize(); void Shutdown(); - std::function<void()> GetGuestThreadStartFunc() { + std::function<void()> GetGuestActivateFunc() { + return [this] { GuestActivate(); }; + } + std::function<void()> GetGuestThreadFunc() { return [this] { GuestThreadFunction(); }; } std::function<void()> GetIdleThreadStartFunc() { @@ -68,20 +71,19 @@ public: private: void GuestThreadFunction(); - void GuestRewindFunction(); void IdleThreadFunction(); void ShutdownThreadFunction(); void MultiCoreRunGuestThread(); - void MultiCoreRunGuestLoop(); void MultiCoreRunIdleThread(); void SingleCoreRunGuestThread(); - void SingleCoreRunGuestLoop(); void SingleCoreRunIdleThread(); static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core); + void GuestActivate(); + void HandleInterrupt(); void ShutdownThread(); void RunThread(std::size_t core); diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index ff15b3e23..7cee0c7df 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 57c6ffc43..1be2dccb0 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <mutex> #include "core/frontend/emu_window.h" diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index b3bffecb2..ac1906d5e 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index bd2384515..8c3895937 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -126,10 +126,14 @@ void EmulatedController::LoadDevices() { battery_params[LeftIndex].Set("battery", true); battery_params[RightIndex].Set("battery", true); + camera_params = Common::ParamPackage{"engine:camera,camera:1"}; + output_params[LeftIndex] = left_joycon; output_params[RightIndex] = right_joycon; + output_params[2] = camera_params; output_params[LeftIndex].Set("output", true); output_params[RightIndex].Set("output", true); + output_params[2].Set("output", true); LoadTASParams(); @@ -146,6 +150,7 @@ void EmulatedController::LoadDevices() { Common::Input::CreateDevice<Common::Input::InputDevice>); std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>); + camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params); std::transform(output_params.begin(), output_params.end(), output_devices.begin(), Common::Input::CreateDevice<Common::Input::OutputDevice>); @@ -267,6 +272,14 @@ void EmulatedController::ReloadInput() { motion_devices[index]->ForceUpdate(); } + if (camera_devices) { + camera_devices->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); }, + }); + camera_devices->ForceUpdate(); + } + // Use a common UUID for TAS static constexpr Common::UUID TAS_UUID = Common::UUID{ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; @@ -851,6 +864,25 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac TriggerOnChange(ControllerTriggerType::Battery, true); } +void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) { + std::unique_lock lock{mutex}; + controller.camera_values = TransformToCamera(callback); + + if (is_configuring) { + lock.unlock(); + TriggerOnChange(ControllerTriggerType::IrSensor, false); + return; + } + + controller.camera_state.sample++; + controller.camera_state.format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format); + controller.camera_state.data = controller.camera_values.data; + + lock.unlock(); + TriggerOnChange(ControllerTriggerType::IrSensor, true); +} + bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { if (device_index >= output_devices.size()) { return false; @@ -928,6 +960,23 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; } +bool EmulatedController::SetCameraFormat( + Core::IrSensor::ImageTransferProcessorFormat camera_format) { + LOG_INFO(Service_HID, "Set camera format {}", camera_format); + + auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; + auto& camera_output_device = output_devices[2]; + + if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( + camera_format)) == Common::Input::CameraError::None) { + return true; + } + + // Fallback to Qt camera if native device doesn't have support + return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( + camera_format)) == Common::Input::CameraError::None; +} + void EmulatedController::SetLedPattern() { for (auto& device : output_devices) { if (!device) { @@ -1163,6 +1212,11 @@ BatteryValues EmulatedController::GetBatteryValues() const { return controller.battery_values; } +CameraValues EmulatedController::GetCameraValues() const { + std::scoped_lock lock{mutex}; + return controller.camera_values; +} + HomeButtonState EmulatedController::GetHomeButtons() const { std::scoped_lock lock{mutex}; if (is_configuring) { @@ -1251,6 +1305,11 @@ BatteryLevelState EmulatedController::GetBattery() const { return controller.battery_state; } +const CameraState& EmulatedController::GetCamera() const { + std::scoped_lock lock{mutex}; + return controller.camera_state; +} + void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { std::scoped_lock lock{callback_mutex}; for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 3f02ed3c0..823c1700c 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -15,10 +15,12 @@ #include "common/settings.h" #include "common/vector_math.h" #include "core/hid/hid_types.h" +#include "core/hid/irs_types.h" #include "core/hid/motion_input.h" namespace Core::HID { const std::size_t max_emulated_controllers = 2; +const std::size_t output_devices = 3; struct ControllerMotionInfo { Common::Input::MotionStatus raw_status{}; MotionInput emulated{}; @@ -34,15 +36,16 @@ using TriggerDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; using BatteryDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; -using OutputDevices = - std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>; +using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; +using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>; using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; -using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>; +using CameraParams = Common::ParamPackage; +using OutputParams = std::array<Common::ParamPackage, output_devices>; using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; @@ -51,6 +54,7 @@ using TriggerValues = using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>; using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; +using CameraValues = Common::Input::CameraStatus; using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; struct AnalogSticks { @@ -70,6 +74,12 @@ struct BatteryLevelState { NpadPowerInfo right{}; }; +struct CameraState { + Core::IrSensor::ImageTransferProcessorFormat format{}; + std::vector<u8> data{}; + std::size_t sample{}; +}; + struct ControllerMotion { Common::Vec3f accel{}; Common::Vec3f gyro{}; @@ -96,6 +106,7 @@ struct ControllerStatus { ColorValues color_values{}; BatteryValues battery_values{}; VibrationValues vibration_values{}; + CameraValues camera_values{}; // Data for HID serices HomeButtonState home_button_state{}; @@ -107,6 +118,7 @@ struct ControllerStatus { NpadGcTriggerState gc_trigger_state{}; ControllerColors colors_state{}; BatteryLevelState battery_state{}; + CameraState camera_state{}; }; enum class ControllerTriggerType { @@ -117,6 +129,7 @@ enum class ControllerTriggerType { Color, Battery, Vibration, + IrSensor, Connected, Disconnected, Type, @@ -269,6 +282,9 @@ public: /// Returns the latest battery status from the controller with parameters BatteryValues GetBatteryValues() const; + /// Returns the latest camera status from the controller with parameters + CameraValues GetCameraValues() const; + /// Returns the latest status of button input for the hid::HomeButton service HomeButtonState GetHomeButtons() const; @@ -296,6 +312,9 @@ public: /// Returns the latest battery status from the controller BatteryLevelState GetBattery() const; + /// Returns the latest camera status from the controller + const CameraState& GetCamera() const; + /** * Sends a specific vibration to the output device * @return true if vibration had no errors @@ -315,6 +334,13 @@ public: */ bool SetPollingMode(Common::Input::PollingMode polling_mode); + /** + * Sets the desired camera format to be polled from a controller + * @param camera_format size of each frame + * @return true if SetCameraFormat was successfull + */ + bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); + /// Returns the led pattern corresponding to this emulated controller LedPattern GetLedPattern() const; @@ -393,6 +419,12 @@ private: void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); /** + * Updates the camera status of the controller + * @param callback A CallbackStatus containing the camera status + */ + void SetCamera(const Common::Input::CallbackStatus& callback); + + /** * Triggers a callback that something has changed on the controller status * @param type Input type of the event to trigger * @param is_service_update indicates if this event should only be sent to HID services @@ -417,6 +449,7 @@ private: ControllerMotionParams motion_params; TriggerParams trigger_params; BatteryParams battery_params; + CameraParams camera_params; OutputParams output_params; ButtonDevices button_devices; @@ -424,6 +457,7 @@ private: ControllerMotionDevices motion_devices; TriggerDevices trigger_devices; BatteryDevices battery_devices; + CameraDevices camera_devices; OutputDevices output_devices; // TAS related variables diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 18d9f042d..68d143a01 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -270,6 +270,20 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu return status; } +Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) { + Common::Input::CameraStatus camera{}; + switch (callback.type) { + case Common::Input::InputType::IrSensor: + camera = callback.camera_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type); + break; + } + + return camera; +} + void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { const auto& properties = analog.properties; float& raw_value = analog.raw_value; diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h index 2be36889f..143c50cc0 100644 --- a/src/core/hid/input_converter.h +++ b/src/core/hid/input_converter.h @@ -77,6 +77,14 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback); /** + * Converts raw input data into a valid camera status. + * + * @param callback Supported callbacks: Camera. + * @return A valid CameraObject object. + */ +Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback); + +/** * Converts raw analog data into a valid analog value * @param analog An analog object containing raw data and properties * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h new file mode 100644 index 000000000..88c5b016d --- /dev/null +++ b/src/core/hid/irs_types.h @@ -0,0 +1,301 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hid/hid_types.h" + +namespace Core::IrSensor { + +// This is nn::irsensor::CameraAmbientNoiseLevel +enum class CameraAmbientNoiseLevel : u32 { + Low, + Medium, + High, + Unkown3, // This level can't be reached +}; + +// This is nn::irsensor::CameraLightTarget +enum class CameraLightTarget : u32 { + AllLeds, + BrightLeds, + DimLeds, + None, +}; + +// This is nn::irsensor::PackedCameraLightTarget +enum class PackedCameraLightTarget : u8 { + AllLeds, + BrightLeds, + DimLeds, + None, +}; + +// This is nn::irsensor::AdaptiveClusteringMode +enum class AdaptiveClusteringMode : u32 { + StaticFov, + DynamicFov, +}; + +// This is nn::irsensor::AdaptiveClusteringTargetDistance +enum class AdaptiveClusteringTargetDistance : u32 { + Near, + Middle, + Far, +}; + +// This is nn::irsensor::ImageTransferProcessorFormat +enum class ImageTransferProcessorFormat : u32 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, +}; + +// This is nn::irsensor::PackedImageTransferProcessorFormat +enum class PackedImageTransferProcessorFormat : u8 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, +}; + +// This is nn::irsensor::IrCameraStatus +enum class IrCameraStatus : u32 { + Available, + Unsupported, + Unconnected, +}; + +// This is nn::irsensor::IrCameraInternalStatus +enum class IrCameraInternalStatus : u32 { + Stopped, + FirmwareUpdateNeeded, + Unkown2, + Unkown3, + Unkown4, + FirmwareVersionRequested, + FirmwareVersionIsInvalid, + Ready, + Setting, +}; + +// This is nn::irsensor::detail::StatusManager::IrSensorMode +enum class IrSensorMode : u64 { + None, + MomentProcessor, + ClusteringProcessor, + ImageTransferProcessor, + PointingProcessorMarker, + TeraPluginProcessor, + IrLedProcessor, +}; + +// This is nn::irsensor::ImageProcessorStatus +enum ImageProcessorStatus : u32 { + Stopped, + Running, +}; + +// This is nn::irsensor::HandAnalysisMode +enum class HandAnalysisMode : u32 { + None, + Silhouette, + Image, + SilhoueteAndImage, + SilhuetteOnly, +}; + +// This is nn::irsensor::IrSensorFunctionLevel +enum class IrSensorFunctionLevel : u8 { + unknown0, + unknown1, + unknown2, + unknown3, + unknown4, +}; + +// This is nn::irsensor::MomentProcessorPreprocess +enum class MomentProcessorPreprocess : u32 { + Unkown0, + Unkown1, +}; + +// This is nn::irsensor::PackedMomentProcessorPreprocess +enum class PackedMomentProcessorPreprocess : u8 { + Unkown0, + Unkown1, +}; + +// This is nn::irsensor::PointingStatus +enum class PointingStatus : u32 { + Unkown0, + Unkown1, +}; + +struct IrsRect { + s16 x; + s16 y; + s16 width; + s16 height; +}; + +struct IrsCentroid { + f32 x; + f32 y; +}; + +struct CameraConfig { + u64 exposure_time; + CameraLightTarget light_target; + u32 gain; + bool is_negative_used; + INSERT_PADDING_BYTES(7); +}; +static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size"); + +struct PackedCameraConfig { + u64 exposure_time; + PackedCameraLightTarget light_target; + u8 gain; + bool is_negative_used; + INSERT_PADDING_BYTES(5); +}; +static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size"); + +// This is nn::irsensor::IrCameraHandle +struct IrCameraHandle { + u8 npad_id{}; + Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); + +// This is nn::irsensor::PackedMcuVersion +struct PackedMcuVersion { + u16 major; + u16 minor; +}; +static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); + +// This is nn::irsensor::PackedMomentProcessorConfig +struct PackedMomentProcessorConfig { + PackedCameraConfig camera_config; + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + PackedMomentProcessorPreprocess preprocess; + u8 preprocess_intensity_threshold; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, + "PackedMomentProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedClusteringProcessorConfig +struct PackedClusteringProcessorConfig { + PackedCameraConfig camera_config; + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + u32 pixel_count_min; + u32 pixel_count_max; + u8 object_intensity_min; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28, + "PackedClusteringProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedImageTransferProcessorConfig +struct PackedImageTransferProcessorConfig { + PackedCameraConfig camera_config; + PackedMcuVersion required_mcu_version; + PackedImageTransferProcessorFormat format; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, + "PackedImageTransferProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedTeraPluginProcessorConfig +struct PackedTeraPluginProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 mode; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; +}; +static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, + "PackedTeraPluginProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedPointingProcessorConfig +struct PackedPointingProcessorConfig { + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; +}; +static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, + "PackedPointingProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedFunctionLevel +struct PackedFunctionLevel { + IrSensorFunctionLevel function_level; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); + +// This is nn::irsensor::PackedImageTransferProcessorExConfig +struct PackedImageTransferProcessorExConfig { + PackedCameraConfig camera_config; + PackedMcuVersion required_mcu_version; + PackedImageTransferProcessorFormat origin_format; + PackedImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(5); +}; +static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, + "PackedImageTransferProcessorExConfig is an invalid size"); + +// This is nn::irsensor::PackedIrLedProcessorConfig +struct PackedIrLedProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 light_target; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, + "PackedIrLedProcessorConfig is an invalid size"); + +// This is nn::irsensor::HandAnalysisConfig +struct HandAnalysisConfig { + HandAnalysisMode mode; +}; +static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size"); + +// This is nn::irsensor::detail::ProcessorState contents are different for each processor +struct ProcessorState { + std::array<u8, 0xE20> processor_raw_data{}; +}; +static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size"); + +// This is nn::irsensor::detail::DeviceFormat +struct DeviceFormat { + Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected}; + Core::IrSensor::IrCameraInternalStatus camera_internal_status{ + Core::IrSensor::IrCameraInternalStatus::Ready}; + Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None}; + ProcessorState state{}; +}; +static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size"); + +// This is nn::irsensor::ImageTransferProcessorState +struct ImageTransferProcessorState { + u64 sampling_number; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(ImageTransferProcessorState) == 0x10, + "ImageTransferProcessorState is an invalid size"); + +} // namespace Core::IrSensor diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 602e12606..416da15ec 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 004bb2005..d631c0357 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp index 164436b26..65576b8c4 100644 --- a/src/core/hle/kernel/global_scheduler_context.cpp +++ b/src/core/hle/kernel/global_scheduler_context.cpp @@ -41,12 +41,7 @@ void GlobalSchedulerContext::PreemptThreads() { ASSERT(IsLocked()); for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { const u32 priority = preemption_priorities[core_id]; - kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority); - - // Signal an interrupt occurred. For core 3, this is a certainty, as preemption will result - // in the rotator thread being scheduled. For cores 0-2, this is to simulate or system - // interrupts that may have occurred. - kernel.PhysicalCore(core_id).Interrupt(); + KScheduler::RotateScheduledQueue(kernel, core_id, priority); } } diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp index d63e77d15..3cb22ff4d 100644 --- a/src/core/hle/kernel/k_client_port.cpp +++ b/src/core/hle/kernel/k_client_port.cpp @@ -1,6 +1,5 @@ -// Copyright 2021 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2021 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "common/scope_exit.h" #include "core/hle/kernel/hle_ipc.h" diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h index ef8583efc..e17eff28f 100644 --- a/src/core/hle/kernel/k_client_port.h +++ b/src/core/hle/kernel/k_client_port.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp index d606a7f86..1b577a5b3 100644 --- a/src/core/hle/kernel/k_interrupt_manager.cpp +++ b/src/core/hle/kernel/k_interrupt_manager.cpp @@ -6,6 +6,7 @@ #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" namespace Kernel::KInterruptManager { @@ -15,6 +16,9 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { return; } + // Acknowledge the interrupt. + kernel.PhysicalCore(core_id).ClearInterrupt(); + auto& current_thread = GetCurrentThread(kernel); // If the user disable count is set, we may need to pin the current thread. @@ -27,6 +31,9 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { // Set the interrupt flag for the thread. GetCurrentThread(kernel).SetInterruptFlag(); } + + // Request interrupt scheduling. + kernel.CurrentScheduler()->RequestScheduleOnInterrupt(); } } // namespace Kernel::KInterruptManager diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index b662788b3..d3e99665f 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <bitset> diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 5e3e22ad8..d56d73bab 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index d599d2bcb..c34ce7a17 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -27,69 +27,185 @@ static void IncrementScheduledCount(Kernel::KThread* thread) { } } -void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) { - auto scheduler = kernel.CurrentScheduler(); - - u32 current_core{0xF}; - bool must_context_switch{}; - if (scheduler) { - current_core = scheduler->core_id; - // TODO(bunnei): Should be set to true when we deprecate single core - must_context_switch = !kernel.IsPhantomModeForSingleCore(); - } - - while (cores_pending_reschedule != 0) { - const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule)); - ASSERT(core < Core::Hardware::NUM_CPU_CORES); - if (!must_context_switch || core != current_core) { - auto& phys_core = kernel.PhysicalCore(core); - phys_core.Interrupt(); +KScheduler::KScheduler(KernelCore& kernel_) : kernel{kernel_} { + m_switch_fiber = std::make_shared<Common::Fiber>([this] { + while (true) { + ScheduleImplFiber(); } - cores_pending_reschedule &= ~(1ULL << core); + }); + + m_state.needs_scheduling = true; +} + +KScheduler::~KScheduler() = default; + +void KScheduler::SetInterruptTaskRunnable() { + m_state.interrupt_task_runnable = true; + m_state.needs_scheduling = true; +} + +void KScheduler::RequestScheduleOnInterrupt() { + m_state.needs_scheduling = true; + + if (CanSchedule(kernel)) { + ScheduleOnInterrupt(); } +} - for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) { - if (kernel.PhysicalCore(core_id).IsInterrupted()) { - KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_id)); - } +void KScheduler::DisableScheduling(KernelCore& kernel) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); + GetCurrentThread(kernel).DisableDispatch(); +} + +void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 1); + + auto* scheduler{kernel.CurrentScheduler()}; + + if (!scheduler || kernel.IsPhantomModeForSingleCore()) { + KScheduler::RescheduleCores(kernel, cores_needing_scheduling); + KScheduler::RescheduleCurrentHLEThread(kernel); + return; + } + + scheduler->RescheduleOtherCores(cores_needing_scheduling); + + if (GetCurrentThread(kernel).GetDisableDispatchCount() > 1) { + GetCurrentThread(kernel).EnableDispatch(); + } else { + scheduler->RescheduleCurrentCore(); + } +} + +void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) { + // HACK: we cannot schedule from this thread, it is not a core thread + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + + // Special case to ensure dummy threads that are waiting block + GetCurrentThread(kernel).IfDummyThreadTryWait(); + + ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting); + GetCurrentThread(kernel).EnableDispatch(); +} + +u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { + if (IsSchedulerUpdateNeeded(kernel)) { + return UpdateHighestPriorityThreadsImpl(kernel); + } else { + return 0; + } +} + +void KScheduler::Schedule() { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + ASSERT(m_core_id == GetCurrentCoreId(kernel)); + + ScheduleImpl(); +} + +void KScheduler::ScheduleOnInterrupt() { + GetCurrentThread(kernel).DisableDispatch(); + Schedule(); + GetCurrentThread(kernel).EnableDispatch(); +} + +void KScheduler::PreemptSingleCore() { + GetCurrentThread(kernel).DisableDispatch(); + + auto* thread = GetCurrentThreadPointer(kernel); + auto& previous_scheduler = kernel.Scheduler(thread->GetCurrentCore()); + previous_scheduler.Unload(thread); + + Common::Fiber::YieldTo(thread->GetHostContext(), *m_switch_fiber); + + GetCurrentThread(kernel).EnableDispatch(); +} + +void KScheduler::RescheduleCurrentCore() { + ASSERT(!kernel.IsPhantomModeForSingleCore()); + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + + GetCurrentThread(kernel).EnableDispatch(); + + if (m_state.needs_scheduling.load()) { + // Disable interrupts, and then check again if rescheduling is needed. + // KScopedInterruptDisable intr_disable; + + kernel.CurrentScheduler()->RescheduleCurrentCoreImpl(); } +} - if (must_context_switch) { - auto core_scheduler = kernel.CurrentScheduler(); - kernel.ExitSVCProfile(); - core_scheduler->RescheduleCurrentCore(); - kernel.EnterSVCProfile(); +void KScheduler::RescheduleCurrentCoreImpl() { + // Check that scheduling is needed. + if (m_state.needs_scheduling.load()) [[likely]] { + GetCurrentThread(kernel).DisableDispatch(); + Schedule(); + GetCurrentThread(kernel).EnableDispatch(); } } +void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id) { + // Set core ID/idle thread/interrupt task manager. + m_core_id = core_id; + m_idle_thread = idle_thread; + // m_state.idle_thread_stack = m_idle_thread->GetStackTop(); + // m_state.interrupt_task_manager = &kernel.GetInterruptTaskManager(); + + // Insert the main thread into the priority queue. + // { + // KScopedSchedulerLock lk{kernel}; + // GetPriorityQueue(kernel).PushBack(GetCurrentThreadPointer(kernel)); + // SetSchedulerUpdateNeeded(kernel); + // } + + // Bind interrupt handler. + // kernel.GetInterruptManager().BindHandler( + // GetSchedulerInterruptHandler(kernel), KInterruptName::Scheduler, m_core_id, + // KInterruptController::PriorityLevel::Scheduler, false, false); + + // Set the current thread. + m_current_thread = main_thread; +} + +void KScheduler::Activate() { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + + // m_state.should_count_idle = KTargetSystem::IsDebugMode(); + m_is_active = true; + RescheduleCurrentCore(); +} + +void KScheduler::OnThreadStart() { + GetCurrentThread(kernel).EnableDispatch(); +} + u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) { - KScopedSpinLock lk{guard}; - if (KThread* prev_highest_thread = state.highest_priority_thread; - prev_highest_thread != highest_thread) { - if (prev_highest_thread != nullptr) { + if (KThread* prev_highest_thread = m_state.highest_priority_thread; + prev_highest_thread != highest_thread) [[likely]] { + if (prev_highest_thread != nullptr) [[likely]] { IncrementScheduledCount(prev_highest_thread); - prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks()); + prev_highest_thread->SetLastScheduledTick(kernel.System().CoreTiming().GetCPUTicks()); } - if (state.should_count_idle) { - if (highest_thread != nullptr) { + if (m_state.should_count_idle) { + if (highest_thread != nullptr) [[likely]] { if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) { - process->SetRunningThread(core_id, highest_thread, state.idle_count); + process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count); } } else { - state.idle_count++; + m_state.idle_count++; } } - state.highest_priority_thread = highest_thread; - state.needs_scheduling.store(true); - return (1ULL << core_id); + m_state.highest_priority_thread = highest_thread; + m_state.needs_scheduling = true; + return (1ULL << m_core_id); } else { return 0; } } u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // Clear that we need to update. ClearSchedulerUpdateNeeded(kernel); @@ -98,18 +214,20 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { KThread* top_threads[Core::Hardware::NUM_CPU_CORES]; auto& priority_queue = GetPriorityQueue(kernel); - /// We want to go over all cores, finding the highest priority thread and determining if - /// scheduling is needed for that core. + // We want to go over all cores, finding the highest priority thread and determining if + // scheduling is needed for that core. for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id)); if (top_thread != nullptr) { - // If the thread has no waiters, we need to check if the process has a thread pinned. - if (top_thread->GetNumKernelWaiters() == 0) { - if (KProcess* parent = top_thread->GetOwnerProcess(); parent != nullptr) { - if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id)); - pinned != nullptr && pinned != top_thread) { - // We prefer our parent's pinned thread if possible. However, we also don't - // want to schedule un-runnable threads. + // We need to check if the thread's process has a pinned thread. + if (KProcess* parent = top_thread->GetOwnerProcess()) { + // Check that there's a pinned thread other than the current top thread. + if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id)); + pinned != nullptr && pinned != top_thread) { + // We need to prefer threads with kernel waiters to the pinned thread. + if (top_thread->GetNumKernelWaiters() == + 0 /* && top_thread != parent->GetExceptionThread() */) { + // If the pinned thread is runnable, use it. if (pinned->GetRawState() == ThreadState::Runnable) { top_thread = pinned; } else { @@ -129,7 +247,8 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // Idle cores are bad. We're going to try to migrate threads to each idle core in turn. while (idle_cores != 0) { - const auto core_id = static_cast<u32>(std::countr_zero(idle_cores)); + const s32 core_id = static_cast<s32>(std::countr_zero(idle_cores)); + if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) { s32 migration_candidates[Core::Hardware::NUM_CPU_CORES]; size_t num_candidates = 0; @@ -150,7 +269,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // The suggested thread isn't bound to its core, so we can migrate it! suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested); - top_threads[core_id] = suggested; cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); @@ -183,7 +301,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // Perform the migration. suggested->SetActiveCore(core_id); priority_queue.ChangeCore(candidate_core, suggested); - top_threads[core_id] = suggested; cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread( @@ -200,24 +317,210 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { return cores_needing_scheduling; } +void KScheduler::SwitchThread(KThread* next_thread) { + KProcess* const cur_process = kernel.CurrentProcess(); + KThread* const cur_thread = GetCurrentThreadPointer(kernel); + + // We never want to schedule a null thread, so use the idle thread if we don't have a next. + if (next_thread == nullptr) { + next_thread = m_idle_thread; + } + + if (next_thread->GetCurrentCore() != m_core_id) { + next_thread->SetCurrentCore(m_core_id); + } + + // If we're not actually switching thread, there's nothing to do. + if (next_thread == cur_thread) { + return; + } + + // Next thread is now known not to be nullptr, and must not be dispatchable. + ASSERT(next_thread->GetDisableDispatchCount() == 1); + ASSERT(!next_thread->IsDummyThread()); + + // Update the CPU time tracking variables. + const s64 prev_tick = m_last_context_switch_time; + const s64 cur_tick = kernel.System().CoreTiming().GetCPUTicks(); + const s64 tick_diff = cur_tick - prev_tick; + cur_thread->AddCpuTime(m_core_id, tick_diff); + if (cur_process != nullptr) { + cur_process->UpdateCPUTimeTicks(tick_diff); + } + m_last_context_switch_time = cur_tick; + + // Update our previous thread. + if (cur_process != nullptr) { + if (!cur_thread->IsTerminationRequested() && cur_thread->GetActiveCore() == m_core_id) + [[likely]] { + m_state.prev_thread = cur_thread; + } else { + m_state.prev_thread = nullptr; + } + } + + // Switch the current process, if we're switching processes. + // if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) { + // KProcess::Switch(cur_process, next_process); + // } + + // Set the new thread. + SetCurrentThread(kernel, next_thread); + m_current_thread = next_thread; + + // Set the new Thread Local region. + // cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress())); +} + +void KScheduler::ScheduleImpl() { + // First, clear the needs scheduling bool. + m_state.needs_scheduling.store(false, std::memory_order_seq_cst); + + // Load the appropriate thread pointers for scheduling. + KThread* const cur_thread{GetCurrentThreadPointer(kernel)}; + KThread* highest_priority_thread{m_state.highest_priority_thread}; + + // Check whether there are runnable interrupt tasks. + if (m_state.interrupt_task_runnable) { + // The interrupt task is runnable. + // We want to switch to the interrupt task/idle thread. + highest_priority_thread = nullptr; + } + + // If there aren't, we want to check if the highest priority thread is the same as the current + // thread. + if (highest_priority_thread == cur_thread) { + // If they're the same, then we can just return. + return; + } + + // The highest priority thread is not the same as the current thread. + // Jump to the switcher and continue executing from there. + m_switch_cur_thread = cur_thread; + m_switch_highest_priority_thread = highest_priority_thread; + m_switch_from_schedule = true; + Common::Fiber::YieldTo(cur_thread->host_context, *m_switch_fiber); + + // Returning from ScheduleImpl occurs after this thread has been scheduled again. +} + +void KScheduler::ScheduleImplFiber() { + KThread* const cur_thread{m_switch_cur_thread}; + KThread* highest_priority_thread{m_switch_highest_priority_thread}; + + // If we're not coming from scheduling (i.e., we came from SC preemption), + // we should restart the scheduling loop directly. Not accurate to HOS. + if (!m_switch_from_schedule) { + goto retry; + } + + // Mark that we are not coming from scheduling anymore. + m_switch_from_schedule = false; + + // Save the original thread context. + Unload(cur_thread); + + // The current thread's context has been entirely taken care of. + // Now we want to loop until we successfully switch the thread context. + while (true) { + // We're starting to try to do the context switch. + // Check if the highest priority thread is null. + if (!highest_priority_thread) { + // The next thread is nullptr! + + // Switch to the idle thread. Note: HOS treats idling as a special case for + // performance. This is not *required* for yuzu's purposes, and for singlecore + // compatibility, we can just move the logic that would go here into the execution + // of the idle thread. If we ever remove singlecore, we should implement this + // accurately to HOS. + highest_priority_thread = m_idle_thread; + } + + // We want to try to lock the highest priority thread's context. + // Try to take it. + while (!highest_priority_thread->context_guard.try_lock()) { + // The highest priority thread's context is already locked. + // Check if we need scheduling. If we don't, we can retry directly. + if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) { + // If we do, another core is interfering, and we must start again. + goto retry; + } + } + + // It's time to switch the thread. + // Switch to the highest priority thread. + SwitchThread(highest_priority_thread); + + // Check if we need scheduling. If we do, then we can't complete the switch and should + // retry. + if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) { + // Our switch failed. + // We should unlock the thread context, and then retry. + highest_priority_thread->context_guard.unlock(); + goto retry; + } else { + break; + } + + retry: + + // We failed to successfully do the context switch, and need to retry. + // Clear needs_scheduling. + m_state.needs_scheduling.store(false, std::memory_order_seq_cst); + + // Refresh the highest priority thread. + highest_priority_thread = m_state.highest_priority_thread; + } + + // Reload the guest thread context. + Reload(highest_priority_thread); + + // Reload the host thread. + Common::Fiber::YieldTo(m_switch_fiber, *highest_priority_thread->host_context); +} + +void KScheduler::Unload(KThread* thread) { + auto& cpu_core = kernel.System().ArmInterface(m_core_id); + cpu_core.SaveContext(thread->GetContext32()); + cpu_core.SaveContext(thread->GetContext64()); + // Save the TPIDR_EL0 system register in case it was modified. + thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + + // Check if the thread is terminated by checking the DPC flags. + if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) { + // The thread isn't terminated, so we want to unlock it. + thread->context_guard.unlock(); + } +} + +void KScheduler::Reload(KThread* thread) { + auto& cpu_core = kernel.System().ArmInterface(m_core_id); + cpu_core.LoadContext(thread->GetContext32()); + cpu_core.LoadContext(thread->GetContext64()); + cpu_core.SetTlsAddress(thread->GetTLSAddress()); + cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); + cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints()); + cpu_core.ClearExclusiveState(); +} + void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) { // Get an atomic reference to the core scheduler's previous thread. - std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread); - static_assert(std::atomic_ref<KThread*>::is_always_lock_free); + auto& prev_thread{kernel.Scheduler(i).m_state.prev_thread}; // Atomically clear the previous thread if it's our target. KThread* compare = thread; - prev_thread.compare_exchange_strong(compare, nullptr); + prev_thread.compare_exchange_strong(compare, nullptr, std::memory_order_seq_cst); } } void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // Check if the state has changed, because if it hasn't there's nothing to do. - const auto cur_state = thread->GetRawState(); + const ThreadState cur_state = thread->GetRawState(); if (cur_state == old_state) { return; } @@ -237,12 +540,12 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa } void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // If the thread is runnable, we want to change its priority in the queue. if (thread->GetRawState() == ThreadState::Runnable) { GetPriorityQueue(kernel).ChangePriority(old_priority, - thread == kernel.GetCurrentEmuThread(), thread); + thread == GetCurrentThreadPointer(kernel), thread); IncrementScheduledCount(thread); SetSchedulerUpdateNeeded(kernel); } @@ -250,7 +553,7 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3 void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, const KAffinityMask& old_affinity, s32 old_core) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // If the thread is runnable, we want to change its affinity in the queue. if (thread->GetRawState() == ThreadState::Runnable) { @@ -260,15 +563,14 @@ void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread } } -void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { - ASSERT(system.GlobalSchedulerContext().IsLocked()); +void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority) { + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // Get a reference to the priority queue. - auto& kernel = system.Kernel(); auto& priority_queue = GetPriorityQueue(kernel); // Rotate the front of the queue to the end. - KThread* top_thread = priority_queue.GetScheduledFront(cpu_core_id, priority); + KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority); KThread* next_thread = nullptr; if (top_thread != nullptr) { next_thread = priority_queue.MoveToScheduledBack(top_thread); @@ -280,7 +582,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { // While we have a suggested thread, try to migrate it! { - KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id, priority); + KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority); while (suggested != nullptr) { // Check if the suggested thread is the top thread on its core. const s32 suggested_core = suggested->GetActiveCore(); @@ -301,7 +603,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { // to the front of the queue. if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { - suggested->SetActiveCore(cpu_core_id); + suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested, true); IncrementScheduledCount(suggested); break; @@ -309,22 +611,21 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { } // Get the next suggestion. - suggested = priority_queue.GetSamePriorityNext(cpu_core_id, suggested); + suggested = priority_queue.GetSamePriorityNext(core_id, suggested); } } // Now that we might have migrated a thread with the same priority, check if we can do better. - { - KThread* best_thread = priority_queue.GetScheduledFront(cpu_core_id); + KThread* best_thread = priority_queue.GetScheduledFront(core_id); if (best_thread == GetCurrentThreadPointer(kernel)) { - best_thread = priority_queue.GetScheduledNext(cpu_core_id, best_thread); + best_thread = priority_queue.GetScheduledNext(core_id, best_thread); } // If the best thread we can choose has a priority the same or worse than ours, try to // migrate a higher priority thread. if (best_thread != nullptr && best_thread->GetPriority() >= priority) { - KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id); + KThread* suggested = priority_queue.GetSuggestedFront(core_id); while (suggested != nullptr) { // If the suggestion's priority is the same as ours, don't bother. if (suggested->GetPriority() >= best_thread->GetPriority()) { @@ -343,7 +644,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { - suggested->SetActiveCore(cpu_core_id); + suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested, true); IncrementScheduledCount(suggested); break; @@ -351,7 +652,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { } // Get the next suggestion. - suggested = priority_queue.GetSuggestedNext(cpu_core_id, suggested); + suggested = priority_queue.GetSuggestedNext(core_id, suggested); } } } @@ -360,64 +661,6 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { SetSchedulerUpdateNeeded(kernel); } -bool KScheduler::CanSchedule(KernelCore& kernel) { - return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1; -} - -bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) { - return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire); -} - -void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) { - kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release); -} - -void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) { - kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release); -} - -void KScheduler::DisableScheduling(KernelCore& kernel) { - // If we are shutting down the kernel, none of this is relevant anymore. - if (kernel.IsShuttingDown()) { - return; - } - - ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0); - GetCurrentThreadPointer(kernel)->DisableDispatch(); -} - -void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { - // If we are shutting down the kernel, none of this is relevant anymore. - if (kernel.IsShuttingDown()) { - return; - } - - auto* current_thread = GetCurrentThreadPointer(kernel); - - ASSERT(current_thread->GetDisableDispatchCount() >= 1); - - if (current_thread->GetDisableDispatchCount() > 1) { - current_thread->EnableDispatch(); - } else { - RescheduleCores(kernel, cores_needing_scheduling); - } - - // Special case to ensure dummy threads that are waiting block. - current_thread->IfDummyThreadTryWait(); -} - -u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { - if (IsSchedulerUpdateNeeded(kernel)) { - return UpdateHighestPriorityThreadsImpl(kernel); - } else { - return 0; - } -} - -KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) { - return kernel.GlobalSchedulerContext().priority_queue; -} - void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) { // Validate preconditions. ASSERT(CanSchedule(kernel)); @@ -437,7 +680,7 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) { // Perform the yield. { - KScopedSchedulerLock lock(kernel); + KScopedSchedulerLock sl{kernel}; const auto cur_state = cur_thread.GetRawState(); if (cur_state == ThreadState::Runnable) { @@ -476,7 +719,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) { // Perform the yield. { - KScopedSchedulerLock lock(kernel); + KScopedSchedulerLock sl{kernel}; const auto cur_state = cur_thread.GetRawState(); if (cur_state == ThreadState::Runnable) { @@ -496,7 +739,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) { if (KThread* running_on_suggested_core = (suggested_core >= 0) - ? kernel.Scheduler(suggested_core).state.highest_priority_thread + ? kernel.Scheduler(suggested_core).m_state.highest_priority_thread : nullptr; running_on_suggested_core != suggested) { // If the current thread's priority is higher than our suggestion's we prefer @@ -564,7 +807,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) { // Perform the yield. { - KScopedSchedulerLock lock(kernel); + KScopedSchedulerLock sl{kernel}; const auto cur_state = cur_thread.GetRawState(); if (cur_state == ThreadState::Runnable) { @@ -621,223 +864,19 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) { } } -KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, core_id{core_id_} { - switch_fiber = std::make_shared<Common::Fiber>([this] { SwitchToCurrent(); }); - state.needs_scheduling.store(true); - state.interrupt_task_thread_runnable = false; - state.should_count_idle = false; - state.idle_count = 0; - state.idle_thread_stack = nullptr; - state.highest_priority_thread = nullptr; -} - -void KScheduler::Finalize() { - if (idle_thread) { - idle_thread->Close(); - idle_thread = nullptr; - } -} - -KScheduler::~KScheduler() { - ASSERT(!idle_thread); -} - -KThread* KScheduler::GetSchedulerCurrentThread() const { - if (auto result = current_thread.load(); result) { - return result; +void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) { + if (const u64 core_mask = cores_needing_scheduling & ~(1ULL << m_core_id); core_mask != 0) { + RescheduleCores(kernel, core_mask); } - return idle_thread; -} - -u64 KScheduler::GetLastContextSwitchTicks() const { - return last_context_switch_time; } -void KScheduler::RescheduleCurrentCore() { - ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1); - - auto& phys_core = system.Kernel().PhysicalCore(core_id); - if (phys_core.IsInterrupted()) { - phys_core.ClearInterrupt(); - } - - guard.Lock(); - if (state.needs_scheduling.load()) { - Schedule(); - } else { - GetCurrentThread(system.Kernel()).EnableDispatch(); - guard.Unlock(); - } -} - -void KScheduler::OnThreadStart() { - SwitchContextStep2(); -} - -void KScheduler::Unload(KThread* thread) { - ASSERT(thread); - - LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr"); - - if (thread->IsCallingSvc()) { - thread->ClearIsCallingSvc(); - } - - auto& physical_core = system.Kernel().PhysicalCore(core_id); - if (!physical_core.IsInitialized()) { - return; - } - - Core::ARM_Interface& cpu_core = physical_core.ArmInterface(); - cpu_core.SaveContext(thread->GetContext32()); - cpu_core.SaveContext(thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - - if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) { - prev_thread = thread; - } else { - prev_thread = nullptr; - } - - thread->context_guard.unlock(); -} - -void KScheduler::Reload(KThread* thread) { - LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread->GetName()); - - Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); - cpu_core.LoadContext(thread->GetContext32()); - cpu_core.LoadContext(thread->GetContext64()); - cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints()); - cpu_core.SetTlsAddress(thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); -} - -void KScheduler::SwitchContextStep2() { - // Load context of new thread - Reload(GetCurrentThreadPointer(system.Kernel())); - - RescheduleCurrentCore(); -} - -void KScheduler::Schedule() { - ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1); - this->ScheduleImpl(); -} - -void KScheduler::ScheduleImpl() { - KThread* previous_thread = GetCurrentThreadPointer(system.Kernel()); - KThread* next_thread = state.highest_priority_thread; - - state.needs_scheduling.store(false); - - // We never want to schedule a null thread, so use the idle thread if we don't have a next. - if (next_thread == nullptr) { - next_thread = idle_thread; - } - - if (next_thread->GetCurrentCore() != core_id) { - next_thread->SetCurrentCore(core_id); - } - - // We never want to schedule a dummy thread, as these are only used by host threads for locking. - if (next_thread->GetThreadType() == ThreadType::Dummy) { - ASSERT_MSG(false, "Dummy threads should never be scheduled!"); - next_thread = idle_thread; - } - - // If we're not actually switching thread, there's nothing to do. - if (next_thread == current_thread.load()) { - previous_thread->EnableDispatch(); - guard.Unlock(); - return; - } - - // Update the CPU time tracking variables. - KProcess* const previous_process = system.Kernel().CurrentProcess(); - UpdateLastContextSwitchTime(previous_thread, previous_process); - - // Save context for previous thread - Unload(previous_thread); - - std::shared_ptr<Common::Fiber>* old_context; - old_context = &previous_thread->GetHostContext(); - - // Set the new thread. - SetCurrentThread(system.Kernel(), next_thread); - current_thread.store(next_thread); - - guard.Unlock(); - - Common::Fiber::YieldTo(*old_context, *switch_fiber); - /// When a thread wakes up, the scheduler may have changed to other in another core. - auto& next_scheduler = *system.Kernel().CurrentScheduler(); - next_scheduler.SwitchContextStep2(); -} - -void KScheduler::SwitchToCurrent() { - while (true) { - { - KScopedSpinLock lk{guard}; - current_thread.store(state.highest_priority_thread); - state.needs_scheduling.store(false); +void KScheduler::RescheduleCores(KernelCore& kernel, u64 core_mask) { + // Send IPI + for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + if (core_mask & (1ULL << i)) { + kernel.PhysicalCore(i).Interrupt(); } - const auto is_switch_pending = [this] { - KScopedSpinLock lk{guard}; - return state.needs_scheduling.load(); - }; - do { - auto next_thread = current_thread.load(); - if (next_thread != nullptr) { - const auto locked = next_thread->context_guard.try_lock(); - if (state.needs_scheduling.load()) { - next_thread->context_guard.unlock(); - break; - } - if (next_thread->GetActiveCore() != core_id) { - next_thread->context_guard.unlock(); - break; - } - if (!locked) { - continue; - } - } - auto thread = next_thread ? next_thread : idle_thread; - SetCurrentThread(system.Kernel(), thread); - Common::Fiber::YieldTo(switch_fiber, *thread->GetHostContext()); - } while (!is_switch_pending()); } } -void KScheduler::UpdateLastContextSwitchTime(KThread* thread, KProcess* process) { - const u64 prev_switch_ticks = last_context_switch_time; - const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); - const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; - - if (thread != nullptr) { - thread->AddCpuTime(core_id, update_ticks); - } - - if (process != nullptr) { - process->UpdateCPUTimeTicks(update_ticks); - } - - last_context_switch_time = most_recent_switch_ticks; -} - -void KScheduler::Initialize() { - idle_thread = KThread::Create(system.Kernel()); - ASSERT(KThread::InitializeIdleThread(system, idle_thread, core_id).IsSuccess()); - idle_thread->SetName(fmt::format("IdleThread:{}", core_id)); - idle_thread->EnableDispatch(); -} - -KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel) - : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {} - -KScopedSchedulerLock::~KScopedSchedulerLock() = default; - } // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h index 6a4760eca..534321d8d 100644 --- a/src/core/hle/kernel/k_scheduler.h +++ b/src/core/hle/kernel/k_scheduler.h @@ -11,6 +11,7 @@ #include "core/hle/kernel/k_scheduler_lock.h" #include "core/hle/kernel/k_scoped_lock.h" #include "core/hle/kernel/k_spin_lock.h" +#include "core/hle/kernel/k_thread.h" namespace Common { class Fiber; @@ -23,184 +24,150 @@ class System; namespace Kernel { class KernelCore; +class KInterruptTaskManager; class KProcess; -class SchedulerLock; class KThread; +class KScopedDisableDispatch; +class KScopedSchedulerLock; +class KScopedSchedulerLockAndSleep; class KScheduler final { public: - explicit KScheduler(Core::System& system_, s32 core_id_); - ~KScheduler(); - - void Finalize(); + YUZU_NON_COPYABLE(KScheduler); + YUZU_NON_MOVEABLE(KScheduler); - /// Reschedules to the next available thread (call after current thread is suspended) - void RescheduleCurrentCore(); + using LockType = KAbstractSchedulerLock<KScheduler>; - /// Reschedules cores pending reschedule, to be called on EnableScheduling. - static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule); + explicit KScheduler(KernelCore& kernel); + ~KScheduler(); - /// The next two are for SingleCore Only. - /// Unload current thread before preempting core. + void Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id); + void Activate(); + void OnThreadStart(); void Unload(KThread* thread); - - /// Reload current thread after core preemption. void Reload(KThread* thread); - /// Gets the current running thread - [[nodiscard]] KThread* GetSchedulerCurrentThread() const; + void SetInterruptTaskRunnable(); + void RequestScheduleOnInterrupt(); + void PreemptSingleCore(); - /// Gets the idle thread - [[nodiscard]] KThread* GetIdleThread() const { - return idle_thread; + u64 GetIdleCount() { + return m_state.idle_count; } - /// Returns true if the scheduler is idle - [[nodiscard]] bool IsIdle() const { - return GetSchedulerCurrentThread() == idle_thread; + KThread* GetIdleThread() const { + return m_idle_thread; } - /// Gets the timestamp for the last context switch in ticks. - [[nodiscard]] u64 GetLastContextSwitchTicks() const; - - [[nodiscard]] bool ContextSwitchPending() const { - return state.needs_scheduling.load(std::memory_order_relaxed); + bool IsIdle() const { + return m_current_thread.load() == m_idle_thread; } - void Initialize(); + KThread* GetPreviousThread() const { + return m_state.prev_thread; + } - void OnThreadStart(); + KThread* GetSchedulerCurrentThread() const { + return m_current_thread.load(); + } - [[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() { - return switch_fiber; + s64 GetLastContextSwitchTime() const { + return m_last_context_switch_time; } - [[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const { - return switch_fiber; + // Static public API. + static bool CanSchedule(KernelCore& kernel) { + return GetCurrentThread(kernel).GetDisableDispatchCount() == 0; + } + static bool IsSchedulerLockedByCurrentThread(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().scheduler_lock.IsLockedByCurrentThread(); } - [[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread); + static bool IsSchedulerUpdateNeeded(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().scheduler_update_needed; + } + static void SetSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed = true; + } + static void ClearSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed = false; + } - /** - * Takes a thread and moves it to the back of the it's priority list. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - static void YieldWithoutCoreMigration(KernelCore& kernel); + static void DisableScheduling(KernelCore& kernel); + static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling); - /** - * Takes a thread and moves it to the back of the it's priority list. - * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or - * a better priority than the next thread in the core. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - static void YieldWithCoreMigration(KernelCore& kernel); - - /** - * Takes a thread and moves it out of the scheduling queue. - * and into the suggested queue. If no thread can be scheduled afterwards in that core, - * a suggested thread is obtained instead. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - static void YieldToAnyThread(KernelCore& kernel); + static u64 UpdateHighestPriorityThreads(KernelCore& kernel); static void ClearPreviousThread(KernelCore& kernel, KThread* thread); - /// Notify the scheduler a thread's status has changed. static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state); - - /// Notify the scheduler a thread's priority has changed. static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority); - - /// Notify the scheduler a thread's core and/or affinity mask has changed. static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, const KAffinityMask& old_affinity, s32 old_core); - static bool CanSchedule(KernelCore& kernel); - static bool IsSchedulerUpdateNeeded(const KernelCore& kernel); - static void SetSchedulerUpdateNeeded(KernelCore& kernel); - static void ClearSchedulerUpdateNeeded(KernelCore& kernel); - static void DisableScheduling(KernelCore& kernel); - static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling); - [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel); + static void RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority); + static void RescheduleCores(KernelCore& kernel, u64 cores_needing_scheduling); + + static void YieldWithoutCoreMigration(KernelCore& kernel); + static void YieldWithCoreMigration(KernelCore& kernel); + static void YieldToAnyThread(KernelCore& kernel); private: - friend class GlobalSchedulerContext; - - /** - * Takes care of selecting the new scheduled threads in three steps: - * - * 1. First a thread is selected from the top of the priority queue. If no thread - * is obtained then we move to step two, else we are done. - * - * 2. Second we try to get a suggested thread that's not assigned to any core or - * that is not the top thread in that core. - * - * 3. Third is no suggested thread is found, we do a second pass and pick a running - * thread in another core and swap it with its current thread. - * - * returns the cores needing scheduling. - */ - [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel); - - [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel); - - void RotateScheduledQueue(s32 cpu_core_id, s32 priority); + // Static private API. + static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().priority_queue; + } + static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel); - void Schedule(); + static void RescheduleCurrentHLEThread(KernelCore& kernel); - /// Switches the CPU's active thread context to that of the specified thread + // Instanced private API. void ScheduleImpl(); + void ScheduleImplFiber(); + void SwitchThread(KThread* next_thread); - /// When a thread wakes up, it must run this through it's new scheduler - void SwitchContextStep2(); - - /** - * Called on every context switch to update the internal timestamp - * This also updates the running time ticks for the given thread and - * process using the following difference: - * - * ticks += most_recent_ticks - last_context_switch_ticks - * - * The internal tick timestamp for the scheduler is simply the - * most recent tick count retrieved. No special arithmetic is - * applied to it. - */ - void UpdateLastContextSwitchTime(KThread* thread, KProcess* process); - - void SwitchToCurrent(); + void Schedule(); + void ScheduleOnInterrupt(); - KThread* prev_thread{}; - std::atomic<KThread*> current_thread{}; + void RescheduleOtherCores(u64 cores_needing_scheduling); + void RescheduleCurrentCore(); + void RescheduleCurrentCoreImpl(); - KThread* idle_thread{}; + u64 UpdateHighestPriorityThread(KThread* thread); - std::shared_ptr<Common::Fiber> switch_fiber{}; +private: + friend class KScopedDisableDispatch; struct SchedulingState { - std::atomic<bool> needs_scheduling{}; - bool interrupt_task_thread_runnable{}; - bool should_count_idle{}; - u64 idle_count{}; - KThread* highest_priority_thread{}; - void* idle_thread_stack{}; + std::atomic<bool> needs_scheduling{false}; + bool interrupt_task_runnable{false}; + bool should_count_idle{false}; + u64 idle_count{0}; + KThread* highest_priority_thread{nullptr}; + void* idle_thread_stack{nullptr}; + std::atomic<KThread*> prev_thread{nullptr}; + KInterruptTaskManager* interrupt_task_manager{nullptr}; }; - SchedulingState state; - - Core::System& system; - u64 last_context_switch_time{}; - const s32 core_id; - - KSpinLock guard{}; + KernelCore& kernel; + SchedulingState m_state; + bool m_is_active{false}; + s32 m_core_id{0}; + s64 m_last_context_switch_time{0}; + KThread* m_idle_thread{nullptr}; + std::atomic<KThread*> m_current_thread{nullptr}; + + std::shared_ptr<Common::Fiber> m_switch_fiber{}; + KThread* m_switch_cur_thread{}; + KThread* m_switch_highest_priority_thread{}; + bool m_switch_from_schedule{}; }; -class [[nodiscard]] KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> { +class KScopedSchedulerLock : public KScopedLock<KScheduler::LockType> { public: - explicit KScopedSchedulerLock(KernelCore& kernel); - ~KScopedSchedulerLock(); + explicit KScopedSchedulerLock(KernelCore& kernel) + : KScopedLock(kernel.GlobalSchedulerContext().scheduler_lock) {} + ~KScopedSchedulerLock() = default; }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h index 4fa256970..73314b45e 100644 --- a/src/core/hle/kernel/k_scheduler_lock.h +++ b/src/core/hle/kernel/k_scheduler_lock.h @@ -5,9 +5,11 @@ #include <atomic> #include "common/assert.h" +#include "core/hle/kernel/k_interrupt_manager.h" #include "core/hle/kernel/k_spin_lock.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" namespace Kernel { diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index b77735736..8ff1545b6 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" #include "core/core.h" diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h index 2c1db0e70..34cb98456 100644 --- a/src/core/hle/kernel/k_shared_memory.h +++ b/src/core/hle/kernel/k_shared_memory.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 90de86770..174afc80d 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -258,7 +258,18 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_ } Result KThread::InitializeDummyThread(KThread* thread) { - return thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy); + // Initialize the thread. + R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy)); + + // Initialize emulation parameters. + thread->stack_parameters.disable_count = 0; + + return ResultSuccess; +} + +Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) { + return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main, + system.GetCpuManager().GetGuestActivateFunc()); } Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { @@ -277,7 +288,7 @@ Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThr KProcess* owner) { system.Kernel().GlobalSchedulerContext().AddThread(thread); return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, - ThreadType::User, system.GetCpuManager().GetGuestThreadStartFunc()); + ThreadType::User, system.GetCpuManager().GetGuestThreadFunc()); } void KThread::PostDestroy(uintptr_t arg) { @@ -1058,6 +1069,8 @@ void KThread::Exit() { // Register the thread as a work task. KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this); } + + UNREACHABLE_MSG("KThread::Exit() would return"); } Result KThread::Sleep(s64 timeout) { @@ -1093,6 +1106,8 @@ void KThread::IfDummyThreadTryWait() { return; } + ASSERT(!kernel.IsPhantomModeForSingleCore()); + // Block until we are no longer waiting. std::unique_lock lk(dummy_wait_lock); dummy_wait_cv.wait( @@ -1197,16 +1212,13 @@ KScopedDisableDispatch::~KScopedDisableDispatch() { return; } - // Skip the reschedule if single-core, as dispatch tracking is disabled here. - if (!Settings::values.use_multi_core.GetValue()) { - return; - } - if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) { - auto scheduler = kernel.CurrentScheduler(); + auto* scheduler = kernel.CurrentScheduler(); - if (scheduler) { + if (scheduler && !kernel.IsPhantomModeForSingleCore()) { scheduler->RescheduleCurrentCore(); + } else { + KScheduler::RescheduleCurrentHLEThread(kernel); } } else { GetCurrentThread(kernel).EnableDispatch(); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 28cd7ecb0..9ee20208e 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -413,6 +413,9 @@ public: [[nodiscard]] static Result InitializeDummyThread(KThread* thread); + [[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread, + s32 virt_core); + [[nodiscard]] static Result InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core); @@ -480,39 +483,16 @@ public: return per_core_priority_queue_entry[core]; } - [[nodiscard]] bool IsKernelThread() const { - return GetActiveCore() == 3; - } - - [[nodiscard]] bool IsDispatchTrackingDisabled() const { - return is_single_core || IsKernelThread(); - } - [[nodiscard]] s32 GetDisableDispatchCount() const { - if (IsDispatchTrackingDisabled()) { - // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. - return 1; - } - return this->GetStackParameters().disable_count; } void DisableDispatch() { - if (IsDispatchTrackingDisabled()) { - // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. - return; - } - ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); this->GetStackParameters().disable_count++; } void EnableDispatch() { - if (IsDispatchTrackingDisabled()) { - // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. - return; - } - ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0); this->GetStackParameters().disable_count--; } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index f23c629dc..f4072e1c3 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -64,8 +64,6 @@ struct KernelCore::Impl { is_phantom_mode_for_singlecore = false; - InitializePhysicalCores(); - // Derive the initial memory layout from the emulated board Init::InitializeSlabResourceCounts(kernel); DeriveInitialMemoryLayout(); @@ -75,9 +73,9 @@ struct KernelCore::Impl { InitializeSystemResourceLimit(kernel, system.CoreTiming()); InitializeMemoryLayout(); Init::InitializeKPageBufferSlabHeap(system); - InitializeSchedulers(); InitializeShutdownThreads(); InitializePreemption(kernel); + InitializePhysicalCores(); RegisterHostThread(); } @@ -136,7 +134,6 @@ struct KernelCore::Impl { shutdown_threads[core_id] = nullptr; } - schedulers[core_id]->Finalize(); schedulers[core_id].reset(); } @@ -199,14 +196,21 @@ struct KernelCore::Impl { exclusive_monitor = Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i); + const s32 core{static_cast<s32>(i)}; + + schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel()); cores.emplace_back(i, system, *schedulers[i], interrupts); - } - } - void InitializeSchedulers() { - for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - cores[i].Scheduler().Initialize(); + auto* main_thread{Kernel::KThread::Create(system.Kernel())}; + main_thread->SetName(fmt::format("MainThread:{}", core)); + main_thread->SetCurrentCore(core); + ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess()); + + auto* idle_thread{Kernel::KThread::Create(system.Kernel())}; + idle_thread->SetCurrentCore(core); + ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess()); + + schedulers[i]->Initialize(main_thread, idle_thread, core); } } @@ -1109,10 +1113,11 @@ void KernelCore::Suspend(bool suspended) { } void KernelCore::ShutdownCores() { + KScopedSchedulerLock lk{*this}; + for (auto* thread : impl->shutdown_threads) { void(thread->Run()); } - InterruptAllPhysicalCores(); } bool KernelCore::IsMulticore() const { diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index a5b16ae2e..6e7dacf97 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -43,6 +43,7 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) { void PhysicalCore::Run() { arm_interface->Run(); + arm_interface->ClearExclusiveState(); } void PhysicalCore::Idle() { diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 8655506b0..27e5a805d 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -887,7 +887,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); const bool same_thread = current_thread == thread.GetPointerUnsafe(); - const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks(); + const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime(); u64 out_ticks = 0; if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { const u64 thread_ticks = current_thread->GetCpuTime(); @@ -3026,11 +3026,6 @@ void Call(Core::System& system, u32 immediate) { } kernel.ExitSVCProfile(); - - if (!thread->IsCallingSvc()) { - auto* host_context = thread->GetHostContext().get(); - host_context->Rewind(); - } } } // namespace Kernel::Svc diff --git a/src/core/hle/result.h b/src/core/hle/result.h index aa9e5b89d..4de44cd06 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h index 46282f42e..4613a4e60 100644 --- a/src/core/hle/service/hid/errors.h +++ b/src/core/hle/service/hid/errors.h @@ -19,3 +19,10 @@ constexpr Result InvalidNpadId{ErrorModule::HID, 709}; constexpr Result NpadNotConnected{ErrorModule::HID, 710}; } // namespace Service::HID + +namespace Service::IRS { + +constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78}; +constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 89bb12442..5ecbddf94 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2345,8 +2345,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system std::make_shared<HidSys>(system)->InstallAsService(service_manager); std::make_shared<HidTmp>(system)->InstallAsService(service_manager); - std::make_shared<IRS>(system)->InstallAsService(service_manager); - std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager); + std::make_shared<Service::IRS::IRS>(system)->InstallAsService(service_manager); + std::make_shared<Service::IRS::IRS_SYS>(system)->InstallAsService(service_manager); std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index d2a91d913..d5107e41f 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -1,16 +1,28 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <algorithm> +#include <random> + #include "core/core.h" #include "core/core_timing.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_shared_memory.h" #include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/irs.h" +#include "core/hle/service/hid/irsensor/clustering_processor.h" +#include "core/hle/service/hid/irsensor/image_transfer_processor.h" +#include "core/hle/service/hid/irsensor/ir_led_processor.h" +#include "core/hle/service/hid/irsensor/moment_processor.h" +#include "core/hle/service/hid/irsensor/pointing_processor.h" +#include "core/hle/service/hid/irsensor/tera_plugin_processor.h" +#include "core/memory.h" -namespace Service::HID { +namespace Service::IRS { IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { // clang-format off @@ -36,14 +48,19 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { }; // clang-format on + u8* raw_shared_memory = system.Kernel().GetIrsSharedMem().GetPointer(); RegisterHandlers(functions); + shared_memory = std::construct_at(reinterpret_cast<StatusManager*>(raw_shared_memory)); + + npad_device = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); } +IRS::~IRS() = default; void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -54,7 +71,7 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -75,7 +92,7 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -88,17 +105,23 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Stop Image processor + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedMomentProcessorConfig processor_config; + Core::IrSensor::PackedMomentProcessorConfig processor_config; }; static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); @@ -109,19 +132,28 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor<MomentProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedClusteringProcessorConfig processor_config; + Core::IrSensor::PackedClusteringProcessorConfig processor_config; }; - static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size."); + static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); const auto parameters{rp.PopRaw<Parameters>()}; @@ -130,17 +162,27 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor<ClusteringProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<ClusteringProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedImageTransferProcessorConfig processor_config; + Core::IrSensor::PackedImageTransferProcessorConfig processor_config; u32 transfer_memory_size; }; static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); @@ -151,20 +193,42 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { auto t_mem = system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " - "applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.transfer_memory_size, parameters.applet_resource_user_id); + if (t_mem.IsNull()) { + LOG_ERROR(Service_IRS, "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() == parameters.transfer_memory_size, "t_mem has incorrect size"); + + u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress()); + + LOG_INFO(Service_IRS, + "called, npad_type={}, npad_id={}, transfer_memory_size={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, t_mem->GetSize(), parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<ImageTransferProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -172,32 +236,68 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.applet_resource_user_id); + LOG_DEBUG(Service_IRS, "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + const auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + + if (device.mode != Core::IrSensor::IrSensorMode::ImageTransferProcessor) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(InvalidProcessorState); + return; + } - IPC::ResponseBuilder rb{ctx, 5}; + std::vector<u8> data{}; + const auto& image_transfer_processor = + GetProcessor<ImageTransferProcessor>(parameters.camera_handle); + const auto& state = image_transfer_processor.GetState(data); + + ctx.WriteBuffer(data); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks()); - rb.PushRaw<u32>(0); + rb.PushRaw(state); } void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto processor_config{rp.PopRaw<PackedTeraPluginProcessorConfig>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Core::IrSensor::IrCameraHandle camera_handle; + Core::IrSensor::PackedTeraPluginProcessorConfig processor_config; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " - "applet_resource_user_id={}", - camera_handle.npad_type, camera_handle.npad_id, processor_config.mode, - processor_config.required_mcu_version.major, - processor_config.required_mcu_version.minor, applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.processor_config.mode, parameters.processor_config.required_mcu_version.major, + parameters.processor_config.required_mcu_version.minor, parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor<TeraPluginProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<TeraPluginProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { @@ -207,17 +307,17 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid && npad_id != Core::HID::NpadIdType::Handheld) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidNpadId); + rb.Push(Service::HID::InvalidNpadId); return; } - IrCameraHandle camera_handle{ + Core::IrSensor::IrCameraHandle camera_handle{ .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)), .npad_type = Core::HID::NpadStyleIndex::None, }; - LOG_WARNING(Service_IRS, "(STUBBED) called, npad_id={}, camera_npad_id={}, camera_npad_type={}", - npad_id, camera_handle.npad_id, camera_handle.npad_type); + LOG_INFO(Service_IRS, "called, npad_id={}, camera_npad_id={}, camera_npad_type={}", npad_id, + camera_handle.npad_id, camera_handle.npad_type); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -226,8 +326,8 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto processor_config{rp.PopRaw<PackedPointingProcessorConfig>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<Core::IrSensor::PackedPointingProcessorConfig>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING( @@ -236,14 +336,23 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major, processor_config.required_mcu_version.minor, applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle); + MakeProcessor<PointingProcessor>(camera_handle, device); + auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle); + image_transfer_processor.SetConfig(processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -256,14 +365,20 @@ void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Suspend image processor + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto mcu_version{rp.PopRaw<PackedMcuVersion>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto mcu_version{rp.PopRaw<Core::IrSensor::PackedMcuVersion>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING( @@ -272,37 +387,45 @@ void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major, mcu_version.minor); + auto result = IsIrCameraHandleValid(camera_handle); + if (result.IsSuccess()) { + // TODO: Check firmware version + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - struct Parameters { - IrCameraHandle camera_handle; - PackedFunctionLevel function_level; - u64 applet_resource_user_id; - }; - static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); - - const auto parameters{rp.PopRaw<Parameters>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto function_level{rp.PopRaw<Core::IrSensor::PackedFunctionLevel>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.applet_resource_user_id); + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, function_level={}, applet_resource_user_id={}", + camera_handle.npad_type, camera_handle.npad_id, function_level.function_level, + applet_resource_user_id); + + auto result = IsIrCameraHandleValid(camera_handle); + if (result.IsSuccess()) { + // TODO: Set Function level + result = ResultSuccess; + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedImageTransferProcessorExConfig processor_config; + Core::IrSensor::PackedImageTransferProcessorExConfig processor_config; u64 transfer_memory_size; }; static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); @@ -313,20 +436,33 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { auto t_mem = system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " - "applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.transfer_memory_size, parameters.applet_resource_user_id); + u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress()); + + LOG_INFO(Service_IRS, + "called, npad_type={}, npad_id={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, parameters.applet_resource_user_id); + + auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<ImageTransferProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto processor_config{rp.PopRaw<PackedIrLedProcessorConfig>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<Core::IrSensor::PackedIrLedProcessorConfig>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING(Service_IRS, @@ -336,14 +472,23 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { processor_config.required_mcu_version.major, processor_config.required_mcu_version.minor, applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle); + MakeProcessor<IrLedProcessor>(camera_handle, device); + auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle); + image_transfer_processor.SetConfig(processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -356,14 +501,20 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Stop image processor async + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - PackedFunctionLevel function_level; + Core::IrSensor::PackedFunctionLevel function_level; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -378,7 +529,22 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } -IRS::~IRS() = default; +Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const { + if (camera_handle.npad_id > + static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) { + return InvalidIrCameraHandle; + } + if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) { + return InvalidIrCameraHandle; + } + return ResultSuccess; +} + +Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry( + const Core::IrSensor::IrCameraHandle& camera_handle) { + ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id"); + return shared_memory->device[camera_handle.npad_id]; +} IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { // clang-format off @@ -395,4 +561,4 @@ IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { IRS_SYS::~IRS_SYS() = default; -} // namespace Service::HID +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 361dc2213..2e6115c73 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -4,13 +4,19 @@ #pragma once #include "core/hid/hid_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" #include "core/hle/service/service.h" namespace Core { class System; } -namespace Service::HID { +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::IRS { class IRS final : public ServiceFramework<IRS> { public: @@ -18,234 +24,19 @@ public: ~IRS() override; private: - // This is nn::irsensor::IrCameraStatus - enum IrCameraStatus : u32 { - Available, - Unsupported, - Unconnected, - }; - - // This is nn::irsensor::IrCameraInternalStatus - enum IrCameraInternalStatus : u32 { - Stopped, - FirmwareUpdateNeeded, - Unkown2, - Unkown3, - Unkown4, - FirmwareVersionRequested, - FirmwareVersionIsInvalid, - Ready, - Setting, - }; - - // This is nn::irsensor::detail::StatusManager::IrSensorMode - enum IrSensorMode : u64 { - None, - MomentProcessor, - ClusteringProcessor, - ImageTransferProcessor, - PointingProcessorMarker, - TeraPluginProcessor, - IrLedProcessor, - }; - - // This is nn::irsensor::ImageProcessorStatus - enum ImageProcessorStatus : u8 { - stopped, - running, - }; - - // This is nn::irsensor::ImageTransferProcessorFormat - enum ImageTransferProcessorFormat : u8 { - Size320x240, - Size160x120, - Size80x60, - Size40x30, - Size20x15, - }; - - // This is nn::irsensor::AdaptiveClusteringMode - enum AdaptiveClusteringMode : u8 { - StaticFov, - DynamicFov, - }; - - // This is nn::irsensor::AdaptiveClusteringTargetDistance - enum AdaptiveClusteringTargetDistance : u8 { - Near, - Middle, - Far, - }; - - // This is nn::irsensor::IrsHandAnalysisMode - enum IrsHandAnalysisMode : u8 { - Silhouette, - Image, - SilhoueteAndImage, - SilhuetteOnly, - }; - - // This is nn::irsensor::IrSensorFunctionLevel - enum IrSensorFunctionLevel : u8 { - unknown0, - unknown1, - unknown2, - unknown3, - unknown4, - }; - - // This is nn::irsensor::IrCameraHandle - struct IrCameraHandle { - u8 npad_id{}; - Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); - - struct IrsRect { - s16 x; - s16 y; - s16 width; - s16 height; + // This is nn::irsensor::detail::AruidFormat + struct AruidFormat { + u64 sensor_aruid; + u64 sensor_aruid_status; }; + static_assert(sizeof(AruidFormat) == 0x10, "AruidFormat is an invalid size"); - // This is nn::irsensor::PackedMcuVersion - struct PackedMcuVersion { - u16 major; - u16 minor; + // This is nn::irsensor::detail::StatusManager + struct StatusManager { + std::array<Core::IrSensor::DeviceFormat, 9> device; + std::array<AruidFormat, 5> aruid; }; - static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); - - // This is nn::irsensor::MomentProcessorConfig - struct MomentProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(7); - IrsRect window_of_interest; - u8 preprocess; - u8 preprocess_intensity_threshold; - INSERT_PADDING_BYTES(5); - }; - static_assert(sizeof(MomentProcessorConfig) == 0x28, - "MomentProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedMomentProcessorConfig - struct PackedMomentProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - u8 preprocess; - u8 preprocess_intensity_threshold; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, - "PackedMomentProcessorConfig is an invalid size"); - - // This is nn::irsensor::ClusteringProcessorConfig - struct ClusteringProcessorConfig { - u64 exposire_time; - u32 light_target; - u32 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(7); - IrsRect window_of_interest; - u32 pixel_count_min; - u32 pixel_count_max; - u32 object_intensity_min; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(ClusteringProcessorConfig) == 0x30, - "ClusteringProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedClusteringProcessorConfig - struct PackedClusteringProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - u32 pixel_count_min; - u32 pixel_count_max; - u32 object_intensity_min; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30, - "PackedClusteringProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedImageTransferProcessorConfig - struct PackedImageTransferProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - PackedMcuVersion required_mcu_version; - u8 format; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, - "PackedImageTransferProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedTeraPluginProcessorConfig - struct PackedTeraPluginProcessorConfig { - PackedMcuVersion required_mcu_version; - u8 mode; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, - "PackedTeraPluginProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedPointingProcessorConfig - struct PackedPointingProcessorConfig { - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - }; - static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, - "PackedPointingProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedFunctionLevel - struct PackedFunctionLevel { - IrSensorFunctionLevel function_level; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); - - // This is nn::irsensor::PackedImageTransferProcessorExConfig - struct PackedImageTransferProcessorExConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - PackedMcuVersion required_mcu_version; - ImageTransferProcessorFormat origin_format; - ImageTransferProcessorFormat trimming_format; - u16 trimming_start_x; - u16 trimming_start_y; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, - "PackedImageTransferProcessorExConfig is an invalid size"); - - // This is nn::irsensor::PackedIrLedProcessorConfig - struct PackedIrLedProcessorConfig { - PackedMcuVersion required_mcu_version; - u8 light_target; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, - "PackedIrLedProcessorConfig is an invalid size"); + static_assert(sizeof(StatusManager) == 0x8000, "StatusManager is an invalid size"); void ActivateIrsensor(Kernel::HLERequestContext& ctx); void DeactivateIrsensor(Kernel::HLERequestContext& ctx); @@ -265,6 +56,56 @@ private: void RunIrLedProcessor(Kernel::HLERequestContext& ctx); void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); + + Result IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const; + Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry( + const Core::IrSensor::IrCameraHandle& camera_handle); + + template <typename T> + void MakeProcessor(const Core::IrSensor::IrCameraHandle& handle, + Core::IrSensor::DeviceFormat& device_state) { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return; + } + processors[index] = std::make_unique<T>(device_state); + } + + template <typename T> + void MakeProcessorWithCoreContext(const Core::IrSensor::IrCameraHandle& handle, + Core::IrSensor::DeviceFormat& device_state) { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return; + } + processors[index] = std::make_unique<T>(system.HIDCore(), device_state, index); + } + + template <typename T> + T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return static_cast<T&>(*processors[0]); + } + return static_cast<T&>(*processors[index]); + } + + template <typename T> + const T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) const { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return static_cast<T&>(*processors[0]); + } + return static_cast<T&>(*processors[index]); + } + + Core::HID::EmulatedController* npad_device = nullptr; + StatusManager* shared_memory = nullptr; + std::array<std::unique_ptr<ProcessorBase>, 9> processors{}; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { @@ -273,4 +114,4 @@ public: ~IRS_SYS() override; }; -} // namespace Service::HID +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp new file mode 100644 index 000000000..6479af212 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/clustering_processor.h" + +namespace Service::IRS { +ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +ClusteringProcessor::~ClusteringProcessor() = default; + +void ClusteringProcessor::StartProcessor() {} + +void ClusteringProcessor::SuspendProcessor() {} + +void ClusteringProcessor::StopProcessor() {} + +void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.pixel_count_min = config.pixel_count_min; + current_config.pixel_count_max = config.pixel_count_max; + current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled; + current_config.object_intensity_min = config.object_intensity_min; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h new file mode 100644 index 000000000..6e2ba8846 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/clustering_processor.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class ClusteringProcessor final : public ProcessorBase { +public: + explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format); + ~ClusteringProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config); + +private: + // This is nn::irsensor::ClusteringProcessorConfig + struct ClusteringProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::IrsRect window_of_interest; + u32 pixel_count_min; + u32 pixel_count_max; + u32 object_intensity_min; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ClusteringProcessorConfig) == 0x30, + "ClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::AdaptiveClusteringProcessorConfig + struct AdaptiveClusteringProcessorConfig { + Core::IrSensor::AdaptiveClusteringMode mode; + Core::IrSensor::AdaptiveClusteringTargetDistance target_distance; + }; + static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8, + "AdaptiveClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::ClusteringData + struct ClusteringData { + f32 average_intensity; + Core::IrSensor::IrsCentroid centroid; + u32 pixel_count; + Core::IrSensor::IrsRect bound; + }; + static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size"); + + // This is nn::irsensor::ClusteringProcessorState + struct ClusteringProcessorState { + s64 sampling_number; + u64 timestamp; + u8 object_count; + INSERT_PADDING_BYTES(3); + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + std::array<ClusteringData, 0x10> data; + }; + static_assert(sizeof(ClusteringProcessorState) == 0x198, + "ClusteringProcessorState is an invalid size"); + + ClusteringProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp new file mode 100644 index 000000000..98f0c579d --- /dev/null +++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/irsensor/image_transfer_processor.h" + +namespace Service::IRS { +ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_, + Core::IrSensor::DeviceFormat& device_format, + std::size_t npad_index) + : device{device_format} { + npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index); + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); }, + .is_npad_service = true, + }; + callback_key = npad_device->SetCallback(engine_callback); + + device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +ImageTransferProcessor::~ImageTransferProcessor() { + npad_device->DeleteCallback(callback_key); +}; + +void ImageTransferProcessor::StartProcessor() { + is_active = true; + device.camera_status = Core::IrSensor::IrCameraStatus::Available; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready; + processor_state.sampling_number = 0; + processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; +} + +void ImageTransferProcessor::SuspendProcessor() {} + +void ImageTransferProcessor::StopProcessor() {} + +void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) { + if (type != Core::HID::ControllerTriggerType::IrSensor) { + return; + } + if (!is_transfer_memory_set) { + return; + } + + const auto camera_data = npad_device->GetCamera(); + + // This indicates how much ambient light is precent + processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; + processor_state.sampling_number = camera_data.sample; + + if (camera_data.format != current_config.origin_format) { + LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format, + current_config.origin_format); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + if (current_config.origin_format > current_config.trimming_format) { + LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}", + current_config.origin_format, current_config.trimming_format); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + std::vector<u8> window_data{}; + const auto origin_width = GetDataWidth(current_config.origin_format); + const auto origin_height = GetDataHeight(current_config.origin_format); + const auto trimming_width = GetDataWidth(current_config.trimming_format); + const auto trimming_height = GetDataHeight(current_config.trimming_format); + window_data.resize(GetDataSize(current_config.trimming_format)); + + if (trimming_width + current_config.trimming_start_x > origin_width || + trimming_height + current_config.trimming_start_y > origin_height) { + LOG_WARNING(Service_IRS, + "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})", + current_config.trimming_start_x, current_config.trimming_start_y, + trimming_width, trimming_height, origin_width, origin_height); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + for (std::size_t y = 0; y < trimming_height; y++) { + for (std::size_t x = 0; x < trimming_width; x++) { + const std::size_t window_index = (y * trimming_width) + x; + const std::size_t origin_index = + ((y + current_config.trimming_start_y) * origin_width) + x + + current_config.trimming_start_x; + window_data[window_index] = camera_data.data[origin_index]; + } + } + + memcpy(transfer_memory, window_data.data(), GetDataSize(current_config.trimming_format)); + + if (!IsProcessorActive()) { + StartProcessor(); + } +} + +void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.origin_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format); + current_config.trimming_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format); + current_config.trimming_start_x = 0; + current_config.trimming_start_y = 0; + + npad_device->SetCameraFormat(current_config.origin_format); +} + +void ImageTransferProcessor::SetConfig( + Core::IrSensor::PackedImageTransferProcessorExConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.origin_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.origin_format); + current_config.trimming_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.trimming_format); + current_config.trimming_start_x = config.trimming_start_x; + current_config.trimming_start_y = config.trimming_start_y; + + npad_device->SetCameraFormat(current_config.origin_format); +} + +void ImageTransferProcessor::SetTransferMemoryPointer(u8* t_mem) { + is_transfer_memory_set = true; + transfer_memory = t_mem; +} + +Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState( + std::vector<u8>& data) const { + const auto size = GetDataSize(current_config.trimming_format); + data.resize(size); + memcpy(data.data(), transfer_memory, size); + return processor_state; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h new file mode 100644 index 000000000..393df492d --- /dev/null +++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::IRS { +class ImageTransferProcessor final : public ProcessorBase { +public: + explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_, + Core::IrSensor::DeviceFormat& device_format, + std::size_t npad_index); + ~ImageTransferProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config); + void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config); + + // Transfer memory where the image data will be stored + void SetTransferMemoryPointer(u8* t_mem); + + Core::IrSensor::ImageTransferProcessorState GetState(std::vector<u8>& data) const; + +private: + // This is nn::irsensor::ImageTransferProcessorConfig + struct ImageTransferProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::ImageTransferProcessorFormat format; + }; + static_assert(sizeof(ImageTransferProcessorConfig) == 0x20, + "ImageTransferProcessorConfig is an invalid size"); + + // This is nn::irsensor::ImageTransferProcessorExConfig + struct ImageTransferProcessorExConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::ImageTransferProcessorFormat origin_format; + Core::IrSensor::ImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28, + "ImageTransferProcessorExConfig is an invalid size"); + + void OnControllerUpdate(Core::HID::ControllerTriggerType type); + + ImageTransferProcessorExConfig current_config{}; + Core::IrSensor::ImageTransferProcessorState processor_state{}; + Core::IrSensor::DeviceFormat& device; + Core::HID::EmulatedController* npad_device; + int callback_key{}; + + u8* transfer_memory = nullptr; + bool is_transfer_memory_set = false; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp new file mode 100644 index 000000000..8e6dd99e4 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/ir_led_processor.h" + +namespace Service::IRS { +IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +IrLedProcessor::~IrLedProcessor() = default; + +void IrLedProcessor::StartProcessor() {} + +void IrLedProcessor::SuspendProcessor() {} + +void IrLedProcessor::StopProcessor() {} + +void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) { + current_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.light_target); +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h new file mode 100644 index 000000000..c3d8693c9 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class IrLedProcessor final : public ProcessorBase { +public: + explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format); + ~IrLedProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config); + +private: + // This is nn::irsensor::IrLedProcessorConfig + struct IrLedProcessorConfig { + Core::IrSensor::CameraLightTarget light_target; + }; + static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size"); + + struct IrLedProcessorState { + s64 sampling_number; + u64 timestamp; + std::array<u8, 0x8> data; + }; + static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size"); + + IrLedProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp new file mode 100644 index 000000000..dbaca420a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/moment_processor.h" + +namespace Service::IRS { +MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::MomentProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +MomentProcessor::~MomentProcessor() = default; + +void MomentProcessor::StartProcessor() {} + +void MomentProcessor::SuspendProcessor() {} + +void MomentProcessor::StopProcessor() {} + +void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.window_of_interest = config.window_of_interest; + current_config.preprocess = + static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess); + current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h new file mode 100644 index 000000000..d4bd22e0f --- /dev/null +++ b/src/core/hle/service/hid/irsensor/moment_processor.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class MomentProcessor final : public ProcessorBase { +public: + explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format); + ~MomentProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config); + +private: + // This is nn::irsensor::MomentProcessorConfig + struct MomentProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::IrsRect window_of_interest; + Core::IrSensor::MomentProcessorPreprocess preprocess; + u32 preprocess_intensity_threshold; + }; + static_assert(sizeof(MomentProcessorConfig) == 0x28, + "MomentProcessorConfig is an invalid size"); + + // This is nn::irsensor::MomentStatistic + struct MomentStatistic { + f32 average_intensity; + Core::IrSensor::IrsCentroid centroid; + }; + static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size"); + + // This is nn::irsensor::MomentProcessorState + struct MomentProcessorState { + s64 sampling_number; + u64 timestamp; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + INSERT_PADDING_BYTES(4); + std::array<MomentStatistic, 0x30> stadistic; + }; + static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size"); + + MomentProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp new file mode 100644 index 000000000..929f177fc --- /dev/null +++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/pointing_processor.h" + +namespace Service::IRS { +PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +PointingProcessor::~PointingProcessor() = default; + +void PointingProcessor::StartProcessor() {} + +void PointingProcessor::SuspendProcessor() {} + +void PointingProcessor::StopProcessor() {} + +void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) { + current_config.window_of_interest = config.window_of_interest; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h new file mode 100644 index 000000000..cf4930794 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/pointing_processor.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class PointingProcessor final : public ProcessorBase { +public: + explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format); + ~PointingProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config); + +private: + // This is nn::irsensor::PointingProcessorConfig + struct PointingProcessorConfig { + Core::IrSensor::IrsRect window_of_interest; + }; + static_assert(sizeof(PointingProcessorConfig) == 0x8, + "PointingProcessorConfig is an invalid size"); + + struct PointingProcessorMarkerData { + u8 pointing_status; + INSERT_PADDING_BYTES(3); + u32 unknown; + float unkown_float1; + float position_x; + float position_y; + float unkown_float2; + Core::IrSensor::IrsRect window_of_interest; + }; + static_assert(sizeof(PointingProcessorMarkerData) == 0x20, + "PointingProcessorMarkerData is an invalid size"); + + struct PointingProcessorMarkerState { + s64 sampling_number; + u64 timestamp; + std::array<PointingProcessorMarkerData, 0x3> data; + }; + static_assert(sizeof(PointingProcessorMarkerState) == 0x70, + "PointingProcessorMarkerState is an invalid size"); + + PointingProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp new file mode 100644 index 000000000..4d43ca17a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/processor_base.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { + +ProcessorBase::ProcessorBase() {} +ProcessorBase::~ProcessorBase() = default; + +bool ProcessorBase::IsProcessorActive() const { + return is_active; +} + +std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 320 * 240; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 160 * 120; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 80 * 60; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 40 * 30; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 20 * 15; + default: + return 0; + } +} + +std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 320; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 160; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 80; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 40; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 20; + default: + return 0; + } +} + +std::size_t ProcessorBase::GetDataHeight( + Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 240; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 120; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 60; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 30; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 15; + default: + return 0; + } +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h new file mode 100644 index 000000000..bc0d2977b --- /dev/null +++ b/src/core/hle/service/hid/irsensor/processor_base.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" + +namespace Service::IRS { +class ProcessorBase { +public: + explicit ProcessorBase(); + virtual ~ProcessorBase(); + + virtual void StartProcessor() = 0; + virtual void SuspendProcessor() = 0; + virtual void StopProcessor() = 0; + + bool IsProcessorActive() const; + +protected: + /// Returns the number of bytes the image uses + std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const; + + /// Returns the width of the image + std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const; + + /// Returns the height of the image + std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const; + + bool is_active{false}; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp new file mode 100644 index 000000000..e691c840a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/tera_plugin_processor.h" + +namespace Service::IRS { +TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +TeraPluginProcessor::~TeraPluginProcessor() = default; + +void TeraPluginProcessor::StartProcessor() {} + +void TeraPluginProcessor::SuspendProcessor() {} + +void TeraPluginProcessor::StopProcessor() {} + +void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) { + current_config.mode = config.mode; + current_config.unknown_1 = config.unknown_1; + current_config.unknown_2 = config.unknown_2; + current_config.unknown_3 = config.unknown_3; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h new file mode 100644 index 000000000..bbea7ed0b --- /dev/null +++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class TeraPluginProcessor final : public ProcessorBase { +public: + explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format); + ~TeraPluginProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config); + +private: + // This is nn::irsensor::TeraPluginProcessorConfig + struct TeraPluginProcessorConfig { + u8 mode; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; + }; + static_assert(sizeof(TeraPluginProcessorConfig) == 0x4, + "TeraPluginProcessorConfig is an invalid size"); + + struct TeraPluginProcessorState { + s64 sampling_number; + u64 timestamp; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + std::array<u8, 0x12c> data; + }; + static_assert(sizeof(TeraPluginProcessorState) == 0x140, + "TeraPluginProcessorState is an invalid size"); + + TeraPluginProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 7055ea93e..2889973e4 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -18,8 +18,8 @@ namespace { } // Anonymous namespace -#include "core/network/network.h" -#include "core/network/network_interface.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" namespace Service::NIFM { diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 3e9dc4a13..c7194731e 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -13,8 +13,8 @@ #include "core/hle/kernel/k_thread.h" #include "core/hle/service/sockets/bsd.h" #include "core/hle/service/sockets/sockets_translate.h" -#include "core/network/network.h" -#include "core/network/sockets.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" namespace Service::Sockets { diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index fed740d87..9ea36428d 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -16,7 +16,7 @@ class System; namespace Network { class Socket; -} +} // namespace Network namespace Service::Sockets { diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index 9c0936d97..2db10ec81 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -7,7 +7,7 @@ #include "common/common_types.h" #include "core/hle/service/sockets/sockets.h" #include "core/hle/service/sockets/sockets_translate.h" -#include "core/network/network.h" +#include "core/internal_network/network.h" namespace Service::Sockets { diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h index 5e9809add..c93291d3e 100644 --- a/src/core/hle/service/sockets/sockets_translate.h +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -7,7 +7,7 @@ #include "common/common_types.h" #include "core/hle/service/sockets/sockets.h" -#include "core/network/network.h" +#include "core/internal_network/network.h" namespace Service::Sockets { diff --git a/src/core/network/network.cpp b/src/core/internal_network/network.cpp index fdafbea92..36c43cc8f 100644 --- a/src/core/network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -29,9 +29,9 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/settings.h" -#include "core/network/network.h" -#include "core/network/network_interface.h" -#include "core/network/sockets.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" +#include "core/internal_network/sockets.h" namespace Network { diff --git a/src/core/network/network.h b/src/core/internal_network/network.h index 10e5ef10d..10e5ef10d 100644 --- a/src/core/network/network.h +++ b/src/core/internal_network/network.h diff --git a/src/core/network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 15ecc6abf..0f0a66160 100644 --- a/src/core/network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -11,7 +11,7 @@ #include "common/logging/log.h" #include "common/settings.h" #include "common/string_util.h" -#include "core/network/network_interface.h" +#include "core/internal_network/network_interface.h" #ifdef _WIN32 #include <iphlpapi.h> diff --git a/src/core/network/network_interface.h b/src/core/internal_network/network_interface.h index 9b98b6b42..9b98b6b42 100644 --- a/src/core/network/network_interface.h +++ b/src/core/internal_network/network_interface.h diff --git a/src/core/network/sockets.h b/src/core/internal_network/sockets.h index f889159f5..77e27e928 100644 --- a/src/core/network/sockets.h +++ b/src/core/internal_network/sockets.h @@ -3,6 +3,7 @@ #pragma once +#include <map> #include <memory> #include <utility> @@ -12,7 +13,7 @@ #endif #include "common/common_types.h" -#include "core/network/network.h" +#include "core/internal_network/network.h" // TODO: C++20 Replace std::vector usages with std::span diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 635449fce..1b44280b5 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <cstring> diff --git a/src/core/memory.h b/src/core/memory.h index 780c45385..2a21fbcfd 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 6ef459b7a..f09c176f8 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <chrono> diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 816202588..dd6becc02 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 654db0b52..abcf6eb11 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <array> diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 6f3d45bea..887dc98f3 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once |